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 | |
42 | static const char [] = " margin: 5px; " ; |
43 | |
44 | BacktraceWidget::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 | |
127 | void 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 |
153 | void 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 | |
166 | void 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 | |
184 | void 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 | |
202 | void 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 | |
318 | void 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 | |
324 | void BacktraceWidget::copyClicked() |
325 | { |
326 | ui.m_backtraceEdit->selectAll(); |
327 | ui.m_backtraceEdit->copy(); |
328 | } |
329 | |
330 | void BacktraceWidget::saveClicked() |
331 | { |
332 | DrKonqi::saveReport(m_btGenerator->backtrace(), this); |
333 | } |
334 | |
335 | void BacktraceWidget::(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 | |
349 | void BacktraceWidget::focusImproveBacktraceButton() |
350 | { |
351 | ui.m_installDebugButton->setFocus(); |
352 | } |
353 | |
354 | void BacktraceWidget::installDebugPackages() |
355 | { |
356 | ui.m_installDebugButton->setVisible(false); |
357 | m_debugPackageInstaller->installDebugPackages(); |
358 | } |
359 | |
360 | void 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 | |
367 | void BacktraceWidget::debugPackageCanceled() |
368 | { |
369 | ui.m_installDebugButton->setVisible(true); |
370 | } |
371 | |
372 | bool BacktraceWidget::canInstallDebugPackages() const |
373 | { |
374 | return m_debugPackageInstaller->canInstallDebugPackages(); |
375 | } |
376 | |
377 | void BacktraceWidget::toggleBacktrace(bool show) |
378 | { |
379 | ui.m_backtraceStack->setCurrentWidget(show ? ui.backtracePage : ui.backtraceHelpPage); |
380 | } |
381 | |
382 | void BacktraceWidget::(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 | |