1 /*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
4 SPDX-FileCopyrightText: 2013-2014 Martin Klapetek <mklapetek@kde.org>
5
6 code from KNotify/KNotifyClient
7 SPDX-FileCopyrightText: 1997 Christian Esken <esken@kde.org>
8 SPDX-FileCopyrightText: 2000 Charles Samuels <charles@kde.org>
9 SPDX-FileCopyrightText: 2000 Stefan Schimanski <1Stein@gmx.de>
10 SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org>
11 SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
12 SPDX-FileCopyrightText: 2000-2003 Carsten Pfeiffer <pfeiffer@kde.org>
13 SPDX-FileCopyrightText: 2005 Allan Sandfeld Jensen <kde@carewolf.com>
14
15 SPDX-License-Identifier: LGPL-2.0-only
16 */
17
18 #include "knotification.h"
19 #include "knotificationmanager_p.h"
20 #include "knotificationreplyaction.h"
21
22 #include <QCoreApplication>
23
24 #include <QStringList>
25 #ifdef QT_WIDGETS_LIB
26 #include <QTabWidget>
27 #endif
28 #include <QTimer>
29 #include <QUrl>
30
31 // incremental notification ID
32 static int notificationIdCounter = 0;
33
34 struct Q_DECL_HIDDEN KNotification::Private {
35 QString eventId;
36 int id = -1;
37 int ref = 0;
38
39 QWidget *widget = nullptr;
40 QString title;
41 QString text;
42 QString iconName;
43 QString defaultAction;
44 QStringList actions;
45 std::unique_ptr<KNotificationReplyAction> replyAction;
46 QPixmap pixmap;
47 ContextList contexts;
48 NotificationFlags flags = KNotification::CloseOnTimeout;
49 QString componentName;
50 KNotification::Urgency urgency = KNotification::DefaultUrgency;
51 QVariantMap hints;
52
53 QTimer updateTimer;
54 bool needUpdate = false;
55 bool isNew = true;
56 bool autoDelete = true;
57
58 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 67)
59 /**
60 * recursive function that raise the widget. @p w
61 *
62 * @see raiseWidget()
63 */
64 static void raiseWidget(QWidget *w);
65 #endif
66 };
67
68 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 75)
KNotification(const QString & eventId,QWidget * parent,const NotificationFlags & flags)69 KNotification::KNotification(const QString &eventId, QWidget *parent, const NotificationFlags &flags)
70 : QObject(parent)
71 , d(new Private)
72 {
73 d->eventId = eventId;
74 d->flags = flags;
75 setWidget(parent);
76 connect(&d->updateTimer, &QTimer::timeout, this, &KNotification::update);
77 d->updateTimer.setSingleShot(true);
78 d->updateTimer.setInterval(100);
79 d->id = ++notificationIdCounter;
80 }
81 #endif
82
KNotification(const QString & eventId,const NotificationFlags & flags,QObject * parent)83 KNotification::KNotification(const QString &eventId, const NotificationFlags &flags, QObject *parent)
84 : QObject(parent)
85 , d(new Private)
86 {
87 d->eventId = eventId;
88 d->flags = flags;
89 connect(&d->updateTimer, &QTimer::timeout, this, &KNotification::update);
90 d->updateTimer.setSingleShot(true);
91 d->updateTimer.setInterval(100);
92 d->id = ++notificationIdCounter;
93 }
94
~KNotification()95 KNotification::~KNotification()
96 {
97 if (d->id >= 0) {
98 KNotificationManager::self()->close(d->id);
99 }
100 delete d;
101 }
102
eventId() const103 QString KNotification::eventId() const
104 {
105 return d->eventId;
106 }
107
setEventId(const QString & eventId)108 void KNotification::setEventId(const QString &eventId)
109 {
110 if (d->eventId != eventId) {
111 d->eventId = eventId;
112 Q_EMIT eventIdChanged();
113 }
114 }
115
title() const116 QString KNotification::title() const
117 {
118 return d->title;
119 }
120
text() const121 QString KNotification::text() const
122 {
123 return d->text;
124 }
125
widget() const126 QWidget *KNotification::widget() const
127 {
128 return d->widget;
129 }
130
setWidget(QWidget * wid)131 void KNotification::setWidget(QWidget *wid)
132 {
133 #ifdef QT_WIDGETS_LIB
134 d->widget = wid;
135 // setParent(wid);
136 if (wid && d->flags & CloseWhenWidgetActivated) {
137 wid->installEventFilter(this);
138 }
139 #endif
140 }
141
setTitle(const QString & title)142 void KNotification::setTitle(const QString &title)
143 {
144 if (title == d->title) {
145 return;
146 }
147
148 d->needUpdate = true;
149 d->title = title;
150 Q_EMIT titleChanged();
151 if (d->id >= 0) {
152 d->updateTimer.start();
153 }
154 }
155
setText(const QString & text)156 void KNotification::setText(const QString &text)
157 {
158 if (text == d->text) {
159 return;
160 }
161
162 d->needUpdate = true;
163 d->text = text;
164 Q_EMIT textChanged();
165 if (d->id >= 0) {
166 d->updateTimer.start();
167 }
168 }
169
setIconName(const QString & icon)170 void KNotification::setIconName(const QString &icon)
171 {
172 if (icon == d->iconName) {
173 return;
174 }
175
176 d->needUpdate = true;
177 d->iconName = icon;
178 Q_EMIT iconNameChanged();
179 if (d->id >= 0) {
180 d->updateTimer.start();
181 }
182 }
183
iconName() const184 QString KNotification::iconName() const
185 {
186 return d->iconName;
187 }
188
pixmap() const189 QPixmap KNotification::pixmap() const
190 {
191 return d->pixmap;
192 }
193
setPixmap(const QPixmap & pix)194 void KNotification::setPixmap(const QPixmap &pix)
195 {
196 d->needUpdate = true;
197 d->pixmap = pix;
198 if (d->id >= 0) {
199 d->updateTimer.start();
200 }
201 }
202
actions() const203 QStringList KNotification::actions() const
204 {
205 return d->actions;
206 }
207
setActions(const QStringList & as)208 void KNotification::setActions(const QStringList &as)
209 {
210 if (as == d->actions) {
211 return;
212 }
213
214 d->needUpdate = true;
215 d->actions = as;
216 Q_EMIT actionsChanged();
217 if (d->id >= 0) {
218 d->updateTimer.start();
219 }
220 }
221
replyAction() const222 KNotificationReplyAction *KNotification::replyAction() const
223 {
224 return d->replyAction.get();
225 }
226
setReplyAction(std::unique_ptr<KNotificationReplyAction> replyAction)227 void KNotification::setReplyAction(std::unique_ptr<KNotificationReplyAction> replyAction)
228 {
229 if (replyAction == d->replyAction) {
230 return;
231 }
232
233 d->needUpdate = true;
234 d->replyAction = std::move(replyAction);
235 if (d->id >= 0) {
236 d->updateTimer.start();
237 }
238 }
239
setDefaultAction(const QString & defaultAction)240 void KNotification::setDefaultAction(const QString &defaultAction)
241 {
242 if (defaultAction == d->defaultAction) {
243 return;
244 }
245
246 d->needUpdate = true;
247 d->defaultAction = defaultAction;
248 Q_EMIT defaultActionChanged();
249 if (d->id >= 0) {
250 d->updateTimer.start();
251 }
252 }
253
defaultAction() const254 QString KNotification::defaultAction() const
255 {
256 return d->defaultAction;
257 }
258
contexts() const259 KNotification::ContextList KNotification::contexts() const
260 {
261 return d->contexts;
262 }
263
setContexts(const KNotification::ContextList & contexts)264 void KNotification::setContexts(const KNotification::ContextList &contexts)
265 {
266 d->contexts = contexts;
267 }
268
addContext(const KNotification::Context & context)269 void KNotification::addContext(const KNotification::Context &context)
270 {
271 d->contexts << context;
272 }
273
addContext(const QString & context_key,const QString & context_value)274 void KNotification::addContext(const QString &context_key, const QString &context_value)
275 {
276 d->contexts << qMakePair(context_key, context_value);
277 }
278
flags() const279 KNotification::NotificationFlags KNotification::flags() const
280 {
281 return d->flags;
282 }
283
setFlags(const NotificationFlags & flags)284 void KNotification::setFlags(const NotificationFlags &flags)
285 {
286 if (d->flags == flags) {
287 return;
288 }
289
290 d->needUpdate = true;
291 d->flags = flags;
292 Q_EMIT flagsChanged();
293 if (d->id >= 0) {
294 d->updateTimer.start();
295 }
296 }
297
componentName() const298 QString KNotification::componentName() const
299 {
300 return d->componentName;
301 }
302
setComponentName(const QString & c)303 void KNotification::setComponentName(const QString &c)
304 {
305 if (d->componentName != c) {
306 d->componentName = c;
307 Q_EMIT componentNameChanged();
308 }
309 }
310
urls() const311 QList<QUrl> KNotification::urls() const
312 {
313 return QUrl::fromStringList(d->hints[QStringLiteral("x-kde-urls")].toStringList());
314 }
315
setUrls(const QList<QUrl> & urls)316 void KNotification::setUrls(const QList<QUrl> &urls)
317 {
318 setHint(QStringLiteral("x-kde-urls"), QUrl::toStringList(urls));
319 Q_EMIT urlsChanged();
320 }
321
urgency() const322 KNotification::Urgency KNotification::urgency() const
323 {
324 return d->urgency;
325 }
326
setUrgency(Urgency urgency)327 void KNotification::setUrgency(Urgency urgency)
328 {
329 if (d->urgency == urgency) {
330 return;
331 }
332
333 d->needUpdate = true;
334 d->urgency = urgency;
335 Q_EMIT urgencyChanged();
336 if (d->id >= 0) {
337 d->updateTimer.start();
338 }
339 }
340
activate(unsigned int action)341 void KNotification::activate(unsigned int action)
342 {
343 switch (action) {
344 case 0:
345 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 76)
346 Q_EMIT activated();
347 #endif
348 Q_EMIT defaultActivated();
349 break;
350 case 1:
351 Q_EMIT action1Activated();
352 break;
353 case 2:
354 Q_EMIT action2Activated();
355 break;
356 case 3:
357 Q_EMIT action3Activated();
358 break;
359 }
360
361 // emitting activated() makes the Manager close all the active plugins
362 // which will deref() the KNotification object, which will result
363 // in closing the notification
364 Q_EMIT activated(action);
365 }
366
close()367 void KNotification::close()
368 {
369 if (d->id >= 0) {
370 KNotificationManager::self()->close(d->id);
371 }
372
373 if (d->id == -1) {
374 d->id = -2;
375 Q_EMIT closed();
376 if (d->autoDelete) {
377 deleteLater();
378 } else {
379 // reset for being reused
380 d->isNew = true;
381 d->id = ++notificationIdCounter;
382 }
383 }
384 }
385
386 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 67)
raiseWidget()387 void KNotification::raiseWidget()
388 {
389 if (!d->widget) {
390 return;
391 }
392
393 Private::raiseWidget(d->widget);
394 }
395 #endif
396
397 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 67)
raiseWidget(QWidget * w)398 void KNotification::Private::raiseWidget(QWidget *w)
399 {
400 // TODO this function is far from finished.
401 if (w->isTopLevel()) {
402 w->raise();
403 w->activateWindow();
404 } else {
405 QWidget *pw = w->parentWidget();
406 raiseWidget(pw);
407
408 if (QTabWidget *tab_widget = qobject_cast<QTabWidget *>(pw)) {
409 tab_widget->setCurrentIndex(tab_widget->indexOf(w));
410 }
411 }
412 }
413 #endif
414
defaultComponentName()415 static QString defaultComponentName()
416 {
417 #if defined(Q_OS_ANDROID)
418 return QStringLiteral("android_defaults");
419 #else
420 return QStringLiteral("plasma_workspace");
421 #endif
422 }
423
event(const QString & eventid,const QString & title,const QString & text,const QPixmap & pixmap,QWidget * widget,const NotificationFlags & flags,const QString & componentName)424 KNotification *KNotification::event(const QString &eventid,
425 const QString &title,
426 const QString &text,
427 const QPixmap &pixmap,
428 QWidget *widget,
429 const NotificationFlags &flags,
430 const QString &componentName)
431 {
432 KNotification *notify = new KNotification(eventid, flags);
433 notify->setWidget(widget);
434 notify->setTitle(title);
435 notify->setText(text);
436 notify->setPixmap(pixmap);
437 notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName);
438
439 QTimer::singleShot(0, notify, &KNotification::sendEvent);
440
441 return notify;
442 }
443
event(const QString & eventid,const QString & text,const QPixmap & pixmap,QWidget * widget,const NotificationFlags & flags,const QString & componentName)444 KNotification *KNotification::event(const QString &eventid,
445 const QString &text,
446 const QPixmap &pixmap,
447 QWidget *widget,
448 const NotificationFlags &flags,
449 const QString &componentName)
450 {
451 return event(eventid, QString(), text, pixmap, widget, flags, componentName);
452 }
453
454 KNotification *
event(StandardEvent eventid,const QString & title,const QString & text,const QPixmap & pixmap,QWidget * widget,const NotificationFlags & flags)455 KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QPixmap &pixmap, QWidget *widget, const NotificationFlags &flags)
456 {
457 return event(standardEventToEventId(eventid), title, text, pixmap, widget, flags | DefaultEvent);
458 }
459
event(StandardEvent eventid,const QString & text,const QPixmap & pixmap,QWidget * widget,const NotificationFlags & flags)460 KNotification *KNotification::event(StandardEvent eventid, const QString &text, const QPixmap &pixmap, QWidget *widget, const NotificationFlags &flags)
461 {
462 return event(eventid, QString(), text, pixmap, widget, flags);
463 }
464
event(const QString & eventid,const QString & title,const QString & text,const QString & iconName,QWidget * widget,const NotificationFlags & flags,const QString & componentName)465 KNotification *KNotification::event(const QString &eventid,
466 const QString &title,
467 const QString &text,
468 const QString &iconName,
469 QWidget *widget,
470 const NotificationFlags &flags,
471 const QString &componentName)
472 {
473 KNotification *notify = new KNotification(eventid, flags);
474 notify->setWidget(widget);
475 notify->setTitle(title);
476 notify->setText(text);
477 notify->setIconName(iconName);
478 notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName);
479
480 QTimer::singleShot(0, notify, &KNotification::sendEvent);
481
482 return notify;
483 }
484
485 KNotification *
event(StandardEvent eventid,const QString & title,const QString & text,const QString & iconName,QWidget * widget,const NotificationFlags & flags)486 KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QString &iconName, QWidget *widget, const NotificationFlags &flags)
487 {
488 return event(standardEventToEventId(eventid), title, text, iconName, widget, flags | DefaultEvent);
489 }
490
event(StandardEvent eventid,const QString & title,const QString & text,QWidget * widget,const NotificationFlags & flags)491 KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, QWidget *widget, const NotificationFlags &flags)
492 {
493 return event(standardEventToEventId(eventid), title, text, standardEventToIconName(eventid), widget, flags | DefaultEvent);
494 }
495
ref()496 void KNotification::ref()
497 {
498 d->ref++;
499 }
deref()500 void KNotification::deref()
501 {
502 Q_ASSERT(d->ref > 0);
503 d->ref--;
504 if (d->ref == 0) {
505 d->id = -1;
506 close();
507 }
508 }
509
beep(const QString & reason,QWidget * widget)510 void KNotification::beep(const QString &reason, QWidget *widget)
511 {
512 event(QStringLiteral("beep"), reason, QPixmap(), widget, CloseOnTimeout | DefaultEvent);
513 }
514
sendEvent()515 void KNotification::sendEvent()
516 {
517 d->needUpdate = false;
518 if (d->isNew) {
519 d->isNew = false;
520 KNotificationManager::self()->notify(this);
521 } else {
522 KNotificationManager::self()->reemit(this);
523 }
524 }
525
id()526 int KNotification::id()
527 {
528 if (!d) {
529 return -1;
530 }
531 return d->id;
532 }
533
appName() const534 QString KNotification::appName() const
535 {
536 QString appname;
537
538 if (d->flags & DefaultEvent) {
539 appname = defaultComponentName();
540 } else if (!d->componentName.isEmpty()) {
541 appname = d->componentName;
542 } else {
543 appname = QCoreApplication::applicationName();
544 }
545
546 return appname;
547 }
548
isAutoDelete() const549 bool KNotification::isAutoDelete() const
550 {
551 return d->autoDelete;
552 }
553
setAutoDelete(bool autoDelete)554 void KNotification::setAutoDelete(bool autoDelete)
555 {
556 if (d->autoDelete != autoDelete) {
557 d->autoDelete = autoDelete;
558 Q_EMIT autoDeleteChanged();
559 }
560 }
561
update()562 void KNotification::update()
563 {
564 if (d->needUpdate) {
565 KNotificationManager::self()->update(this);
566 }
567 }
568
eventFilter(QObject * watched,QEvent * event)569 bool KNotification::eventFilter(QObject *watched, QEvent *event)
570 {
571 #ifdef QT_WIDGETS_LIB
572 if (watched == d->widget) {
573 if (event->type() == QEvent::WindowActivate) {
574 if (d->flags & CloseWhenWidgetActivated) {
575 QTimer::singleShot(500, this, &KNotification::close);
576 }
577 }
578 }
579 #endif
580
581 return false;
582 }
583
standardEventToEventId(KNotification::StandardEvent event)584 QString KNotification::standardEventToEventId(KNotification::StandardEvent event)
585 {
586 QString eventId;
587 switch (event) {
588 case Warning:
589 eventId = QStringLiteral("warning");
590 break;
591 case Error:
592 eventId = QStringLiteral("fatalerror");
593 break;
594 case Catastrophe:
595 eventId = QStringLiteral("catastrophe");
596 break;
597 case Notification: // fall through
598 default:
599 eventId = QStringLiteral("notification");
600 break;
601 }
602 return eventId;
603 }
604
standardEventToIconName(KNotification::StandardEvent event)605 QString KNotification::standardEventToIconName(KNotification::StandardEvent event)
606 {
607 QString iconName;
608 switch (event) {
609 case Warning:
610 iconName = QStringLiteral("dialog-warning");
611 break;
612 case Error:
613 iconName = QStringLiteral("dialog-error");
614 break;
615 case Catastrophe:
616 iconName = QStringLiteral("dialog-error");
617 break;
618 case Notification: // fall through
619 default:
620 iconName = QStringLiteral("dialog-information");
621 break;
622 }
623 return iconName;
624 }
625
setHint(const QString & hint,const QVariant & value)626 void KNotification::setHint(const QString &hint, const QVariant &value)
627 {
628 if (value == d->hints.value(hint)) {
629 return;
630 }
631
632 d->needUpdate = true;
633 d->hints[hint] = value;
634 if (d->id >= 0) {
635 d->updateTimer.start();
636 }
637 }
638
hints() const639 QVariantMap KNotification::hints() const
640 {
641 return d->hints;
642 }
643