From 61e73ddfdd22418890102c44166483ddb28e1c93 Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Sun, 4 Jan 2009 02:32:24 +0100 Subject: [PATCH] Introduce automagic child widget handling in SettingsPage This tackles the most annoying thing about settingspages: the need for tracking the changes of every child widget, comparing it with the stored value, and updating the changedState for the whole page. Now standard widgets only need to have two dynamic properties set in Qt Designer and all that is handled automatically for them, including load/save/defaults. See settingspage.h for more information about how to do that. --- src/uisupport/settingspage.cpp | 128 +++++++++++++++++++++++++++++++-- src/uisupport/settingspage.h | 79 +++++++++++++++----- 2 files changed, 187 insertions(+), 20 deletions(-) diff --git a/src/uisupport/settingspage.cpp b/src/uisupport/settingspage.cpp index 9ee7f00f..cee72a20 100644 --- a/src/uisupport/settingspage.cpp +++ b/src/uisupport/settingspage.cpp @@ -25,6 +25,10 @@ #include #include +#include + +#include "uisettings.h" + SettingsPage::SettingsPage(const QString &category, const QString &title, QWidget *parent) : QWidget(parent), _category(category), @@ -33,10 +37,12 @@ SettingsPage::SettingsPage(const QString &category, const QString &title, QWidge { } -void SettingsPage::setChangedState(bool hasChanged) { - if(hasChanged != _changed) { - _changed = hasChanged; - emit changed(hasChanged); +void SettingsPage::setChangedState(bool hasChanged_) { + if(hasChanged_ != _changed) { + bool old = hasChanged(); + _changed = hasChanged_; + if(hasChanged() != old) + emit changed(hasChanged()); } } @@ -67,3 +73,117 @@ void SettingsPage::load(QSpinBox *box, int value) { bool SettingsPage::hasChanged(QSpinBox *box) { return box->property("StoredValue").toInt() == box->value(); } + +/*** Auto child widget handling ***/ + +void SettingsPage::initAutoWidgets() { + _autoWidgets.clear(); + + if(settingsKey().isNull()) + return; + + // find all descendants that should be considered auto widgets + // we need to climb the QObject tree recursively + findAutoWidgets(this, &_autoWidgets); + + foreach(QObject *widget, _autoWidgets) { + if(widget->inherits("QAbstractButton")) + connect(widget, SIGNAL(toggled(bool)), SLOT(autoWidgetHasChanged())); + else if(widget->inherits("QLineEdit") || widget->inherits("QTextEdit")) + connect(widget, SIGNAL(textChanged(const QString &)), SLOT(autoWidgetHasChanged())); + else if(widget->inherits("QComboBox")) + connect(widget, SIGNAL(currentIndexChanged(int)), SLOT(autoWidgetHasChanged())); + else if(widget->inherits("QSpinBox")) + connect(widget, SIGNAL(valueChanged(int)), SLOT(autoWidgetHasChanged())); + else + qWarning() << "SettingsPage::init(): Unknown autoWidget type" << widget->metaObject()->className(); + } +} + +void SettingsPage::findAutoWidgets(QObject *parent, QObjectList *autoList) const { + foreach(QObject *child, parent->children()) { + if(!child->property("settingsKey").toString().isEmpty()) + autoList->append(child); + findAutoWidgets(child, autoList); + } +} + +QByteArray SettingsPage::autoWidgetPropertyName(QObject *widget) const { + QByteArray prop; + if(widget->inherits("QAbstractButton")) + prop = "checked"; + else if(widget->inherits("QLineEdit") || widget->inherits("QTextEdit")) + prop = "text"; + else if(widget->inherits("QComboBox")) + prop = "currentIndex"; + else if(widget->inherits("QSpinBox")) + prop = "value"; + else + qWarning() << "SettingsPage::autoWidgetPropertyName(): Unhandled widget type for" << widget; + + return prop; +} + +QString SettingsPage::autoWidgetSettingsKey(QObject *widget) const { + QString key = widget->property("settingsKey").toString(); + if(key.startsWith('/')) + key.remove(0, 1); + else + key.prepend(settingsKey() + '/'); + return key; +} + +void SettingsPage::autoWidgetHasChanged() { + bool changed_ = false; + foreach(QObject *widget, _autoWidgets) { + QVariant curValue = widget->property(autoWidgetPropertyName(widget)); + if(!curValue.isValid()) + qWarning() << "SettingsPage::autoWidgetHasChanged(): Unknown property"; + + if(curValue != widget->property("storedValue")) { + changed_ = true; + break; + } + } + + if(changed_ != _autoWidgetsChanged) { + bool old = hasChanged(); + _autoWidgetsChanged = changed_; + if(hasChanged() != old) + emit changed(hasChanged()); + } +} + +void SettingsPage::load() { + UiSettings s(""); + foreach(QObject *widget, _autoWidgets) { + QVariant val = s.value(autoWidgetSettingsKey(widget), widget->property("defaultValue")); + widget->setProperty(autoWidgetPropertyName(widget), val); + widget->setProperty("storedValue", val); + } + bool old = hasChanged(); + _autoWidgetsChanged = _changed = false; + if(hasChanged() != old) + emit changed(hasChanged()); +} + +void SettingsPage::save() { + UiSettings s(""); + foreach(QObject *widget, _autoWidgets) { + QVariant val = widget->property(autoWidgetPropertyName(widget)); + widget->setProperty("storedValue", val); + s.setValue(autoWidgetSettingsKey(widget), val); + } + bool old = hasChanged(); + _autoWidgetsChanged = _changed = false; + if(hasChanged() != old) + emit changed(hasChanged()); +} + +void SettingsPage::defaults() { + foreach(QObject *widget, _autoWidgets) { + QVariant val = widget->property("defaultValue"); + widget->setProperty(autoWidgetPropertyName(widget), val); + } + autoWidgetHasChanged(); +} diff --git a/src/uisupport/settingspage.h b/src/uisupport/settingspage.h index 7a5bcc6a..d3db08a8 100644 --- a/src/uisupport/settingspage.h +++ b/src/uisupport/settingspage.h @@ -31,6 +31,24 @@ class QSpinBox; /** The SettingsDlg provides suitable standard buttons, such as Ok, Apply, Cancel, Restore Defaults and Reset. * Some pages might also be used in standalone dialogs or other containers. A SettingsPage provides suitable * slots and signals to allow interaction with the container. + * + * A derived class needs to keep track of its changed state. Whenever a child widget changes, it needs to be + * compared to its value in permanent storage, and the changed state updated accordingly by calling setChangedState(). + * For most standard widgets, SettingsPage can do this automatically if desired. Such a child widget needs to have + * a dynamic property \c settingsKey that maps to the key in the client configuration file. This key is appended + * to settingsKey(), which must be set to a non-null value in a derived class. If the widget's key starts with '/', + * its key is treated as a global path starting from the root, rather than from settingsKey(). + * A second dynamic property \c defaultValue can be defined in child widgets as well. + * + * SettingsPage manages loading, saving, setting to default and setting the changed state for all automatic child widgets. + * Derived classes must be sure to call initAutoWidgets() *after* setupUi(); they also need to call the baseclass implementations + * of load(), save() and defaults() (preferably at the end of the derived function, since they call setChangedState(false)). + * + * The following widgets can be handled for now: + * - QAbstractButton (isChecked(), e.g. for QCheckBox and QRadioButton) + * - QLineEdit, QTextEdit (text()) + * - QComboBox (currentIndex()) + * - QSpinBox (value()) */ class SettingsPage : public QWidget { Q_OBJECT @@ -38,13 +56,23 @@ class SettingsPage : public QWidget { public: SettingsPage(const QString &category, const QString &name, QWidget *parent = 0); virtual ~SettingsPage() {}; - + //! The category of this settings page. inline virtual QString category() const { return _category; } - + //! The title of this settings page. inline virtual QString title() const { return _title; } - + + //! The key this settings page stores its values under + /** This needs to be overriden to enable automatic loading/saving/hasChanged checking of widgets. + * The child widgets' values will be stored in client settings under this key. Every widget that + * should be automatically handled needs to have a \c settingsKey property set, and should also provide + * a \c defaultValue property. + * You can return an empty string (as opposed to a null string) to use the config root as a base, and + * you can override this key for individual widgets by prefixing their SettingsKey with /. + */ + inline virtual QString settingsKey() const { return QString(); } + //! Derived classes need to define this and return true if they have default settings. /** If this method returns true, the "Restore Defaults" button in the SettingsDlg is * enabled. You also need to provide an implementation of defaults() then. @@ -52,10 +80,10 @@ public: * The default implementation returns false. */ inline virtual bool hasDefaults() const { return false; } - + //! Check if there are changes in the page, compared to the state saved in permanent storage. - inline bool hasChanged() const { return _changed; } - + inline bool hasChanged() const { return _changed || _autoWidgetsChanged; } + //! Called immediately before save() is called. /** Derived classes should return false if saving is not possible (e.g. the current settings are invalid). * \return false, if the SettingsPage cannot be saved in its current state. @@ -72,30 +100,49 @@ public: public slots: //! Save settings to permanent storage. - virtual void save() = 0; - + /** This baseclass implementation saves the autoWidgets, so be sure to call it if you use + * this feature in your settingsPage! + */ + virtual void save(); + //! Load settings from permanent storage, overriding any changes the user might have made in the dialog. - virtual void load() = 0; + /** This baseclass implementation loads the autoWidgets, so be sure to call it if you use + * this feature in your settingsPage! + */ + virtual void load(); //! Restore defaults, overriding any changes the user might have made in the dialog. - /** The default implementation does nothing. + /** This baseclass implementation loads the defaults of the autoWidgets (if available), so be sure + * to call it if you use this feature in your settingsPage! */ - inline virtual void defaults() {} - + virtual void defaults(); + protected slots: //! Calling this slot is equivalent to calling setChangedState(true). inline void changed() { setChangedState(true); } - + //! This should be called whenever the widget state changes from unchanged to change or the other way round. void setChangedState(bool hasChanged = true); - + +protected: + void initAutoWidgets(); + signals: //! Emitted whenever the widget state changes. void changed(bool hasChanged); - + +private slots: + // for auto stuff + void autoWidgetHasChanged(); + private: + void findAutoWidgets(QObject *parent, QObjectList *widgetList) const; + QByteArray autoWidgetPropertyName(QObject *widget) const; + QString autoWidgetSettingsKey(QObject *widget) const; + QString _category, _title; - bool _changed; + bool _changed, _autoWidgetsChanged; + QObjectList _autoWidgets; }; -- 2.20.1