1 | /* This file is part of the KDE project |
2 | Copyright 2008 Dominik Haumann <dhaumann kde org> |
3 | |
4 | This library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Library General Public |
6 | License version 2 as published by the Free Software Foundation. |
7 | |
8 | This library is distributed in the hope that it will be useful, |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
11 | Library General Public License for more details. |
12 | |
13 | You should have received a copy of the GNU Library General Public License |
14 | along with this library; see the file COPYING.LIB. If not, write to |
15 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
16 | Boston, MA 02110-1301, USA. |
17 | */ |
18 | |
19 | //BEGIN Includes |
20 | #include "katebacktracebrowser.h" |
21 | #include "katebacktracebrowser.moc" |
22 | |
23 | #include "btparser.h" |
24 | #include "btfileindexer.h" |
25 | |
26 | #include <klocale.h> // i18n |
27 | #include <kpluginfactory.h> |
28 | #include <kpluginloader.h> |
29 | #include <kaboutdata.h> |
30 | #include <KStandardDirs> |
31 | #include <ktexteditor/view.h> |
32 | #include <kdebug.h> |
33 | #include <klineedit.h> |
34 | #include <kfiledialog.h> |
35 | |
36 | #include <QDir> |
37 | #include <QFile> |
38 | #include <QFileInfo> |
39 | #include <QDataStream> |
40 | #include <QTimer> |
41 | #include <QClipboard> |
42 | |
43 | //END Includes |
44 | |
45 | K_PLUGIN_FACTORY(KateBtBrowserFactory, registerPlugin<KateBtBrowserPlugin>();) |
46 | K_EXPORT_PLUGIN(KateBtBrowserFactory(KAboutData("katebacktracebrowserplugin" ,"katebacktracebrowserplugin" ,ki18n("Backtrace Browser" ), "0.1" , ki18n("Browsing backtraces" ), KAboutData::License_LGPL_V2)) ) |
47 | |
48 | |
49 | KateBtBrowserPlugin* KateBtBrowserPlugin::s_self = 0L; |
50 | static QStringList fileExtensions = |
51 | QStringList() << "*.cpp" << "*.cxx" << "*.c" << "*.cc" |
52 | << "*.h" << "*.hpp" << "*.hxx" |
53 | << "*.moc" ; |
54 | |
55 | |
56 | KateBtBrowserPlugin::KateBtBrowserPlugin( QObject* parent, const QList<QVariant>&) |
57 | : Kate::Plugin ( (Kate::Application*)parent ) |
58 | , Kate::PluginConfigPageInterface() |
59 | , indexer(&db) |
60 | { |
61 | s_self = this; |
62 | db.loadFromFile(KStandardDirs::locateLocal( "data" , "kate/backtracedatabase" )); |
63 | } |
64 | |
65 | KateBtBrowserPlugin::~KateBtBrowserPlugin() |
66 | { |
67 | if (indexer.isRunning()) { |
68 | indexer.cancel(); |
69 | indexer.wait(); |
70 | } |
71 | |
72 | db.saveToFile(KStandardDirs::locateLocal( "data" , "kate/backtracedatabase" )); |
73 | |
74 | s_self = 0L; |
75 | } |
76 | |
77 | KateBtBrowserPlugin& KateBtBrowserPlugin::self() |
78 | { |
79 | return *s_self; |
80 | } |
81 | |
82 | Kate::PluginView *KateBtBrowserPlugin::createView (Kate::MainWindow *mainWindow) |
83 | { |
84 | KateBtBrowserPluginView* pv = new KateBtBrowserPluginView (mainWindow); |
85 | connect(this, SIGNAL(newStatus(QString)), |
86 | pv, SLOT(setStatus(QString))); |
87 | pv->setStatus(i18n("Indexed files: %1" , db.size())); |
88 | return pv; |
89 | } |
90 | |
91 | KateBtDatabase& KateBtBrowserPlugin::database() |
92 | { |
93 | return db; |
94 | } |
95 | |
96 | BtFileIndexer& KateBtBrowserPlugin::fileIndexer() |
97 | { |
98 | return indexer; |
99 | } |
100 | |
101 | void KateBtBrowserPlugin::startIndexer() |
102 | { |
103 | if (indexer.isRunning()) { |
104 | indexer.cancel(); |
105 | indexer.wait(); |
106 | } |
107 | KConfigGroup cg(KGlobal::config(), "backtracebrowser" ); |
108 | indexer.setSearchPaths(cg.readEntry("search-folders" , QStringList())); |
109 | indexer.setFilter(cg.readEntry("file-extensions" , fileExtensions)); |
110 | indexer.start(); |
111 | emit newStatus(i18n("Indexing files..." )); |
112 | } |
113 | |
114 | uint KateBtBrowserPlugin::configPages () const |
115 | { |
116 | return 1; |
117 | } |
118 | |
119 | Kate::PluginConfigPage* KateBtBrowserPlugin::configPage(uint number, QWidget *parent, const char *name) |
120 | { |
121 | if (number == 0) { |
122 | return new KateBtConfigWidget(parent, name); |
123 | } |
124 | |
125 | return 0L; |
126 | } |
127 | |
128 | QString KateBtBrowserPlugin::configPageName (uint number) const |
129 | { |
130 | if (number == 0) { |
131 | return i18n("Backtrace Browser" ); |
132 | } |
133 | return QString(); |
134 | } |
135 | |
136 | QString KateBtBrowserPlugin::configPageFullName (uint number) const |
137 | { |
138 | if (number == 0) { |
139 | return i18n("Backtrace Browser Settings" ); |
140 | } |
141 | return QString(); |
142 | } |
143 | |
144 | KIcon KateBtBrowserPlugin::configPageIcon (uint) const |
145 | { |
146 | return KIcon("kbugbuster" ); |
147 | } |
148 | |
149 | |
150 | |
151 | |
152 | |
153 | KateBtBrowserPluginView::KateBtBrowserPluginView(Kate::MainWindow *mainWindow) |
154 | : Kate::PluginView(mainWindow) |
155 | , mw(mainWindow) |
156 | { |
157 | toolView = mainWindow->createToolView("KateBtBrowserPlugin" , Kate::MainWindow::Bottom, SmallIcon("kbugbuster" ), i18n("Backtrace Browser" )); |
158 | QWidget* w = new QWidget(toolView); |
159 | setupUi(w); |
160 | w->show(); |
161 | |
162 | timer.setSingleShot(true); |
163 | connect(&timer, SIGNAL(timeout()), this, SLOT(clearStatus())); |
164 | |
165 | connect(btnBacktrace, SIGNAL(clicked()), this, SLOT(loadFile())); |
166 | connect(btnClipboard, SIGNAL(clicked()), this, SLOT(loadClipboard())); |
167 | connect(btnConfigure, SIGNAL(clicked()), this, SLOT(configure())); |
168 | connect(lstBacktrace, SIGNAL(itemActivated(QTreeWidgetItem*,int)), this, SLOT(itemActivated(QTreeWidgetItem*,int))); |
169 | } |
170 | |
171 | KateBtBrowserPluginView::~KateBtBrowserPluginView () |
172 | { |
173 | delete toolView; |
174 | } |
175 | |
176 | void KateBtBrowserPluginView::readSessionConfig(KConfigBase*, const QString&) |
177 | { |
178 | } |
179 | |
180 | void KateBtBrowserPluginView::writeSessionConfig(KConfigBase*, const QString&) |
181 | { |
182 | } |
183 | |
184 | void KateBtBrowserPluginView::loadFile() |
185 | { |
186 | QString url = KFileDialog::getOpenFileName(KUrl(), QString(), mw->window(), i18n("Load Backtrace" )); |
187 | QFile f(url); |
188 | if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { |
189 | QString str = f.readAll(); |
190 | loadBacktrace(str); |
191 | } |
192 | } |
193 | |
194 | void KateBtBrowserPluginView::loadClipboard() |
195 | { |
196 | QString bt = QApplication::clipboard()->text(); |
197 | loadBacktrace(bt); |
198 | } |
199 | |
200 | void KateBtBrowserPluginView::loadBacktrace(const QString& bt) |
201 | { |
202 | QList<BtInfo> infos = KateBtParser::parseBacktrace(bt); |
203 | |
204 | lstBacktrace->clear(); |
205 | foreach (const BtInfo& info, infos) { |
206 | QTreeWidgetItem* it = new QTreeWidgetItem(lstBacktrace); |
207 | it->setData(0, Qt::DisplayRole, QString::number(info.step)); |
208 | it->setData(0, Qt::ToolTipRole, QString::number(info.step)); |
209 | QFileInfo fi(info.filename); |
210 | it->setData(1, Qt::DisplayRole, fi.fileName()); |
211 | it->setData(1, Qt::ToolTipRole, info.filename); |
212 | |
213 | if (info.type == BtInfo::Source) { |
214 | it->setData(2, Qt::DisplayRole, QString::number(info.line)); |
215 | it->setData(2, Qt::ToolTipRole, QString::number(info.line)); |
216 | it->setData(2, Qt::UserRole, QVariant(info.line)); |
217 | } |
218 | it->setData(3, Qt::DisplayRole, info.function); |
219 | it->setData(3, Qt::ToolTipRole, info.function); |
220 | |
221 | lstBacktrace->addTopLevelItem(it); |
222 | } |
223 | lstBacktrace->resizeColumnToContents(0); |
224 | lstBacktrace->resizeColumnToContents(1); |
225 | lstBacktrace->resizeColumnToContents(2); |
226 | |
227 | if (lstBacktrace->topLevelItemCount()) { |
228 | setStatus(i18n("Loading backtrace succeeded" )); |
229 | } else { |
230 | setStatus(i18n("Loading backtrace failed" )); |
231 | } |
232 | } |
233 | |
234 | |
235 | void KateBtBrowserPluginView::configure() |
236 | { |
237 | KateBtConfigDialog dlg(mw->window()); |
238 | dlg.exec(); |
239 | } |
240 | |
241 | void KateBtBrowserPluginView::itemActivated(QTreeWidgetItem* item, int column) |
242 | { |
243 | Q_UNUSED(column); |
244 | |
245 | QVariant variant = item->data(2, Qt::UserRole); |
246 | if (variant.isValid()) { |
247 | int line = variant.toInt(); |
248 | QString file = QDir::fromNativeSeparators(item->data(1, Qt::ToolTipRole).toString()); |
249 | file = QDir::cleanPath(file); |
250 | |
251 | QString path = file; |
252 | // if not absolute path + exists, try to find with index |
253 | if (!QFile::exists(path)) { |
254 | // try to match the backtrace forms ".*/foo/bar.txt" and "foo/bar.txt" |
255 | static QRegExp rx1("/([^/]+)/([^/]+)$" ); |
256 | int idx = rx1.indexIn(file); |
257 | if (idx != -1) { |
258 | file = rx1.cap(1) + '/' + rx1.cap(2); |
259 | } else { |
260 | static QRegExp rx2("([^/]+)/([^/]+)$" ); |
261 | idx = rx2.indexIn(file); |
262 | if (idx != -1) { |
263 | // file is of correct form |
264 | } else { |
265 | kDebug() << "file patter did not match:" << file; |
266 | setStatus(i18n("File not found: %1" , file)); |
267 | return; |
268 | } |
269 | } |
270 | path = KateBtBrowserPlugin::self().database().value(file); |
271 | } |
272 | |
273 | if (!path.isEmpty() && QFile::exists(path)) { |
274 | KUrl url(path); |
275 | KTextEditor::View* kv = mw->openUrl(url); |
276 | kv->setCursorPosition(KTextEditor::Cursor(line - 1, 0)); |
277 | kv->setFocus(); |
278 | setStatus(i18n("Opened file: %1" , file)); |
279 | } |
280 | } else { |
281 | setStatus(i18n("No debugging information available" )); |
282 | } |
283 | } |
284 | |
285 | void KateBtBrowserPluginView::setStatus(const QString& status) |
286 | { |
287 | lblStatus->setText(status); |
288 | timer.start(10*1000); |
289 | } |
290 | |
291 | void KateBtBrowserPluginView::clearStatus() |
292 | { |
293 | lblStatus->setText(QString()); |
294 | } |
295 | |
296 | |
297 | |
298 | |
299 | KateBtConfigWidget::KateBtConfigWidget(QWidget* parent, const char* name) |
300 | : Kate::PluginConfigPage(parent, name) |
301 | { |
302 | setupUi(this); |
303 | edtUrl->setMode(KFile::Directory); |
304 | edtUrl->setUrl(KUrl(QDir().absolutePath())); |
305 | |
306 | reset(); |
307 | |
308 | connect(btnAdd, SIGNAL(clicked()), this, SLOT(add())); |
309 | connect(btnRemove, SIGNAL(clicked()), this, SLOT(remove())); |
310 | connect(edtExtensions, SIGNAL(textChanged(QString)), this, SLOT(textChanged())); |
311 | |
312 | m_changed = false; |
313 | } |
314 | |
315 | KateBtConfigWidget::~KateBtConfigWidget() |
316 | { |
317 | } |
318 | |
319 | void KateBtConfigWidget::apply() |
320 | { |
321 | if (m_changed) { |
322 | QStringList sl; |
323 | for (int i = 0; i < lstFolders->count(); ++i) { |
324 | sl << lstFolders->item(i)->data(Qt::DisplayRole).toString(); |
325 | } |
326 | KConfigGroup cg(KGlobal::config(), "backtracebrowser" ); |
327 | cg.writeEntry("search-folders" , sl); |
328 | |
329 | QString filter = edtExtensions->text(); |
330 | filter.replace(',', ' ').replace(';', ' '); |
331 | cg.writeEntry("file-extensions" , filter.split(' ', QString::SkipEmptyParts)); |
332 | |
333 | KateBtBrowserPlugin::self().startIndexer(); |
334 | m_changed = false; |
335 | } |
336 | } |
337 | |
338 | void KateBtConfigWidget::reset() |
339 | { |
340 | KConfigGroup cg(KGlobal::config(), "backtracebrowser" ); |
341 | lstFolders->clear(); |
342 | lstFolders->addItems(cg.readEntry("search-folders" , QStringList())); |
343 | edtExtensions->setText(cg.readEntry("file-extensions" , fileExtensions).join(" " )); |
344 | } |
345 | |
346 | void KateBtConfigWidget::defaults() |
347 | { |
348 | lstFolders->clear(); |
349 | edtExtensions->setText(fileExtensions.join(" " )); |
350 | |
351 | m_changed = true; |
352 | } |
353 | |
354 | void KateBtConfigWidget::add() |
355 | { |
356 | QDir url(edtUrl->lineEdit()->text()); |
357 | if (url.exists()) |
358 | if (lstFolders->findItems(url.absolutePath(), Qt::MatchExactly).size() == 0) { |
359 | lstFolders->addItem(url.absolutePath()); |
360 | emit changed(); |
361 | m_changed = true; |
362 | } |
363 | } |
364 | |
365 | void KateBtConfigWidget::remove() |
366 | { |
367 | QListWidgetItem* item = lstFolders->currentItem(); |
368 | if (item) { |
369 | delete item; |
370 | emit changed(); |
371 | m_changed = true; |
372 | } |
373 | } |
374 | |
375 | void KateBtConfigWidget::textChanged() |
376 | { |
377 | emit changed(); |
378 | m_changed = true; |
379 | } |
380 | |
381 | |
382 | |
383 | |
384 | |
385 | KateBtConfigDialog::KateBtConfigDialog(QWidget* parent) |
386 | : KDialog(parent) |
387 | { |
388 | setCaption(i18n("Backtrace Browser Settings" )); |
389 | setButtons(KDialog::Ok | KDialog::Cancel); |
390 | |
391 | m_configWidget = new KateBtConfigWidget(this, "kate_bt_config_widget" ); |
392 | setMainWidget(m_configWidget); |
393 | |
394 | connect(this, SIGNAL(applyClicked()), m_configWidget, SLOT(apply())); |
395 | connect(this, SIGNAL(okClicked()), m_configWidget, SLOT(apply())); |
396 | connect(m_configWidget, SIGNAL(changed()), this, SLOT(changed())); |
397 | } |
398 | |
399 | KateBtConfigDialog::~KateBtConfigDialog() |
400 | { |
401 | } |
402 | |
403 | void KateBtConfigDialog::changed() |
404 | { |
405 | enableButtonApply(true); |
406 | } |
407 | |
408 | // kate: space-indent on; indent-width 2; replace-tabs on; |
409 | |