effectsmodel.cpp 23.3 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1 2 3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5 6
    SPDX-FileCopyrightText: 2013 Antonis Tsiapaliokas <kok3rs@gmail.com>
    SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
7

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
8 9
    SPDX-License-Identifier: GPL-2.0-or-later
*/
10

11
#include "effectsmodel.h"
12 13 14 15 16 17

#include <config-kwin.h>
#include <effect_builtins.h>
#include <kwin_effects_interface.h>

#include <KAboutData>
18
#include <KCModule>
19 20 21 22 23 24 25 26 27 28
#include <KLocalizedString>
#include <KPackage/PackageLoader>
#include <KPluginLoader>
#include <KPluginMetaData>
#include <KPluginTrader>

#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#include <QDBusPendingCall>
29 30 31 32
#include <QDialog>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

namespace KWin
{

static QString translatedCategory(const QString &category)
{
    static const QVector<QString> knownCategories = {
        QStringLiteral("Accessibility"),
        QStringLiteral("Appearance"),
        QStringLiteral("Focus"),
        QStringLiteral("Show Desktop Animation"),
        QStringLiteral("Tools"),
        QStringLiteral("Virtual Desktop Switching Animation"),
        QStringLiteral("Window Management"),
        QStringLiteral("Window Open/Close Animation")
    };

    static const QVector<QString> translatedCategories = {
        i18nc("Category of Desktop Effects, used as section header", "Accessibility"),
        i18nc("Category of Desktop Effects, used as section header", "Appearance"),
        i18nc("Category of Desktop Effects, used as section header", "Focus"),
        i18nc("Category of Desktop Effects, used as section header", "Show Desktop Animation"),
        i18nc("Category of Desktop Effects, used as section header", "Tools"),
        i18nc("Category of Desktop Effects, used as section header", "Virtual Desktop Switching Animation"),
        i18nc("Category of Desktop Effects, used as section header", "Window Management"),
        i18nc("Category of Desktop Effects, used as section header", "Window Open/Close Animation")
    };

    const int index = knownCategories.indexOf(category);
    if (index == -1) {
        qDebug() << "Unknown category '" << category << "' and thus not translated";
        return category;
    }

    return translatedCategories[index];
}

70
static EffectsModel::Status effectStatus(bool enabled)
71
{
72
    return enabled ? EffectsModel::Status::Enabled : EffectsModel::Status::Disabled;
73 74
}

75
EffectsModel::EffectsModel(QObject *parent)
76 77 78 79
    : QAbstractItemModel(parent)
{
}

80
QHash<int, QByteArray> EffectsModel::roleNames() const
81 82 83 84 85 86 87 88 89 90 91
{
    QHash<int, QByteArray> roleNames;
    roleNames[NameRole] = "NameRole";
    roleNames[DescriptionRole] = "DescriptionRole";
    roleNames[AuthorNameRole] = "AuthorNameRole";
    roleNames[AuthorEmailRole] = "AuthorEmailRole";
    roleNames[LicenseRole] = "LicenseRole";
    roleNames[VersionRole] = "VersionRole";
    roleNames[CategoryRole] = "CategoryRole";
    roleNames[ServiceNameRole] = "ServiceNameRole";
    roleNames[IconNameRole] = "IconNameRole";
92
    roleNames[StatusRole] = "StatusRole";
93 94 95 96 97 98 99
    roleNames[VideoRole] = "VideoRole";
    roleNames[WebsiteRole] = "WebsiteRole";
    roleNames[SupportedRole] = "SupportedRole";
    roleNames[ExclusiveRole] = "ExclusiveRole";
    roleNames[ConfigurableRole] = "ConfigurableRole";
    roleNames[ScriptedRole] = QByteArrayLiteral("ScriptedRole");
    roleNames[EnabledByDefaultRole] = "EnabledByDefaultRole";
100
    roleNames[EnabledByDefaultFunctionRole] = "EnabledByDefaultFunctionRole";
101
    roleNames[ConfigModuleRole] = "ConfigModuleRole";
102 103 104
    return roleNames;
}

105
QModelIndex EffectsModel::index(int row, int column, const QModelIndex &parent) const
106
{
107
    if (parent.isValid() || column > 0 || column < 0 || row < 0 || row >= m_effects.count()) {
108 109 110 111 112 113
        return {};
    }

    return createIndex(row, column);
}

114
QModelIndex EffectsModel::parent(const QModelIndex &child) const
115 116 117 118 119
{
    Q_UNUSED(child)
    return {};
}

120
int EffectsModel::columnCount(const QModelIndex &parent) const
121 122 123 124 125
{
    Q_UNUSED(parent)
    return 1;
}

126
int EffectsModel::rowCount(const QModelIndex &parent) const
127 128 129 130
{
    if (parent.isValid()) {
        return 0;
    }
131
    return m_effects.count();
132 133
}

134
QVariant EffectsModel::data(const QModelIndex &index, int role) const
135 136 137 138 139
{
    if (!index.isValid()) {
        return {};
    }

140
    const EffectData effect = m_effects.at(index.row());
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
    switch (role) {
    case Qt::DisplayRole:
    case NameRole:
        return effect.name;
    case DescriptionRole:
        return effect.description;
    case AuthorNameRole:
        return effect.authorName;
    case AuthorEmailRole:
        return effect.authorEmail;
    case LicenseRole:
        return effect.license;
    case VersionRole:
        return effect.version;
    case CategoryRole:
        return effect.category;
    case ServiceNameRole:
        return effect.serviceName;
    case IconNameRole:
        return effect.iconName;
161 162
    case StatusRole:
        return static_cast<int>(effect.status);
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
    case VideoRole:
        return effect.video;
    case WebsiteRole:
        return effect.website;
    case SupportedRole:
        return effect.supported;
    case ExclusiveRole:
        return effect.exclusiveGroup;
    case InternalRole:
        return effect.internal;
    case ConfigurableRole:
        return effect.configurable;
    case ScriptedRole:
        return effect.kind == Kind::Scripted;
    case EnabledByDefaultRole:
        return effect.enabledByDefault;
179 180
    case EnabledByDefaultFunctionRole:
        return effect.enabledByDefaultFunction;
181 182
    case ConfigModuleRole:
        return effect.configModule;
183 184 185 186 187
    default:
        return {};
    }
}

188
bool EffectsModel::setData(const QModelIndex &index, const QVariant &value, int role)
189 190 191 192 193
{
    if (!index.isValid()) {
        return QAbstractItemModel::setData(index, value, role);
    }

194
    if (role == StatusRole) {
195 196 197
        // note: whenever the StatusRole is modified (even to the same value) the entry
        // gets marked as changed and will get saved to the config file. This means the
        // config file could get polluted
198
        EffectData &data = m_effects[index.row()];
199 200
        data.status = Status(value.toInt());
        data.changed = data.status != data.originalStatus;
201 202
        emit dataChanged(index, index);

203
        if (data.status == Status::Enabled && !data.exclusiveGroup.isEmpty()) {
204
            // need to disable all other exclusive effects in the same category
205
            for (int i = 0; i < m_effects.size(); ++i) {
206 207 208
                if (i == index.row()) {
                    continue;
                }
209
                EffectData &otherData = m_effects[i];
210
                if (otherData.exclusiveGroup == data.exclusiveGroup) {
211 212
                    otherData.status = Status::Disabled;
                    otherData.changed = otherData.status != otherData.originalStatus;
213 214 215 216 217 218 219 220 221 222 223
                    emit dataChanged(this->index(i, 0), this->index(i, 0));
                }
            }
        }

        return true;
    }

    return QAbstractItemModel::setData(index, value, role);
}

224
void EffectsModel::loadBuiltInEffects(const KConfigGroup &kwinConfig)
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
{
    const auto builtins = BuiltInEffects::availableEffects();
    for (auto builtin : builtins) {
        const BuiltInEffects::EffectData &data = BuiltInEffects::effectData(builtin);
        EffectData effect;
        effect.name = data.displayName;
        effect.description = data.comment;
        effect.authorName = i18n("KWin development team");
        effect.authorEmail = QString(); // not used at all
        effect.license = QStringLiteral("GPL");
        effect.version = QStringLiteral(KWIN_VERSION_STRING);
        effect.untranslatedCategory = data.category;
        effect.category = translatedCategory(data.category);
        effect.serviceName = data.name;
        effect.iconName = QStringLiteral("preferences-system-windows");
        effect.enabledByDefault = data.enabled;
        effect.enabledByDefaultFunction = (data.enabledFunction != nullptr);
        const QString enabledKey = QStringLiteral("%1Enabled").arg(effect.serviceName);
        if (kwinConfig.hasKey(enabledKey)) {
244
            effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", effect.enabledByDefault));
245
        } else if (data.enabledFunction != nullptr) {
246
            effect.status = Status::EnabledUndeterminded;
247
        } else {
248
            effect.status = effectStatus(effect.enabledByDefault);
249
        }
250
        effect.originalStatus = effect.status;
251 252 253 254 255 256
        effect.video = data.video;
        effect.website = QUrl();
        effect.supported = true;
        effect.exclusiveGroup = data.exclusiveCategory;
        effect.internal = data.internal;
        effect.kind = Kind::BuiltIn;
257
        effect.configModule = data.configModule;
258

259
        effect.configurable = !effect.configModule.isEmpty();
260 261

        if (shouldStore(effect)) {
262
            m_pendingEffects << effect;
263 264 265 266
        }
    }
}

267
void EffectsModel::loadJavascriptEffects(const KConfigGroup &kwinConfig)
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
{
    const auto plugins = KPackage::PackageLoader::self()->listPackages(
        QStringLiteral("KWin/Effect"),
        QStringLiteral("kwin/effects")
    );
    for (const KPluginMetaData &metaData : plugins) {
        KPluginInfo plugin(metaData);
        EffectData effect;

        effect.name = plugin.name();
        effect.description = plugin.comment();
        effect.authorName = plugin.author();
        effect.authorEmail = plugin.email();
        effect.license = plugin.license();
        effect.version = plugin.version();
        effect.untranslatedCategory = plugin.category();
        effect.category = translatedCategory(plugin.category());
        effect.serviceName = plugin.pluginName();
        effect.iconName = plugin.icon();
287 288
        effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", plugin.isPluginEnabledByDefault()));
        effect.originalStatus = effect.status;
289 290 291
        effect.enabledByDefault = plugin.isPluginEnabledByDefault();
        effect.enabledByDefaultFunction = false;
        effect.video = plugin.property(QStringLiteral("X-KWin-Video-Url")).toUrl();
292
        effect.website = QUrl(plugin.website());
293 294 295 296 297 298 299 300 301 302 303 304 305 306
        effect.supported = true;
        effect.exclusiveGroup = plugin.property(QStringLiteral("X-KWin-Exclusive-Category")).toString();
        effect.internal = plugin.property(QStringLiteral("X-KWin-Internal")).toBool();
        effect.kind = Kind::Scripted;

        const QString pluginKeyword = plugin.property(QStringLiteral("X-KDE-PluginKeyword")).toString();
        if (!pluginKeyword.isEmpty()) {
             // scripted effects have their pluginName() as the keyword
             effect.configurable = plugin.property(QStringLiteral("X-KDE-ParentComponents")).toString() == pluginKeyword;
        } else {
            effect.configurable = false;
        }

        if (shouldStore(effect)) {
307
            m_pendingEffects << effect;
308 309 310 311
        }
    }
}

312
void EffectsModel::loadPluginEffects(const KConfigGroup &kwinConfig)
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
{
    const auto pluginEffects = KPluginLoader::findPlugins(
        QStringLiteral("kwin/effects/plugins/"),
        [](const KPluginMetaData &data) {
            return data.serviceTypes().contains(QStringLiteral("KWin/Effect"));
        }
    );
    for (const KPluginMetaData &pluginEffect : pluginEffects) {
        if (!pluginEffect.isValid()) {
            continue;
        }
        EffectData effect;
        effect.name = pluginEffect.name();
        effect.description = pluginEffect.description();
        effect.license = pluginEffect.license();
        effect.version = pluginEffect.version();
        effect.untranslatedCategory = pluginEffect.category();
        effect.category = translatedCategory(pluginEffect.category());
        effect.serviceName = pluginEffect.pluginId();
        effect.iconName = pluginEffect.iconName();
        effect.enabledByDefault = pluginEffect.isEnabledByDefault();
        effect.supported = true;
        effect.enabledByDefaultFunction = false;
        effect.internal = false;
        effect.kind = Kind::Binary;
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
        effect.configModule = pluginEffect.value(QStringLiteral("X-KDE-ConfigModule"));

        // Compatibility with plugins that don't have ConfigModule in their metadata
        // TODO KF6 remove
        if (effect.configModule.isEmpty()) {

            const QList<KPluginInfo> infos = KPluginTrader::self()->query(
                QStringLiteral("kwin/effects/configs/"),
                QString(),
                QStringLiteral("'%1' in [X-KDE-ParentComponents]").arg(pluginEffect.pluginId())
            );

            if (!infos.isEmpty()) {
                effect.configModule = infos.first().pluginName();
            }
        }
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370

        for (int i = 0; i < pluginEffect.authors().count(); ++i) {
            effect.authorName.append(pluginEffect.authors().at(i).name());
            effect.authorEmail.append(pluginEffect.authors().at(i).emailAddress());
            if (i+1 < pluginEffect.authors().count()) {
                effect.authorName.append(", ");
                effect.authorEmail.append(", ");
            }
        }

        if (pluginEffect.rawData().contains("org.kde.kwin.effect")) {
            const QJsonObject d(pluginEffect.rawData().value("org.kde.kwin.effect").toObject());
            effect.exclusiveGroup = d.value("exclusiveGroup").toString();
            effect.video = QUrl::fromUserInput(d.value("video").toString());
            effect.enabledByDefaultFunction = d.value("enabledByDefaultMethod").toBool();
        }

371
        effect.website = QUrl(pluginEffect.website());
372 373 374

        const QString enabledKey = QStringLiteral("%1Enabled").arg(effect.serviceName);
        if (kwinConfig.hasKey(enabledKey)) {
375
            effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", effect.enabledByDefault));
376
        } else if (effect.enabledByDefaultFunction) {
377
            effect.status = Status::EnabledUndeterminded;
378
        } else {
379
            effect.status = effectStatus(effect.enabledByDefault);
380 381
        }

382
        effect.originalStatus = effect.status;
383

384
        effect.configurable = !effect.configModule.isEmpty();
385 386

        if (shouldStore(effect)) {
387
            m_pendingEffects << effect;
388 389 390 391
        }
    }
}

392
void EffectsModel::load(LoadOptions options)
393 394 395
{
    KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), "Plugins");

396
    m_pendingEffects.clear();
397
    loadBuiltInEffects(kwinConfig);
398
    loadJavascriptEffects(kwinConfig);
399
    loadPluginEffects(kwinConfig);
400

401
    std::sort(m_pendingEffects.begin(), m_pendingEffects.end(),
402 403 404 405
        [](const EffectData &a, const EffectData &b) {
            if (a.category == b.category) {
                if (a.exclusiveGroup == b.exclusiveGroup) {
                    return a.name < b.name;
406
                }
407
                return a.exclusiveGroup < b.exclusiveGroup;
408
            }
409
            return a.category < b.category;
410
        }
411
    );
412

413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
    auto commit = [this, options] {
        if (options == LoadOptions::KeepDirty) {
            for (const EffectData &oldEffect : m_effects) {
                if (!oldEffect.changed) {
                    continue;
                }
                auto effectIt = std::find_if(m_pendingEffects.begin(), m_pendingEffects.end(),
                    [oldEffect](const EffectData &data) {
                        return data.serviceName == oldEffect.serviceName;
                    }
                );
                if (effectIt == m_pendingEffects.end()) {
                    continue;
                }
                effectIt->status = oldEffect.status;
                effectIt->changed = effectIt->status != effectIt->originalStatus;
429 430
            }
        }
431 432 433 434 435 436 437 438

        beginResetModel();
        m_effects = m_pendingEffects;
        m_pendingEffects.clear();
        endResetModel();

        emit loaded();
    };
439 440 441 442 443 444 445

    OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"),
                                         QStringLiteral("/Effects"),
                                         QDBusConnection::sessionBus());

    if (interface.isValid()) {
        QStringList effectNames;
446 447
        effectNames.reserve(m_pendingEffects.count());
        for (const EffectData &data : m_pendingEffects) {
448 449 450
            effectNames.append(data.serviceName);
        }

451 452
        const int serial = ++m_lastSerial;

453
        QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(interface.areEffectsSupported(effectNames), this);
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
        connect(watcher, &QDBusPendingCallWatcher::finished, this,
            [=](QDBusPendingCallWatcher *self) {
                self->deleteLater();

                if (m_lastSerial != serial) {
                    return;
                }

                const QDBusPendingReply<QList<bool > > reply = *self;
                if (reply.isError()) {
                    commit();
                    return;
                }

                const QList<bool> supportedValues = reply.value();
                if (supportedValues.count() != effectNames.count()) {
                    return;
                }

473
                for (int i = 0; i < effectNames.size(); ++i) {
474 475 476 477 478 479
                    const bool supported = supportedValues.at(i);
                    const QString effectName = effectNames.at(i);

                    auto it = std::find_if(m_pendingEffects.begin(), m_pendingEffects.end(),
                        [effectName](const EffectData &data) {
                            return data.serviceName == effectName;
480
                        }
481 482 483 484 485 486 487
                    );
                    if (it == m_pendingEffects.end()) {
                        continue;
                    }

                    if ((*it).supported != supported) {
                        (*it).supported = supported;
488 489
                    }
                }
490 491

                commit();
492
            }
493 494 495
        );
    } else {
        commit();
496 497 498
    }
}

499
void EffectsModel::updateEffectStatus(const QModelIndex &rowIndex, Status effectState)
500
{
501
    setData(rowIndex, static_cast<int>(effectState), StatusRole);
502 503
}

504
void EffectsModel::save()
505 506 507
{
    KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), "Plugins");

508 509
    QVector<EffectData> dirtyEffects;

510
    for (EffectData &effect : m_effects) {
511 512 513
        if (!effect.changed) {
            continue;
        }
514

515
        effect.changed = false;
516
        effect.originalStatus = effect.status;
517 518

        const QString key = effect.serviceName + QStringLiteral("Enabled");
519
        const bool shouldEnable = (effect.status != Status::Disabled);
520
        const bool restoreToDefault = effect.enabledByDefaultFunction
521
            ? effect.status == Status::EnabledUndeterminded
522 523 524 525 526 527
            : shouldEnable == effect.enabledByDefault;
        if (restoreToDefault) {
            kwinConfig.deleteEntry(key);
        } else {
            kwinConfig.writeEntry(key, shouldEnable);
        }
528 529 530 531 532 533

        dirtyEffects.append(effect);
    }

    if (dirtyEffects.isEmpty()) {
        return;
534 535 536
    }

    kwinConfig.sync();
537 538 539 540 541 542 543 544 545 546

    OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"),
                                         QStringLiteral("/Effects"),
                                         QDBusConnection::sessionBus());

    if (!interface.isValid()) {
        return;
    }

    for (const EffectData &effect : dirtyEffects) {
547
        if (effect.status != Status::Disabled) {
548 549 550 551 552
            interface.loadEffect(effect.serviceName);
        } else {
            interface.unloadEffect(effect.serviceName);
        }
    }
553 554
}

555
void EffectsModel::defaults()
556
{
557 558
    for (int i = 0; i < m_effects.count(); ++i) {
        const auto &effect = m_effects.at(i);
559
        if (effect.enabledByDefaultFunction && effect.status != Status::EnabledUndeterminded) {
560
            updateEffectStatus(index(i, 0), Status::EnabledUndeterminded);
561
        } else if (static_cast<bool>(effect.status) != effect.enabledByDefault) {
562 563 564 565 566
            updateEffectStatus(index(i, 0), effect.enabledByDefault ? Status::Enabled : Status::Disabled);
        }
    }
}

567 568 569 570 571 572
bool EffectsModel::isDefaults() const
{
    return std::all_of(m_effects.constBegin(), m_effects.constEnd(), [](const EffectData &effect) {
        if (effect.enabledByDefaultFunction && effect.status != Status::EnabledUndeterminded) {
            return false;
        }
573
        if (static_cast<bool>(effect.status) != effect.enabledByDefault) {
574 575 576 577 578 579
            return false;
        }
        return true;
    });
}

580
bool EffectsModel::needsSave() const
581
{
582
    return std::any_of(m_effects.constBegin(), m_effects.constEnd(),
583 584 585 586 587 588
        [](const EffectData &data) {
            return data.changed;
        }
    );
}

589
QModelIndex EffectsModel::findByPluginId(const QString &pluginId) const
590
{
591
    auto it = std::find_if(m_effects.constBegin(), m_effects.constEnd(),
592 593 594 595
        [pluginId](const EffectData &data) {
            return data.serviceName == pluginId;
        }
    );
596
    if (it == m_effects.constEnd()) {
597 598
        return {};
    }
599
    return index(std::distance(m_effects.constBegin(), it), 0);
600 601
}

602
static KCModule *loadBinaryConfig(const QString &configModule, QObject *parent)
603
{
604 605 606 607 608 609 610 611 612 613
    const QVector<KPluginMetaData> offers = KPluginLoader::findPluginsById(QStringLiteral("kwin/effects/configs/"), configModule);

    if (offers.isEmpty()) {
        return nullptr;
    }

    KPluginLoader loader(offers.first().fileName());
    KPluginFactory *factory = loader.factory();

    return factory->create<KCModule>(parent);
614 615 616 617
}

static KCModule *findScriptedConfig(const QString &pluginId, QObject *parent)
{
618
    const QVector<KPluginMetaData> offers = KPluginLoader::findPluginsById(QStringLiteral("kwin/effects/configs/"), QStringLiteral("kcm_kwin4_genericscripted"));
619 620 621 622 623

    if (offers.isEmpty()) {
        return nullptr;
    }

624 625
    const KPluginMetaData &generic = offers.first();
    KPluginLoader loader(generic.fileName());
626 627 628 629 630 631 632 633
    KPluginFactory *factory = loader.factory();
    if (!factory) {
        return nullptr;
    }

    return factory->create<KCModule>(pluginId, parent);
}

634
void EffectsModel::requestConfigure(const QModelIndex &index, QWindow *transientParent)
635 636 637 638 639
{
    if (!index.isValid()) {
        return;
    }

640
    auto dialog = new QDialog();
641

642 643 644 645 646 647 648 649 650 651 652
    const bool scripted = index.data(ScriptedRole).toBool();

    KCModule *module = nullptr;

    if (scripted) {
        module = findScriptedConfig(index.data(ServiceNameRole).toString(), dialog);
    } else {
        const QString configModule = index.data(ConfigModuleRole).toString();
        module = loadBinaryConfig(configModule, dialog);
    }

653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
    if (!module) {
        delete dialog;
        return;
    }

    dialog->setWindowTitle(index.data(NameRole).toString());
    dialog->winId();
    dialog->windowHandle()->setTransientParent(transientParent);

    auto buttons = new QDialogButtonBox(
        QDialogButtonBox::Ok |
        QDialogButtonBox::Cancel |
        QDialogButtonBox::RestoreDefaults,
        dialog
    );
    connect(buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
    connect(buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
    connect(buttons->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked,
        module, &KCModule::defaults);
672 673 674
    connect(module, &KCModule::defaulted, this, [=](bool defaulted) {
        buttons->button(QDialogButtonBox::RestoreDefaults)->setEnabled(!defaulted);
    });
675 676 677 678 679

    auto layout = new QVBoxLayout(dialog);
    layout->addWidget(module);
    layout->addWidget(buttons);

680
    connect(dialog, &QDialog::accepted, module, &KCModule::save);
681

682 683 684
    dialog->setModal(true);
    dialog->setAttribute(Qt::WA_DeleteOnClose);
    dialog->show();
685 686
}

687
bool EffectsModel::shouldStore(const EffectData &data) const
688 689 690 691 692 693
{
    Q_UNUSED(data)
    return true;
}

}