Pārlūkot izejas kodu

ENH: Add ctkSpinBox DecimalsOption::UseShortcut

ctkSpinBox::DecimalsOption controls how decimals are handled. In the
regular (Fixed) mode, the spinbox behaves like a QDoubleSpinBox, i.e.
the number of decimals is set by setDecimals().
With the UseShortcut flag, the ctkSpinBox responds to keyboard shortcuts to
add (CTRL +) decimals, remove (CTL -) decimals and restore the
default number of decimals (CTRL 0) initially set by setDecimals().

This led to the update of the ctk widgets that use the ctkSpinBox. For most
of them it's pretty straightforward as they can listen to the
decimalsChanged(int) event and update themselves.
In the case of the slider widget, the AutoSpinBoxWidth was extended to
support the change of width due to a change in decimals as well as the
change in decimals (that is propagated to the siblings).

See Slicer issue #2480
Johan Andruejol 12 gadi atpakaļ
vecāks
revīzija
5a63165efd

+ 3 - 0
Libs/Widgets/Resources/UI/ctkScreenshotDialog.ui

@@ -155,6 +155,9 @@
         <property name="toolTip">
          <string>Select an integer scale factor (between 0.5 and 5) for the image file, e.g. a value of &quot;2&quot; will save an image twice the size.</string>
         </property>
+        <property name="decimalsOption">
+         <enum>ctkSpinBox::Fixed</enum>
+        </property>
         <property name="decimals">
          <number>1</number>
         </property>

+ 6 - 6
Libs/Widgets/Testing/Cpp/ctkRangeWidgetEventTranslatorPlayerTest1.xml

@@ -59,11 +59,11 @@
         <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/Slider" command="set_min_double" arguments="35"/>
         <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/Slider" command="set_min_double" arguments="36"/>
         <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/Slider" command="set_min_double" arguments="37"/>
-        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MaximumSpinBox" command="spin" arguments="down"/>
-        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MaximumSpinBox" command="spin" arguments="down"/>
-        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MaximumSpinBox" command="spin" arguments="down"/>
-        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MinimumSpinBox" command="spin" arguments="down"/>
-        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MinimumSpinBox" command="spin" arguments="down"/>
-        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MinimumSpinBox" command="spin" arguments="down"/>
+        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MaximumSpinBox/1QDoubleSpinBox0" command="set_double" arguments="70"/>
+        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MaximumSpinBox/1QDoubleSpinBox0" command="set_double" arguments="71"/>
+        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MaximumSpinBox/1QDoubleSpinBox0" command="set_double" arguments="72"/>
+        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MinimumSpinBox/1QDoubleSpinBox0" command="set_double" arguments="32"/>
+        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MinimumSpinBox/1QDoubleSpinBox0" command="set_double" arguments="33"/>
+        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget1/MinimumSpinBox/1QDoubleSpinBox0" command="set_double" arguments="34"/>
     </events>
 </QtTesting>

+ 2 - 2
Libs/Widgets/Testing/Cpp/ctkRangeWidgetEventTranslatorPlayerTest2.xml

@@ -17,7 +17,7 @@
         <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget2/Slider" command="set_max_double" arguments="0.4"/>
         <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget2/Slider" command="set_max_double" arguments="-4.6"/>
         <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget2/Slider" command="set_max_double" arguments="-9.6"/>
-        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget2/MinimumSpinBox" command="spin" arguments="up"/>
-        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget2/MinimumSpinBox" command="spin" arguments="up"/>
+        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget2/MinimumSpinBox/1QDoubleSpinBox0" command="set_double" arguments="-36"/>
+        <event widget="ctkEventTranslatorPlayerWidget/centralwidget/stackedWidget/RangeWidget2/MinimumSpinBox/1QDoubleSpinBox0" command="set_double" arguments="-35"/>
     </events>
 </QtTesting>

+ 4 - 3
Libs/Widgets/Testing/Cpp/ctkSliderWidgetTest1.cpp

@@ -152,13 +152,14 @@ int ctkSliderWidgetTest1(int argc, char * argv [] )
     return EXIT_FAILURE;
     }
 
-  sliderSpinBox.setAutoSpinBoxWidth(false);
+  sliderSpinBox.setSynchronizeSiblings(ctkSliderWidget::NoSynchronize);
 
-  if (sliderSpinBox.isAutoSpinBoxWidth() != false || 
+  if (sliderSpinBox.synchronizeSiblings().testFlag(
+      ctkSliderWidget::SynchronizeWidth) != false ||
       !qFuzzyCompare(sliderSpinBox.value(), 80.6))
     {
     std::cerr << "ctkSliderWidget::setAutoSpinBoxWidth failed."
-              << sliderSpinBox.isAutoSpinBoxWidth() << " "
+              << sliderSpinBox.synchronizeSiblings() << " "
               << sliderSpinBox.value() << std::endl;
     return EXIT_FAILURE;
     }

+ 3 - 0
Libs/Widgets/ctkCoordinatesWidget.cpp

@@ -63,6 +63,9 @@ void ctkCoordinatesWidget::addSpinBox()
   spinBox->setMaximum(this->Maximum);
   connect( spinBox, SIGNAL(valueChanged(double)),
            this, SLOT(updateCoordinate(double)));
+  // Same number of decimals within the spinboxes.
+  connect( spinBox, SIGNAL(decimalsChanged(int)),
+           this, SLOT(setDecimals(int)));
   this->layout()->addWidget(spinBox);
 }
 

+ 4 - 2
Libs/Widgets/ctkCoordinatesWidget.h

@@ -59,9 +59,8 @@ public:
   void setDimension(int dim);
   int dimension() const;
 
-  /// Set/Get the number of decimals of each coordinate spin box
+  /// Get the number of decimals of each coordinate spin box
   /// The default number of decimals is 3.
-  void setDecimals(int decimals);
   int decimals() const;
 
   /// Set/Get the single step of each coordinate spin box
@@ -106,6 +105,9 @@ public:
 public Q_SLOTS:
   void normalize();
 
+  /// Set the number of decimals of each coordinate spin box.
+  void setDecimals(int decimals);
+
 Q_SIGNALS:
   ///
   /// valueChanged is fired anytime a coordinate is modified, the returned

+ 3 - 0
Libs/Widgets/ctkMatrixWidget.cpp

@@ -56,6 +56,9 @@ namespace
       this->setMaximum(matrixWidget->maximum());
       this->setDecimals(matrixWidget->decimals());
       this->setSingleStep(matrixWidget->singleStep());
+
+      this->connect(this, SIGNAL(decimalsChanged(int)),
+        matrixWidget, SLOT(setDecimals(int)));
     }
   };
 }

+ 5 - 1
Libs/Widgets/ctkMatrixWidget.h

@@ -122,7 +122,6 @@ public:
   /// Dictates how many decimals will be used for displaying and interpreting doubles by the spinbox
   /// used to adjust the value of a matrix element.
   int decimals()const;
-  void setDecimals(int decimals);
 
   ///
   /// Reimplemented from QAbstractScrollArea
@@ -135,6 +134,11 @@ public Q_SLOTS:
   /// Reset the matrix to identity
   void identity();
 
+  ///
+  /// Set how many decimals will be used for displaying and interpreting
+  /// doubles by the spinbox used to adjust the value of a matrix element.
+  void setDecimals(int decimals);
+
 Q_SIGNALS:
   void matrixChanged();
 

+ 4 - 0
Libs/Widgets/ctkRangeWidget.cpp

@@ -92,6 +92,10 @@ void ctkRangeWidgetPrivate::connectSlider()
                    q, SLOT(setMinimumToMaximumSpinBox(double)));
   QObject::connect(this->MaximumSpinBox, SIGNAL(valueChanged(double)),
                    q, SLOT(setMaximumToMinimumSpinBox(double)));
+  QObject::connect(this->MinimumSpinBox, SIGNAL(decimalsChanged(int)),
+                   q, SLOT(setDecimals(int)));
+  QObject::connect(this->MaximumSpinBox, SIGNAL(decimalsChanged(int)),
+                   q, SLOT(setDecimals(int)));
 
   QObject::connect(this->Slider, SIGNAL(sliderPressed()),
                    q, SLOT(startChanging()));

+ 4 - 3
Libs/Widgets/ctkRangeWidget.h

@@ -118,10 +118,7 @@ public:
 
   ///
   /// This property holds the precision of the spin box, in decimals.
-  /// Sets how many decimals the spinbox will use for displaying and
-  /// interpreting doubles.
   int decimals()const;
-  void setDecimals(int decimals);
 
   ///
   /// This property holds the spin box's prefix.
@@ -192,6 +189,10 @@ public Q_SLOTS:
   /// Utility function that set the min and max values at once
   void setValues(double minValue, double maxValue);
 
+  /// Sets how many decimals the spinbox will use for displaying and
+  /// interpreting doubles.
+  void setDecimals(int decimals);
+
 Q_SIGNALS:
   /// Use with care:
   /// sliderMoved is emitted only when the user moves the slider

+ 53 - 14
Libs/Widgets/ctkSliderWidget.cpp

@@ -42,8 +42,12 @@ public:
   virtual ~ctkSliderWidgetPrivate();
 
   void updateSpinBoxWidth();
-  int synchronizedSpinBoxWidth()const;
-  void synchronizeSiblingSpinBox(int newWidth);
+  void updateSpinBoxDecimals();
+
+  int synchronizedSpinBoxWidth() const;
+
+  void synchronizeSiblingWidth(int width);
+  void synchronizeSiblingDecimals(int decimals);
   bool equal(double spinBoxValue, double sliderValue)const
   {
     return qAbs(sliderValue - spinBoxValue) < std::pow(10., -this->SpinBox->decimals());
@@ -52,7 +56,7 @@ public:
   bool   Tracking;
   bool   Changing;
   double ValueBeforeChange;
-  bool   AutoSpinBoxWidth;
+  ctkSliderWidget::SynchronizeSiblings SynchronizeMode;
   ctkPopupWidget* SliderPopup;
 };
 
@@ -60,14 +64,16 @@ public:
 ctkSliderWidgetPrivate::ctkSliderWidgetPrivate(ctkSliderWidget& object)
   :q_ptr(&object)
 {
+  qRegisterMetaType<ctkSliderWidget::SynchronizeSiblings>(
+    "ctkSliderWidget::SynchronizeSiblings");
   this->Tracking = true;
   this->Changing = false;
   this->ValueBeforeChange = 0.;
-  this->AutoSpinBoxWidth = true;
+  this->SynchronizeMode =
+    ctkSliderWidget::SynchronizeWidth | ctkSliderWidget::SynchronizeDecimals;
   this->SliderPopup = 0;
 }
 
-
 // --------------------------------------------------------------------------
 ctkSliderWidgetPrivate::~ctkSliderWidgetPrivate()
 {
@@ -77,7 +83,7 @@ ctkSliderWidgetPrivate::~ctkSliderWidgetPrivate()
 void ctkSliderWidgetPrivate::updateSpinBoxWidth()
 {
   int spinBoxWidth = this->synchronizedSpinBoxWidth();
-  if (this->AutoSpinBoxWidth)
+  if (this->SynchronizeMode.testFlag(ctkSliderWidget::SynchronizeWidth))
     {
     this->SpinBox->setMinimumWidth(spinBoxWidth);
     }
@@ -85,7 +91,17 @@ void ctkSliderWidgetPrivate::updateSpinBoxWidth()
     {
     this->SpinBox->setMinimumWidth(0);
     }
-  this->synchronizeSiblingSpinBox(spinBoxWidth);
+
+  this->synchronizeSiblingWidth(spinBoxWidth);
+}
+
+// --------------------------------------------------------------------------
+void ctkSliderWidgetPrivate::updateSpinBoxDecimals()
+{
+  if (this->SynchronizeMode.testFlag(ctkSliderWidget::SynchronizeDecimals))
+    {
+    this->synchronizeSiblingDecimals(this->SpinBox->decimals());
+    }
 }
 
 // --------------------------------------------------------------------------
@@ -107,16 +123,34 @@ int ctkSliderWidgetPrivate::synchronizedSpinBoxWidth()const
 }
 
 // --------------------------------------------------------------------------
-void ctkSliderWidgetPrivate::synchronizeSiblingSpinBox(int width)
+void ctkSliderWidgetPrivate::synchronizeSiblingWidth(int width)
+{
+  Q_Q(const ctkSliderWidget);
+  QList<ctkSliderWidget*> siblings =
+    q->parent()->findChildren<ctkSliderWidget*>();
+  foreach(ctkSliderWidget* sibling, siblings)
+    {
+    if (sibling != q
+      && sibling->synchronizeSiblings().testFlag(ctkSliderWidget::SynchronizeWidth))
+      {
+      sibling->d_func()->SpinBox->setMinimumWidth(
+        this->SpinBox->minimumWidth());
+      }
+    }
+}
+
+// --------------------------------------------------------------------------
+void ctkSliderWidgetPrivate::synchronizeSiblingDecimals(int decimals)
 {
   Q_Q(const ctkSliderWidget);
   QList<ctkSliderWidget*> siblings =
     q->parent()->findChildren<ctkSliderWidget*>();
   foreach(ctkSliderWidget* sibling, siblings)
     {
-    if (sibling != q && sibling->isAutoSpinBoxWidth())
+    if (sibling != q
+      && sibling->synchronizeSiblings().testFlag(ctkSliderWidget::SynchronizeDecimals))
       {
-      sibling->d_func()->SpinBox->setMinimumWidth(width);
+      sibling->d_func()->SpinBox->setDecimals(this->SpinBox->decimals());
       }
     }
 }
@@ -133,6 +167,7 @@ ctkSliderWidget::ctkSliderWidget(QWidget* _parent) : Superclass(_parent)
   d->Slider->setMinimum(d->SpinBox->minimum());
 
   this->connect(d->SpinBox, SIGNAL(valueChanged(double)), d->Slider, SLOT(setValue(double)));
+  this->connect(d->SpinBox, SIGNAL(decimalsChanged(int)), this, SLOT(setDecimals(int)));
 
   //this->connect(d->Slider, SIGNAL(valueChanged(double)), SIGNAL(valueChanged(double)));
   this->connect(d->Slider, SIGNAL(sliderPressed()), this, SLOT(startChanging()));
@@ -387,6 +422,7 @@ void ctkSliderWidget::setDecimals(int newDecimals)
   Q_ASSERT(d->equal(d->SpinBox->minimum(),d->Slider->minimum()));
   Q_ASSERT(d->equal(d->SpinBox->value(),d->Slider->value()));
   Q_ASSERT(d->equal(d->SpinBox->maximum(),d->Slider->maximum()));
+  d->updateSpinBoxDecimals();
 }
 
 // --------------------------------------------------------------------------
@@ -482,18 +518,21 @@ bool ctkSliderWidget::hasTracking()const
 }
 
 // -------------------------------------------------------------------------
-bool ctkSliderWidget::isAutoSpinBoxWidth()const
+ctkSliderWidget::SynchronizeSiblings
+ctkSliderWidget::synchronizeSiblings() const
 {
   Q_D(const ctkSliderWidget);
-  return d->AutoSpinBoxWidth;
+  return d->SynchronizeMode;
 }
 
 // -------------------------------------------------------------------------
-void ctkSliderWidget::setAutoSpinBoxWidth(bool autoWidth)
+void ctkSliderWidget
+::setSynchronizeSiblings(ctkSliderWidget::SynchronizeSiblings flag)
 {
   Q_D(ctkSliderWidget);
-  d->AutoSpinBoxWidth = autoWidth;
+  d->SynchronizeMode = flag;
   d->updateSpinBoxWidth();
+  d->updateSpinBoxDecimals();
 }
 
 // -------------------------------------------------------------------------

+ 38 - 11
Libs/Widgets/ctkSliderWidget.h

@@ -52,13 +52,36 @@ class CTK_WIDGETS_EXPORT ctkSliderWidget : public QWidget
   Q_PROPERTY(QString suffix READ suffix WRITE setSuffix)
   Q_PROPERTY(double tickInterval READ tickInterval WRITE setTickInterval)
   Q_PROPERTY(QSlider::TickPosition tickPosition READ tickPosition WRITE setTickPosition)
-  Q_PROPERTY(bool autoSpinBoxWidth READ isAutoSpinBoxWidth WRITE setAutoSpinBoxWidth)
+  Q_FLAGS(SynchronizeSiblings)
+  Q_PROPERTY(SynchronizeSiblings SynchronizeSibling READ synchronizeSiblings WRITE setSynchronizeSiblings)
   Q_PROPERTY(Qt::Alignment spinBoxAlignment READ spinBoxAlignment WRITE setSpinBoxAlignment)
   Q_PROPERTY(bool tracking READ hasTracking WRITE setTracking)
   Q_PROPERTY(bool spinBoxVisible READ isSpinBoxVisible WRITE setSpinBoxVisible);
   Q_PROPERTY(bool popupSlider READ hasPopupSlider WRITE setPopupSlider);
 
 public:
+
+  /// Synchronize properties of the slider siblings:
+  /// NoSynchronize:
+  /// The slider widget siblings aren't updated and this widget does not update
+  /// from its siblings.
+  /// SynchronizeWidth:
+  /// The width of the SpinBox is set to the same width of the largest QSpinBox
+  /// of its ctkSliderWidget siblings.
+  /// SynchronizeDecimals:
+  /// Whenever one of the siblings changes its number of decimals, all its
+  /// siblings Synchronize to the new number of decimals.
+  ///
+  /// Default is SynchronizeWidth |SynchronizeDecimals.
+  /// \sa SynchronizeSiblings(), setSynchronizeSiblings()
+  enum SynchronizeSibling
+    {
+    NoSynchronize = 0x000,
+    SynchronizeWidth = 0x001,
+    SynchronizeDecimals = 0x002,
+    };
+  Q_DECLARE_FLAGS(SynchronizeSiblings, SynchronizeSibling)
+
   /// Superclass typedef
   typedef QWidget Superclass;
 
@@ -116,9 +139,7 @@ public:
 
   /// 
   /// This property holds the precision of the spin box, in decimals.
-  /// Sets how many decimals the spinbox will use for displaying and interpreting doubles.
   int decimals()const;
-  void setDecimals(int decimals);
 
   ///
   /// This property holds the spin box's prefix.
@@ -140,7 +161,7 @@ public:
   /// If it is 0, the slider will choose between lineStep() and pageStep().
   /// The default value is 0.
   double tickInterval()const;
-  void setTickInterval(double ti);
+  void setTickInterval(double tick);
 
   /// 
   /// This property holds the tickmark position for the slider.
@@ -166,12 +187,12 @@ public:
   bool hasTracking()const;
 
   /// 
-  /// Set/Get the auto spinbox width
-  /// When the autoSpinBoxWidth property is on, the width of the SpinBox is
-  /// set to the same width of the largest QSpinBox of its
-  // ctkSliderWidget siblings.
-  bool isAutoSpinBoxWidth()const;
-  void setAutoSpinBoxWidth(bool autoWidth);
+  /// Set/Get the synchronize siblings mode. This helps when having multiple
+  /// ctkSliderWidget stacked upon each other.
+  /// Default flag is SynchronizeWidth | SynchronizeDecimals.
+  /// \sa SynchronizeSiblingsModes
+  ctkSliderWidget::SynchronizeSiblings synchronizeSiblings() const;
+  void setSynchronizeSiblings(ctkSliderWidget::SynchronizeSiblings options);
 
   ///
   /// The Spinbox visibility can be controlled using setSpinBoxVisible() and
@@ -212,6 +233,10 @@ public Q_SLOTS:
   void setValue(double value);
   void setSpinBoxVisible(bool);
 
+  /// Sets how many decimals the spinbox uses for displaying and
+  /// interpreting doubles.
+  void setDecimals(int decimals);
+
 Q_SIGNALS:
   /// When tracking is on (default), valueChanged is emitted when the
   /// user drags the slider.
@@ -231,7 +256,7 @@ protected Q_SLOTS:
   void startChanging();
   void stopChanging();
   void changeValue(double value);
-  
+
 protected:
   virtual bool eventFilter(QObject *obj, QEvent *event);
   
@@ -244,4 +269,6 @@ private:
 
 };
 
+Q_DECLARE_OPERATORS_FOR_FLAGS(ctkSliderWidget::SynchronizeSiblings);
+
 #endif

+ 82 - 6
Libs/Widgets/ctkSpinBox.cpp

@@ -29,7 +29,7 @@
 #include <QDoubleSpinBox>
 #include <QEvent>
 #include <QHBoxLayout>
-#include <QKeySequence>
+#include <QKeyEvent>
 #include <QShortcut>
 #include <QSizePolicy>
 #include <QVariant>
@@ -45,18 +45,27 @@ public:
 
   QDoubleSpinBox* SpinBox;
   ctkSpinBox::SetMode Mode;
+  int DefaultDecimals;
+  ctkSpinBox::DecimalsOptions DOption;
 
   void init();
   // Compare two double previously rounded according to the number of decimals
   bool compare(double x1, double x2) const;
+
+  // Set the number of decimals of the spinbox and emit the signal
+  // No check if they are the same.
+  void setDecimals(int dec);
 };
 
 //-----------------------------------------------------------------------------
 ctkSpinBoxPrivate::ctkSpinBoxPrivate(ctkSpinBox& object) : q_ptr(&object)
 {
   qRegisterMetaType<ctkSpinBox::SetMode>("ctkSpinBox::SetMode");
+  qRegisterMetaType<ctkSpinBox::DecimalsOptions>("ctkSpinBox::DecimalsOption");
   this->SpinBox = 0;
   this->Mode = ctkSpinBox::SetIfDifferent;
+  this->DefaultDecimals = 2;
+  this->DOption = ctkSpinBox::UseShortcuts;
 }
 
 //-----------------------------------------------------------------------------
@@ -77,6 +86,8 @@ void ctkSpinBoxPrivate::init()
   q->setLayout(l);
   q->setSizePolicy(QSizePolicy(QSizePolicy::Minimum,
     QSizePolicy::Fixed, QSizePolicy::ButtonBox));
+
+  this->SpinBox->installEventFilter(q);
 }
 
 //-----------------------------------------------------------------------------
@@ -87,6 +98,14 @@ bool ctkSpinBoxPrivate::compare(double x1, double x2) const
 }
 
 //-----------------------------------------------------------------------------
+void ctkSpinBoxPrivate::setDecimals(int dec)
+{
+  Q_Q(ctkSpinBox);
+  this->SpinBox->setDecimals(dec);
+  emit q->decimalsChanged(dec);
+}
+
+//-----------------------------------------------------------------------------
 ctkSpinBox::ctkSpinBox(QWidget* newParent)
   : QWidget(newParent)
   , d_ptr(new ctkSpinBoxPrivate(*this))
@@ -115,7 +134,6 @@ double ctkSpinBox::value() const
 //-----------------------------------------------------------------------------
 double ctkSpinBox::displayedValue() const
 {
-  Q_D(const ctkSpinBox);
   return this->round(this->value());
 }
 
@@ -304,13 +322,15 @@ int ctkSpinBox::decimals() const
 //-----------------------------------------------------------------------------
 void ctkSpinBox::setDecimals(int dec)
 {
-  Q_D(const ctkSpinBox);
-  if (d->Mode == ctkSpinBox::SetIfDifferent && dec == d->SpinBox->decimals())
+  Q_D(ctkSpinBox);
+  dec = qMax(0, dec);
+  if (d->Mode == ctkSpinBox::SetIfDifferent && dec == this->decimals())
     {
     return;
     }
 
-  d->SpinBox->setDecimals(dec);
+  d->DefaultDecimals = dec;
+  d->setDecimals(d->DefaultDecimals);
 }
 
 //-----------------------------------------------------------------------------
@@ -330,7 +350,7 @@ QDoubleSpinBox* ctkSpinBox::spinBox() const
 //-----------------------------------------------------------------------------
 void ctkSpinBox::setValue(double value)
 {
-  Q_D(const ctkSpinBox);
+  Q_D(ctkSpinBox);
   if (d->Mode == ctkSpinBox::SetIfDifferent)
     {
     this->setValueIfDifferent(value);
@@ -386,3 +406,59 @@ void ctkSpinBox::setSetMode(ctkSpinBox::SetMode newMode)
   d->Mode = newMode;
 }
 
+//-----------------------------------------------------------------------------
+ctkSpinBox::DecimalsOptions ctkSpinBox::decimalsOption()
+{
+  Q_D(const ctkSpinBox);
+  return d->DOption;
+}
+
+//-----------------------------------------------------------------------------
+void ctkSpinBox::setDecimalsOption(ctkSpinBox::DecimalsOptions option)
+{
+  Q_D(ctkSpinBox);
+  if (d->Mode == ctkSpinBox::SetIfDifferent && option == d->DOption)
+    {
+    return;
+    }
+
+  d->DOption = option;
+}
+
+//-----------------------------------------------------------------------------
+bool ctkSpinBox::eventFilter(QObject* obj, QEvent* event)
+{
+  Q_D(ctkSpinBox);
+  if (d->DOption & ctkSpinBox::UseShortcuts &&
+    obj == d->SpinBox && event->type() == QEvent::KeyPress)
+    {
+    QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+    Q_ASSERT(keyEvent);
+    if (keyEvent->modifiers() & Qt::ControlModifier)
+      {
+      if (keyEvent->key() == Qt::Key_Plus
+        || keyEvent->key() == Qt::Key_Equal)
+        {
+        d->setDecimals(this->decimals() + 1);
+        return true;
+        }
+      else if (keyEvent->key() == Qt::Key_Minus)
+        {
+        d->setDecimals(this->decimals() - 1);
+        return true;
+        }
+      else if (keyEvent->key() == Qt::Key_0)
+        {
+        d->setDecimals(d->DefaultDecimals);
+        return true;
+        }
+      }
+
+    return QWidget::eventFilter(obj, event);
+    }
+  else
+    {
+    // pass the event on to the parent class
+    return QWidget::eventFilter(obj, event);
+    }
+}

+ 40 - 3
Libs/Widgets/ctkSpinBox.h

@@ -27,6 +27,9 @@
 #include <QWidget>
 
 class QDoubleSpinBox;
+class QEvent;
+class QKeyEvent;
+class QObject;
 
 // CTK includes
 #include "ctkWidgetsExport.h"
@@ -42,6 +45,10 @@ class CTK_WIDGETS_EXPORT ctkSpinBox : public QWidget
   Q_OBJECT
   Q_ENUMS(SetMode)
   Q_PROPERTY(SetMode setMode READ setMode WRITE setSetMode)
+
+  Q_FLAGS(DecimalsOption DecimalsOptions)
+  Q_PROPERTY(DecimalsOptions decimalsOption READ decimalsOption WRITE setDecimalsOption)
+
   Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment)
   Q_PROPERTY(bool frame READ hasFrame WRITE setFrame)
   Q_PROPERTY(QString prefix READ prefix WRITE setPrefix)
@@ -71,6 +78,25 @@ public:
     SetIfDifferent,
     };
 
+  /// DecimalsOption enums the input style of the spinbox decimals.
+  /// Fixed:
+  /// Behaves just like a QDoubleSpinBox. The maximum number of decimals
+  /// allowed is given by decimals().
+  /// UseShortcuts:
+  /// When the spinbox has focus, entering the shortcut "CTRL +"
+  /// adds decimals to the current number of decimals. "CTRL -" decreases the
+  /// number of decimals and "CTRL 0" returns it to its original decimals()
+  /// value.
+  ///
+  /// Default option is UseShortcuts.
+  /// \sa decimals(), currentDecimals()
+  enum DecimalsOption
+    {
+    Fixed =         0x0,
+    UseShortcuts =  0x01,
+    };
+  Q_DECLARE_FLAGS(DecimalsOptions, DecimalsOption)
+
   typedef QWidget Superclass;
 
   /// Constructor, creates a ctkSpinBox. The look and feel
@@ -128,9 +154,9 @@ public:
   void setMaximum(double max);
   void setRange(double min, double max);
 
-  /// Set/Get number of decimals displayed. For a spinbox dealing only with
-  /// integers, set this to 0.
-  /// \sa addOneDecimal(), removeOneDecimal()
+  /// Set/Get number of displayed decimals.
+  /// For a spinbox dealing only with integers, set this to 0.
+  /// \sa ctkSpinBox::DecimalsOption
   int decimals() const;
   void setDecimals(int decimal);
 
@@ -149,6 +175,11 @@ public:
   ctkSpinBox::SetMode setMode() const;
   void setSetMode(SetMode mode);
 
+  /// Set/Get the option used to input the decimals.
+  /// \sa ctkSpinBox::DecimalsOption
+  ctkSpinBox::DecimalsOptions decimalsOption();
+  void setDecimalsOption(ctkSpinBox::DecimalsOptions option);
+
 public Q_SLOTS:
   /// Set the value of the spinbox following the current mode.
   /// \sa setMode(), value(), setValueIfDifferent(), setValueAlways()
@@ -177,14 +208,20 @@ Q_SIGNALS:
   /// \sa QAbstractSpinbox::editingFinished
   void editingFinished();
 
+  /// Signal emitted when the decimals of the displayed are changed.
+  void decimalsChanged(int);
+
 protected:
   ctkSpinBoxPrivate* const d_ptr;
 
+  bool eventFilter(QObject *obj, QEvent *event);
+
 private:
   Q_DECLARE_PRIVATE(ctkSpinBox);
   Q_DISABLE_COPY(ctkSpinBox);
 };
 
 Q_DECLARE_METATYPE(ctkSpinBox::SetMode)
+Q_DECLARE_OPERATORS_FOR_FLAGS(ctkSpinBox::DecimalsOptions)
 
 #endif //__ctkSpinBox_h