1 | /******************************************************************* |
2 | * reportassistantpages_bugzilla.cpp |
3 | * Copyright 2009, 2010, 2011 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.h" |
21 | |
22 | #include <QtCore/QTimer> |
23 | |
24 | #include <QLabel> |
25 | #include <QCheckBox> |
26 | #include <QToolTip> |
27 | #include <QCursor> |
28 | |
29 | #include <QtDBus/QDBusInterface> |
30 | #include <QtDBus/QDBusReply> |
31 | |
32 | #include <KDebug> |
33 | #include <KIcon> |
34 | #include <KMessageBox> |
35 | #include <KToolInvocation> |
36 | #include <KWallet/Wallet> |
37 | #include <KCapacityBar> |
38 | |
39 | /* Unhandled error dialog includes */ |
40 | #include <KFileDialog> |
41 | #include <KWebView> |
42 | #include <KIO/Job> |
43 | #include <KIO/NetAccess> |
44 | #include <KTemporaryFile> |
45 | |
46 | #include "reportinterface.h" |
47 | #include "systeminformation.h" |
48 | #include "crashedapplication.h" |
49 | #include "bugzillalib.h" |
50 | #include "statuswidget.h" |
51 | #include "drkonqi.h" |
52 | #include "drkonqi_globals.h" |
53 | #include "applicationdetailsexamples.h" |
54 | |
55 | static const char kWalletEntryName[] = "drkonqi_bugzilla" ; |
56 | static const char kWalletEntryUsername[] = "username" ; |
57 | static const char kWalletEntryPassword[] = "password" ; |
58 | |
59 | static const char konquerorKWalletEntryName[] = KDE_BUGZILLA_URL "index.cgi#login" ; |
60 | static const char konquerorKWalletEntryUsername[] = "Bugzilla_login" ; |
61 | static const char konquerorKWalletEntryPassword[] = "Bugzilla_password" ; |
62 | |
63 | //BEGIN BugzillaLoginPage |
64 | |
65 | BugzillaLoginPage::BugzillaLoginPage(ReportAssistantDialog * parent) : |
66 | ReportAssistantPage(parent), |
67 | m_wallet(0), m_walletWasOpenedBefore(false) |
68 | { |
69 | connect(bugzillaManager(), SIGNAL(loginFinished(bool)), this, SLOT(loginFinished(bool))); |
70 | connect(bugzillaManager(), SIGNAL(loginError(QString,QString)), this, SLOT(loginError(QString,QString))); |
71 | |
72 | ui.setupUi(this); |
73 | ui.m_statusWidget->setIdle(i18nc("@info:status '1' is replaced with the short URL of the bugzilla " , |
74 | "You need to login with your %1 account in order to proceed." , |
75 | QLatin1String(KDE_BUGZILLA_SHORT_URL))); |
76 | |
77 | ui.m_loginButton->setGuiItem(KGuiItem2(i18nc("@action:button" , "Login" ), |
78 | KIcon("network-connect" ), |
79 | i18nc("@info:tooltip" , "Use this button to login " |
80 | "to the KDE bug tracking system using the provided " |
81 | "username and password." ))); |
82 | ui.m_loginButton->setEnabled(false); |
83 | |
84 | connect(ui.m_loginButton, SIGNAL(clicked()) , this, SLOT(loginClicked())); |
85 | |
86 | connect(ui.m_userEdit, SIGNAL(returnPressed()) , this, SLOT(loginClicked())); |
87 | connect(ui.m_passwordEdit, SIGNAL(returnPressed()) , this, SLOT(loginClicked())); |
88 | |
89 | connect(ui.m_userEdit, SIGNAL(textChanged(QString)) , this, SLOT(updateLoginButtonStatus())); |
90 | connect(ui.m_passwordEdit, SIGNAL(textChanged(QString)) , this, SLOT(updateLoginButtonStatus())); |
91 | |
92 | ui.m_noticeLabel->setText( |
93 | i18nc("@info/rich" ,"<note>You need a user account on the " |
94 | "<link url='%1'>KDE bug tracking system</link> in order to " |
95 | "file a bug report, because we may need to contact you later " |
96 | "for requesting further information. If you do not have " |
97 | "one, you can freely <link url='%2'>create one here</link>. " |
98 | "Please do not use disposable email accounts.</note>" , |
99 | DrKonqi::crashedApplication()->bugReportAddress(), |
100 | QLatin1String(KDE_BUGZILLA_CREATE_ACCOUNT_URL))); |
101 | } |
102 | |
103 | bool BugzillaLoginPage::isComplete() |
104 | { |
105 | return bugzillaManager()->getLogged(); |
106 | } |
107 | |
108 | void BugzillaLoginPage::updateLoginButtonStatus() |
109 | { |
110 | ui.m_loginButton->setEnabled( !ui.m_userEdit->text().isEmpty() && |
111 | !ui.m_passwordEdit->text().isEmpty() ); |
112 | } |
113 | |
114 | void BugzillaLoginPage::loginError(const QString & err, const QString & extendedMessage) |
115 | { |
116 | loginFinished(false); |
117 | ui.m_statusWidget->setIdle(i18nc("@info:status" ,"Error when trying to login: " |
118 | "<message>%1.</message>" , err)); |
119 | if (!extendedMessage.isEmpty()) { |
120 | new UnhandledErrorDialog(this, err, extendedMessage); |
121 | } |
122 | } |
123 | |
124 | void BugzillaLoginPage::aboutToShow() |
125 | { |
126 | if (bugzillaManager()->getLogged()) { |
127 | ui.m_loginButton->setEnabled(false); |
128 | |
129 | ui.m_userEdit->setEnabled(false); |
130 | ui.m_userEdit->clear(); |
131 | ui.m_passwordEdit->setEnabled(false); |
132 | ui.m_passwordEdit->clear(); |
133 | |
134 | ui.m_loginButton->setVisible(false); |
135 | ui.m_userEdit->setVisible(false); |
136 | ui.m_passwordEdit->setVisible(false); |
137 | ui.m_userLabel->setVisible(false); |
138 | ui.m_passwordLabel->setVisible(false); |
139 | |
140 | ui.m_savePasswordCheckBox->setVisible(false); |
141 | |
142 | ui.m_noticeLabel->setVisible(false); |
143 | |
144 | ui.m_statusWidget->setIdle(i18nc("@info:status the user is logged at the bugtracker site " |
145 | "as USERNAME" , |
146 | "Logged in at the KDE bug tracking system (%1) as: %2." , |
147 | QLatin1String(KDE_BUGZILLA_SHORT_URL), |
148 | bugzillaManager()->getUsername())); |
149 | } else { |
150 | //Try to show wallet dialog once this dialog is shown |
151 | QTimer::singleShot(100, this, SLOT(walletLogin())); |
152 | } |
153 | } |
154 | |
155 | bool BugzillaLoginPage::kWalletEntryExists(const QString& entryName) |
156 | { |
157 | return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), |
158 | KWallet::Wallet::FormDataFolder(), |
159 | entryName); |
160 | } |
161 | |
162 | void BugzillaLoginPage::openWallet() |
163 | { |
164 | //Store if the wallet was previously opened so we can know if we should close it later |
165 | m_walletWasOpenedBefore = KWallet::Wallet::isOpen(KWallet::Wallet::NetworkWallet()); |
166 | //Request open the wallet |
167 | m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), |
168 | static_cast<QWidget*>(this->parent())->winId()); |
169 | } |
170 | |
171 | void BugzillaLoginPage::walletLogin() |
172 | { |
173 | if (!m_wallet) { |
174 | if (kWalletEntryExists(QLatin1String(kWalletEntryName))) { //Key exists! |
175 | openWallet(); |
176 | ui.m_savePasswordCheckBox->setCheckState(Qt::Checked); |
177 | //Was the wallet opened? |
178 | if (m_wallet) { |
179 | m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); |
180 | |
181 | //Use wallet data to try login |
182 | QMap<QString, QString> values; |
183 | m_wallet->readMap(QLatin1String(kWalletEntryName), values); |
184 | QString username = values.value(QLatin1String(kWalletEntryUsername)); |
185 | QString password = values.value(QLatin1String(kWalletEntryPassword)); |
186 | |
187 | if (!username.isEmpty() && !password.isEmpty()) { |
188 | ui.m_userEdit->setText(username); |
189 | ui.m_passwordEdit->setText(password); |
190 | } |
191 | } |
192 | } else if (kWalletEntryExists(QLatin1String(konquerorKWalletEntryName))) { |
193 | //If the DrKonqi entry is empty, but a Konqueror entry exists, use and copy it. |
194 | openWallet(); |
195 | if (m_wallet) { |
196 | m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); |
197 | |
198 | //Fetch Konqueror data |
199 | QMap<QString, QString> values; |
200 | m_wallet->readMap(QLatin1String(konquerorKWalletEntryName), values); |
201 | QString username = values.value(QLatin1String(konquerorKWalletEntryUsername)); |
202 | QString password = values.value(QLatin1String(konquerorKWalletEntryPassword)); |
203 | |
204 | if (!username.isEmpty() && !password.isEmpty()) { |
205 | //Copy to DrKonqi own entries |
206 | values.clear(); |
207 | values.insert(QLatin1String(kWalletEntryUsername), username); |
208 | values.insert(QLatin1String(kWalletEntryPassword), password); |
209 | m_wallet->writeMap(QLatin1String(kWalletEntryName), values); |
210 | |
211 | ui.m_savePasswordCheckBox->setCheckState(Qt::Checked); |
212 | |
213 | ui.m_userEdit->setText(username); |
214 | ui.m_passwordEdit->setText(password); |
215 | } |
216 | } |
217 | |
218 | } |
219 | } |
220 | } |
221 | |
222 | bool BugzillaLoginPage::canSetCookies() |
223 | { |
224 | QDBusInterface kded(QLatin1String("org.kde.kded" ), |
225 | QLatin1String("/kded" ), |
226 | QLatin1String("org.kde.kded" )); |
227 | QDBusReply<bool> kcookiejarLoaded = kded.call(QLatin1String("loadModule" ), |
228 | QLatin1String("kcookiejar" )); |
229 | if (!kcookiejarLoaded.isValid()) { |
230 | KMessageBox::error(this, i18n("Failed to communicate with kded. Make sure it is running." )); |
231 | return false; |
232 | } else if (!kcookiejarLoaded.value()) { |
233 | KMessageBox::error(this, i18n("Failed to load KCookieServer. Check your KDE installation." )); |
234 | return false; |
235 | } |
236 | |
237 | |
238 | QDBusInterface kcookiejar(QLatin1String("org.kde.kded" ), |
239 | QLatin1String("/modules/kcookiejar" ), |
240 | QLatin1String("org.kde.KCookieServer" )); |
241 | QDBusReply<QString> advice = kcookiejar.call(QLatin1String("getDomainAdvice" ), |
242 | QLatin1String(KDE_BUGZILLA_URL)); |
243 | |
244 | if (!advice.isValid()) { |
245 | KMessageBox::error(this, i18n("Failed to communicate with KCookieServer." )); |
246 | return false; |
247 | } |
248 | |
249 | kDebug() << "Got reply from KCookieServer:" << advice.value(); |
250 | |
251 | if (advice.value() == QLatin1String("Reject" )) { |
252 | QString msg = i18nc("@info 1 is the bugzilla website url" , |
253 | "Cookies are not allowed in your KDE network settings. In order to " |
254 | "proceed, you need to allow %1 to set cookies." , KDE_BUGZILLA_URL); |
255 | |
256 | KGuiItem yesItem = KStandardGuiItem::yes(); |
257 | yesItem.setText(i18nc("@action:button 1 is the bugzilla website url" , |
258 | "Allow %1 to set cookies" , KDE_BUGZILLA_URL)); |
259 | |
260 | KGuiItem noItem = KStandardGuiItem::no(); |
261 | noItem.setText(i18nc("@action:button do not allow the bugzilla website " |
262 | "to set cookies" , "No, do not allow" )); |
263 | |
264 | if (KMessageBox::warningYesNo(this, msg, QString(), yesItem, noItem) == KMessageBox::Yes) { |
265 | QDBusReply<bool> success = kcookiejar.call(QLatin1String("setDomainAdvice" ), |
266 | QLatin1String(KDE_BUGZILLA_URL), |
267 | QLatin1String("Accept" )); |
268 | if (!success.isValid() || !success.value()) { |
269 | kWarning() << "Failed to set domain advice in KCookieServer" ; |
270 | return false; |
271 | } else { |
272 | return true; |
273 | } |
274 | } else { |
275 | return false; |
276 | } |
277 | } |
278 | |
279 | return true; |
280 | } |
281 | |
282 | void BugzillaLoginPage::loginClicked() |
283 | { |
284 | if (!(ui.m_userEdit->text().isEmpty() || ui.m_passwordEdit->text().isEmpty())) { |
285 | |
286 | if (!canSetCookies()) { |
287 | return; |
288 | } |
289 | |
290 | ui.m_loginButton->setEnabled(false); |
291 | |
292 | ui.m_userLabel->setEnabled(false); |
293 | ui.m_passwordLabel->setEnabled(false); |
294 | |
295 | ui.m_userEdit->setEnabled(false); |
296 | ui.m_passwordEdit->setEnabled(false); |
297 | ui.m_savePasswordCheckBox->setEnabled(false); |
298 | |
299 | if (ui.m_savePasswordCheckBox->checkState()==Qt::Checked) { //Wants to save data |
300 | if (!m_wallet) { |
301 | openWallet(); |
302 | } |
303 | //Got wallet open ? |
304 | if (m_wallet) { |
305 | m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); |
306 | |
307 | QMap<QString, QString> values; |
308 | values.insert(QLatin1String(kWalletEntryUsername), ui.m_userEdit->text()); |
309 | values.insert(QLatin1String(kWalletEntryPassword), ui.m_passwordEdit->text()); |
310 | m_wallet->writeMap(QLatin1String(kWalletEntryName), values); |
311 | } |
312 | |
313 | } else { //User doesn't want to save or wants to remove. |
314 | if (kWalletEntryExists(QLatin1String(kWalletEntryName))) { |
315 | if (!m_wallet) { |
316 | openWallet(); |
317 | } |
318 | //Got wallet open ? |
319 | if (m_wallet) { |
320 | m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); |
321 | m_wallet->removeEntry(QLatin1String(kWalletEntryName)); |
322 | } |
323 | } |
324 | } |
325 | |
326 | ui.m_statusWidget->setBusy(i18nc("@info:status '1' is a url, '2' the username" , |
327 | "Performing login at %1 as %2..." , |
328 | QLatin1String(KDE_BUGZILLA_SHORT_URL), ui.m_userEdit->text())); |
329 | |
330 | bugzillaManager()->tryLogin(ui.m_userEdit->text(), ui.m_passwordEdit->text()); |
331 | } else { |
332 | loginFinished(false); |
333 | } |
334 | } |
335 | |
336 | void BugzillaLoginPage::loginFinished(bool logged) |
337 | { |
338 | if (logged) { |
339 | emitCompleteChanged(); |
340 | |
341 | aboutToShow(); |
342 | if (m_wallet) { |
343 | if (m_wallet->isOpen() && !m_walletWasOpenedBefore) { |
344 | m_wallet->lockWallet(); |
345 | } |
346 | } |
347 | |
348 | emit loggedTurnToNextPage(); |
349 | } else { |
350 | ui.m_statusWidget->setIdle(i18nc("@info:status/rich" ,"<b>Error: Invalid username or " |
351 | "password</b>" )); |
352 | |
353 | ui.m_loginButton->setEnabled(true); |
354 | |
355 | ui.m_userEdit->setEnabled(true); |
356 | ui.m_passwordEdit->setEnabled(true); |
357 | ui.m_savePasswordCheckBox->setEnabled(true); |
358 | |
359 | ui.m_userEdit->setFocus(Qt::OtherFocusReason); |
360 | } |
361 | } |
362 | |
363 | BugzillaLoginPage::~BugzillaLoginPage() |
364 | { |
365 | //Close wallet if we close the assistant in this step |
366 | if (m_wallet) { |
367 | if (m_wallet->isOpen() && !m_walletWasOpenedBefore) { |
368 | m_wallet->lockWallet(); |
369 | } |
370 | delete m_wallet; |
371 | } |
372 | } |
373 | |
374 | //END BugzillaLoginPage |
375 | |
376 | //BEGIN BugzillaInformationPage |
377 | |
378 | BugzillaInformationPage::BugzillaInformationPage(ReportAssistantDialog * parent) |
379 | : ReportAssistantPage(parent), |
380 | m_textsOK(false), m_distributionComboSetup(false), m_distroComboVisible(false), |
381 | m_requiredCharacters(1) |
382 | { |
383 | ui.setupUi(this); |
384 | m_textCompleteBar = new KCapacityBar(KCapacityBar::DrawTextInline, this); |
385 | ui.horizontalLayout_2->addWidget(m_textCompleteBar); |
386 | |
387 | connect(ui.m_titleEdit, SIGNAL(textChanged(QString)), this, SLOT(checkTexts())); |
388 | connect(ui.m_detailsEdit, SIGNAL(textChanged()), this, SLOT(checkTexts())); |
389 | |
390 | connect(ui.m_titleLabel, SIGNAL(linkActivated(QString)), this, SLOT(showTitleExamples())); |
391 | connect(ui.m_detailsLabel, SIGNAL(linkActivated(QString)), this, |
392 | SLOT(showDescriptionHelpExamples())); |
393 | |
394 | ui.m_compiledSourcesCheckBox->setChecked( |
395 | DrKonqi::systemInformation()->compiledSources()); |
396 | |
397 | } |
398 | |
399 | void BugzillaInformationPage::aboutToShow() |
400 | { |
401 | if (!m_distributionComboSetup) { |
402 | //Autodetecting distro failed ? |
403 | if (DrKonqi::systemInformation()->bugzillaPlatform() == QLatin1String("unspecified" )) { |
404 | m_distroComboVisible = true; |
405 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
406 | "Unspecified" ),"unspecified" ); |
407 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
408 | "Archlinux" ), "Archlinux Packages" ); |
409 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
410 | "Chakra" ), "Chakra" ); |
411 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
412 | "Debian stable" ), "Debian stable" ); |
413 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
414 | "Debian testing" ), "Debian testing" ); |
415 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
416 | "Debian unstable" ), "Debian unstable" ); |
417 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
418 | "Exherbo" ), "Exherbo Packages" ); |
419 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
420 | "Fedora" ), "Fedora RPMs" ); |
421 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
422 | "Gentoo" ), "Gentoo Packages" ); |
423 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
424 | "Mageia" ), "Mageia RPMs" ); |
425 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
426 | "Mandriva" ), "Mandriva RPMs" ); |
427 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
428 | "OpenSUSE" ), "openSUSE RPMs" ); |
429 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
430 | "Pardus" ), "Pardus Packages" ); |
431 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
432 | "RedHat" ), "RedHat RPMs" ); |
433 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
434 | "Slackware" ), "Slackware Packages" ); |
435 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
436 | "Ubuntu (and derivatives)" ), |
437 | "Ubuntu Packages" ); |
438 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
439 | "FreeBSD (Ports)" ), "FreeBSD Ports" ); |
440 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
441 | "NetBSD (pkgsrc)" ), "NetBSD pkgsrc" ); |
442 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
443 | "OpenBSD" ), "OpenBSD Packages" ); |
444 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
445 | "Mac OS X" ), "MacPorts Packages" ); |
446 | ui.m_distroChooserCombo->addItem(i18nc("@label:listbox KDE distribution method" , |
447 | "Solaris" ), "Solaris Packages" ); |
448 | |
449 | //Restore previously selected bugzilla platform (distribution) |
450 | KConfigGroup config(KGlobal::config(), "BugzillaInformationPage" ); |
451 | QString entry = config.readEntry("BugzillaPlatform" ,"unspecified" ); |
452 | int index = ui.m_distroChooserCombo->findData(entry); |
453 | if ( index == -1 ) index = 0; |
454 | ui.m_distroChooserCombo->setCurrentIndex(index); |
455 | } else { |
456 | ui.m_distroChooserCombo->setVisible(false); |
457 | } |
458 | m_distributionComboSetup = true; |
459 | } |
460 | |
461 | //Calculate the minimum number of characters required for a description |
462 | //If creating a new report: minimum 40, maximum 80 |
463 | //If attaching to an existent report: minimum 30, maximum 50 |
464 | int multiplier = (reportInterface()->attachToBugNumber() == 0) ? 10 : 5; |
465 | m_requiredCharacters = 20 + (reportInterface()->selectedOptionsRating() * multiplier); |
466 | |
467 | //Fill the description textedit with some headings: |
468 | QString descriptionTemplate; |
469 | if (ui.m_detailsEdit->toPlainText().isEmpty()) { |
470 | if (reportInterface()->userCanProvideActionsAppDesktop()) { |
471 | descriptionTemplate += "- What I was doing when the application crashed:\n\n" ; |
472 | } |
473 | if (reportInterface()->userCanProvideUnusualBehavior()) { |
474 | descriptionTemplate += "- Unusual behavior I noticed:\n\n" ; |
475 | } |
476 | if (reportInterface()->userCanProvideApplicationConfigDetails()) { |
477 | descriptionTemplate += "- Custom settings of the application:\n\n" ; |
478 | } |
479 | ui.m_detailsEdit->setText(descriptionTemplate); |
480 | } |
481 | |
482 | checkTexts(); //May be the options (canDetail) changed and we need to recheck |
483 | } |
484 | |
485 | int BugzillaInformationPage::currentDescriptionCharactersCount() |
486 | { |
487 | QString description = ui.m_detailsEdit->toPlainText(); |
488 | |
489 | //Do not count template messages, and other misc chars |
490 | description.remove("What I was doing when the application crashed" ); |
491 | description.remove("Unusual behavior I noticed" ); |
492 | description.remove("Custom settings of the application" ); |
493 | description.remove('\n'); |
494 | description.remove('-'); |
495 | description.remove(':'); |
496 | description.remove(' '); |
497 | |
498 | return description.size(); |
499 | } |
500 | |
501 | void BugzillaInformationPage::checkTexts() |
502 | { |
503 | //If attaching this report to an existing one then the title is not needed |
504 | bool showTitle = (reportInterface()->attachToBugNumber() == 0); |
505 | ui.m_titleEdit->setVisible(showTitle); |
506 | ui.m_titleLabel->setVisible(showTitle); |
507 | |
508 | bool ok = !((ui.m_titleEdit->isVisible() && ui.m_titleEdit->text().isEmpty()) |
509 | || ui.m_detailsEdit->toPlainText().isEmpty()); |
510 | |
511 | QString message; |
512 | int percent = currentDescriptionCharactersCount() * 100 / m_requiredCharacters; |
513 | if (percent >= 100) { |
514 | percent = 100; |
515 | message = i18nc("the minimum required length of a text was reached" , |
516 | "Minimum length reached" ); |
517 | } else { |
518 | message = i18nc("the minimum required length of a text wasn't reached yet" , |
519 | "Provide more information" ); |
520 | } |
521 | m_textCompleteBar->setValue(percent); |
522 | m_textCompleteBar->setText(message); |
523 | |
524 | if (ok != m_textsOK) { |
525 | m_textsOK = ok; |
526 | emitCompleteChanged(); |
527 | } |
528 | } |
529 | |
530 | bool BugzillaInformationPage::showNextPage() |
531 | { |
532 | checkTexts(); |
533 | |
534 | if (m_textsOK) { |
535 | bool detailsShort = currentDescriptionCharactersCount() < m_requiredCharacters; |
536 | |
537 | if (detailsShort) { |
538 | //The user input is less than we want.... encourage to write more |
539 | QString message = i18nc("@info" ,"The description about the crash details does not provide " |
540 | "enough information yet.<br /><br />" ); |
541 | |
542 | message += ' ' + i18nc("@info" ,"The amount of required information is proportional to " |
543 | "the quality of the other information like the backtrace " |
544 | "or the reproducibility rate." |
545 | "<br /><br />" ); |
546 | |
547 | if (reportInterface()->userCanProvideActionsAppDesktop() |
548 | || reportInterface()->userCanProvideUnusualBehavior() |
549 | || reportInterface()->userCanProvideApplicationConfigDetails()) { |
550 | message += ' ' + i18nc("@info" ,"Previously, you told DrKonqi that you could provide some " |
551 | "contextual information. Try writing more details about your situation. " |
552 | "(even little ones could help us.)<br /><br />" ); |
553 | } |
554 | |
555 | message += ' ' + i18nc("@info" ,"If you cannot provide more information, your report " |
556 | "will probably waste developers' time. Can you tell us more?" ); |
557 | |
558 | KGuiItem yesItem = KStandardGuiItem::yes(); |
559 | yesItem.setText(i18n("Yes, let me add more information" )); |
560 | |
561 | KGuiItem noItem = KStandardGuiItem::no(); |
562 | noItem.setText(i18n("No, I cannot add any other information" )); |
563 | |
564 | if (KMessageBox::warningYesNo(this, message, |
565 | i18nc("@title:window" ,"We need more information" ), |
566 | yesItem, noItem) |
567 | == KMessageBox::No) { |
568 | //Request the assistant to close itself (it will prompt for confirmation anyways) |
569 | assistant()->close(); |
570 | return false; |
571 | } |
572 | } else { |
573 | return true; |
574 | } |
575 | } |
576 | |
577 | return false; |
578 | } |
579 | |
580 | bool BugzillaInformationPage::isComplete() |
581 | { |
582 | return m_textsOK; |
583 | } |
584 | |
585 | void BugzillaInformationPage::aboutToHide() |
586 | { |
587 | //Save fields data |
588 | reportInterface()->setTitle(ui.m_titleEdit->text()); |
589 | reportInterface()->setDetailText(ui.m_detailsEdit->toPlainText()); |
590 | |
591 | if (m_distroComboVisible) { |
592 | //Save bugzilla platform (distribution) |
593 | QString bugzillaPlatform = ui.m_distroChooserCombo->itemData( |
594 | ui.m_distroChooserCombo->currentIndex()).toString(); |
595 | KConfigGroup config(KGlobal::config(), "BugzillaInformationPage" ); |
596 | config.writeEntry("BugzillaPlatform" , bugzillaPlatform); |
597 | DrKonqi::systemInformation()->setBugzillaPlatform(bugzillaPlatform); |
598 | } |
599 | bool compiledFromSources = ui.m_compiledSourcesCheckBox->checkState() == Qt::Checked; |
600 | DrKonqi::systemInformation()->setCompiledSources(compiledFromSources); |
601 | |
602 | } |
603 | |
604 | void BugzillaInformationPage::showTitleExamples() |
605 | { |
606 | QString titleExamples = i18nc("@info:tooltip examples of good bug report titles" , |
607 | "<strong>Examples of good titles:</strong><nl />\"Plasma crashed after adding the Notes " |
608 | "widget and writing on it\"<nl />\"Konqueror crashed when accessing the Facebook " |
609 | "application 'X'\"<nl />\"Kopete suddenly closed after resuming the computer and " |
610 | "talking to a MSN buddy\"<nl />\"Kate closed while editing a log file and pressing the " |
611 | "Delete key a couple of times\"" ); |
612 | QToolTip::showText(QCursor::pos(), titleExamples); |
613 | } |
614 | |
615 | void BugzillaInformationPage::showDescriptionHelpExamples() |
616 | { |
617 | QString descriptionHelp = i18nc("@info:tooltip help and examples of good bug descriptions" , |
618 | "Describe in as much detail as possible the crash circumstances:" ); |
619 | if (reportInterface()->userCanProvideActionsAppDesktop()) { |
620 | descriptionHelp += "<br />" + |
621 | i18nc("@info:tooltip help and examples of good bug descriptions" , |
622 | "- Detail which actions were you taking inside and outside the " |
623 | "application an instant before the crash." ); |
624 | } |
625 | if (reportInterface()->userCanProvideUnusualBehavior()) { |
626 | descriptionHelp += "<br />" + |
627 | i18nc("@info:tooltip help and examples of good bug descriptions" , |
628 | "- Note if you noticed any unusual behavior in the application " |
629 | "or in the whole environment." ); |
630 | } |
631 | if (reportInterface()->userCanProvideApplicationConfigDetails()) { |
632 | descriptionHelp += "<br />" + |
633 | i18nc("@info:tooltip help and examples of good bug descriptions" , |
634 | "- Note any non-default configuration in the application." ); |
635 | if (reportInterface()->appDetailsExamples()->hasExamples()) { |
636 | descriptionHelp += ' ' + |
637 | i18nc("@info:tooltip examples of configuration details. " |
638 | "the examples are already translated" , |
639 | "Examples: %1" , |
640 | reportInterface()->appDetailsExamples()->examples()); |
641 | } |
642 | } |
643 | QToolTip::showText(QCursor::pos(), descriptionHelp); |
644 | } |
645 | |
646 | //END BugzillaInformationPage |
647 | |
648 | //BEGIN BugzillaPreviewPage |
649 | |
650 | BugzillaPreviewPage::BugzillaPreviewPage(ReportAssistantDialog * parent) |
651 | : ReportAssistantPage(parent) |
652 | { |
653 | ui.setupUi(this); |
654 | } |
655 | |
656 | void BugzillaPreviewPage::aboutToShow() |
657 | { |
658 | ui.m_previewEdit->setText(reportInterface()->generateReportFullText(true)); |
659 | } |
660 | |
661 | //END BugzillaPreviewPage |
662 | |
663 | //BEGIN BugzillaSendPage |
664 | |
665 | BugzillaSendPage::BugzillaSendPage(ReportAssistantDialog * parent) |
666 | : ReportAssistantPage(parent), |
667 | m_contentsDialog(0) |
668 | { |
669 | connect(reportInterface(), SIGNAL(reportSent(int)), this, SLOT(sent(int))); |
670 | connect(reportInterface(), SIGNAL(sendReportError(QString,QString)), this, SLOT(sendError(QString,QString))); |
671 | |
672 | ui.setupUi(this); |
673 | |
674 | ui.m_retryButton->setGuiItem(KGuiItem2(i18nc("@action:button" , "Retry..." ), |
675 | KIcon("view-refresh" ), |
676 | i18nc("@info:tooltip" , "Use this button to retry " |
677 | "sending the crash report if it failed before." ))); |
678 | |
679 | ui.m_showReportContentsButton->setGuiItem( |
680 | KGuiItem2(i18nc("@action:button" , "Sho&w Contents of the Report" ), |
681 | KIcon("document-preview" ), |
682 | i18nc("@info:tooltip" , "Use this button to show the generated " |
683 | "report information about this crash." ))); |
684 | connect(ui.m_showReportContentsButton, SIGNAL(clicked()), this, SLOT(openReportContents())); |
685 | |
686 | ui.m_retryButton->setVisible(false); |
687 | connect(ui.m_retryButton, SIGNAL(clicked()), this , SLOT(retryClicked())); |
688 | |
689 | ui.m_launchPageOnFinish->setVisible(false); |
690 | ui.m_restartAppOnFinish->setVisible(false); |
691 | |
692 | connect(assistant(), SIGNAL(user1Clicked()), this, SLOT(finishClicked())); |
693 | } |
694 | |
695 | void BugzillaSendPage::retryClicked() |
696 | { |
697 | ui.m_retryButton->setEnabled(false); |
698 | aboutToShow(); |
699 | } |
700 | |
701 | void BugzillaSendPage::aboutToShow() |
702 | { |
703 | ui.m_statusWidget->setBusy(i18nc("@info:status" ,"Sending crash report... (please wait)" )); |
704 | reportInterface()->sendBugReport(); |
705 | } |
706 | |
707 | void BugzillaSendPage::sent(int bug_id) |
708 | { |
709 | ui.m_statusWidget->setVisible(false); |
710 | ui.m_retryButton->setEnabled(false); |
711 | ui.m_retryButton->setVisible(false); |
712 | |
713 | ui.m_showReportContentsButton->setVisible(false); |
714 | |
715 | ui.m_launchPageOnFinish->setVisible(true); |
716 | ui.m_restartAppOnFinish->setVisible(!DrKonqi::crashedApplication()->hasBeenRestarted()); |
717 | ui.m_restartAppOnFinish->setChecked(false); |
718 | |
719 | reportUrl = bugzillaManager()->urlForBug(bug_id); |
720 | ui.m_finishedLabel->setText(i18nc("@info/rich" ,"Crash report sent.<nl/>" |
721 | "URL: <link>%1</link><nl/>" |
722 | "Thank you for being part of KDE. " |
723 | "You can now close this window." , reportUrl)); |
724 | |
725 | emit finished(false); |
726 | } |
727 | |
728 | void BugzillaSendPage::sendError(const QString & errorString, const QString & extendedMessage) |
729 | { |
730 | ui.m_statusWidget->setIdle(i18nc("@info:status" ,"Error sending the crash report: " |
731 | "<message>%1.</message>" , errorString)); |
732 | |
733 | ui.m_retryButton->setEnabled(true); |
734 | ui.m_retryButton->setVisible(true); |
735 | |
736 | if (!extendedMessage.isEmpty()) { |
737 | new UnhandledErrorDialog(this,errorString, extendedMessage); |
738 | } |
739 | } |
740 | |
741 | void BugzillaSendPage::finishClicked() |
742 | { |
743 | if (ui.m_launchPageOnFinish->isChecked() && !reportUrl.isEmpty()) { |
744 | KToolInvocation::invokeBrowser(reportUrl); |
745 | } |
746 | if (ui.m_restartAppOnFinish->isChecked()) { |
747 | DrKonqi::crashedApplication()->restart(); |
748 | } |
749 | } |
750 | |
751 | void BugzillaSendPage::openReportContents() |
752 | { |
753 | if (!m_contentsDialog) |
754 | { |
755 | QString report = reportInterface()->generateReportFullText(false) + QLatin1Char('\n') + |
756 | i18nc("@info/plain report to KDE bugtracker address" ,"Report to %1" , |
757 | DrKonqi::crashedApplication()->bugReportAddress()); |
758 | m_contentsDialog = new ReportInformationDialog(report); |
759 | } |
760 | m_contentsDialog->show(); |
761 | m_contentsDialog->raise(); |
762 | m_contentsDialog->activateWindow(); |
763 | } |
764 | |
765 | //END BugzillaSendPage |
766 | |
767 | /* Dialog for Unhandled Bugzilla Errors */ |
768 | /* The user can save the bugzilla html output to check the error and/or to report this as a DrKonqi bug */ |
769 | |
770 | //BEGIN UnhandledErrorDialog |
771 | |
772 | UnhandledErrorDialog::UnhandledErrorDialog(QWidget * parent, const QString & error, const QString & extendedMessage) |
773 | : KDialog(parent) |
774 | { |
775 | setWindowTitle(KDialog::makeStandardCaption(i18nc("@title:window" , "Unhandled Bugzilla Error" ))); |
776 | setWindowModality(Qt::ApplicationModal); |
777 | |
778 | setButtons(KDialog::Close | KDialog::User1); |
779 | setButtonText(KDialog::User1, i18nc("@action:button save html to a file" ,"Save to a file" )); |
780 | setButtonIcon(KDialog::User1, KIcon("document-save" )); |
781 | connect(this, SIGNAL(user1Clicked()), this, SLOT(saveErrorMessage())); |
782 | |
783 | setAttribute(Qt::WA_DeleteOnClose); |
784 | |
785 | KWebView * htmlView = new KWebView(this); |
786 | |
787 | QLabel * iconLabel = new QLabel(this); |
788 | iconLabel->setFixedSize(32, 32); |
789 | iconLabel->setPixmap(KIcon("dialog-warning" ).pixmap(32, 32)); |
790 | |
791 | QLabel * mainLabel = new QLabel(this); |
792 | mainLabel->setWordWrap(true); |
793 | mainLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); |
794 | |
795 | QHBoxLayout * titleLayout = new QHBoxLayout(); |
796 | titleLayout->setContentsMargins(5,2,5,2); |
797 | titleLayout->setSpacing(5); |
798 | titleLayout->addWidget(iconLabel); |
799 | titleLayout->addWidget(mainLabel); |
800 | |
801 | QVBoxLayout * layout = new QVBoxLayout(); |
802 | layout->addLayout(titleLayout); |
803 | layout->addWidget(htmlView); |
804 | |
805 | QWidget * mainWidget = new QWidget(this); |
806 | mainWidget->setLayout(layout); |
807 | setMainWidget(mainWidget); |
808 | |
809 | m_extendedHTMLError = extendedMessage; |
810 | mainLabel->setText(i18nc("@label" , "There was an unhandled Bugzilla error: %1.<br />" |
811 | "Below is the HTML that DrKonqi received. " |
812 | "Try to perform the action again or save this error page " |
813 | "to submit a bug against DrKonqi." ).arg(error)); |
814 | htmlView->setHtml(extendedMessage); |
815 | |
816 | setMinimumSize(QSize(550, 350)); |
817 | resize(minimumSize()); |
818 | |
819 | show(); |
820 | } |
821 | |
822 | void UnhandledErrorDialog::saveErrorMessage() |
823 | { |
824 | QString defaultName = QLatin1String("drkonqi-unhandled-bugzilla-error.html" ); |
825 | QWeakPointer<KFileDialog> dlg = new KFileDialog(defaultName, QString(), this); |
826 | dlg.data()->setSelection(defaultName); |
827 | dlg.data()->setCaption(i18nc("@title:window" ,"Select Filename" )); |
828 | dlg.data()->setOperationMode(KFileDialog::Saving); |
829 | dlg.data()->setMode(KFile::File); |
830 | dlg.data()->setConfirmOverwrite(true); |
831 | if ( dlg.data()->exec() ) |
832 | { |
833 | if (dlg.isNull()) { |
834 | //Dialog closed externally (ex. via DBus) |
835 | return; |
836 | } |
837 | |
838 | KUrl fileUrl = dlg.data()->selectedUrl(); |
839 | delete dlg.data(); |
840 | |
841 | if (fileUrl.isValid()) { |
842 | KTemporaryFile tf; |
843 | if (tf.open()) { |
844 | QTextStream ts(&tf); |
845 | ts << m_extendedHTMLError; |
846 | ts.flush(); |
847 | } else { |
848 | KMessageBox::sorry(this, i18nc("@info" ,"Cannot open file <filename>%1</filename> " |
849 | "for writing." , tf.fileName())); |
850 | return; |
851 | } |
852 | |
853 | if (!KIO::NetAccess::upload(tf.fileName(), fileUrl, this)) { |
854 | KMessageBox::sorry(this, KIO::NetAccess::lastErrorString()); |
855 | } |
856 | } |
857 | } |
858 | else |
859 | delete dlg.data(); |
860 | |
861 | } |
862 | |
863 | //END UnhandledErrorDialog |
864 | |