Преглед на файлове

Merge branch 'improve-slider-widget'

* improve-slider-widget:
  Improve ctkSliderWidget and ctkVTKDiscretizableColorTransferWidget
Jean-Christophe Fillion-Robin преди 7 години
родител
ревизия
430d934e60

+ 51 - 32
Libs/Visualization/VTK/Core/vtkDiscretizableColorTransferChart.cpp

@@ -33,6 +33,9 @@
 #include <vtkTable.h>
 #include <vtkTransform2D.h>
 
+// Qt includes
+#include <QDebug>
+
 //#define DEBUG_RANGE
 
 // ----------------------------------------------------------------------------
@@ -148,12 +151,12 @@ vtkDiscretizableColorTransferChart::vtkDiscretizableColorTransferChart()
   this->OriginalRange[1] = VTK_DOUBLE_MIN;
 
 #ifdef DEBUG_RANGE
-  std::cout << "DEBUG_RANGE chart data range = " << this->DataRange[0]
-            << " " << this->DataRange[1] << std::endl;
-  std::cout << "DEBUG_RANGE current range = " << this->CurrentRange[0]
-            << " " << this->CurrentRange[1] << std::endl;
-  std::cout << "DEBUG_RANGE original range = " << this->OriginalRange[0]
-            << " " << this->OriginalRange[1] << std::endl;
+  qDebug() << "DEBUG_RANGE data range = " << this->DataRange[0]
+           << " " << this->DataRange[1];
+  qDebug() << "DEBUG_RANGE current range = " << this->CurrentRange[0]
+           << " " << this->CurrentRange[1];
+  qDebug() << "DEBUG_RANGE original range = " << this->OriginalRange[0]
+           << " " << this->OriginalRange[1];
 #endif
 }
 
@@ -178,29 +181,24 @@ void vtkDiscretizableColorTransferChart::SetColorTransferFunction(
   this->ColorTransferFunction = function;
   this->ClearPlots();
 
-  this->OriginalRange[0] = rangeMin;
-  this->OriginalRange[1] = rangeMax;
-
-  this->CurrentRange[0] = rangeMin;
-  this->CurrentRange[1] = rangeMax;
+  this->SetOriginalRange(rangeMin, rangeMax);
+  this->SetCurrentRange(rangeMin, rangeMax);
 
 #ifdef DEBUG_RANGE
-  std::cout << "DEBUG_RANGE original range = " << this->OriginalRange[0]
-            << " " << this->OriginalRange[1] << std::endl;
-  std::cout << "DEBUG_RANGE current range = " << this->CurrentRange[0]
-            << " " << this->CurrentRange[1] << std::endl;
+  qDebug() << "DEBUG_RANGE original range = " << this->OriginalRange[0]
+           << " " << this->OriginalRange[1];
+  qDebug() << "DEBUG_RANGE current range = " << this->CurrentRange[0]
+           << " " << this->CurrentRange[1];
 #endif
 
   if (function == CTK_NULLPTR)
   {
     this->CompositeHiddenItem = CTK_NULLPTR;
     this->ControlPoints = CTK_NULLPTR;
-    this->DataRange[0] = VTK_DOUBLE_MAX;
-    this->DataRange[1] = VTK_DOUBLE_MIN;
 
 #ifdef DEBUG_RANGE
-  std::cout << "DEBUG_RANGE original range = " << this->DataRange[0]
-            << " " << this->DataRange[1] << std::endl;
+  qDebug() << "DEBUG_RANGE data range = " << this->DataRange[0]
+           << " " << this->DataRange[1];
 #endif
 
     return;
@@ -272,8 +270,8 @@ void vtkDiscretizableColorTransferChart::UpdateMarkerPosition(
     pos.GetData(), 1);
 
   double limitRange[2];
-  limitRange[0] = this->OriginalRange[0]; //std::min(this->OriginalRange[0], this->DataRange[0]);
-  limitRange[1] = this->OriginalRange[1]; //std::max(this->OriginalRange[1], this->DataRange[1]);
+  limitRange[0] = this->OriginalRange[0];
+  limitRange[1] = this->OriginalRange[1];
 
   if (rangeMoving == RangeMoving_MIN)
   {
@@ -435,12 +433,18 @@ void vtkDiscretizableColorTransferChart::SetDataRange(double min, double max)
   this->DataRange[1] = max;
 
 #ifdef DEBUG_RANGE
-  std::cout << "DEBUG_RANGE chart data range = " << this->DataRange[0]
-            << " " << this->DataRange[1] << std::endl;
+  qDebug() << "DEBUG_RANGE data range = " << this->DataRange[0]
+           << " " << this->DataRange[1];
 #endif
 }
 
 // ----------------------------------------------------------------------------
+double* vtkDiscretizableColorTransferChart::GetDataRange()
+{
+  return this->DataRange;
+}
+
+// ----------------------------------------------------------------------------
 void vtkDiscretizableColorTransferChart::SetCurrentRange(
   double min, double max)
 {
@@ -450,7 +454,7 @@ void vtkDiscretizableColorTransferChart::SetCurrentRange(
     return;
   }
 
-  if (this->OriginalRange[0] < this->OriginalRange[1])
+  if (this->OriginalRange[0] <= this->OriginalRange[1])
   {
     min = vtkMath::ClampValue(min, this->OriginalRange[0], this->OriginalRange[1]);
     max = vtkMath::ClampValue(max, this->OriginalRange[0], this->OriginalRange[1]);
@@ -465,8 +469,8 @@ void vtkDiscretizableColorTransferChart::SetCurrentRange(
     this->RemapColorTransferFunction();
 
 #ifdef DEBUG_RANGE
-    std::cout << "DEBUG_RANGE current range = " << this->CurrentRange[0]
-              << " " << this->CurrentRange[1] << std::endl;
+    qDebug() << "DEBUG_RANGE current range = " << this->CurrentRange[0]
+             << " " << this->CurrentRange[1];
 #endif
   }
 }
@@ -474,7 +478,8 @@ void vtkDiscretizableColorTransferChart::SetCurrentRange(
 // ----------------------------------------------------------------------------
 void vtkDiscretizableColorTransferChart::RemapColorTransferFunction()
 {
-  if (this->ColorTransferFunction == CTK_NULLPTR)
+  if (this->ColorTransferFunction == CTK_NULLPTR
+   || this->ControlPoints == CTK_NULLPTR)
   {
     return;
   }
@@ -485,7 +490,7 @@ void vtkDiscretizableColorTransferChart::RemapColorTransferFunction()
 
   if (newRange[0] == newRange[1])
   {
-    newRange[1] += std::numeric_limits<double>::epsilon();
+    newRange[1] += 0.000001;
   }
   else if (this->CurrentRange[0] > this->CurrentRange[1])
   {
@@ -524,20 +529,34 @@ void vtkDiscretizableColorTransferChart::SetOriginalRange(double min, double max
   this->OriginalRange[1] = max;
 
 #ifdef DEBUG_RANGE
-  std::cout << "DEBUG_RANGE original range = " << this->OriginalRange[0]
-            << " " << this->OriginalRange[1] << std::endl;
+  qDebug() << "DEBUG_RANGE original range = " << this->OriginalRange[0]
+           << " " << this->OriginalRange[1];
 #endif
 
   if (this->OriginalRange[0] <= this->OriginalRange[1])
   {
     double currentRange[2];
-    currentRange[0] = vtkMath::ClampValue(this->CurrentRange[0], this->OriginalRange[0], this->OriginalRange[1]);
-    currentRange[1] = vtkMath::ClampValue(this->CurrentRange[1], this->OriginalRange[0], this->OriginalRange[1]);
+    if (this->CurrentRange[0] > this->CurrentRange[1])
+    {
+      currentRange[0] = this->OriginalRange[0];
+      currentRange[1] = this->OriginalRange[1];
+    }
+    else
+    {
+      currentRange[0] = vtkMath::ClampValue(this->CurrentRange[0], this->OriginalRange[0], this->OriginalRange[1]);
+      currentRange[1] = vtkMath::ClampValue(this->CurrentRange[1], this->OriginalRange[0], this->OriginalRange[1]);
+    }
     this->SetCurrentRange(currentRange[0], currentRange[1]);
   }
 }
 
 // ----------------------------------------------------------------------------
+double* vtkDiscretizableColorTransferChart::GetOriginalRange()
+{
+  return this->OriginalRange;
+}
+
+// ----------------------------------------------------------------------------
 void vtkDiscretizableColorTransferChart::CenterRange(double center)
 {
   double width = this->CurrentRange[1] - this->CurrentRange[0];

+ 2 - 0
Libs/Visualization/VTK/Core/vtkDiscretizableColorTransferChart.h

@@ -54,6 +54,7 @@ public:
 
   ///Set/Get the data range
   void SetDataRange(double min, double max);
+  double* GetDataRange();
 
   /// Set/Get the current range
   ///
@@ -64,6 +65,7 @@ public:
 
   ///Set/Get the original range
   void SetOriginalRange(double min, double max);
+  double* GetOriginalRange();
 
   /// Center the current position to the given point
   void CenterRange(double center);

+ 10 - 22
Libs/Visualization/VTK/Core/vtkScalarsToColorsContextItem.cpp

@@ -70,11 +70,6 @@ vtkScalarsToColorsContextItem::vtkScalarsToColorsContextItem()
 
   this->LastSceneSize = vtkVector2i(0, 0);
 
-  this->DataRange[0] = VTK_DOUBLE_MAX;
-  this->DataRange[1] = VTK_DOUBLE_MIN;
-  this->VisibleRange[0] = VTK_DOUBLE_MAX;
-  this->VisibleRange[1] = VTK_DOUBLE_MIN;
-
   vtkSmartPointer<vtkBrush> b = vtkSmartPointer<vtkBrush>::New();
   b->SetOpacityF(0);
 
@@ -175,6 +170,9 @@ void vtkScalarsToColorsContextItem::BuildColorTransferFunction()
     this->PrivateEventForwarder, &EventForwarder::ForwardEvent);
   controlPoints->AddObserver(vtkControlPointsItem::CurrentPointEditEvent,
     this->PrivateEventForwarder, &EventForwarder::ForwardEvent);
+
+  // todo could be replaced by event when OriginalRange changes
+  this->RecalculateChartsRange();
 }
 
 // ----------------------------------------------------------------------------
@@ -195,21 +193,13 @@ void vtkScalarsToColorsContextItem::SetHistogramTable(vtkTable* table,
 // ----------------------------------------------------------------------------
 void vtkScalarsToColorsContextItem::SetDataRange(double min, double max)
 {
-  if (min == this->DataRange[0]
-   && max == this->DataRange[1])
-  {
-    return;
-  }
-
-  this->DataRange[0] = min;
-  this->DataRange[1] = max;
   this->EditorChart->SetDataRange(min, max);
 }
 
 // ----------------------------------------------------------------------------
 double* vtkScalarsToColorsContextItem::GetDataRange()
 {
-  return this->DataRange;
+  return this->EditorChart->GetDataRange();
 }
 
 // ----------------------------------------------------------------------------
@@ -233,14 +223,12 @@ double* vtkScalarsToColorsContextItem::GetCurrentRange()
 // ----------------------------------------------------------------------------
 void vtkScalarsToColorsContextItem::SetVisibleRange(double min, double max)
 {
-  if (min == this->VisibleRange[0]
-   && max == this->VisibleRange[1])
+  if (min == this->GetVisibleRange()[0]
+   && max == this->GetVisibleRange()[1])
   {
     return;
   }
 
-  this->VisibleRange[0] = min;
-  this->VisibleRange[1] = max;
   this->EditorChart->SetOriginalRange(min, max);
   this->RecalculateChartsRange();
 }
@@ -248,7 +236,7 @@ void vtkScalarsToColorsContextItem::SetVisibleRange(double min, double max)
 // ----------------------------------------------------------------------------
 double* vtkScalarsToColorsContextItem::GetVisibleRange()
 {
-  return this->VisibleRange;
+  return this->EditorChart->GetOriginalRange();
 }
 
 // ----------------------------------------------------------------------------
@@ -261,15 +249,15 @@ void vtkScalarsToColorsContextItem::CenterRange(double center)
 void vtkScalarsToColorsContextItem::RecalculateChartsRange()
 {
   this->EditorChart->GetAxis(vtkAxis::BOTTOM)->SetUnscaledRange(
-    this->VisibleRange);
+    this->GetVisibleRange());
   this->EditorChart->RecalculateBounds();
 
   this->HistogramChart->GetAxis(vtkAxis::BOTTOM)->SetUnscaledRange(
-    this->VisibleRange);
+    this->GetVisibleRange());
   this->HistogramChart->RecalculateBounds();
 
   this->PreviewChart->GetAxis(vtkAxis::BOTTOM)->SetUnscaledRange(
-    this->VisibleRange);
+    this->GetVisibleRange());
   this->PreviewChart->RecalculateBounds();
 }
 

+ 0 - 3
Libs/Visualization/VTK/Core/vtkScalarsToColorsContextItem.h

@@ -107,9 +107,6 @@ private:
   /// Cached geometry of the scene
   vtkVector2i LastSceneSize;
 
-  double DataRange[2];
-  double VisibleRange[2];
-
   /// Internal event forwarder
   class EventForwarder;
   EventForwarder* PrivateEventForwarder;

+ 1 - 1
Libs/Visualization/VTK/Widgets/ctkVTKColorTransferFunction.h

@@ -63,7 +63,7 @@ public:
 
   virtual void setControlPointPos(int index, qreal pos);
   virtual void setControlPointValue(int index, const QVariant& value);
-  
+
   virtual void removeControlPoint( qreal pos );
 
   void setColorTransferFunction(vtkColorTransferFunction* colorTransferFunction);

+ 72 - 66
Libs/Visualization/VTK/Widgets/ctkVTKDiscretizableColorTransferWidget.cpp

@@ -31,6 +31,7 @@
 // Qt includes
 #include <QColorDialog>
 #include <QCheckBox>
+#include <QDebug>
 #include <QDoubleValidator>
 #include <QHBoxLayout>
 #include <QIcon>
@@ -68,7 +69,6 @@
 
 //#define DEBUG_RANGE
 
-
 // ----------------------------------------------------------------------------
 class ctkVTKDiscretizableColorTransferWidgetPrivate :
   public Ui_ctkVTKDiscretizableColorTransferWidget
@@ -336,8 +336,8 @@ void ctkVTKDiscretizableColorTransferWidget::copyColorTransferFunction(
 #ifdef DEBUG_RANGE
   if (ctf)
   {
-    std::cout << "DEBUG_RANGE ctf input range = " << ctf->GetRange()[0]
-              << " " << ctf->GetRange()[1] << std::endl;
+    qDebug() << "DEBUG_RANGE ctf input range = " << ctf->GetRange()[0]
+             << " " << ctf->GetRange()[1];
   }
 #endif
 
@@ -364,7 +364,7 @@ void ctkVTKDiscretizableColorTransferWidget::copyColorTransferFunction(
     emit(currentScalarsToColorsChanged(d->scalarsToColorsContextItem->GetDiscretizableColorTransferFunction()));
 
     // set old ranges back
-    if (visibleRange[0] <= visibleRange[0])
+    if (visibleRange[0] < visibleRange[1])
     {
       this->setVisibleRange(visibleRange[0], visibleRange[1]);
       this->setColorTransferFunctionRange(ctfRange[0], ctfRange[1]);
@@ -387,20 +387,39 @@ const
 }
 
 // ----------------------------------------------------------------------------
-void ctkVTKDiscretizableColorTransferWidget::setHistogramInputConnection(
-  vtkAlgorithmOutput* input, bool useInputDataRange)
+void ctkVTKDiscretizableColorTransferWidget::setHistogramConnection(
+  vtkAlgorithmOutput* input)
 {
   Q_D(ctkVTKDiscretizableColorTransferWidget);
 
-  this->initializeHistogramAndDataRange(input);
-  if (useInputDataRange)
+  if (!input)
   {
-    // update visible range, which updates histogram
-    this->resetVisibleRange(ResetVisibleRange::UNION_DATA_AND_CTF);
+    d->histogramFilter = CTK_NULLPTR;
+    d->dataMean = 0.;
+    this->setDataRange(VTK_DOUBLE_MAX, VTK_DOUBLE_MIN);
+    return;
   }
-  else
+
+  d->histogramFilter = vtkSmartPointer<vtkImageAccumulate>::New();
+  d->histogramFilter->SetInputConnection(input);
+}
+
+// ----------------------------------------------------------------------------
+void ctkVTKDiscretizableColorTransferWidget::updateHistogram(
+  bool updateDataRange)
+{
+  Q_D(ctkVTKDiscretizableColorTransferWidget);
+
+  this->updateHistogram();
+
+  if (updateDataRange
+   && d->histogramFilter
+   && d->histogramFilter->GetInputConnection(0, 0))
   {
-    this->updateHistogram();
+    // get min max values from histogram
+    d->dataMean = d->histogramFilter->GetMean()[0];
+    this->setDataRange(d->histogramFilter->GetMin()[0],
+                       d->histogramFilter->GetMax()[0]);
   }
 }
 
@@ -514,10 +533,10 @@ void ctkVTKDiscretizableColorTransferWidget::disableCtfWidgets()
   d->invertColorTransferFunctionButton->setEnabled(false);
 
 #ifdef DEBUG_RANGE
-  std::cout << "DEBUG_RANGE slider range = " << 0
-            << " " << 255 << std::endl;
-  std::cout << "DEBUG_RANGE slider value = " << 0
-            << " " << 1 << std::endl;
+  qDebug() << "DEBUG_RANGE slider range = " << 0
+           << " " << 255;
+  qDebug() << "DEBUG_RANGE slider value = " << 0
+           << " " << 1;
 #endif
 }
 
@@ -544,47 +563,10 @@ void ctkVTKDiscretizableColorTransferWidget::enableCtfWidgets()
   d->rangeSlider->setValues(ctfRange[0], ctfRange[1]);
 
 #ifdef DEBUG_RANGE
-  std::cout << "DEBUG_RANGE slider range = " << visibleRange[0]
-            << " " << visibleRange[1] << std::endl;
-  std::cout << "DEBUG_RANGE slider value = " << ctfRange[0]
-            << " " << ctfRange[1] << std::endl;
-#endif
-}
-
-// ----------------------------------------------------------------------------
-void ctkVTKDiscretizableColorTransferWidget::initializeHistogramAndDataRange(
-    vtkAlgorithmOutput* input)
-{
-  Q_D(ctkVTKDiscretizableColorTransferWidget);
-
-  if (!input)
-  {
-    d->histogramFilter = nullptr;
-    d->dataMean = 0.;
-    this->setDataRange(VTK_DOUBLE_MAX, VTK_DOUBLE_MIN);
-    return;
-  }
-
-  d->histogramFilter = vtkSmartPointer<vtkImageAccumulate>::New();
-  d->histogramFilter->SetInputConnection(input);
-
-  // use histogram filter to compute min max values
-  d->histogramFilter->Update();
-  d->dataMean = d->histogramFilter->GetMean()[0];
-  this->setDataRange(d->histogramFilter->GetMin()[0], d->histogramFilter->GetMax()[0]);
-
-#ifdef DEBUG_RANGE
-  std::cout << "DEBUG_RANGE histo real range = " << *d->histogramFilter->GetMin()
-            << " " << *d->histogramFilter->GetMax() << std::endl;
-  vtkImageData* histogram = d->histogramFilter->GetOutput();
-  int dims[3];
-  histogram->GetDimensions(dims);
-  std::cout << "DEBUG_RANGE histo = ";
-  for(vtkIdType i = 0; i < dims[0]; ++i)
-  {
-      std::cout << *(static_cast<int*>(histogram->GetScalarPointer(i, 0, 0))) << " ";
-  }
-  std::cout << std::endl;
+  qDebug() << "DEBUG_RANGE slider range = " << visibleRange[0]
+           << " " << visibleRange[1];
+  qDebug() << "DEBUG_RANGE slider value = " << ctfRange[0]
+           << " " << ctfRange[1];
 #endif
 }
 
@@ -614,7 +596,8 @@ void ctkVTKDiscretizableColorTransferWidget::updateHistogram()
 
   // fill bins and frequencies
 
-  if (d->histogramFilter == nullptr)
+  if (d->histogramFilter == CTK_NULLPTR
+   || d->histogramFilter->GetInputConnection(0, 0) == CTK_NULLPTR)
   {
     bins->SetNumberOfTuples(1);
     bins->SetTuple1(0, 0);
@@ -624,7 +607,6 @@ void ctkVTKDiscretizableColorTransferWidget::updateHistogram()
   }
   else
   {
-
     double* visibleRange = d->scalarsToColorsContextItem->GetVisibleRange();
 
     int extent = d->histogramFilter->GetComponentExtent()[1];
@@ -640,19 +622,27 @@ void ctkVTKDiscretizableColorTransferWidget::updateHistogram()
     vtkImageData* histogram = d->histogramFilter->GetOutput();
     int* output = static_cast<int*>(histogram->GetScalarPointer());
 
+    // set min and max of the slider widget
+    vtkDataObject* input = d->histogramFilter->GetInputAlgorithm()->GetOutputDataObject(0);
+    vtkImageData* inputImage = vtkImageData::SafeDownCast(input);
+
+    d->rangeSlider->setCustomSpinBoxesLimits(inputImage->GetScalarTypeMin(),
+                                             inputImage->GetScalarTypeMax());
+
+
 #ifdef DEBUG_RANGE
-    std::cout << "DEBUG_RANGE histo input range = " << origin
-              << " " << origin + extent + 1 * spacing << std::endl;
-    std::cout << "DEBUG_RANGE histo real range = " << *d->histogramFilter->GetMin()
-              << " " << *d->histogramFilter->GetMax() << std::endl;
+    qDebug() << "DEBUG_RANGE histo input range = " << origin
+             << " " << origin + (extent + 1) * spacing;
+    qDebug() << "DEBUG_RANGE histo real range = " << *d->histogramFilter->GetMin()
+             << " " << *d->histogramFilter->GetMax();
     int dims[3];
     histogram->GetDimensions(dims);
-    std::cout << "DEBUG_RANGE histo = ";
+    QDebug deb = qDebug();
+    deb << "DEBUG_RANGE histo = ";
     for(vtkIdType i = 0; i < dims[0]; ++i)
     {
-        std::cout << *(static_cast<int*>(histogram->GetScalarPointer(i, 0, 0))) << " ";
+        deb << *(static_cast<int*>(histogram->GetScalarPointer(i, 0, 0))) << " ";
     }
-    std::cout << std::endl;
 #endif
 
     bins->SetNumberOfTuples(extent + 1);
@@ -743,6 +733,22 @@ void ctkVTKDiscretizableColorTransferWidget::setColorTransferFunctionRange(
     return;
   }
 
+  double* visibleRange = this->getVisibleRange();
+  if (min < visibleRange[0]
+   || max > visibleRange[1])
+  {
+    double newRange[2] = { visibleRange[0], visibleRange[1] };
+    if (min < visibleRange[0])
+    {
+      newRange[0] = min;
+    }
+    if (max > visibleRange[1])
+    {
+      newRange[1] = max;
+    }
+    this->setVisibleRange(newRange[0], newRange[1]);
+  }
+
   d->scalarsToColorsContextItem->SetCurrentRange(min, max);
 }
 

+ 2 - 2
Libs/Visualization/VTK/Widgets/ctkVTKDiscretizableColorTransferWidget.h

@@ -70,7 +70,8 @@ public:
   void copyColorTransferFunction(vtkScalarsToColors* ctf, bool useCtfRange = false);
   vtkDiscretizableColorTransferFunction* discretizableColorTransferFunction() const;
 
-  void setHistogramInputConnection(vtkAlgorithmOutput* input, bool useInputDataRange = true);
+  void setHistogramConnection(vtkAlgorithmOutput* input);
+  void updateHistogram(bool updateDataRange);
 
   void setViewBackgroundColor(const QColor& i_color);
   QColor viewBackgroundColor() const;
@@ -112,7 +113,6 @@ protected:
   void updateCtfWidgets();
   void disableCtfWidgets();
   void enableCtfWidgets();
-  void initializeHistogramAndDataRange(vtkAlgorithmOutput* input);
   void updateHistogram();
 
 private:

+ 173 - 25
Libs/Widgets/ctkRangeWidget.cpp

@@ -47,6 +47,8 @@ public:
   void synchronizeSiblingSpinBox(int newWidth);
   bool equal(double v1, double v2)const;
   void relayout();
+  bool useCustomSpinBoxesLimits()const;
+  double clamp(double v, double min, double max);
 
   bool          Tracking;
   bool          Changing;
@@ -55,6 +57,8 @@ public:
   double        MinimumValueBeforeChange;
   double        MaximumValueBeforeChange;
   bool          AutoSpinBoxWidth;
+  double        CustomSpinBoxesLimitsMin;
+  double        CustomSpinBoxesLimitsMax;
   Qt::Alignment SpinBoxAlignment;
   QPointer<ctkValueProxy> Proxy;
 };
@@ -84,6 +88,8 @@ ctkRangeWidgetPrivate::ctkRangeWidgetPrivate(ctkRangeWidget& object)
   this->MinimumValueBeforeChange = 0.;
   this->MaximumValueBeforeChange = 0.;
   this->AutoSpinBoxWidth = true;
+  this->CustomSpinBoxesLimitsMin = std::numeric_limits<double>::max();
+  this->CustomSpinBoxesLimitsMax = std::numeric_limits<double>::min();
   this->SpinBoxAlignment = Qt::AlignVCenter;
 }
 
@@ -212,6 +218,26 @@ void ctkRangeWidgetPrivate::relayout()
 }
 
 // --------------------------------------------------------------------------
+bool ctkRangeWidgetPrivate::useCustomSpinBoxesLimits()const
+{
+  return this->CustomSpinBoxesLimitsMin <= this->CustomSpinBoxesLimitsMax;
+}
+
+// --------------------------------------------------------------------------
+double ctkRangeWidgetPrivate::clamp(double v, double min, double max)
+{
+  if (v < min)
+  {
+    v = min;
+  }
+  if (v > max)
+  {
+    v = max;
+  }
+  return v;
+}
+
+// --------------------------------------------------------------------------
 ctkRangeWidget::ctkRangeWidget(QWidget* _parent) : Superclass(_parent)
   , d_ptr(new ctkRangeWidgetPrivate(*this))
 {
@@ -219,8 +245,16 @@ ctkRangeWidget::ctkRangeWidget(QWidget* _parent) : Superclass(_parent)
   
   d->setupUi(this);
 
-  d->MinimumSpinBox->setRange(d->Slider->minimum(), d->Slider->maximum());
-  d->MaximumSpinBox->setRange(d->Slider->minimum(), d->Slider->maximum());
+  if (d->useCustomSpinBoxesLimits())
+    {
+    d->MinimumSpinBox->setRange(d->CustomSpinBoxesLimitsMin, d->Slider->maximum());
+    d->MaximumSpinBox->setRange(d->Slider->minimum(), d->CustomSpinBoxesLimitsMax);
+    }
+  else
+    {
+    d->MinimumSpinBox->setRange(d->Slider->minimum(), d->Slider->maximum());
+    d->MaximumSpinBox->setRange(d->Slider->minimum(), d->Slider->maximum());
+    }
   d->MinimumSpinBox->setValue(d->Slider->minimumValue());
   d->MaximumSpinBox->setValue(d->Slider->maximumValue());
   
@@ -239,7 +273,10 @@ ctkRangeWidget::~ctkRangeWidget()
 double ctkRangeWidget::minimum()const
 {
   Q_D(const ctkRangeWidget);
-  Q_ASSERT(d->equal(d->MinimumSpinBox->minimum(),d->Slider->minimum()));
+  if (!d->useCustomSpinBoxesLimits())
+    {
+    Q_ASSERT(d->equal(d->MinimumSpinBox->minimum(),d->Slider->minimum()));
+    }
   return d->Slider->minimum();
 }
 
@@ -247,7 +284,10 @@ double ctkRangeWidget::minimum()const
 double ctkRangeWidget::maximum()const
 {
   Q_D(const ctkRangeWidget);
-  Q_ASSERT(d->equal(d->MaximumSpinBox->maximum(), d->Slider->maximum()));
+  if (!d->useCustomSpinBoxesLimits())
+    {
+    Q_ASSERT(d->equal(d->MaximumSpinBox->maximum(), d->Slider->maximum()));
+    }
   return d->Slider->maximum();
 }
 
@@ -257,16 +297,26 @@ void ctkRangeWidget::setMinimum(double min)
   Q_D(ctkRangeWidget);
   bool blocked = d->MinimumSpinBox->blockSignals(true);
   blocked = d->MaximumSpinBox->blockSignals(true);
-  d->MinimumSpinBox->setMinimum(min);
+  if (d->useCustomSpinBoxesLimits())
+    {
+    d->MinimumSpinBox->setMinimum(d->CustomSpinBoxesLimitsMin);
+    }
+  else
+    {
+    d->MinimumSpinBox->setMinimum(min);
+    }
   d->MaximumSpinBox->setMinimum(min);
   d->MinimumSpinBox->blockSignals(blocked);
   d->MaximumSpinBox->blockSignals(blocked);
   // SpinBox can truncate min (depending on decimals).
   // use Spinbox's min to set Slider's min
   d->SettingSliderRange = true;
-  d->Slider->setMinimum(d->MinimumSpinBox->minimum());
+  d->Slider->setMinimum(d->MaximumSpinBox->minimum());
   d->SettingSliderRange = false;
-  Q_ASSERT(d->equal(d->MinimumSpinBox->minimum(),d->Slider->minimum()));
+  if (!d->useCustomSpinBoxesLimits())
+    {
+    Q_ASSERT(d->equal(d->MinimumSpinBox->minimum(),d->Slider->minimum()));
+    }
   d->updateSpinBoxWidth();
 }
 
@@ -276,16 +326,26 @@ void ctkRangeWidget::setMaximum(double max)
   Q_D(ctkRangeWidget);
   bool blocked = d->MinimumSpinBox->blockSignals(true);
   blocked = d->MaximumSpinBox->blockSignals(true);
+  if (d->useCustomSpinBoxesLimits())
+    {
+    d->MaximumSpinBox->setMaximum(d->CustomSpinBoxesLimitsMax);
+    }
+  else
+    {
+    d->MaximumSpinBox->setMaximum(max);
+    }
   d->MinimumSpinBox->setMaximum(max);
-  d->MaximumSpinBox->setMaximum(max);
   d->MinimumSpinBox->blockSignals(blocked);
   d->MaximumSpinBox->blockSignals(blocked);
   // SpinBox can truncate max (depending on decimals).
   // use Spinbox's max to set Slider's max
   d->SettingSliderRange = true;
-  d->Slider->setMaximum(d->MaximumSpinBox->maximum());
+  d->Slider->setMaximum(d->MinimumSpinBox->maximum());
   d->SettingSliderRange = false;
-  Q_ASSERT(d->equal(d->MaximumSpinBox->maximum(), d->Slider->maximum()));
+  if (!d->useCustomSpinBoxesLimits())
+    {
+    Q_ASSERT(d->equal(d->MaximumSpinBox->maximum(), d->Slider->maximum()));
+    }
   d->updateSpinBoxWidth();
 }
 
@@ -294,28 +354,56 @@ void ctkRangeWidget::setRange(double min, double max)
 {
   Q_D(ctkRangeWidget);
 
+  if (d->useCustomSpinBoxesLimits())
+    {
+    min = d->clamp(min, d->CustomSpinBoxesLimitsMin,
+                        d->CustomSpinBoxesLimitsMax);
+    max = d->clamp(max, d->CustomSpinBoxesLimitsMin,
+                        d->CustomSpinBoxesLimitsMax);
+    }
+
   double oldMin = d->MinimumSpinBox->minimum();
   double oldMax = d->MaximumSpinBox->maximum();
   bool blocked = d->MinimumSpinBox->blockSignals(true);
-  d->MinimumSpinBox->setRange(qMin(min,max), qMax(min,max));
+  if (d->useCustomSpinBoxesLimits())
+    {
+    d->MinimumSpinBox->setRange(d->CustomSpinBoxesLimitsMin, qMax(min,max));
+    }
+  else
+    {
+    d->MinimumSpinBox->setRange(qMin(min,max), qMax(min,max));
+    }
   d->MinimumSpinBox->blockSignals(blocked);
   blocked = d->MaximumSpinBox->blockSignals(true);
-  d->MaximumSpinBox->setRange(qMin(min,max), qMax(min,max));
+  if (d->useCustomSpinBoxesLimits())
+    {
+    d->MaximumSpinBox->setRange(qMin(min,max), d->CustomSpinBoxesLimitsMax);
+    }
+  else
+    {
+    d->MaximumSpinBox->setRange(qMin(min,max), qMax(min,max));
+    }
   d->MaximumSpinBox->blockSignals(blocked);
   // SpinBox can truncate the range (depending on decimals).
   // use Spinbox's range to set Slider's range
   d->SettingSliderRange = true;
-  d->Slider->setRange(d->MinimumSpinBox->minimum(), d->MaximumSpinBox->maximum());
+  d->Slider->setRange(d->MaximumSpinBox->minimum(), d->MinimumSpinBox->maximum());
   d->SettingSliderRange = false;
-  Q_ASSERT(d->equal(d->MinimumSpinBox->minimum(), d->Slider->minimum()));
-  Q_ASSERT(d->equal(d->MaximumSpinBox->maximum(), d->Slider->maximum()));
-  Q_ASSERT(d->equal(d->Slider->minimumValue(), d->MinimumSpinBox->value()));
-  Q_ASSERT(d->equal(d->Slider->maximumValue(), d->MaximumSpinBox->value()));
+  if (!d->useCustomSpinBoxesLimits())
+    {
+    Q_ASSERT(d->equal(d->MinimumSpinBox->minimum(), d->Slider->minimum()));
+    Q_ASSERT(d->equal(d->MaximumSpinBox->maximum(), d->Slider->maximum()));
+    Q_ASSERT(d->equal(d->Slider->minimumValue(), d->MinimumSpinBox->value()));
+    Q_ASSERT(d->equal(d->Slider->maximumValue(), d->MaximumSpinBox->value()));
+    }
   d->updateSpinBoxWidth();
-  if (oldMin != d->MinimumSpinBox->minimum() ||
-      oldMax != d->MaximumSpinBox->maximum())
+  if (!d->useCustomSpinBoxesLimits())
     {
-    emit rangeChanged(d->MinimumSpinBox->minimum(), d->MaximumSpinBox->maximum());
+    if (oldMin != d->MinimumSpinBox->minimum() ||
+        oldMax != d->MaximumSpinBox->maximum())
+      {
+      emit rangeChanged(d->MinimumSpinBox->minimum(), d->MaximumSpinBox->maximum());
+      }
     }
 }
 
@@ -323,8 +411,11 @@ void ctkRangeWidget::setRange(double min, double max)
 void ctkRangeWidget::range(double range[2])const
 {
   Q_D(const ctkRangeWidget);
-  Q_ASSERT(d->equal(d->MinimumSpinBox->maximum(), d->Slider->minimum()));
-  Q_ASSERT(d->equal(d->MaximumSpinBox->maximum(), d->Slider->maximum()));
+  if (!d->useCustomSpinBoxesLimits())
+    {
+    Q_ASSERT(d->equal(d->MinimumSpinBox->minimum(), d->Slider->minimum()));
+    Q_ASSERT(d->equal(d->MaximumSpinBox->maximum(), d->Slider->maximum()));
+    }
   range[0] = d->Slider->minimum();
   range[1] = d->Slider->maximum();
 }
@@ -464,6 +555,17 @@ void ctkRangeWidget::setSliderValues()
     {
     return;
     }
+  if (d->useCustomSpinBoxesLimits())
+    {
+    if (d->MinimumSpinBox->value() < d->Slider->minimum())
+      {
+      d->Slider->setMinimum(d->MinimumSpinBox->value());
+      }
+    if (d->MaximumSpinBox->value() > d->Slider->maximum())
+      {
+      d->Slider->setMaximum(d->MaximumSpinBox->value());
+      }
+    }
   d->Slider->setValues(d->MinimumSpinBox->value(), d->MaximumSpinBox->value());
 }
 
@@ -475,7 +577,14 @@ void ctkRangeWidget::setMinimumToMaximumSpinBox(double minimum)
     {
     return;
     }
-  d->MaximumSpinBox->setRange(minimum, d->Slider->maximum());
+  if (d->useCustomSpinBoxesLimits())
+    {
+    d->MaximumSpinBox->setRange(minimum, d->CustomSpinBoxesLimitsMax);
+    }
+  else
+    {
+    d->MaximumSpinBox->setRange(minimum, d->Slider->maximum());
+    }
 }
 
 // --------------------------------------------------------------------------
@@ -486,7 +595,14 @@ void ctkRangeWidget::setMaximumToMinimumSpinBox(double maximum)
     {
     return;
     }
-  d->MinimumSpinBox->setRange(d->Slider->minimum(), maximum);
+  if (d->useCustomSpinBoxesLimits())
+    {
+    d->MinimumSpinBox->setRange(d->CustomSpinBoxesLimitsMin, maximum);
+    }
+  else
+    {
+    d->MinimumSpinBox->setRange(d->Slider->minimum(), maximum);
+    }
 }
 
 // --------------------------------------------------------------------------
@@ -643,7 +759,10 @@ void ctkRangeWidget::setDecimals(int newDecimals)
   // i.e. 50.55 with 2 decimals -> 51 with 0 decimals
   // As the SpinBox range change doesn't fire signals, 
   // we have to do the synchronization manually here
-  d->Slider->setRange(d->MinimumSpinBox->minimum(), d->MaximumSpinBox->maximum());
+  if (!d->useCustomSpinBoxesLimits())
+    {
+    d->Slider->setRange(d->MinimumSpinBox->minimum(), d->MaximumSpinBox->maximum());
+    }
 }
 
 // --------------------------------------------------------------------------
@@ -776,6 +895,35 @@ void ctkRangeWidget::setSymmetricMoves(bool symmetry)
   d->Slider->setSymmetricMoves(symmetry);
 }
 
+// --------------------------------------------------------------------------
+void ctkRangeWidget::setCustomSpinBoxesLimits(double min, double max)
+{
+  Q_D(ctkRangeWidget);
+
+  if (max < min)
+    {
+    return;
+    }
+
+  d->CustomSpinBoxesLimitsMin = min;
+  d->CustomSpinBoxesLimitsMax = max;
+  this->setRange(d->Slider->minimum(), d->Slider->maximum());
+}
+
+// --------------------------------------------------------------------------
+double ctkRangeWidget::customSpinBoxesLimitsMin()const
+{
+  Q_D(const ctkRangeWidget);
+  return d->CustomSpinBoxesLimitsMin;
+}
+
+// --------------------------------------------------------------------------
+double ctkRangeWidget::customSpinBoxesLimitsMax()const
+{
+  Q_D(const ctkRangeWidget);
+  return d->CustomSpinBoxesLimitsMax;
+}
+
 // -------------------------------------------------------------------------
 ctkDoubleRangeSlider* ctkRangeWidget::slider()const
 {

+ 8 - 0
Libs/Widgets/ctkRangeWidget.h

@@ -56,6 +56,8 @@ class CTK_WIDGETS_EXPORT ctkRangeWidget : public QWidget
   Q_PROPERTY(Qt::Alignment spinBoxAlignment READ spinBoxAlignment WRITE setSpinBoxAlignment)
   Q_PROPERTY(bool tracking READ hasTracking WRITE setTracking)
   Q_PROPERTY(bool symmetricMoves READ symmetricMoves WRITE setSymmetricMoves)
+  Q_PROPERTY(double customSpinBoxesLimitsMin READ customSpinBoxesLimitsMin)
+  Q_PROPERTY(double customSpinBoxesLimitsMax READ customSpinBoxesLimitsMax)
 
 public:
   /// Superclass typedef
@@ -179,6 +181,12 @@ public:
   virtual bool symmetricMoves()const;
   virtual void setSymmetricMoves(bool symmetry);
 
+  ///
+  /// This property sets custom limits for spin boxes.
+  virtual void setCustomSpinBoxesLimits(double min, double max);
+  virtual double customSpinBoxesLimitsMin()const;
+  virtual double customSpinBoxesLimitsMax()const;
+
   /// Return the slider of the range widget.
   /// \sa minimumSpinBox(), maximumSpinBox()
   virtual ctkDoubleRangeSlider* slider()const;