diff --git a/applets/notifications/package/contents/ui/NotificationDelegate.qml b/applets/notifications/package/contents/ui/NotificationDelegate.qml index ba60bdf80..d9ac22a05 100644 --- a/applets/notifications/package/contents/ui/NotificationDelegate.qml +++ b/applets/notifications/package/contents/ui/NotificationDelegate.qml @@ -1,167 +1,167 @@ /* * Copyright 2011 Marco Martin * * 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 import QtQuick.Controls.Private 1.0 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 PlasmaComponents.ListItem { id: notificationItem width: historyList.width property ListModel listModel; opacity: 1-Math.abs(x)/width enabled: model.hasDefaultAction checked: notificationItem.containsMouse Timer { interval: 10*60*1000 repeat: false running: !idleTimeSource.idle onTriggered: { if (!listModel.inserting) listModel.remove(index) } } MouseArea { width: parent.width height: childrenRect.height - acceptedButtons: Qt.NoButtons + acceptedButtons: Qt.NoButton drag { target: notificationItem axis: Drag.XAxis //kind of an hack over Column being too smart minimumX: -parent.width + 1 maximumX: parent.width - 1 } onReleased: { if (notificationItem.x < -notificationItem.width/2) { removeAnimation.exitFromRight = false removeAnimation.running = true } else if (notificationItem.x > notificationItem.width/2 ) { removeAnimation.exitFromRight = true removeAnimation.running = true } else { resetAnimation.running = true } } SequentialAnimation { id: removeAnimation property bool exitFromRight: true NumberAnimation { target: notificationItem properties: "x" to: removeAnimation.exitFromRight ? notificationItem.width-1 : 1-notificationItem.width duration: units.longDuration easing.type: Easing.InOutQuad } NumberAnimation { target: notificationItem properties: "height" to: 0 duration: units.longDuration easing.type: Easing.InOutQuad } ScriptAction { script: { closeNotification(model.source); listModel.remove(index); } } } SequentialAnimation { id: resetAnimation NumberAnimation { target: notificationItem properties: "x" to: 0 duration: units.longDuration easing.type: Easing.InOutQuad } } NotificationItem { id: notification width: parent.width compact: true icon: appIcon image: model.image summary: model.summary body: model.body configurable: model.configurable && !Settings.isMobile // model.actions JS array is implicitly turned into a ListModel which we can assign directly actions: model.actions created: model.created hasDefaultAction: model.hasDefaultAction hasConfigureAction: model.hasConfigureAction urls: { // QML ListModel tries to be smart and turns our urls Array into a dict with index as key... var urls = [] var modelUrls = model.urls if (modelUrls) { for (var key in modelUrls) { urls.push(modelUrls[key]) } } return urls } onClose: { if (listModel.count > 1) { removeAnimation.running = true } else { closeNotification(model.source) listModel.remove(index) } } onConfigure: { plasmoid.expanded = false configureNotification(model.appRealName, model.eventId) } onAction: { executeAction(model.source, actionId) actions.clear() } onOpenUrl: { plasmoid.expanded = false Qt.openUrlExternally(url) } } } //MouseArea Component.onCompleted: { mainScrollArea.height = mainScrollArea.implicitHeight } Component.onDestruction: { mainScrollArea.height = mainScrollArea.implicitHeight } } diff --git a/applets/notifications/package/contents/ui/Notifications.qml b/applets/notifications/package/contents/ui/Notifications.qml index 1781185c0..2ebf61778 100644 --- a/applets/notifications/package/contents/ui/Notifications.qml +++ b/applets/notifications/package/contents/ui/Notifications.qml @@ -1,321 +1,322 @@ /* * Copyright 2012 Marco Martin * * 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 import QtQuick.Layouts 1.2 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.plasma.private.notifications 1.0 Column { id: notificationsRoot anchors { left: parent.left right: parent.right } property alias count: notificationsRepeater.count readonly property int historyCount: historyList.count property bool showHistory: plasmoid.configuration.showHistory signal popupShown(var popup) onShowHistoryChanged: { if(!showHistory) clearHistory() } Component.onCompleted: { // Create the popup components and pass them to the C++ plugin for (var i = 0; i < 3; i++) { var popup = notificationPopupComponent.createObject(); notificationPositioner.addNotificationPopup(popup); } } function addNotification(notification) { // Do not show duplicated notifications for (var i = 0; i < notificationsModel.count; ++i) { if (notificationsModel.get(i).source == notification.source && notificationsModel.get(i).appName == notification.appName && notificationsModel.get(i).summary == notification.summary && notificationsModel.get(i).body == notification.body) { return } } for (var i = 0; i < notificationsModel.count; ++i) { if (notificationsModel.get(i).source == notification.source || (notificationsModel.get(i).appName == notification.appName && notificationsModel.get(i).summary == notification.summary && notificationsModel.get(i).body == notification.body)) { notificationsModel.remove(i) break } } if (notificationsModel.count > 20) { notificationsModel.remove(notificationsModel.count-1) } if (notification.isPersistent) { notification.created = new Date(); notificationsModel.inserting = true; notificationsModel.insert(0, notification); notificationsModel.inserting = false; } else if (showHistory) { notificationsHistoryModel.inserting = true; //create a copy of the notification. //Disable actions in this copy as they will stop working once the original notification is closed. notificationsHistoryModel.insert(0, { "compact" : notification.compact, "icon" : notification.icon, "image" : notification.image, "summary" : notification.summary, "body" : notification.body, "configurable" : false, "created" : new Date(), "urls" : notification.urls, "maximumTextHeight" : notification.maximumTextHeight, "actions" : null, "hasDefaultAction" : false, "hasConfigureAction" : false, }); notificationsHistoryModel.inserting = false; } notificationPositioner.displayNotification(notification); } function executeAction(source, id) { //try to use the service if (id.indexOf("jobUrl#") === -1) { var service = notificationsSource.serviceForSource(source) var op = service.operationDescription("invokeAction") op["actionId"] = id service.startOperationCall(op) //try to open the id as url } else if (id.indexOf("jobUrl#") !== -1) { Qt.openUrlExternally(id.slice(7)); } notificationPositioner.closePopup(source); } function configureNotification(appRealName, eventId) { var service = notificationsSource.serviceForSource("notification") var op = service.operationDescription("configureNotification") op.appRealName = appRealName op.eventId = eventId service.startOperationCall(op) } function createNotification(data) { var service = notificationsSource.serviceForSource("notification"); var op = service.operationDescription("createNotification"); // add everything from "data" to "op" for (var attrname in data) { op[attrname] = data[attrname]; } service.startOperationCall(op); } function closeNotification(source) { var service = notificationsSource.serviceForSource(source) var op = service.operationDescription("userClosed") service.startOperationCall(op) } function expireNotification(source) { var service = notificationsSource.serviceForSource(source) var op = service.operationDescription("expireNotification") service.startOperationCall(op) } function clearNotifications() { for (var i = 0, length = notificationsSource.sources.length; i < length; ++i) { var source = notificationsSource.sources[i]; closeNotification(source) notificationPositioner.closePopup(source); } notificationsModel.clear() clearHistory() } function clearHistory() { notificationsHistoryModel.clear() } Component { id: notificationPopupComponent NotificationPopup { } } ListModel { id: notificationsModel property bool inserting: false } ListModel { id: notificationsHistoryModel property bool inserting: false } PlasmaCore.DataSource { id: idleTimeSource property bool idle: data["UserActivity"]["IdleTime"] > 300000 engine: "powermanagement" interval: 30000 connectedSources: ["UserActivity"] //Idle with more than 5 minutes of user inactivity } PlasmaCore.DataSource { id: notificationsSource engine: "notifications" interval: 0 onSourceAdded: { connectSource(source); } onSourceRemoved: { notificationPositioner.closePopup(source); for (var i = 0; i < notificationsModel.count; ++i) { if (notificationsModel.get(i).source == source) { notificationsModel.remove(i) break } } } onNewData: { var _data = data; // Temp copy to avoid lots of context switching var actions = [] _data["hasDefaultAction"] = false + _data["hasConfigureAction"] = false; if (data["actions"] && data["actions"].length % 2 == 0) { for (var i = 0; i < data["actions"].length; i += 2) { var action = data["actions"][i] if (action == "default") { // The default action is not shown, but we want to know it's there _data["hasDefaultAction"] = true } else if (action == "settings") { // configure icon in the notification for custom notification settings _data["hasConfigureAction"] = true; _data["configurable"] = true; } else { actions.push({ id: data["actions"][i], text: data["actions"][i+1] }) } } } _data["source"] = sourceName _data["actions"] = actions notificationsRoot.addNotification(_data) } } Connections { target: plasmoid.nativeInterface onAvailableScreenRectChanged: { notificationPositioner.setPlasmoidScreenGeometry(availableScreenRect); } } NotificationsHelper { id: notificationPositioner popupLocation: plasmoid.nativeInterface.screenPosition Component.onCompleted: { notificationPositioner.setPlasmoidScreenGeometry(plasmoid.nativeInterface.availableScreenRect); } onPopupShown: notificationsRoot.popupShown(popup) } Repeater { id: notificationsRepeater model: notificationsModel delegate: NotificationDelegate { listModel: notificationsModel } } RowLayout { Layout.fillWidth: true spacing: units.smallSpacing visible: historyCount > 0 width: parent.width PlasmaExtras.Heading { Layout.fillWidth: true level: 3 opacity: 0.6 text: i18n("History") } PlasmaComponents.ToolButton { Layout.rightMargin: spacerSvgFrame.margins.right iconSource: "edit-delete" tooltip: i18n("Clear History") onClicked: clearHistory() } } // This hack is unfortunately needed to have the buttons align, // the ones in the list contain have a margin due to a frame for being a list item. PlasmaCore.FrameSvgItem { id : spacerSvgFrame imagePath: "widgets/listitem" prefix: "normal" visible: false } // History stuff // The history is shown outside in a ListView Binding { target: historyList property: "model" value: notificationsHistoryModel when: showHistory } Binding { target: historyList property: "delegate" value: notificationsHistoryDelegate when: showHistory } Component { id: notificationsHistoryDelegate NotificationDelegate { listModel: notificationsHistoryModel } } }