diff --git a/applets/notifications/package/contents/ui/NotificationPopup.qml b/applets/notifications/package/contents/ui/NotificationPopup.qml --- a/applets/notifications/package/contents/ui/NotificationPopup.qml +++ b/applets/notifications/package/contents/ui/NotificationPopup.qml @@ -76,6 +76,8 @@ signal fileActionInvoked signal expired + signal hoverEntered + signal hoverExited signal suspendJobClicked signal resumeJobClicked @@ -115,6 +117,8 @@ acceptedButtons: hasDefaultAction ? Qt.LeftButton : Qt.NoButton onClicked: notificationPopup.defaultActionInvoked() + onEntered: notificationPopup.hoverEntered() + onExited: notificationPopup.hoverExited() LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true diff --git a/applets/notifications/package/contents/ui/global/Globals.qml b/applets/notifications/package/contents/ui/global/Globals.qml --- a/applets/notifications/package/contents/ui/global/Globals.qml +++ b/applets/notifications/package/contents/ui/global/Globals.qml @@ -382,6 +382,7 @@ actionLabels: model.actionLabels onExpired: popupNotificationsModel.expire(popupNotificationsModel.index(index, 0)) + onHoverEntered: model.read = true onCloseClicked: popupNotificationsModel.close(popupNotificationsModel.index(index, 0)) onDismissClicked: model.dismissed = true onConfigureClicked: popupNotificationsModel.configure(popupNotificationsModel.index(index, 0)) diff --git a/libnotificationmanager/jobsmodel.cpp b/libnotificationmanager/jobsmodel.cpp --- a/libnotificationmanager/jobsmodel.cpp +++ b/libnotificationmanager/jobsmodel.cpp @@ -136,6 +136,12 @@ case Notifications::ConfigurableRole: return false; case Notifications::ExpiredRole: return job->expired(); case Notifications::DismissedRole: return job->dismissed(); + + // A job is usually either a long lasting operation you're aware about + // or a quick job you don't care about. + // When it's running, it's there, when it failed, it's persistent. + // There's hardly a reason why it should show up as "unread". + case Notifications::ReadRole: return true; } return QVariant(); diff --git a/libnotificationmanager/notification.h b/libnotificationmanager/notification.h --- a/libnotificationmanager/notification.h +++ b/libnotificationmanager/notification.h @@ -58,6 +58,9 @@ QDateTime updated() const; void resetUpdated(); + bool read() const; + void setRead(bool read); + QString summary() const; void setSummary(const QString &summary); diff --git a/libnotificationmanager/notification.cpp b/libnotificationmanager/notification.cpp --- a/libnotificationmanager/notification.cpp +++ b/libnotificationmanager/notification.cpp @@ -487,6 +487,16 @@ d->updated = QDateTime::currentDateTimeUtc(); } +bool Notification::read() const +{ + return d->read; +} + +void Notification::setRead(bool read) +{ + d->read = read; +} + QString Notification::summary() const { return d->summary; diff --git a/libnotificationmanager/notification_p.h b/libnotificationmanager/notification_p.h --- a/libnotificationmanager/notification_p.h +++ b/libnotificationmanager/notification_p.h @@ -60,6 +60,7 @@ uint id = 0; QDateTime created; QDateTime updated; + bool read = false; QString summary; QString body; diff --git a/libnotificationmanager/notificationgroupcollapsingproxymodel.cpp b/libnotificationmanager/notificationgroupcollapsingproxymodel.cpp --- a/libnotificationmanager/notificationgroupcollapsingproxymodel.cpp +++ b/libnotificationmanager/notificationgroupcollapsingproxymodel.cpp @@ -199,13 +199,16 @@ if (m_expandUnread && m_lastRead.isValid()) { const QModelIndex sourceIdx = sourceModel()->index(source_row, 0, source_parent); - QDateTime time = sourceIdx.data(Notifications::UpdatedRole).toDateTime(); - if (!time.isValid()) { - time = sourceIdx.data(Notifications::CreatedRole).toDateTime(); - } - if (time.isValid() && m_lastRead < time) { - return true; + if (!sourceIdx.data(Notifications::ReadRole).toBool()) { + QDateTime time = sourceIdx.data(Notifications::UpdatedRole).toDateTime(); + if (!time.isValid()) { + time = sourceIdx.data(Notifications::CreatedRole).toDateTime(); + } + + if (time.isValid() && m_lastRead < time) { + return true; + } } } diff --git a/libnotificationmanager/notifications.h b/libnotificationmanager/notifications.h --- a/libnotificationmanager/notifications.h +++ b/libnotificationmanager/notifications.h @@ -256,7 +256,8 @@ ClosableRole, ///< Whether the item can be closed. Notifications are always closable, jobs are only when in JobStateStopped. ExpiredRole, ///< The notification timed out and closed. Actions on it cannot be invoked anymore. - DismissedRole ///< The notification got temporarily hidden by the user but could still be interacted with. + DismissedRole, ///< The notification got temporarily hidden by the user but could still be interacted with. + ReadRole ///< Whether the notification got read by the user. If true, the notification isn't considered unread even if created after lastRead. @since 5.17 }; Q_ENUM(Roles) diff --git a/libnotificationmanager/notifications.cpp b/libnotificationmanager/notifications.cpp --- a/libnotificationmanager/notifications.cpp +++ b/libnotificationmanager/notifications.cpp @@ -269,14 +269,14 @@ ++active; } - QDateTime date = idx.data(Notifications::UpdatedRole).toDateTime(); - if (!date.isValid()) { - date = idx.data(Notifications::CreatedRole).toDateTime(); - } + const bool read = idx.data(Notifications::ReadRole).toBool(); + if (!active && !read) { + QDateTime date = idx.data(Notifications::UpdatedRole).toDateTime(); + if (!date.isValid()) { + date = idx.data(Notifications::CreatedRole).toDateTime(); + } - // TODO Jobs could also be unread? - if (notificationsModel) { - if (!active && date > notificationsModel->lastRead()) { + if (notificationsModel && date > notificationsModel->lastRead()) { ++unread; } } diff --git a/libnotificationmanager/notificationsmodel.h b/libnotificationmanager/notificationsmodel.h --- a/libnotificationmanager/notificationsmodel.h +++ b/libnotificationmanager/notificationsmodel.h @@ -43,6 +43,7 @@ void setLastRead(const QDateTime &lastRead); QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; void expire(uint notificationId); diff --git a/libnotificationmanager/notificationsmodel.cpp b/libnotificationmanager/notificationsmodel.cpp --- a/libnotificationmanager/notificationsmodel.cpp +++ b/libnotificationmanager/notificationsmodel.cpp @@ -308,11 +308,32 @@ case Notifications::ConfigureActionLabelRole: return notification.configureActionLabel(); case Notifications::ExpiredRole: return notification.expired(); + case Notifications::ReadRole: return notification.read(); } return QVariant(); } +bool NotificationsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!checkIndex(index)) { + return false; + } + + Notification ¬ification = d->notifications[index.row()]; + + switch (role) { + case Notifications::ReadRole: + if (value.toBool() != notification.read()) { + notification.setRead(value.toBool()); + return true; + } + break; + } + + return false; +} + int NotificationsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) {