diff --git a/applets/systemtray/CMakeLists.txt b/applets/systemtray/CMakeLists.txt --- a/applets/systemtray/CMakeLists.txt +++ b/applets/systemtray/CMakeLists.txt @@ -3,6 +3,7 @@ plasma_install_package(package org.kde.plasma.private.systemtray) set(systemtray_SRCS + systemtraymodel.cpp systemtray.cpp ) @@ -18,10 +19,11 @@ target_link_libraries(org.kde.plasma.private.systemtray Qt5::Gui Qt5::Quick - KF5::Plasma Qt5::DBus + KF5::Plasma KF5::XmlGui - KF5::I18n) + KF5::I18n + KF5::ItemModels) install(TARGETS org.kde.plasma.private.systemtray DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets) diff --git a/applets/systemtray/package/contents/ui/ConfigEntries.qml b/applets/systemtray/package/contents/ui/ConfigEntries.qml --- a/applets/systemtray/package/contents/ui/ConfigEntries.qml +++ b/applets/systemtray/package/contents/ui/ConfigEntries.qml @@ -36,32 +36,11 @@ property var cfg_hiddenItems: [] property alias cfg_showAllItems: showAllCheckBox.checked - function saveConfig () { - for (var i in tableView.model) { - //tableView.model[i].applet.globalShortcut = tableView.model[i].shortcut - } - } - - PlasmaCore.DataSource { - id: statusNotifierSource - engine: "statusnotifieritem" - interval: 0 - onSourceAdded: { - connectSource(source) - } - Component.onCompleted: { - connectedSources = sources - } - } - PlasmaCore.SortFilterModel { - id: statusNotifierModel - sourceModel: PlasmaCore.DataModel { - dataSource: statusNotifierSource - } + id: systemTrayModel + sourceModel: plasmoid.nativeInterface.systemTrayModel } - Kirigami.FormLayout { QQC2.CheckBox { @@ -83,29 +62,19 @@ } } - + // convert to QtObjects compatible with TableView function retrieveAllItems() { var list = []; - for (var i = 0; i < statusNotifierModel.count; ++i) { - var item = statusNotifierModel.get(i); - list.push({ - "index": i, - "taskId": item.Id, - "name": item.Title, - "iconName": item.IconName, - "icon": item.Icon - }); - } - var lastIndex = list.length; - for (var i = 0; i < plasmoid.applets.length; ++i) { - var item = plasmoid.applets[i] + for (var i = 0; i < systemTrayModel.count; i++) { + var item = systemTrayModel.get(i); + if (item.itemType === "Plasmoid" && !item.hasApplet) { + continue; + } list.push({ - "index": (i + lastIndex), - "applet": item, - "taskId": item.pluginName, - "name": item.title, - "iconName": item.icon, - "shortcut": item.globalShortcut + "taskId": item.itemId, + "name": item.display, + "icon": item.decoration, + "applet": item.applet }); } list.sort(function(a, b) { @@ -154,7 +123,7 @@ QIconItem { width: units.iconSizes.small height: width - icon: modelData.iconName || modelData.icon || "" + icon: modelData.icon } QQC2.Label { @@ -241,16 +210,12 @@ id: keySequenceItem anchors.right: parent.right - keySequence: modelData.shortcut + keySequence: modelData.applet ? modelData.applet.globalShortcut : "" // only Plasmoids have that - visible: modelData.hasOwnProperty("shortcut") + visible: modelData.hasOwnProperty("applet") onKeySequenceChanged: { - if (keySequence !== modelData.shortcut) { - // both SNIs and plasmoids are listed in the same TableView - // but they come from two separate models, so we need to subtract - // the SNI model count to get the actual plasmoid index - var index = modelData.index - statusNotifierModel.count - plasmoid.applets[index].globalShortcut = keySequence + if (modelData.applet && keySequence !== modelData.applet.globalShortcut) { + modelData.applet.globalShortcut = keySequence iconsPage.configurationChanged() } diff --git a/applets/systemtray/package/contents/ui/ConfigGeneral.qml b/applets/systemtray/package/contents/ui/ConfigGeneral.qml --- a/applets/systemtray/package/contents/ui/ConfigGeneral.qml +++ b/applets/systemtray/package/contents/ui/ConfigGeneral.qml @@ -89,13 +89,13 @@ model: plasmoid.nativeInterface.availablePlasmoids delegate: QtControls.CheckBox { QtLayouts.Layout.minimumWidth: childrenRect.width - checked: cfg_extraItems.indexOf(plugin) != -1 + checked: cfg_extraItems.indexOf(itemId) != -1 implicitWidth: itemLayout.width + itemLayout.x onCheckedChanged: { - var index = cfg_extraItems.indexOf(plugin); + var index = cfg_extraItems.indexOf(itemId); if (checked) { if (index === -1) { - cfg_extraItems.push(plugin); + cfg_extraItems.push(itemId); } } else { if (index > -1) { diff --git a/applets/systemtray/systemtray.h b/applets/systemtray/systemtray.h --- a/applets/systemtray/systemtray.h +++ b/applets/systemtray/systemtray.h @@ -29,11 +29,17 @@ class QDBusPendingCallWatcher; class QDBusConnection; class QQuickItem; +namespace Plasma { + class Service; +} class PlasmoidModel; +class StatusNotifierModel; +class SystemTrayModel; class SystemTray : public Plasma::Containment { Q_OBJECT + Q_PROPERTY(QAbstractItemModel* systemTrayModel READ systemTrayModel CONSTANT) Q_PROPERTY(QAbstractItemModel* availablePlasmoids READ availablePlasmoids CONSTANT) Q_PROPERTY(QStringList allowedPlasmoids READ allowedPlasmoids WRITE setAllowedPlasmoids NOTIFY allowedPlasmoidsChanged) Q_PROPERTY(QStringList defaultPlasmoids READ defaultPlasmoids CONSTANT) @@ -47,6 +53,8 @@ void restoreContents(KConfigGroup &group) override; void restorePlasmoids(); + QAbstractItemModel* systemTrayModel(); + QStringList defaultPlasmoids() const; QAbstractItemModel* availablePlasmoids(); @@ -120,6 +128,8 @@ QStringList m_allowedPlasmoids; PlasmoidModel *m_availablePlasmoidsModel; + StatusNotifierModel *m_statusNotifierModel; + SystemTrayModel *m_systemTrayModel; QHash m_knownPlugins; QHash m_dbusServiceCounts; }; diff --git a/applets/systemtray/systemtray.cpp b/applets/systemtray/systemtray.cpp --- a/applets/systemtray/systemtray.cpp +++ b/applets/systemtray/systemtray.cpp @@ -18,6 +18,7 @@ ***************************************************************************/ #include "systemtray.h" +#include "systemtraymodel.h" #include "debug.h" #include @@ -42,27 +43,21 @@ #include -class PlasmoidModel: public QStandardItemModel -{ -public: - explicit PlasmoidModel(QObject *parent = nullptr) - : QStandardItemModel(parent) - { - } - - QHash roleNames() const override { - QHash roles = QStandardItemModel::roleNames(); - roles[Qt::UserRole+1] = "plugin"; - return roles; - } -}; - SystemTray::SystemTray(QObject *parent, const QVariantList &args) : Plasma::Containment(parent, args), - m_availablePlasmoidsModel(nullptr) + m_availablePlasmoidsModel(nullptr), + m_systemTrayModel(new SystemTrayModel(this)) { setHasConfigurationInterface(true); setContainmentType(Plasma::Types::CustomEmbeddedContainment); + + PlasmoidModel *currentPlasmoidsModel = new PlasmoidModel(m_systemTrayModel); + connect(this, &SystemTray::appletAdded, currentPlasmoidsModel, &PlasmoidModel::addApplet); + connect(this, &SystemTray::appletRemoved, currentPlasmoidsModel, &PlasmoidModel::removeApplet); + m_systemTrayModel->addSourceModel(currentPlasmoidsModel); + + m_statusNotifierModel = new StatusNotifierModel(m_systemTrayModel); + m_systemTrayModel->addSourceModel(m_statusNotifierModel); } SystemTray::~SystemTray() @@ -437,6 +432,11 @@ initDBusActivatables(); } +QAbstractItemModel *SystemTray::systemTrayModel() +{ + return m_systemTrayModel; +} + QStringList SystemTray::defaultPlasmoids() const { return m_defaultPlasmoids; @@ -446,19 +446,6 @@ { if (!m_availablePlasmoidsModel) { m_availablePlasmoidsModel = new PlasmoidModel(this); - - for (const KPluginMetaData &info : qAsConst(m_systrayApplets)) { - QString name = info.name(); - const QString dbusactivation = info.rawData().value(QStringLiteral("X-Plasma-DBusActivationService")).toString(); - - if (!dbusactivation.isEmpty()) { - name += i18n(" (Automatic load)"); - } - QStandardItem *item = new QStandardItem(QIcon::fromTheme(info.iconName()), name); - item->setData(info.pluginId()); - m_availablePlasmoidsModel->appendRow(item); - } - m_availablePlasmoidsModel->sort(0 /*column*/); } return m_availablePlasmoidsModel; } diff --git a/applets/systemtray/systemtraymodel.h b/applets/systemtray/systemtraymodel.h new file mode 100644 --- /dev/null +++ b/applets/systemtray/systemtraymodel.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2019 Konrad Materka * + * * + * 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef SYSTEMTRAYMODEL_H +#define SYSTEMTRAYMODEL_H + +#include + +#include +#include +#include + +namespace Plasma { + class Applet; +} + +enum class BaseRole { + ItemType = Qt::UserRole + 1, + ItemId, + CanRender, + LastBaseRole +}; + +class PlasmoidModel: public QStandardItemModel +{ + Q_OBJECT +public: + enum class Role { + Applet = static_cast(BaseRole::LastBaseRole) + 1, + HasApplet + }; + + explicit PlasmoidModel(QObject *parent = nullptr); + + QHash roleNames() const override; + +public slots: + void addApplet(Plasma::Applet *applet); + void removeApplet(Plasma::Applet *applet); +}; + +class StatusNotifierModel : public QStandardItemModel, public Plasma::DataEngineConsumer { + Q_OBJECT +public: + enum class Role { + DataEngineSource = static_cast(BaseRole::LastBaseRole) + 100, + AttentionIcon, + AttentionIconName, + AttentionMovieName, + Category, + Icon, + IconName, + IconThemePath, + IconsChanged, + Id, + ItemIsMenu, + OverlayIconName, + Status, + StatusChanged, + Title, + TitleChanged, + ToolTipChanged, + ToolTipIcon, + ToolTipSubTitle, + ToolTipTitle, + WindowId + }; + + StatusNotifierModel(QObject* parent); + + QHash roleNames() const override; + + Plasma::Service *serviceForSource(const QString &source); + +public slots: + void addSource(const QString &source); + void removeSource(const QString &source); + void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data); + +private: + void updateItemData(QStandardItem *dataItem, const Plasma::DataEngine::Data &data, const Role role); + + Plasma::DataEngine *m_dataEngine = nullptr; + QStringList m_sources; + QHash m_services; +}; + +class SystemTrayModel : public KConcatenateRowsProxyModel +{ + Q_OBJECT +public: + explicit SystemTrayModel(QObject *parent = nullptr); + + QHash roleNames() const override; + + void addSourceModel(QAbstractItemModel *sourceModel); + +private: + QHash m_roleNames; +}; + +#endif // SYSTEMTRAYMODEL_H diff --git a/applets/systemtray/systemtraymodel.cpp b/applets/systemtray/systemtraymodel.cpp new file mode 100644 --- /dev/null +++ b/applets/systemtray/systemtraymodel.cpp @@ -0,0 +1,255 @@ +/*************************************************************************** + * Copyright (C) 2019 Konrad Materka * + * * + * 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include "systemtraymodel.h" +#include "debug.h" + +#include + +#include +#include +#include +#include + +#include + +PlasmoidModel::PlasmoidModel(QObject *parent) : QStandardItemModel(parent) +{ + for (const auto &info : Plasma::PluginLoader::self()->listAppletMetaData(QString())) { + if (!info.isValid() || info.value(QStringLiteral("X-Plasma-NotificationArea")) != "true") { + continue; + } + + QString name = info.name(); + const QString dbusactivation = + info.rawData().value(QStringLiteral("X-Plasma-DBusActivationService")).toString(); + + if (!dbusactivation.isEmpty()) { + name += i18n(" (Automatic load)"); + } + QStandardItem *item = new QStandardItem(QIcon::fromTheme(info.iconName()), name); + item->setData(info.pluginId(), static_cast(BaseRole::ItemId)); + item->setData("Plasmoid", static_cast(BaseRole::ItemType)); + item->setData(false, static_cast(BaseRole::CanRender)); + item->setData(false, static_cast(Role::HasApplet)); + appendRow(item); + } + sort(0); +} + +QHash PlasmoidModel::roleNames() const +{ + QHash roles = QStandardItemModel::roleNames(); + + roles.insert(static_cast(BaseRole::ItemType), QByteArrayLiteral("itemType")); + roles.insert(static_cast(BaseRole::ItemId), QByteArrayLiteral("itemId")); + roles.insert(static_cast(BaseRole::CanRender), QByteArrayLiteral("canRender")); + + roles.insert(static_cast(Role::Applet), QByteArrayLiteral("applet")); + roles.insert(static_cast(Role::HasApplet), QByteArrayLiteral("hasApplet")); + + return roles; +} + +void PlasmoidModel::addApplet(Plasma::Applet *applet) +{ + auto info = applet->pluginMetaData(); + QStandardItem *dataItem = nullptr; + for (int i = 0; i < rowCount(); i++) { + QStandardItem *currentItem = item(i); + if (currentItem->data(static_cast(BaseRole::ItemId)) == info.pluginId()) { + dataItem = currentItem; + break; + } + } + + if (!dataItem) { + dataItem = new QStandardItem(QIcon::fromTheme(info.iconName()), info.name()); + appendRow(dataItem); + } + + dataItem->setData(info.pluginId(), static_cast(BaseRole::ItemId)); + dataItem->setData(applet->property("_plasma_graphicObject"), static_cast(Role::Applet)); + dataItem->setData(true, static_cast(Role::HasApplet)); + dataItem->setData(true, static_cast(BaseRole::CanRender)); +} + +void PlasmoidModel::removeApplet(Plasma::Applet *applet) +{ + int rows = rowCount(); + for (int i = 0; i < rows; i++) { + QStandardItem *currentItem = item(i); + QVariant plugin = currentItem->data(static_cast(BaseRole::ItemId)); + if (plugin.isValid() && plugin.value() == applet->pluginMetaData().pluginId()) { + currentItem->setData(false, static_cast(BaseRole::CanRender)); + currentItem->setData(QVariant(), static_cast(Role::Applet)); + currentItem->setData(false, static_cast(Role::HasApplet)); + return; + } + } +} + +StatusNotifierModel::StatusNotifierModel(QObject *parent) : QStandardItemModel(parent) +{ + m_dataEngine = dataEngine(QStringLiteral("statusnotifieritem")); + + connect(m_dataEngine, &Plasma::DataEngine::sourceAdded, this, &StatusNotifierModel::addSource); + connect(m_dataEngine, &Plasma::DataEngine::sourceRemoved, this, &StatusNotifierModel::removeSource); + + m_dataEngine->connectAllSources(this); +} + +QHash StatusNotifierModel::roleNames() const +{ + QHash roles = QStandardItemModel::roleNames(); + + roles.insert(static_cast(BaseRole::ItemType), QByteArrayLiteral("itemType")); + roles.insert(static_cast(BaseRole::ItemId), QByteArrayLiteral("itemId")); + roles.insert(static_cast(BaseRole::CanRender), QByteArrayLiteral("canRender")); + + roles.insert(static_cast(Role::DataEngineSource), QByteArrayLiteral("DataEngineSource")); + roles.insert(static_cast(Role::AttentionIcon), QByteArrayLiteral("AttentionIcon")); + roles.insert(static_cast(Role::AttentionIconName), QByteArrayLiteral("AttentionIconName")); + roles.insert(static_cast(Role::AttentionMovieName), QByteArrayLiteral("AttentionMovieName")); + roles.insert(static_cast(Role::Category), QByteArrayLiteral("Category")); + roles.insert(static_cast(Role::Icon), QByteArrayLiteral("Icon")); + roles.insert(static_cast(Role::IconName), QByteArrayLiteral("IconName")); + roles.insert(static_cast(Role::IconThemePath), QByteArrayLiteral("IconThemePath")); + roles.insert(static_cast(Role::IconsChanged), QByteArrayLiteral("IconsChanged")); + roles.insert(static_cast(Role::Id), QByteArrayLiteral("Id")); + roles.insert(static_cast(Role::ItemIsMenu), QByteArrayLiteral("ItemIsMenu")); + roles.insert(static_cast(Role::OverlayIconName), QByteArrayLiteral("OverlayIconName")); + roles.insert(static_cast(Role::Status), QByteArrayLiteral("Status")); + roles.insert(static_cast(Role::StatusChanged), QByteArrayLiteral("StatusChanged")); + roles.insert(static_cast(Role::Title), QByteArrayLiteral("Title")); + roles.insert(static_cast(Role::TitleChanged), QByteArrayLiteral("TitleChanged")); + roles.insert(static_cast(Role::ToolTipChanged), QByteArrayLiteral("ToolTipChanged")); + roles.insert(static_cast(Role::ToolTipIcon), QByteArrayLiteral("ToolTipIcon")); + roles.insert(static_cast(Role::ToolTipSubTitle), QByteArrayLiteral("ToolTipSubTitle")); + roles.insert(static_cast(Role::ToolTipTitle), QByteArrayLiteral("ToolTipTitle")); + roles.insert(static_cast(Role::WindowId), QByteArrayLiteral("WindowId")); + + return roles; +} + +Plasma::Service *StatusNotifierModel::serviceForSource(const QString &source) +{ + if (m_services.contains(source)) { + return m_services.value(source); + } + + Plasma::Service *service = m_dataEngine->serviceForSource(source); + if (!service) { + return nullptr; + } + m_services[source] = service; + return service; +} + +void StatusNotifierModel::addSource(const QString &source) +{ + m_dataEngine->connectSource(source, this); +} + +void StatusNotifierModel::removeSource(const QString &source) +{ + m_dataEngine->disconnectSource(source, this); + if (m_sources.contains(source)) { + removeRow(m_sources.indexOf(source)); + m_sources.removeAll(source); + } + + QHash::iterator it = m_services.find(source); + if (it != m_services.end()) { + delete it.value(); + m_services.erase(it); + } +} + +void StatusNotifierModel::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) +{ + QStandardItem *dataItem; + if (m_sources.contains(sourceName)) { + dataItem = item(m_sources.indexOf(sourceName)); + } else { + dataItem = new QStandardItem(); + dataItem->setData("StatusNotifier", static_cast(BaseRole::ItemType)); + dataItem->setData(true, static_cast(BaseRole::CanRender)); + } + + dataItem->setData(data.value("Title"), Qt::DisplayRole); + QVariant icon = data.value("Icon"); + if (icon.isValid() && icon.canConvert() && !icon.value().isNull()) { + dataItem->setData(icon, Qt::DecorationRole); + } else { + dataItem->setData(data.value("IconName"), Qt::DecorationRole); + } + + dataItem->setData(data.value("Id"), static_cast(BaseRole::ItemId)); + + dataItem->setData(sourceName, static_cast(Role::DataEngineSource)); + updateItemData(dataItem, data, Role::AttentionIcon); + updateItemData(dataItem, data, Role::AttentionIconName); + updateItemData(dataItem, data, Role::AttentionMovieName); + updateItemData(dataItem, data, Role::Category); + updateItemData(dataItem, data, Role::Icon); + updateItemData(dataItem, data, Role::IconName); + updateItemData(dataItem, data, Role::IconThemePath); + updateItemData(dataItem, data, Role::IconsChanged); + updateItemData(dataItem, data, Role::Id); + updateItemData(dataItem, data, Role::ItemIsMenu); + updateItemData(dataItem, data, Role::OverlayIconName); + updateItemData(dataItem, data, Role::Status); + updateItemData(dataItem, data, Role::StatusChanged); + updateItemData(dataItem, data, Role::Title); + updateItemData(dataItem, data, Role::TitleChanged); + updateItemData(dataItem, data, Role::ToolTipChanged); + updateItemData(dataItem, data, Role::ToolTipIcon); + updateItemData(dataItem, data, Role::ToolTipSubTitle); + updateItemData(dataItem, data, Role::ToolTipTitle); + updateItemData(dataItem, data, Role::WindowId); + + if (!m_sources.contains(sourceName)) { + m_sources.append(sourceName); + appendRow(dataItem); + } +} + +void StatusNotifierModel::updateItemData(QStandardItem *dataItem, + const Plasma::DataEngine::Data &data, const Role role) +{ + int roleId = static_cast(role); + dataItem->setData(data.value(roleNames().value(roleId)), roleId); +} + +SystemTrayModel::SystemTrayModel(QObject *parent) : KConcatenateRowsProxyModel(parent) +{ + m_roleNames = KConcatenateRowsProxyModel::roleNames(); +} + +QHash SystemTrayModel::roleNames() const +{ + return m_roleNames; +} + +void SystemTrayModel::addSourceModel(QAbstractItemModel *sourceModel) +{ + m_roleNames.unite(sourceModel->roleNames()); + KConcatenateRowsProxyModel::addSourceModel(sourceModel); +}