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: 2004 Michael Brade <brade@kde.org>
6 SPDX-FileCopyrightText: 2021 Ahmad Samir <a.samirh78@gmail.com>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "kconfigdialog.h"
12
13#include <KCoreConfigSkeleton>
14#include <KLocalizedString>
15#include <KPageWidgetModel>
16#include <kconfigdialogmanager.h>
17#include <khelpclient.h>
18
19#include <QDialogButtonBox>
20#include <QIcon>
21#include <QPushButton>
22#include <QScrollArea>
23#include <QScrollBar>
24#include <QVBoxLayout>
25
26#include <vector>
27
28class KConfigDialogPrivate
29{
30public:
31 KConfigDialogPrivate(const QString &name, KCoreConfigSkeleton *config, KConfigDialog *qq)
32 : q(qq)
33 {
34 const QString dialogName = !name.isEmpty() ? name : QString::asprintf(format: "SettingsDialog-%p", static_cast<void *>(q));
35
36 q->setObjectName(dialogName);
37 q->setWindowTitle(i18nc("@title:window", "Configure"));
38 q->setFaceType(KPageDialog::List);
39 s_openDialogs.push_back(x: {.dialogName: dialogName, .dialog: q});
40
41 QDialogButtonBox *buttonBox = q->buttonBox();
42 buttonBox->setStandardButtons(QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel
43 | QDialogButtonBox::Help);
44 QObject::connect(sender: buttonBox->button(which: QDialogButtonBox::Ok), signal: &QAbstractButton::clicked, context: q, slot: &KConfigDialog::updateSettings);
45 QObject::connect(sender: buttonBox->button(which: QDialogButtonBox::Apply), signal: &QAbstractButton::clicked, context: q, slot: &KConfigDialog::updateSettings);
46 QObject::connect(sender: buttonBox->button(which: QDialogButtonBox::Apply), signal: &QAbstractButton::clicked, context: q, slot: [this]() {
47 updateButtons();
48 });
49 QObject::connect(sender: buttonBox->button(which: QDialogButtonBox::Cancel), signal: &QAbstractButton::clicked, context: q, slot: &KConfigDialog::updateWidgets);
50 QObject::connect(sender: buttonBox->button(which: QDialogButtonBox::RestoreDefaults), signal: &QAbstractButton::clicked, context: q, slot: &KConfigDialog::updateWidgetsDefault);
51 QObject::connect(sender: buttonBox->button(which: QDialogButtonBox::RestoreDefaults), signal: &QAbstractButton::clicked, context: q, slot: [this]() {
52 updateButtons();
53 });
54 QObject::connect(sender: buttonBox->button(which: QDialogButtonBox::Help), signal: &QAbstractButton::clicked, context: q, slot: &KConfigDialog::showHelp);
55
56 QObject::connect(sender: q, signal: &KPageDialog::pageRemoved, context: q, slot: &KConfigDialog::onPageRemoved);
57
58 manager = new KConfigDialogManager(q, config);
59 setupManagerConnections(manager);
60
61 if (QPushButton *applyButton = q->buttonBox()->button(which: QDialogButtonBox::Apply)) {
62 applyButton->setEnabled(false);
63 };
64 }
65
66 KPageWidgetItem *addPageInternal(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header);
67
68 void setupManagerConnections(KConfigDialogManager *manager);
69
70 void updateApplyButton();
71 void updateDefaultsButton();
72 void updateButtons();
73 void settingsChangedSlot();
74
75 KConfigDialog *const q;
76 QString mAnchor;
77 QString mHelpApp;
78 bool shown = false;
79 KConfigDialogManager *manager = nullptr;
80
81 struct WidgetManager {
82 QWidget *widget = nullptr;
83 KConfigDialogManager *manager = nullptr;
84 };
85 std::vector<WidgetManager> m_managerForPage;
86
87 /**
88 * The list of existing dialogs.
89 */
90 struct OpenDialogInfo {
91 QString dialogName;
92 KConfigDialog *dialog = nullptr;
93 };
94 static std::vector<OpenDialogInfo> s_openDialogs;
95};
96
97std::vector<KConfigDialogPrivate::OpenDialogInfo> KConfigDialogPrivate::s_openDialogs;
98
99KConfigDialog::KConfigDialog(QWidget *parent, const QString &name, KCoreConfigSkeleton *config)
100 : KPageDialog(parent)
101 , d(new KConfigDialogPrivate(name, config, this))
102{
103}
104
105KConfigDialog::~KConfigDialog()
106{
107 auto &openDlgs = KConfigDialogPrivate::s_openDialogs;
108 const QString currentObjectName = objectName();
109 auto it = std::find_if(first: openDlgs.cbegin(), last: openDlgs.cend(), pred: [=](const KConfigDialogPrivate::OpenDialogInfo &info) {
110 return currentObjectName == info.dialogName;
111 });
112
113 if (it != openDlgs.cend()) {
114 openDlgs.erase(position: it);
115 }
116}
117
118KPageWidgetItem *KConfigDialog::addPage(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header, bool manage)
119{
120 Q_ASSERT(page);
121 if (!page) {
122 return nullptr;
123 }
124
125 KPageWidgetItem *item = d->addPageInternal(page, itemName, pixmapName, header);
126 if (manage) {
127 d->manager->addWidget(widget: page);
128 }
129
130 if (d->shown && manage) {
131 // update the default button if the dialog is shown
132 QPushButton *defaultButton = buttonBox()->button(which: QDialogButtonBox::RestoreDefaults);
133 if (defaultButton) {
134 bool is_default = defaultButton->isEnabled() && d->manager->isDefault();
135 defaultButton->setEnabled(!is_default);
136 }
137 }
138 return item;
139}
140
141KPageWidgetItem *KConfigDialog::addPage(QWidget *page, KCoreConfigSkeleton *config, const QString &itemName, const QString &pixmapName, const QString &header)
142{
143 Q_ASSERT(page);
144 if (!page) {
145 return nullptr;
146 }
147
148 KPageWidgetItem *item = d->addPageInternal(page, itemName, pixmapName, header);
149 auto *manager = new KConfigDialogManager(page, config);
150 d->m_managerForPage.push_back(x: {.widget: page, .manager: manager});
151 d->setupManagerConnections(manager);
152
153 if (d->shown) {
154 // update the default button if the dialog is shown
155 QPushButton *defaultButton = buttonBox()->button(which: QDialogButtonBox::RestoreDefaults);
156 if (defaultButton) {
157 const bool is_default = defaultButton->isEnabled() && manager->isDefault();
158 defaultButton->setEnabled(!is_default);
159 }
160 }
161 return item;
162}
163
164KPageWidgetItem *KConfigDialogPrivate::addPageInternal(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header)
165{
166 QWidget *frame = new QWidget(q);
167 QVBoxLayout *boxLayout = new QVBoxLayout(frame);
168 boxLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
169 boxLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
170
171 QScrollArea *scroll = new QScrollArea(q);
172 scroll->setFrameShape(QFrame::NoFrame);
173 scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
174 scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
175 scroll->setWidget(page);
176 scroll->setWidgetResizable(true);
177 scroll->setSizePolicy(hor: QSizePolicy::MinimumExpanding, ver: QSizePolicy::MinimumExpanding);
178
179 if (page->minimumSizeHint().height() > scroll->sizeHint().height() - 2) {
180 if (page->sizeHint().width() < scroll->sizeHint().width() + 2) {
181 // QScrollArea is planning only a vertical scroll bar,
182 // try to avoid the horizontal one by reserving space for the vertical one.
183 // Currently KPageViewPrivate::_k_modelChanged() queries the minimumSizeHint().
184 // We can only set the minimumSize(), so this approach relies on QStackedWidget size calculation.
185 scroll->setMinimumWidth(scroll->sizeHint().width() + qBound(min: 0, val: scroll->verticalScrollBar()->sizeHint().width(), max: 200) + 4);
186 }
187 }
188
189 boxLayout->addWidget(scroll);
190 KPageWidgetItem *item = new KPageWidgetItem(frame, itemName);
191 item->setHeader(header);
192 if (!pixmapName.isEmpty()) {
193 item->setIcon(QIcon::fromTheme(name: pixmapName));
194 }
195
196 q->KPageDialog::addPage(item);
197 return item;
198}
199
200void KConfigDialogPrivate::setupManagerConnections(KConfigDialogManager *manager)
201{
202 q->connect(sender: manager, signal: qOverload<>(&KConfigDialogManager::settingsChanged), context: q, slot: [this]() {
203 settingsChangedSlot();
204 });
205 q->connect(sender: manager, signal: &KConfigDialogManager::widgetModified, context: q, slot: [this]() {
206 updateButtons();
207 });
208
209 QDialogButtonBox *buttonBox = q->buttonBox();
210 q->connect(sender: buttonBox->button(which: QDialogButtonBox::Ok), signal: &QPushButton::clicked, context: manager, slot: &KConfigDialogManager::updateSettings);
211 q->connect(sender: buttonBox->button(which: QDialogButtonBox::Apply), signal: &QPushButton::clicked, context: manager, slot: &KConfigDialogManager::updateSettings);
212 q->connect(sender: buttonBox->button(which: QDialogButtonBox::Cancel), signal: &QPushButton::clicked, context: manager, slot: &KConfigDialogManager::updateWidgets);
213 q->connect(sender: buttonBox->button(which: QDialogButtonBox::RestoreDefaults), signal: &QPushButton::clicked, context: manager, slot: &KConfigDialogManager::updateWidgetsDefault);
214}
215
216void KConfigDialogPrivate::updateApplyButton()
217{
218 QPushButton *applyButton = q->buttonBox()->button(which: QDialogButtonBox::Apply);
219 if (!applyButton) {
220 return;
221 }
222
223 const bool hasManagerChanged = std::any_of(first: m_managerForPage.cbegin(), last: m_managerForPage.cend(), pred: [](const WidgetManager &widgetManager) {
224 return widgetManager.manager->hasChanged();
225 });
226
227 applyButton->setEnabled(manager->hasChanged() || q->hasChanged() || hasManagerChanged);
228}
229
230void KConfigDialogPrivate::updateDefaultsButton()
231{
232 QPushButton *restoreDefaultsButton = q->buttonBox()->button(which: QDialogButtonBox::RestoreDefaults);
233 if (!restoreDefaultsButton) {
234 return;
235 }
236
237 const bool isManagerDefaulted = std::all_of(first: m_managerForPage.cbegin(), last: m_managerForPage.cend(), pred: [](const WidgetManager &widgetManager) {
238 return widgetManager.manager->isDefault();
239 });
240
241 restoreDefaultsButton->setDisabled(manager->isDefault() && q->isDefault() && isManagerDefaulted);
242}
243
244void KConfigDialog::onPageRemoved(KPageWidgetItem *item)
245{
246 auto it = std::find_if(first: d->m_managerForPage.cbegin(), last: d->m_managerForPage.cend(), pred: [item](const KConfigDialogPrivate::WidgetManager &wm) {
247 return item->widget()->isAncestorOf(child: wm.widget);
248 });
249
250 if (it != d->m_managerForPage.cend()) { // There is a manager for this page, so remove it
251 delete it->manager;
252 d->m_managerForPage.erase(position: it);
253 d->updateButtons();
254 }
255}
256
257KConfigDialog *KConfigDialog::exists(const QString &name)
258{
259 auto &openDlgs = KConfigDialogPrivate::s_openDialogs;
260 auto it = std::find_if(first: openDlgs.cbegin(), last: openDlgs.cend(), pred: [name](const KConfigDialogPrivate::OpenDialogInfo &info) {
261 return name == info.dialogName;
262 });
263
264 return it != openDlgs.cend() ? it->dialog : nullptr;
265}
266
267bool KConfigDialog::showDialog(const QString &name)
268{
269 KConfigDialog *dialog = exists(name);
270 if (dialog) {
271 dialog->show();
272 }
273 return (dialog != nullptr);
274}
275
276void KConfigDialogPrivate::updateButtons()
277{
278 static bool only_once = false;
279 if (only_once) {
280 return;
281 }
282 only_once = true;
283
284 updateApplyButton();
285 updateDefaultsButton();
286
287 Q_EMIT q->widgetModified();
288 only_once = false;
289}
290
291void KConfigDialogPrivate::settingsChangedSlot()
292{
293 // Update the buttons
294 updateButtons();
295 Q_EMIT q->settingsChanged(q->objectName());
296}
297
298void KConfigDialog::showEvent(QShowEvent *e)
299{
300 if (!d->shown) {
301 updateWidgets();
302 d->manager->updateWidgets();
303 for (auto [widget, manager] : d->m_managerForPage) {
304 manager->updateWidgets();
305 }
306
307 d->updateApplyButton();
308 d->updateDefaultsButton();
309
310 d->shown = true;
311 }
312 KPageDialog::showEvent(e);
313}
314
315void KConfigDialog::updateSettings()
316{
317}
318
319void KConfigDialog::updateWidgets()
320{
321}
322
323void KConfigDialog::updateWidgetsDefault()
324{
325}
326
327bool KConfigDialog::hasChanged()
328{
329 return false;
330}
331
332bool KConfigDialog::isDefault()
333{
334 return true;
335}
336
337void KConfigDialog::updateButtons()
338{
339 d->updateButtons();
340}
341
342void KConfigDialog::settingsChangedSlot()
343{
344 d->settingsChangedSlot();
345}
346
347void KConfigDialog::setHelp(const QString &anchor, const QString &appname)
348{
349 d->mAnchor = anchor;
350 d->mHelpApp = appname;
351}
352
353void KConfigDialog::showHelp()
354{
355 KHelpClient::invokeHelp(anchor: d->mAnchor, appname: d->mHelpApp);
356}
357
358#include "moc_kconfigdialog.cpp"
359

source code of kconfigwidgets/src/kconfigdialog.cpp