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("
").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("
").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("
").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("
").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 closedBug %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)",
"
",
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 @@
AssistantPageBugzillaInformation00513345<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
+ falseDistribution method:KDE Platform is compiled from sourcesQt::VerticalQSizePolicy::Fixed2010<i>Note</i>: The crash and system information will be automatically added to the bug report.trueKComboBoxQComboBoxkcombobox.hKLineEditQLineEditklineedit.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 @@
AssistantPageBugzillaLogin00488472Qt::VerticalQSizePolicy::Fixed205Username:Password:
+
+ Password input
+ truetrue
+
+ Username input
+ trueSave login information using the KDE Wallet systemQt::Horizontal4020Qt::VerticalQSizePolicy::Fixed2010truetrueQt::Vertical2040KLineEditQLineEditklineedit.hStatusWidgetQWidgetstatuswidget.h1m_userEditm_passwordEditm_savePasswordCheckBoxm_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