1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2007 Urs Wolfer <uwolfer at kde.org>
4 SPDX-FileCopyrightText: 2008, 2019 Friedrich W. H. Kossebau <kossebau@kde.org>
5 SPDX-FileCopyrightText: 2010 Teo Mrnjavac <teo@kde.org>
6
7 Parts of this class have been take from the KAboutApplication class, which was
8 SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
9 SPDX-FileCopyrightText: 2000 Espen Sand <espen@kde.org>
10
11 SPDX-License-Identifier: LGPL-2.0-only
12*/
13
14#include "kabstractaboutdialog_p.h"
15
16#include "klicensedialog_p.h"
17#include <kaboutdata.h>
18#include <kxmlgui_version.h>
19// KF
20#include <KAdjustingScrollArea>
21#include <KLocalizedString>
22#include <KSandbox>
23#include <KSeparator>
24#include <KTitleWidget>
25// Qt
26#include <QApplication>
27#include <QCheckBox>
28#include <QClipboard>
29#include <QDesktopServices>
30#include <QDialogButtonBox>
31#include <QIcon>
32#include <QLabel>
33#include <QPushButton>
34#include <QStyle>
35#include <QToolButton>
36#include <QVBoxLayout>
37
38using namespace Qt::StringLiterals;
39
40QWidget *KAbstractAboutDialogPrivate::createTitleWidget(const QIcon &icon, const QString &displayName, const QString &version, QWidget *parent)
41{
42 KTitleWidget *titleWidget = new KTitleWidget(parent);
43
44 titleWidget->setIconSize(QSize(48, 48));
45 titleWidget->setIcon(icon, alignment: KTitleWidget::ImageLeft);
46 titleWidget->setText(
47 text: QLatin1String("<html><font size=\"5\">%1</font><br />%2</html>").arg(args: displayName, i18nc("Version version-number", "Version %1", version)));
48 return titleWidget;
49}
50
51QWidget *KAbstractAboutDialogPrivate::createAboutWidget(const QString &shortDescription,
52 const QString &otherText,
53 const QString &copyrightStatement,
54 const QString &homepage,
55 const QList<KAboutLicense> &licenses,
56 QWidget *parent)
57{
58 auto wrapper = new KAdjustingScrollArea(parent);
59 auto aboutWidget = new QWidget(parent);
60 wrapper->setWidget(aboutWidget);
61 auto aboutLayout = new QVBoxLayout(aboutWidget);
62
63 QString aboutPageText = shortDescription + QLatin1Char('\n');
64
65 if (!otherText.isEmpty()) {
66 aboutPageText += QLatin1Char('\n') + otherText + QLatin1Char('\n');
67 }
68
69 if (!copyrightStatement.isEmpty()) {
70 aboutPageText += QLatin1Char('\n') + copyrightStatement + QLatin1Char('\n');
71 }
72
73 if (!homepage.isEmpty()) {
74 aboutPageText += QLatin1Char('\n') + QStringLiteral("<a href=\"%1\">%1</a>").arg(a: homepage) + QLatin1Char('\n');
75 }
76 aboutPageText = aboutPageText.trimmed();
77
78 QLabel *aboutLabel = new QLabel;
79 aboutLabel->setWordWrap(true);
80 aboutLabel->setOpenExternalLinks(true);
81 aboutLabel->setText(aboutPageText.replace(c: QLatin1Char('\n'), QStringLiteral("<br />")));
82 aboutLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
83
84 aboutLayout->addWidget(aboutLabel);
85
86 const int licenseCount = licenses.count();
87 for (int i = 0; i < licenseCount; ++i) {
88 const KAboutLicense &license = licenses.at(i);
89
90 QLabel *showLicenseLabel = new QLabel;
91 showLicenseLabel->setText(QStringLiteral("<a href=\"%1\">%2</a>").arg(args: QString::number(i), i18n("License: %1", license.name(KAboutLicense::FullName))));
92 showLicenseLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
93 QObject::connect(sender: showLicenseLabel, signal: &QLabel::linkActivated, context: parent, slot: [license, parent]() {
94 auto *dialog = new KLicenseDialog(license, parent);
95 dialog->show();
96 });
97
98 aboutLayout->addWidget(showLicenseLabel);
99 }
100
101 aboutLayout->addStretch();
102
103 return wrapper;
104}
105
106QWidget *KAbstractAboutDialogPrivate::createComponentWidget(const QList<KAboutComponent> &components, QWidget *parent)
107{
108 auto wrapper = new KAdjustingScrollArea(parent);
109 auto componentWidget = new QWidget;
110 auto componentLayout = new QVBoxLayout(componentWidget);
111 wrapper->setWidget(componentWidget);
112
113 QList<KAboutComponent> allComponents = components;
114 QString platformName;
115 auto platform = QGuiApplication::platformName();
116 platform.replace(i: 0, len: 1, after: platform[0].toUpper());
117 if (platform == u"Wayland"_s || platform == u"Xcb"_s) {
118 platform = i18nc("@info Platform name", "%1 (%2)", QSysInfo::prettyProductName(), platform);
119 } else {
120 platform = QSysInfo::prettyProductName();
121 }
122
123 allComponents.append(t: KAboutComponent(i18n("KDE Frameworks"),
124 i18nc("@info", "Collection of libraries created by the KDE Community to extend Qt."),
125 QStringLiteral(KXMLGUI_VERSION_STRING),
126 QStringLiteral("https://develop.kde.org/products/frameworks/"),
127 KAboutLicense::LGPL_V2_1));
128 allComponents.append(t: KAboutComponent(i18n("Qt"),
129 i18nc("@info", "Cross-platform application development framework."),
130 i18n("Using %1 and built against %2", QString::fromLocal8Bit(qVersion()), QStringLiteral(QT_VERSION_STR)),
131 QStringLiteral("https://www.qt.io/"),
132 KAboutLicense::LGPL_V3));
133
134 QString packageText;
135 if (KSandbox::isFlatpak()) {
136 packageText = i18nc("Linux packaging format", "Flatpak");
137 }
138 if (KSandbox::isSnap()) {
139 packageText = i18nc("Linux packaging format", "Snap");
140 }
141 if (qEnvironmentVariableIsSet(varName: "APPIMAGE")) {
142 packageText = i18nc("Linux packaging format", "AppImage");
143 }
144 if (!packageText.isEmpty()) {
145 allComponents.append(t: KAboutComponent(packageText, i18nc("@info", "Distribution method.")));
146 }
147
148 allComponents.append(t: KAboutComponent(platform, i18nc("@info", "Underlying platform.")));
149
150 for (qsizetype i = 0, count = allComponents.count(); i < count; i++) {
151 const auto &component = allComponents[i];
152
153 QVBoxLayout *col = nullptr;
154 QHBoxLayout *row = new QHBoxLayout;
155
156 auto name = new QLabel(u"<span style='font-weight: 600'>"_s + component.name() + u"</span>"_s
157 + (!component.version().isEmpty() ? (u" (" + component.version() + u')') : QString{}));
158 if (!component.description().isEmpty()) {
159 col = new QVBoxLayout;
160 col->setSpacing(0);
161 auto description = new QLabel(component.description());
162 auto palette = description->palette();
163 auto foregroundColor = palette.color(cr: QPalette::WindowText);
164 foregroundColor.setAlphaF(0.85);
165 palette.setColor(acr: QPalette::WindowText, acolor: foregroundColor);
166 description->setPalette(palette);
167 col->addWidget(name);
168 col->addWidget(description);
169 row->addLayout(layout: col);
170 } else {
171 row->addWidget(name);
172 }
173
174 if (!component.webAddress().isEmpty()) {
175 const auto url = QUrl(component.webAddress());
176 auto webAction = new QAction(QIcon::fromTheme(name: u"internet-services-symbolic"_s), i18nc("@action:button", "Visit component's homepage"));
177 webAction->setToolTip(i18nc("@info:tooltip", "Visit components's homepage\n%1", component.webAddress()));
178 QObject::connect(sender: webAction, signal: &QAction::triggered, context: webAction, slot: [url]() {
179 QDesktopServices::openUrl(url: QUrl(url));
180 });
181 auto web = new QToolButton;
182 web->setDefaultAction(webAction);
183 web->setToolButtonStyle(Qt::ToolButtonIconOnly);
184 web->setAutoRaise(true);
185 row->addWidget(web);
186 }
187
188 componentLayout->addLayout(layout: row);
189
190 if (i + 1 != count) {
191 auto separator = new KSeparator;
192 separator->setEnabled(false);
193 componentLayout->addWidget(separator);
194 }
195 }
196
197 componentLayout->addSpacing(size: parent->style()->pixelMetric(metric: QStyle::PM_LayoutVerticalSpacing));
198
199 auto copyButton = new QPushButton(QIcon::fromTheme(name: u"edit-copy-symbolic"_s), i18nc("@action:button", "Copy to Clipboard"), parent);
200 copyButton->setSizePolicy(hor: QSizePolicy::Maximum, ver: QSizePolicy::Preferred);
201 QObject::connect(sender: copyButton, signal: &QPushButton::clicked, context: copyButton, slot: [allComponents]() {
202 auto aboutData = KAboutData::applicationData();
203 QString info = aboutData.displayName() + u": "_s + aboutData.version() + u'\n';
204
205 for (const auto &component : allComponents) {
206 info += component.name();
207 if (!component.version().isEmpty()) {
208 info += u": "_s + component.version();
209 }
210 info += u'\n';
211 }
212
213 info += u"Build ABI: "_s + QSysInfo::buildAbi() + u'\n';
214 info += u"Kernel: "_s + QSysInfo::kernelType() + u' ' + QSysInfo::kernelVersion() + u'\n';
215
216 QClipboard *clipboard = QGuiApplication::clipboard();
217 clipboard->setText(info);
218 });
219
220 componentLayout->addWidget(copyButton);
221
222 componentLayout->addStretch();
223
224 return wrapper;
225}
226
227static void createPersonLayout(QVBoxLayout *layout, const QList<KAboutPerson> &persons)
228{
229 for (qsizetype i = 0, count = persons.count(); i < count; i++) {
230 const auto &person = persons[i];
231
232 QVBoxLayout *col = nullptr;
233 QHBoxLayout *row = new QHBoxLayout;
234
235 auto name = new QLabel(person.name());
236 auto font = name->font();
237 font.setWeight(QFont::DemiBold);
238 name->setFont(font);
239
240 if (!person.task().isEmpty()) {
241 col = new QVBoxLayout;
242 col->setSpacing(0);
243 auto task = new QLabel(person.task());
244 auto palette = task->palette();
245 auto foregroundColor = palette.color(cr: QPalette::WindowText);
246 foregroundColor.setAlphaF(0.85);
247 palette.setColor(acr: QPalette::WindowText, acolor: foregroundColor);
248 task->setPalette(palette);
249
250 col->addWidget(name);
251 col->addWidget(task);
252 row->addLayout(layout: col);
253 } else {
254 row->addWidget(name);
255 }
256
257 if (!person.webAddress().isEmpty()) {
258 const auto url = QUrl(person.webAddress());
259 auto webAction = new QAction(QIcon::fromTheme(name: u"internet-services-symbolic"_s), i18nc("@action:button", "Visit author's homepage"));
260 webAction->setToolTip(i18nc("@info:tooltip", "Visit author's homepage\n%1", person.webAddress()));
261 QObject::connect(sender: webAction, signal: &QAction::triggered, context: webAction, slot: [url]() {
262 QDesktopServices::openUrl(url);
263 });
264 auto web = new QToolButton;
265 web->setDefaultAction(webAction);
266 web->setAutoRaise(true);
267 web->setToolButtonStyle(Qt::ToolButtonIconOnly);
268 row->addWidget(web);
269 }
270
271 if (!person.emailAddress().isEmpty()) {
272 const auto url = person.emailAddress();
273 auto webAction =
274 new QAction(QIcon::fromTheme(name: u"mail-send-symbolic"_s), i18nc("@action:button Send an email to a contributor", "Email contributor"));
275 webAction->setToolTip(i18nc("@info:tooltip", "Email contributor: %1", person.emailAddress()));
276 QObject::connect(sender: webAction, signal: &QAction::triggered, context: webAction, slot: [url]() {
277 QDesktopServices::openUrl(url: QUrl(u"mailto:"_s + url));
278 });
279 auto web = new QToolButton;
280 web->setDefaultAction(webAction);
281 web->setToolButtonStyle(Qt::ToolButtonIconOnly);
282 web->setAutoRaise(true);
283 row->addWidget(web);
284 }
285
286 layout->addLayout(layout: row);
287 if (i + 1 != count) {
288 auto separator = new KSeparator;
289 separator->setEnabled(false);
290 layout->addWidget(separator);
291 }
292 }
293
294 layout->addStretch();
295}
296
297QWidget *KAbstractAboutDialogPrivate::createAuthorsWidget(const QList<KAboutPerson> &authors,
298 bool customAuthorTextEnabled,
299 const QString &customAuthorRichText,
300 const QString &bugAddress,
301 QWidget *parent)
302{
303 auto wrapper = new KAdjustingScrollArea;
304 auto authorWidget = new QWidget(parent);
305 wrapper->setWidget(authorWidget);
306 auto authorLayout = new QVBoxLayout(authorWidget);
307
308 if (!customAuthorTextEnabled || !customAuthorRichText.isEmpty()) {
309 QLabel *bugsLabel = new QLabel(authorWidget);
310 bugsLabel->setOpenExternalLinks(true);
311 if (!customAuthorTextEnabled) {
312 if (bugAddress.isEmpty() || bugAddress == QLatin1String("submit@bugs.kde.org")) {
313 bugsLabel->setText(i18nc("Reference to website",
314 "Please use %1 to report bugs.<br/>"
315 "If you have questions or need help, please visit %2.<br/>",
316 QLatin1String("<a href=\"https://bugs.kde.org\">https://bugs.kde.org</a>"),
317 QLatin1String("<a href=\"https://kde.org/support/\">https://kde.org/support/</a>")));
318 } else {
319 QUrl bugUrl(bugAddress);
320 if (bugUrl.scheme().isEmpty()) {
321 bugUrl.setScheme(QStringLiteral("mailto"));
322 }
323 bugsLabel->setText(i18nc("Reference to email address",
324 "Please report bugs to %1.\n",
325 QLatin1String("<a href=\"%1\">%2</a>").arg(bugUrl.toString(), bugAddress)));
326 }
327 } else {
328 bugsLabel->setText(customAuthorRichText);
329 }
330 bugsLabel->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Minimum);
331 authorLayout->addWidget(bugsLabel);
332 }
333
334 createPersonLayout(layout: authorLayout, persons: authors);
335
336 return wrapper;
337}
338
339QWidget *KAbstractAboutDialogPrivate::createCreditWidget(const QList<KAboutPerson> &credits, QWidget *parent)
340{
341 auto wrapper = new KAdjustingScrollArea;
342 auto creditWidget = new QWidget(parent);
343 wrapper->setWidget(creditWidget);
344 auto creditLayout = new QVBoxLayout(creditWidget);
345
346 createPersonLayout(layout: creditLayout, persons: credits);
347
348 return wrapper;
349}
350
351QWidget *KAbstractAboutDialogPrivate::createTranslatorsWidget(const QList<KAboutPerson> &translators, QWidget *parent)
352{
353 auto wrapper = new KAdjustingScrollArea;
354 auto translatorWidget = new QWidget(parent);
355 wrapper->setWidget(translatorWidget);
356 auto translatorLayout = new QVBoxLayout(translatorWidget);
357
358 createPersonLayout(layout: translatorLayout, persons: translators);
359
360 QString aboutTranslationTeam = KAboutData::aboutTranslationTeam();
361 if (!aboutTranslationTeam.isEmpty()) {
362 QLabel *translationTeamLabel = new QLabel(translatorWidget);
363 translationTeamLabel->setContentsMargins(left: 4, top: 2, right: 4, bottom: 4);
364 translationTeamLabel->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Minimum);
365 translationTeamLabel->setWordWrap(true);
366 translationTeamLabel->setText(aboutTranslationTeam);
367 translationTeamLabel->setOpenExternalLinks(true);
368 translatorLayout->addWidget(translationTeamLabel);
369 // TODO: this could be displayed as a view item to save space
370 }
371
372 return wrapper;
373}
374
375void KAbstractAboutDialogPrivate::createForm(QWidget *titleWidget, QWidget *tabWidget, QDialog *dialog)
376{
377 QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog);
378 buttonBox->setStandardButtons(QDialogButtonBox::Close);
379 QObject::connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, context: dialog, slot: &QDialog::accept);
380 QObject::connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, context: dialog, slot: &QDialog::reject);
381
382 // And we jam everything together in a layout...
383 QVBoxLayout *mainLayout = new QVBoxLayout(dialog);
384 mainLayout->addWidget(titleWidget);
385 mainLayout->addWidget(tabWidget);
386 mainLayout->addWidget(buttonBox);
387}
388

source code of kxmlgui/src/kabstractaboutdialog_p.cpp