diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cdae1d49..4448e8b7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,116 +1,119 @@ 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 ) if (HAVE_X11) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras) endif() add_definitions(-DKDE_DEFAULT_DEBUG_AREA=1410) add_subdirectory( data ) add_subdirectory( parser ) -add_subdirectory( tests ) if ( WIN32 ) add_subdirectory( kdbgwin ) endif () set(drkonqi_SRCS main.cpp 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 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/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 # Requires kxmlrpcclient 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 ) add_executable(drkonqi ${drkonqi_SRCS}) ecm_mark_nongui_executable(drkonqi) target_compile_definitions(drkonqi PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") target_link_libraries(drkonqi KF5::I18n KF5::CoreAddons KF5::Service KF5::ConfigWidgets KF5::JobWidgets KF5::KIOCore KF5::Crash KF5::Completion Qt5::DBus KF5::XmlRpcClient KF5::WidgetsAddons KF5::Wallet KF5::Notifications # for status notifier KF5::IdleTime # hide status notifier only if user saw it drkonqi_backtrace_parser ) if (HAVE_X11) target_link_libraries(drkonqi Qt5::X11Extras ) endif() install(TARGETS drkonqi DESTINATION ${KDE_INSTALL_LIBEXECDIR}) + +# Only go into tests once we have a drkonqi target so the tests can reference +# it. +add_subdirectory( tests ) diff --git a/src/aboutbugreportingdialog.cpp b/src/aboutbugreportingdialog.cpp index edbdf83b..625f512b 100644 --- a/src/aboutbugreportingdialog.cpp +++ b/src/aboutbugreportingdialog.cpp @@ -1,233 +1,233 @@ /******************************************************************* * aboutbugreportingdialog.cpp * Copyright 2009 Dario Andres Rodriguez * Copyright 2009 A. L. Spehr * * 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 "aboutbugreportingdialog.h" #include #include #include #include #include #include #include #include #include #include "drkonqi_globals.h" AboutBugReportingDialog::AboutBugReportingDialog(QWidget * parent): QDialog(parent) { setAttribute(Qt::WA_DeleteOnClose, true); setWindowIcon(QIcon::fromTheme(QStringLiteral("help-hint"))); setWindowTitle(i18nc("@title title of the dialog", "About Bug Reporting - Help")); QVBoxLayout* layout = new QVBoxLayout(this); setLayout(layout); m_textBrowser = new QTextBrowser(this); m_textBrowser->setMinimumSize(QSize(600, 300)); connect(m_textBrowser, &QTextBrowser::anchorClicked, this, &AboutBugReportingDialog::handleInternalLinks); QString text = //Introduction QStringLiteral("

%2

").arg(QLatin1String(PAGE_HELP_BEGIN_ID), i18nc("@title","Information about bug reporting")) + QStringLiteral("

%1

%2

%3

").arg( xi18nc("@info/rich", "You can help us improve this software by filing a bug report."), xi18nc("@info/rich","It is safe to close this dialog. If you do not " "want to, you do not have to file a bug report."), xi18nc("@info/rich","In order to generate a useful bug report we need some " "information about both the crash and your system. (You may also " "need to install some debug packages.)")) + //Sub-introduction QStringLiteral("

%1

").arg(i18nc("@title","Bug Reporting Assistant Guide")) + QStringLiteral("

%1

").arg( xi18nc("@info/rich","This assistant will guide you through the crash " "reporting process for the KDE bug tracking system. All the " "information you enter on the bug report must be written " "in English, if possible, as KDE is formed internationally.")) + //Bug Awareness Page QStringLiteral("

%2

").arg(QLatin1String(PAGE_AWARENESS_ID), i18nc("@title","What do you know about the crash?")) + QStringLiteral("

%1

%2

  • %3
  • %4
  • %5
  • %6
  • %7
  • %8
  • " "
%9

").arg( xi18nc("@info/rich","In this page you need to describe how much do you know about " "the desktop and the application state before it crashed."), xi18nc("@info/rich","If you can, describe in as much detail as possible the crash " "circumstances, and what you were doing when the application crashed " "(this information is going to be requested later.) You can mention: "), xi18nc("@info/rich crash situation example","actions you were taking inside or outside " "the application"), xi18nc("@info/rich crash situation example","documents or images that you were using " "and their type/format (later if you go to look at the report in the " "bug tracking system, you can attach a file to the report)"), xi18nc("@info/rich crash situation example","widgets that you were running"), xi18nc("@info/rich crash situation example","the URL of a web site you were browsing"), xi18nc("@info/rich crash situation example","configuration details of the application"), xi18nc("@info/rich crash situation example","or other strange things you notice before " "or after the crash. "), xi18nc("@info/rich","Screenshots can be very helpful sometimes. You can attach them to " "the bug report after it is posted to the bug tracking system.")) + //Crash Information Page QStringLiteral("

%2

").arg(QLatin1String(PAGE_CRASHINFORMATION_ID), i18nc("@title","Crash Information (backtrace)")) + QStringLiteral("

%1

%2

%3

%4

").arg( xi18nc("@info/rich","This page will generate a \"backtrace\" of the crash. This " "is information that tells the developers where the application " "crashed."), xi18nc("@info/rich", "If the crash information is not detailed enough, you may " "need to install some debug packages and reload it (if the " "Install Debug Symbols button is available you " "can use it to automatically install the missing information.)"), xi18nc("@info/rich", "You can find more information about backtraces, what they mean, " "and how they are useful at %1",QStringLiteral(TECHBASE_HOWTO_DOC) ), xi18nc("@info/rich","Once you get a useful backtrace (or if you do not want to " "install the missing debugging packages) you can continue.")) + //Conclusions Page QStringLiteral("

%2

").arg(QLatin1String(PAGE_CONCLUSIONS_ID), i18nc("@title","Conclusions")) + QStringLiteral("

%1

%2

%3

").arg( xi18nc("@info/rich","Using the quality of the crash information gathered, " "and your answers on the previous page, the assistant will " "tell you if the crash is worth reporting or not."), xi18nc("@info/rich","If the crash is worth reporting but the application " "is not supported in the KDE bug tracking system, you " "will need to directly contact the maintainer of the application."), xi18nc("@info/rich","If the crash is listed as being not worth reporting, " "and you think the assistant has made a mistake, " "you can still manually report the bug by logging into the " "bug tracking system. You can also go back and change information " "and download debug packages.")) + //Bugzilla Login Page QStringLiteral("

%2

").arg(QLatin1String(PAGE_BZLOGIN_ID), i18nc("@title","Login into the bug tracking system")) + QStringLiteral("

%1

%2

%3

").arg( xi18nc("@info/rich","We may need to contact you in the future to ask for " "further information. As we need to keep track of the bug reports, " "you " "need to have an account on the KDE bug tracking system. If you do " "not have one, you can create one here: %1", - QStringLiteral(KDE_BUGZILLA_CREATE_ACCOUNT_URL)), + KDE_BUGZILLA_CREATE_ACCOUNT_URL), xi18nc("@info/rich","Then, enter your username and password and " "press the Login button. You can use this login to directly access the " "KDE bug tracking system later."), xi18nc("@info/rich","The KWallet dialog may appear when pressing Login to " "save your password in the KWallet password system. Also, it will " "prompt you for the KWallet password upon loading to autocomplete " "the login fields if you use this assistant again.")) + //Bugzilla Duplicates Page QStringLiteral("

%2

").arg(QLatin1String(PAGE_BZDUPLICATES_ID), i18nc("@title","List of possible duplicate reports")) + QStringLiteral("

%1

%2

%3

%4

%5

").arg( //needs some more string cleanup below xi18nc("@info/rich","This page will search the bug report system for " "similar crashes which are possible duplicates of your bug. If " "there are similar bug reports found, you can double click on them " "to see details. Then, read the current bug report information so " "you can check to see if they are similar. "), xi18nc("@info/rich","If you are very sure your bug is the same as another that is " "previously reported, you can set your information to be attached to " "the existing report."), xi18nc("@info/rich","If you are unsure whether your report is the same, follow the main " "options to tentatively mark your crash as a duplicate of that " "report. This is usually the safest thing to do. We cannot " "uncombine bug reports, but we can easily merge them."), xi18nc("@info/rich","If not enough possible duplicates are found, or you " "did not find a similar report, then you can force it to search " "for more bug reports (only if the date range limit is not reached.)"), xi18nc("@info/rich","If you do not find any related reports, your crash information " "is not useful enough, and you really cannot give additional " "information about the crash context, then it is better to " "not file the bug report, thereby closing the assistant.")) + //Bugzilla Crash Information - Details Page QStringLiteral("

%2

").arg(QLatin1String(PAGE_BZDETAILS_ID), i18nc("@title","Details of the bug report and your system")) + QStringLiteral("

%1%3

%4

%5

").arg( xi18nc("@info/rich","In this case you need to write a title and description " "of the crash. Explain as best you can. "), QLatin1String(PAGE_AWARENESS_ID), i18nc("@title","What do you know about the crash?"), xi18nc("@info/rich", "You can also specify your distribution method (GNU/Linux " "distribution or packaging system) or if you compiled the KDE " "Platform from sources."), xi18nc("@info/rich", "You should write those information in English.")) + //Bugzilla Send Page QStringLiteral("

%2

").arg(QLatin1String(PAGE_BZSEND_ID), i18nc("@title","Sending the Crash Report")) + QStringLiteral("

%1

%2

").arg( xi18nc("@info/rich","The last page will send the bug report to the bug tracking " "system and will notify you when it is done. It will then show " "the web address of the bug report in the KDE bug tracking system, " "so that you can look at the report later."), xi18nc("@info/rich","If the process fails, you can click " "Retry to try sending the bug report again. " "If the report cannot be sent because the bug tracking system has a " "problem, you can save it to a file to manually report later.")) + QStringLiteral("

%1

%2

").arg( xi18nc("@info/rich", "Thank you for being part of KDE!"), xi18nc("@info/rich", "If you are interested in helping us to keep the KDE bug tracker system " "clean and useful, which allows the developers to be more focused on " "fixing the real issues, you are welcome to " "join the BugSquad team.")); m_textBrowser->setText(text); layout->addWidget(m_textBrowser); QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Close, this); connect(box->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &AboutBugReportingDialog::close); layout->addWidget(box); KConfigGroup config(KSharedConfig::openConfig(), "AboutBugReportingDialog"); KWindowConfig::restoreWindowSize(windowHandle(), config); } AboutBugReportingDialog::~AboutBugReportingDialog( ) { KConfigGroup config(KSharedConfig::openConfig(), "AboutBugReportingDialog"); KWindowConfig::saveWindowSize(windowHandle(), config); } void AboutBugReportingDialog::handleInternalLinks(const QUrl& url) { if (!url.isEmpty()) { if (url.scheme().isEmpty() && url.hasFragment()) { showSection(url.fragment()); } else { QDesktopServices::openUrl(url); } } } void AboutBugReportingDialog::showSection(const QString& anchor) { m_textBrowser->scrollToAnchor(anchor); } diff --git a/src/bugreportaddress.h b/src/bugreportaddress.h index c64331c2..a5926423 100644 --- a/src/bugreportaddress.h +++ b/src/bugreportaddress.h @@ -1,44 +1,43 @@ /* 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 BUGREPORTADDRESS_H #define BUGREPORTADDRESS_H #include #include "drkonqi_globals.h" class BugReportAddress : public QString { public: inline BugReportAddress() : QString() {} inline BugReportAddress(const QString & address) - : QString(address == QLatin1String("submit@bugs.kde.org") ? - QLatin1String(KDE_BUGZILLA_URL) : address) + : QString(address == QLatin1String("submit@bugs.kde.org") ? KDE_BUGZILLA_URL : address) {} inline bool isKdeBugzilla() const { - return *this == QLatin1String(KDE_BUGZILLA_URL); + return *this == KDE_BUGZILLA_URL; } inline bool isEmail() const { return contains('@'); } }; #endif diff --git a/src/bugzillaintegration/reportassistantpages_base.cpp b/src/bugzillaintegration/reportassistantpages_base.cpp index ad48e90e..470481dd 100644 --- a/src/bugzillaintegration/reportassistantpages_base.cpp +++ b/src/bugzillaintegration/reportassistantpages_base.cpp @@ -1,463 +1,469 @@ /******************************************************************* * reportassistantpages_base.cpp * Copyright 2009 Dario Andres Rodriguez * Copyright 2009 A. L. Spehr * * 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_base.h" #include #include #include #include #include #include #include #include #include "drkonqi.h" #include "debuggermanager.h" #include "crashedapplication.h" #include "reportinterface.h" #include "parser/backtraceparser.h" #include "backtracegenerator.h" #include "backtracewidget.h" #include "drkonqi_globals.h" #include "applicationdetailsexamples.h" //BEGIN IntroductionPage IntroductionPage::IntroductionPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent) { ui.setupUi(this); ui.m_warningIcon->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(64,64)); } //END IntroductionPage //BEGIN CrashInformationPage CrashInformationPage::CrashInformationPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent) { m_backtraceWidget = new BacktraceWidget(DrKonqi::debuggerManager()->backtraceGenerator(), this, true); connect(m_backtraceWidget, &BacktraceWidget::stateChanged, this, &CrashInformationPage::emitCompleteChanged); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0,0,0,0); layout->addWidget(m_backtraceWidget); layout->addSpacing(10); //We need this for better usability until we get something better //If the backtrace was already fetched on the main dialog, save it. BacktraceGenerator *btGenerator = DrKonqi::debuggerManager()->backtraceGenerator(); if (btGenerator->state() == BacktraceGenerator::Loaded) { BacktraceParser::Usefulness use = btGenerator->parser()->backtraceUsefulness(); if (use != BacktraceParser::Useless && use != BacktraceParser::InvalidUsefulness) { reportInterface()->setBacktrace(btGenerator->backtrace()); } } } void CrashInformationPage::aboutToShow() { m_backtraceWidget->generateBacktrace(); m_backtraceWidget->hilightExtraDetailsLabel(false); emitCompleteChanged(); } void CrashInformationPage::aboutToHide() { BacktraceGenerator *btGenerator = DrKonqi::debuggerManager()->backtraceGenerator(); BacktraceParser::Usefulness use = btGenerator->parser()->backtraceUsefulness(); if (use != BacktraceParser::Useless && use != BacktraceParser::InvalidUsefulness) { reportInterface()->setBacktrace(btGenerator->backtrace()); } reportInterface()->setFirstBacktraceFunctions(btGenerator->parser()->firstValidFunctions()); } bool CrashInformationPage::isComplete() { BacktraceGenerator *generator = DrKonqi::debuggerManager()->backtraceGenerator(); return (generator->state() != BacktraceGenerator::NotLoaded && generator->state() != BacktraceGenerator::Loading); } bool CrashInformationPage::showNextPage() { BacktraceParser::Usefulness use = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness(); + if (DrKonqi::ignoreQuality()) { + return true; + } + if ((use == BacktraceParser::InvalidUsefulness || use == BacktraceParser::ProbablyUseless || use == BacktraceParser::Useless) && m_backtraceWidget->canInstallDebugPackages()) { if ( KMessageBox::Yes == KMessageBox::questionYesNo(this, i18nc("@info","This crash information is not useful enough, " "do you want to try to improve it? You will need " "to install some debugging packages."), i18nc("@title:window","Crash Information is not useful enough")) ) { m_backtraceWidget->hilightExtraDetailsLabel(true); m_backtraceWidget->focusImproveBacktraceButton(); return false; //Cancel show next, to allow the user to write more } else { return true; //Allow to continue } } else { return true; } } //END CrashInformationPage //BEGIN BugAwarenessPage BugAwarenessPage::BugAwarenessPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent) { ui.setupUi(this); ui.m_actionsInsideApp->setText(i18nc("@option:check kind of information the user can provide " "about the crash, %1 is the application name", "What I was doing when the application \"%1\" crashed", DrKonqi::crashedApplication()->name())); connect(ui.m_rememberGroup, static_cast(&QButtonGroup::buttonClicked), this, &BugAwarenessPage::updateCheckBoxes); + // Also listen to toggle so radio buttons are covered. + connect(ui.m_rememberGroup, static_cast(&QButtonGroup::buttonToggled), this, &BugAwarenessPage::updateCheckBoxes); ui.m_appSpecificDetailsExamples->setVisible(reportInterface()->appDetailsExamples()->hasExamples()); ui.m_appSpecificDetailsExamples->setContextMenuPolicy(Qt::NoContextMenu); connect(ui.m_appSpecificDetailsExamples, &QLabel::linkActivated, this, &BugAwarenessPage::showApplicationDetailsExamples); } void BugAwarenessPage::aboutToShow() { updateCheckBoxes(); } void BugAwarenessPage::aboutToHide() { //Save data ReportInterface::Reproducible reproducible = ReportInterface::ReproducibleUnsure; switch(ui.m_reproducibleBox->currentIndex()) { case 0: { reproducible = ReportInterface::ReproducibleUnsure; break; } case 1: { reproducible = ReportInterface::ReproducibleNever; break; } case 2: { reproducible = ReportInterface::ReproducibleSometimes; break; } case 3: { reproducible = ReportInterface::ReproducibleEverytime; break; } } reportInterface()->setBugAwarenessPageData(ui.m_rememberCrashSituationYes->isChecked(), reproducible, ui.m_actionsInsideApp->isChecked(), ui.m_unusualSituation->isChecked(), ui.m_appSpecificDetails->isChecked()); } void BugAwarenessPage::updateCheckBoxes() { const bool rememberSituation = ui.m_rememberCrashSituationYes->isChecked(); ui.m_reproducibleLabel->setEnabled(rememberSituation); ui.m_reproducibleBox->setEnabled(rememberSituation); ui.m_informationLabel->setEnabled(rememberSituation); ui.m_actionsInsideApp->setEnabled(rememberSituation); ui.m_unusualSituation->setEnabled(rememberSituation); ui.m_appSpecificDetails->setEnabled(rememberSituation); ui.m_appSpecificDetailsExamples->setEnabled(rememberSituation); } void BugAwarenessPage::showApplicationDetailsExamples() { QToolTip::showText(QCursor::pos(), i18nc("@label examples about information the user can provide", "Examples: %1", reportInterface()->appDetailsExamples()->examples()), this); } //END BugAwarenessPage //BEGIN ConclusionPage ConclusionPage::ConclusionPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent) , m_needToReport(false) { m_isBKO = DrKonqi::crashedApplication()->bugReportAddress().isKdeBugzilla(); ui.setupUi(this); KGuiItem::assign(ui.m_showReportInformationButton, KGuiItem2(i18nc("@action:button", "&Show 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_showReportInformationButton, &QPushButton::clicked, this, &ConclusionPage::openReportInformation); ui.m_restartAppOnFinish->setVisible(false); } void ConclusionPage::finishClicked() { //Manual report if (m_needToReport && !m_isBKO) { const CrashedApplication *crashedApp = DrKonqi::crashedApplication(); BugReportAddress reportAddress = crashedApp->bugReportAddress(); QString report = reportInterface()->generateReportFullText(false); if (reportAddress.isEmail()) { QString subject = QStringLiteral("[%1] [%2] Automatic crash report generated by DrKonqi"); subject= subject.arg(crashedApp->name()); subject= subject.arg(crashedApp->datetime().toString(QStringLiteral("yyyy-MM-dd"))); KToolInvocation::invokeMailer(reportAddress, QLatin1String(""), QLatin1String("") , subject, report); } else { QUrl url(reportAddress); if (QUrl(reportAddress).isRelative()) { //Scheme is missing url = QUrl(QString::fromLatin1("http://%1").arg(reportAddress)); } QDesktopServices::openUrl(url); } //Show a copy of the bug reported openReportInformation(); } //Restart application if (ui.m_restartAppOnFinish->isChecked()) { DrKonqi::crashedApplication()->restart(); } } void ConclusionPage::aboutToShow() { connect(assistant(), SIGNAL(user1Clicked()), this, SLOT(finishClicked())); ui.m_restartAppOnFinish->setVisible(false); ui.m_restartAppOnFinish->setChecked(false); const bool isDuplicate = reportInterface()->duplicateId() && !reportInterface()->attachToBugNumber(); m_needToReport = reportInterface()->isWorthReporting() && !isDuplicate; emitCompleteChanged(); BugReportAddress reportAddress = DrKonqi::crashedApplication()->bugReportAddress(); BacktraceParser::Usefulness use = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness(); QString explanationHTML = QLatin1String("

    "); bool backtraceGenerated = true; switch (use) { case BacktraceParser::ReallyUseful: { explanationHTML += QStringLiteral("
  • %1
  • ").arg(i18nc("@info","The automatically generated " "crash information is useful.")); break; } case BacktraceParser::MayBeUseful: { explanationHTML += QStringLiteral("
  • %1
  • ").arg(i18nc("@info","The automatically generated " "crash information lacks some " "details " "but may be still be useful.")); break; } case BacktraceParser::ProbablyUseless: { explanationHTML += QStringLiteral("
  • %1
  • ").arg(i18nc("@info","The automatically generated " "crash information lacks important details " "and it is probably not helpful.")); break; } case BacktraceParser::Useless: case BacktraceParser::InvalidUsefulness: { BacktraceGenerator::State state = DrKonqi::debuggerManager()->backtraceGenerator()->state(); if (state == BacktraceGenerator::NotLoaded) { backtraceGenerated = false; explanationHTML += QStringLiteral("
  • %1
  • ").arg(i18nc("@info","The crash information was " "not generated because it was not needed.")); } else { explanationHTML += QStringLiteral("
  • %1
    %2
  • ").arg( i18nc("@info","The automatically generated crash " "information does not contain enough information to be " "helpful."), xi18nc("@info","You can improve it by " "installing debugging packages and reloading the crash on " "the Crash Information page. You can get help with the Bug " "Reporting Guide by clicking on the " "Help button.")); //but this guide doesn't mention bt packages? that's techbase - //->>and the help guide mention techbase page... + //->>and the help guide mention techbase page... } break; } } //User can provide enough information if (reportInterface()->isBugAwarenessPageDataUseful()) { explanationHTML += QStringLiteral("
  • %1
  • ").arg(i18nc("@info","The information you can " "provide could be considered helpful.")); } else { explanationHTML += QStringLiteral("
  • %1
  • ").arg(i18nc("@info","The information you can " "provide is not considered helpful enough in this case.")); } if (isDuplicate) { explanationHTML += QStringLiteral("
  • %1
  • ").arg(xi18nc("@info","Your problem has already been " "reported as bug %1.", QString::number(reportInterface()->duplicateId()))); } explanationHTML += QLatin1String("

"); ui.m_explanationLabel->setText(explanationHTML); //Hide the "Show contents of the report" button if the backtrace was not generated ui.m_showReportInformationButton->setVisible(backtraceGenerated); if (m_needToReport) { ui.m_conclusionsLabel->setText(QStringLiteral("

%1").arg(i18nc("@info","This " "report is considered helpful."))); if (m_isBKO) { emitCompleteChanged(); ui.m_howToProceedLabel->setText(xi18nc("@info","This application's bugs are reported " "to the KDE bug tracking system: click Next" " to start the reporting process. " "You can manually report at %1", reportAddress)); } else { if (!DrKonqi::crashedApplication()->hasBeenRestarted()) { ui.m_restartAppOnFinish->setVisible(true); } ui.m_howToProceedLabel->setText(xi18nc("@info","This application is not supported in the " "KDE bug tracking system. Click " "Finish to report this bug to " "the application maintainer. Also, you can manually " "report at %1.", reportAddress)); emit finished(false); } } else { // (m_needToReport) if (!DrKonqi::crashedApplication()->hasBeenRestarted()) { ui.m_restartAppOnFinish->setVisible(true); } ui.m_conclusionsLabel->setText(QStringLiteral("

%1
%2

").arg( i18nc("@info","This report does not contain enough information for the " "developers, so the automated bug reporting process is not " "enabled for this crash."), i18nc("@info","If you wish, you can go back and change your " "answers. "))); //Only mention "manual reporting" if the backtrace was generated. //FIXME separate the texts "manual reporting" / "click finish to close" //"manual reporting" should be ~"manual report using the contents of the report".... //FIXME for 4.5 (workflow, see ToDo) if (backtraceGenerated) { if (m_isBKO) { ui.m_howToProceedLabel->setText(xi18nc("@info","You can manually report this bug " "at %1. " "Click Finish to close the " "assistant.", reportAddress)); } else { ui.m_howToProceedLabel->setText(xi18nc("@info","You can manually report this " "bug to its maintainer at %1. " "Click Finish to close the " "assistant.", reportAddress)); } } emit finished(true); } } void ConclusionPage::aboutToHide() { assistant()->disconnect(SIGNAL(user1Clicked()), this, SLOT(finishClicked())); } void ConclusionPage::openReportInformation() { if (!m_infoDialog) { QString info = reportInterface()->generateReportFullText(false) + QLatin1Char('\n') + i18nc("@info report to url/mail address","Report to %1", DrKonqi::crashedApplication()->bugReportAddress()); m_infoDialog = new ReportInformationDialog(info); } m_infoDialog->show(); m_infoDialog->raise(); m_infoDialog->activateWindow(); } bool ConclusionPage::isComplete() { return (m_isBKO && m_needToReport); } //END ConclusionPage //BEGIN ReportInformationDialog ReportInformationDialog::ReportInformationDialog(const QString & reportText) : QDialog() { setAttribute(Qt::WA_DeleteOnClose, true); setWindowTitle(i18nc("@title:window","Contents of the Report")); ui.setupUi(this); ui.m_reportInformationBrowser->setPlainText(reportText); QPushButton* saveButton = new QPushButton(ui.buttonBox); KGuiItem::assign(saveButton, KGuiItem2(i18nc("@action:button", "&Save to File..."), QIcon::fromTheme(QStringLiteral("document-save")), i18nc("@info:tooltip", "Use this button to save the " "generated crash report information to " "a file. You can use this option to report the " "bug later."))); connect(saveButton, &QPushButton::clicked, this, &ReportInformationDialog::saveReport); ui.buttonBox->addButton(saveButton, QDialogButtonBox::ActionRole); resize(QSize(800, 600)); KConfigGroup config(KSharedConfig::openConfig(), "ReportInformationDialog"); KWindowConfig::restoreWindowSize(windowHandle(), config); } ReportInformationDialog::~ReportInformationDialog() { KConfigGroup config(KSharedConfig::openConfig(), "ReportInformationDialog"); KWindowConfig::saveWindowSize(windowHandle(), config); } void ReportInformationDialog::saveReport() { DrKonqi::saveReport(ui.m_reportInformationBrowser->toPlainText(), this); } //END ReportInformationDialog diff --git a/src/bugzillaintegration/reportassistantpages_bugzilla.cpp b/src/bugzillaintegration/reportassistantpages_bugzilla.cpp index 9c40a837..d752718b 100644 --- a/src/bugzillaintegration/reportassistantpages_bugzilla.cpp +++ b/src/bugzillaintegration/reportassistantpages_bugzilla.cpp @@ -1,889 +1,889 @@ /******************************************************************* * reportassistantpages_bugzilla.cpp * Copyright 2009, 2010, 2011 Dario Andres Rodriguez * * 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 #include #include #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 const char konquerorKWalletEntryName[] = KDE_BUGZILLA_URL "index.cgi#login"; +static QString konquerorKWalletEntryName = KDE_BUGZILLA_URL + "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(0), m_walletWasOpenedBefore(false), m_bugzillaVersionFound(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 " "username 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, &KLineEdit::returnPressed, this, &BugzillaLoginPage::loginClicked); connect(ui.m_userEdit, &KLineEdit::textChanged, this, &BugzillaLoginPage::updateLoginButtonStatus); connect(ui.m_passwordEdit, &KLineEdit::textChanged, 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(), - QLatin1String(KDE_BUGZILLA_CREATE_ACCOUNT_URL))); + KDE_BUGZILLA_CREATE_ACCOUNT_URL)); } 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->text().isEmpty() && m_bugzillaVersionFound ); } 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->setText(password); } } - } else if (kWalletEntryExists(QLatin1String(konquerorKWalletEntryName))) { + } 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(QLatin1String(konquerorKWalletEntryName), 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->setText(password); } } } } } bool BugzillaLoginPage::canSetCookies() { if (bugzillaManager()->securityMethod() != BugzillaManager::UseCookies) { qDebug() << "Bugzilla software no longer issues cookies."; return false; } QDBusInterface kded(QLatin1String("org.kde.kded5"), QLatin1String("/kded"), QLatin1String("org.kde.kded5")); QDBusReply kcookiejarLoaded = kded.call(QLatin1String("loadModule"), QLatin1String("kcookiejar")); if (!kcookiejarLoaded.isValid()) { KMessageBox::error(this, i18n("Failed to communicate with kded. Make sure it is running.")); return false; } else if (!kcookiejarLoaded.value()) { KMessageBox::error(this, i18n("Failed to load KCookieServer. Check your KDE installation.")); return false; } QDBusInterface kcookiejar(QLatin1String("org.kde.kded5"), QLatin1String("/modules/kcookiejar"), QLatin1String("org.kde.KCookieServer")); QDBusReply advice = kcookiejar.call(QLatin1String("getDomainAdvice"), - QLatin1String(KDE_BUGZILLA_URL)); + KDE_BUGZILLA_URL); if (!advice.isValid()) { KMessageBox::error(this, i18n("Failed to communicate with KCookieServer.")); return false; } qDebug() << "Got reply from KCookieServer:" << advice.value(); if (advice.value() == QLatin1String("Reject")) { QString msg = i18nc("@info 1 is the bugzilla website url", "Cookies are not allowed in your KDE network settings. In order to " "proceed, you need to allow %1 to set cookies.", KDE_BUGZILLA_URL); KGuiItem yesItem = KStandardGuiItem::yes(); yesItem.setText(i18nc("@action:button 1 is the bugzilla website url", "Allow %1 to set cookies", KDE_BUGZILLA_URL)); KGuiItem noItem = KStandardGuiItem::no(); noItem.setText(i18nc("@action:button do not allow the bugzilla website " "to set cookies", "No, do not allow")); if (KMessageBox::warningYesNo(this, msg, QString(), yesItem, noItem) == KMessageBox::Yes) { - QDBusReply success = kcookiejar.call(QLatin1String("setDomainAdvice"), - QLatin1String(KDE_BUGZILLA_URL), - QLatin1String("Accept")); + QDBusReply success = kcookiejar.call(QStringLiteral("setDomainAdvice"), + KDE_BUGZILLA_URL, + QStringLiteral("Accept")); if (!success.isValid() || !success.value()) { qWarning() << "Failed to set domain advice in KCookieServer"; return false; } else { return true; } } else { return false; } } return true; } void BugzillaLoginPage::loginClicked() { if (!(ui.m_userEdit->text().isEmpty() || ui.m_passwordEdit->text().isEmpty())) { if ((bugzillaManager()->securityMethod() == BugzillaManager::UseCookies) && (!canSetCookies())) { return; } ui.m_loginButton->setEnabled(false); ui.m_userLabel->setEnabled(false); ui.m_passwordLabel->setEnabled(false); ui.m_userEdit->setEnabled(false); ui.m_passwordEdit->setEnabled(false); ui.m_savePasswordCheckBox->setEnabled(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->text()); 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)); } } } ui.m_statusWidget->setBusy(i18nc("@info:status '1' is a url, '2' the username", "Performing login at %1 as %2...", QLatin1String(KDE_BUGZILLA_SHORT_URL), ui.m_userEdit->text())); bugzillaManager()->tryLogin(ui.m_userEdit->text(), ui.m_passwordEdit->text()); } else { loginFinished(false); } } 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 username or " "password")); ui.m_loginButton->setEnabled(true); ui.m_userEdit->setEnabled(true); ui.m_passwordEdit->setEnabled(true); ui.m_savePasswordCheckBox->setEnabled(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"),"unspecified"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Archlinux"), "Archlinux Packages"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Chakra"), "Chakra"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Debian stable"), "Debian stable"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Debian testing"), "Debian testing"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Debian unstable"), "Debian unstable"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Exherbo"), "Exherbo Packages"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Fedora"), "Fedora RPMs"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Gentoo"), "Gentoo Packages"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Mageia"), "Mageia RPMs"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Mandriva"), "Mandriva RPMs"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Neon"), "Neon Packages"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "OpenSUSE"), "openSUSE RPMs"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Pardus"), "Pardus Packages"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "RedHat"), "RedHat RPMs"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Slackware"), "Slackware Packages"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Ubuntu (and derivatives)"), "Ubuntu Packages"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "FreeBSD (Ports)"), "FreeBSD Ports"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "NetBSD (pkgsrc)"), "NetBSD pkgsrc"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "OpenBSD"), "OpenBSD Packages"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Mac OS X"), "MacPorts Packages"); ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method", "Solaris"), "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('\n'); description.remove('-'); description.remove(':'); description.remove(' '); 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 += ' ' + 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 += ' ' + 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 += ' ' + 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 += "
" + 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 += "
" + 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 += "
" + i18nc("@info:tooltip help and examples of good bug descriptions", "- Note any non-default configuration in the application."); if (reportInterface()->appDetailsExamples()->hasExamples()) { descriptionHelp += ' ' + 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)); } //END BugzillaPreviewPage //BEGIN BugzillaSendPage BugzillaSendPage::BugzillaSendPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent), m_contentsDialog(0) { 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(), SIGNAL(user1Clicked()), this, SLOT(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)")); reportInterface()->sendBugReport(); } void BugzillaSendPage::sent(int bug_id) { 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(); layout->addLayout(titleLayout); layout->addWidget(htmlView); layout->addWidget(buttonBox); setLayout(layout); 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); setMinimumSize(QSize(550, 350)); resize(minimumSize()); 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_duplicates.cpp b/src/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp index a5bde782..de4f9a24 100644 --- a/src/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp +++ b/src/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp @@ -1,997 +1,1002 @@ /******************************************************************* * reportassistantpages_bugzilla_duplicates.cpp * Copyright 2009 Dario Andres Rodriguez * * 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_duplicates.h" #include #include #include #include #include #include #include #include #include #include "drkonqi_globals.h" #include "reportinterface.h" #include "statuswidget.h" //BEGIN BugzillaDuplicatesPage BugzillaDuplicatesPage::BugzillaDuplicatesPage(ReportAssistantDialog * parent): ReportAssistantPage(parent), m_searching(false), m_foundDuplicate(false) { resetDates(); connect(bugzillaManager(), &BugzillaManager::searchFinished, this, &BugzillaDuplicatesPage::searchFinished); connect(bugzillaManager(), SIGNAL(searchError(QString)), this, SLOT(searchError(QString))); ui.setupUi(this); ui.information->hide(); connect(ui.m_bugListWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(itemClicked(QTreeWidgetItem*,int))); connect(ui.m_bugListWidget, &QTreeWidget::itemSelectionChanged, this, &BugzillaDuplicatesPage::itemSelectionChanged); QHeaderView * header = ui.m_bugListWidget->header(); header->setSectionResizeMode(0, QHeaderView::ResizeToContents); header->setSectionResizeMode(1, QHeaderView::Interactive); //Create manual bug report entry (first one) QTreeWidgetItem * customBugItem = new QTreeWidgetItem( QStringList() << i18nc("@item:intable custom/manaul bug report number", "Manual") << i18nc("@item:intable custom bug report number description", "Manually enter a bug report ID")); customBugItem->setData(0, Qt::UserRole, QLatin1String("custom")); customBugItem->setIcon(1, QIcon::fromTheme(QStringLiteral("edit-rename"))); QString helpMessage = i18nc("@info:tooltip / whatsthis", "Select this option to manually load a specific bug report"); customBugItem->setToolTip(0, helpMessage); customBugItem->setToolTip(1, helpMessage); customBugItem->setWhatsThis(0, helpMessage); customBugItem->setWhatsThis(1, helpMessage); ui.m_bugListWidget->addTopLevelItem(customBugItem); m_searchMoreGuiItem = KGuiItem2(i18nc("@action:button", "Search for more reports"), QIcon::fromTheme(QStringLiteral("edit-find")), i18nc("@info:tooltip", "Use this button to " "search for more similar bug reports on an " "earlier date.")); KGuiItem::assign(ui.m_searchMoreButton, m_searchMoreGuiItem); connect(ui.m_searchMoreButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::searchMore); m_retrySearchGuiItem = KGuiItem2(i18nc("@action:button", "Retry search"), QIcon::fromTheme(QStringLiteral("edit-find")), i18nc("@info:tooltip", "Use this button to " "retry the search that previously " "failed.")); KGuiItem::assign(ui.m_openReportButton, KGuiItem2(i18nc("@action:button", "Open selected report"), QIcon::fromTheme(QStringLiteral("document-preview")), i18nc("@info:tooltip", "Use this button to view " "the information of the selected bug report."))); connect(ui.m_openReportButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::openSelectedReport); KGuiItem::assign(ui.m_stopSearchButton, KGuiItem2(i18nc("@action:button", "Stop searching"), QIcon::fromTheme(QStringLiteral("process-stop")), i18nc("@info:tooltip", "Use this button to stop " "the current search."))); ui.m_stopSearchButton->setText(QString()); //FIXME connect(ui.m_stopSearchButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::stopCurrentSearch); //Possible duplicates list and buttons connect(ui.m_selectedDuplicatesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(itemClicked(QListWidgetItem*))); connect(ui.m_selectedDuplicatesList, &QListWidget::itemSelectionChanged, this, &BugzillaDuplicatesPage::possibleDuplicateSelectionChanged); KGuiItem::assign(ui.m_removeSelectedDuplicateButton, KGuiItem2(i18nc("@action:button remove the selected item from a list", "Remove"), QIcon::fromTheme(QStringLiteral("list-remove")), i18nc("@info:tooltip", "Use this button to remove a selected possible duplicate"))); ui.m_removeSelectedDuplicateButton->setEnabled(false); connect(ui.m_removeSelectedDuplicateButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::removeSelectedDuplicate); ui.m_attachToReportIcon->setPixmap(QIcon::fromTheme(QStringLiteral("mail-attachment")).pixmap(16,16)); ui.m_attachToReportIcon->setFixedSize(16,16); ui.m_attachToReportIcon->setVisible(false); ui.m_attachToReportLabel->setVisible(false); ui.m_attachToReportLabel->setContextMenuPolicy(Qt::NoContextMenu); connect(ui.m_attachToReportLabel, &QLabel::linkActivated, this, &BugzillaDuplicatesPage::cancelAttachToBugReport); ui.information->setContextMenuPolicy(Qt::NoContextMenu); connect(ui.information, &QLabel::linkActivated, this, &BugzillaDuplicatesPage::informationClicked); showDuplicatesPanel(false); } BugzillaDuplicatesPage::~BugzillaDuplicatesPage() { } void BugzillaDuplicatesPage::aboutToShow() { //Perform initial search if we are not currently searching and if there are no results yet if (!m_searching && ui.m_bugListWidget->topLevelItemCount() == 1 && canSearchMore()) { searchMore(); } } void BugzillaDuplicatesPage::aboutToHide() { stopCurrentSearch(); //Save selected possible duplicates by user QStringList possibleDuplicates; int count = ui.m_selectedDuplicatesList->count(); for(int i = 0; iitem(i)->text(); } reportInterface()->setPossibleDuplicates(possibleDuplicates); //Save possible duplicates by query QStringList duplicatesByQuery; count = ui.m_bugListWidget->topLevelItemCount(); for(int i = 1; itopLevelItem(i)->text(0); } reportInterface()->setPossibleDuplicatesByQuery(duplicatesByQuery); } bool BugzillaDuplicatesPage::isComplete() { return !m_searching; } bool BugzillaDuplicatesPage::showNextPage() { //Ask the user to check all the possible duplicates... if (ui.m_bugListWidget->topLevelItemCount() != 1 && ui.m_selectedDuplicatesList->count() == 0 && reportInterface()->attachToBugNumber() == 0 && !m_foundDuplicate) { //The user didn't selected any possible duplicate nor a report to attach the new info. //Double check this, we need to reduce the duplicate count. KGuiItem noDuplicatesButton; noDuplicatesButton.setText(i18n("There are no real duplicates")); noDuplicatesButton.setWhatsThis(i18n("Press this button to declare that, in your opinion " "and according to your experience, the reports found " "as similar do not match the crash you have " "experienced, and you believe it is unlikely that a " "better match would be found after further review.")); noDuplicatesButton.setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"))); KGuiItem letMeCheckMoreReportsButton; letMeCheckMoreReportsButton.setText(i18n("Let me check more reports")); letMeCheckMoreReportsButton.setWhatsThis(i18n("Press this button if you would rather " "review more reports in order to find a " "match for the crash you have experienced.")); letMeCheckMoreReportsButton.setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); if (KMessageBox::questionYesNo(this, i18nc("@info","You have not selected any possible duplicates, or a report to which to attach your " "crash information. Have you read all the reports, and can you confirm that there are no " "real duplicates?"), i18nc("@title:window","No selected possible duplicates"), letMeCheckMoreReportsButton, noDuplicatesButton) == KMessageBox::Yes) { return false; } } return true; } //BEGIN Search related methods void BugzillaDuplicatesPage::searchMore() { //1 year back m_searchingEndDate = m_startDate; m_searchingStartDate = m_searchingEndDate.addYears(-1); performSearch(); } void BugzillaDuplicatesPage::performSearch() { markAsSearching(true); QString startDateStr = m_searchingStartDate.toString(QStringLiteral("yyyy-MM-dd")); QString endDateStr = m_searchingEndDate.toString(QStringLiteral("yyyy-MM-dd")); ui.m_statusWidget->setBusy(i18nc("@info:status","Searching for duplicates (from %1 to %2)...", startDateStr, endDateStr)); //Bugzilla will not search on Today bugs if we send the date. //we need to send "Now" if (m_searchingEndDate == QDate::currentDate()) { endDateStr = QLatin1String("Now"); } #if 1 BugReport report = reportInterface()->newBugReportTemplate(); bugzillaManager()->searchBugs(reportInterface()->relatedBugzillaProducts(), report.bugSeverity(), startDateStr, endDateStr, reportInterface()->firstBacktraceFunctions().join(QStringLiteral(" "))); #else //Test search bugzillaManager()->searchBugs(QStringList() << "plasma", "crash", startDateStr, endDateStr, "QGraphicsScenePrivate::processDirtyItemsRecursive"); #endif } void BugzillaDuplicatesPage::stopCurrentSearch() { if (m_searching) { bugzillaManager()->stopCurrentSearch(); markAsSearching(false); if (m_startDate==m_endDate) { //Never searched ui.m_statusWidget->setIdle(i18nc("@info:status","Search stopped.")); } else { ui.m_statusWidget->setIdle(i18nc("@info:status","Search stopped. Showing results from " "%1 to %2", m_startDate.toString(QStringLiteral("yyyy-MM-dd")), m_endDate.toString(QStringLiteral("yyyy-MM-dd")))); } } } void BugzillaDuplicatesPage::markAsSearching(bool searching) { m_searching = searching; emitCompleteChanged(); ui.m_bugListWidget->setEnabled(!searching); ui.m_searchMoreButton->setEnabled(!searching); ui.m_searchMoreButton->setVisible(!searching); ui.m_stopSearchButton->setEnabled(searching); ui.m_stopSearchButton->setVisible(searching); ui.m_selectedDuplicatesList->setEnabled(!searching); ui.m_selectedPossibleDuplicatesLabel->setEnabled(!searching); ui.m_removeSelectedDuplicateButton->setEnabled(!searching && !ui.m_selectedDuplicatesList->selectedItems().isEmpty()); ui.m_attachToReportLabel->setEnabled(!searching); if (!searching) { itemSelectionChanged(); } else { ui.m_openReportButton->setEnabled(false); } } bool BugzillaDuplicatesPage::canSearchMore() { return (m_startDate.year() >= 2009); } void BugzillaDuplicatesPage::searchFinished(const BugMapList & list) { KGuiItem::assign(ui.m_searchMoreButton, m_searchMoreGuiItem); m_startDate = m_searchingStartDate; int results = list.count(); if (results > 0) { markAsSearching(false); ui.m_statusWidget->setIdle(i18nc("@info:status","Showing results from %1 to %2", m_startDate.toString(QStringLiteral("yyyy-MM-dd")), m_endDate.toString(QStringLiteral("yyyy-MM-dd")))); QList bugIds; for (int i = 0; i < results; i++) { BugMap bug = list.at(i); bool ok; int bugId = bug.value(QStringLiteral("bug_id")).toInt(&ok); if (ok) { bugIds << bugId; } QString title; //Generate a non-geek readable status QString customStatusString; BugReport::Status status = BugReport::parseStatus(bug.value(QStringLiteral("bug_status"))); BugReport::Resolution resolution = BugReport::parseResolution(bug.value(QStringLiteral("resolution"))); if (BugReport::isOpen(status)) { customStatusString = i18nc("@info bug status", "[Open]"); } else if (BugReport::isClosed(status) && status != BugReport::NeedsInfo) { if (resolution == BugReport::Fixed) { customStatusString = i18nc("@info bug resolution", "[Fixed]"); } else if (resolution == BugReport::WorksForMe) { customStatusString = i18nc("@info bug resolution", "[Non-reproducible]"); } else if (resolution == BugReport::Duplicate) { customStatusString = i18nc("@info bug resolution", "[Duplicate report]"); } else if (resolution == BugReport::Invalid) { customStatusString = i18nc("@info bug resolution", "[Invalid]"); } else if (resolution == BugReport::Downstream || resolution == BugReport::Upstream) { customStatusString = i18nc("@info bug resolution", "[External problem]"); } } else if (status == BugReport::NeedsInfo) { customStatusString = i18nc("@info bug status", "[Incomplete]"); } title = customStatusString + ' ' + bug[QStringLiteral("short_desc")]; QStringList fields = QStringList() << bug[QStringLiteral("bug_id")] << title; QTreeWidgetItem * item = new QTreeWidgetItem(fields); item->setToolTip(0, bug[QStringLiteral("short_desc")]); item->setToolTip(1, bug[QStringLiteral("short_desc")]); ui.m_bugListWidget->addTopLevelItem(item); } if (!m_foundDuplicate) { markAsSearching(true); DuplicateFinderJob *job = new DuplicateFinderJob(bugIds, bugzillaManager(), this); connect(job, SIGNAL(result(KJob*)), this, SLOT(analyzedDuplicates(KJob*))); job->start(); } ui.m_bugListWidget->sortItems(0 , Qt::DescendingOrder); ui.m_bugListWidget->resizeColumnToContents(1); if (!canSearchMore()) { ui.m_searchMoreButton->setEnabled(false); } } else { if (canSearchMore()) { //We don't call markAsSearching(false) to avoid flicker //Delayed call to searchMore to avoid unexpected behaviour (signal/slot) //because we are in a slot, and searchMore() will be ending calling this slot again QTimer::singleShot(0, this, &BugzillaDuplicatesPage::searchMore); } else { markAsSearching(false); ui.m_statusWidget->setIdle(i18nc("@info:status","Search Finished. " "No reports found.")); ui.m_searchMoreButton->setEnabled(false); if (ui.m_bugListWidget->topLevelItemCount() == 0) { //No reports to mark as possible duplicate ui.m_selectedDuplicatesList->setEnabled(false); } } } } void BugzillaDuplicatesPage::analyzedDuplicates(KJob *j) { markAsSearching(false); DuplicateFinderJob *job = static_cast(j); m_result = job->result(); m_foundDuplicate = m_result.parentDuplicate; reportInterface()->setDuplicateId(m_result.parentDuplicate); ui.m_searchMoreButton->setEnabled(!m_foundDuplicate); ui.information->setVisible(m_foundDuplicate); BugReport::Status status = m_result.status; const int duplicate = m_result.duplicate; const int parentDuplicate = m_result.parentDuplicate; if (m_foundDuplicate) { const QList items = ui.m_bugListWidget->findItems(QString::number(parentDuplicate), Qt::MatchExactly, 0); const QBrush brush = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NeutralBackground); Q_FOREACH (QTreeWidgetItem* item, items) { for (int i = 0; i < item->columnCount(); ++i) { item->setBackground(i, brush); } } QString text; if (BugReport::isOpen(status) || (BugReport::isClosed(status) && status == BugReport::NeedsInfo)) { text = (parentDuplicate == duplicate ? i18nc("@label", "Your crash is a duplicate and has already been reported as
Bug %1.", QString::number(duplicate)) : i18nc("@label", "Your crash has already been reported as Bug %1, which is a duplicate of Bug %2", QString::number(duplicate), QString::number(parentDuplicate))) + '\n' + i18nc("@label", "Only attach if you can add needed information to the bug report.", QStringLiteral("attach")); } else if (BugReport::isClosed(status)) { text = (parentDuplicate == duplicate ? i18nc("@label", "Your crash has already been reported as Bug %1 which has been closed.", QString::number(duplicate)) : i18nc("@label", "Your crash has already been reported as Bug %1, which is a duplicate of the closed Bug %2.", QString::number(duplicate), QString::number(parentDuplicate))); } ui.information->setText(text); } } void BugzillaDuplicatesPage::informationClicked(const QString &activatedLink) { if (activatedLink == QLatin1String("attach")) { attachToBugReport(m_result.parentDuplicate); } else { int number = activatedLink.toInt(); if (number) { showReportInformationDialog(number, false); } } } void BugzillaDuplicatesPage::searchError(QString err) { KGuiItem::assign(ui.m_searchMoreButton, m_retrySearchGuiItem); markAsSearching(false); ui.m_statusWidget->setIdle(i18nc("@info:status","Error fetching the bug report list")); KMessageBox::error(this , xi18nc("@info/rich","Error fetching the bug report list" "%1." "Please wait some time and try again.", err)); } void BugzillaDuplicatesPage::resetDates() { m_endDate = QDate::currentDate(); m_startDate = m_endDate; } //END Search related methods //BEGIN Duplicates list related methods void BugzillaDuplicatesPage::openSelectedReport() { QList selected = ui.m_bugListWidget->selectedItems(); if (selected.count() == 1) { itemClicked(selected.at(0), 0); } } void BugzillaDuplicatesPage::itemClicked(QTreeWidgetItem * item, int col) { Q_UNUSED(col); int bugNumber = 0; if (item->data(0, Qt::UserRole) == QLatin1String("custom")) { bool ok = false; bugNumber = QInputDialog::getInt(this, i18nc("@title:window", "Enter a custom bug report number"), i18nc("@label", "Enter the number of the bug report you want to check"), 0, 0, 1000000, 1, &ok); } else { bugNumber = item->text(0).toInt(); } showReportInformationDialog(bugNumber); } void BugzillaDuplicatesPage::itemClicked(QListWidgetItem * item) { showReportInformationDialog(item->text().toInt()); } void BugzillaDuplicatesPage::showReportInformationDialog(int bugNumber, bool relatedButtonEnabled) { if (bugNumber <= 0) { return; } BugzillaReportInformationDialog * infoDialog = new BugzillaReportInformationDialog(this); connect(infoDialog, &BugzillaReportInformationDialog::possibleDuplicateSelected, this, &BugzillaDuplicatesPage::addPossibleDuplicateNumber); connect(infoDialog, &BugzillaReportInformationDialog::attachToBugReportSelected, this, &BugzillaDuplicatesPage::attachToBugReport); infoDialog->showBugReport(bugNumber, relatedButtonEnabled); } void BugzillaDuplicatesPage::itemSelectionChanged() { ui.m_openReportButton->setEnabled(ui.m_bugListWidget->selectedItems().count() == 1); } //END Duplicates list related methods //BEGIN Selected duplicates list related methods void BugzillaDuplicatesPage::addPossibleDuplicateNumber(int bugNumber) { QString stringNumber = QString::number(bugNumber); if (ui.m_selectedDuplicatesList->findItems(stringNumber, Qt::MatchExactly).isEmpty()) { ui.m_selectedDuplicatesList->addItem(stringNumber); } showDuplicatesPanel(true); } void BugzillaDuplicatesPage::removeSelectedDuplicate() { QList items = ui.m_selectedDuplicatesList->selectedItems(); if (items.length() > 0) { delete ui.m_selectedDuplicatesList->takeItem(ui.m_selectedDuplicatesList->row(items.at(0))); } if (ui.m_selectedDuplicatesList->count() == 0) { showDuplicatesPanel(false); } } void BugzillaDuplicatesPage::showDuplicatesPanel(bool show) { ui.m_removeSelectedDuplicateButton->setVisible(show); ui.m_selectedDuplicatesList->setVisible(show); ui.m_selectedPossibleDuplicatesLabel->setVisible(show); } void BugzillaDuplicatesPage::possibleDuplicateSelectionChanged() { ui.m_removeSelectedDuplicateButton->setEnabled( !ui.m_selectedDuplicatesList->selectedItems().isEmpty()); } //END Selected duplicates list related methods //BEGIN Attach to bug related methods void BugzillaDuplicatesPage::attachToBugReport(int bugNumber) { ui.m_attachToReportLabel->setText(xi18nc("@label", "The report is going to be " "attached to bug %1. " "Cancel", QString::number(bugNumber))); ui.m_attachToReportLabel->setVisible(true); ui.m_attachToReportIcon->setVisible(true); reportInterface()->setAttachToBugNumber(bugNumber); } void BugzillaDuplicatesPage::cancelAttachToBugReport() { ui.m_attachToReportLabel->setVisible(false); ui.m_attachToReportIcon->setVisible(false); reportInterface()->setAttachToBugNumber(0); } //END Attach to bug related methods //END BugzillaDuplicatesPage //BEGIN BugzillaReportInformationDialog BugzillaReportInformationDialog::BugzillaReportInformationDialog(BugzillaDuplicatesPage * parent) : QDialog(parent), m_relatedButtonEnabled(true), m_parent(parent), m_bugNumber(0), m_duplicatesCount(0) { setWindowTitle(i18nc("@title:window","Bug Description")); 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 " "loading the bug report."))); connect(ui.m_retryButton, &QPushButton::clicked, this, &BugzillaReportInformationDialog::reloadReport); m_suggestButton = new QPushButton(this); ui.buttonBox->addButton(m_suggestButton, QDialogButtonBox::ActionRole); KGuiItem::assign(m_suggestButton, KGuiItem2(i18nc("@action:button", "Suggest this crash is related"), QIcon::fromTheme(QStringLiteral("list-add")), i18nc("@info:tooltip", "Use this button to suggest that " "the crash you experienced is related to this bug " "report"))); connect(m_suggestButton, &QPushButton::clicked, this, &BugzillaReportInformationDialog::relatedReportClicked); connect(ui.m_showOwnBacktraceCheckBox, &QAbstractButton::toggled, this, &BugzillaReportInformationDialog::toggleShowOwnBacktrace); //Connect bugzillalib signals connect(m_parent->bugzillaManager(), &BugzillaManager::bugReportFetched, this, &BugzillaReportInformationDialog::bugFetchFinished); connect(m_parent->bugzillaManager(), SIGNAL(bugReportError(QString,QObject*)), this, SLOT(bugFetchError(QString,QObject*))); resize(QSize(800, 600)); KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); KWindowConfig::restoreWindowSize(windowHandle(), config); } BugzillaReportInformationDialog::~BugzillaReportInformationDialog() { disconnect(m_parent->bugzillaManager(), &BugzillaManager::bugReportFetched, this, &BugzillaReportInformationDialog::bugFetchFinished); disconnect(m_parent->bugzillaManager(), SIGNAL(bugReportError(QString,QObject*)), this, SLOT(bugFetchError(QString,QObject*))); KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); KWindowConfig::saveWindowSize(windowHandle(), config); } void BugzillaReportInformationDialog::reloadReport() { showBugReport(m_bugNumber); } void BugzillaReportInformationDialog::showBugReport(int bugNumber, bool relatedButtonEnabled) { m_relatedButtonEnabled = relatedButtonEnabled; ui.m_retryButton->setVisible(false); m_closedStateString.clear(); m_bugNumber = bugNumber; m_parent->bugzillaManager()->fetchBugReport(m_bugNumber, this); m_suggestButton->setEnabled(false); m_suggestButton->setVisible(m_relatedButtonEnabled); ui.m_infoBrowser->setText(i18nc("@info:status","Loading...")); ui.m_infoBrowser->setEnabled(false); ui.m_linkLabel->setText(xi18nc("@info","Report's webpage", m_parent->bugzillaManager()->urlForBug(m_bugNumber))); ui.m_statusWidget->setBusy(xi18nc("@info:status","Loading information about bug " "%1 from %2....", QString::number(m_bugNumber), QLatin1String(KDE_BUGZILLA_SHORT_URL))); ui.m_backtraceBrowser->setPlainText( i18nc("@info","Backtrace of the crash I experienced:\n\n") + m_parent->reportInterface()->backtrace()); KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); bool showOwnBacktrace = config.readEntry("ShowOwnBacktrace", false); ui.m_showOwnBacktraceCheckBox->setChecked(showOwnBacktrace); if (!showOwnBacktrace) { //setChecked(false) will not emit toggled(false) toggleShowOwnBacktrace(false); } show(); } void BugzillaReportInformationDialog::bugFetchFinished(BugReport report, QObject * jobOwner) { if (jobOwner == this && isVisible()) { if (report.isValid()) { //Handle duplicate state QString duplicate = report.markedAsDuplicateOf(); if (!duplicate.isEmpty()) { bool ok = false; int dupId = duplicate.toInt(&ok); if (ok && dupId > 0) { ui.m_statusWidget->setIdle(QString()); KGuiItem yesItem = KStandardGuiItem::yes(); yesItem.setText(i18nc("@action:button let the user to choose to read the " "main report", "Yes, read the main report")); KGuiItem noItem = KStandardGuiItem::no(); noItem.setText(i18nc("@action:button let the user choose to read the original " "report", "No, let me read the report I selected")); if (KMessageBox::questionYesNo(this, xi18nc("@info","The report you selected (bug %1) is already " "marked as duplicate of bug %2. " "Do you want to read that report instead? (recommended)", report.bugNumber(), QString::number(dupId)), i18nc("@title:window","Nested duplicate detected"), yesItem, noItem) == KMessageBox::Yes) { showBugReport(dupId); return; } } } //Generate html for comments (with proper numbering) QLatin1String duplicatesMark = QLatin1String("has been marked as a duplicate of this bug."); QString comments; QStringList commentList = report.comments(); for (int i = 0; i < commentList.count(); i++) { QString comment = commentList.at(i); //Don't add duplicates mark comments if (!comment.contains(duplicatesMark)) { comment.replace('\n', QLatin1String("
")); comments += i18nc("comment $number to use as subtitle", "

Comment %1:

", (i+1)) + "

" + comment + "


"; //Count the inline attached crashes (DrKonqi feature) QLatin1String attachedCrashMark = QLatin1String("New crash information added by DrKonqi"); if (comment.contains(attachedCrashMark)) { m_duplicatesCount++; } } else { //Count duplicate m_duplicatesCount++; } } //Generate a non-geek readable status QString customStatusString; BugReport::Status status = report.statusValue(); BugReport::Resolution resolution = report.resolutionValue(); if (status == BugReport::Unconfirmed) { customStatusString = i18nc("@info bug status", "Opened (Unconfirmed)"); } else if (report.isOpen()) { customStatusString = i18nc("@info bug status", "Opened (Unfixed)"); } else if (report.isClosed() && status != BugReport::NeedsInfo) { QString customResolutionString; if (resolution == BugReport::Fixed) { if (!report.versionFixedIn().isEmpty()) { customResolutionString = i18nc("@info bug resolution, fixed in version", "Fixed in version \"%1\"", report.versionFixedIn()); m_closedStateString = i18nc("@info bug resolution, fixed by kde devs in version", "the bug was fixed by KDE developers in version \"%1\"", report.versionFixedIn()); } else { customResolutionString = i18nc("@info bug resolution", "Fixed"); m_closedStateString = i18nc("@info bug resolution", "the bug was fixed by KDE developers"); } } else if (resolution == BugReport::WorksForMe) { customResolutionString = i18nc("@info bug resolution", "Non-reproducible"); } else if (resolution == BugReport::Duplicate) { customResolutionString = i18nc("@info bug resolution", "Duplicate report " "(Already reported before)"); } else if (resolution == BugReport::Invalid) { customResolutionString = i18nc("@info bug resolution", "Not a valid report/crash"); } else if (resolution == BugReport::Downstream || resolution == BugReport::Upstream) { customResolutionString = i18nc("@info bug resolution", "Not caused by a problem " "in the KDE's Applications or libraries"); m_closedStateString = i18nc("@info bug resolution", "the bug is caused by a " "problem in an external application or library, or " "by a distribution or packaging issue"); } else { customResolutionString = report.resolution(); } customStatusString = i18nc("@info bug status, %1 is the resolution", "Closed (%1)", customResolutionString); } else if (status == BugReport::NeedsInfo) { customStatusString = i18nc("@info bug status", "Temporarily closed, because of a lack " "of information"); } else { //Fallback to other raw values customStatusString = QStringLiteral("%1 (%2)").arg(report.bugStatus(), report.resolution()); } //Generate notes QString notes = xi18n("

The bug report's title is often written by its reporter " "and may not reflect the bug's nature, root cause or other visible " "symptoms you could use to compare to your crash. Please read the " "complete report and all the comments below.

"); if (m_duplicatesCount >= 10) { //Consider a possible mass duplicate crash notes += xi18np("

This bug report has %1 duplicate report. That means this " "is probably a common crash. Please consider only " "adding a comment or a note if you can provide new valuable " "information which was not already mentioned.

", "

This bug report has %1 duplicate reports. That means this " "is probably a common crash. Please consider only " "adding a comment or a note if you can provide new valuable " "information which was not already mentioned.

", m_duplicatesCount); } //A manually entered bug ID could represent a normal bug if (report.bugSeverity() != QLatin1String("crash") && report.bugSeverity() != QLatin1String("major") && report.bugSeverity() != QLatin1String("grave") && report.bugSeverity() != QLatin1String("critical")) { notes += xi18n("

This bug report is not about a crash or about any other " "critical bug.

"); } //Generate HTML text QString text = i18nc("@info bug report title (quoted)", "

\"%1\"

", report.shortDescription()) + notes + i18nc("@info bug report status", "

Bug Report Status: %1

", customStatusString) + i18nc("@info bug report product and component", "

Affected Component: %1 (%2)

", report.product(), report.component()) + i18nc("@info bug report description", "

Description of the bug

%1

", report.description().replace('\n', QLatin1String("
"))); if (!comments.isEmpty()) { text += i18nc("@label:textbox bug report comments (already formatted)", "

Additional Comments

%1", comments); } ui.m_infoBrowser->setText(text); ui.m_infoBrowser->setEnabled(true); m_suggestButton->setEnabled(m_relatedButtonEnabled); m_suggestButton->setVisible(m_relatedButtonEnabled); ui.m_statusWidget->setIdle(xi18nc("@info:status", "Showing bug %1", QString::number(report.bugNumberAsInt()))); } else { bugFetchError(i18nc("@info", "Invalid report information (malformed data). This could " "mean that the bug report does not exist, or the bug tracking site " "is experiencing a problem."), this); } } } void BugzillaReportInformationDialog::markAsDuplicate() { emit possibleDuplicateSelected(m_bugNumber); hide(); } void BugzillaReportInformationDialog::attachToBugReport() { emit attachToBugReportSelected(m_bugNumber); hide(); } void BugzillaReportInformationDialog::cancelAssistant() { m_parent->assistant()->close(); hide(); } void BugzillaReportInformationDialog::relatedReportClicked() { BugzillaReportConfirmationDialog * confirmation = new BugzillaReportConfirmationDialog(m_bugNumber, (m_duplicatesCount >= 10), m_closedStateString, this); confirmation->show(); } void BugzillaReportInformationDialog::bugFetchError(QString err, QObject * jobOwner) { if (jobOwner == this && isVisible()) { KMessageBox::error(this , xi18nc("@info/rich","Error fetching the bug report" "%1." "Please wait some time and try again.", err)); m_suggestButton->setEnabled(false); ui.m_infoBrowser->setText(i18nc("@info","Error fetching the bug report")); ui.m_statusWidget->setIdle(i18nc("@info:status","Error fetching the bug report")); ui.m_retryButton->setVisible(true); } } void BugzillaReportInformationDialog::toggleShowOwnBacktrace(bool show) { QList sizes; if (show) { int size = (ui.m_reportSplitter->sizeHint().width()-ui.m_reportSplitter->handleWidth())/2; sizes << size << size; } else { sizes << ui.m_reportSplitter->sizeHint().width() << 0; //Hide backtrace } ui.m_reportSplitter->setSizes(sizes); //Save the current show value KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); config.writeEntry("ShowOwnBacktrace", show); } //END BugzillaReportInformationDialog //BEGIN BugzillaReportConfirmationDialog BugzillaReportConfirmationDialog::BugzillaReportConfirmationDialog(int bugNumber, bool commonCrash, QString closedState, BugzillaReportInformationDialog * parent) : QDialog(parent), m_parent(parent), m_showProceedQuestion(false), m_bugNumber(bugNumber) { setAttribute(Qt::WA_DeleteOnClose, true); setModal(true); ui.setupUi(this); //Setup dialog setWindowTitle(i18nc("@title:window", "Related Bug Report")); //Setup buttons ui.buttonBox->button(QDialogButtonBox::Cancel)->setText(i18nc("@action:button", "Cancel (Go back to the report)")); ui.buttonBox->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button continue with the selected option " "and close the dialog", "Continue")); ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); connect(this, &BugzillaReportConfirmationDialog::accepted, this, &BugzillaReportConfirmationDialog::proceedClicked); connect(this, &BugzillaReportConfirmationDialog::rejected, this, &BugzillaReportConfirmationDialog::hide); //Set introduction text ui.introLabel->setText(i18n("You are going to mark your crash as related to bug %1", QString::number(m_bugNumber))); if (commonCrash) { //Common ("massive") crash m_showProceedQuestion = true; ui.commonCrashIcon->setPixmap(QIcon::fromTheme(QStringLiteral("edit-bomb")).pixmap(22,22)); } else { ui.commonCrashLabel->setVisible(false); ui.commonCrashIcon->setVisible(false); } if (!closedState.isEmpty()) { //Bug report closed ui.closedReportLabel->setText( i18nc("@info", "The report is closed because %1. " "If the crash is the same, adding further information will be useless " "and will consume developers' time.", closedState)); ui.closedReportIcon->setPixmap(QIcon::fromTheme(QStringLiteral("document-close")).pixmap(22,22)); m_showProceedQuestion = true; } else { ui.closedReportLabel->setVisible(false); ui.closedReportIcon->setVisible(false); } //Disable all the radio buttons ui.proceedRadioYes->setChecked(false); ui.proceedRadioNo->setChecked(false); ui.markAsDuplicateCheck->setChecked(false); ui.attachToBugReportCheck->setChecked(false); connect(ui.buttonGroupProceed, SIGNAL(buttonClicked(int)), this, SLOT(checkProceed())); connect(ui.buttonGroupProceedQuestion, SIGNAL(buttonClicked(int)), this, SLOT(checkProceed())); + // Also listen to toggle so radio buttons are covered. + connect(ui.buttonGroupProceed, static_cast(&QButtonGroup::buttonToggled), + this, &BugzillaReportConfirmationDialog::checkProceed); + connect(ui.buttonGroupProceedQuestion, static_cast(&QButtonGroup::buttonToggled), + this, &BugzillaReportConfirmationDialog::checkProceed); if (!m_showProceedQuestion) { ui.proceedLabel->setEnabled(false); ui.proceedRadioYes->setEnabled(false); ui.proceedRadioNo->setEnabled(false); ui.proceedLabel->setVisible(false); ui.proceedRadioYes->setVisible(false); ui.proceedRadioNo->setVisible(false); ui.proceedRadioYes->setChecked(true); } checkProceed(); setMinimumSize(QSize(600, 350)); resize(QSize(600, 350)); } BugzillaReportConfirmationDialog::~BugzillaReportConfirmationDialog() { } void BugzillaReportConfirmationDialog::checkProceed() { bool yes = ui.proceedRadioYes->isChecked(); bool no = ui.proceedRadioNo->isChecked(); //Enable/disable labels and controls ui.areYouSureLabel->setEnabled(yes); ui.markAsDuplicateCheck->setEnabled(yes); ui.attachToBugReportCheck->setEnabled(yes); //Enable Continue button if valid options are selected bool possibleDupe = ui.markAsDuplicateCheck->isChecked(); bool attach = ui.attachToBugReportCheck->isChecked(); bool enableContinueButton = yes ? (possibleDupe || attach) : no; ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enableContinueButton); } void BugzillaReportConfirmationDialog::proceedClicked() { if (ui.proceedRadioYes->isChecked()) { if (ui.markAsDuplicateCheck->isChecked()) { m_parent->markAsDuplicate(); hide(); } else { m_parent->attachToBugReport(); hide(); } } else { hide(); m_parent->cancelAssistant(); } } //END BugzillaReportConfirmationDialog diff --git a/src/bugzillaintegration/reportinterface.cpp b/src/bugzillaintegration/reportinterface.cpp index da13d694..7033bcc2 100644 --- a/src/bugzillaintegration/reportinterface.cpp +++ b/src/bugzillaintegration/reportinterface.cpp @@ -1,444 +1,448 @@ /******************************************************************* * reportinterface.cpp * Copyright 2009,2010, 2011 Dario Andres Rodriguez * Copyright 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 . * ******************************************************************/ #include "reportinterface.h" #include #include "drkonqi.h" #include "bugzillalib.h" #include "productmapping.h" #include "systeminformation.h" #include "crashedapplication.h" #include "debuggermanager.h" #include "parser/backtraceparser.h" #include "backtracegenerator.h" #include "applicationdetailsexamples.h" ReportInterface::ReportInterface(QObject *parent) : QObject(parent), m_duplicate(0) { m_bugzillaManager = new BugzillaManager(KDE_BUGZILLA_URL, this); m_productMapping = new ProductMapping(DrKonqi::crashedApplication(), m_bugzillaManager, this); m_appDetailsExamples = new ApplicationDetailsExamples(this); //Information the user can provide about the crash m_userRememberCrashSituation = false; m_reproducible = ReproducibleUnsure; m_provideActionsApplicationDesktop = false; m_provideUnusualBehavior = false; m_provideApplicationConfigurationDetails = false; //Do not attach the bug report to any other existent report (create a new one) m_attachToBugNumber = 0; } void ReportInterface::setBugAwarenessPageData(bool rememberSituation, - Reproducible reproducible, bool actions, + Reproducible reproducible, bool actions, bool unusual, bool configuration) { //Save the information the user can provide about the crash from the assistant page m_userRememberCrashSituation = rememberSituation; m_reproducible = reproducible; m_provideActionsApplicationDesktop = actions; m_provideUnusualBehavior = unusual; m_provideApplicationConfigurationDetails = configuration; } bool ReportInterface::isBugAwarenessPageDataUseful() const { //Determine if the assistant should proceed, considering the amount of information //the user can provide int rating = selectedOptionsRating(); //Minimum information required even for a good backtrace. bool useful = m_userRememberCrashSituation && (rating >= 2 || (m_reproducible==ReproducibleSometimes || m_reproducible==ReproducibleEverytime)); return useful; } int ReportInterface::selectedOptionsRating() const { //Check how many information the user can provide and generate a rating int rating = 0; if (m_provideActionsApplicationDesktop) { rating += 3; } if (m_provideApplicationConfigurationDetails) { rating += 2; } if (m_provideUnusualBehavior) { rating += 1; } return rating; } QString ReportInterface::backtrace() const { return m_backtrace; } void ReportInterface::setBacktrace(const QString & backtrace) { m_backtrace = backtrace; } QStringList ReportInterface::firstBacktraceFunctions() const { return m_firstBacktraceFunctions; } void ReportInterface::setFirstBacktraceFunctions(const QStringList & functions) { m_firstBacktraceFunctions = functions; } QString ReportInterface::title() const { return m_reportTitle; } void ReportInterface::setTitle(const QString & text) { m_reportTitle = text; } void ReportInterface::setDetailText(const QString & text) { m_reportDetailText = text; } void ReportInterface::setPossibleDuplicates(const QStringList & list) { m_possibleDuplicates = list; } QString ReportInterface::generateReportFullText(bool drKonqiStamp) const { //Note: no translations should be done in this function's strings const CrashedApplication * crashedApp = DrKonqi::crashedApplication(); const SystemInformation * sysInfo = DrKonqi::systemInformation(); QString report; //Program name and versions report.append(QStringLiteral("Application: %1 (%2)\n").arg(crashedApp->fakeExecutableBaseName(), crashedApp->version())); if ( sysInfo->compiledSources() ) { report.append(QStringLiteral(" (Compiled from sources)\n")); } else { report.append(QStringLiteral("\n")); } report.append(QStringLiteral("Qt Version: %1\n").arg(sysInfo->qtVersion())); report.append(QStringLiteral("Frameworks Version: %1\n").arg(sysInfo->frameworksVersion())); report.append(QStringLiteral("Operating System: %1\n").arg(sysInfo->operatingSystem())); //LSB output or manually selected distro if ( !sysInfo->lsbRelease().isEmpty() ) { report.append(QStringLiteral("Distribution: %1\n").arg(sysInfo->lsbRelease())); } else if ( !sysInfo->bugzillaPlatform().isEmpty() && sysInfo->bugzillaPlatform() != QLatin1String("unspecified")) { report.append(QStringLiteral("Distribution (Platform): %1\n").arg( sysInfo->bugzillaPlatform())); } report.append(QLatin1Char('\n')); //Details of the crash situation if (isBugAwarenessPageDataUseful()) { report.append(QStringLiteral("-- Information about the crash:\n")); if (!m_reportDetailText.isEmpty()) { report.append(m_reportDetailText.trimmed()); } else { //If the user manual reports this crash, he/she should know what to put in here. //This message is the only one translated in this function report.append(xi18nc("@info/plain","In detail, tell us what you were doing " " when the application crashed.")); } report.append(QLatin1String("\n\n")); } //Crash reproducibility (only if useful) if (m_reproducible != ReproducibleUnsure) { if (m_reproducible == ReproducibleEverytime) { report.append(QStringLiteral("The crash can be reproduced every time.\n\n")); } else if (m_reproducible == ReproducibleSometimes) { report.append(QStringLiteral("The crash can be reproduced sometimes.\n\n")); } else if (m_reproducible == ReproducibleNever) { report.append(QStringLiteral("The crash does not seem to be reproducible.\n\n")); } } //Backtrace report.append(QStringLiteral("-- Backtrace:\n")); if (!m_backtrace.isEmpty()) { QString formattedBacktrace = m_backtrace.trimmed(); report.append(formattedBacktrace + QLatin1Char('\n')); } else { report.append(QStringLiteral("A useful backtrace could not be generated\n")); } //Possible duplicates (selected by the user) if (!m_possibleDuplicates.isEmpty()) { report.append(QLatin1Char('\n')); QString duplicatesString; Q_FOREACH(const QString & dupe, m_possibleDuplicates) { duplicatesString += QLatin1String("bug ") + dupe + QLatin1String(", "); } duplicatesString = duplicatesString.left(duplicatesString.length()-2) + '.'; report.append(QStringLiteral("The reporter indicates this bug may be a duplicate of or related to %1\n") .arg(duplicatesString)); } //Several possible duplicates (by bugzilla query) if (!m_allPossibleDuplicatesByQuery.isEmpty()) { report.append(QLatin1Char('\n')); QString duplicatesString; int count = m_allPossibleDuplicatesByQuery.count(); for(int i=0; i < count && i < 5; i++) { duplicatesString += QLatin1String("bug ") + m_allPossibleDuplicatesByQuery.at(i) + QLatin1String(", "); } duplicatesString = duplicatesString.left(duplicatesString.length()-2) + '.'; report.append(QStringLiteral("Possible duplicates by query: %1\n").arg(duplicatesString)); } if (drKonqiStamp) { report.append(QLatin1String("\nReported using DrKonqi")); } return report; } QString ReportInterface::generateAttachmentComment() const { //Note: no translations should be done in this function's strings const CrashedApplication * crashedApp = DrKonqi::crashedApplication(); const SystemInformation * sysInfo = DrKonqi::systemInformation(); QString comment; //Program name and versions comment.append(QStringLiteral("%1 (%2) using Qt %4\n\n") .arg(crashedApp->fakeExecutableBaseName()) .arg(crashedApp->version()) .arg(sysInfo->qtVersion())); //Details of the crash situation if (isBugAwarenessPageDataUseful()) { comment.append(QStringLiteral("%1\n\n").arg(m_reportDetailText.trimmed())); } //Backtrace (only 6 lines) comment.append(QStringLiteral("-- Backtrace (Reduced):\n")); QString reducedBacktrace = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->simplifiedBacktrace(); comment.append(reducedBacktrace.trimmed()); return comment; } BugReport ReportInterface::newBugReportTemplate() const { //Generate a new bug report template with some values on it BugReport report; const SystemInformation * sysInfo = DrKonqi::systemInformation(); report.setProduct(m_productMapping->bugzillaProduct()); report.setComponent(m_productMapping->bugzillaComponent()); report.setVersion(m_productMapping->bugzillaVersion()); report.setOperatingSystem(sysInfo->bugzillaOperatingSystem()); if (sysInfo->compiledSources()) { report.setPlatform(QLatin1String("Compiled Sources")); } else { report.setPlatform(sysInfo->bugzillaPlatform()); } report.setKeywords(QStringList() << QStringLiteral("drkonqi")); report.setPriority(QLatin1String("NOR")); report.setBugSeverity(QLatin1String("crash")); /* Disable the backtrace functions on title for RELEASE. It also needs a bit of polishment QString title = m_reportTitle; //If there are not too much possible duplicates by query then there are more possibilities //that this report is unique. Let's add the backtrace functions to the title if (m_allPossibleDuplicatesByQuery.count() <= 2) { if (!m_firstBacktraceFunctions.isEmpty()) { title += (QLatin1String(" [") + m_firstBacktraceFunctions.join(", ").trimmed() + QLatin1String("]")); } } */ report.setShortDescription(m_reportTitle); return report; } void ReportInterface::sendBugReport() const { if (m_attachToBugNumber > 0) { //We are going to attach the report to an existent one connect(m_bugzillaManager, &BugzillaManager::addMeToCCFinished, this, &ReportInterface::addedToCC); connect(m_bugzillaManager, &BugzillaManager::addMeToCCError, this, &ReportInterface::sendReportError); //First add the user to the CC list, then attach m_bugzillaManager->addMeToCC(m_attachToBugNumber); } else { //Creating a new bug report BugReport report = newBugReportTemplate(); report.setDescription(generateReportFullText(true)); report.setValid(true); connect(m_bugzillaManager, &BugzillaManager::sendReportErrorInvalidValues, this, &ReportInterface::sendUsingDefaultProduct); connect(m_bugzillaManager, &BugzillaManager::reportSent, this, &ReportInterface::reportSent); connect(m_bugzillaManager, &BugzillaManager::sendReportError, this, &ReportInterface::sendReportError); m_bugzillaManager->sendReport(report); } } void ReportInterface::sendUsingDefaultProduct() const { //Fallback function: if some of the custom values fail, we need to reset all the fields to the default //(and valid) bugzilla values; and try to resend BugReport report = newBugReportTemplate(); report.setProduct(QLatin1String("kde")); report.setComponent(QLatin1String("general")); report.setPlatform(QLatin1String("unspecified")); report.setDescription(generateReportFullText(true)); report.setValid(true); m_bugzillaManager->sendReport(report); } void ReportInterface::addedToCC() { //The user was added to the CC list, proceed with the attachment connect(m_bugzillaManager, &BugzillaManager::attachToReportSent, this, &ReportInterface::attachSent); connect(m_bugzillaManager, &BugzillaManager::attachToReportError, this, &ReportInterface::sendReportError); QString reportText = generateReportFullText(true); QString comment = generateAttachmentComment(); QString filename = getSuggestedKCrashFilename(DrKonqi::crashedApplication()); QLatin1String summary("New crash information added by DrKonqi"); //Attach the report. The comment of the attachment also includes the bug description m_bugzillaManager->attachTextToReport(reportText, filename, summary, m_attachToBugNumber, comment); } void ReportInterface::attachSent(int attachId) { Q_UNUSED(attachId); //The bug was attached, consider it "sent" emit reportSent(m_attachToBugNumber); } QStringList ReportInterface::relatedBugzillaProducts() const { return m_productMapping->relatedBugzillaProducts(); } bool ReportInterface::isWorthReporting() const { + if (DrKonqi::ignoreQuality()) { + return true; + } + //Evaluate if the provided information is useful enough to enable the automatic report bool needToReport = false; if (!m_userRememberCrashSituation) { //This should never happen... but... return false; } int rating = selectedOptionsRating(); BacktraceParser::Usefulness use = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness(); switch (use) { case BacktraceParser::ReallyUseful: { //Perfect backtrace: require at least one option or a 100%-50% reproducible crash needToReport = (rating >=2) || (m_reproducible == ReproducibleEverytime || m_reproducible == ReproducibleSometimes); break; } case BacktraceParser::MayBeUseful: { //Not perfect backtrace: require at least two options or a 100% reproducible crash needToReport = (rating >=3) || (m_reproducible == ReproducibleEverytime); break; } case BacktraceParser::ProbablyUseless: //Bad backtrace: require at least two options and always reproducible (strict) needToReport = (rating >=5) && (m_reproducible == ReproducibleEverytime); break; case BacktraceParser::Useless: case BacktraceParser::InvalidUsefulness: { needToReport = false; } } return needToReport; } void ReportInterface::setAttachToBugNumber(uint bugNumber) { //If bugNumber>0, the report is going to be attached to bugNumber m_attachToBugNumber = bugNumber; } uint ReportInterface::attachToBugNumber() const { return m_attachToBugNumber; } void ReportInterface::setDuplicateId(uint duplicate) { m_duplicate = duplicate; } uint ReportInterface::duplicateId() const { return m_duplicate; } void ReportInterface::setPossibleDuplicatesByQuery(const QStringList & list) { m_allPossibleDuplicatesByQuery = list; } BugzillaManager * ReportInterface::bugzillaManager() const { return m_bugzillaManager; } ApplicationDetailsExamples * ReportInterface::appDetailsExamples() const { return m_appDetailsExamples; } diff --git a/src/bugzillaintegration/ui/assistantpage_bugzilla_information.ui b/src/bugzillaintegration/ui/assistantpage_bugzilla_information.ui index ee74b017..9fc5843f 100644 --- a/src/bugzillaintegration/ui/assistantpage_bugzilla_information.ui +++ b/src/bugzillaintegration/ui/assistantpage_bugzilla_information.ui @@ -1,117 +1,120 @@ AssistantPageBugzillaInformation 0 0 513 345 <strong>Please provide the following information in English.</strong> true <strong>Title of the bug report:</strong> (<a href="#">examples</a>) <strong>Information about the crash:</strong> (<a href="#">help and examples</a>) + + Information about the crash text + false Distribution method: KDE Platform is compiled from sources Qt::Vertical QSizePolicy::Fixed 20 10 <i>Note</i>: The crash and system information will be automatically added to the bug report. true KComboBox QComboBox
kcombobox.h
KLineEdit QLineEdit
klineedit.h
diff --git a/src/bugzillaintegration/ui/assistantpage_bugzilla_login.ui b/src/bugzillaintegration/ui/assistantpage_bugzilla_login.ui index 54a86bac..a46a14ed 100644 --- a/src/bugzillaintegration/ui/assistantpage_bugzilla_login.ui +++ b/src/bugzillaintegration/ui/assistantpage_bugzilla_login.ui @@ -1,157 +1,163 @@ AssistantPageBugzillaLogin 0 0 488 472 Qt::Vertical QSizePolicy::Fixed 20 5 Username: Password: + + Password input + true true + + Username input + true Save login information using the KDE Wallet system Qt::Horizontal 40 20 Qt::Vertical QSizePolicy::Fixed 20 10 true true Qt::Vertical 20 40 KLineEdit QLineEdit
klineedit.h
StatusWidget QWidget
statuswidget.h
1
m_userEdit m_passwordEdit m_savePasswordCheckBox m_loginButton
diff --git a/src/drkonqi.cpp b/src/drkonqi.cpp index 986842be..46aa15b7 100644 --- a/src/drkonqi.cpp +++ b/src/drkonqi.cpp @@ -1,329 +1,352 @@ /* 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 . Parts of this code were originally under the following license: * Copyright (C) 2000-2003 Hans Petter Bieker * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "drkonqi.h" #include #include #include #include #include #include #include #include #include #include #include #include "systeminformation.h" #include "crashedapplication.h" #include "drkonqibackends.h" DrKonqi::DrKonqi() : m_signal(0) , m_pid(0) , m_kdeinit(false) , m_safer(false) , m_restarted(false) , m_keepRunning(false) , m_thread(0) { m_backend = new KCrashBackend(); m_systemInformation = new SystemInformation(); } DrKonqi::~DrKonqi() { delete m_systemInformation; delete m_backend; } //static DrKonqi *DrKonqi::instance() { static DrKonqi *drKonqiInstance = NULL; if (!drKonqiInstance) { drKonqiInstance = new DrKonqi(); } return drKonqiInstance; } //based on KCrashDelaySetHandler from kdeui/util/kcrash.cpp class EnableCrashCatchingDelayed : public QObject { public: EnableCrashCatchingDelayed() { startTimer(10000); // 10 s } protected: void timerEvent(QTimerEvent *event) override { qDebug() << "Enabling drkonqi crash catching"; KCrash::setDrKonqiEnabled(true); killTimer(event->timerId()); this->deleteLater(); } }; bool DrKonqi::init() { if (!instance()->m_backend->init()) { cleanup(); return false; } else { //all ok, continue initialization // Set drkonqi to handle its own crashes, but only if the crashed app // is not drkonqi already. If it is drkonqi, delay enabling crash catching // to prevent recursive crashes (in case it crashes at startup) if (crashedApplication()->fakeExecutableBaseName() != QLatin1String("drkonqi")) { qDebug() << "Enabling drkonqi crash catching"; KCrash::setDrKonqiEnabled(true); } else { new EnableCrashCatchingDelayed; } return true; } } void DrKonqi::cleanup() { delete instance(); } //static SystemInformation *DrKonqi::systemInformation() { return instance()->m_systemInformation; } //static DebuggerManager* DrKonqi::debuggerManager() { return instance()->m_backend->debuggerManager(); } //static CrashedApplication *DrKonqi::crashedApplication() { return instance()->m_backend->crashedApplication(); } //static void DrKonqi::saveReport(const QString & reportText, QWidget *parent) { if (isSafer()) { QTemporaryFile tf; tf.setFileTemplate(QStringLiteral("XXXXXX.kcrash.txt")); tf.setAutoRemove(false); if (tf.open()) { QTextStream textStream(&tf); textStream << reportText; textStream.flush(); KMessageBox::information(parent, xi18nc("@info", "Report saved to %1.", tf.fileName())); } else { KMessageBox::sorry(parent, i18nc("@info","Could not create a file in which to save the report.")); } } else { QString defname = getSuggestedKCrashFilename(crashedApplication()); QPointer dlg(new QFileDialog(parent, defname)); dlg->selectFile(defname); dlg->setWindowTitle(i18nc("@title:window","Select Filename")); dlg->setAcceptMode(QFileDialog::AcceptSave); dlg->setFileMode(QFileDialog::AnyFile); dlg->setConfirmOverwrite(true); if (dlg->exec() != QDialog::Accepted) { return; } if (!dlg) { //Dialog is invalid, it was probably deleted (ex. via DBus call) //return and do not crash return; } QUrl fileUrl; if(!dlg->selectedUrls().isEmpty()) fileUrl = dlg->selectedUrls().first(); delete dlg; if (fileUrl.isValid()) { QTemporaryFile tf; if (tf.open()) { QTextStream ts(&tf); ts << reportText; ts.flush(); } else { KMessageBox::sorry(parent, xi18nc("@info","Cannot open file %1 " "for writing.", tf.fileName())); return; } KIO::FileCopyJob* job = KIO::file_copy(QUrl::fromLocalFile(tf.fileName()), fileUrl); KJobWidgets::setWindow(job, parent); if (!job->exec()) { KMessageBox::sorry(parent, job->errorText()); } } } } void DrKonqi::setSignal(int signal) { instance()->m_signal = signal; } void DrKonqi::setAppName(const QString &appName) { instance()->m_appName = appName; } void DrKonqi::setAppPath(const QString &appPath) { instance()->m_appPath = appPath; } void DrKonqi::setAppVersion(const QString &appVersion) { instance()->m_appVersion = appVersion; } void DrKonqi::setBugAddress(const QString &bugAddress) { instance()->m_bugAddress = bugAddress; } void DrKonqi::setProgramName(const QString &programName) { instance()->m_programName = programName; } void DrKonqi::setPid(int pid) { instance()->m_pid = pid; } void DrKonqi::setKdeinit(bool kdeinit) { instance()->m_kdeinit = kdeinit; } void DrKonqi::setSafer(bool safer) { instance()->m_safer = safer; } void DrKonqi::setRestarted(bool restarted) { instance()->m_restarted = restarted; } void DrKonqi::setKeepRunning(bool keepRunning) { instance()->m_keepRunning = keepRunning; } void DrKonqi::setThread(int thread) { instance()->m_thread = thread; } int DrKonqi::signal() { return instance()->m_signal; } const QString &DrKonqi::appName() { return instance()->m_appName; } const QString &DrKonqi::appPath() { return instance()->m_appPath; } const QString &DrKonqi::appVersion() { return instance()->m_appVersion; } const QString &DrKonqi::bugAddress() { return instance()->m_bugAddress; } const QString &DrKonqi::programName() { return instance()->m_programName; } int DrKonqi::pid() { return instance()->m_pid; } bool DrKonqi::isKdeinit() { return instance()->m_kdeinit; } bool DrKonqi::isSafer() { return instance()->m_safer; } bool DrKonqi::isRestarted() { return instance()->m_restarted; } bool DrKonqi::isKeepRunning() { return instance()->m_keepRunning; } int DrKonqi::thread() { return instance()->m_thread; } + +bool DrKonqi::ignoreQuality() +{ + return qEnvironmentVariableIsSet("DRKONQI_IGNORE_QUALITY"); +} + +const QString &DrKonqi::kdeBugzillaURL() +{ + // NB: for practical reasons this cannot use the shared instance. Initing the instances requires + // knowing the URL already, so we'd have an init loop. Use a local static instead. + static QString url; + if (!url.isEmpty()) { + return url; + } + + url = QString::fromLocal8Bit(qgetenv("DRKONQI_KDE_BUGZILLA_URL")); + if (!url.isEmpty()) { + return url; + } + + url = QStringLiteral("https://bugs.kde.org/"); + return url; +} diff --git a/src/drkonqi.h b/src/drkonqi.h index 1dfb6685..3cbae584 100644 --- a/src/drkonqi.h +++ b/src/drkonqi.h @@ -1,89 +1,91 @@ /* 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_H #define DRKONQI_H #include class QWidget; class SystemInformation; class DebuggerManager; class CrashedApplication; class AbstractDrKonqiBackend; class DrKonqi { public: static bool init(); static void cleanup(); static SystemInformation *systemInformation(); static DebuggerManager *debuggerManager(); static CrashedApplication *crashedApplication(); static void saveReport(const QString & reportText, QWidget *parent = 0); static void setSignal(int signal); static void setAppName(const QString &appName); static void setAppPath(const QString &appPath); static void setAppVersion(const QString &appVersion); static void setBugAddress(const QString &bugAddress); static void setProgramName(const QString &programName); static void setPid(int pid); static void setKdeinit(bool kdeinit); static void setSafer(bool safer); static void setRestarted(bool restarted); static void setKeepRunning(bool keepRunning); static void setThread(int thread); static int signal(); static const QString &appName(); static const QString &appPath(); static const QString &appVersion(); static const QString &bugAddress(); static const QString &programName(); static int pid(); static bool isKdeinit(); static bool isSafer(); static bool isRestarted(); static bool isKeepRunning(); static int thread(); + static bool ignoreQuality(); + static const QString &kdeBugzillaURL(); private: DrKonqi(); ~DrKonqi(); static DrKonqi *instance(); SystemInformation *m_systemInformation; AbstractDrKonqiBackend *m_backend; int m_signal; QString m_appName; QString m_appPath; QString m_appVersion; QString m_bugAddress; QString m_programName; int m_pid; bool m_kdeinit; bool m_safer; bool m_restarted; bool m_keepRunning; int m_thread; }; #endif diff --git a/src/drkonqi_globals.h b/src/drkonqi_globals.h index 16fa369e..960af21c 100644 --- a/src/drkonqi_globals.h +++ b/src/drkonqi_globals.h @@ -1,52 +1,54 @@ /* 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 "https://bugs.kde.org/" -#define KDE_BUGZILLA_CREATE_ACCOUNT_URL KDE_BUGZILLA_URL "createaccount.cgi" +#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_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 diff --git a/src/drkonqidialog.cpp b/src/drkonqidialog.cpp index 5b2bad97..46b404fc 100644 --- a/src/drkonqidialog.cpp +++ b/src/drkonqidialog.cpp @@ -1,304 +1,303 @@ /******************************************************************* * drkonqidialog.cpp * Copyright 2009 Dario Andres Rodriguez * * 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 "drkonqidialog.h" #include #include #include #include #include #include #include #include #include "drkonqi.h" #include "backtracewidget.h" #include "aboutbugreportingdialog.h" #include "crashedapplication.h" #include "debuggermanager.h" #include "debuggerlaunchers.h" #include "drkonqi_globals.h" #include "config-drkonqi.h" #if HAVE_XMLRPCCLIENT #include "bugzillaintegration/reportassistantdialog.h" #endif static const char ABOUT_BUG_REPORTING_URL[] = "#aboutbugreporting"; -static const char DRKONQI_REPORT_BUG_URL[] = - KDE_BUGZILLA_URL "enter_bug.cgi?product=drkonqi&format=guided"; +static QString DRKONQI_REPORT_BUG_URL = KDE_BUGZILLA_URL + QStringLiteral("enter_bug.cgi?product=drkonqi&format=guided"); DrKonqiDialog::DrKonqiDialog(QWidget * parent) : QDialog(parent), m_aboutBugReportingDialog(0), m_backtraceWidget(0) { setAttribute(Qt::WA_DeleteOnClose, true); //Setting dialog title and icon setWindowTitle(DrKonqi::crashedApplication()->name()); setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"))); QVBoxLayout* l = new QVBoxLayout(this); m_tabWidget = new QTabWidget(this); l->addWidget(m_tabWidget); m_buttonBox = new QDialogButtonBox(this); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accepted); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::rejected); l->addWidget(m_buttonBox); connect(m_tabWidget, &QTabWidget::currentChanged, this, &DrKonqiDialog::tabIndexChanged); KConfigGroup config(KSharedConfig::openConfig(), "General"); if (!config.readEntry(QStringLiteral("ShowOnlyBacktrace"), false)) { buildIntroWidget(); m_tabWidget->addTab(m_introWidget, i18nc("@title:tab general information", "&General")); } m_backtraceWidget = new BacktraceWidget(DrKonqi::debuggerManager()->backtraceGenerator(), this); m_backtraceWidget->setMinimumSize(QSize(575, 240)); m_tabWidget->addTab(m_backtraceWidget, i18nc("@title:tab", "&Developer Information")); m_tabWidget->tabBar()->setVisible(m_tabWidget->count() > 1); buildDialogButtons(); setMinimumSize(QSize(640,320)); resize(minimumSize()); KWindowConfig::restoreWindowSize(windowHandle(), config); setLayout(l); } DrKonqiDialog::~DrKonqiDialog() { KConfigGroup config(KSharedConfig::openConfig(), "General"); KWindowConfig::saveWindowSize(windowHandle(), config); } void DrKonqiDialog::tabIndexChanged(int index) { if (index == m_tabWidget->indexOf(m_backtraceWidget)) { m_backtraceWidget->generateBacktrace(); } } void DrKonqiDialog::buildIntroWidget() { const CrashedApplication *crashedApp = DrKonqi::crashedApplication(); m_introWidget = new QWidget(this); ui.setupUi(m_introWidget); ui.titleLabel->setText(xi18nc("@info", "We are sorry, %1 " "closed unexpectedly.", crashedApp->name())); QString reportMessage; if (!crashedApp->bugReportAddress().isEmpty()) { if (crashedApp->fakeExecutableBaseName() == QLatin1String("drkonqi")) { //Handle own crashes reportMessage = xi18nc("@info", "As the Crash Handler itself has failed, the " "automatic reporting process is disabled to reduce the " "risks of failing again." "Please, manually report this error " "to the KDE bug tracking system. Do not forget to include " "the backtrace from the Developer Information " "tab.", - QLatin1String(DRKONQI_REPORT_BUG_URL)); + DRKONQI_REPORT_BUG_URL); } else if (DrKonqi::isSafer()) { reportMessage = xi18nc("@info", "The reporting assistant is disabled because " "the crash handler dialog was started in safe mode." "You can manually report this bug to %1 " "(including the backtrace from the " "Developer Information " "tab.)", crashedApp->bugReportAddress()); } else { reportMessage = xi18nc("@info", "You can help us improve KDE Software by reporting " "this error.Learn " "more about bug reporting.", QLatin1String(ABOUT_BUG_REPORTING_URL)); } } else { reportMessage = xi18nc("@info", "You cannot report this error, because " "%1 does not provide a bug reporting " "address.", crashedApp->name() ); } ui.infoLabel->setText(reportMessage); connect(ui.infoLabel, &QLabel::linkActivated, this, &DrKonqiDialog::linkActivated); ui.detailsTitleLabel->setText(QStringLiteral("%1").arg(i18nc("@label","Details:"))); ui.detailsLabel->setText(xi18nc("@info Note the time information is divided into date and time parts", "Executable: %1" " PID: %2 Signal: %3 (%4) " "Time: %5 %6", crashedApp->fakeExecutableBaseName(), crashedApp->pid(), crashedApp->signalName(), #if defined(Q_OS_UNIX) crashedApp->signalNumber(), #else //windows uses weird big numbers for exception codes, //so it doesn't make sense to display them in decimal QString().sprintf("0x%8x", crashedApp->signalNumber()), #endif crashedApp->datetime().date().toString(Qt::DefaultLocaleShortDate), crashedApp->datetime().time().toString() )); } void DrKonqiDialog::buildDialogButtons() { const CrashedApplication *crashedApp = DrKonqi::crashedApplication(); //Set dialog buttons m_buttonBox->setStandardButtons(QDialogButtonBox::Close); //Report bug button: User1 QPushButton* reportButton = new QPushButton(m_buttonBox); KGuiItem2 reportItem(i18nc("@action:button", "Report &Bug"), QIcon::fromTheme(QStringLiteral("tools-report-bug")), i18nc("@info:tooltip", "Starts the bug report assistant.")); KGuiItem::assign(reportButton, reportItem); m_buttonBox->addButton(reportButton, QDialogButtonBox::ActionRole); bool enableReportAssistant = !crashedApp->bugReportAddress().isEmpty() && crashedApp->fakeExecutableBaseName() != QLatin1String("drkonqi") && !DrKonqi::isSafer() && HAVE_XMLRPCCLIENT; reportButton->setEnabled(enableReportAssistant); connect(reportButton, &QPushButton::clicked, this, &DrKonqiDialog::startBugReportAssistant); //Default debugger button and menu (only for developer mode): User2 DebuggerManager *debuggerManager = DrKonqi::debuggerManager(); m_debugButton = new QPushButton(m_buttonBox); KGuiItem2 debugItem(i18nc("@action:button this is the debug menu button label which contains the debugging applications", "&Debug"), QIcon::fromTheme(QStringLiteral("applications-development")), i18nc("@info:tooltip", "Starts a program to debug " "the crashed application.")); KGuiItem::assign(m_debugButton, debugItem); m_debugButton->setVisible(debuggerManager->showExternalDebuggers()); // Do not add the button unless it is visible, otherwise the Box will force // it visible as it calls show() explicitly. if (m_debugButton->isVisible()) { m_buttonBox->addButton(m_debugButton, QDialogButtonBox::ActionRole); } m_debugMenu = new QMenu(this); m_debugButton->setMenu(m_debugMenu); QList debuggers = debuggerManager->availableExternalDebuggers(); foreach(AbstractDebuggerLauncher *launcher, debuggers) { addDebugger(launcher); } connect(debuggerManager, &DebuggerManager::externalDebuggerAdded, this, &DrKonqiDialog::addDebugger); connect(debuggerManager, &DebuggerManager::externalDebuggerRemoved, this, &DrKonqiDialog::removeDebugger); connect(debuggerManager, &DebuggerManager::debuggerRunning, this, &DrKonqiDialog::enableDebugMenu); //Restart application button KGuiItem2 restartItem(i18nc("@action:button", "&Restart Application"), QIcon::fromTheme(QStringLiteral("system-reboot")), i18nc("@info:tooltip", "Use this button to restart " "the crashed application.")); m_restartButton = new QPushButton(m_buttonBox); KGuiItem::assign(m_restartButton, restartItem); m_restartButton->setEnabled(!crashedApp->hasBeenRestarted() && crashedApp->fakeExecutableBaseName() != QLatin1String("drkonqi")); m_buttonBox->addButton(m_restartButton, QDialogButtonBox::ActionRole); connect(m_restartButton, &QAbstractButton::clicked, crashedApp, &CrashedApplication::restart); connect(crashedApp, &CrashedApplication::restarted, this, &DrKonqiDialog::applicationRestarted); //Close button QString tooltipText = i18nc("@info:tooltip", "Close this dialog (you will lose the crash information.)"); m_buttonBox->button(QDialogButtonBox::Close)->setToolTip(tooltipText); m_buttonBox->button(QDialogButtonBox::Close)->setWhatsThis(tooltipText); m_buttonBox->button(QDialogButtonBox::Close)->setFocus(); } void DrKonqiDialog::addDebugger(AbstractDebuggerLauncher *launcher) { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("applications-development")), i18nc("@action:inmenu 1 is the debugger name", "Debug in %1", launcher->name()), m_debugMenu); m_debugMenu->addAction(action); connect(action, &QAction::triggered, launcher, &AbstractDebuggerLauncher::start); m_debugMenuActions.insert(launcher, action); } void DrKonqiDialog::removeDebugger(AbstractDebuggerLauncher *launcher) { QAction *action = m_debugMenuActions.take(launcher); if ( action ) { m_debugMenu->removeAction(action); action->deleteLater(); } else { qWarning() << "Invalid launcher"; } } void DrKonqiDialog::enableDebugMenu(bool debuggerRunning) { m_debugButton->setEnabled(!debuggerRunning); } void DrKonqiDialog::startBugReportAssistant() { #if HAVE_XMLRPCCLIENT ReportAssistantDialog * bugReportAssistant = new ReportAssistantDialog(); bugReportAssistant->show(); connect(bugReportAssistant, &QObject::destroyed, this, &DrKonqiDialog::reject); hide(); #endif } void DrKonqiDialog::linkActivated(const QString& link) { if (link == QLatin1String(ABOUT_BUG_REPORTING_URL)) { showAboutBugReporting(); - } else if (link == QLatin1String(DRKONQI_REPORT_BUG_URL)) { + } else if (link == DRKONQI_REPORT_BUG_URL) { QDesktopServices::openUrl(QUrl(link)); } else if (link.startsWith(QLatin1String("http"))) { qWarning() << "unexpected link"; QDesktopServices::openUrl(QUrl(link)); } } void DrKonqiDialog::showAboutBugReporting() { if (!m_aboutBugReportingDialog) { m_aboutBugReportingDialog = new AboutBugReportingDialog(); connect(this, &DrKonqiDialog::destroyed, m_aboutBugReportingDialog.data(), &AboutBugReportingDialog::close); } m_aboutBugReportingDialog->show(); m_aboutBugReportingDialog->raise(); m_aboutBugReportingDialog->activateWindow(); } void DrKonqiDialog::applicationRestarted(bool success) { m_restartButton->setEnabled(!success); } diff --git a/src/main.cpp b/src/main.cpp index 8bd5ac49..e88a0646 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,174 +1,177 @@ /***************************************************************** * drkonqi - The KDE Crash Handler * * Copyright (C) 2000-2003 Hans Petter Bieker * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************/ #include #include #include #include #include #include #if HAVE_X11 #include #endif #include "drkonqi.h" #include "drkonqidialog.h" #include "statusnotifier.h" static const char version[] = PROJECT_VERSION; static const char description[] = I18N_NOOP("The KDE Crash Handler gives the user feedback " "if a program has crashed."); int main(int argc, char* argv[]) { #ifndef Q_OS_WIN //krazy:exclude=cpp // Drop privs. setgid(getgid()); if (setuid(getuid()) < 0 && geteuid() != getuid()) { exit(255); } #endif KLocalizedString::setApplicationDomain("drkonqi5"); QApplication qa(argc, argv); qa.setAttribute(Qt::AA_UseHighDpiPixmaps, true); QCoreApplication::setApplicationName(QStringLiteral("drkonqi")); QCoreApplication::setApplicationVersion(version); // Prevent KApplication from setting the crash handler. We will set it later... setenv("KDE_DEBUG", "true", 1); // Session management is not needed, do not even connect in order to survive longer than ksmserver. unsetenv("SESSION_MANAGER"); KAboutData aboutData(QStringLiteral("drkonqi"), i18n("The KDE Crash Handler"), version, i18n(description), KAboutLicense::GPL, i18n("(C) 2000-2009, The DrKonqi Authors")); aboutData.addAuthor(i18nc("@info:credit","Hans Petter Bieker"), QString(), QStringLiteral("bieker@kde.org")); aboutData.addAuthor(i18nc("@info:credit","Dario Andres Rodriguez"), QString(), QStringLiteral("andresbajotierra@gmail.com")); aboutData.addAuthor(i18nc("@info:credit","George Kiagiadakis"), QString(), QStringLiteral("gkiagia@users.sourceforge.net")); aboutData.addAuthor(i18nc("@info:credit","A. L. Spehr"), QString(), QStringLiteral("spehr@kde.org")); qa.setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"))); QCommandLineParser parser; parser.setApplicationDescription(description); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption signalOption(QStringLiteral("signal"), i18nc("@info:shell","The signal that was caught"), QStringLiteral("number")); QCommandLineOption appNameOption(QStringLiteral("appname"), i18nc("@info:shell"," of the program"), QStringLiteral("name")); QCommandLineOption appPathOption(QStringLiteral("apppath"), i18nc("@info:shell"," to the executable"), QStringLiteral("path")); QCommandLineOption appVersionOption(QStringLiteral("appversion"), i18nc("@info:shell","The of the program"), QStringLiteral("version")); QCommandLineOption bugAddressOption(QStringLiteral("bugaddress"), i18nc("@info:shell","The bug
to use"), QStringLiteral("address")); QCommandLineOption programNameOption(QStringLiteral("programname"), i18nc("@info:shell","Translated of the program"), QStringLiteral("name")); QCommandLineOption pidOption(QStringLiteral("pid"), i18nc("@info:shell","The of the program"), QStringLiteral("pid")); QCommandLineOption startupIdOption(QStringLiteral("startupid"), i18nc("@info:shell","Startup of the program"), QStringLiteral("id")); QCommandLineOption kdeinitOption(QStringLiteral("kdeinit"), i18nc("@info:shell","The program was started by kdeinit")); QCommandLineOption saferOption(QStringLiteral("safer"), i18nc("@info:shell","Disable arbitrary disk access")); QCommandLineOption restartedOption(QStringLiteral("restarted"), i18nc("@info:shell","The program has already been restarted")); QCommandLineOption keepRunningOption(QStringLiteral("keeprunning"), i18nc("@info:shell","Keep the program running and generate " "the backtrace at startup")); QCommandLineOption threadOption(QStringLiteral("thread"), i18nc("@info:shell","The of the failing thread"), QStringLiteral("threadid")); + QCommandLineOption dialogOption(QStringLiteral("dialog"), i18nc("@info:shell","Do not show a notification but launch the debug dialog directly")); parser.addOption(signalOption); parser.addOption(appNameOption); parser.addOption(appPathOption); parser.addOption(appVersionOption); parser.addOption(bugAddressOption); parser.addOption(programNameOption); parser.addOption(pidOption); parser.addOption(startupIdOption); parser.addOption(kdeinitOption); parser.addOption(saferOption); parser.addOption(restartedOption); parser.addOption(keepRunningOption); parser.addOption(threadOption); + parser.addOption(dialogOption); aboutData.setupCommandLine(&parser); parser.process(qa); aboutData.processCommandLine(&parser); DrKonqi::setSignal(parser.value(signalOption).toInt()); DrKonqi::setAppName(parser.value(appNameOption)); DrKonqi::setAppPath(parser.value(appPathOption)); DrKonqi::setAppVersion(parser.value(appVersionOption)); DrKonqi::setBugAddress(parser.value(bugAddressOption)); DrKonqi::setProgramName(parser.value(programNameOption)); DrKonqi::setPid(parser.value(pidOption).toInt()); DrKonqi::setKdeinit(parser.isSet(kdeinitOption)); DrKonqi::setSafer(parser.isSet(saferOption)); DrKonqi::setRestarted(parser.isSet(restartedOption)); DrKonqi::setKeepRunning(parser.isSet(keepRunningOption)); DrKonqi::setThread(parser.value(threadOption).toInt()); + auto forceDialog = parser.isSet(dialogOption); #if HAVE_X11 const QString startupId = parser.value(startupIdOption); if (!startupId.isEmpty()) { QX11Info::setNextStartupId(startupId.toUtf8()); } #endif if (!DrKonqi::init()) { return 1; } qa.setQuitOnLastWindowClosed(false); auto openDrKonqiDialog = [&qa]{ DrKonqiDialog *w = new DrKonqiDialog(); QObject::connect(w, &DrKonqiDialog::rejected, &qa, &QApplication::quit); w->show(); }; bool restarted = parser.isSet(restartedOption); // if no notification service is running (eg. shell crashed, or other desktop environment) // and we didn't auto-restart the app, open DrKonqi dialog instead of showing an SNI // and emitting a desktop notification - if (!restarted && !StatusNotifier::notificationServiceRegistered()) { + if (forceDialog || (!restarted && !StatusNotifier::notificationServiceRegistered())) { openDrKonqiDialog(); } else { StatusNotifier *statusNotifier = new StatusNotifier(); if (!restarted) { statusNotifier->notify(); } QObject::connect(statusNotifier, &StatusNotifier::expired, &qa, &QApplication::quit); QObject::connect(statusNotifier, &StatusNotifier::activated, openDrKonqiDialog); } int ret = qa.exec(); DrKonqi::cleanup(); return ret; } diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 324bb30c..f9ec8886 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,5 +1,32 @@ add_subdirectory(crashtest) add_subdirectory(backtraceparsertest) if(KF5XmlRpcClient_FOUND) add_subdirectory(bugzillalibtest) endif() + +if(NOT RUBY_EXECTUABLE) + find_program(RUBY_EXECTUABLE ruby) +endif() +if(RUBY_EXECUTABLE) + execute_process(COMMAND ${RUBY_EXECUTABLE} -e "require 'atspi'" + RESULT_VARIABLE RUBY_ATSPI) + execute_process(COMMAND ${RUBY_EXECUTABLE} -e "require 'xmlrpc/server'" + RESULT_VARIABLE RUBY_XMLRPC) +endif() +if(NOT XVFB_RUN_EXECTUABLE) + find_program(XVFB_RUN_EXECTUABLE xvfb-run) +endif() +if(NOT ATSPI_BUS_LAUNCHER_EXECUTABLE) + find_program(ATSPI_BUS_LAUNCHER_EXECUTABLE + NAMES at-spi-bus-launcher + PATHS /usr/lib/at-spi2-core/ + DOC "AT-SPI accessibility dbus launcher") +endif() + +if(RUBY_EXECUTABLE AND XVFB_RUN_EXECTUABLE AND ATSPI_BUS_LAUNCHER_EXECUTABLE + AND RUBY_ATSPI EQUAL 0 AND RUBY_XMLRPC EQUAL 0) + set(WITH_DRKONI_INTEGRATION_TESTING TRUE) +endif() +add_feature_info(DrKonqiIntegrationTesting WITH_DRKONI_INTEGRATION_TESTING + "Needs Ruby, functional atspi and xmlrpc gems, as well as xvfb-run.") +add_subdirectory(integration) diff --git a/src/tests/integration/CMakeLists.txt b/src/tests/integration/CMakeLists.txt new file mode 100644 index 00000000..0bf266fe --- /dev/null +++ b/src/tests/integration/CMakeLists.txt @@ -0,0 +1,9 @@ +add_test(NAME drkonqi_integration_suite + COMMAND ${RUBY_EXECTUABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/suite + --drkonqi $ + --at-spi-bus-launcher ${ATSPI_BUS_LAUNCHER_EXECUTABLE}) + +# Hack to get rb files to show in qtc. +file(GLOB RUBIES suite *.rb) +add_custom_target(Rubies ALL echo SOURCES ${RUBIES}) diff --git a/src/tests/integration/duplicate_attach_test.rb b/src/tests/integration/duplicate_attach_test.rb new file mode 100644 index 00000000..f33879ee --- /dev/null +++ b/src/tests/integration/duplicate_attach_test.rb @@ -0,0 +1,215 @@ +# Copyright (C) 2017 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) version 3 or any later version +# accepted by the membership of KDE e.V. (or its successor approved +# by the membership of KDE e.V.), which shall act as a proxy +# defined in Section 14 of version 3 of the license. +# +# 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 . + +require_relative 'test_helper' + +require 'xmlrpc/client' +require 'xmlrpc/server' + +# Monkey patch the xmlrpc server to let us handle regular GET requests. +# Drkonqi partially goes through regular bugzilla cgi's simply requesting xml +# output. +module XMLServerInterceptor + # raw xml data is here + # def process(*args) + # warn "+++ #{__method__} +++" + # p args + # warn "--- #{__method__} ---" + # super + # end + + def service(req, resp) + # Where webrick comes in with request, server rejects non-xmlrpc requests + # so we'll manually handle GET requests as necessary and forward to an + # actual bugzilla so we don't have to reimplement everything. + warn "+++ #{__method__} +++" + if req.request_method == 'GET' + if req.request_uri.path.include?('buglist.cgi') # Returns CSV. + resp.body = <<-EOF +bug_id,"bug_severity","priority","bug_status","product","short_desc","resolution" +375161,"crash","NOR","NEEDSINFO","dolphin","Dolphin crash, copy from Samba share","BACKTRACE" + EOF + return + end + + if req.request_uri.path.include?('show_bug.cgi') + uri = req.request_uri.dup + uri.host = 'bugstest.kde.org' + uri.scheme = 'https' + uri.port = nil + resp.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, uri.to_s) + return + end + end + warn "--- #{__method__} ---" + super + end +end + +class XMLRPC::Server + prepend XMLServerInterceptor + + # Expose webrick server so we can get our port :| + # https://github.com/ruby/xmlrpc/issues/17 + attr_accessor :server +end + +class TestDuplicateAttach < ATSPITest + def setup + server = XMLRPC::Server.new(0) + port = server.server.config.fetch(:Port) + ENV['DRKONQI_KDE_BUGZILLA_URL'] = "http://localhost:#{port}/" + + @got_comment = false + + server.set_default_handler do |name, args| + puts '+++ handler +++' + p name, args + if name == 'User.login' + next {"id"=>12345, "token"=>"12345-cJ5o717AbC"} + end + if name == 'Bug.update' + id = args.fetch('ids').fetch(0) + cc_to_add = args.fetch('cc').fetch('add') + next {"bugs"=>[{"last_change_time"=>DateTime.now, "id"=>id, "changes"=>{"cc"=>{"removed"=>"", "added"=>cc_to_add}}, "alias"=>[]}]} + end + if name == 'Bug.add_attachment' + # Check for garbage string from test + @got_comment = args.fetch('comment').include?('yyyyyyyyyyyyyyyy') + next { "ids" => [1234] } + end + puts '~~~ bugzilla ~~~' + # Pipe request through bugstest. + # The arguments are killing me. + client = XMLRPC::Client.new('bugstest.kde.org', '/xmlrpc.cgi', 443, + nil, nil, nil, nil, true) + bugzilla = client.call(name, *args) + p bugzilla + next bugzilla + end + + @xml_server_thread = Thread.start { server.serve } + + @tracee = fork { loop { sleep(999_999_999) } } + + assert File.exist?(DRKONQI_PATH), "drkonqi not at #{DRKONQI_PATH}" + # Will die with our Xephyr in case of errors. + pid = spawn("#{DRKONQI_PATH} --signal 11 --pid #{@tracee} --bugaddress submit@bugs.kde.org --dialog") + sleep 4 # Grace to give time to appear on at-spi + assert_nil Process.waitpid(pid, Process::WNOHANG), + 'drkonqi failed to start or died' + end + + def teardown + Process.kill('KILL', @tracee) + Process.waitpid2(@tracee) + @xml_server_thread.kill + @xml_server_thread.join + end + + # When evaluating duplicates + def test_duplicate_attach + drkonqi = ATSPI.desktops[0].applications.find { |x| x.name == 'drkonqi' } + refute_nil drkonqi + + accessible = find_in(drkonqi.windows[-1], name: 'Report Bug') + press(accessible) + + find_in(drkonqi, name: 'Crash Reporting Assistant') do |window| + accessible = find_in(window, name: 'Next') + press(accessible) + + accessible = find_in(window, name: 'Yes') + toggle_on(accessible) + + accessible = find_in(window, name: /^What I was doing when the application.+/) + toggle_on(accessible) + + accessible = find_in(window, name: 'Next') + press(accessible) + + loop do + # Drkonqi is now doing the trace, wait until it is done. + accessible = find_in(window, name: 'Next') + refute_nil accessible + if accessible.states.include?(:sensitive) + press(accessible) + break + end + warn accessible.states + sleep 2 + end + + # Set pseudo login data if there are none. + accessible = find_in(window, name: 'Username input') + accessible.text.set_to 'xxx' if accessible.text.length <= 0 + accessible = find_in(window, name: 'Password input') + accessible.text.set_to 'yyy' if accessible.text.length <= 0 + + accessible = find_in(window, name: 'Login') + press(accessible) + + sleep 2 # Wait for login and bug listing + + accessible = find_in(window, name: '375161') + toggle_on(accessible) + + accessible = find_in(window, name: 'Open selected report') + press(accessible) + end + + find_in(drkonqi, name: 'Bug Description') do |window| + accessible = find_in(window, name: 'Suggest this crash is related') + press(accessible) + end + + find_in(drkonqi, name: 'Related Bug Report') do |window| + accessible = find_in(window, name: /^Completely sure: attach my information.+/) + toggle_on(accessible) + + accessible = find_in(window, name: 'Continue') + press(accessible) + end + + find_in(drkonqi, name: 'Crash Reporting Assistant') do |window| + accessible = find_in(window, name: /^The report is going to be attached.+/) + refute_nil accessible + + accessible = find_in(window, name: 'Next') + press(accessible) + + accessible = find_in(window, name: 'Information about the crash text') + accessible.text.set_to(accessible.text.to_s + + Array.new(128).collect { 'y' }.join) + + accessible = find_in(window, name: 'Next') + press(accessible) + + accessible = find_in(window, name: 'Next') + press(accessible) + + accessible = find_in(window, name: /.*Crash report sent.*/) + refute_nil accessible + + accessible = find_in(window, name: 'Finish') + press(accessible) + end + + assert @got_comment # only true iff the server go tour yyyyyy garbage string + end +end diff --git a/src/tests/integration/suite b/src/tests/integration/suite new file mode 100755 index 00000000..f5404dfb --- /dev/null +++ b/src/tests/integration/suite @@ -0,0 +1,81 @@ +#!/usr/bin/env ruby +# +# Copyright (C) 2017 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) version 3 or any later version +# accepted by the membership of KDE e.V. (or its successor approved +# by the membership of KDE e.V.), which shall act as a proxy +# defined in Section 14 of version 3 of the license. +# +# 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 . + +require 'fileutils' +require 'optparse' +require 'tmpdir' + +# Isolates GUI via xephyr +class XephyrIsolator + def run(cmd) + ephemeral('Xephyr -screen 1024x768x24+32 :666') do + ENV['DISPLAY'] = ':666' + system(cmd) || raise + end + end + + private + + def ephemeral(*args) + pid = spawn(*args) + yield + ensure + Process.kill('KILL', pid) if pid + Process.waitpid2(pid) if pid + end +end + +# Isolates GUI via xvfb-run +class XvfbIsolator + def run(cmd) + system("xvfb-run -a --server-args=\"-screen 0 1024x768x24\" #{cmd}") || raise + end +end + +OptionParser.new do |opts| + opts.banner = "Usage: #{$0} ARGS" + + opts.separator('') + + opts.on('--drkonqi PATH', 'Path to drkonqi bin to test.') do |v| + ENV['DRKONQI_PATH'] = v + end + + opts.on('--at-spi-bus-launcher PATH', + 'Path to --at-spi-bus-launcher bin to use for testing.') do |v| + ENV['AT_SPI_BUS_LAUNCHER_PATH'] = v + end +end.parse! + +ENV['DRKONQI_PATH'] ||= '/usr/lib/x86_64-linux-gnu/libexec/drkonqi' +ENV['AT_SPI_BUS_LAUNCHER_PATH'] ||= '/usr/lib/at-spi2-core/at-spi-bus-launcher' + +# Isolate ourselves by forcing into a separate home and unsetting the XDG path +# variables. Then spin up a suitable virtual X, run a new dbus session bus +# and our test in that environment. +Dir.mktmpdir do |tmpdir| + ENV['HOME'] = tmpdir + ENV.keys.each { |k| ENV.delete(k) if k.start_with?('XDG_') } + Dir.glob("#{__dir__}/*_test.rb").each do |test| + isolator = ENV['XEPHYR'] ? XephyrIsolator.new : XvfbIsolator.new + isolator.run("dbus-run-session -- ruby #{test} -p") + end + sleep 8 # Wait a bit to make sure all children are dead. +end diff --git a/src/tests/integration/test_helper.rb b/src/tests/integration/test_helper.rb new file mode 100644 index 00000000..3f538941 --- /dev/null +++ b/src/tests/integration/test_helper.rb @@ -0,0 +1,85 @@ +# Copyright (C) 2017 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) version 3 or any later version +# accepted by the membership of KDE e.V. (or its successor approved +# by the membership of KDE e.V.), which shall act as a proxy +# defined in Section 14 of version 3 of the license. +# +# 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 . + +ENV['DRKONQI_IGNORE_QUALITY'] = '1' +ENV['KDE_FORK_SLAVES'] = '1' + +DRKONQI_PATH = ENV['DRKONQI_PATH'] +AT_SPI_BUS_LAUNCHER_PATH = ENV['AT_SPI_BUS_LAUNCHER_PATH'] +warn "Testing against #{DRKONQI_PATH} with #{AT_SPI_BUS_LAUNCHER_PATH}" + +# Dies together with our dbus. +spawn "#{AT_SPI_BUS_LAUNCHER_PATH} --launch-immediately" + +require 'atspi' +require 'minitest/autorun' + +# Adds convenience methods for ATSPI on top of minitest. +class ATSPITest < Minitest::Test + def find_in(parent, name: nil, recursion: false) + raise 'no accessible' if parent.nil? + accessibles = parent.children.collect do |child| + ret = [] + if child.children.size != 0 # recurse + ret += find_in(child, name: name, recursion: true) + end + if name && child.states.include?(:showing) + if (name.is_a?(Regexp) && child.name.match(name)) || + (name.is_a?(String) && child.name == name) + ret << child + end + end + ret + end.compact.uniq.flatten + return accessibles if recursion + raise "not exactly one accessible for #{name} => #{accessibles.collect {|x| x.name}.join(', ')}" if accessibles.size > 1 + raise "cannot find accessible(#{name})" if accessibles.size < 1 + yield accessibles[0] if block_given? + accessibles[0] + end + + def press(accessible) + raise 'no accessible' if accessible.nil? + action = accessible.actions.find { |x| x.name == 'Press' } + refute_nil action, 'expected accessible to be pressable' + action.do_it! + sleep 0.25 + end + + def focus(accessible) + raise 'no accessible' if accessible.nil? + action = accessible.actions.find { |x| x.name == 'SetFocus' } + refute_nil action, 'expected accessible to be focusable' + action.do_it! + sleep 0.1 + end + + def toggle(accessible) + raise 'no accessible' if accessible.nil? + action = accessible.actions.find { |x| x.name == 'Toggle' } + refute_nil action, 'expected accessible to be toggle' + action.do_it! + sleep 0.1 + end + + def toggle_on(accessible) + raise 'no accessible' if accessible.nil? + return if accessible.states.any? { |x| %i[checked selected].include?(x) } + toggle(accessible) + end +end