1 /*
2 SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6
7 #include "notifications.h"
8
9 #include <QDebug>
10 #include <QMetaEnum>
11 #include <QSharedPointer>
12
13 #include <KConcatenateRowsProxyModel>
14 #include <KDescendantsProxyModel>
15
16 #include "limitedrowcountproxymodel_p.h"
17 #include "notificationfilterproxymodel_p.h"
18 #include "notificationgroupcollapsingproxymodel_p.h"
19 #include "notificationgroupingproxymodel_p.h"
20 #include "notificationsmodel.h"
21 #include "notificationsortproxymodel_p.h"
22
23 #include "jobsmodel.h"
24
25 #include "settings.h"
26
27 #include "notification.h"
28
29 #include "utils_p.h"
30
31 #include "debug.h"
32
33 using namespace NotificationManager;
34
35 class Q_DECL_HIDDEN Notifications::Private
36 {
37 public:
38 explicit Private(Notifications *q);
39 ~Private();
40
41 void initSourceModels();
42 void initProxyModels();
43
44 void updateCount();
45
46 bool showNotifications = true;
47 bool showJobs = false;
48
49 Notifications::GroupMode groupMode = Notifications::GroupDisabled;
50 int groupLimit = 0;
51 bool expandUnread = false;
52
53 int activeNotificationsCount = 0;
54 int expiredNotificationsCount = 0;
55
56 int unreadNotificationsCount = 0;
57
58 int activeJobsCount = 0;
59 int jobsPercentage = 0;
60
61 static bool isGroup(const QModelIndex &idx);
62 static uint notificationId(const QModelIndex &idx);
63 QModelIndex mapFromModel(const QModelIndex &idx) const;
64
65 // NOTE when you add or re-arrange models make sure to update mapFromModel()!
66 NotificationsModel::Ptr notificationsModel;
67 JobsModel::Ptr jobsModel;
68 QSharedPointer<Settings> settings() const;
69
70 KConcatenateRowsProxyModel *notificationsAndJobsModel = nullptr;
71
72 NotificationFilterProxyModel *filterModel = nullptr;
73 NotificationSortProxyModel *sortModel = nullptr;
74 NotificationGroupingProxyModel *groupingModel = nullptr;
75 NotificationGroupCollapsingProxyModel *groupCollapsingModel = nullptr;
76 KDescendantsProxyModel *flattenModel = nullptr;
77
78 LimitedRowCountProxyModel *limiterModel = nullptr;
79
80 private:
81 Notifications *q;
82 };
83
Private(Notifications * q)84 Notifications::Private::Private(Notifications *q)
85 : q(q)
86 {
87 }
88
~Private()89 Notifications::Private::~Private()
90 {
91 }
92
initSourceModels()93 void Notifications::Private::initSourceModels()
94 {
95 Q_ASSERT(notificationsAndJobsModel); // initProxyModels must be called before initSourceModels
96
97 if (showNotifications && !notificationsModel) {
98 notificationsModel = NotificationsModel::createNotificationsModel();
99 connect(notificationsModel.data(), &NotificationsModel::lastReadChanged, q, [this] {
100 updateCount();
101 emit q->lastReadChanged();
102 });
103 notificationsAndJobsModel->addSourceModel(notificationsModel.data());
104 } else if (!showNotifications && notificationsModel) {
105 notificationsAndJobsModel->removeSourceModel(notificationsModel.data());
106 disconnect(notificationsModel.data(), nullptr, q, nullptr); // disconnect all
107 notificationsModel = nullptr;
108 }
109
110 if (showJobs && !jobsModel) {
111 jobsModel = JobsModel::createJobsModel();
112 notificationsAndJobsModel->addSourceModel(jobsModel.data());
113 jobsModel->init();
114 } else if (!showJobs && jobsModel) {
115 notificationsAndJobsModel->removeSourceModel(jobsModel.data());
116 jobsModel = nullptr;
117 }
118 }
119
initProxyModels()120 void Notifications::Private::initProxyModels()
121 {
122 /* The data flow is as follows:
123 * NOTE when you add or re-arrange models make sure to update mapFromModel()!
124 *
125 * NotificationsModel JobsModel
126 * \\ /
127 * \\ /
128 * KConcatenateRowsProxyModel
129 * |||
130 * |||
131 * NotificationFilterProxyModel
132 * (filters by urgency, whitelist, etc)
133 * |
134 * |
135 * NotificationSortProxyModel
136 * (sorts by urgency, date, etc)
137 * |
138 * --- BEGIN: Only when grouping is enabled ---
139 * |
140 * NotificationGroupingProxyModel
141 * (turns list into tree grouped by app)
142 * //\\
143 * //\\
144 * NotificationGroupCollapsingProxyModel
145 * (limits number of tree leaves for expand/collapse feature)
146 * /\
147 * /\
148 * KDescendantsProxyModel
149 * (flattens tree back into a list for consumption in ListView)
150 * |
151 * --- END: Only when grouping is enabled ---
152 * |
153 * LimitedRowCountProxyModel
154 * (limits the total number of items in the model)
155 * |
156 * |
157 * \o/ <- Happy user seeing their notifications
158 */
159
160 if (!notificationsAndJobsModel) {
161 notificationsAndJobsModel = new KConcatenateRowsProxyModel(q);
162 }
163
164 if (!filterModel) {
165 filterModel = new NotificationFilterProxyModel();
166 connect(filterModel, &NotificationFilterProxyModel::urgenciesChanged, q, &Notifications::urgenciesChanged);
167 connect(filterModel, &NotificationFilterProxyModel::showExpiredChanged, q, &Notifications::showExpiredChanged);
168 connect(filterModel, &NotificationFilterProxyModel::showDismissedChanged, q, &Notifications::showDismissedChanged);
169 connect(filterModel, &NotificationFilterProxyModel::blacklistedDesktopEntriesChanged, q, &Notifications::blacklistedDesktopEntriesChanged);
170 connect(filterModel, &NotificationFilterProxyModel::blacklistedNotifyRcNamesChanged, q, &Notifications::blacklistedNotifyRcNamesChanged);
171
172 connect(filterModel, &QAbstractItemModel::rowsInserted, q, [this] {
173 updateCount();
174 });
175 connect(filterModel, &QAbstractItemModel::rowsRemoved, q, [this] {
176 updateCount();
177 });
178 connect(filterModel,
179 &QAbstractItemModel::dataChanged,
180 q,
181 [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) {
182 Q_UNUSED(topLeft);
183 Q_UNUSED(bottomRight);
184 if (roles.isEmpty() || roles.contains(Notifications::UpdatedRole) || roles.contains(Notifications::ExpiredRole)
185 || roles.contains(Notifications::JobStateRole) || roles.contains(Notifications::PercentageRole)
186 || roles.contains(Notifications::ReadRole)) {
187 updateCount();
188 }
189 });
190
191 filterModel->setSourceModel(notificationsAndJobsModel);
192 }
193
194 if (!sortModel) {
195 sortModel = new NotificationSortProxyModel(q);
196 connect(sortModel, &NotificationSortProxyModel::sortModeChanged, q, &Notifications::sortModeChanged);
197 connect(sortModel, &NotificationSortProxyModel::sortOrderChanged, q, &Notifications::sortOrderChanged);
198 }
199
200 if (!limiterModel) {
201 limiterModel = new LimitedRowCountProxyModel(q);
202 connect(limiterModel, &LimitedRowCountProxyModel::limitChanged, q, &Notifications::limitChanged);
203 }
204
205 if (groupMode == GroupApplicationsFlat) {
206 if (!groupingModel) {
207 groupingModel = new NotificationGroupingProxyModel(q);
208 groupingModel->setSourceModel(filterModel);
209 }
210
211 if (!groupCollapsingModel) {
212 groupCollapsingModel = new NotificationGroupCollapsingProxyModel(q);
213 groupCollapsingModel->setLimit(groupLimit);
214 groupCollapsingModel->setExpandUnread(expandUnread);
215 groupCollapsingModel->setLastRead(q->lastRead());
216 groupCollapsingModel->setSourceModel(groupingModel);
217 }
218
219 sortModel->setSourceModel(groupCollapsingModel);
220
221 flattenModel = new KDescendantsProxyModel(q);
222 flattenModel->setSourceModel(sortModel);
223
224 limiterModel->setSourceModel(flattenModel);
225 } else {
226 sortModel->setSourceModel(filterModel);
227 limiterModel->setSourceModel(sortModel);
228 delete flattenModel;
229 flattenModel = nullptr;
230 delete groupingModel;
231 groupingModel = nullptr;
232 }
233
234 q->setSourceModel(limiterModel);
235 }
236
updateCount()237 void Notifications::Private::updateCount()
238 {
239 int active = 0;
240 int expired = 0;
241 int unread = 0;
242
243 int jobs = 0;
244 int totalPercentage = 0;
245
246 // We want to get the numbers after main filtering (urgencies, whitelists, etc)
247 // but before any limiting or group limiting, hence asking the filterModel for advice
248 // at which point notifications and jobs also have already been merged
249 for (int i = 0; i < filterModel->rowCount(); ++i) {
250 const QModelIndex idx = filterModel->index(i, 0);
251
252 if (idx.data(Notifications::ExpiredRole).toBool()) {
253 ++expired;
254 } else {
255 ++active;
256 }
257
258 const bool read = idx.data(Notifications::ReadRole).toBool();
259 if (!active && !read) {
260 QDateTime date = idx.data(Notifications::UpdatedRole).toDateTime();
261 if (!date.isValid()) {
262 date = idx.data(Notifications::CreatedRole).toDateTime();
263 }
264
265 if (notificationsModel && date > notificationsModel->lastRead()) {
266 ++unread;
267 }
268 }
269
270 if (idx.data(Notifications::TypeRole).toInt() == Notifications::JobType) {
271 if (idx.data(Notifications::JobStateRole).toInt() != Notifications::JobStateStopped) {
272 ++jobs;
273
274 totalPercentage += idx.data(Notifications::PercentageRole).toInt();
275 }
276 }
277 }
278
279 if (activeNotificationsCount != active) {
280 activeNotificationsCount = active;
281 emit q->activeNotificationsCountChanged();
282 }
283 if (expiredNotificationsCount != expired) {
284 expiredNotificationsCount = expired;
285 emit q->expiredNotificationsCountChanged();
286 }
287 if (unreadNotificationsCount != unread) {
288 unreadNotificationsCount = unread;
289 emit q->unreadNotificationsCountChanged();
290 }
291 if (activeJobsCount != jobs) {
292 activeJobsCount = jobs;
293 emit q->activeJobsCountChanged();
294 }
295
296 const int percentage = (jobs > 0 ? totalPercentage / jobs : 0);
297 if (jobsPercentage != percentage) {
298 jobsPercentage = percentage;
299 emit q->jobsPercentageChanged();
300 }
301
302 // TODO don't emit in dataChanged
303 emit q->countChanged();
304 }
305
isGroup(const QModelIndex & idx)306 bool Notifications::Private::isGroup(const QModelIndex &idx)
307 {
308 return idx.data(Notifications::IsGroupRole).toBool();
309 }
310
notificationId(const QModelIndex & idx)311 uint Notifications::Private::notificationId(const QModelIndex &idx)
312 {
313 return idx.data(Notifications::IdRole).toUInt();
314 }
315
mapFromModel(const QModelIndex & idx) const316 QModelIndex Notifications::Private::mapFromModel(const QModelIndex &idx) const
317 {
318 QModelIndex resolvedIdx = idx;
319
320 QAbstractItemModel *models[] = {
321 notificationsAndJobsModel,
322 filterModel,
323 sortModel,
324 groupingModel,
325 groupCollapsingModel,
326 flattenModel,
327 limiterModel,
328 };
329
330 // TODO can we do this with a generic loop like mapFromModel
331 while (resolvedIdx.isValid() && resolvedIdx.model() != q) {
332 const auto *idxModel = resolvedIdx.model();
333
334 // HACK try to find the model that uses the index' model as source
335 bool found = false;
336 for (QAbstractItemModel *model : models) {
337 if (!model) {
338 continue;
339 }
340
341 if (auto *proxyModel = qobject_cast<QAbstractProxyModel *>(model)) {
342 if (proxyModel->sourceModel() == idxModel) {
343 resolvedIdx = proxyModel->mapFromSource(resolvedIdx);
344 found = true;
345 break;
346 }
347 } else if (auto *concatenateModel = qobject_cast<KConcatenateRowsProxyModel *>(model)) {
348 // There's no "sourceModels()" on KConcatenateRowsProxyModel
349 if (idxModel == notificationsModel.data() || idxModel == jobsModel.data()) {
350 resolvedIdx = concatenateModel->mapFromSource(resolvedIdx);
351 found = true;
352 break;
353 }
354 }
355 }
356
357 if (!found) {
358 break;
359 }
360 }
361 return resolvedIdx;
362 }
363
settings() const364 QSharedPointer<Settings> Notifications::Private::settings() const
365 {
366 static QWeakPointer<Settings> s_instance;
367 if (!s_instance) {
368 QSharedPointer<Settings> ptr(new Settings());
369 s_instance = ptr.toWeakRef();
370 return ptr;
371 }
372 return s_instance.toStrongRef();
373 }
374
Notifications(QObject * parent)375 Notifications::Notifications(QObject *parent)
376 : QSortFilterProxyModel(parent)
377 , d(new Private(this))
378 {
379 // The proxy models are always the same, just with different
380 // properties set whereas we want to avoid loading a source model
381 // e.g. notifications or jobs when we're not actually using them
382 d->initProxyModels();
383
384 // init source models when used from C++
385 QMetaObject::invokeMethod(
386 this,
387 [this] {
388 d->initSourceModels();
389 },
390 Qt::QueuedConnection);
391 }
392
393 Notifications::~Notifications() = default;
394
classBegin()395 void Notifications::classBegin()
396 {
397 }
398
componentComplete()399 void Notifications::componentComplete()
400 {
401 // init source models when used from QML
402 d->initSourceModels();
403 }
404
limit() const405 int Notifications::limit() const
406 {
407 return d->limiterModel->limit();
408 }
409
setLimit(int limit)410 void Notifications::setLimit(int limit)
411 {
412 d->limiterModel->setLimit(limit);
413 }
414
groupLimit() const415 int Notifications::groupLimit() const
416 {
417 return d->groupLimit;
418 }
419
setGroupLimit(int limit)420 void Notifications::setGroupLimit(int limit)
421 {
422 if (d->groupLimit == limit) {
423 return;
424 }
425
426 d->groupLimit = limit;
427 if (d->groupCollapsingModel) {
428 d->groupCollapsingModel->setLimit(limit);
429 }
430 emit groupLimitChanged();
431 }
432
expandUnread() const433 bool Notifications::expandUnread() const
434 {
435 return d->expandUnread;
436 }
437
setExpandUnread(bool expand)438 void Notifications::setExpandUnread(bool expand)
439 {
440 if (d->expandUnread == expand) {
441 return;
442 }
443
444 d->expandUnread = expand;
445 if (d->groupCollapsingModel) {
446 d->groupCollapsingModel->setExpandUnread(expand);
447 }
448 emit expandUnreadChanged();
449 }
450
showExpired() const451 bool Notifications::showExpired() const
452 {
453 return d->filterModel->showExpired();
454 }
455
setShowExpired(bool show)456 void Notifications::setShowExpired(bool show)
457 {
458 d->filterModel->setShowExpired(show);
459 }
460
showDismissed() const461 bool Notifications::showDismissed() const
462 {
463 return d->filterModel->showDismissed();
464 }
465
setShowDismissed(bool show)466 void Notifications::setShowDismissed(bool show)
467 {
468 d->filterModel->setShowDismissed(show);
469 }
470
blacklistedDesktopEntries() const471 QStringList Notifications::blacklistedDesktopEntries() const
472 {
473 return d->filterModel->blacklistedDesktopEntries();
474 }
475
setBlacklistedDesktopEntries(const QStringList & blacklist)476 void Notifications::setBlacklistedDesktopEntries(const QStringList &blacklist)
477 {
478 d->filterModel->setBlackListedDesktopEntries(blacklist);
479 }
480
blacklistedNotifyRcNames() const481 QStringList Notifications::blacklistedNotifyRcNames() const
482 {
483 return d->filterModel->blacklistedNotifyRcNames();
484 }
485
setBlacklistedNotifyRcNames(const QStringList & blacklist)486 void Notifications::setBlacklistedNotifyRcNames(const QStringList &blacklist)
487 {
488 d->filterModel->setBlacklistedNotifyRcNames(blacklist);
489 }
490
whitelistedDesktopEntries() const491 QStringList Notifications::whitelistedDesktopEntries() const
492 {
493 return d->filterModel->whitelistedDesktopEntries();
494 }
495
setWhitelistedDesktopEntries(const QStringList & whitelist)496 void Notifications::setWhitelistedDesktopEntries(const QStringList &whitelist)
497 {
498 d->filterModel->setWhiteListedDesktopEntries(whitelist);
499 }
500
whitelistedNotifyRcNames() const501 QStringList Notifications::whitelistedNotifyRcNames() const
502 {
503 return d->filterModel->whitelistedNotifyRcNames();
504 }
505
setWhitelistedNotifyRcNames(const QStringList & whitelist)506 void Notifications::setWhitelistedNotifyRcNames(const QStringList &whitelist)
507 {
508 d->filterModel->setWhitelistedNotifyRcNames(whitelist);
509 }
510
showNotifications() const511 bool Notifications::showNotifications() const
512 {
513 return d->showNotifications;
514 }
515
setShowNotifications(bool show)516 void Notifications::setShowNotifications(bool show)
517 {
518 if (d->showNotifications == show) {
519 return;
520 }
521
522 d->showNotifications = show;
523 d->initSourceModels();
524 emit showNotificationsChanged();
525 }
526
showJobs() const527 bool Notifications::showJobs() const
528 {
529 return d->showJobs;
530 }
531
setShowJobs(bool show)532 void Notifications::setShowJobs(bool show)
533 {
534 if (d->showJobs == show) {
535 return;
536 }
537
538 d->showJobs = show;
539 d->initSourceModels();
540 emit showJobsChanged();
541 }
542
urgencies() const543 Notifications::Urgencies Notifications::urgencies() const
544 {
545 return d->filterModel->urgencies();
546 }
547
setUrgencies(Urgencies urgencies)548 void Notifications::setUrgencies(Urgencies urgencies)
549 {
550 d->filterModel->setUrgencies(urgencies);
551 }
552
sortMode() const553 Notifications::SortMode Notifications::sortMode() const
554 {
555 return d->sortModel->sortMode();
556 }
557
setSortMode(SortMode sortMode)558 void Notifications::setSortMode(SortMode sortMode)
559 {
560 d->sortModel->setSortMode(sortMode);
561 }
562
sortOrder() const563 Qt::SortOrder Notifications::sortOrder() const
564 {
565 return d->sortModel->sortOrder();
566 }
567
setSortOrder(Qt::SortOrder sortOrder)568 void Notifications::setSortOrder(Qt::SortOrder sortOrder)
569 {
570 d->sortModel->setSortOrder(sortOrder);
571 }
572
groupMode() const573 Notifications::GroupMode Notifications::groupMode() const
574 {
575 return d->groupMode;
576 }
577
setGroupMode(GroupMode groupMode)578 void Notifications::setGroupMode(GroupMode groupMode)
579 {
580 if (d->groupMode != groupMode) {
581 d->groupMode = groupMode;
582 d->initProxyModels();
583 emit groupModeChanged();
584 }
585 }
586
count() const587 int Notifications::count() const
588 {
589 return rowCount(QModelIndex());
590 }
591
activeNotificationsCount() const592 int Notifications::activeNotificationsCount() const
593 {
594 return d->activeNotificationsCount;
595 }
596
expiredNotificationsCount() const597 int Notifications::expiredNotificationsCount() const
598 {
599 return d->expiredNotificationsCount;
600 }
601
lastRead() const602 QDateTime Notifications::lastRead() const
603 {
604 if (d->notificationsModel) {
605 return d->notificationsModel->lastRead();
606 }
607 return QDateTime();
608 }
609
setLastRead(const QDateTime & lastRead)610 void Notifications::setLastRead(const QDateTime &lastRead)
611 {
612 // TODO jobs could also be unread?
613 if (d->notificationsModel) {
614 d->notificationsModel->setLastRead(lastRead);
615 }
616 if (d->groupCollapsingModel) {
617 d->groupCollapsingModel->setLastRead(lastRead);
618 }
619 }
620
resetLastRead()621 void Notifications::resetLastRead()
622 {
623 setLastRead(QDateTime::currentDateTimeUtc());
624 }
625
unreadNotificationsCount() const626 int Notifications::unreadNotificationsCount() const
627 {
628 return d->unreadNotificationsCount;
629 }
630
activeJobsCount() const631 int Notifications::activeJobsCount() const
632 {
633 return d->activeJobsCount;
634 }
635
jobsPercentage() const636 int Notifications::jobsPercentage() const
637 {
638 return d->jobsPercentage;
639 }
640
makePersistentModelIndex(const QModelIndex & idx) const641 QPersistentModelIndex Notifications::makePersistentModelIndex(const QModelIndex &idx) const
642 {
643 return QPersistentModelIndex(idx);
644 }
645
expire(const QModelIndex & idx)646 void Notifications::expire(const QModelIndex &idx)
647 {
648 switch (static_cast<Notifications::Type>(idx.data(Notifications::TypeRole).toInt())) {
649 case Notifications::NotificationType:
650 d->notificationsModel->expire(Private::notificationId(idx));
651 break;
652 case Notifications::JobType:
653 d->jobsModel->expire(Utils::mapToModel(idx, d->jobsModel.data()));
654 break;
655 default:
656 Q_UNREACHABLE();
657 }
658 }
659
close(const QModelIndex & idx)660 void Notifications::close(const QModelIndex &idx)
661 {
662 if (idx.data(Notifications::IsGroupRole).toBool()) {
663 const QModelIndex groupIdx = Utils::mapToModel(idx, d->groupingModel);
664 if (!groupIdx.isValid()) {
665 qCWarning(NOTIFICATIONMANAGER) << "Failed to find group model index for this item";
666 return;
667 }
668
669 Q_ASSERT(groupIdx.model() == d->groupingModel);
670
671 const int childCount = d->groupingModel->rowCount(groupIdx);
672 for (int i = childCount - 1; i >= 0; --i) {
673 const QModelIndex childIdx = d->groupingModel->index(i, 0, groupIdx);
674 close(childIdx);
675 }
676 return;
677 }
678
679 if (!idx.data(Notifications::ClosableRole).toBool()) {
680 return;
681 }
682
683 switch (static_cast<Notifications::Type>(idx.data(Notifications::TypeRole).toInt())) {
684 case Notifications::NotificationType:
685 d->notificationsModel->close(Private::notificationId(idx));
686 break;
687 case Notifications::JobType:
688 d->jobsModel->close(Utils::mapToModel(idx, d->jobsModel.data()));
689 break;
690 default:
691 Q_UNREACHABLE();
692 }
693 }
694
configure(const QModelIndex & idx)695 void Notifications::configure(const QModelIndex &idx)
696 {
697 if (!d->notificationsModel) {
698 return;
699 }
700
701 // For groups just configure the application, not the individual event
702 if (Private::isGroup(idx)) {
703 const QString desktopEntry = idx.data(Notifications::DesktopEntryRole).toString();
704 const QString notifyRcName = idx.data(Notifications::NotifyRcNameRole).toString();
705
706 d->notificationsModel->configure(desktopEntry, notifyRcName, QString() /*eventId*/);
707 return;
708 }
709
710 d->notificationsModel->configure(Private::notificationId(idx));
711 }
712
invokeDefaultAction(const QModelIndex & idx)713 void Notifications::invokeDefaultAction(const QModelIndex &idx)
714 {
715 if (d->notificationsModel) {
716 d->notificationsModel->invokeDefaultAction(Private::notificationId(idx));
717 }
718 }
719
invokeAction(const QModelIndex & idx,const QString & actionId)720 void Notifications::invokeAction(const QModelIndex &idx, const QString &actionId)
721 {
722 if (d->notificationsModel) {
723 d->notificationsModel->invokeAction(Private::notificationId(idx), actionId);
724 }
725 }
726
reply(const QModelIndex & idx,const QString & text)727 void Notifications::reply(const QModelIndex &idx, const QString &text)
728 {
729 if (d->notificationsModel) {
730 d->notificationsModel->reply(Private::notificationId(idx), text);
731 }
732 }
733
startTimeout(const QModelIndex & idx)734 void Notifications::startTimeout(const QModelIndex &idx)
735 {
736 startTimeout(Private::notificationId(idx));
737 }
738
startTimeout(uint notificationId)739 void Notifications::startTimeout(uint notificationId)
740 {
741 if (d->notificationsModel) {
742 d->notificationsModel->startTimeout(notificationId);
743 }
744 }
745
stopTimeout(const QModelIndex & idx)746 void Notifications::stopTimeout(const QModelIndex &idx)
747 {
748 if (d->notificationsModel) {
749 d->notificationsModel->stopTimeout(Private::notificationId(idx));
750 }
751 }
752
suspendJob(const QModelIndex & idx)753 void Notifications::suspendJob(const QModelIndex &idx)
754 {
755 if (d->jobsModel) {
756 d->jobsModel->suspend(Utils::mapToModel(idx, d->jobsModel.data()));
757 }
758 }
759
resumeJob(const QModelIndex & idx)760 void Notifications::resumeJob(const QModelIndex &idx)
761 {
762 if (d->jobsModel) {
763 d->jobsModel->resume(Utils::mapToModel(idx, d->jobsModel.data()));
764 }
765 }
766
killJob(const QModelIndex & idx)767 void Notifications::killJob(const QModelIndex &idx)
768 {
769 if (d->jobsModel) {
770 d->jobsModel->kill(Utils::mapToModel(idx, d->jobsModel.data()));
771 }
772 }
773
clear(ClearFlags flags)774 void Notifications::clear(ClearFlags flags)
775 {
776 if (d->notificationsModel) {
777 d->notificationsModel->clear(flags);
778 }
779 if (d->jobsModel) {
780 d->jobsModel->clear(flags);
781 }
782 }
783
groupIndex(const QModelIndex & idx) const784 QModelIndex Notifications::groupIndex(const QModelIndex &idx) const
785 {
786 if (idx.data(Notifications::IsGroupRole).toBool()) {
787 return idx;
788 }
789
790 if (idx.data(Notifications::IsInGroupRole).toBool()) {
791 QModelIndex groupingIdx = Utils::mapToModel(idx, d->groupingModel);
792 return d->mapFromModel(groupingIdx.parent());
793 }
794
795 qCWarning(NOTIFICATIONMANAGER) << "Cannot get group index for item that isn't a group or inside one";
796 return QModelIndex();
797 }
798
collapseAllGroups()799 void Notifications::collapseAllGroups()
800 {
801 if (d->groupCollapsingModel) {
802 d->groupCollapsingModel->collapseAll();
803 }
804 }
805
data(const QModelIndex & index,int role) const806 QVariant Notifications::data(const QModelIndex &index, int role) const
807 {
808 return QSortFilterProxyModel::data(index, role);
809 }
810
setData(const QModelIndex & index,const QVariant & value,int role)811 bool Notifications::setData(const QModelIndex &index, const QVariant &value, int role)
812 {
813 return QSortFilterProxyModel::setData(index, value, role);
814 }
815
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const816 bool Notifications::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
817 {
818 return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
819 }
820
lessThan(const QModelIndex & source_left,const QModelIndex & source_right) const821 bool Notifications::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
822 {
823 return QSortFilterProxyModel::lessThan(source_left, source_right);
824 }
825
rowCount(const QModelIndex & parent) const826 int Notifications::rowCount(const QModelIndex &parent) const
827 {
828 return QSortFilterProxyModel::rowCount(parent);
829 }
830
roleNames() const831 QHash<int, QByteArray> Notifications::roleNames() const
832 {
833 return Utils::roleNames();
834 }
835