katebacktracebrowser.cpp 11.8 KB
Newer Older
1
/* This file is part of the KDE project
2
   Copyright 2008-2014 Dominik Haumann <dhaumann kde org>
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

19
// BEGIN Includes
20 21 22 23 24
#include "katebacktracebrowser.h"

#include "btparser.h"
#include "btfileindexer.h"

25
#include <klocalizedstring.h> // i18n
26 27 28
#include <kpluginfactory.h>
#include <kpluginloader.h>
#include <kaboutdata.h>
29
#include <ktexteditor/view.h>
30
#include <ksharedconfig.h>
31 32
#include <KConfigGroup>
#include <KLineEdit>
33 34 35 36

#include <QDir>
#include <QFile>
#include <QFileInfo>
37
#include <QFileDialog>
38
#include <QDataStream>
39
#include <QStandardPaths>
40 41
#include <QTimer>
#include <QClipboard>
42 43
#include <QDialogButtonBox>
#include <QUrl>
44
#include <QTreeWidget>
45
// END Includes
46

47
K_PLUGIN_FACTORY_WITH_JSON(KateBtBrowserFactory, "katebacktracebrowserplugin.json", registerPlugin<KateBtBrowserPlugin>();)
48

49
KateBtBrowserPlugin *KateBtBrowserPlugin::s_self = nullptr;
50 51
static QStringList fileExtensions = QStringList() << QStringLiteral("*.cpp") << QStringLiteral("*.cxx") << QStringLiteral("*.c") << QStringLiteral("*.cc") << QStringLiteral("*.h") << QStringLiteral("*.hpp") << QStringLiteral("*.hxx")
                                                  << QStringLiteral("*.moc");
52

53 54 55
KateBtBrowserPlugin::KateBtBrowserPlugin(QObject *parent, const QList<QVariant> &)
    : KTextEditor::Plugin(parent)
    , indexer(&db)
56
{
57
    s_self = this;
58
    db.loadFromFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/katebtbrowser/backtracedatabase.db"));
59 60 61 62
}

KateBtBrowserPlugin::~KateBtBrowserPlugin()
{
63 64 65 66
    if (indexer.isRunning()) {
        indexer.cancel();
        indexer.wait();
    }
67

68
    const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/katebtbrowser");
Dominik Haumann's avatar
Dominik Haumann committed
69
    QDir().mkpath(path);
Joseph Wenninger's avatar
Joseph Wenninger committed
70
    db.saveToFile(path + QStringLiteral("/backtracedatabase.db"));
71

72
    s_self = nullptr;
73 74
}

75
KateBtBrowserPlugin &KateBtBrowserPlugin::self()
76
{
77
    return *s_self;
78 79
}

80
QObject *KateBtBrowserPlugin::createView(KTextEditor::MainWindow *mainWindow)
81
{
82 83
    KateBtBrowserPluginView *view = new KateBtBrowserPluginView(this, mainWindow);
    return view;
84 85
}

86
KateBtDatabase &KateBtBrowserPlugin::database()
87
{
88
    return db;
89 90
}

91
BtFileIndexer &KateBtBrowserPlugin::fileIndexer()
92
{
93
    return indexer;
94 95 96 97
}

void KateBtBrowserPlugin::startIndexer()
{
98 99 100 101 102 103 104 105 106
    if (indexer.isRunning()) {
        indexer.cancel();
        indexer.wait();
    }
    KConfigGroup cg(KSharedConfig::openConfig(), "backtracebrowser");
    indexer.setSearchPaths(cg.readEntry("search-folders", QStringList()));
    indexer.setFilter(cg.readEntry("file-extensions", fileExtensions));
    indexer.start();
    emit newStatus(i18n("Indexing files..."));
107 108
}

109
int KateBtBrowserPlugin::configPages() const
110
{
111
    return 1;
112 113
}

114
KTextEditor::ConfigPage *KateBtBrowserPlugin::configPage(int number, QWidget *parent)
115
{
116 117 118
    if (number == 0) {
        return new KateBtConfigWidget(parent);
    }
119

120
    return nullptr;
121 122
}

123
KateBtBrowserPluginView::KateBtBrowserPluginView(KateBtBrowserPlugin *plugin, KTextEditor::MainWindow *mainWindow)
124 125
    : QObject(mainWindow)
    , m_plugin(plugin)
126
{
127
    // init console
128
    QWidget *toolview = mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katebacktracebrowserplugin"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("tools-report-bug")), i18n("Backtrace Browser"));
129
    m_widget = new KateBtBrowserWidget(mainWindow, toolview);
130

131
    connect(plugin, &KateBtBrowserPlugin::newStatus, m_widget, &KateBtBrowserWidget::setStatus);
132 133
}

134
KateBtBrowserPluginView::~KateBtBrowserPluginView()
135
{
136 137 138 139
    // cleanup, kill toolview + widget
    QWidget *toolview = m_widget->parentWidget();
    delete m_widget;
    delete toolview;
140 141
}

142 143 144
KateBtBrowserWidget::KateBtBrowserWidget(KTextEditor::MainWindow *mainwindow, QWidget *parent)
    : QWidget(parent)
    , mw(mainwindow)
145
{
146
    setupUi(this);
147

148
    timer.setSingleShot(true);
149 150 151 152 153
    connect(&timer, &QTimer::timeout, this, &KateBtBrowserWidget::clearStatus);
    connect(btnBacktrace, &QPushButton::clicked, this, &KateBtBrowserWidget::loadFile);
    connect(btnClipboard, &QPushButton::clicked, this, &KateBtBrowserWidget::loadClipboard);
    connect(btnConfigure, &QPushButton::clicked, this, &KateBtBrowserWidget::configure);
    connect(lstBacktrace, &QTreeWidget::itemActivated, this, &KateBtBrowserWidget::itemActivated);
154 155
}

156
KateBtBrowserWidget::~KateBtBrowserWidget()
157 158 159
{
}

160
void KateBtBrowserWidget::loadFile()
161
{
162 163 164 165 166 167
    QString url = QFileDialog::getOpenFileName(mw->window(), i18n("Load Backtrace"));
    QFile f(url);
    if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QString str = QString::fromUtf8(f.readAll());
        loadBacktrace(str);
    }
168 169
}

170
void KateBtBrowserWidget::loadClipboard()
171
{
172 173
    QString bt = QApplication::clipboard()->text();
    loadBacktrace(bt);
174 175
}

176
void KateBtBrowserWidget::loadBacktrace(const QString &bt)
177
{
178
    QList<BtInfo> infos = KateBtParser::parseBacktrace(bt);
179

180
    lstBacktrace->clear();
181
    foreach (const BtInfo &info, infos) {
182 183 184 185 186 187
        QTreeWidgetItem *it = new QTreeWidgetItem(lstBacktrace);
        it->setData(0, Qt::DisplayRole, QString::number(info.step));
        it->setData(0, Qt::ToolTipRole, QString::number(info.step));
        QFileInfo fi(info.filename);
        it->setData(1, Qt::DisplayRole, fi.fileName());
        it->setData(1, Qt::ToolTipRole, info.filename);
188

189 190 191 192 193 194 195
        if (info.type == BtInfo::Source) {
            it->setData(2, Qt::DisplayRole, QString::number(info.line));
            it->setData(2, Qt::ToolTipRole, QString::number(info.line));
            it->setData(2, Qt::UserRole, QVariant(info.line));
        }
        it->setData(3, Qt::DisplayRole, info.function);
        it->setData(3, Qt::ToolTipRole, info.function);
196

197 198 199 200 201 202 203 204 205 206 207
        lstBacktrace->addTopLevelItem(it);
    }
    lstBacktrace->resizeColumnToContents(0);
    lstBacktrace->resizeColumnToContents(1);
    lstBacktrace->resizeColumnToContents(2);

    if (lstBacktrace->topLevelItemCount()) {
        setStatus(i18n("Loading backtrace succeeded"));
    } else {
        setStatus(i18n("Loading backtrace failed"));
    }
208 209
}

210
void KateBtBrowserWidget::configure()
211
{
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
    KateBtConfigDialog dlg(mw->window());
    dlg.exec();
}

void KateBtBrowserWidget::itemActivated(QTreeWidgetItem *item, int column)
{
    Q_UNUSED(column);

    QVariant variant = item->data(2, Qt::UserRole);
    if (variant.isValid()) {
        int line = variant.toInt();
        QString file = QDir::fromNativeSeparators(item->data(1, Qt::ToolTipRole).toString());
        file = QDir::cleanPath(file);

        QString path = file;
        // if not absolute path + exists, try to find with index
        if (!QFile::exists(path)) {
            // try to match the backtrace forms ".*/foo/bar.txt" and "foo/bar.txt"
Joseph Wenninger's avatar
Joseph Wenninger committed
230
            static QRegExp rx1(QStringLiteral("/([^/]+)/([^/]+)$"));
231 232 233 234
            int idx = rx1.indexIn(file);
            if (idx != -1) {
                file = rx1.cap(1) + QLatin1Char('/') + rx1.cap(2);
            } else {
Joseph Wenninger's avatar
Joseph Wenninger committed
235
                static QRegExp rx2(QStringLiteral("([^/]+)/([^/]+)$"));
236 237 238 239 240 241 242 243 244 245
                idx = rx2.indexIn(file);
                if (idx != -1) {
                    // file is of correct form
                } else {
                    qDebug() << "file patter did not match:" << file;
                    setStatus(i18n("File not found: %1", file));
                    return;
                }
            }
            path = KateBtBrowserPlugin::self().database().value(file);
246 247
        }

248 249 250 251 252 253 254 255
        if (!path.isEmpty() && QFile::exists(path)) {
            KTextEditor::View *kv = mw->openUrl(QUrl(path));
            kv->setCursorPosition(KTextEditor::Cursor(line - 1, 0));
            kv->setFocus();
            setStatus(i18n("Opened file: %1", file));
        }
    } else {
        setStatus(i18n("No debugging information available"));
256 257 258
    }
}

259
void KateBtBrowserWidget::setStatus(const QString &status)
260
{
261 262
    lblStatus->setText(status);
    timer.start(10 * 1000);
263 264
}

265
void KateBtBrowserWidget::clearStatus()
266
{
267
    lblStatus->setText(QString());
268 269
}

270 271
KateBtConfigWidget::KateBtConfigWidget(QWidget *parent)
    : KTextEditor::ConfigPage(parent)
272
{
273 274 275
    setupUi(this);
    edtUrl->setMode(KFile::Directory);
    edtUrl->setUrl(QUrl(QDir().absolutePath()));
276

277
    reset();
278

279 280 281
    connect(btnAdd, &QPushButton::clicked, this, &KateBtConfigWidget::add);
    connect(btnRemove, &QPushButton::clicked, this, &KateBtConfigWidget::remove);
    connect(edtExtensions, &QLineEdit::textChanged, this, &KateBtConfigWidget::textChanged);
282

283
    m_changed = false;
284 285 286 287 288 289
}

KateBtConfigWidget::~KateBtConfigWidget()
{
}

290 291
QString KateBtConfigWidget::name() const
{
292
    return i18n("Backtrace");
293 294 295 296
}

QString KateBtConfigWidget::fullName() const
{
Albert Astals Cid's avatar
Albert Astals Cid committed
297
    return i18n("Backtrace Settings");
298 299 300 301
}

QIcon KateBtConfigWidget::icon() const
{
302
    return QIcon::fromTheme(QStringLiteral("tools-report-bug"));
303 304
}

305 306
void KateBtConfigWidget::apply()
{
307 308 309 310 311 312 313
    if (m_changed) {
        QStringList sl;
        for (int i = 0; i < lstFolders->count(); ++i) {
            sl << lstFolders->item(i)->data(Qt::DisplayRole).toString();
        }
        KConfigGroup cg(KSharedConfig::openConfig(), "backtracebrowser");
        cg.writeEntry("search-folders", sl);
314

315 316 317
        QString filter = edtExtensions->text();
        filter.replace(QLatin1Char(','), QLatin1Char(' ')).replace(QLatin1Char(';'), QLatin1Char(' '));
        cg.writeEntry("file-extensions", filter.split(QLatin1Char(' '), QString::SkipEmptyParts));
318

319 320 321
        KateBtBrowserPlugin::self().startIndexer();
        m_changed = false;
    }
322 323 324 325
}

void KateBtConfigWidget::reset()
{
326 327 328
    KConfigGroup cg(KSharedConfig::openConfig(), "backtracebrowser");
    lstFolders->clear();
    lstFolders->addItems(cg.readEntry("search-folders", QStringList()));
329
    edtExtensions->setText(cg.readEntry("file-extensions", fileExtensions).join(QLatin1Char(' ')));
330 331 332 333
}

void KateBtConfigWidget::defaults()
{
334
    lstFolders->clear();
335
    edtExtensions->setText(fileExtensions.join(QLatin1Char(' ')));
336

337
    m_changed = true;
338 339 340 341
}

void KateBtConfigWidget::add()
{
342 343
    QDir url(edtUrl->lineEdit()->text());
    if (url.exists())
344
        if (lstFolders->findItems(url.absolutePath(), Qt::MatchExactly).empty()) {
345 346 347 348
            lstFolders->addItem(url.absolutePath());
            emit changed();
            m_changed = true;
        }
349 350 351 352
}

void KateBtConfigWidget::remove()
{
353 354 355 356 357 358
    QListWidgetItem *item = lstFolders->currentItem();
    if (item) {
        delete item;
        emit changed();
        m_changed = true;
    }
359 360 361 362
}

void KateBtConfigWidget::textChanged()
{
363 364
    emit changed();
    m_changed = true;
365 366
}

367 368
KateBtConfigDialog::KateBtConfigDialog(QWidget *parent)
    : QDialog(parent)
369
{
370
    setWindowTitle(i18n("Backtrace Browser Settings"));
371

372
    m_configWidget = new KateBtConfigWidget(this);
373

374
    QVBoxLayout *layout = new QVBoxLayout(this);
375

376 377
    QDialogButtonBox *box = new QDialogButtonBox(this);
    box->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
378

379 380
    layout->addWidget(m_configWidget);
    layout->addWidget(box);
381

382 383 384
    connect(this, &KateBtConfigDialog::accepted, m_configWidget, &KateBtConfigWidget::apply);
    connect(box, &QDialogButtonBox::accepted, this, &KateBtConfigDialog::accept);
    connect(box, &QDialogButtonBox::rejected, this, &KateBtConfigDialog::reject);
385 386 387 388 389 390
}

KateBtConfigDialog::~KateBtConfigDialog()
{
}

391 392
#include "katebacktracebrowser.moc"

393
// kate: space-indent on; indent-width 4; replace-tabs on;