File indexing completed on 2024-04-28 16:54:37

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