diff --git a/addons/backtracebrowser/autotests/btbrowsertest.cpp b/addons/backtracebrowser/autotests/btbrowsertest.cpp index 2447ddea3..e67676f29 100644 --- a/addons/backtracebrowser/autotests/btbrowsertest.cpp +++ b/addons/backtracebrowser/autotests/btbrowsertest.cpp @@ -1,97 +1,97 @@ /* This file is part of the KDE project * * Copyright 2014 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "btbrowsertest.h" #include "btparser.h" -#include -#include #include +#include +#include QTEST_MAIN(KateBtBrowserTest) void KateBtBrowserTest::initTestCase() { } void KateBtBrowserTest::cleanupTestCase() { } void KateBtBrowserTest::testParser() { // make sure the different types of gdb backtraces are parsed correctly // 1) #24 0xb688ff8e in QApplication::notify (this=0xbf997e8c, receiver=0x82607e8, e=0xbf997074) at kernel/qapplication.cpp:3115 // 2) #39 0xb634211c in g_main_context_dispatch () from /usr/lib/libglib-2.0.so.0 // 3) #41 0x0805e690 in ?? () // 4) #5 0xffffe410 in __kernel_vsyscall () QString bt = QStringLiteral("#24 0xb688ff8e in QApplication::notify (this=0xbf997e8c, receiver=0x82607e8, e=0xbf997074) at kernel/qapplication.cpp:3115"); QList info = KateBtParser::parseBacktrace(bt); QVERIFY(info.size() == 1); QCOMPARE(info[0].type, BtInfo::Source); QCOMPARE(info[0].original, bt); QCOMPARE(info[0].filename, QLatin1String("kernel/qapplication.cpp")); QCOMPARE(info[0].function, QLatin1String("QApplication::notify (this=0xbf997e8c, receiver=0x82607e8, e=0xbf997074)")); QCOMPARE(info[0].address, QLatin1String("0xb688ff8e")); QCOMPARE(info[0].line, 3115); QCOMPARE(info[0].step, 24); bt = QStringLiteral("#39 0xb634211c in g_main_context_dispatch () from /usr/lib/libglib-2.0.so.0"); info = KateBtParser::parseBacktrace(bt); QVERIFY(info.size() == 1); QCOMPARE(info[0].type, BtInfo::Lib); QCOMPARE(info[0].original, bt); QCOMPARE(info[0].filename, QLatin1String("/usr/lib/libglib-2.0.so.0")); QCOMPARE(info[0].function, QLatin1String("g_main_context_dispatch ()")); QCOMPARE(info[0].address, QLatin1String("0xb634211c")); QCOMPARE(info[0].line, -1); QCOMPARE(info[0].step, 39); bt = QStringLiteral("#41 0x0805e690 in ?? ()"); info = KateBtParser::parseBacktrace(bt); QVERIFY(info.size() == 1); QCOMPARE(info[0].type, BtInfo::Unknown); QCOMPARE(info[0].original, bt); QCOMPARE(info[0].filename, QString()); QCOMPARE(info[0].function, QString()); QCOMPARE(info[0].address, QLatin1String("0x0805e690")); QCOMPARE(info[0].line, -1); QCOMPARE(info[0].step, 41); bt = QStringLiteral("#5 0xffffe410 in __kernel_vsyscall ()"); info = KateBtParser::parseBacktrace(bt); QVERIFY(info.size() == 1); QCOMPARE(info[0].type, BtInfo::Unknown); QCOMPARE(info[0].original, bt); QCOMPARE(info[0].filename, QString()); QCOMPARE(info[0].function, QLatin1String("__kernel_vsyscall ()")); QCOMPARE(info[0].address, QLatin1String("0xffffe410")); QCOMPARE(info[0].line, -1); QCOMPARE(info[0].step, 5); bt = QStringLiteral(" Thread 1 (Thread 0x7fb6ba260780 (LWP 16447)):\n[KCrash Handler]"); info = KateBtParser::parseBacktrace(bt); QVERIFY(info.empty()); } // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/backtracebrowser/btdatabase.cpp b/addons/backtracebrowser/btdatabase.cpp index f5f557450..31f861739 100644 --- a/addons/backtracebrowser/btdatabase.cpp +++ b/addons/backtracebrowser/btdatabase.cpp @@ -1,92 +1,92 @@ /* This file is part of the KDE project Copyright 2008-2014 Dominik Haumann 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. */ #include "btdatabase.h" -#include #include -#include #include +#include +#include void KateBtDatabase::loadFromFile(const QString &url) { QFile file(url); if (file.open(QIODevice::ReadOnly)) { QMutexLocker locker(&mutex); QDataStream ds(&file); ds >> db; } // qDebug() << "Number of entries in the backtrace database" << url << ":" << db.size(); } void KateBtDatabase::saveToFile(const QString &url) const { QFile file(url); if (file.open(QIODevice::WriteOnly)) { QMutexLocker locker(&mutex); QDataStream ds(&file); ds << db; } // qDebug() << "Saved backtrace database to" << url; } QString KateBtDatabase::value(const QString &key) { // key is either of the form "foo/bar.txt" or only "bar.txt" QString file = key; QStringList sl = key.split(QLatin1Char('/')); if (sl.size() > 1) { file = sl[1]; } QMutexLocker locker(&mutex); if (db.contains(file)) { const QStringList &sl = db.value(file); for (int i = 0; i < sl.size(); ++i) { if (sl[i].indexOf(key) != -1) { return sl[i]; } } // try to use the first one if (!sl.empty()) { return sl[0]; } } return QString(); } void KateBtDatabase::add(const QString &folder, const QStringList &files) { QMutexLocker locker(&mutex); for (const QString &file : files) { QStringList &sl = db[file]; QString entry = QDir::fromNativeSeparators(folder + QLatin1Char('/') + file); if (!sl.contains(entry)) { sl << entry; } } } int KateBtDatabase::size() const { QMutexLocker locker(&mutex); return db.size(); } // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/backtracebrowser/btdatabase.h b/addons/backtracebrowser/btdatabase.h index f13aedfe9..d230620ff 100644 --- a/addons/backtracebrowser/btdatabase.h +++ b/addons/backtracebrowser/btdatabase.h @@ -1,53 +1,53 @@ /* This file is part of the KDE project Copyright 2008-2014 Dominik Haumann 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. */ #ifndef BTDATABASE_H #define BTDATABASE_H -#include -#include #include #include +#include +#include class KateBtDatabase { public: KateBtDatabase() { } ~KateBtDatabase() { } void loadFromFile(const QString &url); void saveToFile(const QString &url) const; QString value(const QString &key); void add(const QString &folder, const QStringList &files); int size() const; private: mutable QMutex mutex; QHash db; }; #endif // BTDATABASE_H // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/backtracebrowser/btfileindexer.cpp b/addons/backtracebrowser/btfileindexer.cpp index 014c3c999..7a544b7ce 100644 --- a/addons/backtracebrowser/btfileindexer.cpp +++ b/addons/backtracebrowser/btfileindexer.cpp @@ -1,93 +1,93 @@ /* This file is part of the KDE project Copyright 2008-2014 Dominik Haumann 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. */ #include "btfileindexer.h" #include "btdatabase.h" -#include #include +#include BtFileIndexer::BtFileIndexer(KateBtDatabase *database) : QThread() , cancelAsap(false) , db(database) { } BtFileIndexer::~BtFileIndexer() { } void BtFileIndexer::setSearchPaths(const QStringList &urls) { searchPaths.clear(); for (const QString &url : urls) { if (!searchPaths.contains(url)) { searchPaths += url; } } } void BtFileIndexer::setFilter(const QStringList &fileFilter) { filter = fileFilter; qDebug() << filter; } void BtFileIndexer::run() { if (filter.empty()) { qDebug() << "Filter is empty. Aborting."; return; } cancelAsap = false; for (int i = 0; i < searchPaths.size(); ++i) { if (cancelAsap) { break; } indexFiles(searchPaths[i]); } qDebug() << QStringLiteral("Backtrace file database contains %1 files").arg(db->size()); } void BtFileIndexer::cancel() { cancelAsap = true; } void BtFileIndexer::indexFiles(const QString &url) { QDir dir(url); if (!dir.exists()) { return; } QStringList files = dir.entryList(filter, QDir::Files | QDir::NoSymLinks | QDir::Readable | QDir::NoDotAndDotDot | QDir::CaseSensitive); db->add(url, files); QStringList subdirs = dir.entryList(QDir::Dirs | QDir::NoSymLinks | QDir::Readable | QDir::NoDotAndDotDot | QDir::CaseSensitive); for (int i = 0; i < subdirs.size(); ++i) { if (cancelAsap) { break; } indexFiles(url + QLatin1Char('/') + subdirs[i]); } } // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/backtracebrowser/btfileindexer.h b/addons/backtracebrowser/btfileindexer.h index cae4f65d8..969def1d5 100644 --- a/addons/backtracebrowser/btfileindexer.h +++ b/addons/backtracebrowser/btfileindexer.h @@ -1,54 +1,54 @@ /* This file is part of the KDE project Copyright 2008-2014 Dominik Haumann 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. */ #ifndef BTFILEINDEXER_H #define BTFILEINDEXER_H -#include #include #include +#include class KateBtDatabase; class BtFileIndexer : public QThread { Q_OBJECT public: BtFileIndexer(KateBtDatabase *db); ~BtFileIndexer() override; void setSearchPaths(const QStringList &urls); void setFilter(const QStringList &filter); void cancel(); protected: void run() override; void indexFiles(const QString &url); private: bool cancelAsap; QStringList searchPaths; QStringList filter; KateBtDatabase *db; }; #endif // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/backtracebrowser/btparser.cpp b/addons/backtracebrowser/btparser.cpp index 4100dd29a..302d17c4b 100644 --- a/addons/backtracebrowser/btparser.cpp +++ b/addons/backtracebrowser/btparser.cpp @@ -1,170 +1,170 @@ /* This file is part of the KDE project Copyright 2008-2014 Dominik Haumann 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. */ #include "btparser.h" -#include #include +#include static QString eolDelimiter(const QString &str) { // find the split character QString separator(QLatin1Char('\n')); if (str.indexOf(QLatin1String("\r\n")) != -1) { separator = QStringLiteral("\r\n"); } else if (str.indexOf(QLatin1Char('\r')) != -1) { separator = QLatin1Char('\r'); } return separator; } static bool lineNoLessThan(const QString &lhs, const QString &rhs) { QRegExp rx(QStringLiteral("^#(\\d+)")); int ilhs = rx.indexIn(lhs); int lhsLn = rx.cap(1).toInt(); int irhs = rx.indexIn(rhs); int rhsLn = rx.cap(1).toInt(); if (ilhs != -1 && irhs != -1) { return lhsLn < rhsLn; } else { return lhs < rhs; } } static QStringList normalizeBt(const QStringList &l) { QStringList normalized; bool append = false; for (int i = 0; i < l.size(); ++i) { QString str = l[i].trimmed(); if (str.length()) { if (str[0] == QLatin1Char('#')) { normalized << str; append = true; } else if (append) { normalized.last() += QLatin1Char(' ') + str; } } else { append = false; } } std::sort(normalized.begin(), normalized.end(), lineNoLessThan); // now every single line contains a whole backtrace info return normalized; } static BtInfo parseBtLine(const QString &line) { int index; // the syntax types we support are // a) #24 0xb688ff8e in QApplication::notify (this=0xbf997e8c, receiver=0x82607e8, e=0xbf997074) at kernel/qapplication.cpp:3115 // b) #39 0xb634211c in g_main_context_dispatch () from /usr/lib/libglib-2.0.so.0 // c) #41 0x0805e690 in ?? () // d) #5 0xffffe410 in __kernel_vsyscall () // try a) cap #number(1), address(2), function(3), filename(4), linenumber(5) static QRegExp rxa(QStringLiteral("^#(\\d+)\\s+(0x\\w+)\\s+in\\s+(.+)\\s+at\\s+(.+):(\\d+)$")); index = rxa.indexIn(line); if (index == 0) { BtInfo info; info.original = line; info.filename = rxa.cap(4); info.function = rxa.cap(3); info.address = rxa.cap(2); info.line = rxa.cap(5).toInt(); info.step = rxa.cap(1).toInt(); info.type = BtInfo::Source; return info; } // try b) cap #number(1), address(2), function(3), lib(4) static QRegExp rxb(QStringLiteral("^#(\\d+)\\s+(0x\\w+)\\s+in\\s+(.+)\\s+from\\s+(.+)$")); index = rxb.indexIn(line); if (index == 0) { BtInfo info; info.original = line; info.filename = rxb.cap(4); info.function = rxb.cap(3); info.address = rxb.cap(2); info.line = -1; info.step = rxb.cap(1).toInt(); info.type = BtInfo::Lib; return info; } // try c) #41 0x0805e690 in ?? () static QRegExp rxc(QStringLiteral("^#(\\d+)\\s+(0x\\w+)\\s+in\\s+\\?\\?\\s+\\(\\)$")); index = rxc.indexIn(line); if (index == 0) { BtInfo info; info.original = line; info.filename = QString(); info.function = QString(); info.address = rxc.cap(2); info.line = -1; info.step = rxc.cap(1).toInt(); info.type = BtInfo::Unknown; return info; } // try d) #5 0xffffe410 in __kernel_vsyscall () static QRegExp rxd(QStringLiteral("^#(\\d+)\\s+(0x\\w+)\\s+in\\s+(.+)$")); index = rxd.indexIn(line); if (index == 0) { BtInfo info; info.original = line; info.filename = QString(); info.function = rxd.cap(3); info.address = rxd.cap(2); info.line = -1; info.step = rxd.cap(1).toInt(); info.type = BtInfo::Unknown; return info; } qDebug() << "Unknown backtrace line:" << line; BtInfo info; info.type = BtInfo::Invalid; return info; } QList KateBtParser::parseBacktrace(const QString &bt) { QStringList l = bt.split(eolDelimiter(bt), QString::SkipEmptyParts); l = normalizeBt(l); QList btList; for (int i = 0; i < l.size(); ++i) { BtInfo info = parseBtLine(l[i]); if (info.type != BtInfo::Invalid) { btList.append(parseBtLine(l[i])); } } return btList; } // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/backtracebrowser/katebacktracebrowser.cpp b/addons/backtracebrowser/katebacktracebrowser.cpp index aac4c451b..6aad95c9c 100644 --- a/addons/backtracebrowser/katebacktracebrowser.cpp +++ b/addons/backtracebrowser/katebacktracebrowser.cpp @@ -1,393 +1,393 @@ /* This file is part of the KDE project Copyright 2008-2014 Dominik Haumann 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. */ // BEGIN Includes #include "katebacktracebrowser.h" -#include "btparser.h" #include "btfileindexer.h" +#include "btparser.h" +#include +#include +#include #include // i18n #include #include -#include -#include #include -#include -#include +#include +#include +#include +#include #include #include -#include #include -#include +#include #include #include -#include -#include -#include #include +#include // END Includes K_PLUGIN_FACTORY_WITH_JSON(KateBtBrowserFactory, "katebacktracebrowserplugin.json", registerPlugin();) KateBtBrowserPlugin *KateBtBrowserPlugin::s_self = nullptr; static QStringList fileExtensions = QStringList() << QStringLiteral("*.cpp") << QStringLiteral("*.cxx") << QStringLiteral("*.c") << QStringLiteral("*.cc") << QStringLiteral("*.h") << QStringLiteral("*.hpp") << QStringLiteral("*.hxx") << QStringLiteral("*.moc"); KateBtBrowserPlugin::KateBtBrowserPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) , indexer(&db) { s_self = this; db.loadFromFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/katebtbrowser/backtracedatabase.db")); } KateBtBrowserPlugin::~KateBtBrowserPlugin() { if (indexer.isRunning()) { indexer.cancel(); indexer.wait(); } const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/katebtbrowser"); QDir().mkpath(path); db.saveToFile(path + QStringLiteral("/backtracedatabase.db")); s_self = nullptr; } KateBtBrowserPlugin &KateBtBrowserPlugin::self() { return *s_self; } QObject *KateBtBrowserPlugin::createView(KTextEditor::MainWindow *mainWindow) { KateBtBrowserPluginView *view = new KateBtBrowserPluginView(this, mainWindow); return view; } KateBtDatabase &KateBtBrowserPlugin::database() { return db; } BtFileIndexer &KateBtBrowserPlugin::fileIndexer() { return indexer; } void KateBtBrowserPlugin::startIndexer() { 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...")); } int KateBtBrowserPlugin::configPages() const { return 1; } KTextEditor::ConfigPage *KateBtBrowserPlugin::configPage(int number, QWidget *parent) { if (number == 0) { return new KateBtConfigWidget(parent); } return nullptr; } KateBtBrowserPluginView::KateBtBrowserPluginView(KateBtBrowserPlugin *plugin, KTextEditor::MainWindow *mainWindow) : QObject(mainWindow) , m_plugin(plugin) { // init console QWidget *toolview = mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katebacktracebrowserplugin"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("tools-report-bug")), i18n("Backtrace Browser")); m_widget = new KateBtBrowserWidget(mainWindow, toolview); connect(plugin, &KateBtBrowserPlugin::newStatus, m_widget, &KateBtBrowserWidget::setStatus); } KateBtBrowserPluginView::~KateBtBrowserPluginView() { // cleanup, kill toolview + widget QWidget *toolview = m_widget->parentWidget(); delete m_widget; delete toolview; } KateBtBrowserWidget::KateBtBrowserWidget(KTextEditor::MainWindow *mainwindow, QWidget *parent) : QWidget(parent) , mw(mainwindow) { setupUi(this); timer.setSingleShot(true); 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); } KateBtBrowserWidget::~KateBtBrowserWidget() { } void KateBtBrowserWidget::loadFile() { 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); } } void KateBtBrowserWidget::loadClipboard() { QString bt = QApplication::clipboard()->text(); loadBacktrace(bt); } void KateBtBrowserWidget::loadBacktrace(const QString &bt) { const QList infos = KateBtParser::parseBacktrace(bt); lstBacktrace->clear(); for (const BtInfo &info : infos) { 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); 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); 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")); } } void KateBtBrowserWidget::configure() { 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" static QRegExp rx1(QStringLiteral("/([^/]+)/([^/]+)$")); int idx = rx1.indexIn(file); if (idx != -1) { file = rx1.cap(1) + QLatin1Char('/') + rx1.cap(2); } else { static QRegExp rx2(QStringLiteral("([^/]+)/([^/]+)$")); 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); } 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")); } } void KateBtBrowserWidget::setStatus(const QString &status) { lblStatus->setText(status); timer.start(10 * 1000); } void KateBtBrowserWidget::clearStatus() { lblStatus->setText(QString()); } KateBtConfigWidget::KateBtConfigWidget(QWidget *parent) : KTextEditor::ConfigPage(parent) { setupUi(this); edtUrl->setMode(KFile::Directory); edtUrl->setUrl(QUrl(QDir().absolutePath())); reset(); connect(btnAdd, &QPushButton::clicked, this, &KateBtConfigWidget::add); connect(btnRemove, &QPushButton::clicked, this, &KateBtConfigWidget::remove); connect(edtExtensions, &QLineEdit::textChanged, this, &KateBtConfigWidget::textChanged); m_changed = false; } KateBtConfigWidget::~KateBtConfigWidget() { } QString KateBtConfigWidget::name() const { return i18n("Backtrace"); } QString KateBtConfigWidget::fullName() const { return i18n("Backtrace Settings"); } QIcon KateBtConfigWidget::icon() const { return QIcon::fromTheme(QStringLiteral("tools-report-bug")); } void KateBtConfigWidget::apply() { 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); QString filter = edtExtensions->text(); filter.replace(QLatin1Char(','), QLatin1Char(' ')).replace(QLatin1Char(';'), QLatin1Char(' ')); cg.writeEntry("file-extensions", filter.split(QLatin1Char(' '), QString::SkipEmptyParts)); KateBtBrowserPlugin::self().startIndexer(); m_changed = false; } } void KateBtConfigWidget::reset() { KConfigGroup cg(KSharedConfig::openConfig(), "backtracebrowser"); lstFolders->clear(); lstFolders->addItems(cg.readEntry("search-folders", QStringList())); edtExtensions->setText(cg.readEntry("file-extensions", fileExtensions).join(QLatin1Char(' '))); } void KateBtConfigWidget::defaults() { lstFolders->clear(); edtExtensions->setText(fileExtensions.join(QLatin1Char(' '))); m_changed = true; } void KateBtConfigWidget::add() { QDir url(edtUrl->lineEdit()->text()); if (url.exists()) if (lstFolders->findItems(url.absolutePath(), Qt::MatchExactly).empty()) { lstFolders->addItem(url.absolutePath()); emit changed(); m_changed = true; } } void KateBtConfigWidget::remove() { QListWidgetItem *item = lstFolders->currentItem(); if (item) { delete item; emit changed(); m_changed = true; } } void KateBtConfigWidget::textChanged() { emit changed(); m_changed = true; } KateBtConfigDialog::KateBtConfigDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("Backtrace Browser Settings")); m_configWidget = new KateBtConfigWidget(this); QVBoxLayout *layout = new QVBoxLayout(this); QDialogButtonBox *box = new QDialogButtonBox(this); box->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); layout->addWidget(m_configWidget); layout->addWidget(box); connect(this, &KateBtConfigDialog::accepted, m_configWidget, &KateBtConfigWidget::apply); connect(box, &QDialogButtonBox::accepted, this, &KateBtConfigDialog::accept); connect(box, &QDialogButtonBox::rejected, this, &KateBtConfigDialog::reject); } KateBtConfigDialog::~KateBtConfigDialog() { } #include "katebacktracebrowser.moc" // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/backtracebrowser/katebacktracebrowser.h b/addons/backtracebrowser/katebacktracebrowser.h index 83a73f7df..cc23f29b3 100644 --- a/addons/backtracebrowser/katebacktracebrowser.h +++ b/addons/backtracebrowser/katebacktracebrowser.h @@ -1,152 +1,152 @@ /* This file is part of the KDE project Copyright 2008-2014 Dominik Haumann 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. */ #ifndef KATE_BACKTRACEBROWSER_H #define KATE_BACKTRACEBROWSER_H #include -#include #include +#include -#include "ui_btbrowserwidget.h" -#include "ui_btconfigwidget.h" #include "btdatabase.h" #include "btfileindexer.h" +#include "ui_btbrowserwidget.h" +#include "ui_btconfigwidget.h" +#include #include #include -#include class KateBtConfigWidget; class KateBtBrowserWidget; class KateBtBrowserPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit KateBtBrowserPlugin(QObject *parent = nullptr, const QList & = QList()); ~KateBtBrowserPlugin() override; static KateBtBrowserPlugin &self(); QObject *createView(KTextEditor::MainWindow *mainWindow) override; KateBtDatabase &database(); BtFileIndexer &fileIndexer(); void startIndexer(); Q_SIGNALS: void newStatus(const QString &); public: int configPages() const override; KTextEditor::ConfigPage *configPage(int number, QWidget *parent = nullptr) override; // // private data // private: KateBtDatabase db; BtFileIndexer indexer; static KateBtBrowserPlugin *s_self; }; class KateBtBrowserPluginView : public QObject { Q_OBJECT public: KateBtBrowserPluginView(KateBtBrowserPlugin *plugin, KTextEditor::MainWindow *mainWindow); /** * Virtual destructor. */ ~KateBtBrowserPluginView() override; private: KateBtBrowserPlugin *m_plugin; KateBtBrowserWidget *m_widget; }; class KateBtBrowserWidget : public QWidget, public Ui::BtBrowserWidget { Q_OBJECT public: KateBtBrowserWidget(KTextEditor::MainWindow *mainwindow, QWidget *parent); ~KateBtBrowserWidget() override; void loadBacktrace(const QString &bt); public Q_SLOTS: void loadFile(); void loadClipboard(); void configure(); void clearStatus(); void setStatus(const QString &status); private Q_SLOTS: void itemActivated(QTreeWidgetItem *item, int column); private: KTextEditor::MainWindow *mw; QTimer timer; }; class KateBtConfigWidget : public KTextEditor::ConfigPage, private Ui::BtConfigWidget { Q_OBJECT public: explicit KateBtConfigWidget(QWidget *parent = nullptr); ~KateBtConfigWidget() override; QString name() const override; QString fullName() const override; QIcon icon() const override; public Q_SLOTS: void apply() override; void reset() override; void defaults() override; private Q_SLOTS: void add(); void remove(); void textChanged(); private: bool m_changed; }; class KateBtConfigDialog : public QDialog { Q_OBJECT public: KateBtConfigDialog(QWidget *parent = nullptr); ~KateBtConfigDialog() override; private: KateBtConfigWidget *m_configWidget; }; #endif // KATE_BACKTRACEBROWSER_H // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/close-except-like/close_confirm_dialog.cpp b/addons/close-except-like/close_confirm_dialog.cpp index e558f0d7c..3f2819d72 100644 --- a/addons/close-except-like/close_confirm_dialog.cpp +++ b/addons/close-except-like/close_confirm_dialog.cpp @@ -1,121 +1,121 @@ /** * \file * * \brief Class \c kate::CloseConfirmDialog (implementation) * * Copyright (C) 2012 Alex Turbov * * \date Sun Jun 24 16:29:13 MSK 2012 -- Initial design */ /* * KateCloseExceptPlugin is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * KateCloseExceptPlugin 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ // Project specific includes #include "close_confirm_dialog.h" // Standard includes #include -#include /// \todo Where is \c i18n() defined? +#include #include +#include /// \todo Where is \c i18n() defined? #include -#include #include -#include #include +#include #include #include namespace kate { namespace { class KateDocItem : public QTreeWidgetItem { public: KateDocItem(KTextEditor::Document *doc, QTreeWidget *tw) : QTreeWidgetItem(tw) , document(doc) { setText(0, doc->documentName()); setText(1, doc->url().toString()); setCheckState(0, Qt::Checked); } KTextEditor::Document *document; }; } // anonymous namespace CloseConfirmDialog::CloseConfirmDialog(QList &docs, KToggleAction *show_confirmation_action, QWidget *const parent) : QDialog(parent) , m_docs(docs) { assert("Documents container expected to be non empty" && !docs.isEmpty()); setupUi(this); setWindowTitle(i18nc("@title:window", "Close files confirmation")); setModal(true); buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); icon->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::Desktop, KIconLoader::SizeLarge)); text->setText(i18nc("@label:listbox", "You are about to close the following documents:")); QStringList headers; headers << i18nc("@title:column", "Document") << i18nc("@title:column", "Location"); m_docs_tree->setHeaderLabels(headers); m_docs_tree->setSelectionMode(QAbstractItemView::SingleSelection); m_docs_tree->setRootIsDecorated(false); for (auto &doc : qAsConst(m_docs)) { new KateDocItem(doc, m_docs_tree); } m_docs_tree->header()->setStretchLastSection(false); m_docs_tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); m_docs_tree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); m_dont_ask_again->setText(i18nc("option:check", "Do not ask again")); // NOTE If we are here, it means that 'Show Confirmation' action is enabled, // so not needed to read config... assert("Sanity check" && show_confirmation_action->isChecked()); m_dont_ask_again->setCheckState(Qt::Unchecked); connect(m_dont_ask_again, &QCheckBox::toggled, show_confirmation_action, &KToggleAction::toggle); // Update documents list according checkboxes connect(this, &CloseConfirmDialog::accepted, this, &CloseConfirmDialog::updateDocsList); KConfigGroup gcg(KSharedConfig::openConfig(), "kate-close-except-like-CloseConfirmationDialog"); KWindowConfig::restoreWindowSize(windowHandle(), gcg); // restore dialog geometry from config } CloseConfirmDialog::~CloseConfirmDialog() { KConfigGroup gcg(KSharedConfig::openConfig(), "kate-close-except-like-CloseConfirmationDialog"); KWindowConfig::saveWindowSize(windowHandle(), gcg); // write dialog geometry to config gcg.sync(); } /** * Going to remove unchecked files from the given documents list */ void CloseConfirmDialog::updateDocsList() { for (QTreeWidgetItemIterator it(m_docs_tree, QTreeWidgetItemIterator::NotChecked); *it; ++it) { KateDocItem *item = static_cast(*it); m_docs.removeAll(item->document); qDebug() << "do not close the file " << item->document->url().toString(); } } } // namespace kate diff --git a/addons/close-except-like/close_confirm_dialog.h b/addons/close-except-like/close_confirm_dialog.h index 95e0418c2..c0fa57b15 100644 --- a/addons/close-except-like/close_confirm_dialog.h +++ b/addons/close-except-like/close_confirm_dialog.h @@ -1,65 +1,65 @@ /** * \file * * \brief Class \c kate::CloseConfirmDialog (interface) * * Copyright (C) 2012 Alex Turbov * * \date Sun Jun 24 16:29:13 MSK 2012 -- Initial design */ /* * KateCloseExceptPlugin is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * KateCloseExceptPlugin 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #ifndef __SRC__CLOSE_CONFIRM_DIALOG_H__ #define __SRC__CLOSE_CONFIRM_DIALOG_H__ // Project specific includes // Standard includes -#include +#include "ui_close_confirm_dialog.h" #include #include -#include +#include +#include #include #include #include -#include -#include "ui_close_confirm_dialog.h" +#include namespace kate { /** * \brief [Type brief class description here] * * [More detailed description here] * */ class CloseConfirmDialog : public QDialog, public Ui::CloseConfirmDialog { Q_OBJECT public: /// Default constructor explicit CloseConfirmDialog(QList &, KToggleAction *, QWidget *const = nullptr); ~CloseConfirmDialog() override; private Q_SLOTS: void updateDocsList(); private: QList &m_docs; }; } // namespace kate #endif // __SRC__CLOSE_CONFIRM_DIALOG_H__ diff --git a/addons/close-except-like/close_except_plugin.cpp b/addons/close-except-like/close_except_plugin.cpp index 39a34e047..3554a8600 100644 --- a/addons/close-except-like/close_except_plugin.cpp +++ b/addons/close-except-like/close_except_plugin.cpp @@ -1,276 +1,276 @@ /** * \file * * \brief Kate Close Except/Like plugin implementation * * Copyright (C) 2012 Alex Turbov * * \date Thu Mar 8 08:13:43 MSK 2012 -- Initial design */ /* * KateCloseExceptPlugin is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * KateCloseExceptPlugin 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ // Project specific includes #include "close_except_plugin.h" #include "close_confirm_dialog.h" // Standard includes -#include -#include -#include -#include #include #include +#include #include #include +#include +#include +#include +#include #include #include #include -#include K_PLUGIN_FACTORY_WITH_JSON(CloseExceptPluginFactory, "katecloseexceptplugin.json", registerPlugin();) namespace kate { // BEGIN CloseExceptPlugin CloseExceptPlugin::CloseExceptPlugin(QObject *application, const QList &) : KTextEditor::Plugin(application) { } QObject *CloseExceptPlugin::createView(KTextEditor::MainWindow *parent) { return new CloseExceptPluginView(parent, this); } void CloseExceptPlugin::readSessionConfig(const KConfigGroup &config) { const KConfigGroup scg(&config, QStringLiteral("menu")); m_show_confirmation_needed = scg.readEntry(QStringLiteral("ShowConfirmation"), true); } void CloseExceptPlugin::writeSessionConfig(KConfigGroup &config) { KConfigGroup scg(&config, QStringLiteral("menu")); scg.writeEntry(QStringLiteral("ShowConfirmation"), m_show_confirmation_needed); scg.sync(); } // END CloseExceptPlugin // BEGIN CloseExceptPluginView CloseExceptPluginView::CloseExceptPluginView(KTextEditor::MainWindow *mw, CloseExceptPlugin *plugin) : QObject(mw) , KXMLGUIClient() , m_plugin(plugin) , m_show_confirmation_action(new KToggleAction(i18nc("@action:inmenu", "Show Confirmation"), this)) , m_except_menu(new KActionMenu(i18nc("@action:inmenu close docs except the following...", "Close Except"), this)) , m_like_menu(new KActionMenu(i18nc("@action:inmenu close docs like the following...", "Close Like"), this)) , m_mainWindow(mw) { KXMLGUIClient::setComponentName(QStringLiteral("katecloseexceptplugin"), i18n("Close Except/Like Plugin")); setXMLFile(QStringLiteral("ui.rc")); actionCollection()->addAction(QStringLiteral("file_close_except"), m_except_menu); actionCollection()->addAction(QStringLiteral("file_close_like"), m_like_menu); connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::documentCreated, this, &CloseExceptPluginView::documentCreated); // Configure toggle action and connect it to update state m_show_confirmation_action->setChecked(m_plugin->showConfirmationNeeded()); connect(m_show_confirmation_action.data(), &KToggleAction::toggled, m_plugin, &CloseExceptPlugin::toggleShowConfirmation); // connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &CloseExceptPluginView::viewCreated); // Fill menu w/ currently opened document masks/groups updateMenu(); m_mainWindow->guiFactory()->addClient(this); } CloseExceptPluginView::~CloseExceptPluginView() { m_mainWindow->guiFactory()->removeClient(this); } void CloseExceptPluginView::viewCreated(KTextEditor::View *view) { connectToDocument(view->document()); updateMenu(); } void CloseExceptPluginView::documentCreated(KTextEditor::Editor *, KTextEditor::Document *document) { connectToDocument(document); updateMenu(); } void CloseExceptPluginView::connectToDocument(KTextEditor::Document *document) { // Subscribe self to document close and name changes connect(document, &KTextEditor::Document::aboutToClose, this, &CloseExceptPluginView::updateMenuSlotStub); connect(document, &KTextEditor::Document::documentNameChanged, this, &CloseExceptPluginView::updateMenuSlotStub); connect(document, &KTextEditor::Document::documentUrlChanged, this, &CloseExceptPluginView::updateMenuSlotStub); } void CloseExceptPluginView::updateMenuSlotStub(KTextEditor::Document *) { updateMenu(); } void CloseExceptPluginView::appendActionsFrom(const std::set &paths, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction) { for (const QUrl &path : paths) { QString action = path.path() + QLatin1Char('*'); actions[action] = QPointer(new QAction(action, menu)); menu->addAction(actions[action]); connect(actions[action].data(), &QAction::triggered, this, [this, closeFunction, action]() { (this->*closeFunction)(action); }); } } void CloseExceptPluginView::appendActionsFrom(const std::set &masks, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction) { for (const QString &mask : masks) { QString action = mask.startsWith(QLatin1Char('*')) ? mask : mask + QLatin1Char('*'); actions[action] = QPointer(new QAction(action, menu)); menu->addAction(actions[action]); connect(actions[action].data(), &QAction::triggered, this, [this, closeFunction, action]() { (this->*closeFunction)(action); }); } } void CloseExceptPluginView::updateMenu(const std::set &paths, const std::set &masks, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction) { // turn menu ON or OFF depending on collected results menu->setEnabled(!paths.empty()); // Clear previous menus for (actions_map_type::iterator it = actions.begin(), last = actions.end(); it != last;) { menu->removeAction(*it); actions.erase(it++); } // Form a new one appendActionsFrom(paths, actions, menu, closeFunction); if (!masks.empty()) { if (!paths.empty()) menu->addSeparator(); // Add separator between paths and file's ext filters appendActionsFrom(masks, actions, menu, closeFunction); } // Append 'Show Confirmation' toggle menu item menu->addSeparator(); // Add separator between paths and show confirmation menu->addAction(m_show_confirmation_action); } void CloseExceptPluginView::updateMenu() { const QList &docs = KTextEditor::Editor::instance()->application()->documents(); if (docs.size() < 2) { // qDebug() << "No docs r (or the only) opened right now --> disable menu"; m_except_menu->setEnabled(false); m_except_menu->addSeparator(); m_like_menu->setEnabled(false); m_like_menu->addSeparator(); /// \note It seems there is always a document present... it named \em 'Untitled' } else { // Iterate over documents and form a set of candidates typedef std::set paths_set_type; typedef std::set paths_set_type_masks; paths_set_type doc_paths; paths_set_type_masks masks; for (KTextEditor::Document *document : docs) { const QString &ext = QFileInfo(document->url().path()).completeSuffix(); if (!ext.isEmpty()) masks.insert(QStringLiteral("*.") + ext); doc_paths.insert(KIO::upUrl(document->url())); } paths_set_type paths = doc_paths; // qDebug() << "stage #1: Collected" << paths.size() << "paths and" << masks.size() << "masks"; // Add common paths to the collection for (paths_set_type::iterator it = doc_paths.begin(), last = doc_paths.end(); it != last; ++it) { for (QUrl url = *it; (!url.path().isEmpty()) && url.path() != QLatin1String("/"); url = KIO::upUrl(url)) { paths_set_type::iterator not_it = it; for (++not_it; not_it != last; ++not_it) if (!not_it->path().startsWith(url.path())) break; if (not_it == last) { paths.insert(url); break; } } } // qDebug() << "stage #2: Collected" << paths.size() << "paths and" << masks.size() << "masks"; // updateMenu(paths, masks, m_except_actions, m_except_menu, &CloseExceptPluginView::closeExcept); updateMenu(paths, masks, m_like_actions, m_like_menu, &CloseExceptPluginView::closeLike); } } void CloseExceptPluginView::close(const QString &item, const bool close_if_match) { QChar asterisk = QLatin1Char('*'); assert("Parameter seems invalid! Is smth has changed in the code?" && !item.isEmpty() && (item[0] == asterisk || item[item.size() - 1] == asterisk)); const bool is_path = item[0] != asterisk; const QString mask = is_path ? item.left(item.size() - 1) : item; // qDebug() << "Going to close items [" << close_if_match << "/" << is_path << "]: " << mask; QList docs2close; const QList &docs = KTextEditor::Editor::instance()->application()->documents(); for (KTextEditor::Document *document : docs) { const QString &path = KIO::upUrl(document->url()).path(); /// \note Take a dot in account, so \c *.c would not match for \c blah.kcfgc const QString &ext = QLatin1Char('.') + QFileInfo(document->url().fileName()).completeSuffix(); const bool match = (!is_path && mask.endsWith(ext)) || (is_path && path.startsWith(mask)); if (match == close_if_match) { // qDebug() << "*** Will close: " << document->url(); docs2close.push_back(document); } } if (docs2close.isEmpty()) { displayMessage(i18nc("@title:window", "Error"), i18nc("@info:tooltip", "No files to close ..."), KTextEditor::Message::Error); return; } // Show confirmation dialog if needed const bool removeNeeded = !m_plugin->showConfirmationNeeded() || CloseConfirmDialog(docs2close, m_show_confirmation_action, qobject_cast(this)).exec(); if (removeNeeded) { if (docs2close.isEmpty()) { displayMessage(i18nc("@title:window", "Error"), i18nc("@info:tooltip", "No files to close ..."), KTextEditor::Message::Error); } else { // Close 'em all! KTextEditor::Editor::instance()->application()->closeDocuments(docs2close); updateMenu(); displayMessage(i18nc("@title:window", "Done"), i18np("%1 file closed", "%1 files closed", docs2close.size()), KTextEditor::Message::Positive); } } } void CloseExceptPluginView::displayMessage(const QString &title, const QString &msg, KTextEditor::Message::MessageType level) { KTextEditor::View *kv = m_mainWindow->activeView(); if (!kv) return; delete m_infoMessage; m_infoMessage = new KTextEditor::Message(xi18nc("@info", "%1%2", title, msg), level); m_infoMessage->setWordWrap(true); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(5000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(kv); kv->document()->postMessage(m_infoMessage); } // END CloseExceptPluginView } // namespace kate #include "close_except_plugin.moc" // kate: hl C++11/Qt4; diff --git a/addons/close-except-like/close_except_plugin.h b/addons/close-except-like/close_except_plugin.h index 4373c2cd2..63f0d1f7a 100644 --- a/addons/close-except-like/close_except_plugin.h +++ b/addons/close-except-like/close_except_plugin.h @@ -1,132 +1,132 @@ /** * \file * * \brief Declare Kate's Close Except/Like plugin classes * * Copyright (C) 2012 Alex Turbov * * \date Thu Mar 8 08:13:43 MSK 2012 -- Initial design */ /* * KateCloseExceptPlugin is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * KateCloseExceptPlugin 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #ifndef __SRC__CLOSE_EXCEPT_PLUGIN_H__ #define __SRC__CLOSE_EXCEPT_PLUGIN_H__ // Project specific includes // Standard includes -#include -#include -#include #include +#include #include -#include +#include #include +#include +#include #include -#include #include #include +#include #include namespace kate { class CloseExceptPlugin; // forward declaration /** * \brief Plugin to close docs grouped by extension or location */ class CloseExceptPluginView : public QObject, public KXMLGUIClient { Q_OBJECT typedef QMap> actions_map_type; public: /// Default constructor CloseExceptPluginView(KTextEditor::MainWindow *, CloseExceptPlugin *); /// Destructor ~CloseExceptPluginView() override; private Q_SLOTS: void viewCreated(KTextEditor::View *); void documentCreated(KTextEditor::Editor *, KTextEditor::Document *); void updateMenuSlotStub(KTextEditor::Document *); void close(const QString &, const bool); void closeExcept(const QString &item) { close(item, false); } void closeLike(const QString &item) { close(item, true); } private: void displayMessage(const QString &, const QString &, KTextEditor::Message::MessageType); void connectToDocument(KTextEditor::Document *); void updateMenu(); using CloseFunction = void (CloseExceptPluginView::*)(const QString &); void updateMenu(const std::set &, const std::set &, actions_map_type &, KActionMenu *, CloseFunction); void appendActionsFrom(const std::set &, actions_map_type &, KActionMenu *, CloseFunction); void appendActionsFrom(const std::set &masks, actions_map_type &actions, KActionMenu *menu, CloseFunction); CloseExceptPlugin *m_plugin; QPointer m_show_confirmation_action; QPointer m_except_menu; QPointer m_like_menu; actions_map_type m_except_actions; actions_map_type m_like_actions; KTextEditor::MainWindow *m_mainWindow; QPointer m_infoMessage; }; /** * \brief Plugin view class */ class CloseExceptPlugin : public KTextEditor::Plugin, public KTextEditor::SessionConfigInterface { Q_OBJECT Q_INTERFACES(KTextEditor::SessionConfigInterface) public: /// Default constructor CloseExceptPlugin(QObject * = nullptr, const QList & = QList()); /// Destructor ~CloseExceptPlugin() override { } /// Create a new view of this plugin for the given main window QObject *createView(KTextEditor::MainWindow *) override; /// \name Plugin interface implementation //@{ void readSessionConfig(const KConfigGroup &) override; void writeSessionConfig(KConfigGroup &) override; //@} bool showConfirmationNeeded() const { return m_show_confirmation_needed; } public Q_SLOTS: void toggleShowConfirmation(bool flag) { m_show_confirmation_needed = flag; } private: bool m_show_confirmation_needed; }; } // namespace kate #endif // __SRC__CLOSE_EXCEPT_PLUGIN_H__ diff --git a/addons/externaltools/autotests/externaltooltest.cpp b/addons/externaltools/autotests/externaltooltest.cpp index 627de36f4..28b5d64d9 100644 --- a/addons/externaltools/autotests/externaltooltest.cpp +++ b/addons/externaltools/autotests/externaltooltest.cpp @@ -1,126 +1,126 @@ /* This file is part of the KDE project * * Copyright 2019 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "externaltooltest.h" #include "../kateexternaltool.h" #include "../katetoolrunner.h" -#include #include +#include #include #include QTEST_MAIN(ExternalToolTest) void ExternalToolTest::initTestCase() { } void ExternalToolTest::cleanupTestCase() { } void ExternalToolTest::testLoadSave() { KConfig config; KConfigGroup cg(&config, "tool"); KateExternalTool tool; tool.category = QStringLiteral("Git Tools"); tool.name = QStringLiteral("git cola"); tool.icon = QStringLiteral("git-cola"); tool.executable = QStringLiteral("git-cola"); tool.arguments = QStringLiteral("none"); tool.input = QStringLiteral("in"); tool.workingDir = QStringLiteral("/usr/bin"); tool.mimetypes = QStringList {QStringLiteral("everything")}; tool.hasexec = true; tool.actionName = QStringLiteral("asdf"); tool.cmdname = QStringLiteral("git-cola"); tool.saveMode = KateExternalTool::SaveMode::None; tool.save(cg); KateExternalTool clonedTool; clonedTool.load(cg); QCOMPARE(tool, clonedTool); } void ExternalToolTest::testRunListDirectory() { // Skip, if 'ls' is not installed if (QStandardPaths::findExecutable(QStringLiteral("ls")).isEmpty()) { QSKIP("'ls' not found - skipping test"); } std::unique_ptr tool(new KateExternalTool()); tool->category = QStringLiteral("Tools"); tool->name = QStringLiteral("ls"); tool->icon = QStringLiteral("none"); tool->executable = QStringLiteral("ls"); tool->arguments = QStringLiteral("/usr"); tool->workingDir = QStringLiteral("/tmp"); tool->mimetypes = QStringList {}; tool->hasexec = true; tool->actionName = QStringLiteral("ls"); tool->cmdname = QStringLiteral("ls"); tool->saveMode = KateExternalTool::SaveMode::None; std::unique_ptr tool2(new KateExternalTool(*tool)); // 1. /tmp $ ls /usr KateToolRunner runner1(std::move(tool), nullptr); runner1.run(); runner1.waitForFinished(); QVERIFY(runner1.outputData().contains(QStringLiteral("bin"))); // 2. /usr $ ls tool2->arguments.clear(); tool2->workingDir = QStringLiteral("/usr"); KateToolRunner runner2(std::move(tool2), nullptr); runner2.run(); runner2.waitForFinished(); QVERIFY(runner2.outputData().contains(QStringLiteral("bin"))); // 1. and 2. must give the same result QCOMPARE(runner1.outputData(), runner2.outputData()); } void ExternalToolTest::testRunTac() { // Skip, if 'tac' is not installed if (QStandardPaths::findExecutable(QStringLiteral("tac")).isEmpty()) { QSKIP("'tac' not found - skipping test"); } std::unique_ptr tool(new KateExternalTool()); tool->name = QStringLiteral("tac"); tool->executable = QStringLiteral("tac"); tool->input = QStringLiteral("a\nb\nc\n"); tool->saveMode = KateExternalTool::SaveMode::None; // run tac to reverse order KateToolRunner runner(std::move(tool), nullptr); runner.run(); runner.waitForFinished(); QCOMPARE(runner.outputData(), QStringLiteral("c\nb\na\n")); } // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/externaltoolsplugin.cpp b/addons/externaltools/externaltoolsplugin.cpp index 6ede9de77..3e324202e 100644 --- a/addons/externaltools/externaltoolsplugin.cpp +++ b/addons/externaltools/externaltoolsplugin.cpp @@ -1,329 +1,329 @@ /* This file is part of the KDE project * * Copyright 2019 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "externaltoolsplugin.h" -#include "kateexternaltoolsview.h" #include "kateexternaltool.h" #include "kateexternaltoolscommand.h" -#include "katetoolrunner.h" #include "kateexternaltoolsconfigwidget.h" +#include "kateexternaltoolsview.h" +#include "katetoolrunner.h" -#include -#include -#include #include #include +#include +#include +#include #include #include #include #include #include #include #include #include -#include #include +#include static QVector readDefaultTools() { QVector tools; KConfig systemConfig(QStringLiteral("defaultexternaltoolsrc")); KConfigGroup config(&systemConfig, "Global"); const int toolCount = config.readEntry("tools", 0); for (int i = 0; i < toolCount; ++i) { config = KConfigGroup(&systemConfig, QStringLiteral("Tool %1").arg(i)); KateExternalTool t; t.load(config); tools.push_back(t); } return tools; } K_PLUGIN_FACTORY_WITH_JSON(KateExternalToolsFactory, "externaltoolsplugin.json", registerPlugin();) KateExternalToolsPlugin::KateExternalToolsPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { // read built-in external tools from compiled-in resource file m_defaultTools = readDefaultTools(); // load config from disk reload(); } KateExternalToolsPlugin::~KateExternalToolsPlugin() { clearTools(); } QObject *KateExternalToolsPlugin::createView(KTextEditor::MainWindow *mainWindow) { KateExternalToolsPluginView *view = new KateExternalToolsPluginView(mainWindow, this); connect(this, &KateExternalToolsPlugin::externalToolsChanged, view, &KateExternalToolsPluginView::rebuildMenu); return view; } void KateExternalToolsPlugin::clearTools() { delete m_command; m_command = nullptr; m_commands.clear(); qDeleteAll(m_tools); m_tools.clear(); } void KateExternalToolsPlugin::reload() { clearTools(); KConfig _config(QStringLiteral("externaltools"), KConfig::NoGlobals, QStandardPaths::ApplicationsLocation); KConfigGroup config(&_config, "Global"); const int toolCount = config.readEntry("tools", 0); const bool firstStart = config.readEntry("firststart", true); if (!firstStart || toolCount > 0) { // read user config for (int i = 0; i < toolCount; ++i) { config = KConfigGroup(&_config, QStringLiteral("Tool %1").arg(i)); auto t = new KateExternalTool(); t->load(config); m_tools.push_back(t); } } else { // first start -> use system config for (const auto &tool : m_defaultTools) { m_tools.push_back(new KateExternalTool(tool)); } } // FIXME test for a command name first! for (auto tool : m_tools) { if (tool->hasexec && (!tool->cmdname.isEmpty())) { m_commands.push_back(tool->cmdname); } } if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) { m_command = new KateExternalToolsCommand(this); } Q_EMIT externalToolsChanged(); } QStringList KateExternalToolsPlugin::commands() const { return m_commands; } const KateExternalTool *KateExternalToolsPlugin::toolForCommand(const QString &cmd) const { for (auto tool : m_tools) { if (tool->cmdname == cmd) { return tool; } } return nullptr; } const QVector &KateExternalToolsPlugin::tools() const { return m_tools; } QVector KateExternalToolsPlugin::defaultTools() const { return m_defaultTools; } void KateExternalToolsPlugin::runTool(const KateExternalTool &tool, KTextEditor::View *view) { // expand the macros in command if any, // and construct a command with an absolute path auto mw = view->mainWindow(); // save documents if requested if (tool.saveMode == KateExternalTool::SaveMode::CurrentDocument) { // only save if modified, to avoid unnecessary recompiles if (view->document()->isModified()) { view->document()->save(); } } else if (tool.saveMode == KateExternalTool::SaveMode::AllDocuments) { const auto guiClients = mw->guiFactory()->clients(); for (KXMLGUIClient *client : guiClients) { if (QAction *a = client->actionCollection()->action(QStringLiteral("file_save_all"))) { a->trigger(); break; } } } // copy tool std::unique_ptr copy(new KateExternalTool(tool)); // clear previous toolview data auto pluginView = viewForMainWindow(mw); pluginView->clearToolView(); pluginView->addToolStatus(i18n("Running external tool: %1", copy->name)); pluginView->addToolStatus(i18n("- Executable: %1", copy->executable)); pluginView->addToolStatus(i18n("- Arguments : %1", copy->arguments)); pluginView->addToolStatus(i18n("- Input : %1", copy->input)); pluginView->addToolStatus(QString()); // expand macros auto editor = KTextEditor::Editor::instance(); editor->expandText(copy->executable, view, copy->executable); editor->expandText(copy->arguments, view, copy->arguments); editor->expandText(copy->workingDir, view, copy->workingDir); editor->expandText(copy->input, view, copy->input); // Allocate runner on heap such that it lives as long as the child // process is running and does not block the main thread. auto runner = new KateToolRunner(std::move(copy), view, this); // use QueuedConnection, since handleToolFinished deletes the runner connect(runner, &KateToolRunner::toolFinished, this, &KateExternalToolsPlugin::handleToolFinished, Qt::QueuedConnection); runner->run(); } void KateExternalToolsPlugin::handleToolFinished(KateToolRunner *runner, int exitCode, bool crashed) { auto view = runner->view(); if (view && !runner->outputData().isEmpty()) { switch (runner->tool()->outputMode) { case KateExternalTool::OutputMode::InsertAtCursor: { KTextEditor::Document::EditingTransaction transaction(view->document()); view->removeSelection(); view->insertText(runner->outputData()); break; } case KateExternalTool::OutputMode::ReplaceSelectedText: { KTextEditor::Document::EditingTransaction transaction(view->document()); view->removeSelectionText(); view->insertText(runner->outputData()); break; } case KateExternalTool::OutputMode::ReplaceCurrentDocument: { KTextEditor::Document::EditingTransaction transaction(view->document()); view->document()->clear(); view->insertText(runner->outputData()); break; } case KateExternalTool::OutputMode::AppendToCurrentDocument: { view->document()->insertText(view->document()->documentEnd(), runner->outputData()); break; } case KateExternalTool::OutputMode::InsertInNewDocument: { auto mainWindow = view->mainWindow(); auto newView = mainWindow->openUrl({}); newView->insertText(runner->outputData()); mainWindow->activateView(newView->document()); break; } case KateExternalTool::OutputMode::CopyToClipboard: { QGuiApplication::clipboard()->setText(runner->outputData()); break; } default: break; } } if (view && runner->tool()->reload) { // updates-enabled trick: avoid some flicker const bool wereUpdatesEnabled = view->updatesEnabled(); view->setUpdatesEnabled(false); view->document()->documentReload(); view->setUpdatesEnabled(wereUpdatesEnabled); } KateExternalToolsPluginView *pluginView = runner->view() ? viewForMainWindow(runner->view()->mainWindow()) : nullptr; if (pluginView) { bool hasOutputInPane = false; if (runner->tool()->outputMode == KateExternalTool::OutputMode::DisplayInPane) { pluginView->setOutputData(runner->outputData()); hasOutputInPane = !runner->outputData().isEmpty(); } if (!runner->errorData().isEmpty()) { pluginView->addToolStatus(i18n("Data written to stderr:")); pluginView->addToolStatus(runner->errorData()); } // empty line pluginView->addToolStatus(QString()); // print crash & exit code if (crashed) { pluginView->addToolStatus(i18n("Warning: External tool crashed.")); } pluginView->addToolStatus(i18n("Finished with exit code: %1", exitCode)); if (crashed || exitCode != 0) { pluginView->showToolView(ToolViewFocus::StatusTab); } else if (hasOutputInPane) { pluginView->showToolView(ToolViewFocus::OutputTab); } } delete runner; } int KateExternalToolsPlugin::configPages() const { return 1; } KTextEditor::ConfigPage *KateExternalToolsPlugin::configPage(int number, QWidget *parent) { if (number == 0) { return new KateExternalToolsConfigWidget(parent, this); } return nullptr; } void KateExternalToolsPlugin::registerPluginView(KateExternalToolsPluginView *view) { Q_ASSERT(!m_views.contains(view)); m_views.push_back(view); } void KateExternalToolsPlugin::unregisterPluginView(KateExternalToolsPluginView *view) { Q_ASSERT(m_views.contains(view)); m_views.removeAll(view); } KateExternalToolsPluginView *KateExternalToolsPlugin::viewForMainWindow(KTextEditor::MainWindow *mainWindow) const { for (auto view : m_views) { if (view->mainWindow() == mainWindow) { return view; } } return nullptr; } #include "externaltoolsplugin.moc" // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/externaltoolsplugin.h b/addons/externaltools/externaltoolsplugin.h index a6821c467..db8a15f6e 100644 --- a/addons/externaltools/externaltoolsplugin.h +++ b/addons/externaltools/externaltoolsplugin.h @@ -1,138 +1,138 @@ /* This file is part of the KDE project * * Copyright 2019 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KTEXTEDITOR_EXTERNALTOOLS_PLUGIN_H #define KTEXTEDITOR_EXTERNALTOOLS_PLUGIN_H -#include #include +#include namespace KTextEditor { class View; } class KateExternalToolsMenuAction; class KateExternalToolsPluginView; class KateExternalToolsCommand; class KateExternalTool; class KateToolRunner; class KateExternalToolsPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit KateExternalToolsPlugin(QObject *parent = nullptr, const QList & = QList()); virtual ~KateExternalToolsPlugin(); /** * Reimplemented to return the number of config pages, in this case 1. */ int configPages() const override; /** * Reimplemented to return the KateExternalToolConfigWidget for number==0. */ KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; /** * Reimplemented to instanciate a PluginView for each MainWindow. */ QObject *createView(KTextEditor::MainWindow *mainWindow) override; /** * Deletes all tools. */ void clearTools(); /** * Reloads the external tools from disk. */ void reload(); /** * Returns a list of KTextEDitor::Command strings. This is needed by * the KateExternalToolsCommand constructor to pass the list of commands to * the KTextEditor::Editor. */ QStringList commands() const; /** * Returns the KateExternalTool for a specific command line command 'cmd. */ const KateExternalTool *toolForCommand(const QString &cmd) const; /** * Returns a list of all existing external tools. */ const QVector &tools() const; /** * Returns the list of external tools that are shiped by default with * the external tools plugin. */ QVector defaultTools() const; /** * Executes the tool based on the view as current document. */ void runTool(const KateExternalTool &tool, KTextEditor::View *view); Q_SIGNALS: /** * This signal is emitted whenever the external tools change. * This is typically the case when external tools were modified, * added, or removed via the config page. */ void externalToolsChanged(); public: /** * Called by the KateExternalToolsPluginView to register itself. */ void registerPluginView(KateExternalToolsPluginView *view); /** * Called by the KateExternalToolsPluginView to unregister itself. */ void unregisterPluginView(KateExternalToolsPluginView *view); /** * Returns the KateExternalToolsPluginView for the given mainWindow. */ KateExternalToolsPluginView *viewForMainWindow(KTextEditor::MainWindow *mainWindow) const; private: QVector m_defaultTools; QVector m_views; QVector m_tools; QStringList m_commands; KateExternalToolsCommand *m_command = nullptr; private Q_SLOTS: /** * Called whenever an external tool is done. */ void handleToolFinished(KateToolRunner *runner, int exitCode, bool crashed); }; #endif // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsconfigwidget.cpp b/addons/externaltools/kateexternaltoolsconfigwidget.cpp index 43ec5147e..bf9d8583b 100644 --- a/addons/externaltools/kateexternaltoolsconfigwidget.cpp +++ b/addons/externaltools/kateexternaltoolsconfigwidget.cpp @@ -1,562 +1,562 @@ /* This file is part of the KDE project * * Copyright 2019 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateexternaltoolsconfigwidget.h" #include "externaltoolsplugin.h" #include "kateexternaltool.h" #include "katetoolrunner.h" +#include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr int ToolRole = Qt::UserRole + 1; /** * Helper function to create a QStandardItem that internally stores a pointer to a KateExternalTool. */ QStandardItem *newToolItem(const QIcon &icon, KateExternalTool *tool) { auto item = new QStandardItem(icon, i18n(tool->name.toUtf8().data())); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); item->setData(QVariant::fromValue(reinterpret_cast(tool)), ToolRole); return item; } /** * Helper function to return an internally stored KateExternalTool for a QStandardItem. * If a nullptr is returned, it means the QStandardItem is a category. */ KateExternalTool *toolForItem(QStandardItem *item) { return item ? reinterpret_cast(item->data(ToolRole).value()) : nullptr; } QIcon blankIcon() { QPixmap pm(KIconLoader::SizeSmall, KIconLoader::SizeSmall); pm.fill(); pm.setMask(pm.createHeuristicMask()); return QIcon(pm); } //! Helper that ensures that tool->actionName is unique static void makeActionNameUnique(KateExternalTool *tool, const std::vector &tools) { QString name = tool->actionName; int i = 1; while (true) { auto it = std::find_if(tools.cbegin(), tools.cend(), [tool, &name](const KateExternalTool *t) { return (t != tool) && (t->actionName == name); }); if (it == tools.cend()) { break; } name = tool->actionName + QString::number(i); ++i; } tool->actionName = name; } /** * Helper that ensures that the tool->cmdname is unique */ void makeEditorCommandUnique(KateExternalTool *tool, const std::vector &tools) { // empty command line name is OK if (tool->cmdname.isEmpty()) { return; } QString cmdname = tool->cmdname; int i = 1; while (true) { auto it = std::find_if(tools.cbegin(), tools.cend(), [tool, &cmdname](const KateExternalTool *t) { return (t != tool) && (t->cmdname == cmdname); }); if (it == tools.cend()) { break; } cmdname = tool->cmdname + QString::number(i); ++i; } tool->cmdname = cmdname; } static KateExternalTool defaultTool(const QString &actionName, const QVector &defaultTools) { auto it = std::find_if(defaultTools.cbegin(), defaultTools.cend(), [actionName](const KateExternalTool &defaultTool) { return actionName == defaultTool.actionName; }); return (it != defaultTools.cend()) ? *it : KateExternalTool(); } static bool isDefaultTool(KateExternalTool *tool, const QVector &defaultTools) { return tool && !defaultTool(tool->actionName, defaultTools).actionName.isEmpty(); } } // BEGIN KateExternalToolServiceEditor KateExternalToolServiceEditor::KateExternalToolServiceEditor(KateExternalTool *tool, KateExternalToolsPlugin *plugin, QWidget *parent) : QDialog(parent) , m_plugin(plugin) , m_tool(tool) { setWindowTitle(i18n("Edit External Tool")); setWindowIcon(QIcon::fromTheme(QStringLiteral("system-run"))); ui = new Ui::ToolDialog(); ui->setupUi(this); ui->btnIcon->setIconSize(KIconLoader::SizeSmall); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &KateExternalToolServiceEditor::slotOKClicked); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(ui->btnMimeType, &QToolButton::clicked, this, &KateExternalToolServiceEditor::showMTDlg); Q_ASSERT(m_tool != nullptr); ui->edtName->setText(i18n(m_tool->name.toUtf8().data())); if (!m_tool->icon.isEmpty()) ui->btnIcon->setIcon(m_tool->icon); ui->edtExecutable->setText(m_tool->executable); ui->edtArgs->setText(m_tool->arguments); ui->edtInput->setText(m_tool->input); ui->edtWorkingDir->setText(m_tool->workingDir); ui->edtMimeType->setText(m_tool->mimetypes.join(QStringLiteral("; "))); ui->cmbSave->setCurrentIndex(static_cast(m_tool->saveMode)); ui->chkReload->setChecked(m_tool->reload); ui->cmbOutput->setCurrentIndex(static_cast(m_tool->outputMode)); ui->edtCommand->setText(m_tool->cmdname); static const QRegularExpressionValidator cmdLineValidator(QRegularExpression(QStringLiteral("[\\w-]*"))); ui->edtCommand->setValidator(&cmdLineValidator); if (isDefaultTool(tool, m_plugin->defaultTools())) { ui->buttonBox->setStandardButtons(ui->buttonBox->standardButtons() | QDialogButtonBox::RestoreDefaults); ui->buttonBox->setToolTip(i18n("Revert tool to default settings")); connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, [this, tool]() { const auto t = defaultTool(tool->actionName, m_plugin->defaultTools()); ui->edtName->setText(i18n(t.name.toUtf8().data())); ui->btnIcon->setIcon(t.icon); ui->edtExecutable->setText(t.executable); ui->edtArgs->setText(t.arguments); ui->edtInput->setText(t.input); ui->edtWorkingDir->setText(t.workingDir); ui->edtMimeType->setText(t.mimetypes.join(QStringLiteral("; "))); ui->cmbSave->setCurrentIndex(static_cast(t.saveMode)); ui->chkReload->setChecked(t.reload); ui->cmbOutput->setCurrentIndex(static_cast(t.outputMode)); ui->edtCommand->setText(t.cmdname); }); } // add support for variable expansion KTextEditor::Editor::instance()->addVariableExpansion({ui->edtExecutable->lineEdit(), ui->edtArgs, ui->edtInput, ui->edtWorkingDir->lineEdit()}); } void KateExternalToolServiceEditor::slotOKClicked() { if (ui->edtName->text().isEmpty() || ui->edtExecutable->text().isEmpty()) { QMessageBox::information(this, i18n("External Tool"), i18n("You must specify at least a name and an executable")); return; } accept(); } void KateExternalToolServiceEditor::showMTDlg() { QString text = i18n("Select the MimeTypes for which to enable this tool."); QStringList list = ui->edtMimeType->text().split(QRegularExpression(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts); KMimeTypeChooserDialog d(i18n("Select Mime Types"), text, list, QStringLiteral("text"), this); if (d.exec() == QDialog::Accepted) { ui->edtMimeType->setText(d.chooser()->mimeTypes().join(QStringLiteral(";"))); } } // END KateExternalToolServiceEditor static std::vector childItems(const QStandardItem *item) { // collect all KateExternalTool items std::vector children; for (int i = 0; i < item->rowCount(); ++i) { children.push_back(item->child(i)); } return children; } static std::vector collectTools(const QStandardItemModel &model) { std::vector tools; for (auto categoryItem : childItems(model.invisibleRootItem())) { for (auto child : childItems(categoryItem)) { auto tool = toolForItem(child); Q_ASSERT(tool != nullptr); tools.push_back(tool); } } return tools; } // BEGIN KateExternalToolsConfigWidget KateExternalToolsConfigWidget::KateExternalToolsConfigWidget(QWidget *parent, KateExternalToolsPlugin *plugin) : KTextEditor::ConfigPage(parent) , m_plugin(plugin) { setupUi(this); layout()->setContentsMargins(0, 0, 0, 0); lbTools->setModel(&m_toolsModel); lbTools->setSelectionMode(QAbstractItemView::SingleSelection); lbTools->setDragEnabled(true); lbTools->setAcceptDrops(true); lbTools->setDefaultDropAction(Qt::MoveAction); lbTools->setDropIndicatorShown(true); lbTools->setDragDropOverwriteMode(false); lbTools->setDragDropMode(QAbstractItemView::InternalMove); // Add... button popup menu auto addMenu = new QMenu(); auto addToolAction = addMenu->addAction(i18n("Add Tool...")); auto addDefaultsMenu = addMenu->addMenu(i18n("Add Tool from Defaults")); addMenu->addSeparator(); auto addCategoryAction = addMenu->addAction(i18n("Add Category")); btnAdd->setMenu(addMenu); connect(addDefaultsMenu, &QMenu::aboutToShow, [this, addDefaultsMenu]() { lazyInitDefaultsMenu(addDefaultsMenu); }); connect(addCategoryAction, &QAction::triggered, this, &KateExternalToolsConfigWidget::slotAddCategory); connect(addToolAction, &QAction::triggered, this, &KateExternalToolsConfigWidget::slotAddTool); connect(btnRemove, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotRemove); connect(btnEdit, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotEdit); connect(lbTools->selectionModel(), &QItemSelectionModel::currentChanged, [this]() { slotSelectionChanged(); }); connect(lbTools, &QTreeView::doubleClicked, this, &KateExternalToolsConfigWidget::slotEdit); m_config = new KConfig(QStringLiteral("externaltools"), KConfig::NoGlobals, QStandardPaths::ApplicationsLocation); // reset triggers a reload of the existing tools reset(); slotSelectionChanged(); connect(&m_toolsModel, &QStandardItemModel::itemChanged, [this]() { m_changed = true; Q_EMIT changed(); }); } KateExternalToolsConfigWidget::~KateExternalToolsConfigWidget() { clearTools(); delete m_config; } QString KateExternalToolsConfigWidget::name() const { return i18n("External Tools"); } QString KateExternalToolsConfigWidget::fullName() const { return i18n("External Tools"); } QIcon KateExternalToolsConfigWidget::icon() const { return QIcon::fromTheme(QStringLiteral("system-run")); } void KateExternalToolsConfigWidget::reset() { clearTools(); m_toolsModel.invisibleRootItem()->setFlags(Qt::NoItemFlags); // the "Uncategorized" category always exists m_noCategory = addCategory(i18n("Uncategorized")); m_noCategory->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled); // create other tools and categories const auto tools = m_plugin->tools(); for (auto tool : tools) { auto clone = new KateExternalTool(*tool); auto item = newToolItem(clone->icon.isEmpty() ? blankIcon() : QIcon::fromTheme(clone->icon), clone); auto category = clone->category.isEmpty() ? m_noCategory : addCategory(clone->category); category->appendRow(item); } lbTools->expandAll(); m_changed = false; } void KateExternalToolsConfigWidget::apply() { if (!m_changed) return; m_changed = false; // collect all KateExternalTool items std::vector tools; for (auto categoryItem : childItems(m_toolsModel.invisibleRootItem())) { const QString category = (categoryItem == m_noCategory) ? QString() : categoryItem->text(); for (auto child : childItems(categoryItem)) { auto tool = toolForItem(child); Q_ASSERT(tool != nullptr); // at this point, we have to overwrite the category, since it may have changed (and we never tracked this) tool->category = category; tools.push_back(tool); } } // write tool configuration to disk m_config->group("Global").writeEntry("firststart", false); m_config->group("Global").writeEntry("tools", static_cast(tools.size())); for (size_t i = 0; i < tools.size(); i++) { const QString section = QStringLiteral("Tool ") + QString::number(i); KConfigGroup cg(m_config, section); tools[i]->save(cg); } m_config->sync(); m_plugin->reload(); } void KateExternalToolsConfigWidget::slotSelectionChanged() { // update button state auto item = m_toolsModel.itemFromIndex(lbTools->currentIndex()); const bool isToolItem = toolForItem(item) != nullptr; const bool isCategory = item && !isToolItem; btnEdit->setEnabled(isToolItem || isCategory); btnRemove->setEnabled(isToolItem); } bool KateExternalToolsConfigWidget::editTool(KateExternalTool *tool) { bool changed = false; KateExternalToolServiceEditor editor(tool, m_plugin, this); editor.resize(m_config->group("Editor").readEntry("Size", QSize())); if (editor.exec() == QDialog::Accepted) { tool->name = editor.ui->edtName->text(); tool->icon = editor.ui->btnIcon->icon(); tool->executable = editor.ui->edtExecutable->text(); tool->arguments = editor.ui->edtArgs->text(); tool->input = editor.ui->edtInput->toPlainText(); tool->workingDir = editor.ui->edtWorkingDir->text(); tool->mimetypes = editor.ui->edtMimeType->text().split(QRegularExpression(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts); tool->saveMode = static_cast(editor.ui->cmbSave->currentIndex()); tool->reload = editor.ui->chkReload->isChecked(); tool->outputMode = static_cast(editor.ui->cmbOutput->currentIndex()); tool->cmdname = editor.ui->edtCommand->text(); // sticky action collection name, never changes again, so that shortcuts stay if (tool->actionName.isEmpty()) { tool->actionName = QStringLiteral("externaltool_") + QString(tool->name).remove(QRegularExpression(QStringLiteral("\\W+"))); } const auto tools = collectTools(m_toolsModel); makeActionNameUnique(tool, tools); makeEditorCommandUnique(tool, tools); changed = true; } m_config->group("Editor").writeEntry("Size", editor.size()); m_config->sync(); return changed; } void KateExternalToolsConfigWidget::lazyInitDefaultsMenu(QMenu *defaultsMenu) { if (!defaultsMenu->isEmpty()) { return; } // create tool actions std::map categories; // first add categorized actions, such that the submenus appear at the top int defaultToolsIndex = 0; for (const auto &tool : m_plugin->defaultTools()) { const QString category = tool.category.isEmpty() ? i18n("Uncategorized") : i18n(tool.category.toUtf8().data()); auto categoryMenu = categories[category]; if (!categoryMenu) { categoryMenu = new QMenu(category, this); categories[category] = categoryMenu; defaultsMenu->addMenu(categoryMenu); } auto a = categoryMenu->addAction(QIcon::fromTheme(tool.icon), i18n(tool.name.toUtf8().data())); a->setData(defaultToolsIndex); connect(a, &QAction::triggered, [this, a]() { slotAddDefaultTool(a->data().toInt()); }); ++defaultToolsIndex; } } void KateExternalToolsConfigWidget::slotAddDefaultTool(int defaultToolsIndex) { const auto &defaultTools = m_plugin->defaultTools(); if (defaultToolsIndex < 0 || defaultToolsIndex > defaultTools.size()) { return; } addNewTool(new KateExternalTool(defaultTools[defaultToolsIndex])); } void KateExternalToolsConfigWidget::addNewTool(KateExternalTool *tool) { const auto tools = collectTools(m_toolsModel); makeActionNameUnique(tool, tools); makeEditorCommandUnique(tool, tools); auto item = newToolItem(tool->icon.isEmpty() ? blankIcon() : QIcon::fromTheme(tool->icon), tool); auto category = addCategory(i18n(tool->category.toUtf8().data())); category->appendRow(item); lbTools->setCurrentIndex(item->index()); Q_EMIT changed(); m_changed = true; } QStandardItem *KateExternalToolsConfigWidget::addCategory(const QString &translatedCategory) { if (translatedCategory.isEmpty()) { return m_noCategory; } // search for existing category auto items = m_toolsModel.findItems(translatedCategory); if (!items.empty()) { return items.front(); } // ...otherwise, create it auto item = new QStandardItem(translatedCategory); // for now, categories are not movable, otherwise, the use can move a // category into another category, which is not supported right now item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled | Qt::ItemIsEditable); m_toolsModel.appendRow(item); return item; } QStandardItem *KateExternalToolsConfigWidget::currentCategory() const { auto index = lbTools->currentIndex(); if (!index.isValid()) { return m_noCategory; } auto item = m_toolsModel.itemFromIndex(index); auto tool = toolForItem(item); if (tool) { // the parent of a ToolItem is always a category return item->parent(); } // item is no ToolItem, so we must have a category at hand return item; } void KateExternalToolsConfigWidget::clearTools() { // collect all KateExternalTool items and delete them, since they are copies std::vector tools = collectTools(m_toolsModel); qDeleteAll(tools); tools.clear(); m_toolsModel.clear(); } void KateExternalToolsConfigWidget::slotAddCategory() { // find unique name QString name = i18n("New Category"); int i = 1; while (!m_toolsModel.findItems(name, Qt::MatchFixedString).isEmpty()) { name = (i18n("New Category %1", i++)); } // add category and switch to edit mode auto item = addCategory(name); lbTools->edit(item->index()); } void KateExternalToolsConfigWidget::slotAddTool() { auto t = new KateExternalTool(); if (editTool(t)) { addNewTool(t); } else { delete t; } } void KateExternalToolsConfigWidget::slotRemove() { auto item = m_toolsModel.itemFromIndex(lbTools->currentIndex()); auto tool = toolForItem(item); if (tool) { item->parent()->removeRow(item->index().row()); delete tool; Q_EMIT changed(); m_changed = true; } } void KateExternalToolsConfigWidget::slotEdit() { auto item = m_toolsModel.itemFromIndex(lbTools->currentIndex()); auto tool = toolForItem(item); if (!tool) { if (item) { lbTools->edit(item->index()); } return; } // show the item in an editor if (editTool(tool)) { // renew the icon and name item->setText(tool->name); item->setIcon(tool->icon.isEmpty() ? blankIcon() : QIcon::fromTheme(tool->icon)); Q_EMIT changed(); m_changed = true; } } // END KateExternalToolsConfigWidget // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsconfigwidget.h b/addons/externaltools/kateexternaltoolsconfigwidget.h index c7a4e36d3..244f2e4a7 100644 --- a/addons/externaltools/kateexternaltoolsconfigwidget.h +++ b/addons/externaltools/kateexternaltoolsconfigwidget.h @@ -1,133 +1,133 @@ /* This file is part of the KDE project * * Copyright 2019 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KTEXTEDITOR_EXTERNALTOOLS_CONFIGWIDGET_H #define KTEXTEDITOR_EXTERNALTOOLS_CONFIGWIDGET_H #include "ui_configwidget.h" #include "ui_tooldialog.h" #include #include #include #include #include -#include #include +#include class KConfig; class KateExternalToolsPlugin; class KateExternalTool; /** * The config widget. * The config widget allows the user to view a list of services of the type * "Kate/ExternalTool" and add, remove or edit them. */ class KateExternalToolsConfigWidget : public KTextEditor::ConfigPage, public Ui::ExternalToolsConfigWidget { Q_OBJECT public: KateExternalToolsConfigWidget(QWidget *parent, KateExternalToolsPlugin *plugin); virtual ~KateExternalToolsConfigWidget(); QString name() const override; QString fullName() const override; QIcon icon() const override; public Q_SLOTS: void apply() override; void reset() override; void defaults() override { reset(); } private Q_SLOTS: void addNewTool(KateExternalTool *tool); void lazyInitDefaultsMenu(QMenu *defaultsMenu); void slotAddDefaultTool(int defaultToolsIndex); void slotAddCategory(); void slotAddTool(); void slotEdit(); void slotRemove(); void slotSelectionChanged(); /** * Helper to open the ToolDialog. * Returns true, if the user clicked OK. */ bool editTool(KateExternalTool *tool); /** * Creates a new category or returns existing one. */ QStandardItem *addCategory(const QString &translatedCategory); /** * Returns the currently active category. The returned pointer is always valid. */ QStandardItem *currentCategory() const; /** * Clears the tools model. */ void clearTools(); private: KConfig *m_config = nullptr; bool m_changed = false; KateExternalToolsPlugin *m_plugin; QStandardItemModel m_toolsModel; QStandardItem *m_noCategory = nullptr; }; /** * A Dialog to edit a single KateExternalTool object */ class KateExternalToolServiceEditor : public QDialog { Q_OBJECT public: explicit KateExternalToolServiceEditor(KateExternalTool *tool, KateExternalToolsPlugin *plugin, QWidget *parent = nullptr); private Q_SLOTS: /** * Run when the OK button is clicked, to ensure critical values are provided. */ void slotOKClicked(); /** * show a mimetype chooser dialog */ void showMTDlg(); public: Ui::ToolDialog *ui; private: KateExternalToolsPlugin *m_plugin; KateExternalTool *m_tool; }; #endif // KTEXTEDITOR_EXTERNALTOOLS_CONFIGWIDGET_H // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsview.cpp b/addons/externaltools/kateexternaltoolsview.cpp index 6832b80dd..768760dee 100644 --- a/addons/externaltools/kateexternaltoolsview.cpp +++ b/addons/externaltools/kateexternaltoolsview.cpp @@ -1,271 +1,271 @@ /* This file is part of the KDE project * * Copyright 2019 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateexternaltoolsview.h" #include "externaltoolsplugin.h" #include "kateexternaltool.h" #include "ui_toolview.h" -#include #include +#include #include #include #include #include #include +#include #include -#include -#include #include -#include +#include +#include -#include -#include #include +#include #include +#include #include #include // BEGIN KateExternalToolsMenuAction KateExternalToolsMenuAction::KateExternalToolsMenuAction(const QString &text, KActionCollection *collection, KateExternalToolsPlugin *plugin, KTextEditor::MainWindow *mw) : KActionMenu(text, mw) , m_plugin(plugin) , m_mainwindow(mw) , m_actionCollection(collection) { reload(); // track active view to adapt enabled tool actions connect(mw, &KTextEditor::MainWindow::viewChanged, this, &KateExternalToolsMenuAction::slotViewChanged); } KateExternalToolsMenuAction::~KateExternalToolsMenuAction() = default; void KateExternalToolsMenuAction::reload() { // clear action collection bool needs_readd = (m_actionCollection->takeAction(this) != nullptr); m_actionCollection->clear(); if (needs_readd) m_actionCollection->addAction(QStringLiteral("tools_external"), this); menu()->clear(); // create tool actions std::map categories; std::vector uncategorizedActions; // first add categorized actions, such that the submenus appear at the top for (auto tool : m_plugin->tools()) { if (tool->hasexec) { auto a = new QAction(tool->name, this); a->setIcon(QIcon::fromTheme(tool->icon)); a->setData(QVariant::fromValue(tool)); connect(a, &QAction::triggered, [this, a]() { m_plugin->runTool(*a->data().value(), m_mainwindow->activeView()); }); m_actionCollection->addAction(i18n(tool->actionName.toUtf8().data()), a); if (!tool->category.isEmpty()) { auto categoryMenu = categories[tool->category]; if (!categoryMenu) { categoryMenu = new KActionMenu(i18n(tool->category.toUtf8().data()), this); categories[tool->category] = categoryMenu; addAction(categoryMenu); } categoryMenu->addAction(a); } else { uncategorizedActions.push_back(a); } } } // now add uncategorized actions below for (auto uncategorizedAction : uncategorizedActions) { addAction(uncategorizedAction); } addSeparator(); auto cfgAction = new QAction(i18n("Configure..."), this); addAction(cfgAction); connect(cfgAction, &QAction::triggered, this, &KateExternalToolsMenuAction::showConfigPage, Qt::QueuedConnection); // load shortcuts KSharedConfig::Ptr pConfig = KSharedConfig::openConfig(QStringLiteral("externaltools"), KConfig::NoGlobals, QStandardPaths::ApplicationsLocation); KConfigGroup config(pConfig, "Global"); config = KConfigGroup(pConfig, "Shortcuts"); m_actionCollection->readSettings(&config); slotViewChanged(m_mainwindow->activeView()); } void KateExternalToolsMenuAction::slotViewChanged(KTextEditor::View *view) { // no active view, oh oh if (!view) { return; } // try to enable/disable to match current mime type const QString mimeType = view->document()->mimeType(); const auto actions = m_actionCollection->actions(); for (QAction *action : actions) { if (action && action->data().value()) { auto tool = action->data().value(); action->setEnabled(tool->matchesMimetype(mimeType)); } } } void KateExternalToolsMenuAction::showConfigPage() { m_mainwindow->showPluginConfigPage(m_plugin, 0); } // END KateExternalToolsMenuAction // BEGIN KateExternalToolsPluginView KateExternalToolsPluginView::KateExternalToolsPluginView(KTextEditor::MainWindow *mainWindow, KateExternalToolsPlugin *plugin) : QObject(mainWindow) , m_plugin(plugin) , m_mainWindow(mainWindow) , m_outputDoc(new QTextDocument(this)) , m_statusDoc(new QTextDocument(this)) { m_plugin->registerPluginView(this); KXMLGUIClient::setComponentName(QLatin1String("externaltools"), i18n("External Tools")); setXMLFile(QLatin1String("ui.rc")); if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) { m_externalToolsMenu = new KateExternalToolsMenuAction(i18n("External Tools"), actionCollection(), plugin, mainWindow); actionCollection()->addAction(QStringLiteral("tools_external"), m_externalToolsMenu); m_externalToolsMenu->setWhatsThis(i18n("Launch external helper applications")); } mainWindow->guiFactory()->addClient(this); // ESC should close & hide ToolView connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, [this](QEvent *event) { auto keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape && keyEvent->modifiers() == Qt::NoModifier) { deleteToolView(); } }); } KateExternalToolsPluginView::~KateExternalToolsPluginView() { m_plugin->unregisterPluginView(this); m_mainWindow->guiFactory()->removeClient(this); deleteToolView(); delete m_externalToolsMenu; m_externalToolsMenu = nullptr; } void KateExternalToolsPluginView::rebuildMenu() { if (m_externalToolsMenu) { KXMLGUIFactory *f = factory(); f->removeClient(this); reloadXML(); m_externalToolsMenu->reload(); f->addClient(this); } } KTextEditor::MainWindow *KateExternalToolsPluginView::mainWindow() const { return m_mainWindow; } void KateExternalToolsPluginView::createToolView() { if (!m_toolView) { m_toolView = mainWindow()->createToolView(m_plugin, QStringLiteral("ktexteditor_plugin_externaltools"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("system-run")), i18n("External Tools")); m_ui = new Ui::ToolView(); m_ui->setupUi(m_toolView); // set the documents m_ui->teOutput->setDocument(m_outputDoc); m_ui->teStatus->setDocument(m_statusDoc); // use fixed font for displaying status and output text const auto fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); m_ui->teOutput->setFont(fixedFont); m_ui->teStatus->setFont(fixedFont); // close button to delete tool view auto btnClose = new QToolButton(); btnClose->setAutoRaise(true); btnClose->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); connect(btnClose, &QToolButton::clicked, this, &KateExternalToolsPluginView::deleteToolView); m_ui->tabWidget->setCornerWidget(btnClose); } } void KateExternalToolsPluginView::showToolView(ToolViewFocus tab) { createToolView(); if (tab == ToolViewFocus::OutputTab) { m_ui->tabWidget->setCurrentWidget(m_ui->tabOutput); } else { m_ui->tabWidget->setCurrentWidget(m_ui->tabStatus); } mainWindow()->showToolView(m_toolView); } void KateExternalToolsPluginView::clearToolView() { m_outputDoc->clear(); m_statusDoc->clear(); } void KateExternalToolsPluginView::addToolStatus(const QString &message) { QTextCursor cursor(m_statusDoc); cursor.movePosition(QTextCursor::End); cursor.insertText(message); cursor.insertText(QStringLiteral("\n")); } void KateExternalToolsPluginView::setOutputData(const QString &data) { QTextCursor cursor(m_outputDoc); cursor.movePosition(QTextCursor::End); cursor.insertText(data); } void KateExternalToolsPluginView::deleteToolView() { if (m_toolView) { delete m_ui; m_ui = nullptr; delete m_toolView; m_toolView = nullptr; } } // END KateExternalToolsPluginView // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/katetoolrunner.cpp b/addons/externaltools/katetoolrunner.cpp index 419c68619..32483720c 100644 --- a/addons/externaltools/katetoolrunner.cpp +++ b/addons/externaltools/katetoolrunner.cpp @@ -1,97 +1,97 @@ /* This file is part of the KDE project * * Copyright 2019 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "katetoolrunner.h" #include "kateexternaltool.h" +#include #include #include -#include KateToolRunner::KateToolRunner(std::unique_ptr tool, KTextEditor::View *view, QObject *parent) : QObject(parent) , m_view(view) , m_tool(std::move(tool)) , m_process(new QProcess()) { m_process->setProcessChannelMode(QProcess::SeparateChannels); } KateToolRunner::~KateToolRunner() { } KTextEditor::View *KateToolRunner::view() const { return m_view; } KateExternalTool *KateToolRunner::tool() const { return m_tool.get(); } void KateToolRunner::run() { if (!m_tool->workingDir.isEmpty()) { m_process->setWorkingDirectory(m_tool->workingDir); } else if (m_view) { // if nothing is set, use the current document's directory const auto url = m_view->document()->url(); if (url.isValid()) { const QString path = m_view->document()->url().toString(QUrl::RemoveScheme | QUrl::RemoveFilename); m_process->setWorkingDirectory(path); } } QObject::connect(m_process.get(), &QProcess::readyReadStandardOutput, [this]() { m_stdout += m_process->readAllStandardOutput(); }); QObject::connect(m_process.get(), &QProcess::readyReadStandardError, [this]() { m_stderr += m_process->readAllStandardError(); }); QObject::connect( m_process.get(), static_cast(&QProcess::finished), [this](int exitCode, QProcess::ExitStatus exitStatus) { Q_EMIT toolFinished(this, exitCode, exitStatus == QProcess::CrashExit); }); // Write stdin to process, if applicable, then close write channel QObject::connect(m_process.get(), &QProcess::started, [this]() { if (!m_tool->input.isEmpty()) { m_process->write(m_tool->input.toLocal8Bit()); } m_process->closeWriteChannel(); }); const QStringList args = KShell::splitArgs(m_tool->arguments); m_process->start(m_tool->executable, args); } void KateToolRunner::waitForFinished() { m_process->waitForFinished(); } QString KateToolRunner::outputData() const { return QString::fromLocal8Bit(m_stdout); } QString KateToolRunner::errorData() const { return QString::fromLocal8Bit(m_stderr); } // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/katetoolrunner.h b/addons/externaltools/katetoolrunner.h index c3feb0675..229d9e30a 100644 --- a/addons/externaltools/katetoolrunner.h +++ b/addons/externaltools/katetoolrunner.h @@ -1,115 +1,115 @@ /* This file is part of the KDE project * * Copyright 2019 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KTEXTEDITOR_EXTERNALTOOLRUNNER_H #define KTEXTEDITOR_EXTERNALTOOLRUNNER_H #include #include +#include #include #include -#include #include class KateExternalTool; class QProcess; namespace KTextEditor { class View; } /** * Helper class to run a KateExternalTool. */ class KateToolRunner : public QObject { Q_OBJECT public: /** * Constructor that will run @p tool in the run() method. * The @p view can later be retrieved again with view() to process the data when the tool is finished. */ KateToolRunner(std::unique_ptr tool, KTextEditor::View *view, QObject *parent = nullptr); KateToolRunner(const KateToolRunner &) = delete; void operator=(const KateToolRunner &) = delete; ~KateToolRunner(); /** * Returns the view that was active when running the tool. * @warning May be a nullptr, since the view could have been closed in the meantime. */ KTextEditor::View *view() const; /** * Returns the tool that was passed in the constructor. */ KateExternalTool *tool() const; /** * Starts a child process that executes the tool. */ void run(); /** * Blocking call that waits until the tool is finised. * Used internally for unit testing. */ void waitForFinished(); /** * Returns the data that was collected on stdout. */ QString outputData() const; /** * Returns the data that was collected on stderr. */ QString errorData() const; Q_SIGNALS: /** * This signal is emitted when the tool is finished. */ void toolFinished(KateToolRunner *runner, int exitCode, bool crashed); private: //! Use QPointer here, since the View may be closed in the meantime. QPointer m_view; //! We are the owner of the tool (it was copied) std::unique_ptr m_tool; //! Child process that runs the tool std::unique_ptr m_process; //! Collect stdout QByteArray m_stdout; //! Collect stderr QByteArray m_stderr; }; #endif // KTEXTEDITOR_EXTERNALTOOLRUNNER_H // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/filebrowser/katebookmarkhandler.cpp b/addons/filebrowser/katebookmarkhandler.cpp index 176046f2d..9b1a401ea 100644 --- a/addons/filebrowser/katebookmarkhandler.cpp +++ b/addons/filebrowser/katebookmarkhandler.cpp @@ -1,70 +1,70 @@ /* This file is part of the KDE project Copyright (C) xxxx KFile Authors Copyright (C) 2002 Anders Lund Copyright (C) 2009 Dominik Haumann Copyright (C) 2007 Mirko Stocker 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. */ #include "katebookmarkhandler.h" #include "katefilebrowser.h" #include -#include #include +#include KateBookmarkHandler::KateBookmarkHandler(KateFileBrowser *parent, QMenu *kpopupmenu) : QObject(parent) , KBookmarkOwner() , mParent(parent) , m_menu(kpopupmenu) { setObjectName(QStringLiteral("KateBookmarkHandler")); if (!m_menu) m_menu = new QMenu(parent); QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kate/fsbookmarks.xml")); if (file.isEmpty()) file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kate/fsbookmarks.xml"); KBookmarkManager *manager = KBookmarkManager::managerForFile(file, QStringLiteral("kate")); manager->setUpdate(true); m_bookmarkMenu = new KBookmarkMenu(manager, this, m_menu, parent->actionCollection()); } KateBookmarkHandler::~KateBookmarkHandler() { delete m_bookmarkMenu; } QUrl KateBookmarkHandler::currentUrl() const { return mParent->dirOperator()->url(); } QString KateBookmarkHandler::currentTitle() const { return currentUrl().url(); } void KateBookmarkHandler::openBookmark(const KBookmark &bm, Qt::MouseButtons, Qt::KeyboardModifiers) { emit openUrl(bm.url().url()); } // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/filebrowser/katefilebrowserplugin.cpp b/addons/filebrowser/katefilebrowserplugin.cpp index 90ca44fc2..320d26176 100644 --- a/addons/filebrowser/katefilebrowserplugin.cpp +++ b/addons/filebrowser/katefilebrowserplugin.cpp @@ -1,112 +1,112 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund Copyright (C) 2007 Mirko Stocker Copyright (C) 2009 Dominik Haumann 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. */ // BEGIN Includes #include "katefilebrowserplugin.h" -#include "katefilebrowserconfig.h" #include "katefilebrowser.h" +#include "katefilebrowserconfig.h" #include #include -#include #include +#include // END Includes K_PLUGIN_FACTORY_WITH_JSON(KateFileBrowserPluginFactory, "katefilebrowserplugin.json", registerPlugin();) // BEGIN KateFileBrowserPlugin KateFileBrowserPlugin::KateFileBrowserPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { } QObject *KateFileBrowserPlugin::createView(KTextEditor::MainWindow *mainWindow) { KateFileBrowserPluginView *view = new KateFileBrowserPluginView(this, mainWindow); connect(view, &KateFileBrowserPluginView::destroyed, this, &KateFileBrowserPlugin::viewDestroyed); m_views.append(view); return view; } void KateFileBrowserPlugin::viewDestroyed(QObject *view) { // do not access the view pointer, since it is partially destroyed already m_views.removeAll(static_cast(view)); } int KateFileBrowserPlugin::configPages() const { return 1; } KTextEditor::ConfigPage *KateFileBrowserPlugin::configPage(int number, QWidget *parent) { if (number != 0) return nullptr; return new KateFileBrowserConfigPage(parent, m_views[0]->m_fileBrowser); } // END KateFileBrowserPlugin // BEGIN KateFileBrowserPluginView KateFileBrowserPluginView::KateFileBrowserPluginView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWindow) : QObject(mainWindow) , m_toolView(mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katefileselectorplugin"), KTextEditor::MainWindow::Left, QIcon::fromTheme(QStringLiteral("document-open")), i18n("Filesystem Browser"))) , m_fileBrowser(new KateFileBrowser(mainWindow, m_toolView)) , m_mainWindow(mainWindow) { m_toolView->installEventFilter(this); } KateFileBrowserPluginView::~KateFileBrowserPluginView() { // cleanup, kill toolview + console delete m_fileBrowser->parentWidget(); } void KateFileBrowserPluginView::readSessionConfig(const KConfigGroup &config) { m_fileBrowser->readSessionConfig(config); } void KateFileBrowserPluginView::writeSessionConfig(KConfigGroup &config) { m_fileBrowser->writeSessionConfig(config); } bool KateFileBrowserPluginView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_mainWindow->hideToolView(m_toolView); event->accept(); return true; } } return QObject::eventFilter(obj, event); } // ENDKateFileBrowserPluginView #include "katefilebrowserplugin.moc" // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/filebrowser/katefilebrowserplugin.h b/addons/filebrowser/katefilebrowserplugin.h index 5465a491a..7db2862d3 100644 --- a/addons/filebrowser/katefilebrowserplugin.h +++ b/addons/filebrowser/katefilebrowserplugin.h @@ -1,87 +1,87 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund Copyright (C) 2007 Mirko Stocker Copyright (C) 2009 Dominik Haumann 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. */ #ifndef KATE_FILEBROWSER_PLUGIN_H #define KATE_FILEBROWSER_PLUGIN_H +#include +#include #include -#include #include -#include -#include +#include class KateFileBrowser; class KateFileBrowserPluginView; class KateFileBrowserPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit KateFileBrowserPlugin(QObject *parent = nullptr, const QList & = QList()); ~KateFileBrowserPlugin() override { } QObject *createView(KTextEditor::MainWindow *mainWindow) override; int configPages() const override; KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; public Q_SLOTS: void viewDestroyed(QObject *view); private: QList m_views; }; class KateFileBrowserPluginView : public QObject, public KTextEditor::SessionConfigInterface { Q_OBJECT Q_INTERFACES(KTextEditor::SessionConfigInterface) public: /** * Constructor. */ KateFileBrowserPluginView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWindow); /** * Virtual destructor. */ ~KateFileBrowserPluginView() override; void readSessionConfig(const KConfigGroup &config) override; void writeSessionConfig(KConfigGroup &config) override; private: bool eventFilter(QObject *, QEvent *) override; QWidget *m_toolView; KateFileBrowser *m_fileBrowser; KTextEditor::MainWindow *m_mainWindow; friend class KateFileBrowserPlugin; }; #endif // KATE_FILEBROWSER_PLUGIN_H // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/filetree/katefiletree.cpp b/addons/filetree/katefiletree.cpp index 769095806..d9d18b280 100644 --- a/addons/filetree/katefiletree.cpp +++ b/addons/filetree/katefiletree.cpp @@ -1,709 +1,709 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom Copyright (C) 2014 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ // BEGIN Includes #include "katefiletree.h" +#include "katefiletreedebug.h" #include "katefiletreemodel.h" #include "katefiletreeproxymodel.h" -#include "katefiletreedebug.h" +#include #include #include -#include +#include +#include +#include +#include +#include #include #include #include -#include -#include #include -#include -#include -#include -#include +#include #include #include -#include -#include +#include #include #include -#include +#include +#include // END Includes // BEGIN KateFileTree KateFileTree::KateFileTree(QWidget *parent) : QTreeView(parent) { setAcceptDrops(false); setIndentation(12); setAllColumnsShowFocus(true); setFocusPolicy(Qt::NoFocus); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragOnly); // handle activated (e.g. for pressing enter) + clicked (to avoid to need to do double-click e.g. on Windows) connect(this, &KateFileTree::activated, this, &KateFileTree::mouseClicked); connect(this, &KateFileTree::clicked, this, &KateFileTree::mouseClicked); m_filelistReloadDocument = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reloa&d"), this); connect(m_filelistReloadDocument, &QAction::triggered, this, &KateFileTree::slotDocumentReload); m_filelistReloadDocument->setWhatsThis(i18n("Reload selected document(s) from disk.")); m_filelistCloseDocument = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close"), this); connect(m_filelistCloseDocument, &QAction::triggered, this, &KateFileTree::slotDocumentClose); m_filelistCloseDocument->setWhatsThis(i18n("Close the current document.")); m_filelistExpandRecursive = new QAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Expand recursively"), this); connect(m_filelistExpandRecursive, &QAction::triggered, this, &KateFileTree::slotExpandRecursive); m_filelistExpandRecursive->setWhatsThis(i18n("Expand the file list sub tree recursively.")); m_filelistCollapseRecursive = new QAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Collapse recursively"), this); connect(m_filelistCollapseRecursive, &QAction::triggered, this, &KateFileTree::slotCollapseRecursive); m_filelistCollapseRecursive->setWhatsThis(i18n("Collapse the file list sub tree recursively.")); m_filelistCloseOtherDocument = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close Other"), this); connect(m_filelistCloseOtherDocument, &QAction::triggered, this, &KateFileTree::slotDocumentCloseOther); m_filelistCloseOtherDocument->setWhatsThis(i18n("Close other documents in this folder.")); m_filelistOpenContainingFolder = new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18nc("@action:inmenu", "Open Containing Folder"), this); connect(m_filelistOpenContainingFolder, &QAction::triggered, this, &KateFileTree::slotOpenContainingFolder); m_filelistOpenContainingFolder->setWhatsThis(i18n("Open the folder this file is located in.")); m_filelistCopyFilename = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu", "Copy File Path"), this); connect(m_filelistCopyFilename, &QAction::triggered, this, &KateFileTree::slotCopyFilename); m_filelistCopyFilename->setWhatsThis(i18n("Copy path and filename to the clipboard.")); m_filelistRenameFile = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18nc("@action:inmenu", "Rename..."), this); connect(m_filelistRenameFile, &QAction::triggered, this, &KateFileTree::slotRenameFile); m_filelistRenameFile->setWhatsThis(i18n("Rename the selected file.")); m_filelistPrintDocument = KStandardAction::print(this, SLOT(slotPrintDocument()), this); m_filelistPrintDocument->setWhatsThis(i18n("Print selected document.")); m_filelistPrintDocumentPreview = KStandardAction::printPreview(this, SLOT(slotPrintDocumentPreview()), this); m_filelistPrintDocumentPreview->setWhatsThis(i18n("Show print preview of current document")); m_filelistDeleteDocument = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Delete"), this); connect(m_filelistDeleteDocument, &QAction::triggered, this, &KateFileTree::slotDocumentDelete); m_filelistDeleteDocument->setWhatsThis(i18n("Close and delete selected file from storage.")); QActionGroup *modeGroup = new QActionGroup(this); m_treeModeAction = setupOption(modeGroup, QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Tree Mode"), i18n("Set view style to Tree Mode"), SLOT(slotTreeMode()), true); m_listModeAction = setupOption(modeGroup, QIcon::fromTheme(QStringLiteral("view-list-text")), i18nc("@action:inmenu", "List Mode"), i18n("Set view style to List Mode"), SLOT(slotListMode()), false); QActionGroup *sortGroup = new QActionGroup(this); m_sortByFile = setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Name"), i18n("Sort by Document Name"), SLOT(slotSortName()), true); m_sortByPath = setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Path"), i18n("Sort by Document Path"), SLOT(slotSortPath()), false); m_sortByOpeningOrder = setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Opening Order"), i18n("Sort by Opening Order"), SLOT(slotSortOpeningOrder()), false); m_resetHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18nc("@action:inmenu", "Clear History"), this); connect(m_resetHistory, &QAction::triggered, this, &KateFileTree::slotResetHistory); m_resetHistory->setWhatsThis(i18n("Clear edit/view history.")); QPalette p = palette(); p.setColor(QPalette::Inactive, QPalette::Highlight, p.color(QPalette::Active, QPalette::Highlight)); p.setColor(QPalette::Inactive, QPalette::HighlightedText, p.color(QPalette::Active, QPalette::HighlightedText)); setPalette(p); } KateFileTree::~KateFileTree() { } void KateFileTree::setModel(QAbstractItemModel *model) { Q_ASSERT(qobject_cast(model)); // we don't really work with anything else QTreeView::setModel(model); } QAction *KateFileTree::setupOption(QActionGroup *group, const QIcon &icon, const QString &label, const QString &whatsThis, const char *slot, bool checked) { QAction *new_action = new QAction(icon, label, this); new_action->setWhatsThis(whatsThis); new_action->setActionGroup(group); new_action->setCheckable(true); new_action->setChecked(checked); connect(new_action, SIGNAL(triggered()), this, slot); return new_action; } void KateFileTree::slotListMode() { emit viewModeChanged(true); } void KateFileTree::slotTreeMode() { emit viewModeChanged(false); } void KateFileTree::slotSortName() { emit sortRoleChanged(Qt::DisplayRole); } void KateFileTree::slotSortPath() { emit sortRoleChanged(KateFileTreeModel::PathRole); } void KateFileTree::slotSortOpeningOrder() { emit sortRoleChanged(KateFileTreeModel::OpeningOrderRole); } void KateFileTree::slotCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous) { Q_UNUSED(previous); if (!current.isValid()) { return; } KTextEditor::Document *doc = model()->data(current, KateFileTreeModel::DocumentRole).value(); if (doc) { m_previouslySelected = current; } } void KateFileTree::mouseClicked(const QModelIndex &index) { if (auto doc = model()->data(index, KateFileTreeModel::DocumentRole).value()) { emit activateDocument(doc); } } void KateFileTree::contextMenuEvent(QContextMenuEvent *event) { m_indexContextMenu = selectionModel()->currentIndex(); selectionModel()->setCurrentIndex(m_indexContextMenu, QItemSelectionModel::ClearAndSelect); KateFileTreeProxyModel *ftpm = static_cast(model()); KateFileTreeModel *ftm = static_cast(ftpm->sourceModel()); bool listMode = ftm->listMode(); m_treeModeAction->setChecked(!listMode); m_listModeAction->setChecked(listMode); int sortRole = ftpm->sortRole(); m_sortByFile->setChecked(sortRole == Qt::DisplayRole); m_sortByPath->setChecked(sortRole == KateFileTreeModel::PathRole); m_sortByOpeningOrder->setChecked(sortRole == KateFileTreeModel::OpeningOrderRole); KTextEditor::Document *doc = m_indexContextMenu.data(KateFileTreeModel::DocumentRole).value(); const bool isFile = (nullptr != doc); QMenu menu; menu.addAction(m_filelistReloadDocument); menu.addAction(m_filelistCloseDocument); menu.addAction(m_filelistExpandRecursive); menu.addAction(m_filelistCollapseRecursive); if (isFile) { menu.addAction(m_filelistCloseOtherDocument); menu.addSeparator(); menu.addAction(m_filelistOpenContainingFolder); menu.addAction(m_filelistCopyFilename); menu.addAction(m_filelistRenameFile); menu.addAction(m_filelistPrintDocument); menu.addAction(m_filelistPrintDocumentPreview); QMenu *openWithMenu = menu.addMenu(i18nc("@action:inmenu", "Open With")); connect(openWithMenu, &QMenu::aboutToShow, this, &KateFileTree::slotFixOpenWithMenu); connect(openWithMenu, &QMenu::triggered, this, &KateFileTree::slotOpenWithMenuAction); const bool hasFileName = doc->url().isValid(); m_filelistOpenContainingFolder->setEnabled(hasFileName); m_filelistCopyFilename->setEnabled(hasFileName); m_filelistRenameFile->setEnabled(hasFileName); m_filelistDeleteDocument->setEnabled(hasFileName); menu.addAction(m_filelistDeleteDocument); } menu.addSeparator(); QMenu *view_menu = menu.addMenu(i18nc("@action:inmenu", "View Mode")); view_menu->addAction(m_treeModeAction); view_menu->addAction(m_listModeAction); QMenu *sort_menu = menu.addMenu(QIcon::fromTheme(QStringLiteral("view-sort")), i18nc("@action:inmenu", "Sort By")); sort_menu->addAction(m_sortByFile); sort_menu->addAction(m_sortByPath); sort_menu->addAction(m_sortByOpeningOrder); menu.addAction(m_resetHistory); menu.exec(viewport()->mapToGlobal(event->pos())); if (m_previouslySelected.isValid()) { selectionModel()->setCurrentIndex(m_previouslySelected, QItemSelectionModel::ClearAndSelect); } event->accept(); } void KateFileTree::slotFixOpenWithMenu() { QMenu *menu = (QMenu *)sender(); menu->clear(); KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } // get a list of appropriate services. QMimeDatabase db; QMimeType mime = db.mimeTypeForName(doc->mimeType()); QAction *a = nullptr; const KService::List offers = KMimeTypeTrader::self()->query(mime.name(), QStringLiteral("Application")); // for each one, insert a menu item... for (const auto &service : offers) { if (service->name() == QLatin1String("Kate")) { continue; } a = menu->addAction(QIcon::fromTheme(service->icon()), service->name()); a->setData(service->entryPath()); } // append "Other..." to call the KDE "open with" dialog. a = menu->addAction(i18n("&Other...")); a->setData(QString()); } void KateFileTree::slotOpenWithMenuAction(QAction *a) { QList list; KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } list.append(doc->url()); const QString openWith = a->data().toString(); if (openWith.isEmpty()) { // display "open with" dialog KOpenWithDialog dlg(list); if (dlg.exec()) { KRun::runService(*dlg.service(), list, this); } return; } KService::Ptr app = KService::serviceByDesktopPath(openWith); if (app) { KRun::runService(*app, list, this); } else { KMessageBox::error(this, i18n("Application '%1' not found.", openWith), i18n("Application not found")); } } Q_DECLARE_METATYPE(QList) void KateFileTree::slotDocumentClose() { m_previouslySelected = QModelIndex(); QVariant v = m_indexContextMenu.data(KateFileTreeModel::DocumentTreeRole); if (!v.isValid()) { return; } QList closingDocuments = v.value>(); KTextEditor::Editor::instance()->application()->closeDocuments(closingDocuments); } void KateFileTree::slotExpandRecursive() { if (!m_indexContextMenu.isValid()) { return; } // Work list for DFS walk over sub tree QList worklist = {m_indexContextMenu}; while (!worklist.isEmpty()) { QPersistentModelIndex index = worklist.takeLast(); // Expand current item expand(index); // Append all children of current item for (int i = 0; i < model()->rowCount(index); ++i) { worklist.append(model()->index(i, 0, index)); } } } void KateFileTree::slotCollapseRecursive() { if (!m_indexContextMenu.isValid()) { return; } // Work list for DFS walk over sub tree QList worklist = {m_indexContextMenu}; while (!worklist.isEmpty()) { QPersistentModelIndex index = worklist.takeLast(); // Expand current item collapse(index); // Prepend all children of current item for (int i = 0; i < model()->rowCount(index); ++i) { worklist.append(model()->index(i, 0, index)); } } } void KateFileTree::slotDocumentCloseOther() { QVariant v = model()->data(m_indexContextMenu.parent(), KateFileTreeModel::DocumentTreeRole); if (!v.isValid()) { return; } QList closingDocuments = v.value>(); KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); closingDocuments.removeOne(doc); KTextEditor::Editor::instance()->application()->closeDocuments(closingDocuments); } void KateFileTree::slotDocumentReload() { QVariant v = m_indexContextMenu.data(KateFileTreeModel::DocumentTreeRole); if (!v.isValid()) { return; } const QList docs = v.value>(); for (KTextEditor::Document *doc : docs) { doc->documentReload(); } } void KateFileTree::slotOpenContainingFolder() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (doc) { KIO::highlightInFileManager({doc->url()}); } } void KateFileTree::slotCopyFilename() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here // (make sure that the mentioned bug 381052 does not reappear) if (doc) { // ensure we prefer native separators, bug 381052 if (doc->url().isLocalFile()) { QApplication::clipboard()->setText(QDir::toNativeSeparators(doc->url().toLocalFile())); } else { QApplication::clipboard()->setText(doc->url().url()); } } } void KateFileTree::slotRenameFile() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here if (!doc) { return; } const QUrl oldFileUrl = doc->url(); const QString oldFileName = doc->url().fileName(); bool ok; QString newFileName = QInputDialog::getText(this, i18n("Rename file"), i18n("New file name"), QLineEdit::Normal, oldFileName, &ok); if (!ok) { return; } QUrl newFileUrl = oldFileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); newFileUrl.setPath(newFileUrl.path() + QLatin1Char('/') + newFileName); if (!newFileUrl.isValid()) { return; } if (!doc->closeUrl()) { return; } doc->waitSaveComplete(); KIO::CopyJob *job = KIO::move(oldFileUrl, newFileUrl); QSharedPointer sc(new QMetaObject::Connection()); auto success = [doc, sc](KIO::Job *, const QUrl &, const QUrl &realNewFileUrl, const QDateTime &, bool, bool) { doc->openUrl(realNewFileUrl); doc->documentSavedOrUploaded(doc, true); QObject::disconnect(*sc); }; *sc = connect(job, &KIO::CopyJob::copyingDone, doc, success); if (!job->exec()) { KMessageBox::sorry(this, i18n("File \"%1\" could not be moved to \"%2\"", oldFileUrl.toDisplayString(), newFileUrl.toDisplayString())); doc->openUrl(oldFileUrl); } } void KateFileTree::slotDocumentFirst() { KTextEditor::Document *doc = model()->data(model()->index(0, 0), KateFileTreeModel::DocumentRole).value(); if (doc) { emit activateDocument(doc); } } void KateFileTree::slotDocumentLast() { int count = model()->rowCount(model()->parent(currentIndex())); KTextEditor::Document *doc = model()->data(model()->index(count - 1, 0), KateFileTreeModel::DocumentRole).value(); if (doc) { emit activateDocument(doc); } } void KateFileTree::slotDocumentPrev() { KateFileTreeProxyModel *ftpm = static_cast(model()); QModelIndex current_index = currentIndex(); QModelIndex prev; // scan up the tree skipping any dir nodes while (current_index.isValid()) { if (current_index.row() > 0) { current_index = ftpm->sibling(current_index.row() - 1, current_index.column(), current_index); if (!current_index.isValid()) { break; } if (ftpm->isDir(current_index)) { // try and select the last child in this parent int children = ftpm->rowCount(current_index); current_index = ftpm->index(children - 1, 0, current_index); if (ftpm->isDir(current_index)) { // since we're a dir, keep going while (ftpm->isDir(current_index)) { children = ftpm->rowCount(current_index); current_index = ftpm->index(children - 1, 0, current_index); } if (!ftpm->isDir(current_index)) { prev = current_index; break; } continue; } else { // we're the previous file, set prev prev = current_index; break; } } else { // found document item prev = current_index; break; } } else { // just select the parent, the logic above will handle the rest current_index = ftpm->parent(current_index); if (!current_index.isValid()) { // paste the root node here, try and wrap around int children = ftpm->rowCount(current_index); QModelIndex last_index = ftpm->index(children - 1, 0, current_index); if (!last_index.isValid()) { break; } if (ftpm->isDir(last_index)) { // last node is a dir, select last child row int last_children = ftpm->rowCount(last_index); prev = ftpm->index(last_children - 1, 0, last_index); // bug here? break; } else { // got last file node prev = last_index; break; } } } } if (prev.isValid()) { KTextEditor::Document *doc = model()->data(prev, KateFileTreeModel::DocumentRole).value(); emit activateDocument(doc); } } void KateFileTree::slotDocumentNext() { KateFileTreeProxyModel *ftpm = static_cast(model()); QModelIndex current_index = currentIndex(); int parent_row_count = ftpm->rowCount(ftpm->parent(current_index)); QModelIndex next; // scan down the tree skipping any dir nodes while (current_index.isValid()) { if (current_index.row() < parent_row_count - 1) { current_index = ftpm->sibling(current_index.row() + 1, current_index.column(), current_index); if (!current_index.isValid()) { break; } if (ftpm->isDir(current_index)) { // we have a dir node while (ftpm->isDir(current_index)) { current_index = ftpm->index(0, 0, current_index); } parent_row_count = ftpm->rowCount(ftpm->parent(current_index)); if (!ftpm->isDir(current_index)) { next = current_index; break; } } else { // found document item next = current_index; break; } } else { // select the parent's next sibling QModelIndex parent_index = ftpm->parent(current_index); int grandparent_row_count = ftpm->rowCount(ftpm->parent(parent_index)); current_index = parent_index; parent_row_count = grandparent_row_count; // at least if we're not past the last node if (!current_index.isValid()) { // paste the root node here, try and wrap around QModelIndex last_index = ftpm->index(0, 0, QModelIndex()); if (!last_index.isValid()) { break; } if (ftpm->isDir(last_index)) { // last node is a dir, select first child row while (ftpm->isDir(last_index)) { if (ftpm->rowCount(last_index)) { // has children, select first last_index = ftpm->index(0, 0, last_index); } } next = last_index; break; } else { // got first file node next = last_index; break; } } } } if (next.isValid()) { KTextEditor::Document *doc = model()->data(next, KateFileTreeModel::DocumentRole).value(); emit activateDocument(doc); } } void KateFileTree::slotPrintDocument() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } doc->print(); } void KateFileTree::slotPrintDocumentPreview() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } doc->printPreview(); } void KateFileTree::slotResetHistory() { KateFileTreeProxyModel *ftpm = static_cast(model()); KateFileTreeModel *ftm = static_cast(ftpm->sourceModel()); ftm->resetHistory(); } void KateFileTree::slotDocumentDelete() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here if (!doc) { return; } QUrl url = doc->url(); bool go = (KMessageBox::warningContinueCancel( this, i18n("Do you really want to delete file \"%1\" from storage?", url.toDisplayString()), i18n("Delete file?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("filetreedeletefile")) == KMessageBox::Continue); if (!go) { return; } if (!KTextEditor::Editor::instance()->application()->closeDocument(doc)) { return; // no extra message, the internals of ktexteditor should take care of that. } if (url.isValid()) { KIO::DeleteJob *job = KIO::del(url); if (!job->exec()) { KMessageBox::sorry(this, i18n("File \"%1\" could not be deleted.", url.toDisplayString())); } } } // END KateFileTree diff --git a/addons/filetree/katefiletree.h b/addons/filetree/katefiletree.h index c9140b080..1dcf356de 100644 --- a/addons/filetree/katefiletree.h +++ b/addons/filetree/katefiletree.h @@ -1,116 +1,116 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef KATE_FILETREE_H #define KATE_FILETREE_H -#include #include #include +#include namespace KTextEditor { class Document; } class QActionGroup; class KateFileTree : public QTreeView { Q_OBJECT public: KateFileTree(QWidget *parent); ~KateFileTree() override; void setModel(QAbstractItemModel *model) override; public Q_SLOTS: void slotDocumentClose(); void slotExpandRecursive(); void slotCollapseRecursive(); void slotDocumentCloseOther(); void slotDocumentReload(); void slotOpenContainingFolder(); void slotCopyFilename(); void slotCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous); void slotDocumentFirst(); void slotDocumentLast(); void slotDocumentNext(); void slotDocumentPrev(); void slotPrintDocument(); void slotPrintDocumentPreview(); void slotResetHistory(); void slotDocumentDelete(); protected: void contextMenuEvent(QContextMenuEvent *event) override; Q_SIGNALS: void closeDocument(KTextEditor::Document *); void activateDocument(KTextEditor::Document *); void openDocument(const QUrl &); void viewModeChanged(bool treeMode); void sortRoleChanged(int); private Q_SLOTS: void mouseClicked(const QModelIndex &index); void slotTreeMode(); void slotListMode(); void slotSortName(); void slotSortPath(); void slotSortOpeningOrder(); void slotFixOpenWithMenu(); void slotOpenWithMenuAction(QAction *a); void slotRenameFile(); private: QAction *setupOption(QActionGroup *group, const QIcon &, const QString &, const QString &, const char *slot, bool checked = false); private: QAction *m_filelistCloseDocument; QAction *m_filelistExpandRecursive; QAction *m_filelistCollapseRecursive; QAction *m_filelistCloseOtherDocument; QAction *m_filelistReloadDocument; QAction *m_filelistOpenContainingFolder; QAction *m_filelistCopyFilename; QAction *m_filelistRenameFile; QAction *m_filelistPrintDocument; QAction *m_filelistPrintDocumentPreview; QAction *m_filelistDeleteDocument; QAction *m_treeModeAction; QAction *m_listModeAction; QAction *m_sortByFile; QAction *m_sortByPath; QAction *m_sortByOpeningOrder; QAction *m_resetHistory; QPersistentModelIndex m_previouslySelected; QPersistentModelIndex m_indexContextMenu; }; #endif // KATE_FILETREE_H diff --git a/addons/filetree/katefiletreeconfigpage.cpp b/addons/filetree/katefiletreeconfigpage.cpp index e967e0fee..f36ce3a5c 100644 --- a/addons/filetree/katefiletreeconfigpage.cpp +++ b/addons/filetree/katefiletreeconfigpage.cpp @@ -1,187 +1,187 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001, 2007 Anders Lund Copyright (C) 2009, Abhishek Patil Copyright (C) 2010, Thomas Fjellstrom This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ /* Config stuff plan: ----------------- main default config is stored in KSharedConfig::openConfig()+":filetree" when main config is set, it needs to tell view's to delete existing customized settings, and use the global ones (somehow) (maybe some kind of "customized" flag?) view needs to pull default settings from the main plugin config */ #include "katefiletreeconfigpage.h" -#include "katefiletreeplugin.h" #include "katefiletreedebug.h" #include "katefiletreemodel.h" +#include "katefiletreeplugin.h" #include "katefiletreeproxymodel.h" -#include #include -#include #include -#include -#include #include +#include +#include +#include +#include KateFileTreeConfigPage::KateFileTreeConfigPage(QWidget *parent, KateFileTreePlugin *fl) : KTextEditor::ConfigPage(parent) , m_plug(fl) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); gbEnableShading = new QGroupBox(i18n("Background Shading"), this); gbEnableShading->setCheckable(true); layout->addWidget(gbEnableShading); QGridLayout *lo = new QGridLayout(gbEnableShading); kcbViewShade = new KColorButton(gbEnableShading); lViewShade = new QLabel(i18n("&Viewed documents' shade:"), gbEnableShading); lViewShade->setBuddy(kcbViewShade); lo->addWidget(lViewShade, 2, 0); lo->addWidget(kcbViewShade, 2, 1); kcbEditShade = new KColorButton(gbEnableShading); lEditShade = new QLabel(i18n("&Modified documents' shade:"), gbEnableShading); lEditShade->setBuddy(kcbEditShade); lo->addWidget(lEditShade, 3, 0); lo->addWidget(kcbEditShade, 3, 1); // sorting QHBoxLayout *lo2 = new QHBoxLayout; layout->addLayout(lo2); lSort = new QLabel(i18n("&Sort by:"), this); lo2->addWidget(lSort); cmbSort = new KComboBox(this); lo2->addWidget(cmbSort); lSort->setBuddy(cmbSort); cmbSort->addItem(i18n("Opening Order"), (int)KateFileTreeModel::OpeningOrderRole); cmbSort->addItem(i18n("Document Name"), (int)Qt::DisplayRole); cmbSort->addItem(i18n("Url"), (int)KateFileTreeModel::PathRole); // view mode QHBoxLayout *lo3 = new QHBoxLayout; layout->addLayout(lo3); lMode = new QLabel(i18n("&View Mode:"), this); lo3->addWidget(lMode); cmbMode = new KComboBox(this); lo3->addWidget(cmbMode); lMode->setBuddy(cmbMode); cmbMode->addItem(i18n("Tree View"), QVariant(false)); cmbMode->addItem(i18n("List View"), QVariant(true)); // Show Full Path on Roots? QHBoxLayout *lo4 = new QHBoxLayout; layout->addLayout(lo4); cbShowFullPath = new QCheckBox(i18n("&Show Full Path"), this); lo4->addWidget(cbShowFullPath); layout->insertStretch(-1, 10); gbEnableShading->setWhatsThis( i18n("When background shading is enabled, documents that have been viewed " "or edited within the current session will have a shaded background. " "The most recent documents have the strongest shade.")); kcbViewShade->setWhatsThis(i18n("Set the color for shading viewed documents.")); kcbEditShade->setWhatsThis( i18n("Set the color for modified documents. This color is blended into " "the color for viewed files. The most recently edited documents get " "most of this color.")); cbShowFullPath->setWhatsThis( i18n("When enabled, in tree mode, top level folders will show up with their full path " "rather than just the last folder name.")); // cmbSort->setWhatsThis( i18n( // "Set the sorting method for the documents.") ); reset(); connect(gbEnableShading, &QGroupBox::toggled, this, &KateFileTreeConfigPage::slotMyChanged); connect(kcbViewShade, &KColorButton::changed, this, &KateFileTreeConfigPage::slotMyChanged); connect(kcbEditShade, &KColorButton::changed, this, &KateFileTreeConfigPage::slotMyChanged); connect(cmbSort, static_cast(&KComboBox::activated), this, &KateFileTreeConfigPage::slotMyChanged); connect(cmbMode, static_cast(&KComboBox::activated), this, &KateFileTreeConfigPage::slotMyChanged); connect(cbShowFullPath, &QCheckBox::stateChanged, this, &KateFileTreeConfigPage::slotMyChanged); } QString KateFileTreeConfigPage::name() const { return QString(i18n("Documents")); } QString KateFileTreeConfigPage::fullName() const { return QString(i18n("Configure Documents")); } QIcon KateFileTreeConfigPage::icon() const { return QIcon::fromTheme(QLatin1String("view-list-tree")); } void KateFileTreeConfigPage::apply() { if (!m_changed) { return; } m_changed = false; // apply config to views m_plug->applyConfig( gbEnableShading->isChecked(), kcbViewShade->color(), kcbEditShade->color(), cmbMode->itemData(cmbMode->currentIndex()).toBool(), cmbSort->itemData(cmbSort->currentIndex()).toInt(), cbShowFullPath->checkState() == Qt::Checked); } void KateFileTreeConfigPage::reset() { const KateFileTreePluginSettings &settings = m_plug->settings(); gbEnableShading->setChecked(settings.shadingEnabled()); kcbEditShade->setColor(settings.editShade()); kcbViewShade->setColor(settings.viewShade()); cmbSort->setCurrentIndex(cmbSort->findData(settings.sortRole())); cmbMode->setCurrentIndex(settings.listMode()); cbShowFullPath->setCheckState(settings.showFullPathOnRoots() ? Qt::Checked : Qt::Unchecked); m_changed = false; } void KateFileTreeConfigPage::defaults() { // m_plug->settings().revertToDefaults() ?? // not sure the above is ever needed... reset(); } void KateFileTreeConfigPage::slotMyChanged() { m_changed = true; emit changed(); } diff --git a/addons/filetree/katefiletreemodel.cpp b/addons/filetree/katefiletreemodel.cpp index 697d7f8f9..6d6467701 100644 --- a/addons/filetree/katefiletreemodel.cpp +++ b/addons/filetree/katefiletreemodel.cpp @@ -1,1345 +1,1345 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "katefiletreemodel.h" #include #include +#include #include #include #include -#include #include #include #include -#include #include +#include +#include #include #include -#include #include "katefiletreedebug.h" class ProxyItemDir; class ProxyItem { friend class KateFileTreeModel; public: enum Flag { None = 0, Dir = 1, Modified = 2, ModifiedExternally = 4, DeletedExternally = 8, Empty = 16, ShowFullPath = 32, Host = 64 }; Q_DECLARE_FLAGS(Flags, Flag) ProxyItem(const QString &n, ProxyItemDir *p = nullptr, Flags f = ProxyItem::None); ~ProxyItem(); int addChild(ProxyItem *p); void remChild(ProxyItem *p); ProxyItemDir *parent() const; ProxyItem *child(int idx) const; int childCount() const; int row() const; const QString &display() const; const QString &documentName() const; const QString &path() const; void setPath(const QString &str); void setHost(const QString &host); const QString &host() const; void setIcon(const QIcon &i); const QIcon &icon() const; const QList &children() const; QList &children(); void setDoc(KTextEditor::Document *doc); KTextEditor::Document *doc() const; /** * the view usess this to close all the documents under the folder * @returns list of all the (nested) documents under this node */ QList docTree() const; void setFlags(Flags flags); void setFlag(Flag flag); void clearFlag(Flag flag); bool flag(Flag flag) const; private: QString m_path; QString m_documentName; ProxyItemDir *m_parent; QList m_children; int m_row; Flags m_flags; QString m_display; QIcon m_icon; KTextEditor::Document *m_doc; QString m_host; protected: void updateDisplay(); void updateDocumentName(); }; QDebug operator<<(QDebug dbg, ProxyItem *item) { if (!item) { dbg.nospace() << "ProxyItem(0x0) "; return dbg.maybeSpace(); } const void *parent = static_cast(item->parent()); dbg.nospace() << "ProxyItem(" << (void *)item << ","; dbg.nospace() << parent << "," << item->row() << ","; dbg.nospace() << item->doc() << "," << item->path() << ") "; return dbg.maybeSpace(); } class ProxyItemDir : public ProxyItem { public: ProxyItemDir(const QString &n, ProxyItemDir *p = nullptr) : ProxyItem(n, p) { setFlag(ProxyItem::Dir); updateDisplay(); setIcon(QIcon::fromTheme(QStringLiteral("folder"))); } }; QDebug operator<<(QDebug dbg, ProxyItemDir *item) { if (!item) { dbg.nospace() << "ProxyItemDir(0x0) "; return dbg.maybeSpace(); } const void *parent = static_cast(item->parent()); dbg.nospace() << "ProxyItemDir(" << (void *)item << ","; dbg.nospace() << parent << "," << item->row() << ","; dbg.nospace() << item->path() << ", children:" << item->childCount() << ") "; return dbg.maybeSpace(); } Q_DECLARE_OPERATORS_FOR_FLAGS(ProxyItem::Flags) // BEGIN ProxyItem ProxyItem::ProxyItem(const QString &d, ProxyItemDir *p, ProxyItem::Flags f) : m_path(d) , m_parent(Q_NULLPTR) , m_row(-1) , m_flags(f) , m_doc(nullptr) { updateDisplay(); /** * add to parent, if parent passed * we assigned above nullptr to parent to not trigger * remove from old parent here! */ if (p) { p->addChild(this); } } ProxyItem::~ProxyItem() { qDeleteAll(m_children); } void ProxyItem::updateDisplay() { // triggers only if this is a top level node and the root has the show full path flag set. if (flag(ProxyItem::Dir) && m_parent && !m_parent->m_parent && m_parent->flag(ProxyItem::ShowFullPath)) { m_display = m_path; if (m_display.startsWith(QDir::homePath())) { m_display.replace(0, QDir::homePath().length(), QStringLiteral("~")); } } else { m_display = m_path.section(QLatin1Char('/'), -1, -1); if (flag(ProxyItem::Host) && (!m_parent || (m_parent && !m_parent->m_parent))) { const QString hostPrefix = QStringLiteral("[%1]").arg(host()); if (hostPrefix != m_display) { m_display = hostPrefix + m_display; } } } } int ProxyItem::addChild(ProxyItem *item) { // remove from old parent, is any if (item->m_parent) { item->m_parent->remChild(item); } const int item_row = m_children.count(); item->m_row = item_row; m_children.append(item); item->m_parent = static_cast(this); item->updateDisplay(); return item_row; } void ProxyItem::remChild(ProxyItem *item) { const int idx = m_children.indexOf(item); Q_ASSERT(idx != -1); m_children.removeAt(idx); for (int i = idx; i < m_children.count(); i++) { m_children[i]->m_row = i; } item->m_parent = nullptr; } ProxyItemDir *ProxyItem::parent() const { return m_parent; } ProxyItem *ProxyItem::child(int idx) const { return (idx < 0 || idx >= m_children.count()) ? nullptr : m_children[idx]; } int ProxyItem::childCount() const { return m_children.count(); } int ProxyItem::row() const { return m_row; } const QIcon &ProxyItem::icon() const { return m_icon; } void ProxyItem::setIcon(const QIcon &i) { m_icon = i; } const QString &ProxyItem::documentName() const { return m_documentName; } const QString &ProxyItem::display() const { return m_display; } const QString &ProxyItem::path() const { return m_path; } void ProxyItem::setPath(const QString &p) { m_path = p; updateDisplay(); } const QList &ProxyItem::children() const { return m_children; } QList &ProxyItem::children() { return m_children; } void ProxyItem::setDoc(KTextEditor::Document *doc) { Q_ASSERT(doc); m_doc = doc; updateDocumentName(); } KTextEditor::Document *ProxyItem::doc() const { return m_doc; } QList ProxyItem::docTree() const { QList result; if (m_doc) { result.append(m_doc); return result; } for (const ProxyItem *item : qAsConst(m_children)) { result.append(item->docTree()); } return result; } bool ProxyItem::flag(Flag f) const { return m_flags & f; } void ProxyItem::setFlag(Flag f) { m_flags |= f; } void ProxyItem::setFlags(Flags f) { m_flags = f; } void ProxyItem::clearFlag(Flag f) { m_flags &= ~f; } void ProxyItem::setHost(const QString &host) { m_host = host; if (host.isEmpty()) { clearFlag(Host); } else { setFlag(Host); } updateDocumentName(); updateDisplay(); } const QString &ProxyItem::host() const { return m_host; } void ProxyItem::updateDocumentName() { const QString docName = m_doc ? m_doc->documentName() : QString(); if (flag(ProxyItem::Host)) { m_documentName = QStringLiteral("[%1]%2").arg(m_host, docName); } else { m_documentName = docName; } } // END ProxyItem KateFileTreeModel::KateFileTreeModel(QObject *p) : QAbstractItemModel(p) , m_root(new ProxyItemDir(QStringLiteral("m_root"), nullptr)) { // setup default settings // session init will set these all soon const KColorScheme colors(QPalette::Active); const QColor bg = colors.background().color(); m_editShade = KColorUtils::tint(bg, colors.foreground(KColorScheme::ActiveText).color(), 0.5); m_viewShade = KColorUtils::tint(bg, colors.foreground(KColorScheme::VisitedText).color(), 0.5); m_shadingEnabled = true; m_listMode = false; initModel(); } KateFileTreeModel::~KateFileTreeModel() { delete m_root; } bool KateFileTreeModel::shadingEnabled() const { return m_shadingEnabled; } void KateFileTreeModel::setShadingEnabled(bool se) { if (m_shadingEnabled != se) { updateBackgrounds(true); m_shadingEnabled = se; } } const QColor &KateFileTreeModel::editShade() const { return m_editShade; } void KateFileTreeModel::setEditShade(const QColor &es) { m_editShade = es; } const QColor &KateFileTreeModel::viewShade() const { return m_viewShade; } void KateFileTreeModel::setViewShade(const QColor &vs) { m_viewShade = vs; } bool KateFileTreeModel::showFullPathOnRoots(void) const { return m_root->flag(ProxyItem::ShowFullPath); } void KateFileTreeModel::setShowFullPathOnRoots(bool s) { if (s) { m_root->setFlag(ProxyItem::ShowFullPath); } else { m_root->clearFlag(ProxyItem::ShowFullPath); } const auto rootChildren = m_root->children(); for (ProxyItem *root : rootChildren) { root->updateDisplay(); } } void KateFileTreeModel::initModel() { // add already existing documents const auto documents = KTextEditor::Editor::instance()->application()->documents(); for (KTextEditor::Document *doc : documents) { documentOpened(doc); } } void KateFileTreeModel::clearModel() { // remove all items // can safely ignore documentClosed here beginRemoveRows(QModelIndex(), 0, qMax(m_root->childCount() - 1, 0)); delete m_root; m_root = new ProxyItemDir(QStringLiteral("m_root"), nullptr); m_docmap.clear(); m_viewHistory.clear(); m_editHistory.clear(); m_brushes.clear(); endRemoveRows(); } void KateFileTreeModel::connectDocument(const KTextEditor::Document *doc) { connect(doc, &KTextEditor::Document::documentNameChanged, this, &KateFileTreeModel::documentNameChanged); connect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateFileTreeModel::documentNameChanged); connect(doc, &KTextEditor::Document::modifiedChanged, this, &KateFileTreeModel::documentModifiedChanged); connect(doc, SIGNAL(modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(documentModifiedOnDisc(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } QModelIndex KateFileTreeModel::docIndex(const KTextEditor::Document *doc) const { if (!m_docmap.contains(doc)) { return QModelIndex(); } ProxyItem *item = m_docmap[doc]; return createIndex(item->row(), 0, item); } Qt::ItemFlags KateFileTreeModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = Qt::ItemIsEnabled; if (!index.isValid()) { return nullptr; } const ProxyItem *item = static_cast(index.internalPointer()); if (item) { if (!item->childCount()) { flags |= Qt::ItemIsSelectable; } if (item->doc() && item->doc()->url().isValid()) { flags |= Qt::ItemIsDragEnabled; } } return flags; } Q_DECLARE_METATYPE(QList) QVariant KateFileTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } ProxyItem *item = static_cast(index.internalPointer()); if (!item) { return QVariant(); } switch (role) { case KateFileTreeModel::PathRole: // allow to sort with hostname + path, bug 271488 return (item->doc() && !item->doc()->url().isEmpty()) ? item->doc()->url().toString() : item->path(); case KateFileTreeModel::DocumentRole: return QVariant::fromValue(item->doc()); case KateFileTreeModel::OpeningOrderRole: return item->row(); case KateFileTreeModel::DocumentTreeRole: return QVariant::fromValue(item->docTree()); case Qt::DisplayRole: // in list mode we want to use kate's fancy names. if (m_listMode) { return item->documentName(); } else { return item->display(); } case Qt::DecorationRole: return item->icon(); case Qt::ToolTipRole: { QString tooltip = item->path(); if (item->flag(ProxyItem::DeletedExternally) || item->flag(ProxyItem::ModifiedExternally)) { tooltip = i18nc("%1 is the full path", "

%1

The document has been modified by another application.

", item->path()); } return tooltip; } case Qt::ForegroundRole: { const KColorScheme colors(QPalette::Active); if (!item->flag(ProxyItem::Dir) && (!item->doc() || item->doc()->openingError())) { return colors.foreground(KColorScheme::InactiveText).color(); } } break; case Qt::BackgroundRole: // TODO: do that funky shading the file list does... if (m_shadingEnabled && m_brushes.contains(item)) { return m_brushes[item]; } break; } return QVariant(); } QMimeData *KateFileTreeModel::mimeData(const QModelIndexList &indexes) const { QList urls; for (const auto &index : indexes) { ProxyItem *item = static_cast(index.internalPointer()); if (!item || !item->doc() || !item->doc()->url().isValid()) { continue; } urls.append(item->doc()->url()); } if (urls.isEmpty()) { return nullptr; } QMimeData *mimeData = new QMimeData(); mimeData->setUrls(urls); return mimeData; } QVariant KateFileTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); Q_UNUSED(role); if (section == 0) { return QLatin1String("name"); } return QVariant(); } int KateFileTreeModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return m_root->childCount(); } const ProxyItem *item = static_cast(parent.internalPointer()); if (!item) { return 0; } return item->childCount(); } int KateFileTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } QModelIndex KateFileTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } const ProxyItem *item = static_cast(index.internalPointer()); if (!item) { return QModelIndex(); } if (!item->parent()) { return QModelIndex(); } if (item->parent() == m_root) { return QModelIndex(); } return createIndex(item->parent()->row(), 0, item->parent()); } QModelIndex KateFileTreeModel::index(int row, int column, const QModelIndex &parent) const { const ProxyItem *p = nullptr; if (column != 0) { return QModelIndex(); } if (!parent.isValid()) { p = m_root; } else { p = static_cast(parent.internalPointer()); } if (!p) { return QModelIndex(); } if (row < 0 || row >= p->childCount()) { return QModelIndex(); } return createIndex(row, 0, p->child(row)); } bool KateFileTreeModel::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) { return m_root->childCount() > 0; } const ProxyItem *item = static_cast(parent.internalPointer()); if (!item) { return false; } return item->childCount() > 0; } bool KateFileTreeModel::isDir(const QModelIndex &index) const { if (!index.isValid()) { return true; } const ProxyItem *item = static_cast(index.internalPointer()); if (!item) { return false; } return item->flag(ProxyItem::Dir); } bool KateFileTreeModel::listMode() const { return m_listMode; } void KateFileTreeModel::setListMode(bool lm) { if (lm != m_listMode) { m_listMode = lm; clearModel(); initModel(); } } void KateFileTreeModel::documentOpened(KTextEditor::Document *doc) { ProxyItem *item = new ProxyItem(QString()); item->setDoc(doc); updateItemPathAndHost(item); setupIcon(item); handleInsert(item); m_docmap[doc] = item; connectDocument(doc); } void KateFileTreeModel::documentsOpened(const QList &docs) { for (KTextEditor::Document *doc : docs) { if (m_docmap.contains(doc)) { documentNameChanged(doc); } else { documentOpened(doc); } } } void KateFileTreeModel::documentModifiedChanged(KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; if (doc->isModified()) { item->setFlag(ProxyItem::Modified); } else { item->clearFlag(ProxyItem::Modified); item->clearFlag(ProxyItem::ModifiedExternally); item->clearFlag(ProxyItem::DeletedExternally); } setupIcon(item); const QModelIndex idx = createIndex(item->row(), 0, item); emit dataChanged(idx, idx); } void KateFileTreeModel::documentModifiedOnDisc(KTextEditor::Document *doc, bool modified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { Q_UNUSED(modified); if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; // This didn't do what I thought it did, on an ignore // we'd get !modified causing the warning icons to disappear if (!modified) { item->clearFlag(ProxyItem::ModifiedExternally); item->clearFlag(ProxyItem::DeletedExternally); } else { if (reason == KTextEditor::ModificationInterface::OnDiskDeleted) { item->setFlag(ProxyItem::DeletedExternally); } else if (reason == KTextEditor::ModificationInterface::OnDiskModified) { item->setFlag(ProxyItem::ModifiedExternally); } else if (reason == KTextEditor::ModificationInterface::OnDiskCreated) { // with out this, on "reload" we don't get the icons removed :( item->clearFlag(ProxyItem::ModifiedExternally); item->clearFlag(ProxyItem::DeletedExternally); } } setupIcon(item); const QModelIndex idx = createIndex(item->row(), 0, item); emit dataChanged(idx, idx); } void KateFileTreeModel::documentActivated(const KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; m_viewHistory.removeAll(item); m_viewHistory.prepend(item); while (m_viewHistory.count() > 10) { m_viewHistory.removeLast(); } updateBackgrounds(); } void KateFileTreeModel::documentEdited(const KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; m_editHistory.removeAll(item); m_editHistory.prepend(item); while (m_editHistory.count() > 10) { m_editHistory.removeLast(); } updateBackgrounds(); } void KateFileTreeModel::slotAboutToDeleteDocuments(const QList &docs) { for (const KTextEditor::Document *doc : docs) { disconnect(doc, &KTextEditor::Document::documentNameChanged, this, &KateFileTreeModel::documentNameChanged); disconnect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateFileTreeModel::documentNameChanged); disconnect(doc, &KTextEditor::Document::modifiedChanged, this, &KateFileTreeModel::documentModifiedChanged); disconnect(doc, SIGNAL(modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(documentModifiedOnDisc(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } } void KateFileTreeModel::slotDocumentsDeleted(const QList &docs) { for (const KTextEditor::Document *doc : docs) { connectDocument(doc); } } class EditViewCount { public: EditViewCount() = default; int edit = 0; int view = 0; }; void KateFileTreeModel::updateBackgrounds(bool force) { if (!m_shadingEnabled && !force) { return; } QMap helper; int i = 1; for (ProxyItem *item : qAsConst(m_viewHistory)) { helper[item].view = i; i++; } i = 1; for (ProxyItem *item : qAsConst(m_editHistory)) { helper[item].edit = i; i++; } QMap oldBrushes = m_brushes; m_brushes.clear(); const int hc = m_viewHistory.count(); const int ec = m_editHistory.count(); for (QMap::iterator it = helper.begin(); it != helper.end(); ++it) { QColor shade(m_viewShade); QColor eshade(m_editShade); if (it.value().edit > 0) { int v = hc - it.value().view; int e = ec - it.value().edit + 1; e = e * e; const int n = qMax(v + e, 1); shade.setRgb(((shade.red() * v) + (eshade.red() * e)) / n, ((shade.green() * v) + (eshade.green() * e)) / n, ((shade.blue() * v) + (eshade.blue() * e)) / n); } // blend in the shade color; latest is most colored. const double t = double(hc - it.value().view + 1) / double(hc); m_brushes[it.key()] = QBrush(KColorUtils::mix(QPalette().color(QPalette::Base), shade, t)); } for (auto it = m_brushes.constBegin(), end = m_brushes.constEnd(); it != end; ++it) { ProxyItem *item = it.key(); oldBrushes.remove(item); const QModelIndex idx = createIndex(item->row(), 0, item); dataChanged(idx, idx); } for (auto it = oldBrushes.constBegin(), end = oldBrushes.constEnd(); it != end; ++it) { ProxyItem *item = it.key(); const QModelIndex idx = createIndex(item->row(), 0, item); dataChanged(idx, idx); } } void KateFileTreeModel::handleEmptyParents(ProxyItemDir *item) { Q_ASSERT(item != nullptr); if (!item->parent()) { return; } ProxyItemDir *parent = item->parent(); while (parent) { if (!item->childCount()) { const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent); beginRemoveRows(parent_index, item->row(), item->row()); parent->remChild(item); endRemoveRows(); delete item; } else { // breakout early, if this node isn't empty, theres no use in checking its parents return; } item = parent; parent = item->parent(); } } void KateFileTreeModel::documentClosed(KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } if (m_shadingEnabled) { ProxyItem *toRemove = m_docmap[doc]; if (m_brushes.contains(toRemove)) { m_brushes.remove(toRemove); } if (m_viewHistory.contains(toRemove)) { m_viewHistory.removeAll(toRemove); } if (m_editHistory.contains(toRemove)) { m_editHistory.removeAll(toRemove); } } ProxyItem *node = m_docmap[doc]; ProxyItemDir *parent = node->parent(); const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent); beginRemoveRows(parent_index, node->row(), node->row()); node->parent()->remChild(node); endRemoveRows(); delete node; handleEmptyParents(parent); m_docmap.remove(doc); } void KateFileTreeModel::documentNameChanged(KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; if (m_shadingEnabled) { ProxyItem *toRemove = m_docmap[doc]; if (m_brushes.contains(toRemove)) { QBrush brush = m_brushes[toRemove]; m_brushes.remove(toRemove); m_brushes.insert(item, brush); } if (m_viewHistory.contains(toRemove)) { const int idx = m_viewHistory.indexOf(toRemove); if (idx != -1) { m_viewHistory.replace(idx, item); } } if (m_editHistory.contains(toRemove)) { const int idx = m_editHistory.indexOf(toRemove); if (idx != -1) { m_editHistory.replace(idx, item); } } } handleNameChange(item); emit triggerViewChangeAfterNameChange(); // FIXME: heh, non-standard signal? } ProxyItemDir *KateFileTreeModel::findRootNode(const QString &name, const int r) const { const auto rootChildren = m_root->children(); for (ProxyItem *item : rootChildren) { if (!item->flag(ProxyItem::Dir)) { continue; } // make sure we're actually matching against the right dir, // previously the check below would match /foo/xy against /foo/x // and return /foo/x rather than /foo/xy // this seems a bit hackish, but is the simplest way to solve the // current issue. QString path = item->path().section(QLatin1Char('/'), 0, -r) + QLatin1Char('/'); if (name.startsWith(path)) { return static_cast(item); } } return nullptr; } ProxyItemDir *KateFileTreeModel::findChildNode(const ProxyItemDir *parent, const QString &name) const { Q_ASSERT(parent != nullptr); Q_ASSERT(!name.isEmpty()); if (!parent->childCount()) { return nullptr; } const auto children = parent->children(); for (ProxyItem *item : children) { if (!item->flag(ProxyItem::Dir)) { continue; } if (item->display() == name) { return static_cast(item); } } return nullptr; } void KateFileTreeModel::insertItemInto(ProxyItemDir *root, ProxyItem *item) { Q_ASSERT(root != nullptr); Q_ASSERT(item != nullptr); QString tail = item->path(); tail.remove(0, root->path().length()); QStringList parts = tail.split(QLatin1Char('/'), QString::SkipEmptyParts); ProxyItemDir *ptr = root; QStringList current_parts; current_parts.append(root->path()); // seems this can be empty, see bug 286191 if (!parts.isEmpty()) { parts.pop_back(); } for (const QString &part : qAsConst(parts)) { current_parts.append(part); ProxyItemDir *find = findChildNode(ptr, part); if (!find) { const QString new_name = current_parts.join(QLatin1Char('/')); const QModelIndex parent_index = (ptr == m_root) ? QModelIndex() : createIndex(ptr->row(), 0, ptr); beginInsertRows(parent_index, ptr->childCount(), ptr->childCount()); ptr = new ProxyItemDir(new_name, ptr); endInsertRows(); } else { ptr = find; } } const QModelIndex parent_index = (ptr == m_root) ? QModelIndex() : createIndex(ptr->row(), 0, ptr); beginInsertRows(parent_index, ptr->childCount(), ptr->childCount()); ptr->addChild(item); endInsertRows(); } void KateFileTreeModel::handleInsert(ProxyItem *item) { Q_ASSERT(item != nullptr); if (m_listMode || item->flag(ProxyItem::Empty)) { beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(item); endInsertRows(); return; } // case (item.path > root.path) ProxyItemDir *root = findRootNode(item->path()); if (root) { insertItemInto(root, item); return; } // trim off trailing file and dir QString base = item->path().section(QLatin1Char('/'), 0, -2); // create new root ProxyItemDir *new_root = new ProxyItemDir(base); new_root->setHost(item->host()); // add new root to m_root beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(new_root); endInsertRows(); // same fix as in findRootNode, try to match a full dir, instead of a partial path base += QLatin1Char('/'); // try and merge existing roots with the new root node (new_root.path < root.path) const auto rootChildren = m_root->children(); for (ProxyItem *root : rootChildren) { if (root == new_root || !root->flag(ProxyItem::Dir)) { continue; } if (root->path().startsWith(base)) { beginRemoveRows(QModelIndex(), root->row(), root->row()); m_root->remChild(root); endRemoveRows(); // beginInsertRows(new_root_index, new_root->childCount(), new_root->childCount()); // this can't use new_root->addChild directly, or it'll potentially miss a bunch of subdirs insertItemInto(new_root, root); // endInsertRows(); } } // add item to new root // have to call begin/endInsertRows here, or the new item won't show up. const QModelIndex new_root_index = createIndex(new_root->row(), 0, new_root); beginInsertRows(new_root_index, new_root->childCount(), new_root->childCount()); new_root->addChild(item); endInsertRows(); handleDuplicitRootDisplay(new_root); } void KateFileTreeModel::handleDuplicitRootDisplay(ProxyItemDir *init) { QStack rootsToCheck; rootsToCheck.push(init); // make sure the roots don't match (recursively) while (!rootsToCheck.isEmpty()) { ProxyItemDir *check_root = rootsToCheck.pop(); if (check_root->parent() != m_root) { continue; } const auto rootChildren = m_root->children(); for (ProxyItem *root : rootChildren) { if (root == check_root || !root->flag(ProxyItem::Dir)) { continue; } if (check_root->display() == root->display()) { bool changed = false; bool check_root_removed = false; const QString rdir = root->path().section(QLatin1Char('/'), 0, -2); if (!rdir.isEmpty()) { beginRemoveRows(QModelIndex(), root->row(), root->row()); m_root->remChild(root); endRemoveRows(); ProxyItemDir *irdir = new ProxyItemDir(rdir); beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(irdir); endInsertRows(); insertItemInto(irdir, root); const auto children = m_root->children(); for (ProxyItem *node : children) { if (node == irdir || !root->flag(ProxyItem::Dir)) { continue; } const QString xy = rdir + QLatin1Char('/'); if (node->path().startsWith(xy)) { beginRemoveRows(QModelIndex(), node->row(), node->row()); // check_root_removed must be sticky check_root_removed = check_root_removed || (node == check_root); m_root->remChild(node); endRemoveRows(); insertItemInto(irdir, node); } } rootsToCheck.push(irdir); changed = true; } if (!check_root_removed) { const QString nrdir = check_root->path().section(QLatin1Char('/'), 0, -2); if (!nrdir.isEmpty()) { beginRemoveRows(QModelIndex(), check_root->row(), check_root->row()); m_root->remChild(check_root); endRemoveRows(); ProxyItemDir *irdir = new ProxyItemDir(nrdir); beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(irdir); endInsertRows(); insertItemInto(irdir, check_root); rootsToCheck.push(irdir); changed = true; } } if (changed) { break; // restart } } } // for root } } void KateFileTreeModel::handleNameChange(ProxyItem *item) { Q_ASSERT(item != nullptr); Q_ASSERT(item->parent()); updateItemPathAndHost(item); if (m_listMode) { const QModelIndex idx = createIndex(item->row(), 0, item); setupIcon(item); emit dataChanged(idx, idx); return; } // in either case (new/change) we want to remove the item from its parent ProxyItemDir *parent = item->parent(); const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent); beginRemoveRows(parent_index, item->row(), item->row()); parent->remChild(item); endRemoveRows(); handleEmptyParents(parent); // clear all but Empty flag if (item->flag(ProxyItem::Empty)) { item->setFlags(ProxyItem::Empty); } else { item->setFlags(ProxyItem::None); } setupIcon(item); handleInsert(item); } void KateFileTreeModel::updateItemPathAndHost(ProxyItem *item) const { const KTextEditor::Document *doc = item->doc(); Q_ASSERT(doc); // this method should not be called at directory items QString path = doc->url().path(); QString host; if (doc->url().isEmpty()) { path = doc->documentName(); item->setFlag(ProxyItem::Empty); } else { item->clearFlag(ProxyItem::Empty); host = doc->url().host(); if (!host.isEmpty()) { path = QStringLiteral("[%1]%2").arg(host, path); } } // for some reason we get useless name changes [should be fixed in 5.0] if (item->path() == path) { return; } item->setPath(path); item->setHost(host); } void KateFileTreeModel::setupIcon(ProxyItem *item) const { Q_ASSERT(item != nullptr); QString icon_name; if (item->flag(ProxyItem::Modified)) { icon_name = QStringLiteral("document-save"); } else { const QUrl url(item->path()); icon_name = QMimeDatabase().mimeTypeForFile(url.path(), QMimeDatabase::MatchExtension).iconName(); } QIcon icon = QIcon::fromTheme(icon_name); if (item->flag(ProxyItem::ModifiedExternally) || item->flag(ProxyItem::DeletedExternally)) { icon = KIconUtils::addOverlay(icon, QIcon(QLatin1String("emblem-important")), Qt::TopLeftCorner); } item->setIcon(icon); } void KateFileTreeModel::resetHistory() { QSet list = QSet::fromList(m_viewHistory); list += QSet::fromList(m_editHistory); m_viewHistory.clear(); m_editHistory.clear(); m_brushes.clear(); for (ProxyItem *item : qAsConst(list)) { QModelIndex idx = createIndex(item->row(), 0, item); dataChanged(idx, idx, QVector(1, Qt::BackgroundRole)); } } diff --git a/addons/filetree/katefiletreeplugin.cpp b/addons/filetree/katefiletreeplugin.cpp index 264c81040..056970bcf 100644 --- a/addons/filetree/katefiletreeplugin.cpp +++ b/addons/filetree/katefiletreeplugin.cpp @@ -1,457 +1,457 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ // BEGIN Includes #include "katefiletreeplugin.h" #include "katefiletree.h" +#include "katefiletreeconfigpage.h" #include "katefiletreemodel.h" #include "katefiletreeproxymodel.h" -#include "katefiletreeconfigpage.h" -#include #include +#include #include -#include #include -#include #include +#include +#include +#include #include #include -#include #include #include #include #include #include "katefiletreedebug.h" // END Includes K_PLUGIN_FACTORY_WITH_JSON(KateFileTreeFactory, "katefiletreeplugin.json", registerPlugin();) Q_LOGGING_CATEGORY(FILETREE, "kate-filetree", QtWarningMsg) // BEGIN KateFileTreePlugin KateFileTreePlugin::KateFileTreePlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { } KateFileTreePlugin::~KateFileTreePlugin() { m_settings.save(); } QObject *KateFileTreePlugin::createView(KTextEditor::MainWindow *mainWindow) { KateFileTreePluginView *view = new KateFileTreePluginView(mainWindow, this); connect(view, &KateFileTreePluginView::destroyed, this, &KateFileTreePlugin::viewDestroyed); m_views.append(view); return view; } void KateFileTreePlugin::viewDestroyed(QObject *view) { // do not access the view pointer, since it is partially destroyed already m_views.removeAll(static_cast(view)); } int KateFileTreePlugin::configPages() const { return 1; } KTextEditor::ConfigPage *KateFileTreePlugin::configPage(int number, QWidget *parent) { if (number != 0) { return nullptr; } KateFileTreeConfigPage *page = new KateFileTreeConfigPage(parent, this); return page; } const KateFileTreePluginSettings &KateFileTreePlugin::settings() { return m_settings; } void KateFileTreePlugin::applyConfig(bool shadingEnabled, const QColor &viewShade, const QColor &editShade, bool listMode, int sortRole, bool showFullPath) { // save to settings m_settings.setShadingEnabled(shadingEnabled); m_settings.setViewShade(viewShade); m_settings.setEditShade(editShade); m_settings.setListMode(listMode); m_settings.setSortRole(sortRole); m_settings.setShowFullPathOnRoots(showFullPath); m_settings.save(); // update views for (KateFileTreePluginView *view : qAsConst(m_views)) { view->setHasLocalPrefs(false); view->model()->setShadingEnabled(shadingEnabled); view->model()->setViewShade(viewShade); view->model()->setEditShade(editShade); view->setListMode(listMode); view->proxy()->setSortRole(sortRole); view->model()->setShowFullPathOnRoots(showFullPath); } } // END KateFileTreePlugin // BEGIN KateFileTreePluginView KateFileTreePluginView::KateFileTreePluginView(KTextEditor::MainWindow *mainWindow, KateFileTreePlugin *plug) : QObject(mainWindow) , m_loadingDocuments(false) , m_plug(plug) , m_mainWindow(mainWindow) { KXMLGUIClient::setComponentName(QStringLiteral("katefiletree"), i18n("Kate File Tree")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = mainWindow->createToolView(plug, QStringLiteral("kate_private_plugin_katefiletreeplugin"), KTextEditor::MainWindow::Left, QIcon::fromTheme(QStringLiteral("document-open")), i18n("Documents")); Q_ASSERT(m_toolView->layout()); m_toolView->layout()->setContentsMargins(0, 0, 0, 0); m_toolView->layout()->setSpacing(0); auto mainLayout = m_toolView->layout(); // create toolbar m_toolbar = new KToolBar(m_toolView); m_toolbar->setMovable(false); m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); m_toolbar->setContextMenuPolicy(Qt::NoContextMenu); // ensure reasonable icons sizes, like e.g. the quick-open and co. icons // the normal toolbar sizes are TOO large, e.g. for scaled stuff even more! const int iconSize = m_toolView->style()->pixelMetric(QStyle::PM_ButtonIconSize, nullptr, m_toolView); m_toolbar->setIconSize(QSize(iconSize, iconSize)); mainLayout->addWidget(m_toolbar); // create filetree m_fileTree = new KateFileTree(m_toolView); m_fileTree->setSortingEnabled(true); mainLayout->addWidget(m_fileTree); connect(m_fileTree, &KateFileTree::activateDocument, this, &KateFileTreePluginView::activateDocument); connect(m_fileTree, &KateFileTree::viewModeChanged, this, &KateFileTreePluginView::viewModeChanged); connect(m_fileTree, &KateFileTree::sortRoleChanged, this, &KateFileTreePluginView::sortRoleChanged); m_documentModel = new KateFileTreeModel(this); m_proxyModel = new KateFileTreeProxyModel(this); m_proxyModel->setSourceModel(m_documentModel); m_proxyModel->setDynamicSortFilter(true); m_documentModel->setShowFullPathOnRoots(m_plug->settings().showFullPathOnRoots()); m_documentModel->setShadingEnabled(m_plug->settings().shadingEnabled()); m_documentModel->setViewShade(m_plug->settings().viewShade()); m_documentModel->setEditShade(m_plug->settings().editShade()); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentWillBeDeleted, m_documentModel, &KateFileTreeModel::documentClosed); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentCreated, this, &KateFileTreePluginView::documentOpened); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentWillBeDeleted, this, &KateFileTreePluginView::documentClosed); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::aboutToCreateDocuments, this, &KateFileTreePluginView::slotAboutToCreateDocuments); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentsCreated, this, &KateFileTreePluginView::slotDocumentsCreated); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::aboutToDeleteDocuments, m_documentModel, &KateFileTreeModel::slotAboutToDeleteDocuments); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentsDeleted, m_documentModel, &KateFileTreeModel::slotDocumentsDeleted); connect(m_documentModel, &KateFileTreeModel::triggerViewChangeAfterNameChange, [=] { KateFileTreePluginView::viewChanged(); }); m_fileTree->setModel(m_proxyModel); m_fileTree->setDragEnabled(false); m_fileTree->setDragDropMode(QAbstractItemView::InternalMove); m_fileTree->setDropIndicatorShown(false); m_fileTree->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_fileTree->selectionModel(), &QItemSelectionModel::currentChanged, m_fileTree, &KateFileTree::slotCurrentChanged); connect(mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateFileTreePluginView::viewChanged); // // actions // setupActions(); mainWindow->guiFactory()->addClient(this); m_proxyModel->setSortRole(Qt::DisplayRole); m_proxyModel->sort(0, Qt::AscendingOrder); m_proxyModel->invalidate(); } KateFileTreePluginView::~KateFileTreePluginView() { m_mainWindow->guiFactory()->removeClient(this); // clean up tree and toolview delete m_fileTree->parentWidget(); // delete m_toolView; // and TreeModel delete m_documentModel; } void KateFileTreePluginView::setupActions() { auto aPrev = actionCollection()->addAction(QStringLiteral("filetree_prev_document")); aPrev->setText(i18n("Previous Document")); aPrev->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); actionCollection()->setDefaultShortcut(aPrev, Qt::ALT + Qt::Key_Up); connect(aPrev, &QAction::triggered, m_fileTree, &KateFileTree::slotDocumentPrev); auto aNext = actionCollection()->addAction(QStringLiteral("filetree_next_document")); aNext->setText(i18n("Next Document")); aNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); actionCollection()->setDefaultShortcut(aNext, Qt::ALT + Qt::Key_Down); connect(aNext, &QAction::triggered, m_fileTree, &KateFileTree::slotDocumentNext); auto aShowActive = actionCollection()->addAction(QStringLiteral("filetree_show_active_document")); aShowActive->setText(i18n("&Show Active")); aShowActive->setIcon(QIcon::fromTheme(QStringLiteral("folder-sync"))); connect(aShowActive, &QAction::triggered, this, &KateFileTreePluginView::showActiveDocument); auto aSave = actionCollection()->addAction(QStringLiteral("filetree_save"), this, SLOT(slotDocumentSave())); aSave->setText(i18n("Save Current Document")); aSave->setToolTip(i18n("Save the current document")); aSave->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); auto aSaveAs = actionCollection()->addAction(QStringLiteral("filetree_save_as"), this, SLOT(slotDocumentSaveAs())); aSaveAs->setText(i18n("Save Current Document As")); aSaveAs->setToolTip(i18n("Save current document under new name")); aSaveAs->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); /** * add new & open, if hosting application has it */ if (KXmlGuiWindow *parentClient = qobject_cast(m_mainWindow->window())) { bool newOrOpen = false; if (auto a = parentClient->action("file_new")) { m_toolbar->addAction(a); newOrOpen = true; } if (auto a = parentClient->action("file_open")) { m_toolbar->addAction(a); newOrOpen = true; } if (newOrOpen) { m_toolbar->addSeparator(); } } /** * add own actions */ m_toolbar->addAction(aPrev); m_toolbar->addAction(aNext); m_toolbar->addSeparator(); m_toolbar->addAction(aSave); m_toolbar->addAction(aSaveAs); } KateFileTreeModel *KateFileTreePluginView::model() { return m_documentModel; } KateFileTreeProxyModel *KateFileTreePluginView::proxy() { return m_proxyModel; } KateFileTree *KateFileTreePluginView::tree() { return m_fileTree; } void KateFileTreePluginView::documentOpened(KTextEditor::Document *doc) { if (m_loadingDocuments) { return; } m_documentModel->documentOpened(doc); m_proxyModel->invalidate(); } void KateFileTreePluginView::documentClosed(KTextEditor::Document *doc) { Q_UNUSED(doc); m_proxyModel->invalidate(); } void KateFileTreePluginView::viewChanged(KTextEditor::View *) { KTextEditor::View *view = m_mainWindow->activeView(); if (!view) { return; } KTextEditor::Document *doc = view->document(); QModelIndex index = m_proxyModel->docIndex(doc); QString display = m_proxyModel->data(index, Qt::DisplayRole).toString(); // update the model on which doc is active m_documentModel->documentActivated(doc); m_fileTree->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); m_fileTree->scrollTo(index); while (index != QModelIndex()) { m_fileTree->expand(index); index = index.parent(); } } void KateFileTreePluginView::setListMode(bool listMode) { if (listMode) { m_documentModel->setListMode(true); m_fileTree->setRootIsDecorated(false); } else { m_documentModel->setListMode(false); m_fileTree->setRootIsDecorated(true); } m_proxyModel->sort(0, Qt::AscendingOrder); m_proxyModel->invalidate(); } void KateFileTreePluginView::viewModeChanged(bool listMode) { setHasLocalPrefs(true); setListMode(listMode); } void KateFileTreePluginView::sortRoleChanged(int role) { setHasLocalPrefs(true); m_proxyModel->setSortRole(role); m_proxyModel->invalidate(); } void KateFileTreePluginView::activateDocument(KTextEditor::Document *doc) { m_mainWindow->activateView(doc); } void KateFileTreePluginView::showToolView() { m_mainWindow->showToolView(m_toolView); m_toolView->setFocus(); } void KateFileTreePluginView::hideToolView() { m_mainWindow->hideToolView(m_toolView); } void KateFileTreePluginView::showActiveDocument() { // hack? viewChanged(); // make the tool view show if it was hidden showToolView(); } bool KateFileTreePluginView::hasLocalPrefs() { return m_hasLocalPrefs; } void KateFileTreePluginView::setHasLocalPrefs(bool h) { m_hasLocalPrefs = h; } void KateFileTreePluginView::readSessionConfig(const KConfigGroup &g) { if (g.exists()) { m_hasLocalPrefs = true; } else { m_hasLocalPrefs = false; } // we chain to the global settings by using them as the defaults // here in the session view config loading. const KateFileTreePluginSettings &defaults = m_plug->settings(); bool listMode = g.readEntry("listMode", defaults.listMode()); setListMode(listMode); int sortRole = g.readEntry("sortRole", defaults.sortRole()); m_proxyModel->setSortRole(sortRole); } void KateFileTreePluginView::writeSessionConfig(KConfigGroup &g) { if (m_hasLocalPrefs) { g.writeEntry("listMode", QVariant(m_documentModel->listMode())); g.writeEntry("sortRole", int(m_proxyModel->sortRole())); } else { g.deleteEntry("listMode"); g.deleteEntry("sortRole"); } g.sync(); } void KateFileTreePluginView::slotAboutToCreateDocuments() { m_loadingDocuments = true; } void KateFileTreePluginView::slotDocumentsCreated(const QList &docs) { m_documentModel->documentsOpened(docs); m_loadingDocuments = false; viewChanged(); } void KateFileTreePluginView::slotDocumentSave() { if (auto view = m_mainWindow->activeView()) { view->document()->documentSave(); } } void KateFileTreePluginView::slotDocumentSaveAs() { if (auto view = m_mainWindow->activeView()) { view->document()->documentSaveAs(); } } // END KateFileTreePluginView #include "katefiletreeplugin.moc" diff --git a/addons/filetree/katefiletreeplugin.h b/addons/filetree/katefiletreeplugin.h index 2a3d307bf..3c81d1c2f 100644 --- a/addons/filetree/katefiletreeplugin.h +++ b/addons/filetree/katefiletreeplugin.h @@ -1,141 +1,141 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef KATE_FILETREE_PLUGIN_H #define KATE_FILETREE_PLUGIN_H #include #include +#include #include #include -#include #include #include #include "katefiletreepluginsettings.h" #include class KToolBar; class KateFileTree; class KateFileTreeModel; class KateFileTreeProxyModel; class KateFileTreeConfigPage; class KateFileTreePluginView; class KateFileTreePlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit KateFileTreePlugin(QObject *parent = nullptr, const QList & = QList()); ~KateFileTreePlugin() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; int configPages() const override; KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; const KateFileTreePluginSettings &settings(); void applyConfig(bool shadingEnabled, const QColor &viewShade, const QColor &editShade, bool listMode, int sortRole, bool showFulPath); public Q_SLOTS: void viewDestroyed(QObject *view); private: QList m_views; KateFileTreeConfigPage *m_confPage; KateFileTreePluginSettings m_settings; }; class KateFileTreePluginView : public QObject, public KXMLGUIClient, public KTextEditor::SessionConfigInterface { Q_OBJECT Q_INTERFACES(KTextEditor::SessionConfigInterface) public: /** * Constructor. */ KateFileTreePluginView(KTextEditor::MainWindow *mainWindow, KateFileTreePlugin *plug); /** * Virtual destructor. */ ~KateFileTreePluginView() override; void readSessionConfig(const KConfigGroup &config) override; void writeSessionConfig(KConfigGroup &config) override; /** * The file tree model. * @return the file tree model */ KateFileTreeModel *model(); /** * The file tree proxy model. * @return the file tree proxy model */ KateFileTreeProxyModel *proxy(); /** * The file tree. * @return the file tree */ KateFileTree *tree(); void setListMode(bool listMode); bool hasLocalPrefs(); void setHasLocalPrefs(bool); protected: void setupActions(); private: QWidget *m_toolView; KToolBar *m_toolbar; KateFileTree *m_fileTree; KateFileTreeProxyModel *m_proxyModel; KateFileTreeModel *m_documentModel; bool m_hasLocalPrefs; bool m_loadingDocuments; KateFileTreePlugin *m_plug; KTextEditor::MainWindow *m_mainWindow; private Q_SLOTS: void showToolView(); void hideToolView(); void showActiveDocument(); void activateDocument(KTextEditor::Document *); void viewChanged(KTextEditor::View * = nullptr); void documentOpened(KTextEditor::Document *); void documentClosed(KTextEditor::Document *); void viewModeChanged(bool); void sortRoleChanged(int); void slotAboutToCreateDocuments(); void slotDocumentsCreated(const QList &); void slotDocumentSave(); void slotDocumentSaveAs(); }; #endif // KATE_FILETREE_PLUGIN_H diff --git a/addons/filetree/katefiletreepluginsettings.cpp b/addons/filetree/katefiletreepluginsettings.cpp index 7061571a0..f851bf319 100644 --- a/addons/filetree/katefiletreepluginsettings.cpp +++ b/addons/filetree/katefiletreepluginsettings.cpp @@ -1,113 +1,113 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "katefiletreepluginsettings.h" +#include "katefiletreedebug.h" #include #include -#include "katefiletreedebug.h" KateFileTreePluginSettings::KateFileTreePluginSettings() : m_group(KSharedConfig::openConfig(), "filetree") { KColorScheme colors(QPalette::Active); QColor bg = colors.background().color(); QColor viewShade = KColorUtils::tint(bg, colors.foreground(KColorScheme::VisitedText).color(), 0.5); QColor editShade = KColorUtils::tint(bg, colors.foreground(KColorScheme::ActiveText).color(), 0.5); m_shadingEnabled = m_group.readEntry("shadingEnabled", true); m_viewShade = m_group.readEntry("viewShade", viewShade); m_editShade = m_group.readEntry("editShade", editShade); m_listMode = m_group.readEntry("listMode", false); m_sortRole = m_group.readEntry("sortRole", int(Qt::DisplayRole)); m_showFullPathOnRoots = m_group.readEntry("showFullPathOnRoots", false); } void KateFileTreePluginSettings::save() { m_group.writeEntry("shadingEnabled", m_shadingEnabled); m_group.writeEntry("viewShade", m_viewShade); m_group.writeEntry("editShade", m_editShade); m_group.writeEntry("listMode", m_listMode); m_group.writeEntry("sortRole", m_sortRole); m_group.writeEntry("showFullPathOnRoots", m_showFullPathOnRoots); m_group.sync(); } bool KateFileTreePluginSettings::shadingEnabled() const { return m_shadingEnabled; } void KateFileTreePluginSettings::setShadingEnabled(bool shadingEnabled) { m_shadingEnabled = shadingEnabled; } const QColor &KateFileTreePluginSettings::viewShade() const { return m_viewShade; } void KateFileTreePluginSettings::setViewShade(const QColor &viewShade) { m_viewShade = viewShade; } const QColor &KateFileTreePluginSettings::editShade() const { return m_editShade; } void KateFileTreePluginSettings::setEditShade(const QColor &editShade) { m_editShade = editShade; } bool KateFileTreePluginSettings::listMode() const { return m_listMode; } void KateFileTreePluginSettings::setListMode(bool listMode) { m_listMode = listMode; } int KateFileTreePluginSettings::sortRole() const { return m_sortRole; } void KateFileTreePluginSettings::setSortRole(int sortRole) { m_sortRole = sortRole; } bool KateFileTreePluginSettings::showFullPathOnRoots() const { return m_showFullPathOnRoots; } void KateFileTreePluginSettings::setShowFullPathOnRoots(bool s) { m_showFullPathOnRoots = s; } diff --git a/addons/filetree/katefiletreeproxymodel.cpp b/addons/filetree/katefiletreeproxymodel.cpp index 1ed3785d8..1237a35f2 100644 --- a/addons/filetree/katefiletreeproxymodel.cpp +++ b/addons/filetree/katefiletreeproxymodel.cpp @@ -1,82 +1,82 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "katefiletreeproxymodel.h" -#include "katefiletreemodel.h" #include "katefiletreedebug.h" +#include "katefiletreemodel.h" #include #include KateFileTreeProxyModel::KateFileTreeProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { } void KateFileTreeProxyModel::setSourceModel(QAbstractItemModel *model) { Q_ASSERT(qobject_cast(model)); // we don't really work with anything else QSortFilterProxyModel::setSourceModel(model); } bool KateFileTreeProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { const KateFileTreeModel *model = static_cast(sourceModel()); const bool left_isdir = model->isDir(left); const bool right_isdir = model->isDir(right); // in tree mode, there will be parent nodes, we want to put those first if (left_isdir != right_isdir) { return ((left_isdir - right_isdir)) > 0; } QCollator collate; collate.setCaseSensitivity(Qt::CaseInsensitive); collate.setNumericMode(true); switch (sortRole()) { case Qt::DisplayRole: { const QString left_name = model->data(left).toString(); const QString right_name = model->data(right).toString(); return collate.compare(left_name, right_name) < 0; } case KateFileTreeModel::PathRole: { const QString left_name = model->data(left, KateFileTreeModel::PathRole).toString(); const QString right_name = model->data(right, KateFileTreeModel::PathRole).toString(); return collate.compare(left_name, right_name) < 0; } case KateFileTreeModel::OpeningOrderRole: return (left.row() - right.row()) < 0; } return false; } QModelIndex KateFileTreeProxyModel::docIndex(const KTextEditor::Document *doc) const { return mapFromSource(static_cast(sourceModel())->docIndex(doc)); } bool KateFileTreeProxyModel::isDir(const QModelIndex &index) const { return static_cast(sourceModel())->isDir(mapToSource(index)); } diff --git a/addons/gdbplugin/advanced_settings.h b/addons/gdbplugin/advanced_settings.h index 5446e98a3..3c0ef9c25 100644 --- a/addons/gdbplugin/advanced_settings.h +++ b/addons/gdbplugin/advanced_settings.h @@ -1,57 +1,57 @@ // Description: Advanced settings dialog for gdb // // // Copyright (c) 2012 KÃ¥re Särs // // 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. #ifndef ADVANCED_SETTINGS_H #define ADVANCED_SETTINGS_H #include "ui_advanced_settings.h" -#include #include +#include class AdvancedGDBSettings : public QDialog, public Ui::AdvancedGDBSettings { Q_OBJECT public: enum CustomStringOrder { GDBIndex = 0, LocalRemoteIndex, RemoteBaudIndex, SoAbsoluteIndex, SoRelativeIndex, SrcPathsIndex, CustomStartIndex }; AdvancedGDBSettings(QWidget *parent = nullptr); ~AdvancedGDBSettings() override; const QStringList configs() const; void setConfigs(const QStringList &cfgs); private: void setComboText(QComboBox *combo, const QString &str); private Q_SLOTS: void slotBrowseGDB(); void slotSetSoPrefix(); void slotAddSoPath(); void slotDelSoPath(); void slotAddSrcPath(); void slotDelSrcPath(); void slotLocalRemoteChanged(); }; #endif diff --git a/addons/gdbplugin/configview.cpp b/addons/gdbplugin/configview.cpp index 5b1d7e1e4..ca7a7db1d 100644 --- a/addons/gdbplugin/configview.cpp +++ b/addons/gdbplugin/configview.cpp @@ -1,521 +1,521 @@ // // configview.cpp // // Description: View for configuring the set of targets to be used with the debugger // // // Copyright (c) 2010 Ian Wakeling // Copyright (c) 2012 KÃ¥re Särs // // 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. #include "configview.h" #include #include +#include #include -#include #include -#include +#include -#include #include +#include #include #include #ifdef WIN32 static const QLatin1Char pathSeparator(';'); #else static const QLatin1Char pathSeparator(':'); #endif ConfigView::ConfigView(QWidget *parent, KTextEditor::MainWindow *mainWin) : QWidget(parent) , m_mainWindow(mainWin) { m_targetCombo = new QComboBox(); m_targetCombo->setEditable(true); // don't let Qt insert items when the user edits; new targets are only // added when the user explicitly says so m_targetCombo->setInsertPolicy(QComboBox::NoInsert); m_targetCombo->setDuplicatesEnabled(true); m_addTarget = new QToolButton(); m_addTarget->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); m_addTarget->setToolTip(i18n("Add new target")); m_copyTarget = new QToolButton(); m_copyTarget->setIcon(QIcon::fromTheme(QStringLiteral("document-copy"))); m_copyTarget->setToolTip(i18n("Copy target")); m_deleteTarget = new QToolButton(); m_deleteTarget->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); m_deleteTarget->setToolTip(i18n("Delete target")); m_line = new QFrame(this); m_line->setFrameShadow(QFrame::Sunken); m_execLabel = new QLabel(i18n("Executable:")); m_execLabel->setBuddy(m_targetCombo); m_executable = new QLineEdit(); QCompleter *completer1 = new QCompleter(this); completer1->setModel(new QDirModel(QStringList(), QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name, this)); m_executable->setCompleter(completer1); m_executable->setClearButtonEnabled(true); m_browseExe = new QToolButton(this); m_browseExe->setIcon(QIcon::fromTheme(QStringLiteral("application-x-executable"))); m_workingDirectory = new QLineEdit(); QCompleter *completer2 = new QCompleter(this); completer2->setModel(new QDirModel(completer2)); m_workingDirectory->setCompleter(completer2); m_workingDirectory->setClearButtonEnabled(true); m_workDirLabel = new QLabel(i18n("Working Directory:")); m_workDirLabel->setBuddy(m_workingDirectory); m_browseDir = new QToolButton(this); m_browseDir->setIcon(QIcon::fromTheme(QStringLiteral("inode-directory"))); m_arguments = new QLineEdit(); m_arguments->setClearButtonEnabled(true); m_argumentsLabel = new QLabel(i18nc("Program argument list", "Arguments:")); m_argumentsLabel->setBuddy(m_arguments); m_takeFocus = new QCheckBox(i18nc("Checkbox to for keeping focus on the command line", "Keep focus")); m_takeFocus->setToolTip(i18n("Keep the focus on the command line")); m_redirectTerminal = new QCheckBox(i18n("Redirect IO")); m_redirectTerminal->setToolTip(i18n("Redirect the debugged programs IO to a separate tab")); m_advancedSettings = new QPushButton(i18n("Advanced Settings")); m_checBoxLayout = nullptr; // first false then true to make sure a layout is set m_useBottomLayout = false; resizeEvent(nullptr); m_useBottomLayout = true; resizeEvent(nullptr); m_advanced = new AdvancedGDBSettings(this); m_advanced->hide(); connect(m_targetCombo, &QComboBox::editTextChanged, this, &ConfigView::slotTargetEdited); connect(m_targetCombo, static_cast(&QComboBox::currentIndexChanged), this, &ConfigView::slotTargetSelected); connect(m_addTarget, &QToolButton::clicked, this, &ConfigView::slotAddTarget); connect(m_copyTarget, &QToolButton::clicked, this, &ConfigView::slotCopyTarget); connect(m_deleteTarget, &QToolButton::clicked, this, &ConfigView::slotDeleteTarget); connect(m_browseExe, &QToolButton::clicked, this, &ConfigView::slotBrowseExec); connect(m_browseDir, &QToolButton::clicked, this, &ConfigView::slotBrowseDir); connect(m_redirectTerminal, &QCheckBox::toggled, this, &ConfigView::showIO); connect(m_advancedSettings, &QPushButton::clicked, this, &ConfigView::slotAdvancedClicked); } ConfigView::~ConfigView() { } void ConfigView::registerActions(KActionCollection *actionCollection) { m_targetSelectAction = actionCollection->add(QStringLiteral("targets")); m_targetSelectAction->setText(i18n("Targets")); connect(m_targetSelectAction, static_cast(&KSelectAction::triggered), this, &ConfigView::slotTargetSelected); } void ConfigView::readConfig(const KConfigGroup &group) { m_targetCombo->clear(); int version = group.readEntry(QStringLiteral("version"), 4); int targetCount = group.readEntry(QStringLiteral("targetCount"), 1); int lastTarget = group.readEntry(QStringLiteral("lastTarget"), 0); QString targetKey(QStringLiteral("target_%1")); QStringList targetConfStrs; for (int i = 0; i < targetCount; i++) { targetConfStrs = group.readEntry(targetKey.arg(i), QStringList()); if (targetConfStrs.count() == 0) continue; if ((version == 1) && (targetConfStrs.count() == 3)) { // valid old style config, translate it now; note the // reordering happening here! QStringList temp; temp << targetConfStrs[2]; temp << targetConfStrs[1]; targetConfStrs = temp; } if (version < 4) { targetConfStrs.prepend(targetConfStrs[0].right(15)); } if (targetConfStrs.count() > NameIndex) { m_targetCombo->addItem(targetConfStrs[NameIndex], targetConfStrs); } } if (version < 4) { // all targets now have only one argument string int argListsCount = group.readEntry(QStringLiteral("argsCount"), 0); QString argsKey(QStringLiteral("args_%1")); QString targetName(QStringLiteral("%1<%2>")); QString argStr; int count = m_targetCombo->count(); for (int i = 0; i < argListsCount; i++) { argStr = group.readEntry(argsKey.arg(i), QString()); for (int j = 0; j < count; j++) { targetConfStrs = m_targetCombo->itemData(j).toStringList(); if (i > 0) { // copy the firsts and change the arguments targetConfStrs[0] = targetName.arg(targetConfStrs[0]).arg(i + 1); if (targetConfStrs.count() > 3) targetConfStrs[3] = argStr; m_targetCombo->addItem(targetConfStrs[0], targetConfStrs); } } } } // make sure there is at least one item. if (m_targetCombo->count() == 0) { slotAddTarget(); } QStringList targetNames; for (int i = 0; i < m_targetCombo->count(); i++) { targetNames << m_targetCombo->itemText(i); } m_targetSelectAction->setItems(targetNames); if (lastTarget < 0 || lastTarget >= m_targetCombo->count()) lastTarget = 0; m_targetCombo->setCurrentIndex(lastTarget); m_takeFocus->setChecked(group.readEntry("alwaysFocusOnInput", false)); m_redirectTerminal->setChecked(group.readEntry("redirectTerminal", false)); } void ConfigView::writeConfig(KConfigGroup &group) { // make sure the data is up to date before writing saveCurrentToIndex(m_currentTarget); group.writeEntry("version", 4); QString targetKey(QStringLiteral("target_%1")); QStringList targetConfStrs; group.writeEntry("targetCount", m_targetCombo->count()); group.writeEntry("lastTarget", m_targetCombo->currentIndex()); for (int i = 0; i < m_targetCombo->count(); i++) { targetConfStrs = m_targetCombo->itemData(i).toStringList(); group.writeEntry(targetKey.arg(i), targetConfStrs); } group.writeEntry("alwaysFocusOnInput", m_takeFocus->isChecked()); group.writeEntry("redirectTerminal", m_redirectTerminal->isChecked()); } const GDBTargetConf ConfigView::currentTarget() const { GDBTargetConf cfg; cfg.executable = m_executable->text(); cfg.workDir = m_workingDirectory->text(); cfg.arguments = m_arguments->text(); cfg.customInit = m_advanced->configs(); // Note: AdvancedGDBSettings::GDBIndex == 0 if ((cfg.customInit.size() >= 0) && !cfg.customInit[0].isEmpty()) { cfg.gdbCmd = cfg.customInit[0]; cfg.customInit.removeFirst(); } else { cfg.gdbCmd = QStringLiteral("gdb"); } // remove empty strings in the customInit int i = cfg.customInit.size() - 1; while (i >= 0) { if (cfg.customInit[i].isEmpty()) { cfg.customInit.removeAt(i); } else if (cfg.customInit[i].startsWith(QLatin1String("set directories "))) { QString paths = cfg.customInit[i]; paths.remove(QStringLiteral("set directories ")); cfg.srcPaths = paths.split(pathSeparator, QString::SkipEmptyParts); } i--; } return cfg; } bool ConfigView::takeFocusAlways() const { return m_takeFocus->isChecked(); } bool ConfigView::showIOTab() const { return m_redirectTerminal->isChecked(); } void ConfigView::slotTargetEdited(const QString &newText) { int cursorPosition = m_targetCombo->lineEdit()->cursorPosition(); m_targetCombo->setItemText(m_targetCombo->currentIndex(), newText); m_targetCombo->lineEdit()->setCursorPosition(cursorPosition); // rebuild the target menu QStringList targets; for (int i = 0; i < m_targetCombo->count(); ++i) { targets.append(m_targetCombo->itemText(i)); } m_targetSelectAction->setItems(targets); m_targetSelectAction->setCurrentItem(m_targetCombo->currentIndex()); } void ConfigView::slotTargetSelected(int index) { if ((index < 0) || (index >= m_targetCombo->count())) { return; } if ((m_currentTarget > 0) && (m_currentTarget < m_targetCombo->count())) { saveCurrentToIndex(m_currentTarget); } loadFromIndex(index); m_currentTarget = index; setAdvancedOptions(); // Keep combo box and menu in sync m_targetSelectAction->setCurrentItem(index); } void ConfigView::slotAddTarget() { QStringList targetConfStrs; targetConfStrs << i18n("Target %1", m_targetCombo->count() + 1); targetConfStrs << QString(); targetConfStrs << QString(); targetConfStrs << QString(); m_targetCombo->addItem(targetConfStrs[NameIndex], targetConfStrs); m_targetCombo->setCurrentIndex(m_targetCombo->count() - 1); } void ConfigView::slotCopyTarget() { QStringList tmp = m_targetCombo->itemData(m_targetCombo->currentIndex()).toStringList(); if (tmp.empty()) { slotAddTarget(); return; } tmp[NameIndex] = i18n("Target %1", m_targetCombo->count() + 1); m_targetCombo->addItem(tmp[NameIndex], tmp); m_targetCombo->setCurrentIndex(m_targetCombo->count() - 1); } void ConfigView::slotDeleteTarget() { m_targetCombo->blockSignals(true); int currentIndex = m_targetCombo->currentIndex(); m_targetCombo->removeItem(currentIndex); if (m_targetCombo->count() == 0) { slotAddTarget(); } loadFromIndex(m_targetCombo->currentIndex()); m_targetCombo->blockSignals(false); } void ConfigView::resizeEvent(QResizeEvent *) { if (m_useBottomLayout && size().height() > size().width()) { // Set layout for the side delete m_checBoxLayout; m_checBoxLayout = nullptr; delete layout(); QGridLayout *layout = new QGridLayout(this); layout->addWidget(m_targetCombo, 0, 0); layout->addWidget(m_addTarget, 0, 1); layout->addWidget(m_copyTarget, 0, 2); layout->addWidget(m_deleteTarget, 0, 3); m_line->setFrameShape(QFrame::HLine); layout->addWidget(m_line, 1, 0, 1, 4); layout->addWidget(m_execLabel, 3, 0, Qt::AlignLeft); layout->addWidget(m_executable, 4, 0, 1, 3); layout->addWidget(m_browseExe, 4, 3); layout->addWidget(m_workDirLabel, 5, 0, Qt::AlignLeft); layout->addWidget(m_workingDirectory, 6, 0, 1, 3); layout->addWidget(m_browseDir, 6, 3); layout->addWidget(m_argumentsLabel, 7, 0, Qt::AlignLeft); layout->addWidget(m_arguments, 8, 0, 1, 4); layout->addWidget(m_takeFocus, 9, 0, 1, 4); layout->addWidget(m_redirectTerminal, 10, 0, 1, 4); layout->addWidget(m_advancedSettings, 11, 0, 1, 4); layout->addItem(new QSpacerItem(1, 1), 12, 0); layout->setColumnStretch(0, 1); layout->setRowStretch(12, 1); m_useBottomLayout = false; } else if (!m_useBottomLayout && (size().height() < size().width())) { // Set layout for the bottom delete m_checBoxLayout; delete layout(); m_checBoxLayout = new QHBoxLayout(); m_checBoxLayout->addWidget(m_takeFocus, 10); m_checBoxLayout->addWidget(m_redirectTerminal, 10); m_checBoxLayout->addWidget(m_advancedSettings, 0); QGridLayout *layout = new QGridLayout(this); layout->addWidget(m_targetCombo, 0, 0, 1, 3); layout->addWidget(m_addTarget, 1, 0); layout->addWidget(m_copyTarget, 1, 1); layout->addWidget(m_deleteTarget, 1, 2); m_line->setFrameShape(QFrame::VLine); layout->addWidget(m_line, 0, 3, 4, 1); layout->addWidget(m_execLabel, 0, 5, Qt::AlignRight); layout->addWidget(m_executable, 0, 6); layout->addWidget(m_browseExe, 0, 7); layout->addWidget(m_workDirLabel, 1, 5, Qt::AlignRight); layout->addWidget(m_workingDirectory, 1, 6); layout->addWidget(m_browseDir, 1, 7); layout->addWidget(m_argumentsLabel, 2, 5, Qt::AlignRight); layout->addWidget(m_arguments, 2, 6, 1, 2); layout->addLayout(m_checBoxLayout, 3, 5, 1, 3); layout->addItem(new QSpacerItem(1, 1), 4, 0); layout->setColumnStretch(6, 100); layout->setRowStretch(4, 100); m_useBottomLayout = true; } } void ConfigView::setAdvancedOptions() { QStringList tmp = m_targetCombo->itemData(m_targetCombo->currentIndex()).toStringList(); // make sure we have enough strings; while (tmp.count() < CustomStartIndex) tmp << QString(); if (tmp[GDBIndex].isEmpty()) { tmp[GDBIndex] = QStringLiteral("gdb"); } // Remove the strings that are not part of the advanced settings for (int i = 0; i < GDBIndex; i++) tmp.takeFirst(); m_advanced->setConfigs(tmp); } void ConfigView::slotAdvancedClicked() { setAdvancedOptions(); QStringList newList = m_targetCombo->itemData(m_targetCombo->currentIndex()).toStringList(); // make sure we have enough strings; while (newList.count() < GDBIndex) newList << QString(); // Remove old advanced settings while (newList.count() > GDBIndex) newList.takeLast(); if (m_advanced->exec() == QDialog::Accepted) { // save the new values newList << m_advanced->configs(); m_targetCombo->setItemData(m_targetCombo->currentIndex(), newList); } } void ConfigView::slotBrowseExec() { QString exe = m_executable->text(); if (m_executable->text().isEmpty()) { // try current document dir KTextEditor::View *view = m_mainWindow->activeView(); if (view != nullptr) { exe = view->document()->url().toLocalFile(); } } m_executable->setText(QFileDialog::getOpenFileName((QWidget *)nullptr, QString(), exe, QStringLiteral("application/x-executable"))); } void ConfigView::slotBrowseDir() { QString dir = m_workingDirectory->text(); if (m_workingDirectory->text().isEmpty()) { // try current document dir KTextEditor::View *view = m_mainWindow->activeView(); if (view != nullptr) { dir = view->document()->url().toLocalFile(); } } m_workingDirectory->setText(QFileDialog::getExistingDirectory(this, QString(), dir)); } void ConfigView::saveCurrentToIndex(int index) { if ((index < 0) || (index >= m_targetCombo->count())) { return; } QStringList tmp = m_targetCombo->itemData(index).toStringList(); // make sure we have enough strings. The custom init strings are set in slotAdvancedClicked(). while (tmp.count() < CustomStartIndex) tmp << QString(); tmp[NameIndex] = m_targetCombo->itemText(index); tmp[ExecIndex] = m_executable->text(); tmp[WorkDirIndex] = m_workingDirectory->text(); tmp[ArgsIndex] = m_arguments->text(); m_targetCombo->setItemData(index, tmp); } void ConfigView::loadFromIndex(int index) { if ((index < 0) || (index >= m_targetCombo->count())) { return; } QStringList tmp = m_targetCombo->itemData(index).toStringList(); // make sure we have enough strings. The custom init strings are set in slotAdvancedClicked(). while (tmp.count() < CustomStartIndex) tmp << QString(); m_executable->setText(tmp[ExecIndex]); m_workingDirectory->setText(tmp[WorkDirIndex]); m_arguments->setText(tmp[ArgsIndex]); } diff --git a/addons/gdbplugin/configview.h b/addons/gdbplugin/configview.h index 2054d8f9c..820f50547 100644 --- a/addons/gdbplugin/configview.h +++ b/addons/gdbplugin/configview.h @@ -1,125 +1,125 @@ // // configview.h // // Description: View for configuring the set of targets to be used with the debugger // // // Copyright (c) 2010 Ian Wakeling // Copyright (c) 2012 KÃ¥re Särs // // 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. #ifndef CONFIGVIEW_H #define CONFIGVIEW_H #include "advanced_settings.h" -#include +#include +#include +#include #include #include -#include -#include -#include -#include #include +#include +#include #include #include -#include #include +#include #include struct GDBTargetConf { QString executable; QString workDir; QString arguments; QString gdbCmd; QStringList customInit; QStringList srcPaths; }; class ConfigView : public QWidget { Q_OBJECT public: enum TargetStringOrder { NameIndex = 0, ExecIndex, WorkDirIndex, ArgsIndex, GDBIndex, CustomStartIndex }; ConfigView(QWidget *parent, KTextEditor::MainWindow *mainWin); ~ConfigView() override; public: void registerActions(KActionCollection *actionCollection); void readConfig(const KConfigGroup &config); void writeConfig(KConfigGroup &config); const GDBTargetConf currentTarget() const; bool takeFocusAlways() const; bool showIOTab() const; Q_SIGNALS: void showIO(bool show); private Q_SLOTS: void slotTargetEdited(const QString &newText); void slotTargetSelected(int index); void slotAddTarget(); void slotCopyTarget(); void slotDeleteTarget(); void slotAdvancedClicked(); void slotBrowseExec(); void slotBrowseDir(); protected: void resizeEvent(QResizeEvent *event) override; private: void saveCurrentToIndex(int index); void loadFromIndex(int index); void setAdvancedOptions(); private: KTextEditor::MainWindow *m_mainWindow; QComboBox *m_targetCombo; int m_currentTarget; QToolButton *m_addTarget; QToolButton *m_copyTarget; QToolButton *m_deleteTarget; QFrame *m_line; QLineEdit *m_executable; QToolButton *m_browseExe; QLineEdit *m_workingDirectory; QToolButton *m_browseDir; QLineEdit *m_arguments; QCheckBox *m_takeFocus; QCheckBox *m_redirectTerminal; QPushButton *m_advancedSettings; QBoxLayout *m_checBoxLayout; bool m_useBottomLayout; QLabel *m_execLabel; QLabel *m_workDirLabel; QLabel *m_argumentsLabel; KSelectAction *m_targetSelectAction; AdvancedGDBSettings *m_advanced; }; #endif diff --git a/addons/gdbplugin/debugview.cpp b/addons/gdbplugin/debugview.cpp index 729f34a76..463a276cf 100644 --- a/addons/gdbplugin/debugview.cpp +++ b/addons/gdbplugin/debugview.cpp @@ -1,600 +1,600 @@ // // debugview.cpp // // Description: Manages the interaction with GDB // // // Copyright (c) 2008-2010 Ian Wakeling // Copyright (c) 2011 KÃ¥re Särs // // 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. #include "debugview.h" -#include #include +#include #include +#include #include #include -#include #include #include static const QString PromptStr = QStringLiteral("(prompt)"); DebugView::DebugView(QObject *parent) : QObject(parent) , m_debugProcess(nullptr) , m_state(none) , m_subState(normal) , m_debugLocationChanged(true) , m_queryLocals(false) { } DebugView::~DebugView() { if (m_debugProcess.state() != QProcess::NotRunning) { m_debugProcess.kill(); m_debugProcess.blockSignals(true); m_debugProcess.waitForFinished(); } } void DebugView::runDebugger(const GDBTargetConf &conf, const QStringList &ioFifos) { if (conf.executable.isEmpty()) { return; } m_targetConf = conf; if (ioFifos.size() == 3) { m_ioPipeString = QStringLiteral("< %1 1> %2 2> %3").arg(ioFifos[0]).arg(ioFifos[1]).arg(ioFifos[2]); } if (m_state == none) { m_outBuffer.clear(); m_errBuffer.clear(); m_errorList.clear(); // create a process to control GDB m_debugProcess.setWorkingDirectory(m_targetConf.workDir); connect(&m_debugProcess, static_cast(&QProcess::errorOccurred), this, &DebugView::slotError); connect(&m_debugProcess, &QProcess::readyReadStandardError, this, &DebugView::slotReadDebugStdErr); connect(&m_debugProcess, &QProcess::readyReadStandardOutput, this, &DebugView::slotReadDebugStdOut); connect(&m_debugProcess, static_cast(&QProcess::finished), this, &DebugView::slotDebugFinished); m_debugProcess.start(m_targetConf.gdbCmd); m_nextCommands << QStringLiteral("set pagination off"); m_state = ready; } else { // On startup the gdb prompt will trigger the "nextCommands", // here we have to trigger it manually. QTimer::singleShot(0, this, &DebugView::issueNextCommand); } m_nextCommands << QStringLiteral("file %1").arg(m_targetConf.executable); m_nextCommands << QStringLiteral("set args %1 %2").arg(m_targetConf.arguments).arg(m_ioPipeString); m_nextCommands << QStringLiteral("set inferior-tty /dev/null"); m_nextCommands << m_targetConf.customInit; m_nextCommands << QStringLiteral("(Q) info breakpoints"); } bool DebugView::debuggerRunning() const { return (m_state != none); } bool DebugView::debuggerBusy() const { return (m_state == executingCmd); } bool DebugView::hasBreakpoint(const QUrl &url, int line) { for (const auto &breakpoint : qAsConst(m_breakPointList)) { if ((url == breakpoint.file) && (line == breakpoint.line)) { return true; } } return false; } void DebugView::toggleBreakpoint(QUrl const &url, int line) { if (m_state == ready) { QString cmd; if (hasBreakpoint(url, line)) { cmd = QStringLiteral("clear %1:%2").arg(url.path()).arg(line); } else { cmd = QStringLiteral("break %1:%2").arg(url.path()).arg(line); } issueCommand(cmd); } } void DebugView::slotError() { KMessageBox::sorry(nullptr, i18n("Could not start debugger process")); } void DebugView::slotReadDebugStdOut() { m_outBuffer += QString::fromLocal8Bit(m_debugProcess.readAllStandardOutput().data()); int end = 0; // handle one line at a time do { end = m_outBuffer.indexOf(QLatin1Char('\n')); if (end < 0) break; processLine(m_outBuffer.mid(0, end)); m_outBuffer.remove(0, end + 1); } while (1); if (m_outBuffer == QLatin1String("(gdb) ") || m_outBuffer == QLatin1String(">")) { m_outBuffer.clear(); processLine(PromptStr); } } void DebugView::slotReadDebugStdErr() { m_errBuffer += QString::fromLocal8Bit(m_debugProcess.readAllStandardError().data()); int end = 0; // add whole lines at a time to the error list do { end = m_errBuffer.indexOf(QLatin1Char('\n')); if (end < 0) break; m_errorList << m_errBuffer.mid(0, end); m_errBuffer.remove(0, end + 1); } while (1); processErrors(); } void DebugView::slotDebugFinished(int /*exitCode*/, QProcess::ExitStatus status) { if (status != QProcess::NormalExit) { emit outputText(i18n("*** gdb exited normally ***") + QLatin1Char('\n')); } m_state = none; emit readyForInput(false); // remove all old breakpoints BreakPoint bPoint; while (!m_breakPointList.empty()) { bPoint = m_breakPointList.takeFirst(); emit breakPointCleared(bPoint.file, bPoint.line - 1); } emit gdbEnded(); } void DebugView::movePC(QUrl const &url, int line) { if (m_state == ready) { QString cmd = QStringLiteral("tbreak %1:%2").arg(url.path()).arg(line); m_nextCommands << QStringLiteral("jump %1:%2").arg(url.path()).arg(line); issueCommand(cmd); } } void DebugView::runToCursor(QUrl const &url, int line) { if (m_state == ready) { QString cmd = QStringLiteral("tbreak %1:%2").arg(url.path()).arg(line); m_nextCommands << QStringLiteral("continue"); issueCommand(cmd); } } void DebugView::slotInterrupt() { if (m_state == executingCmd) { m_debugLocationChanged = true; } int pid = m_debugProcess.pid(); if (pid != 0) { ::kill(pid, SIGINT); } } void DebugView::slotKill() { if (m_state != ready) { slotInterrupt(); m_state = ready; } issueCommand(QStringLiteral("kill")); } void DebugView::slotReRun() { slotKill(); m_nextCommands << QStringLiteral("file %1").arg(m_targetConf.executable); m_nextCommands << QStringLiteral("set args %1 %2").arg(m_targetConf.arguments).arg(m_ioPipeString); m_nextCommands << QStringLiteral("set inferior-tty /dev/null"); m_nextCommands << m_targetConf.customInit; m_nextCommands << QStringLiteral("(Q) info breakpoints"); m_nextCommands << QStringLiteral("tbreak main"); m_nextCommands << QStringLiteral("run"); m_nextCommands << QStringLiteral("p setvbuf(stdout, 0, %1, 1024)").arg(_IOLBF); m_nextCommands << QStringLiteral("continue"); } void DebugView::slotStepInto() { issueCommand(QStringLiteral("step")); } void DebugView::slotStepOver() { issueCommand(QStringLiteral("next")); } void DebugView::slotStepOut() { issueCommand(QStringLiteral("finish")); } void DebugView::slotContinue() { issueCommand(QStringLiteral("continue")); } static QRegExp breakpointList(QStringLiteral("Num\\s+Type\\s+Disp\\s+Enb\\s+Address\\s+What.*")); static QRegExp breakpointListed(QStringLiteral("(\\d)\\s+breakpoint\\s+keep\\sy\\s+0x[\\da-f]+\\sin\\s.+\\sat\\s([^:]+):(\\d+).*")); static QRegExp stackFrameAny(QStringLiteral("#(\\d+)\\s(.*)")); static QRegExp stackFrameFile(QStringLiteral("#(\\d+)\\s+(?:0x[\\da-f]+\\s*in\\s)*(\\S+)(\\s\\(.*\\)) at ([^:]+):(\\d+).*")); static QRegExp changeFile(QStringLiteral("(?:(?:Temporary\\sbreakpoint|Breakpoint)\\s*\\d+,\\s*|0x[\\da-f]+\\s*in\\s*)?[^\\s]+\\s*\\([^)]*\\)\\s*at\\s*([^:]+):(\\d+).*")); static QRegExp changeLine(QStringLiteral("(\\d+)\\s+.*")); static QRegExp breakPointReg(QStringLiteral("Breakpoint\\s+(\\d+)\\s+at\\s+0x[\\da-f]+:\\s+file\\s+([^\\,]+)\\,\\s+line\\s+(\\d+).*")); static QRegExp breakPointMultiReg(QStringLiteral("Breakpoint\\s+(\\d+)\\s+at\\s+0x[\\da-f]+:\\s+([^\\,]+):(\\d+).*")); static QRegExp breakPointDel(QStringLiteral("Deleted\\s+breakpoint.*")); static QRegExp exitProgram(QStringLiteral("(?:Program|.*Inferior.*)\\s+exited.*")); static QRegExp threadLine(QStringLiteral("\\**\\s+(\\d+)\\s+Thread.*")); void DebugView::processLine(QString line) { if (line.isEmpty()) return; switch (m_state) { case none: case ready: if (PromptStr == line) { // we get here after initialization QTimer::singleShot(0, this, &DebugView::issueNextCommand); } break; case executingCmd: if (breakpointList.exactMatch(line)) { m_state = listingBreakpoints; emit clearBreakpointMarks(); m_breakPointList.clear(); } else if (line.contains(QLatin1String("No breakpoints or watchpoints."))) { emit clearBreakpointMarks(); m_breakPointList.clear(); } else if (stackFrameAny.exactMatch(line)) { if (m_lastCommand.contains(QLatin1String("info stack"))) { emit stackFrameInfo(stackFrameAny.cap(1), stackFrameAny.cap(2)); } else { m_subState = (m_subState == normal) ? stackFrameSeen : stackTraceSeen; m_newFrameLevel = stackFrameAny.cap(1).toInt(); if (stackFrameFile.exactMatch(line)) { m_newFrameFile = stackFrameFile.cap(4); } } } else if (changeFile.exactMatch(line)) { m_currentFile = changeFile.cap(1).trimmed(); int lineNum = changeFile.cap(2).toInt(); if (!m_nextCommands.contains(QLatin1String("continue"))) { // GDB uses 1 based line numbers, kate uses 0 based... emit debugLocationChanged(resolveFileName(m_currentFile), lineNum - 1); } m_debugLocationChanged = true; } else if (changeLine.exactMatch(line)) { int lineNum = changeLine.cap(1).toInt(); if (m_subState == stackFrameSeen) { m_currentFile = m_newFrameFile; } if (!m_nextCommands.contains(QLatin1String("continue"))) { // GDB uses 1 based line numbers, kate uses 0 based... emit debugLocationChanged(resolveFileName(m_currentFile), lineNum - 1); } m_debugLocationChanged = true; } else if (breakPointReg.exactMatch(line)) { BreakPoint breakPoint; breakPoint.number = breakPointReg.cap(1).toInt(); breakPoint.file = resolveFileName(breakPointReg.cap(2)); breakPoint.line = breakPointReg.cap(3).toInt(); m_breakPointList << breakPoint; emit breakPointSet(breakPoint.file, breakPoint.line - 1); } else if (breakPointMultiReg.exactMatch(line)) { BreakPoint breakPoint; breakPoint.number = breakPointMultiReg.cap(1).toInt(); breakPoint.file = resolveFileName(breakPointMultiReg.cap(2)); breakPoint.line = breakPointMultiReg.cap(3).toInt(); m_breakPointList << breakPoint; emit breakPointSet(breakPoint.file, breakPoint.line - 1); } else if (breakPointDel.exactMatch(line)) { line.remove(QStringLiteral("Deleted breakpoint")); line.remove(QLatin1Char('s')); // in case of multiple breakpoints QStringList numbers = line.split(QLatin1Char(' '), QString::SkipEmptyParts); for (int i = 0; i < numbers.size(); i++) { for (int j = 0; j < m_breakPointList.size(); j++) { if (numbers[i].toInt() == m_breakPointList[j].number) { emit breakPointCleared(m_breakPointList[j].file, m_breakPointList[j].line - 1); m_breakPointList.removeAt(j); break; } } } } else if (exitProgram.exactMatch(line) || line.contains(QLatin1String("The program no longer exists")) || line.contains(QLatin1String("Kill the program being debugged"))) { // if there are still commands to execute remove them to remove unneeded output // except if the "kill was for "re-run" if ((!m_nextCommands.empty()) && !m_nextCommands[0].contains(QLatin1String("file"))) { m_nextCommands.clear(); } m_debugLocationChanged = false; // do not insert (Q) commands emit programEnded(); } else if (PromptStr == line) { if (m_subState == stackFrameSeen) { emit stackFrameChanged(m_newFrameLevel); } m_state = ready; // Give the error a possibility get noticed since stderr and stdout are not in sync QTimer::singleShot(0, this, &DebugView::issueNextCommand); } break; case listingBreakpoints: if (breakpointListed.exactMatch(line)) { BreakPoint breakPoint; breakPoint.number = breakpointListed.cap(1).toInt(); breakPoint.file = resolveFileName(breakpointListed.cap(2)); breakPoint.line = breakpointListed.cap(3).toInt(); m_breakPointList << breakPoint; emit breakPointSet(breakPoint.file, breakPoint.line - 1); } else if (PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } break; case infoArgs: if (PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else { emit infoLocal(line); } break; case printThis: if (PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else { emit infoLocal(line); } break; case infoLocals: if (PromptStr == line) { m_state = ready; emit infoLocal(QString()); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else { emit infoLocal(line); } break; case infoStack: if (PromptStr == line) { m_state = ready; emit stackFrameInfo(QString(), QString()); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if (stackFrameAny.exactMatch(line)) { emit stackFrameInfo(stackFrameAny.cap(1), stackFrameAny.cap(2)); } break; case infoThreads: if (PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if (threadLine.exactMatch(line)) { emit threadInfo(threadLine.cap(1).toInt(), (line[0] == QLatin1Char('*'))); } break; } outputTextMaybe(line); } void DebugView::processErrors() { QString error; while (!m_errorList.empty()) { error = m_errorList.takeFirst(); // qDebug() << error; if (error == QLatin1String("The program is not being run.")) { if (m_lastCommand == QLatin1String("continue")) { m_nextCommands.clear(); m_nextCommands << QStringLiteral("tbreak main"); m_nextCommands << QStringLiteral("run"); m_nextCommands << QStringLiteral("p setvbuf(stdout, 0, %1, 1024)").arg(_IOLBF); m_nextCommands << QStringLiteral("continue"); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if ((m_lastCommand == QLatin1String("step")) || (m_lastCommand == QLatin1String("next")) || (m_lastCommand == QLatin1String("finish"))) { m_nextCommands.clear(); m_nextCommands << QStringLiteral("tbreak main"); m_nextCommands << QStringLiteral("run"); m_nextCommands << QStringLiteral("p setvbuf(stdout, 0, %1, 1024)").arg(_IOLBF); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if ((m_lastCommand == QLatin1String("kill"))) { if (!m_nextCommands.empty()) { if (!m_nextCommands[0].contains(QLatin1String("file"))) { m_nextCommands.clear(); m_nextCommands << QStringLiteral("quit"); } // else continue with "ReRun" } else { m_nextCommands << QStringLiteral("quit"); } m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } // else do nothing } else if (error.contains(QLatin1String("No line ")) || error.contains(QLatin1String("No source file named"))) { // setting a breakpoint failed. Do not continue. m_nextCommands.clear(); emit readyForInput(true); } else if (error.contains(QLatin1String("No stack"))) { m_nextCommands.clear(); emit programEnded(); } if ((m_lastCommand == QLatin1String("(Q)print *this")) && error.contains(QLatin1String("No symbol \"this\" in current context."))) { continue; } emit outputError(error + QLatin1Char('\n')); } } void DebugView::issueCommand(QString const &cmd) { if (m_state == ready) { emit readyForInput(false); m_state = executingCmd; if (cmd == QLatin1String("(Q)info locals")) { m_state = infoLocals; } else if (cmd == QLatin1String("(Q)info args")) { m_state = infoArgs; } else if (cmd == QLatin1String("(Q)print *this")) { m_state = printThis; } else if (cmd == QLatin1String("(Q)info stack")) { m_state = infoStack; } else if (cmd == QLatin1String("(Q)info thread")) { emit threadInfo(-1, false); m_state = infoThreads; } m_subState = normal; m_lastCommand = cmd; if (cmd.startsWith(QLatin1String("(Q)"))) { m_debugProcess.write(qPrintable(cmd.mid(3))); } else { emit outputText(QStringLiteral("(gdb) ") + cmd + QLatin1Char('\n')); m_debugProcess.write(qPrintable(cmd)); } m_debugProcess.write("\n"); } } void DebugView::issueNextCommand() { if (m_state == ready) { if (!m_nextCommands.empty()) { QString cmd = m_nextCommands.takeFirst(); // qDebug() << "Next command" << cmd; issueCommand(cmd); } else { // FIXME "thread" needs a better generic solution if (m_debugLocationChanged || m_lastCommand.startsWith(QLatin1String("thread"))) { m_debugLocationChanged = false; if (m_queryLocals && !m_lastCommand.startsWith(QLatin1String("(Q)"))) { m_nextCommands << QStringLiteral("(Q)info stack"); m_nextCommands << QStringLiteral("(Q)frame"); m_nextCommands << QStringLiteral("(Q)info args"); m_nextCommands << QStringLiteral("(Q)print *this"); m_nextCommands << QStringLiteral("(Q)info locals"); m_nextCommands << QStringLiteral("(Q)info thread"); issueNextCommand(); return; } } emit readyForInput(true); } } } QUrl DebugView::resolveFileName(const QString &fileName) { QUrl url; QFileInfo fInfo = QFileInfo(fileName); // did we end up with an absolute path or a relative one? if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } if (fInfo.isAbsolute()) { // we can not do anything just return the fileName return QUrl::fromUserInput(fileName); } // Now try to add the working path fInfo = QFileInfo(m_targetConf.workDir + fileName); if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } // now try the executable path fInfo = QFileInfo(QFileInfo(m_targetConf.executable).absolutePath() + fileName); if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } for (const QString &srcPath : qAsConst(m_targetConf.srcPaths)) { fInfo = QFileInfo(srcPath + QDir::separator() + fileName); if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } } // we can not do anything just return the fileName return QUrl::fromUserInput(fileName); } void DebugView::outputTextMaybe(const QString &text) { if (!m_lastCommand.startsWith(QLatin1String("(Q)")) && !text.contains(PromptStr)) { emit outputText(text + QLatin1Char('\n')); } } void DebugView::slotQueryLocals(bool query) { m_queryLocals = query; if (query && (m_state == ready) && (m_nextCommands.empty())) { m_nextCommands << QStringLiteral("(Q)info stack"); m_nextCommands << QStringLiteral("(Q)frame"); m_nextCommands << QStringLiteral("(Q)info args"); m_nextCommands << QStringLiteral("(Q)print *this"); m_nextCommands << QStringLiteral("(Q)info locals"); m_nextCommands << QStringLiteral("(Q)info thread"); issueNextCommand(); } } diff --git a/addons/gdbplugin/ioview.cpp b/addons/gdbplugin/ioview.cpp index 6d4a88ee4..54abe0550 100644 --- a/addons/gdbplugin/ioview.cpp +++ b/addons/gdbplugin/ioview.cpp @@ -1,238 +1,238 @@ // // configview.cpp // // Description: View for configuring the set of targets to be used with the debugger // // // Copyright (c) 2010 KÃ¥re Särs // // 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. #include "ioview.h" -#include -#include #include -#include +#include +#include #include #include #include #include -#include +#include +#include #include #include -#include -#include #include +#include +#include #include IOView::IOView(QWidget *parent) : QWidget(parent) { m_output = new QTextEdit(); m_output->setReadOnly(true); m_output->setUndoRedoEnabled(false); m_output->setAcceptRichText(false); // fixed wide font, like konsole m_output->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); // alternate color scheme, like konsole KColorScheme schemeView(QPalette::Active, KColorScheme::View); m_output->setTextBackgroundColor(schemeView.foreground().color()); m_output->setTextColor(schemeView.background().color()); QPalette p = m_output->palette(); p.setColor(QPalette::Base, schemeView.foreground().color()); m_output->setPalette(p); m_input = new QLineEdit(); m_output->setFocusProxy(m_input); // take the focus from the output QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(m_output, 10); layout->addWidget(m_input, 0); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); connect(m_input, &QLineEdit::returnPressed, this, &IOView::returnPressed); createFifos(); } IOView::~IOView() { m_stdin.close(); m_stdout.close(); m_stdout.setFileName(m_stdoutFifo); ::close(m_stdoutFD); m_stderr.close(); m_stderr.setFileName(m_stderrFifo); ::close(m_stderrFD); m_stdin.remove(); m_stdout.remove(); m_stderr.remove(); } void IOView::createFifos() { m_stdinFifo = createFifo(QStringLiteral("stdInFifo")); m_stdoutFifo = createFifo(QStringLiteral("stdOutFifo")); m_stderrFifo = createFifo(QStringLiteral("stdErrFifo")); m_stdin.setFileName(m_stdinFifo); if (!m_stdin.open(QIODevice::ReadWrite)) return; m_stdoutD.setFileName(m_stdoutFifo); m_stdoutD.open(QIODevice::ReadWrite); m_stdout.setFileName(m_stdoutFifo); m_stdoutFD = ::open(m_stdoutFifo.toLocal8Bit().data(), O_RDWR | O_NONBLOCK); if (m_stdoutFD == -1) return; if (!m_stdout.open(m_stdoutFD, QIODevice::ReadWrite)) return; m_stdoutNotifier = new QSocketNotifier(m_stdoutFD, QSocketNotifier::Read, this); connect(m_stdoutNotifier, &QSocketNotifier::activated, this, &IOView::readOutput); m_stdoutNotifier->setEnabled(true); m_stderrD.setFileName(m_stderrFifo); m_stderrD.open(QIODevice::ReadWrite); m_stderr.setFileName(m_stderrFifo); m_stderrFD = ::open(m_stderrFifo.toLocal8Bit().data(), O_RDONLY | O_NONBLOCK); if (m_stderrFD == -1) return; if (!m_stderr.open(m_stderrFD, QIODevice::ReadOnly)) return; m_stderrNotifier = new QSocketNotifier(m_stderrFD, QSocketNotifier::Read, this); connect(m_stderrNotifier, &QSocketNotifier::activated, this, &IOView::readErrors); m_stderrNotifier->setEnabled(true); return; } void IOView::returnPressed() { m_stdin.write(m_input->text().toLocal8Bit()); m_stdin.write("\n"); m_stdin.flush(); m_input->clear(); } void IOView::readOutput() { m_stdoutNotifier->setEnabled(false); qint64 res; char chData[256]; QByteArray data; do { res = m_stdout.read(chData, 255); if (res <= 0) { m_stdoutD.flush(); } else { data.append(chData, res); } } while (res == 255); if (data.size() > 0) { emit stdOutText(QString::fromLocal8Bit(data)); } m_stdoutNotifier->setEnabled(true); } void IOView::readErrors() { m_stderrNotifier->setEnabled(false); qint64 res; char chData[256]; QByteArray data; do { res = m_stderr.read(chData, 255); if (res <= 0) { m_stderrD.flush(); } else { data.append(chData, res); } } while (res == 255); if (data.size() > 0) { emit stdErrText(QString::fromLocal8Bit(data)); } m_stderrNotifier->setEnabled(true); } void IOView::addStdOutText(const QString &text) { QScrollBar *scrollb = m_output->verticalScrollBar(); if (!scrollb) return; bool atEnd = (scrollb->value() == scrollb->maximum()); QTextCursor cursor = m_output->textCursor(); if (!cursor.atEnd()) cursor.movePosition(QTextCursor::End); cursor.insertText(text); if (atEnd) { scrollb->setValue(scrollb->maximum()); } } void IOView::addStdErrText(const QString &text) { m_output->setFontItalic(true); addStdOutText(text); m_output->setFontItalic(false); } QString IOView::createFifo(const QString &prefix) { QString fifo = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QDir::separator() + prefix + KRandom::randomString(3); int result = mkfifo(QFile::encodeName(fifo).data(), 0666); if (result != 0) return QString(); return fifo; } const QString IOView::stdinFifo() { return m_stdinFifo; } const QString IOView::stdoutFifo() { return m_stdoutFifo; } const QString IOView::stderrFifo() { return m_stderrFifo; } void IOView::enableInput(bool enable) { m_input->setEnabled(enable); } void IOView::clearOutput() { m_output->clear(); } diff --git a/addons/gdbplugin/ioview.h b/addons/gdbplugin/ioview.h index ef22559a2..e901f0072 100644 --- a/addons/gdbplugin/ioview.h +++ b/addons/gdbplugin/ioview.h @@ -1,86 +1,86 @@ // // ioview.h // // Description: Widget that interacts with the debugged application // // // Copyright (c) 2010 KÃ¥re Särs // // 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. #ifndef IOVIEW_H #define IOVIEW_H -#include #include +#include class QTextEdit; class QLineEdit; class QSocketNotifier; class IOView : public QWidget { Q_OBJECT public: IOView(QWidget *parent = nullptr); ~IOView() override; const QString stdinFifo(); const QString stdoutFifo(); const QString stderrFifo(); void enableInput(bool enable); void clearOutput(); public Q_SLOTS: void addStdOutText(const QString &text); void addStdErrText(const QString &text); private Q_SLOTS: void returnPressed(); void readOutput(); void readErrors(); Q_SIGNALS: void stdOutText(const QString &text); void stdErrText(const QString &text); private: void createFifos(); QString createFifo(const QString &prefix); QTextEdit *m_output; QLineEdit *m_input; QString m_stdinFifo; QString m_stdoutFifo; QString m_stderrFifo; QFile m_stdin; QFile m_stdout; QFile m_stderr; QFile m_stdoutD; QFile m_stderrD; int m_stdoutFD; int m_stderrFD; QSocketNotifier *m_stdoutNotifier; QSocketNotifier *m_stderrNotifier; }; #endif diff --git a/addons/gdbplugin/localsview.cpp b/addons/gdbplugin/localsview.cpp index 3f8ff0dd4..4cfe6752b 100644 --- a/addons/gdbplugin/localsview.cpp +++ b/addons/gdbplugin/localsview.cpp @@ -1,264 +1,264 @@ // // Description: Widget that local variables of the gdb inferior // // Copyright (c) 2010 KÃ¥re Särs // // 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. #include "localsview.h" -#include #include +#include #include LocalsView::LocalsView(QWidget *parent) : QTreeWidget(parent) { QStringList headers; headers << i18n("Symbol"); headers << i18n("Value"); setHeaderLabels(headers); setAutoScroll(false); } LocalsView::~LocalsView() { } void LocalsView::showEvent(QShowEvent *) { emit localsVisible(true); } void LocalsView::hideEvent(QHideEvent *) { emit localsVisible(false); } void LocalsView::createWrappedItem(QTreeWidgetItem *parent, const QString &name, const QString &value) { QTreeWidgetItem *item = new QTreeWidgetItem(parent, QStringList(name)); QLabel *label = new QLabel(value); label->setWordWrap(true); setItemWidget(item, 1, label); item->setData(1, Qt::UserRole, value); } void LocalsView::createWrappedItem(QTreeWidget *parent, const QString &name, const QString &value) { QTreeWidgetItem *item = new QTreeWidgetItem(parent, QStringList(name)); QLabel *label = new QLabel(value); label->setWordWrap(true); setItemWidget(item, 1, label); } void LocalsView::addLocal(const QString &vString) { static QRegExp isValue(QStringLiteral("(\\S*)\\s=\\s(.*)")); static QRegExp isStruct(QStringLiteral("\\{\\S*\\s=\\s.*")); static QRegExp isStartPartial(QStringLiteral("\\S*\\s=\\s\\S*\\s=\\s\\{")); static QRegExp isPrettyQList(QStringLiteral("\\s*\\[\\S*\\]\\s=\\s\\S*")); static QRegExp isPrettyValue(QStringLiteral("(\\S*)\\s=\\s(\\S*)\\s=\\s(.*)")); static QRegExp isThisValue(QStringLiteral("\\$\\d+")); if (m_allAdded) { clear(); m_allAdded = false; } if (vString.isEmpty()) { m_allAdded = true; return; } if (isStartPartial.exactMatch(vString)) { m_local = vString; return; } if (isPrettyQList.exactMatch(vString)) { m_local += vString.trimmed(); if (m_local.endsWith(QLatin1Char(','))) m_local += QLatin1Char(' '); return; } if (vString == QLatin1String("}")) { m_local += vString; } QStringList symbolAndValue; QString value; if (m_local.isEmpty()) { if (vString == QLatin1String("No symbol table info available.")) { return; /* this is not an error */ } if (!isValue.exactMatch(vString)) { qDebug() << "Could not parse:" << vString; return; } symbolAndValue << isValue.cap(1); // check out for "print *this" if (isThisValue.exactMatch(symbolAndValue[0])) { symbolAndValue[0] = QStringLiteral("*this"); } value = isValue.cap(2); } else { if (!isPrettyValue.exactMatch(m_local)) { qDebug() << "Could not parse:" << m_local; m_local.clear(); return; } symbolAndValue << isPrettyValue.cap(1) << isPrettyValue.cap(2); value = isPrettyValue.cap(3); } QTreeWidgetItem *item; if (value[0] == QLatin1Char('{')) { if (value[1] == QLatin1Char('{')) { item = new QTreeWidgetItem(this, symbolAndValue); addArray(item, value.mid(1, value.size() - 2)); } else { if (isStruct.exactMatch(value)) { item = new QTreeWidgetItem(this, symbolAndValue); addStruct(item, value.mid(1, value.size() - 2)); } else { createWrappedItem(this, symbolAndValue[0], value); } } } else { createWrappedItem(this, symbolAndValue[0], value); } m_local.clear(); } void LocalsView::addStruct(QTreeWidgetItem *parent, const QString &vString) { static QRegExp isArray(QStringLiteral("\\{\\.*\\s=\\s.*")); static QRegExp isStruct(QStringLiteral("\\.*\\s=\\s.*")); QTreeWidgetItem *item; QStringList symbolAndValue; QString subValue; int start = 0; int end; while (start < vString.size()) { // Symbol symbolAndValue.clear(); end = vString.indexOf(QLatin1String(" = "), start); if (end < 0) { // error situation -> bail out createWrappedItem(parent, QString(), vString.right(start)); break; } symbolAndValue << vString.mid(start, end - start); // qDebug() << symbolAndValue; // Value start = end + 3; end = start; if (start < 0 || start >= vString.size()) { qDebug() << vString << start; break; } if (vString[start] == QLatin1Char('{')) { start++; end++; int count = 1; bool inComment = false; // search for the matching } while (end < vString.size()) { if (!inComment) { if (vString[end] == QLatin1Char('"')) inComment = true; else if (vString[end] == QLatin1Char('}')) count--; else if (vString[end] == QLatin1Char('{')) count++; if (count == 0) break; } else { if ((vString[end] == QLatin1Char('"')) && (vString[end - 1] != QLatin1Char('\\'))) { inComment = false; } } end++; } subValue = vString.mid(start, end - start); if (isArray.exactMatch(subValue)) { item = new QTreeWidgetItem(parent, symbolAndValue); addArray(item, subValue); } else if (isStruct.exactMatch(subValue)) { item = new QTreeWidgetItem(parent, symbolAndValue); addStruct(item, subValue); } else { createWrappedItem(parent, symbolAndValue[0], vString.mid(start, end - start)); } start = end + 3; // },_ } else { // look for the end of the value in the vString bool inComment = false; while (end < vString.size()) { if (!inComment) { if (vString[end] == QLatin1Char('"')) inComment = true; else if (vString[end] == QLatin1Char(',')) break; } else { if ((vString[end] == QLatin1Char('"')) && (vString[end - 1] != QLatin1Char('\\'))) { inComment = false; } } end++; } createWrappedItem(parent, symbolAndValue[0], vString.mid(start, end - start)); start = end + 2; // ,_ } } } void LocalsView::addArray(QTreeWidgetItem *parent, const QString &vString) { // getting here we have this kind of string: // "{...}" or "{...}, {...}" or ... QTreeWidgetItem *item; int count = 1; bool inComment = false; int index = 0; int start = 1; int end = 1; while (end < vString.size()) { if (!inComment) { if (vString[end] == QLatin1Char('"')) inComment = true; else if (vString[end] == QLatin1Char('}')) count--; else if (vString[end] == QLatin1Char('{')) count++; if (count == 0) { QStringList name; name << QStringLiteral("[%1]").arg(index); index++; item = new QTreeWidgetItem(parent, name); addStruct(item, vString.mid(start, end - start)); end += 4; // "}, {" start = end; count = 1; } } else { if ((vString[end] == QLatin1Char('"')) && (vString[end - 1] != QLatin1Char('\\'))) { inComment = false; } } end++; } } diff --git a/addons/gdbplugin/plugin_kategdb.cpp b/addons/gdbplugin/plugin_kategdb.cpp index 4f872126b..9126efa8e 100644 --- a/addons/gdbplugin/plugin_kategdb.cpp +++ b/addons/gdbplugin/plugin_kategdb.cpp @@ -1,715 +1,715 @@ // // Description: Kate Plugin for GDB integration // // // Copyright (c) 2010 Ian Wakeling // Copyright (c) 2010-2014 KÃ¥re Särs // // 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. #include "plugin_kategdb.h" #include #include -#include -#include -#include +#include +#include #include -#include #include +#include +#include +#include +#include #include -#include -#include -#include -#include #include #include +#include +#include #include -#include +#include #include +#include +#include #include #include -#include -#include -#include #include -#include #include +#include +#include K_PLUGIN_FACTORY_WITH_JSON(KatePluginGDBFactory, "kategdbplugin.json", registerPlugin();) KatePluginGDB::KatePluginGDB(QObject *parent, const VariantList &) : KTextEditor::Plugin(parent) { // FIXME KF5 KGlobal::locale()->insertCatalog("kategdbplugin"); } KatePluginGDB::~KatePluginGDB() { } QObject *KatePluginGDB::createView(KTextEditor::MainWindow *mainWindow) { return new KatePluginGDBView(this, mainWindow); } KatePluginGDBView::KatePluginGDBView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_mainWin(mainWin) { m_lastExecUrl = QUrl(); m_lastExecLine = -1; m_lastExecFrame = 0; m_kateApplication = KTextEditor::Editor::instance()->application(); m_focusOnInput = true; m_activeThread = -1; KXMLGUIClient::setComponentName(QStringLiteral("kategdb"), i18n("Kate GDB")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = m_mainWin->createToolView(plugin, i18n("Debug View"), KTextEditor::MainWindow::Bottom, QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")), i18n("Debug View")); m_localsStackToolView = m_mainWin->createToolView(plugin, i18n("Locals and Stack"), KTextEditor::MainWindow::Right, QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")), i18n("Locals and Stack")); m_tabWidget = new QTabWidget(m_toolView); // Output m_outputArea = new QTextEdit(); m_outputArea->setAcceptRichText(false); m_outputArea->setReadOnly(true); m_outputArea->setUndoRedoEnabled(false); // fixed wide font, like konsole m_outputArea->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); // alternate color scheme, like konsole KColorScheme schemeView(QPalette::Active, KColorScheme::View); m_outputArea->setTextBackgroundColor(schemeView.foreground().color()); m_outputArea->setTextColor(schemeView.background().color()); QPalette p = m_outputArea->palette(); p.setColor(QPalette::Base, schemeView.foreground().color()); m_outputArea->setPalette(p); // input m_inputArea = new KHistoryComboBox(true); connect(m_inputArea, static_cast(&KHistoryComboBox::returnPressed), this, &KatePluginGDBView::slotSendCommand); QHBoxLayout *inputLayout = new QHBoxLayout(); inputLayout->addWidget(m_inputArea, 10); inputLayout->setContentsMargins(0, 0, 0, 0); m_outputArea->setFocusProxy(m_inputArea); // take the focus from the outputArea m_gdbPage = new QWidget(); QVBoxLayout *layout = new QVBoxLayout(m_gdbPage); layout->addWidget(m_outputArea); layout->addLayout(inputLayout); layout->setStretch(0, 10); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); // stack page QWidget *stackContainer = new QWidget(); QVBoxLayout *stackLayout = new QVBoxLayout(stackContainer); m_threadCombo = new QComboBox(); m_stackTree = new QTreeWidget(); stackLayout->addWidget(m_threadCombo); stackLayout->addWidget(m_stackTree); stackLayout->setStretch(0, 10); stackLayout->setContentsMargins(0, 0, 0, 0); stackLayout->setSpacing(0); QStringList headers; headers << QStringLiteral(" ") << i18nc("Column label (frame number)", "Nr") << i18nc("Column label", "Frame"); m_stackTree->setHeaderLabels(headers); m_stackTree->setRootIsDecorated(false); m_stackTree->resizeColumnToContents(0); m_stackTree->resizeColumnToContents(1); m_stackTree->setAutoScroll(false); connect(m_stackTree, &QTreeWidget::itemActivated, this, &KatePluginGDBView::stackFrameSelected); connect(m_threadCombo, static_cast(&QComboBox::currentIndexChanged), this, &KatePluginGDBView::threadSelected); m_localsView = new LocalsView(); QSplitter *locStackSplitter = new QSplitter(m_localsStackToolView); locStackSplitter->addWidget(m_localsView); locStackSplitter->addWidget(stackContainer); locStackSplitter->setOrientation(Qt::Vertical); // config page m_configView = new ConfigView(nullptr, mainWin); m_ioView = new IOView(); connect(m_configView, &ConfigView::showIO, this, &KatePluginGDBView::showIO); m_tabWidget->addTab(m_gdbPage, i18nc("Tab label", "GDB Output")); m_tabWidget->addTab(m_configView, i18nc("Tab label", "Settings")); m_debugView = new DebugView(this); connect(m_debugView, &DebugView::readyForInput, this, &KatePluginGDBView::enableDebugActions); connect(m_debugView, &DebugView::outputText, this, &KatePluginGDBView::addOutputText); connect(m_debugView, &DebugView::outputError, this, &KatePluginGDBView::addErrorText); connect(m_debugView, &DebugView::debugLocationChanged, this, &KatePluginGDBView::slotGoTo); connect(m_debugView, &DebugView::breakPointSet, this, &KatePluginGDBView::slotBreakpointSet); connect(m_debugView, &DebugView::breakPointCleared, this, &KatePluginGDBView::slotBreakpointCleared); connect(m_debugView, &DebugView::clearBreakpointMarks, this, &KatePluginGDBView::clearMarks); connect(m_debugView, &DebugView::programEnded, this, &KatePluginGDBView::programEnded); connect(m_debugView, &DebugView::gdbEnded, this, &KatePluginGDBView::programEnded); connect(m_debugView, &DebugView::gdbEnded, this, &KatePluginGDBView::gdbEnded); connect(m_debugView, &DebugView::stackFrameInfo, this, &KatePluginGDBView::insertStackFrame); connect(m_debugView, &DebugView::stackFrameChanged, this, &KatePluginGDBView::stackFrameChanged); connect(m_debugView, &DebugView::infoLocal, m_localsView, &LocalsView::addLocal); connect(m_debugView, &DebugView::threadInfo, this, &KatePluginGDBView::insertThread); connect(m_localsView, &LocalsView::localsVisible, m_debugView, &DebugView::slotQueryLocals); // Actions m_configView->registerActions(actionCollection()); QAction *a = actionCollection()->addAction(QStringLiteral("debug")); a->setText(i18n("Start Debugging")); a->setIcon(QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotDebug); a = actionCollection()->addAction(QStringLiteral("kill")); a->setText(i18n("Kill / Stop Debugging")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotKill); a = actionCollection()->addAction(QStringLiteral("rerun")); a->setText(i18n("Restart Debugging")); a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRestart); a = actionCollection()->addAction(QStringLiteral("toggle_breakpoint")); a->setText(i18n("Toggle Breakpoint / Break")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotToggleBreakpoint); a = actionCollection()->addAction(QStringLiteral("step_in")); a->setText(i18n("Step In")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-into"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepInto); a = actionCollection()->addAction(QStringLiteral("step_over")); a->setText(i18n("Step Over")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-over"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepOver); a = actionCollection()->addAction(QStringLiteral("step_out")); a->setText(i18n("Step Out")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-out"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepOut); a = actionCollection()->addAction(QStringLiteral("move_pc")); a->setText(i18nc("Move Program Counter (next execution)", "Move PC")); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotMovePC); a = actionCollection()->addAction(QStringLiteral("run_to_cursor")); a->setText(i18n("Run To Cursor")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-run-cursor"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRunToCursor); a = actionCollection()->addAction(QStringLiteral("continue")); a->setText(i18n("Continue")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotContinue); a = actionCollection()->addAction(QStringLiteral("print_value")); a->setText(i18n("Print Value")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotValue); // popup context m_menu m_menu = new KActionMenu(i18n("Debug"), this); actionCollection()->addAction(QStringLiteral("popup_gdb"), m_menu); connect(m_menu->menu(), &QMenu::aboutToShow, this, &KatePluginGDBView::aboutToShowMenu); m_breakpoint = m_menu->menu()->addAction(i18n("popup_breakpoint"), this, &KatePluginGDBView::slotToggleBreakpoint); QAction *popupAction = m_menu->menu()->addAction(i18n("popup_run_to_cursor"), this, &KatePluginGDBView::slotRunToCursor); popupAction->setText(i18n("Run To Cursor")); popupAction = m_menu->menu()->addAction(QStringLiteral("move_pc"), this, &KatePluginGDBView::slotMovePC); popupAction->setText(i18nc("Move Program Counter (next execution)", "Move PC")); enableDebugActions(false); connect(m_mainWin, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KatePluginGDBView::handleEsc); m_toolView->installEventFilter(this); m_mainWin->guiFactory()->addClient(this); } KatePluginGDBView::~KatePluginGDBView() { m_mainWin->guiFactory()->removeClient(this); delete m_toolView; delete m_localsStackToolView; } void KatePluginGDBView::readSessionConfig(const KConfigGroup &config) { m_configView->readConfig(config); } void KatePluginGDBView::writeSessionConfig(KConfigGroup &config) { m_configView->writeConfig(config); } void KatePluginGDBView::slotDebug() { disconnect(m_ioView, &IOView::stdOutText, nullptr, nullptr); disconnect(m_ioView, &IOView::stdErrText, nullptr, nullptr); if (m_configView->showIOTab()) { connect(m_ioView, &IOView::stdOutText, m_ioView, &IOView::addStdOutText); connect(m_ioView, &IOView::stdErrText, m_ioView, &IOView::addStdErrText); } else { connect(m_ioView, &IOView::stdOutText, this, &KatePluginGDBView::addOutputText); connect(m_ioView, &IOView::stdErrText, this, &KatePluginGDBView::addErrorText); } QStringList ioFifos; ioFifos << m_ioView->stdinFifo(); ioFifos << m_ioView->stdoutFifo(); ioFifos << m_ioView->stderrFifo(); enableDebugActions(true); m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); m_localsView->clear(); m_debugView->runDebugger(m_configView->currentTarget(), ioFifos); } void KatePluginGDBView::slotRestart() { m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); m_localsView->clear(); m_debugView->slotReRun(); } void KatePluginGDBView::aboutToShowMenu() { if (!m_debugView->debuggerRunning() || m_debugView->debuggerBusy()) { m_breakpoint->setText(i18n("Insert breakpoint")); m_breakpoint->setDisabled(true); return; } m_breakpoint->setDisabled(false); KTextEditor::View *editView = m_mainWin->activeView(); QUrl url = editView->document()->url(); int line = editView->cursorPosition().line(); line++; // GDB uses 1 based line numbers, kate uses 0 based... if (m_debugView->hasBreakpoint(url, line)) { m_breakpoint->setText(i18n("Remove breakpoint")); } else { m_breakpoint->setText(i18n("Insert breakpoint")); } } void KatePluginGDBView::slotToggleBreakpoint() { if (!actionCollection()->action(QStringLiteral("continue"))->isEnabled()) { m_debugView->slotInterrupt(); } else { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); int line = editView->cursorPosition().line(); m_debugView->toggleBreakpoint(currURL, line + 1); } } void KatePluginGDBView::slotBreakpointSet(const QUrl &file, int line) { KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(file)); if (iface) { iface->setMarkDescription(KTextEditor::MarkInterface::BreakpointActive, i18n("Breakpoint")); iface->setMarkPixmap(KTextEditor::MarkInterface::BreakpointActive, QIcon::fromTheme(QStringLiteral("media-playback-pause")).pixmap(10, 10)); iface->addMark(line, KTextEditor::MarkInterface::BreakpointActive); } } void KatePluginGDBView::slotBreakpointCleared(const QUrl &file, int line) { KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(file)); if (iface) { iface->removeMark(line, KTextEditor::MarkInterface::BreakpointActive); } } void KatePluginGDBView::slotMovePC() { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); KTextEditor::Cursor cursor = editView->cursorPosition(); m_debugView->movePC(currURL, cursor.line() + 1); } void KatePluginGDBView::slotRunToCursor() { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); KTextEditor::Cursor cursor = editView->cursorPosition(); // GDB starts lines from 1, kate returns lines starting from 0 (displaying 1) m_debugView->runToCursor(currURL, cursor.line() + 1); } void KatePluginGDBView::slotGoTo(const QUrl &url, int lineNum) { // skip not existing files if (!QFile::exists(url.toLocalFile())) { m_lastExecLine = -1; return; } m_lastExecUrl = url; m_lastExecLine = lineNum; KTextEditor::View *editView = m_mainWin->openUrl(m_lastExecUrl); editView->setCursorPosition(KTextEditor::Cursor(m_lastExecLine, 0)); m_mainWin->window()->raise(); m_mainWin->window()->setFocus(); } void KatePluginGDBView::enableDebugActions(bool enable) { actionCollection()->action(QStringLiteral("step_in"))->setEnabled(enable); actionCollection()->action(QStringLiteral("step_over"))->setEnabled(enable); actionCollection()->action(QStringLiteral("step_out"))->setEnabled(enable); actionCollection()->action(QStringLiteral("move_pc"))->setEnabled(enable); actionCollection()->action(QStringLiteral("run_to_cursor"))->setEnabled(enable); actionCollection()->action(QStringLiteral("popup_gdb"))->setEnabled(enable); actionCollection()->action(QStringLiteral("continue"))->setEnabled(enable); actionCollection()->action(QStringLiteral("print_value"))->setEnabled(enable); // "toggle breakpoint" doubles as interrupt while the program is running actionCollection()->action(QStringLiteral("toggle_breakpoint"))->setEnabled(m_debugView->debuggerRunning()); actionCollection()->action(QStringLiteral("kill"))->setEnabled(m_debugView->debuggerRunning()); actionCollection()->action(QStringLiteral("rerun"))->setEnabled(m_debugView->debuggerRunning()); m_inputArea->setEnabled(enable); m_threadCombo->setEnabled(enable); m_stackTree->setEnabled(enable); m_localsView->setEnabled(enable); if (enable) { m_inputArea->setFocusPolicy(Qt::WheelFocus); if (m_focusOnInput || m_configView->takeFocusAlways()) { m_inputArea->setFocus(); m_focusOnInput = false; } else { m_mainWin->activeView()->setFocus(); } } else { m_inputArea->setFocusPolicy(Qt::NoFocus); if (m_mainWin->activeView()) m_mainWin->activeView()->setFocus(); } m_ioView->enableInput(!enable && m_debugView->debuggerRunning()); if ((m_lastExecLine > -1)) { KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(m_lastExecUrl)); if (iface) { if (enable) { iface->setMarkDescription(KTextEditor::MarkInterface::Execution, i18n("Execution point")); iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(10, 10)); iface->addMark(m_lastExecLine, KTextEditor::MarkInterface::Execution); } else { iface->removeMark(m_lastExecLine, KTextEditor::MarkInterface::Execution); } } } } void KatePluginGDBView::programEnded() { // don't set the execution mark on exit m_lastExecLine = -1; m_stackTree->clear(); m_localsView->clear(); m_threadCombo->clear(); // Indicate the state change by showing the debug outputArea m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); } void KatePluginGDBView::gdbEnded() { m_outputArea->clear(); m_localsView->clear(); m_ioView->clearOutput(); clearMarks(); } void KatePluginGDBView::clearMarks() { KTextEditor::MarkInterface *iface; const auto documents = m_kateApplication->documents(); for (KTextEditor::Document *doc : documents) { iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if ((i.value()->type == KTextEditor::MarkInterface::Execution) || (i.value()->type == KTextEditor::MarkInterface::BreakpointActive)) { iface->removeMark(i.value()->line, i.value()->type); } } } } } void KatePluginGDBView::slotSendCommand() { QString cmd = m_inputArea->currentText(); if (cmd.isEmpty()) cmd = m_lastCommand; m_inputArea->addToHistory(cmd); m_inputArea->setCurrentItem(QString()); m_focusOnInput = true; m_lastCommand = cmd; m_debugView->issueCommand(cmd); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); } void KatePluginGDBView::insertStackFrame(QString const &level, QString const &info) { if (level.isEmpty() && info.isEmpty()) { m_stackTree->resizeColumnToContents(2); return; } if (level == QLatin1Char('0')) { m_stackTree->clear(); } QStringList columns; columns << QStringLiteral(" "); // icon place holder columns << level; int lastSpace = info.lastIndexOf(QLatin1Char(' ')); QString shortInfo = info.mid(lastSpace); columns << shortInfo; QTreeWidgetItem *item = new QTreeWidgetItem(columns); item->setToolTip(2, QStringLiteral("%1").arg(info)); m_stackTree->insertTopLevelItem(level.toInt(), item); } void KatePluginGDBView::stackFrameSelected() { m_debugView->issueCommand(QStringLiteral("(Q)f %1").arg(m_stackTree->currentIndex().row())); } void KatePluginGDBView::stackFrameChanged(int level) { QTreeWidgetItem *current = m_stackTree->topLevelItem(m_lastExecFrame); QTreeWidgetItem *next = m_stackTree->topLevelItem(level); if (current) current->setIcon(0, QIcon()); if (next) next->setIcon(0, QIcon::fromTheme(QStringLiteral("arrow-right"))); m_lastExecFrame = level; } void KatePluginGDBView::insertThread(int number, bool active) { if (number < 0) { m_threadCombo->clear(); m_activeThread = -1; return; } if (!active) { m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("")).pixmap(10, 10), i18n("Thread %1", number), number); } else { m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(10, 10), i18n("Thread %1", number), number); m_activeThread = m_threadCombo->count() - 1; } m_threadCombo->setCurrentIndex(m_activeThread); } void KatePluginGDBView::threadSelected(int thread) { m_debugView->issueCommand(QStringLiteral("thread %1").arg(m_threadCombo->itemData(thread).toInt())); } QString KatePluginGDBView::currentWord() { KTextEditor::View *kv = m_mainWin->activeView(); if (!kv) { qDebug() << "no KTextEditor::View" << endl; return QString(); } if (!kv->cursorPosition().isValid()) { qDebug() << "cursor not valid!" << endl; return QString(); } int line = kv->cursorPosition().line(); int col = kv->cursorPosition().column(); QString linestr = kv->document()->line(line); int startPos = qMax(qMin(col, linestr.length() - 1), 0); int lindex = linestr.length() - 1; int endPos = startPos; while (startPos >= 0 && (linestr[startPos].isLetterOrNumber() || linestr[startPos] == QLatin1Char('_') || linestr[startPos] == QLatin1Char('~') || ((startPos > 1) && (linestr[startPos] == QLatin1Char('.')) && !linestr[startPos - 1].isSpace()) || ((startPos > 2) && (linestr[startPos] == QLatin1Char('>')) && (linestr[startPos - 1] == QLatin1Char('-')) && !linestr[startPos - 2].isSpace()))) { if (linestr[startPos] == QLatin1Char('>')) { startPos--; } startPos--; } while (endPos < (int)linestr.length() && (linestr[endPos].isLetterOrNumber() || linestr[endPos] == QLatin1Char('_') || ((endPos < lindex - 1) && (linestr[endPos] == QLatin1Char('.')) && !linestr[endPos + 1].isSpace()) || ((endPos < lindex - 2) && (linestr[endPos] == QLatin1Char('-')) && (linestr[endPos + 1] == QLatin1Char('>')) && !linestr[endPos + 2].isSpace()) || ((endPos > 1) && (linestr[endPos - 1] == QLatin1Char('-')) && (linestr[endPos] == QLatin1Char('>'))))) { if (linestr[endPos] == QLatin1Char('-')) { endPos++; } endPos++; } if (startPos == endPos) { qDebug() << "no word found!" << endl; return QString(); } // qDebug() << linestr.mid(startPos+1, endPos-startPos-1); return linestr.mid(startPos + 1, endPos - startPos - 1); } void KatePluginGDBView::slotValue() { QString variable; KTextEditor::View *editView = m_mainWin->activeView(); if (editView && editView->selection() && editView->selectionRange().onSingleLine()) { variable = editView->selectionText(); } if (variable.isEmpty()) variable = currentWord(); if (variable.isEmpty()) return; QString cmd = QStringLiteral("print %1").arg(variable); m_debugView->issueCommand(cmd); m_inputArea->addToHistory(cmd); m_inputArea->setCurrentItem(QString()); m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); } void KatePluginGDBView::showIO(bool show) { if (show) { m_tabWidget->addTab(m_ioView, i18n("IO")); } else { m_tabWidget->removeTab(m_tabWidget->indexOf(m_ioView)); } } void KatePluginGDBView::addOutputText(QString const &text) { QScrollBar *scrollb = m_outputArea->verticalScrollBar(); if (!scrollb) return; bool atEnd = (scrollb->value() == scrollb->maximum()); QTextCursor cursor = m_outputArea->textCursor(); if (!cursor.atEnd()) cursor.movePosition(QTextCursor::End); cursor.insertText(text); if (atEnd) { scrollb->setValue(scrollb->maximum()); } } void KatePluginGDBView::addErrorText(QString const &text) { m_outputArea->setFontItalic(true); addOutputText(text); m_outputArea->setFontItalic(false); } bool KatePluginGDBView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_mainWin->hideToolView(m_toolView); event->accept(); return true; } } return QObject::eventFilter(obj, event); } void KatePluginGDBView::handleEsc(QEvent *e) { if (!m_mainWin) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_toolView->isVisible()) { m_mainWin->hideToolView(m_toolView); } } } #include "plugin_kategdb.moc" diff --git a/addons/gdbplugin/plugin_kategdb.h b/addons/gdbplugin/plugin_kategdb.h index cd9788052..9118a2fd3 100644 --- a/addons/gdbplugin/plugin_kategdb.h +++ b/addons/gdbplugin/plugin_kategdb.h @@ -1,129 +1,129 @@ // // Description: Kate Plugin for GDB integration // // // Copyright (c) 2010 Ian Wakeling // Copyright (c) 2010-2014 KÃ¥re Särs // // 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. #ifndef PLUGIN_KATEGDB_H #define PLUGIN_KATEGDB_H #include +#include #include #include #include -#include -#include #include +#include -#include "debugview.h" #include "configview.h" +#include "debugview.h" #include "ioview.h" #include "localsview.h" class KHistoryComboBox; class QTextEdit; class QTreeWidget; typedef QList VariantList; class KatePluginGDB : public KTextEditor::Plugin { Q_OBJECT public: explicit KatePluginGDB(QObject *parent = nullptr, const VariantList & = VariantList()); ~KatePluginGDB() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; }; class KatePluginGDBView : public QObject, public KXMLGUIClient, public KTextEditor::SessionConfigInterface { Q_OBJECT Q_INTERFACES(KTextEditor::SessionConfigInterface) public: KatePluginGDBView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin); ~KatePluginGDBView() override; // reimplemented: read and write session config void readSessionConfig(const KConfigGroup &config) override; void writeSessionConfig(KConfigGroup &config) override; private Q_SLOTS: void slotDebug(); void slotRestart(); void slotToggleBreakpoint(); void slotMovePC(); void slotRunToCursor(); void slotGoTo(const QUrl &fileName, int lineNum); void slotValue(); void aboutToShowMenu(); void slotBreakpointSet(const QUrl &file, int line); void slotBreakpointCleared(const QUrl &file, int line); void slotSendCommand(); void enableDebugActions(bool enable); void programEnded(); void gdbEnded(); void insertStackFrame(QString const &level, QString const &info); void stackFrameChanged(int level); void stackFrameSelected(); void insertThread(int number, bool active); void threadSelected(int thread); void showIO(bool show); void addOutputText(QString const &text); void addErrorText(QString const &text); void clearMarks(); void handleEsc(QEvent *e); protected: bool eventFilter(QObject *obj, QEvent *ev) override; private: QString currentWord(); KTextEditor::Application *m_kateApplication; KTextEditor::MainWindow *m_mainWin; QWidget *m_toolView; QWidget *m_localsStackToolView; QTabWidget *m_tabWidget; QTextEdit *m_outputArea; KHistoryComboBox *m_inputArea; QWidget *m_gdbPage; QComboBox *m_threadCombo; int m_activeThread; QTreeWidget *m_stackTree; QString m_lastCommand; DebugView *m_debugView; ConfigView *m_configView; IOView *m_ioView; LocalsView *m_localsView; QPointer m_menu; QAction *m_breakpoint; QUrl m_lastExecUrl; int m_lastExecLine; int m_lastExecFrame; bool m_focusOnInput; }; #endif diff --git a/addons/kate-ctags/kate_ctags_plugin.cpp b/addons/kate-ctags/kate_ctags_plugin.cpp index 31c222c69..3af25faaf 100644 --- a/addons/kate-ctags/kate_ctags_plugin.cpp +++ b/addons/kate-ctags/kate_ctags_plugin.cpp @@ -1,246 +1,246 @@ /* Description : Kate CTags plugin * * Copyright (C) 2008-2011 by Kare Sars * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . */ #include "kate_ctags_plugin.h" -#include -#include #include +#include +#include #include #include #include -#include +#include #include +#include #include -#include +#include #include #include -#include K_PLUGIN_FACTORY_WITH_JSON(KateCTagsPluginFactory, "katectagsplugin.json", registerPlugin();) /******************************************************************/ KateCTagsPlugin::KateCTagsPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { // FIXME KF5 // KGlobal::locale()->insertCatalog("kate-ctags-plugin"); } /******************************************************************/ QObject *KateCTagsPlugin::createView(KTextEditor::MainWindow *mainWindow) { m_view = new KateCTagsView(this, mainWindow); return m_view; } /******************************************************************/ KTextEditor::ConfigPage *KateCTagsPlugin::configPage(int number, QWidget *parent) { if (number != 0) return nullptr; return new KateCTagsConfigPage(parent, this); } /******************************************************************/ void KateCTagsPlugin::readConfig() { } /******************************************************************/ KateCTagsConfigPage::KateCTagsConfigPage(QWidget *parent, KateCTagsPlugin *plugin) : KTextEditor::ConfigPage(parent) , m_plugin(plugin) { m_confUi.setupUi(this); m_confUi.cmdEdit->setText(DEFAULT_CTAGS_CMD); m_confUi.addButton->setToolTip(i18n("Add a directory to index.")); m_confUi.addButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_confUi.delButton->setToolTip(i18n("Remove a directory.")); m_confUi.delButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); m_confUi.updateDB->setToolTip(i18n("(Re-)generate the common CTags database.")); m_confUi.updateDB->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(m_confUi.updateDB, &QPushButton::clicked, this, &KateCTagsConfigPage::updateGlobalDB); connect(m_confUi.addButton, &QPushButton::clicked, this, &KateCTagsConfigPage::addGlobalTagTarget); connect(m_confUi.delButton, &QPushButton::clicked, this, &KateCTagsConfigPage::delGlobalTagTarget); connect(&m_proc, static_cast(&QProcess::finished), this, &KateCTagsConfigPage::updateDone); reset(); } /******************************************************************/ QString KateCTagsConfigPage::name() const { return i18n("CTags"); } /******************************************************************/ QString KateCTagsConfigPage::fullName() const { return i18n("CTags Settings"); } /******************************************************************/ QIcon KateCTagsConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("text-x-csrc")); } /******************************************************************/ void KateCTagsConfigPage::apply() { KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CTags")); config.writeEntry("GlobalCommand", m_confUi.cmdEdit->text()); config.writeEntry("GlobalNumTargets", m_confUi.targetList->count()); QString nr; for (int i = 0; i < m_confUi.targetList->count(); i++) { nr = QStringLiteral("%1").arg(i, 3); config.writeEntry(QStringLiteral("GlobalTarget_") + nr, m_confUi.targetList->item(i)->text()); } config.sync(); } /******************************************************************/ void KateCTagsConfigPage::reset() { KConfigGroup config(KSharedConfig::openConfig(), "CTags"); m_confUi.cmdEdit->setText(config.readEntry(QStringLiteral("GlobalCommand"), DEFAULT_CTAGS_CMD)); int numEntries = config.readEntry(QStringLiteral("GlobalNumTargets"), 0); QString nr; QString target; for (int i = 0; i < numEntries; i++) { nr = QStringLiteral("%1").arg(i, 3); target = config.readEntry(QLatin1String("GlobalTarget_") + nr, QString()); if (!listContains(target)) { new QListWidgetItem(target, m_confUi.targetList); } } config.sync(); } /******************************************************************/ void KateCTagsConfigPage::addGlobalTagTarget() { QFileDialog dialog; dialog.setFileMode(QFileDialog::Directory); // dialog.setMode(KFile::Directory | KFile::Files | KFile::ExistingOnly | KFile::LocalOnly); QString dir; if (m_confUi.targetList->currentItem()) { dir = m_confUi.targetList->currentItem()->text(); } else if (m_confUi.targetList->item(0)) { dir = m_confUi.targetList->item(0)->text(); } dialog.setDirectory(dir); // i18n("CTags Database Location")); if (dialog.exec() != QDialog::Accepted) { return; } QStringList urls = dialog.selectedFiles(); for (int i = 0; i < urls.size(); i++) { if (!listContains(urls[i])) { new QListWidgetItem(urls[i], m_confUi.targetList); } } } /******************************************************************/ void KateCTagsConfigPage::delGlobalTagTarget() { delete m_confUi.targetList->currentItem(); } /******************************************************************/ bool KateCTagsConfigPage::listContains(const QString &target) { for (int i = 0; i < m_confUi.targetList->count(); i++) { if (m_confUi.targetList->item(i)->text() == target) { return true; } } return false; } /******************************************************************/ void KateCTagsConfigPage::updateGlobalDB() { if (m_proc.state() != QProcess::NotRunning) { return; } QString targets; QString target; for (int i = 0; i < m_confUi.targetList->count(); i++) { target = m_confUi.targetList->item(i)->text(); if (target.endsWith(QLatin1Char('/')) || target.endsWith(QLatin1Char('\\'))) { target = target.left(target.size() - 1); } targets += target + QLatin1Char(' '); } QString file = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/katectags"); QDir().mkpath(file); file += QLatin1String("/common_db"); if (targets.isEmpty()) { QFile::remove(file); return; } QString command = QStringLiteral("%1 -f %2 %3").arg(m_confUi.cmdEdit->text(), file, targets); m_proc.start(command); if (!m_proc.waitForStarted(500)) { KMessageBox::error(nullptr, i18n("Failed to run \"%1\". exitStatus = %2", command, m_proc.exitStatus())); return; } m_confUi.updateDB->setDisabled(true); QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); } /******************************************************************/ void KateCTagsConfigPage::updateDone(int exitCode, QProcess::ExitStatus status) { if (status == QProcess::CrashExit) { KMessageBox::error(this, i18n("The CTags executable crashed.")); } else if (exitCode != 0) { KMessageBox::error(this, i18n("The CTags command exited with code %1", exitCode)); } m_confUi.updateDB->setDisabled(false); QApplication::restoreOverrideCursor(); } #include "kate_ctags_plugin.moc" diff --git a/addons/kate-ctags/kate_ctags_plugin.h b/addons/kate-ctags/kate_ctags_plugin.h index 6dbf299a6..828bb5e45 100644 --- a/addons/kate-ctags/kate_ctags_plugin.h +++ b/addons/kate-ctags/kate_ctags_plugin.h @@ -1,92 +1,92 @@ #ifndef KATE_CTAGS_PLUGIN_H #define KATE_CTAGS_PLUGIN_H /* Description : Kate CTags plugin * * Copyright (C) 2008-2011 by Kare Sars * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . */ -#include #include +#include -#include -#include #include #include +#include +#include #include "kate_ctags_view.h" #include "ui_CTagsGlobalConfig.h" //******************************************************************/ class KateCTagsPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit KateCTagsPlugin(QObject *parent = nullptr, const QList & = QList()); ~KateCTagsPlugin() override { } QObject *createView(KTextEditor::MainWindow *mainWindow) override; int configPages() const override { return 1; } KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; void readConfig(); KateCTagsView *m_view = nullptr; }; //******************************************************************/ class KateCTagsConfigPage : public KTextEditor::ConfigPage { Q_OBJECT public: explicit KateCTagsConfigPage(QWidget *parent = nullptr, KateCTagsPlugin *plugin = nullptr); ~KateCTagsConfigPage() override { } QString name() const override; QString fullName() const override; QIcon icon() const override; void apply() override; void reset() override; void defaults() override { } private Q_SLOTS: void addGlobalTagTarget(); void delGlobalTagTarget(); void updateGlobalDB(); void updateDone(int exitCode, QProcess::ExitStatus status); private: bool listContains(const QString &target); QProcess m_proc; KateCTagsPlugin *m_plugin; Ui_CTagsGlobalConfig m_confUi; }; #endif diff --git a/addons/kate-ctags/kate_ctags_view.cpp b/addons/kate-ctags/kate_ctags_view.cpp index 8d3a4b2e9..4c9474185 100644 --- a/addons/kate-ctags/kate_ctags_view.cpp +++ b/addons/kate-ctags/kate_ctags_view.cpp @@ -1,614 +1,614 @@ /* Description : Kate CTags plugin * * Copyright (C) 2008-2011 by Kare Sars * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . */ #include "kate_ctags_view.h" -#include "kate_ctags_plugin.h" #include "kate_ctags_debug.h" +#include "kate_ctags_plugin.h" -#include #include +#include #include -#include #include #include +#include #include +#include #include -#include #include -#include +#include /******************************************************************/ KateCTagsView::KateCTagsView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_proc(nullptr) { KXMLGUIClient::setComponentName(QStringLiteral("katectags"), i18n("Kate CTag")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = mainWin->createToolView(plugin, QStringLiteral("kate_plugin_katectagsplugin"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), i18n("CTags")); m_mWin = mainWin; QAction *back = actionCollection()->addAction(QStringLiteral("ctags_return_step")); back->setText(i18n("Jump back one step")); connect(back, &QAction::triggered, this, &KateCTagsView::stepBack); QAction *decl = actionCollection()->addAction(QStringLiteral("ctags_lookup_current_as_declaration")); decl->setText(i18n("Go to Declaration")); connect(decl, &QAction::triggered, this, &KateCTagsView::gotoDeclaration); QAction *defin = actionCollection()->addAction(QStringLiteral("ctags_lookup_current_as_definition")); defin->setText(i18n("Go to Definition")); connect(defin, &QAction::triggered, this, &KateCTagsView::gotoDefinition); QAction *lookup = actionCollection()->addAction(QStringLiteral("ctags_lookup_current")); lookup->setText(i18n("Lookup Current Text")); connect(lookup, &QAction::triggered, this, &KateCTagsView::lookupTag); QAction *updateDB = actionCollection()->addAction(QStringLiteral("ctags_update_global_db")); updateDB->setText(i18n("Configure ...")); connect(updateDB, &QAction::triggered, this, [this, plugin](bool) { if (m_mWin) { KateCTagsPlugin *p = static_cast(plugin); QDialog *confWin = new QDialog(m_mWin->window()); confWin->setAttribute(Qt::WA_DeleteOnClose); auto confPage = p->configPage(0, confWin); auto controls = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, confWin); connect(confWin, &QDialog::accepted, confPage, &KTextEditor::ConfigPage::apply); connect(controls, &QDialogButtonBox::accepted, confWin, &QDialog::accept); connect(controls, &QDialogButtonBox::rejected, confWin, &QDialog::reject); auto layout = new QVBoxLayout(confWin); layout->addWidget(confPage); layout->addWidget(controls); confWin->setLayout(layout); confWin->setWindowTitle(i18nc("@title:window", "Configure CTags Plugin")); confWin->setWindowIcon(confPage->icon()); confWin->show(); confWin->exec(); } }); // popup menu m_menu = new KActionMenu(i18n("CTags"), this); actionCollection()->addAction(QStringLiteral("popup_ctags"), m_menu); m_gotoDec = m_menu->menu()->addAction(i18n("Go to Declaration: %1", QString()), this, &KateCTagsView::gotoDeclaration); m_gotoDef = m_menu->menu()->addAction(i18n("Go to Definition: %1", QString()), this, &KateCTagsView::gotoDefinition); m_lookup = m_menu->menu()->addAction(i18n("Lookup: %1", QString()), this, &KateCTagsView::lookupTag); connect(m_menu->menu(), &QMenu::aboutToShow, this, &KateCTagsView::aboutToShow); QWidget *ctagsWidget = new QWidget(m_toolView.data()); m_ctagsUi.setupUi(ctagsWidget); m_ctagsUi.cmdEdit->setText(DEFAULT_CTAGS_CMD); m_ctagsUi.addButton->setToolTip(i18n("Add a directory to index.")); m_ctagsUi.addButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_ctagsUi.delButton->setToolTip(i18n("Remove a directory.")); m_ctagsUi.delButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); m_ctagsUi.updateButton->setToolTip(i18n("(Re-)generate the session specific CTags database.")); m_ctagsUi.updateButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ctagsUi.updateButton2->setToolTip(i18n("(Re-)generate the session specific CTags database.")); m_ctagsUi.updateButton2->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ctagsUi.resetCMD->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ctagsUi.tagsFile->setToolTip(i18n("Select new or existing database file.")); m_ctagsUi.tagsFile->setMode(KFile::File); connect(m_ctagsUi.resetCMD, &QToolButton::clicked, this, &KateCTagsView::resetCMD); connect(m_ctagsUi.addButton, &QPushButton::clicked, this, &KateCTagsView::addTagTarget); connect(m_ctagsUi.delButton, &QPushButton::clicked, this, &KateCTagsView::delTagTarget); connect(m_ctagsUi.updateButton, &QPushButton::clicked, this, &KateCTagsView::updateSessionDB); connect(m_ctagsUi.updateButton2, &QPushButton::clicked, this, &KateCTagsView::updateSessionDB); connect(&m_proc, static_cast(&QProcess::finished), this, &KateCTagsView::updateDone); connect(m_ctagsUi.inputEdit, &QLineEdit::textChanged, this, &KateCTagsView::startEditTmr); m_editTimer.setSingleShot(true); connect(&m_editTimer, &QTimer::timeout, this, &KateCTagsView::editLookUp); connect(m_ctagsUi.tagTreeWidget, &QTreeWidget::itemActivated, this, &KateCTagsView::tagHitClicked); connect(m_mWin, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KateCTagsView::handleEsc); m_toolView->layout()->addWidget(ctagsWidget); m_toolView->installEventFilter(this); m_mWin->guiFactory()->addClient(this); m_commonDB = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/katectags/common_db"); } /******************************************************************/ KateCTagsView::~KateCTagsView() { if (m_mWin && m_mWin->guiFactory()) { m_mWin->guiFactory()->removeClient(this); } if (m_toolView) { delete m_toolView; } } /******************************************************************/ void KateCTagsView::aboutToShow() { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } if (Tags::hasTag(m_commonDB, currWord) || Tags::hasTag(m_ctagsUi.tagsFile->text(), currWord)) { QString squeezed = KStringHandler::csqueeze(currWord, 30); m_gotoDec->setText(i18n("Go to Declaration: %1", squeezed)); m_gotoDef->setText(i18n("Go to Definition: %1", squeezed)); m_lookup->setText(i18n("Lookup: %1", squeezed)); } } /******************************************************************/ void KateCTagsView::readSessionConfig(const KConfigGroup &cg) { m_ctagsUi.cmdEdit->setText(cg.readEntry("TagsGenCMD", DEFAULT_CTAGS_CMD)); int numEntries = cg.readEntry("SessionNumTargets", 0); QString nr; QString target; for (int i = 0; i < numEntries; i++) { nr = QStringLiteral("%1").arg(i, 3); target = cg.readEntry(QStringLiteral("SessionTarget_%1").arg(nr), QString()); if (!listContains(target)) { new QListWidgetItem(target, m_ctagsUi.targetList); } } QString sessionDB = cg.readEntry("SessionDatabase", QString()); m_ctagsUi.tagsFile->setText(sessionDB); } /******************************************************************/ void KateCTagsView::writeSessionConfig(KConfigGroup &cg) { cg.writeEntry("TagsGenCMD", m_ctagsUi.cmdEdit->text()); cg.writeEntry("SessionNumTargets", m_ctagsUi.targetList->count()); QString nr; for (int i = 0; i < m_ctagsUi.targetList->count(); i++) { nr = QStringLiteral("%1").arg(i, 3); cg.writeEntry(QStringLiteral("SessionTarget_%1").arg(nr), m_ctagsUi.targetList->item(i)->text()); } cg.writeEntry("SessionDatabase", m_ctagsUi.tagsFile->text()); cg.sync(); } /******************************************************************/ void KateCTagsView::stepBack() { if (m_jumpStack.isEmpty()) { return; } TagJump back; back = m_jumpStack.pop(); m_mWin->openUrl(back.url); m_mWin->activeView()->setCursorPosition(back.cursor); m_mWin->activeView()->setFocus(); } /******************************************************************/ void KateCTagsView::lookupTag() { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } setNewLookupText(currWord); Tags::TagList list = Tags::getExactMatches(m_ctagsUi.tagsFile->text(), currWord); if (list.empty()) list = Tags::getExactMatches(m_commonDB, currWord); displayHits(list); // activate the hits tab m_ctagsUi.tabWidget->setCurrentIndex(0); m_mWin->showToolView(m_toolView); } /******************************************************************/ void KateCTagsView::editLookUp() { Tags::TagList list = Tags::getPartialMatches(m_ctagsUi.tagsFile->text(), m_ctagsUi.inputEdit->text()); if (list.empty()) list = Tags::getPartialMatches(m_commonDB, m_ctagsUi.inputEdit->text()); displayHits(list); } /******************************************************************/ void KateCTagsView::gotoDefinition() { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } QStringList types; types << QStringLiteral("S") << QStringLiteral("d") << QStringLiteral("f") << QStringLiteral("t") << QStringLiteral("v"); gotoTagForTypes(currWord, types); } /******************************************************************/ void KateCTagsView::gotoDeclaration() { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } QStringList types; types << QStringLiteral("L") << QStringLiteral("c") << QStringLiteral("e") << QStringLiteral("g") << QStringLiteral("m") << QStringLiteral("n") << QStringLiteral("p") << QStringLiteral("s") << QStringLiteral("u") << QStringLiteral("x"); gotoTagForTypes(currWord, types); } /******************************************************************/ void KateCTagsView::gotoTagForTypes(const QString &word, const QStringList &types) { Tags::TagList list = Tags::getMatches(m_ctagsUi.tagsFile->text(), word, false, types); if (list.empty()) list = Tags::getMatches(m_commonDB, word, false, types); // qCDebug(KTECTAGS) << "found" << list.count() << word << types; setNewLookupText(word); if (list.count() < 1) { m_ctagsUi.tagTreeWidget->clear(); new QTreeWidgetItem(m_ctagsUi.tagTreeWidget, QStringList(i18n("No hits found"))); m_ctagsUi.tabWidget->setCurrentIndex(0); m_mWin->showToolView(m_toolView); return; } displayHits(list); if (list.count() == 1) { Tags::TagEntry tag = list.first(); jumpToTag(tag.file, tag.pattern, word); } else { Tags::TagEntry tag = list.first(); jumpToTag(tag.file, tag.pattern, word); m_ctagsUi.tabWidget->setCurrentIndex(0); m_mWin->showToolView(m_toolView); } } /******************************************************************/ void KateCTagsView::setNewLookupText(const QString &newString) { m_ctagsUi.inputEdit->blockSignals(true); m_ctagsUi.inputEdit->setText(newString); m_ctagsUi.inputEdit->blockSignals(false); } /******************************************************************/ void KateCTagsView::displayHits(const Tags::TagList &list) { m_ctagsUi.tagTreeWidget->clear(); if (list.isEmpty()) { new QTreeWidgetItem(m_ctagsUi.tagTreeWidget, QStringList(i18n("No hits found"))); return; } m_ctagsUi.tagTreeWidget->setSortingEnabled(false); for (const auto &tag : list) { QTreeWidgetItem *item = new QTreeWidgetItem(m_ctagsUi.tagTreeWidget); item->setText(0, tag.tag); item->setText(1, tag.type); item->setText(2, tag.file); item->setData(0, Qt::UserRole, tag.pattern); QString pattern = tag.pattern; pattern.replace(QStringLiteral("\\/"), QStringLiteral("/")); pattern = pattern.mid(2, pattern.length() - 4); pattern = pattern.trimmed(); item->setData(0, Qt::ToolTipRole, pattern); item->setData(1, Qt::ToolTipRole, pattern); item->setData(2, Qt::ToolTipRole, pattern); } m_ctagsUi.tagTreeWidget->setSortingEnabled(true); } /******************************************************************/ void KateCTagsView::tagHitClicked(QTreeWidgetItem *item) { // get stuff const QString file = item->data(2, Qt::DisplayRole).toString(); const QString pattern = item->data(0, Qt::UserRole).toString(); const QString word = item->data(0, Qt::DisplayRole).toString(); jumpToTag(file, pattern, word); } /******************************************************************/ QString KateCTagsView::currentWord() { KTextEditor::View *kv = m_mWin->activeView(); if (!kv) { qCDebug(KTECTAGS) << "no KTextEditor::View" << endl; return QString(); } if (kv->selection() && kv->selectionRange().onSingleLine()) { return kv->selectionText(); } if (!kv->cursorPosition().isValid()) { qCDebug(KTECTAGS) << "cursor not valid!" << endl; return QString(); } int line = kv->cursorPosition().line(); int col = kv->cursorPosition().column(); bool includeColon = m_ctagsUi.cmdEdit->text().contains(QLatin1String("--extra=+q")); QString linestr = kv->document()->line(line); int startPos = qMax(qMin(col, linestr.length() - 1), 0); int endPos = startPos; while (startPos >= 0 && (linestr[startPos].isLetterOrNumber() || (linestr[startPos] == QLatin1Char(':') && includeColon) || linestr[startPos] == QLatin1Char('_') || linestr[startPos] == QLatin1Char('~'))) { startPos--; } while (endPos < (int)linestr.length() && (linestr[endPos].isLetterOrNumber() || (linestr[endPos] == QLatin1Char(':') && includeColon) || linestr[endPos] == QLatin1Char('_'))) { endPos++; } if (startPos == endPos) { qCDebug(KTECTAGS) << "no word found!" << endl; return QString(); } linestr = linestr.mid(startPos + 1, endPos - startPos - 1); while (linestr.endsWith(QLatin1Char(':'))) { linestr.remove(linestr.size() - 1, 1); } while (linestr.startsWith(QLatin1Char(':'))) { linestr.remove(0, 1); } // qCDebug(KTECTAGS) << linestr; return linestr; } /******************************************************************/ void KateCTagsView::jumpToTag(const QString &file, const QString &pattern, const QString &word) { if (pattern.isEmpty()) return; // generate a regexp from the pattern // ctags interestingly escapes "/", but apparently nothing else. lets revert that QString unescaped = pattern; unescaped.replace(QStringLiteral("\\/"), QStringLiteral("/")); // most of the time, the ctags pattern has the form /^foo$/ // but this isn't true for some macro definitions // where the form is only /^foo/ // I have no idea if this is a ctags bug or not, but we have to deal with it QString reduced; QString escaped; QString re_string; if (unescaped.endsWith(QLatin1String("$/"))) { reduced = unescaped.mid(2, unescaped.length() - 4); escaped = QRegularExpression::escape(reduced); re_string = QStringLiteral("^%1$").arg(escaped); } else { reduced = unescaped.mid(2, unescaped.length() - 3); escaped = QRegularExpression::escape(reduced); re_string = QStringLiteral("^%1").arg(escaped); } QRegularExpression re(re_string); // save current location TagJump from; from.url = m_mWin->activeView()->document()->url(); from.cursor = m_mWin->activeView()->cursorPosition(); m_jumpStack.push(from); // open/activate the new file QFileInfo fInfo(file); // qCDebug(KTECTAGS) << pattern << file << fInfo.absoluteFilePath(); m_mWin->openUrl(QUrl::fromLocalFile(fInfo.absoluteFilePath())); // any view active? if (!m_mWin->activeView()) { return; } // look for the line QString linestr; int line; for (line = 0; line < m_mWin->activeView()->document()->lines(); line++) { linestr = m_mWin->activeView()->document()->line(line); if (linestr.indexOf(re) > -1) break; } // activate the line if (line != m_mWin->activeView()->document()->lines()) { // line found now look for the column int column = linestr.indexOf(word) + (word.length() / 2); m_mWin->activeView()->setCursorPosition(KTextEditor::Cursor(line, column)); } m_mWin->activeView()->setFocus(); } /******************************************************************/ void KateCTagsView::startEditTmr() { if (m_ctagsUi.inputEdit->text().size() > 3) { m_editTimer.start(500); } } /******************************************************************/ void KateCTagsView::updateSessionDB() { if (m_proc.state() != QProcess::NotRunning) { return; } QString targets; QString target; for (int i = 0; i < m_ctagsUi.targetList->count(); i++) { target = m_ctagsUi.targetList->item(i)->text(); if (target.endsWith(QLatin1Char('/')) || target.endsWith(QLatin1Char('\\'))) { target = target.left(target.size() - 1); } targets += target + QLatin1Char(' '); } QString pluginFolder = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/katectags"); QDir().mkpath(pluginFolder); if (m_ctagsUi.tagsFile->text().isEmpty()) { // FIXME we need a way to get the session name pluginFolder += QLatin1String("/session_db_"); pluginFolder += QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMdd_hhmmss")); m_ctagsUi.tagsFile->setText(pluginFolder); } if (targets.isEmpty()) { KMessageBox::error(nullptr, i18n("No folders or files to index")); QFile::remove(m_ctagsUi.tagsFile->text()); return; } QString command = QStringLiteral("%1 -f %2 %3").arg(m_ctagsUi.cmdEdit->text(), m_ctagsUi.tagsFile->text(), targets); m_proc.start(command); if (!m_proc.waitForStarted(500)) { KMessageBox::error(nullptr, i18n("Failed to run \"%1\". exitStatus = %2", command, m_proc.exitStatus())); return; } QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); m_ctagsUi.updateButton->setDisabled(true); m_ctagsUi.updateButton2->setDisabled(true); } /******************************************************************/ void KateCTagsView::updateDone(int exitCode, QProcess::ExitStatus status) { if (status == QProcess::CrashExit) { KMessageBox::error(m_toolView, i18n("The CTags executable crashed.")); } else if (exitCode != 0) { KMessageBox::error(m_toolView, i18n("The CTags program exited with code %1: %2", exitCode, QString::fromLocal8Bit(m_proc.readAllStandardError()))); } m_ctagsUi.updateButton->setDisabled(false); m_ctagsUi.updateButton2->setDisabled(false); QApplication::restoreOverrideCursor(); } /******************************************************************/ void KateCTagsView::addTagTarget() { QFileDialog dialog; dialog.setDirectory(QFileInfo(m_mWin->activeView()->document()->url().path()).path()); dialog.setFileMode(QFileDialog::Directory); // i18n("CTags Database Location")); if (dialog.exec() != QDialog::Accepted) { return; } QStringList urls = dialog.selectedFiles(); for (int i = 0; i < urls.size(); i++) { if (!listContains(urls[i])) { new QListWidgetItem(urls[i], m_ctagsUi.targetList); } } } /******************************************************************/ void KateCTagsView::delTagTarget() { delete m_ctagsUi.targetList->currentItem(); } /******************************************************************/ bool KateCTagsView::listContains(const QString &target) { for (int i = 0; i < m_ctagsUi.targetList->count(); i++) { if (m_ctagsUi.targetList->item(i)->text() == target) { return true; } } return false; } /******************************************************************/ bool KateCTagsView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_mWin->hideToolView(m_toolView); event->accept(); return true; } } return QObject::eventFilter(obj, event); } /******************************************************************/ void KateCTagsView::resetCMD() { m_ctagsUi.cmdEdit->setText(DEFAULT_CTAGS_CMD); } /******************************************************************/ void KateCTagsView::handleEsc(QEvent *e) { if (!m_mWin) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_toolView->isVisible()) { m_mWin->hideToolView(m_toolView); } } } diff --git a/addons/kate-ctags/kate_ctags_view.h b/addons/kate-ctags/kate_ctags_view.h index 272a131bd..585824ca5 100644 --- a/addons/kate-ctags/kate_ctags_view.h +++ b/addons/kate-ctags/kate_ctags_view.h @@ -1,114 +1,114 @@ #ifndef KATE_CTAGS_VIEW_H #define KATE_CTAGS_VIEW_H /* Description : Kate CTags plugin * * Copyright (C) 2008-2011 by Kare Sars * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . */ -#include #include +#include #include #include #include -#include #include +#include -#include -#include #include #include +#include +#include #include "tags.h" #include "ui_kate_ctags.h" const static QString DEFAULT_CTAGS_CMD = QStringLiteral("ctags -R --c++-types=+px --extra=+q --excmd=pattern --exclude=Makefile --exclude=."); typedef struct { QUrl url; KTextEditor::Cursor cursor; } TagJump; /******************************************************************/ class KateCTagsView : public QObject, public KXMLGUIClient, public KTextEditor::SessionConfigInterface { Q_OBJECT Q_INTERFACES(KTextEditor::SessionConfigInterface) public: KateCTagsView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin); ~KateCTagsView() override; // reimplemented: read and write session config void readSessionConfig(const KConfigGroup &config) override; void writeSessionConfig(KConfigGroup &config) override; public Q_SLOTS: void gotoDefinition(); void gotoDeclaration(); void lookupTag(); void stepBack(); void editLookUp(); void aboutToShow(); void tagHitClicked(QTreeWidgetItem *); void startEditTmr(); void addTagTarget(); void delTagTarget(); void updateSessionDB(); void updateDone(int exitCode, QProcess::ExitStatus status); protected: bool eventFilter(QObject *obj, QEvent *ev) override; private Q_SLOTS: void resetCMD(); void handleEsc(QEvent *e); private: bool listContains(const QString &target); QString currentWord(); void setNewLookupText(const QString &newText); void displayHits(const Tags::TagList &list); void gotoTagForTypes(const QString &tag, QStringList const &types); void jumpToTag(const QString &file, const QString &pattern, const QString &word); QPointer m_mWin; QPointer m_toolView; Ui::kateCtags m_ctagsUi; QPointer m_menu; QAction *m_gotoDef; QAction *m_gotoDec; QAction *m_lookup; QProcess m_proc; QString m_commonDB; QTimer m_editTimer; QStack m_jumpStack; }; #endif diff --git a/addons/kate-ctags/tags.h b/addons/kate-ctags/tags.h index 083727d1e..bdb16c30e 100644 --- a/addons/kate-ctags/tags.h +++ b/addons/kate-ctags/tags.h @@ -1,68 +1,68 @@ /*************************************************************************** * Copyright (C) 2004 by Jens Dagerbo * * jens.dagerbo@swipnet.se * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef TAGS_H #define TAGS_H +#include #include #include -#include class Tags { public: struct TagEntry { TagEntry(); TagEntry(const QString &tag, const QString &type, const QString &file, const QString &pattern); QString tag; QString type; QString file; QString pattern; }; typedef QList TagList; /** * Method to set the tag database filename * @param file the tag database filename */ static void setTagsFile(const QString &file); static QString getTagsFile(); /** * Method to check if the tag database contains a specific tag * @param tag Tag to look up * @return returns true if tag database contains 'tag' */ static bool hasTag(const QString &tag); static bool hasTag(const QString &fileName, const QString &tag); static unsigned int numberOfPartialMatches(const QString &tagpart); static unsigned int numberOfExactMatches(const QString &tag); static unsigned int numberOfMatches(const QString &tagpart, bool partial); static TagList getPartialMatches(const QString &tagpart); static TagList getExactMatches(const QString &tag); static TagList getMatches(const QString &tagpart, bool partial, const QStringList &types = QStringList()); static TagList getPartialMatches(const QString &file, const QString &tagpart); static TagList getExactMatches(const QString &file, const QString &tag); static TagList getMatches(const QString &file, const QString &tagpart, bool partial, const QStringList &types = QStringList()); private: static QString _tagsfile; }; #endif // kate: space-indent off; indent-width 4; tab-width 4; show-tabs off; diff --git a/addons/katebuild-plugin/SelectTargetView.cpp b/addons/katebuild-plugin/SelectTargetView.cpp index 6bf93eb1e..07a36d0cd 100644 --- a/addons/katebuild-plugin/SelectTargetView.cpp +++ b/addons/katebuild-plugin/SelectTargetView.cpp @@ -1,126 +1,126 @@ /*************************************************************************** * This file is part of Kate build plugin * * Copyright 2014 KÃ¥re Särs * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "SelectTargetView.h" #include "TargetHtmlDelegate.h" -#include #include -#include +#include #include +#include class TargetFilterProxyModel : public QSortFilterProxyModel { public: TargetFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { } bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override { if (m_filter.isEmpty()) { return true; } QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent); QString name = index0.data().toString(); if (index0.internalId() == 0xffffffff) { int i = 0; auto childIndex = index0.model()->index(i, 0, index0); while (childIndex.data().isValid()) { name = childIndex.data().toString(); if (name.contains(m_filter, Qt::CaseInsensitive)) { return true; } i++; childIndex = index0.model()->index(i, 0, index0); } return false; } return name.contains(m_filter, Qt::CaseInsensitive); } void setFilter(const QString &filter) { m_filter = filter; invalidateFilter(); } Qt::ItemFlags flags(const QModelIndex &index) const override { if (!index.isValid()) { return nullptr; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } QString m_filter; }; SelectTargetView::SelectTargetView(QAbstractItemModel *model, QWidget *parent) : QDialog(parent) { setupUi(this); m_proxyModel = new TargetFilterProxyModel(this); m_proxyModel->setSourceModel(model); u_treeView->setModel(m_proxyModel); u_treeView->expandAll(); u_treeView->resizeColumnToContents(0); u_treeView->setEditTriggers(QAbstractItemView::EditKeyPressed); setFocusProxy(u_filterEdit); connect(u_filterEdit, &QLineEdit::textChanged, this, &SelectTargetView::setFilter); connect(u_treeView, &QTreeView::doubleClicked, this, &SelectTargetView::accept); u_filterEdit->installEventFilter(this); } void SelectTargetView::setFilter(const QString &filter) { m_proxyModel->setFilter(filter); u_treeView->expandAll(); } const QModelIndex SelectTargetView::currentIndex() const { return m_proxyModel->mapToSource(u_treeView->currentIndex()); } void SelectTargetView::setCurrentIndex(const QModelIndex &index) { u_treeView->setCurrentIndex(m_proxyModel->mapFromSource(index)); } bool SelectTargetView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (obj == u_filterEdit) { if ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down) || (keyEvent->key() == Qt::Key_PageUp) || (keyEvent->key() == Qt::Key_PageDown)) { QCoreApplication::sendEvent(u_treeView, event); return true; } } } return QDialog::eventFilter(obj, event); } diff --git a/addons/katebuild-plugin/TargetHtmlDelegate.cpp b/addons/katebuild-plugin/TargetHtmlDelegate.cpp index d961ff55c..d3515582e 100644 --- a/addons/katebuild-plugin/TargetHtmlDelegate.cpp +++ b/addons/katebuild-plugin/TargetHtmlDelegate.cpp @@ -1,183 +1,183 @@ /*************************************************************************** * This file is part of Kate search plugin * * Copyright 2014 KÃ¥re Särs * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "TargetHtmlDelegate.h" #include "TargetModel.h" -#include -#include -#include #include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include #include "UrlInserter.h" #include TargetHtmlDelegate::TargetHtmlDelegate(QObject *parent) : QStyledItemDelegate(parent) , m_isEditing(false) { connect(this, &TargetHtmlDelegate::sendEditStart, this, &TargetHtmlDelegate::editStarted); } TargetHtmlDelegate::~TargetHtmlDelegate() { } void TargetHtmlDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem options = option; initStyleOption(&options, index); QTextDocument doc; QString str; if (!index.parent().isValid()) { if (index.column() == 0) { str = i18nc("T as in Target set", "T: %1", index.data().toString().toHtmlEscaped()); } else if (index.column() == 1) { str = i18nc("D as in working Directory", "Dir: %1", index.data().toString().toHtmlEscaped()); } } else { str = index.data().toString().toHtmlEscaped(); } if (option.state & QStyle::State_Selected) { str = QStringLiteral("").arg(option.palette.highlightedText().color().name()) + str + QStringLiteral(""); } doc.setHtml(str); doc.setDocumentMargin(2); painter->save(); // paint background if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); } else { painter->fillRect(option.rect, option.palette.base()); } options.text = QString(); // clear old text options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget); // draw text painter->translate(option.rect.x(), option.rect.y()); if (index.column() == 0 && index.internalId() != TargetModel::InvalidIndex) { painter->translate(25, 0); } doc.drawContents(painter); painter->restore(); } QSize TargetHtmlDelegate::sizeHint(const QStyleOptionViewItem & /* option */, const QModelIndex &index) const { QTextDocument doc; doc.setHtml(index.data().toString().toHtmlEscaped()); doc.setDocumentMargin(2); if (index.column() == 0 && index.internalId() != TargetModel::InvalidIndex) { return doc.size().toSize() + QSize(30, 0); // add margin for the check-box; } return doc.size().toSize(); } QWidget *TargetHtmlDelegate::createEditor(QWidget *dparent, const QStyleOptionViewItem & /* option */, const QModelIndex &index) const { QWidget *editor; if (index.internalId() == TargetModel::InvalidIndex && index.column() == 1) { UrlInserter *requester = new UrlInserter(parent()->property("docUrl").toUrl(), dparent); requester->setReplace(true); editor = requester; editor->setToolTip(i18n("Leave empty to use the directory of the current document.")); } else if (index.column() == 1) { UrlInserter *urlEditor = new UrlInserter(parent()->property("docUrl").toUrl(), dparent); editor = urlEditor; editor->setToolTip(i18n("Use:\n\"%f\" for current file\n\"%d\" for directory of current file\n\"%n\" for current file name without suffix")); } else { QLineEdit *e = new QLineEdit(dparent); QCompleter *completer = new QCompleter(e); completer->setModel(new QDirModel(QStringList(), QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name, e)); e->setCompleter(completer); editor = e; } editor->setAutoFillBackground(true); emit sendEditStart(); connect(editor, &QWidget::destroyed, this, &TargetHtmlDelegate::editEnded); return editor; } void TargetHtmlDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QString value = index.model()->data(index, Qt::EditRole).toString(); if (index.column() == 1) { UrlInserter *ledit = static_cast(editor); if (ledit) ledit->lineEdit()->setText(value); } else { QLineEdit *ledit = static_cast(editor); if (ledit) ledit->setText(value); } } void TargetHtmlDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QString value; if (index.column() == 1) { UrlInserter *ledit = static_cast(editor); value = ledit->lineEdit()->text(); } else { QLineEdit *ledit = static_cast(editor); value = ledit->text(); } model->setData(index, value, Qt::EditRole); } void TargetHtmlDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { QRect rect = option.rect; int heightDiff = QToolButton().sizeHint().height() - rect.height(); int half = heightDiff / 2; rect.adjust(0, -half, 0, heightDiff - half); if (index.column() == 0 && index.internalId() != TargetModel::InvalidIndex) { rect.adjust(25, 0, 0, 0); } editor->setGeometry(rect); } void TargetHtmlDelegate::editStarted() { m_isEditing = true; } void TargetHtmlDelegate::editEnded() { m_isEditing = false; } bool TargetHtmlDelegate::isEditing() const { return m_isEditing; } diff --git a/addons/katebuild-plugin/UrlInserter.cpp b/addons/katebuild-plugin/UrlInserter.cpp index 52ba54204..8ae649c76 100644 --- a/addons/katebuild-plugin/UrlInserter.cpp +++ b/addons/katebuild-plugin/UrlInserter.cpp @@ -1,75 +1,75 @@ /*************************************************************************** * This file is part of Kate search plugin * * Copyright 2014 KÃ¥re Särs * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "UrlInserter.h" -#include -#include +#include +#include +#include #include #include +#include #include -#include -#include -#include +#include UrlInserter::UrlInserter(const QUrl &startUrl, QWidget *parent) : QWidget(parent) , m_startUrl(startUrl) , m_replace(false) { m_lineEdit = new QLineEdit(); QCompleter *completer = new QCompleter(m_lineEdit); completer->setModel(new QDirModel(QStringList(), QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Executable, QDir::Name, m_lineEdit)); m_lineEdit->setCompleter(completer); m_toolButton = new QToolButton(); m_toolButton->setIcon(QIcon::fromTheme(QStringLiteral("archive-insert-directory"))); m_toolButton->setToolTip(i18n("Insert path")); QHBoxLayout *layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(m_lineEdit); layout->addWidget(m_toolButton); setFocusProxy(m_lineEdit); connect(m_toolButton, &QToolButton::clicked, this, &UrlInserter::insertFolder); } void UrlInserter::insertFolder() { QUrl startUrl; if (QFileInfo(m_lineEdit->text()).exists()) { startUrl.setPath(m_lineEdit->text()); } else { startUrl = m_startUrl; } QString folder = QFileDialog::getExistingDirectory(this, i18n("Select directory to insert"), startUrl.path()); if (!folder.isEmpty()) { if (!m_replace) { m_lineEdit->insert(folder); } else { m_lineEdit->setText(folder); } } } void UrlInserter::setReplace(bool replace) { m_replace = replace; } diff --git a/addons/katebuild-plugin/UrlInserter.h b/addons/katebuild-plugin/UrlInserter.h index 293696f37..562803323 100644 --- a/addons/katebuild-plugin/UrlInserter.h +++ b/addons/katebuild-plugin/UrlInserter.h @@ -1,50 +1,50 @@ /*************************************************************************** * This file is part of Kate search plugin * * Copyright 2014 KÃ¥re Särs * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef UrlInserter_H #define UrlInserter_H -#include #include #include #include +#include class UrlInserter : public QWidget { Q_OBJECT public: UrlInserter(const QUrl &startUrl, QWidget *parent); QLineEdit *lineEdit() { return m_lineEdit; } void setReplace(bool replace); public Q_SLOTS: void insertFolder(); private: QLineEdit *m_lineEdit; QToolButton *m_toolButton; QUrl m_startUrl; bool m_replace; }; #endif diff --git a/addons/katebuild-plugin/plugin_katebuild.cpp b/addons/katebuild-plugin/plugin_katebuild.cpp index d857f72a6..fe2a7f105 100644 --- a/addons/katebuild-plugin/plugin_katebuild.cpp +++ b/addons/katebuild-plugin/plugin_katebuild.cpp @@ -1,1254 +1,1254 @@ /* plugin_katebuild.c Kate Plugin ** ** Copyright (C) 2013 by Alexander Neundorf ** Copyright (C) 2006-2015 by KÃ¥re Särs ** Copyright (C) 2011 by Ian Wakeling ** ** This code is mostly a modification of the GPL'ed Make plugin ** by Adriaan de Groot. */ /* ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program in a file called COPYING; if not, write to ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, ** MA 02110-1301, USA. */ #include "plugin_katebuild.h" #include -#include -#include -#include #include -#include -#include -#include #include +#include #include +#include #include +#include +#include +#include +#include #include +#include #include #include -#include #include +#include #include #include #include -#include #include #include #include "SelectTargetView.h" K_PLUGIN_FACTORY_WITH_JSON(KateBuildPluginFactory, "katebuildplugin.json", registerPlugin();) static const QString DefConfigCmd = QStringLiteral("cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr/local ../"); static const QString DefConfClean; static const QString DefTargetName = QStringLiteral("all"); static const QString DefBuildCmd = QStringLiteral("make"); static const QString DefCleanCmd = QStringLiteral("make clean"); static const QString NinjaPrefix = QStringLiteral("[ninja]"); static QIcon messageIcon(KateBuildView::ErrorCategory severity) { #define RETURN_CACHED_ICON(name) \ { \ static QIcon icon(QIcon::fromTheme(QStringLiteral(name))); \ return icon; \ } switch (severity) { case KateBuildView::CategoryError: RETURN_CACHED_ICON("dialog-error") case KateBuildView::CategoryWarning: RETURN_CACHED_ICON("dialog-warning") default: break; } return QIcon(); } struct ItemData { // ensure destruction, but not inadvertently so by a variant value copy QSharedPointer cursor; }; Q_DECLARE_METATYPE(ItemData) /******************************************************************/ KateBuildPlugin::KateBuildPlugin(QObject *parent, const VariantList &) : KTextEditor::Plugin(parent) { // KF5 FIXME KGlobal::locale()->insertCatalog("katebuild-plugin"); } /******************************************************************/ QObject *KateBuildPlugin::createView(KTextEditor::MainWindow *mainWindow) { return new KateBuildView(this, mainWindow); } /******************************************************************/ KateBuildView::KateBuildView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mw) : QObject(mw) , m_win(mw) , m_buildWidget(nullptr) , m_outputWidgetWidth(0) , m_proc(this) , m_stdOut() , m_stdErr() , m_buildCancelled(false) , m_displayModeBeforeBuild(1) // NOTE this will not allow spaces in file names. // e.g. from gcc: "main.cpp:14: error: cannot convert ‘std::string’ to ‘int’ in return" , m_filenameDetector(QStringLiteral("(([a-np-zA-Z]:[\\\\/])?[a-zA-Z0-9_\\.\\+\\-/\\\\]+\\.[a-zA-Z0-9]+):([0-9]+)(.*)")) // e.g. from icpc: "main.cpp(14): error: no suitable conversion function from "std::string" to "int" exists" , m_filenameDetectorIcpc(QStringLiteral("(([a-np-zA-Z]:[\\\\/])?[a-zA-Z0-9_\\.\\+\\-/\\\\]+\\.[a-zA-Z0-9]+)\\(([0-9]+)\\)(:.*)")) , m_filenameDetectorGccWorked(false) , m_newDirDetector(QStringLiteral("make\\[.+\\]: .+ `.*'")) { KXMLGUIClient::setComponentName(QStringLiteral("katebuild"), i18n("Kate Build Plugin")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = mw->createToolView(plugin, QStringLiteral("kate_plugin_katebuildplugin"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), i18n("Build Output")); QAction *a = actionCollection()->addAction(QStringLiteral("select_target")); a->setText(i18n("Select Target...")); a->setIcon(QIcon::fromTheme(QStringLiteral("select"))); connect(a, &QAction::triggered, this, &KateBuildView::slotSelectTarget); a = actionCollection()->addAction(QStringLiteral("build_default_target")); a->setText(i18n("Build Default Target")); connect(a, &QAction::triggered, this, &KateBuildView::slotBuildDefaultTarget); a = actionCollection()->addAction(QStringLiteral("build_previous_target")); a->setText(i18n("Build Previous Target")); connect(a, &QAction::triggered, this, &KateBuildView::slotBuildPreviousTarget); a = actionCollection()->addAction(QStringLiteral("stop")); a->setText(i18n("Stop")); a->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); connect(a, &QAction::triggered, this, &KateBuildView::slotStop); a = actionCollection()->addAction(QStringLiteral("goto_next")); a->setText(i18n("Next Error")); a->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); actionCollection()->setDefaultShortcut(a, Qt::SHIFT + Qt::ALT + Qt::Key_Right); connect(a, &QAction::triggered, this, &KateBuildView::slotNext); a = actionCollection()->addAction(QStringLiteral("goto_prev")); a->setText(i18n("Previous Error")); a->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); actionCollection()->setDefaultShortcut(a, Qt::SHIFT + Qt::ALT + Qt::Key_Left); connect(a, &QAction::triggered, this, &KateBuildView::slotPrev); m_showMarks = a = actionCollection()->addAction(QStringLiteral("show_marks")); a->setText(i18n("Show Marks")); a->setCheckable(true); connect(a, &QAction::triggered, this, &KateBuildView::slotDisplayOption); m_buildWidget = new QWidget(m_toolView); m_buildUi.setupUi(m_buildWidget); m_targetsUi = new TargetsUi(this, m_buildUi.u_tabWidget); m_buildUi.u_tabWidget->insertTab(0, m_targetsUi, i18nc("Tab label", "Target Settings")); m_buildUi.u_tabWidget->setCurrentWidget(m_targetsUi); m_buildWidget->installEventFilter(this); m_buildUi.buildAgainButton->setVisible(true); m_buildUi.cancelBuildButton->setVisible(true); m_buildUi.buildStatusLabel->setVisible(true); m_buildUi.buildAgainButton2->setVisible(false); m_buildUi.cancelBuildButton2->setVisible(false); m_buildUi.buildStatusLabel2->setVisible(false); m_buildUi.extraLineLayout->setAlignment(Qt::AlignRight); m_buildUi.cancelBuildButton->setEnabled(false); m_buildUi.cancelBuildButton2->setEnabled(false); connect(m_buildUi.errTreeWidget, &QTreeWidget::itemClicked, this, &KateBuildView::slotErrorSelected); m_buildUi.plainTextEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_buildUi.plainTextEdit->setReadOnly(true); slotDisplayMode(FullOutput); connect(m_buildUi.displayModeSlider, &QSlider::valueChanged, this, &KateBuildView::slotDisplayMode); connect(m_buildUi.buildAgainButton, &QPushButton::clicked, this, &KateBuildView::slotBuildPreviousTarget); connect(m_buildUi.cancelBuildButton, &QPushButton::clicked, this, &KateBuildView::slotStop); connect(m_buildUi.buildAgainButton2, &QPushButton::clicked, this, &KateBuildView::slotBuildPreviousTarget); connect(m_buildUi.cancelBuildButton2, &QPushButton::clicked, this, &KateBuildView::slotStop); connect(m_targetsUi->newTarget, &QToolButton::clicked, this, &KateBuildView::targetSetNew); connect(m_targetsUi->copyTarget, &QToolButton::clicked, this, &KateBuildView::targetOrSetCopy); connect(m_targetsUi->deleteTarget, &QToolButton::clicked, this, &KateBuildView::targetDelete); connect(m_targetsUi->addButton, &QToolButton::clicked, this, &KateBuildView::slotAddTargetClicked); connect(m_targetsUi->buildButton, &QToolButton::clicked, this, &KateBuildView::slotBuildActiveTarget); connect(m_targetsUi, &TargetsUi::enterPressed, this, &KateBuildView::slotBuildActiveTarget); m_proc.setOutputChannelMode(KProcess::SeparateChannels); connect(&m_proc, static_cast(&QProcess::finished), this, &KateBuildView::slotProcExited); connect(&m_proc, &KProcess::readyReadStandardError, this, &KateBuildView::slotReadReadyStdErr); connect(&m_proc, &KProcess::readyReadStandardOutput, this, &KateBuildView::slotReadReadyStdOut); connect(m_win, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KateBuildView::handleEsc); connect(m_win, &KTextEditor::MainWindow::viewChanged, this, &KateBuildView::slotViewChanged); m_toolView->installEventFilter(this); m_win->guiFactory()->addClient(this); // watch for project plugin view creation/deletion connect(m_win, &KTextEditor::MainWindow::pluginViewCreated, this, &KateBuildView::slotPluginViewCreated); connect(m_win, &KTextEditor::MainWindow::pluginViewDeleted, this, &KateBuildView::slotPluginViewDeleted); // Connect signals from project plugin to our slots m_projectPluginView = m_win->pluginView(QStringLiteral("kateprojectplugin")); slotPluginViewCreated(QStringLiteral("kateprojectplugin"), m_projectPluginView); } /******************************************************************/ KateBuildView::~KateBuildView() { m_win->guiFactory()->removeClient(this); delete m_toolView; } /******************************************************************/ void KateBuildView::readSessionConfig(const KConfigGroup &cg) { int numTargets = cg.readEntry(QStringLiteral("NumTargets"), 0); m_targetsUi->targetsModel.clear(); int tmpIndex; int tmpCmd; if (numTargets == 0) { // either the config is empty or uses the older format m_targetsUi->targetsModel.addTargetSet(i18n("Target Set"), QString()); m_targetsUi->targetsModel.addCommand(0, i18n("build"), cg.readEntry(QStringLiteral("Make Command"), DefBuildCmd)); m_targetsUi->targetsModel.addCommand(0, i18n("clean"), cg.readEntry(QStringLiteral("Clean Command"), DefCleanCmd)); m_targetsUi->targetsModel.addCommand(0, i18n("config"), DefConfigCmd); QString quickCmd = cg.readEntry(QStringLiteral("Quick Compile Command")); if (!quickCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(0, i18n("quick"), quickCmd); } tmpIndex = 0; tmpCmd = 0; } else { for (int i = 0; i < numTargets; i++) { QStringList targetNames = cg.readEntry(QStringLiteral("%1 Target Names").arg(i), QStringList()); QString targetSetName = cg.readEntry(QStringLiteral("%1 Target").arg(i), QString()); QString buildDir = cg.readEntry(QStringLiteral("%1 BuildPath").arg(i), QString()); m_targetsUi->targetsModel.addTargetSet(targetSetName, buildDir); if (targetNames.isEmpty()) { QString quickCmd = cg.readEntry(QStringLiteral("%1 QuickCmd").arg(i)); m_targetsUi->targetsModel.addCommand(i, i18n("build"), cg.readEntry(QStringLiteral("%1 BuildCmd"), DefBuildCmd)); m_targetsUi->targetsModel.addCommand(i, i18n("clean"), cg.readEntry(QStringLiteral("%1 CleanCmd"), DefCleanCmd)); if (!quickCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(i, i18n("quick"), quickCmd); } m_targetsUi->targetsModel.setDefaultCmd(i, i18n("build")); } else { for (int tn = 0; tn < targetNames.size(); ++tn) { const QString &targetName = targetNames.at(tn); m_targetsUi->targetsModel.addCommand(i, targetName, cg.readEntry(QStringLiteral("%1 BuildCmd %2").arg(i).arg(targetName), DefBuildCmd)); } QString defCmd = cg.readEntry(QStringLiteral("%1 Target Default").arg(i), QString()); m_targetsUi->targetsModel.setDefaultCmd(i, defCmd); } } tmpIndex = cg.readEntry(QStringLiteral("Active Target Index"), 0); tmpCmd = cg.readEntry(QStringLiteral("Active Target Command"), 0); } m_targetsUi->targetsView->expandAll(); m_targetsUi->targetsView->resizeColumnToContents(0); m_targetsUi->targetsView->collapseAll(); QModelIndex root = m_targetsUi->targetsModel.index(tmpIndex); QModelIndex cmdIndex = m_targetsUi->targetsModel.index(tmpCmd, 0, root); m_targetsUi->targetsView->setCurrentIndex(cmdIndex); auto showMarks = cg.readEntry(QStringLiteral("Show Marks"), false); m_showMarks->setChecked(showMarks); // Add project targets, if any slotAddProjectTarget(); } /******************************************************************/ void KateBuildView::writeSessionConfig(KConfigGroup &cg) { // Don't save project targets, is not our area of accountability m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); QList targets = m_targetsUi->targetsModel.targetSets(); cg.writeEntry("NumTargets", targets.size()); for (int i = 0; i < targets.size(); i++) { cg.writeEntry(QStringLiteral("%1 Target").arg(i), targets[i].name); cg.writeEntry(QStringLiteral("%1 BuildPath").arg(i), targets[i].workDir); QStringList cmdNames; for (int j = 0; j < targets[i].commands.count(); j++) { const QString &cmdName = targets[i].commands[j].first; const QString &buildCmd = targets[i].commands[j].second; cmdNames << cmdName; cg.writeEntry(QStringLiteral("%1 BuildCmd %2").arg(i).arg(cmdName), buildCmd); } cg.writeEntry(QStringLiteral("%1 Target Names").arg(i), cmdNames); cg.writeEntry(QStringLiteral("%1 Target Default").arg(i), targets[i].defaultCmd); } int setRow = 0; int set = 0; QModelIndex ind = m_targetsUi->targetsView->currentIndex(); if (ind.internalId() == TargetModel::InvalidIndex) { set = ind.row(); } else { set = ind.internalId(); setRow = ind.row(); } if (setRow < 0) setRow = 0; cg.writeEntry(QStringLiteral("Active Target Index"), set); cg.writeEntry(QStringLiteral("Active Target Command"), setRow); cg.writeEntry(QStringLiteral("Show Marks"), m_showMarks->isChecked()); // Restore project targets, if any slotAddProjectTarget(); } /******************************************************************/ void KateBuildView::slotNext() { const int itemCount = m_buildUi.errTreeWidget->topLevelItemCount(); if (itemCount == 0) { return; } QTreeWidgetItem *item = m_buildUi.errTreeWidget->currentItem(); if (item && item->isHidden()) item = nullptr; int i = (item == nullptr) ? -1 : m_buildUi.errTreeWidget->indexOfTopLevelItem(item); while (++i < itemCount) { item = m_buildUi.errTreeWidget->topLevelItem(i); // Search item which fit view settings and has desired data if (!item->text(1).isEmpty() && !item->isHidden() && item->data(1, Qt::UserRole).toInt()) { m_buildUi.errTreeWidget->setCurrentItem(item); m_buildUi.errTreeWidget->scrollToItem(item); slotErrorSelected(item); return; } } } /******************************************************************/ void KateBuildView::slotPrev() { const int itemCount = m_buildUi.errTreeWidget->topLevelItemCount(); if (itemCount == 0) { return; } QTreeWidgetItem *item = m_buildUi.errTreeWidget->currentItem(); if (item && item->isHidden()) item = nullptr; int i = (item == nullptr) ? itemCount : m_buildUi.errTreeWidget->indexOfTopLevelItem(item); while (--i >= 0) { item = m_buildUi.errTreeWidget->topLevelItem(i); // Search item which fit view settings and has desired data if (!item->text(1).isEmpty() && !item->isHidden() && item->data(1, Qt::UserRole).toInt()) { m_buildUi.errTreeWidget->setCurrentItem(item); m_buildUi.errTreeWidget->scrollToItem(item); slotErrorSelected(item); return; } } } /******************************************************************/ void KateBuildView::slotErrorSelected(QTreeWidgetItem *item) { // any view active? if (!m_win->activeView()) { return; } // Avoid garish highlighting of the selected line m_win->activeView()->setFocus(); // Search the item where the data we need is stored while (!item->data(1, Qt::UserRole).toInt()) { item = m_buildUi.errTreeWidget->itemAbove(item); if (!item) { return; } } // get stuff const QString filename = item->data(0, Qt::UserRole).toString(); if (filename.isEmpty()) { return; } int line = item->data(1, Qt::UserRole).toInt(); int column = item->data(2, Qt::UserRole).toInt(); // check with moving cursor auto data = item->data(0, DataRole).value(); if (data.cursor) { line = data.cursor->line(); column = data.cursor->column(); } // open file (if needed, otherwise, this will activate only the right view...) m_win->openUrl(QUrl::fromLocalFile(filename)); // do it ;) m_win->activeView()->setCursorPosition(KTextEditor::Cursor(line - 1, column - 1)); } /******************************************************************/ void KateBuildView::addError(const QString &filename, const QString &line, const QString &column, const QString &message) { ErrorCategory errorCategory = CategoryInfo; QTreeWidgetItem *item = new QTreeWidgetItem(m_buildUi.errTreeWidget); item->setBackground(1, Qt::gray); // The strings are twice in case kate is translated but not make. if (message.contains(QLatin1String("error")) || message.contains(i18nc("The same word as 'make' uses to mark an error.", "error")) || message.contains(QLatin1String("undefined reference")) || message.contains(i18nc("The same word as 'ld' uses to mark an ...", "undefined reference"))) { errorCategory = CategoryError; item->setForeground(1, Qt::red); m_numErrors++; item->setHidden(false); } if (message.contains(QLatin1String("warning")) || message.contains(i18nc("The same word as 'make' uses to mark a warning.", "warning"))) { errorCategory = CategoryWarning; item->setForeground(1, Qt::yellow); m_numWarnings++; item->setHidden(m_buildUi.displayModeSlider->value() > 2); } item->setTextAlignment(1, Qt::AlignRight); // visible text // remove path from visible file name QFileInfo file(filename); item->setText(0, file.fileName()); item->setText(1, line); item->setText(2, message.trimmed()); // used to read from when activating an item item->setData(0, Qt::UserRole, filename); item->setData(1, Qt::UserRole, line); item->setData(2, Qt::UserRole, column); if (errorCategory == CategoryInfo) { item->setHidden(m_buildUi.displayModeSlider->value() > 1); } item->setData(0, ErrorRole, errorCategory); // add tooltips in all columns // The enclosing ... enables word-wrap for long error messages item->setData(0, Qt::ToolTipRole, filename); item->setData(1, Qt::ToolTipRole, QStringLiteral("%1").arg(message)); item->setData(2, Qt::ToolTipRole, QStringLiteral("%1").arg(message)); } void KateBuildView::clearMarks() { for (auto &doc : m_markedDocs) { if (!doc) { continue; } KTextEditor::MarkInterface *iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); auto markType = KTextEditor::MarkInterface::Error | KTextEditor::MarkInterface::Warning; if (i.value()->type & markType) { iface->removeMark(i.value()->line, markType); } } } } m_markedDocs.clear(); } void KateBuildView::addMarks(KTextEditor::Document *doc, bool mark) { KTextEditor::MarkInterface *iface = qobject_cast(doc); KTextEditor::MovingInterface *miface = qobject_cast(doc); if (!iface || m_markedDocs.contains(doc)) return; QTreeWidgetItemIterator it(m_buildUi.errTreeWidget, QTreeWidgetItemIterator::All); while (*it) { QTreeWidgetItem *item = *it; ++it; auto filename = item->data(0, Qt::UserRole).toString(); auto url = QUrl::fromLocalFile(filename); if (url != doc->url()) continue; auto line = item->data(1, Qt::UserRole).toInt(); if (mark) { ErrorCategory category = (ErrorCategory)item->data(0, ErrorRole).toInt(); KTextEditor::MarkInterface::MarkTypes markType {}; switch (category) { case CategoryError: { markType = KTextEditor::MarkInterface::Error; iface->setMarkDescription(markType, i18n("Error")); break; } case CategoryWarning: { markType = KTextEditor::MarkInterface::Warning; iface->setMarkDescription(markType, i18n("Warning")); break; } default: break; } if (markType) { const int ps = 32; iface->setMarkPixmap(markType, messageIcon(category).pixmap(ps, ps)); iface->addMark(line - 1, markType); } m_markedDocs.insert(doc, doc); } // add moving cursor so link between message and location // is not broken by document changes if (miface) { auto data = item->data(0, DataRole).value(); if (!data.cursor) { auto column = item->data(2, Qt::UserRole).toInt(); data.cursor.reset(miface->newMovingCursor({line, column})); QVariant var; var.setValue(data); item->setData(0, DataRole, var); } } } // ensure cleanup if (miface) { auto conn = connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(slotInvalidateMoving(KTextEditor::Document *)), Qt::UniqueConnection); conn = connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(slotInvalidateMoving(KTextEditor::Document *)), Qt::UniqueConnection); } connect(doc, SIGNAL(markClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), this, SLOT(slotMarkClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), Qt::UniqueConnection); } void KateBuildView::slotInvalidateMoving(KTextEditor::Document *doc) { QTreeWidgetItemIterator it(m_buildUi.errTreeWidget, QTreeWidgetItemIterator::All); while (*it) { QTreeWidgetItem *item = *it; ++it; auto data = item->data(0, DataRole).value(); if (data.cursor && data.cursor->document() == doc) { item->setData(0, DataRole, 0); } } } void KateBuildView::slotMarkClicked(KTextEditor::Document *doc, KTextEditor::Mark mark, bool &handled) { auto tree = m_buildUi.errTreeWidget; QTreeWidgetItemIterator it(tree, QTreeWidgetItemIterator::All); while (*it) { QTreeWidgetItem *item = *it; ++it; auto filename = item->data(0, Qt::UserRole).toString(); auto line = item->data(1, Qt::UserRole).toInt(); // prefer moving cursor's opinion if so available auto data = item->data(0, DataRole).value(); if (data.cursor) { line = data.cursor->line(); } if (line - 1 == mark.line && QUrl::fromLocalFile(filename) == doc->url()) { tree->blockSignals(true); tree->setCurrentItem(item); tree->scrollToItem(item, QAbstractItemView::PositionAtCenter); tree->blockSignals(false); handled = true; break; } } } void KateBuildView::slotViewChanged() { KTextEditor::View *activeView = m_win->activeView(); auto doc = activeView ? activeView->document() : nullptr; if (doc) { addMarks(doc, m_showMarks->isChecked()); } } void KateBuildView::slotDisplayOption() { if (m_showMarks) { if (!m_showMarks->isChecked()) { clearMarks(); } else { slotViewChanged(); } } } /******************************************************************/ QUrl KateBuildView::docUrl() { KTextEditor::View *kv = m_win->activeView(); if (!kv) { qDebug() << "no KTextEditor::View" << endl; return QUrl(); } if (kv->document()->isModified()) kv->document()->save(); return kv->document()->url(); } /******************************************************************/ bool KateBuildView::checkLocal(const QUrl &dir) { if (dir.path().isEmpty()) { KMessageBox::sorry(nullptr, i18n("There is no file or directory specified for building.")); return false; } else if (!dir.isLocalFile()) { KMessageBox::sorry(nullptr, i18n("The file \"%1\" is not a local file. " "Non-local files cannot be compiled.", dir.path())); return false; } return true; } /******************************************************************/ void KateBuildView::clearBuildResults() { clearMarks(); m_buildUi.plainTextEdit->clear(); m_buildUi.errTreeWidget->clear(); m_stdOut.clear(); m_stdErr.clear(); m_numErrors = 0; m_numWarnings = 0; m_make_dir_stack.clear(); } /******************************************************************/ bool KateBuildView::startProcess(const QString &dir, const QString &command) { if (m_proc.state() != QProcess::NotRunning) { return false; } // clear previous runs clearBuildResults(); // activate the output tab m_buildUi.u_tabWidget->setCurrentIndex(1); m_displayModeBeforeBuild = m_buildUi.displayModeSlider->value(); m_buildUi.displayModeSlider->setValue(0); m_win->showToolView(m_toolView); // set working directory m_make_dir = dir; m_make_dir_stack.push(m_make_dir); if (!QFile::exists(m_make_dir)) { KMessageBox::error(nullptr, i18n("Cannot run command: %1\nWork path does not exist: %2", command, m_make_dir)); return false; } // ninja build tool sends all output to stdout, // so follow https://github.com/ninja-build/ninja/issues/1537 to separate ninja and compiler output auto env = QProcessEnvironment::systemEnvironment(); const auto nstatus = QStringLiteral("NINJA_STATUS"); auto curr = env.value(nstatus, QStringLiteral("[%f/%t] ")); // add marker to search on later on env.insert(nstatus, NinjaPrefix + curr); m_ninjaBuildDetected = false; m_proc.setProcessEnvironment(env); m_proc.setWorkingDirectory(m_make_dir); m_proc.setShellCommand(command); m_proc.start(); if (!m_proc.waitForStarted(500)) { KMessageBox::error(nullptr, i18n("Failed to run \"%1\". exitStatus = %2", command, m_proc.exitStatus())); return false; } m_buildUi.cancelBuildButton->setEnabled(true); m_buildUi.cancelBuildButton2->setEnabled(true); m_buildUi.buildAgainButton->setEnabled(false); m_buildUi.buildAgainButton2->setEnabled(false); QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); return true; } /******************************************************************/ bool KateBuildView::slotStop() { if (m_proc.state() != QProcess::NotRunning) { m_buildCancelled = true; QString msg = i18n("Building %1 cancelled", m_currentlyBuildingTarget); m_buildUi.buildStatusLabel->setText(msg); m_buildUi.buildStatusLabel2->setText(msg); m_proc.terminate(); return true; } return false; } /******************************************************************/ void KateBuildView::slotBuildActiveTarget() { if (!m_targetsUi->targetsView->currentIndex().isValid()) { slotSelectTarget(); } else { buildCurrentTarget(); } } /******************************************************************/ void KateBuildView::slotBuildPreviousTarget() { if (!m_previousIndex.isValid()) { slotSelectTarget(); } else { m_targetsUi->targetsView->setCurrentIndex(m_previousIndex); buildCurrentTarget(); } } /******************************************************************/ void KateBuildView::slotBuildDefaultTarget() { QModelIndex defaultTarget = m_targetsUi->targetsModel.defaultTarget(m_targetsUi->targetsView->currentIndex()); m_targetsUi->targetsView->setCurrentIndex(defaultTarget); buildCurrentTarget(); } /******************************************************************/ void KateBuildView::slotSelectTarget() { SelectTargetView *dialog = new SelectTargetView(&(m_targetsUi->targetsModel)); dialog->setCurrentIndex(m_targetsUi->targetsView->currentIndex()); int result = dialog->exec(); if (result == QDialog::Accepted) { m_targetsUi->targetsView->setCurrentIndex(dialog->currentIndex()); buildCurrentTarget(); } delete dialog; dialog = nullptr; } /******************************************************************/ bool KateBuildView::buildCurrentTarget() { if (m_proc.state() != QProcess::NotRunning) { displayBuildResult(i18n("Already building..."), KTextEditor::Message::Warning); return false; } QFileInfo docFInfo = docUrl().toLocalFile(); // docUrl() saves the current document QModelIndex ind = m_targetsUi->targetsView->currentIndex(); m_previousIndex = ind; if (!ind.isValid()) { KMessageBox::sorry(nullptr, i18n("No target available for building.")); return false; } QString buildCmd = m_targetsUi->targetsModel.command(ind); QString cmdName = m_targetsUi->targetsModel.cmdName(ind); QString workDir = m_targetsUi->targetsModel.workDir(ind); QString targetSet = m_targetsUi->targetsModel.targetName(ind); QString dir = workDir; if (workDir.isEmpty()) { dir = docFInfo.absolutePath(); if (dir.isEmpty()) { KMessageBox::sorry(nullptr, i18n("There is no local file or directory specified for building.")); return false; } } // a single target can serve to build lots of projects with similar directory layout if (m_projectPluginView) { QFileInfo baseDir = m_projectPluginView->property("projectBaseDir").toString(); dir.replace(QStringLiteral("%B"), baseDir.absoluteFilePath()); dir.replace(QStringLiteral("%b"), baseDir.baseName()); } // Check if the command contains the file name or directory if (buildCmd.contains(QLatin1String("%f")) || buildCmd.contains(QLatin1String("%d")) || buildCmd.contains(QLatin1String("%n"))) { if (docFInfo.absoluteFilePath().isEmpty()) { return false; } buildCmd.replace(QStringLiteral("%n"), docFInfo.baseName()); buildCmd.replace(QStringLiteral("%f"), docFInfo.absoluteFilePath()); buildCmd.replace(QStringLiteral("%d"), docFInfo.absolutePath()); } m_filenameDetectorGccWorked = false; m_currentlyBuildingTarget = QStringLiteral("%1: %2").arg(targetSet, cmdName); m_buildCancelled = false; QString msg = i18n("Building target %1 ...", m_currentlyBuildingTarget); m_buildUi.buildStatusLabel->setText(msg); m_buildUi.buildStatusLabel2->setText(msg); return startProcess(dir, buildCmd); } /******************************************************************/ void KateBuildView::displayBuildResult(const QString &msg, KTextEditor::Message::MessageType level) { KTextEditor::View *kv = m_win->activeView(); if (!kv) return; delete m_infoMessage; m_infoMessage = new KTextEditor::Message(xi18nc("@info", "Make Results:%1", msg), level); m_infoMessage->setWordWrap(true); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(5000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(kv); kv->document()->postMessage(m_infoMessage); } /******************************************************************/ void KateBuildView::slotProcExited(int exitCode, QProcess::ExitStatus) { QApplication::restoreOverrideCursor(); m_buildUi.cancelBuildButton->setEnabled(false); m_buildUi.cancelBuildButton2->setEnabled(false); m_buildUi.buildAgainButton->setEnabled(true); m_buildUi.buildAgainButton2->setEnabled(true); QString buildStatus = i18n("Building %1 completed.", m_currentlyBuildingTarget); // did we get any errors? if (m_numErrors || m_numWarnings || (exitCode != 0)) { m_buildUi.u_tabWidget->setCurrentIndex(1); if (m_buildUi.displayModeSlider->value() == 0) { m_buildUi.displayModeSlider->setValue(m_displayModeBeforeBuild > 0 ? m_displayModeBeforeBuild : 1); } m_buildUi.errTreeWidget->resizeColumnToContents(0); m_buildUi.errTreeWidget->resizeColumnToContents(1); m_buildUi.errTreeWidget->resizeColumnToContents(2); m_buildUi.errTreeWidget->horizontalScrollBar()->setValue(0); // m_buildUi.errTreeWidget->setSortingEnabled(true); m_win->showToolView(m_toolView); } if (m_numErrors || m_numWarnings) { QStringList msgs; if (m_numErrors) { msgs << i18np("Found one error.", "Found %1 errors.", m_numErrors); buildStatus = i18n("Building %1 had errors.", m_currentlyBuildingTarget); } else if (m_numWarnings) { msgs << i18np("Found one warning.", "Found %1 warnings.", m_numWarnings); buildStatus = i18n("Building %1 had warnings.", m_currentlyBuildingTarget); } displayBuildResult(msgs.join(QLatin1Char('\n')), m_numErrors ? KTextEditor::Message::Error : KTextEditor::Message::Warning); } else if (exitCode != 0) { displayBuildResult(i18n("Build failed."), KTextEditor::Message::Warning); } else { displayBuildResult(i18n("Build completed without problems."), KTextEditor::Message::Positive); } if (!m_buildCancelled) { m_buildUi.buildStatusLabel->setText(buildStatus); m_buildUi.buildStatusLabel2->setText(buildStatus); m_buildCancelled = false; // add marks slotViewChanged(); } } /******************************************************************/ void KateBuildView::slotReadReadyStdOut() { // read data from procs stdout and add // the text to the end of the output // FIXME This works for utf8 but not for all charsets QString l = QString::fromUtf8(m_proc.readAllStandardOutput()); l.remove(QLatin1Char('\r')); m_stdOut += l; // handle one line at a time do { const int end = m_stdOut.indexOf(QLatin1Char('\n')); if (end < 0) break; QString line = m_stdOut.mid(0, end); const bool ninjaOutput = line.startsWith(NinjaPrefix); m_ninjaBuildDetected |= ninjaOutput; if (ninjaOutput) { line = line.mid(NinjaPrefix.length()); } m_buildUi.plainTextEdit->appendPlainText(line); // qDebug() << line; if (m_newDirDetector.match(line).hasMatch()) { // qDebug() << "Enter/Exit dir found"; int open = line.indexOf(QLatin1Char('`')); int close = line.indexOf(QLatin1Char('\'')); QString newDir = line.mid(open + 1, close - open - 1); // qDebug () << "New dir = " << newDir; if ((m_make_dir_stack.size() > 1) && (m_make_dir_stack.top() == newDir)) { m_make_dir_stack.pop(); newDir = m_make_dir_stack.top(); } else { m_make_dir_stack.push(newDir); } m_make_dir = newDir; } else if (m_ninjaBuildDetected && !ninjaOutput) { processLine(line); } m_stdOut.remove(0, end + 1); } while (1); } /******************************************************************/ void KateBuildView::slotReadReadyStdErr() { // FIXME This works for utf8 but not for all charsets QString l = QString::fromUtf8(m_proc.readAllStandardError()); l.remove(QLatin1Char('\r')); m_stdErr += l; do { const int end = m_stdErr.indexOf(QLatin1Char('\n')); if (end < 0) break; const QString line = m_stdErr.mid(0, end); m_buildUi.plainTextEdit->appendPlainText(line); processLine(line); m_stdErr.remove(0, end + 1); } while (1); } /******************************************************************/ void KateBuildView::processLine(const QString &line) { // qDebug() << line ; // look for a filename QRegularExpressionMatch match = m_filenameDetector.match(line); if (match.hasMatch()) { m_filenameDetectorGccWorked = true; } else { if (!m_filenameDetectorGccWorked) { // let's see whether the icpc regexp works: // so for icpc users error detection will be a bit slower, // since always both regexps are checked. // But this should be the minority, for gcc and clang users // both regexes will only be checked until the first regex // matched the first time. match = m_filenameDetectorIcpc.match(line); } } if (!match.hasMatch()) { addError(QString(), QStringLiteral("0"), QString(), line); // kDebug() << "A filename was not found in the line "; return; } QString filename = match.captured(1); const QString line_n = match.captured(3); const QString msg = match.captured(4); // qDebug() << "File Name:"<targetsView->currentIndex(); if (current.parent().isValid()) { current = current.parent(); } QModelIndex index = m_targetsUi->targetsModel.addCommand(current.row(), DefTargetName, DefBuildCmd); m_targetsUi->targetsView->setCurrentIndex(index); } /******************************************************************/ void KateBuildView::targetSetNew() { int row = m_targetsUi->targetsModel.addTargetSet(i18n("Target Set"), QString()); QModelIndex buildIndex = m_targetsUi->targetsModel.addCommand(row, i18n("Build"), DefBuildCmd); m_targetsUi->targetsModel.addCommand(row, i18n("Clean"), DefCleanCmd); m_targetsUi->targetsModel.addCommand(row, i18n("Config"), DefConfigCmd); m_targetsUi->targetsModel.addCommand(row, i18n("ConfigClean"), DefConfClean); m_targetsUi->targetsView->setCurrentIndex(buildIndex); } /******************************************************************/ void KateBuildView::targetOrSetCopy() { QModelIndex newIndex = m_targetsUi->targetsModel.copyTargetOrSet(m_targetsUi->targetsView->currentIndex()); if (m_targetsUi->targetsModel.hasChildren(newIndex)) { m_targetsUi->targetsView->setCurrentIndex(newIndex.model()->index(0, 0, newIndex)); return; } m_targetsUi->targetsView->setCurrentIndex(newIndex); } /******************************************************************/ void KateBuildView::targetDelete() { QModelIndex current = m_targetsUi->targetsView->currentIndex(); m_targetsUi->targetsModel.deleteItem(current); if (m_targetsUi->targetsModel.rowCount() == 0) { targetSetNew(); } } /******************************************************************/ void KateBuildView::slotDisplayMode(int mode) { QTreeWidget *tree = m_buildUi.errTreeWidget; tree->setVisible(mode != 0); m_buildUi.plainTextEdit->setVisible(mode == 0); QString modeText; switch (mode) { case OnlyErrors: modeText = i18n("Only Errors"); break; case ErrorsAndWarnings: modeText = i18n("Errors and Warnings"); break; case ParsedOutput: modeText = i18n("Parsed Output"); break; case FullOutput: modeText = i18n("Full Output"); break; } m_buildUi.displayModeLabel->setText(modeText); if (mode < 1) { return; } const int itemCount = tree->topLevelItemCount(); for (int i = 0; i < itemCount; i++) { QTreeWidgetItem *item = tree->topLevelItem(i); const ErrorCategory errorCategory = static_cast(item->data(0, ErrorRole).toInt()); switch (errorCategory) { case CategoryInfo: item->setHidden(mode > 1); break; case CategoryWarning: item->setHidden(mode > 2); break; case CategoryError: item->setHidden(false); break; } } } /******************************************************************/ void KateBuildView::slotPluginViewCreated(const QString &name, QObject *pluginView) { // add view if (pluginView && name == QLatin1String("kateprojectplugin")) { m_projectPluginView = pluginView; slotAddProjectTarget(); connect(pluginView, SIGNAL(projectMapChanged()), this, SLOT(slotProjectMapChanged()), Qt::UniqueConnection); } } /******************************************************************/ void KateBuildView::slotPluginViewDeleted(const QString &name, QObject *) { // remove view if (name == QLatin1String("kateprojectplugin")) { m_projectPluginView = nullptr; m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); } } /******************************************************************/ void KateBuildView::slotProjectMapChanged() { // only do stuff with valid project if (!m_projectPluginView) { return; } m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); slotAddProjectTarget(); } /******************************************************************/ void KateBuildView::slotAddProjectTarget() { // only do stuff with valid project if (!m_projectPluginView) { return; } // query new project map QVariantMap projectMap = m_projectPluginView->property("projectMap").toMap(); // do we have a valid map for build settings? QVariantMap buildMap = projectMap.value(QStringLiteral("build")).toMap(); if (buildMap.isEmpty()) { return; } // Delete any old project plugin targets m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); int set = m_targetsUi->targetsModel.addTargetSet(i18n("Project Plugin Targets"), buildMap.value(QStringLiteral("directory")).toString()); const QVariantList targetsets = buildMap.value(QStringLiteral("targets")).toList(); for (const QVariant &targetVariant : targetsets) { QVariantMap targetMap = targetVariant.toMap(); QString tgtName = targetMap[QStringLiteral("name")].toString(); QString buildCmd = targetMap[QStringLiteral("build_cmd")].toString(); if (tgtName.isEmpty() || buildCmd.isEmpty()) { continue; } m_targetsUi->targetsModel.addCommand(set, tgtName, buildCmd); } QModelIndex ind = m_targetsUi->targetsModel.index(set); if (!ind.model()->index(0, 0, ind).data().isValid()) { QString buildCmd = buildMap.value(QStringLiteral("build")).toString(); QString cleanCmd = buildMap.value(QStringLiteral("clean")).toString(); QString quickCmd = buildMap.value(QStringLiteral("quick")).toString(); if (!buildCmd.isEmpty()) { // we have loaded an "old" project file (<= 4.12) m_targetsUi->targetsModel.addCommand(set, i18n("build"), buildCmd); } if (!cleanCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(set, i18n("clean"), cleanCmd); } if (!quickCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(set, i18n("quick"), quickCmd); } } } /******************************************************************/ bool KateBuildView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_win->hideToolView(m_toolView); event->accept(); return true; } } if ((event->type() == QEvent::Resize) && (obj == m_buildWidget)) { if (m_buildUi.u_tabWidget->currentIndex() == 1) { if ((m_outputWidgetWidth == 0) && m_buildUi.buildAgainButton->isVisible()) { QSize msh = m_buildWidget->minimumSizeHint(); m_outputWidgetWidth = msh.width(); } } bool useVertLayout = (m_buildWidget->width() < m_outputWidgetWidth); m_buildUi.buildAgainButton->setVisible(!useVertLayout); m_buildUi.cancelBuildButton->setVisible(!useVertLayout); m_buildUi.buildStatusLabel->setVisible(!useVertLayout); m_buildUi.buildAgainButton2->setVisible(useVertLayout); m_buildUi.cancelBuildButton2->setVisible(useVertLayout); m_buildUi.buildStatusLabel2->setVisible(useVertLayout); } return QObject::eventFilter(obj, event); } /******************************************************************/ void KateBuildView::handleEsc(QEvent *e) { if (!m_win) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_toolView->isVisible()) { m_win->hideToolView(m_toolView); } } } #include "plugin_katebuild.moc" // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/katebuild-plugin/plugin_katebuild.h b/addons/katebuild-plugin/plugin_katebuild.h index f5b065f62..f9fa28e42 100644 --- a/addons/katebuild-plugin/plugin_katebuild.h +++ b/addons/katebuild-plugin/plugin_katebuild.h @@ -1,180 +1,180 @@ #ifndef PLUGIN_KATEBUILD_H #define PLUGIN_KATEBUILD_H /* plugin_katebuild.h Kate Plugin ** ** Copyright (C) 2008-2015 by KÃ¥re Särs ** ** This code is almost a total rewrite of the GPL'ed Make plugin ** by Adriaan de Groot. */ /* ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program in a file called COPYING; if not, write to ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, ** MA 02110-1301, USA. */ +#include +#include +#include #include -#include #include -#include -#include -#include +#include -#include #include +#include +#include +#include #include -#include #include -#include -#include +#include -#include #include +#include -#include "ui_build.h" #include "targets.h" +#include "ui_build.h" /******************************************************************/ class KateBuildView : public QObject, public KXMLGUIClient, public KTextEditor::SessionConfigInterface { Q_OBJECT Q_INTERFACES(KTextEditor::SessionConfigInterface) Q_PROPERTY(QUrl docUrl READ docUrl) public: enum ResultDetails { FullOutput, ParsedOutput, ErrorsAndWarnings, OnlyErrors }; enum TreeWidgetRoles { ErrorRole = Qt::UserRole + 1, DataRole }; enum ErrorCategory { CategoryInfo, CategoryWarning, CategoryError }; KateBuildView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mw); ~KateBuildView() override; // reimplemented: read and write session config void readSessionConfig(const KConfigGroup &config) override; void writeSessionConfig(KConfigGroup &config) override; bool buildCurrentTarget(); QUrl docUrl(); private Q_SLOTS: // Building void slotSelectTarget(); void slotBuildActiveTarget(); void slotBuildPreviousTarget(); void slotBuildDefaultTarget(); bool slotStop(); // Parse output void slotProcExited(int exitCode, QProcess::ExitStatus exitStatus); void slotReadReadyStdErr(); void slotReadReadyStdOut(); // Selecting warnings/errors void slotNext(); void slotPrev(); void slotErrorSelected(QTreeWidgetItem *item); // Settings void targetSetNew(); void targetOrSetCopy(); void targetDelete(); void slotAddTargetClicked(); void slotDisplayMode(int mode); void handleEsc(QEvent *e); void slotViewChanged(); void slotDisplayOption(); void slotMarkClicked(KTextEditor::Document *doc, KTextEditor::Mark mark, bool &handled); void slotInvalidateMoving(KTextEditor::Document *doc); /** * keep track if the project plugin is alive and if the project map did change */ void slotPluginViewCreated(const QString &name, QObject *pluginView); void slotPluginViewDeleted(const QString &name, QObject *pluginView); void slotProjectMapChanged(); void slotAddProjectTarget(); protected: bool eventFilter(QObject *obj, QEvent *ev) override; private: void processLine(const QString &); void addError(const QString &filename, const QString &line, const QString &column, const QString &message); bool startProcess(const QString &dir, const QString &command); bool checkLocal(const QUrl &dir); void clearBuildResults(); void displayBuildResult(const QString &message, KTextEditor::Message::MessageType level); void clearMarks(); void addMarks(KTextEditor::Document *doc, bool mark); KTextEditor::MainWindow *m_win; QWidget *m_toolView; Ui::build m_buildUi; QWidget *m_buildWidget; int m_outputWidgetWidth; TargetsUi *m_targetsUi; KProcess m_proc; QString m_stdOut; QString m_stdErr; QString m_currentlyBuildingTarget; bool m_buildCancelled; int m_displayModeBeforeBuild; QString m_make_dir; QStack m_make_dir_stack; QRegularExpression m_filenameDetector; QRegularExpression m_filenameDetectorIcpc; bool m_filenameDetectorGccWorked; bool m_ninjaBuildDetected; QRegularExpression m_newDirDetector; unsigned int m_numErrors; unsigned int m_numWarnings; QString m_prevItemContent; QModelIndex m_previousIndex; QPointer m_infoMessage; QPointer m_showMarks; QHash> m_markedDocs; /** * current project plugin view, if any */ QObject *m_projectPluginView = nullptr; }; typedef QList VariantList; /******************************************************************/ class KateBuildPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit KateBuildPlugin(QObject *parent = nullptr, const VariantList & = VariantList()); ~KateBuildPlugin() override { } QObject *createView(KTextEditor::MainWindow *mainWindow) override; }; #endif diff --git a/addons/katebuild-plugin/targets.cpp b/addons/katebuild-plugin/targets.cpp index e4d6d378c..45b144e94 100644 --- a/addons/katebuild-plugin/targets.cpp +++ b/addons/katebuild-plugin/targets.cpp @@ -1,124 +1,124 @@ // // Description: Widget for configuring build targets // // Copyright (C) 2011-2014 KÃ¥re Särs // // 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. #include "targets.h" -#include -#include +#include #include +#include #include -#include +#include TargetsUi::TargetsUi(QObject *view, QWidget *parent) : QWidget(parent) { targetLabel = new QLabel(i18n("Active target-set:")); targetCombo = new QComboBox(this); targetCombo->setToolTip(i18n("Select active target set")); targetCombo->setModel(&targetsModel); targetLabel->setBuddy(targetCombo); newTarget = new QToolButton(this); newTarget->setToolTip(i18n("Create new set of targets")); newTarget->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); copyTarget = new QToolButton(this); copyTarget->setToolTip(i18n("Copy command or target set")); copyTarget->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); deleteTarget = new QToolButton(this); deleteTarget->setToolTip(i18n("Delete current set of targets")); deleteTarget->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); addButton = new QToolButton(this); addButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addButton->setToolTip(i18n("Add new target")); buildButton = new QToolButton(this); buildButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok"))); buildButton->setToolTip(i18n("Build selected target")); targetsView = new QTreeView(this); targetsView->setAlternatingRowColors(true); targetsView->setModel(&targetsModel); m_delegate = new TargetHtmlDelegate(view); targetsView->setItemDelegate(m_delegate); targetsView->setSelectionBehavior(QAbstractItemView::SelectItems); targetsView->setEditTriggers(QAbstractItemView::AnyKeyPressed | QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); QHBoxLayout *tLayout = new QHBoxLayout(); tLayout->addWidget(targetLabel); tLayout->addWidget(targetCombo); tLayout->addStretch(40); tLayout->addWidget(buildButton); tLayout->addSpacing(addButton->sizeHint().width()); tLayout->addWidget(addButton); tLayout->addWidget(newTarget); tLayout->addWidget(copyTarget); tLayout->addWidget(deleteTarget); tLayout->setContentsMargins(0, 0, 0, 0); QVBoxLayout *layout = new QVBoxLayout(this); layout->addLayout(tLayout); layout->addWidget(targetsView); connect(targetCombo, static_cast(&QComboBox::activated), this, &TargetsUi::targetSetSelected); connect(targetsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TargetsUi::targetActivated); // connect(targetsView, SIGNAL(clicked(QModelIndex)), this, SLOT(targetActivated(QModelIndex))); targetsView->installEventFilter(this); } void TargetsUi::targetSetSelected(int index) { // qDebug() << index; targetsView->collapseAll(); QModelIndex rootItem = targetsModel.index(index, 0); targetsView->setExpanded(rootItem, true); targetsView->setCurrentIndex(targetsModel.index(0, 0, rootItem)); } void TargetsUi::targetActivated(const QModelIndex &index) { // qDebug() << index; if (!index.isValid()) return; QModelIndex rootItem = index; if (rootItem.parent().isValid()) { rootItem = rootItem.parent(); } targetCombo->setCurrentIndex(rootItem.row()); } bool TargetsUi::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (obj == targetsView) { if (((keyEvent->key() == Qt::Key_Return) || (keyEvent->key() == Qt::Key_Enter)) && m_delegate && !m_delegate->isEditing()) { emit enterPressed(); return true; } } } return QWidget::eventFilter(obj, event); } diff --git a/addons/katebuild-plugin/targets.h b/addons/katebuild-plugin/targets.h index 624af3260..5d4a330e5 100644 --- a/addons/katebuild-plugin/targets.h +++ b/addons/katebuild-plugin/targets.h @@ -1,65 +1,65 @@ // // Description: Widget for configuring build targets // // Copyright (c) 2011-2014 KÃ¥re Särs // // 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. #ifndef TARGETS_H #define TARGETS_H +#include "TargetHtmlDelegate.h" +#include "TargetModel.h" +#include #include +#include #include #include -#include -#include #include -#include "TargetHtmlDelegate.h" -#include "TargetModel.h" class TargetsUi : public QWidget { Q_OBJECT public: TargetsUi(QObject *view, QWidget *parent = nullptr); QLabel *targetLabel; QComboBox *targetCombo; QToolButton *newTarget; QToolButton *copyTarget; QToolButton *deleteTarget; QTreeView *targetsView; TargetModel targetsModel; QToolButton *addButton; QToolButton *buildButton; public Q_SLOTS: void targetSetSelected(int index); void targetActivated(const QModelIndex &index); Q_SIGNALS: void enterPressed(); protected: bool eventFilter(QObject *obj, QEvent *event) override; private: TargetHtmlDelegate *m_delegate; }; #endif diff --git a/addons/katesql/cachedsqlquerymodel.h b/addons/katesql/cachedsqlquerymodel.h index a2a322e53..899a4d67f 100644 --- a/addons/katesql/cachedsqlquerymodel.h +++ b/addons/katesql/cachedsqlquerymodel.h @@ -1,51 +1,51 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #ifndef CACHEDSQLQUERYMODEL_H #define CACHEDSQLQUERYMODEL_H +#include #include #include -#include class CachedSqlQueryModel : public QSqlQueryModel { Q_OBJECT public: explicit CachedSqlQueryModel(QObject *parent = nullptr, int cacheCapacity = 1000); QVariant data(const QModelIndex &item, int role = Qt::DisplayRole) const override; QSqlRecord record(int row) const; void clear() override; int cacheCapacity() const; public Q_SLOTS: void clearCache(); void setCacheCapacity(int); protected: void queryChange() override; private: void cacheRecords(int from, int to) const; mutable QContiguousCache cache; }; #endif // CACHEDSQLQUERYMODEL_H diff --git a/addons/katesql/connectionmodel.cpp b/addons/katesql/connectionmodel.cpp index 5ab3a0178..095df4488 100644 --- a/addons/katesql/connectionmodel.cpp +++ b/addons/katesql/connectionmodel.cpp @@ -1,144 +1,144 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "connectionmodel.h" #include #include #include -#include #include +#include #include #include #include ConnectionModel::ConnectionModel(QObject *parent) : QAbstractListModel(parent) { m_icons[Connection::UNKNOWN] = QIcon::fromTheme(QStringLiteral("user-offline")); m_icons[Connection::ONLINE] = QIcon::fromTheme(QStringLiteral("user-online")); m_icons[Connection::OFFLINE] = QIcon::fromTheme(QStringLiteral("user-offline")); m_icons[Connection::REQUIRE_PASSWORD] = QIcon::fromTheme(QStringLiteral("user-invisible")); } ConnectionModel::~ConnectionModel() { } int ConnectionModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_connections.count(); } QVariant ConnectionModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); const QString key = m_connections.keys().at(index.row()); switch (role) { case Qt::DisplayRole: return QVariant(m_connections.value(key).name); case Qt::UserRole: return QVariant::fromValue(m_connections.value(key)); case Qt::DecorationRole: return m_icons[m_connections.value(key).status]; case Qt::SizeHintRole: { const QFontMetrics metrics(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); return QSize(metrics.boundingRect(m_connections.value(key).name).width(), 22); } default: return QVariant(); } return QVariant(); } int ConnectionModel::addConnection(const Connection &conn) { /// FIXME if (m_connections.contains(conn.name)) { qDebug() << "a connection named" << conn.name << "already exists!"; return -1; } int pos = m_connections.count(); beginInsertRows(QModelIndex(), pos, pos); m_connections[conn.name] = conn; endInsertRows(); return m_connections.keys().indexOf(conn.name); } void ConnectionModel::removeConnection(const QString &name) { int pos = m_connections.keys().indexOf(name); beginRemoveRows(QModelIndex(), pos, pos); m_connections.remove(name); endRemoveRows(); } int ConnectionModel::indexOf(const QString &name) { return m_connections.keys().indexOf(name); } Connection::Status ConnectionModel::status(const QString &name) const { if (!m_connections.contains(name)) return Connection::UNKNOWN; return m_connections[name].status; } void ConnectionModel::setStatus(const QString &name, const Connection::Status status) { if (!m_connections.contains(name)) return; m_connections[name].status = status; const int i = indexOf(name); emit dataChanged(index(i), index(i)); } void ConnectionModel::setPassword(const QString &name, const QString &password) { if (!m_connections.contains(name)) return; m_connections[name].password = password; const int i = indexOf(name); emit dataChanged(index(i), index(i)); } diff --git a/addons/katesql/connectionmodel.h b/addons/katesql/connectionmodel.h index 0d0b915f7..e062806f7 100644 --- a/addons/katesql/connectionmodel.h +++ b/addons/katesql/connectionmodel.h @@ -1,58 +1,58 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #ifndef CONNECTIONMODEL_H #define CONNECTIONMODEL_H #include "connection.h" -#include #include -#include +#include #include +#include class ConnectionModel : public QAbstractListModel { Q_OBJECT public: ConnectionModel(QObject *parent = nullptr); ~ConnectionModel() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual int addConnection(const Connection &conn); virtual void removeConnection(const QString &name); Connection::Status status(const QString &name) const; void setStatus(const QString &name, const Connection::Status status); void setPassword(const QString &name, const QString &password); int indexOf(const QString &name); // virtual bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()); // virtual bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); private: QHash m_connections; QHash m_icons; }; #endif // CONNECTIONMODEL_H diff --git a/addons/katesql/connectionwizard.cpp b/addons/katesql/connectionwizard.cpp index 07230be1f..67b8e685f 100644 --- a/addons/katesql/connectionwizard.cpp +++ b/addons/katesql/connectionwizard.cpp @@ -1,305 +1,305 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "connectionwizard.h" #include "sqlmanager.h" -#include #include -#include #include +#include +#include #include +#include #include #include #include -#include ConnectionWizard::ConnectionWizard(SQLManager *manager, Connection *conn, QWidget *parent, Qt::WindowFlags flags) : QWizard(parent, flags) , m_manager(manager) , m_connection(conn) { setWindowTitle(i18nc("@title:window", "Connection Wizard")); setPage(Page_Driver, new ConnectionDriverPage); setPage(Page_Standard_Server, new ConnectionStandardServerPage); setPage(Page_SQLite_Server, new ConnectionSQLiteServerPage); setPage(Page_Save, new ConnectionSavePage); } ConnectionWizard::~ConnectionWizard() { } ConnectionDriverPage::ConnectionDriverPage(QWidget *parent) : QWizardPage(parent) { setTitle(i18nc("@title Wizard page title", "Database Driver")); setSubTitle(i18nc("@title Wizard page subtitle", "Select the database driver")); QFormLayout *layout = new QFormLayout(); driverComboBox = new KComboBox(); driverComboBox->addItems(QSqlDatabase::drivers()); layout->addRow(i18nc("@label:listbox", "Database driver:"), driverComboBox); setLayout(layout); registerField(QStringLiteral("driver"), driverComboBox, "currentText"); } void ConnectionDriverPage::initializePage() { ConnectionWizard *wiz = static_cast(wizard()); Connection *c = wiz->connection(); if (!c->driver.isEmpty()) driverComboBox->setCurrentItem(c->driver); } int ConnectionDriverPage::nextId() const { if (driverComboBox->currentText().contains(QLatin1String("QSQLITE"))) return ConnectionWizard::Page_SQLite_Server; else return ConnectionWizard::Page_Standard_Server; } ConnectionStandardServerPage::ConnectionStandardServerPage(QWidget *parent) : QWizardPage(parent) { setTitle(i18nc("@title Wizard page title", "Connection Parameters")); setSubTitle(i18nc("@title Wizard page subtitle", "Please enter connection parameters")); QFormLayout *layout = new QFormLayout(); hostnameLineEdit = new KLineEdit(); usernameLineEdit = new KLineEdit(); passwordLineEdit = new KLineEdit(); databaseLineEdit = new KLineEdit(); optionsLineEdit = new KLineEdit(); portSpinBox = new QSpinBox(); portSpinBox->setMaximum(65535); portSpinBox->setSpecialValueText(i18nc("@item Spinbox special value", "Default")); portSpinBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); passwordLineEdit->setPasswordMode(true); layout->addRow(i18nc("@label:textbox", "Hostname:"), hostnameLineEdit); layout->addRow(i18nc("@label:textbox", "Username:"), usernameLineEdit); layout->addRow(i18nc("@label:textbox", "Password:"), passwordLineEdit); layout->addRow(i18nc("@label:spinbox", "Port:"), portSpinBox); layout->addRow(i18nc("@label:textbox", "Database name:"), databaseLineEdit); layout->addRow(i18nc("@label:textbox", "Connection options:"), optionsLineEdit); setLayout(layout); registerField(QStringLiteral("hostname*"), hostnameLineEdit); registerField(QStringLiteral("username"), usernameLineEdit); registerField(QStringLiteral("password"), passwordLineEdit); registerField(QStringLiteral("database"), databaseLineEdit); registerField(QStringLiteral("stdOptions"), optionsLineEdit); registerField(QStringLiteral("port"), portSpinBox); } ConnectionStandardServerPage::~ConnectionStandardServerPage() { } void ConnectionStandardServerPage::initializePage() { ConnectionWizard *wiz = static_cast(wizard()); Connection *c = wiz->connection(); hostnameLineEdit->setText(QStringLiteral("localhost")); if (c->driver == field(QStringLiteral("driver")).toString()) { hostnameLineEdit->setText(c->hostname); usernameLineEdit->setText(c->username); passwordLineEdit->setText(c->password); databaseLineEdit->setText(c->database); optionsLineEdit->setText(c->options); portSpinBox->setValue(c->port); } hostnameLineEdit->selectAll(); } bool ConnectionStandardServerPage::validatePage() { Connection c; c.driver = field(QStringLiteral("driver")).toString(); c.hostname = field(QStringLiteral("hostname")).toString(); c.username = field(QStringLiteral("username")).toString(); c.password = field(QStringLiteral("password")).toString(); c.database = field(QStringLiteral("database")).toString(); c.options = field(QStringLiteral("stdOptions")).toString(); c.port = field(QStringLiteral("port")).toInt(); QSqlError e; ConnectionWizard *wiz = static_cast(wizard()); if (!wiz->manager()->testConnection(c, e)) { KMessageBox::error(this, i18n("Unable to connect to database.") + QLatin1Char('\n') + e.text()); return false; } return true; } int ConnectionStandardServerPage::nextId() const { return ConnectionWizard::Page_Save; } ConnectionSQLiteServerPage::ConnectionSQLiteServerPage(QWidget *parent) : QWizardPage(parent) { setTitle(i18nc("@title Wizard page title", "Connection Parameters")); setSubTitle(i18nc("@title Wizard page subtitle", "Please enter the SQLite database file path.\nIf the file does not exist, a new database will be created.")); QFormLayout *layout = new QFormLayout(); pathUrlRequester = new KUrlRequester(this); optionsLineEdit = new KLineEdit(); pathUrlRequester->setMode(KFile::File); pathUrlRequester->setFilter(QLatin1String("*.db *.sqlite|") + i18n("Database files") + QLatin1String("\n*|") + i18n("All files")); layout->addRow(i18nc("@label:textbox", "Path:"), pathUrlRequester); layout->addRow(i18nc("@label:textbox", "Connection options:"), optionsLineEdit); setLayout(layout); registerField(QStringLiteral("path*"), pathUrlRequester->lineEdit()); registerField(QStringLiteral("sqliteOptions"), optionsLineEdit); } void ConnectionSQLiteServerPage::initializePage() { ConnectionWizard *wiz = static_cast(wizard()); Connection *c = wiz->connection(); if (c->driver == field(QStringLiteral("driver")).toString()) { pathUrlRequester->lineEdit()->setText(c->database); optionsLineEdit->setText(c->options); } } bool ConnectionSQLiteServerPage::validatePage() { Connection c; c.driver = field(QStringLiteral("driver")).toString(); c.database = field(QStringLiteral("path")).toString(); c.options = field(QStringLiteral("sqliteOptions")).toString(); QSqlError e; ConnectionWizard *wiz = static_cast(wizard()); if (!wiz->manager()->testConnection(c, e)) { KMessageBox::error(this, xi18nc("@info", "Unable to connect to database.%1", e.text())); return false; } return true; } int ConnectionSQLiteServerPage::nextId() const { return ConnectionWizard::Page_Save; } ConnectionSavePage::ConnectionSavePage(QWidget *parent) : QWizardPage(parent) { setTitle(i18nc("@title Wizard page title", "Connection Name")); setSubTitle(i18nc("@title Wizard page subtitle", "Enter a unique connection name")); QFormLayout *layout = new QFormLayout(); connectionNameLineEdit = new KLineEdit(); layout->addRow(i18nc("@label:textbox", "Connection name:"), connectionNameLineEdit); setLayout(layout); registerField(QStringLiteral("connectionName*"), connectionNameLineEdit); } void ConnectionSavePage::initializePage() { QString name; ConnectionWizard *wiz = static_cast(wizard()); Connection *c = wiz->connection(); if (!c->name.isEmpty()) { name = c->name; } else if (field(QStringLiteral("driver")).toString().contains(QLatin1String("QSQLITE"))) { /// TODO: use db file basename name = QStringLiteral("SQLite"); for (int i = 1; QSqlDatabase::contains(name); i++) name = QStringLiteral("%1%2").arg(QLatin1String("SQLite")).arg(i); } else { name = QStringLiteral("%1 on %2").arg(field(QStringLiteral("database")).toString()).arg(field(QStringLiteral("hostname")).toString()).simplified(); for (int i = 1; QSqlDatabase::contains(name); i++) name = QStringLiteral("%1 on %2 (%3)").arg(field(QStringLiteral("database")).toString()).arg(field(QStringLiteral("hostname")).toString()).arg(i).simplified(); } connectionNameLineEdit->setText(name); connectionNameLineEdit->selectAll(); } bool ConnectionSavePage::validatePage() { QString name = field(QStringLiteral("connectionName")).toString().simplified(); ConnectionWizard *wiz = static_cast(wizard()); Connection *c = wiz->connection(); c->name = name; c->driver = field(QStringLiteral("driver")).toString(); if (field(QStringLiteral("driver")).toString().contains(QLatin1String("QSQLITE"))) { c->database = field(QStringLiteral("path")).toString(); c->options = field(QStringLiteral("sqliteOptions")).toString(); } else { c->hostname = field(QStringLiteral("hostname")).toString(); c->username = field(QStringLiteral("username")).toString(); c->password = field(QStringLiteral("password")).toString(); c->database = field(QStringLiteral("database")).toString(); c->options = field(QStringLiteral("stdOptions")).toString(); c->port = field(QStringLiteral("port")).toInt(); } return true; } int ConnectionSavePage::nextId() const { return -1; } diff --git a/addons/katesql/dataoutputmodel.cpp b/addons/katesql/dataoutputmodel.cpp index 97c01c5b0..6aaf9fec3 100644 --- a/addons/katesql/dataoutputmodel.cpp +++ b/addons/katesql/dataoutputmodel.cpp @@ -1,199 +1,199 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "dataoutputmodel.h" #include "outputstyle.h" +#include #include -#include #include -#include +#include #include #include inline bool isNumeric(const QVariant::Type type) { return (type > 1 && type < 7); } DataOutputModel::DataOutputModel(QObject *parent) : CachedSqlQueryModel(parent, 1000) { m_useSystemLocale = false; m_styles.insert(QStringLiteral("text"), new OutputStyle()); m_styles.insert(QStringLiteral("number"), new OutputStyle()); m_styles.insert(QStringLiteral("null"), new OutputStyle()); m_styles.insert(QStringLiteral("blob"), new OutputStyle()); m_styles.insert(QStringLiteral("datetime"), new OutputStyle()); m_styles.insert(QStringLiteral("bool"), new OutputStyle()); readConfig(); } DataOutputModel::~DataOutputModel() { qDeleteAll(m_styles); } void DataOutputModel::clear() { beginResetModel(); CachedSqlQueryModel::clear(); endResetModel(); } void DataOutputModel::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), "KateSQLPlugin"); KConfigGroup group = config.group("OutputCustomization"); KColorScheme scheme(QPalette::Active, KColorScheme::View); const auto styleKeys = m_styles.keys(); for (const QString &k : styleKeys) { OutputStyle *s = m_styles[k]; KConfigGroup g = group.group(k); s->foreground = scheme.foreground(); s->background = scheme.background(); s->font = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFont dummy = g.readEntry("font", QFontDatabase::systemFont(QFontDatabase::GeneralFont)); s->font.setBold(dummy.bold()); s->font.setItalic(dummy.italic()); s->font.setUnderline(dummy.underline()); s->font.setStrikeOut(dummy.strikeOut()); s->foreground.setColor(g.readEntry("foregroundColor", s->foreground.color())); s->background.setColor(g.readEntry("backgroundColor", s->background.color())); } emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } bool DataOutputModel::useSystemLocale() const { return m_useSystemLocale; } void DataOutputModel::setUseSystemLocale(bool useSystemLocale) { m_useSystemLocale = useSystemLocale; emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } QVariant DataOutputModel::data(const QModelIndex &index, int role) const { if (role == Qt::EditRole) return CachedSqlQueryModel::data(index, role); const QVariant value(CachedSqlQueryModel::data(index, Qt::DisplayRole)); const QVariant::Type type = value.type(); if (value.isNull()) { if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("null"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("null"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("null"))->background); if (role == Qt::DisplayRole) return QVariant(QLatin1String("NULL")); } if (type == QVariant::ByteArray) { if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("blob"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("blob"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("blob"))->background); if (role == Qt::DisplayRole) return QVariant(value.toByteArray().left(255)); } if (isNumeric(type)) { if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("number"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("number"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("number"))->background); if (role == Qt::TextAlignmentRole) return QVariant(Qt::AlignRight | Qt::AlignVCenter); if (role == Qt::DisplayRole || role == Qt::UserRole) { if (useSystemLocale()) return QVariant(value.toString()); // FIXME KF5 KGlobal::locale()->formatNumber(value.toString(), false)); else return QVariant(value.toString()); } } if (type == QVariant::Bool) { if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("bool"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("bool"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("bool"))->background); if (role == Qt::DisplayRole) return QVariant(value.toBool() ? QLatin1String("True") : QLatin1String("False")); } if (type == QVariant::Date || type == QVariant::Time || type == QVariant::DateTime) { if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("datetime"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("datetime"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("datetime"))->background); if (role == Qt::DisplayRole || role == Qt::UserRole) { if (useSystemLocale()) { if (type == QVariant::Date) return QVariant(QLocale().toString(value.toDate(), QLocale::ShortFormat)); if (type == QVariant::Time) return QVariant(QLocale().toString(value.toTime())); if (type == QVariant::DateTime) return QVariant(QLocale().toString(value.toDateTime(), QLocale::ShortFormat)); } else // return sql server format return QVariant(value.toString()); } } if (role == Qt::FontRole) return QVariant(m_styles.value(QStringLiteral("text"))->font); if (role == Qt::ForegroundRole) return QVariant(m_styles.value(QStringLiteral("text"))->foreground); if (role == Qt::BackgroundRole) return QVariant(m_styles.value(QStringLiteral("text"))->background); if (role == Qt::TextAlignmentRole) return QVariant(Qt::AlignVCenter); if (role == Qt::DisplayRole) return value.toString(); if (role == Qt::UserRole) return value; return CachedSqlQueryModel::data(index, role); } diff --git a/addons/katesql/dataoutputwidget.cpp b/addons/katesql/dataoutputwidget.cpp index da7482274..e20c83050 100644 --- a/addons/katesql/dataoutputwidget.cpp +++ b/addons/katesql/dataoutputwidget.cpp @@ -1,357 +1,357 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "dataoutputwidget.h" #include "dataoutputmodel.h" #include "dataoutputview.h" #include "exportwizard.h" -#include #include -#include #include +#include +#include #include -#include -#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include #include -#include +#include +#include #include -#include #include -#include -#include -#include -#include -#include DataOutputWidget::DataOutputWidget(QWidget *parent) : QWidget(parent) , m_model(new DataOutputModel(this)) , m_view(new DataOutputView(this)) , m_isEmpty(true) { m_view->setModel(m_model); QHBoxLayout *layout = new QHBoxLayout(this); m_dataLayout = new QVBoxLayout(); KToolBar *toolbar = new KToolBar(this); toolbar->setOrientation(Qt::Vertical); toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); // ensure reasonable icons sizes, like e.g. the quick-open and co. icons // the normal toolbar sizes are TOO large, e.g. for scaled stuff even more! const int iconSize = style()->pixelMetric(QStyle::PM_ButtonIconSize, nullptr, this); toolbar->setIconSize(QSize(iconSize, iconSize)); /// TODO: disable actions if no results are displayed or selected QAction *action; action = new QAction(QIcon::fromTheme(QStringLiteral("distribute-horizontal-x")), i18nc("@action:intoolbar", "Resize columns to contents"), this); toolbar->addAction(action); connect(action, &QAction::triggered, this, &DataOutputWidget::resizeColumnsToContents); action = new QAction(QIcon::fromTheme(QStringLiteral("distribute-vertical-y")), i18nc("@action:intoolbar", "Resize rows to contents"), this); toolbar->addAction(action); connect(action, &QAction::triggered, this, &DataOutputWidget::resizeRowsToContents); action = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:intoolbar", "Copy"), this); toolbar->addAction(action); m_view->addAction(action); connect(action, &QAction::triggered, this, &DataOutputWidget::slotCopySelected); action = new QAction(QIcon::fromTheme(QStringLiteral("document-export-table")), i18nc("@action:intoolbar", "Export..."), this); toolbar->addAction(action); m_view->addAction(action); connect(action, &QAction::triggered, this, &DataOutputWidget::slotExport); action = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18nc("@action:intoolbar", "Clear"), this); toolbar->addAction(action); connect(action, &QAction::triggered, this, &DataOutputWidget::clearResults); toolbar->addSeparator(); KToggleAction *toggleAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("applications-education-language")), i18nc("@action:intoolbar", "Use system locale"), this); toolbar->addAction(toggleAction); connect(toggleAction, &QAction::triggered, this, &DataOutputWidget::slotToggleLocale); m_dataLayout->addWidget(m_view); layout->addWidget(toolbar); layout->addLayout(m_dataLayout); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); } DataOutputWidget::~DataOutputWidget() { } void DataOutputWidget::showQueryResultSets(QSqlQuery &query) { /// TODO: loop resultsets if > 1 /// NOTE from Qt Documentation: /// When one of the statements is a non-select statement a count of affected rows /// may be available instead of a result set. if (!query.isSelect() || query.lastError().isValid()) return; m_model->setQuery(query); m_isEmpty = false; QTimer::singleShot(0, this, &DataOutputWidget::resizeColumnsToContents); raise(); } void DataOutputWidget::clearResults() { // avoid crash when calling QSqlQueryModel::clear() after removing connection from the QSqlDatabase list if (m_isEmpty) return; m_model->clear(); m_isEmpty = true; /// HACK needed to refresh headers. please correct if there's a better way m_view->horizontalHeader()->hide(); m_view->verticalHeader()->hide(); m_view->horizontalHeader()->show(); m_view->verticalHeader()->show(); } void DataOutputWidget::resizeColumnsToContents() { if (m_model->rowCount() == 0) return; m_view->resizeColumnsToContents(); } void DataOutputWidget::resizeRowsToContents() { if (m_model->rowCount() == 0) return; m_view->resizeRowsToContents(); int h = m_view->rowHeight(0); if (h > 0) m_view->verticalHeader()->setDefaultSectionSize(h); } void DataOutputWidget::slotToggleLocale() { m_model->setUseSystemLocale(!m_model->useSystemLocale()); } void DataOutputWidget::slotCopySelected() { if (m_model->rowCount() <= 0) return; while (m_model->canFetchMore()) m_model->fetchMore(); if (!m_view->selectionModel()->hasSelection()) m_view->selectAll(); QString text; QTextStream stream(&text); exportData(stream); if (!text.isEmpty()) QApplication::clipboard()->setText(text); } void DataOutputWidget::slotExport() { if (m_model->rowCount() <= 0) return; while (m_model->canFetchMore()) m_model->fetchMore(); if (!m_view->selectionModel()->hasSelection()) m_view->selectAll(); ExportWizard wizard(this); if (wizard.exec() != QDialog::Accepted) return; bool outputInDocument = wizard.field(QStringLiteral("outDocument")).toBool(); bool outputInClipboard = wizard.field(QStringLiteral("outClipboard")).toBool(); bool outputInFile = wizard.field(QStringLiteral("outFile")).toBool(); bool exportColumnNames = wizard.field(QStringLiteral("exportColumnNames")).toBool(); bool exportLineNumbers = wizard.field(QStringLiteral("exportLineNumbers")).toBool(); Options opt = NoOptions; if (exportColumnNames) opt |= ExportColumnNames; if (exportLineNumbers) opt |= ExportLineNumbers; bool quoteStrings = wizard.field(QStringLiteral("checkQuoteStrings")).toBool(); bool quoteNumbers = wizard.field(QStringLiteral("checkQuoteNumbers")).toBool(); QChar stringsQuoteChar = (quoteStrings) ? wizard.field(QStringLiteral("quoteStringsChar")).toString().at(0) : QLatin1Char('\0'); QChar numbersQuoteChar = (quoteNumbers) ? wizard.field(QStringLiteral("quoteNumbersChar")).toString().at(0) : QLatin1Char('\0'); QString fieldDelimiter = wizard.field(QStringLiteral("fieldDelimiter")).toString(); if (outputInDocument) { KTextEditor::MainWindow *mw = KTextEditor::Editor::instance()->application()->activeMainWindow(); KTextEditor::View *kv = mw->activeView(); if (!kv) return; QString text; QTextStream stream(&text); exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt); kv->insertText(text); kv->setFocus(); } else if (outputInClipboard) { QString text; QTextStream stream(&text); exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt); QApplication::clipboard()->setText(text); } else if (outputInFile) { QString url = wizard.field(QStringLiteral("outFileUrl")).toString(); QFile data(url); if (data.open(QFile::WriteOnly | QFile::Truncate)) { QTextStream stream(&data); exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt); stream.flush(); } else { KMessageBox::error(this, xi18nc("@info", "Unable to open file %1", url)); } } } void DataOutputWidget::exportData(QTextStream &stream, const QChar stringsQuoteChar, const QChar numbersQuoteChar, const QString &fieldDelimiter, const Options opt) { QItemSelectionModel *selectionModel = m_view->selectionModel(); if (!selectionModel->hasSelection()) return; QString fixedFieldDelimiter = fieldDelimiter; /// FIXME: ugly workaround... fixedFieldDelimiter.replace(QLatin1String("\\t"), QLatin1String("\t")); fixedFieldDelimiter.replace(QLatin1String("\\r"), QLatin1String("\r")); fixedFieldDelimiter.replace(QLatin1String("\\n"), QLatin1String("\n")); QElapsedTimer t; t.start(); QSet columns; QSet rows; QHash, QString> snapshot; const QModelIndexList selectedIndexes = selectionModel->selectedIndexes(); snapshot.reserve(selectedIndexes.count()); for (const QModelIndex &index : selectedIndexes) { const QVariant data = index.data(Qt::UserRole); const int col = index.column(); const int row = index.row(); if (!columns.contains(col)) columns.insert(col); if (!rows.contains(row)) rows.insert(row); if (data.type() < 7) // is numeric or boolean { if (numbersQuoteChar != QLatin1Char('\0')) snapshot[qMakePair(row, col)] = numbersQuoteChar + data.toString() + numbersQuoteChar; else snapshot[qMakePair(row, col)] = data.toString(); } else { if (stringsQuoteChar != QLatin1Char('\0')) snapshot[qMakePair(row, col)] = stringsQuoteChar + data.toString() + stringsQuoteChar; else snapshot[qMakePair(row, col)] = data.toString(); } } if (opt.testFlag(ExportColumnNames)) { if (opt.testFlag(ExportLineNumbers)) stream << fixedFieldDelimiter; QSetIterator j(columns); while (j.hasNext()) { const QVariant data = m_model->headerData(j.next(), Qt::Horizontal); if (stringsQuoteChar != QLatin1Char('\0')) stream << stringsQuoteChar + data.toString() + stringsQuoteChar; else stream << data.toString(); if (j.hasNext()) stream << fixedFieldDelimiter; } stream << "\n"; } for (const int row : qAsConst(rows)) { if (opt.testFlag(ExportLineNumbers)) stream << row + 1 << fixedFieldDelimiter; QSetIterator j(columns); while (j.hasNext()) { stream << snapshot.value(qMakePair(row, j.next())); if (j.hasNext()) stream << fixedFieldDelimiter; } stream << "\n"; } qDebug() << "Export in" << t.elapsed() << "msecs"; } diff --git a/addons/katesql/exportwizard.cpp b/addons/katesql/exportwizard.cpp index a0fb2ac7e..132f05fa9 100644 --- a/addons/katesql/exportwizard.cpp +++ b/addons/katesql/exportwizard.cpp @@ -1,198 +1,198 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "exportwizard.h" #include "dataoutputwidget.h" +#include #include #include -#include -#include #include +#include +#include #include #include #include -#include -#include +#include // BEGIN ExportWizard ExportWizard::ExportWizard(QWidget *parent) : QWizard(parent) { setWindowTitle(i18nc("@title:window", "Export Wizard")); addPage(new ExportOutputPage(this)); addPage(new ExportFormatPage(this)); } ExportWizard::~ExportWizard() { } // END ExportWizard // BEGIN ExportOutputPage ExportOutputPage::ExportOutputPage(QWidget *parent) : QWizardPage(parent) { setTitle(i18nc("@title Wizard page title", "Output Target")); setSubTitle(i18nc("@title Wizard page subtitle", "Select the output target.")); QVBoxLayout *layout = new QVBoxLayout(); documentRadioButton = new QRadioButton(i18nc("@option:radio Output target", "Current document"), this); clipboardRadioButton = new QRadioButton(i18nc("@option:radio Output target", "Clipboard"), this); fileRadioButton = new QRadioButton(i18nc("@option:radio Output target", "File"), this); QHBoxLayout *fileLayout = new QHBoxLayout(); fileLayout->setContentsMargins(20, 0, 0, 0); fileUrl = new KUrlRequester(this); fileUrl->setMode(KFile::File); fileUrl->setFilter(i18n("*.csv|Comma Separated Values\n*|All files")); fileLayout->addWidget(fileUrl); layout->addWidget(documentRadioButton); layout->addWidget(clipboardRadioButton); layout->addWidget(fileRadioButton); layout->addLayout(fileLayout); setLayout(layout); registerField(QStringLiteral("outDocument"), documentRadioButton); registerField(QStringLiteral("outClipboard"), clipboardRadioButton); registerField(QStringLiteral("outFile"), fileRadioButton); registerField(QStringLiteral("outFileUrl"), fileUrl, "text"); connect(fileRadioButton, &QRadioButton::toggled, fileUrl, &KUrlRequester::setEnabled); } void ExportOutputPage::initializePage() { documentRadioButton->setChecked(true); fileUrl->setEnabled(false); } bool ExportOutputPage::validatePage() { if (fileRadioButton->isChecked() && fileUrl->text().isEmpty()) { fileUrl->setFocus(); return false; } /// TODO: check url validity return true; } // END ExportOutputPage // BEGIN ExportFormatPage ExportFormatPage::ExportFormatPage(QWidget *parent) : QWizardPage(parent) { setTitle(i18nc("@title Wizard page title", "Fields Format")); setSubTitle(i18nc("@title Wizard page subtitle", "Select fields format.\nClick on \"Finish\" button to export data.")); QVBoxLayout *layout = new QVBoxLayout(); QGroupBox *headersGroupBox = new QGroupBox(i18nc("@title:group", "Headers"), this); QVBoxLayout *headersLayout = new QVBoxLayout(); exportColumnNamesCheckBox = new QCheckBox(i18nc("@option:check", "Export column names"), headersGroupBox); exportLineNumbersCheckBox = new QCheckBox(i18nc("@option:check", "Export line numbers"), headersGroupBox); headersLayout->addWidget(exportColumnNamesCheckBox); headersLayout->addWidget(exportLineNumbersCheckBox); headersGroupBox->setLayout(headersLayout); QGroupBox *quoteGroupBox = new QGroupBox(i18nc("@title:group", "Quotes"), this); QGridLayout *quoteLayout = new QGridLayout(); quoteStringsCheckBox = new QCheckBox(i18nc("@option:check", "Quote strings"), quoteGroupBox); quoteNumbersCheckBox = new QCheckBox(i18nc("@option:check", "Quote numbers"), quoteGroupBox); quoteStringsLine = new KLineEdit(quoteGroupBox); quoteNumbersLine = new KLineEdit(quoteGroupBox); quoteStringsLine->setMaxLength(1); quoteNumbersLine->setMaxLength(1); quoteLayout->addWidget(quoteStringsCheckBox, 0, 0, Qt::AlignLeft | Qt::AlignVCenter); quoteLayout->addWidget(new QLabel(i18nc("@label:textbox", "Character:")), 0, 1, Qt::AlignRight | Qt::AlignVCenter); quoteLayout->addWidget(quoteStringsLine, 0, 2, Qt::AlignRight | Qt::AlignVCenter); quoteLayout->addWidget(quoteNumbersCheckBox, 1, 0, Qt::AlignLeft | Qt::AlignVCenter); quoteLayout->addWidget(new QLabel(i18nc("@label:textbox", "Character:")), 1, 1, Qt::AlignRight | Qt::AlignVCenter); quoteLayout->addWidget(quoteNumbersLine, 1, 2, Qt::AlignRight | Qt::AlignVCenter); quoteLayout->setColumnStretch(1, 1); quoteGroupBox->setLayout(quoteLayout); QGroupBox *delimitersGroupBox = new QGroupBox(i18nc("@title:group", "Delimiters"), this); QFormLayout *delimitersLayout = new QFormLayout(); fieldDelimiterLine = new KLineEdit(delimitersGroupBox); fieldDelimiterLine->setMaxLength(3); delimitersLayout->addRow(i18nc("@label:textbox", "Field delimiter:"), fieldDelimiterLine); delimitersGroupBox->setLayout(delimitersLayout); layout->addWidget(headersGroupBox); layout->addWidget(quoteGroupBox); layout->addWidget(delimitersGroupBox); setLayout(layout); registerField(QStringLiteral("exportColumnNames"), exportColumnNamesCheckBox); registerField(QStringLiteral("exportLineNumbers"), exportLineNumbersCheckBox); registerField(QStringLiteral("checkQuoteStrings"), quoteStringsCheckBox); registerField(QStringLiteral("checkQuoteNumbers"), quoteNumbersCheckBox); registerField(QStringLiteral("quoteStringsChar"), quoteStringsLine); registerField(QStringLiteral("quoteNumbersChar"), quoteNumbersLine); registerField(QStringLiteral("fieldDelimiter*"), fieldDelimiterLine); connect(quoteStringsCheckBox, &QCheckBox::toggled, quoteStringsLine, &KLineEdit::setEnabled); connect(quoteNumbersCheckBox, &QCheckBox::toggled, quoteNumbersLine, &KLineEdit::setEnabled); } void ExportFormatPage::initializePage() { exportColumnNamesCheckBox->setChecked(true); exportLineNumbersCheckBox->setChecked(false); quoteStringsCheckBox->setChecked(false); quoteNumbersCheckBox->setChecked(false); quoteStringsLine->setEnabled(false); quoteNumbersLine->setEnabled(false); quoteStringsLine->setText(QStringLiteral("\"")); quoteNumbersLine->setText(QStringLiteral("\"")); fieldDelimiterLine->setText(QStringLiteral("\\t")); } bool ExportFormatPage::validatePage() { if ((quoteStringsCheckBox->isChecked() && quoteStringsLine->text().isEmpty()) || (quoteNumbersCheckBox->isChecked() && quoteNumbersLine->text().isEmpty())) return false; if (fieldDelimiterLine->text().isEmpty()) return false; return true; } // END ExportFormatPage diff --git a/addons/katesql/katesqlconfigpage.cpp b/addons/katesql/katesqlconfigpage.cpp index 6e159104e..b8e508d3f 100644 --- a/addons/katesql/katesqlconfigpage.cpp +++ b/addons/katesql/katesqlconfigpage.cpp @@ -1,102 +1,102 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "katesqlconfigpage.h" #include "outputstylewidget.h" +#include #include #include -#include #include -#include #include +#include KateSQLConfigPage::KateSQLConfigPage(QWidget *parent) : KTextEditor::ConfigPage(parent) { QVBoxLayout *layout = new QVBoxLayout(this); m_box = new QCheckBox(i18nc("@option:check", "Save and restore connections in Kate session"), this); QGroupBox *stylesGroupBox = new QGroupBox(i18nc("@title:group", "Output Customization"), this); QVBoxLayout *stylesLayout = new QVBoxLayout(stylesGroupBox); m_outputStyleWidget = new OutputStyleWidget(this); stylesLayout->addWidget(m_outputStyleWidget); layout->addWidget(m_box); layout->addWidget(stylesGroupBox, 1); setLayout(layout); reset(); connect(m_box, &QCheckBox::stateChanged, this, &KateSQLConfigPage::changed); connect(m_outputStyleWidget, &OutputStyleWidget::changed, this, &KateSQLConfigPage::changed); } KateSQLConfigPage::~KateSQLConfigPage() { } QString KateSQLConfigPage::name() const { return i18nc("@title", "SQL"); } QString KateSQLConfigPage::fullName() const { return i18nc("@title:window", "SQL ConfigPage Settings"); } QIcon KateSQLConfigPage::icon() const { return QIcon::fromTheme(QLatin1String("server-database")); } void KateSQLConfigPage::apply() { KConfigGroup config(KSharedConfig::openConfig(), "KateSQLPlugin"); config.writeEntry("SaveConnections", m_box->isChecked()); m_outputStyleWidget->writeConfig(); config.sync(); emit settingsChanged(); } void KateSQLConfigPage::reset() { KConfigGroup config(KSharedConfig::openConfig(), "KateSQLPlugin"); m_box->setChecked(config.readEntry("SaveConnections", true)); m_outputStyleWidget->readConfig(); } void KateSQLConfigPage::defaults() { KConfigGroup config(KSharedConfig::openConfig(), "KateSQLPlugin"); config.revertToDefault("SaveConnections"); config.revertToDefault("OutputCustomization"); } diff --git a/addons/katesql/katesqlplugin.cpp b/addons/katesql/katesqlplugin.cpp index f66ee6a14..816896da7 100644 --- a/addons/katesql/katesqlplugin.cpp +++ b/addons/katesql/katesqlplugin.cpp @@ -1,65 +1,65 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "katesqlplugin.h" #include "katesqlconfigpage.h" #include "katesqlview.h" #include -#include #include #include +#include #include K_PLUGIN_FACTORY_WITH_JSON(KateSQLFactory, "katesql.json", registerPlugin();) // BEGIN KateSQLPLugin KateSQLPlugin::KateSQLPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { } KateSQLPlugin::~KateSQLPlugin() { } QObject *KateSQLPlugin::createView(KTextEditor::MainWindow *mainWindow) { KateSQLView *view = new KateSQLView(this, mainWindow); connect(this, &KateSQLPlugin::globalSettingsChanged, view, &KateSQLView::slotGlobalSettingsChanged); return view; } KTextEditor::ConfigPage *KateSQLPlugin::configPage(int number, QWidget *parent) { if (number != 0) return nullptr; KateSQLConfigPage *page = new KateSQLConfigPage(parent); connect(page, &KateSQLConfigPage::settingsChanged, this, &KateSQLPlugin::globalSettingsChanged); return page; } // END KateSQLPlugin #include "katesqlplugin.moc" diff --git a/addons/katesql/katesqlplugin.h b/addons/katesql/katesqlplugin.h index b6da2a7c8..8d2679324 100644 --- a/addons/katesql/katesqlplugin.h +++ b/addons/katesql/katesqlplugin.h @@ -1,53 +1,53 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #ifndef KATESQLPLUGIN_H #define KATESQLPLUGIN_H -#include -#include #include #include +#include +#include #include class KateSQLPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit KateSQLPlugin(QObject *parent = nullptr, const QList & = QList()); ~KateSQLPlugin() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; int configPages() const override { return 1; }; KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; QString configPageName(int number = 0) const; QString configPageFullName(int number = 0) const; QIcon configPageIcon(int number = 0) const; Q_SIGNALS: void globalSettingsChanged(); }; #endif // KATESQLPLUGIN_H diff --git a/addons/katesql/katesqlview.cpp b/addons/katesql/katesqlview.cpp index c413a5713..e0ae47290 100644 --- a/addons/katesql/katesqlview.cpp +++ b/addons/katesql/katesqlview.cpp @@ -1,359 +1,359 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "katesqlview.h" -#include "katesqlplugin.h" -#include "sqlmanager.h" #include "connectionmodel.h" -#include "textoutputwidget.h" -#include "dataoutputwidget.h" +#include "connectionwizard.h" #include "dataoutputmodel.h" #include "dataoutputview.h" -#include "schemawidget.h" -#include "schemabrowserwidget.h" -#include "connectionwizard.h" +#include "dataoutputwidget.h" +#include "katesqlplugin.h" #include "outputwidget.h" +#include "schemabrowserwidget.h" +#include "schemawidget.h" +#include "sqlmanager.h" +#include "textoutputwidget.h" -#include -#include #include #include +#include +#include #include +#include +#include #include #include -#include +#include #include #include -#include -#include -#include +#include -#include -#include -#include -#include #include +#include #include +#include +#include +#include KateSQLView::KateSQLView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mw) : QObject(mw) , KXMLGUIClient() , m_manager(new SQLManager(this)) , m_mainWindow(mw) { KXMLGUIClient::setComponentName(QStringLiteral("katesql"), i18n("Kate SQL Plugin")); setXMLFile(QStringLiteral("ui.rc")); m_outputToolView = mw->createToolView(plugin, QStringLiteral("kate_private_plugin_katesql_output"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("view-form-table")), i18nc("@title:window", "SQL Results")); m_schemaBrowserToolView = mw->createToolView(plugin, QStringLiteral("kate_private_plugin_katesql_schemabrowser"), KTextEditor::MainWindow::Left, QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@title:window", "SQL Schema Browser")); m_outputWidget = new KateSQLOutputWidget(m_outputToolView); m_schemaBrowserWidget = new SchemaBrowserWidget(m_schemaBrowserToolView, m_manager); m_connectionsComboBox = new KComboBox(false); m_connectionsComboBox->setEditable(false); m_connectionsComboBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_connectionsComboBox->setModel(m_manager->connectionModel()); setupActions(); m_mainWindow->guiFactory()->addClient(this); QMenu *sqlMenu = (QMenu *)factory()->container(QStringLiteral("SQL"), this); m_connectionsGroup = new QActionGroup(sqlMenu); m_connectionsGroup->setExclusive(true); connect(sqlMenu, &QMenu::aboutToShow, this, &KateSQLView::slotSQLMenuAboutToShow); connect(m_connectionsGroup, &QActionGroup::triggered, this, &KateSQLView::slotConnectionSelectedFromMenu); connect(m_manager, &SQLManager::error, this, &KateSQLView::slotError); connect(m_manager, &SQLManager::success, this, &KateSQLView::slotSuccess); connect(m_manager, &SQLManager::queryActivated, this, &KateSQLView::slotQueryActivated); connect(m_manager, &SQLManager::connectionCreated, this, &KateSQLView::slotConnectionCreated); connect(m_manager, &SQLManager::connectionAboutToBeClosed, this, &KateSQLView::slotConnectionAboutToBeClosed); connect(m_connectionsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KateSQLView::slotConnectionChanged); stateChanged(QStringLiteral("has_connection_selected"), KXMLGUIClient::StateReverse); } KateSQLView::~KateSQLView() { m_mainWindow->guiFactory()->removeClient(this); delete m_outputToolView; delete m_schemaBrowserToolView; delete m_manager; } void KateSQLView::setupActions() { QAction *action; KActionCollection *collection = actionCollection(); action = collection->addAction(QStringLiteral("connection_create")); action->setText(i18nc("@action:inmenu", "Add connection...")); action->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); connect(action, &QAction::triggered, this, &KateSQLView::slotConnectionCreate); action = collection->addAction(QStringLiteral("connection_remove")); action->setText(i18nc("@action:inmenu", "Remove connection")); action->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); connect(action, &QAction::triggered, this, &KateSQLView::slotConnectionRemove); action = collection->addAction(QStringLiteral("connection_edit")); action->setText(i18nc("@action:inmenu", "Edit connection...")); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(action, &QAction::triggered, this, &KateSQLView::slotConnectionEdit); action = collection->addAction(QStringLiteral("connection_reconnect")); action->setText(i18nc("@action:inmenu", "Reconnect")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(action, &QAction::triggered, this, &KateSQLView::slotConnectionReconnect); QWidgetAction *wa = new QWidgetAction(this); collection->addAction(QStringLiteral("connection_chooser"), wa); wa->setText(i18nc("@action:intoolbar", "Connection")); wa->setDefaultWidget(m_connectionsComboBox); action = collection->addAction(QStringLiteral("query_run")); action->setText(i18nc("@action:inmenu", "Run query")); action->setIcon(QIcon::fromTheme(QStringLiteral("quickopen"))); collection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_E)); connect(action, &QAction::triggered, this, &KateSQLView::slotRunQuery); /// TODO: stop sql query // action = collection->addAction("sql_stop"); // action->setText( i18n("Stop query") ); // action->setIcon( KIcon("process-stop") ); // action->setShortcut( QKeySequence(Qt::ALT + Qt::Key_F5) ); // connect( action , SIGNAL(triggered()) , this , SLOT(stopQuery())); } void KateSQLView::slotSQLMenuAboutToShow() { qDeleteAll(m_connectionsGroup->actions()); QMenu *sqlMenu = (QMenu *)factory()->container(QStringLiteral("SQL"), this); QAction *before = action("query_run"); QAbstractItemModel *model = m_manager->connectionModel(); int rows = model->rowCount(QModelIndex()); for (int row = 0; row < rows; row++) { QModelIndex index = model->index(row, 0, QModelIndex()); Q_ASSERT(index.isValid()); QString connectionName = index.data(Qt::DisplayRole).toString(); QAction *act = new QAction(connectionName, m_connectionsGroup); act->setCheckable(true); if (m_connectionsComboBox->currentText() == connectionName) act->setChecked(true); sqlMenu->insertAction(before, act); } sqlMenu->insertSeparator(before); } void KateSQLView::slotConnectionSelectedFromMenu(QAction *action) { m_connectionsComboBox->setCurrentItem(action->text()); } void KateSQLView::slotConnectionChanged(const QString &connection) { stateChanged(QStringLiteral("has_connection_selected"), (connection.isEmpty()) ? KXMLGUIClient::StateReverse : KXMLGUIClient::StateNoReverse); m_schemaBrowserWidget->schemaWidget()->buildTree(connection); } void KateSQLView::slotGlobalSettingsChanged() { m_outputWidget->dataOutputWidget()->model()->readConfig(); } void KateSQLView::readSessionConfig(KConfigBase *config, const QString &groupPrefix) { KConfigGroup globalConfig(KSharedConfig::openConfig(), "KateSQLPlugin"); bool saveConnections = globalConfig.readEntry("SaveConnections", true); if (!saveConnections) return; KConfigGroup group(config, groupPrefix + QLatin1String(":connections")); m_manager->loadConnections(&group); QString lastConnection = group.readEntry("LastUsed"); if (m_connectionsComboBox->contains(lastConnection)) m_connectionsComboBox->setCurrentItem(lastConnection); } void KateSQLView::writeSessionConfig(KConfigBase *config, const QString &groupPrefix) { KConfigGroup group(config, groupPrefix + QLatin1String(":connections")); group.deleteGroup(); KConfigGroup globalConfig(KSharedConfig::openConfig(), "KateSQLPlugin"); bool saveConnections = globalConfig.readEntry("SaveConnections", true); if (saveConnections) { m_manager->saveConnections(&group); group.writeEntry("LastUsed", m_connectionsComboBox->currentText()); } config->sync(); } void KateSQLView::slotConnectionCreate() { Connection c; ConnectionWizard wizard(m_manager, &c); if (wizard.exec() != QDialog::Accepted) return; for (int i = 1; QSqlDatabase::contains(c.name); i++) c.name = QStringLiteral("%1 (%2)").arg(c.name).arg(i); m_manager->createConnection(c); if (m_manager->storeCredentials(c) != 0) qDebug() << "Connection credentials not saved"; } void KateSQLView::slotConnectionEdit() { int i = m_connectionsComboBox->currentIndex(); if (i == -1) return; ConnectionModel *model = m_manager->connectionModel(); Connection c = model->data(model->index(i), Qt::UserRole).value(); QString previousName = c.name; ConnectionWizard wizard(m_manager, &c); if (wizard.exec() != QDialog::Accepted) return; m_manager->removeConnection(previousName); m_manager->createConnection(c); if (m_manager->storeCredentials(c) != 0) qDebug() << "Connection credentials not saved"; } void KateSQLView::slotConnectionRemove() { QString connection = m_connectionsComboBox->currentText(); if (!connection.isEmpty()) m_manager->removeConnection(connection); } void KateSQLView::slotConnectionReconnect() { QString connection = m_connectionsComboBox->currentText(); if (!connection.isEmpty()) m_manager->reopenConnection(connection); } void KateSQLView::slotConnectionAboutToBeClosed(const QString &name) { /// must delete the QSqlQuery object inside the model before closing connection if (name == m_currentResultsetConnection) m_outputWidget->dataOutputWidget()->clearResults(); } void KateSQLView::slotRunQuery() { /// TODO: /// bind parameters dialog? QString connection = m_connectionsComboBox->currentText(); if (connection.isEmpty()) { slotConnectionCreate(); return; } KTextEditor::View *view = m_mainWindow->activeView(); if (!view) return; QString text = (view->selection()) ? view->selectionText() : view->document()->text(); text = text.trimmed(); if (text.isEmpty()) return; m_manager->runQuery(text, connection); } void KateSQLView::slotError(const QString &message) { m_outputWidget->textOutputWidget()->showErrorMessage(message); m_outputWidget->setCurrentWidget(m_outputWidget->textOutputWidget()); m_mainWindow->showToolView(m_outputToolView); } void KateSQLView::slotSuccess(const QString &message) { m_outputWidget->textOutputWidget()->showSuccessMessage(message); m_outputWidget->setCurrentWidget(m_outputWidget->textOutputWidget()); m_mainWindow->showToolView(m_outputToolView); } void KateSQLView::slotQueryActivated(QSqlQuery &query, const QString &connection) { if (query.isSelect()) { m_currentResultsetConnection = connection; m_outputWidget->dataOutputWidget()->showQueryResultSets(query); m_outputWidget->setCurrentWidget(m_outputWidget->dataOutputWidget()); m_mainWindow->showToolView(m_outputToolView); } } void KateSQLView::slotConnectionCreated(const QString &name) { m_connectionsComboBox->setCurrentItem(name); m_schemaBrowserWidget->schemaWidget()->buildTree(name); } // END KateSQLView diff --git a/addons/katesql/outputstyle.h b/addons/katesql/outputstyle.h index 0f022d01a..e1c375214 100644 --- a/addons/katesql/outputstyle.h +++ b/addons/katesql/outputstyle.h @@ -1,35 +1,35 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #ifndef OUTPUTSTYLE_H #define OUTPUTSTYLE_H +#include +#include #include #include -#include -#include struct OutputStyle { QFont font; QBrush background; QBrush foreground; }; // Q_DECLARE_METATYPE(OutputStyle) #endif // OUTPUTSTYLE_H diff --git a/addons/katesql/outputstylewidget.cpp b/addons/katesql/outputstylewidget.cpp index d79e20cca..32a3e2831 100644 --- a/addons/katesql/outputstylewidget.cpp +++ b/addons/katesql/outputstylewidget.cpp @@ -1,206 +1,206 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "outputstylewidget.h" +#include +#include +#include +#include #include #include -#include #include -#include -#include -#include #include -#include +#include #include #include -#include +#include OutputStyleWidget::OutputStyleWidget(QWidget *parent) : QTreeWidget(parent) { setColumnCount(7); setRootIsDecorated(false); QStringList headerLabels; headerLabels << i18nc("@title:column", "Context") << QString() << QString() << QString() << QString() << i18nc("@title:column", "Text Color") << i18nc("@title:column", "Background Color"); setHeaderLabels(headerLabels); headerItem()->setIcon(1, QIcon::fromTheme(QStringLiteral("format-text-bold"))); headerItem()->setIcon(2, QIcon::fromTheme(QStringLiteral("format-text-italic"))); headerItem()->setIcon(3, QIcon::fromTheme(QStringLiteral("format-text-underline"))); headerItem()->setIcon(4, QIcon::fromTheme(QStringLiteral("format-text-strikethrough"))); addContext(QStringLiteral("text"), i18nc("@item:intable", "Text")); addContext(QStringLiteral("number"), i18nc("@item:intable", "Number")); addContext(QStringLiteral("bool"), i18nc("@item:intable", "Bool")); addContext(QStringLiteral("datetime"), i18nc("@item:intable", "Date & Time")); addContext(QStringLiteral("null"), i18nc("@item:intable", "NULL")); addContext(QStringLiteral("blob"), i18nc("@item:intable", "BLOB")); for (int i = 0; i < columnCount(); ++i) resizeColumnToContents(i); updatePreviews(); } OutputStyleWidget::~OutputStyleWidget() { } QTreeWidgetItem *OutputStyleWidget::addContext(const QString &key, const QString &name) { QTreeWidgetItem *item = new QTreeWidgetItem(this); item->setText(0, name); item->setData(0, Qt::UserRole, key); QCheckBox *boldCheckBox = new QCheckBox(this); QCheckBox *italicCheckBox = new QCheckBox(this); QCheckBox *underlineCheckBox = new QCheckBox(this); QCheckBox *strikeOutCheckBox = new QCheckBox(this); KColorButton *foregroundColorButton = new KColorButton(this); KColorButton *backgroundColorButton = new KColorButton(this); const KColorScheme scheme(QPalette::Active, KColorScheme::View); foregroundColorButton->setDefaultColor(scheme.foreground().color()); backgroundColorButton->setDefaultColor(scheme.background().color()); setItemWidget(item, 1, boldCheckBox); setItemWidget(item, 2, italicCheckBox); setItemWidget(item, 3, underlineCheckBox); setItemWidget(item, 4, strikeOutCheckBox); setItemWidget(item, 5, foregroundColorButton); setItemWidget(item, 6, backgroundColorButton); readConfig(item); connect(boldCheckBox, &QCheckBox::toggled, this, &OutputStyleWidget::slotChanged); connect(italicCheckBox, &QCheckBox::toggled, this, &OutputStyleWidget::slotChanged); connect(underlineCheckBox, &QCheckBox::toggled, this, &OutputStyleWidget::slotChanged); connect(strikeOutCheckBox, &QCheckBox::toggled, this, &OutputStyleWidget::slotChanged); connect(foregroundColorButton, &KColorButton::changed, this, &OutputStyleWidget::slotChanged); connect(backgroundColorButton, &KColorButton::changed, this, &OutputStyleWidget::slotChanged); return item; } void OutputStyleWidget::readConfig(QTreeWidgetItem *item) { KConfigGroup config(KSharedConfig::openConfig(), "KateSQLPlugin"); KConfigGroup g = config.group("OutputCustomization").group(item->data(0, Qt::UserRole).toString()); QCheckBox *boldCheckBox = static_cast(itemWidget(item, 1)); QCheckBox *italicCheckBox = static_cast(itemWidget(item, 2)); QCheckBox *underlineCheckBox = static_cast(itemWidget(item, 3)); QCheckBox *strikeOutCheckBox = static_cast(itemWidget(item, 4)); KColorButton *foregroundColorButton = static_cast(itemWidget(item, 5)); KColorButton *backgroundColorButton = static_cast(itemWidget(item, 6)); const QFont font = g.readEntry("font", QFontDatabase::systemFont(QFontDatabase::GeneralFont)); boldCheckBox->setChecked(font.bold()); italicCheckBox->setChecked(font.italic()); underlineCheckBox->setChecked(font.underline()); strikeOutCheckBox->setChecked(font.strikeOut()); foregroundColorButton->setColor(g.readEntry("foregroundColor", foregroundColorButton->defaultColor())); backgroundColorButton->setColor(g.readEntry("backgroundColor", backgroundColorButton->defaultColor())); } void OutputStyleWidget::writeConfig(QTreeWidgetItem *item) { KConfigGroup config(KSharedConfig::openConfig(), "KateSQLPlugin"); KConfigGroup g = config.group("OutputCustomization").group(item->data(0, Qt::UserRole).toString()); QCheckBox *boldCheckBox = static_cast(itemWidget(item, 1)); QCheckBox *italicCheckBox = static_cast(itemWidget(item, 2)); QCheckBox *underlineCheckBox = static_cast(itemWidget(item, 3)); QCheckBox *strikeOutCheckBox = static_cast(itemWidget(item, 4)); KColorButton *foregroundColorButton = static_cast(itemWidget(item, 5)); KColorButton *backgroundColorButton = static_cast(itemWidget(item, 6)); QFont f(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); f.setBold(boldCheckBox->isChecked()); f.setItalic(italicCheckBox->isChecked()); f.setUnderline(underlineCheckBox->isChecked()); f.setStrikeOut(strikeOutCheckBox->isChecked()); g.writeEntry("font", f); g.writeEntry("foregroundColor", foregroundColorButton->color()); g.writeEntry("backgroundColor", backgroundColorButton->color()); } void OutputStyleWidget::readConfig() { QTreeWidgetItem *root = invisibleRootItem(); for (int i = 0; i < root->childCount(); ++i) readConfig(root->child(i)); } void OutputStyleWidget::writeConfig() { KConfigGroup config(KSharedConfig::openConfig(), "KateSQLPlugin"); config.deleteGroup("OutputCustomization"); QTreeWidgetItem *root = invisibleRootItem(); for (int i = 0; i < root->childCount(); ++i) writeConfig(root->child(i)); } void OutputStyleWidget::updatePreviews() { QTreeWidgetItem *root = invisibleRootItem(); for (int i = 0; i < root->childCount(); ++i) { QTreeWidgetItem *item = root->child(i); const QCheckBox *boldCheckBox = static_cast(itemWidget(item, 1)); const QCheckBox *italicCheckBox = static_cast(itemWidget(item, 2)); const QCheckBox *underlineCheckBox = static_cast(itemWidget(item, 3)); const QCheckBox *strikeOutCheckBox = static_cast(itemWidget(item, 4)); const KColorButton *foregroundColorButton = static_cast(itemWidget(item, 5)); const KColorButton *backgroundColorButton = static_cast(itemWidget(item, 6)); QFont f(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); f.setBold(boldCheckBox->isChecked()); f.setItalic(italicCheckBox->isChecked()); f.setUnderline(underlineCheckBox->isChecked()); f.setStrikeOut(strikeOutCheckBox->isChecked()); item->setFont(0, f); item->setForeground(0, foregroundColorButton->color()); item->setBackground(0, backgroundColorButton->color()); } } void OutputStyleWidget::slotChanged() { updatePreviews(); emit changed(); } diff --git a/addons/katesql/outputwidget.cpp b/addons/katesql/outputwidget.cpp index 1b6c18690..5b7789319 100644 --- a/addons/katesql/outputwidget.cpp +++ b/addons/katesql/outputwidget.cpp @@ -1,38 +1,38 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "outputwidget.h" #include "dataoutputwidget.h" -#include "textoutputwidget.h" #include "klocalizedstring.h" +#include "textoutputwidget.h" KateSQLOutputWidget::KateSQLOutputWidget(QWidget *parent) : QTabWidget(parent) { addTab(m_textOutputWidget = new TextOutputWidget(this), QIcon::fromTheme(QStringLiteral("view-list-text")), i18nc("@title:window", "SQL Text Output")); addTab(m_dataOutputWidget = new DataOutputWidget(this), QIcon::fromTheme(QStringLiteral("view-form-table")), i18nc("@title:window", "SQL Data Output")); } KateSQLOutputWidget::~KateSQLOutputWidget() { } // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/katesql/schemabrowserwidget.cpp b/addons/katesql/schemabrowserwidget.cpp index 07d86a7c3..6226686c5 100644 --- a/addons/katesql/schemabrowserwidget.cpp +++ b/addons/katesql/schemabrowserwidget.cpp @@ -1,41 +1,41 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "schemabrowserwidget.h" #include "schemawidget.h" -#include #include +#include SchemaBrowserWidget::SchemaBrowserWidget(QWidget *parent, SQLManager *manager) : QWidget(parent) , m_schemaWidget(new SchemaWidget(this, manager)) { QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(m_schemaWidget); setLayout(layout); } SchemaBrowserWidget::~SchemaBrowserWidget() { } SchemaWidget *SchemaBrowserWidget::schemaWidget() const { return m_schemaWidget; } diff --git a/addons/katesql/schemawidget.cpp b/addons/katesql/schemawidget.cpp index 8a70045ab..38cf2f385 100644 --- a/addons/katesql/schemawidget.cpp +++ b/addons/katesql/schemawidget.cpp @@ -1,370 +1,370 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "schemawidget.h" #include "sqlmanager.h" -#include +#include #include +#include #include #include -#include -#include -#include +#include +#include +#include #include +#include #include -#include -#include #include -#include -#include -#include -#include +#include +#include +#include +#include SchemaWidget::SchemaWidget(QWidget *parent, SQLManager *manager) : QTreeWidget(parent) , m_manager(manager) { m_tablesLoaded = false; m_viewsLoaded = false; setHeaderLabels(QStringList() << i18nc("@title:column", "Database schema")); setContextMenuPolicy(Qt::CustomContextMenu); setDragDropMode(QAbstractItemView::DragOnly); setDragEnabled(true); setAcceptDrops(false); connect(this, &SchemaWidget::customContextMenuRequested, this, &SchemaWidget::slotCustomContextMenuRequested); connect(this, &SchemaWidget::itemExpanded, this, &SchemaWidget::slotItemExpanded); } SchemaWidget::~SchemaWidget() { } bool SchemaWidget::isConnectionValidAndOpen() { return m_manager->isValidAndOpen(m_connectionName); } void SchemaWidget::deleteChildren(QTreeWidgetItem *item) { const QList items = item->takeChildren(); for (QTreeWidgetItem *i : items) delete i; } void SchemaWidget::buildTree(const QString &connection) { m_connectionName = connection; clear(); m_tablesLoaded = false; m_viewsLoaded = false; if (!m_connectionName.isEmpty()) buildDatabase(new QTreeWidgetItem(this)); } void SchemaWidget::refresh() { buildTree(m_connectionName); } void SchemaWidget::buildDatabase(QTreeWidgetItem *databaseItem) { QSqlDatabase db = QSqlDatabase::database(m_connectionName); QString dbname = (db.isValid() ? db.databaseName() : m_connectionName); databaseItem->setText(0, dbname); databaseItem->setIcon(0, QIcon::fromTheme(QStringLiteral("server-database"))); QTreeWidgetItem *tablesItem = new QTreeWidgetItem(databaseItem, TablesFolderType); tablesItem->setText(0, i18nc("@title Folder name", "Tables")); tablesItem->setIcon(0, QIcon::fromTheme(QStringLiteral("folder"))); tablesItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); QTreeWidgetItem *viewsItem = new QTreeWidgetItem(databaseItem, ViewsFolderType); viewsItem->setText(0, i18nc("@title Folder name", "Views")); viewsItem->setIcon(0, QIcon::fromTheme(QStringLiteral("folder"))); viewsItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); databaseItem->setExpanded(true); } void SchemaWidget::buildTables(QTreeWidgetItem *tablesItem) { if (!isConnectionValidAndOpen()) return; QTreeWidgetItem *systemTablesItem = new QTreeWidgetItem(tablesItem, SystemTablesFolderType); systemTablesItem->setText(0, i18nc("@title Folder name", "System Tables")); systemTablesItem->setIcon(0, QIcon::fromTheme(QStringLiteral("folder"))); systemTablesItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); QSqlDatabase db = QSqlDatabase::database(m_connectionName); QStringList tables = db.tables(QSql::SystemTables); for (const QString &table : qAsConst(tables)) { QTreeWidgetItem *item = new QTreeWidgetItem(systemTablesItem, SystemTableType); item->setText(0, table); item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-table.png"))); item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); } tables = db.tables(QSql::Tables); for (const QString &table : qAsConst(tables)) { QTreeWidgetItem *item = new QTreeWidgetItem(tablesItem, TableType); item->setText(0, table); item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-table.png"))); item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); } m_tablesLoaded = true; } void SchemaWidget::buildViews(QTreeWidgetItem *viewsItem) { if (!isConnectionValidAndOpen()) return; QSqlDatabase db = QSqlDatabase::database(m_connectionName); const QStringList views = db.tables(QSql::Views); for (const QString &view : views) { QTreeWidgetItem *item = new QTreeWidgetItem(viewsItem, ViewType); item->setText(0, view); item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-view.png"))); item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); } m_viewsLoaded = true; } void SchemaWidget::buildFields(QTreeWidgetItem *tableItem) { if (!isConnectionValidAndOpen()) return; QSqlDatabase db = QSqlDatabase::database(m_connectionName); QString tableName = tableItem->text(0); QSqlIndex pk = db.primaryIndex(tableName); QSqlRecord rec = db.record(tableName); for (int i = 0; i < rec.count(); ++i) { QSqlField f = rec.field(i); QString fieldName = f.name(); QTreeWidgetItem *item = new QTreeWidgetItem(tableItem, FieldType); item->setText(0, fieldName); if (pk.contains(fieldName)) item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-field-pk.png"))); else item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-field.png"))); } } void SchemaWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) m_dragStartPosition = event->pos(); QTreeWidget::mousePressEvent(event); } void SchemaWidget::mouseMoveEvent(QMouseEvent *event) { if (!(event->buttons() & Qt::LeftButton)) return; if ((event->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance()) return; // QTreeWidgetItem *item = currentItem(); QTreeWidgetItem *item = itemAt(event->pos()); if (!item) return; if (item->type() != SchemaWidget::SystemTableType && item->type() != SchemaWidget::TableType && item->type() != SchemaWidget::ViewType && item->type() != SchemaWidget::FieldType) return; QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; if (item->type() == SchemaWidget::FieldType) mimeData->setText(QStringLiteral("%1.%2").arg(item->parent()->text(0), item->text(0))); else mimeData->setText(item->text(0)); drag->setMimeData(mimeData); drag->exec(Qt::CopyAction); QTreeWidget::mouseMoveEvent(event); } void SchemaWidget::slotItemExpanded(QTreeWidgetItem *item) { if (!item) return; switch (item->type()) { case SchemaWidget::TablesFolderType: { if (!m_tablesLoaded) buildTables(item); } break; case SchemaWidget::ViewsFolderType: { if (!m_viewsLoaded) buildViews(item); } break; case SchemaWidget::TableType: case SchemaWidget::SystemTableType: case SchemaWidget::ViewType: { if (item->childCount() == 0) buildFields(item); } break; default: break; } } void SchemaWidget::slotCustomContextMenuRequested(const QPoint &pos) { QMenu menu; menu.addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu Context menu", "Refresh"), this, &SchemaWidget::refresh); QTreeWidgetItem *item = itemAt(pos); if (item) { if (item->type() == SchemaWidget::SystemTableType || item->type() == SchemaWidget::TableType || item->type() == SchemaWidget::ViewType || item->type() == SchemaWidget::FieldType) { menu.addSeparator(); QMenu *submenu = menu.addMenu(QIcon::fromTheme(QStringLiteral("tools-wizard")), i18nc("@action:inmenu Submenu title", "Generate")); submenu->addAction(i18n("SELECT"), this, &SchemaWidget::generateSelect); submenu->addAction(i18n("UPDATE"), this, &SchemaWidget::generateUpdate); submenu->addAction(i18n("INSERT"), this, &SchemaWidget::generateInsert); submenu->addAction(i18n("DELETE"), this, &SchemaWidget::generateDelete); } } menu.exec(QCursor::pos()); } void SchemaWidget::generateStatement(QSqlDriver::StatementType statementType) { if (!isConnectionValidAndOpen()) return; QSqlDatabase db = QSqlDatabase::database(m_connectionName); QSqlDriver *drv = db.driver(); if (!drv) return; QTreeWidgetItem *item = currentItem(); if (!item) return; QString statement; switch (item->type()) { case TableType: case SystemTableType: case ViewType: { QString tableName = item->text(0); QSqlRecord rec = db.record(tableName); // set all fields to a value (NULL) // values are needed to generate update and insert statements if (statementType == QSqlDriver::UpdateStatement || statementType == QSqlDriver::InsertStatement) for (int i = 0, n = rec.count(); i < n; ++i) rec.setNull(i); statement = drv->sqlStatement(statementType, tableName, rec, false); } break; case FieldType: { QString tableName = item->parent()->text(0); QSqlRecord rec = db.record(tableName); // get the selected column... QSqlField field = rec.field(item->text(0)); // ...and set its value to NULL field.clear(); // clear all columns and re-append the selected one rec.clear(); rec.append(field); statement = drv->sqlStatement(statementType, tableName, rec, false); if (statementType == QSqlDriver::DeleteStatement) statement += QLatin1Char(' ') + drv->sqlStatement(QSqlDriver::WhereStatement, tableName, rec, false).replace(QLatin1String(" IS NULL"), QLatin1String("=?")); } break; } KTextEditor::MainWindow *mw = KTextEditor::Editor::instance()->application()->activeMainWindow(); KTextEditor::View *kv = mw->activeView(); // replace NULL with a more generic '?' statement.replace(QLatin1String("NULL"), QLatin1String("?")); if (kv) { // paste statement in the active view kv->insertText(statement); kv->setFocus(); } qDebug() << "Generated statement:" << statement; } void SchemaWidget::generateSelect() { generateStatement(QSqlDriver::SelectStatement); } void SchemaWidget::generateUpdate() { generateStatement(QSqlDriver::UpdateStatement); } void SchemaWidget::generateInsert() { generateStatement(QSqlDriver::InsertStatement); } void SchemaWidget::generateDelete() { generateStatement(QSqlDriver::DeleteStatement); } diff --git a/addons/katesql/schemawidget.h b/addons/katesql/schemawidget.h index ab011138b..af815276f 100644 --- a/addons/katesql/schemawidget.h +++ b/addons/katesql/schemawidget.h @@ -1,80 +1,80 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #ifndef SCHEMAWIDGET_H #define SCHEMAWIDGET_H class SQLManager; class QMouseEvent; #include #include -#include #include +#include class SchemaWidget : public QTreeWidget { Q_OBJECT public: static const int TableType = QTreeWidgetItem::UserType + 1; static const int SystemTableType = QTreeWidgetItem::UserType + 2; static const int ViewType = QTreeWidgetItem::UserType + 3; static const int FieldType = QTreeWidgetItem::UserType + 4; static const int TablesFolderType = QTreeWidgetItem::UserType + 101; static const int SystemTablesFolderType = QTreeWidgetItem::UserType + 102; static const int ViewsFolderType = QTreeWidgetItem::UserType + 103; SchemaWidget(QWidget *parent, SQLManager *manager); ~SchemaWidget() override; void buildDatabase(QTreeWidgetItem *databaseItem); void buildTables(QTreeWidgetItem *tablesItem); void buildViews(QTreeWidgetItem *viewsItem); void buildFields(QTreeWidgetItem *tableItem); public Q_SLOTS: void buildTree(const QString &connection); void refresh(); void generateSelect(); void generateUpdate(); void generateInsert(); void generateDelete(); void generateStatement(QSqlDriver::StatementType type); private Q_SLOTS: void slotCustomContextMenuRequested(const QPoint &pos); void slotItemExpanded(QTreeWidgetItem *item); private: void deleteChildren(QTreeWidgetItem *item); void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; bool isConnectionValidAndOpen(); QString m_connectionName; QPoint m_dragStartPosition; bool m_tablesLoaded; bool m_viewsLoaded; SQLManager *m_manager; }; #endif // SCHEMAWIDGET_H diff --git a/addons/katesql/sqlmanager.cpp b/addons/katesql/sqlmanager.cpp index b322cfd9d..a3aac5927 100644 --- a/addons/katesql/sqlmanager.cpp +++ b/addons/katesql/sqlmanager.cpp @@ -1,357 +1,357 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "sqlmanager.h" #include "connectionmodel.h" -#include #include #include +#include #include #include -#include -#include #include +#include +#include using KWallet::Wallet; SQLManager::SQLManager(QObject *parent) : QObject(parent) , m_model(new ConnectionModel(this)) { } SQLManager::~SQLManager() { for (int i = 0; i < m_model->rowCount(); i++) { QString connection = m_model->data(m_model->index(i), Qt::DisplayRole).toString(); QSqlDatabase::removeDatabase(connection); } delete m_model; delete m_wallet; } void SQLManager::createConnection(const Connection &conn) { if (QSqlDatabase::contains(conn.name)) { qDebug() << "connection" << conn.name << "already exist"; QSqlDatabase::removeDatabase(conn.name); } QSqlDatabase db = QSqlDatabase::addDatabase(conn.driver, conn.name); if (!db.isValid()) { emit error(db.lastError().text()); QSqlDatabase::removeDatabase(conn.name); return; } db.setHostName(conn.hostname); db.setUserName(conn.username); db.setPassword(conn.password); db.setDatabaseName(conn.database); db.setConnectOptions(conn.options); if (conn.port > 0) db.setPort(conn.port); m_model->addConnection(conn); // try to open connection, with or without password if (db.open()) m_model->setStatus(conn.name, Connection::ONLINE); else { if (conn.status != Connection::REQUIRE_PASSWORD) { m_model->setStatus(conn.name, Connection::OFFLINE); emit error(db.lastError().text()); } } emit connectionCreated(conn.name); } bool SQLManager::testConnection(const Connection &conn, QSqlError &error) { QString connectionName = (conn.name.isEmpty()) ? QStringLiteral("katesql-test") : conn.name; QSqlDatabase db = QSqlDatabase::addDatabase(conn.driver, connectionName); if (!db.isValid()) { error = db.lastError(); QSqlDatabase::removeDatabase(connectionName); return false; } db.setHostName(conn.hostname); db.setUserName(conn.username); db.setPassword(conn.password); db.setDatabaseName(conn.database); db.setConnectOptions(conn.options); if (conn.port > 0) db.setPort(conn.port); if (!db.open()) { error = db.lastError(); QSqlDatabase::removeDatabase(connectionName); return false; } QSqlDatabase::removeDatabase(connectionName); return true; } bool SQLManager::isValidAndOpen(const QString &connection) { QSqlDatabase db = QSqlDatabase::database(connection); if (!db.isValid()) { m_model->setStatus(connection, Connection::OFFLINE); emit error(db.lastError().text()); return false; } if (!db.isOpen()) { qDebug() << "database connection is not open. trying to open it..."; if (m_model->status(connection) == Connection::REQUIRE_PASSWORD) { QString password; int ret = readCredentials(connection, password); if (ret != 0) qDebug() << "Can't retrieve password from kwallet. returned code" << ret; else { db.setPassword(password); m_model->setPassword(connection, password); } } if (!db.open()) { m_model->setStatus(connection, Connection::OFFLINE); emit error(db.lastError().text()); return false; } } m_model->setStatus(connection, Connection::ONLINE); return true; } void SQLManager::reopenConnection(const QString &name) { emit connectionAboutToBeClosed(name); QSqlDatabase db = QSqlDatabase::database(name); db.close(); isValidAndOpen(name); } Wallet *SQLManager::openWallet() { if (!m_wallet) /// FIXME get kate window id... m_wallet = Wallet::openWallet(KWallet::Wallet::NetworkWallet(), 0); if (!m_wallet) return nullptr; QString folder(QStringLiteral("SQL Connections")); if (!m_wallet->hasFolder(folder)) m_wallet->createFolder(folder); m_wallet->setFolder(folder); return m_wallet; } // return 0 on success, -1 on error, -2 on user reject int SQLManager::storeCredentials(const Connection &conn) { // Sqlite is without password, avoid to open wallet if (conn.driver.contains(QLatin1String("QSQLITE"))) return 0; Wallet *wallet = openWallet(); if (!wallet) // user reject return -2; QMap map; map[QStringLiteral("driver")] = conn.driver.toUpper(); map[QStringLiteral("hostname")] = conn.hostname.toUpper(); map[QStringLiteral("port")] = QString::number(conn.port); map[QStringLiteral("database")] = conn.database.toUpper(); map[QStringLiteral("username")] = conn.username; map[QStringLiteral("password")] = conn.password; return (wallet->writeMap(conn.name, map) == 0) ? 0 : -1; } // return 0 on success, -1 on error or not found, -2 on user reject // if success, password contain the password int SQLManager::readCredentials(const QString &name, QString &password) { Wallet *wallet = openWallet(); if (!wallet) // user reject return -2; QMap map; if (wallet->readMap(name, map) == 0) { if (!map.isEmpty()) { password = map.value(QStringLiteral("password")); return 0; } } return -1; } ConnectionModel *SQLManager::connectionModel() { return m_model; } void SQLManager::removeConnection(const QString &name) { emit connectionAboutToBeClosed(name); m_model->removeConnection(name); QSqlDatabase::removeDatabase(name); emit connectionRemoved(name); } /// TODO: read KUrl instead of QString for sqlite paths void SQLManager::loadConnections(KConfigGroup *connectionsGroup) { Connection c; const auto groupList = connectionsGroup->groupList(); for (const QString &groupName : groupList) { qDebug() << "reading group:" << groupName; KConfigGroup group = connectionsGroup->group(groupName); c.name = groupName; c.driver = group.readEntry("driver"); c.database = group.readEntry("database"); c.options = group.readEntry("options"); if (!c.driver.contains(QLatin1String("QSQLITE"))) { c.hostname = group.readEntry("hostname"); c.username = group.readEntry("username"); c.port = group.readEntry("port", 0); // for compatibility with version 0.2, when passwords // were stored in config file instead of kwallet c.password = group.readEntry("password"); if (!c.password.isEmpty()) c.status = Connection::ONLINE; else c.status = Connection::REQUIRE_PASSWORD; } createConnection(c); } } void SQLManager::saveConnections(KConfigGroup *connectionsGroup) { for (int i = 0; i < m_model->rowCount(); i++) saveConnection(connectionsGroup, m_model->data(m_model->index(i), Qt::UserRole).value()); } /// TODO: write KUrl instead of QString for sqlite paths void SQLManager::saveConnection(KConfigGroup *connectionsGroup, const Connection &conn) { qDebug() << "saving connection" << conn.name; KConfigGroup group = connectionsGroup->group(conn.name); group.writeEntry("driver", conn.driver); group.writeEntry("database", conn.database); group.writeEntry("options", conn.options); if (!conn.driver.contains(QLatin1String("QSQLITE"))) { group.writeEntry("hostname", conn.hostname); group.writeEntry("username", conn.username); group.writeEntry("port", conn.port); } } void SQLManager::runQuery(const QString &text, const QString &connection) { qDebug() << "connection:" << connection; qDebug() << "text:" << text; if (text.isEmpty()) return; if (!isValidAndOpen(connection)) return; QSqlDatabase db = QSqlDatabase::database(connection); QSqlQuery query(db); if (!query.prepare(text)) { QSqlError err = query.lastError(); if (err.type() == QSqlError::ConnectionError) m_model->setStatus(connection, Connection::OFFLINE); emit error(err.text()); return; } if (!query.exec()) { QSqlError err = query.lastError(); if (err.type() == QSqlError::ConnectionError) m_model->setStatus(connection, Connection::OFFLINE); emit error(err.text()); return; } QString message; /// TODO: improve messages if (query.isSelect()) { if (!query.driver()->hasFeature(QSqlDriver::QuerySize)) message = i18nc("@info", "Query completed successfully"); else { int nRowsSelected = query.size(); message = i18ncp("@info", "%1 record selected", "%1 records selected", nRowsSelected); } } else { int nRowsAffected = query.numRowsAffected(); message = i18ncp("@info", "%1 row affected", "%1 rows affected", nRowsAffected); } emit success(message); emit queryActivated(query, connection); } diff --git a/addons/katesql/textoutputwidget.cpp b/addons/katesql/textoutputwidget.cpp index d80f10193..794446e1e 100644 --- a/addons/katesql/textoutputwidget.cpp +++ b/addons/katesql/textoutputwidget.cpp @@ -1,108 +1,108 @@ /* Copyright (C) 2010 Marco Mentasti 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. */ #include "textoutputwidget.h" #include "connection.h" -#include #include #include +#include #include +#include +#include #include #include -#include -#include TextOutputWidget::TextOutputWidget(QWidget *parent) : QWidget(parent) { m_succesTextColor = QColor::fromRgb(3, 191, 3); m_succesBackgroundColor = QColor::fromRgb(231, 247, 231); m_errorTextColor = QColor::fromRgb(191, 3, 3); m_errorBackgroundColor = QColor::fromRgb(247, 231, 231); m_layout = new QHBoxLayout(this); m_output = new QTextEdit(); m_output->setReadOnly(true); QFont fixedFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_output->setCurrentFont(fixedFont); KToolBar *toolbar = new KToolBar(this); toolbar->setOrientation(Qt::Vertical); toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); // ensure reasonable icons sizes, like e.g. the quick-open and co. icons // the normal toolbar sizes are TOO large, e.g. for scaled stuff even more! const int iconSize = style()->pixelMetric(QStyle::PM_ButtonIconSize, nullptr, this); toolbar->setIconSize(QSize(iconSize, iconSize)); /// TODO: disable actions if no results are displayed QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18nc("@action:intoolbar", "Clear"), this); toolbar->addAction(action); connect(action, &QAction::triggered, m_output, &QTextEdit::clear); m_layout->addWidget(toolbar); m_layout->addWidget(m_output, 1); m_layout->setContentsMargins(0, 0, 0, 0); setLayout(m_layout); } TextOutputWidget::~TextOutputWidget() { } void TextOutputWidget::showErrorMessage(const QString &message) { QColor previousBackgroundColor = m_output->textBackgroundColor(); QColor previousColor = m_output->textColor(); m_output->setTextBackgroundColor(m_errorBackgroundColor); m_output->setTextColor(m_errorTextColor); writeMessage(message); m_output->setTextBackgroundColor(previousBackgroundColor); m_output->setTextColor(previousColor); } void TextOutputWidget::showSuccessMessage(const QString &message) { QColor previousBackgroundColor = m_output->textBackgroundColor(); QColor previousColor = m_output->textColor(); m_output->setTextBackgroundColor(m_succesBackgroundColor); m_output->setTextColor(m_succesTextColor); writeMessage(message); m_output->setTextBackgroundColor(previousBackgroundColor); m_output->setTextColor(previousColor); } void TextOutputWidget::writeMessage(const QString &msg) { m_output->append(QStringLiteral("%1: %2\n").arg(QDateTime::currentDateTime().toString(Qt::SystemLocaleDate)).arg(msg)); raise(); } diff --git a/addons/konsole/kateconsole.cpp b/addons/konsole/kateconsole.cpp index 93a808504..a2a13c387 100644 --- a/addons/konsole/kateconsole.cpp +++ b/addons/konsole/kateconsole.cpp @@ -1,500 +1,500 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2002 Anders Lund Copyright (C) 2007 Anders Lund Copyright (C) 2017 Ederag 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. */ #include "kateconsole.h" #include #include -#include #include +#include -#include -#include -#include #include #include +#include +#include +#include #include #include -#include -#include -#include -#include #include +#include #include +#include +#include #include #include +#include +#include #include -#include -#include -#include -#include -#include #include #include +#include +#include #include #include +#include +#include K_PLUGIN_FACTORY_WITH_JSON(KateKonsolePluginFactory, "katekonsoleplugin.json", registerPlugin();) KateKonsolePlugin::KateKonsolePlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { m_previousEditorEnv = qgetenv("EDITOR"); if (!KAuthorized::authorize(QStringLiteral("shell_access"))) { KMessageBox::sorry(nullptr, i18n("You do not have enough karma to access a shell or terminal emulation")); } } KateKonsolePlugin::~KateKonsolePlugin() { qputenv("EDITOR", m_previousEditorEnv.data()); } QObject *KateKonsolePlugin::createView(KTextEditor::MainWindow *mainWindow) { KateKonsolePluginView *view = new KateKonsolePluginView(this, mainWindow); return view; } KTextEditor::ConfigPage *KateKonsolePlugin::configPage(int number, QWidget *parent) { if (number != 0) return nullptr; return new KateKonsoleConfigPage(parent, this); } void KateKonsolePlugin::readConfig() { for (KateKonsolePluginView *view : qAsConst(mViews)) view->readConfig(); } KateKonsolePluginView::KateKonsolePluginView(KateKonsolePlugin *plugin, KTextEditor::MainWindow *mainWindow) : QObject(mainWindow) , m_plugin(plugin) { // init console QWidget *toolview = mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katekonsoleplugin"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("utilities-terminal")), i18n("Terminal")); m_console = new KateConsole(m_plugin, mainWindow, toolview); // register this view m_plugin->mViews.append(this); } KateKonsolePluginView::~KateKonsolePluginView() { // unregister this view m_plugin->mViews.removeAll(this); // cleanup, kill toolview + console QWidget *toolview = m_console->parentWidget(); delete m_console; delete toolview; } void KateKonsolePluginView::readConfig() { m_console->readConfig(); } KateConsole::KateConsole(KateKonsolePlugin *plugin, KTextEditor::MainWindow *mw, QWidget *parent) : QWidget(parent) , m_part(nullptr) , m_mw(mw) , m_toolView(parent) , m_plugin(plugin) { KXMLGUIClient::setComponentName(QStringLiteral("katekonsole"), i18n("Kate Terminal")); setXMLFile(QStringLiteral("ui.rc")); // make sure we have a vertical layout new QVBoxLayout(this); layout()->setContentsMargins(0, 0, 0, 0); QAction *a = actionCollection()->addAction(QStringLiteral("katekonsole_tools_pipe_to_terminal")); a->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); a->setText(i18nc("@action", "&Pipe to Terminal")); connect(a, &QAction::triggered, this, &KateConsole::slotPipeToConsole); a = actionCollection()->addAction(QStringLiteral("katekonsole_tools_sync")); a->setText(i18nc("@action", "S&ynchronize Terminal with Current Document")); connect(a, &QAction::triggered, this, &KateConsole::slotManualSync); a = actionCollection()->addAction(QStringLiteral("katekonsole_tools_run")); a->setText(i18nc("@action", "Run Current Document")); connect(a, &QAction::triggered, this, &KateConsole::slotRun); a = actionCollection()->addAction(QStringLiteral("katekonsole_tools_toggle_focus")); a->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); a->setText(i18nc("@action", "&Focus Terminal")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::Key_F4)); connect(a, &QAction::triggered, this, &KateConsole::slotToggleFocus); m_mw->guiFactory()->addClient(this); readConfig(); } KateConsole::~KateConsole() { m_mw->guiFactory()->removeClient(this); if (m_part) disconnect(m_part, &KParts::ReadOnlyPart::destroyed, this, &KateConsole::slotDestroyed); } void KateConsole::loadConsoleIfNeeded() { if (m_part) return; if (!window() || !parentWidget()) return; if (!window() || !isVisibleTo(window())) return; /** * get konsole part factory */ KPluginFactory *factory = KPluginLoader(QStringLiteral("konsolepart")).factory(); if (!factory) return; m_part = factory->create(this, this); if (!m_part) return; layout()->addWidget(m_part->widget()); // start the terminal qobject_cast(m_part)->showShellInDir(QString()); // KGlobal::locale()->insertCatalog("konsole"); // FIXME KF5: insert catalog setFocusProxy(m_part->widget()); m_part->widget()->show(); connect(m_part, &KParts::ReadOnlyPart::destroyed, this, &KateConsole::slotDestroyed); connect(m_part, SIGNAL(overrideShortcut(QKeyEvent *, bool &)), this, SLOT(overrideShortcut(QKeyEvent *, bool &))); slotSync(); } void KateConsole::slotDestroyed() { m_part = nullptr; m_currentPath.clear(); setFocusProxy(nullptr); // hide the dockwidget if (parentWidget()) { m_mw->hideToolView(m_toolView); } } void KateConsole::overrideShortcut(QKeyEvent *, bool &override) { /** * let konsole handle all shortcuts */ override = true; } void KateConsole::showEvent(QShowEvent *) { if (m_part) return; loadConsoleIfNeeded(); } void KateConsole::cd(const QString &path) { if (m_currentPath == path) return; if (!m_part) return; m_currentPath = path; QString command = QLatin1String(" cd ") + KShell::quoteArg(m_currentPath) + QLatin1Char('\n'); // special handling for some interpreters TerminalInterface *t = qobject_cast(m_part); if (t) { // ghci doesn't allow \space dir names, does allow spaces in dir names // irb can take spaces or \space but doesn't allow " 'path' " if (t->foregroundProcessName() == QLatin1String("irb")) { command = QLatin1String("Dir.chdir(\"") + path + QLatin1String("\") \n"); } else if (t->foregroundProcessName() == QLatin1String("ghc")) { command = QLatin1String(":cd ") + path + QLatin1Char('\n'); } } // Send prior Ctrl-E, Ctrl-U to ensure the line is empty sendInput(QStringLiteral("\x05\x15")); sendInput(command); } void KateConsole::sendInput(const QString &text) { loadConsoleIfNeeded(); if (!m_part) return; TerminalInterface *t = qobject_cast(m_part); if (!t) return; t->sendInput(text); } void KateConsole::slotPipeToConsole() { if (KMessageBox::warningContinueCancel(m_mw->window(), i18n("Do you really want to pipe the text to the console? This will execute any contained commands with your user rights."), i18n("Pipe to Terminal?"), KGuiItem(i18n("Pipe to Terminal")), KStandardGuiItem::cancel(), QStringLiteral("Pipe To Terminal Warning")) != KMessageBox::Continue) return; KTextEditor::View *v = m_mw->activeView(); if (!v) return; if (v->selection()) sendInput(v->selectionText()); else sendInput(v->document()->text()); } void KateConsole::slotSync(KTextEditor::View *) { if (m_mw->activeView()) { QUrl u = m_mw->activeView()->document()->url(); if (u.isValid() && u.isLocalFile()) { QFileInfo fi(u.toLocalFile()); cd(fi.absolutePath()); } else if (!u.isEmpty()) { sendInput(QStringLiteral("### ") + i18n("Sorry, cannot cd into '%1'", u.toLocalFile()) + QLatin1Char('\n')); } } } void KateConsole::slotManualSync() { m_currentPath.clear(); slotSync(); if (!m_part || !m_part->widget()->isVisible()) m_mw->showToolView(parentWidget()); } void KateConsole::slotRun() { if (m_mw->activeView()) { KTextEditor::Document *document = m_mw->activeView()->document(); QUrl u = document->url(); if (!u.isLocalFile()) { QPointer message = new KTextEditor::Message(i18n("Not a local file: '%1'", u.path()), KTextEditor::Message::Error); // auto hide is enabled and set to a sane default value of several seconds. message->setAutoHide(2000); message->setAutoHideMode(KTextEditor::Message::Immediate); document->postMessage(message); return; } // ensure that file is saved if (document->isModified()) { document->save(); } // The string that should be output to terminal, upon acceptance QString output_str; // prefix first output_str += KConfigGroup(KSharedConfig::openConfig(), "Konsole").readEntry("RunPrefix", ""); // then filename if (KConfigGroup(KSharedConfig::openConfig(), "Konsole").readEntry("RemoveExtension", true)) { // append filename without extension (i.e. keep only the basename) output_str += QFileInfo(u.path()).baseName() + QLatin1Char('\n'); } else { // append filename to the terminal output_str += QFileInfo(u.path()).fileName() + QLatin1Char('\n'); } if (KMessageBox::Continue != KMessageBox::warningContinueCancel(m_mw->window(), i18n("Do you really want to Run the document ?\n" "This will execute the following command,\n" "with your user rights, in the terminal:\n" "'%1'", output_str), i18n("Run in Terminal?"), KGuiItem(i18n("Run")), KStandardGuiItem::cancel(), QStringLiteral("Konsole: Run in Terminal Warning"))) { return; } // echo to terminal sendInput(output_str); } } void KateConsole::slotToggleFocus() { QAction *action = actionCollection()->action(QStringLiteral("katekonsole_tools_toggle_focus")); if (!m_part) { m_mw->showToolView(parentWidget()); action->setText(i18n("Defocus Terminal")); return; // this shows and focuses the konsole } if (!m_part) return; if (m_part->widget()->hasFocus()) { if (m_mw->activeView()) m_mw->activeView()->setFocus(); action->setText(i18n("Focus Terminal")); } else { // show the view if it is hidden if (parentWidget()->isHidden()) m_mw->showToolView(parentWidget()); else // should focus the widget too! m_part->widget()->setFocus(Qt::OtherFocusReason); action->setText(i18n("Defocus Terminal")); } } void KateConsole::readConfig() { disconnect(m_mw, &KTextEditor::MainWindow::viewChanged, this, &KateConsole::slotSync); if (KConfigGroup(KSharedConfig::openConfig(), "Konsole").readEntry("AutoSyncronize", true)) { connect(m_mw, &KTextEditor::MainWindow::viewChanged, this, &KateConsole::slotSync); } if (KConfigGroup(KSharedConfig::openConfig(), "Konsole").readEntry("SetEditor", false)) qputenv("EDITOR", "kate -b"); else qputenv("EDITOR", m_plugin->previousEditorEnv().data()); } KateKonsoleConfigPage::KateKonsoleConfigPage(QWidget *parent, KateKonsolePlugin *plugin) : KTextEditor::ConfigPage(parent) , mPlugin(plugin) { QVBoxLayout *lo = new QVBoxLayout(this); lo->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); lo->setContentsMargins(0, 0, 0, 0); cbAutoSyncronize = new QCheckBox(i18n("&Automatically synchronize the terminal with the current document when possible"), this); lo->addWidget(cbAutoSyncronize); QVBoxLayout *vboxRun = new QVBoxLayout; QGroupBox *groupRun = new QGroupBox(i18n("Run in terminal"), this); // Remove extension cbRemoveExtension = new QCheckBox(i18n("&Remove extension"), this); vboxRun->addWidget(cbRemoveExtension); // Prefix QFrame *framePrefix = new QFrame(this); QHBoxLayout *hboxPrefix = new QHBoxLayout(framePrefix); QLabel *label = new QLabel(i18n("Prefix:"), framePrefix); hboxPrefix->addWidget(label); lePrefix = new QLineEdit(framePrefix); hboxPrefix->addWidget(lePrefix); vboxRun->addWidget(framePrefix); // show warning next time QFrame *frameWarn = new QFrame(this); QHBoxLayout *hboxWarn = new QHBoxLayout(frameWarn); QPushButton *buttonWarn = new QPushButton(i18n("&Show warning next time"), frameWarn); buttonWarn->setWhatsThis( i18n("The next time '%1' is executed, " "make sure a warning window will pop up, " "displaying the command to be sent to terminal, " "for review.", i18n("Run in terminal"))); connect(buttonWarn, &QPushButton::pressed, this, &KateKonsoleConfigPage::slotEnableRunWarning); hboxWarn->addWidget(buttonWarn); vboxRun->addWidget(frameWarn); groupRun->setLayout(vboxRun); lo->addWidget(groupRun); cbSetEditor = new QCheckBox(i18n("Set &EDITOR environment variable to 'kate -b'"), this); lo->addWidget(cbSetEditor); QLabel *tmp = new QLabel(this); tmp->setText(i18n("Important: The document has to be closed to make the console application continue")); lo->addWidget(tmp); reset(); lo->addStretch(); connect(cbAutoSyncronize, &QCheckBox::stateChanged, this, &KateKonsoleConfigPage::changed); connect(cbRemoveExtension, &QCheckBox::stateChanged, this, &KTextEditor::ConfigPage::changed); connect(lePrefix, &QLineEdit::textChanged, this, &KateKonsoleConfigPage::changed); connect(cbSetEditor, &QCheckBox::stateChanged, this, &KateKonsoleConfigPage::changed); } void KateKonsoleConfigPage::slotEnableRunWarning() { KMessageBox::enableMessage(QStringLiteral("Konsole: Run in Terminal Warning")); } QString KateKonsoleConfigPage::name() const { return i18n("Terminal"); } QString KateKonsoleConfigPage::fullName() const { return i18n("Terminal Settings"); } QIcon KateKonsoleConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("utilities-terminal")); } void KateKonsoleConfigPage::apply() { KConfigGroup config(KSharedConfig::openConfig(), "Konsole"); config.writeEntry("AutoSyncronize", cbAutoSyncronize->isChecked()); config.writeEntry("RemoveExtension", cbRemoveExtension->isChecked()); config.writeEntry("RunPrefix", lePrefix->text()); config.writeEntry("SetEditor", cbSetEditor->isChecked()); config.sync(); mPlugin->readConfig(); } void KateKonsoleConfigPage::reset() { KConfigGroup config(KSharedConfig::openConfig(), "Konsole"); cbAutoSyncronize->setChecked(config.readEntry("AutoSyncronize", true)); cbRemoveExtension->setChecked(config.readEntry("RemoveExtension", false)); lePrefix->setText(config.readEntry("RunPrefix", "")); cbSetEditor->setChecked(config.readEntry("SetEditor", false)); } #include "kateconsole.moc" // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/konsole/kateconsole.h b/addons/konsole/kateconsole.h index dc53148b0..a8f3ee192 100644 --- a/addons/konsole/kateconsole.h +++ b/addons/konsole/kateconsole.h @@ -1,240 +1,240 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2002 Anders Lund Copyright (C) 2017 Ederag 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. */ #ifndef __KATE_CONSOLE_H__ #define __KATE_CONSOLE_H__ #include -#include #include +#include -#include #include +#include #include class QShowEvent; namespace KParts { class ReadOnlyPart; } class KateConsole; class KateKonsolePluginView; class KateKonsolePlugin : public KTextEditor::Plugin { Q_OBJECT friend class KateKonsolePluginView; public: explicit KateKonsolePlugin(QObject *parent = nullptr, const QList & = QList()); ~KateKonsolePlugin() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; int configPages() const override { return 1; } KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; void readConfig(); QByteArray previousEditorEnv() { return m_previousEditorEnv; } private: QList mViews; QByteArray m_previousEditorEnv; }; class KateKonsolePluginView : public QObject { Q_OBJECT public: /** * Constructor. */ KateKonsolePluginView(KateKonsolePlugin *plugin, KTextEditor::MainWindow *mainWindow); /** * Virtual destructor. */ ~KateKonsolePluginView() override; void readConfig(); private: KateKonsolePlugin *m_plugin; KateConsole *m_console; }; /** * KateConsole * This class is used for the internal terminal emulator * It uses internally the konsole part, thx to konsole devs :) */ class KateConsole : public QWidget, public KXMLGUIClient { Q_OBJECT public: /** * construct us * @param mw main window * @param parent toolview */ KateConsole(KateKonsolePlugin *plugin, KTextEditor::MainWindow *mw, QWidget *parent); /** * destruct us */ ~KateConsole() override; void readConfig(); /** * cd to dir * @param path given local directory */ void cd(const QString &path); /** * send given text to console * @param text commands for console */ void sendInput(const QString &text); KTextEditor::MainWindow *mainWindow() { return m_mw; } public Q_SLOTS: /** * pipe current document to console */ void slotPipeToConsole(); /** * synchronize the konsole with the current document (cd to the directory) */ void slotSync(KTextEditor::View *view = nullptr); /** * When syncing is done by the user, also show the terminal if it is hidden */ void slotManualSync(); /** * run the current document in the konsole */ void slotRun(); private Q_SLOTS: /** * the konsole exited ;) * handle that, hide the dock */ void slotDestroyed(); /** * construct console if needed */ void loadConsoleIfNeeded(); /** * set or clear focus as appropriate. */ void slotToggleFocus(); /** * Handle that shortcuts are not eaten by console */ void overrideShortcut(QKeyEvent *event, bool &override); protected: /** * the konsole get shown * @param ev show event */ void showEvent(QShowEvent *ev) override; private: /** * console part */ KParts::ReadOnlyPart *m_part; /** * main window of this console */ KTextEditor::MainWindow *m_mw; /** * toolview for this console */ QWidget *m_toolView; KateKonsolePlugin *m_plugin; QString m_currentPath; }; class KateKonsoleConfigPage : public KTextEditor::ConfigPage { Q_OBJECT public: explicit KateKonsoleConfigPage(QWidget *parent = nullptr, KateKonsolePlugin *plugin = nullptr); ~KateKonsoleConfigPage() override { } QString name() const override; QString fullName() const override; QIcon icon() const override; void apply() override; void reset() override; void defaults() override { } private: class QCheckBox *cbAutoSyncronize; class QCheckBox *cbRemoveExtension; class QLineEdit *lePrefix; class QCheckBox *cbSetEditor; KateKonsolePlugin *mPlugin; private Q_SLOTS: /** * Enable the warning dialog for the next "Run in terminal" */ void slotEnableRunWarning(); }; #endif // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/lspclient/lspclientplugin.cpp b/addons/lspclient/lspclientplugin.cpp index 06d45c9f4..a662022ee 100644 --- a/addons/lspclient/lspclientplugin.cpp +++ b/addons/lspclient/lspclientplugin.cpp @@ -1,135 +1,135 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "lspclientplugin.h" -#include "lspclientpluginview.h" #include "lspclientconfigpage.h" +#include "lspclientpluginview.h" #include "lspclient_debug.h" #include #include #include #include #include static const QString CONFIG_LSPCLIENT {QStringLiteral("lspclient")}; static const QString CONFIG_SYMBOL_DETAILS {QStringLiteral("SymbolDetails")}; static const QString CONFIG_SYMBOL_TREE {QStringLiteral("SymbolTree")}; static const QString CONFIG_SYMBOL_EXPAND {QStringLiteral("SymbolExpand")}; static const QString CONFIG_SYMBOL_SORT {QStringLiteral("SymbolSort")}; static const QString CONFIG_COMPLETION_DOC {QStringLiteral("CompletionDocumentation")}; static const QString CONFIG_REFERENCES_DECLARATION {QStringLiteral("ReferencesDeclaration")}; static const QString CONFIG_AUTO_HOVER {QStringLiteral("AutoHover")}; static const QString CONFIG_TYPE_FORMATTING {QStringLiteral("TypeFormatting")}; static const QString CONFIG_INCREMENTAL_SYNC {QStringLiteral("IncrementalSync")}; static const QString CONFIG_DIAGNOSTICS {QStringLiteral("Diagnostics")}; static const QString CONFIG_DIAGNOSTICS_HIGHLIGHT {QStringLiteral("DiagnosticsHighlight")}; static const QString CONFIG_DIAGNOSTICS_MARK {QStringLiteral("DiagnosticsMark")}; static const QString CONFIG_SERVER_CONFIG {QStringLiteral("ServerConfiguration")}; K_PLUGIN_FACTORY_WITH_JSON(LSPClientPluginFactory, "lspclientplugin.json", registerPlugin();) LSPClientPlugin::LSPClientPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { /** * handle plugin verbosity * the m_debugMode will be used to e.g. set debug level for started clangd, too */ m_debugMode = (qgetenv("LSPCLIENT_DEBUG") == QByteArray("1")); if (!m_debugMode) { QLoggingCategory::setFilterRules(QStringLiteral("katelspclientplugin.debug=false\nkatelspclientplugin.info=false")); } else { QLoggingCategory::setFilterRules(QStringLiteral("katelspclientplugin.debug=true\nkatelspclientplugin.info=true")); } readConfig(); } LSPClientPlugin::~LSPClientPlugin() { } QObject *LSPClientPlugin::createView(KTextEditor::MainWindow *mainWindow) { return LSPClientPluginView::new_(this, mainWindow); } int LSPClientPlugin::configPages() const { return 1; } KTextEditor::ConfigPage *LSPClientPlugin::configPage(int number, QWidget *parent) { if (number != 0) { return nullptr; } return new LSPClientConfigPage(parent, this); } void LSPClientPlugin::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), CONFIG_LSPCLIENT); m_symbolDetails = config.readEntry(CONFIG_SYMBOL_DETAILS, false); m_symbolTree = config.readEntry(CONFIG_SYMBOL_TREE, true); m_symbolExpand = config.readEntry(CONFIG_SYMBOL_EXPAND, true); m_symbolSort = config.readEntry(CONFIG_SYMBOL_SORT, false); m_complDoc = config.readEntry(CONFIG_COMPLETION_DOC, true); m_refDeclaration = config.readEntry(CONFIG_REFERENCES_DECLARATION, true); m_autoHover = config.readEntry(CONFIG_AUTO_HOVER, true); m_onTypeFormatting = config.readEntry(CONFIG_TYPE_FORMATTING, false); m_incrementalSync = config.readEntry(CONFIG_INCREMENTAL_SYNC, false); m_diagnostics = config.readEntry(CONFIG_DIAGNOSTICS, true); m_diagnosticsHighlight = config.readEntry(CONFIG_DIAGNOSTICS_HIGHLIGHT, true); m_diagnosticsMark = config.readEntry(CONFIG_DIAGNOSTICS_MARK, true); m_configPath = config.readEntry(CONFIG_SERVER_CONFIG, QUrl()); emit update(); } void LSPClientPlugin::writeConfig() const { KConfigGroup config(KSharedConfig::openConfig(), CONFIG_LSPCLIENT); config.writeEntry(CONFIG_SYMBOL_DETAILS, m_symbolDetails); config.writeEntry(CONFIG_SYMBOL_TREE, m_symbolTree); config.writeEntry(CONFIG_SYMBOL_EXPAND, m_symbolExpand); config.writeEntry(CONFIG_SYMBOL_SORT, m_symbolSort); config.writeEntry(CONFIG_COMPLETION_DOC, m_complDoc); config.writeEntry(CONFIG_REFERENCES_DECLARATION, m_refDeclaration); config.writeEntry(CONFIG_AUTO_HOVER, m_autoHover); config.writeEntry(CONFIG_TYPE_FORMATTING, m_onTypeFormatting); config.writeEntry(CONFIG_INCREMENTAL_SYNC, m_incrementalSync); config.writeEntry(CONFIG_DIAGNOSTICS, m_diagnostics); config.writeEntry(CONFIG_DIAGNOSTICS_HIGHLIGHT, m_diagnosticsHighlight); config.writeEntry(CONFIG_DIAGNOSTICS_MARK, m_diagnosticsMark); config.writeEntry(CONFIG_SERVER_CONFIG, m_configPath); emit update(); } #include "lspclientplugin.moc" diff --git a/addons/lspclient/lspclientplugin.h b/addons/lspclient/lspclientplugin.h index b78bac8c7..d9cadff4c 100644 --- a/addons/lspclient/lspclientplugin.h +++ b/addons/lspclient/lspclientplugin.h @@ -1,74 +1,74 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef LSPCLIENTPLUGIN_H #define LSPCLIENTPLUGIN_H +#include #include #include -#include #include class LSPClientPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit LSPClientPlugin(QObject *parent = nullptr, const QList & = QList()); ~LSPClientPlugin() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; int configPages() const override; KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; void readConfig(); void writeConfig() const; // settings bool m_symbolDetails; bool m_symbolExpand; bool m_symbolTree; bool m_symbolSort; bool m_complDoc; bool m_refDeclaration; bool m_diagnostics; bool m_diagnosticsHighlight; bool m_diagnosticsMark; bool m_autoHover; bool m_onTypeFormatting; bool m_incrementalSync; QUrl m_configPath; // debug mode? bool m_debugMode = false; private: Q_SIGNALS: // signal settings update void update() const; }; #endif diff --git a/addons/lspclient/lspclientpluginview.cpp b/addons/lspclient/lspclientpluginview.cpp index b17ca66b7..29425ea10 100644 --- a/addons/lspclient/lspclientpluginview.cpp +++ b/addons/lspclient/lspclientpluginview.cpp @@ -1,1634 +1,1634 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "lspclientpluginview.h" -#include "lspclientsymbolview.h" -#include "lspclientplugin.h" -#include "lspclientservermanager.h" #include "lspclientcompletion.h" #include "lspclienthover.h" +#include "lspclientplugin.h" +#include "lspclientservermanager.h" +#include "lspclientsymbolview.h" #include "lspclient_debug.h" +#include #include #include #include #include #include -#include #include #include #include -#include #include #include +#include #include +#include #include #include #include -#include -#include -#include #include -#include -#include +#include +#include +#include #include #include -#include +#include +#include #include +#include #include -#include -#include -#include +#include +#include #include namespace RangeData { enum { // preserve UserRole for generic use where needed FileUrlRole = Qt::UserRole + 1, RangeRole, KindRole, }; class KindEnum { public: enum _kind { Text = (int)LSPDocumentHighlightKind::Text, Read = (int)LSPDocumentHighlightKind::Read, Write = (int)LSPDocumentHighlightKind::Write, Error = 10 + (int)LSPDiagnosticSeverity::Error, Warning = 10 + (int)LSPDiagnosticSeverity::Warning, Information = 10 + (int)LSPDiagnosticSeverity::Information, Hint = 10 + (int)LSPDiagnosticSeverity::Hint, Related }; KindEnum(int v) { m_value = (_kind)v; } KindEnum(LSPDocumentHighlightKind hl) : KindEnum((_kind)(hl)) { } KindEnum(LSPDiagnosticSeverity sev) : KindEnum(_kind(10 + (int)sev)) { } operator _kind() { return m_value; } private: _kind m_value; }; static constexpr KTextEditor::MarkInterface::MarkTypes markType = KTextEditor::MarkInterface::markType31; static constexpr KTextEditor::MarkInterface::MarkTypes markTypeDiagError = KTextEditor::MarkInterface::Error; static constexpr KTextEditor::MarkInterface::MarkTypes markTypeDiagWarning = KTextEditor::MarkInterface::Warning; static constexpr KTextEditor::MarkInterface::MarkTypes markTypeDiagOther = KTextEditor::MarkInterface::markType30; static constexpr KTextEditor::MarkInterface::MarkTypes markTypeDiagAll = KTextEditor::MarkInterface::MarkTypes(markTypeDiagError | markTypeDiagWarning | markTypeDiagOther); } static QIcon diagnosticsIcon(LSPDiagnosticSeverity severity) { // clang-format off #define RETURN_CACHED_ICON(name) \ { \ static QIcon icon(QIcon::fromTheme(QStringLiteral(name))); \ return icon; \ } // clang-format on switch (severity) { case LSPDiagnosticSeverity::Error: RETURN_CACHED_ICON("dialog-error") case LSPDiagnosticSeverity::Warning: RETURN_CACHED_ICON("dialog-warning") case LSPDiagnosticSeverity::Information: case LSPDiagnosticSeverity::Hint: RETURN_CACHED_ICON("dialog-information") default: break; } return QIcon(); } static QIcon codeActionIcon() { static QIcon icon(QIcon::fromTheme(QStringLiteral("insert-text"))); return icon; } KTextEditor::Document *findDocument(KTextEditor::MainWindow *mainWindow, const QUrl &url) { auto views = mainWindow->views(); for (const auto v : views) { auto doc = v->document(); if (doc && doc->url() == url) return doc; } return nullptr; } // helper to read lines from unopened documents // lightweight and does not require additional symbols class FileLineReader { QFile file; int lastLineNo = -1; QString lastLine; public: FileLineReader(const QUrl &url) : file(url.path()) { file.open(QIODevice::ReadOnly); } // called with non-descending lineno QString line(int lineno) { if (lineno == lastLineNo) { return lastLine; } while (file.isOpen() && !file.atEnd()) { auto line = file.readLine(); if (++lastLineNo == lineno) { QTextCodec::ConverterState state; QTextCodec *codec = QTextCodec::codecForName("UTF-8"); QString text = codec->toUnicode(line.constData(), line.size(), &state); if (state.invalidChars > 0) { text = QString::fromLatin1(line); } while (text.size() && text.at(text.size() - 1).isSpace()) text.chop(1); lastLine = text; return text; } } return QString(); } }; class LSPClientActionView : public QObject { Q_OBJECT typedef LSPClientActionView self_type; LSPClientPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; KXMLGUIClient *m_client; QSharedPointer m_serverManager; QScopedPointer m_viewTracker; QScopedPointer m_completion; QScopedPointer m_hover; QScopedPointer m_symbolView; QPointer m_findDef; QPointer m_findDecl; QPointer m_findRef; QPointer m_triggerHighlight; QPointer m_triggerHover; QPointer m_triggerFormat; QPointer m_triggerRename; QPointer m_complDocOn; QPointer m_refDeclaration; QPointer m_autoHover; QPointer m_onTypeFormatting; QPointer m_incrementalSync; QPointer m_diagnostics; QPointer m_diagnosticsHighlight; QPointer m_diagnosticsMark; QPointer m_diagnosticsSwitch; QPointer m_diagnosticsCloseNon; QPointer m_restartServer; QPointer m_restartAll; // toolview QScopedPointer m_toolView; QPointer m_tabWidget; // applied search ranges typedef QMultiHash RangeCollection; RangeCollection m_ranges; // applied marks typedef QSet DocumentCollection; DocumentCollection m_marks; // modelis either owned by tree added to tabwidget or owned here QScopedPointer m_ownedModel; // in either case, the model that directs applying marks/ranges QPointer m_markModel; // goto definition and declaration jump list is more a menu than a // search result, so let's not keep adding new tabs for those // previous tree for definition result QPointer m_defTree; // ... and for declaration QPointer m_declTree; // diagnostics tab QPointer m_diagnosticsTree; // tree widget is either owned here or by tab QScopedPointer m_diagnosticsTreeOwn; QScopedPointer m_diagnosticsModel; // diagnostics ranges RangeCollection m_diagnosticsRanges; // and marks DocumentCollection m_diagnosticsMarks; // views on which completions have been registered QSet m_completionViews; // views on which hovers have been registered QSet m_hoverViews; // outstanding request LSPClientServer::RequestHandle m_handle; // timeout on request bool m_req_timeout = false; // accept incoming applyEdit bool m_accept_edit = false; // characters to trigger format request QVector m_onTypeFormattingTriggers; KActionCollection *actionCollection() const { return m_client->actionCollection(); } public: LSPClientActionView(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, KXMLGUIClient *client, QSharedPointer serverManager) : QObject(mainWin) , m_plugin(plugin) , m_mainWindow(mainWin) , m_client(client) , m_serverManager(std::move(serverManager)) , m_completion(LSPClientCompletion::new_(m_serverManager)) , m_hover(LSPClientHover::new_(m_serverManager)) , m_symbolView(LSPClientSymbolView::new_(plugin, mainWin, m_serverManager)) { connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::updateState); connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &self_type::handleEsc); connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, &self_type::updateState); m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, &self_type::goToDefinition); m_findDef->setText(i18n("Go to Definition")); m_findDecl = actionCollection()->addAction(QStringLiteral("lspclient_find_declaration"), this, &self_type::goToDeclaration); m_findDecl->setText(i18n("Go to Declaration")); m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, &self_type::findReferences); m_findRef->setText(i18n("Find References")); m_triggerHighlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), this, &self_type::highlight); m_triggerHighlight->setText(i18n("Highlight")); // perhaps hover suggests to do so on mouse-over, // but let's just use a (convenient) action/shortcut for it m_triggerHover = actionCollection()->addAction(QStringLiteral("lspclient_hover"), this, &self_type::hover); m_triggerHover->setText(i18n("Hover")); m_triggerFormat = actionCollection()->addAction(QStringLiteral("lspclient_format"), this, &self_type::format); m_triggerFormat->setText(i18n("Format")); m_triggerRename = actionCollection()->addAction(QStringLiteral("lspclient_rename"), this, &self_type::rename); m_triggerRename->setText(i18n("Rename")); // general options m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), this, &self_type::displayOptionChanged); m_complDocOn->setText(i18n("Show selected completion documentation")); m_complDocOn->setCheckable(true); m_refDeclaration = actionCollection()->addAction(QStringLiteral("lspclient_references_declaration"), this, &self_type::displayOptionChanged); m_refDeclaration->setText(i18n("Include declaration in references")); m_refDeclaration->setCheckable(true); m_autoHover = actionCollection()->addAction(QStringLiteral("lspclient_auto_hover"), this, &self_type::displayOptionChanged); m_autoHover->setText(i18n("Show hover information")); m_autoHover->setCheckable(true); m_onTypeFormatting = actionCollection()->addAction(QStringLiteral("lspclient_type_formatting"), this, &self_type::displayOptionChanged); m_onTypeFormatting->setText(i18n("Format on typing")); m_onTypeFormatting->setCheckable(true); m_incrementalSync = actionCollection()->addAction(QStringLiteral("lspclient_incremental_sync"), this, &self_type::displayOptionChanged); m_incrementalSync->setText(i18n("Incremental document synchronization")); m_incrementalSync->setCheckable(true); // diagnostics m_diagnostics = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics"), this, &self_type::displayOptionChanged); m_diagnostics->setText(i18n("Show diagnostics notifications")); m_diagnostics->setCheckable(true); m_diagnosticsHighlight = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_highlight"), this, &self_type::displayOptionChanged); m_diagnosticsHighlight->setText(i18n("Show diagnostics highlights")); m_diagnosticsHighlight->setCheckable(true); m_diagnosticsMark = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_mark"), this, &self_type::displayOptionChanged); m_diagnosticsMark->setText(i18n("Show diagnostics marks")); m_diagnosticsMark->setCheckable(true); m_diagnosticsSwitch = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_switch"), this, &self_type::switchToDiagnostics); m_diagnosticsSwitch->setText(i18n("Switch to diagnostics tab")); m_diagnosticsCloseNon = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_close_non"), this, &self_type::closeNonDiagnostics); m_diagnosticsCloseNon->setText(i18n("Close all non-diagnostics tabs")); // server control m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), this, &self_type::restartCurrent); m_restartServer->setText(i18n("Restart LSP Server")); m_restartAll = actionCollection()->addAction(QStringLiteral("lspclient_restart_all"), this, &self_type::restartAll); m_restartAll->setText(i18n("Restart All LSP Servers")); // popup menu auto menu = new KActionMenu(i18n("LSP Client"), this); actionCollection()->addAction(QStringLiteral("popup_lspclient"), menu); menu->addAction(m_findDef); menu->addAction(m_findDecl); menu->addAction(m_findRef); menu->addAction(m_triggerHighlight); menu->addAction(m_triggerHover); menu->addAction(m_triggerFormat); menu->addAction(m_triggerRename); menu->addSeparator(); menu->addAction(m_complDocOn); menu->addAction(m_refDeclaration); menu->addAction(m_autoHover); menu->addAction(m_onTypeFormatting); menu->addAction(m_incrementalSync); menu->addSeparator(); menu->addAction(m_diagnostics); menu->addAction(m_diagnosticsHighlight); menu->addAction(m_diagnosticsMark); menu->addAction(m_diagnosticsSwitch); menu->addAction(m_diagnosticsCloseNon); menu->addSeparator(); menu->addAction(m_restartServer); menu->addAction(m_restartAll); // sync with plugin settings if updated connect(m_plugin, &LSPClientPlugin::update, this, &self_type::configUpdated); // toolview m_toolView.reset(mainWin->createToolView(plugin, QStringLiteral("kate_lspclient"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), i18n("LSP Client"))); m_tabWidget = new QTabWidget(m_toolView.data()); m_toolView->layout()->addWidget(m_tabWidget); m_tabWidget->setFocusPolicy(Qt::NoFocus); m_tabWidget->setTabsClosable(true); KAcceleratorManager::setNoAccel(m_tabWidget); connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested); // diagnostics tab m_diagnosticsTree = new QTreeView(); m_diagnosticsTree->setAlternatingRowColors(true); m_diagnosticsTreeOwn.reset(m_diagnosticsTree); m_diagnosticsModel.reset(new QStandardItemModel()); m_diagnosticsModel->setColumnCount(1); m_diagnosticsTree->setModel(m_diagnosticsModel.data()); configureTreeView(m_diagnosticsTree); connect(m_diagnosticsTree, &QTreeView::clicked, this, &self_type::goToItemLocation); connect(m_diagnosticsTree, &QTreeView::doubleClicked, this, &self_type::triggerCodeAction); // track position in view to sync diagnostics list m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 0, 500)); connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState); configUpdated(); updateState(); } ~LSPClientActionView() override { // unregister all code-completion providers, else we might crash for (auto view : qAsConst(m_completionViews)) { qobject_cast(view)->unregisterCompletionModel(m_completion.data()); } // unregister all text-hint providers, else we might crash for (auto view : qAsConst(m_hoverViews)) { qobject_cast(view)->unregisterTextHintProvider(m_hover.data()); } clearAllLocationMarks(); clearAllDiagnosticsMarks(); } void configureTreeView(QTreeView *treeView) { treeView->setHeaderHidden(true); treeView->setFocusPolicy(Qt::NoFocus); treeView->setLayoutDirection(Qt::LeftToRight); treeView->setSortingEnabled(false); treeView->setEditTriggers(QAbstractItemView::NoEditTriggers); } void displayOptionChanged() { m_diagnosticsHighlight->setEnabled(m_diagnostics->isChecked()); m_diagnosticsMark->setEnabled(m_diagnostics->isChecked()); auto index = m_tabWidget->indexOf(m_diagnosticsTree); // setTabEnabled may still show it ... so let's be more forceful if (m_diagnostics->isChecked() && m_diagnosticsTreeOwn) { m_diagnosticsTreeOwn.take(); m_tabWidget->insertTab(0, m_diagnosticsTree, i18nc("@title:tab", "Diagnostics")); } else if (!m_diagnostics->isChecked() && !m_diagnosticsTreeOwn) { m_diagnosticsTreeOwn.reset(m_diagnosticsTree); m_tabWidget->removeTab(index); } m_diagnosticsSwitch->setEnabled(m_diagnostics->isChecked()); m_serverManager->setIncrementalSync(m_incrementalSync->isChecked()); updateState(); } void configUpdated() { if (m_complDocOn) m_complDocOn->setChecked(m_plugin->m_complDoc); if (m_refDeclaration) m_refDeclaration->setChecked(m_plugin->m_refDeclaration); if (m_autoHover) m_autoHover->setChecked(m_plugin->m_autoHover); if (m_onTypeFormatting) m_onTypeFormatting->setChecked(m_plugin->m_onTypeFormatting); if (m_incrementalSync) m_incrementalSync->setChecked(m_plugin->m_incrementalSync); if (m_diagnostics) m_diagnostics->setChecked(m_plugin->m_diagnostics); if (m_diagnosticsHighlight) m_diagnosticsHighlight->setChecked(m_plugin->m_diagnosticsHighlight); if (m_diagnosticsMark) m_diagnosticsMark->setChecked(m_plugin->m_diagnosticsMark); displayOptionChanged(); } void restartCurrent() { KTextEditor::View *activeView = m_mainWindow->activeView(); auto server = m_serverManager->findServer(activeView); if (server) m_serverManager->restart(server.data()); } void restartAll() { m_serverManager->restart(nullptr); } static void clearMarks(KTextEditor::Document *doc, RangeCollection &ranges, DocumentCollection &docs, uint markType) { KTextEditor::MarkInterface *iface = docs.contains(doc) ? qobject_cast(doc) : nullptr; if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if (i.value()->type & markType) { iface->removeMark(i.value()->line, markType); } } docs.remove(doc); } for (auto it = ranges.find(doc); it != ranges.end() && it.key() == doc;) { delete it.value(); it = ranges.erase(it); } } static void clearMarks(RangeCollection &ranges, DocumentCollection &docs, uint markType) { while (!ranges.empty()) { clearMarks(ranges.begin().key(), ranges, docs, markType); } } Q_SLOT void clearAllMarks(KTextEditor::Document *doc) { clearMarks(doc, m_ranges, m_marks, RangeData::markType); clearMarks(doc, m_diagnosticsRanges, m_diagnosticsMarks, RangeData::markTypeDiagAll); } void clearAllLocationMarks() { clearMarks(m_ranges, m_marks, RangeData::markType); // no longer add any again m_ownedModel.reset(); m_markModel.clear(); } void clearAllDiagnosticsMarks() { clearMarks(m_diagnosticsRanges, m_diagnosticsMarks, RangeData::markTypeDiagAll); } void addMarks(KTextEditor::Document *doc, QStandardItem *item, RangeCollection *ranges, DocumentCollection *docs) { Q_ASSERT(item); KTextEditor::MovingInterface *miface = qobject_cast(doc); KTextEditor::MarkInterface *iface = qobject_cast(doc); KTextEditor::View *activeView = m_mainWindow->activeView(); KTextEditor::ConfigInterface *ciface = qobject_cast(activeView); if (!miface || !iface) return; auto url = item->data(RangeData::FileUrlRole).toUrl(); if (url != doc->url()) return; KTextEditor::Range range = item->data(RangeData::RangeRole).value(); auto line = range.start().line(); RangeData::KindEnum kind = (RangeData::KindEnum)item->data(RangeData::KindRole).toInt(); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); bool enabled = m_diagnostics && m_diagnostics->isChecked() && m_diagnosticsHighlight && m_diagnosticsHighlight->isChecked(); KTextEditor::MarkInterface::MarkTypes markType = RangeData::markType; switch (kind) { case RangeData::KindEnum::Text: { // well, it's a bit like searching for something, so re-use that color QColor rangeColor = Qt::yellow; if (ciface) { rangeColor = ciface->configValue(QStringLiteral("search-highlight-color")).value(); } attr->setBackground(rangeColor); enabled = true; break; } // FIXME are there any symbolic/configurable ways to pick these colors? case RangeData::KindEnum::Read: attr->setBackground(Qt::green); enabled = true; break; case RangeData::KindEnum::Write: attr->setBackground(Qt::red); enabled = true; break; // use underlining for diagnostics to avoid lots of fancy flickering case RangeData::KindEnum::Error: markType = RangeData::markTypeDiagError; attr->setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); attr->setUnderlineColor(Qt::red); break; case RangeData::KindEnum::Warning: markType = RangeData::markTypeDiagWarning; attr->setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); attr->setUnderlineColor(QColor(255, 128, 0)); break; case RangeData::KindEnum::Information: case RangeData::KindEnum::Hint: case RangeData::KindEnum::Related: markType = RangeData::markTypeDiagOther; attr->setUnderlineStyle(QTextCharFormat::DashUnderline); attr->setUnderlineColor(Qt::blue); break; } if (activeView) { attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color()); } // highlight the range if (enabled && ranges) { KTextEditor::MovingRange *mr = miface->newMovingRange(range); mr->setAttribute(attr); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); ranges->insert(doc, mr); } // add match mark for range const int ps = 32; bool handleClick = true; enabled = m_diagnostics && m_diagnostics->isChecked() && m_diagnosticsMark && m_diagnosticsMark->isChecked(); switch (markType) { case RangeData::markType: iface->setMarkDescription(markType, i18n("RangeHighLight")); iface->setMarkPixmap(markType, QIcon().pixmap(0, 0)); handleClick = false; enabled = true; break; case RangeData::markTypeDiagError: iface->setMarkDescription(markType, i18n("Error")); iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Error).pixmap(ps, ps)); break; case RangeData::markTypeDiagWarning: iface->setMarkDescription(markType, i18n("Warning")); iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Warning).pixmap(ps, ps)); break; case RangeData::markTypeDiagOther: iface->setMarkDescription(markType, i18n("Information")); iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Information).pixmap(ps, ps)); break; default: Q_ASSERT(false); break; } if (enabled && docs) { iface->addMark(line, markType); docs->insert(doc); } // ensure runtime match connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearAllMarks(KTextEditor::Document *)), Qt::UniqueConnection); connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearAllMarks(KTextEditor::Document *)), Qt::UniqueConnection); if (handleClick) { connect(doc, SIGNAL(markClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), this, SLOT(onMarkClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), Qt::UniqueConnection); } } void addMarksRec(KTextEditor::Document *doc, QStandardItem *item, RangeCollection *ranges, DocumentCollection *docs) { Q_ASSERT(item); addMarks(doc, item, ranges, docs); for (int i = 0; i < item->rowCount(); ++i) { addMarksRec(doc, item->child(i), ranges, docs); } } void addMarks(KTextEditor::Document *doc, QStandardItemModel *treeModel, RangeCollection &ranges, DocumentCollection &docs) { // check if already added auto oranges = ranges.contains(doc) ? nullptr : &ranges; auto odocs = docs.contains(doc) ? nullptr : &docs; if (!oranges && !odocs) return; Q_ASSERT(treeModel); addMarksRec(doc, treeModel->invisibleRootItem(), oranges, odocs); } void goToDocumentLocation(const QUrl &uri, int line, int column) { KTextEditor::View *activeView = m_mainWindow->activeView(); if (!activeView || uri.isEmpty() || line < 0 || column < 0) return; KTextEditor::Document *document = activeView->document(); KTextEditor::Cursor cdef(line, column); if (document && uri == document->url()) { activeView->setCursorPosition(cdef); } else { KTextEditor::View *view = m_mainWindow->openUrl(uri); if (view) { view->setCursorPosition(cdef); } } } void goToItemLocation(const QModelIndex &index) { auto url = index.data(RangeData::FileUrlRole).toUrl(); auto start = index.data(RangeData::RangeRole).value().start(); goToDocumentLocation(url, start.line(), start.column()); } // custom item subclass that captures additional attributes; // a bit more convenient than the variant/role way struct DiagnosticItem : public QStandardItem { LSPDiagnostic m_diagnostic; LSPCodeAction m_codeAction; QSharedPointer m_snapshot; DiagnosticItem(const LSPDiagnostic &d) : m_diagnostic(d) { } DiagnosticItem(const LSPCodeAction &c, QSharedPointer s) : m_codeAction(c) , m_snapshot(std::move(s)) { m_diagnostic.range = LSPRange::invalid(); } bool isCodeAction() { return !m_diagnostic.range.isValid() && m_codeAction.title.size(); } }; // double click on: // diagnostic item -> request and add actions (below item) // code action -> perform action (literal edit and/or execute command) // (execution of command may lead to an applyEdit request from server) void triggerCodeAction(const QModelIndex &index) { KTextEditor::View *activeView = m_mainWindow->activeView(); QPointer document = activeView->document(); auto server = m_serverManager->findServer(activeView); auto it = dynamic_cast(m_diagnosticsModel->itemFromIndex(index)); if (!server || !document || !it) return; // click on an action ? if (it->isCodeAction()) { auto &action = it->m_codeAction; // apply edit before command applyWorkspaceEdit(action.edit, it->m_snapshot.data()); auto &command = action.command; if (command.command.size()) { // accept edit requests that may be sent to execute command m_accept_edit = true; // but only for a short time QTimer::singleShot(2000, this, [this] { m_accept_edit = false; }); server->executeCommand(command.command, command.arguments); } // diagnostics are likely updated soon, but might be clicked again in meantime // so clear once executed, so not executed again action.edit.changes.clear(); action.command.command.clear(); return; } // only engage action if // * active document matches diagnostic document // * if really clicked a diagnostic item // (which is the case as it != nullptr and not a code action) // * if no code action invoked and added already // (note; related items are also children) auto url = it->data(RangeData::FileUrlRole).toUrl(); if (url != document->url() || it->data(Qt::UserRole).toBool()) return; // store some things to find item safely later on QPersistentModelIndex pindex(index); QSharedPointer snapshot(m_serverManager->snapshot(server.data())); auto h = [this, url, snapshot, pindex](const QList &actions) { if (!pindex.isValid()) return; auto child = m_diagnosticsModel->itemFromIndex(pindex); if (!child) return; // add actions below diagnostic item for (const auto &action : actions) { auto item = new DiagnosticItem(action, snapshot); child->appendRow(item); auto text = action.kind.size() ? QStringLiteral("[%1] %2").arg(action.kind).arg(action.title) : action.title; item->setData(text, Qt::DisplayRole); item->setData(codeActionIcon(), Qt::DecorationRole); } m_diagnosticsTree->setExpanded(child->index(), true); // mark actions added child->setData(true, Qt::UserRole); }; auto range = activeView->selectionRange(); if (!range.isValid()) { range = document->documentRange(); } server->documentCodeAction(url, range, {}, {it->m_diagnostic}, this, h); } void tabCloseRequested(int index) { auto widget = m_tabWidget->widget(index); if (widget != m_diagnosticsTree) { if (m_markModel && widget == m_markModel->parent()) { clearAllLocationMarks(); } delete widget; } } void switchToDiagnostics() { m_tabWidget->setCurrentWidget(m_diagnosticsTree); m_mainWindow->showToolView(m_toolView.data()); } void closeNonDiagnostics() { for (int i = 0; i < m_tabWidget->count();) { if (m_tabWidget->widget(i) != m_diagnosticsTree) { tabCloseRequested(i); } else { ++i; } } } // local helper to overcome some differences in LSP types struct RangeItem { QUrl uri; LSPRange range; LSPDocumentHighlightKind kind; }; static bool compareRangeItem(const RangeItem &a, const RangeItem &b) { return (a.uri < b.uri) || ((a.uri == b.uri) && a.range < b.range); } // provide Qt::DisplayRole (text) line lazily; // only find line's text content when so requested // This may then involve opening reading some file, at which time // all items for that file will be resolved in one go. struct LineItem : public QStandardItem { KTextEditor::MainWindow *m_mainWindow; LineItem(KTextEditor::MainWindow *mainWindow) : m_mainWindow(mainWindow) { } QVariant data(int role = Qt::UserRole + 1) const override { auto rootItem = this->parent(); if (role != Qt::DisplayRole || !rootItem) { return QStandardItem::data(role); } auto line = data(Qt::UserRole); // either of these mean we tried to obtain line already if (line.isValid() || rootItem->data(RangeData::KindRole).toBool()) { return QStandardItem::data(role).toString().append(line.toString()); } KTextEditor::Document *doc = nullptr; QScopedPointer fr; for (int i = 0; i < rootItem->rowCount(); i++) { auto child = rootItem->child(i); if (i == 0) { auto url = child->data(RangeData::FileUrlRole).toUrl(); doc = findDocument(m_mainWindow, url); if (!doc) { fr.reset(new FileLineReader(url)); } } auto lineno = child->data(RangeData::RangeRole).value().start().line(); auto line = doc ? doc->line(lineno) : fr->line(lineno); child->setData(line, Qt::UserRole); } // mark as processed rootItem->setData(RangeData::KindRole, true); // should work ok return data(role); } }; LSPRange transformRange(const QUrl &url, const LSPClientRevisionSnapshot &snapshot, const LSPRange &range) { KTextEditor::MovingInterface *miface; qint64 revision; auto result = range; snapshot.find(url, miface, revision); if (miface) { miface->transformRange(result, KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::AllowEmpty, revision); } return result; } void fillItemRoles(QStandardItem *item, const QUrl &url, const LSPRange _range, RangeData::KindEnum kind, const LSPClientRevisionSnapshot *snapshot = nullptr) { auto range = snapshot ? transformRange(url, *snapshot, _range) : _range; item->setData(QVariant(url), RangeData::FileUrlRole); QVariant vrange; vrange.setValue(range); item->setData(vrange, RangeData::RangeRole); item->setData((int)kind, RangeData::KindRole); } void makeTree(const QVector &locations, const LSPClientRevisionSnapshot *snapshot) { // group by url, assuming input is suitably sorted that way auto treeModel = new QStandardItemModel(); treeModel->setColumnCount(1); QUrl lastUrl; QStandardItem *parent = nullptr; for (const auto &loc : locations) { if (loc.uri != lastUrl) { if (parent) { parent->setText(QStringLiteral("%1: %2").arg(lastUrl.path()).arg(parent->rowCount())); } lastUrl = loc.uri; parent = new QStandardItem(); treeModel->appendRow(parent); } auto item = new LineItem(m_mainWindow); parent->appendRow(item); // add partial display data; line will be added by item later on item->setText(i18n("Line: %1: ", loc.range.start().line() + 1)); fillItemRoles(item, loc.uri, loc.range, loc.kind, snapshot); } if (parent) parent->setText(QStringLiteral("%1: %2").arg(lastUrl.path()).arg(parent->rowCount())); // plain heuristic; mark for auto-expand all when safe and/or useful to do so if (treeModel->rowCount() <= 2 || locations.size() <= 20) { treeModel->invisibleRootItem()->setData(true, RangeData::KindRole); } m_ownedModel.reset(treeModel); m_markModel = treeModel; } void showTree(const QString &title, QPointer *targetTree) { // clean up previous target if any if (targetTree && *targetTree) { int index = m_tabWidget->indexOf(*targetTree); if (index >= 0) tabCloseRequested(index); } // setup view auto treeView = new QTreeView(); configureTreeView(treeView); // transfer model from owned to tree and that in turn to tabwidget auto treeModel = m_ownedModel.take(); treeView->setModel(treeModel); treeModel->setParent(treeView); int index = m_tabWidget->addTab(treeView, title); connect(treeView, &QTreeView::clicked, this, &self_type::goToItemLocation); if (treeModel->invisibleRootItem()->data(RangeData::KindRole).toBool()) { treeView->expandAll(); } // track for later cleanup if (targetTree) *targetTree = treeView; // activate the resulting tab m_tabWidget->setCurrentIndex(index); m_mainWindow->showToolView(m_toolView.data()); } void showMessage(const QString &text, KTextEditor::Message::MessageType level) { KTextEditor::View *view = m_mainWindow->activeView(); if (!view || !view->document()) return; auto kmsg = new KTextEditor::Message(text, level); kmsg->setPosition(KTextEditor::Message::BottomInView); kmsg->setAutoHide(500); kmsg->setView(view); view->document()->postMessage(kmsg); } void handleEsc(QEvent *e) { if (!m_mainWindow) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (!m_ranges.empty()) { clearAllLocationMarks(); } else if (m_toolView->isVisible()) { m_mainWindow->hideToolView(m_toolView.data()); } } } template using LocationRequest = std::function; template void positionRequest(const LocationRequest &req, const Handler &h, QScopedPointer *snapshot = nullptr) { KTextEditor::View *activeView = m_mainWindow->activeView(); auto server = m_serverManager->findServer(activeView); if (!server) return; // track revision if requested if (snapshot) { snapshot->reset(m_serverManager->snapshot(server.data())); } KTextEditor::Cursor cursor = activeView->cursorPosition(); clearAllLocationMarks(); m_req_timeout = false; QTimer::singleShot(1000, this, [this] { m_req_timeout = true; }); m_handle.cancel() = req(*server, activeView->document()->url(), {cursor.line(), cursor.column()}, this, h); } QString currentWord() { KTextEditor::View *activeView = m_mainWindow->activeView(); if (activeView) { KTextEditor::Cursor cursor = activeView->cursorPosition(); return activeView->document()->wordAt(cursor); } else { return QString(); } } // some template and function type trickery here, but at least that buck stops here then ... template >> void processLocations(const QString &title, const typename utils::identity>::type &req, bool onlyshow, const std::function &itemConverter, QPointer *targetTree = nullptr) { // no capture for move only using initializers available (yet), so shared outer type // the additional level of indirection is so it can be 'filled-in' after lambda creation QSharedPointer> s(new QScopedPointer); auto h = [this, title, onlyshow, itemConverter, targetTree, s](const QList &defs) { if (defs.count() == 0) { showMessage(i18n("No results"), KTextEditor::Message::Information); } else { // convert to helper type QVector ranges; ranges.reserve(defs.size()); for (const auto &def : defs) { ranges.push_back(itemConverter(def)); } // ... so we can sort it also std::stable_sort(ranges.begin(), ranges.end(), compareRangeItem); makeTree(ranges, s.data()->data()); // assuming that reply ranges refer to revision when submitted // (not specified anyway in protocol/reply) if (defs.count() > 1 || onlyshow) { showTree(title, targetTree); } // it's not nice to jump to some location if we are too late if (!m_req_timeout && !onlyshow) { // assuming here that the first location is the best one const auto &item = itemConverter(defs.at(0)); const auto &pos = item.range.start(); goToDocumentLocation(item.uri, pos.line(), pos.column()); // forego mark and such if only a single destination if (defs.count() == 1) { clearAllLocationMarks(); } } // update marks updateState(); } }; positionRequest(req, h, s.data()); } static RangeItem locationToRangeItem(const LSPLocation &loc) { return {loc.uri, loc.range, LSPDocumentHighlightKind::Text}; } void goToDefinition() { auto title = i18nc("@title:tab", "Definition: %1", currentWord()); processLocations(title, &LSPClientServer::documentDefinition, false, &self_type::locationToRangeItem, &m_defTree); } void goToDeclaration() { auto title = i18nc("@title:tab", "Declaration: %1", currentWord()); processLocations(title, &LSPClientServer::documentDeclaration, false, &self_type::locationToRangeItem, &m_declTree); } void findReferences() { auto title = i18nc("@title:tab", "References: %1", currentWord()); bool decl = m_refDeclaration->isChecked(); // clang-format off auto req = [decl](LSPClientServer &server, const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentDefinitionReplyHandler &h) { return server.documentReferences(document, pos, decl, context, h); }; // clang-format on processLocations(title, req, true, &self_type::locationToRangeItem); } void highlight() { // determine current url to capture and use later on QUrl url; const KTextEditor::View *viewForRequest(m_mainWindow->activeView()); if (viewForRequest && viewForRequest->document()) { url = viewForRequest->document()->url(); } auto title = i18nc("@title:tab", "Highlight: %1", currentWord()); auto converter = [url](const LSPDocumentHighlight &hl) { return RangeItem {url, hl.range, hl.kind}; }; processLocations(title, &LSPClientServer::documentHighlight, true, converter); } void hover() { // trigger manually the normally automagic hover if (auto activeView = m_mainWindow->activeView()) { m_hover->textHint(activeView, activeView->cursorPosition()); } } void applyEdits(KTextEditor::Document *doc, const LSPClientRevisionSnapshot *snapshot, const QList &edits) { KTextEditor::MovingInterface *miface = qobject_cast(doc); if (!miface) return; // NOTE: // server might be pretty sloppy wrt edits (e.g. python-language-server) // e.g. send one edit for the whole document rather than 'surgical edits' // and that even when requesting format for a limited selection // ... but then we are but a client and do as we are told // all-in-all a low priority feature // all coordinates in edits are wrt original document, // so create moving ranges that will adjust to preceding edits as they are applied QVector ranges; for (const auto &edit : edits) { auto range = snapshot ? transformRange(doc->url(), *snapshot, edit.range) : edit.range; KTextEditor::MovingRange *mr = miface->newMovingRange(range); ranges.append(mr); } // now make one transaction (a.o. for one undo) and apply in sequence { KTextEditor::Document::EditingTransaction transaction(doc); for (int i = 0; i < ranges.length(); ++i) { doc->replaceText(ranges.at(i)->toRange(), edits.at(i).newText); } } qDeleteAll(ranges); } void applyWorkspaceEdit(const LSPWorkspaceEdit &edit, const LSPClientRevisionSnapshot *snapshot) { auto currentView = m_mainWindow->activeView(); for (auto it = edit.changes.begin(); it != edit.changes.end(); ++it) { auto document = findDocument(m_mainWindow, it.key()); if (!document) { KTextEditor::View *view = m_mainWindow->openUrl(it.key()); if (view) { document = view->document(); } } applyEdits(document, snapshot, it.value()); } if (currentView) { m_mainWindow->activateView(currentView->document()); } } void onApplyEdit(const LSPApplyWorkspaceEditParams &edit, const ApplyEditReplyHandler &h, bool &handled) { if (handled) return; handled = true; if (m_accept_edit) { qCInfo(LSPCLIENT) << "applying edit" << edit.label; applyWorkspaceEdit(edit.edit, nullptr); } else { qCInfo(LSPCLIENT) << "ignoring edit"; } h({m_accept_edit, QString()}); } template void checkEditResult(const Collection &c) { if (c.empty()) { showMessage(i18n("No edits"), KTextEditor::Message::Information); } } void delayCancelRequest(LSPClientServer::RequestHandle &&h, int timeout_ms = 4000) { QTimer::singleShot(timeout_ms, this, [h]() mutable { h.cancel(); }); } void format(QChar lastChar = QChar()) { KTextEditor::View *activeView = m_mainWindow->activeView(); QPointer document = activeView->document(); auto server = m_serverManager->findServer(activeView); if (!server || !document) return; int tabSize = 4; bool insertSpaces = true; auto cfgiface = qobject_cast(document); if (cfgiface) { tabSize = cfgiface->configValue(QStringLiteral("tab-width")).toInt(); insertSpaces = cfgiface->configValue(QStringLiteral("replace-tabs")).toBool(); } // sigh, no move initialization capture ... // (again) assuming reply ranges wrt revisions submitted at this time QSharedPointer snapshot(m_serverManager->snapshot(server.data())); auto h = [this, document, snapshot, lastChar](const QList &edits) { if (lastChar.isNull()) { checkEditResult(edits); } if (document) { applyEdits(document, snapshot.data(), edits); } }; auto options = LSPFormattingOptions {tabSize, insertSpaces, QJsonObject()}; auto handle = !lastChar.isNull() ? server->documentOnTypeFormatting(document->url(), activeView->cursorPosition(), lastChar, options, this, h) : (activeView->selection() ? server->documentRangeFormatting(document->url(), activeView->selectionRange(), options, this, h) : server->documentFormatting(document->url(), options, this, h)); delayCancelRequest(std::move(handle)); } void rename() { KTextEditor::View *activeView = m_mainWindow->activeView(); QPointer document = activeView->document(); auto server = m_serverManager->findServer(activeView); if (!server || !document) return; bool ok = false; // results are typically (too) limited // due to server implementation or limited view/scope // so let's add a disclaimer that it's not our fault QString newName = QInputDialog::getText(activeView, i18nc("@title:window", "Rename"), i18nc("@label:textbox", "New name (caution: not all references may be replaced)"), QLineEdit::Normal, QString(), &ok); if (!ok) { return; } QSharedPointer snapshot(m_serverManager->snapshot(server.data())); auto h = [this, snapshot](const LSPWorkspaceEdit &edit) { checkEditResult(edit.changes); applyWorkspaceEdit(edit, snapshot.data()); }; auto handle = server->documentRename(document->url(), activeView->cursorPosition(), newName, this, h); delayCancelRequest(std::move(handle)); } static QStandardItem *getItem(const QStandardItemModel &model, const QUrl &url) { auto l = model.findItems(url.path()); if (l.length()) { return l.at(0); } return nullptr; } // select/scroll to diagnostics item for document and (optionally) line bool syncDiagnostics(KTextEditor::Document *document, int line, bool allowTop, bool doShow) { if (!m_diagnosticsTree) return false; auto hint = QAbstractItemView::PositionAtTop; QStandardItem *targetItem = nullptr; QStandardItem *topItem = getItem(*m_diagnosticsModel, document->url()); if (topItem) { int count = topItem->rowCount(); // let's not run wild on a linear search in a flood of diagnostics // user is already in enough trouble as it is ;-) if (count > 50) count = 0; for (int i = 0; i < count; ++i) { auto item = topItem->child(i); int itemline = item->data(RangeData::RangeRole).value().start().line(); if (line == itemline && m_diagnosticsTree) { targetItem = item; hint = QAbstractItemView::PositionAtCenter; break; } } } if (!targetItem && allowTop) { targetItem = topItem; } if (targetItem) { m_diagnosticsTree->blockSignals(true); m_diagnosticsTree->scrollTo(targetItem->index(), hint); m_diagnosticsTree->setCurrentIndex(targetItem->index()); m_diagnosticsTree->blockSignals(false); if (doShow) { m_tabWidget->setCurrentWidget(m_diagnosticsTree); m_mainWindow->showToolView(m_toolView.data()); } } return targetItem != nullptr; } void onViewState(KTextEditor::View *view, LSPClientViewTracker::State newState) { if (!view || !view->document()) return; // select top item on view change, // but otherwise leave selection unchanged if no match switch (newState) { case LSPClientViewTracker::ViewChanged: syncDiagnostics(view->document(), view->cursorPosition().line(), true, false); break; case LSPClientViewTracker::LineChanged: syncDiagnostics(view->document(), view->cursorPosition().line(), false, false); break; default: // should not happen break; } } Q_SLOT void onMarkClicked(KTextEditor::Document *document, KTextEditor::Mark mark, bool &handled) { // no action if no mark was sprinkled here if (m_diagnosticsMarks.contains(document) && syncDiagnostics(document, mark.line, false, true)) { handled = true; } } void onDiagnostics(const LSPPublishDiagnosticsParams &diagnostics) { if (!m_diagnosticsTree) return; QStandardItemModel *model = m_diagnosticsModel.data(); QStandardItem *topItem = getItem(*m_diagnosticsModel, diagnostics.uri); if (!topItem) { // no need to create an empty one if (diagnostics.diagnostics.empty()) { return; } topItem = new QStandardItem(); model->appendRow(topItem); topItem->setText(diagnostics.uri.path()); } else { topItem->setRowCount(0); } for (const auto &diag : diagnostics.diagnostics) { auto item = new DiagnosticItem(diag); topItem->appendRow(item); QString source; if (diag.source.length()) { source = QStringLiteral("[%1] ").arg(diag.source); } item->setData(diagnosticsIcon(diag.severity), Qt::DecorationRole); item->setText(source + diag.message); fillItemRoles(item, diagnostics.uri, diag.range, diag.severity); const auto &relatedInfo = diag.relatedInformation; for (const auto &related : relatedInfo) { if (related.location.uri.isEmpty()) { continue; } auto relatedItemMessage = new QStandardItem(); fillItemRoles(relatedItemMessage, related.location.uri, related.location.range, RangeData::KindEnum::Related); auto basename = QFileInfo(related.location.uri.path()).fileName(); auto location = QStringLiteral("%1:%2").arg(basename).arg(related.location.range.start().line()); relatedItemMessage->setText(QStringLiteral("[%1] %2").arg(location).arg(related.message)); relatedItemMessage->setData(diagnosticsIcon(LSPDiagnosticSeverity::Information), Qt::DecorationRole); item->appendRow({relatedItemMessage}); m_diagnosticsTree->setExpanded(item->index(), true); } } // TODO perhaps add some custom delegate that only shows 1 line // and only the whole text when item selected ?? m_diagnosticsTree->setExpanded(topItem->index(), true); m_diagnosticsTree->setRowHidden(topItem->row(), QModelIndex(), topItem->rowCount() == 0); m_diagnosticsTree->scrollTo(topItem->index(), QAbstractItemView::PositionAtTop); updateState(); } void onDocumentUrlChanged(KTextEditor::Document *doc) { // url already changed by this time and new url not useufl (void)doc; // note; url also changes when closed // spec says; // if a language has a project system, diagnostics are not cleared by *server* // but in either case (url change or close); remove lingering diagnostics // collect active urls QSet fpaths; for (const auto &view : m_mainWindow->views()) { if (auto doc = view->document()) { fpaths.insert(doc->url().path()); } } // check and clear defunct entries const auto &model = *m_diagnosticsModel; for (int i = 0; i < model.rowCount(); ++i) { auto item = model.item(i); if (item && !fpaths.contains(item->text())) { item->setRowCount(0); if (m_diagnosticsTree) { m_diagnosticsTree->setRowHidden(item->row(), QModelIndex(), true); } } } } void onTextChanged(KTextEditor::Document *doc) { if (m_onTypeFormattingTriggers.empty()) return; KTextEditor::View *activeView = m_mainWindow->activeView(); if (!activeView || activeView->document() != doc) return; // NOTE the intendation mode should probably be set to None, // so as not to experience unpleasant interference auto cursor = activeView->cursorPosition(); QChar lastChar = cursor.column() == 0 ? QChar::fromLatin1('\n') : doc->characterAt({cursor.line(), cursor.column() - 1}); if (m_onTypeFormattingTriggers.contains(lastChar)) { format(lastChar); } } void updateState() { KTextEditor::View *activeView = m_mainWindow->activeView(); auto doc = activeView ? activeView->document() : nullptr; auto server = m_serverManager->findServer(activeView); bool defEnabled = false, declEnabled = false, refEnabled = false; bool hoverEnabled = false, highlightEnabled = false; bool formatEnabled = false; bool renameEnabled = false; if (server) { const auto &caps = server->capabilities(); defEnabled = caps.definitionProvider; // FIXME no real official protocol way to detect, so enable anyway declEnabled = caps.declarationProvider || true; refEnabled = caps.referencesProvider; hoverEnabled = caps.hoverProvider; highlightEnabled = caps.documentHighlightProvider; formatEnabled = caps.documentFormattingProvider || caps.documentRangeFormattingProvider; renameEnabled = caps.renameProvider; connect(server.data(), &LSPClientServer::publishDiagnostics, this, &self_type::onDiagnostics, Qt::UniqueConnection); connect(server.data(), &LSPClientServer::applyEdit, this, &self_type::onApplyEdit, Qt::UniqueConnection); // update format trigger characters const auto &fmt = caps.documentOnTypeFormattingProvider; if (fmt.provider && m_onTypeFormatting->isChecked()) { m_onTypeFormattingTriggers = fmt.triggerCharacters; } else { m_onTypeFormattingTriggers.clear(); } // and monitor for such if (doc) { connect(doc, &KTextEditor::Document::textChanged, this, &self_type::onTextChanged, Qt::UniqueConnection); connect(doc, &KTextEditor::Document::documentUrlChanged, this, &self_type::onDocumentUrlChanged, Qt::UniqueConnection); } } if (m_findDef) m_findDef->setEnabled(defEnabled); if (m_findDecl) m_findDecl->setEnabled(declEnabled); if (m_findRef) m_findRef->setEnabled(refEnabled); if (m_triggerHighlight) m_triggerHighlight->setEnabled(highlightEnabled); if (m_triggerHover) m_triggerHover->setEnabled(hoverEnabled); if (m_triggerFormat) m_triggerFormat->setEnabled(formatEnabled); if (m_triggerRename) m_triggerRename->setEnabled(renameEnabled); if (m_complDocOn) m_complDocOn->setEnabled(server); if (m_restartServer) m_restartServer->setEnabled(server); // update completion with relevant server m_completion->setServer(server); if (m_complDocOn) m_completion->setSelectedDocumentation(m_complDocOn->isChecked()); updateCompletion(activeView, server.data()); // update hover with relevant server m_hover->setServer((m_autoHover && m_autoHover->isChecked()) ? server : nullptr); updateHover(activeView, server.data()); // update marks if applicable if (m_markModel && doc) addMarks(doc, m_markModel, m_ranges, m_marks); if (m_diagnosticsModel && doc) { clearMarks(doc, m_diagnosticsRanges, m_diagnosticsMarks, RangeData::markTypeDiagAll); addMarks(doc, m_diagnosticsModel.data(), m_diagnosticsRanges, m_diagnosticsMarks); } // connect for cleanup stuff if (activeView) connect(activeView, &KTextEditor::View::destroyed, this, &self_type::viewDestroyed, Qt::UniqueConnection); } void viewDestroyed(QObject *view) { m_completionViews.remove(static_cast(view)); m_hoverViews.remove(static_cast(view)); } void updateCompletion(KTextEditor::View *view, LSPClientServer *server) { bool registered = m_completionViews.contains(view); KTextEditor::CodeCompletionInterface *cci = qobject_cast(view); if (!cci) { return; } if (!registered && server && server->capabilities().completionProvider.provider) { qCInfo(LSPCLIENT) << "registering cci"; cci->registerCompletionModel(m_completion.data()); m_completionViews.insert(view); } if (registered && !server) { qCInfo(LSPCLIENT) << "unregistering cci"; cci->unregisterCompletionModel(m_completion.data()); m_completionViews.remove(view); } } void updateHover(KTextEditor::View *view, LSPClientServer *server) { bool registered = m_hoverViews.contains(view); KTextEditor::TextHintInterface *cci = qobject_cast(view); if (!cci) { return; } if (!registered && server && server->capabilities().hoverProvider) { qCInfo(LSPCLIENT) << "registering cci"; cci->registerTextHintProvider(m_hover.data()); m_hoverViews.insert(view); } if (registered && !server) { qCInfo(LSPCLIENT) << "unregistering cci"; cci->unregisterTextHintProvider(m_hover.data()); m_hoverViews.remove(view); } } }; class LSPClientPluginViewImpl : public QObject, public KXMLGUIClient { Q_OBJECT typedef LSPClientPluginViewImpl self_type; KTextEditor::MainWindow *m_mainWindow; QSharedPointer m_serverManager; QScopedPointer m_actionView; public: LSPClientPluginViewImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_mainWindow(mainWin) , m_serverManager(LSPClientServerManager::new_(plugin, mainWin)) , m_actionView(new LSPClientActionView(plugin, mainWin, this, m_serverManager)) { KXMLGUIClient::setComponentName(QStringLiteral("lspclient"), i18n("LSP Client")); setXMLFile(QStringLiteral("ui.rc")); m_mainWindow->guiFactory()->addClient(this); } ~LSPClientPluginViewImpl() override { // minimize/avoid some surprises; // safe construction/destruction by separate (helper) objects; // signals are auto-disconnected when high-level "view" objects are broken down // so it only remains to clean up lowest level here then prior to removal m_actionView.reset(); m_serverManager.reset(); m_mainWindow->guiFactory()->removeClient(this); } }; QObject *LSPClientPluginView::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) { return new LSPClientPluginViewImpl(plugin, mainWin); } #include "lspclientpluginview.moc" diff --git a/addons/lspclient/lspclientprotocol.h b/addons/lspclient/lspclientprotocol.h index a1d071d00..6bf3d7df9 100644 --- a/addons/lspclient/lspclientprotocol.h +++ b/addons/lspclient/lspclientprotocol.h @@ -1,305 +1,305 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef LSPCLIENTPROTOCOL_H #define LSPCLIENTPROTOCOL_H -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include #include #include // Following types roughly follow the types/interfaces as defined in LSP protocol spec // although some deviation may arise where it has been deemed useful // Moreover, to avoid introducing a custom 'optional' type, absence of an optional // part/member is usually signalled by some 'invalid' marker (empty, negative). enum class LSPErrorCode { // Defined by JSON RPC ParseError = -32700, InvalidRequest = -32600, MethodNotFound = -32601, InvalidParams = -32602, InternalError = -32603, serverErrorStart = -32099, serverErrorEnd = -32000, ServerNotInitialized = -32002, UnknownErrorCode = -32001, // Defined by the protocol. RequestCancelled = -32800, ContentModified = -32801 }; enum class LSPDocumentSyncKind { None = 0, Full = 1, Incremental = 2 }; struct LSPCompletionOptions { bool provider = false; bool resolveProvider = false; QVector triggerCharacters; }; struct LSPSignatureHelpOptions { bool provider = false; QVector triggerCharacters; }; // ensure distinct type struct LSPDocumentOnTypeFormattingOptions : public LSPSignatureHelpOptions { }; struct LSPServerCapabilities { LSPDocumentSyncKind textDocumentSync = LSPDocumentSyncKind::None; bool hoverProvider = false; LSPCompletionOptions completionProvider; LSPSignatureHelpOptions signatureHelpProvider; bool definitionProvider = false; // FIXME ? clangd unofficial extension bool declarationProvider = false; bool referencesProvider = false; bool documentSymbolProvider = false; bool documentHighlightProvider = false; bool documentFormattingProvider = false; bool documentRangeFormattingProvider = false; LSPDocumentOnTypeFormattingOptions documentOnTypeFormattingProvider; bool renameProvider = false; // CodeActionOptions not useful/considered at present bool codeActionProvider = false; }; enum class LSPMarkupKind { None = 0, PlainText = 1, MarkDown = 2 }; struct LSPMarkupContent { LSPMarkupKind kind = LSPMarkupKind::None; QString value; }; /** * Language Server Protocol Position * line + column, 0 based, negative for invalid * maps 1:1 to KTextEditor::Cursor */ using LSPPosition = KTextEditor::Cursor; /** * Language Server Protocol Range * start + end tuple of LSPPosition * maps 1:1 to KTextEditor::Range */ using LSPRange = KTextEditor::Range; struct LSPLocation { QUrl uri; LSPRange range; }; struct LSPTextDocumentContentChangeEvent { LSPRange range; QString text; }; enum class LSPDocumentHighlightKind { Text = 1, Read = 2, Write = 3 }; struct LSPDocumentHighlight { LSPRange range; LSPDocumentHighlightKind kind; }; struct LSPHover { // vector for contents to support all three variants: // MarkedString | MarkedString[] | MarkupContent // vector variant is still in use e.g. by Rust rls QVector contents; LSPRange range; }; enum class LSPSymbolKind { File = 1, Module = 2, Namespace = 3, Package = 4, Class = 5, Method = 6, Property = 7, Field = 8, Constructor = 9, Enum = 10, Interface = 11, Function = 12, Variable = 13, Constant = 14, String = 15, Number = 16, Boolean = 17, Array = 18, }; struct LSPSymbolInformation { LSPSymbolInformation(const QString &_name, LSPSymbolKind _kind, LSPRange _range, const QString &_detail) : name(_name) , detail(_detail) , kind(_kind) , range(_range) { } QString name; QString detail; LSPSymbolKind kind; LSPRange range; QList children; }; enum class LSPCompletionItemKind { Text = 1, Method = 2, Function = 3, Constructor = 4, Field = 5, Variable = 6, Class = 7, Interface = 8, Module = 9, Property = 10, Unit = 11, Value = 12, Enum = 13, Keyword = 14, Snippet = 15, Color = 16, File = 17, Reference = 18, Folder = 19, EnumMember = 20, Constant = 21, Struct = 22, Event = 23, Operator = 24, TypeParameter = 25, }; struct LSPCompletionItem { QString label; LSPCompletionItemKind kind; QString detail; LSPMarkupContent documentation; QString sortText; QString insertText; }; struct LSPParameterInformation { // offsets into overall signature label // (-1 if invalid) int start; int end; }; struct LSPSignatureInformation { QString label; LSPMarkupContent documentation; QList parameters; }; struct LSPSignatureHelp { QList signatures; int activeSignature; int activeParameter; }; struct LSPFormattingOptions { int tabSize; bool insertSpaces; // additional fields QJsonObject extra; }; struct LSPTextEdit { LSPRange range; QString newText; }; enum class LSPDiagnosticSeverity { Unknown = 0, Error = 1, Warning = 2, Information = 3, Hint = 4, }; struct LSPDiagnosticRelatedInformation { // empty url / invalid range when absent LSPLocation location; QString message; }; struct LSPDiagnostic { LSPRange range; LSPDiagnosticSeverity severity; QString code; QString source; QString message; QList relatedInformation; }; struct LSPPublishDiagnosticsParams { QUrl uri; QList diagnostics; }; struct LSPCommand { QString title; QString command; // pretty opaque QJsonArray arguments; }; struct LSPWorkspaceEdit { // supported part for now QHash> changes; }; struct LSPCodeAction { QString title; QString kind; QList diagnostics; LSPWorkspaceEdit edit; LSPCommand command; }; struct LSPApplyWorkspaceEditParams { QString label; LSPWorkspaceEdit edit; }; struct LSPApplyWorkspaceEditResponse { bool applied; QString failureReason; }; #endif diff --git a/addons/lspclient/lspclientserver.cpp b/addons/lspclient/lspclientserver.cpp index 95008c269..5eaaff55e 100644 --- a/addons/lspclient/lspclientserver.cpp +++ b/addons/lspclient/lspclientserver.cpp @@ -1,1300 +1,1300 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "lspclientserver.h" #include "lspclient_debug.h" -#include #include +#include #include +#include +#include +#include #include #include -#include -#include #include -#include #include // good/bad old school; allows easier concatenate #define CONTENT_LENGTH "Content-Length" static const QString MEMBER_ID = QStringLiteral("id"); static const QString MEMBER_METHOD = QStringLiteral("method"); static const QString MEMBER_ERROR = QStringLiteral("error"); static const QString MEMBER_CODE = QStringLiteral("code"); static const QString MEMBER_MESSAGE = QStringLiteral("message"); static const QString MEMBER_PARAMS = QStringLiteral("params"); static const QString MEMBER_RESULT = QStringLiteral("result"); static const QString MEMBER_URI = QStringLiteral("uri"); static const QString MEMBER_VERSION = QStringLiteral("version"); static const QString MEMBER_START = QStringLiteral("start"); static const QString MEMBER_END = QStringLiteral("end"); static const QString MEMBER_POSITION = QStringLiteral("position"); static const QString MEMBER_LOCATION = QStringLiteral("location"); static const QString MEMBER_RANGE = QStringLiteral("range"); static const QString MEMBER_LINE = QStringLiteral("line"); static const QString MEMBER_CHARACTER = QStringLiteral("character"); static const QString MEMBER_KIND = QStringLiteral("kind"); static const QString MEMBER_TEXT = QStringLiteral("text"); static const QString MEMBER_LANGID = QStringLiteral("languageId"); static const QString MEMBER_LABEL = QStringLiteral("label"); static const QString MEMBER_DOCUMENTATION = QStringLiteral("documentation"); static const QString MEMBER_DETAIL = QStringLiteral("detail"); static const QString MEMBER_COMMAND = QStringLiteral("command"); static const QString MEMBER_EDIT = QStringLiteral("edit"); static const QString MEMBER_TITLE = QStringLiteral("title"); static const QString MEMBER_ARGUMENTS = QStringLiteral("arguments"); static const QString MEMBER_DIAGNOSTICS = QStringLiteral("diagnostics"); // message construction helpers static QJsonObject to_json(const LSPPosition &pos) { return QJsonObject {{MEMBER_LINE, pos.line()}, {MEMBER_CHARACTER, pos.column()}}; } static QJsonObject to_json(const LSPRange &range) { return QJsonObject {{MEMBER_START, to_json(range.start())}, {MEMBER_END, to_json(range.end())}}; } static QJsonValue to_json(const LSPLocation &location) { if (location.uri.isValid()) { return QJsonObject {{MEMBER_URI, location.uri.toString()}, {MEMBER_RANGE, to_json(location.range)}}; } return QJsonValue(); } static QJsonValue to_json(const LSPDiagnosticRelatedInformation &related) { auto loc = to_json(related.location); if (loc.isObject()) { return QJsonObject {{MEMBER_LOCATION, to_json(related.location)}, {MEMBER_MESSAGE, related.message}}; } return QJsonValue(); } static QJsonObject to_json(const LSPDiagnostic &diagnostic) { // required auto result = QJsonObject(); result[MEMBER_RANGE] = to_json(diagnostic.range); result[MEMBER_MESSAGE] = diagnostic.message; // optional if (!diagnostic.code.isEmpty()) result[QStringLiteral("code")] = diagnostic.code; if (diagnostic.severity != LSPDiagnosticSeverity::Unknown) result[QStringLiteral("severity")] = (int)diagnostic.severity; if (!diagnostic.source.isEmpty()) result[QStringLiteral("source")] = diagnostic.source; QJsonArray relatedInfo; for (const auto &vrelated : diagnostic.relatedInformation) { auto related = to_json(vrelated); if (related.isObject()) { relatedInfo.push_back(related); } } result[QStringLiteral("relatedInformation")] = relatedInfo; return result; } static QJsonArray to_json(const QList &changes) { QJsonArray result; for (const auto &change : changes) { result.push_back(QJsonObject {{MEMBER_RANGE, to_json(change.range)}, {MEMBER_TEXT, change.text}}); } return result; } static QJsonObject versionedTextDocumentIdentifier(const QUrl &document, int version = -1) { QJsonObject map {{MEMBER_URI, document.toString()}}; if (version >= 0) map[MEMBER_VERSION] = version; return map; } static QJsonObject textDocumentItem(const QUrl &document, const QString &lang, const QString &text, int version) { auto map = versionedTextDocumentIdentifier(document, version); map[MEMBER_TEXT] = text; map[MEMBER_LANGID] = lang; return map; } static QJsonObject textDocumentParams(const QJsonObject &m) { return QJsonObject {{QStringLiteral("textDocument"), m}}; } static QJsonObject textDocumentParams(const QUrl &document, int version = -1) { return textDocumentParams(versionedTextDocumentIdentifier(document, version)); } static QJsonObject textDocumentPositionParams(const QUrl &document, LSPPosition pos) { auto params = textDocumentParams(document); params[MEMBER_POSITION] = to_json(pos); return params; } static QJsonObject referenceParams(const QUrl &document, LSPPosition pos, bool decl) { auto params = textDocumentPositionParams(document, pos); params[QStringLiteral("context")] = QJsonObject {{QStringLiteral("includeDeclaration"), decl}}; return params; } static QJsonObject formattingOptions(const LSPFormattingOptions &_options) { auto options = _options.extra; options[QStringLiteral("tabSize")] = _options.tabSize; options[QStringLiteral("insertSpaces")] = _options.insertSpaces; return options; } static QJsonObject documentRangeFormattingParams(const QUrl &document, const LSPRange *range, const LSPFormattingOptions &_options) { auto params = textDocumentParams(document); if (range) { params[MEMBER_RANGE] = to_json(*range); } params[QStringLiteral("options")] = formattingOptions(_options); return params; } static QJsonObject documentOnTypeFormattingParams(const QUrl &document, const LSPPosition &pos, const QChar &lastChar, const LSPFormattingOptions &_options) { auto params = textDocumentPositionParams(document, pos); params[QStringLiteral("ch")] = QString(lastChar); params[QStringLiteral("options")] = formattingOptions(_options); return params; } static QJsonObject renameParams(const QUrl &document, const LSPPosition &pos, const QString &newName) { auto params = textDocumentPositionParams(document, pos); params[QStringLiteral("newName")] = newName; return params; } static QJsonObject codeActionParams(const QUrl &document, const LSPRange &range, const QList &kinds, const QList &diagnostics) { auto params = textDocumentParams(document); params[MEMBER_RANGE] = to_json(range); QJsonObject context; QJsonArray diags; for (const auto &diagnostic : diagnostics) { diags.push_back(to_json(diagnostic)); } context[MEMBER_DIAGNOSTICS] = diags; if (kinds.length()) context[QStringLiteral("only")] = QJsonArray::fromStringList(kinds); params[QStringLiteral("context")] = context; return params; } static QJsonObject executeCommandParams(const QString &command, const QJsonValue &args) { return QJsonObject {{MEMBER_COMMAND, command}, {MEMBER_ARGUMENTS, args}}; } static QJsonObject applyWorkspaceEditResponse(const LSPApplyWorkspaceEditResponse &response) { return QJsonObject {{QStringLiteral("applied"), response.applied}, {QStringLiteral("failureReason"), response.failureReason}}; } static void from_json(QVector &trigger, const QJsonValue &json) { for (const auto &t : json.toArray()) { auto st = t.toString(); if (st.length()) trigger.push_back(st.at(0)); } } static void from_json(LSPCompletionOptions &options, const QJsonValue &json) { if (json.isObject()) { auto ob = json.toObject(); options.provider = true; options.resolveProvider = ob.value(QStringLiteral("resolveProvider")).toBool(); from_json(options.triggerCharacters, ob.value(QStringLiteral("triggerCharacters"))); } } static void from_json(LSPSignatureHelpOptions &options, const QJsonValue &json) { if (json.isObject()) { auto ob = json.toObject(); options.provider = true; from_json(options.triggerCharacters, ob.value(QStringLiteral("triggerCharacters"))); } } static void from_json(LSPDocumentOnTypeFormattingOptions &options, const QJsonValue &json) { if (json.isObject()) { auto ob = json.toObject(); options.provider = true; from_json(options.triggerCharacters, ob.value(QStringLiteral("moreTriggerCharacter"))); auto trigger = ob.value(QStringLiteral("firstTriggerCharacter")).toString(); if (trigger.size()) { options.triggerCharacters.insert(0, trigger.at(0)); } } } static void from_json(LSPServerCapabilities &caps, const QJsonObject &json) { auto sync = json.value(QStringLiteral("textDocumentSync")); caps.textDocumentSync = (LSPDocumentSyncKind)(sync.isObject() ? sync.toObject().value(QStringLiteral("change")) : sync).toInt((int)LSPDocumentSyncKind::None); caps.hoverProvider = json.value(QStringLiteral("hoverProvider")).toBool(); from_json(caps.completionProvider, json.value(QStringLiteral("completionProvider"))); from_json(caps.signatureHelpProvider, json.value(QStringLiteral("signatureHelpProvider"))); caps.definitionProvider = json.value(QStringLiteral("definitionProvider")).toBool(); caps.declarationProvider = json.value(QStringLiteral("declarationProvider")).toBool(); caps.referencesProvider = json.value(QStringLiteral("referencesProvider")).toBool(); caps.documentSymbolProvider = json.value(QStringLiteral("documentSymbolProvider")).toBool(); caps.documentHighlightProvider = json.value(QStringLiteral("documentHighlightProvider")).toBool(); caps.documentFormattingProvider = json.value(QStringLiteral("documentFormattingProvider")).toBool(); caps.documentRangeFormattingProvider = json.value(QStringLiteral("documentRangeFormattingProvider")).toBool(); from_json(caps.documentOnTypeFormattingProvider, json.value(QStringLiteral("documentOnTypeFormattingProvider"))); caps.renameProvider = json.value(QStringLiteral("renameProvider")).toBool(); auto codeActionProvider = json.value(QStringLiteral("codeActionProvider")); caps.codeActionProvider = codeActionProvider.toBool() || codeActionProvider.isObject(); } // follow suit; as performed in kate docmanager // normalize at this stage/layer to avoid surprises elsewhere // sadly this is not a single QUrl method as one might hope ... static QUrl normalizeUrl(const QUrl &url) { // Resolve symbolic links for local files (done anyway in KTextEditor) if (url.isLocalFile()) { QString normalizedUrl = QFileInfo(url.toLocalFile()).canonicalFilePath(); if (!normalizedUrl.isEmpty()) { return QUrl::fromLocalFile(normalizedUrl); } } // else: cleanup only the .. stuff return url.adjusted(QUrl::NormalizePathSegments); } static LSPMarkupContent parseMarkupContent(const QJsonValue &v) { LSPMarkupContent ret; if (v.isObject()) { const auto &vm = v.toObject(); ret.value = vm.value(QStringLiteral("value")).toString(); auto kind = vm.value(MEMBER_KIND).toString(); if (kind == QLatin1String("plaintext")) { ret.kind = LSPMarkupKind::PlainText; } else if (kind == QLatin1String("markdown")) { ret.kind = LSPMarkupKind::MarkDown; } } else if (v.isString()) { ret.kind = LSPMarkupKind::PlainText; ret.value = v.toString(); } return ret; } static LSPPosition parsePosition(const QJsonObject &m) { auto line = m.value(MEMBER_LINE).toInt(-1); auto column = m.value(MEMBER_CHARACTER).toInt(-1); return {line, column}; } static bool isPositionValid(const LSPPosition &pos) { return pos.isValid(); } static LSPRange parseRange(const QJsonObject &range) { auto startpos = parsePosition(range.value(MEMBER_START).toObject()); auto endpos = parsePosition(range.value(MEMBER_END).toObject()); return {startpos, endpos}; } static LSPLocation parseLocation(const QJsonObject &loc) { auto uri = normalizeUrl(QUrl(loc.value(MEMBER_URI).toString())); auto range = parseRange(loc.value(MEMBER_RANGE).toObject()); return {QUrl(uri), range}; } static LSPDocumentHighlight parseDocumentHighlight(const QJsonValue &result) { auto hover = result.toObject(); auto range = parseRange(hover.value(MEMBER_RANGE).toObject()); auto kind = (LSPDocumentHighlightKind)hover.value(MEMBER_KIND).toInt((int)LSPDocumentHighlightKind::Text); // default is // DocumentHighlightKind.Text return {range, kind}; } static QList parseDocumentHighlightList(const QJsonValue &result) { QList ret; // could be array if (result.isArray()) { for (const auto &def : result.toArray()) { ret.push_back(parseDocumentHighlight(def)); } } else if (result.isObject()) { // or a single value ret.push_back(parseDocumentHighlight(result)); } return ret; } static LSPMarkupContent parseHoverContentElement(const QJsonValue &contents) { LSPMarkupContent result; if (contents.isString()) { result.value = contents.toString(); } else { // should be object, pretend so auto cont = contents.toObject(); auto text = cont.value(QStringLiteral("value")).toString(); if (text.isEmpty()) { // nothing to lose, try markdown result = parseMarkupContent(contents); } else { result.value = text; } } if (result.value.length()) result.kind = LSPMarkupKind::PlainText; return result; } static LSPHover parseHover(const QJsonValue &result) { LSPHover ret; auto hover = result.toObject(); // normalize content which can be of many forms ret.range = parseRange(hover.value(MEMBER_RANGE).toObject()); auto contents = hover.value(QStringLiteral("contents")); // support the deprecated MarkedString[] variant, used by e.g. Rust rls if (contents.isArray()) { for (const auto &c : contents.toArray()) { ret.contents.push_back(parseHoverContentElement(c)); } } else { ret.contents.push_back(parseHoverContentElement(contents)); } return ret; } static QList parseDocumentSymbols(const QJsonValue &result) { // the reply could be old SymbolInformation[] or new (hierarchical) DocumentSymbol[] // try to parse it adaptively in any case // if new style, hierarchy is specified clearly in reply // if old style, it is assumed the values enter linearly, that is; // * a parent/container is listed before its children // * if a name is defined/declared several times and then used as a parent, // then we try to find such a parent whose range contains current range // (otherwise fall back to using the last instance as a parent) QList ret; QMultiMap index; std::function parseSymbol = [&](const QJsonObject &symbol, LSPSymbolInformation *parent) { const auto &location = symbol.value(MEMBER_LOCATION).toObject(); const auto &mrange = symbol.contains(MEMBER_RANGE) ? symbol.value(MEMBER_RANGE) : location.value(MEMBER_RANGE); auto range = parseRange(mrange.toObject()); // if flat list, try to find parent by name if (!parent) { auto container = symbol.value(QStringLiteral("containerName")).toString(); auto it = index.find(container); // default to last inserted if (it != index.end()) { parent = it.value(); } // but prefer a containing range while (it != index.end() && it.key() == container) { if (it.value()->range.contains(range)) { parent = it.value(); break; } ++it; } } auto list = parent ? &parent->children : &ret; if (isPositionValid(range.start()) && isPositionValid(range.end())) { auto name = symbol.value(QStringLiteral("name")).toString(); auto kind = (LSPSymbolKind)symbol.value(MEMBER_KIND).toInt(); auto detail = symbol.value(MEMBER_DETAIL).toString(); list->push_back({name, kind, range, detail}); index.insert(name, &list->back()); // proceed recursively for (const auto &child : symbol.value(QStringLiteral("children")).toArray()) parseSymbol(child.toObject(), &list->back()); } }; for (const auto &info : result.toArray()) { parseSymbol(info.toObject(), nullptr); } return ret; } static QList parseDocumentLocation(const QJsonValue &result) { QList ret; // could be array if (result.isArray()) { for (const auto &def : result.toArray()) { ret.push_back(parseLocation(def.toObject())); } } else if (result.isObject()) { // or a single value ret.push_back(parseLocation(result.toObject())); } return ret; } static QList parseDocumentCompletion(const QJsonValue &result) { QList ret; QJsonArray items = result.toArray(); // might be CompletionList if (items.empty()) { items = result.toObject().value(QStringLiteral("items")).toArray(); } for (const auto &vitem : items) { const auto &item = vitem.toObject(); auto label = item.value(MEMBER_LABEL).toString(); auto detail = item.value(MEMBER_DETAIL).toString(); auto doc = parseMarkupContent(item.value(MEMBER_DOCUMENTATION)); auto sortText = item.value(QStringLiteral("sortText")).toString(); if (sortText.isEmpty()) sortText = label; auto insertText = item.value(QStringLiteral("insertText")).toString(); if (insertText.isEmpty()) insertText = label; auto kind = (LSPCompletionItemKind)item.value(MEMBER_KIND).toInt(); ret.push_back({label, kind, detail, doc, sortText, insertText}); } return ret; } static LSPSignatureInformation parseSignatureInformation(const QJsonObject &json) { LSPSignatureInformation info; info.label = json.value(MEMBER_LABEL).toString(); info.documentation = parseMarkupContent(json.value(MEMBER_DOCUMENTATION)); for (const auto &rpar : json.value(QStringLiteral("parameters")).toArray()) { auto par = rpar.toObject(); auto label = par.value(MEMBER_LABEL); int begin = -1, end = -1; if (label.isArray()) { auto range = label.toArray(); if (range.size() == 2) { begin = range.at(0).toInt(-1); end = range.at(1).toInt(-1); if (begin > info.label.length()) begin = -1; if (end > info.label.length()) end = -1; } } else { auto sub = label.toString(); if (sub.length()) { begin = info.label.indexOf(sub); if (begin >= 0) { end = begin + sub.length(); } } } info.parameters.push_back({begin, end}); } return info; } static LSPSignatureHelp parseSignatureHelp(const QJsonValue &result) { LSPSignatureHelp ret; QJsonObject sig = result.toObject(); for (const auto &info : sig.value(QStringLiteral("signatures")).toArray()) { ret.signatures.push_back(parseSignatureInformation(info.toObject())); } ret.activeSignature = sig.value(QStringLiteral("activeSignature")).toInt(0); ret.activeParameter = sig.value(QStringLiteral("activeParameter")).toInt(0); ret.activeSignature = qMin(qMax(ret.activeSignature, 0), ret.signatures.size()); ret.activeParameter = qMin(qMax(ret.activeParameter, 0), ret.signatures.size()); return ret; } static QList parseTextEdit(const QJsonValue &result) { QList ret; for (const auto &redit : result.toArray()) { auto edit = redit.toObject(); auto text = edit.value(QStringLiteral("newText")).toString(); auto range = parseRange(edit.value(MEMBER_RANGE).toObject()); ret.push_back({range, text}); } return ret; } static LSPWorkspaceEdit parseWorkSpaceEdit(const QJsonValue &result) { QHash> ret; auto changes = result.toObject().value(QStringLiteral("changes")).toObject(); for (auto it = changes.begin(); it != changes.end(); ++it) { ret.insert(normalizeUrl(QUrl(it.key())), parseTextEdit(it.value())); } return {ret}; } static LSPCommand parseCommand(const QJsonObject &result) { auto title = result.value(MEMBER_TITLE).toString(); auto command = result.value(MEMBER_COMMAND).toString(); auto args = result.value(MEMBER_ARGUMENTS).toArray(); return {title, command, args}; } static QList parseDiagnostics(const QJsonArray &result) { QList ret; for (const auto &vdiag : result) { auto diag = vdiag.toObject(); auto range = parseRange(diag.value(MEMBER_RANGE).toObject()); auto severity = (LSPDiagnosticSeverity)diag.value(QStringLiteral("severity")).toInt(); auto code = diag.value(QStringLiteral("code")).toString(); auto source = diag.value(QStringLiteral("source")).toString(); auto message = diag.value(MEMBER_MESSAGE).toString(); auto relatedInfo = diag.value(QStringLiteral("relatedInformation")).toArray(); QList relatedInfoList; for (const auto &vrelated : relatedInfo) { auto related = vrelated.toObject(); auto relLocation = parseLocation(related.value(MEMBER_LOCATION).toObject()); auto relMessage = related.value(MEMBER_MESSAGE).toString(); relatedInfoList.push_back({relLocation, relMessage}); } ret.push_back({range, severity, code, source, message, relatedInfoList}); } return ret; } static QList parseCodeAction(const QJsonValue &result) { QList ret; for (const auto &vaction : result.toArray()) { auto action = vaction.toObject(); // entry could be Command or CodeAction if (!action.value(MEMBER_COMMAND).isString()) { // CodeAction auto title = action.value(MEMBER_TITLE).toString(); auto kind = action.value(MEMBER_KIND).toString(); auto command = parseCommand(action.value(MEMBER_COMMAND).toObject()); auto edit = parseWorkSpaceEdit(action.value(MEMBER_EDIT)); auto diagnostics = parseDiagnostics(action.value(MEMBER_DIAGNOSTICS).toArray()); ret.push_back({title, kind, diagnostics, edit, command}); } else { // Command auto command = parseCommand(action); ret.push_back({command.title, QString(), {}, {}, command}); } } return ret; } static LSPPublishDiagnosticsParams parseDiagnostics(const QJsonObject &result) { LSPPublishDiagnosticsParams ret; ret.uri = normalizeUrl(QUrl(result.value(MEMBER_URI).toString())); ret.diagnostics = parseDiagnostics(result.value(MEMBER_DIAGNOSTICS).toArray()); return ret; } static LSPApplyWorkspaceEditParams parseApplyWorkspaceEditParams(const QJsonObject &result) { LSPApplyWorkspaceEditParams ret; ret.label = result.value(MEMBER_LABEL).toString(); ret.edit = parseWorkSpaceEdit(result.value(MEMBER_EDIT)); return ret; } using GenericReplyType = QJsonValue; using GenericReplyHandler = ReplyHandler; class LSPClientServer::LSPClientServerPrivate { typedef LSPClientServerPrivate self_type; LSPClientServer *q; // server cmd line QStringList m_server; // workspace root to pass along QUrl m_root; // user provided init QJsonValue m_init; // server process QProcess m_sproc; // server declared capabilites LSPServerCapabilities m_capabilities; // server state State m_state = State::None; // last msg id int m_id = 0; // receive buffer QByteArray m_receive; // registered reply handlers QHash m_handlers; // pending request responses static constexpr int MAX_REQUESTS = 5; QVector m_requests {MAX_REQUESTS + 1}; public: LSPClientServerPrivate(LSPClientServer *_q, const QStringList &server, const QUrl &root, const QJsonValue &init) : q(_q) , m_server(server) , m_root(root) , m_init(init) { // setup async reading QObject::connect(&m_sproc, &QProcess::readyRead, utils::mem_fun(&self_type::read, this)); QObject::connect(&m_sproc, &QProcess::stateChanged, utils::mem_fun(&self_type::onStateChanged, this)); } ~LSPClientServerPrivate() { stop(TIMEOUT_SHUTDOWN, TIMEOUT_SHUTDOWN); } const QStringList &cmdline() const { return m_server; } State state() { return m_state; } const LSPServerCapabilities &capabilities() { return m_capabilities; } int cancel(int reqid) { if (m_handlers.remove(reqid) > 0) { auto params = QJsonObject {{MEMBER_ID, reqid}}; write(init_request(QStringLiteral("$/cancelRequest"), params)); } return -1; } private: void setState(State s) { if (m_state != s) { m_state = s; emit q->stateChanged(q); } } RequestHandle write(const QJsonObject &msg, const GenericReplyHandler &h = nullptr, const int *id = nullptr) { RequestHandle ret; ret.m_server = q; if (!running()) return ret; auto ob = msg; ob.insert(QStringLiteral("jsonrpc"), QStringLiteral("2.0")); // notification == no handler if (h) { ob.insert(MEMBER_ID, ++m_id); ret.m_id = m_id; m_handlers[m_id] = h; } else if (id) { ob.insert(MEMBER_ID, *id); } QJsonDocument json(ob); auto sjson = json.toJson(); qCInfo(LSPCLIENT) << "calling" << msg[MEMBER_METHOD].toString(); qCDebug(LSPCLIENT) << "sending message:\n" << QString::fromUtf8(sjson); // some simple parsers expect length header first auto hdr = QStringLiteral(CONTENT_LENGTH ": %1\r\n").arg(sjson.length()); // write is async, so no blocking wait occurs here m_sproc.write(hdr.toLatin1()); m_sproc.write("\r\n"); m_sproc.write(sjson); return ret; } RequestHandle send(const QJsonObject &msg, const GenericReplyHandler &h = nullptr) { if (m_state == State::Running) return write(msg, h); else qCWarning(LSPCLIENT) << "send for non-running server"; return RequestHandle(); } void read() { // accumulate in buffer m_receive.append(m_sproc.readAllStandardOutput()); // try to get one (or more) message QByteArray &buffer = m_receive; while (true) { qCDebug(LSPCLIENT) << "buffer size" << buffer.length(); auto header = QByteArray(CONTENT_LENGTH ":"); int index = buffer.indexOf(header); if (index < 0) { // avoid collecting junk if (buffer.length() > 1 << 20) buffer.clear(); break; } index += header.length(); int endindex = buffer.indexOf("\r\n", index); auto msgstart = buffer.indexOf("\r\n\r\n", index); if (endindex < 0 || msgstart < 0) break; msgstart += 4; bool ok = false; auto length = buffer.mid(index, endindex - index).toInt(&ok, 10); // FIXME perhaps detect if no reply for some time // then again possibly better left to user to restart in such case if (!ok) { qCWarning(LSPCLIENT) << "invalid " CONTENT_LENGTH; // flush and try to carry on to some next header buffer.remove(0, msgstart); continue; } // sanity check to avoid extensive buffering if (length > 1 << 29) { qCWarning(LSPCLIENT) << "excessive size"; buffer.clear(); continue; } if (msgstart + length > buffer.length()) break; // now onto payload auto payload = buffer.mid(msgstart, length); buffer.remove(0, msgstart + length); qCInfo(LSPCLIENT) << "got message payload size " << length; qCDebug(LSPCLIENT) << "message payload:\n" << payload; QJsonParseError error {}; auto msg = QJsonDocument::fromJson(payload, &error); if (error.error != QJsonParseError::NoError || !msg.isObject()) { qCWarning(LSPCLIENT) << "invalid response payload"; continue; } auto result = msg.object(); // check if it is the expected result int msgid = -1; if (result.contains(MEMBER_ID)) { msgid = result[MEMBER_ID].toInt(); } else { processNotification(result); continue; } // could be request if (result.contains(MEMBER_METHOD)) { processRequest(result); continue; } // a valid reply; what to do with it now auto it = m_handlers.find(msgid); if (it != m_handlers.end()) { // copy handler to local storage const auto handler = *it; // remove handler from our set, do this pre handler execution to avoid races m_handlers.erase(it); // run handler, might e.g. trigger some new LSP actions for this server handler(result.value(MEMBER_RESULT)); } else { // could have been canceled qCDebug(LSPCLIENT) << "unexpected reply id"; } } } static QJsonObject init_error(const LSPErrorCode code, const QString &msg) { return QJsonObject {{MEMBER_ERROR, QJsonObject {{MEMBER_CODE, (int)code}, {MEMBER_MESSAGE, msg}}}}; } static QJsonObject init_request(const QString &method, const QJsonObject ¶ms = QJsonObject()) { return QJsonObject {{MEMBER_METHOD, method}, {MEMBER_PARAMS, params}}; } static QJsonObject init_response(const QJsonValue &result = QJsonValue()) { return QJsonObject {{MEMBER_RESULT, result}}; } bool running() { return m_sproc.state() == QProcess::Running; } void onStateChanged(QProcess::ProcessState nstate) { if (nstate == QProcess::NotRunning) { setState(State::None); } } void shutdown() { if (m_state == State::Running) { qCInfo(LSPCLIENT) << "shutting down" << m_server; // cancel all pending m_handlers.clear(); // shutdown sequence send(init_request(QStringLiteral("shutdown"))); // maybe we will get/see reply on the above, maybe not // but not important or useful either way send(init_request(QStringLiteral("exit"))); // no longer fit for regular use setState(State::Shutdown); } } void onInitializeReply(const QJsonValue &value) { // only parse parts that we use later on from_json(m_capabilities, value.toObject().value(QStringLiteral("capabilities")).toObject()); // finish init initialized(); } void initialize() { QJsonObject codeAction {{QStringLiteral("codeActionLiteralSupport"), QJsonObject {{QStringLiteral("codeActionKind"), QJsonObject {{QStringLiteral("valueSet"), QJsonArray()}}}}}}; QJsonObject capabilities {{QStringLiteral("textDocument"), QJsonObject {{ QStringLiteral("documentSymbol"), QJsonObject {{QStringLiteral("hierarchicalDocumentSymbolSupport"), true}}, }, {QStringLiteral("publishDiagnostics"), QJsonObject {{QStringLiteral("relatedInformation"), true}}}, {QStringLiteral("codeAction"), codeAction}}}}; // NOTE a typical server does not use root all that much, // other than for some corner case (in) requests QJsonObject params {{QStringLiteral("processId"), QCoreApplication::applicationPid()}, {QStringLiteral("rootPath"), m_root.path()}, {QStringLiteral("rootUri"), m_root.toString()}, {QStringLiteral("capabilities"), capabilities}, {QStringLiteral("initializationOptions"), m_init}}; // write(init_request(QStringLiteral("initialize"), params), utils::mem_fun(&self_type::onInitializeReply, this)); } void initialized() { write(init_request(QStringLiteral("initialized"))); setState(State::Running); } public: bool start() { if (m_state != State::None) return true; auto program = m_server.front(); auto args = m_server; args.pop_front(); qCInfo(LSPCLIENT) << "starting" << m_server << "with root" << m_root; // start LSP server in project root m_sproc.setWorkingDirectory(m_root.path()); // at least we see some errors somewhere then m_sproc.setProcessChannelMode(QProcess::ForwardedErrorChannel); m_sproc.setReadChannel(QProcess::QProcess::StandardOutput); m_sproc.start(program, args); bool result = m_sproc.waitForStarted(); if (!result) { qCWarning(LSPCLIENT) << m_sproc.error(); } else { setState(State::Started); // perform initial handshake initialize(); } return result; } void stop(int to_term, int to_kill) { if (running()) { shutdown(); if ((to_term >= 0) && !m_sproc.waitForFinished(to_term)) m_sproc.terminate(); if ((to_kill >= 0) && !m_sproc.waitForFinished(to_kill)) m_sproc.kill(); } } RequestHandle documentSymbols(const QUrl &document, const GenericReplyHandler &h) { auto params = textDocumentParams(document); return send(init_request(QStringLiteral("textDocument/documentSymbol"), params), h); } RequestHandle documentDefinition(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/definition"), params), h); } RequestHandle documentDeclaration(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/declaration"), params), h); } RequestHandle documentHover(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/hover"), params), h); } RequestHandle documentHighlight(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/documentHighlight"), params), h); } RequestHandle documentReferences(const QUrl &document, const LSPPosition &pos, bool decl, const GenericReplyHandler &h) { auto params = referenceParams(document, pos, decl); return send(init_request(QStringLiteral("textDocument/references"), params), h); } RequestHandle documentCompletion(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/completion"), params), h); } RequestHandle signatureHelp(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/signatureHelp"), params), h); } RequestHandle documentFormatting(const QUrl &document, const LSPFormattingOptions &options, const GenericReplyHandler &h) { auto params = documentRangeFormattingParams(document, nullptr, options); return send(init_request(QStringLiteral("textDocument/formatting"), params), h); } RequestHandle documentRangeFormatting(const QUrl &document, const LSPRange &range, const LSPFormattingOptions &options, const GenericReplyHandler &h) { auto params = documentRangeFormattingParams(document, &range, options); return send(init_request(QStringLiteral("textDocument/rangeFormatting"), params), h); } RequestHandle documentOnTypeFormatting(const QUrl &document, const LSPPosition &pos, QChar lastChar, const LSPFormattingOptions &options, const GenericReplyHandler &h) { auto params = documentOnTypeFormattingParams(document, pos, lastChar, options); return send(init_request(QStringLiteral("textDocument/onTypeFormatting"), params), h); } RequestHandle documentRename(const QUrl &document, const LSPPosition &pos, const QString &newName, const GenericReplyHandler &h) { auto params = renameParams(document, pos, newName); return send(init_request(QStringLiteral("textDocument/rename"), params), h); } RequestHandle documentCodeAction(const QUrl &document, const LSPRange &range, const QList &kinds, QList diagnostics, const GenericReplyHandler &h) { auto params = codeActionParams(document, range, kinds, std::move(diagnostics)); return send(init_request(QStringLiteral("textDocument/codeAction"), params), h); } void executeCommand(const QString &command, const QJsonValue &args) { auto params = executeCommandParams(command, args); send(init_request(QStringLiteral("workspace/executeCommand"), params)); } void didOpen(const QUrl &document, int version, const QString &langId, const QString &text) { auto params = textDocumentParams(textDocumentItem(document, langId, text, version)); send(init_request(QStringLiteral("textDocument/didOpen"), params)); } void didChange(const QUrl &document, int version, const QString &text, const QList &changes) { Q_ASSERT(text.isEmpty() || changes.empty()); auto params = textDocumentParams(document, version); params[QStringLiteral("contentChanges")] = text.size() ? QJsonArray {QJsonObject {{MEMBER_TEXT, text}}} : to_json(changes); send(init_request(QStringLiteral("textDocument/didChange"), params)); } void didSave(const QUrl &document, const QString &text) { auto params = textDocumentParams(document); params[QStringLiteral("text")] = text; send(init_request(QStringLiteral("textDocument/didSave"), params)); } void didClose(const QUrl &document) { auto params = textDocumentParams(document); send(init_request(QStringLiteral("textDocument/didClose"), params)); } void processNotification(const QJsonObject &msg) { auto method = msg[MEMBER_METHOD].toString(); if (method == QLatin1String("textDocument/publishDiagnostics")) { emit q->publishDiagnostics(parseDiagnostics(msg[MEMBER_PARAMS].toObject())); } else { qCWarning(LSPCLIENT) << "discarding notification" << method; } } template static GenericReplyHandler make_handler(const ReplyHandler &h, const QObject *context, typename utils::identity>::type c) { QPointer ctx(context); return [ctx, h, c](const GenericReplyType &m) { if (ctx) h(c(m)); }; } GenericReplyHandler prepareResponse(int msgid) { // allow limited number of outstanding requests auto ctx = QPointer(q); m_requests.push_back(msgid); if (m_requests.size() > MAX_REQUESTS) { m_requests.pop_front(); } auto h = [ctx, this, msgid](const GenericReplyType &response) { if (!ctx) { return; } auto index = m_requests.indexOf(msgid); if (index >= 0) { m_requests.remove(index); write(init_response(response), nullptr, &msgid); } else { qCWarning(LSPCLIENT) << "discarding response" << msgid; } }; return h; } template static ReplyHandler responseHandler(const GenericReplyHandler &h, typename utils::identity>::type c) { return [h, c](const ReplyType &m) { h(c(m)); }; } // pretty rare and limited use, but anyway void processRequest(const QJsonObject &msg) { auto method = msg[MEMBER_METHOD].toString(); auto msgid = msg[MEMBER_ID].toInt(); auto params = msg[MEMBER_PARAMS]; bool handled = false; if (method == QLatin1String("workspace/applyEdit")) { auto h = responseHandler(prepareResponse(msgid), applyWorkspaceEditResponse); emit q->applyEdit(parseApplyWorkspaceEditParams(params.toObject()), h, handled); } else { write(init_error(LSPErrorCode::MethodNotFound, method), nullptr, &msgid); qCWarning(LSPCLIENT) << "discarding request" << method; } } }; // generic convert handler // sprinkle some connection-like context safety // not so likely relevant/needed due to typical sequence of events, // but in case the latter would be changed in surprising ways ... template static GenericReplyHandler make_handler(const ReplyHandler &h, const QObject *context, typename utils::identity>::type c) { QPointer ctx(context); return [ctx, h, c](const GenericReplyType &m) { if (ctx) h(c(m)); }; } LSPClientServer::LSPClientServer(const QStringList &server, const QUrl &root, const QJsonValue &init) : d(new LSPClientServerPrivate(this, server, root, init)) { } LSPClientServer::~LSPClientServer() { delete d; } const QStringList &LSPClientServer::cmdline() const { return d->cmdline(); } LSPClientServer::State LSPClientServer::state() const { return d->state(); } const LSPServerCapabilities &LSPClientServer::capabilities() const { return d->capabilities(); } bool LSPClientServer::start() { return d->start(); } void LSPClientServer::stop(int to_t, int to_k) { return d->stop(to_t, to_k); } int LSPClientServer::cancel(int reqid) { return d->cancel(reqid); } LSPClientServer::RequestHandle LSPClientServer::documentSymbols(const QUrl &document, const QObject *context, const DocumentSymbolsReplyHandler &h) { return d->documentSymbols(document, make_handler(h, context, parseDocumentSymbols)); } LSPClientServer::RequestHandle LSPClientServer::documentDefinition(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentDefinitionReplyHandler &h) { return d->documentDefinition(document, pos, make_handler(h, context, parseDocumentLocation)); } LSPClientServer::RequestHandle LSPClientServer::documentDeclaration(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentDefinitionReplyHandler &h) { return d->documentDeclaration(document, pos, make_handler(h, context, parseDocumentLocation)); } LSPClientServer::RequestHandle LSPClientServer::documentHover(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentHoverReplyHandler &h) { return d->documentHover(document, pos, make_handler(h, context, parseHover)); } LSPClientServer::RequestHandle LSPClientServer::documentHighlight(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentHighlightReplyHandler &h) { return d->documentHighlight(document, pos, make_handler(h, context, parseDocumentHighlightList)); } LSPClientServer::RequestHandle LSPClientServer::documentReferences(const QUrl &document, const LSPPosition &pos, bool decl, const QObject *context, const DocumentDefinitionReplyHandler &h) { return d->documentReferences(document, pos, decl, make_handler(h, context, parseDocumentLocation)); } LSPClientServer::RequestHandle LSPClientServer::documentCompletion(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentCompletionReplyHandler &h) { return d->documentCompletion(document, pos, make_handler(h, context, parseDocumentCompletion)); } LSPClientServer::RequestHandle LSPClientServer::signatureHelp(const QUrl &document, const LSPPosition &pos, const QObject *context, const SignatureHelpReplyHandler &h) { return d->signatureHelp(document, pos, make_handler(h, context, parseSignatureHelp)); } LSPClientServer::RequestHandle LSPClientServer::documentFormatting(const QUrl &document, const LSPFormattingOptions &options, const QObject *context, const FormattingReplyHandler &h) { return d->documentFormatting(document, options, make_handler(h, context, parseTextEdit)); } LSPClientServer::RequestHandle LSPClientServer::documentRangeFormatting(const QUrl &document, const LSPRange &range, const LSPFormattingOptions &options, const QObject *context, const FormattingReplyHandler &h) { return d->documentRangeFormatting(document, range, options, make_handler(h, context, parseTextEdit)); } LSPClientServer::RequestHandle LSPClientServer::documentOnTypeFormatting(const QUrl &document, const LSPPosition &pos, const QChar lastChar, const LSPFormattingOptions &options, const QObject *context, const FormattingReplyHandler &h) { return d->documentOnTypeFormatting(document, pos, lastChar, options, make_handler(h, context, parseTextEdit)); } LSPClientServer::RequestHandle LSPClientServer::documentRename(const QUrl &document, const LSPPosition &pos, const QString &newName, const QObject *context, const WorkspaceEditReplyHandler &h) { return d->documentRename(document, pos, newName, make_handler(h, context, parseWorkSpaceEdit)); } LSPClientServer::RequestHandle LSPClientServer::documentCodeAction(const QUrl &document, const LSPRange &range, const QList &kinds, QList diagnostics, const QObject *context, const CodeActionReplyHandler &h) { return d->documentCodeAction(document, range, kinds, std::move(diagnostics), make_handler(h, context, parseCodeAction)); } void LSPClientServer::executeCommand(const QString &command, const QJsonValue &args) { return d->executeCommand(command, args); } void LSPClientServer::didOpen(const QUrl &document, int version, const QString &langId, const QString &text) { return d->didOpen(document, version, langId, text); } void LSPClientServer::didChange(const QUrl &document, int version, const QString &text, const QList &changes) { return d->didChange(document, version, text, changes); } void LSPClientServer::didSave(const QUrl &document, const QString &text) { return d->didSave(document, text); } void LSPClientServer::didClose(const QUrl &document) { return d->didClose(document); } diff --git a/addons/lspclient/lspclientserver.h b/addons/lspclient/lspclientserver.h index ab0413ebd..22efd24dc 100644 --- a/addons/lspclient/lspclientserver.h +++ b/addons/lspclient/lspclientserver.h @@ -1,154 +1,154 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef LSPCLIENTSERVER_H #define LSPCLIENTSERVER_H #include "lspclientprotocol.h" +#include +#include #include +#include #include #include -#include #include -#include -#include #include namespace utils { // template helper // function bind helpers template inline std::function mem_fun(R (T::*pm)(Args...), Tp object) { return [object, pm](Args... args) { return (object->*pm)(std::forward(args)...); }; } template inline std::function mem_fun(R (T::*pm)(Args...) const, Tp object) { return [object, pm](Args... args) { return (object->*pm)(std::forward(args)...); }; } // prevent argument deduction template struct identity { typedef T type; }; } // namespace utils static const int TIMEOUT_SHUTDOWN = 200; template using ReplyHandler = std::function; using DocumentSymbolsReplyHandler = ReplyHandler>; using DocumentDefinitionReplyHandler = ReplyHandler>; using DocumentHighlightReplyHandler = ReplyHandler>; using DocumentHoverReplyHandler = ReplyHandler; using DocumentCompletionReplyHandler = ReplyHandler>; using SignatureHelpReplyHandler = ReplyHandler; using FormattingReplyHandler = ReplyHandler>; using CodeActionReplyHandler = ReplyHandler>; using WorkspaceEditReplyHandler = ReplyHandler; using ApplyEditReplyHandler = ReplyHandler; class LSPClientServer : public QObject { Q_OBJECT public: enum class State { None, Started, Running, Shutdown }; class LSPClientServerPrivate; class RequestHandle { friend class LSPClientServerPrivate; QPointer m_server; int m_id = -1; public: RequestHandle &cancel() { if (m_server) m_server->cancel(m_id); return *this; } }; LSPClientServer(const QStringList &server, const QUrl &root, const QJsonValue &init = QJsonValue()); ~LSPClientServer() override; // server management // request start bool start(); // request shutdown/stop // if to_xxx >= 0 -> send signal if not exit'ed after timeout void stop(int to_term_ms, int to_kill_ms); int cancel(int id); // properties const QStringList &cmdline() const; State state() const; Q_SIGNAL void stateChanged(LSPClientServer *server); const LSPServerCapabilities &capabilities() const; // language RequestHandle documentSymbols(const QUrl &document, const QObject *context, const DocumentSymbolsReplyHandler &h); RequestHandle documentDefinition(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentDefinitionReplyHandler &h); RequestHandle documentDeclaration(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentDefinitionReplyHandler &h); RequestHandle documentHighlight(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentHighlightReplyHandler &h); RequestHandle documentHover(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentHoverReplyHandler &h); RequestHandle documentReferences(const QUrl &document, const LSPPosition &pos, bool decl, const QObject *context, const DocumentDefinitionReplyHandler &h); RequestHandle documentCompletion(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentCompletionReplyHandler &h); RequestHandle signatureHelp(const QUrl &document, const LSPPosition &pos, const QObject *context, const SignatureHelpReplyHandler &h); RequestHandle documentFormatting(const QUrl &document, const LSPFormattingOptions &options, const QObject *context, const FormattingReplyHandler &h); RequestHandle documentRangeFormatting(const QUrl &document, const LSPRange &range, const LSPFormattingOptions &options, const QObject *context, const FormattingReplyHandler &h); RequestHandle documentOnTypeFormatting(const QUrl &document, const LSPPosition &pos, QChar lastChar, const LSPFormattingOptions &options, const QObject *context, const FormattingReplyHandler &h); RequestHandle documentRename(const QUrl &document, const LSPPosition &pos, const QString &newName, const QObject *context, const WorkspaceEditReplyHandler &h); RequestHandle documentCodeAction(const QUrl &document, const LSPRange &range, const QList &kinds, QList diagnostics, const QObject *context, const CodeActionReplyHandler &h); void executeCommand(const QString &command, const QJsonValue &args); // sync void didOpen(const QUrl &document, int version, const QString &langId, const QString &text); // only 1 of text or changes should be non-empty and is considered void didChange(const QUrl &document, int version, const QString &text, const QList &changes = {}); void didSave(const QUrl &document, const QString &text); void didClose(const QUrl &document); // notifcation = signal Q_SIGNALS: void publishDiagnostics(const LSPPublishDiagnosticsParams &); // request = signal void applyEdit(const LSPApplyWorkspaceEditParams &req, const ApplyEditReplyHandler &h, bool &handled); private: // pimpl data holder LSPClientServerPrivate *const d; }; #endif diff --git a/addons/lspclient/lspclientservermanager.cpp b/addons/lspclient/lspclientservermanager.cpp index 9b4194f8e..b822c50b2 100644 --- a/addons/lspclient/lspclientservermanager.cpp +++ b/addons/lspclient/lspclientservermanager.cpp @@ -1,790 +1,790 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Some explanation here on server configuration JSON, pending such ending up * in real user level documentation ... * * The default configuration in JSON format is roughly as follows; { "global": { "root": null, }, "servers": { "Python": { "command": "python3 -m pyls --check-parent-process" }, "C": { "command": "clangd -log=verbose --background-index" }, "C++": { "use": "C++" } } } * (the "command" can be an array or a string, which is then split into array) * * From the above, the gist is presumably clear. In addition, each server * entry object may also have an "initializationOptions" entry, which is passed * along to the server as part of the 'initialize' method. A clangd-specific * HACK^Hfeature uses this to add "compilationDatabasePath". * * Various stages of override/merge are applied; * + user configuration (loaded from file) overrides (internal) default configuration * + "lspclient" entry in projectMap overrides the above * + the resulting "global" entry is used to supplement (not override) any server entry * * One server instance is used per (root, servertype) combination. * If "root" is not specified, it default to the $HOME directory. If it is * specified as an absolute path, then it used as-is, otherwise it is relative * to the projectBase. For any document, the resulting "root" then determines * whether or not a separate instance is needed. If so, the "root" is passed * as rootUri/rootPath. * * In general, it is recommended to leave root unspecified, as it is not that * important for a server (your mileage may vary though). Fewer instances * are obviously more efficient, and they also have a 'wider' view than * the view of many separate instances. */ #include "lspclientservermanager.h" #include "lspclient_debug.h" +#include #include #include -#include #include -#include #include +#include #include #include #include #include -#include #include +#include #include // helper to find a proper root dir for the given document & file name that indicate the root dir static QString rootForDocumentAndRootIndicationFileName(KTextEditor::Document *document, const QString &rootIndicationFileName) { // search only feasible if document is local file if (!document->url().isLocalFile()) { return QString(); } // search root upwards QDir dir(QFileInfo(document->url().toLocalFile()).absolutePath()); QSet seenDirectories; while (!seenDirectories.contains(dir.absolutePath())) { // update guard seenDirectories.insert(dir.absolutePath()); // the file that indicates the root dir is there => all fine if (dir.exists(rootIndicationFileName)) { return dir.absolutePath(); } // else: cd up, if possible or abort if (!dir.cdUp()) { break; } } // no root found, bad luck return QString(); } #include // local helper; // recursively merge top json top onto bottom json static QJsonObject merge(const QJsonObject &bottom, const QJsonObject &top) { QJsonObject result; for (auto item = top.begin(); item != top.end(); item++) { const auto &key = item.key(); if (item.value().isObject()) { result.insert(key, merge(bottom.value(key).toObject(), item.value().toObject())); } else { result.insert(key, item.value()); } } // parts only in bottom for (auto item = bottom.begin(); item != bottom.end(); item++) { if (!result.contains(item.key())) { result.insert(item.key(), item.value()); } } return result; } // map (highlight)mode to lsp languageId static QString languageId(const QString &mode) { // lookup in cache first static QHash m; auto it = m.find(mode); if (it != m.end()) { return *it; } /** * try to normalize the highlighting name * - lower-case * - transform some special characters */ QString result = mode.toLower(); result.replace(QStringLiteral("++"), QStringLiteral("pp")); result.replace(QStringLiteral("#"), QStringLiteral("sharp")); /** * we still need to take care of some languages that have "very" different names than the normalized lsp names */ if (result.contains(QLatin1String("fortran"))) { result = QStringLiteral("fortran"); } /** * now, cache the resulting mapping and be done */ m[mode] = result; return result; } // helper guard to handle revision (un)lock struct RevisionGuard { QPointer m_doc; KTextEditor::MovingInterface *m_movingInterface = nullptr; qint64 m_revision = -1; RevisionGuard(KTextEditor::Document *doc = nullptr) : m_doc(doc) , m_movingInterface(qobject_cast(doc)) { if (m_movingInterface) { m_revision = m_movingInterface->revision(); m_movingInterface->lockRevision(m_revision); } } // really only need/allow this one (out of 5) RevisionGuard(RevisionGuard &&other) : RevisionGuard(nullptr) { std::swap(m_doc, other.m_doc); std::swap(m_movingInterface, other.m_movingInterface); std::swap(m_revision, other.m_revision); } void release() { m_movingInterface = nullptr; m_revision = -1; } ~RevisionGuard() { // NOTE: hopefully the revision is still valid at this time if (m_doc && m_movingInterface && m_revision >= 0) { m_movingInterface->unlockRevision(m_revision); } } }; class LSPClientRevisionSnapshotImpl : public LSPClientRevisionSnapshot { Q_OBJECT typedef LSPClientRevisionSnapshotImpl self_type; // std::map has more relaxed constraints on value_type std::map m_guards; Q_SLOT void clearRevisions(KTextEditor::Document *doc) { for (auto &item : m_guards) { if (item.second.m_doc == doc) { item.second.release(); } } } public: void add(KTextEditor::Document *doc) { Q_ASSERT(doc); // make sure revision is cleared when needed and no longer used (to unlock or otherwise) // see e.g. implementation in katetexthistory.cpp and assert's in place there auto conn = connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearRevisions(KTextEditor::Document *))); Q_ASSERT(conn); conn = connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearRevisions(KTextEditor::Document *))); Q_ASSERT(conn); m_guards.emplace(doc->url(), doc); } void find(const QUrl &url, KTextEditor::MovingInterface *&miface, qint64 &revision) const override { auto it = m_guards.find(url); if (it != m_guards.end()) { miface = it->second.m_movingInterface; revision = it->second.m_revision; } else { miface = nullptr; revision = -1; } } }; // helper class to sync document changes to LSP server class LSPClientServerManagerImpl : public LSPClientServerManager { Q_OBJECT typedef LSPClientServerManagerImpl self_type; struct DocumentInfo { QSharedPointer server; KTextEditor::MovingInterface *movingInterface; QUrl url; qint64 version; bool open : 1; bool modified : 1; // used for incremental update (if non-empty) QList changes; }; LSPClientPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; // merged default and user config QJsonObject m_serverConfig; // root -> (mode -> server) QMap>> m_servers; QHash m_docs; bool m_incrementalSync = false; typedef QVector> ServerList; public: LSPClientServerManagerImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) : m_plugin(plugin) , m_mainWindow(mainWin) { connect(plugin, &LSPClientPlugin::update, this, &self_type::updateServerConfig); QTimer::singleShot(100, this, &self_type::updateServerConfig); } ~LSPClientServerManagerImpl() override { // stop everything as we go down // several stages; // stage 1; request shutdown of all servers (in parallel) // (give that some time) // stage 2; send TERM // stage 3; send KILL // stage 1 QEventLoop q; QTimer t; connect(&t, &QTimer::timeout, &q, &QEventLoop::quit); auto run = [&q, &t](int ms) { t.setSingleShot(true); t.start(ms); q.exec(); }; int count = 0; for (const auto &el : m_servers) { for (const auto &s : el) { disconnect(s.data(), nullptr, this, nullptr); if (s->state() != LSPClientServer::State::None) { auto handler = [&q, &count, s]() { if (s->state() != LSPClientServer::State::None) { if (--count == 0) { q.quit(); } } }; connect(s.data(), &LSPClientServer::stateChanged, this, handler); ++count; s->stop(-1, -1); } } } run(500); // stage 2 and 3 count = 0; for (count = 0; count < 2; ++count) { for (const auto &el : m_servers) { for (const auto &s : el) { s->stop(count == 0 ? 1 : -1, count == 0 ? -1 : 1); } } run(100); } } void setIncrementalSync(bool inc) override { m_incrementalSync = inc; } QSharedPointer findServer(KTextEditor::Document *document, bool updatedoc = true) override { if (!document || document->url().isEmpty()) return nullptr; auto it = m_docs.find(document); auto server = it != m_docs.end() ? it->server : nullptr; if (!server) { if ((server = _findServer(document))) trackDocument(document, server); } if (server && updatedoc) update(server.data(), false); return server; } QSharedPointer findServer(KTextEditor::View *view, bool updatedoc = true) override { return view ? findServer(view->document(), updatedoc) : nullptr; } // restart a specific server or all servers if server == nullptr void restart(LSPClientServer *server) override { ServerList servers; // find entry for server(s) and move out for (auto &m : m_servers) { for (auto it = m.begin(); it != m.end();) { if (!server || it->data() == server) { servers.push_back(*it); it = m.erase(it); } else { ++it; } } } restart(servers); } qint64 revision(KTextEditor::Document *doc) override { auto it = m_docs.find(doc); return it != m_docs.end() ? it->version : -1; } LSPClientRevisionSnapshot *snapshot(LSPClientServer *server) override { auto result = new LSPClientRevisionSnapshotImpl; for (auto it = m_docs.begin(); it != m_docs.end(); ++it) { if (it->server == server) { // sync server to latest revision that will be recorded update(it.key(), false); result->add(it.key()); } } return result; } private: void showMessage(const QString &msg, KTextEditor::Message::MessageType level) { KTextEditor::View *view = m_mainWindow->activeView(); if (!view || !view->document()) return; auto kmsg = new KTextEditor::Message(xi18nc("@info", "LSP Client: %1", msg), level); kmsg->setPosition(KTextEditor::Message::AboveView); kmsg->setAutoHide(5000); kmsg->setAutoHideMode(KTextEditor::Message::Immediate); kmsg->setView(view); view->document()->postMessage(kmsg); } // caller ensures that servers are no longer present in m_servers void restart(const ServerList &servers) { // close docs for (const auto &server : servers) { // controlling server here, so disable usual state tracking response disconnect(server.data(), nullptr, this, nullptr); for (auto it = m_docs.begin(); it != m_docs.end();) { auto &item = it.value(); if (item.server == server) { // no need to close if server not in proper state if (server->state() != LSPClientServer::State::Running) { item.open = false; } it = _close(it, true); } else { ++it; } } } // helper captures servers auto stopservers = [servers](int t, int k) { for (const auto &server : servers) { server->stop(t, k); } }; // trigger server shutdown now stopservers(-1, -1); // initiate delayed stages (TERM and KILL) // async, so give a bit more time QTimer::singleShot(2 * TIMEOUT_SHUTDOWN, this, [stopservers]() { stopservers(1, -1); }); QTimer::singleShot(4 * TIMEOUT_SHUTDOWN, this, [stopservers]() { stopservers(-1, 1); }); // as for the start part // trigger interested parties, which will again request a server as needed // let's delay this; less chance for server instances to trip over each other QTimer::singleShot(6 * TIMEOUT_SHUTDOWN, this, [this]() { emit serverChanged(); }); } void onStateChanged(LSPClientServer *server) { if (server->state() == LSPClientServer::State::Running) { // clear for normal operation emit serverChanged(); } else if (server->state() == LSPClientServer::State::None) { // went down showMessage(i18n("Server terminated unexpectedly: %1", server->cmdline().join(QLatin1Char(' '))), KTextEditor::Message::Warning); restart(server); } } QSharedPointer _findServer(KTextEditor::Document *document) { QObject *projectView = m_mainWindow->pluginView(QStringLiteral("kateprojectplugin")); const auto projectBase = QDir(projectView ? projectView->property("projectBaseDir").toString() : QString()); const auto &projectMap = projectView ? projectView->property("projectMap").toMap() : QVariantMap(); // compute the LSP standardized language id auto langId = languageId(document->highlightingMode()); // merge with project specific auto projectConfig = QJsonDocument::fromVariant(projectMap).object().value(QStringLiteral("lspclient")).toObject(); auto serverConfig = merge(m_serverConfig, projectConfig); // locate server config QJsonValue config; QSet used; while (true) { qCInfo(LSPCLIENT) << "language id " << langId; used << langId; config = serverConfig.value(QStringLiteral("servers")).toObject().value(langId); if (config.isObject()) { const auto &base = config.toObject().value(QStringLiteral("use")).toString(); // basic cycle detection if (!base.isEmpty() && !used.contains(base)) { langId = base; continue; } } break; } if (!config.isObject()) return nullptr; // merge global settings serverConfig = merge(serverConfig.value(QStringLiteral("global")).toObject(), config.toObject()); QString rootpath; auto rootv = serverConfig.value(QStringLiteral("root")); if (rootv.isString()) { auto sroot = rootv.toString(); if (QDir::isAbsolutePath(sroot)) { rootpath = sroot; } else if (!projectBase.isEmpty()) { rootpath = QDir(projectBase).absoluteFilePath(sroot); } } /** * no explicit set root dir? search for a matching root based on some name filters * this is required for some LSP servers like rls that don't handle that on their own like * clangd does */ if (rootpath.isEmpty()) { const auto fileNamesForDetection = serverConfig.value(QStringLiteral("rootIndicationFileNames")); if (fileNamesForDetection.isArray()) { // we try each file name alternative in the listed order // this allows to have preferences for (auto name : fileNamesForDetection.toArray()) { if (name.isString()) { rootpath = rootForDocumentAndRootIndicationFileName(document, name.toString()); if (!rootpath.isEmpty()) { break; } } } } } // last fallback: home directory if (rootpath.isEmpty()) { rootpath = QDir::homePath(); } auto root = QUrl::fromLocalFile(rootpath); auto server = m_servers.value(root).value(langId); if (!server) { QStringList cmdline; // choose debug command line for debug mode, fallback to command auto vcmdline = serverConfig.value(m_plugin->m_debugMode ? QStringLiteral("commandDebug") : QStringLiteral("command")); if (vcmdline.isUndefined()) { vcmdline = serverConfig.value(QStringLiteral("command")); } auto scmdline = vcmdline.toString(); if (!scmdline.isEmpty()) { cmdline = scmdline.split(QLatin1Char(' ')); } else { for (const auto &c : vcmdline.toArray()) { cmdline.push_back(c.toString()); } } if (cmdline.length() > 0) { server.reset(new LSPClientServer(cmdline, root, serverConfig.value(QStringLiteral("initializationOptions")))); m_servers[root][langId] = server; connect(server.data(), &LSPClientServer::stateChanged, this, &self_type::onStateChanged, Qt::UniqueConnection); if (!server->start()) { showMessage(i18n("Failed to start server: %1", cmdline.join(QLatin1Char(' '))), KTextEditor::Message::Error); } } } return (server && server->state() == LSPClientServer::State::Running) ? server : nullptr; } void updateServerConfig() { // default configuration, compiled into plugin resource, reading can't fail QFile defaultConfigFile(QStringLiteral(":/lspclient/settings.json")); defaultConfigFile.open(QIODevice::ReadOnly); Q_ASSERT(defaultConfigFile.isOpen()); m_serverConfig = QJsonDocument::fromJson(defaultConfigFile.readAll()).object(); // consider specified configuration const auto &configPath = m_plugin->m_configPath.path(); if (!configPath.isEmpty()) { QFile f(configPath); if (f.open(QIODevice::ReadOnly)) { auto data = f.readAll(); auto json = QJsonDocument::fromJson(data); if (json.isObject()) { m_serverConfig = merge(m_serverConfig, json.object()); } else { showMessage(i18n("Failed to parse server configuration: %1", configPath), KTextEditor::Message::Error); } } else { showMessage(i18n("Failed to read server configuration: %1", configPath), KTextEditor::Message::Error); } } // we could (but do not) perform restartAll here; // for now let's leave that up to user // but maybe we do have a server now where not before, so let's signal emit serverChanged(); } void trackDocument(KTextEditor::Document *doc, const QSharedPointer &server) { auto it = m_docs.find(doc); if (it == m_docs.end()) { KTextEditor::MovingInterface *miface = qobject_cast(doc); it = m_docs.insert(doc, {server, miface, doc->url(), 0, false, false, {}}); // track document connect(doc, &KTextEditor::Document::documentUrlChanged, this, &self_type::untrack, Qt::UniqueConnection); connect(doc, &KTextEditor::Document::highlightingModeChanged, this, &self_type::untrack, Qt::UniqueConnection); connect(doc, &KTextEditor::Document::aboutToClose, this, &self_type::untrack, Qt::UniqueConnection); connect(doc, &KTextEditor::Document::destroyed, this, &self_type::untrack, Qt::UniqueConnection); connect(doc, &KTextEditor::Document::textChanged, this, &self_type::onTextChanged, Qt::UniqueConnection); // in case of incremental change connect(doc, &KTextEditor::Document::textInserted, this, &self_type::onTextInserted, Qt::UniqueConnection); connect(doc, &KTextEditor::Document::textRemoved, this, &self_type::onTextRemoved, Qt::UniqueConnection); connect(doc, &KTextEditor::Document::lineWrapped, this, &self_type::onLineWrapped, Qt::UniqueConnection); connect(doc, &KTextEditor::Document::lineUnwrapped, this, &self_type::onLineUnwrapped, Qt::UniqueConnection); } else { it->server = server; } } decltype(m_docs)::iterator _close(decltype(m_docs)::iterator it, bool remove) { if (it != m_docs.end()) { if (it->open) { // release server side (use url as registered with) (it->server)->didClose(it->url); it->open = false; } if (remove) { disconnect(it.key(), nullptr, this, nullptr); it = m_docs.erase(it); } } return it; } void _close(KTextEditor::Document *doc, bool remove) { auto it = m_docs.find(doc); if (it != m_docs.end()) { _close(it, remove); } } void untrack(QObject *doc) { _close(qobject_cast(doc), true); emit serverChanged(); } void close(KTextEditor::Document *doc) { _close(doc, false); } void update(const decltype(m_docs)::iterator &it, bool force) { auto doc = it.key(); if (it != m_docs.end() && it->server) { if (it->movingInterface) { it->version = it->movingInterface->revision(); } else if (it->modified) { ++it->version; } if (!m_incrementalSync) { it->changes.clear(); } if (it->open) { if (it->modified || force) { (it->server)->didChange(it->url, it->version, (it->changes.empty()) ? doc->text() : QString(), it->changes); } } else { (it->server)->didOpen(it->url, it->version, languageId(doc->highlightingMode()), doc->text()); it->open = true; } it->modified = false; it->changes.clear(); } } void update(KTextEditor::Document *doc, bool force) override { update(m_docs.find(doc), force); } void update(LSPClientServer *server, bool force) { for (auto it = m_docs.begin(); it != m_docs.end(); ++it) { if (it->server == server) { update(it, force); } } } void onTextChanged(KTextEditor::Document *doc) { auto it = m_docs.find(doc); if (it != m_docs.end()) { it->modified = true; } } DocumentInfo *getDocumentInfo(KTextEditor::Document *doc) { if (!m_incrementalSync) return nullptr; auto it = m_docs.find(doc); if (it != m_docs.end() && it->server) { const auto &caps = it->server->capabilities(); if (caps.textDocumentSync == LSPDocumentSyncKind::Incremental) { return &(*it); } } return nullptr; } void onTextInserted(KTextEditor::Document *doc, const KTextEditor::Cursor &position, const QString &text) { auto info = getDocumentInfo(doc); if (info) { info->changes.push_back({LSPRange {position, position}, text}); } } void onTextRemoved(KTextEditor::Document *doc, const KTextEditor::Range &range, const QString &text) { (void)text; auto info = getDocumentInfo(doc); if (info) { info->changes.push_back({range, QString()}); } } void onLineWrapped(KTextEditor::Document *doc, const KTextEditor::Cursor &position) { // so a 'newline' has been inserted at position // could have been UNIX style or other kind, let's ask the document auto text = doc->text({position, {position.line() + 1, 0}}); onTextInserted(doc, position, text); } void onLineUnwrapped(KTextEditor::Document *doc, int line) { // lines line-1 and line got replaced by current content of line-1 Q_ASSERT(line > 0); auto info = getDocumentInfo(doc); if (info) { LSPRange oldrange {{line - 1, 0}, {line + 1, 0}}; LSPRange newrange {{line - 1, 0}, {line, 0}}; auto text = doc->text(newrange); info->changes.push_back({oldrange, text}); } } }; QSharedPointer LSPClientServerManager::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) { return QSharedPointer(new LSPClientServerManagerImpl(plugin, mainWin)); } #include "lspclientservermanager.moc" diff --git a/addons/lspclient/lspclientservermanager.h b/addons/lspclient/lspclientservermanager.h index 1cca7c6e8..c99eb600a 100644 --- a/addons/lspclient/lspclientservermanager.h +++ b/addons/lspclient/lspclientservermanager.h @@ -1,91 +1,91 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef LSPCLIENTSERVERMANAGER_H #define LSPCLIENTSERVERMANAGER_H -#include "lspclientserver.h" #include "lspclientplugin.h" +#include "lspclientserver.h" #include namespace KTextEditor { class MainWindow; class Document; class View; class MovingInterface; } class LSPClientRevisionSnapshot; /* * A helper class that manages LSP servers in relation to a KTextDocument. * That is, spin up a server for a document when so requested, and then * monitor when the server is up (or goes down) and the document (for changes). * Those changes may then be synced to the server when so requested (e.g. prior * to another component performing an LSP request for a document). * So, other than managing servers, it also manages the document-server * relationship (and document), what's in a name ... */ class LSPClientServerManager : public QObject { Q_OBJECT public: // factory method; private implementation by interface static QSharedPointer new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin); virtual QSharedPointer findServer(KTextEditor::Document *document, bool updatedoc = true) = 0; virtual QSharedPointer findServer(KTextEditor::View *view, bool updatedoc = true) = 0; virtual void update(KTextEditor::Document *doc, bool force) = 0; virtual void restart(LSPClientServer *server) = 0; virtual void setIncrementalSync(bool inc) = 0; // latest sync'ed revision of doc (-1 if N/A) virtual qint64 revision(KTextEditor::Document *doc) = 0; // lock all relevant documents' current revision and sync that to server // locks are released when returned snapshot is delete'd virtual LSPClientRevisionSnapshot *snapshot(LSPClientServer *server) = 0; public: Q_SIGNALS: void serverChanged(); }; class LSPClientRevisionSnapshot : public QObject { Q_OBJECT public: // find a locked revision for url in snapshot virtual void find(const QUrl &url, KTextEditor::MovingInterface *&miface, qint64 &revision) const = 0; }; #endif diff --git a/addons/lspclient/lspclientsymbolview.cpp b/addons/lspclient/lspclientsymbolview.cpp index 426d6e593..56675b495 100644 --- a/addons/lspclient/lspclientsymbolview.cpp +++ b/addons/lspclient/lspclientsymbolview.cpp @@ -1,536 +1,536 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Copyright (C) 2019 Christoph Cullmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "lspclientsymbolview.h" #include #include #include #include #include #include #include -#include #include -#include #include #include +#include +#include #include #include class LSPClientViewTrackerImpl : public LSPClientViewTracker { Q_OBJECT typedef LSPClientViewTrackerImpl self_type; LSPClientPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; // timers to delay some todo's QTimer m_changeTimer; int m_change; QTimer m_motionTimer; int m_motion; int m_oldCursorLine = -1; public: LSPClientViewTrackerImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, int change_ms, int motion_ms) : m_plugin(plugin) , m_mainWindow(mainWin) , m_change(change_ms) , m_motion(motion_ms) { // get updated m_changeTimer.setSingleShot(true); auto ch = [this]() { emit newState(m_mainWindow->activeView(), TextChanged); }; connect(&m_changeTimer, &QTimer::timeout, this, ch); m_motionTimer.setSingleShot(true); auto mh = [this]() { emit newState(m_mainWindow->activeView(), LineChanged); }; connect(&m_motionTimer, &QTimer::timeout, this, mh); // track views connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::viewChanged); } void viewChanged(KTextEditor::View *view) { m_motionTimer.stop(); m_changeTimer.stop(); if (view) { if (m_motion) { connect(view, &KTextEditor::View::cursorPositionChanged, this, &self_type::cursorPositionChanged, Qt::UniqueConnection); } if (m_change > 0 && view->document()) { connect(view->document(), &KTextEditor::Document::textChanged, this, &self_type::textChanged, Qt::UniqueConnection); } emit newState(view, ViewChanged); m_oldCursorLine = view->cursorPosition().line(); } } void textChanged() { m_motionTimer.stop(); m_changeTimer.start(m_change); } void cursorPositionChanged(KTextEditor::View *view, const KTextEditor::Cursor &newPosition) { if (m_changeTimer.isActive()) { // change trumps motion return; } if (view && newPosition.line() != m_oldCursorLine) { m_oldCursorLine = newPosition.line(); m_motionTimer.start(m_motion); } } }; LSPClientViewTracker *LSPClientViewTracker::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, int change_ms, int motion_ms) { return new LSPClientViewTrackerImpl(plugin, mainWin, change_ms, motion_ms); } /* * Instantiates and manages the symbol outline toolview. */ class LSPClientSymbolViewImpl : public QObject, public LSPClientSymbolView { Q_OBJECT typedef LSPClientSymbolViewImpl self_type; LSPClientPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; QSharedPointer m_serverManager; QScopedPointer m_toolview; // parent ownership QPointer m_symbols; QPointer m_filter; QScopedPointer m_popup; // initialized/updated from plugin settings // managed by context menu later on // parent ownership QAction *m_detailsOn; QAction *m_expandOn; QAction *m_treeOn; QAction *m_sortOn; // view tracking QScopedPointer m_viewTracker; // outstanding request LSPClientServer::RequestHandle m_handle; // cached outline models struct ModelData { KTextEditor::Document *document; qint64 revision; std::shared_ptr model; }; QList m_models; // max number to cache static constexpr int MAX_MODELS = 10; // last outline model we constructed std::shared_ptr m_outline; // filter model, setup once KRecursiveFilterProxyModel m_filterModel; // cached icons for model const QIcon m_icon_pkg = QIcon::fromTheme(QStringLiteral("code-block")); const QIcon m_icon_class = QIcon::fromTheme(QStringLiteral("code-class")); const QIcon m_icon_typedef = QIcon::fromTheme(QStringLiteral("code-typedef")); const QIcon m_icon_function = QIcon::fromTheme(QStringLiteral("code-function")); const QIcon m_icon_var = QIcon::fromTheme(QStringLiteral("code-variable")); public: LSPClientSymbolViewImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, QSharedPointer manager) : m_plugin(plugin) , m_mainWindow(mainWin) , m_serverManager(std::move(manager)) , m_outline(new QStandardItemModel()) { m_toolview.reset(m_mainWindow->createToolView(plugin, QStringLiteral("lspclient_symbol_outline"), KTextEditor::MainWindow::Right, QIcon::fromTheme(QStringLiteral("code-context")), i18n("LSP Client Symbol Outline"))); m_symbols = new QTreeView(m_toolview.data()); m_symbols->setFocusPolicy(Qt::NoFocus); m_symbols->setLayoutDirection(Qt::LeftToRight); m_toolview->layout()->setContentsMargins(0, 0, 0, 0); m_toolview->layout()->addWidget(m_symbols); m_toolview->layout()->setSpacing(0); // setup filter line edit m_filter = new KLineEdit(m_toolview.data()); m_toolview->layout()->addWidget(m_filter); m_filter->setPlaceholderText(i18n("Filter...")); m_filter->setClearButtonEnabled(true); connect(m_filter, &KLineEdit::textChanged, this, &self_type::filterTextChanged); m_symbols->setContextMenuPolicy(Qt::CustomContextMenu); m_symbols->setIndentation(10); m_symbols->setEditTriggers(QAbstractItemView::NoEditTriggers); m_symbols->setAllColumnsShowFocus(true); // init filter model once, later we only swap the source model! QItemSelectionModel *m = m_symbols->selectionModel(); m_filterModel.setFilterCaseSensitivity(Qt::CaseInsensitive); m_filterModel.setSortCaseSensitivity(Qt::CaseInsensitive); m_filterModel.setSourceModel(m_outline.get()); m_symbols->setModel(&m_filterModel); delete m; connect(m_symbols, &QTreeView::customContextMenuRequested, this, &self_type::showContextMenu); connect(m_symbols, &QTreeView::activated, this, &self_type::goToSymbol); connect(m_symbols, &QTreeView::clicked, this, &self_type::goToSymbol); // context menu m_popup.reset(new QMenu(m_symbols)); m_treeOn = m_popup->addAction(i18n("Tree Mode"), this, &self_type::displayOptionChanged); m_treeOn->setCheckable(true); m_expandOn = m_popup->addAction(i18n("Automatically Expand Tree"), this, &self_type::displayOptionChanged); m_expandOn->setCheckable(true); m_sortOn = m_popup->addAction(i18n("Sort Alphabetically"), this, &self_type::displayOptionChanged); m_sortOn->setCheckable(true); m_detailsOn = m_popup->addAction(i18n("Show Details"), this, &self_type::displayOptionChanged); m_detailsOn->setCheckable(true); m_popup->addSeparator(); m_popup->addAction(i18n("Expand All"), m_symbols.data(), &QTreeView::expandAll); m_popup->addAction(i18n("Collapse All"), m_symbols.data(), &QTreeView::collapseAll); // sync with plugin settings if updated connect(m_plugin, &LSPClientPlugin::update, this, &self_type::configUpdated); // get updated m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 500, 100)); connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState); connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, [this]() { refresh(false); }); // limit cached models; will not go beyond capacity set here m_models.reserve(MAX_MODELS + 1); // initial trigger of symbols view update configUpdated(); } void displayOptionChanged() { m_expandOn->setEnabled(m_treeOn->isChecked()); refresh(false); } void configUpdated() { m_treeOn->setChecked(m_plugin->m_symbolTree); m_detailsOn->setChecked(m_plugin->m_symbolDetails); m_expandOn->setChecked(m_plugin->m_symbolExpand); m_sortOn->setChecked(m_plugin->m_symbolSort); displayOptionChanged(); } void showContextMenu(const QPoint &) { m_popup->popup(QCursor::pos(), m_treeOn); } void onViewState(KTextEditor::View *, LSPClientViewTracker::State newState) { switch (newState) { case LSPClientViewTracker::ViewChanged: refresh(true); break; case LSPClientViewTracker::TextChanged: refresh(false); break; case LSPClientViewTracker::LineChanged: updateCurrentTreeItem(); break; } } void makeNodes(const QList &symbols, bool tree, bool show_detail, QStandardItemModel *model, QStandardItem *parent, bool &details) { const QIcon *icon = nullptr; for (const auto &symbol : symbols) { switch (symbol.kind) { case LSPSymbolKind::File: case LSPSymbolKind::Module: case LSPSymbolKind::Namespace: case LSPSymbolKind::Package: if (symbol.children.count() == 0) continue; icon = &m_icon_pkg; break; case LSPSymbolKind::Class: case LSPSymbolKind::Interface: icon = &m_icon_class; break; case LSPSymbolKind::Enum: icon = &m_icon_typedef; break; case LSPSymbolKind::Method: case LSPSymbolKind::Function: case LSPSymbolKind::Constructor: icon = &m_icon_function; break; // all others considered/assumed Variable case LSPSymbolKind::Variable: case LSPSymbolKind::Constant: case LSPSymbolKind::String: case LSPSymbolKind::Number: case LSPSymbolKind::Property: case LSPSymbolKind::Field: default: // skip local variable // property, field, etc unlikely in such case anyway if (parent && parent->icon().cacheKey() == m_icon_function.cacheKey()) continue; icon = &m_icon_var; } auto node = new QStandardItem(); if (parent && tree) parent->appendRow(node); else model->appendRow(node); if (!symbol.detail.isEmpty()) details = true; auto detail = show_detail ? symbol.detail : QString(); node->setText(symbol.name + detail); node->setIcon(*icon); node->setData(QVariant::fromValue(symbol.range), Qt::UserRole); // recurse children makeNodes(symbol.children, tree, show_detail, model, node, details); } } void onDocumentSymbols(const QList &outline) { onDocumentSymbolsOrProblem(outline, QString(), true); } void onDocumentSymbolsOrProblem(const QList &outline, const QString &problem = QString(), bool cache = false) { if (!m_symbols) return; // construct new model for data auto newModel = std::make_shared(); // if we have some problem, just report that, else construct model bool details = false; if (problem.isEmpty()) { makeNodes(outline, m_treeOn->isChecked(), m_detailsOn->isChecked(), newModel.get(), nullptr, details); if (cache) { // last request has been placed at head of model list Q_ASSERT(!m_models.isEmpty()); m_models[0].model = newModel; } } else { newModel->appendRow(new QStandardItem(problem)); } // cache detail info with model newModel->invisibleRootItem()->setData(details); // fixup headers QStringList headers {i18n("Symbols")}; newModel->setHorizontalHeaderLabels(headers); setModel(newModel); } void setModel(const std::shared_ptr &newModel) { Q_ASSERT(newModel); // update filter model, do this before the assignment below deletes the old model! m_filterModel.setSourceModel(newModel.get()); // delete old outline if there, keep our new one alive m_outline = newModel; // fixup sorting if (m_sortOn->isChecked()) { m_symbols->setSortingEnabled(true); m_symbols->sortByColumn(0, Qt::AscendingOrder); } else { m_symbols->sortByColumn(-1, Qt::AscendingOrder); } // handle auto-expansion if (m_expandOn->isChecked()) { m_symbols->expandAll(); } // recover detail info from model data bool details = newModel->invisibleRootItem()->data().toBool(); // disable detail setting if no such info available // (as an indication there is nothing to show anyway) m_detailsOn->setEnabled(details); // hide detail column if not needed/wanted bool showDetails = m_detailsOn->isChecked() && details; m_symbols->setColumnHidden(1, !showDetails); // current item tracking updateCurrentTreeItem(); } void refresh(bool clear) { // cancel old request! m_handle.cancel(); // check if we have some server for the current view => trigger request auto view = m_mainWindow->activeView(); if (auto server = m_serverManager->findServer(view)) { // clear current model in any case // this avoids that we show stuff not matching the current view // but let's only do it if needed, e.g. when changing view // so as to avoid unhealthy flickering in other cases if (clear) { onDocumentSymbolsOrProblem(QList(), QString(), false); } // check (valid) cache auto doc = view->document(); auto revision = m_serverManager->revision(doc); auto it = m_models.begin(); for (; it != m_models.end(); ++it) { if (it->document == doc) { break; } } if (it != m_models.end()) { // move to most recently used head m_models.move(it - m_models.begin(), 0); auto &model = m_models.front(); // re-use if possible if (revision == model.revision && model.model) { setModel(model.model); return; } it->revision = revision; } else { m_models.insert(0, {doc, revision, nullptr}); if (m_models.size() > MAX_MODELS) { m_models.pop_back(); } } server->documentSymbols(view->document()->url(), this, utils::mem_fun(&self_type::onDocumentSymbols, this)); return; } // else: inform that no server is there onDocumentSymbolsOrProblem(QList(), i18n("No LSP server for this document.")); } QStandardItem *getCurrentItem(QStandardItem *item, int line) { // first traverse the child items to have deepest match! // only do this if our stuff is expanded if (item == m_outline->invisibleRootItem() || m_symbols->isExpanded(m_filterModel.mapFromSource(m_outline->indexFromItem(item)))) { for (int i = 0; i < item->rowCount(); i++) { if (auto citem = getCurrentItem(item->child(i), line)) { return citem; } } } // does the line match our item? return item->data(Qt::UserRole).value().overlapsLine(line) ? item : nullptr; } void updateCurrentTreeItem() { KTextEditor::View *editView = m_mainWindow->activeView(); if (!editView || !m_symbols) { return; } /** * get item if any */ QStandardItem *item = getCurrentItem(m_outline->invisibleRootItem(), editView->cursorPositionVirtual().line()); if (!item) { return; } /** * select it */ QModelIndex index = m_filterModel.mapFromSource(m_outline->indexFromItem(item)); m_symbols->scrollTo(index); m_symbols->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Clear | QItemSelectionModel::Select); } void goToSymbol(const QModelIndex &index) { KTextEditor::View *kv = m_mainWindow->activeView(); const auto range = index.data(Qt::UserRole).value(); if (kv && range.isValid()) { kv->setCursorPosition(range.start()); } } private Q_SLOTS: /** * React on filter change * @param filterText new filter text */ void filterTextChanged(const QString &filterText) { if (!m_symbols) { return; } /** * filter */ m_filterModel.setFilterFixedString(filterText); /** * expand */ if (!filterText.isEmpty()) { QTimer::singleShot(100, m_symbols, &QTreeView::expandAll); } } }; QObject *LSPClientSymbolView::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, QSharedPointer manager) { return new LSPClientSymbolViewImpl(plugin, mainWin, std::move(manager)); } #include "lspclientsymbolview.moc" diff --git a/addons/lspclient/tests/lsptestapp.cpp b/addons/lspclient/tests/lsptestapp.cpp index 0350d97ad..2e2f82225 100644 --- a/addons/lspclient/tests/lsptestapp.cpp +++ b/addons/lspclient/tests/lsptestapp.cpp @@ -1,128 +1,128 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "../lspclientserver.h" #include #include #include #include -#include #include +#include int main(int argc, char **argv) { if (argc < 5) return -1; LSPClientServer lsp(QString::fromLatin1(argv[1]).split(QLatin1Char(' ')), QUrl(QString::fromLatin1(argv[2]))); QCoreApplication app(argc, argv); QEventLoop q; auto state_h = [&lsp, &q]() { if (lsp.state() == LSPClientServer::State::Running) q.quit(); }; auto conn = QObject::connect(&lsp, &LSPClientServer::stateChanged, state_h); lsp.start(); q.exec(); QObject::disconnect(conn); auto diagnostics_h = [](const LSPPublishDiagnosticsParams &diag) { std::cout << "diagnostics " << diag.uri.path().toUtf8().toStdString() << " count: " << diag.diagnostics.length(); }; QObject::connect(&lsp, &LSPClientServer::publishDiagnostics, diagnostics_h); auto document = QUrl(QString::fromLatin1(argv[3])); QFile file(document.path()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return -1; QTextStream in(&file); QString content = in.readAll(); lsp.didOpen(document, 0, QString(), content); auto ds_h = [&q](const QList &syms) { std::cout << "symbol count: " << syms.length() << std::endl; q.quit(); }; lsp.documentSymbols(document, &app, ds_h); q.exec(); auto position = QString::fromLatin1(argv[4]).split(QLatin1Char(' ')); auto def_h = [&q](const QList &defs) { std::cout << "definition count: " << defs.length() << std::endl; q.quit(); }; lsp.documentDefinition(document, {position[0].toInt(), position[1].toInt()}, &app, def_h); q.exec(); auto comp_h = [&q](const QList &completions) { std::cout << "completion count: " << completions.length() << std::endl; q.quit(); }; lsp.documentCompletion(document, {position[0].toInt(), position[1].toInt()}, &app, comp_h); q.exec(); auto sig_h = [&q](const LSPSignatureHelp &help) { std::cout << "signature help count: " << help.signatures.length() << std::endl; q.quit(); }; lsp.signatureHelp(document, {position[0].toInt(), position[1].toInt()}, &app, sig_h); q.exec(); auto hover_h = [&q](const LSPHover &hover) { for (auto &element : hover.contents) { std::cout << "hover: " << element.value.toStdString() << std::endl; } q.quit(); }; lsp.documentHover(document, {position[0].toInt(), position[1].toInt()}, &app, hover_h); q.exec(); auto ref_h = [&q](const QList &refs) { std::cout << "refs: " << refs.length() << std::endl; q.quit(); }; lsp.documentReferences(document, {position[0].toInt(), position[1].toInt()}, true, &app, ref_h); q.exec(); auto hl_h = [&q](const QList &hls) { std::cout << "highlights: " << hls.length() << std::endl; q.quit(); }; lsp.documentHighlight(document, {position[0].toInt(), position[1].toInt()}, &app, hl_h); q.exec(); auto fmt_h = [&q](const QList &edits) { std::cout << "edits: " << edits.length() << std::endl; q.quit(); }; lsp.documentFormatting(document, {2, true, QJsonObject()}, &app, fmt_h); q.exec(); // lsp.didOpen(document, 0, QStringLiteral("blah")); lsp.didChange(document, 1, QStringLiteral("foo")); lsp.didClose(document); } diff --git a/addons/openheader/plugin_kateopenheader.cpp b/addons/openheader/plugin_kateopenheader.cpp index b7693dd3a..cf8bc6f83 100644 --- a/addons/openheader/plugin_kateopenheader.cpp +++ b/addons/openheader/plugin_kateopenheader.cpp @@ -1,233 +1,233 @@ /* This file is part of the KDE project Copyright (C) 2001 Joseph Wenninger Copyright (C) 2009 Erlend Hamberg 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. */ #include "plugin_kateopenheader.h" +#include +#include #include #include -#include -#include +#include +#include +#include +#include +#include #include -#include -#include #include -#include -#include #include -#include -#include -#include -#include +#include +#include +#include K_PLUGIN_FACTORY_WITH_JSON(KateOpenHeaderFactory, "kateopenheaderplugin.json", registerPlugin();) // K_EXPORT_PLUGIN(KateOpenHeaderFactory(KAboutData("kateopenheader","kateopenheader",ki18n("Open Header"), "0.1", ki18n("Open header for a source file"), KAboutData::License_LGPL_V2)) ) PluginViewKateOpenHeader::PluginViewKateOpenHeader(PluginKateOpenHeader *plugin, KTextEditor::MainWindow *mainwindow) : KTextEditor::Command(QStringList() << QStringLiteral("toggle-header"), mainwindow) , KXMLGUIClient() , m_plugin(plugin) , m_mainWindow(mainwindow) { KXMLGUIClient::setComponentName(QStringLiteral("kateopenheaderplugin"), i18n("Open Header")); setXMLFile(QStringLiteral("ui.rc")); QAction *a = actionCollection()->addAction(QStringLiteral("file_openheader")); a->setText(i18n("Open .h/.cpp/.c")); actionCollection()->setDefaultShortcut(a, Qt::Key_F12); connect(a, &QAction::triggered, plugin, &PluginKateOpenHeader::slotOpenHeader); mainwindow->guiFactory()->addClient(this); } PluginViewKateOpenHeader::~PluginViewKateOpenHeader() { m_mainWindow->guiFactory()->removeClient(this); } PluginKateOpenHeader::PluginKateOpenHeader(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { } PluginKateOpenHeader::~PluginKateOpenHeader() { } QObject *PluginKateOpenHeader::createView(KTextEditor::MainWindow *mainWindow) { return new PluginViewKateOpenHeader(this, mainWindow); } void PluginKateOpenHeader::slotOpenHeader() { KTextEditor::Application *application = KTextEditor::Editor::instance()->application(); if (!application->activeMainWindow()) return; KTextEditor::View *kv(application->activeMainWindow()->activeView()); if (!kv) return; QUrl url = kv->document()->url(); if ((!url.isValid()) || (url.isEmpty())) return; qDebug() << "Trying to open opposite of " << url.toString(); qDebug() << "Trying to open opposite of toLocalFile:" << url.toLocalFile(); qDebug() << "Trying to open opposite of path:" << url.path(); QFileInfo info(url.path()); QString extension = info.suffix().toLower(); QStringList headers(QStringList() << QStringLiteral("h") << QStringLiteral("H") << QStringLiteral("hh") << QStringLiteral("hpp") << QStringLiteral("cuh")); QStringList sources(QStringList() << QStringLiteral("c") << QStringLiteral("cpp") << QStringLiteral("cc") << QStringLiteral("cp") << QStringLiteral("cxx") << QStringLiteral("m") << QStringLiteral("cu")); if (sources.contains(extension)) { if (tryOpenInternal(url, headers)) return; tryOpen(url, headers); } else if (headers.contains(extension)) { if (tryOpenInternal(url, sources)) return; tryOpen(url, sources); } } bool PluginKateOpenHeader::tryOpenInternal(const QUrl &url, const QStringList &extensions) { KTextEditor::Application *application = KTextEditor::Editor::instance()->application(); if (!application->activeMainWindow()) return false; qDebug() << "Trying to find already opened" << url.toString() << " with extensions " << extensions.join(QLatin1Char(' ')); QString basename = QFileInfo(url.path()).baseName(); QUrl newURL(url); for (const auto &extension : extensions) { setFileName(&newURL, basename + QStringLiteral(".") + extension); KTextEditor::Document *doc = application->findUrl(newURL); if (doc) { application->activeMainWindow()->openUrl(newURL); return true; } setFileName(&newURL, basename + QStringLiteral(".") + extension.toUpper()); doc = application->findUrl(newURL); if (doc) { application->activeMainWindow()->openUrl(newURL); return true; } } return false; } void PluginKateOpenHeader::tryOpen(const QUrl &url, const QStringList &extensions) { KTextEditor::Application *application = KTextEditor::Editor::instance()->application(); if (!application->activeMainWindow()) return; qDebug() << "Trying to open " << url.toString() << " with extensions " << extensions.join(QLatin1Char(' ')); QString basename = QFileInfo(url.path()).baseName(); QUrl newURL(url); for (const auto &extension : extensions) { setFileName(&newURL, basename + QStringLiteral(".") + extension); if (fileExists(newURL)) { application->activeMainWindow()->openUrl(newURL); return; } setFileName(&newURL, basename + QStringLiteral(".") + extension.toUpper()); if (fileExists(newURL)) { application->activeMainWindow()->openUrl(newURL); return; } } } bool PluginKateOpenHeader::fileExists(const QUrl &url) { if (url.isLocalFile()) { return QFile::exists(url.toLocalFile()); } KIO::JobFlags flags = KIO::DefaultFlags; KIO::StatJob *job = KIO::stat(url, flags); KJobWidgets::setWindow(job, KTextEditor::Editor::instance()->application()->activeMainWindow()->window()); job->setSide(KIO::StatJob::DestinationSide /*SourceSide*/); job->exec(); return !job->error(); } void PluginKateOpenHeader::setFileName(QUrl *url, const QString &_txt) { url->setFragment(QString()); int i = 0; while (i < _txt.length() && _txt[i] == QLatin1Char('/')) { ++i; } QString tmp = i ? _txt.mid(i) : _txt; QString path = url->path(); if (path.isEmpty()) #ifdef Q_OS_WIN path = url->isLocalFile() ? QDir::rootPath() : QStringLiteral("/"); #else path = QDir::rootPath(); #endif else { int lastSlash = path.lastIndexOf(QLatin1Char('/')); if (lastSlash == -1) { path.clear(); // there's only the file name, remove it } else if (!path.endsWith(QLatin1Char('/'))) { path.truncate(lastSlash + 1); // keep the "/" } } path += tmp; url->setPath(path); } bool PluginViewKateOpenHeader::exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &) { Q_UNUSED(view) Q_UNUSED(cmd) Q_UNUSED(msg) m_plugin->slotOpenHeader(); return true; } bool PluginViewKateOpenHeader::help(KTextEditor::View *view, const QString &cmd, QString &msg) { Q_UNUSED(view) Q_UNUSED(cmd) msg = i18n( "

toggle-header — switch between header and corresponding c/cpp file

" "

usage: toggle-header

" "

When editing C or C++ code, this command will switch between a header file and " "its corresponding C/C++ file or vice versa.

" "

For example, if you are editing myclass.cpp, toggle-header will change " "to myclass.h if this file is available.

" "

Pairs of the following filename suffixes will work:
" " Header files: h, H, hh, hpp
" " Source files: c, cpp, cc, cp, cxx

"); return true; } #include "plugin_kateopenheader.moc" diff --git a/addons/openheader/plugin_kateopenheader.h b/addons/openheader/plugin_kateopenheader.h index b322750c0..fbab643b7 100644 --- a/addons/openheader/plugin_kateopenheader.h +++ b/addons/openheader/plugin_kateopenheader.h @@ -1,66 +1,66 @@ /* This file is part of the KDE project Copyright (C) 2001 Joseph Wenninger Copyright (C) 2009 Erlend Hamberg 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. */ #ifndef PLUGIN_KATEOPENHEADER_H #define PLUGIN_KATEOPENHEADER_H -#include -#include #include #include -#include #include #include +#include +#include +#include class PluginKateOpenHeader : public KTextEditor::Plugin { Q_OBJECT public: explicit PluginKateOpenHeader(QObject *parent = nullptr, const QList & = QList()); ~PluginKateOpenHeader() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; public Q_SLOTS: void slotOpenHeader(); void tryOpen(const QUrl &url, const QStringList &extensions); bool tryOpenInternal(const QUrl &url, const QStringList &extensions); private: bool fileExists(const QUrl &url); void setFileName(QUrl *url, const QString &_txt); }; class PluginViewKateOpenHeader : public KTextEditor::Command, public KXMLGUIClient { Q_OBJECT public: PluginViewKateOpenHeader(PluginKateOpenHeader *plugin, KTextEditor::MainWindow *mainwindow); ~PluginViewKateOpenHeader() override; bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) override; bool help(KTextEditor::View *view, const QString &cmd, QString &msg) override; private: PluginKateOpenHeader *m_plugin; KTextEditor::MainWindow *m_mainWindow; }; #endif // PLUGIN_KATEOPENHEADER_H diff --git a/addons/preview/kpartview.cpp b/addons/preview/kpartview.cpp index 6a55b7ab0..1c774967a 100644 --- a/addons/preview/kpartview.cpp +++ b/addons/preview/kpartview.cpp @@ -1,227 +1,227 @@ /* * Copyright (C) 2017 by Friedrich W. H. Kossebau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kpartview.h" #include // KF #include #include -#include -#include -#include #include +#include +#include +#include // Qt #include -#include #include +#include using namespace KTextEditorPreview; // 300 ms as initial proposal, was found to be delay which worked for the // author with the use-case of quickly peeking over to the preview while // typing to see if things are working out as intended, without getting a // having-to-wait feeling. // Surely could get some more serious research what is a proper (default) value. // Perhaps the whole update logic cpuld also be overhauled, to only do an // update once there was at least xxx ms idle time to meet the use-case of // quickly-peeking-over. And otherwise update in bigger intervals of // 500-2000(?) ms, to cover the use-case of seeing from the corner of one's // eye that something is changing while one is editing the sources. static const int updateDelay = 300; // ms KPartView::KPartView(const KService::Ptr &service, QObject *parent) : QObject(parent) { QString errorString; m_part = service->createInstance(nullptr, this, QVariantList(), &errorString); if (!m_part) { m_errorLabel = new QLabel(errorString); } else if (!m_part->widget()) { // should not happen, but just be safe delete m_part; m_errorLabel = new QLabel(QStringLiteral("KPart provides no widget.")); } else { m_updateSquashingTimer.setSingleShot(true); m_updateSquashingTimer.setInterval(updateDelay); connect(&m_updateSquashingTimer, &QTimer::timeout, this, &KPartView::updatePreview); auto browserExtension = m_part->browserExtension(); if (browserExtension) { connect(browserExtension, &KParts::BrowserExtension::openUrlRequestDelayed, this, &KPartView::handleOpenUrlRequest); } m_part->widget()->installEventFilter(this); } } KPartView::~KPartView() { delete m_errorLabel; } QWidget *KPartView::widget() const { return m_part ? m_part->widget() : m_errorLabel; } KParts::ReadOnlyPart *KPartView::kPart() const { return m_part; } KTextEditor::Document *KPartView::document() const { return m_document; } bool KPartView::isAutoUpdating() const { return m_autoUpdating; } void KPartView::setDocument(KTextEditor::Document *document) { if (m_document == document) { return; } if (!m_part) { return; } if (m_document) { disconnect(m_document, &KTextEditor::Document::textChanged, this, &KPartView::triggerUpdatePreview); m_updateSquashingTimer.stop(); } m_document = document; // delete any temporary file, to trigger creation of a new if needed // for some unique url/path of the temporary file for the new document (or use a counter ourselves?) // but see comment for stream url delete m_bufferFile; m_bufferFile = nullptr; if (m_document) { m_previewDirty = true; updatePreview(); connect(m_document, &KTextEditor::Document::textChanged, this, &KPartView::triggerUpdatePreview); } else { m_part->closeUrl(); } } void KPartView::setAutoUpdating(bool autoUpdating) { if (m_autoUpdating == autoUpdating) { return; } m_autoUpdating = autoUpdating; if (m_autoUpdating) { if (m_document && m_part && m_previewDirty) { updatePreview(); } } else { m_updateSquashingTimer.stop(); } } void KPartView::triggerUpdatePreview() { m_previewDirty = true; if (m_part->widget()->isVisible() && m_autoUpdating && !m_updateSquashingTimer.isActive()) { m_updateSquashingTimer.start(); } } void KPartView::updatePreview() { if (!m_part->widget()->isVisible()) { return; } // TODO: some kparts seem to steal the focus after they have loaded a file, sometimes also async // that possibly needs fixing in the respective kparts, as that could be considered non-cooperative // TODO: investigate if pushing of the data to the kpart could be done in a non-gui-thread, // so their loading of the file (e.g. ReadOnlyPart::openFile() is sync design) does not block const auto mimeType = m_document->mimeType(); KParts::OpenUrlArguments arguments; arguments.setMimeType(mimeType); m_part->setArguments(arguments); // try to stream the data to avoid filesystem I/O // create url unique for this document // TODO: encode existing url instead, and for yet-to-be-stored docs some other unique id const QUrl streamUrl(QStringLiteral("ktexteditorpreview:/object/%1").arg(reinterpret_cast(m_document), 0, 16)); if (m_part->openStream(mimeType, streamUrl)) { qCDebug(KTEPREVIEW) << "Pushing data via streaming API, url:" << streamUrl.url(); m_part->writeStream(m_document->text().toUtf8()); m_part->closeStream(); m_previewDirty = false; return; } // have to go via filesystem for now, not nice if (!m_bufferFile) { m_bufferFile = new QTemporaryFile(this); m_bufferFile->open(); } else { // reset position m_bufferFile->seek(0); } const QUrl tempFileUrl(QUrl::fromLocalFile(m_bufferFile->fileName())); qCDebug(KTEPREVIEW) << "Pushing data via temporary file, url:" << tempFileUrl.url(); // write current data m_bufferFile->write(m_document->text().toUtf8()); // truncate at end of new content m_bufferFile->resize(m_bufferFile->pos()); m_bufferFile->flush(); // TODO: find out why we need to send this queued QMetaObject::invokeMethod(m_part, "openUrl", Qt::QueuedConnection, Q_ARG(QUrl, tempFileUrl)); m_previewDirty = false; } void KPartView::handleOpenUrlRequest(const QUrl &url) { QDesktopServices::openUrl(url); } bool KPartView::eventFilter(QObject *object, QEvent *event) { if (object == m_part->widget() && event->type() == QEvent::Show) { if (m_document && m_autoUpdating && m_previewDirty) { updatePreview(); } return true; } return QObject::eventFilter(object, event); } diff --git a/addons/preview/ktexteditorpreviewplugin.cpp b/addons/preview/ktexteditorpreviewplugin.cpp index 61df84798..b4aa0c3a8 100644 --- a/addons/preview/ktexteditorpreviewplugin.cpp +++ b/addons/preview/ktexteditorpreviewplugin.cpp @@ -1,44 +1,44 @@ /* * Copyright (C) 2017 by Friedrich W. H. Kossebau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "ktexteditorpreviewplugin.h" #include "ktexteditorpreviewview.h" #include // KF -#include #include +#include K_PLUGIN_FACTORY_WITH_JSON(KTextEditorPreviewPluginFactory, "ktexteditorpreview.json", registerPlugin();) KTextEditorPreviewPlugin::KTextEditorPreviewPlugin(QObject *parent, const QVariantList & /*args*/) : KTextEditor::Plugin(parent) { } KTextEditorPreviewPlugin::~KTextEditorPreviewPlugin() = default; QObject *KTextEditorPreviewPlugin::createView(KTextEditor::MainWindow *mainwindow) { return new KTextEditorPreviewView(this, mainwindow); } // needed for K_PLUGIN_FACTORY_WITH_JSON #include diff --git a/addons/preview/ktexteditorpreviewview.cpp b/addons/preview/ktexteditorpreviewview.cpp index c31dee826..8a4f2b8e3 100644 --- a/addons/preview/ktexteditorpreviewview.cpp +++ b/addons/preview/ktexteditorpreviewview.cpp @@ -1,62 +1,62 @@ /* * Copyright (C) 2017 by Friedrich W. H. Kossebau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "ktexteditorpreviewview.h" #include "ktexteditorpreviewplugin.h" #include "previewwidget.h" // KF -#include #include +#include // Qt #include #include using namespace KTextEditorPreview; KTextEditorPreviewView::KTextEditorPreviewView(KTextEditorPreviewPlugin *plugin, KTextEditor::MainWindow *mainWindow) : QObject(mainWindow) { Q_UNUSED(plugin); m_toolView = mainWindow->createToolView(plugin, QStringLiteral("ktexteditorpreviewplugin"), KTextEditor::MainWindow::Right, QIcon::fromTheme(QStringLiteral("document-preview")), i18n("Preview")); // add preview widget m_previewView = new PreviewWidget(plugin, mainWindow, m_toolView.data()); m_toolView->layout()->setContentsMargins(0, 0, 0, 0); m_toolView->layout()->addWidget(m_previewView); m_toolView->addActions(m_previewView->actions()); } KTextEditorPreviewView::~KTextEditorPreviewView() { delete m_toolView; } void KTextEditorPreviewView::readSessionConfig(const KConfigGroup &config) { m_previewView->readSessionConfig(config); } void KTextEditorPreviewView::writeSessionConfig(KConfigGroup &config) { m_previewView->writeSessionConfig(config); } diff --git a/addons/preview/previewwidget.cpp b/addons/preview/previewwidget.cpp index 9f9248453..50721551d 100644 --- a/addons/preview/previewwidget.cpp +++ b/addons/preview/previewwidget.cpp @@ -1,373 +1,373 @@ /* * Copyright (C) 2017 by Friedrich W. H. Kossebau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "previewwidget.h" -#include "ktexteditorpreviewplugin.h" #include "kpartview.h" +#include "ktexteditorpreviewplugin.h" #include // KF -#include #include #include +#include -#include -#include -#include +#include +#include #include +#include #include +#include +#include +#include +#include #include -#include -#include -#include #include -#include // Qt -#include -#include -#include #include +#include +#include +#include #include #include -#include +#include using namespace KTextEditorPreview; PreviewWidget::PreviewWidget(KTextEditorPreviewPlugin *core, KTextEditor::MainWindow *mainWindow, QWidget *parent) : QStackedWidget(parent) , KXMLGUIBuilder(this) , m_core(core) , m_mainWindow(mainWindow) , m_xmlGuiFactory(new KXMLGUIFactory(this, this)) { m_lockAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("object-unlocked")), i18n("Lock Current Document"), this); m_lockAction->setToolTip(i18n("Lock preview to current document")); m_lockAction->setCheckedState(KGuiItem(i18n("Unlock Current View"), QIcon::fromTheme(QStringLiteral("object-locked")), i18n("Unlock current view"))); m_lockAction->setChecked(false); connect(m_lockAction, &QAction::triggered, this, &PreviewWidget::toggleDocumentLocking); addAction(m_lockAction); // TODO: better icon(s) const QIcon autoUpdateIcon = QIcon::fromTheme(QStringLiteral("media-playback-start")); m_autoUpdateAction = new KToggleAction(autoUpdateIcon, i18n("Automatically Update Preview"), this); m_autoUpdateAction->setToolTip(i18n("Enable automatic updates of the preview to the current document content")); m_autoUpdateAction->setCheckedState(KGuiItem(i18n("Manually Update Preview"), autoUpdateIcon, i18n("Disable automatic updates of the preview to the current document content"))); m_autoUpdateAction->setChecked(false); connect(m_autoUpdateAction, &QAction::triggered, this, &PreviewWidget::toggleAutoUpdating); addAction(m_autoUpdateAction); m_updateAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Update Preview"), this); m_updateAction->setToolTip(i18n("Update the preview to the current document content")); connect(m_updateAction, &QAction::triggered, this, &PreviewWidget::updatePreview); m_updateAction->setEnabled(false); addAction(m_updateAction); // manually prepare a proper dropdown menu button, because Qt itself does not do what one would expect // when adding a default menu->menuAction() to a QToolbar const auto kPartMenuIcon = QIcon::fromTheme(QStringLiteral("application-menu")); const auto kPartMenuText = i18n("View"); // m_kPartMenu may not be a child of this, because otherwise its XMLGUI-menu is deleted when switching views // and therefore closing the tool view, which is a QMainWindow in KDevelop (IdealController::addView). // see KXMLGUIBuilder::createContainer => tagName == d->tagMenu m_kPartMenu = new QMenu; QToolButton *toolButton = new QToolButton(); toolButton->setMenu(m_kPartMenu); toolButton->setIcon(kPartMenuIcon); toolButton->setText(kPartMenuText); toolButton->setPopupMode(QToolButton::InstantPopup); m_kPartMenuAction = new QWidgetAction(this); m_kPartMenuAction->setIcon(kPartMenuIcon); m_kPartMenuAction->setText(kPartMenuText); m_kPartMenuAction->setMenu(m_kPartMenu); m_kPartMenuAction->setDefaultWidget(toolButton); m_kPartMenuAction->setEnabled(false); addAction(m_kPartMenuAction); m_aboutKPartAction = new QAction(this); connect(m_aboutKPartAction, &QAction::triggered, this, &PreviewWidget::showAboutKPartPlugin); m_aboutKPartAction->setEnabled(false); auto label = new QLabel(i18n("No preview available."), this); label->setAlignment(Qt::AlignHCenter); addWidget(label); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &PreviewWidget::setTextEditorView); setTextEditorView(m_mainWindow->activeView()); } PreviewWidget::~PreviewWidget() { delete m_kPartMenu; } void PreviewWidget::readSessionConfig(const KConfigGroup &configGroup) { // TODO: also store document id/url and see to catch the same document on restoring config m_lockAction->setChecked(configGroup.readEntry("documentLocked", false)); m_autoUpdateAction->setChecked(configGroup.readEntry("automaticUpdate", false)); } void PreviewWidget::writeSessionConfig(KConfigGroup &configGroup) const { configGroup.writeEntry("documentLocked", m_lockAction->isChecked()); configGroup.writeEntry("automaticUpdate", m_autoUpdateAction->isChecked()); } void PreviewWidget::setTextEditorView(KTextEditor::View *view) { if ((view && view == m_previewedTextEditorView && view->document() == m_previewedTextEditorDocument && (!m_previewedTextEditorDocument || m_previewedTextEditorDocument->mode() == m_currentMode)) || !view || !isVisible() || m_lockAction->isChecked()) { return; } m_previewedTextEditorView = view; m_previewedTextEditorDocument = view ? view->document() : nullptr; resetTextEditorView(m_previewedTextEditorDocument); } void PreviewWidget::resetTextEditorView(KTextEditor::Document *document) { if (!isVisible() || m_previewedTextEditorDocument != document) { return; } KService::Ptr service; if (m_previewedTextEditorDocument) { // TODO: mimetype is not set for new documents which have not been saved yet. // Maybe retry to guess as soon as content is inserted. m_currentMode = m_previewedTextEditorDocument->mode(); // Get mimetypes assigned to the currently set mode. auto mimeTypes = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("katemoderc")), m_currentMode).readXdgListEntry("Mimetypes"); // Also try to guess from the content, if the above fails. mimeTypes << m_previewedTextEditorDocument->mimeType(); for (const auto &mimeType : qAsConst(mimeTypes)) { service = KMimeTypeTrader::self()->preferredService(mimeType, QStringLiteral("KParts/ReadOnlyPart")); if (service) { qCDebug(KTEPREVIEW) << "Found preferred kpart service named" << service->name() << "with library" << service->library() << "for mimetype" << mimeType; if (service->library().isEmpty()) { qCWarning(KTEPREVIEW) << "Discarding preferred kpart service due to empty library name:" << service->name(); service.reset(); } // no interest in kparts which also just display the text (like katepart itself) // TODO: what about parts which also support importing plain text and turning into richer format // and thus have it in their mimetypes list? // could that perhaps be solved by introducing the concept of "native" and "imported" mimetypes? // or making a distinction between source editors/viewers and final editors/viewers? // latter would also help other source editors/viewers like a hexeditor, which "supports" any mimetype if (service && service->mimeTypes().contains(QLatin1String("text/plain"))) { qCDebug(KTEPREVIEW) << "Blindly discarding preferred service as it also supports text/plain, to avoid useless plain/text preview."; service.reset(); } if (service) { break; } } } if (!service) { qCDebug(KTEPREVIEW) << "Found no preferred kpart service for mimetypes" << mimeTypes; } // Update if the mode is changed. The signal may also be emitted, when a new // url is loaded, therefore wait (QueuedConnection) for the document to load. connect(m_previewedTextEditorDocument, &KTextEditor::Document::modeChanged, this, &PreviewWidget::resetTextEditorView, static_cast(Qt::QueuedConnection | Qt::UniqueConnection)); // Explicitly clear the old document, which otherwise might be accessed in // m_partView->setDocument. connect(m_previewedTextEditorDocument, &KTextEditor::Document::aboutToClose, this, &PreviewWidget::unsetDocument, Qt::UniqueConnection); } else { m_currentMode.clear(); } // change of preview type? // TODO: find a better id than library? const QString serviceId = service ? service->library() : QString(); if (serviceId != m_currentServiceId) { if (m_partView) { clearMenu(); } m_currentServiceId = serviceId; if (service) { qCDebug(KTEPREVIEW) << "Creating new kpart service instance."; m_partView = new KPartView(service, this); const bool autoupdate = m_autoUpdateAction->isChecked(); m_partView->setAutoUpdating(autoupdate); int index = addWidget(m_partView->widget()); setCurrentIndex(index); // update kpart menu const auto kPart = m_partView->kPart(); if (kPart) { m_xmlGuiFactory->addClient(kPart); const auto kPartDisplayName = kPart->componentData().displayName(); m_aboutKPartAction->setText(i18n("About %1", kPartDisplayName)); m_aboutKPartAction->setEnabled(true); m_kPartMenu->addSeparator(); m_kPartMenu->addAction(m_aboutKPartAction); m_kPartMenuAction->setEnabled(true); } m_updateAction->setEnabled(!autoupdate); } else { m_partView = nullptr; } } else if (m_partView) { qCDebug(KTEPREVIEW) << "Reusing active kpart service instance."; } if (m_partView) { m_partView->setDocument(m_previewedTextEditorDocument); } } void PreviewWidget::unsetDocument(KTextEditor::Document *document) { if (!m_partView || m_previewedTextEditorDocument != document) { return; } m_partView->setDocument(nullptr); m_previewedTextEditorDocument = nullptr; // remove any current partview clearMenu(); m_partView = nullptr; m_currentServiceId.clear(); } void PreviewWidget::showEvent(QShowEvent *event) { Q_UNUSED(event); m_updateAction->setEnabled(m_partView && !m_autoUpdateAction->isChecked()); if (m_lockAction->isChecked()) { resetTextEditorView(m_previewedTextEditorDocument); } else { setTextEditorView(m_mainWindow->activeView()); } } void PreviewWidget::hideEvent(QHideEvent *event) { Q_UNUSED(event); // keep active part for reuse, but close preview document // TODO: we also get hide event in kdevelop when the view is changed, // need to find out how to filter this out or how to fix kdevelop // so currently keep the preview document // unsetDocument(m_previewedTextEditorDocument); m_updateAction->setEnabled(false); } void PreviewWidget::toggleDocumentLocking(bool locked) { if (!locked) { setTextEditorView(m_mainWindow->activeView()); } } void PreviewWidget::toggleAutoUpdating(bool autoRefreshing) { if (!m_partView) { // nothing to do return; } m_updateAction->setEnabled(!autoRefreshing && isVisible()); m_partView->setAutoUpdating(autoRefreshing); } void PreviewWidget::updatePreview() { if (m_partView && m_partView->document()) { m_partView->updatePreview(); } } QWidget *PreviewWidget::createContainer(QWidget *parent, int index, const QDomElement &element, QAction *&containerAction) { containerAction = nullptr; if (element.attribute(QStringLiteral("deleted")).toLower() == QLatin1String("true")) { return nullptr; } const QString tagName = element.tagName().toLower(); // filter out things we do not support // TODO: consider integrating the toolbars if (tagName == QLatin1String("mainwindow") || tagName == QLatin1String("toolbar") || tagName == QLatin1String("statusbar")) { return nullptr; } if (tagName == QLatin1String("menubar")) { return m_kPartMenu; } return KXMLGUIBuilder::createContainer(parent, index, element, containerAction); } void PreviewWidget::removeContainer(QWidget *container, QWidget *parent, QDomElement &element, QAction *containerAction) { if (container == m_kPartMenu) { return; } KXMLGUIBuilder::removeContainer(container, parent, element, containerAction); } void PreviewWidget::showAboutKPartPlugin() { if (m_partView && m_partView->kPart()) { QPointer aboutDialog = new KAboutApplicationDialog(m_partView->kPart()->componentData(), this); aboutDialog->exec(); delete aboutDialog; } } void PreviewWidget::clearMenu() { // clear kpart menu m_xmlGuiFactory->removeClient(m_partView->kPart()); m_kPartMenu->clear(); removeWidget(m_partView->widget()); delete m_partView; m_updateAction->setEnabled(false); m_kPartMenuAction->setEnabled(false); m_aboutKPartAction->setEnabled(false); } diff --git a/addons/project/kateproject.cpp b/addons/project/kateproject.cpp index d1ace8481..8930ebdc0 100644 --- a/addons/project/kateproject.cpp +++ b/addons/project/kateproject.cpp @@ -1,451 +1,451 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateproject.h" -#include "kateprojectworker.h" #include "kateprojectplugin.h" +#include "kateprojectworker.h" #include #include #include #include #include #include -#include +#include #include -#include #include -#include +#include +#include #include KateProject::KateProject(ThreadWeaver::Queue *weaver, KateProjectPlugin *plugin) : QObject() , m_fileLastModified() , m_notesDocument(nullptr) , m_untrackedDocumentsRoot(nullptr) , m_weaver(weaver) , m_plugin(plugin) { } KateProject::~KateProject() { saveNotesDocument(); } bool KateProject::loadFromFile(const QString &fileName) { /** * bail out if already fileName set! */ if (!m_fileName.isEmpty()) { return false; } /** * set new filename and base directory */ m_fileName = fileName; m_baseDir = QFileInfo(m_fileName).canonicalPath(); /** * trigger reload */ return reload(); } bool KateProject::reload(bool force) { QVariantMap map = readProjectFile(); if (map.isEmpty()) { m_fileLastModified = QDateTime(); } else { m_fileLastModified = QFileInfo(m_fileName).lastModified(); m_globalProject = map; } return load(m_globalProject, force); } QVariantMap KateProject::readProjectFile() const { QFile file(m_fileName); if (!file.open(QFile::ReadOnly)) { return QVariantMap(); } /** * parse the whole file, bail out again on error! */ const QByteArray jsonData = file.readAll(); QJsonParseError parseError; QJsonDocument project(QJsonDocument::fromJson(jsonData, &parseError)); if (parseError.error != QJsonParseError::NoError) { return QVariantMap(); } /** * convenience; align with auto-generated projects * generate 'name' and 'files' if not specified explicitly, * so those parts need not be given if only wishes to specify additional * project configuration (e.g. build, ctags) */ if (project.isObject()) { auto dir = QFileInfo(m_fileName).dir(); auto object = project.object(); auto name = object[QStringLiteral("name")]; if (name.isUndefined() || name.isNull()) { name = dir.dirName(); } auto files = object[QStringLiteral("files")]; if (files.isUndefined() || files.isNull()) { // support all we can, could try to detect, // but it will be sorted out upon loading anyway QJsonArray afiles; for (const auto &t : {QStringLiteral("git"), QStringLiteral("hg"), QStringLiteral("svn"), QStringLiteral("darcs")}) { afiles.push_back(QJsonObject {{t, true}}); } files = afiles; } project.setObject(object); } return project.toVariant().toMap(); } bool KateProject::loadFromData(const QVariantMap &globalProject, const QString &directory) { m_baseDir = directory; m_fileName = QDir(directory).filePath(QStringLiteral(".kateproject")); m_globalProject = globalProject; return load(globalProject); } bool KateProject::load(const QVariantMap &globalProject, bool force) { /** * no name, bad => bail out */ if (globalProject[QStringLiteral("name")].toString().isEmpty()) { return false; } /** * support out-of-source project files */ if (!globalProject[QStringLiteral("directory")].toString().isEmpty()) { m_baseDir = QFileInfo(globalProject[QStringLiteral("directory")].toString()).canonicalFilePath(); } /** * anything changed? * else be done without forced reload! */ if (!force && (m_projectMap == globalProject)) { return true; } /** * setup global attributes in this object */ m_projectMap = globalProject; // emit that we changed stuff emit projectMapChanged(); // trigger loading of project in background thread QString indexDir; if (m_plugin->getIndexEnabled()) { indexDir = m_plugin->getIndexDirectory().toLocalFile(); // if empty, use regular tempdir if (indexDir.isEmpty()) { indexDir = QDir::tempPath(); } } auto w = new KateProjectWorker(m_baseDir, indexDir, m_projectMap, force); connect(w, &KateProjectWorker::loadDone, this, &KateProject::loadProjectDone); connect(w, &KateProjectWorker::loadIndexDone, this, &KateProject::loadIndexDone); m_weaver->stream() << w; // we are done here return true; } void KateProject::loadProjectDone(const KateProjectSharedQStandardItem &topLevel, KateProjectSharedQMapStringItem file2Item) { m_model.clear(); m_model.invisibleRootItem()->appendColumn(topLevel->takeColumn(0)); m_file2Item = std::move(file2Item); /** * readd the documents that are open atm */ m_untrackedDocumentsRoot = nullptr; for (auto i = m_documents.constBegin(); i != m_documents.constEnd(); i++) { registerDocument(i.key()); } emit modelChanged(); } void KateProject::loadIndexDone(KateProjectSharedProjectIndex projectIndex) { /** * move to our project */ m_projectIndex = std::move(projectIndex); /** * notify external world that data is available */ emit indexChanged(); } QString KateProject::projectLocalFileName(const QString &suffix) const { /** * nothing on empty file names for project * should not happen */ if (m_baseDir.isEmpty() || suffix.isEmpty()) { return QString(); } /** * compute full file name */ return m_baseDir + QStringLiteral(".kateproject.") + suffix; } QTextDocument *KateProject::notesDocument() { /** * already there? */ if (m_notesDocument) { return m_notesDocument; } /** * else create it */ m_notesDocument = new QTextDocument(this); m_notesDocument->setDocumentLayout(new QPlainTextDocumentLayout(m_notesDocument)); /** * get file name */ const QString notesFileName = projectLocalFileName(QStringLiteral("notes")); if (notesFileName.isEmpty()) { return m_notesDocument; } /** * and load text if possible */ QFile inFile(notesFileName); if (inFile.open(QIODevice::ReadOnly)) { QTextStream inStream(&inFile); inStream.setCodec("UTF-8"); m_notesDocument->setPlainText(inStream.readAll()); } /** * and be done */ return m_notesDocument; } void KateProject::saveNotesDocument() { /** * no notes document, nothing to do */ if (!m_notesDocument) { return; } /** * get content & filename */ const QString content = m_notesDocument->toPlainText(); const QString notesFileName = projectLocalFileName(QStringLiteral("notes")); if (notesFileName.isEmpty()) { return; } /** * no content => unlink file, if there */ if (content.isEmpty()) { if (QFile::exists(notesFileName)) { QFile::remove(notesFileName); } return; } /** * else: save content to file */ QFile outFile(projectLocalFileName(QStringLiteral("notes"))); if (outFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QTextStream outStream(&outFile); outStream.setCodec("UTF-8"); outStream << content; } } void KateProject::slotModifiedChanged(KTextEditor::Document *document) { KateProjectItem *item = itemForFile(m_documents.value(document)); if (!item) { return; } item->slotModifiedChanged(document); } void KateProject::slotModifiedOnDisk(KTextEditor::Document *document, bool isModified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { KateProjectItem *item = itemForFile(m_documents.value(document)); if (!item) { return; } item->slotModifiedOnDisk(document, isModified, reason); } void KateProject::registerDocument(KTextEditor::Document *document) { // remember the document, if not already there if (!m_documents.contains(document)) { m_documents[document] = document->url().toLocalFile(); } // try to get item for the document KateProjectItem *item = itemForFile(document->url().toLocalFile()); // if we got one, we are done, else create a dummy! if (item) { disconnect(document, &KTextEditor::Document::modifiedChanged, this, &KateProject::slotModifiedChanged); disconnect(document, SIGNAL(modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(slotModifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason))); item->slotModifiedChanged(document); /*FIXME item->slotModifiedOnDisk(document,document->isModified(),qobject_cast(document)->modifiedOnDisk()); FIXME*/ connect(document, &KTextEditor::Document::modifiedChanged, this, &KateProject::slotModifiedChanged); connect(document, SIGNAL(modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(slotModifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason))); return; } registerUntrackedDocument(document); } void KateProject::registerUntrackedDocument(KTextEditor::Document *document) { // perhaps create the parent item if (!m_untrackedDocumentsRoot) { m_untrackedDocumentsRoot = new KateProjectItem(KateProjectItem::Directory, i18n("")); m_model.insertRow(0, m_untrackedDocumentsRoot); } // create document item QFileInfo fileInfo(document->url().toLocalFile()); KateProjectItem *fileItem = new KateProjectItem(KateProjectItem::File, fileInfo.fileName()); fileItem->setData(document->url().toLocalFile(), Qt::ToolTipRole); fileItem->slotModifiedChanged(document); connect(document, &KTextEditor::Document::modifiedChanged, this, &KateProject::slotModifiedChanged); connect(document, SIGNAL(modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(slotModifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason))); bool inserted = false; for (int i = 0; i < m_untrackedDocumentsRoot->rowCount(); ++i) { if (m_untrackedDocumentsRoot->child(i)->data(Qt::UserRole).toString() > document->url().toLocalFile()) { m_untrackedDocumentsRoot->insertRow(i, fileItem); inserted = true; break; } } if (!inserted) { m_untrackedDocumentsRoot->appendRow(fileItem); } fileItem->setData(document->url().toLocalFile(), Qt::UserRole); fileItem->setData(QVariant(true), Qt::UserRole + 3); if (!m_file2Item) { m_file2Item = KateProjectSharedQMapStringItem(new QMap()); } (*m_file2Item)[document->url().toLocalFile()] = fileItem; } void KateProject::unregisterDocument(KTextEditor::Document *document) { if (!m_documents.contains(document)) { return; } disconnect(document, &KTextEditor::Document::modifiedChanged, this, &KateProject::slotModifiedChanged); const QString &file = m_documents.value(document); if (m_untrackedDocumentsRoot) { KateProjectItem *item = static_cast(itemForFile(file)); if (item && item->data(Qt::UserRole + 3).toBool()) { unregisterUntrackedItem(item); m_file2Item->remove(file); } } m_documents.remove(document); } void KateProject::unregisterUntrackedItem(const KateProjectItem *item) { for (int i = 0; i < m_untrackedDocumentsRoot->rowCount(); ++i) { if (m_untrackedDocumentsRoot->child(i) == item) { m_untrackedDocumentsRoot->removeRow(i); break; } } if (m_untrackedDocumentsRoot->rowCount() < 1) { m_model.removeRow(0); m_untrackedDocumentsRoot = nullptr; } } diff --git a/addons/project/kateproject.h b/addons/project/kateproject.h index 1511f6dfc..c27eedca2 100644 --- a/addons/project/kateproject.h +++ b/addons/project/kateproject.h @@ -1,319 +1,319 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KATE_PROJECT_H #define KATE_PROJECT_H +#include "kateprojectindex.h" +#include "kateprojectitem.h" +#include #include #include #include #include -#include -#include "kateprojectindex.h" -#include "kateprojectitem.h" /** * Shared pointer data types. * Used to pass pointers over queued connected slots */ typedef QSharedPointer KateProjectSharedQStandardItem; Q_DECLARE_METATYPE(KateProjectSharedQStandardItem) typedef QSharedPointer> KateProjectSharedQMapStringItem; Q_DECLARE_METATYPE(KateProjectSharedQMapStringItem) typedef QSharedPointer KateProjectSharedProjectIndex; Q_DECLARE_METATYPE(KateProjectSharedProjectIndex) namespace ThreadWeaver { class Queue; } class KateProjectPlugin; /** * Class representing a project. * Holds project properties like name, groups, contained files, ... */ class KateProject : public QObject { Q_OBJECT public: /** * construct empty project */ KateProject(ThreadWeaver::Queue *weaver, KateProjectPlugin *plugin); /** * deconstruct project */ ~KateProject() override; /** * Load a project from project file * Only works once, afterwards use reload(). * @param fileName name of project file * @return success */ bool loadFromFile(const QString &fileName); bool loadFromData(const QVariantMap &globalProject, const QString &directory); /** * Try to reload a project. * If the reload fails, e.g. because the file is not readable or corrupt, nothing will happen! * @param force will enforce the worker to update files list and co even if the content of the file was not changed! * @return success */ bool reload(bool force = false); /** * Accessor to file name. * @return file name */ const QString &fileName() const { return m_fileName; } /** * Return the base directory of this project. * @return base directory of project, might not be the directory of the fileName! */ const QString &baseDir() const { return m_baseDir; } /** * Return the time when the project file has been modified last. * @return QFileInfo::lastModified() */ QDateTime fileLastModified() const { return m_fileLastModified; } /** * Accessor to project map containing the whole project info. * @return project info */ const QVariantMap &projectMap() const { return m_projectMap; } /** * Accessor to project name. * @return project name */ QString name() const { // MSVC doesn't support QStringLiteral here return m_projectMap[QStringLiteral("name")].toString(); } /** * Accessor for the model. * @return model of this project */ QStandardItemModel *model() { return &m_model; } /** * Flat list of all files in the project * @return list of files in project */ QStringList files() { return m_file2Item ? m_file2Item->keys() : QStringList(); } /** * get item for file * @param file file to get item for * @return item for given file or 0 */ KateProjectItem *itemForFile(const QString &file) { return m_file2Item ? m_file2Item->value(file) : nullptr; } /** * Access to project index. * May be null. * Don't store this pointer, might change. * @return project index */ KateProjectIndex *projectIndex() { return m_projectIndex.data(); } /** * Computes a suitable file name for the given suffix. * If you e.g. want to store a "notes" file, you could pass "notes" and get * the full path to projectbasedir/.kateproject.notes * @param suffix suffix for the file * @return full path for project local file, on error => empty string */ QString projectLocalFileName(const QString &suffix) const; /** * Document with project local notes. * Will be stored in a projectLocalFile "notes.txt". * @return notes document */ QTextDocument *notesDocument(); /** * Save the notes document to "notes.txt" if any document around. */ void saveNotesDocument(); /** * Register a document for this project. * @param document document to register */ void registerDocument(KTextEditor::Document *document); /** * Unregister a document for this project. * @param document document to unregister */ void unregisterDocument(KTextEditor::Document *document); private Q_SLOTS: bool load(const QVariantMap &globalProject, bool force = false); /** * Used for worker to send back the results of project loading * @param topLevel new toplevel element for model * @param file2Item new file => item mapping */ void loadProjectDone(const KateProjectSharedQStandardItem &topLevel, KateProjectSharedQMapStringItem file2Item); /** * Used for worker to send back the results of index loading * @param projectIndex new project index */ void loadIndexDone(KateProjectSharedProjectIndex projectIndex); void slotModifiedChanged(KTextEditor::Document *); void slotModifiedOnDisk(KTextEditor::Document *document, bool isModified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason); Q_SIGNALS: /** * Emitted on project map changes. * This includes the name! */ void projectMapChanged(); /** * Emitted on model changes. * This includes the files list, itemForFile mapping! */ void modelChanged(); /** * Emitted when the index creation is finished. * This includes the ctags index. */ void indexChanged(); private: void registerUntrackedDocument(KTextEditor::Document *document); void unregisterUntrackedItem(const KateProjectItem *item); QVariantMap readProjectFile() const; private: /** * Last modification time of the project file */ QDateTime m_fileLastModified; /** * project file name */ QString m_fileName; /** * base directory of the project */ QString m_baseDir; /** * project name */ QString m_name; /** * variant map representing the project */ QVariantMap m_projectMap; /** * standard item model with content of this project */ QStandardItemModel m_model; /** * mapping files => items */ KateProjectSharedQMapStringItem m_file2Item; /** * project index, if any */ KateProjectSharedProjectIndex m_projectIndex; /** * notes buffer for project local notes */ QTextDocument *m_notesDocument; /** * Set of existing documents for this project. */ QMap m_documents; /** * Parent item for existing documents that are not in the project tree */ QStandardItem *m_untrackedDocumentsRoot; ThreadWeaver::Queue *m_weaver; /** * project configuration (read from file or injected) */ QVariantMap m_globalProject; /** * Project plugin (configuration) */ KateProjectPlugin *m_plugin; }; #endif diff --git a/addons/project/kateprojectcompletion.h b/addons/project/kateprojectcompletion.h index d16ef8890..7b138b4c0 100644 --- a/addons/project/kateprojectcompletion.h +++ b/addons/project/kateprojectcompletion.h @@ -1,97 +1,97 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * Copyright (C) 2003 Anders Lund * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KATE_PROJECT_COMPLETION_H #define KATE_PROJECT_COMPLETION_H -#include #include #include +#include #include /** * Project wide completion support. */ class KateProjectCompletion : public KTextEditor::CodeCompletionModel, public KTextEditor::CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: /** * Construct project completion. * @param plugin our plugin */ KateProjectCompletion(class KateProjectPlugin *plugin); /** * Deconstruct project completion. */ ~KateProjectCompletion() override; /** * This function is responsible to generating / updating the list of current * completions. The default implementation does nothing. * * When implementing this function, remember to call setRowCount() (or implement * rowCount()), and to generate the appropriate change notifications (for instance * by calling QAbstractItemModel::reset()). * @param view The view to generate completions for * @param range The range of text to generate completions for * */ void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType) override; bool shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) override; bool shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) override; void saveMatches(KTextEditor::View *view, const KTextEditor::Range &range); int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; MatchReaction matchingItem(const QModelIndex &matched) override; KTextEditor::Range completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position) override; void allMatches(QStandardItemModel &model, KTextEditor::View *view, const KTextEditor::Range &range) const; private: /** * our plugin view */ KateProjectPlugin *m_plugin; /** * model with matching data */ QStandardItemModel m_matches; /** * automatic invocation? */ bool m_automatic; }; #endif diff --git a/addons/project/kateprojectconfigpage.cpp b/addons/project/kateprojectconfigpage.cpp index cf99c7e31..5cf8ae133 100644 --- a/addons/project/kateprojectconfigpage.cpp +++ b/addons/project/kateprojectconfigpage.cpp @@ -1,128 +1,128 @@ /* This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "kateprojectconfigpage.h" #include "kateprojectplugin.h" -#include -#include -#include -#include #include #include +#include +#include +#include +#include KateProjectConfigPage::KateProjectConfigPage(QWidget *parent, KateProjectPlugin *plugin) : KTextEditor::ConfigPage(parent) , m_plugin(plugin) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); QVBoxLayout *vbox = new QVBoxLayout; QGroupBox *group = new QGroupBox(i18nc("Groupbox title", "Autoload Repositories"), this); group->setWhatsThis( i18n("Project plugin is able to autoload repository working copies when " "there is no .kateproject file defined yet.")); m_cbAutoGit = new QCheckBox(i18n("&Git"), this); vbox->addWidget(m_cbAutoGit); m_cbAutoSubversion = new QCheckBox(i18n("&Subversion"), this); vbox->addWidget(m_cbAutoSubversion); m_cbAutoMercurial = new QCheckBox(i18n("&Mercurial"), this); vbox->addWidget(m_cbAutoMercurial); vbox->addStretch(1); group->setLayout(vbox); layout->addWidget(group); vbox = new QVBoxLayout(); group = new QGroupBox(i18nc("Groupbox title", "Project Index"), this); group->setWhatsThis(i18n("Project ctags index settings")); m_cbIndexEnabled = new QCheckBox(i18n("Enable indexing"), this); vbox->addWidget(m_cbIndexEnabled); auto label = new QLabel(this); label->setText(QStringLiteral("Directory for index files")); vbox->addWidget(label); m_indexPath = new KUrlRequester(this); m_indexPath->setToolTip(QStringLiteral("The system tempory directory is used if not specified, which may overflow for very large repositories")); vbox->addWidget(m_indexPath); vbox->addStretch(1); group->setLayout(vbox); layout->addWidget(group); layout->insertStretch(-1, 10); reset(); connect(m_cbAutoGit, &QCheckBox::stateChanged, this, &KateProjectConfigPage::slotMyChanged); connect(m_cbAutoSubversion, &QCheckBox::stateChanged, this, &KateProjectConfigPage::slotMyChanged); connect(m_cbAutoMercurial, &QCheckBox::stateChanged, this, &KateProjectConfigPage::slotMyChanged); connect(m_cbIndexEnabled, &QCheckBox::stateChanged, this, &KateProjectConfigPage::slotMyChanged); connect(m_indexPath, &KUrlRequester::textChanged, this, &KateProjectConfigPage::slotMyChanged); connect(m_indexPath, &KUrlRequester::urlSelected, this, &KateProjectConfigPage::slotMyChanged); } QString KateProjectConfigPage::name() const { return i18n("Projects"); } QString KateProjectConfigPage::fullName() const { return i18nc("Groupbox title", "Projects Properties"); } QIcon KateProjectConfigPage::icon() const { return QIcon::fromTheme(QLatin1String("view-list-tree")); } void KateProjectConfigPage::apply() { if (!m_changed) { return; } m_changed = false; m_plugin->setAutoRepository(m_cbAutoGit->checkState() == Qt::Checked, m_cbAutoSubversion->checkState() == Qt::Checked, m_cbAutoMercurial->checkState() == Qt::Checked); m_plugin->setIndex(m_cbIndexEnabled->checkState() == Qt::Checked, m_indexPath->url()); } void KateProjectConfigPage::reset() { m_cbAutoGit->setCheckState(m_plugin->autoGit() ? Qt::Checked : Qt::Unchecked); m_cbAutoSubversion->setCheckState(m_plugin->autoSubversion() ? Qt::Checked : Qt::Unchecked); m_cbAutoMercurial->setCheckState(m_plugin->autoMercurial() ? Qt::Checked : Qt::Unchecked); m_cbIndexEnabled->setCheckState(m_plugin->getIndexEnabled() ? Qt::Checked : Qt::Unchecked); m_indexPath->setUrl(m_plugin->getIndexDirectory()); m_changed = false; } void KateProjectConfigPage::defaults() { reset(); } void KateProjectConfigPage::slotMyChanged() { m_changed = true; emit changed(); } diff --git a/addons/project/kateprojectindex.cpp b/addons/project/kateprojectindex.cpp index 102c5c941..ed7a88730 100644 --- a/addons/project/kateprojectindex.cpp +++ b/addons/project/kateprojectindex.cpp @@ -1,239 +1,239 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojectindex.h" -#include #include +#include /** * include ctags reading */ #include "ctags/readtags.c" KateProjectIndex::KateProjectIndex(const QString &baseDir, const QString &indexDir, const QStringList &files, const QVariantMap &ctagsMap, bool force) : m_ctagsIndexHandle(nullptr) { // allow project to override and specify a (re-usable) indexfile // otherwise fall-back to a temporary file if nothing specified auto ctagsFile = ctagsMap.value(QStringLiteral("index_file")); if (ctagsFile.userType() == QMetaType::QString) { auto path = ctagsFile.toString(); if (!QDir::isAbsolutePath(path)) { path = QDir(baseDir).absoluteFilePath(path); } m_ctagsIndexFile.reset(new QFile(path)); } else { // indexDir is typically QDir::tempPath() or otherwise specified in configuration m_ctagsIndexFile.reset(new QTemporaryFile(indexDir + QStringLiteral("/kate.project.ctags"))); } /** * load ctags */ loadCtags(files, ctagsMap, force); } KateProjectIndex::~KateProjectIndex() { /** * delete ctags handle if any */ if (m_ctagsIndexHandle) { tagsClose(m_ctagsIndexHandle); m_ctagsIndexHandle = nullptr; } } void KateProjectIndex::loadCtags(const QStringList &files, const QVariantMap &ctagsMap, bool force) { /** * only overwrite existing index upon reload * (a temporary index file will never exist) */ if (m_ctagsIndexFile->exists() && !force) { openCtags(); return; } /** * create temporary file * if not possible, fail */ if (!m_ctagsIndexFile->open(QIODevice::ReadWrite)) { return; } /** * close file again, other process will use it */ m_ctagsIndexFile->close(); /** * try to run ctags for all files in this project * output to our ctags index file */ QProcess ctags; QStringList args; args << QStringLiteral("-L") << QStringLiteral("-") << QStringLiteral("-f") << m_ctagsIndexFile->fileName() << QStringLiteral("--fields=+K+n"); const QString keyOptions = QStringLiteral("options"); for (const QVariant &optVariant : ctagsMap[keyOptions].toList()) { args << optVariant.toString(); } ctags.start(QStringLiteral("ctags"), args); if (!ctags.waitForStarted()) { return; } /** * write files list and close write channel */ ctags.write(files.join(QLatin1Char('\n')).toLocal8Bit()); ctags.closeWriteChannel(); /** * wait for done */ if (!ctags.waitForFinished(-1)) { return; } openCtags(); } void KateProjectIndex::openCtags() { /** * file not openable, bad */ if (!m_ctagsIndexFile->open(QIODevice::ReadOnly)) { return; } /** * get size */ qint64 size = m_ctagsIndexFile->size(); /** * close again */ m_ctagsIndexFile->close(); /** * empty file, bad */ if (!size) { return; } /** * close current */ if (m_ctagsIndexHandle) { tagsClose(m_ctagsIndexHandle); m_ctagsIndexHandle = nullptr; } /** * try to open ctags file */ tagFileInfo info; memset(&info, 0, sizeof(tagFileInfo)); m_ctagsIndexHandle = tagsOpen(m_ctagsIndexFile->fileName().toLocal8Bit().constData(), &info); } void KateProjectIndex::findMatches(QStandardItemModel &model, const QString &searchWord, MatchType type) { /** * abort if no ctags index */ if (!m_ctagsIndexHandle) { return; } /** * word to complete * abort if empty */ QByteArray word = searchWord.toLocal8Bit(); if (word.isEmpty()) { return; } /** * try to search entry * fail if none found */ tagEntry entry; if (tagsFind(m_ctagsIndexHandle, &entry, word.constData(), TAG_PARTIALMATCH | TAG_OBSERVECASE) != TagSuccess) { return; } /** * set to show words only once for completion matches */ QSet guard; /** * loop over all found tags * first one is filled by above find, others by find next */ do { /** * skip if no name */ if (!entry.name) { continue; } /** * get name */ QString name(QString::fromLocal8Bit(entry.name)); /** * construct right items */ switch (type) { case CompletionMatches: /** * add new completion item, if new name */ if (!guard.contains(name)) { model.appendRow(new QStandardItem(name)); guard.insert(name); } break; case FindMatches: /** * add new find item, contains of multiple columns */ QList items; items << new QStandardItem(name); items << new QStandardItem(entry.kind ? QString::fromLocal8Bit(entry.kind) : QString()); items << new QStandardItem(entry.file ? QString::fromLocal8Bit(entry.file) : QString()); items << new QStandardItem(QString::number(entry.address.lineNumber)); model.appendRow(items); break; } } while (tagsFindNext(m_ctagsIndexHandle, &entry) == TagSuccess); } diff --git a/addons/project/kateprojectindex.h b/addons/project/kateprojectindex.h index c2cf7917e..279ccf72e 100644 --- a/addons/project/kateprojectindex.h +++ b/addons/project/kateprojectindex.h @@ -1,118 +1,118 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KATE_PROJECT_INDEX_H #define KATE_PROJECT_INDEX_H #include #include +#include #include #include -#include /** * ctags reading */ #include "ctags/readtags.h" /** * Class representing the index of a project. * This includes knowledge from ctags and Co. * Allows you to search for stuff and to get some useful auto-completion. * Is created in Worker thread in the background, then passed to project in * the main thread for usage. */ class KateProjectIndex { public: /** * construct new index for given files * @param files files to index * @param ctagsMap ctags section for extra options */ KateProjectIndex(const QString &baseDir, const QString &indexDir, const QStringList &files, const QVariantMap &ctagsMap, bool force); /** * deconstruct project */ ~KateProjectIndex(); /** * Which kind of match items should be created in the passed model * of the findMatches function? */ enum MatchType { /** * Completion matches, containing only name and same name only once */ CompletionMatches, /** * Find matches, containing name, kind, file, line, ... */ FindMatches }; /** * Fill in completion matches for given view/range. * Uses e.g. ctags index. * @param model model to fill with matches * @param searchWord word to search for * @param type type of matches */ void findMatches(QStandardItemModel &model, const QString &searchWord, MatchType type); /** * Check if running ctags was successful. This can be used * as indicator whether ctags is installed or not. * @return true if a valid index exists, otherwise false */ bool isValid() const { return m_ctagsIndexHandle; } private: /** * Load ctags tags. * @param files files to index * @param ctagsMap ctags section for extra options */ void loadCtags(const QStringList &files, const QVariantMap &ctagsMap, bool force); /** * Open ctags tags. */ void openCtags(); private: /** * ctags index file */ QScopedPointer m_ctagsIndexFile; /** * handle to ctags file for querying, if possible */ tagFile *m_ctagsIndexHandle; }; #endif diff --git a/addons/project/kateprojectinfoview.cpp b/addons/project/kateprojectinfoview.cpp index e9b6aff77..47f0ae949 100644 --- a/addons/project/kateprojectinfoview.cpp +++ b/addons/project/kateprojectinfoview.cpp @@ -1,81 +1,81 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojectinfoview.h" -#include "kateprojectpluginview.h" -#include "kateprojectinfoviewterminal.h" -#include "kateprojectinfoviewindex.h" #include "kateprojectinfoviewcodeanalysis.h" +#include "kateprojectinfoviewindex.h" #include "kateprojectinfoviewnotes.h" +#include "kateprojectinfoviewterminal.h" +#include "kateprojectpluginview.h" #include "klocalizedstring.h" #include KateProjectInfoView::KateProjectInfoView(KateProjectPluginView *pluginView, KateProject *project) : QTabWidget() , m_pluginView(pluginView) , m_project(project) { /** * skip terminal toolviews if no terminal aka KonsolePart around */ if (KateProjectInfoViewTerminal::pluginFactory()) { /** * terminal for the directory with the .kateproject file inside */ const QString projectPath = QFileInfo(QFileInfo(m_project->fileName()).path()).canonicalFilePath(); if (!projectPath.isEmpty()) { addTab(new KateProjectInfoViewTerminal(pluginView, projectPath), i18n("Terminal (.kateproject)")); } /** * terminal for the base directory, if different to directory of .kateproject */ const QString basePath = QFileInfo(m_project->baseDir()).canonicalFilePath(); if (!basePath.isEmpty() && projectPath != basePath) { addTab(new KateProjectInfoViewTerminal(pluginView, basePath), i18n("Terminal (Base)")); } } /** * index */ addTab(new KateProjectInfoViewIndex(pluginView, project), i18n("Code Index")); /** * code analysis */ addTab(new KateProjectInfoViewCodeAnalysis(pluginView, project), i18n("Code Analysis")); /** * notes */ addTab(new KateProjectInfoViewNotes(pluginView, project), i18n("Notes")); } KateProjectInfoView::~KateProjectInfoView() { } void KateProjectInfoView::showEvent(QShowEvent *) { setFocusProxy(currentWidget()); } diff --git a/addons/project/kateprojectinfoviewcodeanalysis.cpp b/addons/project/kateprojectinfoviewcodeanalysis.cpp index 331c20af4..f5d57b802 100644 --- a/addons/project/kateprojectinfoviewcodeanalysis.cpp +++ b/addons/project/kateprojectinfoviewcodeanalysis.cpp @@ -1,249 +1,249 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojectinfoviewcodeanalysis.h" -#include "kateprojectpluginview.h" #include "kateprojectcodeanalysistool.h" +#include "kateprojectpluginview.h" #include "tools/kateprojectcodeanalysisselector.h" #include #include -#include #include +#include #include #include KateProjectInfoViewCodeAnalysis::KateProjectInfoViewCodeAnalysis(KateProjectPluginView *pluginView, KateProject *project) : QWidget() , m_pluginView(pluginView) , m_project(project) , m_messageWidget(nullptr) , m_startStopAnalysis(new QPushButton(i18n("Start Analysis..."))) , m_treeView(new QTreeView(this)) , m_model(new QStandardItemModel(m_treeView)) , m_analyzer(nullptr) , m_analysisTool(nullptr) , m_toolSelector(new QComboBox()) { /** * default style */ m_treeView->setEditTriggers(QAbstractItemView::NoEditTriggers); m_treeView->setUniformRowHeights(true); m_treeView->setRootIsDecorated(false); m_model->setHorizontalHeaderLabels(QStringList() << i18n("File") << i18n("Line") << i18n("Severity") << i18n("Message")); /** * attach model * kill selection model */ QItemSelectionModel *m = m_treeView->selectionModel(); m_treeView->setModel(m_model); delete m; m_treeView->setSortingEnabled(true); m_treeView->sortByColumn(1, Qt::AscendingOrder); m_treeView->sortByColumn(2, Qt::AscendingOrder); /** * Connect selection change callback * and attach model to code analysis selector */ connect(m_toolSelector, static_cast(&QComboBox::currentIndexChanged), this, &KateProjectInfoViewCodeAnalysis::slotToolSelectionChanged); m_toolSelector->setModel(KateProjectCodeAnalysisSelector::model(this)); /** * layout widget */ QVBoxLayout *layout = new QVBoxLayout; layout->setSpacing(0); // top: selector and buttons... QHBoxLayout *hlayout = new QHBoxLayout; layout->addLayout(hlayout); hlayout->setSpacing(0); hlayout->addWidget(m_toolSelector); auto infoButton = new QPushButton(QIcon::fromTheme(QStringLiteral("documentinfo")), QString(), this); infoButton->setFocusPolicy(Qt::FocusPolicy::TabFocus); connect(infoButton, &QPushButton::clicked, this, [this]() { QToolTip::showText(QCursor::pos(), m_toolInfoText); }); hlayout->addWidget(infoButton); hlayout->addWidget(m_startStopAnalysis); hlayout->addStretch(); // below: result list... layout->addWidget(m_treeView); setLayout(layout); /** * connect needed signals */ connect(m_startStopAnalysis, &QPushButton::clicked, this, &KateProjectInfoViewCodeAnalysis::slotStartStopClicked); connect(m_treeView, &QTreeView::clicked, this, &KateProjectInfoViewCodeAnalysis::slotClicked); } KateProjectInfoViewCodeAnalysis::~KateProjectInfoViewCodeAnalysis() { delete m_analyzer; } void KateProjectInfoViewCodeAnalysis::slotToolSelectionChanged(int) { m_analysisTool = m_toolSelector->currentData(Qt::UserRole + 1).value(); m_toolInfoText = i18n("%1

The tool will be run on all project files which match this list of file extensions:

%2", m_analysisTool->description(), m_analysisTool->fileExtensions()); } void KateProjectInfoViewCodeAnalysis::slotStartStopClicked() { /** * get files for the external tool */ m_analysisTool = m_toolSelector->currentData(Qt::UserRole + 1).value(); m_analysisTool->setProject(m_project); /** * clear existing entries */ m_model->removeRows(0, m_model->rowCount(), QModelIndex()); /** * launch selected tool */ delete m_analyzer; m_analyzer = new QProcess; m_analyzer->setProcessChannelMode(QProcess::MergedChannels); connect(m_analyzer, &QProcess::readyRead, this, &KateProjectInfoViewCodeAnalysis::slotReadyRead); connect(m_analyzer, static_cast(&QProcess::finished), this, &KateProjectInfoViewCodeAnalysis::finished); m_analyzer->start(m_analysisTool->path(), m_analysisTool->arguments()); if (m_messageWidget) { delete m_messageWidget; m_messageWidget = nullptr; } if (!m_analyzer->waitForStarted()) { m_messageWidget = new KMessageWidget(this); m_messageWidget->setCloseButtonVisible(true); m_messageWidget->setMessageType(KMessageWidget::Warning); m_messageWidget->setWordWrap(false); m_messageWidget->setText(m_analysisTool->notInstalledMessage()); static_cast(layout())->addWidget(m_messageWidget); m_messageWidget->animatedShow(); return; } m_startStopAnalysis->setEnabled(false); /** * write files list and close write channel */ const QString stdinMessage = m_analysisTool->stdinMessages(); if (!stdinMessage.isEmpty()) { m_analyzer->write(stdinMessage.toLocal8Bit()); } m_analyzer->closeWriteChannel(); } void KateProjectInfoViewCodeAnalysis::slotReadyRead() { /** * get results of analysis */ while (m_analyzer->canReadLine()) { /** * get one line, split it, skip it, if too few elements */ QString line = QString::fromLocal8Bit(m_analyzer->readLine()); QStringList elements = m_analysisTool->parseLine(line); if (elements.size() < 4) { continue; } /** * feed into model */ QList items; QStandardItem *fileNameItem = new QStandardItem(QFileInfo(elements[0]).fileName()); fileNameItem->setToolTip(elements[0]); items << fileNameItem; items << new QStandardItem(elements[1]); items << new QStandardItem(elements[2]); const auto message = elements[3].simplified(); auto messageItem = new QStandardItem(message); messageItem->setToolTip(message); items << messageItem; m_model->appendRow(items); } /** * tree view polish ;) */ m_treeView->resizeColumnToContents(2); m_treeView->resizeColumnToContents(1); m_treeView->resizeColumnToContents(0); } void KateProjectInfoViewCodeAnalysis::slotClicked(const QModelIndex &index) { /** * get path */ QString filePath = m_model->item(index.row(), 0)->toolTip(); if (filePath.isEmpty()) { return; } /** * create view */ KTextEditor::View *view = m_pluginView->mainWindow()->openUrl(QUrl::fromLocalFile(filePath)); if (!view) { return; } /** * set cursor, if possible */ int line = m_model->item(index.row(), 1)->text().toInt(); if (line >= 1) { view->setCursorPosition(KTextEditor::Cursor(line - 1, 0)); } } void KateProjectInfoViewCodeAnalysis::finished(int exitCode, QProcess::ExitStatus) { m_startStopAnalysis->setEnabled(true); m_messageWidget = new KMessageWidget(this); m_messageWidget->setCloseButtonVisible(true); m_messageWidget->setWordWrap(false); if (m_analysisTool->isSuccessfulExitCode(exitCode)) { // normally 0 is successful but there are exceptions m_messageWidget->setMessageType(KMessageWidget::Information); m_messageWidget->setText(i18np("Analysis on %1 file finished.", "Analysis on %1 files finished.", m_analysisTool->getActualFilesCount())); } else { // unfortunately, output was eaten by slotReadyRead() // TODO: get stderr output, show it here m_messageWidget->setMessageType(KMessageWidget::Warning); m_messageWidget->setText(i18np("Analysis on %1 file failed with exit code %2.", "Analysis on %1 files failed with exit code %2.", m_analysisTool->getActualFilesCount(), exitCode)); } static_cast(layout())->addWidget(m_messageWidget); m_messageWidget->animatedShow(); } diff --git a/addons/project/kateprojectinfoviewterminal.cpp b/addons/project/kateprojectinfoviewterminal.cpp index 937cdb7c9..bd232ce88 100644 --- a/addons/project/kateprojectinfoviewterminal.cpp +++ b/addons/project/kateprojectinfoviewterminal.cpp @@ -1,122 +1,122 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojectinfoviewterminal.h" #include "kateprojectpluginview.h" -#include -#include #include +#include +#include KPluginFactory *KateProjectInfoViewTerminal::s_pluginFactory = nullptr; KateProjectInfoViewTerminal::KateProjectInfoViewTerminal(KateProjectPluginView *pluginView, const QString &directory) : QWidget() , m_pluginView(pluginView) , m_directory(directory) , m_konsolePart(nullptr) { /** * layout widget */ m_layout = new QVBoxLayout(this); m_layout->setSpacing(0); m_layout->setContentsMargins(0, 0, 0, 0); } KateProjectInfoViewTerminal::~KateProjectInfoViewTerminal() { /** * avoid endless loop */ if (m_konsolePart) { disconnect(m_konsolePart, &KParts::ReadOnlyPart::destroyed, this, &KateProjectInfoViewTerminal::loadTerminal); } } KPluginFactory *KateProjectInfoViewTerminal::pluginFactory() { if (s_pluginFactory) { return s_pluginFactory; } return s_pluginFactory = KPluginLoader(QStringLiteral("konsolepart")).factory(); } void KateProjectInfoViewTerminal::showEvent(QShowEvent *) { /** * we delay the terminal construction until we have some part to have a usable WINDOWID, see bug 411965 */ if (!m_konsolePart) { loadTerminal(); } } void KateProjectInfoViewTerminal::loadTerminal() { /** * null in any case, if loadTerminal fails below and we are in the destroyed event */ m_konsolePart = nullptr; setFocusProxy(nullptr); /** * we shall not arrive here without a factory, if it is not there, no terminal toolview shall be created */ Q_ASSERT(pluginFactory()); /** * create part */ m_konsolePart = pluginFactory()->create(this, this); if (!m_konsolePart) { return; } /** * init locale translation stuff */ // FIXME KF5 KGlobal::locale()->insertCatalog("konsole"); /** * switch to right directory */ qobject_cast(m_konsolePart)->showShellInDir(m_directory); /** * add to widget */ m_layout->addWidget(m_konsolePart->widget()); setFocusProxy(m_konsolePart->widget()); /** * guard destruction, create new terminal! */ connect(m_konsolePart, &KParts::ReadOnlyPart::destroyed, this, &KateProjectInfoViewTerminal::loadTerminal); connect(m_konsolePart, SIGNAL(overrideShortcut(QKeyEvent *, bool &)), this, SLOT(overrideShortcut(QKeyEvent *, bool &))); } void KateProjectInfoViewTerminal::overrideShortcut(QKeyEvent *, bool &override) { /** * let konsole handle all shortcuts */ override = true; } diff --git a/addons/project/kateprojectinfoviewterminal.h b/addons/project/kateprojectinfoviewterminal.h index 2e679ad03..ba302c462 100644 --- a/addons/project/kateprojectinfoviewterminal.h +++ b/addons/project/kateprojectinfoviewterminal.h @@ -1,107 +1,107 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KATE_PROJECT_INFO_VIEW_TERMINAL_H #define KATE_PROJECT_INFO_VIEW_TERMINAL_H #include "kateproject.h" -#include #include +#include #include #include class KateProjectPluginView; /** * Class representing a view of a project. * A tree like view of project content. */ class KateProjectInfoViewTerminal : public QWidget { Q_OBJECT public: /** * construct project info view for given project * @param pluginView our plugin view * @param directory base directory for this terminal view */ KateProjectInfoViewTerminal(KateProjectPluginView *pluginView, const QString &directory); /** * deconstruct info view */ ~KateProjectInfoViewTerminal() override; /** * global plugin factory to create terminal * exposed to allow to skip terminal toolview creation if not possible * @return plugin factory for terminal or nullptr if no terminal part there */ static KPluginFactory *pluginFactory(); private Q_SLOTS: /** * Construct a new terminal for this view */ void loadTerminal(); /** * Handle that shortcuts are not eaten by console */ void overrideShortcut(QKeyEvent *event, bool &override); protected: /** * the konsole get shown * @param ev show event */ void showEvent(QShowEvent *ev) override; private: /** * plugin factory for the terminal */ static KPluginFactory *s_pluginFactory; /** * our plugin view */ KateProjectPluginView *const m_pluginView; /** * our start directory for the terminal */ const QString m_directory; /** * our layout */ QVBoxLayout *m_layout; /** * konsole part */ KParts::ReadOnlyPart *m_konsolePart; }; #endif diff --git a/addons/project/kateprojectitem.cpp b/addons/project/kateprojectitem.cpp index bf3af381e..e0b81f5e0 100644 --- a/addons/project/kateprojectitem.cpp +++ b/addons/project/kateprojectitem.cpp @@ -1,125 +1,125 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojectitem.h" +#include #include #include #include #include -#include -#include #include +#include #include #include KateProjectItem::KateProjectItem(Type type, const QString &text) : QStandardItem(text) , m_type(type) , m_icon(nullptr) { } KateProjectItem::~KateProjectItem() { delete m_icon; } void KateProjectItem::slotModifiedChanged(KTextEditor::Document *doc) { if (m_icon) { delete m_icon; m_icon = nullptr; } if (doc->isModified()) { if (m_emblem.isEmpty()) { m_icon = new QIcon(QIcon::fromTheme(QStringLiteral("document-save"))); } else { m_icon = new QIcon(KIconUtils::addOverlay(QIcon::fromTheme(QStringLiteral("document-save")), QIcon(m_emblem), Qt::TopLeftCorner)); } } emitDataChanged(); } void KateProjectItem::slotModifiedOnDisk(KTextEditor::Document *document, bool isModified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { Q_UNUSED(document) Q_UNUSED(isModified) if (m_icon) { delete m_icon; m_icon = nullptr; } m_emblem.clear(); if (reason != KTextEditor::ModificationInterface::OnDiskUnmodified) { m_emblem = QStringLiteral("emblem-important"); } emitDataChanged(); } QVariant KateProjectItem::data(int role) const { if (role == Qt::DecorationRole) { /** * this should only happen in main thread * the background thread should only construct this elements and fill data * but never query gui stuff! */ Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); return QVariant(*icon()); } return QStandardItem::data(role); } QIcon *KateProjectItem::icon() const { if (m_icon) { return m_icon; } switch (m_type) { case Project: m_icon = new QIcon(QIcon::fromTheme(QStringLiteral("folder-documents"))); break; case Directory: m_icon = new QIcon(QIcon::fromTheme(QStringLiteral("folder"))); break; case File: { QString iconName = QMimeDatabase().mimeTypeForUrl(QUrl::fromLocalFile(data(Qt::UserRole).toString())).iconName(); QStringList emblems; if (!m_emblem.isEmpty()) { m_icon = new QIcon(KIconUtils::addOverlay(QIcon::fromTheme(iconName), QIcon(m_emblem), Qt::TopLeftCorner)); } else { m_icon = new QIcon(QIcon::fromTheme(iconName)); } break; } } return m_icon; } diff --git a/addons/project/kateprojectitem.h b/addons/project/kateprojectitem.h index da84aaeab..44940d50b 100644 --- a/addons/project/kateprojectitem.h +++ b/addons/project/kateprojectitem.h @@ -1,88 +1,88 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KATE_PROJECT_ITEM_H #define KATE_PROJECT_ITEM_H -#include #include +#include namespace KTextEditor { class Document; } /** * Class representing a item inside a project. * Items can be: projects, directories, files */ class KateProjectItem : public QStandardItem { public: /** * Possible Types */ enum Type { Project, Directory, File }; /** * construct new item with given text * @param type type for this item * @param text text for this item */ KateProjectItem(Type type, const QString &text); /** * deconstruct project */ ~KateProjectItem() override; /** * Overwritten data methode for on-demand icon creation and co. * @param role role to get data for * @return data for role */ QVariant data(int role = Qt::UserRole + 1) const override; public: void slotModifiedChanged(KTextEditor::Document *); void slotModifiedOnDisk(KTextEditor::Document *document, bool isModified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason); private: QIcon *icon() const; private: /** * type */ const Type m_type; /** * cached icon */ mutable QIcon *m_icon; /** * for document icons */ QString m_emblem; }; #endif diff --git a/addons/project/kateprojectplugin.cpp b/addons/project/kateprojectplugin.cpp index 217ef2193..c9741d9be 100644 --- a/addons/project/kateprojectplugin.cpp +++ b/addons/project/kateprojectplugin.cpp @@ -1,443 +1,443 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojectplugin.h" #include "kateproject.h" #include "kateprojectconfigpage.h" #include "kateprojectpluginview.h" -#include #include #include +#include #include // delete, when we depend on KF 5.63 +#include #include #include -#include #include #include #include #include #ifdef HAVE_CTERMID -#include -#include #include +#include +#include #include #include #endif namespace { const QString ProjectFileName = QStringLiteral(".kateproject"); const QString GitFolderName = QStringLiteral(".git"); const QString SubversionFolderName = QStringLiteral(".svn"); const QString MercurialFolderName = QStringLiteral(".hg"); const QString GitConfig = QStringLiteral("git"); const QString SubversionConfig = QStringLiteral("subversion"); const QString MercurialConfig = QStringLiteral("mercurial"); const QStringList DefaultConfig = QStringList() << GitConfig << SubversionConfig << MercurialConfig; } KateProjectPlugin::KateProjectPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) , m_completion(this) , m_autoGit(true) , m_autoSubversion(true) , m_autoMercurial(true) , m_weaver(new ThreadWeaver::Queue(this)) { qRegisterMetaType("KateProjectSharedQStandardItem"); qRegisterMetaType("KateProjectSharedQMapStringItem"); qRegisterMetaType("KateProjectSharedProjectIndex"); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentCreated, this, &KateProjectPlugin::slotDocumentCreated); connect(&m_fileWatcher, &QFileSystemWatcher::directoryChanged, this, &KateProjectPlugin::slotDirectoryChanged); // read configuration prior to cwd project setup below readConfig(); #ifdef HAVE_CTERMID /** * open project for our current working directory, if this kate has a terminal * http://stackoverflow.com/questions/1312922/detect-if-stdin-is-a-terminal-or-pipe-in-c-c-qt */ char tty[L_ctermid + 1] = {0}; ctermid(tty); int fd = ::open(tty, O_RDONLY); if (fd >= 0) { projectForDir(QDir::current()); ::close(fd); } #endif for (auto document : KTextEditor::Editor::instance()->application()->documents()) { slotDocumentCreated(document); } registerVariables(); } KateProjectPlugin::~KateProjectPlugin() { unregisterVariables(); for (KateProject *project : m_projects) { m_fileWatcher.removePath(QFileInfo(project->fileName()).canonicalPath()); delete project; } m_projects.clear(); m_weaver->shutDown(); delete m_weaver; } QObject *KateProjectPlugin::createView(KTextEditor::MainWindow *mainWindow) { return new KateProjectPluginView(this, mainWindow); } int KateProjectPlugin::configPages() const { return 1; } KTextEditor::ConfigPage *KateProjectPlugin::configPage(int number, QWidget *parent) { if (number != 0) { return nullptr; } return new KateProjectConfigPage(parent, this); } KateProject *KateProjectPlugin::createProjectForFileName(const QString &fileName) { KateProject *project = new KateProject(m_weaver, this); if (!project->loadFromFile(fileName)) { delete project; return nullptr; } m_projects.append(project); m_fileWatcher.addPath(QFileInfo(fileName).canonicalPath()); emit projectCreated(project); return project; } KateProject *KateProjectPlugin::projectForDir(QDir dir) { /** * search project file upwards * with recursion guard * do this first for all level and only after this fails try to invent projects * otherwise one e.g. invents projects for .kateproject tree structures with sub .git clones */ QSet seenDirectories; std::vector directoryStack; while (!seenDirectories.contains(dir.absolutePath())) { // update guard seenDirectories.insert(dir.absolutePath()); // remember directory for later project creation based on other criteria directoryStack.push_back(dir.absolutePath()); // check for project and load it if found const QString canonicalPath = dir.canonicalPath(); const QString canonicalFileName = dir.filePath(ProjectFileName); for (KateProject *project : m_projects) { if (project->baseDir() == canonicalPath || project->fileName() == canonicalFileName) { return project; } } // project file found => done if (dir.exists(ProjectFileName)) { return createProjectForFileName(canonicalFileName); } // else: cd up, if possible or abort if (!dir.cdUp()) { break; } } /** * if we arrive here, we found no .kateproject * => we want to invent a project based on e.g. version control system info */ for (const QString &dir : directoryStack) { // try to invent project based on version control stuff KateProject *project = nullptr; if ((project = detectGit(dir)) || (project = detectSubversion(dir)) || (project = detectMercurial(dir))) { return project; } } // no project found, bad luck return nullptr; } KateProject *KateProjectPlugin::projectForUrl(const QUrl &url) { if (url.isEmpty() || !url.isLocalFile()) { return nullptr; } return projectForDir(QFileInfo(url.toLocalFile()).absoluteDir()); } void KateProjectPlugin::slotDocumentCreated(KTextEditor::Document *document) { connect(document, &KTextEditor::Document::documentUrlChanged, this, &KateProjectPlugin::slotDocumentUrlChanged); connect(document, &KTextEditor::Document::destroyed, this, &KateProjectPlugin::slotDocumentDestroyed); slotDocumentUrlChanged(document); } void KateProjectPlugin::slotDocumentDestroyed(QObject *document) { if (KateProject *project = m_document2Project.value(document)) { project->unregisterDocument(static_cast(document)); } m_document2Project.remove(document); } void KateProjectPlugin::slotDocumentUrlChanged(KTextEditor::Document *document) { KateProject *project = projectForUrl(document->url()); if (KateProject *project = m_document2Project.value(document)) { project->unregisterDocument(document); } if (!project) { m_document2Project.remove(document); } else { m_document2Project[document] = project; } if (KateProject *project = m_document2Project.value(document)) { project->registerDocument(document); } } void KateProjectPlugin::slotDirectoryChanged(const QString &path) { QString fileName = QDir(path).filePath(ProjectFileName); for (KateProject *project : m_projects) { if (project->fileName() == fileName) { QDateTime lastModified = QFileInfo(fileName).lastModified(); if (project->fileLastModified().isNull() || (lastModified > project->fileLastModified())) { project->reload(); } break; } } } KateProject *KateProjectPlugin::detectGit(const QDir &dir) { // allow .git as dir and file (file for git worktree stuff, https://git-scm.com/docs/git-worktree) if (m_autoGit && dir.exists(GitFolderName)) { return createProjectForRepository(QStringLiteral("git"), dir); } return nullptr; } KateProject *KateProjectPlugin::detectSubversion(const QDir &dir) { if (m_autoSubversion && dir.exists(SubversionFolderName) && QFileInfo(dir, SubversionFolderName).isDir()) { return createProjectForRepository(QStringLiteral("svn"), dir); } return nullptr; } KateProject *KateProjectPlugin::detectMercurial(const QDir &dir) { if (m_autoMercurial && dir.exists(MercurialFolderName) && QFileInfo(dir, MercurialFolderName).isDir()) { return createProjectForRepository(QStringLiteral("hg"), dir); } return nullptr; } KateProject *KateProjectPlugin::createProjectForRepository(const QString &type, const QDir &dir) { QVariantMap cnf, files; files[type] = 1; cnf[QStringLiteral("name")] = dir.dirName(); cnf[QStringLiteral("files")] = (QVariantList() << files); KateProject *project = new KateProject(m_weaver, this); project->loadFromData(cnf, dir.canonicalPath()); m_projects.append(project); emit projectCreated(project); return project; } void KateProjectPlugin::setAutoRepository(bool onGit, bool onSubversion, bool onMercurial) { m_autoGit = onGit; m_autoSubversion = onSubversion; m_autoMercurial = onMercurial; writeConfig(); } bool KateProjectPlugin::autoGit() const { return m_autoGit; } bool KateProjectPlugin::autoSubversion() const { return m_autoSubversion; } bool KateProjectPlugin::autoMercurial() const { return m_autoMercurial; } void KateProjectPlugin::setIndex(bool enabled, const QUrl &directory) { m_indexEnabled = enabled; m_indexDirectory = directory; writeConfig(); } bool KateProjectPlugin::getIndexEnabled() const { return m_indexEnabled; } QUrl KateProjectPlugin::getIndexDirectory() const { return m_indexDirectory; } void KateProjectPlugin::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), "project"); QStringList autorepository = config.readEntry("autorepository", DefaultConfig); m_autoGit = m_autoSubversion = m_autoMercurial = false; if (autorepository.contains(GitConfig)) { m_autoGit = true; } if (autorepository.contains(SubversionConfig)) { m_autoSubversion = true; } if (autorepository.contains(MercurialConfig)) { m_autoMercurial = true; } m_indexEnabled = config.readEntry("index", false); m_indexDirectory = config.readEntry("indexDirectory", QUrl()); } void KateProjectPlugin::writeConfig() { KConfigGroup config(KSharedConfig::openConfig(), "project"); QStringList repos; if (m_autoGit) { repos << GitConfig; } if (m_autoSubversion) { repos << SubversionConfig; } if (m_autoMercurial) { repos << MercurialConfig; } config.writeEntry("autorepository", repos); config.writeEntry("index", m_indexEnabled); config.writeEntry("indexDirectory", m_indexDirectory); } #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 63, 0) static KateProjectPlugin *findProjectPlugin() { auto plugin = KTextEditor::Editor::instance()->application()->plugin(QStringLiteral("kateprojectplugin")); return qobject_cast(plugin); } #endif void KateProjectPlugin::registerVariables() { #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 63, 0) auto editor = KTextEditor::Editor::instance(); editor->registerVariableMatch(QStringLiteral("Project:Path"), i18n("Full path to current project excluding the file name."), [](const QStringView &, KTextEditor::View *view) { if (!view) { return QString(); } auto projectPlugin = findProjectPlugin(); if (!projectPlugin) { return QString(); } auto kateProject = findProjectPlugin()->projectForUrl(view->document()->url()); if (!kateProject) { return QString(); } return QDir(kateProject->baseDir()).absolutePath(); }); editor->registerVariableMatch(QStringLiteral("Project:NativePath"), i18n("Full path to current project excluding the file name, with native path separator (backslash on Windows)."), [](const QStringView &, KTextEditor::View *view) { if (!view) { return QString(); } auto projectPlugin = findProjectPlugin(); if (!projectPlugin) { return QString(); } auto kateProject = findProjectPlugin()->projectForUrl(view->document()->url()); if (!kateProject) { return QString(); } return QDir::toNativeSeparators(QDir(kateProject->baseDir()).absolutePath()); }); #endif } void KateProjectPlugin::unregisterVariables() { #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 63, 0) auto editor = KTextEditor::Editor::instance(); editor->unregisterVariableMatch(QStringLiteral("Project:Path")); editor->unregisterVariableMatch(QStringLiteral("Project:NativePath")); #endif } diff --git a/addons/project/kateprojectplugin.h b/addons/project/kateprojectplugin.h index df935da87..16221d198 100644 --- a/addons/project/kateprojectplugin.h +++ b/addons/project/kateprojectplugin.h @@ -1,192 +1,192 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef _KATE_PROJECT_PLUGIN_H_ #define _KATE_PROJECT_PLUGIN_H_ -#include #include +#include +#include #include #include -#include #include #include "kateproject.h" #include "kateprojectcompletion.h" namespace ThreadWeaver { class Queue; } class KateProjectPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit KateProjectPlugin(QObject *parent = nullptr, const QList & = QList()); ~KateProjectPlugin() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; int configPages() const override; KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; /** * Create new project for given project filename. * Null pointer if no project can be opened. * File name will be canonicalized! * @param fileName canonicalized file name for the project * @return project or null if not openable */ KateProject *createProjectForFileName(const QString &fileName); /** * Search and open project for given dir, if possible. * Will search upwards for .kateproject file. * Will use internally projectForFileName if project file is found. * @param dir dir to search matching project for * @return project or null if not openable */ KateProject *projectForDir(QDir dir); /** * Search and open project that contains given url, if possible. * Will search upwards for .kateproject file, if the url is a local file. * Will use internally projectForDir. * @param url url to search matching project for * @return project or null if not openable */ KateProject *projectForUrl(const QUrl &url); /** * get list of all current open projects * @return list of all open projects */ QList projects() const { return m_projects; } /** * Get global code completion. * @return global completion object for KTextEditor::View */ KateProjectCompletion *completion() { return &m_completion; } /** * Map current open documents to projects. * @param document document we want to know which project it belongs to * @return project or 0 if none found for this document */ KateProject *projectForDocument(KTextEditor::Document *document) { return m_document2Project.value(document); } void setAutoRepository(bool onGit, bool onSubversion, bool onMercurial); bool autoGit() const; bool autoSubversion() const; bool autoMercurial() const; void setIndex(bool enabled, const QUrl &directory); bool getIndexEnabled() const; QUrl getIndexDirectory() const; Q_SIGNALS: /** * Signal that a new project got created. * @param project new created project */ void projectCreated(KateProject *project); public Q_SLOTS: /** * New document got created, we need to update our connections * @param document new created document */ void slotDocumentCreated(KTextEditor::Document *document); /** * Document got destroyed. * @param document deleted document */ void slotDocumentDestroyed(QObject *document); /** * Url changed, to auto-load projects */ void slotDocumentUrlChanged(KTextEditor::Document *document); /** * did some project file change? * @param path name of directory that did change */ void slotDirectoryChanged(const QString &path); private: KateProject *createProjectForRepository(const QString &type, const QDir &dir); KateProject *detectGit(const QDir &dir); KateProject *detectSubversion(const QDir &dir); KateProject *detectMercurial(const QDir &dir); void readConfig(); void writeConfig(); void registerVariables(); void unregisterVariables(); private: /** * open plugins, maps project base directory => project */ QList m_projects; /** * filesystem watcher to keep track of all project files * and auto-reload */ QFileSystemWatcher m_fileWatcher; /** * Mapping document => project */ QHash m_document2Project; /** * Project completion */ KateProjectCompletion m_completion; bool m_autoGit : 1; bool m_autoSubversion : 1; bool m_autoMercurial : 1; bool m_indexEnabled : 1; QUrl m_indexDirectory; ThreadWeaver::Queue *m_weaver; }; #endif diff --git a/addons/project/kateprojectpluginview.cpp b/addons/project/kateprojectpluginview.cpp index 6eddcee25..f745c604a 100644 --- a/addons/project/kateprojectpluginview.cpp +++ b/addons/project/kateprojectpluginview.cpp @@ -1,475 +1,475 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojectpluginview.h" -#include "kateprojectinfoviewindex.h" #include "fileutil.h" +#include "kateprojectinfoviewindex.h" -#include #include -#include -#include #include +#include +#include +#include +#include +#include +#include #include -#include +#include #include #include -#include -#include -#include -#include +#include -#include #include #include #include +#include #include K_PLUGIN_FACTORY_WITH_JSON(KateProjectPluginFactory, "kateprojectplugin.json", registerPlugin();) KateProjectPluginView::KateProjectPluginView(KateProjectPlugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_plugin(plugin) , m_mainWindow(mainWin) , m_toolView(nullptr) , m_toolInfoView(nullptr) , m_lookupAction(nullptr) { KXMLGUIClient::setComponentName(QStringLiteral("kateproject"), i18n("Kate Project Manager")); setXMLFile(QStringLiteral("ui.rc")); /** * create toolviews */ m_toolView = m_mainWindow->createToolView(m_plugin, QStringLiteral("kateproject"), KTextEditor::MainWindow::Left, QIcon::fromTheme(QStringLiteral("project-open")), i18n("Projects")); m_toolInfoView = m_mainWindow->createToolView(m_plugin, QStringLiteral("kateprojectinfo"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("view-choose")), i18n("Current Project")); /** * create the combo + buttons for the toolViews + stacked widgets */ m_projectsCombo = new QComboBox(m_toolView); m_projectsCombo->setFrame(false); m_reloadButton = new QToolButton(m_toolView); m_reloadButton->setAutoRaise(true); m_reloadButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); QHBoxLayout *layout = new QHBoxLayout(); layout->setSpacing(0); layout->addWidget(m_projectsCombo); layout->addWidget(m_reloadButton); m_toolView->layout()->addItem(layout); m_toolView->layout()->setSpacing(0); m_stackedProjectViews = new QStackedWidget(m_toolView); m_stackedProjectInfoViews = new QStackedWidget(m_toolInfoView); connect(m_projectsCombo, static_cast(&QComboBox::currentIndexChanged), this, &KateProjectPluginView::slotCurrentChanged); connect(m_reloadButton, &QToolButton::clicked, this, &KateProjectPluginView::slotProjectReload); /** * create views for all already existing projects * will create toolviews on demand! */ const auto projectList = m_plugin->projects(); for (KateProject *project : projectList) viewForProject(project); /** * connect to important signals, e.g. for auto project view creation */ connect(m_plugin, &KateProjectPlugin::projectCreated, this, &KateProjectPluginView::viewForProject); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateProjectPluginView::slotViewChanged); connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &KateProjectPluginView::slotViewCreated); /** * connect for all already existing views */ const auto views = m_mainWindow->views(); for (KTextEditor::View *view : views) slotViewCreated(view); /** * trigger once view change, to highlight right document */ slotViewChanged(); /** * back + forward */ auto a = actionCollection()->addAction(KStandardAction::Back, QStringLiteral("projects_prev_project"), this, SLOT(slotProjectPrev())); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Left)); a = actionCollection()->addAction(KStandardAction::Forward, QStringLiteral("projects_next_project"), this, SLOT(slotProjectNext())); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Right)); a = actionCollection()->addAction(KStandardAction::Goto, QStringLiteral("projects_goto_index"), this, SLOT(slotProjectIndex())); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_1)); // popup menu auto popup = new KActionMenu(i18n("Project"), this); actionCollection()->addAction(QStringLiteral("popup_project"), popup); m_lookupAction = popup->menu()->addAction(i18n("Lookup: %1", QString()), this, &KateProjectPluginView::slotProjectIndex); connect(popup->menu(), &QMenu::aboutToShow, this, &KateProjectPluginView::slotContextMenuAboutToShow); /** * add us to gui */ m_mainWindow->guiFactory()->addClient(this); } KateProjectPluginView::~KateProjectPluginView() { /** * cleanup for all views */ for (QObject *view : qAsConst(m_textViews)) { KTextEditor::CodeCompletionInterface *cci = qobject_cast(view); if (cci) { cci->unregisterCompletionModel(m_plugin->completion()); } } /** * cu toolviews */ delete m_toolView; m_toolView = nullptr; delete m_toolInfoView; m_toolInfoView = nullptr; /** * cu gui client */ m_mainWindow->guiFactory()->removeClient(this); } QPair KateProjectPluginView::viewForProject(KateProject *project) { /** * needs valid project */ Q_ASSERT(project); /** * existing view? */ if (m_project2View.contains(project)) { return m_project2View.value(project); } /** * create new views */ KateProjectView *view = new KateProjectView(this, project); KateProjectInfoView *infoView = new KateProjectInfoView(this, project); /** * attach to toolboxes * first the views, then the combo, that triggers signals */ m_stackedProjectViews->addWidget(view); m_stackedProjectInfoViews->addWidget(infoView); m_projectsCombo->addItem(QIcon::fromTheme(QStringLiteral("project-open")), project->name(), project->fileName()); /** * remember and return it */ return (m_project2View[project] = QPair(view, infoView)); } QString KateProjectPluginView::projectFileName() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QString(); } return static_cast(active)->project()->fileName(); } QString KateProjectPluginView::projectName() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QString(); } return static_cast(active)->project()->name(); } QString KateProjectPluginView::projectBaseDir() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QString(); } return static_cast(active)->project()->baseDir(); } QVariantMap KateProjectPluginView::projectMap() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QVariantMap(); } return static_cast(active)->project()->projectMap(); } QStringList KateProjectPluginView::projectFiles() const { KateProjectView *active = static_cast(m_stackedProjectViews->currentWidget()); if (!active) { return QStringList(); } return active->project()->files(); } QString KateProjectPluginView::allProjectsCommonBaseDir() const { auto projects = m_plugin->projects(); if (projects.empty()) { return QString(); } if (projects.size() == 1) { return projects[0]->baseDir(); } QString commonParent1 = FileUtil::commonParent(projects[0]->baseDir(), projects[1]->baseDir()); for (int i = 2; i < projects.size(); i++) { commonParent1 = FileUtil::commonParent(commonParent1, projects[i]->baseDir()); } return commonParent1; } QStringList KateProjectPluginView::allProjectsFiles() const { QStringList fileList; const auto projectList = m_plugin->projects(); for (auto project : projectList) { fileList.append(project->files()); } return fileList; } void KateProjectPluginView::slotViewChanged() { /** * get active view */ KTextEditor::View *activeView = m_mainWindow->activeView(); /** * update pointer, maybe disconnect before */ if (m_activeTextEditorView) { m_activeTextEditorView->document()->disconnect(this); } m_activeTextEditorView = activeView; /** * no current active view, return */ if (!m_activeTextEditorView) { return; } /** * connect to url changed, for auto load */ connect(m_activeTextEditorView->document(), &KTextEditor::Document::documentUrlChanged, this, &KateProjectPluginView::slotDocumentUrlChanged); /** * trigger slot once */ slotDocumentUrlChanged(m_activeTextEditorView->document()); } void KateProjectPluginView::slotCurrentChanged(int index) { // trigger change of stacked widgets m_stackedProjectViews->setCurrentIndex(index); m_stackedProjectInfoViews->setCurrentIndex(index); // update focus proxy + open currently selected document if (QWidget *current = m_stackedProjectViews->currentWidget()) { m_stackedProjectViews->setFocusProxy(current); static_cast(current)->openSelectedDocument(); } // update focus proxy if (QWidget *current = m_stackedProjectInfoViews->currentWidget()) { m_stackedProjectInfoViews->setFocusProxy(current); } // project file name might have changed emit projectFileNameChanged(); emit projectMapChanged(); } void KateProjectPluginView::slotDocumentUrlChanged(KTextEditor::Document *document) { /** * abort if empty url or no local path */ if (document->url().isEmpty() || !document->url().isLocalFile()) { return; } /** * search matching project */ KateProject *project = m_plugin->projectForUrl(document->url()); if (!project) { return; } /** * select the file FIRST */ m_project2View.value(project).first->selectFile(document->url().toLocalFile()); /** * get active project view and switch it, if it is for a different project * do this AFTER file selection */ KateProjectView *active = static_cast(m_stackedProjectViews->currentWidget()); if (active != m_project2View.value(project).first) { int index = m_projectsCombo->findData(project->fileName()); if (index >= 0) { m_projectsCombo->setCurrentIndex(index); } } } void KateProjectPluginView::slotViewCreated(KTextEditor::View *view) { /** * connect to destroyed */ connect(view, &KTextEditor::View::destroyed, this, &KateProjectPluginView::slotViewDestroyed); /** * add completion model if possible */ KTextEditor::CodeCompletionInterface *cci = qobject_cast(view); if (cci) { cci->registerCompletionModel(m_plugin->completion()); } /** * remember for this view we need to cleanup! */ m_textViews.insert(view); } void KateProjectPluginView::slotViewDestroyed(QObject *view) { /** * remove remembered views for which we need to cleanup on exit! */ m_textViews.remove(view); } void KateProjectPluginView::slotProjectPrev() { if (!m_projectsCombo->count()) { return; } if (m_projectsCombo->currentIndex() == 0) { m_projectsCombo->setCurrentIndex(m_projectsCombo->count() - 1); } else { m_projectsCombo->setCurrentIndex(m_projectsCombo->currentIndex() - 1); } } void KateProjectPluginView::slotProjectNext() { if (!m_projectsCombo->count()) { return; } if (m_projectsCombo->currentIndex() + 1 == m_projectsCombo->count()) { m_projectsCombo->setCurrentIndex(0); } else { m_projectsCombo->setCurrentIndex(m_projectsCombo->currentIndex() + 1); } } void KateProjectPluginView::slotProjectReload() { /** * force reload if any active project */ if (QWidget *current = m_stackedProjectViews->currentWidget()) { static_cast(current)->project()->reload(true); } } QString KateProjectPluginView::currentWord() const { KTextEditor::View *kv = m_activeTextEditorView; if (!kv) { return QString(); } if (kv->selection() && kv->selectionRange().onSingleLine()) { return kv->selectionText(); } return kv->document()->wordAt(kv->cursorPosition()); } void KateProjectPluginView::slotProjectIndex() { const QString word = currentWord(); if (!word.isEmpty()) { auto tabView = qobject_cast(m_stackedProjectInfoViews->currentWidget()); if (tabView) { if (auto codeIndex = tabView->findChild()) { tabView->setCurrentWidget(codeIndex); } } m_mainWindow->showToolView(m_toolInfoView); emit projectLookupWord(word); } } void KateProjectPluginView::slotContextMenuAboutToShow() { const QString word = currentWord(); if (word.isEmpty()) { return; } const QString squeezed = KStringHandler::csqueeze(word, 30); m_lookupAction->setText(i18n("Lookup: %1", squeezed)); } #include "kateprojectpluginview.moc" diff --git a/addons/project/kateprojectpluginview.h b/addons/project/kateprojectpluginview.h index 587e9792b..e8ac1db93 100644 --- a/addons/project/kateprojectpluginview.h +++ b/addons/project/kateprojectpluginview.h @@ -1,256 +1,256 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef _KATE_PROJECT_PLUGIN_VIEW_H_ #define _KATE_PROJECT_PLUGIN_VIEW_H_ -#include "kateprojectplugin.h" #include "kateproject.h" -#include "kateprojectview.h" #include "kateprojectinfoview.h" +#include "kateprojectplugin.h" +#include "kateprojectview.h" -#include #include +#include #include #include #include class QAction; class KateProjectPluginView : public QObject, public KXMLGUIClient { Q_OBJECT Q_PROPERTY(QString projectFileName READ projectFileName NOTIFY projectFileNameChanged) Q_PROPERTY(QString projectName READ projectName) Q_PROPERTY(QString projectBaseDir READ projectBaseDir) Q_PROPERTY(QVariantMap projectMap READ projectMap NOTIFY projectMapChanged) Q_PROPERTY(QStringList projectFiles READ projectFiles) Q_PROPERTY(QString allProjectsCommonBaseDir READ allProjectsCommonBaseDir) Q_PROPERTY(QStringList allProjectsFiles READ allProjectsFiles) public: KateProjectPluginView(KateProjectPlugin *plugin, KTextEditor::MainWindow *mainWindow); ~KateProjectPluginView() override; /** * content of current active project, as variant map * @return empty map if no project active, else content of project JSON */ QVariantMap projectMap() const; /** * which project file is currently active? * @return empty string if none, else project file name */ QString projectFileName() const; /** * Returns the name of the project */ QString projectName() const; /** * Returns the base directory of the project */ QString projectBaseDir() const; /** * files for the current active project? * @return empty list if none, else project files as stringlist */ QStringList projectFiles() const; /** * Example: Two projects are loaded with baseDir1="/home/dev/project1" and * baseDir2="/home/dev/project2". Then "/home/dev/" is returned. * @see projectBaseDir(). * Used for the Search&Replace plugin for option "Search in all open projects". */ QString allProjectsCommonBaseDir() const; /** * @returns a flat list of files for all open projects (@see also projectFiles()) */ QStringList allProjectsFiles() const; /** * the main window we belong to * @return our main window */ KTextEditor::MainWindow *mainWindow() const { return m_mainWindow; } public Q_SLOTS: /** * Create views for given project. * Either gives existing ones or creates new one * @param project project we want view for * @return views (normal + info view) */ QPair viewForProject(KateProject *project); private Q_SLOTS: /** * New view got created, we need to update our connections * @param view new created view */ void slotViewCreated(KTextEditor::View *view); /** * View got destroyed. * @param view deleted view */ void slotViewDestroyed(QObject *view); /** * Activate the previous project. */ void slotProjectPrev(); /** * Activate the next project. */ void slotProjectNext(); /** * Reload current project, if any. * This will trigger a reload with force. */ void slotProjectReload(); /** * Lookup current word */ void slotProjectIndex(); Q_SIGNALS: /** * Emitted if projectFileName changed. */ void projectFileNameChanged(); /** * Emitted if projectMap changed. */ void projectMapChanged(); /** * Emitted when a ctags lookup in requested * @param word lookup word */ void projectLookupWord(const QString &word); private Q_SLOTS: /** * This slot is called whenever the active view changes in our main window. */ void slotViewChanged(); /** * Current project changed. * @param index index in toolbox */ void slotCurrentChanged(int index); /** * Url changed, to auto-load projects */ void slotDocumentUrlChanged(KTextEditor::Document *document); /** * Show context menu */ void slotContextMenuAboutToShow(); private: /** * find current selected or under cursor word */ QString currentWord() const; private: /** * our plugin */ KateProjectPlugin *m_plugin; /** * the main window we belong to */ KTextEditor::MainWindow *m_mainWindow; /** * our projects toolview */ QWidget *m_toolView; /** * our projects info toolview */ QWidget *m_toolInfoView; /** * combo box with all loaded projects inside */ QComboBox *m_projectsCombo; /** * Reload button */ QToolButton *m_reloadButton; /** * stacked widget will all currently created project views */ QStackedWidget *m_stackedProjectViews; /** * stacked widget will all currently created project info views */ QStackedWidget *m_stackedProjectInfoViews; /** * project => view */ QMap> m_project2View; /** * remember current active view text editor view * might be 0 */ QPointer m_activeTextEditorView; /** * remember for which text views we might need to cleanup stuff */ QSet m_textViews; /** * lookup action */ QAction *m_lookupAction; }; #endif diff --git a/addons/project/kateprojecttreeviewcontextmenu.cpp b/addons/project/kateprojecttreeviewcontextmenu.cpp index 522804813..71c19346f 100644 --- a/addons/project/kateprojecttreeviewcontextmenu.cpp +++ b/addons/project/kateprojecttreeviewcontextmenu.cpp @@ -1,156 +1,156 @@ /* This file is part of the Kate project. * * Copyright (C) 2013 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojecttreeviewcontextmenu.h" -#include #include #include -#include -#include #include #include +#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include KateProjectTreeViewContextMenu::KateProjectTreeViewContextMenu() { } KateProjectTreeViewContextMenu::~KateProjectTreeViewContextMenu() { } static bool isGit(const QString &filename) { QFileInfo fi(filename); QDir dir(fi.absoluteDir()); QProcess git; git.setWorkingDirectory(dir.absolutePath()); QStringList args; // git ls-files -z results a bytearray where each entry is \0-terminated. // NOTE: Without -z, Umlauts such as "Der Bäcker/Das Brötchen.txt" do not work (#389415, #402213) args << QStringLiteral("ls-files") << QStringLiteral("-z") << fi.fileName(); git.start(QStringLiteral("git"), args); bool isGit = false; if (git.waitForStarted() && git.waitForFinished(-1)) { const QList byteArrayList = git.readAllStandardOutput().split('\0'); const QString fn = fi.fileName(); for (const QByteArray &byteArray : byteArrayList) { if (fn == QString::fromUtf8(byteArray)) { isGit = true; break; } } } return isGit; } void KateProjectTreeViewContextMenu::exec(const QString &filename, const QPoint &pos, QWidget *parent) { /** * Create context menu */ QMenu menu; /** * Copy Path */ QAction *copyAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy File Path")); /** * Handle "open with", * find correct mimetype to query for possible applications */ QMenu *openWithMenu = menu.addMenu(i18n("Open With")); QMimeType mimeType = QMimeDatabase().mimeTypeForFile(filename); const KService::List offers = KMimeTypeTrader::self()->query(mimeType.name(), QStringLiteral("Application")); // For each one, insert a menu item... for (const auto &service : offers) { if (service->name() == QLatin1String("Kate")) { continue; // omit Kate } QAction *action = openWithMenu->addAction(QIcon::fromTheme(service->icon()), service->name()); action->setData(service->entryPath()); } // Perhaps disable menu, if no entries openWithMenu->setEnabled(!openWithMenu->isEmpty()); /** * Open Containing folder */ auto openContaingFolderAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("&Open Containing Folder")); /** * File Properties Dialog */ auto filePropertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-object-properties")), i18n("Properties")); /** * Git menu */ KMoreToolsMenuFactory menuFactory(QStringLiteral("kate/addons/project/git-tools")); QMenu gitMenu; // must live as long as the maybe filled menu items should live if (isGit(filename)) { menuFactory.fillMenuFromGroupingNames(&gitMenu, {QLatin1String("git-clients-and-actions")}, QUrl::fromLocalFile(filename)); menu.addSection(i18n("Git:")); const auto gitActions = gitMenu.actions(); for (auto action : gitActions) { menu.addAction(action); } } /** * run menu and handle the triggered action */ if (QAction *const action = menu.exec(pos)) { if (action == copyAction) { QApplication::clipboard()->setText(filename); } else if (action->parentWidget() == openWithMenu) { // handle "open with" const QString openWith = action->data().toString(); if (KService::Ptr app = KService::serviceByDesktopPath(openWith)) { QList list; list << QUrl::fromLocalFile(filename); KRun::runService(*app, list, parent); } } else if (action == openContaingFolderAction) { KIO::highlightInFileManager({QUrl::fromLocalFile(filename)}); } else if (action == filePropertiesAction) { // code copied and adapted from frameworks/kio/src/filewidgets/knewfilemenu.cpp KFileItem fileItem(QUrl::fromLocalFile(filename)); QDialog *dlg = new KPropertiesDialog(fileItem); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->show(); } else { // One of the git actions was triggered } } } diff --git a/addons/project/kateprojecttreeviewcontextmenu.h b/addons/project/kateprojecttreeviewcontextmenu.h index 20900538d..fe9393f1f 100644 --- a/addons/project/kateprojecttreeviewcontextmenu.h +++ b/addons/project/kateprojecttreeviewcontextmenu.h @@ -1,53 +1,53 @@ /* This file is part of the Kate project. * * Copyright (C) 2013 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KATE_PROJECT_TREE_VIEW_CONTEXT_MENU_H #define KATE_PROJECT_TREE_VIEW_CONTEXT_MENU_H -#include #include +#include class QWidget; class KateProjectTreeViewContextMenu { public: /** * construct project view for given project * @param pluginView our plugin view * @param project project this view is for */ KateProjectTreeViewContextMenu(); /** * deconstruct project */ ~KateProjectTreeViewContextMenu(); /** * our project. * @return project */ void exec(const QString &filename, const QPoint &pos, QWidget *parent); protected: }; #endif diff --git a/addons/project/kateprojectview.cpp b/addons/project/kateprojectview.cpp index 1b67a7bf9..59034be9e 100644 --- a/addons/project/kateprojectview.cpp +++ b/addons/project/kateprojectview.cpp @@ -1,89 +1,89 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojectview.h" #include "kateprojectpluginview.h" #include #include #include #include #include -#include #include +#include KateProjectView::KateProjectView(KateProjectPluginView *pluginView, KateProject *project) : QWidget() , m_pluginView(pluginView) , m_project(project) , m_treeView(new KateProjectViewTree(pluginView, project)) , m_filter(new KLineEdit()) { /** * layout tree view and co. */ QVBoxLayout *layout = new QVBoxLayout(); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_treeView); layout->addWidget(m_filter); setLayout(layout); // let tree get focus for keyboard selection of file to open setFocusProxy(m_treeView); /** * setup filter line edit */ m_filter->setPlaceholderText(i18n("Filter...")); m_filter->setClearButtonEnabled(true); connect(m_filter, &KLineEdit::textChanged, this, &KateProjectView::filterTextChanged); } KateProjectView::~KateProjectView() { } void KateProjectView::selectFile(const QString &file) { m_treeView->selectFile(file); } void KateProjectView::openSelectedDocument() { m_treeView->openSelectedDocument(); } void KateProjectView::filterTextChanged(const QString &filterText) { /** * filter */ static_cast(m_treeView->model())->setFilterFixedString(filterText); /** * expand */ if (!filterText.isEmpty()) { QTimer::singleShot(100, m_treeView, &QTreeView::expandAll); } } diff --git a/addons/project/kateprojectworker.cpp b/addons/project/kateprojectworker.cpp index 8bad8f156..e7d6d7392 100644 --- a/addons/project/kateprojectworker.cpp +++ b/addons/project/kateprojectworker.cpp @@ -1,493 +1,493 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojectworker.h" #include "kateproject.h" #include #include #include #include #include #include #include -#include #include +#include KateProjectWorker::KateProjectWorker(const QString &baseDir, const QString &indexDir, const QVariantMap &projectMap, bool force) : QObject() , ThreadWeaver::Job() , m_baseDir(baseDir) , m_indexDir(indexDir) , m_projectMap(projectMap) , m_force(force) { Q_ASSERT(!m_baseDir.isEmpty()); } void KateProjectWorker::run(ThreadWeaver::JobPointer, ThreadWeaver::Thread *) { /** * Create dummy top level parent item and empty map inside shared pointers * then load the project recursively */ KateProjectSharedQStandardItem topLevel(new QStandardItem()); KateProjectSharedQMapStringItem file2Item(new QMap()); loadProject(topLevel.data(), m_projectMap, file2Item.data()); /** * create some local backup of some data we need for further processing! */ QStringList files = file2Item->keys(); emit loadDone(topLevel, file2Item); // trigger index loading, will internally handle enable/disabled loadIndex(files, m_force); } void KateProjectWorker::loadProject(QStandardItem *parent, const QVariantMap &project, QMap *file2Item) { /** * recurse to sub-projects FIRST */ QVariantList subGroups = project[QStringLiteral("projects")].toList(); for (const QVariant &subGroupVariant : subGroups) { /** * convert to map and get name, else skip */ QVariantMap subProject = subGroupVariant.toMap(); const QString keyName = QStringLiteral("name"); if (subProject[keyName].toString().isEmpty()) { continue; } /** * recurse */ QStandardItem *subProjectItem = new KateProjectItem(KateProjectItem::Project, subProject[keyName].toString()); loadProject(subProjectItem, subProject, file2Item); parent->appendRow(subProjectItem); } /** * load all specified files */ const QString keyFiles = QStringLiteral("files"); QVariantList files = project[keyFiles].toList(); for (const QVariant &fileVariant : files) { loadFilesEntry(parent, fileVariant.toMap(), file2Item); } } /** * small helper to construct directory parent items * @param dir2Item map for path => item * @param path current path we need item for * @return correct parent item for given path, will reuse existing ones */ static QStandardItem *directoryParent(QMap &dir2Item, QString path) { /** * throw away simple / */ if (path == QLatin1String("/")) { path = QString(); } /** * quick check: dir already seen? */ if (dir2Item.contains(path)) { return dir2Item[path]; } /** * else: construct recursively */ int slashIndex = path.lastIndexOf(QLatin1Char('/')); /** * no slash? * simple, no recursion, append new item toplevel */ if (slashIndex < 0) { dir2Item[path] = new KateProjectItem(KateProjectItem::Directory, path); dir2Item[QString()]->appendRow(dir2Item[path]); return dir2Item[path]; } /** * else, split and recurse */ const QString leftPart = path.left(slashIndex); const QString rightPart = path.right(path.size() - (slashIndex + 1)); /** * special handling if / with nothing on one side are found */ if (leftPart.isEmpty() || rightPart.isEmpty()) { return directoryParent(dir2Item, leftPart.isEmpty() ? rightPart : leftPart); } /** * else: recurse on left side */ dir2Item[path] = new KateProjectItem(KateProjectItem::Directory, rightPart); directoryParent(dir2Item, leftPart)->appendRow(dir2Item[path]); return dir2Item[path]; } void KateProjectWorker::loadFilesEntry(QStandardItem *parent, const QVariantMap &filesEntry, QMap *file2Item) { QDir dir(m_baseDir); if (!dir.cd(filesEntry[QStringLiteral("directory")].toString())) { return; } QStringList files = findFiles(dir, filesEntry); if (files.isEmpty()) { return; } files.sort(Qt::CaseInsensitive); /** * construct paths first in tree and items in a map */ QMap dir2Item; dir2Item[QString()] = parent; QList> item2ParentPath; for (const QString &filePath : files) { /** * skip dupes */ if (file2Item->contains(filePath)) { continue; } /** * get file info and skip NON-files */ QFileInfo fileInfo(filePath); if (!fileInfo.isFile()) { continue; } /** * construct the item with right directory prefix * already hang in directories in tree */ KateProjectItem *fileItem = new KateProjectItem(KateProjectItem::File, fileInfo.fileName()); fileItem->setData(filePath, Qt::ToolTipRole); // get the directory's relative path to the base directory QString dirRelPath = dir.relativeFilePath(fileInfo.absolutePath()); // if the relative path is ".", clean it up if (dirRelPath == QLatin1Char('.')) { dirRelPath = QString(); } item2ParentPath.append(QPair(fileItem, directoryParent(dir2Item, dirRelPath))); fileItem->setData(filePath, Qt::UserRole); (*file2Item)[filePath] = fileItem; } /** * plug in the file items to the tree */ auto i = item2ParentPath.constBegin(); while (i != item2ParentPath.constEnd()) { i->second->appendRow(i->first); ++i; } } QStringList KateProjectWorker::findFiles(const QDir &dir, const QVariantMap &filesEntry) { const bool recursive = !filesEntry.contains(QLatin1String("recursive")) || filesEntry[QStringLiteral("recursive")].toBool(); if (filesEntry[QStringLiteral("git")].toBool()) { return filesFromGit(dir, recursive); } else if (filesEntry[QStringLiteral("hg")].toBool()) { return filesFromMercurial(dir, recursive); } else if (filesEntry[QStringLiteral("svn")].toBool()) { return filesFromSubversion(dir, recursive); } else if (filesEntry[QStringLiteral("darcs")].toBool()) { return filesFromDarcs(dir, recursive); } else { QStringList files = filesEntry[QStringLiteral("list")].toStringList(); if (files.empty()) { QStringList filters = filesEntry[QStringLiteral("filters")].toStringList(); files = filesFromDirectory(dir, recursive, filters); } return files; } } QStringList KateProjectWorker::filesFromGit(const QDir &dir, bool recursive) { /** * query files via ls-files and make them absolute afterwards */ const QStringList relFiles = gitLsFiles(dir); QStringList files; for (const QString &relFile : relFiles) { if (!recursive && (relFile.indexOf(QLatin1Char('/')) != -1)) { continue; } files.append(dir.absolutePath() + QLatin1Char('/') + relFile); } return files; } QStringList KateProjectWorker::gitLsFiles(const QDir &dir) { /** * git ls-files -z results a bytearray where each entry is \0-terminated. * NOTE: Without -z, Umlauts such as "Der Bäcker/Das Brötchen.txt" do not work (#389415) * * use --recurse-submodules, there since git 2.11 (released 2016) * our own submodules handling code leads to file duplicates */ QStringList args; args << QStringLiteral("ls-files") << QStringLiteral("-z") << QStringLiteral("--recurse-submodules") << QStringLiteral("."); QProcess git; git.setWorkingDirectory(dir.absolutePath()); git.start(QStringLiteral("git"), args); QStringList files; if (!git.waitForStarted() || !git.waitForFinished(-1)) { return files; } const QList byteArrayList = git.readAllStandardOutput().split('\0'); for (const QByteArray &byteArray : byteArrayList) { files << QString::fromUtf8(byteArray); } return files; } QStringList KateProjectWorker::filesFromMercurial(const QDir &dir, bool recursive) { QStringList files; QProcess hg; hg.setWorkingDirectory(dir.absolutePath()); QStringList args; args << QStringLiteral("manifest") << QStringLiteral("."); hg.start(QStringLiteral("hg"), args); if (!hg.waitForStarted() || !hg.waitForFinished(-1)) { return files; } const QStringList relFiles = QString::fromLocal8Bit(hg.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), QString::SkipEmptyParts); for (const QString &relFile : relFiles) { if (!recursive && (relFile.indexOf(QLatin1Char('/')) != -1)) { continue; } files.append(dir.absolutePath() + QLatin1Char('/') + relFile); } return files; } QStringList KateProjectWorker::filesFromSubversion(const QDir &dir, bool recursive) { QStringList files; QProcess svn; svn.setWorkingDirectory(dir.absolutePath()); QStringList args; args << QStringLiteral("status") << QStringLiteral("--verbose") << QStringLiteral("."); if (recursive) { args << QStringLiteral("--depth=infinity"); } else { args << QStringLiteral("--depth=files"); } svn.start(QStringLiteral("svn"), args); if (!svn.waitForStarted() || !svn.waitForFinished(-1)) { return files; } /** * get output and split up into lines */ const QStringList lines = QString::fromLocal8Bit(svn.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), QString::SkipEmptyParts); /** * remove start of line that is no filename, sort out unknown and ignore */ bool first = true; int prefixLength = -1; for (const QString &line : lines) { /** * get length of stuff to cut */ if (first) { /** * try to find ., else fail */ prefixLength = line.lastIndexOf(QLatin1Char('.')); if (prefixLength < 0) { break; } /** * skip first */ first = false; continue; } /** * get file, if not unknown or ignored * prepend directory path */ if ((line.size() > prefixLength) && line[0] != QLatin1Char('?') && line[0] != QLatin1Char('I')) { files.append(dir.absolutePath() + QLatin1Char('/') + line.right(line.size() - prefixLength)); } } return files; } QStringList KateProjectWorker::filesFromDarcs(const QDir &dir, bool recursive) { QStringList files; const QString cmd = QStringLiteral("darcs"); QString root; { QProcess darcs; darcs.setWorkingDirectory(dir.absolutePath()); QStringList args; args << QStringLiteral("list") << QStringLiteral("repo"); darcs.start(cmd, args); if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) return files; auto str = QString::fromLocal8Bit(darcs.readAllStandardOutput()); QRegularExpression exp(QStringLiteral("Root: ([^\\n\\r]*)")); auto match = exp.match(str); if (!match.hasMatch()) return files; root = match.captured(1); } QStringList relFiles; { QProcess darcs; QStringList args; darcs.setWorkingDirectory(dir.absolutePath()); args << QStringLiteral("list") << QStringLiteral("files") << QStringLiteral("--no-directories") << QStringLiteral("--pending"); darcs.start(cmd, args); if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) return files; relFiles = QString::fromLocal8Bit(darcs.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), QString::SkipEmptyParts); } for (const QString &relFile : relFiles) { const QString path = dir.relativeFilePath(root + QLatin1String("/") + relFile); if ((!recursive && (relFile.indexOf(QLatin1Char('/')) != -1)) || (recursive && (relFile.indexOf(QLatin1String("..")) == 0))) { continue; } files.append(dir.absoluteFilePath(path)); } return files; } QStringList KateProjectWorker::filesFromDirectory(const QDir &_dir, bool recursive, const QStringList &filters) { QStringList files; QDir dir(_dir); dir.setFilter(QDir::Files); if (!filters.isEmpty()) { dir.setNameFilters(filters); } /** * construct flags for iterator */ QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags; if (recursive) { flags = flags | QDirIterator::Subdirectories; } /** * create iterator and collect all files */ QDirIterator dirIterator(dir, flags); while (dirIterator.hasNext()) { dirIterator.next(); files.append(dirIterator.filePath()); } return files; } void KateProjectWorker::loadIndex(const QStringList &files, bool force) { const QString keyCtags = QStringLiteral("ctags"); const QVariantMap ctagsMap = m_projectMap[keyCtags].toMap(); /** * load index, if enabled * before this was default on, which is dangerous for large repositories, e.g. out-of-memory or out-of-disk * if specified in project map; use that setting, otherwise fall back to global setting */ bool indexEnabled = !m_indexDir.isEmpty(); auto indexValue = ctagsMap[QStringLiteral("enable")]; if (!indexValue.isNull()) { indexEnabled = indexValue.toBool(); } if (!indexEnabled) { emit loadIndexDone(KateProjectSharedProjectIndex()); return; } /** * create new index, this will do the loading in the constructor * wrap it into shared pointer for transfer to main thread */ KateProjectSharedProjectIndex index(new KateProjectIndex(m_baseDir, m_indexDir, files, ctagsMap, force)); emit loadIndexDone(index); } diff --git a/addons/project/kateprojectworker.h b/addons/project/kateprojectworker.h index ce14d584c..fb39e39c8 100644 --- a/addons/project/kateprojectworker.h +++ b/addons/project/kateprojectworker.h @@ -1,110 +1,110 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KATE_PROJECT_WORKER_H #define KATE_PROJECT_WORKER_H -#include "kateprojectitem.h" #include "kateproject.h" +#include "kateprojectitem.h" #include -#include #include +#include class QDir; /** * Class representing a project background worker. * This worker will build up the model for the project on load and do other stuff in the background. */ class KateProjectWorker : public QObject, public ThreadWeaver::Job { Q_OBJECT public: /** * Type for QueuedConnection */ typedef QMap MapString2Item; explicit KateProjectWorker(const QString &baseDir, const QString &indexDir, const QVariantMap &projectMap, bool force); void run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread) override; Q_SIGNALS: void loadDone(KateProjectSharedQStandardItem topLevel, KateProjectSharedQMapStringItem file2Item); void loadIndexDone(KateProjectSharedProjectIndex index); private: /** * Load one project inside the project tree. * Fill data from JSON storage to model and recurse to sub-projects. * @param parent parent standard item in the model * @param project variant map for this group * @param file2Item mapping file => item, will be filled */ void loadProject(QStandardItem *parent, const QVariantMap &project, QMap *file2Item); /** * Load one files entry in the current parent item. * @param parent parent standard item in the model * @param filesEntry one files entry specification to load * @param file2Item mapping file => item, will be filled */ void loadFilesEntry(QStandardItem *parent, const QVariantMap &filesEntry, QMap *file2Item); /** * Load index for whole project. * @param files list of all project files to index */ void loadIndex(const QStringList &files, bool force); QStringList findFiles(const QDir &dir, const QVariantMap &filesEntry); QStringList filesFromGit(const QDir &dir, bool recursive); QStringList filesFromMercurial(const QDir &dir, bool recursive); QStringList filesFromSubversion(const QDir &dir, bool recursive); QStringList filesFromDarcs(const QDir &dir, bool recursive); QStringList filesFromDirectory(const QDir &dir, bool recursive, const QStringList &filters); QStringList gitLsFiles(const QDir &dir); private: /** * our project, only as QObject, we only send messages back and forth! */ QObject *m_project; /** * project base directory name */ const QString m_baseDir; /** * index directory */ const QString m_indexDir; const QVariantMap m_projectMap; const bool m_force; }; #endif diff --git a/addons/project/tools/kateprojectcodeanalysistoolcppcheck.cpp b/addons/project/tools/kateprojectcodeanalysistoolcppcheck.cpp index 4f38f44f2..298e44fcb 100644 --- a/addons/project/tools/kateprojectcodeanalysistoolcppcheck.cpp +++ b/addons/project/tools/kateprojectcodeanalysistoolcppcheck.cpp @@ -1,93 +1,93 @@ /* This file is part of the Kate project. * * Copyright (C) 2017 Héctor Mesa Jiménez * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "kateprojectcodeanalysistoolcppcheck.h" -#include #include +#include #include KateProjectCodeAnalysisToolCppcheck::KateProjectCodeAnalysisToolCppcheck(QObject *parent) : KateProjectCodeAnalysisTool(parent) { } KateProjectCodeAnalysisToolCppcheck::~KateProjectCodeAnalysisToolCppcheck() { } QString KateProjectCodeAnalysisToolCppcheck::name() const { return i18n("Cppcheck (C++)"); } QString KateProjectCodeAnalysisToolCppcheck::description() const { return i18n("Cppcheck is a static analysis tool for C/C++ code"); } QString KateProjectCodeAnalysisToolCppcheck::fileExtensions() const { return QStringLiteral("cpp|cxx|cc|c++|c|tpp|txx"); } QStringList KateProjectCodeAnalysisToolCppcheck::filter(const QStringList &files) const { // c++ files return files.filter(QRegularExpression(QStringLiteral("\\.(") + fileExtensions().replace(QStringLiteral("+"), QStringLiteral("\\+")) + QStringLiteral(")$"))); } QString KateProjectCodeAnalysisToolCppcheck::path() const { return QStringLiteral("cppcheck"); } QStringList KateProjectCodeAnalysisToolCppcheck::arguments() { QStringList _args; _args << QStringLiteral("-q") << QStringLiteral("-f") << QStringLiteral("-j") + QString::number(QThread::idealThreadCount()) << QStringLiteral("--inline-suppr") << QStringLiteral("--enable=all") << QStringLiteral("--template={file}////{line}////{severity}////{message}") << QStringLiteral("--file-list=-"); return _args; } QString KateProjectCodeAnalysisToolCppcheck::notInstalledMessage() const { return i18n("Please install 'cppcheck'."); } QStringList KateProjectCodeAnalysisToolCppcheck::parseLine(const QString &line) const { return line.split(QRegularExpression(QStringLiteral("////")), QString::SkipEmptyParts); } QString KateProjectCodeAnalysisToolCppcheck::stdinMessages() { // filenames are written to stdin (--file-list=-) if (!m_project) { return QString(); } auto &&fileList = filter(m_project->files()); setActualFilesCount(fileList.size()); return fileList.join(QLatin1Char('\n')); } diff --git a/addons/replicode/replicodeconfig.cpp b/addons/replicode/replicodeconfig.cpp index 07fd54187..1ee4002f8 100644 --- a/addons/replicode/replicodeconfig.cpp +++ b/addons/replicode/replicodeconfig.cpp @@ -1,143 +1,143 @@ /* This file is part of the KDE project Copyright (C) 2014 Martin Sandsmark This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "replicodeconfig.h" -#include "ui_config.h" #include "replicodesettings.h" +#include "ui_config.h" enum TraceLevels { CompositeInputs = 1 << 0, CompositeOutputs = 1 << 1, ModelInputs = 1 << 2, ModelOutputs = 1 << 3, PredictionMonitoring = 1 << 4, GoalMonitoring = 1 << 5, ModelRevision = 1 << 6, ModelCompositeInjection = 1 << 7 }; ReplicodeConfig::ReplicodeConfig(QWidget *parent) : QTabWidget(parent) , m_ui(new Ui_tabWidget) , m_settings(new ReplicodeSettings(this)) { m_ui->setupUi(this); load(); } ReplicodeConfig::~ReplicodeConfig() { delete m_ui; } void ReplicodeConfig::load() { m_ui->basePeriod->setValue(m_settings->basePeriod); m_ui->debug->setChecked(m_settings->debug); m_ui->debugWindows->setValue(m_settings->debugWindows); m_ui->decObjFile->setText(m_settings->decompilationFilePath); m_ui->decompileModels->setChecked(m_settings->decompileModels); m_ui->decompileObjects->setChecked(m_settings->decompileObjects); m_ui->dumpModelsFile->setText(m_settings->modelsPath); m_ui->dumpObjFile->setText(m_settings->objectsPath); m_ui->floatTolerance->setValue(m_settings->floatTolerance); m_ui->goalPredSuccessRes->setValue(m_settings->goalPredictionSuccessResilience); m_ui->ignoreNamedModels->setChecked(m_settings->ignoreNamedModels); m_ui->maxSimTimeHoriz->setValue(m_settings->maximumSimulationTimeHorizon); m_ui->minSimTimeHoriz->setValue(m_settings->minimumSimulationTimeHorizon); m_ui->modelInertiacount->setValue(m_settings->mdlInertiaCountThreshold); m_ui->modelInertiaSuccessRate->setValue(m_settings->mdlInertiaSuccessRateThreshold); m_ui->notifMarkerRes->setValue(m_settings->notificationMarkerResilience); m_ui->perfSamplePeriod->setValue(m_settings->perfSamplingPeriod); m_ui->primaryTimeHoriz->setValue(m_settings->primaryTimeHorizon); m_ui->probeLevel->setValue(m_settings->probeLevel); m_ui->reductionCoreCount->setValue(m_settings->reductionCoreCount); m_ui->runTime->setValue(m_settings->runTime); m_ui->secondaryTimeHoriz->setValue(m_settings->secondaryTimeHorizon); m_ui->simTimeHoriz->setValue(m_settings->simulationTimeHorizon); m_ui->testModels->setChecked(m_settings->testModels); m_ui->testObj->setChecked(m_settings->testObjects); m_ui->timeCoreCount->setValue(m_settings->timeCoreCount); m_ui->timerTolerance->setValue(m_settings->timeTolerance); m_ui->tpxDeltaSuccessRate->setValue(m_settings->tpxDeltaSuccessRateThreshold); m_ui->tpxTimeHoriz->setValue(m_settings->tpxTimehorizon); m_ui->userClassPath->setText(m_settings->userClassPath); m_ui->userModulePath->setText(m_settings->userOperatorPath); m_ui->traceCompInputs->setChecked(m_settings->traceLevels & CompositeInputs); m_ui->traceCompOutputs->setChecked(m_settings->traceLevels & CompositeOutputs); m_ui->traceModelIn->setChecked(m_settings->traceLevels & ModelInputs); m_ui->traceModelOut->setChecked(m_settings->traceLevels & ModelOutputs); m_ui->tracePredMon->setChecked(m_settings->traceLevels & PredictionMonitoring); m_ui->traceGoalMon->setChecked(m_settings->traceLevels & GoalMonitoring); m_ui->traceModelRev->setChecked(m_settings->traceLevels & ModelRevision); m_ui->traceModComInj->setChecked(m_settings->traceLevels & ModelCompositeInjection); } void ReplicodeConfig::save() { m_settings->basePeriod = m_ui->basePeriod->value(); m_settings->debug = m_ui->debug->isChecked(); m_settings->debugWindows = m_ui->debugWindows->value(); m_settings->decompilationFilePath = m_ui->decObjFile->text(); m_settings->decompileModels = m_ui->decompileModels->isChecked(); m_settings->decompileObjects = m_ui->decompileObjects->isChecked(); m_settings->modelsPath = m_ui->dumpModelsFile->text(); m_settings->objectsPath = m_ui->dumpObjFile->text(); m_settings->floatTolerance = m_ui->floatTolerance->value(); m_settings->goalPredictionSuccessResilience = m_ui->goalPredSuccessRes->value(); m_settings->ignoreNamedModels = m_ui->ignoreNamedModels->isChecked(); m_settings->maximumSimulationTimeHorizon = m_ui->maxSimTimeHoriz->value(); m_settings->minimumSimulationTimeHorizon = m_ui->minSimTimeHoriz->value(); m_settings->mdlInertiaCountThreshold = m_ui->modelInertiacount->value(); m_settings->mdlInertiaSuccessRateThreshold = m_ui->modelInertiaSuccessRate->value(); m_settings->notificationMarkerResilience = m_ui->notifMarkerRes->value(); m_settings->perfSamplingPeriod = m_ui->perfSamplePeriod->value(); m_settings->primaryTimeHorizon = m_ui->primaryTimeHoriz->value(); m_settings->probeLevel = m_ui->probeLevel->value(); m_settings->reductionCoreCount = m_ui->reductionCoreCount->value(); m_settings->runTime = m_ui->runTime->value(); m_settings->secondaryTimeHorizon = m_ui->secondaryTimeHoriz->value(); m_settings->simulationTimeHorizon = m_ui->simTimeHoriz->value(); m_settings->testModels = m_ui->testModels->isChecked(); m_settings->testObjects = m_ui->testObj->isChecked(); m_settings->timeCoreCount = m_ui->timeCoreCount->value(); m_settings->timeTolerance = m_ui->timerTolerance->value(); m_settings->tpxDeltaSuccessRateThreshold = m_ui->tpxDeltaSuccessRate->value(); m_settings->tpxTimehorizon = m_ui->tpxTimeHoriz->value(); m_settings->userClassPath = m_ui->userClassPath->text(); m_settings->userOperatorPath = m_ui->userModulePath->text(); int trace = 0; if (m_ui->traceCompInputs->isChecked()) trace |= CompositeInputs; if (m_ui->traceCompOutputs->isChecked()) trace |= CompositeOutputs; if (m_ui->traceModelIn->isChecked()) trace |= ModelInputs; if (m_ui->traceModelOut->isChecked()) trace |= ModelOutputs; if (m_ui->tracePredMon->isChecked()) trace |= PredictionMonitoring; if (m_ui->traceGoalMon->isChecked()) trace |= GoalMonitoring; if (m_ui->traceModelRev->isChecked()) trace |= ModelRevision; if (m_ui->traceModComInj->isChecked()) trace |= ModelCompositeInjection; m_settings->traceLevels = trace; m_settings->save(); } void ReplicodeConfig::reset() { m_settings->setDefaults(); load(); } diff --git a/addons/replicode/replicodeconfig.h b/addons/replicode/replicodeconfig.h index 5efc0e263..fd3da77d4 100644 --- a/addons/replicode/replicodeconfig.h +++ b/addons/replicode/replicodeconfig.h @@ -1,51 +1,51 @@ /* This file is part of the KDE project Copyright (C) 2014 Martin Sandsmark This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef REPLICODECONFIG_H #define REPLICODECONFIG_H -#include #include +#include class Ui_tabWidget; class ReplicodeSettings; class ReplicodeConfig : public QTabWidget { Q_OBJECT public: explicit ReplicodeConfig(QWidget *parent = nullptr); ~ReplicodeConfig() override; public Q_SLOTS: void reset(); void save(); void load(); ReplicodeSettings *settingsObject() { save(); return m_settings; } private: Ui_tabWidget *m_ui; ReplicodeSettings *m_settings; }; #endif // REPLICODECONFIG_H diff --git a/addons/replicode/replicodeconfigpage.cpp b/addons/replicode/replicodeconfigpage.cpp index 2f5728465..f423f8fe0 100644 --- a/addons/replicode/replicodeconfigpage.cpp +++ b/addons/replicode/replicodeconfigpage.cpp @@ -1,80 +1,80 @@ /* This file is part of the KDE project Copyright (C) 2014 Martin Sandsmark This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "replicodeconfigpage.h" #include "replicodeconfig.h" -#include -#include #include +#include #include +#include #include -#include -#include #include +#include #include +#include ReplicodeConfigPage::ReplicodeConfigPage(QWidget *parent) : KTextEditor::ConfigPage(parent) , m_config(new ReplicodeConfig(this)) { QGridLayout *gridlayout = new QGridLayout; setLayout(gridlayout); gridlayout->addWidget(new QLabel(i18n("Path to replicode executor:")), 0, 0); m_requester = new KUrlRequester; m_requester->setMode(KFile::File | KFile::ExistingOnly); gridlayout->addWidget(m_requester, 0, 1); gridlayout->addWidget(m_config, 1, 0, 1, 2); reset(); connect(m_requester, &KUrlRequester::textChanged, this, &ReplicodeConfigPage::changed); } QString ReplicodeConfigPage::name() const { return i18n("Replicode"); } QString ReplicodeConfigPage::fullName() const { return i18n("Replicode configuration"); } void ReplicodeConfigPage::apply() { KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Replicode")); config.writeEntry("replicodePath", m_requester->text()); m_config->save(); } void ReplicodeConfigPage::reset() { KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Replicode")); m_requester->setText(config.readEntry("replicodePath", QString())); m_config->load(); } void ReplicodeConfigPage::defaults() { m_requester->setText(QString()); m_config->reset(); } diff --git a/addons/replicode/replicodeplugin.h b/addons/replicode/replicodeplugin.h index 704523e39..55e5a74c5 100644 --- a/addons/replicode/replicodeplugin.h +++ b/addons/replicode/replicodeplugin.h @@ -1,49 +1,49 @@ /* This file is part of the KDE project Copyright (C) 2014 Martin Sandsmark This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef REPLICODEPLUGIN_H #define REPLICODEPLUGIN_H -#include -#include #include "replicodeview.h" +#include +#include class ReplicodePlugin : public KTextEditor::Plugin { Q_OBJECT public: // Constructor explicit ReplicodePlugin(QObject *parent = nullptr, const QList &args = QList()); // Destructor ~ReplicodePlugin() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override { return new ReplicodeView(this, mainWindow); } // Config interface int configPages() const override { return 1; } KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; }; #endif diff --git a/addons/replicode/replicodesettings.cpp b/addons/replicode/replicodesettings.cpp index b2b6ac399..204ffb786 100644 --- a/addons/replicode/replicodesettings.cpp +++ b/addons/replicode/replicodesettings.cpp @@ -1,211 +1,211 @@ /* This file is part of the KDE project Copyright (C) 2014 Martin Sandsmark This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "replicodesettings.h" -#include -#include #include +#include #include +#include ReplicodeSettings::ReplicodeSettings(QObject *parent) : QObject(parent) { load(); } void ReplicodeSettings::load() { QSettings settings(QStringLiteral("replicode"), QStringLiteral("replicode")); settings.beginGroup(QStringLiteral("Load")); userOperatorPath = settings.value(QStringLiteral("User Operator Module Path"), QString()).toString(); userClassPath = settings.value(QStringLiteral("User Class File Path"), QString()).toString(); sourcePath = settings.value(QStringLiteral("Source File Path"), QString()).toString(); settings.endGroup(); settings.beginGroup(QStringLiteral("Init")); basePeriod = settings.value(QStringLiteral("Base Period"), 50000).toInt(); reductionCoreCount = settings.value(QStringLiteral("Reduction Core Count"), 6).toInt(); timeCoreCount = settings.value(QStringLiteral("Time Core Count"), 2).toInt(); settings.beginGroup(QStringLiteral("System")); mdlInertiaSuccessRateThreshold = settings.value(QStringLiteral("Model Inertia Success Rate Threshold"), 0.9).toFloat(); mdlInertiaCountThreshold = settings.value(QStringLiteral("Model Inertia Count Threshold"), 6).toInt(); tpxDeltaSuccessRateThreshold = settings.value(QStringLiteral("Targeted Pattern Extractor Delta Success Rate Threshold"), 0.1).toFloat(); minimumSimulationTimeHorizon = settings.value(QStringLiteral("Minimum Simulation Time Horizon"), 0).toInt(); maximumSimulationTimeHorizon = settings.value(QStringLiteral("Maximum Simulation Time Horizon"), 0).toInt(); simulationTimeHorizon = settings.value(QStringLiteral("Simulation Time Horizon"), 0.3).toFloat(); tpxTimehorizon = settings.value(QStringLiteral("Targeted Pattern Extractor Time Horizon"), 500000).toInt(); perfSamplingPeriod = settings.value(QStringLiteral("Perf Sampling Period"), 250000).toInt(); floatTolerance = settings.value(QStringLiteral("Float Tolerance"), 0.00001).toFloat(); timeTolerance = settings.value(QStringLiteral("Timer Tolerance"), 10000).toInt(); primaryTimeHorizon = settings.value(QStringLiteral("Primary Time Horizon"), 3600000).toInt(); secondaryTimeHorizon = settings.value(QStringLiteral("Secondary Time Horizon"), 7200000).toInt(); settings.endGroup(); settings.beginGroup(QStringLiteral("Debug")); debug = settings.value(QStringLiteral("Debug"), true).toBool(); debugWindows = settings.value(QStringLiteral("Debug Windows"), 1).toInt(); traceLevels = settings.value(QStringLiteral("Trace Levels"), QStringLiteral("CC")).toString().toInt(nullptr, 16); settings.endGroup(); settings.beginGroup(QStringLiteral("Resilience")); notificationMarkerResilience = settings.value(QStringLiteral("Notification Marker Resilience"), 1).toInt(); goalPredictionSuccessResilience = settings.value(QStringLiteral("Goal Prediction Success Resilience"), 1000).toInt(); settings.endGroup(); settings.beginGroup(QStringLiteral("Objects")); getObjects = settings.value(QStringLiteral("Get Objects"), true).toBool(); decompileObjects = settings.value(QStringLiteral("Decompile Objects"), true).toBool(); decompilationFilePath = settings.value(QStringLiteral("Decompilation Files Paths"), QString()).toString(); ignoreNamedObjects = settings.value(QStringLiteral("Ignore Named Objects"), false).toBool(); objectsPath = settings.value(QStringLiteral("Objects Path"), QString()).toString(); testObjects = settings.value(QStringLiteral("Test Objects"), false).toBool(); settings.endGroup(); settings.beginGroup(QStringLiteral("Run")); runTime = settings.value(QStringLiteral("Run Time"), 1080).toInt(); probeLevel = settings.value(QStringLiteral("Probe Level"), 2).toInt(); settings.endGroup(); settings.beginGroup(QStringLiteral("Models")); getModels = settings.value(QStringLiteral("Get Models"), false).toBool(); decompileModels = settings.value(QStringLiteral("Decompile Models"), false).toBool(); ignoreNamedModels = settings.value(QStringLiteral("Ignore Named Models"), true).toBool(); modelsPath = settings.value(QStringLiteral("Models Path"), QString()).toString(); testModels = settings.value(QStringLiteral("Test Models"), false).toBool(); settings.endGroup(); } void ReplicodeSettings::save() { QSettings settings(QStringLiteral("replicode"), QStringLiteral("replicode")); settings.beginGroup(QStringLiteral("Load")); settings.setValue(QStringLiteral("User Operator Module Path"), userOperatorPath); settings.setValue(QStringLiteral("User Class File Path"), userClassPath); settings.setValue(QStringLiteral("Source File Path"), sourcePath); settings.endGroup(); settings.beginGroup(QStringLiteral("Init")); settings.setValue(QStringLiteral("Base Period"), basePeriod); settings.setValue(QStringLiteral("Reduction Core Count"), reductionCoreCount); settings.setValue(QStringLiteral("Time Core Count"), timeCoreCount); settings.endGroup(); settings.beginGroup(QStringLiteral("System")); settings.setValue(QStringLiteral("Model Inertia Success Rate Threshold"), mdlInertiaSuccessRateThreshold); settings.setValue(QStringLiteral("Model Inertia Count Threshold"), mdlInertiaCountThreshold); settings.setValue(QStringLiteral("Targeted Pattern Extractor Delta Success Rate Threshold"), tpxDeltaSuccessRateThreshold); settings.setValue(QStringLiteral("Minimum Simulation Time Horizon"), minimumSimulationTimeHorizon); settings.setValue(QStringLiteral("Maximum Simulation Time Horizon"), maximumSimulationTimeHorizon); settings.setValue(QStringLiteral("Simulation Time Horizon"), simulationTimeHorizon); settings.setValue(QStringLiteral("Targeted Pattern Extractor Time Horizon"), tpxTimehorizon); settings.setValue(QStringLiteral("Perf Sampling Period"), perfSamplingPeriod); settings.setValue(QStringLiteral("Float Tolerance"), floatTolerance); settings.setValue(QStringLiteral("Timer Tolerance"), timeTolerance); settings.setValue(QStringLiteral("Primary Time Horizon"), primaryTimeHorizon); settings.setValue(QStringLiteral("Secondary Time Horizon"), secondaryTimeHorizon); settings.endGroup(); settings.beginGroup(QStringLiteral("Debug")); settings.setValue(QStringLiteral("Debug"), debug); settings.setValue(QStringLiteral("Debug Windows"), debugWindows); settings.setValue(QStringLiteral("Trace Levels"), QString::number(traceLevels, 16)); settings.endGroup(); settings.beginGroup(QStringLiteral("Resilience")); settings.setValue(QStringLiteral("Notification Marker Resilience"), notificationMarkerResilience); settings.setValue(QStringLiteral("Goal Prediction Success Resilience"), goalPredictionSuccessResilience); settings.endGroup(); settings.beginGroup(QStringLiteral("Objects")); settings.setValue(QStringLiteral("Get Objects"), getObjects); settings.setValue(QStringLiteral("Decompile Objects"), decompileObjects); settings.setValue(QStringLiteral("Decompilation Files Paths"), decompilationFilePath); settings.setValue(QStringLiteral("Ignore Named Objects"), ignoreNamedObjects); settings.setValue(QStringLiteral("Objects Path"), objectsPath); settings.setValue(QStringLiteral("Test Objects"), testObjects); settings.endGroup(); settings.beginGroup(QStringLiteral("Run")); settings.setValue(QStringLiteral("Run Time"), runTime); settings.setValue(QStringLiteral("Probe Level"), probeLevel); settings.endGroup(); settings.beginGroup(QStringLiteral("Models")); settings.setValue(QStringLiteral("Get Models"), getModels); settings.setValue(QStringLiteral("Decompile Models"), decompileModels); settings.setValue(QStringLiteral("Ignore Named Models"), ignoreNamedModels); settings.setValue(QStringLiteral("Models Path"), modelsPath); settings.setValue(QStringLiteral("Test Models"), testModels); } void ReplicodeSettings::setDefaults() { // Load userOperatorPath = QString(); userClassPath = QString(); sourcePath = QString(); // Init basePeriod = 50000; reductionCoreCount = 6; timeCoreCount = 2; // System mdlInertiaSuccessRateThreshold = 0.9f; mdlInertiaCountThreshold = 6; tpxDeltaSuccessRateThreshold = 0.1f; minimumSimulationTimeHorizon = 0; maximumSimulationTimeHorizon = 0; simulationTimeHorizon = 0.3f; tpxTimehorizon = 500000; perfSamplingPeriod = 250000; floatTolerance = 0.00001f; timeTolerance = 10000; primaryTimeHorizon = 3600000; secondaryTimeHorizon = 7200000; // Debug debug = true; debugWindows = 1; traceLevels = 0xCC; // Debug Resilience notificationMarkerResilience = 1; goalPredictionSuccessResilience = 1000; // Debug Objects getObjects = true; decompileObjects = true; decompilationFilePath = QString(); ignoreNamedObjects = false; objectsPath = QString(); testObjects = false; // Run runTime = 1080; probeLevel = 2; getModels = false; decompileModels = false; ignoreNamedModels = true; modelsPath = QString(); testModels = false; } diff --git a/addons/replicode/replicodeview.cpp b/addons/replicode/replicodeview.cpp index 3feed81e5..eefa6721d 100644 --- a/addons/replicode/replicodeview.cpp +++ b/addons/replicode/replicodeview.cpp @@ -1,253 +1,253 @@ /* This file is part of the KDE project Copyright (C) 2014 Martin Sandsmark This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "replicodeview.h" -#include +#include "replicodeconfig.h" +#include "replicodesettings.h" #include -#include #include -#include "replicodesettings.h" -#include "replicodeconfig.h" +#include +#include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include +#include #include #include #include ReplicodeView::ReplicodeView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWindow) : QObject(mainWindow) , m_mainWindow(mainWindow) , m_executor(nullptr) { m_runAction = new QAction(QIcon(QStringLiteral("code-block")), i18n("Run replicode"), this); connect(m_runAction, &QAction::triggered, this, &ReplicodeView::runReplicode); actionCollection()->addAction(QStringLiteral("katereplicode_run"), m_runAction); m_stopAction = new QAction(QIcon(QStringLiteral("process-stop")), i18n("Stop replicode"), this); connect(m_stopAction, &QAction::triggered, this, &ReplicodeView::stopReplicode); actionCollection()->addAction(QStringLiteral("katereplicode_stop"), m_stopAction); m_stopAction->setEnabled(false); m_toolview = m_mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katereplicodeplugin_run"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("code-block")), i18n("Replicode Output")); m_replicodeOutput = new QListWidget(m_toolview); m_replicodeOutput->setSelectionMode(QAbstractItemView::ContiguousSelection); connect(m_replicodeOutput, &QListWidget::itemActivated, this, &ReplicodeView::outputClicked); m_mainWindow->hideToolView(m_toolview); m_configSidebar = m_mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katereplicodeplugin_config"), KTextEditor::MainWindow::Right, QIcon::fromTheme(QStringLiteral("code-block")), i18n("Replicode Config")); m_configView = new ReplicodeConfig(m_configSidebar); m_runButton = new QPushButton(i18nc("shortcut for action", "Run (%1)", m_runAction->shortcut().toString())); m_stopButton = new QPushButton(i18nc("shortcut for action", "Stop (%1)", m_stopAction->shortcut().toString())); m_stopButton->setEnabled(false); QFormLayout *l = qobject_cast(m_configView->widget(0)->layout()); Q_ASSERT(l); l->addRow(m_runButton, m_stopButton); connect(m_runButton, &QPushButton::clicked, m_runAction, &QAction::trigger); connect(m_stopButton, &QPushButton::clicked, m_stopAction, &QAction::trigger); m_mainWindow->guiFactory()->addClient(this); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &ReplicodeView::viewChanged); } ReplicodeView::~ReplicodeView() { m_mainWindow->guiFactory()->removeClient(this); delete m_executor; } void ReplicodeView::viewChanged() { if (m_mainWindow->activeView() && m_mainWindow->activeView()->document() && m_mainWindow->activeView()->document()->url().fileName().endsWith(QLatin1String(".replicode"))) { m_mainWindow->showToolView(m_configSidebar); } else { m_mainWindow->hideToolView(m_configSidebar); m_mainWindow->hideToolView(m_toolview); } } void ReplicodeView::runReplicode() { m_mainWindow->showToolView(m_toolview); KTextEditor::View *editor = m_mainWindow->activeView(); if (!editor || !editor->document()) { QMessageBox::warning(m_mainWindow->window(), i18nc("@title:window", "Active Document Not Found"), i18n("Could not find an active document to run.")); return; } if (editor->document()->isEmpty()) { QMessageBox::warning(m_mainWindow->window(), i18nc("@title:window", "Empty Document"), i18n("Cannot execute an empty document.")); return; } QFileInfo sourceFile = QFileInfo(editor->document()->url().toLocalFile()); if (!sourceFile.isReadable()) { QMessageBox::warning(m_mainWindow->window(), i18nc("@title:window", "File Not Found"), i18n("Unable to open source file for reading.")); return; } KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Replicode")); QString executorPath = config.readEntry("replicodePath", QString()); if (executorPath.isEmpty()) { QMessageBox::warning(m_mainWindow->window(), i18nc("@title:window", "Replicode Executable Not Found"), i18n("Unable to find replicode executor.\n" "Please go to settings and set the path to the Replicode executable.")); return; } if (m_configView->settingsObject()->userOperatorPath.isEmpty()) { QMessageBox::warning(m_mainWindow->window(), i18nc("@title:window", "User Operator Library Not Found"), i18n("Unable to find user operator library.\n" "Please go to settings and set the path to the library.")); } m_configView->settingsObject()->sourcePath = editor->document()->url().toLocalFile(); m_configView->load(); m_configView->settingsObject()->save(); m_replicodeOutput->clear(); if (m_executor) delete m_executor; m_executor = new QProcess(this); m_executor->setWorkingDirectory(sourceFile.canonicalPath()); connect(m_executor, &QProcess::readyReadStandardError, this, &ReplicodeView::gotStderr); connect(m_executor, &QProcess::readyReadStandardOutput, this, &ReplicodeView::gotStdout); connect(m_executor, static_cast(&QProcess::finished), this, &ReplicodeView::replicodeFinished); connect(m_executor, static_cast(&QProcess::errorOccurred), this, &ReplicodeView::runErrored); qDebug() << executorPath << sourceFile.canonicalPath(); m_completed = false; m_executor->start(executorPath, QStringList(), QProcess::ReadOnly); m_runAction->setEnabled(false); m_runButton->setEnabled(false); m_stopAction->setEnabled(true); m_stopButton->setEnabled(true); } void ReplicodeView::stopReplicode() { if (m_executor) { m_executor->kill(); } } void ReplicodeView::outputClicked(QListWidgetItem *item) { QString output = item->text(); QStringList pieces = output.split(QLatin1Char(':')); if (pieces.length() < 2) return; QFileInfo file(pieces[0]); if (!file.isReadable()) return; bool ok = false; int lineNumber = pieces[1].toInt(&ok); qDebug() << lineNumber; if (!ok) return; KTextEditor::View *doc = m_mainWindow->openUrl(QUrl::fromLocalFile(pieces[0])); doc->setCursorPosition(KTextEditor::Cursor(lineNumber, 0)); qDebug() << doc->cursorPosition().line(); } void ReplicodeView::runErrored(QProcess::ProcessError error) { Q_UNUSED(error); QListWidgetItem *item = new QListWidgetItem(i18n("Replicode execution failed: %1", m_executor->errorString())); item->setForeground(Qt::red); m_replicodeOutput->addItem(item); m_replicodeOutput->scrollToBottom(); m_completed = true; } void ReplicodeView::replicodeFinished() { if (!m_completed) { QListWidgetItem *item = new QListWidgetItem(i18n("Replicode execution finished.")); item->setForeground(Qt::blue); m_replicodeOutput->addItem(item); m_replicodeOutput->scrollToBottom(); } m_runAction->setEnabled(true); m_runButton->setEnabled(true); m_stopAction->setEnabled(false); m_stopButton->setEnabled(false); // delete m_executor; // delete m_settingsFile; // m_executor = 0; // m_settingsFile = 0; } void ReplicodeView::gotStderr() { const QByteArray output = m_executor->readAllStandardError(); const auto lines = output.split('\n'); for (QByteArray line : lines) { line = line.simplified(); if (line.isEmpty()) continue; QListWidgetItem *item = new QListWidgetItem(QString::fromLocal8Bit(line)); item->setForeground(Qt::red); m_replicodeOutput->addItem(item); } m_replicodeOutput->scrollToBottom(); } void ReplicodeView::gotStdout() { const QByteArray output = m_executor->readAllStandardOutput(); const auto lines = output.split('\n'); for (QByteArray line : lines) { line = line.simplified(); if (line.isEmpty()) continue; QListWidgetItem *item = new QListWidgetItem(QString::fromLocal8Bit(' ' + line)); if (line[0] == '>') item->setForeground(Qt::gray); m_replicodeOutput->addItem(item); } m_replicodeOutput->scrollToBottom(); } diff --git a/addons/replicode/replicodeview.h b/addons/replicode/replicodeview.h index 841385336..2ee19e151 100644 --- a/addons/replicode/replicodeview.h +++ b/addons/replicode/replicodeview.h @@ -1,76 +1,76 @@ /* This file is part of the KDE project Copyright (C) 2014 Martin Sandsmark This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef REPLICODEVIEW_H #define REPLICODEVIEW_H -#include -#include #include #include +#include +#include class QListWidgetItem; class QListWidget; class QTemporaryFile; class QProcess; class QListWidget; class QPushButton; class QAction; class ReplicodeConfig; class ReplicodeView : public QObject, public KXMLGUIClient, public KTextEditor::SessionConfigInterface { Q_OBJECT Q_INTERFACES(KTextEditor::SessionConfigInterface) public: explicit ReplicodeView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWindow); ~ReplicodeView() override; void readSessionConfig(const KConfigGroup &) override { } void writeSessionConfig(KConfigGroup &) override { } private Q_SLOTS: void runReplicode(); void stopReplicode(); void replicodeFinished(); void gotStderr(); void gotStdout(); void runErrored(QProcess::ProcessError); void outputClicked(QListWidgetItem *item); void viewChanged(); private: KTextEditor::MainWindow *m_mainWindow; QProcess *m_executor; QListWidget *m_replicodeOutput; QWidget *m_toolview; QWidget *m_configSidebar; QPushButton *m_runButton; QPushButton *m_stopButton; QAction *m_runAction; QAction *m_stopAction; ReplicodeConfig *m_configView; bool m_completed; }; #endif diff --git a/addons/search/FolderFilesList.cpp b/addons/search/FolderFilesList.cpp index fc1342492..5cfd82bab 100644 --- a/addons/search/FolderFilesList.cpp +++ b/addons/search/FolderFilesList.cpp @@ -1,149 +1,149 @@ /* Kate search plugin * * Copyright (C) 2013 by KÃ¥re Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "FolderFilesList.h" +#include #include #include #include -#include #include #include FolderFilesList::FolderFilesList(QObject *parent) : QThread(parent) { } FolderFilesList::~FolderFilesList() { m_cancelSearch = true; wait(); } void FolderFilesList::run() { m_files.clear(); QFileInfo folderInfo(m_folder); checkNextItem(folderInfo); if (m_cancelSearch) m_files.clear(); } void FolderFilesList::generateList(const QString &folder, bool recursive, bool hidden, bool symlinks, bool binary, const QString &types, const QString &excludes) { m_cancelSearch = false; m_folder = folder; if (!m_folder.endsWith(QLatin1Char('/'))) { m_folder += QLatin1Char('/'); } m_recursive = recursive; m_hidden = hidden; m_symlinks = symlinks; m_binary = binary; m_types.clear(); const auto typesList = types.split(QLatin1Char(','), QString::SkipEmptyParts); for (const QString &type : typesList) { m_types << type.trimmed(); } if (m_types.isEmpty()) { m_types << QStringLiteral("*"); } QStringList tmpExcludes = excludes.split(QLatin1Char(',')); m_excludeList.clear(); for (int i = 0; i < tmpExcludes.size(); i++) { QRegExp rx(tmpExcludes[i].trimmed()); rx.setPatternSyntax(QRegExp::Wildcard); m_excludeList << rx; } m_time.restart(); start(); } QStringList FolderFilesList::fileList() { return m_files; } void FolderFilesList::cancelSearch() { m_cancelSearch = true; } void FolderFilesList::checkNextItem(const QFileInfo &item) { if (m_cancelSearch) { return; } if (m_time.elapsed() > 100) { m_time.restart(); emit searching(item.absoluteFilePath()); } if (item.isFile()) { if (!m_binary) { QMimeType mimeType = QMimeDatabase().mimeTypeForFile(item); if (!mimeType.inherits(QStringLiteral("text/plain"))) { return; } } m_files << item.canonicalFilePath(); } else { QDir currentDir(item.absoluteFilePath()); if (!currentDir.isReadable()) { // qDebug() << currentDir.absolutePath() << "Not readable"; return; } QDir::Filters filter = QDir::Files | QDir::NoDotAndDotDot | QDir::Readable; if (m_hidden) filter |= QDir::Hidden; if (m_recursive) filter |= QDir::AllDirs; if (!m_symlinks) filter |= QDir::NoSymLinks; // sort the items to have an deterministic order! const QFileInfoList currentItems = currentDir.entryInfoList(m_types, filter, QDir::Name | QDir::LocaleAware); bool skip; for (const auto ¤tItem : currentItems) { skip = false; for (const auto ®ex : qAsConst(m_excludeList)) { QString matchString = currentItem.filePath(); if (currentItem.filePath().startsWith(m_folder)) { matchString = currentItem.filePath().mid(m_folder.size()); } if (regex.exactMatch(matchString)) { skip = true; break; } } if (!skip) { checkNextItem(currentItem); } } } } diff --git a/addons/search/FolderFilesList.h b/addons/search/FolderFilesList.h index de4c3b591..09e8be3e3 100644 --- a/addons/search/FolderFilesList.h +++ b/addons/search/FolderFilesList.h @@ -1,68 +1,68 @@ /* Kate search plugin * * Copyright (C) 2013 by KÃ¥re Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifndef FolderFilesList_h #define FolderFilesList_h -#include -#include +#include #include -#include +#include #include -#include +#include +#include class FolderFilesList : public QThread { Q_OBJECT public: FolderFilesList(QObject *parent = nullptr); ~FolderFilesList() override; void run() override; void generateList(const QString &folder, bool recursive, bool hidden, bool symlinks, bool binary, const QString &types, const QString &excludes); QStringList fileList(); public Q_SLOTS: void cancelSearch(); Q_SIGNALS: void searching(const QString &path); private: void checkNextItem(const QFileInfo &item); private: QString m_folder; QStringList m_files; bool m_cancelSearch; bool m_recursive; bool m_hidden; bool m_symlinks; bool m_binary; QStringList m_types; QVector m_excludeList; QElapsedTimer m_time; }; #endif diff --git a/addons/search/SearchDiskFiles.cpp b/addons/search/SearchDiskFiles.cpp index b0f601f43..68e5fe58f 100644 --- a/addons/search/SearchDiskFiles.cpp +++ b/addons/search/SearchDiskFiles.cpp @@ -1,184 +1,184 @@ /* Kate search plugin * * Copyright (C) 2011-2013 by KÃ¥re Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "SearchDiskFiles.h" #include -#include #include +#include SearchDiskFiles::SearchDiskFiles(QObject *parent) : QThread(parent) { } SearchDiskFiles::~SearchDiskFiles() { m_cancelSearch = true; wait(); } void SearchDiskFiles::startSearch(const QStringList &files, const QRegularExpression ®exp) { if (files.empty()) { emit searchDone(); return; } m_cancelSearch = false; m_files = files; m_regExp = regexp; m_matchCount = 0; m_statusTime.restart(); start(); } void SearchDiskFiles::run() { for (const QString &fileName : qAsConst(m_files)) { if (m_cancelSearch) { break; } if (m_statusTime.elapsed() > 100) { m_statusTime.restart(); emit searching(fileName); } if (m_regExp.pattern().contains(QLatin1String("\\n"))) { searchMultiLineRegExp(fileName); } else { searchSingleLineRegExp(fileName); } } emit searchDone(); m_cancelSearch = true; } void SearchDiskFiles::cancelSearch() { m_cancelSearch = true; } bool SearchDiskFiles::searching() { return !m_cancelSearch; } void SearchDiskFiles::searchSingleLineRegExp(const QString &fileName) { QFile file(fileName); if (!file.open(QFile::ReadOnly)) { return; } QTextStream stream(&file); QString line; int i = 0; int column; QRegularExpressionMatch match; while (!(line = stream.readLine()).isNull()) { if (m_cancelSearch) break; match = m_regExp.match(line); column = match.capturedStart(); while (column != -1 && !match.captured().isEmpty()) { // limit line length if (line.length() > 1024) line = line.left(1024); QUrl fileUrl = QUrl::fromUserInput(fileName); emit matchFound(fileUrl.toString(), fileUrl.fileName(), line, match.capturedLength(), i, column, i, column + match.capturedLength()); match = m_regExp.match(line, column + match.capturedLength()); column = match.capturedStart(); m_matchCount++; // NOTE: This sleep is here so that the main thread will get a chance to // handle any stop button clicks if there are a lot of matches if (m_matchCount % 50) msleep(1); } i++; } } void SearchDiskFiles::searchMultiLineRegExp(const QString &fileName) { QFile file(fileName); int column = 0; int line = 0; static QString fullDoc; static QVector lineStart; QRegularExpression tmpRegExp = m_regExp; if (!file.open(QFile::ReadOnly)) { return; } QTextStream stream(&file); fullDoc = stream.readAll(); fullDoc.remove(QLatin1Char('\r')); lineStart.clear(); lineStart << 0; for (int i = 0; i < fullDoc.size() - 1; i++) { if (fullDoc[i] == QLatin1Char('\n')) { lineStart << i + 1; } } if (tmpRegExp.pattern().endsWith(QLatin1Char('$'))) { fullDoc += QLatin1Char('\n'); QString newPatern = tmpRegExp.pattern(); newPatern.replace(QStringLiteral("$"), QStringLiteral("(?=\\n)")); tmpRegExp.setPattern(newPatern); } QRegularExpressionMatch match; match = tmpRegExp.match(fullDoc); column = match.capturedStart(); while (column != -1 && !match.captured().isEmpty()) { if (m_cancelSearch) break; // search for the line number of the match int i; line = -1; for (i = 1; i < lineStart.size(); i++) { if (lineStart[i] > column) { line = i - 1; break; } } if (line == -1) { break; } QUrl fileUrl = QUrl::fromUserInput(fileName); int startColumn = (column - lineStart[line]); int endLine = line + match.captured().count(QLatin1Char('\n')); int lastNL = match.captured().lastIndexOf(QLatin1Char('\n')); int endColumn = lastNL == -1 ? startColumn + match.captured().length() : match.captured().length() - lastNL - 1; emit matchFound(fileUrl.toString(), fileUrl.fileName(), fullDoc.mid(lineStart[line], column - lineStart[line]) + match.captured(), match.capturedLength(), line, startColumn, endLine, endColumn); match = tmpRegExp.match(fullDoc, column + match.capturedLength()); column = match.capturedStart(); m_matchCount++; // NOTE: This sleep is here so that the main thread will get a chance to // handle any stop button clicks if there are a lot of matches if (m_matchCount % 50) msleep(1); } } diff --git a/addons/search/SearchDiskFiles.h b/addons/search/SearchDiskFiles.h index 7dc4eb014..422bd2b1d 100644 --- a/addons/search/SearchDiskFiles.h +++ b/addons/search/SearchDiskFiles.h @@ -1,65 +1,65 @@ /* Kate search plugin * * Copyright (C) 2011-2013 by KÃ¥re Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifndef SearchDiskFiles_h #define SearchDiskFiles_h -#include -#include +#include #include -#include #include +#include #include -#include +#include +#include class SearchDiskFiles : public QThread { Q_OBJECT public: SearchDiskFiles(QObject *parent = nullptr); ~SearchDiskFiles() override; void startSearch(const QStringList &iles, const QRegularExpression ®exp); void run() override; bool searching(); private: void searchSingleLineRegExp(const QString &fileName); void searchMultiLineRegExp(const QString &fileName); public Q_SLOTS: void cancelSearch(); Q_SIGNALS: void matchFound(const QString &url, const QString &docName, const QString &lineContent, int matchLen, int line, int column, int endLine, int endColumn); void searchDone(); void searching(const QString &file); private: QRegularExpression m_regExp; QStringList m_files; bool m_cancelSearch = true; int m_matchCount = 0; QElapsedTimer m_statusTime; }; #endif diff --git a/addons/search/htmldelegate.cpp b/addons/search/htmldelegate.cpp index 2a22bc114..349cefc6d 100644 --- a/addons/search/htmldelegate.cpp +++ b/addons/search/htmldelegate.cpp @@ -1,79 +1,79 @@ /*************************************************************************** * This file is part of Kate search plugin * * Copyright 2011 KÃ¥re Särs * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "htmldelegate.h" -#include -#include -#include +#include #include +#include +#include #include -#include +#include // make list spacing resemble the default list spacing // (which would not be the case with default QTextDocument margin) static const int s_ItemMargin = 1; SPHtmlDelegate::SPHtmlDelegate(QObject *parent) : QStyledItemDelegate(parent) { } SPHtmlDelegate::~SPHtmlDelegate() { } void SPHtmlDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem options = option; initStyleOption(&options, index); QTextDocument doc; doc.setDocumentMargin(s_ItemMargin); doc.setHtml(index.data().toString()); painter->save(); options.text = QString(); // clear old text options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget); // draw area QRect clip = options.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &options); if (index.flags() == Qt::NoItemFlags) { painter->setBrush(QBrush(QWidget().palette().color(QPalette::Base))); painter->setPen(QWidget().palette().color(QPalette::Base)); painter->drawRect(QRect(clip.topLeft() - QPoint(20, 0), clip.bottomRight())); painter->translate(clip.topLeft() - QPoint(20, 0)); } else { painter->translate(clip.topLeft() - QPoint(0, 0)); } QAbstractTextDocumentLayout::PaintContext pcontext; doc.documentLayout()->draw(painter, pcontext); painter->restore(); } QSize SPHtmlDelegate::sizeHint(const QStyleOptionViewItem & /*option*/, const QModelIndex &index) const { QTextDocument doc; doc.setDocumentMargin(s_ItemMargin); doc.setHtml(index.data().toString()); // qDebug() << doc.toPlainText() << doc.size().toSize(); return doc.size().toSize() + QSize(30, 0); // add margin for the check-box } diff --git a/addons/search/plugin_search.cpp b/addons/search/plugin_search.cpp index 984ee5eeb..0aa0bc46c 100644 --- a/addons/search/plugin_search.cpp +++ b/addons/search/plugin_search.cpp @@ -1,2325 +1,2325 @@ /* Kate search plugin * * Copyright (C) 2011-2013 by KÃ¥re Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "plugin_search.h" #include "htmldelegate.h" #include -#include -#include +#include #include +#include #include #include #include -#include +#include #include "kacceleratormanager.h" +#include #include +#include +#include #include #include #include -#include #include -#include -#include -#include #include +#include -#include #include +#include +#include +#include +#include +#include #include #include -#include #include -#include -#include -#include -#include +#include static QUrl localFileDirUp(const QUrl &url) { if (!url.isLocalFile()) return url; // else go up return QUrl::fromLocalFile(QFileInfo(url.toLocalFile()).dir().absolutePath()); } static QAction *menuEntry(QMenu *menu, const QString &before, const QString &after, const QString &desc, QString menuBefore = QString(), QString menuAfter = QString()); /** * When the action is triggered the cursor will be placed between @p before and @p after. */ static QAction *menuEntry(QMenu *menu, const QString &before, const QString &after, const QString &desc, QString menuBefore, QString menuAfter) { if (menuBefore.isEmpty()) menuBefore = before; if (menuAfter.isEmpty()) menuAfter = after; QAction *const action = menu->addAction(menuBefore + menuAfter + QLatin1Char('\t') + desc); if (!action) return nullptr; action->setData(QString(before + QLatin1Char(' ') + after)); return action; } /** * adds items and separators for special chars in "replace" field */ static void addSpecialCharsHelperActionsForReplace(QSet *actionList, QMenu *menu) { QSet &actionPointers = *actionList; QString emptyQSTring; actionPointers << menuEntry(menu, QStringLiteral("\\n"), emptyQSTring, i18n("Line break")); actionPointers << menuEntry(menu, QStringLiteral("\\t"), emptyQSTring, i18n("Tab")); } /** * adds items and separators for regex in "search" field */ static void addRegexHelperActionsForSearch(QSet *actionList, QMenu *menu) { QSet &actionPointers = *actionList; QString emptyQSTring; actionPointers << menuEntry(menu, QStringLiteral("^"), emptyQSTring, i18n("Beginning of line")); actionPointers << menuEntry(menu, QStringLiteral("$"), emptyQSTring, i18n("End of line")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("."), emptyQSTring, i18n("Any single character (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("[.]"), emptyQSTring, i18n("Literal dot")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("+"), emptyQSTring, i18n("One or more occurrences")); actionPointers << menuEntry(menu, QStringLiteral("*"), emptyQSTring, i18n("Zero or more occurrences")); actionPointers << menuEntry(menu, QStringLiteral("?"), emptyQSTring, i18n("Zero or one occurrences")); actionPointers << menuEntry(menu, QStringLiteral("{"), QStringLiteral(",}"), i18n(" through occurrences"), QStringLiteral("{a"), QStringLiteral(",b}")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("("), QStringLiteral(")"), i18n("Group, capturing")); actionPointers << menuEntry(menu, QStringLiteral("|"), emptyQSTring, i18n("Or")); actionPointers << menuEntry(menu, QStringLiteral("["), QStringLiteral("]"), i18n("Set of characters")); actionPointers << menuEntry(menu, QStringLiteral("[^"), QStringLiteral("]"), i18n("Negative set of characters")); actionPointers << menuEntry(menu, QStringLiteral("(?:"), QStringLiteral(")"), i18n("Group, non-capturing"), QStringLiteral("(?:E")); actionPointers << menuEntry(menu, QStringLiteral("(?="), QStringLiteral(")"), i18n("Lookahead"), QStringLiteral("(?=E")); actionPointers << menuEntry(menu, QStringLiteral("(?!"), QStringLiteral(")"), i18n("Negative lookahead"), QStringLiteral("(?!E")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("\\n"), emptyQSTring, i18n("Line break")); actionPointers << menuEntry(menu, QStringLiteral("\\t"), emptyQSTring, i18n("Tab")); actionPointers << menuEntry(menu, QStringLiteral("\\b"), emptyQSTring, i18n("Word boundary")); actionPointers << menuEntry(menu, QStringLiteral("\\B"), emptyQSTring, i18n("Not word boundary")); actionPointers << menuEntry(menu, QStringLiteral("\\d"), emptyQSTring, i18n("Digit")); actionPointers << menuEntry(menu, QStringLiteral("\\D"), emptyQSTring, i18n("Non-digit")); actionPointers << menuEntry(menu, QStringLiteral("\\s"), emptyQSTring, i18n("Whitespace (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("\\S"), emptyQSTring, i18n("Non-whitespace (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("\\w"), emptyQSTring, i18n("Word character (alphanumerics plus '_')")); actionPointers << menuEntry(menu, QStringLiteral("\\W"), emptyQSTring, i18n("Non-word character")); } /** * adds items and separators for regex in "replace" field */ static void addRegexHelperActionsForReplace(QSet *actionList, QMenu *menu) { QSet &actionPointers = *actionList; QString emptyQSTring; menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("\\0"), emptyQSTring, i18n("Regular expression capture 0 (whole match)")); actionPointers << menuEntry(menu, QStringLiteral("\\"), emptyQSTring, i18n("Regular expression capture 1-9"), QStringLiteral("\\#")); actionPointers << menuEntry(menu, QStringLiteral("\\{"), QStringLiteral("}"), i18n("Regular expression capture 0-999"), QStringLiteral("\\{#")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("\\U\\"), emptyQSTring, i18n("Upper-cased capture 0-9"), QStringLiteral("\\U\\#")); actionPointers << menuEntry(menu, QStringLiteral("\\U\\{"), QStringLiteral("}"), i18n("Upper-cased capture 0-999"), QStringLiteral("\\U\\{###")); actionPointers << menuEntry(menu, QStringLiteral("\\L\\"), emptyQSTring, i18n("Lower-cased capture 0-9"), QStringLiteral("\\L\\#")); actionPointers << menuEntry(menu, QStringLiteral("\\L\\{"), QStringLiteral("}"), i18n("Lower-cased capture 0-999"), QStringLiteral("\\L\\{###")); } /** * inserts text and sets cursor position */ static void regexHelperActOnAction(QAction *resultAction, const QSet &actionList, QLineEdit *lineEdit) { if (resultAction && actionList.contains(resultAction)) { const int cursorPos = lineEdit->cursorPosition(); QStringList beforeAfter = resultAction->data().toString().split(QLatin1Char(' ')); if (beforeAfter.size() != 2) return; lineEdit->insert(beforeAfter[0] + beforeAfter[1]); lineEdit->setCursorPosition(cursorPos + beforeAfter[0].count()); lineEdit->setFocus(); } } class TreeWidgetItem : public QTreeWidgetItem { public: TreeWidgetItem(QTreeWidget *parent) : QTreeWidgetItem(parent) { } TreeWidgetItem(QTreeWidget *parent, const QStringList &list) : QTreeWidgetItem(parent, list) { } TreeWidgetItem(QTreeWidgetItem *parent, const QStringList &list) : QTreeWidgetItem(parent, list) { } private: bool operator<(const QTreeWidgetItem &other) const override { if (childCount() == 0) { int line = data(0, ReplaceMatches::StartLineRole).toInt(); int column = data(0, ReplaceMatches::StartColumnRole).toInt(); int oLine = other.data(0, ReplaceMatches::StartLineRole).toInt(); int oColumn = other.data(0, ReplaceMatches::StartColumnRole).toInt(); if (line < oLine) { return true; } if ((line == oLine) && (column < oColumn)) { return true; } return false; } int sepCount = data(0, ReplaceMatches::FileUrlRole).toString().count(QDir::separator()); int oSepCount = other.data(0, ReplaceMatches::FileUrlRole).toString().count(QDir::separator()); if (sepCount < oSepCount) return true; if (sepCount > oSepCount) return false; return data(0, ReplaceMatches::FileUrlRole).toString().toLower() < other.data(0, ReplaceMatches::FileUrlRole).toString().toLower(); } }; Results::Results(QWidget *parent) : QWidget(parent) { setupUi(this); tree->setItemDelegate(new SPHtmlDelegate(tree)); } K_PLUGIN_FACTORY_WITH_JSON(KatePluginSearchFactory, "katesearch.json", registerPlugin();) KatePluginSearch::KatePluginSearch(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { m_searchCommand = new KateSearchCommand(this); } KatePluginSearch::~KatePluginSearch() { delete m_searchCommand; } QObject *KatePluginSearch::createView(KTextEditor::MainWindow *mainWindow) { KatePluginSearchView *view = new KatePluginSearchView(this, mainWindow, KTextEditor::Editor::instance()->application()); connect(m_searchCommand, &KateSearchCommand::setSearchPlace, view, &KatePluginSearchView::setSearchPlace); connect(m_searchCommand, &KateSearchCommand::setCurrentFolder, view, &KatePluginSearchView::setCurrentFolder); connect(m_searchCommand, &KateSearchCommand::setSearchString, view, &KatePluginSearchView::setSearchString); connect(m_searchCommand, &KateSearchCommand::startSearch, view, &KatePluginSearchView::startSearch); connect(m_searchCommand, SIGNAL(newTab()), view, SLOT(addTab())); return view; } bool ContainerWidget::focusNextPrevChild(bool next) { QWidget *fw = focusWidget(); bool found = false; emit nextFocus(fw, &found, next); if (found) { return true; } return QWidget::focusNextPrevChild(next); } void KatePluginSearchView::nextFocus(QWidget *currentWidget, bool *found, bool next) { *found = false; if (!currentWidget) { return; } // we use the object names here because there can be multiple replaceButtons (on multiple result tabs) if (next) { if (currentWidget->objectName() == QLatin1String("tree") || currentWidget == m_ui.binaryCheckBox) { m_ui.newTabButton->setFocus(); *found = true; return; } if (currentWidget == m_ui.displayOptions) { if (m_ui.displayOptions->isChecked()) { m_ui.folderRequester->setFocus(); *found = true; return; } else { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } res->tree->setFocus(); *found = true; return; } } } else { if (currentWidget == m_ui.newTabButton) { if (m_ui.displayOptions->isChecked()) { m_ui.binaryCheckBox->setFocus(); } else { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } res->tree->setFocus(); } *found = true; return; } else { if (currentWidget->objectName() == QLatin1String("tree")) { m_ui.displayOptions->setFocus(); *found = true; return; } } } } KatePluginSearchView::KatePluginSearchView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin, KTextEditor::Application *application) : QObject(mainWin) , m_kateApp(application) , m_curResults(nullptr) , m_searchJustOpened(false) , m_switchToProjectModeWhenAvailable(false) , m_searchDiskFilesDone(true) , m_searchOpenFilesDone(true) , m_isSearchAsYouType(false) , m_isLeftRight(false) , m_projectPluginView(nullptr) , m_mainWindow(mainWin) { KXMLGUIClient::setComponentName(QStringLiteral("katesearch"), i18n("Kate Search & Replace")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = mainWin->createToolView(plugin, QStringLiteral("kate_plugin_katesearch"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Search and Replace")); ContainerWidget *container = new ContainerWidget(m_toolView); m_ui.setupUi(container); container->setFocusProxy(m_ui.searchCombo); connect(container, &ContainerWidget::nextFocus, this, &KatePluginSearchView::nextFocus); QAction *a = actionCollection()->addAction(QStringLiteral("search_in_files")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_F)); a->setText(i18n("Search in Files")); connect(a, &QAction::triggered, this, &KatePluginSearchView::openSearchView); a = actionCollection()->addAction(QStringLiteral("search_in_files_new_tab")); a->setText(i18n("Search in Files (in new tab)")); // first add tab, then open search view, since open search view switches to show the search options connect(a, &QAction::triggered, this, &KatePluginSearchView::addTab); connect(a, &QAction::triggered, this, &KatePluginSearchView::openSearchView); a = actionCollection()->addAction(QStringLiteral("go_to_next_match")); a->setText(i18n("Go to Next Match")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::Key_F6)); connect(a, &QAction::triggered, this, &KatePluginSearchView::goToNextMatch); a = actionCollection()->addAction(QStringLiteral("go_to_prev_match")); a->setText(i18n("Go to Previous Match")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_F6)); connect(a, &QAction::triggered, this, &KatePluginSearchView::goToPreviousMatch); m_ui.resultTabWidget->tabBar()->setSelectionBehaviorOnRemove(QTabBar::SelectLeftTab); KAcceleratorManager::setNoAccel(m_ui.resultTabWidget); // Gnome does not seem to have all icons we want, so we use fall-back icons for those that are missing. QIcon dispOptIcon = QIcon::fromTheme(QStringLiteral("games-config-options"), QIcon::fromTheme(QStringLiteral("preferences-system"))); QIcon matchCaseIcon = QIcon::fromTheme(QStringLiteral("format-text-superscript"), QIcon::fromTheme(QStringLiteral("format-text-bold"))); QIcon useRegExpIcon = QIcon::fromTheme(QStringLiteral("code-context"), QIcon::fromTheme(QStringLiteral("edit-find-replace"))); QIcon expandResultsIcon = QIcon::fromTheme(QStringLiteral("view-list-tree"), QIcon::fromTheme(QStringLiteral("format-indent-more"))); m_ui.displayOptions->setIcon(dispOptIcon); m_ui.searchButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_ui.nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); m_ui.stopButton->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); m_ui.matchCase->setIcon(matchCaseIcon); m_ui.useRegExp->setIcon(useRegExpIcon); m_ui.expandResults->setIcon(expandResultsIcon); m_ui.searchPlaceCombo->setItemIcon(CurrentFile, QIcon::fromTheme(QStringLiteral("text-plain"))); m_ui.searchPlaceCombo->setItemIcon(OpenFiles, QIcon::fromTheme(QStringLiteral("text-plain"))); m_ui.searchPlaceCombo->setItemIcon(Folder, QIcon::fromTheme(QStringLiteral("folder"))); m_ui.folderUpButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); m_ui.currentFolderButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ui.newTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); m_ui.filterCombo->setToolTip(i18n("Comma separated list of file types to search in. Example: \"*.cpp,*.h\"\n")); m_ui.excludeCombo->setToolTip(i18n("Comma separated list of files and directories to exclude from the search. Example: \"build*\"")); // the order here is important to get the tabBar hidden for only one tab addTab(); m_ui.resultTabWidget->tabBar()->hide(); // get url-requester's combo box and sanely initialize KComboBox *cmbUrl = m_ui.folderRequester->comboBox(); cmbUrl->setDuplicatesEnabled(false); cmbUrl->setEditable(true); m_ui.folderRequester->setMode(KFile::Directory | KFile::LocalOnly); KUrlCompletion *cmpl = new KUrlCompletion(KUrlCompletion::DirCompletion); cmbUrl->setCompletionObject(cmpl); cmbUrl->setAutoDeleteCompletionObject(true); connect(m_ui.newTabButton, &QToolButton::clicked, this, &KatePluginSearchView::addTab); connect(m_ui.resultTabWidget, &QTabWidget::tabCloseRequested, this, &KatePluginSearchView::tabCloseRequested); connect(m_ui.resultTabWidget, &QTabWidget::currentChanged, this, &KatePluginSearchView::resultTabChanged); connect(m_ui.folderUpButton, &QToolButton::clicked, this, &KatePluginSearchView::navigateFolderUp); connect(m_ui.currentFolderButton, &QToolButton::clicked, this, &KatePluginSearchView::setCurrentFolder); connect(m_ui.expandResults, &QToolButton::clicked, this, &KatePluginSearchView::expandResults); connect(m_ui.searchCombo, &QComboBox::editTextChanged, &m_changeTimer, static_cast(&QTimer::start)); connect(m_ui.matchCase, &QToolButton::toggled, &m_changeTimer, static_cast(&QTimer::start)); connect(m_ui.matchCase, &QToolButton::toggled, this, [=] { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (res) { res->matchCase = m_ui.matchCase->isChecked(); } }); connect(m_ui.searchCombo->lineEdit(), &QLineEdit::returnPressed, this, &KatePluginSearchView::startSearch); // connecting to returnPressed() of the folderRequester doesn't work, I haven't found out why yet. But connecting to the linedit works: connect(m_ui.folderRequester->comboBox()->lineEdit(), &QLineEdit::returnPressed, this, &KatePluginSearchView::startSearch); connect(m_ui.filterCombo, static_cast(&KComboBox::returnPressed), this, &KatePluginSearchView::startSearch); connect(m_ui.excludeCombo, static_cast(&KComboBox::returnPressed), this, &KatePluginSearchView::startSearch); connect(m_ui.searchButton, &QPushButton::clicked, this, &KatePluginSearchView::startSearch); connect(m_ui.displayOptions, &QToolButton::toggled, this, &KatePluginSearchView::toggleOptions); connect(m_ui.searchPlaceCombo, static_cast(&QComboBox::currentIndexChanged), this, &KatePluginSearchView::searchPlaceChanged); connect(m_ui.searchPlaceCombo, static_cast(&QComboBox::currentIndexChanged), this, [this](int) { if (m_ui.searchPlaceCombo->currentIndex() == Folder) { m_ui.displayOptions->setChecked(true); } }); connect(m_ui.stopButton, &QPushButton::clicked, &m_searchOpenFiles, &SearchOpenFiles::cancelSearch); connect(m_ui.stopButton, &QPushButton::clicked, &m_searchDiskFiles, &SearchDiskFiles::cancelSearch); connect(m_ui.stopButton, &QPushButton::clicked, &m_folderFilesList, &FolderFilesList::cancelSearch); connect(m_ui.stopButton, &QPushButton::clicked, &m_replacer, &ReplaceMatches::cancelReplace); connect(m_ui.nextButton, &QToolButton::clicked, this, &KatePluginSearchView::goToNextMatch); connect(m_ui.replaceButton, &QPushButton::clicked, this, &KatePluginSearchView::replaceSingleMatch); connect(m_ui.replaceCheckedBtn, &QPushButton::clicked, this, &KatePluginSearchView::replaceChecked); connect(m_ui.replaceCombo->lineEdit(), &QLineEdit::returnPressed, this, &KatePluginSearchView::replaceChecked); m_ui.displayOptions->setChecked(true); connect(&m_searchOpenFiles, &SearchOpenFiles::matchFound, this, &KatePluginSearchView::matchFound); connect(&m_searchOpenFiles, &SearchOpenFiles::searchDone, this, &KatePluginSearchView::searchDone); connect(&m_searchOpenFiles, static_cast(&SearchOpenFiles::searching), this, &KatePluginSearchView::searching); connect(&m_folderFilesList, &FolderFilesList::finished, this, &KatePluginSearchView::folderFileListChanged); connect(&m_folderFilesList, &FolderFilesList::searching, this, &KatePluginSearchView::searching); connect(&m_searchDiskFiles, &SearchDiskFiles::matchFound, this, &KatePluginSearchView::matchFound); connect(&m_searchDiskFiles, &SearchDiskFiles::searchDone, this, &KatePluginSearchView::searchDone); connect(&m_searchDiskFiles, static_cast(&SearchDiskFiles::searching), this, &KatePluginSearchView::searching); connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, &m_searchOpenFiles, &SearchOpenFiles::cancelSearch); connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, &m_replacer, &ReplaceMatches::cancelReplace); connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, this, &KatePluginSearchView::clearDocMarks); connect(&m_replacer, &ReplaceMatches::replaceStatus, this, &KatePluginSearchView::replaceStatus); // Hook into line edit context menus m_ui.searchCombo->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui.searchCombo, &QComboBox::customContextMenuRequested, this, &KatePluginSearchView::searchContextMenu); m_ui.searchCombo->completer()->setCompletionMode(QCompleter::PopupCompletion); m_ui.searchCombo->completer()->setCaseSensitivity(Qt::CaseSensitive); m_ui.searchCombo->setInsertPolicy(QComboBox::NoInsert); m_ui.searchCombo->lineEdit()->setClearButtonEnabled(true); m_ui.searchCombo->setMaxCount(25); QAction *searchComboActionForInsertRegexButton = m_ui.searchCombo->lineEdit()->addAction(QIcon::fromTheme(QStringLiteral("code-context"), QIcon::fromTheme(QStringLiteral("edit-find-replace"))), QLineEdit::TrailingPosition); connect(searchComboActionForInsertRegexButton, &QAction::triggered, this, [this]() { QMenu menu; QSet actionList; addRegexHelperActionsForSearch(&actionList, &menu); auto &&action = menu.exec(QCursor::pos()); regexHelperActOnAction(action, actionList, m_ui.searchCombo->lineEdit()); }); m_ui.replaceCombo->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui.replaceCombo, &QComboBox::customContextMenuRequested, this, &KatePluginSearchView::replaceContextMenu); m_ui.replaceCombo->completer()->setCompletionMode(QCompleter::PopupCompletion); m_ui.replaceCombo->completer()->setCaseSensitivity(Qt::CaseSensitive); m_ui.replaceCombo->setInsertPolicy(QComboBox::NoInsert); m_ui.replaceCombo->lineEdit()->setClearButtonEnabled(true); m_ui.replaceCombo->setMaxCount(25); QAction *replaceComboActionForInsertRegexButton = m_ui.replaceCombo->lineEdit()->addAction(QIcon::fromTheme(QStringLiteral("code-context")), QLineEdit::TrailingPosition); connect(replaceComboActionForInsertRegexButton, &QAction::triggered, this, [this]() { QMenu menu; QSet actionList; addRegexHelperActionsForReplace(&actionList, &menu); auto &&action = menu.exec(QCursor::pos()); regexHelperActOnAction(action, actionList, m_ui.replaceCombo->lineEdit()); }); QAction *replaceComboActionForInsertSpecialButton = m_ui.replaceCombo->lineEdit()->addAction(QIcon::fromTheme(QStringLiteral("insert-text")), QLineEdit::TrailingPosition); connect(replaceComboActionForInsertSpecialButton, &QAction::triggered, this, [this]() { QMenu menu; QSet actionList; addSpecialCharsHelperActionsForReplace(&actionList, &menu); auto &&action = menu.exec(QCursor::pos()); regexHelperActOnAction(action, actionList, m_ui.replaceCombo->lineEdit()); }); connect(m_ui.useRegExp, &QToolButton::toggled, &m_changeTimer, static_cast(&QTimer::start)); auto onRegexToggleChanged = [=] { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (res) { bool useRegExp = m_ui.useRegExp->isChecked(); res->useRegExp = useRegExp; searchComboActionForInsertRegexButton->setVisible(useRegExp); replaceComboActionForInsertRegexButton->setVisible(useRegExp); } }; connect(m_ui.useRegExp, &QToolButton::toggled, this, onRegexToggleChanged); onRegexToggleChanged(); // invoke initially m_changeTimer.setInterval(300); m_changeTimer.setSingleShot(true); connect(&m_changeTimer, &QTimer::timeout, this, &KatePluginSearchView::startSearchWhileTyping); m_toolView->setMinimumHeight(container->sizeHint().height()); connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KatePluginSearchView::handleEsc); // watch for project plugin view creation/deletion connect(m_mainWindow, &KTextEditor::MainWindow::pluginViewCreated, this, &KatePluginSearchView::slotPluginViewCreated); connect(m_mainWindow, &KTextEditor::MainWindow::pluginViewDeleted, this, &KatePluginSearchView::slotPluginViewDeleted); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KatePluginSearchView::docViewChanged); // Connect signals from project plugin to our slots m_projectPluginView = m_mainWindow->pluginView(QStringLiteral("kateprojectplugin")); slotPluginViewCreated(QStringLiteral("kateprojectplugin"), m_projectPluginView); m_replacer.setDocumentManager(m_kateApp); connect(&m_replacer, &ReplaceMatches::replaceDone, this, &KatePluginSearchView::replaceDone); searchPlaceChanged(); m_toolView->installEventFilter(this); m_mainWindow->guiFactory()->addClient(this); m_updateSumaryTimer.setInterval(1); m_updateSumaryTimer.setSingleShot(true); connect(&m_updateSumaryTimer, &QTimer::timeout, this, &KatePluginSearchView::updateResultsRootItem); } KatePluginSearchView::~KatePluginSearchView() { clearMarks(); m_mainWindow->guiFactory()->removeClient(this); delete m_toolView; } void KatePluginSearchView::navigateFolderUp() { // navigate one folder up m_ui.folderRequester->setUrl(localFileDirUp(m_ui.folderRequester->url())); } void KatePluginSearchView::setCurrentFolder() { if (!m_mainWindow) { return; } KTextEditor::View *editView = m_mainWindow->activeView(); if (editView && editView->document()) { // upUrl as we want the folder not the file m_ui.folderRequester->setUrl(localFileDirUp(editView->document()->url())); } m_ui.displayOptions->setChecked(true); } void KatePluginSearchView::openSearchView() { if (!m_mainWindow) { return; } if (!m_toolView->isVisible()) { m_mainWindow->showToolView(m_toolView); } m_ui.searchCombo->setFocus(Qt::OtherFocusReason); if (m_ui.searchPlaceCombo->currentIndex() == Folder) { m_ui.displayOptions->setChecked(true); } KTextEditor::View *editView = m_mainWindow->activeView(); if (editView && editView->document()) { if (m_ui.folderRequester->text().isEmpty()) { // upUrl as we want the folder not the file m_ui.folderRequester->setUrl(localFileDirUp(editView->document()->url())); } QString selection; if (editView->selection()) { selection = editView->selectionText(); // remove possible trailing '\n' if (selection.endsWith(QLatin1Char('\n'))) { selection = selection.left(selection.size() - 1); } } if (selection.isEmpty()) { selection = editView->document()->wordAt(editView->cursorPosition()); } if (!selection.isEmpty() && !selection.contains(QLatin1Char('\n'))) { m_ui.searchCombo->blockSignals(true); m_ui.searchCombo->lineEdit()->setText(selection); m_ui.searchCombo->blockSignals(false); } m_ui.searchCombo->lineEdit()->selectAll(); m_searchJustOpened = true; startSearchWhileTyping(); } } void KatePluginSearchView::handleEsc(QEvent *e) { if (!m_mainWindow) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { static ulong lastTimeStamp; if (lastTimeStamp == k->timestamp()) { // Same as previous... This looks like a bug somewhere... return; } lastTimeStamp = k->timestamp(); if (!m_matchRanges.isEmpty()) { clearMarks(); } else if (m_toolView->isVisible()) { m_mainWindow->hideToolView(m_toolView); } // Remove check marks Results *curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!curResults) { qWarning() << "This is a bug"; return; } QTreeWidgetItemIterator it(curResults->tree); while (*it) { (*it)->setCheckState(0, Qt::Unchecked); ++it; } } } void KatePluginSearchView::setSearchString(const QString &pattern) { m_ui.searchCombo->lineEdit()->setText(pattern); } void KatePluginSearchView::toggleOptions(bool show) { m_ui.stackedWidget->setCurrentIndex((show) ? 1 : 0); } void KatePluginSearchView::setSearchPlace(int place) { m_ui.searchPlaceCombo->setCurrentIndex(place); } QStringList KatePluginSearchView::filterFiles(const QStringList &files) const { QString types = m_ui.filterCombo->currentText(); QString excludes = m_ui.excludeCombo->currentText(); if (((types.isEmpty() || types == QLatin1String("*"))) && (excludes.isEmpty())) { // shortcut for use all files return files; } QStringList tmpTypes = types.split(QLatin1Char(',')); QVector typeList(tmpTypes.size()); for (int i = 0; i < tmpTypes.size(); i++) { QRegExp rx(tmpTypes[i].trimmed()); rx.setPatternSyntax(QRegExp::Wildcard); typeList << rx; } QStringList tmpExcludes = excludes.split(QLatin1Char(',')); QVector excludeList(tmpExcludes.size()); for (int i = 0; i < tmpExcludes.size(); i++) { QRegExp rx(tmpExcludes[i].trimmed()); rx.setPatternSyntax(QRegExp::Wildcard); excludeList << rx; } QStringList filteredFiles; for (const QString &fileName : files) { bool isInSubDir = fileName.startsWith(m_resultBaseDir); QString nameToCheck = fileName; if (isInSubDir) { nameToCheck = fileName.mid(m_resultBaseDir.size()); } bool skip = false; for (const auto ®ex : qAsConst(excludeList)) { if (regex.exactMatch(nameToCheck)) { skip = true; break; } } if (skip) { continue; } for (const auto ®ex : qAsConst(typeList)) { if (regex.exactMatch(nameToCheck)) { filteredFiles << fileName; break; } } } return filteredFiles; } void KatePluginSearchView::folderFileListChanged() { m_searchDiskFilesDone = false; m_searchOpenFilesDone = false; if (!m_curResults) { qWarning() << "This is a bug"; m_searchDiskFilesDone = true; m_searchOpenFilesDone = true; searchDone(); return; } QStringList fileList = m_folderFilesList.fileList(); QList openList; for (int i = 0; i < m_kateApp->documents().size(); i++) { int index = fileList.indexOf(m_kateApp->documents()[i]->url().toLocalFile()); if (index != -1) { openList << m_kateApp->documents()[i]; fileList.removeAt(index); } } // search order is important: Open files starts immediately and should finish // earliest after first event loop. // The DiskFile might finish immediately if (!openList.empty()) { m_searchOpenFiles.startSearch(openList, m_curResults->regExp); } else { m_searchOpenFilesDone = true; } m_searchDiskFiles.startSearch(fileList, m_curResults->regExp); } void KatePluginSearchView::searchPlaceChanged() { int searchPlace = m_ui.searchPlaceCombo->currentIndex(); const bool inFolder = (searchPlace == Folder); m_ui.filterCombo->setEnabled(searchPlace >= Folder); m_ui.excludeCombo->setEnabled(searchPlace >= Folder); m_ui.folderRequester->setEnabled(inFolder); m_ui.folderUpButton->setEnabled(inFolder); m_ui.currentFolderButton->setEnabled(inFolder); m_ui.recursiveCheckBox->setEnabled(inFolder); m_ui.hiddenCheckBox->setEnabled(inFolder); m_ui.symLinkCheckBox->setEnabled(inFolder); m_ui.binaryCheckBox->setEnabled(inFolder); if (inFolder && sender() == m_ui.searchPlaceCombo) { setCurrentFolder(); } // ... and the labels: m_ui.folderLabel->setEnabled(m_ui.folderRequester->isEnabled()); m_ui.filterLabel->setEnabled(m_ui.filterCombo->isEnabled()); m_ui.excludeLabel->setEnabled(m_ui.excludeCombo->isEnabled()); Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (res) { res->searchPlaceIndex = searchPlace; } } void KatePluginSearchView::addHeaderItem() { QTreeWidgetItem *item = new QTreeWidgetItem(m_curResults->tree, QStringList()); item->setCheckState(0, Qt::Checked); item->setFlags(item->flags() | Qt::ItemIsAutoTristate); m_curResults->tree->expandItem(item); } QTreeWidgetItem *KatePluginSearchView::rootFileItem(const QString &url, const QString &fName) { if (!m_curResults) { return nullptr; } QUrl fullUrl = QUrl::fromUserInput(url); QString path = fullUrl.isLocalFile() ? localFileDirUp(fullUrl).path() : fullUrl.url(); if (!path.isEmpty() && !path.endsWith(QLatin1Char('/'))) { path += QLatin1Char('/'); } path.remove(m_resultBaseDir); QString name = fullUrl.fileName(); if (url.isEmpty()) { name = fName; } // make sure we have a root item if (m_curResults->tree->topLevelItemCount() == 0) { addHeaderItem(); } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (m_isSearchAsYouType) { return root; } for (int i = 0; i < root->childCount(); i++) { // qDebug() << root->child(i)->data(0, ReplaceMatches::FileNameRole).toString() << fName; if ((root->child(i)->data(0, ReplaceMatches::FileUrlRole).toString() == url) && (root->child(i)->data(0, ReplaceMatches::FileNameRole).toString() == fName)) { int matches = root->child(i)->data(0, ReplaceMatches::StartLineRole).toInt() + 1; QString tmpUrl = QStringLiteral("%1%2: %3").arg(path, name).arg(matches); root->child(i)->setData(0, Qt::DisplayRole, tmpUrl); root->child(i)->setData(0, ReplaceMatches::StartLineRole, matches); return root->child(i); } } // file item not found create a new one QString tmpUrl = QStringLiteral("%1%2: %3").arg(path, name).arg(1); TreeWidgetItem *item = new TreeWidgetItem(root, QStringList(tmpUrl)); item->setData(0, ReplaceMatches::FileUrlRole, url); item->setData(0, ReplaceMatches::FileNameRole, fName); item->setData(0, ReplaceMatches::StartLineRole, 1); item->setCheckState(0, Qt::Checked); item->setFlags(item->flags() | Qt::ItemIsAutoTristate); return item; } void KatePluginSearchView::addMatchMark(KTextEditor::Document *doc, QTreeWidgetItem *item) { if (!doc || !item) { return; } KTextEditor::View *activeView = m_mainWindow->activeView(); KTextEditor::MovingInterface *miface = qobject_cast(doc); KTextEditor::ConfigInterface *ciface = qobject_cast(activeView); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); int line = item->data(0, ReplaceMatches::StartLineRole).toInt(); int column = item->data(0, ReplaceMatches::StartColumnRole).toInt(); int endLine = item->data(0, ReplaceMatches::EndLineRole).toInt(); int endColumn = item->data(0, ReplaceMatches::EndColumnRole).toInt(); bool isReplaced = item->data(0, ReplaceMatches::ReplacedRole).toBool(); if (isReplaced) { QColor replaceColor(Qt::green); if (ciface) replaceColor = ciface->configValue(QStringLiteral("replace-highlight-color")).value(); attr->setBackground(replaceColor); if (activeView) { attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color()); } } else { QColor searchColor(Qt::yellow); if (ciface) searchColor = ciface->configValue(QStringLiteral("search-highlight-color")).value(); attr->setBackground(searchColor); if (activeView) { attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color()); } } KTextEditor::Range range(line, column, endLine, endColumn); // Check that the match still matches if (m_curResults) { if (!isReplaced) { // special handling for "(?=\\n)" in multi-line search QRegularExpression tmpReg = m_curResults->regExp; if (m_curResults->regExp.pattern().endsWith(QLatin1String("(?=\\n)"))) { QString newPatern = tmpReg.pattern(); newPatern.replace(QStringLiteral("(?=\\n)"), QStringLiteral("$")); tmpReg.setPattern(newPatern); } // Check that the match still matches ;) if (tmpReg.match(doc->text(range)).capturedStart() != 0) { // qDebug() << doc->text(range) << "Does not match" << m_curResults->regExp.pattern(); return; } } else { if (doc->text(range) != item->data(0, ReplaceMatches::ReplacedTextRole).toString()) { // qDebug() << doc->text(range) << "Does not match" << item->data(0, ReplaceMatches::ReplacedTextRole).toString(); return; } } } // Highlight the match KTextEditor::MovingRange *mr = miface->newMovingRange(range); mr->setAttribute(attr); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); m_matchRanges.append(mr); // Add a match mark KTextEditor::MarkInterface *iface = qobject_cast(doc); if (!iface) return; iface->setMarkDescription(KTextEditor::MarkInterface::markType32, i18n("SearchHighLight")); iface->setMarkPixmap(KTextEditor::MarkInterface::markType32, QIcon().pixmap(0, 0)); iface->addMark(line, KTextEditor::MarkInterface::markType32); connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearMarks()), Qt::UniqueConnection); } static const int contextLen = 70; void KatePluginSearchView::matchFound(const QString &url, const QString &fName, const QString &lineContent, int matchLen, int startLine, int startColumn, int endLine, int endColumn) { if (!m_curResults) { return; } int preLen = contextLen; int preStart = startColumn - preLen; if (preStart < 0) { preLen += preStart; preStart = 0; } QString pre; if (preLen == contextLen) { pre = QStringLiteral("..."); } pre += lineContent.mid(preStart, preLen).toHtmlEscaped(); QString match = lineContent.mid(startColumn, matchLen).toHtmlEscaped(); match.replace(QLatin1Char('\n'), QStringLiteral("\\n")); QString post = lineContent.mid(startColumn + matchLen, contextLen); if (post.size() >= contextLen) { post += QStringLiteral("..."); } post = post.toHtmlEscaped(); QStringList row; row << i18n("Line: %1 Column: %2: %3", startLine + 1, startColumn + 1, pre + QStringLiteral("") + match + QStringLiteral("") + post); TreeWidgetItem *item = new TreeWidgetItem(rootFileItem(url, fName), row); item->setData(0, ReplaceMatches::FileUrlRole, url); item->setData(0, Qt::ToolTipRole, url); item->setData(0, ReplaceMatches::FileNameRole, fName); item->setData(0, ReplaceMatches::StartLineRole, startLine); item->setData(0, ReplaceMatches::StartColumnRole, startColumn); item->setData(0, ReplaceMatches::MatchLenRole, matchLen); item->setData(0, ReplaceMatches::PreMatchRole, pre); item->setData(0, ReplaceMatches::MatchRole, match); item->setData(0, ReplaceMatches::PostMatchRole, post); item->setData(0, ReplaceMatches::EndLineRole, endLine); item->setData(0, ReplaceMatches::EndColumnRole, endColumn); item->setCheckState(0, Qt::Checked); m_curResults->matches++; } void KatePluginSearchView::clearMarks() { const auto docs = m_kateApp->documents(); for (KTextEditor::Document *doc : docs) { clearDocMarks(doc); } qDeleteAll(m_matchRanges); m_matchRanges.clear(); } void KatePluginSearchView::clearDocMarks(KTextEditor::Document *doc) { KTextEditor::MarkInterface *iface; iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if (i.value()->type & KTextEditor::MarkInterface::markType32) { iface->removeMark(i.value()->line, KTextEditor::MarkInterface::markType32); } } } int i = 0; while (i < m_matchRanges.size()) { if (m_matchRanges.at(i)->document() == doc) { delete m_matchRanges.at(i); m_matchRanges.removeAt(i); } else { i++; } } m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "This is a bug"; return; } } void KatePluginSearchView::startSearch() { m_changeTimer.stop(); // make sure not to start a "while you type" search now m_mainWindow->showToolView(m_toolView); // in case we are invoked from the command interface m_switchToProjectModeWhenAvailable = false; // now that we started, don't switch back automatically if (m_ui.searchCombo->currentText().isEmpty()) { // return pressed in the folder combo or filter combo return; } m_isSearchAsYouType = false; QString currentSearchText = m_ui.searchCombo->currentText(); m_ui.searchCombo->setItemText(0, QString()); // remove the text from index 0 on enter/search int index = m_ui.searchCombo->findText(currentSearchText); if (index > 0) { m_ui.searchCombo->removeItem(index); } m_ui.searchCombo->insertItem(1, currentSearchText); m_ui.searchCombo->setCurrentIndex(1); if (m_ui.filterCombo->findText(m_ui.filterCombo->currentText()) == -1) { m_ui.filterCombo->insertItem(0, m_ui.filterCombo->currentText()); m_ui.filterCombo->setCurrentIndex(0); } if (m_ui.excludeCombo->findText(m_ui.excludeCombo->currentText()) == -1) { m_ui.excludeCombo->insertItem(0, m_ui.excludeCombo->currentText()); m_ui.excludeCombo->setCurrentIndex(0); } if (m_ui.folderRequester->comboBox()->findText(m_ui.folderRequester->comboBox()->currentText()) == -1) { m_ui.folderRequester->comboBox()->insertItem(0, m_ui.folderRequester->comboBox()->currentText()); m_ui.folderRequester->comboBox()->setCurrentIndex(0); } m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "This is a bug"; return; } QRegularExpression::PatternOptions patternOptions = (m_ui.matchCase->isChecked() ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); QString pattern = (m_ui.useRegExp->isChecked() ? currentSearchText : QRegularExpression::escape(currentSearchText)); QRegularExpression reg(pattern, patternOptions); if (!reg.isValid()) { // qDebug() << "invalid regexp"; indicateMatch(false); return; } m_curResults->regExp = reg; m_curResults->useRegExp = m_ui.useRegExp->isChecked(); m_curResults->matchCase = m_ui.matchCase->isChecked(); m_curResults->searchPlaceIndex = m_ui.searchPlaceCombo->currentIndex(); m_ui.newTabButton->setDisabled(true); m_ui.searchCombo->setDisabled(true); m_ui.searchButton->setDisabled(true); m_ui.displayOptions->setChecked(false); m_ui.displayOptions->setDisabled(true); m_ui.replaceCheckedBtn->setDisabled(true); m_ui.replaceButton->setDisabled(true); m_ui.stopAndNext->setCurrentWidget(m_ui.stopButton); m_ui.replaceCombo->setDisabled(true); m_ui.searchPlaceCombo->setDisabled(true); m_ui.useRegExp->setDisabled(true); m_ui.matchCase->setDisabled(true); m_ui.expandResults->setDisabled(true); m_ui.currentFolderButton->setDisabled(true); clearMarks(); m_curResults->tree->clear(); m_curResults->tree->setCurrentItem(nullptr); m_curResults->matches = 0; disconnect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, nullptr); m_ui.resultTabWidget->setTabText(m_ui.resultTabWidget->currentIndex(), m_ui.searchCombo->currentText()); m_toolView->setCursor(Qt::WaitCursor); m_searchDiskFilesDone = false; m_searchOpenFilesDone = false; const bool inCurrentProject = m_ui.searchPlaceCombo->currentIndex() == Project; const bool inAllOpenProjects = m_ui.searchPlaceCombo->currentIndex() == AllProjects; if (m_ui.searchPlaceCombo->currentIndex() == CurrentFile) { m_searchDiskFilesDone = true; m_resultBaseDir.clear(); QList documents; documents << m_mainWindow->activeView()->document(); addHeaderItem(); m_searchOpenFiles.startSearch(documents, reg); } else if (m_ui.searchPlaceCombo->currentIndex() == OpenFiles) { m_searchDiskFilesDone = true; m_resultBaseDir.clear(); const QList documents = m_kateApp->documents(); addHeaderItem(); m_searchOpenFiles.startSearch(documents, reg); } else if (m_ui.searchPlaceCombo->currentIndex() == Folder) { m_resultBaseDir = m_ui.folderRequester->url().path(); if (!m_resultBaseDir.isEmpty() && !m_resultBaseDir.endsWith(QLatin1Char('/'))) m_resultBaseDir += QLatin1Char('/'); addHeaderItem(); m_folderFilesList.generateList(m_ui.folderRequester->text(), m_ui.recursiveCheckBox->isChecked(), m_ui.hiddenCheckBox->isChecked(), m_ui.symLinkCheckBox->isChecked(), m_ui.binaryCheckBox->isChecked(), m_ui.filterCombo->currentText(), m_ui.excludeCombo->currentText()); // the file list will be ready when the thread returns (connected to folderFileListChanged) } else if (inCurrentProject || inAllOpenProjects) { /** * init search with file list from current project, if any */ m_resultBaseDir.clear(); QStringList files; if (m_projectPluginView) { if (inCurrentProject) { m_resultBaseDir = m_projectPluginView->property("projectBaseDir").toString(); } else { m_resultBaseDir = m_projectPluginView->property("allProjectsCommonBaseDir").toString(); } if (!m_resultBaseDir.endsWith(QLatin1Char('/'))) m_resultBaseDir += QLatin1Char('/'); QStringList projectFiles; if (inCurrentProject) { projectFiles = m_projectPluginView->property("projectFiles").toStringList(); } else { projectFiles = m_projectPluginView->property("allProjectsFiles").toStringList(); } files = filterFiles(projectFiles); } addHeaderItem(); QList openList; const auto docs = m_kateApp->documents(); for (const auto doc : docs) { // match project file's list toLocalFile() int index = files.indexOf(doc->url().toLocalFile()); if (index != -1) { openList << doc; files.removeAt(index); } } // search order is important: Open files starts immediately and should finish // earliest after first event loop. // The DiskFile might finish immediately if (!openList.empty()) { m_searchOpenFiles.startSearch(openList, m_curResults->regExp); } else { m_searchOpenFilesDone = true; } m_searchDiskFiles.startSearch(files, reg); } else { Q_ASSERT_X(false, "KatePluginSearchView::startSearch", "case not handled"); } } void KatePluginSearchView::startSearchWhileTyping() { if (!m_searchDiskFilesDone || !m_searchOpenFilesDone) { return; } m_isSearchAsYouType = true; QString currentSearchText = m_ui.searchCombo->currentText(); m_ui.searchButton->setDisabled(currentSearchText.isEmpty()); // Do not clear the search results if you press up by mistake if (currentSearchText.isEmpty()) return; if (!m_mainWindow->activeView()) return; KTextEditor::Document *doc = m_mainWindow->activeView()->document(); if (!doc) return; m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "This is a bug"; return; } // check if we typed something or just changed combobox index // changing index should not trigger a search-as-you-type if (m_ui.searchCombo->currentIndex() > 0 && currentSearchText == m_ui.searchCombo->itemText(m_ui.searchCombo->currentIndex())) { return; } // Now we should have a true typed text change QRegularExpression::PatternOptions patternOptions = (m_ui.matchCase->isChecked() ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); QString pattern = (m_ui.useRegExp->isChecked() ? currentSearchText : QRegularExpression::escape(currentSearchText)); QRegularExpression reg(pattern, patternOptions); if (!reg.isValid()) { // qDebug() << "invalid regexp"; indicateMatch(false); return; } disconnect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, nullptr); m_curResults->regExp = reg; m_curResults->useRegExp = m_ui.useRegExp->isChecked(); m_ui.replaceCheckedBtn->setDisabled(true); m_ui.replaceButton->setDisabled(true); m_ui.nextButton->setDisabled(true); int cursorPosition = m_ui.searchCombo->lineEdit()->cursorPosition(); bool hasSelected = m_ui.searchCombo->lineEdit()->hasSelectedText(); m_ui.searchCombo->blockSignals(true); m_ui.searchCombo->setItemText(0, currentSearchText); m_ui.searchCombo->setCurrentIndex(0); m_ui.searchCombo->lineEdit()->setCursorPosition(cursorPosition); if (hasSelected) { // This restores the select all from invoking openSearchView // This selects too much if we have a partial selection and toggle match-case/regexp m_ui.searchCombo->lineEdit()->selectAll(); } m_ui.searchCombo->blockSignals(false); // Prepare for the new search content clearMarks(); m_resultBaseDir.clear(); m_curResults->tree->clear(); m_curResults->tree->setCurrentItem(nullptr); m_curResults->matches = 0; // Add the search-as-you-type header item TreeWidgetItem *item = new TreeWidgetItem(m_curResults->tree, QStringList()); item->setData(0, ReplaceMatches::FileUrlRole, doc->url().toString()); item->setData(0, ReplaceMatches::FileNameRole, doc->documentName()); item->setData(0, ReplaceMatches::StartLineRole, 0); item->setCheckState(0, Qt::Checked); item->setFlags(item->flags() | Qt::ItemIsAutoTristate); // Do the search int searchStoppedAt = m_searchOpenFiles.searchOpenFile(doc, reg, 0); searchWhileTypingDone(); if (searchStoppedAt != 0) { delete m_infoMessage; const QString msg = i18n("Searching while you type was interrupted. It would have taken too long."); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Warning); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(3000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } } void KatePluginSearchView::searchDone() { m_changeTimer.stop(); // avoid "while you type" search directly after if (sender() == &m_searchDiskFiles) { m_searchDiskFilesDone = true; } if (sender() == &m_searchOpenFiles) { m_searchOpenFilesDone = true; } if (!m_searchDiskFilesDone || !m_searchOpenFilesDone) { return; } QWidget *fw = QApplication::focusWidget(); // NOTE: we take the focus widget here before the enabling/disabling // moves the focus around. m_ui.newTabButton->setDisabled(false); m_ui.searchCombo->setDisabled(false); m_ui.searchButton->setDisabled(false); m_ui.stopAndNext->setCurrentWidget(m_ui.nextButton); m_ui.displayOptions->setDisabled(false); m_ui.replaceCombo->setDisabled(false); m_ui.searchPlaceCombo->setDisabled(false); m_ui.useRegExp->setDisabled(false); m_ui.matchCase->setDisabled(false); m_ui.expandResults->setDisabled(false); m_ui.currentFolderButton->setDisabled(false); if (!m_curResults) { return; } m_ui.replaceCheckedBtn->setDisabled(m_curResults->matches < 1); m_ui.replaceButton->setDisabled(m_curResults->matches < 1); m_ui.nextButton->setDisabled(m_curResults->matches < 1); m_curResults->tree->sortItems(0, Qt::AscendingOrder); m_curResults->tree->expandAll(); m_curResults->tree->resizeColumnToContents(0); if (m_curResults->tree->columnWidth(0) < m_curResults->tree->width() - 30) { m_curResults->tree->setColumnWidth(0, m_curResults->tree->width() - 30); } // expand the "header item " to display all files and all results if configured expandResults(); updateResultsRootItem(); connect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, static_cast(&QTimer::start)); indicateMatch(m_curResults->matches > 0); m_curResults = nullptr; m_toolView->unsetCursor(); if (fw == m_ui.stopButton) { m_ui.searchCombo->setFocus(); } m_searchJustOpened = false; } void KatePluginSearchView::searchWhileTypingDone() { if (!m_curResults) { return; } bool popupVisible = m_ui.searchCombo->lineEdit()->completer()->popup()->isVisible(); m_ui.replaceCheckedBtn->setDisabled(m_curResults->matches < 1); m_ui.replaceButton->setDisabled(m_curResults->matches < 1); m_ui.nextButton->setDisabled(m_curResults->matches < 1); m_curResults->tree->expandAll(); m_curResults->tree->resizeColumnToContents(0); if (m_curResults->tree->columnWidth(0) < m_curResults->tree->width() - 30) { m_curResults->tree->setColumnWidth(0, m_curResults->tree->width() - 30); } QWidget *focusObject = nullptr; QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { QTreeWidgetItem *child = root->child(0); if (!m_searchJustOpened) { focusObject = qobject_cast(QGuiApplication::focusObject()); } indicateMatch(child); updateResultsRootItem(); connect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, static_cast(&QTimer::start)); } m_curResults = nullptr; if (focusObject) { focusObject->setFocus(); } if (popupVisible) { m_ui.searchCombo->lineEdit()->completer()->complete(); } m_searchJustOpened = false; } void KatePluginSearchView::searching(const QString &file) { if (!m_curResults) { return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { if (file.size() > 70) { root->setData(0, Qt::DisplayRole, i18n("Searching: ...%1", file.right(70))); } else { root->setData(0, Qt::DisplayRole, i18n("Searching: %1", file)); } } } void KatePluginSearchView::indicateMatch(bool hasMatch) { QLineEdit *const lineEdit = m_ui.searchCombo->lineEdit(); QPalette background(lineEdit->palette()); if (hasMatch) { // Green background for line edit KColorScheme::adjustBackground(background, KColorScheme::PositiveBackground); } else { // Reset background of line edit background = QPalette(); } // Red background for line edit // KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground); // Neutral background // KColorScheme::adjustBackground(background, KColorScheme::NeutralBackground); lineEdit->setPalette(background); } void KatePluginSearchView::replaceSingleMatch() { // Save the search text if (m_ui.searchCombo->findText(m_ui.searchCombo->currentText()) == -1) { m_ui.searchCombo->insertItem(1, m_ui.searchCombo->currentText()); m_ui.searchCombo->setCurrentIndex(1); } // Save the replace text if (m_ui.replaceCombo->findText(m_ui.replaceCombo->currentText()) == -1) { m_ui.replaceCombo->insertItem(1, m_ui.replaceCombo->currentText()); m_ui.replaceCombo->setCurrentIndex(1); } // Check if the cursor is at the current item if not jump there Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; // Security measure } QTreeWidgetItem *item = res->tree->currentItem(); if (!item || !item->parent()) { // Nothing was selected goToNextMatch(); return; } if (!m_mainWindow->activeView() || !m_mainWindow->activeView()->cursorPosition().isValid()) { itemSelected(item); // Correct any bad cursor positions return; } int cursorLine = m_mainWindow->activeView()->cursorPosition().line(); int cursorColumn = m_mainWindow->activeView()->cursorPosition().column(); int startLine = item->data(0, ReplaceMatches::StartLineRole).toInt(); int startColumn = item->data(0, ReplaceMatches::StartColumnRole).toInt(); if ((cursorLine != startLine) || (cursorColumn != startColumn)) { itemSelected(item); return; } KTextEditor::Document *doc = m_mainWindow->activeView()->document(); // Find the corresponding range int i; for (i = 0; i < m_matchRanges.size(); i++) { if (m_matchRanges[i]->document() != doc) continue; if (m_matchRanges[i]->start().line() != startLine) continue; if (m_matchRanges[i]->start().column() != startColumn) continue; break; } if (i >= m_matchRanges.size()) { goToNextMatch(); return; } m_replacer.replaceSingleMatch(doc, item, res->regExp, m_ui.replaceCombo->currentText()); goToNextMatch(); } void KatePluginSearchView::replaceChecked() { if (m_ui.searchCombo->findText(m_ui.searchCombo->currentText()) == -1) { m_ui.searchCombo->insertItem(1, m_ui.searchCombo->currentText()); m_ui.searchCombo->setCurrentIndex(1); } if (m_ui.replaceCombo->findText(m_ui.replaceCombo->currentText()) == -1) { m_ui.replaceCombo->insertItem(1, m_ui.replaceCombo->currentText()); m_ui.replaceCombo->setCurrentIndex(1); } m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "Results not found"; return; } m_ui.stopAndNext->setCurrentWidget(m_ui.stopButton); m_ui.displayOptions->setChecked(false); m_ui.displayOptions->setDisabled(true); m_ui.newTabButton->setDisabled(true); m_ui.searchCombo->setDisabled(true); m_ui.searchButton->setDisabled(true); m_ui.replaceCheckedBtn->setDisabled(true); m_ui.replaceButton->setDisabled(true); m_ui.replaceCombo->setDisabled(true); m_ui.searchPlaceCombo->setDisabled(true); m_ui.useRegExp->setDisabled(true); m_ui.matchCase->setDisabled(true); m_ui.expandResults->setDisabled(true); m_ui.currentFolderButton->setDisabled(true); m_curResults->replaceStr = m_ui.replaceCombo->currentText(); QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { m_curResults->treeRootText = root->data(0, Qt::DisplayRole).toString(); } m_replacer.replaceChecked(m_curResults->tree, m_curResults->regExp, m_curResults->replaceStr); } void KatePluginSearchView::replaceStatus(const QUrl &url, int replacedInFile, int matchesInFile) { if (!m_curResults) { // qDebug() << "m_curResults == nullptr"; return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { QString file = url.toString(QUrl::PreferLocalFile); if (file.size() > 70) { root->setData(0, Qt::DisplayRole, i18n("Processed %1 of %2 matches in: ...%3", replacedInFile, matchesInFile, file.right(70))); } else { root->setData(0, Qt::DisplayRole, i18n("Processed %1 of %2 matches in: %3", replacedInFile, matchesInFile, file)); } } } void KatePluginSearchView::replaceDone() { m_ui.stopAndNext->setCurrentWidget(m_ui.nextButton); m_ui.replaceCombo->setDisabled(false); m_ui.newTabButton->setDisabled(false); m_ui.searchCombo->setDisabled(false); m_ui.searchButton->setDisabled(false); m_ui.replaceCheckedBtn->setDisabled(false); m_ui.replaceButton->setDisabled(false); m_ui.displayOptions->setDisabled(false); m_ui.searchPlaceCombo->setDisabled(false); m_ui.useRegExp->setDisabled(false); m_ui.matchCase->setDisabled(false); m_ui.expandResults->setDisabled(false); m_ui.currentFolderButton->setDisabled(false); if (!m_curResults) { // qDebug() << "m_curResults == nullptr"; return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { root->setData(0, Qt::DisplayRole, m_curResults->treeRootText); } } void KatePluginSearchView::docViewChanged() { if (!m_mainWindow->activeView()) { return; } Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { // qDebug() << "No res"; return; } m_curResults = res; // add the marks if it is not already open KTextEditor::Document *doc = m_mainWindow->activeView()->document(); if (doc && res->tree->topLevelItemCount() > 0) { // There is always one root item with match count // and X children with files or matches in case of search while typing QTreeWidgetItem *rootItem = res->tree->topLevelItem(0); QTreeWidgetItem *fileItem = nullptr; for (int i = 0; i < rootItem->childCount(); i++) { QString url = rootItem->child(i)->data(0, ReplaceMatches::FileUrlRole).toString(); QString fName = rootItem->child(i)->data(0, ReplaceMatches::FileNameRole).toString(); if (url == doc->url().toString() && fName == doc->documentName()) { fileItem = rootItem->child(i); break; } } if (fileItem) { clearDocMarks(doc); if (m_isSearchAsYouType) { fileItem = fileItem->parent(); } for (int i = 0; i < fileItem->childCount(); i++) { if (fileItem->child(i)->checkState(0) == Qt::Unchecked) { continue; } addMatchMark(doc, fileItem->child(i)); } } // Re-add the highlighting on document reload connect(doc, &KTextEditor::Document::reloaded, this, &KatePluginSearchView::docViewChanged, Qt::UniqueConnection); } } void KatePluginSearchView::expandResults() { m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "Results not found"; return; } if (m_ui.expandResults->isChecked()) { m_curResults->tree->expandAll(); } else { QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); m_curResults->tree->expandItem(root); if (root && (root->childCount() > 1)) { for (int i = 0; i < root->childCount(); i++) { m_curResults->tree->collapseItem(root->child(i)); } } } } void KatePluginSearchView::updateResultsRootItem() { m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (!root) { // nothing to update return; } int checkedItemCount = 0; if (m_curResults->matches > 0) { for (QTreeWidgetItemIterator it(m_curResults->tree, QTreeWidgetItemIterator::Checked | QTreeWidgetItemIterator::NoChildren); *it; ++it) { checkedItemCount++; } } QString checkedStr = i18np("One checked", "%1 checked", checkedItemCount); int searchPlace = m_ui.searchPlaceCombo->currentIndex(); if (m_isSearchAsYouType) { searchPlace = CurrentFile; } switch (searchPlace) { case CurrentFile: root->setData(0, Qt::DisplayRole, i18np("One match (%2) found in file", "%1 matches (%2) found in file", m_curResults->matches, checkedStr)); break; case OpenFiles: root->setData(0, Qt::DisplayRole, i18np("One match (%2) found in open files", "%1 matches (%2) found in open files", m_curResults->matches, checkedStr)); break; case Folder: root->setData(0, Qt::DisplayRole, i18np("One match (%3) found in folder %2", "%1 matches (%3) found in folder %2", m_curResults->matches, m_resultBaseDir, checkedStr)); break; case Project: { QString projectName; if (m_projectPluginView) { projectName = m_projectPluginView->property("projectName").toString(); } root->setData(0, Qt::DisplayRole, i18np("One match (%4) found in project %2 (%3)", "%1 matches (%4) found in project %2 (%3)", m_curResults->matches, projectName, m_resultBaseDir, checkedStr)); break; } case AllProjects: // "in Open Projects" root->setData( 0, Qt::DisplayRole, i18np("One match (%3) found in all open projects (common parent: %2)", "%1 matches (%3) found in all open projects (common parent: %2)", m_curResults->matches, m_resultBaseDir, checkedStr)); break; } docViewChanged(); } void KatePluginSearchView::itemSelected(QTreeWidgetItem *item) { if (!item) return; m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { return; } while (item->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { item->treeWidget()->expandItem(item); item = item->child(0); if (!item) return; } item->treeWidget()->setCurrentItem(item); // get stuff int toLine = item->data(0, ReplaceMatches::StartLineRole).toInt(); int toColumn = item->data(0, ReplaceMatches::StartColumnRole).toInt(); KTextEditor::Document *doc; QString url = item->data(0, ReplaceMatches::FileUrlRole).toString(); if (!url.isEmpty()) { doc = m_kateApp->findUrl(QUrl::fromUserInput(url)); } else { doc = m_replacer.findNamed(item->data(0, ReplaceMatches::FileNameRole).toString()); } // add the marks to the document if it is not already open if (!doc) { doc = m_kateApp->openUrl(QUrl::fromUserInput(url)); } if (!doc) return; // open the right view... m_mainWindow->activateView(doc); // any view active? if (!m_mainWindow->activeView()) { return; } // set the cursor to the correct position m_mainWindow->activeView()->setCursorPosition(KTextEditor::Cursor(toLine, toColumn)); m_mainWindow->activeView()->setFocus(); } void KatePluginSearchView::goToNextMatch() { bool wrapFromFirst = false; bool startFromFirst = false; bool startFromCursor = false; Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } QTreeWidgetItem *curr = res->tree->currentItem(); bool focusInView = m_mainWindow->activeView() && m_mainWindow->activeView()->hasFocus(); if (!curr && focusInView) { // no item has been visited && focus is not in searchCombo (probably in the view) -> // jump to the closest match after current cursor position // check if current file is in the file list curr = res->tree->topLevelItem(0); while (curr && curr->data(0, ReplaceMatches::FileUrlRole).toString() != m_mainWindow->activeView()->document()->url().toString()) { curr = res->tree->itemBelow(curr); } // now we are either in this file or !curr if (curr) { QTreeWidgetItem *fileBefore = curr; res->tree->expandItem(curr); int lineNr = 0; int columnNr = 0; if (m_mainWindow->activeView()->cursorPosition().isValid()) { lineNr = m_mainWindow->activeView()->cursorPosition().line(); columnNr = m_mainWindow->activeView()->cursorPosition().column(); } if (!curr->data(0, ReplaceMatches::StartColumnRole).isValid()) { curr = res->tree->itemBelow(curr); }; while (curr && curr->data(0, ReplaceMatches::StartLineRole).toInt() <= lineNr && curr->data(0, ReplaceMatches::FileUrlRole).toString() == m_mainWindow->activeView()->document()->url().toString()) { if (curr->data(0, ReplaceMatches::StartLineRole).toInt() == lineNr && curr->data(0, ReplaceMatches::StartColumnRole).toInt() >= columnNr - curr->data(0, ReplaceMatches::MatchLenRole).toInt()) { break; } fileBefore = curr; curr = res->tree->itemBelow(curr); } curr = fileBefore; startFromCursor = true; } } if (!curr) { curr = res->tree->topLevelItem(0); startFromFirst = true; } if (!curr) return; if (!curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { curr = res->tree->itemBelow(curr); if (!curr) { wrapFromFirst = true; curr = res->tree->topLevelItem(0); } } itemSelected(curr); if (startFromFirst) { delete m_infoMessage; const QString msg = i18n("Starting from first match"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } else if (startFromCursor) { delete m_infoMessage; const QString msg = i18n("Next from cursor"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } else if (wrapFromFirst) { delete m_infoMessage; const QString msg = i18n("Continuing from first match"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } } void KatePluginSearchView::goToPreviousMatch() { bool fromLast = false; Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } if (res->tree->topLevelItemCount() == 0) { return; } QTreeWidgetItem *curr = res->tree->currentItem(); if (!curr) { // no item has been visited -> jump to the closest match before current cursor position // check if current file is in the file curr = res->tree->topLevelItem(0); while (curr && curr->data(0, ReplaceMatches::FileUrlRole).toString() != m_mainWindow->activeView()->document()->url().toString()) { curr = res->tree->itemBelow(curr); } // now we are either in this file or !curr if (curr) { res->tree->expandItem(curr); int lineNr = 0; int columnNr = 0; if (m_mainWindow->activeView()->cursorPosition().isValid()) { lineNr = m_mainWindow->activeView()->cursorPosition().line(); columnNr = m_mainWindow->activeView()->cursorPosition().column() - 1; } if (!curr->data(0, ReplaceMatches::StartColumnRole).isValid()) { curr = res->tree->itemBelow(curr); }; while (curr && curr->data(0, ReplaceMatches::StartLineRole).toInt() <= lineNr && curr->data(0, ReplaceMatches::FileUrlRole).toString() == m_mainWindow->activeView()->document()->url().toString()) { if (curr->data(0, ReplaceMatches::StartLineRole).toInt() == lineNr && curr->data(0, ReplaceMatches::StartColumnRole).toInt() > columnNr) { break; } curr = res->tree->itemBelow(curr); } } } QTreeWidgetItem *startChild = curr; // go to the item above. (curr == null is not a problem) curr = res->tree->itemAbove(curr); // expand the items above if needed if (curr && curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { res->tree->expandItem(curr); // probably this file item curr = res->tree->itemAbove(curr); if (curr && curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { res->tree->expandItem(curr); // probably file above if this is reached } curr = res->tree->itemAbove(startChild); } // skip file name items and the root item while (curr && curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { curr = res->tree->itemAbove(curr); } if (!curr) { // select the last child of the last next-to-top-level item QTreeWidgetItem *root = res->tree->topLevelItem(0); // select the last "root item" if (!root || (root->childCount() < 1)) return; root = root->child(root->childCount() - 1); // select the last match of the "root item" if (!root || (root->childCount() < 1)) return; curr = root->child(root->childCount() - 1); fromLast = true; } itemSelected(curr); if (fromLast) { delete m_infoMessage; const QString msg = i18n("Continuing from last match"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } } void KatePluginSearchView::readSessionConfig(const KConfigGroup &cg) { m_ui.searchCombo->clear(); m_ui.searchCombo->addItem(QString()); // Add empty Item m_ui.searchCombo->addItems(cg.readEntry("Search", QStringList())); m_ui.replaceCombo->clear(); m_ui.replaceCombo->addItem(QString()); // Add empty Item m_ui.replaceCombo->addItems(cg.readEntry("Replaces", QStringList())); m_ui.matchCase->setChecked(cg.readEntry("MatchCase", false)); m_ui.useRegExp->setChecked(cg.readEntry("UseRegExp", false)); m_ui.expandResults->setChecked(cg.readEntry("ExpandSearchResults", false)); int searchPlaceIndex = cg.readEntry("Place", 1); if (searchPlaceIndex < 0) { searchPlaceIndex = Folder; // for the case we happen to read -1 as Place } if ((searchPlaceIndex == Project) && (searchPlaceIndex >= m_ui.searchPlaceCombo->count())) { // handle the case that project mode was selected, but not yet available m_switchToProjectModeWhenAvailable = true; searchPlaceIndex = Folder; } m_ui.searchPlaceCombo->setCurrentIndex(searchPlaceIndex); m_ui.recursiveCheckBox->setChecked(cg.readEntry("Recursive", true)); m_ui.hiddenCheckBox->setChecked(cg.readEntry("HiddenFiles", false)); m_ui.symLinkCheckBox->setChecked(cg.readEntry("FollowSymLink", false)); m_ui.binaryCheckBox->setChecked(cg.readEntry("BinaryFiles", false)); m_ui.folderRequester->comboBox()->clear(); m_ui.folderRequester->comboBox()->addItems(cg.readEntry("SearchDiskFiless", QStringList())); m_ui.folderRequester->setText(cg.readEntry("SearchDiskFiles", QString())); m_ui.filterCombo->clear(); m_ui.filterCombo->addItems(cg.readEntry("Filters", QStringList())); m_ui.filterCombo->setCurrentIndex(cg.readEntry("CurrentFilter", -1)); m_ui.excludeCombo->clear(); m_ui.excludeCombo->addItems(cg.readEntry("ExcludeFilters", QStringList())); m_ui.excludeCombo->setCurrentIndex(cg.readEntry("CurrentExcludeFilter", -1)); m_ui.displayOptions->setChecked(searchPlaceIndex == Folder); } void KatePluginSearchView::writeSessionConfig(KConfigGroup &cg) { QStringList searchHistoy; for (int i = 1; i < m_ui.searchCombo->count(); i++) { searchHistoy << m_ui.searchCombo->itemText(i); } cg.writeEntry("Search", searchHistoy); QStringList replaceHistoy; for (int i = 1; i < m_ui.replaceCombo->count(); i++) { replaceHistoy << m_ui.replaceCombo->itemText(i); } cg.writeEntry("Replaces", replaceHistoy); cg.writeEntry("MatchCase", m_ui.matchCase->isChecked()); cg.writeEntry("UseRegExp", m_ui.useRegExp->isChecked()); cg.writeEntry("ExpandSearchResults", m_ui.expandResults->isChecked()); cg.writeEntry("Place", m_ui.searchPlaceCombo->currentIndex()); cg.writeEntry("Recursive", m_ui.recursiveCheckBox->isChecked()); cg.writeEntry("HiddenFiles", m_ui.hiddenCheckBox->isChecked()); cg.writeEntry("FollowSymLink", m_ui.symLinkCheckBox->isChecked()); cg.writeEntry("BinaryFiles", m_ui.binaryCheckBox->isChecked()); QStringList folders; for (int i = 0; i < qMin(m_ui.folderRequester->comboBox()->count(), 10); i++) { folders << m_ui.folderRequester->comboBox()->itemText(i); } cg.writeEntry("SearchDiskFiless", folders); cg.writeEntry("SearchDiskFiles", m_ui.folderRequester->text()); QStringList filterItems; for (int i = 0; i < qMin(m_ui.filterCombo->count(), 10); i++) { filterItems << m_ui.filterCombo->itemText(i); } cg.writeEntry("Filters", filterItems); cg.writeEntry("CurrentFilter", m_ui.filterCombo->findText(m_ui.filterCombo->currentText())); QStringList excludeFilterItems; for (int i = 0; i < qMin(m_ui.excludeCombo->count(), 10); i++) { excludeFilterItems << m_ui.excludeCombo->itemText(i); } cg.writeEntry("ExcludeFilters", excludeFilterItems); cg.writeEntry("CurrentExcludeFilter", m_ui.excludeCombo->findText(m_ui.excludeCombo->currentText())); } void KatePluginSearchView::addTab() { if ((sender() != m_ui.newTabButton) && (m_ui.resultTabWidget->count() > 0) && m_ui.resultTabWidget->tabText(m_ui.resultTabWidget->currentIndex()).isEmpty()) { return; } Results *res = new Results(); res->tree->setRootIsDecorated(false); connect(res->tree, &QTreeWidget::itemDoubleClicked, this, &KatePluginSearchView::itemSelected, Qt::UniqueConnection); res->searchPlaceIndex = m_ui.searchPlaceCombo->currentIndex(); res->useRegExp = m_ui.useRegExp->isChecked(); res->matchCase = m_ui.matchCase->isChecked(); m_ui.resultTabWidget->addTab(res, QString()); m_ui.resultTabWidget->setCurrentIndex(m_ui.resultTabWidget->count() - 1); m_ui.stackedWidget->setCurrentIndex(0); m_ui.resultTabWidget->tabBar()->show(); m_ui.displayOptions->setChecked(false); res->tree->installEventFilter(this); } void KatePluginSearchView::tabCloseRequested(int index) { Results *tmp = qobject_cast(m_ui.resultTabWidget->widget(index)); if (m_curResults == tmp) { m_searchOpenFiles.cancelSearch(); m_searchDiskFiles.cancelSearch(); } if (m_ui.resultTabWidget->count() > 1) { delete tmp; // remove the tab m_curResults = nullptr; } if (m_ui.resultTabWidget->count() == 1) { m_ui.resultTabWidget->tabBar()->hide(); } } void KatePluginSearchView::resultTabChanged(int index) { if (index < 0) { return; } Results *res = qobject_cast(m_ui.resultTabWidget->widget(index)); if (!res) { // qDebug() << "No res found"; return; } m_ui.searchCombo->blockSignals(true); m_ui.matchCase->blockSignals(true); m_ui.useRegExp->blockSignals(true); m_ui.searchPlaceCombo->blockSignals(true); m_ui.searchCombo->lineEdit()->setText(m_ui.resultTabWidget->tabText(index)); m_ui.useRegExp->setChecked(res->useRegExp); m_ui.matchCase->setChecked(res->matchCase); m_ui.searchPlaceCombo->setCurrentIndex(res->searchPlaceIndex); m_ui.searchCombo->blockSignals(false); m_ui.matchCase->blockSignals(false); m_ui.useRegExp->blockSignals(false); m_ui.searchPlaceCombo->blockSignals(false); searchPlaceChanged(); } void KatePluginSearchView::onResize(const QSize &size) { bool vertical = size.width() < size.height(); if (!m_isLeftRight && vertical) { m_isLeftRight = true; m_ui.gridLayout->addWidget(m_ui.searchCombo, 0, 1, 1, 8); m_ui.gridLayout->addWidget(m_ui.findLabel, 0, 0); m_ui.gridLayout->addWidget(m_ui.searchButton, 1, 0, 1, 2); m_ui.gridLayout->addWidget(m_ui.stopAndNext, 1, 2); m_ui.gridLayout->addWidget(m_ui.searchPlaceCombo, 1, 3, 1, 3); m_ui.gridLayout->addWidget(m_ui.displayOptions, 1, 6); m_ui.gridLayout->addWidget(m_ui.matchCase, 1, 7); m_ui.gridLayout->addWidget(m_ui.useRegExp, 1, 8); m_ui.gridLayout->addWidget(m_ui.replaceCombo, 2, 1, 1, 8); m_ui.gridLayout->addWidget(m_ui.replaceLabel, 2, 0); m_ui.gridLayout->addWidget(m_ui.replaceButton, 3, 0, 1, 2); m_ui.gridLayout->addWidget(m_ui.replaceCheckedBtn, 3, 2); m_ui.gridLayout->addWidget(m_ui.expandResults, 3, 7); m_ui.gridLayout->addWidget(m_ui.newTabButton, 3, 8); m_ui.gridLayout->setColumnStretch(4, 2); m_ui.gridLayout->setColumnStretch(2, 0); } else if (m_isLeftRight && !vertical) { m_isLeftRight = false; m_ui.gridLayout->addWidget(m_ui.searchCombo, 0, 2); m_ui.gridLayout->addWidget(m_ui.findLabel, 0, 1); m_ui.gridLayout->addWidget(m_ui.searchButton, 0, 3); m_ui.gridLayout->addWidget(m_ui.stopAndNext, 0, 4); m_ui.gridLayout->addWidget(m_ui.searchPlaceCombo, 0, 5, 1, 4); m_ui.gridLayout->addWidget(m_ui.matchCase, 1, 5); m_ui.gridLayout->addWidget(m_ui.useRegExp, 1, 6); m_ui.gridLayout->addWidget(m_ui.replaceCombo, 1, 2); m_ui.gridLayout->addWidget(m_ui.replaceLabel, 1, 1); m_ui.gridLayout->addWidget(m_ui.replaceButton, 1, 3); m_ui.gridLayout->addWidget(m_ui.replaceCheckedBtn, 1, 4); m_ui.gridLayout->addWidget(m_ui.expandResults, 1, 8); m_ui.gridLayout->addWidget(m_ui.newTabButton, 0, 0); m_ui.gridLayout->addWidget(m_ui.displayOptions, 1, 0); m_ui.gridLayout->setColumnStretch(4, 0); m_ui.gridLayout->setColumnStretch(2, 2); m_ui.findLabel->setAlignment(Qt::AlignRight); m_ui.replaceLabel->setAlignment(Qt::AlignRight); } } bool KatePluginSearchView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); QTreeWidget *tree = qobject_cast(obj); if (tree) { if (ke->matches(QKeySequence::Copy)) { // user pressed ctrl+c -> copy full URL to the clipboard QVariant variant = tree->currentItem()->data(0, ReplaceMatches::FileUrlRole); QApplication::clipboard()->setText(variant.toString()); event->accept(); return true; } if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) { if (tree->currentItem()) { itemSelected(tree->currentItem()); event->accept(); return true; } } } // NOTE: Qt::Key_Escape is handled by handleEsc } if (event->type() == QEvent::Resize) { QResizeEvent *re = static_cast(event); if (obj == m_toolView) { onResize(re->size()); } } return QObject::eventFilter(obj, event); } void KatePluginSearchView::searchContextMenu(const QPoint &pos) { QSet actionPointers; QMenu *const contextMenu = m_ui.searchCombo->lineEdit()->createStandardContextMenu(); if (!contextMenu) return; if (m_ui.useRegExp->isChecked()) { QMenu *menu = contextMenu->addMenu(i18n("Add...")); if (!menu) return; menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addRegexHelperActionsForSearch(&actionPointers, menu); } // Show menu and act QAction *const result = contextMenu->exec(m_ui.searchCombo->mapToGlobal(pos)); regexHelperActOnAction(result, actionPointers, m_ui.searchCombo->lineEdit()); } void KatePluginSearchView::replaceContextMenu(const QPoint &pos) { QMenu *const contextMenu = m_ui.replaceCombo->lineEdit()->createStandardContextMenu(); if (!contextMenu) return; QMenu *menu = contextMenu->addMenu(i18n("Add...")); if (!menu) return; menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); QSet actionPointers; addSpecialCharsHelperActionsForReplace(&actionPointers, menu); if (m_ui.useRegExp->isChecked()) { addRegexHelperActionsForReplace(&actionPointers, menu); } // Show menu and act QAction *const result = contextMenu->exec(m_ui.replaceCombo->mapToGlobal(pos)); regexHelperActOnAction(result, actionPointers, m_ui.replaceCombo->lineEdit()); } void KatePluginSearchView::slotPluginViewCreated(const QString &name, QObject *pluginView) { // add view if (pluginView && name == QLatin1String("kateprojectplugin")) { m_projectPluginView = pluginView; slotProjectFileNameChanged(); connect(pluginView, SIGNAL(projectFileNameChanged()), this, SLOT(slotProjectFileNameChanged())); } } void KatePluginSearchView::slotPluginViewDeleted(const QString &name, QObject *) { // remove view if (name == QLatin1String("kateprojectplugin")) { m_projectPluginView = nullptr; slotProjectFileNameChanged(); } } void KatePluginSearchView::slotProjectFileNameChanged() { // query new project file name QString projectFileName; if (m_projectPluginView) { projectFileName = m_projectPluginView->property("projectFileName").toString(); } // have project, enable gui for it if (!projectFileName.isEmpty()) { if (m_ui.searchPlaceCombo->count() <= Project) { // add "in Project" m_ui.searchPlaceCombo->addItem(QIcon::fromTheme(QStringLiteral("project-open")), i18n("In Current Project")); if (m_switchToProjectModeWhenAvailable) { // switch to search "in Project" m_switchToProjectModeWhenAvailable = false; setSearchPlace(Project); } // add "in Open Projects" m_ui.searchPlaceCombo->addItem(QIcon::fromTheme(QStringLiteral("project-open")), i18n("In All Open Projects")); } } // else: disable gui for it else { if (m_ui.searchPlaceCombo->count() >= Project) { // switch to search "in Open files", if "in Project" is active if (m_ui.searchPlaceCombo->currentIndex() >= Project) { setSearchPlace(OpenFiles); } // remove "in Project" and "in all projects" while (m_ui.searchPlaceCombo->count() > Project) { m_ui.searchPlaceCombo->removeItem(m_ui.searchPlaceCombo->count() - 1); } } } } KateSearchCommand::KateSearchCommand(QObject *parent) : KTextEditor::Command(QStringList() << QStringLiteral("grep") << QStringLiteral("newGrep") << QStringLiteral("search") << QStringLiteral("newSearch") << QStringLiteral("pgrep") << QStringLiteral("newPGrep"), parent) { } bool KateSearchCommand::exec(KTextEditor::View * /*view*/, const QString &cmd, QString & /*msg*/, const KTextEditor::Range &) { // create a list of args QStringList args(cmd.split(QLatin1Char(' '), QString::KeepEmptyParts)); QString command = args.takeFirst(); QString searchText = args.join(QLatin1Char(' ')); if (command == QLatin1String("grep") || command == QLatin1String("newGrep")) { emit setSearchPlace(KatePluginSearchView::Folder); emit setCurrentFolder(); if (command == QLatin1String("newGrep")) emit newTab(); } else if (command == QLatin1String("search") || command == QLatin1String("newSearch")) { emit setSearchPlace(KatePluginSearchView::OpenFiles); if (command == QLatin1String("newSearch")) emit newTab(); } else if (command == QLatin1String("pgrep") || command == QLatin1String("newPGrep")) { emit setSearchPlace(KatePluginSearchView::Project); if (command == QLatin1String("newPGrep")) emit newTab(); } emit setSearchString(searchText); emit startSearch(); return true; } bool KateSearchCommand::help(KTextEditor::View * /*view*/, const QString &cmd, QString &msg) { if (cmd.startsWith(QLatin1String("grep"))) { msg = i18n("Usage: grep [pattern to search for in folder]"); } else if (cmd.startsWith(QLatin1String("newGrep"))) { msg = i18n("Usage: newGrep [pattern to search for in folder]"); } else if (cmd.startsWith(QLatin1String("search"))) { msg = i18n("Usage: search [pattern to search for in open files]"); } else if (cmd.startsWith(QLatin1String("newSearch"))) { msg = i18n("Usage: search [pattern to search for in open files]"); } else if (cmd.startsWith(QLatin1String("pgrep"))) { msg = i18n("Usage: pgrep [pattern to search for in current project]"); } else if (cmd.startsWith(QLatin1String("newPGrep"))) { msg = i18n("Usage: newPGrep [pattern to search for in current project]"); } return true; } #include "plugin_search.moc" // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/search/plugin_search.h b/addons/search/plugin_search.h index b6a9f7dfc..08af4b938 100644 --- a/addons/search/plugin_search.h +++ b/addons/search/plugin_search.h @@ -1,238 +1,238 @@ /* Kate search plugin * * Copyright (C) 2011-2013 by KÃ¥re Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifndef _PLUGIN_SEARCH_H_ #define _PLUGIN_SEARCH_H_ -#include -#include -#include #include -#include #include +#include #include +#include +#include +#include -#include #include +#include #include -#include "ui_search.h" #include "ui_results.h" +#include "ui_search.h" -#include "search_open_files.h" -#include "SearchDiskFiles.h" #include "FolderFilesList.h" +#include "SearchDiskFiles.h" #include "replace_matches.h" +#include "search_open_files.h" class KateSearchCommand; namespace KTextEditor { class MovingRange; } class Results : public QWidget, public Ui::Results { Q_OBJECT public: Results(QWidget *parent = nullptr); int matches = 0; QRegularExpression regExp; bool useRegExp = false; bool matchCase; QString replaceStr; int searchPlaceIndex = 0; QString treeRootText; }; // This class keeps the focus inside the S&R plugin when pressing tab/shift+tab by overriding focusNextPrevChild() class ContainerWidget : public QWidget { Q_OBJECT public: ContainerWidget(QWidget *parent) : QWidget(parent) { } Q_SIGNALS: void nextFocus(QWidget *currentWidget, bool *found, bool next); protected: bool focusNextPrevChild(bool next) override; }; class KatePluginSearch : public KTextEditor::Plugin { Q_OBJECT public: explicit KatePluginSearch(QObject *parent = nullptr, const QList & = QList()); ~KatePluginSearch() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; private: KateSearchCommand *m_searchCommand = nullptr; }; class KatePluginSearchView : public QObject, public KXMLGUIClient, public KTextEditor::SessionConfigInterface { Q_OBJECT Q_INTERFACES(KTextEditor::SessionConfigInterface) public: enum SearchPlaces { CurrentFile, OpenFiles, Folder, Project, AllProjects }; KatePluginSearchView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWindow, KTextEditor::Application *application); ~KatePluginSearchView() override; void readSessionConfig(const KConfigGroup &config) override; void writeSessionConfig(KConfigGroup &config) override; public Q_SLOTS: void startSearch(); void setSearchString(const QString &pattern); void navigateFolderUp(); void setCurrentFolder(); void setSearchPlace(int place); void goToNextMatch(); void goToPreviousMatch(); private Q_SLOTS: void openSearchView(); void handleEsc(QEvent *e); void nextFocus(QWidget *currentWidget, bool *found, bool next); void addTab(); void tabCloseRequested(int index); void toggleOptions(bool show); void searchContextMenu(const QPoint &pos); void replaceContextMenu(const QPoint &pos); void searchPlaceChanged(); void startSearchWhileTyping(); void folderFileListChanged(); void matchFound(const QString &url, const QString &fileName, const QString &lineContent, int matchLen, int startLine, int startColumn, int endLine, int endColumn); void addMatchMark(KTextEditor::Document *doc, QTreeWidgetItem *item); void searchDone(); void searchWhileTypingDone(); void indicateMatch(bool hasMatch); void searching(const QString &file); void itemSelected(QTreeWidgetItem *item); void clearMarks(); void clearDocMarks(KTextEditor::Document *doc); void replaceSingleMatch(); void replaceChecked(); void replaceStatus(const QUrl &url, int replacedInFile, int matchesInFile); void replaceDone(); void docViewChanged(); void resultTabChanged(int index); void expandResults(); void updateResultsRootItem(); /** * keep track if the project plugin is alive and if the project file did change */ void slotPluginViewCreated(const QString &name, QObject *pluginView); void slotPluginViewDeleted(const QString &name, QObject *pluginView); void slotProjectFileNameChanged(); protected: bool eventFilter(QObject *obj, QEvent *ev) override; void addHeaderItem(); private: QTreeWidgetItem *rootFileItem(const QString &url, const QString &fName); QStringList filterFiles(const QStringList &files) const; void onResize(const QSize &size); Ui::SearchDialog m_ui; QWidget *m_toolView; KTextEditor::Application *m_kateApp; SearchOpenFiles m_searchOpenFiles; FolderFilesList m_folderFilesList; SearchDiskFiles m_searchDiskFiles; ReplaceMatches m_replacer; QAction *m_matchCase; QAction *m_useRegExp; Results *m_curResults; bool m_searchJustOpened; bool m_switchToProjectModeWhenAvailable; bool m_searchDiskFilesDone; bool m_searchOpenFilesDone; bool m_isSearchAsYouType; bool m_isLeftRight; QString m_resultBaseDir; QList m_matchRanges; QTimer m_changeTimer; QTimer m_updateSumaryTimer; QPointer m_infoMessage; /** * current project plugin view, if any */ QObject *m_projectPluginView; /** * our main window */ KTextEditor::MainWindow *m_mainWindow; }; class KateSearchCommand : public KTextEditor::Command { Q_OBJECT public: KateSearchCommand(QObject *parent); Q_SIGNALS: void setSearchPlace(int place); void setCurrentFolder(); void setSearchString(const QString &pattern); void startSearch(); void newTab(); // // KTextEditor::Command // public: bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) override; bool help(KTextEditor::View *view, const QString &cmd, QString &msg) override; }; #endif // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/search/replace_matches.cpp b/addons/search/replace_matches.cpp index 189585eab..ef884a474 100644 --- a/addons/search/replace_matches.cpp +++ b/addons/search/replace_matches.cpp @@ -1,339 +1,339 @@ /* Kate search plugin * * Copyright (C) 2011-2013 by KÃ¥re Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "replace_matches.h" -#include #include +#include #include ReplaceMatches::ReplaceMatches(QObject *parent) : QObject(parent) { } void ReplaceMatches::replaceChecked(QTreeWidget *tree, const QRegularExpression ®exp, const QString &replace) { if (m_manager == nullptr) return; if (m_rootIndex != -1) return; // already replacing m_tree = tree; m_rootIndex = 0; m_childStartIndex = 0; m_regExp = regexp; m_replaceText = replace; m_cancelReplace = false; m_progressTime.restart(); doReplaceNextMatch(); } void ReplaceMatches::setDocumentManager(KTextEditor::Application *manager) { m_manager = manager; } void ReplaceMatches::cancelReplace() { m_cancelReplace = true; } KTextEditor::Document *ReplaceMatches::findNamed(const QString &name) { const QList docs = m_manager->documents(); for (KTextEditor::Document *it : docs) { if (it->documentName() == name) { return it; } } return nullptr; } bool ReplaceMatches::replaceMatch(KTextEditor::Document *doc, QTreeWidgetItem *item, const KTextEditor::Range &range, const QRegularExpression ®Exp, const QString &replaceTxt) { if (!doc || !item) { return false; } // don't replace an already replaced item if (item->data(0, ReplaceMatches::ReplacedRole).toBool()) { // qDebug() << "not replacing already replaced item"; return false; } // Check that the text has not been modified and still matches + get captures for the replace QString matchLines = doc->text(range); QRegularExpressionMatch match = regExp.match(matchLines); if (match.capturedStart() != 0) { // qDebug() << matchLines << "Does not match" << regExp.pattern(); return false; } // Modify the replace string according to this match QString replaceText = replaceTxt; replaceText.replace(QLatin1String("\\\\"), QLatin1String("¤Search&Replace¤")); // allow captures \0 .. \9 for (int j = qMin(9, match.lastCapturedIndex()); j >= 0; --j) { QString captureLX = QStringLiteral("\\L\\%1").arg(j); QString captureUX = QStringLiteral("\\U\\%1").arg(j); QString captureX = QStringLiteral("\\%1").arg(j); replaceText.replace(captureLX, match.captured(j).toLower()); replaceText.replace(captureUX, match.captured(j).toUpper()); replaceText.replace(captureX, match.captured(j)); } // allow captures \{0} .. \{9999999}... for (int j = match.lastCapturedIndex(); j >= 0; --j) { QString captureLX = QStringLiteral("\\L\\{%1}").arg(j); QString captureUX = QStringLiteral("\\U\\{%1}").arg(j); QString captureX = QStringLiteral("\\{%1}").arg(j); replaceText.replace(captureLX, match.captured(j).toLower()); replaceText.replace(captureUX, match.captured(j).toUpper()); replaceText.replace(captureX, match.captured(j)); } replaceText.replace(QLatin1String("\\n"), QLatin1String("\n")); replaceText.replace(QLatin1String("\\t"), QLatin1String("\t")); replaceText.replace(QLatin1String("¤Search&Replace¤"), QLatin1String("\\")); doc->replaceText(range, replaceText); int newEndLine = range.start().line() + replaceText.count(QLatin1Char('\n')); int lastNL = replaceText.lastIndexOf(QLatin1Char('\n')); int newEndColumn = lastNL == -1 ? range.start().column() + replaceText.length() : replaceText.length() - lastNL - 1; item->setData(0, ReplaceMatches::ReplacedRole, true); item->setData(0, ReplaceMatches::StartLineRole, range.start().line()); item->setData(0, ReplaceMatches::StartColumnRole, range.start().column()); item->setData(0, ReplaceMatches::EndLineRole, newEndLine); item->setData(0, ReplaceMatches::EndColumnRole, newEndColumn); item->setData(0, ReplaceMatches::ReplacedTextRole, replaceText); // Convert replace text back to "html" replaceText.replace(QLatin1Char('\n'), QStringLiteral("\\n")); replaceText.replace(QLatin1Char('\t'), QStringLiteral("\\t")); QString html = item->data(0, ReplaceMatches::PreMatchRole).toString(); html += QLatin1String("") + item->data(0, ReplaceMatches::MatchRole).toString() + QLatin1String(" "); html += QLatin1String("") + replaceText + QLatin1String(""); html += item->data(0, ReplaceMatches::PostMatchRole).toString(); item->setData(0, Qt::DisplayRole, i18n("Line: %1: %2", range.start().line() + 1, html)); return true; } bool ReplaceMatches::replaceSingleMatch(KTextEditor::Document *doc, QTreeWidgetItem *item, const QRegularExpression ®Exp, const QString &replaceTxt) { if (!doc || !item) { return false; } QTreeWidgetItem *rootItem = item->parent(); if (!rootItem) { return false; } // Create a vector of moving ranges for updating the tree-view after replace QVector matches; QVector replaced; KTextEditor::MovingInterface *miface = qobject_cast(doc); int i = 0; // Only add items after "item" for (; i < rootItem->childCount(); i++) { if (item == rootItem->child(i)) break; } for (int j = i; j < rootItem->childCount(); j++) { QTreeWidgetItem *tmp = rootItem->child(j); int startLine = tmp->data(0, ReplaceMatches::StartLineRole).toInt(); int startColumn = tmp->data(0, ReplaceMatches::StartColumnRole).toInt(); int endLine = tmp->data(0, ReplaceMatches::EndLineRole).toInt(); int endColumn = tmp->data(0, ReplaceMatches::EndColumnRole).toInt(); KTextEditor::Range range(startLine, startColumn, endLine, endColumn); KTextEditor::MovingRange *mr = miface->newMovingRange(range); matches.append(mr); } if (matches.isEmpty()) { return false; } // The first range in the vector is for this match if (!replaceMatch(doc, item, matches[0]->toRange(), regExp, replaceTxt)) { return false; } delete matches.takeFirst(); // Update the remaining tree-view-items for (int j = i + 1; j < rootItem->childCount() && !matches.isEmpty(); j++) { QTreeWidgetItem *tmp = rootItem->child(j); tmp->setData(0, ReplaceMatches::StartLineRole, matches.first()->start().line()); tmp->setData(0, ReplaceMatches::StartColumnRole, matches.first()->start().column()); tmp->setData(0, ReplaceMatches::EndLineRole, matches.first()->end().line()); tmp->setData(0, ReplaceMatches::EndColumnRole, matches.first()->end().column()); delete matches.takeFirst(); } qDeleteAll(matches); return true; } void ReplaceMatches::doReplaceNextMatch() { if (!m_manager || m_tree->topLevelItemCount() != 1) { updateTreeViewItems(nullptr); m_rootIndex = -1; emit replaceDone(); return; } // NOTE The document managers signal documentWillBeDeleted() must be connected to // cancelReplace(). A closed file could lead to a crash if it is not handled. // Open the file QTreeWidgetItem *fileItem = m_tree->topLevelItem(0)->child(m_rootIndex); if (!fileItem) { updateTreeViewItems(nullptr); m_rootIndex = -1; emit replaceDone(); return; } bool isSearchAsYouType = false; if (!fileItem->data(0, StartColumnRole).toString().isEmpty()) { // this is a search as you type replace fileItem = m_tree->topLevelItem(0); isSearchAsYouType = true; } if (m_cancelReplace) { updateTreeViewItems(fileItem); m_rootIndex = -1; emit replaceDone(); return; } if (fileItem->checkState(0) == Qt::Unchecked) { updateTreeViewItems(fileItem); QTimer::singleShot(0, this, &ReplaceMatches::doReplaceNextMatch); return; } KTextEditor::Document *doc; QString docUrl = fileItem->data(0, FileUrlRole).toString(); if (docUrl.isEmpty()) { doc = findNamed(fileItem->data(0, FileNameRole).toString()); } else { doc = m_manager->findUrl(QUrl::fromUserInput(docUrl)); if (!doc) { doc = m_manager->openUrl(QUrl::fromUserInput(fileItem->data(0, FileUrlRole).toString())); } } if (!doc) { updateTreeViewItems(fileItem); QTimer::singleShot(0, this, &ReplaceMatches::doReplaceNextMatch); return; } if (m_progressTime.elapsed() > 100) { m_progressTime.restart(); if (m_currentMatches.isEmpty()) { emit replaceStatus(doc->url(), 0, 0); } else { emit replaceStatus(doc->url(), m_childStartIndex, m_currentMatches.count()); } } if (m_childStartIndex == 0) { // Create a vector of moving ranges for updating the tree-view after replace QVector replaced; KTextEditor::MovingInterface *miface = qobject_cast(doc); for (int j = 0; j < fileItem->childCount(); ++j) { QTreeWidgetItem *item = fileItem->child(j); int startLine = item->data(0, ReplaceMatches::StartLineRole).toInt(); int startColumn = item->data(0, ReplaceMatches::StartColumnRole).toInt(); int endLine = item->data(0, ReplaceMatches::EndLineRole).toInt(); int endColumn = item->data(0, ReplaceMatches::EndColumnRole).toInt(); KTextEditor::Range range(startLine, startColumn, endLine, endColumn); KTextEditor::MovingRange *mr = miface->newMovingRange(range); m_currentMatches.append(mr); m_currentReplaced << false; } } // Make one transaction for the whole replace to speed up things // and get all replacements in one "undo" KTextEditor::Document::EditingTransaction transaction(doc); // now do the replaces int i = m_childStartIndex; for (; i < fileItem->childCount(); ++i) { if (m_progressTime.elapsed() > 100) { break; } QTreeWidgetItem *item = fileItem->child(i); if (item->checkState(0) == Qt::Checked) { m_currentReplaced[i] = replaceMatch(doc, item, m_currentMatches[i]->toRange(), m_regExp, m_replaceText); item->setCheckState(0, Qt::PartiallyChecked); } } if (i == fileItem->childCount()) { updateTreeViewItems(fileItem); if (isSearchAsYouType) { m_rootIndex = -1; emit replaceDone(); return; } } else { m_childStartIndex = i; } QTimer::singleShot(0, this, &ReplaceMatches::doReplaceNextMatch); } void ReplaceMatches::updateTreeViewItems(QTreeWidgetItem *fileItem) { if (fileItem && m_currentReplaced.size() == m_currentMatches.size() && m_currentReplaced.size() == fileItem->childCount()) { for (int j = 0; j < m_currentReplaced.size() && j < m_currentMatches.size(); ++j) { QTreeWidgetItem *item = fileItem->child(j); if (!m_currentReplaced[j] && item) { item->setData(0, ReplaceMatches::StartLineRole, m_currentMatches[j]->start().line()); item->setData(0, ReplaceMatches::StartColumnRole, m_currentMatches[j]->start().column()); item->setData(0, ReplaceMatches::EndLineRole, m_currentMatches[j]->end().line()); item->setData(0, ReplaceMatches::EndColumnRole, m_currentMatches[j]->end().column()); } } } qDeleteAll(m_currentMatches); m_rootIndex++; m_childStartIndex = 0; m_currentMatches.clear(); m_currentReplaced.clear(); } diff --git a/addons/search/replace_matches.h b/addons/search/replace_matches.h index 1c4ced9cb..00fae1b65 100644 --- a/addons/search/replace_matches.h +++ b/addons/search/replace_matches.h @@ -1,88 +1,88 @@ /* Kate search plugin * * Copyright (C) 2011 by KÃ¥re Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifndef _REPLACE_MATCHES_H_ #define _REPLACE_MATCHES_H_ +#include #include #include #include -#include -#include #include +#include #include #include class ReplaceMatches : public QObject { Q_OBJECT public: enum MatchData { FileUrlRole = Qt::UserRole, FileNameRole, StartLineRole, StartColumnRole, EndLineRole, EndColumnRole, MatchLenRole, PreMatchRole, MatchRole, PostMatchRole, ReplacedRole, ReplacedTextRole, }; ReplaceMatches(QObject *parent = nullptr); void setDocumentManager(KTextEditor::Application *manager); bool replaceMatch(KTextEditor::Document *doc, QTreeWidgetItem *item, const KTextEditor::Range &range, const QRegularExpression ®Exp, const QString &replaceTxt); bool replaceSingleMatch(KTextEditor::Document *doc, QTreeWidgetItem *item, const QRegularExpression ®Exp, const QString &replaceTxt); void replaceChecked(QTreeWidget *tree, const QRegularExpression ®exp, const QString &replace); KTextEditor::Document *findNamed(const QString &name); public Q_SLOTS: void cancelReplace(); private Q_SLOTS: void doReplaceNextMatch(); Q_SIGNALS: void replaceStatus(const QUrl &url, int replacedInFile, int matchesInFile); void replaceDone(); private: void updateTreeViewItems(QTreeWidgetItem *fileItem); KTextEditor::Application *m_manager = nullptr; QTreeWidget *m_tree = nullptr; int m_rootIndex = -1; int m_childStartIndex = -1; QVector m_currentMatches; QVector m_currentReplaced; QRegularExpression m_regExp; QString m_replaceText; bool m_cancelReplace; QElapsedTimer m_progressTime; }; #endif diff --git a/addons/search/search_open_files.h b/addons/search/search_open_files.h index f85c6cdf4..865eaf443 100644 --- a/addons/search/search_open_files.h +++ b/addons/search/search_open_files.h @@ -1,68 +1,68 @@ /* Kate search plugin * * Copyright (C) 2011-2013 by KÃ¥re Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifndef _SEARCH_OPEN_FILES_H_ #define _SEARCH_OPEN_FILES_H_ +#include #include #include -#include #include class SearchOpenFiles : public QObject { Q_OBJECT public: SearchOpenFiles(QObject *parent = nullptr); void startSearch(const QList &list, const QRegularExpression ®exp); bool searching(); public Q_SLOTS: void cancelSearch(); /// return 0 on success or a line number where we stopped. int searchOpenFile(KTextEditor::Document *doc, const QRegularExpression ®Exp, int startLine); private Q_SLOTS: void doSearchNextFile(int startLine); private: int searchSingleLineRegExp(KTextEditor::Document *doc, const QRegularExpression ®Exp, int startLine); int searchMultiLineRegExp(KTextEditor::Document *doc, const QRegularExpression ®Exp, int startLine); Q_SIGNALS: void searchNextFile(int startLine); void matchFound(const QString &url, const QString &fileName, const QString &lineContent, int matchLen, int line, int column, int endLine, int endColumn); void searchDone(); void searching(const QString &file); private: QList m_docList; int m_nextIndex = -1; QRegularExpression m_regExp; bool m_cancelSearch = true; QString m_fullDoc; QVector m_lineStart; QElapsedTimer m_statusTime; }; #endif diff --git a/addons/sessionapplet/katesessionsjob.cpp b/addons/sessionapplet/katesessionsjob.cpp index ab36f45d0..4fb7fa29c 100644 --- a/addons/sessionapplet/katesessionsjob.cpp +++ b/addons/sessionapplet/katesessionsjob.cpp @@ -1,84 +1,84 @@ /******************************************************************** This file is part of the KDE project. Copyright (C) 2014 Joseph Wenninger based on clipboard engine: Copyright (C) 2014 Martin Gräßlin partly based on code: Copyright (C) 2008 by Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "katesessionsjob.h" #include "katesessionsengine.h" -#include #include +#include #include static const QString s_clipboardSourceName = QStringLiteral("katesessions"); KateSessionsJob::KateSessionsJob(KateSessionsEngine *engine, const QString &destination, const QString &operation, const QVariantMap ¶meters, QObject *parent) : Plasma::ServiceJob(destination, operation, parameters, parent) , m_engine(engine) { } void KateSessionsJob::start() { qDebug() << "Job started:" << operationName() << " " << destination(); const QString operation = operationName(); // first check for operations not needing an item if (operation == QLatin1String("newSession")) { QString sessionName = parameters().value(QStringLiteral("sessionName")).toString(); if (sessionName.isEmpty()) { setResult(false); emitResult(); return; } // CHECK IF SESSION EXISTS QStringList args; args << QStringLiteral("-n") << QStringLiteral("--start") << sessionName; KToolInvocation::kdeinitExec(QStringLiteral("kate"), args); setResult(true); emitResult(); return; } else if (operation == QLatin1String("invoke")) { QString dest = destination(); QStringList args; if (dest == QLatin1String("_kate_noargs")) { // do nothing } else if (dest == QLatin1String("_kate_anon_newsession")) { args << QStringLiteral("--startanon"); } else if (dest == QLatin1String("_kate_newsession")) { args << QStringLiteral("--startanon"); qDebug() << "This should not be reached"; } else { dest.chop(12); // .katesession args << QStringLiteral("-n") << QStringLiteral("--start") << QUrl::fromPercentEncoding(dest.toLatin1()); // args <<"-n"<< "--start"< * * Copyright (C) 2008 by Montel Laurent * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * ***************************************************************************/ #include "katesessionsmodel.h" -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include #include -#include +#include +#include #include +#include +#include +#include +#include #include -#include +#include +#include #include -#include +#include +#include +#include bool katesessions_compare_sessions(const QString &s1, const QString &s2) { return QString::localeAwareCompare(s1, s2) == -1; } KateSessionsModel::KateSessionsModel(QObject *parent) : QStandardItemModel(parent) /*, m_config(0)*/ { KDirWatch *dirwatch = new KDirWatch(this); m_sessionsDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kate/sessions"); dirwatch->addDir(m_sessionsDir); connect(dirwatch, &KDirWatch::dirty, this, &KateSessionsModel::slotUpdateSessionMenu); slotUpdateSessionMenu(); } KateSessionsModel::~KateSessionsModel() { } void KateSessionsModel::slotUpdateSessionMenu() { clear(); m_sessions.clear(); m_fullList.clear(); initSessionFiles(); } void KateSessionsModel::initSessionFiles() { QStandardItem *item = new QStandardItem(); item->setData(i18n("Start Kate (no arguments)"), Qt::DisplayRole); item->setData(QIcon::fromTheme(QStringLiteral("kate")), Qt::DecorationRole); item->setData(QStringLiteral("_kate_noargs"), Uuid); item->setData(0, TypeRole); m_fullList << item->data(Qt::DisplayRole).toString(); appendRow(item); item = new QStandardItem(); item->setData(i18n("New Kate Session"), Qt::DisplayRole); item->setData(QIcon::fromTheme(QStringLiteral("document-new")), Qt::DecorationRole); qDebug() << QIcon::fromTheme(QStringLiteral("document-new")); item->setData(QStringLiteral("_kate_newsession"), Uuid); item->setData(1, TypeRole); m_fullList << item->data(Qt::DisplayRole).toString(); appendRow(item); item = new QStandardItem(); item->setData(i18n("New Anonymous Session"), Qt::DisplayRole); item->setData(QStringLiteral("_kate_anon_newsession"), Uuid); item->setData(0, TypeRole); item->setData(QIcon::fromTheme(QStringLiteral("document-new")), Qt::DecorationRole); m_fullList << item->data(Qt::DisplayRole).toString(); appendRow(item); QDir dir(m_sessionsDir, QStringLiteral("*.katesession")); for (unsigned int i = 0; i < dir.count(); ++i) { QString name = dir[i]; name.chop(12); // .katesession m_sessions << QUrl::fromPercentEncoding(name.toLatin1()); } std::sort(m_sessions.begin(), m_sessions.end(), katesessions_compare_sessions); QLatin1String ext(".katesession"); for (QStringList::ConstIterator it = m_sessions.constBegin(); it != m_sessions.constEnd(); ++it) { m_fullList << *it; item = new QStandardItem(); item->setData(*it, Qt::DisplayRole); item->setData(QString((*it) + ext), Uuid); item->setData(QIcon::fromTheme(QStringLiteral("document-open")), Qt::DecorationRole); item->setData(2, TypeRole); appendRow(item); } } QHash KateSessionsModel::roleNames() const { QHash hash; hash.insert(Qt::DisplayRole, QByteArrayLiteral("DisplayRole")); hash.insert(Qt::DecorationRole, QByteArrayLiteral("DecorationRole")); hash.insert(Qt::UserRole + 3, QByteArrayLiteral("UuidRole")); hash.insert(Qt::UserRole + 4, QByteArrayLiteral("TypeRole")); return hash; } diff --git a/addons/snippets/editrepository.cpp b/addons/snippets/editrepository.cpp index 1b4ccf459..56b4724dd 100644 --- a/addons/snippets/editrepository.cpp +++ b/addons/snippets/editrepository.cpp @@ -1,143 +1,143 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2010 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * Copyright (C) 2014 Sven Brauch * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "editrepository.h" #include "snippetrepository.h" #include "snippetstore.h" -#include #include +#include #include #include #include -#include #include +#include EditRepository::EditRepository(SnippetRepository *repository, QWidget *parent) : QDialog(parent) , Ui::EditRepositoryBase() , m_repo(repository) { setupUi(this); connect(repoNameEdit, &KLineEdit::textEdited, this, &EditRepository::validate); connect(this, &QDialog::accepted, this, &EditRepository::save); auto ok = buttonBox->button(QDialogButtonBox::Ok); KGuiItem::assign(ok, KStandardGuiItem::ok()); connect(ok, &QPushButton::clicked, this, &EditRepository::accept); auto cancel = buttonBox->button(QDialogButtonBox::Cancel); KGuiItem::assign(cancel, KStandardGuiItem::cancel()); connect(cancel, &QPushButton::clicked, this, &EditRepository::reject); // fill list of available modes QSharedPointer document(KTextEditor::Editor::instance()->createDocument(nullptr)); repoFileTypesList->addItems(document->highlightingModes()); repoFileTypesList->sortItems(); repoFileTypesList->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(repoFileTypesList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EditRepository::updateFileTypes); // add default licenses repoLicenseEdit->addItems(QStringList() << QStringLiteral("BSD") << QStringLiteral("Artistic") << QStringLiteral("LGPL v2+") << QStringLiteral("LGPL v3+")); repoLicenseEdit->setEditable(true); // if we edit a repo, add all existing data if (m_repo) { repoNameEdit->setText(m_repo->text()); repoAuthorsEdit->setText(m_repo->authors()); repoNamespaceEdit->setText(m_repo->completionNamespace()); if (!m_repo->license().isEmpty()) { int index = repoLicenseEdit->findText(m_repo->license()); if (index == -1) { repoLicenseEdit->addItem(m_repo->license()); repoLicenseEdit->model()->sort(0); index = repoLicenseEdit->findText(m_repo->license()); } repoLicenseEdit->setCurrentIndex(index); } const auto fileTypes = m_repo->fileTypes(); for (const QString &type : fileTypes) { const auto items = repoFileTypesList->findItems(type, Qt::MatchExactly); for (QListWidgetItem *item : items) { item->setSelected(true); } } setWindowTitle(i18n("Edit Snippet Repository %1", m_repo->text())); } else { setWindowTitle(i18n("Create New Snippet Repository")); KUser user; repoAuthorsEdit->setText(user.property(KUser::FullName).toString()); } validate(); updateFileTypes(); repoNameEdit->setFocus(); } void EditRepository::validate() { bool valid = !repoNameEdit->text().isEmpty() && !repoNameEdit->text().contains(QLatin1Char('/')); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); } void EditRepository::save() { Q_ASSERT(!repoNameEdit->text().isEmpty()); if (!m_repo) { // save as new repo m_repo = SnippetRepository::createRepoFromName(repoNameEdit->text()); } m_repo->setText(repoNameEdit->text()); m_repo->setAuthors(repoAuthorsEdit->text()); m_repo->setLicense(repoLicenseEdit->currentText()); m_repo->setCompletionNamespace(repoNamespaceEdit->text()); QStringList types; const auto selectedItems = repoFileTypesList->selectedItems(); for (QListWidgetItem *item : selectedItems) { types << item->text(); } m_repo->setFileTypes(types); m_repo->save(); setWindowTitle(i18n("Edit Snippet Repository %1", m_repo->text())); } void EditRepository::updateFileTypes() { QStringList types; const auto selectedItems = repoFileTypesList->selectedItems(); for (QListWidgetItem *item : selectedItems) { types << item->text(); } if (types.isEmpty()) { repoFileTypesListLabel->setText(i18n("leave empty for general purpose snippets")); } else { repoFileTypesListLabel->setText(types.join(QLatin1String(", "))); } } diff --git a/addons/snippets/editsnippet.cpp b/addons/snippets/editsnippet.cpp index 179a305cc..f071c4f87 100644 --- a/addons/snippets/editsnippet.cpp +++ b/addons/snippets/editsnippet.cpp @@ -1,204 +1,204 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2010 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "editsnippet.h" #include "ui_editsnippet.h" +#include "snippet.h" #include "snippetrepository.h" #include "snippetstore.h" -#include "snippet.h" #include -#include -#include #include #include +#include +#include -#include +#include #include +#include #include -#include -#include #include +#include #include KTextEditor::View *createView(QWidget *tabWidget) { auto document = KTextEditor::Editor::instance()->createDocument(tabWidget); auto view = document->createView(tabWidget); view->action("file_save")->setEnabled(false); tabWidget->layout()->addWidget(view); view->setStatusBarEnabled(false); return view; } EditSnippet::EditSnippet(SnippetRepository *repository, Snippet *snippet, QWidget *parent) : QDialog(parent) , m_ui(new Ui::EditSnippetBase) , m_repo(repository) , m_snippet(snippet) , m_topBoxModified(false) { Q_ASSERT(m_repo); m_ui->setupUi(this); connect(this, &QDialog::accepted, this, &EditSnippet::save); m_okButton = m_ui->buttons->button(QDialogButtonBox::Ok); KGuiItem::assign(m_okButton, KStandardGuiItem::ok()); m_ui->buttons->addButton(m_okButton, QDialogButtonBox::AcceptRole); connect(m_okButton, &QPushButton::clicked, this, &QDialog::accept); auto cancelButton = m_ui->buttons->button(QDialogButtonBox::Cancel); KGuiItem::assign(cancelButton, KStandardGuiItem::cancel()); m_ui->buttons->addButton(cancelButton, QDialogButtonBox::RejectRole); connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); m_snippetView = createView(m_ui->snippetTab); if (!m_repo->fileTypes().isEmpty()) { m_snippetView->document()->setMode(m_repo->fileTypes().first()); } m_scriptsView = createView(m_ui->scriptTab); m_scriptsView->document()->setMode(QStringLiteral("JavaScript")); m_scriptsView->document()->setText(m_repo->script()); m_scriptsView->document()->setModified(false); // view for testing the snippet m_testView = createView(m_ui->testWidget); // splitter default size ratio m_ui->splitter->setSizes(QList() << 400 << 150); connect(m_ui->dotest_button, &QPushButton::clicked, this, &EditSnippet::test); // modified notification stuff connect(m_ui->snippetNameEdit, &QLineEdit::textEdited, this, &EditSnippet::topBoxModified); connect(m_ui->snippetNameEdit, &QLineEdit::textEdited, this, &EditSnippet::validate); connect(m_ui->snippetShortcut, &KKeySequenceWidget::keySequenceChanged, this, &EditSnippet::topBoxModified); connect(m_snippetView->document(), &KTextEditor::Document::textChanged, this, &EditSnippet::validate); auto showHelp = [](const QString &text) { QWhatsThis::showText(QCursor::pos(), text); }; connect(m_ui->snippetLabel, &QLabel::linkActivated, showHelp); connect(m_ui->scriptLabel, &QLabel::linkActivated, showHelp); // if we edit a snippet, add all existing data if (m_snippet) { setWindowTitle(i18n("Edit Snippet %1 in %2", m_snippet->text(), m_repo->text())); m_snippetView->document()->setText(m_snippet->snippet()); m_ui->snippetNameEdit->setText(m_snippet->text()); m_ui->snippetShortcut->setKeySequence(m_snippet->action()->shortcut()); // unset modified flags m_snippetView->document()->setModified(false); m_topBoxModified = false; } else { setWindowTitle(i18n("Create New Snippet in Repository %1", m_repo->text())); } m_ui->messageWidget->hide(); validate(); m_ui->snippetNameEdit->setFocus(); setTabOrder(m_ui->snippetNameEdit, m_snippetView); QSize initSize = sizeHint(); initSize.setHeight(initSize.height() + 200); } void EditSnippet::test() { m_testView->document()->clear(); m_testView->insertTemplate(KTextEditor::Cursor(0, 0), m_snippetView->document()->text(), m_scriptsView->document()->text()); m_testView->setFocus(); } EditSnippet::~EditSnippet() { delete m_ui; } void EditSnippet::setSnippetText(const QString &text) { m_snippetView->document()->setText(text); validate(); } void EditSnippet::validate() { const QString &name = m_ui->snippetNameEdit->text(); bool valid = !name.isEmpty() && !m_snippetView->document()->isEmpty(); // make sure the snippetname includes no spaces if (name.contains(QLatin1Char(' ')) || name.contains(QLatin1Char('\t'))) { m_ui->messageWidget->setText(i18n("Snippet name cannot contain spaces")); m_ui->messageWidget->animatedShow(); valid = false; } else { // hide message widget if snippet does not include spaces m_ui->messageWidget->animatedHide(); } if (valid) { m_ui->messageWidget->hide(); } m_okButton->setEnabled(valid); } void EditSnippet::save() { Q_ASSERT(!m_ui->snippetNameEdit->text().isEmpty()); if (!m_snippet) { // save as new snippet m_snippet = new Snippet(); m_snippet->action(); // ensure that the snippet's QAction is created before it is added to a widget by the rowsInserted() signal m_repo->appendRow(m_snippet); } m_snippet->setSnippet(m_snippetView->document()->text()); m_snippetView->document()->setModified(false); m_snippet->setText(m_ui->snippetNameEdit->text()); m_snippet->action()->setShortcut(m_ui->snippetShortcut->keySequence()); m_repo->setScript(m_scriptsView->document()->text()); m_scriptsView->document()->setModified(false); m_topBoxModified = false; m_repo->save(); setWindowTitle(i18n("Edit Snippet %1 in %2", m_snippet->text(), m_repo->text())); } void EditSnippet::reject() { if (m_topBoxModified || m_snippetView->document()->isModified() || m_scriptsView->document()->isModified()) { int ret = KMessageBox::warningContinueCancel(qApp->activeWindow(), i18n("The snippet contains unsaved changes. Do you want to continue and lose all changes?"), i18n("Warning - Unsaved Changes")); if (ret == KMessageBox::Cancel) { return; } } QDialog::reject(); } void EditSnippet::topBoxModified() { m_topBoxModified = true; } diff --git a/addons/snippets/katesnippetglobal.cpp b/addons/snippets/katesnippetglobal.cpp index 17c7b8fbb..210db1c64 100644 --- a/addons/snippets/katesnippetglobal.cpp +++ b/addons/snippets/katesnippetglobal.cpp @@ -1,125 +1,125 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "katesnippetglobal.h" -#include "snippetcompletionmodel.h" -#include "snippetstore.h" +#include "editsnippet.h" #include "snippet.h" -#include "snippetrepository.h" #include "snippetcompletionitem.h" -#include "editsnippet.h" +#include "snippetcompletionmodel.h" +#include "snippetrepository.h" +#include "snippetstore.h" -#include #include +#include +#include #include -#include +#include +#include #include #include -#include #include -#include -#include +#include #include #include KateSnippetGlobal *KateSnippetGlobal::s_self = nullptr; KateSnippetGlobal::KateSnippetGlobal(QObject *parent, const QVariantList &) : QObject(parent) { s_self = this; SnippetStore::init(this); m_model.reset(new SnippetCompletionModel); } KateSnippetGlobal::~KateSnippetGlobal() { delete SnippetStore::self(); s_self = nullptr; } void KateSnippetGlobal::insertSnippet(Snippet *snippet) { // query active view, always prefer that! KTextEditor::View *view = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView(); // fallback to stuff set for dialog if (!view) view = m_activeViewForDialog; // no view => nothing to do if (!view) return; // try to insert snippet SnippetCompletionItem item(snippet, static_cast(snippet->parent())); item.execute(view, KTextEditor::Range(view->cursorPosition(), view->cursorPosition())); // set focus to view view->setFocus(); } void KateSnippetGlobal::insertSnippetFromActionData() { QAction *action = dynamic_cast(sender()); Q_ASSERT(action); Snippet *snippet = action->data().value(); Q_ASSERT(snippet); insertSnippet(snippet); } void KateSnippetGlobal::createSnippet(KTextEditor::View *view) { // no active view, bad if (!view) return; // get mode QString mode = view->document()->highlightingModeAt(view->selectionRange().isValid() ? view->selectionRange().start() : view->cursorPosition()); if (mode.isEmpty()) mode = view->document()->mode(); // try to look for a fitting repo SnippetRepository *match = nullptr; for (int i = 0; i < SnippetStore::self()->rowCount(); ++i) { SnippetRepository *repo = dynamic_cast(SnippetStore::self()->item(i)); if (repo && repo->fileTypes().count() == 1 && repo->fileTypes().first() == mode) { match = repo; break; } } bool created = !match; if (created) { match = SnippetRepository::createRepoFromName(i18nc("Autogenerated repository name for a programming language", "%1 snippets", mode)); match->setFileTypes(QStringList() << mode); } EditSnippet dlg(match, nullptr, view); dlg.setSnippetText(view->selectionText()); int status = dlg.exec(); if (created && status != QDialog::Accepted) { // cleanup match->remove(); } } diff --git a/addons/snippets/katesnippetglobal.h b/addons/snippets/katesnippetglobal.h index bec03a6fa..5a650c38d 100644 --- a/addons/snippets/katesnippetglobal.h +++ b/addons/snippets/katesnippetglobal.h @@ -1,82 +1,82 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef __KATE_SNIPPET_GLOBAL_H__ #define __KATE_SNIPPET_GLOBAL_H__ -#include #include +#include #include class SnippetCompletionModel; class Snippet; /** * This is the main class of KDevelop's snippet plugin. * @author Robert Gruber */ class KateSnippetGlobal : public QObject { Q_OBJECT public: KateSnippetGlobal(QObject *parent, const QVariantList &args = QVariantList()); ~KateSnippetGlobal() override; /** * Inserts the given @p snippet into the currently active view. * If the current active view is not inherited from KTextEditor::View * nothing will happen. */ void insertSnippet(Snippet *snippet); static KateSnippetGlobal *self() { return s_self; } /** * Code completion model. * @return code completion model for snippets */ SnippetCompletionModel *completionModel() { return m_model.data(); } public Q_SLOTS: /** * Create snippet for given view, e.g. by using the selection * @param view view to create snippet for */ void createSnippet(KTextEditor::View *view); void insertSnippetFromActionData(); private: static KateSnippetGlobal *s_self; QScopedPointer m_model; QPointer m_activeViewForDialog; }; #endif diff --git a/addons/snippets/katesnippets.cpp b/addons/snippets/katesnippets.cpp index 330ad74ca..f047c32f3 100644 --- a/addons/snippets/katesnippets.cpp +++ b/addons/snippets/katesnippets.cpp @@ -1,132 +1,132 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "katesnippets.h" -#include "snippetcompletionmodel.h" #include "katesnippetglobal.h" +#include "snippetcompletionmodel.h" #include "snippetview.h" #include #include #include -#include #include #include +#include #include K_PLUGIN_FACTORY_WITH_JSON(KateSnippetsPluginFactory, "katesnippetsplugin.json", registerPlugin();) KateSnippetsPlugin::KateSnippetsPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) , m_snippetGlobal(new KateSnippetGlobal(this)) { } KateSnippetsPlugin::~KateSnippetsPlugin() { } QObject *KateSnippetsPlugin::createView(KTextEditor::MainWindow *mainWindow) { KateSnippetsPluginView *view = new KateSnippetsPluginView(this, mainWindow); return view; } KateSnippetsPluginView::KateSnippetsPluginView(KateSnippetsPlugin *plugin, KTextEditor::MainWindow *mainWindow) : QObject(mainWindow) , m_plugin(plugin) , m_mainWindow(mainWindow) , m_toolView(nullptr) , m_snippets(nullptr) { KXMLGUIClient::setComponentName(QStringLiteral("katesnippets"), i18n("Snippets tool view")); setXMLFile(QStringLiteral("ui.rc")); // Toolview for snippets m_toolView = mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katesnippetsplugin"), KTextEditor::MainWindow::Right, QIcon::fromTheme(QStringLiteral("document-new")), i18n("Snippets")); // add snippets widget m_snippets = new SnippetView(KateSnippetGlobal::self(), mainWindow, m_toolView.data()); m_toolView->layout()->addWidget(m_snippets); m_snippets->setupActionsForWindow(mainWindow->window()); m_toolView->addActions(m_snippets->actions()); // create actions QAction *a = actionCollection()->addAction(QStringLiteral("tools_create_snippet")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); a->setText(i18n("Create Snippet")); connect(a, &QAction::triggered, this, &KateSnippetsPluginView::createSnippet); connect(mainWindow, &KTextEditor::MainWindow::viewCreated, this, &KateSnippetsPluginView::slotViewCreated); /** * connect for all already existing views */ const auto views = mainWindow->views(); for (KTextEditor::View *view : views) { slotViewCreated(view); } // register if factory around if (auto factory = m_mainWindow->guiFactory()) { factory->addClient(this); } } KateSnippetsPluginView::~KateSnippetsPluginView() { // cleanup for all views for (auto view : qAsConst(m_textViews)) { if (!view) { continue; } auto iface = qobject_cast(view); iface->unregisterCompletionModel(KateSnippetGlobal::self()->completionModel()); } // unregister if factory around if (auto factory = m_mainWindow->guiFactory()) { factory->removeClient(this); } if (m_toolView) { delete m_toolView; } } void KateSnippetsPluginView::slotViewCreated(KTextEditor::View *view) { m_textViews.append(QPointer(view)); // add snippet completion auto model = KateSnippetGlobal::self()->completionModel(); auto iface = qobject_cast(view); iface->unregisterCompletionModel(model); iface->registerCompletionModel(model); } void KateSnippetsPluginView::createSnippet() { KateSnippetGlobal::self()->createSnippet(m_mainWindow->activeView()); } #include "katesnippets.moc" diff --git a/addons/snippets/katesnippets.h b/addons/snippets/katesnippets.h index 774665bd9..80b4977bf 100644 --- a/addons/snippets/katesnippets.h +++ b/addons/snippets/katesnippets.h @@ -1,88 +1,88 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef __KATE_SNIPPETS_H__ #define __KATE_SNIPPETS_H__ -#include -#include #include #include +#include +#include #include "katesnippetglobal.h" class SnippetView; class KateSnippetsPluginView; class KateSnippetsPlugin : public KTextEditor::Plugin { Q_OBJECT friend class KateSnippetsPluginView; public: explicit KateSnippetsPlugin(QObject *parent = nullptr, const QList & = QList()); ~KateSnippetsPlugin() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; private: KateSnippetGlobal *m_snippetGlobal; }; class KateSnippetsPluginView : public QObject, public KXMLGUIClient { Q_OBJECT public: /** * Constructor. */ KateSnippetsPluginView(KateSnippetsPlugin *plugin, KTextEditor::MainWindow *mainWindow); /** * Virtual destructor. */ ~KateSnippetsPluginView() override; void readConfig(); private Q_SLOTS: /** * New view got created, we need to update our connections * @param view new created view */ void slotViewCreated(KTextEditor::View *view); void createSnippet(); private: KateSnippetsPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; QPointer m_toolView; SnippetView *m_snippets; /** * remember for which text views we might need to cleanup stuff */ QVector> m_textViews; }; #endif diff --git a/addons/snippets/snippet.cpp b/addons/snippets/snippet.cpp index c1d22274f..37f9bd19b 100644 --- a/addons/snippets/snippet.cpp +++ b/addons/snippets/snippet.cpp @@ -1,93 +1,93 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2010 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "snippet.h" #include "katesnippetglobal.h" -#include "ktexteditor/editor.h" #include "ktexteditor/application.h" +#include "ktexteditor/editor.h" #include "ktexteditor/mainwindow.h" -#include -#include #include +#include +#include #include Snippet::Snippet() : QStandardItem(i18n("")) { setIcon(QIcon::fromTheme(QStringLiteral("text-plain"))); } Snippet::~Snippet() { delete m_action; } QString Snippet::snippet() const { return m_snippet; } void Snippet::setSnippet(const QString &snippet) { m_snippet = snippet; } void Snippet::registerActionForView(QWidget *view) { if (view->actions().contains(m_action)) { return; } view->addAction(m_action); } QAction *Snippet::action() { /// TODO: this is quite ugly, or is it? if someone knows how to do it better, please refactor if (!m_action) { static int actionCount = 0; actionCount += 1; m_action = new QAction(QStringLiteral("insertSnippet%1").arg(actionCount), KateSnippetGlobal::self()); m_action->setData(QVariant::fromValue(this)); KateSnippetGlobal::self()->connect(m_action, &QAction::triggered, KateSnippetGlobal::self(), &KateSnippetGlobal::insertSnippetFromActionData); } m_action->setText(i18n("insert snippet %1", text())); return m_action; } QVariant Snippet::data(int role) const { if (role == Qt::ToolTipRole) { return m_snippet; } else if ((role == Qt::ForegroundRole || role == Qt::BackgroundRole) && parent()->checkState() != Qt::Checked) { /// TODO: make the selected items also "disalbed" so the toggle action is seen directly KColorScheme scheme(QPalette::Disabled, KColorScheme::View); if (role == Qt::ForegroundRole) { return scheme.foreground(KColorScheme::NormalText).color(); } else { return scheme.background(KColorScheme::NormalBackground).color(); } } return QStandardItem::data(role); } diff --git a/addons/snippets/snippetcompletionitem.cpp b/addons/snippets/snippetcompletionitem.cpp index 4cc5d6f2c..b907772a9 100644 --- a/addons/snippets/snippetcompletionitem.cpp +++ b/addons/snippets/snippetcompletionitem.cpp @@ -1,98 +1,98 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2009 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "snippetcompletionitem.h" -#include #include -#include #include +#include +#include -#include #include #include +#include #include "snippet.h" #include "snippetrepository.h" SnippetCompletionItem::SnippetCompletionItem(Snippet *snippet, SnippetRepository *repo) : m_name(snippet->text()) , m_snippet(snippet->snippet()) , m_repo(repo) { Q_ASSERT(m_repo); const auto &namespace_ = repo->completionNamespace(); if (!namespace_.isEmpty()) { m_name.prepend(QLatin1String(":")); m_name.prepend(repo->completionNamespace()); } } SnippetCompletionItem::~SnippetCompletionItem() { } QVariant SnippetCompletionItem::data(const QModelIndex &index, int role, const KTextEditor::CodeCompletionModel *model) const { // as long as the snippet completion model is not a kdevelop code completion model, // model will usually be 0. hence don't use it. Q_UNUSED(model); switch (role) { case Qt::DisplayRole: switch (index.column()) { case KTextEditor::CodeCompletionModel::Name: return m_name; case KTextEditor::CodeCompletionModel::Prefix: return QString(); case KTextEditor::CodeCompletionModel::Postfix: return QString(); case KTextEditor::CodeCompletionModel::Arguments: return QString(); } break; case KTextEditor::CodeCompletionModel::IsExpandable: return QVariant(true); case KTextEditor::CodeCompletionModel::ExpandingWidget: { QTextEdit *textEdit = new QTextEdit(); /// TODO: somehow make it possible to scroll like in other expanding widgets // don't make it too large, only show a few lines textEdit->resize(textEdit->width(), 100); textEdit->setPlainText(m_snippet); textEdit->setReadOnly(true); textEdit->setLineWrapMode(QTextEdit::NoWrap); QVariant v; v.setValue(textEdit); return v; } } return QVariant(); } void SnippetCompletionItem::execute(KTextEditor::View *view, const KTextEditor::Range &word) { // insert snippet content view->insertTemplate(view->cursorPosition(), m_snippet, m_repo->script()); view->document()->removeText(word); } diff --git a/addons/snippets/snippetcompletionmodel.cpp b/addons/snippets/snippetcompletionmodel.cpp index 9426d4685..548c05e7e 100644 --- a/addons/snippets/snippetcompletionmodel.cpp +++ b/addons/snippets/snippetcompletionmodel.cpp @@ -1,187 +1,187 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2008 Andreas Pakulat * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "snippetcompletionmodel.h" #include #include -#include "snippetstore.h" -#include "snippetrepository.h" #include "snippet.h" #include "snippetcompletionitem.h" +#include "snippetrepository.h" +#include "snippetstore.h" #include SnippetCompletionModel::SnippetCompletionModel() : KTextEditor::CodeCompletionModel(nullptr) { setHasGroups(false); } SnippetCompletionModel::~SnippetCompletionModel() { qDeleteAll(m_snippets); m_snippets.clear(); } QVariant SnippetCompletionModel::data(const QModelIndex &idx, int role) const { if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) return 11000; // grouping of snippets if (!idx.parent().isValid()) { if (role == Qt::DisplayRole) { return i18n("Snippets"); } if (role == KTextEditor::CodeCompletionModel::GroupRole) { return Qt::DisplayRole; } return QVariant(); } // snippets if (!idx.isValid() || idx.row() < 0 || idx.row() >= m_snippets.count()) { return QVariant(); } else { return m_snippets.at(idx.row())->data(idx, role, nullptr); } } void SnippetCompletionModel::executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const { if (index.parent().isValid()) { m_snippets[index.row()]->execute(view, word); } } void SnippetCompletionModel::completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType) { Q_UNUSED(range); Q_UNUSED(invocationType); initData(view); } void SnippetCompletionModel::initData(KTextEditor::View *view) { QString mode = view->document()->highlightingModeAt(view->cursorPosition()); if (mode.isEmpty()) { mode = view->document()->highlightingMode(); } beginResetModel(); qDeleteAll(m_snippets); m_snippets.clear(); SnippetStore *store = SnippetStore::self(); for (int i = 0; i < store->rowCount(); i++) { if (store->item(i, 0)->checkState() != Qt::Checked) { continue; } SnippetRepository *repo = dynamic_cast(store->item(i, 0)); if (repo && (repo->fileTypes().isEmpty() || repo->fileTypes().contains(mode))) { for (int j = 0; j < repo->rowCount(); ++j) { if (Snippet *snippet = dynamic_cast(repo->child(j))) { m_snippets << new SnippetCompletionItem(snippet, repo); } } } } endResetModel(); } QModelIndex SnippetCompletionModel::parent(const QModelIndex &index) const { if (index.internalId()) { return createIndex(0, 0, quintptr(0)); } else { return QModelIndex(); } } QModelIndex SnippetCompletionModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { if (row == 0) { return createIndex(row, column, quintptr(0)); // header index } else { return QModelIndex(); } } else if (parent.parent().isValid()) { // we only have header and children, no subheaders return QModelIndex(); } if (row < 0 || row >= m_snippets.count() || column < 0 || column >= ColumnCount) { return QModelIndex(); } return createIndex(row, column, 1); // normal item index } int SnippetCompletionModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid() && !m_snippets.isEmpty()) { return 1; // one toplevel node (group header) } else if (parent.parent().isValid()) { return 0; // we don't have sub children } else { return m_snippets.count(); // only the children } } KTextEditor::Range SnippetCompletionModel::completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position) { const QString &line = view->document()->line(position.line()); KTextEditor::Range range(position, position); // include everything non-space before for (int i = position.column() - 1; i >= 0; --i) { if (line.at(i).isSpace()) { break; } else { range.setStart(KTextEditor::Cursor(range.start().line(), i)); } } // include everything non-space after for (int i = position.column() + 1; i < line.length(); ++i) { if (line.at(i).isSpace()) { break; } else { range.setEnd(KTextEditor::Cursor(range.end().line(), i)); } } return range; } bool SnippetCompletionModel::shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) { if (view->cursorPosition() < range.start() || view->cursorPosition() > range.end()) { return true; // Always abort when the completion-range has been left } for (const auto token : currentCompletion) { if (token.isSpace()) { return true; } } // else it's valid return false; } diff --git a/addons/snippets/snippetrepository.cpp b/addons/snippets/snippetrepository.cpp index 0774f6869..1b4bf1042 100644 --- a/addons/snippets/snippetrepository.cpp +++ b/addons/snippets/snippetrepository.cpp @@ -1,404 +1,404 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2010 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "snippetrepository.h" #include "snippet.h" -#include +#include #include #include -#include +#include #include #include -#include #include +#include #include #include #include #include #include #include "snippetstore.h" static const QString defaultScript = QStringLiteral( "\ function fileName() { return document.fileName(); }\n\ function fileUrl() { return document.url(); }\n\ function encoding() { return document.encoding(); }\n\ function selection() { return view.selectedText(); }\n\ function year() { return new Date().getFullYear(); }\n\ function upper(x) { return x.toUpperCase(); }\n\ function lower(x) { return x.toLowerCase(); }\n"); SnippetRepository::SnippetRepository(const QString &file) : QStandardItem(i18n("")) , m_file(file) , m_script(defaultScript) { setIcon(QIcon::fromTheme(QStringLiteral("folder"))); const auto &config = SnippetStore::self()->getConfig(); bool activated = config.readEntry("enabledRepositories", QStringList()).contains(file); setCheckState(activated ? Qt::Checked : Qt::Unchecked); if (QFile::exists(file)) { // Tell the new repository to load it's snippets QTimer::singleShot(0, this, &SnippetRepository::slotParseFile); } qDebug() << "created new snippet repo" << file << this; } SnippetRepository::~SnippetRepository() { // remove all our children from both the model and our internal data structures removeRows(0, rowCount()); } QDir SnippetRepository::dataPath() { auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); const auto &subdir = QLatin1String("ktexteditor_snippets/data/"); bool success = dir.mkpath(dir.absoluteFilePath(subdir)); Q_ASSERT(success); dir.setPath(dir.path() + QLatin1String("/") + subdir); return dir; } SnippetRepository *SnippetRepository::createRepoFromName(const QString &name) { QString cleanName = name; cleanName.replace(QLatin1Char('/'), QLatin1Char('-')); const auto &dir = dataPath(); const auto &path = dir.absoluteFilePath(cleanName + QLatin1String(".xml")); qDebug() << "repo path:" << path << cleanName; SnippetRepository *repo = new SnippetRepository(path); repo->setText(name); repo->setCheckState(Qt::Checked); KUser user; repo->setAuthors(user.property(KUser::FullName).toString()); SnippetStore::self()->appendRow(repo); return repo; } const QString &SnippetRepository::file() const { return m_file; } QString SnippetRepository::authors() const { return m_authors; } void SnippetRepository::setAuthors(const QString &authors) { m_authors = authors; } QStringList SnippetRepository::fileTypes() const { return m_filetypes; } void SnippetRepository::setFileTypes(const QStringList &filetypes) { if (filetypes.contains(QLatin1String("*"))) { m_filetypes.clear(); } else { m_filetypes = filetypes; } } QString SnippetRepository::license() const { return m_license; } void SnippetRepository::setLicense(const QString &license) { m_license = license; } QString SnippetRepository::completionNamespace() const { return m_namespace; } void SnippetRepository::setCompletionNamespace(const QString &completionNamespace) { m_namespace = completionNamespace; } QString SnippetRepository::script() const { return m_script; } void SnippetRepository::setScript(const QString &script) { m_script = script; } void SnippetRepository::remove() { QFile::remove(m_file); setCheckState(Qt::Unchecked); model()->invisibleRootItem()->removeRow(row()); } /// copied code from snippets_tng/lib/completionmodel.cpp ///@copyright 2009 Joseph Wenninger static void addAndCreateElement(QDomDocument &doc, QDomElement &item, const QString &name, const QString &content) { QDomElement element = doc.createElement(name); element.appendChild(doc.createTextNode(content)); item.appendChild(element); } void SnippetRepository::save() { qDebug() << "*** called"; /// based on the code from snippets_tng/lib/completionmodel.cpp ///@copyright 2009 Joseph Wenninger /* prefix test1 postfix (param1, param2) This is a test testtemplate This is a test ${WHAT} template */ QDomDocument doc; QDomElement root = doc.createElement(QStringLiteral("snippets")); root.setAttribute(QStringLiteral("name"), text()); root.setAttribute(QStringLiteral("filetypes"), m_filetypes.isEmpty() ? QStringLiteral("*") : m_filetypes.join(QLatin1Char(';'))); root.setAttribute(QStringLiteral("authors"), m_authors); root.setAttribute(QStringLiteral("license"), m_license); root.setAttribute(QStringLiteral("namespace"), m_namespace); doc.appendChild(root); addAndCreateElement(doc, root, QStringLiteral("script"), m_script); for (int i = 0; i < rowCount(); ++i) { Snippet *snippet = dynamic_cast(child(i)); if (!snippet) { continue; } QDomElement item = doc.createElement(QStringLiteral("item")); addAndCreateElement(doc, item, QStringLiteral("match"), snippet->text()); addAndCreateElement(doc, item, QStringLiteral("fillin"), snippet->snippet()); root.appendChild(item); } // KMessageBox::information(0,doc.toString()); QFileInfo fi(m_file); QDir dir = dataPath(); QString outname = dir.absoluteFilePath(fi.fileName()); if (m_file != outname) { QFileInfo fiout(outname); // there could be cases that new new name clashes with a global file, but I guess it is not that often. int i = 0; while (QFile::exists(outname)) { i++; outname = dir.absoluteFilePath(QString::number(i) + fi.fileName()); } KMessageBox::information(QApplication::activeWindow(), i18n("You have edited a data file not located in your personal data directory; as such, a renamed clone of the original data file has been created within your personal data directory.")); } QFile outfile(outname); if (!outfile.open(QIODevice::WriteOnly)) { KMessageBox::error(nullptr, i18n("Output file '%1' could not be opened for writing", outname)); return; } outfile.write(doc.toByteArray()); outfile.close(); m_file = outname; // save shortcuts KConfigGroup config = SnippetStore::self()->getConfig().group(QLatin1String("repository ") + m_file); for (int i = 0; i < rowCount(); ++i) { Snippet *snippet = dynamic_cast(child(i)); if (!snippet) { continue; } QStringList shortcuts; const auto shortcutList = snippet->action()->shortcuts(); for (const QKeySequence &keys : shortcutList) { shortcuts << keys.toString(); } config.writeEntry(QLatin1String("shortcut ") + snippet->text(), shortcuts); } config.sync(); } void SnippetRepository::slotParseFile() { /// based on the code from snippets_tng/lib/completionmodel.cpp ///@copyright 2009 Joseph Wenninger QFile f(m_file); if (!f.open(QIODevice::ReadOnly)) { KMessageBox::error(QApplication::activeWindow(), i18n("Cannot open snippet repository %1.", m_file)); return; } QDomDocument doc; QString errorMsg; int line, col; bool success = doc.setContent(&f, &errorMsg, &line, &col); f.close(); if (!success) { KMessageBox::error(QApplication::activeWindow(), i18n("The error %4
has been detected in the file %1 at %2/%3
", m_file, line, col, i18nc("QXml", errorMsg.toUtf8().data()))); return; } // parse root item const QDomElement &docElement = doc.documentElement(); if (docElement.tagName() != QLatin1String("snippets")) { KMessageBox::error(QApplication::activeWindow(), i18n("Invalid XML snippet file: %1", m_file)); return; } setLicense(docElement.attribute(QStringLiteral("license"))); setAuthors(docElement.attribute(QStringLiteral("authors"))); setFileTypes(docElement.attribute(QStringLiteral("filetypes")).split(QLatin1Char(';'), QString::SkipEmptyParts)); setText(docElement.attribute(QStringLiteral("name"))); setCompletionNamespace(docElement.attribute(QStringLiteral("namespace"))); // load shortcuts KConfigGroup config = SnippetStore::self()->getConfig().group(QLatin1String("repository ") + m_file); // parse children, i.e. 's const QDomNodeList &nodes = docElement.childNodes(); for (int i = 0; i < nodes.size(); ++i) { const QDomNode &node = nodes.at(i); if (!node.isElement()) { continue; } const QDomElement &item = node.toElement(); if (item.tagName() == QLatin1String("script")) { setScript(item.text()); } if (item.tagName() != QLatin1String("item")) { continue; } Snippet *snippet = new Snippet; const QDomNodeList &children = node.childNodes(); for (int j = 0; j < children.size(); ++j) { const QDomNode &childNode = children.at(j); if (!childNode.isElement()) { continue; } const QDomElement &child = childNode.toElement(); if (child.tagName() == QLatin1String("match")) { snippet->setText(child.text()); } else if (child.tagName() == QLatin1String("fillin")) { snippet->setSnippet(child.text()); } } // require at least a non-empty name and snippet if (snippet->text().isEmpty() || snippet->snippet().isEmpty()) { delete snippet; continue; } else { const QStringList shortcuts = config.readEntry(QLatin1String("shortcut ") + snippet->text(), QStringList()); QList sequences; for (const QString &shortcut : shortcuts) { sequences << QKeySequence::fromString(shortcut); } snippet->action()->setShortcuts(sequences); appendRow(snippet); } } } QVariant SnippetRepository::data(int role) const { if (role == Qt::ToolTipRole) { if (checkState() != Qt::Checked) { return i18n("Repository is disabled, the contained snippets will not be shown during code-completion."); } if (m_filetypes.isEmpty()) { return i18n("Applies to all filetypes"); } else { return i18n("Applies to the following filetypes: %1", m_filetypes.join(QLatin1String(", "))); } } else if (role == Qt::ForegroundRole && checkState() != Qt::Checked) { /// TODO: make the selected items also "disalbed" so the toggle action is seen directly KColorScheme scheme(QPalette::Disabled, KColorScheme::View); QColor c = scheme.foreground(KColorScheme::NormalText).color(); return QVariant(c); } return QStandardItem::data(role); } void SnippetRepository::setData(const QVariant &value, int role) { if (role == Qt::CheckStateRole) { const int state = value.toInt(); if (state != checkState()) { KConfigGroup config = SnippetStore::self()->getConfig(); QStringList currentlyEnabled = config.readEntry("enabledRepositories", QStringList()); bool shouldSave = false; if (state == Qt::Checked && !currentlyEnabled.contains(m_file)) { currentlyEnabled << m_file; shouldSave = true; } else if (state == Qt::Unchecked && currentlyEnabled.contains(m_file)) { currentlyEnabled.removeAll(m_file); shouldSave = true; } if (shouldSave) { config.writeEntry("enabledRepositories", currentlyEnabled); config.sync(); } } } QStandardItem::setData(value, role); } diff --git a/addons/snippets/snippetrepository.h b/addons/snippets/snippetrepository.h index 76ae79684..dc7aa441f 100644 --- a/addons/snippets/snippetrepository.h +++ b/addons/snippets/snippetrepository.h @@ -1,165 +1,165 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2010 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef __SNIPPETREPOSITORY_H__ #define __SNIPPETREPOSITORY_H__ +#include #include #include #include -#include namespace KTextEditor { } /** * Each object of this type represents a repository of snippets. Each repository * has a name and will be saved to an XML file that includes all items of this repository. * * To access the snippets in this repo, iterate over it's children and dynamic_cast as required. * To add a snippet, @p appendRow() it. * To access the name of the repository, use @p text() and @p setText(). * * NOTE: Unchecked repositories are considered "disabled" in the sense that their snippets * won't show up during code completion. * * @author Robert Gruber * @author Milian Wolff */ class SnippetRepository : public QObject, public QStandardItem { Q_OBJECT public: /** * Creates a new SnippetRepository. When @p file exists it will be parsed (XML). * * @param file Location of the snippet's repository file. */ SnippetRepository(const QString &file); ~SnippetRepository() override; /** * Creates a snippet repository for the given name and adds it to the SnippetStore. */ static SnippetRepository *createRepoFromName(const QString &name); /** * The license for the snippets contained in this repository. */ QString license() const; /** * Sets the license for the snippets contained in this repository. */ void setLicense(const QString &license); /** * The author(s) of the snippets contained in this repository. */ QString authors() const; /** * Sets the author(s) of the snippets contained in this repository. */ void setAuthors(const QString &authors); /** * The valid filetypes for the snippets contained in this repository. * Empty list means no restriction on the modes. * @see KTextEditor::Document::mode() */ QStringList fileTypes() const; /** * Sets the valid filetypes for the snippets contained in this repository. * An empty list, or any list which contains an element "*" is treated as * a no-restriction filter. */ void setFileTypes(const QStringList &filetypes); /** * The path to this repository's file. */ const QString &file() const; /** * The namespace associated with this repository. * Used in CodeCompletion for filtering. */ QString completionNamespace() const; /** * Sets the code completion namespace for this repository. */ void setCompletionNamespace(const QString &completionNamespace); /** * The QtScript(s) associated with this repository. * * @since KDE 4.5 */ QString script() const; /** * Sets the QtScript(s) associated with this repository. * * @since KDE 4.5 */ void setScript(const QString &script); /** * Remove this repository from the disk. Also deletes the item and all its children. */ void remove(); /** * Save this repository to disk. */ void save(); /** * Get directory for data storage from QStandardPaths */ static QDir dataPath(); QVariant data(int role = Qt::UserRole + 1) const override; void setData(const QVariant &value, int role = Qt::UserRole + 1) override; private Q_SLOTS: /// parses the XML file and load the containing snippets. void slotParseFile(); private: /// path to the repository file QString m_file; /// license of the snippets in this repo QString m_license; /// author(s) of the snippets in this repo QString m_authors; /// valid filetypes for the snippets in this repo QStringList m_filetypes; /// filtering namespace for code completion QString m_namespace; /// QtScript with functions to be used in the snippets; common to all snippets QString m_script; }; #endif diff --git a/addons/snippets/snippetstore.h b/addons/snippets/snippetstore.h index 6af77b95e..ab064d88d 100644 --- a/addons/snippets/snippetstore.h +++ b/addons/snippets/snippetstore.h @@ -1,74 +1,74 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2010 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef __SNIPPETSTORE_H__ #define __SNIPPETSTORE_H__ -#include #include +#include class SnippetRepository; class KateSnippetGlobal; namespace KTextEditor { } /** * This class is implemented as singelton. * It represents the model containing all snippet repositories with their snippets. * @author Robert Gruber * @author Milian Wolff */ class SnippetStore : public QStandardItemModel { Q_OBJECT public: /** * Initialize the SnippetStore. */ static void init(KateSnippetGlobal *plugin); /** * Retuns the SnippetStore. Call init() to set it up first. */ static SnippetStore *self(); ~SnippetStore() override; KConfigGroup getConfig(); bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; /** * Returns the repository for the given @p file if there is any. */ SnippetRepository *repositoryForFile(const QString &file); private: SnippetStore(KateSnippetGlobal *plugin); Qt::ItemFlags flags(const QModelIndex &index) const override; static SnippetStore *m_self; KateSnippetGlobal *m_plugin; }; #endif diff --git a/addons/snippets/snippetview.cpp b/addons/snippets/snippetview.cpp index 73c499a11..b292a024b 100644 --- a/addons/snippets/snippetview.cpp +++ b/addons/snippets/snippetview.cpp @@ -1,377 +1,377 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2007 Robert Gruber * Copyright (C) 2010 Milian Wolff * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "snippetview.h" -#include "snippet.h" +#include "editrepository.h" +#include "editsnippet.h" #include "katesnippetglobal.h" +#include "snippet.h" #include "snippetrepository.h" #include "snippetstore.h" -#include "editrepository.h" -#include "editsnippet.h" #include #include #include #include +#include #include #include -#include #include #include class SnippetFilterModel : public QSortFilterProxyModel { public: SnippetFilterModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) {}; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override { auto index = sourceModel()->index(sourceRow, 0, sourceParent); auto item = SnippetStore::self()->itemFromIndex(index); if (!item) { return false; } auto snippet = dynamic_cast(item); if (!snippet) { return true; } return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); } }; void SnippetView::setupActionsForWindow(QWidget *widget) { const auto &model = SnippetStore::self(); for (int i = 0; i < model->rowCount(); i++) { auto index = model->index(i, 0, QModelIndex()); auto item = model->itemFromIndex(index); auto repo = dynamic_cast(item); if (!repo) { continue; } for (int j = 0; j < model->rowCount(index); j++) { auto item = model->itemFromIndex(model->index(j, 0, index)); auto snippet = dynamic_cast(item); if (!snippet) { continue; } snippet->registerActionForView(widget); } } } SnippetView::SnippetView(KateSnippetGlobal *plugin, KTextEditor::MainWindow *mainWindow, QWidget *parent) : QWidget(parent) , Ui::SnippetViewBase() , m_plugin(plugin) { Ui::SnippetViewBase::setupUi(this); setWindowTitle(i18n("Snippets")); setWindowIcon(QIcon::fromTheme(QStringLiteral("document-new"), windowIcon())); snippetTree->setContextMenuPolicy(Qt::CustomContextMenu); snippetTree->viewport()->installEventFilter(this); connect(snippetTree, &QTreeView::customContextMenuRequested, this, &SnippetView::contextMenu); m_proxy = new SnippetFilterModel(this); m_proxy->setFilterKeyColumn(0); m_proxy->setSourceModel(SnippetStore::self()); connect(filterText, &KLineEdit::textChanged, m_proxy, &QSortFilterProxyModel::setFilterFixedString); snippetTree->setModel(m_proxy); snippetTree->header()->hide(); m_addRepoAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-new")), i18n("Add Repository"), this); connect(m_addRepoAction, &QAction::triggered, this, &SnippetView::slotAddRepo); addAction(m_addRepoAction); m_editRepoAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-txt")), i18n("Edit Repository"), this); connect(m_editRepoAction, &QAction::triggered, this, &SnippetView::slotEditRepo); addAction(m_editRepoAction); m_removeRepoAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Remove Repository"), this); connect(m_removeRepoAction, &QAction::triggered, this, &SnippetView::slotRemoveRepo); addAction(m_removeRepoAction); const bool newStuffAllowed = KAuthorized::authorize(QStringLiteral("ghns")); m_putNewStuffAction = new QAction(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")), i18n("Publish Repository"), this); m_putNewStuffAction->setVisible(newStuffAllowed); connect(m_putNewStuffAction, &QAction::triggered, this, &SnippetView::slotSnippetToGHNS); addAction(m_putNewStuffAction); QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); m_addSnippetAction = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add Snippet"), this); connect(m_addSnippetAction, &QAction::triggered, this, &SnippetView::slotAddSnippet); addAction(m_addSnippetAction); m_editSnippetAction = new QAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Edit Snippet"), this); connect(m_editSnippetAction, &QAction::triggered, this, &SnippetView::slotEditSnippet); addAction(m_editSnippetAction); m_removeSnippetAction = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Remove Snippet"), this); connect(m_removeSnippetAction, &QAction::triggered, this, &SnippetView::slotRemoveSnippet); addAction(m_removeSnippetAction); addAction(separator); m_getNewStuffAction = new QAction(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")), i18n("Get New Snippets"), this); m_getNewStuffAction->setVisible(newStuffAllowed); connect(m_getNewStuffAction, &QAction::triggered, this, &SnippetView::slotGHNS); addAction(m_getNewStuffAction); connect(snippetTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SnippetView::validateActions); validateActions(); connect(snippetTree->model(), &QAbstractItemModel::rowsInserted, this, [this, mainWindow]() { setupActionsForWindow(mainWindow->window()); }); m_proxy->setDynamicSortFilter(true); m_proxy->sort(0, Qt::AscendingOrder); } void SnippetView::validateActions() { QStandardItem *item = currentItem(); Snippet *selectedSnippet = dynamic_cast(item); SnippetRepository *selectedRepo = dynamic_cast(item); m_addRepoAction->setEnabled(true); m_editRepoAction->setEnabled(selectedRepo); m_removeRepoAction->setEnabled(selectedRepo); m_putNewStuffAction->setEnabled(selectedRepo); m_addSnippetAction->setEnabled(selectedRepo || selectedSnippet); m_editSnippetAction->setEnabled(selectedSnippet); m_removeSnippetAction->setEnabled(selectedSnippet); } QStandardItem *SnippetView::currentItem() { /// TODO: support multiple selected items QModelIndex index = snippetTree->currentIndex(); index = m_proxy->mapToSource(index); return SnippetStore::self()->itemFromIndex(index); } void SnippetView::slotSnippetClicked(const QModelIndex &index) { QStandardItem *item = SnippetStore::self()->itemFromIndex(m_proxy->mapToSource(index)); if (!item) return; Snippet *snippet = dynamic_cast(item); if (!snippet) return; m_plugin->insertSnippet(snippet); } void SnippetView::contextMenu(const QPoint &pos) { QModelIndex index = snippetTree->indexAt(pos); index = m_proxy->mapToSource(index); QStandardItem *item = SnippetStore::self()->itemFromIndex(index); if (!item) { // User clicked into an empty place of the tree QMenu menu(this); menu.addSection(i18n("Snippets")); menu.addAction(m_addRepoAction); menu.addAction(m_getNewStuffAction); menu.exec(snippetTree->mapToGlobal(pos)); } else if (Snippet *snippet = dynamic_cast(item)) { QMenu menu(this); menu.addSection(i18n("Snippet: %1", snippet->text())); menu.addAction(m_editSnippetAction); menu.addAction(m_removeSnippetAction); menu.exec(snippetTree->mapToGlobal(pos)); } else if (SnippetRepository *repo = dynamic_cast(item)) { QMenu menu(this); menu.addSection(i18n("Repository: %1", repo->text())); menu.addAction(m_addSnippetAction); menu.addSeparator(); menu.addAction(m_editRepoAction); menu.addAction(m_removeRepoAction); menu.addAction(m_putNewStuffAction); menu.exec(snippetTree->mapToGlobal(pos)); } } void SnippetView::slotEditSnippet() { QStandardItem *item = currentItem(); if (!item) return; Snippet *snippet = dynamic_cast(item); if (!snippet) return; SnippetRepository *repo = dynamic_cast(item->parent()); if (!repo) return; EditSnippet dlg(repo, snippet, this); dlg.exec(); } void SnippetView::slotAddSnippet() { QStandardItem *item = currentItem(); if (!item) return; SnippetRepository *repo = dynamic_cast(item); if (!repo) { repo = dynamic_cast(item->parent()); if (!repo) return; } EditSnippet dlg(repo, nullptr, this); dlg.exec(); } void SnippetView::slotRemoveSnippet() { QStandardItem *item = currentItem(); if (!item) return; SnippetRepository *repo = dynamic_cast(item->parent()); if (!repo) return; int ans = KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("Do you really want to delete the snippet \"%1\"?", item->text())); if (ans == KMessageBox::Continue) { item->parent()->removeRow(item->row()); repo->save(); } } void SnippetView::slotAddRepo() { EditRepository dlg(nullptr, this); dlg.exec(); } void SnippetView::slotEditRepo() { QStandardItem *item = currentItem(); if (!item) return; SnippetRepository *repo = dynamic_cast(item); if (!repo) return; EditRepository dlg(repo, this); dlg.exec(); } void SnippetView::slotRemoveRepo() { QStandardItem *item = currentItem(); if (!item) return; SnippetRepository *repo = dynamic_cast(item); if (!repo) return; int ans = KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("Do you really want to delete the repository \"%1\" with all its snippets?", repo->text())); if (ans == KMessageBox::Continue) { repo->remove(); } } void SnippetView::slotGHNS() { KNS3::DownloadDialog dialog(QStringLiteral(":/katesnippets/ktexteditor_codesnippets_core.knsrc"), this); dialog.exec(); const auto changedEntries = dialog.changedEntries(); for (const KNS3::Entry &entry : changedEntries) { const auto uninstalledFiles = entry.uninstalledFiles(); for (const QString &path : uninstalledFiles) { if (path.endsWith(QLatin1String(".xml"))) { if (SnippetRepository *repo = SnippetStore::self()->repositoryForFile(path)) { repo->remove(); } } } const auto installedFiles = entry.installedFiles(); for (const QString &path : installedFiles) { if (path.endsWith(QLatin1String(".xml"))) { SnippetStore::self()->appendRow(new SnippetRepository(path)); } } } } void SnippetView::slotSnippetToGHNS() { QStandardItem *item = currentItem(); if (!item) return; SnippetRepository *repo = dynamic_cast(item); if (!repo) return; KNS3::UploadDialog dialog(QStringLiteral(":/katesnippets/ktexteditor_codesnippets_core.knsrc"), this); dialog.setUploadFile(QUrl::fromLocalFile(repo->file())); dialog.setUploadName(repo->text()); dialog.exec(); } bool SnippetView::eventFilter(QObject *obj, QEvent *e) { // no, listening to activated() is not enough since that would also trigger the edit mode which we _dont_ want here // users may still rename stuff via select + F2 though if (obj == snippetTree->viewport()) { const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if ((!singleClick && e->type() == QEvent::MouseButtonDblClick) || (singleClick && e->type() == QEvent::MouseButtonRelease)) { QMouseEvent *mouseEvent = dynamic_cast(e); Q_ASSERT(mouseEvent); QModelIndex clickedIndex = snippetTree->indexAt(mouseEvent->pos()); if (clickedIndex.isValid() && clickedIndex.parent().isValid()) { slotSnippetClicked(clickedIndex); e->accept(); return true; } } } return QObject::eventFilter(obj, e); } diff --git a/addons/symbolviewer/plugin_katesymbolviewer.cpp b/addons/symbolviewer/plugin_katesymbolviewer.cpp index 945667ec1..7d6c8c1fa 100644 --- a/addons/symbolviewer/plugin_katesymbolviewer.cpp +++ b/addons/symbolviewer/plugin_katesymbolviewer.cpp @@ -1,489 +1,489 @@ /*************************************************************************** * plugin_katesymbolviewer.cpp - description * ------------------- * begin : Apr 2 2003 * author : 2003 Massimo Callegari * email : massimocallegari@yahoo.it * * Changes: * Nov 09 2004 v.1.3 - For changelog please refer to KDE CVS * Nov 05 2004 v.1.2 - Choose parser from the current highlight. Minor i18n changes. * Nov 28 2003 v.1.1 - Structured for multilanguage support * Added preliminary Tcl/Tk parser (thanks Rohit). To be improved. * Various bugfixing. * Jun 19 2003 v.1.0 - Removed QTimer (polling is Evil(tm)... ) * - Captured documentChanged() event to refresh symbol list * - Tooltips vanished into nowhere...sigh :( * May 04 2003 v 0.6 - Symbol List becomes a K3ListView object. Removed Tooltip class. * Added a QTimer that every 200ms checks: * * if the list width has changed * * if the document has changed * Added an entry in the m_popup menu to switch between List and Tree mode * Various bugfixing. * Apr 24 2003 v 0.5 - Added three check buttons in m_popup menu to show/hide symbols * Apr 23 2003 v 0.4 - "View Symbol" moved in Settings menu. "Refresh List" is no * longer in Kate menu. Moved into a m_popup menu activated by a * mouse right button click. + Bugfixing. * Apr 22 2003 v 0.3 - Added macro extraction + several bugfixing * Apr 19 2003 v 0.2 - Added to CVS. Extract functions and structures * Apr 07 2003 v 0.1 - First version. * * Copyright (C) 2014,2018 by Kåre Särs * ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "plugin_katesymbolviewer.h" +#include +#include +#include +#include #include +#include #include #include #include -#include #include -#include -#include -#include -#include #include #include +#include #include #include -#include -#include +#include #include #include +#include #include -#include K_PLUGIN_FACTORY_WITH_JSON(KatePluginSymbolViewerFactory, "katesymbolviewerplugin.json", registerPlugin();) KatePluginSymbolViewerView::KatePluginSymbolViewerView(KatePluginSymbolViewer *plugin, KTextEditor::MainWindow *mw) : QObject(mw) , m_mainWindow(mw) , m_plugin(plugin) { // FIXME KF5 KGlobal::locale()->insertCatalog("katesymbolviewerplugin"); KXMLGUIClient::setComponentName(QStringLiteral("katesymbolviewer"), i18n("SymbolViewer")); setXMLFile(QStringLiteral("ui.rc")); mw->guiFactory()->addClient(this); m_symbols = nullptr; // FIXME Let the parser decide which options are available and how they are named // because not all these options are supported by all parsers m_popup = new QMenu(m_symbols); m_treeOn = m_popup->addAction(i18n("Tree Mode"), this, &KatePluginSymbolViewerView::displayOptionChanged); m_treeOn->setCheckable(true); m_expandOn = m_popup->addAction(i18n("Expand Tree"), this, &KatePluginSymbolViewerView::displayOptionChanged); m_expandOn->setCheckable(true); m_sort = m_popup->addAction(i18n("Show Sorted"), this, &KatePluginSymbolViewerView::displayOptionChanged); m_sort->setCheckable(true); m_popup->addSeparator(); m_macro = m_popup->addAction(i18n("Show Macros"), this, &KatePluginSymbolViewerView::displayOptionChanged); m_macro->setCheckable(true); m_struct = m_popup->addAction(i18n("Show Structures"), this, &KatePluginSymbolViewerView::displayOptionChanged); m_struct->setCheckable(true); m_func = m_popup->addAction(i18n("Show Functions"), this, &KatePluginSymbolViewerView::displayOptionChanged); m_func->setCheckable(true); m_typesOn = m_popup->addAction(i18n("Show Parameters"), this, &KatePluginSymbolViewerView::displayOptionChanged); m_typesOn->setCheckable(true); KConfigGroup config(KSharedConfig::openConfig(), "PluginSymbolViewer"); m_typesOn->setChecked(config.readEntry(QStringLiteral("ViewTypes"), false)); m_expandOn->setChecked(config.readEntry(QStringLiteral("ExpandTree"), false)); m_treeOn->setChecked(config.readEntry(QStringLiteral("TreeView"), false)); m_sort->setChecked(config.readEntry(QStringLiteral("SortSymbols"), false)); m_macro->setChecked(true); m_struct->setChecked(true); m_func->setChecked(true); m_expandOn->setEnabled(m_treeOn->isChecked()); m_typesOn->setEnabled(m_func->isChecked()); m_updateTimer.setSingleShot(true); connect(&m_updateTimer, &QTimer::timeout, this, &KatePluginSymbolViewerView::parseSymbols); m_currItemTimer.setSingleShot(true); connect(&m_currItemTimer, &QTimer::timeout, this, &KatePluginSymbolViewerView::updateCurrTreeItem); QPixmap cls((const char **)class_xpm); m_toolview = m_mainWindow->createToolView(plugin, QStringLiteral("kate_plugin_symbolviewer"), KTextEditor::MainWindow::Left, cls, i18n("Symbol List")); QWidget *container = new QWidget(m_toolview); QHBoxLayout *layout = new QHBoxLayout(container); m_symbols = new QTreeWidget(); m_symbols->setFocusPolicy(Qt::NoFocus); m_symbols->setLayoutDirection(Qt::LeftToRight); layout->addWidget(m_symbols, 10); layout->setContentsMargins(0, 0, 0, 0); connect(m_symbols, &QTreeWidget::itemClicked, this, &KatePluginSymbolViewerView::goToSymbol); connect(m_symbols, &QTreeWidget::customContextMenuRequested, this, &KatePluginSymbolViewerView::slotShowContextMenu); connect(m_symbols, &QTreeWidget::itemExpanded, this, &KatePluginSymbolViewerView::updateCurrTreeItem); connect(m_symbols, &QTreeWidget::itemCollapsed, this, &KatePluginSymbolViewerView::updateCurrTreeItem); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KatePluginSymbolViewerView::slotDocChanged); QStringList titles; titles << i18nc("@title:column", "Symbols") << i18nc("@title:column", "Position"); m_symbols->setColumnCount(2); m_symbols->setHeaderLabels(titles); m_symbols->setColumnHidden(1, true); m_symbols->setSortingEnabled(m_sort->isChecked()); // Sets the default sorting order: m_symbols->sortByColumn(0, Qt::AscendingOrder); m_symbols->setRootIsDecorated(0); m_symbols->setContextMenuPolicy(Qt::CustomContextMenu); m_symbols->setIndentation(10); m_toolview->installEventFilter(this); // register view m_plugin->m_views.insert(this); } KatePluginSymbolViewerView::~KatePluginSymbolViewerView() { // un-register view m_plugin->m_views.remove(this); m_mainWindow->guiFactory()->removeClient(this); delete m_toolview; delete m_popup; } void KatePluginSymbolViewerView::slotDocChanged() { parseSymbols(); KTextEditor::View *view = m_mainWindow->activeView(); // qDebug()<<"Document changed !!!!" << view; if (view) { connect(view, &KTextEditor::View::cursorPositionChanged, this, &KatePluginSymbolViewerView::cursorPositionChanged, Qt::UniqueConnection); if (view->document()) { connect(view->document(), &KTextEditor::Document::textChanged, this, &KatePluginSymbolViewerView::slotDocEdited, Qt::UniqueConnection); } } } void KatePluginSymbolViewerView::slotDocEdited() { m_currItemTimer.stop(); // Avoid unneeded update m_updateTimer.start(500); } void KatePluginSymbolViewerView::cursorPositionChanged() { if (m_updateTimer.isActive()) { // No need for update, will come anyway return; } KTextEditor::View *editView = m_mainWindow->activeView(); if (!editView) { return; } int currLine = editView->cursorPositionVirtual().line(); if (currLine != m_oldCursorLine) { m_oldCursorLine = currLine; m_currItemTimer.start(100); } } void KatePluginSymbolViewerView::updateCurrTreeItem() { if (!m_mainWindow) { return; } KTextEditor::View *editView = m_mainWindow->activeView(); if (!editView) { return; } KTextEditor::Document *doc = editView->document(); if (!doc) { return; } int currLine = editView->cursorPositionVirtual().line(); int newItemLine = 0; QTreeWidgetItem *newItem = nullptr; QTreeWidgetItem *tmp = nullptr; for (int i = 0; i < m_symbols->topLevelItemCount(); i++) { tmp = newActveItem(newItemLine, currLine, m_symbols->topLevelItem(i)); if (tmp) newItem = tmp; } if (!newItem) { return; } // check if the item has a parent and if that parent is expanded. // if the parent is not expanded, set the parent as current item in stead of // expanding the tree. The tree was probably collapsed on purpose QTreeWidgetItem *parent = newItem->parent(); if (parent && !parent->isExpanded()) { newItem = parent; } m_symbols->blockSignals(true); m_symbols->setCurrentItem(newItem); m_symbols->blockSignals(false); } QTreeWidgetItem *KatePluginSymbolViewerView::newActveItem(int &newItemLine, int currLine, QTreeWidgetItem *item) { QTreeWidgetItem *newItem = nullptr; QTreeWidgetItem *tmp = nullptr; int itemLine = item->data(1, Qt::DisplayRole).toInt(); if ((itemLine <= currLine) && (itemLine >= newItemLine)) { newItemLine = itemLine; newItem = item; } for (int i = 0; i < item->childCount(); i++) { tmp = newActveItem(newItemLine, currLine, item->child(i)); if (tmp) newItem = tmp; } return newItem; } bool KatePluginSymbolViewerView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolview) && (ke->key() == Qt::Key_Escape)) { m_mainWindow->activeView()->setFocus(); event->accept(); return true; } } return QObject::eventFilter(obj, event); } void KatePluginSymbolViewerView::slotShowContextMenu(const QPoint &) { m_popup->popup(QCursor::pos(), m_treeOn); } /** * Each popup menu action is connected to this slot which offer the possibility * to modify the menu depended on current settings. */ void KatePluginSymbolViewerView::displayOptionChanged() { m_expandOn->setEnabled(m_treeOn->isChecked()); m_typesOn->setEnabled(m_func->isChecked()); parseSymbols(); } void KatePluginSymbolViewerView::parseSymbols() { if (!m_symbols) return; m_symbols->clear(); // Qt docu recommends to populate view with disabled sorting // https://doc.qt.io/qt-5/qtreeview.html#sortingEnabled-prop m_symbols->setSortingEnabled(false); Qt::SortOrder sortOrder = m_symbols->header()->sortIndicatorOrder(); if (!m_mainWindow->activeView()) return; KTextEditor::Document *doc = m_mainWindow->activeView()->document(); // be sure we have some document around ! if (!doc) return; /** Get the current highlighting mode */ QString hlModeName = doc->mode(); if (hlModeName.contains(QLatin1String("C++")) || hlModeName == QLatin1Char('C') || hlModeName == QLatin1String("ANSI C89")) parseCppSymbols(); else if (hlModeName == QLatin1String("PHP (HTML)")) parsePhpSymbols(); else if (hlModeName == QLatin1String("Tcl/Tk")) parseTclSymbols(); else if (hlModeName.contains(QLatin1String("Fortran"))) parseFortranSymbols(); else if (hlModeName == QLatin1String("Perl")) parsePerlSymbols(); else if (hlModeName == QLatin1String("Python")) parsePythonSymbols(); else if (hlModeName == QLatin1String("Ruby")) parseRubySymbols(); else if (hlModeName == QLatin1String("Java")) parseCppSymbols(); else if (hlModeName == QLatin1String("xslt")) parseXsltSymbols(); else if (hlModeName == QLatin1String("XML") || hlModeName == QLatin1String("HTML")) parseXMLSymbols(); else if (hlModeName == QLatin1String("Bash")) parseBashSymbols(); else if (hlModeName == QLatin1String("ActionScript 2.0") || hlModeName == QLatin1String("JavaScript") || hlModeName == QLatin1String("QML")) parseEcmaSymbols(); else { QTreeWidgetItem *node = new QTreeWidgetItem(m_symbols); node->setText(0, i18n("Sorry, not supported yet!")); // Setting invalid line number avoid jump to top of document when clicked node->setText(1, QStringLiteral("-1")); node = new QTreeWidgetItem(m_symbols); node->setText(0, i18n("File type: %1", hlModeName)); node->setText(1, QStringLiteral("-1")); } m_oldCursorLine = -1; updateCurrTreeItem(); if (m_sort->isChecked()) { m_symbols->setSortingEnabled(true); m_symbols->sortItems(0, sortOrder); } } void KatePluginSymbolViewerView::goToSymbol(QTreeWidgetItem *it) { KTextEditor::View *kv = m_mainWindow->activeView(); // be sure we really have a view ! if (!kv) return; // qDebug()<<"Slot Activated at pos: "<indexOfTopLevelItem(it); if (!it || it->text(1).isEmpty()) { return; } kv->setCursorPosition(KTextEditor::Cursor(it->text(1).toInt(nullptr, 10), 0)); } KatePluginSymbolViewer::KatePluginSymbolViewer(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { // qDebug()<<"KatePluginSymbolViewer"; } KatePluginSymbolViewer::~KatePluginSymbolViewer() { // qDebug()<<"~KatePluginSymbolViewer"; } QObject *KatePluginSymbolViewer::createView(KTextEditor::MainWindow *mainWindow) { return new KatePluginSymbolViewerView(this, mainWindow); } KTextEditor::ConfigPage *KatePluginSymbolViewer::configPage(int, QWidget *parent) { KatePluginSymbolViewerConfigPage *p = new KatePluginSymbolViewerConfigPage(this, parent); KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("PluginSymbolViewer")); p->viewReturns->setChecked(config.readEntry(QStringLiteral("ViewTypes"), false)); p->expandTree->setChecked(config.readEntry(QStringLiteral("ExpandTree"), false)); p->treeView->setChecked(config.readEntry(QStringLiteral("TreeView"), false)); p->sortSymbols->setChecked(config.readEntry(QStringLiteral("SortSymbols"), false)); connect(p, &KatePluginSymbolViewerConfigPage::configPageApplyRequest, this, &KatePluginSymbolViewer::applyConfig); return (KTextEditor::ConfigPage *)p; } void KatePluginSymbolViewer::applyConfig(KatePluginSymbolViewerConfigPage *p) { KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("PluginSymbolViewer")); config.writeEntry(QStringLiteral("ViewTypes"), p->viewReturns->isChecked()); config.writeEntry(QStringLiteral("ExpandTree"), p->expandTree->isChecked()); config.writeEntry(QStringLiteral("TreeView"), p->treeView->isChecked()); config.writeEntry(QStringLiteral("SortSymbols"), p->sortSymbols->isChecked()); for (auto view : m_views) { view->m_typesOn->setChecked(p->viewReturns->isChecked()); view->m_expandOn->setChecked(p->expandTree->isChecked()); view->m_treeOn->setChecked(p->treeView->isChecked()); view->m_sort->setChecked(p->sortSymbols->isChecked()); view->m_expandOn->setEnabled(view->m_treeOn->isChecked()); view->m_typesOn->setEnabled(view->m_func->isChecked()); } } // BEGIN KatePluginSymbolViewerConfigPage KatePluginSymbolViewerConfigPage::KatePluginSymbolViewerConfigPage(QObject * /*parent*/ /*= 0L*/, QWidget *parentWidget /*= 0L*/) : KTextEditor::ConfigPage(parentWidget) { QVBoxLayout *lo = new QVBoxLayout(this); // int spacing = KDialog::spacingHint(); // lo->setSpacing( spacing ); viewReturns = new QCheckBox(i18n("Display functions parameters")); expandTree = new QCheckBox(i18n("Automatically expand nodes in tree mode")); treeView = new QCheckBox(i18n("Always display symbols in tree mode")); sortSymbols = new QCheckBox(i18n("Always sort symbols")); QGroupBox *parserGBox = new QGroupBox(i18n("Parser Options"), this); QVBoxLayout *top = new QVBoxLayout(parserGBox); top->addWidget(viewReturns); top->addWidget(expandTree); top->addWidget(treeView); top->addWidget(sortSymbols); // QGroupBox* generalGBox = new QGroupBox( i18n("General Options"), this); // QVBoxLayout* genLay = new QVBoxLayout(generalGBox); // genLay->addWidget( ); lo->addWidget(parserGBox); // lo->addWidget( generalGBox ); lo->addStretch(1); // throw signal changed connect(viewReturns, &QCheckBox::toggled, this, &KatePluginSymbolViewerConfigPage::changed); connect(expandTree, &QCheckBox::toggled, this, &KatePluginSymbolViewerConfigPage::changed); connect(treeView, &QCheckBox::toggled, this, &KatePluginSymbolViewerConfigPage::changed); connect(sortSymbols, &QCheckBox::toggled, this, &KatePluginSymbolViewerConfigPage::changed); } KatePluginSymbolViewerConfigPage::~KatePluginSymbolViewerConfigPage() { } QString KatePluginSymbolViewerConfigPage::name() const { return i18n("Symbol Viewer"); } QString KatePluginSymbolViewerConfigPage::fullName() const { return i18n("Symbol Viewer Configuration Page"); } QIcon KatePluginSymbolViewerConfigPage::icon() const { return QPixmap((const char **)class_xpm); } void KatePluginSymbolViewerConfigPage::apply() { emit configPageApplyRequest(this); } // END KatePluginSymbolViewerConfigPage #include "plugin_katesymbolviewer.moc" // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/symbolviewer/plugin_katesymbolviewer.h b/addons/symbolviewer/plugin_katesymbolviewer.h index 93c0922a5..bc2ea121b 100644 --- a/addons/symbolviewer/plugin_katesymbolviewer.h +++ b/addons/symbolviewer/plugin_katesymbolviewer.h @@ -1,176 +1,176 @@ /*************************************************************************** plugin_katesymbolviewer.h - description ------------------- begin : Apr 2 2003 author : 2003 Massimo Callegari email : massimocallegari@yahoo.it ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef _PLUGIN_KATE_SYMBOLVIEWER_H_ #define _PLUGIN_KATE_SYMBOLVIEWER_H_ -#include +#include #include +#include #include -#include #include -#include +#include -#include #include +#include -#include #include -#include -#include #include +#include +#include #include #include +#include #include /** * Plugin's config page */ class KatePluginSymbolViewerConfigPage : public KTextEditor::ConfigPage { Q_OBJECT friend class KatePluginSymbolViewer; public: explicit KatePluginSymbolViewerConfigPage(QObject *parent = nullptr, QWidget *parentWidget = nullptr); ~KatePluginSymbolViewerConfigPage() override; /** * Reimplemented from KTextEditor::ConfigPage * just emits configPageApplyRequest( this ). */ QString name() const override; QString fullName() const override; QIcon icon() const override; void apply() override; void reset() override { ; } void defaults() override { ; } Q_SIGNALS: /** * Ask the plugin to set initial values */ void configPageApplyRequest(KatePluginSymbolViewerConfigPage *); /** * Ask the plugin to apply changes */ void configPageInitRequest(KatePluginSymbolViewerConfigPage *); private: QCheckBox *viewReturns; QCheckBox *expandTree; QCheckBox *treeView; QCheckBox *sortSymbols; }; class KatePluginSymbolViewer; class KatePluginSymbolViewerView : public QObject, public KXMLGUIClient { Q_OBJECT friend class KatePluginSymbolViewer; public: KatePluginSymbolViewerView(KatePluginSymbolViewer *plugin, KTextEditor::MainWindow *mw); ~KatePluginSymbolViewerView() override; public Q_SLOTS: void displayOptionChanged(); void parseSymbols(); void slotDocChanged(); void goToSymbol(QTreeWidgetItem *); void slotShowContextMenu(const QPoint &); void cursorPositionChanged(); QTreeWidgetItem *newActveItem(int &currMinLine, int currLine, QTreeWidgetItem *item); void updateCurrTreeItem(); void slotDocEdited(); protected: bool eventFilter(QObject *obj, QEvent *ev) override; private: KTextEditor::MainWindow *m_mainWindow; KatePluginSymbolViewer *m_plugin; QMenu *m_popup; QWidget *m_toolview; QTreeWidget *m_symbols; QAction *m_treeOn; // FIXME Rename other actions accordingly QAction *m_sort; // m_sortOn etc QAction *m_macro; QAction *m_struct; QAction *m_func; QAction *m_typesOn; QAction *m_expandOn; QTimer m_updateTimer; QTimer m_currItemTimer; int m_oldCursorLine; void updatePixmapScroll(); void parseCppSymbols(void); void parseTclSymbols(void); void parseFortranSymbols(void); void parsePerlSymbols(void); void parsePythonSymbols(void); void parseRubySymbols(void); void parseXsltSymbols(void); void parseXMLSymbols(void); void parsePhpSymbols(void); void parseBashSymbols(void); void parseEcmaSymbols(void); }; class KatePluginSymbolViewer : public KTextEditor::Plugin { friend class KatePluginSymbolViewerView; Q_OBJECT public: explicit KatePluginSymbolViewer(QObject *parent = nullptr, const QList & = QList()); ~KatePluginSymbolViewer() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; int configPages() const override { return 1; } KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; public Q_SLOTS: void applyConfig(KatePluginSymbolViewerConfigPage *p); private: QSet m_views; }; // icons #include "icons.xpm" #endif diff --git a/addons/tabswitcher/tabswitcher.cpp b/addons/tabswitcher/tabswitcher.cpp index 587ecf6de..a8ff267bc 100644 --- a/addons/tabswitcher/tabswitcher.cpp +++ b/addons/tabswitcher/tabswitcher.cpp @@ -1,267 +1,267 @@ /* This file is part of the KDE project Copyright (C) 2014-2019 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "tabswitcher.h" -#include "tabswitchertreeview.h" #include "tabswitcherfilesmodel.h" +#include "tabswitchertreeview.h" #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(TabSwitcherPluginFactory, "tabswitcherplugin.json", registerPlugin();) TabSwitcherPlugin::TabSwitcherPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { } QObject *TabSwitcherPlugin::createView(KTextEditor::MainWindow *mainWindow) { return new TabSwitcherPluginView(this, mainWindow); } TabSwitcherPluginView::TabSwitcherPluginView(TabSwitcherPlugin *plugin, KTextEditor::MainWindow *mainWindow) : QObject(mainWindow) , m_plugin(plugin) , m_mainWindow(mainWindow) { // register this view m_plugin->m_views.append(this); m_model = new detail::TabswitcherFilesModel(this); m_treeView = new TabSwitcherTreeView(); m_treeView->setModel(m_model); KXMLGUIClient::setComponentName(QStringLiteral("tabswitcher"), i18n("Document Switcher")); setXMLFile(QStringLiteral("ui.rc")); // note: call after m_treeView is created setupActions(); // fill the model setupModel(); // register action in menu m_mainWindow->guiFactory()->addClient(this); // popup connections connect(m_treeView, &TabSwitcherTreeView::pressed, this, &TabSwitcherPluginView::switchToClicked); connect(m_treeView, &TabSwitcherTreeView::itemActivated, this, &TabSwitcherPluginView::activateView); // track existing documents connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentCreated, this, &TabSwitcherPluginView::registerDocument); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentWillBeDeleted, this, &TabSwitcherPluginView::unregisterDocument); ; // track lru activation of views to raise the respective documents in the model connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &TabSwitcherPluginView::raiseView); } TabSwitcherPluginView::~TabSwitcherPluginView() { // delete popup widget delete m_treeView; // unregister action in menu m_mainWindow->guiFactory()->removeClient(this); // unregister this view m_plugin->m_views.removeAll(this); } void TabSwitcherPluginView::setupActions() { auto aNext = actionCollection()->addAction(QStringLiteral("view_lru_document_next")); aNext->setText(i18n("Last Used Views")); aNext->setIcon(QIcon::fromTheme(QStringLiteral("go-next-view-page"))); actionCollection()->setDefaultShortcut(aNext, Qt::CTRL | Qt::Key_Tab); aNext->setWhatsThis(i18n("Opens a list to walk through the list of last used views.")); aNext->setStatusTip(i18n("Walk through the list of last used views")); connect(aNext, &QAction::triggered, this, &TabSwitcherPluginView::walkForward); auto aPrev = actionCollection()->addAction(QStringLiteral("view_lru_document_prev")); aPrev->setText(i18n("Last Used Views (Reverse)")); aPrev->setIcon(QIcon::fromTheme(QStringLiteral("go-previous-view-page"))); actionCollection()->setDefaultShortcut(aPrev, Qt::CTRL | Qt::SHIFT | Qt::Key_Tab); aPrev->setWhatsThis(i18n("Opens a list to walk through the list of last used views in reverse.")); aPrev->setStatusTip(i18n("Walk through the list of last used views")); connect(aPrev, &QAction::triggered, this, &TabSwitcherPluginView::walkBackward); // make sure action work when the popup has focus m_treeView->addAction(aNext); m_treeView->addAction(aPrev); } void TabSwitcherPluginView::setupModel() { const auto documents = KTextEditor::Editor::instance()->application()->documents(); // initial fill of model for (auto doc : documents) { registerDocument(doc); } } void TabSwitcherPluginView::registerDocument(KTextEditor::Document *document) { // insert into hash m_documents.insert(document); // add to model m_model->insertDocument(0, document); // track document name changes connect(document, &KTextEditor::Document::documentNameChanged, this, &TabSwitcherPluginView::updateDocumentName); } void TabSwitcherPluginView::unregisterDocument(KTextEditor::Document *document) { // remove from hash if (!m_documents.contains(document)) { return; } m_documents.remove(document); // remove from model m_model->removeDocument(document); // disconnect documentNameChanged() signal disconnect(document, nullptr, this, nullptr); } void TabSwitcherPluginView::updateDocumentName(KTextEditor::Document *document) { if (!m_documents.contains(document)) { return; } // update all items, since a document URL change menas we have to recalculate // common prefix path of all items. m_model->updateItems(); } void TabSwitcherPluginView::raiseView(KTextEditor::View *view) { if (!view || !m_documents.contains(view->document())) { return; } m_model->raiseDocument(view->document()); } void TabSwitcherPluginView::walk(const int from, const int to) { QModelIndex index; const int step = from < to ? 1 : -1; if (!m_treeView->isVisible()) { updateViewGeometry(); index = m_model->index(from + step, 0); if (!index.isValid()) { index = m_model->index(0, 0); } m_treeView->show(); m_treeView->setFocus(); } else { int newRow = m_treeView->selectionModel()->currentIndex().row() + step; if (newRow == to + step) { newRow = from; } index = m_model->index(newRow, 0); } m_treeView->selectionModel()->select(index, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); m_treeView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } void TabSwitcherPluginView::walkForward() { walk(0, m_model->rowCount() - 1); } void TabSwitcherPluginView::walkBackward() { walk(m_model->rowCount() - 1, 0); } void TabSwitcherPluginView::updateViewGeometry() { QWidget *window = m_mainWindow->window(); const QSize centralSize = window->size(); // Maximum size of the view is 3/4th of the central widget (the editor area) // so the view does not overlap the mainwindow since that looks awkward. const QSize viewMaxSize(centralSize.width() * 3 / 4, centralSize.height() * 3 / 4); // The actual view size should be as big as the columns/rows need it, but // smaller than the max-size. This means the view will get quite high with // many open files but I think thats ok. Otherwise one can easily tweak the // max size to be only 1/2th of the central widget size const int rowHeight = m_treeView->sizeHintForRow(0); const int frameWidth = m_treeView->frameWidth(); // const QSize viewSize(std::min(m_treeView->sizeHintForColumn(0) + 2 * frameWidth + m_treeView->verticalScrollBar()->width(), viewMaxSize.width()), // ORIG line, sizeHintForColumn was QListView but is protected for QTreeView so we // introduced sizeHintWidth() const QSize viewSize(std::min(m_treeView->sizeHintWidth() + 2 * frameWidth + m_treeView->verticalScrollBar()->width(), viewMaxSize.width()), std::min(std::max(rowHeight * m_model->rowCount() + 2 * frameWidth, rowHeight * 6), viewMaxSize.height())); // Position should be central over the editor area, so map to global from // parent of central widget since the view is positioned in global coords const QPoint centralWidgetPos = window->parentWidget() ? window->mapToGlobal(window->pos()) : window->pos(); const int xPos = std::max(0, centralWidgetPos.x() + (centralSize.width() - viewSize.width()) / 2); const int yPos = std::max(0, centralWidgetPos.y() + (centralSize.height() - viewSize.height()) / 2); m_treeView->setFixedSize(viewSize); m_treeView->move(xPos, yPos); } void TabSwitcherPluginView::switchToClicked(const QModelIndex &index) { m_treeView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); activateView(index); } void TabSwitcherPluginView::activateView(const QModelIndex &index) { Q_UNUSED(index) // guard against empty selection if (m_treeView->selectionModel()->selectedRows().isEmpty()) { return; } const int row = m_treeView->selectionModel()->selectedRows().first().row(); auto doc = m_model->item(row); m_mainWindow->activateView(doc); m_treeView->hide(); } // required for TabSwitcherPluginFactory vtable #include "tabswitcher.moc" diff --git a/addons/tabswitcher/tabswitcher.h b/addons/tabswitcher/tabswitcher.h index 84eaf2d7d..20d491355 100644 --- a/addons/tabswitcher/tabswitcher.h +++ b/addons/tabswitcher/tabswitcher.h @@ -1,148 +1,148 @@ /* This file is part of the KDE project Copyright (C) 2014 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef KTEXTEDITOR_TAB_SWITCHER_PLUGIN_H #define KTEXTEDITOR_TAB_SWITCHER_PLUGIN_H -#include #include +#include #include #include #include #include class TabSwitcherPluginView; class TabSwitcherTreeView; class QStandardItemModel; class QModelIndex; namespace detail { class TabswitcherFilesModel; } class TabSwitcherPlugin : public KTextEditor::Plugin { Q_OBJECT friend TabSwitcherPluginView; public: /** * Plugin constructor. */ explicit TabSwitcherPlugin(QObject *parent = nullptr, const QList & = QList()); /** * Create a new tab switcher for @p mainWindow. */ QObject *createView(KTextEditor::MainWindow *mainWindow) override; private: QList m_views; }; class TabSwitcherPluginView : public QObject, public KXMLGUIClient { Q_OBJECT public: /** * View constructor. */ TabSwitcherPluginView(TabSwitcherPlugin *plugin, KTextEditor::MainWindow *mainWindow); /** * View destructor. */ ~TabSwitcherPluginView() override; /** * Setup the shortcut actions. */ void setupActions(); /** * Initial fill of model with documents from the application. */ void setupModel(); public Q_SLOTS: /** * Adds @p document to the model. */ void registerDocument(KTextEditor::Document *document); /** * Removes @p document from the model. */ void unregisterDocument(KTextEditor::Document *document); /** * Update the name in the model for @p document. */ void updateDocumentName(KTextEditor::Document *document); /** * Raise @p view in a lru fashion. */ void raiseView(KTextEditor::View *view); /** * Focus next item in the treeview. */ void walkForward(); /** * Focus previous item in the treeview. */ void walkBackward(); /** * Activate the document for @p index. */ void switchToClicked(const QModelIndex &index); /** * Show the document for @p index. */ void activateView(const QModelIndex &index); protected: /** * Move through the list. */ void walk(const int from, const int to); /** * Make sure the popup view has a sane size. */ void updateViewGeometry(); private: TabSwitcherPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; detail::TabswitcherFilesModel *m_model; QSet m_documents; TabSwitcherTreeView *m_treeView; }; #endif // KTEXTEDITOR_TAB_SWITCHER_PLUGIN_H diff --git a/addons/tabswitcher/tabswitcherfilesmodel.cpp b/addons/tabswitcher/tabswitcherfilesmodel.cpp index a41c06c09..4de090ec9 100644 --- a/addons/tabswitcher/tabswitcherfilesmodel.cpp +++ b/addons/tabswitcher/tabswitcherfilesmodel.cpp @@ -1,241 +1,241 @@ /* This file is part of the KDE project Copyright (C) 2018 Gregor Mi Copyright (C) 2019 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "tabswitcherfilesmodel.h" -#include #include +#include #include #include #include #include namespace detail { FilenameListItem::FilenameListItem(KTextEditor::Document *doc) : document(doc) { } QIcon FilenameListItem::icon() const { return QIcon::fromTheme(QMimeDatabase().mimeTypeForUrl(document->url()).iconName()); } QString FilenameListItem::documentName() const { return document->documentName(); } QString FilenameListItem::fullPath() const { return document->url().toLocalFile(); } /** * Note that if strs contains the empty string, the result will be "" */ QString longestCommonPrefix(std::vector const &strs) { if (strs.empty()) { return QString(); } if (strs.size() == 1) { return strs.front(); } // get the min length auto it = std::min_element(strs.begin(), strs.end(), [](const QString &lhs, const QString &rhs) { return lhs.size() < rhs.size(); }); const int n = it->size(); for (int pos = 0; pos < n; pos++) { // check each character for (size_t i = 1; i < strs.size(); i++) { if (strs[i][pos] != strs[i - 1][pos]) { // we found a mis-match // reverse search to find path separator const int sepIndex = strs.front().leftRef(pos).lastIndexOf(QLatin1Char('/')); if (sepIndex >= 0) { pos = sepIndex + 1; } return strs.front().left(pos); } } } // prefix is n-length return strs.front().left(n); } void post_process(FilenameList &data) { // collect non-empty paths std::vector paths; for (const auto &item : data) { const auto path = item.fullPath(); if (!path.isEmpty()) { paths.push_back(path); } } const QString prefix = longestCommonPrefix(paths); int prefix_length = prefix.length(); if (prefix_length == 1) { // if there is only the "/" at the beginning, then keep it prefix_length = 0; } for (auto &item : data) { // Note that item.documentName can contain additional characters - e.g. "README.md (2)" - // so we cannot use that and have to parse the base filename by other means: const QString basename = QFileInfo(item.fullPath()).fileName(); // e.g. "archive.tar.gz" // cut prefix (left side) and cut document name (plus slash) on the right side int len = item.fullPath().length() - prefix_length - basename.length() - 1; if (len > 0) { // only assign in case item.fullPath() is not empty // "PREFIXPATH/REMAININGPATH/BASENAME" --> "REMAININGPATH" item.displayPathPrefix = item.fullPath().mid(prefix_length, len); } } } } detail::TabswitcherFilesModel::TabswitcherFilesModel(QObject *parent) : QAbstractTableModel(parent) { } bool detail::TabswitcherFilesModel::insertDocument(int row, KTextEditor::Document *document) { beginInsertRows(QModelIndex(), row, row); data_.insert(data_.begin() + row, FilenameListItem(document)); endInsertRows(); // update all other items, since the common prefix path may have changed updateItems(); return true; } bool detail::TabswitcherFilesModel::removeDocument(KTextEditor::Document *document) { auto it = std::find_if(data_.begin(), data_.end(), [document](FilenameListItem &item) { return item.document == document; }); if (it == data_.end()) { return false; } const int row = std::distance(data_.begin(), it); removeRow(row); return true; } bool detail::TabswitcherFilesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); if (row < 0 || row + count > rowCount()) { return false; } beginRemoveRows(QModelIndex(), row, row + count - 1); data_.erase(data_.begin() + row, data_.begin() + row + count); endRemoveRows(); // update all other items, since the common prefix path may have changed updateItems(); return true; } void detail::TabswitcherFilesModel::clear() { if (!data_.empty()) { beginResetModel(); data_.clear(); endResetModel(); } } void detail::TabswitcherFilesModel::raiseDocument(KTextEditor::Document *document) { // skip row 0, since row 0 is already correct for (int row = 1; row < rowCount(); ++row) { if (data_[row].document == document) { beginMoveRows(QModelIndex(), row, row, QModelIndex(), 0); std::rotate(data_.begin(), data_.begin() + row, data_.begin() + row + 1); endMoveRows(); break; } } } KTextEditor::Document *detail::TabswitcherFilesModel::item(int row) const { return data_[row].document; } void detail::TabswitcherFilesModel::updateItems() { post_process(data_); emit dataChanged(createIndex(0, 0), createIndex(data_.size() - 1, 1), {}); } int detail::TabswitcherFilesModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 2; } int detail::TabswitcherFilesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return data_.size(); } QVariant detail::TabswitcherFilesModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { const auto &row = data_[index.row()]; if (index.column() == 0) return row.displayPathPrefix; else return row.documentName(); } else if (role == Qt::DecorationRole) { if (index.column() == 1) { const auto &row = data_[index.row()]; return row.icon(); } } else if (role == Qt::ToolTipRole) { const auto &row = data_[index.row()]; return row.fullPath(); } else if (role == Qt::TextAlignmentRole) { if (index.column() == 0) return Qt::AlignRight + Qt::AlignVCenter; else return Qt::AlignVCenter; } else if (role == Qt::ForegroundRole) { if (index.column() == 0) return QBrush(Qt::darkGray); else return QVariant(); } return QVariant(); } diff --git a/addons/tabswitcher/tabswitcherfilesmodel.h b/addons/tabswitcher/tabswitcherfilesmodel.h index c7bd40064..71fe1383d 100644 --- a/addons/tabswitcher/tabswitcherfilesmodel.h +++ b/addons/tabswitcher/tabswitcherfilesmodel.h @@ -1,116 +1,116 @@ /* This file is part of the KDE project Copyright (C) 2018 Gregor Mi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef KTEXTEDITOR_TAB_SWITCHER_FILES_MODEL_H #define KTEXTEDITOR_TAB_SWITCHER_FILES_MODEL_H -#include -#include #include +#include +#include namespace KTextEditor { class Document; } namespace detail { /** * Represents one item in the table view of the tab switcher. */ class FilenameListItem { public: FilenameListItem(KTextEditor::Document *doc); KTextEditor::Document *document; QIcon icon() const; QString documentName() const; QString fullPath() const; /** * calculated from documentName and fullPath */ QString displayPathPrefix; }; using FilenameList = std::vector; class TabswitcherFilesModel : public QAbstractTableModel { Q_OBJECT public: explicit TabswitcherFilesModel(QObject *parent = nullptr); ~TabswitcherFilesModel() override = default; bool insertDocument(int row, KTextEditor::Document *document); bool removeDocument(KTextEditor::Document *document); /** * Clears all data from the model */ void clear(); /** * NOTE: The returned pointer will become invalid as soon as the underlying vector changes. */ KTextEditor::Document *item(int row) const; /** * Move the document to row 0. */ void raiseDocument(KTextEditor::Document *document); /* * Use this method to update all items. * This is typically needed when a document name changes, since then the prefix paths change, * so all items need an update. */ void updateItems(); /** * Reimplemented to return the column count of top-level items. */ int columnCount(const QModelIndex &parent = QModelIndex()) const override; /** * Reimplemented to return the top-level row count. */ int rowCount(const QModelIndex &parent = QModelIndex()) const override; /** * Returns the data for the requested model index. */ QVariant data(const QModelIndex &index, int role) const override; /** * Reimplemented to remove the specified rows. * The paret is always ignored since this is a table model. */ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; private: FilenameList data_; }; QString longestCommonPrefix(std::vector const &strs); } #endif // KTEXTEDITOR_TAB_SWITCHER_FILES_MODEL_H diff --git a/addons/tabswitcher/tabswitchertreeview.cpp b/addons/tabswitcher/tabswitchertreeview.cpp index 786d4d405..f9d471c4f 100644 --- a/addons/tabswitcher/tabswitchertreeview.cpp +++ b/addons/tabswitcher/tabswitchertreeview.cpp @@ -1,77 +1,77 @@ /* This file is part of the KDE project Copyright (C) 2014 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "tabswitchertreeview.h" #include "tabswitcher.h" -#include #include +#include TabSwitcherTreeView::TabSwitcherTreeView() : QTreeView() { setWindowFlags(Qt::Popup | Qt::FramelessWindowHint); setSelectionBehavior(QAbstractItemView::SelectRows); setSelectionMode(QAbstractItemView::SingleSelection); // setUniformItemSizes(true); setTextElideMode(Qt::ElideMiddle); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHeaderHidden(true); setRootIsDecorated(false); } int TabSwitcherTreeView::sizeHintWidth() const { return sizeHintForColumn(0) + sizeHintForColumn(1); } void TabSwitcherTreeView::resizeColumnsToContents() { resizeColumnToContents(0); resizeColumnToContents(1); } void TabSwitcherTreeView::keyReleaseEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control) { emit itemActivated(selectionModel()->currentIndex()); event->accept(); hide(); } else { QTreeView::keyReleaseEvent(event); } } void TabSwitcherTreeView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { event->accept(); hide(); } else { QTreeView::keyPressEvent(event); } } void TabSwitcherTreeView::showEvent(QShowEvent *event) { resizeColumnsToContents(); QTreeView::showEvent(event); } diff --git a/addons/tabswitcher/tests/tstestapp.cpp b/addons/tabswitcher/tests/tstestapp.cpp index 9af5c4865..0d75dd004 100644 --- a/addons/tabswitcher/tests/tstestapp.cpp +++ b/addons/tabswitcher/tests/tstestapp.cpp @@ -1,148 +1,148 @@ /* This file is part of the KDE project * * Copyright (C) 2018 Gregor Mi * Copyright (C) 2019 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "tstestapp.h" #include "../tabswitcherfilesmodel.h" -#include -#include -#include #include -#include -#include -#include -#include +#include #include #include -#include #include +#include +#include +#include +#include +#include +#include +#include -#include #include +#include static KTextEditor::Document *addDoc(const QString &path) { auto doc = KTextEditor::Editor::instance()->createDocument(nullptr); doc->openUrl(QUrl::fromLocalFile(path)); return doc; } class TsTestApp::Impl { public: void insert_1_item() { model.insertDocument(0, addDoc(QStringLiteral("/home/user2/folder1/abc.d"))); treeview1->resizeColumnToContents(0); } void remove_1_item() { model.removeRow(0); treeview1->resizeColumnToContents(0); } void set_items_cutoff_bug() { model.clear(); auto icon = QIcon::fromTheme(QLatin1String("document-export")); model.insertDocument(model.rowCount(), addDoc(QStringLiteral("/home/gregor/logs/notifications/multimedia-system.log"))); model.insertDocument(model.rowCount(), addDoc(QStringLiteral("/home/gregor/dev/src/kservicemenueditor-0.2a/servicemenueditor"))); model.insertDocument(model.rowCount(), addDoc(QStringLiteral("/home/gregor/kde/src/kdesrc-build/kdesrc-build"))); model.insertDocument(model.rowCount(), addDoc(QStringLiteral("/home/gregor/node_modules/autolinker/README.md"))); model.insertDocument(model.rowCount(), addDoc(QStringLiteral("/home/gregor/node_modules/autolinker/package.json"))); model.insertDocument(model.rowCount(), addDoc(QStringLiteral("/home/gregor/node_modules/autolinker/LICENSE"))); model.insertDocument(model.rowCount(), addDoc(QStringLiteral("/home/gregor/node_modules/asynckit/package.json"))); treeview1->resizeColumnToContents(0); } public: detail::TabswitcherFilesModel model; QTreeView *treeview1; }; TsTestApp::TsTestApp(QWidget *parent) : QMainWindow(parent) , impl_(new TsTestApp::Impl) { setGeometry(0, 0, 1024, 800); setCentralWidget(new QWidget(this)); auto l = new QVBoxLayout(); centralWidget()->setLayout(l); auto hl = new QHBoxLayout(); l->addLayout(hl); auto buttonInsert1 = new QPushButton(QStringLiteral("Ins 1 item"), this); connect(buttonInsert1, &QPushButton::clicked, this, [=] { impl_->insert_1_item(); }); hl->addWidget(buttonInsert1); auto buttonRemove1 = new QPushButton(QStringLiteral("Del 1 item"), this); connect(buttonRemove1, &QPushButton::clicked, this, [=] { impl_->remove_1_item(); }); hl->addWidget(buttonRemove1); auto buttonSetTestSet1 = new QPushButton(QStringLiteral("set_items_cutoff_bug"), this); connect(buttonSetTestSet1, &QPushButton::clicked, this, [=] { impl_->set_items_cutoff_bug(); }); hl->addWidget(buttonSetTestSet1); impl_->treeview1 = new QTreeView(this); l->addWidget(impl_->treeview1); impl_->treeview1->setHeaderHidden(true); impl_->treeview1->setRootIsDecorated(false); auto icon = QIcon::fromTheme(QLatin1String("edit-undo")); impl_->model.insertDocument(impl_->model.rowCount(), addDoc(QStringLiteral("/home/gm/projects/proj1/src/file1.h"))); impl_->model.insertDocument(impl_->model.rowCount(), addDoc(QStringLiteral("/home/gm/projects/proj1/src/file2.cpp"))); impl_->model.insertDocument(impl_->model.rowCount(), addDoc(QStringLiteral("/home/gm/dev/file3.py"))); impl_->model.insertDocument(impl_->model.rowCount(), addDoc(QStringLiteral("/home/gm/dev/file3kjaskdfkljasdfklj089asdfkjklasdjf90asdfsdfkj.py"))); impl_->model.insertDocument(impl_->model.rowCount(), addDoc(QStringLiteral("/home/gm/dev/proj2/asldfkjasdfk/asdlfkjasd;faf/;ajsdkfgjaskdfgasdf/file3.py"))); // impl_->insert_a_item(); // impl_->remove_a_item(); impl_->model.rowCount(); impl_->model.item(0); impl_->model.index(0, 0); impl_->treeview1->setModel(&impl_->model); impl_->treeview1->resizeColumnToContents(0); impl_->treeview1->resizeColumnToContents(1); auto listview1 = new QListView(this); l->addWidget(listview1); listview1->setModel(&impl_->model); auto treeview2 = new QTreeView(this); l->addWidget(treeview2); } TsTestApp::~TsTestApp() { } int main(int argc, char *argv[]) { QApplication app(argc, argv); TsTestApp w; w.show(); return app.exec(); } diff --git a/addons/textfilter/plugin_katetextfilter.cpp b/addons/textfilter/plugin_katetextfilter.cpp index 70087edff..2ece3dbf6 100644 --- a/addons/textfilter/plugin_katetextfilter.cpp +++ b/addons/textfilter/plugin_katetextfilter.cpp @@ -1,260 +1,260 @@ /*************************************************************************** plugin_katetextfilter.cpp - description ------------------- begin : FRE Feb 23 2001 copyright : (C) 2001 by Joseph Wenninger copyright : (C) 2009 Dominik Haumann ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "plugin_katetextfilter.h" #include "ui_textfilterwidget.h" #include #include -#include #include -#include -#include -#include +#include #include +#include +#include #include +#include -#include #include -#include -#include #include +#include #include +#include +#include #include #include K_PLUGIN_FACTORY_WITH_JSON(TextFilterPluginFactory, "textfilterplugin.json", registerPlugin();) PluginKateTextFilter::PluginKateTextFilter(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { // register command new PluginKateTextFilterCommand(this); } PluginKateTextFilter::~PluginKateTextFilter() { // cleanup the process the right way (TM) if (m_pFilterProcess) { m_pFilterProcess->kill(); m_pFilterProcess->waitForFinished(); delete m_pFilterProcess; } } QObject *PluginKateTextFilter::createView(KTextEditor::MainWindow *mainWindow) { // create a plugin view return new PluginViewKateTextFilter(this, mainWindow); } void PluginKateTextFilter::slotFilterReceivedStdout() { m_strFilterOutput += QString::fromLocal8Bit(m_pFilterProcess->readAllStandardOutput()); } void PluginKateTextFilter::slotFilterReceivedStderr() { const QString block = QString::fromLocal8Bit(m_pFilterProcess->readAllStandardError()); if (mergeOutput) m_strFilterOutput += block; else m_stderrOutput += block; } void PluginKateTextFilter::slotFilterProcessExited(int, QProcess::ExitStatus) { KTextEditor::View *kv(KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView()); if (!kv) return; // Is there any error output to display? if (!mergeOutput && !m_stderrOutput.isEmpty()) { QPointer message = new KTextEditor::Message(xi18nc("@info", "Result of:
$ %1\n%2
", m_last_command, m_stderrOutput), KTextEditor::Message::Error); message->setWordWrap(true); message->setAutoHide(1000); kv->document()->postMessage(message); } if (copyResult) { QApplication::clipboard()->setText(m_strFilterOutput); return; } // Do not even try to change the document if no result collected... if (m_strFilterOutput.isEmpty()) return; KTextEditor::Document::EditingTransaction transaction(kv->document()); KTextEditor::Cursor start = kv->cursorPosition(); if (kv->selection()) { start = kv->selectionRange().start(); kv->removeSelectionText(); } kv->setCursorPosition(start); // for block selection kv->insertText(m_strFilterOutput); } static void slipInFilter(KProcess &proc, KTextEditor::View &view, const QString &command) { QString inputText; if (view.selection()) { inputText = view.selectionText(); } proc.clearProgram(); proc.setShellCommand(command); proc.start(); QByteArray encoded = inputText.toLocal8Bit(); proc.write(encoded); proc.closeWriteChannel(); // TODO: Put up a modal dialog to defend the text from further // keystrokes while the command is out. With a cancel button... } void PluginKateTextFilter::slotEditFilter() { if (!KAuthorized::authorize(QStringLiteral("shell_access"))) { KMessageBox::sorry(nullptr, i18n("You are not allowed to execute arbitrary external applications. If " "you want to be able to do this, contact your system administrator."), i18n("Access Restrictions")); return; } if (!KTextEditor::Editor::instance()->application()->activeMainWindow()) return; KTextEditor::View *kv(KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView()); if (!kv) return; QDialog dialog(KTextEditor::Editor::instance()->application()->activeMainWindow()->window()); Ui::TextFilterWidget ui; ui.setupUi(&dialog); ui.filterBox->setFocus(); dialog.setWindowTitle(i18n("Text Filter")); KConfigGroup config(KSharedConfig::openConfig(), "PluginTextFilter"); QStringList items = config.readEntry("Completion list", QStringList()); copyResult = config.readEntry("Copy result", false); mergeOutput = config.readEntry("Merge output", true); ui.filterBox->setMaxCount(10); ui.filterBox->setHistoryItems(items, true); ui.filterBox->setMinimumContentsLength(80); ui.copyResult->setChecked(copyResult); ui.mergeOutput->setChecked(mergeOutput); if (dialog.exec() == QDialog::Accepted) { copyResult = ui.copyResult->isChecked(); mergeOutput = ui.mergeOutput->isChecked(); const QString filter = ui.filterBox->currentText(); if (!filter.isEmpty()) { ui.filterBox->addToHistory(filter); config.writeEntry("Completion list", ui.filterBox->historyItems()); config.writeEntry("Copy result", copyResult); config.writeEntry("Merge output", mergeOutput); m_last_command = filter; runFilter(kv, filter); } } } void PluginKateTextFilter::runFilter(KTextEditor::View *kv, const QString &filter) { m_strFilterOutput.clear(); m_stderrOutput.clear(); if (!m_pFilterProcess) { m_pFilterProcess = new KProcess; connect(m_pFilterProcess, &KProcess::readyReadStandardOutput, this, &PluginKateTextFilter::slotFilterReceivedStdout); connect(m_pFilterProcess, &KProcess::readyReadStandardError, this, &PluginKateTextFilter::slotFilterReceivedStderr); connect(m_pFilterProcess, static_cast(&KProcess::finished), this, &PluginKateTextFilter::slotFilterProcessExited); } m_pFilterProcess->setOutputChannelMode(mergeOutput ? KProcess::MergedChannels : KProcess::SeparateChannels); slipInFilter(*m_pFilterProcess, *kv, filter); } // BEGIN Kate::Command methods PluginKateTextFilterCommand::PluginKateTextFilterCommand(PluginKateTextFilter *plugin) : KTextEditor::Command(QStringList() << QStringLiteral("textfilter"), plugin) , m_plugin(plugin) { } bool PluginKateTextFilterCommand::exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &) { QString filter = cmd.section(QLatin1Char(' '), 1).trimmed(); if (filter.isEmpty()) { msg = i18n("Usage: textfilter COMMAND"); return false; } m_plugin->runFilter(view, filter); return true; } bool PluginKateTextFilterCommand::help(KTextEditor::View *, const QString &, QString &msg) { msg = i18n( "

Usage: textfilter COMMAND

" "

Replace the selection with the output of the specified shell command.

"); return true; } // END PluginViewKateTextFilter::PluginViewKateTextFilter(PluginKateTextFilter *plugin, KTextEditor::MainWindow *mainwindow) : QObject(mainwindow) , m_mainWindow(mainwindow) { // setup right xml gui data KXMLGUIClient::setComponentName(QStringLiteral("textfilter"), i18n("Text Filter")); setXMLFile(QStringLiteral("ui.rc")); // create our one and only action QAction *a = actionCollection()->addAction(QStringLiteral("edit_filter")); a->setText(i18n("&Filter Through Command...")); actionCollection()->setDefaultShortcut(a, Qt::CTRL + Qt::Key_Backslash); connect(a, &QAction::triggered, plugin, &PluginKateTextFilter::slotEditFilter); // register us at the UI mainwindow->guiFactory()->addClient(this); } PluginViewKateTextFilter::~PluginViewKateTextFilter() { // remove us from the UI again m_mainWindow->guiFactory()->removeClient(this); } // required for TextFilterPluginFactory vtable #include "plugin_katetextfilter.moc" diff --git a/addons/textfilter/plugin_katetextfilter.h b/addons/textfilter/plugin_katetextfilter.h index 1e20251f4..92a0b97d1 100644 --- a/addons/textfilter/plugin_katetextfilter.h +++ b/addons/textfilter/plugin_katetextfilter.h @@ -1,103 +1,103 @@ /*************************************************************************** plugin_katetextfilter.h - description ------------------- begin : FRE Feb 23 2001 copyright : (C) 2001 by Joseph Wenninger email : jowenn@bigfoot.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef PLUGIN_KATETEXTFILTER_H #define PLUGIN_KATETEXTFILTER_H -#include #include +#include +#include #include +#include #include -#include -#include #include #include class PluginKateTextFilter : public KTextEditor::Plugin { Q_OBJECT public: /** * Plugin constructor. */ explicit PluginKateTextFilter(QObject *parent = nullptr, const QList & = QList()); ~PluginKateTextFilter() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; void runFilter(KTextEditor::View *kv, const QString &filter); private: QString m_strFilterOutput; QString m_stderrOutput; QString m_last_command; KProcess *m_pFilterProcess = nullptr; QStringList completionList; bool copyResult = false; bool mergeOutput = false; public Q_SLOTS: void slotEditFilter(); void slotFilterReceivedStdout(); void slotFilterReceivedStderr(); void slotFilterProcessExited(int exitCode, QProcess::ExitStatus exitStatus); }; class PluginKateTextFilterCommand : public KTextEditor::Command { Q_OBJECT public: PluginKateTextFilterCommand(PluginKateTextFilter *plugin); // Kate::Command bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) override; bool help(KTextEditor::View *view, const QString &cmd, QString &msg) override; private: PluginKateTextFilter *m_plugin; }; /** * Plugin view to merge the actions into the UI */ class PluginViewKateTextFilter : public QObject, public KXMLGUIClient { Q_OBJECT public: /** * Construct plugin view * @param plugin our plugin * @param mainwindows the mainwindow for this view */ explicit PluginViewKateTextFilter(PluginKateTextFilter *plugin, KTextEditor::MainWindow *mainwindow); /** * Our Destructor */ ~PluginViewKateTextFilter() override; private: /** * the main window we belong to */ KTextEditor::MainWindow *m_mainWindow; }; #endif diff --git a/addons/xmlcheck/plugin_katexmlcheck.cpp b/addons/xmlcheck/plugin_katexmlcheck.cpp index e5f9849f8..e6c3b5561 100644 --- a/addons/xmlcheck/plugin_katexmlcheck.cpp +++ b/addons/xmlcheck/plugin_katexmlcheck.cpp @@ -1,394 +1,394 @@ /*************************************************************************** plugin_katexmlcheck.cpp - checks XML files using xmllint ------------------- begin : 2002-07-06 copyright : (C) 2002 by Daniel Naber email : daniel.naber@t-online.de ***************************************************************************/ /*************************************************************************** This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ***************************************************************************/ /* -fixme: show dock if "Validate XML" is selected (doesn't currently work when Kate was just started and the dockwidget isn't yet visible) -fixme(?): doesn't correctly disappear when deactivated in config */ // TODO: // Cleanup unneeded headers // Find resources and translate i18n messages // all translations were deleted in https://websvn.kde.org/?limit_changes=0&view=revision&revision=1433517 // What to do with catalogs? What is it for? // Implement hot key shortcut to do xml validation // Remove copyright above due to author orphaned this plugin? // Possibility to check only well-formdness without validation // Hide output in dock when switching to another tab // Make ability to validate against xml schema and then edit docbook // Should del space in [km] strang in katexmlcheck.desktop? // Which variant should I choose? QUrl.adjusted(rm filename).path() or QUrl.toString(rm filename|rm schema) // What about replace xmllint xmlstarlet or something? // Maybe use QXmlReader to take dtds and xsds? #include "plugin_katexmlcheck.h" #include //#include "plugin_katexmlcheck.moc" this goes to end +#include +#include +#include +#include #include #include #include #include #include -#include -#include -#include -#include #include +#include +#include #include #include #include -#include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(PluginKateXMLCheckFactory, "katexmlcheck.json", registerPlugin();) PluginKateXMLCheck::PluginKateXMLCheck(QObject *const parent, const QVariantList &) : KTextEditor::Plugin(parent) { qDebug() << "PluginXmlCheck()"; } PluginKateXMLCheck::~PluginKateXMLCheck() { } QObject *PluginKateXMLCheck::createView(KTextEditor::MainWindow *mainWindow) { return new PluginKateXMLCheckView(this, mainWindow); } //--------------------------------- PluginKateXMLCheckView::PluginKateXMLCheckView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainwin) : QObject(mainwin) , KXMLGUIClient() , m_mainWindow(mainwin) { KXMLGUIClient::setComponentName(QStringLiteral("katexmlcheck"), i18n("Kate XML check")); // where i18n resources? setXMLFile(QStringLiteral("ui.rc")); dock = m_mainWindow->createToolView(plugin, QStringLiteral("kate_plugin_xmlcheck_ouputview"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("misc")), i18n("XML Checker Output")); listview = new QTreeWidget(dock); m_tmp_file = nullptr; QAction *a = actionCollection()->addAction(QStringLiteral("xml_check")); a->setText(i18n("Validate XML")); connect(a, &QAction::triggered, this, &PluginKateXMLCheckView::slotValidate); // TODO?: //(void) new KAction ( i18n("Indent XML"), KShortcut(), this, // SLOT(slotIndent()), actionCollection(), "xml_indent" ); listview->setFocusPolicy(Qt::NoFocus); QStringList headers; headers << i18n("#"); headers << i18n("Line"); headers << i18n("Column"); headers << i18n("Message"); listview->setHeaderLabels(headers); listview->setRootIsDecorated(false); connect(listview, &QTreeWidget::itemClicked, this, &PluginKateXMLCheckView::slotClicked); QHeaderView *header = listview->header(); header->setSectionResizeMode(0, QHeaderView::ResizeToContents); header->setSectionResizeMode(1, QHeaderView::ResizeToContents); header->setSectionResizeMode(2, QHeaderView::ResizeToContents); /* TODO?: invalidate the listview when document has changed Kate::View *kv = application()->activeMainWindow()->activeView(); if( ! kv ) { qDebug() << "Warning: no Kate::View"; return; } connect(kv, SIGNAL(modifiedChanged()), this, SLOT(slotUpdate())); */ connect(&m_proc, static_cast(&QProcess::finished), this, &PluginKateXMLCheckView::slotProcExited); // we currently only want errors: m_proc.setProcessChannelMode(QProcess::SeparateChannels); // m_proc.setProcessChannelMode(QProcess::ForwardedChannels); // For Debugging. Do not use this. mainwin->guiFactory()->addClient(this); } PluginKateXMLCheckView::~PluginKateXMLCheckView() { m_mainWindow->guiFactory()->removeClient(this); delete m_tmp_file; delete dock; } void PluginKateXMLCheckView::slotProcExited(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); // FIXME: doesn't work correct the first time: // if( m_dockwidget->isDockBackPossible() ) { // m_dockwidget->dockBack(); // } if (exitStatus != QProcess::NormalExit) { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, QStringLiteral("1").rightJustified(4, ' ')); item->setText(3, QStringLiteral("Validate process crashed.")); listview->addTopLevelItem(item); return; } qDebug() << "slotProcExited()"; QApplication::restoreOverrideCursor(); delete m_tmp_file; QString proc_stderr = QString::fromLocal8Bit(m_proc.readAllStandardError()); m_tmp_file = nullptr; listview->clear(); uint list_count = 0; uint err_count = 0; if (!m_validating) { // no i18n here, so we don't get an ugly English<->Non-english mixup: QString msg; if (m_dtdname.isEmpty()) { msg = QStringLiteral("No DOCTYPE found, will only check well-formedness."); } else { msg = '\'' + m_dtdname + "' not found, will only check well-formedness."; } QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, QStringLiteral("1").rightJustified(4, ' ')); item->setText(3, msg); listview->addTopLevelItem(item); list_count++; } if (!proc_stderr.isEmpty()) { QStringList lines = proc_stderr.split('\n', QString::SkipEmptyParts); QString linenumber, msg; int line_count = 0; for (QStringList::Iterator it = lines.begin(); it != lines.end(); ++it) { QString line = *it; line_count++; int semicolon_1 = line.indexOf(':'); int semicolon_2 = line.indexOf(':', semicolon_1 + 1); int semicolon_3 = line.indexOf(':', semicolon_2 + 2); int caret_pos = line.indexOf('^'); if (semicolon_1 != -1 && semicolon_2 != -1 && semicolon_3 != -1) { linenumber = line.mid(semicolon_1 + 1, semicolon_2 - semicolon_1 - 1).trimmed(); linenumber = linenumber.rightJustified(6, ' '); // for sorting numbers msg = line.mid(semicolon_3 + 1, line.length() - semicolon_3 - 1).trimmed(); } else if (caret_pos != -1 || line_count == lines.size()) { // TODO: this fails if "^" occurs in the real text?! if (line_count == lines.size() && caret_pos == -1) { msg = msg + '\n' + line; } QString col = QString::number(caret_pos); if (col == QLatin1String("-1")) { col = QLatin1String(""); } err_count++; list_count++; QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, QString::number(list_count).rightJustified(4, ' ')); item->setText(1, linenumber); item->setTextAlignment(1, (item->textAlignment(1) & ~Qt::AlignHorizontal_Mask) | Qt::AlignRight); item->setText(2, col); item->setTextAlignment(2, (item->textAlignment(2) & ~Qt::AlignHorizontal_Mask) | Qt::AlignRight); item->setText(3, msg); listview->addTopLevelItem(item); } else { msg = msg + '\n' + line; } } } if (err_count == 0) { QString msg; if (m_validating) { msg = QStringLiteral("No errors found, document is valid."); // no i18n here } else { msg = QStringLiteral("No errors found, document is well-formed."); // no i18n here } QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, QString::number(list_count + 1).rightJustified(4, ' ')); item->setText(3, msg); listview->addTopLevelItem(item); } } void PluginKateXMLCheckView::slotClicked(QTreeWidgetItem *item, int column) { Q_UNUSED(column); qDebug() << "slotClicked"; if (item) { bool ok = true; uint line = item->text(1).toUInt(&ok); bool ok2 = true; uint column = item->text(2).toUInt(&ok); if (ok && ok2) { KTextEditor::View *kv = m_mainWindow->activeView(); if (!kv) return; kv->setCursorPosition(KTextEditor::Cursor(line - 1, column)); } } } void PluginKateXMLCheckView::slotUpdate() { qDebug() << "slotUpdate() (not implemented yet)"; } bool PluginKateXMLCheckView::slotValidate() { qDebug() << "slotValidate()"; m_mainWindow->showToolView(dock); m_validating = false; m_dtdname = QLatin1String(""); KTextEditor::View *kv = m_mainWindow->activeView(); if (!kv) return false; delete m_tmp_file; m_tmp_file = new QTemporaryFile(); if (!m_tmp_file->open()) { qDebug() << "Error (slotValidate()): could not create '" << m_tmp_file->fileName() << "': " << m_tmp_file->errorString(); KMessageBox::error(nullptr, i18n("Error: Could not create " "temporary file '%1'.", m_tmp_file->fileName())); delete m_tmp_file; m_tmp_file = nullptr; return false; } QTextStream s(m_tmp_file); s << kv->document()->text(); s.flush(); QString exe = QStandardPaths::findExecutable(QStringLiteral("xmllint")); if (exe.isEmpty()) { exe = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, QStringLiteral("xmllint")); } // qDebug() << "exe=" <findResource("data", "ksgmltools2/customization/catalog.xml"); // qDebug() << "catalogs: " << catalogs; // setenv("XML_CATALOG_FILES", QFile::encodeName( catalogs ).data(), 1); // } // qDebug() << "**catalogs: " << getenv("XML_CATALOG_FILES"); QStringList args; args << QStringLiteral("--noout"); // tell xmllint the working path of the document's file, if possible. // otherwise it will not find relative DTDs // I should give path to location of file, but remove filename // I can make QUrl.adjusted(rm filename).path() // or QUrl.toString(rm filename|rm schema) // Result is the same. Which variant should I choose? // QString path = kv->document()->url().adjusted(QUrl::RemoveFilename).path(); // xmllint uses space- or colon-separated path option, so spaces should be encoded to %20. It is done with EncodeSpaces. // Now what about colons in file names or paths? // This way xmllint works normally: // xmllint --noout --path "/home/user/my/with:colon/" --valid "/home/user/my/with:colon/demo-1.xml" // but because this plugin makes temp file path to file is another and this way xmllint refuses to find dtd: // xmllint --noout --path "/home/user/my/with:colon/" --valid "/tmp/kate.X23725" // As workaround we can encode ':' with %3A QString path = kv->document()->url().toString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::EncodeSpaces); path.replace(':', QLatin1String("%3A")); // because of such inconvenience with xmllint and paths, maybe switch to xmlstarlet? qDebug() << "path=" << path; if (!path.isEmpty()) { args << QStringLiteral("--path") << path; } // heuristic: assume that the doctype is in the first 10,000 bytes: QString text_start = kv->document()->text().left(10000); // remove comments before looking for doctype (as a doctype might be commented out // and needs to be ignored then): QRegExp re(""); re.setMinimal(true); text_start.remove(re); QRegExp re_doctype("fileName(); qDebug() << "m_tmp_file->fileName()=" << m_tmp_file->fileName(); m_proc.start(exe, args); qDebug() << "m_proc.program():" << m_proc.program(); // I want to see parameters qDebug() << "args=" << args; qDebug() << "exit code:" << m_proc.exitCode(); if (!m_proc.waitForStarted(-1)) { KMessageBox::error(nullptr, i18n("Error: Failed to execute xmllint. Please make " "sure that xmllint is installed. It is part of libxml2.")); return false; } QApplication::setOverrideCursor(Qt::WaitCursor); return true; } #include "plugin_katexmlcheck.moc" diff --git a/addons/xmlcheck/plugin_katexmlcheck.h b/addons/xmlcheck/plugin_katexmlcheck.h index 186f23bea..be47157f4 100644 --- a/addons/xmlcheck/plugin_katexmlcheck.h +++ b/addons/xmlcheck/plugin_katexmlcheck.h @@ -1,83 +1,83 @@ /*************************************************************************** plugin_katexmlcheck.h ------------------- begin : 2002-07-06 copyright : (C) 2002 by Daniel Naber email : daniel.naber@t-online.de ***************************************************************************/ /*************************************************************************** This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ***************************************************************************/ #ifndef PLUGIN_KATEXMLCHECK_H #define PLUGIN_KATEXMLCHECK_H #include -#include #include #include +#include -#include #include +#include #include #include class QTreeWidget; class QTreeWidgetItem; class QTemporaryFile; class QProcess; class PluginKateXMLCheckView : public QObject, public KXMLGUIClient { Q_OBJECT public: PluginKateXMLCheckView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainwin); ~PluginKateXMLCheckView() override; KTextEditor::MainWindow *m_mainWindow; QWidget *dock; public Q_SLOTS: bool slotValidate(); void slotClicked(QTreeWidgetItem *item, int column); void slotProcExited(int exitCode, QProcess::ExitStatus exitStatus); void slotUpdate(); private: QTemporaryFile *m_tmp_file; KParts::ReadOnlyPart *part; bool m_validating; QProcess m_proc; QString m_proc_stderr; QString m_dtdname; QTreeWidget *listview; }; class PluginKateXMLCheck : public KTextEditor::Plugin { Q_OBJECT public: explicit PluginKateXMLCheck(QObject *parent = nullptr, const QVariantList & = QVariantList()); ~PluginKateXMLCheck() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; }; #endif // PLUGIN_KATEXMLCHECK_H diff --git a/addons/xmltools/pseudo_dtd.h b/addons/xmltools/pseudo_dtd.h index 312132605..9b586525a 100644 --- a/addons/xmltools/pseudo_dtd.h +++ b/addons/xmltools/pseudo_dtd.h @@ -1,75 +1,75 @@ /*************************************************************************** pseudoDtd.cpp copyright : (C) 2001-2002 by Daniel Naber email : daniel.naber@t-online.de ***************************************************************************/ /*************************************************************************** This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or ( at your option ) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ***************************************************************************/ #ifndef PSEUDO_DTD_H #define PSEUDO_DTD_H +#include #include #include -#include /** * This class contains the attributes for one element. * To get ALL attributes, concatenate the two lists. */ class ElementAttributes { public: QStringList optionalAttributes; QStringList requiredAttributes; }; class PseudoDTD { public: PseudoDTD(); ~PseudoDTD(); void analyzeDTD(QString &metaDtdUrl, QString &metaDtd); QStringList allowedElements(const QString &parentElement); QStringList allowedAttributes(const QString &parentElement); QStringList attributeValues(const QString &element, const QString &attribute); QStringList entities(const QString &start); QStringList requiredAttributes(const QString &parentElement) const; protected: bool parseElements(QDomDocument *doc, QProgressDialog *progress); bool parseAttributes(QDomDocument *doc, QProgressDialog *progress); bool parseAttributeValues(QDomDocument *doc, QProgressDialog *progress); bool parseEntities(QDomDocument *doc, QProgressDialog *progress); bool m_sgmlSupport; // Entities, e.g. <"nbsp", "160"> QMap m_entityList; // Elements, e.g. <"a", ( "b", "i", "em", "strong" )> QMap m_elementsList; // Attributes e.g. <"a", ( "href", "lang", "title" )> QMap m_attributesList; // Attribute values e.g. <"td", <"align", ( "left", "right", "justify" )>> QMap> m_attributevaluesList; }; #endif // PSEUDO_DTD_H // kate: space-indent on; indent-width 4; replace-tabs on; mixed-indent off; diff --git a/kate/autotests/session_manager_test.cpp b/kate/autotests/session_manager_test.cpp index df82504be..4ff90b5cc 100644 --- a/kate/autotests/session_manager_test.cpp +++ b/kate/autotests/session_manager_test.cpp @@ -1,176 +1,176 @@ /* This file is part of the KDE project * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "session_manager_test.h" -#include "katesessionmanager.h" #include "kateapp.h" +#include "katesessionmanager.h" #include #include -#include -#include #include +#include +#include QTEST_MAIN(KateSessionManagerTest) void KateSessionManagerTest::initTestCase() { /** * init resources from our static lib */ Q_INIT_RESOURCE(kate); // we need an application object, as session loading will trigger modifications to that m_app = new KateApp(QCommandLineParser()); m_app->sessionManager()->activateAnonymousSession(); } void KateSessionManagerTest::cleanupTestCase() { delete m_app; } void KateSessionManagerTest::init() { m_tempdir = new QTemporaryDir; QVERIFY(m_tempdir->isValid()); m_manager = new KateSessionManager(this, m_tempdir->path()); } void KateSessionManagerTest::cleanup() { delete m_manager; delete m_tempdir; } void KateSessionManagerTest::basic() { QCOMPARE(m_manager->sessionsDir(), m_tempdir->path()); QCOMPARE(m_manager->sessionList().size(), 0); QVERIFY(m_manager->activateAnonymousSession()); QVERIFY(m_manager->activeSession()); } void KateSessionManagerTest::activateNewNamedSession() { const QString sessionName = QStringLiteral("hello_world"); QVERIFY(m_manager->activateSession(sessionName, false, false)); QCOMPARE(m_manager->sessionList().size(), 1); KateSession::Ptr s = m_manager->activeSession(); QCOMPARE(s->name(), sessionName); QCOMPARE(s->isAnonymous(), false); const QString sessionFile = m_tempdir->path() + QLatin1Char('/') + sessionName + QLatin1String(".katesession"); QCOMPARE(s->config()->name(), sessionFile); } void KateSessionManagerTest::anonymousSessionFile() { const QString anonfile = QDir().cleanPath(m_tempdir->path() + QLatin1String("/../anonymous.katesession")); QVERIFY(m_manager->activateAnonymousSession()); QVERIFY(m_manager->activeSession()->isAnonymous()); QCOMPARE(m_manager->activeSession()->config()->name(), anonfile); } void KateSessionManagerTest::urlizeSessionFile() { const QString sessionName = QStringLiteral("hello world/#"); m_manager->activateSession(sessionName, false, false); KateSession::Ptr s = m_manager->activeSession(); const QString sessionFile = m_tempdir->path() + QLatin1String("/hello%20world%2F%23.katesession"); QCOMPARE(s->config()->name(), sessionFile); } void KateSessionManagerTest::deleteSession() { m_manager->activateSession(QStringLiteral("foo")); KateSession::Ptr s = m_manager->activeSession(); m_manager->activateSession(QStringLiteral("bar")); QCOMPARE(m_manager->sessionList().size(), 2); m_manager->deleteSession(s); QCOMPARE(m_manager->sessionList().size(), 1); } void KateSessionManagerTest::deleteActiveSession() { m_manager->activateSession(QStringLiteral("foo")); KateSession::Ptr s = m_manager->activeSession(); QCOMPARE(m_manager->sessionList().size(), 1); m_manager->deleteSession(s); QCOMPARE(m_manager->sessionList().size(), 1); } void KateSessionManagerTest::renameSession() { m_manager->activateSession(QStringLiteral("foo")); KateSession::Ptr s = m_manager->activeSession(); QCOMPARE(m_manager->sessionList().size(), 1); const QString newName = QStringLiteral("bar"); m_manager->renameSession(s, newName); // non-collision path QCOMPARE(s->name(), newName); QCOMPARE(m_manager->sessionList().size(), 1); QCOMPARE(m_manager->sessionList().first(), s); } void KateSessionManagerTest::saveActiveSessionWithAnynomous() { QVERIFY(m_manager->activateAnonymousSession()); QVERIFY(m_manager->activeSession()->isAnonymous()); QVERIFY(m_manager->sessionList().empty()); QCOMPARE(m_manager->saveActiveSession(), true); QCOMPARE(m_manager->activeSession()->isAnonymous(), true); QCOMPARE(m_manager->activeSession()->name(), QString()); QCOMPARE(m_manager->sessionList().size(), 0); } void KateSessionManagerTest::deletingSessionFilesUnderRunningApp() { m_manager->activateSession(QStringLiteral("foo")); m_manager->activateSession(QStringLiteral("bar")); QVERIFY(m_manager->sessionList().size() == 2); QVERIFY(m_manager->activeSession()->name() == QLatin1String("bar")); const QString file = m_tempdir->path() + QLatin1String("/foo.katesession"); QVERIFY(QFile(file).remove()); QTRY_COMPARE_WITH_TIMEOUT(m_manager->sessionList().size(), 1, 1000); // that should be enough for KDirWatch to kick in QCOMPARE(m_manager->activeSession()->name(), QLatin1String("bar")); } void KateSessionManagerTest::startNonEmpty() { m_manager->activateSession(QStringLiteral("foo")); m_manager->activateSession(QStringLiteral("bar")); KateSessionManager m(this, m_tempdir->path()); QCOMPARE(m.sessionList().size(), 2); } diff --git a/kate/autotests/session_test.cpp b/kate/autotests/session_test.cpp index 65164dd29..36a8132b8 100644 --- a/kate/autotests/session_test.cpp +++ b/kate/autotests/session_test.cpp @@ -1,151 +1,151 @@ /* This file is part of the KDE project * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "session_test.h" #include "katesession.h" #include #include -#include -#include #include +#include +#include QTEST_MAIN(KateSessionTest) void KateSessionTest::initTestCase() { } void KateSessionTest::cleanupTestCase() { } void KateSessionTest::init() { m_tmpfile = new QTemporaryFile; QVERIFY(m_tmpfile->open()); } void KateSessionTest::cleanup() { delete m_tmpfile; } void KateSessionTest::create() { const QString name = QStringLiteral("session name"); KateSession::Ptr s = KateSession::create(m_tmpfile->fileName(), name); QCOMPARE(s->name(), name); QCOMPARE((int)s->documents(), 0); QCOMPARE(s->isAnonymous(), false); QCOMPARE(s->config()->name(), m_tmpfile->fileName()); } void KateSessionTest::createAnonymous() { KateSession::Ptr s = KateSession::createAnonymous(m_tmpfile->fileName()); QCOMPARE(s->name(), QString()); QCOMPARE((int)s->documents(), 0); QCOMPARE(s->isAnonymous(), true); QCOMPARE(s->config()->name(), m_tmpfile->fileName()); } void KateSessionTest::createAnonymousFrom() { // Regular KateSession::Ptr s = KateSession::create(m_tmpfile->fileName(), QStringLiteral("session name")); const QString groupName = QStringLiteral("test group"); QTemporaryFile newFile; newFile.open(); KateSession::Ptr ns; s->config()->group(groupName).writeEntry("foo", "bar"); // Create Anon from Other ns = KateSession::createAnonymousFrom(s, newFile.fileName()); QCOMPARE(ns->name(), QString()); QCOMPARE((int)ns->documents(), 0); QCOMPARE(ns->isAnonymous(), true); QCOMPARE(ns->config()->name(), newFile.fileName()); QCOMPARE(ns->config()->group(groupName).readEntry("foo"), QLatin1String("bar")); } void KateSessionTest::createFrom() { KateSession::Ptr s = KateSession::create(m_tmpfile->fileName(), QStringLiteral("session name")); const QString newName = QStringLiteral("new session name"); const QString groupName = QStringLiteral("test group"); QTemporaryFile newFile; newFile.open(); KateSession::Ptr ns; s->config()->group(groupName).writeEntry("foo", "bar"); ns = KateSession::createFrom(s, newFile.fileName(), newName); QCOMPARE(ns->name(), newName); QCOMPARE((int)ns->documents(), 0); QCOMPARE(ns->isAnonymous(), false); QCOMPARE(ns->config()->name(), newFile.fileName()); QCOMPARE(ns->config()->group(groupName).readEntry("foo"), QLatin1String("bar")); } void KateSessionTest::documents() { KateSession::Ptr s = KateSession::create(m_tmpfile->fileName(), QStringLiteral("session name")); s->setDocuments(42); QCOMPARE((int)s->documents(), 42); s->config()->sync(); KConfig c(m_tmpfile->fileName()); QCOMPARE(c.group("Open Documents").readEntry("Count", 0), 42); } void KateSessionTest::setFile() { KateSession::Ptr s = KateSession::create(m_tmpfile->fileName(), QStringLiteral("session name")); s->config()->group("test").writeEntry("foo", "bar"); QTemporaryFile file2; file2.open(); s->setFile(file2.fileName()); QCOMPARE(s->config()->name(), file2.fileName()); QCOMPARE(s->config()->group("test").readEntry("foo"), QLatin1String("bar")); } void KateSessionTest::timestamp() { QFileInfo i(m_tmpfile->fileName()); KateSession::Ptr s = KateSession::create(m_tmpfile->fileName(), QStringLiteral("session name")); QCOMPARE(s->timestamp(), i.lastModified()); } void KateSessionTest::setName() { KateSession::Ptr s = KateSession::create(m_tmpfile->fileName(), QStringLiteral("session name")); const QString newName = QStringLiteral("bar"); s->setName(newName); QCOMPARE(s->name(), newName); QCOMPARE(s->file(), m_tmpfile->fileName()); // on purpose, orthogonal } diff --git a/kate/autotests/sessions_action_test.cpp b/kate/autotests/sessions_action_test.cpp index 9b1a5f497..03d7f9c38 100644 --- a/kate/autotests/sessions_action_test.cpp +++ b/kate/autotests/sessions_action_test.cpp @@ -1,103 +1,103 @@ /* This file is part of the KDE project * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "sessions_action_test.h" -#include "katesessionsaction.h" -#include "katesessionmanager.h" #include "kateapp.h" +#include "katesessionmanager.h" +#include "katesessionsaction.h" -#include -#include #include #include +#include +#include QTEST_MAIN(KateSessionsActionTest) void KateSessionsActionTest::initTestCase() { /** * init resources from our static lib */ Q_INIT_RESOURCE(kate); // we need an application object, as session loading will trigger modifications to that m_app = new KateApp(QCommandLineParser()); m_app->sessionManager()->activateAnonymousSession(); } void KateSessionsActionTest::cleanupTestCase() { delete m_app; } void KateSessionsActionTest::init() { m_tempdir = new QTemporaryDir; QVERIFY(m_tempdir->isValid()); m_manager = new KateSessionManager(this, m_tempdir->path()); m_ac = new KateSessionsAction(QStringLiteral("menu"), this, m_manager); } void KateSessionsActionTest::cleanup() { delete m_ac; delete m_manager; delete m_tempdir; } void KateSessionsActionTest::basic() { m_ac->slotAboutToShow(); QCOMPARE(m_ac->isEnabled(), false); QList actions = m_ac->sessionsGroup->actions(); QCOMPARE(actions.size(), 0); } void KateSessionsActionTest::limit() { for (int i = 0; i < 14; i++) { m_manager->activateSession(QStringLiteral("session %1").arg(i)); } QCOMPARE(m_manager->activeSession()->isAnonymous(), false); QCOMPARE(m_manager->sessionList().size(), 14); QCOMPARE(m_ac->isEnabled(), true); m_ac->slotAboutToShow(); QList actions = m_ac->sessionsGroup->actions(); QCOMPARE(actions.size(), 10); } void KateSessionsActionTest::timesorted() { /* m_manager->activateSession("one", false, false); m_manager->saveActiveSession(); m_manager->activateSession("two", false, false); m_manager->saveActiveSession(); m_manager->activateSession("three", false, false); m_manager->saveActiveSession(); KateSessionList list = m_manager->sessionList(); QCOMPARE(list.size(), 3); TODO: any idea how to test this simply and not calling utime()? */ } diff --git a/kate/kateapp.cpp b/kate/kateapp.cpp index ab9780f96..3ceb20771 100644 --- a/kate/kateapp.cpp +++ b/kate/kateapp.cpp @@ -1,482 +1,482 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger 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. */ #include "kateapp.h" -#include "kateviewmanager.h" #include "katemainwindow.h" +#include "kateviewmanager.h" #include -#include +#include +#include +#include #include +#include #include -#include -#include -#include #include +#include #include #include -#include -#include #include +#include #include "../../urlinfo.h" /** * singleton instance pointer */ static KateApp *appSelf = Q_NULLPTR; Q_LOGGING_CATEGORY(LOG_KATE, "kate", QtWarningMsg) KateApp::KateApp(const QCommandLineParser &args) : m_args(args) , m_wrapper(appSelf = this) , m_docManager(this) , m_adaptor(this) , m_pluginManager(this) , m_sessionManager(this) { /** * re-route some signals to application wrapper */ connect(&m_docManager, &KateDocManager::documentCreated, &m_wrapper, &KTextEditor::Application::documentCreated); connect(&m_docManager, &KateDocManager::documentWillBeDeleted, &m_wrapper, &KTextEditor::Application::documentWillBeDeleted); connect(&m_docManager, &KateDocManager::documentDeleted, &m_wrapper, &KTextEditor::Application::documentDeleted); connect(&m_docManager, &KateDocManager::aboutToCreateDocuments, &m_wrapper, &KTextEditor::Application::aboutToCreateDocuments); connect(&m_docManager, &KateDocManager::documentsCreated, &m_wrapper, &KTextEditor::Application::documentsCreated); /** * handle mac os x like file open request via event filter */ qApp->installEventFilter(this); } KateApp::~KateApp() { /** * unregister from dbus before we get unusable... */ if (QDBusConnection::sessionBus().interface()) { m_adaptor.emitExiting(); QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/MainApplication")); } /** * delete all main windows before the document manager & co. die */ while (!m_mainWindows.isEmpty()) { // mainwindow itself calls KateApp::removeMainWindow(this) delete m_mainWindows[0]; } } KateApp *KateApp::self() { return appSelf; } bool KateApp::init() { // set KATE_PID for use in child processes qputenv("KATE_PID", QStringLiteral("%1").arg(QCoreApplication::applicationPid()).toLatin1().constData()); // handle restore different if (qApp->isSessionRestored()) { restoreKate(); } else { // let us handle our command line args and co ;) // we can exit here if session chooser decides if (!startupKate()) { // session chooser told to exit kate return false; } } return true; } void KateApp::restoreKate() { KConfig *sessionConfig = KConfigGui::sessionConfig(); // activate again correct session!!! QString lastSession(sessionConfig->group("General").readEntry("Last Session", QString())); sessionManager()->activateSession(lastSession, false, false); // plugins KateApp::self()->pluginManager()->loadConfig(sessionConfig); // restore the files we need m_docManager.restoreDocumentList(sessionConfig); // restore all windows ;) for (int n = 1; KMainWindow::canBeRestored(n); n++) { newMainWindow(sessionConfig, QString::number(n)); } // oh, no mainwindow, create one, should not happen, but make sure ;) if (mainWindowsCount() == 0) { newMainWindow(); } } bool KateApp::startupKate() { // user specified session to open if (m_args.isSet(QStringLiteral("start"))) { sessionManager()->activateSession(m_args.value(QStringLiteral("start")), false); } else if (m_args.isSet(QStringLiteral("startanon"))) { sessionManager()->activateAnonymousSession(); } else if (!m_args.isSet(QStringLiteral("stdin")) && (m_args.positionalArguments().count() == 0)) { // only start session if no files specified // let the user choose session if possible if (!sessionManager()->chooseSession()) { // we will exit kate now, notify the rest of the world we are done KStartupInfo::appStarted(KStartupInfo::startupId()); return false; } } else { sessionManager()->activateAnonymousSession(); } // oh, no mainwindow, create one, should not happen, but make sure ;) if (mainWindowsCount() == 0) { newMainWindow(); } // notify about start KStartupInfo::setNewStartupId(activeKateMainWindow(), KStartupInfo::startupId()); QTextCodec *codec = m_args.isSet(QStringLiteral("encoding")) ? QTextCodec::codecForName(m_args.value(QStringLiteral("encoding")).toUtf8()) : nullptr; bool tempfileSet = m_args.isSet(QStringLiteral("tempfile")); KTextEditor::Document *doc = nullptr; const QString codec_name = codec ? QString::fromLatin1(codec->name()) : QString(); // Bug 397913: Reverse the order here so the new tabs are opened in same order as the files were passed in on the command line QString positionalArgument; for (int i = m_args.positionalArguments().count() - 1; i >= 0; --i) { positionalArgument = m_args.positionalArguments().at(i); UrlInfo info(positionalArgument); // this file is no local dir, open it, else warn bool noDir = !info.url.isLocalFile() || !QFileInfo(info.url.toLocalFile()).isDir(); if (noDir) { doc = openDocUrl(info.url, codec_name, tempfileSet); if (info.cursor.isValid()) { setCursor(info.cursor.line(), info.cursor.column()); } else if (info.url.hasQuery()) { QUrlQuery q(info.url); QString lineStr = q.queryItemValue(QStringLiteral("line")); QString columnStr = q.queryItemValue(QStringLiteral("column")); int line = lineStr.toInt(); if (line > 0) line--; int column = columnStr.toInt(); if (column > 0) column--; setCursor(line, column); } } else { KMessageBox::sorry(activeKateMainWindow(), i18n("The file '%1' could not be opened: it is not a normal file, it is a folder.", info.url.toString())); } } // handle stdin input if (m_args.isSet(QStringLiteral("stdin"))) { QTextStream input(stdin, QIODevice::ReadOnly); // set chosen codec if (codec) { input.setCodec(codec); } QString line; QString text; do { line = input.readLine(); text.append(line + QLatin1Char('\n')); } while (!line.isNull()); openInput(text, codec_name); } else if (doc) { activeKateMainWindow()->viewManager()->activateView(doc); } int line = 0; int column = 0; bool nav = false; if (m_args.isSet(QStringLiteral("line"))) { line = m_args.value(QStringLiteral("line")).toInt() - 1; nav = true; } if (m_args.isSet(QStringLiteral("column"))) { column = m_args.value(QStringLiteral("column")).toInt() - 1; nav = true; } if (nav && activeKateMainWindow()->viewManager()->activeView()) { activeKateMainWindow()->viewManager()->activeView()->setCursorPosition(KTextEditor::Cursor(line, column)); } activeKateMainWindow()->setAutoSaveSettings(); return true; } void KateApp::shutdownKate(KateMainWindow *win) { if (!win->queryClose_internal()) { return; } sessionManager()->saveActiveSession(true); /** * all main windows will be cleaned up * in the KateApp destructor after the event * loop is left */ QApplication::quit(); } KatePluginManager *KateApp::pluginManager() { return &m_pluginManager; } KateDocManager *KateApp::documentManager() { return &m_docManager; } KateSessionManager *KateApp::sessionManager() { return &m_sessionManager; } bool KateApp::openUrl(const QUrl &url, const QString &encoding, bool isTempFile) { return openDocUrl(url, encoding, isTempFile); } bool KateApp::isOnActivity(const QString &activity) { for (const auto &window : m_mainWindows) { const KWindowInfo info(window->winId(), nullptr, NET::WM2Activities); const auto activities = info.activities(); // handle special case of "on all activities" if (activities.isEmpty() || activities.contains(activity)) return true; } return false; } KTextEditor::Document *KateApp::openDocUrl(const QUrl &url, const QString &encoding, bool isTempFile) { KateMainWindow *mainWindow = activeKateMainWindow(); if (!mainWindow) { return nullptr; } QTextCodec *codec = encoding.isEmpty() ? nullptr : QTextCodec::codecForName(encoding.toLatin1()); // this file is no local dir, open it, else warn bool noDir = !url.isLocalFile() || !QFileInfo(url.toLocalFile()).isDir(); KTextEditor::Document *doc = nullptr; if (noDir) { // open a normal file if (codec) { doc = mainWindow->viewManager()->openUrl(url, QString::fromLatin1(codec->name()), true, isTempFile); } else { doc = mainWindow->viewManager()->openUrl(url, QString(), true, isTempFile); } } else KMessageBox::sorry(mainWindow, i18n("The file '%1' could not be opened: it is not a normal file, it is a folder.", url.url())); return doc; } bool KateApp::setCursor(int line, int column) { KateMainWindow *mainWindow = activeKateMainWindow(); if (!mainWindow) { return false; } if (auto v = mainWindow->viewManager()->activeView()) { v->removeSelection(); v->setCursorPosition(KTextEditor::Cursor(line, column)); } return true; } bool KateApp::openInput(const QString &text, const QString &encoding) { activeKateMainWindow()->viewManager()->openUrl(QUrl(), encoding, true); if (!activeKateMainWindow()->viewManager()->activeView()) { return false; } KTextEditor::Document *doc = activeKateMainWindow()->viewManager()->activeView()->document(); if (!doc) { return false; } return doc->setText(text); } KateMainWindow *KateApp::newMainWindow(KConfig *sconfig_, const QString &sgroup_) { KConfig *sconfig = sconfig_ ? sconfig_ : KSharedConfig::openConfig().data(); QString sgroup = !sgroup_.isEmpty() ? sgroup_ : QStringLiteral("MainWindow0"); KateMainWindow *mainWindow = new KateMainWindow(sconfig, sgroup); mainWindow->show(); return mainWindow; } void KateApp::addMainWindow(KateMainWindow *mainWindow) { m_mainWindows.push_back(mainWindow); } void KateApp::removeMainWindow(KateMainWindow *mainWindow) { m_mainWindows.removeAll(mainWindow); } KateMainWindow *KateApp::activeKateMainWindow() { if (m_mainWindows.isEmpty()) { return nullptr; } int n = m_mainWindows.indexOf(static_cast((static_cast(QCoreApplication::instance())->activeWindow()))); if (n < 0) { n = 0; } return m_mainWindows[n]; } int KateApp::mainWindowsCount() const { return m_mainWindows.size(); } int KateApp::mainWindowID(KateMainWindow *window) { return m_mainWindows.indexOf(window); } KateMainWindow *KateApp::mainWindow(int n) { if (n < m_mainWindows.size()) { return m_mainWindows[n]; } return nullptr; } void KateApp::emitDocumentClosed(const QString &token) { m_adaptor.emitDocumentClosed(token); } KTextEditor::Plugin *KateApp::plugin(const QString &name) { return m_pluginManager.plugin(name); } bool KateApp::eventFilter(QObject *obj, QEvent *event) { /** * handle mac os like file open */ if (event->type() == QEvent::FileOpen) { /** * try to open and activate the new document, like we would do for stuff * opened via dbus */ QFileOpenEvent *foe = static_cast(event); KTextEditor::Document *doc = openDocUrl(foe->url(), QString(), false); if (doc && activeKateMainWindow()) { activeKateMainWindow()->viewManager()->activateView(doc); } return true; } /** * else: pass over to default implementation */ return QObject::eventFilter(obj, event); } void KateApp::remoteMessageReceived(const QString &message, QObject *) { /** * try to parse message, ignore if no object */ const QJsonDocument jsonMessage = QJsonDocument::fromJson(message.toUtf8()); if (!jsonMessage.isObject()) return; /** * open all passed urls */ const QJsonArray urls = jsonMessage.object().value(QLatin1String("urls")).toArray(); for (QJsonValue urlObject : urls) { /** * get url meta data */ const QUrl url = urlObject.toObject().value(QLatin1String("url")).toVariant().toUrl(); const int line = urlObject.toObject().value(QLatin1String("line")).toVariant().toInt(); const int column = urlObject.toObject().value(QLatin1String("column")).toVariant().toInt(); /** * open file + set line/column if requested */ openUrl(url, QString(), false); if (line >= 0 && column >= 0) { setCursor(line, column); } } // try to activate current window m_adaptor.activate(); } diff --git a/kate/kateapp.h b/kate/kateapp.h index 815457628..8983a72a7 100644 --- a/kate/kateapp.h +++ b/kate/kateapp.h @@ -1,384 +1,384 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef __KATE_APP_H__ #define __KATE_APP_H__ #include -#include "katetests_export.h" -#include "katemainwindow.h" +#include "kateappadaptor.h" #include "katedocmanager.h" +#include "katemainwindow.h" #include "katepluginmanager.h" #include "katesessionmanager.h" -#include "kateappadaptor.h" +#include "katetests_export.h" #include #include class KateSessionManager; class KateMainWindow; class KatePluginManager; class KateDocManager; class KateAppCommands; class KateAppAdaptor; class QCommandLineParser; /** * Kate Application * This class represents the core kate application object */ class KATE_TESTS_EXPORT KateApp : public QObject { Q_OBJECT /** * constructors & accessor to app object + plugin interface for it */ public: /** * application constructor */ KateApp(const QCommandLineParser &args); /** * get kate inited * @return false, if application should exit */ bool init(); /** * application destructor */ ~KateApp() override; /** * static accessor to avoid casting ;) * @return app instance */ static KateApp *self(); /** * KTextEditor::Application wrapper * @return KTextEditor::Application wrapper. */ KTextEditor::Application *wrapper() { return &m_wrapper; } /** * kate init */ private: /** * restore a old kate session */ void restoreKate(); /** * try to start kate * @return success, if false, kate should exit */ bool startupKate(); /** * kate shutdown */ public: /** * shutdown kate application * @param win mainwindow which is used for dialogs */ void shutdownKate(KateMainWindow *win); /** * other accessors for global unique instances */ public: /** * accessor to plugin manager * @return plugin manager instance */ KatePluginManager *pluginManager(); /** * accessor to document manager * @return document manager instance */ KateDocManager *documentManager(); /** * accessor to session manager * @return session manager instance */ KateSessionManager *sessionManager(); /** * window management */ public: /** * create a new main window, use given config if any for restore * @param sconfig session config object * @param sgroup session group for this window * @return new constructed main window */ KateMainWindow *newMainWindow(KConfig *sconfig = nullptr, const QString &sgroup = QString()); /** * add the mainwindow given * should be called in mainwindow constructor * @param mainWindow window to remove */ void addMainWindow(KateMainWindow *mainWindow); /** * removes the mainwindow given, DOES NOT DELETE IT * should be called in mainwindow destructor * @param mainWindow window to remove */ void removeMainWindow(KateMainWindow *mainWindow); /** * give back current active main window * can only be 0 at app start or exit * @return current active main window */ KateMainWindow *activeKateMainWindow(); /** * give back number of existing main windows * @return number of main windows */ int mainWindowsCount() const; /** * give back the window you want * @param n window index * @return requested main window */ KateMainWindow *mainWindow(int n); int mainWindowID(KateMainWindow *window); /** * some stuff for the dcop API */ public: /** * open url with given encoding * used by kate if --use given * @param url filename * @param encoding encoding name * @param isTempFile whether the file is temporary * @return success */ bool openUrl(const QUrl &url, const QString &encoding, bool isTempFile); /** * checks if the current instance is in a given activity * @param activity activity to check * @return true if the window is in the given activity, false otherwise */ bool isOnActivity(const QString &activity); KTextEditor::Document *openDocUrl(const QUrl &url, const QString &encoding, bool isTempFile); void emitDocumentClosed(const QString &token); /** * position cursor in current active view * will clear selection * @param line line to set * @param column column to set * @return success */ bool setCursor(int line, int column); /** * helper to handle stdin input * open a new document/view, fill it with the text given * @param text text to fill in the new doc/view * @param encoding encoding to set for the document, if any * @return success */ bool openInput(const QString &text, const QString &encoding); // // KTextEditor::Application interface, called by wrappers via invokeMethod // public Q_SLOTS: /** * Get a list of all main windows. * @return all main windows */ QList mainWindows() { // assemble right list QList windows; windows.reserve(m_mainWindows.size()); for (const auto mainWindow : qAsConst(m_mainWindows)) { windows.push_back(mainWindow->wrapper()); } return windows; } /** * Accessor to the active main window. * \return a pointer to the active mainwindow */ KTextEditor::MainWindow *activeMainWindow() { // either return wrapper or nullptr if (KateMainWindow *a = activeKateMainWindow()) { return a->wrapper(); } return nullptr; } /** * Get a list of all documents that are managed by the application. * This might contain less documents than the editor has in his documents () list. * @return all documents the application manages */ QList documents() { return m_docManager.documentList(); } /** * Get the document with the URL \p url. * if multiple documents match the searched url, return the first found one... * \param url the document's URL * \return the document with the given \p url or NULL, if none found */ KTextEditor::Document *findUrl(const QUrl &url) { return m_docManager.findDocument(url); } /** * Open the document \p url with the given \p encoding. * if the url is empty, a new empty document will be created * \param url the document's url * \param encoding the preferred encoding. If encoding is QString() the * encoding will be guessed or the default encoding will be used. * \return a pointer to the created document */ KTextEditor::Document *openUrl(const QUrl &url, const QString &encoding = QString()) { return m_docManager.openUrl(url, encoding); } /** * Close the given \p document. If the document is modified, user will be asked if he wants that. * \param document the document to be closed * \return \e true on success, otherwise \e false */ bool closeDocument(KTextEditor::Document *document) { return m_docManager.closeDocument(document); } /** * Close a list of documents. If any of them are modified, user will be asked if he wants that. * Use this, if you want to close multiple documents at once, as the application might * be able to group the "do you really want that" dialogs into one. * \param documents list of documents to be closed * \return \e true on success, otherwise \e false */ bool closeDocuments(const QList &documents) { return m_docManager.closeDocumentList(documents); } /** * Get a plugin for the plugin with with identifier \p name. * \param name the plugin's name * \return pointer to the plugin if a plugin with \p name is loaded, otherwise nullptr */ KTextEditor::Plugin *plugin(const QString &name); /** * Ask app to quit. The app might interact with the user and decide that * quitting is not possible and return false. * * \return true if the app could quit */ bool quit() { shutdownKate(activeKateMainWindow()); return true; } /** * A message is received from an external instance, if we use QtSingleApplication * * \p message is a serialized message (at the moment just the file list separated by ';') * \p socket is the QLocalSocket used for the communication */ void remoteMessageReceived(const QString &message, QObject *socket); protected: /** * Event filter for QApplication to handle mac os like file open */ bool eventFilter(QObject *obj, QEvent *event) override; private: /** * kate's command line args */ const QCommandLineParser &m_args; /** * known main windows */ QList m_mainWindows; /** * Wrapper of application for KTextEditor */ KTextEditor::Application m_wrapper; /** * document manager */ KateDocManager m_docManager; /** * dbus interface */ KateAppAdaptor m_adaptor; /** * plugin manager */ KatePluginManager m_pluginManager; /** * session manager */ KateSessionManager m_sessionManager; }; #endif diff --git a/kate/kateappadaptor.cpp b/kate/kateappadaptor.cpp index b65902bf7..860f72348 100644 --- a/kate/kateappadaptor.cpp +++ b/kate/kateappadaptor.cpp @@ -1,143 +1,143 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann 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. */ #include "kateappadaptor.h" #include "kateapp.h" -#include "katesessionmanager.h" #include "katedocmanager.h" #include "katemainwindow.h" +#include "katesessionmanager.h" #include "katedebug.h" #include #include #include /** * add the adapter to the global application instance to have * it auto-register with KDBusService, see bug 410742 */ KateAppAdaptor::KateAppAdaptor(KateApp *app) : QDBusAbstractAdaptor(qApp) , m_app(app) { } void KateAppAdaptor::activate() { KateMainWindow *win = m_app->activeKateMainWindow(); if (!win) { return; } // like QtSingleApplication win->setWindowState(win->windowState() & ~Qt::WindowMinimized); win->raise(); win->activateWindow(); // try to raise window, see bug 407288 KStartupInfo::setNewStartupId(win, KStartupInfo::startupId()); KWindowSystem::activateWindow(win->effectiveWinId()); } bool KateAppAdaptor::openUrl(const QString &url, const QString &encoding) { return m_app->openUrl(QUrl(url), encoding, false); } bool KateAppAdaptor::openUrl(const QString &url, const QString &encoding, bool isTempFile) { qCDebug(LOG_KATE) << "openURL"; return m_app->openUrl(QUrl(url), encoding, isTempFile); } bool KateAppAdaptor::isOnActivity(const QString &activity) { return m_app->isOnActivity(activity); } //----------- QString KateAppAdaptor::tokenOpenUrl(const QString &url, const QString &encoding) { KTextEditor::Document *doc = m_app->openDocUrl(QUrl(url), encoding, false); if (!doc) { return QStringLiteral("ERROR"); } return QStringLiteral("%1").arg((qptrdiff)doc); } QString KateAppAdaptor::tokenOpenUrl(const QString &url, const QString &encoding, bool isTempFile) { qCDebug(LOG_KATE) << "openURL"; KTextEditor::Document *doc = m_app->openDocUrl(QUrl(url), encoding, isTempFile); if (!doc) { return QStringLiteral("ERROR"); } return QStringLiteral("%1").arg((qptrdiff)doc); } QString KateAppAdaptor::tokenOpenUrlAt(const QString &url, int line, int column, const QString &encoding, bool isTempFile) { qCDebug(LOG_KATE) << "openURLAt"; KTextEditor::Document *doc = m_app->openDocUrl(QUrl(url), encoding, isTempFile); if (!doc) { return QStringLiteral("ERROR"); } m_app->setCursor(line, column); return QStringLiteral("%1").arg((qptrdiff)doc); } //-------- bool KateAppAdaptor::setCursor(int line, int column) { return m_app->setCursor(line, column); } bool KateAppAdaptor::openInput(const QString &text, const QString &encoding) { return m_app->openInput(text, encoding); } bool KateAppAdaptor::activateSession(const QString &session) { return m_app->sessionManager()->activateSession(session); } int KateAppAdaptor::desktopNumber() { KWindowInfo appInfo(m_app->activeKateMainWindow()->winId(), NET::WMDesktop); return appInfo.desktop(); } QString KateAppAdaptor::activeSession() { return m_app->sessionManager()->activeSession()->name(); } void KateAppAdaptor::emitExiting() { emit exiting(); } void KateAppAdaptor::emitDocumentClosed(const QString &token) { documentClosed(token); } diff --git a/kate/katecolorschemechooser.cpp b/kate/katecolorschemechooser.cpp index 69d59e4a4..f77bfefe8 100644 --- a/kate/katecolorschemechooser.cpp +++ b/kate/katecolorschemechooser.cpp @@ -1,100 +1,100 @@ /************************************************************************************* * This file is part of KDevPlatform * * Copyright 2016 Zhigalin Alexander * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) version 3, or any * * later version accepted by the membership of KDE e.V. (or its * * successor approved by the membership of KDE e.V.), which shall * * act as a proxy defined in Section 6 of version 3 of the license. * * * * 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library. If not, see . * *************************************************************************************/ #include "katecolorschemechooser.h" -#include -#include +#include #include #include -#include -#include #include +#include #include #include +#include +#include +#include #include #include +#include #include -#include #include -#include -#include "katemainwindow.h" #include "katedebug.h" +#include "katemainwindow.h" KateColorSchemeChooser::KateColorSchemeChooser(QObject *parent) : QAction(parent) { auto manager = new KColorSchemeManager(parent); const auto scheme(currentSchemeName()); qCDebug(LOG_KATE) << "Color scheme : " << scheme; auto selectionMenu = manager->createSchemeSelectionMenu(scheme, this); connect(selectionMenu->menu(), &QMenu::triggered, this, &KateColorSchemeChooser::slotSchemeChanged); manager->activateScheme(manager->indexForScheme(scheme)); setMenu(selectionMenu->menu()); menu()->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-color"))); menu()->setTitle(i18n("&Color Theme")); } QString KateColorSchemeChooser::loadCurrentScheme() const { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup cg(config, "UiSettings"); return cg.readEntry("ColorScheme", currentDesktopDefaultScheme()); } void KateColorSchemeChooser::saveCurrentScheme(const QString &name) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup cg(config, "UiSettings"); cg.writeEntry("ColorScheme", name); cg.sync(); } QString KateColorSchemeChooser::currentDesktopDefaultScheme() const { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kdeglobals")); KConfigGroup group(config, "General"); return group.readEntry("ColorScheme", QStringLiteral("Breeze")); } QString KateColorSchemeChooser::currentSchemeName() const { if (!menu()) return loadCurrentScheme(); QAction *const action = menu()->activeAction(); if (action) return KLocalizedString::removeAcceleratorMarker(action->text()); return currentDesktopDefaultScheme(); } void KateColorSchemeChooser::slotSchemeChanged(QAction *triggeredAction) { saveCurrentScheme(KLocalizedString::removeAcceleratorMarker(triggeredAction->text())); } diff --git a/kate/katecolorschemechooser.h b/kate/katecolorschemechooser.h index d056924ec..4194c348d 100644 --- a/kate/katecolorschemechooser.h +++ b/kate/katecolorschemechooser.h @@ -1,54 +1,54 @@ /************************************************************************************* * This file is part of KDevPlatform * * Copyright 2016 Zhigalin Alexander * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) version 3, or any * * later version accepted by the membership of KDE e.V. (or its * * successor approved by the membership of KDE e.V.), which shall * * act as a proxy defined in Section 6 of version 3 of the license. * * * * 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library. If not, see . * *************************************************************************************/ #ifndef COLORSCHEMECHOOSER_H #define COLORSCHEMECHOOSER_H -#include #include #include +#include #include #include class KActionCollection; /** * Provides a menu that will offer to change the color scheme * * Furthermore, it will save the selection in the user configuration. */ class KateColorSchemeChooser : public QAction { public: KateColorSchemeChooser(QObject *parent); QString currentSchemeName() const; private Q_SLOTS: void slotSchemeChanged(QAction *triggeredAction); private: QString loadCurrentScheme() const; void saveCurrentScheme(const QString &name); QString currentDesktopDefaultScheme() const; }; #endif // COLORSCHEMECHOOSER_H diff --git a/kate/kateconfigdialog.cpp b/kate/kateconfigdialog.cpp index d515059aa..d30933239 100644 --- a/kate/kateconfigdialog.cpp +++ b/kate/kateconfigdialog.cpp @@ -1,460 +1,460 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2007 Mirko Stocker 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. */ #include "kateconfigdialog.h" #include "ui_sessionconfigwidget.h" -#include "katemainwindow.h" -#include "katedocmanager.h" -#include "katepluginmanager.h" -#include "kateconfigplugindialogpage.h" -#include "kateviewmanager.h" #include "kateapp.h" -#include "katesessionmanager.h" +#include "kateconfigplugindialogpage.h" #include "katedebug.h" +#include "katedocmanager.h" +#include "katemainwindow.h" +#include "katepluginmanager.h" #include "katequickopenmodel.h" +#include "katesessionmanager.h" +#include "kateviewmanager.h" #include -#include +#include #include #include -#include -#include #include +#include +#include -#include #include +#include +#include #include #include #include #include #include -#include KateConfigDialog::KateConfigDialog(KateMainWindow *parent, KTextEditor::View *view) : KPageDialog(parent) , m_mainWindow(parent) , m_view(view) { setFaceType(Tree); setWindowTitle(i18n("Configure")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::Help); setObjectName(QStringLiteral("configdialog")); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup cgGeneral = KConfigGroup(config, "General"); buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); KPageWidgetItem *applicationItem = addPage(new QWidget, i18n("Application")); applicationItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); applicationItem->setHeader(i18n("Application Options")); applicationItem->setCheckable(false); applicationItem->setEnabled(false); m_applicationPage = applicationItem; // BEGIN General page QFrame *generalFrame = new QFrame; KPageWidgetItem *item = addSubPage(applicationItem, generalFrame, i18n("General")); item->setHeader(i18n("General Options")); item->setIcon(QIcon::fromTheme(QStringLiteral("go-home"))); setCurrentPage(item); QVBoxLayout *layout = new QVBoxLayout(generalFrame); layout->setContentsMargins(0, 0, 0, 0); // GROUP with the one below: "Behavior" QGroupBox *buttonGroup = new QGroupBox(i18n("&Behavior"), generalFrame); QVBoxLayout *vbox = new QVBoxLayout; layout->addWidget(buttonGroup); // modified files notification m_modNotifications = new QCheckBox(i18n("Wa&rn about files modified by foreign processes"), buttonGroup); m_modNotifications->setChecked(parent->modNotificationEnabled()); m_modNotifications->setWhatsThis( i18n("If enabled, when Kate receives focus you will be asked what to do with " "files that have been modified on the hard disk. If not enabled, you will " "be asked what to do with a file that has been modified on the hard disk only " "when that file is tried to be saved.")); connect(m_modNotifications, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); vbox->addWidget(m_modNotifications); // Closing last file closes Kate m_modCloseAfterLast = new QCheckBox(i18n("Close Kate entirely when the last file is closed"), buttonGroup); m_modCloseAfterLast->setChecked(parent->modCloseAfterLast()); m_modCloseAfterLast->setWhatsThis( i18n("If enabled, Kate will shutdown when the last file being edited is closed, " "otherwise a blank page will open so that you can start a new file.")); connect(m_modCloseAfterLast, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); vbox->addWidget(m_modCloseAfterLast); buttonGroup->setLayout(vbox); // GROUP with the one below: "Meta-information" buttonGroup = new QGroupBox(i18n("Meta-Information"), generalFrame); vbox = new QVBoxLayout; layout->addWidget(buttonGroup); // save meta infos m_saveMetaInfos = new QCheckBox(buttonGroup); m_saveMetaInfos->setText(i18n("Keep &meta-information past sessions")); m_saveMetaInfos->setChecked(KateApp::self()->documentManager()->getSaveMetaInfos()); m_saveMetaInfos->setWhatsThis( i18n("Check this if you want document configuration like for example " "bookmarks to be saved past editor sessions. The configuration will be " "restored if the document has not changed when reopened.")); connect(m_saveMetaInfos, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); vbox->addWidget(m_saveMetaInfos); // meta infos days QFrame *metaInfos = new QFrame(buttonGroup); QHBoxLayout *hlayout = new QHBoxLayout(metaInfos); metaInfos->setEnabled(KateApp::self()->documentManager()->getSaveMetaInfos()); QLabel *label = new QLabel(i18n("&Delete unused meta-information after:"), metaInfos); hlayout->addWidget(label); m_daysMetaInfos = new KPluralHandlingSpinBox(metaInfos); m_daysMetaInfos->setMaximum(180); m_daysMetaInfos->setSpecialValueText(i18nc("The special case of 'Delete unused meta-information after'", "(never)")); m_daysMetaInfos->setSuffix(ki18ncp("The suffix of 'Delete unused meta-information after'", " day", " days")); m_daysMetaInfos->setValue(KateApp::self()->documentManager()->getDaysMetaInfos()); hlayout->addWidget(m_daysMetaInfos); label->setBuddy(m_daysMetaInfos); connect(m_saveMetaInfos, &QCheckBox::toggled, metaInfos, &QFrame::setEnabled); connect(m_daysMetaInfos, static_cast(&KPluralHandlingSpinBox::valueChanged), this, &KateConfigDialog::slotChanged); vbox->addWidget(metaInfos); buttonGroup->setLayout(vbox); // quick search buttonGroup = new QGroupBox(i18n("&Quick Open"), generalFrame); vbox = new QVBoxLayout; buttonGroup->setLayout(vbox); // quick open match mode hlayout = new QHBoxLayout; label = new QLabel(i18n("&Match Mode:"), buttonGroup); hlayout->addWidget(label); m_cmbQuickOpenMatchMode = new QComboBox(buttonGroup); hlayout->addWidget(m_cmbQuickOpenMatchMode); label->setBuddy(m_cmbQuickOpenMatchMode); m_cmbQuickOpenMatchMode->addItem(i18n("Filename"), QVariant(KateQuickOpenModel::Columns::FileName)); m_cmbQuickOpenMatchMode->addItem(i18n("Filepath"), QVariant(KateQuickOpenModel::Columns::FilePath)); m_cmbQuickOpenMatchMode->setCurrentIndex(m_cmbQuickOpenMatchMode->findData(m_mainWindow->quickOpenMatchMode())); m_mainWindow->setQuickOpenMatchMode(m_cmbQuickOpenMatchMode->currentData().toInt()); connect(m_cmbQuickOpenMatchMode, static_cast(&QComboBox::currentIndexChanged), this, &KateConfigDialog::slotChanged); vbox->addLayout(hlayout); // quick open list mode hlayout = new QHBoxLayout; label = new QLabel(i18n("&List Mode:"), buttonGroup); hlayout->addWidget(label); m_cmbQuickOpenListMode = new QComboBox(buttonGroup); hlayout->addWidget(m_cmbQuickOpenListMode); label->setBuddy(m_cmbQuickOpenListMode); m_cmbQuickOpenListMode->addItem(i18n("Current Project Files"), QVariant(KateQuickOpenModel::List::CurrentProject)); m_cmbQuickOpenListMode->addItem(i18n("All Projects Files"), QVariant(KateQuickOpenModel::List::AllProjects)); m_cmbQuickOpenListMode->setCurrentIndex(m_cmbQuickOpenListMode->findData(m_mainWindow->quickOpenListMode())); m_mainWindow->setQuickOpenListMode(static_cast(m_cmbQuickOpenListMode->currentData().toInt())); connect(m_cmbQuickOpenListMode, static_cast(&QComboBox::currentIndexChanged), this, &KateConfigDialog::slotChanged); vbox->addLayout(hlayout); layout->addWidget(buttonGroup); layout->addStretch(1); // :-] works correct without autoadd // END General page // BEGIN Session page QWidget *sessionsPage = new QWidget(); item = addSubPage(applicationItem, sessionsPage, i18n("Sessions")); item->setHeader(i18n("Session Management")); item->setIcon(QIcon::fromTheme(QStringLiteral("view-history"))); sessionConfigUi = new Ui::SessionConfigWidget(); sessionConfigUi->setupUi(sessionsPage); // restore view config sessionConfigUi->restoreVC->setChecked(cgGeneral.readEntry("Restore Window Configuration", true)); connect(sessionConfigUi->restoreVC, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged); sessionConfigUi->spinBoxRecentFilesCount->setValue(recentFilesMaxCount()); connect(sessionConfigUi->spinBoxRecentFilesCount, static_cast(&QSpinBox::valueChanged), this, &KateConfigDialog::slotChanged); QString sesStart(cgGeneral.readEntry("Startup Session", "manual")); if (sesStart == QLatin1String("new")) sessionConfigUi->startNewSessionRadioButton->setChecked(true); else if (sesStart == QLatin1String("last")) sessionConfigUi->loadLastUserSessionRadioButton->setChecked(true); else sessionConfigUi->manuallyChooseSessionRadioButton->setChecked(true); connect(sessionConfigUi->startNewSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged); connect(sessionConfigUi->loadLastUserSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged); connect(sessionConfigUi->manuallyChooseSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged); // END Session page // BEGIN Plugins page QFrame *page = new QFrame(this); QVBoxLayout *vlayout = new QVBoxLayout(page); vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing(0); KateConfigPluginPage *configPluginPage = new KateConfigPluginPage(page, this); vlayout->addWidget(configPluginPage); connect(configPluginPage, &KateConfigPluginPage::changed, this, &KateConfigDialog::slotChanged); item = addSubPage(applicationItem, page, i18n("Plugins")); item->setHeader(i18n("Plugin Manager")); item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-plugin"))); const KatePluginList &pluginList(KateApp::self()->pluginManager()->pluginList()); for (const KatePluginInfo &plugin : pluginList) { if (plugin.load) { addPluginPage(plugin.plugin); } } // END Plugins page // editor widgets from kwrite/kwdialog m_editorPage = addPage(new QWidget, i18n("Editor Component")); m_editorPage->setIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor"))); m_editorPage->setHeader(i18n("Editor Component Options")); m_editorPage->setCheckable(false); m_editorPage->setEnabled(false); addEditorPages(); connect(this, &KateConfigDialog::accepted, this, &KateConfigDialog::slotApply); connect(buttonBox()->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &KateConfigDialog::slotApply); connect(buttonBox()->button(QDialogButtonBox::Help), &QPushButton::clicked, this, &KateConfigDialog::slotHelp); connect(this, &KateConfigDialog::currentPageChanged, this, &KateConfigDialog::slotCurrentPageChanged); resize(minimumSizeHint()); // ensure no stray signals already set this! m_dataChanged = false; } KateConfigDialog::~KateConfigDialog() { delete sessionConfigUi; } void KateConfigDialog::addEditorPages() { for (int i = 0; i < KTextEditor::Editor::instance()->configPages(); ++i) { KTextEditor::ConfigPage *page = KTextEditor::Editor::instance()->configPage(i, this); connect(page, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged); m_editorPages.push_back(page); KPageWidgetItem *item = addSubPage(m_editorPage, page, page->name()); item->setHeader(page->fullName()); item->setIcon(page->icon()); } } void KateConfigDialog::addPluginPage(KTextEditor::Plugin *plugin) { for (int i = 0; i < plugin->configPages(); i++) { QFrame *page = new QFrame(); QVBoxLayout *layout = new QVBoxLayout(page); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); KTextEditor::ConfigPage *cp = plugin->configPage(i, page); page->layout()->addWidget(cp); KPageWidgetItem *item = addSubPage(m_applicationPage, page, cp->name()); item->setHeader(cp->fullName()); item->setIcon(cp->icon()); PluginPageListItem *info = new PluginPageListItem; info->plugin = plugin; info->pageParent = page; info->pluginPage = cp; info->idInPlugin = i; info->pageWidgetItem = item; connect(info->pluginPage, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged); m_pluginPages.insert(item, info); } } void KateConfigDialog::slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem * /*before*/) { PluginPageListItem *info = m_pluginPages[current]; if (!info) { return; } if (info->pluginPage) { return; } qCDebug(LOG_KATE) << "creating config page (should not get here)"; info->pluginPage = info->plugin->configPage(info->idInPlugin, info->pageParent); info->pageParent->layout()->addWidget(info->pluginPage); info->pluginPage->show(); connect(info->pluginPage, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged); } void KateConfigDialog::removePluginPage(KTextEditor::Plugin *plugin) { QList remove; for (QHash::const_iterator it = m_pluginPages.constBegin(); it != m_pluginPages.constEnd(); ++it) { PluginPageListItem *pli = it.value(); if (!pli) { continue; } if (pli->plugin == plugin) { remove.append(it.key()); } } qCDebug(LOG_KATE) << remove.count(); while (!remove.isEmpty()) { KPageWidgetItem *wItem = remove.takeLast(); PluginPageListItem *pItem = m_pluginPages.take(wItem); delete pItem->pluginPage; delete pItem->pageParent; removePage(wItem); delete pItem; } } void KateConfigDialog::slotApply() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); // if data changed apply the kate app stuff if (m_dataChanged) { KConfigGroup cg = KConfigGroup(config, "General"); cg.writeEntry("Restore Window Configuration", sessionConfigUi->restoreVC->isChecked()); cg.writeEntry("Recent File List Entry Count", sessionConfigUi->spinBoxRecentFilesCount->value()); if (sessionConfigUi->startNewSessionRadioButton->isChecked()) { cg.writeEntry("Startup Session", "new"); } else if (sessionConfigUi->loadLastUserSessionRadioButton->isChecked()) { cg.writeEntry("Startup Session", "last"); } else { cg.writeEntry("Startup Session", "manual"); } cg.writeEntry("Save Meta Infos", m_saveMetaInfos->isChecked()); KateApp::self()->documentManager()->setSaveMetaInfos(m_saveMetaInfos->isChecked()); cg.writeEntry("Days Meta Infos", m_daysMetaInfos->value()); KateApp::self()->documentManager()->setDaysMetaInfos(m_daysMetaInfos->value()); cg.writeEntry("Modified Notification", m_modNotifications->isChecked()); m_mainWindow->setModNotificationEnabled(m_modNotifications->isChecked()); cg.writeEntry("Close After Last", m_modCloseAfterLast->isChecked()); m_mainWindow->setModCloseAfterLast(m_modCloseAfterLast->isChecked()); cg.writeEntry("Quick Open Search Mode", m_cmbQuickOpenMatchMode->currentData().toInt()); m_mainWindow->setQuickOpenMatchMode(m_cmbQuickOpenMatchMode->currentData().toInt()); cg.writeEntry("Quick Open List Mode", m_cmbQuickOpenListMode->currentData().toInt()); m_mainWindow->setQuickOpenListMode(static_cast(m_cmbQuickOpenListMode->currentData().toInt())); // patch document modified warn state const QList &docs = KateApp::self()->documentManager()->documentList(); for (KTextEditor::Document *doc : docs) if (qobject_cast(doc)) { qobject_cast(doc)->setModifiedOnDiskWarning(!m_modNotifications->isChecked()); } m_mainWindow->saveOptions(); // save plugin config !! KateSessionManager *sessionmanager = KateApp::self()->sessionManager(); KConfig *sessionConfig = sessionmanager->activeSession()->config(); KateApp::self()->pluginManager()->writeConfig(sessionConfig); } for (PluginPageListItem *plugin : qAsConst(m_pluginPages)) { if (!plugin) { continue; } if (plugin->pluginPage) { plugin->pluginPage->apply(); } } // apply ktexteditor pages for (KTextEditor::ConfigPage *page : qAsConst(m_editorPages)) page->apply(); config->sync(); m_dataChanged = false; buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); } void KateConfigDialog::slotChanged() { m_dataChanged = true; buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(true); } void KateConfigDialog::showAppPluginPage(KTextEditor::Plugin *p, int id) { for (PluginPageListItem *plugin : qAsConst(m_pluginPages)) { if ((plugin->plugin == p) && (id == plugin->idInPlugin)) { setCurrentPage(plugin->pageWidgetItem); break; } } } void KateConfigDialog::slotHelp() { QDesktopServices::openUrl(QUrl(QStringLiteral("help:/"))); } int KateConfigDialog::recentFilesMaxCount() { int maxItems = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("Recent File List Entry Count", 10); return maxItems; } void KateConfigDialog::closeEvent(QCloseEvent *event) { if (!m_dataChanged) { event->accept(); return; } const auto response = KMessageBox::warningYesNoCancel(this, i18n("You have unsaved changes. Do you want to apply the changes or discard them?"), i18n("Warning"), KStandardGuiItem::save(), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); switch (response) { case KMessageBox::Yes: slotApply(); Q_FALLTHROUGH(); case KMessageBox::No: event->accept(); break; case KMessageBox::Cancel: event->ignore(); break; default: break; } } diff --git a/kate/kateconfigdialog.h b/kate/kateconfigdialog.h index ad970a291..4d03c0268 100644 --- a/kate/kateconfigdialog.h +++ b/kate/kateconfigdialog.h @@ -1,106 +1,106 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2007 Mirko Stocker This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef __kate_configdialog_h__ #define __kate_configdialog_h__ -#include #include -#include #include +#include +#include #include #include class QCheckBox; class QComboBox; class QSpinBox; class KateMainWindow; class KPluralHandlingSpinBox; namespace Ui { class SessionConfigWidget; } struct PluginPageListItem { KTextEditor::Plugin *plugin; int idInPlugin; KTextEditor::ConfigPage *pluginPage; QWidget *pageParent; KPageWidgetItem *pageWidgetItem; }; class KateConfigDialog : public KPageDialog { Q_OBJECT public: KateConfigDialog(KateMainWindow *parent, KTextEditor::View *view); ~KateConfigDialog() override; public: // static /** * Reads the value from the given open config. If not present in config yet then * the default value 10 is used. */ static int recentFilesMaxCount(); public: void addPluginPage(KTextEditor::Plugin *plugin); void removePluginPage(KTextEditor::Plugin *plugin); void showAppPluginPage(KTextEditor::Plugin *plugin, int id); protected Q_SLOTS: void slotApply(); void slotChanged(); void slotHelp(); void slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem *before); protected: void closeEvent(QCloseEvent *event) override; private: KateMainWindow *m_mainWindow; KTextEditor::View *m_view; bool m_dataChanged = false; QCheckBox *m_modNotifications; QCheckBox *m_modCloseAfterLast; QCheckBox *m_saveMetaInfos; KPluralHandlingSpinBox *m_daysMetaInfos; QComboBox *m_cmbQuickOpenMatchMode; QComboBox *m_cmbQuickOpenListMode; // Sessions Page Ui::SessionConfigWidget *sessionConfigUi; QHash m_pluginPages; QList m_editorPages; KPageWidgetItem *m_applicationPage; KPageWidgetItem *m_editorPage; void addEditorPages(); }; #endif diff --git a/kate/kateconfigplugindialogpage.cpp b/kate/kateconfigplugindialogpage.cpp index 0a8beeb1a..0a66e1996 100644 --- a/kate/kateconfigplugindialogpage.cpp +++ b/kate/kateconfigplugindialogpage.cpp @@ -1,125 +1,125 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2007 Mirko Stocker 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. */ #include "kateconfigplugindialogpage.h" #include "kateapp.h" -#include "katepluginmanager.h" #include "kateconfigdialog.h" +#include "katepluginmanager.h" #include #include class KatePluginListItem : public QTreeWidgetItem { public: KatePluginListItem(bool checked, KatePluginInfo *info); KatePluginInfo *info() const { return mInfo; } protected: void stateChange(bool); private: KatePluginInfo *mInfo; }; KatePluginListItem::KatePluginListItem(bool checked, KatePluginInfo *info) : QTreeWidgetItem() , mInfo(info) { setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); } KatePluginListView::KatePluginListView(QWidget *parent) : QTreeWidget(parent) { setRootIsDecorated(false); connect(this, &KatePluginListView::itemChanged, this, &KatePluginListView::stateChanged); } void KatePluginListView::stateChanged(QTreeWidgetItem *item) { emit stateChange(static_cast(item), item->checkState(0) == Qt::Checked); } KateConfigPluginPage::KateConfigPluginPage(QWidget *parent, KateConfigDialog *dialog) : QFrame(parent) , myDialog(dialog) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); KatePluginListView *listView = new KatePluginListView(this); layout->addWidget(listView); QStringList headers; headers << i18n("Name") << i18n("Description"); listView->setHeaderLabels(headers); listView->setWhatsThis(i18n("Here you can see all available Kate plugins. Those with a check mark are loaded, and will be loaded again the next time Kate is started.")); KatePluginList &pluginList(KateApp::self()->pluginManager()->pluginList()); for (auto &pluginInfo : pluginList) { QTreeWidgetItem *item = new KatePluginListItem(pluginInfo.load, &pluginInfo); item->setText(0, pluginInfo.metaData.name()); item->setText(1, pluginInfo.metaData.description()); listView->addTopLevelItem(item); } listView->resizeColumnToContents(0); listView->sortByColumn(0, Qt::AscendingOrder); connect(listView, &KatePluginListView::stateChange, this, &KateConfigPluginPage::stateChange); } void KateConfigPluginPage::stateChange(KatePluginListItem *item, bool b) { if (b) { loadPlugin(item); } else { unloadPlugin(item); } emit changed(); } void KateConfigPluginPage::loadPlugin(KatePluginListItem *item) { const bool ok = KateApp::self()->pluginManager()->loadPlugin(item->info()); if (!ok) { return; } KateApp::self()->pluginManager()->enablePluginGUI(item->info()); myDialog->addPluginPage(item->info()->plugin); item->setCheckState(0, Qt::Checked); } void KateConfigPluginPage::unloadPlugin(KatePluginListItem *item) { myDialog->removePluginPage(item->info()->plugin); KateApp::self()->pluginManager()->unloadPlugin(item->info()); item->setCheckState(0, Qt::Unchecked); } diff --git a/kate/katedocmanager.cpp b/kate/katedocmanager.cpp index a6f70314c..19b0d25b0 100644 --- a/kate/katedocmanager.cpp +++ b/kate/katedocmanager.cpp @@ -1,598 +1,598 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2007 Flavio Castelli 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. */ #include "katedocmanager.h" #include "kateapp.h" +#include "katedebug.h" #include "katemainwindow.h" -#include "kateviewmanager.h" #include "katesavemodifieddialog.h" -#include "katedebug.h" +#include "kateviewmanager.h" -#include #include +#include #include -#include -#include #include -#include #include +#include +#include +#include +#include #include #include +#include #include -#include -#include -#include #include #include -#include +#include +#include KateDocManager::KateDocManager(QObject *parent) : QObject(parent) , m_metaInfos(QStringLiteral("katemetainfos"), KConfig::NoGlobals) , m_saveMetaInfos(true) , m_daysMetaInfos(0) { // set our application wrapper KTextEditor::Editor::instance()->setApplication(KateApp::self()->wrapper()); // create one doc, we always have at least one around! createDoc(); } KateDocManager::~KateDocManager() { // write metainfos? if (m_saveMetaInfos) { // saving meta-infos when file is saved is not enough, we need to do it once more at the end saveMetaInfos(m_docList); // purge saved filesessions if (m_daysMetaInfos > 0) { const QStringList groups = m_metaInfos.groupList(); QDateTime def(QDate(1970, 1, 1)); for (const auto &group : groups) { QDateTime last = m_metaInfos.group(group).readEntry("Time", def); if (last.daysTo(QDateTime::currentDateTimeUtc()) > m_daysMetaInfos) { m_metaInfos.deleteGroup(group); } } } } qDeleteAll(m_docInfos); } KTextEditor::Document *KateDocManager::createDoc(const KateDocumentInfo &docInfo) { KTextEditor::Document *doc = KTextEditor::Editor::instance()->createDocument(this); // turn of the editorpart's own modification dialog, we have our own one, too! const KConfigGroup generalGroup(KSharedConfig::openConfig(), "General"); bool ownModNotification = generalGroup.readEntry("Modified Notification", false); if (qobject_cast(doc)) { qobject_cast(doc)->setModifiedOnDiskWarning(!ownModNotification); } m_docList.append(doc); m_docInfos.insert(doc, new KateDocumentInfo(docInfo)); // connect internal signals... connect(doc, &KTextEditor::Document::modifiedChanged, this, &KateDocManager::slotModChanged1); connect(doc, SIGNAL(modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(slotModifiedOnDisc(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason))); // we have a new document, show it the world emit documentCreated(doc); emit documentCreatedViewManager(doc); // return our new document return doc; } KateDocumentInfo *KateDocManager::documentInfo(KTextEditor::Document *doc) { return m_docInfos.contains(doc) ? m_docInfos[doc] : nullptr; } static QUrl normalizeUrl(const QUrl &url) { // Resolve symbolic links for local files (done anyway in KTextEditor) if (url.isLocalFile()) { QString normalizedUrl = QFileInfo(url.toLocalFile()).canonicalFilePath(); if (!normalizedUrl.isEmpty()) { return QUrl::fromLocalFile(normalizedUrl); } } // else: cleanup only the .. stuff return url.adjusted(QUrl::NormalizePathSegments); } KTextEditor::Document *KateDocManager::findDocument(const QUrl &url) const { const QUrl u(normalizeUrl(url)); for (KTextEditor::Document *it : m_docList) { if (it->url() == u) { return it; } } return nullptr; } QList KateDocManager::openUrls(const QList &urls, const QString &encoding, bool isTempFile, const KateDocumentInfo &docInfo) { QList docs; emit aboutToCreateDocuments(); for (const QUrl &url : urls) { docs << openUrl(url, encoding, isTempFile, docInfo); } emit documentsCreated(docs); return docs; } KTextEditor::Document *KateDocManager::openUrl(const QUrl &url, const QString &encoding, bool isTempFile, const KateDocumentInfo &docInfo) { // special handling: if only one unmodified empty buffer in the list, // keep this buffer in mind to close it after opening the new url KTextEditor::Document *untitledDoc = nullptr; if ((documentList().count() == 1) && (!documentList().at(0)->isModified() && documentList().at(0)->url().isEmpty())) { untitledDoc = documentList().first(); } // // create new document // const QUrl u(normalizeUrl(url)); KTextEditor::Document *doc = nullptr; // always new document if url is empty... if (!u.isEmpty()) { doc = findDocument(u); } if (!doc) { if (untitledDoc) { // reuse the untitled document which is not needed auto &info = m_docInfos.find(untitledDoc).value(); delete info; info = new KateDocumentInfo(docInfo); doc = untitledDoc; } else { doc = createDoc(docInfo); } if (!encoding.isEmpty()) { doc->setEncoding(encoding); } if (!u.isEmpty()) { doc->openUrl(u); loadMetaInfos(doc, u); } } // // if needed, register as temporary file // if (isTempFile && u.isLocalFile()) { QFileInfo fi(u.toLocalFile()); if (fi.exists()) { m_tempFiles[doc] = qMakePair(u, fi.lastModified()); qCDebug(LOG_KATE) << "temporary file will be deleted after use unless modified: " << u; } } return doc; } bool KateDocManager::closeDocuments(const QList &documents, bool closeUrl) { if (documents.isEmpty()) { return false; } saveMetaInfos(documents); emit aboutToDeleteDocuments(documents); int last = 0; bool success = true; for (KTextEditor::Document *doc : documents) { if (closeUrl && !doc->closeUrl()) { success = false; // get out on first error break; } if (closeUrl && m_tempFiles.contains(doc)) { QFileInfo fi(m_tempFiles[doc].first.toLocalFile()); if (fi.lastModified() <= m_tempFiles[doc].second || KMessageBox::questionYesNo(KateApp::self()->activeKateMainWindow(), i18n("The supposedly temporary file %1 has been modified. " "Do you want to delete it anyway?", m_tempFiles[doc].first.url(QUrl::PreferLocalFile)), i18n("Delete File?")) == KMessageBox::Yes) { KIO::del(m_tempFiles[doc].first, KIO::HideProgressInfo); qCDebug(LOG_KATE) << "Deleted temporary file " << m_tempFiles[doc].first; m_tempFiles.remove(doc); } else { m_tempFiles.remove(doc); qCDebug(LOG_KATE) << "The supposedly temporary file " << m_tempFiles[doc].first.url() << " have been modified since loaded, and has not been deleted."; } } KateApp::self()->emitDocumentClosed(QString::number((qptrdiff)doc)); // document will be deleted, soon emit documentWillBeDeleted(doc); // really delete the document and its infos delete m_docInfos.take(doc); delete m_docList.takeAt(m_docList.indexOf(doc)); // document is gone, emit our signals emit documentDeleted(doc); last++; } /** * never ever empty the whole document list * do this before documentsDeleted is emitted, to have no flicker */ if (m_docList.isEmpty()) { createDoc(); } emit documentsDeleted(documents.mid(last)); return success; } bool KateDocManager::closeDocument(KTextEditor::Document *doc, bool closeUrl) { if (!doc) { return false; } QList documents; documents.append(doc); return closeDocuments(documents, closeUrl); } bool KateDocManager::closeDocumentList(const QList &documents) { QList modifiedDocuments; for (KTextEditor::Document *document : documents) { if (document->isModified()) { modifiedDocuments.append(document); } } if (!modifiedDocuments.empty() && !KateSaveModifiedDialog::queryClose(nullptr, modifiedDocuments)) { return false; } return closeDocuments(documents, false); // Do not show save/discard dialog } bool KateDocManager::closeAllDocuments(bool closeUrl) { /** * just close all documents */ return closeDocuments(m_docList, closeUrl); } bool KateDocManager::closeOtherDocuments(KTextEditor::Document *doc) { /** * close all documents beside the passed one */ QList documents = m_docList; documents.removeOne(doc); return closeDocuments(documents); } /** * Find all modified documents. * @return Return the list of all modified documents. */ QList KateDocManager::modifiedDocumentList() { QList modified; for (KTextEditor::Document *doc : qAsConst(m_docList)) { if (doc->isModified()) { modified.append(doc); } } return modified; } bool KateDocManager::queryCloseDocuments(KateMainWindow *w) { int docCount = m_docList.count(); for (KTextEditor::Document *doc : qAsConst(m_docList)) { if (doc->url().isEmpty() && doc->isModified()) { int msgres = KMessageBox::warningYesNoCancel(w, i18n("

The document '%1' has been modified, but not saved.

" "

Do you want to save your changes or discard them?

", doc->documentName()), i18n("Close Document"), KStandardGuiItem::save(), KStandardGuiItem::discard()); if (msgres == KMessageBox::Cancel) { return false; } if (msgres == KMessageBox::Yes) { const QUrl url = QFileDialog::getSaveFileUrl(w, i18n("Save As")); if (!url.isEmpty()) { if (!doc->saveAs(url)) { return false; } } else { return false; } } } else { if (!doc->queryClose()) { return false; } } } // document count changed while queryClose, abort and notify user if (m_docList.count() > docCount) { KMessageBox::information(w, i18n("New file opened while trying to close Kate, closing aborted."), i18n("Closing Aborted")); return false; } return true; } void KateDocManager::saveAll() { for (KTextEditor::Document *doc : qAsConst(m_docList)) if (doc->isModified()) { doc->documentSave(); } } void KateDocManager::saveSelected(const QList &docList) { for (KTextEditor::Document *doc : qAsConst(docList)) { if (doc->isModified()) { doc->documentSave(); } } } void KateDocManager::reloadAll() { // reload all docs that are NOT modified on disk for (KTextEditor::Document *doc : qAsConst(m_docList)) { if (!documentInfo(doc)->modifiedOnDisc) { doc->documentReload(); } } // take care of all documents that ARE modified on disk KateApp::self()->activeKateMainWindow()->showModOnDiskPrompt(); } void KateDocManager::closeOrphaned() { QList documents; for (KTextEditor::Document *doc : qAsConst(m_docList)) { KateDocumentInfo *info = documentInfo(doc); if (info && !info->openSuccess) { documents.append(doc); } } closeDocuments(documents); } void KateDocManager::saveDocumentList(KConfig *config) { KConfigGroup openDocGroup(config, "Open Documents"); openDocGroup.writeEntry("Count", m_docList.count()); int i = 0; for (KTextEditor::Document *doc : qAsConst(m_docList)) { KConfigGroup cg(config, QStringLiteral("Document %1").arg(i)); doc->writeSessionConfig(cg); i++; } } void KateDocManager::restoreDocumentList(KConfig *config) { KConfigGroup openDocGroup(config, "Open Documents"); unsigned int count = openDocGroup.readEntry("Count", 0); if (count == 0) { return; } QProgressDialog progress; progress.setWindowTitle(i18n("Starting Up")); progress.setLabelText(i18n("Reopening files from the last session...")); progress.setModal(true); progress.setCancelButton(nullptr); progress.setRange(0, count); for (unsigned int i = 0; i < count; i++) { KConfigGroup cg(config, QStringLiteral("Document %1").arg(i)); KTextEditor::Document *doc = nullptr; if (i == 0) { doc = m_docList.first(); } else { doc = createDoc(); } connect(doc, SIGNAL(completed()), this, SLOT(documentOpened())); connect(doc, &KParts::ReadOnlyPart::canceled, this, &KateDocManager::documentOpened); doc->readSessionConfig(cg); progress.setValue(i); } } void KateDocManager::slotModifiedOnDisc(KTextEditor::Document *doc, bool b, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { if (m_docInfos.contains(doc)) { m_docInfos[doc]->modifiedOnDisc = b; m_docInfos[doc]->modifiedOnDiscReason = reason; slotModChanged1(doc); } } /** * Load file's meta-information if the checksum didn't change since last time. */ bool KateDocManager::loadMetaInfos(KTextEditor::Document *doc, const QUrl &url) { if (!m_saveMetaInfos) { return false; } if (!m_metaInfos.hasGroup(url.toDisplayString())) { return false; } const QByteArray checksum = doc->checksum().toHex(); bool ok = true; if (!checksum.isEmpty()) { KConfigGroup urlGroup(&m_metaInfos, url.toDisplayString()); const QString old_checksum = urlGroup.readEntry("Checksum"); if (QString::fromLatin1(checksum) == old_checksum) { QSet flags; if (documentInfo(doc)->openedByUser) { flags << QStringLiteral("SkipEncoding"); } flags << QStringLiteral("SkipUrl"); doc->readSessionConfig(urlGroup, flags); } else { urlGroup.deleteGroup(); ok = false; } m_metaInfos.sync(); } return ok && doc->url() == url; } /** * Save file's meta-information if doc is in 'unmodified' state */ void KateDocManager::saveMetaInfos(const QList &documents) { /** * skip work if no meta infos wanted */ if (!m_saveMetaInfos) { return; } /** * store meta info for all non-modified documents which have some checksum */ const QDateTime now = QDateTime::currentDateTimeUtc(); for (KTextEditor::Document *doc : documents) { /** * skip modified docs */ if (doc->isModified()) { continue; } const QByteArray checksum = doc->checksum().toHex(); if (!checksum.isEmpty()) { /** * write the group with checksum and time */ KConfigGroup urlGroup(&m_metaInfos, doc->url().toString()); urlGroup.writeEntry("Checksum", QString::fromLatin1(checksum)); urlGroup.writeEntry("Time", now); /** * write document session config */ doc->writeSessionConfig(urlGroup); } } /** * sync to not loose data */ m_metaInfos.sync(); } void KateDocManager::slotModChanged(KTextEditor::Document *doc) { QList documents; documents.append(doc); saveMetaInfos(documents); } void KateDocManager::slotModChanged1(KTextEditor::Document *doc) { QMetaObject::invokeMethod(KateApp::self()->activeKateMainWindow(), "queueModifiedOnDisc", Qt::QueuedConnection, Q_ARG(KTextEditor::Document *, doc)); } void KateDocManager::documentOpened() { KColorScheme colors(QPalette::Active); KTextEditor::Document *doc = qobject_cast(sender()); if (!doc) { return; // should never happen, but who knows } disconnect(doc, SIGNAL(completed()), this, SLOT(documentOpened())); disconnect(doc, &KParts::ReadOnlyPart::canceled, this, &KateDocManager::documentOpened); // Only set "no success" when doc is empty to avoid close of files // with other trouble when do closeOrphaned() if (doc->openingError() && doc->isEmpty()) { KateDocumentInfo *info = documentInfo(doc); if (info) { info->openSuccess = false; } } } diff --git a/kate/katedocmanager.h b/kate/katedocmanager.h index 771a8ad07..517776c60 100644 --- a/kate/katedocmanager.h +++ b/kate/katedocmanager.h @@ -1,213 +1,213 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger 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. */ #ifndef __KATE_DOCMANAGER_H__ #define __KATE_DOCMANAGER_H__ #include #include #include -#include -#include #include +#include #include +#include #include +#include #include -#include #include class KateMainWindow; class KateDocumentInfo { public: enum CustomRoles { RestoreOpeningFailedRole }; public: KateDocumentInfo() = default; bool modifiedOnDisc = false; KTextEditor::ModificationInterface::ModifiedOnDiskReason modifiedOnDiscReason = KTextEditor::ModificationInterface::OnDiskUnmodified; bool openedByUser = false; bool openSuccess = true; }; class KateDocManager : public QObject { Q_OBJECT public: KateDocManager(QObject *parent); ~KateDocManager() override; KTextEditor::Document *createDoc(const KateDocumentInfo &docInfo = KateDocumentInfo()); KateDocumentInfo *documentInfo(KTextEditor::Document *doc); /** Returns the documentNumber of the doc with url URL or -1 if no such doc is found */ KTextEditor::Document *findDocument(const QUrl &url) const; const QList &documentList() const { return m_docList; } KTextEditor::Document *openUrl(const QUrl &, const QString &encoding = QString(), bool isTempFile = false, const KateDocumentInfo &docInfo = KateDocumentInfo()); QList openUrls(const QList &, const QString &encoding = QString(), bool isTempFile = false, const KateDocumentInfo &docInfo = KateDocumentInfo()); bool closeDocument(KTextEditor::Document *, bool closeUrl = true); bool closeDocuments(const QList &documents, bool closeUrl = true); bool closeDocumentList(const QList &documents); bool closeAllDocuments(bool closeUrl = true); bool closeOtherDocuments(KTextEditor::Document *); QList modifiedDocumentList(); bool queryCloseDocuments(KateMainWindow *w); void saveDocumentList(KConfig *config); void restoreDocumentList(KConfig *config); inline bool getSaveMetaInfos() { return m_saveMetaInfos; } inline void setSaveMetaInfos(bool b) { m_saveMetaInfos = b; } inline int getDaysMetaInfos() { return m_daysMetaInfos; } inline void setDaysMetaInfos(int i) { m_daysMetaInfos = i; } public Q_SLOTS: /** * saves all documents that has at least one view. * documents with no views are ignored :P */ void saveAll(); /** * reloads all documents that has at least one view. * documents with no views are ignored :P */ void reloadAll(); /** * close all documents, which could not be reopened */ void closeOrphaned(); /** * save selected documents from the File List */ void saveSelected(const QList &); Q_SIGNALS: /** * This signal is emitted when the \p document was created. */ void documentCreated(KTextEditor::Document *document); /** * This signal is emitted when the \p document was created. * This is emitted after the initial documentCreated for internal use in view manager */ void documentCreatedViewManager(KTextEditor::Document *document); /** * This signal is emitted before a \p document which should be closed is deleted * The document is still accessible and usable, but it will be deleted * after this signal was send. * * @param document document that will be deleted */ void documentWillBeDeleted(KTextEditor::Document *document); /** * This signal is emitted when the \p document has been deleted. * * Warning !!! DO NOT ACCESS THE DATA REFERENCED BY THE POINTER, IT IS ALREADY INVALID * Use the pointer only to remove mappings in hash or maps */ void documentDeleted(KTextEditor::Document *document); /** * This signal is emitted before the batch of documents is being created. * * You can use it to pause some updates. */ void aboutToCreateDocuments(); /** * This signal is emitted after the batch of documents is created. * * @param documents list of documents that have been created */ void documentsCreated(const QList &documents); /** * This signal is emitted before the documents batch is going to be deleted * * note that the batch can be interrupted in the middle and only some * of the documents may be actually deleted. See documentsDeleted() signal. */ void aboutToDeleteDocuments(const QList &); /** * This signal is emitted after the documents batch was deleted * * This is the batch closing signal for aboutToDeleteDocuments * @param documents the documents that weren't deleted after all */ void documentsDeleted(const QList &documents); private Q_SLOTS: void slotModifiedOnDisc(KTextEditor::Document *doc, bool b, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason); void slotModChanged(KTextEditor::Document *doc); void slotModChanged1(KTextEditor::Document *doc); private: bool loadMetaInfos(KTextEditor::Document *doc, const QUrl &url); void saveMetaInfos(const QList &docs); QList m_docList; QHash m_docInfos; KConfig m_metaInfos; bool m_saveMetaInfos; int m_daysMetaInfos; typedef QPair TPair; QMap m_tempFiles; private Q_SLOTS: void documentOpened(); }; #endif diff --git a/kate/katefileactions.h b/kate/katefileactions.h index 2d1da24f7..71a177628 100644 --- a/kate/katefileactions.h +++ b/kate/katefileactions.h @@ -1,91 +1,91 @@ /* This file is part of the KDE project * * Copyright (C) 2018 Gregor Mi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . **/ #ifndef KATE_FILEACTIONS_H #define KATE_FILEACTIONS_H -#include #include +#include #include class QWidget; namespace KTextEditor { class Document; } namespace KateFileActions { /** * Copies the file path to clipboard. * If the document has no file, the clipboard will be emptied. */ void copyFilePathToClipboard(KTextEditor::Document *document); /** * Tries to open and highlight the underlying url in the filemanager */ void openContainingFolder(KTextEditor::Document *document); /** * Shows a Rename dialog to rename the file associated with the document. * The document will be closed an reopened. * * Nothing is done if the document is nullptr or has no associated file. */ void renameDocumentFile(QWidget *parent, KTextEditor::Document *document); void openFilePropertiesDialog(KTextEditor::Document *document); /** * Asks the user if the file should really be deleted. If yes, the file * is deleted from disk and the document closed. * * Nothing is done if the document is nullptr or has no associated file. */ void deleteDocumentFile(QWidget *parent, KTextEditor::Document *document); /** * @returns a list of supported diff tools (names of the executables) */ QStringList supportedDiffTools(); /** * Runs an external program to compare the underlying files of two given documents. * * @p diffExecutable tested to work with "kdiff3", "kompare", and "meld" * @see supportedDiffTools() * * The parameters documentA and documentB must not be nullptr. Otherwise an assertion fails. * * If @p documentA or @p documentB have an empty url, * then an empty string is passed to the diff program instead of a local file path. * * @returns true if program was started successfully; otherwise false * (which can mean the program is not installed) * * IDEA for later: compare with unsaved buffer data instead of underlying file */ bool compareWithExternalProgram(KTextEditor::Document *documentA, KTextEditor::Document *documentB, const QString &diffExecutable); } #endif diff --git a/kate/katemainwindow.cpp b/kate/katemainwindow.cpp index 38cf9f2f4..631514f11 100644 --- a/kate/katemainwindow.cpp +++ b/kate/katemainwindow.cpp @@ -1,1282 +1,1282 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund Copyright (C) 2007 Flavio Castelli 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. */ // BEGIN Includes #include "katemainwindow.h" +#include "kateapp.h" +#include "katecolorschemechooser.h" #include "kateconfigdialog.h" +#include "kateconfigplugindialogpage.h" +#include "katedebug.h" #include "katedocmanager.h" +#include "katefileactions.h" +#include "katemwmodonhddialog.h" #include "katepluginmanager.h" -#include "kateconfigplugindialogpage.h" -#include "kateviewmanager.h" -#include "kateapp.h" +#include "katequickopen.h" +#include "katequickopenmodel.h" #include "katesavemodifieddialog.h" -#include "katemwmodonhddialog.h" -#include "katesessionsaction.h" #include "katesessionmanager.h" -#include "kateviewspace.h" -#include "katequickopen.h" +#include "katesessionsaction.h" #include "kateupdatedisabler.h" -#include "katedebug.h" -#include "katecolorschemechooser.h" -#include "katefileactions.h" -#include "katequickopenmodel.h" +#include "kateviewmanager.h" +#include "kateviewspace.h" -#include #include +#include +#include +#include +#include #include -#include +#include #include -#include -#include #include #include -#include -#include +#include #include +#include +#include +#include +#include #include -#include -#include -#include #include -#include -#include -#include -#include +#include #include +#include +#include -#include #include +#include +#include #include -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include #include #include +#include +#include #include -#include #include -#include -#include +#include #include // END KateMwModOnHdDialog *KateMainWindow::s_modOnHdDialog = nullptr; KateContainerStackedLayout::KateContainerStackedLayout(QWidget *parent) : QStackedLayout(parent) { } QSize KateContainerStackedLayout::sizeHint() const { if (currentWidget()) { return currentWidget()->sizeHint(); } return QStackedLayout::sizeHint(); } QSize KateContainerStackedLayout::minimumSize() const { if (currentWidget()) { return currentWidget()->minimumSize(); } return QStackedLayout::minimumSize(); } KateMainWindow::KateMainWindow(KConfig *sconfig, const QString &sgroup) : KateMDI::MainWindow(nullptr) , m_modignore(false) , m_wrapper(new KTextEditor::MainWindow(this)) { /** * we don't want any flicker here */ KateUpdateDisabler disableUpdates(this); /** * get and set config revision */ static const int currentConfigRevision = 10; const int readConfigRevision = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("Config Revision", 0); KConfigGroup(KSharedConfig::openConfig(), "General").writeEntry("Config Revision", currentConfigRevision); const bool firstStart = readConfigRevision < currentConfigRevision; // start session restore if needed startRestore(sconfig, sgroup); // setup most important actions first, needed by setupMainWindow setupImportantActions(); // setup the most important widgets setupMainWindow(); // setup the actions setupActions(); setStandardToolBarMenuEnabled(true); setXMLFile(QStringLiteral("kateui.rc")); createShellGUI(true); // qCDebug(LOG_KATE) << "****************************************************************************" << sconfig; // register mainwindow in app KateApp::self()->addMainWindow(this); // enable plugin guis KateApp::self()->pluginManager()->enableAllPluginsGUI(this, sconfig); // caption update const auto documents = KateApp::self()->documentManager()->documentList(); for (auto doc : documents) { slotDocumentCreated(doc); } connect(KateApp::self()->documentManager(), &KateDocManager::documentCreated, this, &KateMainWindow::slotDocumentCreated); readOptions(); if (sconfig) { m_viewManager->restoreViewConfiguration(KConfigGroup(sconfig, sgroup)); } finishRestore(); m_fileOpenRecent->loadEntries(KConfigGroup(sconfig, "Recent Files")); setAcceptDrops(true); connect(KateApp::self()->sessionManager(), SIGNAL(sessionChanged()), this, SLOT(updateCaption())); connect(this, &KateMDI::MainWindow::sigShowPluginConfigPage, this, &KateMainWindow::showPluginConfigPage); // prior to this there was (possibly) no view, therefore not context menu. // Hence, we have to take care of the menu bar here toggleShowMenuBar(false); // on first start: deactivate toolbar if (firstStart) toolBar(QStringLiteral("mainToolBar"))->hide(); // in all cases: avoid that arbitrary plugin toolviews get focus, like terminal, bug 412227 // we need to delay this a bit due to lazy view creation (and lazy e.g. terminal widget creation) QTimer::singleShot(0, centralWidget(), SLOT(setFocus())); } KateMainWindow::~KateMainWindow() { // first, save our fallback window size ;) KConfigGroup cfg(KSharedConfig::openConfig(), "MainWindow"); KWindowConfig::saveWindowSize(windowHandle(), cfg); // save other options ;=) saveOptions(); // unregister mainwindow in app KateApp::self()->removeMainWindow(this); // disable all plugin guis, delete all pluginViews KateApp::self()->pluginManager()->disableAllPluginsGUI(this); // manually delete quick open, it's event filters will cause crash otherwise later delete m_quickOpen; m_quickOpen = nullptr; // delete the view manager, before KateMainWindow's wrapper is dead delete m_viewManager; m_viewManager = nullptr; // kill the wrapper object, now that all views are dead delete m_wrapper; m_wrapper = nullptr; } QSize KateMainWindow::sizeHint() const { /** * have some useful size hint, else we have mini windows per default */ return (QSize(640, 480).expandedTo(minimumSizeHint())); } void KateMainWindow::setupImportantActions() { m_paShowStatusBar = KStandardAction::showStatusbar(this, SLOT(toggleShowStatusBar()), actionCollection()); m_paShowStatusBar->setWhatsThis(i18n("Use this command to show or hide the view's statusbar")); m_paShowMenuBar = KStandardAction::showMenubar(this, SLOT(toggleShowMenuBar()), actionCollection()); m_paShowTabBar = new KToggleAction(i18n("Show &Tabs"), this); actionCollection()->addAction(QStringLiteral("settings_show_tab_bar"), m_paShowTabBar); connect(m_paShowTabBar, &QAction::toggled, this, &KateMainWindow::toggleShowTabBar); m_paShowTabBar->setWhatsThis(i18n("Use this command to show or hide the tabs for the views")); m_paShowPath = new KToggleAction(i18n("Sho&w Path in Titlebar"), this); actionCollection()->addAction(QStringLiteral("settings_show_full_path"), m_paShowPath); connect(m_paShowPath, SIGNAL(toggled(bool)), this, SLOT(updateCaption())); m_paShowPath->setWhatsThis(i18n("Show the complete document path in the window caption")); // Load themes actionCollection()->addAction(QStringLiteral("colorscheme_menu"), new KateColorSchemeChooser(actionCollection())); QAction *a = actionCollection()->addAction(KStandardAction::Back, QStringLiteral("view_prev_tab")); a->setText(i18n("&Previous Tab")); a->setWhatsThis(i18n("Focus the previous tab.")); actionCollection()->setDefaultShortcuts(a, a->shortcuts() << KStandardShortcut::tabPrev()); connect(a, &QAction::triggered, this, &KateMainWindow::slotFocusPrevTab); a = actionCollection()->addAction(KStandardAction::Forward, QStringLiteral("view_next_tab")); a->setText(i18n("&Next Tab")); a->setWhatsThis(i18n("Focus the next tab.")); actionCollection()->setDefaultShortcuts(a, a->shortcuts() << KStandardShortcut::tabNext()); connect(a, &QAction::triggered, this, &KateMainWindow::slotFocusNextTab); // the quick open action is used by the KateViewSpace "quick open button" a = actionCollection()->addAction(QStringLiteral("view_quick_open")); a->setIcon(QIcon::fromTheme(QStringLiteral("quickopen"))); a->setText(i18n("&Quick Open")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_O)); connect(a, &QAction::triggered, this, &KateMainWindow::slotQuickOpen); a->setWhatsThis(i18n("Open a form to quick open documents.")); } void KateMainWindow::setupMainWindow() { setToolViewStyle(KMultiTabBar::KDEV3ICON); /** * create central stacked widget with its children */ m_mainStackedWidget = new QStackedWidget(centralWidget()); centralWidget()->layout()->addWidget(m_mainStackedWidget); (static_cast(centralWidget()->layout()))->setStretchFactor(m_mainStackedWidget, 100); m_quickOpen = new KateQuickOpen(m_mainStackedWidget, this); m_mainStackedWidget->addWidget(m_quickOpen); m_viewManager = new KateViewManager(m_mainStackedWidget, this); m_mainStackedWidget->addWidget(m_viewManager); // make view manager default visible! m_mainStackedWidget->setCurrentWidget(m_viewManager); m_bottomViewBarContainer = new QWidget(centralWidget()); centralWidget()->layout()->addWidget(m_bottomViewBarContainer); m_bottomContainerStack = new KateContainerStackedLayout(m_bottomViewBarContainer); } void KateMainWindow::setupActions() { QAction *a; actionCollection()->addAction(KStandardAction::New, QStringLiteral("file_new"), m_viewManager, SLOT(slotDocumentNew()))->setWhatsThis(i18n("Create a new document")); actionCollection()->addAction(KStandardAction::Open, QStringLiteral("file_open"), m_viewManager, SLOT(slotDocumentOpen()))->setWhatsThis(i18n("Open an existing document for editing")); m_fileOpenRecent = KStandardAction::openRecent(m_viewManager, SLOT(openUrl(QUrl)), this); m_fileOpenRecent->setMaxItems(KateConfigDialog::recentFilesMaxCount()); actionCollection()->addAction(m_fileOpenRecent->objectName(), m_fileOpenRecent); m_fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); a = actionCollection()->addAction(QStringLiteral("file_save_all")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-all"))); a->setText(i18n("Save A&ll")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_L)); connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::saveAll); a->setWhatsThis(i18n("Save all open, modified documents to disk.")); a = actionCollection()->addAction(QStringLiteral("file_reload_all")); a->setText(i18n("&Reload All")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::reloadAll); a->setWhatsThis(i18n("Reload all open documents.")); a = actionCollection()->addAction(QStringLiteral("file_copy_filepath")); a->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); a->setText(i18n("Copy File &Path")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { auto &&view = viewManager()->activeView(); KateFileActions::copyFilePathToClipboard(view->document()); }); a->setWhatsThis(i18n("Copies the file path of the current file to clipboard.")); a = actionCollection()->addAction(QStringLiteral("file_open_containing_folder")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder"))); a->setText(i18n("&Open Containing Folder")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { auto &&view = viewManager()->activeView(); KateFileActions::openContainingFolder(view->document()); }); a->setWhatsThis(i18n("Copies the file path of the current file to clipboard.")); a = actionCollection()->addAction(QStringLiteral("file_rename")); a->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); a->setText(i18nc("@action:inmenu", "Rename...")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { auto &&view = viewManager()->activeView(); KateFileActions::renameDocumentFile(this, view->document()); }); a->setWhatsThis(i18n("Renames the file belonging to the current document.")); a = actionCollection()->addAction(QStringLiteral("file_delete")); a->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); a->setText(i18nc("@action:inmenu", "Delete")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { auto &&view = viewManager()->activeView(); KateFileActions::deleteDocumentFile(this, view->document()); }); a->setWhatsThis(i18n("Deletes the file belonging to the current document.")); a = actionCollection()->addAction(QStringLiteral("file_properties")); a->setIcon(QIcon::fromTheme(QStringLiteral("dialog-object-properties"))); a->setText(i18n("Properties")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { auto &&view = viewManager()->activeView(); KateFileActions::openFilePropertiesDialog(view->document()); }); a->setWhatsThis(i18n("Deletes the file belonging to the current document.")); a = actionCollection()->addAction(QStringLiteral("file_compare")); a->setText(i18n("Compare")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { QMessageBox::information(this, i18n("Compare"), i18n("Use the Tabbar context menu to compare two documents")); }); a->setWhatsThis(i18n("Shows a hint how to compare documents.")); a = actionCollection()->addAction(QStringLiteral("file_close_orphaned")); a->setText(i18n("Close Orphaned")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::closeOrphaned); a->setWhatsThis(i18n("Close all documents in the file list that could not be reopened, because they are not accessible anymore.")); a = actionCollection()->addAction(KStandardAction::Close, QStringLiteral("file_close"), m_viewManager, SLOT(slotDocumentClose())); a->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); a->setWhatsThis(i18n("Close the current document.")); a = actionCollection()->addAction(QStringLiteral("file_close_other")); a->setText(i18n("Close Other")); connect(a, SIGNAL(triggered()), this, SLOT(slotDocumentCloseOther())); a->setWhatsThis(i18n("Close other open documents.")); a = actionCollection()->addAction(QStringLiteral("file_close_all")); a->setText(i18n("Clos&e All")); connect(a, &QAction::triggered, this, &KateMainWindow::slotDocumentCloseAll); a->setWhatsThis(i18n("Close all open documents.")); a = actionCollection()->addAction(KStandardAction::Quit, QStringLiteral("file_quit")); // Qt::QueuedConnection: delay real shutdown, as we are inside menu action handling (bug #185708) connect(a, &QAction::triggered, this, &KateMainWindow::slotFileQuit, Qt::QueuedConnection); a->setWhatsThis(i18n("Close this window")); a = actionCollection()->addAction(QStringLiteral("view_new_view")); a->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); a->setText(i18n("&New Window")); connect(a, &QAction::triggered, this, &KateMainWindow::newWindow); a->setWhatsThis(i18n("Create a new Kate view (a new window with the same document list).")); m_showFullScreenAction = KStandardAction::fullScreen(nullptr, nullptr, this, this); actionCollection()->addAction(m_showFullScreenAction->objectName(), m_showFullScreenAction); connect(m_showFullScreenAction, &QAction::toggled, this, &KateMainWindow::slotFullScreen); documentOpenWith = new KActionMenu(i18n("Open W&ith"), this); actionCollection()->addAction(QStringLiteral("file_open_with"), documentOpenWith); documentOpenWith->setWhatsThis(i18n("Open the current document using another application registered for its file type, or an application of your choice.")); connect(documentOpenWith->menu(), &QMenu::aboutToShow, this, &KateMainWindow::mSlotFixOpenWithMenu); connect(documentOpenWith->menu(), &QMenu::triggered, this, &KateMainWindow::slotOpenWithMenuAction); a = KStandardAction::keyBindings(this, SLOT(editKeys()), actionCollection()); a->setWhatsThis(i18n("Configure the application's keyboard shortcut assignments.")); a = KStandardAction::configureToolbars(this, SLOT(slotEditToolbars()), actionCollection()); a->setWhatsThis(i18n("Configure which items should appear in the toolbar(s).")); QAction *settingsConfigure = KStandardAction::preferences(this, SLOT(slotConfigure()), actionCollection()); settingsConfigure->setWhatsThis(i18n("Configure various aspects of this application and the editing component.")); if (KateApp::self()->pluginManager()->pluginList().count() > 0) { a = actionCollection()->addAction(QStringLiteral("help_plugins_contents")); a->setText(i18n("&Plugins Handbook")); connect(a, &QAction::triggered, this, &KateMainWindow::pluginHelp); a->setWhatsThis(i18n("This shows help files for various available plugins.")); } a = actionCollection()->addAction(QStringLiteral("help_about_editor")); a->setText(i18n("&About Editor Component")); connect(a, &QAction::triggered, this, &KateMainWindow::aboutEditor); connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotWindowActivated); connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateOpenWith); connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateActionsNeedingUrl); connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateBottomViewBar); // re-route signals to our wrapper connect(m_viewManager, &KateViewManager::viewChanged, m_wrapper, &KTextEditor::MainWindow::viewChanged); connect(m_viewManager, &KateViewManager::viewCreated, m_wrapper, &KTextEditor::MainWindow::viewCreated); connect(this, &KateMainWindow::unhandledShortcutOverride, m_wrapper, &KTextEditor::MainWindow::unhandledShortcutOverride); slotWindowActivated(); // session actions a = actionCollection()->addAction(QStringLiteral("sessions_new")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); a->setText(i18nc("Menu entry Session->New", "&New")); // Qt::QueuedConnection to avoid deletion of code that is executed when reducing the amount of mainwindows. (bug #227008) connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionNew, Qt::QueuedConnection); a = actionCollection()->addAction(QStringLiteral("sessions_save")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); a->setText(i18n("&Save Session")); connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionSave); a = actionCollection()->addAction(QStringLiteral("sessions_save_as")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); a->setText(i18n("Save Session &As...")); connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionSaveAs); a = actionCollection()->addAction(QStringLiteral("sessions_manage")); a->setIcon(QIcon::fromTheme(QStringLiteral("view-choose"))); a->setText(i18n("&Manage Sessions...")); // Qt::QueuedConnection to avoid deletion of code that is executed when reducing the amount of mainwindows. (bug #227008) connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionManage, Qt::QueuedConnection); // quick open menu ;) a = new KateSessionsAction(i18n("&Quick Open Session"), this); actionCollection()->addAction(QStringLiteral("sessions_list"), a); } void KateMainWindow::slotDocumentCloseAll() { if (!KateApp::self()->documentManager()->documentList().empty() && KMessageBox::warningContinueCancel( this, i18n("This will close all open documents. Are you sure you want to continue?"), i18n("Close all documents"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("closeAll")) != KMessageBox::Cancel) { if (queryClose_internal()) { KateApp::self()->documentManager()->closeAllDocuments(false); } } } void KateMainWindow::slotDocumentCloseOther(KTextEditor::Document *document) { if (KateApp::self()->documentManager()->documentList().size() > 1 && KMessageBox::warningContinueCancel(this, i18n("This will close all open documents beside the current one. Are you sure you want to continue?"), i18n("Close all documents beside current one"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("closeOther")) != KMessageBox::Cancel) { if (queryClose_internal(document)) { KateApp::self()->documentManager()->closeOtherDocuments(document); } } } void KateMainWindow::slotDocumentCloseSelected(const QList &docList) { QList documents; for (KTextEditor::Document *doc : docList) { if (queryClose_internal(doc)) { documents.append(doc); } } KateApp::self()->documentManager()->closeDocuments(documents); } void KateMainWindow::slotDocumentCloseOther() { slotDocumentCloseOther(m_viewManager->activeView()->document()); } bool KateMainWindow::queryClose_internal(KTextEditor::Document *doc) { int documentCount = KateApp::self()->documentManager()->documentList().size(); if (!showModOnDiskPrompt()) { return false; } QList modifiedDocuments = KateApp::self()->documentManager()->modifiedDocumentList(); modifiedDocuments.removeAll(doc); bool shutdown = (modifiedDocuments.count() == 0); if (!shutdown) { shutdown = KateSaveModifiedDialog::queryClose(this, modifiedDocuments); } if (KateApp::self()->documentManager()->documentList().size() > documentCount) { KMessageBox::information(this, i18n("New file opened while trying to close Kate, closing aborted."), i18n("Closing Aborted")); shutdown = false; } return shutdown; } /** * queryClose(), take care that after the last mainwindow the stuff is closed */ bool KateMainWindow::queryClose() { // session saving, can we close all views ? // just test, not close them actually if (qApp->isSavingSession()) { return queryClose_internal(); } // normal closing of window // allow to close all windows until the last without restrictions if (KateApp::self()->mainWindowsCount() > 1) { return true; } // last one: check if we can close all documents, try run // and save docs if we really close down ! if (queryClose_internal()) { KateApp::self()->sessionManager()->saveActiveSession(true); return true; } return false; } void KateMainWindow::newWindow() { KateApp::self()->newMainWindow(KateApp::self()->sessionManager()->activeSession()->config()); } void KateMainWindow::slotEditToolbars() { KConfigGroup cfg(KSharedConfig::openConfig(), "MainWindow"); saveMainWindowSettings(cfg); KEditToolBar dlg(factory()); connect(&dlg, &KEditToolBar::newToolBarConfig, this, &KateMainWindow::slotNewToolbarConfig); dlg.exec(); } void KateMainWindow::reloadXmlGui() { for (KTextEditor::Document *doc : KateApp::self()->documentManager()->documentList()) { doc->reloadXML(); for (KTextEditor::View *view : doc->views()) { view->reloadXML(); } } } void KateMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(KConfigGroup(KSharedConfig::openConfig(), "MainWindow")); // we need to reload all View's XML Gui from disk to ensure toolbar // changes are applied to all views. reloadXmlGui(); } void KateMainWindow::slotFileQuit() { KateApp::self()->shutdownKate(this); } void KateMainWindow::slotFileClose() { m_viewManager->slotDocumentClose(); } void KateMainWindow::slotOpenDocument(const QUrl &url) { m_viewManager->openUrl(url, QString(), true, false); } void KateMainWindow::readOptions() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); const KConfigGroup generalGroup(config, "General"); m_modNotification = generalGroup.readEntry("Modified Notification", false); m_modCloseAfterLast = generalGroup.readEntry("Close After Last", false); KateApp::self()->documentManager()->setSaveMetaInfos(generalGroup.readEntry("Save Meta Infos", true)); KateApp::self()->documentManager()->setDaysMetaInfos(generalGroup.readEntry("Days Meta Infos", 30)); m_paShowPath->setChecked(generalGroup.readEntry("Show Full Path in Title", false)); m_paShowStatusBar->setChecked(generalGroup.readEntry("Show Status Bar", true)); m_paShowMenuBar->setChecked(generalGroup.readEntry("Show Menu Bar", true)); m_paShowTabBar->setChecked(generalGroup.readEntry("Show Tab Bar", true)); m_quickOpen->setMatchMode(generalGroup.readEntry("Quick Open Search Mode", (int)KateQuickOpenModel::Columns::FileName)); int listMode = generalGroup.readEntry("Quick Open List Mode", (int)KateQuickOpenModel::List::CurrentProject); m_quickOpen->setListMode(static_cast(listMode)); // emit signal to hide/show statusbars toggleShowStatusBar(); toggleShowTabBar(); } void KateMainWindow::saveOptions() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup generalGroup(config, "General"); generalGroup.writeEntry("Save Meta Infos", KateApp::self()->documentManager()->getSaveMetaInfos()); generalGroup.writeEntry("Days Meta Infos", KateApp::self()->documentManager()->getDaysMetaInfos()); generalGroup.writeEntry("Show Full Path in Title", m_paShowPath->isChecked()); generalGroup.writeEntry("Show Status Bar", m_paShowStatusBar->isChecked()); generalGroup.writeEntry("Show Menu Bar", m_paShowMenuBar->isChecked()); generalGroup.writeEntry("Show Tab Bar", m_paShowTabBar->isChecked()); } void KateMainWindow::toggleShowMenuBar(bool showMessage) { if (m_paShowMenuBar->isChecked()) { menuBar()->show(); removeMenuBarActionFromContextMenu(); } else { if (showMessage) { const QString accel = m_paShowMenuBar->shortcut().toString(); KMessageBox::information(this, i18n("This will hide the menu bar completely." " You can show it again by typing %1.", accel), i18n("Hide menu bar"), QStringLiteral("HideMenuBarWarning")); } menuBar()->hide(); addMenuBarActionToContextMenu(); } } void KateMainWindow::addMenuBarActionToContextMenu() { if (m_viewManager->activeView()) { m_viewManager->activeView()->contextMenu()->addAction(m_paShowMenuBar); } } void KateMainWindow::removeMenuBarActionFromContextMenu() { if (m_viewManager->activeView()) { m_viewManager->activeView()->contextMenu()->removeAction(m_paShowMenuBar); } } void KateMainWindow::toggleShowStatusBar() { emit statusBarToggled(); } bool KateMainWindow::showStatusBar() { return m_paShowStatusBar->isChecked(); } void KateMainWindow::toggleShowTabBar() { emit tabBarToggled(); } bool KateMainWindow::showTabBar() { return m_paShowTabBar->isChecked(); } void KateMainWindow::slotWindowActivated() { if (m_viewManager->activeView()) { updateCaption(m_viewManager->activeView()->document()); } // show view manager in any case if (m_mainStackedWidget->currentWidget() != m_viewManager) { m_mainStackedWidget->setCurrentWidget(m_viewManager); } // update proxy centralWidget()->setFocusProxy(m_viewManager->activeView()); } void KateMainWindow::slotUpdateOpenWith() { if (m_viewManager->activeView()) { documentOpenWith->setEnabled(!m_viewManager->activeView()->document()->url().isEmpty()); } else { documentOpenWith->setEnabled(false); } } void KateMainWindow::slotUpdateActionsNeedingUrl() { auto &&view = viewManager()->activeView(); const bool hasUrl = view && !view->document()->url().isEmpty(); action("file_copy_filepath")->setEnabled(hasUrl); action("file_open_containing_folder")->setEnabled(hasUrl); action("file_rename")->setEnabled(hasUrl); action("file_delete")->setEnabled(hasUrl); action("file_properties")->setEnabled(hasUrl); } void KateMainWindow::dragEnterEvent(QDragEnterEvent *event) { if (!event->mimeData()) { return; } const bool accept = event->mimeData()->hasUrls() || event->mimeData()->hasText(); event->setAccepted(accept); } void KateMainWindow::dropEvent(QDropEvent *event) { slotDropEvent(event); } void KateMainWindow::slotDropEvent(QDropEvent *event) { if (event->mimeData() == nullptr) { return; } // // are we dropping files? // if (event->mimeData()->hasUrls()) { QList textlist = event->mimeData()->urls(); // Try to get the KTextEditor::View that sent this, and activate it, so that the file opens in the // view where it was dropped KTextEditor::View *kVsender = qobject_cast(QObject::sender()); if (kVsender != nullptr) { QWidget *parent = kVsender->parentWidget(); if (parent != nullptr) { KateViewSpace *vs = qobject_cast(parent->parentWidget()); if (vs != nullptr) { m_viewManager->setActiveSpace(vs); } } } for (const QUrl &url : qAsConst(textlist)) { // if url has no file component, try and recursively scan dir KFileItem kitem(url); kitem.setDelayedMimeTypes(true); if (kitem.isDir()) { if (KMessageBox::questionYesNo(this, i18n("You dropped the directory %1 into Kate. " "Do you want to load all files contained in it ?", url.url()), i18n("Load files recursively?")) == KMessageBox::Yes) { KIO::ListJob *list_job = KIO::listRecursive(url, KIO::DefaultFlags, false); connect(list_job, &KIO::ListJob::entries, this, &KateMainWindow::slotListRecursiveEntries); } } else { m_viewManager->openUrl(url); } } } // // or are we dropping text? // else if (event->mimeData()->hasText()) { KTextEditor::Document *doc = KateApp::self()->documentManager()->createDoc(); doc->setText(event->mimeData()->text()); m_viewManager->activateView(doc); } } void KateMainWindow::slotListRecursiveEntries(KIO::Job *job, const KIO::UDSEntryList &list) { const QUrl dir = static_cast(job)->url(); for (const KIO::UDSEntry &entry : list) { if (!entry.isDir()) { QUrl url(dir); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + entry.stringValue(KIO::UDSEntry::UDS_NAME)); m_viewManager->openUrl(url); } } } void KateMainWindow::editKeys() { KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); const QList clients = guiFactory()->clients(); for (KXMLGUIClient *client : clients) { // FIXME there appear to be invalid clients after session switching // qCDebug(LOG_KATE)<<"adding client to shortcut editor"; // qCDebug(LOG_KATE)<actionCollection(); // qCDebug(LOG_KATE)<componentData().aboutData(); // qCDebug(LOG_KATE)<componentData().aboutData()->programName(); dlg.addCollection(client->actionCollection(), client->componentName()); } dlg.configure(); // reloadXML gui clients, to ensure all clients are up-to-date reloadXmlGui(); } void KateMainWindow::openUrl(const QString &name) { m_viewManager->openUrl(QUrl(name)); } void KateMainWindow::slotConfigure() { showPluginConfigPage(nullptr, 0); } bool KateMainWindow::showPluginConfigPage(KTextEditor::Plugin *configpageinterface, int id) { if (!m_viewManager->activeView()) { return false; } KateConfigDialog *dlg = new KateConfigDialog(this, m_viewManager->activeView()); if (configpageinterface) { dlg->showAppPluginPage(configpageinterface, id); } if (dlg->exec() == QDialog::Accepted) { m_fileOpenRecent->setMaxItems(KateConfigDialog::recentFilesMaxCount()); } delete dlg; m_viewManager->reactivateActiveView(); // gui (toolbars...) needs to be updated, because // of possible changes that the configure dialog // could have done on it, specially for plugins. return true; } QUrl KateMainWindow::activeDocumentUrl() { // anders: i make this one safe, as it may be called during // startup (by the file selector) KTextEditor::View *v = m_viewManager->activeView(); if (v) { return v->document()->url(); } return QUrl(); } void KateMainWindow::mSlotFixOpenWithMenu() { // dh: in bug #307699, this slot is called when launching the Kate application // unfortunately, no one ever could reproduce except users. KTextEditor::View *activeView = m_viewManager->activeView(); if (!activeView) { return; } // cleanup menu QMenu *menu = documentOpenWith->menu(); menu->clear(); // get a list of appropriate services. QMimeDatabase db; QMimeType mime = db.mimeTypeForName(activeView->document()->mimeType()); // qCDebug(LOG_KATE) << "mime type: " << mime.name(); QAction *a = nullptr; const KService::List offers = KMimeTypeTrader::self()->query(mime.name(), QStringLiteral("Application")); // add all default open-with-actions except "Kate" for (const auto &service : offers) { if (service->name() == QLatin1String("Kate")) { continue; } a = menu->addAction(QIcon::fromTheme(service->icon()), service->name()); a->setData(service->entryPath()); } // append "Other..." to call the KDE "open with" dialog. a = documentOpenWith->menu()->addAction(i18n("&Other...")); a->setData(QString()); } void KateMainWindow::slotOpenWithMenuAction(QAction *a) { QList list; list.append(m_viewManager->activeView()->document()->url()); const QString openWith = a->data().toString(); if (openWith.isEmpty()) { // display "open with" dialog KOpenWithDialog dlg(list); if (dlg.exec()) { KRun::runService(*dlg.service(), list, this); } return; } KService::Ptr app = KService::serviceByDesktopPath(openWith); if (app) { KRun::runService(*app, list, this); } else { KMessageBox::error(this, i18n("Application '%1' not found.", openWith), i18n("Application not found")); } } void KateMainWindow::pluginHelp() { KHelpClient::invokeHelp(QString(), QStringLiteral("kate-plugins")); } void KateMainWindow::aboutEditor() { KAboutApplicationDialog ad(KTextEditor::Editor::instance()->aboutData(), this); ad.exec(); } void KateMainWindow::slotFullScreen(bool t) { KToggleFullScreenAction::setFullScreen(this, t); QMenuBar *mb = menuBar(); if (t) { QToolButton *b = new QToolButton(mb); b->setDefaultAction(m_showFullScreenAction); b->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Ignored)); b->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); mb->setCornerWidget(b, Qt::TopRightCorner); b->setVisible(true); b->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else { QWidget *w = mb->cornerWidget(Qt::TopRightCorner); if (w) w->deleteLater(); } } bool KateMainWindow::showModOnDiskPrompt() { const auto documents = KateApp::self()->documentManager()->documentList(); DocVector list; list.reserve(documents.size()); for (auto doc : documents) { if (KateApp::self()->documentManager()->documentInfo(doc)->modifiedOnDisc && doc->isModified()) { list.append(doc); } } if (!list.isEmpty() && !m_modignore) { KateMwModOnHdDialog mhdlg(list, this); m_modignore = true; bool res = mhdlg.exec(); m_modignore = false; return res; } return true; } void KateMainWindow::slotDocumentCreated(KTextEditor::Document *doc) { connect(doc, SIGNAL(modifiedChanged(KTextEditor::Document *)), this, SLOT(updateCaption(KTextEditor::Document *))); connect(doc, SIGNAL(readWriteChanged(KTextEditor::Document *)), this, SLOT(updateCaption(KTextEditor::Document *))); connect(doc, SIGNAL(documentNameChanged(KTextEditor::Document *)), this, SLOT(updateCaption(KTextEditor::Document *))); connect(doc, SIGNAL(documentUrlChanged(KTextEditor::Document *)), this, SLOT(updateCaption(KTextEditor::Document *))); connect(doc, &KTextEditor::Document::documentNameChanged, this, &KateMainWindow::slotUpdateOpenWith); updateCaption(doc); } void KateMainWindow::updateCaption() { if (m_viewManager->activeView()) { updateCaption(m_viewManager->activeView()->document()); } } void KateMainWindow::updateCaption(KTextEditor::Document *doc) { if (!m_viewManager->activeView()) { setCaption(QString(), false); return; } // block signals from inactive docs if (!((KTextEditor::Document *)m_viewManager->activeView()->document() == doc)) { return; } QString c; if (m_viewManager->activeView()->document()->url().isEmpty() || (!m_paShowPath || !m_paShowPath->isChecked())) { c = ((KTextEditor::Document *)m_viewManager->activeView()->document())->documentName(); } else { c = m_viewManager->activeView()->document()->url().toString(QUrl::PreferLocalFile); const QString homePath = QDir::homePath(); if (c.startsWith(homePath)) { c = QLatin1String("~") + c.right(c.length() - homePath.length()); } } QString sessName = KateApp::self()->sessionManager()->activeSession()->name(); if (!sessName.isEmpty()) { sessName = QStringLiteral("%1: ").arg(sessName); } QString readOnlyCaption; if (!m_viewManager->activeView()->document()->isReadWrite()) { readOnlyCaption = i18n(" [read only]"); } setCaption(sessName + c + readOnlyCaption + QStringLiteral(" [*]"), m_viewManager->activeView()->document()->isModified()); } void KateMainWindow::saveProperties(KConfigGroup &config) { saveSession(config); // store all plugin view states int id = KateApp::self()->mainWindowID(this); const auto plugins = KateApp::self()->pluginManager()->pluginList(); for (const KatePluginInfo &item : plugins) { if (item.plugin && pluginViews().contains(item.plugin)) { if (auto interface = qobject_cast(pluginViews().value(item.plugin))) { KConfigGroup group(config.config(), QStringLiteral("Plugin:%1:MainWindow:%2").arg(item.saveName()).arg(id)); interface->writeSessionConfig(group); } } } saveOpenRecent(config.config()); m_viewManager->saveViewConfiguration(config); } void KateMainWindow::readProperties(const KConfigGroup &config) { // KDE5: TODO startRestore should take a const KConfigBase*, or even just a const KConfigGroup&, // but this propagates down to interfaces/kate/plugin.h so all plugins have to be ported KConfigBase *configBase = const_cast(config.config()); startRestore(configBase, config.name()); // perhaps enable plugin guis KateApp::self()->pluginManager()->enableAllPluginsGUI(this, configBase); finishRestore(); loadOpenRecent(config.config()); m_viewManager->restoreViewConfiguration(config); } void KateMainWindow::saveOpenRecent(KConfig *config) { m_fileOpenRecent->saveEntries(KConfigGroup(config, "Recent Files")); } void KateMainWindow::loadOpenRecent(const KConfig *config) { m_fileOpenRecent->loadEntries(KConfigGroup(config, "Recent Files")); } void KateMainWindow::saveGlobalProperties(KConfig *sessionConfig) { KateApp::self()->documentManager()->saveDocumentList(sessionConfig); KConfigGroup cg(sessionConfig, "General"); cg.writeEntry("Last Session", KateApp::self()->sessionManager()->activeSession()->name()); // save plugin config !! KateApp::self()->pluginManager()->writeConfig(sessionConfig); } void KateMainWindow::saveWindowConfig(const KConfigGroup &_config) { KConfigGroup config(_config); saveMainWindowSettings(config); KWindowConfig::saveWindowSize(windowHandle(), config); config.writeEntry("WindowState", int(((KParts::MainWindow *)this)->windowState())); config.sync(); } void KateMainWindow::restoreWindowConfig(const KConfigGroup &config) { setWindowState(Qt::WindowNoState); applyMainWindowSettings(config); KWindowConfig::restoreWindowSize(windowHandle(), config); setWindowState(QFlags(config.readEntry("WindowState", int(Qt::WindowActive)))); } void KateMainWindow::slotUpdateBottomViewBar() { // qCDebug(LOG_KATE)<<"slotUpdateHorizontalViewBar()"<activeView(); BarState bs = m_bottomViewBarMapping[view]; if (bs.bar() && bs.state()) { m_bottomContainerStack->setCurrentWidget(bs.bar()); m_bottomContainerStack->currentWidget()->show(); m_bottomViewBarContainer->show(); } else { QWidget *wid = m_bottomContainerStack->currentWidget(); if (wid) { wid->hide(); } // qCDebug(LOG_KATE)<hide(); } } void KateMainWindow::queueModifiedOnDisc(KTextEditor::Document *doc) { if (!m_modNotification) { return; } KateDocumentInfo *docInfo = KateApp::self()->documentManager()->documentInfo(doc); if (!docInfo) { return; } bool modOnDisk = (uint)docInfo->modifiedOnDisc; if (s_modOnHdDialog == nullptr && modOnDisk) { DocVector list; list.append(doc); s_modOnHdDialog = new KateMwModOnHdDialog(list, this); m_modignore = true; KWindowSystem::setOnAllDesktops(s_modOnHdDialog->winId(), true); s_modOnHdDialog->exec(); delete s_modOnHdDialog; // s_modOnHdDialog is set to 0 in destructor of KateMwModOnHdDialog (jowenn!!!) m_modignore = false; } else if (s_modOnHdDialog != nullptr) { s_modOnHdDialog->addDocument(doc); } } bool KateMainWindow::event(QEvent *e) { if (e->type() == QEvent::ShortcutOverride) { QKeyEvent *k = static_cast(e); emit unhandledShortcutOverride(k); } return KateMDI::MainWindow::event(e); } QObject *KateMainWindow::pluginView(const QString &name) { KTextEditor::Plugin *plugin = KateApp::self()->pluginManager()->plugin(name); if (!plugin) { return nullptr; } return m_pluginViews.contains(plugin) ? m_pluginViews.value(plugin) : nullptr; } void KateMainWindow::mousePressEvent(QMouseEvent *e) { switch (e->button()) { case Qt::ForwardButton: slotFocusNextTab(); break; case Qt::BackButton: slotFocusPrevTab(); break; default:; } } void KateMainWindow::slotFocusPrevTab() { if (m_viewManager->activeViewSpace()) { m_viewManager->activeViewSpace()->focusPrevTab(); } } void KateMainWindow::slotFocusNextTab() { if (m_viewManager->activeViewSpace()) { m_viewManager->activeViewSpace()->focusNextTab(); } } void KateMainWindow::slotQuickOpen() { /** * toggle back to view manager when when quick open is already shown */ if (m_mainStackedWidget->currentWidget() == m_quickOpen) { m_mainStackedWidget->setCurrentWidget(m_viewManager); centralWidget()->setFocusProxy(m_viewManager); return; } /** * show quick open and pass focus to it */ m_quickOpen->update(); m_mainStackedWidget->setCurrentWidget(m_quickOpen); centralWidget()->setFocusProxy(m_quickOpen); m_quickOpen->setFocus(); } QWidget *KateMainWindow::createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text) { return KateMDI::MainWindow::createToolView(plugin, identifier, (KMultiTabBar::KMultiTabBarPosition)(pos), icon, text); } bool KateMainWindow::moveToolView(QWidget *widget, KTextEditor::MainWindow::ToolViewPosition pos) { if (!qobject_cast(widget)) { return false; } return KateMDI::MainWindow::moveToolView(qobject_cast(widget), (KMultiTabBar::KMultiTabBarPosition)(pos)); } bool KateMainWindow::showToolView(QWidget *widget) { if (!qobject_cast(widget)) { return false; } return KateMDI::MainWindow::showToolView(qobject_cast(widget)); } bool KateMainWindow::hideToolView(QWidget *widget) { if (!qobject_cast(widget)) { return false; } return KateMDI::MainWindow::hideToolView(qobject_cast(widget)); } void KateMainWindow::setQuickOpenMatchMode(int mode) { m_quickOpen->setMatchMode(mode); } int KateMainWindow::quickOpenMatchMode() { return m_quickOpen->matchMode(); } void KateMainWindow::setQuickOpenListMode(KateQuickOpenModel::List mode) { m_quickOpen->setListMode(mode); } KateQuickOpenModel::List KateMainWindow::quickOpenListMode() const { return m_quickOpen->listMode(); } diff --git a/kate/katemainwindow.h b/kate/katemainwindow.h index a371ef549..31a889fde 100644 --- a/kate/katemainwindow.h +++ b/kate/katemainwindow.h @@ -1,636 +1,636 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ #ifndef __KATE_MAINWINDOW_H__ #define __KATE_MAINWINDOW_H__ #include "katemdi.h" #include "kateviewmanager.h" -#include #include #include +#include #include #include -#include #include -#include -#include +#include #include -#include +#include #include +#include #include +#include class QMenu; namespace KIO { class UDSEntry; typedef class QList UDSEntryList; } class KFileItem; class KRecentFilesAction; class KateViewManager; class KateMwModOnHdDialog; class KateQuickOpen; enum KateQuickOpenModelList : int; // Helper layout class to always provide minimum size class KateContainerStackedLayout : public QStackedLayout { Q_OBJECT public: KateContainerStackedLayout(QWidget *parent); QSize sizeHint() const override; QSize minimumSize() const override; }; class KateMainWindow : public KateMDI::MainWindow, virtual public KParts::PartBase { Q_OBJECT public: /** * Construct the window and restore its state from given config if any * @param sconfig session config for this window, 0 if none * @param sgroup session config group to use */ KateMainWindow(KConfig *sconfig, const QString &sgroup); /** * Destruct the nice window */ ~KateMainWindow() override; /** * Accessor methodes for interface and child objects */ public: KateViewManager *viewManager() { return m_viewManager; } /** * KTextEditor::MainWindow wrapper * @return KTextEditor::MainWindow wrapper. */ KTextEditor::MainWindow *wrapper() { return m_wrapper; } public: /** Returns the URL of the current document. * anders: I add this for use from the file selector. */ QUrl activeDocumentUrl(); /** * Prompts the user for what to do with files that are modified on disk if any. * This is optionally run when the window receives focus, and when the last * window is closed. * @return true if no documents are modified on disk, or all documents were * handled by the dialog; otherwise (the dialog was canceled) false. */ bool showModOnDiskPrompt(); public: /*reimp*/ void readProperties(const KConfigGroup &config) override; /*reimp*/ void saveProperties(KConfigGroup &config) override; /*reimp*/ void saveGlobalProperties(KConfig *sessionConfig) override; void saveOpenRecent(KConfig *config); void loadOpenRecent(const KConfig *config); public: bool queryClose_internal(KTextEditor::Document *doc = nullptr); /** * save the settings, size and state of this window in * the provided config group */ void saveWindowConfig(const KConfigGroup &); /** * restore the settings, size and state of this window from * the provided config group. */ void restoreWindowConfig(const KConfigGroup &); /** * save some global options to katerc */ void saveOptions(); private: /** * Setup actions which pointers are needed already in setupMainWindow */ void setupImportantActions(); void setupMainWindow(); void setupActions(); bool queryClose() override; void addMenuBarActionToContextMenu(); void removeMenuBarActionFromContextMenu(); /** * read some global options from katerc */ void readOptions(); void dragEnterEvent(QDragEnterEvent *) override; void dropEvent(QDropEvent *) override; public Q_SLOTS: void slotFileClose(); void slotFileQuit(); void queueModifiedOnDisc(KTextEditor::Document *doc); void slotFocusPrevTab(); void slotFocusNextTab(); /** * Show quick open */ void slotQuickOpen(); /** * Overwrite size hint for better default window sizes * @return size hint */ QSize sizeHint() const override; /** * slots used for actions in the menus/toolbars * or internal signal connections */ private Q_SLOTS: void newWindow(); void slotConfigure(); void slotOpenWithMenuAction(QAction *a); void slotEditToolbars(); void slotNewToolbarConfig(); void slotUpdateOpenWith(); void slotUpdateActionsNeedingUrl(); void slotOpenDocument(const QUrl &); void slotDropEvent(QDropEvent *); void editKeys(); void mSlotFixOpenWithMenu(); void reloadXmlGui(); /* to update the caption */ void slotDocumentCreated(KTextEditor::Document *doc); void updateCaption(KTextEditor::Document *doc); // calls updateCaption(doc) with the current document void updateCaption(); void pluginHelp(); void aboutEditor(); void slotFullScreen(bool); void slotListRecursiveEntries(KIO::Job *job, const KIO::UDSEntryList &list); private Q_SLOTS: void toggleShowMenuBar(bool showMessage = true); void toggleShowStatusBar(); void toggleShowTabBar(); public: bool showStatusBar(); bool showTabBar(); Q_SIGNALS: void statusBarToggled(); void tabBarToggled(); void unhandledShortcutOverride(QEvent *e); public: void openUrl(const QString &name = QString()); QHash &pluginViews() { return m_pluginViews; } QWidget *bottomViewBarContainer() { return m_bottomViewBarContainer; } void addToBottomViewBarContainer(KTextEditor::View *view, QWidget *bar) { m_bottomContainerStack->addWidget(bar); m_bottomViewBarMapping[view] = BarState(bar); } void hideBottomViewBarForView(KTextEditor::View *view) { BarState &state = m_bottomViewBarMapping[view]; if (state.bar()) { m_bottomContainerStack->setCurrentWidget(state.bar()); state.bar()->hide(); state.setState(false); } m_bottomViewBarContainer->hide(); } void showBottomViewBarForView(KTextEditor::View *view) { BarState &state = m_bottomViewBarMapping[view]; if (state.bar()) { m_bottomContainerStack->setCurrentWidget(state.bar()); state.bar()->show(); state.setState(true); m_bottomViewBarContainer->show(); } } void deleteBottomViewBarForView(KTextEditor::View *view) { BarState state = m_bottomViewBarMapping.take(view); if (state.bar()) { if (m_bottomContainerStack->currentWidget() == state.bar()) { m_bottomViewBarContainer->hide(); } delete state.bar(); } } bool modNotificationEnabled() const { return m_modNotification; } void setModNotificationEnabled(bool e) { m_modNotification = e; } bool modCloseAfterLast() const { return m_modCloseAfterLast; } void setModCloseAfterLast(bool e) { m_modCloseAfterLast = e; } void setQuickOpenMatchMode(int mode); int quickOpenMatchMode(); void setQuickOpenListMode(KateQuickOpenModelList mode); KateQuickOpenModelList quickOpenListMode() const; KRecentFilesAction *fileOpenRecent() const { return m_fileOpenRecent; } // // KTextEditor::MainWindow interface, get called by invokeMethod from our wrapper object! // public Q_SLOTS: /** * get the toplevel widget. * \return the real main window widget. */ QWidget *window() { return this; } /** * Accessor to the XMLGUIFactory. * \return the mainwindow's KXMLGUIFactory. */ KXMLGUIFactory *guiFactory() override { return KateMDI::MainWindow::guiFactory(); } /** * Get a list of all views for this main window. * @return all views */ QList views() { return viewManager()->views(); } /** * Access the active view. * \return active view */ KTextEditor::View *activeView() { return viewManager()->activeView(); } /** * Activate the view with the corresponding \p document. * If none exist for this document, create one * \param document the document * \return activated view of this document */ KTextEditor::View *activateView(KTextEditor::Document *document) { return viewManager()->activateView(document); } /** * Open the document \p url with the given \p encoding. * \param url the document's url * \param encoding the preferred encoding. If encoding is QString() the * encoding will be guessed or the default encoding will be used. * \return a pointer to the created view for the new document, if a document * with this url is already existing, its view will be activated */ KTextEditor::View *openUrl(const QUrl &url, const QString &encoding = QString()) { return viewManager()->openUrlWithView(url, encoding); } /** * Close selected view * \param view the view * \return true if view was closed */ bool closeView(KTextEditor::View *view) { m_viewManager->closeView(view); return true; } /** * Close the split view where the given view is contained. * \param view the view. * \return true if the split view was closed. */ bool closeSplitView(KTextEditor::View *view) { m_viewManager->closeViewSpace(view); return true; } /** * @returns true if the two given views share the same split view, * false otherwise. */ bool viewsInSameSplitView(KTextEditor::View *view1, KTextEditor::View *view2) { return m_viewManager->viewsInSameViewSpace(view1, view2); } /** * Split current view space according to \p orientation * \param orientation in which line split the view */ void splitView(Qt::Orientation orientation) { m_viewManager->splitViewSpace(nullptr, orientation); } /** * Try to create a view bar for the given view. * Its parameter is the view for which we want a view bar * @return suitable widget that can host view bars widgets or nullptr */ QWidget *createViewBar(KTextEditor::View *) { return bottomViewBarContainer(); } /** * Delete the view bar for the given view. * @param view view for which we want an view bar */ void deleteViewBar(KTextEditor::View *view) { deleteBottomViewBarForView(view); } /** * Add a widget to the view bar. * @param view view for which the view bar is used * @param bar bar widget, shall have the viewBarParent() as parent widget */ void addWidgetToViewBar(KTextEditor::View *view, QWidget *bar) { addToBottomViewBarContainer(view, bar); } /** * Show the view bar for the given view * @param view view for which the view bar is used */ void showViewBar(KTextEditor::View *view) { showBottomViewBarForView(view); } /** * Hide the view bar for the given view * @param view view for which the view bar is used */ void hideViewBar(KTextEditor::View *view) { hideBottomViewBarForView(view); } /** * Create a new toolview with unique \p identifier at side \p pos * with \p icon and caption \p text. Use the returned widget to embedd * your widgets. * \param plugin which owns this tool view * \param identifier unique identifier for this toolview * \param pos position for the toolview, if we are in session restore, * this is only a preference * \param icon icon to use in the sidebar for the toolview * \param text translated text (i18n()) to use in addition to icon * \return created toolview on success, otherwise NULL */ QWidget *createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text); /** * Move the toolview \p widget to position \p pos. * \param widget the toolview to move, where the widget was constructed * by createToolView(). * \param pos new position to move widget to * \return \e true on success, otherwise \e false */ bool moveToolView(QWidget *widget, KTextEditor::MainWindow::ToolViewPosition pos); /** * Show the toolview \p widget. * \param widget the toolview to show, where the widget was constructed * by createToolView(). * \return \e true on success, otherwise \e false * \todo add focus parameter: bool showToolView (QWidget *widget, bool giveFocus ); */ bool showToolView(QWidget *widget); /** * Hide the toolview \p widget. * \param widget the toolview to hide, where the widget was constructed * by createToolView(). * \return \e true on success, otherwise \e false */ bool hideToolView(QWidget *widget); /** * Shows the @p plugin's config page. The @p page specifies which * config page will be shown, see KTextEditor::Plugin::configPages(). * * \return \e true on success, otherwise \e false * \since 5.63 */ bool showPluginConfigPage(KTextEditor::Plugin *configpageinterface, int id); /** * Get a plugin view for the plugin with with identifier \p name. * \param name the plugin's name * \return pointer to the plugin view if a plugin with \p name is loaded and has a view for this mainwindow, * otherwise NULL */ QObject *pluginView(const QString &name); private Q_SLOTS: void slotUpdateBottomViewBar(); private Q_SLOTS: void slotDocumentCloseAll(); void slotDocumentCloseOther(); void slotDocumentCloseOther(KTextEditor::Document *document); void slotDocumentCloseSelected(const QList &); private: /** * Notify about file modifications from other processes? */ bool m_modNotification; /** * Shutdown Kate after last file is closed */ bool m_modCloseAfterLast; /** * stacked widget containing the central area, aka view manager, quickopen, ... */ QStackedWidget *m_mainStackedWidget; /** * quick open to fast switch documents */ KateQuickOpen *m_quickOpen; /** * keeps track of views */ KateViewManager *m_viewManager; KRecentFilesAction *m_fileOpenRecent; KActionMenu *documentOpenWith; KToggleAction *settingsShowFileselector; KToggleAction *m_showFullScreenAction; bool m_modignore; // all plugin views for this mainwindow, used by the pluginmanager QHash m_pluginViews; // options: show statusbar + show path KToggleAction *m_paShowPath; KToggleAction *m_paShowMenuBar; KToggleAction *m_paShowStatusBar; KToggleAction *m_paShowTabBar; QWidget *m_bottomViewBarContainer; KateContainerStackedLayout *m_bottomContainerStack; class BarState { public: BarState() = default; BarState(QWidget *bar) : m_bar(bar) , m_state(false) { } ~BarState() { } QWidget *bar() { return m_bar; } bool state() { return m_state; } void setState(bool state) { m_state = state; } private: QWidget *m_bar = nullptr; bool m_state = false; }; QHash m_bottomViewBarMapping; public: static void unsetModifiedOnDiscDialogIfIf(KateMwModOnHdDialog *diag) { if (s_modOnHdDialog == diag) { s_modOnHdDialog = nullptr; } } private: static KateMwModOnHdDialog *s_modOnHdDialog; /** * Wrapper of main window for KTextEditor */ KTextEditor::MainWindow *m_wrapper; public Q_SLOTS: void slotWindowActivated(); protected: bool event(QEvent *e) override; void mousePressEvent(QMouseEvent *e) override; }; #endif diff --git a/kate/katemdi.cpp b/kate/katemdi.cpp index 9a6d71bbd..28090b7ea 100644 --- a/kate/katemdi.cpp +++ b/kate/katemdi.cpp @@ -1,1083 +1,1083 @@ /* This file is part of the KDE libraries Copyright (C) 2005 Christoph Cullmann Copyright (C) 2002, 2003 Joseph Wenninger GUIClient partly based on ktoolbarhandler.cpp: Copyright (C) 2002 Simon Hausmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #include "katemdi.h" #include "katedebug.h" #include #include #include -#include -#include #include +#include #include -#include #include +#include +#include #include -#include #include -#include +#include +#include #include +#include #include #include #include -#include -#include #include +#include namespace KateMDI { // BEGIN TOGGLETOOLVIEWACTION // ToggleToolViewAction::ToggleToolViewAction(const QString &text, ToolView *tv, QObject *parent) : KToggleAction(text, parent) , m_tv(tv) { connect(this, &ToggleToolViewAction::toggled, this, &ToggleToolViewAction::slotToggled); connect(m_tv, &ToolView::toolVisibleChanged, this, &ToggleToolViewAction::toolVisibleChanged); setChecked(m_tv->toolVisible()); } ToggleToolViewAction::~ToggleToolViewAction() { } void ToggleToolViewAction::toolVisibleChanged(bool) { if (isChecked() != m_tv->toolVisible()) { setChecked(m_tv->toolVisible()); } } void ToggleToolViewAction::slotToggled(bool t) { if (t) { m_tv->mainWindow()->showToolView(m_tv); m_tv->setFocus(); } else { m_tv->mainWindow()->hideToolView(m_tv); } } // END TOGGLETOOLVIEWACTION // BEGIN GUICLIENT static const QString actionListName = QStringLiteral("kate_mdi_view_actions"); // please don't use QStringLiteral since it can't be used with a concatenated string parameter on all platforms static const QString guiDescription = QStringLiteral( "" "" "" " " " " " " "" ""); GUIClient::GUIClient(MainWindow *mw) : QObject(mw) , KXMLGUIClient(mw) , m_mw(mw) { connect(m_mw->guiFactory(), &KXMLGUIFactory::clientAdded, this, &GUIClient::clientAdded); if (domDocument().documentElement().isNull()) { QString completeDescription = guiDescription.arg(actionListName); setXML(completeDescription, false /*merge*/); } m_toolMenu = new KActionMenu(i18n("Tool &Views"), this); actionCollection()->addAction(QStringLiteral("kate_mdi_toolview_menu"), m_toolMenu); m_showSidebarsAction = new KToggleAction(i18n("Show Side&bars"), this); actionCollection()->addAction(QStringLiteral("kate_mdi_sidebar_visibility"), m_showSidebarsAction); actionCollection()->setDefaultShortcut(m_showSidebarsAction, Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_F); m_showSidebarsAction->setChecked(m_mw->sidebarsVisible()); connect(m_showSidebarsAction, &KToggleAction::toggled, m_mw, &MainWindow::setSidebarsVisible); m_toolMenu->addAction(m_showSidebarsAction); QAction *sep_act = new QAction(this); sep_act->setSeparator(true); m_toolMenu->addAction(sep_act); // read shortcuts actionCollection()->setConfigGroup(QStringLiteral("Shortcuts")); actionCollection()->readSettings(); actionCollection()->addAssociatedWidget(m_mw); const auto actions = actionCollection()->actions(); for (QAction *action : actions) action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } GUIClient::~GUIClient() { } void GUIClient::updateSidebarsVisibleAction() { m_showSidebarsAction->setChecked(m_mw->sidebarsVisible()); } void GUIClient::registerToolView(ToolView *tv) { QString aname = QLatin1String("kate_mdi_toolview_") + tv->id; // try to read the action shortcut QList shortcuts; KSharedConfigPtr cfg = KSharedConfig::openConfig(); QString shortcutString = cfg->group("Shortcuts").readEntry(aname, QString()); const auto shortcutStrings = shortcutString.split(QLatin1Char(';')); for (const QString &shortcut : shortcutStrings) { shortcuts << QKeySequence::fromString(shortcut); } KToggleAction *a = new ToggleToolViewAction(i18n("Show %1", tv->text), tv, this); actionCollection()->setDefaultShortcuts(a, shortcuts); actionCollection()->addAction(aname, a); m_toolViewActions.append(a); m_toolMenu->addAction(a); m_toolToAction.insert(tv, a); updateActions(); } void GUIClient::unregisterToolView(ToolView *tv) { QAction *a = m_toolToAction[tv]; if (!a) { return; } m_toolViewActions.removeAt(m_toolViewActions.indexOf(a)); delete a; m_toolToAction.remove(tv); updateActions(); } void GUIClient::clientAdded(KXMLGUIClient *client) { if (client == this) { updateActions(); } } void GUIClient::updateActions() { if (!factory()) { return; } unplugActionList(actionListName); QList addList; addList.append(m_toolMenu); plugActionList(actionListName, addList); } // END GUICLIENT // BEGIN TOOLVIEW ToolView::ToolView(MainWindow *mainwin, Sidebar *sidebar, QWidget *parent) : QFrame(parent) , m_mainWin(mainwin) , m_sidebar(sidebar) , m_toolbar(nullptr) , m_toolVisible(false) , persistent(false) { // try to fix resize policy QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred); policy.setRetainSizeWhenHidden(true); setSizePolicy(policy); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); m_toolbar = new KToolBar(this); m_toolbar->setVisible(false); m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); // ensure reasonable icons sizes, like e.g. the quick-open and co. icons // the normal toolbar sizes are TOO large, e.g. for scaled stuff even more! const int iconSize = style()->pixelMetric(QStyle::PM_ButtonIconSize, nullptr, this); m_toolbar->setIconSize(QSize(iconSize, iconSize)); } QSize ToolView::sizeHint() const { return size(); } QSize ToolView::minimumSizeHint() const { return QSize(160, 160); } ToolView::~ToolView() { m_mainWin->toolViewDeleted(this); } void ToolView::setToolVisible(bool vis) { if (m_toolVisible == vis) { return; } m_toolVisible = vis; emit toolVisibleChanged(m_toolVisible); } bool ToolView::toolVisible() const { return m_toolVisible; } void ToolView::childEvent(QChildEvent *ev) { // set the widget to be focus proxy if possible if ((ev->type() == QEvent::ChildAdded) && qobject_cast(ev->child())) { QWidget *widget = qobject_cast(ev->child()); setFocusProxy(widget); layout()->addWidget(widget); } QFrame::childEvent(ev); } void ToolView::actionEvent(QActionEvent *event) { QFrame::actionEvent(event); if (event->type() == QEvent::ActionAdded) { m_toolbar->addAction(event->action()); } else if (event->type() == QEvent::ActionRemoved) { m_toolbar->removeAction(event->action()); } m_toolbar->setVisible(!m_toolbar->actions().isEmpty()); } // END TOOLVIEW // BEGIN SIDEBAR Sidebar::Sidebar(KMultiTabBar::KMultiTabBarPosition pos, MainWindow *mainwin, QWidget *parent) : KMultiTabBar(pos, parent) , m_mainWin(mainwin) , m_splitter(nullptr) , m_ownSplit(nullptr) , m_lastSize(0) { hide(); } Sidebar::~Sidebar() { } void Sidebar::setSplitter(QSplitter *sp) { m_splitter = sp; m_ownSplit = new QSplitter((position() == KMultiTabBar::Top || position() == KMultiTabBar::Bottom) ? Qt::Horizontal : Qt::Vertical, m_splitter); m_ownSplit->setChildrenCollapsible(false); m_ownSplit->hide(); } ToolView *Sidebar::addWidget(const QIcon &icon, const QString &text, ToolView *widget) { static int id = 0; if (widget) { if (widget->sidebar() == this) { return widget; } widget->sidebar()->removeWidget(widget); } int newId = ++id; appendTab(icon, newId, text); if (!widget) { widget = new ToolView(m_mainWin, this, m_ownSplit); widget->hide(); widget->icon = icon; widget->text = text; } else { widget->hide(); widget->setParent(m_ownSplit); widget->m_sidebar = this; } // save its pos ;) widget->persistent = false; m_idToWidget.insert(newId, widget); m_widgetToId.insert(widget, newId); m_toolviews.push_back(widget); // widget => size, for correct size restoration after hide/show // starts with invalid size m_widgetToSize.insert(widget, QSize()); show(); connect(tab(newId), SIGNAL(clicked(int)), this, SLOT(tabClicked(int))); tab(newId)->installEventFilter(this); tab(newId)->setToolTip(QString()); return widget; } bool Sidebar::removeWidget(ToolView *widget) { if (!m_widgetToId.contains(widget)) { return false; } removeTab(m_widgetToId[widget]); m_idToWidget.remove(m_widgetToId[widget]); m_widgetToId.remove(widget); m_widgetToSize.remove(widget); m_toolviews.removeAt(m_toolviews.indexOf(widget)); bool anyVis = false; QMapIterator it(m_idToWidget); while (it.hasNext()) { it.next(); if ((anyVis = it.value()->isVisible())) { break; } } if (m_idToWidget.isEmpty()) { m_ownSplit->hide(); hide(); } else if (!anyVis) { m_ownSplit->hide(); } return true; } bool Sidebar::showWidget(ToolView *widget) { if (!m_widgetToId.contains(widget)) { return false; } // hide other non-persistent views QMapIterator it(m_idToWidget); bool unfixSize = false; while (it.hasNext()) { it.next(); if ((it.value() != widget) && !it.value()->persistent) { hideWidget(it.value()); } // lock persistents' size while show/hide reshuffle happens // (could also make this behavior per-widget configurable) if (it.value()->persistent) { auto w = it.value(); auto s = w->size(); w->setMinimumSize(s); w->setMaximumSize(s); unfixSize = true; } } setTab(m_widgetToId[widget], true); /** * resize to right size again and show, else artifacts */ if (m_widgetToSize[widget].isValid()) { widget->resize(m_widgetToSize[widget]); } /** * resize to right size again and show, else artifacts * same as for widget, both needed */ if (m_preHideSize.isValid()) { widget->resize(m_preHideSize); m_ownSplit->resize(m_preHideSize); } m_ownSplit->show(); widget->show(); // release persistent size again // (later on, when all event processing has happened) auto func = [this]() { auto wsizes = m_ownSplit->sizes(); for (auto w : m_idToWidget) { w->setMinimumSize(QSize(0, 0)); w->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); } m_ownSplit->setSizes(wsizes); }; if (unfixSize) { QTimer::singleShot(0, this, func); } /** * we are visible again! */ widget->setToolVisible(true); return true; } bool Sidebar::hideWidget(ToolView *widget) { if (!m_widgetToId.contains(widget)) { return false; } bool anyVis = false; updateLastSize(); QMapIterator it(m_idToWidget); while (it.hasNext()) { it.next(); if (it.value() == widget) { // remember size and hide if (widget->isVisible()) { m_widgetToSize[widget] = widget->size(); } } else if ((anyVis = it.value()->isVisible())) { break; } } widget->hide(); // lower tab setTab(m_widgetToId[widget], false); if (!anyVis) { if (m_ownSplit->isVisible()) { m_preHideSize = m_ownSplit->size(); } m_ownSplit->hide(); } widget->setToolVisible(false); return true; } void Sidebar::tabClicked(int i) { ToolView *w = m_idToWidget[i]; if (!w) { return; } if (isTabRaised(i)) { showWidget(w); w->setFocus(); } else { hideWidget(w); } } bool Sidebar::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() == QEvent::ContextMenu) { QContextMenuEvent *e = (QContextMenuEvent *)ev; KMultiTabBarTab *bt = dynamic_cast(obj); if (bt) { // qCDebug(LOG_KATE) << "Request for popup"; m_popupButton = bt->id(); ToolView *w = m_idToWidget[m_popupButton]; if (w) { QMenu *menu = new QMenu(this); if (!w->plugin.isNull()) { if (w->plugin.data()->configPages() > 0) { menu->addAction(i18n("Configure ..."))->setData(20); } } menu->addSection(QIcon::fromTheme(QStringLiteral("view_remove")), i18n("Behavior")); menu->addAction(w->persistent ? QIcon::fromTheme(QStringLiteral("view-restore")) : QIcon::fromTheme(QStringLiteral("view-fullscreen")), w->persistent ? i18n("Make Non-Persistent") : i18n("Make Persistent"))->setData(10); menu->addSection(QIcon::fromTheme(QStringLiteral("move")), i18n("Move To")); if (position() != 0) { menu->addAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Left Sidebar"))->setData(0); } if (position() != 1) { menu->addAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Right Sidebar"))->setData(1); } if (position() != 2) { menu->addAction(QIcon::fromTheme(QStringLiteral("go-up")), i18n("Top Sidebar"))->setData(2); } if (position() != 3) { menu->addAction(QIcon::fromTheme(QStringLiteral("go-down")), i18n("Bottom Sidebar"))->setData(3); } connect(menu, &QMenu::triggered, this, &Sidebar::buttonPopupActivate); menu->exec(e->globalPos()); delete menu; return true; } } } return false; } void Sidebar::setVisible(bool visible) { // visible==true means show-request if (visible && (m_idToWidget.isEmpty() || !m_mainWin->sidebarsVisible())) { return; } KMultiTabBar::setVisible(visible); } void Sidebar::buttonPopupActivate(QAction *a) { int id = a->data().toInt(); ToolView *w = m_idToWidget[m_popupButton]; if (!w) { return; } // move ids if (id < 4) { // move + show ;) m_mainWin->moveToolView(w, (KMultiTabBar::KMultiTabBarPosition)id); m_mainWin->showToolView(w); } // toggle persistent if (id == 10) { w->persistent = !w->persistent; } // configure actionCollection if (id == 20) { if (!w->plugin.isNull()) { if (w->plugin.data()->configPages() > 0) { emit sigShowPluginConfigPage(w->plugin.data(), 0); } } } } void Sidebar::updateLastSize() { QList s = m_splitter->sizes(); int i = 0; if ((position() == KMultiTabBar::Right || position() == KMultiTabBar::Bottom)) { i = 2; } // little threshold if (s[i] > 2) { m_lastSize = s[i]; } } class TmpToolViewSorter { public: ToolView *tv; unsigned int pos; }; void Sidebar::restoreSession(KConfigGroup &config) { // get the last correct placed toolview int firstWrong = 0; for (; firstWrong < m_toolviews.size(); ++firstWrong) { ToolView *tv = m_toolviews[firstWrong]; int pos = config.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Sidebar-Position").arg(tv->id), firstWrong); if (pos != firstWrong) { break; } } // we need to reshuffle, ahhh :( if (firstWrong < m_toolviews.size()) { // first: collect the items to reshuffle QList toSort; for (int i = firstWrong; i < m_toolviews.size(); ++i) { TmpToolViewSorter s; s.tv = m_toolviews[i]; s.pos = config.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Sidebar-Position").arg(m_toolviews[i]->id), i); toSort.push_back(s); } // now: sort the stuff we need to reshuffle for (int m = 0; m < toSort.size(); ++m) for (int n = m + 1; n < toSort.size(); ++n) if (toSort[n].pos < toSort[m].pos) { TmpToolViewSorter tmp = toSort[n]; toSort[n] = toSort[m]; toSort[m] = tmp; } // then: remove this items from the button bar // do this backwards, to minimize the relayout efforts for (int i = m_toolviews.size() - 1; i >= (int)firstWrong; --i) { removeTab(m_widgetToId[m_toolviews[i]]); } // insert the reshuffled things in order :) for (int i = 0; i < toSort.size(); ++i) { ToolView *tv = toSort[i].tv; m_toolviews[firstWrong + i] = tv; // readd the button int newId = m_widgetToId[tv]; appendTab(tv->icon, newId, tv->text); connect(tab(newId), SIGNAL(clicked(int)), this, SLOT(tabClicked(int))); tab(newId)->installEventFilter(this); tab(newId)->setToolTip(QString()); // reshuffle in splitter: move to last m_ownSplit->addWidget(tv); } } // update last size if needed updateLastSize(); // restore the own splitter sizes QList s = config.readEntry(QStringLiteral("Kate-MDI-Sidebar-%1-Splitter").arg(position()), QList()); m_ownSplit->setSizes(s); // show only correct toolviews, remember persistent values ;) bool anyVis = false; for (auto tv : qAsConst(m_toolviews)) { tv->persistent = config.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Persistent").arg(tv->id), false); tv->setToolVisible(config.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Visible").arg(tv->id), false)); if (!anyVis) { anyVis = tv->toolVisible(); } setTab(m_widgetToId[tv], tv->toolVisible()); if (tv->toolVisible()) { tv->show(); } else { tv->hide(); } } if (anyVis) { m_ownSplit->show(); } else { m_ownSplit->hide(); } } void Sidebar::saveSession(KConfigGroup &config) { // store the own splitter sizes QList s = m_ownSplit->sizes(); config.writeEntry(QStringLiteral("Kate-MDI-Sidebar-%1-Splitter").arg(position()), s); // store the data about all toolviews in this sidebar ;) for (int i = 0; i < m_toolviews.size(); ++i) { ToolView *tv = m_toolviews[i]; config.writeEntry(QStringLiteral("Kate-MDI-ToolView-%1-Position").arg(tv->id), int(tv->sidebar()->position())); config.writeEntry(QStringLiteral("Kate-MDI-ToolView-%1-Sidebar-Position").arg(tv->id), i); config.writeEntry(QStringLiteral("Kate-MDI-ToolView-%1-Visible").arg(tv->id), tv->toolVisible()); config.writeEntry(QStringLiteral("Kate-MDI-ToolView-%1-Persistent").arg(tv->id), tv->persistent); } } // END SIDEBAR // BEGIN MAIN WINDOW MainWindow::MainWindow(QWidget *parentWidget) : KParts::MainWindow(parentWidget, Qt::Window) , m_guiClient(new GUIClient(this)) { // init the internal widgets QFrame *hb = new QFrame(this); QHBoxLayout *hlayout = new QHBoxLayout(hb); hlayout->setContentsMargins(0, 0, 0, 0); hlayout->setSpacing(0); setCentralWidget(hb); m_sidebars[KMultiTabBar::Left] = new Sidebar(KMultiTabBar::Left, this, hb); hlayout->addWidget(m_sidebars[KMultiTabBar::Left]); m_hSplitter = new QSplitter(Qt::Horizontal, hb); hlayout->addWidget(m_hSplitter); m_sidebars[KMultiTabBar::Left]->setSplitter(m_hSplitter); QFrame *vb = new QFrame(m_hSplitter); QVBoxLayout *vlayout = new QVBoxLayout(vb); vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing(0); m_hSplitter->setCollapsible(m_hSplitter->indexOf(vb), false); m_hSplitter->setStretchFactor(m_hSplitter->indexOf(vb), 1); m_sidebars[KMultiTabBar::Top] = new Sidebar(KMultiTabBar::Top, this, vb); vlayout->addWidget(m_sidebars[KMultiTabBar::Top]); m_vSplitter = new QSplitter(Qt::Vertical, vb); vlayout->addWidget(m_vSplitter); m_sidebars[KMultiTabBar::Top]->setSplitter(m_vSplitter); m_centralWidget = new QWidget(m_vSplitter); m_centralWidget->setLayout(new QVBoxLayout); m_centralWidget->layout()->setSpacing(0); m_centralWidget->layout()->setContentsMargins(0, 0, 0, 0); m_vSplitter->setCollapsible(m_vSplitter->indexOf(m_centralWidget), false); m_vSplitter->setStretchFactor(m_vSplitter->indexOf(m_centralWidget), 1); m_sidebars[KMultiTabBar::Bottom] = new Sidebar(KMultiTabBar::Bottom, this, vb); vlayout->addWidget(m_sidebars[KMultiTabBar::Bottom]); m_sidebars[KMultiTabBar::Bottom]->setSplitter(m_vSplitter); m_sidebars[KMultiTabBar::Right] = new Sidebar(KMultiTabBar::Right, this, hb); hlayout->addWidget(m_sidebars[KMultiTabBar::Right]); m_sidebars[KMultiTabBar::Right]->setSplitter(m_hSplitter); for (const auto sidebar : qAsConst(m_sidebars)) { connect(sidebar, &Sidebar::sigShowPluginConfigPage, this, &MainWindow::sigShowPluginConfigPage); } } MainWindow::~MainWindow() { // cu toolviews qDeleteAll(m_toolviews); // seems like we really should delete this by hand ;) delete m_centralWidget; // cleanup the sidebars for (auto sidebar : qAsConst(m_sidebars)) { delete sidebar; } } QWidget *MainWindow::centralWidget() const { return m_centralWidget; } ToolView *MainWindow::createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KMultiTabBar::KMultiTabBarPosition pos, const QIcon &icon, const QString &text) { if (m_idToWidget[identifier]) { return nullptr; } // try the restore config to figure out real pos if (m_restoreConfig && m_restoreConfig->hasGroup(m_restoreGroup)) { KConfigGroup cg(m_restoreConfig, m_restoreGroup); pos = (KMultiTabBar::KMultiTabBarPosition)cg.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Position").arg(identifier), int(pos)); } ToolView *v = m_sidebars[pos]->addWidget(icon, text, nullptr); v->id = identifier; v->plugin = plugin; m_idToWidget.insert(identifier, v); m_toolviews.push_back(v); // register for menu stuff m_guiClient->registerToolView(v); return v; } ToolView *MainWindow::toolView(const QString &identifier) const { return m_idToWidget[identifier]; } void MainWindow::toolViewDeleted(ToolView *widget) { if (!widget) { return; } if (widget->mainWindow() != this) { return; } // unregister from menu stuff m_guiClient->unregisterToolView(widget); widget->sidebar()->removeWidget(widget); m_idToWidget.remove(widget->id); m_toolviews.removeAt(m_toolviews.indexOf(widget)); } void MainWindow::setSidebarsVisible(bool visible) { bool old_visible = m_sidebarsVisible; m_sidebarsVisible = visible; m_sidebars[0]->setVisible(visible); m_sidebars[1]->setVisible(visible); m_sidebars[2]->setVisible(visible); m_sidebars[3]->setVisible(visible); m_guiClient->updateSidebarsVisibleAction(); // show information message box, if the users hides the sidebars if (old_visible && (!m_sidebarsVisible)) { KMessageBox::information(this, i18n("You are about to hide the sidebars. With " "hidden sidebars it is not possible to directly " "access the tool views with the mouse anymore, " "so if you need to access the sidebars again " "invoke View > Tool Views > Show Sidebars " "in the menu. It is still possible to show/hide " "the tool views with the assigned shortcuts."), QString(), QStringLiteral("Kate hide sidebars notification message")); } } bool MainWindow::sidebarsVisible() const { return m_sidebarsVisible; } void MainWindow::setToolViewStyle(KMultiTabBar::KMultiTabBarStyle style) { m_sidebars[0]->setStyle(style); m_sidebars[1]->setStyle(style); m_sidebars[2]->setStyle(style); m_sidebars[3]->setStyle(style); } KMultiTabBar::KMultiTabBarStyle MainWindow::toolViewStyle() const { // all sidebars have the same style, so just take Top return m_sidebars[KMultiTabBar::Top]->tabStyle(); } bool MainWindow::moveToolView(ToolView *widget, KMultiTabBar::KMultiTabBarPosition pos) { if (!widget || widget->mainWindow() != this) { return false; } // try the restore config to figure out real pos if (m_restoreConfig && m_restoreConfig->hasGroup(m_restoreGroup)) { KConfigGroup cg(m_restoreConfig, m_restoreGroup); pos = (KMultiTabBar::KMultiTabBarPosition)cg.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Position").arg(widget->id), int(pos)); } m_sidebars[pos]->addWidget(widget->icon, widget->text, widget); return true; } bool MainWindow::showToolView(ToolView *widget) { if (!widget || widget->mainWindow() != this) { return false; } // skip this if happens during restoring, or we will just see flicker if (m_restoreConfig && m_restoreConfig->hasGroup(m_restoreGroup)) { return true; } return widget->sidebar()->showWidget(widget); } bool MainWindow::hideToolView(ToolView *widget) { if (!widget || widget->mainWindow() != this) { return false; } // skip this if happens during restoring, or we will just see flicker if (m_restoreConfig && m_restoreConfig->hasGroup(m_restoreGroup)) { return true; } const bool ret = widget->sidebar()->hideWidget(widget); m_centralWidget->setFocus(); return ret; } void MainWindow::startRestore(KConfigBase *config, const QString &group) { // first save this stuff m_restoreConfig = config; m_restoreGroup = group; if (!m_restoreConfig || !m_restoreConfig->hasGroup(m_restoreGroup)) { // if no config around, set already now sane default sizes // otherwise, set later in ::finishRestore(), since it does not work // if set already now (see bug #164438) QList hs = (QList() << 200 << 100 << 200); QList vs = (QList() << 150 << 100 << 200); m_sidebars[0]->setLastSize(hs[0]); m_sidebars[1]->setLastSize(hs[2]); m_sidebars[2]->setLastSize(vs[0]); m_sidebars[3]->setLastSize(vs[2]); m_hSplitter->setSizes(hs); m_vSplitter->setSizes(vs); return; } // apply size once, to get sizes ready ;) KConfigGroup cg(m_restoreConfig, m_restoreGroup); KWindowConfig::restoreWindowSize(windowHandle(), cg); setToolViewStyle((KMultiTabBar::KMultiTabBarStyle)cg.readEntry("Kate-MDI-Sidebar-Style", (int)toolViewStyle())); // after reading m_sidebarsVisible, update the GUI toggle action m_sidebarsVisible = cg.readEntry("Kate-MDI-Sidebar-Visible", true); m_guiClient->updateSidebarsVisibleAction(); } void MainWindow::finishRestore() { if (!m_restoreConfig) { return; } if (m_restoreConfig->hasGroup(m_restoreGroup)) { // apply all settings, like toolbar pos and more ;) KConfigGroup cg(m_restoreConfig, m_restoreGroup); applyMainWindowSettings(cg); // reshuffle toolviews only if needed for (const auto tv : qAsConst(m_toolviews)) { KMultiTabBar::KMultiTabBarPosition newPos = (KMultiTabBar::KMultiTabBarPosition)cg.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Position").arg(tv->id), int(tv->sidebar()->position())); if (tv->sidebar()->position() != newPos) { moveToolView(tv, newPos); } } // restore the sidebars for (auto sidebar : qAsConst(m_sidebars)) { sidebar->restoreSession(cg); } // restore splitter sizes QList hs = (QList() << 200 << 100 << 200); QList vs = (QList() << 150 << 100 << 200); // get main splitter sizes ;) hs = cg.readEntry("Kate-MDI-H-Splitter", hs); vs = cg.readEntry("Kate-MDI-V-Splitter", vs); m_sidebars[0]->setLastSize(hs[0]); m_sidebars[1]->setLastSize(hs[2]); m_sidebars[2]->setLastSize(vs[0]); m_sidebars[3]->setLastSize(vs[2]); m_hSplitter->setSizes(hs); m_vSplitter->setSizes(vs); } // clear this stuff, we are done ;) m_restoreConfig = nullptr; m_restoreGroup.clear(); } void MainWindow::saveSession(KConfigGroup &config) { saveMainWindowSettings(config); // save main splitter sizes ;) QList hs = m_hSplitter->sizes(); QList vs = m_vSplitter->sizes(); if (hs[0] <= 2 && !m_sidebars[0]->splitterVisible()) { hs[0] = m_sidebars[0]->lastSize(); } if (hs[2] <= 2 && !m_sidebars[1]->splitterVisible()) { hs[2] = m_sidebars[1]->lastSize(); } if (vs[0] <= 2 && !m_sidebars[2]->splitterVisible()) { vs[0] = m_sidebars[2]->lastSize(); } if (vs[2] <= 2 && !m_sidebars[3]->splitterVisible()) { vs[2] = m_sidebars[3]->lastSize(); } config.writeEntry("Kate-MDI-H-Splitter", hs); config.writeEntry("Kate-MDI-V-Splitter", vs); // save sidebar style config.writeEntry("Kate-MDI-Sidebar-Style", (int)toolViewStyle()); config.writeEntry("Kate-MDI-Sidebar-Visible", m_sidebarsVisible); // save the sidebars for (auto sidebar : qAsConst(m_sidebars)) { sidebar->saveSession(config); } } // END MAIN WINDOW } // namespace KateMDI diff --git a/kate/katemdi.h b/kate/katemdi.h index 9aa991beb..5062b4f88 100644 --- a/kate/katemdi.h +++ b/kate/katemdi.h @@ -1,462 +1,462 @@ /* This file is part of the KDE libraries Copyright (C) 2005 Christoph Cullmann Copyright (C) 2002, 2003 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef __KATE_MDI_H__ #define __KATE_MDI_H__ #include #include #include -#include #include +#include -#include -#include -#include -#include #include -#include +#include #include +#include +#include +#include +#include class KActionMenu; class QAction; class QPixmap; class KConfigBase; namespace KTextEditor { class ConfigPageInterface; } namespace KateMDI { class ToolView; class ToggleToolViewAction : public KToggleAction { Q_OBJECT public: ToggleToolViewAction(const QString &text, ToolView *tv, QObject *parent); ~ToggleToolViewAction() override; protected Q_SLOTS: void slotToggled(bool) override; void toolVisibleChanged(bool); private: ToolView *m_tv; }; class GUIClient : public QObject, public KXMLGUIClient { Q_OBJECT public: GUIClient(class MainWindow *mw); ~GUIClient() override; void registerToolView(ToolView *tv); void unregisterToolView(ToolView *tv); void updateSidebarsVisibleAction(); private Q_SLOTS: void clientAdded(KXMLGUIClient *client); void updateActions(); private: MainWindow *m_mw; KToggleAction *m_showSidebarsAction; QList m_toolViewActions; QMap m_toolToAction; KActionMenu *m_toolMenu; }; class ToolView : public QFrame { Q_OBJECT friend class Sidebar; friend class MainWindow; friend class GUIClient; friend class ToggleToolViewAction; protected: /** * ToolView * Objects of this clas represent a toolview in the mainwindow * you should only add one widget as child to this toolview, it will * be automatically set to be the focus proxy of the toolview * @param mainwin main window for this toolview * @param sidebar sidebar of this toolview * @param parent parent widget, e.g. the splitter of one of the sidebars */ ToolView(class MainWindow *mainwin, class Sidebar *sidebar, QWidget *parent); public: /** * destruct me, this is allowed for all, will care itself that the toolview is removed * from the mainwindow and sidebar */ ~ToolView() override; Q_SIGNALS: /** * toolview hidden or shown * @param visible is this toolview made visible? */ void toolVisibleChanged(bool visible); /** * some internal methodes needed by the main window and the sidebars */ protected: MainWindow *mainWindow() { return m_mainWin; } Sidebar *sidebar() { return m_sidebar; } void setToolVisible(bool vis); public: bool toolVisible() const; QSize sizeHint() const override; QSize minimumSizeHint() const override; protected: void childEvent(QChildEvent *ev) override; void actionEvent(QActionEvent *event) override; private: MainWindow *m_mainWin; Sidebar *m_sidebar; KToolBar *m_toolbar; /// plugin this view belongs to, may be 0 QPointer plugin; /** * unique id */ QString id; /** * is visible in sidebar */ bool m_toolVisible; /** * is this view persistent? */ bool persistent; QIcon icon; QString text; }; class Sidebar : public KMultiTabBar { Q_OBJECT public: Sidebar(KMultiTabBar::KMultiTabBarPosition pos, class MainWindow *mainwin, QWidget *parent); ~Sidebar() override; void setSplitter(QSplitter *sp); public: ToolView *addWidget(const QIcon &icon, const QString &text, ToolView *widget); bool removeWidget(ToolView *widget); bool showWidget(ToolView *widget); bool hideWidget(ToolView *widget); void setLastSize(int s) { m_lastSize = s; } int lastSize() const { return m_lastSize; } void updateLastSize(); bool splitterVisible() const { return m_ownSplit->isVisible(); } void restoreSession(); /** * restore the current session config from given object, use current group * @param config config object to use */ void restoreSession(KConfigGroup &config); /** * save the current session config to given object, use current group * @param config config object to use */ void saveSession(KConfigGroup &config); public Q_SLOTS: // reimplemented, to block a show() call if all sidebars are forced hidden void setVisible(bool visible) override; private Q_SLOTS: void tabClicked(int); protected: bool eventFilter(QObject *obj, QEvent *ev) override; private Q_SLOTS: void buttonPopupActivate(QAction *); private: MainWindow *m_mainWin; KMultiTabBar::KMultiTabBarPosition m_pos; QSplitter *m_splitter; KMultiTabBar *m_tabBar; QSplitter *m_ownSplit; QMap m_idToWidget; QMap m_widgetToId; QMap m_widgetToSize; /** * list of all toolviews around in this sidebar */ QList m_toolviews; int m_lastSize; QSize m_preHideSize; int m_popupButton; Q_SIGNALS: void sigShowPluginConfigPage(KTextEditor::Plugin *configpageinterface, int id); }; class MainWindow : public KParts::MainWindow { Q_OBJECT friend class ToolView; // // Constructor area // public: /** * Constructor */ MainWindow(QWidget *parentWidget = nullptr); /** * Destructor */ ~MainWindow() override; // // public interfaces // /** * add a given widget to the given sidebar if possible, name is very important * @param plugin pointer to the plugin * @param identifier unique identifier for this toolview * @param pos position for the toolview, if we are in session restore, this is only a preference * @param icon icon to use for the toolview * @param text text to use in addition to icon * @return created toolview on success or 0 */ ToolView *createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KMultiTabBar::KMultiTabBarPosition pos, const QIcon &icon, const QString &text); /** * give you handle to toolview for the given name, 0 if no toolview around * @param identifier toolview name * @return toolview if existing, else 0 */ ToolView *toolView(const QString &identifier) const; /** * set the toolview's tabbar style. * @param style the tabbar style. */ void setToolViewStyle(KMultiTabBar::KMultiTabBarStyle style); /** * get the toolview's tabbar style. Call this before @p startRestore(), * otherwise you overwrite the usersettings. * @return toolview's tabbar style */ KMultiTabBar::KMultiTabBarStyle toolViewStyle() const; /** * get the sidebars' visibility. * @return false, if the sidebars' visibility is forced hidden, otherwise true */ bool sidebarsVisible() const; public Q_SLOTS: /** * set the sidebars' visibility to @p visible. If false, the sidebars * are @e always hidden. Usually you do not have to call this because * the user can set this in the menu. * @param visible sidebars visibility */ void setSidebarsVisible(bool visible); protected: /** * called by toolview destructor * @param widget toolview which is destroyed */ void toolViewDeleted(ToolView *widget); /** * central widget ;) * use this as parent for your content * this widget will get focus if a toolview is hidden * @return central widget */ QWidget *centralWidget() const; /** * modifiers for existing toolviews */ public: /** * move a toolview around * @param widget toolview to move * @param pos position to move too, during session restore, only preference * @return success */ bool moveToolView(ToolView *widget, KMultiTabBar::KMultiTabBarPosition pos); /** * show given toolview, discarded while session restore * @param widget toolview to show * @return success */ bool showToolView(ToolView *widget); /** * hide given toolview, discarded while session restore * @param widget toolview to hide * @return success */ bool hideToolView(ToolView *widget); /** * session saving and restore stuff */ public: /** * start the restore * @param config config object to use * @param group config group to use */ void startRestore(KConfigBase *config, const QString &group); /** * finish the restore */ void finishRestore(); /** * save the current session config to given object and group * @param group config group to use */ void saveSession(KConfigGroup &group); /** * internal data ;) */ private: /** * map identifiers to widgets */ QMap m_idToWidget; /** * list of all toolviews around */ QList m_toolviews; /** * widget, which is the central part of the * main window ;) */ QWidget *m_centralWidget; /** * horizontal splitter */ QSplitter *m_hSplitter; /** * vertical splitter */ QSplitter *m_vSplitter; /** * sidebars for the four sides */ Sidebar *m_sidebars[4]; /** * sidebars state. */ bool m_sidebarsVisible = true; /** * config object for session restore, only valid between * start and finish restore calls */ KConfigBase *m_restoreConfig = nullptr; /** * restore group */ QString m_restoreGroup; /** * out guiclient */ GUIClient *m_guiClient; Q_SIGNALS: void sigShowPluginConfigPage(KTextEditor::Plugin *configpageinterface, int id); }; } #endif diff --git a/kate/katemwmodonhddialog.cpp b/kate/katemwmodonhddialog.cpp index a1a019845..bd4ed8dda 100644 --- a/kate/katemwmodonhddialog.cpp +++ b/kate/katemwmodonhddialog.cpp @@ -1,372 +1,372 @@ /* 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. --- Copyright (C) 2004, Anders Lund */ #include "katemwmodonhddialog.h" #include "kateapp.h" #include "katedocmanager.h" #include "katemainwindow.h" +#include +#include #include #include #include -#include -#include -#include -#include #include #include #include +#include +#include #include #include class KateDocItem : public QTreeWidgetItem { public: KateDocItem(KTextEditor::Document *doc, const QString &status, QTreeWidget *tw) : QTreeWidgetItem(tw) , document(doc) { setText(0, doc->url().toString()); setText(1, status); if (!doc->isModified()) { setCheckState(0, Qt::Checked); } else { setCheckState(0, Qt::Unchecked); } } ~KateDocItem() override { } KTextEditor::Document *document; }; KateMwModOnHdDialog::KateMwModOnHdDialog(DocVector docs, QWidget *parent, const char *name) : QDialog(parent) , m_proc(nullptr) , m_diffFile(nullptr) , m_blockAddDocument(false) { setWindowTitle(i18n("Documents Modified on Disk")); setObjectName(QString::fromLatin1(name)); setModal(true); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); // Message QHBoxLayout *hb = new QHBoxLayout; mainLayout->addLayout(hb); // dialog text QLabel *icon = new QLabel(this); hb->addWidget(icon); icon->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(KIconLoader::SizeLarge)); QLabel *t = new QLabel(i18n("The documents listed below have changed on disk.

Select one " "or more at once, and press an action button until the list is empty.

"), this); hb->addWidget(t); hb->setStretchFactor(t, 1000); // Document list twDocuments = new QTreeWidget(this); mainLayout->addWidget(twDocuments); QStringList header; header << i18n("Filename") << i18n("Status on Disk"); twDocuments->setHeaderLabels(header); twDocuments->setSelectionMode(QAbstractItemView::SingleSelection); twDocuments->setRootIsDecorated(false); m_stateTexts << QString() << i18n("Modified") << i18n("Created") << i18n("Deleted"); for (auto &doc : qAsConst(docs)) { new KateDocItem(doc, m_stateTexts[(uint)KateApp::self()->documentManager()->documentInfo(doc)->modifiedOnDiscReason], twDocuments); } twDocuments->header()->setStretchLastSection(false); twDocuments->header()->setSectionResizeMode(0, QHeaderView::Stretch); twDocuments->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); connect(twDocuments, &QTreeWidget::currentItemChanged, this, &KateMwModOnHdDialog::slotSelectionChanged); // Diff line hb = new QHBoxLayout; mainLayout->addLayout(hb); btnDiff = new QPushButton(QIcon::fromTheme(QStringLiteral("document-preview")), i18n("&View Difference"), this); btnDiff->setWhatsThis( i18n("Calculates the difference between the editor contents and the disk " "file for the selected document, and shows the difference with the " "default application. Requires diff(1).")); hb->addWidget(btnDiff); connect(btnDiff, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotDiff); // Dialog buttons QDialogButtonBox *buttons = new QDialogButtonBox(this); mainLayout->addWidget(buttons); QPushButton *ignoreButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-warning")), i18n("&Ignore Changes")); ignoreButton->setToolTip(i18n("Remove modified flag from selected documents")); buttons->addButton(ignoreButton, QDialogButtonBox::RejectRole); connect(ignoreButton, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotIgnore); QPushButton *overwriteButton = new QPushButton; KGuiItem::assign(overwriteButton, KStandardGuiItem::overwrite()); overwriteButton->setToolTip(i18n("Overwrite selected documents, discarding disk changes")); buttons->addButton(overwriteButton, QDialogButtonBox::DestructiveRole); connect(overwriteButton, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotOverwrite); QPushButton *reloadButton = new QPushButton(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("&Reload")); reloadButton->setDefault(true); reloadButton->setToolTip(i18n("Reload selected documents from disk")); buttons->addButton(reloadButton, QDialogButtonBox::DestructiveRole); connect(reloadButton, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotReload); slotSelectionChanged(nullptr, nullptr); } KateMwModOnHdDialog::~KateMwModOnHdDialog() { KateMainWindow::unsetModifiedOnDiscDialogIfIf(this); if (m_proc) { m_proc->kill(); m_proc->waitForFinished(); delete m_proc; m_proc = Q_NULLPTR; } if (m_diffFile) { m_diffFile->setAutoRemove(true); delete m_diffFile; m_diffFile = Q_NULLPTR; } } void KateMwModOnHdDialog::slotIgnore() { handleSelected(Ignore); } void KateMwModOnHdDialog::slotOverwrite() { handleSelected(Overwrite); } void KateMwModOnHdDialog::slotReload() { handleSelected(Reload); } void KateMwModOnHdDialog::handleSelected(int action) { // don't alter the treewidget via addDocument, we modify it here! m_blockAddDocument = true; // collect all items we can remove QList itemsToDelete; for (QTreeWidgetItemIterator it(twDocuments); *it; ++it) { KateDocItem *item = (KateDocItem *)*it; if (item->checkState(0) == Qt::Checked) { KTextEditor::ModificationInterface::ModifiedOnDiskReason reason = KateApp::self()->documentManager()->documentInfo(item->document)->modifiedOnDiscReason; bool success = true; if (KTextEditor::ModificationInterface *iface = qobject_cast(item->document)) { iface->setModifiedOnDisk(KTextEditor::ModificationInterface::OnDiskUnmodified); } switch (action) { case Overwrite: success = item->document->save(); if (!success) { KMessageBox::sorry(this, i18n("Could not save the document \n'%1'", item->document->url().toString())); } break; case Reload: item->document->documentReload(); break; default: break; } if (success) { itemsToDelete.append(item); } else { if (KTextEditor::ModificationInterface *iface = qobject_cast(item->document)) { iface->setModifiedOnDisk(reason); } } } } // remove the marked items, addDocument is blocked, this is save! for (int i = 0; i < itemsToDelete.count(); ++i) { delete itemsToDelete[i]; } // any documents left unhandled? if (!twDocuments->topLevelItemCount()) { accept(); } // allow addDocument again m_blockAddDocument = false; } void KateMwModOnHdDialog::slotSelectionChanged(QTreeWidgetItem *current, QTreeWidgetItem *) { KateDocItem *currentDocItem = static_cast(current); // set the diff button enabled btnDiff->setEnabled(currentDocItem && KateApp::self()->documentManager()->documentInfo(currentDocItem->document)->modifiedOnDiscReason != KTextEditor::ModificationInterface::OnDiskDeleted); } // ### the code below is slightly modified from kdelibs/kate/part/katedialogs, // class KateModOnHdPrompt. void KateMwModOnHdDialog::slotDiff() { if (!btnDiff->isEnabled()) { // diff button already pressed, proc not finished yet return; } if (!twDocuments->currentItem()) { return; } KTextEditor::Document *doc = (static_cast(twDocuments->currentItem()))->document; // don't try to diff a deleted file if (KateApp::self()->documentManager()->documentInfo(doc)->modifiedOnDiscReason == KTextEditor::ModificationInterface::OnDiskDeleted) { return; } if (m_diffFile) { return; } m_diffFile = new QTemporaryFile(); m_diffFile->open(); // Start a KProcess that creates a diff m_proc = new KProcess(this); m_proc->setOutputChannelMode(KProcess::MergedChannels); *m_proc << QStringLiteral("diff") << QStringLiteral("-ub") << QStringLiteral("-") << doc->url().toLocalFile(); connect(m_proc, &KProcess::readyRead, this, &KateMwModOnHdDialog::slotDataAvailable); connect(m_proc, static_cast(&KProcess::finished), this, &KateMwModOnHdDialog::slotPDone); setCursor(Qt::WaitCursor); btnDiff->setEnabled(false); m_proc->start(); QTextStream ts(m_proc); int lastln = doc->lines() - 1; for (int l = 0; l < lastln; ++l) { ts << doc->line(l) << QLatin1Char('\n'); } ts << doc->line(lastln); ts.flush(); m_proc->closeWriteChannel(); } void KateMwModOnHdDialog::slotDataAvailable() { m_diffFile->write(m_proc->readAll()); } void KateMwModOnHdDialog::slotPDone() { setCursor(Qt::ArrowCursor); slotSelectionChanged(twDocuments->currentItem(), nullptr); const QProcess::ExitStatus es = m_proc->exitStatus(); delete m_proc; m_proc = nullptr; if (es != QProcess::NormalExit) { KMessageBox::sorry(this, i18n("The diff command failed. Please make sure that " "diff(1) is installed and in your PATH."), i18n("Error Creating Diff")); delete m_diffFile; m_diffFile = nullptr; return; } if (m_diffFile->size() == 0) { KMessageBox::information(this, i18n("Ignoring amount of white space changed, the files are identical."), i18n("Diff Output")); delete m_diffFile; m_diffFile = nullptr; return; } m_diffFile->setAutoRemove(false); QUrl url = QUrl::fromLocalFile(m_diffFile->fileName()); delete m_diffFile; m_diffFile = nullptr; // KRun::runUrl should delete the file, once the client exits KRun::runUrl(url, QStringLiteral("text/x-patch"), this, KRun::RunFlags(KRun::DeleteTemporaryFiles)); } void KateMwModOnHdDialog::addDocument(KTextEditor::Document *doc) { // guard this e.g. during handleSelected if (m_blockAddDocument) return; for (QTreeWidgetItemIterator it(twDocuments); *it; ++it) { KateDocItem *item = (KateDocItem *)*it; if (item->document == doc) { delete item; break; } } uint reason = (uint)KateApp::self()->documentManager()->documentInfo(doc)->modifiedOnDiscReason; if (reason) { new KateDocItem(doc, m_stateTexts[reason], twDocuments); } if (!twDocuments->topLevelItemCount()) { accept(); } } void KateMwModOnHdDialog::keyPressEvent(QKeyEvent *event) { if (event->modifiers() == 0) { if (event->key() == Qt::Key_Escape) { event->accept(); return; } } QDialog::keyPressEvent(event); } void KateMwModOnHdDialog::closeEvent(QCloseEvent *e) { if (!twDocuments->topLevelItemCount()) { QDialog::closeEvent(e); } else { e->ignore(); } } diff --git a/kate/katemwmodonhddialog.h b/kate/katemwmodonhddialog.h index b5472b034..677370968 100644 --- a/kate/katemwmodonhddialog.h +++ b/kate/katemwmodonhddialog.h @@ -1,71 +1,71 @@ /* Copyright (C) 2004, Anders Lund This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef _KATE_MW_MODONHD_DIALOG_H_ #define _KATE_MW_MODONHD_DIALOG_H_ #include -#include #include +#include class KProcess; class QTemporaryFile; class QTreeWidget; class QTreeWidgetItem; typedef QVector DocVector; /** * A dialog for handling multiple documents modified on disk * from within KateMainWindow */ class KateMwModOnHdDialog : public QDialog { Q_OBJECT public: explicit KateMwModOnHdDialog(DocVector docs, QWidget *parent = nullptr, const char *name = nullptr); ~KateMwModOnHdDialog() override; void addDocument(KTextEditor::Document *doc); private Q_SLOTS: void slotIgnore(); void slotOverwrite(); void slotReload(); void slotDiff(); void slotSelectionChanged(QTreeWidgetItem *current, QTreeWidgetItem *); void slotDataAvailable(); void slotPDone(); private: enum Action { Ignore, Overwrite, Reload }; void handleSelected(int action); class QTreeWidget *twDocuments; class QPushButton *btnDiff; KProcess *m_proc; QTemporaryFile *m_diffFile; QStringList m_stateTexts; bool m_blockAddDocument; protected: void closeEvent(QCloseEvent *e) override; void keyPressEvent(QKeyEvent *) override; }; #endif // _KATE_MW_MODONHD_DIALOG_H_ diff --git a/kate/katepluginmanager.cpp b/kate/katepluginmanager.cpp index 3802cf9b3..5daef486f 100644 --- a/kate/katepluginmanager.cpp +++ b/kate/katepluginmanager.cpp @@ -1,359 +1,359 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ #include "katepluginmanager.h" #include "kateapp.h" -#include "katemainwindow.h" #include "katedebug.h" +#include "katemainwindow.h" #include #include #include #include #include #include #include QString KatePluginInfo::saveName() const { return QFileInfo(metaData.fileName()).baseName(); } bool KatePluginInfo::operator<(const KatePluginInfo &other) const { if (sortOrder != other.sortOrder) return sortOrder < other.sortOrder; return saveName() < other.saveName(); } KatePluginManager::KatePluginManager(QObject *parent) : QObject(parent) { setupPluginList(); } KatePluginManager::~KatePluginManager() { unloadAllPlugins(); } void KatePluginManager::setupPluginList() { // activate a hand-picked list of plugins per default, give them a hand-picked sort order for loading const QMap defaultPlugins { {QStringLiteral("katefiletreeplugin"), -1000}, {QStringLiteral("katesearchplugin"), -900}, {QStringLiteral("kateprojectplugin"), -800}, {QStringLiteral("tabswitcherplugin"), -100}, {QStringLiteral("textfilterplugin"), -100}, {QStringLiteral("externaltoolsplugin"), -100} #ifndef WIN32 , {QStringLiteral("katefilebrowserplugin"), -100} // currently works badly on Windows , {QStringLiteral("katekonsoleplugin"), -100} // currently does not work on Windows at all #endif }; // handle all install KTextEditor plugins m_pluginList.clear(); QSet unique; const QVector plugins = KPluginLoader::findPlugins(QStringLiteral("ktexteditor"), [](const KPluginMetaData &md) { return md.serviceTypes().contains(QLatin1String("KTextEditor/Plugin")); }); for (const auto &pluginMetaData : plugins) { KatePluginInfo info; info.metaData = pluginMetaData; // only load plugins once, even if found multiple times! if (unique.contains(info.saveName())) continue; info.defaultLoad = defaultPlugins.contains(info.saveName()); info.sortOrder = defaultPlugins.value(info.saveName()); info.load = false; info.plugin = nullptr; m_pluginList.push_back(info); unique.insert(info.saveName()); } // sort to ensure some deterministic plugin load order, this is important for tool-view creation order std::sort(m_pluginList.begin(), m_pluginList.end()); // construct fast lookup map, do this after vector has final size, resize will invalidate the pointers! m_name2Plugin.clear(); for (auto &pluginInfo : m_pluginList) { m_name2Plugin[pluginInfo.saveName()] = &pluginInfo; } } void KatePluginManager::loadConfig(KConfig *config) { // first: unload the plugins unloadAllPlugins(); /** * ask config object */ if (config) { KConfigGroup cg = KConfigGroup(config, QStringLiteral("Kate Plugins")); // disable all plugin if no config, beside the ones marked as default load for (auto &pluginInfo : m_pluginList) { pluginInfo.load = cg.readEntry(pluginInfo.saveName(), pluginInfo.defaultLoad); } } /** * load plugins */ for (auto &pluginInfo : m_pluginList) { if (pluginInfo.load) { /** * load plugin + trigger update of GUI for already existing main windows */ loadPlugin(&pluginInfo); enablePluginGUI(&pluginInfo); // restore config if (auto interface = qobject_cast(pluginInfo.plugin)) { KConfigGroup group(config, QStringLiteral("Plugin:%1:").arg(pluginInfo.saveName())); interface->readSessionConfig(group); } } } } void KatePluginManager::writeConfig(KConfig *config) { Q_ASSERT(config); KConfigGroup cg = KConfigGroup(config, QStringLiteral("Kate Plugins")); for (const KatePluginInfo &plugin : qAsConst(m_pluginList)) { QString saveName = plugin.saveName(); cg.writeEntry(saveName, plugin.load); // save config if (auto interface = qobject_cast(plugin.plugin)) { KConfigGroup group(config, QStringLiteral("Plugin:%1:").arg(saveName)); interface->writeSessionConfig(group); } } } void KatePluginManager::unloadAllPlugins() { for (auto &pluginInfo : m_pluginList) { if (pluginInfo.plugin) { unloadPlugin(&pluginInfo); } } } void KatePluginManager::enableAllPluginsGUI(KateMainWindow *win, KConfigBase *config) { for (auto &pluginInfo : m_pluginList) { if (pluginInfo.plugin) { enablePluginGUI(&pluginInfo, win, config); } } } void KatePluginManager::disableAllPluginsGUI(KateMainWindow *win) { for (auto &pluginInfo : m_pluginList) { if (pluginInfo.plugin) { disablePluginGUI(&pluginInfo, win); } } } bool KatePluginManager::loadPlugin(KatePluginInfo *item) { /** * try to load the plugin */ auto factory = KPluginLoader(item->metaData.fileName()).factory(); if (factory) { item->plugin = factory->create(this, QVariantList() << item->saveName()); } item->load = item->plugin != nullptr; /** * tell the world about the success */ if (item->plugin) { emit KateApp::self()->wrapper()->pluginCreated(item->saveName(), item->plugin); } return item->plugin != nullptr; } void KatePluginManager::unloadPlugin(KatePluginInfo *item) { disablePluginGUI(item); delete item->plugin; KTextEditor::Plugin *plugin = item->plugin; item->plugin = nullptr; item->load = false; emit KateApp::self()->wrapper()->pluginDeleted(item->saveName(), plugin); } void KatePluginManager::enablePluginGUI(KatePluginInfo *item, KateMainWindow *win, KConfigBase *config) { // plugin around at all? if (!item->plugin) { return; } // lookup if there is already a view for it.. QObject *createdView = nullptr; if (!win->pluginViews().contains(item->plugin)) { // create the view + try to correctly load shortcuts, if it's a GUI Client createdView = item->plugin->createView(win->wrapper()); if (createdView) { win->pluginViews().insert(item->plugin, createdView); } } // load session config if needed if (config && win->pluginViews().contains(item->plugin)) { if (auto interface = qobject_cast(win->pluginViews().value(item->plugin))) { KConfigGroup group(config, QStringLiteral("Plugin:%1:MainWindow:0").arg(item->saveName())); interface->readSessionConfig(group); } } if (createdView) { emit win->wrapper()->pluginViewCreated(item->saveName(), createdView); } } void KatePluginManager::enablePluginGUI(KatePluginInfo *item) { // plugin around at all? if (!item->plugin) { return; } // enable the gui for all mainwindows... for (int i = 0; i < KateApp::self()->mainWindowsCount(); i++) { enablePluginGUI(item, KateApp::self()->mainWindow(i), nullptr); } } void KatePluginManager::disablePluginGUI(KatePluginInfo *item, KateMainWindow *win) { // plugin around at all? if (!item->plugin) { return; } // lookup if there is a view for it.. if (!win->pluginViews().contains(item->plugin)) { return; } // really delete the view of this plugin QObject *deletedView = win->pluginViews().value(item->plugin); delete deletedView; win->pluginViews().remove(item->plugin); emit win->wrapper()->pluginViewDeleted(item->saveName(), deletedView); } void KatePluginManager::disablePluginGUI(KatePluginInfo *item) { // plugin around at all? if (!item->plugin) { return; } // disable the gui for all mainwindows... for (int i = 0; i < KateApp::self()->mainWindowsCount(); i++) { disablePluginGUI(item, KateApp::self()->mainWindow(i)); } } KTextEditor::Plugin *KatePluginManager::plugin(const QString &name) { /** * name known? */ if (!m_name2Plugin.contains(name)) { return nullptr; } /** * real plugin instance, if any ;) */ return m_name2Plugin.value(name)->plugin; } bool KatePluginManager::pluginAvailable(const QString &name) { return m_name2Plugin.contains(name); } class KTextEditor::Plugin *KatePluginManager::loadPlugin(const QString &name, bool permanent) { /** * name known? */ if (!m_name2Plugin.contains(name)) { return nullptr; } /** * load, bail out on error */ loadPlugin(m_name2Plugin.value(name)); if (!m_name2Plugin.value(name)->plugin) { return nullptr; } /** * perhaps patch not load again back to "ok, load it once again on next loadConfig" */ m_name2Plugin.value(name)->load = permanent; return m_name2Plugin.value(name)->plugin; } void KatePluginManager::unloadPlugin(const QString &name, bool permanent) { /** * name known? */ if (!m_name2Plugin.contains(name)) { return; } /** * unload */ unloadPlugin(m_name2Plugin.value(name)); /** * perhaps patch load again back to "ok, load it once again on next loadConfig" */ m_name2Plugin.value(name)->load = !permanent; } diff --git a/kate/katepluginmanager.h b/kate/katepluginmanager.h index 4fc29dc22..408afec5a 100644 --- a/kate/katepluginmanager.h +++ b/kate/katepluginmanager.h @@ -1,102 +1,102 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef __KATE_PLUGINMANAGER_H__ #define __KATE_PLUGINMANAGER_H__ #include -#include #include +#include -#include #include #include +#include class KConfig; class KateMainWindow; class KatePluginInfo { public: bool load = false; bool defaultLoad = false; KPluginMetaData metaData; KTextEditor::Plugin *plugin = nullptr; int sortOrder = 0; QString saveName() const; bool operator<(const KatePluginInfo &other) const; }; typedef QList KatePluginList; class KatePluginManager : public QObject { Q_OBJECT public: KatePluginManager(QObject *parent); ~KatePluginManager() override; void unloadAllPlugins(); void enableAllPluginsGUI(KateMainWindow *win, KConfigBase *config = nullptr); void disableAllPluginsGUI(KateMainWindow *win); void loadConfig(KConfig *); void writeConfig(KConfig *); bool loadPlugin(KatePluginInfo *item); void unloadPlugin(KatePluginInfo *item); void enablePluginGUI(KatePluginInfo *item, KateMainWindow *win, KConfigBase *config = nullptr); void enablePluginGUI(KatePluginInfo *item); void disablePluginGUI(KatePluginInfo *item, KateMainWindow *win); void disablePluginGUI(KatePluginInfo *item); inline KatePluginList &pluginList() { return m_pluginList; } KTextEditor::Plugin *plugin(const QString &name); bool pluginAvailable(const QString &name); KTextEditor::Plugin *loadPlugin(const QString &name, bool permanent = true); void unloadPlugin(const QString &name, bool permanent = true); private: void setupPluginList(); /** * all known plugins */ KatePluginList m_pluginList; /** * fast access map from name => plugin info * uses the info stored in the plugin list */ QMap m_name2Plugin; }; #endif diff --git a/kate/katequickopen.cpp b/kate/katequickopen.cpp index baa4ca3df..c0b1f8df7 100644 --- a/kate/katequickopen.cpp +++ b/kate/katequickopen.cpp @@ -1,187 +1,187 @@ /* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. --- Copyright (C) 2007,2009 Joseph Wenninger */ #include "katequickopen.h" #include "katequickopenmodel.h" +#include "kateapp.h" #include "katemainwindow.h" #include "kateviewmanager.h" -#include "kateapp.h" #include #include #include -#include -#include #include -#include #include +#include #include +#include +#include +#include +#include +#include #include #include -#include -#include +#include +#include #include +#include #include -#include -#include -#include #include -#include Q_DECLARE_METATYPE(QPointer) KateQuickOpen::KateQuickOpen(QWidget *parent, KateMainWindow *mainWindow) : QWidget(parent) , m_mainWindow(mainWindow) { QVBoxLayout *layout = new QVBoxLayout(); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); m_inputLine = new KLineEdit(); setFocusProxy(m_inputLine); m_inputLine->setPlaceholderText(i18n("Quick Open Search")); layout->addWidget(m_inputLine); m_listView = new QTreeView(); layout->addWidget(m_listView, 1); m_listView->setTextElideMode(Qt::ElideLeft); m_base_model = new KateQuickOpenModel(m_mainWindow, this); m_model = new QSortFilterProxyModel(this); m_model->setFilterRole(Qt::DisplayRole); m_model->setSortRole(Qt::DisplayRole); m_model->setFilterCaseSensitivity(Qt::CaseInsensitive); m_model->setSortCaseSensitivity(Qt::CaseInsensitive); m_model->setFilterKeyColumn(0); connect(m_inputLine, &KLineEdit::textChanged, m_model, &QSortFilterProxyModel::setFilterWildcard); connect(m_inputLine, &KLineEdit::returnPressed, this, &KateQuickOpen::slotReturnPressed); connect(m_model, &QSortFilterProxyModel::rowsInserted, this, &KateQuickOpen::reselectFirst); connect(m_model, &QSortFilterProxyModel::rowsRemoved, this, &KateQuickOpen::reselectFirst); connect(m_listView, &QTreeView::activated, this, &KateQuickOpen::slotReturnPressed); m_listView->setModel(m_model); m_model->setSourceModel(m_base_model); m_inputLine->installEventFilter(this); m_listView->installEventFilter(this); m_listView->setHeaderHidden(true); m_listView->setRootIsDecorated(false); } bool KateQuickOpen::eventFilter(QObject *obj, QEvent *event) { // catch key presses + shortcut overrides to allow to have ESC as application wide shortcut, too, see bug 409856 if (event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride) { QKeyEvent *keyEvent = static_cast(event); if (obj == m_inputLine) { const bool forward2list = (keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down) || (keyEvent->key() == Qt::Key_PageUp) || (keyEvent->key() == Qt::Key_PageDown); if (forward2list) { QCoreApplication::sendEvent(m_listView, event); return true; } if (keyEvent->key() == Qt::Key_Escape) { m_mainWindow->slotWindowActivated(); m_inputLine->clear(); keyEvent->accept(); return true; } } else { const bool forward2input = (keyEvent->key() != Qt::Key_Up) && (keyEvent->key() != Qt::Key_Down) && (keyEvent->key() != Qt::Key_PageUp) && (keyEvent->key() != Qt::Key_PageDown) && (keyEvent->key() != Qt::Key_Tab) && (keyEvent->key() != Qt::Key_Backtab); if (forward2input) { QCoreApplication::sendEvent(m_inputLine, event); return true; } } } // hide on focus out, if neither input field nor list have focus! else if (event->type() == QEvent::FocusOut && !(m_inputLine->hasFocus() || m_listView->hasFocus())) { m_mainWindow->slotWindowActivated(); m_inputLine->clear(); return true; } return QWidget::eventFilter(obj, event); } void KateQuickOpen::reselectFirst() { int first = 0; if (m_mainWindow->viewManager()->sortedViews().size() > 1) first = 1; QModelIndex index = m_model->index(first, 0); m_listView->setCurrentIndex(index); } void KateQuickOpen::update() { m_base_model->refresh(); m_listView->resizeColumnToContents(0); // If we have a very long file name we restrict the size of the first column // to take at most half of the space. Otherwise it would look odd. int colw0 = m_listView->header()->sectionSize(0); // file name int colw1 = m_listView->header()->sectionSize(1); // file path if (colw0 > colw1) { m_listView->setColumnWidth(0, (colw0 + colw1) / 2); } reselectFirst(); } void KateQuickOpen::slotReturnPressed() { const auto index = m_listView->model()->index(m_listView->currentIndex().row(), KateQuickOpenModel::Columns::FilePath); auto url = index.data(Qt::UserRole).toUrl(); m_mainWindow->wrapper()->openUrl(url); m_mainWindow->slotWindowActivated(); m_inputLine->clear(); } void KateQuickOpen::setMatchMode(int mode) { m_model->setFilterKeyColumn(mode); } int KateQuickOpen::matchMode() { return m_model->filterKeyColumn(); } void KateQuickOpen::setListMode(KateQuickOpenModel::List mode) { m_base_model->setListMode(mode); } KateQuickOpenModel::List KateQuickOpen::listMode() const { return m_base_model->listMode(); } diff --git a/kate/katequickopenmodel.cpp b/kate/katequickopenmodel.cpp index 22bf3ccef..0c45da64d 100644 --- a/kate/katequickopenmodel.cpp +++ b/kate/katequickopenmodel.cpp @@ -1,126 +1,126 @@ /* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. --- Copyright (C) 2018 Tomaz Canabrava */ #include "katequickopenmodel.h" +#include "kateapp.h" #include "katemainwindow.h" #include "kateviewmanager.h" -#include "kateapp.h" #include #include KateQuickOpenModel::KateQuickOpenModel(KateMainWindow *mainWindow, QObject *parent) : QAbstractTableModel(parent) , m_mainWindow(mainWindow) { } int KateQuickOpenModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_modelEntries.size(); } int KateQuickOpenModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 2; } QVariant KateQuickOpenModel::data(const QModelIndex &idx, int role) const { if (!idx.isValid()) { return {}; } if (role != Qt::DisplayRole && role != Qt::FontRole && role != Qt::UserRole) { return {}; } auto entry = m_modelEntries.at(idx.row()); if (role == Qt::DisplayRole) { switch (idx.column()) { case Columns::FileName: return entry.fileName; case Columns::FilePath: return entry.filePath; } } else if (role == Qt::FontRole) { if (entry.bold) { QFont font; font.setBold(true); return font; } } else if (role == Qt::UserRole) { return entry.url; } return {}; } void KateQuickOpenModel::refresh() { QObject *projectView = m_mainWindow->pluginView(QStringLiteral("kateprojectplugin")); const QList sortedViews = m_mainWindow->viewManager()->sortedViews(); const QList openDocs = KateApp::self()->documentManager()->documentList(); const QStringList projectDocs = projectView ? (m_listMode == CurrentProject ? projectView->property("projectFiles") : projectView->property("allProjectsFiles")).toStringList() : QStringList(); QVector allDocuments; allDocuments.reserve(sortedViews.size() + openDocs.size() + projectDocs.size()); size_t sort_id = (size_t)-1; for (auto *view : qAsConst(sortedViews)) { auto doc = view->document(); allDocuments.push_back({doc->url(), doc->documentName(), doc->url().toDisplayString(QUrl::NormalizePathSegments | QUrl::PreferLocalFile), true, sort_id--}); } for (auto *doc : qAsConst(openDocs)) { const auto normalizedUrl = doc->url().toString(QUrl::NormalizePathSegments | QUrl::PreferLocalFile); allDocuments.push_back({doc->url(), doc->documentName(), normalizedUrl, true, 0}); } for (const auto &file : qAsConst(projectDocs)) { QFileInfo fi(file); const auto localFile = QUrl::fromLocalFile(fi.absoluteFilePath()); allDocuments.push_back({localFile, fi.fileName(), localFile.toString(QUrl::NormalizePathSegments | QUrl::PreferLocalFile), false, 0}); } /** Sort the arrays by filePath. */ std::stable_sort(std::begin(allDocuments), std::end(allDocuments), [](const ModelEntry &a, const ModelEntry &b) { return a.filePath < b.filePath; }); /** remove Duplicates. * Note that the stable_sort above guarantees that the items that the * bold/sort_id fields of the items added first are correctly preserved. */ allDocuments.erase(std::unique(allDocuments.begin(), allDocuments.end(), [](const ModelEntry &a, const ModelEntry &b) { return a.filePath == b.filePath; }), std::end(allDocuments)); /** sort the arrays via boldness (open or not */ std::stable_sort(std::begin(allDocuments), std::end(allDocuments), [](const ModelEntry &a, const ModelEntry &b) { if (a.bold == b.bold) return a.sort_id > b.sort_id; return a.bold > b.bold; }); beginResetModel(); m_modelEntries = allDocuments; endResetModel(); } diff --git a/kate/katequickopenmodel.h b/kate/katequickopenmodel.h index 61ca44153..fa84c8f57 100644 --- a/kate/katequickopenmodel.h +++ b/kate/katequickopenmodel.h @@ -1,74 +1,74 @@ /* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. --- Copyright (C) 2018 Tomaz Canabrava */ #ifndef KATEQUICKOPENMODEL_H #define KATEQUICKOPENMODEL_H #include +#include #include #include -#include #include "katemainwindow.h" struct ModelEntry { QUrl url; // used for actually opening a selected file (local or remote) QString fileName; // display string for left column QString filePath; // display string for right column bool bold; // format line in bold text or not size_t sort_id; }; // needs to be defined outside of class to support forward declaration elsewhere enum KateQuickOpenModelList : int { CurrentProject, AllProjects }; class KateQuickOpenModel : public QAbstractTableModel { Q_OBJECT public: enum Columns : int { FileName, FilePath, Bold }; explicit KateQuickOpenModel(KateMainWindow *mainWindow, QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &idx, int role) const override; void refresh(); // add a convenient in-class alias using List = KateQuickOpenModelList; List listMode() const { return m_listMode; } void setListMode(List mode) { m_listMode = mode; } private: QVector m_modelEntries; /* TODO: don't rely in a pointer to the main window. * this is bad engineering, but current code is too tight * on this and it's hard to untangle without breaking existing * code. */ KateMainWindow *m_mainWindow; List m_listMode; }; #endif diff --git a/kate/katerunninginstanceinfo.cpp b/kate/katerunninginstanceinfo.cpp index a9a1d0f32..cc3d0d9ea 100644 --- a/kate/katerunninginstanceinfo.cpp +++ b/kate/katerunninginstanceinfo.cpp @@ -1,72 +1,72 @@ /* This file is part of the KDE project Copyright (C) 2009 Joseph Wenninger 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. */ #include "katerunninginstanceinfo.h" -#include -#include #include #include +#include +#include int KateRunningInstanceInfo::dummy_session = 0; bool fillinRunningKateAppInstances(KateRunningInstanceMap *map) { QDBusConnectionInterface *i = QDBusConnection::sessionBus().interface(); if (!i) { return true; // we do not know about any others... } // look up all running kate instances and there sessions QDBusReply servicesReply = i->registeredServiceNames(); QStringList services; if (servicesReply.isValid()) { services = servicesReply.value(); } QString serviceName; QString my_pid = QString::number(QCoreApplication::applicationPid()); for (const QString &s : qAsConst(services)) { if (s.startsWith(QLatin1String("org.kde.kate-"))) { if (s.contains(my_pid)) { continue; } KateRunningInstanceInfo *rii = new KateRunningInstanceInfo(s); if (rii->valid) { if (map->contains(rii->sessionName)) { return false; // ERROR no two instances may have the same session name } map->insert(rii->sessionName, rii); // std::cerr<sessionName.toUtf8().data()<constBegin(); it != map->constEnd(); ++it) { delete it.value(); } map->clear(); } diff --git a/kate/katerunninginstanceinfo.h b/kate/katerunninginstanceinfo.h index 81c4aa6fd..2ae7f5cb6 100644 --- a/kate/katerunninginstanceinfo.h +++ b/kate/katerunninginstanceinfo.h @@ -1,82 +1,82 @@ /* This file is part of the KDE project Copyright (C) 2009 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef _KATE_RUNNING_INSTANCE_INFO_ #define _KATE_RUNNING_INSTANCE_INFO_ -#include -#include #include +#include +#include #include #include class KateRunningInstanceInfo : public QObject { Q_OBJECT public: KateRunningInstanceInfo(const QString &serviceName_) : QObject() , valid(false) , serviceName(serviceName_) , dbus_if(new QDBusInterface(serviceName_, QStringLiteral("/MainApplication"), QString(), // I don't know why it does not work if I specify org.kde.Kate.Application here QDBusConnection::sessionBus(), this)) { if (!dbus_if->isValid()) { std::cerr << qPrintable(QDBusConnection::sessionBus().lastError().message()) << std::endl; } QVariant a_s = dbus_if->property("activeSession"); /* std::cerr< KateRunningInstanceMap; Q_DECL_EXPORT bool fillinRunningKateAppInstances(KateRunningInstanceMap *map); Q_DECL_EXPORT void cleanupRunningKateAppInstanceMap(KateRunningInstanceMap *map); #endif diff --git a/kate/katesavemodifieddialog.cpp b/kate/katesavemodifieddialog.cpp index a74425953..df2ae8531 100644 --- a/kate/katesavemodifieddialog.cpp +++ b/kate/katesavemodifieddialog.cpp @@ -1,254 +1,254 @@ /* This file is part of the KDE project Copyright (C) 2004 Joseph Wenninger 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. */ #include "katesavemodifieddialog.h" #include "katedebug.h" #include -#include -#include #include +#include +#include +#include #include #include #include #include -#include class AbstractKateSaveModifiedDialogCheckListItem : public QTreeWidgetItem { public: AbstractKateSaveModifiedDialogCheckListItem(const QString &title, const QString &url) : QTreeWidgetItem() { setFlags(flags() | Qt::ItemIsUserCheckable); setText(0, title); setText(1, url); setCheckState(0, Qt::Checked); setState(InitialState); } ~AbstractKateSaveModifiedDialogCheckListItem() override { } virtual bool synchronousSave(QWidget *dialogParent) = 0; enum STATE { InitialState, SaveOKState, SaveFailedState }; STATE state() const { return m_state; } void setState(enum STATE state) { m_state = state; switch (state) { case InitialState: setIcon(0, QIcon()); break; case SaveOKState: setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-ok"))); // QStringLiteral("ok") icon should probably be QStringLiteral("dialog-success"), but we don't have that icon in KDE 4.0 break; case SaveFailedState: setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-error"))); break; } } private: STATE m_state; }; class KateSaveModifiedDocumentCheckListItem : public AbstractKateSaveModifiedDialogCheckListItem { public: KateSaveModifiedDocumentCheckListItem(KTextEditor::Document *document) : AbstractKateSaveModifiedDialogCheckListItem(document->documentName(), document->url().toString()) { m_document = document; } ~KateSaveModifiedDocumentCheckListItem() override { } bool synchronousSave(QWidget *dialogParent) override { if (m_document->url().isEmpty()) { const QUrl url = QFileDialog::getSaveFileUrl(dialogParent, i18n("Save As (%1)", m_document->documentName())); if (!url.isEmpty()) { if (!m_document->saveAs(url)) { setState(SaveFailedState); setText(1, m_document->url().toString()); return false; } else { bool sc = m_document->waitSaveComplete(); setText(1, m_document->url().toString()); if (!sc) { setState(SaveFailedState); return false; } else { setState(SaveOKState); return true; } } } else { // setState(SaveFailedState); return false; } } else { // document has an existing location if (!m_document->save()) { setState(SaveFailedState); setText(1, m_document->url().toString()); return false; } else { bool sc = m_document->waitSaveComplete(); setText(1, m_document->url().toString()); if (!sc) { setState(SaveFailedState); return false; } else { setState(SaveOKState); return true; } } } return false; } private: KTextEditor::Document *m_document; }; KateSaveModifiedDialog::KateSaveModifiedDialog(QWidget *parent, const QList &documents) : QDialog(parent) { setWindowTitle(i18n("Save Documents")); setObjectName(QStringLiteral("KateSaveModifiedDialog")); setModal(true); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); // label QLabel *lbl = new QLabel(i18n("The following documents have been modified. Do you want to save them before closing?"), this); mainLayout->addWidget(lbl); // main view m_list = new QTreeWidget(this); mainLayout->addWidget(m_list); m_list->setColumnCount(2); m_list->setHeaderLabels(QStringList() << i18n("Documents") << i18n("Location")); m_list->setRootIsDecorated(true); for (KTextEditor::Document *doc : documents) { m_list->addTopLevelItem(new KateSaveModifiedDocumentCheckListItem(doc)); } m_list->resizeColumnToContents(0); connect(m_list, &QTreeWidget::itemChanged, this, &KateSaveModifiedDialog::slotItemActivated); QPushButton *selectAllButton = new QPushButton(i18n("Se&lect All"), this); mainLayout->addWidget(selectAllButton); connect(selectAllButton, &QPushButton::clicked, this, &KateSaveModifiedDialog::slotSelectAll); // dialog buttons QDialogButtonBox *buttons = new QDialogButtonBox(this); mainLayout->addWidget(buttons); m_saveButton = new QPushButton; KGuiItem::assign(m_saveButton, KStandardGuiItem::save()); buttons->addButton(m_saveButton, QDialogButtonBox::YesRole); connect(m_saveButton, &QPushButton::clicked, this, &KateSaveModifiedDialog::slotSaveSelected); QPushButton *discardButton = new QPushButton; KGuiItem::assign(discardButton, KStandardGuiItem::discard()); buttons->addButton(discardButton, QDialogButtonBox::NoRole); connect(discardButton, &QPushButton::clicked, this, &KateSaveModifiedDialog::slotDoNotSave); QPushButton *cancelButton = new QPushButton; KGuiItem::assign(cancelButton, KStandardGuiItem::cancel()); cancelButton->setDefault(true); cancelButton->setFocus(); buttons->addButton(cancelButton, QDialogButtonBox::RejectRole); connect(cancelButton, &QPushButton::clicked, this, &KateSaveModifiedDialog::reject); } KateSaveModifiedDialog::~KateSaveModifiedDialog() { } void KateSaveModifiedDialog::slotItemActivated(QTreeWidgetItem *, int) { bool enableSaveButton = false; for (int i = 0; i < m_list->topLevelItemCount(); ++i) { if (m_list->topLevelItem(i)->checkState(0) == Qt::Checked) { enableSaveButton = true; break; } } m_saveButton->setEnabled(enableSaveButton); } void KateSaveModifiedDialog::slotSelectAll() { for (int i = 0; i < m_list->topLevelItemCount(); ++i) { m_list->topLevelItem(i)->setCheckState(0, Qt::Checked); } m_saveButton->setEnabled(true); } void KateSaveModifiedDialog::slotSaveSelected() { if (doSave()) { done(QDialog::Accepted); } } void KateSaveModifiedDialog::slotDoNotSave() { done(QDialog::Accepted); } bool KateSaveModifiedDialog::doSave() { for (int i = 0; i < m_list->topLevelItemCount(); ++i) { AbstractKateSaveModifiedDialogCheckListItem *cit = static_cast(m_list->topLevelItem(i)); if (cit->checkState(0) == Qt::Checked && (cit->state() != AbstractKateSaveModifiedDialogCheckListItem::SaveOKState)) { if (!cit->synchronousSave(this /*perhaps that should be the kate mainwindow*/)) { if (cit->state() == AbstractKateSaveModifiedDialogCheckListItem::SaveFailedState) { KMessageBox::sorry(this, i18n("Data you requested to be saved could not be written. Please choose how you want to proceed.")); } return false; } } else if ((cit->checkState(0) != Qt::Checked) && (cit->state() == AbstractKateSaveModifiedDialogCheckListItem::SaveFailedState)) { cit->setState(AbstractKateSaveModifiedDialogCheckListItem::InitialState); } } return true; } bool KateSaveModifiedDialog::queryClose(QWidget *parent, const QList &documents) { KateSaveModifiedDialog d(parent, documents); return (d.exec() != QDialog::Rejected); } diff --git a/kate/katesavemodifieddialog.h b/kate/katesavemodifieddialog.h index c1560e0ed..37d2ab4c2 100644 --- a/kate/katesavemodifieddialog.h +++ b/kate/katesavemodifieddialog.h @@ -1,54 +1,54 @@ /* This file is part of the KDE project Copyright (C) 2004 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef _KATE_SAVE_MODIFIED_DIALOG_ #define _KATE_SAVE_MODIFIED_DIALOG_ #include -#include #include +#include class QTreeWidget; class QTreeWidgetItem; class QPushButton; class KateSaveModifiedDialog : public QDialog { Q_OBJECT public: KateSaveModifiedDialog(QWidget *parent, const QList &documents); ~KateSaveModifiedDialog() override; static bool queryClose(QWidget *parent, const QList &documents); protected: bool doSave(); protected Q_SLOTS: void slotSelectAll(); void slotItemActivated(QTreeWidgetItem *, int); void slotSaveSelected(); void slotDoNotSave(); private: QTreeWidgetItem *m_documentRoot; QTreeWidget *m_list; QPushButton *m_saveButton; }; #endif diff --git a/kate/katetabbar.h b/kate/katetabbar.h index 5cc5d5ee0..a5878cae8 100644 --- a/kate/katetabbar.h +++ b/kate/katetabbar.h @@ -1,263 +1,263 @@ /* This file is part of the KDE project * * Copyright (C) 2014 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KATE_TAB_BAR_H #define KATE_TAB_BAR_H #include -#include #include #include +#include class KateTabButton; class KateTabBarPrivate; /** * The \p KateTabBar class provides a tab bar, e.g. for tabbed documents. * * The API closely follows the API of QTabBar. * * @author Dominik Haumann */ class KateTabBar : public QWidget { Q_OBJECT Q_PROPERTY(bool isActive READ isActive WRITE setActive) public: explicit KateTabBar(QWidget *parent = nullptr); ~KateTabBar() override; /** * Adds a new tab with \a text. Returns the new tab's id. */ int addTab(const QString &text); /** * Insert a tab at \p position with \a text. Returns the new tab's id. * @param position index of the tab, i.e. 0, ..., count() * @param text the text snippet */ int insertTab(int position, const QString &text); /** * Removes the tab with ID \a id. * @return the position where the tab was */ int removeTab(int index); /** * Get the ID of the tab bar's activated tab. Returns -1 if no tab is activated. */ int currentTab() const; /** * Get the ID of the tab that is located left of the current tab. * The return value is -1, if there is no previous tab. */ int prevTab() const; /** * Get the ID of the tab that is located right of the current tab. * The return value is -1, if there is no next tab. */ int nextTab() const; public Q_SLOTS: /** * Activate the tab with \p id. No signal is emitted. */ void setCurrentTab(int index); // does not emit signal public: /** * Returns whether a tab with ID \a id exists. */ bool containsTab(int index) const; /** * Set the button @p id's tool tip to @p tip. */ void setTabToolTip(int index, const QString &tip); /** * Get the button @p id's url. Result is QStrint() if not available. */ QString tabToolTip(int index) const; /** * Sets the text of the tab with ID \a id to \a text. * \see tabText() */ void setTabText(int index, const QString &text); /** * Returns the text of the tab with ID \a id. If the button id does not * exist \a QString() is returned. * \see setTabText() */ QString tabText(int index) const; /** * Sets the URL of the tab with ID \a id to \a url. * \see tabUrl() * \since 17.08 */ void setTabUrl(int index, const QUrl &url); /** * Returns the text of the tab with ID \a id. If the button id does not * exist \a QString() is returned. * \see setTabUrl() * \since 17.08 */ QUrl tabUrl(int index) const; /** * Sets the icon of the tab with ID \a id to \a icon. * \see tabIcon() */ void setTabIcon(int index, const QIcon &pixmap); /** * Returns the icon of the tab with ID \a id. If the button id does not * exist \a QIcon() is returned. * \see setTabIcon() */ QIcon tabIcon(int index) const; /** * Returns the number of tabs in the tab bar. */ int count() const; /** * Return the maximum amount of tabs that fit into the tab bar given * the minimumTabWidth(). */ int maxTabCount() const; /** * Marks this tabbar as active. That is, current-tab indicators are * properly highlighted, indicating that child widgets of this tabbar * will get input. * * This concept is mostly useful, if your application has multiple tabbars. * Inactive tabbars are grayed out. */ void setActive(bool active); /** * Returns whether this tabbar is active. */ bool isActive() const; Q_SIGNALS: /** * This signal is emitted whenever the current activated tab changes. */ void currentChanged(int id); /** * This signal is emitted whenever tab @p id should be closed. */ void closeTabRequested(int id); /** * This signal is emitted whenever the context menu is requested for * button @p id at position @p globalPos. * @param id the button, or -1 if the context menu was requested on * at a place where no tab exists * @param globalPos the position of the context menu in global coordinates */ void contextMenuRequest(int id, const QPoint &globalPos); /** * This signal is emitted whenever the tab bar's width allows to * show more tabs than currently available. In other words, * you can safely add @p count tabs which are guaranteed to be visible. */ void moreTabsRequested(int count); /** * This signal is emitted whenever the tab bar's width is too small, * such that not all tabs can be shown. * Therefore, @p count tabs should be removed. */ void lessTabsRequested(int count); /** * This signal is emitted whenever the users double clicks on the free * space next to the tab bar. Typically, a new document should be * created. */ void newTabRequested(); /** * This signal is emitted whenever the tab bar was clicked while the * tab bar is not the active view space tab bar. */ void activateViewSpaceRequested(); protected Q_SLOTS: /** * Active button changed. Emit signal \p currentChanged() with the button's ID. */ void tabButtonActivated(KateTabButton *tabButton); /** * If the user wants to close a tab with the context menu, it sends a close * request. */ void tabButtonCloseRequest(KateTabButton *tabButton); protected: //! Recalculate geometry for all tabs. void resizeEvent(QResizeEvent *event) override; //! Override to avoid requesting a new tab. void mouseDoubleClickEvent(QMouseEvent *event) override; //! Override to request making the tab bar active. void mousePressEvent(QMouseEvent *event) override; //! trigger repaint on hover leave event void leaveEvent(QEvent *event) override; //! Paint tab separators void paintEvent(QPaintEvent *event) override; //! Request context menu void contextMenuEvent(QContextMenuEvent *ev) override; //! Cycle through tabs void wheelEvent(QWheelEvent *event) override; //! Support for drag & drop of tabs void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; private: // pimpl data holder KateTabBarPrivate *const d; }; #endif // KATE_TAB_BAR_H diff --git a/kate/kateviewmanager.cpp b/kate/kateviewmanager.cpp index 2e8db4dee..abf860ada 100644 --- a/kate/kateviewmanager.cpp +++ b/kate/kateviewmanager.cpp @@ -1,1237 +1,1237 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ // BEGIN Includes #include "kateviewmanager.h" #include "config.h" #include "kateapp.h" #include "katemainwindow.h" -#include "kateviewspace.h" #include "kateupdatedisabler.h" +#include "kateviewspace.h" -#include -#include #include +#include +#include #include -#include -#include -#include #include #include #include +#include +#include +#include #include #ifdef KF5Activities_FOUND #include #endif #include #include // END Includes static const qint64 FileSizeAboveToAskUserIfProceedWithOpen = 10 * 1024 * 1024; // 10MB should suffice KateViewManager::KateViewManager(QWidget *parentW, KateMainWindow *parent) : QSplitter(parentW) , m_mainWindow(parent) , m_blockViewCreationAndActivation(false) , m_activeViewRunning(false) , m_minAge(0) , m_guiMergedView(nullptr) { // while init m_init = true; // we don't allow full collapse, see bug 366014 setChildrenCollapsible(false); // important, set them up, as we use them in other methodes setupActions(); KateViewSpace *vs = new KateViewSpace(this, nullptr); addWidget(vs); vs->setActive(true); m_viewSpaceList.append(vs); connect(this, &KateViewManager::viewChanged, this, &KateViewManager::slotViewChanged); connect(KateApp::self()->documentManager(), &KateDocManager::documentCreatedViewManager, this, &KateViewManager::documentCreated); /** * before document is really deleted: cleanup all views! */ connect(KateApp::self()->documentManager(), &KateDocManager::documentWillBeDeleted, this, &KateViewManager::documentWillBeDeleted); /** * handle document deletion transactions * disable view creation in between * afterwards ensure we have views ;) */ connect(KateApp::self()->documentManager(), &KateDocManager::aboutToDeleteDocuments, this, &KateViewManager::aboutToDeleteDocuments); connect(KateApp::self()->documentManager(), &KateDocManager::documentsDeleted, this, &KateViewManager::documentsDeleted); // register all already existing documents m_blockViewCreationAndActivation = true; const QList &docs = KateApp::self()->documentManager()->documentList(); for (KTextEditor::Document *doc : docs) { documentCreated(doc); } m_blockViewCreationAndActivation = false; // init done m_init = false; } KateViewManager::~KateViewManager() { /** * remove the single client that is registered at the factory, if any */ if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } } void KateViewManager::setupActions() { /** * view splitting */ m_splitViewVert = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_vert")); m_splitViewVert->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); m_splitViewVert->setText(i18n("Split Ve&rtical")); m_mainWindow->actionCollection()->setDefaultShortcut(m_splitViewVert, Qt::CTRL + Qt::SHIFT + Qt::Key_L); connect(m_splitViewVert, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceVert); m_splitViewVert->setWhatsThis(i18n("Split the currently active view vertically into two views.")); m_splitViewHoriz = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_horiz")); m_splitViewHoriz->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom"))); m_splitViewHoriz->setText(i18n("Split &Horizontal")); m_mainWindow->actionCollection()->setDefaultShortcut(m_splitViewHoriz, Qt::CTRL + Qt::SHIFT + Qt::Key_T); connect(m_splitViewHoriz, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceHoriz); m_splitViewHoriz->setWhatsThis(i18n("Split the currently active view horizontally into two views.")); m_closeView = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_close_current_space")); m_closeView->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); m_closeView->setText(i18n("Cl&ose Current View")); m_mainWindow->actionCollection()->setDefaultShortcut(m_closeView, Qt::CTRL + Qt::SHIFT + Qt::Key_R); connect(m_closeView, &QAction::triggered, this, &KateViewManager::slotCloseCurrentViewSpace, Qt::QueuedConnection); m_closeView->setWhatsThis(i18n("Close the currently active split view")); m_closeOtherViews = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_close_others")); m_closeOtherViews->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); m_closeOtherViews->setText(i18n("Close Inactive Views")); connect(m_closeOtherViews, &QAction::triggered, this, &KateViewManager::slotCloseOtherViews, Qt::QueuedConnection); m_closeOtherViews->setWhatsThis(i18n("Close every view but the active one")); m_hideOtherViews = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_hide_others")); m_hideOtherViews->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); m_hideOtherViews->setText(i18n("Hide Inactive Views")); m_hideOtherViews->setCheckable(true); connect(m_hideOtherViews, &QAction::triggered, this, &KateViewManager::slotHideOtherViews, Qt::QueuedConnection); m_hideOtherViews->setWhatsThis(i18n("Hide every view but the active one")); m_toggleSplitterOrientation = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_toggle")); m_toggleSplitterOrientation->setText(i18n("Toggle Orientation")); connect(m_toggleSplitterOrientation, &QAction::triggered, this, &KateViewManager::toggleSplitterOrientation, Qt::QueuedConnection); m_toggleSplitterOrientation->setWhatsThis(i18n("Toggles the orientation of the current split view")); goNext = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_next_split_view")); goNext->setText(i18n("Next Split View")); m_mainWindow->actionCollection()->setDefaultShortcut(goNext, Qt::Key_F8); connect(goNext, &QAction::triggered, this, &KateViewManager::activateNextView); goNext->setWhatsThis(i18n("Make the next split view the active one.")); goPrev = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_prev_split_view")); goPrev->setText(i18n("Previous Split View")); m_mainWindow->actionCollection()->setDefaultShortcut(goPrev, Qt::SHIFT + Qt::Key_F8); connect(goPrev, &QAction::triggered, this, &KateViewManager::activatePrevView); goPrev->setWhatsThis(i18n("Make the previous split view the active one.")); QAction *a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_right")); a->setText(i18n("Move Splitter Right")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterRight); a->setWhatsThis(i18n("Move the splitter of the current view to the right")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_left")); a->setText(i18n("Move Splitter Left")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterLeft); a->setWhatsThis(i18n("Move the splitter of the current view to the left")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_up")); a->setText(i18n("Move Splitter Up")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterUp); a->setWhatsThis(i18n("Move the splitter of the current view up")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_down")); a->setText(i18n("Move Splitter Down")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterDown); a->setWhatsThis(i18n("Move the splitter of the current view down")); } void KateViewManager::updateViewSpaceActions() { m_closeView->setEnabled(m_viewSpaceList.count() > 1); m_closeOtherViews->setEnabled(m_viewSpaceList.count() > 1); m_toggleSplitterOrientation->setEnabled(m_viewSpaceList.count() > 1); goNext->setEnabled(m_viewSpaceList.count() > 1); goPrev->setEnabled(m_viewSpaceList.count() > 1); } void KateViewManager::slotDocumentNew() { createView(); } void KateViewManager::slotDocumentOpen() { // try to start dialog in useful dir: either dir of current doc or last used one KTextEditor::View *const cv = activeView(); QUrl startUrl = cv ? cv->document()->url() : QUrl(); if (startUrl.isValid()) { m_lastOpenDialogUrl = startUrl; } else { startUrl = m_lastOpenDialogUrl; } const QList urls = QFileDialog::getOpenFileUrls(m_mainWindow, i18n("Open File"), startUrl); /** * emit size warning, for local files */ QString fileListWithTooLargeFiles; for (const QUrl &url : urls) { if (!url.isLocalFile()) { continue; } const auto size = QFile(url.toLocalFile()).size(); if (size > FileSizeAboveToAskUserIfProceedWithOpen) { fileListWithTooLargeFiles += QStringLiteral("
  • %1 (%2MB)
  • ").arg(url.fileName()).arg(size / 1024 / 1024); } } if (!fileListWithTooLargeFiles.isEmpty()) { const QString text = i18n("

    You are attempting to open one or more large files:

      %1

    Do you want to proceed?

    Beware that kate may stop responding for some time when opening large files.

    ", fileListWithTooLargeFiles); const auto ret = KMessageBox::warningYesNo(this, text, i18n("Opening Large File"), KStandardGuiItem::cont(), KStandardGuiItem::stop()); if (ret == KMessageBox::No) { return; } } // activate view of last opened document KateDocumentInfo docInfo; docInfo.openedByUser = true; if (KTextEditor::Document *lastID = openUrls(urls, QString(), false, docInfo)) { activateView(lastID); } } void KateViewManager::slotDocumentClose(KTextEditor::Document *document) { bool shutdownKate = m_mainWindow->modCloseAfterLast() && KateApp::self()->documentManager()->documentList().size() == 1; // close document if (KateApp::self()->documentManager()->closeDocument(document) && shutdownKate) { KateApp::self()->shutdownKate(m_mainWindow); } } void KateViewManager::slotDocumentClose() { // no active view, do nothing if (!activeView()) { return; } slotDocumentClose(activeView()->document()); } KTextEditor::Document *KateViewManager::openUrl(const QUrl &url, const QString &encoding, bool activate, bool isTempFile, const KateDocumentInfo &docInfo) { KTextEditor::Document *doc = KateApp::self()->documentManager()->openUrl(url, encoding, isTempFile, docInfo); if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } if (activate) { activateView(doc); } return doc; } KTextEditor::Document *KateViewManager::openUrls(const QList &urls, const QString &encoding, bool isTempFile, const KateDocumentInfo &docInfo) { const QList docs = KateApp::self()->documentManager()->openUrls(urls, encoding, isTempFile, docInfo); for (const KTextEditor::Document *doc : docs) { if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } } return docs.isEmpty() ? nullptr : docs.last(); } KTextEditor::View *KateViewManager::openUrlWithView(const QUrl &url, const QString &encoding) { KTextEditor::Document *doc = KateApp::self()->documentManager()->openUrl(url, encoding); if (!doc) { return nullptr; } if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } activateView(doc); return activeView(); } void KateViewManager::openUrl(const QUrl &url) { openUrl(url, QString()); } KateMainWindow *KateViewManager::mainWindow() { return m_mainWindow; } void KateViewManager::documentCreated(KTextEditor::Document *doc) { // forward to currently active view space activeViewSpace()->registerDocument(doc); // to update open recent files on saving connect(doc, &KTextEditor::Document::documentSavedOrUploaded, this, &KateViewManager::documentSavedOrUploaded); if (m_blockViewCreationAndActivation) { return; } if (!activeView()) { activateView(doc); } /** * check if we have any empty viewspaces and give them a view */ for (KateViewSpace *vs : qAsConst(m_viewSpaceList)) { if (!vs->currentView()) { createView(activeView()->document(), vs); } } } void KateViewManager::aboutToDeleteDocuments(const QList &) { /** * block view creation until the transaction is done * this shall not stack! */ Q_ASSERT(!m_blockViewCreationAndActivation); m_blockViewCreationAndActivation = true; /** * disable updates hard (we can't use KateUpdateDisabler here, we have delayed signal */ mainWindow()->setUpdatesEnabled(false); } void KateViewManager::documentsDeleted(const QList &) { /** * again allow view creation */ m_blockViewCreationAndActivation = false; /** * try to have active view around! */ if (!activeView() && !KateApp::self()->documentManager()->documentList().isEmpty()) { createView(KateApp::self()->documentManager()->documentList().last()); } /** * if we have one now, show them in all viewspaces that got empty! */ if (KTextEditor::View *const newActiveView = activeView()) { /** * check if we have any empty viewspaces and give them a view */ for (KateViewSpace *vs : qAsConst(m_viewSpaceList)) { if (!vs->currentView()) { createView(newActiveView->document(), vs); } } emit viewChanged(newActiveView); } /** * enable updates hard (we can't use KateUpdateDisabler here, we have delayed signal */ mainWindow()->setUpdatesEnabled(true); } void KateViewManager::documentSavedOrUploaded(KTextEditor::Document *doc, bool) { if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } } KTextEditor::View *KateViewManager::createView(KTextEditor::Document *doc, KateViewSpace *vs) { if (m_blockViewCreationAndActivation) { return nullptr; } // create doc if (!doc) { doc = KateApp::self()->documentManager()->createDoc(); } /** * create view, registers its XML gui itself * pass the view the correct main window */ KTextEditor::View *view = (vs ? vs : activeViewSpace())->createView(doc); /** * remember this view, active == false, min age set * create activity resource */ m_views[view].active = false; m_views[view].lruAge = m_minAge--; #ifdef KF5Activities_FOUND m_views[view].activityResource = new KActivities::ResourceInstance(view->window()->winId(), view); m_views[view].activityResource->setUri(doc->url()); #endif // disable settings dialog action delete view->actionCollection()->action(QStringLiteral("set_confdlg")); delete view->actionCollection()->action(QStringLiteral("editor_options")); connect(view, SIGNAL(dropEventPass(QDropEvent *)), mainWindow(), SLOT(slotDropEvent(QDropEvent *))); connect(view, &KTextEditor::View::focusIn, this, &KateViewManager::activateSpace); viewCreated(view); if (!vs) { activateView(view); } return view; } bool KateViewManager::deleteView(KTextEditor::View *view) { if (!view) { return true; } KateViewSpace *viewspace = static_cast(view->parentWidget()->parentWidget()); viewspace->removeView(view); /** * deregister if needed */ if (m_guiMergedView == view) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } // remove view from mapping and memory !! m_views.remove(view); delete view; return true; } KateViewSpace *KateViewManager::activeViewSpace() { for (QList::const_iterator it = m_viewSpaceList.constBegin(); it != m_viewSpaceList.constEnd(); ++it) { if ((*it)->isActiveSpace()) { return *it; } } // none active, so use the first we grab if (!m_viewSpaceList.isEmpty()) { m_viewSpaceList.first()->setActive(true); return m_viewSpaceList.first(); } Q_ASSERT(false); return nullptr; } KTextEditor::View *KateViewManager::activeView() { if (m_activeViewRunning) { return nullptr; } m_activeViewRunning = true; QHashIterator it(m_views); while (it.hasNext()) { it.next(); if (it.value().active) { m_activeViewRunning = false; return it.key(); } } // if we get to here, no view isActive() // first, try to get one from activeViewSpace() KateViewSpace *vs = activeViewSpace(); if (vs && vs->currentView()) { activateView(vs->currentView()); m_activeViewRunning = false; return vs->currentView(); } // last attempt: pick MRU view auto views = sortedViews(); if (!views.isEmpty()) { KTextEditor::View *v = views.front(); activateView(v); m_activeViewRunning = false; return v; } m_activeViewRunning = false; // no views exists! return nullptr; } void KateViewManager::setActiveSpace(KateViewSpace *vs) { if (activeViewSpace()) { activeViewSpace()->setActive(false); } vs->setActive(true); } void KateViewManager::setActiveView(KTextEditor::View *view) { if (activeView()) { m_views[activeView()].active = false; } if (view) { m_views[view].active = true; } } void KateViewManager::activateSpace(KTextEditor::View *v) { if (!v) { return; } KateViewSpace *vs = static_cast(v->parentWidget()->parentWidget()); if (!vs->isActiveSpace()) { setActiveSpace(vs); activateView(v); } } void KateViewManager::reactivateActiveView() { KTextEditor::View *view = activeView(); if (view) { m_views[view].active = false; activateView(view); } } void KateViewManager::activateView(KTextEditor::View *view) { if (!view) { return; } Q_ASSERT(m_views.contains(view)); if (!m_views[view].active) { // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); if (!activeViewSpace()->showView(view)) { // since it wasn't found, give'em a new one createView(view->document()); return; } setActiveView(view); bool toolbarVisible = mainWindow()->toolBar()->isVisible(); if (toolbarVisible) { mainWindow()->toolBar()->hide(); // hide to avoid toolbar flickering } if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } if (!m_blockViewCreationAndActivation) { mainWindow()->guiFactory()->addClient(view); m_guiMergedView = view; } if (toolbarVisible) { mainWindow()->toolBar()->show(); } // remember age of this view m_views[view].lruAge = m_minAge--; emit viewChanged(view); #ifdef KF5Activities_FOUND // inform activity manager m_views[view].activityResource->setUri(view->document()->url()); m_views[view].activityResource->notifyFocusedIn(); #endif } } KTextEditor::View *KateViewManager::activateView(KTextEditor::Document *d) { // no doc with this id found if (!d) { return activeView(); } // activate existing view if possible if (activeViewSpace()->showView(d)) { activateView(activeViewSpace()->currentView()); return activeView(); } // create new view otherwise createView(d); return activeView(); } void KateViewManager::slotViewChanged() { if (activeView() && !activeView()->hasFocus()) { activeView()->setFocus(); } } void KateViewManager::activateNextView() { int i = m_viewSpaceList.indexOf(activeViewSpace()) + 1; if (i >= m_viewSpaceList.count()) { i = 0; } setActiveSpace(m_viewSpaceList.at(i)); activateView(m_viewSpaceList.at(i)->currentView()); } void KateViewManager::activatePrevView() { int i = m_viewSpaceList.indexOf(activeViewSpace()) - 1; if (i < 0) { i = m_viewSpaceList.count() - 1; } setActiveSpace(m_viewSpaceList.at(i)); activateView(m_viewSpaceList.at(i)->currentView()); } void KateViewManager::documentWillBeDeleted(KTextEditor::Document *doc) { /** * collect all views of that document that belong to this manager */ QList closeList; const auto views = doc->views(); for (KTextEditor::View *v : views) { if (m_views.contains(v)) { closeList.append(v); } } while (!closeList.isEmpty()) { deleteView(closeList.takeFirst()); } } void KateViewManager::closeView(KTextEditor::View *view) { /** * kill view we want to kill */ deleteView(view); /** * try to have active view around! */ if (!activeView() && !KateApp::self()->documentManager()->documentList().isEmpty()) { createView(KateApp::self()->documentManager()->documentList().last()); } /** * if we have one now, show them in all viewspaces that got empty! */ if (KTextEditor::View *const newActiveView = activeView()) { /** * check if we have any empty viewspaces and give them a view */ for (KateViewSpace *vs : qAsConst(m_viewSpaceList)) { if (!vs->currentView()) { createView(newActiveView->document(), vs); } } emit viewChanged(newActiveView); } } void KateViewManager::splitViewSpace(KateViewSpace *vs, // = 0 Qt::Orientation o) // = Qt::Horizontal { // emergency: fallback to activeViewSpace, and if still invalid, abort if (!vs) { vs = activeViewSpace(); } if (!vs) { return; } // get current splitter, and abort if null QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter) { return; } // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); // index where to insert new splitter/viewspace const int index = currentSplitter->indexOf(vs); // create new viewspace KateViewSpace *vsNew = new KateViewSpace(this); // only 1 children -> we are the root container. So simply set the orientation // and add the new view space, then correct the sizes to 50%:50% if (currentSplitter->count() == 1) { if (currentSplitter->orientation() != o) { currentSplitter->setOrientation(o); } QList sizes = currentSplitter->sizes(); sizes << (sizes.first() - currentSplitter->handleWidth()) / 2; sizes[0] = sizes[1]; currentSplitter->insertWidget(index, vsNew); currentSplitter->setSizes(sizes); } else { // create a new QSplitter and replace vs with the splitter. vs and newVS are // the new children of the new QSplitter QSplitter *newContainer = new QSplitter(o); // we don't allow full collapse, see bug 366014 newContainer->setChildrenCollapsible(false); QList currentSizes = currentSplitter->sizes(); newContainer->addWidget(vs); newContainer->addWidget(vsNew); currentSplitter->insertWidget(index, newContainer); newContainer->show(); // fix sizes of children of old and new splitter currentSplitter->setSizes(currentSizes); QList newSizes = newContainer->sizes(); newSizes[0] = (newSizes[0] + newSizes[1] - newContainer->handleWidth()) / 2; newSizes[1] = newSizes[0]; newContainer->setSizes(newSizes); } m_viewSpaceList.append(vsNew); activeViewSpace()->setActive(false); vsNew->setActive(true); vsNew->show(); createView((KTextEditor::Document *)activeView()->document()); updateViewSpaceActions(); } void KateViewManager::closeViewSpace(KTextEditor::View *view) { KateViewSpace *space; if (view) { space = static_cast(view->parentWidget()->parentWidget()); } else { space = activeViewSpace(); } removeViewSpace(space); } void KateViewManager::toggleSplitterOrientation() { KateViewSpace *vs = activeViewSpace(); if (!vs) { return; } // get current splitter, and abort if null QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter || (currentSplitter->count() == 1)) { return; } // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); // toggle orientation if (currentSplitter->orientation() == Qt::Horizontal) { currentSplitter->setOrientation(Qt::Vertical); } else { currentSplitter->setOrientation(Qt::Horizontal); } } bool KateViewManager::viewsInSameViewSpace(KTextEditor::View *view1, KTextEditor::View *view2) { if (!view1 || !view2) { return false; } if (m_viewSpaceList.size() == 1) { return true; } KateViewSpace *vs1 = static_cast(view1->parentWidget()->parentWidget()); KateViewSpace *vs2 = static_cast(view2->parentWidget()->parentWidget()); return vs1 && (vs1 == vs2); } void KateViewManager::removeViewSpace(KateViewSpace *viewspace) { // abort if viewspace is 0 if (!viewspace) { return; } // abort if this is the last viewspace if (m_viewSpaceList.count() < 2) { return; } // get current splitter QSplitter *currentSplitter = qobject_cast(viewspace->parentWidget()); // no splitter found, bah if (!currentSplitter) { return; } // // 1. get LRU document list from current viewspace // 2. delete current view space // 3. add LRU documents from deleted viewspace to new active viewspace // // backup LRU list const QVector lruDocumntsList = viewspace->lruDocumentList(); // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); // delete views of the viewspace while (viewspace->currentView()) { deleteView(viewspace->currentView()); } // cu viewspace m_viewSpaceList.removeAt(m_viewSpaceList.indexOf(viewspace)); delete viewspace; // at this point, the splitter has exactly 1 child Q_ASSERT(currentSplitter->count() == 1); // if we are not the root splitter, move the child one level up and delete // the splitter then. if (currentSplitter != this) { // get parent splitter QSplitter *parentSplitter = qobject_cast(currentSplitter->parentWidget()); // only do magic if found ;) if (parentSplitter) { int index = parentSplitter->indexOf(currentSplitter); // save current splitter size, as the removal of currentSplitter looses the info QList parentSizes = parentSplitter->sizes(); parentSplitter->insertWidget(index, currentSplitter->widget(0)); delete currentSplitter; // now restore the sizes again parentSplitter->setSizes(parentSizes); } } else if (QSplitter *splitter = qobject_cast(currentSplitter->widget(0))) { // we are the root splitter and have only one child, which is also a splitter // -> eliminate the redundant splitter and move both children into the root splitter QList sizes = splitter->sizes(); // adapt splitter orientation to the splitter we are about to delete currentSplitter->setOrientation(splitter->orientation()); currentSplitter->addWidget(splitter->widget(0)); currentSplitter->addWidget(splitter->widget(0)); delete splitter; currentSplitter->setSizes(sizes); } // merge docuemnts of closed view space activeViewSpace()->mergeLruList(lruDocumntsList); // find the view that is now active. KTextEditor::View *v = activeViewSpace()->currentView(); if (v) { activateView(v); } updateViewSpaceActions(); emit viewChanged(v); } void KateViewManager::slotCloseOtherViews() { // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); const KateViewSpace *active = activeViewSpace(); const auto viewSpaces = m_viewSpaceList; for (KateViewSpace *v : viewSpaces) { if (active != v) { removeViewSpace(v); } } } void KateViewManager::slotHideOtherViews(bool hideOthers) { // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); const KateViewSpace *active = activeViewSpace(); for (KateViewSpace *v : qAsConst(m_viewSpaceList)) { if (active != v) { v->setVisible(!hideOthers); } } // disable the split actions, if we are in single-view-mode m_splitViewVert->setDisabled(hideOthers); m_splitViewHoriz->setDisabled(hideOthers); m_closeView->setDisabled(hideOthers); m_closeOtherViews->setDisabled(hideOthers); m_toggleSplitterOrientation->setDisabled(hideOthers); } /** * session config functions */ void KateViewManager::saveViewConfiguration(KConfigGroup &config) { // set Active ViewSpace to 0, just in case there is none active (would be // strange) and config somehow has previous value set config.writeEntry("Active ViewSpace", 0); m_splitterIndex = 0; saveSplitterConfig(this, config.config(), config.name()); } void KateViewManager::restoreViewConfiguration(const KConfigGroup &config) { /** * remove the single client that is registered at the factory, if any */ if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } /** * delete viewspaces, they will delete the views */ qDeleteAll(m_viewSpaceList); m_viewSpaceList.clear(); /** * delete mapping of now deleted views */ m_views.clear(); /** * kill all previous existing sub-splitters, just to be sure * e.g. important if one restores a config in an existing window with some splitters */ while (count() > 0) { delete widget(0); } // reset lru history, too! m_minAge = 0; // start recursion for the root splitter (Splitter 0) restoreSplitter(config.config(), config.name() + QStringLiteral("-Splitter 0"), this, config.name()); // finally, make the correct view from the last session active int lastViewSpace = config.readEntry("Active ViewSpace", 0); if (lastViewSpace > m_viewSpaceList.size()) { lastViewSpace = 0; } if (lastViewSpace >= 0 && lastViewSpace < m_viewSpaceList.size()) { setActiveSpace(m_viewSpaceList.at(lastViewSpace)); // activate correct view (wish #195435, #188764) activateView(m_viewSpaceList.at(lastViewSpace)->currentView()); // give view the focus to avoid focus stealing by toolviews / plugins m_viewSpaceList.at(lastViewSpace)->currentView()->setFocus(); } // emergency if (m_viewSpaceList.empty()) { // kill bad children while (count()) { delete widget(0); } KateViewSpace *vs = new KateViewSpace(this, nullptr); addWidget(vs); vs->setActive(true); m_viewSpaceList.append(vs); /** * activate at least one document! */ activateView(KateApp::self()->documentManager()->documentList().last()); if (!vs->currentView()) { createView(activeView()->document(), vs); } } updateViewSpaceActions(); } QString KateViewManager::saveSplitterConfig(QSplitter *s, KConfigBase *configBase, const QString &viewConfGrp) { /** * avoid to export invisible view spaces * else they will stick around for ever in sessions * bug 358266 - code initially done during load * bug 381433 - moved code to save */ /** * create new splitter name, might be not used */ const auto grp = QString(viewConfGrp + QStringLiteral("-Splitter %1")).arg(m_splitterIndex); ++m_splitterIndex; // a QSplitter has two children, either QSplitters and/or KateViewSpaces // special case: root splitter might have only one child (just for info) QStringList childList; const auto sizes = s->sizes(); for (int it = 0; it < s->count(); ++it) { // skip empty sized invisible ones, if not last one, we need one thing at least if ((sizes[it] == 0) && ((it + 1 < s->count()) || !childList.empty())) continue; // For KateViewSpaces, ask them to save the file list. auto obj = s->widget(it); if (auto kvs = qobject_cast(obj)) { childList.append(QString(viewConfGrp + QStringLiteral("-ViewSpace %1")).arg(m_viewSpaceList.indexOf(kvs))); kvs->saveConfig(configBase, m_viewSpaceList.indexOf(kvs), viewConfGrp); // save active viewspace if (kvs->isActiveSpace()) { KConfigGroup viewConfGroup(configBase, viewConfGrp); viewConfGroup.writeEntry("Active ViewSpace", m_viewSpaceList.indexOf(kvs)); } } // for QSplitters, recurse else if (auto splitter = qobject_cast(obj)) { childList.append(saveSplitterConfig(splitter, configBase, viewConfGrp)); } } // if only one thing, skip splitter config export, if not top splitter if ((s != this) && (childList.size() == 1)) return childList.at(0); // Save sizes, orient, children for this splitter KConfigGroup config(configBase, grp); config.writeEntry("Sizes", sizes); config.writeEntry("Orientation", int(s->orientation())); config.writeEntry("Children", childList); return grp; } void KateViewManager::restoreSplitter(const KConfigBase *configBase, const QString &group, QSplitter *parent, const QString &viewConfGrp) { KConfigGroup config(configBase, group); parent->setOrientation((Qt::Orientation)config.readEntry("Orientation", int(Qt::Horizontal))); const QStringList children = config.readEntry("Children", QStringList()); for (const auto &str : children) { // for a viewspace, create it and open all documents therein. if (str.startsWith(viewConfGrp + QStringLiteral("-ViewSpace"))) { KateViewSpace *vs = new KateViewSpace(this, nullptr); m_viewSpaceList.append(vs); // make active so that the view created in restoreConfig has this // new view space as parent. setActiveSpace(vs); parent->addWidget(vs); vs->restoreConfig(this, configBase, str); vs->show(); } else { // for a splitter, recurse auto newContainer = new QSplitter(parent); // we don't allow full collapse, see bug 366014 newContainer->setChildrenCollapsible(false); restoreSplitter(configBase, str, newContainer, viewConfGrp); } } // set sizes parent->setSizes(config.readEntry("Sizes", QList())); parent->show(); } void KateViewManager::moveSplitter(Qt::Key key, int repeats) { if (repeats < 1) { return; } KateViewSpace *vs = activeViewSpace(); if (!vs) { return; } QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter) { return; } if (currentSplitter->count() == 1) { return; } int move = 4 * repeats; // try to use font height in pixel to move splitter { KTextEditor::Attribute::Ptr attrib(vs->currentView()->defaultStyleAttribute(KTextEditor::dsNormal)); QFontMetrics fm(attrib->font()); move = fm.height() * repeats; } QWidget *currentWidget = (QWidget *)vs; bool foundSplitter = false; // find correct splitter to be moved while (currentSplitter && currentSplitter->count() != 1) { if (currentSplitter->orientation() == Qt::Horizontal && (key == Qt::Key_Right || key == Qt::Key_Left)) { foundSplitter = true; } if (currentSplitter->orientation() == Qt::Vertical && (key == Qt::Key_Up || key == Qt::Key_Down)) { foundSplitter = true; } // if the views within the current splitter can be resized, resize them if (foundSplitter) { QList currentSizes = currentSplitter->sizes(); int index = currentSplitter->indexOf(currentWidget); if ((index == 0 && (key == Qt::Key_Left || key == Qt::Key_Up)) || (index == 1 && (key == Qt::Key_Right || key == Qt::Key_Down))) { currentSizes[index] -= move; } if ((index == 0 && (key == Qt::Key_Right || key == Qt::Key_Down)) || (index == 1 && (key == Qt::Key_Left || key == Qt::Key_Up))) { currentSizes[index] += move; } if (index == 0 && (key == Qt::Key_Right || key == Qt::Key_Down)) { currentSizes[index + 1] -= move; } if (index == 0 && (key == Qt::Key_Left || key == Qt::Key_Up)) { currentSizes[index + 1] += move; } if (index == 1 && (key == Qt::Key_Right || key == Qt::Key_Down)) { currentSizes[index - 1] += move; } if (index == 1 && (key == Qt::Key_Left || key == Qt::Key_Up)) { currentSizes[index - 1] -= move; } currentSplitter->setSizes(currentSizes); break; } currentWidget = (QWidget *)currentSplitter; // the parent of the current splitter will become the current splitter currentSplitter = qobject_cast(currentSplitter->parentWidget()); } } diff --git a/kate/kateviewmanager.h b/kate/kateviewmanager.h index eb14d825d..31c604f9b 100644 --- a/kate/kateviewmanager.h +++ b/kate/kateviewmanager.h @@ -1,346 +1,346 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ #ifndef __KATE_VIEWMANAGER_H__ #define __KATE_VIEWMANAGER_H__ #include "katedocmanager.h" -#include #include -#include #include +#include +#include namespace KActivities { class ResourceInstance; } namespace KTextEditor { class View; class Document; } class KateDocumentInfo; class KConfigGroup; class KConfigBase; class KateMainWindow; class KateViewSpace; class KateViewManager : public QSplitter { Q_OBJECT public: KateViewManager(QWidget *parentW, KateMainWindow *parent); ~KateViewManager() override; private: /** * create all actions needed for the view manager */ void setupActions(); void updateViewSpaceActions(); public: /* This will save the splitter configuration */ void saveViewConfiguration(KConfigGroup &group); /* restore it */ void restoreViewConfiguration(const KConfigGroup &group); KTextEditor::Document *openUrl(const QUrl &url, const QString &encoding, bool activate = true, bool isTempFile = false, const KateDocumentInfo &docInfo = KateDocumentInfo()); KTextEditor::Document *openUrls(const QList &url, const QString &encoding, bool isTempFile = false, const KateDocumentInfo &docInfo = KateDocumentInfo()); KTextEditor::View *openUrlWithView(const QUrl &url, const QString &encoding); public Q_SLOTS: void openUrl(const QUrl &url); public: void closeView(KTextEditor::View *view); KateMainWindow *mainWindow(); private Q_SLOTS: void activateView(KTextEditor::View *view); void activateSpace(KTextEditor::View *v); public Q_SLOTS: void slotDocumentNew(); void slotDocumentOpen(); void slotDocumentClose(); void slotDocumentClose(KTextEditor::Document *document); void setActiveSpace(KateViewSpace *vs); void setActiveView(KTextEditor::View *view); void activateNextView(); void activatePrevView(); Q_SIGNALS: void viewChanged(KTextEditor::View *); void viewCreated(KTextEditor::View *); public: /** * create and activate a new view for doc, if doc == 0, then * create a new document. * Can return NULL. */ KTextEditor::View *createView(KTextEditor::Document *doc = nullptr, KateViewSpace *vs = nullptr); private: bool deleteView(KTextEditor::View *view); void moveViewtoSplit(KTextEditor::View *view); void moveViewtoStack(KTextEditor::View *view); /* Save the configuration of a single splitter. * If child splitters are found, it calls it self with those as the argument. * If a viewspace child is found, it is asked to save its filelist. */ QString saveSplitterConfig(QSplitter *s, KConfigBase *config, const QString &viewConfGrp); /** Restore a single splitter. * This is all the work is done for @see saveSplitterConfig() */ void restoreSplitter(const KConfigBase *config, const QString &group, QSplitter *parent, const QString &viewConfGrp); void removeViewSpace(KateViewSpace *viewspace); public: KTextEditor::View *activeView(); KateViewSpace *activeViewSpace(); private Q_SLOTS: void slotViewChanged(); void documentCreated(KTextEditor::Document *doc); void documentWillBeDeleted(KTextEditor::Document *doc); void documentSavedOrUploaded(KTextEditor::Document *document, bool saveAs); /** * This signal is emitted before the documents batch is going to be deleted * * note that the batch can be interrupted in the middle and only some * of the documents may be actually deleted. See documentsDeleted() signal. * * @param documents documents we want to delete, may not be deleted */ void aboutToDeleteDocuments(const QList &documents); /** * This signal is emitted after the documents batch was deleted * * This is the batch closing signal for aboutToDeleteDocuments * @param documents the documents that weren't deleted after all */ void documentsDeleted(const QList &documents); public Q_SLOTS: /** * Splits a KateViewSpace into two in the following steps: * 1. create a QSplitter in the parent of the KateViewSpace to be split * 2. move the to-be-split KateViewSpace to the new splitter * 3. create new KateViewSpace and added to the new splitter * 4. create KateView to populate the new viewspace. * 5. The new KateView is made the active one, because createView() does that. * If no viewspace is provided, the result of activeViewSpace() is used. * The orientation of the new splitter is determined by the value of o. * Note: horizontal splitter means vertically aligned views. */ void splitViewSpace(KateViewSpace *vs = nullptr, Qt::Orientation o = Qt::Horizontal); /** * Close the view space that contains the given view. If no view was * given, then the active view space will be closed instead. */ void closeViewSpace(KTextEditor::View *view = nullptr); /** * @returns true of the two given views share the same view space. */ bool viewsInSameViewSpace(KTextEditor::View *view1, KTextEditor::View *view2); /** * activate view for given document * @param doc document to activate view for */ KTextEditor::View *activateView(KTextEditor::Document *doc); /** Splits the active viewspace horizontally */ void slotSplitViewSpaceHoriz() { splitViewSpace(nullptr, Qt::Vertical); } /** Splits the active viewspace vertically */ void slotSplitViewSpaceVert() { splitViewSpace(); } /** moves the splitter according to the key that has been pressed */ void moveSplitter(Qt::Key key, int repeats = 1); /** moves the splitter to the right */ void moveSplitterRight() { moveSplitter(Qt::Key_Right); } /** moves the splitter to the left */ void moveSplitterLeft() { moveSplitter(Qt::Key_Left); } /** moves the splitter up */ void moveSplitterUp() { moveSplitter(Qt::Key_Up); } /** moves the splitter down */ void moveSplitterDown() { moveSplitter(Qt::Key_Down); } /** closes the current view space. */ void slotCloseCurrentViewSpace() { closeViewSpace(); } /** closes every view but the active one */ void slotCloseOtherViews(); /** hide every view but the active one */ void slotHideOtherViews(bool hideOthers); void reactivateActiveView(); /** * Toogle the orientation of current split view */ void toggleSplitterOrientation(); /** * Get a list of all views. * @return all views */ QList views() const { return m_views.keys(); } /** * get views in lru order * @return views in lru order */ QList sortedViews() const { QMap sortedViews; QHashIterator i(m_views); while (i.hasNext()) { i.next(); sortedViews[i.value().lruAge] = i.key(); } return sortedViews.values(); } private: KateMainWindow *m_mainWindow; bool m_init; QAction *m_splitViewVert; QAction *m_splitViewHoriz; QAction *m_closeView; QAction *m_closeOtherViews; QAction *m_toggleSplitterOrientation; QAction *m_hideOtherViews; QAction *goNext; QAction *goPrev; QList m_viewSpaceList; bool m_blockViewCreationAndActivation; bool m_activeViewRunning; int m_splitterIndex; // used during saving splitter config. /** * View meta data */ class ViewData { public: /** * Default constructor */ ViewData() = default; /** * view active? */ bool active = false; /** * lru age of the view * important: smallest age ==> latest used view */ qint64 lruAge = 0; /** * activity resource for the view */ KActivities::ResourceInstance *activityResource = nullptr; }; /** * central storage of all views known in the view manager * maps the view to meta data */ QHash m_views; /** * current minimal age */ qint64 m_minAge; /** * the view that is ATM merged to the xml gui factory */ QPointer m_guiMergedView; /** * last url of open file dialog, used if current document has no valid url */ QUrl m_lastOpenDialogUrl; }; #endif diff --git a/kate/kateviewspace.cpp b/kate/kateviewspace.cpp index 3c4e0370b..8e4e68aa9 100644 --- a/kate/kateviewspace.cpp +++ b/kate/kateviewspace.cpp @@ -1,737 +1,737 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001, 2005 Anders Lund 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. */ #include "kateviewspace.h" -#include "katemainwindow.h" -#include "kateviewmanager.h" -#include "katedocmanager.h" +#include "kactioncollection.h" #include "kateapp.h" +#include "katedebug.h" +#include "katedocmanager.h" #include "katefileactions.h" +#include "katemainwindow.h" #include "katesessionmanager.h" -#include "katedebug.h" #include "katetabbar.h" -#include "kactioncollection.h" #include "kateupdatedisabler.h" +#include "kateviewmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include // BEGIN KateViewSpace KateViewSpace::KateViewSpace(KateViewManager *viewManager, QWidget *parent, const char *name) : QWidget(parent) , m_viewManager(viewManager) , m_isActiveSpace(false) { setObjectName(QString::fromLatin1(name)); QVBoxLayout *layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); // BEGIN tab bar QHBoxLayout *hLayout = new QHBoxLayout(); hLayout->setSpacing(0); hLayout->setContentsMargins(0, 0, 0, 0); // add tab bar m_tabBar = new KateTabBar(this); connect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView); connect(m_tabBar, &KateTabBar::moreTabsRequested, this, &KateViewSpace::addTabs); connect(m_tabBar, &KateTabBar::lessTabsRequested, this, &KateViewSpace::removeTabs); connect(m_tabBar, &KateTabBar::closeTabRequested, this, &KateViewSpace::closeTabRequest, Qt::QueuedConnection); connect(m_tabBar, &KateTabBar::contextMenuRequest, this, &KateViewSpace::showContextMenu, Qt::QueuedConnection); connect(m_tabBar, &KateTabBar::newTabRequested, this, &KateViewSpace::createNewDocument); connect(m_tabBar, SIGNAL(activateViewSpaceRequested()), this, SLOT(makeActive())); hLayout->addWidget(m_tabBar); // add quick open m_quickOpen = new QToolButton(this); m_quickOpen->setAutoRaise(true); KAcceleratorManager::setNoAccel(m_quickOpen); m_quickOpen->installEventFilter(this); // on click, active this view space hLayout->addWidget(m_quickOpen); // forward tab bar quick open action to globa quick open action QAction *bridge = new QAction(QIcon::fromTheme(QStringLiteral("tab-duplicate")), i18nc("indicator for more documents", "+%1", 100), this); m_quickOpen->setDefaultAction(bridge); QAction *quickOpen = m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_quick_open")); Q_ASSERT(quickOpen); bridge->setToolTip(quickOpen->toolTip()); bridge->setWhatsThis(i18n("Click here to switch to the Quick Open view.")); connect(bridge, &QAction::triggered, quickOpen, &QAction::trigger); // add vertical split view space m_split = new QToolButton(this); m_split->setAutoRaise(true); m_split->setPopupMode(QToolButton::InstantPopup); m_split->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_split_vert"))); m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_split_horiz"))); m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_close_current_space"))); m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_close_others"))); m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_hide_others"))); m_split->setWhatsThis(i18n("Control view space splitting")); m_split->installEventFilter(this); // on click, active this view space hLayout->addWidget(m_split); layout->addLayout(hLayout); // END tab bar stack = new QStackedWidget(this); stack->setFocus(); stack->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding)); layout->addWidget(stack); m_group.clear(); // connect signal to hide/show statusbar connect(m_viewManager->mainWindow(), &KateMainWindow::statusBarToggled, this, &KateViewSpace::statusBarToggled); connect(m_viewManager->mainWindow(), &KateMainWindow::tabBarToggled, this, &KateViewSpace::tabBarToggled); // init the bars... statusBarToggled(); tabBarToggled(); // make sure we show correct number of hidden documents updateQuickOpen(); connect(KateApp::self()->documentManager(), &KateDocManager::documentCreated, this, &KateViewSpace::updateQuickOpen); connect(KateApp::self()->documentManager(), &KateDocManager::documentsDeleted, this, &KateViewSpace::updateQuickOpen); } bool KateViewSpace::eventFilter(QObject *obj, QEvent *event) { QToolButton *button = qobject_cast(obj); // quick open button: show tool tip with shortcut if (button == m_quickOpen && event->type() == QEvent::ToolTip) { QHelpEvent *e = static_cast(event); QAction *quickOpen = m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_quick_open")); Q_ASSERT(quickOpen); QToolTip::showText(e->globalPos(), button->toolTip() + QStringLiteral(" (%1)").arg(quickOpen->shortcut().toString()), button); return true; } // quick open button: What's This if (button == m_quickOpen && event->type() == QEvent::WhatsThis) { QHelpEvent *e = static_cast(event); const int hiddenDocs = hiddenDocuments(); QString helpText = (hiddenDocs == 0) ? i18n("Click here to switch to the Quick Open view.") : i18np("Currently, there is one more document open. To see all open documents, switch to the Quick Open view by clicking here.", "Currently, there are %1 more documents open. To see all open documents, switch to the Quick Open view by clicking here.", hiddenDocs); QWhatsThis::showText(e->globalPos(), helpText, m_quickOpen); return true; } // on mouse press on view space bar tool buttons: activate this space if (button && !isActiveSpace() && event->type() == QEvent::MouseButtonPress) { m_viewManager->setActiveSpace(this); if (currentView()) { m_viewManager->activateView(currentView()->document()); } } return false; } void KateViewSpace::statusBarToggled() { KateUpdateDisabler updatesDisabled(m_viewManager->mainWindow()); for (KTextEditor::Document *doc : qAsConst(m_lruDocList)) { if (m_docToView.contains(doc)) { m_docToView[doc]->setStatusBarEnabled(m_viewManager->mainWindow()->showStatusBar()); } } } QVector KateViewSpace::lruDocumentList() const { return m_lruDocList; } void KateViewSpace::mergeLruList(const QVector &lruList) { // merge lruList documents that are not in m_lruDocList QVectorIterator it(lruList); it.toBack(); while (it.hasPrevious()) { KTextEditor::Document *doc = it.previous(); if (!m_lruDocList.contains(doc)) { registerDocument(doc, false); } } } void KateViewSpace::tabBarToggled() { KateUpdateDisabler updatesDisabled(m_viewManager->mainWindow()); m_tabBar->setVisible(m_viewManager->mainWindow()->showTabBar()); m_split->setVisible(m_viewManager->mainWindow()->showTabBar()); m_quickOpen->setVisible(m_viewManager->mainWindow()->showTabBar()); } KTextEditor::View *KateViewSpace::createView(KTextEditor::Document *doc) { // should only be called if a view does not yet exist Q_ASSERT(!m_docToView.contains(doc)); /** * Create a fresh view */ KTextEditor::View *v = doc->createView(stack, m_viewManager->mainWindow()->wrapper()); // set status bar to right state v->setStatusBarEnabled(m_viewManager->mainWindow()->showStatusBar()); // restore the config of this view if possible if (!m_group.isEmpty()) { QString fn = v->document()->url().toString(); if (!fn.isEmpty()) { QString vgroup = QStringLiteral("%1 %2").arg(m_group, fn); KateSession::Ptr as = KateApp::self()->sessionManager()->activeSession(); if (as->config() && as->config()->hasGroup(vgroup)) { KConfigGroup cg(as->config(), vgroup); v->readSessionConfig(cg); } } } // register document, it is shown below through showView() then if (!m_lruDocList.contains(doc)) { registerDocument(doc); Q_ASSERT(m_lruDocList.contains(doc)); } // insert View into stack stack->addWidget(v); m_docToView[doc] = v; showView(v); return v; } void KateViewSpace::removeView(KTextEditor::View *v) { // remove view mappings Q_ASSERT(m_docToView.contains(v->document())); m_docToView.remove(v->document()); // ...and now: remove from view space stack->removeWidget(v); // switch to most recently used rather than letting stack choose one // (last element could well be v->document() being removed here) for (auto it = m_lruDocList.rbegin(); it != m_lruDocList.rend(); ++it) { if (m_docToView.contains(*it)) { showView(*it); break; } } } bool KateViewSpace::showView(KTextEditor::Document *document) { const int index = m_lruDocList.lastIndexOf(document); if (index < 0) { return false; } if (!m_docToView.contains(document)) { return false; } KTextEditor::View *kv = m_docToView[document]; // move view to end of list m_lruDocList.removeAt(index); m_lruDocList.append(document); stack->setCurrentWidget(kv); kv->show(); // in case a tab does not exist, add one if (!m_docToTabId.contains(document)) { // if space is available, add button if (m_tabBar->count() < m_tabBar->maxTabCount()) { // just insert insertTab(0, document); } else { // remove "oldest" button and replace with new one Q_ASSERT(m_lruDocList.size() > m_tabBar->count()); // we need to subtract by 1 more, as we just added ourself to the end of the lru list! KTextEditor::Document *docToHide = m_lruDocList[m_lruDocList.size() - m_tabBar->maxTabCount() - 1]; Q_ASSERT(m_docToTabId.contains(docToHide)); removeTab(docToHide, false); // add new one always at the beginning insertTab(0, document); } } // follow current view Q_ASSERT(m_docToTabId.contains(document)); m_tabBar->setCurrentTab(m_docToTabId.value(document, -1)); return true; } void KateViewSpace::changeView(int id) { KTextEditor::Document *doc = m_docToTabId.key(id); Q_ASSERT(doc); // make sure we open the view in this view space if (!isActiveSpace()) { m_viewManager->setActiveSpace(this); } // tell the view manager to show the view m_viewManager->activateView(doc); } KTextEditor::View *KateViewSpace::currentView() { // might be 0 if the stack contains no view return (KTextEditor::View *)stack->currentWidget(); } bool KateViewSpace::isActiveSpace() { return m_isActiveSpace; } void KateViewSpace::setActive(bool active) { m_isActiveSpace = active; m_tabBar->setActive(active); } void KateViewSpace::makeActive(bool focusCurrentView) { if (!isActiveSpace()) { m_viewManager->setActiveSpace(this); if (focusCurrentView && currentView()) { m_viewManager->activateView(currentView()->document()); } } Q_ASSERT(isActiveSpace()); } void KateViewSpace::insertTab(int index, KTextEditor::Document *doc) { // doc should be in the lru list Q_ASSERT(m_lruDocList.contains(doc)); // doc should not have a id Q_ASSERT(!m_docToTabId.contains(doc)); const int id = m_tabBar->insertTab(index, doc->documentName()); m_tabBar->setTabToolTip(id, doc->url().toDisplayString()); m_docToTabId[doc] = id; updateDocumentState(doc); connect(doc, &KTextEditor::Document::documentNameChanged, this, &KateViewSpace::updateDocumentName); connect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateViewSpace::updateDocumentUrl); connect(doc, &KTextEditor::Document::modifiedChanged, this, &KateViewSpace::updateDocumentState); } int KateViewSpace::removeTab(KTextEditor::Document *doc, bool documentDestroyed) { // // WARNING: removeTab() is also called from documentDestroyed(). // Therefore, is may be that doc is half destroyed already. // Therefore, do not access any KTextEditor::Document functions here! // Only access QObject functions! // Q_ASSERT(m_docToTabId.contains(doc)); const int id = m_docToTabId.value(doc, -1); const int removeIndex = m_tabBar->removeTab(id); m_docToTabId.remove(doc); if (!documentDestroyed) { disconnect(doc, &KTextEditor::Document::documentNameChanged, this, &KateViewSpace::updateDocumentName); disconnect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateViewSpace::updateDocumentUrl); disconnect(doc, &KTextEditor::Document::modifiedChanged, this, &KateViewSpace::updateDocumentState); } return removeIndex; } void KateViewSpace::removeTabs(int count) { const int start = count; /// remove @p count tabs from the tab bar, as they do not all fit while (count > 0) { const int tabCount = m_tabBar->count(); KTextEditor::Document *removeDoc = m_lruDocList[m_lruDocList.size() - tabCount]; removeTab(removeDoc, false); Q_ASSERT(!m_docToTabId.contains(removeDoc)); --count; } // make sure quick open shows the correct number of hidden documents if (start != count) { updateQuickOpen(); } } void KateViewSpace::addTabs(int count) { const int start = count; /// @p count tabs still fit into the tab bar: add as man as possible while (count > 0) { const int tabCount = m_tabBar->count(); if (m_lruDocList.size() <= tabCount) { break; } insertTab(tabCount, m_lruDocList[m_lruDocList.size() - tabCount - 1]); --count; } // make sure quick open shows the correct number of hidden documents if (start != count) { updateQuickOpen(); } } void KateViewSpace::registerDocument(KTextEditor::Document *doc, bool append) { // at this point, the doc should be completely unknown Q_ASSERT(!m_lruDocList.contains(doc)); Q_ASSERT(!m_docToView.contains(doc)); Q_ASSERT(!m_docToTabId.contains(doc)); if (append) { m_lruDocList.append(doc); } else { // prepending == merge doc of closed viewspace m_lruDocList.prepend(doc); } connect(doc, &QObject::destroyed, this, &KateViewSpace::documentDestroyed); // if space is available, add button if (m_tabBar->count() < m_tabBar->maxTabCount()) { insertTab(0, doc); updateQuickOpen(); } else if (append) { // remove "oldest" button and replace with new one Q_ASSERT(m_lruDocList.size() > m_tabBar->count()); KTextEditor::Document *docToHide = m_lruDocList[m_lruDocList.size() - m_tabBar->maxTabCount() - 1]; Q_ASSERT(m_docToTabId.contains(docToHide)); removeTab(docToHide, false); // add new one at removed position insertTab(0, doc); } } void KateViewSpace::documentDestroyed(QObject *doc) { // WARNING: this pointer is half destroyed KTextEditor::Document *invalidDoc = static_cast(doc); Q_ASSERT(m_lruDocList.contains(invalidDoc)); m_lruDocList.remove(m_lruDocList.indexOf(invalidDoc)); // disconnect entirely disconnect(doc, nullptr, this, nullptr); // case: there was no view created yet, but still a button was added if (m_docToTabId.contains(invalidDoc)) { removeTab(invalidDoc, true); // maybe show another tab button in its stead if (m_lruDocList.size() >= m_tabBar->maxTabCount() && m_tabBar->count() < m_tabBar->maxTabCount()) { KTextEditor::Document *docToShow = m_lruDocList[m_lruDocList.size() - m_tabBar->count() - 1]; Q_ASSERT(!m_docToTabId.contains(docToShow)); // add tab that now fits into the bar insertTab(m_tabBar->count(), docToShow); } } // at this point, the doc should be completely unknown Q_ASSERT(!m_lruDocList.contains(invalidDoc)); Q_ASSERT(!m_docToView.contains(invalidDoc)); Q_ASSERT(!m_docToTabId.contains(invalidDoc)); } void KateViewSpace::updateDocumentName(KTextEditor::Document *doc) { const int buttonId = m_docToTabId[doc]; Q_ASSERT(buttonId >= 0); m_tabBar->setTabText(buttonId, doc->documentName()); m_tabBar->setTabToolTip(buttonId, doc->url().toDisplayString()); } void KateViewSpace::updateDocumentUrl(KTextEditor::Document *doc) { const int buttonId = m_docToTabId[doc]; Q_ASSERT(buttonId >= 0); m_tabBar->setTabUrl(buttonId, doc->url()); } void KateViewSpace::updateDocumentState(KTextEditor::Document *doc) { QIcon icon; if (doc->isModified()) { icon = QIcon::fromTheme(QStringLiteral("document-save")); } Q_ASSERT(m_docToTabId.contains(doc)); const int buttonId = m_docToTabId[doc]; m_tabBar->setTabIcon(buttonId, icon); } void KateViewSpace::closeTabRequest(int id) { KTextEditor::Document *doc = m_docToTabId.key(id); Q_ASSERT(doc); m_viewManager->slotDocumentClose(doc); } void KateViewSpace::createNewDocument() { // make sure we open the view in this view space if (!isActiveSpace()) { m_viewManager->setActiveSpace(this); } // create document KTextEditor::Document *doc = KateApp::self()->documentManager()->createDoc(); // tell the view manager to show the document m_viewManager->activateView(doc); } void KateViewSpace::updateQuickOpen() { const int hiddenDocs = hiddenDocuments(); if (hiddenDocs == 0) { m_quickOpen->setToolButtonStyle(Qt::ToolButtonIconOnly); m_quickOpen->defaultAction()->setText(QString()); } else { m_quickOpen->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); m_quickOpen->defaultAction()->setText(i18nc("indicator for more documents", "+%1", hiddenDocs)); } } void KateViewSpace::focusPrevTab() { const int id = m_tabBar->prevTab(); if (id >= 0) { changeView(id); } } void KateViewSpace::focusNextTab() { const int id = m_tabBar->nextTab(); if (id >= 0) { changeView(id); } } int KateViewSpace::hiddenDocuments() const { const int hiddenDocs = KateApp::self()->documents().count() - m_tabBar->count(); Q_ASSERT(hiddenDocs >= 0); return hiddenDocs; } void KateViewSpace::showContextMenu(int id, const QPoint &globalPos) { // right now, show no context menu on empty tab bar space if (id < 0) { return; } KTextEditor::Document *doc = m_docToTabId.key(id); Q_ASSERT(doc); auto addActionFromCollection = [this](QMenu *menu, const char *action_name) { QAction *action = m_viewManager->mainWindow()->action(action_name); return menu->addAction(action->icon(), action->text()); }; QMenu menu(this); QAction *aCloseTab = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-close")), i18n("&Close Document")); QAction *aCloseOthers = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-close-other")), i18n("Close Other &Documents")); menu.addSeparator(); QAction *aCopyPath = addActionFromCollection(&menu, "file_copy_filepath"); QAction *aOpenFolder = addActionFromCollection(&menu, "file_open_containing_folder"); QAction *aFileProperties = addActionFromCollection(&menu, "file_properties"); menu.addSeparator(); QAction *aRenameFile = addActionFromCollection(&menu, "file_rename"); QAction *aDeleteFile = addActionFromCollection(&menu, "file_delete"); menu.addSeparator(); QMenu *mCompareWithActive = new QMenu(i18n("Compare with active document"), &menu); mCompareWithActive->setIcon(QIcon::fromTheme(QStringLiteral("kompare"))); menu.addMenu(mCompareWithActive); if (KateApp::self()->documentManager()->documentList().count() < 2) { aCloseOthers->setEnabled(false); } if (doc->url().isEmpty()) { aCopyPath->setEnabled(false); aOpenFolder->setEnabled(false); aRenameFile->setEnabled(false); aDeleteFile->setEnabled(false); aFileProperties->setEnabled(false); mCompareWithActive->setEnabled(false); } auto activeDocument = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView()->document(); // used for mCompareWithActive which is used with another tab which is not active // both documents must have urls and must not be the same to have the compare feature enabled if (activeDocument->url().isEmpty() || activeDocument == doc) { mCompareWithActive->setEnabled(false); } if (mCompareWithActive->isEnabled()) { for (auto &&diffTool : KateFileActions::supportedDiffTools()) { QAction *compareAction = mCompareWithActive->addAction(diffTool); compareAction->setData(diffTool); } } QAction *choice = menu.exec(globalPos); if (!choice) { return; } if (choice == aCloseTab) { closeTabRequest(id); } else if (choice == aCloseOthers) { KateApp::self()->documentManager()->closeOtherDocuments(doc); } else if (choice == aCopyPath) { KateFileActions::copyFilePathToClipboard(doc); } else if (choice == aOpenFolder) { KateFileActions::openContainingFolder(doc); } else if (choice == aFileProperties) { KateFileActions::openFilePropertiesDialog(doc); } else if (choice == aRenameFile) { KateFileActions::renameDocumentFile(this, doc); } else if (choice == aDeleteFile) { KateFileActions::deleteDocumentFile(this, doc); } else if (choice->parent() == mCompareWithActive) { QString actionData = choice->data().toString(); // name of the executable of the diff program if (!KateFileActions::compareWithExternalProgram(activeDocument, doc, actionData)) { QMessageBox::information(this, i18n("Could not start program"), i18n("The selected program could not be started. Maybe it is not installed."), QMessageBox::StandardButton::Ok); } } } void KateViewSpace::saveConfig(KConfigBase *config, int myIndex, const QString &viewConfGrp) { // qCDebug(LOG_KATE)<<"KateViewSpace::saveConfig("< views; QStringList lruList; for (KTextEditor::Document *doc : qAsConst(m_lruDocList)) { lruList << doc->url().toString(); if (m_docToView.contains(doc)) { views.append(m_docToView[doc]); } } KConfigGroup group(config, groupname); group.writeEntry("Documents", lruList); group.writeEntry("Count", views.count()); if (currentView()) { group.writeEntry("Active View", currentView()->document()->url().toString()); } // Save file list, including cursor position in this instance. int idx = 0; for (QVector::iterator it = views.begin(); it != views.end(); ++it) { if (!(*it)->document()->url().isEmpty()) { group.writeEntry(QStringLiteral("View %1").arg(idx), (*it)->document()->url().toString()); // view config, group: "ViewSpace url" QString vgroup = QStringLiteral("%1 %2").arg(groupname, (*it)->document()->url().toString()); KConfigGroup viewGroup(config, vgroup); (*it)->writeSessionConfig(viewGroup); } ++idx; } } void KateViewSpace::restoreConfig(KateViewManager *viewMan, const KConfigBase *config, const QString &groupname) { KConfigGroup group(config, groupname); // restore Document lru list so that all tabs from the last session reappear const QStringList lruList = group.readEntry("Documents", QStringList()); for (int i = 0; i < lruList.size(); ++i) { // ignore non-existing documents and documents we already added to the LRU list // no wild m_lruDocList modifications to keep list + tabs in sync even for restore auto doc = KateApp::self()->documentManager()->findDocument(QUrl(lruList[i])); if (doc && !m_lruDocList.contains(doc)) { registerDocument(doc); Q_ASSERT(m_lruDocList.contains(doc)); } } // restore active view properties const QString fn = group.readEntry("Active View"); if (!fn.isEmpty()) { KTextEditor::Document *doc = KateApp::self()->documentManager()->findDocument(QUrl(fn)); if (doc) { // view config, group: "ViewSpace url" QString vgroup = QStringLiteral("%1 %2").arg(groupname, fn); KConfigGroup configGroup(config, vgroup); auto view = viewMan->createView(doc, this); if (view) { view->readSessionConfig(configGroup); } } } // avoid empty view space if (m_docToView.isEmpty()) { viewMan->createView(KateApp::self()->documentManager()->documentList().first(), this); } m_group = groupname; // used for restroing view configs later } // END KateViewSpace diff --git a/kate/kateviewspace.h b/kate/kateviewspace.h index 6514d6bbe..9471c48b0 100644 --- a/kate/kateviewspace.h +++ b/kate/kateviewspace.h @@ -1,215 +1,215 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ #ifndef KATE_VIEWSPACE_H #define KATE_VIEWSPACE_H -#include #include #include +#include #include #include class KConfigBase; class KateViewManager; class QStackedWidget; class QToolButton; class KateTabBar; class KateViewSpace : public QWidget { Q_OBJECT public: explicit KateViewSpace(KateViewManager *, QWidget *parent = nullptr, const char *name = nullptr); /** * Returns \e true, if this view space is currently the active view space. */ bool isActiveSpace(); /** * Depending on @p active, mark this view space as active or inactive. * Called from the view manager. */ void setActive(bool active); /** * Create new view for given document * @param doc document to create view for * @return new created view */ KTextEditor::View *createView(KTextEditor::Document *doc); void removeView(KTextEditor::View *v); bool showView(KTextEditor::View *view) { return showView(view->document()); } bool showView(KTextEditor::Document *document); // might be nullptr, if there is no view KTextEditor::View *currentView(); void saveConfig(KConfigBase *config, int myIndex, const QString &viewConfGrp); void restoreConfig(KateViewManager *viewMan, const KConfigBase *config, const QString &group); /** * Returns the document LRU list of this view space. */ QVector lruDocumentList() const; /** * Called by the view manager if a viewspace was closed. * The documents of the closed are merged into this viewspace */ void mergeLruList(const QVector &lruList); /** * Called by the view manager to notify that new documents were created * while this view space was active. If @p append is @e true, the @p doc * is appended to the lru document list, otherwise, it is prepended. */ void registerDocument(KTextEditor::Document *doc, bool append = true); /** * Event filter to catch events from view space tool buttons. */ bool eventFilter(QObject *obj, QEvent *event) override; /** * Focus the previous tab in the tabbar. */ void focusPrevTab(); /** * Focus the next tab in the tabbar. */ void focusNextTab(); public Q_SLOTS: void documentDestroyed(QObject *doc); void updateDocumentName(KTextEditor::Document *doc); void updateDocumentUrl(KTextEditor::Document *doc); void updateDocumentState(KTextEditor::Document *doc); private Q_SLOTS: void statusBarToggled(); void tabBarToggled(); void changeView(int buttonId); /** * Calls this slot to make this view space the currently active view space. * Making it active goes through the KateViewManager. * @param focusCurrentView if @e true, the current view will get focus */ void makeActive(bool focusCurrentView = true); /** * Add a tab for @p doc at position @p index. */ void insertTab(int index, KTextEditor::Document *doc); /** * Remove tab for @p doc, and return the index (position) * of the removed tab. */ int removeTab(KTextEditor::Document *doc, bool documentDestroyed); /** * Remove @p count tabs, since the tab bar shrunk. */ void removeTabs(int count); /** * Add @p count tabs, since the tab bar grew. */ void addTabs(int count); /** * This slot is called by the tabbar, if tab @p id was closed through the * context menu. */ void closeTabRequest(int id); /** * This slot is called when the context menu is requested for button * @p id at position @p globalPos. * @param id the button, or -1 if the context menu was requested on * at a place where no tab exists * @param globalPos the position of the context menu in global coordinates */ void showContextMenu(int id, const QPoint &globalPos); /** * Called to create a new empty document. */ void createNewDocument(); /** * Update the quick open button to reflect the currently hidden tabs. */ void updateQuickOpen(); private: /** * Returns the amount of documents in KateDocManager that currently * have no tab in this tab bar. */ int hiddenDocuments() const; private: // Kate's view manager KateViewManager *m_viewManager; // config group string, used for restoring View session configuration QString m_group; // flag that indicates whether this view space is the active one. // correct setter: m_viewManager->setActiveSpace(this); bool m_isActiveSpace; // widget stack that contains all KTE::Views QStackedWidget *stack; // document's in the view space, sorted in LRU mode: // the most recently used Document is at the end of the list QVector m_lruDocList; // the list of views that are contained in this view space, // mapped through a hash from Document to View. // note: the number of entries match stack->count(); QHash m_docToView; // tab bar that contains viewspace tabs KateTabBar *m_tabBar; // split action QToolButton *m_split; // quick open action QToolButton *m_quickOpen; // map from Document to button id QHash m_docToTabId; }; #endif diff --git a/kate/katewaiter.h b/kate/katewaiter.h index 8aa59d718..78c01e7cd 100644 --- a/kate/katewaiter.h +++ b/kate/katewaiter.h @@ -1,49 +1,49 @@ /* This file is part of the KDE libraries Copyright (C) 2005 Christoph Cullmann Copyright (C) 2002, 2003 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. */ #ifndef __KATE_WAITER_H__ #define __KATE_WAITER_H__ #include -#include #include #include #include +#include class KateWaiter : public QObject { Q_OBJECT public: KateWaiter(const QString &service, const QStringList &tokens); public Q_SLOTS: void exiting(); void documentClosed(const QString &token); void serviceOwnerChanged(const QString &, const QString &, const QString &); private: QStringList m_tokens; QDBusServiceWatcher m_watcher; }; #endif diff --git a/kate/main.cpp b/kate/main.cpp index 37e2df9b2..ef8015cf4 100644 --- a/kate/main.cpp +++ b/kate/main.cpp @@ -1,621 +1,621 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger 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. */ #include "config.h" #include "kateapp.h" #include "katerunninginstanceinfo.h" #include "katewaiter.h" #include #include +#include #include -#include +#include #include +#include #include -#include -#include +#include #include #include -#include -#include -#include #include #include #include -#include #include #include +#include +#include +#include #include "../urlinfo.h" #ifdef USE_QT_SINGLE_APP #include "qtsingleapplication/qtsingleapplication.h" #endif #ifndef Q_OS_WIN #include #endif #include int main(int argc, char **argv) { #ifndef Q_OS_WIN // Prohibit using sudo or kdesu (but allow using the root user directly) if (getuid() == 0) { if (!qEnvironmentVariableIsEmpty("SUDO_USER")) { std::cout << "Executing Kate with sudo is not possible due to unfixable security vulnerabilities." << std::endl; return EXIT_FAILURE; } else if (!qEnvironmentVariableIsEmpty("KDESU_USER")) { std::cout << "Executing Kate with kdesu is not possible due to unfixable security vulnerabilities." << std::endl; return EXIT_FAILURE; } } #endif /** * init resources from our static lib */ Q_INIT_RESOURCE(kate); /** * enable high dpi support */ QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); /** * Create application first */ #ifdef USE_QT_SINGLE_APP SharedTools::QtSingleApplication app(QStringLiteral("kate"), argc, argv); #else QApplication app(argc, argv); #endif /** * Enforce application name even if the executable is renamed */ app.setApplicationName(QStringLiteral("kate")); /** * Enable crash handling through KCrash. */ KCrash::initialize(); /** * Connect application with translation catalogs */ KLocalizedString::setApplicationDomain("kate"); /** * construct about data for Kate */ KAboutData aboutData( QStringLiteral("kate"), i18n("Kate"), QStringLiteral(KATE_VERSION), i18n("Kate - Advanced Text Editor"), KAboutLicense::LGPL_V2, i18n("(c) 2000-2019 The Kate Authors"), QString(), QStringLiteral("https://kate-editor.org")); /** * right dbus prefix == org.kde. */ aboutData.setOrganizationDomain("kde.org"); /** * desktop file association to make application icon work (e.g. in Wayland window decoration) */ aboutData.setDesktopFileName(QStringLiteral("org.kde.kate")); /** * authors & co. */ aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("https://cullmann.io")); aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org")); aboutData.addAuthor(i18n("Sven Brauch"), i18n("Developer"), QStringLiteral("mail@svenbrauch.de")); aboutData.addAuthor(i18n("KÃ¥re Särs"), i18n("Developer"), QStringLiteral("kare.sars@iki.fi")); aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("http://www.alweb.dk")); aboutData.addAuthor(i18n("Joseph Wenninger"), i18n("Core Developer"), QStringLiteral("jowenn@kde.org"), QStringLiteral("http://stud3.tuwien.ac.at/~e9925371")); aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org")); aboutData.addAuthor(i18n("Alexander Neundorf"), i18n("Developer"), QStringLiteral("neundorf@kde.org")); aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org")); aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org")); aboutData.addAuthor(i18n("Matt Newell"), i18n("Testing, ..."), QStringLiteral("newellm@proaxis.com")); aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at")); aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz")); aboutData.addAuthor(i18n("Jochen Wilhemly"), i18n("KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de")); aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org")); aboutData.addAuthor(i18n("Christian Gebauer"), QString(), QStringLiteral("gebauer@kde.org")); aboutData.addAuthor(i18n("Simon Hausmann"), QString(), QStringLiteral("hausmann@kde.org")); aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com")); aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net")); aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org")); aboutData.addAuthor(i18n("Pablo Martín"), i18n("Python Plugin Developer"), QStringLiteral("goinnn@gmail.com"), QStringLiteral("http://github.com/goinnn/")); aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"), i18n("QA and Scripting"), QStringLiteral("oss@senarclens.eu"), QStringLiteral("http://find-santa.eu/")); aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it")); aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu")); aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL")); aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite")); aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG")); aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX")); aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python")); aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python")); aboutData.addCredit(i18n("Daniel Naber")); aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme")); aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list")); aboutData.addCredit(i18n("Carsten Pfeiffer"), i18n("Very nice help")); aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention")); /** * set the new Kate mascot */ aboutData.setProgramLogo(QImage(QStringLiteral(":/kate/mascot.png"))); /** * set and register app about data */ KAboutData::setApplicationData(aboutData); /** * set the program icon */ QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("kate"), app.windowIcon())); /** * Create command line parser and feed it with known options */ QCommandLineParser parser; aboutData.setupCommandLine(&parser); // -s/--start session option const QCommandLineOption startSessionOption(QStringList() << QStringLiteral("s") << QStringLiteral("start"), i18n("Start Kate with a given session."), i18n("session")); parser.addOption(startSessionOption); // --startanon session option const QCommandLineOption startAnonymousSessionOption(QStringList() << QStringLiteral("startanon"), i18n("Start Kate with a new anonymous session, implies '-n'.")); parser.addOption(startAnonymousSessionOption); // -n/--new option const QCommandLineOption startNewInstanceOption( QStringList() << QStringLiteral("n") << QStringLiteral("new"), i18n("Force start of a new kate instance (is ignored if start is used and another kate instance already has the given session opened), forced if no parameters and no URLs are given at all.")); parser.addOption(startNewInstanceOption); // -b/--block option const QCommandLineOption startBlockingOption(QStringList() << QStringLiteral("b") << QStringLiteral("block"), i18n("If using an already running kate instance, block until it exits, if URLs given to open.")); parser.addOption(startBlockingOption); // -p/--pid option const QCommandLineOption usePidOption( QStringList() << QStringLiteral("p") << QStringLiteral("pid"), i18n("Only try to reuse kate instance with this pid (is ignored if start is used and another kate instance already has the given session opened)."), i18n("pid")); parser.addOption(usePidOption); // -e/--encoding option const QCommandLineOption useEncodingOption(QStringList() << QStringLiteral("e") << QStringLiteral("encoding"), i18n("Set encoding for the file to open."), i18n("encoding")); parser.addOption(useEncodingOption); // -l/--line option const QCommandLineOption gotoLineOption(QStringList() << QStringLiteral("l") << QStringLiteral("line"), i18n("Navigate to this line."), i18n("line")); parser.addOption(gotoLineOption); // -c/--column option const QCommandLineOption gotoColumnOption(QStringList() << QStringLiteral("c") << QStringLiteral("column"), i18n("Navigate to this column."), i18n("column")); parser.addOption(gotoColumnOption); // -i/--stdin option const QCommandLineOption readStdInOption(QStringList() << QStringLiteral("i") << QStringLiteral("stdin"), i18n("Read the contents of stdin.")); parser.addOption(readStdInOption); // --tempfile option const QCommandLineOption tempfileOption(QStringList() << QStringLiteral("tempfile"), i18n("The files/URLs opened by the application will be deleted after use")); parser.addOption(tempfileOption); // urls to open parser.addPositionalArgument(QStringLiteral("urls"), i18n("Documents to open."), i18n("[urls...]")); /** * do the command line parsing */ parser.process(app); /** * handle standard options */ aboutData.processCommandLine(&parser); /** * remember the urls we shall open */ const QStringList urls = parser.positionalArguments(); /** * compute if we shall start a new instance or reuse * an old one * this will later be updated once more after detecting some * things about already running kate's, like their sessions */ bool force_new = parser.isSet(startNewInstanceOption); if (!force_new) { if (!(parser.isSet(startSessionOption) || parser.isSet(startNewInstanceOption) || parser.isSet(usePidOption) || parser.isSet(useEncodingOption) || parser.isSet(gotoLineOption) || parser.isSet(gotoColumnOption) || parser.isSet(readStdInOption)) && (urls.isEmpty())) { force_new = true; } } /** * only block, if files to open there.... */ const bool needToBlock = parser.isSet(startBlockingOption) && !urls.isEmpty(); /** * use dbus, if available for linux and co. * allows for reuse of running Kate instances */ #ifndef USE_QT_SINGLE_APP if (QDBusConnectionInterface *const sessionBusInterface = QDBusConnection::sessionBus().interface()) { /** * try to get the current running kate instances */ KateRunningInstanceMap mapSessionRii; if (!fillinRunningKateAppInstances(&mapSessionRii)) { return 1; } QString currentActivity; QDBusMessage m = QDBusMessage::createMethodCall(QStringLiteral("org.kde.ActivityManager"), QStringLiteral("/ActivityManager/Activities"), QStringLiteral("org.kde.ActivityManager.Activities"), QStringLiteral("CurrentActivity")); QDBusMessage res = QDBusConnection::sessionBus().call(m); QList answer = res.arguments(); if (answer.size() == 1) { currentActivity = answer.at(0).toString(); } QStringList kateServices; for (KateRunningInstanceMap::const_iterator it = mapSessionRii.constBegin(); it != mapSessionRii.constEnd(); ++it) { QString serviceName = (*it)->serviceName; if (currentActivity.length() != 0) { QDBusMessage m = QDBusMessage::createMethodCall(serviceName, QStringLiteral("/MainApplication"), QStringLiteral("org.kde.Kate.Application"), QStringLiteral("isOnActivity")); QList dbargs; // convert to an url dbargs.append(currentActivity); m.setArguments(dbargs); QDBusMessage res = QDBusConnection::sessionBus().call(m); QList answer = res.arguments(); if (answer.size() == 1) { const bool canBeUsed = answer.at(0).toBool(); // If the Kate instance is in a specific activity, add it to // the list of candidate reusable services if (canBeUsed) { kateServices << serviceName; } } } else { kateServices << serviceName; } } QString serviceName; QString start_session; bool session_already_opened = false; // check if we try to start an already opened session if (parser.isSet(startAnonymousSessionOption)) { force_new = true; } else if (parser.isSet(startSessionOption)) { start_session = parser.value(startSessionOption); if (mapSessionRii.contains(start_session)) { serviceName = mapSessionRii[start_session]->serviceName; force_new = false; session_already_opened = true; } } // cleanup map cleanupRunningKateAppInstanceMap(&mapSessionRii); // if no new instance is forced and no already opened session is requested, // check if a pid is given, which should be reused. // two possibilities: pid given or not... if ((!force_new) && serviceName.isEmpty()) { if ((parser.isSet(usePidOption)) || (!qgetenv("KATE_PID").isEmpty())) { QString usePid = (parser.isSet(usePidOption)) ? parser.value(usePidOption) : QString::fromLocal8Bit(qgetenv("KATE_PID")); serviceName = QLatin1String("org.kde.kate-") + usePid; if (!kateServices.contains(serviceName)) { serviceName.clear(); } } } // prefer the Kate instance running on the current virtual desktop bool foundRunningService = false; if ((!force_new) && (serviceName.isEmpty())) { const int desktopnumber = KWindowSystem::currentDesktop(); for (int s = 0; s < kateServices.count(); s++) { serviceName = kateServices[s]; if (!serviceName.isEmpty()) { QDBusReply there = sessionBusInterface->isServiceRegistered(serviceName); if (there.isValid() && there.value()) { // query instance current desktop QDBusMessage m = QDBusMessage::createMethodCall(serviceName, QStringLiteral("/MainApplication"), QStringLiteral("org.kde.Kate.Application"), QStringLiteral("desktopNumber")); QDBusMessage res = QDBusConnection::sessionBus().call(m); QList answer = res.arguments(); if (answer.size() == 1) { // special case: on all desktops! that is -1 aka NET::OnAllDesktops, see KWindowInfo::desktop() docs const int sessionDesktopNumber = answer.at(0).toInt(); if (sessionDesktopNumber == desktopnumber || sessionDesktopNumber == NET::OnAllDesktops) { // stop searching. a candidate instance in the current desktop has been found foundRunningService = true; break; } } } } serviceName.clear(); } } // check again if service is still running foundRunningService = false; if (!serviceName.isEmpty()) { QDBusReply there = sessionBusInterface->isServiceRegistered(serviceName); foundRunningService = there.isValid() && there.value(); } if (foundRunningService) { // open given session if (parser.isSet(startSessionOption) && (!session_already_opened)) { QDBusMessage m = QDBusMessage::createMethodCall(serviceName, QStringLiteral("/MainApplication"), QStringLiteral("org.kde.Kate.Application"), QStringLiteral("activateSession")); QList dbusargs; dbusargs.append(parser.value(startSessionOption)); m.setArguments(dbusargs); QDBusConnection::sessionBus().call(m); } QString enc = parser.isSet(useEncodingOption) ? parser.value(useEncodingOption) : QString(); bool tempfileSet = parser.isSet(tempfileOption); QStringList tokens; // open given files... // Bug 397913: Reverse the order here so the new tabs are opened in same order as the files were passed in on the command line for (int i = urls.size() - 1; i >= 0; --i) { const QString &url = urls[i]; QDBusMessage m = QDBusMessage::createMethodCall(serviceName, QStringLiteral("/MainApplication"), QStringLiteral("org.kde.Kate.Application"), QStringLiteral("tokenOpenUrlAt")); UrlInfo info(url); QList dbusargs; // convert to an url dbusargs.append(info.url.toString()); dbusargs.append(info.cursor.line()); dbusargs.append(info.cursor.column()); dbusargs.append(enc); dbusargs.append(tempfileSet); m.setArguments(dbusargs); QDBusMessage res = QDBusConnection::sessionBus().call(m); if (res.type() == QDBusMessage::ReplyMessage) { if (res.arguments().count() == 1) { QVariant v = res.arguments()[0]; if (v.isValid()) { QString s = v.toString(); if ((!s.isEmpty()) && (s != QLatin1String("ERROR"))) { tokens << s; } } } } } if (parser.isSet(readStdInOption)) { QTextStream input(stdin, QIODevice::ReadOnly); // set chosen codec QTextCodec *codec = parser.isSet(useEncodingOption) ? QTextCodec::codecForName(parser.value(useEncodingOption).toUtf8()) : nullptr; if (codec) { input.setCodec(codec); } QString line; QString text; do { line = input.readLine(); text.append(line + QLatin1Char('\n')); } while (!line.isNull()); QDBusMessage m = QDBusMessage::createMethodCall(serviceName, QStringLiteral("/MainApplication"), QStringLiteral("org.kde.Kate.Application"), QStringLiteral("openInput")); QList dbusargs; dbusargs.append(text); dbusargs.append(codec ? QString::fromLatin1(codec->name()) : QString()); m.setArguments(dbusargs); QDBusConnection::sessionBus().call(m); } int line = 0; int column = 0; bool nav = false; if (parser.isSet(gotoLineOption)) { line = parser.value(gotoLineOption).toInt() - 1; nav = true; } if (parser.isSet(gotoColumnOption)) { column = parser.value(gotoColumnOption).toInt() - 1; nav = true; } if (nav) { QDBusMessage m = QDBusMessage::createMethodCall(serviceName, QStringLiteral("/MainApplication"), QStringLiteral("org.kde.Kate.Application"), QStringLiteral("setCursor")); QList args; args.append(line); args.append(column); m.setArguments(args); QDBusConnection::sessionBus().call(m); } // activate the used instance QDBusMessage activateMsg = QDBusMessage::createMethodCall(serviceName, QStringLiteral("/MainApplication"), QStringLiteral("org.kde.Kate.Application"), QStringLiteral("activate")); QDBusConnection::sessionBus().call(activateMsg); // connect dbus signal if (needToBlock) { KateWaiter *waiter = new KateWaiter(serviceName, tokens); QDBusConnection::sessionBus().connect(serviceName, QStringLiteral("/MainApplication"), QStringLiteral("org.kde.Kate.Application"), QStringLiteral("exiting"), waiter, SLOT(exiting())); QDBusConnection::sessionBus().connect(serviceName, QStringLiteral("/MainApplication"), QStringLiteral("org.kde.Kate.Application"), QStringLiteral("documentClosed"), waiter, SLOT(documentClosed(QString))); } // KToolInvocation (and KRun) will wait until we register on dbus KDBusService dbusService(KDBusService::Multiple); dbusService.unregister(); // make the world happy, we are started, kind of... KStartupInfo::appStarted(); // We don't want the session manager to restart us on next login // if we block if (needToBlock) { QObject::connect( qApp, &QGuiApplication::saveStateRequest, qApp, [](QSessionManager &session) { session.setRestartHint(QSessionManager::RestartNever); }, Qt::DirectConnection); } // this will wait until exiting is emitted by the used instance, if wanted... return needToBlock ? app.exec() : 0; } } /** * for mac & windows: use QtSingleApplication */ #else /** * only try to reuse existing kate instances if not already forbidden by arguments */ if (!force_new) { /** * any instance running we can use? * later we could do here pid checks and stuff */ bool instanceFound = app.isRunning(); /** * if instance was found, send over all urls to be opened */ if (instanceFound) { /** * tell single application to block if needed */ app.setBlock(needToBlock); /** * construct one big message with all urls to open * later we will add additional data to this */ QVariantMap message; QVariantList messageUrls; for (const QString &url : urls) { /** * get url info and pack them into the message as extra element in urls list */ UrlInfo info(url); QVariantMap urlMessagePart; urlMessagePart[QLatin1String("url")] = info.url; urlMessagePart[QLatin1String("line")] = info.cursor.line(); urlMessagePart[QLatin1String("column")] = info.cursor.column(); messageUrls.append(urlMessagePart); } message[QLatin1String("urls")] = messageUrls; /** * try to send message, return success */ return !app.sendMessage(QString::fromUtf8(QJsonDocument::fromVariant(QVariant(message)).toJson())); } } #endif // USE_QT_SINGLE_APP /** * if we arrive here, we need to start a new kate instance! */ /** * set some KTextEditor defaults */ { KConfigGroup viewConfig(KSharedConfig::openConfig(), QStringLiteral("KTextEditor View")); if (!viewConfig.exists()) { viewConfig.writeEntry("Line Modification", true); viewConfig.writeEntry("Line Numbers", true); } } /** * construct the real kate app object ;) * behaves like a singleton, one unique instance * we are passing our local command line parser to it */ KateApp kateApp(parser); /** * init kate * if this returns false, we shall exit * else we may enter the main event loop */ if (!kateApp.init()) { return 0; } #ifndef USE_QT_SINGLE_APP /** * finally register this kate instance for dbus, don't die if no dbus is around! */ const KDBusService dbusService(KDBusService::Multiple | KDBusService::NoExitOnFailure); #else /** * else: connect the single application notifications */ QObject::connect(&app, &SharedTools::QtSingleApplication::messageReceived, &kateApp, &KateApp::remoteMessageReceived); KateMainWindow *win = kateApp.activeKateMainWindow(); app.setActivationWindow(win, true); #endif /** * start main event loop for our application */ return app.exec(); } diff --git a/kate/qtsingleapplication/qtlocalpeer.h b/kate/qtsingleapplication/qtlocalpeer.h index 50277c843..7bb1ba28b 100644 --- a/kate/qtsingleapplication/qtlocalpeer.h +++ b/kate/qtsingleapplication/qtlocalpeer.h @@ -1,63 +1,63 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #pragma once #include +#include #include #include -#include namespace SharedTools { class QtLocalPeer : public QObject { Q_OBJECT public: explicit QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); bool isClient(); bool sendMessage(const QString &message, int timeout, bool block); QString applicationId() const { return id; } static QString appSessionId(const QString &appId); Q_SIGNALS: void messageReceived(const QString &message, QObject *socket); protected Q_SLOTS: void receiveConnection(); protected: QString id; QString socketName; QLocalServer *server; QtLockedFile lockFile; }; } // namespace SharedTools diff --git a/kate/qtsingleapplication/qtlockedfile_unix.cpp b/kate/qtsingleapplication/qtlockedfile_unix.cpp index b1a9180f4..a8914066a 100644 --- a/kate/qtsingleapplication/qtlockedfile_unix.cpp +++ b/kate/qtsingleapplication/qtlockedfile_unix.cpp @@ -1,102 +1,102 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qtlockedfile.h" -#include #include -#include #include +#include +#include namespace SharedTools { bool QtLockedFile::lock(LockMode mode, bool block) { if (!isOpen()) { qWarning("QtLockedFile::lock(): file is not opened"); return false; } if (mode == NoLock) return unlock(); if (mode == m_lock_mode) return true; if (m_lock_mode != NoLock) unlock(); struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; int cmd = block ? F_SETLKW : F_SETLK; int ret = fcntl(handle(), cmd, &fl); if (ret == -1) { if (errno != EINTR && errno != EAGAIN) qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); return false; } m_lock_mode = mode; return true; } bool QtLockedFile::unlock() { if (!isOpen()) { qWarning("QtLockedFile::unlock(): file is not opened"); return false; } if (!isLocked()) return true; struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_type = F_UNLCK; int ret = fcntl(handle(), F_SETLKW, &fl); if (ret == -1) { qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); return false; } m_lock_mode = NoLock; remove(); return true; } QtLockedFile::~QtLockedFile() { if (isOpen()) unlock(); } } // namespace SharedTools diff --git a/kate/qtsingleapplication/qtlockedfile_win.cpp b/kate/qtsingleapplication/qtlockedfile_win.cpp index aa183abbd..75b9041bd 100644 --- a/kate/qtsingleapplication/qtlockedfile_win.cpp +++ b/kate/qtsingleapplication/qtlockedfile_win.cpp @@ -1,179 +1,179 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qtlockedfile.h" -#include #include +#include namespace SharedTools { #define SEMAPHORE_PREFIX "QtLockedFile semaphore " #define MUTEX_PREFIX "QtLockedFile mutex " #define SEMAPHORE_MAX 100 static QString errorCodeToString(DWORD errorCode) { QString result; char *data = 0; FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, errorCode, 0, (char *)&data, 0, 0); result = QString::fromLocal8Bit(data); if (data != 0) LocalFree(data); if (result.endsWith(QLatin1Char('\n'))) result.chop(1); return result; } bool QtLockedFile::lock(LockMode mode, bool block) { if (!isOpen()) { qWarning("QtLockedFile::lock(): file is not opened"); return false; } if (mode == m_lock_mode) return true; if (m_lock_mode != 0) unlock(); if (m_semaphore_hnd == 0) { QFileInfo fi(*this); QString sem_name = QString::fromLatin1(SEMAPHORE_PREFIX) + fi.absoluteFilePath().toLower(); m_semaphore_hnd = CreateSemaphoreW(0, SEMAPHORE_MAX, SEMAPHORE_MAX, (TCHAR *)sem_name.utf16()); if (m_semaphore_hnd == 0) { qWarning("QtLockedFile::lock(): CreateSemaphore: %s", errorCodeToString(GetLastError()).toLatin1().constData()); return false; } } bool gotMutex = false; int decrement; if (mode == ReadLock) { decrement = 1; } else { decrement = SEMAPHORE_MAX; if (m_mutex_hnd == 0) { QFileInfo fi(*this); QString mut_name = QString::fromLatin1(MUTEX_PREFIX) + fi.absoluteFilePath().toLower(); m_mutex_hnd = CreateMutexW(NULL, FALSE, (TCHAR *)mut_name.utf16()); if (m_mutex_hnd == 0) { qWarning("QtLockedFile::lock(): CreateMutex: %s", errorCodeToString(GetLastError()).toLatin1().constData()); return false; } } DWORD res = WaitForSingleObject(m_mutex_hnd, block ? INFINITE : 0); if (res == WAIT_TIMEOUT) return false; if (res == WAIT_FAILED) { qWarning("QtLockedFile::lock(): WaitForSingleObject (mutex): %s", errorCodeToString(GetLastError()).toLatin1().constData()); return false; } gotMutex = true; } for (int i = 0; i < decrement; ++i) { DWORD res = WaitForSingleObject(m_semaphore_hnd, block ? INFINITE : 0); if (res == WAIT_TIMEOUT) { if (i) { // A failed nonblocking rw locking. Undo changes to semaphore. if (ReleaseSemaphore(m_semaphore_hnd, i, NULL) == 0) { qWarning("QtLockedFile::unlock(): ReleaseSemaphore: %s", errorCodeToString(GetLastError()).toLatin1().constData()); // Fall through } } if (gotMutex) ReleaseMutex(m_mutex_hnd); return false; } if (res != WAIT_OBJECT_0) { if (gotMutex) ReleaseMutex(m_mutex_hnd); qWarning("QtLockedFile::lock(): WaitForSingleObject (semaphore): %s", errorCodeToString(GetLastError()).toLatin1().constData()); return false; } } m_lock_mode = mode; if (gotMutex) ReleaseMutex(m_mutex_hnd); return true; } bool QtLockedFile::unlock() { if (!isOpen()) { qWarning("QtLockedFile::unlock(): file is not opened"); return false; } if (!isLocked()) return true; int increment; if (m_lock_mode == ReadLock) increment = 1; else increment = SEMAPHORE_MAX; DWORD ret = ReleaseSemaphore(m_semaphore_hnd, increment, 0); if (ret == 0) { qWarning("QtLockedFile::unlock(): ReleaseSemaphore: %s", errorCodeToString(GetLastError()).toLatin1().constData()); return false; } m_lock_mode = QtLockedFile::NoLock; remove(); return true; } QtLockedFile::~QtLockedFile() { if (isOpen()) unlock(); if (m_mutex_hnd != 0) { DWORD ret = CloseHandle(m_mutex_hnd); if (ret == 0) { qWarning("QtLockedFile::~QtLockedFile(): CloseHandle (mutex): %s", errorCodeToString(GetLastError()).toLatin1().constData()); } m_mutex_hnd = 0; } if (m_semaphore_hnd != 0) { DWORD ret = CloseHandle(m_semaphore_hnd); if (ret == 0) { qWarning("QtLockedFile::~QtLockedFile(): CloseHandle (semaphore): %s", errorCodeToString(GetLastError()).toLatin1().constData()); } m_semaphore_hnd = 0; } } } // namespace SharedTools diff --git a/kate/session/katesession.cpp b/kate/session/katesession.cpp index 91d277880..343544197 100644 --- a/kate/session/katesession.cpp +++ b/kate/session/katesession.cpp @@ -1,129 +1,129 @@ /* This file is part of the KDE project * * Copyright (C) 2005 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "katesession.h" -#include "katesessionmanager.h" #include "katedebug.h" +#include "katesessionmanager.h" #include #include #include #include #include static const QLatin1String opGroupName("Open Documents"); static const QLatin1String keyCount("Count"); KateSession::KateSession(const QString &file, const QString &name, const bool anonymous, const KConfig *_config) : m_name(name) , m_file(file) , m_anonymous(anonymous) , m_documents(0) , m_config(nullptr) , m_timestamp() { Q_ASSERT(!m_file.isEmpty()); if (_config) { // copy data from config instead m_config = _config->copyTo(m_file); } else if (!QFile::exists(m_file)) { // given file exists, use it to load some stuff qCDebug(LOG_KATE) << "Warning, session file not found: " << m_file; return; } m_timestamp = QFileInfo(m_file).lastModified(); // get the document count m_documents = config()->group(opGroupName).readEntry(keyCount, 0); } KateSession::~KateSession() { delete m_config; } const QString &KateSession::file() const { return m_file; } void KateSession::setDocuments(const unsigned int number) { config()->group(opGroupName).writeEntry(keyCount, number); m_documents = number; } void KateSession::setFile(const QString &filename) { if (m_config) { KConfig *cfg = m_config->copyTo(filename); delete m_config; m_config = cfg; } m_file = filename; } void KateSession::setName(const QString &name) { m_name = name; } KConfig *KateSession::config() { if (m_config) { return m_config; } // reread documents number? return m_config = new KConfig(m_file, KConfig::SimpleConfig); } KateSession::Ptr KateSession::create(const QString &file, const QString &name) { return Ptr(new KateSession(file, name, false)); } KateSession::Ptr KateSession::createFrom(const KateSession::Ptr &session, const QString &file, const QString &name) { return Ptr(new KateSession(file, name, false, session->config())); } KateSession::Ptr KateSession::createAnonymous(const QString &file) { return Ptr(new KateSession(file, QString(), true)); } KateSession::Ptr KateSession::createAnonymousFrom(const KateSession::Ptr &session, const QString &file) { return Ptr(new KateSession(file, QString(), true, session->config())); } bool KateSession::compareByName(const KateSession::Ptr &s1, const KateSession::Ptr &s2) { return QCollator().compare(s1->name(), s2->name()) == -1; } bool KateSession::compareByTimeDesc(const KateSession::Ptr &s1, const KateSession::Ptr &s2) { return s1->timestamp() > s2->timestamp(); } diff --git a/kate/session/katesession.h b/kate/session/katesession.h index 5ed603f48..fc205dc6f 100644 --- a/kate/session/katesession.h +++ b/kate/session/katesession.h @@ -1,140 +1,140 @@ /* This file is part of the KDE project * * Copyright (C) 2005 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef __KATE_SESSION_H__ #define __KATE_SESSION_H__ #include "katetests_export.h" +#include #include #include -#include class KConfig; class KATE_TESTS_EXPORT KateSession : public QSharedData { public: /** * Define a Shared-Pointer type */ typedef QExplicitlySharedDataPointer Ptr; public: ~KateSession(); /** * session name * @return name for this session */ const QString &name() const { return m_name; } /** * session config * on first access, will create the config object, delete will be done automagic * return 0 if we have no file to read config from atm * @return correct KConfig, never null * @note never delete configRead(), because the return value might be * KSharedConfig::openConfig(). Only delete the member variables directly. */ KConfig *config(); /** * count of documents in this session * @return documents count */ unsigned int documents() const { return m_documents; } /** * update \p number of opened documents in session */ void setDocuments(const unsigned int number); /** * @return true if this is anonymous/new session */ bool isAnonymous() const { return m_anonymous; } /** * @return path to session file */ const QString &file() const; /** * returns last save time of this session */ const QDateTime ×tamp() const { return m_timestamp; } /** * Factories */ public: static KateSession::Ptr create(const QString &file, const QString &name); static KateSession::Ptr createFrom(const KateSession::Ptr &session, const QString &file, const QString &name); static KateSession::Ptr createAnonymous(const QString &file); static KateSession::Ptr createAnonymousFrom(const KateSession::Ptr &session, const QString &file); static bool compareByName(const KateSession::Ptr &s1, const KateSession::Ptr &s2); static bool compareByTimeDesc(const KateSession::Ptr &s1, const KateSession::Ptr &s2); private: friend class KateSessionManager; friend class KateSessionTest; /** * set session name */ void setName(const QString &name); /** * set's new session file to @filename */ void setFile(const QString &filename); /** * create a session from given @file * @param file configuration file * @param name name of this session * @param anonymous anonymous flag * @param config if specified, the session will copy configuration from the KConfig instead of opening the file */ KateSession(const QString &file, const QString &name, const bool anonymous, const KConfig *config = nullptr); private: QString m_name; QString m_file; bool m_anonymous; unsigned int m_documents; KConfig *m_config; QDateTime m_timestamp; }; #endif diff --git a/kate/session/katesessionmanager.cpp b/kate/session/katesessionmanager.cpp index b4c55f873..f73a366ed 100644 --- a/kate/session/katesessionmanager.cpp +++ b/kate/session/katesessionmanager.cpp @@ -1,646 +1,646 @@ /* This file is part of the KDE project * * Copyright (C) 2005 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "katesessionmanager.h" #include "katesessionmanagedialog.h" #include "kateapp.h" #include "katepluginmanager.h" #include "katerunninginstanceinfo.h" #include -#include -#include -#include -#include #include #include +#include +#include +#include #include +#include #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include #endif // BEGIN KateSessionManager KateSessionManager::KateSessionManager(QObject *parent, const QString &sessionsDir) : QObject(parent) { if (sessionsDir.isEmpty()) { m_sessionsDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kate/sessions"); } else { m_sessionsDir = sessionsDir; } // create dir if needed QDir().mkpath(m_sessionsDir); m_dirWatch = new KDirWatch(this); m_dirWatch->addDir(m_sessionsDir); connect(m_dirWatch, &KDirWatch::dirty, this, &KateSessionManager::updateSessionList); updateSessionList(); } KateSessionManager::~KateSessionManager() { delete m_dirWatch; } void KateSessionManager::updateSessionList() { QStringList list; // Let's get a list of all session we have atm QDir dir(m_sessionsDir, QStringLiteral("*.katesession"), QDir::Time); for (unsigned int i = 0; i < dir.count(); ++i) { QString name = dir[i]; name.chop(12); // .katesession list << QUrl::fromPercentEncoding(name.toLatin1()); } // write jump list actions to disk in the kate.desktop file updateJumpListActions(list); bool changed = false; // Add new sessions to our list for (const QString &session : qAsConst(list)) { if (!m_sessions.contains(session)) { const QString file = sessionFileForName(session); m_sessions.insert(session, KateSession::create(file, session)); changed = true; } } // Remove gone sessions from our list for (const QString &session : m_sessions.keys()) { if ((list.indexOf(session) < 0) && (m_sessions.value(session) != activeSession())) { m_sessions.remove(session); changed = true; } } if (changed) { emit sessionListChanged(); } } bool KateSessionManager::activateSession(KateSession::Ptr session, const bool closeAndSaveLast, const bool loadNew) { if (activeSession() == session) { return true; } if (!session->isAnonymous()) { // check if the requested session is already open in another instance KateRunningInstanceMap instances; if (!fillinRunningKateAppInstances(&instances)) { KMessageBox::error(nullptr, i18n("Internal error: there is more than one instance open for a given session.")); return false; } if (instances.contains(session->name())) { if (KMessageBox::questionYesNo(nullptr, i18n("Session '%1' is already opened in another kate instance, change there instead of reopening?", session->name()), QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("katesessionmanager_switch_instance")) == KMessageBox::Yes) { instances[session->name()]->dbus_if->call(QStringLiteral("activate")); cleanupRunningKateAppInstanceMap(&instances); return false; } } cleanupRunningKateAppInstanceMap(&instances); } // try to close and save last session if (closeAndSaveLast) { if (KateApp::self()->activeKateMainWindow()) { if (!KateApp::self()->activeKateMainWindow()->queryClose_internal()) { return true; } } // save last session or not? saveActiveSession(); // really close last KateApp::self()->documentManager()->closeAllDocuments(); } // set the new session m_activeSession = session; // there is one case in which we don't want the restoration and that is // when restoring session from session manager. // In that case the restore is handled by the caller if (loadNew) { loadSession(session); } emit sessionChanged(); return true; } void KateSessionManager::loadSession(const KateSession::Ptr &session) const { // open the new session KSharedConfigPtr sharedConfig = KSharedConfig::openConfig(); KConfig *sc = session->config(); const bool loadDocs = !session->isAnonymous(); // do not load docs for new sessions // if we have no session config object, try to load the default // (anonymous/unnamed sessions) // load plugin config + plugins KateApp::self()->pluginManager()->loadConfig(sc); if (loadDocs) { KateApp::self()->documentManager()->restoreDocumentList(sc); } // window config KConfigGroup c(sharedConfig, "General"); KConfig *cfg = sc; bool delete_cfg = false; // a new, named session, read settings of the default session. if (!sc->hasGroup("Open MainWindows")) { delete_cfg = true; cfg = new KConfig(anonymousSessionFile(), KConfig::SimpleConfig); } if (c.readEntry("Restore Window Configuration", true)) { int wCount = cfg->group("Open MainWindows").readEntry("Count", 1); for (int i = 0; i < wCount; ++i) { if (i >= KateApp::self()->mainWindowsCount()) { KateApp::self()->newMainWindow(cfg, QStringLiteral("MainWindow%1").arg(i)); } else { KateApp::self()->mainWindow(i)->readProperties(KConfigGroup(cfg, QStringLiteral("MainWindow%1").arg(i))); } KateApp::self()->mainWindow(i)->restoreWindowConfig(KConfigGroup(cfg, QStringLiteral("MainWindow%1 Settings").arg(i))); } // remove mainwindows we need no longer... if (wCount > 0) { while (wCount < KateApp::self()->mainWindowsCount()) { delete KateApp::self()->mainWindow(KateApp::self()->mainWindowsCount() - 1); } } } else { // load recent files for all existing windows, see bug 408499 for (int i = 0; i < KateApp::self()->mainWindowsCount(); ++i) { KateApp::self()->mainWindow(i)->loadOpenRecent(cfg); } } // ensure we have at least one window, always! load recent files for it, too, see bug 408499 if (KateApp::self()->mainWindowsCount() == 0) { auto w = KateApp::self()->newMainWindow(); w->loadOpenRecent(cfg); } if (delete_cfg) { delete cfg; } // we shall always have some existing windows here! Q_ASSERT(KateApp::self()->mainWindowsCount() > 0); } bool KateSessionManager::activateSession(const QString &name, const bool closeAndSaveLast, const bool loadNew) { return activateSession(giveSession(name), closeAndSaveLast, loadNew); } bool KateSessionManager::activateAnonymousSession() { return activateSession(QString(), false); } KateSession::Ptr KateSessionManager::giveSession(const QString &name) { if (name.isEmpty()) { return KateSession::createAnonymous(anonymousSessionFile()); } if (m_sessions.contains(name)) { return m_sessions.value(name); } KateSession::Ptr s = KateSession::create(sessionFileForName(name), name); saveSessionTo(s->config()); m_sessions[name] = s; // Due to this add to m_sessions will updateSessionList() no signal emit, // but it's importand to add. Otherwise could it be happen that m_activeSession // is not part of m_sessions but a double emit sessionListChanged(); return s; } bool KateSessionManager::deleteSession(KateSession::Ptr session) { if (sessionIsActive(session->name())) { return false; } QFile::remove(session->file()); m_sessions.remove(session->name()); // Due to this remove from m_sessions will updateSessionList() no signal emit, // but this way is there no delay between deletion and information emit sessionListChanged(); return true; } QString KateSessionManager::copySession(const KateSession::Ptr &session, const QString &newName) { const QString name = askForNewSessionName(session, newName); if (name.isEmpty()) { return name; } const QString newFile = sessionFileForName(name); KateSession::Ptr ns = KateSession::createFrom(session, newFile, name); ns->config()->sync(); return name; } QString KateSessionManager::renameSession(KateSession::Ptr session, const QString &newName) { const QString name = askForNewSessionName(session, newName); if (name.isEmpty()) { return name; } const QString newFile = sessionFileForName(name); session->config()->sync(); const QUrl srcUrl = QUrl::fromLocalFile(session->file()); const QUrl dstUrl = QUrl::fromLocalFile(newFile); KIO::CopyJob *job = KIO::move(srcUrl, dstUrl, KIO::HideProgressInfo); if (!job->exec()) { KMessageBox::sorry(QApplication::activeWindow(), i18n("The session could not be renamed to \"%1\". Failed to write to \"%2\"", newName, newFile), i18n("Session Renaming")); return QString(); } m_sessions[newName] = m_sessions.take(session->name()); session->setName(newName); session->setFile(newFile); session->config()->sync(); // updateSessionList() will this edit not notice, so force signal emit sessionListChanged(); if (session == activeSession()) { emit sessionChanged(); } return name; } void KateSessionManager::saveSessionTo(KConfig *sc) const { // Clear the session file to avoid to accumulate outdated entries for (const auto &group : sc->groupList()) { sc->deleteGroup(group); } // save plugin configs and which plugins to load KateApp::self()->pluginManager()->writeConfig(sc); // save document configs + which documents to load KateApp::self()->documentManager()->saveDocumentList(sc); sc->group("Open MainWindows").writeEntry("Count", KateApp::self()->mainWindowsCount()); // save config for all windows around ;) bool saveWindowConfig = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("Restore Window Configuration", true); for (int i = 0; i < KateApp::self()->mainWindowsCount(); ++i) { KConfigGroup cg(sc, QStringLiteral("MainWindow%1").arg(i)); // saveProperties() handles saving the "open recent" files list KateApp::self()->mainWindow(i)->saveProperties(cg); if (saveWindowConfig) { KateApp::self()->mainWindow(i)->saveWindowConfig(KConfigGroup(sc, QStringLiteral("MainWindow%1 Settings").arg(i))); } } sc->sync(); /** * try to sync file to disk */ QFile fileToSync(sc->name()); if (fileToSync.open(QIODevice::ReadOnly)) { #ifndef Q_OS_WIN // ensure that the file is written to disk #ifdef HAVE_FDATASYNC fdatasync(fileToSync.handle()); #else fsync(fileToSync.handle()); #endif #endif } } bool KateSessionManager::saveActiveSession(bool rememberAsLast) { if (!activeSession()) { return false; } KConfig *sc = activeSession()->config(); saveSessionTo(sc); if (rememberAsLast && !activeSession()->isAnonymous()) { KSharedConfigPtr c = KSharedConfig::openConfig(); c->group("General").writeEntry("Last Session", activeSession()->name()); c->sync(); } return true; } bool KateSessionManager::chooseSession() { const KConfigGroup c(KSharedConfig::openConfig(), "General"); // get last used session, default to default session const QString lastSession(c.readEntry("Last Session", QString())); const QString sesStart(c.readEntry("Startup Session", "manual")); // uhh, just open last used session, show no chooser if (sesStart == QLatin1String("last")) { return activateSession(lastSession, false); } // start with empty new session or in case no sessions exist if (sesStart == QLatin1String("new") || sessionList().empty()) { return activateAnonymousSession(); } // else: ask the user return QScopedPointer(new KateSessionManageDialog(nullptr, lastSession))->exec(); } void KateSessionManager::sessionNew() { activateSession(giveSession(QString())); } void KateSessionManager::sessionSave() { if (activeSession() && activeSession()->isAnonymous()) { sessionSaveAs(); } else { saveActiveSession(); } } void KateSessionManager::sessionSaveAs() { const QString newName = askForNewSessionName(activeSession()); if (newName.isEmpty()) { return; } activeSession()->config()->sync(); KateSession::Ptr ns = KateSession::createFrom(activeSession(), sessionFileForName(newName), newName); m_activeSession = ns; saveActiveSession(); emit sessionChanged(); } QString KateSessionManager::askForNewSessionName(KateSession::Ptr session, const QString &newName) { if (session->name() == newName && !session->isAnonymous()) { return QString(); } const QString messagePrompt = i18n("Session name:"); const KLocalizedString messageExist = ki18n( "There is already an existing session with your chosen name: %1\n" "Please choose a different one."); const QString messageEmpty = i18n("To save a session, you must specify a name."); QString messageTotal = messagePrompt; QString name = newName; while (true) { QString preset = name; if (name.isEmpty()) { preset = suggestNewSessionName(session->name()); messageTotal = messageEmpty + QLatin1String("\n\n") + messagePrompt; } else if (QFile::exists(sessionFileForName(name))) { preset = suggestNewSessionName(name); if (preset.isEmpty()) { // Very unlikely, but as fall back we keep users input preset = name; } messageTotal = messageExist.subs(name).toString() + QLatin1String("\n\n") + messagePrompt; } else { return name; } QInputDialog dlg(KateApp::self()->activeKateMainWindow()); dlg.setInputMode(QInputDialog::TextInput); if (session->isAnonymous()) { dlg.setWindowTitle(i18n("Specify a name for this session")); } else { dlg.setWindowTitle(i18n("Specify a new name for session: %1", session->name())); } dlg.setLabelText(messageTotal); dlg.setTextValue(preset); dlg.resize(900, 100); // FIXME Calc somehow a proper size bool ok = dlg.exec(); name = dlg.textValue(); if (!ok) { return QString(); } } } QString KateSessionManager::suggestNewSessionName(const QString &target) { if (target.isEmpty()) { // Here could also a default name set or the current session name used return QString(); } const QString mask = QStringLiteral("%1 (%2)"); QString name; for (int i = 2; i < 1000000; i++) { // Should be enough to get an unique name name = mask.arg(target).arg(i); if (!QFile::exists(sessionFileForName(name))) { return name; } } return QString(); } void KateSessionManager::sessionManage() { QScopedPointer(new KateSessionManageDialog(KateApp::self()->activeKateMainWindow()))->exec(); } bool KateSessionManager::sessionIsActive(const QString &session) { // Try to avoid unneed action if (activeSession() && activeSession()->name() == session) { return true; } QDBusConnectionInterface *i = QDBusConnection::sessionBus().interface(); if (!i) { return false; } // look up all running kate instances and there sessions QDBusReply servicesReply = i->registeredServiceNames(); QStringList services; if (servicesReply.isValid()) { services = servicesReply.value(); } for (const QString &s : qAsConst(services)) { if (!s.startsWith(QLatin1String("org.kde.kate-"))) { continue; } KateRunningInstanceInfo rii(s); if (rii.valid && rii.sessionName == session) { return true; } } return false; } QString KateSessionManager::anonymousSessionFile() const { const QString file = m_sessionsDir + QStringLiteral("/../anonymous.katesession"); return QDir().cleanPath(file); } QString KateSessionManager::sessionFileForName(const QString &name) const { Q_ASSERT(!name.isEmpty()); const QString sname = QString::fromLatin1(QUrl::toPercentEncoding(name, QByteArray(), QByteArray("."))); return m_sessionsDir + QStringLiteral("/") + sname + QStringLiteral(".katesession"); } KateSessionList KateSessionManager::sessionList() { return m_sessions.values(); } void KateSessionManager::updateJumpListActions(const QStringList &sessionList) { #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) KService::Ptr service = KService::serviceByStorageId(qApp->desktopFileName()); if (!service) { return; } QScopedPointer df(new KDesktopFile(service->entryPath())); QStringList newActions = df->readActions(); // try to keep existing custom actions intact, only remove our "Session" actions and add them back later newActions.erase(std::remove_if(newActions.begin(), newActions.end(), [](const QString &action) { return action.startsWith(QLatin1String("Session ")); }), newActions.end()); // Limit the number of list entries we like to offer const int maxEntryCount = std::min(sessionList.count(), 10); // sessionList is ordered by time, but we like it alphabetical to avoid even more a needed update QStringList sessionSubList = sessionList.mid(0, maxEntryCount); sessionSubList.sort(); // we compute the new group names in advance so we can tell whether we changed something // and avoid touching the desktop file leading to an expensive ksycoca recreation QStringList sessionActions; sessionActions.reserve(maxEntryCount); for (int i = 0; i < maxEntryCount; ++i) { sessionActions << QStringLiteral("Session %1").arg(QString::fromLatin1(QCryptographicHash::hash(sessionSubList.at(i).toUtf8(), QCryptographicHash::Md5).toHex())); } newActions += sessionActions; // nothing to do if (df->readActions() == newActions) { return; } const QString &localPath = service->locateLocal(); if (service->entryPath() != localPath) { df.reset(df->copyTo(localPath)); } // remove all Session action groups first to not leave behind any cruft for (const QString &action : df->readActions()) { if (action.startsWith(QLatin1String("Session "))) { // TODO is there no deleteGroup(KConfigGroup)? df->deleteGroup(df->actionGroup(action).name()); } } for (int i = 0; i < maxEntryCount; ++i) { const QString &action = sessionActions.at(i); // is a transform of sessionSubList, so count and order is identical const QString &session = sessionSubList.at(i); KConfigGroup grp = df->actionGroup(action); grp.writeEntry(QStringLiteral("Name"), session); grp.writeEntry(QStringLiteral("Exec"), QStringLiteral("kate -s %1").arg(KShell::quoteArg(session))); // TODO proper executable name? } df->desktopGroup().writeXdgListEntry("Actions", newActions); #endif } // END KateSessionManager diff --git a/kate/session/katesessionmanager.h b/kate/session/katesessionmanager.h index 4bdc3e9d2..cd14a8cb0 100644 --- a/kate/session/katesessionmanager.h +++ b/kate/session/katesessionmanager.h @@ -1,252 +1,252 @@ /* This file is part of the KDE project * * Copyright (C) 2005 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef __KATE_SESSION_MANAGER_H__ #define __KATE_SESSION_MANAGER_H__ #include "katesession.h" -#include #include +#include typedef QList KateSessionList; class KATE_TESTS_EXPORT KateSessionManager : public QObject { Q_OBJECT friend class KateSessionManageDialog; public: KateSessionManager(QObject *parent = nullptr, const QString &sessionsDir = QString()); ~KateSessionManager() override; /** * allow access to the session list * kept up to date by watching the dir */ KateSessionList sessionList(); /** * activate session by \p name * first, it will look if a session with this name exists in list * if yes, it will use this session, else it will create a new session file * @param name name of the session to activate * @param closeAndSaveLast try to close and save last session or not? * @param loadNew load new session stuff? * @return false==session has been delegated, true==session has been activated in this distance */ bool activateSession(const QString &name, const bool closeAndSaveLast = true, const bool loadNew = true); /** * activates new/anonymous session */ bool activateAnonymousSession(); /** * save current session * @param rememberAsLast remember this session as last used? * @return success */ bool saveActiveSession(bool rememberAsLast = false); /** * return the current active session * sessionFile == empty means we have no session around for this instance of kate * @return session active atm */ inline KateSession::Ptr activeSession() { return m_activeSession; } /** * session dir * @return global session dir */ inline const QString &sessionsDir() const { return m_sessionsDir; } /** * initial session chooser, on app start * @return success, if false, app should exit */ bool chooseSession(); public Q_SLOTS: /** * try to start a new session * asks user first for name */ void sessionNew(); /** * try to save current session */ void sessionSave(); /** * try to save as current session */ void sessionSaveAs(); /** * show dialog to manage our sessions */ void sessionManage(); Q_SIGNALS: /** * Emitted, whenever the session changes, e.g. when it was renamed. */ void sessionChanged(); /** * Emitted whenever the session list has changed. * @see sessionList() */ void sessionListChanged(); /** * module internal APIs */ public: /** * return session with given name * if no existing session matches, create new one with this name * @param name session name */ KateSession::Ptr giveSession(const QString &name); /** * Try to delete the @p session and removes the session from sessions list * @param session the session to delete * @return true on success, false if @p session is currently in use */ bool deleteSession(KateSession::Ptr session); /** * Try to copy the @p session to a new session @p newName. * Will ask by @c askForNewSessionName() for a differend name when @p newName is already in use or is an * empty string. * @param session the session to copy * @param newName is wished name of the new session * @return the new session name on success, otherwise an empty string * @see askForNewSessionName() */ QString copySession(const KateSession::Ptr &session, const QString &newName = QString()); /** * Try to rename the @p session to @p newName. * Will ask by @c askForNewSessionName() for a differend name when @p newName is already in use or is an * empty string. * @param session the session to rename * @param newName is wished new name of the session * @return the new session name on success, otherwise an empty string * @see askForNewSessionName() */ QString renameSession(KateSession::Ptr session, const QString &newName = QString()); /** * activate a session * first, it will look if a session with this name exists in list * if yes, it will use this session, else it will create a new session file * @param session session to activate * @param closeAndSaveLast try to close and save last session or not? * @param loadNew load new session stuff? * @return false==session has been delegated, true==session has been activated in this distance */ bool activateSession(KateSession::Ptr session, const bool closeAndSaveLast = true, const bool loadNew = true); private Q_SLOTS: /** * trigger update of session list */ void updateSessionList(); private: /** * Ask the user for a new session name, when needed. * @param session is the session to rename or copy * @param newName is a preset value. Is @p newName not already in use is nothing asked * @return a (currently) not used new session name or an empty string when * user aborted or when @p newName is the current session name. */ QString askForNewSessionName(KateSession::Ptr session, const QString &newName = QString()); /** * Try to generate a new session name from @p target by a number suffix. * @param target is the base name * @return a (currently) not used session name or an empty string */ QString suggestNewSessionName(const QString &target); /** * returns session config file according to policy */ QString sessionFileForName(const QString &name) const; /** * @return true when @p session is active in any Kate instance, otherwise false */ bool sessionIsActive(const QString &session); /** * returns session file for anonymous session */ QString anonymousSessionFile() const; /** * helper function to save the session to a given config object */ void saveSessionTo(KConfig *sc) const; /** * restore sessions documents, windows, etc... */ void loadSession(const KateSession::Ptr &session) const; /** * Writes sessions as jump list actions to the kate.desktop file */ void updateJumpListActions(const QStringList &sessionList); private: /** * absolute path to dir in home dir where to store the sessions */ QString m_sessionsDir; /** * list of current available sessions */ QHash m_sessions; /** * current active session */ KateSession::Ptr m_activeSession; class KDirWatch *m_dirWatch; }; #endif diff --git a/kate/session/katesessionsaction.cpp b/kate/session/katesessionsaction.cpp index 7be9fc5c6..9c4835625 100644 --- a/kate/session/katesessionsaction.cpp +++ b/kate/session/katesessionsaction.cpp @@ -1,81 +1,81 @@ /* This file is part of the KDE project * * Copyright (C) 2005 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #include "katesessionsaction.h" #include "kateapp.h" #include "katesessionmanager.h" -#include #include +#include KateSessionsAction::KateSessionsAction(const QString &text, QObject *parent, KateSessionManager *manager) : KActionMenu(text, parent) { m_manager = manager ? manager : KateApp::self()->sessionManager(); connect(menu(), &QMenu::aboutToShow, this, &KateSessionsAction::slotAboutToShow); sessionsGroup = new QActionGroup(menu()); // reason for Qt::QueuedConnection: when switching session with N mainwindows // to e.g. 1 mainwindow, the last N - 1 mainwindows are deleted. Invoking // a session switch without queued connection deletes a mainwindow in which // the current code path is executed ---> crash. See bug #227008. connect(sessionsGroup, &QActionGroup::triggered, this, &KateSessionsAction::openSession, Qt::QueuedConnection); connect(m_manager, &KateSessionManager::sessionChanged, this, &KateSessionsAction::slotSessionChanged); setDisabled(m_manager->sessionList().empty()); } void KateSessionsAction::slotAboutToShow() { qDeleteAll(sessionsGroup->actions()); KateSessionList slist = m_manager->sessionList(); std::sort(slist.begin(), slist.end(), KateSession::compareByTimeDesc); slist = slist.mid(0, 10); // take first 10 // sort the reduced list alphabetically (#364089) std::sort(slist.begin(), slist.end(), KateSession::compareByName); for (const KateSession::Ptr &session : qAsConst(slist)) { QString sessionName = session->name(); sessionName.replace(QStringLiteral("&"), QStringLiteral("&&")); QAction *action = new QAction(sessionName, sessionsGroup); action->setData(QVariant(session->name())); action->setCheckable(true); action->setChecked(session == m_manager->activeSession()); menu()->addAction(action); } } void KateSessionsAction::openSession(QAction *action) { const QString name = action->data().toString(); m_manager->activateSession(name); } void KateSessionsAction::slotSessionChanged() { setDisabled(m_manager->sessionList().empty()); } diff --git a/kwrite/kwrite.cpp b/kwrite/kwrite.cpp index cea8f7b68..4ddc2893e 100644 --- a/kwrite/kwrite.cpp +++ b/kwrite/kwrite.cpp @@ -1,519 +1,519 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ #include "kwrite.h" -#include "kwriteapplication.h" #include "config.h" +#include "kwriteapplication.h" +#include #include -#include -#include #include -#include +#include +#include #include #include -#include +#include +#include #include #include #include #include #include -#include #include +#include +#include #include -#include -#include #ifdef KF5Activities_FOUND #include #endif -#include -#include -#include #include -#include -#include -#include #include +#include #include #include +#include +#include +#include +#include +#include KWrite::KWrite(KTextEditor::Document *doc, KWriteApplication *app) : m_app(app) , m_mainWindow(this) { if (!doc) { doc = KTextEditor::Editor::instance()->createDocument(nullptr); // enable the modified on disk warning dialogs if any if (qobject_cast(doc)) { qobject_cast(doc)->setModifiedOnDiskWarning(true); } m_app->addDocument(doc); } m_view = doc->createView(this); setCentralWidget(m_view); setupActions(); // signals for the statusbar connect(m_view->document(), &KTextEditor::Document::modifiedChanged, this, &KWrite::modifiedChanged); connect(m_view->document(), &KTextEditor::Document::documentNameChanged, this, &KWrite::documentNameChanged); connect(m_view->document(), &KTextEditor::Document::readWriteChanged, this, &KWrite::documentNameChanged); connect(m_view->document(), &KTextEditor::Document::documentUrlChanged, this, &KWrite::urlChanged); setAcceptDrops(true); connect(m_view, SIGNAL(dropEventPass(QDropEvent *)), this, SLOT(slotDropEvent(QDropEvent *))); setXMLFile(QStringLiteral("kwriteui.rc")); createShellGUI(true); guiFactory()->addClient(m_view); // FIXME: make sure the config dir exists, any idea how to do it more cleanly? QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)).mkpath(QStringLiteral(".")); // call it as last thing, must be sure everything is already set up ;) setAutoSaveSettings(); readConfig(); documentNameChanged(); show(); // give view focus m_view->setFocus(Qt::OtherFocusReason); /** * handle mac os x like file open request via event filter */ qApp->installEventFilter(this); } KWrite::~KWrite() { m_app->removeWindow(this); guiFactory()->removeClient(m_view); KTextEditor::Document *doc = m_view->document(); delete m_view; // kill document, if last view is closed if (doc->views().isEmpty()) { m_app->removeDocument(doc); delete doc; } KSharedConfig::openConfig()->sync(); } QSize KWrite::sizeHint() const { /** * have some useful size hint, else we have mini windows per default */ return (QSize(640, 480).expandedTo(minimumSizeHint())); } void KWrite::setupActions() { m_closeAction = actionCollection()->addAction(KStandardAction::Close, QStringLiteral("file_close"), this, SLOT(slotFlush())); m_closeAction->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); m_closeAction->setWhatsThis(i18n("Use this command to close the current document")); m_closeAction->setDisabled(true); // setup File menu actionCollection()->addAction(KStandardAction::New, QStringLiteral("file_new"), this, SLOT(slotNew()))->setWhatsThis(i18n("Use this command to create a new document")); actionCollection()->addAction(KStandardAction::Open, QStringLiteral("file_open"), this, SLOT(slotOpen()))->setWhatsThis(i18n("Use this command to open an existing document for editing")); m_recentFiles = KStandardAction::openRecent(this, SLOT(slotOpen(QUrl)), this); actionCollection()->addAction(m_recentFiles->objectName(), m_recentFiles); m_recentFiles->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); QAction *a = actionCollection()->addAction(QStringLiteral("view_new_view")); a->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); a->setText(i18n("&New Window")); connect(a, &QAction::triggered, this, &KWrite::newView); a->setWhatsThis(i18n("Create another view containing the current document")); actionCollection()->addAction(KStandardAction::Quit, this, SLOT(close()))->setWhatsThis(i18n("Close the current document view")); // setup Settings menu setStandardToolBarMenuEnabled(true); m_paShowMenuBar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()), actionCollection()); m_paShowStatusBar = KStandardAction::showStatusbar(this, SLOT(toggleStatusBar()), this); actionCollection()->addAction(m_paShowStatusBar->objectName(), m_paShowStatusBar); m_paShowStatusBar->setWhatsThis(i18n("Use this command to show or hide the view's statusbar")); m_paShowPath = new KToggleAction(i18n("Sho&w Path in Titlebar"), this); actionCollection()->addAction(QStringLiteral("set_showPath"), m_paShowPath); connect(m_paShowPath, &QAction::triggered, this, &KWrite::documentNameChanged); m_paShowPath->setWhatsThis(i18n("Show the complete document path in the window caption")); a = actionCollection()->addAction(KStandardAction::KeyBindings, this, SLOT(editKeys())); a->setWhatsThis(i18n("Configure the application's keyboard shortcut assignments.")); a = actionCollection()->addAction(KStandardAction::ConfigureToolbars, QStringLiteral("options_configure_toolbars"), this, SLOT(editToolbars())); a->setWhatsThis(i18n("Configure which items should appear in the toolbar(s).")); a = actionCollection()->addAction(QStringLiteral("help_about_editor")); a->setText(i18n("&About Editor Component")); connect(a, &QAction::triggered, this, &KWrite::aboutEditor); } // load on url void KWrite::loadURL(const QUrl &url) { m_view->document()->openUrl(url); #ifdef KF5Activities_FOUND if (!m_activityResource) { m_activityResource = new KActivities::ResourceInstance(winId(), this); } m_activityResource->setUri(m_view->document()->url()); #endif m_closeAction->setEnabled(true); } // is closing the window wanted by user ? bool KWrite::queryClose() { if (m_view->document()->views().count() > 1) { return true; } if (m_view->document()->queryClose()) { writeConfig(); return true; } return false; } void KWrite::slotFlush() { if (m_view->document()->closeUrl()) { m_closeAction->setDisabled(true); } } void KWrite::modifiedChanged() { documentNameChanged(); m_closeAction->setEnabled(true); } void KWrite::slotNew() { m_app->newWindow(); } void KWrite::slotOpen() { const QList urls = QFileDialog::getOpenFileUrls(this, i18n("Open File"), m_view->document()->url()); for (const QUrl &url : urls) { slotOpen(url); } } void KWrite::slotOpen(const QUrl &url) { if (url.isEmpty()) { return; } if (m_view->document()->isModified() || !m_view->document()->url().isEmpty()) { KWrite *t = m_app->newWindow(); t->loadURL(url); } else { loadURL(url); } } void KWrite::urlChanged() { if (!m_view->document()->url().isEmpty()) { m_recentFiles->addUrl(m_view->document()->url()); } // update caption documentNameChanged(); } void KWrite::newView() { m_app->newWindow(m_view->document()); } void KWrite::toggleMenuBar(bool showMessage) { if (m_paShowMenuBar->isChecked()) { menuBar()->show(); removeMenuBarActionFromContextMenu(); } else { if (showMessage) { const QString accel = m_paShowMenuBar->shortcut().toString(); KMessageBox::information(this, i18n("This will hide the menu bar completely." " You can show it again by typing %1.", accel), i18n("Hide menu bar"), QStringLiteral("HideMenuBarWarning")); } menuBar()->hide(); addMenuBarActionToContextMenu(); } } void KWrite::addMenuBarActionToContextMenu() { m_view->contextMenu()->addAction(m_paShowMenuBar); } void KWrite::removeMenuBarActionFromContextMenu() { m_view->contextMenu()->removeAction(m_paShowMenuBar); } void KWrite::toggleStatusBar() { m_view->setStatusBarEnabled(m_paShowStatusBar->isChecked()); } void KWrite::editKeys() { KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); dlg.addCollection(actionCollection()); if (m_view) { dlg.addCollection(m_view->actionCollection()); } dlg.configure(); } void KWrite::editToolbars() { KConfigGroup cfg = KSharedConfig::openConfig()->group("MainWindow"); saveMainWindowSettings(cfg); KEditToolBar dlg(guiFactory(), this); connect(&dlg, &KEditToolBar::newToolBarConfig, this, &KWrite::slotNewToolbarConfig); dlg.exec(); } void KWrite::slotNewToolbarConfig() { applyMainWindowSettings(KSharedConfig::openConfig()->group("MainWindow")); } void KWrite::dragEnterEvent(QDragEnterEvent *event) { const QList uriList = event->mimeData()->urls(); event->setAccepted(!uriList.isEmpty()); } void KWrite::dropEvent(QDropEvent *event) { slotDropEvent(event); } void KWrite::slotDropEvent(QDropEvent *event) { const QList textlist = event->mimeData()->urls(); for (const QUrl &url : textlist) slotOpen(url); } void KWrite::slotEnableActions(bool enable) { QList actions = actionCollection()->actions(); QList::ConstIterator it = actions.constBegin(); QList::ConstIterator end = actions.constEnd(); for (; it != end; ++it) { (*it)->setEnabled(enable); } actions = m_view->actionCollection()->actions(); it = actions.constBegin(); end = actions.constEnd(); for (; it != end; ++it) { (*it)->setEnabled(enable); } } // common config void KWrite::readConfig(KSharedConfigPtr config) { KConfigGroup cfg(config, "General Options"); m_paShowMenuBar->setChecked(cfg.readEntry("ShowMenuBar", true)); m_paShowStatusBar->setChecked(cfg.readEntry("ShowStatusBar", true)); m_paShowPath->setChecked(cfg.readEntry("ShowPath", false)); m_recentFiles->loadEntries(config->group("Recent Files")); // update visibility of menu bar and status bar toggleMenuBar(false); m_view->setStatusBarEnabled(m_paShowStatusBar->isChecked()); } void KWrite::writeConfig(KSharedConfigPtr config) { KConfigGroup generalOptions(config, "General Options"); generalOptions.writeEntry("ShowMenuBar", m_paShowMenuBar->isChecked()); generalOptions.writeEntry("ShowStatusBar", m_paShowStatusBar->isChecked()); generalOptions.writeEntry("ShowPath", m_paShowPath->isChecked()); m_recentFiles->saveEntries(KConfigGroup(config, "Recent Files")); config->sync(); } // config file void KWrite::readConfig() { readConfig(KSharedConfig::openConfig()); } void KWrite::writeConfig() { writeConfig(KSharedConfig::openConfig()); } // session management void KWrite::restore(KConfig *config, int n) { readPropertiesInternal(config, n); } void KWrite::readProperties(const KConfigGroup &config) { readConfig(); m_view->readSessionConfig(KConfigGroup(&config, QStringLiteral("General Options"))); } void KWrite::saveProperties(KConfigGroup &config) { writeConfig(); config.writeEntry("DocumentNumber", m_app->documents().indexOf(m_view->document()) + 1); KConfigGroup cg(&config, QStringLiteral("General Options")); m_view->writeSessionConfig(cg); } void KWrite::saveGlobalProperties(KConfig *config) // save documents { m_app->saveProperties(config); } void KWrite::aboutEditor() { KAboutApplicationDialog dlg(KTextEditor::Editor::instance()->aboutData(), this); dlg.exec(); } void KWrite::documentNameChanged() { QString readOnlyCaption; if (!m_view->document()->isReadWrite()) { readOnlyCaption = i18n(" [read only]"); } if (m_view->document()->url().isEmpty()) { setCaption(i18n("Untitled") + readOnlyCaption + QStringLiteral(" [*]"), m_view->document()->isModified()); return; } QString c; if (m_paShowPath->isChecked()) { c = m_view->document()->url().toString(QUrl::PreferLocalFile); const QString homePath = QDir::homePath(); if (c.startsWith(homePath)) { c = QLatin1String("~") + c.right(c.length() - homePath.length()); } // File name shouldn't be too long - Maciek if (c.length() > 64) { c = QLatin1String("...") + c.right(64); } } else { c = m_view->document()->url().fileName(); // File name shouldn't be too long - Maciek if (c.length() > 64) { c = c.left(64) + QStringLiteral("..."); } } setCaption(c + readOnlyCaption + QStringLiteral(" [*]"), m_view->document()->isModified()); } bool KWrite::eventFilter(QObject *obj, QEvent *event) { /** * handle mac os like file open */ if (event->type() == QEvent::FileOpen) { /** * try to open and activate the new document, like we would do for stuff * opened via file dialog */ QFileOpenEvent *foe = static_cast(event); slotOpen(foe->url()); return true; } /** * else: pass over to default implementation */ return KParts::MainWindow::eventFilter(obj, event); } QList KWrite::views() { QList list; list.append(m_view); return list; } KTextEditor::View *KWrite::activateView(KTextEditor::Document *document) { if (m_view->document() == document) { return m_view; } return nullptr; } diff --git a/kwrite/kwrite.h b/kwrite/kwrite.h index 58ace25fb..40df46e33 100644 --- a/kwrite/kwrite.h +++ b/kwrite/kwrite.h @@ -1,154 +1,154 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ #ifndef KWRITE_MAIN_H #define KWRITE_MAIN_H -#include #include #include +#include -#include #include +#include #include class QLabel; namespace KActivities { class ResourceInstance; } class KToggleAction; class KRecentFilesAction; class KSqueezedTextLabel; class KWriteApplication; class KWrite : public KParts::MainWindow { Q_OBJECT public: KWrite(KTextEditor::Document * = nullptr, KWriteApplication *app = nullptr); ~KWrite() override; void loadURL(const QUrl &url); private: void setupActions(); void addMenuBarActionToContextMenu(); void removeMenuBarActionFromContextMenu(); bool queryClose() override; void dragEnterEvent(QDragEnterEvent *) override; void dropEvent(QDropEvent *) override; public Q_SLOTS: void slotNew(); void slotFlush(); void slotOpen(); void slotOpen(const QUrl &url); void newView(); void toggleStatusBar(); void toggleMenuBar(bool showMessage = true); void editKeys(); void editToolbars(); void aboutEditor(); void modifiedChanged(); private Q_SLOTS: void slotNewToolbarConfig(); public Q_SLOTS: void slotDropEvent(QDropEvent *); void slotEnableActions(bool enable); /** * adds a changed URL to the recent files */ void urlChanged(); /** * Overwrite size hint for better default window sizes * @return size hint */ QSize sizeHint() const override; // config file functions public: void readConfig(KSharedConfigPtr); void writeConfig(KSharedConfigPtr); void readConfig(); void writeConfig(); // session management public: void restore(KConfig *, int); public: KTextEditor::MainWindow *mainWindow() { return &m_mainWindow; } public Q_SLOTS: QWidget *window() { return this; } QList views(); KTextEditor::View *activeView() { return m_view; } KTextEditor::View *activateView(KTextEditor::Document *document); private: void readProperties(const KConfigGroup &) override; void saveProperties(KConfigGroup &) override; void saveGlobalProperties(KConfig *) override; private: KTextEditor::View *m_view = nullptr; KRecentFilesAction *m_recentFiles = nullptr; KToggleAction *m_paShowPath = nullptr; KToggleAction *m_paShowMenuBar = nullptr; KToggleAction *m_paShowStatusBar = nullptr; QAction *m_closeAction; KActivities::ResourceInstance *m_activityResource = nullptr; KWriteApplication *m_app; KTextEditor::MainWindow m_mainWindow; public Q_SLOTS: void documentNameChanged(); protected: /** * Event filter for QApplication to handle mac os like file open */ bool eventFilter(QObject *obj, QEvent *event) override; }; #endif diff --git a/kwrite/kwriteapplication.cpp b/kwrite/kwriteapplication.cpp index 7b0a3056c..c52815d0a 100644 --- a/kwrite/kwriteapplication.cpp +++ b/kwrite/kwriteapplication.cpp @@ -1,145 +1,145 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ #include "kwriteapplication.h" #include "kwrite.h" -#include #include +#include KWriteApplication::KWriteApplication::KWriteApplication() { m_application = new KTextEditor::Application(this); KTextEditor::Editor::instance()->setApplication(m_application); } KWriteApplication::~KWriteApplication() { delete m_application; } KWrite *KWriteApplication::newWindow(KTextEditor::Document *doc) { KWrite *k = new KWrite(doc, this); m_kwrites.append(k); return k; } void KWriteApplication::restore() { KConfig *config = KConfigGui::sessionConfig(); if (!config) { return; } int docs, windows; QString buf; KTextEditor::Document *doc; KWrite *t; KConfigGroup numberConfig(config, "Number"); docs = numberConfig.readEntry("NumberOfDocuments", 0); windows = numberConfig.readEntry("NumberOfWindows", 0); for (int z = 1; z <= docs; z++) { buf = QStringLiteral("Document %1").arg(z); KConfigGroup cg(config, buf); doc = KTextEditor::Editor::instance()->createDocument(nullptr); doc->readSessionConfig(cg); addDocument(doc); } for (int z = 1; z <= windows; z++) { buf = QStringLiteral("Window %1").arg(z); KConfigGroup cg(config, buf); t = newWindow(m_documents.at(cg.readEntry("DocumentNumber", 0) - 1)); t->restore(config, z); } } void KWriteApplication::saveProperties(KConfig *config) { config->group("Number").writeEntry("NumberOfDocuments", m_documents.count()); for (int z = 1; z <= m_documents.count(); z++) { QString buf = QStringLiteral("Document %1").arg(z); KConfigGroup cg(config, buf); KTextEditor::Document *doc = m_documents.at(z - 1); doc->writeSessionConfig(cg); } for (int z = 1; z <= m_kwrites.count(); z++) { QString buf = QStringLiteral("Window %1").arg(z); KConfigGroup cg(config, buf); cg.writeEntry("DocumentNumber", m_documents.indexOf(m_kwrites.at(z - 1)->activeView()->document()) + 1); } } bool KWriteApplication::quit() { QList copy(m_kwrites); for (auto kwrite : copy) { if (!kwrite->close()) { return false; } m_kwrites.removeAll(kwrite); delete kwrite; } return true; } KTextEditor::MainWindow *KWriteApplication::activeMainWindow() { for (auto kwrite : m_kwrites) { if (kwrite->isActiveWindow()) { return kwrite->mainWindow(); } } return nullptr; } QList KWriteApplication::mainWindows() { QList windows; for (auto kwrite : m_kwrites) { windows.append(kwrite->mainWindow()); } return windows; } bool KWriteApplication::closeDocument(KTextEditor::Document *document) { QList copy(m_kwrites); for (auto kwrite : copy) { if (kwrite->activeView()->document() == document) { if (!kwrite->close()) { return false; } m_kwrites.removeAll(kwrite); delete kwrite; } } return true; } diff --git a/kwrite/kwriteapplication.h b/kwrite/kwriteapplication.h index d2e4830e3..8e72b4d1b 100644 --- a/kwrite/kwriteapplication.h +++ b/kwrite/kwriteapplication.h @@ -1,78 +1,78 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ #ifndef KWRITE_APPLICATION_H #define KWRITE_APPLICATION_H -#include -#include #include +#include #include +#include class KWrite; class KWriteApplication : public QObject { Q_OBJECT public: KWriteApplication(); ~KWriteApplication() override; void addDocument(KTextEditor::Document *doc) { m_documents.append(doc); } void removeDocument(KTextEditor::Document *doc) { m_documents.removeAll(doc); } void removeWindow(KWrite *kwrite) { m_kwrites.removeAll(kwrite); } bool noWindows() { return m_kwrites.isEmpty(); } KWrite *newWindow(KTextEditor::Document *doc = nullptr); void restore(); void saveProperties(KConfig *config); public Q_SLOTS: QList documents() { return m_documents; } bool quit(); KTextEditor::MainWindow *activeMainWindow(); QList mainWindows(); bool closeDocument(KTextEditor::Document *document); private: KTextEditor::Application *m_application; QList m_documents; QList m_kwrites; }; #endif diff --git a/kwrite/main.cpp b/kwrite/main.cpp index f385ec65d..a70cc0e92 100644 --- a/kwrite/main.cpp +++ b/kwrite/main.cpp @@ -1,312 +1,312 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ #include "config.h" #include "kwrite.h" #include "kwriteapplication.h" #include -#include #include +#include #include -#include // for KAboutData::setDesktopFileName() +#include +#include #include #include -#include +#include // for KAboutData::setDesktopFileName() #include -#include -#include #include -#include -#include +#include #include +#include +#include #include #include "../urlinfo.h" #ifndef Q_OS_WIN #include #endif #include extern "C" Q_DECL_EXPORT int main(int argc, char **argv) { #ifndef Q_OS_WIN // Prohibit using sudo or kdesu (but allow using the root user directly) if (getuid() == 0) { if (!qEnvironmentVariableIsEmpty("SUDO_USER")) { std::cout << "Executing KWrite with sudo is not possible due to unfixable security vulnerabilities." << std::endl; return EXIT_FAILURE; } else if (!qEnvironmentVariableIsEmpty("KDESU_USER")) { std::cout << "Executing KWrite with kdesu is not possible due to unfixable security vulnerabilities." << std::endl; return EXIT_FAILURE; } } #endif /** * enable high dpi support */ QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); /** * Create application first * Enforce application name even if the executable is renamed */ QApplication app(argc, argv); app.setApplicationName(QStringLiteral("kwrite")); /** * Enable crash handling through KCrash. */ KCrash::initialize(); /** * Connect application with translation catalogs */ KLocalizedString::setApplicationDomain("kwrite"); /** * then use i18n and co */ KAboutData aboutData( QStringLiteral("kwrite"), i18n("KWrite"), QStringLiteral(KWRITE_VERSION), i18n("KWrite - Text Editor"), KAboutLicense::LGPL_V2, i18n("(c) 2000-2019 The Kate Authors"), QString(), QStringLiteral("https://kate-editor.org")); /** * right dbus prefix == org.kde. */ aboutData.setOrganizationDomain(QByteArray("kde.org")); /** * desktop file association to make application icon work (e.g. in Wayland window decoration) */ aboutData.setDesktopFileName(QStringLiteral("org.kde.kwrite")); /** * authors & co. */ aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("https://cullmann.io")); aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org")); aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("http://www.alweb.dk")); aboutData.addAuthor(i18n("Joseph Wenninger"), i18n("Core Developer"), QStringLiteral("jowenn@kde.org"), QStringLiteral("http://stud3.tuwien.ac.at/~e9925371")); aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org")); aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org")); aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org")); aboutData.addAuthor(i18n("Matt Newell"), i18nc("Credit text for someone that did testing and some other similar things", "Testing, ..."), QStringLiteral("newellm@proaxis.com")); aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at")); aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz")); aboutData.addAuthor(i18n("Jochen Wilhemly"), i18n("KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de")); aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org")); aboutData.addAuthor(i18n("Christian Gebauer"), QString(), QStringLiteral("gebauer@kde.org")); aboutData.addAuthor(i18n("Simon Hausmann"), QString(), QStringLiteral("hausmann@kde.org")); aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com")); aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net")); aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org")); aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"), i18n("QA and Scripting"), QStringLiteral("oss@senarclens.eu"), QStringLiteral("http://find-santa.eu/")); aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it")); aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu")); aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL")); aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite")); aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG")); aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX")); aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python")); aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python")); aboutData.addCredit(i18n("Daniel Naber")); aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme")); aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list")); aboutData.addCredit(i18n("Carsten Pfeiffer"), i18nc("Credit text for someone that helped a lot", "Very nice help")); aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention")); /** * bugzilla */ aboutData.setProductName(QByteArray("kate/kwrite")); /** * set and register app about data */ KAboutData::setApplicationData(aboutData); /** * set the program icon */ QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor"), app.windowIcon())); /** * Create command line parser and feed it with known options */ QCommandLineParser parser; aboutData.setupCommandLine(&parser); // -e/--encoding option const QCommandLineOption useEncoding(QStringList() << QStringLiteral("e") << QStringLiteral("encoding"), i18n("Set encoding for the file to open."), i18n("encoding")); parser.addOption(useEncoding); // -l/--line option const QCommandLineOption gotoLine(QStringList() << QStringLiteral("l") << QStringLiteral("line"), i18n("Navigate to this line."), i18n("line")); parser.addOption(gotoLine); // -c/--column option const QCommandLineOption gotoColumn(QStringList() << QStringLiteral("c") << QStringLiteral("column"), i18n("Navigate to this column."), i18n("column")); parser.addOption(gotoColumn); // -i/--stdin option const QCommandLineOption readStdIn(QStringList() << QStringLiteral("i") << QStringLiteral("stdin"), i18n("Read the contents of stdin.")); parser.addOption(readStdIn); // --tempfile option const QCommandLineOption tempfile(QStringList() << QStringLiteral("tempfile"), i18n("The files/URLs opened by the application will be deleted after use")); parser.addOption(tempfile); // urls to open parser.addPositionalArgument(QStringLiteral("urls"), i18n("Documents to open."), i18n("[urls...]")); /** * do the command line parsing */ parser.process(app); /** * handle standard options */ aboutData.processCommandLine(&parser); KWriteApplication kapp; if (app.isSessionRestored()) { kapp.restore(); } else { bool nav = false; int line = 0, column = 0; QTextCodec *codec = parser.isSet(QStringLiteral("encoding")) ? QTextCodec::codecForName(parser.value(QStringLiteral("encoding")).toLocal8Bit()) : nullptr; if (parser.isSet(QStringLiteral("line"))) { line = parser.value(QStringLiteral("line")).toInt() - 1; nav = true; } if (parser.isSet(QStringLiteral("column"))) { column = parser.value(QStringLiteral("column")).toInt() - 1; nav = true; } if (parser.positionalArguments().count() == 0) { KWrite *t = kapp.newWindow(); if (parser.isSet(QStringLiteral("stdin"))) { QTextStream input(stdin, QIODevice::ReadOnly); // set chosen codec if (codec) { input.setCodec(codec); } QString line; QString text; do { line = input.readLine(); text.append(line + QLatin1Char('\n')); } while (!line.isNull()); KTextEditor::Document *doc = t->activeView()->document(); if (doc) { // remember codec in document, e.g. to show the right one if (codec) { doc->setEncoding(QString::fromLatin1(codec->name())); } doc->setText(text); } } if (nav && t->activeView()) { t->activeView()->setCursorPosition(KTextEditor::Cursor(line, column)); } } else { int docs_opened = 0; const auto positionalArguments = parser.positionalArguments(); for (const QString &positionalArgument : positionalArguments) { UrlInfo info(positionalArgument); if (nav) { info.cursor = KTextEditor::Cursor(line, column); } // this file is no local dir, open it, else warn bool noDir = !info.url.isLocalFile() || !QFileInfo(info.url.toLocalFile()).isDir(); if (noDir) { ++docs_opened; KWrite *t = kapp.newWindow(); if (codec) { t->activeView()->document()->setEncoding(QString::fromLatin1(codec->name())); } t->loadURL(info.url); if (info.cursor.isValid()) { t->activeView()->setCursorPosition(info.cursor); } else if (info.url.hasQuery()) { QUrlQuery q(info.url); QString lineStr = q.queryItemValue(QStringLiteral("line")); QString columnStr = q.queryItemValue(QStringLiteral("column")); line = lineStr.toInt(); if (line > 0) line--; column = columnStr.toInt(); if (column > 0) column--; t->activeView()->setCursorPosition(KTextEditor::Cursor(line, column)); } } else { KMessageBox::sorry(nullptr, i18n("The file '%1' could not be opened: it is not a normal file, it is a folder.", info.url.toString())); } } if (!docs_opened) { ::exit(1); // see http://bugs.kde.org/show_bug.cgi?id=124708 } } } // no window there, uh, ohh, for example borked session config !!! // create at least one !! if (kapp.noWindows()) { kapp.newWindow(); } /** * finally register this kwrite instance for dbus, don't die if no dbus is around! */ const KDBusService dbusService(KDBusService::Multiple | KDBusService::NoExitOnFailure); /** * Run the event loop */ return app.exec(); }