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

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