Browse Source

Add ctkComplementMapper to "invert" boolean properties/signals

It can be handy when registering boolean properties in
ctkSettingsPanel.
Julien Finet 13 years ago
parent
commit
8e2b3d2480

+ 3 - 0
Libs/Core/CMakeLists.txt

@@ -36,6 +36,8 @@ set(KIT_SRCS
   ctkCheckableModelHelper.h
   ctkCommandLineParser.cpp
   ctkCommandLineParser.h
+  ctkComplementMapper.cpp
+  ctkComplementMapper.h
   ctkDependencyGraph.cpp
   ctkDependencyGraph.h
   ctkErrorLogModel.cpp
@@ -84,6 +86,7 @@ set(KIT_MOC_SRCS
   ctkCallback.h
   ctkCheckableModelHelper.h
   ctkCommandLineParser.h
+  ctkComplementMapper.h
   ctkErrorLogFDMessageHandler_p.h
   ctkErrorLogModel.h
   ctkLogger.h

+ 10 - 0
Libs/Core/Testing/Cpp/CMakeLists.txt

@@ -25,6 +25,7 @@ set(KITTests_SRCS
   ctkCallbackTest1.cpp
   ctkCheckableModelHelperTest1.cpp
   ctkCommandLineParserTest1.cpp
+  ctkComplementMapperTest.cpp
   ctkErrorLogModelTest1.cpp
   ctkErrorLogModelEntryGroupingTest1.cpp
   ctkErrorLogModelTerminalOutputTest1.cpp
@@ -63,6 +64,11 @@ if(HAVE_BFD)
     )
 endif()
 
+include_directories(
+  ${CMAKE_SOURCE_DIR}/Libs/Testing
+  ${CMAKE_CURRENT_BINARY_DIR}
+  )
+
 create_test_sourcelist(Tests ${KIT}CppTests.cpp
   ${KITTests_SRCS}
   #EXTRA_INCLUDE TestingMacros.h
@@ -92,6 +98,9 @@ set(Tests_Helpers_MOC_SRCS
 
 set(Tests_Helpers_MOC_CPP)
 QT4_WRAP_CPP(Tests_Helpers_MOC_CPP ${Tests_Helpers_MOC_SRCS})
+QT4_GENERATE_MOCS(
+  ctkComplementMapperTest.cpp
+  )
 
 if(HAVE_BFD)
   add_executable(ctkBinaryFileDescriptorTestHelper ctkBinaryFileDescriptorTestHelper.cpp)
@@ -122,6 +131,7 @@ endif()
 SIMPLE_TEST( ctkCallbackTest1 )
 SIMPLE_TEST( ctkCheckableModelHelperTest1 )
 SIMPLE_TEST( ctkCommandLineParserTest1 )
+SIMPLE_TEST( ctkComplementMapperTest )
 SIMPLE_TEST( ctkDependencyGraphTest1 )
 SIMPLE_TEST( ctkDependencyGraphTest2 )
 SIMPLE_TEST( ctkErrorLogModelTest1 )

+ 142 - 0
Libs/Core/Testing/Cpp/ctkComplementMapperTest.cpp

@@ -0,0 +1,142 @@
+/*=========================================================================
+
+  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 <QSignalSpy>
+
+// CTK includes
+#include "ctkComplementMapper.h"
+#include "ctkTest.h"
+
+// ----------------------------------------------------------------------------
+// We can't use a QCheckBox because we are in QtCore, simulate one.
+class ctkObjectWithBoolProp: public QObject
+{
+  Q_OBJECT
+  Q_PROPERTY(bool checked READ checked WRITE setChecked NOTIFY toggled);
+public:
+  ctkObjectWithBoolProp(){this->Checked = false;}
+  bool checked()const {return this->Checked;}
+
+public Q_SLOTS:
+  void setChecked(bool check){
+    if (check == this->Checked)
+      return;
+    this->Checked = check;
+    emit toggled(this->Checked);
+  }
+Q_SIGNALS:
+  void toggled(bool);
+private:
+  bool Checked;
+};
+
+// ----------------------------------------------------------------------------
+class ctkComplementMapperTester: public QObject
+{
+  Q_OBJECT
+private Q_SLOTS:
+  void testValue();
+  void testValueComplement();
+  void testSignals();
+};
+
+// ----------------------------------------------------------------------------
+void ctkComplementMapperTester::testValue()
+{
+  ctkObjectWithBoolProp object;
+  ctkComplementMapper* complementMapper =
+    new ctkComplementMapper(&object, "checked", SIGNAL(toggled(bool)));
+
+  QCOMPARE(object.checked(), false);
+  QCOMPARE(complementMapper->value(), false);
+
+  object.setChecked(true);
+  QCOMPARE(object.checked(), true);
+  QCOMPARE(complementMapper->value(), true);
+
+  object.setChecked(false);
+  QCOMPARE(object.checked(), false);
+  QCOMPARE(complementMapper->value(), false);
+
+  complementMapper->setValue(true);
+  QCOMPARE(object.checked(), true);
+  QCOMPARE(complementMapper->value(), true);
+
+  complementMapper->setValue(false);
+  QCOMPARE(object.checked(), false);
+  QCOMPARE(complementMapper->value(), false);
+}
+
+// ----------------------------------------------------------------------------
+void ctkComplementMapperTester::testValueComplement()
+{
+  ctkObjectWithBoolProp object;
+  ctkComplementMapper* complementMapper =
+    new ctkComplementMapper(&object, "checked", SIGNAL(toggled(bool)));
+
+  QCOMPARE(object.checked(), false);
+  QCOMPARE(complementMapper->valueComplement(), true);
+
+  object.setChecked(true);
+  QCOMPARE(object.checked(), true);
+  QCOMPARE(complementMapper->valueComplement(), false);
+
+  complementMapper->setValueComplement(true);
+  QCOMPARE(object.checked(), false);
+  QCOMPARE(complementMapper->valueComplement(), true);
+}
+
+// ----------------------------------------------------------------------------
+void ctkComplementMapperTester::testSignals()
+{
+  ctkObjectWithBoolProp object;
+  ctkComplementMapper* complementMapper =
+    new ctkComplementMapper(&object, "checked", SIGNAL(toggled(bool)));
+
+  QSignalSpy spyToggled(&object, SIGNAL(toggled(bool)));
+  QSignalSpy spyValueChanged(complementMapper, SIGNAL(valueChanged(bool)));
+  QSignalSpy spyValueComplementChanged(complementMapper,
+                                       SIGNAL(valueComplementChanged(bool)));
+
+  object.setChecked(true);
+  QCOMPARE(spyToggled.count(), 1);
+  QCOMPARE(spyValueChanged.count(), 1);
+  QCOMPARE(spyValueComplementChanged.count(), 1);
+
+  spyToggled.clear();
+  spyValueChanged.clear();
+  spyValueComplementChanged.clear();
+
+  // no op
+  complementMapper->setValue(complementMapper->value());
+  QCOMPARE(spyToggled.count(), 0);
+  QCOMPARE(spyValueChanged.count(), 0);
+  QCOMPARE(spyValueComplementChanged.count(), 0);
+
+  complementMapper->toggle();
+  QCOMPARE(spyToggled.count(), 1);
+  QCOMPARE(spyValueChanged.count(), 1);
+  QCOMPARE(spyValueComplementChanged.count(), 1);
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkComplementMapperTest)
+#include "moc_ctkComplementMapperTest.cpp"

+ 107 - 0
Libs/Core/ctkComplementMapper.cpp

@@ -0,0 +1,107 @@
+/*=========================================================================
+
+  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 <QVariant>
+
+// CTK includes
+#include "ctkComplementMapper.h"
+
+//-----------------------------------------------------------------------------
+class ctkComplementMapperPrivate
+{
+public:
+  QByteArray PropertyName;
+};
+
+// --------------------------------------------------------------------------
+// ctkComplementMapper methods
+
+// --------------------------------------------------------------------------
+ctkComplementMapper::ctkComplementMapper(
+  QObject* targetObject, const QByteArray& property, const char* signal)
+  : QObject(targetObject)
+  , d_ptr(new ctkComplementMapperPrivate)
+{
+  Q_ASSERT(property.isEmpty() != true);
+  Q_ASSERT(targetObject != 0);
+  Q_D(ctkComplementMapper);
+  d->PropertyName = property;
+  if (signal)
+    {
+    connect(targetObject, signal, this, SLOT(emitValueChanged()));
+    }
+}
+
+// --------------------------------------------------------------------------
+ctkComplementMapper::~ctkComplementMapper()
+{
+}
+
+// --------------------------------------------------------------------------
+QByteArray ctkComplementMapper::propertyName()const
+{
+  Q_D(const ctkComplementMapper);
+  return d->PropertyName;
+}
+
+// --------------------------------------------------------------------------
+QObject* ctkComplementMapper::targetObject()const
+{
+  return this->parent();
+}
+
+// --------------------------------------------------------------------------
+bool ctkComplementMapper::value()const
+{
+  return this->targetObject()->property(this->propertyName()).toBool();
+}
+
+// --------------------------------------------------------------------------
+bool ctkComplementMapper::valueComplement()const
+{
+  return !this->value();
+}
+
+// --------------------------------------------------------------------------
+void ctkComplementMapper::setValue(bool value)
+{
+  this->targetObject()->setProperty(this->propertyName(), QVariant(value));
+}
+
+// --------------------------------------------------------------------------
+void ctkComplementMapper::setValueComplement(bool value)
+{
+  this->setValue(!value);
+}
+
+// --------------------------------------------------------------------------
+void ctkComplementMapper::toggle()
+{
+  this->setValue(this->valueComplement());
+}
+
+// --------------------------------------------------------------------------
+void ctkComplementMapper::emitValueChanged()
+{
+  emit valueChanged(this->value());
+  emit valueComplementChanged(this->valueComplement());
+}
+

+ 92 - 0
Libs/Core/ctkComplementMapper.h

@@ -0,0 +1,92 @@
+/*=========================================================================
+
+  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.
+
+=========================================================================*/
+
+#ifndef __ctkComplementMapper_h
+#define __ctkComplementMapper_h
+
+// Qt includes
+#include <QObject>
+
+// CTK includes
+#include "ctkCoreExport.h"
+class ctkComplementMapperPrivate;
+
+//---------------------------------------------------------------------------
+/// \ingroup Core
+/// QCheckBox* checkBox = new QCheckBox;
+/// ctkComplementMapper* inverter =
+///   new ctkComplementMapper("checked", SIGNAL("toggled(bool)"), checkBox);
+/// inverter->setComplementValue(true);
+/// // -> checkBox->checked() == false
+/// inverter->setValue(false);
+/// // -> checkBox->checked() == false
+class CTK_CORE_EXPORT ctkComplementMapper : public QObject
+{
+  Q_OBJECT
+  /// This property contains the name of the object mapped property.
+  Q_PROPERTY(QByteArray propertyName READ propertyName)
+
+  /// This property holds the mapped property.
+  /// It is the value of the mapped object property
+  Q_PROPERTY(bool value READ value WRITE setValue NOTIFY valueComplementChanged STORED false);
+
+  /// This property is the complement of the mapped property.
+  /// false if \a value is true and true if \a value is false
+  Q_PROPERTY(bool valueComplement READ valueComplement WRITE setValueComplement NOTIFY valueComplementChanged STORED false)
+
+public:
+  /// Map the property \a property of the object.
+  /// The mapper becomes a child of \a object and will be destructed when
+  /// \a object is destructed.
+  /// property and object must be valid and non empty. If signal is 0,
+  /// \a valueChanged(bool) and \a valueComplementChanged(bool) won't be fired.
+  ctkComplementMapper(QObject* targetObject, const QByteArray& propertyName, const char* signal);
+  virtual ~ctkComplementMapper();
+
+  QByteArray propertyName()const;
+
+  /// The mapped object (the mapper parent)
+  QObject* targetObject()const;
+
+  bool value()const;
+  bool valueComplement()const;
+
+public Q_SLOTS:
+  void setValue(bool value);
+  void setValueComplement(bool valueComplement);
+
+  void toggle();
+
+Q_SIGNALS:
+  void valueChanged(bool value);
+  void valueComplementChanged(bool valueComplement);
+
+protected Q_SLOTS:
+  void emitValueChanged();
+protected:
+  QScopedPointer<ctkComplementMapperPrivate> d_ptr;
+
+private:
+  Q_DECLARE_PRIVATE(ctkComplementMapper);
+  Q_DISABLE_COPY(ctkComplementMapper);
+};
+
+#endif
+

+ 45 - 1
Libs/Widgets/Testing/Cpp/ctkSettingsPanelTest1.cpp

@@ -27,6 +27,7 @@
 #include <QVariant>
 
 // CTK includes
+#include "ctkComplementMapper.h"
 #include "ctkSettingsPanel.h"
 
 // STD includes
@@ -237,9 +238,52 @@ int ctkSettingsPanelTest1(int argc, char * argv [] )
     return EXIT_FAILURE;
     }
 
+  box->setChecked(false);
+  settingsPanel.registerProperty("key complement",
+                                 new ctkComplementMapper(box, "checked", SIGNAL(toggled(bool))),
+                                 "valueComplement",
+                                  SIGNAL(valueComplementChanged(bool)));
+
+  // Check settings value after a property is registered
+  boxVal = settings.value("key complement");
+  if (!boxVal.isValid() || boxVal.toBool() != true)
+    {
+    std::cerr << "Line " << __LINE__ << " - Saving to settings failed" << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (settingsPanel.myPreviousPropertyValue("key complement").toBool() != true)
+    {
+    std::cerr << "Line " << __LINE__ << " - Problem with previousPropertyValue()!" << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (settingsPanel.myPropertyValue("key complement").toBool() != true)
+    {
+    std::cerr << "Line " << __LINE__ << " - Problem with propertyValue()!" << std::endl;
+    return EXIT_FAILURE;
+    }
+  // Update value using the object/widget API
+  box->setChecked(true);
+
+  // Check settings value after it has been updated using object/widget API
+  boxVal = settings.value("key complement");
+  if (!boxVal.isValid() || boxVal.toBool() != false)
+    {
+    std::cerr << "Line " << __LINE__ << " - Saving to settings failed" << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (settingsPanel.myPreviousPropertyValue("key complement").toBool() != true)
+    {
+    std::cerr << "Line " << __LINE__ << " - Problem with previousPropertyValue()!" << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (settingsPanel.myPropertyValue("key complement").toBool() != false)
+    {
+    std::cerr << "Line " << __LINE__ << " - Problem with propertyValue()!" << std::endl;
+    return EXIT_FAILURE;
+    }
 
   settingsPanel.show();
-      
+
   if (argc < 2 || QString(argv[1]) != "-I" )
     {
     QTimer::singleShot(200, &app, SLOT(quit()));

+ 8 - 1
Libs/Widgets/ctkSettingsPanel.h

@@ -55,7 +55,14 @@ public:
   /// \a signal is typically the value under NOTIFY in Q_PROPERTY.
   /// The current value of the property is later used when
   /// restoreDefaultSettings() is called.
-  /// \sa Q_PROPERTY()
+  /// If you want to register the logical complement of a boolean property
+  /// you can use ctkComplementMapper:
+  /// <code>
+  /// panel->registerProperty("unchecked",
+  ///                         new ctkComplementMapper(checkBox, "checked", SIGNAL(toggled(bool))),
+  ///                         "valueComplement", SIGNAL(valueComplementChanged(bool)));
+  /// </code>
+  /// \sa Q_PROPERTY(), \sa ctkComplementMapper
   void registerProperty(const QString& key,
                         QObject* object,
                         const QString& property,