소스 검색

Propagate value proxy to other widgets

Adding the value proxy to:
 - ctkDoubleSlider
 - ctkSliderWidget
 - ctkCoordinatesWidget
 - ctkRangeWidget
 - ctkDoubleRangeSlider

For the ctkSliderWidget, since it's using the ctkDoubleSlider and the
ctkDoubleSpinBox, we can simply pass the value proxy to these. The same
thing applies to ctkCoordinatesWidget.
For the others, it's like the ctkDoubleSpinBox. The proxy is set between
the values accessors.

The tests added are very similar to the ctkDoubleSpinBox test, just tweaked
to these classes specific API.
Johan Andruejol 12 년 전
부모
커밋
1f1dc9c7f0

+ 15 - 0
Libs/Widgets/Testing/Cpp/CMakeLists.txt

@@ -23,12 +23,15 @@ set(TEST_SOURCES
   ctkConsoleTest1.cpp
   ctkCoordinatesWidgetTest.cpp
   ctkCoordinatesWidgetTest1.cpp
+  ctkCoordinatesWidgetValueProxyTest.cpp
   ctkCrosshairLabelTest1.cpp
   ctkDirectoryButtonTest1.cpp
   ctkDoubleRangeSliderTest1.cpp
   ctkDoubleRangeSliderTest2.cpp
+  ctkDoubleRangeSliderValueProxyTest.cpp
   ctkDoubleSliderTest.cpp
   ctkDoubleSliderTest1.cpp
+  ctkDoubleSliderValueProxyTest.cpp
   ctkDoubleSpinBoxTest.cpp
   ctkDoubleSpinBoxTest1.cpp
   ctkDoubleSpinBoxValueProxyTest.cpp
@@ -64,6 +67,7 @@ set(TEST_SOURCES
   ctkRangeSliderTest1.cpp
   ctkRangeWidgetTest.cpp
   ctkRangeWidgetTest1.cpp
+  ctkRangeWidgetValueProxyTest.cpp
   ctkDateRangeWidgetTest1.cpp
   ctkScreenshotDialogTest1.cpp
   ctkSearchBoxTest1.cpp
@@ -76,6 +80,7 @@ set(TEST_SOURCES
   ctkSliderWidgetTest.cpp
   ctkSliderWidgetTest1.cpp
   ctkSliderWidgetTest2.cpp
+  ctkSliderWidgetValueProxyTest.cpp
   ctkThumbnailListWidgetTest1.cpp
   ctkThumbnailLabelTest1.cpp
   ctkToolTipTrapperTest1.cpp
@@ -182,7 +187,10 @@ set(Tests_MOC_CPP)
 QT4_WRAP_CPP(Tests_MOC_CPP ${Tests_MOC_SRCS})
 QT4_GENERATE_MOCS(
   ctkCoordinatesWidgetTest.cpp
+  ctkCoordinatesWidgetValueProxyTest.cpp
+  ctkDoubleRangeSliderValueProxyTest.cpp
   ctkDoubleSliderTest.cpp
+  ctkDoubleSliderValueProxyTest.cpp
   ctkDoubleSpinBoxTest.cpp
   ctkDoubleSpinBoxValueProxyTest.cpp
   ctkFlatProxyModelTest.cpp
@@ -194,8 +202,10 @@ QT4_GENERATE_MOCS(
   ctkPathListWidgetWithButtonsTest.cpp
   ctkRangeSliderTest.cpp
   ctkRangeWidgetTest.cpp
+  ctkRangeWidgetValueProxyTest.cpp
   ctkSettingsPanelTest.cpp
   ctkSliderWidgetTest.cpp
+  ctkSliderWidgetValueProxyTest.cpp
   )
 set(Tests_UI_CPP)
 if(TEST_UI_FORMS)
@@ -233,13 +243,16 @@ SIMPLE_TEST( ctkCompleterTest1 )
 SIMPLE_TEST( ctkConsoleTest1 )
 SIMPLE_TEST( ctkCoordinatesWidgetTest )
 SIMPLE_TEST( ctkCoordinatesWidgetTest1 )
+SIMPLE_TEST( ctkCoordinatesWidgetValueProxyTest )
 SIMPLE_TEST( ctkCrosshairLabelTest1 )
 SIMPLE_TEST( ctkDateRangeWidgetTest1 )
 SIMPLE_TEST( ctkDirectoryButtonTest1 )
 SIMPLE_TEST( ctkDoubleRangeSliderTest1 )
 SIMPLE_TEST( ctkDoubleRangeSliderTest2 )
+SIMPLE_TEST( ctkDoubleRangeSliderValueProxyTest )
 SIMPLE_TEST( ctkDoubleSliderTest )
 SIMPLE_TEST( ctkDoubleSliderTest1 )
+SIMPLE_TEST( ctkDoubleSliderValueProxyTest )
 SIMPLE_TEST( ctkDoubleSpinBoxTest )
 SIMPLE_TEST( ctkDoubleSpinBoxTest1 )
 SIMPLE_TEST( ctkDoubleSpinBoxValueProxyTest )
@@ -278,6 +291,7 @@ SIMPLE_TEST( ctkRangeSliderTest )
 SIMPLE_TEST( ctkRangeSliderTest1 )
 SIMPLE_TEST( ctkRangeWidgetTest )
 SIMPLE_TEST( ctkRangeWidgetTest1 )
+SIMPLE_TEST( ctkRangeWidgetValueProxyTest )
 SIMPLE_TEST( ctkResizableFrameTest1 )
 SIMPLE_TEST( ctkScreenshotDialogTest1 )
 SIMPLE_TEST( ctkSearchBoxTest1 )
@@ -290,6 +304,7 @@ SIMPLE_TEST( ctkSignalMapperTest1 )
 SIMPLE_TEST( ctkSliderWidgetTest )
 SIMPLE_TEST( ctkSliderWidgetTest1 )
 SIMPLE_TEST( ctkSliderWidgetTest2 )
+SIMPLE_TEST( ctkSliderWidgetValueProxyTest )
 SIMPLE_TEST( ctkThumbnailListWidgetTest1 )
 SIMPLE_TEST( ctkThumbnailLabelTest1 )
 SIMPLE_TEST( ctkToolTipTrapperTest1 )

+ 265 - 0
Libs/Widgets/Testing/Cpp/ctkCoordinatesWidgetValueProxyTest.cpp

@@ -0,0 +1,265 @@
+/*=========================================================================
+
+  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.
+
+=========================================================================*/
+
+// Qt includes
+#include <QApplication>
+
+// CTK includes
+#include "ctkCoordinatesWidget.h"
+#include "ctkLinearValueProxy.h"
+#include "ctkTest.h"
+#include "ctkValueProxy.h"
+
+namespace
+{
+class Spy : public QObject
+{
+  Q_OBJECT
+
+public:
+  explicit Spy()
+    {
+    AcknowledgedSignals = 0;
+    }
+
+  void getSpyReport(QString coordinatesString)
+    {
+    ctkTest::COMPARE(AcknowledgedSignals, 1);
+    AcknowledgedSignals = 0;
+
+    QStringList coordinatesList = coordinatesString.split(',');
+    ctkTest::COMPARE(coordinatesList.count(), this->Coordinates.size());
+    for (int i = 0; i < this->Coordinates.size(); ++i)
+      {
+      ctkTest::COMPARE(this->Coordinates[i], coordinatesList[i].toDouble());
+      }
+    this->Coordinates.clear();
+    };
+
+public slots:
+  void onCoordinatesChanged(double* coordinates)
+    {
+    ctkCoordinatesWidget* coordWidget =
+      qobject_cast<ctkCoordinatesWidget*>(this->sender());
+    QVERIFY(coordWidget != 0);
+    for (int i = 0; i < coordWidget->dimension(); ++i)
+      {
+      this->Coordinates.append(coordinates[i]);
+      }
+    ++AcknowledgedSignals;
+    }
+
+public:
+  QList<double> Coordinates;
+  int AcknowledgedSignals;
+};
+
+// ----------------------------------------------------------------------------
+QString coordinatesFromValue(double val)
+{
+  return QString("%1,%1,%1").arg(val);
+}
+
+} // end namespace
+
+// ----------------------------------------------------------------------------
+class ctkCoordinatesWidgetValueProxyTester: public QObject
+{
+  Q_OBJECT
+private slots:
+
+  void testSetValue();
+  void testSetValue_data();
+};
+
+//-----------------------------------------------------------------------------
+void ctkCoordinatesWidgetValueProxyTester::testSetValue()
+{
+  // Setup
+  ctkCoordinatesWidget coordinatesWidget;
+  coordinatesWidget.setMinimum(-200);
+  coordinatesWidget.setMaximum(200);
+  coordinatesWidget.setCoordinatesAsString("13.2, 13.2, 13.2");
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  coordinatesWidget.setValueProxy(&proxy);
+
+  // Spy
+  Spy valueSpy;
+  QObject::connect(&coordinatesWidget, SIGNAL(coordinatesChanged(double*)),
+                   &valueSpy, SLOT(onCoordinatesChanged(double*)));
+
+  // Test
+  QFETCH(QString, coordinates);
+  coordinatesWidget.setCoordinatesAsString(coordinates);
+
+  QFETCH(QString, expectedCoordinates);
+  valueSpy.getSpyReport(expectedCoordinates);
+  QCOMPARE(coordinatesWidget.coordinatesAsString(), expectedCoordinates);
+}
+
+//-----------------------------------------------------------------------------
+void ctkCoordinatesWidgetValueProxyTester::testSetValue_data()
+{
+  QTest::addColumn<double>("coefficient");
+  QTest::addColumn<double>("offset");
+  QTest::addColumn<QString>("coordinates");
+  QTest::addColumn<QString>("expectedCoordinates");
+
+  //---------------------------------------------------------------------------
+  // Offset
+  QTest::newRow("Offset only") << 1.0 << 42.19 << "0.1,0.2,0.3"
+    << "0.1,0.2,0.3";
+
+  QTest::newRow("Offset only: less than min")
+    << 1.0 << 42.19 << "-250.0,-900.0,-3000.0" << "-242.19,-242.19,-242.19";
+  QTest::newRow("Offset only: less than min but ok with offset")
+    << 1.0 << 42.19 << "-240.3,-232.1,-200.01" << "-240.3,-232.1,-200.01";
+  QTest::newRow("Offset only: less than min with offset")
+    << 1.0 << -42.19 << "-160.15,-199.99,-159.0" << "-157.81,-157.81,-157.81";
+
+  QTest::newRow("Offset only: more than max with offset")
+    << 1.0 << 42.19 << "160.0,199.9,163.32" << "157.81,157.81,157.81";
+  QTest::newRow("Offset only: more than max")
+    << 1.0 << -42.19 << "4830.0,250.01,1e6" << "242.19,242.19,242.19";
+  QTest::newRow("Offset only: less than max but ok with offset")
+    << 1.0 << -42.19 << "210.3,200.01,241.03" << "210.3,200.01,241.03";
+
+  QTest::newRow("Offset only: max")
+    << 1.0 << 42.19
+    << coordinatesFromValue(std::numeric_limits<double>::max())
+    << "157.81,157.81,157.81";
+
+  QTest::newRow("Offset only:  min")
+    << 1.0 << 42.19
+    << coordinatesFromValue(- std::numeric_limits<double>::max())
+    << "-242.19,-242.19,-242.19";
+
+  QTest::newRow("Offset only: infinity")
+    << 1.0 << 42.19
+    << coordinatesFromValue(std::numeric_limits<double>::infinity())
+    << "157.81,157.81,157.81";
+
+  QTest::newRow("Offset only:  - infinity")
+    << 1.0 << 42.19
+    << coordinatesFromValue(- std::numeric_limits<double>::infinity())
+    << "-242.19,-242.19,-242.19";
+
+  QTest::newRow("Offset only: Nan")
+    << 1.0 << 42.19
+    << coordinatesFromValue(std::numeric_limits<double>::quiet_NaN())
+    << "157.81,157.81,157.81";
+
+  //---------------------------------------------------------------------------
+  // Coefficient
+  QTest::newRow("Coeff only") << 5.0 << 0.0 << "0.1,0.2,0.3"
+    << "0.1,0.2,0.3";
+
+  QTest::newRow("Coeff only: less than min")
+    << 5.0 << 0.0 << "-510.08,-2000,-1000000." << "-40,-40,-40";
+  QTest::newRow("Coeff only: less than min but ok with coeff")
+    << 0.5 << 0.0 << "-250.08,-399.99,-120" << "-250.08,-399.99,-120";
+  QTest::newRow("Coeff only: less than min with coeff")
+    << 5.0 << 0.0<< "-42.08,-199.99,-40.01" << "-40,-40,-40";
+
+  QTest::newRow("Coeff only: more than max with coeff")
+    << 5.0 << 0.0 << "160.08,40.01,199.99" << "40,40,40";
+  QTest::newRow("Coeff only: more than max")
+    << 5.0 << 0.0 << "510.08,2000,1000000." << "40,40,40";
+  QTest::newRow("Offset only: more than max but ok with coeff")
+    << 0.5 << 0.0 << "380.08,399.99,200.01" << "380.08,399.99,200.01";
+
+  QTest::newRow("Offset only: max")
+    << 5.0 << 0.0
+    << coordinatesFromValue(std::numeric_limits<double>::max())
+    << "40,40,40";
+
+  QTest::newRow("Offset only:  min")
+    << 5.0 << 0.0
+    << coordinatesFromValue(- std::numeric_limits<double>::max())
+    << "-40,-40,-40";
+
+  QTest::newRow("Offset only: infinity")
+    << 5.0 << 0.0
+    << coordinatesFromValue(std::numeric_limits<double>::infinity())
+    << "40,40,40";
+
+  QTest::newRow("Offset only:  - infinity")
+    << 5.0 << 0.0
+    << coordinatesFromValue(- std::numeric_limits<double>::infinity())
+    << "-40,-40,-40";
+
+  QTest::newRow("Offset only: Nan")
+    << 5.0 << 0.0
+    << coordinatesFromValue(std::numeric_limits<double>::quiet_NaN())
+    << "40,40,40";
+
+  //---------------------------------------------------------------------------
+  // Linear
+  QTest::newRow("Linear") << 5.0 << 12.0 << "0.1,0.2,0.3"
+    << "0.1,0.2,0.3";
+
+  QTest::newRow("Linear: less than min")
+    << 5.0 << 12.0 << "-510.08,-2000,-1000000." << "-42.4,-42.4,-42.4";
+  QTest::newRow("Linear: less than min but ok with function")
+    << 0.5 << 12.0 << "-250.08,-411.99,-120" << "-250.08,-411.99,-120";
+  QTest::newRow("Linear: less than min with function")
+    << 5.0 << 12.0 << "-64.08,-199.99,-52.01" << "-42.4,-42.4,-42.4";
+
+  QTest::newRow("Linear: more than max with function")
+    << 5.0 << 12.0 << "64.08,189.99,37.61" << "37.6,37.6,37.6";
+  QTest::newRow("Linear: more than max")
+    << 5.0 << 12.0 << "200.01,900000.0,411.99" << "37.6,37.6,37.6";
+  QTest::newRow("Offset only: more than max but ok with function")
+    << 0.5 << 12.0 << "209.01,356.9,350.9" << "209.01,356.9,350.9";
+
+  QTest::newRow("Linear: max")
+    << 5.0 << 12.0
+    << coordinatesFromValue(std::numeric_limits<double>::max())
+    << "37.6,37.6,37.6";
+
+  QTest::newRow("Offset only:  min")
+    << 5.0 << 12.0
+    << coordinatesFromValue(- std::numeric_limits<double>::max())
+    << "-42.4,-42.4,-42.4";
+
+  QTest::newRow("Offset only: infinity")
+    << 5.0 << 12.0
+    << coordinatesFromValue(std::numeric_limits<double>::infinity())
+    << "37.6,37.6,37.6";
+
+  QTest::newRow("Offset only:  - infinity")
+    << 5.0 << 12.0
+    << coordinatesFromValue(- std::numeric_limits<double>::infinity())
+    << "-42.4,-42.4,-42.4";
+
+  QTest::newRow("Offset only: Nan")
+    << 5.0 << 12.0
+    << coordinatesFromValue(std::numeric_limits<double>::quiet_NaN())
+    << "37.6,37.6,37.6";
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkCoordinatesWidgetValueProxyTest)
+#include "moc_ctkCoordinatesWidgetValueProxyTest.cpp"

+ 641 - 0
Libs/Widgets/Testing/Cpp/ctkDoubleRangeSliderValueProxyTest.cpp

@@ -0,0 +1,641 @@
+/*=========================================================================
+
+  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.commontk.org/LICENSE
+
+  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 <QApplication>
+#include <QTest>
+
+// CTK includes
+#include "ctkDoubleRangeSlider.h"
+#include "ctkLinearValueProxy.h"
+#include "ctkTest.h"
+#include "ctkValueProxy.h"
+
+// STD includes
+#include <limits>
+
+namespace
+{
+
+//-----------------------------------------------------------------------------
+void getSpyReport(QSignalSpy& spy, double expectedValue)
+{
+  QCOMPARE(spy.count(), 1);
+
+  QList<QVariant> arguments = spy.takeFirst(); // take the first signal
+  ctkTest::COMPARE(arguments.at(0).toDouble(), expectedValue);
+}
+
+//-----------------------------------------------------------------------------
+class CustomSpy : public QObject
+{
+  Q_OBJECT
+public:
+  CustomSpy()
+    {
+    this->AcknowledgedSignals = 0;
+    }
+
+public slots:
+  void onValuesChanged(double min, double max)
+    {
+    ++this->AcknowledgedSignals;
+    this->MinSpyData.append(min);
+    this->MaxSpyData.append(max);
+    }
+
+public:
+  void getSpyReport(double min, double max)
+    {
+    QCOMPARE(this->AcknowledgedSignals, 1);
+
+    QCOMPARE(this->MinSpyData.size(), 1);
+    ctkTest::COMPARE(this->MinSpyData[0], min);
+    QCOMPARE(this->MaxSpyData.size(), 1);
+    ctkTest::COMPARE(this->MaxSpyData[0], max);
+    }
+
+  QList<double> MinSpyData;
+  QList<double> MaxSpyData;
+  int AcknowledgedSignals;
+};
+
+} // end namespace
+
+//-----------------------------------------------------------------------------
+class ctkDoubleRangeSliderValueProxyTester: public QObject
+{
+  Q_OBJECT
+private slots:
+
+  void testSetValues();
+  void testSetValues_data();
+
+  void testSetMinValue();
+  void testSetMinValue_data() { testSetValueCommonData(); };
+
+  void testSetMaxValue();
+  void testSetMaxValue_data(){ testSetValueCommonData(); };
+
+  void testSetMinPosition();
+  void testSetMinPosition_data() { testSetPositionCommonData(); };
+
+  void testSetMaxPosition();
+  void testSetMaxPosition_data() { testSetPositionCommonData(); };
+
+private:
+  void testSetValueCommonData();
+  void testSetPositionCommonData();
+};
+
+//-----------------------------------------------------------------------------
+void ctkDoubleRangeSliderValueProxyTester::testSetValues()
+{
+  // Setup
+  ctkDoubleRangeSlider slider;
+  slider.setMinimum(-200);
+  slider.setMaximum(200);
+  slider.setSingleStep(0.01);
+  slider.setMinimumValue(-32.6);
+  slider.setMaximumValue(32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  slider.setValueProxy(&proxy);
+
+  // Spy
+  CustomSpy valuesSpy();
+  QObject::connect(&slider, SIGNAL(valuesChanged(double, double)),
+                   &valuesSpy, SLOT(onValuesChanged(double, double)));
+
+  // Test
+  QFETCH(double, min);
+  QFETCH(double, max);
+  slider.setValues(min, max);
+
+  QFETCH(double, expectedMin);
+  QFETCH(double, expectedMax);
+  valuesSpy.getSpyReport(expectedMin, expectedMax);
+  ctkTest::COMPARE(slider.minimumValue(), expectedMin);
+  ctkTest::COMPARE(slider.maximumValue(), expectedMax);
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleRangeSliderValueProxyTester::testSetValues_data()
+{
+  QTest::addColumn<double>("coefficient");
+  QTest::addColumn<double>("offset");
+  QTest::addColumn<double>("min");
+  QTest::addColumn<double>("expectedMin");
+  QTest::addColumn<double>("max");
+  QTest::addColumn<double>("expectedMax");
+
+  //---------------------------------------------------------------------------
+  // Offset
+  QTest::newRow("Offset only") << 1.0 << 42.19176 << 0.1 << 0.1 << 0.2 << 0.2;
+
+  QTest::newRow("Offset only: max+offset < min+offset < -200") << 1.0 << -42.19
+    << -160.0 << -157.81
+    << -190.9 << -157.81;
+  QTest::newRow("Offset only: max+offset < -200 < min+offset") << 1.0 << -42.19
+    << -0.1 << -157.81
+    << -160.9 << -0.1;
+  QTest::newRow("Offset only: -200 < max+offset < min+offset") << 1.0 << 42.19
+    << -0.1 << -130.9
+    << -130.9 << -0.1;
+
+  QTest::newRow("Offset only: 200 < max+offset < min+offset") << 1.0 << 42.19
+    << 160.0 << 157.81
+    << 190.9 << 157.81;
+  QTest::newRow("Offset only: max+offset < 200 < min+offset") << 1.0 << 42.19
+    << 160.9 << -0.9
+    << -0.9 << 157.81;
+  QTest::newRow("Offset only: max+offset < min+offset < 200") << 1.0 << 42.19
+    << 130.6 << -13.9
+    << -13.9 << 130.6;
+
+  QTest::newRow("Offset only: 200 < max = max_double = min = max_double")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::max() << 157.81
+    << std::numeric_limits<double>::max() << 157.81;
+  QTest::newRow("Offset only: max = -max_double < -200 < 200 < min = max_double")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::max() << -242.19
+    << -std::numeric_limits<double>::max() << 157.81;
+  QTest::newRow("Offset only: max = -max_double = min = -max_double < -200")
+    << 1.0 << 42.19
+    << - std::numeric_limits<double>::max() << -242.19
+    << - std::numeric_limits<double>::max() << -242.19;
+
+  QTest::newRow("Offset only: 200 < max = infinity = min = infinity")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::infinity() << 157.81
+    << std::numeric_limits<double>::infinity() << 157.81;
+  QTest::newRow("Offset only: max = -infinity < -200 < 200 < min = infinity")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::infinity() << -242.19
+    << -std::numeric_limits<double>::infinity() << 157.81;
+  QTest::newRow("Offset only: max = -infinity = min = -infinity < -200")
+    << 1.0 << 42.19
+    << - std::numeric_limits<double>::infinity() << -242.19
+    << - std::numeric_limits<double>::infinity() << -242.19;
+
+  QTest::newRow("Offset only: max = min = NaN")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::quiet_NaN() << -242.19
+    << std::numeric_limits<double>::quiet_NaN() << -242.19;
+  QTest::newRow("Offset only: max = NaN && min > 200")
+    << 1.0 << 42.19
+    << 630.0 << -242.19
+    << std::numeric_limits<double>::quiet_NaN() << 157.81;
+  QTest::newRow("Offset only: min = NaN && max < -200")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::quiet_NaN() << -242.19
+    << -794348.12 << -242.19;
+
+  //---------------------------------------------------------------------------
+  // Coefficient
+  QTest::newRow("Coeff only") << 5.0 << 0.0 << 0.1 << 0.1 << 0.2 << 0.2;
+
+  QTest::newRow("Coeff only: max*coeff < min*coeff < -200") << 5.0 << 0.0
+    << -160.0 << -40.0
+    << -190.9 << -40.0;
+  QTest::newRow("Coeff only: max*coeff < -200 < min*coeff") << 5.0 << 0.0
+    << -0.1 << -40.0
+    << -160.9 << -0.1;
+  QTest::newRow("Coeff only: -200 < max*coeff < min*coeff") << 5.0 << 0.0
+    << -0.1 << -20.9
+    << -20.9 << -0.1;
+
+  QTest::newRow("Coeff only: 200 < max*coeff < min*coeff") << 5.0 << 0.0
+    << 160.0 << 40.0
+    << 190.9 << 40.0;
+  QTest::newRow("Coeff only: max*coeff < 200 < min*coeff") << 5.0 << 0.0
+    << 160.9 << -0.9
+    << -0.9 << 40.00;
+  QTest::newRow("Coeff only: max*coeff < min*coeff < 200") << 5.0 << 0.0
+    << 13.6 << -13.9
+    << -13.9 << 13.6;
+
+  QTest::newRow("Coeff only: 200 < max = max_double = min = max_double")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::max() << 40.0
+    << std::numeric_limits<double>::max() << 40.0;
+  QTest::newRow("Coeff only: max = -max_double < -200 < 200 < min = max_double")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::max() << -40.0
+    << - std::numeric_limits<double>::max() << 40.0;
+  QTest::newRow("Coeff only: max = -max_double = min = -max_double < -200")
+    << 5.0 << 0.0
+    << -std::numeric_limits<double>::max() << -40.0
+    << -std::numeric_limits<double>::max() << -40.0;
+
+  QTest::newRow("Coeff only: 200 < max = infinity = min = infinity")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::infinity() << 40.0
+    << std::numeric_limits<double>::infinity() << 40.0;
+  QTest::newRow("Coeff only: max = -infinity < -200 < 200 < min = infinity")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::infinity() << -40.0
+    << -std::numeric_limits<double>::infinity() << 40.0;
+  QTest::newRow("Coeff only: max = -infinity = min = -infinity < -200")
+    << 5.0 << 0.0
+    << - std::numeric_limits<double>::infinity() << -40.0
+    << - std::numeric_limits<double>::infinity() << -40.0;
+
+  QTest::newRow("Coeff only: max = min = NaN")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::quiet_NaN() << -40.0
+    << std::numeric_limits<double>::quiet_NaN() << -40.0;
+  QTest::newRow("Coeff only: max = NaN && min > 200")
+    << 5.0 << 0.0
+    << 630.0 << -40.0
+    << std::numeric_limits<double>::quiet_NaN() << 40.0;
+  QTest::newRow("Coeff only: min = NaN && max < -200")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::quiet_NaN() << -40.0
+    << -794348.12 << -40.0;
+
+  //---------------------------------------------------------------------------
+  // Linear
+  QTest::newRow("Linear") << 5.0 << 12.0 << 0.1 << 0.1 << 0.2 << 0.2;
+
+  QTest::newRow("Linear:f(max) < f(min) < -200") << 5.0 << 12.0
+    << -160.0 << -42.4
+    << -190.9 << -42.4;
+  QTest::newRow("Linear: f(max) < -200 < f(min)") << 5.0 << 12.0
+    << -0.1 << -42.4
+    << -160.9 << -0.1;
+  QTest::newRow("Linear: -200 < f(max) < f(min)") << 5.0 << 12.0
+    << -0.1 << -20.9
+    << -20.9 << -0.1;
+
+  QTest::newRow("Linear: 200 < f(max) < f(min)") << 5.0 << 12.0
+    << 160.0 << 37.6
+    << 190.9 << 37.6;
+  QTest::newRow("Linear: f(max) < 200 < f(min)") << 5.0 << 12.0
+    << 160.9 << -0.9
+    << -0.9 << 37.6;
+  QTest::newRow("Linear: f(max) < f(min) < 200") << 5.0 << 12.0
+    << 13.6 << -13.9
+    << -13.9 << 13.6;
+
+  QTest::newRow("Linear: 200 < max = max_double = min = max_double")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::max() << 37.6
+    << std::numeric_limits<double>::max() << 37.6;
+  QTest::newRow("Linear: max = -max_double < -200 < 200 < min = max_double")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::max() << -42.4
+    << - std::numeric_limits<double>::max() << 37.6;
+  QTest::newRow("Linear: max = -max_double = min = -max_double < -200")
+    << 5.0 << 12.0
+    << -std::numeric_limits<double>::max() << -42.4
+    << -std::numeric_limits<double>::max() << -42.4;
+
+  QTest::newRow("Linear: 200 < max = infinity = min = infinity")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::infinity() << 37.6
+    << std::numeric_limits<double>::infinity() << 37.6;
+  QTest::newRow("Linear: max = -infinity < -200 < 200 < min = infinity")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::infinity() << -42.4
+    << -std::numeric_limits<double>::infinity() << 37.6;
+  QTest::newRow("Linear: max = -infinity = min = -infinity < -200")
+    << 5.0 << 12.0
+    << - std::numeric_limits<double>::infinity() << -42.4
+    << - std::numeric_limits<double>::infinity() << -42.4;
+
+  QTest::newRow("Linear: max = min = NaN")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::quiet_NaN() << -42.4
+    << std::numeric_limits<double>::quiet_NaN() << -42.4;
+  QTest::newRow("Linear: max = NaN && f(min) > 200")
+    << 5.0 << 12.0
+    << 630.0 << -42.4
+    << std::numeric_limits<double>::quiet_NaN() << 37.6;
+  QTest::newRow("Linear: min = NaN && f(max) < -200")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::quiet_NaN() << -42.4
+    << -794348.12 << -42.4;
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleRangeSliderValueProxyTester::testSetMinValue()
+{
+  // Setup
+  ctkDoubleRangeSlider slider;
+  slider.setMinimum(-200);
+  slider.setMaximum(200);
+  slider.setSingleStep(0.01);
+  slider.setMinimumValue(-32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  slider.setValueProxy(&proxy);
+
+  // Spy
+  QSignalSpy valueSpy(&slider, SIGNAL(minimumValueChanged(double)));
+
+  // Test
+  QFETCH(double, value);
+  slider.setMinimumValue(value);
+
+  QFETCH(double, expectedValue);
+  getSpyReport(valueSpy, expectedValue);
+  ctkTest::COMPARE(slider.minimumValue(), expectedValue);
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleRangeSliderValueProxyTester::testSetMaxValue()
+{
+  // Setup
+  ctkDoubleRangeSlider slider;
+  slider.setMinimum(-200);
+  slider.setMaximum(200);
+  slider.setSingleStep(0.01);
+  slider.setMaximumValue(-32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  slider.setValueProxy(&proxy);
+
+  // Spy
+  QSignalSpy valueSpy(&slider, SIGNAL(maximumValueChanged(double)));
+
+  // Test
+  QFETCH(double, value);
+  slider.setMaximumValue(value);
+
+  QFETCH(double, expectedValue);
+  getSpyReport(valueSpy, expectedValue);
+  ctkTest::COMPARE(slider.maximumValue(), expectedValue);
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleRangeSliderValueProxyTester::testSetValueCommonData()
+{
+  QTest::addColumn<double>("coefficient");
+  QTest::addColumn<double>("offset");
+  QTest::addColumn<double>("value");
+  QTest::addColumn<double>("expectedValue");
+
+  //---------------------------------------------------------------------------
+  // Offset
+  QTest::newRow("Offset only") << 1.0 << 42.19176 << 0.1 << 0.1;
+
+  QTest::newRow("Offset only: less than min")
+    << 1.0 << 42.19 << -510.0 << -242.19;
+  QTest::newRow("Offset only: less than min but ok with offset")
+    << 1.0 << 42.19 << -230.0 << -230.0;
+  QTest::newRow("Offset only: less than min with offset")
+    << 1.0 << -42.19 << -190.0 << -157.81;
+
+  QTest::newRow("Offset only: more than max with offset")
+    << 1.0 << 42.19 << 160.0 << 157.81;
+  QTest::newRow("Offset only: more than max")
+    << 1.0 << -42.19 << 65010.0 << 242.19;
+  QTest::newRow("Offset only: less than max but ok with offset")
+    << 1.0 << -42.19 << 229.1 << 229.1;
+
+  QTest::newRow("Offset only: max")
+    << 1.0 << 42.19 << std::numeric_limits<double>::max() << 157.81;
+  QTest::newRow("Offset only:  min")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::max() << -242.19;
+  QTest::newRow("Offset only: infinity")
+    << 1.0 << 42.19 << std::numeric_limits<double>::infinity() << 157.81;
+  QTest::newRow("Offset only:  - infinity")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::infinity() << -242.19;
+  QTest::newRow("Offset only: Nan")
+    << 1.0 << 42.19 << std::numeric_limits<double>::quiet_NaN() << -242.19;
+
+  //---------------------------------------------------------------------------
+  // Coefficient
+  QTest::newRow("Coeff only") << 5.0 << 0.0 << 0.1 << 0.1;
+
+  QTest::newRow("Coeff only: less than min")
+    << 5.0 << 0.0 << -510.0 << -40.0;
+  QTest::newRow("Coeff only: less than min but ok with coeff")
+    << 0.5 << 0.0 << -230.0 << -230.0;
+  QTest::newRow("Coeff only: less than min with coeff")
+    << 5.0 << 0.0 << -190.0 << -40.0;
+
+  QTest::newRow("Coeff only: more than max with coeff")
+    << 5.0 << 0.0 << 160.0 << 40.0;
+  QTest::newRow("Coeff only: more than max")
+    << 5.0 << 0.0 << 65010.0 << 40.0;
+  QTest::newRow("Offset only: less than max but ok with coeff")
+    << 0.5 << 0.0 << 229.2 << 229.2;
+
+  QTest::newRow("Coeff only: max")
+    << 5.0 << 0.0 << std::numeric_limits<double>::max() << 40.0;
+  QTest::newRow("Coeff only:  min")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::max() << -40.0;
+  QTest::newRow("Coeff only: infinity")
+    << 5.0 << 0.0 << std::numeric_limits<double>::infinity() << 40.0;
+  QTest::newRow("Coeff only:  - infinity")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::infinity() << -40.0;
+  QTest::newRow("Coeff only: Nan")
+    << 5.0 << 0.0 << std::numeric_limits<double>::quiet_NaN() << -40.0;
+
+  //---------------------------------------------------------------------------
+  // Linear
+  QTest::newRow("Linear") << 5.0 << 0.0 << 0.1 << 0.1;
+
+  QTest::newRow("Linear: less than min")
+    << 5.0 << 12.0 << -510.0 << -42.4;
+  QTest::newRow("Linear: less than min but ok with function")
+    << 0.5 << 12.0 << -230.0 << -230.0;
+  QTest::newRow("Linear: less than min with function")
+    << 5.0 << 12.0 << -61.5 << -42.4;
+
+  QTest::newRow("Linear: more than max with function")
+    << 5.0 << 12.0 << 160.0 << 37.6;
+  QTest::newRow("Linear: more than max")
+    << 5.0 << 12.0 << 65010.0 << 37.6;
+  QTest::newRow("Offset only: less than max but ok with function")
+    << 0.5 << 12.0 << 229.2 << 229.2;
+
+  QTest::newRow("Linear: max")
+    << 5.0 << 12.0 << std::numeric_limits<double>::max() << 37.6;
+  QTest::newRow("Linear:  min")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::max() << -42.4;
+  QTest::newRow("Linear: infinity")
+    << 5.0 << 12.0 << std::numeric_limits<double>::infinity() << 37.6;
+  QTest::newRow("Linear:  - infinity")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::infinity() << -42.4;
+  QTest::newRow("Linear: Nan")
+    << 5.0 << 12.0 << std::numeric_limits<double>::quiet_NaN() << -42.4;
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleRangeSliderValueProxyTester::testSetMinPosition()
+{
+  // Setup
+  ctkDoubleRangeSlider slider;
+  slider.setMinimum(-200);
+  slider.setMaximum(200);
+  slider.setSingleStep(0.01);
+  slider.setMinimumValue(-32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  slider.setValueProxy(&proxy);
+
+  // Spy
+  QSignalSpy valueSpy(&slider, SIGNAL(minimumValueChanged(double)));
+
+  // Test
+  QFETCH(double, sliderPosition);
+  slider.setMinimumPosition(sliderPosition);
+
+  QFETCH(double, expectedSliderPosition);
+  QFETCH(double, expectedValue);
+  getSpyReport(valueSpy, expectedValue);
+  ctkTest::COMPARE(slider.minimumValue(), expectedValue);
+  ctkTest::COMPARE(slider.minimumPosition(), expectedSliderPosition);
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleRangeSliderValueProxyTester::testSetMaxPosition()
+{
+  // Setup
+  ctkDoubleRangeSlider slider;
+  slider.setMinimum(-200);
+  slider.setMaximum(200);
+  slider.setSingleStep(0.01);
+  slider.setMaximumValue(-32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  slider.setValueProxy(&proxy);
+
+  // Spy
+  QSignalSpy valueSpy(&slider, SIGNAL(maximumValueChanged(double)));
+
+  // Test
+  QFETCH(double, sliderPosition);
+  slider.setMaximumPosition(sliderPosition);
+
+  QFETCH(double, expectedSliderPosition);
+  QFETCH(double, expectedValue);
+  getSpyReport(valueSpy, expectedValue);
+  ctkTest::COMPARE(slider.maximumValue(), expectedValue);
+  ctkTest::COMPARE(slider.maximumPosition(), expectedSliderPosition);
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleRangeSliderValueProxyTester::testSetPositionCommonData()
+{
+  QTest::addColumn<double>("coefficient");
+  QTest::addColumn<double>("offset");
+  QTest::addColumn<double>("sliderPosition");
+  QTest::addColumn<double>("expectedValue");
+  QTest::addColumn<double>("expectedSliderPosition");
+
+  //---------------------------------------------------------------------------
+  // Offset
+  QTest::newRow("Offset only") << 1.0 << 42.19 << 0.1 << -42.09 << 0.1;
+
+  QTest::newRow("Offset only: less than min")
+    << 1.0 << 42.19 << -510.0 << -242.19 << -200.0;
+  QTest::newRow("Offset only: more than max")
+    << 1.0 << -42.19 << 65010.0 << 242.19 << 200.0;
+
+  QTest::newRow("Offset only: max")
+    << 1.0 << 42.19 << std::numeric_limits<double>::max()
+    << 157.81 << 200.0;
+  QTest::newRow("Offset only:  min")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::max()
+    << -242.19 << -200.0;
+  QTest::newRow("Offset only: infinity")
+    << 1.0 << 42.19 << std::numeric_limits<double>::infinity()
+    << 157.81 << 200.0;
+  QTest::newRow("Offset only:  - infinity")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::infinity()
+    << -242.19 << -200.0;
+  QTest::newRow("Offset only: Nan")
+    << 1.0 << 42.19 << std::numeric_limits<double>::quiet_NaN()
+    << -242.19 << -200.0;
+
+  //---------------------------------------------------------------------------
+  // Coefficient
+  QTest::newRow("Coeff only") << 5.0 << 0.0 << 5.0 << 1.0 << 5.0;
+
+  QTest::newRow("Coeff only: less than min")
+    << 5.0 << 0.0 << -1010.0 << -40.0 << -200.0;
+  QTest::newRow("Coeff only: more than max")
+    << 5.0 << 0.0 << 65010.0 << 40.0 << 200.0;
+
+  QTest::newRow("Coeff only: max")
+    << 5.0 << 0.0 << std::numeric_limits<double>::max() << 40.0 << 200.0;
+  QTest::newRow("Coeff only:  min")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::max() << -40.0 << -200.0;
+  QTest::newRow("Coeff only: infinity")
+    << 5.0 << 0.0 << std::numeric_limits<double>::infinity() << 40.0 << 200.0;
+  QTest::newRow("Coeff only:  - infinity")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::infinity()
+    << -40.0 << -200.0;
+  QTest::newRow("Coeff only: Nan")
+    << 5.0 << 0.0 << std::numeric_limits<double>::quiet_NaN()
+    << -40.0 << -200.0;
+
+  //---------------------------------------------------------------------------
+  // Linear
+  QTest::newRow("Linear") << 5.0 << 12.0 << 42.0 << 6.0 << 42.0;
+
+  QTest::newRow("Linear: less than min")
+    << 5.0 << 12.0 << -5010.0 << -42.4 << -200.0;
+
+  QTest::newRow("Linear: more than max")
+    << 5.0 << 12.0 << 65010.0 << 37.6 << 200.0;
+
+  QTest::newRow("Linear: max")
+    << 5.0 << 12.0 << std::numeric_limits<double>::max() << 37.6 << 200.0;
+  QTest::newRow("Linear:  min")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::max() << -42.4 << -200.0;
+  QTest::newRow("Linear: infinity")
+    << 5.0 << 12.0 << std::numeric_limits<double>::infinity() << 37.6 << 200.0;
+  QTest::newRow("Linear:  - infinity")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::infinity()
+    << -42.4 << -200.0;
+  QTest::newRow("Linear: Nan")
+    << 5.0 << 12.0 << std::numeric_limits<double>::quiet_NaN()
+    << -42.4 << -200.0;
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkDoubleRangeSliderValueProxyTest)
+#include "moc_ctkDoubleRangeSliderValueProxyTest.cpp"

+ 302 - 0
Libs/Widgets/Testing/Cpp/ctkDoubleSliderValueProxyTest.cpp

@@ -0,0 +1,302 @@
+/*=========================================================================
+
+  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.commontk.org/LICENSE
+
+  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 <QApplication>
+#include <QTest>
+
+// CTK includes
+#include "ctkDoubleSlider.h"
+#include "ctkLinearValueProxy.h"
+#include "ctkTest.h"
+#include "ctkValueProxy.h"
+
+// STD includes
+#include <limits>
+
+namespace
+{
+
+//-----------------------------------------------------------------------------
+void getSpyReport(QSignalSpy& spy, double expectedValue)
+{
+  QCOMPARE(spy.count(), 1);
+
+  QList<QVariant> arguments = spy.takeFirst(); // take the first signal
+  ctkTest::COMPARE(arguments.at(0).toDouble(), expectedValue);
+}
+
+} // end namespace
+
+//-----------------------------------------------------------------------------
+class ctkDoubleSliderValueProxyTester: public QObject
+{
+  Q_OBJECT
+private slots:
+
+  void testSetValue();
+  void testSetValue_data();
+
+  void testSetSliderPosition();
+  void testSetSliderPosition_data();
+};
+
+//-----------------------------------------------------------------------------
+void ctkDoubleSliderValueProxyTester::testSetValue()
+{
+  // Setup
+  ctkDoubleSlider slider;
+  slider.setMinimum(-200);
+  slider.setMaximum(200);
+  slider.setSingleStep(0.01);
+  slider.setValue(-32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  slider.setValueProxy(&proxy);
+
+  // Spy
+  QSignalSpy valueSpy(&slider, SIGNAL(valueChanged(double)));
+
+  // Test
+  QFETCH(double, value);
+  slider.setValue(value);
+
+  QFETCH(double, expectedValue);
+  getSpyReport(valueSpy, expectedValue);
+  ctkTest::COMPARE(slider.value(), expectedValue);
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleSliderValueProxyTester::testSetValue_data()
+{
+  QTest::addColumn<double>("coefficient");
+  QTest::addColumn<double>("offset");
+  QTest::addColumn<double>("value");
+  QTest::addColumn<double>("expectedValue");
+
+  //---------------------------------------------------------------------------
+  // Offset
+  QTest::newRow("Offset only") << 1.0 << 42.19176 << 0.1 << 0.1;
+
+  QTest::newRow("Offset only: less than min")
+    << 1.0 << 42.19 << -510.0 << -242.19;
+  QTest::newRow("Offset only: less than min but ok with offset")
+    << 1.0 << 42.19 << -230.0 << -230.0;
+  QTest::newRow("Offset only: less than min with offset")
+    << 1.0 << -42.19 << -190.0 << -157.81;
+
+  QTest::newRow("Offset only: more than max with offset")
+    << 1.0 << 42.19 << 160.0 << 157.81;
+  QTest::newRow("Offset only: more than max")
+    << 1.0 << -42.19 << 65010.0 << 242.19;
+  QTest::newRow("Offset only: less than max but ok with offset")
+    << 1.0 << -42.19 << 229.1 << 229.1;
+
+  QTest::newRow("Offset only: max")
+    << 1.0 << 42.19 << std::numeric_limits<double>::max() << 157.81;
+  QTest::newRow("Offset only:  min")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::max() << -242.19;
+  QTest::newRow("Offset only: infinity")
+    << 1.0 << 42.19 << std::numeric_limits<double>::infinity() << 157.81;
+  QTest::newRow("Offset only:  - infinity")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::infinity() << -242.19;
+  QTest::newRow("Offset only: Nan")
+    << 1.0 << 42.19 << std::numeric_limits<double>::quiet_NaN() << -242.19;
+
+  // coeff // offset // value // expectedValue
+  //---------------------------------------------------------------------------
+  // Coefficient
+  QTest::newRow("Coeff only") << 5.0 << 0.0 << 0.1 << 0.1;
+
+  QTest::newRow("Coeff only: less than min")
+    << 5.0 << 0.0 << -510.0 << -40.0;
+  QTest::newRow("Coeff only: less than min but ok with coeff")
+    << 0.5 << 0.0 << -230.0 << -230.0;
+  QTest::newRow("Coeff only: less than min with coeff")
+    << 5.0 << 0.0 << -190.0 << -40.0;
+
+  QTest::newRow("Coeff only: more than max with coeff")
+    << 5.0 << 0.0 << 160.0 << 40.0;
+  QTest::newRow("Coeff only: more than max")
+    << 5.0 << 0.0 << 65010.0 << 40.0;
+  QTest::newRow("Offset only: less than max but ok with coeff")
+    << 0.5 << 0.0 << 229.2 << 229.2;
+
+  QTest::newRow("Coeff only: max")
+    << 5.0 << 0.0 << std::numeric_limits<double>::max() << 40.0;
+  QTest::newRow("Coeff only:  min")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::max() << -40.0;
+  QTest::newRow("Coeff only: infinity")
+    << 5.0 << 0.0 << std::numeric_limits<double>::infinity() << 40.0;
+  QTest::newRow("Coeff only:  - infinity")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::infinity() << -40.0;
+  QTest::newRow("Coeff only: Nan")
+    << 5.0 << 0.0 << std::numeric_limits<double>::quiet_NaN() << -40.0;
+
+
+  // coeff // offset // value // expectedValue
+  //---------------------------------------------------------------------------
+  // Linear
+  QTest::newRow("Linear") << 5.0 << 0.0 << 0.1 << 0.1;
+
+  QTest::newRow("Linear: less than min")
+    << 5.0 << 12.0 << -510.0 << -42.4;
+  QTest::newRow("Linear: less than min but ok with function")
+    << 0.5 << 12.0 << -230.0 << -230.0;
+  QTest::newRow("Linear: less than min with function")
+    << 5.0 << 12.0 << -61.5 << -42.4;
+
+  QTest::newRow("Linear: more than max with function")
+    << 5.0 << 12.0 << 160.0 << 37.6;
+  QTest::newRow("Linear: more than max")
+    << 5.0 << 12.0 << 65010.0 << 37.6;
+  QTest::newRow("Offset only: less than max but ok with function")
+    << 0.5 << 12.0 << 229.2 << 229.2;
+
+  QTest::newRow("Linear: max")
+    << 5.0 << 12.0 << std::numeric_limits<double>::max() << 37.6;
+  QTest::newRow("Linear:  min")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::max() << -42.4;
+  QTest::newRow("Linear: infinity")
+    << 5.0 << 12.0 << std::numeric_limits<double>::infinity() << 37.6;
+  QTest::newRow("Linear:  - infinity")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::infinity() << -42.4;
+  QTest::newRow("Linear: Nan")
+    << 5.0 << 12.0 << std::numeric_limits<double>::quiet_NaN() << -42.4;
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleSliderValueProxyTester::testSetSliderPosition()
+{
+  // Setup
+  ctkDoubleSlider slider;
+  slider.setMinimum(-200);
+  slider.setMaximum(200);
+  slider.setSingleStep(0.01);
+  slider.setValue(-32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  slider.setValueProxy(&proxy);
+
+  // Spy
+  QSignalSpy valueSpy(&slider, SIGNAL(valueChanged(double)));
+
+  // Test
+  QFETCH(double, sliderPosition);
+  slider.setSliderPosition(sliderPosition);
+
+  QFETCH(double, expectedSliderPosition);
+  QFETCH(double, expectedValue);
+  getSpyReport(valueSpy, expectedValue);
+  ctkTest::COMPARE(slider.value(), expectedValue);
+  ctkTest::COMPARE(slider.sliderPosition(), expectedSliderPosition);
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleSliderValueProxyTester::testSetSliderPosition_data()
+{
+ QTest::addColumn<double>("coefficient");
+  QTest::addColumn<double>("offset");
+  QTest::addColumn<double>("sliderPosition");
+  QTest::addColumn<double>("expectedValue");
+  QTest::addColumn<double>("expectedSliderPosition");
+
+  //---------------------------------------------------------------------------
+  // Offset
+  QTest::newRow("Offset only") << 1.0 << 42.19 << 0.1 << -42.09 << 0.1;
+
+  QTest::newRow("Offset only: less than min")
+    << 1.0 << 42.19 << -510.0 << -242.19 << -200.0;
+  QTest::newRow("Offset only: more than max")
+    << 1.0 << -42.19 << 65010.0 << 242.19 << 200.0;
+
+  QTest::newRow("Offset only: max")
+    << 1.0 << 42.19 << std::numeric_limits<double>::max()
+    << 157.81 << 200.0;
+  QTest::newRow("Offset only:  min")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::max()
+    << -242.19 << -200.0;
+  QTest::newRow("Offset only: infinity")
+    << 1.0 << 42.19 << std::numeric_limits<double>::infinity()
+    << 157.81 << 200.0;
+  QTest::newRow("Offset only:  - infinity")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::infinity()
+    << -242.19 << -200.0;
+  QTest::newRow("Offset only: Nan")
+    << 1.0 << 42.19 << std::numeric_limits<double>::quiet_NaN()
+    << -242.19 << -200.0;
+
+  //---------------------------------------------------------------------------
+  // Coefficient
+  QTest::newRow("Coeff only") << 5.0 << 0.0 << 5.0 << 1.0 << 5.0;
+
+  QTest::newRow("Coeff only: less than min")
+    << 5.0 << 0.0 << -1010.0 << -40.0 << -200.0;
+  QTest::newRow("Coeff only: more than max")
+    << 5.0 << 0.0 << 65010.0 << 40.0 << 200.0;
+
+  QTest::newRow("Coeff only: max")
+    << 5.0 << 0.0 << std::numeric_limits<double>::max() << 40.0 << 200.0;
+  QTest::newRow("Coeff only:  min")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::max() << -40.0 << -200.0;
+  QTest::newRow("Coeff only: infinity")
+    << 5.0 << 0.0 << std::numeric_limits<double>::infinity() << 40.0 << 200.0;
+  QTest::newRow("Coeff only:  - infinity")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::infinity()
+    << -40.0 << -200.0;
+  QTest::newRow("Coeff only: Nan")
+    << 5.0 << 0.0 << std::numeric_limits<double>::quiet_NaN()
+    << -40.0 << -200.0;
+
+  //---------------------------------------------------------------------------
+  // Linear
+  QTest::newRow("Linear") << 5.0 << 12.0 << 42.0 << 6.0 << 42.0;
+
+  QTest::newRow("Linear: less than min")
+    << 5.0 << 12.0 << -5010.0 << -42.4 << -200.0;
+
+  QTest::newRow("Linear: more than max")
+    << 5.0 << 12.0 << 65010.0 << 37.6 << 200.0;
+
+  QTest::newRow("Linear: max")
+    << 5.0 << 12.0 << std::numeric_limits<double>::max() << 37.6 << 200.0;
+  QTest::newRow("Linear:  min")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::max() << -42.4 << -200.0;
+  QTest::newRow("Linear: infinity")
+    << 5.0 << 12.0 << std::numeric_limits<double>::infinity() << 37.6 << 200.0;
+  QTest::newRow("Linear:  - infinity")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::infinity()
+    << -42.4 << -200.0;
+  QTest::newRow("Linear: Nan")
+    << 5.0 << 12.0 << std::numeric_limits<double>::quiet_NaN()
+    << -42.4 << -200.0;
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkDoubleSliderValueProxyTest)
+#include "moc_ctkDoubleSliderValueProxyTest.cpp"

+ 589 - 0
Libs/Widgets/Testing/Cpp/ctkRangeWidgetValueProxyTest.cpp

@@ -0,0 +1,589 @@
+/*=========================================================================
+
+  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.commontk.org/LICENSE
+
+  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 <QApplication>
+#include <QTest>
+
+// CTK includes
+#include "ctkRangeWidget.h"
+#include "ctkLinearValueProxy.h"
+#include "ctkTest.h"
+#include "ctkValueProxy.h"
+
+namespace
+{
+
+//-----------------------------------------------------------------------------
+void getSpyReport(QSignalSpy& spy, double expectedValue)
+{
+  QCOMPARE(spy.count(), 1);
+
+  QList<QVariant> arguments = spy.takeFirst(); // take the first signal
+  ctkTest::COMPARE(arguments.at(0).toDouble(), expectedValue);
+}
+
+//-----------------------------------------------------------------------------
+class CustomSpy : public QObject
+{
+  Q_OBJECT
+public:
+  CustomSpy()
+    {
+    this->AcknowledgedSignals = 0;
+    }
+
+public slots:
+  void onValuesChanged(double min, double max)
+    {
+    ++this->AcknowledgedSignals;
+    this->MinSpyData.append(min);
+    this->MaxSpyData.append(max);
+    }
+
+public:
+  void getSpyReport(double min, double max)
+    {
+    QCOMPARE(this->AcknowledgedSignals, 1);
+
+    QCOMPARE(this->MinSpyData.size(), 1);
+    ctkTest::COMPARE(this->MinSpyData[0], min);
+    QCOMPARE(this->MaxSpyData.size(), 1);
+    ctkTest::COMPARE(this->MaxSpyData[0], max);
+    }
+
+  QList<double> MinSpyData;
+  QList<double> MaxSpyData;
+  int AcknowledgedSignals;
+};
+
+} // end namespace
+
+//-----------------------------------------------------------------------------
+class ctkRangeWidgetValueProxyTester: public QObject
+{
+  Q_OBJECT
+private slots:
+
+  void testSetMinValue();
+  void testSetMinValue_data();
+
+  void testSetMaxValue();
+  void testSetMaxValue_data();
+
+  void testSetValues();
+  void testSetValues_data();
+
+private:
+  void testSetValueCommonData();
+  void testSetPositionCommonData();
+};
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetValueProxyTester::testSetValues()
+{
+  // Setup
+  ctkRangeWidget ranger;
+  ranger.setMinimum(-200);
+  ranger.setMaximum(200);
+  ranger.setSingleStep(0.01);
+  ranger.setMinimumValue(-32.6);
+  ranger.setMaximumValue(32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  ranger.setValueProxy(&proxy);
+
+  // \todo when valuesChanged is fixed
+  // Spy
+  //CustomSpy valuesSpy;
+  //QObject::connect(&ranger, SIGNAL(valuesChanged(double, double)),
+  //                 &valuesSpy, SLOT(onValuesChanged(double, double)));
+
+  // Test
+  QFETCH(double, min);
+  QFETCH(double, max);
+  ranger.setValues(min, max);
+
+  QFETCH(double, expectedMin);
+  QFETCH(double, expectedMax);
+  //valuesSpy.getSpyReport(expectedMin, expectedMax);
+  ctkTest::COMPARE(ranger.minimumValue(), expectedMin);
+  ctkTest::COMPARE(ranger.maximumValue(), expectedMax);
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetValueProxyTester::testSetValues_data()
+{
+  QTest::addColumn<double>("coefficient");
+  QTest::addColumn<double>("offset");
+  QTest::addColumn<double>("min");
+  QTest::addColumn<double>("expectedMin");
+  QTest::addColumn<double>("max");
+  QTest::addColumn<double>("expectedMax");
+
+  //---------------------------------------------------------------------------
+  // Offset
+  QTest::newRow("Offset only") << 1.0 << 49.19 << 0.1 << 0.1 << 0.2 << 0.2;
+
+  QTest::newRow("Offset only: max+offset < min+offset < -200") << 1.0 << -42.19
+    << -160.0 << -157.81
+    << -190.9 << -157.81;
+  QTest::newRow("Offset only: max+offset < -200 < min+offset") << 1.0 << -42.19
+    << -0.1 << -157.81
+    << -160.9 << -0.1;
+  QTest::newRow("Offset only: -200 < max+offset < min+offset") << 1.0 << 42.19
+    << -0.1 << -130.9
+    << -130.9 << -0.1;
+
+  QTest::newRow("Offset only: 200 < max+offset < min+offset") << 1.0 << 42.19
+    << 160.0 << 157.81
+    << 190.9 << 157.81;
+  QTest::newRow("Offset only: max+offset < 200 < min+offset") << 1.0 << 42.19
+    << 160.9 << -0.9
+    << -0.9 << 157.81;
+  QTest::newRow("Offset only: max+offset < min+offset < 200") << 1.0 << 42.19
+    << 130.6 << -13.9
+    << -13.9 << 130.6;
+
+  QTest::newRow("Offset only: 200 < max = max_double = min = max_double")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::max() << 157.81
+    << std::numeric_limits<double>::max() << 157.81;
+  QTest::newRow("Offset only: max = -max_double < -200 < 200 < min = max_double")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::max() << -242.19
+    << -std::numeric_limits<double>::max() << 157.81;
+  QTest::newRow("Offset only: max = -max_double = min = -max_double < -200")
+    << 1.0 << 42.19
+    << - std::numeric_limits<double>::max() << -242.19
+    << - std::numeric_limits<double>::max() << -242.19;
+
+  QTest::newRow("Offset only: 200 < max = infinity = min = infinity")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::infinity() << 157.81
+    << std::numeric_limits<double>::infinity() << 157.81;
+  QTest::newRow("Offset only: max = -infinity < -200 < 200 < min = infinity")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::infinity() << -242.19
+    << -std::numeric_limits<double>::infinity() << 157.81;
+  QTest::newRow("Offset only: max = -infinity = min = -infinity < -200")
+    << 1.0 << 42.19
+    << - std::numeric_limits<double>::infinity() << -242.19
+    << - std::numeric_limits<double>::infinity() << -242.19;
+
+  QTest::newRow("Offset only: max = min = NaN")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::quiet_NaN() << 157.81
+    << std::numeric_limits<double>::quiet_NaN() << 157.81;
+  QTest::newRow("Offset only: max = NaN && min > 200")
+    << 1.0 << 42.19
+    << 630.0 << 157.81
+    << std::numeric_limits<double>::quiet_NaN() << 157.81;
+  QTest::newRow("Offset only: min = NaN && max < -200")
+    << 1.0 << 42.19
+    << std::numeric_limits<double>::quiet_NaN() << -74.79
+    << -794348.12 << -74.79;
+
+  //---------------------------------------------------------------------------
+  // Coefficient
+  QTest::newRow("Coeff only") << 5.0 << 0.0 << 0.1 << 0.1 << 0.2 << 0.2;
+
+  QTest::newRow("Coeff only: max*coeff < min*coeff < -200") << 5.0 << 0.0
+    << -160.0 << -40.0
+    << -190.9 << -40.0;
+  QTest::newRow("Coeff only: max*coeff < -200 < min*coeff") << 5.0 << 0.0
+    << -0.1 << -40.0
+    << -160.9 << -0.1;
+  QTest::newRow("Coeff only: -200 < max*coeff < min*coeff") << 5.0 << 0.0
+    << -0.1 << -20.9
+    << -20.9 << -0.1;
+
+  QTest::newRow("Coeff only: 200 < max*coeff < min*coeff") << 5.0 << 0.0
+    << 160.0 << 40.0
+    << 190.9 << 40.0;
+  QTest::newRow("Coeff only: max*coeff < 200 < min*coeff") << 5.0 << 0.0
+    << 160.9 << -0.9
+    << -0.9 << 40.00;
+  QTest::newRow("Coeff only: max*coeff < min*coeff < 200") << 5.0 << 0.0
+    << 13.6 << -13.9
+    << -13.9 << 13.6;
+
+  QTest::newRow("Coeff only: 200 < max = max_double = min = max_double")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::max() << 40.0
+    << std::numeric_limits<double>::max() << 40.0;
+  QTest::newRow("Coeff only: max = -max_double < -200 < 200 < min = max_double")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::max() << -40.0
+    << - std::numeric_limits<double>::max() << 40.0;
+  QTest::newRow("Coeff only: max = -max_double = min = -max_double < -200")
+    << 5.0 << 0.0
+    << -std::numeric_limits<double>::max() << -40.0
+    << -std::numeric_limits<double>::max() << -40.0;
+
+  QTest::newRow("Coeff only: 200 < max = infinity = min = infinity")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::infinity() << 40.0
+    << std::numeric_limits<double>::infinity() << 40.0;
+  QTest::newRow("Coeff only: max = -infinity < -200 < 200 < min = infinity")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::infinity() << -40.0
+    << -std::numeric_limits<double>::infinity() << 40.0;
+  QTest::newRow("Coeff only: max = -infinity = min = -infinity < -200")
+    << 5.0 << 0.0
+    << - std::numeric_limits<double>::infinity() << -40.0
+    << - std::numeric_limits<double>::infinity() << -40.0;
+
+  QTest::newRow("Coeff only: max = min = NaN")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::quiet_NaN() << 40.0
+    << std::numeric_limits<double>::quiet_NaN() << 40.0;
+  QTest::newRow("Coeff only: max = NaN && min > 200")
+    << 5.0 << 0.0
+    << 630.0 << 40.0
+    << std::numeric_limits<double>::quiet_NaN() << 40.0;
+  QTest::newRow("Coeff only: min = NaN && max < -200")
+    << 5.0 << 0.0
+    << std::numeric_limits<double>::quiet_NaN() << -6.52
+    << -794348.12 << -6.52;
+
+  //---------------------------------------------------------------------------
+  // Linear
+  QTest::newRow("Linear") << 5.0 << 12.0 << 0.1 << 0.1 << 0.2 << 0.2;
+
+  QTest::newRow("Linear:f(max) < f(min) < -200") << 5.0 << 12.0
+    << -160.0 << -42.4
+    << -190.9 << -42.4;
+  QTest::newRow("Linear: f(max) < -200 < f(min)") << 5.0 << 12.0
+    << -0.1 << -42.4
+    << -160.9 << -0.1;
+  QTest::newRow("Linear: -200 < f(max) < f(min)") << 5.0 << 12.0
+    << -0.1 << -20.9
+    << -20.9 << -0.1;
+
+  QTest::newRow("Linear: 200 < f(max) < f(min)") << 5.0 << 12.0
+    << 160.0 << 37.6
+    << 190.9 << 37.6;
+  QTest::newRow("Linear: f(max) < 200 < f(min)") << 5.0 << 12.0
+    << 160.9 << -0.9
+    << -0.9 << 37.6;
+  QTest::newRow("Linear: f(max) < f(min) < 200") << 5.0 << 12.0
+    << 13.6 << -13.9
+    << -13.9 << 13.6;
+
+  QTest::newRow("Linear: 200 < max = max_double = min = max_double")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::max() << 37.6
+    << std::numeric_limits<double>::max() << 37.6;
+  QTest::newRow("Linear: max = -max_double < -200 < 200 < min = max_double")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::max() << -42.4
+    << - std::numeric_limits<double>::max() << 37.6;
+  QTest::newRow("Linear: max = -max_double = min = -max_double < -200")
+    << 5.0 << 12.0
+    << -std::numeric_limits<double>::max() << -42.4
+    << -std::numeric_limits<double>::max() << -42.4;
+
+  QTest::newRow("Linear: 200 < max = infinity = min = infinity")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::infinity() << 37.6
+    << std::numeric_limits<double>::infinity() << 37.6;
+  QTest::newRow("Linear: max = -infinity < -200 < 200 < min = infinity")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::infinity() << -42.4
+    << -std::numeric_limits<double>::infinity() << 37.6;
+  QTest::newRow("Linear: max = -infinity = min = -infinity < -200")
+    << 5.0 << 12.0
+    << - std::numeric_limits<double>::infinity() << -42.4
+    << - std::numeric_limits<double>::infinity() << -42.4;
+
+  QTest::newRow("Linear: max = min = NaN")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::quiet_NaN() << 37.6
+    << std::numeric_limits<double>::quiet_NaN() << 37.6;
+  QTest::newRow("Linear: max = NaN && f(min) > 200")
+    << 5.0 << 12.0
+    << 630.0 << 37.6
+    << std::numeric_limits<double>::quiet_NaN() << 37.6;
+  QTest::newRow("Linear: min = NaN && f(max) < -200")
+    << 5.0 << 12.0
+    << std::numeric_limits<double>::quiet_NaN() << -8.92
+    << -794348.12 << -8.92;
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetValueProxyTester::testSetMinValue()
+{
+  // Setup
+  ctkRangeWidget ranger;
+  ranger.setMinimum(-200);
+  ranger.setMaximum(200);
+  ranger.setSingleStep(0.01);
+  ranger.setMinimumValue(-32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  ranger.setValueProxy(&proxy);
+
+  // \todo when valuesChanged is fixed
+  // Spy
+  //QSignalSpy valueSpy(&ranger, SIGNAL(minimumValueChanged(double)));
+
+  // Test
+  QFETCH(double, value);
+  ranger.setMinimumValue(value);
+
+  QFETCH(double, expectedValue);
+  //getSpyReport(valueSpy, expectedValue);
+  ctkTest::COMPARE(ranger.minimumValue(), expectedValue);
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetValueProxyTester::testSetMinValue_data()
+{
+  QTest::addColumn<double>("coefficient");
+  QTest::addColumn<double>("offset");
+  QTest::addColumn<double>("value");
+  QTest::addColumn<double>("expectedValue");
+
+  //---------------------------------------------------------------------------
+  // Offset
+  QTest::newRow("Offset only") << 1.0 << 42.19 << 0.1 << 0.1;
+
+  QTest::newRow("Offset only: less than min")
+    << 1.0 << 42.19 << -510.0 << -242.19;
+  QTest::newRow("Offset only: less than min but ok with offset")
+    << 1.0 << 42.19 << -230.0 << -230.0;
+  QTest::newRow("Offset only: less than min with offset")
+    << 1.0 << -42.19 << -190.0 << -157.81;
+
+  QTest::newRow("Offset only: more than max with offset")
+    << 1.0 << 42.19 << 160.0 << 157.81;
+  QTest::newRow("Offset only: more than max")
+    << 1.0 << -42.19 << 65010.0 << 242.19;
+  QTest::newRow("Offset only: less than max but ok with offset")
+    << 1.0 << -42.19 << 229.1 << 229.1;
+
+  QTest::newRow("Offset only: max")
+    << 1.0 << 42.19 << std::numeric_limits<double>::max() << 157.81;
+  QTest::newRow("Offset only:  min")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::max() << -242.19;
+  QTest::newRow("Offset only: infinity")
+    << 1.0 << 42.19 << std::numeric_limits<double>::infinity() << 157.81;
+  QTest::newRow("Offset only:  - infinity")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::infinity() << -242.19;
+  QTest::newRow("Offset only: Nan")
+    << 1.0 << 42.19 << std::numeric_limits<double>::quiet_NaN() << 157.81;
+
+  //---------------------------------------------------------------------------
+  // Coefficient
+  QTest::newRow("Coeff only") << 5.0 << 0.0 << 0.1 << 0.1;
+
+  QTest::newRow("Coeff only: less than min")
+    << 5.0 << 0.0 << -510.0 << -40.0;
+  QTest::newRow("Coeff only: less than min but ok with coeff")
+    << 0.5 << 0.0 << -230.0 << -230.0;
+  QTest::newRow("Coeff only: less than min with coeff")
+    << 5.0 << 0.0 << -190.0 << -40.0;
+
+  QTest::newRow("Coeff only: more than max with coeff")
+    << 5.0 << 0.0 << 160.0 << 40.0;
+  QTest::newRow("Coeff only: more than max")
+    << 5.0 << 0.0 << 65010.0 << 40.0;
+  QTest::newRow("Offset only: less than max but ok with coeff")
+    << 0.5 << 0.0 << 229.2 << 229.2;
+
+  QTest::newRow("Coeff only: max")
+    << 5.0 << 0.0 << std::numeric_limits<double>::max() << 40.0;
+  QTest::newRow("Coeff only:  min")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::max() << -40.0;
+  QTest::newRow("Coeff only: infinity")
+    << 5.0 << 0.0 << std::numeric_limits<double>::infinity() << 40.0;
+  QTest::newRow("Coeff only:  - infinity")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::infinity() << -40.0;
+  QTest::newRow("Coeff only: Nan")
+    << 5.0 << 0.0 << std::numeric_limits<double>::quiet_NaN() << 40.0;
+
+  //---------------------------------------------------------------------------
+  // Linear
+  QTest::newRow("Linear") << 5.0 << 0.0 << 0.1 << 0.1;
+
+  QTest::newRow("Linear: less than min")
+    << 5.0 << 12.0 << -510.0 << -42.4;
+  QTest::newRow("Linear: less than min but ok with function")
+    << 0.5 << 12.0 << -230.0 << -230.0;
+  QTest::newRow("Linear: less than min with function")
+    << 5.0 << 12.0 << -61.5 << -42.4;
+
+  QTest::newRow("Linear: more than max with function")
+    << 5.0 << 12.0 << 160.0 << 37.6;
+  QTest::newRow("Linear: more than max")
+    << 5.0 << 12.0 << 65010.0 << 37.6;
+  QTest::newRow("Offset only: less than max but ok with function")
+    << 0.5 << 12.0 << 229.2 << 229.2;
+
+  QTest::newRow("Linear: max")
+    << 5.0 << 12.0 << std::numeric_limits<double>::max() << 37.6;
+  QTest::newRow("Linear:  min")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::max() << -42.4;
+  QTest::newRow("Linear: infinity")
+    << 5.0 << 12.0 << std::numeric_limits<double>::infinity() << 37.6;
+  QTest::newRow("Linear:  - infinity")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::infinity() << -42.4;
+  QTest::newRow("Linear: Nan")
+    << 5.0 << 12.0 << std::numeric_limits<double>::quiet_NaN() << 37.6;
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetValueProxyTester::testSetMaxValue()
+{
+  // Setup
+  ctkRangeWidget ranger;
+  ranger.setMinimum(-200);
+  ranger.setMaximum(200);
+  ranger.setSingleStep(0.01);
+  ranger.setMaximumValue(-32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  ranger.setValueProxy(&proxy);
+
+  // \todo when valuesChanged is fixed
+  // Spy
+  //QSignalSpy valueSpy(&ranger, SIGNAL(maximumValueChanged(double)));
+
+  // Test
+  QFETCH(double, value);
+  ranger.setMaximumValue(value);
+
+  QFETCH(double, expectedValue);
+  //getSpyReport(valueSpy, expectedValue);
+  ctkTest::COMPARE(ranger.maximumValue(), expectedValue);
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetValueProxyTester::testSetMaxValue_data()
+{
+  QTest::addColumn<double>("coefficient");
+  QTest::addColumn<double>("offset");
+  QTest::addColumn<double>("value");
+  QTest::addColumn<double>("expectedValue");
+
+  //---------------------------------------------------------------------------
+  // Offset
+  QTest::newRow("Offset only") << 1.0 << 42.19 << 0.1 << 0.1;
+
+  QTest::newRow("Offset only: less than min")
+    << 1.0 << 42.19 << -510.0 << -32.6 - 42.19;
+  QTest::newRow("Offset only: less than min but ok with offset")
+    << 1.0 << 42.19 << -230.0 << -32.6 - 42.19;
+  QTest::newRow("Offset only: less than min with offset")
+    << 1.0 << -42.19 << -190.0 << 9.59;
+
+  QTest::newRow("Offset only: more than max with offset")
+    << 1.0 << 42.19 << 160.0 << 157.81;
+  QTest::newRow("Offset only: more than max")
+    << 1.0 << -42.19 << 65010.0 << 242.19;
+  QTest::newRow("Offset only: less than max but ok with offset")
+    << 1.0 << -42.19 << 229.1 << 229.1;
+
+  QTest::newRow("Offset only: max")
+    << 1.0 << 42.19 << std::numeric_limits<double>::max() << 157.81;
+  QTest::newRow("Offset only:  min")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::max() << -74.79;
+  QTest::newRow("Offset only: infinity")
+    << 1.0 << 42.19 << std::numeric_limits<double>::infinity() << 157.81;
+  QTest::newRow("Offset only:  - infinity")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::infinity() << -74.79;
+  QTest::newRow("Offset only: Nan")
+    << 1.0 << 42.19 << std::numeric_limits<double>::quiet_NaN() << 157.81;
+
+  //---------------------------------------------------------------------------
+  // Coefficient
+  QTest::newRow("Coeff only") << 5.0 << 0.0 << 0.1 << 0.1;
+
+  QTest::newRow("Coeff only: less than min")
+    << 5.0 << 0.0 << -510.0 << -6.52;
+  QTest::newRow("Coeff only: less than min but ok with coeff")
+    << 0.5 << 0.0 << -230.0 << -65.2;
+  QTest::newRow("Coeff only: less than min with coeff")
+    << 5.0 << 0.0 << -190.0 << -6.52;
+
+  QTest::newRow("Coeff only: more than max with coeff")
+    << 5.0 << 0.0 << 160.0 << 40.0;
+  QTest::newRow("Coeff only: more than max")
+    << 5.0 << 0.0 << 65010.0 << 40.0;
+  QTest::newRow("Offset only: less than max but ok with coeff")
+    << 0.5 << 0.0 << 229.2 << 229.2;
+
+  QTest::newRow("Coeff only: max")
+    << 5.0 << 0.0 << std::numeric_limits<double>::max() << 40.0;
+  QTest::newRow("Coeff only:  min")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::max() << -6.52;
+  QTest::newRow("Coeff only: infinity")
+    << 5.0 << 0.0 << std::numeric_limits<double>::infinity() << 40.0;
+  QTest::newRow("Coeff only:  - infinity")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::infinity() << -6.52;
+  QTest::newRow("Coeff only: Nan")
+    << 5.0 << 0.0 << std::numeric_limits<double>::quiet_NaN() << 40.0;
+
+  //---------------------------------------------------------------------------
+  // Linear
+  QTest::newRow("Linear") << 5.0 << 0.0 << 0.1 << 0.1;
+
+  QTest::newRow("Linear: less than min")
+    << 5.0 << 12.0 << -510.0 << -8.92;
+  QTest::newRow("Linear: less than min but ok with function")
+    << 0.5 << 12.0 << -230.0 << -89.2;
+  QTest::newRow("Linear: less than min with function")
+    << 5.0 << 12.0 << -61.5 << -8.92;
+
+  QTest::newRow("Linear: more than max with function")
+    << 5.0 << 12.0 << 160.0 << 37.6;
+  QTest::newRow("Linear: more than max")
+    << 5.0 << 12.0 << 65010.0 << 37.6;
+  QTest::newRow("Offset only: less than max but ok with function")
+    << 0.5 << 12.0 << 229.2 << 229.2;
+
+  QTest::newRow("Linear: max")
+    << 5.0 << 12.0 << std::numeric_limits<double>::max() << 37.6;
+  QTest::newRow("Linear:  min")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::max() << -8.92;
+  QTest::newRow("Linear: infinity")
+    << 5.0 << 12.0 << std::numeric_limits<double>::infinity() << 37.6;
+  QTest::newRow("Linear:  - infinity")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::infinity() << -8.92;
+  QTest::newRow("Linear: Nan")
+    << 5.0 << 12.0 << std::numeric_limits<double>::quiet_NaN() << 37.6;
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkRangeWidgetValueProxyTest)
+#include "moc_ctkRangeWidgetValueProxyTest.cpp"

+ 190 - 0
Libs/Widgets/Testing/Cpp/ctkSliderWidgetValueProxyTest.cpp

@@ -0,0 +1,190 @@
+/*=========================================================================
+
+  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.
+
+=========================================================================*/
+
+// Qt includes
+#include <QApplication>
+#include <QDoubleSpinBox>
+#include <QLineEdit>
+#include <QString>
+#include <QStyle>
+#include <QStyleOptionSlider>
+#include <QTimer>
+
+// CTK includes
+#include "ctkDoubleSlider.h"
+#include "ctkDoubleSpinBox.h"
+#include "ctkLinearValueProxy.h"
+#include "ctkSliderWidget.h"
+#include "ctkTest.h"
+#include "ctkValueProxy.h"
+
+namespace
+{
+//-----------------------------------------------------------------------------
+void getSpyReport(QSignalSpy& spy, double expectedValue)
+{
+  QCOMPARE(spy.count(), 1);
+
+  QList<QVariant> arguments = spy.takeFirst(); // take the first signal
+  ctkTest::COMPARE(arguments.at(0).toDouble(), expectedValue);
+}
+} // end namespace
+
+// ----------------------------------------------------------------------------
+class ctkSliderWidgetValueProxyTester: public QObject
+{
+  Q_OBJECT
+private slots:
+
+  void testSetValue();
+  void testSetValue_data();
+};
+
+//-----------------------------------------------------------------------------
+void ctkSliderWidgetValueProxyTester::testSetValue()
+{
+  // Setup
+  ctkSliderWidget slider;
+  slider.setMinimum(-200);
+  slider.setMaximum(200);
+  slider.setValue(-32.6);
+
+  QFETCH(double, coefficient);
+  QFETCH(double, offset);
+  ctkLinearValueProxy proxy;
+  proxy.setCoefficient(coefficient);
+  proxy.setOffset(offset);
+  slider.setValueProxy(&proxy);
+
+  // Spy
+  QSignalSpy valueSpy(&slider, SIGNAL(valueChanged(double)));
+
+  // Test
+  QFETCH(double, value);
+  slider.setValue(value);
+
+  QFETCH(double, expectedValue);
+  getSpyReport(valueSpy, expectedValue);
+  ctkTest::COMPARE(slider.value(), expectedValue);
+}
+
+//-----------------------------------------------------------------------------
+void ctkSliderWidgetValueProxyTester::testSetValue_data()
+{
+  QTest::addColumn<double>("coefficient");
+  QTest::addColumn<double>("offset");
+  QTest::addColumn<double>("value");
+  QTest::addColumn<double>("expectedValue");
+
+  //---------------------------------------------------------------------------
+  // Offset
+  QTest::newRow("Offset only") << 1.0 << 42.19176 << 0.1 << 0.1;
+
+  QTest::newRow("Offset only: less than min")
+    << 1.0 << 42.19 << -510.0 << -242.19;
+  QTest::newRow("Offset only: less than min but ok with offset")
+    << 1.0 << 42.19 << -230.0 << -230.0;
+  QTest::newRow("Offset only: less than min with offset")
+    << 1.0 << -42.19 << -190.0 << -157.81;
+
+  QTest::newRow("Offset only: more than max with offset")
+    << 1.0 << 42.19 << 160.0 << 157.81;
+  QTest::newRow("Offset only: more than max")
+    << 1.0 << -42.19 << 65010.0 << 242.19;
+  QTest::newRow("Offset only: less than max but ok with offset")
+    << 1.0 << -42.19 << 229.1 << 229.1;
+
+  QTest::newRow("Offset only: max")
+    << 1.0 << 42.19 << std::numeric_limits<double>::max() << 157.81;
+  QTest::newRow("Offset only:  min")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::max() << -242.19;
+  QTest::newRow("Offset only: infinity")
+    << 1.0 << 42.19 << std::numeric_limits<double>::infinity() << 157.81;
+  QTest::newRow("Offset only:  - infinity")
+    << 1.0 << 42.19 << -std::numeric_limits<double>::infinity() << -242.19;
+  QTest::newRow("Offset only: Nan")
+    << 1.0 << 42.19 << std::numeric_limits<double>::quiet_NaN() << 157.81;
+
+  // coeff // offset // value // expectedValue
+  //---------------------------------------------------------------------------
+  // Coefficient
+  QTest::newRow("Coeff only") << 5.0 << 0.0 << 0.1 << 0.1;
+
+  QTest::newRow("Coeff only: less than min")
+    << 5.0 << 0.0 << -510.0 << -40.0;
+  QTest::newRow("Coeff only: less than min but ok with coeff")
+    << 0.5 << 0.0 << -230.0 << -230.0;
+  QTest::newRow("Coeff only: less than min with coeff")
+    << 5.0 << 0.0 << -190.0 << -40.0;
+
+  QTest::newRow("Coeff only: more than max with coeff")
+    << 5.0 << 0.0 << 160.0 << 40.0;
+  QTest::newRow("Coeff only: more than max")
+    << 5.0 << 0.0 << 65010.0 << 40.0;
+  QTest::newRow("Offset only: less than max but ok with coeff")
+    << 0.5 << 0.0 << 229.2 << 229.2;
+
+  QTest::newRow("Coeff only: max")
+    << 5.0 << 0.0 << std::numeric_limits<double>::max() << 40.0;
+  QTest::newRow("Coeff only:  min")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::max() << -40.0;
+  QTest::newRow("Coeff only: infinity")
+    << 5.0 << 0.0 << std::numeric_limits<double>::infinity() << 40.0;
+  QTest::newRow("Coeff only:  - infinity")
+    << 5.0 << 0.0 << -std::numeric_limits<double>::infinity() << -40.0;
+  QTest::newRow("Coeff only: Nan")
+    << 5.0 << 0.0 << std::numeric_limits<double>::quiet_NaN() << 40.0;
+
+  // coeff // offset // value // expectedValue
+  //---------------------------------------------------------------------------
+  // Linear
+  QTest::newRow("Linear") << 5.0 << 0.0 << 0.1 << 0.1;
+
+  QTest::newRow("Linear: less than min")
+    << 5.0 << 12.0 << -510.0 << -42.4;
+  QTest::newRow("Linear: less than min but ok with function")
+    << 0.5 << 12.0 << -230.0 << -230.0;
+  QTest::newRow("Linear: less than min with function")
+    << 5.0 << 12.0 << -61.5 << -42.4;
+
+  QTest::newRow("Linear: more than max with function")
+    << 5.0 << 12.0 << 160.0 << 37.6;
+  QTest::newRow("Linear: more than max")
+    << 5.0 << 12.0 << 65010.0 << 37.6;
+  QTest::newRow("Offset only: less than max but ok with function")
+    << 0.5 << 12.0 << 229.2 << 229.2;
+
+  QTest::newRow("Linear: max")
+    << 5.0 << 12.0 << std::numeric_limits<double>::max() << 37.6;
+  QTest::newRow("Linear:  min")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::max() << -42.4;
+  QTest::newRow("Linear: infinity")
+    << 5.0 << 12.0 << std::numeric_limits<double>::infinity() << 37.6;
+  QTest::newRow("Linear:  - infinity")
+    << 5.0 << 12.0 << -std::numeric_limits<double>::infinity() << -42.4;
+  QTest::newRow("Linear: Nan")
+    << 5.0 << 12.0 << std::numeric_limits<double>::quiet_NaN() << 37.6;
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkSliderWidgetValueProxyTest)
+#include "moc_ctkSliderWidgetValueProxyTest.cpp"
+
+

+ 29 - 0
Libs/Widgets/ctkCoordinatesWidget.cpp

@@ -26,6 +26,7 @@
 // CTK includes
 #include "ctkCoordinatesWidget.h"
 #include "ctkDoubleSpinBox.h"
+#include "ctkValueProxy.h"
 
 // STD includes
 #include <cmath>
@@ -65,6 +66,7 @@ void ctkCoordinatesWidget::addSpinBox()
   spinBox->setSingleStep(this->SingleStep);
   spinBox->setMinimum(this->Minimum);
   spinBox->setMaximum(this->Maximum);
+  spinBox->setValueProxy(this->Proxy.data());
   connect( spinBox, SIGNAL(valueChanged(double)),
            this, SLOT(updateCoordinate(double)));
   // Same number of decimals within the spinboxes.
@@ -553,3 +555,30 @@ double ctkCoordinatesWidget::squaredNorm(double* coordinates, int dimension)
     }
   return sum;
 }
+
+//----------------------------------------------------------------------------
+void ctkCoordinatesWidget::setValueProxy(ctkValueProxy* proxy)
+{
+  if (this->Proxy.data() == proxy)
+    {
+    return;
+    }
+
+  this->Proxy = proxy;
+  for (int i = 0; i < this->Dimension; ++i)
+    {
+    QLayoutItem* item = this->layout()->itemAt(i);
+    ctkDoubleSpinBox* spinBox =
+      item ? qobject_cast<ctkDoubleSpinBox*>(item->widget()) : 0;
+    if ( spinBox)
+      {
+      spinBox->setValueProxy(this->Proxy.data());
+      }
+    }
+}
+
+//----------------------------------------------------------------------------
+ctkValueProxy* ctkCoordinatesWidget::valueProxy() const
+{
+  return this->Proxy.data();
+}

+ 7 - 0
Libs/Widgets/ctkCoordinatesWidget.h

@@ -23,6 +23,7 @@
 
 // Qt includes
 #include <QWidget>
+#include <QWeakPointer>
 
 // CTK includes
 #include "ctkDoubleSpinBox.h"
@@ -114,6 +115,11 @@ public:
   /// Convenient function that sets up to 4 elements of the coordinates.
   void setCoordinates(double x, double y = 0., double z = 0., double w = 0.);
 
+  /// Set/Get the value proxy of the spinboxes used to display the coordinates.
+  /// \sa setValueProxy(), valueProxy()
+  void setValueProxy(ctkValueProxy* proxy);
+  ctkValueProxy* valueProxy() const;
+
 public Q_SLOTS:
   void normalize();
 
@@ -151,6 +157,7 @@ protected:
   int     Dimension;
   double* Coordinates;
   QList<int> LastUserEditedCoordinates;
+  QWeakPointer<ctkValueProxy> Proxy;
 };
 
 #endif

+ 58 - 12
Libs/Widgets/ctkDoubleRangeSlider.cpp

@@ -21,10 +21,12 @@
 // Qt includes
 #include <QDebug>
 #include <QHBoxLayout>
+#include <QWeakPointer>
 
 // CTK includes
 #include "ctkRangeSlider.h"
 #include "ctkDoubleRangeSlider.h"
+#include "ctkValueProxy.h"
 
 // STD includes
 #include <limits>
@@ -62,6 +64,8 @@ public:
   double MinValue;
   double MaxValue;
 
+  QWeakPointer<ctkValueProxy> Proxy;
+
 private:
   Q_DISABLE_COPY(ctkDoubleRangeSliderPrivate);
 };
@@ -339,13 +343,22 @@ void ctkDoubleRangeSlider::setPositions(double minPos, double maxPos)
 double ctkDoubleRangeSlider::minimumValue()const
 {
   Q_D(const ctkDoubleRangeSlider);
-  return d->MinValue;
+  double val = d->MinValue;
+  if (d->Proxy)
+    {
+    val = d->Proxy.data()->valueFromProxyValue(val);
+    }
+  return val;
 }
 
 // --------------------------------------------------------------------------
 void ctkDoubleRangeSlider::setMinimumValue(double newMinValue)
 {
   Q_D(ctkDoubleRangeSlider);
+  if (d->Proxy)
+    {
+    newMinValue = d->Proxy.data()->proxyValueFromValue(newMinValue);
+    }
   newMinValue = qBound(d->Minimum, newMinValue, d->Maximum);
   d->updateMinOffset(newMinValue);  
   if (newMinValue >= d->MaxValue)
@@ -367,8 +380,8 @@ void ctkDoubleRangeSlider::setMinimumValue(double newMinValue)
     // similar to the old value.
     if (qAbs(newMinValue - oldValue) > (d->SingleStep * 0.000000001))
       {
-      emit this->valuesChanged(newMinValue, this->maximumValue());
-      emit this->minimumValueChanged(newMinValue);
+      emit this->valuesChanged(this->minimumValue(), this->maximumValue());
+      emit this->minimumValueChanged(this->minimumValue());
       }
     }
 }
@@ -377,13 +390,22 @@ void ctkDoubleRangeSlider::setMinimumValue(double newMinValue)
 double ctkDoubleRangeSlider::maximumValue()const
 {
   Q_D(const ctkDoubleRangeSlider);
-  return d->MaxValue;
+  double val = d->MaxValue;
+  if (d->Proxy)
+    {
+    val = d->Proxy.data()->valueFromProxyValue(val);
+    }
+  return val;
 }
 
 // --------------------------------------------------------------------------
 void ctkDoubleRangeSlider::setMaximumValue(double newMaxValue)
 {
   Q_D(ctkDoubleRangeSlider);
+  if (d->Proxy)
+    {
+    newMaxValue = d->Proxy.data()->proxyValueFromValue(newMaxValue);
+    }
   newMaxValue = qBound(d->Minimum, newMaxValue, d->Maximum);
   d->updateMaxOffset(newMaxValue);
   if (newMaxValue <= d->MinValue)
@@ -405,8 +427,8 @@ void ctkDoubleRangeSlider::setMaximumValue(double newMaxValue)
     // similar to the old value.
     if (qAbs(newMaxValue - oldValue) > (d->SingleStep * 0.000000001))
       {
-      emit this->valuesChanged(this->minimumValue(), newMaxValue);
-      emit this->maximumValueChanged(newMaxValue);
+      emit this->valuesChanged(this->minimumValue(), this->maximumValue());
+      emit this->maximumValueChanged(this->maximumValue());
       }
     }
 }
@@ -418,6 +440,11 @@ void ctkDoubleRangeSlider::setValues(double newMinVal, double newMaxVal)
   // We can't call setMinimumValue() and setMaximumValue() as they would
   // generate an inconsistent state. when minimumValueChanged() is fired the
   // new max value wouldn't be updated yet.
+  if (d->Proxy)
+    {
+    newMaxVal = d->Proxy.data()->proxyValueFromValue(newMaxVal);
+    newMinVal = d->Proxy.data()->proxyValueFromValue(newMinVal);
+    }
   double newMinValue = qBound(d->Minimum, qMin(newMinVal, newMaxVal), d->Maximum);
   double newMaxValue = qBound(d->Minimum, qMax(newMinVal, newMaxVal), d->Maximum);
   d->updateMinOffset(newMinValue);
@@ -443,14 +470,14 @@ void ctkDoubleRangeSlider::setValues(double newMinVal, double newMaxVal)
     bool maxChanged = qAbs(newMaxValue - oldMaxValue) > (d->SingleStep * 0.000000001);
     if (minChanged || maxChanged)
       {
-      emit this->valuesChanged(newMinValue, newMaxValue);
+      emit this->valuesChanged(this->minimumValue(), this->maximumValue());
       if (minChanged)
         {
-        emit this->minimumValueChanged(newMinValue);
+        emit this->minimumValueChanged(this->minimumValue());
         }
       if (maxChanged)
         {
-        emit this->maximumValueChanged(newMaxValue);
+        emit this->maximumValueChanged(this->maximumValue());
         }
       }
     }
@@ -608,14 +635,14 @@ void ctkDoubleRangeSlider::onValuesChanged(int newMinValue, int newMaxValue)
     }
   d->MinValue = doubleNewMinValue;
   d->MaxValue = doubleNewMaxValue;
-  emit this->valuesChanged(d->MinValue, d->MaxValue);
+  emit this->valuesChanged(this->minimumValue(), this->maximumValue());
   if (emitMinValueChanged)
     {
-    emit this->minimumValueChanged(d->MinValue);
+    emit this->minimumValueChanged(this->minimumValue());
     }
   if (emitMaxValueChanged)
     {
-    emit this->maximumValueChanged(d->MaxValue);
+    emit this->maximumValueChanged(this->maximumValue());
     }
 }
 
@@ -674,3 +701,22 @@ void ctkDoubleRangeSlider::setSlider(ctkRangeSlider* newslider)
   d->Slider = newslider;
   d->connectSlider();
 }
+
+//----------------------------------------------------------------------------
+void ctkDoubleRangeSlider::setValueProxy(ctkValueProxy* proxy)
+{
+  Q_D(ctkDoubleRangeSlider);
+  if (d->Proxy.data() == proxy)
+    {
+    return;
+    }
+
+  d->Proxy = proxy;
+}
+
+//----------------------------------------------------------------------------
+ctkValueProxy* ctkDoubleRangeSlider::valueProxy() const
+{
+  Q_D(const ctkDoubleRangeSlider);
+  return d->Proxy.data();
+}

+ 6 - 0
Libs/Widgets/ctkDoubleRangeSlider.h

@@ -31,6 +31,7 @@
 
 class ctkRangeSlider;
 class ctkDoubleRangeSliderPrivate;
+class ctkValueProxy;
 
 /// \ingroup Widgets
 /// ctkDoubleRangeSlider is a slider that controls 2 numbers as double.
@@ -177,6 +178,11 @@ public:
   bool symmetricMoves()const; 
   void setSymmetricMoves(bool symmetry);
 
+  /// Set/Get the value proxy of the internal range slider.
+  /// \sa setValueProxy(), valueProxy()
+  void setValueProxy(ctkValueProxy* proxy);
+  ctkValueProxy* valueProxy() const;
+
 Q_SIGNALS:
   ///
   /// This signal is emitted when the slider minimum value has changed, 

+ 44 - 4
Libs/Widgets/ctkDoubleSlider.cpp

@@ -28,11 +28,15 @@
 
 // CTK includes
 #include "ctkDoubleSlider.h"
+#include "ctkValueProxy.h"
 
 // STD includes
 #include <limits>
 
 //-----------------------------------------------------------------------------
+// ctkSlider
+
+//-----------------------------------------------------------------------------
 class ctkSlider: public QSlider
 {
 public:
@@ -46,6 +50,9 @@ ctkSlider::ctkSlider(QWidget* parent): QSlider(parent)
 }
 
 //-----------------------------------------------------------------------------
+// ctkDoubleSliderPrivate
+
+//-----------------------------------------------------------------------------
 class ctkDoubleSliderPrivate
 {
   Q_DECLARE_PUBLIC(ctkDoubleSlider);
@@ -69,6 +76,7 @@ public:
   double      SingleStep;
   double      PageStep;
   double      Value;
+  QWeakPointer<ctkValueProxy> Proxy;
 };
 
 // --------------------------------------------------------------------------
@@ -153,6 +161,9 @@ void ctkDoubleSliderPrivate::updateOffset(double value)
   this->Offset = (value / this->SingleStep) - this->toInt(value);
 }
 
+//-----------------------------------------------------------------------------
+// ctkDoubleSlider
+
 // --------------------------------------------------------------------------
 ctkDoubleSlider::ctkDoubleSlider(QWidget* _parent) : Superclass(_parent)
   , d_ptr(new ctkDoubleSliderPrivate(*this))
@@ -211,7 +222,7 @@ void ctkDoubleSlider::setRange(double min, double max)
   emit this->rangeChanged(d->Minimum, d->Maximum);
   /// In case QSlider::setRange(...) didn't notify the value
   /// has changed.
-  this->setValue(d->Value);
+  this->setValue(this->value());
 }
 
 // --------------------------------------------------------------------------
@@ -246,13 +257,23 @@ void ctkDoubleSlider::setSliderPosition(double newSliderPosition)
 double ctkDoubleSlider::value()const
 {
   Q_D(const ctkDoubleSlider);
-  return d->Value;
+  double val = d->Value;
+  if (d->Proxy)
+    {
+    val = d->Proxy.data()->valueFromProxyValue(val);
+    }
+  return val;
 }
 
 // --------------------------------------------------------------------------
 void ctkDoubleSlider::setValue(double newValue)
 {
   Q_D(ctkDoubleSlider);
+  if (d->Proxy)
+    {
+    newValue = d->Proxy.data()->proxyValueFromValue(newValue);
+    }
+
   newValue = qBound(d->Minimum, newValue, d->Maximum);
   d->updateOffset(newValue);
   int newIntValue = d->toInt(newValue);
@@ -270,7 +291,7 @@ void ctkDoubleSlider::setValue(double newValue)
     // similar to the old value.
     if (qAbs(newValue - oldValue) > (d->SingleStep * 0.000000001))
       {
-      emit this->valueChanged(newValue);
+      emit this->valueChanged(this->value());
       }
     }
 }
@@ -460,7 +481,7 @@ void ctkDoubleSlider::onValueChanged(int newValue)
     return;
     }
   d->Value = doubleNewValue;
-  emit this->valueChanged(d->Value);
+  emit this->valueChanged(this->value());
 }
 
 // --------------------------------------------------------------------------
@@ -511,3 +532,22 @@ bool ctkDoubleSlider::eventFilter(QObject* watched, QEvent* event)
   return this->Superclass::eventFilter(watched, event);
 }
 
+//----------------------------------------------------------------------------
+void ctkDoubleSlider::setValueProxy(ctkValueProxy* proxy)
+{
+  Q_D(ctkDoubleSlider);
+  if (d->Proxy.data() == proxy)
+    {
+    return;
+    }
+
+  d->Proxy = proxy;
+}
+
+//----------------------------------------------------------------------------
+ctkValueProxy* ctkDoubleSlider::valueProxy() const
+{
+  Q_D(const ctkDoubleSlider);
+  return d->Proxy.data();
+}
+

+ 16 - 1
Libs/Widgets/ctkDoubleSlider.h

@@ -30,6 +30,7 @@
 #include "ctkWidgetsExport.h"
 
 class ctkDoubleSliderPrivate;
+class ctkValueProxy;
 
 /// \ingroup Widgets
 /// ctkDoubleSlider is a QSlider that controls doubles instead of integers.
@@ -139,7 +140,10 @@ public:
   
   /// 
   /// This property holds the current slider position.
-  /// If tracking is enabled (the default), this is identical to value.
+  /// If there is no proxy installed and tracking is enabled (the default),
+  /// this is identical to value.
+  /// With a proxy installed, it allows to modify the proxy value.
+  /// \sa value(), setValue(), setValueProxy(), valueProxy()
   double sliderPosition()const;
   void setSliderPosition(double);
 
@@ -195,6 +199,17 @@ public:
   /// Reimplemented for internal reasons (handle tooltip).
   virtual bool eventFilter(QObject*, QEvent*);
 
+  /// Install or remove a value proxy filter. The value proxy decouples the
+  /// displayed value from the value retrieved by the value property.
+  /// For example, the value proxy can allow one to display celsius in the
+  /// spinbox while the value retrieved from the value property and signals
+  /// are in farenheit.
+  /// To remove the proxy, simply install a new empty proxy. The proxy
+  /// installation/removal is silent.
+  /// \sa setValueProxy(), valueProxy()
+  void setValueProxy(ctkValueProxy* proxy);
+  ctkValueProxy* valueProxy() const;
+
 public Q_SLOTS:
   /// 
   /// This property holds the slider's current value.

+ 60 - 11
Libs/Widgets/ctkRangeWidget.cpp

@@ -21,9 +21,11 @@
 // Qt includes
 #include <QDebug>
 #include <QMouseEvent>
+#include <QWeakPointer>
 
 // CTK includes
 #include "ctkRangeWidget.h"
+#include "ctkValueProxy.h"
 #include "ui_ctkRangeWidget.h"
 
 // STD includes
@@ -53,6 +55,7 @@ public:
   double        MaximumValueBeforeChange;
   bool          AutoSpinBoxWidth;
   Qt::Alignment SpinBoxAlignment;
+  QWeakPointer<ctkValueProxy> Proxy;
 };
 
 // --------------------------------------------------------------------------
@@ -66,7 +69,6 @@ bool ctkRangeWidgetPrivate::equal(double v1, double v2)const
     {// NaN check
     return true;
     }
-  qDebug() << "equal: " << v1 << v2 << qAbs(v1 - v2) << pow(10., -this->MinimumSpinBox->decimals());
   return qAbs(v1 - v2) < pow(10., -this->MinimumSpinBox->decimals());
 }
 
@@ -301,7 +303,6 @@ void ctkRangeWidget::setMaximum(double max)
 void ctkRangeWidget::setRange(double min, double max)
 {
   Q_D(ctkRangeWidget);
-  qDebug() << "setRange" << min << max;
   
   double oldMin = d->MinimumSpinBox->minimum();
   double oldMax = d->MaximumSpinBox->maximum();
@@ -326,7 +327,6 @@ void ctkRangeWidget::setRange(double min, double max)
     {
     emit rangeChanged(d->MinimumSpinBox->minimum(), d->MaximumSpinBox->maximum());
     }
-  qDebug() << "end setRange";
 }
 
 // --------------------------------------------------------------------------
@@ -368,6 +368,12 @@ void ctkRangeWidget::values(double &minValue, double &maxValue)const
   Q_ASSERT(d->equal(d->Slider->maximumValue(), d->MaximumSpinBox->value()));
   minValue = d->Changing ? d->MinimumValueBeforeChange : d->Slider->minimumValue();
   maxValue = d->Changing ? d->MaximumValueBeforeChange : d->Slider->maximumValue();
+
+  if (d->Proxy)
+    {
+    minValue = d->Proxy.data()->valueFromProxyValue(minValue);
+    maxValue = d->Proxy.data()->valueFromProxyValue(maxValue);
+    }
 }
 
 // --------------------------------------------------------------------------
@@ -375,7 +381,14 @@ double ctkRangeWidget::minimumValue()const
 {
   Q_D(const ctkRangeWidget);
   Q_ASSERT(d->equal(d->Slider->minimumValue(), d->MinimumSpinBox->value()));
-  return d->Changing ? d->MinimumValueBeforeChange : d->Slider->minimumValue();
+
+  double val =
+    d->Changing ? d->MinimumValueBeforeChange : d->Slider->minimumValue();
+  if (d->Proxy)
+    {
+    val = d->Proxy.data()->valueFromProxyValue(val);
+    }
+  return val;
 }
 
 // --------------------------------------------------------------------------
@@ -383,14 +396,24 @@ double ctkRangeWidget::maximumValue()const
 {
   Q_D(const ctkRangeWidget);
   Q_ASSERT(d->equal(d->Slider->maximumValue(), d->MaximumSpinBox->value()));
-  return d->Changing ? d->MaximumValueBeforeChange : d->Slider->maximumValue();
+
+  double val =
+    d->Changing ? d->MaximumValueBeforeChange : d->Slider->maximumValue();
+  if (d->Proxy)
+    {
+    val = d->Proxy.data()->valueFromProxyValue(val);
+    }
+  return val;
 }
 
 // --------------------------------------------------------------------------
 void ctkRangeWidget::setMinimumValue(double _value)
 {
   Q_D(ctkRangeWidget);
-  qDebug() << "setMinimumValue" << _value;
+  if (d->Proxy)
+    {
+    _value = d->Proxy.data()->proxyValueFromValue(_value);
+    }
 
   // disable the tracking temporally to emit the
   // signal valueChanged if changeValue() is called
@@ -401,14 +424,17 @@ void ctkRangeWidget::setMinimumValue(double _value)
   Q_ASSERT(d->equal(d->Slider->minimumValue(), d->MinimumSpinBox->value()));
   // restore the prop
   d->Changing = isChanging;
-  qDebug() << "end setMinimumValue";
 }
 
 // --------------------------------------------------------------------------
 void ctkRangeWidget::setMaximumValue(double _value)
 {
   Q_D(ctkRangeWidget);
-  qDebug() << "setMaximumValue" << _value;
+  if (d->Proxy)
+    {
+    _value = d->Proxy.data()->proxyValueFromValue(_value);
+    }
+
   // disable the tracking temporally to emit the
   // signal valueChanged if changeValue() is called
   bool isChanging = d->Changing;
@@ -418,14 +444,18 @@ void ctkRangeWidget::setMaximumValue(double _value)
   Q_ASSERT(d->equal(d->Slider->maximumValue(), d->MaximumSpinBox->value()));
   // restore the prop
   d->Changing = isChanging;
-  qDebug() << "end setMaximumValue";
 }
 
 // --------------------------------------------------------------------------
 void ctkRangeWidget::setValues(double newMinimumValue, double newMaximumValue)
 {
   Q_D(ctkRangeWidget);
-  qDebug() << "setValues" << newMinimumValue << newMaximumValue;
+  if (d->Proxy)
+    {
+    newMinimumValue = d->Proxy.data()->proxyValueFromValue(newMinimumValue);
+    newMaximumValue = d->Proxy.data()->proxyValueFromValue(newMaximumValue);
+    }
+
   if (newMinimumValue > newMaximumValue)
     {
     qSwap(newMinimumValue, newMaximumValue);
@@ -453,7 +483,6 @@ void ctkRangeWidget::setValues(double newMinimumValue, double newMaximumValue)
   Q_ASSERT(d->equal(d->Slider->maximumValue(), d->MaximumSpinBox->value()));
   // restore the prop
   d->Changing = isChanging;
-  qDebug() << "end setValues";
 }
 
 // --------------------------------------------------------------------------
@@ -475,6 +504,7 @@ void ctkRangeWidget::setMaximumToMinimumSpinBox(double maximum)
     {
     return;
     }
+
   d->MinimumSpinBox->setMaximum(maximum);
 }
 
@@ -804,3 +834,22 @@ ctkDoubleSpinBox* ctkRangeWidget::maximumSpinBox()const
   Q_D(const ctkRangeWidget);
   return d->MaximumSpinBox;
 }
+
+//----------------------------------------------------------------------------
+void ctkRangeWidget::setValueProxy(ctkValueProxy* proxy)
+{
+  Q_D(ctkRangeWidget);
+  if (d->Proxy.data() == proxy)
+    {
+    return;
+    }
+
+  d->Proxy = proxy;
+}
+
+//----------------------------------------------------------------------------
+ctkValueProxy* ctkRangeWidget::valueProxy() const
+{
+  Q_D(const ctkRangeWidget);
+  return d->Proxy.data();
+}

+ 6 - 0
Libs/Widgets/ctkRangeWidget.h

@@ -32,6 +32,7 @@
 class ctkDoubleRangeSlider;
 class ctkDoubleSpinBox;
 class ctkRangeWidgetPrivate;
+class ctkValueProxy;
 
 /// \ingroup Widgets
 ///
@@ -190,6 +191,11 @@ public:
   /// \sa minimumSpinBox(), slider()
   ctkDoubleSpinBox* maximumSpinBox()const;
 
+  /// Set/Get the value proxy of the slider and spinboxes.
+  /// \sa setValueProxy(), valueProxy()
+  void setValueProxy(ctkValueProxy* proxy);
+  ctkValueProxy* valueProxy() const;
+
 public Q_SLOTS:
   ///
   /// Reset the slider and spinbox to zero (value and position)

+ 24 - 0
Libs/Widgets/ctkSliderWidget.cpp

@@ -21,10 +21,12 @@
 // Qt includes
 #include <QDebug>
 #include <QMouseEvent>
+#include <QWeakPointer>
 
 // CTK includes
 #include "ctkPopupWidget.h"
 #include "ctkSliderWidget.h"
+#include "ctkValueProxy.h"
 #include "ui_ctkSliderWidget.h"
 
 // STD includes 
@@ -59,6 +61,7 @@ public:
   bool   BlockSetSliderValue;
   ctkSliderWidget::SynchronizeSiblings SynchronizeMode;
   ctkPopupWidget* SliderPopup;
+  QWeakPointer<ctkValueProxy> Proxy;
 };
 
 // --------------------------------------------------------------------------
@@ -669,3 +672,24 @@ ctkDoubleSlider* ctkSliderWidget::slider()
   Q_D(ctkSliderWidget);
   return d->Slider;
 }
+
+// --------------------------------------------------------------------------
+void ctkSliderWidget::setValueProxy(ctkValueProxy* proxy)
+{
+  Q_D(ctkSliderWidget);
+  if (d->Proxy.data() == proxy)
+    {
+    return;
+    }
+
+  d->Proxy = proxy;
+  this->slider()->setValueProxy(proxy);
+  this->spinBox()->setValueProxy(proxy);
+}
+
+// --------------------------------------------------------------------------
+ctkValueProxy* ctkSliderWidget::valueProxy() const
+{
+  Q_D(const ctkSliderWidget);
+  return d->Proxy.data();
+}

+ 10 - 0
Libs/Widgets/ctkSliderWidget.h

@@ -33,6 +33,7 @@ class ctkDoubleSlider;
 class ctkPopupWidget;
 class ctkSliderWidgetPrivate;
 class ctkDoubleSpinBox;
+class ctkValueProxy;
 
 /// \ingroup Widgets
 ///
@@ -250,6 +251,15 @@ public:
   /// with what you do with the slider as the spinbox might change
   /// properties automatically.
   ctkDoubleSlider* slider();
+
+  ///
+  /// Set/Get a value proxy filter.
+  /// This simply sets the same value proxy filter on the spinbox
+  /// and the slider
+  /// \sa setValueProxy(), valueProxy()
+  void setValueProxy(ctkValueProxy* proxy);
+  ctkValueProxy* valueProxy() const;
+
 public Q_SLOTS:
   /// 
   /// Reset the slider and spinbox to zero (value and position)