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 systemtraymodel.cpp + sortedsystemtraymodel.cpp systemtray.cpp ) diff --git a/applets/systemtray/package/contents/applet/CompactApplet.qml b/applets/systemtray/package/contents/applet/CompactApplet.qml --- a/applets/systemtray/package/contents/applet/CompactApplet.qml +++ b/applets/systemtray/package/contents/applet/CompactApplet.qml @@ -31,7 +31,7 @@ icon: plasmoid.icon mainText: plasmoid.toolTipMainText subText: plasmoid.toolTipSubText - location: if (plasmoid.parent && plasmoid.parent.parent.objectName === "hiddenTasksColumn" && plasmoid.location !== PlasmaCore.Types.LeftEdge) { + location: if (plasmoid.parent && plasmoid.parent.inHiddenLayout && plasmoid.location !== PlasmaCore.Types.LeftEdge) { return PlasmaCore.Types.RightEdge; } else { return plasmoid.location; diff --git a/applets/systemtray/package/contents/ui/ConfigEntries.qml b/applets/systemtray/package/contents/ui/ConfigEntries.qml --- a/applets/systemtray/package/contents/ui/ConfigEntries.qml +++ b/applets/systemtray/package/contents/ui/ConfigEntries.qml @@ -75,19 +75,7 @@ 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 { @@ -179,14 +167,14 @@ return 0 } - property var currentValue: model[currentIndex].value + 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 (currentValue) { + switch (myCurrentValue) { case "auto": if (shownIndex > -1) { cfg_shownItems.splice(shownIndex, 1) @@ -257,7 +245,7 @@ Component.onCompleted: itemsList.keySequenceColumnWidth = Math.max(implicitWidth, itemsList.keySequenceColumnWidth) visible: isPlasmoid - enabled: visibilityComboBox.currentValue !== "disabled" + enabled: visibilityComboBox.myCurrentValue !== "disabled" keySequence: model.applet ? model.applet.globalShortcut : "" onKeySequenceChanged: { if (model.applet && keySequence !== model.applet.globalShortcut) { diff --git a/applets/systemtray/package/contents/ui/ExpandedRepresentation.qml b/applets/systemtray/package/contents/ui/ExpandedRepresentation.qml --- a/applets/systemtray/package/contents/ui/ExpandedRepresentation.qml +++ b/applets/systemtray/package/contents/ui/ExpandedRepresentation.qml @@ -98,8 +98,8 @@ PlasmaComponents.ToolButton { id: pinButton - Layout.preferredHeight: Math.round(units.gridUnit * 1.25) - Layout.preferredWidth: Layout.preferredHeight + implicitHeight: Math.round(units.gridUnit * 1.25) + implicitWidth: implicitHeight checkable: true checked: plasmoid.configuration.pin onToggled: plasmoid.configuration.pin = checked @@ -116,7 +116,6 @@ HiddenItemsView { id: hiddenItemsView Layout.fillWidth: !activeApplet - Layout.preferredWidth: activeApplet ? iconColumnWidth : -1 Layout.fillHeight: true } 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,55 +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: !activeApplet || activeApplet.parent.parent == hiddenTasksColumn - width: activeApplet ? iconColumnWidth : parent.width + 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 - Column { - id: hiddenTasksColumn + currentIndex: -1 + highlight: PlasmaComponents.Highlight {} + highlightMoveDuration: 0 + highlightResizeDuration: 0 - spacing: units.smallSpacing - width: parent.width - property Item hoveredItem - property alias marginHints: highlight.marginHints + readonly property int iconItemHeight: root.hiddenItemSize + highlight.marginHints.top + highlight.marginHints.bottom - objectName: "hiddenTasksColumn" + 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/PlasmoidPopupsContainer.qml b/applets/systemtray/package/contents/ui/PlasmoidPopupsContainer.qml --- a/applets/systemtray/package/contents/ui/PlasmoidPopupsContainer.qml +++ b/applets/systemtray/package/contents/ui/PlasmoidPopupsContainer.qml @@ -43,7 +43,6 @@ activeApplet.fullRepresentationItem.anchors.centerIn = undefined; activeApplet.fullRepresentationItem.anchors.fill = undefined; - mainStack.replace({item: activeApplet.fullRepresentationItem, immediate: !dialog.visible, properties: {focus: true}}); } else { mainStack.replace(emptyPage); 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,47 +25,30 @@ PlasmaCore.ToolTipArea { id: abstractItem - height: effectiveItemSize + marginHints.top + marginHints.bottom - width: labelVisible ? parent.width : effectiveItemSize + marginHints.left + marginHints.right + height: inVisibleLayout ? visibleLayout.cellHeight : hiddenLayout.iconItemHeight + width: inVisibleLayout ? visibleLayout.cellWidth : hiddenLayout.width - property real effectiveItemSize: hidden ? root.hiddenItemSize : root.itemSize property string itemId - property string category property alias text: label.text - property bool hidden: parent.objectName == "hiddenTasksColumn" - property QtObject marginHints: parent.marginHints - property bool labelVisible: abstractItem.hidden && !root.activeApplet property Item iconItem - //PlasmaCore.Types.ItemStatus - property int status - property QtObject model + 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 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 tiiltip properties + /* subclasses need to assign to this tooltip properties mainText: subText: - icon: + icon: */ location: { - if (abstractItem.parent && abstractItem.parent.objectName === "hiddenTasksColumn") { + if (inHiddenLayout) { if (LayoutMirroring.enabled && plasmoid.location !== PlasmaCore.Types.RightEdge) { return PlasmaCore.Types.LeftEdge; } else if (plasmoid.location !== PlasmaCore.Types.LeftEdge) { @@ -77,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 (hidden && containsMouse) { - root.hiddenLayout.hoveredItem = abstractItem + if (inHiddenLayout && containsMouse) { + root.hiddenLayout.currentIndex = index } } @@ -154,8 +129,8 @@ leftMargin: iconItem ? iconItem.width + units.smallSpacing : 0 verticalCenter: parent.verticalCenter } - opacity: labelVisible ? 1 : 0 - visible: abstractItem.hidden + opacity: visible ? 1 : 0 + visible: abstractItem.inHiddenLayout && !root.activeApplet Behavior on opacity { NumberAnimation { duration: units.longDuration 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,15 +28,18 @@ 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 : "" mainItem: applet && applet.toolTipItem ? applet.toolTipItem : null textFormat: applet ? applet.toolTipTextFormat : "" status: applet ? applet.status : PlasmaCore.Types.UnknownStatus active: root.activeApplet !== applet + Component.onDestruction: { + applet = null + } + onClicked: { if (applet && mouse.button === Qt.LeftButton) { applet.expanded = true; @@ -49,42 +52,64 @@ } onContextMenu: { if (applet) { - plasmoid.nativeInterface.showPlasmoidMenu(applet, 0, plasmoidContainer.hidden ? applet.height : 0); + plasmoid.nativeInterface.showPlasmoidMenu(applet, 0, plasmoidContainer.inHiddenLayout ? applet.height : 0); } } onHeightChanged: { if (applet) { applet.width = height } } + + //This is to make preloading effective, minimizes the scene changes + function preloadFullRepresentationItem(fullRepresentationItem) { + if (fullRepresentationItem && applet.fullRepresentationItem.parent === null) { + fullRepresentationItem.width = expandedRepresentation.width + fullRepresentationItem.width = expandedRepresentation.height + fullRepresentationItem.parent = preloadedStorage; + } + } + onAppletChanged: { - if (!applet) { - plasmoidContainer.destroy(); - print("applet destroyed") + if (applet) { + applet.parent = plasmoidContainer + applet.anchors.left = plasmoidContainer.left + applet.anchors.top = plasmoidContainer.top + applet.anchors.bottom = plasmoidContainer.bottom + applet.width = plasmoidContainer.height + applet.visible = true + plasmoidContainer.visible = true + + preloadFullRepresentationItem(applet.fullRepresentationItem) } } + Connections { target: applet onActivated: plasmoidContainer.activated() onExpandedChanged: { if (expanded) { var oldApplet = root.activeApplet; root.activeApplet = applet; - if (oldApplet) { + if (oldApplet && oldApplet !== applet) { oldApplet.expanded = false; } dialog.visible = true; plasmoidContainer.activated() } else if (root.activeApplet === applet) { - if (!applet.parent.hidden) { + if (!inHiddenLayout) { dialog.visible = false; } //if not expanded we don't have an active applet anymore root.activeApplet = null; } } + + onFullRepresentationItemChanged: { + preloadFullRepresentationItem(fullRepresentationItem) + } } } 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) @@ -77,8 +68,8 @@ var pos = plasmoid.nativeInterface.popupPosition(taskIcon, mouse.x, mouse.y); switch (mouse.button) { - case Qt.LeftButton: { - var service = statusNotifierSource.serviceForSource(DataEngineSource); + case Qt.LeftButton: + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("Activate"); operation.x = pos.x; operation.y = pos.y; @@ -92,13 +83,12 @@ }); taskIcon.activated() break; - } case Qt.RightButton: openContextMenu(pos); break; case Qt.MiddleButton: - var service = statusNotifierSource.serviceForSource(DataEngineSource); + var service = plasmoid.nativeInterface.serviceForSource(model.DataEngineSource); var operation = service.operationDescription("SecondaryActivate"); operation.x = pos.x; @@ -110,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; @@ -124,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,137 +48,26 @@ 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}); - - applet.parent = plasmoidContainer - applet.anchors.left = plasmoidContainer.left - applet.anchors.top = plasmoidContainer.top - applet.anchors.bottom = plasmoidContainer.bottom - applet.width = plasmoidContainer.height - applet.visible = true - plasmoidContainer.visible = true - - //This is to make preloading effective, minimizes the scene changes - if (applet.fullRepresentationItem) { - applet.fullRepresentationItem.width = expandedRepresentation.width - applet.fullRepresentationItem.width = expandedRepresentation.height - applet.fullRepresentationItem.parent = preloadedStorage; - } else { - applet.fullRepresentationItemChanged.connect(function() { - applet.fullRepresentationItem.width = expandedRepresentation.width - applet.fullRepresentationItem.width = expandedRepresentation.height - applet.fullRepresentationItem.parent = preloadedStorage; - }); - } - } - //being there forces the items to fully load, and they will be reparented in the popup one by one, this item is *never* visible Item { id: preloadedStorage visible: false } - Containment.onAppletRemoved: { - } - Connections { target: plasmoid onUserConfiguringChanged: { @@ -187,91 +77,15 @@ } } - Connections { + Connections { target: plasmoid.configuration onExtraItemsChanged: plasmoid.nativeInterface.allowedPlasmoids = plasmoid.configuration.extraItems } - Component.onCompleted: { - //script, don't bind - plasmoid.nativeInterface.allowedPlasmoids = initializePlasmoidList(); - } - - function initializePlasmoidList() { - var newKnownItems = []; - var newExtraItems = []; - - //NOTE:why this? otherwise the interpreter will execute plasmoid.nativeInterface.defaultPlasmoids() on - //every access of defaults[], resulting in a very slow iteration - var defaults = []; - //defaults = defaults.concat(plasmoid.nativeInterface.defaultPlasmoids); - defaults = plasmoid.nativeInterface.defaultPlasmoids.slice() - var candidate; - - //Add every plasmoid that is both not enabled explicitly and not already known - for (var i = 0; i < defaults.length; ++i) { - candidate = defaults[i]; - if (plasmoid.configuration.knownItems.indexOf(candidate) === -1) { - newKnownItems.push(candidate); - if (plasmoid.configuration.extraItems.indexOf(candidate) === -1) { - newExtraItems.push(candidate); - } - } - } - - if (newExtraItems.length > 0) { - plasmoid.configuration.extraItems = plasmoid.configuration.extraItems.slice().concat(newExtraItems); - } - if (newKnownItems.length > 0) { - plasmoid.configuration.knownItems = plasmoid.configuration.knownItems.slice().concat(newKnownItems); - } - - return plasmoid.configuration.extraItems; - } - - PlasmaCore.DataSource { - id: statusNotifierSource - engine: "statusnotifieritem" - interval: 0 - onSourceAdded: { - connectSource(source) - } - Component.onCompleted: { - connectedSources = sources - } - } - - 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 {} - } - //NOTE: this exists mostly for not causing reference errors - property QtObject marginHints: QtObject { - property int left: 0 - property int top: 0 - property int right: 0 - property int bottom: 0 - } - } - CurrentItemHighLight { - visualParent: tasksRow - target: root.activeApplet && root.activeApplet.parent.parent == tasksRow ? root.activeApplet.parent : root + visualParent: tasksGrid + target: root.activeApplet && root.activeApplet.parent && root.activeApplet.parent.inVisibleLayout ? root.activeApplet.parent.parent : root location: plasmoid.location } @@ -316,51 +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) - - - //Do spacing with margins, to correctly compute the number of lines - property QtObject marginHints: QtObject { - property int left: Math.round(units.smallSpacing / 2) - property int top: Math.round(units.smallSpacing / 2) - property int right: Math.round(units.smallSpacing / 2) - property int bottom: Math.round(units.smallSpacing / 2) - } + GridLayout { + id: mainLayout + + rowSpacing: 0 + columnSpacing: 0 + anchors.fill: parent + + flow: vertical ? GridLayout.TopToBottom : GridLayout.LeftToRight - //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 + 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 new file mode 100644 --- /dev/null +++ b/applets/systemtray/sortedsystemtraymodel.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * 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, + SystemTray + }; + + explicit SortedSystemTrayModel(SortingType sorting, QObject *parent = nullptr); + +protected: + virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; + +private: + bool lessThanConfigurationPage(const QModelIndex &left, const QModelIndex &right) const; + bool lessThanByCategories(const QModelIndex &left, const QModelIndex &right) const; + + int compareDisplayAlphabetically(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; +}; + +#endif // SORTEDSYSTEMTRAYMODEL_H diff --git a/applets/systemtray/sortedsystemtraymodel.cpp b/applets/systemtray/sortedsystemtraymodel.cpp new file mode 100644 --- /dev/null +++ b/applets/systemtray/sortedsystemtraymodel.cpp @@ -0,0 +1,113 @@ +/*************************************************************************** + * 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 + +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) +{ + sort(0); +} + +bool SortedSystemTrayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + switch (m_sorting) { + case SortedSystemTrayModel::SortingType::ConfigurationPage: + return lessThanConfigurationPage(left, right); + case SortedSystemTrayModel::SortingType::SystemTray: + default: + return lessThanByCategories(left, right); + } +} + +bool SortedSystemTrayModel::lessThanConfigurationPage(const QModelIndex &left, const QModelIndex &right) const +{ + const int categoriesComparison = compareCategoriesAlphabetically(left, right); + if (categoriesComparison == 0) { + return compareDisplayAlphabetically(left, right); + } else { + return categoriesComparison < 0; + } +} + +bool SortedSystemTrayModel::lessThanByCategories(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 compareDisplayAlphabetically(left, right); + } else { + return categoriesComparison < 0; + } +} + +int SortedSystemTrayModel::compareDisplayAlphabetically(const QModelIndex &left, const QModelIndex &right) const +{ + QString leftData = sourceModel()->data(left).toString(); + QString rightData = sourceModel()->data(right).toString(); + + return QString::localeAwareCompare(leftData, rightData) < 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); +} + +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 @@ -35,14 +35,14 @@ 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* availablePlasmoids READ availablePlasmoids CONSTANT) + Q_PROPERTY(QAbstractItemModel* configSystemTrayModel READ configSystemTrayModel 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 ); @@ -53,11 +53,11 @@ void restoreContents(KConfigGroup &group) override; void restorePlasmoids(); - QAbstractItemModel* systemTrayModel(); + void configChanged() override; - QStringList defaultPlasmoids() const; + QAbstractItemModel *systemTrayModel(); - QAbstractItemModel* availablePlasmoids(); + QAbstractItemModel *configSystemTrayModel(); QStringList allowedPlasmoids() const; void setAllowedPlasmoids(const QStringList &allowed); @@ -91,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); @@ -119,17 +109,19 @@ 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 @@ -19,6 +19,7 @@ #include "systemtray.h" #include "systemtraymodel.h" +#include "sortedsystemtraymodel.h" #include "debug.h" #include @@ -42,18 +43,22 @@ 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_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); - m_systemTrayModel->addSourceModel(currentPlasmoidsModel); + 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); } @@ -312,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() @@ -386,8 +399,6 @@ } } - QStringList ownApplets; - QMap sortedApplets; for (auto it = m_systrayApplets.constBegin(); it != m_systrayApplets.constEnd(); ++it) { const KPluginMetaData &info = it.value(); @@ -429,22 +440,28 @@ initDBusActivatables(); } -QAbstractItemModel *SystemTray::systemTrayModel() +void SystemTray::configChanged() { - return m_systemTrayModel; + 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 @@ -487,7 +504,7 @@ connect(systemCallWatcher, &QDBusPendingCallWatcher::finished, [=](QDBusPendingCallWatcher *callWatcher){ SystemTray::serviceNameFetchFinished(callWatcher, QDBusConnection::systemBus()); - }); + }); } void SystemTray::serviceNameFetchFinished(QDBusPendingCallWatcher* watcher, const QDBusConnection &connection) diff --git a/applets/systemtray/systemtraymodel.h b/applets/systemtray/systemtraymodel.h --- a/applets/systemtray/systemtraymodel.h +++ b/applets/systemtray/systemtraymodel.h @@ -21,6 +21,7 @@ #define SYSTEMTRAYMODEL_H #include +#include #include #include @@ -30,20 +31,46 @@ 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, + 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 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 }; @@ -56,11 +83,11 @@ 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, diff --git a/applets/systemtray/systemtraymodel.cpp b/applets/systemtray/systemtraymodel.cpp --- a/applets/systemtray/systemtraymodel.cpp +++ b/applets/systemtray/systemtraymodel.cpp @@ -29,11 +29,114 @@ #include +BaseModel::BaseModel(QObject *parent) + : QStandardItemModel(parent), + m_showAllItems(false) +{ + connect(this, &BaseModel::dataChanged, this, &BaseModel::onDataChanged); +} + +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")); + 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; + } +} + +// temporary hack to fix known broken categories +// should go away as soon as fixes are merged +static const QHash s_categoryOverride = { + {QStringLiteral("org.kde.discovernotifier"), QStringLiteral("SystemServices")}, + {QStringLiteral("org.kde.plasma.networkmanagement"), QStringLiteral("Hardware")}, + {QStringLiteral("org.kde.kdeconnect"), QStringLiteral("Hardware")}, + {QStringLiteral("org.kde.plasma.keyboardindicator"), QStringLiteral("Hardware")}, + {QStringLiteral("touchpad"), QStringLiteral("Hardware")} +}; + static QString plasmoidCategoryForMetadata(const KPluginMetaData &metadata) { QString category = QStringLiteral("UnknownCategory"); if (metadata.isValid()) { + if (s_categoryOverride.contains(metadata.pluginId())) { + return s_categoryOverride.value(metadata.pluginId()); + } + const QString notificationAreaCategory = metadata.value(QStringLiteral("X-Plasma-NotificationAreaCategory")); if (!notificationAreaCategory.isEmpty()) { category = notificationAreaCategory; @@ -43,7 +146,7 @@ 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") { @@ -58,24 +161,18 @@ 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")); @@ -85,44 +182,58 @@ 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->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)); - 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)); return; } } } -StatusNotifierModel::StatusNotifierModel(QObject *parent) : QStandardItemModel(parent) +StatusNotifierModel::StatusNotifierModel(QObject *parent) : BaseModel(parent) { m_dataEngine = dataEngine(QStringLiteral("statusnotifieritem")); @@ -134,12 +245,7 @@ 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")); @@ -207,8 +313,8 @@ 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); @@ -219,8 +325,18 @@ 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)); + + 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);