Explorar o código

Merge branch '348-order-of-magnitude-infinite-loop'

* 348-order-of-magnitude-infinite-loop:
  Reject inf single step in ctkRangeWidget
  Add support for inf and nan in ctkRangeWidget
  Add support for min, max, inf and NaN in ctkUtils
  Cleanup ctkUtilsTests using QTestLib
Julien Finet %!s(int64=13) %!d(string=hai) anos
pai
achega
3859f9bb31

+ 3 - 6
Libs/Core/Testing/Cpp/CMakeLists.txt

@@ -41,11 +41,9 @@ set(KITTests_SRCS
   ctkLoggerTest1.cpp
   ctkModelTesterTest1.cpp
   ctkModelTesterTest2.cpp
-  ctkUtilsClosestPowerOfTenTest1.cpp
   ctkUtilsCopyDirRecursivelyTest1.cpp
-  ctkUtilsOrderOfMagnitudeTest1.cpp
   ctkUtilsQtHandleToStringTest1.cpp
-  ctkUtilsSignificantDecimalsTest1.cpp
+  ctkUtilsTest.cpp
   ctkUtilsTest1.cpp
   ctkUtilsTest2.cpp
   ctkUtilsTest3.cpp
@@ -105,6 +103,7 @@ set(Tests_Helpers_MOC_CPP)
 QT4_WRAP_CPP(Tests_Helpers_MOC_CPP ${Tests_Helpers_MOC_SRCS})
 QT4_GENERATE_MOCS(
   ctkBooleanMapperTest.cpp
+  ctkUtilsTest.cpp
   )
 
 if(HAVE_BFD)
@@ -165,11 +164,9 @@ SIMPLE_TEST( ctkSingletonTest1 )
 SIMPLE_TEST( ctkTransferFunctionTest1 )
 SIMPLE_TEST( ctkTransferFunctionRepresentationTest1 )
 SIMPLE_TEST( ctkTransferFunctionRepresentationTest2 )
-SIMPLE_TEST( ctkUtilsClosestPowerOfTenTest1 )
 SIMPLE_TEST( ctkUtilsCopyDirRecursivelyTest1 )
-SIMPLE_TEST( ctkUtilsOrderOfMagnitudeTest1 )
 SIMPLE_TEST( ctkUtilsQtHandleToStringTest1 )
-SIMPLE_TEST( ctkUtilsSignificantDecimalsTest1 )
+SIMPLE_TEST( ctkUtilsTest )
 SIMPLE_TEST( ctkUtilsTest1 )
 SIMPLE_TEST( ctkUtilsTest2 )
 SIMPLE_TEST( ctkUtilsTest3 )

+ 0 - 134
Libs/Core/Testing/Cpp/ctkUtilsClosestPowerOfTenTest1.cpp

@@ -1,134 +0,0 @@
-/*=========================================================================
-
-  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 <QDebug>
-#include <QStringList>
-
-// CTK includes
-#include "ctkUtils.h"
-
-// STD includes
-#include <stdlib.h>
-#include <iostream>
-#include <limits>
-#include <string>
-#include <vector>
-
-//-----------------------------------------------------------------------------
-int ctkUtilsClosestPowerOfTenTest1(int , char *  [] )
-{
-  std::cout.precision(16);
-
-  if (ctk::closestPowerOfTen(1.) != 1.)
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << 1. << ". Found " << ctk::closestPowerOfTen(1.)
-              << " instead of 1." << std::endl;
-    return EXIT_FAILURE;
-    }
-  if (ctk::closestPowerOfTen(2.) != 1.)
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << 2. << ". Found " << ctk::closestPowerOfTen(2.)
-              << " instead of 1." << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::closestPowerOfTen(10.) != 10.)
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << 10. << ". Found " << ctk::closestPowerOfTen(10.)
-              << " instead of 10." << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::closestPowerOfTen(45.) != 10.)
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << 45. << ". Found " << ctk::closestPowerOfTen(45.)
-              << " instead of 10." << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::closestPowerOfTen(98) != 100.)
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << 98. << ". Found " << ctk::closestPowerOfTen(98.)
-              << " instead of 100." << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::closestPowerOfTen(50.) != 10.)
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << 50. << ". Found " << ctk::closestPowerOfTen(50.)
-              << " instead of 10." << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::closestPowerOfTen(-1234.) != -1000)
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << -1234 << ". Found " << ctk::closestPowerOfTen(-1234)
-              << " instead of -1000" << std::endl;
-    return EXIT_FAILURE;
-    }
-  double closest  = ctk::closestPowerOfTen(0.01);
-  if ( closest < 0.01 - std::numeric_limits<double>::epsilon() ||
-       closest > 0.01 + std::numeric_limits<double>::epsilon())
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << 0.01 << ". Found " << closest
-              << " instead of 0.01" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  closest = ctk::closestPowerOfTen(0.00000000015);
-  if ( closest < 0.0000000001 - std::numeric_limits<double>::epsilon() ||
-       closest > 0.0000000001 + std::numeric_limits<double>::epsilon())
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << 0.00000000015 << ". Found " << closest
-              << " instead of 0.0000000001" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  closest = ctk::closestPowerOfTen(0.1);
-  if (closest < 0.1 - std::numeric_limits<double>::epsilon() ||
-       closest > 0.1 + std::numeric_limits<double>::epsilon())
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << 0.1 << ". Found " << closest
-              << " instead of 0.1" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  closest = ctk::closestPowerOfTen(0.);
-  if (closest != 0)
-    {
-    std::cerr << "closest power of 10 failed for number:"
-              << 0. << ". Found " << closest
-              << " instead of 0" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  return EXIT_SUCCESS;
-}

+ 0 - 155
Libs/Core/Testing/Cpp/ctkUtilsOrderOfMagnitudeTest1.cpp

@@ -1,155 +0,0 @@
-/*=========================================================================
-
-  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 <QDebug>
-#include <QStringList>
-
-// CTK includes
-#include "ctkUtils.h"
-
-// STD includes
-#include <iostream>
-#include <limits>
-#include <stdlib.h>
-#include <string>
-#include <vector>
-
-bool test(double value, int expected)
-{
-  int decimals = ctk::significantDecimals(value);
-  if (decimals != expected)
-    {
-    std::cerr << std::fixed << value << " decimals: " << decimals << " -> " << expected << std::endl;
-    return false;
-    }
-  return true;
-}
-
-//-----------------------------------------------------------------------------
-int ctkUtilsOrderOfMagnitudeTest1(int , char *  [] )
-{
-  std::cout.precision(16);
-
-  if (ctk::orderOfMagnitude(1.) != 0)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 1. << ". Found " << ctk::orderOfMagnitude(1.)
-              << " instead of 0" << std::endl;
-    return EXIT_FAILURE;
-    }
-  if (ctk::orderOfMagnitude(2.) != 0)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 2. << ". Found " << ctk::orderOfMagnitude(2.)
-              << " instead of 0" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(10.) != 1)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 10. << ". Found " << ctk::orderOfMagnitude(10.)
-              << " instead of 1" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(11.) != 1)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 11. << ". Found " << ctk::orderOfMagnitude(11.)
-              << " instead of 1" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(0.1) != -1)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 0.1 << ". Found " << ctk::orderOfMagnitude(0.1)
-              << " instead of -1" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(0.11) != -1)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 0.11 << ". Found " << ctk::orderOfMagnitude(0.11)
-              << " instead of -1" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(0.2) != -1)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 0.2 << ". Found " << ctk::orderOfMagnitude(0.2)
-              << " instead of -1" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(0.01) != -2)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 0.01 << ". Found " << ctk::orderOfMagnitude(0.01)
-              << " instead of -2" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(0.0000000001) != -10)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 0.0000000001 << ". Found " << ctk::orderOfMagnitude(0.0000000001)
-              << " instead of -10" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(10.0001) != 1)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 10.0001 << ". Found " << ctk::orderOfMagnitude(10.0001)
-              << " instead of 1" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(100000000000.0001) != 11)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 100000000000.0001 << ". Found " << ctk::orderOfMagnitude(100000000000.0001)
-              << " instead of 11" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(100000000001.0001) != 11)
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 100000000001.0001 << ". Found " << ctk::orderOfMagnitude(100000000001.0001)
-              << " instead of 11" << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  if (ctk::orderOfMagnitude(0) != std::numeric_limits<int>::min())
-    {
-    std::cerr << "order of magnitude failed for number:"
-              << 0 << ". Found " << ctk::orderOfMagnitude(0)
-              << " instead of " << std::numeric_limits<int>::min() << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  return EXIT_SUCCESS;
-}

+ 0 - 152
Libs/Core/Testing/Cpp/ctkUtilsSignificantDecimalsTest1.cpp

@@ -1,152 +0,0 @@
-/*=========================================================================
-
-  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 <QDebug>
-#include <QStringList>
-
-// CTK includes
-#include "ctkUtils.h"
-
-// STD includes
-#include <stdlib.h>
-#include <iostream>
-#include <string>
-#include <vector>
-
-bool testSignificantDecimals(double value, int expected)
-{
-  int decimals = ctk::significantDecimals(value);
-  if (decimals != expected)
-    {
-    std::cerr << std::fixed << value << " decimals: " << decimals << " -> " << expected << std::endl;
-    return true;
-    }
-  return false;
-}
-
-//-----------------------------------------------------------------------------
-int ctkUtilsSignificantDecimalsTest1(int , char *  [] )
-{
-  std::cout.precision(16);
-  if (testSignificantDecimals(123456., 0))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.1, 1))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.12, 2))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.123, 3))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.122, 3))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.1223, 4))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.1234, 4))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.0123, 4))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.0012, 4))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.001234, 6))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.000123, 6))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.0000, 0))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.0001, 4))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.3333333, 2))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.1333333, 3))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.3333334, 2))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.00122, 5))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.00123, 5))
-    {
-    return EXIT_FAILURE;
-    }
-  // internally representated as 123456.001109999997425
-  if (testSignificantDecimals(123456.00111, 5))
-    {
-    return EXIT_FAILURE;
-    }
-  // internally representated as 123456.270000000004075
-  if (testSignificantDecimals(123456.26999999999999996, 2))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(123456.863899999999987, 4))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(0.5, 1))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(0.25, 2))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(0.125, 3))
-    {
-    return EXIT_FAILURE;
-    }
-  if (testSignificantDecimals(0.1234567891013151, 16))
-    {
-    return EXIT_FAILURE;
-    }
-  return EXIT_SUCCESS;
-}

+ 215 - 0
Libs/Core/Testing/Cpp/ctkUtilsTest.cpp

@@ -0,0 +1,215 @@
+/*=========================================================================
+
+  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.
+
+=========================================================================*/
+
+// CTK includes
+#include "ctkUtils.h"
+#include "ctkTest.h"
+
+// STD includes
+#include <iostream>
+#include <limits>
+
+// ----------------------------------------------------------------------------
+class ctkUtilsTester: public QObject
+{
+  Q_OBJECT
+private slots:
+  void initTestCase();
+
+  void testOrderOfMagnitude();
+  void testOrderOfMagnitude_data();
+
+  void testClosestPowerOfTen();
+  void testClosestPowerOfTen_data();
+
+  void testSignificantDecimals();
+  void testSignificantDecimals_data();
+};
+
+// ----------------------------------------------------------------------------
+void ctkUtilsTester::initTestCase()
+{
+  std::cout.precision(16);
+  std::cerr.precision(16);
+  std::cout << std::fixed;
+  std::cerr << std::fixed;
+}
+
+// ----------------------------------------------------------------------------
+void ctkUtilsTester::testOrderOfMagnitude()
+{
+  QFETCH(double, value);
+  QFETCH(int, expectedOrder);
+  QCOMPARE(ctk::orderOfMagnitude(value), expectedOrder);
+}
+
+// ----------------------------------------------------------------------------
+void ctkUtilsTester::testOrderOfMagnitude_data()
+{
+  QTest::addColumn<double>("value");
+  QTest::addColumn<int>("expectedOrder");
+
+  QTest::newRow("1. -> 0") << 1. << 0;
+  QTest::newRow("2. -> 0") << 2. << 0;
+  QTest::newRow("10. -> 1") << 10. << 1;
+  QTest::newRow("11. -> 1") << 11. << 1;
+  QTest::newRow("0.1 -> -1") << 0.1 << -1;
+  QTest::newRow("0.11 -> -1") << 0.1 << -1;
+  QTest::newRow("0.2 -> -1") << 0.2 << -1;
+  QTest::newRow("0.01 -> -2") << 0.01 << -2;
+  QTest::newRow("0.0000000001 -> -10") << 0.0000000001 << -10;
+  QTest::newRow("10.0001 -> 1") << 10.0001 << 1;
+  QTest::newRow("100000000001.0001 -> 11") << 100000000001.0001 << 11;
+  QTest::newRow("0. -> min") << 0. << std::numeric_limits<int>::min();
+  QTest::newRow("inf -> min") << std::numeric_limits<double>::infinity() << std::numeric_limits<int>::min();
+  QTest::newRow("-inf -> min") << -std::numeric_limits<double>::infinity() << std::numeric_limits<int>::min();
+  QTest::newRow("nan -> min") << std::numeric_limits<double>::quiet_NaN()  << std::numeric_limits<int>::min();
+  QTest::newRow("min -> min") << std::numeric_limits<double>::min() << std::numeric_limits<int>::min();
+  QTest::newRow("max -> 308") << std::numeric_limits<double>::max() << 308;
+  QTest::newRow("denorm -> min") << std::numeric_limits<double>::denorm_min() << std::numeric_limits<int>::min();
+}
+
+// ----------------------------------------------------------------------------
+void ctkUtilsTester::testClosestPowerOfTen()
+{
+  QFETCH(double, value);
+  QFETCH(double, expectedValue);
+  QFETCH(int, compareMode);
+  const double closestValue = ctk::closestPowerOfTen(value);
+  switch (compareMode)
+    {
+    default:
+    case 0:
+      QVERIFY(closestValue == expectedValue);
+      break;
+    case 1:
+      {
+      const double epsilon = std::numeric_limits<double>::epsilon();
+      QVERIFY( closestValue > expectedValue - epsilon );
+      QVERIFY( closestValue < expectedValue + epsilon );
+      break;
+      }
+    case 2:
+      QVERIFY( closestValue != closestValue );
+      break;
+    }
+}
+
+// ----------------------------------------------------------------------------
+void ctkUtilsTester::testClosestPowerOfTen_data()
+{
+  QTest::addColumn<double>("value");
+  QTest::addColumn<double>("expectedValue");
+  /// 0 exact compare
+  /// 1 compare with epsilon
+  /// 2 isNaN
+  QTest::addColumn<int>("compareMode");
+
+  QTest::newRow("1. -> 1.") << 1. << 1. << 0;
+  QTest::newRow("2. -> 1.") << 2. << 1. << 0;
+  QTest::newRow("10. -> 10.") << 10. << 10. << 0;
+  QTest::newRow("45. -> 10.") << 45. << 10. << 0;
+  QTest::newRow("98. -> 100.") << 98. << 100. << 0;
+  QTest::newRow("50. -> 10.") << 50. << 10. << 0;
+  QTest::newRow("-1234. -> -1000.") << -1234. << -1000. << 0;
+  QTest::newRow("0.01 -> 0.01") << 0.01 << 0.01 << 1;
+  QTest::newRow("0.00000000015 -> 0.0000000001")
+    << 0.00000000015 << 0.0000000001 << 1;
+  QTest::newRow("0.1 -> 0.1") << 0.1 << 0.1 << 1;
+  QTest::newRow("0. -> 0.") << 0. << 0. << 0;
+  QTest::newRow("inf -> inf") << std::numeric_limits<double>::infinity()
+                              << std::numeric_limits<double>::infinity()
+                              << 0;
+  QTest::newRow("-inf -> -inf") << -std::numeric_limits<double>::infinity()
+                                << -std::numeric_limits<double>::infinity()
+                                << 0;
+  QTest::newRow("nan -> nan") << std::numeric_limits<double>::quiet_NaN()
+                              << std::numeric_limits<double>::quiet_NaN()
+                              << 2;
+  QTest::newRow("min -> min") << std::numeric_limits<double>::min()
+                              << std::numeric_limits<double>::min()
+                              << 0;
+  //QTest::newRow("max -> max") << std::numeric_limits<double>::max()
+  //                            << 1e+308
+  //                            << 0;
+  QTest::newRow("denorm -> denorm") << std::numeric_limits<double>::denorm_min()
+                                    << std::numeric_limits<double>::denorm_min()
+                                    << 0;
+}
+
+// ----------------------------------------------------------------------------
+void ctkUtilsTester::testSignificantDecimals()
+{
+  QFETCH(double, value);
+  QFETCH(int, expectedDecimals);
+
+  QCOMPARE(ctk::significantDecimals(value), expectedDecimals);
+}
+
+// ----------------------------------------------------------------------------
+void ctkUtilsTester::testSignificantDecimals_data()
+{
+  QTest::addColumn<double>("value");
+  QTest::addColumn<int>("expectedDecimals");
+
+  QTest::newRow("123456 -> 0") << 123456. << 0;
+  QTest::newRow("123456.1 -> 1") << 123456.1 << 1;
+  QTest::newRow("123456.12 -> 2") << 123456.12 << 2;
+  QTest::newRow("123456.123 -> 3") << 123456.123 << 3;
+  QTest::newRow("123456.122 -> 3") << 123456.122 << 3;
+  QTest::newRow("123456.1223 -> 4") << 123456.1223 << 4;
+  QTest::newRow("123456.1234 -> 4") << 123456.1234 << 4;
+  QTest::newRow("123456.0123 -> 4") << 123456.0123 << 4;
+  QTest::newRow("123456.0012 -> 4") << 123456.0012 << 4;
+  QTest::newRow("123456.001234 -> 6") << 123456.001234 << 6;
+  QTest::newRow("123456.000123 -> 6") << 123456.000123 << 6;
+  QTest::newRow("123456.0000 -> 0") << 123456.0000 << 0;
+  QTest::newRow("123456.0001 -> 4") << 123456.0001 << 4;
+  QTest::newRow("123456.3333333 -> 2") << 123456.3333333 << 2;
+  QTest::newRow("123456.1333333 -> 3") << 123456.1333333 << 3;
+  QTest::newRow("123456.3333334 -> 2") << 123456.3333334 << 2;
+  QTest::newRow("123456.00122 -> 5") << 123456.00122 << 5;
+  QTest::newRow("123456.00123 -> 5") << 123456.00123 << 5;
+  // internally representated as 123456.001109999997425
+  QTest::newRow("123456.00111 -> 5") << 123456.00111 << 5;
+  // internally representated as 123456.270000000004075
+  QTest::newRow("123456.26999999999999996 -> 2")
+    << 123456.26999999999999996 << 2;
+  QTest::newRow("123456.863899999999987 -> 4") << 123456.863899999999987 << 4;
+  QTest::newRow("0.5 -> 1") << 0.5 << 1;
+  QTest::newRow("0.25 -> 2") << 0.25 << 2;
+  QTest::newRow("0.125 -> 3") << 0.125 << 3;
+  QTest::newRow("0.1234567891013151 -> 16") << 0.1234567891013151 << 16;
+  QTest::newRow("0. -> 0") << 0. << 0;
+  QTest::newRow("inf -> 0") << std::numeric_limits<double>::infinity() << 0;
+  QTest::newRow("-inf -> 0") << -std::numeric_limits<double>::infinity() << 0;
+  QTest::newRow("nan -> -1") << std::numeric_limits<double>::quiet_NaN() << -1;
+  QTest::newRow("min -> 16") << std::numeric_limits<double>::min() << 16;
+  QTest::newRow("max -> 0") << std::numeric_limits<double>::max() << 0;
+  QTest::newRow("denorm -> 16") << std::numeric_limits<double>::denorm_min()
+                                << 16;
+
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkUtilsTest)
+#include "moc_ctkUtilsTest.cpp"
+
+

+ 27 - 8
Libs/Core/ctkUtils.cpp

@@ -164,12 +164,22 @@ QRegExp ctk::nameFiltersToRegExp(const QStringList& nameFilters)
 //-----------------------------------------------------------------------------
 int ctk::significantDecimals(double value)
 {
+  if (value == 0.
+      || qAbs(value) == std::numeric_limits<double>::infinity())
+    {
+    return 0;
+    }
+  if (value != value) // is NaN
+    {
+    return -1;
+    }
   QString number = QString::number(value, 'f', 16);
   QString fractional = number.section('.', 1, 1);
   Q_ASSERT(fractional.length() == 16);
   QChar previous;
   int previousRepeat=0;
   bool only0s = true;
+  bool isUnit = value > -1. && value < 1.;
   for (int i = 0; i < fractional.length(); ++i)
     {
     QChar digit = fractional.at(i);
@@ -190,7 +200,7 @@ int ctk::significantDecimals(double value)
     // Last digit
     if (i == fractional.length() - 1)
       {
-      if (previousRepeat > 2)
+      if (previousRepeat > 2 && !(only0s && isUnit) )
         {
         return i - previousRepeat;
         }
@@ -215,7 +225,11 @@ int ctk::significantDecimals(double value)
 int ctk::orderOfMagnitude(double value)
 {
   value = qAbs(value);
-  if (value == 0.)
+  if (value == 0.
+      || value == std::numeric_limits<double>::infinity()
+      || value != value // is NaN
+      || value < std::numeric_limits<double>::epsilon() // is tool small to compute
+  )
     {
     return std::numeric_limits<int>::min();
     }
@@ -232,8 +246,9 @@ int ctk::orderOfMagnitude(double value)
     magnitudeFactor = 0.1;
     }
 
+  double epsilon = std::numeric_limits<double>::epsilon();
   while ( (magnitudeStep > 0 && value >= magnitude) ||
-          (magnitudeStep < 0 && value < magnitude - std::numeric_limits<double>::epsilon()))
+          (magnitudeStep < 0 && value < magnitude - epsilon))
     {
     magnitude *= magnitudeFactor;
     magnitudeOrder += magnitudeStep;
@@ -243,13 +258,17 @@ int ctk::orderOfMagnitude(double value)
 }
 
 //-----------------------------------------------------------------------------
-double ctk::closestPowerOfTen(double value)
+double ctk::closestPowerOfTen(double _value)
 {
-  double sign = value >= 0. ? 1 : -1;
-  value = qAbs(value);
-  if (value == 0.)
+  const double sign = _value >= 0. ? 1 : -1;
+  const double value = qAbs(_value);
+  if (value == 0.
+      || value == std::numeric_limits<double>::infinity()
+      || value != value // is NaN
+      || value < std::numeric_limits<double>::epsilon() // is denormalized
+  )
     {
-    return 0.;
+    return _value;
     }
 
   double magnitude = 1.;

+ 3 - 2
Libs/Core/ctkUtils.h

@@ -78,7 +78,7 @@ QRegExp CTK_CORE_EXPORT nameFiltersToRegExp(const QStringList& nameFilters);
 ///
 /// \ingroup Core
 /// Return a "smart" number of decimals needed to display (in a gui) a floating
-/// number.
+/// number. 16 is the max that can be returned, -1 for NaN numbers.
 /// e.g. significantDecimals(120.01) returns 2
 ///      significantDecimals(123456.1333333) returns 3
 ///      significantDecimals(123456.26999999999999996) returns 2
@@ -87,7 +87,8 @@ int CTK_CORE_EXPORT significantDecimals(double value);
 
 ///
 /// \ingroup Core
-/// Return the order of magnitude of a number.
+/// Return the order of magnitude of a number or numeric_limits<int>::min() if
+/// the order of magnitude can't be computed (e.g. 0, inf, Nan, denorm)...
 /// e.g.: orderOfMagnitude(1) returns 0
 ///       orderOfMagnitude(10) returns 1
 ///       orderOfMagnitude(99) returns 1

+ 33 - 0
Libs/Testing/ctkTest.h

@@ -21,6 +21,12 @@
 // Qt includes
 #include <QtTest/QtTest>
 
+// STD includes
+#include <limits>
+
+#ifndef __ctkTest_h
+#define __ctkTest_h
+
 #define CTK_TEST_NOOP_MAIN(TestObject) \
 int TestObject(int argc, char *argv[]) \
 { \
@@ -95,4 +101,31 @@ inline void mouseMove(QWidget *widget, Qt::MouseButton button, Qt::KeyboardModif
                       QPoint pos = QPoint(), int delay=-1)
   { ctkTest::mouseEvent(QTest::MouseMove, widget, button, stateKey, pos, delay); }
 
+
+// ----------------------------------------------------------------------------
+static void COMPARE(double v1, double v2)
+{
+  // QCOMPARE fails to compare NaN numbers
+  if (v2 != v2)
+    {
+    QVERIFY(v1 != v1);
+    }
+  // QCOMPARE fails to compare - infinity
+  else if (v2 == -std::numeric_limits<double>::infinity())
+    {
+    QVERIFY(v1 == -std::numeric_limits<double>::infinity());
+    }
+  // QCOMPARE fails to compare infinity
+  else if (v2 == std::numeric_limits<double>::infinity())
+    {
+    QVERIFY(v1 == std::numeric_limits<double>::infinity());
+    }
+  else
+    {
+    QCOMPARE(v1, v2);
+    }
 }
+
+}; // end ctkTest namespace
+
+#endif

+ 6 - 4
Libs/Widgets/Testing/Cpp/CMakeLists.txt

@@ -27,8 +27,8 @@ set(TEST_SOURCES
   ctkDirectoryButtonTest1.cpp
   ctkDoubleRangeSliderTest1.cpp
   ctkDoubleRangeSliderTest2.cpp
+  ctkDoubleSliderTest.cpp
   ctkDoubleSliderTest1.cpp
-  ctkDoubleSliderTest2.cpp
   ctkDoubleSpinBoxTest.cpp
   ctkDoubleSpinBoxTest1.cpp
   ctkDynamicSpacerTest1.cpp
@@ -61,8 +61,8 @@ set(TEST_SOURCES
   ctkProxyStyleTest1.cpp
   ctkRangeSliderTest.cpp
   ctkRangeSliderTest1.cpp
+  ctkRangeWidgetTest.cpp
   ctkRangeWidgetTest1.cpp
-  ctkRangeWidgetTest2.cpp
   ctkDateRangeWidgetTest1.cpp
   ctkScreenshotDialogTest1.cpp
   ctkSearchBoxTest1.cpp
@@ -181,6 +181,7 @@ set(Tests_MOC_CPP)
 QT4_WRAP_CPP(Tests_MOC_CPP ${Tests_MOC_SRCS})
 QT4_GENERATE_MOCS(
   ctkCoordinatesWidgetTest.cpp
+  ctkDoubleSliderTest.cpp
   ctkDoubleSpinBoxTest.cpp
   ctkFlatProxyModelTest.cpp
   ctkFontButtonTest.cpp
@@ -190,6 +191,7 @@ QT4_GENERATE_MOCS(
   ctkPathListWidgetTest.cpp
   ctkPathListWidgetWithButtonsTest.cpp
   ctkRangeSliderTest.cpp
+  ctkRangeWidgetTest.cpp
   ctkSettingsPanelTest.cpp
   ctkSliderWidgetTest.cpp
   )
@@ -234,8 +236,8 @@ SIMPLE_TEST( ctkDateRangeWidgetTest1 )
 SIMPLE_TEST( ctkDirectoryButtonTest1 )
 SIMPLE_TEST( ctkDoubleRangeSliderTest1 )
 SIMPLE_TEST( ctkDoubleRangeSliderTest2 )
+SIMPLE_TEST( ctkDoubleSliderTest )
 SIMPLE_TEST( ctkDoubleSliderTest1 )
-SIMPLE_TEST( ctkDoubleSliderTest2 )
 SIMPLE_TEST( ctkDoubleSpinBoxTest )
 SIMPLE_TEST( ctkDoubleSpinBoxTest1 )
 SIMPLE_TEST( ctkDynamicSpacerTest1 )
@@ -271,8 +273,8 @@ SIMPLE_TEST( ctkPopupWidgetTest1 )
 SIMPLE_TEST( ctkProxyStyleTest1 )
 SIMPLE_TEST( ctkRangeSliderTest )
 SIMPLE_TEST( ctkRangeSliderTest1 )
+SIMPLE_TEST( ctkRangeWidgetTest )
 SIMPLE_TEST( ctkRangeWidgetTest1 )
-SIMPLE_TEST( ctkRangeWidgetTest2 )
 SIMPLE_TEST( ctkResizableFrameTest1 )
 SIMPLE_TEST( ctkScreenshotDialogTest1 )
 SIMPLE_TEST( ctkSearchBoxTest1 )

+ 120 - 0
Libs/Widgets/Testing/Cpp/ctkDoubleSliderTest.cpp

@@ -0,0 +1,120 @@
+/*=========================================================================
+
+  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 "ctkTest.h"
+
+//-----------------------------------------------------------------------------
+class ctkDoubleSliderTester: public QObject
+{
+  Q_OBJECT
+private slots:
+  void testUI();
+
+  void testRange();
+  void testRange_data();
+
+  void testSingleStep();
+  void testSingleStep_data();
+};
+
+// ----------------------------------------------------------------------------
+void ctkDoubleSliderTester::testUI()
+{
+  ctkDoubleSlider slider;
+  slider.show();
+  QTest::qWaitForWindowShown(&slider);
+  // qApp->exec();
+}
+
+// ----------------------------------------------------------------------------
+void ctkDoubleSliderTester::testRange()
+{
+  ctkDoubleSlider slider;
+
+  QFETCH(double, minimum);
+  QFETCH(double, maximum);
+  slider.setRange(minimum, maximum);
+
+  QFETCH(double, expectedMinimum);
+  QFETCH(double, expectedMaximum);
+  QFETCH(double, expectedValue);
+
+  QCOMPARE(slider.minimum(), expectedMinimum);
+  QCOMPARE(slider.maximum(), expectedMaximum);
+  QCOMPARE(slider.value(), expectedValue);
+}
+
+// ----------------------------------------------------------------------------
+void ctkDoubleSliderTester::testRange_data()
+{
+  QTest::addColumn<double>("minimum");
+  QTest::addColumn<double>("maximum");
+  QTest::addColumn<double>("expectedMinimum");
+  QTest::addColumn<double>("expectedMaximum");
+  QTest::addColumn<double>("expectedValue");
+
+  QTest::newRow("[20.123,20.1234]") << 20.123 << 20.1234 << 20.123 << 20.1234 << 20.123;
+}
+
+// ----------------------------------------------------------------------------
+void ctkDoubleSliderTester::testSingleStep()
+{
+  ctkDoubleSlider slider;
+  slider.setValue(50.);
+
+  QFETCH(double, minimum);
+  QFETCH(double, maximum);
+  slider.setRange(minimum, maximum);
+
+  QFETCH(double, singleStep);
+  slider.setSingleStep(singleStep);
+
+  QFETCH(double, expectedValue);
+  QCOMPARE(slider.value(), expectedValue);
+}
+
+// ----------------------------------------------------------------------------
+void ctkDoubleSliderTester::testSingleStep_data()
+{
+  QTest::addColumn<double>("minimum");
+  QTest::addColumn<double>("maximum");
+  QTest::addColumn<double>("singleStep");
+  QTest::addColumn<double>("expectedValue");
+
+  QTest::newRow("1.") << 0. << 100. << 1. << 50.;
+  QTest::newRow("100.") << 0. << 100. << 100. << 50.;
+  QTest::newRow("0.1") << 0. << 100. << 0.1 << 50.;
+  QTest::newRow("min") << 0. << 100. << std::numeric_limits<double>::min() << 50.;
+  QTest::newRow("max") << 0. << 100. << std::numeric_limits<double>::max() << 50.;
+  QTest::newRow("-max") << 0. << 100. << -std::numeric_limits<double>::max() << 50.;
+  QTest::newRow("-inf") << 0. << 100. << -std::numeric_limits<double>::infinity() << 50.;
+  QTest::newRow("inf") << 0. << 100. << std::numeric_limits<double>::infinity() << 50.;
+  QTest::newRow("NaN") << 0. << 100. << std::numeric_limits<double>::quiet_NaN() << 50.;
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkDoubleSliderTest)
+#include "moc_ctkDoubleSliderTest.cpp"

+ 0 - 60
Libs/Widgets/Testing/Cpp/ctkDoubleSliderTest2.cpp

@@ -1,60 +0,0 @@
-/*=========================================================================
-
-  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 <QString>
-#include <QTimer>
-
-// CTK includes
-#include "ctkDoubleSlider.h"
-
-// STD includes
-#include <cstdlib>
-#include <iostream>
-
-//-----------------------------------------------------------------------------
-int ctkDoubleSliderTest2(int argc, char * argv [] )
-{
-  QApplication app(argc, argv);
-
-  ctkDoubleSlider slider;
-  slider.setRange(-1., 1.);
-  slider.setValue(-0.5);
-  slider.setRange(0.,1.);
-  if (slider.value() != 0.)
-    {
-    std::cout << "ctkDoubleSlider::setRange() failed: " << slider.value() << std::endl;
-    return EXIT_FAILURE;
-    }
-  slider.setRange(2.0001, 2.0001);
-  if (slider.value() != 2.0001)
-    {
-    std::cout << "ctkDoubleSlider::setRange() failed: " << slider.value() << std::endl;
-    return EXIT_FAILURE;
-    }
-  slider.show();
-  if (argc < 2 || QString(argv[1]) != "-I" )
-    {
-    QTimer::singleShot(100, &app, SLOT(quit()));
-    }
-  return app.exec();
-}
-

+ 127 - 0
Libs/Widgets/Testing/Cpp/ctkDoubleSpinBoxTest.cpp

@@ -31,6 +31,9 @@
 #include "ctkDoubleSpinBox.h"
 #include "ctkTest.h"
 
+// STD includes
+#include <limits>
+
 // ----------------------------------------------------------------------------
 class ctkDoubleSpinBoxTester: public QObject
 {
@@ -40,6 +43,12 @@ private slots:
 
   void testToLocals();
 
+  void testSetValue();
+  void testSetValue_data();
+
+  void testSetRange();
+  void testSetRange_data();
+
   void testDecimalsByKey();
   void testDecimalsByKey_data();
 
@@ -88,6 +97,124 @@ void ctkDoubleSpinBoxTester::testToLocals()
   qDebug() << "0.0 1" << ok;
 }
 
+//-----------------------------------------------------------------------------
+void ctkDoubleSpinBoxTester::testSetValue()
+{
+  ctkDoubleSpinBox spinBox;
+  spinBox.setValue(25.);
+  // Compare with a QDoubleSpinBox as we are supposed to have the same behavior.
+  QDoubleSpinBox compareSpinBox;
+  compareSpinBox.setValue(25.);
+
+  QFETCH(double, value);
+
+  QSignalSpy valueChangedSpy(&spinBox,
+                             SIGNAL(valueChanged(double)));
+  spinBox.setValue(value);
+  QSignalSpy compareValueChangedSpy(&compareSpinBox,
+                                    SIGNAL(valueChanged(double)));
+  compareSpinBox.setValue(value);
+
+  QCOMPARE(spinBox.value(), compareSpinBox.value());
+  QCOMPARE(valueChangedSpy.count(), compareValueChangedSpy.count());
+
+  QFETCH(double, expectedValue);
+  QCOMPARE(spinBox.value(), expectedValue);
+
+  const bool valueChanged = expectedValue != 25.;
+  QCOMPARE(valueChangedSpy.count(), valueChanged ? 1 : 0);
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleSpinBoxTester::testSetValue_data()
+{
+  QTest::addColumn<double>("value");
+  QTest::addColumn<double>("expectedValue");
+
+  QTest::newRow("1. -> 1.]") << 1. << 1.;
+  QTest::newRow("100. -> 99.99]") << 100. << 99.99;
+  QTest::newRow("-1. -> 0.]") << -1. << 0.;
+
+  QTest::newRow("min -> 0.") << std::numeric_limits<double>::min() << 0.;
+  QTest::newRow("max -> 99.99") << std::numeric_limits<double>::max() << 99.99;
+  QTest::newRow("-inf -> 0.") << -std::numeric_limits<double>::infinity() << 0.;
+  QTest::newRow("inf -> 99.99") << std::numeric_limits<double>::infinity() << 99.99;
+  QTest::newRow("NaN -> 99.99") << std::numeric_limits<double>::quiet_NaN() << 99.99;
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleSpinBoxTester::testSetRange()
+{
+  ctkDoubleSpinBox spinBox;
+  spinBox.setValue(25.);
+  QDoubleSpinBox compareSpinBox;
+  compareSpinBox.setValue(25.);
+
+  QSignalSpy valueChangedSpy(&spinBox,
+                             SIGNAL(valueChanged(double)));
+  QSignalSpy compareValueChangedSpy(&compareSpinBox,
+                             SIGNAL(valueChanged(double)));
+
+  QFETCH(double, minimum);
+  QFETCH(double, maximum);
+  spinBox.setRange(minimum, maximum);
+  compareSpinBox.setRange(minimum, maximum);
+
+  ctkTest::COMPARE(spinBox.minimum(), compareSpinBox.minimum());
+  ctkTest::COMPARE(spinBox.maximum(), compareSpinBox.maximum());
+  ctkTest::COMPARE(spinBox.value(), compareSpinBox.value());
+
+  QFETCH(double, expectedMinimum);
+  QFETCH(double, expectedMaximum);
+  ctkTest::COMPARE(spinBox.minimum(), expectedMinimum);
+  ctkTest::COMPARE(spinBox.maximum(), expectedMaximum);
+
+  QFETCH(double, expectedValue);
+  ctkTest::COMPARE(spinBox.value(), expectedValue);
+
+  const bool valueChanged = expectedValue != 25.;
+  ctkTest::COMPARE(valueChangedSpy.count(), valueChanged ? 1 : 0);
+}
+
+//-----------------------------------------------------------------------------
+void ctkDoubleSpinBoxTester::testSetRange_data()
+{
+  QTest::addColumn<double>("minimum");
+  QTest::addColumn<double>("maximum");
+  QTest::addColumn<double>("expectedMinimum");
+  QTest::addColumn<double>("expectedMaximum");
+  QTest::addColumn<double>("expectedValue");
+
+  QTest::newRow("[1.,98.]") << 1. << 98. << 1. << 98. << 25.;
+  QTest::newRow("[-1.,101.]") << -1. << 101. << -1. << 101. << 25.;
+  QTest::newRow("[1.,10.]") << 1. << 10. << 1. << 10. << 10.;
+  QTest::newRow("[90.,99.]") << 90. << 99. << 90. << 99. << 90.;
+  QTest::newRow("[min,max]")
+    << std::numeric_limits<double>::min()
+    << std::numeric_limits<double>::max()
+    << std::numeric_limits<double>::min()
+    << std::numeric_limits<double>::max()
+    << 25.;
+  QTest::newRow("[-max,max]")
+    << -std::numeric_limits<double>::max()
+    << std::numeric_limits<double>::max()
+    << -std::numeric_limits<double>::max()
+    << std::numeric_limits<double>::max()
+    << 25.;
+  QTest::newRow("[-inf,inf]")
+    << -std::numeric_limits<double>::infinity()
+    << std::numeric_limits<double>::infinity()
+    << -std::numeric_limits<double>::infinity()
+    << std::numeric_limits<double>::infinity()
+    << 25.;
+  QTest::newRow("[NaN,NaN]")
+    << std::numeric_limits<double>::quiet_NaN()
+    << std::numeric_limits<double>::quiet_NaN()
+    << std::numeric_limits<double>::quiet_NaN()
+    << std::numeric_limits<double>::quiet_NaN()
+    << std::numeric_limits<double>::quiet_NaN();
+}
+
 // ----------------------------------------------------------------------------
 void ctkDoubleSpinBoxTester::testDecimalsByKey()
 {

+ 307 - 0
Libs/Widgets/Testing/Cpp/ctkRangeWidgetTest.cpp

@@ -0,0 +1,307 @@
+/*=========================================================================
+
+  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 "ctkRangeWidget.h"
+#include "ctkTest.h"
+
+// STD includes
+#include <limits>
+
+// ----------------------------------------------------------------------------
+class ctkRangeWidgetTester: public QObject
+{
+  Q_OBJECT
+private slots:
+  void testUI();
+
+  void testSetMinimumValue();
+  void testSetMinimumValue_data();
+
+  void testSetMaximumValue();
+  void testSetMaximumValue_data();
+
+  void testSetValues();
+  void testSetValues_data();
+
+  void testSetRange();
+  void testSetRange_data();
+
+  void testSetRangeSetValues();
+  void testSetRangeSetValues_data();
+
+};
+
+// ----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testUI()
+{
+  ctkRangeWidget rangeWidget;
+  rangeWidget.show();
+  QTest::qWaitForWindowShown(&rangeWidget);
+  //qApp->exec();
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testSetMinimumValue()
+{
+  ctkRangeWidget rangeWidget;
+  rangeWidget.setValues(25., 75.);
+
+  QSignalSpy lowerValueChangedSpy(&rangeWidget,
+                                  SIGNAL(minimumValueChanged(double)));
+  QSignalSpy valuesChangedSpy(&rangeWidget,
+                              SIGNAL(valuesChanged(double,double)));
+  QFETCH(double, lowerValue);
+  rangeWidget.setMinimumValue(lowerValue);
+
+  QFETCH(double, expectedLowerValue);
+  QCOMPARE(rangeWidget.minimumValue(), expectedLowerValue);
+
+  const bool lowerValueChanged = expectedLowerValue != 25.;
+  QCOMPARE(lowerValueChangedSpy.count(), lowerValueChanged ? 1 : 0);
+  QCOMPARE(valuesChangedSpy.count(), lowerValueChanged ? 1 : 0);
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testSetMinimumValue_data()
+{
+  QTest::addColumn<double>("lowerValue");
+  QTest::addColumn<double>("expectedLowerValue");
+
+  QTest::newRow("1. -> 1.]") << 1. << 1.;
+  QTest::newRow("100. -> 75.]") << 100. << 75.;
+  QTest::newRow("-1. -> 0.]") << -1. << 0.;
+
+  QTest::newRow("min -> 0.") << std::numeric_limits<double>::min() << 0.;
+  QTest::newRow("max -> 75.") << std::numeric_limits<double>::max() << 75.;
+  QTest::newRow("-inf -> 0.") << -std::numeric_limits<double>::infinity() << 0.;
+  QTest::newRow("inf -> 75.") << std::numeric_limits<double>::infinity() << 75.;
+  QTest::newRow("NaN -> 75.") << std::numeric_limits<double>::quiet_NaN() << 75.;
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testSetMaximumValue()
+{
+  ctkRangeWidget rangeWidget;
+  rangeWidget.setValues(25., 75.);
+
+  QSignalSpy upperValueChangedSpy(&rangeWidget,
+                                  SIGNAL(maximumValueChanged(double)));
+  QSignalSpy valuesChangedSpy(&rangeWidget,
+                              SIGNAL(valuesChanged(double,double)));
+
+  QFETCH(double, upperValue);
+  rangeWidget.setMaximumValue(upperValue);
+
+  QFETCH(double, expectedUpperValue);
+  QCOMPARE(rangeWidget.maximumValue(), expectedUpperValue);
+
+  const bool upperValueChanged = expectedUpperValue != 75.;
+  QCOMPARE(upperValueChangedSpy.count(), upperValueChanged ? 1 : 0);
+  QCOMPARE(valuesChangedSpy.count(), upperValueChanged ? 1 : 0);
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testSetMaximumValue_data()
+{
+  QTest::addColumn<double>("upperValue");
+  QTest::addColumn<double>("expectedUpperValue");
+
+  QTest::newRow("99. -> 99.]") << 99. << 99.;
+  QTest::newRow("0. -> 25.]") << 0. << 25.;
+  QTest::newRow("100. -> 99.]") << 100. << 99.;
+
+  QTest::newRow("min -> 25.") << std::numeric_limits<double>::min() << 25.;
+  QTest::newRow("max -> 99.") << std::numeric_limits<double>::max() << 99.;
+  QTest::newRow("-inf -> 25.") << -std::numeric_limits<double>::infinity() << 25.;
+  QTest::newRow("inf -> 99.") << std::numeric_limits<double>::infinity() << 99.;
+  QTest::newRow("NaN -> 99.") << std::numeric_limits<double>::quiet_NaN() << 99.;
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testSetValues()
+{
+  ctkRangeWidget rangeWidget;
+  rangeWidget.setValues(25., 75.);
+
+  QSignalSpy lowerValueChangedSpy(&rangeWidget,
+                                  SIGNAL(minimumValueChanged(double)));
+  QSignalSpy upperValueChangedSpy(&rangeWidget,
+                                  SIGNAL(maximumValueChanged(double)));
+  QSignalSpy valuesChangedSpy(&rangeWidget,
+                              SIGNAL(valuesChanged(double,double)));
+
+  QFETCH(double, lowerValue);
+  QFETCH(double, upperValue);
+  rangeWidget.setValues(lowerValue, upperValue);
+
+  QFETCH(double, expectedLowerValue);
+  QFETCH(double, expectedUpperValue);
+  QCOMPARE(rangeWidget.minimumValue(), expectedLowerValue);
+  QCOMPARE(rangeWidget.maximumValue(), expectedUpperValue);
+
+  const bool lowerValueChanged = expectedLowerValue != 25.;
+  const bool upperValueChanged = expectedUpperValue != 75.;
+  QCOMPARE(lowerValueChangedSpy.count(), lowerValueChanged ? 1 : 0);
+  QCOMPARE(upperValueChangedSpy.count(), upperValueChanged ? 1 : 0);
+  // \todo fix the valuesChanged signal count.
+  //QCOMPARE(valuesChangedSpy.count(),
+  //         (lowerValueChanged || upperValueChanged) ? 1 : 0);
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testSetValues_data()
+{
+  QTest::addColumn<double>("lowerValue");
+  QTest::addColumn<double>("upperValue");
+  QTest::addColumn<double>("expectedLowerValue");
+  QTest::addColumn<double>("expectedUpperValue");
+
+  QTest::newRow("[1.,10.] -> [1., 10.]") << 1. << 10. << 1. << 10.;
+  QTest::newRow("[98.,99.] -> [98., 99.]") << 98. << 99. << 98. << 99.;
+  QTest::newRow("[1.,1.] -> [1., 1.]") << 1. << 1. << 1. << 1.;
+  QTest::newRow("[10.,1.] -> [1., 10.]") << 10. << 1. << 1. << 10.;
+  QTest::newRow("[-1.,100.] -> [0., 99.]") << -1. << 100. << 0. << 99.;
+
+  QTest::newRow("[min,max] -> [0., 99.]")
+    << std::numeric_limits<double>::min()
+    << std::numeric_limits<double>::max() << 0. << 99.;
+  QTest::newRow("[-inf,inf] -> [0., 99.]")
+    << -std::numeric_limits<double>::infinity()
+    << std::numeric_limits<double>::infinity() << 0. << 99.;
+  QTest::newRow("[NaN,NaN] -> [99., 99.]")
+    << std::numeric_limits<double>::quiet_NaN()
+    << std::numeric_limits<double>::quiet_NaN() << 99. << 99.;
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testSetRange()
+{
+  ctkRangeWidget rangeWidget;
+  rangeWidget.setValues(25., 75.);
+
+  QSignalSpy lowerValueChangedSpy(&rangeWidget,
+                                  SIGNAL(minimumValueChanged(double)));
+  QSignalSpy upperValueChangedSpy(&rangeWidget,
+                                  SIGNAL(maximumValueChanged(double)));
+  QSignalSpy rangeChangedSpy(&rangeWidget,
+                              SIGNAL(rangeChanged(double,double)));
+
+  QFETCH(double, minimum);
+  QFETCH(double, maximum);
+  rangeWidget.setRange(minimum, maximum);
+
+  QFETCH(double, expectedMinimum);
+  QFETCH(double, expectedMaximum);
+  ctkTest::COMPARE(rangeWidget.minimum(), expectedMinimum);
+  ctkTest::COMPARE(rangeWidget.maximum(), expectedMaximum);
+
+  const bool minimumChanged = expectedMinimum != 0.;
+  const bool maximumChanged = expectedMaximum != 99.;
+  QCOMPARE(rangeChangedSpy.count(),(minimumChanged || maximumChanged) ? 1 : 0);
+
+  QFETCH(double, expectedLowerValue);
+  QFETCH(double, expectedUpperValue);
+  ctkTest::COMPARE(rangeWidget.minimumValue(), expectedLowerValue);
+  ctkTest::COMPARE(rangeWidget.maximumValue(), expectedUpperValue);
+
+  const bool lowerValueChanged = expectedLowerValue != 25.;
+  const bool upperValueChanged = expectedUpperValue != 75.;
+  QCOMPARE(lowerValueChangedSpy.count(), lowerValueChanged ? 1 : 0);
+  QCOMPARE(upperValueChangedSpy.count(), upperValueChanged ? 1 : 0);
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testSetRange_data()
+{
+  QTest::addColumn<double>("minimum");
+  QTest::addColumn<double>("maximum");
+  QTest::addColumn<double>("expectedMinimum");
+  QTest::addColumn<double>("expectedMaximum");
+  QTest::addColumn<double>("expectedLowerValue");
+  QTest::addColumn<double>("expectedUpperValue");
+
+  QTest::newRow("[1.,98.]") << 1. << 98. << 1. << 98. << 25. << 75.;
+  QTest::newRow("[-1.,101.]") << -1. << 101. << -1. << 101. << 25. << 75.;
+  QTest::newRow("[1.,50.]") << 1. << 50. << 1. << 50. << 25. << 50.;
+  QTest::newRow("[50.,99.]") << 50. << 99. << 50. << 99. << 50. << 75. ;
+  QTest::newRow("[1.,10.]") << 1. << 10. << 1. << 10. << 10. << 10.;
+  QTest::newRow("[90.,99.]") << 90. << 99. << 90. << 99. << 90. << 90.;
+  QTest::newRow("[min,max]")
+    << std::numeric_limits<double>::min()
+    << std::numeric_limits<double>::max()
+    << 0.
+    << std::numeric_limits<double>::max()
+    << 25. << 75.;
+  QTest::newRow("[-inf,inf]")
+    << -std::numeric_limits<double>::infinity()
+    << std::numeric_limits<double>::infinity()
+    << -std::numeric_limits<double>::infinity()
+    << std::numeric_limits<double>::infinity()
+    << 25. << 75.;
+  QTest::newRow("[NaN,NaN]")
+    << std::numeric_limits<double>::quiet_NaN()
+    << std::numeric_limits<double>::quiet_NaN()
+    << std::numeric_limits<double>::quiet_NaN()
+    << std::numeric_limits<double>::quiet_NaN()
+    << std::numeric_limits<double>::quiet_NaN()
+    << std::numeric_limits<double>::quiet_NaN();
+}
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testSetRangeSetValues()
+{
+  ctkRangeWidget rangeWidget;
+  QFETCH(double, minimum);
+  QFETCH(double, maximum);
+  rangeWidget.setRange(minimum, maximum);
+
+  rangeWidget.setDecimals(0);
+  rangeWidget.setSingleStep(std::numeric_limits<double>::infinity());
+  QFETCH(double, lowerValue);
+  QFETCH(double, upperValue);
+  rangeWidget.setValues(lowerValue, upperValue);
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkRangeWidgetTester::testSetRangeSetValues_data()
+{
+  QTest::addColumn<double>("minimum");
+  QTest::addColumn<double>("maximum");
+  QTest::addColumn<double>("lowerValue");
+  QTest::addColumn<double>("upperValue");
+
+  QTest::newRow("[-inf,inf,-inf,inf]")
+    << -std::numeric_limits<double>::infinity()
+    << std::numeric_limits<double>::infinity()
+    << -std::numeric_limits<double>::infinity()
+    << std::numeric_limits<double>::infinity();
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkRangeWidgetTest)
+#include "moc_ctkRangeWidgetTest.cpp"

+ 0 - 62
Libs/Widgets/Testing/Cpp/ctkRangeWidgetTest2.cpp

@@ -1,62 +0,0 @@
-/*=========================================================================
-
-  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 <QTimer>
-
-// CTK includes
-#include "ctkRangeWidget.h"
-
-// STD includes
-#include <cstdlib>
-#include <iostream>
-
-//-----------------------------------------------------------------------------
-int ctkRangeWidgetTest2(int argc, char * argv [] )
-{
-  QApplication app(argc, argv);
-
-  ctkRangeWidget sliderSpinBox;
-  sliderSpinBox.setDecimals(2);
-  sliderSpinBox.setRange(0, 99);
-  
-  sliderSpinBox.setValues(1., 10.);
-  sliderSpinBox.setRange(-10., -0.10);
-
-  sliderSpinBox.setMaximum(-11.);
-  
-  sliderSpinBox.setMinimum(101.);
-  
-  sliderSpinBox.setValues(0., 1000.);
-  
-  sliderSpinBox.setRange(-2002, 2002);
-
-  sliderSpinBox.show();
-
-  if (argc < 2 || QString(argv[1]) != "-I" )
-    {
-    QTimer::singleShot(200, &app, SLOT(quit()));
-    }
-
-  return app.exec();
-
-}
-

+ 1 - 1
Libs/Widgets/ctkDoubleSlider.h

@@ -43,7 +43,7 @@ class ctkDoubleSliderPrivate;
 class CTK_WIDGETS_EXPORT ctkDoubleSlider : public QWidget
 {
   Q_OBJECT
-  Q_PROPERTY(double value READ value WRITE setValue)
+  Q_PROPERTY(double value READ value WRITE setValue NOTIFY valueChanged USER true)
   Q_PROPERTY(double sliderPosition READ sliderPosition WRITE setSliderPosition)
   Q_PROPERTY(double singleStep READ singleStep WRITE setSingleStep)
   Q_PROPERTY(double pageStep READ pageStep WRITE setPageStep)

+ 66 - 8
Libs/Widgets/ctkRangeWidget.cpp

@@ -28,6 +28,7 @@
 
 // STD includes
 #include <cmath>
+#include <limits>
 
 //-----------------------------------------------------------------------------
 class ctkRangeWidgetPrivate: public Ui_ctkRangeWidget
@@ -57,6 +58,15 @@ public:
 // --------------------------------------------------------------------------
 bool ctkRangeWidgetPrivate::equal(double v1, double v2)const
 {
+  if (v1 == v2)
+    {// don't bother computing difference as it could fail for infinity numbers
+    return true;
+    }
+  if (v1 != v1 && v2 != v2)
+    {// 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());
 }
 
@@ -291,6 +301,7 @@ 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();
@@ -307,12 +318,15 @@ void ctkRangeWidget::setRange(double min, double max)
   d->SettingSliderRange = false;
   Q_ASSERT(d->equal(d->MinimumSpinBox->minimum(), d->Slider->minimum()));
   Q_ASSERT(d->equal(d->MaximumSpinBox->maximum(), d->Slider->maximum()));
+  Q_ASSERT(d->equal(d->Slider->minimumValue(), d->MinimumSpinBox->value()));
+  Q_ASSERT(d->equal(d->Slider->maximumValue(), d->MaximumSpinBox->value()));
   d->updateSpinBoxWidth();
   if (oldMin != d->MinimumSpinBox->minimum() ||
       oldMax != d->MaximumSpinBox->maximum())
     {
     emit rangeChanged(d->MinimumSpinBox->minimum(), d->MaximumSpinBox->maximum());
     }
+  qDebug() << "end setRange";
 }
 
 // --------------------------------------------------------------------------
@@ -376,6 +390,8 @@ double ctkRangeWidget::maximumValue()const
 void ctkRangeWidget::setMinimumValue(double _value)
 {
   Q_D(ctkRangeWidget);
+  qDebug() << "setMinimumValue" << _value;
+
   // disable the tracking temporally to emit the
   // signal valueChanged if changeValue() is called
   bool isChanging = d->Changing;
@@ -385,12 +401,14 @@ 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;
   // disable the tracking temporally to emit the
   // signal valueChanged if changeValue() is called
   bool isChanging = d->Changing;
@@ -400,31 +418,52 @@ 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 (newMinimumValue > newMaximumValue)
+    {
+    qSwap(newMinimumValue, newMaximumValue);
+    }
+  const bool minimumFirst = (newMinimumValue <= this->maximumValue());
+
   // disable the tracking temporally to emit the
   // signal valueChanged if changeValue() is called
   bool isChanging = d->Changing;
   d->Changing = false;
-  // the pb here is that setting the spinbox separately will fired 2 signals and
-  // between the state will be inconsistent
-  d->MinimumSpinBox->setValue(newMinimumValue);
-  d->MaximumSpinBox->setValue(newMaximumValue);
+  // \todo: setting the spinbox separately is currently firing 2 signals and
+  // between the signals, the state of the widget is inconsistent.
+  if (minimumFirst)
+    {
+    d->MinimumSpinBox->setValue(newMinimumValue);
+    d->MaximumSpinBox->setValue(newMaximumValue);
+    }
+  else
+    {
+    d->MaximumSpinBox->setValue(newMaximumValue);
+    d->MinimumSpinBox->setValue(newMinimumValue);
+    }
 
   Q_ASSERT(d->equal(d->Slider->minimumValue(), d->MinimumSpinBox->value()));
   Q_ASSERT(d->equal(d->Slider->maximumValue(), d->MaximumSpinBox->value()));
   // restore the prop
   d->Changing = isChanging;
+  qDebug() << "end setValues";
 }
 
 // --------------------------------------------------------------------------
 void ctkRangeWidget::setMinimumToMaximumSpinBox(double minimum)
 {
   Q_D(ctkRangeWidget);
+  if (minimum != minimum) // NaN check
+    {
+    return;
+    }
   d->MaximumSpinBox->setMinimum(minimum);
 }
 
@@ -432,6 +471,10 @@ void ctkRangeWidget::setMinimumToMaximumSpinBox(double minimum)
 void ctkRangeWidget::setMaximumToMinimumSpinBox(double maximum)
 {
   Q_D(ctkRangeWidget);
+  if (maximum != maximum) // NaN check
+    {
+    return;
+    }
   d->MinimumSpinBox->setMaximum(maximum);
 }
 
@@ -544,8 +587,8 @@ bool ctkRangeWidget::eventFilter(QObject *obj, QEvent *event)
 double ctkRangeWidget::singleStep()const
 {
   Q_D(const ctkRangeWidget);
-  Q_ASSERT(d->equal(d->Slider->singleStep(), d->MinimumSpinBox->singleStep()) &&
-           d->equal(d->Slider->singleStep(), d->MaximumSpinBox->singleStep()));
+  Q_ASSERT(d->equal(d->Slider->singleStep(), d->MinimumSpinBox->singleStep()));
+  Q_ASSERT(d->equal(d->Slider->singleStep(), d->MaximumSpinBox->singleStep()));
   return d->Slider->singleStep();
 }
 
@@ -553,11 +596,26 @@ double ctkRangeWidget::singleStep()const
 void ctkRangeWidget::setSingleStep(double step)
 {
   Q_D(ctkRangeWidget);
+  const double minSingleStep( qMax(this->maximum() / std::numeric_limits<int>::max(),
+                                   std::numeric_limits<double>::epsilon()) );
+  const double maxSingleStep( qMin(this->maximum() - this->minimum(),
+                                   static_cast<double>(std::numeric_limits<int>::max())) );
+  if (step < minSingleStep || step > maxSingleStep)
+    {
+    qWarning() << "ctkRangeWidget single step is out of bounds";
+    return;
+    }
+  qDebug() << "setSingleStep" << step;
+  qDebug() << "min slider: " << d->Slider->minimumValue() << "max slider" << d->Slider->maximumValue();
+  qDebug() << "min spinbox: " << d->MinimumSpinBox->value() << "max spinBox" << d->MaximumSpinBox->value();
   d->MinimumSpinBox->setSingleStep(step);
   d->MaximumSpinBox->setSingleStep(step);
+  qDebug() << "min single step: " << d->MinimumSpinBox->singleStep();
   d->Slider->setSingleStep(d->MinimumSpinBox->singleStep());
-  Q_ASSERT(d->equal(d->Slider->singleStep(), d->MinimumSpinBox->singleStep()) &&
-           d->equal(d->Slider->singleStep(), d->MaximumSpinBox->singleStep()));
+  Q_ASSERT(d->equal(d->Slider->singleStep(), d->MinimumSpinBox->singleStep()));
+  Q_ASSERT(d->equal(d->Slider->singleStep(), d->MaximumSpinBox->singleStep()));
+  Q_ASSERT(d->equal(d->Slider->minimumValue(), d->MinimumSpinBox->value()));
+  Q_ASSERT(d->equal(d->Slider->maximumValue(), d->MaximumSpinBox->value()));
 }
 
 // --------------------------------------------------------------------------