diff --git a/applets/systemtray/CMakeLists.txt b/applets/systemtray/CMakeLists.txt --- a/applets/systemtray/CMakeLists.txt +++ b/applets/systemtray/CMakeLists.txt @@ -4,6 +4,7 @@ set(systemtray_SRCS systemtray.cpp + entrymodel.cpp ) ecm_qt_declare_logging_category(systemtray_SRCS HEADER debug.h diff --git a/applets/systemtray/entrymodel.h b/applets/systemtray/entrymodel.h new file mode 100644 --- /dev/null +++ b/applets/systemtray/entrymodel.h @@ -0,0 +1,66 @@ +/** + * Copyright 2019 Nicolas Fella + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#ifndef ENTRYMODEL_H +#define ENTRYMODEL_H + +#include +#include + +#include "systemtray.h" +struct Entry { + QString name; + QString iconName; + QString taskId; + QVariant icon; + bool isPlasmoid; + Plasma::Applet *applet; +}; + +class EntryModel + : public QAbstractListModel +{ + Q_OBJECT + +public: + enum ModelRoles { + AppletRole = Qt::UserRole + 1, + TaskIdRole, + NameRole, + IconNameRole, + ShortcutRole, + IconRole, + IsPlasmoidRole + }; + + explicit EntryModel(SystemTray *systemTray); + ~EntryModel() override; + + QVariant data(const QModelIndex& index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QHash roleNames() const override; + +private: + QVector m_entries; +}; + +#endif // ENTRYMODEL_H diff --git a/applets/systemtray/entrymodel.cpp b/applets/systemtray/entrymodel.cpp new file mode 100644 --- /dev/null +++ b/applets/systemtray/entrymodel.cpp @@ -0,0 +1,133 @@ +/** + * Copyright 2019 Nicolas Fella + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#include "entrymodel.h" + +#include +#include +#include +#include +#include + +EntryModel::EntryModel(SystemTray *systemTray) + : QAbstractListModel(systemTray) + , m_entries() +{ + // load plasmoids + for (const auto &info: Plasma::PluginLoader::self()->listAppletMetaData(QString())) { + if (!info.isValid() || info.value(QStringLiteral("X-Plasma-NotificationArea")) != "true") { + continue; + } + Entry e; + e.name = info.name(); + e.iconName = info.iconName(); + e.taskId = info.pluginId(); + e.isPlasmoid = true; + + const auto applets = systemTray->applets(); + for (Plasma::Applet *applet : applets) { + if (applet->pluginInfo().name() == info.name()) { + e.applet = applet; + } + } + m_entries.append(e); + } + + // Load statusnotifieritems + Plasma::DataEngineConsumer consumer; + Plasma::DataEngine *eng = consumer.dataEngine(QStringLiteral("statusnotifieritem")); + + const auto sources = eng->sources(); + for (const QString source : sources) { + Plasma::DataContainer *container = eng->containerForSource(source); + Entry e; + e.name = container->data().value(QStringLiteral("Title")).toString(); + e.taskId = container->data().value(QStringLiteral("Id")).toString(); + e.iconName = container->data().value(QStringLiteral("IconName")).toString(); + e.icon = container->data().value(QStringLiteral("Icon")); + e.isPlasmoid = false; + m_entries.append(e); + } +} + +QHash EntryModel::roleNames() const +{ + //Role names for QML + QHash names = QAbstractItemModel::roleNames(); + names.insert(AppletRole, "applet"); + names.insert(TaskIdRole, "taskId"); + names.insert(NameRole, "name"); + names.insert(IconNameRole, "iconName"); + names.insert(ShortcutRole, "shortcut"); + names.insert(IconRole, "icon"); + names.insert(IsPlasmoidRole, "isPlasmoid"); + + return names; +} + +EntryModel::~EntryModel() +{ +} + +QVariant EntryModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() + || index.row() < 0 + || index.row() >= rowCount()) + { + return QVariant(); + } + + switch (role) { + case NameRole: + return m_entries[index.row()].name; + case TaskIdRole: + return m_entries[index.row()].taskId; + case IconNameRole: + return m_entries[index.row()].iconName; + case IconRole: + return m_entries[index.row()].icon; + case IsPlasmoidRole: + return m_entries[index.row()].isPlasmoid; + case ShortcutRole: + return m_entries[index.row()].applet->globalShortcut(); + default: + return QVariant(); + } +} + +int EntryModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + //Return size 0 if we are a child because this is not a tree + return 0; + } + + return m_entries.size(); +} + +bool EntryModel::setData(const QModelIndex &index, const QVariant &value, int role) { + + if (role == ShortcutRole) { + m_entries[index.row()].applet->setGlobalShortcut(value.value()); + } + + return true; +} diff --git a/applets/systemtray/package/contents/config/config.qml b/applets/systemtray/package/contents/config/config.qml --- a/applets/systemtray/package/contents/config/config.qml +++ b/applets/systemtray/package/contents/config/config.qml @@ -27,9 +27,4 @@ icon: "preferences-system-windows" source: "ConfigGeneral.qml" } - ConfigCategory { - name: i18n("Entries") - icon: "preferences-desktop-notification" - source: "ConfigEntries.qml" - } } diff --git a/applets/systemtray/package/contents/ui/ConfigEntries.qml b/applets/systemtray/package/contents/ui/ConfigEntries.qml deleted file mode 100644 --- a/applets/systemtray/package/contents/ui/ConfigEntries.qml +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright 2013 Sebastian Kügler - * Copyright 2014 Marco Martin - * Copyright 2019 ivan tkachenko - * - * 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 1.4 as QQC1 -import QtQuick.Controls 2.5 as QQC2 -import QtQuick.Layouts 1.3 - -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents -import org.kde.plasma.extras 2.0 as PlasmaExtras -import org.kde.kquickcontrolsaddons 2.0 -import org.kde.kquickcontrols 2.0 as KQC -import org.kde.kirigami 2.5 as Kirigami - -ColumnLayout { - id: iconsPage - - signal configurationChanged - - property var cfg_shownItems: [] - 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 - } - } - - StatusNotifierItemModel { - id: statusNotifierModel - } - - - Kirigami.FormLayout { - - QQC2.CheckBox { - id: showAllCheckBox - text: i18n("Always show all entries") - } - - QQC2.Button { // just for measurement - id: measureButton - text: "measureButton" - visible: false - } - - // resizeToContents does not take into account the heading - QQC2.Label { - id: shortcutColumnMeasureLabel - text: shortcutColumn.title - visible: false - } - } - - - 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] - list.push({ - "index": (i + lastIndex), - "applet": item, - "taskId": item.pluginName, - "name": item.title, - "iconName": item.icon, - "shortcut": item.globalShortcut - }); - } - list.sort(function(a, b) { - return a.name.localeCompare(b.name); - }); - return list; - } - - // There is no QQC2 version of TableView yet - QQC1.TableView { - id: tableView - Layout.fillWidth: true - Layout.fillHeight: true - - model: retrieveAllItems() - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - flickableItem.boundsBehavior: Flickable.StopAtBounds - - Component.onCompleted: { - visibilityColumn.resizeToContents() - shortcutColumn.resizeToContents() - } - - // Taken from QtQuickControls BasicTableViewStyle, just to make its height sensible... - rowDelegate: BorderImage { - visible: styleData.selected || styleData.alternate - source: "image://__tablerow/" + (styleData.alternate ? "alternate_" : "") - + (tableView.activeFocus ? "active" : "") - height: measureButton.height - border.left: 4 ; border.right: 4 - } - - QQC1.TableViewColumn { - id: entryColumn - width: tableView.viewport.width - visibilityColumn.width - shortcutColumn.width - title: i18nc("Name of the system tray entry", "Entry") - movable: false - resizable: false - - delegate: RowLayout { - Item { // spacer - Layout.preferredWidth: 1 - Layout.fillHeight: true - } - - QIconItem { - width: units.iconSizes.small - height: width - icon: modelData.iconName || modelData.icon || "" - } - - QQC2.Label { - Layout.fillWidth: true - text: modelData.name - elide: Text.ElideRight - wrapMode: Text.NoWrap - } - } - } - - QQC1.TableViewColumn { - id: visibilityColumn - title: i18n("Visibility") - movable: false - resizable: false - - delegate: QQC2.ComboBox { - implicitWidth: Math.round(units.gridUnit * 6.5) // ComboBox sizing is broken - - enabled: !showAllCheckBox.checked - currentIndex: { - if (cfg_shownItems.indexOf(modelData.taskId) != -1) { - return 1; - } else if (cfg_hiddenItems.indexOf(modelData.taskId) != -1) { - return 2; - } else { - return 0; - } - } - - // activated, in contrast to currentIndexChanged, only fires if the user himself changed the value - onActivated: { - var shownIndex = cfg_shownItems.indexOf(modelData.taskId); - var hiddenIndex = cfg_hiddenItems.indexOf(modelData.taskId); - - switch (index) { - case 0: { - if (shownIndex > -1) { - cfg_shownItems.splice(shownIndex, 1); - } - if (hiddenIndex > -1) { - cfg_hiddenItems.splice(hiddenIndex, 1); - } - break; - } - case 1: { - if (shownIndex === -1) { - cfg_shownItems.push(modelData.taskId); - } - if (hiddenIndex > -1) { - cfg_hiddenItems.splice(hiddenIndex, 1); - } - break; - } - case 2: { - if (shownIndex > -1) { - cfg_shownItems.splice(shownIndex, 1); - } - if (hiddenIndex === -1) { - cfg_hiddenItems.push(modelData.taskId); - } - break; - } - } - iconsPage.configurationChanged(); - } - model: [i18n("Auto"), i18n("Shown"), i18n("Hidden")] - } - } - - QQC1.TableViewColumn { - id: shortcutColumn - title: i18n("Keyboard Shortcut") // FIXME doesn't fit - movable: false - resizable: false - - // this Item wrapper prevents TableView from ripping apart the two KeySequenceItem buttons - delegate: Item { - implicitWidth: Math.max(shortcutColumnMeasureLabel.width, keySequenceItem.width) + 10 - height: keySequenceItem.height - - KQC.KeySequenceItem { - id: keySequenceItem - anchors.right: parent.right - - keySequence: modelData.shortcut - // only Plasmoids have that - 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 - - iconsPage.configurationChanged() - } - - shortcutColumn.resizeToContents() - } - } - } - } - } -} diff --git a/applets/systemtray/package/contents/ui/ConfigGeneral.qml b/applets/systemtray/package/contents/ui/ConfigGeneral.qml --- a/applets/systemtray/package/contents/ui/ConfigGeneral.qml +++ b/applets/systemtray/package/contents/ui/ConfigGeneral.qml @@ -1,6 +1,8 @@ /* * Copyright 2013 Sebastian Kügler * Copyright 2014 Marco Martin + * Copyright 2019 ivan tkachenko + * Copyright 2019 Nicolas Fella * * 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 @@ -17,108 +19,164 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ -import QtQuick 2.0 -import QtQuick.Controls 2.3 as QtControls -import QtQuick.Layouts 1.0 as QtLayouts +import QtQuick 2.5 +import QtQuick.Controls 1.4 as QQC1 +import QtQuick.Controls 2.5 as QQC2 +import QtQuick.Layouts 1.3 -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kquickcontrolsaddons 2.0 +import org.kde.kquickcontrols 2.0 as KQC import org.kde.kirigami 2.5 as Kirigami -Item { - id: iconsPage +ColumnLayout { - signal configurationChanged + id: root - width: childrenRect.width - height: childrenRect.height - implicitWidth: mainColumn.implicitWidth - implicitHeight: pageColumn.implicitHeight + signal configurationChanged - property alias cfg_applicationStatusShown: applicationStatus.checked - property alias cfg_communicationsShown: communications.checked - property alias cfg_systemServicesShown: systemServices.checked - property alias cfg_hardwareControlShown: hardwareControl.checked - property alias cfg_miscellaneousShown: miscellaneous.checked + property var cfg_shownItems: [] + property var cfg_hiddenItems: [] property var cfg_extraItems: [] + property alias cfg_showAllItems: showAllCheckBox.checked + + QQC2.ComboBox { + id: comboboxMeasure + visible: false + } + + KQC.KeySequenceItem { + id: keyMeasure + visible: false + } + + QQC2.CheckBox { + id: showAllCheckBox + text: i18n("Always show all entries") + } + + QQC2.ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true - QtControls.CheckBox { - id: dummyCheckbox - visible: false + ListView { + model: plasmoid.nativeInterface.entryModel + + Layout.fillWidth: true + Layout.fillHeight: true + + header: RowLayout { + width: parent.width + Kirigami.Heading { + text: i18n("Entry") + Layout.fillWidth: true + } + Kirigami.Heading { + text: i18n("Visibility") + Layout.preferredWidth: comboboxMeasure.width + } + Kirigami.Heading { + text: i18n("Shortcut") + Layout.preferredWidth: keyMeasure.width + } } - Kirigami.FormLayout { - id: pageColumn - anchors { - left: parent.left - right: parent.right - } + delegate: RowLayout { + width: parent.width - Item { - Kirigami.FormData.isSection: true - Kirigami.FormData.label: i18n("Categories") - } + QIconItem { + id: theIcon + width: units.iconSizes.small + height: width + icon: model.iconName || model.icon + } + QQC2.Label { + text: name + Layout.fillWidth: true + elide: Text.ElideRight + wrapMode: Text.NoWrap + } - QtControls.CheckBox { - id: applicationStatus - text: i18n("Application Status") - } - QtControls.CheckBox { - id: communications - text: i18n("Communications") - } - QtControls.CheckBox { - id: systemServices - text: i18n("System Services") - } - QtControls.CheckBox { - id: hardwareControl - text: i18n("Hardware Control") - } - QtControls.CheckBox { - id: miscellaneous - text: i18n("Miscellaneous") - } + QQC2.ComboBox { + enabled: !showAllCheckBox.checked + model: isPlasmoid ? [i18n("Auto"), i18n("Shown"), i18n("Hidden"), i18n("Disabled")] : [i18n("Auto"), i18n("Shown"), i18n("Hidden")] + + currentIndex: { + if (cfg_shownItems.indexOf(taskId) != -1) { + return 1; + } else if (cfg_hiddenItems.indexOf(taskId) != -1) { + return 2; + } else { + return 0; + } + } + onActivated: { + var shownIndex = cfg_shownItems.indexOf(taskId); + var hiddenIndex = cfg_hiddenItems.indexOf(taskId); + var extraIndex = cfg_extraItems.indexOf(taskId); - Item { - Kirigami.FormData.isSection: true - Kirigami.FormData.label: i18n("Extra Items") - } + switch (index) { + case 0: { + if (shownIndex > -1) { + cfg_shownItems.splice(shownIndex, 1); + } + if (hiddenIndex > -1) { + cfg_hiddenItems.splice(hiddenIndex, 1); + } - Repeater { - model: plasmoid.nativeInterface.availablePlasmoids - delegate: QtControls.CheckBox { - QtLayouts.Layout.minimumWidth: childrenRect.width - checked: cfg_extraItems.indexOf(plugin) != -1 - implicitWidth: itemLayout.width + itemLayout.x - onCheckedChanged: { - var index = cfg_extraItems.indexOf(plugin); - if (checked) { - if (index === -1) { - cfg_extraItems.push(plugin); + if (extraIndex === -1) { + cfg_extraItems.push(taskId) + } + + break; + } + case 1: { + if (shownIndex === -1) { + cfg_shownItems.push(taskId); + } + if (hiddenIndex > -1) { + cfg_hiddenItems.splice(hiddenIndex, 1); + } + if (extraIndex === -1) { + cfg_extraItems.push(taskId) + } + break; } - } else { - if (index > -1) { - cfg_extraItems.splice(index, 1); + case 2: { + if (shownIndex > -1) { + cfg_shownItems.splice(shownIndex, 1); + } + if (hiddenIndex === -1) { + cfg_hiddenItems.push(taskId); + } + if (extraIndex === -1) { + cfg_extraItems.push(taskId) + } + break; } + case 3: { + if (extraIndex > -1) { + cfg_extraItems.splice(extraIndex, 1); + } + break; + } + } + root.configurationChanged(); } - configurationChanged() // qml cannot detect changes inside an Array } - QtLayouts.RowLayout { - id: itemLayout - anchors.verticalCenter: parent.verticalCenter - x: dummyCheckbox.width - QIconItem { - icon: model.decoration - width: units.iconSizes.small - height: width - } - QtControls.Label { - text: model.display + KQC.KeySequenceItem { + id: keySequenceItem + visible: isPlasmoid + keySequence: shortcut + onKeySequenceChanged: { + shortcut = keySequence } } + // Placeholder for when KeySequenceItem is not visible + Item { + Layout.preferredWidth: keyMeasure.width + visible: !keySequenceItem.visible + } } } } diff --git a/applets/systemtray/systemtray.h b/applets/systemtray/systemtray.h --- a/applets/systemtray/systemtray.h +++ b/applets/systemtray/systemtray.h @@ -30,13 +30,14 @@ class QDBusConnection; class QQuickItem; class PlasmoidModel; +class EntryModel; class SystemTray : public Plasma::Containment { Q_OBJECT - Q_PROPERTY(QAbstractItemModel* availablePlasmoids READ availablePlasmoids CONSTANT) Q_PROPERTY(QStringList allowedPlasmoids READ allowedPlasmoids WRITE setAllowedPlasmoids NOTIFY allowedPlasmoidsChanged) Q_PROPERTY(QStringList defaultPlasmoids READ defaultPlasmoids CONSTANT) + Q_PROPERTY(QAbstractListModel* entryModel READ entryModel) public: SystemTray( QObject *parent, const QVariantList &args ); @@ -49,7 +50,7 @@ QStringList defaultPlasmoids() const; - QAbstractItemModel* availablePlasmoids(); + QAbstractListModel* entryModel(); QStringList allowedPlasmoids() const; void setAllowedPlasmoids(const QStringList &allowed); @@ -119,7 +120,7 @@ QHash m_dbusActivatableTasks; QStringList m_allowedPlasmoids; - PlasmoidModel *m_availablePlasmoidsModel; + EntryModel *m_entryModel; 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 @@ -42,24 +42,11 @@ #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; - } -}; +#include "entrymodel.h" SystemTray::SystemTray(QObject *parent, const QVariantList &args) : Plasma::Containment(parent, args), - m_availablePlasmoidsModel(nullptr) + m_entryModel(nullptr) { setHasConfigurationInterface(true); setContainmentType(Plasma::Types::CustomEmbeddedContainment); @@ -442,27 +429,15 @@ return m_defaultPlasmoids; } -QAbstractItemModel* SystemTray::availablePlasmoids() +QAbstractListModel * SystemTray::entryModel() { - if (!m_availablePlasmoidsModel) { - m_availablePlasmoidsModel = new PlasmoidModel(this); - - for (const KPluginInfo &info : qAsConst(m_systrayApplets)) { - QString name = info.name(); - const QString dbusactivation = info.property(QStringLiteral("X-Plasma-DBusActivationService")).toString(); - - if (!dbusactivation.isEmpty()) { - name += i18n(" (Automatic load)"); - } - QStandardItem *item = new QStandardItem(QIcon::fromTheme(info.icon()), name); - item->setData(info.pluginName()); - m_availablePlasmoidsModel->appendRow(item); - } - m_availablePlasmoidsModel->sort(0 /*column*/); + if (!m_entryModel) { + m_entryModel = new EntryModel(this); } - return m_availablePlasmoidsModel; + return m_entryModel; } + QStringList SystemTray::allowedPlasmoids() const { return m_allowedPlasmoids;