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
45K_PLUGIN_FACTORY(KateBtBrowserFactory, registerPlugin<KateBtBrowserPlugin>();)
46K_EXPORT_PLUGIN(KateBtBrowserFactory(KAboutData("katebacktracebrowserplugin","katebacktracebrowserplugin",ki18n("Backtrace Browser"), "0.1", ki18n("Browsing backtraces"), KAboutData::License_LGPL_V2)) )
47
48
49KateBtBrowserPlugin* KateBtBrowserPlugin::s_self = 0L;
50static QStringList fileExtensions =
51 QStringList() << "*.cpp" << "*.cxx" << "*.c" << "*.cc"
52 << "*.h" << "*.hpp" << "*.hxx"
53 << "*.moc";
54
55
56KateBtBrowserPlugin::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
65KateBtBrowserPlugin::~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
77KateBtBrowserPlugin& KateBtBrowserPlugin::self()
78{
79 return *s_self;
80}
81
82Kate::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
91KateBtDatabase& KateBtBrowserPlugin::database()
92{
93 return db;
94}
95
96BtFileIndexer& KateBtBrowserPlugin::fileIndexer()
97{
98 return indexer;
99}
100
101void 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
114uint KateBtBrowserPlugin::configPages () const
115{
116 return 1;
117}
118
119Kate::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
128QString KateBtBrowserPlugin::configPageName (uint number) const
129{
130 if (number == 0) {
131 return i18n("Backtrace Browser");
132 }
133 return QString();
134}
135
136QString KateBtBrowserPlugin::configPageFullName (uint number) const
137{
138 if (number == 0) {
139 return i18n("Backtrace Browser Settings");
140 }
141 return QString();
142}
143
144KIcon KateBtBrowserPlugin::configPageIcon (uint) const
145{
146 return KIcon("kbugbuster");
147}
148
149
150
151
152
153KateBtBrowserPluginView::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
171KateBtBrowserPluginView::~KateBtBrowserPluginView ()
172{
173 delete toolView;
174}
175
176void KateBtBrowserPluginView::readSessionConfig(KConfigBase*, const QString&)
177{
178}
179
180void KateBtBrowserPluginView::writeSessionConfig(KConfigBase*, const QString&)
181{
182}
183
184void 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
194void KateBtBrowserPluginView::loadClipboard()
195{
196 QString bt = QApplication::clipboard()->text();
197 loadBacktrace(bt);
198}
199
200void 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
235void KateBtBrowserPluginView::configure()
236{
237 KateBtConfigDialog dlg(mw->window());
238 dlg.exec();
239}
240
241void 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
285void KateBtBrowserPluginView::setStatus(const QString& status)
286{
287 lblStatus->setText(status);
288 timer.start(10*1000);
289}
290
291void KateBtBrowserPluginView::clearStatus()
292{
293 lblStatus->setText(QString());
294}
295
296
297
298
299KateBtConfigWidget::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
315KateBtConfigWidget::~KateBtConfigWidget()
316{
317}
318
319void 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
338void 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
346void KateBtConfigWidget::defaults()
347{
348 lstFolders->clear();
349 edtExtensions->setText(fileExtensions.join(" "));
350
351 m_changed = true;
352}
353
354void 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
365void KateBtConfigWidget::remove()
366{
367 QListWidgetItem* item = lstFolders->currentItem();
368 if (item) {
369 delete item;
370 emit changed();
371 m_changed = true;
372 }
373}
374
375void KateBtConfigWidget::textChanged()
376{
377 emit changed();
378 m_changed = true;
379}
380
381
382
383
384
385KateBtConfigDialog::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
399KateBtConfigDialog::~KateBtConfigDialog()
400{
401}
402
403void KateBtConfigDialog::changed()
404{
405 enableButtonApply(true);
406}
407
408// kate: space-indent on; indent-width 2; replace-tabs on;
409