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 | |
28 | class KConfigDialogPrivate |
29 | { |
30 | public: |
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 &); |
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 | |
97 | std::vector<KConfigDialogPrivate::OpenDialogInfo> KConfigDialogPrivate::s_openDialogs; |
98 | |
99 | KConfigDialog::KConfigDialog(QWidget *parent, const QString &name, KCoreConfigSkeleton *config) |
100 | : KPageDialog(parent) |
101 | , d(new KConfigDialogPrivate(name, config, this)) |
102 | { |
103 | } |
104 | |
105 | KConfigDialog::~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 | |
118 | KPageWidgetItem *KConfigDialog::addPage(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &, 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 | |
141 | KPageWidgetItem *KConfigDialog::addPage(QWidget *page, KCoreConfigSkeleton *config, const QString &itemName, const QString &pixmapName, const QString &) |
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 | |
164 | KPageWidgetItem *KConfigDialogPrivate::addPageInternal(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &) |
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 | |
200 | void 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 | |
216 | void 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 | |
230 | void 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 | |
244 | void KConfigDialog::(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 | |
257 | KConfigDialog *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 | |
267 | bool KConfigDialog::showDialog(const QString &name) |
268 | { |
269 | KConfigDialog *dialog = exists(name); |
270 | if (dialog) { |
271 | dialog->show(); |
272 | } |
273 | return (dialog != nullptr); |
274 | } |
275 | |
276 | void 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 | |
291 | void KConfigDialogPrivate::settingsChangedSlot() |
292 | { |
293 | // Update the buttons |
294 | updateButtons(); |
295 | Q_EMIT q->settingsChanged(q->objectName()); |
296 | } |
297 | |
298 | void 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 | |
315 | void KConfigDialog::updateSettings() |
316 | { |
317 | } |
318 | |
319 | void KConfigDialog::updateWidgets() |
320 | { |
321 | } |
322 | |
323 | void KConfigDialog::updateWidgetsDefault() |
324 | { |
325 | } |
326 | |
327 | bool KConfigDialog::hasChanged() |
328 | { |
329 | return false; |
330 | } |
331 | |
332 | bool KConfigDialog::isDefault() |
333 | { |
334 | return true; |
335 | } |
336 | |
337 | void KConfigDialog::updateButtons() |
338 | { |
339 | d->updateButtons(); |
340 | } |
341 | |
342 | void KConfigDialog::settingsChangedSlot() |
343 | { |
344 | d->settingsChangedSlot(); |
345 | } |
346 | |
347 | void KConfigDialog::setHelp(const QString &anchor, const QString &appname) |
348 | { |
349 | d->mAnchor = anchor; |
350 | d->mHelpApp = appname; |
351 | } |
352 | |
353 | void KConfigDialog::showHelp() |
354 | { |
355 | KHelpClient::invokeHelp(anchor: d->mAnchor, appname: d->mHelpApp); |
356 | } |
357 | |
358 | #include "moc_kconfigdialog.cpp" |
359 | |