diff --git a/kcmkwin/kwindesktop/animationsmodel.cpp b/kcmkwin/kwindesktop/animationsmodel.cpp index 64c2a3e3e..f80a64c53 100644 --- a/kcmkwin/kwindesktop/animationsmodel.cpp +++ b/kcmkwin/kwindesktop/animationsmodel.cpp @@ -1,158 +1,165 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2018 Vlad Zahorodnii 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 . *********************************************************************/ #include "animationsmodel.h" namespace KWin { AnimationsModel::AnimationsModel(QObject *parent) : EffectsModel(parent) { connect(this, &EffectsModel::loaded, this, [this] { setEnabled(modelCurrentEnabled()); setCurrentIndex(modelCurrentIndex()); } ); connect(this, &AnimationsModel::currentIndexChanged, this, [this] { const QModelIndex index_ = index(m_currentIndex, 0); if (!index_.isValid()) { return; } const bool configurable = index_.data(ConfigurableRole).toBool(); if (configurable != m_currentConfigurable) { m_currentConfigurable = configurable; emit currentConfigurableChanged(); } } ); } bool AnimationsModel::enabled() const { return m_enabled; } void AnimationsModel::setEnabled(bool enabled) { if (m_enabled != enabled) { m_enabled = enabled; emit enabledChanged(); } } int AnimationsModel::currentIndex() const { return m_currentIndex; } void AnimationsModel::setCurrentIndex(int index) { if (m_currentIndex != index) { m_currentIndex = index; emit currentIndexChanged(); } } bool AnimationsModel::currentConfigurable() const { return m_currentConfigurable; } bool AnimationsModel::shouldStore(const EffectData &data) const { return data.untranslatedCategory.contains( QStringLiteral("Virtual Desktop Switching Animation"), Qt::CaseInsensitive); } EffectsModel::Status AnimationsModel::status(int row) const { return Status(data(index(row, 0), static_cast(StatusRole)).toInt()); } bool AnimationsModel::modelCurrentEnabled() const { for (int i = 0; i < rowCount(); ++i) { if (status(i) != Status::Disabled) { return true; } } return false; } int AnimationsModel::modelCurrentIndex() const { for (int i = 0; i < rowCount(); ++i) { if (status(i) != Status::Disabled) { return i; } } return 0; } void AnimationsModel::load() { EffectsModel::load(); } void AnimationsModel::save() { for (int i = 0; i < rowCount(); ++i) { const auto status = (m_enabled && i == m_currentIndex) ? EffectsModel::Status::Enabled : EffectsModel::Status::Disabled; updateEffectStatus(index(i, 0), status); } EffectsModel::save(); } void AnimationsModel::defaults() { EffectsModel::defaults(); setEnabled(modelCurrentEnabled()); setCurrentIndex(modelCurrentIndex()); } +bool AnimationsModel::isDefaults() const +{ + // effect at m_currentIndex index may not be the current saved selected effect + const bool enabledByDefault = index(m_currentIndex, 0).data(EnabledByDefaultRole).toBool(); + return enabledByDefault; +} + bool AnimationsModel::needsSave() const { KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), "Plugins"); for (int i = 0; i < rowCount(); ++i) { const QModelIndex index_ = index(i, 0); const bool enabledConfig = kwinConfig.readEntry( index_.data(ServiceNameRole).toString() + QLatin1String("Enabled"), index_.data(EnabledByDefaultRole).toBool() ); const bool enabled = (m_enabled && i == m_currentIndex); if (enabled != enabledConfig) { return true; } } return false; } } diff --git a/kcmkwin/kwindesktop/animationsmodel.h b/kcmkwin/kwindesktop/animationsmodel.h index fd740e852..f05d9feec 100644 --- a/kcmkwin/kwindesktop/animationsmodel.h +++ b/kcmkwin/kwindesktop/animationsmodel.h @@ -1,71 +1,72 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2018 Vlad Zahorodnii 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 . *********************************************************************/ #pragma once #include "effectsmodel.h" namespace KWin { class AnimationsModel : public EffectsModel { Q_OBJECT Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) Q_PROPERTY(bool currentConfigurable READ currentConfigurable NOTIFY currentConfigurableChanged) public: explicit AnimationsModel(QObject *parent = nullptr); bool enabled() const; void setEnabled(bool enabled); int currentIndex() const; void setCurrentIndex(int index); bool currentConfigurable() const; void load(); void save(); void defaults(); + bool isDefaults() const; bool needsSave() const; Q_SIGNALS: void enabledChanged(); void currentIndexChanged(); void currentConfigurableChanged(); protected: bool shouldStore(const EffectData &data) const override; private: Status status(int row) const; bool modelCurrentEnabled() const; int modelCurrentIndex() const; bool m_enabled = false; int m_currentIndex = -1; bool m_currentConfigurable = false; Q_DISABLE_COPY(AnimationsModel) }; } diff --git a/kcmkwin/kwindesktop/desktopsmodel.cpp b/kcmkwin/kwindesktop/desktopsmodel.cpp index e3ad60c67..b060ab641 100644 --- a/kcmkwin/kwindesktop/desktopsmodel.cpp +++ b/kcmkwin/kwindesktop/desktopsmodel.cpp @@ -1,637 +1,676 @@ /* * Copyright (C) 2018 Eike Hein * Copyright (C) 2018 Marco Martin * * 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 . */ #include "desktopsmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_serviceName(QStringLiteral("org.kde.KWin")); static const QString s_virtualDesktopsInterface(QStringLiteral("org.kde.KWin.VirtualDesktopManager")); static const QString s_virtDesktopsPath(QStringLiteral("/VirtualDesktopManager")); static const QString s_fdoPropertiesInterface(QStringLiteral("org.freedesktop.DBus.Properties")); DesktopsModel::DesktopsModel(QObject *parent) : QAbstractListModel(parent) , m_userModified(false) , m_serverModified(false) , m_serverSideRows(-1) , m_rows(-1) , m_synchronizing(false) { qDBusRegisterMetaType(); qDBusRegisterMetaType(); m_serviceWatcher = new QDBusServiceWatcher(s_serviceName, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange); QObject::connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, [this]() { reset(); }); QObject::connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() { QDBusConnection::sessionBus().disconnect( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("desktopCreated"), this, SLOT(desktopCreated(QString,KWin::DBusDesktopDataStruct))); QDBusConnection::sessionBus().disconnect( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("desktopRemoved"), this, SLOT(desktopRemoved(QString))); QDBusConnection::sessionBus().disconnect( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("desktopDataChanged"), this, SLOT(desktopDataChanged(QString,KWin::DBusDesktopDataStruct))); QDBusConnection::sessionBus().disconnect( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("rowsChanged"), this, SLOT(desktopRowsChanged(uint))); } ); reset(); } DesktopsModel::~DesktopsModel() { } QHash DesktopsModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); for (int i = 0; i < e.keyCount(); ++i) { roles.insert(e.value(i), e.key(i)); } return roles; } QVariant DesktopsModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() > (m_desktops.count() - 1)) { return QVariant(); } if (role == Qt::DisplayRole) { return m_names.value(m_desktops.at(index.row())); } else if (role == Id) { return m_desktops.at(index.row()); } else if (role == DesktopRow) { const int rows = std::max(m_rows, 1); const int perRow = std::ceil((qreal)m_desktops.count() / (qreal)rows); return (index.row() / perRow) + 1; } return QVariant(); } int DesktopsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_desktops.count(); } bool DesktopsModel::ready() const { return !m_desktops.isEmpty(); } QString DesktopsModel::error() const { return m_error; } bool DesktopsModel::userModified() const { return m_userModified; } bool DesktopsModel::serverModified() const { return m_serverModified; } int DesktopsModel::rows() const { return m_rows; } void DesktopsModel::setRows(int rows) { if (!ready()) { return; } if (m_rows != rows) { m_rows = rows; emit rowsChanged(); emit dataChanged(index(0, 0), index(m_desktops.count() - 1, 0), QVector{DesktopRow}); updateModifiedState(); } } void DesktopsModel::createDesktop(const QString &name) { if (!ready()) { return; } beginInsertRows(QModelIndex(), m_desktops.count(), m_desktops.count()); const QString &dummyId = QUuid::createUuid().toString(QUuid::WithoutBraces); m_desktops.append(dummyId); m_names[dummyId] = name; endInsertRows(); updateModifiedState(); } void DesktopsModel::removeDesktop(const QString &id) { if (!ready() || !m_desktops.contains(id)) { return; } const int desktopIndex = m_desktops.indexOf(id); beginRemoveRows(QModelIndex(), desktopIndex, desktopIndex); m_desktops.removeAt(desktopIndex); m_names.remove(id); endRemoveRows(); updateModifiedState(); } void DesktopsModel::setDesktopName(const QString &id, const QString &name) { if (!ready() || !m_desktops.contains(id)) { return; } m_names[id] = name; const QModelIndex &idx = index(m_desktops.indexOf(id), 0); dataChanged(idx, idx, QVector{Qt::DisplayRole}); updateModifiedState(); } void DesktopsModel::syncWithServer() { m_synchronizing = true; auto callFinished = [this](QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) { handleCallError(); } call->deleteLater(); }; if (m_desktops.count() > m_serverSideDesktops.count()) { auto call = QDBusMessage::createMethodCall( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("createDesktop")); const int newIndex = m_serverSideDesktops.count(); call.setArguments({(uint)newIndex, m_names.value(m_desktops.at(newIndex))}); QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); const auto *watcher = new QDBusPendingCallWatcher(pending, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); return; // The change-handling slot will call syncWithServer() again, // until everything is in sync. } if (m_desktops.count() < m_serverSideDesktops.count()) { QStringListIterator i(m_serverSideDesktops); i.toBack(); while (i.hasPrevious()) { const QString &previous = i.previous(); if (!m_desktops.contains(previous)) { auto call = QDBusMessage::createMethodCall( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("removeDesktop")); call.setArguments({previous}); QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); return; // The change-handling slot will call syncWithServer() again, // until everything is in sync. } } } // Sync ids. Replace dummy ids in the process. for (int i = 0; i < m_serverSideDesktops.count(); ++i) { const QString oldId = m_desktops.at(i); const QString &newId = m_serverSideDesktops.at(i); m_desktops[i] = newId; m_names[newId] = m_names.take(oldId); } emit dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector{Qt::DisplayRole}); // Sync names. if (m_names != m_serverSideNames) { QHashIterator i(m_names); while (i.hasNext()) { i.next(); if (i.value() != m_serverSideNames.value(i.key())) { auto call = QDBusMessage::createMethodCall( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("setDesktopName")); call.setArguments({i.key(), i.value()}); QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); break; } } return; // The change-handling slot will call syncWithServer() again, // until everything is in sync.. } // Sync rows. if (m_rows != m_serverSideRows) { auto call = QDBusMessage::createMethodCall( s_serviceName, s_virtDesktopsPath, s_fdoPropertiesInterface, QStringLiteral("Set")); call.setArguments({s_virtualDesktopsInterface, QStringLiteral("rows"), QVariant::fromValue(QDBusVariant(QVariant((uint)m_rows)))}); QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); } } void DesktopsModel::reset() { m_synchronizing = false; // Sanity. auto getAllAndConnectCall = QDBusMessage::createMethodCall( s_serviceName, s_virtDesktopsPath, s_fdoPropertiesInterface, QStringLiteral("GetAll")); getAllAndConnectCall.setArguments({s_virtualDesktopsInterface}); QDBusConnection::sessionBus().callWithCallback( getAllAndConnectCall, this, SLOT(getAllAndConnect(QDBusMessage)), SLOT(handleCallError())); } +bool DesktopsModel::needsSave() const +{ + return m_userModified; +} + +bool DesktopsModel::isDefaults() const +{ + return m_rows == 2 && m_desktops.count() == 1; +} + +void DesktopsModel::defaults() +{ + beginResetModel(); + // default is 1 desktop with 2 rows + // see kwin/virtualdesktops.cpp VirtualDesktopGrid::VirtualDesktopGrid + while (m_desktops.count() > 1) { + const auto desktop = m_desktops.takeLast(); + m_names.remove(desktop); + } + m_rows = 2; + + endResetModel(); + + m_userModified = true; + updateModifiedState(); +} + +void DesktopsModel::load() +{ + beginResetModel(); + m_desktops = m_serverSideDesktops; + m_names = m_serverSideNames; + m_rows = m_serverSideRows; + endResetModel(); + + m_userModified = true; + updateModifiedState(); +} + void DesktopsModel::getAllAndConnect(const QDBusMessage &msg) { const QVariantMap &data = qdbus_cast(msg.arguments().at(0).value()); const KWin::DBusDesktopDataVector &desktops = qdbus_cast( data.value(QStringLiteral("desktops")).value() ); const int newServerSideRows = data.value(QStringLiteral("rows")).toUInt(); QStringList newServerSideDesktops; QHash newServerSideNames; for (const KWin::DBusDesktopDataStruct &d : desktops) { newServerSideDesktops.append(d.id); newServerSideNames[d.id] = d.name; } // If the server-side state changed during a KWin restart, and the // user had made notifications, the model should notify about the // change. if (m_serverSideDesktops != newServerSideDesktops || m_serverSideNames != newServerSideNames || m_serverSideRows != newServerSideRows) { if (!m_serverSideDesktops.isEmpty() || m_userModified) { m_serverModified = true; emit serverModifiedChanged(); } m_serverSideDesktops = newServerSideDesktops; m_serverSideNames = newServerSideNames; m_serverSideRows = newServerSideRows; } // For the case KWin restarts while the KCM was open: If the user had // made no modifications, just reset to the server data. E.g. perhaps // the user intentionally nuked the KWin config while it was down, so // we should follow. if (!m_userModified || m_desktops.empty()) { beginResetModel(); m_desktops = m_serverSideDesktops; m_names = m_serverSideNames; m_rows = m_serverSideRows; endResetModel(); } emit readyChanged(); auto handleConnectionError = [this]() { m_error = i18n("There was an error connecting to the compositor."); emit errorChanged(); }; bool connected = QDBusConnection::sessionBus().connect( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("desktopCreated"), this, SLOT(desktopCreated(QString,KWin::DBusDesktopDataStruct))); if (!connected) { handleConnectionError(); return; } connected = QDBusConnection::sessionBus().connect( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("desktopRemoved"), this, SLOT(desktopRemoved(QString))); if (!connected) { handleConnectionError(); return; } connected = QDBusConnection::sessionBus().connect( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("desktopDataChanged"), this, SLOT(desktopDataChanged(QString,KWin::DBusDesktopDataStruct))); if (!connected) { handleConnectionError(); return; } connected = QDBusConnection::sessionBus().connect( s_serviceName, s_virtDesktopsPath, s_virtualDesktopsInterface, QStringLiteral("rowsChanged"), this, SLOT(desktopRowsChanged(uint))); if (!connected) { handleConnectionError(); return; } } void DesktopsModel::desktopCreated(const QString &id, const KWin::DBusDesktopDataStruct &data) { m_serverSideDesktops.insert(data.position, id); m_serverSideNames[data.id] = data.name; // If the user didn't make any changes, we can just stay in sync. if (!m_userModified) { beginInsertRows(QModelIndex(), data.position, data.position); m_desktops = m_serverSideDesktops; m_names = m_serverSideNames; endInsertRows(); } else { // Remove dummy data. const QString dummyId = m_desktops.at(data.position); m_desktops[data.position] = id; m_names.remove(dummyId); m_names[id] = data.name; const QModelIndex &idx = index(data.position, 0); emit dataChanged(idx, idx, QVector{Id}); updateModifiedState(/* server */ true); } } void DesktopsModel::desktopRemoved(const QString &id) { const int desktopIndex = m_serverSideDesktops.indexOf(id); m_serverSideDesktops.removeAt(desktopIndex); m_serverSideNames.remove(id); // If the user didn't make any changes, we can just stay in sync. if (!m_userModified) { beginRemoveRows(QModelIndex(), desktopIndex, desktopIndex); m_desktops = m_serverSideDesktops; m_names = m_serverSideNames; endRemoveRows(); } else { updateModifiedState(/* server */ true); } } void DesktopsModel::desktopDataChanged(const QString &id, const KWin::DBusDesktopDataStruct &data) { const int desktopIndex = m_serverSideDesktops.indexOf(id); m_serverSideDesktops[desktopIndex] = id; m_serverSideNames[id] = data.name; // If the user didn't make any changes, we can just stay in sync. if (!m_userModified) { m_desktops = m_serverSideDesktops; m_names = m_serverSideNames; const QModelIndex &idx = index(desktopIndex, 0); dataChanged(idx, idx, QVector{Qt::DisplayRole}); } else { updateModifiedState(/* server */ true); } } void DesktopsModel::desktopRowsChanged(uint rows) { // Unfortunately we sometimes get this signal from the server with an unchanged value. if ((int)rows == m_serverSideRows) { return; } m_serverSideRows = rows; // If the user didn't make any changes, we can just stay in sync. if (!m_userModified) { m_rows = m_serverSideRows; emit rowsChanged(); emit dataChanged(index(0, 0), index(m_desktops.count() - 1, 0), QVector{DesktopRow}); } else { updateModifiedState(/* server */ true); } } void DesktopsModel::updateModifiedState(bool server) { // Count is the same but contents are not: The user may have // removed and created new desktops in the UI, but there were // no changes to send to the server because number and names // have remained the same. In that case we can just clean // that up here. if (m_desktops.count() == m_serverSideDesktops.count() && m_desktops != m_serverSideDesktops) { for (int i = 0; i < m_serverSideDesktops.count(); ++i) { const QString oldId = m_desktops.at(i); const QString &newId = m_serverSideDesktops.at(i); m_desktops[i] = newId; m_names[newId] = m_names.take(oldId); } emit dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector{Qt::DisplayRole}); } if (m_desktops == m_serverSideDesktops && m_names == m_serverSideNames && m_rows == m_serverSideRows) { m_userModified = false; emit userModifiedChanged(); m_serverModified = false; emit serverModifiedChanged(); m_synchronizing = false; } else { if (m_synchronizing) { m_serverModified = false; emit serverModifiedChanged(); syncWithServer(); } else if (server) { m_serverModified = true; emit serverModifiedChanged(); } else { m_userModified = true; emit userModifiedChanged(); } } } void DesktopsModel::handleCallError() { if (m_synchronizing) { m_synchronizing = false; m_serverModified = false; emit serverModifiedChanged(); m_error = i18n("There was an error saving the settings to the compositor."); emit errorChanged(); } else { m_error = i18n("There was an error requesting information from the compositor."); emit errorChanged(); } } } diff --git a/kcmkwin/kwindesktop/desktopsmodel.h b/kcmkwin/kwindesktop/desktopsmodel.h index e2626fe61..9d9f8ad7d 100644 --- a/kcmkwin/kwindesktop/desktopsmodel.h +++ b/kcmkwin/kwindesktop/desktopsmodel.h @@ -1,129 +1,134 @@ /* * Copyright (C) 2018 Eike Hein * Copyright (C) 2018 Marco Martin * * 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 . */ #ifndef DESKTOPSMODEL_H #define DESKTOPSMODEL_H #include #include "../virtualdesktopsdbustypes.h" class QDBusArgument; class QDBusMessage; class QDBusServiceWatcher; namespace KWin { /** * @short An item model around KWin's D-Bus API for virtual desktops. * * The model initially gets the state from KWin and populates. * * As long as the user makes no changes, KWin-side changes are directly * exposed in the model. * * If the user makes changes (see the `userModified` property), it stops * exposing KWin-side changes live, but it keeps track of the KWin-side * changes, so it can figure out and apply the delta when `syncWithServer` * is called. * * When KWin-side changes happen while the model is user-modified, the * model signals this via the `serverModified` property. A call to * `syncWithServer` will overwrite the KWin-side changes. * * After synchronization, the model tracks Kwin-side changes again, * until the user makes further changes. * * @author Eike Hein */ class DesktopsModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(bool ready READ ready NOTIFY readyChanged) Q_PROPERTY(QString error READ error NOTIFY errorChanged) Q_PROPERTY(bool userModified READ userModified NOTIFY userModifiedChanged) Q_PROPERTY(bool serverModified READ serverModified NOTIFY serverModifiedChanged) Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged) public: enum AdditionalRoles { Id = Qt::UserRole + 1, DesktopRow }; Q_ENUM(AdditionalRoles) explicit DesktopsModel(QObject *parent = nullptr); ~DesktopsModel() override; QHash roleNames() const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = {}) const override; bool ready() const; QString error() const; bool userModified() const; bool serverModified() const; int rows() const; void setRows(int rows); Q_INVOKABLE void createDesktop(const QString &name); Q_INVOKABLE void removeDesktop(const QString &id); Q_INVOKABLE void setDesktopName(const QString &id, const QString &name); Q_INVOKABLE void syncWithServer(); + bool needsSave() const; + void load(); + void defaults(); + bool isDefaults() const; + Q_SIGNALS: void readyChanged() const; void errorChanged() const; void userModifiedChanged() const; void serverModifiedChanged() const; void rowsChanged() const; protected Q_SLOTS: void reset(); void getAllAndConnect(const QDBusMessage &msg); void desktopCreated(const QString &id, const KWin::DBusDesktopDataStruct &data); void desktopRemoved(const QString &id); void desktopDataChanged(const QString &id, const KWin::DBusDesktopDataStruct &data); void desktopRowsChanged(uint rows); void updateModifiedState(bool server = false); void handleCallError(); private: QDBusServiceWatcher *m_serviceWatcher; QString m_error; bool m_userModified; bool m_serverModified; QStringList m_serverSideDesktops; QHash m_serverSideNames; int m_serverSideRows; QStringList m_desktops; QHash m_names; int m_rows; bool m_synchronizing; }; } #endif diff --git a/kcmkwin/kwindesktop/package/contents/ui/main.qml b/kcmkwin/kwindesktop/package/contents/ui/main.qml index dea0bd69f..5e0f39b26 100644 --- a/kcmkwin/kwindesktop/package/contents/ui/main.qml +++ b/kcmkwin/kwindesktop/package/contents/ui/main.qml @@ -1,288 +1,276 @@ /* * Copyright (C) 2018 Eike Hein * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.5 import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.1 import org.kde.kcm 1.2 import org.kde.kirigami 2.10 as Kirigami import org.kde.plasma.core 2.1 as PlasmaCore ScrollViewKCM { id: root ConfigModule.quickHelp: i18n("This module lets you configure the navigation, number and layout of virtual desktops.") Connections { target: kcm.desktopsModel onReadyChanged: { rowsSpinBox.value = kcm.desktopsModel.rows; } onRowsChanged: { rowsSpinBox.value = kcm.desktopsModel.rows; } } implicitWidth: Kirigami.Units.gridUnit * 35 implicitHeight: Kirigami.Units.gridUnit * 30 Component { id: desktopsListItemComponent Kirigami.SwipeListItem { id: listItem contentItem: RowLayout { QQC2.TextField { id: nameField background: null leftPadding: Kirigami.Units.largeSpacing topPadding: 0 bottomPadding: 0 Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter text: model.display readOnly: true onEditingFinished: { readOnly = true; Qt.callLater(kcm.desktopsModel.setDesktopName, model.Id, text); } } } actions: [ Kirigami.Action { enabled: !model.IsMissing iconName: "edit-rename" tooltip: i18nc("@info:tooltip", "Rename") onTriggered: { nameField.readOnly = false; nameField.selectAll(); nameField.forceActiveFocus(); } }, Kirigami.Action { enabled: !model.IsMissing iconName: "edit-delete-remove" tooltip: i18nc("@info:tooltip", "Remove") onTriggered: kcm.desktopsModel.removeDesktop(model.Id) }] } } header: ColumnLayout { id: messagesLayout spacing: Kirigami.Units.largeSpacing Kirigami.InlineMessage { Layout.fillWidth: true type: Kirigami.MessageType.Error text: kcm.desktopsModel.error visible: kcm.desktopsModel.error != "" } Kirigami.InlineMessage { Layout.fillWidth: true type: Kirigami.MessageType.Information text: i18n("Virtual desktops have been changed outside this settings application. Saving now will overwrite the changes.") visible: kcm.desktopsModel.serverModified } } view: ListView { id: desktopsList model: kcm.desktopsModel.ready ? kcm.desktopsModel : null section.property: "DesktopRow" section.delegate: Kirigami.ListSectionHeader { width: desktopsList.width label: i18n("Row %1", section) } delegate: Kirigami.DelegateRecycler { width: desktopsList.width sourceComponent: desktopsListItemComponent } } footer: ColumnLayout { RowLayout { QQC2.Button { text: i18nc("@action:button", "Add") icon.name: "list-add" onClicked: kcm.desktopsModel.createDesktop(i18n("New Desktop")) } Item { // Spacer Layout.fillWidth: true } QQC2.SpinBox { id: rowsSpinBox from: 1 to: 20 editable: true textFromValue: function(value, locale) { return i18np("1 Row", "%1 Rows", value)} onValueModified: kcm.desktopsModel.rows = value } } Kirigami.FormLayout { - Connections { - target: kcm - - onNavWrapsChanged: navWraps.checked = kcm.navWraps - - onOsdEnabledChanged: osdEnabled.checked = kcm.osdEnabled - onOsdDurationChanged: osdDuration.value = kcm.osdDuration - onOsdTextOnlyChanged: osdTextOnly.checked = !kcm.osdTextOnly - } QQC2.CheckBox { id: navWraps Kirigami.FormData.label: i18n("Options:") text: i18n("Navigation wraps around") - - checked: kcm.navWraps - - onCheckedChanged: kcm.navWraps = checked + enabled: !kcm.virtualDesktopsSettings.isImmutable("rollOverDesktops") + checked: kcm.virtualDesktopsSettings.rollOverDesktops + onToggled: kcm.virtualDesktopsSettings.rollOverDesktops = checked } RowLayout { Layout.fillWidth: true QQC2.CheckBox { id: animationEnabled // Let it elide but don't make it push the ComboBox away from it Layout.fillWidth: true Layout.maximumWidth: implicitWidth text: i18n("Show animation when switching:") checked: kcm.animationsModel.enabled - onCheckedChanged: kcm.animationsModel.enabled = checked + onToggled: kcm.animationsModel.enabled = checked } QQC2.ComboBox { enabled: animationEnabled.checked model: kcm.animationsModel textRole: "NameRole" currentIndex: kcm.animationsModel.currentIndex onActivated: kcm.animationsModel.currentIndex = currentIndex } QQC2.Button { enabled: animationEnabled.checked && kcm.animationsModel.currentConfigurable icon.name: "configure" onClicked: kcm.configureAnimation() } QQC2.Button { enabled: animationEnabled.checked icon.name: "dialog-information" onClicked: kcm.showAboutAnimation() } Item { Layout.fillWidth: true } } RowLayout { Layout.fillWidth: true QQC2.CheckBox { id: osdEnabled text: i18n("Show on-screen display when switching:") - checked: kcm.osdEnabled + enabled: !kcm.virtualDesktopsSettings.isImmutable("desktopChangeOsdEnabled") - onToggled: kcm.osdEnabled = checked + checked: kcm.virtualDesktopsSettings.desktopChangeOsdEnabled + + onToggled: kcm.virtualDesktopsSettings.desktopChangeOsdEnabled = checked } QQC2.SpinBox { id: osdDuration - enabled: osdEnabled.checked + enabled: osdEnabled.checked && !kcm.virtualDesktopsSettings.isImmutable("popupHideDelay") from: 0 to: 10000 stepSize: 100 textFromValue: function(value, locale) { return i18n("%1 ms", value)} - value: kcm.osdDuration + value: kcm.virtualDesktopsSettings.popupHideDelay - onValueChanged: kcm.osdDuration = value + onValueModified: kcm.virtualDesktopsSettings.popupHideDelay = value } } RowLayout { Layout.fillWidth: true Item { width: units.largeSpacing } QQC2.CheckBox { id: osdTextOnly - - enabled: osdEnabled.checked - + enabled: osdEnabled.checked && !kcm.virtualDesktopsSettings.isImmutable("textOnly") text: i18n("Show desktop layout indicators") - - checked: !kcm.osdTextOnly - - onToggled: kcm.osdTextOnly = !checked + checked: !kcm.virtualDesktopsSettings.textOnly + onToggled: kcm.virtualDesktopsSettings.textOnly = !checked } } } } } diff --git a/kcmkwin/kwindesktop/virtualdesktops.cpp b/kcmkwin/kwindesktop/virtualdesktops.cpp index f450557fa..5a433f4e3 100644 --- a/kcmkwin/kwindesktop/virtualdesktops.cpp +++ b/kcmkwin/kwindesktop/virtualdesktops.cpp @@ -1,271 +1,175 @@ /* * Copyright (C) 2018 Eike Hein * Copyright (C) 2018 Vlad Zahorodnii * * 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 . */ #include "virtualdesktops.h" #include "animationsmodel.h" #include "desktopsmodel.h" #include "virtualdesktopssettings.h" #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(VirtualDesktopsFactory, "kcm_kwin_virtualdesktops.json", registerPlugin();) namespace KWin { VirtualDesktops::VirtualDesktops(QObject *parent, const QVariantList &args) - : KQuickAddons::ConfigModule(parent, args) + : KQuickAddons::ManagedConfigModule(parent, args) , m_settings(new VirtualDesktopsSettings(this)) , m_desktopsModel(new KWin::DesktopsModel(this)) - , m_navWraps(true) - , m_osdEnabled(false) - , m_osdDuration(1000) - , m_osdTextOnly(false) , m_animationsModel(new AnimationsModel(this)) { KAboutData *about = new KAboutData(QStringLiteral("kcm_kwin_virtualdesktops"), i18n("Virtual Desktops"), QStringLiteral("2.0"), QString(), KAboutLicense::GPL); setAboutData(about); + qmlRegisterType(); + setButtons(Apply | Default); QObject::connect(m_desktopsModel, &KWin::DesktopsModel::userModifiedChanged, - this, &VirtualDesktops::updateNeedsSave); + this, &VirtualDesktops::settingsChanged); connect(m_animationsModel, &AnimationsModel::enabledChanged, - this, &VirtualDesktops::updateNeedsSave); + this, &VirtualDesktops::settingsChanged); connect(m_animationsModel, &AnimationsModel::currentIndexChanged, - this, &VirtualDesktops::updateNeedsSave); + this, &VirtualDesktops::settingsChanged); } VirtualDesktops::~VirtualDesktops() { } QAbstractItemModel *VirtualDesktops::desktopsModel() const { return m_desktopsModel; } -bool VirtualDesktops::navWraps() const -{ - return m_navWraps; -} - -void VirtualDesktops::setNavWraps(bool wraps) -{ - if (m_navWraps != wraps) { - m_navWraps = wraps; - - emit navWrapsChanged(); - - updateNeedsSave(); - } -} - -bool VirtualDesktops::osdEnabled() const -{ - return m_osdEnabled; -} - -void VirtualDesktops::setOsdEnabled(bool enabled) -{ - if (m_osdEnabled != enabled) { - m_osdEnabled = enabled; - - emit osdEnabledChanged(); - - updateNeedsSave(); - } -} - -int VirtualDesktops::osdDuration() const -{ - return m_osdDuration; -} - -void VirtualDesktops::setOsdDuration(int duration) -{ - if (m_osdDuration != duration) { - m_osdDuration = duration; - - emit osdDurationChanged(); - - updateNeedsSave(); - } -} - -int VirtualDesktops::osdTextOnly() const -{ - return m_osdTextOnly; -} - -void VirtualDesktops::setOsdTextOnly(bool textOnly) +QAbstractItemModel *VirtualDesktops::animationsModel() const { - if (m_osdTextOnly != textOnly) { - m_osdTextOnly = textOnly; - - emit osdTextOnlyChanged(); - - updateNeedsSave(); - } + return m_animationsModel; } -QAbstractItemModel *VirtualDesktops::animationsModel() const +VirtualDesktopsSettings *VirtualDesktops::virtualDesktopsSettings() const { - return m_animationsModel; + return m_settings; } void VirtualDesktops::load() { - setNavWraps(m_settings->rollOverDesktop()); - - setOsdEnabled(m_settings->desktopChangeOsdEnabled()); - - setOsdDuration(m_settings->popupHideDelay()); - setOsdTextOnly(m_settings->textOnly()); + ManagedConfigModule::load(); + m_desktopsModel->load(); m_animationsModel->load(); } void VirtualDesktops::save() { + ManagedConfigModule::save(); + m_desktopsModel->syncWithServer(); m_animationsModel->save(); - m_settings->setRollOverDesktop(m_navWraps); - m_settings->setDesktopChangeOsdEnabled(m_osdEnabled); - m_settings->setPopupHideDelay(m_osdDuration); - m_settings->setTextOnly(m_osdTextOnly); - - m_settings->save(); - QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig")); QDBusConnection::sessionBus().send(message); - - setNeedsSave(false); } void VirtualDesktops::defaults() { - m_desktopsModel->setRows(1); + ManagedConfigModule::defaults(); + + m_desktopsModel->defaults(); m_animationsModel->defaults(); +} - setNavWraps(true); - setOsdEnabled(false); - setOsdDuration(1000); - setOsdTextOnly(false); +bool VirtualDesktops::isDefaults() const +{ + return m_animationsModel->isDefaults() && m_desktopsModel->isDefaults(); } void VirtualDesktops::configureAnimation() { const QModelIndex index = m_animationsModel->index(m_animationsModel->currentIndex(), 0); if (!index.isValid()) { return; } m_animationsModel->requestConfigure(index, nullptr); } void VirtualDesktops::showAboutAnimation() { const QModelIndex index = m_animationsModel->index(m_animationsModel->currentIndex(), 0); if (!index.isValid()) { return; } const QString name = index.data(AnimationsModel::NameRole).toString(); const QString comment = index.data(AnimationsModel::DescriptionRole).toString(); const QString author = index.data(AnimationsModel::AuthorNameRole).toString(); const QString email = index.data(AnimationsModel::AuthorEmailRole).toString(); const QString website = index.data(AnimationsModel::WebsiteRole).toString(); const QString version = index.data(AnimationsModel::VersionRole).toString(); const QString license = index.data(AnimationsModel::LicenseRole).toString(); const QString icon = index.data(AnimationsModel::IconNameRole).toString(); const KAboutLicense::LicenseKey licenseType = KAboutLicense::byKeyword(license).key(); KAboutData aboutData( name, // Plugin name name, // Display name version, // Version comment, // Short description licenseType, // License QString(), // Copyright statement QString(), // Other text website.toLatin1() // Home page ); aboutData.setProgramLogo(icon); const QStringList authors = author.split(','); const QStringList emails = email.split(','); if (authors.count() == emails.count()) { int i = 0; for (const QString &author : authors) { if (!author.isEmpty()) { aboutData.addAuthor(i18n(author.toUtf8()), QString(), emails[i]); } i++; } } QPointer aboutPlugin = new KAboutApplicationDialog(aboutData); aboutPlugin->exec(); delete aboutPlugin; } -void VirtualDesktops::updateNeedsSave() +bool VirtualDesktops::isSaveNeeded() const { - bool needsSave = false; - - if (m_desktopsModel->userModified()) { - needsSave = true; - } - - if (m_animationsModel->needsSave()) { - needsSave = true; - } - - if (m_navWraps != m_settings->rollOverDesktop()) { - needsSave = true; - } - - if (m_osdEnabled != m_settings->desktopChangeOsdEnabled()) { - needsSave = true; - } - - if (m_osdDuration != m_settings->popupHideDelay()) { - needsSave = true; - } - - if (m_osdTextOnly != m_settings->textOnly()) { - needsSave = true; - } - - setNeedsSave(needsSave); + return m_animationsModel->needsSave() || m_desktopsModel->needsSave(); } } #include "virtualdesktops.moc" diff --git a/kcmkwin/kwindesktop/virtualdesktops.h b/kcmkwin/kwindesktop/virtualdesktops.h index 285c4fe27..9b486da9c 100644 --- a/kcmkwin/kwindesktop/virtualdesktops.h +++ b/kcmkwin/kwindesktop/virtualdesktops.h @@ -1,93 +1,70 @@ /* * Copyright (C) 2018 Eike Hein * Copyright (C) 2018 Vlad Zahorodnii * * 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 . */ #ifndef VIRTUALDESKTOPS_H #define VIRTUALDESKTOPS_H -#include +#include #include class VirtualDesktopsSettings; namespace KWin { class AnimationsModel; class DesktopsModel; -class VirtualDesktops : public KQuickAddons::ConfigModule +class VirtualDesktops : public KQuickAddons::ManagedConfigModule { Q_OBJECT Q_PROPERTY(QAbstractItemModel* desktopsModel READ desktopsModel CONSTANT) - Q_PROPERTY(bool navWraps READ navWraps WRITE setNavWraps NOTIFY navWrapsChanged) - Q_PROPERTY(bool osdEnabled READ osdEnabled WRITE setOsdEnabled NOTIFY osdEnabledChanged) - Q_PROPERTY(int osdDuration READ osdDuration WRITE setOsdDuration NOTIFY osdDurationChanged) - Q_PROPERTY(bool osdTextOnly READ osdTextOnly WRITE setOsdTextOnly NOTIFY osdTextOnlyChanged) Q_PROPERTY(QAbstractItemModel *animationsModel READ animationsModel CONSTANT) + Q_PROPERTY(VirtualDesktopsSettings *virtualDesktopsSettings READ virtualDesktopsSettings CONSTANT) public: explicit VirtualDesktops(QObject *parent = nullptr, const QVariantList &list = QVariantList()); ~VirtualDesktops() override; QAbstractItemModel *desktopsModel() const; - bool navWraps() const; - void setNavWraps(bool wraps); - - bool osdEnabled() const; - void setOsdEnabled(bool enabled); - - int osdDuration() const; - void setOsdDuration(int duration); - - int osdTextOnly() const; - void setOsdTextOnly(bool textOnly); - QAbstractItemModel *animationsModel() const; -Q_SIGNALS: - void navWrapsChanged() const; - void osdEnabledChanged() const; - void osdDurationChanged() const; - void osdTextOnlyChanged() const; + VirtualDesktopsSettings *virtualDesktopsSettings() const; + + bool isDefaults() const override; + bool isSaveNeeded() const override; public Q_SLOTS: void load() override; void save() override; void defaults() override; void configureAnimation(); void showAboutAnimation(); -private Q_SLOTS: - void updateNeedsSave(); - private: VirtualDesktopsSettings *m_settings; DesktopsModel *m_desktopsModel; - bool m_navWraps; - bool m_osdEnabled; - int m_osdDuration; - bool m_osdTextOnly; AnimationsModel *m_animationsModel; }; } #endif diff --git a/kcmkwin/kwindesktop/virtualdesktopssettings.kcfg b/kcmkwin/kwindesktop/virtualdesktopssettings.kcfg index df154d801..7db898aef 100644 --- a/kcmkwin/kwindesktop/virtualdesktopssettings.kcfg +++ b/kcmkwin/kwindesktop/virtualdesktopssettings.kcfg @@ -1,29 +1,29 @@ - + true false - + 1000 - + false