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 }