Commit 5890aa64 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇
Browse files

[Notifications] Support persistency

This allows a client to mark a notification as "resident" which means it will
not be closed by the sever when expired or an action is invoked, thus transferring
full control over notification life time to the client.

A notification popup still times out as normal (unless persistent, of course)
but the notification remains alive until the client revokes it or a user
explicitly closes it.

It is opt-in so that a client can expect the notification lifetime to be
indefinite and explicitly close it when an action is invoked on its end.
parent d840c5dc
......@@ -281,7 +281,6 @@ PlasmaComponents3.Page {
var urls = historyModel.data(idx, NotificationManager.Notifications.UrlsRole);
if (urls && urls.length === 1) {
Qt.openUrlExternally(urls[0]);
historyModel.expire(idx);
return;
}
......@@ -520,18 +519,26 @@ PlasmaComponents3.Page {
} else {
historyModel.invokeAction(historyModel.index(index, 0), actionName);
}
// Keep it in the history
historyModel.expire(historyModel.index(index, 0));
expire();
}
onOpenUrl: {
Qt.openUrlExternally(url);
historyModel.expire(historyModel.index(index, 0));
expire();
}
onFileActionInvoked: historyModel.expire(historyModel.index(index, 0))
onFileActionInvoked: expire()
onSuspendJobClicked: historyModel.suspendJob(historyModel.index(index, 0))
onResumeJobClicked: historyModel.resumeJob(historyModel.index(index, 0))
onKillJobClicked: historyModel.killJob(historyModel.index(index, 0))
function expire() {
if (model.resident) {
model.expired = true;
} else {
historyModel.expire(historyModel.index(index, 0));
}
}
}
}
......
......@@ -510,8 +510,17 @@ QtObject {
replySubmitButtonText: model.replySubmitButtonText || ""
replySubmitButtonIconName: model.replySubmitButtonIconName || ""
onExpired: popupNotificationsModel.expire(popupNotificationsModel.index(index, 0))
onExpired: {
if (model.resident) {
// When resident, only mark it as expired so the popup disappears
// but don't actually invalidate the notification
model.expired = true;
} else {
popupNotificationsModel.expire(popupNotificationsModel.index(index, 0))
}
}
onHoverEntered: model.read = true
// explicit close, even when resident
onCloseClicked: popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
onDismissClicked: model.dismissed = true
onConfigureClicked: popupNotificationsModel.configure(popupNotificationsModel.index(index, 0))
......@@ -540,33 +549,54 @@ QtObject {
if (highestIdx && highestIdx.valid) {
tasksModel.requestActivate(highestIdx);
popupNotificationsModel.close(popupNotificationsModel.index(index, 0));
if (!model.resident) {
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
}
}
return;
}
tasksModel.requestActivate(defaultActionFallbackWindowIdx);
popupNotificationsModel.close(popupNotificationsModel.index(index, 0));
if (!model.resident) {
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
}
return;
}
popupNotificationsModel.invokeDefaultAction(popupNotificationsModel.index(index, 0))
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
if (!model.resident) {
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
}
}
onActionInvoked: {
popupNotificationsModel.invokeAction(popupNotificationsModel.index(index, 0), actionName)
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
if (!model.resident) {
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
}
}
onReplied: {
popupNotificationsModel.reply(popupNotificationsModel.index(index, 0), text);
popupNotificationsModel.close(popupNotificationsModel.index(index, 0));
if (!model.resident) {
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
}
}
onOpenUrl: {
Qt.openUrlExternally(url);
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
// Client isn't informed of this action, so we always hide the popup
if (model.resident) {
model.expired = true;
} else {
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
}
}
onFileActionInvoked: {
if (model.resident) {
model.expired = true;
} else {
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
}
}
onFileActionInvoked: popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
onSuspendJobClicked: popupNotificationsModel.suspendJob(popupNotificationsModel.index(index, 0))
onResumeJobClicked: popupNotificationsModel.resumeJob(popupNotificationsModel.index(index, 0))
......
......@@ -101,7 +101,20 @@ void AbstractNotificationsModel::Private::onNotificationReplaced(uint replacedId
setupNotificationTimeout(notification);
notifications[row] = notification;
Notification newNotification(notification);
// Resident notifications are expected to be updateable in the history
// transfer a few flags over to new notification so it doesn't plop back up
if (newNotification.resident()) {
const Notification &oldNotification = notifications.at(row);
if (oldNotification.resident()) {
newNotification.setExpired(oldNotification.expired());
newNotification.setDismissed(oldNotification.dismissed());
newNotification.setRead(oldNotification.read());
}
}
notifications[row] = newNotification;
const QModelIndex idx = q->index(row, 0);
emit q->dataChanged(idx, idx);
}
......@@ -342,6 +355,10 @@ QVariant AbstractNotificationsModel::data(const QModelIndex &index, int role) co
return notification.expired();
case Notifications::ReadRole:
return notification.read();
case Notifications::ResidentRole:
return notification.resident();
case Notifications::TransientRole:
return notification.transient();
case Notifications::HasReplyActionRole:
return notification.hasReplyAction();
......@@ -374,6 +391,13 @@ bool AbstractNotificationsModel::setData(const QModelIndex &index, const QVarian
dirty = true;
}
break;
// Allows to mark a notification as expired without actually sending that out through expire() for persistency
case Notifications::ExpiredRole:
if (value.toBool() != notification.expired()) {
notification.setExpired(value.toBool());
dirty = true;
}
break;
}
if (dirty) {
......
......@@ -372,6 +372,9 @@ void Notification::Private::processHints(const QVariantMap &hints)
}
}
resident = hints.value(QStringLiteral("resident")).toBool();
transient = hints.value(QStringLiteral("transient")).toBool();
userActionFeedback = hints.value(QStringLiteral("x-kde-user-action-feedback")).toBool();
if (userActionFeedback) {
// A confirmation of an explicit user interaction is assumed to have been seen by the user.
......@@ -753,6 +756,27 @@ void Notification::setDismissed(bool dismissed)
d->dismissed = dismissed;
}
bool Notification::resident() const
{
return d->resident;
}
void Notification::setResident(bool resident)
{
d->resident = resident;
}
bool Notification::transient() const
{
return d->transient;
}
void Notification::setTransient(bool transient)
{
d->transient = transient;
}
QVariantMap Notification::hints() const
{
return d->hints;
......
......@@ -130,6 +130,12 @@ public:
bool dismissed() const;
void setDismissed(bool dismissed);
bool resident() const;
void setResident(bool resident);
bool transient() const;
void setTransient(bool transient);
// Little bit of mess here, we want to sometime keep track of processed hints, and not process it.
QVariantMap hints() const;
void setHints(const QVariantMap &hints);
......
......@@ -108,6 +108,9 @@ public:
bool expired = false;
bool dismissed = false;
bool resident = false;
bool transient = false;
};
} // namespace NotificationManager
......@@ -287,6 +287,8 @@ public:
CategoryRole, ///< The (optional) category of the notification. Notifications can optionally have a type indicator. Although neither client or nor
///< server must support this, some may choose to. Those servers implementing categories may use them to intelligently display the
///< notification in a certain way, or group notifications of similar types. @since 5.21
ResidentRole, ///< Whether the notification should keep its actions even when they were invoked. @since 5.22
TransientRole, ///< Whether the notification is transient and should not be kept in history. @since 5.22
};
Q_ENUM(Roles)
......
......@@ -292,6 +292,7 @@ QStringList ServerPrivate::GetCapabilities() const
QStringLiteral("body-images"),
QStringLiteral("icon-static"),
QStringLiteral("actions"),
QStringLiteral("persistence"),
QStringLiteral("inline-reply"),
QStringLiteral("x-kde-urls"),
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment