| 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 | |