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,11 +19,12 @@ target_link_libraries(org.kde.plasma.private.systemtray Qt5::Gui Qt5::Quick - KF5::Plasma Qt5::DBus + KF5::Plasma KF5::IconThemes 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 @@ -94,7 +94,7 @@ "index": i, "taskId": item.Id, "name": item.Title, - "iconName": item.IconName, + "iconName": item.ToolTipIcon !== "" ? item.ToolTipIcon : item.Icon ? item.Icon : item.IconName, "icon": item.Icon }); } @@ -248,11 +248,7 @@ visible: modelData.hasOwnProperty("shortcut") 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 + modelData.applet.globalShortcut = keySequence iconsPage.configurationChanged() } 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 @@ -60,8 +60,30 @@ } } onAppletChanged: { + 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; + }); + } + } + if (!applet) { - plasmoidContainer.destroy(); print("applet destroyed") } } 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 @@ -24,16 +24,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": @@ -48,7 +50,7 @@ PlasmaCore.IconItem { id: iconItem - source: Icon ? Icon : IconName + source: model.Icon ? model.Icon : model.IconName width: Math.min(parent.width, parent.height) height: width active: taskIcon.containsMouse @@ -67,8 +69,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; @@ -81,13 +83,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; @@ -98,7 +99,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; @@ -112,14 +113,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 @@ -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") - Plasmoid.onExpandedChanged: { if (!plasmoid.expanded) { dialog.visible = plasmoid.expanded; @@ -104,32 +99,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 @@ -148,62 +117,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: { @@ -231,23 +150,31 @@ 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 - - delegate: StatusNotifierItem {} + height: contentHeight + + model: plasmoid.nativeInterface.systemTrayModel + 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,9 +460,9 @@ initDBusActivatables(); } -QStringList SystemTray::defaultPlasmoids() const +QAbstractItemModel *SystemTray::systemTrayModel() { - return m_defaultPlasmoids; + return m_systemTrayModel; } QAbstractItemModel* SystemTray::availablePlasmoids() @@ -455,7 +478,7 @@ name += i18n(" (Automatic load)"); } QStandardItem *item = new QStandardItem(QIcon::fromTheme(info.iconName()), name); - item->setData(info.pluginId()); + item->setData(info.pluginId(), static_cast(PlasmoidModel::PlasmoidModelRole::PluginRole)); m_availablePlasmoidsModel->appendRow(item); } m_availablePlasmoidsModel->sort(0 /*column*/); @@ -503,7 +526,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,110 @@ +/*************************************************************************** + * 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 +#include + +namespace Plasma { + class Applet; +} + +class PlasmoidModel: public QStandardItemModel +{ + Q_OBJECT +public: + enum class PlasmoidModelRole { + PluginRole = Qt::UserRole + 100, + AppletRole + }; + + 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 = Qt::UserRole + 1, + AttentionIcon = DataEngineSource + 2, + 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: + 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,189 @@ +/*************************************************************************** + * 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 + +PlasmoidModel::PlasmoidModel(QObject *parent): QStandardItemModel(parent) +{ +} + +QHash PlasmoidModel::roleNames() const +{ + QHash roles = QStandardItemModel::roleNames(); + roles.insert(static_cast(PlasmoidModelRole::PluginRole), QByteArrayLiteral("plugin")); + roles.insert(static_cast(PlasmoidModelRole::AppletRole), QByteArrayLiteral("applet")); + return roles; +} + +void PlasmoidModel::addApplet(Plasma::Applet *applet) +{ + auto info = applet->pluginMetaData(); + QStandardItem *item = new QStandardItem(info.name()); + item->setData(info.pluginId(), static_cast(PlasmoidModelRole::PluginRole)); + item->setData(QVariant::fromValue(applet->property("_plasma_graphicObject").value()), static_cast(PlasmoidModelRole::AppletRole)); + appendRow(item); +} + +void PlasmoidModel::removeApplet(Plasma::Applet *applet) +{ + int rows = rowCount(); + for (int i = 0; i < rows; i++) { + QModelIndex idx = index(i, 0); + QVariant plugin = data(idx, static_cast(PlasmoidModelRole::PluginRole)); + if (plugin.isValid() && plugin.value() == applet->pluginMetaData().pluginId()) { + removeRow(i); + 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(Role::DataEngineSource), QByteArrayLiteral("DataEngineSource")); + roles.insert(static_cast(Role::AttentionIcon), "AttentionIcon"); + roles.insert(static_cast(Role::AttentionIconName), "AttentionIconName"); + roles.insert(static_cast(Role::AttentionMovieName), "AttentionMovieName"); + roles.insert(static_cast(Role::Category), "Category"); + roles.insert(static_cast(Role::Icon), "Icon"); + roles.insert(static_cast(Role::IconName), "IconName"); + roles.insert(static_cast(Role::IconThemePath), "IconThemePath"); + roles.insert(static_cast(Role::IconsChanged), "IconsChanged"); + roles.insert(static_cast(Role::Id), "Id"); + roles.insert(static_cast(Role::ItemIsMenu), "ItemIsMenu"); + roles.insert(static_cast(Role::OverlayIconName), "OverlayIconName"); + roles.insert(static_cast(Role::Status), "Status"); + roles.insert(static_cast(Role::StatusChanged), "StatusChanged"); + roles.insert(static_cast(Role::Title), "Title"); + roles.insert(static_cast(Role::TitleChanged), "TitleChanged"); + roles.insert(static_cast(Role::ToolTipChanged), "ToolTipChanged"); + roles.insert(static_cast(Role::ToolTipIcon), "ToolTipIcon"); + roles.insert(static_cast(Role::ToolTipSubTitle), "ToolTipSubTitle"); + roles.insert(static_cast(Role::ToolTipTitle), "ToolTipTitle"); + roles.insert(static_cast(Role::WindowId), "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(data.value("Title").toString(), Qt::DisplayRole); + + dataItem->setData(sourceName, static_cast(Role::DataEngineSource)); + + dataItem->setData(data.value("AttentionIcon"), static_cast(Role::AttentionIcon)); + dataItem->setData(data.value("AttentionIconName"), static_cast(Role::AttentionIconName)); + dataItem->setData(data.value("AttentionMovieName"), static_cast(Role::AttentionMovieName)); + dataItem->setData(data.value("Category"), static_cast(Role::Category)); + dataItem->setData(data.value("Icon"), static_cast(Role::Icon)); + dataItem->setData(data.value("IconName"), static_cast(Role::IconName)); + dataItem->setData(data.value("IconThemePath"), static_cast(Role::IconThemePath)); + dataItem->setData(data.value("IconsChanged"), static_cast(Role::IconsChanged)); + dataItem->setData(data.value("Id"), static_cast(Role::Id)); + dataItem->setData(data.value("ItemIsMenu"), static_cast(Role::ItemIsMenu)); + dataItem->setData(data.value("OverlayIconName"), static_cast(Role::OverlayIconName)); + dataItem->setData(data.value("Status"), static_cast(Role::Status)); + dataItem->setData(data.value("StatusChanged"), static_cast(Role::StatusChanged)); + dataItem->setData(data.value("Title"), static_cast(Role::Title)); + dataItem->setData(data.value("TitleChanged"), static_cast(Role::TitleChanged)); + dataItem->setData(data.value("ToolTipChanged"), static_cast(Role::ToolTipChanged)); + dataItem->setData(data.value("ToolTipIcon"), static_cast(Role::ToolTipIcon)); + dataItem->setData(data.value("ToolTipSubTitle"), static_cast(Role::ToolTipSubTitle)); + dataItem->setData(data.value("ToolTipTitle"), static_cast(Role::ToolTipTitle)); + dataItem->setData(data.value("WindowId"), static_cast(Role::WindowId)); + + if (!m_sources.contains(sourceName)) { + m_sources.append(sourceName); + appendRow(dataItem); + } +} + +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); +}