diff --git a/applets/devicenotifier/package/contents/ui/ActionItem.qml b/applets/devicenotifier/package/contents/ui/ActionItem.qml deleted file mode 100644 --- a/applets/devicenotifier/package/contents/ui/ActionItem.qml +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2011 Viranch Mehta - * Copyright 2012 Jacopo De Simoi - * Copyright 2016 Kai Uwe Broulik - * - * 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 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 -import QtQuick.Layouts 1.1 - -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents - -MouseArea { - id: area - property string icon - property alias label: actionText.text - property string predicate - - height: row.height + 2 * row.y - hoverEnabled: true - - onContainsMouseChanged: { - area.ListView.view.currentIndex = (containsMouse ? index : -1) - } - - onClicked: { - var service = hpSource.serviceForSource(udi); - var operation = service.operationDescription("invokeAction"); - operation.predicate = predicate; - service.startOperationCall(operation); - devicenotifier.expandedDevice = ""; - devicenotifier.currentIndex = -1; - } - - RowLayout { - id: row - anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - 2 * units.smallSpacing - y: units.smallSpacing - spacing: units.smallSpacing - - PlasmaCore.IconItem { - source: area.icon - Layout.preferredWidth: units.iconSizes.smallMedium - Layout.preferredHeight: width - } - - PlasmaComponents.Label { - id: actionText - Layout.fillWidth: true - Layout.fillHeight: true - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - elide: Text.ElideRight - maximumLineCount: 2 - } - } -} diff --git a/applets/devicenotifier/package/contents/ui/DeviceItem.qml b/applets/devicenotifier/package/contents/ui/DeviceItem.qml --- a/applets/devicenotifier/package/contents/ui/DeviceItem.qml +++ b/applets/devicenotifier/package/contents/ui/DeviceItem.qml @@ -2,6 +2,7 @@ * Copyright 2011 Viranch Mehta * Copyright 2012 Jacopo De Simoi * Copyright 2016 Kai Uwe Broulik + * Copyright 2020 Nate Graham * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as @@ -20,105 +21,82 @@ */ import QtQuick 2.0 -import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.12 +import QtQml.Models 2.14 import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.extras 2.0 as PlasmaExtras + import org.kde.kquickcontrolsaddons 2.0 -MouseArea { +// TODO: fix expanding when a new device is connected; done in devicenotifier.qml +// TODO: clean up list item expanding code in devicenotifier.qml +// TODO: make the free space calculation/display work again for newly-mounted volumes +// TODO: make sure message display works +// TODO: make fix multiple highlight effects when an item is unmounted + +PlasmaExtras.ExpandableListItem { id: deviceItem property string udi - property alias icon: deviceIcon.source - property alias deviceName: deviceLabel.text - property string emblemIcon - property int state - - property bool mounted - property bool isRoot - property bool expanded: devicenotifier.expandedDevice == udi - property alias percentUsage: freeSpaceBar.value - property string freeSpaceText - - signal actionTriggered - - property alias actionIcon: actionButton.iconName - property alias actionToolTip: actionButton.tooltip - property bool actionVisible + readonly property int state: sdSource.data[udi] ? sdSource.data[udi].State : 0 + readonly property int operationResult: (model["Operation result"]) + readonly property bool isMounted: devicenotifier.isMounted(udi) + readonly property bool isRoot: sdSource.data[udi]["File Path"] === "/" readonly property bool hasMessage: statusSource.lastUdi == udi && statusSource.data[statusSource.last] ? true : false readonly property var message: hasMessage ? statusSource.data[statusSource.last] || ({}) : ({}) - height: row.childrenRect.height + 2 * row.y - hoverEnabled: true - - onHasMessageChanged: { - if (hasMessage) { - messageHighlight.highlight(this) - } - } - - onContainsMouseChanged: { - if (containsMouse) { - devicenotifier.currentIndex = index - } - - // this is done to hide the highlight if the mouse moves out of the list view - // and we are not hovering anything - if (deviceItem.ListView.view.highlightItem) { - deviceItem.ListView.view.highlightItem.opacity = (containsMouse ? 1 : 0) + readonly property double freeSpace: sdSource.data[udi] && sdSource.data[udi]["Free Space"] ? sdSource.data[udi]["Free Space"] : -1.0 + readonly property double totalSpace: sdSource.data[udi] && sdSource.data[udi]["Size"] ? sdSource.data[udi]["Size"] : -1.0 + readonly property string freeSpaceText: sdSource.data[udi] && sdSource.data[udi]["Free Space Text"] ? sdSource.data[udi]["Free Space Text"] : "" + readonly property string totalSpaceText: sdSource.data[udi] && sdSource.data[udi]["Size Text"] ? sdSource.data[udi]["Size Text"] : "" + property bool freeSpaceKnown: freeSpace > 0 && totalSpace > 0 + + onOperationResultChanged: { + if (operationResult == 1) { + devicenotifier.popupIcon = "dialog-ok" + popupIconTimer.restart() + } else if (operationResult == 2) { + devicenotifier.popupIcon = "dialog-error" + popupIconTimer.restart() } } - onClicked: { - var data = hpSource.data[udi] - if (!data) { - return - } - - var actions = data.actions - if (actions.length === 1) { - var service = hpSource.serviceForSource(udi) - var operation = service.operationDescription("invokeAction") - operation.predicate = actions[0].predicate - service.startOperationCall(operation) - } else { - devicenotifier.expandedDevice = (expanded ? "" : udi) + onHasMessageChanged: { + if (deviceItem.hasMessage) { + messageHighlight.highlight(this) } } Connections { target: unmountAll onClicked: { - if (model["Removable"] && mounted) { + if (model["Removable"] && isMounted) { actionTriggered(); } } } // this keeps the delegate around for 5 seconds after the device has been // removed in case there was a message, such as "you can now safely remove this" ListView.onRemove: { + deviceItem.isEnabled = false if (devicenotifier.expandedDevice == udi) { devicenotifier.expandedDevice = "" } if (deviceItem.hasMessage) { ListView.delayRemove = true keepDelegateTimer.restart() - statusMessage.opacity = 1 // HACK seems the Column animation breaksf - freeSpaceBar.visible = false - actionButton.visible = false - ++devicenotifier.pendingDelegateRemoval // QTBUG-50380 } } Timer { id: keepDelegateTimer - interval: 3000 // same interval as the auto hide / passive timer + interval: 5000 // same interval as the auto hide / passive timer onTriggered: { deviceItem.ListView.delayRemove = false // otherwise the last message will show again when this device reappears @@ -132,211 +110,126 @@ id: updateStorageSpaceTimer interval: 5000 repeat: true - running: mounted && plasmoid.expanded + running: isMounted && plasmoid.expanded triggeredOnStart: true // Update the storage space as soon as we open the plasmoid onTriggered: { var service = sdSource.serviceForSource(udi); var operation = service.operationDescription("updateFreespace"); service.startOperationCall(operation); } } - RowLayout { - id: row - anchors.horizontalCenter: parent.horizontalCenter - y: units.smallSpacing - width: parent.width - 2 * units.smallSpacing - spacing: units.smallSpacing - - // FIXME: Device item loses focus on mounting/unmounting it, - // or specifically, when some UI element changes. - PlasmaCore.IconItem { - id: deviceIcon - Layout.alignment: Qt.AlignTop - Layout.preferredWidth: units.iconSizes.medium - Layout.preferredHeight: width - enabled: deviceItem.state == 0 - active: iconToolTip.containsMouse - - PlasmaCore.IconItem { - id: deviceEmblem - anchors { - left: parent.left - bottom: parent.bottom - } - width: units.iconSizes.small - height: width - source: { - if (deviceItem.hasMessage) { - if (deviceItem.message.solidError === 0) { - return "emblem-information" - } else { - return "emblem-error" - } - } else if (deviceItem.state == 0) { - return emblemIcon - } else { - return "" - } - } - } + Component { + id: deviceActionComponent + Action { } + } - PlasmaCore.ToolTipArea { - id: iconToolTip - anchors.fill: parent - subText: { - if ((mounted || deviceItem.state != 0) && model["Available Content"] !== "Audio") { - if (model["Removable"]) { - return i18n("It is currently not safe to remove this device: applications may be accessing it. Click the eject button to safely remove this device.") - } else { - return i18n("This device is currently accessible.") - } - } else { - if (model["Removable"]) { - if (model["In Use"]) { - return i18n("It is currently not safe to remove this device: applications may be accessing other volumes on this device. Click the eject button on these other volumes to safely remove this device."); - } else { - return i18n("It is currently safe to remove this device.") - } - } else { - return i18n("This device is not currently accessible.") - } - } - } - } + // We need a JS array full of QQC2 actions; this function creates them + // from the actions list of the data source + function populateActions() { + var deviceActions = []; + + for (let rawAction of hpSource.data[udi].actions) { + const newAction = deviceActionComponent.createObject(deviceItem, { + text: rawAction.text, + 'icon.name': rawAction.icon + }); + newAction.triggered.connect(() => { + var service = hpSource.serviceForSource(udi); + var operation = service.operationDescription('invokeAction'); + operation.predicate = rawAction.predicate; + service.startOperationCall(operation); + devicenotifier.expandedDevice = ''; + devicenotifier.currentIndex = -1; + }); + deviceActions.push(newAction); } + return deviceActions; + } - Column { - Layout.fillWidth: true - - move: Transition { - NumberAnimation { property: "y"; duration: units.longDuration; easing.type: Easing.InOutQuad } - // ensure opacity values return to 1.0 if the add transition animation has been interrupted - NumberAnimation { property: "opacity"; to: 1.0 } - } - - add: Transition { - NumberAnimation { - property: "opacity" - from: 0 - to: 1 - duration: units.longDuration - easing.type: Easing.InOutQuad - } - } - - PlasmaComponents.Label { - id: deviceLabel - width: parent.width - height: undefined // reset PlasmaComponent.Label's strange default height - elide: Text.ElideRight - } - - PlasmaComponents.ProgressBar { - id: freeSpaceBar - width: parent.width - height: units.gridUnit // default is * 1.6 - visible: deviceItem.state == 0 && mounted - minimumValue: 0 - maximumValue: 100 - - PlasmaCore.ToolTipArea { - anchors.fill: parent - subText: freeSpaceText != "" ? i18nc("@info:status Free disk space", "%1 free", freeSpaceText) : "" - } + function actionTriggered() { + var wasMounted = isMounted; + var operationName = wasMounted ? "unmount" : "mount"; + var service = sdSource.serviceForSource(udi); + var operation = service.operationDescription(operationName); + service.startOperationCall(operation); + if (wasMounted) { + deviceItem.collapse(); + } + } - // ProgressBar eats click events, so we'll forward them manually here... - // setting enabled to false will also make the ProgressBar *look* disabled - MouseArea { - anchors.fill: parent - onClicked: deviceItem.clicked(mouse) - } - } - PlasmaComponents.Label { - id: actionMessage - width: parent.width - height: undefined - opacity: 0.6 - font.pointSize: theme.smallestFont.pointSize - visible: deviceItem.state != 0 || (!actionsList.visible && !deviceItem.hasMessage) - text: { - if (deviceItem.state == 0) { - if (!hpSource.data[udi]) { - return "" - } + icon: sdSource.data[udi] == undefined ? "" : sdSource.data[udi].Icon - var actions = hpSource.data[udi].actions - if (actions.length > 1) { - return i18np("1 action for this device", "%1 actions for this device", actions.length); - } else { - return actions[0].text - } - } else if (deviceItem.state == 1) { - return i18nc("Accessing is a less technical word for Mounting; translation should be short and mean \'Currently mounting this device\'", "Accessing...") - } else { - return i18nc("Removing is a less technical word for Unmounting; translation should be short and mean \'Currently unmounting this device\'", "Removing...") - } + iconEmblem: { + if (sdSource.data[udi] != undefined) { + if (deviceItem.hasMessage) { + if (deviceItem.message.solidError === 0) { + return "emblem-information" + } else { + return "emblem-error" } + } else if (deviceItem.state == 0 && Emblems && Emblems[0]) { + return Emblems[0] + } else { + return "" } + } + return "" + } - PlasmaComponents.Label { - id: statusMessage - width: parent.width - height: undefined - font.pointSize: theme.smallestFont.pointSize - text: deviceItem.hasMessage ? (deviceItem.message.error || "") : "" - wrapMode: Text.WordWrap - maximumLineCount: 10 - elide: Text.ElideRight - visible: deviceItem.hasMessage - } + title: sdSource.data[udi] == undefined ? "" : sdSource.data[udi].Description - Item { // spacer - width: 1 - height: units.smallSpacing - visible: actionsList.visible + subtitle: { + if (deviceItem.hasMessage) { + return deviceItem.message.error + } + if (deviceItem.state == 0) { + if (!hpSource.data[udi]) { + return "" } - - ListView { - id: actionsList - width: parent.width - interactive: false - model: hpSource.data[udi] ? hpSource.data[udi].actions : null - height: deviceItem.expanded ? actionsList.contentHeight : 0 - visible: height > 0 - cacheBuffer: 50000 // create all items - delegate: ActionItem { - width: actionsList.width - icon: modelData.icon - label: modelData.text - predicate: modelData.predicate - } - highlight: PlasmaComponents.Highlight {} - highlightMoveDuration: 0 - highlightResizeDuration: 0 + if (freeSpaceKnown) { + return i18nc("@info:status Free disk space", "%1 free of %2", freeSpaceText, totalSpaceText) } + } else if (deviceItem.state == 1) { + return i18nc("Accessing is a less technical word for Mounting; translation should be short and mean \'Currently mounting this device\'", "Accessing...") + } else { + return i18nc("Removing is a less technical word for Unmounting; translation should be short and mean \'Currently unmounting this device\'", "Removing...") } + } - Item { - Layout.preferredWidth: units.iconSizes.medium - Layout.fillHeight: true + subtitleCanWrap: true - PlasmaComponents.ToolButton { - id: actionButton - visible: !busyIndicator.visible && deviceItem.actionVisible - enabled: !isRoot - onClicked: actionTriggered() - y: mounted ? deviceLabel.height + (freeSpaceBar.height - height - units.smallSpacing) / 2 : (deviceLabel.height + actionMessage.height - height) / 2 + // Color the subtitle red for disks with less than 5% free space + subtitleColor: { + if (freeSpaceKnown) { + if (freeSpace / totalSpace <= 0.05) { + return theme.negativeTextColor } + } + return theme.textColor + } - PlasmaComponents.BusyIndicator { - id: busyIndicator - width: parent.width - height: width - running: visible - visible: deviceItem.state != 0 + defaultActionButtonAction: Action { + icon.name: isMounted ? "media-eject" : "media-mount" + text: { + var types = model["Device Types"]; + if (!isMounted) { + return i18n("Mount") + } else if (types && types.indexOf("OpticalDisc") !== -1) { + return i18n("Eject") + } else { + return i18n("Safely remove") } } + onTriggered: actionTriggered() } + // Don't let the user try to unmount root + defaultActionButtonVisible: !isRoot + // don't show for media players since mount/unmount don't really apply + && model["Device Types"].indexOf("Portable Media Player") === -1 + + isBusy: deviceItem.state != 0 + + contextualActionsModel: hpSource.data[udi] ? populateActions() : null } diff --git a/applets/devicenotifier/package/contents/ui/FullRepresentation.qml b/applets/devicenotifier/package/contents/ui/FullRepresentation.qml --- a/applets/devicenotifier/package/contents/ui/FullRepresentation.qml +++ b/applets/devicenotifier/package/contents/ui/FullRepresentation.qml @@ -3,6 +3,7 @@ * Copyright 2012 Jacopo De Simoi * Copyright 2014 David Edmundson * Copyright 2014 Marco Martin + * Copyright 2020 Nate Graham * * * This program is free software; you can redistribute it and/or modify @@ -137,11 +138,10 @@ model: filterModel - delegate: deviceItem + delegate: DeviceItem { + udi: DataEngineSource + } highlight: PlasmaComponents.Highlight { } - highlightMoveDuration: 0 - highlightResizeDuration: 0 - spacing: units.smallSpacing currentIndex: devicenotifier.currentIndex @@ -164,68 +164,4 @@ } } } - - Component { - id: deviceItem - - DeviceItem { - width: notifierDialog.width - udi: DataEngineSource - Binding on icon { - when: sdSource.data[udi] !== undefined - value: sdSource.data[udi].Icon - } - Binding on deviceName { - when: sdSource.data[udi] !== undefined - value: sdSource.data[udi].Description - } - emblemIcon: Emblems && Emblems[0] ? Emblems[0] : "" - state: sdSource.data[udi] ? sdSource.data[udi].State : 0 - isRoot: sdSource.data[udi]["File Path"] === "/" - - percentUsage: { - if (!sdSource.data[udi]) { - return 0 - } - var freeSpace = new Number(sdSource.data[udi]["Free Space"]); - var size = new Number(sdSource.data[udi]["Size"]); - var used = size-freeSpace; - return used*100/size; - } - freeSpaceText: sdSource.data[udi] && sdSource.data[udi]["Free Space Text"] ? sdSource.data[udi]["Free Space Text"] : "" - - actionIcon: mounted ? "media-eject" : "media-mount" - actionVisible: model["Device Types"].indexOf("Portable Media Player") === -1 - actionToolTip: { - var types = model["Device Types"]; - if (!mounted) { - return i18n("Click to access this device from other applications.") - } else if (types && types.indexOf("OpticalDisc") !== -1) { - return i18n("Click to eject this disc.") - } else { - return i18n("Click to safely remove this device.") - } - } - mounted: devicenotifier.isMounted(udi) - - onActionTriggered: { - var operationName = mounted ? "unmount" : "mount"; - var service = sdSource.serviceForSource(udi); - var operation = service.operationDescription(operationName); - service.startOperationCall(operation); - } - property int operationResult: (model["Operation result"]) - - onOperationResultChanged: { - if (operationResult == 1) { - devicenotifier.popupIcon = "dialog-ok" - popupIconTimer.restart() - } else if (operationResult == 2) { - devicenotifier.popupIcon = "dialog-error" - popupIconTimer.restart() - } - } - Behavior on height { NumberAnimation { duration: units.shortDuration } } - } - } } diff --git a/applets/devicenotifier/package/contents/ui/devicenotifier.qml b/applets/devicenotifier/package/contents/ui/devicenotifier.qml --- a/applets/devicenotifier/package/contents/ui/devicenotifier.qml +++ b/applets/devicenotifier/package/contents/ui/devicenotifier.qml @@ -259,7 +259,7 @@ } function expandDevice(udi) { - if (hpSource.data[udi]["actions"].length > 1) { + if (hpSource.data[udi]["actions"].length > 0) { expandedDevice = udi } diff --git a/applets/notifications/package/contents/ui/NotificationItem.qml b/applets/notifications/package/contents/ui/NotificationItem.qml --- a/applets/notifications/package/contents/ui/NotificationItem.qml +++ b/applets/notifications/package/contents/ui/NotificationItem.qml @@ -325,10 +325,10 @@ model: { var buttons = []; + var actionNames = (notificationItem.actionNames || []); + var actionLabels = (notificationItem.actionLabels || []); // HACK We want the actions to be right-aligned but Flow also reverses - var actionNames = (notificationItem.actionNames || []).reverse(); - var actionLabels = (notificationItem.actionLabels || []).reverse(); - for (var i = 0; i < actionNames.length; ++i) { + for (var i = actionNames.length - 1; i >= 0; --i) { buttons.push({ actionName: actionNames[i], label: actionLabels[i] diff --git a/dataengines/soliddevice/soliddeviceengine.cpp b/dataengines/soliddevice/soliddeviceengine.cpp --- a/dataengines/soliddevice/soliddeviceengine.cpp +++ b/dataengines/soliddevice/soliddeviceengine.cpp @@ -572,9 +572,10 @@ timer->stop(); if (!job->error()) { - setData(udi, I18N_NOOP("Free Space"), QVariant(available)); + setData(udi, I18N_NOOP("Free Space"), QVariant(available).toDouble()); setData(udi, I18N_NOOP("Free Space Text"), KFormat().formatByteSize(available)); - setData(udi, I18N_NOOP("Size"), QVariant(size)); + setData(udi, I18N_NOOP("Size"), QVariant(size).toDouble()); + setData(udi, I18N_NOOP("Size Text"), KFormat().formatByteSize(size)); } m_paths.remove(path); diff --git a/krunner/CMakeLists.txt b/krunner/CMakeLists.txt --- a/krunner/CMakeLists.txt +++ b/krunner/CMakeLists.txt @@ -32,6 +32,7 @@ configure_file(krunner.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/krunner.desktop @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/krunner.desktop DESTINATION ${DATA_INSTALL_DIR}/kglobalaccel) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/krunner.desktop DESTINATION ${KDE_INSTALL_APPDIR}) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KRunnerAppDBusInterface") configure_package_config_file(KRunnerAppDBusInterfaceConfig.cmake.in diff --git a/krunner/krunner.desktop.cmake b/krunner/krunner.desktop.cmake --- a/krunner/krunner.desktop.cmake +++ b/krunner/krunner.desktop.cmake @@ -54,6 +54,7 @@ X-KDE-Shortcuts=Alt+Space,Alt+F2,Search Actions=RunClipboard X-KDE-Wayland-Interfaces=org_kde_plasma_window_management +NoDisplay=true [Desktop Action RunClipboard] Exec=krunner -c diff --git a/krunner/view.cpp b/krunner/view.cpp --- a/krunner/view.cpp +++ b/krunner/view.cpp @@ -236,10 +236,10 @@ QDBusPendingCall async = strutManager.asyncCall("availableScreenRect", shownOnScreen->name()); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this); - QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [=]() { + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, shownOnScreen]() { + watcher->deleteLater(); QDBusPendingReply reply = *watcher; - setScreen(shownOnScreen); const QRect r = reply.isValid() ? reply.value() : shownOnScreen->availableGeometry(); if (m_floating && !m_customPos.isNull()) { @@ -268,14 +268,13 @@ KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop()); KWindowSystem::setType(winId(), NET::Normal); //Turn the sliding effect off - KWindowEffects::slideWindow(winId(), KWindowEffects::NoEdge, 0); + setLocation(Plasma::Types::Floating); } else { KWindowSystem::setOnAllDesktops(winId(), true); - KWindowEffects::slideWindow(winId(), KWindowEffects::TopEdge, 0); + setLocation(Plasma::Types::TopEdge); } KWindowSystem::forceActiveWindow(winId()); - watcher->deleteLater(); }); } diff --git a/libnotificationmanager/jobsmodel.h b/libnotificationmanager/jobsmodel.h --- a/libnotificationmanager/jobsmodel.h +++ b/libnotificationmanager/jobsmodel.h @@ -57,6 +57,7 @@ QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QHash roleNames() const override; /** * @brief Close a job diff --git a/libnotificationmanager/jobsmodel.cpp b/libnotificationmanager/jobsmodel.cpp --- a/libnotificationmanager/jobsmodel.cpp +++ b/libnotificationmanager/jobsmodel.cpp @@ -22,6 +22,7 @@ #include "jobsmodel_p.h" #include "notifications.h" +#include "utils_p.h" #include #include @@ -176,6 +177,11 @@ return d->m_jobViews.count(); } +QHash JobsModel::roleNames() const +{ + return Utils::roleNames(); +} + void JobsModel::close(const QModelIndex &idx) { if (checkIndex(idx)) { diff --git a/libnotificationmanager/notifications.cpp b/libnotificationmanager/notifications.cpp --- a/libnotificationmanager/notifications.cpp +++ b/libnotificationmanager/notifications.cpp @@ -843,26 +843,5 @@ QHash Notifications::roleNames() const { - static QHash s_roles; - - if (s_roles.isEmpty()) { - s_roles = QSortFilterProxyModel::roleNames(); - - // This generates role names from the Roles enum in the form of: FooRole -> foo - const QMetaEnum e = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Roles")); - - for (int i = 0; i < e.keyCount(); ++i) { - const int value = e.value(i); - - QByteArray key(e.key(i)); - key[0] = key[0] + 32; // lower case first letter - key.chop(4); // strip "Role" suffix - - s_roles.insert(value, key); - } - - s_roles.insert(IdRole, QByteArrayLiteral("notificationId")); // id is QML-reserved - } - - return s_roles; + return Utils::roleNames(); } diff --git a/libnotificationmanager/notificationsmodel.h b/libnotificationmanager/notificationsmodel.h --- a/libnotificationmanager/notificationsmodel.h +++ b/libnotificationmanager/notificationsmodel.h @@ -45,6 +45,7 @@ QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QHash roleNames() const override; void expire(uint notificationId); void close(uint notificationId); diff --git a/libnotificationmanager/notificationsmodel.cpp b/libnotificationmanager/notificationsmodel.cpp --- a/libnotificationmanager/notificationsmodel.cpp +++ b/libnotificationmanager/notificationsmodel.cpp @@ -23,6 +23,7 @@ #include "debug.h" #include "server.h" +#include "utils_p.h" #include "notifications.h" @@ -350,6 +351,11 @@ return d->notifications.count(); } +QHash NotificationsModel::roleNames() const +{ + return Utils::roleNames(); +} + void NotificationsModel::expire(uint notificationId) { if (d->rowOfNotification(notificationId) > -1) { diff --git a/libnotificationmanager/utils.cpp b/libnotificationmanager/utils.cpp --- a/libnotificationmanager/utils.cpp +++ b/libnotificationmanager/utils.cpp @@ -20,20 +20,51 @@ #include "utils_p.h" +#include "notifications.h" + #include #include #include #include #include #include +#include #include #include #include using namespace NotificationManager; +QHash Utils::roleNames() +{ + static QHash s_roles; + + if (s_roles.isEmpty()) { + // This generates role names from the Roles enum in the form of: FooRole -> foo + const QMetaEnum e = QMetaEnum::fromType(); + + // Qt built-in roles we use + s_roles.insert(Qt::DisplayRole, QByteArrayLiteral("display")); + s_roles.insert(Qt::DecorationRole, QByteArrayLiteral("decoration")); + + for (int i = 0; i < e.keyCount(); ++i) { + const int value = e.value(i); + + QByteArray key(e.key(i)); + key[0] = key[0] + 32; // lower case first letter + key.chop(4); // strip "Role" suffix + + s_roles.insert(value, key); + } + + s_roles.insert(Notifications::IdRole, QByteArrayLiteral("notificationId")); // id is QML-reserved + } + + return s_roles; +} + QString Utils::processNameFromPid(uint pid) { auto processInfo = KProcessList::processInfo(pid); diff --git a/libnotificationmanager/utils_p.h b/libnotificationmanager/utils_p.h --- a/libnotificationmanager/utils_p.h +++ b/libnotificationmanager/utils_p.h @@ -32,6 +32,8 @@ namespace Utils { +QHash roleNames(); + QString processNameFromPid(uint pid); QString desktopEntryFromPid(uint pid);