Plasma-workspace

notification.cpp
1/*
2 SPDX-FileCopyrightText: 2008 Dmitry Suzdalev <dimsuz@gmail.com>
3 SPDX-FileCopyrightText: 2017 David Edmundson <davidedmundson@kde.org>
4 SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
5
6 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7*/
8
9#include "notification.h"
10#include "notification_p.h"
11
12#include <algorithm>
13
14#include <QDBusArgument>
15#include <QDebug>
16#include <QImageReader>
17#include <QRegularExpression>
18#include <QXmlStreamReader>
19
20#include <KApplicationTrader>
21#include <KConfig>
22#include <KConfigGroup>
23#include <KService>
24
25#include "debug.h"
26
27using namespace NotificationManager;
28using namespace Qt::StringLiterals;
29
30QCache<uint, QImage> Notification::Private::s_imageCache = QCache<uint, QImage>{};
31
32Notification::Private::Private()
33{
34}
35
36Notification::Private::~Private()
37{
38 // The image cache is cleared by AbstractNotificationsModel::pendingRemovalTimer
39}
40
41QString Notification::Private::sanitize(const QString &text)
42{
43 // replace all \ns with <br/>
44 QString t = text;
45
46 t.replace(QLatin1String("\n"), QStringLiteral("<br/>"));
47 // Now remove all inner whitespace (\ns are already <br/>s)
48 t = std::move(t).simplified();
49 // Finally, check if we don't have multiple <br/>s following,
50 // can happen for example when "\n \n" is sent, this replaces
51 // all <br/>s in succession with just one
52 static const QRegularExpression brExpr(QStringLiteral("<br/>\\s*<br/>(\\s|<br/>)*"));
53 t.replace(brExpr, QLatin1String("<br/>"));
54 // This fancy RegExp escapes every occurrence of & since QtQuick Text will blatantly cut off
55 // text where it finds a stray ampersand.
56 // Only &{apos, quot, gt, lt, amp}; as well as &#123 character references will be allowed
57 static const QRegularExpression escapeExpr(QStringLiteral("&(?!(?:apos|quot|[gl]t|amp);|#)"));
58 t.replace(escapeExpr, QLatin1String("&amp;"));
59
60 // Don't bother adding some HTML structure if the body is now empty
61 if (t.isEmpty()) {
62 return t;
63 }
64
65 t = u"<html>" + std::move(t) + u"</html>";
67 QString result;
68 QXmlStreamWriter out(&result);
69
70 static constexpr std::array<QStringView, 10> allowedTags{u"b", u"i", u"u", u"img", u"a", u"html", u"br", u"table", u"tr", u"td"};
71
72 out.writeStartDocument();
73 while (!r.atEnd()) {
74 r.readNext();
75
76 if (r.tokenType() == QXmlStreamReader::StartElement) {
77 const QStringView name = r.name();
78 if (std::ranges::find(allowedTags, name) == allowedTags.end()) {
79 continue;
80 }
81 out.writeStartElement(name);
82 if (name == QLatin1String("img")) {
83 const QString src = r.attributes().value("src").toString();
84 const QStringView alt = r.attributes().value("alt");
85
86 const QUrl url(src);
87 if (url.isLocalFile()) {
88 out.writeAttribute(QStringLiteral("src"), src);
89 } else {
90 // image denied for security reasons! Do not copy the image src here!
91 }
92
93 out.writeAttribute(u"alt", alt);
94 }
95 if (name == QLatin1Char('a')) {
96 out.writeAttribute(u"href", r.attributes().value("href"));
97 }
98 }
99
100 if (r.tokenType() == QXmlStreamReader::EndElement) {
101 const QStringView name = r.name();
102 if (std::ranges::find(allowedTags, name) == allowedTags.end()) {
103 continue;
104 }
105 out.writeEndElement();
106 }
107
108 if (r.tokenType() == QXmlStreamReader::Characters) {
109 out.writeCharacters(r.text()); // this auto escapes chars -> HTML entities
110 }
111 }
112 out.writeEndDocument();
113
114 if (r.hasError()) {
115 qCWarning(NOTIFICATIONMANAGER) << "Notification to send to backend contains invalid XML: " << r.errorString() << "line" << r.lineNumber() << "col"
116 << r.columnNumber();
117 }
118
119 // The Text.StyledText format handles only html3.2 stuff and &apos; is html4 stuff
120 // so we need to replace it here otherwise it will not render at all.
121 result.replace(QLatin1String("&apos;"), QChar(u'\''));
122
123 return result;
124}
125
126QImage Notification::Private::decodeNotificationSpecImageHint(const QDBusArgument &arg)
127{
128 int width, height, rowStride, hasAlpha, bitsPerSample, channels;
129 QByteArray pixels;
130 char *ptr;
131 char *end;
132
134 return QImage();
135 }
136 arg.beginStructure();
137 arg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> pixels;
138 arg.endStructure();
139
140#define SANITY_CHECK(condition) \
141 if (!(condition)) { \
142 qCWarning(NOTIFICATIONMANAGER) << "Image decoding sanity check failed on" << #condition; \
143 return QImage(); \
144 }
145
146 SANITY_CHECK(width > 0);
147 SANITY_CHECK(width < 2048);
148 SANITY_CHECK(height > 0);
149 SANITY_CHECK(height < 2048);
150 SANITY_CHECK(rowStride > 0);
151
152#undef SANITY_CHECK
153
154 auto copyLineRGB32 = [](QRgb *dst, const char *src, int width) {
155 const char *end = src + width * 3;
156 for (; src != end; ++dst, src += 3) {
157 *dst = qRgb(src[0], src[1], src[2]);
158 }
159 };
160
161 auto copyLineARGB32 = [](QRgb *dst, const char *src, int width) {
162 const char *end = src + width * 4;
163 for (; src != end; ++dst, src += 4) {
164 *dst = qRgba(src[0], src[1], src[2], src[3]);
165 }
166 };
167
169 void (*fcn)(QRgb *, const char *, int) = nullptr;
170 if (bitsPerSample == 8) {
171 if (channels == 4) {
172 format = QImage::Format_ARGB32;
173 fcn = copyLineARGB32;
174 } else if (channels == 3) {
175 format = QImage::Format_RGB32;
176 fcn = copyLineRGB32;
177 }
178 }
179 if (format == QImage::Format_Invalid) {
180 qCWarning(NOTIFICATIONMANAGER) << "Unsupported image format (hasAlpha:" << hasAlpha << "bitsPerSample:" << bitsPerSample << "channels:" << channels
181 << ")";
182 return QImage();
183 }
184
185 QImage image(width, height, format);
186 ptr = pixels.data();
187 end = ptr + pixels.length();
188 for (int y = 0; y < height; ++y, ptr += rowStride) {
189 if (ptr + channels * width > end) {
190 qCWarning(NOTIFICATIONMANAGER) << "Image data is incomplete. y:" << y << "height:" << height;
191 break;
192 }
193 fcn((QRgb *)image.scanLine(y), ptr, width);
194 }
195
196 return image;
197}
198
199void Notification::Private::sanitizeImage(QImage &image)
200{
201 if (image.isNull()) {
202 return;
203 }
204
205 const QSize max = maximumImageSize();
206 if (image.size().width() > max.width() || image.size().height() > max.height()) {
208 }
209}
210
211void Notification::Private::loadImagePath(const QString &path)
212{
213 // image_path and appIcon should either be a URL with file scheme or the name of a themed icon.
214 // We're lenient and also allow local paths.
215
216 s_imageCache.remove(id); // clear
217 icon.clear();
218
219 QUrl imageUrl;
220 if (path.startsWith(QLatin1Char('/'))) {
221 imageUrl = QUrl::fromLocalFile(path);
222 } else if (path.contains(QLatin1Char('/'))) { // bad heuristic to detect a URL
223 imageUrl = QUrl(path);
224
225 if (!imageUrl.isLocalFile()) {
226 qCDebug(NOTIFICATIONMANAGER) << "Refused to load image from" << path << "which isn't a valid local location.";
227 return;
228 }
229 }
230
231 if (!imageUrl.isValid()) {
232 // try icon path instead;
233 icon = path;
234 return;
235 }
236
237 QImageReader reader(imageUrl.toLocalFile());
238 reader.setAutoTransform(true);
239
240 if (QSize imageSize = reader.size(); imageSize.isValid()) {
241 if (imageSize.width() > maximumImageSize().width() || imageSize.height() > maximumImageSize().height()) {
242 imageSize = imageSize.scaled(maximumImageSize(), Qt::KeepAspectRatio);
243 reader.setScaledSize(imageSize);
244 }
245 s_imageCache.insert(id, new QImage(reader.read()), imageSize.width() * imageSize.height());
246 }
247}
248
249QString Notification::Private::defaultComponentName()
250{
251 // NOTE Keep in sync with KNotification
252 return QStringLiteral("plasma_workspace");
253}
254
255constexpr QSize Notification::Private::maximumImageSize()
256{
257 return QSize(256, 256);
258}
259
260KService::Ptr Notification::Private::serviceForDesktopEntry(const QString &desktopEntry)
261{
262 if (desktopEntry.isEmpty()) {
263 return {};
264 }
265
266 KService::Ptr service;
267
268 if (desktopEntry.startsWith(QLatin1Char('/'))) {
269 service = KService::serviceByDesktopPath(desktopEntry);
270 } else {
271 service = KService::serviceByDesktopName(desktopEntry);
272 }
273
274 if (!service) {
275 const QString lowerDesktopEntry = desktopEntry.toLower();
276 service = KService::serviceByDesktopName(lowerDesktopEntry);
277 }
278
279 // Try if it's a renamed flatpak
280 if (!service) {
281 const QString desktopId = desktopEntry + QLatin1String(".desktop");
282
283 const auto services = KApplicationTrader::query([&desktopId](const KService::Ptr &app) -> bool {
284 const QStringList renamedFrom = app->property<QStringList>(QStringLiteral("X-Flatpak-RenamedFrom"));
285 return renamedFrom.contains(desktopId);
286 });
287
288 if (!services.isEmpty()) {
289 service = services.first();
290 }
291 }
292
293 // Try snap instance name.
294 if (!service) {
295 const auto services = KApplicationTrader::query([&desktopEntry](const KService::Ptr &app) -> bool {
296 const QString snapInstanceName = app->property<QString>(QStringLiteral("X-SnapInstanceName"));
297 return desktopEntry.compare(snapInstanceName, Qt::CaseInsensitive) == 0;
298 });
299
300 if (!services.isEmpty()) {
301 service = services.first();
302 }
303 }
304
305 return service;
306}
307
308void Notification::Private::setDesktopEntry(const QString &desktopEntry)
309{
310 QString serviceName;
311
312 configurableService = false;
313
314 KService::Ptr service = serviceForDesktopEntry(desktopEntry);
315 if (service) {
316 this->desktopEntry = service->desktopEntryName();
317 serviceName = service->name();
318 applicationIconName = service->icon();
319 configurableService = !service->noDisplay();
320 }
321
322 const bool isDefaultEvent = (notifyRcName == defaultComponentName());
323 configurableNotifyRc = false;
324 if (!notifyRcName.isEmpty()) {
325 // Check whether the application actually has notifications we can configure
326 KConfig config(notifyRcName + QStringLiteral(".notifyrc"), KConfig::NoGlobals);
327
328 QStringList configSources =
329 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications6/%1.notifyrc").arg(notifyRcName));
330 // Keep compatibility with KF5 applications
331 if (configSources.isEmpty()) {
332 configSources = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications5/%1.notifyrc").arg(notifyRcName));
333 }
334 // `QStandardPaths` follows the order of precedence given by `$XDG_DATA_DIRS
335 // (more priority goest first), but for `addConfigSources() it is the opposite
336 std::reverse(configSources.begin(), configSources.end());
337 config.addConfigSources(configSources);
338
339 KConfigGroup globalGroup(&config, u"Global"_s);
340
341 const QString iconName = globalGroup.readEntry("IconName");
342
343 // also only overwrite application icon name for non-default events (or if we don't have a service icon)
344 if (!iconName.isEmpty() && (!isDefaultEvent || applicationIconName.isEmpty())) {
345 applicationIconName = iconName;
346 }
347
348 const QRegularExpression regexp(QStringLiteral("^Event/([^/]*)$"));
349 configurableNotifyRc = !config.groupList().filter(regexp).isEmpty();
350 }
351
352 // For default events we try to show the application name from the desktop entry if possible
353 // This will have us show e.g. "Dr Konqi" instead of generic "Plasma Desktop"
354 // The application may not send an applicationName. Use the name from the desktop entry then
355 if ((isDefaultEvent || applicationName.isEmpty()) && !serviceName.isEmpty()) {
356 applicationName = serviceName;
357 }
358}
359
360void Notification::Private::processHints(const QVariantMap &hints)
361{
362 auto end = hints.end();
363
364 notifyRcName = hints.value(QStringLiteral("x-kde-appname")).toString();
365
366 setDesktopEntry(hints.value(QStringLiteral("desktop-entry")).toString());
367
368 // Special override for KDE Connect since the notification is sent by kdeconnectd
369 // but actually comes from a different app on the phone
370 const QString applicationDisplayName = hints.value(QStringLiteral("x-kde-display-appname")).toString();
371 if (!applicationDisplayName.isEmpty()) {
372 applicationName = applicationDisplayName;
373 }
374
375 originName = hints.value(QStringLiteral("x-kde-origin-name")).toString();
376
377 eventId = hints.value(QStringLiteral("x-kde-eventId")).toString();
378 xdgTokenAppId = hints.value(QStringLiteral("x-kde-xdgTokenAppId")).toString();
379
380 bool ok;
381 const int urgency = hints.value(QStringLiteral("urgency")).toInt(&ok); // DBus type is actually "byte"
382 if (ok) {
383 // FIXME use separate enum again
384 switch (urgency) {
385 case 0:
386 setUrgency(Notifications::LowUrgency);
387 break;
388 case 1:
390 break;
391 case 2:
392 setUrgency(Notifications::CriticalUrgency);
393 break;
394 }
395 }
396
397 resident = hints.value(QStringLiteral("resident")).toBool();
398 transient = hints.value(QStringLiteral("transient")).toBool();
399
400 userActionFeedback = hints.value(QStringLiteral("x-kde-user-action-feedback")).toBool();
401 if (userActionFeedback) {
402 // A confirmation of an explicit user interaction is assumed to have been seen by the user.
403 read = true;
404 }
405
406 urls = QUrl::fromStringList(hints.value(QStringLiteral("x-kde-urls")).toStringList());
407
408 replyPlaceholderText = hints.value(QStringLiteral("x-kde-reply-placeholder-text")).toString();
409 replySubmitButtonText = hints.value(QStringLiteral("x-kde-reply-submit-button-text")).toString();
410 replySubmitButtonIconName = hints.value(QStringLiteral("x-kde-reply-submit-button-icon-name")).toString();
411
412 category = hints.value(QStringLiteral("category")).toString();
413
414 // Underscored hints was in use in version 1.1 of the spec but has been
415 // replaced by dashed hints in version 1.2. We need to support it for
416 // users of the 1.2 version of the spec.
417 auto it = hints.find(QStringLiteral("image-data"));
418 if (it == end) {
419 it = hints.find(QStringLiteral("image_data"));
420 }
421 if (it == end) {
422 // This hint was in use in version 1.0 of the spec but has been
423 // replaced by "image_data" in version 1.1. We need to support it for
424 // users of the 1.0 version of the spec.
425 it = hints.find(QStringLiteral("icon_data"));
426 }
427
428 QImage *image = nullptr;
429 if (it != end) {
430 QImage imageFromHint(decodeNotificationSpecImageHint(it->value<QDBusArgument>()));
431 if (!imageFromHint.isNull()) {
432 Q_ASSERT_X(imageFromHint.width() > 0 && imageFromHint.height() > 0,
433 Q_FUNC_INFO,
434 qPrintable(QStringLiteral("%1 %2").arg(QString::number(imageFromHint.width()), QString::number(imageFromHint.height()))));
435 sanitizeImage(imageFromHint);
436 image = new QImage(imageFromHint);
437 s_imageCache.insert(id, image, imageFromHint.width() * imageFromHint.height());
438 }
439 }
440
441 if (!image) {
442 it = hints.find(QStringLiteral("image-path"));
443 if (it == end) {
444 it = hints.find(QStringLiteral("image_path"));
445 }
446
447 if (it != end) {
448 loadImagePath(it->toString());
449 }
450 }
451}
452
453void Notification::Private::setUrgency(Notifications::Urgency urgency)
454{
455 this->urgency = urgency;
456
457 // Critical notifications must not time out
458 // TODO should we really imply this here and not on the view side?
459 // are there usecases for critical but can expire?
460 // "critical updates available"?
461 if (urgency == Notifications::CriticalUrgency) {
462 timeout = 0;
463 }
464}
465
466Notification::Notification(uint id)
467 : d(new Private())
468{
469 d->id = id;
470 d->created = QDateTime::currentDateTimeUtc();
471}
472
473Notification::Notification(const Notification &other)
474 : d(new Private(*other.d))
475{
476}
477
478Notification::Notification(Notification &&other) noexcept
479 : d(other.d)
480{
481 other.d = nullptr;
482}
483
484Notification &Notification::operator=(const Notification &other)
485{
486 *d = *other.d;
487 return *this;
488}
489
490Notification &Notification::operator=(Notification &&other) noexcept
491{
492 d = other.d;
493 other.d = nullptr;
494 return *this;
495}
496
497Notification::~Notification()
498{
499 delete d;
500}
501
502uint Notification::id() const
503{
504 return d->id;
505}
506
507QString Notification::dBusService() const
508{
509 return d->dBusService;
510}
511
512void Notification::setDBusService(const QString &dBusService)
513{
514 d->dBusService = dBusService;
515}
516
517QDateTime Notification::created() const
518{
519 return d->created;
520}
521
522void Notification::setCreated(const QDateTime &created)
523{
524 d->created = created;
525}
526
527QDateTime Notification::updated() const
528{
529 return d->updated;
530}
531
532void Notification::resetUpdated()
533{
534 d->updated = QDateTime::currentDateTimeUtc();
535}
536
537bool Notification::read() const
538{
539 return d->read;
540}
541
542void Notification::setRead(bool read)
543{
544 d->read = read;
545}
546
547QString Notification::summary() const
548{
549 return d->summary;
550}
551
552void Notification::setSummary(const QString &summary)
553{
554 d->summary = summary;
555}
556
557QString Notification::body() const
558{
559 return d->body;
560}
561
562void Notification::setBody(const QString &body)
563{
564 d->rawBody = body;
565 d->body = Private::sanitize(body.trimmed());
566}
567
568QString Notification::rawBody() const
569{
570 return d->rawBody;
571}
572
573QString Notification::icon() const
574{
575 return d->icon;
576}
577
578void Notification::setIcon(const QString &icon)
579{
580 d->loadImagePath(icon);
581}
582
583QImage Notification::image() const
584{
585 if (d->s_imageCache.contains(d->id)) {
586 return *d->s_imageCache.object(d->id);
587 }
588 return QImage();
589}
590
591void Notification::setImage(const QImage &image)
592{
593 d->s_imageCache.insert(d->id, new QImage(image));
594}
595
596QString Notification::desktopEntry() const
597{
598 return d->desktopEntry;
599}
600
601void Notification::setDesktopEntry(const QString &desktopEntry)
602{
603 d->setDesktopEntry(desktopEntry);
604}
605
606QString Notification::notifyRcName() const
607{
608 return d->notifyRcName;
609}
610
611QString Notification::eventId() const
612{
613 return d->eventId;
614}
615
616QString Notification::applicationName() const
617{
618 return d->applicationName;
619}
620
621void Notification::setApplicationName(const QString &applicationName)
622{
623 d->applicationName = applicationName;
624}
625
626QString Notification::applicationIconName() const
627{
628 return d->applicationIconName;
629}
630
631void Notification::setApplicationIconName(const QString &applicationIconName)
632{
633 d->applicationIconName = applicationIconName;
634}
635
636QString Notification::originName() const
637{
638 return d->originName;
639}
640
641QStringList Notification::actionNames() const
642{
643 return d->actionNames;
644}
645
646QStringList Notification::actionLabels() const
647{
648 return d->actionLabels;
649}
650
651bool Notification::hasDefaultAction() const
652{
653 return d->hasDefaultAction;
654}
655
656QString Notification::defaultActionLabel() const
657{
658 return d->defaultActionLabel;
659}
660
661void Notification::setActions(const QStringList &actions)
662{
663 if (actions.count() % 2 != 0) {
664 qCWarning(NOTIFICATIONMANAGER) << "List of actions must contain an even number of items, tried to set actions to" << actions;
665 return;
666 }
667
668 d->hasDefaultAction = false;
669 d->hasConfigureAction = false;
670 d->hasReplyAction = false;
671
672 QStringList names;
673 QStringList labels;
674
675 for (int i = 0; i < actions.count(); i += 2) {
676 const QString &name = actions.at(i);
677 const QString &label = actions.at(i + 1);
678
679 if (!d->hasDefaultAction && name == QLatin1String("default")) {
680 d->hasDefaultAction = true;
681 d->defaultActionLabel = label;
682 continue;
683 }
684
685 if (!d->hasConfigureAction && name == QLatin1String("settings")) {
686 d->hasConfigureAction = true;
687 d->configureActionLabel = label;
688 continue;
689 }
690
691 if (!d->hasReplyAction && name == QLatin1String("inline-reply")) {
692 d->hasReplyAction = true;
693 d->replyActionLabel = label;
694 continue;
695 }
696
697 names << name;
698 labels << label;
699 }
700
701 d->actionNames = names;
702 d->actionLabels = labels;
703}
704
705QList<QUrl> Notification::urls() const
706{
707 return d->urls;
708}
709
710void Notification::setUrls(const QList<QUrl> &urls)
711{
712 d->urls = urls;
713}
714
715Notifications::Urgency Notification::urgency() const
716{
717 return d->urgency;
718}
719
720bool Notification::userActionFeedback() const
721{
722 return d->userActionFeedback;
723}
724
725int Notification::timeout() const
726{
727 return d->timeout;
728}
729
730void Notification::setTimeout(int timeout)
731{
732 d->timeout = timeout;
733}
734
735bool Notification::configurable() const
736{
737 return d->hasConfigureAction || d->configurableNotifyRc || d->configurableService;
738}
739
740QString Notification::configureActionLabel() const
741{
742 return d->configureActionLabel;
743}
744
745bool Notification::hasReplyAction() const
746{
747 return d->hasReplyAction;
748}
749
750QString Notification::replyActionLabel() const
751{
752 return d->replyActionLabel;
753}
754
755QString Notification::replyPlaceholderText() const
756{
757 return d->replyPlaceholderText;
758}
759
760QString Notification::replySubmitButtonText() const
761{
762 return d->replySubmitButtonText;
763}
764
765QString Notification::replySubmitButtonIconName() const
766{
767 return d->replySubmitButtonIconName;
768}
769
770QString Notification::category() const
771{
772 return d->category;
773}
774
775bool Notification::expired() const
776{
777 return d->expired;
778}
779
780void Notification::setExpired(bool expired)
781{
782 d->expired = expired;
783}
784
785bool Notification::dismissed() const
786{
787 return d->dismissed;
788}
789
790void Notification::setDismissed(bool dismissed)
791{
792 d->dismissed = dismissed;
793}
794
795bool Notification::resident() const
796{
797 return d->resident;
798}
799
800void Notification::setResident(bool resident)
801{
802 d->resident = resident;
803}
804
805bool Notification::transient() const
806{
807 return d->transient;
808}
809
810void Notification::setTransient(bool transient)
811{
812 d->transient = transient;
813}
814
815QVariantMap Notification::hints() const
816{
817 return d->hints;
818}
819
820void Notification::setHints(const QVariantMap &hints)
821{
822 d->hints = hints;
823}
824
825void Notification::processHints(const QVariantMap &hints)
826{
827 d->processHints(hints);
828}
static Ptr serviceByDesktopName(const QString &_name)
static Ptr serviceByDesktopPath(const QString &_path)
Represents a single notification.
Urgency
The notification urgency.
@ LowUrgency
The notification has low urgency, it is not important and may not be shown or added to a history.
@ NormalUrgency
The notification has normal urgency. This is also the default if no urgecny is supplied.
char * toString(const EngineQuery &query)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QVariant read(const QByteArray &data, int versionOverride=0)
QString path(const QString &relativePath)
void transient(const QString &message, const QString &title)
QString name(StandardAction id)
Category category(StandardShortcut id)
QString label(StandardShortcut id)
const QList< QKeySequence > & end()
QDateTime currentDateTimeUtc()
void beginStructure()
ElementType currentType() const const
void endStructure()
bool isNull() const const
QImage scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
QSize size() const const
int width() const const
const_reference at(qsizetype i) const const
iterator begin()
qsizetype count() const const
iterator end()
bool isEmpty() const const
int height() const const
bool isValid() const const
int width() const const
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString simplified() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
KeepAspectRatio
CaseInsensitive
SmoothTransformation
QUrl fromLocalFile(const QString &localFile)
QList< QUrl > fromStringList(const QStringList &urls, ParsingMode mode)
bool isLocalFile() const const
bool isValid() const const
QString toLocalFile() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:58:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.