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

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 #include "notifications.h"
0008 
0009 #include <QConcatenateTablesProxyModel>
0010 #include <QDebug>
0011 #include <QMetaEnum>
0012 #include <memory>
0013 
0014 #include <KDescendantsProxyModel>
0015 
0016 #include "limitedrowcountproxymodel_p.h"
0017 #include "notificationfilterproxymodel_p.h"
0018 #include "notificationgroupcollapsingproxymodel_p.h"
0019 #include "notificationgroupingproxymodel_p.h"
0020 #include "notificationsmodel.h"
0021 #include "notificationsortproxymodel_p.h"
0022 
0023 #include "jobsmodel.h"
0024 
0025 #include "settings.h"
0026 
0027 #include "notification.h"
0028 
0029 #include "utils_p.h"
0030 
0031 #include "debug.h"
0032 
0033 using namespace NotificationManager;
0034 
0035 class Q_DECL_HIDDEN Notifications::Private
0036 {
0037 public:
0038     explicit Private(Notifications *q);
0039     ~Private();
0040 
0041     void initSourceModels();
0042     void initProxyModels();
0043 
0044     void updateCount();
0045 
0046     bool showNotifications = true;
0047     bool showJobs = false;
0048 
0049     Notifications::GroupMode groupMode = Notifications::GroupDisabled;
0050     int groupLimit = 0;
0051     bool expandUnread = false;
0052 
0053     int activeNotificationsCount = 0;
0054     int expiredNotificationsCount = 0;
0055 
0056     int unreadNotificationsCount = 0;
0057 
0058     int activeJobsCount = 0;
0059     int jobsPercentage = 0;
0060 
0061     static bool isGroup(const QModelIndex &idx);
0062     static uint notificationId(const QModelIndex &idx);
0063     QModelIndex mapFromModel(const QModelIndex &idx) const;
0064 
0065     // NOTE when you add or re-arrange models make sure to update mapFromModel()!
0066     NotificationsModel::Ptr notificationsModel;
0067     JobsModel::Ptr jobsModel;
0068     std::shared_ptr<Settings> settings() const;
0069 
0070     QConcatenateTablesProxyModel *notificationsAndJobsModel = nullptr;
0071 
0072     NotificationFilterProxyModel *filterModel = nullptr;
0073     NotificationSortProxyModel *sortModel = nullptr;
0074     NotificationGroupingProxyModel *groupingModel = nullptr;
0075     NotificationGroupCollapsingProxyModel *groupCollapsingModel = nullptr;
0076     KDescendantsProxyModel *flattenModel = nullptr;
0077 
0078     LimitedRowCountProxyModel *limiterModel = nullptr;
0079 
0080 private:
0081     Notifications *q;
0082 };
0083 
0084 Notifications::Private::Private(Notifications *q)
0085     : q(q)
0086 {
0087 }
0088 
0089 Notifications::Private::~Private()
0090 {
0091 }
0092 
0093 void Notifications::Private::initSourceModels()
0094 {
0095     Q_ASSERT(notificationsAndJobsModel); // initProxyModels must be called before initSourceModels
0096 
0097     if (showNotifications && !notificationsModel) {
0098         notificationsModel = NotificationsModel::createNotificationsModel();
0099         notificationsAndJobsModel->addSourceModel(notificationsModel.get());
0100         connect(notificationsModel.get(), &NotificationsModel::lastReadChanged, q, [this] {
0101             updateCount();
0102             Q_EMIT q->lastReadChanged();
0103         });
0104     } else if (!showNotifications && notificationsModel) {
0105         notificationsAndJobsModel->removeSourceModel(notificationsModel.get());
0106         disconnect(notificationsModel.get(), nullptr, q, nullptr); // disconnect all
0107         notificationsModel = nullptr;
0108     }
0109 
0110     if (showJobs && !jobsModel) {
0111         jobsModel = JobsModel::createJobsModel();
0112         notificationsAndJobsModel->addSourceModel(jobsModel.get());
0113         jobsModel->init();
0114     } else if (!showJobs && jobsModel) {
0115         notificationsAndJobsModel->removeSourceModel(jobsModel.get());
0116         jobsModel = nullptr;
0117     }
0118 }
0119 
0120 void Notifications::Private::initProxyModels()
0121 {
0122     /* The data flow is as follows:
0123      * NOTE when you add or re-arrange models make sure to update mapFromModel()!
0124      *
0125      * NotificationsModel      JobsModel
0126      *        \\                 /
0127      *         \\               /
0128      *     QConcatenateTablesProxyModel
0129      *               |||
0130      *               |||
0131      *     NotificationFilterProxyModel
0132      *     (filters by urgency, whitelist, etc)
0133      *                |
0134      *                |
0135      *      NotificationSortProxyModel
0136      *      (sorts by urgency, date, etc)
0137      *                |
0138      * --- BEGIN: Only when grouping is enabled ---
0139      *                |
0140      *    NotificationGroupingProxyModel
0141      *    (turns list into tree grouped by app)
0142      *               //\\
0143      *               //\\
0144      *   NotificationGroupCollapsingProxyModel
0145      *   (limits number of tree leaves for expand/collapse feature)
0146      *                /\
0147      *                /\
0148      *       KDescendantsProxyModel
0149      *       (flattens tree back into a list for consumption in ListView)
0150      *                |
0151      * --- END: Only when grouping is enabled ---
0152      *                |
0153      *    LimitedRowCountProxyModel
0154      *    (limits the total number of items in the model)
0155      *                |
0156      *                |
0157      *               \o/ <- Happy user seeing their notifications
0158      */
0159 
0160     if (!notificationsAndJobsModel) {
0161         notificationsAndJobsModel = new QConcatenateTablesProxyModel(q);
0162     }
0163 
0164     if (!filterModel) {
0165         filterModel = new NotificationFilterProxyModel();
0166         connect(filterModel, &NotificationFilterProxyModel::urgenciesChanged, q, &Notifications::urgenciesChanged);
0167         connect(filterModel, &NotificationFilterProxyModel::showExpiredChanged, q, &Notifications::showExpiredChanged);
0168         connect(filterModel, &NotificationFilterProxyModel::showDismissedChanged, q, &Notifications::showDismissedChanged);
0169         connect(filterModel, &NotificationFilterProxyModel::blacklistedDesktopEntriesChanged, q, &Notifications::blacklistedDesktopEntriesChanged);
0170         connect(filterModel, &NotificationFilterProxyModel::blacklistedNotifyRcNamesChanged, q, &Notifications::blacklistedNotifyRcNamesChanged);
0171 
0172         filterModel->setSourceModel(notificationsAndJobsModel);
0173 
0174         connect(filterModel, &QAbstractItemModel::rowsInserted, q, [this] {
0175             updateCount();
0176         });
0177         connect(filterModel, &QAbstractItemModel::rowsRemoved, q, [this] {
0178             updateCount();
0179         });
0180         connect(filterModel, &QAbstractItemModel::dataChanged, q, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles) {
0181             Q_UNUSED(topLeft);
0182             Q_UNUSED(bottomRight);
0183             if (roles.isEmpty() || roles.contains(Notifications::UpdatedRole) || roles.contains(Notifications::ExpiredRole)
0184                 || roles.contains(Notifications::JobStateRole) || roles.contains(Notifications::PercentageRole) || roles.contains(Notifications::ReadRole)) {
0185                 updateCount();
0186             }
0187         });
0188     }
0189 
0190     if (!sortModel) {
0191         sortModel = new NotificationSortProxyModel(q);
0192         connect(sortModel, &NotificationSortProxyModel::sortModeChanged, q, &Notifications::sortModeChanged);
0193         connect(sortModel, &NotificationSortProxyModel::sortOrderChanged, q, &Notifications::sortOrderChanged);
0194     }
0195 
0196     if (!limiterModel) {
0197         limiterModel = new LimitedRowCountProxyModel(q);
0198         connect(limiterModel, &LimitedRowCountProxyModel::limitChanged, q, &Notifications::limitChanged);
0199     }
0200 
0201     if (groupMode == GroupApplicationsFlat) {
0202         if (!groupingModel) {
0203             groupingModel = new NotificationGroupingProxyModel(q);
0204             groupingModel->setSourceModel(filterModel);
0205         }
0206 
0207         if (!groupCollapsingModel) {
0208             groupCollapsingModel = new NotificationGroupCollapsingProxyModel(q);
0209             groupCollapsingModel->setLimit(groupLimit);
0210             groupCollapsingModel->setExpandUnread(expandUnread);
0211             groupCollapsingModel->setLastRead(q->lastRead());
0212             groupCollapsingModel->setSourceModel(groupingModel);
0213         }
0214 
0215         sortModel->setSourceModel(groupCollapsingModel);
0216 
0217         flattenModel = new KDescendantsProxyModel(q);
0218         flattenModel->setSourceModel(sortModel);
0219 
0220         limiterModel->setSourceModel(flattenModel);
0221     } else {
0222         sortModel->setSourceModel(filterModel);
0223         limiterModel->setSourceModel(sortModel);
0224         delete flattenModel;
0225         flattenModel = nullptr;
0226         delete groupingModel;
0227         groupingModel = nullptr;
0228     }
0229 
0230     q->setSourceModel(limiterModel);
0231 }
0232 
0233 void Notifications::Private::updateCount()
0234 {
0235     int active = 0;
0236     int expired = 0;
0237     int unread = 0;
0238 
0239     int jobs = 0;
0240     int totalPercentage = 0;
0241 
0242     // We want to get the numbers after main filtering (urgencies, whitelists, etc)
0243     // but before any limiting or group limiting, hence asking the filterModel for advice
0244     // at which point notifications and jobs also have already been merged
0245     for (int i = 0; i < filterModel->rowCount(); ++i) {
0246         const QModelIndex idx = filterModel->index(i, 0);
0247 
0248         if (idx.data(Notifications::ExpiredRole).toBool()) {
0249             ++expired;
0250         } else {
0251             ++active;
0252         }
0253 
0254         const bool read = idx.data(Notifications::ReadRole).toBool();
0255         if (!active && !read) {
0256             QDateTime date = idx.data(Notifications::UpdatedRole).toDateTime();
0257             if (!date.isValid()) {
0258                 date = idx.data(Notifications::CreatedRole).toDateTime();
0259             }
0260 
0261             if (notificationsModel && date > notificationsModel->lastRead()) {
0262                 ++unread;
0263             }
0264         }
0265 
0266         if (idx.data(Notifications::TypeRole).toInt() == Notifications::JobType) {
0267             if (idx.data(Notifications::JobStateRole).toInt() != Notifications::JobStateStopped) {
0268                 ++jobs;
0269 
0270                 totalPercentage += idx.data(Notifications::PercentageRole).toInt();
0271             }
0272         }
0273     }
0274 
0275     if (activeNotificationsCount != active) {
0276         activeNotificationsCount = active;
0277         Q_EMIT q->activeNotificationsCountChanged();
0278     }
0279     if (expiredNotificationsCount != expired) {
0280         expiredNotificationsCount = expired;
0281         Q_EMIT q->expiredNotificationsCountChanged();
0282     }
0283     if (unreadNotificationsCount != unread) {
0284         unreadNotificationsCount = unread;
0285         Q_EMIT q->unreadNotificationsCountChanged();
0286     }
0287     if (activeJobsCount != jobs) {
0288         activeJobsCount = jobs;
0289         Q_EMIT q->activeJobsCountChanged();
0290     }
0291 
0292     const int percentage = (jobs > 0 ? totalPercentage / jobs : 0);
0293     if (jobsPercentage != percentage) {
0294         jobsPercentage = percentage;
0295         Q_EMIT q->jobsPercentageChanged();
0296     }
0297 
0298     // TODO don't Q_EMIT in dataChanged
0299     Q_EMIT q->countChanged();
0300 }
0301 
0302 bool Notifications::Private::isGroup(const QModelIndex &idx)
0303 {
0304     return idx.data(Notifications::IsGroupRole).toBool();
0305 }
0306 
0307 uint Notifications::Private::notificationId(const QModelIndex &idx)
0308 {
0309     return idx.data(Notifications::IdRole).toUInt();
0310 }
0311 
0312 QModelIndex Notifications::Private::mapFromModel(const QModelIndex &idx) const
0313 {
0314     QModelIndex resolvedIdx = idx;
0315 
0316     QAbstractItemModel *models[] = {
0317         notificationsAndJobsModel,
0318         filterModel,
0319         sortModel,
0320         groupingModel,
0321         groupCollapsingModel,
0322         flattenModel,
0323         limiterModel,
0324     };
0325 
0326     // TODO can we do this with a generic loop like mapFromModel
0327     while (resolvedIdx.isValid() && resolvedIdx.model() != q) {
0328         const auto *idxModel = resolvedIdx.model();
0329 
0330         // HACK try to find the model that uses the index' model as source
0331         bool found = false;
0332         for (QAbstractItemModel *model : models) {
0333             if (!model) {
0334                 continue;
0335             }
0336 
0337             if (auto *proxyModel = qobject_cast<QAbstractProxyModel *>(model)) {
0338                 if (proxyModel->sourceModel() == idxModel) {
0339                     resolvedIdx = proxyModel->mapFromSource(resolvedIdx);
0340                     found = true;
0341                     break;
0342                 }
0343             } else if (auto *concatenateModel = qobject_cast<QConcatenateTablesProxyModel *>(model)) {
0344                 if (idxModel == notificationsModel.get() || idxModel == jobsModel.get()) {
0345                     resolvedIdx = concatenateModel->mapFromSource(resolvedIdx);
0346                     found = true;
0347                     break;
0348                 }
0349             }
0350         }
0351 
0352         if (!found) {
0353             break;
0354         }
0355     }
0356     return resolvedIdx;
0357 }
0358 
0359 std::shared_ptr<Settings> Notifications::Private::settings() const
0360 {
0361     static std::weak_ptr<Settings> s_instance;
0362     if (!s_instance.expired()) {
0363         std::shared_ptr<Settings> ptr(new Settings());
0364         s_instance = ptr;
0365         return ptr;
0366     }
0367     return s_instance.lock();
0368 }
0369 
0370 Notifications::Notifications(QObject *parent)
0371     : QSortFilterProxyModel(parent)
0372     , d(new Private(this))
0373 {
0374     // The proxy models are always the same, just with different
0375     // properties set whereas we want to avoid loading a source model
0376     // e.g. notifications or jobs when we're not actually using them
0377     d->initProxyModels();
0378 
0379     // init source models when used from C++
0380     QMetaObject::invokeMethod(
0381         this,
0382         [this] {
0383             d->initSourceModels();
0384         },
0385         Qt::QueuedConnection);
0386 }
0387 
0388 Notifications::~Notifications() = default;
0389 
0390 void Notifications::classBegin()
0391 {
0392 }
0393 
0394 void Notifications::componentComplete()
0395 {
0396     // init source models when used from QML
0397     d->initSourceModels();
0398 }
0399 
0400 int Notifications::limit() const
0401 {
0402     return d->limiterModel->limit();
0403 }
0404 
0405 void Notifications::setLimit(int limit)
0406 {
0407     d->limiterModel->setLimit(limit);
0408 }
0409 
0410 int Notifications::groupLimit() const
0411 {
0412     return d->groupLimit;
0413 }
0414 
0415 void Notifications::setGroupLimit(int limit)
0416 {
0417     if (d->groupLimit == limit) {
0418         return;
0419     }
0420 
0421     d->groupLimit = limit;
0422     if (d->groupCollapsingModel) {
0423         d->groupCollapsingModel->setLimit(limit);
0424     }
0425     Q_EMIT groupLimitChanged();
0426 }
0427 
0428 bool Notifications::expandUnread() const
0429 {
0430     return d->expandUnread;
0431 }
0432 
0433 void Notifications::setExpandUnread(bool expand)
0434 {
0435     if (d->expandUnread == expand) {
0436         return;
0437     }
0438 
0439     d->expandUnread = expand;
0440     if (d->groupCollapsingModel) {
0441         d->groupCollapsingModel->setExpandUnread(expand);
0442     }
0443     Q_EMIT expandUnreadChanged();
0444 }
0445 
0446 QWindow *Notifications::window() const
0447 {
0448     return d->notificationsModel ? d->notificationsModel->window() : nullptr;
0449 }
0450 
0451 void Notifications::setWindow(QWindow *window)
0452 {
0453     if (d->notificationsModel) {
0454         d->notificationsModel->setWindow(window);
0455     } else {
0456         qCWarning(NOTIFICATIONMANAGER) << "Setting window before initialising the model" << this << window;
0457     }
0458 }
0459 
0460 bool Notifications::showExpired() const
0461 {
0462     return d->filterModel->showExpired();
0463 }
0464 
0465 void Notifications::setShowExpired(bool show)
0466 {
0467     d->filterModel->setShowExpired(show);
0468 }
0469 
0470 bool Notifications::showDismissed() const
0471 {
0472     return d->filterModel->showDismissed();
0473 }
0474 
0475 void Notifications::setShowDismissed(bool show)
0476 {
0477     d->filterModel->setShowDismissed(show);
0478 }
0479 
0480 QStringList Notifications::blacklistedDesktopEntries() const
0481 {
0482     return d->filterModel->blacklistedDesktopEntries();
0483 }
0484 
0485 void Notifications::setBlacklistedDesktopEntries(const QStringList &blacklist)
0486 {
0487     d->filterModel->setBlackListedDesktopEntries(blacklist);
0488 }
0489 
0490 QStringList Notifications::blacklistedNotifyRcNames() const
0491 {
0492     return d->filterModel->blacklistedNotifyRcNames();
0493 }
0494 
0495 void Notifications::setBlacklistedNotifyRcNames(const QStringList &blacklist)
0496 {
0497     d->filterModel->setBlacklistedNotifyRcNames(blacklist);
0498 }
0499 
0500 QStringList Notifications::whitelistedDesktopEntries() const
0501 {
0502     return d->filterModel->whitelistedDesktopEntries();
0503 }
0504 
0505 void Notifications::setWhitelistedDesktopEntries(const QStringList &whitelist)
0506 {
0507     d->filterModel->setWhiteListedDesktopEntries(whitelist);
0508 }
0509 
0510 QStringList Notifications::whitelistedNotifyRcNames() const
0511 {
0512     return d->filterModel->whitelistedNotifyRcNames();
0513 }
0514 
0515 void Notifications::setWhitelistedNotifyRcNames(const QStringList &whitelist)
0516 {
0517     d->filterModel->setWhitelistedNotifyRcNames(whitelist);
0518 }
0519 
0520 bool Notifications::showNotifications() const
0521 {
0522     return d->showNotifications;
0523 }
0524 
0525 void Notifications::setShowNotifications(bool show)
0526 {
0527     if (d->showNotifications == show) {
0528         return;
0529     }
0530 
0531     d->showNotifications = show;
0532     d->initSourceModels();
0533     Q_EMIT showNotificationsChanged();
0534 }
0535 
0536 bool Notifications::showJobs() const
0537 {
0538     return d->showJobs;
0539 }
0540 
0541 void Notifications::setShowJobs(bool show)
0542 {
0543     if (d->showJobs == show) {
0544         return;
0545     }
0546 
0547     d->showJobs = show;
0548     d->initSourceModels();
0549     Q_EMIT showJobsChanged();
0550 }
0551 
0552 Notifications::Urgencies Notifications::urgencies() const
0553 {
0554     return d->filterModel->urgencies();
0555 }
0556 
0557 void Notifications::setUrgencies(Urgencies urgencies)
0558 {
0559     d->filterModel->setUrgencies(urgencies);
0560 }
0561 
0562 Notifications::SortMode Notifications::sortMode() const
0563 {
0564     return d->sortModel->sortMode();
0565 }
0566 
0567 void Notifications::setSortMode(SortMode sortMode)
0568 {
0569     d->sortModel->setSortMode(sortMode);
0570 }
0571 
0572 Qt::SortOrder Notifications::sortOrder() const
0573 {
0574     return d->sortModel->sortOrder();
0575 }
0576 
0577 void Notifications::setSortOrder(Qt::SortOrder sortOrder)
0578 {
0579     d->sortModel->setSortOrder(sortOrder);
0580 }
0581 
0582 Notifications::GroupMode Notifications::groupMode() const
0583 {
0584     return d->groupMode;
0585 }
0586 
0587 void Notifications::setGroupMode(GroupMode groupMode)
0588 {
0589     if (d->groupMode != groupMode) {
0590         d->groupMode = groupMode;
0591         d->initProxyModels();
0592         Q_EMIT groupModeChanged();
0593     }
0594 }
0595 
0596 int Notifications::count() const
0597 {
0598     return rowCount(QModelIndex());
0599 }
0600 
0601 int Notifications::activeNotificationsCount() const
0602 {
0603     return d->activeNotificationsCount;
0604 }
0605 
0606 int Notifications::expiredNotificationsCount() const
0607 {
0608     return d->expiredNotificationsCount;
0609 }
0610 
0611 QDateTime Notifications::lastRead() const
0612 {
0613     if (d->notificationsModel) {
0614         return d->notificationsModel->lastRead();
0615     }
0616     return QDateTime();
0617 }
0618 
0619 void Notifications::setLastRead(const QDateTime &lastRead)
0620 {
0621     // TODO jobs could also be unread?
0622     if (d->notificationsModel) {
0623         d->notificationsModel->setLastRead(lastRead);
0624     }
0625     if (d->groupCollapsingModel) {
0626         d->groupCollapsingModel->setLastRead(lastRead);
0627     }
0628 }
0629 
0630 void Notifications::resetLastRead()
0631 {
0632     setLastRead(QDateTime::currentDateTimeUtc());
0633 }
0634 
0635 int Notifications::unreadNotificationsCount() const
0636 {
0637     return d->unreadNotificationsCount;
0638 }
0639 
0640 int Notifications::activeJobsCount() const
0641 {
0642     return d->activeJobsCount;
0643 }
0644 
0645 int Notifications::jobsPercentage() const
0646 {
0647     return d->jobsPercentage;
0648 }
0649 
0650 QPersistentModelIndex Notifications::makePersistentModelIndex(const QModelIndex &idx) const
0651 {
0652     return QPersistentModelIndex(idx);
0653 }
0654 
0655 void Notifications::expire(const QModelIndex &idx)
0656 {
0657     switch (static_cast<Notifications::Type>(idx.data(Notifications::TypeRole).toInt())) {
0658     case Notifications::NotificationType:
0659         d->notificationsModel->expire(Private::notificationId(idx));
0660         break;
0661     case Notifications::JobType:
0662         d->jobsModel->expire(Utils::mapToModel(idx, d->jobsModel.get()));
0663         break;
0664     default:
0665         Q_UNREACHABLE();
0666     }
0667 }
0668 
0669 void Notifications::close(const QModelIndex &idx)
0670 {
0671     if (idx.data(Notifications::IsGroupRole).toBool()) {
0672         const QModelIndex groupIdx = Utils::mapToModel(idx, d->groupingModel);
0673         if (!groupIdx.isValid()) {
0674             qCWarning(NOTIFICATIONMANAGER) << "Failed to find group model index for this item";
0675             return;
0676         }
0677 
0678         Q_ASSERT(groupIdx.model() == d->groupingModel);
0679 
0680         const int childCount = d->groupingModel->rowCount(groupIdx);
0681         for (int i = childCount - 1; i >= 0; --i) {
0682             const QModelIndex childIdx = d->groupingModel->index(i, 0, groupIdx);
0683             close(childIdx);
0684         }
0685         return;
0686     }
0687 
0688     if (!idx.data(Notifications::ClosableRole).toBool()) {
0689         return;
0690     }
0691 
0692     switch (static_cast<Notifications::Type>(idx.data(Notifications::TypeRole).toInt())) {
0693     case Notifications::NotificationType:
0694         d->notificationsModel->close(Private::notificationId(idx));
0695         break;
0696     case Notifications::JobType:
0697         d->jobsModel->close(Utils::mapToModel(idx, d->jobsModel.get()));
0698         break;
0699     default:
0700         Q_UNREACHABLE();
0701     }
0702 }
0703 
0704 void Notifications::configure(const QModelIndex &idx)
0705 {
0706     if (!d->notificationsModel) {
0707         return;
0708     }
0709 
0710     // For groups just configure the application, not the individual event
0711     if (Private::isGroup(idx)) {
0712         const QString desktopEntry = idx.data(Notifications::DesktopEntryRole).toString();
0713         const QString notifyRcName = idx.data(Notifications::NotifyRcNameRole).toString();
0714 
0715         d->notificationsModel->configure(desktopEntry, notifyRcName, QString() /*eventId*/);
0716         return;
0717     }
0718 
0719     d->notificationsModel->configure(Private::notificationId(idx));
0720 }
0721 
0722 void Notifications::invokeDefaultAction(const QModelIndex &idx, InvokeBehavior behavior)
0723 {
0724     if (d->notificationsModel) {
0725         d->notificationsModel->invokeDefaultAction(Private::notificationId(idx), behavior);
0726     }
0727 }
0728 
0729 void Notifications::invokeAction(const QModelIndex &idx, const QString &actionId, InvokeBehavior behavior)
0730 {
0731     if (d->notificationsModel) {
0732         d->notificationsModel->invokeAction(Private::notificationId(idx), actionId, behavior);
0733     }
0734 }
0735 
0736 void Notifications::reply(const QModelIndex &idx, const QString &text, InvokeBehavior behavior)
0737 {
0738     if (d->notificationsModel) {
0739         d->notificationsModel->reply(Private::notificationId(idx), text, behavior);
0740     }
0741 }
0742 
0743 void Notifications::startTimeout(const QModelIndex &idx)
0744 {
0745     startTimeout(Private::notificationId(idx));
0746 }
0747 
0748 void Notifications::startTimeout(uint notificationId)
0749 {
0750     if (d->notificationsModel) {
0751         d->notificationsModel->startTimeout(notificationId);
0752     }
0753 }
0754 
0755 void Notifications::stopTimeout(const QModelIndex &idx)
0756 {
0757     if (d->notificationsModel) {
0758         d->notificationsModel->stopTimeout(Private::notificationId(idx));
0759     }
0760 }
0761 
0762 void Notifications::suspendJob(const QModelIndex &idx)
0763 {
0764     if (d->jobsModel) {
0765         d->jobsModel->suspend(Utils::mapToModel(idx, d->jobsModel.get()));
0766     }
0767 }
0768 
0769 void Notifications::resumeJob(const QModelIndex &idx)
0770 {
0771     if (d->jobsModel) {
0772         d->jobsModel->resume(Utils::mapToModel(idx, d->jobsModel.get()));
0773     }
0774 }
0775 
0776 void Notifications::killJob(const QModelIndex &idx)
0777 {
0778     if (d->jobsModel) {
0779         d->jobsModel->kill(Utils::mapToModel(idx, d->jobsModel.get()));
0780     }
0781 }
0782 
0783 void Notifications::clear(ClearFlags flags)
0784 {
0785     if (d->notificationsModel) {
0786         d->notificationsModel->clear(flags);
0787     }
0788     if (d->jobsModel) {
0789         d->jobsModel->clear(flags);
0790     }
0791 }
0792 
0793 QModelIndex Notifications::groupIndex(const QModelIndex &idx) const
0794 {
0795     if (idx.data(Notifications::IsGroupRole).toBool()) {
0796         return idx;
0797     }
0798 
0799     if (idx.data(Notifications::IsInGroupRole).toBool()) {
0800         QModelIndex groupingIdx = Utils::mapToModel(idx, d->groupingModel);
0801         return d->mapFromModel(groupingIdx.parent());
0802     }
0803 
0804     qCWarning(NOTIFICATIONMANAGER) << "Cannot get group index for item that isn't a group or inside one";
0805     return QModelIndex();
0806 }
0807 
0808 void Notifications::collapseAllGroups()
0809 {
0810     if (d->groupCollapsingModel) {
0811         d->groupCollapsingModel->collapseAll();
0812     }
0813 }
0814 
0815 QVariant Notifications::data(const QModelIndex &index, int role) const
0816 {
0817     return QSortFilterProxyModel::data(index, role);
0818 }
0819 
0820 bool Notifications::setData(const QModelIndex &index, const QVariant &value, int role)
0821 {
0822     return QSortFilterProxyModel::setData(index, value, role);
0823 }
0824 
0825 bool Notifications::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
0826 {
0827     return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
0828 }
0829 
0830 bool Notifications::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
0831 {
0832     return QSortFilterProxyModel::lessThan(source_left, source_right);
0833 }
0834 
0835 int Notifications::rowCount(const QModelIndex &parent) const
0836 {
0837     return QSortFilterProxyModel::rowCount(parent);
0838 }
0839 
0840 QHash<int, QByteArray> Notifications::roleNames() const
0841 {
0842     return Utils::roleNames();
0843 }