Browse Source

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 12 years ago
parent
commit
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()));
 }
 
 // --------------------------------------------------------------------------