1/*******************************************************************
2* reportassistantpages_bugzilla_duplicates.cpp
3* Copyright 2009 Dario Andres Rodriguez <andresbajotierra@gmail.com>
4*
5* This program is free software; you can redistribute it and/or
6* modify it under the terms of the GNU General Public License as
7* published by the Free Software Foundation; either version 2 of
8* the License, or (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License
16* along with this program. If not, see <http://www.gnu.org/licenses/>.
17*
18******************************************************************/
19
20#include "reportassistantpages_bugzilla_duplicates.h"
21
22#include <QtCore/QDate>
23#include <QtCore/QTimer>
24
25#include <QLabel>
26#include <QTreeWidgetItem>
27#include <QHeaderView>
28
29#include <KColorScheme>
30#include <KIcon>
31#include <KMessageBox>
32#include <KInputDialog>
33
34#include "drkonqi_globals.h"
35#include "reportinterface.h"
36#include "statuswidget.h"
37
38//BEGIN BugzillaDuplicatesPage
39
40BugzillaDuplicatesPage::BugzillaDuplicatesPage(ReportAssistantDialog * parent):
41 ReportAssistantPage(parent),
42 m_searching(false),
43 m_foundDuplicate(false)
44{
45 resetDates();
46
47 connect(bugzillaManager(), SIGNAL(searchFinished(BugMapList)),
48 this, SLOT(searchFinished(BugMapList)));
49 connect(bugzillaManager(), SIGNAL(searchError(QString)),
50 this, SLOT(searchError(QString)));
51
52 ui.setupUi(this);
53 ui.information->hide();
54
55 connect(ui.m_bugListWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
56 this, SLOT(itemClicked(QTreeWidgetItem*,int)));
57 connect(ui.m_bugListWidget, SIGNAL(itemSelectionChanged()), this, SLOT(itemSelectionChanged()));
58
59 QHeaderView * header = ui.m_bugListWidget->header();
60 header->setResizeMode(0, QHeaderView::ResizeToContents);
61 header->setResizeMode(1, QHeaderView::Interactive);
62
63 //Create manual bug report entry (first one)
64 QTreeWidgetItem * customBugItem = new QTreeWidgetItem(
65 QStringList() << i18nc("@item:intable custom/manaul bug report number", "Manual")
66 << i18nc("@item:intable custom bug report number description",
67 "Manually enter a bug report ID"));
68 customBugItem->setData(0, Qt::UserRole, QLatin1String("custom"));
69 customBugItem->setIcon(1, KIcon("edit-rename"));
70
71 QString helpMessage = i18nc("@info:tooltip / whatsthis",
72 "Select this option to manually load a specific bug report");
73 customBugItem->setToolTip(0, helpMessage);
74 customBugItem->setToolTip(1, helpMessage);
75 customBugItem->setWhatsThis(0, helpMessage);
76 customBugItem->setWhatsThis(1, helpMessage);
77
78 ui.m_bugListWidget->addTopLevelItem(customBugItem);
79
80 m_searchMoreGuiItem = KGuiItem2(i18nc("@action:button", "Search for more reports"),
81 KIcon("edit-find"),
82 i18nc("@info:tooltip", "Use this button to "
83 "search for more similar bug reports on an "
84 "earlier date."));
85 ui.m_searchMoreButton->setGuiItem(m_searchMoreGuiItem);
86 connect(ui.m_searchMoreButton, SIGNAL(clicked()), this, SLOT(searchMore()));
87
88 m_retrySearchGuiItem = KGuiItem2(i18nc("@action:button", "Retry search"),
89 KIcon("edit-find"),
90 i18nc("@info:tooltip", "Use this button to "
91 "retry the search that previously "
92 "failed."));
93
94 ui.m_openReportButton->setGuiItem(KGuiItem2(i18nc("@action:button", "Open selected report"),
95 KIcon("document-preview"),
96 i18nc("@info:tooltip", "Use this button to view "
97 "the information of the selected bug report.")));
98 connect(ui.m_openReportButton, SIGNAL(clicked()), this, SLOT(openSelectedReport()));
99
100 ui.m_stopSearchButton->setGuiItem(KGuiItem2(i18nc("@action:button", "Stop searching"),
101 KIcon("process-stop"),
102 i18nc("@info:tooltip", "Use this button to stop "
103 "the current search.")));
104 ui.m_stopSearchButton->setText(QString()); //FIXME
105 connect(ui.m_stopSearchButton, SIGNAL(clicked()), this, SLOT(stopCurrentSearch()));
106
107 //Possible duplicates list and buttons
108 connect(ui.m_selectedDuplicatesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
109 this, SLOT(itemClicked(QListWidgetItem*)));
110 connect(ui.m_selectedDuplicatesList, SIGNAL(itemSelectionChanged()),
111 this, SLOT(possibleDuplicateSelectionChanged()));
112
113 ui.m_removeSelectedDuplicateButton->setGuiItem(
114 KGuiItem2(i18nc("@action:button remove the selected item from a list", "Remove"),
115 KIcon("list-remove"),
116 i18nc("@info:tooltip", "Use this button to remove a selected possible duplicate")));
117 ui.m_removeSelectedDuplicateButton->setEnabled(false);
118 connect(ui.m_removeSelectedDuplicateButton, SIGNAL(clicked()), this,
119 SLOT(removeSelectedDuplicate()));
120
121 ui.m_attachToReportIcon->setPixmap(KIcon("mail-attachment").pixmap(16,16));
122 ui.m_attachToReportIcon->setFixedSize(16,16);
123 ui.m_attachToReportIcon->setVisible(false);
124 ui.m_attachToReportLabel->setVisible(false);
125 connect(ui.m_attachToReportLabel, SIGNAL(linkActivated(QString)), this,
126 SLOT(cancelAttachToBugReport()));
127 connect(ui.information, SIGNAL(linkActivated(QString)), this, SLOT(informationClicked(QString)));
128 showDuplicatesPanel(false);
129}
130
131BugzillaDuplicatesPage::~BugzillaDuplicatesPage()
132{
133}
134
135void BugzillaDuplicatesPage::aboutToShow()
136{
137 //Perform initial search if we are not currently searching and if there are no results yet
138 if (!m_searching && ui.m_bugListWidget->topLevelItemCount() == 1 && canSearchMore()) {
139 searchMore();
140 }
141}
142
143void BugzillaDuplicatesPage::aboutToHide()
144{
145 stopCurrentSearch();
146
147 //Save selected possible duplicates by user
148 QStringList possibleDuplicates;
149 int count = ui.m_selectedDuplicatesList->count();
150 for(int i = 0; i<count; i++) {
151 possibleDuplicates << ui.m_selectedDuplicatesList->item(i)->text();
152 }
153 reportInterface()->setPossibleDuplicates(possibleDuplicates);
154
155 //Save possible duplicates by query
156 QStringList duplicatesByQuery;
157 count = ui.m_bugListWidget->topLevelItemCount();
158 for(int i = 1; i<count; i++) {
159 duplicatesByQuery << ui.m_bugListWidget->topLevelItem(i)->text(0);
160 }
161 reportInterface()->setPossibleDuplicatesByQuery(duplicatesByQuery);
162}
163
164bool BugzillaDuplicatesPage::isComplete()
165{
166 return !m_searching;
167}
168
169bool BugzillaDuplicatesPage::showNextPage()
170{
171 //Ask the user to check all the possible duplicates...
172 if (ui.m_bugListWidget->topLevelItemCount() != 1 && ui.m_selectedDuplicatesList->count() == 0
173 && reportInterface()->attachToBugNumber() == 0 && !m_foundDuplicate) {
174 //The user didn't selected any possible duplicate nor a report to attach the new info.
175 //Double check this, we need to reduce the duplicate count.
176 KGuiItem noDuplicatesButton;
177 noDuplicatesButton.setText(i18n("There are no real duplicates"));
178 noDuplicatesButton.setWhatsThis(i18n("Press this button to declare that, in your opinion "
179 "and according to your experience, the reports found "
180 "as similar do not match the crash you have "
181 "experienced, and you believe it is unlikely that a "
182 "better match would be found after further review."));
183 noDuplicatesButton.setIcon(KIcon("dialog-cancel"));
184
185 KGuiItem letMeCheckMoreReportsButton;
186 letMeCheckMoreReportsButton.setText(i18n("Let me check more reports"));
187 letMeCheckMoreReportsButton.setWhatsThis(i18n("Press this button if you would rather "
188 "review more reports in order to find a "
189 "match for the crash you have experienced."));
190 letMeCheckMoreReportsButton.setIcon(KIcon("document-preview"));
191
192 if (KMessageBox::questionYesNo(this,
193 i18nc("@info","You have not selected any possible duplicates, or a report to which to attach your "
194 "crash information. Have you read all the reports, and can you confirm that there are no "
195 "real duplicates?"),
196 i18nc("@title:window","No selected possible duplicates"), letMeCheckMoreReportsButton,
197 noDuplicatesButton)
198 == KMessageBox::Yes) {
199 return false;
200 }
201 }
202 return true;
203}
204
205//BEGIN Search related methods
206void BugzillaDuplicatesPage::searchMore()
207{
208 //1 year back
209 m_searchingEndDate = m_startDate;
210 m_searchingStartDate = m_searchingEndDate.addYears(-1);
211
212 performSearch();
213}
214
215void BugzillaDuplicatesPage::performSearch()
216{
217 markAsSearching(true);
218
219 QString startDateStr = m_searchingStartDate.toString("yyyy-MM-dd");
220 QString endDateStr = m_searchingEndDate.toString("yyyy-MM-dd");
221
222 ui.m_statusWidget->setBusy(i18nc("@info:status","Searching for duplicates (from %1 to %2)...",
223 startDateStr, endDateStr));
224
225 //Bugzilla will not search on Today bugs if we send the date.
226 //we need to send "Now"
227 if (m_searchingEndDate == QDate::currentDate()) {
228 endDateStr = QLatin1String("Now");
229 }
230
231#if 1
232 BugReport report = reportInterface()->newBugReportTemplate();
233 bugzillaManager()->searchBugs(reportInterface()->relatedBugzillaProducts(),
234 report.bugSeverity(), startDateStr, endDateStr,
235 reportInterface()->firstBacktraceFunctions().join(" "));
236#else //Test search
237 bugzillaManager()->searchBugs(QStringList() << "plasma", "crash", startDateStr, endDateStr,
238 "QGraphicsScenePrivate::processDirtyItemsRecursive");
239#endif
240}
241
242void BugzillaDuplicatesPage::stopCurrentSearch()
243{
244 if (m_searching) {
245 bugzillaManager()->stopCurrentSearch();
246
247 markAsSearching(false);
248
249 if (m_startDate==m_endDate) { //Never searched
250 ui.m_statusWidget->setIdle(i18nc("@info:status","Search stopped."));
251 } else {
252 ui.m_statusWidget->setIdle(i18nc("@info:status","Search stopped. Showing results from "
253 "%1 to %2", m_startDate.toString("yyyy-MM-dd"),
254 m_endDate.toString("yyyy-MM-dd")));
255 }
256 }
257}
258
259void BugzillaDuplicatesPage::markAsSearching(bool searching)
260{
261 m_searching = searching;
262 emitCompleteChanged();
263
264 ui.m_bugListWidget->setEnabled(!searching);
265 ui.m_searchMoreButton->setEnabled(!searching);
266 ui.m_searchMoreButton->setVisible(!searching);
267 ui.m_stopSearchButton->setEnabled(searching);
268 ui.m_stopSearchButton->setVisible(searching);
269
270 ui.m_selectedDuplicatesList->setEnabled(!searching);
271 ui.m_selectedPossibleDuplicatesLabel->setEnabled(!searching);
272 ui.m_removeSelectedDuplicateButton->setEnabled(!searching &&
273 !ui.m_selectedDuplicatesList->selectedItems().isEmpty());
274
275 ui.m_attachToReportLabel->setEnabled(!searching);
276
277 if (!searching) {
278 itemSelectionChanged();
279 } else {
280 ui.m_openReportButton->setEnabled(false);
281 }
282}
283
284bool BugzillaDuplicatesPage::canSearchMore()
285{
286 return (m_startDate.year() >= 2009);
287}
288
289void BugzillaDuplicatesPage::searchFinished(const BugMapList & list)
290{
291 ui.m_searchMoreButton->setGuiItem(m_searchMoreGuiItem);
292 m_startDate = m_searchingStartDate;
293
294 int results = list.count();
295 if (results > 0) {
296 markAsSearching(false);
297
298 ui.m_statusWidget->setIdle(i18nc("@info:status","Showing results from %1 to %2",
299 m_startDate.toString("yyyy-MM-dd"),
300 m_endDate.toString("yyyy-MM-dd")));
301
302 QList<int> bugIds;
303 for (int i = 0; i < results; i++) {
304 BugMap bug = list.at(i);
305
306 bool ok;
307 int bugId = bug.value("bug_id").toInt(&ok);
308 if (ok) {
309 bugIds << bugId;
310 }
311
312 QString title;
313
314 //Generate a non-geek readable status
315 QString customStatusString;
316 BugReport::Status status = BugReport::parseStatus(bug.value("bug_status"));
317 BugReport::Resolution resolution = BugReport::parseResolution(bug.value("resolution"));
318 if (BugReport::isOpen(status)) {
319 customStatusString = i18nc("@info/plain bug status", "[Open]");
320 } else if (BugReport::isClosed(status) && status != BugReport::NeedsInfo) {
321 if (resolution == BugReport::Fixed) {
322 customStatusString = i18nc("@info/plain bug resolution", "[Fixed]");
323 } else if (resolution == BugReport::WorksForMe) {
324 customStatusString = i18nc("@info/plain bug resolution", "[Non-reproducible]");
325 } else if (resolution == BugReport::Duplicate) {
326 customStatusString = i18nc("@info/plain bug resolution", "[Duplicate report]");
327 } else if (resolution == BugReport::Invalid) {
328 customStatusString = i18nc("@info/plain bug resolution", "[Invalid]");
329 } else if (resolution == BugReport::Downstream
330 || resolution == BugReport::Upstream) {
331 customStatusString = i18nc("@info/plain bug resolution", "[External problem]");
332 }
333 } else if (status == BugReport::NeedsInfo) {
334 customStatusString = i18nc("@info/plain bug status", "[Incomplete]");
335 }
336
337 title = customStatusString + ' ' + bug["short_desc"];
338
339 QStringList fields = QStringList() << bug["bug_id"] << title;
340
341 QTreeWidgetItem * item = new QTreeWidgetItem(fields);
342 item->setToolTip(0, bug["short_desc"]);
343 item->setToolTip(1, bug["short_desc"]);
344
345 ui.m_bugListWidget->addTopLevelItem(item);
346 }
347
348 if (!m_foundDuplicate) {
349 markAsSearching(true);
350 DuplicateFinderJob *job = new DuplicateFinderJob(bugIds, bugzillaManager(), this);
351 connect(job, SIGNAL(result(KJob*)), this, SLOT(analyzedDuplicates(KJob*)));
352 job->start();
353 }
354
355 ui.m_bugListWidget->sortItems(0 , Qt::DescendingOrder);
356 ui.m_bugListWidget->resizeColumnToContents(1);
357
358 if (!canSearchMore()) {
359 ui.m_searchMoreButton->setEnabled(false);
360 }
361
362 } else {
363
364 if (canSearchMore()) {
365 //We don't call markAsSearching(false) to avoid flicker
366 //Delayed call to searchMore to avoid unexpected behaviour (signal/slot)
367 //because we are in a slot, and searchMore() will be ending calling this slot again
368 QTimer::singleShot(0, this, SLOT(searchMore()));
369 } else {
370 markAsSearching(false);
371 ui.m_statusWidget->setIdle(i18nc("@info:status","Search Finished. "
372 "No reports found."));
373 ui.m_searchMoreButton->setEnabled(false);
374 if (ui.m_bugListWidget->topLevelItemCount() == 0) {
375 //No reports to mark as possible duplicate
376 ui.m_selectedDuplicatesList->setEnabled(false);
377 }
378 }
379 }
380}
381
382void BugzillaDuplicatesPage::analyzedDuplicates(KJob *j)
383{
384 markAsSearching(false);
385
386 DuplicateFinderJob *job = static_cast<DuplicateFinderJob*>(j);
387 m_result = job->result();
388 m_foundDuplicate = m_result.parentDuplicate;
389 reportInterface()->setDuplicateId(m_result.parentDuplicate);
390 ui.m_searchMoreButton->setEnabled(!m_foundDuplicate);
391 ui.information->setVisible(m_foundDuplicate);
392 BugReport::Status status = m_result.status;
393 const int duplicate = m_result.duplicate;
394 const int parentDuplicate = m_result.parentDuplicate;
395
396 if (m_foundDuplicate) {
397 const QList<QTreeWidgetItem*> items = ui.m_bugListWidget->findItems(QString::number(parentDuplicate), Qt::MatchExactly, 0);
398 const QBrush brush = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NeutralBackground);
399 Q_FOREACH (QTreeWidgetItem* item, items) {
400 for (int i = 0; i < item->columnCount(); ++i) {
401 item->setBackground(i, brush);
402 }
403 }
404
405 QString text;
406 if (BugReport::isOpen(status) || (BugReport::isClosed(status) && status == BugReport::NeedsInfo)) {
407 text = (parentDuplicate == duplicate ? i18nc("@label", "Your crash is a <strong>duplicate</strong> and has already been reported as <a href=\"%1\">Bug %1</a>.", QString::number(duplicate)) :
408 i18nc("@label", "Your crash has already been reported as <a href=\"%1\">Bug %1</a>, which is a <strong>duplicate</strong> of <a href=\"%2\">Bug %2</a>", QString::number(duplicate), QString::number(parentDuplicate))) +
409 '\n' + i18nc("@label", "Only <strong><a href=\"%1\">attach</a></strong> if you can add needed information to the bug report.", QString("attach"));
410 } else if (BugReport::isClosed(status)) {
411 text = (parentDuplicate == duplicate ? i18nc("@label", "Your crash has already been reported as <a href=\"%1\">Bug %1</a> which has been <strong>closed</strong>.", QString::number(duplicate)) :
412 i18nc("@label", "Your crash has already been reported as <a href=\"%1\">Bug %1</a>, which is a duplicate of the <strong>closed</strong> <a href=\"%2\">Bug %2</a>.", QString::number(duplicate), QString::number(parentDuplicate)));
413 }
414 ui.information->setText(text);
415 }
416}
417
418void BugzillaDuplicatesPage::informationClicked(const QString &activatedLink)
419{
420 if (activatedLink == QLatin1String("attach")) {
421 attachToBugReport(m_result.parentDuplicate);
422 } else {
423 int number = activatedLink.toInt();
424 if (number) {
425 showReportInformationDialog(number, false);
426 }
427 }
428}
429
430void BugzillaDuplicatesPage::searchError(QString err)
431{
432 ui.m_searchMoreButton->setGuiItem(m_retrySearchGuiItem);
433 markAsSearching(false);
434
435 ui.m_statusWidget->setIdle(i18nc("@info:status","Error fetching the bug report list"));
436
437 KMessageBox::error(this , i18nc("@info/rich","Error fetching the bug report list<nl/>"
438 "<message>%1.</message><nl/>"
439 "Please wait some time and try again.", err));
440}
441
442void BugzillaDuplicatesPage::resetDates()
443{
444 m_endDate = QDate::currentDate();
445 m_startDate = m_endDate;
446}
447//END Search related methods
448
449//BEGIN Duplicates list related methods
450void BugzillaDuplicatesPage::openSelectedReport()
451{
452 QList<QTreeWidgetItem*> selected = ui.m_bugListWidget->selectedItems();
453 if (selected.count() == 1) {
454 itemClicked(selected.at(0), 0);
455 }
456}
457
458void BugzillaDuplicatesPage::itemClicked(QTreeWidgetItem * item, int col)
459{
460 Q_UNUSED(col);
461
462 int bugNumber = 0;
463 if (item->data(0, Qt::UserRole) == QLatin1String("custom")) {
464 bool ok = false;
465 bugNumber = KInputDialog::getInteger(
466 i18nc("@title:window", "Enter a custom bug report number"),
467 i18nc("@label", "Enter the number of the bug report you want to check"),
468 0, 0, 1000000, 1, &ok, this);
469 } else {
470 bugNumber = item->text(0).toInt();
471 }
472 showReportInformationDialog(bugNumber);
473}
474
475void BugzillaDuplicatesPage::itemClicked(QListWidgetItem * item)
476{
477 showReportInformationDialog(item->text().toInt());
478}
479
480void BugzillaDuplicatesPage::showReportInformationDialog(int bugNumber, bool relatedButtonEnabled)
481{
482 if (bugNumber <= 0) {
483 return;
484 }
485
486 BugzillaReportInformationDialog * infoDialog = new BugzillaReportInformationDialog(this);
487 connect(infoDialog, SIGNAL(possibleDuplicateSelected(int)), this,
488 SLOT(addPossibleDuplicateNumber(int)));
489 connect(infoDialog, SIGNAL(attachToBugReportSelected(int)), this, SLOT(attachToBugReport(int)));
490
491 infoDialog->showBugReport(bugNumber, relatedButtonEnabled);
492}
493
494void BugzillaDuplicatesPage::itemSelectionChanged()
495{
496 ui.m_openReportButton->setEnabled(ui.m_bugListWidget->selectedItems().count() == 1);
497}
498//END Duplicates list related methods
499
500//BEGIN Selected duplicates list related methods
501void BugzillaDuplicatesPage::addPossibleDuplicateNumber(int bugNumber)
502{
503 QString stringNumber = QString::number(bugNumber);
504 if (ui.m_selectedDuplicatesList->findItems(stringNumber, Qt::MatchExactly).isEmpty()) {
505 ui.m_selectedDuplicatesList->addItem(stringNumber);
506 }
507
508 showDuplicatesPanel(true);
509}
510
511void BugzillaDuplicatesPage::removeSelectedDuplicate()
512{
513 QList<QListWidgetItem*> items = ui.m_selectedDuplicatesList->selectedItems();
514 if (items.length() > 0) {
515 delete ui.m_selectedDuplicatesList->takeItem(ui.m_selectedDuplicatesList->row(items.at(0)));
516 }
517
518 if (ui.m_selectedDuplicatesList->count() == 0) {
519 showDuplicatesPanel(false);
520 }
521}
522
523void BugzillaDuplicatesPage::showDuplicatesPanel(bool show)
524{
525 ui.m_removeSelectedDuplicateButton->setVisible(show);
526 ui.m_selectedDuplicatesList->setVisible(show);
527 ui.m_selectedPossibleDuplicatesLabel->setVisible(show);
528}
529
530void BugzillaDuplicatesPage::possibleDuplicateSelectionChanged()
531{
532 ui.m_removeSelectedDuplicateButton->setEnabled(
533 !ui.m_selectedDuplicatesList->selectedItems().isEmpty());
534}
535//END Selected duplicates list related methods
536
537//BEGIN Attach to bug related methods
538void BugzillaDuplicatesPage::attachToBugReport(int bugNumber)
539{
540 ui.m_attachToReportLabel->setText(i18nc("@label", "The report is going to be "
541 "<strong>attached</strong> to bug <numid>%1</numid>. "
542 "<a href=\"#\">Cancel</a>", bugNumber));
543 ui.m_attachToReportLabel->setVisible(true);
544 ui.m_attachToReportIcon->setVisible(true);
545 reportInterface()->setAttachToBugNumber(bugNumber);
546}
547
548void BugzillaDuplicatesPage::cancelAttachToBugReport()
549{
550 ui.m_attachToReportLabel->setVisible(false);
551 ui.m_attachToReportIcon->setVisible(false);
552 reportInterface()->setAttachToBugNumber(0);
553}
554//END Attach to bug related methods
555
556//END BugzillaDuplicatesPage
557
558//BEGIN BugzillaReportInformationDialog
559
560BugzillaReportInformationDialog::BugzillaReportInformationDialog(BugzillaDuplicatesPage * parent) :
561 KDialog(parent),
562 m_relatedButtonEnabled(true),
563 m_parent(parent),
564 m_bugNumber(0),
565 m_duplicatesCount(0)
566{
567 //Create the GUI
568 setButtons(KDialog::Close | KDialog::User1);
569 setDefaultButton(KDialog::Close);
570 setCaption(i18nc("@title:window","Bug Description"));
571
572 QWidget * widget = new QWidget(this);
573 ui.setupUi(widget);
574 setMainWidget(widget);
575
576 ui.m_retryButton->setGuiItem(KGuiItem2(i18nc("@action:button", "Retry..."),
577 KIcon("view-refresh"),
578 i18nc("@info:tooltip", "Use this button to retry "
579 "loading the bug report.")));
580 connect(ui.m_retryButton, SIGNAL(clicked()), this, SLOT(reloadReport()));
581
582 setButtonGuiItem(KDialog::User1,
583 KGuiItem2(i18nc("@action:button", "Suggest this crash is related"),
584 KIcon("list-add"), i18nc("@info:tooltip", "Use this button to suggest that "
585 "the crash you experienced is related to this bug "
586 "report")));
587 connect(this, SIGNAL(user1Clicked()) , this, SLOT(relatedReportClicked()));
588
589 connect(ui.m_showOwnBacktraceCheckBox, SIGNAL(toggled(bool)), this, SLOT(toggleShowOwnBacktrace(bool)));
590
591 //Connect bugzillalib signals
592 connect(m_parent->bugzillaManager(), SIGNAL(bugReportFetched(BugReport,QObject*)),
593 this, SLOT(bugFetchFinished(BugReport,QObject*)));
594 connect(m_parent->bugzillaManager(), SIGNAL(bugReportError(QString,QObject*)),
595 this, SLOT(bugFetchError(QString,QObject*)));
596
597 setInitialSize(QSize(800, 600));
598 KConfigGroup config(KGlobal::config(), "BugzillaReportInformationDialog");
599 restoreDialogSize(config);
600}
601
602BugzillaReportInformationDialog::~BugzillaReportInformationDialog()
603{
604 disconnect(m_parent->bugzillaManager(), SIGNAL(bugReportFetched(BugReport,QObject*)),
605 this, SLOT(bugFetchFinished(BugReport,QObject*)));
606 disconnect(m_parent->bugzillaManager(), SIGNAL(bugReportError(QString,QObject*)),
607 this, SLOT(bugFetchError(QString,QObject*)));
608
609 KConfigGroup config(KGlobal::config(), "BugzillaReportInformationDialog");
610 saveDialogSize(config);
611}
612
613void BugzillaReportInformationDialog::reloadReport()
614{
615 showBugReport(m_bugNumber);
616}
617
618void BugzillaReportInformationDialog::showBugReport(int bugNumber, bool relatedButtonEnabled)
619{
620 m_relatedButtonEnabled = relatedButtonEnabled;
621 ui.m_retryButton->setVisible(false);
622
623 m_closedStateString.clear();
624 m_bugNumber = bugNumber;
625 m_parent->bugzillaManager()->fetchBugReport(m_bugNumber, this);
626
627 button(KDialog::User1)->setEnabled(false);
628 button(KDialog::User1)->setVisible(m_relatedButtonEnabled);
629
630 ui.m_infoBrowser->setText(i18nc("@info:status","Loading..."));
631 ui.m_infoBrowser->setEnabled(false);
632
633 ui.m_linkLabel->setText(i18nc("@info","<link url='%1'>Report's webpage</link>",
634 m_parent->bugzillaManager()->urlForBug(m_bugNumber)));
635
636 ui.m_statusWidget->setBusy(i18nc("@info:status","Loading information about bug "
637 "<numid>%1</numid> from %2....",
638 m_bugNumber,
639 QLatin1String(KDE_BUGZILLA_SHORT_URL)));
640
641 ui.m_backtraceBrowser->setPlainText(
642 i18nc("@info/plain","Backtrace of the crash I experienced:\n\n") +
643 m_parent->reportInterface()->backtrace());
644
645 KConfigGroup config(KGlobal::config(), "BugzillaReportInformationDialog");
646 bool showOwnBacktrace = config.readEntry("ShowOwnBacktrace", false);
647 ui.m_showOwnBacktraceCheckBox->setChecked(showOwnBacktrace);
648 if (!showOwnBacktrace) { //setChecked(false) will not emit toggled(false)
649 toggleShowOwnBacktrace(false);
650 }
651
652 show();
653}
654
655void BugzillaReportInformationDialog::bugFetchFinished(BugReport report, QObject * jobOwner)
656{
657 if (jobOwner == this && isVisible()) {
658 if (report.isValid()) {
659
660 //Handle duplicate state
661 QString duplicate = report.markedAsDuplicateOf();
662 if (!duplicate.isEmpty()) {
663 bool ok = false;
664 int dupId = duplicate.toInt(&ok);
665 if (ok && dupId > 0) {
666 ui.m_statusWidget->setIdle(QString());
667
668 KGuiItem yesItem = KStandardGuiItem::yes();
669 yesItem.setText(i18nc("@action:button let the user to choose to read the "
670 "main report", "Yes, read the main report"));
671
672 KGuiItem noItem = KStandardGuiItem::no();
673 noItem.setText(i18nc("@action:button let the user choose to read the original "
674 "report", "No, let me read the report I selected"));
675
676 if (KMessageBox::questionYesNo(this,
677 i18nc("@info","The report you selected (bug <numid>%1</numid>) is already "
678 "marked as duplicate of bug <numid>%2</numid>. "
679 "Do you want to read that report instead? (recommended)",
680 report.bugNumber(), dupId),
681 i18nc("@title:window","Nested duplicate detected"), yesItem, noItem)
682 == KMessageBox::Yes) {
683 showBugReport(dupId);
684 return;
685 }
686 }
687 }
688
689 //Generate html for comments (with proper numbering)
690 QLatin1String duplicatesMark = QLatin1String("has been marked as a duplicate of this bug.");
691
692 QString comments;
693 QStringList commentList = report.comments();
694 for (int i = 0; i < commentList.count(); i++) {
695 QString comment = commentList.at(i);
696 //Don't add duplicates mark comments
697 if (!comment.contains(duplicatesMark)) {
698 comment.replace('\n', "<br />");
699 comments += i18nc("comment $number to use as subtitle", "<h4>Comment %1:</h4>", (i+1))
700 + "<p>" + comment + "</p><hr />";
701 //Count the inline attached crashes (DrKonqi feature)
702 QLatin1String attachedCrashMark =
703 QLatin1String("New crash information added by DrKonqi");
704 if (comment.contains(attachedCrashMark)) {
705 m_duplicatesCount++;
706 }
707 } else {
708 //Count duplicate
709 m_duplicatesCount++;
710 }
711 }
712
713 //Generate a non-geek readable status
714 QString customStatusString;
715 BugReport::Status status = report.statusValue();
716 BugReport::Resolution resolution = report.resolutionValue();
717 if (status == BugReport::Unconfirmed) {
718 customStatusString = i18nc("@info bug status", "Opened (Unconfirmed)");
719 } else if (report.isOpen()) {
720 customStatusString = i18nc("@info bug status", "Opened (Unfixed)");
721 } else if (report.isClosed() && status != BugReport::NeedsInfo) {
722 QString customResolutionString;
723 if (resolution == BugReport::Fixed) {
724 if (!report.versionFixedIn().isEmpty()) {
725 customResolutionString = i18nc("@info bug resolution, fixed in version",
726 "Fixed in version \"%1\"",
727 report.versionFixedIn());
728 m_closedStateString = i18nc("@info bug resolution, fixed by kde devs in version",
729 "the bug was fixed by KDE developers in version \"%1\"",
730 report.versionFixedIn());
731 } else {
732 customResolutionString = i18nc("@info bug resolution", "Fixed");
733 m_closedStateString = i18nc("@info bug resolution", "the bug was fixed by KDE developers");
734 }
735 } else if (resolution == BugReport::WorksForMe) {
736 customResolutionString = i18nc("@info bug resolution", "Non-reproducible");
737 } else if (resolution == BugReport::Duplicate) {
738 customResolutionString = i18nc("@info bug resolution", "Duplicate report "
739 "(Already reported before)");
740 } else if (resolution == BugReport::Invalid) {
741 customResolutionString = i18nc("@info bug resolution", "Not a valid report/crash");
742 } else if (resolution == BugReport::Downstream || resolution == BugReport::Upstream) {
743 customResolutionString = i18nc("@info bug resolution", "Not caused by a problem "
744 "in the KDE's Applications or libraries");
745 m_closedStateString = i18nc("@info bug resolution", "the bug is caused by a "
746 "problem in an external application or library, or "
747 "by a distribution or packaging issue");
748 } else {
749 customResolutionString = report.resolution();
750 }
751
752 customStatusString = i18nc("@info bug status, %1 is the resolution", "Closed (%1)",
753 customResolutionString);
754 } else if (status == BugReport::NeedsInfo) {
755 customStatusString = i18nc("@info bug status", "Temporarily closed, because of a lack "
756 "of information");
757 } else { //Fallback to other raw values
758 customStatusString = QString("%1 (%2)").arg(report.bugStatus(), report.resolution());
759 }
760
761 //Generate notes
762 QString notes = i18n("<p><note>The bug report's title is often written by its reporter "
763 "and may not reflect the bug's nature, root cause or other visible "
764 "symptoms you could use to compare to your crash. Please read the "
765 "complete report and all the comments below.</note></p>");
766
767 if (m_duplicatesCount >= 10) { //Consider a possible mass duplicate crash
768 notes += i18np("<p><note>This bug report has %1 duplicate report. That means this "
769 "is probably a <strong>common crash</strong>. <i>Please consider only "
770 "adding a comment or a note if you can provide new valuable "
771 "information which was not already mentioned.</i></note></p>",
772 "<p><note>This bug report has %1 duplicate reports. That means this "
773 "is probably a <strong>common crash</strong>. <i>Please consider only "
774 "adding a comment or a note if you can provide new valuable "
775 "information which was not already mentioned.</i></note></p>",
776 m_duplicatesCount);
777 }
778
779 //A manually entered bug ID could represent a normal bug
780 if (report.bugSeverity() != QLatin1String("crash")
781 && report.bugSeverity() != QLatin1String("major")
782 && report.bugSeverity() != QLatin1String("grave")
783 && report.bugSeverity() != QLatin1String("critical"))
784 {
785 notes += i18n("<p><note>This bug report is not about a crash or about any other "
786 "critical bug.</note></p>");
787 }
788
789 //Generate HTML text
790 QString text =
791 i18nc("@info bug report title (quoted)",
792 "<h3>\"%1\"</h3>", report.shortDescription()) +
793 notes +
794 i18nc("@info bug report status",
795 "<h4>Bug Report Status: %1</h4>", customStatusString) +
796 i18nc("@info bug report product and component",
797 "<h4>Affected Component: %1 (%2)</h4>",
798 report.product(), report.component()) +
799 i18nc("@info bug report description",
800 "<h3>Description of the bug</h3><p>%1</p>",
801 report.description().replace('\n', "<br />"));
802
803 if (!comments.isEmpty()) {
804 text += i18nc("@label:textbox bug report comments (already formatted)",
805 "<h2>Additional Comments</h2>%1", comments);
806 }
807
808 ui.m_infoBrowser->setText(text);
809 ui.m_infoBrowser->setEnabled(true);
810
811 button(KDialog::User1)->setEnabled(m_relatedButtonEnabled);
812 button(KDialog::User1)->setVisible(m_relatedButtonEnabled);
813
814 ui.m_statusWidget->setIdle(i18nc("@info:status", "Showing bug <numid>%1</numid>",
815 report.bugNumberAsInt()));
816 } else {
817 bugFetchError(i18nc("@info", "Invalid report information (malformed data). This could "
818 "mean that the bug report does not exist, or the bug tracking site "
819 "is experiencing a problem."), this);
820 }
821 }
822}
823
824void BugzillaReportInformationDialog::markAsDuplicate()
825{
826 emit possibleDuplicateSelected(m_bugNumber);
827 hide();
828}
829
830void BugzillaReportInformationDialog::attachToBugReport()
831{
832 emit attachToBugReportSelected(m_bugNumber);
833 hide();
834}
835
836void BugzillaReportInformationDialog::cancelAssistant()
837{
838 m_parent->assistant()->close();
839 hide();
840}
841
842void BugzillaReportInformationDialog::relatedReportClicked()
843{
844 BugzillaReportConfirmationDialog * confirmation =
845 new BugzillaReportConfirmationDialog(m_bugNumber, (m_duplicatesCount >= 10),
846 m_closedStateString, this);
847 confirmation->show();
848}
849
850void BugzillaReportInformationDialog::bugFetchError(QString err, QObject * jobOwner)
851{
852 if (jobOwner == this && isVisible()) {
853 KMessageBox::error(this , i18nc("@info/rich","Error fetching the bug report<nl/>"
854 "<message>%1.</message><nl/>"
855 "Please wait some time and try again.", err));
856 button(KDialog::User1)->setEnabled(false);
857 ui.m_infoBrowser->setText(i18nc("@info","Error fetching the bug report"));
858 ui.m_statusWidget->setIdle(i18nc("@info:status","Error fetching the bug report"));
859 ui.m_retryButton->setVisible(true);
860 }
861}
862
863void BugzillaReportInformationDialog::toggleShowOwnBacktrace(bool show)
864{
865 QList<int> sizes;
866 if (show) {
867 int size = (ui.m_reportSplitter->sizeHint().width()-ui.m_reportSplitter->handleWidth())/2;
868 sizes << size << size;
869 } else {
870 sizes << ui.m_reportSplitter->sizeHint().width() << 0; //Hide backtrace
871 }
872 ui.m_reportSplitter->setSizes(sizes);
873
874 //Save the current show value
875 KConfigGroup config(KGlobal::config(), "BugzillaReportInformationDialog");
876 config.writeEntry("ShowOwnBacktrace", show);
877}
878
879//END BugzillaReportInformationDialog
880
881//BEGIN BugzillaReportConfirmationDialog
882
883BugzillaReportConfirmationDialog::BugzillaReportConfirmationDialog(int bugNumber, bool commonCrash,
884 QString closedState, BugzillaReportInformationDialog * parent)
885 : KDialog(parent),
886 m_parent(parent),
887 m_showProceedQuestion(false),
888 m_bugNumber(bugNumber)
889{
890 KGlobal::ref();
891 setAttribute(Qt::WA_DeleteOnClose, true);
892 setModal(true);
893
894 QWidget * widget = new QWidget(this);
895 ui.setupUi(widget);
896 setMainWidget(widget);
897
898 //Setup dialog
899 setButtons(KDialog::Ok | KDialog::Cancel);
900 setDefaultButton(KDialog::Cancel);
901 setCaption(i18nc("@title:window", "Related Bug Report"));
902
903 //Setup buttons
904 button(KDialog::Cancel)->setText(i18nc("@action:button", "Cancel (Go back to the report)"));
905 button(KDialog::Ok)->setText(i18nc("@action:button continue with the selected option "
906 "and close the dialog", "Continue"));
907 button(KDialog::Ok)->setEnabled(false);
908
909 connect(this, SIGNAL(okClicked()) , this, SLOT(proceedClicked()));
910 connect(this, SIGNAL(cancelClicked()) , this, SLOT(hide()));
911
912 //Set introduction text
913 ui.introLabel->setText(i18n("You are going to mark your crash as related to bug <numid>%1</numid>",
914 m_bugNumber));
915
916 if (commonCrash) { //Common ("massive") crash
917 m_showProceedQuestion = true;
918 ui.commonCrashIcon->setPixmap(KIcon("edit-bomb").pixmap(22,22));
919 } else {
920 ui.commonCrashLabel->setVisible(false);
921 ui.commonCrashIcon->setVisible(false);
922 }
923
924 if (!closedState.isEmpty()) { //Bug report closed
925 ui.closedReportLabel->setText(
926 i18nc("@info", "The report is closed because %1. "
927 "<i>If the crash is the same, adding further information will be useless "
928 "and will consume developers' time.</i>",
929 closedState));
930 ui.closedReportIcon->setPixmap(KIcon("document-close").pixmap(22,22));
931 m_showProceedQuestion = true;
932 } else {
933 ui.closedReportLabel->setVisible(false);
934 ui.closedReportIcon->setVisible(false);
935 }
936
937 //Disable all the radio buttons
938 ui.proceedRadioYes->setChecked(false);
939 ui.proceedRadioNo->setChecked(false);
940 ui.markAsDuplicateCheck->setChecked(false);
941 ui.attachToBugReportCheck->setChecked(false);
942
943 connect(ui.buttonGroupProceed, SIGNAL(buttonClicked(int)), this, SLOT(checkProceed()));
944 connect(ui.buttonGroupProceedQuestion, SIGNAL(buttonClicked(int)), this, SLOT(checkProceed()));
945
946 if (!m_showProceedQuestion) {
947 ui.proceedLabel->setEnabled(false);
948 ui.proceedRadioYes->setEnabled(false);
949 ui.proceedRadioNo->setEnabled(false);
950
951 ui.proceedLabel->setVisible(false);
952 ui.proceedRadioYes->setVisible(false);
953 ui.proceedRadioNo->setVisible(false);
954
955 ui.proceedRadioYes->setChecked(true);
956 }
957
958 checkProceed();
959
960 setMinimumSize(QSize(600, 350));
961 setInitialSize(QSize(600, 350));
962}
963
964BugzillaReportConfirmationDialog::~BugzillaReportConfirmationDialog()
965{
966 KGlobal::deref();
967}
968
969void BugzillaReportConfirmationDialog::checkProceed()
970{
971 bool yes = ui.proceedRadioYes->isChecked();
972 bool no = ui.proceedRadioNo->isChecked();
973
974 //Enable/disable labels and controls
975 ui.areYouSureLabel->setEnabled(yes);
976 ui.markAsDuplicateCheck->setEnabled(yes);
977 ui.attachToBugReportCheck->setEnabled(yes);
978
979 //Enable Continue button if valid options are selected
980 bool possibleDupe = ui.markAsDuplicateCheck->isChecked();
981 bool attach = ui.attachToBugReportCheck->isChecked();
982 bool enableContinueButton = yes ? (possibleDupe || attach) : no;
983 button(KDialog::Ok)->setEnabled(enableContinueButton);
984}
985
986void BugzillaReportConfirmationDialog::proceedClicked()
987{
988 if (ui.proceedRadioYes->isChecked()) {
989 if (ui.markAsDuplicateCheck->isChecked()) {
990 m_parent->markAsDuplicate();
991 hide();
992 } else {
993 m_parent->attachToBugReport();
994 hide();
995 }
996 } else {
997 hide();
998 m_parent->cancelAssistant();
999 }
1000}
1001
1002//END BugzillaReportConfirmationDialog
1003