Commit 6d2fa8e6 authored by Konrad Materka's avatar Konrad Materka
Browse files

[System Tray] Unified data model for System Tray items

Summary:
Currently there are two different sources of SystemTray items:
* Plasmoids
* Status Notifier

Thi change adds new model that holds both Plasmoids and SNIs.

Test Plan: Add/disable applets. Start/stop SNI app.

Reviewers: #plasma, broulik, ngraham, nicolasfella

Reviewed By: nicolasfella

Subscribers: plasma-devel, nicolasfella, anthonyfieroni

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D25580
parent 360bc1df
......@@ -3,6 +3,7 @@ add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.private.syst
plasma_install_package(package org.kde.plasma.private.systemtray)
set(systemtray_SRCS
systemtraymodel.cpp
systemtray.cpp
)
......@@ -18,10 +19,11 @@ kcoreaddons_desktop_to_json(org.kde.plasma.private.systemtray package/metadata.d
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)
......
......@@ -36,32 +36,11 @@ ColumnLayout {
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 @@ ColumnLayout {
}
}
// 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 @@ ColumnLayout {
QIconItem {
width: units.iconSizes.small
height: width
icon: modelData.iconName || modelData.icon || ""
icon: modelData.icon
}
QQC2.Label {
......@@ -241,16 +210,12 @@ ColumnLayout {
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()
}
......
......@@ -89,13 +89,13 @@ Item {
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) {
......
......@@ -18,6 +18,7 @@
***************************************************************************/
#include "systemtray.h"
#include "systemtraymodel.h"
#include "debug.h"
#include <QDebug>
......@@ -42,27 +43,21 @@
#include <plasma_version.h>
class PlasmoidModel: public QStandardItemModel
{
public:
explicit PlasmoidModel(QObject *parent = nullptr)
: QStandardItemModel(parent)
{
}
QHash<int, QByteArray> roleNames() const override {
QHash<int, QByteArray> roles = QStandardItemModel::roleNames();
roles[Qt::UserRole+1] = "plugin";
return roles;
}
};
SystemTray::SystemTray(QObject *parent, const QVariantList &args)
: Plasma::Containment(parent, args),
m_availablePlasmoidsModel(nullptr)
m_availablePlasmoidsModel(nullptr),
m_systemTrayModel(new SystemTrayModel(this))
{
setHasConfigurationInterface(true);
setContainmentType(Plasma::Types::CustomEmbeddedContainment);
PlasmoidModel *currentPlasmoidsModel = new PlasmoidModel(m_systemTrayModel);
connect(this, &SystemTray::appletAdded, currentPlasmoidsModel, &PlasmoidModel::addApplet);
connect(this, &SystemTray::appletRemoved, currentPlasmoidsModel, &PlasmoidModel::removeApplet);
m_systemTrayModel->addSourceModel(currentPlasmoidsModel);
m_statusNotifierModel = new StatusNotifierModel(m_systemTrayModel);
m_systemTrayModel->addSourceModel(m_statusNotifierModel);
}
SystemTray::~SystemTray()
......@@ -437,6 +432,11 @@ void SystemTray::restorePlasmoids()
initDBusActivatables();
}
QAbstractItemModel *SystemTray::systemTrayModel()
{
return m_systemTrayModel;
}
QStringList SystemTray::defaultPlasmoids() const
{
return m_defaultPlasmoids;
......@@ -446,19 +446,6 @@ 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;
}
......
......@@ -29,11 +29,17 @@
class QDBusPendingCallWatcher;
class QDBusConnection;
class QQuickItem;
namespace Plasma {
class Service;
}
class PlasmoidModel;
class StatusNotifierModel;
class SystemTrayModel;
class SystemTray : public Plasma::Containment
{
Q_OBJECT
Q_PROPERTY(QAbstractItemModel* systemTrayModel READ systemTrayModel CONSTANT)
Q_PROPERTY(QAbstractItemModel* availablePlasmoids READ availablePlasmoids CONSTANT)
Q_PROPERTY(QStringList allowedPlasmoids READ allowedPlasmoids WRITE setAllowedPlasmoids NOTIFY allowedPlasmoidsChanged)
Q_PROPERTY(QStringList defaultPlasmoids READ defaultPlasmoids CONSTANT)
......@@ -47,6 +53,8 @@ public:
void restoreContents(KConfigGroup &group) override;
void restorePlasmoids();
QAbstractItemModel* systemTrayModel();
QStringList defaultPlasmoids() const;
QAbstractItemModel* availablePlasmoids();
......@@ -120,6 +128,8 @@ private:
QStringList m_allowedPlasmoids;
PlasmoidModel *m_availablePlasmoidsModel;
StatusNotifierModel *m_statusNotifierModel;
SystemTrayModel *m_systemTrayModel;
QHash<QString, int> m_knownPlugins;
QHash<QString, int> m_dbusServiceCounts;
};
......
/***************************************************************************
* Copyright (C) 2019 Konrad Materka <materka@gmail.com> *
* *
* 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 <QQuickItem>
#include <Plasma/Applet>
#include <Plasma/DataContainer>
#include <Plasma/Service>
#include <PluginLoader>
#include <KLocalizedString>
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<int>(BaseRole::ItemId));
item->setData("Plasmoid", static_cast<int>(BaseRole::ItemType));
item->setData(false, static_cast<int>(BaseRole::CanRender));
item->setData(false, static_cast<int>(Role::HasApplet));
appendRow(item);
}
sort(0);
}
QHash<int, QByteArray> PlasmoidModel::roleNames() const
{
QHash<int, QByteArray> roles = QStandardItemModel::roleNames();
roles.insert(static_cast<int>(BaseRole::ItemType), QByteArrayLiteral("itemType"));
roles.insert(static_cast<int>(BaseRole::ItemId), QByteArrayLiteral("itemId"));
roles.insert(static_cast<int>(BaseRole::CanRender), QByteArrayLiteral("canRender"));
roles.insert(static_cast<int>(Role::Applet), QByteArrayLiteral("applet"));
roles.insert(static_cast<int>(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<int>(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<int>(BaseRole::ItemId));
dataItem->setData(applet->property("_plasma_graphicObject"), static_cast<int>(Role::Applet));
dataItem->setData(true, static_cast<int>(Role::HasApplet));
dataItem->setData(true, static_cast<int>(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<int>(BaseRole::ItemId));
if (plugin.isValid() && plugin.value<QString>() == applet->pluginMetaData().pluginId()) {
currentItem->setData(false, static_cast<int>(BaseRole::CanRender));
currentItem->setData(QVariant(), static_cast<int>(Role::Applet));
currentItem->setData(false, static_cast<int>(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<int, QByteArray> StatusNotifierModel::roleNames() const
{
QHash<int, QByteArray> roles = QStandardItemModel::roleNames();
roles.insert(static_cast<int>(BaseRole::ItemType), QByteArrayLiteral("itemType"));
roles.insert(static_cast<int>(BaseRole::ItemId), QByteArrayLiteral("itemId"));
roles.insert(static_cast<int>(BaseRole::CanRender), QByteArrayLiteral("canRender"));
roles.insert(static_cast<int>(Role::DataEngineSource), QByteArrayLiteral("DataEngineSource"));
roles.insert(static_cast<int>(Role::AttentionIcon), QByteArrayLiteral("AttentionIcon"));
roles.insert(static_cast<int>(Role::AttentionIconName), QByteArrayLiteral("AttentionIconName"));
roles.insert(static_cast<int>(Role::AttentionMovieName), QByteArrayLiteral("AttentionMovieName"));
roles.insert(static_cast<int>(Role::Category), QByteArrayLiteral("Category"));
roles.insert(static_cast<int>(Role::Icon), QByteArrayLiteral("Icon"));
roles.insert(static_cast<int>(Role::IconName), QByteArrayLiteral("IconName"));
roles.insert(static_cast<int>(Role::IconThemePath), QByteArrayLiteral("IconThemePath"));
roles.insert(static_cast<int>(Role::IconsChanged), QByteArrayLiteral("IconsChanged"));
roles.insert(static_cast<int>(Role::Id), QByteArrayLiteral("Id"));
roles.insert(static_cast<int>(Role::ItemIsMenu), QByteArrayLiteral("ItemIsMenu"));
roles.insert(static_cast<int>(Role::OverlayIconName), QByteArrayLiteral("OverlayIconName"));
roles.insert(static_cast<int>(Role::Status), QByteArrayLiteral("Status"));
roles.insert(static_cast<int>(Role::StatusChanged), QByteArrayLiteral("StatusChanged"));
roles.insert(static_cast<int>(Role::Title), QByteArrayLiteral("Title"));
roles.insert(static_cast<int>(Role::TitleChanged), QByteArrayLiteral("TitleChanged"));
roles.insert(static_cast<int>(Role::ToolTipChanged), QByteArrayLiteral("ToolTipChanged"));
roles.insert(static_cast<int>(Role::ToolTipIcon), QByteArrayLiteral("ToolTipIcon"));
roles.insert(static_cast<int>(Role::ToolTipSubTitle), QByteArrayLiteral("ToolTipSubTitle"));
roles.insert(static_cast<int>(Role::ToolTipTitle), QByteArrayLiteral("ToolTipTitle"));
roles.insert(static_cast<int>(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<QString, Plasma::Service *>::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<int>(BaseRole::ItemType));
dataItem->setData(true, static_cast<int>(BaseRole::CanRender));
}
dataItem->setData(data.value("Title"), Qt::DisplayRole);
QVariant icon = data.value("Icon");
if (icon.isValid() && icon.canConvert<QIcon>() && !icon.value<QIcon>().isNull()) {
dataItem->setData(icon, Qt::DecorationRole);
} else {
dataItem->setData(data.value("IconName"), Qt::DecorationRole);
}
dataItem->setData(data.value("Id"), static_cast<int>(BaseRole::ItemId));
dataItem->setData(sourceName, static_cast<int>(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<int>(role);
dataItem->setData(data.value(roleNames().value(roleId)), roleId);
}
SystemTrayModel::SystemTrayModel(QObject *parent) : KConcatenateRowsProxyModel(parent)
{
m_roleNames = KConcatenateRowsProxyModel::roleNames();
}
QHash<int, QByteArray> SystemTrayModel::roleNames() const
{
return m_roleNames;
}
void SystemTrayModel::addSourceModel(QAbstractItemModel *sourceModel)
{
m_roleNames.unite(sourceModel->roleNames());
KConcatenateRowsProxyModel::addSourceModel(sourceModel);
}
/***************************************************************************
* Copyright (C) 2019 Konrad Materka <materka@gmail.com> *
* *
* 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 <QStandardItemModel>
#include <KItemModels/KConcatenateRowsProxyModel>
#include <Plasma/DataEngineConsumer>
#include <Plasma/DataEngine>
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<int>(BaseRole::LastBaseRole) + 1,
HasApplet
};
explicit PlasmoidModel(QObject *parent = nullptr);
QHash<int, QByteArray> 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<int>(BaseRole::LastBaseRole) + 100,
AttentionIcon,
AttentionIconName,
AttentionMovieName,
Category,
Icon,
IconName,
IconThemePath,
IconsChanged,
Id,
ItemIsMenu,
OverlayIconName,
Status,
StatusChanged,
Title,
TitleChanged,
ToolTipChanged,
ToolTipIcon,
ToolTipSubTitle,
ToolTipTitle,