You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

364 lines
13 KiB

/*
* Copyright 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* 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 <http://www.gnu.org/licenses/>
*/
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 deviceName: notificationHeading.deviceName
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);
}
} 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);
} else {
return i18n("Job Failed");
}
} else {
if (notificationItem.summary) {
return i18nc("Job name, e.g. Copying has finished", "%1 (Finished)", notificationItem.summary);
} else {
return i18n("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
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
visible: actionRepeater.count > 0
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
}
}
]
}