Changeset View
Changeset View
Standalone View
Standalone View
libnotificationmanager/notificationsmodel.cpp
- This file was copied to libnotificationmanager/abstractnotificationsmodel.cpp.
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | * Copyright 2020 Shah Bhushan <bshah@kde.org> | ||||
2 | * Copyright 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de> | 3 | * Copyright 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de> | ||
3 | * | 4 | * | ||
4 | * This library is free software; you can redistribute it and/or | 5 | * This library is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU Lesser General Public | 6 | * modify it under the terms of the GNU Lesser General Public | ||
6 | * License as published by the Free Software Foundation; either | 7 | * License as published by the Free Software Foundation; either | ||
7 | * version 2.1 of the License, or (at your option) version 3, or any | 8 | * version 2.1 of the License, or (at your option) version 3, or any | ||
8 | * later version accepted by the membership of KDE e.V. (or its | 9 | * later version accepted by the membership of KDE e.V. (or its | ||
9 | * successor approved by the membership of KDE e.V.), which shall | 10 | * successor approved by the membership of KDE e.V.), which shall | ||
10 | * act as a proxy defined in Section 6 of version 3 of the license. | 11 | * act as a proxy defined in Section 6 of version 3 of the license. | ||
11 | * | 12 | * | ||
12 | * This library is distributed in the hope that it will be useful, | 13 | * This library is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * Lesser General Public License for more details. | 16 | * Lesser General Public License for more details. | ||
16 | * | 17 | * | ||
17 | * You should have received a copy of the GNU Lesser General Public | 18 | * You should have received a copy of the GNU Lesser General Public | ||
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. | 19 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | 20 | */ | ||
20 | 21 | | |||
21 | #include "notificationsmodel.h" | 22 | #include "notificationsmodel.h" | ||
22 | | ||||
23 | #include "debug.h" | | |||
24 | | ||||
25 | #include "server.h" | 23 | #include "server.h" | ||
26 | #include "utils_p.h" | 24 | #include "abstractnotificationsmodel_p.h" | ||
27 | | ||||
28 | #include "notifications.h" | | |||
29 | | ||||
30 | #include "notification.h" | | |||
31 | #include "notification_p.h" | 25 | #include "notification_p.h" | ||
32 | 26 | | |||
33 | #include <QDebug> | 27 | #include "debug.h" | ||
28 | | ||||
34 | #include <QProcess> | 29 | #include <QProcess> | ||
35 | #include <QTimer> | | |||
36 | 30 | | |||
37 | #include <KShell> | 31 | #include <KShell> | ||
38 | 32 | | |||
39 | #include <algorithm> | | |||
40 | #include <functional> | | |||
41 | | ||||
42 | static const int s_notificationsLimit = 1000; | | |||
43 | | ||||
44 | using namespace NotificationManager; | 33 | using namespace NotificationManager; | ||
45 | 34 | | |||
46 | class Q_DECL_HIDDEN NotificationsModel::Private | 35 | NotificationsModel::Ptr NotificationsModel::createNotificationsModel() | ||
47 | { | | |||
48 | public: | | |||
49 | explicit Private(NotificationsModel *q); | | |||
50 | ~Private(); | | |||
51 | | ||||
52 | void onNotificationAdded(const Notification ¬ification); | | |||
53 | void onNotificationReplaced(uint replacedId, const Notification ¬ification); | | |||
54 | void onNotificationRemoved(uint notificationId, Server::CloseReason reason); | | |||
55 | | ||||
56 | void setupNotificationTimeout(const Notification ¬ification); | | |||
57 | | ||||
58 | int rowOfNotification(uint id) const; | | |||
59 | | ||||
60 | NotificationsModel *q; | | |||
61 | | ||||
62 | QVector<Notification> notifications; | | |||
63 | // Fallback timeout to ensure all notifications expire eventually | | |||
64 | // otherwise when it isn't shown to the user and doesn't expire | | |||
65 | // an app might wait indefinitely for the notification to do so | | |||
66 | QHash<uint /*notificationId*/, QTimer*> notificationTimeouts; | | |||
67 | | ||||
68 | QDateTime lastRead; | | |||
69 | | ||||
70 | }; | | |||
71 | | ||||
72 | NotificationsModel::Private::Private(NotificationsModel *q) | | |||
73 | : q(q) | | |||
74 | , lastRead(QDateTime::currentDateTimeUtc()) | | |||
75 | { | | |||
76 | | ||||
77 | } | | |||
78 | | ||||
79 | NotificationsModel::Private::~Private() | | |||
80 | { | | |||
81 | qDeleteAll(notificationTimeouts); | | |||
82 | notificationTimeouts.clear(); | | |||
83 | } | | |||
84 | | ||||
85 | void NotificationsModel::Private::onNotificationAdded(const Notification ¬ification) | | |||
86 | { | | |||
87 | // Once we reach a certain insane number of notifications discard some old ones | | |||
88 | // as we keep pixmaps around etc | | |||
89 | if (notifications.count() >= s_notificationsLimit) { | | |||
90 | const int cleanupCount = s_notificationsLimit / 2; | | |||
91 | qCDebug(NOTIFICATIONMANAGER) << "Reached the notification limit of" << s_notificationsLimit << ", discarding the oldest" << cleanupCount << "notifications"; | | |||
92 | q->beginRemoveRows(QModelIndex(), 0, cleanupCount - 1); | | |||
93 | for (int i = 0 ; i < cleanupCount; ++i) { | | |||
94 | notifications.removeAt(0); | | |||
95 | // TODO close gracefully? | | |||
96 | } | | |||
97 | q->endRemoveRows(); | | |||
98 | } | | |||
99 | | ||||
100 | setupNotificationTimeout(notification); | | |||
101 | | ||||
102 | q->beginInsertRows(QModelIndex(), notifications.count(), notifications.count()); | | |||
103 | notifications.append(std::move(notification)); | | |||
104 | q->endInsertRows(); | | |||
105 | } | | |||
106 | | ||||
107 | void NotificationsModel::Private::onNotificationReplaced(uint replacedId, const Notification ¬ification) | | |||
108 | { | | |||
109 | const int row = rowOfNotification(replacedId); | | |||
110 | | ||||
111 | if (row == -1) { | | |||
112 | qCWarning(NOTIFICATIONMANAGER) << "Trying to replace notification with id" << replacedId << "which doesn't exist, creating a new one. This is an application bug!"; | | |||
113 | onNotificationAdded(notification); | | |||
114 | return; | | |||
115 | } | | |||
116 | | ||||
117 | setupNotificationTimeout(notification); | | |||
118 | | ||||
119 | notifications[row] = notification; | | |||
120 | const QModelIndex idx = q->index(row, 0); | | |||
121 | emit q->dataChanged(idx, idx); | | |||
122 | } | | |||
123 | | ||||
124 | void NotificationsModel::Private::onNotificationRemoved(uint removedId, Server::CloseReason reason) | | |||
125 | { | | |||
126 | const int row = rowOfNotification(removedId); | | |||
127 | if (row == -1) { | | |||
128 | return; | | |||
129 | } | | |||
130 | | ||||
131 | q->stopTimeout(removedId); | | |||
132 | | ||||
133 | // When a notification expired, keep it around in the history and mark it as such | | |||
134 | if (reason == Server::CloseReason::Expired) { | | |||
135 | const QModelIndex idx = q->index(row, 0); | | |||
136 | | ||||
137 | Notification ¬ification = notifications[row]; | | |||
138 | notification.setExpired(true); | | |||
139 | | ||||
140 | // Since the notification is "closed" it cannot have any actions | | |||
141 | // unless it is "resident" which we don't support | | |||
142 | notification.setActions(QStringList()); | | |||
143 | | ||||
144 | emit q->dataChanged(idx, idx, { | | |||
145 | Notifications::ExpiredRole, | | |||
146 | // TODO only emit those if actually changed? | | |||
147 | Notifications::ActionNamesRole, | | |||
148 | Notifications::ActionLabelsRole, | | |||
149 | Notifications::HasDefaultActionRole, | | |||
150 | Notifications::DefaultActionLabelRole, | | |||
151 | Notifications::ConfigurableRole | | |||
152 | }); | | |||
153 | | ||||
154 | return; | | |||
155 | } | | |||
156 | | ||||
157 | // Otherwise if explicitly closed by either user or app, remove it | | |||
158 | | ||||
159 | q->beginRemoveRows(QModelIndex(), row, row); | | |||
160 | notifications.removeAt(row); | | |||
161 | q->endRemoveRows(); | | |||
162 | } | | |||
163 | | ||||
164 | void NotificationsModel::Private::setupNotificationTimeout(const Notification ¬ification) | | |||
165 | { | | |||
166 | if (notification.timeout() == 0) { | | |||
167 | // In case it got replaced by a persistent notification | | |||
168 | q->stopTimeout(notification.id()); | | |||
169 | return; | | |||
170 | } | | |||
171 | | ||||
172 | QTimer *timer = notificationTimeouts.value(notification.id()); | | |||
173 | if (!timer) { | | |||
174 | timer = new QTimer(); | | |||
175 | timer->setSingleShot(true); | | |||
176 | | ||||
177 | connect(timer, &QTimer::timeout, q, [this, timer] { | | |||
178 | const uint id = timer->property("notificationId").toUInt(); | | |||
179 | q->expire(id); | | |||
180 | }); | | |||
181 | notificationTimeouts.insert(notification.id(), timer); | | |||
182 | } | | |||
183 | | ||||
184 | timer->stop(); | | |||
185 | timer->setProperty("notificationId", notification.id()); | | |||
186 | timer->setInterval(60000 /*1min*/ + (notification.timeout() == -1 ? 120000 /*2min, max configurable default timeout*/ : notification.timeout())); | | |||
187 | timer->start(); | | |||
188 | } | | |||
189 | | ||||
190 | int NotificationsModel::Private::rowOfNotification(uint id) const | | |||
191 | { | 36 | { | ||
192 | auto it = std::find_if(notifications.constBegin(), notifications.constEnd(), [id](const Notification &item) { | 37 | static QWeakPointer<NotificationsModel> s_instance; | ||
193 | return item.id() == id; | 38 | if (!s_instance) { | ||
194 | }); | 39 | QSharedPointer<NotificationsModel> ptr(new NotificationsModel()); | ||
195 | 40 | s_instance = ptr.toWeakRef(); | |||
196 | if (it == notifications.constEnd()) { | 41 | return ptr; | ||
197 | return -1; | | |||
198 | } | 42 | } | ||
199 | 43 | return s_instance.toStrongRef(); | |||
200 | return std::distance(notifications.constBegin(), it); | | |||
201 | } | 44 | } | ||
202 | 45 | | |||
203 | NotificationsModel::NotificationsModel() | 46 | NotificationsModel::NotificationsModel() | ||
204 | : QAbstractListModel(nullptr) | | |||
205 | , d(new Private(this)) | | |||
206 | { | 47 | { | ||
207 | connect(&Server::self(), &Server::notificationAdded, this, [this](const Notification ¬ification) { | 48 | connect(&Server::self(), &Server::notificationAdded, this, [this](const Notification ¬ification) { | ||
208 | d->onNotificationAdded(notification); | 49 | onNotificationAdded(notification); | ||
209 | }); | 50 | }); | ||
210 | connect(&Server::self(), &Server::notificationReplaced, this, [this](uint replacedId, const Notification ¬ification) { | 51 | connect(&Server::self(), &Server::notificationReplaced, this, [this](uint replacedId, const Notification ¬ification) { | ||
211 | d->onNotificationReplaced(replacedId, notification); | 52 | onNotificationReplaced(replacedId, notification); | ||
212 | }); | 53 | }); | ||
213 | connect(&Server::self(), &Server::notificationRemoved, this, [this](uint removedId, Server::CloseReason reason) { | 54 | connect(&Server::self(), &Server::notificationRemoved, this, [this](uint removedId, Server::CloseReason reason) { | ||
214 | d->onNotificationRemoved(removedId, reason); | 55 | onNotificationRemoved(removedId, reason); | ||
215 | }); | 56 | }); | ||
216 | connect(&Server::self(), &Server::serviceOwnershipLost, this, [this] { | 57 | connect(&Server::self(), &Server::serviceOwnershipLost, this, [this] { | ||
217 | // Expire all notifications as we're defunct now | 58 | // Expire all notifications as we're defunct now | ||
218 | const auto notifications = d->notifications; | 59 | const auto notificationList = notifications(); | ||
219 | for (const Notification ¬ification : notifications) { | 60 | for (const Notification ¬ification : notificationList) { | ||
220 | if (!notification.expired()) { | 61 | if (!notification.expired()) { | ||
221 | d->onNotificationRemoved(notification.id(), Server::CloseReason::Expired); | 62 | onNotificationRemoved(notification.id(), Server::CloseReason::Expired); | ||
222 | } | 63 | } | ||
223 | } | 64 | } | ||
224 | }); | 65 | }); | ||
225 | | ||||
226 | Server::self().init(); | 66 | Server::self().init(); | ||
227 | } | 67 | } | ||
228 | 68 | | |||
229 | NotificationsModel::~NotificationsModel() = default; | 69 | void NotificationsModel::expire(uint notificationId) | ||
230 | | ||||
231 | NotificationsModel::Ptr NotificationsModel::createNotificationsModel() | | |||
232 | { | 70 | { | ||
233 | static QWeakPointer<NotificationsModel> s_instance; | 71 | if (rowOfNotification(notificationId) > -1) { | ||
234 | if (!s_instance) { | 72 | Server::self().closeNotification(notificationId, Server::CloseReason::Expired); | ||
235 | QSharedPointer<NotificationsModel> ptr(new NotificationsModel()); | | |||
236 | s_instance = ptr.toWeakRef(); | | |||
237 | return ptr; | | |||
238 | } | | |||
239 | return s_instance.toStrongRef(); | | |||
240 | } | 73 | } | ||
241 | | ||||
242 | QDateTime NotificationsModel::lastRead() const | | |||
243 | { | | |||
244 | return d->lastRead; | | |||
245 | } | 74 | } | ||
246 | 75 | | |||
247 | void NotificationsModel::setLastRead(const QDateTime &lastRead) | 76 | void NotificationsModel::close(uint notificationId) | ||
248 | { | 77 | { | ||
249 | if (d->lastRead != lastRead) { | 78 | if (rowOfNotification(notificationId) > -1) { | ||
250 | d->lastRead = lastRead; | 79 | Server::self().closeNotification(notificationId, Server::CloseReason::DismissedByUser); | ||
251 | emit lastReadChanged(); | | |||
252 | } | 80 | } | ||
253 | } | 81 | } | ||
254 | 82 | | |||
255 | QVariant NotificationsModel::data(const QModelIndex &index, int role) const | | |||
256 | { | | |||
257 | if (!checkIndex(index)) { | | |||
258 | return QVariant(); | | |||
259 | } | | |||
260 | | ||||
261 | const Notification ¬ification = d->notifications.at(index.row()); | | |||
262 | | ||||
263 | switch (role) { | | |||
264 | case Notifications::IdRole: return notification.id(); | | |||
265 | case Notifications::TypeRole: return Notifications::NotificationType; | | |||
266 | 83 | | |||
267 | case Notifications::CreatedRole: | 84 | void NotificationsModel::invokeDefaultAction(uint notificationId) | ||
268 | if (notification.created().isValid()) { | 85 | { | ||
269 | return notification.created(); | 86 | const int row = rowOfNotification(notificationId); | ||
270 | } | 87 | if (row == -1) { | ||
271 | break; | 88 | return; | ||
272 | case Notifications::UpdatedRole: | | |||
273 | if (notification.updated().isValid()) { | | |||
274 | return notification.updated(); | | |||
275 | } | | |||
276 | break; | | |||
277 | case Notifications::SummaryRole: return notification.summary(); | | |||
278 | case Notifications::BodyRole: return notification.body(); | | |||
279 | case Notifications::IconNameRole: | | |||
280 | if (notification.image().isNull()) { | | |||
281 | return notification.icon(); | | |||
282 | } | | |||
283 | break; | | |||
284 | case Notifications::ImageRole: | | |||
285 | if (!notification.image().isNull()) { | | |||
286 | return notification.image(); | | |||
287 | } | 89 | } | ||
288 | break; | | |||
289 | case Notifications::DesktopEntryRole: return notification.desktopEntry(); | | |||
290 | case Notifications::NotifyRcNameRole: return notification.notifyRcName(); | | |||
291 | | ||||
292 | case Notifications::ApplicationNameRole: return notification.applicationName(); | | |||
293 | case Notifications::ApplicationIconNameRole: return notification.applicationIconName(); | | |||
294 | case Notifications::OriginNameRole: return notification.originName(); | | |||
295 | | ||||
296 | case Notifications::ActionNamesRole: return notification.actionNames(); | | |||
297 | case Notifications::ActionLabelsRole: return notification.actionLabels(); | | |||
298 | case Notifications::HasDefaultActionRole: return notification.hasDefaultAction(); | | |||
299 | case Notifications::DefaultActionLabelRole: return notification.defaultActionLabel(); | | |||
300 | | ||||
301 | case Notifications::UrlsRole: return QVariant::fromValue(notification.urls()); | | |||
302 | | ||||
303 | case Notifications::UrgencyRole: return static_cast<int>(notification.urgency()); | | |||
304 | case Notifications::UserActionFeedbackRole: return notification.userActionFeedback(); | | |||
305 | | ||||
306 | case Notifications::TimeoutRole: return notification.timeout(); | | |||
307 | | ||||
308 | case Notifications::ClosableRole: return true; | | |||
309 | case Notifications::ConfigurableRole: return notification.configurable(); | | |||
310 | case Notifications::ConfigureActionLabelRole: return notification.configureActionLabel(); | | |||
311 | 90 | | |||
312 | case Notifications::ExpiredRole: return notification.expired(); | 91 | const Notification ¬ification = notifications().at(row); | ||
313 | case Notifications::ReadRole: return notification.read(); | 92 | if (!notification.hasDefaultAction()) { | ||
314 | 93 | qCWarning(NOTIFICATIONMANAGER) << "Trying to invoke default action on notification" << notificationId << "which doesn't have one"; | |||
315 | case Notifications::HasReplyActionRole: return notification.hasReplyAction(); | 94 | return; | ||
316 | case Notifications::ReplyActionLabelRole: return notification.replyActionLabel(); | | |||
317 | case Notifications::ReplyPlaceholderTextRole: return notification.replyPlaceholderText(); | | |||
318 | case Notifications::ReplySubmitButtonTextRole: return notification.replySubmitButtonText(); | | |||
319 | case Notifications::ReplySubmitButtonIconNameRole: return notification.replySubmitButtonIconName(); | | |||
320 | } | 95 | } | ||
321 | 96 | | |||
322 | return QVariant(); | 97 | Server::self().invokeAction(notificationId, QStringLiteral("default")); // FIXME make a static Notification::defaultActionName() or something | ||
323 | } | 98 | } | ||
324 | 99 | | |||
325 | bool NotificationsModel::setData(const QModelIndex &index, const QVariant &value, int role) | 100 | void NotificationsModel::invokeAction(uint notificationId, const QString &actionName) | ||
326 | { | 101 | { | ||
327 | if (!checkIndex(index)) { | 102 | const int row = rowOfNotification(notificationId); | ||
328 | return false; | 103 | if (row == -1) { | ||
104 | return; | ||||
329 | } | 105 | } | ||
330 | 106 | | |||
331 | Notification ¬ification = d->notifications[index.row()]; | 107 | const Notification ¬ification = notifications().at(row); | ||
332 | 108 | if (!notification.actionNames().contains(actionName)) { | |||
333 | switch (role) { | 109 | qCWarning(NOTIFICATIONMANAGER) << "Trying to invoke action" << actionName << "on notification" << notificationId << "which it doesn't have"; | ||
334 | case Notifications::ReadRole: | 110 | return; | ||
335 | if (value.toBool() != notification.read()) { | | |||
336 | notification.setRead(value.toBool()); | | |||
337 | return true; | | |||
338 | } | | |||
339 | break; | | |||
340 | } | 111 | } | ||
341 | 112 | | |||
342 | return false; | 113 | Server::self().invokeAction(notificationId, actionName); | ||
343 | } | 114 | } | ||
344 | 115 | | |||
345 | int NotificationsModel::rowCount(const QModelIndex &parent) const | 116 | void NotificationsModel::reply(uint notificationId, const QString &text) | ||
346 | { | 117 | { | ||
347 | if (parent.isValid()) { | 118 | const int row = rowOfNotification(notificationId); | ||
348 | return 0; | 119 | if (row == -1) { | ||
349 | } | 120 | return; | ||
350 | | ||||
351 | return d->notifications.count(); | | |||
352 | } | 121 | } | ||
353 | 122 | | |||
354 | QHash<int, QByteArray> NotificationsModel::roleNames() const | 123 | const Notification ¬ification = notifications().at(row); | ||
355 | { | 124 | if (!notification.hasReplyAction()) { | ||
356 | return Utils::roleNames(); | 125 | qCWarning(NOTIFICATIONMANAGER) << "Trying to reply to a notification which doesn't have a reply action"; | ||
126 | return; | ||||
357 | } | 127 | } | ||
358 | 128 | | |||
359 | void NotificationsModel::expire(uint notificationId) | 129 | Server::self().reply(notification.dBusService(), notificationId, text); | ||
360 | { | | |||
361 | if (d->rowOfNotification(notificationId) > -1) { | | |||
362 | Server::self().closeNotification(notificationId, Server::CloseReason::Expired); | | |||
363 | } | | |||
364 | } | 130 | } | ||
365 | 131 | | |||
366 | void NotificationsModel::close(uint notificationId) | | |||
367 | { | | |||
368 | if (d->rowOfNotification(notificationId) > -1) { | | |||
369 | Server::self().closeNotification(notificationId, Server::CloseReason::DismissedByUser); | | |||
370 | } | | |||
371 | } | | |||
372 | 132 | | |||
373 | void NotificationsModel::configure(uint notificationId) | 133 | void NotificationsModel::configure(uint notificationId) | ||
374 | { | 134 | { | ||
375 | const int row = d->rowOfNotification(notificationId); | 135 | const int row = rowOfNotification(notificationId); | ||
376 | if (row == -1) { | 136 | if (row == -1) { | ||
377 | return; | 137 | return; | ||
378 | } | 138 | } | ||
379 | 139 | | |||
380 | const Notification ¬ification = d->notifications.at(row); | 140 | const Notification ¬ification = notifications().at(row); | ||
381 | 141 | | |||
382 | if (notification.d->hasConfigureAction) { | 142 | if (notification.d->hasConfigureAction) { | ||
383 | Server::self().invokeAction(notificationId, QStringLiteral("settings")); // FIXME make a static Notification::configureActionName() or something | 143 | Server::self().invokeAction(notificationId, QStringLiteral("settings")); // FIXME make a static Notification::configureActionName() or something | ||
384 | return; | 144 | return; | ||
385 | } | 145 | } | ||
386 | 146 | | |||
387 | if (!notification.desktopEntry().isEmpty() || !notification.notifyRcName().isEmpty()) { | 147 | if (!notification.desktopEntry().isEmpty() || !notification.notifyRcName().isEmpty()) { | ||
388 | configure(notification.desktopEntry(), notification.notifyRcName(), notification.eventId()); | 148 | configure(notification.desktopEntry(), notification.notifyRcName(), notification.eventId()); | ||
Show All 24 Lines | 156 | { | |||
413 | } | 173 | } | ||
414 | 174 | | |||
415 | QProcess::startDetached(QStringLiteral("kcmshell5"), { | 175 | QProcess::startDetached(QStringLiteral("kcmshell5"), { | ||
416 | QStringLiteral("notifications"), | 176 | QStringLiteral("notifications"), | ||
417 | QStringLiteral("--args"), | 177 | QStringLiteral("--args"), | ||
418 | KShell::joinArgs(args) | 178 | KShell::joinArgs(args) | ||
419 | }); | 179 | }); | ||
420 | } | 180 | } | ||
421 | | ||||
422 | void NotificationsModel::invokeDefaultAction(uint notificationId) | | |||
423 | { | | |||
424 | const int row = d->rowOfNotification(notificationId); | | |||
425 | if (row == -1) { | | |||
426 | return; | | |||
427 | } | | |||
428 | | ||||
429 | const Notification ¬ification = d->notifications.at(row); | | |||
430 | if (!notification.hasDefaultAction()) { | | |||
431 | qCWarning(NOTIFICATIONMANAGER) << "Trying to invoke default action on notification" << notificationId << "which doesn't have one"; | | |||
432 | return; | | |||
433 | } | | |||
434 | | ||||
435 | Server::self().invokeAction(notificationId, QStringLiteral("default")); // FIXME make a static Notification::defaultActionName() or something | | |||
436 | } | | |||
437 | | ||||
438 | void NotificationsModel::invokeAction(uint notificationId, const QString &actionName) | | |||
439 | { | | |||
440 | const int row = d->rowOfNotification(notificationId); | | |||
441 | if (row == -1) { | | |||
442 | return; | | |||
443 | } | | |||
444 | | ||||
445 | const Notification ¬ification = d->notifications.at(row); | | |||
446 | if (!notification.actionNames().contains(actionName)) { | | |||
447 | qCWarning(NOTIFICATIONMANAGER) << "Trying to invoke action" << actionName << "on notification" << notificationId << "which it doesn't have"; | | |||
448 | return; | | |||
449 | } | | |||
450 | | ||||
451 | Server::self().invokeAction(notificationId, actionName); | | |||
452 | } | | |||
453 | | ||||
454 | void NotificationsModel::reply(uint notificationId, const QString &text) | | |||
455 | { | | |||
456 | const int row = d->rowOfNotification(notificationId); | | |||
457 | if (row == -1) { | | |||
458 | return; | | |||
459 | } | | |||
460 | | ||||
461 | const Notification ¬ification = d->notifications.at(row); | | |||
462 | if (!notification.hasReplyAction()) { | | |||
463 | qCWarning(NOTIFICATIONMANAGER) << "Trying to reply to a notification which doesn't have a reply action"; | | |||
464 | return; | | |||
465 | } | | |||
466 | | ||||
467 | Server::self().reply(notification.dBusService(), notificationId, text); | | |||
468 | } | | |||
469 | | ||||
470 | void NotificationsModel::startTimeout(uint notificationId) | | |||
471 | { | | |||
472 | const int row = d->rowOfNotification(notificationId); | | |||
473 | if (row == -1) { | | |||
474 | return; | | |||
475 | } | | |||
476 | | ||||
477 | const Notification ¬ification = d->notifications.at(row); | | |||
478 | | ||||
479 | if (!notification.timeout() || notification.expired()) { | | |||
480 | return; | | |||
481 | } | | |||
482 | | ||||
483 | d->setupNotificationTimeout(notification); | | |||
484 | } | | |||
485 | | ||||
486 | void NotificationsModel::stopTimeout(uint notificationId) | | |||
487 | { | | |||
488 | delete d->notificationTimeouts.take(notificationId); | | |||
489 | } | | |||
490 | | ||||
491 | void NotificationsModel::clear(Notifications::ClearFlags flags) | | |||
492 | { | | |||
493 | if (d->notifications.isEmpty()) { | | |||
494 | return; | | |||
495 | } | | |||
496 | | ||||
497 | // Tries to remove a contiguous group if possible as the likely case is | | |||
498 | // you have n unread notifications at the end of the list, we don't want to | | |||
499 | // remove and signal each item individually | | |||
500 | QVector<QPair<int, int>> clearQueue; | | |||
501 | | ||||
502 | QPair<int, int> clearRange{-1, -1}; | | |||
503 | | ||||
504 | for (int i = d->notifications.count() - 1; i >= 0; --i) { | | |||
505 | const Notification ¬ification = d->notifications.at(i); | | |||
506 | | ||||
507 | bool clear = (flags.testFlag(Notifications::ClearExpired) && notification.expired()); | | |||
508 | | ||||
509 | if (clear) { | | |||
510 | if (clearRange.second == -1) { | | |||
511 | clearRange.second = i; | | |||
512 | } | | |||
513 | clearRange.first = i; | | |||
514 | } else { | | |||
515 | if (clearRange.first != -1) { | | |||
516 | clearQueue.append(clearRange); | | |||
517 | clearRange.first = -1; | | |||
518 | clearRange.second = -1; | | |||
519 | } | | |||
520 | } | | |||
521 | } | | |||
522 | | ||||
523 | if (clearRange.first != -1) { | | |||
524 | clearQueue.append(clearRange); | | |||
525 | clearRange.first = -1; | | |||
526 | clearRange.second = -1; | | |||
527 | } | | |||
528 | | ||||
529 | for (const auto &range : clearQueue) { | | |||
530 | beginRemoveRows(QModelIndex(), range.first, range.second); | | |||
531 | for (int i = range.second; i >= range.first; --i) { | | |||
532 | d->notifications.removeAt(i); | | |||
533 | } | | |||
534 | endRemoveRows(); | | |||
535 | } | | |||
536 | } | |