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/package/contents/ui/items/PlasmoidItem.qml b/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml --- a/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml +++ b/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml @@ -59,9 +59,27 @@ } } onAppletChanged: { - if (!applet) { - plasmoidContainer.destroy(); - print("applet destroyed") + if (applet) { + applet.parent = plasmoidContainer + applet.anchors.left = plasmoidContainer.left + applet.anchors.top = plasmoidContainer.top + applet.anchors.bottom = plasmoidContainer.bottom + applet.width = plasmoidContainer.height + applet.visible = true + plasmoidContainer.visible = true + + //This is to make preloading effective, minimizes the scene changes + if (applet.fullRepresentationItem) { + applet.fullRepresentationItem.width = expandedRepresentation.width + applet.fullRepresentationItem.width = expandedRepresentation.height + applet.fullRepresentationItem.parent = preloadedStorage; + } else { + applet.fullRepresentationItemChanged.connect(function() { + applet.fullRepresentationItem.width = expandedRepresentation.width + applet.fullRepresentationItem.width = expandedRepresentation.height + applet.fullRepresentationItem.parent = preloadedStorage; + }); + } } } Connections { diff --git a/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml b/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml --- a/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml +++ b/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml @@ -23,16 +23,18 @@ AbstractItem { id: taskIcon - itemId: Id - text: Title - mainText: ToolTipTitle != "" ? ToolTipTitle : Title - subText: ToolTipSubTitle - icon: ToolTipIcon != "" ? ToolTipIcon : Icon ? Icon : IconName + property var model + + itemId: model.Id + text: model.Title + mainText: model.ToolTipTitle !== "" ? model.ToolTipTitle : model.Title + subText: model.ToolTipSubTitle + icon: model.ToolTipIcon !== "" ? model.ToolTipIcon : model.Icon ? model.Icon : model.IconName textFormat: Text.AutoText - category: Category + category: model.Category status: { - switch (Status) { + switch (model.Status) { case "Active": return PlasmaCore.Types.ActiveStatus; case "NeedsAttention": @@ -49,14 +51,14 @@ id: iconItem source: { if (taskIcon.status === PlasmaCore.Types.NeedsAttentionStatus) { - if (AttentionIcon) { - return AttentionIcon + if (model.AttentionIcon) { + return model.AttentionIcon } - if (AttentionIconName) { - return AttentionIconName + if (model.AttentionIconName) { + return model.AttentionIconName } } - return Icon ? Icon : IconName + return model.Icon ? model.Icon : model.IconName } width: Math.min(parent.width, parent.height) @@ -77,8 +79,8 @@ var pos = plasmoid.nativeInterface.popupPosition(taskIcon, mouse.x, mouse.y); switch (mouse.button) { - case Qt.LeftButton: { - var service = statusNotifierSource.serviceForSource(DataEngineSource); + case Qt.LeftButton: + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("Activate"); operation.x = pos.x; operation.y = pos.y; @@ -91,13 +93,12 @@ } }); break; - } case Qt.RightButton: openContextMenu(pos); break; case Qt.MiddleButton: - var service = statusNotifierSource.serviceForSource(DataEngineSource); + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("SecondaryActivate"); operation.x = pos.x; @@ -108,7 +109,7 @@ } function openContextMenu(pos) { - var service = statusNotifierSource.serviceForSource(DataEngineSource); + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("ContextMenu"); operation.x = pos.x; operation.y = pos.y; @@ -122,14 +123,14 @@ onWheel: { //don't send activateVertScroll with a delta of 0, some clients seem to break (kmix) if (wheel.angleDelta.y !== 0) { - var service = statusNotifierSource.serviceForSource(DataEngineSource); + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("Scroll"); operation.delta =wheel.angleDelta.y; operation.direction = "Vertical"; service.startOperationCall(operation); } if (wheel.angleDelta.x !== 0) { - var service = statusNotifierSource.serviceForSource(DataEngineSource); + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("Scroll"); operation.delta =wheel.angleDelta.x; operation.direction = "Horizontal"; diff --git a/applets/systemtray/package/contents/ui/main.qml b/applets/systemtray/package/contents/ui/main.qml --- a/applets/systemtray/package/contents/ui/main.qml +++ b/applets/systemtray/package/contents/ui/main.qml @@ -19,7 +19,7 @@ import QtQuick 2.5 import QtQuick.Layouts 1.1 -import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.core 2.1 as PlasmaCore import org.kde.plasma.plasmoid 2.0 import org.kde.draganddrop 2.0 as DnD import org.kde.kirigami 2.5 as Kirigami @@ -50,11 +50,6 @@ property alias visibleLayout: tasksRow property alias hiddenLayout: expandedRepresentation.hiddenLayout - property alias statusNotifierModel: statusNotifierModel - - // workaround https://bugreports.qt.io/browse/QTBUG-71238 / https://bugreports.qt.io/browse/QTBUG-72004 - property Component plasmoidItemComponent: Qt.createComponent("items/PlasmoidItem.qml") - property int creationIdCounter: 0 Plasmoid.onExpandedChanged: { @@ -143,32 +138,6 @@ wheel.accepted = true; } - Containment.onAppletAdded: { - //Allow the plasmoid expander to know in what window it will be - var plasmoidContainer = plasmoidItemComponent.createObject(invisibleEntriesContainer, {"x": x, "y": y, "applet": applet}); - - applet.parent = plasmoidContainer - applet.anchors.left = plasmoidContainer.left - applet.anchors.top = plasmoidContainer.top - applet.anchors.bottom = plasmoidContainer.bottom - applet.width = plasmoidContainer.height - applet.visible = true - plasmoidContainer.visible = true - - //This is to make preloading effective, minimizes the scene changes - if (applet.fullRepresentationItem) { - applet.fullRepresentationItem.width = expandedRepresentation.width - applet.fullRepresentationItem.width = expandedRepresentation.height - applet.fullRepresentationItem.parent = preloadedStorage; - } else { - applet.fullRepresentationItemChanged.connect(function() { - applet.fullRepresentationItem.width = expandedRepresentation.width - applet.fullRepresentationItem.width = expandedRepresentation.height - applet.fullRepresentationItem.parent = preloadedStorage; - }); - } - } - //being there forces the items to fully load, and they will be reparented in the popup one by one, this item is *never* visible Item { id: preloadedStorage @@ -187,62 +156,12 @@ } } - Connections { + Connections { target: plasmoid.configuration onExtraItemsChanged: plasmoid.nativeInterface.allowedPlasmoids = plasmoid.configuration.extraItems } - Component.onCompleted: { - //script, don't bind - plasmoid.nativeInterface.allowedPlasmoids = initializePlasmoidList(); - } - - function initializePlasmoidList() { - var newKnownItems = []; - var newExtraItems = []; - - //NOTE:why this? otherwise the interpreter will execute plasmoid.nativeInterface.defaultPlasmoids() on - //every access of defaults[], resulting in a very slow iteration - var defaults = []; - //defaults = defaults.concat(plasmoid.nativeInterface.defaultPlasmoids); - defaults = plasmoid.nativeInterface.defaultPlasmoids.slice() - var candidate; - - //Add every plasmoid that is both not enabled explicitly and not already known - for (var i = 0; i < defaults.length; ++i) { - candidate = defaults[i]; - if (plasmoid.configuration.knownItems.indexOf(candidate) === -1) { - newKnownItems.push(candidate); - if (plasmoid.configuration.extraItems.indexOf(candidate) === -1) { - newExtraItems.push(candidate); - } - } - } - - if (newExtraItems.length > 0) { - plasmoid.configuration.extraItems = plasmoid.configuration.extraItems.slice().concat(newExtraItems); - } - if (newKnownItems.length > 0) { - plasmoid.configuration.knownItems = plasmoid.configuration.knownItems.slice().concat(newKnownItems); - } - - return plasmoid.configuration.extraItems; - } - - PlasmaCore.DataSource { - id: statusNotifierSource - engine: "statusnotifieritem" - interval: 0 - onSourceAdded: { - connectSource(source) - } - Component.onCompleted: { - connectedSources = sources - } - } - - //due to the magic of property bindings this function will be //re-executed all the times a setting changes property var shownCategories: { @@ -270,23 +189,36 @@ return array; } - PlasmaCore.SortFilterModel { - id: statusNotifierModel - sourceModel: PlasmaCore.DataModel { - dataSource: statusNotifierSource - } - } - //This is a dump for items we don't want to be seen or as an incubation, when they are //created as a nursery before going in their final place - Item { + ListView { //Repeater behavies wierdly, messes up with parents of applets id: invisibleEntriesContainer visible: false - Repeater { - id: tasksRepeater - model: statusNotifierModel + height: contentHeight - delegate: StatusNotifierItem {} + model: PlasmaCore.SortFilterModel { + id: systemTrayModel + sourceModel: plasmoid.nativeInterface.systemTrayModel + filterRole: "canRender" + filterCallback: function(source_row, value) { return value; } + } + delegate: Loader { + parent: invisibleEntriesContainer + id: itemLoader + + Component.onCompleted: { + if (model.applet) { + itemLoader.setSource("items/PlasmoidItem.qml", { + "parent": invisibleEntriesContainer, + "applet": model.applet + }) + } else if (model.DataEngineSource) { + itemLoader.setSource("items/StatusNotifierItem.qml", { + "parent": invisibleEntriesContainer, + "model": model + }) + } + } } //NOTE: this exists mostly for not causing reference errors property QtObject marginHints: QtObject { diff --git a/applets/systemtray/systemtray.h b/applets/systemtray/systemtray.h --- a/applets/systemtray/systemtray.h +++ b/applets/systemtray/systemtray.h @@ -29,14 +29,19 @@ 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) public: SystemTray( QObject *parent, const QVariantList &args ); @@ -47,7 +52,7 @@ void restoreContents(KConfigGroup &group) override; void restorePlasmoids(); - QStringList defaultPlasmoids() const; + QAbstractItemModel* systemTrayModel(); QAbstractItemModel* availablePlasmoids(); @@ -101,6 +106,12 @@ Q_INVOKABLE bool isSystemTrayApplet(const QString &appletId); + /** + * @returns a Plasma::Service given a source name + * @param source source name we want a service of + */ + Q_INVOKABLE Plasma::Service *serviceForSource(const QString &source); + private Q_SLOTS: void serviceNameFetchFinished(QDBusPendingCallWatcher* watcher, const QDBusConnection &connection); void serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner); @@ -120,6 +131,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() @@ -349,10 +344,40 @@ return m_systrayApplets.contains(appletId); } +Plasma::Service *SystemTray::serviceForSource(const QString &source) +{ + return m_statusNotifierModel->serviceForSource(source); +} + void SystemTray::restoreContents(KConfigGroup &group) { - Q_UNUSED(group); - //NOTE: RestoreContents shouldn't do anything here because is too soon, so have an empty reimplementation + QStringList newKnownItems; + QStringList newExtraItems; + + KConfigGroup general = group.group("General"); + + QStringList knownItems = general.readEntry("knownItems", QStringList()); + QStringList extraItems = general.readEntry("extraItems", QStringList()); + + //Add every plasmoid that is both not enabled explicitly and not already known + for (int i = 0; i < m_defaultPlasmoids.length(); ++i) { + QString candidate = m_defaultPlasmoids[i]; + if (!knownItems.contains(candidate)) { + newKnownItems.append(candidate); + if (!extraItems.contains(candidate)) { + newExtraItems.append(candidate); + } + } + } + + if (newExtraItems.length() > 0) { + general.writeEntry("extraItems", extraItems + newExtraItems); + } + if (newKnownItems.length() > 0) { + general.writeEntry("knownItems", knownItems + newKnownItems); + } + + setAllowedPlasmoids(general.readEntry("extraItems", QStringList())); } void SystemTray::restorePlasmoids() @@ -394,8 +419,6 @@ } } - QStringList ownApplets; - QMap sortedApplets; for (auto it = m_systrayApplets.constBegin(); it != m_systrayApplets.constEnd(); ++it) { const KPluginMetaData &info = it.value(); @@ -437,28 +460,15 @@ initDBusActivatables(); } -QStringList SystemTray::defaultPlasmoids() const +QAbstractItemModel *SystemTray::systemTrayModel() { - return m_defaultPlasmoids; + return m_systemTrayModel; } QAbstractItemModel* SystemTray::availablePlasmoids() { 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; } @@ -503,7 +513,7 @@ connect(systemCallWatcher, &QDBusPendingCallWatcher::finished, [=](QDBusPendingCallWatcher *callWatcher){ SystemTray::serviceNameFetchFinished(callWatcher, QDBusConnection::systemBus()); - }); + }); } void SystemTray::serviceNameFetchFinished(QDBusPendingCallWatcher* watcher, const QDBusConnection &connection) 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,254 @@ +/*************************************************************************** + * 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)) { + Plasma::Service *service = m_dataEngine->serviceForSource(source); + if (!service) { + return nullptr; + } + m_services[source] = service; + } + + return m_services.value(source); +} + +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); +}