diff --git a/drkonqi/CMakeLists.txt b/drkonqi/CMakeLists.txt index deb8c401d..cdae1d49a 100644 --- a/drkonqi/CMakeLists.txt +++ b/drkonqi/CMakeLists.txt @@ -1,115 +1,116 @@ 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}) diff --git a/drkonqi/bugzillaintegration/bugzillalib.cpp b/drkonqi/bugzillaintegration/bugzillalib.cpp index 802c5fb71..70bd3eb31 100644 --- a/drkonqi/bugzillaintegration/bugzillalib.cpp +++ b/drkonqi/bugzillaintegration/bugzillalib.cpp @@ -1,685 +1,685 @@ /******************************************************************* * bugzillalib.cpp * Copyright 2009, 2011 Dario Andres Rodriguez * Copyright 2012 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 "bugzillalib.h" #include #include #include #include #include #include #include #include #include #include #include #define MAKE_BUGZILLA_VERSION(a,b,c) (((a) << 16) | ((b) << 8) | (c)) static const char columns[] = "bug_severity,priority,bug_status,product,short_desc,resolution"; //Bugzilla URLs static const char searchUrl[] = "buglist.cgi?query_format=advanced&order=Importance&ctype=csv" "&product=%1" "&longdesc_type=allwordssubstr&longdesc=%2" "&chfieldfrom=%3&chfieldto=%4&chfield=[Bug+creation]" "&bug_severity=%5" "&columnlist=%6"; // short_desc, product, long_desc(possible backtraces lines), searchFrom, searchTo, severity, columnList static const char showBugUrl[] = "show_bug.cgi?id=%1"; static const char fetchBugUrl[] = "show_bug.cgi?id=%1&ctype=xml"; static inline Component buildComponent(const QVariantMap& map); static inline Version buildVersion(const QVariantMap& map); static inline Product buildProduct(const QVariantMap& map); //BEGIN BugzillaManager BugzillaManager::BugzillaManager(const QString &bugTrackerUrl, QObject *parent) : QObject(parent) , m_bugTrackerUrl(bugTrackerUrl) , m_logged(false) , m_searchJob(0) { m_xmlRpcClient = new KXmlRpc::Client(QUrl(m_bugTrackerUrl + "xmlrpc.cgi"), this); m_xmlRpcClient->setUserAgent(QLatin1String("DrKonqi")); // Allow constructors for ReportInterface and assistant dialogs to finish. // We do not want them to be racing the remote Bugzilla database. QMetaObject::invokeMethod (this, "lookupVersion", Qt::QueuedConnection); } // BEGIN Checks of Bugzilla software versions. void BugzillaManager::lookupVersion() { QMap args; callBugzilla("Bugzilla.version", "version", args, SecurityDisabled); } void BugzillaManager::setFeaturesForVersion(const QString& version) { // A procedure to change Dr Konqi behaviour automatically when Bugzilla // software versions change. // // Changes should be added to Dr Konqi AHEAD of when the corresponding // Bugzilla software changes are released into bugs.kde.org, so that // Dr Konqi can continue to operate smoothly, without bug reports and a // reactive KDE software release. // // If Bugzilla announces a change to its software that affects Dr Konqi, // add executable code to implement the change automatically when the // Bugzilla software version changes. It goes at the end of this procedure // and elsewhere in this class (BugzillaManager) and/or other classes where // the change should actually be implemented. const int nVersionParts = 3; QString seps = QLatin1String("[._-]"); QStringList digits = version.split(QRegExp(seps), QString::SkipEmptyParts); while (digits.count() < nVersionParts) { digits << QLatin1String("0"); } if (digits.count() > nVersionParts) { qWarning() << QStringLiteral("Current Bugzilla version %1 has more than %2 parts. Check that this is not a problem.").arg(version).arg(nVersionParts); } int currentVersion = MAKE_BUGZILLA_VERSION(digits.at(0).toUInt(), digits.at(1).toUInt(), digits.at(2).toUInt()); // Set the code(s) for historical versions of Bugzilla - before any change. m_security = UseCookies; // Used to have cookies for update-security. if (currentVersion >= MAKE_BUGZILLA_VERSION(4, 4, 3)) { // Security method changes from cookies to tokens in Bugzilla 4.4.3. // BUT, tokens fail when kio_http sends any cookies found in KCookieJar, // so go directly to passwords-only security (supported since Bugzilla // 3.6 and will be enforced in Bugzilla 4.5.x). m_security = UsePasswords; } qDebug() << "VERSION" << version << "SECURITY" << m_security; } // END Checks of Bugzilla software versions. // BEGIN Generic remote-procedure (RPC) call to Bugzilla void BugzillaManager::callBugzilla(const char* method, const char* id, QMap& args, SecurityStatus security) { if (security == SecurityEnabled) { switch (m_security) { case UseTokens: qDebug() << method << id << "using token"; args.insert(QLatin1String("Bugzilla_token"), m_token); break; case UsePasswords: qDebug() << method << id << "using username" << m_username; args.insert(QLatin1String("Bugzilla_login"), m_username); args.insert(QLatin1String("Bugzilla_password"), m_password); break; case UseCookies: qDebug() << method << id << "using cookies"; // Some KDE process other than Dr Konqi should provide cookies. break; } } m_xmlRpcClient->call(QLatin1String(method), args, this, SLOT(callMessage(QList,QVariant)), this, SLOT(callFault(int,QString,QVariant)), - QString::fromAscii(id)); + QLatin1String(id)); } // END Generic call to Bugzilla //BEGIN Login methods void BugzillaManager::tryLogin(const QString& username, const QString& password) { m_username = username; if (m_security == UsePasswords) { m_password = password; } m_logged = false; QMap args; args.insert(QLatin1String("login"), username); args.insert(QLatin1String("password"), password); if (m_security == UseCookies) { // Removed in Bugzilla 4.4.3 software, which no longer issues cookies. args.insert(QLatin1String("remember"), false); } callBugzilla("User.login", "login", args, SecurityDisabled); } bool BugzillaManager::getLogged() const { return m_logged; } QString BugzillaManager::getUsername() const { return m_username; } //END Login methods //BEGIN Bugzilla Action methods void BugzillaManager::fetchBugReport(int bugnumber, QObject * jobOwner) { QUrl url(m_bugTrackerUrl + QString(fetchBugUrl).arg(bugnumber)); if (!jobOwner) { jobOwner = this; } KIO::Job * fetchBugJob = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); fetchBugJob->setParent(jobOwner); connect(fetchBugJob, &KIO::Job::finished, this, &BugzillaManager::fetchBugJobFinished); } void BugzillaManager::searchBugs(const QStringList & products, const QString & severity, const QString & date_start, const QString & date_end, QString comment) { QString product; if (products.size() > 0) { if (products.size() == 1) { product = products.at(0); } else { Q_FOREACH(const QString & p, products) { product += p + "&product="; } product = product.mid(0,product.size()-9); } } QString url = QString(m_bugTrackerUrl) + QString(searchUrl).arg(product, comment.replace(' ' , '+'), date_start, date_end, severity, QString(columns)); stopCurrentSearch(); m_searchJob = KIO::storedGet(QUrl(url) , KIO::Reload, KIO::HideProgressInfo); connect(m_searchJob, &KIO::Job::finished, this, &BugzillaManager::searchBugsJobFinished); } void BugzillaManager::sendReport(const BugReport & report) { QMap args; args.insert(QLatin1String("product"), report.product()); args.insert(QLatin1String("component"), report.component()); args.insert(QLatin1String("version"), report.version()); args.insert(QLatin1String("summary"), report.shortDescription()); args.insert(QLatin1String("description"), report.description()); args.insert(QLatin1String("op_sys"), report.operatingSystem()); args.insert(QLatin1String("platform"), report.platform()); args.insert(QLatin1String("keywords"), report.keywords()); args.insert(QLatin1String("priority"), report.priority()); args.insert(QLatin1String("severity"), report.bugSeverity()); callBugzilla("Bug.create", "Bug.create", args, SecurityEnabled); } void BugzillaManager::attachTextToReport(const QString & text, const QString & filename, const QString & summary, int bugId, const QString & comment) { QMap args; args.insert(QLatin1String("ids"), QVariantList() << bugId); args.insert(QLatin1String("file_name"), filename); args.insert(QLatin1String("summary"), summary); args.insert(QLatin1String("comment"), comment); - args.insert(QLatin1String("content_type"), QString::fromAscii("text/plain")); + args.insert(QLatin1String("content_type"), QLatin1String("text/plain")); //data needs to be a QByteArray so that it is encoded in base64 (query.cpp:246) args.insert(QLatin1String("data"), text.toUtf8()); callBugzilla("Bug.add_attachment", "Bug.add_attachment", args, SecurityEnabled); } void BugzillaManager::addMeToCC(int bugId) { QMap args; args.insert(QLatin1String("ids"), QVariantList() << bugId); QMap ccChanges; ccChanges.insert(QLatin1String("add"), QVariantList() << m_username); args.insert(QLatin1String("cc"), ccChanges); callBugzilla("Bug.update", "Bug.update.cc", args, SecurityEnabled); } void BugzillaManager::fetchProductInfo(const QString & product) { QMap args; args.insert(QStringLiteral("names"), (QStringList() << product) ) ; QStringList includeFields; // currently we only need these informations includeFields << QStringLiteral("name") << QStringLiteral("is_active") << QStringLiteral("components") << QStringLiteral("versions"); args.insert(QStringLiteral("include_fields"), includeFields) ; callBugzilla("Product.get", "Product.get.versions", args, SecurityDisabled); } //END Bugzilla Action methods //BEGIN Misc methods QString BugzillaManager::urlForBug(int bug_number) const { return QString(m_bugTrackerUrl) + QString(showBugUrl).arg(bug_number); } void BugzillaManager::stopCurrentSearch() { if (m_searchJob) { //Stop previous searchJob m_searchJob->disconnect(); m_searchJob->kill(); m_searchJob = 0; } } //END Misc methods //BEGIN Slots to handle KJob::finished void BugzillaManager::fetchBugJobFinished(KJob* job) { if (!job->error()) { KIO::StoredTransferJob * fetchBugJob = static_cast(job); BugReportXMLParser * parser = new BugReportXMLParser(fetchBugJob->data()); BugReport report = parser->parse(); if (parser->isValid()) { emit bugReportFetched(report, job->parent()); } else { emit bugReportError(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."), job->parent()); } delete parser; } else { emit bugReportError(job->errorString(), job->parent()); } } void BugzillaManager::searchBugsJobFinished(KJob * job) { if (!job->error()) { KIO::StoredTransferJob * searchBugsJob = static_cast(job); BugListCSVParser * parser = new BugListCSVParser(searchBugsJob->data()); BugMapList list = parser->parse(); if (parser->isValid()) { emit searchFinished(list); } else { emit searchError(i18nc("@info","Invalid bug list: corrupted data")); } delete parser; } else { emit searchError(job->errorString()); } m_searchJob = 0; } static inline Component buildComponent(const QVariantMap& map) { QString name = map.value(QStringLiteral("name")).toString(); bool active = map.value(QStringLiteral("is_active")).toBool(); return Component(name, active); } static inline Version buildVersion(const QVariantMap& map) { QString name = map.value(QStringLiteral("name")).toString(); bool active = map.value(QStringLiteral("is_active")).toBool(); return Version(name, active); } static inline Product buildProduct(const QVariantMap& map) { QString name = map.value(QStringLiteral("name")).toString(); bool active = map.value(QStringLiteral("is_active")).toBool(); Product product(name, active); QVariantList components = map.value(QStringLiteral("components")).toList(); foreach (const QVariant& c, components) { Component component = buildComponent(c.toMap()); product.addComponent(component); } QVariantList versions = map.value(QStringLiteral("versions")).toList(); foreach (const QVariant& v, versions) { Version version = buildVersion(v.toMap()); product.addVersion(version); } return product; } void BugzillaManager::fetchProductInfoFinished(const QVariantMap & map) { QList products; QVariantList plist = map.value(QStringLiteral("products")).toList(); foreach (const QVariant& p, plist) { Product product = buildProduct(p.toMap()); products.append(product); } if ( products.size() > 0 ) { emit productInfoFetched(products.at(0)); } else { emit productInfoError(); } } //END Slots to handle KJob::finished void BugzillaManager::callMessage(const QList & result, const QVariant & id) { qDebug() << id << result; if (id.toString() == QLatin1String("login")) { if ((m_security == UseTokens) && (result.count() > 0)) { QVariantMap map = result.at(0).toMap(); m_token = map.value(QLatin1String("token")).toString(); } m_logged = true; Q_EMIT loginFinished(true); } else if (id.toString() == QLatin1String("Product.get.versions")) { QVariantMap map = result.at(0).toMap(); fetchProductInfoFinished(map); } else if (id.toString() == QLatin1String("Bug.create")) { QVariantMap map = result.at(0).toMap(); int bug_id = map.value(QLatin1String("id")).toInt(); Q_ASSERT(bug_id != 0); Q_EMIT reportSent(bug_id); } else if (id.toString() == QLatin1String("Bug.add_attachment")) { QVariantMap map = result.at(0).toMap(); if (map.contains(QLatin1String("attachments"))){ // for bugzilla 4.2 map = map.value(QLatin1String("attachments")).toMap(); map = map.constBegin()->toMap(); const int attachment_id = map.value(QLatin1String("id")).toInt(); Q_EMIT attachToReportSent(attachment_id); } else if (map.contains(QLatin1String("ids"))) { // for bugzilla 4.4 const int attachment_id = map.value(QLatin1String("ids")).toList().at(0).toInt(); Q_EMIT attachToReportSent(attachment_id); } } else if (id.toString() == QLatin1String("Bug.update.cc")) { QVariantMap map = result.at(0).toMap().value(QLatin1String("bugs")).toList().at(0).toMap(); int bug_id = map.value(QLatin1String("id")).toInt(); Q_ASSERT(bug_id != 0); Q_EMIT addMeToCCFinished(bug_id); } else if (id.toString() == QLatin1String("version")) { QVariantMap map = result.at(0).toMap(); QString bugzillaVersion = map.value(QLatin1String("version")).toString(); setFeaturesForVersion(bugzillaVersion); Q_EMIT bugzillaVersionFound(); } } void BugzillaManager::callFault(int errorCode, const QString & errorString, const QVariant & id) { qDebug() << id << errorCode << errorString; QString genericError = i18nc("@info", "Received unexpected error code %1 from bugzilla. " "Error message was: %2", errorCode, errorString); if (id.toString() == QLatin1String("login")) { switch(errorCode) { case 300: //invalid username or password Q_EMIT loginFinished(false); //TODO replace with loginError break; default: Q_EMIT loginError(genericError); break; } } else if (id.toString() == QLatin1String("Bug.create")) { switch (errorCode) { case 51: //invalid object (one example is invalid platform value) case 105: //invalid component case 106: //invalid product Q_EMIT sendReportErrorInvalidValues(); break; default: Q_EMIT sendReportError(genericError); break; } } else if (id.toString() == QLatin1String("Bug.add_attachment")) { switch (errorCode) { default: Q_EMIT attachToReportError(genericError); break; } } else if (id.toString() == QLatin1String("Bug.update.cc")) { switch (errorCode) { default: Q_EMIT addMeToCCError(genericError); break; } } } //END BugzillaManager //BEGIN BugzillaCSVParser BugListCSVParser::BugListCSVParser(const QByteArray& data) { m_data = data; m_isValid = false; } BugMapList BugListCSVParser::parse() { BugMapList list; if (!m_data.isEmpty()) { //Parse buglist CSV QTextStream ts(&m_data); QString headersLine = ts.readLine().remove(QLatin1Char('\"')) ; //Discard headers QString expectedHeadersLine = QString(columns); if (headersLine == (QStringLiteral("bug_id,") + expectedHeadersLine)) { QStringList headers = expectedHeadersLine.split(',', QString::KeepEmptyParts); int headersCount = headers.count(); while (!ts.atEnd()) { BugMap bug; //bug report data map QString line = ts.readLine(); //Get bug_id (always at first column) int bug_id_index = line.indexOf(','); QString bug_id = line.left(bug_id_index); bug.insert(QStringLiteral("bug_id"), bug_id); line = line.mid(bug_id_index + 2); QStringList fields = line.split(QStringLiteral(",\"")); for (int i = 0; i < headersCount && i < fields.count(); i++) { QString field = fields.at(i); field = field.left(field.size() - 1) ; //Remove trailing " bug.insert(headers.at(i), field); } list.append(bug); } m_isValid = true; } } return list; } //END BugzillaCSVParser //BEGIN BugzillaXMLParser BugReportXMLParser::BugReportXMLParser(const QByteArray & data) { m_valid = m_xml.setContent(data, true); } BugReport BugReportXMLParser::parse() { BugReport report; //creates an invalid and empty report object if (m_valid) { //Check bug notfound QDomNodeList bug_number = m_xml.elementsByTagName(QStringLiteral("bug")); QDomNode d = bug_number.at(0); QDomNamedNodeMap a = d.attributes(); QDomNode d2 = a.namedItem(QStringLiteral("error")); m_valid = d2.isNull(); if (m_valid) { report.setValid(true); //Get basic fields report.setBugNumber(getSimpleValue(QStringLiteral("bug_id"))); report.setShortDescription(getSimpleValue(QStringLiteral("short_desc"))); report.setProduct(getSimpleValue(QStringLiteral("product"))); report.setComponent(getSimpleValue(QStringLiteral("component"))); report.setVersion(getSimpleValue(QStringLiteral("version"))); report.setOperatingSystem(getSimpleValue(QStringLiteral("op_sys"))); report.setBugStatus(getSimpleValue(QStringLiteral("bug_status"))); report.setResolution(getSimpleValue(QStringLiteral("resolution"))); report.setPriority(getSimpleValue(QStringLiteral("priority"))); report.setBugSeverity(getSimpleValue(QStringLiteral("bug_severity"))); report.setMarkedAsDuplicateOf(getSimpleValue(QStringLiteral("dup_id"))); report.setVersionFixedIn(getSimpleValue(QStringLiteral("cf_versionfixedin"))); //Parse full content + comments QStringList m_commentList; QDomNodeList comments = m_xml.elementsByTagName(QStringLiteral("long_desc")); for (int i = 0; i < comments.count(); i++) { QDomElement element = comments.at(i).firstChildElement(QStringLiteral("thetext")); m_commentList << element.text(); } report.setComments(m_commentList); } //isValid } //isValid return report; } QString BugReportXMLParser::getSimpleValue(const QString & name) //Extract an unique tag from XML { QString ret; QDomNodeList bug_number = m_xml.elementsByTagName(name); if (bug_number.count() == 1) { QDomNode node = bug_number.at(0); ret = node.toElement().text(); } return ret; } //END BugzillaXMLParser void BugReport::setBugStatus(const QString &stat) { setData(QStringLiteral("bug_status"), stat); m_status = parseStatus(stat); } void BugReport::setResolution(const QString &res) { setData(QStringLiteral("resolution"), res); m_resolution = parseResolution(res); } BugReport::Status BugReport::parseStatus(const QString &stat) { if (stat == QLatin1String("UNCONFIRMED")) { return Unconfirmed; } else if (stat == QLatin1String("CONFIRMED")) { return New; } else if (stat == QLatin1String("ASSIGNED")) { return Assigned; } else if (stat == QLatin1String("REOPENED")) { return Reopened; } else if (stat == QLatin1String("RESOLVED")) { return Resolved; } else if (stat == QLatin1String("NEEDSINFO")) { return NeedsInfo; } else if (stat == QLatin1String("VERIFIED")) { return Verified; } else if (stat == QLatin1String("CLOSED")) { return Closed; } else { return UnknownStatus; } } BugReport::Resolution BugReport::parseResolution(const QString &res) { if (res.isEmpty()) { return NotResolved; } else if (res == QLatin1String("FIXED")) { return Fixed; } else if (res == QLatin1String("INVALID")) { return Invalid; } else if (res == QLatin1String("WONTFIX")) { return WontFix; } else if (res == QLatin1String("LATER")) { return Later; } else if (res == QLatin1String("REMIND")) { return Remind; } else if (res == QLatin1String("DUPLICATE")) { return Duplicate; } else if (res == QLatin1String("WORKSFORME")) { return WorksForMe; } else if (res == QLatin1String("MOVED")) { return Moved; } else if (res == QLatin1String("UPSTREAM")) { return Upstream; } else if (res == QLatin1String("DOWNSTREAM")) { return Downstream; } else if (res == QLatin1String("WAITINGFORINFO")) { return WaitingForInfo; } else if (res == QLatin1String("BACKTRACE")) { return Backtrace; } else if (res == QLatin1String("UNMAINTAINED")) { return Unmaintained; } else { return UnknownResolution; } } diff --git a/drkonqi/bugzillaintegration/reportassistantpages_bugzilla.cpp b/drkonqi/bugzillaintegration/reportassistantpages_bugzilla.cpp index e60fb1473..09f18de25 100644 --- a/drkonqi/bugzillaintegration/reportassistantpages_bugzilla.cpp +++ b/drkonqi/bugzillaintegration/reportassistantpages_bugzilla.cpp @@ -1,886 +1,886 @@ /******************************************************************* * 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 /* 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 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))); } 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))) { //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); 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)); 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")); 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", "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()) { KToolInvocation::invokeBrowser(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"); - QWeakPointer dlg = new QFileDialog(this); - dlg.data()->selectFile(defaultName); - dlg.data()->setWindowTitle(i18nc("@title:window","Select Filename")); - dlg.data()->setAcceptMode(QFileDialog::AcceptSave); - dlg.data()->setFileMode(QFileDialog::AnyFile); - dlg.data()->setConfirmOverwrite(true); - if ( dlg.data()->exec() ) + 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.isNull()) { + if (!dlg) { //Dialog closed externally (ex. via DBus) return; } QUrl fileUrl; - if(!dlg.data()->selectedUrls().isEmpty()) - fileUrl = dlg.data()->selectedUrls().first(); - delete dlg.data(); + 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(tf.fileName(), fileUrl); KJobWidgets::setWindow(job, this); if (!job->exec()) { KMessageBox::sorry(this, job->errorString()); } } } - else - delete dlg.data(); + delete dlg; } //END UnhandledErrorDialog diff --git a/drkonqi/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp b/drkonqi/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp index 4f8f4ea3a..6a4b44fdb 100644 --- a/drkonqi/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp +++ b/drkonqi/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp @@ -1,995 +1,995 @@ /******************************************************************* * 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->setResizeMode(0, QHeaderView::ResizeToContents); - header->setResizeMode(1, QHeaderView::Interactive); + header->setSectionResizeMode(0, QHeaderView::ResizeToContents); + header->setSectionResizeMode(1, QHeaderView::Interactive); //Create manual bug report entry (first one) QTreeWidgetItem * customBugItem = new QTreeWidgetItem( QStringList() << i18nc("@item:intable custom/manaul bug report number", "Manual") << i18nc("@item:intable custom bug report number description", "Manually enter a bug report ID")); customBugItem->setData(0, Qt::UserRole, QLatin1String("custom")); customBugItem->setIcon(1, QIcon::fromTheme(QStringLiteral("edit-rename"))); QString helpMessage = i18nc("@info:tooltip / whatsthis", "Select this option to manually load a specific bug report"); customBugItem->setToolTip(0, helpMessage); customBugItem->setToolTip(1, helpMessage); customBugItem->setWhatsThis(0, helpMessage); customBugItem->setWhatsThis(1, helpMessage); ui.m_bugListWidget->addTopLevelItem(customBugItem); m_searchMoreGuiItem = KGuiItem2(i18nc("@action:button", "Search for more reports"), QIcon::fromTheme(QStringLiteral("edit-find")), i18nc("@info:tooltip", "Use this button to " "search for more similar bug reports on an " "earlier date.")); KGuiItem::assign(ui.m_searchMoreButton, m_searchMoreGuiItem); connect(ui.m_searchMoreButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::searchMore); m_retrySearchGuiItem = KGuiItem2(i18nc("@action:button", "Retry search"), QIcon::fromTheme(QStringLiteral("edit-find")), i18nc("@info:tooltip", "Use this button to " "retry the search that previously " "failed.")); KGuiItem::assign(ui.m_openReportButton, KGuiItem2(i18nc("@action:button", "Open selected report"), QIcon::fromTheme(QStringLiteral("document-preview")), i18nc("@info:tooltip", "Use this button to view " "the information of the selected bug report."))); connect(ui.m_openReportButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::openSelectedReport); KGuiItem::assign(ui.m_stopSearchButton, KGuiItem2(i18nc("@action:button", "Stop searching"), QIcon::fromTheme(QStringLiteral("process-stop")), i18nc("@info:tooltip", "Use this button to stop " "the current search."))); ui.m_stopSearchButton->setText(QString()); //FIXME connect(ui.m_stopSearchButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::stopCurrentSearch); //Possible duplicates list and buttons connect(ui.m_selectedDuplicatesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(itemClicked(QListWidgetItem*))); connect(ui.m_selectedDuplicatesList, &QListWidget::itemSelectionChanged, this, &BugzillaDuplicatesPage::possibleDuplicateSelectionChanged); KGuiItem::assign(ui.m_removeSelectedDuplicateButton, KGuiItem2(i18nc("@action:button remove the selected item from a list", "Remove"), QIcon::fromTheme(QStringLiteral("list-remove")), i18nc("@info:tooltip", "Use this button to remove a selected possible duplicate"))); ui.m_removeSelectedDuplicateButton->setEnabled(false); connect(ui.m_removeSelectedDuplicateButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::removeSelectedDuplicate); ui.m_attachToReportIcon->setPixmap(QIcon::fromTheme(QStringLiteral("mail-attachment")).pixmap(16,16)); ui.m_attachToReportIcon->setFixedSize(16,16); ui.m_attachToReportIcon->setVisible(false); ui.m_attachToReportLabel->setVisible(false); ui.m_attachToReportLabel->setContextMenuPolicy(Qt::NoContextMenu); connect(ui.m_attachToReportLabel, &QLabel::linkActivated, this, &BugzillaDuplicatesPage::cancelAttachToBugReport); ui.information->setContextMenuPolicy(Qt::NoContextMenu); connect(ui.information, &QLabel::linkActivated, this, &BugzillaDuplicatesPage::informationClicked); showDuplicatesPanel(false); } BugzillaDuplicatesPage::~BugzillaDuplicatesPage() { } void BugzillaDuplicatesPage::aboutToShow() { //Perform initial search if we are not currently searching and if there are no results yet if (!m_searching && ui.m_bugListWidget->topLevelItemCount() == 1 && canSearchMore()) { searchMore(); } } void BugzillaDuplicatesPage::aboutToHide() { stopCurrentSearch(); //Save selected possible duplicates by user QStringList possibleDuplicates; int count = ui.m_selectedDuplicatesList->count(); for(int i = 0; iitem(i)->text(); } reportInterface()->setPossibleDuplicates(possibleDuplicates); //Save possible duplicates by query QStringList duplicatesByQuery; count = ui.m_bugListWidget->topLevelItemCount(); for(int i = 1; itopLevelItem(i)->text(0); } reportInterface()->setPossibleDuplicatesByQuery(duplicatesByQuery); } bool BugzillaDuplicatesPage::isComplete() { return !m_searching; } bool BugzillaDuplicatesPage::showNextPage() { //Ask the user to check all the possible duplicates... if (ui.m_bugListWidget->topLevelItemCount() != 1 && ui.m_selectedDuplicatesList->count() == 0 && reportInterface()->attachToBugNumber() == 0 && !m_foundDuplicate) { //The user didn't selected any possible duplicate nor a report to attach the new info. //Double check this, we need to reduce the duplicate count. KGuiItem noDuplicatesButton; noDuplicatesButton.setText(i18n("There are no real duplicates")); noDuplicatesButton.setWhatsThis(i18n("Press this button to declare that, in your opinion " "and according to your experience, the reports found " "as similar do not match the crash you have " "experienced, and you believe it is unlikely that a " "better match would be found after further review.")); noDuplicatesButton.setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"))); KGuiItem letMeCheckMoreReportsButton; letMeCheckMoreReportsButton.setText(i18n("Let me check more reports")); letMeCheckMoreReportsButton.setWhatsThis(i18n("Press this button if you would rather " "review more reports in order to find a " "match for the crash you have experienced.")); letMeCheckMoreReportsButton.setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); if (KMessageBox::questionYesNo(this, i18nc("@info","You have not selected any possible duplicates, or a report to which to attach your " "crash information. Have you read all the reports, and can you confirm that there are no " "real duplicates?"), i18nc("@title:window","No selected possible duplicates"), letMeCheckMoreReportsButton, noDuplicatesButton) == KMessageBox::Yes) { return false; } } return true; } //BEGIN Search related methods void BugzillaDuplicatesPage::searchMore() { //1 year back m_searchingEndDate = m_startDate; m_searchingStartDate = m_searchingEndDate.addYears(-1); performSearch(); } void BugzillaDuplicatesPage::performSearch() { markAsSearching(true); QString startDateStr = m_searchingStartDate.toString(QStringLiteral("yyyy-MM-dd")); QString endDateStr = m_searchingEndDate.toString(QStringLiteral("yyyy-MM-dd")); ui.m_statusWidget->setBusy(i18nc("@info:status","Searching for duplicates (from %1 to %2)...", startDateStr, endDateStr)); //Bugzilla will not search on Today bugs if we send the date. //we need to send "Now" if (m_searchingEndDate == QDate::currentDate()) { endDateStr = QLatin1String("Now"); } #if 1 BugReport report = reportInterface()->newBugReportTemplate(); bugzillaManager()->searchBugs(reportInterface()->relatedBugzillaProducts(), report.bugSeverity(), startDateStr, endDateStr, reportInterface()->firstBacktraceFunctions().join(QStringLiteral(" "))); #else //Test search bugzillaManager()->searchBugs(QStringList() << "plasma", "crash", startDateStr, endDateStr, "QGraphicsScenePrivate::processDirtyItemsRecursive"); #endif } void BugzillaDuplicatesPage::stopCurrentSearch() { if (m_searching) { bugzillaManager()->stopCurrentSearch(); markAsSearching(false); if (m_startDate==m_endDate) { //Never searched ui.m_statusWidget->setIdle(i18nc("@info:status","Search stopped.")); } else { ui.m_statusWidget->setIdle(i18nc("@info:status","Search stopped. Showing results from " "%1 to %2", m_startDate.toString(QStringLiteral("yyyy-MM-dd")), m_endDate.toString(QStringLiteral("yyyy-MM-dd")))); } } } void BugzillaDuplicatesPage::markAsSearching(bool searching) { m_searching = searching; emitCompleteChanged(); ui.m_bugListWidget->setEnabled(!searching); ui.m_searchMoreButton->setEnabled(!searching); ui.m_searchMoreButton->setVisible(!searching); ui.m_stopSearchButton->setEnabled(searching); ui.m_stopSearchButton->setVisible(searching); ui.m_selectedDuplicatesList->setEnabled(!searching); ui.m_selectedPossibleDuplicatesLabel->setEnabled(!searching); ui.m_removeSelectedDuplicateButton->setEnabled(!searching && !ui.m_selectedDuplicatesList->selectedItems().isEmpty()); ui.m_attachToReportLabel->setEnabled(!searching); if (!searching) { itemSelectionChanged(); } else { ui.m_openReportButton->setEnabled(false); } } bool BugzillaDuplicatesPage::canSearchMore() { return (m_startDate.year() >= 2009); } void BugzillaDuplicatesPage::searchFinished(const BugMapList & list) { KGuiItem::assign(ui.m_searchMoreButton, m_searchMoreGuiItem); m_startDate = m_searchingStartDate; int results = list.count(); if (results > 0) { markAsSearching(false); ui.m_statusWidget->setIdle(i18nc("@info:status","Showing results from %1 to %2", m_startDate.toString(QStringLiteral("yyyy-MM-dd")), m_endDate.toString(QStringLiteral("yyyy-MM-dd")))); QList bugIds; for (int i = 0; i < results; i++) { BugMap bug = list.at(i); bool ok; int bugId = bug.value(QStringLiteral("bug_id")).toInt(&ok); if (ok) { bugIds << bugId; } QString title; //Generate a non-geek readable status QString customStatusString; BugReport::Status status = BugReport::parseStatus(bug.value(QStringLiteral("bug_status"))); BugReport::Resolution resolution = BugReport::parseResolution(bug.value(QStringLiteral("resolution"))); if (BugReport::isOpen(status)) { customStatusString = i18nc("@info bug status", "[Open]"); } else if (BugReport::isClosed(status) && status != BugReport::NeedsInfo) { if (resolution == BugReport::Fixed) { customStatusString = i18nc("@info bug resolution", "[Fixed]"); } else if (resolution == BugReport::WorksForMe) { customStatusString = i18nc("@info bug resolution", "[Non-reproducible]"); } else if (resolution == BugReport::Duplicate) { customStatusString = i18nc("@info bug resolution", "[Duplicate report]"); } else if (resolution == BugReport::Invalid) { customStatusString = i18nc("@info bug resolution", "[Invalid]"); } else if (resolution == BugReport::Downstream || resolution == BugReport::Upstream) { customStatusString = i18nc("@info bug resolution", "[External problem]"); } } else if (status == BugReport::NeedsInfo) { customStatusString = i18nc("@info bug status", "[Incomplete]"); } title = customStatusString + ' ' + bug[QStringLiteral("short_desc")]; QStringList fields = QStringList() << bug[QStringLiteral("bug_id")] << title; QTreeWidgetItem * item = new QTreeWidgetItem(fields); item->setToolTip(0, bug[QStringLiteral("short_desc")]); item->setToolTip(1, bug[QStringLiteral("short_desc")]); ui.m_bugListWidget->addTopLevelItem(item); } if (!m_foundDuplicate) { markAsSearching(true); DuplicateFinderJob *job = new DuplicateFinderJob(bugIds, bugzillaManager(), this); connect(job, SIGNAL(result(KJob*)), this, SLOT(analyzedDuplicates(KJob*))); job->start(); } ui.m_bugListWidget->sortItems(0 , Qt::DescendingOrder); ui.m_bugListWidget->resizeColumnToContents(1); if (!canSearchMore()) { ui.m_searchMoreButton->setEnabled(false); } } else { if (canSearchMore()) { //We don't call markAsSearching(false) to avoid flicker //Delayed call to searchMore to avoid unexpected behaviour (signal/slot) //because we are in a slot, and searchMore() will be ending calling this slot again QTimer::singleShot(0, this, &BugzillaDuplicatesPage::searchMore); } else { markAsSearching(false); ui.m_statusWidget->setIdle(i18nc("@info:status","Search Finished. " "No reports found.")); ui.m_searchMoreButton->setEnabled(false); if (ui.m_bugListWidget->topLevelItemCount() == 0) { //No reports to mark as possible duplicate ui.m_selectedDuplicatesList->setEnabled(false); } } } } void BugzillaDuplicatesPage::analyzedDuplicates(KJob *j) { markAsSearching(false); DuplicateFinderJob *job = static_cast(j); m_result = job->result(); m_foundDuplicate = m_result.parentDuplicate; reportInterface()->setDuplicateId(m_result.parentDuplicate); ui.m_searchMoreButton->setEnabled(!m_foundDuplicate); ui.information->setVisible(m_foundDuplicate); BugReport::Status status = m_result.status; const int duplicate = m_result.duplicate; const int parentDuplicate = m_result.parentDuplicate; if (m_foundDuplicate) { const QList items = ui.m_bugListWidget->findItems(QString::number(parentDuplicate), Qt::MatchExactly, 0); const QBrush brush = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NeutralBackground); Q_FOREACH (QTreeWidgetItem* item, items) { for (int i = 0; i < item->columnCount(); ++i) { item->setBackground(i, brush); } } QString text; if (BugReport::isOpen(status) || (BugReport::isClosed(status) && status == BugReport::NeedsInfo)) { text = (parentDuplicate == duplicate ? i18nc("@label", "Your crash is a duplicate and has already been reported as Bug %1.", QString::number(duplicate)) : i18nc("@label", "Your crash has already been reported as Bug %1, which is a duplicate of Bug %2", QString::number(duplicate), QString::number(parentDuplicate))) + '\n' + i18nc("@label", "Only attach if you can add needed information to the bug report.", QStringLiteral("attach")); } else if (BugReport::isClosed(status)) { text = (parentDuplicate == duplicate ? i18nc("@label", "Your crash has already been reported as Bug %1 which has been closed.", QString::number(duplicate)) : i18nc("@label", "Your crash has already been reported as Bug %1, which is a duplicate of the closed Bug %2.", QString::number(duplicate), QString::number(parentDuplicate))); } ui.information->setText(text); } } void BugzillaDuplicatesPage::informationClicked(const QString &activatedLink) { if (activatedLink == QLatin1String("attach")) { attachToBugReport(m_result.parentDuplicate); } else { int number = activatedLink.toInt(); if (number) { showReportInformationDialog(number, false); } } } void BugzillaDuplicatesPage::searchError(QString err) { KGuiItem::assign(ui.m_searchMoreButton, m_retrySearchGuiItem); markAsSearching(false); ui.m_statusWidget->setIdle(i18nc("@info:status","Error fetching the bug report list")); KMessageBox::error(this , xi18nc("@info/rich","Error fetching the bug report list" "%1." "Please wait some time and try again.", err)); } void BugzillaDuplicatesPage::resetDates() { m_endDate = QDate::currentDate(); m_startDate = m_endDate; } //END Search related methods //BEGIN Duplicates list related methods void BugzillaDuplicatesPage::openSelectedReport() { QList selected = ui.m_bugListWidget->selectedItems(); if (selected.count() == 1) { itemClicked(selected.at(0), 0); } } void BugzillaDuplicatesPage::itemClicked(QTreeWidgetItem * item, int col) { Q_UNUSED(col); int bugNumber = 0; if (item->data(0, Qt::UserRole) == QLatin1String("custom")) { bool ok = false; - bugNumber = QInputDialog::getInteger(this, + bugNumber = QInputDialog::getInt(this, i18nc("@title:window", "Enter a custom bug report number"), i18nc("@label", "Enter the number of the bug report you want to check"), 0, 0, 1000000, 1, &ok); } else { bugNumber = item->text(0).toInt(); } showReportInformationDialog(bugNumber); } void BugzillaDuplicatesPage::itemClicked(QListWidgetItem * item) { showReportInformationDialog(item->text().toInt()); } void BugzillaDuplicatesPage::showReportInformationDialog(int bugNumber, bool relatedButtonEnabled) { if (bugNumber <= 0) { return; } BugzillaReportInformationDialog * infoDialog = new BugzillaReportInformationDialog(this); connect(infoDialog, &BugzillaReportInformationDialog::possibleDuplicateSelected, this, &BugzillaDuplicatesPage::addPossibleDuplicateNumber); connect(infoDialog, &BugzillaReportInformationDialog::attachToBugReportSelected, this, &BugzillaDuplicatesPage::attachToBugReport); infoDialog->showBugReport(bugNumber, relatedButtonEnabled); } void BugzillaDuplicatesPage::itemSelectionChanged() { ui.m_openReportButton->setEnabled(ui.m_bugListWidget->selectedItems().count() == 1); } //END Duplicates list related methods //BEGIN Selected duplicates list related methods void BugzillaDuplicatesPage::addPossibleDuplicateNumber(int bugNumber) { QString stringNumber = QString::number(bugNumber); if (ui.m_selectedDuplicatesList->findItems(stringNumber, Qt::MatchExactly).isEmpty()) { ui.m_selectedDuplicatesList->addItem(stringNumber); } showDuplicatesPanel(true); } void BugzillaDuplicatesPage::removeSelectedDuplicate() { QList items = ui.m_selectedDuplicatesList->selectedItems(); if (items.length() > 0) { delete ui.m_selectedDuplicatesList->takeItem(ui.m_selectedDuplicatesList->row(items.at(0))); } if (ui.m_selectedDuplicatesList->count() == 0) { showDuplicatesPanel(false); } } void BugzillaDuplicatesPage::showDuplicatesPanel(bool show) { ui.m_removeSelectedDuplicateButton->setVisible(show); ui.m_selectedDuplicatesList->setVisible(show); ui.m_selectedPossibleDuplicatesLabel->setVisible(show); } void BugzillaDuplicatesPage::possibleDuplicateSelectionChanged() { ui.m_removeSelectedDuplicateButton->setEnabled( !ui.m_selectedDuplicatesList->selectedItems().isEmpty()); } //END Selected duplicates list related methods //BEGIN Attach to bug related methods void BugzillaDuplicatesPage::attachToBugReport(int bugNumber) { ui.m_attachToReportLabel->setText(xi18nc("@label", "The report is going to be " "attached to bug %1. " "Cancel", QString::number(bugNumber))); ui.m_attachToReportLabel->setVisible(true); ui.m_attachToReportIcon->setVisible(true); reportInterface()->setAttachToBugNumber(bugNumber); } void BugzillaDuplicatesPage::cancelAttachToBugReport() { ui.m_attachToReportLabel->setVisible(false); ui.m_attachToReportIcon->setVisible(false); reportInterface()->setAttachToBugNumber(0); } //END Attach to bug related methods //END BugzillaDuplicatesPage //BEGIN BugzillaReportInformationDialog BugzillaReportInformationDialog::BugzillaReportInformationDialog(BugzillaDuplicatesPage * parent) : QDialog(parent), m_relatedButtonEnabled(true), m_parent(parent), m_bugNumber(0), m_duplicatesCount(0) { setWindowTitle(i18nc("@title:window","Bug Description")); ui.setupUi(this); KGuiItem::assign(ui.m_retryButton, KGuiItem2(i18nc("@action:button", "Retry..."), QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@info:tooltip", "Use this button to retry " "loading the bug report."))); connect(ui.m_retryButton, &QPushButton::clicked, this, &BugzillaReportInformationDialog::reloadReport); m_suggestButton = new QPushButton(this); ui.buttonBox->addButton(m_suggestButton, QDialogButtonBox::ActionRole); KGuiItem::assign(m_suggestButton, KGuiItem2(i18nc("@action:button", "Suggest this crash is related"), QIcon::fromTheme(QStringLiteral("list-add")), i18nc("@info:tooltip", "Use this button to suggest that " "the crash you experienced is related to this bug " "report"))); connect(m_suggestButton, &QPushButton::clicked, this, &BugzillaReportInformationDialog::relatedReportClicked); connect(ui.m_showOwnBacktraceCheckBox, &QAbstractButton::toggled, this, &BugzillaReportInformationDialog::toggleShowOwnBacktrace); //Connect bugzillalib signals connect(m_parent->bugzillaManager(), &BugzillaManager::bugReportFetched, this, &BugzillaReportInformationDialog::bugFetchFinished); connect(m_parent->bugzillaManager(), SIGNAL(bugReportError(QString,QObject*)), this, SLOT(bugFetchError(QString,QObject*))); resize(QSize(800, 600)); KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); KWindowConfig::restoreWindowSize(windowHandle(), config); } BugzillaReportInformationDialog::~BugzillaReportInformationDialog() { disconnect(m_parent->bugzillaManager(), &BugzillaManager::bugReportFetched, this, &BugzillaReportInformationDialog::bugFetchFinished); disconnect(m_parent->bugzillaManager(), SIGNAL(bugReportError(QString,QObject*)), this, SLOT(bugFetchError(QString,QObject*))); KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); KWindowConfig::saveWindowSize(windowHandle(), config); } void BugzillaReportInformationDialog::reloadReport() { showBugReport(m_bugNumber); } void BugzillaReportInformationDialog::showBugReport(int bugNumber, bool relatedButtonEnabled) { m_relatedButtonEnabled = relatedButtonEnabled; ui.m_retryButton->setVisible(false); m_closedStateString.clear(); m_bugNumber = bugNumber; m_parent->bugzillaManager()->fetchBugReport(m_bugNumber, this); m_suggestButton->setEnabled(false); m_suggestButton->setVisible(m_relatedButtonEnabled); ui.m_infoBrowser->setText(i18nc("@info:status","Loading...")); ui.m_infoBrowser->setEnabled(false); ui.m_linkLabel->setText(xi18nc("@info","Report's webpage", m_parent->bugzillaManager()->urlForBug(m_bugNumber))); ui.m_statusWidget->setBusy(xi18nc("@info:status","Loading information about bug " "%1 from %2....", QString::number(m_bugNumber), QLatin1String(KDE_BUGZILLA_SHORT_URL))); ui.m_backtraceBrowser->setPlainText( i18nc("@info","Backtrace of the crash I experienced:\n\n") + m_parent->reportInterface()->backtrace()); KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); bool showOwnBacktrace = config.readEntry("ShowOwnBacktrace", false); ui.m_showOwnBacktraceCheckBox->setChecked(showOwnBacktrace); if (!showOwnBacktrace) { //setChecked(false) will not emit toggled(false) toggleShowOwnBacktrace(false); } show(); } void BugzillaReportInformationDialog::bugFetchFinished(BugReport report, QObject * jobOwner) { if (jobOwner == this && isVisible()) { if (report.isValid()) { //Handle duplicate state QString duplicate = report.markedAsDuplicateOf(); if (!duplicate.isEmpty()) { bool ok = false; int dupId = duplicate.toInt(&ok); if (ok && dupId > 0) { ui.m_statusWidget->setIdle(QString()); KGuiItem yesItem = KStandardGuiItem::yes(); yesItem.setText(i18nc("@action:button let the user to choose to read the " "main report", "Yes, read the main report")); KGuiItem noItem = KStandardGuiItem::no(); noItem.setText(i18nc("@action:button let the user choose to read the original " "report", "No, let me read the report I selected")); if (KMessageBox::questionYesNo(this, xi18nc("@info","The report you selected (bug %1) is already " "marked as duplicate of bug %2. " "Do you want to read that report instead? (recommended)", report.bugNumber(), QString::number(dupId)), i18nc("@title:window","Nested duplicate detected"), yesItem, noItem) == KMessageBox::Yes) { showBugReport(dupId); return; } } } //Generate html for comments (with proper numbering) QLatin1String duplicatesMark = QLatin1String("has been marked as a duplicate of this bug."); QString comments; QStringList commentList = report.comments(); for (int i = 0; i < commentList.count(); i++) { QString comment = commentList.at(i); //Don't add duplicates mark comments if (!comment.contains(duplicatesMark)) { comment.replace('\n', QLatin1String("
")); comments += i18nc("comment $number to use as subtitle", "

Comment %1:

", (i+1)) + "

" + comment + "


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

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

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

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

", "

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

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

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

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

\"%1\"

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

Bug Report Status: %1

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

Affected Component: %1 (%2)

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

Description of the bug

%1

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

Additional Comments

%1", comments); } ui.m_infoBrowser->setText(text); ui.m_infoBrowser->setEnabled(true); m_suggestButton->setEnabled(m_relatedButtonEnabled); m_suggestButton->setVisible(m_relatedButtonEnabled); ui.m_statusWidget->setIdle(xi18nc("@info:status", "Showing bug %1", QString::number(report.bugNumberAsInt()))); } else { bugFetchError(i18nc("@info", "Invalid report information (malformed data). This could " "mean that the bug report does not exist, or the bug tracking site " "is experiencing a problem."), this); } } } void BugzillaReportInformationDialog::markAsDuplicate() { emit possibleDuplicateSelected(m_bugNumber); hide(); } void BugzillaReportInformationDialog::attachToBugReport() { emit attachToBugReportSelected(m_bugNumber); hide(); } void BugzillaReportInformationDialog::cancelAssistant() { m_parent->assistant()->close(); hide(); } void BugzillaReportInformationDialog::relatedReportClicked() { BugzillaReportConfirmationDialog * confirmation = new BugzillaReportConfirmationDialog(m_bugNumber, (m_duplicatesCount >= 10), m_closedStateString, this); confirmation->show(); } void BugzillaReportInformationDialog::bugFetchError(QString err, QObject * jobOwner) { if (jobOwner == this && isVisible()) { KMessageBox::error(this , xi18nc("@info/rich","Error fetching the bug report" "%1." "Please wait some time and try again.", err)); m_suggestButton->setEnabled(false); ui.m_infoBrowser->setText(i18nc("@info","Error fetching the bug report")); ui.m_statusWidget->setIdle(i18nc("@info:status","Error fetching the bug report")); ui.m_retryButton->setVisible(true); } } void BugzillaReportInformationDialog::toggleShowOwnBacktrace(bool show) { QList sizes; if (show) { int size = (ui.m_reportSplitter->sizeHint().width()-ui.m_reportSplitter->handleWidth())/2; sizes << size << size; } else { sizes << ui.m_reportSplitter->sizeHint().width() << 0; //Hide backtrace } ui.m_reportSplitter->setSizes(sizes); //Save the current show value KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); config.writeEntry("ShowOwnBacktrace", show); } //END BugzillaReportInformationDialog //BEGIN BugzillaReportConfirmationDialog BugzillaReportConfirmationDialog::BugzillaReportConfirmationDialog(int bugNumber, bool commonCrash, QString closedState, BugzillaReportInformationDialog * parent) : QDialog(parent), m_parent(parent), m_showProceedQuestion(false), m_bugNumber(bugNumber) { setAttribute(Qt::WA_DeleteOnClose, true); setModal(true); ui.setupUi(this); //Setup dialog setWindowTitle(i18nc("@title:window", "Related Bug Report")); //Setup buttons ui.buttonBox->button(QDialogButtonBox::Cancel)->setText(i18nc("@action:button", "Cancel (Go back to the report)")); ui.buttonBox->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button continue with the selected option " "and close the dialog", "Continue")); ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); connect(this, SIGNAL(okClicked()) , this, SLOT(proceedClicked())); connect(this, SIGNAL(cancelClicked()) , this, SLOT(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())); 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/drkonqi/drkonqi.cpp b/drkonqi/drkonqi.cpp index b12c11810..2b2e6cbec 100644 --- a/drkonqi/drkonqi.cpp +++ b/drkonqi/drkonqi.cpp @@ -1,327 +1,329 @@ /* 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 #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) { 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()); - QWeakPointer dlg = new QFileDialog(parent, defname); - dlg.data()->selectFile(defname); - dlg.data()->setWindowTitle(i18nc("@title:window","Select Filename")); - dlg.data()->setAcceptMode(QFileDialog::AcceptSave); - dlg.data()->setFileMode(QFileDialog::AnyFile); - dlg.data()->setConfirmOverwrite(true); - dlg.data()->exec(); + 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.isNull()) { + if (!dlg) { //Dialog is invalid, it was probably deleted (ex. via DBus call) //return and do not crash return; } QUrl fileUrl; - if(!dlg.data()->selectedUrls().isEmpty()) - fileUrl = dlg.data()->selectedUrls().first(); - delete dlg.data(); + 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; }