1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 2003 Benjamin C Meyer <ben+kdelibs at meyerhome dot net>
4     SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
5     SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau <kossebau@kde.org>
6     SPDX-FileCopyrightText: 2020 Kevin Ottens <kevin.ottens@enioka.com>
7     SPDX-FileCopyrightText: 2020 Cyril Rossi <cyril.rossi@enioka.com>
8 
9     SPDX-License-Identifier: LGPL-2.0-or-later
10 */
11 
12 #include "kconfigdialogmanager.h"
13 #include "kconfigdialogmanager_p.h"
14 #include "kconfigwidgets_debug.h"
15 
16 #include <QComboBox>
17 #include <QGroupBox>
18 #include <QLabel>
19 #include <QLayout>
20 #include <QMetaObject>
21 #include <QMetaProperty>
22 #include <QRadioButton>
23 #include <QTimer>
24 
25 #include <KConfigSkeleton>
26 
27 typedef QHash<QString, QByteArray> MyHash;
Q_GLOBAL_STATIC(MyHash,s_propertyMap)28 Q_GLOBAL_STATIC(MyHash, s_propertyMap)
29 Q_GLOBAL_STATIC(MyHash, s_changedMap)
30 
31 KConfigDialogManager::KConfigDialogManager(QWidget *parent, KCoreConfigSkeleton *conf)
32     : QObject(parent)
33     , d(new KConfigDialogManagerPrivate(this))
34 {
35     d->m_conf = conf;
36     d->m_dialog = parent;
37     init(true);
38 }
39 
40 #if KCONFIGWIDGETS_BUILD_DEPRECATED_SINCE(5, 84)
KConfigDialogManager(QWidget * parent,KConfigSkeleton * conf)41 KConfigDialogManager::KConfigDialogManager(QWidget *parent, KConfigSkeleton *conf)
42     : QObject(parent)
43     , d(new KConfigDialogManagerPrivate(this))
44 {
45     d->m_conf = conf;
46     d->m_dialog = parent;
47     init(true);
48 }
49 #endif
50 
51 KConfigDialogManager::~KConfigDialogManager() = default;
52 
53 // KF6: Drop this and get signals only from metaObject and/or widget's dynamic properties kcfg_property/kcfg_propertyNotify
initMaps()54 void KConfigDialogManager::initMaps()
55 {
56     if (s_propertyMap()->isEmpty()) {
57         s_propertyMap()->insert(QStringLiteral("KButtonGroup"), "current");
58         s_propertyMap()->insert(QStringLiteral("KColorButton"), "color");
59         s_propertyMap()->insert(QStringLiteral("KColorCombo"), "color");
60         s_propertyMap()->insert(QStringLiteral("KKeySequenceWidget"), "keySequence");
61     }
62 
63     if (s_changedMap()->isEmpty()) {
64         // QT
65         s_changedMap()->insert(QStringLiteral("QCheckBox"), SIGNAL(stateChanged(int)));
66         s_changedMap()->insert(QStringLiteral("QPushButton"), SIGNAL(clicked(bool)));
67         s_changedMap()->insert(QStringLiteral("QRadioButton"), SIGNAL(toggled(bool)));
68         s_changedMap()->insert(QStringLiteral("QGroupBox"), SIGNAL(toggled(bool)));
69         s_changedMap()->insert(QStringLiteral("QComboBox"), SIGNAL(activated(int)));
70         s_changedMap()->insert(QStringLiteral("QDateEdit"), SIGNAL(dateChanged(QDate)));
71         s_changedMap()->insert(QStringLiteral("QTimeEdit"), SIGNAL(timeChanged(QTime)));
72         s_changedMap()->insert(QStringLiteral("QDateTimeEdit"), SIGNAL(dateTimeChanged(QDateTime)));
73         s_changedMap()->insert(QStringLiteral("QDial"), SIGNAL(valueChanged(int)));
74         s_changedMap()->insert(QStringLiteral("QDoubleSpinBox"), SIGNAL(valueChanged(double)));
75         s_changedMap()->insert(QStringLiteral("QLineEdit"), SIGNAL(textChanged(QString)));
76         s_changedMap()->insert(QStringLiteral("QSlider"), SIGNAL(valueChanged(int)));
77         s_changedMap()->insert(QStringLiteral("QSpinBox"), SIGNAL(valueChanged(int)));
78         s_changedMap()->insert(QStringLiteral("QTextEdit"), SIGNAL(textChanged()));
79         s_changedMap()->insert(QStringLiteral("QTextBrowser"), SIGNAL(sourceChanged(QString)));
80         s_changedMap()->insert(QStringLiteral("QPlainTextEdit"), SIGNAL(textChanged()));
81         s_changedMap()->insert(QStringLiteral("QTabWidget"), SIGNAL(currentChanged(int)));
82 
83         // KDE
84         s_changedMap()->insert(QStringLiteral("KComboBox"), SIGNAL(activated(int)));
85         s_changedMap()->insert(QStringLiteral("KFontComboBox"), SIGNAL(activated(int)));
86         s_changedMap()->insert(QStringLiteral("KFontRequester"), SIGNAL(fontSelected(QFont)));
87         s_changedMap()->insert(QStringLiteral("KFontChooser"), SIGNAL(fontSelected(QFont)));
88         s_changedMap()->insert(QStringLiteral("KColorCombo"), SIGNAL(activated(QColor)));
89         s_changedMap()->insert(QStringLiteral("KColorButton"), SIGNAL(changed(QColor)));
90         s_changedMap()->insert(QStringLiteral("KDatePicker"), SIGNAL(dateSelected(QDate)));
91         s_changedMap()->insert(QStringLiteral("KDateWidget"), SIGNAL(changed(QDate)));
92         s_changedMap()->insert(QStringLiteral("KDateTimeWidget"), SIGNAL(valueChanged(QDateTime)));
93         s_changedMap()->insert(QStringLiteral("KEditListWidget"), SIGNAL(changed()));
94         s_changedMap()->insert(QStringLiteral("KListWidget"), SIGNAL(itemSelectionChanged()));
95         s_changedMap()->insert(QStringLiteral("KLineEdit"), SIGNAL(textChanged(QString)));
96         s_changedMap()->insert(QStringLiteral("KRestrictedLine"), SIGNAL(textChanged(QString)));
97         s_changedMap()->insert(QStringLiteral("KTextEdit"), SIGNAL(textChanged()));
98         s_changedMap()->insert(QStringLiteral("KUrlRequester"), SIGNAL(textChanged(QString)));
99         s_changedMap()->insert(QStringLiteral("KUrlComboRequester"), SIGNAL(textChanged(QString)));
100         s_changedMap()->insert(QStringLiteral("KUrlComboBox"), SIGNAL(urlActivated(QUrl)));
101         s_changedMap()->insert(QStringLiteral("KButtonGroup"), SIGNAL(changed(int)));
102     }
103 }
104 
propertyMap()105 QHash<QString, QByteArray> *KConfigDialogManager::propertyMap()
106 {
107     initMaps();
108     return s_propertyMap();
109 }
110 
111 #if KCONFIGWIDGETS_BUILD_DEPRECATED_SINCE(5, 32)
changedMap()112 QHash<QString, QByteArray> *KConfigDialogManager::changedMap()
113 {
114     initMaps();
115     return s_changedMap();
116 }
117 #endif
118 
init(bool trackChanges)119 void KConfigDialogManager::init(bool trackChanges)
120 {
121     initMaps();
122     d->trackChanges = trackChanges;
123 
124     // Go through all of the children of the widgets and find all known widgets
125     (void)parseChildren(d->m_dialog, trackChanges);
126 }
127 
addWidget(QWidget * widget)128 void KConfigDialogManager::addWidget(QWidget *widget)
129 {
130     (void)parseChildren(widget, true);
131 }
132 
setupWidget(QWidget * widget,KConfigSkeletonItem * item)133 void KConfigDialogManager::setupWidget(QWidget *widget, KConfigSkeletonItem *item)
134 {
135     QVariant minValue = item->minValue();
136     if (minValue.isValid()) {
137         // KSelector is using this property
138         if (widget->metaObject()->indexOfProperty("minValue") != -1) {
139             widget->setProperty("minValue", minValue);
140         }
141         if (widget->metaObject()->indexOfProperty("minimum") != -1) {
142             widget->setProperty("minimum", minValue);
143         }
144     }
145     QVariant maxValue = item->maxValue();
146     if (maxValue.isValid()) {
147         // KSelector is using this property
148         if (widget->metaObject()->indexOfProperty("maxValue") != -1) {
149             widget->setProperty("maxValue", maxValue);
150         }
151         if (widget->metaObject()->indexOfProperty("maximum") != -1) {
152             widget->setProperty("maximum", maxValue);
153         }
154     }
155 
156     if (widget->whatsThis().isEmpty()) {
157         QString whatsThis = item->whatsThis();
158         if (!whatsThis.isEmpty()) {
159             widget->setWhatsThis(whatsThis);
160         }
161     }
162 
163     if (widget->toolTip().isEmpty()) {
164         QString toolTip = item->toolTip();
165         if (!toolTip.isEmpty()) {
166             widget->setToolTip(toolTip);
167         }
168     }
169 
170     // If it is a QGroupBox with only autoExclusive buttons
171     // and has no custom property and the config item type
172     // is an integer, assume we want to save the index like we did with
173     // KButtonGroup instead of if it is checked or not
174     QGroupBox *gb = qobject_cast<QGroupBox *>(widget);
175     if (gb && getCustomProperty(gb).isEmpty()) {
176         const KConfigSkeletonItem *item = d->m_conf->findItem(widget->objectName().mid(5));
177         if (item->property().type() == QVariant::Int) {
178             QObjectList children = gb->children();
179             children.removeAll(gb->layout());
180             const QList<QAbstractButton *> buttons = gb->findChildren<QAbstractButton *>();
181             bool allAutoExclusiveDirectChildren = true;
182             for (QAbstractButton *button : buttons) {
183                 allAutoExclusiveDirectChildren = allAutoExclusiveDirectChildren && button->autoExclusive() && button->parent() == gb;
184             }
185             if (allAutoExclusiveDirectChildren) {
186                 d->allExclusiveGroupBoxes << widget;
187             }
188         }
189     }
190 
191     if (!item->isEqual(property(widget))) {
192         setProperty(widget, item->property());
193     }
194 
195     d->updateWidgetIndicator(item->name(), widget);
196 }
197 
parseChildren(const QWidget * widget,bool trackChanges)198 bool KConfigDialogManager::parseChildren(const QWidget *widget, bool trackChanges)
199 {
200     bool valueChanged = false;
201     const QList<QObject *> listOfChildren = widget->children();
202     if (listOfChildren.isEmpty()) { //?? XXX
203         return valueChanged;
204     }
205 
206     const QMetaMethod onWidgetModifiedSlot = metaObject()->method(metaObject()->indexOfSlot("onWidgetModified()"));
207     Q_ASSERT(onWidgetModifiedSlot.isValid() && metaObject()->indexOfSlot("onWidgetModified()") >= 0);
208 
209     for (QObject *object : listOfChildren) {
210         if (!object->isWidgetType()) {
211             continue; // Skip non-widgets
212         }
213 
214         QWidget *childWidget = static_cast<QWidget *>(object);
215 
216         QString widgetName = childWidget->objectName();
217         bool bParseChildren = true;
218         bool bSaveInsideGroupBox = d->insideGroupBox;
219 
220         if (widgetName.startsWith(QLatin1String("kcfg_"))) {
221             // This is one of our widgets!
222             QString configId = widgetName.mid(5);
223             KConfigSkeletonItem *item = d->m_conf->findItem(configId);
224             if (item) {
225                 d->knownWidget.insert(configId, childWidget);
226 
227                 setupWidget(childWidget, item);
228 
229                 if (trackChanges) {
230                     bool changeSignalFound = false;
231 
232                     if (d->allExclusiveGroupBoxes.contains(childWidget)) {
233                         const QList<QAbstractButton *> buttons = childWidget->findChildren<QAbstractButton *>();
234                         for (QAbstractButton *button : buttons) {
235                             connect(button, &QAbstractButton::toggled, this, [this] {
236                                 d->onWidgetModified();
237                             });
238                         }
239                     }
240 
241                     QByteArray propertyChangeSignal = getCustomPropertyChangedSignal(childWidget);
242                     if (propertyChangeSignal.isEmpty()) {
243                         propertyChangeSignal = getUserPropertyChangedSignal(childWidget);
244                     }
245 
246                     if (propertyChangeSignal.isEmpty()) {
247                         // get the change signal from the meta object
248                         const QMetaObject *metaObject = childWidget->metaObject();
249                         QByteArray userproperty = getCustomProperty(childWidget);
250                         if (userproperty.isEmpty()) {
251                             userproperty = getUserProperty(childWidget);
252                         }
253                         if (!userproperty.isEmpty()) {
254                             const int indexOfProperty = metaObject->indexOfProperty(userproperty);
255                             if (indexOfProperty != -1) {
256                                 const QMetaProperty property = metaObject->property(indexOfProperty);
257                                 const QMetaMethod notifySignal = property.notifySignal();
258                                 if (notifySignal.isValid()) {
259                                     connect(childWidget, notifySignal, this, onWidgetModifiedSlot);
260                                     changeSignalFound = true;
261                                 }
262                             }
263                         } else {
264                             qCWarning(KCONFIG_WIDGETS_LOG)
265                                 << "Don't know how to monitor widget '" << childWidget->metaObject()->className() << "' for changes!";
266                         }
267                     } else {
268                         connect(childWidget, propertyChangeSignal, this, SLOT(onWidgetModified()));
269                         changeSignalFound = true;
270                     }
271 
272                     if (changeSignalFound) {
273                         QComboBox *cb = qobject_cast<QComboBox *>(childWidget);
274                         if (cb && cb->isEditable()) {
275                             connect(cb, &QComboBox::editTextChanged, this, &KConfigDialogManager::widgetModified);
276                         }
277                     }
278                 }
279                 QGroupBox *gb = qobject_cast<QGroupBox *>(childWidget);
280                 if (!gb) {
281                     bParseChildren = false;
282                 } else {
283                     d->insideGroupBox = true;
284                 }
285             } else {
286                 qCWarning(KCONFIG_WIDGETS_LOG) << "A widget named '" << widgetName << "' was found but there is no setting named '" << configId << "'";
287             }
288         } else if (QLabel *label = qobject_cast<QLabel *>(childWidget)) {
289             QWidget *buddy = label->buddy();
290             if (!buddy) {
291                 continue;
292             }
293             QString buddyName = buddy->objectName();
294             if (buddyName.startsWith(QLatin1String("kcfg_"))) {
295                 // This is one of our widgets!
296                 QString configId = buddyName.mid(5);
297                 d->buddyWidget.insert(configId, childWidget);
298             }
299         }
300         // kf5: commented out to reduce debug output
301         // #ifndef NDEBUG
302         //     else if (!widgetName.isEmpty() && trackChanges)
303         //     {
304         //       QHash<QString, QByteArray>::const_iterator changedIt = s_changedMap()->constFind(childWidget->metaObject()->className());
305         //       if (changedIt != s_changedMap()->constEnd())
306         //       {
307         //         if ((!d->insideGroupBox || !qobject_cast<QRadioButton*>(childWidget)) &&
308         //             !qobject_cast<QGroupBox*>(childWidget) &&!qobject_cast<QTabWidget*>(childWidget) )
309         //           qCDebug(KCONFIG_WIDGETS_LOG) << "Widget '" << widgetName << "' (" << childWidget->metaObject()->className() << ") remains unmanaged.";
310         //       }
311         //     }
312         // #endif
313 
314         if (bParseChildren) {
315             // this widget is not known as something we can store.
316             // Maybe we can store one of its children.
317             valueChanged |= parseChildren(childWidget, trackChanges);
318         }
319         d->insideGroupBox = bSaveInsideGroupBox;
320     }
321     return valueChanged;
322 }
323 
updateWidgets()324 void KConfigDialogManager::updateWidgets()
325 {
326     bool changed = false;
327     bool bSignalsBlocked = signalsBlocked();
328     blockSignals(true);
329 
330     QWidget *widget;
331     QHashIterator<QString, QWidget *> it(d->knownWidget);
332     while (it.hasNext()) {
333         it.next();
334         widget = it.value();
335 
336         KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
337         if (!item) {
338             qCWarning(KCONFIG_WIDGETS_LOG) << "The setting '" << it.key() << "' has disappeared!";
339             continue;
340         }
341 
342         if (!item->isEqual(property(widget))) {
343             setProperty(widget, item->property());
344             //        qCDebug(KCONFIG_WIDGETS_LOG) << "The setting '" << it.key() << "' [" << widget->className() << "] has changed";
345             changed = true;
346         }
347         if (item->isImmutable()) {
348             widget->setEnabled(false);
349             QWidget *buddy = d->buddyWidget.value(it.key(), nullptr);
350             if (buddy) {
351                 buddy->setEnabled(false);
352             }
353         }
354     }
355     blockSignals(bSignalsBlocked);
356 
357     if (changed) {
358         QTimer::singleShot(0, this, &KConfigDialogManager::widgetModified);
359         d->updateAllWidgetIndicators();
360     }
361 }
362 
updateWidgetsDefault()363 void KConfigDialogManager::updateWidgetsDefault()
364 {
365     bool bUseDefaults = d->m_conf->useDefaults(true);
366     updateWidgets();
367     d->m_conf->useDefaults(bUseDefaults);
368     d->updateAllWidgetIndicators();
369 }
370 
setDefaultsIndicatorsVisible(bool enabled)371 void KConfigDialogManager::setDefaultsIndicatorsVisible(bool enabled)
372 {
373     d->setDefaultsIndicatorsVisible(enabled);
374 }
375 
updateSettings()376 void KConfigDialogManager::updateSettings()
377 {
378     bool changed = false;
379 
380     QWidget *widget;
381     QHashIterator<QString, QWidget *> it(d->knownWidget);
382     while (it.hasNext()) {
383         it.next();
384         widget = it.value();
385 
386         KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
387         if (!item) {
388             qCWarning(KCONFIG_WIDGETS_LOG) << "The setting '" << it.key() << "' has disappeared!";
389             continue;
390         }
391 
392         QVariant fromWidget = property(widget);
393         if (!item->isEqual(fromWidget)) {
394             item->setProperty(fromWidget);
395             changed = true;
396         }
397     }
398     if (changed) {
399         d->m_conf->save();
400         Q_EMIT settingsChanged();
401         d->updateAllWidgetIndicators();
402     }
403 }
404 
getUserProperty(const QWidget * widget) const405 QByteArray KConfigDialogManager::getUserProperty(const QWidget *widget) const
406 {
407     if (!s_propertyMap()->contains(widget->metaObject()->className())) {
408         const QMetaObject *metaObject = widget->metaObject();
409         const QMetaProperty user = metaObject->userProperty();
410         if (user.isValid()) {
411             s_propertyMap()->insert(widget->metaObject()->className(), user.name());
412             // qCDebug(KCONFIG_WIDGETS_LOG) << "class name: '" << widget->metaObject()->className()
413             //<< " 's USER property: " << metaProperty.name() << endl;
414         } else {
415             return QByteArray(); // no USER property
416         }
417     }
418     const QComboBox *cb = qobject_cast<const QComboBox *>(widget);
419     if (cb) {
420         const char *qcomboUserPropertyName = cb->QComboBox::metaObject()->userProperty().name();
421         const int qcomboUserPropertyIndex = qcomboUserPropertyName ? cb->QComboBox::metaObject()->indexOfProperty(qcomboUserPropertyName) : -1;
422         const char *widgetUserPropertyName = widget->metaObject()->userProperty().name();
423         const int widgetUserPropertyIndex = widgetUserPropertyName ? cb->metaObject()->indexOfProperty(widgetUserPropertyName) : -1;
424 
425         // no custom user property set on subclass of QComboBox?
426         if (qcomboUserPropertyIndex == widgetUserPropertyIndex) {
427             return QByteArray(); // use the q/kcombobox special code
428         }
429     }
430 
431     return s_propertyMap()->value(widget->metaObject()->className());
432 }
433 
getCustomProperty(const QWidget * widget) const434 QByteArray KConfigDialogManager::getCustomProperty(const QWidget *widget) const
435 {
436     QVariant prop(widget->property("kcfg_property"));
437     if (prop.isValid()) {
438         if (!prop.canConvert(QVariant::ByteArray)) {
439             qCWarning(KCONFIG_WIDGETS_LOG) << "kcfg_property on" << widget->metaObject()->className() << "is not of type ByteArray";
440         } else {
441             return prop.toByteArray();
442         }
443     }
444     return QByteArray();
445 }
446 
getUserPropertyChangedSignal(const QWidget * widget) const447 QByteArray KConfigDialogManager::getUserPropertyChangedSignal(const QWidget *widget) const
448 {
449     QHash<QString, QByteArray>::const_iterator changedIt = s_changedMap()->constFind(widget->metaObject()->className());
450 
451     if (changedIt == s_changedMap()->constEnd()) {
452         // If the class name of the widget wasn't in the monitored widgets map, then look for
453         // it again using the super class name. This fixes a problem with using QtRuby/Korundum
454         // widgets with KConfigXT where 'Qt::Widget' wasn't being seen a the real deal, even
455         // though it was a 'QWidget'.
456         if (widget->metaObject()->superClass()) {
457             changedIt = s_changedMap()->constFind(widget->metaObject()->superClass()->className());
458         }
459     }
460 
461     return (changedIt == s_changedMap()->constEnd()) ? QByteArray() : *changedIt;
462 }
463 
getCustomPropertyChangedSignal(const QWidget * widget) const464 QByteArray KConfigDialogManager::getCustomPropertyChangedSignal(const QWidget *widget) const
465 {
466     QVariant prop(widget->property("kcfg_propertyNotify"));
467     if (prop.isValid()) {
468         if (!prop.canConvert(QVariant::ByteArray)) {
469             qCWarning(KCONFIG_WIDGETS_LOG) << "kcfg_propertyNotify on" << widget->metaObject()->className() << "is not of type ByteArray";
470         } else {
471             return prop.toByteArray();
472         }
473     }
474     return QByteArray();
475 }
476 
setProperty(QWidget * w,const QVariant & v)477 void KConfigDialogManager::setProperty(QWidget *w, const QVariant &v)
478 {
479     if (d->allExclusiveGroupBoxes.contains(w)) {
480         const QList<QAbstractButton *> buttons = w->findChildren<QAbstractButton *>();
481         if (v.toInt() < buttons.count()) {
482             buttons[v.toInt()]->setChecked(true);
483         }
484         return;
485     }
486 
487     QByteArray userproperty = getCustomProperty(w);
488     if (userproperty.isEmpty()) {
489         userproperty = getUserProperty(w);
490     }
491     if (userproperty.isEmpty()) {
492         QComboBox *cb = qobject_cast<QComboBox *>(w);
493         if (cb) {
494             if (cb->isEditable()) {
495                 int i = cb->findText(v.toString());
496                 if (i != -1) {
497                     cb->setCurrentIndex(i);
498                 } else {
499                     cb->setEditText(v.toString());
500                 }
501             } else {
502                 cb->setCurrentIndex(v.toInt());
503             }
504             return;
505         }
506     }
507     if (userproperty.isEmpty()) {
508         qCWarning(KCONFIG_WIDGETS_LOG) << w->metaObject()->className() << " widget not handled!";
509         return;
510     }
511 
512     w->setProperty(userproperty, v);
513 }
514 
property(QWidget * w) const515 QVariant KConfigDialogManager::property(QWidget *w) const
516 {
517     if (d->allExclusiveGroupBoxes.contains(w)) {
518         const QList<QAbstractButton *> buttons = w->findChildren<QAbstractButton *>();
519         for (int i = 0; i < buttons.count(); ++i) {
520             if (buttons[i]->isChecked()) {
521                 return i;
522             }
523         }
524         return -1;
525     }
526 
527     QByteArray userproperty = getCustomProperty(w);
528     if (userproperty.isEmpty()) {
529         userproperty = getUserProperty(w);
530     }
531     if (userproperty.isEmpty()) {
532         QComboBox *cb = qobject_cast<QComboBox *>(w);
533         if (cb) {
534             if (cb->isEditable()) {
535                 return QVariant(cb->currentText());
536             } else {
537                 return QVariant(cb->currentIndex());
538             }
539         }
540     }
541     if (userproperty.isEmpty()) {
542         qCWarning(KCONFIG_WIDGETS_LOG) << w->metaObject()->className() << " widget not handled!";
543         return QVariant();
544     }
545 
546     return w->property(userproperty);
547 }
548 
hasChanged() const549 bool KConfigDialogManager::hasChanged() const
550 {
551     QWidget *widget;
552     QHashIterator<QString, QWidget *> it(d->knownWidget);
553     while (it.hasNext()) {
554         it.next();
555         widget = it.value();
556 
557         KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
558         if (!item) {
559             qCWarning(KCONFIG_WIDGETS_LOG) << "The setting '" << it.key() << "' has disappeared!";
560             continue;
561         }
562 
563         if (!item->isEqual(property(widget))) {
564             // qCDebug(KCONFIG_WIDGETS_LOG) << "Widget for '" << it.key() << "' has changed.";
565             return true;
566         }
567     }
568     return false;
569 }
570 
isDefault() const571 bool KConfigDialogManager::isDefault() const
572 {
573     QWidget *widget;
574     QHashIterator<QString, QWidget *> it(d->knownWidget);
575     while (it.hasNext()) {
576         it.next();
577         widget = it.value();
578 
579         KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
580         if (!item) {
581             qCWarning(KCONFIG_WIDGETS_LOG) << "The setting '" << it.key() << "' has disappeared!";
582             continue;
583         }
584 
585         if (property(widget) != item->getDefault()) {
586             return false;
587         }
588     }
589     return true;
590 }
591 
KConfigDialogManagerPrivate(KConfigDialogManager * qq)592 KConfigDialogManagerPrivate::KConfigDialogManagerPrivate(KConfigDialogManager *qq)
593     : q(qq)
594     , insideGroupBox(false)
595     , defaultsIndicatorsVisible(false)
596 {
597 }
598 
setDefaultsIndicatorsVisible(bool enabled)599 void KConfigDialogManagerPrivate::setDefaultsIndicatorsVisible(bool enabled)
600 {
601     if (defaultsIndicatorsVisible != enabled) {
602         defaultsIndicatorsVisible = enabled;
603         updateAllWidgetIndicators();
604     }
605 }
606 
onWidgetModified()607 void KConfigDialogManagerPrivate::onWidgetModified()
608 {
609     const auto widget = qobject_cast<QWidget *>(q->sender());
610     Q_ASSERT(widget);
611 
612     if (!widget->objectName().startsWith("kcfg_")) {
613         Q_ASSERT(widget->parent()->objectName().startsWith("kcfg_"));
614         const auto configId = widget->parent()->objectName().mid(5);
615         const auto parent = qobject_cast<QWidget *>(widget->parent());
616         Q_ASSERT(parent);
617         updateWidgetIndicator(configId, parent);
618     } else {
619         const auto configId = widget->objectName().mid(5);
620         updateWidgetIndicator(configId, widget);
621     }
622     Q_EMIT q->widgetModified();
623 }
624 
updateWidgetIndicator(const QString & configId,QWidget * widget)625 void KConfigDialogManagerPrivate::updateWidgetIndicator(const QString &configId, QWidget *widget)
626 {
627     const auto item = m_conf->findItem(configId);
628     Q_ASSERT(item);
629 
630     const auto widgetValue = q->property(widget);
631     const auto defaultValue = item->getDefault();
632 
633     const auto defaulted = widgetValue == defaultValue;
634 
635     if (allExclusiveGroupBoxes.contains(widget)) {
636         const QList<QAbstractButton *> buttons = widget->findChildren<QAbstractButton *>();
637         for (int i = 0; i < buttons.count(); i++) {
638             const auto value = widgetValue.toInt() == i && !defaulted && defaultsIndicatorsVisible;
639             buttons.at(i)->setProperty("_kde_highlight_neutral", value);
640             buttons.at(i)->update();
641         }
642     } else {
643         widget->setProperty("_kde_highlight_neutral", !defaulted && defaultsIndicatorsVisible);
644         widget->update();
645     }
646 }
647 
updateAllWidgetIndicators()648 void KConfigDialogManagerPrivate::updateAllWidgetIndicators()
649 {
650     QHashIterator<QString, QWidget *> it(knownWidget);
651     while (it.hasNext()) {
652         it.next();
653         updateWidgetIndicator(it.key(), it.value());
654     }
655 }
656 
657 #include "moc_kconfigdialogmanager.cpp"
658