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

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