effectsmodel.cpp 23.3 KB
Newer Older
1 2 3 4 5
/********************************************************************
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 2013 Antonis Tsiapaliokas <kok3rs@gmail.com>
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
6
Copyright (C) 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/

22
#include "effectsmodel.h"
23 24 25 26 27 28

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

#include <KAboutData>
29
#include <KCModule>
30 31 32 33 34 35 36 37 38 39
#include <KLocalizedString>
#include <KPackage/PackageLoader>
#include <KPluginLoader>
#include <KPluginMetaData>
#include <KPluginTrader>

#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#include <QDBusPendingCall>
40 41 42 43
#include <QDialog>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
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 70 71 72 73 74 75 76 77 78 79 80

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];
}

81
static EffectsModel::Status effectStatus(bool enabled)
82
{
83
    return enabled ? EffectsModel::Status::Enabled : EffectsModel::Status::Disabled;
84 85
}

86
EffectsModel::EffectsModel(QObject *parent)
87 88 89 90
    : QAbstractItemModel(parent)
{
}

91
QHash<int, QByteArray> EffectsModel::roleNames() const
92 93 94 95 96 97 98 99 100 101 102
{
    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";
103
    roleNames[StatusRole] = "StatusRole";
104 105 106 107 108 109 110 111 112 113
    roleNames[VideoRole] = "VideoRole";
    roleNames[WebsiteRole] = "WebsiteRole";
    roleNames[SupportedRole] = "SupportedRole";
    roleNames[ExclusiveRole] = "ExclusiveRole";
    roleNames[ConfigurableRole] = "ConfigurableRole";
    roleNames[ScriptedRole] = QByteArrayLiteral("ScriptedRole");
    roleNames[EnabledByDefaultRole] = "EnabledByDefaultRole";
    return roleNames;
}

114
QModelIndex EffectsModel::index(int row, int column, const QModelIndex &parent) const
115
{
116
    if (parent.isValid() || column > 0 || column < 0 || row < 0 || row >= m_effects.count()) {
117 118 119 120 121 122
        return {};
    }

    return createIndex(row, column);
}

123
QModelIndex EffectsModel::parent(const QModelIndex &child) const
124 125 126 127 128
{
    Q_UNUSED(child)
    return {};
}

129
int EffectsModel::columnCount(const QModelIndex &parent) const
130 131 132 133 134
{
    Q_UNUSED(parent)
    return 1;
}

135
int EffectsModel::rowCount(const QModelIndex &parent) const
136 137 138 139
{
    if (parent.isValid()) {
        return 0;
    }
140
    return m_effects.count();
141 142
}

143
QVariant EffectsModel::data(const QModelIndex &index, int role) const
144 145 146 147 148
{
    if (!index.isValid()) {
        return {};
    }

149
    const EffectData effect = m_effects.at(index.row());
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
    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;
170 171
    case StatusRole:
        return static_cast<int>(effect.status);
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
    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;
    default:
        return {};
    }
}

193
bool EffectsModel::setData(const QModelIndex &index, const QVariant &value, int role)
194 195 196 197 198
{
    if (!index.isValid()) {
        return QAbstractItemModel::setData(index, value, role);
    }

199
    if (role == StatusRole) {
200 201 202
        // 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
203
        EffectData &data = m_effects[index.row()];
204 205
        data.status = Status(value.toInt());
        data.changed = data.status != data.originalStatus;
206 207
        emit dataChanged(index, index);

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

        return true;
    }

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

229
void EffectsModel::loadBuiltInEffects(const KConfigGroup &kwinConfig, const KPluginInfo::List &configs)
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
{
    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)) {
249
            effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", effect.enabledByDefault));
250
        } else if (data.enabledFunction != nullptr) {
251
            effect.status = Status::EnabledUndeterminded;
252
        } else {
253
            effect.status = effectStatus(effect.enabledByDefault);
254
        }
255
        effect.originalStatus = effect.status;
256 257 258 259 260 261 262 263 264 265 266 267 268 269
        effect.video = data.video;
        effect.website = QUrl();
        effect.supported = true;
        effect.exclusiveGroup = data.exclusiveCategory;
        effect.internal = data.internal;
        effect.kind = Kind::BuiltIn;

        effect.configurable = std::any_of(configs.constBegin(), configs.constEnd(),
            [data](const KPluginInfo &info) {
                return info.property(QStringLiteral("X-KDE-ParentComponents")).toString() == data.name;
            }
        );

        if (shouldStore(effect)) {
270
            m_pendingEffects << effect;
271 272 273 274
        }
    }
}

275
void EffectsModel::loadJavascriptEffects(const KConfigGroup &kwinConfig)
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
{
    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();
295 296
        effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", plugin.isPluginEnabledByDefault()));
        effect.originalStatus = effect.status;
297 298 299
        effect.enabledByDefault = plugin.isPluginEnabledByDefault();
        effect.enabledByDefaultFunction = false;
        effect.video = plugin.property(QStringLiteral("X-KWin-Video-Url")).toUrl();
300
        effect.website = QUrl(plugin.website());
301 302 303 304 305 306 307 308 309 310 311 312 313 314
        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)) {
315
            m_pendingEffects << effect;
316 317 318 319
        }
    }
}

320
void EffectsModel::loadPluginEffects(const KConfigGroup &kwinConfig, const KPluginInfo::List &configs)
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
{
    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;

        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();
        }

363
        effect.website = QUrl(pluginEffect.website());
364 365 366

        const QString enabledKey = QStringLiteral("%1Enabled").arg(effect.serviceName);
        if (kwinConfig.hasKey(enabledKey)) {
367
            effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", effect.enabledByDefault));
368
        } else if (effect.enabledByDefaultFunction) {
369
            effect.status = Status::EnabledUndeterminded;
370
        } else {
371
            effect.status = effectStatus(effect.enabledByDefault);
372 373
        }

374
        effect.originalStatus = effect.status;
375

376 377 378 379 380 381 382
        effect.configurable = std::any_of(configs.constBegin(), configs.constEnd(),
            [pluginEffect](const KPluginInfo &info) {
                return info.property(QStringLiteral("X-KDE-ParentComponents")).toString() == pluginEffect.pluginId();
            }
        );

        if (shouldStore(effect)) {
383
            m_pendingEffects << effect;
384 385 386 387
        }
    }
}

388
void EffectsModel::load(LoadOptions options)
389 390 391
{
    KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), "Plugins");

392
    m_pendingEffects.clear();
393 394 395 396 397
    const KPluginInfo::List configs = KPluginTrader::self()->query(QStringLiteral("kwin/effects/configs/"));
    loadBuiltInEffects(kwinConfig, configs);
    loadJavascriptEffects(kwinConfig);
    loadPluginEffects(kwinConfig, configs);

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

410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
    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;
426 427
            }
        }
428 429 430 431 432 433 434 435

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

        emit loaded();
    };
436 437 438 439 440 441 442

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

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

448 449
        const int serial = ++m_lastSerial;

450
        QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(interface.areEffectsSupported(effectNames), this);
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
        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;
                }

470
                for (int i = 0; i < effectNames.size(); ++i) {
471 472 473 474 475 476
                    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;
477
                        }
478 479 480 481 482 483 484
                    );
                    if (it == m_pendingEffects.end()) {
                        continue;
                    }

                    if ((*it).supported != supported) {
                        (*it).supported = supported;
485 486
                    }
                }
487 488

                commit();
489
            }
490 491 492
        );
    } else {
        commit();
493 494 495
    }
}

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

501
void EffectsModel::save()
502 503 504
{
    KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), "Plugins");

505 506
    QVector<EffectData> dirtyEffects;

507
    for (EffectData &effect : m_effects) {
508 509 510
        if (!effect.changed) {
            continue;
        }
511

512
        effect.changed = false;
513
        effect.originalStatus = effect.status;
514 515

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

        dirtyEffects.append(effect);
    }

    if (dirtyEffects.isEmpty()) {
        return;
531 532 533
    }

    kwinConfig.sync();
534 535 536 537 538 539 540 541 542 543

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

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

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

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

564 565 566 567 568 569
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;
        }
570
        if (static_cast<bool>(effect.status) != effect.enabledByDefault) {
571 572 573 574 575 576
            return false;
        }
        return true;
    });
}

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

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

599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
static KCModule *findBinaryConfig(const QString &pluginId, QObject *parent)
{
    return KPluginTrader::createInstanceFromQuery<KCModule>(
        QStringLiteral("kwin/effects/configs/"),
        QString(),
        QStringLiteral("'%1' in [X-KDE-ParentComponents]").arg(pluginId),
        parent
    );
}

static KCModule *findScriptedConfig(const QString &pluginId, QObject *parent)
{
    const auto offers = KPluginTrader::self()->query(
        QStringLiteral("kwin/effects/configs/"),
        QString(),
        QStringLiteral("[X-KDE-Library] == 'kcm_kwin4_genericscripted'")
    );

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

    const KPluginInfo &generic = offers.first();
    KPluginLoader loader(generic.libraryPath());
    KPluginFactory *factory = loader.factory();
    if (!factory) {
        return nullptr;
    }

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

631
void EffectsModel::requestConfigure(const QModelIndex &index, QWindow *transientParent)
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
{
    if (!index.isValid()) {
        return;
    }

    QPointer<QDialog> dialog = new QDialog();

    KCModule *module = index.data(ScriptedRole).toBool()
        ? findScriptedConfig(index.data(ServiceNameRole).toString(), dialog)
        : findBinaryConfig(index.data(ServiceNameRole).toString(), dialog);
    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);
661 662 663
    connect(module, &KCModule::defaulted, this, [=](bool defaulted) {
        buttons->button(QDialogButtonBox::RestoreDefaults)->setEnabled(!defaulted);
    });
664 665 666 667 668

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

669 670 671
    if (dialog->exec() == QDialog::Accepted) {
        module->save();
    }
672 673 674 675

    delete dialog;
}

676
bool EffectsModel::shouldStore(const EffectData &data) const
677 678 679 680 681 682
{
    Q_UNUSED(data)
    return true;
}

}