Browse Source

Add RequireRestart property in ctkSettingsPanel/Dialog

Julien Finet 13 years ago
parent
commit
ff24d612b4

+ 32 - 4
Libs/Widgets/Resources/UI/ctkSettingsDialog.ui

@@ -6,15 +6,15 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>690</width>
+    <width>457</width>
     <height>397</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>Settings</string>
   </property>
-  <layout class="QGridLayout" name="gridLayout" columnstretch="0,1">
-   <item row="0" column="0">
+  <layout class="QGridLayout" name="gridLayout" columnstretch="0,0">
+   <item row="0" column="0" rowspan="2">
     <widget class="QTreeWidget" name="SettingsTreeWidget">
      <property name="headerHidden">
       <bool>true</bool>
@@ -29,7 +29,7 @@
    <item row="0" column="1">
     <widget class="QStackedWidget" name="SettingsStackedWidget"/>
    </item>
-   <item row="1" column="0" colspan="2">
+   <item row="2" column="0" colspan="2">
     <widget class="QDialogButtonBox" name="SettingsButtonBox">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
@@ -39,6 +39,34 @@
      </property>
     </widget>
    </item>
+   <item row="1" column="1">
+    <widget class="QLabel" name="RestartRequiredLabel">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Sunken</enum>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="textFormat">
+      <enum>Qt::RichText</enum>
+     </property>
+     <property name="scaledContents">
+      <bool>true</bool>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+     <property name="textInteractionFlags">
+      <set>Qt::NoTextInteraction</set>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <resources/>

+ 3 - 2
Libs/Widgets/Testing/Cpp/CMakeLists.txt

@@ -59,8 +59,8 @@ set(TEST_SOURCES
   ctkScreenshotDialogTest1.cpp
   ctkSearchBoxTest1.cpp
   ctkSearchBoxTest2.cpp
+  ctkSettingsPanelTest.cpp
   ctkSettingsPanelTest1.cpp
-  ctkSettingsPanelTest2.cpp
   ctkSettingsTest1.cpp
   ctkSettingsDialogTest1.cpp
   ctkSignalMapperTest1.cpp
@@ -174,6 +174,7 @@ QT4_GENERATE_MOCS(
   ctkFontButtonTest.cpp
   ctkMessageBoxDontShowAgainTest.cpp
   ctkRangeSliderTest.cpp
+  ctkSettingsPanelTest.cpp
   )
 set(Tests_UI_CPP)
 if(TEST_UI_FORMS)
@@ -250,8 +251,8 @@ SIMPLE_TEST( ctkScreenshotDialogTest1 )
 SIMPLE_TEST( ctkSearchBoxTest1 )
 SIMPLE_TEST( ctkSearchBoxTest2 )
 SIMPLE_TEST( ctkSettingsDialogTest1 )
+SIMPLE_TEST( ctkSettingsPanelTest )
 SIMPLE_TEST( ctkSettingsPanelTest1 )
-SIMPLE_TEST( ctkSettingsPanelTest2 )
 SIMPLE_TEST( ctkSettingsTest1 )
 SIMPLE_TEST( ctkSignalMapperTest1 )
 SIMPLE_TEST( ctkSliderWidgetTest1 )

+ 150 - 0
Libs/Widgets/Testing/Cpp/ctkSettingsPanelTest.cpp

@@ -0,0 +1,150 @@
+/*=========================================================================
+
+  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 <QSettings>
+#include <QSignalSpy>
+#include <QSpinBox>
+
+// CTK includes
+#include "ctkSettingsPanel.h"
+#include "ctkSettingsPanelTest2Helper.h"
+#include "ctkTest.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+
+// ----------------------------------------------------------------------------
+class ctkSettingsPanelTester: public QObject
+{
+  Q_OBJECT
+private slots:
+
+  void testChangeProperty();
+  void testChangeProperty_data();
+
+  void testEmptyQStringList();
+};
+
+//-----------------------------------------------------------------------------
+void ctkSettingsPanelTester::testChangeProperty()
+{
+  QSettings settings(QSettings::IniFormat, QSettings::UserScope, "Common ToolKit", "CTK");
+  QSpinBox spinBox;
+  ctkSettingsPanel panel;
+  panel.setSettings(&settings);
+  spinBox.setValue(1);
+
+  QFETCH(QString, label);
+  QFETCH(ctkSettingsPanel::SettingOptions, options);
+  panel.registerProperty("property", &spinBox,
+                         "value", SIGNAL(valueChanged(int)),
+                         label, options);
+
+  QCOMPARE(spinBox.value(), 1);
+  QCOMPARE(panel.settingLabel("property"), label);
+  QCOMPARE(panel.settingOptions("property"), options);
+
+  qRegisterMetaType<QVariant>("QVariant");
+  QSignalSpy spy(&panel, SIGNAL(settingChanged(QString,QVariant)));
+  QFETCH(int, value);
+  QFETCH(bool, setOnObject);
+  if (setOnObject)
+    {
+    spinBox.setValue(value);
+    }
+  else
+    {
+    panel.setSetting("property", QVariant(value));
+    }
+
+  QFETCH(int, expectedSettingChangedCount);
+  QCOMPARE(spy.count(), expectedSettingChangedCount);
+  QFETCH(QStringList, expectedChangedSettings);
+  QCOMPARE(panel.changedSettings(), expectedChangedSettings);
+
+  // make sure it didn't change
+  QCOMPARE(panel.settingLabel("property"), label);
+  QCOMPARE(panel.settingOptions("property"), options);
+
+  panel.resetSettings();
+}
+
+//-----------------------------------------------------------------------------
+void ctkSettingsPanelTester::testChangeProperty_data()
+{
+  QTest::addColumn<QString>("label");
+  QTest::addColumn<ctkSettingsPanel::SettingOptions>("options");
+  QTest::addColumn<int>("value");
+  QTest::addColumn<bool>("setOnObject");
+  QTest::addColumn<int>("expectedSettingChangedCount");
+  QTest::addColumn<QStringList>("expectedChangedSettings");
+
+  QTest::newRow("null none changed obj") << QString() << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 2 << true << 1 << QStringList("property");
+  QTest::newRow("null none changed panel") << QString() << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 2 << false << 1 << QStringList("property");
+  QTest::newRow("null none unchanged obj") << QString() << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 1 << true << 0 << QStringList();
+  QTest::newRow("null none unchanged panel") << QString() << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 1 << false << 0 << QStringList();
+  QTest::newRow("null RequireRestart changed obj") << QString() << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 2 << true << 1 << QStringList("property");
+  QTest::newRow("null RequireRestart changed panel") << QString() << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 2 << false << 1 << QStringList("property");
+  QTest::newRow("null RequireRestart unchanged obj") << QString() << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 1 << true << 0 << QStringList();
+  QTest::newRow("null RequireRestart unchanged panel") << QString() << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 1 << false << 0 << QStringList();
+  QTest::newRow("empty none changed obj") << QString("") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 2 << true << 1 << QStringList("property");
+  QTest::newRow("empty none changed panel") << QString("") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 2 << false << 1 << QStringList("property");
+  QTest::newRow("empty none unchanged obj") << QString("") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 1 << true << 0 << QStringList();
+  QTest::newRow("empty none unchanged panel") << QString("") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 1 << false << 0 << QStringList();
+  QTest::newRow("empty RequireRestart changed obj") << QString("") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 2 << true << 1 << QStringList("property");
+  QTest::newRow("empty RequireRestart changed panel") << QString("") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 2 << false << 1 << QStringList("property");
+  QTest::newRow("empty RequireRestart unchanged obj") << QString("") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 1 << true << 0 << QStringList();
+  QTest::newRow("empty RequireRestart unchanged panel") << QString("") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 1 << false << 0 << QStringList();
+  QTest::newRow("label none changed obj") << QString("label") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 2 << true << 1 << QStringList("property");
+  QTest::newRow("label none changed panel") << QString("label") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 2 << false << 1 << QStringList("property");
+  QTest::newRow("label none unchanged obj") << QString("label") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 1 << true << 0 << QStringList();
+  QTest::newRow("label none unchanged panel") << QString("label") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionNone) << 1 << false << 0 << QStringList();
+  QTest::newRow("label RequireRestart changed obj") << QString("label") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 2 << true << 1 << QStringList("property");
+  QTest::newRow("label RequireRestart changed panel") << QString("label") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 2 << false << 1 << QStringList("property");
+  QTest::newRow("label RequireRestart unchanged obj") << QString("label") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 1 << true << 0 << QStringList();
+  QTest::newRow("label RequireRestart unchanged panel") << QString("label") << ctkSettingsPanel::SettingOptions(ctkSettingsPanel::OptionRequireRestart) << 1 << false << 0 << QStringList();
+}
+
+//-----------------------------------------------------------------------------
+void ctkSettingsPanelTester::testEmptyQStringList()
+{
+  {
+    // When QSettings goes out of scope, we are the settings file is up-to-date
+    QSettings settings(QSettings::IniFormat, QSettings::UserScope, "Common ToolKit", "CTK");
+    settings.clear();
+    settings.setValue("list", QVariant(QStringList()));
+  }
+
+  // Regression: Reading empty QStringList property from settings should be handled properly
+
+  ctkSettingsPanel settingsPanel;
+  ctkSettingsPanelTest2Helper * list = new ctkSettingsPanelTest2Helper(&settingsPanel);
+  settingsPanel.registerProperty("list", list, "list", SIGNAL(listChanged()));
+  QSettings settings2(QSettings::IniFormat, QSettings::UserScope, "Common ToolKit", "CTK");
+  settingsPanel.setSettings(&settings2);
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkSettingsPanelTest)
+#include "moc_ctkSettingsPanelTest.cpp"

+ 0 - 56
Libs/Widgets/Testing/Cpp/ctkSettingsPanelTest2.cpp

@@ -1,56 +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 <QSettings>
-
-// CTK includes
-#include "ctkSettingsPanel.h"
-#include "ctkSettingsPanelTest2Helper.h"
-
-// STD includes
-#include <cstdlib>
-#include <iostream>
-
-//-----------------------------------------------------------------------------
-int ctkSettingsPanelTest2(int argc, char * argv [] )
-{
-  QApplication app(argc, argv);
-  Q_UNUSED(app);
-
-  {
-    // When QSettings goes out of scope, we are the settings file is up-to-date
-    QSettings settings(QSettings::IniFormat, QSettings::UserScope, "Common ToolKit", "CTK");
-    settings.clear();
-    settings.setValue("list", QVariant(QStringList()));
-  }
-
-  // Regression: Reading empty QStringList property from settings should be handled properly
-
-  ctkSettingsPanel settingsPanel;
-  ctkSettingsPanelTest2Helper * list = new ctkSettingsPanelTest2Helper(&settingsPanel);
-  settingsPanel.registerProperty("list", list, "list", SIGNAL(listChanged()));
-  QSettings settings2(QSettings::IniFormat, QSettings::UserScope, "Common ToolKit", "CTK");
-  settingsPanel.setSettings(&settings2);
-
-  return EXIT_SUCCESS;
-}
-

+ 89 - 0
Libs/Widgets/ctkSettingsDialog.cpp

@@ -43,6 +43,7 @@ public:
   ctkSettingsDialogPrivate(ctkSettingsDialog& object);
   void init();
 
+  QList<ctkSettingsPanel*> panels()const;
   ctkSettingsPanel* panel(QTreeWidgetItem* item)const;
   QTreeWidgetItem* item(ctkSettingsPanel* panel)const;
   QTreeWidgetItem* item(const QString& label)const;
@@ -50,6 +51,9 @@ public:
   void beginGroup(ctkSettingsPanel* panel);
   void endGroup(ctkSettingsPanel* panel);
 
+  void updatePanelTitle(ctkSettingsPanel* panel);
+  void updateRestartRequiredLabel();
+
   QSettings* Settings;
 
 protected:
@@ -88,6 +92,15 @@ void ctkSettingsDialogPrivate::init()
     q, SLOT(onCurrentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
   QObject::connect(this->SettingsButtonBox, SIGNAL(clicked(QAbstractButton*)),
                    q, SLOT(onDialogButtonClicked(QAbstractButton*)));
+
+  this->updateRestartRequiredLabel();
+  q->adjustTreeWidgetToContents();
+}
+
+// --------------------------------------------------------------------------
+QList<ctkSettingsPanel*> ctkSettingsDialogPrivate::panels()const
+{
+  return this->Panels.values();
 }
 
 // --------------------------------------------------------------------------
@@ -117,6 +130,50 @@ QTreeWidgetItem* ctkSettingsDialogPrivate::item(const QString& label)const
 }
 
 // --------------------------------------------------------------------------
+void ctkSettingsDialogPrivate::updatePanelTitle(ctkSettingsPanel* panel)
+{
+  Q_Q(ctkSettingsDialog);
+  QTreeWidgetItem* panelItem = this->item(panel);
+  QString title = panelItem->text(0);
+  title.replace(QRegExp("\\*$"),"");
+  if (!panel->changedSettings().isEmpty())
+    {
+    title.append('*');
+    }
+  panelItem->setText(0,title);
+}
+
+// --------------------------------------------------------------------------
+void ctkSettingsDialogPrivate::updateRestartRequiredLabel()
+{
+  Q_Q(ctkSettingsDialog);
+  QStringList restartRequiredSettings;
+  foreach(const ctkSettingsPanel* panel, this->panels())
+    {
+    foreach(const QString& settingKey, panel->changedSettings())
+      {
+      if (panel->settingOptions(settingKey) & ctkSettingsPanel::OptionRequireRestart)
+        {
+        restartRequiredSettings << (panel->settingLabel(settingKey).isEmpty() ?
+          settingKey : panel->settingLabel(settingKey));
+        }
+      }
+    }
+  bool restartRequired = !restartRequiredSettings.isEmpty();
+  if (restartRequired)
+    {
+    QString header = q->tr(
+      "<b style=\"color:red\">Restart required!</b><br>\n<small>"
+      "The application must be restarted to take into account "
+      "the new values of the following properties:\n");
+    QString footer = q->tr("</small>");
+    restartRequiredSettings.push_front(QString());
+    this->RestartRequiredLabel->setText( header + restartRequiredSettings.join("<br>&nbsp;&nbsp;") + footer);
+    }
+  this->RestartRequiredLabel->setVisible(restartRequired);
+}
+
+// --------------------------------------------------------------------------
 ctkSettingsDialog::ctkSettingsDialog(QWidget* _parent)
   : Superclass(_parent)
   , d_ptr(new ctkSettingsDialogPrivate(*this))
@@ -231,8 +288,30 @@ ctkSettingsPanel* ctkSettingsDialog::panel(const QString& label)const
 // --------------------------------------------------------------------------
 void ctkSettingsDialog::accept()
 {
+  Q_D(ctkSettingsDialog);
+  bool emitRestartRequested = false;
+  if (this->isRestartRequired())
+    {
+    QMessageBox::StandardButton answer = QMessageBox::question(this,"Restart required",
+      "For settings to be taken into account, the application\n"
+      "must be restarted. Restart the application now ?\n",
+      QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, QMessageBox::No);
+    if (answer == QMessageBox::Cancel)
+      {
+      return;
+      }
+    else
+      {
+      emitRestartRequested = (answer == QMessageBox::Yes);
+      }
+    }
+
   this->applySettings();
   this->Superclass::accept();
+  if (emitRestartRequested)
+    {
+    emit restartRequested();
+    }
 }
 
 // --------------------------------------------------------------------------
@@ -287,6 +366,8 @@ void ctkSettingsDialog
 {
   Q_D(ctkSettingsDialog);
   d->SettingsButtonBox->button(QDialogButtonBox::Reset)->setEnabled(true);
+  d->updatePanelTitle(qobject_cast<ctkSettingsPanel*>(this->sender()));
+  d->updateRestartRequiredLabel();
   emit settingChanged(key, newVal);
 }
 
@@ -332,6 +413,7 @@ void ctkSettingsDialog::adjustTreeWidgetToContents()
 
   d->SettingsTreeWidget->setFixedWidth(
       d->SettingsTreeWidget->QAbstractItemView::sizeHintForColumn(0) +
+      d->SettingsTreeWidget->fontMetrics().width('*') +
       2 * d->SettingsTreeWidget->indentation() +
       2 * d->SettingsTreeWidget->frameWidth());
 }
@@ -362,3 +444,10 @@ void ctkSettingsDialog::setResetButton(bool show)
   d->SettingsButtonBox->button(QDialogButtonBox::Reset)->setVisible(show);
 }
 
+// --------------------------------------------------------------------------
+bool ctkSettingsDialog::isRestartRequired()const
+{
+  Q_D(const ctkSettingsDialog);
+  return d->RestartRequiredLabel->isVisibleTo(
+    const_cast<ctkSettingsDialog*>(this));
+}

+ 9 - 0
Libs/Widgets/ctkSettingsDialog.h

@@ -69,6 +69,11 @@ public:
   bool resetButton()const;
   void setResetButton(bool show);
 
+  /// True if at least one OptionRestartRequired setting is changed.
+  /// It doesn't mean the user accepted to restart the application
+  /// \sa restartRequired
+  bool isRestartRequired()const;
+
 public Q_SLOTS:
   void setCurrentPanel(ctkSettingsPanel* panel);
   void setCurrentPanel(const QString& label);
@@ -82,6 +87,10 @@ public Q_SLOTS:
 
 Q_SIGNALS:
   void settingChanged(const QString& key, const QVariant& value);
+  /// Signal fired when the user accepts to restart the application because
+  /// some OptionRestartRequired settings have changed.
+  /// \sa isrestartRequired
+  void restartRequested();
 
 protected Q_SLOTS:
   void onSettingChanged(const QString& key, const QVariant& newVal);

+ 46 - 2
Libs/Widgets/ctkSettingsPanel.cpp

@@ -41,6 +41,8 @@ struct PropertyType
   QString  Property;
   QVariant PreviousValue;
   QVariant DefaultValue;
+  QString  Label;
+  ctkSettingsPanel::SettingOptions Options;
 
   QVariant value()const;
   bool setValue(const QVariant& value);
@@ -51,6 +53,7 @@ struct PropertyType
 // --------------------------------------------------------------------------
 PropertyType::PropertyType()
   : Object(0)
+  , Options(ctkSettingsPanel::OptionNone)
 {
 }
 
@@ -122,6 +125,8 @@ public:
 ctkSettingsPanelPrivate::ctkSettingsPanelPrivate(ctkSettingsPanel& object)
   :q_ptr(&object)
 {
+  qRegisterMetaType<ctkSettingsPanel::SettingOption>("ctkSettingsPanel::SettingOption");
+  qRegisterMetaType<ctkSettingsPanel::SettingOptions>("ctkSettingsPanel::SettingOptions");
   this->Settings = 0;
   this->SignalMapper = 0;
   this->SaveToSettingsWhenRegister = true;
@@ -211,12 +216,16 @@ void ctkSettingsPanel::updateSetting(const QString& key)
 void ctkSettingsPanel::setSetting(const QString& key, const QVariant& newVal)
 {
   Q_D(ctkSettingsPanel);
+  if (!d->Settings)
+    {
+    return;
+    }
   QVariant oldVal = d->Settings->value(key);
   d->Settings->setValue(key, newVal);
   d->Properties[key].setValue(newVal);
   if (d->Settings->status() != QSettings::NoError)
     {
-    logger.warn( QString("Error %1 while writing setting %1")
+    logger.warn( QString("Error #%1 while writing setting \"%2\"")
       .arg(static_cast<int>(d->Settings->status()))
       .arg(key));
     }
@@ -230,13 +239,17 @@ void ctkSettingsPanel::setSetting(const QString& key, const QVariant& newVal)
 void ctkSettingsPanel::registerProperty(const QString& key,
                                         QObject* object,
                                         const QString& property,
-                                        const char* signal)
+                                        const char* signal,
+                                        const QString& label,
+                                        ctkSettingsPanel::SettingOptions options)
 {
   Q_D(ctkSettingsPanel);
   PropertyType prop;
   prop.Object = object;
   prop.Property = property;
   prop.DefaultValue = prop.PreviousValue = prop.value();
+  prop.Label = label;
+  prop.Options = options;
 
   if (d->Settings && d->Settings->contains(key))
     {
@@ -289,6 +302,37 @@ QVariant ctkSettingsPanel::propertyValue(const QString& key) const
 }
 
 // --------------------------------------------------------------------------
+QStringList ctkSettingsPanel::changedSettings()const
+{
+  Q_D(const ctkSettingsPanel);
+  QStringList settingsKeys;
+  foreach(const QString& key, d->Properties.keys())
+    {
+    const PropertyType& prop = d->Properties[key];
+    if (prop.PreviousValue != prop.value())
+      {
+      settingsKeys << key;
+      }
+    }
+  return settingsKeys;
+}
+
+// --------------------------------------------------------------------------
+QString ctkSettingsPanel::settingLabel(const QString& settingKey)const
+{
+  Q_D(const ctkSettingsPanel);
+  return d->Properties[settingKey].Label;
+}
+
+// --------------------------------------------------------------------------
+ctkSettingsPanel::SettingOptions ctkSettingsPanel
+::settingOptions(const QString& settingKey)const
+{
+  Q_D(const ctkSettingsPanel);
+  return d->Properties[settingKey].Options;
+}
+
+// --------------------------------------------------------------------------
 void ctkSettingsPanel::applySettings()
 {
   Q_D(ctkSettingsPanel);

+ 28 - 3
Libs/Widgets/ctkSettingsPanel.h

@@ -22,6 +22,7 @@
 #define __ctkSettingsPanel_h
 
 // Qt includes
+#include <QMetaType>
 #include <QWidget>
 
 // CTK includes
@@ -34,6 +35,8 @@ class ctkSettingsPanelPrivate;
 class CTK_WIDGETS_EXPORT ctkSettingsPanel : public QWidget
 {
   Q_OBJECT
+  Q_ENUMS(SettingOption)
+  Q_FLAGS(SettingOptions)
 public:
   /// Superclass typedef
   typedef QWidget Superclass;
@@ -47,6 +50,12 @@ public:
   QSettings* settings()const;
   void setSettings(QSettings* settings);
 
+  enum SettingOption{
+    OptionNone = 0x0000,
+    OptionRequireRestart = 0x0001,
+    OptionAll_Mask = ~0
+  };
+  Q_DECLARE_FLAGS(SettingOptions, SettingOption)
   /// Add an entry into the settings uniquely defined by the \a key name and the
   /// current value of the property.
   /// The property is then synchronized with the settings by observing the signal
@@ -63,14 +72,26 @@ public:
   ///                         "complement", SIGNAL(complementChanged(bool)));
   /// </code>
   /// \sa Q_PROPERTY(), \sa ctkBooleanMapper
-  void registerProperty(const QString& key,
+  void registerProperty(const QString& settingKey,
                         QObject* object,
-                        const QString& property,
-                        const char* signal);
+                        const QString& objectProperty,
+                        const char* propertySignal,
+                        const QString& settingLabel = QString(),
+                        SettingOptions options = OptionNone);
+
   /// Set the setting to the property defined by the key.
   /// The old value can be restored using resetSettings()
   void setSetting(const QString& key, const QVariant& newVal);
 
+  /// Return the list of settings keys that have been modified and are
+  /// not yet applied.
+  QStringList changedSettings()const;
+
+  /// Return the label associated to a setting
+  QString settingLabel(const QString& settingKey)const;
+
+  /// Return the options associated to a setting
+  SettingOptions settingOptions(const QString& settingKey)const;
 public Q_SLOTS:
 
   /// Forget the old property values so next time resetSettings is called it
@@ -115,4 +136,8 @@ private:
   Q_DISABLE_COPY(ctkSettingsPanel);
 };
 
+Q_DECLARE_METATYPE(ctkSettingsPanel::SettingOption)
+Q_DECLARE_METATYPE(ctkSettingsPanel::SettingOptions)
+Q_DECLARE_OPERATORS_FOR_FLAGS(ctkSettingsPanel::SettingOptions)
+
 #endif