diff --git a/applets/systemtray/CMakeLists.txt b/applets/systemtray/CMakeLists.txt index 97794e59e..55737caa2 100644 --- a/applets/systemtray/CMakeLists.txt +++ b/applets/systemtray/CMakeLists.txt @@ -1,33 +1,34 @@ add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.private.systemtray\") plasma_install_package(package org.kde.plasma.private.systemtray) set(systemtray_SRCS systemtraymodel.cpp + sortedsystemtraymodel.cpp systemtray.cpp ) ecm_qt_declare_logging_category(systemtray_SRCS HEADER debug.h IDENTIFIER SYSTEM_TRAY CATEGORY_NAME kde.systemtray DEFAULT_SEVERITY Info) add_library(org.kde.plasma.private.systemtray MODULE ${systemtray_SRCS}) kcoreaddons_desktop_to_json(org.kde.plasma.private.systemtray package/metadata.desktop) target_link_libraries(org.kde.plasma.private.systemtray Qt5::Gui Qt5::Quick Qt5::DBus KF5::Plasma KF5::XmlGui KF5::I18n KF5::ItemModels) install(TARGETS org.kde.plasma.private.systemtray DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets) add_subdirectory(container) if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/applets/systemtray/package/contents/ui/ConfigEntries.qml b/applets/systemtray/package/contents/ui/ConfigEntries.qml index 33a7f325d..b7e2bfddc 100644 --- a/applets/systemtray/package/contents/ui/ConfigEntries.qml +++ b/applets/systemtray/package/contents/ui/ConfigEntries.qml @@ -1,280 +1,268 @@ /* * Copyright 2013 Sebastian Kügler * Copyright 2014 Marco Martin * Copyright 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 2.010-1301, USA. */ import QtQuick 2.5 import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.3 import org.kde.plasma.core 2.1 as PlasmaCore import org.kde.kquickcontrols 2.0 as KQC import org.kde.kirigami 2.10 as Kirigami ColumnLayout { id: iconsPage signal configurationChanged property var cfg_shownItems: [] property var cfg_hiddenItems: [] property var cfg_extraItems: [] property alias cfg_showAllItems: showAllCheckBox.checked QQC2.CheckBox { id: showAllCheckBox text: i18n("Always show all entries") } function categoryName(category) { switch (category) { case "ApplicationStatus": return i18n("Application Status") case "Communications": return i18n("Communications") case "SystemServices": return i18n("System Services") case "Hardware": return i18n("Hardware Control") case "UnknownCategory": default: return i18n("Miscellaneous") } } QQC2.ScrollView { id: scrollView Layout.fillWidth: true Layout.fillHeight: true contentHeight: itemsList.implicitHeight Component.onCompleted: scrollView.background.visible = true property bool scrollBarVisible: QQC2.ScrollBar.vertical && QQC2.ScrollBar.vertical.visible property var scrollBarWidth: scrollBarVisible ? QQC2.ScrollBar.vertical.width : 0 ListView { id: itemsList property var visibilityColumnWidth: units.gridUnit property var keySequenceColumnWidth: units.gridUnit - model: PlasmaCore.SortFilterModel { - sourceModel: PlasmaCore.SortFilterModel { - sourceModel: plasmoid.nativeInterface.systemTrayModel - - sortRole: "display" - sortColumn: 0 - isSortLocaleAware: true - } - - sortRole: "category" - sortColumn: 0 - isSortLocaleAware: true - } + model: plasmoid.nativeInterface.configSystemTrayModel header: Kirigami.AbstractListItem { hoverEnabled: false RowLayout { Kirigami.Heading { text: i18nc("Name of the system tray entry", "Entry") level: 2 Layout.fillWidth: true } Kirigami.Heading { text: i18n("Visibility") level: 2 Layout.preferredWidth: itemsList.visibilityColumnWidth Component.onCompleted: itemsList.visibilityColumnWidth = Math.max(implicitWidth, itemsList.visibilityColumnWidth) } Kirigami.Heading { text: i18n("Keyboard Shortcut") level: 2 Layout.preferredWidth: itemsList.keySequenceColumnWidth Component.onCompleted: itemsList.keySequenceColumnWidth = Math.max(implicitWidth, itemsList.keySequenceColumnWidth) } } } section { property: "category" delegate: Kirigami.ListSectionHeader { label: categoryName(section) } } delegate: Kirigami.AbstractListItem { highlighted: false hoverEnabled: false property bool isPlasmoid: model.itemType === "Plasmoid" contentItem: RowLayout { RowLayout { Layout.fillWidth: true Kirigami.Icon { width: units.iconSizes.small height: width source: model.decoration } QQC2.Label { Layout.fillWidth: true text: model.display elide: Text.ElideRight wrapMode: Text.NoWrap } } QQC2.ComboBox { id: visibilityComboBox property var contentWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) implicitWidth: Math.max(contentWidth, itemsList.visibilityColumnWidth) Component.onCompleted: itemsList.visibilityColumnWidth = Math.max(implicitWidth, itemsList.visibilityColumnWidth) enabled: (!showAllCheckBox.checked || isPlasmoid) && itemId textRole: "text" model: comboBoxModel() currentIndex: { var value if (cfg_shownItems.indexOf(itemId) !== -1) { value = "shown" } else if (cfg_hiddenItems.indexOf(itemId) !== -1) { value = "hidden" } else if (isPlasmoid && cfg_extraItems.indexOf(itemId) === -1) { value = "disabled" } else { value = "auto" } for (var i = 0; i < model.length; i++) { if (model[i].value === value) { return i } } return 0 } property var myCurrentValue: model[currentIndex].value onActivated: { var shownIndex = cfg_shownItems.indexOf(itemId) var hiddenIndex = cfg_hiddenItems.indexOf(itemId) var extraIndex = cfg_extraItems.indexOf(itemId) switch (myCurrentValue) { case "auto": if (shownIndex > -1) { cfg_shownItems.splice(shownIndex, 1) } if (hiddenIndex > -1) { cfg_hiddenItems.splice(hiddenIndex, 1) } if (extraIndex === -1) { cfg_extraItems.push(itemId) } break case "shown": if (shownIndex === -1) { cfg_shownItems.push(itemId) } if (hiddenIndex > -1) { cfg_hiddenItems.splice(hiddenIndex, 1) } if (extraIndex === -1) { cfg_extraItems.push(itemId) } break case "hidden": if (shownIndex > -1) { cfg_shownItems.splice(shownIndex, 1) } if (hiddenIndex === -1) { cfg_hiddenItems.push(itemId) } if (extraIndex === -1) { cfg_extraItems.push(itemId) } break case "disabled": if (extraIndex > -1) { cfg_extraItems.splice(extraIndex, 1) } break } iconsPage.configurationChanged() } function comboBoxModel() { var autoElement = {"value": "auto", "text": i18n("Shown when relevant")} var shownElement = {"value": "shown", "text": i18n("Always shown")} var hiddenElement = {"value": "hidden", "text": i18n("Always hidden")} var disabledElement = {"value": "disabled", "text": i18n("Disabled")} if (showAllCheckBox.checked) { if (isPlasmoid) { return [autoElement, disabledElement] } else { return [shownElement] } } else { if (isPlasmoid) { return [autoElement, shownElement, hiddenElement, disabledElement] } else { return [autoElement, shownElement, hiddenElement] } } } } KQC.KeySequenceItem { id: keySequenceItem Layout.minimumWidth: itemsList.keySequenceColumnWidth Layout.preferredWidth: itemsList.keySequenceColumnWidth Component.onCompleted: itemsList.keySequenceColumnWidth = Math.max(implicitWidth, itemsList.keySequenceColumnWidth) visible: isPlasmoid enabled: visibilityComboBox.myCurrentValue !== "disabled" keySequence: model.applet ? model.applet.globalShortcut : "" onKeySequenceChanged: { if (model.applet && keySequence !== model.applet.globalShortcut) { model.applet.globalShortcut = keySequence itemsList.keySequenceColumnWidth = Math.max(implicitWidth, itemsList.keySequenceColumnWidth) } } } // Placeholder for when KeySequenceItem is not visible Item { Layout.minimumWidth: itemsList.keySequenceColumnWidth Layout.maximumWidth: itemsList.keySequenceColumnWidth visible: !keySequenceItem.visible } } } } } } diff --git a/applets/systemtray/sortedsystemtraymodel.cpp b/applets/systemtray/sortedsystemtraymodel.cpp new file mode 100644 index 000000000..3d6bf8413 --- /dev/null +++ b/applets/systemtray/sortedsystemtraymodel.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * 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 "sortedsystemtraymodel.h" +#include "systemtraymodel.h" +#include "debug.h" + +#include + +SortedSystemTrayModel::SortedSystemTrayModel(SortingType sorting, QObject *parent) + : QSortFilterProxyModel(parent), + m_sorting(sorting) +{ + setSortLocaleAware(true); + sort(0); +} + +bool SortedSystemTrayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + switch (m_sorting) { + case SortedSystemTrayModel::SortingType::ConfigurationPage: + return lessThanConfigurationPage(left, right); + } + + return QSortFilterProxyModel::lessThan(left, right); +} + +bool SortedSystemTrayModel::lessThanConfigurationPage(const QModelIndex &left, const QModelIndex &right) const +{ + const int categoriesComparison = compareCategoriesAlphabetically(left, right); + if (categoriesComparison == 0) { + return QSortFilterProxyModel::lessThan(left, right); + } else { + return categoriesComparison < 0; + } +} + +int SortedSystemTrayModel::compareCategoriesAlphabetically(const QModelIndex &left, const QModelIndex &right) const +{ + QVariant leftData = sourceModel()->data(left, static_cast(BaseModel::BaseRole::Category)); + QString leftCategory = leftData.isNull() ? QStringLiteral("UnknownCategory") : leftData.toString(); + + QVariant rightData = sourceModel()->data(right, static_cast(BaseModel::BaseRole::Category)); + QString rightCategory = rightData.isNull() ? QStringLiteral("UnknownCategory") : rightData.toString(); + + return QString::localeAwareCompare(leftCategory, rightCategory); +} diff --git a/applets/systemtray/sortedsystemtraymodel.h b/applets/systemtray/sortedsystemtraymodel.h new file mode 100644 index 000000000..94daabd49 --- /dev/null +++ b/applets/systemtray/sortedsystemtraymodel.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * 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 SORTEDSYSTEMTRAYMODEL_H +#define SORTEDSYSTEMTRAYMODEL_H + +#include + +class SortedSystemTrayModel : public QSortFilterProxyModel { + Q_OBJECT +public: + enum class SortingType { + ConfigurationPage + }; + + explicit SortedSystemTrayModel(SortingType sorting, QObject *parent = nullptr); + +protected: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; + +private: + bool lessThanConfigurationPage(const QModelIndex &left, const QModelIndex &right) const; + + int compareCategoriesAlphabetically(const QModelIndex &left, const QModelIndex &right) const; + + SortingType m_sorting; +}; + +#endif // SORTEDSYSTEMTRAYMODEL_H diff --git a/applets/systemtray/systemtray.cpp b/applets/systemtray/systemtray.cpp index c065dc4d5..e666962c4 100644 --- a/applets/systemtray/systemtray.cpp +++ b/applets/systemtray/systemtray.cpp @@ -1,574 +1,579 @@ /*************************************************************************** * Copyright (C) 2015 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "systemtray.h" #include "systemtraymodel.h" +#include "sortedsystemtraymodel.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SystemTray::SystemTray(QObject *parent, const QVariantList &args) : Plasma::Containment(parent, args), m_availablePlasmoidsModel(nullptr), - m_systemTrayModel(new SystemTrayModel(this)) + m_systemTrayModel(new SystemTrayModel(this)), + m_configSystemTrayModel(nullptr) { 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(currentPlasmoidsModel); m_systemTrayModel->addSourceModel(m_statusNotifierModel); } SystemTray::~SystemTray() { } void SystemTray::init() { Containment::init(); for (const auto &info: Plasma::PluginLoader::self()->listAppletMetaData(QString())) { if (!info.isValid() || info.value(QStringLiteral("X-Plasma-NotificationArea")) != "true") { continue; } m_systrayApplets[info.pluginId()] = info; if (info.isEnabledByDefault()) { m_defaultPlasmoids += info.pluginId(); } const QString dbusactivation = info.value(QStringLiteral("X-Plasma-DBusActivationService")); if (!dbusactivation.isEmpty()) { qCDebug(SYSTEM_TRAY) << "ST Found DBus-able Applet: " << info.pluginId() << dbusactivation; QRegExp rx(dbusactivation); rx.setPatternSyntax(QRegExp::Wildcard); m_dbusActivatableTasks[info.pluginId()] = rx; } } } void SystemTray::newTask(const QString &task) { const auto appletsList = applets(); for (Plasma::Applet *applet : appletsList) { if (!applet->pluginMetaData().isValid()) { continue; } //only allow one instance per applet if (task == applet->pluginMetaData().pluginId()) { //Applet::destroy doesn't delete the applet from Containment::applets in the same event //potentially a dbus activated service being restarted can be added in this time. if (!applet->destroyed()) { return; } } } //known one, recycle the id to reuse old config if (m_knownPlugins.contains(task)) { Applet *applet = Plasma::PluginLoader::self()->loadApplet(task, m_knownPlugins.value(task), QVariantList()); //this should never happen unless explicitly wrong config is hand-written or //(more likely) a previously added applet is uninstalled if (!applet) { qWarning() << "Unable to find applet" << task; return; } applet->setProperty("org.kde.plasma:force-create", true); addApplet(applet); //create a new one automatic id, new config group } else { Applet * applet = createApplet(task, QVariantList() << "org.kde.plasma:force-create"); if (applet) { m_knownPlugins[task] = applet->id(); } } } void SystemTray::cleanupTask(const QString &task) { const auto appletsList = applets(); for (Plasma::Applet *applet : appletsList) { if (applet->pluginMetaData().isValid() && task == applet->pluginMetaData().pluginId()) { //we are *not* cleaning the config here, because since is one //of those automatically loaded/unloaded by dbus, we want to recycle //the config the next time it's loaded, in case the user configured something here applet->deleteLater(); //HACK: we need to remove the applet from Containment::applets() as soon as possible //otherwise we may have disappearing applets for restarting dbus services //this may be removed when we depend from a frameworks version in which appletDeleted is emitted as soon as deleteLater() is called emit appletDeleted(applet); } } } void SystemTray::showPlasmoidMenu(QQuickItem *appletInterface, int x, int y) { if (!appletInterface) { return; } Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); QPointF pos = appletInterface->mapToScene(QPointF(x, y)); if (appletInterface->window() && appletInterface->window()->screen()) { pos = appletInterface->window()->mapToGlobal(pos.toPoint()); } else { pos = QPoint(); } QMenu *desktopMenu = new QMenu; connect(this, &QObject::destroyed, desktopMenu, &QMenu::close); desktopMenu->setAttribute(Qt::WA_DeleteOnClose); //this is a workaround where Qt will fail to realize a mouse has been released // this happens if a window which does not accept focus spawns a new window that takes focus and X grab // whilst the mouse is depressed // https://bugreports.qt.io/browse/QTBUG-59044 // this causes the next click to go missing //by releasing manually we avoid that situation auto ungrabMouseHack = [appletInterface]() { if (appletInterface->window() && appletInterface->window()->mouseGrabberItem()) { appletInterface->window()->mouseGrabberItem()->ungrabMouse(); } }; QTimer::singleShot(0, appletInterface, ungrabMouseHack); //end workaround emit applet->contextualActionsAboutToShow(); const auto contextActions = applet->contextualActions(); for (QAction *action : contextActions) { if (action) { desktopMenu->addAction(action); } } QAction *runAssociatedApplication = applet->actions()->action(QStringLiteral("run associated application")); if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { desktopMenu->addAction(runAssociatedApplication); } if (applet->actions()->action(QStringLiteral("configure"))) { desktopMenu->addAction(applet->actions()->action(QStringLiteral("configure"))); } if (desktopMenu->isEmpty()) { delete desktopMenu; return; } desktopMenu->adjustSize(); if (QScreen *screen = appletInterface->window()->screen()) { const QRect geo = screen->availableGeometry(); pos = QPoint(qBound(geo.left(), (int)pos.x(), geo.right() - desktopMenu->width()), qBound(geo.top(), (int)pos.y(), geo.bottom() - desktopMenu->height())); } KAcceleratorManager::manage(desktopMenu); desktopMenu->winId(); desktopMenu->windowHandle()->setTransientParent(appletInterface->window()); desktopMenu->popup(pos.toPoint()); } QString SystemTray::plasmoidCategory(QQuickItem *appletInterface) const { if (!appletInterface) { return QStringLiteral("UnknownCategory"); } Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); if (!applet || !applet->pluginMetaData().isValid()) { return QStringLiteral("UnknownCategory"); } const QString cat = applet->pluginMetaData().value(QStringLiteral("X-Plasma-NotificationAreaCategory")); if (cat.isEmpty()) { return QStringLiteral("UnknownCategory"); } return cat; } void SystemTray::showStatusNotifierContextMenu(KJob *job, QQuickItem *statusNotifierIcon) { if (QCoreApplication::closingDown() || !statusNotifierIcon) { // apparently an edge case can be triggered due to the async nature of all this // see: https://bugs.kde.org/show_bug.cgi?id=251977 return; } Plasma::ServiceJob *sjob = qobject_cast(job); if (!sjob) { return; } QMenu *menu = qobject_cast(sjob->result().value()); if (menu) { menu->adjustSize(); const auto parameters = sjob->parameters(); int x = parameters[QStringLiteral("x")].toInt(); int y = parameters[QStringLiteral("y")].toInt(); //try tofind the icon screen coordinates, and adjust the position as a poor //man's popupPosition QRect screenItemRect(statusNotifierIcon->mapToScene(QPointF(0, 0)).toPoint(), QSize(statusNotifierIcon->width(), statusNotifierIcon->height())); if (statusNotifierIcon->window()) { screenItemRect.moveTopLeft(statusNotifierIcon->window()->mapToGlobal(screenItemRect.topLeft())); } switch (location()) { case Plasma::Types::LeftEdge: x = screenItemRect.right(); y = screenItemRect.top(); break; case Plasma::Types::RightEdge: x = screenItemRect.left() - menu->width(); y = screenItemRect.top(); break; case Plasma::Types::TopEdge: x = screenItemRect.left(); y = screenItemRect.bottom(); break; case Plasma::Types::BottomEdge: x = screenItemRect.left(); y = screenItemRect.top() - menu->height(); break; default: x = screenItemRect.left(); if (screenItemRect.top() - menu->height() >= statusNotifierIcon->window()->screen()->geometry().top()) { y = screenItemRect.top() - menu->height(); } else { y = screenItemRect.bottom(); } } KAcceleratorManager::manage(menu); menu->winId(); menu->windowHandle()->setTransientParent(statusNotifierIcon->window()); menu->popup(QPoint(x, y)); } } QPointF SystemTray::popupPosition(QQuickItem* visualParent, int x, int y) { if (!visualParent) { return QPointF(0, 0); } QPointF pos = visualParent->mapToScene(QPointF(x, y)); if (visualParent->window() && visualParent->window()->screen()) { pos = visualParent->window()->mapToGlobal(pos.toPoint()); } else { return QPoint(); } return pos; } void SystemTray::reorderItemBefore(QQuickItem* before, QQuickItem* after) { if (!before || !after) { return; } before->setVisible(false); before->setParentItem(after->parentItem()); before->stackBefore(after); before->setVisible(true); } void SystemTray::reorderItemAfter(QQuickItem* after, QQuickItem* before) { if (!before || !after) { return; } after->setVisible(false); after->setParentItem(before->parentItem()); after->stackAfter(before); after->setVisible(true); } bool SystemTray::isSystemTrayApplet(const QString &appletId) { return m_systrayApplets.contains(appletId); } void SystemTray::restoreContents(KConfigGroup &group) { Q_UNUSED(group); //NOTE: RestoreContents shouldn't do anything here because is too soon, so have an empty reimplementation } void SystemTray::restorePlasmoids() { if (!isContainment()) { qCWarning(SYSTEM_TRAY) << "Loaded as an applet, this shouldn't have happened"; return; } //First: remove all that are not allowed anymore const auto appletsList = applets(); for (Plasma::Applet *applet : appletsList) { //Here it should always be valid. //for some reason it not always is. if (!applet->pluginMetaData().isValid()) { applet->config().parent().deleteGroup(); applet->deleteLater(); } else { const QString task = applet->pluginMetaData().pluginId(); if (!m_allowedPlasmoids.contains(task)) { //in those cases we do delete the applet config completely //as they were explicitly disabled by the user applet->config().parent().deleteGroup(); applet->deleteLater(); } } } KConfigGroup cg = config(); cg = KConfigGroup(&cg, "Applets"); const auto groups = cg.groupList(); for (const QString &group : groups) { KConfigGroup appletConfig(&cg, group); QString plugin = appletConfig.readEntry("plugin"); if (!plugin.isEmpty()) { m_knownPlugins[plugin] = group.toInt(); } } - QStringList ownApplets; - QMap sortedApplets; for (auto it = m_systrayApplets.constBegin(); it != m_systrayApplets.constEnd(); ++it) { const KPluginMetaData &info = it.value(); if (m_allowedPlasmoids.contains(info.pluginId()) && ! m_dbusActivatableTasks.contains(info.pluginId())) { //FIXME // if we already have a plugin with this exact name in it, then check if it is the // same plugin and skip it if it is indeed already listed if (sortedApplets.contains(info.name())) { bool dupe = false; // it is possible (though poor form) to have multiple applets // with the same visible name but different plugins, so we hve to check all values const auto infos = sortedApplets.values(info.name()); for (const KPluginMetaData &existingInfo : infos) { if (existingInfo.pluginId() == info.pluginId()) { dupe = true; break; } } if (dupe) { continue; } } // insertMulti because it is possible (though poor form) to have multiple applets // with the same visible name but different plugins sortedApplets.insertMulti(info.name(), info); } } for (const KPluginMetaData &info : qAsConst(sortedApplets)) { qCDebug(SYSTEM_TRAY) << " Adding applet: " << info.name(); if (m_allowedPlasmoids.contains(info.pluginId())) { newTask(info.pluginId()); } } initDBusActivatables(); } -QAbstractItemModel *SystemTray::systemTrayModel() +QAbstractItemModel *SystemTray::configSystemTrayModel() { - return m_systemTrayModel; + if (!m_configSystemTrayModel) { + m_configSystemTrayModel = new SortedSystemTrayModel(SortedSystemTrayModel::SortingType::ConfigurationPage, this); + m_configSystemTrayModel->setSourceModel(m_systemTrayModel); + } + return m_configSystemTrayModel; } QStringList SystemTray::defaultPlasmoids() const { return m_defaultPlasmoids; } QAbstractItemModel* SystemTray::availablePlasmoids() { if (!m_availablePlasmoidsModel) { m_availablePlasmoidsModel = new PlasmoidModel(this); } return m_availablePlasmoidsModel; } QStringList SystemTray::allowedPlasmoids() const { return m_allowedPlasmoids; } void SystemTray::setAllowedPlasmoids(const QStringList &allowed) { if (allowed == m_allowedPlasmoids) { return; } m_allowedPlasmoids = allowed; restorePlasmoids(); emit allowedPlasmoidsChanged(); } void SystemTray::initDBusActivatables() { /* Loading and unloading Plasmoids when dbus services come and go * * This works as follows: * - we collect a list of plugins and related services in m_dbusActivatableTasks * - we query DBus for the list of services, async (initDBusActivatables()) * - we go over that list, adding tasks when a service and plugin match (serviceNameFetchFinished()) * - we start watching for new services, and do the same (serviceNameFetchFinished()) * - whenever a service is gone, we check whether to unload a Plasmoid (serviceUnregistered()) */ QDBusPendingCall async = QDBusConnection::sessionBus().interface()->asyncCall(QStringLiteral("ListNames")); QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); connect(callWatcher, &QDBusPendingCallWatcher::finished, [=](QDBusPendingCallWatcher *callWatcher){ SystemTray::serviceNameFetchFinished(callWatcher, QDBusConnection::sessionBus()); }); QDBusPendingCall systemAsync = QDBusConnection::systemBus().interface()->asyncCall(QStringLiteral("ListNames")); QDBusPendingCallWatcher *systemCallWatcher = new QDBusPendingCallWatcher(systemAsync, this); connect(systemCallWatcher, &QDBusPendingCallWatcher::finished, [=](QDBusPendingCallWatcher *callWatcher){ SystemTray::serviceNameFetchFinished(callWatcher, QDBusConnection::systemBus()); - }); + }); } void SystemTray::serviceNameFetchFinished(QDBusPendingCallWatcher* watcher, const QDBusConnection &connection) { QDBusPendingReply propsReply = *watcher; watcher->deleteLater(); if (propsReply.isError()) { qCWarning(SYSTEM_TRAY) << "Could not get list of available D-Bus services"; } else { const auto propsReplyValue = propsReply.value(); for (const QString& serviceName : propsReplyValue) { serviceRegistered(serviceName); } } // Watch for new services // We need to watch for all of new services here, since we want to "match" the names, // not just compare them // This makes mpris work, since it wants to match org.mpris.MediaPlayer2.dragonplayer // against org.mpris.MediaPlayer2 // QDBusServiceWatcher is not capable for watching wildcard service right now // See: // https://bugreports.qt.io/browse/QTBUG-51683 // https://bugreports.qt.io/browse/QTBUG-33829 connect(connection.interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, &SystemTray::serviceOwnerChanged); } void SystemTray::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { if (oldOwner.isEmpty()) { serviceRegistered(serviceName); } else if (newOwner.isEmpty()) { serviceUnregistered(serviceName); } } void SystemTray::serviceRegistered(const QString &service) { if (service.startsWith(QLatin1Char(':'))) { return; } //qCDebug(SYSTEM_TRAY) << "DBus service appeared:" << service; for (auto it = m_dbusActivatableTasks.constBegin(), end = m_dbusActivatableTasks.constEnd(); it != end; ++it) { const QString &plugin = it.key(); if (!m_allowedPlasmoids.contains(plugin)) { continue; } const auto &rx = it.value(); if (rx.exactMatch(service)) { //qCDebug(SYSTEM_TRAY) << "ST : DBus service " << m_dbusActivatableTasks[plugin] << "appeared. Loading " << plugin; newTask(plugin); m_dbusServiceCounts[plugin]++; } } } void SystemTray::serviceUnregistered(const QString &service) { //qCDebug(SYSTEM_TRAY) << "DBus service disappeared:" << service; for (auto it = m_dbusActivatableTasks.constBegin(), end = m_dbusActivatableTasks.constEnd(); it != end; ++it) { const QString &plugin = it.key(); if (!m_allowedPlasmoids.contains(plugin)) { continue; } const auto &rx = it.value(); if (rx.exactMatch(service)) { m_dbusServiceCounts[plugin]--; Q_ASSERT(m_dbusServiceCounts[plugin] >= 0); if (m_dbusServiceCounts[plugin] == 0) { //qCDebug(SYSTEM_TRAY) << "ST : DBus service " << m_dbusActivatableTasks[plugin] << " disappeared. Unloading " << plugin; cleanupTask(plugin); } } } } K_EXPORT_PLASMA_APPLET_WITH_JSON(systemtray, SystemTray, "metadata.json") #include "systemtray.moc" diff --git a/applets/systemtray/systemtray.h b/applets/systemtray/systemtray.h index 2a74c7b14..fb2ed976e 100644 --- a/applets/systemtray/systemtray.h +++ b/applets/systemtray/systemtray.h @@ -1,137 +1,139 @@ /*************************************************************************** * Copyright (C) 2015 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #ifndef SYSTEMTRAY_H #define SYSTEMTRAY_H #include #include #include class QDBusPendingCallWatcher; class QDBusConnection; class QQuickItem; namespace Plasma { class Service; } class PlasmoidModel; class StatusNotifierModel; class SystemTrayModel; +class SortedSystemTrayModel; class SystemTray : public Plasma::Containment { Q_OBJECT - Q_PROPERTY(QAbstractItemModel* systemTrayModel READ systemTrayModel CONSTANT) + Q_PROPERTY(QAbstractItemModel* configSystemTrayModel READ configSystemTrayModel 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 ); ~SystemTray() override; void init() override; void restoreContents(KConfigGroup &group) override; void restorePlasmoids(); - QAbstractItemModel* systemTrayModel(); + QAbstractItemModel *configSystemTrayModel(); QStringList defaultPlasmoids() const; QAbstractItemModel* availablePlasmoids(); QStringList allowedPlasmoids() const; void setAllowedPlasmoids(const QStringList &allowed); //Creates an applet *if not already existing* void newTask(const QString &task); //cleans all instances of a given applet void cleanupTask(const QString &task); //Invokable utilities /** * Given an AppletInterface pointer, shows a proper context menu for it */ Q_INVOKABLE void showPlasmoidMenu(QQuickItem *appletInterface, int x, int y); /** * Returns the "X-Plasma-NotificationAreaCategory" * of the plasmoid metadata */ Q_INVOKABLE QString plasmoidCategory(QQuickItem *appletInterface) const; /** * Shows the context menu for a statusnotifieritem */ Q_INVOKABLE void showStatusNotifierContextMenu(KJob *job, QQuickItem *statusNotifierIcon); /** * Find out global coordinates for a popup given local MouseArea * coordinates */ Q_INVOKABLE QPointF popupPosition(QQuickItem* visualParent, int x, int y); /** * Reparent the item "before" with the same parent as the item "after", * then restack it before it, using QQuickITem::stackBefore. * used to quickly reorder icons in the systray (or hidden popup) * @see QQuickITem::stackBefore */ Q_INVOKABLE void reorderItemBefore(QQuickItem* before, QQuickItem* after); /** * Reparent the item "after" with the same parent as the item "before", * then restack it after it, using QQuickITem::stackAfter. * used to quickly reorder icons in the systray (or hidden popup) * @see QQuickITem::stackAfter */ Q_INVOKABLE void reorderItemAfter(QQuickItem* after, QQuickItem* before); Q_INVOKABLE bool isSystemTrayApplet(const QString &appletId); private Q_SLOTS: void serviceNameFetchFinished(QDBusPendingCallWatcher* watcher, const QDBusConnection &connection); void serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner); private: void serviceRegistered(const QString &service); void serviceUnregistered(const QString &service); Q_SIGNALS: void allowedPlasmoidsChanged(); private: void initDBusActivatables(); QStringList m_defaultPlasmoids; QHash m_systrayApplets; QHash m_dbusActivatableTasks; QStringList m_allowedPlasmoids; PlasmoidModel *m_availablePlasmoidsModel; StatusNotifierModel *m_statusNotifierModel; SystemTrayModel *m_systemTrayModel; + SortedSystemTrayModel *m_configSystemTrayModel; QHash m_knownPlugins; QHash m_dbusServiceCounts; }; #endif diff --git a/applets/systemtray/systemtraymodel.cpp b/applets/systemtray/systemtraymodel.cpp index 00fbf5440..2c3755275 100644 --- a/applets/systemtray/systemtraymodel.cpp +++ b/applets/systemtray/systemtraymodel.cpp @@ -1,282 +1,300 @@ /*************************************************************************** * 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 +BaseModel::BaseModel(QObject *parent) + : QStandardItemModel(parent) +{ +} + +QHash BaseModel::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(BaseRole::Category), QByteArrayLiteral("category")); + + return roles; +} + static QString plasmoidCategoryForMetadata(const KPluginMetaData &metadata) { QString category = QStringLiteral("UnknownCategory"); if (metadata.isValid()) { const QString notificationAreaCategory = metadata.value(QStringLiteral("X-Plasma-NotificationAreaCategory")); if (!notificationAreaCategory.isEmpty()) { category = notificationAreaCategory; } } return category; } -PlasmoidModel::PlasmoidModel(QObject *parent) : QStandardItemModel(parent) +PlasmoidModel::PlasmoidModel(QObject *parent) : BaseModel(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(QStringLiteral("Plasmoid"), static_cast(BaseRole::ItemType)); - item->setData(false, static_cast(BaseRole::CanRender)); - item->setData(plasmoidCategoryForMetadata(info), static_cast(BaseRole::Category)); + item->setData(info.pluginId(), static_cast(BaseModel::BaseRole::ItemId)); + item->setData(QStringLiteral("Plasmoid"), static_cast(BaseModel::BaseRole::ItemType)); + item->setData(false, static_cast(BaseModel::BaseRole::CanRender)); + item->setData(plasmoidCategoryForMetadata(info), static_cast(BaseModel::BaseRole::Category)); 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(BaseRole::Category), QByteArrayLiteral("category")); + QHash roles = BaseModel::roleNames(); 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(); + auto pluginMetaData = 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()) { + if (currentItem->data(static_cast(BaseModel::BaseRole::ItemId)) == pluginMetaData.pluginId()) { dataItem = currentItem; break; } } if (!dataItem) { - dataItem = new QStandardItem(QIcon::fromTheme(info.iconName()), info.name()); + dataItem = new QStandardItem(); appendRow(dataItem); } - dataItem->setData(info.pluginId(), static_cast(BaseRole::ItemId)); + dataItem->setData(applet->title(), Qt::DisplayRole); + connect(applet, &Plasma::Applet::titleChanged, this, [dataItem] (const QString &title) { + dataItem->setData(title, static_cast(Qt::DisplayRole)); + }); + dataItem->setData(QIcon::fromTheme(applet->icon()), Qt::DecorationRole); + connect(applet, &Plasma::Applet::iconChanged, this, [dataItem] (const QString &icon) { + dataItem->setData(QIcon::fromTheme(icon), Qt::DecorationRole); + }); + + dataItem->setData(pluginMetaData.pluginId(), static_cast(BaseModel::BaseRole::ItemId)); + dataItem->setData(true, static_cast(BaseModel::BaseRole::CanRender)); + dataItem->setData(plasmoidCategoryForMetadata(pluginMetaData), static_cast(BaseModel::BaseRole::Category)); + dataItem->setData(applet->property("_plasma_graphicObject"), static_cast(Role::Applet)); dataItem->setData(true, static_cast(Role::HasApplet)); - dataItem->setData(true, static_cast(BaseRole::CanRender)); - dataItem->setData(plasmoidCategoryForMetadata(applet->pluginMetaData()), static_cast(BaseRole::Category)); } 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)); + QVariant plugin = currentItem->data(static_cast(BaseModel::BaseRole::ItemId)); if (plugin.isValid() && plugin.value() == applet->pluginMetaData().pluginId()) { - currentItem->setData(false, static_cast(BaseRole::CanRender)); + currentItem->setData(false, static_cast(BaseModel::BaseRole::CanRender)); currentItem->setData(QVariant(), static_cast(Role::Applet)); currentItem->setData(false, static_cast(Role::HasApplet)); + applet->disconnect(this); return; } } } -StatusNotifierModel::StatusNotifierModel(QObject *parent) : QStandardItemModel(parent) +StatusNotifierModel::StatusNotifierModel(QObject *parent) : BaseModel(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(BaseRole::Category), QByteArrayLiteral("category")); + QHash roles = BaseModel::roleNames(); 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(QStringLiteral("StatusNotifier"), static_cast(BaseRole::ItemType)); - dataItem->setData(true, static_cast(BaseRole::CanRender)); + dataItem->setData(QStringLiteral("StatusNotifier"), static_cast(BaseModel::BaseRole::ItemType)); + dataItem->setData(true, static_cast(BaseModel::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(data.value("Category"), static_cast(BaseRole::Category)); + dataItem->setData(data.value("Id"), static_cast(BaseModel::BaseRole::ItemId)); + QVariant category = data.value("Category"); + dataItem->setData(category.isNull() ? QStringLiteral("UnknownCategory") : data.value("Category"), static_cast(BaseModel::BaseRole::Category)); 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) { QHashIterator it(sourceModel->roleNames()); while (it.hasNext()) { it.next(); if (!m_roleNames.contains(it.key())) { m_roleNames.insert(it.key(), it.value()); } } KConcatenateRowsProxyModel::addSourceModel(sourceModel); } diff --git a/applets/systemtray/systemtraymodel.h b/applets/systemtray/systemtraymodel.h index ea6cfdb5f..60b3c39fb 100644 --- a/applets/systemtray/systemtraymodel.h +++ b/applets/systemtray/systemtraymodel.h @@ -1,119 +1,128 @@ /*************************************************************************** * 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, - Category, - LastBaseRole +class BaseModel: public QStandardItemModel +{ + Q_OBJECT +public: + enum class BaseRole { + ItemType = Qt::UserRole + 1, + ItemId, + CanRender, + Category, + LastBaseRole + }; + + explicit BaseModel(QObject *parent = nullptr); + + QHash roleNames() const override; }; -class PlasmoidModel: public QStandardItemModel +class PlasmoidModel: public BaseModel { Q_OBJECT public: enum class Role { - Applet = static_cast(BaseRole::LastBaseRole) + 1, + Applet = static_cast(BaseModel::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 { +class StatusNotifierModel : public BaseModel, public Plasma::DataEngineConsumer { Q_OBJECT public: enum class Role { - DataEngineSource = static_cast(BaseRole::LastBaseRole) + 100, + DataEngineSource = static_cast(BaseModel::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