File indexing completed on 2026-07-12 13:08:06

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2013 Antonis Tsiapaliokas <kok3rs@gmail.com>
0006     SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 #include "effectsmodel.h"
0011 
0012 #include <config-kwin.h>
0013 
0014 #include <kwin_effects_interface.h>
0015 
0016 #include <KAboutData>
0017 #include <KCModule>
0018 #include <KCModuleLoader>
0019 #include <KConfigGroup>
0020 #include <KLocalizedString>
0021 #include <KPackage/PackageLoader>
0022 #include <KPluginFactory>
0023 #include <KPluginMetaData>
0024 
0025 #include <QDBusConnection>
0026 #include <QDBusInterface>
0027 #include <QDBusMessage>
0028 #include <QDBusPendingCall>
0029 #include <QDialog>
0030 #include <QDialogButtonBox>
0031 #include <QDirIterator>
0032 #include <QPushButton>
0033 #include <QStandardPaths>
0034 #include <QVBoxLayout>
0035 
0036 namespace KWin
0037 {
0038 
0039 static QString translatedCategory(const QString &category)
0040 {
0041     static const QVector<QString> knownCategories = {
0042         QStringLiteral("Accessibility"),
0043         QStringLiteral("Appearance"),
0044         QStringLiteral("Focus"),
0045         QStringLiteral("Show Desktop Animation"),
0046         QStringLiteral("Tools"),
0047         QStringLiteral("Virtual Desktop Switching Animation"),
0048         QStringLiteral("Window Management"),
0049         QStringLiteral("Window Open/Close Animation")};
0050 
0051     static const QVector<QString> translatedCategories = {
0052         i18nc("Category of Desktop Effects, used as section header", "Accessibility"),
0053         i18nc("Category of Desktop Effects, used as section header", "Appearance"),
0054         i18nc("Category of Desktop Effects, used as section header", "Focus"),
0055         i18nc("Category of Desktop Effects, used as section header", "Peek at Desktop Animation"),
0056         i18nc("Category of Desktop Effects, used as section header", "Tools"),
0057         i18nc("Category of Desktop Effects, used as section header", "Virtual Desktop Switching Animation"),
0058         i18nc("Category of Desktop Effects, used as section header", "Window Management"),
0059         i18nc("Category of Desktop Effects, used as section header", "Window Open/Close Animation")};
0060 
0061     const int index = knownCategories.indexOf(category);
0062     if (index == -1) {
0063         qDebug() << "Unknown category '" << category << "' and thus not translated";
0064         return category;
0065     }
0066 
0067     return translatedCategories[index];
0068 }
0069 
0070 static EffectsModel::Status effectStatus(bool enabled)
0071 {
0072     return enabled ? EffectsModel::Status::Enabled : EffectsModel::Status::Disabled;
0073 }
0074 
0075 EffectsModel::EffectsModel(QObject *parent)
0076     : QAbstractItemModel(parent)
0077 {
0078 }
0079 
0080 QHash<int, QByteArray> EffectsModel::roleNames() const
0081 {
0082     QHash<int, QByteArray> roleNames;
0083     roleNames[NameRole] = "NameRole";
0084     roleNames[DescriptionRole] = "DescriptionRole";
0085     roleNames[AuthorNameRole] = "AuthorNameRole";
0086     roleNames[AuthorEmailRole] = "AuthorEmailRole";
0087     roleNames[LicenseRole] = "LicenseRole";
0088     roleNames[VersionRole] = "VersionRole";
0089     roleNames[CategoryRole] = "CategoryRole";
0090     roleNames[ServiceNameRole] = "ServiceNameRole";
0091     roleNames[IconNameRole] = "IconNameRole";
0092     roleNames[StatusRole] = "StatusRole";
0093     roleNames[VideoRole] = "VideoRole";
0094     roleNames[WebsiteRole] = "WebsiteRole";
0095     roleNames[SupportedRole] = "SupportedRole";
0096     roleNames[ExclusiveRole] = "ExclusiveRole";
0097     roleNames[ConfigurableRole] = "ConfigurableRole";
0098     roleNames[ScriptedRole] = QByteArrayLiteral("ScriptedRole");
0099     roleNames[EnabledByDefaultRole] = "EnabledByDefaultRole";
0100     roleNames[EnabledByDefaultFunctionRole] = "EnabledByDefaultFunctionRole";
0101     roleNames[ConfigModuleRole] = "ConfigModuleRole";
0102     return roleNames;
0103 }
0104 
0105 QModelIndex EffectsModel::index(int row, int column, const QModelIndex &parent) const
0106 {
0107     if (parent.isValid() || column > 0 || column < 0 || row < 0 || row >= m_effects.count()) {
0108         return {};
0109     }
0110 
0111     return createIndex(row, column);
0112 }
0113 
0114 QModelIndex EffectsModel::parent(const QModelIndex &child) const
0115 {
0116     return {};
0117 }
0118 
0119 int EffectsModel::columnCount(const QModelIndex &parent) const
0120 {
0121     return 1;
0122 }
0123 
0124 int EffectsModel::rowCount(const QModelIndex &parent) const
0125 {
0126     if (parent.isValid()) {
0127         return 0;
0128     }
0129     return m_effects.count();
0130 }
0131 
0132 QVariant EffectsModel::data(const QModelIndex &index, int role) const
0133 {
0134     if (!index.isValid()) {
0135         return {};
0136     }
0137 
0138     const EffectData effect = m_effects.at(index.row());
0139     switch (role) {
0140     case Qt::DisplayRole:
0141     case NameRole:
0142         return effect.name;
0143     case DescriptionRole:
0144         return effect.description;
0145     case AuthorNameRole:
0146         return effect.authorName;
0147     case AuthorEmailRole:
0148         return effect.authorEmail;
0149     case LicenseRole:
0150         return effect.license;
0151     case VersionRole:
0152         return effect.version;
0153     case CategoryRole:
0154         return effect.category;
0155     case ServiceNameRole:
0156         return effect.serviceName;
0157     case IconNameRole:
0158         return effect.iconName;
0159     case StatusRole:
0160         return static_cast<int>(effect.status);
0161     case VideoRole:
0162         return effect.video;
0163     case WebsiteRole:
0164         return effect.website;
0165     case SupportedRole:
0166         return effect.supported;
0167     case ExclusiveRole:
0168         return effect.exclusiveGroup;
0169     case InternalRole:
0170         return effect.internal;
0171     case ConfigurableRole:
0172         return effect.configurable;
0173     case ScriptedRole:
0174         return effect.kind == Kind::Scripted;
0175     case EnabledByDefaultRole:
0176         return effect.enabledByDefault;
0177     case EnabledByDefaultFunctionRole:
0178         return effect.enabledByDefaultFunction;
0179     case ConfigModuleRole:
0180         return effect.configModule;
0181     default:
0182         return {};
0183     }
0184 }
0185 
0186 bool EffectsModel::setData(const QModelIndex &index, const QVariant &value, int role)
0187 {
0188     if (!index.isValid()) {
0189         return QAbstractItemModel::setData(index, value, role);
0190     }
0191 
0192     if (role == StatusRole) {
0193         // note: whenever the StatusRole is modified (even to the same value) the entry
0194         // gets marked as changed and will get saved to the config file. This means the
0195         // config file could get polluted
0196         EffectData &data = m_effects[index.row()];
0197         data.status = Status(value.toInt());
0198         data.changed = data.status != data.originalStatus;
0199         Q_EMIT dataChanged(index, index);
0200 
0201         if (data.status == Status::Enabled && !data.exclusiveGroup.isEmpty()) {
0202             // need to disable all other exclusive effects in the same category
0203             for (int i = 0; i < m_effects.size(); ++i) {
0204                 if (i == index.row()) {
0205                     continue;
0206                 }
0207                 EffectData &otherData = m_effects[i];
0208                 if (otherData.exclusiveGroup == data.exclusiveGroup) {
0209                     otherData.status = Status::Disabled;
0210                     otherData.changed = otherData.status != otherData.originalStatus;
0211                     Q_EMIT dataChanged(this->index(i, 0), this->index(i, 0));
0212                 }
0213             }
0214         }
0215 
0216         return true;
0217     }
0218 
0219     return QAbstractItemModel::setData(index, value, role);
0220 }
0221 
0222 void EffectsModel::loadBuiltInEffects(const KConfigGroup &kwinConfig)
0223 {
0224     const QString rootDirectory = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
0225                                                          QStringLiteral("kwin/builtin-effects"),
0226                                                          QStandardPaths::LocateDirectory);
0227 
0228     const QStringList nameFilters{QStringLiteral("metadata.json")};
0229     QDirIterator it(rootDirectory, nameFilters, QDir::Files, QDirIterator::Subdirectories);
0230     while (it.hasNext()) {
0231         it.next();
0232 
0233         const KPluginMetaData metaData = KPluginMetaData::fromJsonFile(it.filePath());
0234         if (!metaData.isValid()) {
0235             continue;
0236         }
0237 
0238         EffectData effect;
0239         effect.name = metaData.name();
0240         effect.description = metaData.description();
0241         effect.authorName = i18n("KWin development team");
0242         effect.authorEmail = QString(); // not used at all
0243         effect.license = metaData.license();
0244         effect.version = metaData.version();
0245         effect.untranslatedCategory = metaData.category();
0246         effect.category = translatedCategory(metaData.category());
0247         effect.serviceName = metaData.pluginId();
0248         effect.iconName = metaData.iconName();
0249         effect.enabledByDefault = metaData.isEnabledByDefault();
0250         effect.supported = true;
0251         effect.enabledByDefaultFunction = false;
0252         effect.internal = false;
0253         effect.kind = Kind::BuiltIn;
0254         effect.configModule = metaData.value(QStringLiteral("X-KDE-ConfigModule"));
0255         effect.website = QUrl(metaData.website());
0256 
0257         if (metaData.rawData().contains("org.kde.kwin.effect")) {
0258             const QJsonObject d(metaData.rawData().value("org.kde.kwin.effect").toObject());
0259             effect.exclusiveGroup = d.value("exclusiveGroup").toString();
0260             effect.video = QUrl::fromUserInput(d.value("video").toString());
0261             effect.enabledByDefaultFunction = d.value("enabledByDefaultMethod").toBool();
0262             effect.internal = d.value("internal").toBool();
0263         }
0264 
0265         const QString enabledKey = QStringLiteral("%1Enabled").arg(effect.serviceName);
0266         if (kwinConfig.hasKey(enabledKey)) {
0267             effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", effect.enabledByDefault));
0268         } else if (effect.enabledByDefaultFunction) {
0269             effect.status = Status::EnabledUndeterminded;
0270         } else {
0271             effect.status = effectStatus(effect.enabledByDefault);
0272         }
0273 
0274         effect.originalStatus = effect.status;
0275         effect.configurable = !effect.configModule.isEmpty();
0276 
0277         if (shouldStore(effect)) {
0278             m_pendingEffects << effect;
0279         }
0280     }
0281 }
0282 
0283 void EffectsModel::loadJavascriptEffects(const KConfigGroup &kwinConfig)
0284 {
0285     const auto plugins = KPackage::PackageLoader::self()->listPackages(
0286         QStringLiteral("KWin/Effect"),
0287         QStringLiteral("kwin/effects"));
0288     for (const KPluginMetaData &plugin : plugins) {
0289         EffectData effect;
0290 
0291         effect.name = plugin.name();
0292         effect.description = plugin.description();
0293         const auto authors = plugin.authors();
0294         effect.authorName = !authors.isEmpty() ? authors.first().name() : QString();
0295         effect.authorEmail = !authors.isEmpty() ? authors.first().emailAddress() : QString();
0296         effect.license = plugin.license();
0297         effect.version = plugin.version();
0298         effect.untranslatedCategory = plugin.category();
0299         effect.category = translatedCategory(plugin.category());
0300         effect.serviceName = plugin.pluginId();
0301         effect.iconName = plugin.iconName();
0302         effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", plugin.isEnabledByDefault()));
0303         effect.originalStatus = effect.status;
0304         effect.enabledByDefault = plugin.isEnabledByDefault();
0305         effect.enabledByDefaultFunction = false;
0306         effect.video = QUrl(plugin.value(QStringLiteral("X-KWin-Video-Url")));
0307         effect.website = QUrl(plugin.website());
0308         effect.supported = true;
0309         effect.exclusiveGroup = plugin.value(QStringLiteral("X-KWin-Exclusive-Category"));
0310         effect.internal = plugin.value(QStringLiteral("X-KWin-Internal"), false);
0311         effect.kind = Kind::Scripted;
0312 
0313         const QString pluginKeyword = plugin.value(QStringLiteral("X-KDE-PluginKeyword"));
0314         if (!pluginKeyword.isEmpty()) {
0315             QDir package(QFileInfo(plugin.metaDataFileName()).dir());
0316             package.cd(QStringLiteral("contents"));
0317             const QString xmlFile = package.filePath(QStringLiteral("config/main.xml"));
0318             const QString uiFile = package.filePath(QStringLiteral("ui/config.ui"));
0319             effect.configurable = QFileInfo::exists(xmlFile) && QFileInfo::exists(uiFile);
0320         } else {
0321             effect.configurable = false;
0322         }
0323 
0324         if (shouldStore(effect)) {
0325             m_pendingEffects << effect;
0326         }
0327     }
0328 }
0329 
0330 void EffectsModel::loadPluginEffects(const KConfigGroup &kwinConfig)
0331 {
0332     const auto pluginEffects = KPluginMetaData::findPlugins(QStringLiteral("kwin/effects/plugins"));
0333     for (const KPluginMetaData &pluginEffect : pluginEffects) {
0334         if (!pluginEffect.isValid()) {
0335             continue;
0336         }
0337         EffectData effect;
0338         effect.name = pluginEffect.name();
0339         effect.description = pluginEffect.description();
0340         effect.license = pluginEffect.license();
0341         effect.version = pluginEffect.version();
0342         effect.untranslatedCategory = pluginEffect.category();
0343         effect.category = translatedCategory(pluginEffect.category());
0344         effect.serviceName = pluginEffect.pluginId();
0345         effect.iconName = pluginEffect.iconName();
0346         effect.enabledByDefault = pluginEffect.isEnabledByDefault();
0347         effect.supported = true;
0348         effect.enabledByDefaultFunction = false;
0349         effect.internal = false;
0350         effect.kind = Kind::Binary;
0351         effect.configModule = pluginEffect.value(QStringLiteral("X-KDE-ConfigModule"));
0352 
0353         for (int i = 0; i < pluginEffect.authors().count(); ++i) {
0354             effect.authorName.append(pluginEffect.authors().at(i).name());
0355             effect.authorEmail.append(pluginEffect.authors().at(i).emailAddress());
0356             if (i + 1 < pluginEffect.authors().count()) {
0357                 effect.authorName.append(", ");
0358                 effect.authorEmail.append(", ");
0359             }
0360         }
0361 
0362         if (pluginEffect.rawData().contains("org.kde.kwin.effect")) {
0363             const QJsonObject d(pluginEffect.rawData().value("org.kde.kwin.effect").toObject());
0364             effect.exclusiveGroup = d.value("exclusiveGroup").toString();
0365             effect.video = QUrl::fromUserInput(d.value("video").toString());
0366             effect.enabledByDefaultFunction = d.value("enabledByDefaultMethod").toBool();
0367         }
0368 
0369         effect.website = QUrl(pluginEffect.website());
0370 
0371         const QString enabledKey = QStringLiteral("%1Enabled").arg(effect.serviceName);
0372         if (kwinConfig.hasKey(enabledKey)) {
0373             effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", effect.enabledByDefault));
0374         } else if (effect.enabledByDefaultFunction) {
0375             effect.status = Status::EnabledUndeterminded;
0376         } else {
0377             effect.status = effectStatus(effect.enabledByDefault);
0378         }
0379 
0380         effect.originalStatus = effect.status;
0381 
0382         effect.configurable = !effect.configModule.isEmpty();
0383 
0384         if (shouldStore(effect)) {
0385             m_pendingEffects << effect;
0386         }
0387     }
0388 }
0389 
0390 void EffectsModel::load(LoadOptions options)
0391 {
0392     KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), "Plugins");
0393 
0394     m_pendingEffects.clear();
0395     loadBuiltInEffects(kwinConfig);
0396     loadJavascriptEffects(kwinConfig);
0397     loadPluginEffects(kwinConfig);
0398 
0399     std::sort(m_pendingEffects.begin(), m_pendingEffects.end(),
0400               [](const EffectData &a, const EffectData &b) {
0401                   if (a.category == b.category) {
0402                       if (a.exclusiveGroup == b.exclusiveGroup) {
0403                           return a.name < b.name;
0404                       }
0405                       return a.exclusiveGroup < b.exclusiveGroup;
0406                   }
0407                   return a.category < b.category;
0408               });
0409 
0410     auto commit = [this, options] {
0411         if (options == LoadOptions::KeepDirty) {
0412             for (const EffectData &oldEffect : std::as_const(m_effects)) {
0413                 if (!oldEffect.changed) {
0414                     continue;
0415                 }
0416                 auto effectIt = std::find_if(m_pendingEffects.begin(), m_pendingEffects.end(),
0417                                              [oldEffect](const EffectData &data) {
0418                                                  return data.serviceName == oldEffect.serviceName;
0419                                              });
0420                 if (effectIt == m_pendingEffects.end()) {
0421                     continue;
0422                 }
0423                 effectIt->status = oldEffect.status;
0424                 effectIt->changed = effectIt->status != effectIt->originalStatus;
0425             }
0426         }
0427 
0428         beginResetModel();
0429         m_effects = m_pendingEffects;
0430         m_pendingEffects.clear();
0431         endResetModel();
0432 
0433         Q_EMIT loaded();
0434     };
0435 
0436     OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"),
0437                                          QStringLiteral("/Effects"),
0438                                          QDBusConnection::sessionBus());
0439 
0440     if (interface.isValid()) {
0441         QStringList effectNames;
0442         effectNames.reserve(m_pendingEffects.count());
0443         for (const EffectData &data : std::as_const(m_pendingEffects)) {
0444             effectNames.append(data.serviceName);
0445         }
0446 
0447         const int serial = ++m_lastSerial;
0448 
0449         QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(interface.areEffectsSupported(effectNames), this);
0450         connect(watcher, &QDBusPendingCallWatcher::finished, this, [=, this](QDBusPendingCallWatcher *self) {
0451             self->deleteLater();
0452 
0453             if (m_lastSerial != serial) {
0454                 return;
0455             }
0456 
0457             const QDBusPendingReply<QList<bool>> reply = *self;
0458             if (reply.isError()) {
0459                 commit();
0460                 return;
0461             }
0462 
0463             const QList<bool> supportedValues = reply.value();
0464             if (supportedValues.count() != effectNames.count()) {
0465                 return;
0466             }
0467 
0468             for (int i = 0; i < effectNames.size(); ++i) {
0469                 const bool supported = supportedValues.at(i);
0470                 const QString effectName = effectNames.at(i);
0471 
0472                 auto it = std::find_if(m_pendingEffects.begin(), m_pendingEffects.end(),
0473                                        [effectName](const EffectData &data) {
0474                                            return data.serviceName == effectName;
0475                                        });
0476                 if (it == m_pendingEffects.end()) {
0477                     continue;
0478                 }
0479 
0480                 if ((*it).supported != supported) {
0481                     (*it).supported = supported;
0482                 }
0483             }
0484 
0485             commit();
0486         });
0487     } else {
0488         commit();
0489     }
0490 }
0491 
0492 void EffectsModel::updateEffectStatus(const QModelIndex &rowIndex, Status effectState)
0493 {
0494     setData(rowIndex, static_cast<int>(effectState), StatusRole);
0495 }
0496 
0497 void EffectsModel::save()
0498 {
0499     KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), "Plugins");
0500 
0501     QVector<EffectData> dirtyEffects;
0502 
0503     for (EffectData &effect : m_effects) {
0504         if (!effect.changed) {
0505             continue;
0506         }
0507 
0508         effect.changed = false;
0509         effect.originalStatus = effect.status;
0510 
0511         const QString key = effect.serviceName + QStringLiteral("Enabled");
0512         const bool shouldEnable = (effect.status != Status::Disabled);
0513         const bool restoreToDefault = effect.enabledByDefaultFunction
0514             ? effect.status == Status::EnabledUndeterminded
0515             : shouldEnable == effect.enabledByDefault;
0516         if (restoreToDefault) {
0517             kwinConfig.deleteEntry(key);
0518         } else {
0519             kwinConfig.writeEntry(key, shouldEnable);
0520         }
0521 
0522         dirtyEffects.append(effect);
0523     }
0524 
0525     if (dirtyEffects.isEmpty()) {
0526         return;
0527     }
0528 
0529     kwinConfig.sync();
0530 
0531     OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"),
0532                                          QStringLiteral("/Effects"),
0533                                          QDBusConnection::sessionBus());
0534 
0535     if (!interface.isValid()) {
0536         return;
0537     }
0538 
0539     // Unload effects first, it's need to ensure that switching between mutually exclusive
0540     // effects works as expected, for example so global shortcuts are handed over, etc.
0541     auto split = std::partition(dirtyEffects.begin(), dirtyEffects.end(), [](const EffectData &data) {
0542         return data.status == Status::Disabled;
0543     });
0544 
0545     for (auto it = dirtyEffects.begin(); it != split; ++it) {
0546         interface.unloadEffect(it->serviceName);
0547     }
0548 
0549     for (auto it = split; it != dirtyEffects.end(); ++it) {
0550         interface.loadEffect(it->serviceName);
0551     }
0552 }
0553 
0554 void EffectsModel::defaults()
0555 {
0556     for (int i = 0; i < m_effects.count(); ++i) {
0557         const auto &effect = m_effects.at(i);
0558         if (effect.enabledByDefaultFunction && effect.status != Status::EnabledUndeterminded) {
0559             updateEffectStatus(index(i, 0), Status::EnabledUndeterminded);
0560         } else if (static_cast<bool>(effect.status) != effect.enabledByDefault) {
0561             updateEffectStatus(index(i, 0), effect.enabledByDefault ? Status::Enabled : Status::Disabled);
0562         }
0563     }
0564 }
0565 
0566 bool EffectsModel::isDefaults() const
0567 {
0568     return std::all_of(m_effects.constBegin(), m_effects.constEnd(), [](const EffectData &effect) {
0569         if (effect.enabledByDefaultFunction && effect.status != Status::EnabledUndeterminded) {
0570             return false;
0571         }
0572         if (static_cast<bool>(effect.status) != effect.enabledByDefault) {
0573             return false;
0574         }
0575         return true;
0576     });
0577 }
0578 
0579 bool EffectsModel::needsSave() const
0580 {
0581     return std::any_of(m_effects.constBegin(), m_effects.constEnd(),
0582                        [](const EffectData &data) {
0583                            return data.changed;
0584                        });
0585 }
0586 
0587 QModelIndex EffectsModel::findByPluginId(const QString &pluginId) const
0588 {
0589     auto it = std::find_if(m_effects.constBegin(), m_effects.constEnd(),
0590                            [pluginId](const EffectData &data) {
0591                                return data.serviceName == pluginId;
0592                            });
0593     if (it == m_effects.constEnd()) {
0594         return {};
0595     }
0596     return index(std::distance(m_effects.constBegin(), it), 0);
0597 }
0598 
0599 static KCModule *loadBinaryConfig(const QString &configModule, QWidget *parent)
0600 {
0601     const KPluginMetaData metaData(QStringLiteral("kwin/effects/configs/") + configModule);
0602     return KCModuleLoader::loadModule(metaData, parent);
0603 }
0604 
0605 static KCModule *findScriptedConfig(const QString &pluginId, QObject *parent)
0606 {
0607     KPluginMetaData metaData(QStringLiteral("kwin/effects/configs/kcm_kwin4_genericscripted"));
0608     return KPluginFactory::instantiatePlugin<KCModule>(metaData, parent, QVariantList{pluginId}).plugin;
0609 }
0610 
0611 void EffectsModel::requestConfigure(const QModelIndex &index, QWindow *transientParent)
0612 {
0613     if (!index.isValid()) {
0614         return;
0615     }
0616 
0617     auto dialog = new QDialog();
0618 
0619     const bool scripted = index.data(ScriptedRole).toBool();
0620 
0621     KCModule *module = nullptr;
0622 
0623     if (scripted) {
0624         module = findScriptedConfig(index.data(ServiceNameRole).toString(), dialog);
0625     } else {
0626         const QString configModule = index.data(ConfigModuleRole).toString();
0627         module = loadBinaryConfig(configModule, dialog);
0628     }
0629 
0630     dialog->setWindowTitle(index.data(NameRole).toString());
0631     dialog->winId();
0632     dialog->windowHandle()->setTransientParent(transientParent);
0633 
0634     auto buttons = new QDialogButtonBox(
0635         QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults,
0636         dialog);
0637     connect(buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
0638     connect(buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
0639     connect(buttons->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked,
0640             module, &KCModule::defaults);
0641     connect(module, &KCModule::defaulted, this, [=](bool defaulted) {
0642         buttons->button(QDialogButtonBox::RestoreDefaults)->setEnabled(!defaulted);
0643     });
0644 
0645     auto layout = new QVBoxLayout(dialog);
0646     layout->addWidget(module);
0647     layout->addWidget(buttons);
0648 
0649     connect(dialog, &QDialog::accepted, module, &KCModule::save);
0650 
0651     dialog->setModal(true);
0652     dialog->setAttribute(Qt::WA_DeleteOnClose);
0653     dialog->show();
0654 }
0655 
0656 bool EffectsModel::shouldStore(const EffectData &data) const
0657 {
0658     return true;
0659 }
0660 
0661 }