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

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