1/*
2 This file is part of the KDE Libraries
3
4 SPDX-FileCopyrightText: 2000 Espen Sand <espen@kde.org>
5 SPDX-FileCopyrightText: 2006 Nicolas GOUTTE <goutte@kde.org>
6 SPDX-FileCopyrightText: 2008 Friedrich W. H. Kossebau <kossebau@kde.org>
7 SPDX-FileCopyrightText: 2010 Teo Mrnjavac <teo@kde.org>
8 SPDX-FileCopyrightText: 2017 Harald Sitter <sitter@kde.org>
9 SPDX-FileCopyrightText: 2021 Julius Künzel <jk.kdedev@smartlab.uber.space>
10
11 SPDX-License-Identifier: LGPL-2.0-or-later
12*/
13
14#include "kaboutdata.h"
15#include "kjsonutils.h"
16
17#include <QCommandLineOption>
18#include <QCommandLineParser>
19#include <QCoreApplication>
20#include <QFile>
21#include <QHash>
22#include <QJsonObject>
23#include <QList>
24#include <QLoggingCategory>
25#include <QSharedData>
26#include <QStandardPaths>
27#include <QTextStream>
28#include <QUrl>
29
30#include <algorithm>
31
32Q_DECLARE_LOGGING_CATEGORY(KABOUTDATA)
33// logging category for this framework, default: log stuff >= warning
34Q_LOGGING_CATEGORY(KABOUTDATA, "kf.coreaddons.kaboutdata", QtWarningMsg)
35
36class KAboutPersonPrivate : public QSharedData
37{
38public:
39 QString _name;
40 QString _task;
41 QString _emailAddress;
42 QString _webAddress;
43 QUrl _avatarUrl;
44};
45
46KAboutPerson::KAboutPerson(const QString &_name, const QString &_task, const QString &_emailAddress, const QString &_webAddress, const QUrl &avatarUrl)
47 : d(new KAboutPersonPrivate)
48{
49 d->_name = _name;
50 d->_task = _task;
51 d->_emailAddress = _emailAddress;
52 d->_webAddress = _webAddress;
53 d->_avatarUrl = avatarUrl;
54}
55
56KAboutPerson::KAboutPerson(const QString &_name, const QString &_email, bool)
57 : d(new KAboutPersonPrivate)
58{
59 d->_name = _name;
60 d->_emailAddress = _email;
61}
62
63KAboutPerson::KAboutPerson(const KAboutPerson &other) = default;
64
65KAboutPerson::~KAboutPerson() = default;
66
67QString KAboutPerson::name() const
68{
69 return d->_name;
70}
71
72QString KAboutPerson::task() const
73{
74 return d->_task;
75}
76
77QString KAboutPerson::emailAddress() const
78{
79 return d->_emailAddress;
80}
81
82QString KAboutPerson::webAddress() const
83{
84 return d->_webAddress;
85}
86
87QUrl KAboutPerson::avatarUrl() const
88{
89 return d->_avatarUrl;
90}
91
92KAboutPerson &KAboutPerson::operator=(const KAboutPerson &other) = default;
93
94KAboutPerson KAboutPerson::fromJSON(const QJsonObject &obj)
95{
96 const QString name = KJsonUtils::readTranslatedString(jo: obj, QStringLiteral("Name"));
97 const QString task = KJsonUtils::readTranslatedString(jo: obj, QStringLiteral("Task"));
98 const QString email = obj.value(key: QLatin1String("Email")).toString();
99 const QString website = obj.value(key: QLatin1String("Website")).toString();
100 const QUrl avatarUrl = obj.value(key: QLatin1String("AvatarUrl")).toVariant().toUrl();
101 return KAboutPerson(name, task, email, website, avatarUrl);
102}
103
104class KAboutLicensePrivate : public QSharedData
105{
106public:
107 KAboutLicensePrivate(KAboutLicense::LicenseKey licenseType, KAboutLicense::VersionRestriction versionRestriction, const KAboutData *aboutData);
108 KAboutLicensePrivate(const KAboutLicensePrivate &other);
109
110 QString spdxID() const;
111
112 KAboutLicense::LicenseKey _licenseKey;
113 QString _licenseText;
114 QString _pathToLicenseTextFile;
115 KAboutLicense::VersionRestriction _versionRestriction;
116 // needed for access to the possibly changing copyrightStatement()
117 const KAboutData *_aboutData;
118};
119
120KAboutLicensePrivate::KAboutLicensePrivate(KAboutLicense::LicenseKey licenseType,
121 KAboutLicense::VersionRestriction versionRestriction,
122 const KAboutData *aboutData)
123 : QSharedData()
124 , _licenseKey(licenseType)
125 , _versionRestriction(versionRestriction)
126 , _aboutData(aboutData)
127{
128}
129
130KAboutLicensePrivate::KAboutLicensePrivate(const KAboutLicensePrivate &other)
131 : QSharedData(other)
132 , _licenseKey(other._licenseKey)
133 , _licenseText(other._licenseText)
134 , _pathToLicenseTextFile(other._pathToLicenseTextFile)
135 , _versionRestriction(other._versionRestriction)
136 , _aboutData(other._aboutData)
137{
138}
139
140QString KAboutLicensePrivate::spdxID() const
141{
142 switch (_licenseKey) {
143 case KAboutLicense::GPL_V2:
144 return QStringLiteral("GPL-2.0");
145 case KAboutLicense::LGPL_V2:
146 return QStringLiteral("LGPL-2.0");
147 case KAboutLicense::BSDL:
148 return QStringLiteral("BSD-2-Clause");
149 case KAboutLicense::Artistic:
150 return QStringLiteral("Artistic-1.0");
151 case KAboutLicense::GPL_V3:
152 return QStringLiteral("GPL-3.0");
153 case KAboutLicense::LGPL_V3:
154 return QStringLiteral("LGPL-3.0");
155 case KAboutLicense::LGPL_V2_1:
156 return QStringLiteral("LGPL-2.1");
157 case KAboutLicense::MIT:
158 return QStringLiteral("MIT");
159 case KAboutLicense::Custom:
160 case KAboutLicense::File:
161 case KAboutLicense::Unknown:
162 return QString();
163 }
164 return QString();
165}
166
167KAboutLicense::KAboutLicense()
168 : d(new KAboutLicensePrivate(Unknown, {}, nullptr))
169{
170}
171
172KAboutLicense::KAboutLicense(LicenseKey licenseType, VersionRestriction versionRestriction, const KAboutData *aboutData)
173 : d(new KAboutLicensePrivate(licenseType, versionRestriction, aboutData))
174{
175}
176
177KAboutLicense::KAboutLicense(LicenseKey licenseType, const KAboutData *aboutData)
178 : d(new KAboutLicensePrivate(licenseType, OnlyThisVersion, aboutData))
179{
180}
181
182KAboutLicense::KAboutLicense(const KAboutData *aboutData)
183 : d(new KAboutLicensePrivate(Unknown, OnlyThisVersion, aboutData))
184{
185}
186
187KAboutLicense::KAboutLicense(const KAboutLicense &other)
188 : d(other.d)
189{
190}
191
192KAboutLicense::~KAboutLicense()
193{
194}
195
196void KAboutLicense::setLicenseFromPath(const QString &pathToFile)
197{
198 d->_licenseKey = KAboutLicense::File;
199 d->_pathToLicenseTextFile = pathToFile;
200}
201
202void KAboutLicense::setLicenseFromText(const QString &licenseText)
203{
204 d->_licenseKey = KAboutLicense::Custom;
205 d->_licenseText = licenseText;
206}
207
208QString KAboutLicense::text() const
209{
210 QString result;
211
212 const QString lineFeed = QStringLiteral("\n\n");
213
214 if (d->_aboutData && !d->_aboutData->copyrightStatement().isEmpty()) {
215 result = d->_aboutData->copyrightStatement() + lineFeed;
216 }
217
218 bool knownLicense = false;
219 QString pathToFile; // rel path if known license
220 switch (d->_licenseKey) {
221 case KAboutLicense::File:
222 pathToFile = d->_pathToLicenseTextFile;
223 break;
224 case KAboutLicense::GPL_V2:
225 knownLicense = true;
226 pathToFile = QStringLiteral("GPL_V2");
227 break;
228 case KAboutLicense::LGPL_V2:
229 knownLicense = true;
230 pathToFile = QStringLiteral("LGPL_V2");
231 break;
232 case KAboutLicense::BSDL:
233 knownLicense = true;
234 pathToFile = QStringLiteral("BSD");
235 break;
236 case KAboutLicense::Artistic:
237 knownLicense = true;
238 pathToFile = QStringLiteral("ARTISTIC");
239 break;
240 case KAboutLicense::GPL_V3:
241 knownLicense = true;
242 pathToFile = QStringLiteral("GPL_V3");
243 break;
244 case KAboutLicense::LGPL_V3:
245 knownLicense = true;
246 pathToFile = QStringLiteral("LGPL_V3");
247 break;
248 case KAboutLicense::LGPL_V2_1:
249 knownLicense = true;
250 pathToFile = QStringLiteral("LGPL_V21");
251 break;
252 case KAboutLicense::MIT:
253 knownLicense = true;
254 pathToFile = QStringLiteral("MIT");
255 break;
256 case KAboutLicense::Custom:
257 if (!d->_licenseText.isEmpty()) {
258 result = d->_licenseText;
259 break;
260 }
261 Q_FALLTHROUGH();
262 // fall through
263 default:
264 result += QCoreApplication::translate(context: "KAboutLicense",
265 key: "No licensing terms for this program have been specified.\n"
266 "Please check the documentation or the source for any\n"
267 "licensing terms.\n");
268 }
269
270 if (knownLicense) {
271#ifndef Q_OS_ANDROID
272 pathToFile = QStandardPaths::locate(type: QStandardPaths::GenericDataLocation, QStringLiteral("kf6/licenses/") + pathToFile);
273#else
274 pathToFile = QStringLiteral("assets:/share/kf6/licenses/") + pathToFile;
275#endif
276 result += QCoreApplication::translate(context: "KAboutLicense", key: "This program is distributed under the terms of the %1.").arg(a: name(formatName: KAboutLicense::ShortName));
277 if (!pathToFile.isEmpty()) {
278 result += lineFeed;
279 }
280 }
281
282 if (!pathToFile.isEmpty()) {
283 QFile file(pathToFile);
284 if (file.open(flags: QIODevice::ReadOnly)) {
285 QTextStream str(&file);
286 result += str.readAll();
287 }
288 }
289
290 return result;
291}
292
293QString KAboutLicense::spdx() const
294{
295 // SPDX licenses are comprised of an identifier (e.g. GPL-2.0), an optional + to denote 'or
296 // later versions' and optional ' WITH $exception' to denote standardized exceptions from the
297 // core license. As we do not offer exceptions we effectively only return GPL-2.0 or GPL-2.0+,
298 // this may change in the future. To that end the documentation makes no assertions about the
299 // actual content of the SPDX license expression we return.
300 // Expressions can in theory also contain AND, OR and () to build constructs involving more than
301 // one license. As this is outside the scope of a single license object we'll ignore this here
302 // for now.
303 // The expectation is that the return value is only run through spec-compliant parsers, so this
304 // can potentially be changed.
305
306 auto id = d->spdxID();
307 if (id.isNull()) { // Guard against potential future changes which would allow 'Foo+' as input.
308 return id;
309 }
310 return d->_versionRestriction == OrLaterVersions ? id.append(c: QLatin1Char('+')) : id;
311}
312
313QString KAboutLicense::name(KAboutLicense::NameFormat formatName) const
314{
315 QString licenseShort;
316 QString licenseFull;
317
318 switch (d->_licenseKey) {
319 case KAboutLicense::GPL_V2:
320 licenseShort = QCoreApplication::translate(context: "KAboutLicense", key: "GPL v2", disambiguation: "@item license (short name)");
321 licenseFull = QCoreApplication::translate(context: "KAboutLicense", key: "GNU General Public License Version 2", disambiguation: "@item license");
322 break;
323 case KAboutLicense::LGPL_V2:
324 licenseShort = QCoreApplication::translate(context: "KAboutLicense", key: "LGPL v2", disambiguation: "@item license (short name)");
325 licenseFull = QCoreApplication::translate(context: "KAboutLicense", key: "GNU Lesser General Public License Version 2", disambiguation: "@item license");
326 break;
327 case KAboutLicense::BSDL:
328 licenseShort = QCoreApplication::translate(context: "KAboutLicense", key: "BSD License", disambiguation: "@item license (short name)");
329 licenseFull = QCoreApplication::translate(context: "KAboutLicense", key: "BSD License", disambiguation: "@item license");
330 break;
331 case KAboutLicense::Artistic:
332 licenseShort = QCoreApplication::translate(context: "KAboutLicense", key: "Artistic License", disambiguation: "@item license (short name)");
333 licenseFull = QCoreApplication::translate(context: "KAboutLicense", key: "Artistic License", disambiguation: "@item license");
334 break;
335 case KAboutLicense::GPL_V3:
336 licenseShort = QCoreApplication::translate(context: "KAboutLicense", key: "GPL v3", disambiguation: "@item license (short name)");
337 licenseFull = QCoreApplication::translate(context: "KAboutLicense", key: "GNU General Public License Version 3", disambiguation: "@item license");
338 break;
339 case KAboutLicense::LGPL_V3:
340 licenseShort = QCoreApplication::translate(context: "KAboutLicense", key: "LGPL v3", disambiguation: "@item license (short name)");
341 licenseFull = QCoreApplication::translate(context: "KAboutLicense", key: "GNU Lesser General Public License Version 3", disambiguation: "@item license");
342 break;
343 case KAboutLicense::LGPL_V2_1:
344 licenseShort = QCoreApplication::translate(context: "KAboutLicense", key: "LGPL v2.1", disambiguation: "@item license (short name)");
345 licenseFull = QCoreApplication::translate(context: "KAboutLicense", key: "GNU Lesser General Public License Version 2.1", disambiguation: "@item license");
346 break;
347 case KAboutLicense::MIT:
348 licenseShort = QCoreApplication::translate(context: "KAboutLicense", key: "MIT License", disambiguation: "@item license (short name)");
349 licenseFull = QCoreApplication::translate(context: "KAboutLicense", key: "MIT License", disambiguation: "@item license");
350 break;
351 case KAboutLicense::Custom:
352 case KAboutLicense::File:
353 licenseShort = licenseFull = QCoreApplication::translate(context: "KAboutLicense", key: "Custom", disambiguation: "@item license");
354 break;
355 default:
356 licenseShort = licenseFull = QCoreApplication::translate(context: "KAboutLicense", key: "Not specified", disambiguation: "@item license");
357 }
358
359 const QString result = (formatName == KAboutLicense::ShortName) ? licenseShort : (formatName == KAboutLicense::FullName) ? licenseFull : QString();
360
361 return result;
362}
363
364KAboutLicense &KAboutLicense::operator=(const KAboutLicense &other)
365{
366 d = other.d;
367 return *this;
368}
369
370KAboutLicense::LicenseKey KAboutLicense::key() const
371{
372 return d->_licenseKey;
373}
374
375KAboutLicense KAboutLicense::byKeyword(const QString &rawKeyword)
376{
377 // Setup keyword->enum dictionary on first call.
378 // Use normalized keywords, by the algorithm below.
379 static const QHash<QByteArray, KAboutLicense::LicenseKey> licenseDict{
380 {"gpl", KAboutLicense::GPL}, {"gplv2", KAboutLicense::GPL_V2},
381 {"gplv2+", KAboutLicense::GPL_V2}, {"gpl20", KAboutLicense::GPL_V2},
382 {"gpl20+", KAboutLicense::GPL_V2}, {"lgpl", KAboutLicense::LGPL},
383 {"lgplv2", KAboutLicense::LGPL_V2}, {"lgplv2+", KAboutLicense::LGPL_V2},
384 {"lgpl20", KAboutLicense::LGPL_V2}, {"lgpl20+", KAboutLicense::LGPL_V2},
385 {"bsd", KAboutLicense::BSDL}, {"bsd2clause", KAboutLicense::BSDL},
386 {"artistic", KAboutLicense::Artistic}, {"artistic10", KAboutLicense::Artistic},
387 {"gplv3", KAboutLicense::GPL_V3}, {"gplv3+", KAboutLicense::GPL_V3},
388 {"gpl30", KAboutLicense::GPL_V3}, {"gpl30+", KAboutLicense::GPL_V3},
389 {"lgplv3", KAboutLicense::LGPL_V3}, {"lgplv3+", KAboutLicense::LGPL_V3},
390 {"lgpl30", KAboutLicense::LGPL_V3}, {"lgpl30+", KAboutLicense::LGPL_V3},
391 {"lgplv21", KAboutLicense::LGPL_V2_1}, {"lgplv21+", KAboutLicense::LGPL_V2_1},
392 {"lgpl21", KAboutLicense::LGPL_V2_1}, {"lgpl21+", KAboutLicense::LGPL_V2_1},
393 {"mit", KAboutLicense::MIT},
394 };
395
396 // Normalize keyword.
397 QString keyword = rawKeyword;
398 keyword = keyword.toLower();
399 keyword.replace(before: QLatin1StringView("-or-later"), after: QLatin1StringView("+"));
400 keyword.remove(c: QLatin1Char(' '));
401 keyword.remove(c: QLatin1Char('.'));
402 keyword.remove(c: QLatin1Char('-'));
403
404 LicenseKey license = licenseDict.value(key: keyword.toLatin1(), defaultValue: KAboutLicense::Custom);
405 auto restriction = keyword.endsWith(c: QLatin1Char('+')) ? OrLaterVersions : OnlyThisVersion;
406 return KAboutLicense(license, restriction, nullptr);
407}
408
409class KAboutComponentPrivate : public QSharedData
410{
411public:
412 QString _name;
413 QString _description;
414 QString _version;
415 QString _webAddress;
416 KAboutLicense _license;
417};
418
419KAboutComponent::KAboutComponent(const QString &_name,
420 const QString &_description,
421 const QString &_version,
422 const QString &_webAddress,
423 enum KAboutLicense::LicenseKey licenseType)
424 : d(new KAboutComponentPrivate)
425{
426 d->_name = _name;
427 d->_description = _description;
428 d->_version = _version;
429 d->_webAddress = _webAddress;
430 d->_license = KAboutLicense(licenseType, nullptr);
431}
432
433KAboutComponent::KAboutComponent(const QString &_name,
434 const QString &_description,
435 const QString &_version,
436 const QString &_webAddress,
437 const QString &pathToLicenseFile)
438 : d(new KAboutComponentPrivate)
439{
440 d->_name = _name;
441 d->_description = _description;
442 d->_version = _version;
443 d->_webAddress = _webAddress;
444 d->_license = KAboutLicense();
445 d->_license.setLicenseFromPath(pathToLicenseFile);
446}
447
448KAboutComponent::KAboutComponent(const KAboutComponent &other) = default;
449
450KAboutComponent::~KAboutComponent() = default;
451
452QString KAboutComponent::name() const
453{
454 return d->_name;
455}
456
457QString KAboutComponent::description() const
458{
459 return d->_description;
460}
461
462QString KAboutComponent::version() const
463{
464 return d->_version;
465}
466
467QString KAboutComponent::webAddress() const
468{
469 return d->_webAddress;
470}
471
472KAboutLicense KAboutComponent::license() const
473{
474 return d->_license;
475}
476
477KAboutComponent &KAboutComponent::operator=(const KAboutComponent &other) = default;
478
479class KAboutDataPrivate
480{
481public:
482 KAboutDataPrivate()
483 : customAuthorTextEnabled(false)
484 {
485 }
486 QString _componentName;
487 QString _displayName;
488 QString _shortDescription;
489 QString _copyrightStatement;
490 QString _otherText;
491 QString _homepageAddress;
492 QList<KAboutPerson> _authorList;
493 QList<KAboutPerson> _creditList;
494 QList<KAboutPerson> _translatorList;
495 QList<KAboutComponent> _componentList;
496 QList<KAboutLicense> _licenseList;
497 QVariant ;
498 QString customAuthorPlainText, customAuthorRichText;
499 bool customAuthorTextEnabled;
500
501 QString organizationDomain;
502 QString desktopFileName;
503
504 // Everything dr.konqi needs, we store as utf-8, so we
505 // can just give it a pointer, w/o any allocations.
506 QByteArray _internalProgramName;
507 QByteArray _version;
508 QByteArray _bugAddress;
509 QByteArray productName;
510
511 static QList<KAboutPerson> parseTranslators(const QString &translatorName, const QString &translatorEmail);
512};
513
514KAboutData::KAboutData(const QString &_componentName,
515 const QString &_displayName,
516 const QString &_version,
517 const QString &_shortDescription,
518 enum KAboutLicense::LicenseKey licenseType,
519 const QString &_copyrightStatement,
520 const QString &text,
521 const QString &homePageAddress,
522 const QString &bugAddress)
523 : d(new KAboutDataPrivate)
524{
525 d->_componentName = _componentName;
526 int p = d->_componentName.indexOf(c: QLatin1Char('/'));
527 if (p >= 0) {
528 d->_componentName = d->_componentName.mid(position: p + 1);
529 }
530
531 d->_displayName = _displayName;
532 if (!d->_displayName.isEmpty()) { // KComponentData("klauncher") gives empty program name
533 d->_internalProgramName = _displayName.toUtf8();
534 }
535 d->_version = _version.toUtf8();
536 d->_shortDescription = _shortDescription;
537 d->_licenseList.append(t: KAboutLicense(licenseType, this));
538 d->_copyrightStatement = _copyrightStatement;
539 d->_otherText = text;
540 d->_homepageAddress = homePageAddress;
541 d->_bugAddress = bugAddress.toUtf8();
542
543 QUrl homePageUrl(homePageAddress);
544 if (!homePageUrl.isValid() || homePageUrl.scheme().isEmpty()) {
545 // Default domain if nothing else is better
546 homePageUrl.setUrl(QStringLiteral("https://kde.org/"));
547 }
548
549 const QChar dotChar(QLatin1Char('.'));
550 QStringList hostComponents = homePageUrl.host().split(sep: dotChar);
551
552 // Remove leading component unless 2 (or less) components are present
553 if (hostComponents.size() > 2) {
554 hostComponents.removeFirst();
555 }
556
557 d->organizationDomain = hostComponents.join(sep: dotChar);
558
559 // KF6: do not set a default desktopFileName value here, but remove this code and leave it empty
560 // see KAboutData::desktopFileName() for details
561
562 // desktop file name is reverse domain name
563 std::reverse(first: hostComponents.begin(), last: hostComponents.end());
564 hostComponents.append(t: _componentName);
565
566 d->desktopFileName = hostComponents.join(sep: dotChar);
567}
568
569KAboutData::KAboutData(const QString &_componentName, const QString &_displayName, const QString &_version)
570 : d(new KAboutDataPrivate)
571{
572 d->_componentName = _componentName;
573 int p = d->_componentName.indexOf(c: QLatin1Char('/'));
574 if (p >= 0) {
575 d->_componentName = d->_componentName.mid(position: p + 1);
576 }
577
578 d->_displayName = _displayName;
579 if (!d->_displayName.isEmpty()) { // KComponentData("klauncher") gives empty program name
580 d->_internalProgramName = _displayName.toUtf8();
581 }
582 d->_version = _version.toUtf8();
583
584 // match behaviour of other constructors
585 d->_licenseList.append(t: KAboutLicense(KAboutLicense::Unknown, this));
586 d->_bugAddress = "submit@bugs.kde.org";
587 d->organizationDomain = QStringLiteral("kde.org");
588 // KF6: do not set a default desktopFileName value here, but remove this code and leave it empty
589 // see KAboutData::desktopFileName() for details
590 d->desktopFileName = QLatin1String("org.kde.") + d->_componentName;
591}
592
593KAboutData::~KAboutData() = default;
594
595KAboutData::KAboutData(const KAboutData &other)
596 : d(new KAboutDataPrivate)
597{
598 *d = *other.d;
599 for (KAboutLicense &al : d->_licenseList) {
600 al.d.detach();
601 al.d->_aboutData = this;
602 }
603}
604
605KAboutData &KAboutData::operator=(const KAboutData &other)
606{
607 if (this != &other) {
608 *d = *other.d;
609 for (KAboutLicense &al : d->_licenseList) {
610 al.d.detach();
611 al.d->_aboutData = this;
612 }
613 }
614 return *this;
615}
616
617KAboutData &KAboutData::addAuthor(const QString &name, const QString &task, const QString &emailAddress, const QString &webAddress, const QUrl &avatarUrl)
618{
619 d->_authorList.append(t: KAboutPerson(name, task, emailAddress, webAddress, avatarUrl));
620 return *this;
621}
622
623KAboutData &KAboutData::addCredit(const QString &name, const QString &task, const QString &emailAddress, const QString &webAddress, const QUrl &avatarUrl)
624{
625 d->_creditList.append(t: KAboutPerson(name, task, emailAddress, webAddress, avatarUrl));
626 return *this;
627}
628
629KAboutData &KAboutData::setTranslator(const QString &name, const QString &emailAddress)
630{
631 d->_translatorList = KAboutDataPrivate::parseTranslators(translatorName: name, translatorEmail: emailAddress);
632 return *this;
633}
634
635KAboutData &KAboutData::addComponent(const QString &name,
636 const QString &description,
637 const QString &version,
638 const QString &webAddress,
639 KAboutLicense::LicenseKey licenseKey)
640{
641 d->_componentList.append(t: KAboutComponent(name, description, version, webAddress, licenseKey));
642 return *this;
643}
644
645KAboutData &
646KAboutData::addComponent(const QString &name, const QString &description, const QString &version, const QString &webAddress, const QString &pathToLicenseFile)
647{
648 d->_componentList.append(t: KAboutComponent(name, description, version, webAddress, pathToLicenseFile));
649 return *this;
650}
651
652KAboutData &KAboutData::setLicenseText(const QString &licenseText)
653{
654 d->_licenseList[0] = KAboutLicense(this);
655 d->_licenseList[0].setLicenseFromText(licenseText);
656 return *this;
657}
658
659KAboutData &KAboutData::addLicenseText(const QString &licenseText)
660{
661 // if the default license is unknown, overwrite instead of append
662 KAboutLicense &firstLicense = d->_licenseList[0];
663 KAboutLicense newLicense(this);
664 newLicense.setLicenseFromText(licenseText);
665 if (d->_licenseList.count() == 1 && firstLicense.d->_licenseKey == KAboutLicense::Unknown) {
666 firstLicense = newLicense;
667 } else {
668 d->_licenseList.append(t: newLicense);
669 }
670
671 return *this;
672}
673
674KAboutData &KAboutData::setLicenseTextFile(const QString &pathToFile)
675{
676 d->_licenseList[0] = KAboutLicense(this);
677 d->_licenseList[0].setLicenseFromPath(pathToFile);
678 return *this;
679}
680
681KAboutData &KAboutData::addLicenseTextFile(const QString &pathToFile)
682{
683 // if the default license is unknown, overwrite instead of append
684 KAboutLicense &firstLicense = d->_licenseList[0];
685 KAboutLicense newLicense(this);
686 newLicense.setLicenseFromPath(pathToFile);
687 if (d->_licenseList.count() == 1 && firstLicense.d->_licenseKey == KAboutLicense::Unknown) {
688 firstLicense = newLicense;
689 } else {
690 d->_licenseList.append(t: newLicense);
691 }
692 return *this;
693}
694
695KAboutData &KAboutData::setComponentName(const QString &componentName)
696{
697 d->_componentName = componentName;
698 return *this;
699}
700
701KAboutData &KAboutData::setDisplayName(const QString &_displayName)
702{
703 d->_displayName = _displayName;
704 d->_internalProgramName = _displayName.toUtf8();
705 return *this;
706}
707
708KAboutData &KAboutData::setVersion(const QByteArray &_version)
709{
710 d->_version = _version;
711 return *this;
712}
713
714KAboutData &KAboutData::setShortDescription(const QString &_shortDescription)
715{
716 d->_shortDescription = _shortDescription;
717 return *this;
718}
719
720KAboutData &KAboutData::setLicense(KAboutLicense::LicenseKey licenseKey)
721{
722 return setLicense(licenseKey, versionRestriction: KAboutLicense::OnlyThisVersion);
723}
724
725KAboutData &KAboutData::setLicense(KAboutLicense::LicenseKey licenseKey, KAboutLicense::VersionRestriction versionRestriction)
726{
727 d->_licenseList[0] = KAboutLicense(licenseKey, versionRestriction, this);
728 return *this;
729}
730
731KAboutData &KAboutData::addLicense(KAboutLicense::LicenseKey licenseKey)
732{
733 return addLicense(licenseKey, versionRestriction: KAboutLicense::OnlyThisVersion);
734}
735
736KAboutData &KAboutData::addLicense(KAboutLicense::LicenseKey licenseKey, KAboutLicense::VersionRestriction versionRestriction)
737{
738 // if the default license is unknown, overwrite instead of append
739 KAboutLicense &firstLicense = d->_licenseList[0];
740 if (d->_licenseList.count() == 1 && firstLicense.d->_licenseKey == KAboutLicense::Unknown) {
741 firstLicense = KAboutLicense(licenseKey, versionRestriction, this);
742 } else {
743 d->_licenseList.append(t: KAboutLicense(licenseKey, versionRestriction, this));
744 }
745 return *this;
746}
747
748KAboutData &KAboutData::setCopyrightStatement(const QString &_copyrightStatement)
749{
750 d->_copyrightStatement = _copyrightStatement;
751 return *this;
752}
753
754KAboutData &KAboutData::setOtherText(const QString &_otherText)
755{
756 d->_otherText = _otherText;
757 return *this;
758}
759
760KAboutData &KAboutData::setHomepage(const QString &homepage)
761{
762 d->_homepageAddress = homepage;
763 return *this;
764}
765
766KAboutData &KAboutData::setBugAddress(const QByteArray &_bugAddress)
767{
768 d->_bugAddress = _bugAddress;
769 return *this;
770}
771
772KAboutData &KAboutData::setOrganizationDomain(const QByteArray &domain)
773{
774 d->organizationDomain = QString::fromLatin1(ba: domain.data());
775 return *this;
776}
777
778KAboutData &KAboutData::setProductName(const QByteArray &_productName)
779{
780 d->productName = _productName;
781 return *this;
782}
783
784QString KAboutData::componentName() const
785{
786 return d->_componentName;
787}
788
789QString KAboutData::productName() const
790{
791 if (!d->productName.isEmpty()) {
792 return QString::fromUtf8(ba: d->productName);
793 }
794 return componentName();
795}
796
797const char *KAboutData::internalProductName() const
798{
799 return d->productName.isEmpty() ? nullptr : d->productName.constData();
800}
801
802QString KAboutData::displayName() const
803{
804 return d->_displayName;
805}
806
807/// @internal
808/// Return the program name. It is always pre-allocated.
809/// Needed for KCrash in particular.
810const char *KAboutData::internalProgramName() const
811{
812 return d->_internalProgramName.constData();
813}
814
815QVariant KAboutData::programLogo() const
816{
817 return d->programLogo;
818}
819
820KAboutData &KAboutData::setProgramLogo(const QVariant &image)
821{
822 d->programLogo = image;
823 return *this;
824}
825
826QString KAboutData::version() const
827{
828 return QString::fromUtf8(utf8: d->_version.data());
829}
830
831/// @internal
832/// Return the untranslated and uninterpreted (to UTF8) string
833/// for the version information. Used in particular for KCrash.
834const char *KAboutData::internalVersion() const
835{
836 return d->_version.constData();
837}
838
839QString KAboutData::shortDescription() const
840{
841 return d->_shortDescription;
842}
843
844QString KAboutData::homepage() const
845{
846 return d->_homepageAddress;
847}
848
849QString KAboutData::bugAddress() const
850{
851 return QString::fromUtf8(utf8: d->_bugAddress.constData());
852}
853
854QString KAboutData::organizationDomain() const
855{
856 return d->organizationDomain;
857}
858
859/// @internal
860/// Return the untranslated and uninterpreted (to UTF8) string
861/// for the bug mail address. Used in particular for KCrash.
862const char *KAboutData::internalBugAddress() const
863{
864 if (d->_bugAddress.isEmpty()) {
865 return nullptr;
866 }
867 return d->_bugAddress.constData();
868}
869
870QList<KAboutPerson> KAboutData::authors() const
871{
872 return d->_authorList;
873}
874
875QList<KAboutPerson> KAboutData::credits() const
876{
877 return d->_creditList;
878}
879
880QList<KAboutPerson> KAboutDataPrivate::parseTranslators(const QString &translatorName, const QString &translatorEmail)
881{
882 if (translatorName.isEmpty() || translatorName == QLatin1String("Your names")) {
883 return {};
884 }
885
886 // use list of string views to delay creating new QString instances after the white-space trimming
887 const QList<QStringView> nameList = QStringView(translatorName).split(sep: QLatin1Char(','));
888
889 QList<QStringView> emailList;
890 if (!translatorEmail.isEmpty() && translatorEmail != QLatin1String("Your emails")) {
891 emailList = QStringView(translatorEmail).split(sep: QLatin1Char(','), behavior: Qt::KeepEmptyParts);
892 }
893
894 QList<KAboutPerson> personList;
895 personList.reserve(asize: nameList.size());
896
897 auto eit = emailList.constBegin();
898
899 for (const QStringView &name : nameList) {
900 QStringView email;
901 if (eit != emailList.constEnd()) {
902 email = *eit;
903 ++eit;
904 }
905
906 personList.append(t: KAboutPerson(name.trimmed().toString(), email.trimmed().toString(), true));
907 }
908
909 return personList;
910}
911
912QList<KAboutPerson> KAboutData::translators() const
913{
914 return d->_translatorList;
915}
916
917QString KAboutData::aboutTranslationTeam()
918{
919 return QCoreApplication::translate(context: "KAboutData",
920 key: "<p>KDE is translated into many languages thanks to the work "
921 "of the translation teams all over the world.</p>"
922 "<p>For more information on KDE internationalization "
923 "visit <a href=\"https://l10n.kde.org\">https://l10n.kde.org</a></p>",
924 disambiguation: "replace this with information about your translation team");
925}
926
927QString KAboutData::otherText() const
928{
929 return d->_otherText;
930}
931
932QList<KAboutComponent> KAboutData::components() const
933{
934 return d->_componentList;
935}
936
937QList<KAboutLicense> KAboutData::licenses() const
938{
939 return d->_licenseList;
940}
941
942QString KAboutData::copyrightStatement() const
943{
944 return d->_copyrightStatement;
945}
946
947QString KAboutData::customAuthorPlainText() const
948{
949 return d->customAuthorPlainText;
950}
951
952QString KAboutData::customAuthorRichText() const
953{
954 return d->customAuthorRichText;
955}
956
957bool KAboutData::customAuthorTextEnabled() const
958{
959 return d->customAuthorTextEnabled;
960}
961
962KAboutData &KAboutData::setCustomAuthorText(const QString &plainText, const QString &richText)
963{
964 d->customAuthorPlainText = plainText;
965 d->customAuthorRichText = richText;
966
967 d->customAuthorTextEnabled = true;
968
969 return *this;
970}
971
972KAboutData &KAboutData::unsetCustomAuthorText()
973{
974 d->customAuthorPlainText = QString();
975 d->customAuthorRichText = QString();
976
977 d->customAuthorTextEnabled = false;
978
979 return *this;
980}
981
982KAboutData &KAboutData::setDesktopFileName(const QString &desktopFileName)
983{
984 d->desktopFileName = desktopFileName;
985
986 return *this;
987}
988
989QString KAboutData::desktopFileName() const
990{
991 return d->desktopFileName;
992 // KF6: switch to this code and adapt API dox
993#if 0
994 // if desktopFileName has been explicitly set, use that value
995 if (!d->desktopFileName.isEmpty()) {
996 return d->desktopFileName;
997 }
998
999 // return a string calculated on-the-fly from the current org domain & component name
1000 const QChar dotChar(QLatin1Char('.'));
1001 QStringList hostComponents = d->organizationDomain.split(dotChar);
1002
1003 // desktop file name is reverse domain name
1004 std::reverse(hostComponents.begin(), hostComponents.end());
1005 hostComponents.append(componentName());
1006
1007 return hostComponents.join(dotChar);
1008#endif
1009}
1010
1011class KAboutDataRegistry
1012{
1013public:
1014 KAboutDataRegistry()
1015 : m_appData(nullptr)
1016 {
1017 }
1018 ~KAboutDataRegistry()
1019 {
1020 delete m_appData;
1021 }
1022 KAboutDataRegistry(const KAboutDataRegistry &) = delete;
1023 KAboutDataRegistry &operator=(const KAboutDataRegistry &) = delete;
1024
1025 KAboutData *m_appData;
1026};
1027
1028Q_GLOBAL_STATIC(KAboutDataRegistry, s_registry)
1029
1030namespace
1031{
1032void warnIfOutOfSync(const char *aboutDataString, const QString &aboutDataValue, const char *appDataString, const QString &appDataValue)
1033{
1034 if (aboutDataValue != appDataValue) {
1035 qCWarning(KABOUTDATA) << appDataString << appDataValue << "is out-of-sync with" << aboutDataString << aboutDataValue;
1036 }
1037}
1038
1039}
1040
1041KAboutData KAboutData::applicationData()
1042{
1043 QCoreApplication *app = QCoreApplication::instance();
1044
1045 KAboutData *aboutData = s_registry->m_appData;
1046
1047 // not yet existing
1048 if (!aboutData) {
1049 // init from current Q*Application data
1050 aboutData = new KAboutData(QCoreApplication::applicationName(), QString(), QString());
1051 // Unset the default (KDE) bug address, this is likely a third-party app. https://bugs.kde.org/show_bug.cgi?id=473517
1052 aboutData->setBugAddress(QByteArray());
1053 // For applicationDisplayName & desktopFileName, which are only properties of QGuiApplication,
1054 // we have to try to get them via the property system, as the static getter methods are
1055 // part of QtGui only. Disadvantage: requires an app instance.
1056 // Either get all or none of the properties & warn about it
1057 if (app) {
1058 aboutData->setOrganizationDomain(QCoreApplication::organizationDomain().toUtf8());
1059 aboutData->setVersion(QCoreApplication::applicationVersion().toUtf8());
1060 aboutData->setDisplayName(app->property(name: "applicationDisplayName").toString());
1061 aboutData->setDesktopFileName(app->property(name: "desktopFileName").toString());
1062 } else {
1063 qCWarning(KABOUTDATA) << "Could not initialize the properties of KAboutData::applicationData by the equivalent properties from Q*Application: no "
1064 "app instance (yet) existing.";
1065 }
1066
1067 s_registry->m_appData = aboutData;
1068 } else {
1069 // check if in-sync with Q*Application metadata, as their setters could have been called
1070 // after the last KAboutData::setApplicationData, with different values
1071 warnIfOutOfSync(aboutDataString: "KAboutData::applicationData().componentName",
1072 aboutDataValue: aboutData->componentName(),
1073 appDataString: "QCoreApplication::applicationName",
1074 appDataValue: QCoreApplication::applicationName());
1075 warnIfOutOfSync(aboutDataString: "KAboutData::applicationData().version",
1076 aboutDataValue: aboutData->version(),
1077 appDataString: "QCoreApplication::applicationVersion",
1078 appDataValue: QCoreApplication::applicationVersion());
1079 warnIfOutOfSync(aboutDataString: "KAboutData::applicationData().organizationDomain",
1080 aboutDataValue: aboutData->organizationDomain(),
1081 appDataString: "QCoreApplication::organizationDomain",
1082 appDataValue: QCoreApplication::organizationDomain());
1083 if (app) {
1084 warnIfOutOfSync(aboutDataString: "KAboutData::applicationData().displayName",
1085 aboutDataValue: aboutData->displayName(),
1086 appDataString: "QGuiApplication::applicationDisplayName",
1087 appDataValue: app->property(name: "applicationDisplayName").toString());
1088 warnIfOutOfSync(aboutDataString: "KAboutData::applicationData().desktopFileName",
1089 aboutDataValue: aboutData->desktopFileName(),
1090 appDataString: "QGuiApplication::desktopFileName",
1091 appDataValue: app->property(name: "desktopFileName").toString());
1092 }
1093 }
1094
1095 return *aboutData;
1096}
1097
1098void KAboutData::setApplicationData(const KAboutData &aboutData)
1099{
1100 if (s_registry->m_appData) {
1101 *s_registry->m_appData = aboutData;
1102 } else {
1103 s_registry->m_appData = new KAboutData(aboutData);
1104 }
1105
1106 // For applicationDisplayName & desktopFileName, which are only properties of QGuiApplication,
1107 // we have to try to set them via the property system, as the static getter methods are
1108 // part of QtGui only. Disadvantage: requires an app instance.
1109 // So set either all or none of the properties & warn about it
1110 QCoreApplication *app = QCoreApplication::instance();
1111 if (app) {
1112 app->setApplicationVersion(aboutData.version());
1113 app->setApplicationName(aboutData.componentName());
1114 app->setOrganizationDomain(aboutData.organizationDomain());
1115 app->setProperty(name: "applicationDisplayName", value: aboutData.displayName());
1116 app->setProperty(name: "desktopFileName", value: aboutData.desktopFileName());
1117 } else {
1118 qCWarning(KABOUTDATA) << "Could not initialize the equivalent properties of Q*Application: no instance (yet) existing.";
1119 }
1120
1121 // KF6: Rethink the current relation between KAboutData::applicationData and the Q*Application metadata
1122 // Always overwriting the Q*Application metadata here, but not updating back the KAboutData
1123 // in applicationData() is unbalanced and can result in out-of-sync data if the Q*Application
1124 // setters have been called meanwhile
1125 // Options are to remove the overlapping properties of KAboutData for cleancode, or making the
1126 // overlapping properties official shadow properties of their Q*Application countparts, though
1127 // that increases behavioural complexity a little.
1128}
1129
1130// only for KCrash (no memory allocation allowed)
1131const KAboutData *KAboutData::applicationDataPointer()
1132{
1133 if (s_registry.exists()) {
1134 return s_registry->m_appData;
1135 }
1136 return nullptr;
1137}
1138
1139bool KAboutData::setupCommandLine(QCommandLineParser *parser)
1140{
1141 if (!d->_shortDescription.isEmpty()) {
1142 parser->setApplicationDescription(d->_shortDescription);
1143 }
1144
1145 parser->addHelpOption();
1146
1147 QCoreApplication *app = QCoreApplication::instance();
1148 if (app && !app->applicationVersion().isEmpty()) {
1149 parser->addVersionOption();
1150 }
1151
1152 return parser->addOption(commandLineOption: QCommandLineOption(QStringLiteral("author"), QCoreApplication::translate(context: "KAboutData CLI", key: "Show author information.")))
1153 && parser->addOption(commandLineOption: QCommandLineOption(QStringLiteral("license"), QCoreApplication::translate(context: "KAboutData CLI", key: "Show license information.")))
1154 && parser->addOption(commandLineOption: QCommandLineOption(QStringLiteral("desktopfile"),
1155 QCoreApplication::translate(context: "KAboutData CLI", key: "The base file name of the desktop entry for this application."),
1156 QCoreApplication::translate(context: "KAboutData CLI", key: "file name")));
1157}
1158
1159void KAboutData::processCommandLine(QCommandLineParser *parser)
1160{
1161 bool foundArgument = false;
1162 if (parser->isSet(QStringLiteral("author"))) {
1163 foundArgument = true;
1164 if (d->_authorList.isEmpty()) {
1165 printf(format: "%s\n",
1166 qPrintable(QCoreApplication::translate("KAboutData CLI", "This application was written by somebody who wants to remain anonymous.")));
1167 } else {
1168 printf(format: "%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "%1 was written by:").arg(qAppName())));
1169 for (const KAboutPerson &person : std::as_const(t&: d->_authorList)) {
1170 QString authorData = QLatin1String(" ") + person.name();
1171 if (!person.emailAddress().isEmpty()) {
1172 authorData.append(s: QLatin1String(" <") + person.emailAddress() + QLatin1Char('>'));
1173 }
1174 printf(format: "%s\n", qPrintable(authorData));
1175 }
1176 }
1177 if (!customAuthorTextEnabled()) {
1178 if (bugAddress() == QLatin1String("submit@bugs.kde.org")) {
1179 printf(format: "%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "Please use https://bugs.kde.org to report bugs.")));
1180 } else if (!bugAddress().isEmpty()) {
1181 printf(format: "%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "Please report bugs to %1.").arg(bugAddress())));
1182 }
1183 } else {
1184 printf(format: "%s\n", qPrintable(customAuthorPlainText()));
1185 }
1186 } else if (parser->isSet(QStringLiteral("license"))) {
1187 foundArgument = true;
1188 for (const KAboutLicense &license : std::as_const(t&: d->_licenseList)) {
1189 printf(format: "%s\n", qPrintable(license.text()));
1190 }
1191 }
1192
1193 const QString desktopFileName = parser->value(QStringLiteral("desktopfile"));
1194 if (!desktopFileName.isEmpty()) {
1195 d->desktopFileName = desktopFileName;
1196 }
1197
1198 if (foundArgument) {
1199 ::exit(EXIT_SUCCESS);
1200 }
1201}
1202
1203#include "moc_kaboutdata.cpp"
1204

source code of kcoreaddons/src/lib/kaboutdata.cpp