diff --git a/addons/backtracebrowser/btparser.h b/addons/backtracebrowser/btparser.h index dee138e9f..338eb29e6 100644 --- a/addons/backtracebrowser/btparser.h +++ b/addons/backtracebrowser/btparser.h @@ -1,64 +1,60 @@ /* 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 BTPARSER_H #define BTPARSER_H #include #include class BtInfo { public: enum Type { Source = 0, Lib, Unknown, Invalid }; /** * Default constructor => invalid element */ - BtInfo() - : step(-1) - , line(-1) - , type(Invalid) { - } + BtInfo() = default; public: QString original; QString filename; QString function; QString address; - int step; - int line; + int step = -1; + int line = -1; - Type type; + Type type = Invalid; }; namespace KateBtParser { QList parseBacktrace(const QString &bt); } #endif //BTPARSER_H // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/filebrowser/katefilebrowserconfig.cpp b/addons/filebrowser/katefilebrowserconfig.cpp index a476b879e..8b4d44786 100644 --- a/addons/filebrowser/katefilebrowserconfig.cpp +++ b/addons/filebrowser/katefilebrowserconfig.cpp @@ -1,204 +1,203 @@ /* 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 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 "katefilebrowserconfig.h" #include "katefilebrowser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //BEGIN ACtionLBItem /* QListboxItem that can store and return a string, used for the toolbar action selector. */ class ActionLBItem : public QListWidgetItem { public: ActionLBItem( QListWidget *lb = nullptr, const QIcon &pm = QIcon(), const QString &text = QString(), const QString &str = QString() ) : QListWidgetItem(pm, text, lb, 0 ), _str(str) {} QString idstring() { return _str; } private: QString _str; }; //END ActionLBItem //BEGIN KateFileBrowserConfigPage KateFileBrowserConfigPage::KateFileBrowserConfigPage( QWidget *parent, KateFileBrowser *kfb ) : KTextEditor::ConfigPage( parent ), - fileBrowser( kfb ), - m_changed( false ) + fileBrowser( kfb ) { QVBoxLayout *lo = new QVBoxLayout( this ); int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); lo->setSpacing( spacing ); lo->setContentsMargins(0, 0, 0, 0); // Toolbar - a lot for a little... QGroupBox *gbToolbar = new QGroupBox(i18n("Toolbar"), this ); acSel = new KActionSelector( gbToolbar ); acSel->setAvailableLabel( i18n("A&vailable actions:") ); acSel->setSelectedLabel( i18n("S&elected actions:") ); QVBoxLayout *vbox = new QVBoxLayout; vbox->addWidget(acSel); gbToolbar->setLayout(vbox); lo->addWidget( gbToolbar ); connect(acSel, &KActionSelector::added, this, &KateFileBrowserConfigPage::slotMyChanged); connect(acSel, &KActionSelector::removed, this, &KateFileBrowserConfigPage::slotMyChanged); connect(acSel, &KActionSelector::movedUp, this, &KateFileBrowserConfigPage::slotMyChanged); connect(acSel, &KActionSelector::movedDown, this, &KateFileBrowserConfigPage::slotMyChanged); // make it look nice lo->addStretch( 1 ); init(); } QString KateFileBrowserConfigPage::name() const { return i18n("Filesystem Browser"); } QString KateFileBrowserConfigPage::fullName() const { return i18n("Filesystem Browser Settings"); } QIcon KateFileBrowserConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("document-open")); } void KateFileBrowserConfigPage::apply() { if ( ! m_changed ) return; m_changed = false; KConfigGroup config(KSharedConfig::openConfig(), "filebrowser"); QStringList l; ActionLBItem *aItem; QList list = acSel->selectedListWidget()->findItems(QStringLiteral("*"), Qt::MatchWildcard); foreach(QListWidgetItem *item, list) { aItem = static_cast(item); l << aItem->idstring(); } config.writeEntry( "toolbar actions", l ); fileBrowser->setupToolbar(); } void KateFileBrowserConfigPage::reset() { // hmm, what is this supposed to do, actually?? init(); m_changed = false; } void KateFileBrowserConfigPage::init() { KConfigGroup config(KSharedConfig::openConfig(), "filebrowser"); // toolbar QStringList l = config.readEntry( "toolbar actions", QStringList() ); if ( l.isEmpty() ) // default toolbar l << QStringLiteral("back") << QStringLiteral("forward") << QStringLiteral("bookmarks") << QStringLiteral("sync_dir") << QStringLiteral("configure"); // actions from diroperator + two of our own const QStringList allActions{ QStringLiteral("up"), QStringLiteral("back"), QStringLiteral("forward"), QStringLiteral("home"), QStringLiteral("reload"), QStringLiteral("mkdir"), QStringLiteral("delete"), QStringLiteral("short view"), QStringLiteral("detailed view"), QStringLiteral("tree view"), QStringLiteral("detailed tree view"), QStringLiteral("show hidden"), //QStringLiteral("view menu"), //QStringLiteral("properties"), QStringLiteral("bookmarks"), QStringLiteral("sync_dir"), QStringLiteral("configure")}; QRegularExpression re(QStringLiteral("&(?=[^&])")); QAction *ac = nullptr; QListWidget *lb; for (const auto& actionName : allActions) { lb = l.contains( actionName ) ? acSel->selectedListWidget() : acSel->availableListWidget(); if ( actionName == QStringLiteral ("bookmarks") || actionName == QStringLiteral ("sync_dir") || actionName == QStringLiteral ("configure") ) ac = fileBrowser->actionCollection()->action( actionName ); else ac = fileBrowser->dirOperator()->actionCollection()->action( actionName ); if ( ac ) { QString text = ac->text().remove( re ); // CJK languages need a filtering message for action texts in lists, // to remove special accelerators that they use. // The exact same filtering message exists in kdelibs; hence, // avoid extraction here and let it be sourced from kdelibs. #define i18ncX i18nc text = i18ncX( "@item:intable Action name in toolbar editor", "%1", text ); new ActionLBItem( lb, ac->icon(), text, actionName ); } } } void KateFileBrowserConfigPage::slotMyChanged() { m_changed = true; emit changed(); } //END KateFileBrowserConfigPage // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/filebrowser/katefilebrowserconfig.h b/addons/filebrowser/katefilebrowserconfig.h index c42174099..26e9f7e41 100644 --- a/addons/filebrowser/katefilebrowserconfig.h +++ b/addons/filebrowser/katefilebrowserconfig.h @@ -1,63 +1,63 @@ /* 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_CONFIG_H #define KATE_FILEBROWSER_CONFIG_H #include class KateFileBrowser; class KActionSelector; class KateFileBrowserConfigPage : public KTextEditor::ConfigPage { Q_OBJECT public: explicit KateFileBrowserConfigPage( QWidget* parent = nullptr, KateFileBrowser *kfb = nullptr); ~KateFileBrowserConfigPage() 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 slotMyChanged(); private: void init(); KateFileBrowser *fileBrowser; KActionSelector *acSel; - bool m_changed; + bool m_changed = false; }; #endif //KATE_FILEBROWSER_CONFIG_H // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/filetree/autotests/filetree_model_test.cpp b/addons/filetree/autotests/filetree_model_test.cpp index f95b5bdb2..9d95dccf9 100644 --- a/addons/filetree/autotests/filetree_model_test.cpp +++ b/addons/filetree/autotests/filetree_model_test.cpp @@ -1,915 +1,915 @@ /* 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 "filetree_model_test.h" #include "katefiletreemodel.h" #include "document_dummy.h" #include QTEST_GUILESS_MAIN(FileTreeModelTest) //BEGIN ResultNode class ResultNode { public: - ResultNode() : name(), dir(true), children() {} // root node + ResultNode() = default; // root node ResultNode(const ResultNode &other) : name(other.name), dir(other.dir), children(other.children) {} ResultNode(const char *_name, const bool _dir = false) : ResultNode(QString::fromLatin1(_name), _dir) {} ResultNode(const QString &_name, const bool _dir = false) : name(_name), dir(_dir), children() {} ResultNode &operator<<(const ResultNode &node) { children << node; return *this; } bool operator!=(const ResultNode &other) const { return !(*this == other); } bool operator==(const ResultNode &other) const { return (other.name == name) && (other.dir == dir) && (other.children == children); } friend QDebug operator<< (QDebug s, const ResultNode &node) { s << node.toString(); return s; } friend void debugOutput(QString &s, const ResultNode &rootNode, const int level = 0) { for (int i = 0; i < level; i++) { s += QLatin1String(" "); } const QString name = rootNode.name.isEmpty() ? QStringLiteral("ROOT") : rootNode.name; s += QLatin1String("( ") + name; if (rootNode.dir) { s += QLatin1String(", {D}"); } if (rootNode.children.isEmpty()) { s += QLatin1String(" )"); } else { s += QLatin1String(",\n"); for (int i = 0; i < rootNode.children.size(); i++) { const ResultNode &node = rootNode.children[i]; debugOutput(s, node, level + 1); if ((i + 1) < rootNode.children.size()) { s+= QLatin1String("\n"); } } s += (level == 0) ? QLatin1String("\n);") : QLatin1String(")"); } } QString toString() const { QString out; debugOutput(out, *this, 0); return out; } QString name; - bool dir; + bool dir = true; QList children; }; Q_DECLARE_METATYPE(ResultNode) namespace QTest { inline bool qCompare(const ResultNode &t1, const ResultNode &t2, const char *actual, const char *expected, const char *file, int line) { /* compare_helper is not helping that much, we need to prepare copy of data */ const QByteArray a = t1.toString().toLatin1(); const QByteArray b = t2.toString().toLatin1(); char *val1 = new char[a.size() + 1]; char *val2 = new char[b.size() + 1]; memcpy(val1, a.constData(), a.size() + 1); memcpy(val2, b.constData(), b.size() + 1); return compare_helper(t1 == t2, "Compared ResultNode trees are not the same", val1, val2, actual, expected, file, line); } } //END ResultNode void FileTreeModelTest::initTestCase() {} void FileTreeModelTest::cleanupTestCase() {} void FileTreeModelTest::init() {} void FileTreeModelTest::cleanup() {} void FileTreeModelTest::basic() { QScopedPointer d1(new DummyDocument()); QScopedPointer d2(new DummyDocument()); KateFileTreeModel m(this); QCOMPARE(m.rowCount(QModelIndex()), 0); m.documentOpened(d1.data()); QCOMPARE(m.rowCount(QModelIndex()), 1); m.documentOpened(d2.data()); QCOMPARE(m.rowCount(QModelIndex()), 2); } void FileTreeModelTest::buildTree_data() { QTest::addColumn>("documents"); QTest::addColumn("nodes"); QTest::newRow("easy") << ( QList() << new DummyDocument("file:///a/foo.txt") ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt")) ); QTest::newRow("two") << ( QList() << new DummyDocument("file:///a/foo.txt") << new DummyDocument("file:///a/bar.txt") ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt") << ResultNode("bar.txt")) ); QTest::newRow("strangers") << ( QList() << new DummyDocument("file:///a/foo.txt") << new DummyDocument("file:///b/bar.txt") ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt")) << (ResultNode("b", true) << ResultNode("bar.txt")) ); QTest::newRow("lvl1 strangers") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt")) << (ResultNode("b", true) << ResultNode("bar.txt")) ); QTest::newRow("multiples") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") << new DummyDocument("file:///c/a/bar.txt") ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt") << ResultNode("bar.txt")) << (ResultNode("b", true) << ResultNode("bar.txt")) ); QTest::newRow("stairs") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/bar.txt") ) << ( ResultNode() << (ResultNode("c", true) << (ResultNode("a", true) << ResultNode("foo.txt")) << ResultNode("bar.txt")) ); QTest::newRow("reverse stairs") << ( QList() << new DummyDocument("file:///c/bar.txt") << new DummyDocument("file:///c/a/foo.txt") ) << ( ResultNode() << (ResultNode("c", true) << ResultNode("bar.txt") << (ResultNode("a", true) << ResultNode("foo.txt"))) ); QTest::newRow("matching") << ( QList() << new DummyDocument("file:///a/x/foo.txt") << new DummyDocument("file:///b/x/bar.txt") ) << ( ResultNode() << (ResultNode("a", true) << (ResultNode("x", true) << ResultNode("foo.txt"))) << (ResultNode("b", true) << (ResultNode("x", true) << ResultNode("bar.txt"))) ); QTest::newRow("matching even more") << ( QList() << new DummyDocument("file:///a/x/y/z/foo.txt") << new DummyDocument("file:///b/x/y/z/bar.txt") ) << ( ResultNode() << (ResultNode("a", true) << (ResultNode("x", true) << (ResultNode("y", true) << (ResultNode("z", true) << ResultNode("foo.txt"))))) << (ResultNode("b", true) << (ResultNode("x", true) << (ResultNode("y", true) << (ResultNode("z", true) << ResultNode("bar.txt"))))) ); QTest::newRow("matching with booby trap") << ( QList() << new DummyDocument("file:///x/y/foo.txt") << new DummyDocument("file:///c/x/bar.txt") << new DummyDocument("file:///d/y/baz.txt") ) << ( ResultNode() << (ResultNode("x", true) << (ResultNode("y", true) << ResultNode("foo.txt"))) << (ResultNode("d", true) << (ResultNode("y", true) << ResultNode("baz.txt"))) << (ResultNode("c", true) << (ResultNode("x", true) << ResultNode("bar.txt"))) ); QTest::newRow("branches") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") << new DummyDocument("file:///d/a/foo.txt") ) << ( ResultNode() << (ResultNode("c", true) << (ResultNode("a", true) << ResultNode("foo.txt")) << (ResultNode("b", true) << ResultNode("bar.txt"))) << (ResultNode("d", true) << (ResultNode("a", true) << ResultNode("foo.txt"))) ); QTest::newRow("branches (more)") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") << new DummyDocument("file:///c/c/bar.txt") << new DummyDocument("file:///d/a/foo.txt") ) << ( ResultNode() << (ResultNode("c", true) << (ResultNode("a", true) << ResultNode("foo.txt")) << (ResultNode("b", true) << ResultNode("bar.txt")) << (ResultNode("c", true) << ResultNode("bar.txt"))) << (ResultNode("d", true) << (ResultNode("a", true) << ResultNode("foo.txt"))) ); QTest::newRow("bug347578") << ( QList() << new DummyDocument("file:///f/g/a/b/c/d/e.txt") << new DummyDocument("file:///f/g/a/t/b/c/d/e.txt") ) << ( ResultNode() << (ResultNode("a", true) << (ResultNode("b", true) << (ResultNode("c", true) << (ResultNode("d", true) << ResultNode("e.txt")))) << (ResultNode("t", true) << (ResultNode("b", true) << (ResultNode("c", true) << (ResultNode("d", true) << ResultNode("e.txt")))))) ); QTest::newRow("levels") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") << new DummyDocument("file:///d/foo.txt") ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt")) << (ResultNode("b", true) << ResultNode("bar.txt")) << (ResultNode("d", true) << ResultNode("foo.txt")) ); QTest::newRow("remote simple") << ( QList() << new DummyDocument("http://example.org/foo.txt") ) << ( ResultNode() << (ResultNode("[example.org]", true) << ResultNode("foo.txt")) ); QTest::newRow("remote nested") << ( QList() << new DummyDocument("http://example.org/a/foo.txt") ) << ( ResultNode() << (ResultNode("[example.org]a", true) << ResultNode("foo.txt")) ); /* NOTE: this one is also not completely ok, is it? * on other hand, it would get confusing or overly leveled if opening * something like http://example.org/a/b/c/d/e/f/g.txt */ QTest::newRow("remote diverge") << ( QList() << new DummyDocument("http://example.org/a/foo.txt") << new DummyDocument("http://example.org/b/foo.txt") ) << ( ResultNode() << (ResultNode("[example.org]a", true) << ResultNode("foo.txt")) << (ResultNode("[example.org]b", true) << ResultNode("foo.txt")) ); } void FileTreeModelTest::buildTree() { KateFileTreeModel m(this); QFETCH(QList, documents); QFETCH(ResultNode, nodes); foreach(DummyDocument *doc, documents) { m.documentOpened(doc); } ResultNode root; walkTree(m, QModelIndex(), root); QCOMPARE(root, nodes); qDeleteAll(documents); } void FileTreeModelTest::buildTreeBatch_data() { // the easiest way to verify the equality of those two calls:) buildTree_data(); } void FileTreeModelTest::buildTreeBatch() { KateFileTreeModel m(this); QFETCH(QList, documents); QFETCH(ResultNode, nodes); QList list; foreach(DummyDocument *doc, documents) { list << doc; } m.documentsOpened(list); ResultNode root; walkTree(m, QModelIndex(), root); QCOMPARE(root, nodes); qDeleteAll(documents); } void FileTreeModelTest::buildTreeBatchPrefill_data() { QTest::addColumn>("prefill"); QTest::addColumn>("documents"); QTest::addColumn("nodes"); QTest::newRow("easy") << ( QList() << new DummyDocument("file:///a/foo.txt") ) << ( QList() << new DummyDocument("file:///a/bar.txt") ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt") << ResultNode("bar.txt")) ); QTest::newRow("split") << ( QList() << new DummyDocument("file:///a/foo.txt") ) << ( QList() << new DummyDocument("file:///b/foo.txt") ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt")) << (ResultNode("b", true) << ResultNode("foo.txt")) ); } void FileTreeModelTest::buildTreeBatchPrefill() { KateFileTreeModel m(this); QFETCH(QList, prefill); QFETCH(QList, documents); QFETCH(ResultNode, nodes); foreach(DummyDocument *doc, prefill) { m.documentOpened(doc); } QList list; foreach(DummyDocument *doc, documents) { list << doc; } m.documentsOpened(list); ResultNode root; walkTree(m, QModelIndex(), root); QCOMPARE(root, nodes); qDeleteAll(prefill); qDeleteAll(documents); } void FileTreeModelTest::walkTree(KateFileTreeModel &model, const QModelIndex &rootIndex, ResultNode &rootNode) { if (!model.hasChildren(rootIndex)) { return; } const int rows = model.rowCount(rootIndex); for (int i = 0; i < rows; i++) { const QModelIndex idx = model.index(i, 0, rootIndex); ResultNode node(model.data(idx).toString(), model.isDir(idx)); walkTree(model, idx, node); rootNode << node; } } void FileTreeModelTest::buildTreeFullPath_data() { QTest::addColumn>("documents"); QTest::addColumn("nodes"); QTest::newRow("two") << ( QList() << new DummyDocument("file:///a/foo.txt") << new DummyDocument("file:///a/bar.txt") ) << ( ResultNode() << (ResultNode("/a", true) << ResultNode("foo.txt") << ResultNode("bar.txt")) ); QTest::newRow("multiples") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") << new DummyDocument("file:///c/a/bar.txt") ) << ( ResultNode() << (ResultNode("/c/a", true) << ResultNode("foo.txt") << ResultNode("bar.txt")) << (ResultNode("/c/b", true) << ResultNode("bar.txt")) ); /* This one and the case after can get a little bit tricky and * in some situation could end up in little bit confusing layout. * current root merge algorithm sees the divergent paths, so it * doesn't invoke merging. QTest::newRow("branches") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") ) << ( ResultNode() << (ResultNode("/c", true) << (ResultNode("a", true) << ResultNode("foo.txt")) << (ResultNode("b", true) << ResultNode("bar.txt"))) ); */ /* QTest::newRow("levels") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") << new DummyDocument("file:///d/foo.txt") ) << ( ResultNode() << (ResultNode("/c", true) << (ResultNode("a", true) << ResultNode("foo.txt")) << (ResultNode("b", true) << ResultNode("bar.txt"))) << (ResultNode("/d", true) << ResultNode("foo.txt")) ); */ QTest::newRow("remote simple") << ( QList() << new DummyDocument("http://example.org/foo.txt") ) << ( ResultNode() << (ResultNode("[example.org]", true) << ResultNode("foo.txt")) ); QTest::newRow("remote nested") << ( QList() << new DummyDocument("http://example.org/a/b/foo.txt") ) << ( ResultNode() << (ResultNode("[example.org]/a/b", true) << ResultNode("foo.txt")) ); /* NOTE: see the similar testcase in buildTree */ QTest::newRow("remote diverge") << ( QList() << new DummyDocument("http://example.org/c/a/foo.txt") << new DummyDocument("http://example.org/c/b/foo.txt") ) << ( ResultNode() << (ResultNode("[example.org]/c/a", true) << ResultNode("foo.txt")) << (ResultNode("[example.org]/c/b", true) << ResultNode("foo.txt")) ); } void FileTreeModelTest::buildTreeFullPath() { KateFileTreeModel m(this); m.setShowFullPathOnRoots(true); QFETCH(QList, documents); QFETCH(ResultNode, nodes); foreach(DummyDocument *doc, documents) { m.documentOpened(doc); } ResultNode root; walkTree(m, QModelIndex(), root); QCOMPARE(root, nodes); qDeleteAll(documents); } void FileTreeModelTest::listMode_data() { QTest::addColumn>("documents"); QTest::addColumn("nodes"); QTest::newRow("easy") << ( QList() << new DummyDocument("file:///a/foo.txt") ) << ( ResultNode() << ResultNode("foo.txt") ); QTest::newRow("two") << ( QList() << new DummyDocument("file:///a/foo.txt") << new DummyDocument("file:///a/bar.txt") ) << ( ResultNode() << ResultNode("foo.txt") << ResultNode("bar.txt") ); QTest::newRow("multiples") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") << new DummyDocument("file:///c/a/bar.txt") ) << ( ResultNode() << ResultNode("foo.txt") << ResultNode("bar.txt") << ResultNode("bar.txt") ); QTest::newRow("remote diverge") << ( QList() << new DummyDocument("http://example.org/a/foo.txt") << new DummyDocument("http://example.org/b/foo.txt") ) << ( ResultNode() << ResultNode("[example.org]foo.txt") << ResultNode("[example.org]foo.txt") ); } void FileTreeModelTest::listMode() { KateFileTreeModel m(this); m.setListMode(true); QFETCH(QList, documents); QFETCH(ResultNode, nodes); foreach(DummyDocument *doc, documents) { m.documentOpened(doc); } ResultNode root; walkTree(m, QModelIndex(), root); QCOMPARE(root, nodes); qDeleteAll(documents); } void FileTreeModelTest::deleteDocument_data() { QTest::addColumn>("documents"); QTest::addColumn>("remove"); QTest::addColumn("nodes"); QTest::newRow("empty") << ( QList() << new DummyDocument("file:///a/foo.txt") ) << ( QList() << 0 ) << ( ResultNode() ); QTest::newRow("two") << ( QList() << new DummyDocument("file:///a/foo.txt") << new DummyDocument("file:///a/bar.txt") ) << ( QList() << 0 ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("bar.txt")) ); QTest::newRow("multiple") << ( QList() << new DummyDocument("file:///a/foo0.txt") << new DummyDocument("file:///a/foo1.txt") << new DummyDocument("file:///a/foo2.txt") << new DummyDocument("file:///a/foo3.txt") << new DummyDocument("file:///a/foo4.txt") << new DummyDocument("file:///a/foo5.txt") << new DummyDocument("file:///a/foo6.txt") << new DummyDocument("file:///a/foo7.txt") ) << ( QList() << 1 << 2 << 4 << 6 ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo0.txt") << ResultNode("foo3.txt") << ResultNode("foo5.txt") << ResultNode("foo7.txt")) ); QTest::newRow("strangers") << ( QList() << new DummyDocument("file:///a/foo.txt") << new DummyDocument("file:///b/bar.txt") ) << ( QList() << 1 ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt")) ); QTest::newRow("branches") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") << new DummyDocument("file:///d/a/foo.txt") ) << ( QList() << 1 ) << ( ResultNode() << (ResultNode("c", true) << (ResultNode("a", true) << ResultNode("foo.txt"))) << (ResultNode("d", true) << (ResultNode("a", true) << ResultNode("foo.txt"))) ); QTest::newRow("levels") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") << new DummyDocument("file:///d/foo.txt") ) << ( QList() << 0 ) << ( ResultNode() << (ResultNode("b", true) << ResultNode("bar.txt")) << (ResultNode("d", true) << ResultNode("foo.txt")) ); QTest::newRow("levels extra") << ( QList() << new DummyDocument("file:///c/a/foo.txt") << new DummyDocument("file:///c/b/bar.txt") << new DummyDocument("file:///d/foo.txt") ) << ( QList() << 2 ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt")) << (ResultNode("b", true) << ResultNode("bar.txt")) ); QTest::newRow("remote diverge") << ( QList() << new DummyDocument("http://example.org/a/foo.txt") << new DummyDocument("http://example.org/b/foo.txt") ) << ( QList() << 1 ) << ( ResultNode() << (ResultNode("[example.org]a", true) << ResultNode("foo.txt")) ); } void FileTreeModelTest::deleteDocument() { KateFileTreeModel m(this); QFETCH(QList, documents); QFETCH(QList, remove); QFETCH(ResultNode, nodes); foreach(DummyDocument *doc, documents) { m.documentOpened(doc); } foreach(const int &index, remove) { m.documentClosed(documents[index]); } ResultNode root; walkTree(m, QModelIndex(), root); QCOMPARE(root, nodes); qDeleteAll(documents); } void FileTreeModelTest::deleteDocumentBatch_data() { QTest::addColumn>("documents"); QTest::addColumn>("remove"); QTest::addColumn>("fail"); QTest::addColumn("nodes"); QTest::newRow("neo") << ( QList() << new DummyDocument("file:///a/foo0.txt") << new DummyDocument("file:///a/foo1.txt") << new DummyDocument("file:///a/foo2.txt") << new DummyDocument("file:///a/foo3.txt") << new DummyDocument("file:///a/foo4.txt") << new DummyDocument("file:///a/foo5.txt") << new DummyDocument("file:///a/foo6.txt") << new DummyDocument("file:///a/foo7.txt") ) << ( QList() << 1 << 2 << 4 << 6 ) << ( QList() << 2 << 4 ) << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo0.txt") << ResultNode("foo2.txt") << ResultNode("foo3.txt") << ResultNode("foo4.txt") << ResultNode("foo5.txt") << ResultNode("foo7.txt")) ); } void FileTreeModelTest::deleteDocumentBatch() { KateFileTreeModel m(this); QFETCH(QList, documents); QFETCH(QList, remove); QFETCH(QList, fail); QFETCH(ResultNode, nodes); foreach (DummyDocument *doc, documents) { m.documentOpened(doc); } QList removing; foreach (const int &index, remove) { removing << documents[index]; } m.slotAboutToDeleteDocuments(removing); foreach (const int &index, remove) { if (!fail.contains(index)) { m.documentClosed(documents[index]); } } removing.clear(); foreach (const int &index, fail) { removing << documents[index]; } m.slotDocumentsDeleted(removing); ResultNode root; walkTree(m, QModelIndex(), root); QCOMPARE(root, nodes); qDeleteAll(documents); } void FileTreeModelTest::rename_data() { QTest::addColumn>("documents"); QTest::addColumn("rename_idx"); QTest::addColumn("rename_url"); QTest::addColumn("nodes"); QTest::newRow("empty") << ( QList() << new DummyDocument() ) << 0 << QStringLiteral("file:///a/foo.txt") << ( ResultNode() << (ResultNode("a", true) << ResultNode("foo.txt")) ); QTest::newRow("moving") << ( QList() << new DummyDocument("file:///a/foo.txt") ) << 0 << QStringLiteral("file:///b/foo.txt") << ( ResultNode() << (ResultNode("b", true) << ResultNode("foo.txt")) ); QTest::newRow("splitting") << ( QList() << new DummyDocument("file:///a/foo.txt") << new DummyDocument("file:///a/bar.txt") ) << 0 << QStringLiteral("file:///b/foo.txt") << ( ResultNode() << (ResultNode("a", true) << ResultNode("bar.txt")) << (ResultNode("b", true) << ResultNode("foo.txt")) ); } void FileTreeModelTest::rename() { KateFileTreeModel m(this); QFETCH(QList, documents); QFETCH(int, rename_idx); QFETCH(QString, rename_url); QFETCH(ResultNode, nodes); foreach (DummyDocument *doc, documents) { m.documentOpened(doc); } documents[rename_idx]->setUrl(rename_url); m.documentNameChanged(documents[rename_idx]); ResultNode root; walkTree(m, QModelIndex(), root); QCOMPARE(root, nodes); qDeleteAll(documents); } // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/filetree/katefiletreeconfigpage.cpp b/addons/filetree/katefiletreeconfigpage.cpp index d9facc181..475208fa2 100644 --- a/addons/filetree/katefiletreeconfigpage.cpp +++ b/addons/filetree/katefiletreeconfigpage.cpp @@ -1,198 +1,197 @@ /* 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 "katefiletreeproxymodel.h" #include #include #include #include #include #include #include KateFileTreeConfigPage::KateFileTreeConfigPage(QWidget *parent, KateFileTreePlugin *fl) : KTextEditor::ConfigPage(parent), - m_plug(fl), - m_changed(false) + 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/katefiletreeconfigpage.h b/addons/filetree/katefiletreeconfigpage.h index 89eb06aaf..dfb5c3c75 100644 --- a/addons/filetree/katefiletreeconfigpage.h +++ b/addons/filetree/katefiletreeconfigpage.h @@ -1,66 +1,66 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001,2006 Joseph Wenninger Copyright (C) 2001, 2007 Anders Lund 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_CONFIGPAGE_H #define KATE_FILETREE_CONFIGPAGE_H #include #include class KateFileTreePlugin; class KateFileTreeConfigPage : public KTextEditor::ConfigPage { Q_OBJECT public: explicit KateFileTreeConfigPage(QWidget *parent = nullptr, KateFileTreePlugin *plug = nullptr); ~KateFileTreeConfigPage() override {} QString name() const override; QString fullName() const override; QIcon icon() const override; public Q_SLOTS: void apply() override; void defaults() override; void reset() override; //Q_SIGNALS: // void changed(); private Q_SLOTS: void slotMyChanged(); private: class QGroupBox *gbEnableShading; class KColorButton *kcbViewShade, *kcbEditShade; class QLabel *lEditShade, *lViewShade, *lSort, *lMode; class KComboBox *cmbSort, *cmbMode; class QCheckBox *cbShowFullPath; KateFileTreePlugin *m_plug; - bool m_changed; + bool m_changed = false; }; #endif /* KATE_FILETREE_CONFIGPAGE_H */ diff --git a/addons/filetree/katefiletreemodel.cpp b/addons/filetree/katefiletreemodel.cpp index e84d1a165..feeed4ce3 100644 --- a/addons/filetree/katefiletreemodel.cpp +++ b/addons/filetree/katefiletreemodel.cpp @@ -1,1337 +1,1336 @@ /* 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 "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; } foreach(const ProxyItem * item, 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); } foreach(ProxyItem * root, m_root->children()) { root->updateDisplay(); } } void KateFileTreeModel::initModel() { // add already existing documents foreach(KTextEditor::Document * doc, KTextEditor::Editor::instance()->application()->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) { foreach(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) { foreach(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) { foreach(const KTextEditor::Document * doc, docs) { connectDocument(doc); } } class EditViewCount { public: - EditViewCount(): edit(0), view(0) - {} - int edit; - int view; + EditViewCount() = default; + int edit = 0; + int view = 0; }; void KateFileTreeModel::updateBackgrounds(bool force) { if (!m_shadingEnabled && !force) { return; } QMap helper; int i = 1; foreach(ProxyItem * item, m_viewHistory) { helper[item].view = i; i++; } i = 1; foreach(ProxyItem * item, 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 { foreach(ProxyItem * item, m_root->children()) { 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; } foreach(ProxyItem * item, parent->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(); } foreach(const QString & part, parts) { current_parts.append(part); ProxyItemDir *find = findChildNode(ptr, part); if (!find) { const QString new_name = current_parts.join(QLatin1String("/")); 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) foreach(ProxyItem * root, m_root->children()) { 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; } foreach(ProxyItem * root, m_root->children()) { 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); foreach(ProxyItem * node, m_root->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 } } } // foreach 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(); foreach(ProxyItem * item, list) { QModelIndex idx = createIndex(item->row(), 0, item); dataChanged(idx, idx, QVector(1, Qt::BackgroundRole)); } } diff --git a/addons/gdbplugin/localsview.cpp b/addons/gdbplugin/localsview.cpp index 9d6434599..08ddaf48d 100644 --- a/addons/gdbplugin/localsview.cpp +++ b/addons/gdbplugin/localsview.cpp @@ -1,267 +1,267 @@ // // 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 LocalsView::LocalsView(QWidget *parent) -: QTreeWidget(parent), m_allAdded(true) +: 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(QStringLiteral(" = "), 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/localsview.h b/addons/gdbplugin/localsview.h index 4c38eb886..55e9ebb73 100644 --- a/addons/gdbplugin/localsview.h +++ b/addons/gdbplugin/localsview.h @@ -1,54 +1,54 @@ // // 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. #ifndef LOCALSVIEW_H #define LOCALSVIEW_H #include #include class LocalsView : public QTreeWidget { Q_OBJECT public: LocalsView(QWidget *parent = nullptr); ~LocalsView() override; public Q_SLOTS: // An empty value string ends the locals void addLocal(const QString &vString); void addStruct(QTreeWidgetItem *parent, const QString &vString); void addArray(QTreeWidgetItem *parent, const QString &vString); Q_SIGNALS: void localsVisible(bool visible); protected: void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; private: void createWrappedItem(QTreeWidgetItem *parent, const QString &name, const QString &value); void createWrappedItem(QTreeWidget *parent, const QString &name, const QString &value); - bool m_allAdded; + bool m_allAdded = true; QString m_local; }; #endif diff --git a/addons/kate-ctags/kate_ctags_plugin.cpp b/addons/kate-ctags/kate_ctags_plugin.cpp index 9362bf682..1c78b9e33 100644 --- a/addons/kate-ctags/kate_ctags_plugin.cpp +++ b/addons/kate-ctags/kate_ctags_plugin.cpp @@ -1,257 +1,257 @@ /* 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 K_PLUGIN_FACTORY_WITH_JSON (KateCTagsPluginFactory, "katectagsplugin.json", registerPlugin();) /******************************************************************/ KateCTagsPlugin::KateCTagsPlugin(QObject* parent, const QList&): -KTextEditor::Plugin (parent), m_view(nullptr) +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; icount(); 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; icurrentItem()) { 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; icurrentItem (); } /******************************************************************/ bool KateCTagsConfigPage::listContains(const QString &target) { for (int i=0; icount(); 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; icount(); 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 3a7f867de..3dccb849e 100644 --- a/addons/kate-ctags/kate_ctags_plugin.h +++ b/addons/kate-ctags/kate_ctags_plugin.h @@ -1,85 +1,85 @@ #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 "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; }; + int configPages() const override { return 1; } KTextEditor::ConfigPage *configPage (int number = 0, QWidget *parent = nullptr) override; void readConfig(); - KateCTagsView *m_view; + 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/katesql/sqlmanager.cpp b/addons/katesql/sqlmanager.cpp index ffb427b2b..abf814597 100644 --- a/addons/katesql/sqlmanager.cpp +++ b/addons/katesql/sqlmanager.cpp @@ -1,391 +1,390 @@ /* 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 using KWallet::Wallet; SQLManager::SQLManager(QObject *parent) : QObject(parent) , m_model(new ConnectionModel(this)) -, m_wallet(nullptr) { } 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; foreach ( const QString& groupName, connectionsGroup->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/sqlmanager.h b/addons/katesql/sqlmanager.h index a633c5259..25f6978c2 100644 --- a/addons/katesql/sqlmanager.h +++ b/addons/katesql/sqlmanager.h @@ -1,71 +1,71 @@ /* 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 SQLMANAGER_H #define SQLMANAGER_H class ConnectionModel; class KConfigGroup; #include "connection.h" #include #include class SQLManager : public QObject { Q_OBJECT public: SQLManager(QObject *parent = nullptr); ~SQLManager() override; ConnectionModel *connectionModel(); void createConnection(const Connection &conn); bool testConnection(const Connection &conn, QSqlError &error); bool isValidAndOpen(const QString &connection); KWallet::Wallet * openWallet(); int storeCredentials(const Connection &conn); int readCredentials(const QString &name, QString &password); public Q_SLOTS: void removeConnection(const QString &name); void reopenConnection(const QString &name); void loadConnections(KConfigGroup *connectionsGroup); void saveConnections(KConfigGroup *connectionsGroup); void runQuery(const QString &text, const QString &connection ); protected: void saveConnection(KConfigGroup *connectionsGroup, const Connection &conn); Q_SIGNALS: void connectionCreated(const QString &name); void connectionRemoved(const QString &name); void connectionAboutToBeClosed(const QString &name); void queryActivated(QSqlQuery &query, const QString &connection); void error(const QString &message); void success(const QString &message); private: ConnectionModel *m_model; - KWallet::Wallet *m_wallet; + KWallet::Wallet *m_wallet = nullptr; }; #endif // SQLMANAGER_H diff --git a/addons/lspclient/lspclientservermanager.cpp b/addons/lspclient/lspclientservermanager.cpp index 29118dbb3..80fc537c7 100644 --- a/addons/lspclient/lspclientservermanager.cpp +++ b/addons/lspclient/lspclientservermanager.cpp @@ -1,808 +1,807 @@ /* 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 // 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) { // special cases static QHash m; auto it = m.find(mode); if (it != m.end()) { return *it; } // assume sane naming QString result = mode.toLower(); result = result.replace(QStringLiteral("++"), QStringLiteral("pp")); return result.replace(QStringLiteral("#"), QStringLiteral("sharp")); } // 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)), - m_revision(-1) + 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() { // 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); } virtual qint64 revision(KTextEditor::Document *doc) override { auto it = m_docs.find(doc); return it != m_docs.end() ? it->version : -1; } virtual 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, 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.size() == 0) ? 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/project/kateprojectcodeanalysistool.cpp b/addons/project/kateprojectcodeanalysistool.cpp index cc58d4c41..08f7d2dd8 100644 --- a/addons/project/kateprojectcodeanalysistool.cpp +++ b/addons/project/kateprojectcodeanalysistool.cpp @@ -1,51 +1,50 @@ /* 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 "kateprojectcodeanalysistool.h" KateProjectCodeAnalysisTool::KateProjectCodeAnalysisTool(QObject *parent) : QObject(parent) - , m_project(nullptr) { } KateProjectCodeAnalysisTool::~KateProjectCodeAnalysisTool() { } void KateProjectCodeAnalysisTool::setProject(KateProject *project) { m_project = project; } bool KateProjectCodeAnalysisTool::isSuccessfulExitCode(int exitCode) const { return exitCode == 0; } int KateProjectCodeAnalysisTool::getActualFilesCount() const { return m_filesCount; } void KateProjectCodeAnalysisTool::setActualFilesCount(int count) { m_filesCount = count; } diff --git a/addons/project/kateprojectcodeanalysistool.h b/addons/project/kateprojectcodeanalysistool.h index f1e235ba0..6e436ac8a 100644 --- a/addons/project/kateprojectcodeanalysistool.h +++ b/addons/project/kateprojectcodeanalysistool.h @@ -1,138 +1,138 @@ /* 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. */ #ifndef KATE_PROJECT_CODE_ANALYSIS_TOOL_H #define KATE_PROJECT_CODE_ANALYSIS_TOOL_H #include #include #include "kateproject.h" /** * Information provider for a code analysis tool */ class KateProjectCodeAnalysisTool: public QObject { Q_OBJECT protected: explicit KateProjectCodeAnalysisTool(QObject *parent = nullptr); /** * Current project */ - KateProject *m_project; + KateProject *m_project = nullptr; public: virtual ~KateProjectCodeAnalysisTool(); /** * bind to this project * @param project project this tool will analyze */ virtual void setProject(KateProject *project); /** * @return tool descriptive name */ virtual QString name() const = 0; /** * @return tool short description */ virtual QString description() const = 0; /** * @returns a string containing the file extensions this * tool should be run, separated by '|', * e.g. "cpp|cxx" * NOTE that this is used directly as part of a regular expression. * If more flexibility is required this method probably will change */ virtual QString fileExtensions() const = 0; /** * filter relevant files * @param files set of files in project * @return relevant files that can be analyzed */ virtual QStringList filter(const QStringList &files) const = 0; /** * @return tool path */ virtual QString path() const = 0; /** * @return arguments required for the tool * NOTE that this method is not const because here setActualFilesCount might be called */ virtual QStringList arguments() = 0; /** * @return warning message when the tool is not installed */ virtual QString notInstalledMessage() const = 0; /** * parse output line * @param line * @return file, line, severity, message */ virtual QStringList parseLine(const QString &line) const = 0; /** * Tells the tool runner if the returned process exit code * was a successful one. * * The default implementation returns true on exitCode 0. * * Override this method for a tool that use a non-zero exit code * e.g. if the processing itself was successful but not all files * had no linter errors. */ virtual bool isSuccessfulExitCode(int exitCode) const; /** * @return messages passed to the tool through stdin * This is used when the files are not passed as arguments to the tool. * * NOTE that this method is not const because here setActualFilesCount might be called */ virtual QString stdinMessages() = 0; /** * @returns the number of files to be processed after the filter * has been applied */ int getActualFilesCount() const; /** * To be called by derived classes */ void setActualFilesCount(int count); private: int m_filesCount = 0; }; Q_DECLARE_METATYPE(KateProjectCodeAnalysisTool*) #endif // KATE_PROJECT_CODE_ANALYSIS_TOOL_H diff --git a/addons/project/kateprojectconfigpage.cpp b/addons/project/kateprojectconfigpage.cpp index 37e48a002..b8b090547 100644 --- a/addons/project/kateprojectconfigpage.cpp +++ b/addons/project/kateprojectconfigpage.cpp @@ -1,109 +1,108 @@ /* 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 KateProjectConfigPage::KateProjectConfigPage(QWidget *parent, KateProjectPlugin *plugin) : KTextEditor::ConfigPage(parent) , m_plugin(plugin) - , m_changed(false) { 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); 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); } 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); } 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_changed = false; } void KateProjectConfigPage::defaults() { reset(); } void KateProjectConfigPage::slotMyChanged() { m_changed = true; emit changed(); } diff --git a/addons/project/kateprojectconfigpage.h b/addons/project/kateprojectconfigpage.h index 4d8521f4e..6798e4cd8 100644 --- a/addons/project/kateprojectconfigpage.h +++ b/addons/project/kateprojectconfigpage.h @@ -1,55 +1,55 @@ /* 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. */ #ifndef KATE_PROJECT_CONFIGPAGE_H #define KATE_PROJECT_CONFIGPAGE_H #include class KateProjectPlugin; class QWidget; class QCheckBox; class KateProjectConfigPage : public KTextEditor::ConfigPage { Q_OBJECT public: explicit KateProjectConfigPage(QWidget *parent = nullptr, KateProjectPlugin *plugin = nullptr); ~KateProjectConfigPage() override {} QString name() const override; QString fullName() const override; QIcon icon() const override; public Q_SLOTS: void apply() override; void defaults() override; void reset() override; private Q_SLOTS: void slotMyChanged(); private: QCheckBox *m_cbAutoGit; QCheckBox *m_cbAutoSubversion; QCheckBox *m_cbAutoMercurial; KateProjectPlugin *m_plugin; - bool m_changed; + bool m_changed = false; }; #endif /* KATE_PROJECT_CONFIGPAGE_H */ diff --git a/addons/rustcompletion/kterustcompletion.h b/addons/rustcompletion/kterustcompletion.h index d75a5ba11..88d26831e 100644 --- a/addons/rustcompletion/kterustcompletion.h +++ b/addons/rustcompletion/kterustcompletion.h @@ -1,81 +1,81 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * * * 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 KTERUSTCOMPLETION_H #define KTERUSTCOMPLETION_H #include #include #include #include class KTERustCompletionPlugin; namespace KTextEditor { class Document; class View; } struct CompletionMatch { - CompletionMatch() : type(KTextEditor::CodeCompletionModel::NoProperty), depth(0), line(-1), col(-1) {} + CompletionMatch() = default; QString text; QIcon icon; - KTextEditor::CodeCompletionModel::CompletionProperty type; - int depth; + KTextEditor::CodeCompletionModel::CompletionProperty type = KTextEditor::CodeCompletionModel::NoProperty; + int depth = 0; QUrl url; - int line; - int col; + int line = -1; + int col = -1; }; class KTERustCompletion : public KTextEditor::CodeCompletionModel, public KTextEditor::CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: KTERustCompletion(KTERustCompletionPlugin *plugin); ~KTERustCompletion() override; enum MatchAction { Complete = 0, FindDefinition }; bool shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) override; void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType) override; void aborted(KTextEditor::View *view) override; QVariant data(const QModelIndex &index, int role) const override; QList getMatches(const KTextEditor::Document *document, MatchAction action, const KTextEditor::Cursor &position); private: static void addType(CompletionMatch &match, const QString &type); QList m_matches; KTERustCompletionPlugin *m_plugin; }; #endif diff --git a/addons/rustcompletion/kterustcompletionconfigpage.cpp b/addons/rustcompletion/kterustcompletionconfigpage.cpp index 75e9ad316..4726a2fbc 100644 --- a/addons/rustcompletion/kterustcompletionconfigpage.cpp +++ b/addons/rustcompletion/kterustcompletionconfigpage.cpp @@ -1,106 +1,105 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * * * 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 "kterustcompletionconfigpage.h" #include "kterustcompletionplugin.h" #include #include #include #include #include KTERustCompletionConfigPage::KTERustCompletionConfigPage(QWidget *parent, KTERustCompletionPlugin *plugin) : KTextEditor::ConfigPage(parent) - , m_changed(false) , m_plugin(plugin) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); QVBoxLayout *vbox = new QVBoxLayout; QGroupBox *group = new QGroupBox(i18n("Racer command"), this); m_racerCmd = new QLineEdit(this); vbox->addWidget(m_racerCmd); group->setLayout(vbox); layout->addWidget(group); vbox = new QVBoxLayout; group = new QGroupBox(i18n("Rust source tree location"), this); m_rustSrcPath = new KUrlRequester(this); m_rustSrcPath->setMode(KFile::Directory | KFile::LocalOnly); vbox->addWidget(m_rustSrcPath); group->setLayout(vbox); layout->addWidget(group); layout->insertStretch(-1, 10); reset(); connect(m_racerCmd, &QLineEdit::textChanged, this, &KTERustCompletionConfigPage::changedInternal); connect(m_rustSrcPath, &KUrlRequester::textChanged, this, &KTERustCompletionConfigPage::changedInternal); connect(m_rustSrcPath, &KUrlRequester::urlSelected, this, &KTERustCompletionConfigPage::changedInternal); } QString KTERustCompletionConfigPage::name() const { return QString(i18n("Rust code completion")); } QString KTERustCompletionConfigPage::fullName() const { return QString(i18n("Rust code completion")); } QIcon KTERustCompletionConfigPage::icon() const { return QIcon::fromTheme(QLatin1String("text-field")); } void KTERustCompletionConfigPage::apply() { if (!m_changed) { return; } m_changed = false; m_plugin->setRacerCmd(m_racerCmd->text()); m_plugin->setRustSrcPath(m_rustSrcPath->url()); } void KTERustCompletionConfigPage::reset() { m_racerCmd->setText(m_plugin->racerCmd()); m_rustSrcPath->setUrl(m_plugin->rustSrcPath()); m_changed = false; } void KTERustCompletionConfigPage::defaults() { reset(); } void KTERustCompletionConfigPage::changedInternal() { m_changed = true; emit changed(); } diff --git a/addons/rustcompletion/kterustcompletionconfigpage.h b/addons/rustcompletion/kterustcompletionconfigpage.h index afc79a0c4..d0d8a2a91 100644 --- a/addons/rustcompletion/kterustcompletionconfigpage.h +++ b/addons/rustcompletion/kterustcompletionconfigpage.h @@ -1,60 +1,60 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * * * 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 KTERUSTCOMPLETIONCONFIGPAGE_H #define KTERUSTCOMPLETIONCONFIGPAGE_H #include class KTERustCompletionPlugin; class QLineEdit; class KUrlRequester; class KTERustCompletionConfigPage : public KTextEditor::ConfigPage { Q_OBJECT public: explicit KTERustCompletionConfigPage(QWidget *parent = nullptr, KTERustCompletionPlugin *plugin = nullptr); ~KTERustCompletionConfigPage() override {}; QString name() const override; QString fullName() const override; QIcon icon() const override; public Q_SLOTS: void apply() override; void defaults() override; void reset() override; private Q_SLOTS: void changedInternal(); private: QLineEdit *m_racerCmd; KUrlRequester *m_rustSrcPath; - bool m_changed; + bool m_changed = false; KTERustCompletionPlugin *m_plugin; }; #endif diff --git a/addons/rustcompletion/kterustcompletionplugin.cpp b/addons/rustcompletion/kterustcompletionplugin.cpp index 32eef5a9f..b6215254a 100644 --- a/addons/rustcompletion/kterustcompletionplugin.cpp +++ b/addons/rustcompletion/kterustcompletionplugin.cpp @@ -1,147 +1,145 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * * * 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 "kterustcompletionplugin.h" #include "kterustcompletionpluginview.h" #include "kterustcompletionconfigpage.h" #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KTERustCompletionPluginFactory, "kterustcompletionplugin.json", registerPlugin();) KTERustCompletionPlugin::KTERustCompletionPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent), - m_completion(this), - m_rustSrcWatch(nullptr), - m_configOk(false) + m_completion(this) { readConfig(); } KTERustCompletionPlugin::~KTERustCompletionPlugin() { } QObject *KTERustCompletionPlugin::createView(KTextEditor::MainWindow *mainWindow) { return new KTERustCompletionPluginView(this, mainWindow); } int KTERustCompletionPlugin::configPages() const { return 1; } KTextEditor::ConfigPage *KTERustCompletionPlugin::configPage(int number, QWidget *parent) { if (number != 0) { return nullptr; } return new KTERustCompletionConfigPage(parent, this); } KTERustCompletion *KTERustCompletionPlugin::completion() { return &m_completion; } QString KTERustCompletionPlugin::racerCmd() const { return m_racerCmd; } void KTERustCompletionPlugin::setRacerCmd(const QString &cmd) { if (cmd != m_racerCmd) { m_racerCmd = cmd; writeConfig(); updateConfigOk(); } } QUrl KTERustCompletionPlugin::rustSrcPath() const { return m_rustSrcPath; } void KTERustCompletionPlugin::setRustSrcPath(const QUrl &path) { if (path != m_rustSrcPath) { m_rustSrcPath = path; writeConfig(); updateConfigOk(); } } bool KTERustCompletionPlugin::configOk() const { return m_configOk; } void KTERustCompletionPlugin::updateConfigOk() { m_configOk = false; if (m_rustSrcPath.isLocalFile()) { QString path = m_rustSrcPath.toLocalFile(); if (QDir(path).exists()) { m_configOk = true; if (m_rustSrcWatch && !m_rustSrcWatch->contains(path)) { delete m_rustSrcWatch; m_rustSrcWatch = nullptr; } if (!m_rustSrcWatch) { m_rustSrcWatch = new KDirWatch(this); m_rustSrcWatch->addDir(path, KDirWatch::WatchDirOnly); connect(m_rustSrcWatch, &KDirWatch::deleted, this, &KTERustCompletionPlugin::updateConfigOk, Qt::UniqueConnection); } } } } void KTERustCompletionPlugin::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("kterustcompletion")); m_racerCmd = config.readEntry(QStringLiteral("racerCmd"), QStringLiteral("racer")); m_rustSrcPath = config.readEntry(QStringLiteral("rustSrcPath"), QUrl(QStringLiteral("/usr/local/src/rust/src"))); updateConfigOk(); } void KTERustCompletionPlugin::writeConfig() { KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("kterustcompletion")); config.writeEntry(QStringLiteral("racerCmd"), m_racerCmd); config.writeEntry(QStringLiteral("rustSrcPath"), m_rustSrcPath); } #include "kterustcompletionplugin.moc" diff --git a/addons/rustcompletion/kterustcompletionplugin.h b/addons/rustcompletion/kterustcompletionplugin.h index 7ca09a0f0..fa6edf040 100644 --- a/addons/rustcompletion/kterustcompletionplugin.h +++ b/addons/rustcompletion/kterustcompletionplugin.h @@ -1,71 +1,71 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * * * 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 KTERUSTCOMPLETIONPLUGIN_H #define KTERUSTCOMPLETIONPLUGIN_H #include "kterustcompletion.h" #include #include #include class KDirWatch; class KTERustCompletionPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit KTERustCompletionPlugin(QObject *parent = nullptr, const QList & = QList()); ~KTERustCompletionPlugin() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; int configPages() const override; KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; KTERustCompletion *completion(); QString racerCmd() const; void setRacerCmd(const QString &cmd); QUrl rustSrcPath() const; void setRustSrcPath(const QUrl &path); bool configOk() const; private Q_SLOTS: void updateConfigOk(); private: void readConfig(); void writeConfig(); KTERustCompletion m_completion; QString m_racerCmd; QUrl m_rustSrcPath; - KDirWatch *m_rustSrcWatch; + KDirWatch *m_rustSrcWatch{nullptr}; - bool m_configOk; + bool m_configOk{false}; }; #endif diff --git a/addons/search/SearchDiskFiles.cpp b/addons/search/SearchDiskFiles.cpp index 0b6a7b19e..f490adb11 100644 --- a/addons/search/SearchDiskFiles.cpp +++ b/addons/search/SearchDiskFiles.cpp @@ -1,188 +1,186 @@ /* 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 SearchDiskFiles::SearchDiskFiles(QObject *parent) : QThread(parent) -,m_cancelSearch(true) -,m_matchCount(0) {} SearchDiskFiles::~SearchDiskFiles() { m_cancelSearch = true; wait(); } void SearchDiskFiles::startSearch(const QStringList &files, const QRegularExpression ®exp) { if (files.size() == 0) { emit searchDone(); return; } m_cancelSearch = false; m_files = files; m_regExp = regexp; m_matchCount = 0; m_statusTime.restart(); start(); } void SearchDiskFiles::run() { foreach (QString fileName, m_files) { if (m_cancelSearch) { break; } if (m_statusTime.elapsed() > 100) { m_statusTime.restart(); emit searching(fileName); } if (m_regExp.pattern().contains(QStringLiteral("\\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 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 94d28e8bc..75bcb3e96 100644 --- a/addons/search/SearchDiskFiles.h +++ b/addons/search/SearchDiskFiles.h @@ -1,69 +1,69 @@ /* 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 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; - int m_matchCount; + bool m_cancelSearch = true; + int m_matchCount = 0; QTime m_statusTime; }; #endif diff --git a/addons/search/plugin_search.cpp b/addons/search/plugin_search.cpp index 82bd777fd..93984211e 100644 --- a/addons/search/plugin_search.cpp +++ b/addons/search/plugin_search.cpp @@ -1,2304 +1,2303 @@ /* 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 "kacceleratormanager.h" #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()); 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; } 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), matches(0), useRegExp(false), searchPlaceIndex(0) +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(nullptr) + : 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() == QStringLiteral("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() == QStringLiteral("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.useRegExp, &QToolButton::toggled, &m_changeTimer, static_cast(&QTimer::start)); connect(m_ui.useRegExp, &QToolButton::toggled, this, [=]{ Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (res) { res->useRegExp = m_ui.useRegExp->isChecked(); } }); m_changeTimer.setInterval(300); m_changeTimer.setSingleShot(true); connect(&m_changeTimer, &QTimer::timeout, this, &KatePluginSearchView::startSearchWhileTyping); 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); 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); 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 == QStringLiteral("*"))) && (excludes.isEmpty())) { // shortcut for use all files return files; } QStringList tmpTypes = types.split(QLatin1Char(',')); QVector typeList(tmpTypes.size()); for (int i=0; i excludeList(tmpExcludes.size()); for (int i=0; i openList; for (int i=0; idocuments().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.size() > 0) { 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::ItemIsTristate); 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.replace(m_resultBaseDir, QString()); 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; ichildCount(); 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::ItemIsTristate); 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(QStringLiteral("(?=\\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() { foreach (KTextEditor::Document* doc, m_kateApp->documents()) { 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 (idocument() == 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; for (int i=0; idocuments().size(); i++) { int index = files.indexOf(m_kateApp->documents()[i]->url().toString()); if (index != -1) { openList << m_kateApp->documents()[i]; 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.size() > 0) { 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::ItemIsTristate); // 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; idocument() != 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; ichildCount(); 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; ichildCount(); 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; ichildCount(); 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 > 1) { 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; icount(); i++) { searchHistoy << m_ui.searchCombo->itemText(i); } cg.writeEntry("Search", searchHistoy); QStringList replaceHistoy; for (int i=1; icount(); 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; icomboBox()->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; icount(), 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; icount(), 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"))); actionPointers << menuEntry(menu, QStringLiteral("^"), QStringLiteral(""), i18n("Beginning of line")); actionPointers << menuEntry(menu, QStringLiteral("$"), QStringLiteral(""), i18n("End of line")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("."), QStringLiteral(""), i18n("Any single character (excluding line breaks)")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("+"), QStringLiteral(""), i18n("One or more occurrences")); actionPointers << menuEntry(menu, QStringLiteral("*"), QStringLiteral(""), i18n("Zero or more occurrences")); actionPointers << menuEntry(menu, QStringLiteral("?"), QStringLiteral(""), 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("|"), QStringLiteral(""), 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"), QStringLiteral(""), i18n("Line break")); actionPointers << menuEntry(menu, QStringLiteral("\\t"), QStringLiteral(""), i18n("Tab")); actionPointers << menuEntry(menu, QStringLiteral("\\b"), QStringLiteral(""), i18n("Word boundary")); actionPointers << menuEntry(menu, QStringLiteral("\\B"), QStringLiteral(""), i18n("Not word boundary")); actionPointers << menuEntry(menu, QStringLiteral("\\d"), QStringLiteral(""), i18n("Digit")); actionPointers << menuEntry(menu, QStringLiteral("\\D"), QStringLiteral(""), i18n("Non-digit")); actionPointers << menuEntry(menu, QStringLiteral("\\s"), QStringLiteral(""), i18n("Whitespace (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("\\S"), QStringLiteral(""), i18n("Non-whitespace (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("\\w"), QStringLiteral(""), i18n("Word character (alphanumerics plus '_')")); actionPointers << menuEntry(menu, QStringLiteral("\\W"), QStringLiteral(""), i18n("Non-word character")); } // Show menu QAction * const result = contextMenu->exec(m_ui.searchCombo->mapToGlobal(pos)); // Act on action if (result && actionPointers.contains(result)) { QLineEdit * lineEdit = m_ui.searchCombo->lineEdit(); const int cursorPos = lineEdit->cursorPosition(); QStringList beforeAfter = result->data().toString().split(QLatin1Char(' ')); if (beforeAfter.size() != 2) return; lineEdit->insert(beforeAfter[0] + beforeAfter[1]); lineEdit->setCursorPosition(cursorPos + beforeAfter[0].count()); lineEdit->setFocus(); } } 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; actionPointers << menuEntry(menu, QStringLiteral("\\n"), QStringLiteral(""), i18n("Line break")); actionPointers << menuEntry(menu, QStringLiteral("\\t"), QStringLiteral(""), i18n("Tab")); if (m_ui.useRegExp->isChecked()) { menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("\\0"), QStringLiteral(""), i18n("Regular expression capture 0 (whole match)")); actionPointers << menuEntry(menu, QStringLiteral("\\"), QStringLiteral(""), 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\\"), QStringLiteral(""), 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\\"), QStringLiteral(""), i18n("Lower-cased capture 0-9"), QStringLiteral("\\L\\#")); actionPointers << menuEntry(menu, QStringLiteral("\\L\\{"), QStringLiteral("}"), i18n("Lower-cased capture 0-999"), QStringLiteral("\\L\\{###")); } // Show menu QAction * const result = contextMenu->exec(m_ui.replaceCombo->mapToGlobal(pos)); // Act on action if (result && actionPointers.contains(result)) { QLineEdit * lineEdit = m_ui.replaceCombo->lineEdit(); const int cursorPos = lineEdit->cursorPosition(); QStringList beforeAfter = result->data().toString().split(QLatin1Char(' ')); if (beforeAfter.size() != 2) return; lineEdit->insert(beforeAfter[0] + beforeAfter[1]); lineEdit->setCursorPosition(cursorPos + beforeAfter[0].count()); lineEdit->setFocus(); } } void KatePluginSearchView::slotPluginViewCreated(const QString &name, QObject *pluginView) { // add view if (pluginView && name == QStringLiteral("kateprojectplugin")) { m_projectPluginView = pluginView; slotProjectFileNameChanged(); connect (pluginView, SIGNAL(projectFileNameChanged()), this, SLOT(slotProjectFileNameChanged())); } } void KatePluginSearchView::slotPluginViewDeleted(const QString &name, QObject *) { // remove view if (name == QStringLiteral("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 == QStringLiteral("grep") || command == QStringLiteral("newGrep")) { emit setSearchPlace(KatePluginSearchView::Folder); emit setCurrentFolder(); if (command == QStringLiteral("newGrep")) emit newTab(); } else if (command == QStringLiteral("search") || command == QStringLiteral("newSearch")) { emit setSearchPlace(KatePluginSearchView::OpenFiles); if (command == QStringLiteral("newSearch")) emit newTab(); } else if (command == QStringLiteral("pgrep") || command == QStringLiteral("newPGrep")) { emit setSearchPlace(KatePluginSearchView::Project); if (command == QStringLiteral("newPGrep")) emit newTab(); } emit setSearchString(searchText); emit startSearch(); return true; } bool KateSearchCommand::help(KTextEditor::View */*view*/, const QString &cmd, QString & msg) { if (cmd.startsWith(QStringLiteral("grep"))) { msg = i18n("Usage: grep [pattern to search for in folder]"); } else if (cmd.startsWith(QStringLiteral("newGrep"))) { msg = i18n("Usage: newGrep [pattern to search for in folder]"); } else if (cmd.startsWith(QStringLiteral("search"))) { msg = i18n("Usage: search [pattern to search for in open files]"); } else if (cmd.startsWith(QStringLiteral("newSearch"))) { msg = i18n("Usage: search [pattern to search for in open files]"); } else if (cmd.startsWith(QStringLiteral("pgrep"))) { msg = i18n("Usage: pgrep [pattern to search for in current project]"); } else if (cmd.startsWith(QStringLiteral("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 15035bd22..578163938 100644 --- a/addons/search/plugin_search.h +++ b/addons/search/plugin_search.h @@ -1,247 +1,247 @@ /* 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 "ui_search.h" #include "ui_results.h" #include "search_open_files.h" #include "SearchDiskFiles.h" #include "FolderFilesList.h" #include "replace_matches.h" class KateSearchCommand; namespace KTextEditor{ class MovingRange; } class Results: public QWidget, public Ui::Results { Q_OBJECT public: Results(QWidget *parent = nullptr); - int matches; + int matches = 0; QRegularExpression regExp; - bool useRegExp; + bool useRegExp = false; bool matchCase; QString replaceStr; - int searchPlaceIndex; + 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; + 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/search_open_files.cpp b/addons/search/search_open_files.cpp index 388c9ec77..c09f10644 100644 --- a/addons/search/search_open_files.cpp +++ b/addons/search/search_open_files.cpp @@ -1,192 +1,192 @@ /* 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 "search_open_files.h" #include -SearchOpenFiles::SearchOpenFiles(QObject *parent) : QObject(parent), m_nextIndex(-1), m_cancelSearch(true) +SearchOpenFiles::SearchOpenFiles(QObject *parent) : QObject(parent) { connect(this, &SearchOpenFiles::searchNextFile, this, &SearchOpenFiles::doSearchNextFile, Qt::QueuedConnection); } bool SearchOpenFiles::searching() { return !m_cancelSearch; } void SearchOpenFiles::startSearch(const QList &list, const QRegularExpression ®exp) { if (m_nextIndex != -1) return; m_docList = list; m_nextIndex = 0; m_regExp = regexp; m_cancelSearch = false; m_statusTime.restart(); emit searchNextFile(0); } void SearchOpenFiles::cancelSearch() { m_cancelSearch = true; } void SearchOpenFiles::doSearchNextFile(int startLine) { if (m_cancelSearch) { m_nextIndex = -1; m_cancelSearch = true; emit searchDone(); return; } // NOTE The document managers signal documentWillBeDeleted() must be connected to // cancelSearch(). A closed file could lead to a crash if it is not handled. int line = searchOpenFile(m_docList[m_nextIndex], m_regExp, startLine); if (line == 0) { // file searched go to next m_nextIndex++; if (m_nextIndex == m_docList.size()) { m_nextIndex = -1; m_cancelSearch = true; emit searchDone(); } else { emit searchNextFile(0); } } else { emit searchNextFile(line); } } int SearchOpenFiles::searchOpenFile(KTextEditor::Document *doc, const QRegularExpression ®Exp, int startLine) { if (m_statusTime.elapsed() > 100) { m_statusTime.restart(); emit searching(doc->url().toString()); } if (regExp.pattern().contains(QStringLiteral("\\n"))) { return searchMultiLineRegExp(doc, regExp, startLine); } return searchSingleLineRegExp(doc, regExp, startLine); } int SearchOpenFiles::searchSingleLineRegExp(KTextEditor::Document *doc, const QRegularExpression ®Exp, int startLine) { int column; QTime time; time.start(); for (int line = startLine; line < doc->lines(); line++) { if (time.elapsed() > 100) { //qDebug() << "Search time exceeded" << time.elapsed() << line; return line; } QRegularExpressionMatch match; match = regExp.match(doc->line(line)); column = match.capturedStart(); while (column != -1 && !match.captured().isEmpty()) { emit matchFound(doc->url().toString(), doc->documentName(), doc->line(line), match.capturedLength(), line, column, line, column+match.capturedLength()); match = regExp.match(doc->line(line), column + match.capturedLength()); column = match.capturedStart(); } } return 0; } int SearchOpenFiles::searchMultiLineRegExp(KTextEditor::Document *doc, const QRegularExpression ®Exp, int inStartLine) { int column = 0; int startLine = 0; QTime time; time.start(); QRegularExpression tmpRegExp = regExp; if (inStartLine == 0) { // Copy the whole file to a temporary buffer to be able to search newlines m_fullDoc.clear(); m_lineStart.clear(); m_lineStart << 0; for (int i=0; ilines(); i++) { m_fullDoc += doc->line(i) + QLatin1Char('\n'); m_lineStart << m_fullDoc.size(); } if (!regExp.pattern().endsWith(QStringLiteral("$"))) { // if regExp ends with '$' leave the extra newline at the end as // '$' will be replaced with (?=\\n), which needs the extra newline m_fullDoc.remove(m_fullDoc.size()-1, 1); } } else { if (inStartLine>0 && inStartLine column) { startLine = i-1; break; } } if (startLine == -1) { break; } int startColumn = (column - m_lineStart[startLine]); int endLine = startLine + 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(doc->url().toString(), doc->documentName(), doc->line(startLine).left(column - m_lineStart[startLine])+match.captured(), match.capturedLength(), startLine, startColumn, endLine, endColumn); match = tmpRegExp.match(m_fullDoc, column + match.capturedLength()); column = match.capturedStart(); if (time.elapsed() > 100) { //qDebug() << "Search time exceeded" << time.elapsed() << line; return startLine; } } return 0; } diff --git a/addons/search/search_open_files.h b/addons/search/search_open_files.h index 0d11204cf..a1c2b3628 100644 --- a/addons/search/search_open_files.h +++ b/addons/search/search_open_files.h @@ -1,70 +1,70 @@ /* 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 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; + int m_nextIndex = -1; QRegularExpression m_regExp; - bool m_cancelSearch; + bool m_cancelSearch = true; QString m_fullDoc; QVector m_lineStart; QTime m_statusTime; }; #endif diff --git a/addons/snippets/snippet.cpp b/addons/snippets/snippet.cpp index b3e8e511c..557d67198 100644 --- a/addons/snippets/snippet.cpp +++ b/addons/snippets/snippet.cpp @@ -1,94 +1,94 @@ /* 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/mainwindow.h" #include #include #include #include Snippet::Snippet() - : QStandardItem(i18n("")), m_action(nullptr) + : 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/snippet.h b/addons/snippets/snippet.h index 874200737..dad5e892d 100644 --- a/addons/snippets/snippet.h +++ b/addons/snippets/snippet.h @@ -1,77 +1,77 @@ /* 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 __SNIPPET_H__ #define __SNIPPET_H__ #include class QAction; /** * One object of this class represents a single snippet. * Multiple snippets are stored in one repository (XML-file). * * To access the snippet's name (which should also be used for matching * during code completion) use @p QStandardItem::text(). * * @author Robert Gruber * @author Milian Wolff */ class Snippet : public QStandardItem { public: /** * Construct an empty snippet. */ Snippet(); ~Snippet() override; /** * Returns the actual contents of this snippet. */ QString snippet() const; /** * Sets the actual contents of this snippet. */ void setSnippet(const QString& snippet); /** * Action to trigger insertion of this snippet. */ QAction* action(); void registerActionForView(QWidget* view); QVariant data(int role = Qt::UserRole + 1) const override; private: /// the actual snippet contents aka \code\endcode QString m_snippet; /// the insertion action for this snippet. - QAction* m_action; + QAction* m_action = nullptr; }; Q_DECLARE_METATYPE ( Snippet* ) #endif diff --git a/addons/textfilter/plugin_katetextfilter.cpp b/addons/textfilter/plugin_katetextfilter.cpp index 5e92dfbd6..39e52e61a 100644 --- a/addons/textfilter/plugin_katetextfilter.cpp +++ b/addons/textfilter/plugin_katetextfilter.cpp @@ -1,278 +1,275 @@ /*************************************************************************** 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 K_PLUGIN_FACTORY_WITH_JSON(TextFilterPluginFactory, "textfilterplugin.json", registerPlugin();) PluginKateTextFilter::PluginKateTextFilter(QObject *parent, const QList &): KTextEditor::Plugin(parent) - , m_pFilterProcess(Q_NULLPTR) - , copyResult(false) - , mergeOutput(false) { // 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 430682473..3990640b5 100644 --- a/addons/textfilter/plugin_katetextfilter.h +++ b/addons/textfilter/plugin_katetextfilter.h @@ -1,104 +1,104 @@ /*************************************************************************** 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 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; + KProcess * m_pFilterProcess = nullptr; QStringList completionList; - bool copyResult; - bool mergeOutput; + 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/kate/katedocmanager.h b/kate/katedocmanager.h index 41517b50a..0074179d0 100644 --- a/kate/katedocmanager.h +++ b/kate/katedocmanager.h @@ -1,219 +1,214 @@ /* 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 class KateMainWindow; class KateDocumentInfo { public: enum CustomRoles {RestoreOpeningFailedRole }; public: - KateDocumentInfo() - : modifiedOnDisc(false) - , modifiedOnDiscReason(KTextEditor::ModificationInterface::OnDiskUnmodified) - , openedByUser(false) - , openSuccess(true) - {} - - bool modifiedOnDisc; - KTextEditor::ModificationInterface::ModifiedOnDiskReason modifiedOnDiscReason; - - bool openedByUser; - bool openSuccess; + 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(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/katemainwindow.h b/kate/katemainwindow.h index c628f251e..3cd48eefd 100644 --- a/kate/katemainwindow.h +++ b/kate/katemainwindow.h @@ -1,600 +1,600 @@ /* 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 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); /** * 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(): m_bar(nullptr), m_state(false) {} + 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; - bool m_state; + 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 showPluginConfigPage(KTextEditor::Plugin *configpageinterface, uint id); 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 5a84be013..5892aefe9 100644 --- a/kate/katemdi.cpp +++ b/kate/katemdi.cpp @@ -1,1055 +1,1053 @@ /* 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 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); foreach(QAction * action, actionCollection()->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()); foreach(const QString & shortcut, shortcutString.split(QLatin1String(";"))) { 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); } 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); while (it.hasNext()) { it.next(); if ((it.value() != widget) && !it.value()->persistent) { hideWidget(it.value()); } } 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(); /** * 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_sidebarsVisible(true) - , m_restoreConfig(nullptr) , 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 82617e9fa..c3873b674 100644 --- a/kate/katemdi.h +++ b/kate/katemdi.h @@ -1,461 +1,461 @@ /* 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 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, uint 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; + bool m_sidebarsVisible = true; /** * config object for session restore, only valid between * start and finish restore calls */ - KConfigBase *m_restoreConfig; + KConfigBase *m_restoreConfig = nullptr; /** * restore group */ QString m_restoreGroup; /** * out guiclient */ GUIClient *m_guiClient; Q_SIGNALS: void sigShowPluginConfigPage(KTextEditor::Plugin*configpageinterface, uint id); }; } #endif diff --git a/kate/kateviewmanager.h b/kate/kateviewmanager.h index 955453754..8ba1047b8 100644 --- a/kate/kateviewmanager.h +++ b/kate/kateviewmanager.h @@ -1,349 +1,344 @@ /* 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 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() - : active(false) - , lruAge(0) - , activityResource(Q_NULLPTR) - { - } + ViewData() = default; /** * view active? */ - bool active; + bool active = false; /** * lru age of the view * important: smallest age ==> latest used view */ - qint64 lruAge; + qint64 lruAge = 0; /** * activity resource for the view */ - KActivities::ResourceInstance *activityResource; + 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/kwrite/kwrite.cpp b/kwrite/kwrite.cpp index 0c9591a9c..98f6bfe30 100644 --- a/kwrite/kwrite.cpp +++ b/kwrite/kwrite.cpp @@ -1,528 +1,522 @@ /* 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 #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 KWrite::KWrite(KTextEditor::Document *doc, KWriteApplication *app) - : m_view(nullptr) - , m_recentFiles(nullptr) - , m_paShowPath(nullptr) - , m_paShowMenuBar(nullptr) - , m_paShowStatusBar(nullptr) - , m_activityResource(nullptr) - , m_app(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()); Q_FOREACH(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(); foreach(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 = QStringLiteral("~") + c.right(c.length() - homePath.length()); } //File name shouldn't be too long - Maciek if (c.length() > 64) { c = QStringLiteral("...") + 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 46c4c4b02..83e0dfd07 100644 --- a/kwrite/kwrite.h +++ b/kwrite/kwrite.h @@ -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. */ #ifndef KWRITE_MAIN_H #define KWRITE_MAIN_H #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; + KTextEditor::View *m_view = nullptr; - KRecentFilesAction *m_recentFiles; - KToggleAction *m_paShowPath; - KToggleAction *m_paShowMenuBar; - KToggleAction *m_paShowStatusBar; + KRecentFilesAction *m_recentFiles = nullptr; + KToggleAction *m_paShowPath = nullptr; + KToggleAction *m_paShowMenuBar = nullptr; + KToggleAction *m_paShowStatusBar = nullptr; QAction *m_closeAction; - KActivities::ResourceInstance *m_activityResource; + 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