diff --git a/applets/notifications/package/contents/ui/NotificationDelegate.qml b/applets/notifications/package/contents/ui/NotificationDelegate.qml index 1d2200812..8d39afa64 100644 --- a/applets/notifications/package/contents/ui/NotificationDelegate.qml +++ b/applets/notifications/package/contents/ui/NotificationDelegate.qml @@ -1,173 +1,164 @@ /* * 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: popupFlickable.width - property int layoutSpacing: units.smallSpacing - property int toolIconSize: units.iconSizes.smallMedium - opacity: 1-Math.abs(x)/width enabled: model.hasDefaultAction checked: notificationItem.containsMouse Timer { interval: 10*60*1000 repeat: false running: !idleTimeSource.idle onTriggered: { if (!notificationsModel.inserting) notificationsModel.remove(index) } } MouseArea { width: parent.width height: childrenRect.height + acceptedButtons: Qt.NoButtons 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 } } - onClicked: { - if (model.hasDefaultAction) { - executeAction(model.source, "default") - } else { - notification.close(); - } - } - 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); notificationsModel.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 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 (notificationsModel.count > 1) { removeAnimation.running = true } else { closeNotification(model.source) notificationsModel.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/NotificationItem.qml b/applets/notifications/package/contents/ui/NotificationItem.qml index 29c200015..af0d76344 100644 --- a/applets/notifications/package/contents/ui/NotificationItem.qml +++ b/applets/notifications/package/contents/ui/NotificationItem.qml @@ -1,336 +1,348 @@ /* * Copyright 2011 Marco Martin * Copyright 2014 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 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.1 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 MouseArea { id: notificationItem width: parent.width implicitHeight: Math.max(appIconItem.visible || imageItem.visible ? units.iconSizes.large : 0, mainLayout.height) // We need to clip here because we support displaying images through // and if we don't clip, they will be painted over the borders of the dialog/item clip: true signal close signal configure signal action(string actionId) signal openUrl(url url) property bool compact: false property alias icon: appIconItem.source property alias image: imageItem.image property alias summary: summaryLabel.text property alias body: bodyText.text property alias configurable: settingsButton.visible property var created property var urls: [] property int maximumTextHeight: -1 property ListModel actions: ListModel { } - property bool hasDefaultAction: false; + property bool hasDefaultAction: false + + onClicked: { + // the MEL would close the notification before the action button + // onClicked handler would fire effectively breaking notification actions + if (pressedAction()) { + return + } + + if (hasDefaultAction) { + // the notifications was clicked, trigger the default action if set + action("default") + } + } function pressedAction() { for (var i = 0, count = actionRepeater.count; i < count; ++i) { var item = actionRepeater.itemAt(i) if (item.pressed) { return item } } if (thumbnailStripLoader.item) { var item = thumbnailStripLoader.item.pressedAction() if (item) { return item } } if (settingsButton.pressed) { return settingsButton } if (closeButton.pressed) { return closeButton } return null } function updateTimeLabel() { if (!created || created.getTime() <= 0) { timeLabel.text = "" return } var currentTime = new Date().getTime() var createdTime = created.getTime() var d = (currentTime - createdTime) / 1000 if (d < 10) { timeLabel.text = i18nc("notification was just added, keep short", "Just now") } else if (d < 20) { timeLabel.text = i18nc("10 seconds ago, keep short", "10 s ago"); } else if (d < 40) { timeLabel.text = i18nc("30 seconds ago, keep short", "30 s ago"); } else if (d < 60 * 60) { timeLabel.text = i18ncp("minutes ago, keep short", "%1 min ago", "%1 min ago", Math.round(d / 60)) } else if (d <= 60 * 60 * 23) { timeLabel.text = Qt.formatTime(created, Qt.locale().timeFormat(Locale.ShortFormat).replace(/.ss?/i, "")) } else { var yesterday = new Date() yesterday.setDate(yesterday.getDate() - 1) // this will wrap yesterday.setHours(0) yesterday.setMinutes(0) yesterday.setSeconds(0) if (createdTime > yesterday.getTime()) { timeLabel.text = i18nc("notification was added yesterday, keep short", "Yesterday"); } else { timeLabel.text = i18ncp("notification was added n days ago, keep short", "%1 day ago", "%1 days ago", Math.round((currentTime - yesterday.getTime()) / 1000 / 3600 / 24)); } } } Timer { interval: 15000 running: plasmoid.expanded repeat: true triggeredOnStart: true onTriggered: updateTimeLabel() } PlasmaCore.IconItem { id: appIconItem width: units.iconSizes.large height: units.iconSizes.large anchors { top: parent.top left: parent.left } visible: !imageItem.visible && valid animated: false } QImageItem { id: imageItem anchors.fill: appIconItem smooth: true visible: nativeWidth > 0 } ColumnLayout { id: mainLayout anchors { top: parent.top left: appIconItem.visible || imageItem.visible ? appIconItem.right : parent.left right: parent.right leftMargin: units.smallSpacing } spacing: Math.round(units.smallSpacing / 2) RowLayout { id: titleBar spacing: units.smallSpacing PlasmaExtras.Heading { id: summaryLabel Layout.fillWidth: true Layout.fillHeight: true verticalAlignment: Text.AlignVCenter level: 4 elide: Text.ElideRight wrapMode: Text.NoWrap } PlasmaExtras.Heading { id: timeLabel Layout.fillHeight: true level: 5 visible: text !== "" verticalAlignment: Text.AlignVCenter PlasmaCore.ToolTipArea { anchors.fill: parent subText: Qt.formatDateTime(created, Qt.DefaultLocaleLongDate) } } PlasmaComponents.ToolButton { id: settingsButton width: units.iconSizes.smallMedium height: width visible: false iconSource: "configure" onClicked: configure() } PlasmaComponents.ToolButton { id: closeButton width: units.iconSizes.smallMedium height: width flat: compact iconSource: "window-close" onClicked: close() } } RowLayout { id: bottomPart Layout.alignment: Qt.AlignTop spacing: units.smallSpacing // Force the whole thing to collapse if the children are invisible // If there is a big notification followed by a small one, the height // of the popup does not always shrink back, so this forces it to // height=0 when those are invisible. -1 means "default to implicitHeight" Layout.maximumHeight: bodyText.visible || actionsColumn.visible ? -1 : 0 - MouseArea { - id: contextMouseArea - + PlasmaExtras.ScrollArea { + id: bodyTextScrollArea Layout.alignment: Qt.AlignTop Layout.fillWidth: true implicitHeight: maximumTextHeight > 0 ? Math.min(maximumTextHeight, bodyText.paintedHeight) : bodyText.paintedHeight - - acceptedButtons: Qt.RightButton - preventStealing: true - - onPressed: contextMenu.open(mouse.x, mouse.y) - - PlasmaComponents.ContextMenu { - id: contextMenu - visualParent: contextMouseArea - - PlasmaComponents.MenuItem { - text: i18n("Copy") - onClicked: bodyText.copy() + visible: bodyText.length > 0 + + flickableItem.boundsBehavior: Flickable.StopAtBounds + flickableItem.flickableDirection: Flickable.VerticalFlick + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + TextEdit { + id: bodyText + width: bodyTextScrollArea.width + enabled: !Settings.isMobile + + color: PlasmaCore.ColorScope.textColor + selectedTextColor: theme.viewBackgroundColor + selectionColor: theme.viewFocusColor + font.capitalization: theme.defaultFont.capitalization + font.family: theme.defaultFont.family + font.italic: theme.defaultFont.italic + font.letterSpacing: theme.defaultFont.letterSpacing + font.pointSize: theme.defaultFont.pointSize + font.strikeout: theme.defaultFont.strikeout + font.underline: theme.defaultFont.underline + font.weight: theme.defaultFont.weight + font.wordSpacing: theme.defaultFont.wordSpacing + renderType: Text.NativeRendering + selectByMouse: true + readOnly: true + wrapMode: Text.Wrap + textFormat: TextEdit.RichText + + onLinkActivated: Qt.openUrlExternally(link) + + // ensure selecting text scrolls the view as needed... + onCursorRectangleChanged: { + var flick = bodyTextScrollArea.flickableItem + if (flick.contentY >= cursorRectangle.y) { + flick.contentY = cursorRectangle.y + } else if (flick.contentY + flick.height <= cursorRectangle.y + cursorRectangle.height) { + flick.contentY = cursorRectangle.y + cursorRectangle.height - flick.height + } } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + + onClicked: { + if (mouse.button == Qt.RightButton) + contextMenu.open(mouse.x, mouse.y) + else { + notificationItem.clicked(mouse) + } + } - PlasmaComponents.MenuItem { - text: i18n("Select All") - onClicked: bodyText.selectAll() - } - } + PlasmaComponents.ContextMenu { + id: contextMenu + visualParent: parent - PlasmaExtras.ScrollArea { - id: bodyTextScrollArea - anchors.fill: parent - visible: bodyText.length > 0 - - flickableItem.boundsBehavior: Flickable.StopAtBounds - flickableItem.flickableDirection: Flickable.VerticalFlick - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - TextEdit { - id: bodyText - width: bodyTextScrollArea.width - enabled: !Settings.isMobile - - color: PlasmaCore.ColorScope.textColor - selectedTextColor: theme.viewBackgroundColor - selectionColor: theme.viewFocusColor - font.capitalization: theme.defaultFont.capitalization - font.family: theme.defaultFont.family - font.italic: theme.defaultFont.italic - font.letterSpacing: theme.defaultFont.letterSpacing - font.pointSize: theme.defaultFont.pointSize - font.strikeout: theme.defaultFont.strikeout - font.underline: theme.defaultFont.underline - font.weight: theme.defaultFont.weight - font.wordSpacing: theme.defaultFont.wordSpacing - renderType: Text.NativeRendering - selectByMouse: true - readOnly: true - wrapMode: Text.Wrap - textFormat: TextEdit.RichText - - onLinkActivated: Qt.openUrlExternally(link) - - // ensure selecting text scrolls the view as needed... - onCursorRectangleChanged: { - var flick = bodyTextScrollArea.flickableItem - if (flick.contentY >= cursorRectangle.y) { - flick.contentY = cursorRectangle.y - } else if (flick.contentY + flick.height <= cursorRectangle.y + cursorRectangle.height) { - flick.contentY = cursorRectangle.y + cursorRectangle.height - flick.height + PlasmaComponents.MenuItem { + text: i18n("Copy") + onClicked: { + bodyText.selectAll() + bodyText.copy() + } } } } } } ColumnLayout { id: actionsColumn Layout.alignment: Qt.AlignTop Layout.maximumWidth: theme.mSize(theme.defaultFont).width * (compact ? 10 : 16) // this is so it never collapses but always follows what the Buttons below want // but also don't let the buttons get too narrow (e.g. "View" or "Open" button) Layout.minimumWidth: Math.max(units.gridUnit * 4, implicitWidth) spacing: units.smallSpacing visible: notificationItem.actions && notificationItem.actions.count > 0 Repeater { id: actionRepeater model: notificationItem.actions PlasmaComponents.Button { Layout.fillWidth: true Layout.preferredWidth: minimumWidth Layout.maximumWidth: actionsColumn.Layout.maximumWidth text: model.text onClicked: notificationItem.action(model.id) } } } } Loader { id: thumbnailStripLoader Layout.fillWidth: true Layout.preferredHeight: item ? item.implicitHeight : 0 source: "ThumbnailStrip.qml" active: notificationItem.urls.length > 0 } } } diff --git a/applets/notifications/package/contents/ui/NotificationPopup.qml b/applets/notifications/package/contents/ui/NotificationPopup.qml index 442bbb89c..76ae70c37 100644 --- a/applets/notifications/package/contents/ui/NotificationPopup.qml +++ b/applets/notifications/package/contents/ui/NotificationPopup.qml @@ -1,150 +1,130 @@ /* * Copyright 2014 Martin Klapetek * * 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 QtQuick.Layouts 1.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons PlasmaCore.Dialog { id: notificationPopup location: PlasmaCore.Types.Floating type: PlasmaCore.Dialog.Notification flags: Qt.WindowDoesNotAcceptFocus property var notificationProperties: ({}) signal notificationTimeout() onVisibleChanged: { if (!visible) { notificationTimer.stop(); } } onYChanged: { if (visible) { notificationTimer.restart(); } } function populatePopup(notification) { notificationProperties = notification notificationTimer.interval = notification.expireTimeout notificationTimer.restart() // notification.actions is a JS array, but we can easily append that to our model notificationItem.actions.clear() notificationItem.actions.append(notificationProperties.actions) } function clearPopup() { notificationProperties = {} notificationItem.actions.clear() } Behavior on y { NumberAnimation { duration: units.longDuration easing.type: Easing.OutQuad } } - mainItem: KQuickControlsAddons.MouseEventListener { - id: root + mainItem: NotificationItem { + id: notificationItem + hoverEnabled: true + LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true - width: notificationItem.width - height: notificationItem.implicitHeight - - hoverEnabled: true - - onClicked: { - // the MEL would close the notification before the action button - // onClicked handler would fire effectively breaking notification actions - if (notificationItem.pressedAction()) { - return - } + height: implicitHeight - if (notificationItem.hasDefaultAction) { - // the notifications was clicked, trigger the default action if set - notificationItem.action("default") - notificationItem.close() + Timer { + id: notificationTimer + onTriggered: { + if (!notificationProperties.isPersistent) { + expireNotification(notificationProperties.source) + } + notificationPopup.notificationTimeout(); } } onContainsMouseChanged: { if (containsMouse) { notificationTimer.stop() } else if (!containsMouse && visible) { notificationTimer.restart() } } - Timer { - id: notificationTimer - onTriggered: { - if (!notificationProperties.isPersistent) { - expireNotification(notificationProperties.source) - } - notificationPopup.notificationTimeout(); - } + summary: notificationProperties.summary || "" + body: notificationProperties.body || "" + icon: notificationProperties.appIcon || "" + image: notificationProperties.image + // explicit true/false or else it complains about assigning undefined to bool + configurable: notificationProperties.configurable && !Settings.isMobile ? true : false + urls: notificationProperties.urls || [] + hasDefaultAction: notificationProperties.hasDefaultAction || false + + width: Math.round(23 * units.gridUnit) + maximumTextHeight: theme.mSize(theme.defaultFont).height * 10 + + onClose: { + closeNotification(notificationProperties.source) + // the popup will be closed in response to sourceRemoved } - - NotificationItem { - id: notificationItem - - summary: notificationProperties.summary || "" - body: notificationProperties.body || "" - icon: notificationProperties.appIcon || "" - image: notificationProperties.image - // explicit true/false or else it complains about assigning undefined to bool - configurable: notificationProperties.configurable && !Settings.isMobile ? true : false - urls: notificationProperties.urls || [] - hasDefaultAction: notificationProperties.hasDefaultAction || false - - width: Math.round(23 * units.gridUnit) - maximumTextHeight: theme.mSize(theme.defaultFont).height * 10 - - onClose: { - closeNotification(notificationProperties.source) - // the popup will be closed in response to sourceRemoved - } - onConfigure: { - configureNotification(notificationProperties.appRealName, notificationProperties.eventId) - notificationPositioner.closePopup(notificationProperties.source); - } - onAction: { - executeAction(notificationProperties.source, actionId) - actions.clear() - } - onOpenUrl: { - Qt.openUrlExternally(url) - notificationPositioner.closePopup(notificationProperties.source); - } + onConfigure: { + configureNotification(notificationProperties.appRealName, notificationProperties.eventId) + notificationPositioner.closePopup(notificationProperties.source); + } + onAction: { + executeAction(notificationProperties.source, actionId) + actions.clear() + } + onOpenUrl: { + Qt.openUrlExternally(url) + notificationPositioner.closePopup(notificationProperties.source); } } - } diff --git a/applets/notifications/package/contents/ui/Notifications.qml b/applets/notifications/package/contents/ui/Notifications.qml index b8b796513..2b09a0abc 100644 --- a/applets/notifications/package/contents/ui/Notifications.qml +++ b/applets/notifications/package/contents/ui/Notifications.qml @@ -1,222 +1,220 @@ /* * 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 org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.private.notifications 1.0 Column { id: notificationsRoot anchors { left: parent.left right: parent.right } property QtObject notificationPopup property alias count: notificationsRepeater.count 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; } 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() } Component { id: notificationPopupComponent NotificationPopup { } } ListModel { id: notificationsModel 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 if (data["actions"] && data["actions"].length % 2 == 0) { for (var i = 0; i < data["actions"].length; i += 2) { if (data["actions"][i] == "default") { // The default action is not shown, but we want to know it's there _data["hasDefaultAction"] = 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); } } Repeater { id: notificationsRepeater model: notificationsModel - delegate: NotificationDelegate { - toolIconSize: notificationsApplet.toolIconSize - } + delegate: NotificationDelegate {} } } diff --git a/applets/notifications/package/contents/ui/main.qml b/applets/notifications/package/contents/ui/main.qml index 873f1875a..203ddd80a 100644 --- a/applets/notifications/package/contents/ui/main.qml +++ b/applets/notifications/package/contents/ui/main.qml @@ -1,193 +1,192 @@ /*************************************************************************** * Copyright 2011 Davide Bettio * * 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 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 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.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kquickcontrolsaddons 2.0 import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.plasma.private.notifications 1.0 import "uiproperties.js" as UiProperties MouseEventListener { id: notificationsApplet //width: units.gridUnit.width * 10 //height: units.gridUnit.width * 15 //Layout.minimumWidth: mainScrollArea.implicitWidth //Layout.minimumHeight: mainScrollArea.implicitHeight Layout.minimumWidth: 256 // FIXME: use above Layout.minimumHeight: 256 Layout.maximumWidth: -1 Layout.maximumHeight: mainScrollArea.implicitHeight LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true - property int toolIconSize: UiProperties.toolIconSize property int layoutSpacing: UiProperties.layoutSpacing property real globalProgress: 0 property Item notifications: notificationsLoader.item property Item jobs: jobsLoader.item //notifications + jobs property int totalCount: (notifications ? notifications.count : 0) + (jobs ? jobs.count : 0) property Item notificationIcon Plasmoid.switchWidth: units.gridUnit * 20 Plasmoid.switchHeight: units.gridUnit * 30 Plasmoid.status: totalCount > 0 ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.PassiveStatus Plasmoid.icon: { if (jobs && jobs.count) { return "notification-active" } return totalCount ? "notification-inactive" : "notification-disabled" } Plasmoid.toolTipSubText: { if (totalCount == 0) { return i18n("No notifications or jobs") } else if (!notifications || !notifications.count) { return i18np("%1 running job", "%1 running jobs", jobs.count) } else if (!jobs || !jobs.count) { return i18np("%1 notification", "%1 notifications", notifications.count) } else { return i18np("%1 running job", "%1 running jobs", jobs.count) + "\n" + i18np("%1 notification", "%1 notifications", notifications.count) } } Plasmoid.compactRepresentation: Component { NotificationIcon { id: notificationIcon Component.onCompleted: notificationsApplet.notificationIcon = notificationIcon } } state: "default" hoverEnabled: !UiProperties.touchInput onTotalCountChanged: { print(" totalCountChanged " + totalCount) if (totalCount > 0) { state = "new-notifications" } else { state = "default" //plasmoid.hidePopup() plasmoid.expanded = false; } } PlasmaCore.Svg { id: configIconsSvg imagePath: "widgets/configuration-icons" } PlasmaExtras.Heading { width: parent.width level: 3 opacity: 0.6 visible: notificationsApplet.totalCount == 0 text: i18n("No new notifications.") } PlasmaExtras.ScrollArea { id: mainScrollArea anchors.fill: parent implicitWidth: theme.mSize(theme.defaultFont).width * 40 implicitHeight: Math.min(theme.mSize(theme.defaultFont).height * 40, Math.max(theme.mSize(theme.defaultFont).height * 6, contentsColumn.height)) state: "" Flickable { id: popupFlickable anchors.fill:parent contentWidth: width contentHeight: contentsColumn.height clip: true Column { id: contentsColumn width: popupFlickable.width Loader { id: jobsLoader width: parent.width source: "Jobs.qml" active: notificationsApplet.Plasmoid.configuration.showJobs } Loader { id: notificationsLoader width: parent.width source: "Notifications.qml" active: notificationsApplet.Plasmoid.configuration.showNotifications } } } states: [ State { name: "underMouse" when: notificationsApplet.containsMouse PropertyChanges { target: mainScrollArea implicitHeight: implicitHeight } }, State { name: "" when: !notificationsApplet.containsMouse PropertyChanges { target: mainScrollArea implicitHeight: Math.min(theme.mSize(theme.defaultFont).height * 40, Math.max(theme.mSize(theme.defaultFont).height * 6, contentsColumn.height)) } } ] } function action_clearNotifications() { notifications.clearNotifications() } function action_notificationskcm() { ProcessRunner.runNotificationsKCM() } Component.onCompleted: { plasmoid.setAction("clearNotifications", i18n("Clear Notifications"), "edit-clear") var clearAction = plasmoid.action("clearNotifications"); clearAction.visible = Qt.binding(function() { return notificationsApplet.notifications && notificationsApplet.notifications.count > 0 }) //var allApplications = new Object plasmoid.setAction("notificationskcm", i18n("&Configure Event Notifications and Actions..."), "preferences-desktop-notification") } }