| 1 | /* |
| 2 | This file is part of the KDE project |
| 3 | SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org> |
| 4 | |
| 5 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 6 | */ |
| 7 | |
| 8 | #include "kbugreport.h" |
| 9 | |
| 10 | #include <QDesktopServices> |
| 11 | #include <QDialogButtonBox> |
| 12 | #include <QHBoxLayout> |
| 13 | #include <QLabel> |
| 14 | #include <QPushButton> |
| 15 | #include <QTimer> |
| 16 | #include <QUrl> |
| 17 | #include <QUrlQuery> |
| 18 | |
| 19 | #include <KAboutData> |
| 20 | #include <KConfig> |
| 21 | #include <KLocalizedString> |
| 22 | #include <KMessageBox> |
| 23 | #include <KMessageDialog> |
| 24 | #include <KTitleWidget> |
| 25 | |
| 26 | #include "config-xmlgui.h" |
| 27 | #include "systeminformation_p.h" |
| 28 | #include <kxmlgui_version.h> |
| 29 | |
| 30 | #include <chrono> |
| 31 | |
| 32 | using namespace std::chrono_literals; |
| 33 | |
| 34 | class KBugReportPrivate |
| 35 | { |
| 36 | public: |
| 37 | KBugReportPrivate(KBugReport *qq) |
| 38 | : q(qq) |
| 39 | { |
| 40 | } |
| 41 | |
| 42 | enum BugDestination { |
| 43 | BugsKdeOrg, |
| 44 | CustomUrl, |
| 45 | }; |
| 46 | |
| 47 | // Update the url to match the current OS, selected app, etc |
| 48 | void updateUrl(); |
| 49 | |
| 50 | KBugReport *const q; |
| 51 | |
| 52 | QLabel *m_version = nullptr; |
| 53 | QString m_strVersion; |
| 54 | |
| 55 | QString lastError; |
| 56 | QString kde_version; |
| 57 | QString appname; |
| 58 | QString os; |
| 59 | QUrl url; |
| 60 | BugDestination bugDestination = KBugReportPrivate::CustomUrl; |
| 61 | }; |
| 62 | |
| 63 | KBugReport::KBugReport(const KAboutData &aboutData, QWidget *_parent) |
| 64 | : QDialog(_parent) |
| 65 | , d(new KBugReportPrivate(this)) |
| 66 | { |
| 67 | setWindowTitle(i18nc("@title:window" , "Submit Bug Report" )); |
| 68 | |
| 69 | QDialogButtonBox *buttonBox = new QDialogButtonBox(this); |
| 70 | buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); |
| 71 | connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, context: this, slot: &QDialog::accept); |
| 72 | connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, context: this, slot: &QDialog::reject); |
| 73 | |
| 74 | const QString bugAddress = aboutData.bugAddress(); |
| 75 | if (bugAddress == QLatin1String("submit@bugs.kde.org" )) { |
| 76 | // This is a core KDE application -> redirect to the web form |
| 77 | d->bugDestination = KBugReportPrivate::BugsKdeOrg; |
| 78 | } else if (!QUrl(bugAddress).scheme().isEmpty()) { |
| 79 | // The bug reporting address is a URL -> redirect to that |
| 80 | d->bugDestination = KBugReportPrivate::CustomUrl; |
| 81 | } |
| 82 | |
| 83 | KGuiItem::assign(button: buttonBox->button(which: QDialogButtonBox::Cancel), item: KStandardGuiItem::close()); |
| 84 | |
| 85 | QLabel *tmpLabel; |
| 86 | QVBoxLayout *lay = new QVBoxLayout(this); |
| 87 | |
| 88 | KTitleWidget *title = new KTitleWidget(this); |
| 89 | title->setText(i18n("Submit Bug Report" )); |
| 90 | title->setIconSize(QSize(32, 32)); |
| 91 | title->setIcon(icon: QIcon::fromTheme(QStringLiteral("tools-report-bug" ))); |
| 92 | lay->addWidget(title); |
| 93 | |
| 94 | QGridLayout *glay = new QGridLayout(); |
| 95 | lay->addLayout(layout: glay); |
| 96 | |
| 97 | int row = 0; |
| 98 | |
| 99 | // Program name |
| 100 | QString qwtstr = |
| 101 | i18n("The application for which you wish to submit a bug report - if incorrect, please use the Report Bug menu item of the correct application" ); |
| 102 | tmpLabel = new QLabel(i18n("Application: " ), this); |
| 103 | glay->addWidget(tmpLabel, row, column: 0); |
| 104 | tmpLabel->setWhatsThis(qwtstr); |
| 105 | QLabel *appLabel = new QLabel(this); |
| 106 | d->appname = aboutData.productName(); |
| 107 | appLabel->setText(d->appname); |
| 108 | glay->addWidget(appLabel, row, column: 1); |
| 109 | tmpLabel->setWhatsThis(qwtstr); |
| 110 | |
| 111 | // Version |
| 112 | qwtstr = i18n("The version of this application - please make sure that no newer version is available before sending a bug report" ); |
| 113 | tmpLabel = new QLabel(i18n("Version:" ), this); |
| 114 | glay->addWidget(tmpLabel, row: ++row, column: 0); |
| 115 | tmpLabel->setWhatsThis(qwtstr); |
| 116 | d->m_strVersion = aboutData.version(); |
| 117 | if (d->m_strVersion.isEmpty()) { |
| 118 | d->m_strVersion = i18n("no version set (programmer error)" ); |
| 119 | } |
| 120 | d->kde_version = QStringLiteral(KXMLGUI_VERSION_STRING); |
| 121 | if (d->bugDestination != KBugReportPrivate::BugsKdeOrg) { |
| 122 | d->m_strVersion = i18nc("%1 is the program version, %2 is the kde framework version" , "%1 with KDE Frameworks %2" , d->m_strVersion, d->kde_version); |
| 123 | } |
| 124 | d->m_version = new QLabel(d->m_strVersion, this); |
| 125 | d->m_version->setTextInteractionFlags(Qt::TextBrowserInteraction); |
| 126 | // glay->addWidget( d->m_version, row, 1 ); |
| 127 | glay->addWidget(d->m_version, row, column: 1, rowSpan: 1, columnSpan: 2); |
| 128 | d->m_version->setWhatsThis(qwtstr); |
| 129 | |
| 130 | tmpLabel = new QLabel(i18n("OS:" ), this); |
| 131 | glay->addWidget(tmpLabel, row: ++row, column: 0); |
| 132 | |
| 133 | #ifdef Q_OS_WINDOWS |
| 134 | d->os = i18nc("%1 is the operating system name, e.g. 'Windows 10', %2 is the CPU architecture, e.g. 'x86_64'" , |
| 135 | "%1 (%2)" , |
| 136 | QSysInfo::prettyProductName(), |
| 137 | QSysInfo::currentCpuArchitecture()); |
| 138 | #else |
| 139 | if (QSysInfo::productVersion() != QLatin1String("unknown" )) { |
| 140 | d->os = i18nc( |
| 141 | "%1 is the operating system name, e.g. 'Fedora Linux', %2 is the operating system version, e.g. '35', %3 is the CPU architecture, e.g. 'x86_64'" , |
| 142 | "%1 %2 (%3)" , |
| 143 | QSysInfo::prettyProductName(), |
| 144 | QSysInfo::productVersion(), |
| 145 | QSysInfo::currentCpuArchitecture()); |
| 146 | } else { |
| 147 | d->os = i18nc("%1 is the operating system name, e.g. 'Fedora Linux', %2 is the CPU architecture, e.g. 'x86_64'" , |
| 148 | "%1 (%2)" , |
| 149 | QSysInfo::prettyProductName(), |
| 150 | QSysInfo::currentCpuArchitecture()); |
| 151 | } |
| 152 | #endif |
| 153 | |
| 154 | tmpLabel = new QLabel(d->os, this); |
| 155 | tmpLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); |
| 156 | glay->addWidget(tmpLabel, row, column: 1, rowSpan: 1, columnSpan: 2); |
| 157 | // Point to the web form |
| 158 | |
| 159 | QString text; |
| 160 | if (d->bugDestination == KBugReportPrivate::BugsKdeOrg) { |
| 161 | text = i18n( |
| 162 | "<qt>To submit a bug report, click on the button below. This will open a web browser " |
| 163 | "window on <a href=\"https://bugs.kde.org\">https://bugs.kde.org</a> where you will find " |
| 164 | "a form to fill in. The information displayed above will be transferred to that server.</qt>" ); |
| 165 | d->updateUrl(); |
| 166 | } else { |
| 167 | text = i18n( |
| 168 | "<qt>To submit a bug report, click on the button below. This will open a web browser " |
| 169 | "window on <a href=\"%1\">%2</a>.</qt>" , |
| 170 | bugAddress, |
| 171 | bugAddress); |
| 172 | d->url = QUrl(bugAddress); |
| 173 | } |
| 174 | |
| 175 | lay->addSpacing(size: 10); |
| 176 | QLabel *label = new QLabel(text, this); |
| 177 | label->setOpenExternalLinks(true); |
| 178 | label->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); |
| 179 | label->setWordWrap(true); |
| 180 | lay->addWidget(label); |
| 181 | lay->addSpacing(size: 10); |
| 182 | |
| 183 | QPushButton *okButton = buttonBox->button(which: QDialogButtonBox::Ok); |
| 184 | if (d->bugDestination == KBugReportPrivate::BugsKdeOrg) { |
| 185 | okButton->setText(i18nc("@action:button" , "&Launch Bug Report Wizard" )); |
| 186 | } else { |
| 187 | okButton->setText(i18nc("@action:button" , "&Submit Bug Report" )); |
| 188 | } |
| 189 | okButton->setIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug" ))); |
| 190 | |
| 191 | lay->addWidget(buttonBox); |
| 192 | setMinimumHeight(sizeHint().height() + 20); // WORKAROUND: prevent "cropped" qcombobox |
| 193 | } |
| 194 | |
| 195 | KBugReport::~KBugReport() = default; |
| 196 | |
| 197 | void KBugReportPrivate::updateUrl() |
| 198 | { |
| 199 | url = QUrl(QStringLiteral("https://bugs.kde.org/enter_bug.cgi" )); |
| 200 | QUrlQuery query; |
| 201 | query.addQueryItem(QStringLiteral("format" ), QStringLiteral("guided" )); // use the guided form |
| 202 | |
| 203 | // the string format is product/component, where component is optional |
| 204 | QStringList list = appname.split(sep: QLatin1Char('/')); |
| 205 | query.addQueryItem(QStringLiteral("product" ), value: list[0]); |
| 206 | if (list.size() == 2) { |
| 207 | query.addQueryItem(QStringLiteral("component" ), value: list[1]); |
| 208 | } |
| 209 | |
| 210 | query.addQueryItem(QStringLiteral("version" ), value: m_strVersion); |
| 211 | url.setQuery(query); |
| 212 | |
| 213 | // TODO: guess and fill OS(sys_os) and Platform(rep_platform) fields |
| 214 | } |
| 215 | |
| 216 | void KBugReport::accept() |
| 217 | { |
| 218 | QDesktopServices::openUrl(url: d->url); |
| 219 | |
| 220 | // HACK: accept will close the window, which breaks the xdg-activation handling in QDesktopServices::openUrl() |
| 221 | // Slightly delay the closing to give openUrl time to finish |
| 222 | QTimer::singleShot(interval: 500ms, slot: [this] { |
| 223 | QDialog::accept(); |
| 224 | }); |
| 225 | } |
| 226 | |
| 227 | #include "moc_kbugreport.cpp" |
| 228 | |