diff --git a/applets/systemtray/package/contents/ui/ExpanderArrow.qml b/applets/systemtray/package/contents/ui/ExpanderArrow.qml --- a/applets/systemtray/package/contents/ui/ExpanderArrow.qml +++ b/applets/systemtray/package/contents/ui/ExpanderArrow.qml @@ -28,7 +28,7 @@ property bool vertical: plasmoid.formFactor === PlasmaCore.Types.Vertical implicitWidth: units.iconSizes.smallMedium implicitHeight: implicitWidth - visible: root.hiddenLayout.children.length > 0 + visible: root.hiddenLayout.contentItem.children.length > 0 subText: root.expanded ? i18n("Close popup") : i18n("Show hidden icons") diff --git a/applets/systemtray/package/contents/ui/HiddenItemsView.qml b/applets/systemtray/package/contents/ui/HiddenItemsView.qml --- a/applets/systemtray/package/contents/ui/HiddenItemsView.qml +++ b/applets/systemtray/package/contents/ui/HiddenItemsView.qml @@ -1,5 +1,6 @@ /* * Copyright 2016 Marco Martin + * Copyright 2020 Konrad Materka * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as @@ -19,53 +20,62 @@ import QtQuick 2.1 import QtQuick.Layouts 1.1 -import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.core 2.1 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras +import "items" -PlasmaExtras.ScrollArea { +MouseArea { id: hiddenTasksView visible: !root.activeApplet || (root.activeApplet.parent && root.activeApplet.parent.inHiddenLayout) implicitWidth: root.activeApplet ? iconColumnWidth : parent.width property alias layout: hiddenTasksColumn //Useful to align stuff to the column of icons, both in expanded and shrink modes property int iconColumnWidth: root.hiddenItemSize + highlight.marginHints.left + highlight.marginHints.right - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: activeApplet ? Qt.ScrollBarAlwaysOff : Qt.ScrollBarAsNeeded - Flickable { - contentWidth: width - contentHeight: hiddenTasksColumn.height + hoverEnabled: true + onExited: hiddenTasksColumn.currentIndex = -1 - MouseArea { - width: parent.width - height: hiddenTasksColumn.height - drag.filterChildren: true - hoverEnabled: true - onExited: hiddenTasksColumn.hoveredItem = null; + PlasmaExtras.ScrollArea { + width: parent.width + height: parent.height - CurrentItemHighLight { - target: root.activeApplet && root.activeApplet.parent.parent == hiddenTasksColumn ? root.activeApplet.parent : null - location: PlasmaCore.Types.LeftEdge - } - PlasmaComponents.Highlight { - id: highlight - visible: hiddenTasksColumn.hoveredItem != null && !root.activeApplet - y: hiddenTasksColumn.hoveredItem ? hiddenTasksColumn.hoveredItem.y : 0 - width: hiddenTasksColumn.hoveredItem ? hiddenTasksColumn.hoveredItem.width : 0 - height: hiddenTasksColumn.hoveredItem ? hiddenTasksColumn.hoveredItem.height : 0 - } + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: root.activeApplet ? Qt.ScrollBarAlwaysOff : Qt.ScrollBarAsNeeded + + ListView { + id: hiddenTasksColumn + + spacing: units.smallSpacing + + currentIndex: -1 + highlight: PlasmaComponents.Highlight {} + highlightMoveDuration: 0 + highlightResizeDuration: 0 - Column { - id: hiddenTasksColumn + readonly property int iconItemHeight: root.hiddenItemSize + highlight.marginHints.top + highlight.marginHints.bottom - spacing: units.smallSpacing - width: parent.width - property Item hoveredItem - readonly property int iconItemHeight: root.hiddenItemSize + highlight.marginHints.top + highlight.marginHints.bottom + model: PlasmaCore.SortFilterModel { + sourceModel: plasmoid.nativeInterface.systemTrayModel + filterRole: "effectiveStatus" + filterCallback: function(source_row, value) { + return value === PlasmaCore.Types.PassiveStatus + } } + delegate: ItemLoader {} } } + + PlasmaComponents.Highlight { + id: highlight + visible: false + } + + CurrentItemHighLight { + parent: hiddenTasksColumn.contentItem + target: root.activeApplet && root.activeApplet.parent && root.activeApplet.parent.inHiddenLayout ? root.activeApplet.parent.parent : null + location: PlasmaCore.Types.LeftEdge + } } diff --git a/applets/systemtray/package/contents/ui/items/AbstractItem.qml b/applets/systemtray/package/contents/ui/items/AbstractItem.qml --- a/applets/systemtray/package/contents/ui/items/AbstractItem.qml +++ b/applets/systemtray/package/contents/ui/items/AbstractItem.qml @@ -1,5 +1,6 @@ /* * Copyright 2016 Marco Martin + * Copyright 2020 Konrad Materka * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as @@ -24,36 +25,22 @@ PlasmaCore.ToolTipArea { id: abstractItem - height: inVisibleLayout ? visibleLayout.iconSize : hiddenLayout.iconItemHeight - width: inVisibleLayout ? visibleLayout.iconSize : hiddenLayout.width + height: inVisibleLayout ? visibleLayout.cellHeight : hiddenLayout.iconItemHeight + width: inVisibleLayout ? visibleLayout.cellWidth : hiddenLayout.width property string itemId - property string category property alias text: label.text property Item iconItem property int /*PlasmaCore.Types.ItemStatus*/ status + property int /*PlasmaCore.Types.ItemStatus*/ effectiveStatus readonly property bool inHiddenLayout: effectiveStatus === PlasmaCore.Types.PassiveStatus readonly property bool inVisibleLayout: effectiveStatus === PlasmaCore.Types.ActiveStatus - property QtObject model signal clicked(var mouse) signal pressed(var mouse) signal wheel(var wheel) signal contextMenu(var mouse) - property bool forcedHidden: plasmoid.configuration.hiddenItems.indexOf(itemId) !== -1 - property bool forcedShown: plasmoid.configuration.showAllItems || plasmoid.configuration.shownItems.indexOf(itemId) !== -1 - - readonly property int effectiveStatus: { - if (status === PlasmaCore.Types.HiddenStatus) { - return PlasmaCore.Types.HiddenStatus - } else if (forcedShown || (!forcedHidden && status !== PlasmaCore.Types.PassiveStatus)) { - return PlasmaCore.Types.ActiveStatus - } else { - return PlasmaCore.Types.PassiveStatus - } - } - /* subclasses need to assign to this tooltip properties mainText: subText: @@ -74,18 +61,9 @@ //BEGIN CONNECTIONS - property int creationId // used for item order tie breaking - onEffectiveStatusChanged: updateItemVisibility(abstractItem) - onCategoryChanged: updateItemVisibility(abstractItem) - onTextChanged: updateItemVisibility(abstractItem) - Component.onCompleted: { - creationId = root.creationIdCounter++ - updateItemVisibility(abstractItem) - } - onContainsMouseChanged: { if (inHiddenLayout && containsMouse) { - root.hiddenLayout.hoveredItem = abstractItem + root.hiddenLayout.currentIndex = index } } diff --git a/applets/systemtray/package/contents/ui/items/ItemLoader.qml b/applets/systemtray/package/contents/ui/items/ItemLoader.qml new file mode 100644 --- /dev/null +++ b/applets/systemtray/package/contents/ui/items/ItemLoader.qml @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Konrad Materka + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details + * + * You should have received a copy of the GNU Library 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. + */ + +import QtQuick 2.0 + +Loader { + id: itemLoader + + Component.onCompleted: { + if (model.itemType === "Plasmoid" && model.hasApplet) { + itemLoader.setSource("PlasmoidItem.qml", { + "applet": model.applet, + "effectiveStatus": model.effectiveStatus + }) + } else if (model.itemType === "StatusNotifier") { + itemLoader.setSource("StatusNotifierItem.qml", { + "model": model, + "effectiveStatus": model.effectiveStatus + }) + } + } +} diff --git a/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml b/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml --- a/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml +++ b/applets/systemtray/package/contents/ui/items/PlasmoidItem.qml @@ -28,7 +28,6 @@ text: applet ? applet.title : "" itemId: applet ? applet.pluginName : "" - category: applet ? plasmoid.nativeInterface.plasmoidCategory(applet) : "UnknownCategory" mainText: applet ? applet.toolTipMainText : "" subText: applet ? applet.toolTipSubText : "" icon: applet ? applet.icon : "" @@ -80,9 +79,6 @@ preloadFullRepresentationItem(applet.fullRepresentationItem) } - if (!applet) { - plasmoidContainer.destroy(); - } } Connections { diff --git a/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml b/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml --- a/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml +++ b/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml @@ -23,40 +23,31 @@ AbstractItem { id: taskIcon - itemId: Id - text: Title - mainText: ToolTipTitle != "" ? ToolTipTitle : Title - subText: ToolTipSubTitle - icon: ToolTipIcon != "" ? ToolTipIcon : Icon ? Icon : IconName + property var model + + itemId: model.Id + text: model.Title + mainText: model.ToolTipTitle !== "" ? model.ToolTipTitle : model.Title + subText: model.ToolTipSubTitle + icon: model.ToolTipIcon !== "" ? model.ToolTipIcon : model.Icon ? model.Icon : model.IconName textFormat: Text.AutoText - category: Category - - status: { - switch (Status) { - case "Active": - return PlasmaCore.Types.ActiveStatus; - case "NeedsAttention": - return PlasmaCore.Types.NeedsAttentionStatus; - //just assume passive - default: - return PlasmaCore.Types.PassiveStatus; - } - } + + status: model.status iconItem: iconItem PlasmaCore.IconItem { id: iconItem source: { - if (taskIcon.status === PlasmaCore.Types.NeedsAttentionStatus) { - if (AttentionIcon) { - return AttentionIcon + if (model.status === PlasmaCore.Types.NeedsAttentionStatus) { + if (model.AttentionIcon) { + return model.AttentionIcon } - if (AttentionIconName) { - return AttentionIconName + if (model.AttentionIconName) { + return model.AttentionIconName } } - return Icon ? Icon : IconName + return model.Icon ? model.Icon : model.IconName } width: Math.min(parent.width, parent.height) @@ -78,7 +69,7 @@ switch (mouse.button) { case Qt.LeftButton: - var service = statusNotifierSource.serviceForSource(DataEngineSource); + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("Activate"); operation.x = pos.x; operation.y = pos.y; @@ -97,7 +88,7 @@ break; case Qt.MiddleButton: - var service = statusNotifierSource.serviceForSource(DataEngineSource); + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("SecondaryActivate"); operation.x = pos.x; @@ -109,7 +100,7 @@ } function openContextMenu(pos) { - var service = statusNotifierSource.serviceForSource(DataEngineSource); + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("ContextMenu"); operation.x = pos.x; operation.y = pos.y; @@ -123,14 +114,14 @@ onWheel: { //don't send activateVertScroll with a delta of 0, some clients seem to break (kmix) if (wheel.angleDelta.y !== 0) { - var service = statusNotifierSource.serviceForSource(DataEngineSource); + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("Scroll"); operation.delta =wheel.angleDelta.y; operation.direction = "Vertical"; service.startOperationCall(operation); } if (wheel.angleDelta.x !== 0) { - var service = statusNotifierSource.serviceForSource(DataEngineSource); + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("Scroll"); operation.delta =wheel.angleDelta.x; operation.direction = "Horizontal"; diff --git a/applets/systemtray/package/contents/ui/main.qml b/applets/systemtray/package/contents/ui/main.qml --- a/applets/systemtray/package/contents/ui/main.qml +++ b/applets/systemtray/package/contents/ui/main.qml @@ -1,5 +1,6 @@ /* * Copyright 2011 Marco Martin + * Copyright 2020 Konrad Materka * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as @@ -19,7 +20,7 @@ import QtQuick 2.5 import QtQuick.Layouts 1.1 -import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.core 2.1 as PlasmaCore import org.kde.plasma.plasmoid 2.0 import org.kde.draganddrop 2.0 as DnD import org.kde.kirigami 2.5 as Kirigami @@ -29,9 +30,9 @@ MouseArea { id: root - Layout.minimumWidth: vertical ? units.iconSizes.small : tasksRow.implicitWidth + (expander.visible ? expander.implicitWidth : 0) + units.smallSpacing + Layout.minimumWidth: vertical ? units.iconSizes.small : tasksGrid.implicitWidth + (expander.visible ? expander.implicitWidth : 0) + units.smallSpacing - Layout.minimumHeight: vertical ? tasksRow.implicitHeight + (expander.visible ? expander.implicitHeight : 0) + units.smallSpacing : units.smallSpacing + Layout.minimumHeight: vertical ? tasksGrid.implicitHeight + (expander.visible ? expander.implicitHeight : 0) + units.smallSpacing : units.smallSpacing Layout.preferredHeight: Layout.minimumHeight LayoutMirroring.enabled: !vertical && Qt.application.layoutDirection === Qt.RightToLeft @@ -47,107 +48,20 @@ property Item activeApplet property int status: dialog.visible ? PlasmaCore.Types.RequiresAttentionStatus : PlasmaCore.Types.PassiveStatus - property alias visibleLayout: tasksRow + property alias visibleLayout: tasksGrid property alias hiddenLayout: expandedRepresentation.hiddenLayout - property alias statusNotifierModel: statusNotifierModel - - // workaround https://bugreports.qt.io/browse/QTBUG-71238 / https://bugreports.qt.io/browse/QTBUG-72004 - property Component plasmoidItemComponent: Qt.createComponent("items/PlasmoidItem.qml") - - property int creationIdCounter: 0 - Plasmoid.onExpandedChanged: { if (!plasmoid.expanded) { dialog.visible = plasmoid.expanded; } } - // temporary hack to fix known broken categories - // should go away as soon as fixes are merged - readonly property var categoryOverride: { - "org.kde.discovernotifier": "SystemServices", - "org.kde.plasma.networkmanagement": "Hardware", - "org.kde.kdeconnect": "Hardware", - "org.kde.plasma.keyboardindicator": "Hardware", - "touchpad": "Hardware" - } - - readonly property var categoryOrder: [ - "UnknownCategory", "ApplicationStatus", "Communications", - "SystemServices", "Hardware" - ] - function indexForItemCategory(item) { - if (item.itemId == "org.kde.plasma.notifications") { - return -1 - } - var i = categoryOrder.indexOf(categoryOverride[item.itemId] || item.category) - return i == -1 ? categoryOrder.indexOf("UnknownCategory") : i - } - - // return negative integer if a < b, 0 if a === b, and positive otherwise - function compareItems(a, b) { - var categoryDiff = indexForItemCategory(a) - indexForItemCategory(b) - var textDiff = (categoryDiff != 0 ? categoryDiff : a.text.localeCompare(b.text)) - return textDiff != 0 ? textDiff : b.creationId - a.creationId - } - - function moveItemAt(item, container, index) { - if (container.children.length == 0) { - item.parent = container - } else { - if (index == container.children.length) { - var other = container.children[index - 1] - if (item != other) { - plasmoid.nativeInterface.reorderItemAfter(item, other) - } - } else { - var other = container.children[index] - if (item != other) { - plasmoid.nativeInterface.reorderItemBefore(item, other) - } - } - } - } - - function reorderItem(item, container) { - var i = 0; - while (i < container.children.length && - compareItems(container.children[i], item) <= 0) { - i++ - } - moveItemAt(item, container, i) - } - - function updateItemVisibility(item) { - switch (item.effectiveStatus) { - case PlasmaCore.Types.HiddenStatus: - if (item.parent != invisibleEntriesContainer) { - item.parent = invisibleEntriesContainer; - } - break; - - case PlasmaCore.Types.ActiveStatus: - reorderItem(item, visibleLayout) - break; - - case PlasmaCore.Types.PassiveStatus: - reorderItem(item, hiddenLayout) - item.x = 0; - break; - } - } - onWheel: { // Don't propagate unhandled wheel events wheel.accepted = true; } - Containment.onAppletAdded: { - //Allow the plasmoid expander to know in what window it will be - var plasmoidContainer = plasmoidItemComponent.createObject(invisibleEntriesContainer, {"x": x, "y": y, "applet": applet}); - } - //being there forces the items to fully load, and they will be reparented in the popup one by one, this item is *never* visible Item { id: preloadedStorage @@ -169,78 +83,9 @@ onExtraItemsChanged: plasmoid.nativeInterface.allowedPlasmoids = plasmoid.configuration.extraItems } - Component.onCompleted: { - //script, don't bind - plasmoid.nativeInterface.allowedPlasmoids = initializePlasmoidList(); - } - - function initializePlasmoidList() { - var newKnownItems = []; - var newExtraItems = []; - - //NOTE:why this? otherwise the interpreter will execute plasmoid.nativeInterface.defaultPlasmoids() on - //every access of defaults[], resulting in a very slow iteration - var defaults = []; - //defaults = defaults.concat(plasmoid.nativeInterface.defaultPlasmoids); - defaults = plasmoid.nativeInterface.defaultPlasmoids.slice() - var candidate; - - //Add every plasmoid that is both not enabled explicitly and not already known - for (var i = 0; i < defaults.length; ++i) { - candidate = defaults[i]; - if (plasmoid.configuration.knownItems.indexOf(candidate) === -1) { - newKnownItems.push(candidate); - if (plasmoid.configuration.extraItems.indexOf(candidate) === -1) { - newExtraItems.push(candidate); - } - } - } - - if (newExtraItems.length > 0) { - plasmoid.configuration.extraItems = plasmoid.configuration.extraItems.slice().concat(newExtraItems); - } - if (newKnownItems.length > 0) { - plasmoid.configuration.knownItems = plasmoid.configuration.knownItems.slice().concat(newKnownItems); - } - - return plasmoid.configuration.extraItems; - } - - PlasmaCore.DataSource { - id: statusNotifierSource - engine: "statusnotifieritem" - interval: 0 - onSourceAdded: { - connectSource(source) - } - Component.onCompleted: { - connectedSources = sources - } - } - - PlasmaCore.SortFilterModel { - id: statusNotifierModel - sourceModel: PlasmaCore.DataModel { - dataSource: statusNotifierSource - } - } - - //This is a dump for items we don't want to be seen or as an incubation, when they are - //created as a nursery before going in their final place - Item { - id: invisibleEntriesContainer - visible: false - Repeater { - id: tasksRepeater - model: statusNotifierModel - - delegate: StatusNotifierItem {} - } - } - CurrentItemHighLight { - visualParent: tasksRow - target: root.activeApplet && root.activeApplet.parent && root.activeApplet.parent.inVisibleLayout ? root.activeApplet.parent : root + visualParent: tasksGrid + target: root.activeApplet && root.activeApplet.parent && root.activeApplet.parent.inVisibleLayout ? root.activeApplet.parent.parent : root location: plasmoid.location } @@ -285,44 +130,79 @@ } //Main Layout - Flow { - id: tasksRow - spacing: 0 - height: parent.height - (vertical && expander.visible ? expander.height : 0) - width: parent.width - (vertical || !expander.visible ? 0 : expander.width) - property string skipItems - flow: vertical ? Flow.LeftToRight : Flow.TopToBottom - //To make it look centered - y: Math.round(height/2 - childrenRect.height/2) - x: (expander.visible && LayoutMirroring.enabled ? expander.width : 0) + Math.round(width/2 - childrenRect.width/2) - - readonly property var iconSize: root.itemSize + units.smallSpacing - - //add doesn't seem to work used in conjunction with stackBefore/stackAfter - /*add: Transition { - NumberAnimation { - property: "scale" - from: 0 - to: 1 - easing.type: Easing.InQuad - duration: units.longDuration + GridLayout { + id: mainLayout + + rowSpacing: 0 + columnSpacing: 0 + anchors.fill: parent + + flow: vertical ? GridLayout.TopToBottom : GridLayout.LeftToRight + + GridView { + id: tasksGrid + Layout.alignment: Qt.AlignCenter + + interactive: false //disable features we don't need + flow: vertical ? GridView.LeftToRight : GridView.TopToBottom + + cellHeight: root.itemSize + units.smallSpacing + cellWidth: root.itemSize + units.smallSpacing + + readonly property int columns: !vertical ? Math.ceil(count / rows) + : Math.max(1, Math.floor(root.width / cellWidth)) + readonly property int rows: vertical ? Math.ceil(count / columns) + : Math.max(1, Math.floor(root.height / cellHeight)) + + implicitHeight: rows * cellHeight + implicitWidth: columns * cellWidth + + model: PlasmaCore.SortFilterModel { + sourceModel: plasmoid.nativeInterface.systemTrayModel + filterRole: "effectiveStatus" + filterCallback: function(source_row, value) { + return value === PlasmaCore.Types.ActiveStatus + } } - } - move: Transition { - NumberAnimation { - properties: "x,y" - easing.type: Easing.InQuad - duration: units.longDuration + + delegate: ItemLoader {} + + add: Transition { + enabled: root.itemSize > 0 + + NumberAnimation { + property: "scale" + from: 0 + to: 1 + easing.type: Easing.InOutQuad + duration: units.longDuration + } } - }*/ - } - ExpanderArrow { - id: expander - anchors { - fill: parent - leftMargin: vertical ? 0 : parent.width - implicitWidth - topMargin: vertical ? parent.height - implicitHeight : 0 + displaced: Transition { + //ensure scale value returns to 1.0 + //https://doc.qt.io/qt-5/qml-qtquick-viewtransition.html#handling-interrupted-animations + NumberAnimation { + property: "scale" + to: 1 + easing.type: Easing.InOutQuad + duration: units.longDuration + } + } + + move: Transition { + NumberAnimation { + properties: "x,y" + easing.type: Easing.InOutQuad + duration: units.longDuration + } + } + } + + ExpanderArrow { + id: expander + Layout.fillWidth: vertical + Layout.fillHeight: !vertical } } diff --git a/applets/systemtray/sortedsystemtraymodel.h b/applets/systemtray/sortedsystemtraymodel.h --- a/applets/systemtray/sortedsystemtraymodel.h +++ b/applets/systemtray/sortedsystemtraymodel.h @@ -26,7 +26,8 @@ Q_OBJECT public: enum class SortingType { - ConfigurationPage + ConfigurationPage, + SystemTray }; explicit SortedSystemTrayModel(SortingType sorting, QObject *parent = nullptr); @@ -36,8 +37,10 @@ private: bool lessThanConfigurationPage(const QModelIndex &left, const QModelIndex &right) const; + bool lessThanSystemTray(const QModelIndex &left, const QModelIndex &right) const; int compareCategoriesAlphabetically(const QModelIndex &left, const QModelIndex &right) const; + int compareCategoriesOrderly(const QModelIndex &left, const QModelIndex &right) const; SortingType m_sorting; }; diff --git a/applets/systemtray/sortedsystemtraymodel.cpp b/applets/systemtray/sortedsystemtraymodel.cpp --- a/applets/systemtray/sortedsystemtraymodel.cpp +++ b/applets/systemtray/sortedsystemtraymodel.cpp @@ -23,6 +23,12 @@ #include +static const QList s_categoryOrder = {QStringLiteral("UnknownCategory"), + QStringLiteral("ApplicationStatus"), + QStringLiteral("Communications"), + QStringLiteral("SystemServices"), + QStringLiteral("Hardware")}; + SortedSystemTrayModel::SortedSystemTrayModel(SortingType sorting, QObject *parent) : QSortFilterProxyModel(parent), m_sorting(sorting) @@ -36,6 +42,8 @@ switch (m_sorting) { case SortedSystemTrayModel::SortingType::ConfigurationPage: return lessThanConfigurationPage(left, right); + case SortedSystemTrayModel::SortingType::SystemTray: + return lessThanSystemTray(left, right); } return QSortFilterProxyModel::lessThan(left, right); @@ -51,6 +59,21 @@ } } +bool SortedSystemTrayModel::lessThanSystemTray(const QModelIndex &left, const QModelIndex &right) const +{ + QVariant itemId = sourceModel()->data(left, static_cast(BaseModel::BaseRole::ItemId)); + if (itemId.isValid() && itemId.toString() == QLatin1String("org.kde.plasma.notifications")) { + return true; + } + + const int categoriesComparison = compareCategoriesOrderly(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)); @@ -61,3 +84,24 @@ return QString::localeAwareCompare(leftCategory, rightCategory); } + +int SortedSystemTrayModel::compareCategoriesOrderly(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(); + + int leftIndex = s_categoryOrder.indexOf(leftCategory); + if (leftIndex == -1) { + leftIndex = s_categoryOrder.indexOf(QStringLiteral("UnknownCategory")); + } + + int rightIndex = s_categoryOrder.indexOf(rightCategory); + if (rightIndex == -1) { + rightIndex = s_categoryOrder.indexOf(QStringLiteral("UnknownCategory")); + } + + return leftIndex - rightIndex; +} diff --git a/applets/systemtray/systemtray.h b/applets/systemtray/systemtray.h --- a/applets/systemtray/systemtray.h +++ b/applets/systemtray/systemtray.h @@ -40,10 +40,9 @@ 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 ); @@ -54,11 +53,11 @@ void restoreContents(KConfigGroup &group) override; void restorePlasmoids(); - QAbstractItemModel *configSystemTrayModel(); + void configChanged() override; - QStringList defaultPlasmoids() const; + QAbstractItemModel *systemTrayModel(); - QAbstractItemModel* availablePlasmoids(); + QAbstractItemModel *configSystemTrayModel(); QStringList allowedPlasmoids() const; void setAllowedPlasmoids(const QStringList &allowed); @@ -92,23 +91,13 @@ */ 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); + Q_INVOKABLE bool isSystemTrayApplet(const QString &appletId); /** - * 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 + * @returns a Plasma::Service given a source name + * @param source source name we want a service of */ - Q_INVOKABLE void reorderItemAfter(QQuickItem* after, QQuickItem* before); - - Q_INVOKABLE bool isSystemTrayApplet(const QString &appletId); + Q_INVOKABLE Plasma::Service *serviceForSource(const QString &source); private Q_SLOTS: void serviceNameFetchFinished(QDBusPendingCallWatcher* watcher, const QDBusConnection &connection); @@ -120,17 +109,18 @@ Q_SIGNALS: void allowedPlasmoidsChanged(); + void configurationChanged(const KConfigGroup &config); 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_sortedSystemTrayModel; SortedSystemTrayModel *m_configSystemTrayModel; 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 @@ -43,18 +43,20 @@ SystemTray::SystemTray(QObject *parent, const QVariantList &args) : Plasma::Containment(parent, args), - m_availablePlasmoidsModel(nullptr), m_systemTrayModel(new SystemTrayModel(this)), + m_sortedSystemTrayModel(nullptr), 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); + connect(this, &SystemTray::configurationChanged, currentPlasmoidsModel, &PlasmoidModel::onConfigurationChanged); m_statusNotifierModel = new StatusNotifierModel(m_systemTrayModel); + connect(this, &SystemTray::configurationChanged, m_statusNotifierModel, &StatusNotifierModel::onConfigurationChanged); m_systemTrayModel->addSourceModel(currentPlasmoidsModel); m_systemTrayModel->addSourceModel(m_statusNotifierModel); @@ -315,39 +317,47 @@ return pos; } -void SystemTray::reorderItemBefore(QQuickItem* before, QQuickItem* after) +bool SystemTray::isSystemTrayApplet(const QString &appletId) { - if (!before || !after) { - return; - } + return m_systrayApplets.contains(appletId); +} - before->setVisible(false); - before->setParentItem(after->parentItem()); - before->stackBefore(after); - before->setVisible(true); +Plasma::Service *SystemTray::serviceForSource(const QString &source) +{ + return m_statusNotifierModel->serviceForSource(source); } -void SystemTray::reorderItemAfter(QQuickItem* after, QQuickItem* before) +void SystemTray::restoreContents(KConfigGroup &group) { - if (!before || !after) { - return; + QStringList newKnownItems; + QStringList newExtraItems; + + KConfigGroup general = group.group("General"); + + QStringList knownItems = general.readEntry("knownItems", QStringList()); + QStringList extraItems = general.readEntry("extraItems", QStringList()); + + //Add every plasmoid that is both not enabled explicitly and not already known + for (int i = 0; i < m_defaultPlasmoids.length(); ++i) { + QString candidate = m_defaultPlasmoids[i]; + if (!knownItems.contains(candidate)) { + newKnownItems.append(candidate); + if (!extraItems.contains(candidate)) { + newExtraItems.append(candidate); + } + } } - after->setVisible(false); - after->setParentItem(before->parentItem()); - after->stackAfter(before); - after->setVisible(true); -} + if (newExtraItems.length() > 0) { + general.writeEntry("extraItems", extraItems + newExtraItems); + } + if (newKnownItems.length() > 0) { + general.writeEntry("knownItems", knownItems + newKnownItems); + } -bool SystemTray::isSystemTrayApplet(const QString &appletId) -{ - return m_systrayApplets.contains(appletId); -} + setAllowedPlasmoids(general.readEntry("extraItems", QStringList())); -void SystemTray::restoreContents(KConfigGroup &group) -{ - Q_UNUSED(group); - //NOTE: RestoreContents shouldn't do anything here because is too soon, so have an empty reimplementation + emit configurationChanged(config()); } void SystemTray::restorePlasmoids() @@ -430,26 +440,28 @@ initDBusActivatables(); } -QAbstractItemModel *SystemTray::configSystemTrayModel() +void SystemTray::configChanged() { - if (!m_configSystemTrayModel) { - m_configSystemTrayModel = new SortedSystemTrayModel(SortedSystemTrayModel::SortingType::ConfigurationPage, this); - m_configSystemTrayModel->setSourceModel(m_systemTrayModel); - } - return m_configSystemTrayModel; + Containment::configChanged(); + emit configurationChanged(config()); } -QStringList SystemTray::defaultPlasmoids() const +QAbstractItemModel *SystemTray::systemTrayModel() { - return m_defaultPlasmoids; + if (!m_sortedSystemTrayModel) { + m_sortedSystemTrayModel = new SortedSystemTrayModel(SortedSystemTrayModel::SortingType::SystemTray, this); + m_sortedSystemTrayModel->setSourceModel(m_systemTrayModel); + } + return m_sortedSystemTrayModel; } -QAbstractItemModel* SystemTray::availablePlasmoids() +QAbstractItemModel *SystemTray::configSystemTrayModel() { - if (!m_availablePlasmoidsModel) { - m_availablePlasmoidsModel = new PlasmoidModel(this); + if (!m_configSystemTrayModel) { + m_configSystemTrayModel = new SortedSystemTrayModel(SortedSystemTrayModel::SortingType::ConfigurationPage, this); + m_configSystemTrayModel->setSourceModel(m_systemTrayModel); } - return m_availablePlasmoidsModel; + return m_configSystemTrayModel; } QStringList SystemTray::allowedPlasmoids() const diff --git a/applets/systemtray/systemtraymodel.h b/applets/systemtray/systemtraymodel.h --- a/applets/systemtray/systemtraymodel.h +++ b/applets/systemtray/systemtraymodel.h @@ -39,12 +39,29 @@ ItemId, CanRender, Category, + Status, + EffectiveStatus, LastBaseRole }; explicit BaseModel(QObject *parent = nullptr); QHash roleNames() const override; + +public slots: + void onConfigurationChanged(const KConfigGroup &config); + +private slots: + void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); + +private: + void updateEffectiveStatus(QStandardItem *dataItem); + Plasma::Types::ItemStatus calculateEffectiveStatus(QStandardItem *dataItem); + Plasma::Types::ItemStatus readStatus(QStandardItem *dataItem) const; + + bool m_showAllItems; + QStringList m_shownItems; + QStringList m_hiddenItems; }; class PlasmoidModel: public BaseModel diff --git a/applets/systemtray/systemtraymodel.cpp b/applets/systemtray/systemtraymodel.cpp --- a/applets/systemtray/systemtraymodel.cpp +++ b/applets/systemtray/systemtraymodel.cpp @@ -30,8 +30,10 @@ #include BaseModel::BaseModel(QObject *parent) - : QStandardItemModel(parent) + : QStandardItemModel(parent), + m_showAllItems(false) { + connect(this, &BaseModel::dataChanged, this, &BaseModel::onDataChanged); } QHash BaseModel::roleNames() const @@ -42,10 +44,80 @@ roles.insert(static_cast(BaseRole::ItemId), QByteArrayLiteral("itemId")); roles.insert(static_cast(BaseRole::CanRender), QByteArrayLiteral("canRender")); roles.insert(static_cast(BaseRole::Category), QByteArrayLiteral("category")); + roles.insert(static_cast(BaseRole::Status), QByteArrayLiteral("status")); + roles.insert(static_cast(BaseRole::EffectiveStatus), QByteArrayLiteral("effectiveStatus")); return roles; } +void BaseModel::onConfigurationChanged(const KConfigGroup &config) +{ + if (!config.isValid()) { + return; + } + + const KConfigGroup generalGroup = config.group("General"); + + m_showAllItems = generalGroup.readEntry("showAllItems", false); + m_shownItems = generalGroup.readEntry("shownItems", QStringList()); + m_hiddenItems = generalGroup.readEntry("hiddenItems", QStringList()); + + for (int i = 0; i < rowCount(); i++) { + QStandardItem *dataItem = item(i); + updateEffectiveStatus(dataItem); + } +} + +void BaseModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) +{ + if (roles.contains(static_cast(BaseRole::Status)) || roles.contains(static_cast(BaseRole::CanRender))) { + for (int i = topLeft.row(); i <= bottomRight.row(); i++) { + QStandardItem *dataItem = item(i); + updateEffectiveStatus(dataItem); + } + } +} + +void BaseModel::updateEffectiveStatus(QStandardItem *dataItem) +{ + Plasma::Types::ItemStatus status = calculateEffectiveStatus(dataItem); + dataItem->setData(status, static_cast(BaseModel::BaseRole::EffectiveStatus)); +} + +Plasma::Types::ItemStatus BaseModel::calculateEffectiveStatus(QStandardItem *dataItem) +{ + bool canRender = dataItem->data(static_cast(BaseRole::CanRender)).toBool(); + if (!canRender) { + return Plasma::Types::ItemStatus::HiddenStatus; + } + + Plasma::Types::ItemStatus status = readStatus(dataItem); + if (status == Plasma::Types::ItemStatus::HiddenStatus) { + return Plasma::Types::ItemStatus::HiddenStatus; + } + + QString itemId = dataItem->data(static_cast(BaseRole::ItemId)).toString(); + + bool forcedShown = m_showAllItems || m_shownItems.contains(itemId); + bool forcedHidden = m_hiddenItems.contains(itemId); + + if (forcedShown || (!forcedHidden && status != Plasma::Types::ItemStatus::PassiveStatus)) { + return Plasma::Types::ItemStatus::ActiveStatus; + } else { + return Plasma::Types::ItemStatus::PassiveStatus; + } +} + +Plasma::Types::ItemStatus BaseModel::readStatus(QStandardItem *dataItem) const +{ + QVariant statusData = dataItem->data(static_cast(BaseRole::Status)); + if (statusData.isValid()) { + return statusData.value(); + } else { + return Plasma::Types::ItemStatus::UnknownStatus; + } +} + static QString plasmoidCategoryForMetadata(const KPluginMetaData &metadata) { QString category = QStringLiteral("UnknownCategory"); @@ -123,6 +195,10 @@ 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->status(), static_cast(BaseModel::BaseRole::Status)); + connect(applet, &Plasma::Applet::statusChanged, this, [dataItem] (Plasma::Types::ItemStatus status) { + dataItem->setData(status, static_cast(BaseModel::BaseRole::Status)); + }); dataItem->setData(applet->property("_plasma_graphicObject"), static_cast(Role::Applet)); dataItem->setData(true, static_cast(Role::HasApplet)); @@ -240,6 +316,15 @@ QVariant category = data.value("Category"); dataItem->setData(category.isNull() ? QStringLiteral("UnknownCategory") : data.value("Category"), static_cast(BaseModel::BaseRole::Category)); + QString status = data.value("Status").toString(); + if (status == QLatin1String("Active")) { + dataItem->setData(Plasma::Types::ItemStatus::ActiveStatus, static_cast(BaseModel::BaseRole::Status)); + } else if (status == QLatin1String("NeedsAttention")) { + dataItem->setData(Plasma::Types::ItemStatus::NeedsAttentionStatus, static_cast(BaseModel::BaseRole::Status)); + } else { + dataItem->setData(Plasma::Types::ItemStatus::PassiveStatus, static_cast(BaseModel::BaseRole::Status)); + } + dataItem->setData(sourceName, static_cast(Role::DataEngineSource)); updateItemData(dataItem, data, Role::AttentionIcon); updateItemData(dataItem, data, Role::AttentionIconName);