File indexing completed on 2024-05-05 05:38:31

0001 /*
0002     SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #pragma once
0008 
0009 #include <QQmlParserStatus>
0010 #include <QSortFilterProxyModel>
0011 #include <QWindow>
0012 
0013 #include <memory>
0014 
0015 #include "notificationmanager_export.h"
0016 
0017 #include <qqmlregistration.h>
0018 
0019 namespace NotificationManager
0020 {
0021 /**
0022  * @brief A model with notifications and jobs
0023  *
0024  * This model contains application notifications as well as jobs
0025  * and lets you apply fine-grained filter, sorting, and grouping rules.
0026  *
0027  * @author Kai Uwe Broulik <kde@privat.broulik.de>
0028  **/
0029 class NOTIFICATIONMANAGER_EXPORT Notifications : public QSortFilterProxyModel, public QQmlParserStatus
0030 {
0031     Q_OBJECT
0032     QML_ELEMENT
0033     Q_INTERFACES(QQmlParserStatus)
0034 
0035     /**
0036      * The number of notifications the model should at most contain.
0037      *
0038      * Default is 0, which is no limit.
0039      */
0040     Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged)
0041 
0042     /**
0043      * Whether to show expired notifications.
0044      *
0045      * Expired notifications are those that timed out, i.e. ones that were not explicitly
0046      * closed or acted upon by the user, nor revoked by the issuing application.
0047      *
0048      * An expired notification has its actions removed.
0049      *
0050      * Default is false.
0051      */
0052     Q_PROPERTY(bool showExpired READ showExpired WRITE setShowExpired NOTIFY showExpiredChanged)
0053 
0054     /**
0055      * Whether to show dismissed notifications.
0056      *
0057      * Dismissed notifications are those that are temporarily hidden by the user.
0058      * This can e.g. be a copy job that has its popup closed but still continues in the background.
0059      *
0060      * Default is false.
0061      */
0062     Q_PROPERTY(bool showDismissed READ showDismissed WRITE setShowDismissed NOTIFY showDismissedChanged)
0063 
0064     /**
0065      * A list of desktop entries for which no notifications should be shown.
0066      *
0067      * If the same desktop entry is present in both blacklist and whitelist,
0068      * the blacklist takes precedence, i.e. the notification is not shown.
0069      */
0070     Q_PROPERTY(QStringList blacklistedDesktopEntries READ blacklistedDesktopEntries WRITE setBlacklistedDesktopEntries NOTIFY blacklistedDesktopEntriesChanged)
0071 
0072     /**
0073      * A list of notifyrc names for which no notifications should be shown.
0074      *
0075      * If the same notifyrc name is present in both blacklist and whitelist,
0076      * the blacklist takes precedence, i.e. the notification is not shown.
0077      */
0078     Q_PROPERTY(QStringList blacklistedNotifyRcNames READ blacklistedNotifyRcNames WRITE setBlacklistedNotifyRcNames NOTIFY blacklistedNotifyRcNamesChanged)
0079 
0080     /**
0081      * A list of desktop entries for which notifications should be shown.
0082      *
0083      * This bypasses any filtering for urgency.
0084      *
0085      * If the same desktop entry is present in both whitelist and blacklist,
0086      * the blacklist takes precedence, i.e. the notification is not shown.
0087      *
0088      * Default is empty list, which means normal filtering is applied.
0089      */
0090     Q_PROPERTY(QStringList whitelistedDesktopEntries READ whitelistedDesktopEntries WRITE setWhitelistedDesktopEntries NOTIFY whitelistedDesktopEntriesChanged)
0091 
0092     /**
0093      * A list of notifyrc names for which notifications should be shown.
0094      *
0095      * This bypasses any filtering for urgency.
0096      *
0097      * If the same notifyrc name is present in both whitelist and blacklist,
0098      * the blacklist takes precedence, i.e. the notification is not shown.
0099      *
0100      * Default is empty list, which means normal filtering is applied.
0101      */
0102     Q_PROPERTY(QStringList whitelistedNotifyRcNames READ whitelistedNotifyRcNames WRITE setWhitelistedNotifyRcNames NOTIFY whitelistedNotifyRcNamesChanged)
0103 
0104     /**
0105      * Whether to show notifications.
0106      *
0107      * Default is true.
0108      */
0109     Q_PROPERTY(bool showNotifications READ showNotifications WRITE setShowNotifications NOTIFY showNotificationsChanged)
0110 
0111     /**
0112      * Whether to show application jobs.
0113      *
0114      * Default is false.
0115      */
0116     Q_PROPERTY(bool showJobs READ showJobs WRITE setShowJobs NOTIFY showJobsChanged)
0117 
0118     /**
0119      * The notification urgency types the model should contain.
0120      *
0121      * Default is all urgencies: low, normal, critical.
0122      */
0123     Q_PROPERTY(Urgencies urgencies READ urgencies WRITE setUrgencies NOTIFY urgenciesChanged)
0124 
0125     /**
0126      * The sort mode for notifications.
0127      *
0128      * Default is strictly by date created/updated.
0129      */
0130     Q_PROPERTY(SortMode sortMode READ sortMode WRITE setSortMode NOTIFY sortModeChanged)
0131 
0132     /**
0133      * The sort order for notifications.
0134      *
0135      * This only affects the sort order by date. When @c sortMode is set to SortByTypeAndUrgency
0136      * the order of notification groups (e.g. high - jobs - normal - low) is unaffected, and only
0137      * notifications within the same group are either sorted ascending or descending by their
0138      * creation/update date.
0139      *
0140      * Default is DescendingOrder, i.e. newest notifications come first.
0141      *
0142      * @since 5.19
0143      */
0144     Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged)
0145 
0146     /**
0147      * The group mode for notifications.
0148      *
0149      * Default is ungrouped.
0150      */
0151     Q_PROPERTY(GroupMode groupMode READ groupMode WRITE setGroupMode NOTIFY groupModeChanged)
0152 
0153     /**
0154      * How many notifications are shown in each group.
0155      *
0156      * You can expand a group by setting the IsGroupExpandedRole to true.
0157      *
0158      * Default is 0, which means no limit.
0159      */
0160     Q_PROPERTY(int groupLimit READ groupLimit WRITE setGroupLimit NOTIFY groupLimitChanged)
0161 
0162     /**
0163      * Whether to automatically show notifications that are unread.
0164      *
0165      * This is any notification that was created or updated after the value of @c lastRead.
0166      */
0167     Q_PROPERTY(bool expandUnread READ expandUnread WRITE setExpandUnread NOTIFY expandUnreadChanged)
0168 
0169     /**
0170      * The number of notifications in the model
0171      */
0172     Q_PROPERTY(int count READ count NOTIFY countChanged)
0173 
0174     /**
0175      * The number of active, i.e. non-expired notifications
0176      */
0177     Q_PROPERTY(int activeNotificationsCount READ activeNotificationsCount NOTIFY activeNotificationsCountChanged)
0178 
0179     /**
0180      * The number of inactive, i.e. non-expired notifications
0181      */
0182     Q_PROPERTY(int expiredNotificationsCount READ expiredNotificationsCount NOTIFY expiredNotificationsCountChanged)
0183 
0184     /**
0185      * The time when the user last could read the notifications.
0186      * This is typically reset whenever the list of notifications is opened and is used to determine
0187      * the @c unreadNotificationsCount
0188      */
0189     Q_PROPERTY(QDateTime lastRead READ lastRead WRITE setLastRead RESET resetLastRead NOTIFY lastReadChanged)
0190 
0191     /**
0192      * The number of notifications added since lastRead
0193      *
0194      * This can be used to show a "n unread notifications" label
0195      */
0196     Q_PROPERTY(int unreadNotificationsCount READ unreadNotificationsCount NOTIFY unreadNotificationsCountChanged)
0197 
0198     /**
0199      * The number of active jobs
0200      */
0201     Q_PROPERTY(int activeJobsCount READ activeJobsCount NOTIFY activeJobsCountChanged)
0202     /**
0203      * The combined percentage of all jobs.
0204      *
0205      * This is the average of all percentages and could can be used to show
0206      * a global progress bar.
0207      */
0208     Q_PROPERTY(int jobsPercentage READ jobsPercentage NOTIFY jobsPercentageChanged)
0209 
0210     /**
0211      * The window that will render the notifications
0212      *
0213      * This is used to tell the xdg_activation_v1 protocol who is requesting the activation.
0214      */
0215     Q_PROPERTY(QWindow *window READ window WRITE setWindow NOTIFY windowChanged)
0216 public:
0217     explicit Notifications(QObject *parent = nullptr);
0218     ~Notifications() override;
0219 
0220     enum Roles {
0221         IdRole = Qt::UserRole + 1, ///< A notification identifier. This can be uint notification ID or string application job source.
0222         SummaryRole = Qt::DisplayRole, ///< The notification summary.
0223         ImageRole = Qt::DecorationRole, ///< The notification main image, which is not the application icon. Only valid for pixmap icons.
0224 
0225         IsGroupRole = Qt::UserRole + 2, ///< Whether the item is a group
0226         GroupChildrenCountRole, ///< The number of children in a group.
0227         ExpandedGroupChildrenCountRole, ///< The number of children in a group that are expanded.
0228         IsGroupExpandedRole, ///< Whether the group is expanded, this role is writable.
0229 
0230         IsInGroupRole, ///< Whether the notification is currently inside a group.
0231         TypeRole, ///< The type of model entry, either NotificationType or JobType.
0232         CreatedRole, ///< When the notification was first created.
0233         UpdatedRole, ///< When the notification was last updated, invalid when it hasn't been updated.
0234 
0235         BodyRole, ///< The notification body text.
0236         IconNameRole, ///< The notification main icon name, which is not the application icon. Only valid for icon names, if a URL supplied, it is loaded and
0237                       ///< exposed as ImageRole instead.
0238 
0239         DesktopEntryRole, ///< The desktop entry (without .desktop suffix, e.g. org.kde.spectacle) of the application that sent the notification.
0240         NotifyRcNameRole, ///< The notifyrc name (e.g. spectaclerc) of the application that sent the notification.
0241 
0242         ApplicationNameRole, ///< The user-visible name of the application (e.g. Spectacle)
0243         ApplicationIconNameRole, ///< The icon name of the application
0244         OriginNameRole, ///< The name of the device or account the notification originally came from, e.g. "My Phone" (in case of device sync) or
0245                         ///< "foo@example.com" (in case of an email notification)
0246 
0247         // Jobs
0248         JobStateRole, ///< The state of the job, either JobStateJopped, JobStateSuspended, or JobStateRunning.
0249         PercentageRole, ///< The percentage of the job. Use @c jobsPercentage to get a global percentage for all jobs.
0250         JobErrorRole, ///< The error id of the job, zero in case of no error.
0251         SuspendableRole, ///< Whether the job can be suspended @sa suspendJob
0252         KillableRole, ///< Whether the job can be killed/canceled @sa killJob
0253         JobDetailsRole, ///< A pointer to a Job item itself containing more detailed information about the job
0254 
0255         ActionNamesRole, ///< The IDs of the actions, excluding the default and settings action, e.g. [action1, action2]
0256         ActionLabelsRole, ///< The user-visible labels of the actions, excluding the default and settings action, e.g. ["Accept", "Reject"]
0257         HasDefaultActionRole, ///< Whether the notification has a default action, which is one that is invoked when the popup itself is clicked
0258         DefaultActionLabelRole, ///< The user-visible label of the default action, typically not shown as the popup itself becomes clickable
0259 
0260         UrlsRole, ///< A list of URLs associated with the notification, e.g. a path to a screenshot that was just taken or image received
0261 
0262         UrgencyRole, ///< The notification urgency, either LowUrgency, NormalUrgency, or CriticalUrgency. Jobs do not have an urgency.
0263         TimeoutRole, ///< The timeout for the notification in milliseconds. 0 means the notification should not timeout, -1 means a sensible default should be
0264                      ///< applied.
0265 
0266         ConfigurableRole, ///< Whether the notification can be configured because a desktopEntry or notifyRcName is known, or the notification has a setting
0267                           ///< action. @sa configure
0268         ConfigureActionLabelRole, ///< The user-visible label for the settings action
0269         ClosableRole, ///< Whether the item can be closed. Notifications are always closable, jobs are only when in JobStateStopped.
0270 
0271         ExpiredRole, ///< The notification timed out and closed. Actions on it cannot be invoked anymore.
0272         DismissedRole, ///< The notification got temporarily hidden by the user but could still be interacted with.
0273         ReadRole, ///< Whether the notification got read by the user. If true, the notification isn't considered unread even if created after lastRead.
0274                   ///< @since 5.17
0275 
0276         UserActionFeedbackRole, ///< Whether this notification is a response/confirmation to an explicit user action. @since 5.18
0277 
0278         HasReplyActionRole, ///< Whether the notification has a reply action. @since 5.18
0279         ReplyActionLabelRole, ///< The user-visible label for the reply action. @since 5.18
0280         ReplyPlaceholderTextRole, ///< A custom placeholder text for the reply action, e.g. "Reply to Max...". @since 5.18
0281         ReplySubmitButtonTextRole, ///< A custom text for the reply submit button, e.g. "Submit Comment". @since 5.18
0282         ReplySubmitButtonIconNameRole, ///< A custom icon name for the reply submit button. @since 5.18
0283         CategoryRole, ///< The (optional) category of the notification. Notifications can optionally have a type indicator. Although neither client or nor
0284                       ///< server must support this, some may choose to. Those servers implementing categories may use them to intelligently display the
0285                       ///< notification in a certain way, or group notifications of similar types.  @since 5.21
0286         ResidentRole, ///< Whether the notification should keep its actions even when they were invoked. @since 5.22
0287         TransientRole, ///< Whether the notification is transient and should not be kept in history. @since 5.22
0288     };
0289     Q_ENUM(Roles)
0290 
0291     /**
0292      * The type of model item.
0293      */
0294     enum Type {
0295         NoType,
0296         NotificationType, ///< This item represents a notification.
0297         JobType, ///< This item represents an application job.
0298     };
0299     Q_ENUM(Type)
0300 
0301     /**
0302      * The notification urgency.
0303      *
0304      * @note jobs do not have an urgency, yet still might be above normal urgency notifications.
0305      */
0306     enum Urgency {
0307         // these don't match the spec's value
0308         LowUrgency = 1 << 0, ///< The notification has low urgency, it is not important and may not be shown or added to a history.
0309         NormalUrgency = 1 << 1, ///< The notification has normal urgency. This is also the default if no urgecny is supplied.
0310         CriticalUrgency = 1 << 2,
0311     };
0312     Q_ENUM(Urgency)
0313     Q_DECLARE_FLAGS(Urgencies, Urgency)
0314     Q_FLAG(Urgencies)
0315 
0316     /**
0317      * Which items should be cleared in a call to @c clear
0318      */
0319     enum ClearFlag {
0320         ClearExpired = 1 << 1,
0321         // TODO more
0322     };
0323     Q_ENUM(ClearFlag)
0324     Q_DECLARE_FLAGS(ClearFlags, ClearFlag)
0325     Q_FLAG(ClearFlags)
0326 
0327     /**
0328      * The state an application job is in.
0329      */
0330     enum JobState {
0331         JobStateStopped, ///< The job is stopped. It has either finished (error is 0) or failed (error is not 0)
0332         JobStateRunning, ///< The job is currently running.
0333         JobStateSuspended, ///< The job is currentl paused
0334     };
0335     Q_ENUM(JobState)
0336 
0337     /**
0338      * The sort mode for the model.
0339      */
0340     enum SortMode {
0341         SortByDate = 0, ///< Sort notifications strictly by the date they were updated or created.
0342         // should this be flags? SortJobsFirst | SortByUrgency | ...?
0343         SortByTypeAndUrgency, ///< Sort notifications taking into account their type and urgency. The order is (descending): Critical, jobs, Normal, Low.
0344     };
0345     Q_ENUM(SortMode)
0346 
0347     /**
0348      * The group mode for the model.
0349      */
0350     enum GroupMode {
0351         GroupDisabled = 0,
0352         // GroupApplicationsTree, // TODO make actual tree
0353         GroupApplicationsFlat,
0354     };
0355     Q_ENUM(GroupMode)
0356 
0357     enum InvokeBehavior {
0358         None = 0,
0359         Close = 1,
0360     };
0361     Q_ENUM(InvokeBehavior)
0362     Q_DECLARE_FLAGS(InvokeBehaviors, InvokeBehavior)
0363     Q_FLAG(InvokeBehaviors)
0364 
0365     int limit() const;
0366     void setLimit(int limit);
0367 
0368     bool showExpired() const;
0369     void setShowExpired(bool show);
0370 
0371     bool showDismissed() const;
0372     void setShowDismissed(bool show);
0373 
0374     QStringList blacklistedDesktopEntries() const;
0375     void setBlacklistedDesktopEntries(const QStringList &blacklist);
0376 
0377     QStringList blacklistedNotifyRcNames() const;
0378     void setBlacklistedNotifyRcNames(const QStringList &blacklist);
0379 
0380     QStringList whitelistedDesktopEntries() const;
0381     void setWhitelistedDesktopEntries(const QStringList &whitelist);
0382 
0383     QStringList whitelistedNotifyRcNames() const;
0384     void setWhitelistedNotifyRcNames(const QStringList &whitelist);
0385 
0386     bool showNotifications() const;
0387     void setShowNotifications(bool showNotifications);
0388 
0389     bool showJobs() const;
0390     void setShowJobs(bool showJobs);
0391 
0392     Urgencies urgencies() const;
0393     void setUrgencies(Urgencies urgencies);
0394 
0395     SortMode sortMode() const;
0396     void setSortMode(SortMode sortMode);
0397 
0398     Qt::SortOrder sortOrder() const;
0399     void setSortOrder(Qt::SortOrder sortOrder);
0400 
0401     GroupMode groupMode() const;
0402     void setGroupMode(GroupMode groupMode);
0403 
0404     int groupLimit() const;
0405     void setGroupLimit(int limit);
0406 
0407     bool expandUnread() const;
0408     void setExpandUnread(bool expand);
0409 
0410     QWindow *window() const;
0411     void setWindow(QWindow *window);
0412 
0413     int count() const;
0414 
0415     int activeNotificationsCount() const;
0416     int expiredNotificationsCount() const;
0417 
0418     QDateTime lastRead() const;
0419     void setLastRead(const QDateTime &lastRead);
0420     void resetLastRead();
0421 
0422     int unreadNotificationsCount() const;
0423 
0424     int activeJobsCount() const;
0425     int jobsPercentage() const;
0426 
0427     /**
0428      * Convert the given QModelIndex into a QPersistentModelIndex
0429      */
0430     Q_INVOKABLE QPersistentModelIndex makePersistentModelIndex(const QModelIndex &idx) const;
0431 
0432     /**
0433      * @brief Expire a notification
0434      *
0435      * Closes the notification in response to its timeout running out.
0436      *
0437      * Call this if you have an implementation that handles the timeout itself
0438      * by having called @c stopTimeout
0439      *
0440      * @sa stopTimeout
0441      */
0442     Q_INVOKABLE void expire(const QModelIndex &idx);
0443     /**
0444      * @brief Close a notification
0445      *
0446      * Closes the notification in response to the user explicitly closing it.
0447      *
0448      * When the model index belongs to a group, the entire group is closed.
0449      */
0450     Q_INVOKABLE void close(const QModelIndex &idx);
0451     /**
0452      * @brief Configure a notification
0453      *
0454      * This will invoke the settings action, if available, otherwise open the
0455      * kcm_notifications KCM for configuring the respective application and event.
0456      */
0457     Q_INVOKABLE void configure(const QModelIndex &idx); // TODO pass ctx for transient handling
0458     /**
0459      * @brief Invoke the default notification action
0460      *
0461      * Invokes the action that should be triggered when clicking
0462      * the notification bubble itself.
0463      */
0464     Q_INVOKABLE void invokeDefaultAction(const QModelIndex &idx, InvokeBehavior behavior = None);
0465     /**
0466      * @brief Invoke a notification action
0467      *
0468      * Invokes the action with the given actionId on the notification.
0469      * For invoking the default action, i.e. the one that is triggered
0470      * when clicking the notification bubble, use invokeDefaultAction
0471      */
0472     Q_INVOKABLE void invokeAction(const QModelIndex &idx, const QString &actionId, InvokeBehavior = None);
0473 
0474     /**
0475      * @brief Reply to a notification
0476      *
0477      * Replies to the given notification with the given text.
0478      * @since 5.18
0479      */
0480     Q_INVOKABLE void reply(const QModelIndex &idx, const QString &text, InvokeBehavior behavior);
0481 
0482     /**
0483      * @brief Start automatic timeout of notifications
0484      *
0485      * Call this if you no longer handle the timeout yourself.
0486      *
0487      * @sa stopTimeout
0488      */
0489     Q_INVOKABLE void startTimeout(const QModelIndex &idx);
0490 
0491     Q_INVOKABLE void startTimeout(uint notificationId);
0492     /**
0493      * @brief Stop the automatic timeout of notifications
0494      *
0495      * Call this if you have an implementation that handles the timeout itself
0496      * taking into account e.g. whether the user is currently interacting with
0497      * the notification to not close it under their mouse. Call @c expire
0498      * once your custom timer has run out.
0499      *
0500      * @sa expire
0501      */
0502     Q_INVOKABLE void stopTimeout(const QModelIndex &idx);
0503 
0504     /**
0505      * @brief Suspend a job
0506      */
0507     Q_INVOKABLE void suspendJob(const QModelIndex &idx);
0508     /**
0509      * @brief Resume a job
0510      */
0511     Q_INVOKABLE void resumeJob(const QModelIndex &idx);
0512     /**
0513      * @brief Kill a job
0514      */
0515     Q_INVOKABLE void killJob(const QModelIndex &idx);
0516 
0517     /**
0518      * @brief Clear notifications
0519      *
0520      * Removes the notifications matching th ClearFlags from the model.
0521      * This can be used for e.g. a "Clear History" action.
0522      */
0523     Q_INVOKABLE void clear(ClearFlags flags);
0524 
0525     /**
0526      * Returns a model index pointing to the group of a notification.
0527      */
0528     Q_INVOKABLE QModelIndex groupIndex(const QModelIndex &idx) const;
0529 
0530     Q_INVOKABLE void collapseAllGroups();
0531 
0532     QVariant data(const QModelIndex &index, int role) const override;
0533     bool setData(const QModelIndex &index, const QVariant &value, int role) override;
0534     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
0535     QHash<int, QByteArray> roleNames() const override;
0536 
0537     bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
0538     bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
0539 
0540 Q_SIGNALS:
0541     void limitChanged();
0542     void showExpiredChanged();
0543     void showDismissedChanged();
0544     void blacklistedDesktopEntriesChanged();
0545     void blacklistedNotifyRcNamesChanged();
0546     void whitelistedDesktopEntriesChanged();
0547     void whitelistedNotifyRcNamesChanged();
0548     void showNotificationsChanged();
0549     void showJobsChanged();
0550     void urgenciesChanged();
0551     void sortModeChanged();
0552     void sortOrderChanged();
0553     void groupModeChanged();
0554     void groupLimitChanged();
0555     void expandUnreadChanged();
0556     void countChanged();
0557     void activeNotificationsCountChanged();
0558     void expiredNotificationsCountChanged();
0559     void lastReadChanged();
0560     void unreadNotificationsCountChanged();
0561     void activeJobsCountChanged();
0562     void jobsPercentageChanged();
0563     void windowChanged(QWindow *window);
0564 
0565 protected:
0566     void classBegin() override;
0567     void componentComplete() override;
0568 
0569 private:
0570     class Private;
0571     std::unique_ptr<Private> d;
0572 };
0573 
0574 } // namespace NotificationManager
0575 
0576 Q_DECLARE_OPERATORS_FOR_FLAGS(NotificationManager::Notifications::Urgencies)