diff --git a/applets/notifications/package/contents/ui/EditContextMenu.qml b/applets/notifications/package/contents/ui/EditContextMenu.qml index 7d31d8ff4..f39397f05 100644 --- a/applets/notifications/package/contents/ui/EditContextMenu.qml +++ b/applets/notifications/package/contents/ui/EditContextMenu.qml @@ -1,78 +1,78 @@ /* * Copyright 2019 Kai Uwe Broulik * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see */ import QtQuick 2.8 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kquickcontrolsaddons 2.0 as KQCAddons PlasmaComponents.ContextMenu { id: contextMenu signal closed property QtObject __clipboard: KQCAddons.Clipboard { } // can be a Text or TextEdit property Item target property string link onStatusChanged: { if (status === PlasmaComponents.DialogStatus.Closed) { closed(); } } PlasmaComponents.MenuItem { - text: i18n("Copy Link Address") + text: i18nd("plasma_applet_org.kde.plasma.notifications", "Copy Link Address") onClicked: __clipboard.content = contextMenu.link visible: contextMenu.link !== "" } PlasmaComponents.MenuItem { separator: true visible: contextMenu.link !== "" } PlasmaComponents.MenuItem { - text: i18n("Copy") + text: i18nd("plasma_applet_org.kde.plasma.notifications", "Copy") icon: "edit-copy" enabled: typeof target.selectionStart !== "undefined" ? target.selectionStart !== target.selectionEnd : (target.text || "").length > 0 onClicked: { if (typeof target.copy === "function") { target.copy(); } else { __clipboard.content = target.text; } } } PlasmaComponents.MenuItem { id: selectAllAction - text: i18n("Select All") + text: i18nd("plasma_applet_org.kde.plasma.notifications", "Select All") onClicked: target.selectAll() visible: typeof target.selectAll === "function" } } diff --git a/applets/notifications/package/contents/ui/JobDetails.qml b/applets/notifications/package/contents/ui/JobDetails.qml index b56b1e21d..e161b19bf 100644 --- a/applets/notifications/package/contents/ui/JobDetails.qml +++ b/applets/notifications/package/contents/ui/JobDetails.qml @@ -1,153 +1,153 @@ /* * Copyright 2019 Kai Uwe Broulik * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see */ import QtQuick 2.8 import QtQuick.Layouts 1.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kcoreaddons 1.0 as KCoreAddons import org.kde.notificationmanager 1.0 as NotificationManager GridLayout { id: detailsGrid property QtObject jobDetails columns: 2 rowSpacing: Math.round(units.smallSpacing / 2) columnSpacing: units.smallSpacing // once you use Layout.column/Layout.row *all* of the items in the Layout have to use them Repeater { model: [1, 2] PlasmaExtras.DescriptiveLabel { Layout.column: 0 Layout.row: index Layout.alignment: Qt.AlignTop | Qt.AlignRight text: jobDetails["descriptionLabel" + modelData] && jobDetails["descriptionValue" + modelData] - ? i18nc("Row description, e.g. Source", "%1:", jobDetails["descriptionLabel" + modelData]) : "" + ? i18ndc("plasma_applet_org.kde.plasma.notifications", "Row description, e.g. Source", "%1:", jobDetails["descriptionLabel" + modelData]) : "" font: theme.smallestFont textFormat: Text.PlainText visible: text !== "" } } Repeater { model: [1, 2] PlasmaExtras.DescriptiveLabel { id: descriptionValueLabel Layout.column: 1 Layout.row: index Layout.fillWidth: true font: theme.smallestFont elide: Text.ElideMiddle textFormat: Text.PlainText wrapMode: Text.WrapAtWordBoundaryOrAnywhere maximumLineCount: 5 visible: text !== "" Component.onCompleted: bindText() function bindText() { text = Qt.binding(function() { return jobDetails["descriptionLabel" + modelData] && jobDetails["descriptionValue" + modelData] ? jobDetails["descriptionValue" + modelData] : ""; }); } MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onPressed: { // break binding so it doesn't update while the menu is opened descriptionValueLabel.text = descriptionValueLabel.text; descriptionValueMenu.open(mouse.x, mouse.y) } } EditContextMenu { id: descriptionValueMenu target: descriptionValueLabel // defer re-binding until after the "Copy" action in the menu has triggered onClosed: Qt.callLater(descriptionValueLabel.bindText) } } } Repeater { model: ["Bytes", "Files", "Directories"] PlasmaExtras.DescriptiveLabel { Layout.column: 1 Layout.row: 2 + index Layout.fillWidth: true text: { var processed = jobDetails["processed" + modelData]; var total = jobDetails["total" + modelData]; if (processed > 0 || total > 1) { if (processed > 0 && total > 0 && processed <= total) { switch(modelData) { case "Bytes": - return i18nc("How many bytes have been copied", "%2 of %1", + return i18ndc("plasma_applet_org.kde.plasma.notifications", "How many bytes have been copied", "%2 of %1", KCoreAddons.Format.formatByteSize(total), KCoreAddons.Format.formatByteSize(processed)) case "Files": - return i18ncp("How many files have been copied", "%2 of %1 file", "%2 of %1 files", + return i18ndcp("plasma_applet_org.kde.plasma.notifications", "How many files have been copied", "%2 of %1 file", "%2 of %1 files", total, processed); case "Directories": - return i18ncp("How many dirs have been copied", "%2 of %1 folder", "%2 of %1 folders", + return i18ndcp("plasma_applet_org.kde.plasma.notifications", "How many dirs have been copied", "%2 of %1 folder", "%2 of %1 folders", total, processed); } } else { switch(modelData) { case "Bytes": return KCoreAddons.Format.formatByteSize(processed || total) case "Files": - return i18np("%1 file", "%1 files", (processed || total)); + return i18ndp("plasma_applet_org.kde.plasma.notifications", "%1 file", "%1 files", (processed || total)); case "Directories": - return i18np("%1 folder", "%1 folders", (processed || total)); + return i18ndp("plasma_applet_org.kde.plasma.notifications", "%1 folder", "%1 folders", (processed || total)); } } } return ""; } font: theme.smallestFont textFormat: Text.PlainText visible: text !== "" } } PlasmaExtras.DescriptiveLabel { Layout.column: 1 Layout.row: 2 + 3 Layout.fillWidth: true - text: jobDetails.speed > 0 ? i18nc("Bytes per second", "%1/s", + text: jobDetails.speed > 0 ? i18ndc("plasma_applet_org.kde.plasma.notifications", "Bytes per second", "%1/s", KCoreAddons.Format.formatByteSize(jobDetails.speed)) : "" font: theme.smallestFont textFormat: Text.PlainText visible: text !== "" } } diff --git a/applets/notifications/package/contents/ui/JobItem.qml b/applets/notifications/package/contents/ui/JobItem.qml index 2d8a67d6e..f31af3ebd 100644 --- a/applets/notifications/package/contents/ui/JobItem.qml +++ b/applets/notifications/package/contents/ui/JobItem.qml @@ -1,200 +1,202 @@ /* * Copyright 2019 Kai Uwe Broulik * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see */ import QtQuick 2.8 import QtQuick.Window 2.2 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.notificationmanager 1.0 as NotificationManager import org.kde.plasma.private.notifications 2.0 as Notifications ColumnLayout { id: jobItem property int jobState property int jobError property alias percentage: progressBar.value property alias suspendable: suspendButton.visible property alias killable: killButton.visible property bool hovered property QtObject jobDetails // TOOD make an alias on visible if we're not doing an animation property bool showDetails readonly property alias menuOpen: otherFileActionsMenu.visible signal suspendJobClicked signal resumeJobClicked signal killJobClicked signal openUrl(string url) signal fileActionInvoked spacing: 0 RowLayout { id: progressRow Layout.fillWidth: true spacing: units.smallSpacing PlasmaComponents.ProgressBar { id: progressBar Layout.fillWidth: true minimumValue: 0 maximumValue: 100 // TODO do we actually need the window visible check? perhaps I do because it can be in popup or expanded plasmoid indeterminate: visible && Window.window && Window.window.visible && percentage < 1 && jobItem.jobState === NotificationManager.Notifications.JobStateRunning // is this too annoying? && (jobItem.jobDetails.processedBytes === 0 || jobItem.jobDetails.totalBytes === 0) && jobItem.jobDetails.processedFiles === 0 //&& jobItem.jobDetails.processedDirectories === 0 } RowLayout { spacing: 0 PlasmaComponents.ToolButton { id: suspendButton - tooltip: i18nc("Pause running job", "Pause") + tooltip: i18ndc("plasma_applet_org.kde.plasma.notifications", "Pause running job", "Pause") iconSource: "media-playback-pause" onClicked: jobItem.jobState === NotificationManager.Notifications.JobStateSuspended ? jobItem.resumeJobClicked() : jobItem.suspendJobClicked() } PlasmaComponents.ToolButton { id: killButton - tooltip: i18nc("Cancel running job", "Cancel") + tooltip: i18ndc("plasma_applet_org.kde.plasma.notifications", "Cancel running job", "Cancel") iconSource: "media-playback-stop" onClicked: jobItem.killJobClicked() } PlasmaComponents.ToolButton { id: expandButton Layout.leftMargin: units.smallSpacing iconSource: checked ? "arrow-down" : (LayoutMirroring.enabled ? "arrow-left" : "arrow-right") - tooltip: checked ? i18nc("A button tooltip; hides item details", "Hide Details") - : i18nc("A button tooltip; expands the item to show details", "Show Details") + tooltip: checked ? i18ndc("plasma_applet_org.kde.plasma.notifications", "A button tooltip; hides item details", "Hide Details") + : i18ndc("plasma_applet_org.kde.plasma.notifications", "A button tooltip; expands the item to show details", "Show Details") checkable: true enabled: jobItem.jobDetails && jobItem.jobDetails.hasDetails } } } Loader { Layout.fillWidth: true active: expandButton.checked // Loader doesn't reset its height when unloaded, just hide it altogether visible: active sourceComponent: JobDetails { jobDetails: jobItem.jobDetails } } Flow { // it's a Flow so it can wrap if too long id: jobDoneActions Layout.fillWidth: true spacing: units.smallSpacing // We want the actions to be right-aligned but Flow also reverses // the order of items, so we put them in reverse order layoutDirection: Qt.RightToLeft visible: url && url.toString() !== "" property var url: { if (jobItem.jobState !== NotificationManager.Notifications.JobStateStopped || jobItem.jobError || !jobItem.jobDetails || jobItem.jobDetails.totalFiles <= 0) { return null; } // For a single file show actions for it if (jobItem.jobDetails.totalFiles === 1) { return jobItem.jobDetails.descriptionUrl; } else { return jobItem.jobDetails.destUrl; } } PlasmaComponents.Button { id: otherFileActionsButton height: Math.max(implicitHeight, openButton.implicitHeight) iconName: "application-menu" - tooltip: i18n("More Options...") + tooltip: i18nd("plasma_applet_org.kde.plasma.notifications", "More Options...") checkable: true onPressedChanged: { if (pressed) { checked = Qt.binding(function() { return otherFileActionsMenu.visible; }); otherFileActionsMenu.open(-1, -1); } } Notifications.FileMenu { id: otherFileActionsMenu url: jobDoneActions.url || "" visualParent: otherFileActionsButton onActionTriggered: jobItem.fileActionInvoked() } } PlasmaComponents.Button { id: openButton height: Math.max(implicitHeight, otherFileActionsButton.implicitHeight) // would be nice to have the file icon here? - text: jobItem.jobDetails && jobItem.jobDetails.totalFiles > 1 ? i18n("Open Containing Folder") : i18n("Open") + text: jobItem.jobDetails && jobItem.jobDetails.totalFiles > 1 + ? i18nd("plasma_applet_org.kde.plasma.notifications", "Open Containing Folder") + : i18nd("plasma_applet_org.kde.plasma.notifications", "Open") onClicked: jobItem.openUrl(jobDoneActions.url) width: minimumWidth } } states: [ State { when: jobItem.jobState === NotificationManager.Notifications.JobStateSuspended PropertyChanges { target: suspendButton - tooltip: i18nc("Resume paused job", "Resume") + tooltip: i18ndc("plasma_applet_org.kde.plasma.notifications", "Resume paused job", "Resume") iconSource: "media-playback-start" } PropertyChanges { target: progressBar enabled: false } }, State { when: jobItem.jobState === NotificationManager.Notifications.JobStateStopped PropertyChanges { target: progressRow visible: false } PropertyChanges { target: expandButton checked: false } } ] } diff --git a/applets/notifications/package/contents/ui/NotificationHeader.qml b/applets/notifications/package/contents/ui/NotificationHeader.qml index 50cc95647..79ca09dbe 100644 --- a/applets/notifications/package/contents/ui/NotificationHeader.qml +++ b/applets/notifications/package/contents/ui/NotificationHeader.qml @@ -1,217 +1,219 @@ /* * Copyright 2018-2019 Kai Uwe Broulik * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see */ import QtQuick 2.8 import QtQuick.Layouts 1.1 import QtQuick.Window 2.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.notificationmanager 1.0 as NotificationManager import org.kde.kcoreaddons 1.0 as KCoreAddons import "global" RowLayout { id: notificationHeading property bool inGroup property int notificationType property var applicationIconSource property string applicationName property string originName property string configureActionLabel property alias configurable: configureButton.visible property alias dismissable: dismissButton.visible property bool dismissed property alias closeButtonTooltip: closeButton.tooltip property alias closable: closeButton.visible property var time property int jobState property QtObject jobDetails signal configureClicked signal dismissClicked signal closeClicked // notification created/updated time changed onTimeChanged: updateAgoText() function updateAgoText() { ageLabel.agoText = ageLabel.generateAgoText(); } spacing: units.smallSpacing Layout.preferredHeight: Math.max(applicationNameLabel.implicitHeight, units.iconSizes.small) Connections { target: Globals // clock time changed onTimeChanged: notificationHeading.updateAgoText() } PlasmaCore.IconItem { id: applicationIconItem Layout.preferredWidth: units.iconSizes.small Layout.preferredHeight: units.iconSizes.small source: notificationHeading.applicationIconSource usesPlasmaTheme: false visible: valid } PlasmaExtras.DescriptiveLabel { id: applicationNameLabel Layout.fillWidth: true textFormat: Text.PlainText elide: Text.ElideRight text: notificationHeading.applicationName + (notificationHeading.originName ? " ยท " + notificationHeading.originName : "") } PlasmaExtras.DescriptiveLabel { id: ageLabel // the "n minutes ago" text, for jobs we show remaining time instead // updated periodically by a Timer hence this property with generate() function property string agoText: "" visible: text !== "" text: generateRemainingText() || agoText Layout.rightMargin: -notificationHeading.spacing // the ToolButton's margins are enough function generateAgoText() { if (!time || isNaN(time.getTime()) || notificationHeading.jobState === NotificationManager.Notifications.JobStateRunning) { return ""; } var now = new Date(); var deltaMinutes = Math.floor((now.getTime() - time.getTime()) / 1000 / 60); if (deltaMinutes < 1) { return ""; } // Received less than an hour ago, show relative minutes if (deltaMinutes < 60) { - return i18ncp("Notification was added minutes ago, keep short", "%1 min ago", "%1 min ago", deltaMinutes); + return i18ndcp("plasma_applet_org.kde.plasma.notifications", "Notification was added minutes ago, keep short", "%1 min ago", "%1 min ago", deltaMinutes); } // Received less than a day ago, show time, 22 hours so the time isn't as ambiguous between today and yesterday if (deltaMinutes < 60 * 22) { return Qt.formatTime(time, Qt.locale().timeFormat(Locale.ShortFormat).replace(/.ss?/i, "")); } // Otherwise show relative date (Yesterday, "Last Sunday", or just date if too far in the past) return KCoreAddons.Format.formatRelativeDate(time, Locale.ShortFormat); } function generateRemainingText() { if (notificationHeading.notificationType !== NotificationManager.Notifications.JobType || notificationHeading.jobState === NotificationManager.Notifications.JobStateStopped) { return ""; } var details = notificationHeading.jobDetails; if (!details || !details.speed) { return ""; } var remaining = details.totalBytes - details.processedBytes; if (remaining <= 0) { return ""; } var eta = remaining / details.speed; if (!eta) { return ""; } if (eta < 60) { // 1 minute - return i18ncp("seconds remaining, keep short", + return i18ndcp("plasma_applet_org.kde.plasma.notifications", "seconds remaining, keep short", "%1 s remaining", "%1 s remaining", Math.round(eta)); } if (eta < 60 * 60) {// 1 hour - return i18ncp("minutes remaining, keep short", + return i18ndcp("plasma_applet_org.kde.plasma.notifications", "minutes remaining, keep short", "%1 min remaining", "%1 min remaining", Math.round(eta / 60)); } if (eta < 60 * 60 * 5) { // 5 hours max, if it takes even longer there's no real point in shoing that - return i18ncp("hours remaining, keep short", + return i18ndcp("plasma_applet_org.kde.plasma.notifications", "hours remaining, keep short", "%1 h remaining", "%1 h remaining", Math.round(eta / 60 / 60)); } return ""; } PlasmaCore.ToolTipArea { anchors.fill: parent active: ageLabel.agoText !== "" subText: notificationHeading.time ? notificationHeading.time.toLocaleString(Qt.locale(), Locale.LongFormat) : "" } } RowLayout { id: headerButtonsRow spacing: 0 PlasmaComponents.ToolButton { id: configureButton - tooltip: notificationHeading.configureActionLabel || i18n("Configure") + tooltip: notificationHeading.configureActionLabel || i18nd("plasma_applet_org.kde.plasma.notifications", "Configure") iconSource: "configure" visible: false onClicked: notificationHeading.configureClicked() } PlasmaComponents.ToolButton { id: dismissButton - tooltip: notificationHeading.dismissed ? i18nc("Opposite of minimize", "Restore") : i18n("Minimize") + tooltip: notificationHeading.dismissed + ? i18ndc("plasma_applet_org.kde.plasma.notifications", "Opposite of minimize", "Restore") + : i18nd("plasma_applet_org.kde.plasma.notifications", "Minimize") iconSource: notificationHeading.dismissed ? "window-restore" : "window-minimize" visible: false onClicked: notificationHeading.dismissClicked() } PlasmaComponents.ToolButton { id: closeButton - tooltip: i18n("Close") + tooltip: i18nd("plasma_applet_org.kde.plasma.notifications", "Close") iconSource: "window-close" visible: false onClicked: notificationHeading.closeClicked() } } states: [ State { when: notificationHeading.inGroup PropertyChanges { target: applicationIconItem source: "" } PropertyChanges { target: applicationNameLabel visible: false } } ] } diff --git a/applets/notifications/package/contents/ui/NotificationItem.qml b/applets/notifications/package/contents/ui/NotificationItem.qml index cb33b8241..c272d041c 100644 --- a/applets/notifications/package/contents/ui/NotificationItem.qml +++ b/applets/notifications/package/contents/ui/NotificationItem.qml @@ -1,364 +1,364 @@ /* * Copyright 2018-2019 Kai Uwe Broulik * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see */ import QtQuick 2.8 import QtQuick.Layouts 1.1 import QtQuick.Window 2.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.kquickcontrolsaddons 2.0 as KQCAddons import org.kde.notificationmanager 1.0 as NotificationManager ColumnLayout { id: notificationItem property bool hovered: false property int maximumLineCount: 0 property alias bodyCursorShape: bodyLabel.cursorShape property int notificationType property bool inGroup: false property alias applicationIconSource: notificationHeading.applicationIconSource property alias applicationName: notificationHeading.applicationName property alias originName: notificationHeading.originName property string summary property alias time: notificationHeading.time property alias configurable: notificationHeading.configurable property alias dismissable: notificationHeading.dismissable property alias dismissed: notificationHeading.dismissed property alias closable: notificationHeading.closable // This isn't an alias because TextEdit RichText adds some HTML tags to it property string body property var icon property var urls: [] property int jobState property int percentage property int jobError: 0 property bool suspendable property bool killable property QtObject jobDetails property bool showDetails property alias configureActionLabel: notificationHeading.configureActionLabel property var actionNames: [] property var actionLabels: [] property int headingLeftPadding: 0 property int headingRightPadding: 0 property int thumbnailLeftPadding: 0 property int thumbnailRightPadding: 0 property int thumbnailTopPadding: 0 property int thumbnailBottomPadding: 0 readonly property bool menuOpen: bodyLabel.contextMenu !== null || (thumbnailStripLoader.item && thumbnailStripLoader.item.menuOpen) || (jobLoader.item && jobLoader.item.menuOpen) readonly property bool dragging: thumbnailStripLoader.item && thumbnailStripLoader.item.dragging signal bodyClicked(var mouse) signal closeClicked signal configureClicked signal dismissClicked signal actionInvoked(string actionName) signal openUrl(string url) signal fileActionInvoked signal suspendJobClicked signal resumeJobClicked signal killJobClicked spacing: units.smallSpacing NotificationHeader { id: notificationHeading Layout.fillWidth: true Layout.leftMargin: notificationItem.headingLeftPadding Layout.rightMargin: notificationItem.headingRightPadding inGroup: notificationItem.inGroup notificationType: notificationItem.notificationType jobState: notificationItem.jobState jobDetails: notificationItem.jobDetails onConfigureClicked: notificationItem.configureClicked() onDismissClicked: notificationItem.dismissClicked() onCloseClicked: notificationItem.closeClicked() } RowLayout { id: defaultHeaderContainer Layout.fillWidth: true } // Notification body RowLayout { id: bodyRow Layout.fillWidth: true spacing: units.smallSpacing ColumnLayout { Layout.fillWidth: true spacing: 0 RowLayout { id: summaryRow Layout.fillWidth: true visible: summaryLabel.text !== "" PlasmaExtras.Heading { id: summaryLabel Layout.fillWidth: true Layout.preferredHeight: implicitHeight textFormat: Text.PlainText maximumLineCount: 3 wrapMode: Text.WordWrap elide: Text.ElideRight level: 4 text: { if (notificationItem.notificationType === NotificationManager.Notifications.JobType) { if (notificationItem.jobState === NotificationManager.Notifications.JobStateSuspended) { if (notificationItem.summary) { - return i18nc("Job name, e.g. Copying is paused", "%1 (Paused)", notificationItem.summary); + return i18ndc("plasma_applet_org.kde.plasma.notifications", "Job name, e.g. Copying is paused", "%1 (Paused)", notificationItem.summary); } } else if (notificationItem.jobState === NotificationManager.Notifications.JobStateStopped) { if (notificationItem.jobError) { if (notificationItem.summary) { - return i18nc("Job name, e.g. Copying has failed", "%1 (Failed)", notificationItem.summary); + return i18ndc("plasma_applet_org.kde.plasma.notifications", "Job name, e.g. Copying has failed", "%1 (Failed)", notificationItem.summary); } else { - return i18n("Job Failed"); + return i18nd("plasma_applet_org.kde.plasma.notifications", "Job Failed"); } } else { if (notificationItem.summary) { - return i18nc("Job name, e.g. Copying has finished", "%1 (Finished)", notificationItem.summary); + return i18ndc("plasma_applet_org.kde.plasma.notifications", "Job name, e.g. Copying has finished", "%1 (Finished)", notificationItem.summary); } else { - return i18n("Job Finished"); + return i18nd("plasma_applet_org.kde.plasma.notifications", "Job Finished"); } } } } // some apps use their app name as summary, avoid showing the same text twice // try very hard to match the two if (notificationItem.summary && notificationItem.summary.toLocaleLowerCase().trim() != notificationItem.applicationName.toLocaleLowerCase().trim()) { return notificationItem.summary; } return ""; } visible: text !== "" } // inGroup headerItem is reparented here } RowLayout { id: bodyTextRow Layout.fillWidth: true spacing: units.smallSpacing SelectableLabel { id: bodyLabel // FIXME how to assign this via State? target: bodyLabel.Layout doesn't work and just assigning the property doesn't either Layout.alignment: notificationItem.inGroup ? Qt.AlignTop : Qt.AlignVCenter Layout.fillWidth: true Layout.maximumHeight: notificationItem.maximumLineCount > 0 ? (theme.mSize(font).height * notificationItem.maximumLineCount) : -1 text: notificationItem.body // Cannot do text !== "" because RichText adds some HTML tags even when empty visible: notificationItem.body !== "" onClicked: notificationItem.bodyClicked(mouse) onLinkActivated: Qt.openUrlExternally(link) } // inGroup iconContainer is reparented here } } Item { id: iconContainer Layout.preferredWidth: units.iconSizes.large Layout.preferredHeight: units.iconSizes.large visible: iconItem.active || imageItem.active PlasmaCore.IconItem { id: iconItem // don't show two identical icons readonly property bool active: valid && source != notificationItem.applicationIconSource anchors.fill: parent usesPlasmaTheme: false smooth: true source: { var icon = notificationItem.icon; if (typeof icon !== "string") { // displayed by QImageItem below return ""; } // don't show a generic "info" icon since this is a notification already if (icon === "dialog-information") { return ""; } return icon; } visible: active } KQCAddons.QImageItem { id: imageItem readonly property bool active: !null && nativeWidth > 0 anchors.fill: parent smooth: true fillMode: KQCAddons.QImageItem.PreserveAspectFit visible: active image: typeof notificationItem.icon === "object" ? notificationItem.icon : undefined } } } // Job progress reporting Loader { id: jobLoader Layout.fillWidth: true active: notificationItem.notificationType === NotificationManager.Notifications.JobType visible: active sourceComponent: JobItem { jobState: notificationItem.jobState jobError: notificationItem.jobError percentage: notificationItem.percentage suspendable: notificationItem.suspendable killable: notificationItem.killable jobDetails: notificationItem.jobDetails showDetails: notificationItem.showDetails onSuspendJobClicked: notificationItem.suspendJobClicked() onResumeJobClicked: notificationItem.resumeJobClicked() onKillJobClicked: notificationItem.killJobClicked() onOpenUrl: notificationItem.openUrl(url) onFileActionInvoked: notificationItem.fileActionInvoked() hovered: notificationItem.hovered } } RowLayout { Layout.fillWidth: true visible: actionRepeater.count > 0 // Notification actions Flow { // it's a Flow so it can wrap if too long Layout.fillWidth: true spacing: units.smallSpacing layoutDirection: Qt.RightToLeft Repeater { id: actionRepeater model: { var buttons = []; // 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) { buttons.push({ actionName: actionNames[i], label: actionLabels[i] }); } return buttons; } PlasmaComponents.ToolButton { flat: false // why does it spit "cannot assign undefined to string" when a notification becomes expired? text: modelData.label || "" Layout.preferredWidth: minimumWidth onClicked: notificationItem.actionInvoked(modelData.actionName) } } } } // thumbnails Loader { id: thumbnailStripLoader Layout.leftMargin: notificationItem.thumbnailLeftPadding Layout.rightMargin: notificationItem.thumbnailRightPadding Layout.topMargin: notificationItem.thumbnailTopPadding Layout.bottomMargin: notificationItem.thumbnailBottomPadding Layout.fillWidth: true active: notificationItem.urls.length > 0 visible: active sourceComponent: ThumbnailStrip { leftPadding: -thumbnailStripLoader.Layout.leftMargin rightPadding: -thumbnailStripLoader.Layout.rightMargin topPadding: -thumbnailStripLoader.Layout.topMargin bottomPadding: -thumbnailStripLoader.Layout.bottomMargin urls: notificationItem.urls onOpenUrl: notificationItem.openUrl(url) onFileActionInvoked: notificationItem.fileActionInvoked() } } states: [ State { when: notificationItem.inGroup PropertyChanges { target: notificationHeading parent: summaryRow } PropertyChanges { target: summaryRow visible: true } PropertyChanges { target: summaryLabel visible: true } /*PropertyChanges { target: bodyLabel.Label alignment: Qt.AlignTop }*/ PropertyChanges { target: iconContainer parent: bodyTextRow } } ] } diff --git a/applets/notifications/package/contents/ui/ThumbnailStrip.qml b/applets/notifications/package/contents/ui/ThumbnailStrip.qml index f4c43f88b..45f2fd569 100644 --- a/applets/notifications/package/contents/ui/ThumbnailStrip.qml +++ b/applets/notifications/package/contents/ui/ThumbnailStrip.qml @@ -1,187 +1,187 @@ /* * 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 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 QtGraphicalEffects 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 as KQCAddons import org.kde.plasma.private.notifications 2.0 as Notifications MouseArea { id: thumbnailArea // The protocol supports multiple URLs but so far it's only used to show // a single preview image, so this code is simplified a lot to accomodate // this usecase and drops everything else (fallback to app icon or ListView // for multiple files) property var urls readonly property bool dragging: plasmoid.nativeInterface.dragActive readonly property alias menuOpen: fileMenu.visible property int _pressX: -1 property int _pressY: -1 property int leftPadding: 0 property int rightPadding: 0 property int topPadding: 0 property int bottomPadding: 0 signal openUrl(string url) signal fileActionInvoked implicitHeight: Math.max(menuButton.height + 2 * menuButton.anchors.topMargin, Math.round(Math.min(width / 3, width / thumbnailer.ratio))) + topPadding + bottomPadding preventStealing: true cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: { if (mouse.button === Qt.LeftButton) { thumbnailArea.openUrl(thumbnailer.url) } } onPressed: { if (mouse.button === Qt.LeftButton) { _pressX = mouse.x; _pressY = mouse.y; } else if (mouse.button === Qt.RightButton) { // avoid menu button glowing if we didn't actually press it menuButton.checked = false; fileMenu.visualParent = this; fileMenu.open(mouse.x, mouse.y); } } onPositionChanged: { if (_pressX !== -1 && _pressY !== -1 && plasmoid.nativeInterface.isDrag(_pressX, _pressY, mouse.x, mouse.y)) { plasmoid.nativeInterface.startDrag(previewPixmap, thumbnailer.url, thumbnailer.pixmap); _pressX = -1; _pressY = -1; } } onReleased: { _pressX = -1; _pressY = -1; } onContainsMouseChanged: { if (!containsMouse) { _pressX = -1; _pressY = -1; } } Notifications.FileMenu { id: fileMenu url: thumbnailer.url visualParent: menuButton onActionTriggered: thumbnailArea.fileActionInvoked() } Notifications.Thumbnailer { id: thumbnailer readonly property real ratio: pixmapSize.height ? pixmapSize.width / pixmapSize.height : 1 url: urls[0] // height is dynamic, so request a "square" size and then show it fitting to aspect ratio size: Qt.size(thumbnailArea.width, thumbnailArea.width) } KQCAddons.QPixmapItem { id: previewBackground anchors.fill: parent fillMode: Image.PreserveAspectCrop layer.enabled: true opacity: 0.25 pixmap: thumbnailer.pixmap layer.effect: FastBlur { source: previewBackground anchors.fill: parent radius: 30 } } Item { anchors { fill: parent leftMargin: thumbnailArea.leftPadding rightMargin: thumbnailArea.rightPadding topMargin: thumbnailArea.topPadding bottomMargin: thumbnailArea.bottomPadding } KQCAddons.QPixmapItem { id: previewPixmap anchors.fill: parent pixmap: thumbnailer.pixmap smooth: true fillMode: Image.PreserveAspectFit } PlasmaCore.IconItem { anchors.centerIn: parent width: height height: units.roundToIconSize(parent.height) usesPlasmaTheme: false source: !thumbnailer.busy && !thumbnailer.hasPreview ? thumbnailer.iconName : "" } PlasmaComponents.BusyIndicator { anchors.centerIn: parent running: thumbnailer.busy visible: thumbnailer.busy } PlasmaComponents.Button { id: menuButton anchors { top: parent.top right: parent.right margins: units.smallSpacing } - tooltip: i18n("More Options...") + tooltip: i18nd("plasma_applet_org.kde.plasma.notifications", "More Options...") Accessible.name: tooltip iconName: "application-menu" checkable: true onPressedChanged: { if (pressed) { // fake "pressed" while menu is open checked = Qt.binding(function() { return fileMenu.visible; }); fileMenu.visualParent = this; // -1 tells it to "align bottom left of visualParent (this)" fileMenu.open(-1, -1); } } } } }