Commit 2211a951 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii

[kcmkwin] Split out Desktop Effects KCM

Summary:
A while ago desktop effects and compositing settings lived under the
same roof

{F6584639}

{F6584642}

but time has passed and now those two have their own kcms. This causes
some issues:

* for newcomers it's harder to find code of the Desktop Effects KCM;
* git history doesn't look good, e.g. "[kcmkwin/compositing] Add some
  bugs to Desktop Effects KCM to fix later";
* in general, the mix of two doesn't look good in the code.

This change splits out the Desktop Effects KCM. Unfortunately, in order
to have more nicer code I had to refactor EffectModel a little bit.

Before:

{F6584669}

After:

{F6587570}

Reviewers: #kwin, davidedmundson

Reviewed By: #kwin, davidedmundson

Subscribers: davidedmundson, ltoscano, mart, ngraham, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D18703
parent 5eb28512
......@@ -8,6 +8,7 @@ add_subdirectory( kwinrules )
add_subdirectory( kwinscreenedges )
add_subdirectory( kwinscripts )
add_subdirectory( kwindesktop )
add_subdirectory( kwineffects )
if( KWIN_BUILD_TABBOX )
add_subdirectory( kwintabbox )
......
......@@ -199,7 +199,7 @@ bool EffectModel::setData(const QModelIndex &index, const QVariant &value, int r
// config file could get polluted
EffectData &data = m_effectsList[index.row()];
data.effectStatus = Status(value.toInt());
data.changed = true;
data.changed = data.effectStatus != data.originalStatus;
emit dataChanged(index, index);
if (data.effectStatus == Status::Enabled && !data.exclusiveGroup.isEmpty()) {
......@@ -211,7 +211,7 @@ bool EffectModel::setData(const QModelIndex &index, const QVariant &value, int r
EffectData &otherData = m_effectsList[i];
if (otherData.exclusiveGroup == data.exclusiveGroup) {
otherData.effectStatus = Status::Disabled;
otherData.changed = true;
otherData.changed = otherData.effectStatus != otherData.originalStatus;
emit dataChanged(this->index(i, 0), this->index(i, 0));
}
}
......@@ -249,6 +249,7 @@ void EffectModel::loadBuiltInEffects(const KConfigGroup &kwinConfig, const KPlug
} else {
effect.effectStatus = effectStatus(effect.enabledByDefault);
}
effect.originalStatus = effect.effectStatus;
effect.video = data.video;
effect.website = QUrl();
effect.supported = true;
......@@ -289,6 +290,7 @@ void EffectModel::loadJavascriptEffects(const KConfigGroup &kwinConfig)
effect.serviceName = plugin.pluginName();
effect.iconName = plugin.icon();
effect.effectStatus = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", plugin.isPluginEnabledByDefault()));
effect.originalStatus = effect.effectStatus;
effect.enabledByDefault = plugin.isPluginEnabledByDefault();
effect.enabledByDefaultFunction = false;
effect.video = plugin.property(QStringLiteral("X-KWin-Video-Url")).toUrl();
......@@ -366,6 +368,8 @@ void EffectModel::loadPluginEffects(const KConfigGroup &kwinConfig, const KPlugi
effect.effectStatus = effectStatus(effect.enabledByDefault);
}
effect.originalStatus = effect.effectStatus;
effect.configurable = std::any_of(configs.constBegin(), configs.constEnd(),
[pluginEffect](const KPluginInfo &info) {
return info.property(QStringLiteral("X-KDE-ParentComponents")).toString() == pluginEffect.pluginId();
......@@ -430,7 +434,7 @@ void EffectModel::load()
if (it != m_effectsList.end()) {
if ((*it).supported != supportedValue) {
(*it).supported = supportedValue;
QModelIndex i = index(findRowByServiceName(effectName), 0);
QModelIndex i = findByPluginId(effectName);
if (i.isValid()) {
emit dataChanged(i, i, QVector<int>() << SupportedRole);
}
......@@ -446,16 +450,6 @@ void EffectModel::load()
endResetModel();
}
int EffectModel::findRowByServiceName(const QString &serviceName)
{
for (int it = 0; it < m_effectsList.size(); it++) {
if (m_effectsList.at(it).serviceName == serviceName) {
return it;
}
}
return -1;
}
void EffectModel::syncEffectsToKWin()
{
OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"),
......@@ -490,6 +484,7 @@ void EffectModel::save()
continue;
}
effect.changed = false;
effect.originalStatus = effect.effectStatus;
const QString key = effect.serviceName + QStringLiteral("Enabled");
const bool shouldEnable = (effect.effectStatus != Status::Disabled);
......@@ -519,6 +514,28 @@ void EffectModel::defaults()
}
}
bool EffectModel::needsSave() const
{
return std::any_of(m_effectsList.constBegin(), m_effectsList.constEnd(),
[](const EffectData &data) {
return data.changed;
}
);
}
QModelIndex EffectModel::findByPluginId(const QString &pluginId) const
{
auto it = std::find_if(m_effectsList.constBegin(), m_effectsList.constEnd(),
[pluginId](const EffectData &data) {
return data.serviceName == pluginId;
}
);
if (it == m_effectsList.constEnd()) {
return {};
}
return index(std::distance(m_effectsList.constBegin(), it), 0);
}
bool EffectModel::shouldStore(const EffectData &data) const
{
Q_UNUSED(data)
......
......@@ -177,6 +177,16 @@ public:
**/
void defaults();
/**
* Whether the model has unsaved changes.
**/
bool needsSave() const;
/**
* Finds an effect with the given plugin id.
**/
QModelIndex findByPluginId(const QString &pluginId) const;
protected:
enum class Kind {
BuiltIn,
......@@ -196,6 +206,7 @@ protected:
QString serviceName;
QString iconName;
Status effectStatus;
Status originalStatus;
bool enabledByDefault;
bool enabledByDefaultFunction;
QUrl video;
......@@ -220,7 +231,6 @@ private:
void loadBuiltInEffects(const KConfigGroup &kwinConfig, const KPluginInfo::List &configs);
void loadJavascriptEffects(const KConfigGroup &kwinConfig);
void loadPluginEffects(const KConfigGroup &kwinConfig, const KPluginInfo::List &configs);
int findRowByServiceName(const QString &serviceName);
void syncEffectsToKWin();
QVector<EffectData> m_effectsList;
......
......@@ -6,76 +6,28 @@ add_definitions(-DQT_NO_URL_CAST_FROM_STRING)
remove_definitions(-DQT_NO_CAST_FROM_ASCII -DQT_STRICT_ITERATORS -DQT_NO_CAST_FROM_BYTEARRAY)
include_directories(${KWIN_SOURCE_DIR}/effects)
################# configure checks and create the configured files #################
set(kwincomposing_SRC
model.cpp
main.cpp
compositing.cpp
effectconfig.cpp)
compositing.cpp)
qt5_add_dbus_interface( kwincomposing_SRC
${KWIN_SOURCE_DIR}/org.kde.kwin.Compositing.xml kwin_compositing_interface)
qt5_add_dbus_interface( kwincomposing_SRC
${KWIN_SOURCE_DIR}/org.kde.kwin.Effects.xml kwin_effects_interface)
ki18n_wrap_ui(kwincomposing_SRC compositing.ui)
add_library(kwincompositing MODULE ${kwincomposing_SRC})
target_link_libraries(kwincompositing
Qt5::Quick
Qt5::QuickWidgets
Qt5::DBus
Qt5::Widgets
KF5::CoreAddons
KF5::ConfigCore
KF5::Declarative
KF5::I18n
KF5::KCMUtils
KF5::NewStuff
kcmkwincommon
)
if (BUILD_TESTING)
include(ECMMarkAsTest)
set(modelTest_SRC
model.cpp
effectconfig.cpp
compositing.cpp
test/effectmodeltest.cpp
test/modeltest.cpp)
qt5_add_dbus_interface(modelTest_SRC
${KWIN_SOURCE_DIR}/org.kde.kwin.Compositing.xml kwin_compositing_interface)
qt5_add_dbus_interface(modelTest_SRC
${KWIN_SOURCE_DIR}/org.kde.kwin.Effects.xml kwin_effects_interface)
add_executable(effectModelTest ${modelTest_SRC})
ecm_mark_as_test(effectModelTest)
target_link_libraries(effectModelTest
Qt5::Quick
Qt5::QuickWidgets
Qt5::DBus
Qt5::Test
Qt5::Widgets
KF5::CoreAddons
KF5::ConfigCore
KF5::Declarative
KF5::I18n
KF5::KCMUtils
KF5::NewStuff
kwineffects
kcmkwincommon
)
endif()
INSTALL(DIRECTORY qml DESTINATION ${DATA_INSTALL_DIR}/kwincompositing)
INSTALL(TARGETS kwincompositing DESTINATION ${PLUGIN_INSTALL_DIR})
install(FILES kwincompositing.desktop kcmkwineffects.desktop DESTINATION ${SERVICES_INSTALL_DIR})
install(FILES kwineffect.knsrc DESTINATION ${CONFIG_INSTALL_DIR})
install(FILES kwincompositing.desktop DESTINATION ${SERVICES_INSTALL_DIR})
################# list the subdirectories #################
/**************************************************************************
* KWin - the KDE window manager *
* This file is part of the KDE project. *
* *
* Copyright (C) 2013 Antonis Tsiapaliokas <kok3rs@gmail.com> *
* *
* 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/>. *
**************************************************************************/
#include "effectconfig.h"
#include <KCModule>
#include <KPluginTrader>
#include <KNS3/DownloadDialog>
#include <QDialog>
#include <QDialogButtonBox>
#include <QVBoxLayout>
#include <QPointer>
#include <QPushButton>
#include <QStandardPaths>
#include <QString>
static const QString s_pluginDir = QStringLiteral("kwin/effects/configs/");
namespace KWin {
namespace Compositing {
EffectConfig::EffectConfig(QObject *parent)
: QObject(parent)
{
}
void EffectConfig::openConfig(const QString &serviceName, bool scripted, const QString &title)
{
//setup the UI
QDialog dialog;
dialog.setWindowTitle(title);
// create the KCModule through the plugintrader
KCModule *kcm = nullptr;
if (scripted) {
// try generic module for scripted
const auto offers = KPluginTrader::self()->query(s_pluginDir, QString(),
QStringLiteral("[X-KDE-Library] == 'kcm_kwin4_genericscripted'"));
if (!offers.isEmpty()) {
const KPluginInfo &generic = offers.first();
KPluginLoader loader(generic.libraryPath());
KPluginFactory *factory = loader.factory();
if (factory) {
kcm = factory->create<KCModule>(serviceName, &dialog);
}
}
} else {
kcm = KPluginTrader::createInstanceFromQuery<KCModule>(s_pluginDir, QString(),
QStringLiteral("'%1' in [X-KDE-ParentComponents]").arg(serviceName),
&dialog);
}
if (!kcm) {
return;
}
connect(&dialog, &QDialog::accepted, kcm, &KCModule::save);
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel |
QDialogButtonBox::Apply |
QDialogButtonBox::RestoreDefaults |
QDialogButtonBox::Reset,
&dialog);
QPushButton *apply = buttons->button(QDialogButtonBox::Apply);
QPushButton *reset = buttons->button(QDialogButtonBox::Reset);
apply->setEnabled(false);
reset->setEnabled(false);
//Here we connect our buttons with the dialog
connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
connect(apply, &QPushButton::clicked, kcm, &KCModule::save);
connect(reset, &QPushButton::clicked, kcm, &KCModule::load);
auto changedSignal = static_cast<void(KCModule::*)(bool)>(&KCModule::changed);
connect(kcm, changedSignal, apply, &QPushButton::setEnabled);
connect(kcm, changedSignal, reset, &QPushButton::setEnabled);
connect(buttons->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, kcm, &KCModule::defaults);
QVBoxLayout *layout = new QVBoxLayout(&dialog);
layout->addWidget(kcm);
layout->addWidget(buttons);
dialog.exec();
}
void EffectConfig::openGHNS()
{
QPointer<KNS3::DownloadDialog> downloadDialog = new KNS3::DownloadDialog(QStringLiteral("kwineffect.knsrc"));
if (downloadDialog->exec() == QDialog::Accepted) {
emit effectListChanged();
}
delete downloadDialog;
}
}//end namespace Compositing
}//end namespace KWin
/**************************************************************************
* KWin - the KDE window manager *
* This file is part of the KDE project. *
* *
* Copyright (C) 2013 Antonis Tsiapaliokas <kok3rs@gmail.com> *
* *
* 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/>. *
**************************************************************************/
#ifndef EFFECTCONFIG_H
#define EFFECTCONFIG_H
#include <QObject>
class QString;
namespace KWin {
namespace Compositing {
class EffectConfig : public QObject
{
Q_OBJECT
public:
explicit EffectConfig(QObject *parent = 0);
QString serviceName(const QString &serviceName);
Q_INVOKABLE void openConfig(const QString &effectName, bool scripted, const QString &title);
Q_INVOKABLE void openGHNS();
Q_SIGNALS:
void effectListChanged();
};
}//end namespace Compositing
}//end namespace KWin
#endif
......@@ -21,7 +21,6 @@
#include "compositing.h"
#include "model.h"
#include "ui_compositing.h"
#include <QAction>
#include <QApplication>
......@@ -30,33 +29,6 @@
#include <kcmodule.h>
#include <kservice.h>
class KWinCompositingKCM : public KCModule
{
Q_OBJECT
public:
virtual ~KWinCompositingKCM();
public Q_SLOTS:
void save() override;
void load() override;
void defaults() override;
protected:
explicit KWinCompositingKCM(QWidget* parent, const QVariantList& args,
KWin::Compositing::EffectView::ViewType viewType);
private:
QScopedPointer<KWin::Compositing::EffectView> m_view;
};
class KWinDesktopEffects : public KWinCompositingKCM
{
Q_OBJECT
public:
explicit KWinDesktopEffects(QWidget* parent = 0, const QVariantList& args = QVariantList())
: KWinCompositingKCM(parent, args, KWin::Compositing::EffectView::DesktopEffectsView) {}
};
class KWinCompositingSettings : public KCModule
{
Q_OBJECT
......@@ -230,44 +202,7 @@ void KWinCompositingSettings::save()
m_compositing->save();
}
KWinCompositingKCM::KWinCompositingKCM(QWidget* parent, const QVariantList& args, KWin::Compositing::EffectView::ViewType viewType)
: KCModule(parent, args)
, m_view(new KWin::Compositing::EffectView(viewType))
{
QVBoxLayout *vl = new QVBoxLayout(this);
vl->addWidget(m_view.data());
setLayout(vl);
connect(m_view.data(), &KWin::Compositing::EffectView::changed, [this]{
emit changed(true);
});
m_view->setFocusPolicy(Qt::StrongFocus);
}
KWinCompositingKCM::~KWinCompositingKCM()
{
}
void KWinCompositingKCM::save()
{
m_view->save();
KCModule::save();
}
void KWinCompositingKCM::load()
{
m_view->load();
KCModule::load();
}
void KWinCompositingKCM::defaults()
{
m_view->defaults();
KCModule::defaults();
}
K_PLUGIN_FACTORY(KWinCompositingConfigFactory,
registerPlugin<KWinDesktopEffects>("effects");
registerPlugin<KWinCompositingSettings>("compositing");
)
......
/**************************************************************************
* KWin - the KDE window manager *
* This file is part of the KDE project. *
* *
* Copyright (C) 2013 Antonis Tsiapaliokas <kok3rs@gmail.com> *
* *
* 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/>. *
**************************************************************************/
#include "model.h"
#include "effectconfig.h"
#include "effectmodel.h"
#include "compositing.h"
#include <config-kwin.h>
#include <kwin_effects_interface.h>
#include <effect_builtins.h>
#include <KSharedConfig>
#include <KCModuleProxy>
#include <kdeclarative/kdeclarative.h>
#include <QVariant>
#include <QString>
#include <QQmlEngine>
#include <QtQml>
#include <QQuickItem>
#include <QDebug>
namespace KWin {
namespace Compositing {
EffectFilterModel::EffectFilterModel(QObject *parent)
: QSortFilterProxyModel(parent)
, m_effectModel(new EffectModel(this))
, m_filterOutUnsupported(true)
, m_filterOutInternal(true)
{
setSourceModel(m_effectModel);
connect(this, &EffectFilterModel::filterOutUnsupportedChanged, this, &EffectFilterModel::invalidateFilter);
connect(this, &EffectFilterModel::filterOutInternalChanged, this, &EffectFilterModel::invalidateFilter);
}
const QString &EffectFilterModel::filter() const
{
return m_filter;
}
void EffectFilterModel::setFilter(const QString &filter)
{
if (filter == m_filter) {
return;
}
m_filter = filter;
emit filterChanged();
invalidateFilter();
}
bool EffectFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (!m_effectModel) {
return false;
}
QModelIndex index = m_effectModel->index(source_row, 0, source_parent);
if (!index.isValid()) {
return false;
}
if (m_filterOutUnsupported) {
if (!index.data(EffectModel::SupportedRole).toBool()) {
return false;
}
}
if (m_filterOutInternal) {
if (index.data(EffectModel::InternalRole).toBool()) {
return false;
}
}
if (m_filter.isEmpty()) {
return true;
}
QVariant data = index.data();
if (!data.isValid()) {
//An invalid QVariant is valid data
return true;
}
if (m_effectModel->data(index, EffectModel::NameRole).toString().contains(m_filter, Qt::CaseInsensitive)) {
return true;
} else if (m_effectModel->data(index, EffectModel::DescriptionRole).toString().contains(m_filter, Qt::CaseInsensitive)) {
return true;
}
if (index.data(EffectModel::CategoryRole).toString().contains(m_filter, Qt::CaseInsensitive)) {
return true;
}
return false;
}
void EffectFilterModel::updateEffectStatus(int rowIndex, int effectState)
{
const QModelIndex sourceIndex = mapToSource(index(rowIndex, 0));
m_effectModel->updateEffectStatus(sourceIndex, EffectModel::Status(effectState));
}
void EffectFilterModel::syncConfig()
{
m_effectModel->save();
}
void EffectFilterModel::load()
{
m_effectModel->load();
}
void EffectFilterModel::defaults()
{
m_effectModel->defaults();
}
EffectView::EffectView(ViewType type, QWidget *parent)
: QQuickWidget(parent)
{
qRegisterMetaType<OpenGLPlatformInterfaceModel*>();
qmlRegisterType<EffectConfig>("org.kde.kwin.kwincompositing", 1, 0, "EffectConfig");
qmlRegisterType<EffectFilterModel>("org.kde.kwin.kwincompositing", 1, 0, "EffectFilterModel");
qmlRegisterType<Compositing>("org.kde.kwin.kwincompositing", 1, 0, "Compositing");
qmlRegisterType<CompositingType>("org.kde.kwin.kwincompositing", 1, 0, "CompositingType");
init(type);
}
void EffectView::init(ViewType type)
{
KDeclarative::KDeclarative kdeclarative;
kdeclarative.setDeclarativeEngine(engine());
kdeclarative.setTranslationDomain(QStringLiteral(TRANSLATION_DOMAIN));
kdeclarative.setupContext();
kdeclarative.setupEngine(engine());
QString path;
switch (type) {
case CompositingSettingsView:
path = QStringLiteral("kwincompositing/qml/main-compositing.qml");
break;