1/*******************************************************************
2* backtracewidget.cpp
3* Copyright 2009,2010 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 "backtracewidget.h"
21
22#include <QLabel>
23#include <QHBoxLayout>
24#include <QVBoxLayout>
25#include <QScrollBar>
26
27#include <KIcon>
28#include <KMessageBox>
29#include <KLocalizedString>
30#include <KToolInvocation>
31#include <KGlobalSettings>
32
33#include "drkonqi.h"
34#include "backtraceratingwidget.h"
35#include "crashedapplication.h"
36#include "backtracegenerator.h"
37#include "parser/backtraceparser.h"
38#include "drkonqi_globals.h"
39#include "debuggermanager.h"
40#include "gdbhighlighter.h"
41
42static const char extraDetailsLabelMargin[] = " margin: 5px; ";
43
44BacktraceWidget::BacktraceWidget(BacktraceGenerator *generator, QWidget *parent,
45 bool showToggleBacktrace) :
46 QWidget(parent),
47 m_btGenerator(generator),
48 m_highlighter(0)
49{
50 ui.setupUi(this);
51
52 //Debug package installer
53 m_debugPackageInstaller = new DebugPackageInstaller(this);
54 connect(m_debugPackageInstaller, SIGNAL(error(QString)), this, SLOT(debugPackageError(QString)));
55 connect(m_debugPackageInstaller, SIGNAL(packagesInstalled()), this, SLOT(regenerateBacktrace()));
56 connect(m_debugPackageInstaller, SIGNAL(canceled()), this, SLOT(debugPackageCanceled()));
57
58 connect(m_btGenerator, SIGNAL(done()) , this, SLOT(loadData()));
59 connect(m_btGenerator, SIGNAL(someError()) , this, SLOT(loadData()));
60 connect(m_btGenerator, SIGNAL(failedToStart()) , this, SLOT(loadData()));
61 connect(m_btGenerator, SIGNAL(newLine(QString)) , this, SLOT(backtraceNewLine(QString)));
62
63 connect(ui.m_extraDetailsLabel, SIGNAL(linkActivated(QString)), this,
64 SLOT(extraDetailsLinkActivated(QString)));
65 ui.m_extraDetailsLabel->setVisible(false);
66 ui.m_extraDetailsLabel->setStyleSheet(QLatin1String(extraDetailsLabelMargin));
67
68 //Setup the buttons
69 ui.m_reloadBacktraceButton->setGuiItem(
70 KGuiItem2(i18nc("@action:button", "&Reload"),
71 KIcon("view-refresh"), i18nc("@info:tooltip", "Use this button to "
72 "reload the crash information (backtrace). This is useful when you have "
73 "installed the proper debug symbol packages and you want to obtain "
74 "a better backtrace.")));
75 connect(ui.m_reloadBacktraceButton, SIGNAL(clicked()), this, SLOT(regenerateBacktrace()));
76
77 ui.m_installDebugButton->setGuiItem(
78 KGuiItem2(i18nc("@action:button", "&Install Debug Symbols"),
79 KIcon("system-software-update"), i18nc("@info:tooltip", "Use this button to "
80 "install the missing debug symbols packages.")));
81 ui.m_installDebugButton->setVisible(false);
82 connect(ui.m_installDebugButton, SIGNAL(clicked()), this, SLOT(installDebugPackages()));
83
84 ui.m_copyButton->setGuiItem(KGuiItem2(QString(), KIcon("edit-copy"),
85 i18nc("@info:tooltip", "Use this button to copy the "
86 "crash information (backtrace) to the clipboard.")));
87 connect(ui.m_copyButton, SIGNAL(clicked()) , this, SLOT(copyClicked()));
88 ui.m_copyButton->setEnabled(false);
89
90 ui.m_saveButton->setGuiItem(KGuiItem2(QString(),
91 KIcon("document-save"),
92 i18nc("@info:tooltip", "Use this button to save the "
93 "crash information (backtrace) to a file. This is useful "
94 "if you want to take a look at it or to report the bug "
95 "later.")));
96 connect(ui.m_saveButton, SIGNAL(clicked()) , this, SLOT(saveClicked()));
97 ui.m_saveButton->setEnabled(false);
98
99 //Create the rating widget
100 m_backtraceRatingWidget = new BacktraceRatingWidget(ui.m_statusWidget);
101 ui.m_statusWidget->addCustomStatusWidget(m_backtraceRatingWidget);
102
103 ui.m_statusWidget->setIdle(QString());
104
105 //Do we need the "Show backtrace" toggle action ?
106 if (!showToggleBacktrace) {
107 ui.mainLayout->removeWidget(ui.m_toggleBacktraceCheckBox);
108 ui.m_toggleBacktraceCheckBox->setVisible(false);
109 toggleBacktrace(true);
110 } else {
111 //Generate help widget
112 ui.m_backtraceHelpLabel->setText(
113 i18n("<h2>What is a \"backtrace\" ?</h2><p>A backtrace basically describes what was "
114 "happening inside the application when it crashed, so the developers may track "
115 "down where the mess started. They may look meaningless to you, but they might "
116 "actually contain a wealth of useful information.<br />Backtraces are commonly "
117 "used during interactive and post-mortem debugging.</p>"));
118 ui.m_backtraceHelpIcon->setPixmap(KIcon("help-hint").pixmap(48,48));
119 connect(ui.m_toggleBacktraceCheckBox, SIGNAL(toggled(bool)), this,
120 SLOT(toggleBacktrace(bool)));
121 toggleBacktrace(false);
122 }
123
124 ui.m_backtraceEdit->setFont( KGlobalSettings::fixedFont() );
125}
126
127void BacktraceWidget::setAsLoading()
128{
129 //remove the syntax highlighter
130 delete m_highlighter;
131 m_highlighter = 0;
132
133 //Set the widget as loading and disable all the action buttons
134 ui.m_backtraceEdit->setText(i18nc("@info:status", "Loading..."));
135 ui.m_backtraceEdit->setEnabled(false);
136
137 ui.m_statusWidget->setBusy(i18nc("@info:status",
138 "Generating backtrace... (this may take some time)"));
139 m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless);
140 m_backtraceRatingWidget->setState(BacktraceGenerator::Loading);
141
142 ui.m_extraDetailsLabel->setVisible(false);
143 ui.m_extraDetailsLabel->clear();
144
145 ui.m_installDebugButton->setVisible(false);
146 ui.m_reloadBacktraceButton->setEnabled(false);
147
148 ui.m_copyButton->setEnabled(false);
149 ui.m_saveButton->setEnabled(false);
150}
151
152//Force backtrace generation
153void BacktraceWidget::regenerateBacktrace()
154{
155 setAsLoading();
156
157 if (!DrKonqi::debuggerManager()->debuggerIsRunning()) {
158 m_btGenerator->start();
159 } else {
160 anotherDebuggerRunning();
161 }
162
163 emit stateChanged();
164}
165
166void BacktraceWidget::generateBacktrace()
167{
168 if (m_btGenerator->state() == BacktraceGenerator::NotLoaded) {
169 //First backtrace generation
170 regenerateBacktrace();
171 } else if (m_btGenerator->state() == BacktraceGenerator::Loading) {
172 //Set in loading state, the widget will catch the backtrace events anyway
173 setAsLoading();
174 emit stateChanged();
175 } else {
176 //*Finished* states
177 setAsLoading();
178 emit stateChanged();
179 //Load already generated information
180 loadData();
181 }
182}
183
184void BacktraceWidget::anotherDebuggerRunning()
185{
186 //As another debugger is running, we should disable the actions and notify the user
187 ui.m_backtraceEdit->setEnabled(false);
188 ui.m_backtraceEdit->setText(i18nc("@info", "Another debugger is currently debugging the "
189 "same application. The crash information could not be fetched."));
190 m_backtraceRatingWidget->setState(BacktraceGenerator::Failed);
191 m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless);
192 ui.m_statusWidget->setIdle(i18nc("@info:status", "The crash information could not be fetched."));
193 ui.m_extraDetailsLabel->setVisible(true);
194 ui.m_extraDetailsLabel->setText(i18nc("@info/rich", "Another debugging process is attached to "
195 "the crashed application. Therefore, the DrKonqi debugger cannot "
196 "fetch the backtrace. Please close the other debugger and "
197 "click <interface>Reload</interface>."));
198 ui.m_installDebugButton->setVisible(false);
199 ui.m_reloadBacktraceButton->setEnabled(true);
200}
201
202void BacktraceWidget::loadData()
203{
204 //Load the backtrace data from the generator
205 m_backtraceRatingWidget->setState(m_btGenerator->state());
206
207 if (m_btGenerator->state() == BacktraceGenerator::Loaded) {
208 ui.m_backtraceEdit->setEnabled(true);
209 ui.m_backtraceEdit->setPlainText(m_btGenerator->backtrace());
210
211 // scroll to crash
212 QTextCursor crashCursor = ui.m_backtraceEdit->document()->find("[KCrash Handler]");
213 if (!crashCursor.isNull()) {
214 crashCursor.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor);
215 ui.m_backtraceEdit->verticalScrollBar()->setValue(ui.m_backtraceEdit->cursorRect(crashCursor).top());
216 }
217
218 // highlight if possible
219 if (m_btGenerator->debugger().codeName() == "gdb") {
220 m_highlighter = new GdbHighlighter(ui.m_backtraceEdit->document(),
221 m_btGenerator->parser()->parsedBacktraceLines());
222 }
223
224 BacktraceParser * btParser = m_btGenerator->parser();
225 m_backtraceRatingWidget->setUsefulness(btParser->backtraceUsefulness());
226
227 //Generate the text to put in the status widget (backtrace usefulness)
228 QString usefulnessText;
229 switch (btParser->backtraceUsefulness()) {
230 case BacktraceParser::ReallyUseful:
231 usefulnessText = i18nc("@info", "The generated crash information is useful");
232 break;
233 case BacktraceParser::MayBeUseful:
234 usefulnessText = i18nc("@info", "The generated crash information may be useful");
235 break;
236 case BacktraceParser::ProbablyUseless:
237 usefulnessText = i18nc("@info", "The generated crash information is probably not useful");
238 break;
239 case BacktraceParser::Useless:
240 usefulnessText = i18nc("@info", "The generated crash information is not useful");
241 break;
242 default:
243 //let's hope nobody will ever see this... ;)
244 usefulnessText = i18nc("@info", "The rating of this crash information is invalid. "
245 "This is a bug in DrKonqi itself.");
246 break;
247 }
248 ui.m_statusWidget->setIdle(usefulnessText);
249
250 if (btParser->backtraceUsefulness() != BacktraceParser::ReallyUseful) {
251 //Not a perfect bactrace, ask the user to try to improve it
252 ui.m_extraDetailsLabel->setVisible(true);
253 if (canInstallDebugPackages()) {
254 //The script to install the debug packages is available
255 ui.m_extraDetailsLabel->setText(i18nc("@info/rich", "You can click the <interface>"
256 "Install Debug Symbols</interface> button in order to automatically "
257 "install the missing debugging information packages. If this method "
258 "does not work: please read <link url='%1'>How to "
259 "create useful crash reports</link> to learn how to get a useful "
260 "backtrace; install the needed packages (<link url='%2'>"
261 "list of files</link>) and click the "
262 "<interface>Reload</interface> button.",
263 QLatin1String(TECHBASE_HOWTO_DOC),
264 QLatin1String("#missingDebugPackages")));
265 ui.m_installDebugButton->setVisible(true);
266 //Retrieve the libraries with missing debug info
267 QStringList missingLibraries = btParser->librariesWithMissingDebugSymbols().toList();
268 m_debugPackageInstaller->setMissingLibraries(missingLibraries);
269 } else {
270 //No automated method to install the missing debug info
271 //Tell the user to read the howto and reload
272 ui.m_extraDetailsLabel->setText(i18nc("@info/rich", "Please read <link url='%1'>How to "
273 "create useful crash reports</link> to learn how to get a useful "
274 "backtrace; install the needed packages (<link url='%2'>"
275 "list of files</link>) and click the "
276 "<interface>Reload</interface> button.",
277 QLatin1String(TECHBASE_HOWTO_DOC),
278 QLatin1String("#missingDebugPackages")));
279 }
280 }
281
282 ui.m_copyButton->setEnabled(true);
283 ui.m_saveButton->setEnabled(true);
284 } else if (m_btGenerator->state() == BacktraceGenerator::Failed) {
285 //The backtrace could not be generated because the debugger had an error
286 m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless);
287
288 ui.m_statusWidget->setIdle(i18nc("@info:status", "The debugger has quit unexpectedly."));
289
290 ui.m_backtraceEdit->setPlainText(i18nc("@info:status",
291 "The crash information could not be generated."));
292
293 ui.m_extraDetailsLabel->setVisible(true);
294 ui.m_extraDetailsLabel->setText(i18nc("@info/rich", "You could try to regenerate the "
295 "backtrace by clicking the <interface>Reload"
296 "</interface> button."));
297 } else if (m_btGenerator->state() == BacktraceGenerator::FailedToStart) {
298 //The backtrace could not be generated because the debugger could not start (missing)
299 //Tell the user to install it.
300 m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless);
301
302 ui.m_statusWidget->setIdle(i18nc("@info:status", "<strong>The debugger application is missing or "
303 "could not be launched.</strong>"));
304
305 ui.m_backtraceEdit->setPlainText(i18nc("@info:status",
306 "The crash information could not be generated."));
307 ui.m_extraDetailsLabel->setVisible(true);
308 ui.m_extraDetailsLabel->setText(i18nc("@info/rich", "<strong>You need to first install the debugger "
309 "application (%1) then click the <interface>Reload"
310 "</interface> button.</strong>",
311 m_btGenerator->debugger().name()));
312 }
313
314 ui.m_reloadBacktraceButton->setEnabled(true);
315 emit stateChanged();
316}
317
318void BacktraceWidget::backtraceNewLine(const QString & line)
319{
320 //While loading the backtrace (unparsed) a new line was sent from the debugger, append it
321 ui.m_backtraceEdit->append(line.trimmed());
322}
323
324void BacktraceWidget::copyClicked()
325{
326 ui.m_backtraceEdit->selectAll();
327 ui.m_backtraceEdit->copy();
328}
329
330void BacktraceWidget::saveClicked()
331{
332 DrKonqi::saveReport(m_btGenerator->backtrace(), this);
333}
334
335void BacktraceWidget::hilightExtraDetailsLabel(bool hilight)
336{
337 QString stylesheet;
338 if (hilight) {
339 stylesheet = QLatin1String("border-width: 2px; "
340 "border-style: solid; "
341 "border-color: red;");
342 } else {
343 stylesheet = QLatin1String("border-width: 0px;");
344 }
345 stylesheet += QLatin1String(extraDetailsLabelMargin);
346 ui.m_extraDetailsLabel->setStyleSheet(stylesheet);
347}
348
349void BacktraceWidget::focusImproveBacktraceButton()
350{
351 ui.m_installDebugButton->setFocus();
352}
353
354void BacktraceWidget::installDebugPackages()
355{
356 ui.m_installDebugButton->setVisible(false);
357 m_debugPackageInstaller->installDebugPackages();
358}
359
360void BacktraceWidget::debugPackageError(const QString & errorMessage)
361{
362 ui.m_installDebugButton->setVisible(true);
363 KMessageBox::error(this, errorMessage, i18nc("@title:window", "Error during the installation of"
364 " debug symbols"));
365}
366
367void BacktraceWidget::debugPackageCanceled()
368{
369 ui.m_installDebugButton->setVisible(true);
370}
371
372bool BacktraceWidget::canInstallDebugPackages() const
373{
374 return m_debugPackageInstaller->canInstallDebugPackages();
375}
376
377void BacktraceWidget::toggleBacktrace(bool show)
378{
379 ui.m_backtraceStack->setCurrentWidget(show ? ui.backtracePage : ui.backtraceHelpPage);
380}
381
382void BacktraceWidget::extraDetailsLinkActivated(QString link)
383{
384 if (link.startsWith(QLatin1String("http"))) {
385 //Open externally
386 KToolInvocation::invokeBrowser(link);
387 } else if (link == QLatin1String("#missingDebugPackages")) {
388 BacktraceParser * btParser = m_btGenerator->parser();
389
390 QStringList missingDbgForFiles = btParser->librariesWithMissingDebugSymbols().toList();
391 missingDbgForFiles.insert(0, DrKonqi::crashedApplication()->executable().absoluteFilePath());
392
393 //HTML message
394 QString message;
395 message = "<html>";
396 message += i18n("The packages containing debug information for the following application and libraries are missing:");
397 message += "<br /><ul>";
398
399 Q_FOREACH(const QString & string, missingDbgForFiles) {
400 message += "<li>" + string + "</li>";
401 }
402
403 message += "</ul></html>";
404
405 KMessageBox::information(this, message, i18nc("messagebox title","Missing debug information packages"));
406 }
407}
408
409