diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a17f4a..dd5cbd6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,111 +1,112 @@ ########### kcmutils ############### set(kcmutils_LIB_SRCS kcmoduleinfo.cpp kcmoduleloader.cpp kcmoduleqml.cpp kcmultidialog.cpp kcmoduleproxy.cpp kpluginselector.cpp kcmodulecontainer.cpp ksettingswidgetadaptor.cpp ksettings/dispatcher.cpp ksettings/dialog.cpp ksettings/pluginpage.cpp ksettings/componentsdialog.cpp ) add_library(KF5KCMUtils ${kcmutils_LIB_SRCS}) generate_export_header(KF5KCMUtils BASE_NAME KCMUtils) target_include_directories(KF5KCMUtils INTERFACE "$") target_link_libraries(KF5KCMUtils PUBLIC Qt5::Widgets KF5::ConfigWidgets # KCModule KF5::Service # KService PRIVATE Qt5::DBus # dbus usage in kcmoduleproxy.cpp Qt5::Qml Qt5::Quick Qt5::QuickWidgets KF5::CoreAddons KF5::I18n KF5::IconThemes # KIconLoader KF5::ItemViews # KWidgetItemDelegate KF5::XmlGui # KAboutApplicationDialog KF5::QuickAddons + KF5::Declarative KF5::Package ) set_target_properties(KF5KCMUtils PROPERTIES VERSION ${KCMUTILS_VERSION_STRING} SOVERSION ${KCMUTILS_SOVERSION} EXPORT_NAME KCMUtils) ecm_generate_headers(KCMUtils_HEADERS HEADER_NAMES KCModuleInfo KCModuleLoader KCMultiDialog KCModuleProxy KPluginSelector KCModuleContainer REQUIRED_HEADERS KCMUtils_HEADERS ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kcmutils_export.h ${KCMUtils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KCMUtils COMPONENT Devel ) ecm_generate_headers(KSettings_HEADERS HEADER_NAMES Dispatcher Dialog PluginPage RELATIVE ksettings REQUIRED_HEADERS KSettings_HEADERS ) install(FILES ${KSettings_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KCMUtils/ksettings COMPONENT Devel ) install(TARGETS KF5KCMUtils EXPORT KF5KCMUtilsTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES kcmodule.desktop kcmoduleinit.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR} ) if(BUILD_QCH) ecm_add_qch( KF5KCMUtils_QCH NAME KCMUtils BASE_NAME KF5KCMUtils VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KCMUtils_HEADERS} ${KSettings_HEADERS} ksettings/README.dox MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS Qt5Widgets_QCH KF5ConfigWidgets_QCH KF5Service_QCH BLANK_MACROS KCMUTILS_EXPORT KCMUTILS_DEPRECATED KCMUTILS_DEPRECATED_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KCMUtils LIB_NAME KF5KCMUtils DEPS "widgets KConfigWidgets KService" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KCMUtils) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/kcmoduleqml.cpp b/src/kcmoduleqml.cpp index 9d113a0..836d19a 100644 --- a/src/kcmoduleqml.cpp +++ b/src/kcmoduleqml.cpp @@ -1,240 +1,296 @@ /* This file is part of the KDE Project Copyright (c) 2014 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcmoduleqml_p.h" #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include +#include class KCModuleQmlPrivate { public: - KCModuleQmlPrivate(KQuickAddons::ConfigModule *cm) - : quickWindow(nullptr), + KCModuleQmlPrivate(KQuickAddons::ConfigModule *cm, KCModuleQml *q) + : q(q), + quickWindow(nullptr), configModule(cm) { } + ~KCModuleQmlPrivate() + { + } + + KCModuleQml *q; QQuickWindow *quickWindow; QQuickWidget *quickWidget; QQuickItem *rootPlaceHolder; + QQuickItem *pageRow; KQuickAddons::ConfigModule *configModule; + KDeclarative::QmlObjectSharedEngine *qmlObject; }; KCModuleQml::KCModuleQml(KQuickAddons::ConfigModule *configModule, QWidget* parent, const QVariantList& args) : KCModule(parent, args), - d(new KCModuleQmlPrivate(configModule)) + d(new KCModuleQmlPrivate(configModule, this)) { connect(configModule, &KQuickAddons::ConfigModule::quickHelpChanged, this, &KCModuleQml::quickHelpChanged); //HACK:Here is important those two enums keep having the exact same values //but the kdeclarative one can't use the KCModule's enum setButtons((KCModule::Buttons)(int)d->configModule->buttons()); connect(configModule, &KQuickAddons::ConfigModule::buttonsChanged, [=] { setButtons((KCModule::Buttons)(int)d->configModule->buttons()); }); if (d->configModule->needsSave()) { emit changed(true); } connect(configModule, &KQuickAddons::ConfigModule::needsSaveChanged, [=] { emit changed(d->configModule->needsSave()); }); setNeedsAuthorization(d->configModule->needsAuthorization()); connect(configModule, &KQuickAddons::ConfigModule::needsAuthorizationChanged, [=] { setNeedsAuthorization(d->configModule->needsAuthorization()); }); setRootOnlyMessage(d->configModule->rootOnlyMessage()); setUseRootOnlyMessage(d->configModule->useRootOnlyMessage()); connect(configModule, &KQuickAddons::ConfigModule::rootOnlyMessageChanged, [=] { setRootOnlyMessage(d->configModule->rootOnlyMessage()); }); connect(configModule, &KQuickAddons::ConfigModule::useRootOnlyMessageChanged, [=] { setUseRootOnlyMessage(d->configModule->useRootOnlyMessage()); }); if (!d->configModule->authActionName().isEmpty()) { setAuthAction(KAuth::Action(d->configModule->authActionName())); } connect(configModule, &KQuickAddons::ConfigModule::authActionNameChanged, [=] { setAuthAction(d->configModule->authActionName()); }); + setAboutData(d->configModule->aboutData()); setFocusPolicy(Qt::StrongFocus); -} -KCModuleQml::~KCModuleQml() -{ - delete d; -} -void KCModuleQml::showEvent(QShowEvent *event) -{ - if (d->quickWindow || !d->configModule->mainUi()) { - KCModule::showEvent(event); - return; - } + //Build the UI QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); - d->quickWidget = new QQuickWidget(d->configModule->engine(), this); + d->qmlObject = new KDeclarative::QmlObjectSharedEngine(this); + d->quickWidget = new QQuickWidget(d->qmlObject->engine(), this); d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); d->quickWidget->setFocusPolicy(Qt::StrongFocus); d->quickWidget->installEventFilter(this); d->quickWindow = d->quickWidget->quickWindow(); d->quickWindow->setColor(QGuiApplication::palette().window().color()); connect(qApp, &QGuiApplication::paletteChanged, d->quickWindow, [=]() { d->quickWindow->setColor(QGuiApplication::palette().window().color()); }); - QQmlComponent *component = new QQmlComponent(d->configModule->engine(), this); + QQmlComponent *component = new QQmlComponent(d->qmlObject->engine(), this); //this has activeFocusOnTab to notice when the navigation wraps //around, so when we need to go outside and inside + //pushPage/popPage are needed as push of StackView can't be directly invoked from c++ + //because its parameters are QQmlV4Function which is not public //the managers of onEnter/ReturnPressed are a workaround of //Qt bug https://bugreports.qt.io/browse/QTBUG-70934 - component->setData(QByteArrayLiteral("import QtQuick 2.3\nItem{activeFocusOnTab:true;Keys.onReturnPressed:{event.accepted=true}Keys.onEnterPressed:{event.accepted=true}}"), QUrl()); + component->setData(QByteArrayLiteral("import QtQuick 2.3\n" + "import org.kde.kirigami 2.4 as Kirigami\n" + "Kirigami.ApplicationItem{" + //purely cosmetic space + "header: Item {height: Kirigami.Units.largeSpacing}" + // allow only one column by default + "pageStack.defaultColumnWidth:width;" + "pageStack.separatorVisible:false;" + "pageStack.globalToolBar.style: pageStack.wideScreen ? Kirigami.ApplicationHeaderStyle.Titles : Kirigami.ApplicationHeaderStyle.Breadcrumb;" + "pageStack.globalToolBar.showNavigationButtons:false;" + "pageStack.globalToolBar.preferredHeight:Kirigami.Units.gridUnit*1.6;" + "pageStack.globalToolBar.separatorVisible:false;" + "activeFocusOnTab:true;" + "Keys.onReturnPressed:{event.accepted=true}" + "Keys.onEnterPressed:{event.accepted=true}" + "}"), QUrl()); + d->rootPlaceHolder = qobject_cast(component->create()); d->quickWidget->setContent(QUrl(), component, d->rootPlaceHolder); - d->configModule->mainUi()->setParentItem(d->quickWidget->rootObject()); - - //set anchors - QQmlExpression expr(d->configModule->engine()->rootContext(), d->configModule->mainUi(), QStringLiteral("parent")); - QQmlProperty prop(d->configModule->mainUi(), QStringLiteral("anchors.fill")); - prop.write(expr.evaluate()); + d->pageRow = d->rootPlaceHolder->property("pageStack").value(); + if (d->pageRow) { + QMetaObject::invokeMethod(d->pageRow, "push", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(d->configModule->mainUi())), Q_ARG(QVariant, QVariant())); + connect(d->configModule, &KQuickAddons::ConfigModule::pagePushed, this, [this](QQuickItem *page) { + QMetaObject::invokeMethod(d->pageRow, "push", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(page)), Q_ARG(QVariant, QVariant())); + } + ); + connect(d->configModule, &KQuickAddons::ConfigModule::pageRemoved, this, [this]() { + QMetaObject::invokeMethod(d->pageRow, "pop", Qt::DirectConnection, Q_ARG(QVariant, QVariant())); + } + ); + + auto syncColumnWidth = [this](){ + d->pageRow->setProperty("defaultColumnWidth", d->configModule->columnWidth() > 0 ? d->configModule->columnWidth() : d->rootPlaceHolder->width()); + }; + syncColumnWidth(); + + connect(d->configModule, &KQuickAddons::ConfigModule::columnWidthChanged, + this, syncColumnWidth); + connect(d->rootPlaceHolder, &QQuickItem::widthChanged, + this, syncColumnWidth); + + //HACK: in order to work with old Systemsettings + //search if we are in a KPageWidget, search ofr its page, and if it has + //an header set, disable our own title + //FIXME: eventually remove this hack + QObject *candidate = this; + while (candidate) { + candidate = candidate->parent(); + KPageWidget *page = qobject_cast(candidate); + if (page && !page->currentPage()->header().isEmpty()) { + QObject *globalToolBar = d->pageRow->property("globalToolBar").value(); + //5 is None + globalToolBar->setProperty("style", 5); + } + } + } + layout->addWidget(d->quickWidget); - KCModule::showEvent(event); +} + +KCModuleQml::~KCModuleQml() +{ + delete d; } bool KCModuleQml::eventFilter(QObject* watched, QEvent* event) { //FIXME: those are all workarounds around the QQuickWidget brokeness //BUG https://bugreports.qt.io/browse/QTBUG-64561 if (watched == d->quickWidget && event->type() == QEvent::KeyPress) { //allow tab navigation inside the qquickwidget QKeyEvent *ke = static_cast(event); QQuickItem *currentItem = d->quickWindow->activeFocusItem(); if (!currentItem) { return KCModule::eventFilter(watched, event); } if (currentItem->scopedFocusItem()) { currentItem = currentItem->scopedFocusItem(); } if (ke->key() == Qt::Key_Tab) { //nextItemInFocusChain will always return something, in the worst case will still be currentItem QQuickItem *nextItem = currentItem->nextItemInFocusChain(true); //when it arrives at the place holder item, go out of the qqw and //go to the other widgets around systemsettigns if (nextItem == d->rootPlaceHolder) { QWidget *w = d->quickWidget->nextInFocusChain(); while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) { w = w->nextInFocusChain(); } w->setFocus(Qt::TabFocusReason); } else { nextItem->forceActiveFocus(Qt::TabFocusReason); } return true; } else if (ke->key() == Qt::Key_Backtab || (ke->key() == Qt::Key_Tab && (ke->modifiers() & Qt::ShiftModifier))) { QQuickItem *nextItem = currentItem->nextItemInFocusChain(false); if (nextItem == d->rootPlaceHolder) { QWidget *w = d->quickWidget->previousInFocusChain(); while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) { w = w->previousInFocusChain(); } w->setFocus(Qt::BacktabFocusReason); } else { nextItem->forceActiveFocus(Qt::BacktabFocusReason); } return true; } } return KCModule::eventFilter(watched, event); } void KCModuleQml::focusInEvent(QFocusEvent *event) { Q_UNUSED(event) if (event->reason() == Qt::TabFocusReason) { d->rootPlaceHolder->nextItemInFocusChain(true)->forceActiveFocus(Qt::TabFocusReason); } else if (event->reason() == Qt::BacktabFocusReason) { d->rootPlaceHolder->nextItemInFocusChain(false)->forceActiveFocus(Qt::BacktabFocusReason); } } QSize KCModuleQml::sizeHint() const { if (!d->configModule->mainUi()) { return QSize(); } return QSize(d->configModule->mainUi()->implicitWidth(), d->configModule->mainUi()->implicitHeight()); } QString KCModuleQml::quickHelp() const { return d->configModule->quickHelp(); } const KAboutData *KCModuleQml::aboutData() const { return d->configModule->aboutData(); } void KCModuleQml::load() { d->configModule->load(); } void KCModuleQml::save() { d->configModule->save(); d->configModule->setNeedsSave(false); } void KCModuleQml::defaults() { d->configModule->defaults(); } #include "moc_kcmoduleqml_p.cpp" diff --git a/src/kcmultidialog.cpp b/src/kcmultidialog.cpp index 2fd7cb8..1fde03e 100644 --- a/src/kcmultidialog.cpp +++ b/src/kcmultidialog.cpp @@ -1,540 +1,545 @@ /* Copyright (c) 2000 Matthias Elter Copyright (c) 2003 Daniel Molkentin Copyright (c) 2003,2006 Matthias Kretz Copyright (c) 2004 Frans Englich Copyright (c) 2006 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcmultidialog.h" #include "kcmultidialog_p.h" +#include "kcmoduleqml_p.h" #include "kcmoduleproxy.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool KCMultiDialogPrivate::resolveChanges(KCModuleProxy *currentProxy) { Q_Q(KCMultiDialog); if (!currentProxy || !currentProxy->changed()) { return true; } // Let the user decide const int queryUser = KMessageBox::warningYesNoCancel( q, i18n("The settings of the current module have changed.\n" "Do you want to apply the changes or discard them?"), i18n("Apply Settings"), KStandardGuiItem::apply(), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); switch (queryUser) { case KMessageBox::Yes: return moduleSave(currentProxy); case KMessageBox::No: currentProxy->load(); return true; case KMessageBox::Cancel: return false; default: Q_ASSERT(false); return false; } } void KCMultiDialogPrivate::_k_slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem *previous) { Q_Q(KCMultiDialog); // qDebug(); q->blockSignals(true); q->setCurrentPage(previous); KCModuleProxy *previousModule = nullptr; for (int i = 0; i < modules.count(); ++i) { if (modules[ i ].item == previous) { previousModule = modules[ i ].kcm; break; } } if (resolveChanges(previousModule)) { q->setCurrentPage(current); } q->blockSignals(false); // We need to get the state of the now active module _k_clientChanged(); } void KCMultiDialogPrivate::_k_clientChanged() { Q_Q(KCMultiDialog); // qDebug(); // Get the current module KCModuleProxy *activeModule = nullptr; for (int i = 0; i < modules.count(); ++i) { if (modules[ i ].item == q->currentPage()) { activeModule = modules[ i ].kcm; break; } } bool change = false; if (activeModule) { change = activeModule->changed(); QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply); if (applyButton) { q->disconnect(applyButton, &QAbstractButton::clicked, q, &KCMultiDialog::slotApplyClicked); delete applyButton->findChild(); applyButton->setEnabled(change); } QPushButton *okButton = q->buttonBox()->button(QDialogButtonBox::Ok); if (okButton) { q->disconnect(okButton, &QAbstractButton::clicked, q, &KCMultiDialog::slotOkClicked); delete okButton->findChild(); } if (activeModule->realModule()->needsAuthorization()) { if (applyButton) { KAuth::ObjectDecorator *decorator = new KAuth::ObjectDecorator(applyButton); decorator->setAuthAction(activeModule->realModule()->authAction()); activeModule->realModule()->authAction().setParentWidget(activeModule->realModule()); q->connect(decorator, &KAuth::ObjectDecorator::authorized, q, &KCMultiDialog::slotApplyClicked); } if (okButton) { KAuth::ObjectDecorator *decorator = new KAuth::ObjectDecorator(okButton); decorator->setAuthAction(activeModule->realModule()->authAction()); activeModule->realModule()->authAction().setParentWidget(activeModule->realModule()); q->connect(decorator, &KAuth::ObjectDecorator::authorized, q, &KCMultiDialog::slotOkClicked); } } else { if (applyButton) { q->connect(applyButton, &QAbstractButton::clicked, q, &KCMultiDialog::slotApplyClicked); delete applyButton->findChild(); } if (okButton) { q->connect(okButton, &QAbstractButton::clicked, q, &KCMultiDialog::slotOkClicked); delete okButton->findChild(); } } } QPushButton *resetButton = q->buttonBox()->button(QDialogButtonBox::Reset); if (resetButton) { resetButton->setEnabled(change); } QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply); if (applyButton) { applyButton->setEnabled(change); } if (activeModule) { QPushButton *helpButton = q->buttonBox()->button(QDialogButtonBox::Help); if (helpButton) { helpButton->setEnabled(activeModule->buttons() & KCModule::Help); } QPushButton *defaultButton = q->buttonBox()->button(QDialogButtonBox::RestoreDefaults); if (defaultButton) { defaultButton->setEnabled(activeModule->buttons() & KCModule::Default); } } } void KCMultiDialogPrivate::_k_updateHeader(bool use, const QString &message) { Q_Q(KCMultiDialog); KPageWidgetItem *item = q->currentPage(); KCModuleProxy *kcm = qobject_cast(item->widget()); if (use) { item->setHeader(QStringLiteral("") + kcm->moduleInfo().comment() + QStringLiteral("
") + message + QStringLiteral("")); item->setIcon(KDE::icon(kcm->moduleInfo().icon(), QStringList() << QStringLiteral("dialog-warning"))); } else { item->setHeader(kcm->moduleInfo().comment()); item->setIcon(QIcon::fromTheme(kcm->moduleInfo().icon())); } } void KCMultiDialogPrivate::init() { Q_Q(KCMultiDialog); q->setFaceType(KPageDialog::Auto); q->setWindowTitle(i18n("Configure")); q->setModal(false); QDialogButtonBox *buttonBox = new QDialogButtonBox(q); buttonBox->setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Cancel | QDialogButtonBox::Apply | QDialogButtonBox::Ok | QDialogButtonBox::Reset); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::apply()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Reset), KStandardGuiItem::reset()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Help), KStandardGuiItem::help()); buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false); buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); q->connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, q, &KCMultiDialog::slotApplyClicked); q->connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, q, &KCMultiDialog::slotOkClicked); q->connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, q, &KCMultiDialog::slotDefaultClicked); q->connect(buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked, q, &KCMultiDialog::slotHelpClicked); q->connect(buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, q, &KCMultiDialog::slotUser1Clicked); q->setButtonBox(buttonBox); q->connect(q, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), SLOT(_k_slotCurrentPageChanged(KPageWidgetItem*,KPageWidgetItem*))); } KCMultiDialog::KCMultiDialog(QWidget *parent) : KPageDialog(parent) , d_ptr(new KCMultiDialogPrivate(this)) { d_func()->init(); } KCMultiDialog::KCMultiDialog(KPageWidget *pageWidget, QWidget *parent, Qt::WindowFlags flags) : KPageDialog(pageWidget, parent, flags) , d_ptr(new KCMultiDialogPrivate(this)) { d_func()->init(); } KCMultiDialog::KCMultiDialog(KCMultiDialogPrivate &dd, KPageWidget *pageWidget, QWidget *parent, Qt::WindowFlags flags) : KPageDialog(pageWidget, parent, flags) , d_ptr(&dd) { d_func()->init(); } KCMultiDialog::~KCMultiDialog() { delete d_ptr; } void KCMultiDialog::showEvent(QShowEvent *ev) { KPageDialog::showEvent(ev); adjustSize(); /** * adjustSize() relies on sizeHint but is limited to 2/3 of the desktop size * Workaround for https://bugreports.qt.io/browse/QTBUG-3459 * * We adjust the size after passing the show event * because otherwise window pos is set to (0,0) */ const QSize maxSize = QApplication::desktop()->availableGeometry(pos()).size(); resize(qMin(sizeHint().width(), maxSize.width()), qMin(sizeHint().height(), maxSize.height())); } void KCMultiDialog::slotDefaultClicked() { Q_D(KCMultiDialog); const KPageWidgetItem *item = currentPage(); if (!item) { return; } for (int i = 0; i < d->modules.count(); ++i) { if (d->modules[ i ].item == item) { d->modules[ i ].kcm->defaults(); d->_k_clientChanged(); return; } } } void KCMultiDialog::slotUser1Clicked() { const KPageWidgetItem *item = currentPage(); if (!item) { return; } Q_D(KCMultiDialog); for (int i = 0; i < d->modules.count(); ++i) { if (d->modules[ i ].item == item) { d->modules[ i ].kcm->load(); d->_k_clientChanged(); return; } } } bool KCMultiDialogPrivate::moduleSave(KCModuleProxy *module) { if (!module) { return false; } module->save(); return true; } void KCMultiDialogPrivate::apply() { Q_Q(KCMultiDialog); QStringList updatedComponents; foreach (const CreatedModule &module, modules) { KCModuleProxy *proxy = module.kcm; if (proxy->changed()) { proxy->save(); /** * Add name of the components the kcm belongs to the list * of updated components. */ const QStringList componentNames = module.componentNames; foreach (const QString &componentName, module.componentNames) { if (!updatedComponents.contains(componentName)) { updatedComponents.append(componentName); } } } } // Send the configCommitted signal for every updated component. foreach (const QString &name, updatedComponents) { emit q->configCommitted(name.toLatin1()); } emit q->configCommitted(); } void KCMultiDialog::slotApplyClicked() { QPushButton *applyButton = buttonBox()->button(QDialogButtonBox::Apply); applyButton->setFocus(); d_func()->apply(); } void KCMultiDialog::slotOkClicked() { QPushButton *okButton = buttonBox()->button(QDialogButtonBox::Ok); okButton->setFocus(); d_func()->apply(); accept(); } void KCMultiDialog::slotHelpClicked() { const KPageWidgetItem *item = currentPage(); if (!item) { return; } Q_D(KCMultiDialog); QString docPath; for (int i = 0; i < d->modules.count(); ++i) { if (d->modules[ i ].item == item) { docPath = d->modules[ i ].kcm->moduleInfo().docPath(); break; } } const QUrl docUrl = QUrl(QStringLiteral("help:/")).resolved(QUrl(docPath)); // same code as in KHelpClient::invokeHelp const QString docUrlScheme = docUrl.scheme(); if (docUrlScheme == QLatin1String("help") || docUrlScheme == QLatin1String("man") || docUrlScheme == QLatin1String("info")) { QProcess::startDetached(QStringLiteral("khelpcenter"), QStringList() << docUrl.toString()); } else { QDesktopServices::openUrl(docUrl); } } void KCMultiDialog::closeEvent(QCloseEvent *event) { Q_D(KCMultiDialog); KPageDialog::closeEvent(event); /** * If we don't delete them, the DBUS registration stays, and trying to load the KCMs * in other situations will lead to "module already loaded in Foo," while to the user * doesn't appear so(the dialog is hidden) */ Q_FOREACH(auto &proxy, d->modules) { proxy.kcm->deleteClient(); } } KPageWidgetItem *KCMultiDialog::addModule(const QString &path, const QStringList &args) { QString complete = path; if (!path.endsWith(QLatin1String(".desktop"))) { complete += QStringLiteral(".desktop"); } KService::Ptr service = KService::serviceByStorageId(complete); return addModule(KCModuleInfo(service), nullptr, args); } KPageWidgetItem *KCMultiDialog::addModule(const KCModuleInfo &moduleInfo, KPageWidgetItem *parentItem, const QStringList &args) { if (!moduleInfo.service()) { return nullptr; } //KAuthorized::authorizeControlModule( moduleInfo.service()->menuId() ) is //checked in noDisplay already if (moduleInfo.service()->noDisplay()) { return nullptr; } // Create the scroller auto *moduleScroll = new UnboundScrollArea(this); // Prepare the scroll area moduleScroll->setWidgetResizable(true); moduleScroll->setFrameStyle(QFrame::NoFrame); moduleScroll->viewport()->setAutoFillBackground(false); KCModuleProxy *kcm = new KCModuleProxy(moduleInfo, moduleScroll, args); moduleScroll->setWidget(kcm); // qDebug() << moduleInfo.moduleName(); KPageWidgetItem *item = new KPageWidgetItem(moduleScroll, moduleInfo.moduleName()); + if (qobject_cast(kcm->realModule())) { + item->setHeaderVisible(false); + } + if (kcm->realModule() && kcm->realModule()->useRootOnlyMessage()) { item->setHeader(QStringLiteral("") + moduleInfo.comment() + QStringLiteral("
") + kcm->realModule()->rootOnlyMessage() + QStringLiteral("")); item->setIcon(KDE::icon(moduleInfo.icon(), QStringList() << QStringLiteral("dialog-warning"))); } else { item->setHeader(moduleInfo.comment()); item->setIcon(QIcon::fromTheme(moduleInfo.icon())); } item->setProperty("_k_weight", moduleInfo.weight()); bool updateCurrentPage = false; const KPageWidgetModel *model = qobject_cast(pageWidget()->model()); Q_ASSERT(model); if (parentItem) { const QModelIndex parentIndex = model->index(parentItem); const int siblingCount = model->rowCount(parentIndex); int row = 0; for (; row < siblingCount; ++row) { KPageWidgetItem *siblingItem = model->item(parentIndex.child(row, 0)); if (siblingItem->property("_k_weight").toInt() > moduleInfo.weight()) { // the item we found is heavier than the new module // qDebug() << "adding KCM " << item->name() << " before " << siblingItem->name(); insertPage(siblingItem, item); break; } } if (row >= siblingCount) { // the new module is either the first or the heaviest item // qDebug() << "adding KCM " << item->name() << " with parent " << parentItem->name(); addSubPage(parentItem, item); } } else { const int siblingCount = model->rowCount(); int row = 0; for (; row < siblingCount; ++row) { KPageWidgetItem *siblingItem = model->item(model->index(row, 0)); if (siblingItem->property("_k_weight").toInt() > moduleInfo.weight()) { // the item we found is heavier than the new module // qDebug() << "adding KCM " << item->name() << " before " << siblingItem->name(); insertPage(siblingItem, item); if (siblingItem == currentPage()) { updateCurrentPage = true; } break; } } if (row == siblingCount) { // the new module is either the first or the heaviest item // qDebug() << "adding KCM " << item->name() << " at the top level"; addPage(item); } } connect(kcm, SIGNAL(changed(bool)), this, SLOT(_k_clientChanged())); connect(kcm->realModule(), SIGNAL(rootOnlyMessageChanged(bool,QString)), this, SLOT(_k_updateHeader(bool,QString))); Q_D(KCMultiDialog); KCMultiDialogPrivate::CreatedModule cm; cm.kcm = kcm; cm.item = item; cm.componentNames = moduleInfo.service()->property(QStringLiteral("X-KDE-ParentComponents")).toStringList(); d->modules.append(cm); if (d->modules.count() == 1 || updateCurrentPage) { setCurrentPage(item); d->_k_clientChanged(); } return item; } void KCMultiDialog::clear() { Q_D(KCMultiDialog); // qDebug() ; for (int i = 0; i < d->modules.count(); ++i) { removePage(d->modules[ i ].item); delete d->modules[ i ].kcm; } d->modules.clear(); d->_k_clientChanged(); } #include "moc_kcmultidialog.cpp"