diff --git a/autotests/kcmoduleinfotest.cpp b/autotests/kcmoduleinfotest.cpp index 8b2dbae..9e7bc6a 100644 --- a/autotests/kcmoduleinfotest.cpp +++ b/autotests/kcmoduleinfotest.cpp @@ -1,88 +1,104 @@ /* This file is part of the KDE Frameworks Copyright (c) 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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 #include #include +#include #include #include #include class KCModuleInfoTest : public QObject { Q_OBJECT private Q_SLOTS: void testExternalApp(); void testFakeKCM(); void testDesktopFileKCM(); + void testInvalidKCM(); }; void KCModuleInfoTest::testExternalApp() { const QString yast = QFINDTESTDATA("YaST-systemsettings.desktop"); QVERIFY(!yast.isEmpty()); KCModuleInfo info(yast); QVERIFY(info.service()); + QVERIFY(info.isValid()); } void KCModuleInfoTest::testFakeKCM() { // Similar to kontact's code const QVector pluginMetaDatas = KPluginLoader::findPlugins( QStringLiteral("testplugins"), [](const KPluginMetaData &) { return true; }); const QList pluginInfos = KPluginInfo::fromMetaData(pluginMetaDatas); QVERIFY(pluginInfos.count() > 0); KPluginInfo pluginInfo = pluginInfos.at(0); QVERIFY(pluginInfo.isValid()); // WHEN KCModuleInfo info(pluginInfo); // like Dialog::addPluginInfos does // THEN + QVERIFY(info.isValid()); QCOMPARE(info.pluginInfo().name(), QStringLiteral("Test")); QCOMPARE(QFileInfo(info.library()).fileName(), QStringLiteral("jsonplugin.so")); QCOMPARE(QFileInfo(info.fileName()).fileName(), QStringLiteral("jsonplugin.so")); QCOMPARE(info.icon(), QStringLiteral("view-pim-mail")); QCOMPARE(info.comment(), QStringLiteral("Test plugin")); QCOMPARE(info.docPath(), QStringLiteral("doc/path")); QVERIFY(!info.service()); } void KCModuleInfoTest::testDesktopFileKCM() { const QString desktopFile = QFINDTESTDATA("desktopfilekcm/kcmtest.desktop"); QVERIFY(!desktopFile.isEmpty()); // WHEN KCModuleInfo info(desktopFile); // THEN + QVERIFY(info.isValid()); QVERIFY(info.service()); QVERIFY(!info.pluginInfo().isValid()); QCOMPARE(QFileInfo(info.library()).fileName(), QStringLiteral("kcm_kded")); QCOMPARE(QFileInfo(info.fileName()).fileName(), QStringLiteral("kcmtest.desktop")); QCOMPARE(info.icon(), QStringLiteral("preferences-system-session-services")); QCOMPARE(info.comment(), QStringLiteral("Configure background services")); QCOMPARE(info.docPath(), QStringLiteral("kcontrol/kded/index.html")); + + // WHEN actually loading the module + KCMultiDialog dlg; + QVERIFY(dlg.addModule(info)); +} + +void KCModuleInfoTest::testInvalidKCM() +{ + KCModuleInfo info(QStringLiteral("doest_not_exist.desktop")); + QVERIFY(!info.isValid()); + QVERIFY(!info.service()); } QTEST_MAIN(KCModuleInfoTest) #include "kcmoduleinfotest.moc" diff --git a/src/kcmoduleinfo.cpp b/src/kcmoduleinfo.cpp index 8f27b9c..faea919 100644 --- a/src/kcmoduleinfo.cpp +++ b/src/kcmoduleinfo.cpp @@ -1,256 +1,270 @@ /* Copyright (c) 1999 Matthias Hoelzer-Kluepfel Copyright (c) 2000 Matthias Elter Copyright (c) 2003 Daniel Molkentin Copyright (c) 2003,2006 Matthias Kretz This file is part of the KDE project 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 "kcmoduleinfo.h" #include #include #include #include #include class Q_DECL_HIDDEN KCModuleInfo::Private { public: Private(); Private(const KPluginInfo &); Private(const KService::Ptr &); QStringList keywords; QString name, icon, lib, handle, fileName, doc, comment; bool allLoaded = false; int weight = 100; // For real C++ plugins KPluginInfo pluginInfo; // Can be a C++ plugin, or just a desktop file launching an executable (see autotest) KService::Ptr service; /** * Reads the service entries specific for KCModule from the desktop file. * The usual desktop entries are read in the Private ctor. */ void loadAll(); }; KCModuleInfo::Private::Private() { } KCModuleInfo::Private::Private(const KPluginInfo &pluginInfo) : allLoaded(false) , pluginInfo(pluginInfo) { if (!pluginInfo.isValid()) { qWarning() << "Invalid plugin"; return; } // set the modules simple attributes name = pluginInfo.name(); comment = pluginInfo.comment(); icon = pluginInfo.icon(); fileName = pluginInfo.entryPath(); lib = pluginInfo.libraryPath(); keywords = pluginInfo.property(QStringLiteral("Keywords")).toStringList(); } KCModuleInfo::Private::Private(const KService::Ptr &service) : allLoaded(false), pluginInfo(), service(service) { if (!service) { return; } name = service->name(); comment = service->comment(); icon = service->icon(); fileName = service->entryPath(); lib = service->library(); keywords = service->keywords(); } KCModuleInfo::KCModuleInfo() : d(new Private) { } KCModuleInfo::KCModuleInfo(const QString &desktopFile) : d(new Private(KService::serviceByStorageId(desktopFile))) { } KCModuleInfo::KCModuleInfo(KService::Ptr service) : d(new Private(service)) { } KCModuleInfo::KCModuleInfo(const KPluginInfo &pluginInfo) : d(new Private(pluginInfo)) { } KCModuleInfo::KCModuleInfo(const KCModuleInfo &rhs) : d(new Private) { (*this) = rhs; } KCModuleInfo &KCModuleInfo::operator=(const KCModuleInfo &rhs) { *d = *(rhs.d); return *this; } bool KCModuleInfo::operator==(const KCModuleInfo &rhs) const { return ((d->name == rhs.d->name) && (d->lib == rhs.d->lib) && (d->fileName == rhs.d->fileName)); } bool KCModuleInfo::operator!=(const KCModuleInfo &rhs) const { return ! operator==(rhs); } KCModuleInfo::~KCModuleInfo() { delete d; } +bool KCModuleInfo::isValid() const +{ + return d->pluginInfo.isValid() || d->service; +} + void KCModuleInfo::Private::loadAll() { allLoaded = true; if (!pluginInfo.isValid() && !service) { /* We have a bogus service. All get functions will return empty/zero values */ return; } if (service) { // get the documentation path doc = service->property(QStringLiteral("X-DocPath"), QVariant::String).toString(); if (doc.isEmpty()) { doc = service->property(QStringLiteral("DocPath"), QVariant::String).toString(); } // read weight QVariant tmp = service->property(QStringLiteral("X-KDE-Weight"), QVariant::Int); weight = tmp.isValid() ? tmp.toInt() : 100; // factory handle tmp = service->property(QStringLiteral("X-KDE-FactoryName"), QVariant::String); handle = tmp.isValid() ? tmp.toString() : lib; } else { // get the documentation path doc = pluginInfo.property(QStringLiteral("X-DocPath")).toString(); if (doc.isEmpty()) { doc = pluginInfo.property(QStringLiteral("DocPath")).toString(); } // read weight QVariant tmp = pluginInfo.property(QStringLiteral("X-KDE-Weight")).toInt(); weight = tmp.isValid() ? tmp.toInt() : 100; // factory handle tmp = pluginInfo.property(QStringLiteral("X-KDE-FactoryName")); handle = tmp.isValid() ? tmp.toString() : lib; } } QString KCModuleInfo::fileName() const { return d->fileName; } QStringList KCModuleInfo::keywords() const { return d->keywords; } QString KCModuleInfo::moduleName() const { return d->name; } KService::Ptr KCModuleInfo::service() const { if (d->service) { return d->service; } if (!d->pluginInfo.isValid()) { return {}; } return d->pluginInfo.service(); } KPluginInfo KCModuleInfo::pluginInfo() const { return d->pluginInfo; } QString KCModuleInfo::comment() const { return d->comment; } QString KCModuleInfo::icon() const { return d->icon; } QString KCModuleInfo::library() const { return d->lib; } QString KCModuleInfo::docPath() const { if (!d->allLoaded) { d->loadAll(); } return d->doc; } QString KCModuleInfo::handle() const { if (!d->allLoaded) { d->loadAll(); } return d->handle; } int KCModuleInfo::weight() const { if (!d->allLoaded) { d->loadAll(); } return d->weight; } +QVariant KCModuleInfo::property(const QString &key) const +{ + if (d->service) { + return d->service->property(key); + } else { + return d->pluginInfo.property(key); + } +} + diff --git a/src/kcmoduleinfo.h b/src/kcmoduleinfo.h index 4dc998e..529ba33 100644 --- a/src/kcmoduleinfo.h +++ b/src/kcmoduleinfo.h @@ -1,179 +1,193 @@ /* Copyright (c) 1999 Matthias Hoelzer-Kluepfel Copyright (c) 2000 Matthias Elter Copyright (c) 2003 Daniel Molkentin Copyright (c) 2003,2006 Matthias Kretz This file is part of the KDE project 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. */ #ifndef KCMODULEINFO_H #define KCMODULEINFO_H #include #include class KPluginInfo; class QString; class QStringList; /** * A class that provides information about a KCModule * * KCModuleInfo provides various technical information, such as icon, library * etc. about a KCModule.n * @note Any values set with the set* functions is not * written back with KCModuleInfo it only reads value from the desktop file. * * @internal * @author Matthias Hoelzer-Kluepfel * @author Matthias Elter * @author Daniel Molkentin * */ class KCMUTILS_EXPORT KCModuleInfo // krazy:exclude=dpointer (implicitly shared) { public: /** * Constructs a KCModuleInfo. * @note a KCModuleInfo object will have to be manually deleted, it is not * done automatically for you. * @param desktopFile the desktop file representing the module, or * the name of the module. */ KCModuleInfo(const QString &desktopFile); /** * Same as above but takes a KPluginInfo as argument. * This allows to encapsulate both the case of KService (desktop file) * and the case of KPluginMetaData (JSon data in .so file) under the same API. * * @param pluginInfo specifies the module * @since 5.70 */ KCModuleInfo(const KPluginInfo &pluginInfo); /** * Same as above but takes a KService::Ptr as argument. * * @note @p moduleInfo must be a valid pointer. * * @param moduleInfo specifies the module */ KCModuleInfo(KService::Ptr moduleInfo); /** * Copy constructor * @param rhs specifies the module info to copy */ KCModuleInfo(const KCModuleInfo &rhs); /** * Same as above but creates an empty KCModuleInfo. * You should not normally call this. */ KCModuleInfo(); /** * Assignment operator */ KCModuleInfo &operator=(const KCModuleInfo &rhs); /** * Returns true if @p rhs describes the same KCModule as this object. */ bool operator==(const KCModuleInfo &rhs) const; /** * @return true if @p rhs is not equal itself */ bool operator!=(const KCModuleInfo &rhs) const; /** * Default destructor. */ ~KCModuleInfo(); + /** + * Returns true if the KCM was found + * @since 5.71 + */ + bool isValid() const; + /** * @return the filename of the .desktop file that describes the KCM */ QString fileName() const; /** * @return the keywords associated with this KCM. */ QStringList keywords() const; /** * @return the module\'s (translated) name */ QString moduleName() const; /** * @return a QExplicitlySharedDataPointer to KService created from the modules .desktop file * @warning This will be null if this KCModuleInfo was created from a KPluginInfo coming from KPluginMetaData. * Prefer using pluginInfo() instead, which works for both kinds. */ KService::Ptr service() const; // TODO KF6 REMOVE /** * @return the KPluginInfo containing more information about this module * @since 5.70 */ KPluginInfo pluginInfo() const; /** * @return the module's (translated) comment field */ QString comment() const; /** * @return the module's icon name */ QString icon() const; /** * @return the path of the module's documentation */ QString docPath() const; /** * @return the library name */ QString library() const; /** * @return a handle (the contents of the X-KDE-FactoryName field if it exists, * else the same as the library name) */ QString handle() const; /** * @return the weight of the module which determines the order of the pages in * the KCMultiDialog. It's set by the X-KDE-Weight field. */ int weight() const; + /** + * @return The value associated to the @p key. You can use it if you + * want to read custom values. To do this you need to define + * your own servicetype and add it to the ServiceTypes keys. + * @since 5.71 + */ + QVariant property(const QString &key) const; + private: class Private; Private *d; }; #endif // KCMODULEINFO_H diff --git a/src/kcmoduleloader.cpp b/src/kcmoduleloader.cpp index 197023e..113da6f 100644 --- a/src/kcmoduleloader.cpp +++ b/src/kcmoduleloader.cpp @@ -1,182 +1,184 @@ /* Copyright (c) 1999 Matthias Hoelzer-Kluepfel Copyright (c) 2000 Matthias Elter Copyright (c) 2003,2004,2006 Matthias Kretz Copyright (c) 2004 Frans Englich This file is part of the KDE project 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 "kcmoduleloader.h" #include "kcmoduleqml_p.h" #include #include #include #include #include #include #include #include #include #include using namespace KCModuleLoader; /***************************************************************/ /** * When something goes wrong in loading the module, this one * jumps in as a "dummy" module. */ class KCMError : public KCModule { public: KCMError(const QString &msg, const QString &details, QWidget *parent) : KCModule(parent) { QVBoxLayout *topLayout = new QVBoxLayout(this); QLabel *lab = new QLabel(msg, this); lab->setWordWrap(true); topLayout->addWidget(lab); lab = new QLabel(details, this); lab->setWordWrap(true); topLayout->addWidget(lab); } }; /***************************************************************/ KCModule *KCModuleLoader::loadModule(const QString &module, ErrorReporting report, QWidget *parent, const QStringList &args) { return loadModule(KCModuleInfo(module), report, parent, args); } KCModule *KCModuleLoader::loadModule(const KCModuleInfo &mod, ErrorReporting report, QWidget *parent, const QStringList &args) { /* * Simple libraries as modules are the easiest case: * We just have to load the library and get the module * from the factory. */ - if (!mod.pluginInfo().isValid()) + if (!mod.isValid()) { return reportError(report, i18n("The module %1 could not be found.", mod.moduleName()), i18n("

The diagnosis is:
The desktop file %1 could not be found.

", mod.fileName()), parent); - if (mod.service() && mod.service()->noDisplay()) + } + if (mod.service() && mod.service()->noDisplay()) { return reportError(report, i18n("The module %1 is disabled.", mod.moduleName()), i18n("

Either the hardware/software the module configures is not available or the module has been disabled by the administrator.

"), parent); + } if (!mod.library().isEmpty()) { QString error; QVariantList args2; args2.reserve(args.count()); for (const QString &arg : args) { args2 << arg; } KCModule *module = nullptr; KPluginLoader loader(KPluginLoader::findPlugin(QLatin1String("kcms/") + mod.library())); KPluginFactory* factory = loader.factory(); if (!factory) { // KF6 TODO: make this a warning, and remove mention of fallback qDebug() << "Couldn't load plugin" << QLatin1String("kcms/") + mod.library() << ":" << loader.errorString() << " -- falling back to old-style loading from desktop file"; } else { std::unique_ptr cm(factory->create(nullptr, args2)); if (!cm) { qWarning() << "Error creating object from plugin" << loader.fileName(); } else { if (!cm->mainUi()) { return reportError(report, i18n("Error loading QML file."), cm->errorString(), parent); } module = new KCModuleQml(std::move(cm), parent, args2); return module; } } // KF6 TODO: remove this compat block if (mod.service()) { module = mod.service()->createInstance(parent, args2, &error); } if (module) { return module; } else //#ifndef NDEBUG { // KF6 TODO: remove this old compat block // get the create_ function QLibrary lib(KPluginLoader::findPlugin(mod.library())); if (lib.load()) { KCModule *(*create)(QWidget *, const char *); QByteArray factorymethod("create_"); factorymethod += mod.handle().toLatin1(); create = reinterpret_cast(lib.resolve(factorymethod.constData())); if (create) { return create(parent, mod.handle().toLatin1().constData()); } else { qWarning() << "This module has no valid entry symbol at all. The reason could be that it's still using K_EXPORT_COMPONENT_FACTORY with a custom X-KDE-FactoryName which is not supported anymore"; } lib.unload(); } } //#endif // NDEBUG return reportError(report, error, QString(), parent); } /* * Ok, we could not load the library. * Try to run it as an executable. * This must not be done when calling from kcmshell, or you'll * have infinite recursion * (startService calls kcmshell which calls modloader which calls startService...) * */ return reportError(report, i18n("The module %1 is not a valid configuration module.", mod.moduleName()), i18n("The diagnosis is:
The desktop file %1 does not specify a library.
", mod.fileName()), parent); } void KCModuleLoader::unloadModule(const KCModuleInfo &mod) { // get the library loader instance KPluginLoader loader(mod.library()); loader.unload(); } KCModule *KCModuleLoader::reportError(ErrorReporting report, const QString &text, const QString &details, QWidget *parent) { QString realDetails = details; if (realDetails.isNull()) { realDetails = i18n("

Possible reasons:

  • An error occurred during your last " "system upgrade, leaving an orphaned control module behind
  • You have old third party " "modules lying around.

Check these points carefully and try to remove " "the module mentioned in the error message. If this fails, consider contacting " "your distributor or packager.

"); } if (report & KCModuleLoader::Dialog) { KMessageBox::detailedError(parent, text, realDetails); } if (report & KCModuleLoader::Inline) { return new KCMError(text, realDetails, parent); } return nullptr; } diff --git a/src/kcmultidialog.cpp b/src/kcmultidialog.cpp index 82d0fb1..49e9137 100644 --- a/src/kcmultidialog.cpp +++ b/src/kcmultidialog.cpp @@ -1,582 +1,582 @@ /* 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 #ifndef KCONFIGWIDGETS_NO_KAUTH #include #include #endif #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); KCModuleProxy *previousModule = nullptr; for (int i = 0; i < modules.count(); ++i) { if (modules[i].item == previous) { previousModule = modules[i].kcm; } } // Remove margins for the buttonbox; KPageDialog handles that already q->buttonBox()->setContentsMargins(0, 0, 0, 0); q->blockSignals(true); q->setCurrentPage(previous); 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; bool defaulted = false; if (activeModule) { change = activeModule->changed(); defaulted = activeModule->defaulted(); QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply); if (applyButton) { q->disconnect(applyButton, &QAbstractButton::clicked, q, &KCMultiDialog::slotApplyClicked); #ifndef KCONFIGWIDGETS_NO_KAUTH delete applyButton->findChild(); #endif } QPushButton *okButton = q->buttonBox()->button(QDialogButtonBox::Ok); if (okButton) { q->disconnect(okButton, &QAbstractButton::clicked, q, &KCMultiDialog::slotOkClicked); #ifndef KCONFIGWIDGETS_NO_KAUTH delete okButton->findChild(); #endif } #ifndef KCONFIGWIDGETS_NO_KAUTH 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(); } } #endif } auto buttons = activeModule ? activeModule->buttons() : KCModule::NoAdditionalButton; QPushButton *resetButton = q->buttonBox()->button(QDialogButtonBox::Reset); if (resetButton) { resetButton->setVisible(buttons & KCModule::Apply); resetButton->setEnabled(change); } QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply); if (applyButton) { applyButton->setVisible(buttons & KCModule::Apply); applyButton->setEnabled(change); } QPushButton *cancelButton = q->buttonBox()->button(QDialogButtonBox::Cancel); if (cancelButton) { cancelButton->setVisible(buttons & KCModule::Apply); } QPushButton *okButton = q->buttonBox()->button(QDialogButtonBox::Ok); if (okButton) { okButton->setVisible(buttons & KCModule::Apply); } QPushButton *closeButton = q->buttonBox()->button(QDialogButtonBox::Close); if (closeButton) { closeButton->setHidden(buttons & KCModule::Apply); } QPushButton *helpButton = q->buttonBox()->button(QDialogButtonBox::Help); if (helpButton) { helpButton->setVisible(buttons & KCModule::Help); } QPushButton *defaultButton = q->buttonBox()->button(QDialogButtonBox::RestoreDefaults); if (defaultButton) { defaultButton->setVisible(buttons & KCModule::Default); defaultButton->setEnabled(!defaulted); } } 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().moduleName() + QStringLiteral("
") + message + QStringLiteral("")); item->setIcon(KIconUtils::addOverlay(QIcon::fromTheme(kcm->moduleInfo().icon()), QIcon::fromTheme(QStringLiteral("dialog-warning")), Qt::BottomRightCorner)); } else { item->setHeader(kcm->moduleInfo().moduleName()); 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::Close | 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::Close), KStandardGuiItem::close()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Reset), KStandardGuiItem::reset()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Help), KStandardGuiItem::help()); buttonBox->button(QDialogButtonBox::Close)->setVisible(false); 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) */ QScreen *screen = QApplication::screenAt(pos()); if (screen) { const QSize maxSize = screen->availableGeometry().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; for (const CreatedModule &module : qAsConst(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; for (const QString &componentName : componentNames) { if (!updatedComponents.contains(componentName)) { updatedComponents.append(componentName); } } } } // Send the configCommitted signal for every updated component. for (const QString &name : qAsConst(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) */ for(auto &proxy : qAsConst(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) { Q_D(KCMultiDialog); - if (!moduleInfo.pluginInfo().isValid()) { + if (!moduleInfo.isValid()) { return nullptr; } //KAuthorized::authorizeControlModule( moduleInfo.service()->menuId() ) is //checked in noDisplay already if (moduleInfo.service() && 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()); KCMultiDialogPrivate::CreatedModule cm; cm.kcm = kcm; cm.item = item; - cm.componentNames = moduleInfo.pluginInfo().property(QStringLiteral("X-KDE-ParentComponents")).toStringList(); + cm.componentNames = moduleInfo.property(QStringLiteral("X-KDE-ParentComponents")).toStringList(); d->modules.append(cm); if (qobject_cast(kcm->realModule())) { item->setHeaderVisible(false); } if (kcm->realModule() && kcm->realModule()->useRootOnlyMessage()) { item->setHeader(QStringLiteral("") + moduleInfo.moduleName() + QStringLiteral("
") + kcm->realModule()->rootOnlyMessage() + QStringLiteral("")); item->setIcon(KIconUtils::addOverlay(QIcon::fromTheme(moduleInfo.icon()), QIcon::fromTheme(QStringLiteral("dialog-warning")), Qt::BottomRightCorner)); } else { item->setHeader(moduleInfo.moduleName()); 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(model->index(row, 0, parentIndex)); 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))); 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" diff --git a/src/kpluginselector.cpp b/src/kpluginselector.cpp index 9788e36..3477fc2 100644 --- a/src/kpluginselector.cpp +++ b/src/kpluginselector.cpp @@ -1,927 +1,927 @@ /** * This file is part of the KDE project * Copyright (C) 2007, 2006 Rafael Fernández López * Copyright (C) 2002-2003 Matthias Kretz * * 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 "kpluginselector.h" #include "kpluginselector_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MARGIN 5 KPluginSelector::Private::Private(KPluginSelector *parent) : QObject(parent) , parent(parent) , listView(nullptr) , categoryDrawer(nullptr) , showIcons(false) { } KPluginSelector::Private::~Private() { } void KPluginSelector::Private::updateDependencies(PluginEntry *pluginEntry, bool added) { if (added) { QStringList dependencyList = pluginEntry->pluginInfo.dependencies(); if (dependencyList.isEmpty()) { return; } for (int i = 0; i < pluginModel->rowCount(); i++) { const QModelIndex index = pluginModel->index(i, 0); PluginEntry *pe = static_cast(index.internalPointer()); if ((pe->pluginInfo.pluginName() != pluginEntry->pluginInfo.pluginName()) && dependencyList.contains(pe->pluginInfo.pluginName()) && !pe->checked) { dependenciesWidget->addDependency(pe->pluginInfo.name(), pluginEntry->pluginInfo.name(), added); const_cast(index.model())->setData(index, added, Qt::CheckStateRole); updateDependencies(pe, added); } } } else { for (int i = 0; i < pluginModel->rowCount(); i++) { const QModelIndex index = pluginModel->index(i, 0); PluginEntry *pe = static_cast(index.internalPointer()); if ((pe->pluginInfo.pluginName() != pluginEntry->pluginInfo.pluginName()) && pe->pluginInfo.dependencies().contains(pluginEntry->pluginInfo.pluginName()) && pe->checked) { dependenciesWidget->addDependency(pe->pluginInfo.name(), pluginEntry->pluginInfo.name(), added); const_cast(index.model())->setData(index, added, Qt::CheckStateRole); updateDependencies(pe, added); } } } } int KPluginSelector::Private::dependantLayoutValue(int value, int width, int totalWidth) const { if (listView->layoutDirection() == Qt::LeftToRight) { return value; } return totalWidth - width - value; } KPluginSelector::Private::DependenciesWidget::DependenciesWidget(QWidget *parent) : QWidget(parent) , addedByDependencies(0) , removedByDependencies(0) { setVisible(false); details = new QLabel(); QHBoxLayout *layout = new QHBoxLayout(this); QVBoxLayout *dataLayout = new QVBoxLayout; dataLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); layout->setAlignment(Qt::AlignLeft); QLabel *label = new QLabel(); label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize))); label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); layout->addWidget(label); KUrlLabel *link = new KUrlLabel(); link->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); link->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); link->setGlowEnabled(false); link->setUnderline(false); link->setFloatEnabled(true); link->setUseCursor(true); link->setHighlightedColor(palette().color(QPalette::Link)); link->setSelectedColor(palette().color(QPalette::Link)); link->setText(i18n("Automatic changes have been performed due to plugin dependencies. Click here for further information")); dataLayout->addWidget(link); dataLayout->addWidget(details); layout->addLayout(dataLayout); QObject::connect(link, SIGNAL(leftClickedUrl()), this, SLOT(showDependencyDetails())); } KPluginSelector::Private::DependenciesWidget::~DependenciesWidget() { } void KPluginSelector::Private::DependenciesWidget::addDependency(const QString &dependency, const QString &pluginCausant, bool added) { if (!isVisible()) { setVisible(true); } struct FurtherInfo furtherInfo; furtherInfo.added = added; furtherInfo.pluginCausant = pluginCausant; if (dependencyMap.contains(dependency)) { // The dependency moved from added to removed or vice-versa if (added && removedByDependencies) { removedByDependencies--; } else if (addedByDependencies) { addedByDependencies--; } dependencyMap[dependency] = furtherInfo; } else { dependencyMap.insert(dependency, furtherInfo); } if (added) { addedByDependencies++; } else { removedByDependencies++; } updateDetails(); } void KPluginSelector::Private::DependenciesWidget::userOverrideDependency(const QString &dependency) { if (dependencyMap.contains(dependency)) { if (addedByDependencies && dependencyMap[dependency].added) { addedByDependencies--; } else if (removedByDependencies) { removedByDependencies--; } dependencyMap.remove(dependency); } updateDetails(); } void KPluginSelector::Private::DependenciesWidget::clearDependencies() { addedByDependencies = 0; removedByDependencies = 0; dependencyMap.clear(); updateDetails(); } void KPluginSelector::Private::DependenciesWidget::showDependencyDetails() { QString message = i18n("Automatic changes have been performed in order to satisfy plugin dependencies:\n"); const auto lstKeys = dependencyMap.keys(); for (const QString &dependency : lstKeys) { if (dependencyMap[dependency].added) { message += i18n("\n %1 plugin has been automatically checked because of the dependency of %2 plugin", dependency, dependencyMap[dependency].pluginCausant); } else { message += i18n("\n %1 plugin has been automatically unchecked because of its dependency on %2 plugin", dependency, dependencyMap[dependency].pluginCausant); } } KMessageBox::information(this, message, i18n("Dependency Check")); addedByDependencies = 0; removedByDependencies = 0; updateDetails(); } void KPluginSelector::Private::DependenciesWidget::updateDetails() { if (dependencyMap.isEmpty()) { setVisible(false); return; } QString message; if (addedByDependencies) { message += i18np("%1 plugin automatically added due to plugin dependencies", "%1 plugins automatically added due to plugin dependencies", addedByDependencies); } if (removedByDependencies && !message.isEmpty()) { message += i18n(", "); } if (removedByDependencies) { message += i18np("%1 plugin automatically removed due to plugin dependencies", "%1 plugins automatically removed due to plugin dependencies", removedByDependencies); } if (message.isEmpty()) { details->setVisible(false); } else { details->setVisible(true); details->setText(message); } } KPluginSelector::KPluginSelector(QWidget *parent) : QWidget(parent) , d(new Private(this)) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); d->lineEdit = new QLineEdit(this); d->lineEdit->setClearButtonEnabled(true); d->lineEdit->setPlaceholderText(i18n("Search...")); d->listView = new KCategorizedView(this); d->categoryDrawer = new KCategoryDrawer(d->listView); d->listView->setVerticalScrollMode(QListView::ScrollPerPixel); d->listView->setAlternatingRowColors(true); d->listView->setCategoryDrawer(d->categoryDrawer); d->dependenciesWidget = new Private::DependenciesWidget(this); d->pluginModel = new Private::PluginModel(d, this); d->proxyModel = new Private::ProxyModel(d, this); d->proxyModel->setCategorizedModel(true); d->proxyModel->setSourceModel(d->pluginModel); d->listView->setModel(d->proxyModel); d->listView->setAlternatingRowColors(true); Private::PluginDelegate *pluginDelegate = new Private::PluginDelegate(d, this); d->listView->setItemDelegate(pluginDelegate); d->listView->setMouseTracking(true); d->listView->viewport()->setAttribute(Qt::WA_Hover); connect(d->lineEdit, &QLineEdit::textChanged, d->proxyModel, &QSortFilterProxyModel::invalidate); connect(pluginDelegate, &Private::PluginDelegate::changed, this, &KPluginSelector::changed); connect(pluginDelegate, &Private::PluginDelegate::configCommitted, this, &KPluginSelector::configCommitted); connect(this, &KPluginSelector::changed, [this]{ emit defaulted(isDefault()); }); layout->addWidget(d->lineEdit); layout->addWidget(d->listView); layout->addWidget(d->dependenciesWidget); // When a KPluginSelector instance gets focus, // it should pass over the focus to its child searchbar. setFocusProxy(d->lineEdit); } KPluginSelector::~KPluginSelector() { delete d->listView->itemDelegate(); delete d->listView; // depends on some other things in d, make sure this dies first. delete d; } void KPluginSelector::addPlugins(const QString &componentName, const QString &categoryName, const QString &categoryKey, KSharedConfig::Ptr config) { QStringList desktopFileNames; const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, componentName + QStringLiteral("/kpartplugins"), QStandardPaths::LocateDirectory); for (const QString &dir : dirs) { QDirIterator it(dir, QStringList() << QStringLiteral("*.desktop"), QDir::NoFilter, QDirIterator::Subdirectories); while (it.hasNext()) { desktopFileNames.append(it.next()); } } QList pluginInfoList = KPluginInfo::fromFiles(desktopFileNames); if (pluginInfoList.isEmpty()) { return; } if (!config) { config = KSharedConfig::openConfig(componentName + QStringLiteral("rc")); } Q_ASSERT(config); KConfigGroup cfgGroup(config, "KParts Plugins"); // qDebug() << "cfgGroup = " << &cfgGroup; d->pluginModel->addPlugins(pluginInfoList, categoryName, categoryKey, cfgGroup); d->proxyModel->sort(0); } void KPluginSelector::addPlugins(const QList &pluginInfoList, PluginLoadMethod pluginLoadMethod, const QString &categoryName, const QString &categoryKey, const KSharedConfig::Ptr &config) { if (pluginInfoList.isEmpty()) { return; } KConfigGroup cfgGroup(config ? config : KSharedConfig::openConfig(), "Plugins"); // qDebug() << "cfgGroup = " << &cfgGroup; d->pluginModel->addPlugins(pluginInfoList, categoryName, categoryKey, cfgGroup, pluginLoadMethod, true /* manually added */); d->proxyModel->sort(0); } void KPluginSelector::load() { for (int i = 0; i < d->pluginModel->rowCount(); i++) { const QModelIndex index = d->pluginModel->index(i, 0); PluginEntry *pluginEntry = static_cast(index.internalPointer()); pluginEntry->pluginInfo.load(pluginEntry->cfgGroup); d->pluginModel->setData(index, pluginEntry->pluginInfo.isPluginEnabled(), Qt::CheckStateRole); } emit changed(false); } void KPluginSelector::save() { for (int i = 0; i < d->pluginModel->rowCount(); i++) { const QModelIndex index = d->pluginModel->index(i, 0); PluginEntry *pluginEntry = static_cast(index.internalPointer()); pluginEntry->pluginInfo.setPluginEnabled(pluginEntry->checked); pluginEntry->pluginInfo.save(pluginEntry->cfgGroup); pluginEntry->cfgGroup.sync(); } emit changed(false); } void KPluginSelector::defaults() { bool isChanged = false; for (int i = 0; i < d->pluginModel->rowCount(); i++) { const QModelIndex index = d->pluginModel->index(i, 0); PluginEntry *pluginEntry = static_cast(index.internalPointer()); isChanged |= pluginEntry->pluginInfo.isPluginEnabled() != pluginEntry->pluginInfo.isPluginEnabledByDefault(); d->pluginModel->setData(index, pluginEntry->pluginInfo.isPluginEnabledByDefault(), Qt::CheckStateRole); } emit changed(isChanged); } bool KPluginSelector::isDefault() const { for (int i = 0; i < d->pluginModel->rowCount(); i++) { const QModelIndex index = d->pluginModel->index(i, 0); PluginEntry *pluginEntry = static_cast(index.internalPointer()); if (d->pluginModel->data(index, Qt::CheckStateRole).toBool() != pluginEntry->pluginInfo.isPluginEnabledByDefault()) { return false; } } return true; } void KPluginSelector::updatePluginsState() { for (int i = 0; i < d->pluginModel->rowCount(); i++) { const QModelIndex index = d->pluginModel->index(i, 0); PluginEntry *pluginEntry = static_cast(index.internalPointer()); if (pluginEntry->manuallyAdded) { pluginEntry->pluginInfo.setPluginEnabled(pluginEntry->checked); } } } void KPluginSelector::setConfigurationArguments(const QStringList& arguments) { d->kcmArguments = arguments; } QStringList KPluginSelector::configurationArguments() const { return d->kcmArguments; } void KPluginSelector::showConfiguration(const QString& componentName) { QModelIndex idx; for (int i = 0, c = d->proxyModel->rowCount(); iproxyModel->index(i, 0); const auto entry = currentIndex.data(KPluginSelector::Private::PluginEntryRole).value(); if (entry->pluginInfo.pluginName() == componentName) { idx = currentIndex; break; } } if (idx.isValid()) { auto delegate = static_cast(d->listView->itemDelegate()); delegate->configure(idx); } else { qWarning() << "Could not find plugin" << componentName; } } KPluginSelector::Private::PluginModel::PluginModel(KPluginSelector::Private *pluginSelector_d, QObject *parent) : QAbstractListModel(parent) , pluginSelector_d(pluginSelector_d) { } KPluginSelector::Private::PluginModel::~PluginModel() { } void KPluginSelector::Private::PluginModel::addPlugins(const QList &pluginList, const QString &categoryName, const QString &categoryKey, const KConfigGroup &cfgGroup, PluginLoadMethod pluginLoadMethod, bool manuallyAdded) { QList listToAdd; for (const KPluginInfo &pluginInfo : pluginList) { PluginEntry pluginEntry; pluginEntry.category = categoryName; pluginEntry.pluginInfo = pluginInfo; if (pluginLoadMethod == ReadConfigFile) { pluginEntry.pluginInfo.load(cfgGroup); } pluginEntry.checked = pluginInfo.isPluginEnabled(); pluginEntry.manuallyAdded = manuallyAdded; if (cfgGroup.isValid()) { pluginEntry.cfgGroup = cfgGroup; } else { pluginEntry.cfgGroup = pluginInfo.config(); } // this is where kiosk will set if a plugin is checkable or not (pluginName + "Enabled") pluginEntry.isCheckable = !pluginInfo.isValid() || !pluginEntry.cfgGroup.isEntryImmutable(pluginInfo.pluginName() + QLatin1String("Enabled")); if (!pluginEntryList.contains(pluginEntry) && !listToAdd.contains(pluginEntry) && (categoryKey.isEmpty() || !pluginInfo.category().compare(categoryKey, Qt::CaseInsensitive)) && (!pluginInfo.service() || !pluginInfo.service()->noDisplay())) { listToAdd << pluginEntry; if (!pluginSelector_d->showIcons && !pluginInfo.icon().isEmpty()) { pluginSelector_d->showIcons = true; } } } if (!listToAdd.isEmpty()) { beginInsertRows(QModelIndex(), pluginEntryList.count(), pluginEntryList.count() + listToAdd.count() - 1); pluginEntryList << listToAdd; endInsertRows(); } } QList KPluginSelector::Private::PluginModel::pluginServices(const QModelIndex &index) const { return static_cast(index.internalPointer())->pluginInfo.kcmServices(); } QModelIndex KPluginSelector::Private::PluginModel::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(parent) return createIndex(row, column, (row < pluginEntryList.count()) ? (void *) &pluginEntryList.at(row) : nullptr); } QVariant KPluginSelector::Private::PluginModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || !index.internalPointer()) { return QVariant(); } PluginEntry *pluginEntry = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: return pluginEntry->pluginInfo.name(); case PluginEntryRole: return QVariant::fromValue(pluginEntry); case ServicesCountRole: return pluginEntry->pluginInfo.kcmServices().count(); case NameRole: return pluginEntry->pluginInfo.name(); case CommentRole: return pluginEntry->pluginInfo.comment(); case AuthorRole: return pluginEntry->pluginInfo.author(); case EmailRole: return pluginEntry->pluginInfo.email(); case WebsiteRole: return pluginEntry->pluginInfo.website(); case VersionRole: return pluginEntry->pluginInfo.version(); case LicenseRole: return pluginEntry->pluginInfo.license(); case DependenciesRole: return pluginEntry->pluginInfo.dependencies(); case IsCheckableRole: return pluginEntry->isCheckable; case Qt::DecorationRole: return pluginEntry->pluginInfo.icon(); case Qt::CheckStateRole: return pluginEntry->checked; case KCategorizedSortFilterProxyModel::CategoryDisplayRole: // fall through case KCategorizedSortFilterProxyModel::CategorySortRole: return pluginEntry->category; default: return QVariant(); } } bool KPluginSelector::Private::PluginModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) { return false; } bool ret = false; if (role == Qt::CheckStateRole) { static_cast(index.internalPointer())->checked = value.toBool(); ret = true; } if (ret) { emit dataChanged(index, index); } return ret; } int KPluginSelector::Private::PluginModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return pluginEntryList.count(); } KPluginSelector::Private::ProxyModel::ProxyModel(KPluginSelector::Private *pluginSelector_d, QObject *parent) : KCategorizedSortFilterProxyModel(parent) , pluginSelector_d(pluginSelector_d) { sort(0); } KPluginSelector::Private::ProxyModel::~ProxyModel() { } bool KPluginSelector::Private::ProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { Q_UNUSED(sourceParent) if (!pluginSelector_d->lineEdit->text().isEmpty()) { const QModelIndex index = sourceModel()->index(sourceRow, 0); const KPluginInfo pluginInfo = static_cast(index.internalPointer())->pluginInfo; return pluginInfo.name().contains(pluginSelector_d->lineEdit->text(), Qt::CaseInsensitive) || pluginInfo.comment().contains(pluginSelector_d->lineEdit->text(), Qt::CaseInsensitive); } return true; } bool KPluginSelector::Private::ProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const { return static_cast(left.internalPointer())->pluginInfo.name().compare(static_cast(right.internalPointer())->pluginInfo.name(), Qt::CaseInsensitive) < 0; } KPluginSelector::Private::PluginDelegate::PluginDelegate(KPluginSelector::Private *pluginSelector_d, QObject *parent) : KWidgetItemDelegate(pluginSelector_d->listView, parent) , checkBox(new QCheckBox) , pushButton(new QPushButton) , pluginSelector_d(pluginSelector_d) { pushButton->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); // only for getting size matters } KPluginSelector::Private::PluginDelegate::~PluginDelegate() { delete checkBox; delete pushButton; } void KPluginSelector::Private::PluginDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } int xOffset = checkBox->sizeHint().width(); bool disabled = !index.model()->data(index, IsCheckableRole).toBool(); painter->save(); QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); int iconSize = option.rect.height() - MARGIN * 2; if (pluginSelector_d->showIcons) { QIcon icon = QIcon::fromTheme(index.model()->data(index, Qt::DecorationRole).toString()); icon.paint(painter, QRect(pluginSelector_d->dependantLayoutValue(MARGIN + option.rect.left() + xOffset, iconSize, option.rect.width()), MARGIN + option.rect.top(), iconSize, iconSize)); } else { iconSize = -MARGIN; } QRect contentsRect(pluginSelector_d->dependantLayoutValue(MARGIN * 2 + iconSize + option.rect.left() + xOffset, option.rect.width() - MARGIN * 3 - iconSize - xOffset, option.rect.width()), MARGIN + option.rect.top(), option.rect.width() - MARGIN * 3 - iconSize - xOffset, option.rect.height() - MARGIN * 2); int lessHorizontalSpace = MARGIN * 2 + pushButton->sizeHint().width(); if (index.model()->data(index, ServicesCountRole).toBool()) { lessHorizontalSpace += MARGIN + pushButton->sizeHint().width(); } contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.highlightedText().color()); } if (pluginSelector_d->listView->layoutDirection() == Qt::RightToLeft) { contentsRect.translate(lessHorizontalSpace, 0); } painter->save(); if (disabled) { QPalette pal(option.palette); pal.setCurrentColorGroup(QPalette::Disabled); painter->setPen(pal.text().color()); } painter->save(); QFont font = titleFont(option.font); QFontMetrics fmTitle(font); painter->setFont(font); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignTop, fmTitle.elidedText(index.model()->data(index, Qt::DisplayRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignBottom, option.fontMetrics.elidedText(index.model()->data(index, CommentRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->restore(); } QSize KPluginSelector::Private::PluginDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { int i = 5; int j = 1; if (index.model()->data(index, ServicesCountRole).toBool()) { i = 6; j = 2; } if (!pluginSelector_d->showIcons) { i--; } const QFont font = titleFont(option.font); const QFontMetrics fmTitle(font); const QString text = index.model()->data(index, Qt::DisplayRole).toString(); const QString comment = index.model()->data(index, CommentRole).toString(); const int maxTextWidth = qMax(fmTitle.boundingRect(text).width(), option.fontMetrics.boundingRect(comment).width()); const auto iconSize = pluginSelector_d->listView->style()->pixelMetric(QStyle::PM_IconViewIconSize); return QSize(maxTextWidth + (pluginSelector_d->showIcons ? iconSize : 0) + MARGIN * i + pushButton->sizeHint().width() * j, qMax(iconSize + MARGIN * 2, fmTitle.height() + option.fontMetrics.height() + MARGIN * 2)); } QList KPluginSelector::Private::PluginDelegate::createItemWidgets(const QModelIndex &index) const { Q_UNUSED(index); QList widgetList; QCheckBox *enabledCheckBox = new QCheckBox; connect(enabledCheckBox, &QAbstractButton::clicked, this, &PluginDelegate::slotStateChanged); connect(enabledCheckBox, &QAbstractButton::clicked, this, &PluginDelegate::emitChanged); QPushButton *aboutPushButton = new QPushButton; aboutPushButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); aboutPushButton->setToolTip(i18n("About")); connect(aboutPushButton, &QAbstractButton::clicked, this, &PluginDelegate::slotAboutClicked); QPushButton *configurePushButton = new QPushButton; configurePushButton->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); configurePushButton->setToolTip(i18n("Configure")); connect(configurePushButton, &QAbstractButton::clicked, this, &PluginDelegate::slotConfigureClicked); setBlockedEventTypes(enabledCheckBox, QList() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick << QEvent::KeyPress << QEvent::KeyRelease); setBlockedEventTypes(aboutPushButton, QList() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick << QEvent::KeyPress << QEvent::KeyRelease); setBlockedEventTypes(configurePushButton, QList() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick << QEvent::KeyPress << QEvent::KeyRelease); widgetList << enabledCheckBox << configurePushButton << aboutPushButton; return widgetList; } void KPluginSelector::Private::PluginDelegate::updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const { QCheckBox *checkBox = static_cast(widgets[0]); checkBox->resize(checkBox->sizeHint()); checkBox->move(pluginSelector_d->dependantLayoutValue(MARGIN, checkBox->sizeHint().width(), option.rect.width()), option.rect.height() / 2 - checkBox->sizeHint().height() / 2); QPushButton *aboutPushButton = static_cast(widgets[2]); QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint(); aboutPushButton->resize(aboutPushButtonSizeHint); aboutPushButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - MARGIN - aboutPushButtonSizeHint.width(), aboutPushButtonSizeHint.width(), option.rect.width()), option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2); QPushButton *configurePushButton = static_cast(widgets[1]); QSize configurePushButtonSizeHint = configurePushButton->sizeHint(); configurePushButton->resize(configurePushButtonSizeHint); configurePushButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - MARGIN * 2 - configurePushButtonSizeHint.width() - aboutPushButtonSizeHint.width(), configurePushButtonSizeHint.width(), option.rect.width()), option.rect.height() / 2 - configurePushButtonSizeHint.height() / 2); if (!index.isValid() || !index.internalPointer()) { checkBox->setVisible(false); aboutPushButton->setVisible(false); configurePushButton->setVisible(false); } else { checkBox->setChecked(index.model()->data(index, Qt::CheckStateRole).toBool()); checkBox->setEnabled(index.model()->data(index, IsCheckableRole).toBool()); configurePushButton->setVisible(index.model()->data(index, ServicesCountRole).toBool()); configurePushButton->setEnabled(index.model()->data(index, Qt::CheckStateRole).toBool()); } } void KPluginSelector::Private::PluginDelegate::slotStateChanged(bool state) { if (!focusedIndex().isValid()) { return; } const QModelIndex index = focusedIndex(); pluginSelector_d->dependenciesWidget->clearDependencies(); PluginEntry *pluginEntry = index.model()->data(index, PluginEntryRole).value(); pluginSelector_d->updateDependencies(pluginEntry, state); const_cast(index.model())->setData(index, state, Qt::CheckStateRole); } void KPluginSelector::Private::PluginDelegate::emitChanged(bool state) { const QModelIndex index = focusedIndex(); PluginEntry *pluginEntry = index.model()->data(index, PluginEntryRole).value(); emit changed(pluginEntry->pluginInfo.isPluginEnabled() != state); } void KPluginSelector::Private::PluginDelegate::slotAboutClicked() { const QModelIndex index = focusedIndex(); const QAbstractItemModel *model = index.model(); PluginEntry *pluginEntry = model->data(index, PluginEntryRole).value(); KPluginMetaData pluginMetaData = pluginEntry->pluginInfo.toMetaData(); KAboutPluginDialog aboutPlugin(pluginMetaData, itemView()); aboutPlugin.exec(); } void KPluginSelector::Private::PluginDelegate::slotConfigureClicked() { configure(focusedIndex()); } void KPluginSelector::Private::PluginDelegate::configure(const QModelIndex& index) { const QAbstractItemModel *model = index.model(); PluginEntry *pluginEntry = model->data(index, PluginEntryRole).value(); KPluginInfo pluginInfo = pluginEntry->pluginInfo; QDialog configDialog(itemView()); configDialog.setWindowTitle(model->data(index, NameRole).toString()); // The number of KCModuleProxies in use determines whether to use a tabwidget QTabWidget *newTabWidget = nullptr; // Widget to use for the setting dialog's main widget, // either a QTabWidget or a KCModuleProxy QWidget *mainWidget = nullptr; // Widget to use as the KCModuleProxy's parent. // The first proxy is owned by the dialog itself QWidget *moduleProxyParentWidget = &configDialog; const auto lstServices = pluginInfo.kcmServices(); for (const KService::Ptr &servicePtr : lstServices) { if (!servicePtr->noDisplay()) { KCModuleInfo moduleInfo(servicePtr); KCModuleProxy *currentModuleProxy = new KCModuleProxy(moduleInfo, moduleProxyParentWidget, pluginSelector_d->kcmArguments); if (currentModuleProxy->realModule()) { moduleProxyList << currentModuleProxy; if (mainWidget && !newTabWidget) { // we already created one KCModuleProxy, so we need a tab widget. // Move the first proxy into the tab widget and ensure this and subsequent // proxies are in the tab widget newTabWidget = new QTabWidget(&configDialog); moduleProxyParentWidget = newTabWidget; mainWidget->setParent(newTabWidget); KCModuleProxy *moduleProxy = qobject_cast(mainWidget); if (moduleProxy) { newTabWidget->addTab(mainWidget, moduleProxy->moduleInfo().moduleName()); mainWidget = newTabWidget; } else { delete newTabWidget; newTabWidget = nullptr; moduleProxyParentWidget = &configDialog; mainWidget->setParent(nullptr); } } if (newTabWidget) { newTabWidget->addTab(currentModuleProxy, servicePtr->name()); } else { mainWidget = currentModuleProxy; } } else { delete currentModuleProxy; } } } // it could happen that we had services to show, but none of them were real modules. if (!moduleProxyList.isEmpty()) { QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(mainWidget); const int marginHint = configDialog.style()->pixelMetric(QStyle::PM_DefaultChildMargin); layout->insertSpacing(-1, marginHint); QDialogButtonBox *buttonBox = new QDialogButtonBox(&configDialog); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults()); connect(buttonBox, &QDialogButtonBox::accepted, &configDialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &configDialog, &QDialog::reject); connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, &PluginDelegate::slotDefaultClicked); layout->addWidget(buttonBox); configDialog.setLayout(layout); if (configDialog.exec() == QDialog::Accepted) { for (KCModuleProxy *moduleProxy : qAsConst(moduleProxyList)) { - const QStringList parentComponents = moduleProxy->moduleInfo().pluginInfo().property(QStringLiteral("X-KDE-ParentComponents")).toStringList(); + const QStringList parentComponents = moduleProxy->moduleInfo().property(QStringLiteral("X-KDE-ParentComponents")).toStringList(); moduleProxy->save(); for (const QString &parentComponent : parentComponents) { emit configCommitted(parentComponent.toLatin1()); } } } else { for (KCModuleProxy *moduleProxy : qAsConst(moduleProxyList)) { moduleProxy->load(); } } qDeleteAll(moduleProxyList); moduleProxyList.clear(); } } void KPluginSelector::Private::PluginDelegate::slotDefaultClicked() { for (KCModuleProxy *moduleProxy : qAsConst(moduleProxyList)) { moduleProxy->defaults(); } } QFont KPluginSelector::Private::PluginDelegate::titleFont(const QFont &baseFont) const { QFont retFont(baseFont); retFont.setBold(true); return retFont; } #include "moc_kpluginselector_p.cpp" #include "moc_kpluginselector.cpp" diff --git a/src/ksettings/dialog.cpp b/src/ksettings/dialog.cpp index c64da39..2412413 100644 --- a/src/ksettings/dialog.cpp +++ b/src/ksettings/dialog.cpp @@ -1,567 +1,567 @@ /* This file is part of the KDE project Copyright (C) 2003 Matthias Kretz 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 "dialog.h" #include "dialog_p.h" #include "dispatcher.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include uint qHash(const KCModuleInfo &info) { return qHash(info.fileName()); } namespace KSettings { Dialog::Dialog(QWidget *parent) : KCMultiDialog(*new DialogPrivate(this), new KPageWidget, parent) { } Dialog::Dialog(const QStringList &components, QWidget *parent) : KCMultiDialog(*new DialogPrivate(this), new KPageWidget, parent) { Q_D(Dialog); d->components = components; } Dialog::~Dialog() { } void Dialog::setAllowComponentSelection(bool selection) { d_func()->staticlistview = !selection; } bool Dialog::allowComponentSelection() const { return !d_func()->staticlistview; } void Dialog::setKCMArguments(const QStringList &arguments) { Q_D(Dialog); d->arguments = arguments; } void Dialog::setComponentBlacklist(const QStringList &blacklist) { Q_D(Dialog); d->componentBlacklist = blacklist; } void Dialog::addPluginInfos(const KPluginInfo::List &plugininfos) { Q_D(Dialog); for (KPluginInfo::List::ConstIterator it = plugininfos.begin(); it != plugininfos.end(); ++it) { d->registeredComponents.append(it->pluginName()); const auto lst = it->kcmServices(); if (lst.isEmpty()) { // this plugin has no kcm services, still we want to show the disable/enable stuff // so add a dummy kcm d->kcmInfos << KCModuleInfo(*it); continue; } for (const KService::Ptr &service : lst) { d->kcmInfos << KCModuleInfo(service); } } // The plugin, when disabled, disables all the KCMs described by kcmServices(). // - Normally they are grouped using a .setdlg file so that the group parent can get a // checkbox to enable/disable the plugin. // - If the plugin does not belong to a group and has only one KCM the checkbox can be // used with this KCM. // - If the plugin belongs to a group but there are other modules in the group that do not // belong to this plugin we give a kError and show no checkbox // - If the plugin belongs to multiple groups we give a kError and show no checkbox d->plugininfos = plugininfos; } KPluginInfo::List Dialog::pluginInfos() const { return d_func()->plugininfos; } void Dialog::showEvent(QShowEvent *) { Q_D(Dialog); if (d->firstshow) { setUpdatesEnabled(false); d->kcmInfos += d->instanceServices(); if (!d->components.isEmpty()) { d->kcmInfos += d->parentComponentsServices(d->components); } d->createDialogFromServices(); d->firstshow = false; setUpdatesEnabled(true); } Dispatcher::syncConfiguration(); } DialogPrivate::DialogPrivate(Dialog *parent) : KCMultiDialogPrivate(parent), staticlistview(true), firstshow(true), pluginStateDirty(0) { } QSet DialogPrivate::instanceServices() { //qDebug() ; QString componentName = QCoreApplication::instance()->applicationName(); registeredComponents.append(componentName); //qDebug() << "calling KServiceGroup::childGroup( " << componentName << " )"; KServiceGroup::Ptr service = KServiceGroup::childGroup(componentName); QSet ret; if (service && service->isValid()) { //qDebug() << "call was successful"; const KServiceGroup::List list = service->entries(); for (KServiceGroup::List::ConstIterator it = list.begin(); it != list.end(); ++it) { KSycocaEntry::Ptr p = (*it); if (p->isType(KST_KService)) { //qDebug() << "found service"; const KService::Ptr service(static_cast(p.data())); ret << KCModuleInfo(service); } else qWarning() << "KServiceGroup::childGroup returned" " something else than a KService"; } } return ret; } QSet DialogPrivate::parentComponentsServices(const QStringList &kcdparents) { registeredComponents += kcdparents; QString constraint = kcdparents.join(QLatin1String("' in [X-KDE-ParentComponents]) or ('")); constraint = QStringLiteral("('") + constraint + QStringLiteral("' in [X-KDE-ParentComponents])"); //qDebug() << "constraint = " << constraint; const QList services = KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), constraint); QSet ret; ret.reserve(services.count()); for (const KService::Ptr &service : services) { ret << KCModuleInfo(service); } return ret; } bool DialogPrivate::isPluginForKCMEnabled(const KCModuleInfo *moduleinfo, KPluginInfo &pinfo) const { // if the user of this class requested to hide disabled modules // we check whether it should be enabled or not bool enabled = true; //qDebug() << "check whether the '" << moduleinfo->moduleName() << "' KCM should be shown"; // for all parent components - const QStringList parentComponents = moduleinfo->pluginInfo().property( + const QStringList parentComponents = moduleinfo->property( QStringLiteral("X-KDE-ParentComponents")).toStringList(); for (QStringList::ConstIterator pcit = parentComponents.begin(); pcit != parentComponents.end(); ++pcit) { // if the parentComponent is not registered ignore it if (!registeredComponents.contains(*pcit)) { continue; } // we check if the parent component is a plugin // if not the KCModule must be enabled enabled = true; if (pinfo.pluginName() == *pcit) { // it is a plugin: we check whether the plugin is enabled pinfo.load(); enabled = pinfo.isPluginEnabled(); //qDebug() << "parent " << *pcit << " is " << (enabled ? "enabled" : "disabled"); } // if it is enabled we're done for this KCModuleInfo if (enabled) { return true; } } return enabled; } bool DialogPrivate::isPluginImmutable(const KPluginInfo &pinfo) const { return pinfo.property(QStringLiteral("X-KDE-PluginInfo-Immutable")).toBool(); } KPageWidgetItem *DialogPrivate::createPageItem(KPageWidgetItem *parentItem, const QString &name, const QString &comment, const QString &iconName, int weight) { Q_Q(Dialog); QWidget *page = new QWidget(q); QCheckBox *checkBox = new QCheckBox(i18n("Enable component"), page); QLabel *iconLabel = new QLabel(page); QLabel *commentLabel = new QLabel(comment, page); commentLabel->setTextFormat(Qt::RichText); QVBoxLayout *layout = new QVBoxLayout(page); layout->addWidget(checkBox); layout->addWidget(iconLabel); layout->addWidget(commentLabel); layout->addStretch(); page->setLayout(layout); KPageWidgetItem *item = new KPageWidgetItem(page, name); item->setIcon(QIcon::fromTheme(iconName)); iconLabel->setPixmap(item->icon().pixmap(128, 128)); item->setProperty("_k_weight", weight); checkBoxForItem.insert(item, checkBox); const KPageWidgetModel *model = qobject_cast(q->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(model->index(row, 0, parentIndex)); if (siblingItem->property("_k_weight").toInt() > weight) { // the item we found is heavier than the new module q->insertPage(siblingItem, item); break; } } if (row == siblingCount) { // the new module is either the first or the heaviest item q->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() > weight) { // the item we found is heavier than the new module q->insertPage(siblingItem, item); break; } } if (row == siblingCount) { // the new module is either the first or the heaviest item q->addPage(item); } } return (item); } void DialogPrivate::parseGroupFile(const QString &filename) { KConfig file(filename, KConfig::SimpleConfig); const QStringList groups = file.groupList(); for (const QString &group : groups) { if (group.isEmpty()) { continue; } KConfigGroup conf(&file, group); const QString parentId = conf.readEntry("Parent"); KPageWidgetItem *parentItem = pageItemForGroupId.value(parentId); KPageWidgetItem *item = createPageItem(parentItem, conf.readEntry("Name"), conf.readEntry("Comment"), conf.readEntry("Icon"), conf.readEntry("Weight", 100)); pageItemForGroupId.insert(group, item); } } void DialogPrivate::createDialogFromServices() { Q_Q(Dialog); // read .setdlg files (eg: share/kapp/kapp.setdlg) const QString setdlgpath = QStandardPaths::locate(QStandardPaths::DataLocation /*includes appname, too*/, QCoreApplication::instance()->applicationName() + QStringLiteral(".setdlg")); if (!setdlgpath.isEmpty()) { parseGroupFile(setdlgpath); } const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::DataLocation, QStringLiteral("ksettingsdialog"), QStandardPaths::LocateDirectory); QMap fileMap; for (const QString &dir : dirs) { const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.setdlg")); for (const QString &file : fileNames) { if (!fileMap.contains(file)) { fileMap.insert(file, dir + QLatin1Char('/') + file); } } } for (auto it = fileMap.constBegin(); it != fileMap.constEnd(); ++it) { parseGroupFile(it.value()); } //qDebug() << kcmInfos.count(); for (const KCModuleInfo &info : qAsConst(kcmInfos)) { const QStringList parentComponents = info.pluginInfo().property(QStringLiteral("X-KDE-ParentComponents")).toStringList(); bool blacklisted = false; for (const QString &parentComponent : parentComponents) { if (componentBlacklist.contains(parentComponent)) { blacklisted = true; break; } } if (blacklisted) { continue; } const QString parentId = info.pluginInfo().property(QStringLiteral("X-KDE-CfgDlgHierarchy")).toString(); KPageWidgetItem *parent = pageItemForGroupId.value(parentId); if (!parent) { // dummy kcm bool foundPlugin = false; for (const KPluginInfo &pinfo : qAsConst(plugininfos)) { if (pinfo.libraryPath() == info.library()) { if (pinfo.kcmServices().isEmpty()) { // FIXME get weight from service or plugin info const int weight = 1000; KPageWidgetItem *item = createPageItem(nullptr, pinfo.name(), pinfo.comment(), pinfo.icon(), weight); connectItemCheckBox(item, pinfo, pinfo.isPluginEnabled()); foundPlugin = true; break; } } } if (foundPlugin) { continue; } } KPageWidgetItem *item = q->addModule(info, parent, arguments); // qDebug() << "added KCM '" << info.moduleName() << "'"; for (KPluginInfo pinfo : qAsConst(plugininfos)) { // qDebug() << pinfo.pluginName(); if (pinfo.kcmServices().contains(info.service())) { const bool isEnabled = isPluginForKCMEnabled(&info, pinfo); // qDebug() << "correct KPluginInfo for this KCM"; // this KCM belongs to a plugin if (parent && pinfo.kcmServices().count() >= 1) { item->setEnabled(isEnabled); const KPluginInfo &plugin = pluginForItem.value(parent); if (plugin.isValid()) { if (plugin != pinfo) { qCritical() << "A group contains more than one plugin: '" << plugin.pluginName() << "' and '" << pinfo.pluginName() << "'. Now it won't be possible to enable/disable the plugin."; parent->setCheckable(false); q->disconnect(parent, SIGNAL(toggled(bool)), q, SLOT(_k_updateEnabledState(bool))); } // else everything is fine } else { connectItemCheckBox(parent, pinfo, isEnabled); } } else { pluginForItem.insert(item, pinfo); item->setCheckable(!isPluginImmutable(pinfo)); item->setChecked(isEnabled); q->connect(item, SIGNAL(toggled(bool)), q, SLOT(_k_updateEnabledState(bool))); } break; } } } // now that the KCMs are in, check for empty groups and remove them again { const KPageWidgetModel *model = qobject_cast(q->pageWidget()->model()); const QHash::ConstIterator end = pageItemForGroupId.constEnd(); QHash::ConstIterator it = pageItemForGroupId.constBegin(); for (; it != end; ++it) { const QModelIndex index = model->index(it.value()); KPluginInfo pinfo; for (const KPluginInfo &p : qAsConst(plugininfos)) { if (p.name() == it.key()) { pinfo = p; break; } } bool allowEmpty = false; if (pinfo.isValid()) { allowEmpty = pinfo.property(QStringLiteral("X-KDE-PluginInfo-AllowEmptySettings")).toBool(); } if (model->rowCount(index) == 0) { // no children, and it's not allowed => remove this item if (!allowEmpty) { q->removePage(it.value()); } else { connectItemCheckBox(it.value(), pinfo, pinfo.isPluginEnabled()); } } } } // TODO: Don't show the reset button until the issue with the // KPluginSelector::load() method is solved. // Problem: // KCMultiDialog::show() call KCModule::load() to reset all KCMs // (KPluginSelector::load() resets all plugin selections and all plugin // KCMs). // The reset button calls KCModule::load(), too but in this case we want the // KPluginSelector to only reset the current visible plugin KCM and not // touch the plugin selections. // I have no idea how to check that in KPluginSelector::load()... //q->showButton(KDialog::User1, true); QObject::connect(q->button(QDialogButtonBox::Ok), SIGNAL(clicked()), q, SLOT(_k_syncConfiguration())); QObject::connect(q->button(QDialogButtonBox::Apply), SIGNAL(clicked()), q, SLOT(_k_syncConfiguration())); QObject::connect(q, SIGNAL(configCommitted(QByteArray)), q, SLOT(_k_reparseConfiguration(QByteArray))); } void DialogPrivate::connectItemCheckBox(KPageWidgetItem *item, const KPluginInfo &pinfo, bool isEnabled) { Q_Q(Dialog); QCheckBox *checkBox = checkBoxForItem.value(item); Q_ASSERT(checkBox); pluginForItem.insert(item, pinfo); item->setCheckable(!isPluginImmutable(pinfo)); item->setChecked(isEnabled); checkBox->setVisible(!isPluginImmutable(pinfo)); checkBox->setChecked(isEnabled); q->connect(item, SIGNAL(toggled(bool)), q, SLOT(_k_updateEnabledState(bool))); q->connect(item, &KPageWidgetItem::toggled, checkBox, &QAbstractButton::setChecked); q->connect(checkBox, &QAbstractButton::clicked, item, &KPageWidgetItem::setChecked); } void DialogPrivate::_k_syncConfiguration() { Q_Q(Dialog); const QHash::Iterator endIt = pluginForItem.end(); QHash::Iterator it = pluginForItem.begin(); for (; it != endIt; ++it) { KPageWidgetItem *item = it.key(); KPluginInfo pinfo = it.value(); pinfo.setPluginEnabled(item->isChecked()); pinfo.save(); } if (pluginStateDirty > 0) { emit q->pluginSelectionChanged(); pluginStateDirty = 0; } Dispatcher::syncConfiguration(); } void DialogPrivate::_k_reparseConfiguration(const QByteArray &a) { Dispatcher::reparseConfiguration(QString::fromLatin1(a)); } /* void DialogPrivate::_k_configureTree() { // qDebug() ; QObject::connect(subdlg, SIGNAL(okClicked()), q, SLOT(_k_updateTreeList())); QObject::connect(subdlg, SIGNAL(applyClicked()), q, SLOT(_k_updateTreeList())); QObject::connect(subdlg, SIGNAL(okClicked()), q, SIGNAL(pluginSelectionChanged())); QObject::connect(subdlg, SIGNAL(applyClicked()), q, SIGNAL(pluginSelectionChanged())); } */ void DialogPrivate::_k_clientChanged() { if (pluginStateDirty > 0) { Q_Q(Dialog); q->buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(true); } else { KCMultiDialogPrivate::_k_clientChanged(); } } void DialogPrivate::_k_updateEnabledState(bool enabled) { Q_Q(Dialog); KPageWidgetItem *item = qobject_cast(q->sender()); if (!item) { qWarning() << "invalid sender"; return; } // iterate over all child KPageWidgetItem objects and check whether they need to be enabled/disabled const KPageWidgetModel *model = qobject_cast(q->pageWidget()->model()); Q_ASSERT(model); QModelIndex index = model->index(item); if (!index.isValid()) { qWarning() << "could not find item in model"; return; } const KPluginInfo &pinfo = pluginForItem.value(item); if (!pinfo.isValid()) { qWarning() << "could not find KPluginInfo in item"; return; } if (pinfo.isPluginEnabled() != enabled) { ++pluginStateDirty; } else { --pluginStateDirty; } if (pluginStateDirty < 2) { _k_clientChanged(); } //qDebug() ; QModelIndex firstborn = model->index(0, 0, index); if (firstborn.isValid()) { //qDebug() << "iterating over children"; // change all children index = firstborn; QStack stack; while (index.isValid()) { //qDebug() << index; KPageWidgetItem *item = model->item(index); //qDebug() << "item->setEnabled(" << enabled << ')'; item->setEnabled(enabled); firstborn = model->index(0, 0, index); if (firstborn.isValid()) { stack.push(index); index = firstborn; } else { index = index.sibling(index.row() + 1, 0); while (!index.isValid() && !stack.isEmpty()) { index = stack.pop(); index = index.sibling(index.row() + 1, 0); } } } } } } //namespace #include "moc_dialog.cpp"