mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
widgets/notifications: Cleanup, fix some visual issues, and add jobs
Note: There was a lot of whitespace that my IDE is now removing. Fixes job notifications not being dismissable (#208), and imports an implementation from workspace for the progress bar and actions. Fixes notification contents not being clipped when being dragged (https://invent.kde.org/teams/plasma-mobile/issues/-/issues/287) Also fixes notification text being spread over multiple lines unnecessarily (https://invent.kde.org/teams/plasma-mobile/issues/-/issues/302).
This commit is contained in:
parent
b426a7d59e
commit
5d84e6e47d
9 changed files with 659 additions and 160 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||||
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -18,25 +18,25 @@ import org.kde.coreaddons 1.0 as KCoreAddons
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: notificationItem
|
id: notificationItem
|
||||||
|
|
||||||
required property var notificationsModel
|
required property var notificationsModel
|
||||||
|
|
||||||
required property int notificationsModelType
|
required property int notificationsModelType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the notification is allowed to invoke any action, or if it should instead
|
* Whether the notification is allowed to invoke any action, or if it should instead
|
||||||
* emit the runActionRequested(action) signal, containing the code to run.
|
* emit the runActionRequested(action) signal, containing the code to run.
|
||||||
*
|
*
|
||||||
* This is useful for cases like the lockscreen, where actions should only be run after
|
* This is useful for cases like the lockscreen, where actions should only be run after
|
||||||
* the user logs in.
|
* the user logs in.
|
||||||
*/
|
*/
|
||||||
property bool requestToInvoke: false
|
property bool requestToInvoke: false
|
||||||
|
|
||||||
property var model
|
property var model
|
||||||
property int modelIndex
|
property int modelIndex
|
||||||
|
|
||||||
property P5Support.DataSource timeSource
|
property P5Support.DataSource timeSource
|
||||||
|
|
||||||
readonly property int notificationType: model.type
|
readonly property int notificationType: model.type
|
||||||
|
|
||||||
readonly property bool inGroup: model.isInGroup || false
|
readonly property bool inGroup: model.isInGroup || false
|
||||||
|
|
@ -54,7 +54,7 @@ Item {
|
||||||
readonly property string replyPlaceholderText: model.replyPlaceholderText || ""
|
readonly property string replyPlaceholderText: model.replyPlaceholderText || ""
|
||||||
readonly property string replySubmitButtonText: model.replySubmitButtonText || ""
|
readonly property string replySubmitButtonText: model.replySubmitButtonText || ""
|
||||||
readonly property string replySubmitButtonIconName: model.replySubmitButtonIconName || ""
|
readonly property string replySubmitButtonIconName: model.replySubmitButtonIconName || ""
|
||||||
|
|
||||||
// configure button on every single notifications is a bit overwhelming
|
// configure button on every single notifications is a bit overwhelming
|
||||||
readonly property bool configurable: !inGroup && model.configurable
|
readonly property bool configurable: !inGroup && model.configurable
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ Item {
|
||||||
readonly property int jobError: model.jobError || 0
|
readonly property int jobError: model.jobError || 0
|
||||||
readonly property bool suspendable: !!model.suspendable
|
readonly property bool suspendable: !!model.suspendable
|
||||||
readonly property bool killable: !!model.killable
|
readonly property bool killable: !!model.killable
|
||||||
|
|
||||||
readonly property QtObject jobDetails: model.jobDetails || null
|
readonly property QtObject jobDetails: model.jobDetails || null
|
||||||
|
|
||||||
readonly property string configureActionLabel: model.configureActionLabel || ""
|
readonly property string configureActionLabel: model.configureActionLabel || ""
|
||||||
|
|
@ -97,13 +97,13 @@ Item {
|
||||||
}
|
}
|
||||||
return labels;
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This signal is emitted and intended for the parent to make its own decision
|
* This signal is emitted and intended for the parent to make its own decision
|
||||||
* on whether to run the requested notification action.
|
* on whether to run the requested notification action.
|
||||||
*/
|
*/
|
||||||
signal runActionRequested()
|
signal runActionRequested()
|
||||||
|
|
||||||
signal actionInvoked(string actionName)
|
signal actionInvoked(string actionName)
|
||||||
signal replied(string text)
|
signal replied(string text)
|
||||||
signal openUrl(string url)
|
signal openUrl(string url)
|
||||||
|
|
@ -112,7 +112,7 @@ Item {
|
||||||
signal suspendJobClicked
|
signal suspendJobClicked
|
||||||
signal resumeJobClicked
|
signal resumeJobClicked
|
||||||
signal killJobClicked
|
signal killJobClicked
|
||||||
|
|
||||||
function expire() {
|
function expire() {
|
||||||
if (model.resident) {
|
if (model.resident) {
|
||||||
model.expired = true;
|
model.expired = true;
|
||||||
|
|
@ -132,17 +132,17 @@ Item {
|
||||||
notificationsModel.close(notificationsModel.index(modelIndex, 0));
|
notificationsModel.close(notificationsModel.index(modelIndex, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO call
|
// TODO call
|
||||||
function configure() {
|
function configure() {
|
||||||
notificationsModel.configure(notificationsModel.index(modelIndex, 0))
|
notificationsModel.configure(notificationsModel.index(modelIndex, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
property var pendingAction: () => {}
|
property var pendingAction: () => {}
|
||||||
function runPendingAction() {
|
function runPendingAction() {
|
||||||
pendingAction();
|
pendingAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
onActionInvoked: {
|
onActionInvoked: {
|
||||||
let action = () => {
|
let action = () => {
|
||||||
if (notificationsModelType === NotificationsModelType.WatchedNotificationsModel) {
|
if (notificationsModelType === NotificationsModelType.WatchedNotificationsModel) {
|
||||||
|
|
@ -153,14 +153,14 @@ Item {
|
||||||
}
|
}
|
||||||
} else if (notificationsModelType === NotificationsModelType.NotificationsModel) {
|
} else if (notificationsModelType === NotificationsModelType.NotificationsModel) {
|
||||||
if (actionName === "default") {
|
if (actionName === "default") {
|
||||||
notificationsModel.invokeDefaultAction(notificationsModel.index(modelIndex, 0), NotificationManager.Close); // notification closes
|
notificationsModel.invokeDefaultAction(notificationsModel.index(modelIndex, 0), NotificationManager.Close); // notification closes
|
||||||
} else {
|
} else {
|
||||||
notificationsModel.invokeAction(notificationsModel.index(modelIndex, 0), actionName, NotificationManager.Close); // notification closes
|
notificationsModel.invokeAction(notificationsModel.index(modelIndex, 0), actionName, NotificationManager.Close); // notification closes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expire();
|
expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationItem.requestToInvoke) {
|
if (notificationItem.requestToInvoke) {
|
||||||
pendingAction = action;
|
pendingAction = action;
|
||||||
runActionRequested();
|
runActionRequested();
|
||||||
|
|
@ -168,13 +168,13 @@ Item {
|
||||||
action();
|
action();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenUrl: {
|
onOpenUrl: {
|
||||||
let action = () => {
|
let action = () => {
|
||||||
Qt.openUrlExternally(url);
|
Qt.openUrlExternally(url);
|
||||||
expire();
|
expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationItem.requestToInvoke) {
|
if (notificationItem.requestToInvoke) {
|
||||||
pendingAction = action;
|
pendingAction = action;
|
||||||
runActionRequested();
|
runActionRequested();
|
||||||
|
|
@ -182,7 +182,7 @@ Item {
|
||||||
action();
|
action();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileActionInvoked: {
|
onFileActionInvoked: {
|
||||||
let action = () => {
|
let action = () => {
|
||||||
if (action.objectName === "movetotrash" || action.objectName === "deletefile") {
|
if (action.objectName === "movetotrash" || action.objectName === "deletefile") {
|
||||||
|
|
@ -191,7 +191,7 @@ Item {
|
||||||
expire();
|
expire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationItem.requestToInvoke) {
|
if (notificationItem.requestToInvoke) {
|
||||||
pendingAction = action;
|
pendingAction = action;
|
||||||
runActionRequested();
|
runActionRequested();
|
||||||
|
|
@ -199,10 +199,10 @@ Item {
|
||||||
action();
|
action();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuspendJobClicked: {
|
onSuspendJobClicked: {
|
||||||
let action = () => notificationsModel.suspendJob(notificationsModel.index(modelIndex, 0));
|
let action = () => notificationsModel.suspendJob(notificationsModel.index(modelIndex, 0));
|
||||||
|
|
||||||
if (notificationItem.requestToInvoke) {
|
if (notificationItem.requestToInvoke) {
|
||||||
pendingAction = action;
|
pendingAction = action;
|
||||||
runActionRequested();
|
runActionRequested();
|
||||||
|
|
@ -210,10 +210,10 @@ Item {
|
||||||
action();
|
action();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onResumeJobClicked: {
|
onResumeJobClicked: {
|
||||||
let action = () => notificationsModel.resumeJob(notificationsModel.index(modelIndex, 0));
|
let action = () => notificationsModel.resumeJob(notificationsModel.index(modelIndex, 0));
|
||||||
|
|
||||||
if (notificationItem.requestToInvoke) {
|
if (notificationItem.requestToInvoke) {
|
||||||
pendingAction = action;
|
pendingAction = action;
|
||||||
runActionRequested();
|
runActionRequested();
|
||||||
|
|
@ -221,10 +221,10 @@ Item {
|
||||||
action();
|
action();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onKillJobClicked: {
|
onKillJobClicked: {
|
||||||
let action = () => notificationsModel.killJob(notificationsModel.index(modelIndex, 0));
|
let action = () => notificationsModel.killJob(notificationsModel.index(modelIndex, 0));
|
||||||
|
|
||||||
if (notificationItem.requestToInvoke) {
|
if (notificationItem.requestToInvoke) {
|
||||||
pendingAction = action;
|
pendingAction = action;
|
||||||
runActionRequested();
|
runActionRequested();
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||||
* SPDX-FileCopyrightText: 2011 Marco Martin <notmart@gmail.com>
|
* SPDX-FileCopyrightText: 2011 Marco Martin <notmart@gmail.com>
|
||||||
* SPDX-FileCopyrightText: 2014, 2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
* SPDX-FileCopyrightText: 2014, 2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick
|
||||||
import QtQuick.Window 2.2
|
import QtQuick.Window
|
||||||
|
|
||||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,52 +10,54 @@ import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
default property Item contentItem
|
default property Item contentItem
|
||||||
|
|
||||||
property bool tapEnabled: false
|
property bool tapEnabled: false
|
||||||
|
|
||||||
property bool swipeGestureEnabled: false
|
property bool swipeGestureEnabled: false
|
||||||
|
|
||||||
property real dragOffset: 0
|
property real dragOffset: 0
|
||||||
|
|
||||||
signal tapped()
|
signal tapped()
|
||||||
signal dismissRequested()
|
signal dismissRequested()
|
||||||
signal configureClicked() // TODO implement settings button
|
signal configureClicked() // TODO implement settings button
|
||||||
|
|
||||||
onContentItemChanged: {
|
onContentItemChanged: {
|
||||||
contentItem.parent = contentParent;
|
contentItem.parent = contentParent;
|
||||||
contentItem.anchors.fill = contentParent;
|
contentItem.anchors.fill = contentParent;
|
||||||
contentItem.anchors.margins = Kirigami.Units.largeSpacing;
|
contentItem.anchors.margins = Kirigami.Units.largeSpacing;
|
||||||
contentParent.children.push(contentItem);
|
contentParent.children.push(contentItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitHeight: contentParent.implicitHeight
|
implicitHeight: contentParent.implicitHeight
|
||||||
|
|
||||||
NumberAnimation on dragOffset {
|
NumberAnimation on dragOffset {
|
||||||
id: dragAnim
|
id: dragAnim
|
||||||
duration: Kirigami.Units.longDuration
|
duration: Kirigami.Units.longDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
onFinished: {
|
onFinished: {
|
||||||
if (to !== 0) {
|
if (to !== 0) {
|
||||||
root.dismissRequested();
|
root.dismissRequested();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// shadow
|
// shadow
|
||||||
MultiEffect {
|
MultiEffect {
|
||||||
anchors.fill: mainCard
|
anchors.fill: mainCard
|
||||||
visible: Math.abs(dragOffset) !== root.width
|
visible: Math.abs(dragOffset) !== root.width
|
||||||
source: mainCard
|
source: simpleShadow
|
||||||
blurMax: 16
|
blurMax: 16
|
||||||
shadowEnabled: true
|
shadowEnabled: true
|
||||||
shadowVerticalOffset: 1
|
shadowVerticalOffset: 1
|
||||||
shadowOpacity: 0.5
|
shadowOpacity: 0.3
|
||||||
shadowColor: Qt.lighter(Kirigami.Theme.backgroundColor, 0.1)
|
shadowColor: Qt.lighter(Kirigami.Theme.backgroundColor, 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// shadow
|
// shadow
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
id: simpleShadow
|
||||||
visible: Math.abs(dragOffset) !== root.width
|
visible: Math.abs(dragOffset) !== root.width
|
||||||
anchors.fill: mainCard
|
anchors.fill: mainCard
|
||||||
anchors.leftMargin: -1
|
anchors.leftMargin: -1
|
||||||
|
|
@ -74,12 +76,14 @@ Item {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: root.dragOffset < 0 ? -root.dragOffset : 0
|
anchors.rightMargin: root.dragOffset < 0 ? -root.dragOffset : 0
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
|
||||||
color: (root.tapEnabled && mouseArea.pressed) ? Qt.darker(Kirigami.Theme.backgroundColor, 1.1) : Kirigami.Theme.backgroundColor
|
color: (root.tapEnabled && mouseArea.pressed) ? Qt.darker(Kirigami.Theme.backgroundColor, 1.1) : Kirigami.Theme.backgroundColor
|
||||||
radius: Kirigami.Units.smallSpacing
|
radius: Kirigami.Units.smallSpacing
|
||||||
implicitHeight: contentParent.implicitHeight
|
implicitHeight: contentParent.implicitHeight
|
||||||
clip: true
|
|
||||||
|
// clip
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
// ensure this is behind the content to not interfere
|
// ensure this is behind the content to not interfere
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
|
|
@ -90,28 +94,28 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// content parent
|
// content parent
|
||||||
Item {
|
Item {
|
||||||
id: contentParent
|
id: contentParent
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: root.dragOffset > 0 ? parent.left : undefined
|
anchors.left: root.dragOffset > 0 ? parent.left : undefined
|
||||||
anchors.right: root.dragOffset < 0 ? parent.right : undefined
|
anchors.right: root.dragOffset < 0 ? parent.right : undefined
|
||||||
|
|
||||||
width: root.width
|
width: root.width
|
||||||
implicitHeight: contentItem.implicitHeight + contentItem.anchors.topMargin + contentItem.anchors.bottomMargin
|
implicitHeight: contentItem.implicitHeight + contentItem.anchors.topMargin + contentItem.anchors.bottomMargin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DragHandler {
|
DragHandler {
|
||||||
id: dragHandler
|
id: dragHandler
|
||||||
enabled: root.swipeGestureEnabled
|
enabled: root.swipeGestureEnabled
|
||||||
yAxis.enabled: false
|
yAxis.enabled: false
|
||||||
|
|
||||||
property real startDragOffset: 0
|
property real startDragOffset: 0
|
||||||
property real startPosition: 0
|
property real startPosition: 0
|
||||||
property bool startActive: false
|
property bool startActive: false
|
||||||
|
|
||||||
onTranslationChanged: {
|
onTranslationChanged: {
|
||||||
if (startActive) {
|
if (startActive) {
|
||||||
startDragOffset = root.dragOffset;
|
startDragOffset = root.dragOffset;
|
||||||
|
|
@ -120,11 +124,11 @@ Item {
|
||||||
}
|
}
|
||||||
root.dragOffset = startDragOffset + (translation.x - startPosition);
|
root.dragOffset = startDragOffset + (translation.x - startPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
dragAnim.stop();
|
dragAnim.stop();
|
||||||
startActive = active;
|
startActive = active;
|
||||||
|
|
||||||
if (!active) { // release event
|
if (!active) { // release event
|
||||||
let threshold = Kirigami.Units.gridUnit * 5; // drag threshold
|
let threshold = Kirigami.Units.gridUnit * 5; // drag threshold
|
||||||
if (root.dragOffset > threshold) {
|
if (root.dragOffset > threshold) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.8
|
||||||
|
|
||||||
|
import org.kde.plasma.extras 2.0 as PlasmaExtras
|
||||||
|
|
||||||
|
import org.kde.kquickcontrolsaddons 2.0 as KQCAddons
|
||||||
|
|
||||||
|
PlasmaExtras.Menu {
|
||||||
|
id: contextMenu
|
||||||
|
|
||||||
|
signal closed
|
||||||
|
|
||||||
|
property QtObject __clipboard: KQCAddons.Clipboard { }
|
||||||
|
|
||||||
|
// can be a Text or TextEdit
|
||||||
|
property Item target
|
||||||
|
|
||||||
|
property string link
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === PlasmaExtras.Menu.Closed) {
|
||||||
|
closed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaExtras.MenuItem {
|
||||||
|
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "@action:inmenu", "Copy Link Address")
|
||||||
|
icon: "edit-copy-symbolic"
|
||||||
|
onClicked: __clipboard.content = contextMenu.link
|
||||||
|
visible: contextMenu.link !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaExtras.MenuItem {
|
||||||
|
separator: true
|
||||||
|
visible: contextMenu.link !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaExtras.MenuItem {
|
||||||
|
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "@action:inmenu", "Copy")
|
||||||
|
icon: "edit-copy-symbolic"
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaExtras.MenuItem {
|
||||||
|
id: selectAllAction
|
||||||
|
icon: "edit-select-all-symbolic"
|
||||||
|
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "@action:inmenu", "Select All")
|
||||||
|
onClicked: target.selectAll()
|
||||||
|
visible: typeof target.selectAll === "function"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||||
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -20,21 +20,11 @@ import org.kde.coreaddons 1.0 as KCoreAddons
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: notificationHeading
|
id: notificationHeading
|
||||||
property int notificationType
|
|
||||||
|
|
||||||
property var applicationIconSource
|
property var applicationIconSource
|
||||||
property string applicationName
|
property string applicationName
|
||||||
property string originName
|
property string originName
|
||||||
|
|
||||||
property var time
|
|
||||||
property P5Support.DataSource timeSource
|
|
||||||
|
|
||||||
property int jobState
|
|
||||||
property QtObject jobDetails
|
|
||||||
|
|
||||||
property real timeout: 5000
|
|
||||||
property real remainingTime: 0
|
|
||||||
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
Layout.preferredHeight: Math.max(applicationNameLabel.implicitHeight, Kirigami.Units.iconSizes.small)
|
Layout.preferredHeight: Math.max(applicationNameLabel.implicitHeight, Kirigami.Units.iconSizes.small)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||||
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ import org.kde.coreaddons 1.0 as KCoreAddons
|
||||||
BaseNotificationItem {
|
BaseNotificationItem {
|
||||||
id: notificationItem
|
id: notificationItem
|
||||||
implicitHeight: mainCard.implicitHeight + mainCard.anchors.topMargin + notificationHeading.height
|
implicitHeight: mainCard.implicitHeight + mainCard.anchors.topMargin + notificationHeading.height
|
||||||
|
|
||||||
// notification heading for groups with one element
|
// notification heading for groups with one element
|
||||||
NotificationGroupHeader {
|
NotificationGroupHeader {
|
||||||
id: notificationHeading
|
id: notificationHeading
|
||||||
|
|
@ -40,15 +40,8 @@ BaseNotificationItem {
|
||||||
applicationName: notificationItem.applicationName
|
applicationName: notificationItem.applicationName
|
||||||
applicationIconSource: notificationItem.applicationIconSource
|
applicationIconSource: notificationItem.applicationIconSource
|
||||||
originName: notificationItem.originName
|
originName: notificationItem.originName
|
||||||
|
|
||||||
notificationType: notificationItem.notificationType
|
|
||||||
jobState: notificationItem.jobState
|
|
||||||
jobDetails: notificationItem.jobDetails
|
|
||||||
|
|
||||||
time: notificationItem.time
|
|
||||||
timeSource: notificationItem.timeSource
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// notification
|
// notification
|
||||||
NotificationCard {
|
NotificationCard {
|
||||||
id: mainCard
|
id: mainCard
|
||||||
|
|
@ -56,21 +49,21 @@ BaseNotificationItem {
|
||||||
anchors.top: notificationHeading.bottom
|
anchors.top: notificationHeading.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
tapEnabled: notificationItem.hasDefaultAction
|
tapEnabled: notificationItem.hasDefaultAction
|
||||||
onTapped: notificationItem.actionInvoked("default");
|
onTapped: notificationItem.actionInvoked("default");
|
||||||
swipeGestureEnabled: notificationItem.notificationType != NotificationManager.Notifications.JobType
|
swipeGestureEnabled: notificationItem.closable
|
||||||
onDismissRequested: notificationItem.close()
|
onDismissRequested: notificationItem.close()
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: column
|
id: column
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
// notification summary row
|
// notification summary row
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
// notification summary
|
// notification summary
|
||||||
PlasmaComponents.Label {
|
PlasmaComponents.Label {
|
||||||
id: summaryLabel
|
id: summaryLabel
|
||||||
|
|
@ -83,19 +76,19 @@ BaseNotificationItem {
|
||||||
visible: text !== ""
|
visible: text !== ""
|
||||||
font.weight: Font.DemiBold
|
font.weight: Font.DemiBold
|
||||||
}
|
}
|
||||||
|
|
||||||
// notification timestamp
|
// notification timestamp
|
||||||
NotificationTimeText {
|
NotificationTimeText {
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||||
notificationType: notificationItem.notificationType
|
notificationType: notificationItem.notificationType
|
||||||
jobState: notificationItem.jobState
|
jobState: notificationItem.jobState
|
||||||
jobDetails: notificationItem.jobDetails
|
jobDetails: notificationItem.jobDetails
|
||||||
|
|
||||||
time: notificationItem.time
|
time: notificationItem.time
|
||||||
timeSource: notificationItem.timeSource
|
timeSource: notificationItem.timeSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// notification contents
|
// notification contents
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -105,17 +98,11 @@ BaseNotificationItem {
|
||||||
NotificationBodyLabel {
|
NotificationBodyLabel {
|
||||||
id: bodyLabel
|
id: bodyLabel
|
||||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||||
Layout.fillWidth: true
|
Layout.preferredWidth: column.width - iconContainer.width - Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
// HACK RichText does not allow to specify link color and since LineEdit
|
|
||||||
// does not support StyledText, we have to inject some CSS to force the color,
|
|
||||||
// cf. QTBUG-81463 and to some extent QTBUG-80354
|
|
||||||
text: "<style>a { color: " + Kirigami.Theme.linkColor + "; }</style>" + notificationItem.body
|
|
||||||
|
|
||||||
// Cannot do text !== "" because RichText adds some HTML tags even when empty
|
text: notificationItem.body
|
||||||
visible: notificationItem.body !== ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// notification icon
|
// notification icon
|
||||||
Item {
|
Item {
|
||||||
id: iconContainer
|
id: iconContainer
|
||||||
|
|
@ -138,13 +125,41 @@ BaseNotificationItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Job progress reporting
|
||||||
|
Loader {
|
||||||
|
id: jobLoader
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: item ? item.implicitHeight : 0
|
||||||
|
active: notificationItem.notificationType === NotificationManager.Notifications.JobType
|
||||||
|
visible: active
|
||||||
|
sourceComponent: NotificationJobItem {
|
||||||
|
iconContainerItem: iconContainer
|
||||||
|
|
||||||
|
jobState: notificationItem.jobState
|
||||||
|
jobError: notificationItem.jobError
|
||||||
|
percentage: notificationItem.percentage
|
||||||
|
suspendable: notificationItem.suspendable
|
||||||
|
killable: notificationItem.killable
|
||||||
|
|
||||||
|
jobDetails: notificationItem.jobDetails
|
||||||
|
|
||||||
|
onSuspendJobClicked: notificationItem.suspendJobClicked()
|
||||||
|
onResumeJobClicked: notificationItem.resumeJobClicked()
|
||||||
|
onKillJobClicked: notificationItem.killJobClicked()
|
||||||
|
|
||||||
|
onOpenUrl: notificationItem.openUrl(url)
|
||||||
|
onFileActionInvoked: notificationItem.fileActionInvoked(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// notification actions
|
// notification actions
|
||||||
NotificationFooterActions {
|
NotificationFooterActions {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
notification: notificationItem
|
notification: notificationItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// thumbnails
|
// thumbnails
|
||||||
Loader {
|
Loader {
|
||||||
id: thumbnailStripLoader
|
id: thumbnailStripLoader
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
// SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
// SPDX-FileCopyrightText: 2024 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
import QtQuick 2.8
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
|
||||||
|
import org.kde.plasma.extras 2.0 as PlasmaExtras
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.coreaddons 1.0 as KCoreAddons
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: detailsGrid
|
||||||
|
|
||||||
|
property QtObject jobDetails
|
||||||
|
|
||||||
|
columns: 2
|
||||||
|
rowSpacing: Math.round(Kirigami.Units.smallSpacing / 2)
|
||||||
|
columnSpacing: Kirigami.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]
|
||||||
|
? i18ndc("plasma_applet_org.kde.plasma.notifications", "Row description, e.g. Source", "%1:", jobDetails["descriptionLabel" + modelData]) : ""
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: [1, 2]
|
||||||
|
|
||||||
|
PlasmaExtras.DescriptiveLabel {
|
||||||
|
id: descriptionValueLabel
|
||||||
|
Layout.column: 1
|
||||||
|
Layout.row: index
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||||
|
verticalAlignment: Text.AlignTop
|
||||||
|
maximumLineCount: 5
|
||||||
|
visible: text !== ""
|
||||||
|
|
||||||
|
// Only let the label grow, never shrink, to avoid repeatedly resizing the dialog when copying many files
|
||||||
|
onImplicitHeightChanged: {
|
||||||
|
if (implicitHeight > Layout.preferredHeight) {
|
||||||
|
Layout.preferredHeight = implicitHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: bindText()
|
||||||
|
function bindText() {
|
||||||
|
text = Qt.binding(function() {
|
||||||
|
return jobDetails["descriptionValue" + modelData] || "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onPressed: mouse => {
|
||||||
|
// break binding so it doesn't update while the menu is opened
|
||||||
|
descriptionValueLabel.text = descriptionValueLabel.text;
|
||||||
|
descriptionValueMenu.open(mouse.x, mouse.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationEditContextMenu {
|
||||||
|
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", "Items"]
|
||||||
|
|
||||||
|
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 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 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 i18ndcp("plasma_applet_org.kde.plasma.notifications", "How many dirs have been copied", "%2 of %1 folder", "%2 of %1 folders",
|
||||||
|
total, processed);
|
||||||
|
case "Items":
|
||||||
|
return i18ndcp("plasma_applet_org.kde.plasma.notifications", "How many items (that includes files and dirs) have been copied", "%2 of %1 item", "%2 of %1 items",
|
||||||
|
total, processed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch(modelData) {
|
||||||
|
case "Bytes":
|
||||||
|
return KCoreAddons.Format.formatByteSize(processed || total)
|
||||||
|
case "Files":
|
||||||
|
return i18ndp("plasma_applet_org.kde.plasma.notifications", "%1 file", "%1 files", (processed || total));
|
||||||
|
case "Directories":
|
||||||
|
return i18ndp("plasma_applet_org.kde.plasma.notifications", "%1 folder", "%1 folders", (processed || total));
|
||||||
|
case "Items":
|
||||||
|
return i18ndp("plasma_applet_org.kde.plasma.notifications", "%1 item", "%1 items", (processed || total));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaExtras.DescriptiveLabel {
|
||||||
|
Layout.column: 1
|
||||||
|
Layout.row: 2 + 4
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: jobDetails.speed > 0 ? i18ndc("plasma_applet_org.kde.plasma.notifications", "Bytes per second", "%1/s",
|
||||||
|
KCoreAddons.Format.formatByteSize(jobDetails.speed)) : ""
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,277 @@
|
||||||
|
// SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
// SPDX-FileCopyrightText: 2024 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
import QtQuick 2.8
|
||||||
|
import QtQuick.Window 2.2
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import QtQml 2.15
|
||||||
|
|
||||||
|
import org.kde.plasma.components 3.0 as PlasmaComponents3
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.notificationmanager 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 QtObject jobDetails
|
||||||
|
|
||||||
|
readonly property int totalFiles: jobItem.jobDetails && jobItem.jobDetails.totalFiles || 0
|
||||||
|
readonly property var url: {
|
||||||
|
if (jobItem.jobState !== NotificationManager.Notifications.JobStateStopped
|
||||||
|
|| jobItem.jobError) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a single file show actions for it
|
||||||
|
// Otherwise the destination folder all of them were copied into
|
||||||
|
const url = totalFiles === 1 ? jobItem.jobDetails.descriptionUrl
|
||||||
|
: jobItem.jobDetails.destUrl;
|
||||||
|
|
||||||
|
// Don't offer opening files in Trash
|
||||||
|
if (url && url.toString().startsWith("trash:")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
property alias iconContainerItem: busyIndicatorContainer.parent
|
||||||
|
|
||||||
|
readonly property alias menuOpen: otherFileActionsMenu.visible
|
||||||
|
|
||||||
|
signal suspendJobClicked
|
||||||
|
signal resumeJobClicked
|
||||||
|
signal killJobClicked
|
||||||
|
|
||||||
|
signal openUrl(string url)
|
||||||
|
signal fileActionInvoked(QtObject action)
|
||||||
|
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Notifications.FileInfo {
|
||||||
|
id: fileInfo
|
||||||
|
url: jobItem.totalFiles === 1 && jobItem.url ? jobItem.url : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: busyIndicatorContainer
|
||||||
|
width: parent ? parent.width : 0
|
||||||
|
height: parent ? parent.height : 0
|
||||||
|
|
||||||
|
PlasmaComponents3.BusyIndicator {
|
||||||
|
id: busyIndicator
|
||||||
|
anchors.centerIn: parent
|
||||||
|
running: fileInfo.busy && !delayBusyTimer.running
|
||||||
|
visible: running
|
||||||
|
|
||||||
|
// Avoid briefly flashing the busy indicator
|
||||||
|
Timer {
|
||||||
|
id: delayBusyTimer
|
||||||
|
interval: 500
|
||||||
|
repeat: false
|
||||||
|
running: fileInfo.busy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: progressRow
|
||||||
|
Layout.fillWidth: true
|
||||||
|
// Even when indeterminate, we want to reserve the height for the text, otherwise it's too tightly spaced
|
||||||
|
Layout.minimumHeight: progressText.implicitHeight
|
||||||
|
// We want largeSpacing between the progress bar and the label
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
PlasmaComponents3.ProgressBar {
|
||||||
|
id: progressBar
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 0
|
||||||
|
to: 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
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Label {
|
||||||
|
id: progressText
|
||||||
|
|
||||||
|
visible: !progressBar.indeterminate
|
||||||
|
// the || "0" is a workaround for the fact that 0 as number is falsey, and is wrongly considered a missing argument
|
||||||
|
// BUG: 451807
|
||||||
|
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "Percentage of a job", "%1%", jobItem.percentage || "0")
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: jobActionsRow
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
PlasmaComponents3.Button {
|
||||||
|
id: expandButton
|
||||||
|
|
||||||
|
icon.name: checked ? "collapse-symbolic" : "expand-symbolic"
|
||||||
|
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "Hides/expands item details", "Details")
|
||||||
|
checkable: true
|
||||||
|
enabled: jobItem.jobDetails && jobItem.jobDetails.hasDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
PlasmaComponents3.Button {
|
||||||
|
id: suspendButton
|
||||||
|
|
||||||
|
icon.name: "media-playback-pause-symbolic"
|
||||||
|
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "Pause running job", "Pause")
|
||||||
|
onClicked: jobItem.jobState === NotificationManager.Notifications.JobStateSuspended ? jobItem.resumeJobClicked()
|
||||||
|
: jobItem.suspendJobClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Button {
|
||||||
|
id: killButton
|
||||||
|
|
||||||
|
icon.name: "dialog-cancel-symbolic"
|
||||||
|
text: i18ndc("plasma_applet_org.kde.plasma.notifications", "Cancel running job", "Cancel")
|
||||||
|
onClicked: jobItem.killJobClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: item ? item.implicitHeight : 0
|
||||||
|
active: expandButton.checked
|
||||||
|
// Loader doesn't reset its height when unloaded, just hide it altogether
|
||||||
|
visible: active
|
||||||
|
sourceComponent: NotificationJobDetails {
|
||||||
|
jobDetails: jobItem.jobDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: fileActionsRow
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
// We want the actions to be right-aligned but Row also reverses
|
||||||
|
// the order of items, so we put them in reverse order
|
||||||
|
layoutDirection: Qt.RightToLeft
|
||||||
|
visible: jobItem.url && jobItem.url.toString() !== "" && !fileInfo.error
|
||||||
|
|
||||||
|
PlasmaComponents3.Button {
|
||||||
|
id: otherFileActionsButton
|
||||||
|
height: Math.max(implicitHeight, openButton.implicitHeight)
|
||||||
|
icon.name: "application-menu-symbolic"
|
||||||
|
checkable: true
|
||||||
|
text: openButton.visible ? "" : Accessible.name
|
||||||
|
Accessible.name: i18nd("plasma_applet_org.kde.plasma.notifications", "More Options…")
|
||||||
|
onPressedChanged: {
|
||||||
|
if (pressed) {
|
||||||
|
checked = Qt.binding(function() {
|
||||||
|
return otherFileActionsMenu.visible;
|
||||||
|
});
|
||||||
|
otherFileActionsMenu.visualParent = this;
|
||||||
|
// -1 tells it to "align bottom left of visualParent (this)"
|
||||||
|
otherFileActionsMenu.open(-1, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.ToolTip {
|
||||||
|
text: parent.Accessible.name
|
||||||
|
enabled: parent.text === ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Notifications.FileMenu {
|
||||||
|
id: otherFileActionsMenu
|
||||||
|
url: jobItem.url || ""
|
||||||
|
onActionTriggered: jobItem.fileActionInvoked(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents3.Button {
|
||||||
|
id: openButton
|
||||||
|
width: Math.min(implicitWidth, jobItem.width - otherFileActionsButton.width - fileActionsRow.spacing)
|
||||||
|
height: Math.max(implicitHeight, otherFileActionsButton.implicitHeight)
|
||||||
|
text: i18nd("plasma_applet_org.kde.plasma.notifications", "Open")
|
||||||
|
onClicked: jobItem.openUrl(jobItem.url)
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
when: jobItem.jobDetails && jobItem.jobDetails.totalFiles !== 1
|
||||||
|
PropertyChanges {
|
||||||
|
target: openButton
|
||||||
|
text: i18nd("plasma_applet_org.kde.plasma.notifications", "Open Containing Folder")
|
||||||
|
icon.name: "folder-open-symbolic"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
when: fileInfo.openAction
|
||||||
|
PropertyChanges {
|
||||||
|
target: openButton
|
||||||
|
text: fileInfo.openAction.text
|
||||||
|
icon.name: fileInfo.openActionIconName
|
||||||
|
visible: fileInfo.openAction.enabled
|
||||||
|
onClicked: {
|
||||||
|
fileInfo.openAction.trigger();
|
||||||
|
jobItem.fileActionInvoked(fileInfo.openAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
when: jobItem.jobState === NotificationManager.Notifications.JobStateRunning
|
||||||
|
PropertyChanges {
|
||||||
|
target: suspendButton
|
||||||
|
// Explicitly set it to false so it unchecks when pausing from applet
|
||||||
|
// and then the job unpauses programmatically elsewhere.
|
||||||
|
checked: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
when: jobItem.jobState === NotificationManager.Notifications.JobStateSuspended
|
||||||
|
PropertyChanges {
|
||||||
|
target: suspendButton
|
||||||
|
checked: true
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: progressBar
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
when: jobItem.jobState === NotificationManager.Notifications.JobStateStopped
|
||||||
|
PropertyChanges {
|
||||||
|
target: progressRow
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: jobActionsRow
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: expandButton
|
||||||
|
checked: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -26,69 +26,69 @@ import org.kde.notificationmanager as NotificationManager
|
||||||
*/
|
*/
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The notification model for the widget.
|
* The notification model for the widget.
|
||||||
*/
|
*/
|
||||||
property var historyModel
|
property var historyModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of notification model used for the widget.
|
* The type of notification model used for the widget.
|
||||||
*/
|
*/
|
||||||
property int historyModelType
|
property int historyModelType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The notification model settings for the widget.
|
* The notification model settings for the widget.
|
||||||
*/
|
*/
|
||||||
property var notificationSettings
|
property var notificationSettings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether invoking notification actions requires authentiation of some sort.
|
* Whether invoking notification actions requires authentiation of some sort.
|
||||||
*
|
*
|
||||||
* If set to true, any attempted invoking will trigger the unlockRequested() signal.
|
* If set to true, any attempted invoking will trigger the unlockRequested() signal.
|
||||||
* Any consumers can then call the runPendingAction() function if authenticated to proceed
|
* Any consumers can then call the runPendingAction() function if authenticated to proceed
|
||||||
* executing the notification action.
|
* executing the notification action.
|
||||||
*/
|
*/
|
||||||
property bool actionsRequireUnlock: false
|
property bool actionsRequireUnlock: false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the widget has notifications.
|
* Whether the widget has notifications.
|
||||||
*/
|
*/
|
||||||
readonly property bool hasNotifications: list.count > 0
|
readonly property bool hasNotifications: list.count > 0
|
||||||
|
|
||||||
readonly property bool doNotDisturbModeEnabled: !isNaN(notificationSettings.notificationsInhibitedUntil)
|
readonly property bool doNotDisturbModeEnabled: !isNaN(notificationSettings.notificationsInhibitedUntil)
|
||||||
|
|
||||||
enum ModelType {
|
enum ModelType {
|
||||||
NotificationsModel, // used in the logged-in shell
|
NotificationsModel, // used in the logged-in shell
|
||||||
WatchedNotificationsModel // used on the lockscreen
|
WatchedNotificationsModel // used on the lockscreen
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signal emitted when authentication is requested for an action.
|
* Signal emitted when authentication is requested for an action.
|
||||||
* Listeners should call runPendingAction() if authentication is successful.
|
* Listeners should call runPendingAction() if authentication is successful.
|
||||||
*
|
*
|
||||||
* Only emitted if actionsRequireUnlock is enabled.
|
* Only emitted if actionsRequireUnlock is enabled.
|
||||||
*/
|
*/
|
||||||
signal unlockRequested()
|
signal unlockRequested()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the background is clicked (not a notification or other element).
|
* Emitted when the background is clicked (not a notification or other element).
|
||||||
*/
|
*/
|
||||||
signal backgroundClicked()
|
signal backgroundClicked()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run pending action that was pending for authentication when unlockRequested() was emitted.
|
* Run pending action that was pending for authentication when unlockRequested() was emitted.
|
||||||
*/
|
*/
|
||||||
function runPendingAction() {
|
function runPendingAction() {
|
||||||
list.pendingNotificationWithAction.runPendingAction();
|
list.pendingNotificationWithAction.runPendingAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the history of the notification model.
|
* Clears the history of the notification model.
|
||||||
*/
|
*/
|
||||||
function clearHistory() {
|
function clearHistory() {
|
||||||
historyModel.clear(NotificationManager.Notifications.ClearExpired);
|
historyModel.clear(NotificationManager.Notifications.ClearExpired);
|
||||||
|
|
||||||
if (historyModel.count === 0) {
|
if (historyModel.count === 0) {
|
||||||
backgroundClicked();
|
backgroundClicked();
|
||||||
}
|
}
|
||||||
|
|
@ -102,15 +102,15 @@ Item {
|
||||||
notificationSettings.defaults();
|
notificationSettings.defaults();
|
||||||
} else {
|
} else {
|
||||||
var until = new Date();
|
var until = new Date();
|
||||||
|
|
||||||
until.setFullYear(until.getFullYear() + 1);
|
until.setFullYear(until.getFullYear() + 1);
|
||||||
|
|
||||||
notificationSettings.notificationsInhibitedUntil = until;
|
notificationSettings.notificationsInhibitedUntil = until;
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationSettings.save();
|
notificationSettings.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the system notification settings.
|
* Open the system notification settings.
|
||||||
*/
|
*/
|
||||||
|
|
@ -125,7 +125,7 @@ Item {
|
||||||
interval: 60000 // 1 min
|
interval: 60000 // 1 min
|
||||||
intervalAlignment: P5Support.Types.AlignToMinute
|
intervalAlignment: P5Support.Types.AlignToMinute
|
||||||
}
|
}
|
||||||
|
|
||||||
// implement background clicking signal
|
// implement background clicking signal
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
@ -136,40 +136,40 @@ Item {
|
||||||
ListView {
|
ListView {
|
||||||
id: list
|
id: list
|
||||||
model: historyModel
|
model: historyModel
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
currentIndex: 0
|
currentIndex: 0
|
||||||
|
|
||||||
property var pendingNotificationWithAction
|
property var pendingNotificationWithAction
|
||||||
|
|
||||||
readonly property int animationDuration: ShellSettings.Settings.animationsEnabled ? Kirigami.Units.longDuration : 0
|
readonly property int animationDuration: ShellSettings.Settings.animationsEnabled ? Kirigami.Units.longDuration : 0
|
||||||
|
|
||||||
// If a screen overflow occurs, fix height in order to maintain tool buttons in place.
|
// If a screen overflow occurs, fix height in order to maintain tool buttons in place.
|
||||||
readonly property bool listOverflowing: contentItem.childrenRect.height + toolButtons.height + spacing >= root.height
|
readonly property bool listOverflowing: contentItem.childrenRect.height + toolButtons.height + spacing >= root.height
|
||||||
|
|
||||||
bottomMargin: spacing
|
bottomMargin: spacing
|
||||||
height: count === 0 ? 0 : (listOverflowing ? root.height - toolButtons.height : contentItem.childrenRect.height + bottomMargin)
|
height: count === 0 ? 0 : (listOverflowing ? root.height - toolButtons.height : contentItem.childrenRect.height + bottomMargin)
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: parent.top
|
top: parent.top
|
||||||
left: parent.left
|
left: parent.left
|
||||||
right: parent.right
|
right: parent.right
|
||||||
}
|
}
|
||||||
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
// TODO keyboard focus
|
// TODO keyboard focus
|
||||||
highlightMoveDuration: 0
|
highlightMoveDuration: 0
|
||||||
highlightResizeDuration: 0
|
highlightResizeDuration: 0
|
||||||
highlight: Item {}
|
highlight: Item {}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
property: "isGroup"
|
property: "isGroup"
|
||||||
criteria: ViewSection.FullString
|
criteria: ViewSection.FullString
|
||||||
}
|
}
|
||||||
|
|
||||||
PlasmaExtras.PlaceholderMessage {
|
PlasmaExtras.PlaceholderMessage {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width - (Kirigami.Units.gridUnit * 4)
|
width: parent.width - (Kirigami.Units.gridUnit * 4)
|
||||||
|
|
@ -187,7 +187,7 @@ Item {
|
||||||
visible: currentOwner && currentOwner.vendor && currentOwner.name
|
visible: currentOwner && currentOwner.vendor && currentOwner.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run every time an item is visually added to the list, thus when `Show n more` button is clicked as well.
|
// Run every time an item is visually added to the list, thus when `Show n more` button is clicked as well.
|
||||||
add: Transition {
|
add: Transition {
|
||||||
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: list.animationDuration }
|
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: list.animationDuration }
|
||||||
|
|
@ -196,7 +196,7 @@ Item {
|
||||||
displaced: Transition {
|
displaced: Transition {
|
||||||
NumberAnimation { properties: "y"; duration: list.animationDuration }
|
NumberAnimation { properties: "y"; duration: list.animationDuration }
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRowExpanded(row) {
|
function isRowExpanded(row) {
|
||||||
var idx = historyModel.index(row, 0);
|
var idx = historyModel.index(row, 0);
|
||||||
return historyModel.data(idx, NotificationManager.Notifications.IsGroupExpandedRole);
|
return historyModel.data(idx, NotificationManager.Notifications.IsGroupExpandedRole);
|
||||||
|
|
@ -219,90 +219,89 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantly re-align items after group expansion.
|
// Instantly re-align items after group expansion.
|
||||||
forceLayout();
|
forceLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: Loader {
|
delegate: Loader {
|
||||||
id: delegateLoader
|
id: delegateLoader
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: parent ? parent.left : undefined
|
left: parent ? parent.left : undefined
|
||||||
leftMargin: Kirigami.Units.gridUnit
|
leftMargin: Kirigami.Units.gridUnit
|
||||||
right: parent ? parent.right : undefined
|
right: parent ? parent.right : undefined
|
||||||
rightMargin: Kirigami.Units.gridUnit
|
rightMargin: Kirigami.Units.gridUnit
|
||||||
}
|
}
|
||||||
|
|
||||||
height: model.isGroup ? groupDelegate.height : notificationDelegate.height
|
height: model.isGroup ? groupDelegate.height : notificationDelegate.height
|
||||||
sourceComponent: model.isGroup ? groupDelegate : notificationDelegate
|
sourceComponent: model.isGroup ? groupDelegate : notificationDelegate
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
required property var model
|
required property var model
|
||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
// We have to do this here in order to control the animation before the item is completely removed
|
// We have to do this here in order to control the animation before the item is completely removed
|
||||||
ListView.onRemove: SequentialAnimation {
|
ListView.onRemove: SequentialAnimation {
|
||||||
PropertyAction { target: delegateLoader; property: "ListView.delayRemove"; value: true }
|
PropertyAction { target: delegateLoader; property: "ListView.delayRemove"; value: true }
|
||||||
NumberAnimation { target: delegateLoader; property: "opacity"; to: 0.0; duration: list.animationDuration }
|
NumberAnimation { target: delegateLoader; property: "opacity"; to: 0.0; duration: list.animationDuration }
|
||||||
PropertyAction { target: delegateLoader; property: "ListView.delayRemove"; value: false }
|
PropertyAction { target: delegateLoader; property: "ListView.delayRemove"; value: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: groupDelegate
|
id: groupDelegate
|
||||||
NotificationGroupHeader {
|
NotificationGroupHeader {
|
||||||
applicationName: model.applicationName
|
applicationName: model.applicationName
|
||||||
applicationIconSource: model.applicationIconName
|
applicationIconSource: model.applicationIconName
|
||||||
originName: model.originName || ""
|
originName: model.originName || ""
|
||||||
timeSource: timeDataSource
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: notificationDelegate
|
id: notificationDelegate
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
height: notificationItem.height + showMoreLoader.height
|
height: notificationItem.height + showMoreLoader.height
|
||||||
|
|
||||||
NotificationItem {
|
NotificationItem {
|
||||||
id: notificationItem
|
id: notificationItem
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
|
|
||||||
model: delegateLoader.model
|
model: delegateLoader.model
|
||||||
modelIndex: delegateLoader.index
|
modelIndex: delegateLoader.index
|
||||||
notificationsModel: root.historyModel
|
notificationsModel: root.historyModel
|
||||||
notificationsModelType: root.historyModelType
|
notificationsModelType: root.historyModelType
|
||||||
timeSource: timeDataSource
|
timeSource: timeDataSource
|
||||||
|
|
||||||
requestToInvoke: root.actionsRequireUnlock
|
requestToInvoke: root.actionsRequireUnlock
|
||||||
onRunActionRequested: {
|
onRunActionRequested: {
|
||||||
list.pendingNotificationWithAction = notificationItem;
|
list.pendingNotificationWithAction = notificationItem;
|
||||||
root.unlockRequested();
|
root.unlockRequested();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every item has got an instance of this loader, but it becomes active only for last ones that take place in big enough groups.
|
// Every item has got an instance of this loader, but it becomes active only for last ones that take place in big enough groups.
|
||||||
Loader {
|
Loader {
|
||||||
id: showMoreLoader
|
id: showMoreLoader
|
||||||
|
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
opacity: 0.0
|
opacity: 0.0
|
||||||
visible: active
|
visible: active
|
||||||
|
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
active: {
|
active: {
|
||||||
// if we have the WatchedNotificationsModel, we don't have notification grouping support
|
// if we have the WatchedNotificationsModel, we don't have notification grouping support
|
||||||
if (typeof model.groupChildrenCount === 'undefined')
|
if (typeof model.groupChildrenCount === 'undefined')
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded)
|
return (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded)
|
||||||
&& delegateLoader.ListView.nextSection != delegateLoader.ListView.section;
|
&& delegateLoader.ListView.nextSection != delegateLoader.ListView.section;
|
||||||
}
|
}
|
||||||
|
|
||||||
// state + transition: animates the item when it becomes visible. Fade off is handled by above ListView.onRemove.
|
// state + transition: animates the item when it becomes visible. Fade off is handled by above ListView.onRemove.
|
||||||
states: State {
|
states: State {
|
||||||
name: "VISIBLE"
|
name: "VISIBLE"
|
||||||
|
|
@ -316,7 +315,7 @@ Item {
|
||||||
NumberAnimation { properties: "opacity"; duration: list.animationDuration }
|
NumberAnimation { properties: "opacity"; duration: list.animationDuration }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceComponent: PlasmaComponents3.ToolButton {
|
sourceComponent: PlasmaComponents3.ToolButton {
|
||||||
icon.name: model.isGroupExpanded ? "arrow-up" : "arrow-down"
|
icon.name: model.isGroupExpanded ? "arrow-up" : "arrow-down"
|
||||||
text: model.isGroupExpanded ? i18n("Show Fewer")
|
text: model.isGroupExpanded ? i18n("Show Fewer")
|
||||||
|
|
@ -331,31 +330,31 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: toolButtons
|
id: toolButtons
|
||||||
height: visible ? spacer.height + toolLayout.height + toolLayout.anchors.topMargin + toolLayout.anchors.bottomMargin : 0
|
height: visible ? spacer.height + toolLayout.height + toolLayout.anchors.topMargin + toolLayout.anchors.bottomMargin : 0
|
||||||
|
|
||||||
// do not show on lockscreen
|
// do not show on lockscreen
|
||||||
visible: !root.actionsRequireUnlock
|
visible: !root.actionsRequireUnlock
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: list.bottom
|
top: list.bottom
|
||||||
left: parent.left
|
left: parent.left
|
||||||
right: parent.right
|
right: parent.right
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: spacer
|
id: spacer
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
visible: list.listOverflowing
|
visible: list.listOverflowing
|
||||||
height: 1
|
height: 1
|
||||||
opacity: 0.25
|
opacity: 0.25
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: toolLayout
|
id: toolLayout
|
||||||
|
|
||||||
|
|
@ -368,17 +367,17 @@ Item {
|
||||||
topMargin: list.spacing
|
topMargin: list.spacing
|
||||||
bottomMargin: list.spacing
|
bottomMargin: list.spacing
|
||||||
}
|
}
|
||||||
|
|
||||||
PlasmaComponents3.ToolButton {
|
PlasmaComponents3.ToolButton {
|
||||||
id: clearButton
|
id: clearButton
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignCenter
|
Layout.alignment: Qt.AlignCenter
|
||||||
|
|
||||||
visible: hasNotifications
|
visible: hasNotifications
|
||||||
|
|
||||||
font.bold: true
|
font.bold: true
|
||||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
|
||||||
icon.name: "edit-clear-history"
|
icon.name: "edit-clear-history"
|
||||||
text: i18n("Clear All Notifications")
|
text: i18n("Clear All Notifications")
|
||||||
onClicked: clearHistory()
|
onClicked: clearHistory()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue