diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5146510a..21d71130 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,132 +1,134 @@ include (CheckFunctionExists) check_function_exists("strsignal" HAVE_STRSIGNAL) check_function_exists("uname" HAVE_UNAME) if (NOT DEBUG_PACKAGE_INSTALLER_NAME) set (DEBUG_PACKAGE_INSTALLER_NAME "installdbgsymbols.sh") endif () configure_file (config-drkonqi.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-drkonqi.h ) add_subdirectory( bugzillaintegration/libbugzilla ) add_subdirectory( data ) add_subdirectory( parser ) if ( WIN32 ) find_package(KDEWin REQUIRED) # for finding drkonqi_debug.h include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory( kdbgwin ) endif () set(drkonqi_SRCS drkonqidialog.cpp statuswidget.cpp aboutbugreportingdialog.cpp backtraceratingwidget.cpp backtracewidget.cpp backtracegenerator.cpp drkonqi.cpp drkonqibackends.cpp detachedprocessmonitor.cpp debugpackageinstaller.cpp systeminformation.cpp crashedapplication.cpp debugger.cpp debuggerlaunchers.cpp ptracer.cpp debuggermanager.cpp applicationdetailsexamples.cpp gdbhighlighter.cpp statusnotifier.cpp ) ki18n_wrap_ui(drkonqi_SRCS ui/maindialog.ui ui/backtracewidget.ui ) # if BACKTRACE_PARSER_DEBUG is enabled, it will show both the # parsed and the unparsed backtrace in the backtrace widget. # Comment this out for release. #add_definitions(-DBACKTRACE_PARSER_DEBUG) set(drkonqi_SRCS ${drkonqi_SRCS} + bugzillaintegration/assistantpage_bugzilla_version.cpp bugzillaintegration/bugzillalib.cpp bugzillaintegration/reportassistantdialog.cpp bugzillaintegration/reportassistantpage.cpp bugzillaintegration/reportassistantpages_base.cpp bugzillaintegration/reportassistantpages_bugzilla.cpp bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp bugzillaintegration/reportinterface.cpp bugzillaintegration/productmapping.cpp bugzillaintegration/parsebugbacktraces.cpp bugzillaintegration/duplicatefinderjob.cpp ) ki18n_wrap_ui(drkonqi_SRCS bugzillaintegration/ui/assistantpage_introduction.ui bugzillaintegration/ui/assistantpage_bugawareness.ui bugzillaintegration/ui/assistantpage_conclusions.ui bugzillaintegration/ui/assistantpage_conclusions_dialog.ui bugzillaintegration/ui/assistantpage_bugzilla_login.ui bugzillaintegration/ui/assistantpage_bugzilla_duplicates.ui bugzillaintegration/ui/assistantpage_bugzilla_duplicates_dialog.ui bugzillaintegration/ui/assistantpage_bugzilla_duplicates_dialog_confirmation.ui bugzillaintegration/ui/assistantpage_bugzilla_information.ui bugzillaintegration/ui/assistantpage_bugzilla_preview.ui bugzillaintegration/ui/assistantpage_bugzilla_send.ui + bugzillaintegration/ui/assistantpage_bugzilla_version.ui ) ecm_qt_declare_logging_category(drkonqi_SRCS HEADER drkonqi_debug.h IDENTIFIER DRKONQI_LOG CATEGORY_NAME org.kde.drkonqi) # transient static lib we can use to link autotests against add_library(DrKonqiInternal STATIC ${drkonqi_SRCS}) target_link_libraries(DrKonqiInternal KF5::I18n KF5::CoreAddons KF5::Service KF5::ConfigWidgets KF5::JobWidgets KF5::KIOCore KF5::Crash KF5::Completion Qt5::DBus KF5::WidgetsAddons KF5::Wallet KF5::Notifications # for status notifier KF5::IdleTime # hide status notifier only if user saw it drkonqi_backtrace_parser qbugzilla ) if (${Qt5X11Extras_FOUND}) target_link_libraries(DrKonqiInternal Qt5::X11Extras ) endif() if (APPLE) target_link_libraries(DrKonqiInternal KF5::WindowSystem ) endif() if (WIN32) target_link_libraries(DrKonqiInternal kdewin) endif() add_executable(drkonqi main.cpp) ecm_mark_nongui_executable(drkonqi) target_link_libraries(drkonqi DrKonqiInternal) install(TARGETS drkonqi DESTINATION ${KDE_INSTALL_LIBEXECDIR}) configure_file(org.kde.drkonqi.desktop.cmake ${CMAKE_BINARY_DIR}/src/org.kde.drkonqi.desktop) install(PROGRAMS ${CMAKE_BINARY_DIR}/src/org.kde.drkonqi.desktop DESTINATION ${KDE_INSTALL_APPDIR}) add_subdirectory( tests ) add_subdirectory(bugzillaintegration/libbugzilla/autotests) diff --git a/src/bugzillaintegration/assistantpage_bugzilla_version.cpp b/src/bugzillaintegration/assistantpage_bugzilla_version.cpp new file mode 100644 index 00000000..de2d8c8c --- /dev/null +++ b/src/bugzillaintegration/assistantpage_bugzilla_version.cpp @@ -0,0 +1,81 @@ +/******************************************************************* +* Copyright 2019 Harald Sitter +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +******************************************************************/ + +#include "assistantpage_bugzilla_version.h" +#include "ui_assistantpage_bugzilla_version.h" + +#include + +#include "bugzillalib.h" + +BugzillaVersionPage::BugzillaVersionPage(ReportAssistantDialog *parent) + : ReportAssistantPage(parent) + , ui(new Ui::BugzillaVersionPage) + , m_item(new KPageWidgetItem(this)) +{ + // This item is intentionally not titled. The page should usually not show + // up when when it shows up it should focus on the bare essentials! + m_item->setIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"))); + + // We are not valid until the version came back! + assistant()->setValid(m_item, false); + + ui->setupUi(this); + + ui->errorIconLabel->setPixmap(QIcon::fromTheme(QStringLiteral("state-error")).pixmap(ui->errorIconLabel->size())); + + connect(bugzillaManager(), &BugzillaManager::bugzillaVersionFound, + this, [=] { + // Don't show this page ever again! + assistant()->setAppropriate(m_item, false); + if (assistant()->currentPage() == m_item) { + assistant()->next(); + } + }); + connect(bugzillaManager(), &BugzillaManager::bugzillaVersionError, + this, [=](const QString &error) { + ui->busyWidget->hide(); + ui->errorWidget->show(); + ui->errorLabel->setText(xi18nc("@info %1 is an error message from the backend", + "Failed to contact bugs.kde.org: %1", + error)); + }); + connect(ui->retryButton, &QPushButton::clicked, + this, [=] { + ui->busyWidget->show(); + ui->errorWidget->hide(); + bugzillaManager()->lookupVersion(); + }); + + // Finally trigger the actual load! + ui->retryButton->click(); +} + +BugzillaVersionPage::~BugzillaVersionPage() +{ + delete ui; +} + +bool BugzillaVersionPage::isComplete() +{ + return false; +} + +KPageWidgetItem *BugzillaVersionPage::item() const +{ + return m_item; +} diff --git a/src/bugzillaintegration/assistantpage_bugzilla_version.h b/src/bugzillaintegration/assistantpage_bugzilla_version.h new file mode 100644 index 00000000..b5d8ca49 --- /dev/null +++ b/src/bugzillaintegration/assistantpage_bugzilla_version.h @@ -0,0 +1,46 @@ +/******************************************************************* +* Copyright 2019 Harald Sitter +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +******************************************************************/ + +#ifndef ASSISTANTPAGE_BUGZILLA_VERSION_H +#define ASSISTANTPAGE_BUGZILLA_VERSION_H + +#include + +#include "reportassistantpage.h" + +class ReportAssistantDialog; +namespace Ui { +class BugzillaVersionPage; +} + +class BugzillaVersionPage : public ReportAssistantPage +{ + Q_OBJECT + +public: + explicit BugzillaVersionPage(ReportAssistantDialog *parent = nullptr); + ~BugzillaVersionPage(); + + KPageWidgetItem *item() const; + virtual bool isComplete() override; + +private: + Ui::BugzillaVersionPage *ui = nullptr; + KPageWidgetItem *m_item = nullptr; +}; + +#endif // ASSISTANTPAGE_BUGZILLA_VERSION_H diff --git a/src/bugzillaintegration/bugzillalib.cpp b/src/bugzillaintegration/bugzillalib.cpp index e4479463..80ef6478 100644 --- a/src/bugzillaintegration/bugzillalib.cpp +++ b/src/bugzillaintegration/bugzillalib.cpp @@ -1,371 +1,366 @@ /******************************************************************* * bugzillalib.cpp * Copyright 2009, 2011 Dario Andres Rodriguez * Copyright 2012 George Kiagiadakis * Copyright 2019 Harald Sitter * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "bugzillalib.h" #include #include "libbugzilla/clients/commentclient.h" #include "libbugzilla/connection.h" #include "libbugzilla/bugzilla.h" #include "drkonqi_debug.h" static const char showBugUrl[] = "show_bug.cgi?id=%1"; // Extra filter rigging. We don't want to leak secrets via qdebug, so install // a message handler which does nothing more than replace secrets in debug // messages with placeholders. // This is used as a global static (since message handlers are meant to be // static) and is slightly synchronizing across threads WRT the filter hash. struct QMessageFilterContainer { QMessageFilterContainer(); ~QMessageFilterContainer(); void insert(const QString &needle, const QString &replace); void clear(); QString filter(const QString &msg); // Message handler is called across threads. Syncronize for good meassure. QReadWriteLock lock; QtMessageHandler handler; private: QHash filters; }; Q_GLOBAL_STATIC(QMessageFilterContainer, s_messageFilter) QMessageFilterContainer::QMessageFilterContainer() { handler = qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &context, const QString &msg) { s_messageFilter->handler(type, context, s_messageFilter->filter(msg)); }); } QMessageFilterContainer::~QMessageFilterContainer() { qInstallMessageHandler(handler); } void QMessageFilterContainer::insert(const QString &needle, const QString &replace) { if (needle.isEmpty()) { return; } QWriteLocker locker(&lock); filters[needle] = replace; } QString QMessageFilterContainer::filter(const QString &msg) { QReadLocker locker(&lock); QString filteredMsg = msg; for (auto it = filters.constBegin(); it != filters.constEnd(); ++it) { filteredMsg.replace(it.key(), it.value()); } return filteredMsg; } void QMessageFilterContainer::clear() { QWriteLocker locker(&lock); filters.clear(); } BugzillaManager::BugzillaManager(const QString &bugTrackerUrl, QObject *parent) : QObject(parent) , m_bugTrackerUrl(bugTrackerUrl) , m_logged(false) , m_searchJob(nullptr) { Q_ASSERT(bugTrackerUrl.endsWith(QLatin1Char('/'))); Bugzilla::setConnection(new Bugzilla::HTTPConnection(QUrl(m_bugTrackerUrl + QStringLiteral("rest")))); - // Allow constructors for ReportInterface and assistant dialogs to finish. - // Otherwise we may have a race on our hand if the lookup finishes before - // the constructors. - // I am not sure why this is so weirdly done TBH. Might deserve some looking - // into. - QMetaObject::invokeMethod(this, &BugzillaManager::lookupVersion, Qt::QueuedConnection); } void BugzillaManager::lookupVersion() { KJob *job = Bugzilla::version(); connect(job, &KJob::finished, this, [this](KJob *job) { try { QString version = Bugzilla::version(job); setFeaturesForVersion(version); emit bugzillaVersionFound(); } catch (Bugzilla::Exception &e) { // Version detection problems simply mean we'll not mark the version // found and the UI will not allow reporting. qCWarning(DRKONQI_LOG) << e.whatString(); + emit bugzillaVersionError(e.whatString()); } }); } void BugzillaManager::setFeaturesForVersion(const QString& version) { // A procedure to change Dr Konqi behaviour automatically when Bugzilla // software versions change. // // Changes should be added to Dr Konqi AHEAD of when the corresponding // Bugzilla software changes are released into bugs.kde.org, so that // Dr Konqi can continue to operate smoothly, without bug reports and a // reactive KDE software release. // // If Bugzilla announces a change to its software that affects Dr Konqi, // add executable code to implement the change automatically when the // Bugzilla software version changes. It goes at the end of this procedure // and elsewhere in this class (BugzillaManager) and/or other classes where // the change should actually be implemented. const int nVersionParts = 3; QString seps = QLatin1String("[._-]"); QStringList digits = version.split(QRegExp(seps), QString::SkipEmptyParts); while (digits.count() < nVersionParts) { digits << QLatin1String("0"); } if (digits.count() > nVersionParts) { qCWarning(DRKONQI_LOG) << QStringLiteral("Current Bugzilla version %1 has more than %2 parts. Check that this is not a problem.").arg(version).arg(nVersionParts); } qCDebug(DRKONQI_LOG) << "VERSION" << version; } void BugzillaManager::tryLogin(const QString &username, const QString &password) { m_username = username; m_password = password; refreshToken(); } void BugzillaManager::refreshToken() { Q_ASSERT(!m_username.isEmpty()); Q_ASSERT(!m_password.isEmpty()); m_logged = false; // Rest token and qdebug filters Bugzilla::connection().setToken(QString()); s_messageFilter->clear(); s_messageFilter->insert(m_password, QStringLiteral("PASSWORD")); KJob *job = Bugzilla::login(m_username, m_password); connect(job, &KJob::finished, this, [this](KJob *job) { try { auto details = Bugzilla::login(job); m_token = details.token; if (m_token.isEmpty()) { throw Bugzilla::RuntimeException(QStringLiteral("Did not receive a token")); } s_messageFilter->insert(m_token, QStringLiteral("TOKEN")); Bugzilla::connection().setToken(m_token); m_logged = true; emit loginFinished(true); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); // Version detection problems simply mean we'll not mark the version // found and the UI will not allow reporting. emit loginError(e.whatString()); } }); } bool BugzillaManager::getLogged() const { return m_logged; } QString BugzillaManager::getUsername() const { return m_username; } void BugzillaManager::fetchBugReport(int bugnumber, QObject *jobOwner) { Bugzilla::BugSearch search; search.id = bugnumber; Bugzilla::BugClient client; auto job = m_searchJob = client.search(search); connect(job, &KJob::finished, this, [this, &client, jobOwner](KJob *job) { try { auto list = client.search(job); if (list.size() != 1) { throw Bugzilla::RuntimeException(QStringLiteral("Unexpected bug amount returned: %1").arg(list.size())); } auto bug = list.at(0); m_searchJob = nullptr; emit bugReportFetched(bug, jobOwner); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit bugReportError(e.whatString(), jobOwner); } }); } void BugzillaManager::fetchComments(const Bugzilla::Bug::Ptr &bug, QObject *jobOwner) { Bugzilla::CommentClient client; auto job = client.getFromBug(bug->id()); connect(job, &KJob::finished, this, [this, &client, jobOwner](KJob *job) { try { auto comments = client.getFromBug(job); emit commentsFetched(comments, jobOwner); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit commentsError(e.whatString(), jobOwner); } }); } // TODO: This would kinda benefit from an actual pagination class, // currently this implicitly relies on the caller to handle offests correctly. // Fortunately we only have one caller so it makes no difference. void BugzillaManager::searchBugs(const QStringList &products, const QString &severity, const QString &comment, int offset) { Bugzilla::BugSearch search; search.products = products; search.severity = severity; search.longdesc = comment; // Order descedingly by bug_id. This allows us to offset through the results // from newest to oldest. // The UI will later order our data anyway, so the order at which we receive // the data is not important for the UI (outside the fact that we want // to step through pages of data) search.order << QStringLiteral("bug_id DESC"); search.limit = 25; search.offset = offset; stopCurrentSearch(); Bugzilla::BugClient client; auto job = m_searchJob = Bugzilla::BugClient().search(search); connect(job, &KJob::finished, this, [this, &client](KJob *job) { try { auto list = client.search(job); m_searchJob = nullptr; emit searchFinished(list); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit searchError(e.whatString()); } }); } void BugzillaManager::sendReport(const Bugzilla::NewBug &bug) { auto job = Bugzilla::BugClient().create(bug); connect(job, &KJob::finished, this, [this](KJob *job) { try { int id = Bugzilla::BugClient().create(job); Q_ASSERT(id > 0); emit reportSent(id); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit sendReportError(e.whatString()); } }); } void BugzillaManager::attachTextToReport(const QString & text, const QString & filename, const QString & summary, int bugId, const QString & comment) { Bugzilla::NewAttachment attachment; attachment.ids = QList { bugId }; attachment.data = text; attachment.file_name = filename; attachment.summary = summary; attachment.comment = comment; attachment.content_type = QLatin1Literal("text/plain"); auto job = Bugzilla::AttachmentClient().createAttachment(bugId, attachment); connect(job, &KJob::finished, this, [this](KJob *job) { try { QList ids = Bugzilla::AttachmentClient().createAttachment(job); Q_ASSERT(ids.size() == 1); emit attachToReportSent(ids.at(0)); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit attachToReportError(e.whatString()); } }); } void BugzillaManager::addMeToCC(int bugId) { Bugzilla::BugUpdate update; Q_ASSERT(!m_username.isEmpty()); update.cc->add << m_username; auto job = Bugzilla::BugClient().update(bugId, update); connect(job, &KJob::finished, this, [this](KJob *job) { try { const auto bugId = Bugzilla::BugClient().update(job); Q_ASSERT(bugId != 0); emit addMeToCCFinished(bugId); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit addMeToCCError(e.whatString()); } }); } void BugzillaManager::fetchProductInfo(const QString &product) { auto job = Bugzilla::ProductClient().get(product); connect(job, &KJob::finished, this, [this](KJob *job) { try { auto ptr = Bugzilla::ProductClient().get(job); Q_ASSERT(ptr); productInfoFetched(ptr); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); // This doesn't have a string because it is actually not used for // anything... emit productInfoError(); } }); } QString BugzillaManager::urlForBug(int bug_number) const { return QString(m_bugTrackerUrl) + QString::fromLatin1(showBugUrl).arg(bug_number); } void BugzillaManager::stopCurrentSearch() { if (m_searchJob) { //Stop previous searchJob m_searchJob->disconnect(); m_searchJob->kill(); m_searchJob = nullptr; } } diff --git a/src/bugzillaintegration/bugzillalib.h b/src/bugzillaintegration/bugzillalib.h index 53a1cbe5..5f4b1e29 100644 --- a/src/bugzillaintegration/bugzillalib.h +++ b/src/bugzillaintegration/bugzillalib.h @@ -1,106 +1,105 @@ /******************************************************************* * bugzillalib.h * Copyright 2009, 2011 Dario Andres Rodriguez * Copyright 2012 George Kiagiadakis * Copyright 2019 Harald Sitter * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #ifndef BUGZILLALIB__H #define BUGZILLALIB__H #include #include #include "libbugzilla/clients/bugclient.h" #include "libbugzilla/clients/productclient.h" #include "libbugzilla/clients/attachmentclient.h" namespace KIO { class KJob; } class BugzillaManager : public QObject { Q_OBJECT public: // Note: it expect the bugTrackerUrl parameter to contain the trailing slash. // so it should be "https://bugs.kde.org/", not "https://bugs.kde.org" explicit BugzillaManager(const QString &bugTrackerUrl, QObject *parent = nullptr); /* Login methods */ void tryLogin(const QString &username, const QString &password); void refreshToken(); bool getLogged() const; QString getUsername() const; /* Bugzilla Action methods */ void fetchBugReport(int, QObject *jobOwner = nullptr); void searchBugs(const QStringList &products, const QString &severity, const QString &comment, int offset); void sendReport(const Bugzilla::NewBug &bug); void attachTextToReport(const QString &text, const QString &filename, const QString &description, int bugId, const QString &comment); void addMeToCC(int bugId); void fetchProductInfo(const QString &); /* Misc methods */ QString urlForBug(int bug_number) const; void stopCurrentSearch(); void fetchComments(const Bugzilla::Bug::Ptr &bug, QObject *jobOwner); - -private Q_SLOTS: void lookupVersion(); Q_SIGNALS: /* Bugzilla actions finished successfully */ void loginFinished(bool logged); void bugReportFetched(Bugzilla::Bug::Ptr bug, QObject *jobOwner); void commentsFetched(QList comments, QObject *jobOwner); void searchFinished(const QList &bug); void reportSent(int bugId); void attachToReportSent(int bugId); void addMeToCCFinished(int bugId); void productInfoFetched(const Bugzilla::Product::Ptr &product); void bugzillaVersionFound(); /* Bugzilla actions had errors */ void loginError(const QString &errorMsg, const QString & xtendedErrorMsg = QString()); void bugReportError(const QString &errorMsg, QObject *jobOwner); void commentsError(const QString &errorMsg, QObject *jobOwner); void searchError(const QString &errorMsg); void sendReportError(const QString &errorMsg, const QString &extendedErrorMsg = QString()); void sendReportErrorInvalidValues(); //To use default values void attachToReportError(const QString &errorMsg, const QString &extendedErrorMsg = QString()); void addMeToCCError(const QString &errorMsg, const QString &extendedErrorMsg = QString()); void productInfoError(); + void bugzillaVersionError(const QString &errorMsg); private: QString m_bugTrackerUrl; QString m_username; QString m_token; QString m_password; bool m_logged = false; KJob *m_searchJob = nullptr; void setFeaturesForVersion(const QString &version); }; #endif diff --git a/src/bugzillaintegration/reportassistantdialog.cpp b/src/bugzillaintegration/reportassistantdialog.cpp index 15881216..1ecb0865 100644 --- a/src/bugzillaintegration/reportassistantdialog.cpp +++ b/src/bugzillaintegration/reportassistantdialog.cpp @@ -1,398 +1,415 @@ /******************************************************************* * reportassistantdialog.cpp * Copyright 2009,2010 Dario Andres Rodriguez * Copyright 2019 Harald Sitter * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "reportassistantdialog.h" #include #include #include #include #include "drkonqi.h" #include "parser/backtraceparser.h" #include "debuggermanager.h" #include "backtracegenerator.h" +#include "assistantpage_bugzilla_version.h" #include "crashedapplication.h" #include "aboutbugreportingdialog.h" #include "reportassistantpages_base.h" #include "reportassistantpages_bugzilla.h" #include "reportassistantpages_bugzilla_duplicates.h" #include "reportinterface.h" static const char KDE_BUGZILLA_DESCRIPTION[] = I18N_NOOP("the KDE Bug Tracking System"); ReportAssistantDialog::ReportAssistantDialog(QWidget * parent) : KAssistantDialog(parent), m_aboutBugReportingDialog(nullptr), m_reportInterface(new ReportInterface(this)), m_canClose(false) { setAttribute(Qt::WA_DeleteOnClose, true); //Set window properties setWindowTitle(i18nc("@title:window","Crash Reporting Assistant")); setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"))); connect(this, &ReportAssistantDialog::currentPageChanged, this, &ReportAssistantDialog::currentPageChanged_slot); connect(button(QDialogButtonBox::Help), &QPushButton::clicked, this, &ReportAssistantDialog::showHelp); //Create the assistant pages //-Introduction Page KConfigGroup group(KSharedConfig::openConfig(), "ReportAssistant"); const bool skipIntroduction = group.readEntry("SkipIntroduction", false); if (!skipIntroduction) { IntroductionPage * m_introduction = new IntroductionPage(this); KPageWidgetItem * m_introductionPage = new KPageWidgetItem(m_introduction, QLatin1String(PAGE_INTRODUCTION_ID)); m_pageWidgetMap.insert(QLatin1String(PAGE_INTRODUCTION_ID),m_introductionPage); m_introductionPage->setHeader(i18nc("@title","Welcome to the Reporting Assistant")); m_introductionPage->setIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"))); addPage(m_introductionPage); } //-Bug Awareness Page BugAwarenessPage * m_awareness = new BugAwarenessPage(this); connectSignals(m_awareness); KPageWidgetItem * m_awarenessPage = new KPageWidgetItem(m_awareness, QLatin1String(PAGE_AWARENESS_ID)); m_pageWidgetMap.insert(QLatin1String(PAGE_AWARENESS_ID),m_awarenessPage); m_awarenessPage->setHeader(i18nc("@title","What do you know about the crash?")); m_awarenessPage->setIcon(QIcon::fromTheme(QStringLiteral("checkbox"))); //-Crash Information Page CrashInformationPage * m_backtrace = new CrashInformationPage(this); connectSignals(m_backtrace); KPageWidgetItem * m_backtracePage = new KPageWidgetItem(m_backtrace, QLatin1String(PAGE_CRASHINFORMATION_ID)); m_pageWidgetMap.insert(QLatin1String(PAGE_CRASHINFORMATION_ID),m_backtracePage); m_backtracePage->setHeader(i18nc("@title","Fetching the Backtrace (Automatic Crash Information)")); m_backtracePage->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); //-Results Page ConclusionPage * m_conclusions = new ConclusionPage(this); connectSignals(m_conclusions); KPageWidgetItem * m_conclusionsPage = new KPageWidgetItem(m_conclusions, QLatin1String(PAGE_CONCLUSIONS_ID)); m_pageWidgetMap.insert(QLatin1String(PAGE_CONCLUSIONS_ID),m_conclusionsPage); m_conclusionsPage->setHeader(i18nc("@title","Results of the Analyzed Crash Details")); m_conclusionsPage->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); connect(m_conclusions, &ConclusionPage::finished, this, &ReportAssistantDialog::assistantFinished); + // Version check page + BugzillaVersionPage *versionPage = new BugzillaVersionPage(this); + m_pageWidgetMap.insert(QLatin1String(PAGE_BZVERSION_ID), versionPage->item()); + //-Bugzilla Login BugzillaLoginPage * m_bugzillaLogin = new BugzillaLoginPage(this); connectSignals(m_bugzillaLogin); KPageWidgetItem * m_bugzillaLoginPage = new KPageWidgetItem(m_bugzillaLogin, QLatin1String(PAGE_BZLOGIN_ID)); m_pageWidgetMap.insert(QLatin1String(PAGE_BZLOGIN_ID),m_bugzillaLoginPage); m_bugzillaLoginPage->setHeader(i18nc("@title", "Login into %1", i18n(KDE_BUGZILLA_DESCRIPTION))); m_bugzillaLoginPage->setIcon(QIcon::fromTheme(QStringLiteral("user-identity"))); connect(m_bugzillaLogin, &BugzillaLoginPage::loggedTurnToNextPage, this, &ReportAssistantDialog::loginFinished); //-Bugzilla duplicates BugzillaDuplicatesPage * m_bugzillaDuplicates = new BugzillaDuplicatesPage(this); connectSignals(m_bugzillaDuplicates); KPageWidgetItem * m_bugzillaDuplicatesPage = new KPageWidgetItem(m_bugzillaDuplicates, QLatin1String(PAGE_BZDUPLICATES_ID)); m_pageWidgetMap.insert(QLatin1String(PAGE_BZDUPLICATES_ID),m_bugzillaDuplicatesPage); m_bugzillaDuplicatesPage->setHeader(i18nc("@title","Look for Possible Duplicate Reports")); m_bugzillaDuplicatesPage->setIcon(QIcon::fromTheme(QStringLiteral("repository"))); //-Bugzilla information BugzillaInformationPage * m_bugzillaInformation = new BugzillaInformationPage(this); connectSignals(m_bugzillaInformation); KPageWidgetItem * m_bugzillaInformationPage = new KPageWidgetItem(m_bugzillaInformation, QLatin1String(PAGE_BZDETAILS_ID)); m_pageWidgetMap.insert(QLatin1String(PAGE_BZDETAILS_ID),m_bugzillaInformationPage); m_bugzillaInformationPage->setHeader(i18nc("@title","Enter the Details about the Crash")); m_bugzillaInformationPage->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); //-Bugzilla Report Preview BugzillaPreviewPage * m_bugzillaPreview = new BugzillaPreviewPage(this); KPageWidgetItem * m_bugzillaPreviewPage = new KPageWidgetItem(m_bugzillaPreview, QLatin1String(PAGE_BZPREVIEW_ID)); m_pageWidgetMap.insert(QLatin1String(PAGE_BZPREVIEW_ID),m_bugzillaPreviewPage); m_bugzillaPreviewPage->setHeader(i18nc("@title","Preview the Report")); m_bugzillaPreviewPage->setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); //-Bugzilla commit BugzillaSendPage * m_bugzillaSend = new BugzillaSendPage(this); KPageWidgetItem * m_bugzillaSendPage = new KPageWidgetItem(m_bugzillaSend, QLatin1String(PAGE_BZSEND_ID)); m_pageWidgetMap.insert(QLatin1String(PAGE_BZSEND_ID),m_bugzillaSendPage); m_bugzillaSendPage->setHeader(i18nc("@title","Sending the Crash Report")); m_bugzillaSendPage->setIcon(QIcon::fromTheme(QStringLiteral("applications-internet"))); connect(m_bugzillaSend, &BugzillaSendPage::finished, this, &ReportAssistantDialog::assistantFinished); //TODO Remember to keep the pages ordered addPage(m_awarenessPage); addPage(m_backtracePage); addPage(m_conclusionsPage); + addPage(versionPage->item()); addPage(m_bugzillaLoginPage); addPage(m_bugzillaDuplicatesPage); addPage(m_bugzillaInformationPage); addPage(m_bugzillaPreviewPage); addPage(m_bugzillaSendPage); // Force a 16:9 ratio for nice appearance by default. QSize aspect(16, 9); aspect.scale(sizeHint(), Qt::KeepAspectRatioByExpanding); resize(aspect); } ReportAssistantDialog::~ReportAssistantDialog() { } void ReportAssistantDialog::setAboutToSend(bool aboutTo) { if (aboutTo) { m_nextButtonIconCache = nextButton()->icon(); m_nextButtonTextCache = nextButton()->text(); nextButton()->setIcon(QIcon::fromTheme(QStringLiteral("document-send"))); nextButton()->setText(i18nc("@action button to submit report", "Submit")); return; } nextButton()->setIcon(m_nextButtonIconCache); nextButton()->setText(m_nextButtonTextCache); m_nextButtonIconCache = QIcon(); m_nextButtonTextCache = QString(); } void ReportAssistantDialog::connectSignals(ReportAssistantPage * page) { //React to the changes in the assistant pages connect(page, &ReportAssistantPage::completeChanged, this, &ReportAssistantDialog::completeChanged); } void ReportAssistantDialog::currentPageChanged_slot(KPageWidgetItem * current , KPageWidgetItem * before) { //Page changed buttonBox()->button(QDialogButtonBox::Cancel)->setEnabled(true); m_canClose = false; //Save data of the previous page if (before) { ReportAssistantPage* beforePage = dynamic_cast(before->widget()); beforePage->aboutToHide(); } //Load data of the current(new) page if (current) { ReportAssistantPage* currentPage = dynamic_cast(current->widget()); nextButton()->setEnabled(currentPage->isComplete()); currentPage->aboutToShow(); } //If the current page is the last one, disable all the buttons until the bug is sent if (current->name() == QLatin1String(PAGE_BZSEND_ID)) { nextButton()->setEnabled(false); backButton()->setEnabled(false); finishButton()->setEnabled(false); } } void ReportAssistantDialog::completeChanged(ReportAssistantPage* page, bool isComplete) { if (page == dynamic_cast(currentPage()->widget())) { nextButton()->setEnabled(isComplete); } } void ReportAssistantDialog::assistantFinished(bool showBack) { //The assistant finished: allow the user to close the dialog normally nextButton()->setEnabled(false); backButton()->setEnabled(showBack); finishButton()->setEnabled(true); buttonBox()->button(QDialogButtonBox::Cancel)->setEnabled(false); m_canClose = true; } void ReportAssistantDialog::loginFinished() { //Bugzilla login finished, go to the next page if (currentPage()->name() == QLatin1String(PAGE_BZLOGIN_ID)) { next(); } } void ReportAssistantDialog::showHelp() { //Show the bug reporting guide dialog if (!m_aboutBugReportingDialog) { m_aboutBugReportingDialog = new AboutBugReportingDialog(); } m_aboutBugReportingDialog->show(); m_aboutBugReportingDialog->raise(); m_aboutBugReportingDialog->activateWindow(); m_aboutBugReportingDialog->showSection(QLatin1String(PAGE_HELP_BEGIN_ID)); m_aboutBugReportingDialog->showSection(currentPage()->name()); } //Override KAssistantDialog "next" page implementation void ReportAssistantDialog::next() { + // FIXME: this entire function is a bit weird. It'd likely make more sense to + // use the page appropriateness more globally. i.e. mark pages inappropriate + // when they are not applicable based on earlier settings done (e.g. put + // a conclusion page under/after the awareness page but only mark it + // appropriate if the data is not useful. that way kassistantdialog would + // just skip over the page). + //Allow the widget to Ask a question to the user before changing the page ReportAssistantPage * page = dynamic_cast(currentPage()->widget()); if (page) { if (!page->showNextPage()) { return; } } const QString name = currentPage()->name(); //If the information the user can provide is not useful, skip the backtrace page if (name == QLatin1String(PAGE_AWARENESS_ID)) { //Force save settings in the current page page->aboutToHide(); if (!(m_reportInterface->isBugAwarenessPageDataUseful())) { setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_CONCLUSIONS_ID))); return; } } else if (name == QLatin1String(PAGE_CRASHINFORMATION_ID)){ //Force save settings in current page page->aboutToHide(); //If the crash is worth reporting and it is BKO, skip the Conclusions page if (m_reportInterface->isWorthReporting() && DrKonqi::crashedApplication()->bugReportAddress().isKdeBugzilla()) { - setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_BZLOGIN_ID))); + // Depending on whether the page is appropriate either go to version + // check page or login page. + const auto versionPage = m_pageWidgetMap.value(QLatin1String(PAGE_BZVERSION_ID)); + const auto loginPage = m_pageWidgetMap.value(QLatin1String(PAGE_BZLOGIN_ID)); + setCurrentPage(isAppropriate(versionPage) ? versionPage : loginPage); return; } } else if (name == QLatin1String(PAGE_BZDUPLICATES_ID)) { //a duplicate has been found, yet the report is not being attached if (m_reportInterface->duplicateId() && !m_reportInterface->attachToBugNumber()) { setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_CONCLUSIONS_ID))); return; } } KAssistantDialog::next(); } //Override KAssistantDialog "back"(previous) page implementation //It has to mirror the custom next() implementation void ReportAssistantDialog::back() { if (currentPage()->name() == QLatin1String(PAGE_CONCLUSIONS_ID)) { if (m_reportInterface->duplicateId() && !m_reportInterface->attachToBugNumber()) { setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_BZDUPLICATES_ID))); return; } if (!(m_reportInterface->isBugAwarenessPageDataUseful())) { setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_AWARENESS_ID))); return; } } if (currentPage()->name() == QLatin1String(PAGE_BZLOGIN_ID)) { if (m_reportInterface->isWorthReporting() && DrKonqi::crashedApplication()->bugReportAddress().isKdeBugzilla()) { setCurrentPage(m_pageWidgetMap.value(QLatin1String(PAGE_CRASHINFORMATION_ID))); return; } } KAssistantDialog::back(); } void ReportAssistantDialog::reject() { close(); } void ReportAssistantDialog::closeEvent(QCloseEvent * event) { //Handle the close event if (!m_canClose) { //If the assistant didn't finished yet, offer the user the possibilities to //Close, Cancel, or Save the bug report and Close" KGuiItem closeItem = KStandardGuiItem::close(); closeItem.setText(i18nc("@action:button", "Close the assistant")); KGuiItem keepOpenItem = KStandardGuiItem::cancel(); keepOpenItem.setText(i18nc("@action:button", "Cancel")); BacktraceParser::Usefulness use = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness(); if (use == BacktraceParser::ReallyUseful || use == BacktraceParser::MayBeUseful) { //Backtrace is still useful, let the user save it. KGuiItem saveBacktraceItem = KStandardGuiItem::save(); saveBacktraceItem.setText(i18nc("@action:button", "Save information and close")); int ret = KMessageBox::questionYesNoCancel(this, xi18nc("@info","Do you really want to close the bug reporting assistant? " "The crash information is still valid, so " "you can save the report before closing if you want."), i18nc("@title:window","Close the Assistant"), closeItem, saveBacktraceItem, keepOpenItem, QString(), KMessageBox::Dangerous); if(ret == KMessageBox::Yes) { event->accept(); } else if (ret == KMessageBox::No) { //Save backtrace and accept event (dialog will be closed) DrKonqi::saveReport(reportInterface()->generateReportFullText(false)); event->accept(); } else { event->ignore(); } } else { if (KMessageBox::questionYesNo(this, i18nc("@info","Do you really want to close the bug " "reporting assistant?"), i18nc("@title:window","Close the Assistant"), closeItem, keepOpenItem, QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { event->accept(); } else { event->ignore(); } } } else { event->accept(); } } diff --git a/src/bugzillaintegration/reportassistantdialog.h b/src/bugzillaintegration/reportassistantdialog.h index 237a8058..5bd59bf3 100644 --- a/src/bugzillaintegration/reportassistantdialog.h +++ b/src/bugzillaintegration/reportassistantdialog.h @@ -1,80 +1,81 @@ /******************************************************************* * reportassistantdialog.h * Copyright 2009 Dario Andres Rodriguez * Copyright 2019 Harald Sitter * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #ifndef REPORTASSISTANTDIALOG__H #define REPORTASSISTANTDIALOG__H #include #include #include class ReportAssistantPage; class AboutBugReportingDialog; class ReportInterface; class QCloseEvent; class ReportAssistantDialog: public KAssistantDialog { Q_OBJECT public: explicit ReportAssistantDialog(QWidget * parent = nullptr); ~ReportAssistantDialog() override; ReportInterface *reportInterface() const { return m_reportInterface; } void setAboutToSend(bool aboutTo); +public Q_SLOTS: + void next() override; + void back() override; + private Q_SLOTS: void currentPageChanged_slot(KPageWidgetItem *, KPageWidgetItem *); void completeChanged(ReportAssistantPage*, bool); void loginFinished(); void assistantFinished(bool); void showHelp(); - void next() override; - void back() override; - //Override default reject method void reject() override; private: void connectSignals(ReportAssistantPage *); void closeEvent(QCloseEvent*) override; QHash m_pageWidgetMap; QPointer m_aboutBugReportingDialog; ReportInterface * m_reportInterface = nullptr; bool m_canClose; QIcon m_nextButtonIconCache; QString m_nextButtonTextCache; }; #endif diff --git a/src/bugzillaintegration/reportassistantpages_bugzilla.cpp b/src/bugzillaintegration/reportassistantpages_bugzilla.cpp index bbe2f8fb..486749ec 100644 --- a/src/bugzillaintegration/reportassistantpages_bugzilla.cpp +++ b/src/bugzillaintegration/reportassistantpages_bugzilla.cpp @@ -1,862 +1,854 @@ /******************************************************************* * reportassistantpages_bugzilla.cpp * Copyright 2009, 2010, 2011 Dario Andres Rodriguez * Copyright 2019 Harald Sitter * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "reportassistantpages_bugzilla.h" +#include #include #include #include #include #include #include #include #include #include #include "drkonqi_debug.h" #include #include #include #include /* Unhandled error dialog includes */ #include #include #include #include #include "reportinterface.h" #include "systeminformation.h" #include "crashedapplication.h" #include "bugzillalib.h" #include "statuswidget.h" #include "drkonqi.h" #include "drkonqi_globals.h" #include "applicationdetailsexamples.h" static const char kWalletEntryName[] = "drkonqi_bugzilla"; static const char kWalletEntryUsername[] = "username"; static const char kWalletEntryPassword[] = "password"; static QString konquerorKWalletEntryName = KDE_BUGZILLA_URL + QStringLiteral("index.cgi#login"); static const char konquerorKWalletEntryUsername[] = "Bugzilla_login"; static const char konquerorKWalletEntryPassword[] = "Bugzilla_password"; //BEGIN BugzillaLoginPage BugzillaLoginPage::BugzillaLoginPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent), - m_wallet(nullptr), m_walletWasOpenedBefore(false), - m_bugzillaVersionFound(false) + m_wallet(nullptr), + m_walletWasOpenedBefore(false) { - connect(bugzillaManager(), &BugzillaManager::bugzillaVersionFound, this, &BugzillaLoginPage::bugzillaVersionFound); connect(bugzillaManager(), &BugzillaManager::loginFinished, this, &BugzillaLoginPage::loginFinished); connect(bugzillaManager(), &BugzillaManager::loginError, this, &BugzillaLoginPage::loginError); ui.setupUi(this); ui.m_statusWidget->setIdle(i18nc("@info:status '1' is replaced with the short URL of the bugzilla ", "You need to login with your %1 account in order to proceed.", QLatin1String(KDE_BUGZILLA_SHORT_URL))); KGuiItem::assign(ui.m_loginButton, KGuiItem2(i18nc("@action:button", "Login"), QIcon::fromTheme(QStringLiteral("network-connect")), i18nc("@info:tooltip", "Use this button to login " "to the KDE bug tracking system using the provided " "e-mail address and password."))); ui.m_loginButton->setEnabled(false); connect(ui.m_loginButton, &QPushButton::clicked, this, &BugzillaLoginPage::loginClicked); connect(ui.m_userEdit, &KLineEdit::returnPressed, this, &BugzillaLoginPage::loginClicked); connect(ui.m_passwordEdit->lineEdit(), &QLineEdit::returnPressed, this, &BugzillaLoginPage::loginClicked); connect(ui.m_userEdit, &KLineEdit::textChanged, this, &BugzillaLoginPage::updateLoginButtonStatus); connect(ui.m_passwordEdit, &KPasswordLineEdit::passwordChanged, this, &BugzillaLoginPage::updateLoginButtonStatus); ui.m_noticeLabel->setText( xi18nc("@info/rich","You need a user account on the " "KDE bug tracking system in order to " "file a bug report, because we may need to contact you later " "for requesting further information. If you do not have " "one, you can freely create one here. " "Please do not use disposable email accounts.", DrKonqi::crashedApplication()->bugReportAddress(), KDE_BUGZILLA_CREATE_ACCOUNT_URL)); // Don't advertise saving credentials when we can't save them. // https://bugs.kde.org/show_bug.cgi?id=363570 if (!KWallet::Wallet::isEnabled()) { ui.m_savePasswordCheckBox->setVisible(false); ui.m_savePasswordCheckBox->setCheckState(Qt::Unchecked); } } bool BugzillaLoginPage::isComplete() { return bugzillaManager()->getLogged(); } -void BugzillaLoginPage::bugzillaVersionFound() -{ - // Login depends on first knowing the Bugzilla software version number. - m_bugzillaVersionFound = true; - updateLoginButtonStatus(); -} - void BugzillaLoginPage::updateLoginButtonStatus() { - ui.m_loginButton->setEnabled( !ui.m_userEdit->text().isEmpty() && - !ui.m_passwordEdit->password().isEmpty() && - m_bugzillaVersionFound ); + ui.m_loginButton->setEnabled(canLogin()); } void BugzillaLoginPage::loginError(const QString & err, const QString & extendedMessage) { loginFinished(false); ui.m_statusWidget->setIdle(xi18nc("@info:status","Error when trying to login: " "%1", err)); if (!extendedMessage.isEmpty()) { new UnhandledErrorDialog(this, err, extendedMessage); } } void BugzillaLoginPage::aboutToShow() { if (bugzillaManager()->getLogged()) { ui.m_loginButton->setEnabled(false); ui.m_userEdit->setEnabled(false); ui.m_userEdit->clear(); ui.m_passwordEdit->setEnabled(false); ui.m_passwordEdit->clear(); ui.m_loginButton->setVisible(false); ui.m_userEdit->setVisible(false); ui.m_passwordEdit->setVisible(false); ui.m_userLabel->setVisible(false); ui.m_passwordLabel->setVisible(false); ui.m_savePasswordCheckBox->setVisible(false); ui.m_noticeLabel->setVisible(false); ui.m_statusWidget->setIdle(i18nc("@info:status the user is logged at the bugtracker site " "as USERNAME", "Logged in at the KDE bug tracking system (%1) as: %2.", QLatin1String(KDE_BUGZILLA_SHORT_URL), bugzillaManager()->getUsername())); } else { //Try to show wallet dialog once this dialog is shown QTimer::singleShot(100, this, &BugzillaLoginPage::walletLogin); } } bool BugzillaLoginPage::kWalletEntryExists(const QString& entryName) { return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::FormDataFolder(), entryName); } void BugzillaLoginPage::openWallet() { //Store if the wallet was previously opened so we can know if we should close it later m_walletWasOpenedBefore = KWallet::Wallet::isOpen(KWallet::Wallet::NetworkWallet()); //Request open the wallet m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), static_cast(this->parent())->winId()); } void BugzillaLoginPage::walletLogin() { if (!m_wallet) { if (kWalletEntryExists(QLatin1String(kWalletEntryName))) { //Key exists! openWallet(); ui.m_savePasswordCheckBox->setCheckState(Qt::Checked); //Was the wallet opened? if (m_wallet) { m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); //Use wallet data to try login QMap values; m_wallet->readMap(QLatin1String(kWalletEntryName), values); QString username = values.value(QLatin1String(kWalletEntryUsername)); QString password = values.value(QLatin1String(kWalletEntryPassword)); if (!username.isEmpty() && !password.isEmpty()) { ui.m_userEdit->setText(username); ui.m_passwordEdit->setPassword(password); } } } else if (kWalletEntryExists(konquerorKWalletEntryName)) { //If the DrKonqi entry is empty, but a Konqueror entry exists, use and copy it. openWallet(); if (m_wallet) { m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); //Fetch Konqueror data QMap values; m_wallet->readMap(konquerorKWalletEntryName, values); QString username = values.value(QLatin1String(konquerorKWalletEntryUsername)); QString password = values.value(QLatin1String(konquerorKWalletEntryPassword)); if (!username.isEmpty() && !password.isEmpty()) { //Copy to DrKonqi own entries values.clear(); values.insert(QLatin1String(kWalletEntryUsername), username); values.insert(QLatin1String(kWalletEntryPassword), password); m_wallet->writeMap(QLatin1String(kWalletEntryName), values); ui.m_savePasswordCheckBox->setCheckState(Qt::Checked); ui.m_userEdit->setText(username); ui.m_passwordEdit->setPassword(password); } } } if (canLogin()) { loginClicked(); } } } void BugzillaLoginPage::loginClicked() { if (!canLogin()) { loginFinished(false); return; } updateWidget(false); if (ui.m_savePasswordCheckBox->checkState()==Qt::Checked) { //Wants to save data if (!m_wallet) { openWallet(); } //Got wallet open ? if (m_wallet) { m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); QMap values; values.insert(QLatin1String(kWalletEntryUsername), ui.m_userEdit->text()); values.insert(QLatin1String(kWalletEntryPassword), ui.m_passwordEdit->password()); m_wallet->writeMap(QLatin1String(kWalletEntryName), values); } } else { //User doesn't want to save or wants to remove. if (kWalletEntryExists(QLatin1String(kWalletEntryName))) { if (!m_wallet) { openWallet(); } //Got wallet open ? if (m_wallet) { m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); m_wallet->removeEntry(QLatin1String(kWalletEntryName)); } } } login(); } bool BugzillaLoginPage::canLogin() const { - return (!(ui.m_userEdit->text().isEmpty() || ui.m_passwordEdit->password().isEmpty())); + return (!(ui.m_userEdit->text().isEmpty() || + ui.m_passwordEdit->password().isEmpty())); } void BugzillaLoginPage::login() { Q_ASSERT(canLogin()); ui.m_statusWidget->setBusy(i18nc("@info:status '1' is a url, '2' the e-mail address", "Performing login at %1 as %2...", QLatin1String(KDE_BUGZILLA_SHORT_URL), ui.m_userEdit->text())); bugzillaManager()->tryLogin(ui.m_userEdit->text(), ui.m_passwordEdit->password()); } void BugzillaLoginPage::updateWidget(bool enabled) { ui.m_loginButton->setEnabled(enabled); ui.m_userLabel->setEnabled(enabled); ui.m_passwordLabel->setEnabled(enabled); ui.m_userEdit->setEnabled(enabled); ui.m_passwordEdit->setEnabled(enabled); ui.m_savePasswordCheckBox->setEnabled(enabled); } void BugzillaLoginPage::loginFinished(bool logged) { if (logged) { emitCompleteChanged(); aboutToShow(); if (m_wallet) { if (m_wallet->isOpen() && !m_walletWasOpenedBefore) { m_wallet->lockWallet(); } } emit loggedTurnToNextPage(); } else { ui.m_statusWidget->setIdle(i18nc("@info:status", "Error: Invalid e-mail address or password")); updateWidget(true); ui.m_userEdit->setFocus(Qt::OtherFocusReason); } } BugzillaLoginPage::~BugzillaLoginPage() { //Close wallet if we close the assistant in this step if (m_wallet) { if (m_wallet->isOpen() && !m_walletWasOpenedBefore) { m_wallet->lockWallet(); } delete m_wallet; } } //END BugzillaLoginPage //BEGIN BugzillaInformationPage BugzillaInformationPage::BugzillaInformationPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent), m_textsOK(false), m_distributionComboSetup(false), m_distroComboVisible(false), m_requiredCharacters(1) { ui.setupUi(this); m_textCompleteBar = new KCapacityBar(KCapacityBar::DrawTextInline, this); ui.horizontalLayout_2->addWidget(m_textCompleteBar); connect(ui.m_titleEdit, &KLineEdit::textChanged, this, &BugzillaInformationPage::checkTexts); connect(ui.m_detailsEdit, &QTextEdit::textChanged, this, &BugzillaInformationPage::checkTexts); connect(ui.m_titleLabel, &QLabel::linkActivated, this, &BugzillaInformationPage::showTitleExamples); connect(ui.m_detailsLabel, &QLabel::linkActivated, this, &BugzillaInformationPage::showDescriptionHelpExamples); ui.m_compiledSourcesCheckBox->setChecked( DrKonqi::systemInformation()->compiledSources()); } void BugzillaInformationPage::aboutToShow() { if (!m_distributionComboSetup) { //Autodetecting distro failed ? if (DrKonqi::systemInformation()->bugzillaPlatform() == QLatin1String("unspecified")) { m_distroComboVisible = true; ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Unspecified"),QStringLiteral("unspecified")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Archlinux"), QStringLiteral("Archlinux Packages")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Chakra"), QStringLiteral("Chakra")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Debian stable"), QStringLiteral("Debian stable")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Debian testing"), QStringLiteral("Debian testing")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Debian unstable"), QStringLiteral("Debian unstable")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Exherbo"), QStringLiteral("Exherbo Packages")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Fedora"), QStringLiteral("Fedora RPMs")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Gentoo"), QStringLiteral("Gentoo Packages")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Mageia"), QStringLiteral("Mageia RPMs")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Mandriva"), QStringLiteral("Mandriva RPMs")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Neon"), QStringLiteral("Neon Packages")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "OpenSUSE"), QStringLiteral("openSUSE RPMs")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Pardus"), QStringLiteral("Pardus Packages")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "RedHat"), QStringLiteral("RedHat RPMs")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Slackware"), QStringLiteral("Slackware Packages")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Ubuntu (and derivatives)"), QStringLiteral("Ubuntu Packages")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "FreeBSD (Ports)"), QStringLiteral("FreeBSD Ports")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "NetBSD (pkgsrc)"), QStringLiteral("NetBSD pkgsrc")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "OpenBSD"), QStringLiteral("OpenBSD Packages")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Mac OS X"), QStringLiteral("MacPorts Packages")); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Solaris"), QStringLiteral("Solaris Packages")); //Restore previously selected bugzilla platform (distribution) KConfigGroup config(KSharedConfig::openConfig(), "BugzillaInformationPage"); QString entry = config.readEntry("BugzillaPlatform","unspecified"); int index = ui.m_distroChooserCombo->findData(entry); if ( index == -1 ) index = 0; ui.m_distroChooserCombo->setCurrentIndex(index); } else { ui.m_distroChooserCombo->setVisible(false); } m_distributionComboSetup = true; } //Calculate the minimum number of characters required for a description //If creating a new report: minimum 40, maximum 80 //If attaching to an existent report: minimum 30, maximum 50 int multiplier = (reportInterface()->attachToBugNumber() == 0) ? 10 : 5; m_requiredCharacters = 20 + (reportInterface()->selectedOptionsRating() * multiplier); //Fill the description textedit with some headings: QString descriptionTemplate; if (ui.m_detailsEdit->toPlainText().isEmpty()) { if (reportInterface()->userCanProvideActionsAppDesktop()) { descriptionTemplate += QLatin1String("- What I was doing when the application crashed:\n\n"); } if (reportInterface()->userCanProvideUnusualBehavior()) { descriptionTemplate += QLatin1String("- Unusual behavior I noticed:\n\n"); } if (reportInterface()->userCanProvideApplicationConfigDetails()) { descriptionTemplate += QLatin1String("- Custom settings of the application:\n\n"); } ui.m_detailsEdit->setText(descriptionTemplate); } checkTexts(); //May be the options (canDetail) changed and we need to recheck } int BugzillaInformationPage::currentDescriptionCharactersCount() { QString description = ui.m_detailsEdit->toPlainText(); //Do not count template messages, and other misc chars description.remove(QStringLiteral("What I was doing when the application crashed")); description.remove(QStringLiteral("Unusual behavior I noticed")); description.remove(QStringLiteral("Custom settings of the application")); description.remove(QLatin1Char('\n')); description.remove(QLatin1Char('-')); description.remove(QLatin1Char(':')); description.remove(QLatin1Char(' ')); return description.size(); } void BugzillaInformationPage::checkTexts() { //If attaching this report to an existing one then the title is not needed bool showTitle = (reportInterface()->attachToBugNumber() == 0); ui.m_titleEdit->setVisible(showTitle); ui.m_titleLabel->setVisible(showTitle); bool ok = !((ui.m_titleEdit->isVisible() && ui.m_titleEdit->text().isEmpty()) || ui.m_detailsEdit->toPlainText().isEmpty()); QString message; int percent = currentDescriptionCharactersCount() * 100 / m_requiredCharacters; if (percent >= 100) { percent = 100; message = i18nc("the minimum required length of a text was reached", "Minimum length reached"); } else { message = i18nc("the minimum required length of a text wasn't reached yet", "Provide more information"); } m_textCompleteBar->setValue(percent); m_textCompleteBar->setText(message); if (ok != m_textsOK) { m_textsOK = ok; emitCompleteChanged(); } } bool BugzillaInformationPage::showNextPage() { checkTexts(); if (m_textsOK) { bool detailsShort = currentDescriptionCharactersCount() < m_requiredCharacters; if (detailsShort) { //The user input is less than we want.... encourage to write more QString message = i18nc("@info","The description about the crash details does not provide " "enough information yet.

"); message += QLatin1Char(' ') + i18nc("@info","The amount of required information is proportional to " "the quality of the other information like the backtrace " "or the reproducibility rate." "

"); if (reportInterface()->userCanProvideActionsAppDesktop() || reportInterface()->userCanProvideUnusualBehavior() || reportInterface()->userCanProvideApplicationConfigDetails()) { message += QLatin1Char(' ') + i18nc("@info","Previously, you told DrKonqi that you could provide some " "contextual information. Try writing more details about your situation. " "(even little ones could help us.)

"); } message += QLatin1Char(' ') + i18nc("@info","If you cannot provide more information, your report " "will probably waste developers' time. Can you tell us more?"); KGuiItem yesItem = KStandardGuiItem::yes(); yesItem.setText(i18n("Yes, let me add more information")); KGuiItem noItem = KStandardGuiItem::no(); noItem.setText(i18n("No, I cannot add any other information")); if (KMessageBox::warningYesNo(this, message, i18nc("@title:window","We need more information"), yesItem, noItem) == KMessageBox::No) { //Request the assistant to close itself (it will prompt for confirmation anyways) assistant()->close(); return false; } } else { return true; } } return false; } bool BugzillaInformationPage::isComplete() { return m_textsOK; } void BugzillaInformationPage::aboutToHide() { //Save fields data reportInterface()->setTitle(ui.m_titleEdit->text()); reportInterface()->setDetailText(ui.m_detailsEdit->toPlainText()); if (m_distroComboVisible) { //Save bugzilla platform (distribution) QString bugzillaPlatform = ui.m_distroChooserCombo->itemData( ui.m_distroChooserCombo->currentIndex()).toString(); KConfigGroup config(KSharedConfig::openConfig(), "BugzillaInformationPage"); config.writeEntry("BugzillaPlatform", bugzillaPlatform); DrKonqi::systemInformation()->setBugzillaPlatform(bugzillaPlatform); } bool compiledFromSources = ui.m_compiledSourcesCheckBox->checkState() == Qt::Checked; DrKonqi::systemInformation()->setCompiledSources(compiledFromSources); } void BugzillaInformationPage::showTitleExamples() { QString titleExamples = xi18nc("@info:tooltip examples of good bug report titles", "Examples of good titles:\"Plasma crashed after adding the Notes " "widget and writing on it\"\"Konqueror crashed when accessing the Facebook " "application 'X'\"\"Kopete suddenly closed after resuming the computer and " "talking to a MSN buddy\"\"Kate closed while editing a log file and pressing the " "Delete key a couple of times\""); QToolTip::showText(QCursor::pos(), titleExamples); } void BugzillaInformationPage::showDescriptionHelpExamples() { QString descriptionHelp = i18nc("@info:tooltip help and examples of good bug descriptions", "Describe in as much detail as possible the crash circumstances:"); if (reportInterface()->userCanProvideActionsAppDesktop()) { descriptionHelp += QStringLiteral("
") + i18nc("@info:tooltip help and examples of good bug descriptions", "- Detail which actions were you taking inside and outside the " "application an instant before the crash."); } if (reportInterface()->userCanProvideUnusualBehavior()) { descriptionHelp += QStringLiteral("
") + i18nc("@info:tooltip help and examples of good bug descriptions", "- Note if you noticed any unusual behavior in the application " "or in the whole environment."); } if (reportInterface()->userCanProvideApplicationConfigDetails()) { descriptionHelp += QStringLiteral("
") + i18nc("@info:tooltip help and examples of good bug descriptions", "- Note any non-default configuration in the application."); if (reportInterface()->appDetailsExamples()->hasExamples()) { descriptionHelp += QLatin1Char(' ') + i18nc("@info:tooltip examples of configuration details. " "the examples are already translated", "Examples: %1", reportInterface()->appDetailsExamples()->examples()); } } QToolTip::showText(QCursor::pos(), descriptionHelp); } //END BugzillaInformationPage //BEGIN BugzillaPreviewPage BugzillaPreviewPage::BugzillaPreviewPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent) { ui.setupUi(this); } void BugzillaPreviewPage::aboutToShow() { ui.m_previewEdit->setText(reportInterface()->generateReportFullText(true)); assistant()->setAboutToSend(true); } void BugzillaPreviewPage::aboutToHide() { assistant()->setAboutToSend(false); } //END BugzillaPreviewPage //BEGIN BugzillaSendPage BugzillaSendPage::BugzillaSendPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent), m_contentsDialog(nullptr) { connect(reportInterface(), &ReportInterface::reportSent, this, &BugzillaSendPage::sent); connect(reportInterface(), &ReportInterface::sendReportError, this, &BugzillaSendPage::sendError); ui.setupUi(this); KGuiItem::assign(ui.m_retryButton, KGuiItem2(i18nc("@action:button", "Retry..."), QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@info:tooltip", "Use this button to retry " "sending the crash report if it failed before."))); KGuiItem::assign(ui.m_showReportContentsButton, KGuiItem2(i18nc("@action:button", "Sho&w Contents of the Report"), QIcon::fromTheme(QStringLiteral("document-preview")), i18nc("@info:tooltip", "Use this button to show the generated " "report information about this crash."))); connect(ui.m_showReportContentsButton, &QPushButton::clicked, this, &BugzillaSendPage::openReportContents); ui.m_retryButton->setVisible(false); connect(ui.m_retryButton, &QAbstractButton::clicked, this , &BugzillaSendPage::retryClicked); ui.m_launchPageOnFinish->setVisible(false); ui.m_restartAppOnFinish->setVisible(false); connect(assistant()->finishButton(), &QPushButton::clicked, this, &BugzillaSendPage::finishClicked); } void BugzillaSendPage::retryClicked() { ui.m_retryButton->setEnabled(false); aboutToShow(); } void BugzillaSendPage::aboutToShow() { ui.m_statusWidget->setBusy(i18nc("@info:status","Sending crash report... (please wait)")); // Trigger relogin. If the user took a long time to prepare the login our // token might have gone invalid in the meantime. As a cheap way to prevent // this we'll simply refresh the token regardless. It's plenty cheap and // should reliably ensure that the token is current. // Disconnect everything first though, this function may get called a bunch // of times, so we don't want duplicated submissions. disconnect(bugzillaManager(), &BugzillaManager::loginFinished, reportInterface(), &ReportInterface::sendBugReport); disconnect(bugzillaManager(), &BugzillaManager::loginError, this, nullptr); connect(bugzillaManager(), &BugzillaManager::loginFinished, reportInterface(), &ReportInterface::sendBugReport); connect(bugzillaManager(), &BugzillaManager::loginError, this, [this](const QString &error) { sendError(error, QString()); }); bugzillaManager()->refreshToken(); } void BugzillaSendPage::sent(int bug_id) { // Disconnect login->submit chain again. disconnect(bugzillaManager(), &BugzillaManager::loginFinished, reportInterface(), &ReportInterface::sendBugReport); disconnect(bugzillaManager(), &BugzillaManager::loginError, this, nullptr); ui.m_statusWidget->setVisible(false); ui.m_retryButton->setEnabled(false); ui.m_retryButton->setVisible(false); ui.m_showReportContentsButton->setVisible(false); ui.m_launchPageOnFinish->setVisible(true); ui.m_restartAppOnFinish->setVisible(!DrKonqi::crashedApplication()->hasBeenRestarted()); ui.m_restartAppOnFinish->setChecked(false); reportUrl = bugzillaManager()->urlForBug(bug_id); ui.m_finishedLabel->setText(xi18nc("@info/rich","Crash report sent." "URL: %1" "Thank you for being part of KDE. " "You can now close this window.", reportUrl)); emit finished(false); } void BugzillaSendPage::sendError(const QString & errorString, const QString & extendedMessage) { ui.m_statusWidget->setIdle(xi18nc("@info:status","Error sending the crash report: " "%1.", errorString)); ui.m_retryButton->setEnabled(true); ui.m_retryButton->setVisible(true); if (!extendedMessage.isEmpty()) { new UnhandledErrorDialog(this,errorString, extendedMessage); } } void BugzillaSendPage::finishClicked() { if (ui.m_launchPageOnFinish->isChecked() && !reportUrl.isEmpty()) { QDesktopServices::openUrl(QUrl(reportUrl)); } if (ui.m_restartAppOnFinish->isChecked()) { DrKonqi::crashedApplication()->restart(); } } void BugzillaSendPage::openReportContents() { if (!m_contentsDialog) { QString report = reportInterface()->generateReportFullText(false) + QLatin1Char('\n') + i18nc("@info report to KDE bugtracker address","Report to %1", DrKonqi::crashedApplication()->bugReportAddress()); m_contentsDialog = new ReportInformationDialog(report); } m_contentsDialog->show(); m_contentsDialog->raise(); m_contentsDialog->activateWindow(); } //END BugzillaSendPage /* Dialog for Unhandled Bugzilla Errors */ /* The user can save the bugzilla html output to check the error and/or to report this as a DrKonqi bug */ //BEGIN UnhandledErrorDialog UnhandledErrorDialog::UnhandledErrorDialog(QWidget * parent, const QString & error, const QString & extendedMessage) : QDialog(parent) { setWindowTitle(i18nc("@title:window", "Unhandled Bugzilla Error")); setWindowModality(Qt::ApplicationModal); QPushButton* saveButton = new QPushButton(this); saveButton->setText(i18nc("@action:button save html to a file","Save to a file")); saveButton->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); connect(saveButton, &QPushButton::clicked, this, &UnhandledErrorDialog::saveErrorMessage); setAttribute(Qt::WA_DeleteOnClose); QTextBrowser * htmlView = new QTextBrowser(this); QLabel * iconLabel = new QLabel(this); iconLabel->setFixedSize(32, 32); iconLabel->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(32, 32)); QLabel * mainLabel = new QLabel(this); mainLabel->setWordWrap(true); mainLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); QHBoxLayout * titleLayout = new QHBoxLayout(); titleLayout->setContentsMargins(5,2,5,2); titleLayout->setSpacing(5); titleLayout->addWidget(iconLabel); titleLayout->addWidget(mainLabel); QDialogButtonBox* buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QVBoxLayout * layout = new QVBoxLayout(this); layout->addLayout(titleLayout); layout->addWidget(htmlView); layout->addWidget(buttonBox); m_extendedHTMLError = extendedMessage; mainLabel->setText(i18nc("@label", "There was an unhandled Bugzilla error: %1.
" "Below is the HTML that DrKonqi received. " "Try to perform the action again or save this error page " "to submit a bug against DrKonqi.").arg(error)); htmlView->setHtml(extendedMessage); show(); } void UnhandledErrorDialog::saveErrorMessage() { QString defaultName = QLatin1String("drkonqi-unhandled-bugzilla-error.html"); QPointer dlg(new QFileDialog(this)); dlg->selectFile(defaultName); dlg->setWindowTitle(i18nc("@title:window","Select Filename")); dlg->setAcceptMode(QFileDialog::AcceptSave); dlg->setFileMode(QFileDialog::AnyFile); dlg->setConfirmOverwrite(true); if ( dlg->exec() == QDialog::Accepted ) { if (!dlg) { //Dialog closed externally (ex. via DBus) return; } QUrl fileUrl; if(!dlg->selectedUrls().isEmpty()) fileUrl = dlg->selectedUrls().first(); if (fileUrl.isValid()) { QTemporaryFile tf; if (tf.open()) { QTextStream ts(&tf); ts << m_extendedHTMLError; ts.flush(); } else { KMessageBox::sorry(this, xi18nc("@info","Cannot open file %1 " "for writing.", tf.fileName())); delete dlg; return; } KIO::FileCopyJob* job = KIO::file_copy(QUrl::fromLocalFile(tf.fileName()), fileUrl); KJobWidgets::setWindow(job, this); if (!job->exec()) { KMessageBox::sorry(this, job->errorString()); } } } delete dlg; } //END UnhandledErrorDialog diff --git a/src/bugzillaintegration/reportassistantpages_bugzilla.h b/src/bugzillaintegration/reportassistantpages_bugzilla.h index c20ada1d..b43cbf06 100644 --- a/src/bugzillaintegration/reportassistantpages_bugzilla.h +++ b/src/bugzillaintegration/reportassistantpages_bugzilla.h @@ -1,167 +1,166 @@ /******************************************************************* * reportassistantpages_bugzilla.h * Copyright 2009, 2011 Dario Andres Rodriguez * Copyright 2019 Harald Sitter * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #ifndef REPORTASSISTANTPAGES__BUGZILLA__H #define REPORTASSISTANTPAGES__BUGZILLA__H #include "reportassistantpage.h" #include "reportassistantpages_base.h" #include "ui_assistantpage_bugzilla_login.h" #include "ui_assistantpage_bugzilla_information.h" #include "ui_assistantpage_bugzilla_preview.h" #include "ui_assistantpage_bugzilla_send.h" namespace KWallet { class Wallet; } class KCapacityBar; /** Bugzilla login **/ class BugzillaLoginPage: public ReportAssistantPage { Q_OBJECT public: explicit BugzillaLoginPage(ReportAssistantDialog *); ~BugzillaLoginPage() override; void aboutToShow() override; bool isComplete() override; private Q_SLOTS: - void bugzillaVersionFound(); void loginClicked(); bool canLogin() const; void login(); void loginFinished(bool); void loginError(const QString &, const QString &); void walletLogin(); void updateLoginButtonStatus(); Q_SIGNALS: void loggedTurnToNextPage(); private: void updateWidget(bool enabled); bool kWalletEntryExists(const QString&); void openWallet(); Ui::AssistantPageBugzillaLogin ui; KWallet::Wallet * m_wallet; bool m_walletWasOpenedBefore; bool m_bugzillaVersionFound; }; /** Title and details page **/ class BugzillaInformationPage : public ReportAssistantPage { Q_OBJECT public: explicit BugzillaInformationPage(ReportAssistantDialog *); void aboutToShow() override; void aboutToHide() override; bool isComplete() override; bool showNextPage() override; private Q_SLOTS: void showTitleExamples(); void showDescriptionHelpExamples(); void checkTexts(); private: int currentDescriptionCharactersCount(); Ui::AssistantPageBugzillaInformation ui; KCapacityBar * m_textCompleteBar; bool m_textsOK; bool m_distributionComboSetup; bool m_distroComboVisible; int m_requiredCharacters; }; /** Preview report page **/ class BugzillaPreviewPage : public ReportAssistantPage { Q_OBJECT public: explicit BugzillaPreviewPage(ReportAssistantDialog *); void aboutToShow() override; void aboutToHide() override; private: Ui::AssistantPageBugzillaPreview ui; }; /** Send crash report page **/ class BugzillaSendPage : public ReportAssistantPage { Q_OBJECT public: explicit BugzillaSendPage(ReportAssistantDialog *); void aboutToShow() override; private Q_SLOTS: void sent(int); void sendError(const QString &, const QString &); void retryClicked(); void finishClicked(); void openReportContents(); private: Ui::AssistantPageBugzillaSend ui; QString reportUrl; QPointer m_contentsDialog; Q_SIGNALS: void finished(bool); }; class UnhandledErrorDialog: public QDialog { Q_OBJECT public: UnhandledErrorDialog(QWidget * parent, const QString &, const QString &); private Q_SLOTS: void saveErrorMessage(); private: QString m_extendedHTMLError; }; #endif diff --git a/src/bugzillaintegration/ui/assistantpage_bugzilla_version.ui b/src/bugzillaintegration/ui/assistantpage_bugzilla_version.ui new file mode 100644 index 00000000..a49d58e5 --- /dev/null +++ b/src/bugzillaintegration/ui/assistantpage_bugzilla_version.ui @@ -0,0 +1,158 @@ + + + BugzillaVersionPage + + + + 0 + 0 + 409 + 319 + + + + Form + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + 0 + + + -1 + + + + + + + Trying to contact bugs.kde.org... + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + //icon// + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + //Error// + + + Qt::AlignCenter + + + true + + + + + + + + + + 0 + 0 + + + + Retry + + + + .. + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + diff --git a/src/drkonqi_globals.h b/src/drkonqi_globals.h index 960af21c..5d402fde 100644 --- a/src/drkonqi_globals.h +++ b/src/drkonqi_globals.h @@ -1,54 +1,55 @@ /* Copyright (C) 2009 George Kiagiadakis This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DRKONQI_GLOBALS_H #define DRKONQI_GLOBALS_H #include #include #include "drkonqi.h" /** This class provides a custom constructor to fill the "toolTip" * and "whatsThis" texts of KGuiItem with the same text. */ class KGuiItem2 : public KGuiItem { public: inline KGuiItem2(const QString &text, const QIcon &icon, const QString &toolTip) : KGuiItem(text, icon, toolTip, toolTip) {} }; /* Urls are defined globally here, so that they can change easily */ #define KDE_BUGZILLA_URL DrKonqi::kdeBugzillaURL() #define KDE_BUGZILLA_CREATE_ACCOUNT_URL KDE_BUGZILLA_URL + QStringLiteral("createaccount.cgi") #define KDE_BUGZILLA_SHORT_URL "bugs.kde.org" #define TECHBASE_HOWTO_DOC "https://community.kde.org/Guidelines_and_HOWTOs/Debugging/How_to_create_useful_crash_reports#Preparing_your_KDE_packages" /* IDs for bugreport assistant pages -> help anchors */ #define PAGE_INTRODUCTION_ID "IntroductionID" #define PAGE_CRASHINFORMATION_ID "BacktraceID" #define PAGE_AWARENESS_ID "AwarenessID" #define PAGE_CONCLUSIONS_ID "ConclusionsID" +#define PAGE_BZVERSION_ID "BugzillaVersionID" #define PAGE_BZLOGIN_ID "BugzillaLoginID" #define PAGE_BZDUPLICATES_ID "BugzillaDuplicatesID" #define PAGE_BZDETAILS_ID "BugzillaDetailsID" #define PAGE_BZPREVIEW_ID "BugzillaPreviewID" #define PAGE_BZSEND_ID "BugzillaSendID" #define PAGE_HELP_BEGIN_ID "Begin" #endif