Browse Source

Merge branch 'master' into ctk-dicom-model-object

Alireza Mehrtash 11 years ago
parent
commit
b2e887f6aa

+ 1 - 1
CMakeExternals/PythonQt.cmake

@@ -53,7 +53,7 @@ if(${add_project})
         message(FATAL_ERROR "error: Python is required to build ${PROJECT_NAME}")
       endif()
 
-      set(revision_tag 9c92fd212605bb5ff4d462323763acf65d87e4a7)
+      set(revision_tag e1f1c77d9675c3c5fb1cba19d2a32ace483eda2c)
       if(${proj}_REVISION_TAG)
         set(revision_tag ${${proj}_REVISION_TAG})
       endif()

+ 36 - 14
CMakeLists.txt

@@ -876,31 +876,53 @@ foreach(_external_target ${EXTERNAL_TARGETS})
 endforeach()
 
 foreach(_external_target ${EXTERNAL_TARGETS})
+  set(_package_name ${${_external_target}_FIND_PACKAGE_CMD})
+  if("${_package_name}" STREQUAL "")
+    set(_package_name ${_external_target})
+  endif()
   if(${_external_target}_INCLUDE_DIRS)
-    #message("[${_external_target}] Resolving include variables: ${${_external_target}_INCLUDE_DIRS}")
+    #message("[${_package_name}] Resolving include variables: ${${_external_target}_INCLUDE_DIRS}")
+    set(_updated_include_dirs)
     foreach(_include_variable ${${_external_target}_INCLUDE_DIRS})
-      #message("[${_external_target}] Appending ${${_include_variable}}")
+      set(_expanded_include_variable ${_include_variable})
       if(${_include_variable})
-        list(APPEND ${_external_target}_INCLUDE_DIRS ${${_include_variable}})
-      else()
-        list(APPEND ${_external_target}_INCLUDE_DIRS ${_include_variable})
+        #message("[${_package_name}] Expanding [${_include_variable}] into [${${_include_variable}}]")
+        set(_expanded_include_variable ${${_include_variable}})
       endif()
-      #message("[${_external_target}] New dirs: ${${_external_target}_INCLUDE_DIRS}")
+      foreach(_include_dir ${_expanded_include_variable})
+        if(EXISTS ${_include_dir})
+          #message("[${_package_name}] Appending ${_include_dir}")
+          list(APPEND _updated_include_dirs ${_include_dir})
+        else()
+          message(STATUS "[${_package_name}] Skipping nonexistent include directory or not set variable [${_include_dir}]")
+        endif()
+      endforeach()
+      #message("[${_package_name}] New dirs: ${_updated_include_dirs}")
     endforeach()
-    #message("[${_external_target}] Appended dirs: ${${_external_target}_INCLUDE_DIRS}")
+    set(${_external_target}_INCLUDE_DIRS ${_updated_include_dirs})
+    #message("[${_package_name}] Appended dirs: ${${_external_target}_INCLUDE_DIRS}")
   endif()
   if(${_external_target}_LIBRARY_DIRS)
-    #message("[${_external_target}] Resolving library variables: ${${_external_target}_LIBRARY_DIRS}")
+    #message("[${_package_name}] Resolving library variables: ${${_external_target}_LIBRARY_DIRS}")
+    set(_updated_library_dirs)
     foreach(_library_variable ${${_external_target}_LIBRARY_DIRS})
-      #message("[${_external_target}] Appending ${${_library_variable}}")
+      set(_expanded_library_variable ${_library_variable})
       if(${_library_variable})
-        list(APPEND ${_external_target}_LIBRARY_DIRS ${${_library_variable}})
-      else()
-        list(APPEND ${_external_target}_LIBRARY_DIRS ${_library_variable})
+        #message("[${_package_name}] Expanding [${_library_variable}] into [${${_library_variable}}]")
+        set(_expanded_library_variable ${${_library_variable}})
       endif()
-      #message("[${_external_target}] New dirs: ${${_external_target}_LIBRARY_DIRS}")
+      foreach(_library_dir ${_expanded_library_variable})
+        if(EXISTS ${_library_dir})
+          #message("[${_package_name}] Appending ${_library_dir}")
+          list(APPEND _updated_library_dirs ${_library_dir})
+        else()
+          message(STATUS "[${_package_name}] Skipping nonexistent library directory or not set variable [${_library_dir}]")
+        endif()
+      endforeach()
+      #message("[${_package_name}] New dirs: ${_updated_library_dirs}")
     endforeach()
-    #message("[${_external_target}] Appended dirs: ${${_external_target}_LIBRARY_DIRS}")
+    set(${_external_target}_LIBRARY_DIRS ${_updated_library_dirs})
+    #message("[${_package_name}] Appended dirs: ${${_external_target}_LIBRARY_DIRS}")
   endif()
 endforeach()
 

+ 13 - 0
Libs/Core/ctkCorePythonQtDecorators.h

@@ -25,6 +25,7 @@
 #include <PythonQt.h>
 
 // CTK includes
+#include <ctkErrorLogModel.h>
 #include <ctkWorkflowStep.h>
 #include <ctkWorkflowTransitions.h>
 
@@ -160,11 +161,23 @@ public Q_SLOTS:
     {
     delete transition;
     }
+
+  // ctkErrorLogLevel
+
+  QString static_ctkErrorLogLevel_logLevelAsString(ctkErrorLogLevel::LogLevel logLevel)
+    {
+    return ctkErrorLogLevel::logLevelAsString(logLevel);
+    }
 };
 
 //-----------------------------------------------------------------------------
 void initCTKCorePythonQtDecorators()
 {
+  // HACK: Since the CMake based light wrapping only consider class name matching the
+  //       filename where the class is defined, let's explicitly register ctkErrorLogLevel
+  //       so that the log level QFlags are exposed to python.
+  PythonQt::self()->registerClass(&ctkErrorLogLevel::staticMetaObject, "CTKCore");
+
   PythonQt::self()->addDecorators(new ctkCorePythonQtDecorators);
 }
 

+ 4 - 3
Libs/Core/ctkErrorLogModel.cpp

@@ -55,13 +55,13 @@ ctkErrorLogLevel::ctkErrorLogLevel()
 // --------------------------------------------------------------------------
 QString ctkErrorLogLevel::operator()(ctkErrorLogLevel::LogLevel logLevel)
 {
-  return this->logLevelAsString(logLevel);
+  return ctkErrorLogLevel::logLevelAsString(logLevel);
 }
 
 // --------------------------------------------------------------------------
-QString ctkErrorLogLevel::logLevelAsString(ctkErrorLogLevel::LogLevel logLevel)const
+QString ctkErrorLogLevel::logLevelAsString(ctkErrorLogLevel::LogLevel logLevel)
 {
-  QMetaEnum logLevelEnum = this->metaObject()->enumerator(0);
+  QMetaEnum logLevelEnum = ctkErrorLogLevel::staticMetaObject.enumerator(0);
   Q_ASSERT(QString("LogLevel").compare(logLevelEnum.name()) == 0);
   return QLatin1String(logLevelEnum.valueToKey(logLevel));
 }
@@ -217,6 +217,7 @@ public:
 ctkErrorLogModelPrivate::ctkErrorLogModelPrivate(ctkErrorLogModel& object)
   : q_ptr(&object)
 {
+  this->StandardItemModel.setColumnCount(ctkErrorLogModel::MaxColumn);
   this->LogEntryGrouping = false;
   this->AsynchronousLogging = true;
   this->AddingEntry = false;

+ 1 - 1
Libs/Core/ctkErrorLogModel.h

@@ -54,7 +54,7 @@ public:
 
   QString operator ()(LogLevel logLevel);
 
-  QString logLevelAsString(ctkErrorLogLevel::LogLevel logLevel)const;
+  static QString logLevelAsString(ctkErrorLogLevel::LogLevel logLevel);
 };
 Q_DECLARE_OPERATORS_FOR_FLAGS(ctkErrorLogLevel::LogLevels)
 

+ 2 - 0
Libs/DICOM/Core/CMakeLists.txt

@@ -50,6 +50,8 @@ set(KIT_SRCS
   ctkDICOMRetrieve.h
   ctkDICOMTester.cpp
   ctkDICOMTester.h
+  ctkDICOMUtil.cpp
+  ctkDICOMUtil.h
 )
 
 if(DCMTK_VERSION_IS_360)

+ 24 - 0
Libs/DICOM/Core/ctkDICOMCorePythonQtDecorators.h

@@ -25,6 +25,7 @@
 #include <PythonQt.h>
 
 // CTK includes
+#include <ctkDICOMUtil.h>
 
 // NOTE:
 //
@@ -52,9 +53,32 @@ public Q_SLOTS:
 };
 
 //-----------------------------------------------------------------------------
+class PythonQtWrapper_CTKDICOMCore : public QObject
+{
+  Q_OBJECT
+
+public Q_SLOTS:
+  ctkErrorLogLevel::LogLevel static_ctk_dicomLogLevel()
+    {
+    return ctk::dicomLogLevel();
+    }
+
+  void static_ctk_setDICOMLogLevel(ctkErrorLogLevel::LogLevel level)
+    {
+    ctk::setDICOMLogLevel(level);
+    }
+
+  QString static_ctk_dicomLogLevelAsString()
+    {
+    return ctk::dicomLogLevelAsString();
+    }
+};
+
+//-----------------------------------------------------------------------------
 void initCTKDICOMCorePythonQtDecorators()
 {
   PythonQt::self()->addDecorators(new ctkDICOMCorePythonQtDecorators);
+  PythonQt::self()->registerCPPClass("ctk", "", "CTKDICOMCore", PythonQtCreateObject<PythonQtWrapper_CTKDICOMCore>);
 }
 
 #endif

+ 69 - 0
Libs/DICOM/Core/ctkDICOMUtil.cpp

@@ -0,0 +1,69 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Brigham & Women's Hospital
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QDebug>
+
+// CTK includes
+#include "ctkDICOMUtil.h"
+
+// DCMTK includes
+#include <dcmtk/dcmnet/diutil.h>
+
+//------------------------------------------------------------------------------
+void ctk::setDICOMLogLevel(ctkErrorLogLevel::LogLevel level)
+{
+  dcmtk::log4cplus::Logger log = dcmtk::log4cplus::Logger::getRoot();
+  switch (level)
+    {
+    case ctkErrorLogLevel::Trace: log.setLogLevel(OFLogger::TRACE_LOG_LEVEL); break;
+    case ctkErrorLogLevel::Debug: log.setLogLevel(OFLogger::DEBUG_LOG_LEVEL); break;
+    case ctkErrorLogLevel::Info: log.setLogLevel(OFLogger::INFO_LOG_LEVEL); break;
+    case ctkErrorLogLevel::Warning: log.setLogLevel(OFLogger::WARN_LOG_LEVEL); break;
+    case ctkErrorLogLevel::Error: log.setLogLevel(OFLogger::ERROR_LOG_LEVEL); break;
+    case ctkErrorLogLevel::Fatal: log.setLogLevel(OFLogger::FATAL_LOG_LEVEL); break;
+    default:
+      qWarning() << "Failed to set DICOM log level - Supported levels are Trace, Debug, "
+                    "Info, Warning, Error and Fatal !";
+      break;
+    }
+}
+
+//------------------------------------------------------------------------------
+ctkErrorLogLevel::LogLevel ctk::dicomLogLevel()
+{
+  dcmtk::log4cplus::Logger log = dcmtk::log4cplus::Logger::getRoot();
+  switch (log.getLogLevel())
+    {
+    case OFLogger::TRACE_LOG_LEVEL: return ctkErrorLogLevel::Trace;
+    case OFLogger::DEBUG_LOG_LEVEL: return ctkErrorLogLevel::Debug;
+    case OFLogger::INFO_LOG_LEVEL: return ctkErrorLogLevel::Info;
+    case OFLogger::WARN_LOG_LEVEL: return ctkErrorLogLevel::Warning;
+    case OFLogger::ERROR_LOG_LEVEL: return ctkErrorLogLevel::Error;
+    case OFLogger::FATAL_LOG_LEVEL: return ctkErrorLogLevel::Fatal;
+    default: return ctkErrorLogLevel::None;
+    }
+}
+
+//------------------------------------------------------------------------------
+QString ctk::dicomLogLevelAsString()
+{
+  return ctkErrorLogLevel::logLevelAsString(ctk::dicomLogLevel());
+}

+ 38 - 0
Libs/DICOM/Core/ctkDICOMUtil.h

@@ -0,0 +1,38 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Brigham & Women's Hospital
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+#ifndef __ctkDICOMUtil_h
+#define __ctkDICOMUtil_h
+
+// CTK includes
+#include "ctkDICOMCoreExport.h"
+#include "ctkErrorLogModel.h"
+
+namespace ctk {
+
+void CTK_DICOM_CORE_EXPORT setDICOMLogLevel(ctkErrorLogLevel::LogLevel level);
+
+ctkErrorLogLevel::LogLevel CTK_DICOM_CORE_EXPORT dicomLogLevel();
+
+QString CTK_DICOM_CORE_EXPORT dicomLogLevelAsString();
+
+} // end of ctk namespace
+
+#endif

+ 4 - 0
Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.cpp

@@ -47,6 +47,10 @@ QImage ctk::grabVTKWidget(QWidget* widget, QRect rectangle)
   painter.begin(&widgetImage);
   foreach(QVTKWidget* vtkWidget, widget->findChildren<QVTKWidget*>())
     {
+    if (!vtkWidget->isVisible())
+      {
+      continue;
+      }
     QRect subWidgetRect = QRect(vtkWidget->mapTo(widget, QPoint(0,0)), vtkWidget->size());
     if (!rectangle.intersects(subWidgetRect))
       {

+ 2 - 0
Libs/Widgets/CMakeLists.txt

@@ -57,6 +57,7 @@ set(KIT_SRCS
   ctkConsole_p.h
   ctkCoordinatesWidget.cpp
   ctkCoordinatesWidget.h
+  ctkCoordinatesWidget_p.h
   ctkCrosshairLabel.cpp
   ctkCrosshairLabel.h
   ctkDateRangeWidget.cpp
@@ -216,6 +217,7 @@ set(KIT_MOC_SRCS
   ctkConsole.h
   ctkConsole_p.h
   ctkCoordinatesWidget.h
+  ctkCoordinatesWidget_p.h
   ctkCrosshairLabel.h
   ctkDateRangeWidget.h
   ctkDirectoryButton.h

+ 58 - 40
Libs/Widgets/ctkConsole.cpp

@@ -157,9 +157,7 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
     // Set to true if there's a current selection
     const bool selection = text_cursor.anchor() != text_cursor.position();
     // Set to true if the cursor overlaps the history area
-    const bool history_area =
-      text_cursor.anchor() < this->InteractivePosition
-      || text_cursor.position() < this->InteractivePosition;
+    const bool history_area = this->isCursorInHistoryArea();
 
     // Allow copying anywhere in the console ...
     if(e->key() == Qt::Key_C && e->modifiers() == Qt::ControlModifier)
@@ -185,48 +183,11 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
       return;
       }
 
-    // Allow paste only if the selection is in the interactive area ...
-    if(e->key() == Qt::Key_V && e->modifiers() == Qt::ControlModifier)
-      {
-      if(!history_area)
-        {
-        const QMimeData* const clipboard = QApplication::clipboard()->mimeData();
-        const QString text = clipboard->text();
-        if(!text.isNull())
-          {
-          if (this->EditorHints & ctkConsole::SplitCopiedTextByLine)
-            {
-            QStringList lines = text.split(QRegExp("(?:\r\n|\r|\n)"));
-            for(int i=0; i < lines.count(); ++i)
-              {
-              this->switchToUserInputTextColor(&text_cursor);
-              text_cursor.insertText(lines.at(i));
-              this->updateCommandBuffer();
-              if (i < lines.count() - 1)
-                {
-                this->internalExecuteCommand();
-                }
-              }
-            }
-          else
-            {
-            this->switchToUserInputTextColor(&text_cursor);
-            text_cursor.insertText(text);
-            this->updateCommandBuffer();
-            }
-          }
-        }
-
-      e->accept();
-      return;
-      }
-
     // Force the cursor back to the interactive area
     if(history_area && e->key() != Qt::Key_Control)
       {
       text_cursor.setPosition(this->documentEnd());
       this->setTextCursor(text_cursor);
-
       }
 
     switch(e->key())
@@ -650,6 +611,63 @@ void ctkConsolePrivate::onScrollBarValueChanged(int value)
 }
 
 //-----------------------------------------------------------------------------
+bool ctkConsolePrivate::isCursorInHistoryArea()const
+{
+  return this->textCursor().anchor() < this->InteractivePosition
+    || this->textCursor().position() < this->InteractivePosition;
+}
+
+//-----------------------------------------------------------------------------
+void ctkConsolePrivate::insertFromMimeData(const QMimeData* source)
+{
+  if (this->isCursorInHistoryArea())
+    {
+    QTextCursor textCursor = this->textCursor();
+    textCursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
+    this->setTextCursor(textCursor);
+    }
+  const QString text = source->text();
+  if (!text.isEmpty())
+    {
+    this->pasteText(text);
+    }
+  else
+    {
+    this->Superclass::insertFromMimeData(source);
+    }
+}
+
+//-----------------------------------------------------------------------------
+void ctkConsolePrivate::pasteText(const QString& text)
+{
+  if(text.isNull())
+    {
+    return;
+    }
+  QTextCursor textCursor = this->textCursor();
+  if (this->EditorHints & ctkConsole::SplitCopiedTextByLine)
+    {
+    QStringList lines = text.split(QRegExp("(?:\r\n|\r|\n)"));
+    for(int i=0; i < lines.count(); ++i)
+      {
+      this->switchToUserInputTextColor(&textCursor);
+      textCursor.insertText(lines.at(i));
+      this->updateCommandBuffer();
+      if (i < lines.count() - 1)
+        {
+        this->internalExecuteCommand();
+        }
+      }
+    }
+  else
+    {
+    this->switchToUserInputTextColor(&textCursor);
+    textCursor.insertText(text);
+    this->updateCommandBuffer();
+    }
+}
+
+//-----------------------------------------------------------------------------
 // ctkConsole methods
 
 //-----------------------------------------------------------------------------

+ 11 - 0
Libs/Widgets/ctkConsole_p.h

@@ -121,6 +121,17 @@ public Q_SLOTS:
   /// Update the value of ScrollbarAtBottom given the current position of the scollbar
   void onScrollBarValueChanged(int value);
 
+protected:
+  /// Return true if the cursor position is in the history area
+  /// false if it is after the InteractivePosition.
+  bool isCursorInHistoryArea()const;
+
+  /// Reimplemented to make sure there is no text added into the
+  /// history logs.
+  virtual void insertFromMimeData(const QMimeData* source);
+
+  /// Paste text at the current text cursor position.
+  void pasteText(const QString& text);
 public:
 
   /// A custom completer

+ 340 - 281
Libs/Widgets/ctkCoordinatesWidget.cpp

@@ -24,7 +24,7 @@
 #include <QHBoxLayout>
 
 // CTK includes
-#include "ctkCoordinatesWidget.h"
+#include "ctkCoordinatesWidget_p.h"
 #include "ctkDoubleSpinBox.h"
 #include "ctkDoubleSpinBox_p.h"
 #include "ctkUtils.h"
@@ -34,8 +34,10 @@
 #include <cmath>
 #include <limits>
 
-//------------------------------------------------------------------------------
-ctkCoordinatesWidget::ctkCoordinatesWidget(QWidget* _parent) :QWidget(_parent)
+// --------------------------------------------------------------------------
+ctkCoordinatesWidgetPrivate
+::ctkCoordinatesWidgetPrivate(ctkCoordinatesWidget& object)
+  :q_ptr(&object)
 {
   this->Decimals = 3;
   ctkDoubleSpinBox temp;
@@ -48,24 +50,31 @@ ctkCoordinatesWidget::ctkCoordinatesWidget(QWidget* _parent) :QWidget(_parent)
   this->SizeHintPolicy = ctkDoubleSpinBox::SizeHintByValue;
   this->Coordinates = 0;
   this->ChangingDecimals = false;
-
-  QHBoxLayout* hboxLayout = new QHBoxLayout(this);
-  hboxLayout->setContentsMargins(0, 0, 0, 0);
-  this->setLayout(hboxLayout);
-
-  this->setDimension(3);
 }
 
-//------------------------------------------------------------------------------
-ctkCoordinatesWidget::~ctkCoordinatesWidget()
+// --------------------------------------------------------------------------
+ctkCoordinatesWidgetPrivate
+::~ctkCoordinatesWidgetPrivate()
 {
   delete [] this->Coordinates;
 }
 
+// --------------------------------------------------------------------------
+void ctkCoordinatesWidgetPrivate::init()
+{
+  Q_Q(ctkCoordinatesWidget);
+  QHBoxLayout* hboxLayout = new QHBoxLayout(q);
+  hboxLayout->setContentsMargins(0, 0, 0, 0);
+  q->setLayout(hboxLayout);
+
+  q->setDimension(3);
+}
+
 //------------------------------------------------------------------------------
-void ctkCoordinatesWidget::addSpinBox()
+void ctkCoordinatesWidgetPrivate::addSpinBox()
 {
-  ctkDoubleSpinBox* spinBox = new ctkDoubleSpinBox(this);
+  Q_Q(ctkCoordinatesWidget);
+  ctkDoubleSpinBox* spinBox = new ctkDoubleSpinBox(q);
   spinBox->setDecimals(this->Decimals);
   spinBox->setDecimalsOption(this->DecimalsOption);
   spinBox->setSingleStep(this->SingleStep);
@@ -74,293 +83,383 @@ void ctkCoordinatesWidget::addSpinBox()
   spinBox->setSizeHintPolicy(this->SizeHintPolicy);
   spinBox->setValueProxy(this->Proxy.data());
   connect( spinBox, SIGNAL(valueChanged(double)),
-           this, SLOT(updateCoordinate(double)));
+           q, SLOT(updateCoordinate(double)));
   // Same number of decimals within the spinboxes.
   connect( spinBox, SIGNAL(decimalsChanged(int)),
            this, SLOT(updateOtherDecimals(int)));
-  qobject_cast<QHBoxLayout*>(this->layout())->addWidget(spinBox, 1.);
+  qobject_cast<QHBoxLayout*>(q->layout())->addWidget(spinBox, 1.);
 }
 
+
+
 //------------------------------------------------------------------------------
-void ctkCoordinatesWidget::setDimension(int dim)
+void ctkCoordinatesWidgetPrivate::updateDecimals()
 {
-  if (dim < 1)
+  Q_Q(ctkCoordinatesWidget);
+
+  if (this->ChangingDecimals)
     {
     return;
     }
-  double* newPos = new double[dim];
-  if (dim > this->Dimension)
+  int maxDecimals = 0;
+  for (int i = 0; i < this->Dimension; ++i)
     {
-    memcpy(newPos, this->Coordinates, this->Dimension * sizeof(double));
-    for (int i = this->Dimension; i < dim; ++i)
+    int spinBoxDecimals = this->Decimals;
+    if (q->decimalsOption() & ctkDoubleSpinBox::DecimalsByKey ||
+        q->decimalsOption() & ctkDoubleSpinBox::DecimalsByShortcuts)
       {
-      newPos[i] = 0.;
-      this->addSpinBox();
-      this->LastUserEditedCoordinates.push_back(i);
+      spinBoxDecimals = q->spinBox(i)->decimals();
       }
-    }
-  else
-    {
-    memcpy(newPos, this->Coordinates, dim * sizeof(double));
-    for (int i = this->Dimension - 1 ; i >= dim; --i)
+    if (q->decimalsOption() & ctkDoubleSpinBox::DecimalsByValue)
       {
-      QLayoutItem* item = this->layout()->takeAt(i);
-      QWidget* widget = item ? item->widget() : 0;
-      delete item;
-      delete widget;
-      this->LastUserEditedCoordinates.pop_back();
+      spinBoxDecimals = ctkCoordinatesWidgetPrivate::spinBoxSignificantDecimals(
+        q->spinBox(i));
+      if (spinBoxDecimals == 16)
+        {
+        spinBoxDecimals = q->spinBox(i)->decimals();
+        }
       }
+    maxDecimals = qMax(maxDecimals, spinBoxDecimals);
     }
-  delete [] this->Coordinates;
-  this->Coordinates = newPos;
-  this->Dimension = dim;
-
-  this->updateGeometry();
-  
-  this->updateCoordinates();
+  this->ChangingDecimals = true;
+  this->setTemporaryDecimals(maxDecimals);
+  this->ChangingDecimals = false;
 }
 
 //------------------------------------------------------------------------------
-int ctkCoordinatesWidget::dimension() const
+void ctkCoordinatesWidgetPrivate::updateOtherDecimals(int senderDecimals)
 {
-  return this->Dimension;
+  Q_Q(ctkCoordinatesWidget);
+  if (this->ChangingDecimals)
+    {
+    return;
+    }
+  int senderSpinBoxDecimals = ctkCoordinatesWidgetPrivate::spinBoxSignificantDecimals(
+    qobject_cast<ctkDoubleSpinBox*>(this->sender()));
+
+  int maxDecimals = senderDecimals;
+  for (int i = 0; i < this->Dimension; ++i)
+    {
+    if (this->sender() == q->spinBox(i))
+      {
+      continue;
+      }
+    int spinBoxDecimals = maxDecimals;
+    if (q->decimalsOption() & ctkDoubleSpinBox::DecimalsByKey)
+      {
+      spinBoxDecimals = q->spinBox(i)->decimals();
+      }
+    if (q->decimalsOption() & ctkDoubleSpinBox::DecimalsByValue)
+      {
+      spinBoxDecimals = ctkCoordinatesWidgetPrivate::spinBoxSignificantDecimals(
+        q->spinBox(i));
+      // if the edited spinbox has an undefined number of decimals and the
+      // the current spinbox too, then use the new number of decimals otherwise
+      // there would be no way to increase/decrease decimals for all the
+      // spinboxes.
+      if (spinBoxDecimals == 16)
+        {
+        spinBoxDecimals = (senderSpinBoxDecimals == 16)?
+          senderDecimals : q->spinBox(i)->decimals();
+        }
+      }
+    maxDecimals = qMax(maxDecimals, spinBoxDecimals);
+    }
+  this->ChangingDecimals = true;
+  this->setTemporaryDecimals(maxDecimals);
+  this->ChangingDecimals = false;
 }
 
 //------------------------------------------------------------------------------
-void ctkCoordinatesWidget::setMinimum(double min)
+void ctkCoordinatesWidgetPrivate::setTemporaryDecimals(int newDecimals)
 {
+  Q_Q(ctkCoordinatesWidget);
   for (int i = 0; i < this->Dimension; ++i)
     {
-    this->spinBox(i)->setMinimum(min);
+    if (this->sender() == q->spinBox(i))
+      {
+      continue;
+      }
+    // Increasing the number of decimals might have lost precision.
+    double currentValue = q->spinBox(i)->value();
+    if (q->spinBox(i)->valueProxy())
+      {
+      currentValue = q->spinBox(i)->valueProxy()->proxyValueFromValue(currentValue);
+      }
+    q->spinBox(i)->d_ptr->setValue(currentValue, newDecimals);
     }
-  this->Minimum = min;
 }
 
 //------------------------------------------------------------------------------
-double ctkCoordinatesWidget::minimum() const
+int ctkCoordinatesWidgetPrivate::spinBoxSignificantDecimals(ctkDoubleSpinBox* spinBox)
 {
-  return this->Minimum;
+  if (!spinBox)
+    {
+    return 0;
+    }
+  double currentValue = spinBox->value();
+  if (spinBox->valueProxy())
+    {
+    currentValue = spinBox->valueProxy()->proxyValueFromValue(currentValue);
+    }
+  return ctk::significantDecimals(currentValue);
 }
 
 //------------------------------------------------------------------------------
-void ctkCoordinatesWidget::setMaximum(double max)
+double ctkCoordinatesWidgetPrivate::normalize(double* coordinates, int dimension)
 {
-  for (int i = 0; i < this->Dimension; ++i)
+  double den = ctkCoordinatesWidgetPrivate::norm( coordinates, dimension );
+  if ( den != 0.0 )
     {
-    this->spinBox(i)->setMaximum(max);
+    for (int i = 0; i < dimension; ++i)
+      {
+      coordinates[i] /= den;
+      }
     }
-  this->Maximum = max;
+  return den;
 }
 
 //------------------------------------------------------------------------------
-double ctkCoordinatesWidget::maximum() const
+double ctkCoordinatesWidgetPrivate::norm(double* coordinates, int dimension)
 {
-  return this->Maximum;
+  return sqrt(ctkCoordinatesWidgetPrivate::squaredNorm(coordinates, dimension));
 }
 
 //------------------------------------------------------------------------------
-void ctkCoordinatesWidget::setRange(double min, double max)
+double ctkCoordinatesWidgetPrivate::squaredNorm(double* coordinates, int dimension)
 {
-  for (int i = 0; i < this->Dimension; ++i)
+  double sum = 0.;
+  for (int i = 0; i < dimension; ++i)
     {
-    this->spinBox(i)->setRange(min, max);
+    sum += coordinates[i] * coordinates[i];
     }
-  this->Minimum = min;
-  this->Maximum = max;
+  return sum;
 }
 
-//------------------------------------------------------------------------------
-void ctkCoordinatesWidget::setNormalized(bool normalized)
+//----------------------------------------------------------------------------
+void ctkCoordinatesWidgetPrivate::onValueProxyAboutToBeModified()
 {
-  this->Normalized = normalized;
-  if (this->Normalized)
+  Q_Q(ctkCoordinatesWidget);
+  for (int i = 0; i < this->Dimension; ++i)
     {
-    double* normalizedCoordinates = new double[this->Dimension];
-    memcpy(normalizedCoordinates, this->Coordinates, sizeof(double)*this->Dimension);
-    ctkCoordinatesWidget::normalize(normalizedCoordinates, this->Dimension);
-
-    this->setMinimum(-1.);
-    this->setMaximum(1.);
+    q->spinBox(i)->blockSignals(true);
+    }
+}
 
-    this->setCoordinates(normalizedCoordinates);
-    delete [] normalizedCoordinates;
+//----------------------------------------------------------------------------
+void ctkCoordinatesWidgetPrivate::onValueProxyModified()
+{
+  Q_Q(ctkCoordinatesWidget);
+  for (int i = 0; i < this->Dimension; ++i)
+    {
+    q->spinBox(i)->blockSignals(false);
     }
+  // Only decimals (not range/nor value) may have change during a proxy
+  // modification.
+  this->updateDecimals();
 }
 
 //------------------------------------------------------------------------------
-bool ctkCoordinatesWidget::isNormalized() const
+ctkCoordinatesWidget::ctkCoordinatesWidget(QWidget* _parent)
+  : QWidget(_parent)
+  , d_ptr(new ctkCoordinatesWidgetPrivate(*this))
 {
-  return this->Normalized;
+  Q_D(ctkCoordinatesWidget);
+  d->init();
 }
 
 //------------------------------------------------------------------------------
-void ctkCoordinatesWidget::setDecimals(int newDecimals)
+ctkCoordinatesWidget::~ctkCoordinatesWidget()
 {
-  this->Decimals = newDecimals;
-  for (int i = 0; i < this->Dimension; ++i)
-    {
-    this->spinBox(i)->setDecimals(newDecimals);
-    }
 }
 
 //------------------------------------------------------------------------------
-void ctkCoordinatesWidget::updateDecimals()
+void ctkCoordinatesWidget::setDimension(int dim)
 {
-  if (this->ChangingDecimals)
+  Q_D(ctkCoordinatesWidget);
+  if (dim < 1)
     {
     return;
     }
-  int maxDecimals = 0;
-  for (int i = 0; i < this->Dimension; ++i)
+  double* newPos = new double[dim];
+  if (dim > d->Dimension)
     {
-    int spinBoxDecimals = this->Decimals;
-    if (this->decimalsOption() & ctkDoubleSpinBox::DecimalsByKey ||
-        this->decimalsOption() & ctkDoubleSpinBox::DecimalsByShortcuts)
+    memcpy(newPos, d->Coordinates, d->Dimension * sizeof(double));
+    for (int i = d->Dimension; i < dim; ++i)
       {
-      spinBoxDecimals = this->spinBox(i)->decimals();
+      newPos[i] = 0.;
+      d->addSpinBox();
+      d->LastUserEditedCoordinates.push_back(i);
       }
-    if (this->decimalsOption() & ctkDoubleSpinBox::DecimalsByValue)
+    }
+  else
+    {
+    memcpy(newPos, d->Coordinates, dim * sizeof(double));
+    for (int i = d->Dimension - 1 ; i >= dim; --i)
       {
-      spinBoxDecimals = ctkCoordinatesWidget::spinBoxSignificantDecimals(
-        this->spinBox(i));
-      if (spinBoxDecimals == 16)
-        {
-        spinBoxDecimals = this->spinBox(i)->decimals();
-        }
+      QLayoutItem* item = this->layout()->takeAt(i);
+      QWidget* widget = item ? item->widget() : 0;
+      delete item;
+      delete widget;
+      d->LastUserEditedCoordinates.pop_back();
       }
-    maxDecimals = qMax(maxDecimals, spinBoxDecimals);
     }
-  this->ChangingDecimals = true;
-  this->setTemporaryDecimals(maxDecimals);
-  this->ChangingDecimals = false;
+  delete [] d->Coordinates;
+  d->Coordinates = newPos;
+  d->Dimension = dim;
+
+  this->updateGeometry();
+
+  this->updateCoordinates();
 }
 
 //------------------------------------------------------------------------------
-void ctkCoordinatesWidget::updateOtherDecimals(int senderDecimals)
+int ctkCoordinatesWidget::dimension() const
 {
-  if (this->ChangingDecimals)
+  Q_D(const ctkCoordinatesWidget);
+  return d->Dimension;
+}
+
+//------------------------------------------------------------------------------
+void ctkCoordinatesWidget::setMinimum(double min)
+{
+  Q_D(ctkCoordinatesWidget);
+  for (int i = 0; i < d->Dimension; ++i)
     {
-    return;
+    this->spinBox(i)->setMinimum(min);
     }
-  int senderSpinBoxDecimals = ctkCoordinatesWidget::spinBoxSignificantDecimals(
-    qobject_cast<ctkDoubleSpinBox*>(this->sender()));
+  d->Minimum = min;
+}
 
-  int maxDecimals = senderDecimals;
-  for (int i = 0; i < this->Dimension; ++i)
+//------------------------------------------------------------------------------
+double ctkCoordinatesWidget::minimum() const
+{
+  Q_D(const ctkCoordinatesWidget);
+  return d->Minimum;
+}
+
+//------------------------------------------------------------------------------
+void ctkCoordinatesWidget::setMaximum(double max)
+{
+  Q_D(ctkCoordinatesWidget);
+  for (int i = 0; i < d->Dimension; ++i)
     {
-    if (this->sender() == this->spinBox(i))
-      {
-      continue;
-      }
-    int spinBoxDecimals = maxDecimals;
-    if (this->decimalsOption() & ctkDoubleSpinBox::DecimalsByKey)
-      {
-      spinBoxDecimals = this->spinBox(i)->decimals();
-      }
-    if (this->decimalsOption() & ctkDoubleSpinBox::DecimalsByValue)
-      {
-      spinBoxDecimals = ctkCoordinatesWidget::spinBoxSignificantDecimals(
-        this->spinBox(i));
-      // if the edited spinbox has an undefined number of decimals and the
-      // the current spinbox too, then use the new number of decimals otherwise
-      // there would be no way to increase/decrease decimals for all the
-      // spinboxes.
-      if (spinBoxDecimals == 16)
-        {
-        spinBoxDecimals = (senderSpinBoxDecimals == 16)?
-          senderDecimals : this->spinBox(i)->decimals();
-        }
-      }
-    maxDecimals = qMax(maxDecimals, spinBoxDecimals);
+    this->spinBox(i)->setMaximum(max);
     }
-  this->ChangingDecimals = true;
-  this->setTemporaryDecimals(maxDecimals);
-  this->ChangingDecimals = false;
+  d->Maximum = max;
 }
 
 //------------------------------------------------------------------------------
-void ctkCoordinatesWidget::setTemporaryDecimals(int newDecimals)
+double ctkCoordinatesWidget::maximum() const
 {
-  for (int i = 0; i < this->Dimension; ++i)
+  Q_D(const ctkCoordinatesWidget);
+  return d->Maximum;
+}
+
+//------------------------------------------------------------------------------
+void ctkCoordinatesWidget::setRange(double min, double max)
+{
+  Q_D(ctkCoordinatesWidget);
+  for (int i = 0; i < d->Dimension; ++i)
     {
-    if (this->sender() == this->spinBox(i))
-      {
-      continue;
-      }
-    // Increasing the number of decimals might have lost precision.
-    double currentValue = this->spinBox(i)->value();
-    if (this->spinBox(i)->valueProxy())
-      {
-      currentValue = this->spinBox(i)->valueProxy()->proxyValueFromValue(currentValue);
-      }
-    this->spinBox(i)->d_ptr->setValue(currentValue, newDecimals);
+    this->spinBox(i)->setRange(min, max);
     }
+  d->Minimum = min;
+  d->Maximum = max;
 }
 
 //------------------------------------------------------------------------------
-int ctkCoordinatesWidget::spinBoxSignificantDecimals(ctkDoubleSpinBox* spinBox)
+void ctkCoordinatesWidget::setNormalized(bool normalized)
 {
-  if (!spinBox)
+  Q_D(ctkCoordinatesWidget);
+  d->Normalized = normalized;
+  if (d->Normalized)
     {
-    return 0;
+    double* normalizedCoordinates = new double[d->Dimension];
+    memcpy(normalizedCoordinates, d->Coordinates, sizeof(double)*d->Dimension);
+    ctkCoordinatesWidgetPrivate::normalize(normalizedCoordinates, d->Dimension);
+
+    this->setMinimum(-1.);
+    this->setMaximum(1.);
+
+    this->setCoordinates(normalizedCoordinates);
+    delete [] normalizedCoordinates;
     }
-  double currentValue = spinBox->value();
-  if (spinBox->valueProxy())
+}
+
+//------------------------------------------------------------------------------
+bool ctkCoordinatesWidget::isNormalized() const
+{
+  Q_D(const ctkCoordinatesWidget);
+  return d->Normalized;
+}
+
+//------------------------------------------------------------------------------
+void ctkCoordinatesWidget::setDecimals(int newDecimals)
+{
+  Q_D(ctkCoordinatesWidget);
+  d->Decimals = newDecimals;
+  for (int i = 0; i < d->Dimension; ++i)
     {
-    currentValue = spinBox->valueProxy()->proxyValueFromValue(currentValue);
+    this->spinBox(i)->setDecimals(newDecimals);
     }
-  return ctk::significantDecimals(currentValue);
 }
 
 //------------------------------------------------------------------------------
 int ctkCoordinatesWidget::decimals() const
 {
-  return this->Decimals;
+  Q_D(const ctkCoordinatesWidget);
+  return d->Decimals;
 }
 
 // --------------------------------------------------------------------------
 ctkDoubleSpinBox::DecimalsOptions ctkCoordinatesWidget::decimalsOption()const
 {
-  return this->DecimalsOption;
+  Q_D(const ctkCoordinatesWidget);
+  return d->DecimalsOption;
 }
 
 // --------------------------------------------------------------------------
 void ctkCoordinatesWidget
 ::setDecimalsOption(ctkDoubleSpinBox::DecimalsOptions newDecimalsOption)
 {
-  for (int i = 0; i < this->Dimension; ++i)
+  Q_D(ctkCoordinatesWidget);
+  for (int i = 0; i < d->Dimension; ++i)
     {
     this->spinBox(i)->setDecimalsOption(newDecimalsOption);
     }
-  this->DecimalsOption = newDecimalsOption;
+  d->DecimalsOption = newDecimalsOption;
 }
 
 //------------------------------------------------------------------------------
 void ctkCoordinatesWidget::setSingleStep(double step)
 {
-  for (int i = 0; i < this->Dimension; ++i)
+  Q_D(ctkCoordinatesWidget);
+  for (int i = 0; i < d->Dimension; ++i)
     {
     this->spinBox(i)->setSingleStep(step);
     }
-  this->SingleStep = step;
+  d->SingleStep = step;
 }
 
 //------------------------------------------------------------------------------
 double ctkCoordinatesWidget::singleStep() const
 {
-  return this->SingleStep;
+  Q_D(const ctkCoordinatesWidget);
+  return d->SingleStep;
 }
 
 //------------------------------------------------------------------------------
 void ctkCoordinatesWidget::setCoordinatesAsString(QString _pos)
 {
+  Q_D(ctkCoordinatesWidget);
   QStringList posList = _pos.split(',');
-  if (posList.count() != this->Dimension)
+  if (posList.count() != d->Dimension)
     {
     return;
     }
-  double* newPos = new double[this->Dimension];
-  for (int i = 0; i < this->Dimension; ++i)
+  double* newPos = new double[d->Dimension];
+  for (int i = 0; i < d->Dimension; ++i)
     {
     newPos[i] = posList[i].toDouble();
     }
@@ -371,14 +470,15 @@ void ctkCoordinatesWidget::setCoordinatesAsString(QString _pos)
 //------------------------------------------------------------------------------
 QString ctkCoordinatesWidget::coordinatesAsString()const
 {
+  Q_D(const ctkCoordinatesWidget);
   QString res;
-  for (int i = 0; i < this->Dimension; ++i)
+  for (int i = 0; i < d->Dimension; ++i)
     {
     if (i != 0)
       {
       res += ",";
       }
-    res += QString::number(this->Coordinates[i]);
+    res += QString::number(d->Coordinates[i]);
     }
   return res;
 }
@@ -386,17 +486,18 @@ QString ctkCoordinatesWidget::coordinatesAsString()const
 //------------------------------------------------------------------------------
 void ctkCoordinatesWidget::setCoordinates(double* coordinates)
 {
-  for (int i = 0; i < this->Dimension; ++i)
+  Q_D(ctkCoordinatesWidget);
+  for (int i = 0; i < d->Dimension; ++i)
     {
-    this->Coordinates[i] = coordinates[i];
+    d->Coordinates[i] = coordinates[i];
     }
-  if (this->Normalized)
+  if (d->Normalized)
     {
-    this->normalize(this->Coordinates, this->Dimension);
+    d->normalize(d->Coordinates, d->Dimension);
     }
   bool valuesModified = false;
   bool blocked = this->blockSignals(true);
-  for (int i = 0; i < this->Dimension; ++i)
+  for (int i = 0; i < d->Dimension; ++i)
     {
     ctkDoubleSpinBox* spinbox = this->spinBox(i);
     if (spinbox)
@@ -404,18 +505,18 @@ void ctkCoordinatesWidget::setCoordinates(double* coordinates)
       // we don't want updateCoordinate() to be called.
       // it could mess with the LastUserEditedCoordinates list.
       bool spinBoxSignalWasBlocked = spinbox->blockSignals(true);
-      if (spinbox->value() != this->Coordinates[i])
+      if (spinbox->value() != d->Coordinates[i])
         {
         valuesModified = true;
         }
       // Still setValue needs to be called to recompute the number of decimals
       // if DecimalsByValue is set.
-      spinbox->setValue(this->Coordinates[i]);
+      spinbox->setValue(d->Coordinates[i]);
       spinbox->blockSignals(spinBoxSignalWasBlocked);
       }
     }
   this->blockSignals(blocked);
-  this->updateDecimals();
+  d->updateDecimals();
   if (valuesModified)
     {
     this->updateCoordinates();
@@ -425,26 +526,27 @@ void ctkCoordinatesWidget::setCoordinates(double* coordinates)
 //------------------------------------------------------------------------------
 void ctkCoordinatesWidget::setCoordinates(double x, double y, double z, double w)
 {
-  double* coordinates = new double[this->Dimension];
-  if (this->Dimension >= 1)
+  Q_D(ctkCoordinatesWidget);
+  double* coordinates = new double[d->Dimension];
+  if (d->Dimension >= 1)
     {
     coordinates[0] = x;
     }
-  if (this->Dimension >= 2)
+  if (d->Dimension >= 2)
     {
     coordinates[1] = y;
     }
-  if (this->Dimension >= 3)
+  if (d->Dimension >= 3)
     {
     coordinates[2] = z;
     }
-  if (this->Dimension >= 4)
+  if (d->Dimension >= 4)
     {
     coordinates[3] = w;
     }
-  for (int i = 4; i < this->Dimension; ++i)
+  for (int i = 4; i < d->Dimension; ++i)
     {
-    coordinates[i] = this->Coordinates[i];
+    coordinates[i] = d->Coordinates[i];
     }
   this->setCoordinates(coordinates);
   delete [] coordinates;
@@ -453,32 +555,34 @@ void ctkCoordinatesWidget::setCoordinates(double x, double y, double z, double w
 //------------------------------------------------------------------------------
 double const * ctkCoordinatesWidget::coordinates()const
 {
-  return this->Coordinates;
+  Q_D(const ctkCoordinatesWidget);
+  return d->Coordinates;
 }
 
 //------------------------------------------------------------------------------
 void ctkCoordinatesWidget::updateCoordinate(double coordinate)
 {
+  Q_D(ctkCoordinatesWidget);
   int element = -1;
-  for (int i = 0; i < this->Dimension; ++i)
+  for (int i = 0; i < d->Dimension; ++i)
     {
     if ( this->spinBox(i) && this->spinBox(i) == this->sender())
       {
-      this->Coordinates[i] = coordinate;
+      d->Coordinates[i] = coordinate;
       element = i;
       }
     }
   Q_ASSERT(element != -1);
   // Update the last edited history by push first the element.
-  for (int i = this->Dimension -1; i > 0; --i)
+  for (int i = d->Dimension -1; i > 0; --i)
     {
-    if (this->LastUserEditedCoordinates[i] == element)
+    if (d->LastUserEditedCoordinates[i] == element)
       {
-      this->LastUserEditedCoordinates.swap(i,i-1);
+      d->LastUserEditedCoordinates.swap(i,i-1);
       }
     }
   // What is the oldest coordinate to be edited
-  int oldestElement = this->LastUserEditedCoordinates.last();
+  int oldestElement = d->LastUserEditedCoordinates.last();
 
   if (this->isNormalized())
     {
@@ -490,7 +594,7 @@ void ctkCoordinatesWidget::updateCoordinate(double coordinate)
     // Say we are changing y into y':
     // x'x' + z'z' = 1 - y'y'
     if (oldestElement != -1 &&
-        this->Coordinates[oldestElement] != 0.0 &&
+        d->Coordinates[oldestElement] != 0.0 &&
         squaredNorm != 0.0)
       {
       // 1) Normalize only with the oldest user edited value
@@ -503,10 +607,10 @@ void ctkCoordinatesWidget::updateCoordinate(double coordinate)
       // aa*zz = 1 - y'y' - xx
       // a = sqrt( (1 - y'y' - xx) / zz )
       den = (1. - (squaredNorm -
-             this->Coordinates[oldestElement] *
-             this->Coordinates[oldestElement])) /
-              (this->Coordinates[oldestElement] *
-               this->Coordinates[oldestElement]);
+             d->Coordinates[oldestElement] *
+             d->Coordinates[oldestElement])) /
+              (d->Coordinates[oldestElement] *
+               d->Coordinates[oldestElement]);
       if (den > 0.)
         {
         den = sqrt(den);
@@ -531,24 +635,24 @@ void ctkCoordinatesWidget::updateCoordinate(double coordinate)
         {
         den = sqrt( (1. - coordinate * coordinate) / squaredNorm);
         }
-      else if (this->Dimension > 1)
+      else if (d->Dimension > 1)
         {
         mult = false;
-        den = sqrt((1. - coordinate*coordinate) / (this->Dimension - 1));
+        den = sqrt((1. - coordinate*coordinate) / (d->Dimension - 1));
         }
       }
     // Normalize coordinates
-    double* normalizedCoordinates = new double[this->Dimension];
-    for (int i = 0; i < this->Dimension; ++i)
+    double* normalizedCoordinates = new double[d->Dimension];
+    for (int i = 0; i < d->Dimension; ++i)
       {
       if ((i != element && oldestElement == -1) ||
           (i == oldestElement && oldestElement != -1))
         {
-        normalizedCoordinates[i] = mult ? this->Coordinates[i] * den : den;
+        normalizedCoordinates[i] = mult ? d->Coordinates[i] * den : den;
         }
       else
         {
-        normalizedCoordinates[i] = this->Coordinates[i];
+        normalizedCoordinates[i] = d->Coordinates[i];
         }
       }
     this->setCoordinates(normalizedCoordinates);
@@ -556,89 +660,63 @@ void ctkCoordinatesWidget::updateCoordinate(double coordinate)
     }
   else
     {
-    emit coordinatesChanged(this->Coordinates);
+    emit coordinatesChanged(d->Coordinates);
     }
 }
 
 //------------------------------------------------------------------------------
 void ctkCoordinatesWidget::updateCoordinates()
 {
-  for (int i = 0; i < this->Dimension; ++i)
+  Q_D(ctkCoordinatesWidget);
+  for (int i = 0; i < d->Dimension; ++i)
     {
-    this->Coordinates[i] = this->spinBox(i)->value();
+    d->Coordinates[i] = this->spinBox(i)->value();
     }
-  emit coordinatesChanged(this->Coordinates);
+  emit coordinatesChanged(d->Coordinates);
 }
 
 //------------------------------------------------------------------------------
 void ctkCoordinatesWidget::normalize()
 {
-  double* normalizedCoordinates = new double[this->Dimension];
-  memcpy(normalizedCoordinates, this->Coordinates,
-         sizeof(double) * this->Dimension);
-  ctkCoordinatesWidget::normalize(normalizedCoordinates, this->Dimension);
+  Q_D(ctkCoordinatesWidget);
+  double* normalizedCoordinates = new double[d->Dimension];
+  memcpy(normalizedCoordinates, d->Coordinates,
+         sizeof(double) * d->Dimension);
+  ctkCoordinatesWidgetPrivate::normalize(normalizedCoordinates, d->Dimension);
   this->setCoordinates(normalizedCoordinates);
   delete [] normalizedCoordinates;
 }
 
 //------------------------------------------------------------------------------
-double ctkCoordinatesWidget::normalize(double* coordinates, int dimension)
-{
-  double den = ctkCoordinatesWidget::norm( coordinates, dimension );
-  if ( den != 0.0 )
-    {
-    for (int i = 0; i < dimension; ++i)
-      {
-      coordinates[i] /= den;
-      }
-    }
-  return den;
-}
-
-//------------------------------------------------------------------------------
 double ctkCoordinatesWidget::norm()const
 {
-  return ctkCoordinatesWidget::norm(this->Coordinates, this->Dimension);
-}
-
-//------------------------------------------------------------------------------
-double ctkCoordinatesWidget::norm(double* coordinates, int dimension)
-{
-  return sqrt(ctkCoordinatesWidget::squaredNorm(coordinates, dimension));
+  Q_D(const ctkCoordinatesWidget);
+  return ctkCoordinatesWidgetPrivate::norm(d->Coordinates, d->Dimension);
 }
 
 //------------------------------------------------------------------------------
 double ctkCoordinatesWidget::squaredNorm()const
 {
-  return ctkCoordinatesWidget::squaredNorm(this->Coordinates, this->Dimension);
+  Q_D(const ctkCoordinatesWidget);
+  return ctkCoordinatesWidgetPrivate::squaredNorm(d->Coordinates, d->Dimension);
 }
 
-//------------------------------------------------------------------------------
-double ctkCoordinatesWidget::squaredNorm(double* coordinates, int dimension)
-{
-  double sum = 0.;
-  for (int i = 0; i < dimension; ++i)
-    {
-    sum += coordinates[i] * coordinates[i];
-    }
-  return sum;
-}
-
-
 //----------------------------------------------------------------------------
 void ctkCoordinatesWidget::setSizeHintPolicy(ctkDoubleSpinBox::SizeHintPolicy newSizeHintPolicy)
 {
-  for (int i = 0; i < this->Dimension; ++i)
+  Q_D(ctkCoordinatesWidget);
+  for (int i = 0; i < d->Dimension; ++i)
     {
     this->spinBox(i)->setSizeHintPolicy(newSizeHintPolicy);
     }
-  this->SizeHintPolicy = newSizeHintPolicy;
+  d->SizeHintPolicy = newSizeHintPolicy;
 }
 
 //----------------------------------------------------------------------------
 ctkDoubleSpinBox::SizeHintPolicy ctkCoordinatesWidget::sizeHintPolicy()const
 {
-  return this->SizeHintPolicy;
+  Q_D(const ctkCoordinatesWidget);
+  return d->SizeHintPolicy;
 }
 
 //----------------------------------------------------------------------------
@@ -653,65 +731,46 @@ ctkDoubleSpinBox* ctkCoordinatesWidget::spinBox(int i)
 //----------------------------------------------------------------------------
 void ctkCoordinatesWidget::setValueProxy(ctkValueProxy* proxy)
 {
-  if (this->Proxy.data() == proxy)
+  Q_D(ctkCoordinatesWidget);
+  if (d->Proxy.data() == proxy)
     {
     return;
     }
 
-  this->onValueProxyAboutToBeModified();
+  d->onValueProxyAboutToBeModified();
 
-  if (this->Proxy)
+  if (d->Proxy)
     {
-    disconnect(this->Proxy.data(), SIGNAL(proxyAboutToBeModified()),
-               this, SLOT(onValueProxyAboutToBeModified()));
-    disconnect(this->Proxy.data(), SIGNAL(proxyModified()),
-               this, SLOT(onValueProxyModified()));
+    disconnect(d->Proxy.data(), SIGNAL(proxyAboutToBeModified()),
+               d, SLOT(onValueProxyAboutToBeModified()));
+    disconnect(d->Proxy.data(), SIGNAL(proxyModified()),
+               d, SLOT(onValueProxyModified()));
     }
 
-  this->Proxy = proxy;
+  d->Proxy = proxy;
 
-  if (this->Proxy)
+  if (d->Proxy)
     {
-    connect(this->Proxy.data(), SIGNAL(proxyAboutToBeModified()),
-            this, SLOT(onValueProxyAboutToBeModified()));
+    connect(d->Proxy.data(), SIGNAL(proxyAboutToBeModified()),
+            d, SLOT(onValueProxyAboutToBeModified()));
     }
 
-  for (int i = 0; i < this->Dimension; ++i)
+  for (int i = 0; i < d->Dimension; ++i)
     {
-    this->spinBox(i)->setValueProxy(this->Proxy.data());
+    this->spinBox(i)->setValueProxy(d->Proxy.data());
     }
 
-  if (this->Proxy)
+  if (d->Proxy)
     {
-    connect(this->Proxy.data(), SIGNAL(proxyModified()),
-            this, SLOT(onValueProxyModified()));
+    connect(d->Proxy.data(), SIGNAL(proxyModified()),
+            d, SLOT(onValueProxyModified()));
     }
-  this->onValueProxyModified();
+  d->onValueProxyModified();
 }
 
 //----------------------------------------------------------------------------
 ctkValueProxy* ctkCoordinatesWidget::valueProxy() const
 {
-  return this->Proxy.data();
-}
-
-//----------------------------------------------------------------------------
-void ctkCoordinatesWidget::onValueProxyAboutToBeModified()
-{
-  for (int i = 0; i < this->Dimension; ++i)
-    {
-    this->spinBox(i)->blockSignals(true);
-    }
-}
-
-//----------------------------------------------------------------------------
-void ctkCoordinatesWidget::onValueProxyModified()
-{
-  for (int i = 0; i < this->Dimension; ++i)
-    {
-    this->spinBox(i)->blockSignals(false);
-    }
-  // Only decimals (not range/nor value) may have change during a proxy
-  // modification.
-  this->updateDecimals();
+  Q_D(const ctkCoordinatesWidget);
+  return d->Proxy.data();
 }

+ 7 - 34
Libs/Widgets/ctkCoordinatesWidget.h

@@ -23,11 +23,11 @@
 
 // Qt includes
 #include <QWidget>
-#include <QWeakPointer>
 
 // CTK includes
 #include "ctkDoubleSpinBox.h"
 #include "ctkWidgetsExport.h"
+class ctkCoordinatesWidgetPrivate;
 
 /// \ingroup Widgets
 ///
@@ -162,42 +162,15 @@ Q_SIGNALS:
   void coordinatesChanged(double* pos);
 
 protected Q_SLOTS:
-  void updateCoordinate(double);
   void updateCoordinates();
-  void updateDecimals();
-  void updateOtherDecimals(int);
-  void setTemporaryDecimals(int);
-  void onValueProxyAboutToBeModified();
-  void onValueProxyModified();
+  void updateCoordinate(double coordinate);
 
 protected:
-  void addSpinBox();
-
-  /// Normalize coordinates vector and return the previous norm.
-  static double normalize(double* coordinates, int dimension);
-
-  /// Compute the norm of a coordinates \a dimension vector
-  static double norm(double* coordinates, int dimension);
-  static double squaredNorm(double* coordinates, int dimension);
-
-  /// Return the ideal number of decimals based on the spinBox value or
-  /// 16 if there is no "good" number of decimals.
-  /// \sa ctk::significantDecimals()
-  static int spinBoxSignificantDecimals(ctkDoubleSpinBox* spinBox);
-
-  int     Decimals;
-  ctkDoubleSpinBox::DecimalsOptions DecimalsOption;
-  double  SingleStep;
-  double  Minimum;
-  double  Maximum;
-  bool    Normalized;
-  int     Dimension;
-  ctkDoubleSpinBox::SizeHintPolicy SizeHintPolicy;
-
-  double* Coordinates;
-  QList<int> LastUserEditedCoordinates;
-  bool    ChangingDecimals;
-  QWeakPointer<ctkValueProxy> Proxy;
+  QScopedPointer<ctkCoordinatesWidgetPrivate> d_ptr;
+
+private:
+  Q_DECLARE_PRIVATE(ctkCoordinatesWidget);
+  Q_DISABLE_COPY(ctkCoordinatesWidget);
 };
 
 #endif

+ 82 - 0
Libs/Widgets/ctkCoordinatesWidget_p.h

@@ -0,0 +1,82 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+#ifndef __ctkCoordinatesWidget_p_h
+#define __ctkCoordinatesWidget_p_h
+
+// Qt includes
+#include <QWeakPointer>
+
+// CTK includes
+#include "ctkCoordinatesWidget.h"
+
+class ctkCoordinatesWidgetPrivate: public QObject
+{
+  Q_OBJECT
+  Q_DECLARE_PUBLIC(ctkCoordinatesWidget);
+protected:
+  ctkCoordinatesWidget* const q_ptr;
+public:
+  ctkCoordinatesWidgetPrivate(ctkCoordinatesWidget& object);
+  virtual ~ctkCoordinatesWidgetPrivate();
+
+  void init();
+public Q_SLOTS:
+  void updateDecimals();
+  void updateOtherDecimals(int);
+  void setTemporaryDecimals(int);
+  void onValueProxyAboutToBeModified();
+  void onValueProxyModified();
+
+public:
+  void addSpinBox();
+
+  /// Normalize coordinates vector and return the previous norm.
+  static double normalize(double* coordinates, int dimension);
+
+  /// Compute the norm of a coordinates \a dimension vector
+  static double norm(double* coordinates, int dimension);
+  static double squaredNorm(double* coordinates, int dimension);
+
+  /// Return the ideal number of decimals based on the spinBox value or
+  /// 16 if there is no "good" number of decimals.
+  /// \sa ctk::significantDecimals()
+  static int spinBoxSignificantDecimals(ctkDoubleSpinBox* spinBox);
+
+  int     Decimals;
+  ctkDoubleSpinBox::DecimalsOptions DecimalsOption;
+  double  SingleStep;
+  double  Minimum;
+  double  Maximum;
+  bool    Normalized;
+  int     Dimension;
+  ctkDoubleSpinBox::SizeHintPolicy SizeHintPolicy;
+
+  double* Coordinates;
+  QList<int> LastUserEditedCoordinates;
+  bool    ChangingDecimals;
+  QWeakPointer<ctkValueProxy> Proxy;
+
+private:
+  Q_DISABLE_COPY(ctkCoordinatesWidgetPrivate);
+
+};
+
+#endif

+ 59 - 11
Libs/Widgets/ctkDirectoryButton.cpp

@@ -20,8 +20,10 @@
 
 // Qt includes
 #include <QDebug>
+#include <QFileSystemModel>
 #include <QHBoxLayout>
 #include <QPushButton>
+#include <QSortFilterProxyModel>
 #include <QStyle>
 
 // CTK includes
@@ -51,6 +53,7 @@ public:
 #endif
   // TODO expose DisplayAbsolutePath into the API
   bool         DisplayAbsolutePath;
+  QFileDialog::AcceptMode AcceptMode;
 };
 
 //-----------------------------------------------------------------------------
@@ -63,6 +66,7 @@ ctkDirectoryButtonPrivate::ctkDirectoryButtonPrivate(ctkDirectoryButton& object)
   this->DialogOptions = ctkDirectoryButton::ShowDirsOnly;
 #endif
   this->DisplayAbsolutePath = true;
+  this->AcceptMode = QFileDialog::AcceptOpen;
 }
 
 //-----------------------------------------------------------------------------
@@ -229,20 +233,64 @@ const ctkDirectoryButton::Options& ctkDirectoryButton::options()const
 }
 
 //-----------------------------------------------------------------------------
+QFileDialog::AcceptMode ctkDirectoryButton::acceptMode() const
+{
+  Q_D(const ctkDirectoryButton);
+  return d->AcceptMode;
+}
+
+//-----------------------------------------------------------------------------
+void ctkDirectoryButton::setAcceptMode(QFileDialog::AcceptMode mode)
+{
+  Q_D(ctkDirectoryButton);
+  d->AcceptMode = mode;
+}
+
+//-----------------------------------------------------------------------------
 void ctkDirectoryButton::browse()
 {
+  // See https://bugreports.qt-project.org/browse/QTBUG-10244
+  class ExcludeReadOnlyFilterProxyModel : public QSortFilterProxyModel
+  {
+  public:
+    ExcludeReadOnlyFilterProxyModel(QObject *parent):QSortFilterProxyModel(parent)
+    {
+    }
+    virtual bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const
+    {
+      QString filePath =
+          this->sourceModel()->data(sourceModel()->index(source_row, 0, source_parent),
+          QFileSystemModel::FilePathRole).toString();
+      return QFileInfo(filePath).isWritable();
+    }
+  };
+
   Q_D(ctkDirectoryButton);
-  QString dir =
-    QFileDialog::getExistingDirectory(
-      this,
-      d->DialogCaption.isEmpty() ? this->toolTip() : d->DialogCaption,
-      d->Directory.path(),
-#ifdef USE_QFILEDIALOG_OPTIONS
-      d->DialogOptions);
-#else
-      QFlags<QFileDialog::Option>(int(d->DialogOptions)));
-#endif
-  // An empty directory means that the user cancelled the dialog.
+  QScopedPointer<QFileDialog> fileDialog(
+          new QFileDialog(this, d->DialogCaption.isEmpty() ? this->toolTip() :
+          d->DialogCaption, d->Directory.path()));
+  #ifdef USE_QFILEDIALOG_OPTIONS
+    fileDialog->setOptions(d->DialogOptions);
+  #else
+    fileDialog->setOptions(QFlags<QFileDialog::Option>(int(d->DialogOptions)));
+  #endif
+    fileDialog->setAcceptMode(d->AcceptMode);
+    fileDialog->setFileMode(QFileDialog::DirectoryOnly);
+
+  if (d->AcceptMode == QFileDialog::AcceptSave)
+    {
+    // Ideally "Choose" button of QFileDialog should be disabled if a read-only folder
+    // is selected and the acceptMode was AcceptSave.
+    // This is captured in https://github.com/commontk/CTK/issues/365
+    fileDialog->setProxyModel(new ExcludeReadOnlyFilterProxyModel(fileDialog.data()));
+    }
+
+  QString dir;
+  if (fileDialog->exec())
+    {
+    dir = fileDialog->selectedFiles().at(0);
+    }
+  // An empty directory means either that the user cancelled the dialog or the selected directory is readonly
   if (dir.isEmpty())
     {
     return;

+ 14 - 0
Libs/Widgets/ctkDirectoryButton.h

@@ -43,6 +43,13 @@ class ctkDirectoryButtonPrivate;
 class CTK_WIDGETS_EXPORT ctkDirectoryButton: public QWidget
 {
   Q_OBJECT
+  /// This property holds the accept mode of the dialog.
+  /// The action mode defines whether the dialog is for opening or saving files.
+  /// By default, this property is set to AcceptOpen.
+  /// If set to QFileDialog::AcceptSave mode, the regular behavior of QFileDialog will be extended
+  /// to prevent user from selecting read-only folder. The caveat is that writable folder existing
+  /// in a readonly one won't be selectable.
+  Q_PROPERTY(QFileDialog::AcceptMode acceptMode READ acceptMode WRITE setAcceptMode)
   Q_PROPERTY(QString directory READ directory WRITE setDirectory NOTIFY directoryChanged USER true)
   /// This property holds the title of the file dialog used to select a new directory
   /// If caption is not set, internally use QWidget::tooltip()
@@ -128,6 +135,12 @@ public:
   const Options& options()const;
 #endif
 
+  /// \sa setAcceptMode QFileDialog::AcceptMode
+  QFileDialog::AcceptMode acceptMode() const;
+
+  /// \sa acceptMode QFileDialog::AcceptMode
+  void setAcceptMode(QFileDialog::AcceptMode mode);
+
 public Q_SLOTS:
   /// browse() opens a pop up where the user can select a new directory for the
   /// button. browse() is automatically called when the button is clicked.
@@ -146,6 +159,7 @@ Q_SIGNALS:
   /// the current directory.
   /// \sa directoryChanged
   void directorySelected(const QString&);
+
 protected:
   QScopedPointer<ctkDirectoryButtonPrivate> d_ptr;
 

+ 1 - 1
Libs/Widgets/ctkDoubleSpinBox.h

@@ -326,7 +326,7 @@ protected:
   /// Reimplemented to support shortcuts on the double spinbox.
   virtual bool eventFilter(QObject *obj, QEvent *event);
 
-  friend class ctkCoordinatesWidget;
+  friend class ctkCoordinatesWidgetPrivate;
 private:
   Q_DECLARE_PRIVATE(ctkDoubleSpinBox);
   Q_DISABLE_COPY(ctkDoubleSpinBox);

+ 9 - 2
Libs/Widgets/ctkErrorLogWidget.cpp

@@ -83,8 +83,6 @@ void ctkErrorLogWidgetPrivate::init()
 
   QObject::connect(this->ClearButton, SIGNAL(clicked()),
                    q, SLOT(removeEntries()));
-
-  this->ErrorLogTableView->setColumnHidden(ctkErrorLogModel::ThreadIdColumn, true);
 }
 
 // --------------------------------------------------------------------------
@@ -177,6 +175,15 @@ void ctkErrorLogWidget::setErrorLogModel(ctkErrorLogModel * newErrorLogModel)
     {
     this->setAllEntriesVisible(0);
     }
+
+  d->ErrorLogTableView->setColumnHidden(ctkErrorLogModel::ThreadIdColumn, true);
+}
+
+// --------------------------------------------------------------------------
+void ctkErrorLogWidget::setColumnHidden(int columnId, bool hidden) const
+{
+  Q_D(const ctkErrorLogWidget);
+  d->ErrorLogTableView->setColumnHidden(columnId, hidden);
 }
 
 // --------------------------------------------------------------------------

+ 4 - 0
Libs/Widgets/ctkErrorLogWidget.h

@@ -45,6 +45,10 @@ public:
   ctkErrorLogModel* errorLogModel()const;
   void setErrorLogModel(ctkErrorLogModel * newErrorLogModel);
 
+  /// Hide table column identified by /a columnId.
+  /// \sa ctkErrorLogModel::ColumnsIds
+  Q_INVOKABLE void setColumnHidden(int columnId, bool hidden) const;
+
 public Q_SLOTS:
   void setAllEntriesVisible(bool visibility = true);
 

+ 9 - 0
Libs/Widgets/ctkProxyStyle.cpp

@@ -38,11 +38,13 @@ public:
 private:
   ctkProxyStylePrivate(ctkProxyStyle& object);
   mutable QPointer <QStyle> baseStyle;
+  mutable bool ensureBaseStyleInProgress;
 };
 
 // ----------------------------------------------------------------------------
 ctkProxyStylePrivate::ctkProxyStylePrivate(ctkProxyStyle& object)
   : q_ptr(&object)
+  , ensureBaseStyleInProgress(false)
 {
 }
 
@@ -102,6 +104,12 @@ ctkProxyStyle::~ctkProxyStyle()
 void ctkProxyStyle::ensureBaseStyle() const
 {
   Q_D(const ctkProxyStyle);
+  if (d->ensureBaseStyleInProgress)
+  {
+    // avoid infinite loop
+    return;
+  }
+  d->ensureBaseStyleInProgress = true;  
   d->baseStyle = this->baseStyle();
   // Set the proxy to the entire hierarchy.
   QProxyStyle* proxyStyle = const_cast<QProxyStyle*>(qobject_cast<const QProxyStyle*>(
@@ -115,6 +123,7 @@ void ctkProxyStyle::ensureBaseStyle() const
     baseStyle = proxy ? proxy->baseStyle() : 0;
     }
   d->setBaseStyle(proxyStyle, proxyBaseStyle);
+  d->ensureBaseStyleInProgress = false;
 }
 
 // ----------------------------------------------------------------------------

+ 4 - 0
Libs/Widgets/ctkWidgetsUtils.cpp

@@ -56,6 +56,10 @@ QImage ctk::grabWidget(QWidget* widget, QRect rectangle)
   painter.begin(&widgetImage);
   foreach(QGLWidget* glWidget, widget->findChildren<QGLWidget*>())
     {
+    if (!glWidget->isVisible())
+      {
+      continue;
+      }
     QRect subWidgetRect = QRect(glWidget->mapTo(widget, QPoint(0,0)), glWidget->size());
     if (!rectangle.intersects(subWidgetRect))
       {