diff --git a/addons/close-except-like/close_confirm_dialog.cpp b/addons/close-except-like/close_confirm_dialog.cpp index db8bc6689..d2d418ef7 100644 --- a/addons/close-except-like/close_confirm_dialog.cpp +++ b/addons/close-except-like/close_confirm_dialog.cpp @@ -1,131 +1,131 @@ /** * \file * * \brief Class \c kate::CloseConfirmDialog (implementation) * * Copyright (C) 2012 Alex Turbov * * \date Sun Jun 24 16:29:13 MSK 2012 -- Initial design */ /* * KateCloseExceptPlugin is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * KateCloseExceptPlugin is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ // Project specific includes #include "close_confirm_dialog.h" // Standard includes #include #include /// \todo Where is \c i18n() defined? #include #include #include #include #include #include #include #include namespace kate { namespace { class KateDocItem : public QTreeWidgetItem { public: KateDocItem(KTextEditor::Document* doc, QTreeWidget* tw) : QTreeWidgetItem(tw) , document(doc) { setText(0, doc->documentName()); setText(1, doc->url().toString()); setCheckState(0, Qt::Checked); } KTextEditor::Document* document; }; } // anonymous namespace CloseConfirmDialog::CloseConfirmDialog( QList& docs , KToggleAction* show_confirmation_action , QWidget* const parent ) : QDialog(parent) , m_docs(docs) { assert("Documents container expected to be non empty" && !docs.isEmpty()); setupUi(this); setWindowTitle(i18nc("@title:window", "Close files confirmation")); setModal(true); buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); icon->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"),KIconLoader::Desktop,KIconLoader::SizeLarge)); text->setText( i18nc("@label:listbox", "You are about to close the following documents:") ); QStringList headers; headers << i18nc("@title:column", "Document") << i18nc("@title:column", "Location"); m_docs_tree->setHeaderLabels(headers); m_docs_tree->setSelectionMode(QAbstractItemView::SingleSelection); m_docs_tree->setRootIsDecorated(false); - for (int i = 0; i < m_docs.size(); i++) + for (auto& doc : qAsConst(m_docs)) { - new KateDocItem(m_docs[i], m_docs_tree); + new KateDocItem(doc, m_docs_tree); } m_docs_tree->header()->setStretchLastSection(false); m_docs_tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); m_docs_tree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); m_dont_ask_again->setText(i18nc("option:check", "Do not ask again")); // NOTE If we are here, it means that 'Show Confirmation' action is enabled, // so not needed to read config... assert("Sanity check" && show_confirmation_action->isChecked()); m_dont_ask_again->setCheckState(Qt::Unchecked); connect(m_dont_ask_again, &QCheckBox::toggled, show_confirmation_action, &KToggleAction::toggle); // Update documents list according checkboxes connect(this, &CloseConfirmDialog::accepted, this, &CloseConfirmDialog::updateDocsList); KConfigGroup gcg(KSharedConfig::openConfig(), "kate-close-except-like-CloseConfirmationDialog"); KWindowConfig::restoreWindowSize(windowHandle(),gcg); // restore dialog geometry from config } CloseConfirmDialog::~CloseConfirmDialog() { KConfigGroup gcg(KSharedConfig::openConfig(), "kate-close-except-like-CloseConfirmationDialog"); KWindowConfig::saveWindowSize(windowHandle(),gcg); // write dialog geometry to config gcg.sync(); } /** * Going to remove unchecked files from the given documents list */ void CloseConfirmDialog::updateDocsList() { for ( QTreeWidgetItemIterator it(m_docs_tree, QTreeWidgetItemIterator::NotChecked) ; *it ; ++it ) { KateDocItem* item = static_cast(*it); m_docs.removeAll(item->document); qDebug() << "do not close the file " << item->document->url().toString(); } } } // namespace kate diff --git a/addons/filebrowser/katefilebrowserconfig.cpp b/addons/filebrowser/katefilebrowserconfig.cpp index 9ed2e1712..a476b879e 100644 --- a/addons/filebrowser/katefilebrowserconfig.cpp +++ b/addons/filebrowser/katefilebrowserconfig.cpp @@ -1,192 +1,204 @@ /* 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 ) { 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 - QStringList allActions; - 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"); + 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 ( QStringList::Iterator it = allActions.begin(); it != allActions.end(); ++it ) + for (const auto& actionName : allActions) { - lb = l.contains( *it ) ? acSel->selectedListWidget() : acSel->availableListWidget(); + lb = l.contains( actionName ) ? acSel->selectedListWidget() : acSel->availableListWidget(); - if ( *it == QStringLiteral ("bookmarks") || *it == QStringLiteral ("sync_dir") || *it == QStringLiteral ("configure") ) - ac = fileBrowser->actionCollection()->action( *it ); + if ( actionName == QStringLiteral ("bookmarks") || actionName == QStringLiteral ("sync_dir") || actionName == QStringLiteral ("configure") ) + ac = fileBrowser->actionCollection()->action( actionName ); else - ac = fileBrowser->dirOperator()->actionCollection()->action( *it ); + 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, *it ); + 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/filetree/katefiletree.cpp b/addons/filetree/katefiletree.cpp index e3b7338d5..ee3d7d4fd 100644 --- a/addons/filetree/katefiletree.cpp +++ b/addons/filetree/katefiletree.cpp @@ -1,732 +1,731 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom Copyright (C) 2014 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN Includes #include "katefiletree.h" #include "katefiletreemodel.h" #include "katefiletreeproxymodel.h" #include "katefiletreedebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //END Includes //BEGIN KateFileTree KateFileTree::KateFileTree(QWidget *parent): QTreeView(parent) { setAcceptDrops(false); setIndentation(12); setAllColumnsShowFocus(true); setFocusPolicy(Qt::NoFocus); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragOnly); // handle activated (e.g. for pressing enter) + clicked (to avoid to need to do double-click e.g. on Windows) connect(this, &KateFileTree::activated, this, &KateFileTree::mouseClicked); connect(this, &KateFileTree::clicked, this, &KateFileTree::mouseClicked); m_filelistReloadDocument = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reloa&d"), this); connect(m_filelistReloadDocument, &QAction::triggered, this, &KateFileTree::slotDocumentReload); m_filelistReloadDocument->setWhatsThis(i18n("Reload selected document(s) from disk.")); m_filelistCloseDocument = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close"), this); connect(m_filelistCloseDocument, &QAction::triggered, this, &KateFileTree::slotDocumentClose); m_filelistCloseDocument->setWhatsThis(i18n("Close the current document.")); m_filelistExpandRecursive = new QAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Expand recursively"), this); connect(m_filelistExpandRecursive, &QAction::triggered, this, &KateFileTree::slotExpandRecursive); m_filelistExpandRecursive->setWhatsThis(i18n("Expand the file list sub tree recursively.")); m_filelistCollapseRecursive = new QAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Collapse recursively"), this); connect(m_filelistCollapseRecursive, &QAction::triggered, this, &KateFileTree::slotCollapseRecursive); m_filelistCollapseRecursive->setWhatsThis(i18n("Collapse the file list sub tree recursively.")); m_filelistCloseOtherDocument = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close Other"), this); connect(m_filelistCloseOtherDocument, &QAction::triggered, this, &KateFileTree::slotDocumentCloseOther); m_filelistCloseOtherDocument->setWhatsThis(i18n("Close other documents in this folder.")); m_filelistOpenContainingFolder = new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18nc("@action:inmenu", "Open Containing Folder"), this); connect(m_filelistOpenContainingFolder, &QAction::triggered, this, &KateFileTree::slotOpenContainingFolder); m_filelistOpenContainingFolder->setWhatsThis(i18n("Open the folder this file is located in.")); m_filelistCopyFilename = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu", "Copy File Path"), this); connect(m_filelistCopyFilename, &QAction::triggered, this, &KateFileTree::slotCopyFilename); m_filelistCopyFilename->setWhatsThis(i18n("Copy path and filename to the clipboard.")); m_filelistRenameFile = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18nc("@action:inmenu", "Rename..."), this); connect(m_filelistRenameFile, &QAction::triggered, this, &KateFileTree::slotRenameFile); m_filelistRenameFile->setWhatsThis(i18n("Rename the selected file.")); m_filelistPrintDocument = KStandardAction::print(this, SLOT(slotPrintDocument()), this); m_filelistPrintDocument->setWhatsThis(i18n("Print selected document.")); m_filelistPrintDocumentPreview = KStandardAction::printPreview(this, SLOT(slotPrintDocumentPreview()), this); m_filelistPrintDocumentPreview->setWhatsThis(i18n("Show print preview of current document")); m_filelistDeleteDocument = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Delete"), this); connect(m_filelistDeleteDocument, &QAction::triggered, this, &KateFileTree::slotDocumentDelete); m_filelistDeleteDocument->setWhatsThis(i18n("Close and delete selected file from storage.")); QActionGroup *modeGroup = new QActionGroup(this); m_treeModeAction = setupOption(modeGroup, QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Tree Mode"), i18n("Set view style to Tree Mode"), SLOT(slotTreeMode()), true); m_listModeAction = setupOption(modeGroup, QIcon::fromTheme(QStringLiteral("view-list-text")), i18nc("@action:inmenu", "List Mode"), i18n("Set view style to List Mode"), SLOT(slotListMode()), false); QActionGroup *sortGroup = new QActionGroup(this); m_sortByFile = setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Name"), i18n("Sort by Document Name"), SLOT(slotSortName()), true); m_sortByPath = setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Path"), i18n("Sort by Document Path"), SLOT(slotSortPath()), false); m_sortByOpeningOrder = setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Opening Order"), i18n("Sort by Opening Order"), SLOT(slotSortOpeningOrder()), false); m_resetHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18nc("@action:inmenu", "Clear History"), this); connect(m_resetHistory, &QAction::triggered, this, &KateFileTree::slotResetHistory); m_resetHistory->setWhatsThis(i18n("Clear edit/view history.")); QPalette p = palette(); p.setColor(QPalette::Inactive, QPalette::Highlight, p.color(QPalette::Active, QPalette::Highlight)); p.setColor(QPalette::Inactive, QPalette::HighlightedText, p.color(QPalette::Active, QPalette::HighlightedText)); setPalette(p); } KateFileTree::~KateFileTree() {} void KateFileTree::setModel(QAbstractItemModel *model) { Q_ASSERT(qobject_cast(model)); // we don't really work with anything else QTreeView::setModel(model); } QAction *KateFileTree::setupOption( QActionGroup *group, const QIcon &icon, const QString &label, const QString &whatsThis, const char *slot, bool checked ) { QAction *new_action = new QAction(icon, label, this); new_action->setWhatsThis(whatsThis); new_action->setActionGroup(group); new_action->setCheckable(true); new_action->setChecked(checked); connect(new_action, SIGNAL(triggered()), this, slot); return new_action; } void KateFileTree::slotListMode() { emit viewModeChanged(true); } void KateFileTree::slotTreeMode() { emit viewModeChanged(false); } void KateFileTree::slotSortName() { emit sortRoleChanged(Qt::DisplayRole); } void KateFileTree::slotSortPath() { emit sortRoleChanged(KateFileTreeModel::PathRole); } void KateFileTree::slotSortOpeningOrder() { emit sortRoleChanged(KateFileTreeModel::OpeningOrderRole); } void KateFileTree::slotCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous) { Q_UNUSED(previous); if (!current.isValid()) { return; } KTextEditor::Document *doc = model()->data(current, KateFileTreeModel::DocumentRole).value(); if (doc) { m_previouslySelected = current; } } void KateFileTree::mouseClicked(const QModelIndex &index) { if (auto doc = model()->data(index, KateFileTreeModel::DocumentRole).value()) { emit activateDocument(doc); } } void KateFileTree::contextMenuEvent(QContextMenuEvent *event) { m_indexContextMenu = selectionModel()->currentIndex(); selectionModel()->setCurrentIndex(m_indexContextMenu, QItemSelectionModel::ClearAndSelect); KateFileTreeProxyModel *ftpm = static_cast(model()); KateFileTreeModel *ftm = static_cast(ftpm->sourceModel()); bool listMode = ftm->listMode(); m_treeModeAction->setChecked(!listMode); m_listModeAction->setChecked(listMode); int sortRole = ftpm->sortRole(); m_sortByFile->setChecked(sortRole == Qt::DisplayRole); m_sortByPath->setChecked(sortRole == KateFileTreeModel::PathRole); m_sortByOpeningOrder->setChecked(sortRole == KateFileTreeModel::OpeningOrderRole); KTextEditor::Document *doc = m_indexContextMenu.data(KateFileTreeModel::DocumentRole).value(); const bool isFile = (nullptr != doc); QMenu menu; menu.addAction(m_filelistReloadDocument); menu.addAction(m_filelistCloseDocument); menu.addAction(m_filelistExpandRecursive); menu.addAction(m_filelistCollapseRecursive); if (isFile) { menu.addAction(m_filelistCloseOtherDocument); menu.addSeparator(); menu.addAction(m_filelistOpenContainingFolder); menu.addAction(m_filelistCopyFilename); menu.addAction(m_filelistRenameFile); menu.addAction(m_filelistPrintDocument); menu.addAction(m_filelistPrintDocumentPreview); QMenu *openWithMenu = menu.addMenu(i18nc("@action:inmenu", "Open With")); connect(openWithMenu, &QMenu::aboutToShow, this, &KateFileTree::slotFixOpenWithMenu); connect(openWithMenu, &QMenu::triggered, this, &KateFileTree::slotOpenWithMenuAction); const bool hasFileName = doc->url().isValid(); m_filelistOpenContainingFolder->setEnabled(hasFileName); m_filelistCopyFilename->setEnabled(hasFileName); m_filelistRenameFile->setEnabled(hasFileName); m_filelistDeleteDocument->setEnabled(hasFileName); menu.addAction(m_filelistDeleteDocument); } menu.addSeparator(); QMenu *view_menu = menu.addMenu(i18nc("@action:inmenu", "View Mode")); view_menu->addAction(m_treeModeAction); view_menu->addAction(m_listModeAction); QMenu *sort_menu = menu.addMenu(QIcon::fromTheme(QStringLiteral("view-sort")), i18nc("@action:inmenu", "Sort By")); sort_menu->addAction(m_sortByFile); sort_menu->addAction(m_sortByPath); sort_menu->addAction(m_sortByOpeningOrder); menu.addAction(m_resetHistory); menu.exec(viewport()->mapToGlobal(event->pos())); if (m_previouslySelected.isValid()) { selectionModel()->setCurrentIndex(m_previouslySelected, QItemSelectionModel::ClearAndSelect); } event->accept(); } void KateFileTree::slotFixOpenWithMenu() { QMenu *menu = (QMenu *)sender(); menu->clear(); KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } // get a list of appropriate services. QMimeDatabase db; QMimeType mime = db.mimeTypeForName(doc->mimeType()); QAction *a = nullptr; - KService::List offers = KMimeTypeTrader::self()->query(mime.name(), QStringLiteral("Application")); + const KService::List offers = KMimeTypeTrader::self()->query(mime.name(), QStringLiteral("Application")); // for each one, insert a menu item... - for (KService::List::Iterator it = offers.begin(); it != offers.end(); ++it) { - KService::Ptr service = *it; + for (const auto& service : offers) { if (service->name() == QLatin1String("Kate")) { continue; } a = menu->addAction(QIcon::fromTheme(service->icon()), service->name()); a->setData(service->entryPath()); } // append "Other..." to call the KDE "open with" dialog. a = menu->addAction(i18n("&Other...")); a->setData(QString()); } void KateFileTree::slotOpenWithMenuAction(QAction *a) { QList list; KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } list.append(doc->url()); const QString openWith = a->data().toString(); if (openWith.isEmpty()) { // display "open with" dialog KOpenWithDialog dlg(list); if (dlg.exec()) { KRun::runService(*dlg.service(), list, this); } return; } KService::Ptr app = KService::serviceByDesktopPath(openWith); if (app) { KRun::runService(*app, list, this); } else { KMessageBox::error(this, i18n("Application '%1' not found.", openWith), i18n("Application not found")); } } Q_DECLARE_METATYPE(QList) void KateFileTree::slotDocumentClose() { m_previouslySelected = QModelIndex(); QVariant v = m_indexContextMenu.data(KateFileTreeModel::DocumentTreeRole); if (!v.isValid()) { return; } QList closingDocuments = v.value >(); KTextEditor::Editor::instance()->application()->closeDocuments(closingDocuments); } void KateFileTree::slotExpandRecursive() { if (! m_indexContextMenu.isValid()) { return; } // Work list for DFS walk over sub tree QList worklist = { m_indexContextMenu }; while (! worklist.isEmpty()) { QPersistentModelIndex index = worklist.takeLast(); // Expand current item expand(index); // Append all children of current item for (int i=0 ; i < model()->rowCount(index) ; ++i) { worklist.append(index.child(i,0)); } } } void KateFileTree::slotCollapseRecursive() { if (! m_indexContextMenu.isValid()) { return; } // Work list for DFS walk over sub tree QList worklist = { m_indexContextMenu }; while (! worklist.isEmpty()) { QPersistentModelIndex index = worklist.takeLast(); // Expand current item collapse(index); // Prepend all children of current item for (int i=0 ; i < model()->rowCount(index) ; ++i) { worklist.append(index.child(i,0)); } } } void KateFileTree::slotDocumentCloseOther() { QVariant v = model()->data(m_indexContextMenu.parent(), KateFileTreeModel::DocumentTreeRole); if (!v.isValid()) { return; } QList closingDocuments = v.value >(); KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); closingDocuments.removeOne(doc); KTextEditor::Editor::instance()->application()->closeDocuments(closingDocuments); } void KateFileTree::slotDocumentReload() { QVariant v = m_indexContextMenu.data(KateFileTreeModel::DocumentTreeRole); if (!v.isValid()) { return; } QList docs = v.value >(); foreach(KTextEditor::Document * doc, docs) { doc->documentReload(); } } void KateFileTree::slotOpenContainingFolder() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (doc) { KIO::highlightInFileManager({doc->url()}); } } void KateFileTree::slotCopyFilename() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here // (make sure that the mentioned bug 381052 does not reappear) if (doc) { // ensure we prefer native separators, bug 381052 if (doc->url().isLocalFile()) { QApplication::clipboard()->setText(QDir::toNativeSeparators(doc->url().toLocalFile())); } else { QApplication::clipboard()->setText(doc->url().url()); } } } void KateFileTree::slotRenameFile() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here if (!doc) { return; } const QUrl oldFileUrl = doc->url(); const QString oldFileName = doc->url().fileName(); bool ok; QString newFileName = QInputDialog::getText(this, i18n("Rename file"), i18n("New file name"), QLineEdit::Normal, oldFileName, &ok); if (!ok) { return; } QUrl newFileUrl = oldFileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); newFileUrl.setPath(newFileUrl.path() + QLatin1Char('/') + newFileName); if (!newFileUrl.isValid()) { return; } if (!doc->closeUrl()) { return; } doc->waitSaveComplete(); KIO::CopyJob* job = KIO::move(oldFileUrl, newFileUrl); QSharedPointer sc(new QMetaObject::Connection()); auto success = [doc, sc] (KIO::Job*, const QUrl&, const QUrl &realNewFileUrl, const QDateTime&, bool, bool) { doc->openUrl(realNewFileUrl); doc->documentSavedOrUploaded(doc, true); QObject::disconnect(*sc); }; *sc = connect(job, &KIO::CopyJob::copyingDone, doc, success); if (!job->exec()) { KMessageBox::sorry(this, i18n("File \"%1\" could not be moved to \"%2\"", oldFileUrl.toDisplayString(), newFileUrl.toDisplayString())); doc->openUrl(oldFileUrl); } } void KateFileTree::slotDocumentFirst() { KTextEditor::Document *doc = model()->data(model()->index(0, 0), KateFileTreeModel::DocumentRole).value(); if (doc) { emit activateDocument(doc); } } void KateFileTree::slotDocumentLast() { int count = model()->rowCount(model()->parent(currentIndex())); KTextEditor::Document *doc = model()->data(model()->index(count - 1, 0), KateFileTreeModel::DocumentRole).value(); if (doc) { emit activateDocument(doc); } } void KateFileTree::slotDocumentPrev() { KateFileTreeProxyModel *ftpm = static_cast(model()); QModelIndex current_index = currentIndex(); QModelIndex prev; // scan up the tree skipping any dir nodes while (current_index.isValid()) { if (current_index.row() > 0) { current_index = ftpm->sibling(current_index.row() - 1, current_index.column(), current_index); if (!current_index.isValid()) { break; } if (ftpm->isDir(current_index)) { // try and select the last child in this parent int children = ftpm->rowCount(current_index); current_index = ftpm->index(children - 1, 0, current_index); if (ftpm->isDir(current_index)) { // since we're a dir, keep going while (ftpm->isDir(current_index)) { children = ftpm->rowCount(current_index); current_index = ftpm->index(children - 1, 0, current_index); } if (!ftpm->isDir(current_index)) { prev = current_index; break; } continue; } else { // we're the previous file, set prev prev = current_index; break; } } else { // found document item prev = current_index; break; } } else { // just select the parent, the logic above will handle the rest current_index = ftpm->parent(current_index); if (!current_index.isValid()) { // paste the root node here, try and wrap around int children = ftpm->rowCount(current_index); QModelIndex last_index = ftpm->index(children - 1, 0, current_index); if (!last_index.isValid()) { break; } if (ftpm->isDir(last_index)) { // last node is a dir, select last child row int last_children = ftpm->rowCount(last_index); prev = ftpm->index(last_children - 1, 0, last_index); // bug here? break; } else { // got last file node prev = last_index; break; } } } } if (prev.isValid()) { KTextEditor::Document *doc = model()->data(prev, KateFileTreeModel::DocumentRole).value(); emit activateDocument(doc); } } void KateFileTree::slotDocumentNext() { KateFileTreeProxyModel *ftpm = static_cast(model()); QModelIndex current_index = currentIndex(); int parent_row_count = ftpm->rowCount(ftpm->parent(current_index)); QModelIndex next; // scan down the tree skipping any dir nodes while (current_index.isValid()) { if (current_index.row() < parent_row_count - 1) { current_index = ftpm->sibling(current_index.row() + 1, current_index.column(), current_index); if (!current_index.isValid()) { break; } if (ftpm->isDir(current_index)) { // we have a dir node while (ftpm->isDir(current_index)) { current_index = ftpm->index(0, 0, current_index); } parent_row_count = ftpm->rowCount(ftpm->parent(current_index)); if (!ftpm->isDir(current_index)) { next = current_index; break; } } else { // found document item next = current_index; break; } } else { // select the parent's next sibling QModelIndex parent_index = ftpm->parent(current_index); int grandparent_row_count = ftpm->rowCount(ftpm->parent(parent_index)); current_index = parent_index; parent_row_count = grandparent_row_count; // at least if we're not past the last node if (!current_index.isValid()) { // paste the root node here, try and wrap around QModelIndex last_index = ftpm->index(0, 0, QModelIndex()); if (!last_index.isValid()) { break; } if (ftpm->isDir(last_index)) { // last node is a dir, select first child row while (ftpm->isDir(last_index)) { if (ftpm->rowCount(last_index)) { // has children, select first last_index = ftpm->index(0, 0, last_index); } } next = last_index; break; } else { // got first file node next = last_index; break; } } } } if (next.isValid()) { KTextEditor::Document *doc = model()->data(next, KateFileTreeModel::DocumentRole).value(); emit activateDocument(doc); } } void KateFileTree::slotPrintDocument() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } doc->print(); } void KateFileTree::slotPrintDocumentPreview() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } doc->printPreview(); } void KateFileTree::slotResetHistory() { KateFileTreeProxyModel *ftpm = static_cast(model()); KateFileTreeModel *ftm = static_cast(ftpm->sourceModel()); ftm->resetHistory(); } void KateFileTree::slotDocumentDelete() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here if (!doc) { return; } QUrl url = doc->url(); bool go = (KMessageBox::warningContinueCancel(this, i18n("Do you really want to delete file \"%1\" from storage?", url.toDisplayString()), i18n("Delete file?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("filetreedeletefile") ) == KMessageBox::Continue); if (!go) { return; } if (!KTextEditor::Editor::instance()->application()->closeDocument(doc)) { return; // no extra message, the internals of ktexteditor should take care of that. } if (url.isValid()) { KIO::DeleteJob *job = KIO::del(url); if (!job->exec()) { KMessageBox::sorry(this, i18n("File \"%1\" could not be deleted.", url.toDisplayString())); } } } //END KateFileTree diff --git a/addons/gdbplugin/debugview.cpp b/addons/gdbplugin/debugview.cpp index 59da507e8..c4dbe2299 100644 --- a/addons/gdbplugin/debugview.cpp +++ b/addons/gdbplugin/debugview.cpp @@ -1,696 +1,696 @@ // // debugview.cpp // // Description: Manages the interaction with GDB // // // Copyright (c) 2008-2010 Ian Wakeling // Copyright (c) 2011 Kåre Särs // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License version 2 as published by the Free Software Foundation. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public License // along with this library; see the file COPYING.LIB. If not, write to // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, // Boston, MA 02110-1301, USA. #include "debugview.h" #include #include #include #include #include #include #include #include static const QString PromptStr = QStringLiteral("(prompt)"); DebugView::DebugView(QObject* parent) : QObject(parent), m_debugProcess(nullptr), m_state(none), m_subState(normal), m_debugLocationChanged(true), m_queryLocals(false) { } DebugView::~DebugView() { if ( m_debugProcess.state() != QProcess::NotRunning) { m_debugProcess.kill(); m_debugProcess.blockSignals(true); m_debugProcess.waitForFinished(); } } void DebugView::runDebugger(const GDBTargetConf &conf, const QStringList &ioFifos) { if (conf.executable.isEmpty()) { return; } m_targetConf = conf; if (ioFifos.size() == 3) { m_ioPipeString = QStringLiteral("< %1 1> %2 2> %3") .arg(ioFifos[0]) .arg(ioFifos[1]) .arg(ioFifos[2]); } if (m_state == none) { m_outBuffer.clear(); m_errBuffer.clear(); m_errorList.clear(); //create a process to control GDB m_debugProcess.setWorkingDirectory(m_targetConf.workDir); connect(&m_debugProcess, static_cast(&QProcess::error), this, &DebugView::slotError); connect(&m_debugProcess, &QProcess::readyReadStandardError, this, &DebugView::slotReadDebugStdErr); connect(&m_debugProcess, &QProcess::readyReadStandardOutput, this, &DebugView::slotReadDebugStdOut); connect(&m_debugProcess, static_cast(&QProcess::finished), this, &DebugView::slotDebugFinished); m_debugProcess.start(m_targetConf.gdbCmd); m_nextCommands << QStringLiteral("set pagination off"); m_state = ready; } else { // On startup the gdb prompt will trigger the "nextCommands", // here we have to trigger it manually. QTimer::singleShot(0, this, &DebugView::issueNextCommand); } m_nextCommands << QStringLiteral("file %1").arg(m_targetConf.executable); m_nextCommands << QStringLiteral("set args %1 %2").arg(m_targetConf.arguments).arg(m_ioPipeString); m_nextCommands << QStringLiteral("set inferior-tty /dev/null"); m_nextCommands << m_targetConf.customInit; m_nextCommands << QStringLiteral("(Q) info breakpoints"); } bool DebugView::debuggerRunning() const { return(m_state != none); } bool DebugView::debuggerBusy() const { return(m_state == executingCmd); } bool DebugView::hasBreakpoint(const QUrl& url, int line) { - for (int i = 0; i")) { m_outBuffer.clear(); processLine(PromptStr); } } void DebugView::slotReadDebugStdErr() { m_errBuffer += QString::fromLocal8Bit(m_debugProcess.readAllStandardError().data()); int end=0; // add whole lines at a time to the error list do { end = m_errBuffer.indexOf(QLatin1Char('\n')); if (end < 0) break; m_errorList << m_errBuffer.mid(0, end); m_errBuffer.remove(0,end+1); } while (1); processErrors(); } void DebugView::slotDebugFinished(int /*exitCode*/, QProcess::ExitStatus status) { if(status != QProcess::NormalExit) { emit outputText(i18n("*** gdb exited normally ***") + QLatin1Char('\n')); } m_state = none; emit readyForInput(false); // remove all old breakpoints BreakPoint bPoint; while (m_breakPointList.size() > 0) { bPoint = m_breakPointList.takeFirst(); emit breakPointCleared(bPoint.file, bPoint.line -1); } emit gdbEnded(); } void DebugView::movePC(QUrl const& url, int line) { if(m_state == ready) { QString cmd = QStringLiteral("tbreak %1:%2").arg(url.path()).arg(line); m_nextCommands << QStringLiteral("jump %1:%2").arg(url.path()).arg(line); issueCommand(cmd); } } void DebugView::runToCursor(QUrl const& url, int line) { if(m_state == ready) { QString cmd = QStringLiteral("tbreak %1:%2").arg(url.path()).arg(line); m_nextCommands << QStringLiteral("continue"); issueCommand(cmd); } } void DebugView::slotInterrupt() { if (m_state == executingCmd) { m_debugLocationChanged = true; } int pid = m_debugProcess.pid(); if (pid != 0) { ::kill(pid, SIGINT); } } void DebugView::slotKill() { if(m_state != ready) { slotInterrupt(); m_state = ready; } issueCommand(QStringLiteral("kill")); } void DebugView::slotReRun() { slotKill(); m_nextCommands << QStringLiteral("file %1").arg(m_targetConf.executable); m_nextCommands << QStringLiteral("set args %1 %2").arg(m_targetConf.arguments).arg(m_ioPipeString); m_nextCommands << QStringLiteral("set inferior-tty /dev/null"); m_nextCommands << m_targetConf.customInit; m_nextCommands << QStringLiteral("(Q) info breakpoints"); m_nextCommands << QStringLiteral("tbreak main"); m_nextCommands << QStringLiteral("run"); m_nextCommands << QStringLiteral("p setvbuf(stdout, 0, %1, 1024)").arg(_IOLBF); m_nextCommands << QStringLiteral("continue"); } void DebugView::slotStepInto() { issueCommand(QStringLiteral("step")); } void DebugView::slotStepOver() { issueCommand(QStringLiteral("next")); } void DebugView::slotStepOut() { issueCommand(QStringLiteral("finish")); } void DebugView::slotContinue() { issueCommand(QStringLiteral("continue")); } static QRegExp breakpointList(QStringLiteral("Num\\s+Type\\s+Disp\\s+Enb\\s+Address\\s+What.*")); static QRegExp breakpointListed(QStringLiteral("(\\d)\\s+breakpoint\\s+keep\\sy\\s+0x[\\da-f]+\\sin\\s.+\\sat\\s([^:]+):(\\d+).*")); static QRegExp stackFrameAny(QStringLiteral("#(\\d+)\\s(.*)")); static QRegExp stackFrameFile(QStringLiteral("#(\\d+)\\s+(?:0x[\\da-f]+\\s*in\\s)*(\\S+)(\\s\\(.*\\)) at ([^:]+):(\\d+).*")); static QRegExp changeFile(QStringLiteral("(?:(?:Temporary\\sbreakpoint|Breakpoint)\\s*\\d+,\\s*|0x[\\da-f]+\\s*in\\s*)?[^\\s]+\\s*\\([^)]*\\)\\s*at\\s*([^:]+):(\\d+).*")); static QRegExp changeLine(QStringLiteral("(\\d+)\\s+.*")); static QRegExp breakPointReg(QStringLiteral("Breakpoint\\s+(\\d+)\\s+at\\s+0x[\\da-f]+:\\s+file\\s+([^\\,]+)\\,\\s+line\\s+(\\d+).*")); static QRegExp breakPointMultiReg(QStringLiteral("Breakpoint\\s+(\\d+)\\s+at\\s+0x[\\da-f]+:\\s+([^\\,]+):(\\d+).*")); static QRegExp breakPointDel(QStringLiteral("Deleted\\s+breakpoint.*")); static QRegExp exitProgram(QStringLiteral("(?:Program|.*Inferior.*)\\s+exited.*")); static QRegExp threadLine(QStringLiteral("\\**\\s+(\\d+)\\s+Thread.*")); void DebugView::processLine(QString line) { if (line.isEmpty()) return; switch(m_state) { case none: case ready: if(PromptStr == line) { // we get here after initialization QTimer::singleShot(0, this, &DebugView::issueNextCommand); } break; case executingCmd: if(breakpointList.exactMatch(line)) { m_state = listingBreakpoints; emit clearBreakpointMarks(); m_breakPointList.clear(); } else if (line.contains(QStringLiteral("No breakpoints or watchpoints."))) { emit clearBreakpointMarks(); m_breakPointList.clear(); } else if (stackFrameAny.exactMatch(line)) { if (m_lastCommand.contains(QStringLiteral("info stack"))) { emit stackFrameInfo(stackFrameAny.cap(1), stackFrameAny.cap(2)); } else { m_subState = (m_subState == normal) ? stackFrameSeen : stackTraceSeen; m_newFrameLevel = stackFrameAny.cap(1).toInt(); if (stackFrameFile.exactMatch(line)) { m_newFrameFile = stackFrameFile.cap(4); } } } else if(changeFile.exactMatch(line)) { m_currentFile = changeFile.cap(1).trimmed(); int lineNum = changeFile.cap(2).toInt(); if (!m_nextCommands.contains(QStringLiteral("continue"))) { // GDB uses 1 based line numbers, kate uses 0 based... emit debugLocationChanged(resolveFileName(m_currentFile), lineNum - 1); } m_debugLocationChanged = true; } else if(changeLine.exactMatch(line)) { int lineNum = changeLine.cap(1).toInt(); if(m_subState == stackFrameSeen) { m_currentFile = m_newFrameFile; } if (!m_nextCommands.contains(QStringLiteral("continue"))) { // GDB uses 1 based line numbers, kate uses 0 based... emit debugLocationChanged(resolveFileName(m_currentFile), lineNum - 1); } m_debugLocationChanged = true; } else if (breakPointReg.exactMatch(line)) { BreakPoint breakPoint; breakPoint.number = breakPointReg.cap(1).toInt(); breakPoint.file = resolveFileName(breakPointReg.cap(2)); breakPoint.line = breakPointReg.cap(3).toInt(); m_breakPointList << breakPoint; emit breakPointSet(breakPoint.file, breakPoint.line -1); } else if (breakPointMultiReg.exactMatch(line)) { BreakPoint breakPoint; breakPoint.number = breakPointMultiReg.cap(1).toInt(); breakPoint.file = resolveFileName(breakPointMultiReg.cap(2)); breakPoint.line = breakPointMultiReg.cap(3).toInt(); m_breakPointList << breakPoint; emit breakPointSet(breakPoint.file, breakPoint.line -1); } else if (breakPointDel.exactMatch(line)) { line.remove(QStringLiteral("Deleted breakpoint")); line.remove(QLatin1Char('s')); // in case of multiple breakpoints QStringList numbers = line.split(QLatin1Char(' '), QString::SkipEmptyParts); for (int i=0; i 0) && !m_nextCommands[0].contains(QStringLiteral("file"))) { m_nextCommands.clear(); } m_debugLocationChanged = false; // do not insert (Q) commands emit programEnded(); } else if(PromptStr == line) { if(m_subState == stackFrameSeen) { emit stackFrameChanged(m_newFrameLevel); } m_state = ready; // Give the error a possibility get noticed since stderr and stdout are not in sync QTimer::singleShot(0, this, &DebugView::issueNextCommand); } break; case listingBreakpoints: if (breakpointListed.exactMatch(line)) { BreakPoint breakPoint; breakPoint.number = breakpointListed.cap(1).toInt(); breakPoint.file = resolveFileName(breakpointListed.cap(2)); breakPoint.line = breakpointListed.cap(3).toInt(); m_breakPointList << breakPoint; emit breakPointSet(breakPoint.file, breakPoint.line -1); } else if(PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } break; case infoArgs: if(PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else { emit infoLocal(line); } break; case printThis: if(PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else { emit infoLocal(line); } break; case infoLocals: if(PromptStr == line) { m_state = ready; emit infoLocal(QString()); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else { emit infoLocal(line); } break; case infoStack: if(PromptStr == line) { m_state = ready; emit stackFrameInfo(QString(), QString()); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if (stackFrameAny.exactMatch(line)) { emit stackFrameInfo(stackFrameAny.cap(1), stackFrameAny.cap(2)); } break; case infoThreads: if(PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if (threadLine.exactMatch(line)) { emit threadInfo(threadLine.cap(1).toInt(), (line[0] == QLatin1Char('*'))); } break; } outputTextMaybe(line); } void DebugView::processErrors() { QString error; while (m_errorList.size() > 0) { error = m_errorList.takeFirst(); //qDebug() << error; if(error == QLatin1String("The program is not being run.")) { if (m_lastCommand == QLatin1String("continue")) { m_nextCommands.clear(); m_nextCommands << QStringLiteral("tbreak main"); m_nextCommands << QStringLiteral("run"); m_nextCommands << QStringLiteral("p setvbuf(stdout, 0, %1, 1024)").arg(_IOLBF); m_nextCommands << QStringLiteral("continue"); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if ((m_lastCommand == QLatin1String("step")) || (m_lastCommand == QLatin1String("next")) || (m_lastCommand == QLatin1String("finish"))) { m_nextCommands.clear(); m_nextCommands << QStringLiteral("tbreak main"); m_nextCommands << QStringLiteral("run"); m_nextCommands << QStringLiteral("p setvbuf(stdout, 0, %1, 1024)").arg(_IOLBF); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if ((m_lastCommand == QLatin1String("kill"))) { if (m_nextCommands.size() > 0) { if (!m_nextCommands[0].contains(QStringLiteral("file"))) { m_nextCommands.clear(); m_nextCommands << QStringLiteral("quit"); } // else continue with "ReRun" } else { m_nextCommands << QStringLiteral("quit"); } m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } // else do nothing } else if (error.contains(QStringLiteral("No line ")) || error.contains(QStringLiteral("No source file named"))) { // setting a breakpoint failed. Do not continue. m_nextCommands.clear(); emit readyForInput(true); } else if (error.contains(QStringLiteral("No stack"))) { m_nextCommands.clear(); emit programEnded(); } if ((m_lastCommand == QLatin1String("(Q)print *this")) && error.contains(QStringLiteral("No symbol \"this\" in current context."))) { continue; } emit outputError(error + QLatin1Char('\n')); } } void DebugView::issueCommand(QString const& cmd) { if(m_state == ready) { emit readyForInput(false); m_state = executingCmd; if (cmd == QLatin1String("(Q)info locals")) { m_state = infoLocals; } else if (cmd == QLatin1String("(Q)info args")) { m_state = infoArgs; } else if (cmd == QLatin1String("(Q)print *this")) { m_state = printThis; } else if (cmd == QLatin1String("(Q)info stack")) { m_state = infoStack; } else if (cmd == QLatin1String("(Q)info thread")) { emit threadInfo(-1 , false); m_state = infoThreads; } m_subState = normal; m_lastCommand = cmd; if (cmd.startsWith(QStringLiteral("(Q)"))) { m_debugProcess.write(qPrintable(cmd.mid(3))); } else { emit outputText(QStringLiteral("(gdb) ") + cmd + QLatin1Char('\n')); m_debugProcess.write(qPrintable(cmd)); } m_debugProcess.write("\n"); } } void DebugView::issueNextCommand() { if(m_state == ready) { if(m_nextCommands.size() > 0) { QString cmd = m_nextCommands.takeFirst(); //qDebug() << "Next command" << cmd; issueCommand(cmd); } else { // FIXME "thread" needs a better generic solution if (m_debugLocationChanged || m_lastCommand.startsWith(QStringLiteral("thread"))) { m_debugLocationChanged = false; if (m_queryLocals && !m_lastCommand.startsWith(QStringLiteral("(Q)"))) { m_nextCommands << QStringLiteral("(Q)info stack"); m_nextCommands << QStringLiteral("(Q)frame"); m_nextCommands << QStringLiteral("(Q)info args"); m_nextCommands << QStringLiteral("(Q)print *this"); m_nextCommands << QStringLiteral("(Q)info locals"); m_nextCommands << QStringLiteral("(Q)info thread"); issueNextCommand(); return; } } emit readyForInput(true); } } } QUrl DebugView::resolveFileName(const QString &fileName) { QUrl url; QFileInfo fInfo = QFileInfo(fileName); //did we end up with an absolute path or a relative one? if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } if (fInfo.isAbsolute()) { // we can not do anything just return the fileName return QUrl::fromUserInput(fileName); } // Now try to add the working path fInfo = QFileInfo(m_targetConf.workDir + fileName); if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } // now try the executable path fInfo = QFileInfo(QFileInfo(m_targetConf.executable).absolutePath() + fileName); if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } foreach (QString srcPath, m_targetConf.srcPaths) { fInfo = QFileInfo(srcPath + QDir::separator() + fileName); if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } } // we can not do anything just return the fileName return QUrl::fromUserInput(fileName); } void DebugView::outputTextMaybe(const QString &text) { if (!m_lastCommand.startsWith(QStringLiteral("(Q)")) && !text.contains(PromptStr)) { emit outputText(text + QLatin1Char('\n')); } } void DebugView::slotQueryLocals(bool query) { m_queryLocals = query; if (query && (m_state == ready) && (m_nextCommands.size() == 0)) { m_nextCommands << QStringLiteral("(Q)info stack"); m_nextCommands << QStringLiteral("(Q)frame"); m_nextCommands << QStringLiteral("(Q)info args"); m_nextCommands << QStringLiteral("(Q)print *this"); m_nextCommands << QStringLiteral("(Q)info locals"); m_nextCommands << QStringLiteral("(Q)info thread"); issueNextCommand(); } } diff --git a/addons/kate-ctags/kate_ctags_view.cpp b/addons/kate-ctags/kate_ctags_view.cpp index cbdbd9d47..f817fd9f6 100644 --- a/addons/kate-ctags/kate_ctags_view.cpp +++ b/addons/kate-ctags/kate_ctags_view.cpp @@ -1,643 +1,643 @@ /* Description : Kate CTags plugin * * Copyright (C) 2008-2011 by Kare Sars * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . */ #include "kate_ctags_view.h" #include "kate_ctags_plugin.h" #include "kate_ctags_debug.h" #include #include #include #include #include #include #include #include #include #include #include /******************************************************************/ KateCTagsView::KateCTagsView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_proc(nullptr) { KXMLGUIClient::setComponentName (QStringLiteral("katectags"), i18n ("Kate CTag")); setXMLFile( QStringLiteral("ui.rc") ); m_toolView = mainWin->createToolView(plugin, QStringLiteral("kate_plugin_katectagsplugin"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), i18n("CTags")); m_mWin = mainWin; QAction *back = actionCollection()->addAction(QStringLiteral("ctags_return_step")); back->setText(i18n("Jump back one step")); connect(back, &QAction::triggered, this, &KateCTagsView::stepBack); QAction *decl = actionCollection()->addAction(QStringLiteral("ctags_lookup_current_as_declaration")); decl->setText(i18n("Go to Declaration")); connect(decl, &QAction::triggered, this, &KateCTagsView::gotoDeclaration); QAction *defin = actionCollection()->addAction(QStringLiteral("ctags_lookup_current_as_definition")); defin->setText(i18n("Go to Definition")); connect(defin, &QAction::triggered, this, &KateCTagsView::gotoDefinition); QAction *lookup = actionCollection()->addAction(QStringLiteral("ctags_lookup_current")); lookup->setText(i18n("Lookup Current Text")); connect(lookup, &QAction::triggered, this, &KateCTagsView::lookupTag); QAction *updateDB = actionCollection()->addAction(QStringLiteral("ctags_update_global_db")); updateDB->setText(i18n("Configure ...")); connect(updateDB, &QAction::triggered, this, [this, plugin] (bool) { if (m_mWin) { KateCTagsPlugin *p = static_cast(plugin); QDialog *confWin = new QDialog(m_mWin->window()); confWin->setAttribute(Qt::WA_DeleteOnClose); auto confPage = p->configPage(0, confWin); auto controls = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, Qt::Horizontal, confWin); connect(confWin, &QDialog::accepted, confPage, &KTextEditor::ConfigPage::apply); connect(controls, &QDialogButtonBox::accepted, confWin, &QDialog::accept); connect(controls, &QDialogButtonBox::rejected, confWin, &QDialog::reject); auto layout = new QVBoxLayout(confWin); layout->addWidget(confPage); layout->addWidget(controls); confWin->setLayout(layout); confWin->setWindowTitle(i18nc("@title:window", "Configure CTags Plugin")); confWin->setWindowIcon(confPage->icon()); confWin->show(); confWin->exec(); } }); // popup menu m_menu = new KActionMenu(i18n("CTags"), this); actionCollection()->addAction(QStringLiteral("popup_ctags"), m_menu); m_gotoDec=m_menu->menu()->addAction(i18n("Go to Declaration: %1",QString()), this, &KateCTagsView::gotoDeclaration); m_gotoDef=m_menu->menu()->addAction(i18n("Go to Definition: %1",QString()), this, &KateCTagsView::gotoDefinition); m_lookup=m_menu->menu()->addAction(i18n("Lookup: %1",QString()), this, &KateCTagsView::lookupTag); connect(m_menu->menu(), &QMenu::aboutToShow, this, &KateCTagsView::aboutToShow); QWidget *ctagsWidget = new QWidget(m_toolView.data()); m_ctagsUi.setupUi(ctagsWidget); m_ctagsUi.cmdEdit->setText(DEFAULT_CTAGS_CMD); m_ctagsUi.addButton->setToolTip(i18n("Add a directory to index.")); m_ctagsUi.addButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_ctagsUi.delButton->setToolTip(i18n("Remove a directory.")); m_ctagsUi.delButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); m_ctagsUi.updateButton->setToolTip(i18n("(Re-)generate the session specific CTags database.")); m_ctagsUi.updateButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ctagsUi.updateButton2->setToolTip(i18n("(Re-)generate the session specific CTags database.")); m_ctagsUi.updateButton2->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ctagsUi.resetCMD->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ctagsUi.tagsFile->setToolTip(i18n("Select new or existing database file.")); m_ctagsUi.tagsFile->setMode(KFile::File); connect(m_ctagsUi.resetCMD, &QToolButton::clicked, this, &KateCTagsView::resetCMD); connect(m_ctagsUi.addButton, &QPushButton::clicked, this, &KateCTagsView::addTagTarget); connect(m_ctagsUi.delButton, &QPushButton::clicked, this, &KateCTagsView::delTagTarget); connect(m_ctagsUi.updateButton, &QPushButton::clicked, this, &KateCTagsView::updateSessionDB); connect(m_ctagsUi.updateButton2, &QPushButton::clicked, this, &KateCTagsView::updateSessionDB); connect(&m_proc, static_cast(&QProcess::finished), this, &KateCTagsView::updateDone); connect(m_ctagsUi.inputEdit, &QLineEdit::textChanged, this, &KateCTagsView::startEditTmr); m_editTimer.setSingleShot(true); connect(&m_editTimer, &QTimer::timeout, this, &KateCTagsView::editLookUp); connect(m_ctagsUi.tagTreeWidget, &QTreeWidget::itemActivated, this, &KateCTagsView::tagHitClicked); connect(m_mWin, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KateCTagsView::handleEsc); m_toolView->layout()->addWidget(ctagsWidget); m_toolView->installEventFilter(this); m_mWin->guiFactory()->addClient(this); m_commonDB = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/katectags/common_db"); } /******************************************************************/ KateCTagsView::~KateCTagsView() { if (m_mWin && m_mWin->guiFactory()) { m_mWin->guiFactory()->removeClient( this ); } if (m_toolView) { delete m_toolView; } } /******************************************************************/ void KateCTagsView::aboutToShow() { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } if (Tags::hasTag(m_commonDB, currWord) || Tags::hasTag(m_ctagsUi.tagsFile->text(), currWord)) { QString squeezed = KStringHandler::csqueeze(currWord, 30); m_gotoDec->setText(i18n("Go to Declaration: %1",squeezed)); m_gotoDef->setText(i18n("Go to Definition: %1",squeezed)); m_lookup->setText(i18n("Lookup: %1",squeezed)); } } /******************************************************************/ void KateCTagsView::readSessionConfig (const KConfigGroup& cg) { m_ctagsUi.cmdEdit->setText(cg.readEntry("TagsGenCMD", DEFAULT_CTAGS_CMD)); int numEntries = cg.readEntry("SessionNumTargets", 0); QString nr; QString target; for (int i=0; isetText(sessionDB); } /******************************************************************/ void KateCTagsView::writeSessionConfig (KConfigGroup& cg) { cg.writeEntry("TagsGenCMD", m_ctagsUi.cmdEdit->text()); cg.writeEntry("SessionNumTargets", m_ctagsUi.targetList->count()); QString nr; for (int i=0; icount(); i++) { nr = QStringLiteral("%1").arg(i,3); cg.writeEntry(QStringLiteral("SessionTarget_%1").arg(nr), m_ctagsUi.targetList->item(i)->text()); } cg.writeEntry("SessionDatabase", m_ctagsUi.tagsFile->text()); cg.sync(); } /******************************************************************/ void KateCTagsView::stepBack() { if (m_jumpStack.isEmpty()) { return; } TagJump back; back = m_jumpStack.pop(); m_mWin->openUrl(back.url); m_mWin->activeView()->setCursorPosition(back.cursor); m_mWin->activeView()->setFocus(); } /******************************************************************/ void KateCTagsView::lookupTag( ) { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } setNewLookupText(currWord); Tags::TagList list = Tags::getExactMatches(m_ctagsUi.tagsFile->text(), currWord); if (list.size() == 0) list = Tags::getExactMatches(m_commonDB, currWord); displayHits(list); // activate the hits tab m_ctagsUi.tabWidget->setCurrentIndex(0); m_mWin->showToolView(m_toolView); } /******************************************************************/ void KateCTagsView::editLookUp() { Tags::TagList list = Tags::getPartialMatches(m_ctagsUi.tagsFile->text(), m_ctagsUi.inputEdit->text()); if (list.size() == 0) list = Tags::getPartialMatches(m_commonDB, m_ctagsUi.inputEdit->text()); displayHits(list); } /******************************************************************/ void KateCTagsView::gotoDefinition( ) { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } QStringList types; types << QStringLiteral("S") << QStringLiteral("d") << QStringLiteral("f") << QStringLiteral("t") << QStringLiteral("v"); gotoTagForTypes(currWord, types); } /******************************************************************/ void KateCTagsView::gotoDeclaration( ) { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } QStringList types; types << QStringLiteral("L") << QStringLiteral("c") << QStringLiteral("e") << QStringLiteral("g") << QStringLiteral("m") << QStringLiteral("n") << QStringLiteral("p") << QStringLiteral("s") << QStringLiteral("u") << QStringLiteral("x"); gotoTagForTypes(currWord, types); } /******************************************************************/ void KateCTagsView::gotoTagForTypes(const QString &word, const QStringList &types) { Tags::TagList list = Tags::getMatches(m_ctagsUi.tagsFile->text(), word, false, types); if (list.size() == 0) list = Tags::getMatches(m_commonDB, word, false, types); //qCDebug(KTECTAGS) << "found" << list.count() << word << types; setNewLookupText(word); if ( list.count() < 1) { m_ctagsUi.tagTreeWidget->clear(); new QTreeWidgetItem(m_ctagsUi.tagTreeWidget, QStringList(i18n("No hits found"))); m_ctagsUi.tabWidget->setCurrentIndex(0); m_mWin->showToolView(m_toolView); return; } displayHits(list); if (list.count() == 1) { Tags::TagEntry tag = list.first(); jumpToTag(tag.file, tag.pattern, word); } else { Tags::TagEntry tag = list.first(); jumpToTag(tag.file, tag.pattern, word); m_ctagsUi.tabWidget->setCurrentIndex(0); m_mWin->showToolView(m_toolView); } } /******************************************************************/ void KateCTagsView::setNewLookupText(const QString &newString) { m_ctagsUi.inputEdit->blockSignals( true ); m_ctagsUi.inputEdit->setText(newString); m_ctagsUi.inputEdit->blockSignals( false ); } /******************************************************************/ void KateCTagsView::displayHits(const Tags::TagList &list) { m_ctagsUi.tagTreeWidget->clear(); if (list.isEmpty()) { new QTreeWidgetItem(m_ctagsUi.tagTreeWidget, QStringList(i18n("No hits found"))); return; } m_ctagsUi.tagTreeWidget->setSortingEnabled(false); - for (int i=0; isetText(0, list[i].tag); - item->setText(1, list[i].type); - item->setText(2, list[i].file); - item->setData(0, Qt::UserRole, list[i].pattern); + item->setText(0, tag.tag); + item->setText(1, tag.type); + item->setText(2, tag.file); + item->setData(0, Qt::UserRole, tag.pattern); - QString pattern = list[i].pattern; + QString pattern = tag.pattern; pattern.replace( QStringLiteral("\\/"), QStringLiteral("/")); pattern = pattern.mid(2, pattern.length() - 4); pattern = pattern.trimmed(); item->setData(0, Qt::ToolTipRole, pattern); item->setData(1, Qt::ToolTipRole, pattern); item->setData(2, Qt::ToolTipRole, pattern); } m_ctagsUi.tagTreeWidget->setSortingEnabled(true); } /******************************************************************/ void KateCTagsView::tagHitClicked(QTreeWidgetItem *item) { // get stuff const QString file = item->data(2, Qt::DisplayRole).toString(); const QString pattern = item->data(0, Qt::UserRole).toString(); const QString word = item->data(0, Qt::DisplayRole).toString(); jumpToTag(file, pattern, word); } /******************************************************************/ QString KateCTagsView::currentWord( ) { KTextEditor::View *kv = m_mWin->activeView(); if (!kv) { qCDebug(KTECTAGS) << "no KTextEditor::View" << endl; return QString(); } if (kv->selection() && kv->selectionRange().onSingleLine()) { return kv->selectionText(); } if (!kv->cursorPosition().isValid()) { qCDebug(KTECTAGS) << "cursor not valid!" << endl; return QString(); } int line = kv->cursorPosition().line(); int col = kv->cursorPosition().column(); bool includeColon = m_ctagsUi.cmdEdit->text().contains(QLatin1String("--extra=+q")); QString linestr = kv->document()->line(line); int startPos = qMax(qMin(col, linestr.length()-1), 0); int endPos = startPos; while (startPos >= 0 && (linestr[startPos].isLetterOrNumber() || (linestr[startPos] == QLatin1Char(':') && includeColon) || linestr[startPos] == QLatin1Char('_') || linestr[startPos] == QLatin1Char('~'))) { startPos--; } while (endPos < (int)linestr.length() && (linestr[endPos].isLetterOrNumber() || (linestr[endPos] == QLatin1Char(':') && includeColon) || linestr[endPos] == QLatin1Char('_'))) { endPos++; } if (startPos == endPos) { qCDebug(KTECTAGS) << "no word found!" << endl; return QString(); } linestr = linestr.mid(startPos+1, endPos-startPos-1); while (linestr.endsWith(QLatin1Char(':'))) { linestr.remove(linestr.size()-1, 1); } while (linestr.startsWith(QLatin1Char(':'))) { linestr.remove(0, 1); } //qCDebug(KTECTAGS) << linestr; return linestr; } /******************************************************************/ void KateCTagsView::jumpToTag(const QString &file, const QString &pattern, const QString &word) { if (pattern.isEmpty()) return; // generate a regexp from the pattern // ctags interestingly escapes "/", but apparently nothing else. lets revert that QString unescaped = pattern; unescaped.replace( QStringLiteral("\\/"), QStringLiteral("/") ); // most of the time, the ctags pattern has the form /^foo$/ // but this isn't true for some macro definitions // where the form is only /^foo/ // I have no idea if this is a ctags bug or not, but we have to deal with it QString reduced; QString escaped; QString re_string; if (unescaped.endsWith(QStringLiteral("$/"))) { reduced = unescaped.mid(2, unescaped.length() - 4); escaped = QRegularExpression::escape(reduced); re_string = QStringLiteral("^%1$").arg(escaped); } else { reduced = unescaped.mid( 2, unescaped.length() -3 ); escaped = QRegularExpression::escape(reduced); re_string = QStringLiteral("^%1").arg(escaped); } QRegularExpression re(re_string); // save current location TagJump from; from.url = m_mWin->activeView()->document()->url(); from.cursor = m_mWin->activeView()->cursorPosition(); m_jumpStack.push(from); // open/activate the new file QFileInfo fInfo(file); //qCDebug(KTECTAGS) << pattern << file << fInfo.absoluteFilePath(); m_mWin->openUrl(QUrl::fromLocalFile(fInfo.absoluteFilePath())); // any view active? if (!m_mWin->activeView()) { return; } // look for the line QString linestr; int line; for (line =0; line < m_mWin->activeView()->document()->lines(); line++) { linestr = m_mWin->activeView()->document()->line(line); if (linestr.indexOf(re) > -1) break; } // activate the line if (line != m_mWin->activeView()->document()->lines()) { // line found now look for the column int column = linestr.indexOf(word) + (word.length()/2); m_mWin->activeView()->setCursorPosition(KTextEditor::Cursor(line, column)); } m_mWin->activeView()->setFocus(); } /******************************************************************/ void KateCTagsView::startEditTmr() { if (m_ctagsUi.inputEdit->text().size() > 3) { m_editTimer.start(500); } } /******************************************************************/ void KateCTagsView::updateSessionDB() { if (m_proc.state() != QProcess::NotRunning) { return; } QString targets; QString target; for (int i=0; icount(); i++) { target = m_ctagsUi.targetList->item(i)->text(); if (target.endsWith(QLatin1Char('/')) || target.endsWith(QLatin1Char('\\'))) { target = target.left(target.size() - 1); } targets += target + QLatin1Char(' '); } QString pluginFolder = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/katectags"); QDir().mkpath(pluginFolder); if (m_ctagsUi.tagsFile->text().isEmpty()) { // FIXME we need a way to get the session name pluginFolder += QLatin1String("/session_db_"); pluginFolder += QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMdd_hhmmss")); m_ctagsUi.tagsFile->setText(pluginFolder); } if (targets.isEmpty()) { KMessageBox::error(nullptr, i18n("No folders or files to index")); QFile::remove(m_ctagsUi.tagsFile->text()); return; } QString command = QStringLiteral("%1 -f %2 %3").arg(m_ctagsUi.cmdEdit->text(), m_ctagsUi.tagsFile->text(), targets); m_proc.start(command); if(!m_proc.waitForStarted(500)) { KMessageBox::error(nullptr, i18n("Failed to run \"%1\". exitStatus = %2", command, m_proc.exitStatus())); return; } QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); m_ctagsUi.updateButton->setDisabled(true); m_ctagsUi.updateButton2->setDisabled(true); } /******************************************************************/ void KateCTagsView::updateDone(int exitCode, QProcess::ExitStatus status) { if (status == QProcess::CrashExit) { KMessageBox::error(m_toolView, i18n("The CTags executable crashed.")); } else if (exitCode != 0) { KMessageBox::error(m_toolView, i18n("The CTags program exited with code %1: %2" , exitCode , QString::fromLocal8Bit(m_proc.readAllStandardError()))); } m_ctagsUi.updateButton->setDisabled(false); m_ctagsUi.updateButton2->setDisabled(false); QApplication::restoreOverrideCursor(); } /******************************************************************/ void KateCTagsView::addTagTarget() { QFileDialog dialog; dialog.setDirectory(QFileInfo(m_mWin->activeView()->document()->url().path()).path()); dialog.setFileMode(QFileDialog::Directory); // i18n("CTags Database Location")); if (dialog.exec() != QDialog::Accepted) { return; } QStringList urls = dialog.selectedFiles(); for (int i=0; icurrentItem (); } /******************************************************************/ bool KateCTagsView::listContains(const QString &target) { for (int i=0; icount(); i++) { if (m_ctagsUi.targetList->item(i)->text() == target) { return true; } } return false; } /******************************************************************/ bool KateCTagsView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_mWin->hideToolView(m_toolView); event->accept(); return true; } } return QObject::eventFilter(obj, event); } /******************************************************************/ void KateCTagsView::resetCMD() { m_ctagsUi.cmdEdit->setText(DEFAULT_CTAGS_CMD); } /******************************************************************/ void KateCTagsView::handleEsc(QEvent *e) { if (!m_mWin) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_toolView->isVisible()) { m_mWin->hideToolView(m_toolView); } } } diff --git a/addons/openheader/plugin_kateopenheader.cpp b/addons/openheader/plugin_kateopenheader.cpp index 0c3812a9a..165798428 100644 --- a/addons/openheader/plugin_kateopenheader.cpp +++ b/addons/openheader/plugin_kateopenheader.cpp @@ -1,234 +1,234 @@ /* This file is part of the KDE project Copyright (C) 2001 Joseph Wenninger Copyright (C) 2009 Erlend Hamberg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plugin_kateopenheader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KateOpenHeaderFactory,"kateopenheaderplugin.json", registerPlugin();) //K_EXPORT_PLUGIN(KateOpenHeaderFactory(KAboutData("kateopenheader","kateopenheader",ki18n("Open Header"), "0.1", ki18n("Open header for a source file"), KAboutData::License_LGPL_V2)) ) PluginViewKateOpenHeader::PluginViewKateOpenHeader(PluginKateOpenHeader *plugin, KTextEditor::MainWindow *mainwindow) : KTextEditor::Command(QStringList() << QStringLiteral("toggle-header"), mainwindow) , KXMLGUIClient() , m_plugin(plugin) , m_mainWindow(mainwindow) { KXMLGUIClient::setComponentName (QStringLiteral("kateopenheaderplugin"), i18n ("Open Header")); setXMLFile( QStringLiteral("ui.rc") ); QAction *a = actionCollection()->addAction(QStringLiteral("file_openheader")); a->setText(i18n("Open .h/.cpp/.c")); actionCollection()->setDefaultShortcut(a, Qt::Key_F12 ); connect(a, &QAction::triggered, plugin, &PluginKateOpenHeader::slotOpenHeader); mainwindow->guiFactory()->addClient (this); } PluginViewKateOpenHeader::~PluginViewKateOpenHeader() { m_mainWindow->guiFactory()->removeClient (this); } PluginKateOpenHeader::PluginKateOpenHeader( QObject* parent, const QList& ) : KTextEditor::Plugin ( parent ) { } PluginKateOpenHeader::~PluginKateOpenHeader() { } QObject *PluginKateOpenHeader::createView (KTextEditor::MainWindow *mainWindow) { return new PluginViewKateOpenHeader(this,mainWindow); } void PluginKateOpenHeader::slotOpenHeader () { KTextEditor::Application *application=KTextEditor::Editor::instance()->application(); if (!application->activeMainWindow()) return; KTextEditor::View * kv (application->activeMainWindow()->activeView()); if (!kv) return; QUrl url=kv->document()->url(); if ((!url.isValid()) || (url.isEmpty())) return; qDebug() << "Trying to open opposite of " << url.toString(); qDebug() << "Trying to open opposite of toLocalFile:" << url.toLocalFile(); qDebug() << "Trying to open opposite of path:" << url.path(); QFileInfo info( url.path() ); QString extension = info.suffix().toLower(); QStringList headers( QStringList() << QStringLiteral("h") << QStringLiteral("H") << QStringLiteral("hh") << QStringLiteral("hpp") << QStringLiteral("cuh")); QStringList sources( QStringList() << QStringLiteral("c") << QStringLiteral("cpp") << QStringLiteral("cc") << QStringLiteral("cp") << QStringLiteral("cxx") << QStringLiteral("m")<< QStringLiteral("cu")); if( sources.contains( extension ) ) { if (tryOpenInternal(url, headers)) return; tryOpen( url, headers ); } else if ( headers.contains( extension ) ) { if (tryOpenInternal(url, sources)) return; tryOpen( url, sources ); } } bool PluginKateOpenHeader::tryOpenInternal( const QUrl& url, const QStringList& extensions ) { KTextEditor::Application *application=KTextEditor::Editor::instance()->application(); if (!application->activeMainWindow()) return false; qDebug() << "Trying to find already opened" << url.toString() << " with extensions " << extensions.join(QStringLiteral(" ")); QString basename = QFileInfo( url.path() ).baseName(); QUrl newURL( url ); - for( QStringList::ConstIterator it = extensions.begin(); it != extensions.end(); ++it ) { - setFileName( &newURL,basename + QStringLiteral(".") + *it ); + for(const auto& extension : extensions) { + setFileName( &newURL,basename + QStringLiteral(".") + extension ); KTextEditor::Document *doc= application->findUrl(newURL); if (doc) { application->activeMainWindow()->openUrl(newURL); return true; } - setFileName(&newURL, basename + QStringLiteral(".") + (*it).toUpper() ); + setFileName(&newURL, basename + QStringLiteral(".") + extension.toUpper() ); doc= application->findUrl(newURL); if (doc) { application->activeMainWindow()->openUrl(newURL); return true; } } return false; } void PluginKateOpenHeader::tryOpen( const QUrl& url, const QStringList& extensions ) { KTextEditor::Application *application=KTextEditor::Editor::instance()->application(); if (!application->activeMainWindow()) return; qDebug() << "Trying to open " << url.toString() << " with extensions " << extensions.join(QStringLiteral(" ")); QString basename = QFileInfo( url.path() ).baseName(); QUrl newURL( url ); - for( QStringList::ConstIterator it = extensions.begin(); it != extensions.end(); ++it ) { - setFileName( &newURL,basename + QStringLiteral(".") + *it ); + for(const auto& extension : extensions) { + setFileName( &newURL,basename + QStringLiteral(".") + extension ); if( fileExists( newURL) ) { application->activeMainWindow()->openUrl( newURL ); return; } - setFileName(&newURL, basename + QStringLiteral(".") + (*it).toUpper() ); + setFileName(&newURL, basename + QStringLiteral(".") + extension.toUpper() ); if( fileExists( newURL) ) { application->activeMainWindow()->openUrl( newURL ); return; } } } bool PluginKateOpenHeader::fileExists(const QUrl &url) { if (url.isLocalFile()) { return QFile::exists(url.toLocalFile()); } KIO::JobFlags flags = KIO::DefaultFlags; KIO::StatJob *job = KIO::stat(url, flags); KJobWidgets::setWindow(job, KTextEditor::Editor::instance()->application()->activeMainWindow()->window()); job->setSide(KIO::StatJob::DestinationSide/*SourceSide*/); job->exec(); return !job->error(); } void PluginKateOpenHeader::setFileName(QUrl *url,const QString &_txt) { url->setFragment(QString()); int i = 0; while (i < _txt.length() && _txt[i] == QLatin1Char('/')) { ++i; } QString tmp = i ? _txt.mid(i) : _txt; QString path = url->path(); if (path.isEmpty()) #ifdef Q_OS_WIN path = url->isLocalFile() ? QDir::rootPath() : QStringLiteral("/"); #else path = QDir::rootPath(); #endif else { int lastSlash = path.lastIndexOf(QLatin1Char('/')); if (lastSlash == -1) { path.clear(); // there's only the file name, remove it } else if (!path.endsWith(QLatin1Char('/'))) { path.truncate(lastSlash + 1); // keep the "/" } } path += tmp; url->setPath(path); } bool PluginViewKateOpenHeader::exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &) { Q_UNUSED(view) Q_UNUSED(cmd) Q_UNUSED(msg) m_plugin->slotOpenHeader(); return true; } bool PluginViewKateOpenHeader::help(KTextEditor::View *view, const QString &cmd, QString &msg) { Q_UNUSED(view) Q_UNUSED(cmd) msg = i18n("

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

" "

usage: toggle-header

" "

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

" "

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

" "

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

"); return true; } #include "plugin_kateopenheader.moc" diff --git a/addons/project/kateprojecttreeviewcontextmenu.cpp b/addons/project/kateprojecttreeviewcontextmenu.cpp index 0c4fdef2f..6ee0eafd2 100644 --- a/addons/project/kateprojecttreeviewcontextmenu.cpp +++ b/addons/project/kateprojecttreeviewcontextmenu.cpp @@ -1,161 +1,160 @@ /* This file is part of the Kate project. * * Copyright (C) 2013 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kateprojecttreeviewcontextmenu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KateProjectTreeViewContextMenu::KateProjectTreeViewContextMenu() { } KateProjectTreeViewContextMenu::~KateProjectTreeViewContextMenu() { } static bool isGit(const QString &filename) { QFileInfo fi(filename); QDir dir(fi.absoluteDir()); QProcess git; git.setWorkingDirectory(dir.absolutePath()); QStringList args; // git ls-files -z results a bytearray where each entry is \0-terminated. // NOTE: Without -z, Umlauts such as "Der Bäcker/Das Brötchen.txt" do not work (#389415, #402213) args << QStringLiteral("ls-files") << QStringLiteral("-z") << fi.fileName(); git.start(QStringLiteral("git"), args); bool isGit = false; if (git.waitForStarted() && git.waitForFinished(-1)) { const QList byteArrayList = git.readAllStandardOutput().split('\0'); const QString fn = fi.fileName(); for (const QByteArray & byteArray : byteArrayList) { if (fn == QString::fromUtf8(byteArray)) { isGit = true; break; } } } return isGit; } void KateProjectTreeViewContextMenu::exec(const QString &filename, const QPoint &pos, QWidget *parent) { /** * Create context menu */ QMenu menu; /** * Copy Path */ QAction *copyAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy File Path")); /** * Handle "open with", * find correct mimetype to query for possible applications */ QMenu *openWithMenu = menu.addMenu(i18n("Open With")); QMimeType mimeType = QMimeDatabase().mimeTypeForFile(filename); - KService::List offers = KMimeTypeTrader::self()->query(mimeType.name(), QStringLiteral("Application")); + const KService::List offers = KMimeTypeTrader::self()->query(mimeType.name(), QStringLiteral("Application")); // For each one, insert a menu item... - for (KService::List::Iterator it = offers.begin(); it != offers.end(); ++it) { - KService::Ptr service = *it; + for (const auto& service : offers) { if (service->name() == QStringLiteral("Kate")) { continue; // omit Kate } QAction *action = openWithMenu->addAction(QIcon::fromTheme(service->icon()), service->name()); action->setData(service->entryPath()); } // Perhaps disable menu, if no entries openWithMenu->setEnabled(!openWithMenu->isEmpty()); /** * Open Containing folder */ auto openContaingFolderAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("&Open Containing Folder")); /** * File Properties Dialog */ auto filePropertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-object-properties")), i18n("Properties")); /** * Git menu */ KMoreToolsMenuFactory menuFactory(QStringLiteral("kate/addons/project/git-tools")); QMenu gitMenu; // must live as long as the maybe filled menu items should live if (isGit(filename)) { menuFactory.fillMenuFromGroupingNames(&gitMenu, { QLatin1String("git-clients-and-actions") }, QUrl::fromLocalFile(filename)); menu.addSection(i18n("Git:")); Q_FOREACH(auto action, gitMenu.actions()) { menu.addAction(action); } } /** * run menu and handle the triggered action */ if (QAction* const action = menu.exec(pos)) { if (action == copyAction) { QApplication::clipboard()->setText(filename); } else if (action->parentWidget() == openWithMenu) { // handle "open with" const QString openWith = action->data().toString(); if (KService::Ptr app = KService::serviceByDesktopPath(openWith)) { QList list; list << QUrl::fromLocalFile(filename); KRun::runService(*app, list, parent); } } else if (action == openContaingFolderAction) { KIO::highlightInFileManager({ QUrl::fromLocalFile(filename) }); } else if (action == filePropertiesAction) { // code copied and adapted from frameworks/kio/src/filewidgets/knewfilemenu.cpp KFileItem fileItem(QUrl::fromLocalFile(filename)); QDialog* dlg = new KPropertiesDialog(fileItem); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->show(); } else { // One of the git actions was triggered } } } diff --git a/addons/search/FolderFilesList.cpp b/addons/search/FolderFilesList.cpp index df519ad30..f17cf0d7e 100644 --- a/addons/search/FolderFilesList.cpp +++ b/addons/search/FolderFilesList.cpp @@ -1,147 +1,147 @@ /* Kate search plugin * * Copyright (C) 2013 by Kåre Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "FolderFilesList.h" #include #include #include #include #include #include FolderFilesList::FolderFilesList(QObject *parent) : QThread(parent) {} FolderFilesList::~FolderFilesList() { m_cancelSearch = true; wait(); } void FolderFilesList::run() { m_files.clear(); QFileInfo folderInfo(m_folder); checkNextItem(folderInfo); if (m_cancelSearch) m_files.clear(); } void FolderFilesList::generateList(const QString &folder, bool recursive, bool hidden, bool symlinks, bool binary, const QString &types, const QString &excludes) { m_cancelSearch = false; m_folder = folder; if (!m_folder.endsWith(QLatin1Char('/'))) { m_folder += QLatin1Char('/'); } m_recursive = recursive; m_hidden = hidden; m_symlinks = symlinks; m_binary = binary; m_types.clear(); foreach (QString type, types.split(QLatin1Char(','), QString::SkipEmptyParts)) { m_types << type.trimmed(); } if (m_types.isEmpty()) { m_types << QStringLiteral("*"); } QStringList tmpExcludes = excludes.split(QLatin1Char(',')); m_excludeList.clear(); for (int i=0; i 100) { m_time.restart(); emit searching(item.absoluteFilePath()); } if (item.isFile()) { if (!m_binary) { QMimeType mimeType = QMimeDatabase().mimeTypeForFile(item); if (!mimeType.inherits(QStringLiteral("text/plain"))) { return; } } m_files << item.canonicalFilePath(); } else { QDir currentDir(item.absoluteFilePath()); if (!currentDir.isReadable()) { qDebug() << currentDir.absolutePath() << "Not readable"; return; } QDir::Filters filter = QDir::Files | QDir::NoDotAndDotDot | QDir::Readable; if (m_hidden) filter |= QDir::Hidden; if (m_recursive) filter |= QDir::AllDirs; if (!m_symlinks) filter |= QDir::NoSymLinks; // sort the items to have an deterministic order! const QFileInfoList currentItems = currentDir.entryInfoList(m_types, filter, QDir::Name | QDir::LocaleAware); bool skip; - for (int i = 0; i * * 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) { 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) { 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; + QVector typeList(tmpTypes.size()); for (int i=0; i excludeList; + QVector 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->setCurrentIndex(1); 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->setCurrentIndex(0); 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->setCurrentIndex(1); 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->setCurrentIndex(0); 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.nextButton, 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.nextButton, 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/snippets/snippetcompletionmodel.cpp b/addons/snippets/snippetcompletionmodel.cpp index 024ff55fb..0851c1de5 100644 --- a/addons/snippets/snippetcompletionmodel.cpp +++ b/addons/snippets/snippetcompletionmodel.cpp @@ -1,188 +1,188 @@ /* This file is part of the Kate project. * Based on the snippet plugin from KDevelop 4. * * Copyright (C) 2008 Andreas Pakulat * Copyright (C) 2012 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "snippetcompletionmodel.h" #include #include #include "snippetstore.h" #include "snippetrepository.h" #include "snippet.h" #include "snippetcompletionitem.h" #include SnippetCompletionModel::SnippetCompletionModel() : KTextEditor::CodeCompletionModel(nullptr) { setHasGroups(false); } SnippetCompletionModel::~SnippetCompletionModel() { qDeleteAll(m_snippets); m_snippets.clear(); } QVariant SnippetCompletionModel::data( const QModelIndex& idx, int role ) const { if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) return 11000; // grouping of snippets if (!idx.parent().isValid()) { if (role == Qt::DisplayRole) { return i18n("Snippets"); } if (role == KTextEditor::CodeCompletionModel::GroupRole) { return Qt::DisplayRole; } return QVariant(); } // snippets if( !idx.isValid() || idx.row() < 0 || idx.row() >= m_snippets.count() ) { return QVariant(); } else { return m_snippets.at( idx.row() )->data(idx, role, nullptr); } } void SnippetCompletionModel::executeCompletionItem (KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const { if ( index.parent().isValid() ) { m_snippets[index.row()]->execute(view, word); } } void SnippetCompletionModel::completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType) { Q_UNUSED( range ); Q_UNUSED( invocationType ); initData(view); } void SnippetCompletionModel::initData(KTextEditor::View* view) { QString mode = view->document()->highlightingModeAt(view->cursorPosition()); if ( mode.isEmpty() ) { mode = view->document()->highlightingMode(); } beginResetModel(); qDeleteAll(m_snippets); m_snippets.clear(); SnippetStore* store = SnippetStore::self(); for(int i = 0; i < store->rowCount(); i++ ) { if ( store->item(i, 0)->checkState() != Qt::Checked ) { continue; } SnippetRepository* repo = dynamic_cast( store->item( i, 0 ) ); if( repo && (repo->fileTypes().isEmpty() || repo->fileTypes().contains(mode)) ) { for ( int j = 0; j < repo->rowCount(); ++j ) { if ( Snippet* snippet = dynamic_cast(repo->child(j)) ) { m_snippets << new SnippetCompletionItem(snippet, repo); } } } } endResetModel(); } QModelIndex SnippetCompletionModel::parent(const QModelIndex& index) const { if (index.internalId()) { return createIndex(0, 0, quintptr (0)); } else { return QModelIndex(); } } QModelIndex SnippetCompletionModel::index(int row, int column, const QModelIndex& parent) const { if (!parent.isValid()) { if (row == 0) { return createIndex(row, column, quintptr (0)); //header index } else { return QModelIndex(); } } else if (parent.parent().isValid()) { //we only have header and children, no subheaders return QModelIndex(); } if (row < 0 || row >= m_snippets.count() || column < 0 || column >= ColumnCount ) { return QModelIndex(); } return createIndex(row, column, 1); // normal item index } int SnippetCompletionModel::rowCount (const QModelIndex & parent) const { if (!parent.isValid() && !m_snippets.isEmpty()) { return 1; //one toplevel node (group header) } else if(parent.parent().isValid()) { return 0; //we don't have sub children } else { return m_snippets.count(); // only the children } } KTextEditor::Range SnippetCompletionModel::completionRange(KTextEditor::View* view, const KTextEditor::Cursor& position) { const QString& line = view->document()->line(position.line()); KTextEditor::Range range(position, position); // include everything non-space before for ( int i = position.column() - 1; i >= 0; --i ) { if ( line.at(i).isSpace() ) { break; } else { range.setStart(KTextEditor::Cursor (range.start().line(), i)); } } // include everything non-space after for ( int i = position.column() + 1; i < line.length(); ++i ) { if ( line.at(i).isSpace() ) { break; } else { range.setEnd(KTextEditor::Cursor (range.end().line(), i)); } } return range; } bool SnippetCompletionModel::shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range& range, const QString& currentCompletion) { if(view->cursorPosition() < range.start() || view->cursorPosition() > range.end()) { return true; //Always abort when the completion-range has been left } - for ( int i = 0; i < currentCompletion.length(); ++i ) { - if ( currentCompletion.at(i).isSpace() ) { + for (const auto token : currentCompletion) { + if ( token.isSpace() ) { return true; } } // else it's valid return false; } diff --git a/addons/xmltools/plugin_katexmltools.cpp b/addons/xmltools/plugin_katexmltools.cpp index 1cf116cdc..9d91864a1 100644 --- a/addons/xmltools/plugin_katexmltools.cpp +++ b/addons/xmltools/plugin_katexmltools.cpp @@ -1,1114 +1,1113 @@ /*************************************************************************** pluginKatexmltools.cpp List elements, attributes, attribute values and entities allowed by DTD. Needs a DTD in XML format ( as produced by dtdparse ) for most features. copyright : ( C ) 2001-2002 by Daniel Naber email : daniel.naber@t-online.de Copyright (C) 2005 by Anders Lund KDE SC 4 version (C) 2010 Tomas Trnka ***************************************************************************/ /*************************************************************************** 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. ***************************************************************************/ /* README: The basic idea is this: certain keyEvents(), namely [<&" ], trigger a completion box. This is intended as a help for editing. There are some cases where the XML spec is not followed, e.g. one can add the same attribute twice to an element. Also see the user documentation. If backspace is pressed after a completion popup was closed, the popup will re-open. This way typos can be corrected and the popup will reappear, which is quite comfortable. FIXME: -( docbook ) : insert space between the quotes, press "de" and return -> only "d" inserted -The "Insert Element" dialog isn't case insensitive, but it should be -See the "fixme"'s in the code TODO: -check for mem leaks -add "Go to opening/parent tag"? -check doctype to get top-level element -can undo behaviour be improved?, e.g. the plugins internal deletions of text don't have to be an extra step -don't offer entities if inside tag but outside attribute value -Support for more than one namespace at the same time ( e.g. XSLT + XSL-FO )? =>This could also be handled in the XSLT DTD fragment, as described in the XSLT 1.0 spec, but then at it will only show you HTML elements! =>So better "Assign meta DTD" and "Add meta DTD", the latter will expand the current meta DTD -Option to insert empty element in form -Show expanded entities with QChar::QChar( int rc ) + unicode font -Don't ignore entities defined in the document's prologue -Only offer 'valid' elements, i.e. don't take the elements as a set but check if the DTD is matched ( order, number of occurrences, ... ) -Maybe only read the meta DTD file once, then store the resulting QMap on disk ( using QDataStream )? We'll then have to compare timeOf_cacheFile <-> timeOf_metaDtd. -Try to use libxml */ #include "plugin_katexmltools.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(PluginKateXMLToolsFactory, "katexmltools.json", registerPlugin();) PluginKateXMLTools::PluginKateXMLTools(QObject *const parent, const QVariantList &) : KTextEditor::Plugin(parent) { } PluginKateXMLTools::~PluginKateXMLTools() { } QObject *PluginKateXMLTools::createView(KTextEditor::MainWindow *mainWindow) { return new PluginKateXMLToolsView(mainWindow); } PluginKateXMLToolsView::PluginKateXMLToolsView(KTextEditor::MainWindow *mainWin) : QObject(mainWin) , KXMLGUIClient() , m_mainWindow(mainWin) , m_model(this) { //qDebug() << "PluginKateXMLTools constructor called"; KXMLGUIClient::setComponentName(QStringLiteral("katexmltools"), i18n("Kate XML Tools")); setXMLFile(QStringLiteral("ui.rc")); QAction *actionInsert = new QAction(i18n("&Insert Element..."), this); connect(actionInsert, &QAction::triggered, &m_model, &PluginKateXMLToolsCompletionModel::slotInsertElement); actionCollection()->addAction(QStringLiteral("xml_tool_insert_element"), actionInsert); actionCollection()->setDefaultShortcut(actionInsert, Qt::CTRL + Qt::Key_Return); QAction *actionClose = new QAction(i18n("&Close Element"), this); connect(actionClose, &QAction::triggered, &m_model, &PluginKateXMLToolsCompletionModel::slotCloseElement); actionCollection()->addAction(QStringLiteral("xml_tool_close_element"), actionClose); actionCollection()->setDefaultShortcut(actionClose, Qt::CTRL + Qt::Key_Less); QAction *actionAssignDTD = new QAction(i18n("Assign Meta &DTD..."), this); connect(actionAssignDTD, &QAction::triggered, &m_model, &PluginKateXMLToolsCompletionModel::getDTD); actionCollection()->addAction(QStringLiteral("xml_tool_assign"), actionAssignDTD); mainWin->guiFactory()->addClient(this); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentDeleted, &m_model, &PluginKateXMLToolsCompletionModel::slotDocumentDeleted); } PluginKateXMLToolsView::~PluginKateXMLToolsView() { m_mainWindow->guiFactory()->removeClient(this); //qDebug() << "xml tools destructor 1..."; //TODO: unregister the model } PluginKateXMLToolsCompletionModel::PluginKateXMLToolsCompletionModel(QObject *const parent) : CodeCompletionModel(parent) , m_viewToAssignTo(nullptr) , m_mode(none) , m_correctPos(0) { } PluginKateXMLToolsCompletionModel::~PluginKateXMLToolsCompletionModel() { qDeleteAll(m_dtds); m_dtds.clear(); } void PluginKateXMLToolsCompletionModel::slotDocumentDeleted(KTextEditor::Document *doc) { // Remove the document from m_DTDs, and also delete the PseudoDTD // if it becomes unused. if (m_docDtds.contains(doc)) { qDebug() << "XMLTools:slotDocumentDeleted: documents: " << m_docDtds.count() << ", DTDs: " << m_dtds.count(); PseudoDTD *dtd = m_docDtds.take(doc); if (m_docDtds.key(dtd)) { return; } QHash::iterator it; for (it = m_dtds.begin() ; it != m_dtds.end() ; ++it) { if (it.value() == dtd) { m_dtds.erase(it); delete dtd; return; } } } } void PluginKateXMLToolsCompletionModel::completionInvoked(KTextEditor::View *kv, const KTextEditor::Range &range, const InvocationType invocationType) { Q_UNUSED(range) Q_UNUSED(invocationType) qDebug() << "xml tools completionInvoked"; KTextEditor::Document *doc = kv->document(); if (! m_docDtds[ doc ]) // no meta DTD assigned yet { return; } // debug to test speed: //QTime t; t.start(); beginResetModel(); m_allowed.clear(); // get char on the left of the cursor: KTextEditor::Cursor curpos = kv->cursorPosition(); uint line = curpos.line(), col = curpos.column(); QString lineStr = kv->document()->line(line); QString leftCh = lineStr.mid(col - 1, 1); QString secondLeftCh = lineStr.mid(col - 2, 1); if (leftCh == QLatin1String("&")) { qDebug() << "Getting entities"; m_allowed = m_docDtds[doc]->entities(QString()); m_mode = entities; } else if (leftCh == QLatin1String("<")) { qDebug() << "*outside tag -> get elements"; QString parentElement = getParentElement(*kv, 1); qDebug() << "parent: " << parentElement; m_allowed = m_docDtds[doc]->allowedElements(parentElement); m_mode = elements; } else if (leftCh == QLatin1String("/") && secondLeftCh == QLatin1String("<")) { qDebug() << "*close parent element"; QString parentElement = getParentElement(*kv, 2); if (! parentElement.isEmpty()) { m_mode = closingtag; m_allowed = QStringList(parentElement); } } else if (leftCh == QLatin1String(" ") || (isQuote(leftCh) && secondLeftCh == QLatin1String("="))) { // TODO: check secondLeftChar, too?! then you don't need to trigger // with space and we yet save CPU power QString currentElement = insideTag(*kv); QString currentAttribute; if (! currentElement.isEmpty()) { currentAttribute = insideAttribute(*kv); } qDebug() << "Tag: " << currentElement; qDebug() << "Attr: " << currentAttribute; if (! currentElement.isEmpty() && ! currentAttribute.isEmpty()) { qDebug() << "*inside attribute -> get attribute values"; m_allowed = m_docDtds[doc]->attributeValues(currentElement, currentAttribute); if (m_allowed.count() == 1 && (m_allowed[0] == QLatin1String("CDATA") || m_allowed[0] == QLatin1String("ID") || m_allowed[0] == QLatin1String("IDREF") || m_allowed[0] == QLatin1String("IDREFS") || m_allowed[0] == QLatin1String("ENTITY") || m_allowed[0] == QLatin1String("ENTITIES") || m_allowed[0] == QLatin1String("NMTOKEN") || m_allowed[0] == QLatin1String("NMTOKENS") || m_allowed[0] == QLatin1String("NAME"))) { // these must not be taken literally, e.g. don't insert the string "CDATA" m_allowed.clear(); } else { m_mode = attributevalues; } } else if (! currentElement.isEmpty()) { qDebug() << "*inside tag -> get attributes"; m_allowed = m_docDtds[doc]->allowedAttributes(currentElement); m_mode = attributes; } } //qDebug() << "time elapsed (ms): " << t.elapsed(); qDebug() << "Allowed strings: " << m_allowed.count(); if (m_allowed.count() >= 1 && m_allowed[0] != QLatin1String("__EMPTY")) { m_allowed = sortQStringList(m_allowed); } setRowCount(m_allowed.count()); endResetModel(); } int PluginKateXMLToolsCompletionModel::columnCount(const QModelIndex &) const { return 1; } int PluginKateXMLToolsCompletionModel::rowCount(const QModelIndex &parent) const { if (!m_allowed.isEmpty()) { // Is there smth to complete? if (!parent.isValid()) { // Return the only one group node for root return 1; } if (parent.internalId() == groupNode) { // Return available rows count for group level node return m_allowed.size(); } } return 0; } QModelIndex PluginKateXMLToolsCompletionModel::parent(const QModelIndex &index) const { if (!index.isValid()) { // Is root/invalid index? return QModelIndex(); // Nothing to return... } if (index.internalId() == groupNode) { // Return a root node for group return QModelIndex(); } // Otherwise, this is a leaf level, so return the only group as a parent return createIndex(0, 0, groupNode); } QModelIndex PluginKateXMLToolsCompletionModel::index(const int row, const int column, const QModelIndex &parent) const { if (!parent.isValid()) { // At 'top' level only 'header' present, so nothing else than row 0 can be here... return row == 0 ? createIndex(row, column, groupNode) : QModelIndex(); } if (parent.internalId() == groupNode) { // Is this a group node? if (0 <= row && row < m_allowed.size()) { // Make sure to return only valid indices return createIndex(row, column, (void *)nullptr); // Just return a leaf-level index } } // Leaf node has no children... nothing to return return QModelIndex(); } QVariant PluginKateXMLToolsCompletionModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { // Nothing to do w/ invalid index return QVariant(); } if (index.internalId() == groupNode) { // Return group level node data switch (role) { case KTextEditor::CodeCompletionModel::GroupRole: return QVariant(Qt::DisplayRole); case Qt::DisplayRole: return currentModeToString(); default: break; } return QVariant(); // Nothing to return for other roles } switch (role) { case Qt::DisplayRole: switch (index.column()) { case KTextEditor::CodeCompletionModel::Name: return m_allowed.at(index.row()); default: break; } default: break; } return QVariant(); } bool PluginKateXMLToolsCompletionModel::shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) { Q_UNUSED(view) Q_UNUSED(userInsertion) Q_UNUSED(position) const QString triggerChars = QStringLiteral("&application()->activeMainWindow()) { return; } KTextEditor::View *kv = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView(); if (! kv) { qDebug() << "Warning: no KTextEditor::View"; return; } // ### replace this with something more sane // Start where the supplied XML-DTDs are fed by default unless // user changed directory last time: QString defaultDir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("katexmltools")) + "/katexmltools/"; if (m_urlString.isNull()) { m_urlString = defaultDir; } // Guess the meta DTD by looking at the doctype's public identifier. // XML allows comments etc. before the doctype, so look further than // just the first line. // Example syntax: // uint checkMaxLines = 200; QString documentStart = kv->document()->text(KTextEditor::Range(0, 0, checkMaxLines + 1, 0)); QRegExp re(" */ filename = QStringLiteral("xslt-1.0.dtd.xml"); doctype = QStringLiteral("XSLT 1.0"); } else { qDebug() << "No doctype found"; } QUrl url; if (filename.isEmpty()) { // no meta dtd found for this file url = QFileDialog::getOpenFileUrl(KTextEditor::Editor::instance()->application()->activeMainWindow()->window(), i18n("Assign Meta DTD in XML Format"), QUrl::fromLocalFile(m_urlString), QStringLiteral("*.xml")); } else { url.setUrl(defaultDir + filename); KMessageBox::information(nullptr, i18n("The current file has been identified " "as a document of type \"%1\". The meta DTD for this document type " "will now be loaded.", doctype), i18n("Loading XML Meta DTD"), QStringLiteral("DTDAssigned")); } if (url.isEmpty()) { return; } m_urlString = url.url(); // remember directory for next time if (m_dtds[ m_urlString ]) { assignDTD(m_dtds[ m_urlString ], kv); } else { m_dtdString.clear(); m_viewToAssignTo = kv; QGuiApplication::setOverrideCursor(Qt::WaitCursor); KIO::TransferJob *job = KIO::get(url); connect(job, &KIO::TransferJob::result, this, &PluginKateXMLToolsCompletionModel::slotFinished); connect(job, &KIO::TransferJob::data, this, &PluginKateXMLToolsCompletionModel::slotData); } qDebug() << "XMLTools::getDTD: Documents: " << m_docDtds.count() << ", DTDs: " << m_dtds.count(); } void PluginKateXMLToolsCompletionModel::slotFinished(KJob *job) { if (job->error()) { //qDebug() << "XML Plugin error: DTD in XML format (" << filename << " ) could not be loaded"; static_cast(job)->uiDelegate()->showErrorMessage(); } else if (static_cast(job)->isErrorPage()) { // catch failed loading loading via http: KMessageBox::error(nullptr, i18n("The file '%1' could not be opened. " "The server returned an error.", m_urlString), i18n("XML Plugin Error")); } else { PseudoDTD *dtd = new PseudoDTD(); dtd->analyzeDTD(m_urlString, m_dtdString); m_dtds.insert(m_urlString, dtd); assignDTD(dtd, m_viewToAssignTo); // clean up a bit m_viewToAssignTo = nullptr; m_dtdString.clear(); } QGuiApplication::restoreOverrideCursor(); } void PluginKateXMLToolsCompletionModel::slotData(KIO::Job *, const QByteArray &data) { m_dtdString += QString(data); } void PluginKateXMLToolsCompletionModel::assignDTD(PseudoDTD *dtd, KTextEditor::View *view) { m_docDtds.insert(view->document(), dtd); //TODO:perhaps foreach views()? KTextEditor::CodeCompletionInterface *cci = qobject_cast(view); if (cci) { cci->registerCompletionModel(this); cci->setAutomaticInvocationEnabled(true); qDebug() << "PluginKateXMLToolsView: completion model registered"; } else { qWarning() << "PluginKateXMLToolsView: completion interface unavailable"; } } /** * Offer a line edit with completion for possible elements at cursor position and insert the * tag one chosen/entered by the user, plus its closing tag. If there's a text selection, * add the markup around it. */ void PluginKateXMLToolsCompletionModel::slotInsertElement() { if (!KTextEditor::Editor::instance()->application()->activeMainWindow()) { return; } KTextEditor::View *kv = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView(); if (! kv) { qDebug() << "Warning: no KTextEditor::View"; return; } KTextEditor::Document *doc = kv->document(); PseudoDTD *dtd = m_docDtds[doc]; QString parentElement = getParentElement(*kv, 0); QStringList allowed; if (dtd) { allowed = dtd->allowedElements(parentElement); } QString text; InsertElement dialog(allowed, kv); if (dialog.exec() == QDialog::Accepted) { text = dialog.text(); } if (!text.isEmpty()) { QStringList list = text.split(QChar(' ')); QString pre; QString post; // anders: use if the tag is required to be empty. // In that case maybe we should not remove the selection? or overwrite it? int adjust = 0; // how much to move cursor. // if we know that we have attributes, it goes // just after the tag name, otherwise between tags. if (dtd && dtd->allowedAttributes(list[0]).count()) { adjust++; // the ">" } if (dtd && dtd->allowedElements(list[0]).contains(QStringLiteral("__EMPTY"))) { pre = '<' + text + "/>"; if (adjust) { adjust++; // for the "/" } } else { pre = '<' + text + '>'; post = "'; } QString marked; if (! post.isEmpty()) { marked = kv->selectionText(); } KTextEditor::Document::EditingTransaction transaction(doc); if (! marked.isEmpty()) { kv->removeSelectionText(); } // with the old selection now removed, curPos points to the start of pre KTextEditor::Cursor curPos = kv->cursorPosition(); curPos.setColumn(curPos.column() + pre.length() - adjust); kv->insertText(pre + marked + post); kv->setCursorPosition(curPos); } } /** * Insert a closing tag for the nearest not-closed parent element. */ void PluginKateXMLToolsCompletionModel::slotCloseElement() { if (!KTextEditor::Editor::instance()->application()->activeMainWindow()) { return; } KTextEditor::View *kv = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView(); if (! kv) { qDebug() << "Warning: no KTextEditor::View"; return; } QString parentElement = getParentElement(*kv, 0); //qDebug() << "parentElement: '" << parentElement << "'"; QString closeTag = "'; if (! parentElement.isEmpty()) { kv->insertText(closeTag); } } // modify the completion string before it gets inserted void PluginKateXMLToolsCompletionModel::executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const { KTextEditor::Range toReplace = word; KTextEditor::Document *document = view->document(); QString text = data(index.sibling(index.row(), Name), Qt::DisplayRole).toString(); qDebug() << "executeCompletionItem text: " << text; int line, col; view->cursorPosition().position(line, col); QString lineStr = document->line(line); QString leftCh = lineStr.mid(col - 1, 1); QString rightCh = lineStr.mid(col, 1); int posCorrection = 0; // where to move the cursor after completion ( >0 = move right ) if (m_mode == entities) { text = text + ';'; } else if (m_mode == attributes) { text = text + "=\"\""; posCorrection = -1; if (!rightCh.isEmpty() && rightCh != QLatin1String(">") && rightCh != QLatin1String("/") && rightCh != QLatin1String(" ")) { // TODO: other whitespaces // add space in front of the next attribute text = text + ' '; posCorrection--; } } else if (m_mode == attributevalues) { // TODO: support more than one line uint startAttValue = 0; uint endAttValue = 0; // find left quote: for (startAttValue = col; startAttValue > 0; startAttValue--) { QString ch = lineStr.mid(startAttValue - 1, 1); if (isQuote(ch)) { break; } } // find right quote: for (endAttValue = col; endAttValue <= (uint) lineStr.length(); endAttValue++) { QString ch = lineStr.mid(endAttValue - 1, 1); if (isQuote(ch)) { break; } } // replace the current contents of the attribute if (startAttValue < endAttValue) { toReplace = KTextEditor::Range(line, startAttValue, line, endAttValue - 1); } } else if (m_mode == elements) { // anders: if the tag is marked EMPTY, insert in form QString str; bool isEmptyTag = m_docDtds[document]->allowedElements(text).contains(QStringLiteral("__EMPTY")); if (isEmptyTag) { str = text + "/>"; } else { str = text + ">'; } // Place the cursor where it is most likely wanted: // always inside the tag if the tag is empty AND the DTD indicates that there are attribs) // outside for open tags, UNLESS there are mandatory attributes if (m_docDtds[document]->requiredAttributes(text).count() || (isEmptyTag && m_docDtds[document]->allowedAttributes(text).count())) { posCorrection = text.length() - str.length(); } else if (! isEmptyTag) { posCorrection = text.length() - str.length() + 1; } text = str; } else if (m_mode == closingtag) { text += '>'; } document->replaceText(toReplace, text); // move the cursor to desired position KTextEditor::Cursor curPos = view->cursorPosition(); curPos.setColumn(curPos.column() + posCorrection); view->setCursorPosition(curPos); } // ======================================================================== // Pseudo-XML stuff: /** * Check if cursor is inside a tag, that is * if "<" occurs before ">" occurs ( on the left side of the cursor ). * Return the tag name, return "" if we cursor is outside a tag. */ QString PluginKateXMLToolsCompletionModel::insideTag(KTextEditor::View &kv) { int line, col; kv.cursorPosition().position(line, col); int y = line; // another variable because uint <-> int do { QString lineStr = kv.document()->line(y); for (uint x = col; x > 0; x--) { QString ch = lineStr.mid(x - 1, 1); if (ch == QLatin1String(">")) { // cursor is outside tag return QString(); } if (ch == QLatin1String("<")) { QString tag; // look for white space on the right to get the tag name for (int z = x; z <= lineStr.length() ; ++z) { ch = lineStr.mid(z - 1, 1); if (ch.at(0).isSpace() || ch == QLatin1String("/") || ch == QLatin1String(">")) { return tag.right(tag.length() - 1); } if (z == lineStr.length()) { tag += ch; return tag.right(tag.length() - 1); } tag += ch; } } } y--; col = kv.document()->line(y).length(); } while (y >= 0); return QString(); } /** * Check if cursor is inside an attribute value, that is * if '="' is on the left, and if it's nearer than "<" or ">". * * @Return the attribute name or "" if we're outside an attribute * value. * * Note: only call when insideTag() == true. * TODO: allow whitespace around "=" */ QString PluginKateXMLToolsCompletionModel::insideAttribute(KTextEditor::View &kv) { int line, col; kv.cursorPosition().position(line, col); int y = line; // another variable because uint <-> int uint x = 0; QString lineStr; QString ch; do { lineStr = kv.document()->line(y); for (x = col; x > 0; x--) { ch = lineStr.mid(x - 1, 1); QString chLeft = lineStr.mid(x - 2, 1); // TODO: allow whitespace if (isQuote(ch) && chLeft == QLatin1String("=")) { break; } else if (isQuote(ch) && chLeft != QLatin1String("=")) { return QString(); } else if (ch == QLatin1String("<") || ch == QLatin1String(">")) { return QString(); } } y--; col = kv.document()->line(y).length(); } while (!isQuote(ch)); // look for next white space on the left to get the tag name QString attr; for (int z = x; z >= 0; z--) { ch = lineStr.mid(z - 1, 1); if (ch.at(0).isSpace()) { break; } if (z == 0) { // start of line == whitespace attr += ch; break; } attr = ch + attr; } return attr.left(attr.length() - 2); } /** * Find the parent element for the current cursor position. That is, * go left and find the first opening element that's not closed yet, * ignoring empty elements. * Examples: If cursor is at "X", the correct parent element is "p": *

foo bar X *

foo bar X *

foo bar X */ QString PluginKateXMLToolsCompletionModel::getParentElement(KTextEditor::View &kv, int skipCharacters) { enum { parsingText, parsingElement, parsingElementBoundary, parsingNonElement, parsingAttributeDquote, parsingAttributeSquote, parsingIgnore } parseState; parseState = (skipCharacters > 0) ? parsingIgnore : parsingText; int nestingLevel = 0; int line, col; kv.cursorPosition().position(line, col); QString str = kv.document()->line(line); while (true) { // move left a character if (!col--) { do { if (!line--) { return QString(); // reached start of document } str = kv.document()->line(line); col = str.length(); } while (!col); --col; } ushort ch = str.at(col).unicode(); switch (parseState) { case parsingIgnore: // ignore the specified number of characters parseState = (--skipCharacters > 0) ? parsingIgnore : parsingText; break; case parsingText: switch (ch) { case '<': // hmm... we were actually inside an element return QString(); case '>': // we just hit an element boundary parseState = parsingElementBoundary; break; } break; case parsingElement: switch (ch) { case '"': // attribute ( double quoted ) parseState = parsingAttributeDquote; break; case '\'': // attribute ( single quoted ) parseState = parsingAttributeSquote; break; case '/': // close tag parseState = parsingNonElement; ++nestingLevel; break; case '<': // we just hit the start of the element... if (nestingLevel--) { break; } QString tag = str.mid(col + 1); for (uint pos = 0, len = tag.length(); pos < len; ++pos) { ch = tag.at(pos).unicode(); if (ch == ' ' || ch == '\t' || ch == '>') { tag.truncate(pos); break; } } return tag; } break; case parsingElementBoundary: switch (ch) { case '?': // processing instruction case '-': // comment case '/': // empty element parseState = parsingNonElement; break; case '"': parseState = parsingAttributeDquote; break; case '\'': parseState = parsingAttributeSquote; break; case '<': // empty tag ( bad XML ) parseState = parsingText; break; default: parseState = parsingElement; } break; case parsingAttributeDquote: if (ch == '"') { parseState = parsingElement; } break; case parsingAttributeSquote: if (ch == '\'') { parseState = parsingElement; } break; case parsingNonElement: if (ch == '<') { parseState = parsingText; } break; } } } /** * Return true if the tag is neither a closing tag * nor an empty tag, nor a comment, nor processing instruction. */ bool PluginKateXMLToolsCompletionModel::isOpeningTag(const QString &tag) { return (!isClosingTag(tag) && !isEmptyTag(tag) && !tag.startsWith(QLatin1String("")); } /** * Return true if ch is a single or double quote. Expects ch to be of length 1. */ bool PluginKateXMLToolsCompletionModel::isQuote(const QString &ch) { return (ch == QLatin1String("\"") || ch == QLatin1String("'")); } // ======================================================================== // Tools: /// Get string describing current mode QString PluginKateXMLToolsCompletionModel::currentModeToString() const { switch (m_mode) { case entities: return i18n("XML entities"); case attributevalues: return i18n("XML attribute values"); case attributes: return i18n("XML attributes"); case elements: case closingtag: return i18n("XML elements"); default: break; } return QString(); } /** Sort a QStringList case-insensitively. Static. TODO: make it more simple. */ QStringList PluginKateXMLToolsCompletionModel::sortQStringList(QStringList list) { // Sort list case-insensitive. This looks complicated but using a QMap // is even suggested by the Qt documentation. QMap mapList; - for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { - QString str = *it; + for (const auto& str : qAsConst(list)) { if (mapList.contains(str.toLower())) { // do not override a previous value, e.g. "Auml" and "auml" are two different // entities, but they should be sorted next to each other. // TODO: currently it's undefined if e.g. "A" or "a" comes first, it depends on // the meta DTD ( really? it seems to work okay?!? ) mapList[str.toLower() + '_'] = str; } else { mapList[str.toLower()] = str; } } list.clear(); QMap::Iterator it; // Qt doc: "the items are alphabetically sorted [by key] when iterating over the map": for (it = mapList.begin(); it != mapList.end(); ++it) { list.append(it.value()); } return list; } //BEGIN InsertElement dialog InsertElement::InsertElement(const QStringList & completions, QWidget * parent) : QDialog(parent) { setWindowTitle(i18n("Insert XML Element")); QVBoxLayout *topLayout = new QVBoxLayout(this); // label QString text = i18n("Enter XML tag name and attributes (\"<\", \">\" and closing tag will be supplied):"); QLabel *label = new QLabel(text, this); label->setWordWrap(true); // combo box m_cmbElements = new KHistoryComboBox(this); static_cast(m_cmbElements)->setHistoryItems(completions, true); connect(m_cmbElements->lineEdit(), &QLineEdit::textChanged, this, &InsertElement::slotHistoryTextChanged); // button box QDialogButtonBox * box = new QDialogButtonBox(this); box->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_okButton = box->button(QDialogButtonBox::Ok); m_okButton->setDefault(true); connect(box, &QDialogButtonBox::accepted, this, &InsertElement::accept); connect(box, &QDialogButtonBox::rejected, this, &InsertElement::reject); // fill layout topLayout->addWidget(label); topLayout->addWidget(m_cmbElements); topLayout->addWidget(box); m_cmbElements->setFocus(); // make sure the ok button is enabled/disabled correctly slotHistoryTextChanged(m_cmbElements->lineEdit()->text()); } InsertElement::~InsertElement() { } void InsertElement::slotHistoryTextChanged(const QString &text) { m_okButton->setEnabled(!text.isEmpty()); } QString InsertElement::text() const { return m_cmbElements->currentText(); } //END InsertElement dialog #include "plugin_katexmltools.moc" // kate: space-indent on; indent-width 4; replace-tabs on; mixed-indent off; diff --git a/kate/kateapp.h b/kate/kateapp.h index d593fb9fc..f30ec7b52 100644 --- a/kate/kateapp.h +++ b/kate/kateapp.h @@ -1,374 +1,376 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KATE_APP_H__ #define __KATE_APP_H__ #include #include "kateprivate_export.h" #include "katemainwindow.h" #include "katedocmanager.h" #include "katepluginmanager.h" #include "katesessionmanager.h" #include "kateappadaptor.h" #include #include class KateSessionManager; class KateMainWindow; class KatePluginManager; class KateDocManager; class KateAppCommands; class KateAppAdaptor; class QCommandLineParser; /** * Kate Application * This class represents the core kate application object */ class KATE_TESTS_EXPORT KateApp : public QObject { Q_OBJECT /** * constructors & accessor to app object + plugin interface for it */ public: /** * application constructor */ KateApp(const QCommandLineParser &args); /** * get kate inited * @return false, if application should exit */ bool init(); /** * application destructor */ ~KateApp() override; /** * static accessor to avoid casting ;) * @return app instance */ static KateApp *self(); /** * KTextEditor::Application wrapper * @return KTextEditor::Application wrapper. */ KTextEditor::Application *wrapper() { return &m_wrapper; } /** * kate init */ private: /** * restore a old kate session */ void restoreKate(); /** * try to start kate * @return success, if false, kate should exit */ bool startupKate(); /** * kate shutdown */ public: /** * shutdown kate application * @param win mainwindow which is used for dialogs */ void shutdownKate(KateMainWindow *win); /** * other accessors for global unique instances */ public: /** * accessor to plugin manager * @return plugin manager instance */ KatePluginManager *pluginManager(); /** * accessor to document manager * @return document manager instance */ KateDocManager *documentManager(); /** * accessor to session manager * @return session manager instance */ KateSessionManager *sessionManager(); /** * window management */ public: /** * create a new main window, use given config if any for restore * @param sconfig session config object * @param sgroup session group for this window * @return new constructed main window */ KateMainWindow *newMainWindow(KConfig *sconfig = nullptr, const QString &sgroup = QString()); /** * add the mainwindow given * should be called in mainwindow constructor * @param mainWindow window to remove */ void addMainWindow(KateMainWindow *mainWindow); /** * removes the mainwindow given, DOES NOT DELETE IT * should be called in mainwindow destructor * @param mainWindow window to remove */ void removeMainWindow(KateMainWindow *mainWindow); /** * give back current active main window * can only be 0 at app start or exit * @return current active main window */ KateMainWindow *activeKateMainWindow(); /** * give back number of existing main windows * @return number of main windows */ int mainWindowsCount() const; /** * give back the window you want * @param n window index * @return requested main window */ KateMainWindow *mainWindow(int n); int mainWindowID(KateMainWindow *window); /** * some stuff for the dcop API */ public: /** * open url with given encoding * used by kate if --use given * @param url filename * @param encoding encoding name * @param isTempFile whether the file is temporary * @return success */ bool openUrl(const QUrl &url, const QString &encoding, bool isTempFile); /** * checks if the current instance is in a given activity * @param activity activity to check * @return true if the window is in the given activity, false otherwise */ bool isOnActivity(const QString &activity); KTextEditor::Document *openDocUrl(const QUrl &url, const QString &encoding, bool isTempFile); void emitDocumentClosed(const QString &token); /** * position cursor in current active view * will clear selection * @param line line to set * @param column column to set * @return success */ bool setCursor(int line, int column); /** * helper to handle stdin input * open a new document/view, fill it with the text given * @param text text to fill in the new doc/view * @param encoding encoding to set for the document, if any * @return success */ bool openInput(const QString &text, const QString &encoding); // // KTextEditor::Application interface, called by wrappers via invokeMethod // public Q_SLOTS: /** * Get a list of all main windows. * @return all main windows */ QList mainWindows() { // assemble right list QList windows; - for (int i = 0; i < m_mainWindows.size(); ++i) { - windows.push_back(m_mainWindows[i]->wrapper()); + windows.reserve(m_mainWindows.size()); + + for (const auto mainWindow : qAsConst(m_mainWindows)) { + windows.push_back(mainWindow->wrapper()); } return windows; } /** * Accessor to the active main window. * \return a pointer to the active mainwindow */ KTextEditor::MainWindow *activeMainWindow() { // either return wrapper or nullptr if (KateMainWindow *a = activeKateMainWindow()) { return a->wrapper(); } return nullptr; } /** * Get a list of all documents that are managed by the application. * This might contain less documents than the editor has in his documents () list. * @return all documents the application manages */ QList documents() { return m_docManager.documentList(); } /** * Get the document with the URL \p url. * if multiple documents match the searched url, return the first found one... * \param url the document's URL * \return the document with the given \p url or NULL, if none found */ KTextEditor::Document *findUrl(const QUrl &url) { return m_docManager.findDocument(url); } /** * Open the document \p url with the given \p encoding. * if the url is empty, a new empty document will be created * \param url the document's url * \param encoding the preferred encoding. If encoding is QString() the * encoding will be guessed or the default encoding will be used. * \return a pointer to the created document */ KTextEditor::Document *openUrl(const QUrl &url, const QString &encoding = QString()) { return m_docManager.openUrl(url, encoding); } /** * Close the given \p document. If the document is modified, user will be asked if he wants that. * \param document the document to be closed * \return \e true on success, otherwise \e false */ bool closeDocument(KTextEditor::Document *document) { return m_docManager.closeDocument(document); } /** * Close a list of documents. If any of them are modified, user will be asked if he wants that. * Use this, if you want to close multiple documents at once, as the application might * be able to group the "do you really want that" dialogs into one. * \param documents list of documents to be closed * \return \e true on success, otherwise \e false */ bool closeDocuments(const QList &documents) { return m_docManager.closeDocumentList(documents); } /** * Get a plugin for the plugin with with identifier \p name. * \param name the plugin's name * \return pointer to the plugin if a plugin with \p name is loaded, otherwise nullptr */ KTextEditor::Plugin *plugin(const QString &name); /** * Ask app to quit. The app might interact with the user and decide that * quitting is not possible and return false. * * \return true if the app could quit */ bool quit() { shutdownKate(activeKateMainWindow()); return true; } /** * A message is received from an external instance, if we use QtSingleApplication * * \p message is a serialized message (at the moment just the file list separated by ';') * \p socket is the QLocalSocket used for the communication */ void remoteMessageReceived(const QString &message, QObject *socket); protected: /** * Event filter for QApplication to handle mac os like file open */ bool eventFilter(QObject *obj, QEvent *event) override; private: /** * kate's command line args */ const QCommandLineParser &m_args; /** * known main windows */ QList m_mainWindows; /** * Wrapper of application for KTextEditor */ KTextEditor::Application m_wrapper; /** * document manager */ KateDocManager m_docManager; /** * dbus interface */ KateAppAdaptor m_adaptor; /** * plugin manager */ KatePluginManager m_pluginManager; /** * session manager */ KateSessionManager m_sessionManager; }; #endif diff --git a/kate/kateconfigplugindialogpage.cpp b/kate/kateconfigplugindialogpage.cpp index 209037c95..41d48d9b3 100644 --- a/kate/kateconfigplugindialogpage.cpp +++ b/kate/kateconfigplugindialogpage.cpp @@ -1,125 +1,125 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2007 Mirko Stocker This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateconfigplugindialogpage.h" #include "kateapp.h" #include "katepluginmanager.h" #include "kateconfigdialog.h" #include #include class KatePluginListItem : public QTreeWidgetItem { public: KatePluginListItem(bool checked, KatePluginInfo *info); KatePluginInfo *info() const { return mInfo; } protected: void stateChange(bool); private: KatePluginInfo *mInfo; }; KatePluginListItem::KatePluginListItem(bool checked, KatePluginInfo *info) : QTreeWidgetItem() , mInfo(info) { setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); } KatePluginListView::KatePluginListView(QWidget *parent) : QTreeWidget(parent) { setRootIsDecorated(false); connect(this, &KatePluginListView::itemChanged, this, &KatePluginListView::stateChanged); } void KatePluginListView::stateChanged(QTreeWidgetItem *item) { emit stateChange(static_cast(item), item->checkState(0) == Qt::Checked); } KateConfigPluginPage::KateConfigPluginPage(QWidget *parent, KateConfigDialog *dialog) : QFrame(parent) , myDialog(dialog) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); KatePluginListView *listView = new KatePluginListView(this); layout->addWidget(listView); QStringList headers; headers << i18n("Name") << i18n("Description"); listView->setHeaderLabels(headers); listView->setWhatsThis(i18n("Here you can see all available Kate plugins. Those with a check mark are loaded, and will be loaded again the next time Kate is started.")); KatePluginList &pluginList(KateApp::self()->pluginManager()->pluginList()); - for (KatePluginList::iterator it = pluginList.begin(); it != pluginList.end(); ++it) { - QTreeWidgetItem *item = new KatePluginListItem(it->load, &(*it)); - item->setText(0, it->metaData.name()); - item->setText(1, it->metaData.description()); + for (auto& pluginInfo : pluginList) { + QTreeWidgetItem *item = new KatePluginListItem(pluginInfo.load, &pluginInfo); + item->setText(0, pluginInfo.metaData.name()); + item->setText(1, pluginInfo.metaData.description()); listView->addTopLevelItem(item); } listView->resizeColumnToContents(0); listView->sortByColumn(0, Qt::AscendingOrder); connect(listView, &KatePluginListView::stateChange, this, &KateConfigPluginPage::stateChange); } void KateConfigPluginPage::stateChange(KatePluginListItem *item, bool b) { if (b) { loadPlugin(item); } else { unloadPlugin(item); } emit changed(); } void KateConfigPluginPage::loadPlugin(KatePluginListItem *item) { const bool ok = KateApp::self()->pluginManager()->loadPlugin(item->info()); if (!ok) { return; } KateApp::self()->pluginManager()->enablePluginGUI(item->info()); myDialog->addPluginPage(item->info()->plugin); item->setCheckState(0, Qt::Checked); } void KateConfigPluginPage::unloadPlugin(KatePluginListItem *item) { myDialog->removePluginPage(item->info()->plugin); KateApp::self()->pluginManager()->unloadPlugin(item->info()); item->setCheckState(0, Qt::Unchecked); } diff --git a/kate/katedocmanager.cpp b/kate/katedocmanager.cpp index 95a02ba4f..9b0c84fea 100644 --- a/kate/katedocmanager.cpp +++ b/kate/katedocmanager.cpp @@ -1,598 +1,598 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2007 Flavio Castelli This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "katedocmanager.h" #include "kateapp.h" #include "katemainwindow.h" #include "kateviewmanager.h" #include "katesavemodifieddialog.h" #include "katedebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KateDocManager::KateDocManager(QObject *parent) : QObject(parent) , m_metaInfos(QStringLiteral("katemetainfos"), KConfig::NoGlobals) , m_saveMetaInfos(true) , m_daysMetaInfos(0) { // set our application wrapper KTextEditor::Editor::instance()->setApplication(KateApp::self()->wrapper()); // create one doc, we always have at least one around! createDoc(); } KateDocManager::~KateDocManager() { // write metainfos? if (m_saveMetaInfos) { // saving meta-infos when file is saved is not enough, we need to do it once more at the end saveMetaInfos(m_docList); // purge saved filesessions if (m_daysMetaInfos > 0) { const QStringList groups = m_metaInfos.groupList(); QDateTime def(QDate(1970, 1, 1)); - for (QStringList::const_iterator it = groups.begin(); it != groups.end(); ++it) { - QDateTime last = m_metaInfos.group(*it).readEntry("Time", def); + for (const auto& group : groups) { + QDateTime last = m_metaInfos.group(group).readEntry("Time", def); if (last.daysTo(QDateTime::currentDateTimeUtc()) > m_daysMetaInfos) { - m_metaInfos.deleteGroup(*it); + m_metaInfos.deleteGroup(group); } } } } qDeleteAll(m_docInfos); } KTextEditor::Document *KateDocManager::createDoc(const KateDocumentInfo &docInfo) { KTextEditor::Document *doc = KTextEditor::Editor::instance()->createDocument(this); // turn of the editorpart's own modification dialog, we have our own one, too! const KConfigGroup generalGroup(KSharedConfig::openConfig(), "General"); bool ownModNotification = generalGroup.readEntry("Modified Notification", false); if (qobject_cast(doc)) { qobject_cast(doc)->setModifiedOnDiskWarning(!ownModNotification); } m_docList.append(doc); m_docInfos.insert(doc, new KateDocumentInfo(docInfo)); // connect internal signals... connect(doc, &KTextEditor::Document::modifiedChanged, this, &KateDocManager::slotModChanged1); connect(doc, SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(slotModifiedOnDisc(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); // we have a new document, show it the world emit documentCreated(doc); emit documentCreatedViewManager(doc); // return our new document return doc; } KateDocumentInfo *KateDocManager::documentInfo(KTextEditor::Document *doc) { return m_docInfos.contains(doc) ? m_docInfos[doc] : nullptr; } static QUrl normalizeUrl(const QUrl & url) { // Resolve symbolic links for local files (done anyway in KTextEditor) if (url.isLocalFile()) { QString normalizedUrl = QFileInfo(url.toLocalFile()).canonicalFilePath(); if (!normalizedUrl.isEmpty()) { return QUrl::fromLocalFile(normalizedUrl); } } // else: cleanup only the .. stuff return url.adjusted(QUrl::NormalizePathSegments); } KTextEditor::Document *KateDocManager::findDocument(const QUrl &url) const { const QUrl u(normalizeUrl(url)); for(KTextEditor::Document *it : m_docList) { if (it->url() == u) { return it; } } return nullptr; } QList KateDocManager::openUrls(const QList &urls, const QString &encoding, bool isTempFile, const KateDocumentInfo &docInfo) { QList docs; emit aboutToCreateDocuments(); foreach(const QUrl & url, urls) { docs << openUrl(url, encoding, isTempFile, docInfo); } emit documentsCreated(docs); return docs; } KTextEditor::Document *KateDocManager::openUrl(const QUrl &url, const QString &encoding, bool isTempFile, const KateDocumentInfo &docInfo) { // special handling: if only one unmodified empty buffer in the list, // keep this buffer in mind to close it after opening the new url KTextEditor::Document *untitledDoc = nullptr; if ((documentList().count() == 1) && (!documentList().at(0)->isModified() && documentList().at(0)->url().isEmpty())) { untitledDoc = documentList().first(); } // // create new document // const QUrl u(normalizeUrl(url)); KTextEditor::Document *doc = nullptr; // always new document if url is empty... if (!u.isEmpty()) { doc = findDocument(u); } if (!doc) { if (untitledDoc) { // reuse the untitled document which is not needed auto & info = m_docInfos.find(untitledDoc).value(); delete info; info = new KateDocumentInfo(docInfo); doc = untitledDoc; } else { doc = createDoc(docInfo); } if (!encoding.isEmpty()) { doc->setEncoding(encoding); } if (!u.isEmpty()) { doc->openUrl(u); loadMetaInfos(doc, u); } } // // if needed, register as temporary file // if (isTempFile && u.isLocalFile()) { QFileInfo fi(u.toLocalFile()); if (fi.exists()) { m_tempFiles[doc] = qMakePair(u, fi.lastModified()); qCDebug(LOG_KATE) << "temporary file will be deleted after use unless modified: " << u; } } return doc; } bool KateDocManager::closeDocuments(const QList &documents, bool closeUrl) { if (documents.isEmpty()) { return false; } saveMetaInfos(documents); emit aboutToDeleteDocuments(documents); int last = 0; bool success = true; foreach(KTextEditor::Document * doc, documents) { if (closeUrl && !doc->closeUrl()) { success = false; // get out on first error break; } if (closeUrl && m_tempFiles.contains(doc)) { QFileInfo fi(m_tempFiles[ doc ].first.toLocalFile()); if (fi.lastModified() <= m_tempFiles[ doc ].second || KMessageBox::questionYesNo(KateApp::self()->activeKateMainWindow(), i18n("The supposedly temporary file %1 has been modified. " "Do you want to delete it anyway?", m_tempFiles[ doc ].first.url(QUrl::PreferLocalFile)), i18n("Delete File?")) == KMessageBox::Yes) { KIO::del(m_tempFiles[ doc ].first, KIO::HideProgressInfo); qCDebug(LOG_KATE) << "Deleted temporary file " << m_tempFiles[ doc ].first; m_tempFiles.remove(doc); } else { m_tempFiles.remove(doc); qCDebug(LOG_KATE) << "The supposedly temporary file " << m_tempFiles[ doc ].first.url() << " have been modified since loaded, and has not been deleted."; } } KateApp::self()->emitDocumentClosed(QString::number((qptrdiff)doc)); // document will be deleted, soon emit documentWillBeDeleted(doc); // really delete the document and its infos delete m_docInfos.take(doc); delete m_docList.takeAt(m_docList.indexOf(doc)); // document is gone, emit our signals emit documentDeleted(doc); last++; } /** * never ever empty the whole document list * do this before documentsDeleted is emitted, to have no flicker */ if (m_docList.isEmpty()) { createDoc(); } emit documentsDeleted(documents.mid(last)); return success; } bool KateDocManager::closeDocument(KTextEditor::Document *doc, bool closeUrl) { if (!doc) { return false; } QList documents; documents.append(doc); return closeDocuments(documents, closeUrl); } bool KateDocManager::closeDocumentList(QList documents) { QList modifiedDocuments; foreach(KTextEditor::Document * document, documents) { if (document->isModified()) { modifiedDocuments.append(document); } } if (modifiedDocuments.size() > 0 && !KateSaveModifiedDialog::queryClose(nullptr, modifiedDocuments)) { return false; } return closeDocuments(documents, false); // Do not show save/discard dialog } bool KateDocManager::closeAllDocuments(bool closeUrl) { /** * just close all documents */ return closeDocuments(m_docList, closeUrl); } bool KateDocManager::closeOtherDocuments(KTextEditor::Document *doc) { /** * close all documents beside the passed one */ QList documents = m_docList; documents.removeOne(doc); return closeDocuments(documents); } /** * Find all modified documents. * @return Return the list of all modified documents. */ QList KateDocManager::modifiedDocumentList() { QList modified; foreach(KTextEditor::Document * doc, m_docList) { if (doc->isModified()) { modified.append(doc); } } return modified; } bool KateDocManager::queryCloseDocuments(KateMainWindow *w) { int docCount = m_docList.count(); foreach(KTextEditor::Document * doc, m_docList) { if (doc->url().isEmpty() && doc->isModified()) { int msgres = KMessageBox::warningYesNoCancel(w, i18n("

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

" "

Do you want to save your changes or discard them?

", doc->documentName()), i18n("Close Document"), KStandardGuiItem::save(), KStandardGuiItem::discard()); if (msgres == KMessageBox::Cancel) { return false; } if (msgres == KMessageBox::Yes) { const QUrl url = QFileDialog::getSaveFileUrl(w, i18n("Save As")); if (!url.isEmpty()) { if (!doc->saveAs(url)) { return false; } } else { return false; } } } else { if (!doc->queryClose()) { return false; } } } // document count changed while queryClose, abort and notify user if (m_docList.count() > docCount) { KMessageBox::information(w, i18n("New file opened while trying to close Kate, closing aborted."), i18n("Closing Aborted")); return false; } return true; } void KateDocManager::saveAll() { foreach(KTextEditor::Document * doc, m_docList) if (doc->isModified()) { doc->documentSave(); } } void KateDocManager::saveSelected(const QList &docList) { foreach(KTextEditor::Document * doc, docList) { if (doc->isModified()) { doc->documentSave(); } } } void KateDocManager::reloadAll() { // reload all docs that are NOT modified on disk foreach(KTextEditor::Document * doc, m_docList) { if (! documentInfo(doc)->modifiedOnDisc) { doc->documentReload(); } } // take care of all documents that ARE modified on disk KateApp::self()->activeKateMainWindow()->showModOnDiskPrompt(); } void KateDocManager::closeOrphaned() { QList documents; foreach(KTextEditor::Document * doc, m_docList) { KateDocumentInfo *info = documentInfo(doc); if (info && !info->openSuccess) { documents.append(doc); } } closeDocuments(documents); } void KateDocManager::saveDocumentList(KConfig *config) { KConfigGroup openDocGroup(config, "Open Documents"); openDocGroup.writeEntry("Count", m_docList.count()); int i = 0; foreach(KTextEditor::Document * doc, m_docList) { KConfigGroup cg(config, QStringLiteral("Document %1").arg(i)); doc->writeSessionConfig(cg); i++; } } void KateDocManager::restoreDocumentList(KConfig *config) { KConfigGroup openDocGroup(config, "Open Documents"); unsigned int count = openDocGroup.readEntry("Count", 0); if (count == 0) { return; } QProgressDialog progress; progress.setWindowTitle(i18n("Starting Up")); progress.setLabelText(i18n("Reopening files from the last session...")); progress.setModal(true); progress.setCancelButton(nullptr); progress.setRange(0, count); for (unsigned int i = 0; i < count; i++) { KConfigGroup cg(config, QStringLiteral("Document %1").arg(i)); KTextEditor::Document *doc = nullptr; if (i == 0) { doc = m_docList.first(); } else { doc = createDoc(); } connect(doc, SIGNAL(completed()), this, SLOT(documentOpened())); connect(doc, &KParts::ReadOnlyPart::canceled, this, &KateDocManager::documentOpened); doc->readSessionConfig(cg); progress.setValue(i); } } void KateDocManager::slotModifiedOnDisc(KTextEditor::Document *doc, bool b, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { if (m_docInfos.contains(doc)) { m_docInfos[doc]->modifiedOnDisc = b; m_docInfos[doc]->modifiedOnDiscReason = reason; slotModChanged1(doc); } } /** * Load file's meta-information if the checksum didn't change since last time. */ bool KateDocManager::loadMetaInfos(KTextEditor::Document *doc, const QUrl &url) { if (!m_saveMetaInfos) { return false; } if (!m_metaInfos.hasGroup(url.toDisplayString())) { return false; } const QByteArray checksum = doc->checksum().toHex(); bool ok = true; if (!checksum.isEmpty()) { KConfigGroup urlGroup(&m_metaInfos, url.toDisplayString()); const QString old_checksum = urlGroup.readEntry("Checksum"); if (QString::fromLatin1(checksum) == old_checksum) { QSet flags; if (documentInfo(doc)->openedByUser) { flags << QStringLiteral ("SkipEncoding"); } flags << QStringLiteral ("SkipUrl"); doc->readSessionConfig(urlGroup, flags); } else { urlGroup.deleteGroup(); ok = false; } m_metaInfos.sync(); } return ok && doc->url() == url; } /** * Save file's meta-information if doc is in 'unmodified' state */ void KateDocManager::saveMetaInfos(const QList &documents) { /** * skip work if no meta infos wanted */ if (!m_saveMetaInfos) { return; } /** * store meta info for all non-modified documents which have some checksum */ const QDateTime now = QDateTime::currentDateTimeUtc(); foreach(KTextEditor::Document * doc, documents) { /** * skip modified docs */ if (doc->isModified()) { continue; } const QByteArray checksum = doc->checksum().toHex(); if (!checksum.isEmpty()) { /** * write the group with checksum and time */ KConfigGroup urlGroup(&m_metaInfos, doc->url().toString()); urlGroup.writeEntry("Checksum", QString::fromLatin1(checksum)); urlGroup.writeEntry("Time", now); /** * write document session config */ doc->writeSessionConfig(urlGroup); } } /** * sync to not loose data */ m_metaInfos.sync(); } void KateDocManager::slotModChanged(KTextEditor::Document *doc) { QList documents; documents.append(doc); saveMetaInfos(documents); } void KateDocManager::slotModChanged1(KTextEditor::Document *doc) { QMetaObject::invokeMethod(KateApp::self()->activeKateMainWindow(), "queueModifiedOnDisc", Qt::QueuedConnection, Q_ARG(KTextEditor::Document *, doc)); } void KateDocManager::documentOpened() { KColorScheme colors(QPalette::Active); KTextEditor::Document *doc = qobject_cast(sender()); if (!doc) { return; // should never happen, but who knows } disconnect(doc, SIGNAL(completed()), this, SLOT(documentOpened())); disconnect(doc, &KParts::ReadOnlyPart::canceled, this, &KateDocManager::documentOpened); // Only set "no success" when doc is empty to avoid close of files // with other trouble when do closeOrphaned() if (doc->openingError() && doc->isEmpty()) { KateDocumentInfo *info = documentInfo(doc); if (info) { info->openSuccess = false; } } } diff --git a/kate/katemainwindow.cpp b/kate/katemainwindow.cpp index 3ba602dd2..1e9fbd492 100644 --- a/kate/katemainwindow.cpp +++ b/kate/katemainwindow.cpp @@ -1,1286 +1,1285 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund Copyright (C) 2007 Flavio Castelli This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN Includes #include "katemainwindow.h" #include "kateconfigdialog.h" #include "katedocmanager.h" #include "katepluginmanager.h" #include "kateconfigplugindialogpage.h" #include "kateviewmanager.h" #include "kateapp.h" #include "katesavemodifieddialog.h" #include "katemwmodonhddialog.h" #include "katesessionsaction.h" #include "katesessionmanager.h" #include "kateviewspace.h" #include "katequickopen.h" #include "kateupdatedisabler.h" #include "katedebug.h" #include "katecolorschemechooser.h" #include "katefileactions.h" #include "katequickopenmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //END KateMwModOnHdDialog *KateMainWindow::s_modOnHdDialog = nullptr; KateContainerStackedLayout::KateContainerStackedLayout(QWidget *parent) : QStackedLayout(parent) {} QSize KateContainerStackedLayout::sizeHint() const { if (currentWidget()) { return currentWidget()->sizeHint(); } return QStackedLayout::sizeHint(); } QSize KateContainerStackedLayout::minimumSize() const { if (currentWidget()) { return currentWidget()->minimumSize(); } return QStackedLayout::minimumSize(); } KateMainWindow::KateMainWindow(KConfig *sconfig, const QString &sgroup) : KateMDI::MainWindow(nullptr) , m_modignore(false) , m_wrapper(new KTextEditor::MainWindow(this)) { /** * we don't want any flicker here */ KateUpdateDisabler disableUpdates (this); /** * get and set config revision */ static const int currentConfigRevision = 10; const int readConfigRevision = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("Config Revision", 0); KConfigGroup(KSharedConfig::openConfig(), "General").writeEntry("Config Revision", currentConfigRevision); const bool firstStart = readConfigRevision < currentConfigRevision; // start session restore if needed startRestore(sconfig, sgroup); // setup most important actions first, needed by setupMainWindow setupImportantActions(); // setup the most important widgets setupMainWindow(); // setup the actions setupActions(); setStandardToolBarMenuEnabled(true); setXMLFile(QStringLiteral("kateui.rc")); createShellGUI(true); //qCDebug(LOG_KATE) << "****************************************************************************" << sconfig; // register mainwindow in app KateApp::self()->addMainWindow(this); // enable plugin guis KateApp::self()->pluginManager()->enableAllPluginsGUI(this, sconfig); // caption update Q_FOREACH (auto doc, KateApp::self()->documentManager()->documentList()) { slotDocumentCreated(doc); } connect(KateApp::self()->documentManager(), &KateDocManager::documentCreated, this, &KateMainWindow::slotDocumentCreated); readOptions(); if (sconfig) { m_viewManager->restoreViewConfiguration(KConfigGroup(sconfig, sgroup)); } finishRestore(); m_fileOpenRecent->loadEntries(KConfigGroup(sconfig, "Recent Files")); setAcceptDrops(true); connect(KateApp::self()->sessionManager(), SIGNAL(sessionChanged()), this, SLOT(updateCaption())); connect(this, &KateMDI::MainWindow::sigShowPluginConfigPage, this, &KateMainWindow::showPluginConfigPage); // prior to this there was (possibly) no view, therefore not context menu. // Hence, we have to take care of the menu bar here toggleShowMenuBar(false); // on first start: deactivate toolbar if (firstStart) toolBar(QStringLiteral("mainToolBar"))->hide(); // pass focus to first view! if (m_viewManager->activeView()) m_viewManager->activeView()->setFocus(); } KateMainWindow::~KateMainWindow() { // first, save our fallback window size ;) KConfigGroup cfg(KSharedConfig::openConfig(), "MainWindow"); KWindowConfig::saveWindowSize(windowHandle(), cfg); // save other options ;=) saveOptions(); // unregister mainwindow in app KateApp::self()->removeMainWindow(this); // disable all plugin guis, delete all pluginViews KateApp::self()->pluginManager()->disableAllPluginsGUI(this); // manually delete quick open, it's event filters will cause crash otherwise later delete m_quickOpen; m_quickOpen = nullptr; // delete the view manager, before KateMainWindow's wrapper is dead delete m_viewManager; m_viewManager = nullptr; // kill the wrapper object, now that all views are dead delete m_wrapper; m_wrapper = nullptr; } QSize KateMainWindow::sizeHint() const { /** * have some useful size hint, else we have mini windows per default */ return (QSize(640, 480).expandedTo(minimumSizeHint())); } void KateMainWindow::setupImportantActions() { m_paShowStatusBar = KStandardAction::showStatusbar(this, SLOT(toggleShowStatusBar()), actionCollection()); m_paShowStatusBar->setWhatsThis(i18n("Use this command to show or hide the view's statusbar")); m_paShowMenuBar = KStandardAction::showMenubar(this, SLOT(toggleShowMenuBar()), actionCollection()); m_paShowTabBar = new KToggleAction(i18n("Show &Tabs"), this); actionCollection()->addAction(QStringLiteral("settings_show_tab_bar"), m_paShowTabBar); connect(m_paShowTabBar, &QAction::toggled, this, &KateMainWindow::toggleShowTabBar); m_paShowTabBar->setWhatsThis(i18n("Use this command to show or hide the tabs for the views")); m_paShowPath = new KToggleAction(i18n("Sho&w Path in Titlebar"), this); actionCollection()->addAction(QStringLiteral("settings_show_full_path"), m_paShowPath); connect(m_paShowPath, SIGNAL(toggled(bool)), this, SLOT(updateCaption())); m_paShowPath->setWhatsThis(i18n("Show the complete document path in the window caption")); // Load themes actionCollection()->addAction(QStringLiteral("colorscheme_menu"), new KateColorSchemeChooser(actionCollection())); QAction * a = actionCollection()->addAction(KStandardAction::Back, QStringLiteral("view_prev_tab")); a->setText(i18n("&Previous Tab")); a->setWhatsThis(i18n("Focus the previous tab.")); actionCollection()->setDefaultShortcuts(a, a->shortcuts() << KStandardShortcut::tabPrev()); connect(a, &QAction::triggered, this, &KateMainWindow::slotFocusPrevTab); a = actionCollection()->addAction(KStandardAction::Forward, QStringLiteral("view_next_tab")); a->setText(i18n("&Next Tab")); a->setWhatsThis(i18n("Focus the next tab.")); actionCollection()->setDefaultShortcuts(a, a->shortcuts() << KStandardShortcut::tabNext()); connect(a, &QAction::triggered, this, &KateMainWindow::slotFocusNextTab); // the quick open action is used by the KateViewSpace "quick open button" a = actionCollection()->addAction(QStringLiteral("view_quick_open")); a->setIcon(QIcon::fromTheme(QStringLiteral("quickopen"))); a->setText(i18n("&Quick Open")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_O)); connect(a, &QAction::triggered, this, &KateMainWindow::slotQuickOpen); a->setWhatsThis(i18n("Open a form to quick open documents.")); } void KateMainWindow::setupMainWindow() { setToolViewStyle(KMultiTabBar::KDEV3ICON); /** * create central stacked widget with its children */ m_mainStackedWidget = new QStackedWidget(centralWidget()); centralWidget()->layout()->addWidget(m_mainStackedWidget); (static_cast(centralWidget()->layout()))->setStretchFactor(m_mainStackedWidget, 100); m_quickOpen = new KateQuickOpen(m_mainStackedWidget, this); m_mainStackedWidget->addWidget(m_quickOpen); m_viewManager = new KateViewManager(m_mainStackedWidget, this); m_mainStackedWidget->addWidget(m_viewManager); // make view manager default visible! m_mainStackedWidget->setCurrentWidget(m_viewManager); m_bottomViewBarContainer = new QWidget(centralWidget()); centralWidget()->layout()->addWidget(m_bottomViewBarContainer); m_bottomContainerStack = new KateContainerStackedLayout(m_bottomViewBarContainer); } void KateMainWindow::setupActions() { QAction *a; actionCollection()->addAction(KStandardAction::New, QStringLiteral("file_new"), m_viewManager, SLOT(slotDocumentNew())) ->setWhatsThis(i18n("Create a new document")); actionCollection()->addAction(KStandardAction::Open, QStringLiteral("file_open"), m_viewManager, SLOT(slotDocumentOpen())) ->setWhatsThis(i18n("Open an existing document for editing")); m_fileOpenRecent = KStandardAction::openRecent(m_viewManager, SLOT(openUrl(QUrl)), this); m_fileOpenRecent->setMaxItems(KateConfigDialog::recentFilesMaxCount()); actionCollection()->addAction(m_fileOpenRecent->objectName(), m_fileOpenRecent); m_fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); a = actionCollection()->addAction(QStringLiteral("file_save_all")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-all"))); a->setText(i18n("Save A&ll")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_L)); connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::saveAll); a->setWhatsThis(i18n("Save all open, modified documents to disk.")); a = actionCollection()->addAction(QStringLiteral("file_reload_all")); a->setText(i18n("&Reload All")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::reloadAll); a->setWhatsThis(i18n("Reload all open documents.")); a = actionCollection()->addAction(QStringLiteral("file_copy_filepath")); a->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); a->setText(i18n("Copy File &Path")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { auto&& view = viewManager()->activeView(); KateFileActions::copyFilePathToClipboard(view->document()); }); a->setWhatsThis(i18n("Copies the file path of the current file to clipboard.")); a = actionCollection()->addAction(QStringLiteral("file_open_containing_folder")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder"))); a->setText(i18n("&Open Containing Folder")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { auto&& view = viewManager()->activeView(); KateFileActions::openContainingFolder(view->document()); }); a->setWhatsThis(i18n("Copies the file path of the current file to clipboard.")); a = actionCollection()->addAction(QStringLiteral("file_rename")); a->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); a->setText(i18nc("@action:inmenu", "Rename...")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { auto&& view = viewManager()->activeView(); KateFileActions::renameDocumentFile(this, view->document()); }); a->setWhatsThis(i18n("Renames the file belonging to the current document.")); a = actionCollection()->addAction(QStringLiteral("file_delete")); a->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); a->setText(i18nc("@action:inmenu", "Delete")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { auto&& view = viewManager()->activeView(); KateFileActions::deleteDocumentFile(this, view->document()); }); a->setWhatsThis(i18n("Deletes the file belonging to the current document.")); a = actionCollection()->addAction(QStringLiteral("file_properties")); a->setIcon(QIcon::fromTheme(QStringLiteral("dialog-object-properties"))); a->setText(i18n("Properties")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { auto&& view = viewManager()->activeView(); KateFileActions::openFilePropertiesDialog(view->document()); }); a->setWhatsThis(i18n("Deletes the file belonging to the current document.")); a = actionCollection()->addAction(QStringLiteral("file_compare")); a->setText(i18n("Compare")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() { QMessageBox::information(this, i18n("Compare"), i18n("Use the Tabbar context menu to compare two documents")); }); a->setWhatsThis(i18n("Shows a hint how to compare documents.")); a = actionCollection()->addAction(QStringLiteral("file_close_orphaned")); a->setText(i18n("Close Orphaned")); connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::closeOrphaned); a->setWhatsThis(i18n("Close all documents in the file list that could not be reopened, because they are not accessible anymore.")); a = actionCollection()->addAction(KStandardAction::Close, QStringLiteral("file_close"), m_viewManager, SLOT(slotDocumentClose())); a->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); a->setWhatsThis(i18n("Close the current document.")); a = actionCollection()->addAction(QStringLiteral("file_close_other")); a->setText(i18n("Close Other")); connect(a, SIGNAL(triggered()), this, SLOT(slotDocumentCloseOther())); a->setWhatsThis(i18n("Close other open documents.")); a = actionCollection()->addAction(QStringLiteral("file_close_all")); a->setText(i18n("Clos&e All")); connect(a, &QAction::triggered, this, &KateMainWindow::slotDocumentCloseAll); a->setWhatsThis(i18n("Close all open documents.")); a = actionCollection()->addAction(KStandardAction::Quit, QStringLiteral("file_quit")); // Qt::QueuedConnection: delay real shutdown, as we are inside menu action handling (bug #185708) connect(a, &QAction::triggered, this, &KateMainWindow::slotFileQuit, Qt::QueuedConnection); a->setWhatsThis(i18n("Close this window")); a = actionCollection()->addAction(QStringLiteral("view_new_view")); a->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); a->setText(i18n("&New Window")); connect(a, &QAction::triggered, this, &KateMainWindow::newWindow); a->setWhatsThis(i18n("Create a new Kate view (a new window with the same document list).")); m_showFullScreenAction = KStandardAction::fullScreen(nullptr, nullptr, this, this); actionCollection()->addAction(m_showFullScreenAction->objectName(), m_showFullScreenAction); connect(m_showFullScreenAction, &QAction::toggled, this, &KateMainWindow::slotFullScreen); documentOpenWith = new KActionMenu(i18n("Open W&ith"), this); actionCollection()->addAction(QStringLiteral("file_open_with"), documentOpenWith); documentOpenWith->setWhatsThis(i18n("Open the current document using another application registered for its file type, or an application of your choice.")); connect(documentOpenWith->menu(), &QMenu::aboutToShow, this, &KateMainWindow::mSlotFixOpenWithMenu); connect(documentOpenWith->menu(), &QMenu::triggered, this, &KateMainWindow::slotOpenWithMenuAction); a = KStandardAction::keyBindings(this, SLOT(editKeys()), actionCollection()); a->setWhatsThis(i18n("Configure the application's keyboard shortcut assignments.")); a = KStandardAction::configureToolbars(this, SLOT(slotEditToolbars()), actionCollection()); a->setWhatsThis(i18n("Configure which items should appear in the toolbar(s).")); QAction *settingsConfigure = KStandardAction::preferences(this, SLOT(slotConfigure()), actionCollection()); settingsConfigure->setWhatsThis(i18n("Configure various aspects of this application and the editing component.")); if (KateApp::self()->pluginManager()->pluginList().count() > 0) { a = actionCollection()->addAction(QStringLiteral("help_plugins_contents")); a->setText(i18n("&Plugins Handbook")); connect(a, &QAction::triggered, this, &KateMainWindow::pluginHelp); a->setWhatsThis(i18n("This shows help files for various available plugins.")); } a = actionCollection()->addAction(QStringLiteral("help_about_editor")); a->setText(i18n("&About Editor Component")); connect(a, &QAction::triggered, this, &KateMainWindow::aboutEditor); connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotWindowActivated); connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateOpenWith); connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateActionsNeedingUrl); connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateBottomViewBar); // re-route signals to our wrapper connect(m_viewManager, &KateViewManager::viewChanged, m_wrapper, &KTextEditor::MainWindow::viewChanged); connect(m_viewManager, &KateViewManager::viewCreated, m_wrapper, &KTextEditor::MainWindow::viewCreated); connect(this, &KateMainWindow::unhandledShortcutOverride, m_wrapper, &KTextEditor::MainWindow::unhandledShortcutOverride); slotWindowActivated(); // session actions a = actionCollection()->addAction(QStringLiteral("sessions_new")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); a->setText(i18nc("Menu entry Session->New", "&New")); // Qt::QueuedConnection to avoid deletion of code that is executed when reducing the amount of mainwindows. (bug #227008) connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionNew, Qt::QueuedConnection); a = actionCollection()->addAction(QStringLiteral("sessions_save")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); a->setText(i18n("&Save Session")); connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionSave); a = actionCollection()->addAction(QStringLiteral("sessions_save_as")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); a->setText(i18n("Save Session &As...")); connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionSaveAs); a = actionCollection()->addAction(QStringLiteral("sessions_manage")); a->setIcon(QIcon::fromTheme(QStringLiteral("view-choose"))); a->setText(i18n("&Manage Sessions...")); // Qt::QueuedConnection to avoid deletion of code that is executed when reducing the amount of mainwindows. (bug #227008) connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionManage, Qt::QueuedConnection); // quick open menu ;) a = new KateSessionsAction(i18n("&Quick Open Session"), this); actionCollection()->addAction(QStringLiteral("sessions_list"), a); } void KateMainWindow::slotDocumentCloseAll() { if (KateApp::self()->documentManager()->documentList().size() >= 1 && KMessageBox::warningContinueCancel(this, i18n("This will close all open documents. Are you sure you want to continue?"), i18n("Close all documents"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("closeAll")) != KMessageBox::Cancel) { if (queryClose_internal()) { KateApp::self()->documentManager()->closeAllDocuments(false); } } } void KateMainWindow::slotDocumentCloseOther(KTextEditor::Document *document) { if (KateApp::self()->documentManager()->documentList().size() > 1 && KMessageBox::warningContinueCancel(this, i18n("This will close all open documents beside the current one. Are you sure you want to continue?"), i18n("Close all documents beside current one"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("closeOther")) != KMessageBox::Cancel) { if (queryClose_internal(document)) { KateApp::self()->documentManager()->closeOtherDocuments(document); } } } void KateMainWindow::slotDocumentCloseSelected(const QList &docList) { QList documents; foreach(KTextEditor::Document * doc, docList) { if (queryClose_internal(doc)) { documents.append(doc); } } KateApp::self()->documentManager()->closeDocuments(documents); } void KateMainWindow::slotDocumentCloseOther() { slotDocumentCloseOther(m_viewManager->activeView()->document()); } bool KateMainWindow::queryClose_internal(KTextEditor::Document *doc) { int documentCount = KateApp::self()->documentManager()->documentList().size(); if (! showModOnDiskPrompt()) { return false; } QList modifiedDocuments = KateApp::self()->documentManager()->modifiedDocumentList(); modifiedDocuments.removeAll(doc); bool shutdown = (modifiedDocuments.count() == 0); if (!shutdown) { shutdown = KateSaveModifiedDialog::queryClose(this, modifiedDocuments); } if (KateApp::self()->documentManager()->documentList().size() > documentCount) { KMessageBox::information(this, i18n("New file opened while trying to close Kate, closing aborted."), i18n("Closing Aborted")); shutdown = false; } return shutdown; } /** * queryClose(), take care that after the last mainwindow the stuff is closed */ bool KateMainWindow::queryClose() { // session saving, can we close all views ? // just test, not close them actually if (qApp->isSavingSession()) { return queryClose_internal(); } // normal closing of window // allow to close all windows until the last without restrictions if (KateApp::self()->mainWindowsCount() > 1) { return true; } // last one: check if we can close all documents, try run // and save docs if we really close down ! if (queryClose_internal()) { KateApp::self()->sessionManager()->saveActiveSession(true); return true; } return false; } void KateMainWindow::newWindow() { KateApp::self()->newMainWindow(KateApp::self()->sessionManager()->activeSession()->config()); } void KateMainWindow::slotEditToolbars() { KConfigGroup cfg(KSharedConfig::openConfig(), "MainWindow"); saveMainWindowSettings(cfg); KEditToolBar dlg(factory()); connect(&dlg, &KEditToolBar::newToolBarConfig, this, &KateMainWindow::slotNewToolbarConfig); dlg.exec(); } void KateMainWindow::reloadXmlGui() { for (KTextEditor::Document* doc : KateApp::self()->documentManager()->documentList()) { doc->reloadXML(); for (KTextEditor::View* view : doc->views()) { view->reloadXML(); } } } void KateMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(KConfigGroup(KSharedConfig::openConfig(), "MainWindow")); // we need to reload all View's XML Gui from disk to ensure toolbar // changes are applied to all views. reloadXmlGui(); } void KateMainWindow::slotFileQuit() { KateApp::self()->shutdownKate(this); } void KateMainWindow::slotFileClose() { m_viewManager->slotDocumentClose(); } void KateMainWindow::slotOpenDocument(const QUrl &url) { m_viewManager->openUrl(url, QString(), true, false); } void KateMainWindow::readOptions() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); const KConfigGroup generalGroup(config, "General"); m_modNotification = generalGroup.readEntry("Modified Notification", false); m_modCloseAfterLast = generalGroup.readEntry("Close After Last", false); KateApp::self()->documentManager()->setSaveMetaInfos(generalGroup.readEntry("Save Meta Infos", true)); KateApp::self()->documentManager()->setDaysMetaInfos(generalGroup.readEntry("Days Meta Infos", 30)); m_paShowPath->setChecked(generalGroup.readEntry("Show Full Path in Title", false)); m_paShowStatusBar->setChecked(generalGroup.readEntry("Show Status Bar", true)); m_paShowMenuBar->setChecked(generalGroup.readEntry("Show Menu Bar", true)); m_paShowTabBar->setChecked(generalGroup.readEntry("Show Tab Bar", true)); m_quickOpen->setMatchMode(generalGroup.readEntry("Quick Open Search Mode", (int)KateQuickOpenModel::Columns::FileName)); // emit signal to hide/show statusbars toggleShowStatusBar(); toggleShowTabBar(); } void KateMainWindow::saveOptions() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup generalGroup(config, "General"); generalGroup.writeEntry("Save Meta Infos", KateApp::self()->documentManager()->getSaveMetaInfos()); generalGroup.writeEntry("Days Meta Infos", KateApp::self()->documentManager()->getDaysMetaInfos()); generalGroup.writeEntry("Show Full Path in Title", m_paShowPath->isChecked()); generalGroup.writeEntry("Show Status Bar", m_paShowStatusBar->isChecked()); generalGroup.writeEntry("Show Menu Bar", m_paShowMenuBar->isChecked()); generalGroup.writeEntry("Show Tab Bar", m_paShowTabBar->isChecked()); } void KateMainWindow::toggleShowMenuBar(bool showMessage) { if (m_paShowMenuBar->isChecked()) { menuBar()->show(); removeMenuBarActionFromContextMenu(); } else { if (showMessage) { const QString accel = m_paShowMenuBar->shortcut().toString(); KMessageBox::information(this, i18n("This will hide the menu bar completely." " You can show it again by typing %1.", accel), i18n("Hide menu bar"), QStringLiteral("HideMenuBarWarning")); } menuBar()->hide(); addMenuBarActionToContextMenu(); } } void KateMainWindow::addMenuBarActionToContextMenu() { if (m_viewManager->activeView()) { m_viewManager->activeView()->contextMenu()->addAction(m_paShowMenuBar); } } void KateMainWindow::removeMenuBarActionFromContextMenu() { if (m_viewManager->activeView()) { m_viewManager->activeView()->contextMenu()->removeAction(m_paShowMenuBar); } } void KateMainWindow::toggleShowStatusBar() { emit statusBarToggled(); } bool KateMainWindow::showStatusBar() { return m_paShowStatusBar->isChecked(); } void KateMainWindow::toggleShowTabBar() { emit tabBarToggled(); } bool KateMainWindow::showTabBar() { return m_paShowTabBar->isChecked(); } void KateMainWindow::slotWindowActivated() { if (m_viewManager->activeView()) { updateCaption(m_viewManager->activeView()->document()); } // show view manager in any case if (m_mainStackedWidget->currentWidget() != m_viewManager) { m_mainStackedWidget->setCurrentWidget(m_viewManager); } // update proxy centralWidget()->setFocusProxy(m_viewManager->activeView()); } void KateMainWindow::slotUpdateOpenWith() { if (m_viewManager->activeView()) { documentOpenWith->setEnabled(!m_viewManager->activeView()->document()->url().isEmpty()); } else { documentOpenWith->setEnabled(false); } } void KateMainWindow::slotUpdateActionsNeedingUrl() { auto&& view = viewManager()->activeView(); const bool hasUrl = view && !view->document()->url().isEmpty(); action("file_copy_filepath")->setEnabled(hasUrl); action("file_open_containing_folder")->setEnabled(hasUrl); action("file_rename")->setEnabled(hasUrl); action("file_delete")->setEnabled(hasUrl); action("file_properties")->setEnabled(hasUrl); } void KateMainWindow::dragEnterEvent(QDragEnterEvent *event) { if (!event->mimeData()) { return; } const bool accept = event->mimeData()->hasUrls() || event->mimeData()->hasText(); event->setAccepted(accept); } void KateMainWindow::dropEvent(QDropEvent *event) { slotDropEvent(event); } void KateMainWindow::slotDropEvent(QDropEvent *event) { if (event->mimeData() == nullptr) { return; } // // are we dropping files? // if (event->mimeData()->hasUrls()) { QList textlist = event->mimeData()->urls(); // Try to get the KTextEditor::View that sent this, and activate it, so that the file opens in the // view where it was dropped KTextEditor::View *kVsender = qobject_cast(QObject::sender()); if (kVsender != nullptr) { QWidget *parent = kVsender->parentWidget(); if (parent != nullptr) { KateViewSpace *vs = qobject_cast(parent->parentWidget()); if (vs != nullptr) { m_viewManager->setActiveSpace(vs); } } } foreach(const QUrl & url, textlist) { // if url has no file component, try and recursively scan dir KFileItem kitem(url); kitem.setDelayedMimeTypes(true); if (kitem.isDir()) { if (KMessageBox::questionYesNo(this, i18n("You dropped the directory %1 into Kate. " "Do you want to load all files contained in it ?", url.url()), i18n("Load files recursively?")) == KMessageBox::Yes) { KIO::ListJob *list_job = KIO::listRecursive(url, KIO::DefaultFlags, false); connect(list_job, &KIO::ListJob::entries, this, &KateMainWindow::slotListRecursiveEntries); } } else { m_viewManager->openUrl(url); } } } // // or are we dropping text? // else if (event->mimeData()->hasText()) { KTextEditor::Document *doc = KateApp::self()->documentManager()->createDoc(); doc->setText(event->mimeData()->text()); m_viewManager->activateView(doc); } } void KateMainWindow::slotListRecursiveEntries(KIO::Job *job, const KIO::UDSEntryList &list) { const QUrl dir = static_cast(job)->url(); foreach(const KIO::UDSEntry & entry, list) { if (!entry.isDir()) { QUrl url(dir); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + entry.stringValue(KIO::UDSEntry::UDS_NAME)); m_viewManager->openUrl(url); } } } void KateMainWindow::editKeys() { KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); QList clients = guiFactory()->clients(); foreach(KXMLGUIClient * client, clients) { // FIXME there appear to be invalid clients after session switching // qCDebug(LOG_KATE)<<"adding client to shortcut editor"; // qCDebug(LOG_KATE)<actionCollection(); // qCDebug(LOG_KATE)<componentData().aboutData(); // qCDebug(LOG_KATE)<componentData().aboutData()->programName(); dlg.addCollection(client->actionCollection(), client->componentName()); } dlg.configure(); // reloadXML gui clients, to ensure all clients are up-to-date reloadXmlGui(); } void KateMainWindow::openUrl(const QString &name) { m_viewManager->openUrl(QUrl(name)); } void KateMainWindow::slotConfigure() { showPluginConfigPage(nullptr, 0); } void KateMainWindow::showPluginConfigPage(KTextEditor::Plugin *configpageinterface, uint id) { if (!m_viewManager->activeView()) { return; } KateConfigDialog *dlg = new KateConfigDialog(this, m_viewManager->activeView()); if (configpageinterface) { dlg->showAppPluginPage(configpageinterface, id); } if (dlg->exec() == QDialog::Accepted) { m_fileOpenRecent->setMaxItems(KateConfigDialog::recentFilesMaxCount()); } delete dlg; m_viewManager->reactivateActiveView(); // gui (toolbars...) needs to be updated, because // of possible changes that the configure dialog // could have done on it, specially for plugins. } QUrl KateMainWindow::activeDocumentUrl() { // anders: i make this one safe, as it may be called during // startup (by the file selector) KTextEditor::View *v = m_viewManager->activeView(); if (v) { return v->document()->url(); } return QUrl(); } void KateMainWindow::mSlotFixOpenWithMenu() { // dh: in bug #307699, this slot is called when launching the Kate application // unfortunately, no one ever could reproduce except users. KTextEditor::View *activeView = m_viewManager->activeView(); if (! activeView) { return; } // cleanup menu QMenu *menu = documentOpenWith->menu(); menu->clear(); // get a list of appropriate services. QMimeDatabase db; QMimeType mime = db.mimeTypeForName(activeView->document()->mimeType()); //qCDebug(LOG_KATE) << "mime type: " << mime.name(); QAction *a = nullptr; - KService::List offers = KMimeTypeTrader::self()->query(mime.name(), QStringLiteral("Application")); + const KService::List offers = KMimeTypeTrader::self()->query(mime.name(), QStringLiteral("Application")); // add all default open-with-actions except "Kate" - for (KService::List::Iterator it = offers.begin(); it != offers.end(); ++it) { - KService::Ptr service = *it; + for (const auto& service : offers) { if (service->name() == QStringLiteral("Kate")) { continue; } a = menu->addAction(QIcon::fromTheme(service->icon()), service->name()); a->setData(service->entryPath()); } // append "Other..." to call the KDE "open with" dialog. a = documentOpenWith->menu()->addAction(i18n("&Other...")); a->setData(QString()); } void KateMainWindow::slotOpenWithMenuAction(QAction *a) { QList list; list.append(m_viewManager->activeView()->document()->url()); const QString openWith = a->data().toString(); if (openWith.isEmpty()) { // display "open with" dialog KOpenWithDialog dlg(list); if (dlg.exec()) { KRun::runService(*dlg.service(), list, this); } return; } KService::Ptr app = KService::serviceByDesktopPath(openWith); if (app) { KRun::runService(*app, list, this); } else { KMessageBox::error(this, i18n("Application '%1' not found.", openWith), i18n("Application not found")); } } void KateMainWindow::pluginHelp() { KHelpClient::invokeHelp(QString(), QStringLiteral("kate-plugins")); } void KateMainWindow::aboutEditor() { KAboutApplicationDialog ad(KTextEditor::Editor::instance()->aboutData(), this); ad.exec(); } void KateMainWindow::slotFullScreen(bool t) { KToggleFullScreenAction::setFullScreen(this, t); QMenuBar *mb = menuBar(); if (t) { QToolButton *b = new QToolButton(mb); b->setDefaultAction(m_showFullScreenAction); b->setSizePolicy(QSizePolicy(QSizePolicy::Minimum,QSizePolicy::Ignored)); b->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); mb->setCornerWidget(b,Qt::TopRightCorner); b->setVisible(true); b->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else { QWidget *w=mb->cornerWidget(Qt::TopRightCorner); if (w) w->deleteLater(); } } bool KateMainWindow::showModOnDiskPrompt() { KTextEditor::Document *doc; DocVector list; list.reserve(KateApp::self()->documentManager()->documentList().size()); foreach(doc, KateApp::self()->documentManager()->documentList()) { if (KateApp::self()->documentManager()->documentInfo(doc)->modifiedOnDisc && doc->isModified()) { list.append(doc); } } if (!list.isEmpty() && !m_modignore) { KateMwModOnHdDialog mhdlg(list, this); m_modignore = true; bool res = mhdlg.exec(); m_modignore = false; return res; } return true; } void KateMainWindow::slotDocumentCreated(KTextEditor::Document *doc) { connect(doc, SIGNAL(modifiedChanged(KTextEditor::Document*)), this, SLOT(updateCaption(KTextEditor::Document*))); connect(doc, SIGNAL(readWriteChanged(KTextEditor::Document*)), this, SLOT(updateCaption(KTextEditor::Document*))); connect(doc, SIGNAL(documentNameChanged(KTextEditor::Document*)), this, SLOT(updateCaption(KTextEditor::Document*))); connect(doc, SIGNAL(documentUrlChanged(KTextEditor::Document*)), this, SLOT(updateCaption(KTextEditor::Document*))); connect(doc, &KTextEditor::Document::documentNameChanged, this, &KateMainWindow::slotUpdateOpenWith); updateCaption(doc); } void KateMainWindow::updateCaption() { if (m_viewManager->activeView()) { updateCaption(m_viewManager->activeView()->document()); } } void KateMainWindow::updateCaption(KTextEditor::Document *doc) { if (!m_viewManager->activeView()) { setCaption(QString(), false); return; } // block signals from inactive docs if (!((KTextEditor::Document *)m_viewManager->activeView()->document() == doc)) { return; } QString c; if (m_viewManager->activeView()->document()->url().isEmpty() || (!m_paShowPath || !m_paShowPath->isChecked())) { c = ((KTextEditor::Document *)m_viewManager->activeView()->document())->documentName(); } else { c = m_viewManager->activeView()->document()->url().toString(QUrl::PreferLocalFile); const QString homePath = QDir::homePath(); if (c.startsWith(homePath)) { c = QStringLiteral("~") + c.right(c.length() - homePath.length()); } } QString sessName = KateApp::self()->sessionManager()->activeSession()->name(); if (!sessName.isEmpty()) { sessName = QStringLiteral("%1: ").arg(sessName); } QString readOnlyCaption; if (!m_viewManager->activeView()->document()->isReadWrite()) { readOnlyCaption = i18n(" [read only]"); } setCaption(sessName + c + readOnlyCaption + QStringLiteral(" [*]"), m_viewManager->activeView()->document()->isModified()); } void KateMainWindow::saveProperties(KConfigGroup &config) { saveSession(config); // store all plugin view states int id = KateApp::self()->mainWindowID(this); foreach(const KatePluginInfo & item, KateApp::self()->pluginManager()->pluginList()) { if (item.plugin && pluginViews().contains(item.plugin)) { if (auto interface = qobject_cast (pluginViews().value(item.plugin))) { KConfigGroup group(config.config(), QStringLiteral("Plugin:%1:MainWindow:%2").arg(item.saveName()).arg(id)); interface->writeSessionConfig(group); } } } saveOpenRecent(config.config()); m_viewManager->saveViewConfiguration(config); } void KateMainWindow::readProperties(const KConfigGroup &config) { // KDE5: TODO startRestore should take a const KConfigBase*, or even just a const KConfigGroup&, // but this propagates down to interfaces/kate/plugin.h so all plugins have to be ported KConfigBase *configBase = const_cast(config.config()); startRestore(configBase, config.name()); // perhaps enable plugin guis KateApp::self()->pluginManager()->enableAllPluginsGUI(this, configBase); finishRestore(); loadOpenRecent(config.config()); m_viewManager->restoreViewConfiguration(config); } void KateMainWindow::saveOpenRecent(KConfig *config) { m_fileOpenRecent->saveEntries(KConfigGroup(config, "Recent Files")); } void KateMainWindow::loadOpenRecent(const KConfig *config) { m_fileOpenRecent->loadEntries(KConfigGroup(config, "Recent Files")); } void KateMainWindow::saveGlobalProperties(KConfig *sessionConfig) { KateApp::self()->documentManager()->saveDocumentList(sessionConfig); KConfigGroup cg(sessionConfig, "General"); cg.writeEntry("Last Session", KateApp::self()->sessionManager()->activeSession()->name()); // save plugin config !! KateApp::self()->pluginManager()->writeConfig(sessionConfig); } void KateMainWindow::saveWindowConfig(const KConfigGroup &_config) { KConfigGroup config(_config); saveMainWindowSettings(config); KWindowConfig::saveWindowSize(windowHandle(), config); config.writeEntry("WindowState", int(((KParts::MainWindow *)this)->windowState())); config.sync(); } void KateMainWindow::restoreWindowConfig(const KConfigGroup &config) { setWindowState(Qt::WindowNoState); applyMainWindowSettings(config); KWindowConfig::restoreWindowSize(windowHandle(), config); setWindowState(QFlags(config.readEntry("WindowState", int(Qt::WindowActive)))); } void KateMainWindow::slotUpdateBottomViewBar() { //qCDebug(LOG_KATE)<<"slotUpdateHorizontalViewBar()"<activeView(); BarState bs = m_bottomViewBarMapping[view]; if (bs.bar() && bs.state()) { m_bottomContainerStack->setCurrentWidget(bs.bar()); m_bottomContainerStack->currentWidget()->show(); m_bottomViewBarContainer->show(); } else { QWidget *wid = m_bottomContainerStack->currentWidget(); if (wid) { wid->hide(); } //qCDebug(LOG_KATE)<hide(); } } void KateMainWindow::queueModifiedOnDisc(KTextEditor::Document *doc) { if (!m_modNotification) { return; } KateDocumentInfo *docInfo = KateApp::self()->documentManager()->documentInfo(doc); if (!docInfo) { return; } bool modOnDisk = (uint)docInfo->modifiedOnDisc; if (s_modOnHdDialog == nullptr && modOnDisk) { DocVector list; list.append(doc); s_modOnHdDialog = new KateMwModOnHdDialog(list, this); m_modignore = true; KWindowSystem::setOnAllDesktops(s_modOnHdDialog->winId(), true); s_modOnHdDialog->exec(); delete s_modOnHdDialog; // s_modOnHdDialog is set to 0 in destructor of KateMwModOnHdDialog (jowenn!!!) m_modignore = false; } else if (s_modOnHdDialog != nullptr) { s_modOnHdDialog->addDocument(doc); } } bool KateMainWindow::event(QEvent *e) { if (e->type() == QEvent::ShortcutOverride) { QKeyEvent *k = static_cast(e); emit unhandledShortcutOverride(k); } return KateMDI::MainWindow::event(e); } QObject *KateMainWindow::pluginView(const QString &name) { KTextEditor::Plugin *plugin = KateApp::self()->pluginManager()->plugin(name); if (!plugin) { return nullptr; } return m_pluginViews.contains(plugin) ? m_pluginViews.value(plugin) : nullptr; } void KateMainWindow::mousePressEvent(QMouseEvent *e) { switch(e->button()) { case Qt::ForwardButton: slotFocusNextTab(); break; case Qt::BackButton: slotFocusPrevTab(); break; default: ; } } void KateMainWindow::slotFocusPrevTab() { if (m_viewManager->activeViewSpace()) { m_viewManager->activeViewSpace()->focusPrevTab(); } } void KateMainWindow::slotFocusNextTab() { if (m_viewManager->activeViewSpace()) { m_viewManager->activeViewSpace()->focusNextTab(); } } void KateMainWindow::slotQuickOpen() { /** * toggle back to view manager when when quick open is already shown */ if (m_mainStackedWidget->currentWidget() == m_quickOpen) { m_mainStackedWidget->setCurrentWidget(m_viewManager); centralWidget()->setFocusProxy(m_viewManager); return; } /** * show quick open and pass focus to it */ m_quickOpen->update(); m_mainStackedWidget->setCurrentWidget(m_quickOpen); centralWidget()->setFocusProxy(m_quickOpen); m_quickOpen->setFocus(); } QWidget *KateMainWindow::createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text) { // FIXME KF5 return KateMDI::MainWindow::createToolView(plugin, identifier, (KMultiTabBar::KMultiTabBarPosition)(pos), icon.pixmap(QSize(16, 16)), text); } bool KateMainWindow::moveToolView(QWidget *widget, KTextEditor::MainWindow::ToolViewPosition pos) { if (!qobject_cast(widget)) { return false; } // FIXME KF5 return KateMDI::MainWindow::moveToolView(qobject_cast(widget), (KMultiTabBar::KMultiTabBarPosition)(pos)); } bool KateMainWindow::showToolView(QWidget *widget) { if (!qobject_cast(widget)) { return false; } return KateMDI::MainWindow::showToolView(qobject_cast(widget)); } bool KateMainWindow::hideToolView(QWidget *widget) { if (!qobject_cast(widget)) { return false; } return KateMDI::MainWindow::hideToolView(qobject_cast(widget)); } void KateMainWindow::setQuickOpenMatchMode(int mode) { m_quickOpen->setMatchMode(mode); } int KateMainWindow::quickOpenMatchMode() { return m_quickOpen->matchMode(); } diff --git a/kate/katemdi.cpp b/kate/katemdi.cpp index 8173ebea1..5a84be013 100644 --- a/kate/katemdi.cpp +++ b/kate/katemdi.cpp @@ -1,1057 +1,1055 @@ /* 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 (int i = 0; i < m_toolviews.size(); ++i) { - ToolView *tv = m_toolviews[i]; - + 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 (int i = 0; i < 4; i++) { - connect(m_sidebars[i], &Sidebar::sigShowPluginConfigPage, this, &MainWindow::sigShowPluginConfigPage); + 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 (unsigned int i = 0; i < 4; ++i) { - delete m_sidebars[i]; + 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 (int i = 0; i < m_toolviews.size(); ++i) { - KMultiTabBar::KMultiTabBarPosition newPos = (KMultiTabBar::KMultiTabBarPosition) cg.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Position").arg(m_toolviews[i]->id), int(m_toolviews[i]->sidebar()->position())); + 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 (m_toolviews[i]->sidebar()->position() != newPos) { - moveToolView(m_toolviews[i], newPos); + if (tv->sidebar()->position() != newPos) { + moveToolView(tv, newPos); } } // restore the sidebars - for (unsigned int i = 0; i < 4; ++i) { - m_sidebars[i]->restoreSession(cg); + 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 (unsigned int i = 0; i < 4; ++i) { - m_sidebars[i]->saveSession(config); + for (auto sidebar : qAsConst(m_sidebars)) { + sidebar->saveSession(config); } } //END MAIN WINDOW } // namespace KateMDI diff --git a/kate/katemwmodonhddialog.cpp b/kate/katemwmodonhddialog.cpp index d9cc3e411..c76770438 100644 --- a/kate/katemwmodonhddialog.cpp +++ b/kate/katemwmodonhddialog.cpp @@ -1,375 +1,375 @@ /* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2004, Anders Lund */ #include "katemwmodonhddialog.h" #include "kateapp.h" #include "katedocmanager.h" #include "katemainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include class KateDocItem : public QTreeWidgetItem { public: KateDocItem(KTextEditor::Document *doc, const QString &status, QTreeWidget *tw) : QTreeWidgetItem(tw), document(doc) { setText(0, doc->url().toString()); setText(1, status); if (! doc->isModified()) { setCheckState(0, Qt::Checked); } else { setCheckState(0, Qt::Unchecked); } } ~KateDocItem() override {} KTextEditor::Document *document; }; KateMwModOnHdDialog::KateMwModOnHdDialog(DocVector docs, QWidget *parent, const char *name) : QDialog(parent), m_proc(nullptr), m_diffFile(nullptr), m_blockAddDocument(false) { setWindowTitle(i18n("Documents Modified on Disk")); setObjectName(QString::fromLatin1(name)); setModal(true); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); // Message QHBoxLayout *hb = new QHBoxLayout; mainLayout->addLayout(hb); // dialog text QLabel *icon = new QLabel(this); hb->addWidget(icon); icon->setPixmap(DesktopIcon(QStringLiteral("dialog-warning"))); QLabel *t = new QLabel(i18n( "The documents listed below have changed on disk.

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

"), this); hb->addWidget(t); hb->setStretchFactor(t, 1000); // Document list twDocuments = new QTreeWidget(this); mainLayout->addWidget(twDocuments); QStringList header; header << i18n("Filename") << i18n("Status on Disk"); twDocuments->setHeaderLabels(header); twDocuments->setSelectionMode(QAbstractItemView::SingleSelection); twDocuments->setRootIsDecorated(false); m_stateTexts << QString() << i18n("Modified") << i18n("Created") << i18n("Deleted"); - for (int i = 0; i < docs.size(); i++) { - new KateDocItem(docs[i], m_stateTexts[(uint)KateApp::self()->documentManager()->documentInfo(docs[i])->modifiedOnDiscReason ], twDocuments); + for (auto& doc : qAsConst(docs)) { + new KateDocItem(doc, m_stateTexts[(uint)KateApp::self()->documentManager()->documentInfo(doc)->modifiedOnDiscReason ], twDocuments); } twDocuments->header()->setStretchLastSection(false); twDocuments->header()->setSectionResizeMode(0, QHeaderView::Stretch); twDocuments->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); connect(twDocuments, &QTreeWidget::currentItemChanged, this, &KateMwModOnHdDialog::slotSelectionChanged); // Diff line hb = new QHBoxLayout; mainLayout->addLayout(hb); btnDiff = new QPushButton(QIcon::fromTheme(QStringLiteral("document-preview")), i18n("&View Difference"), this); btnDiff->setWhatsThis(i18n( "Calculates the difference between the editor contents and the disk " "file for the selected document, and shows the difference with the " "default application. Requires diff(1).")); hb->addWidget(btnDiff); connect(btnDiff, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotDiff); // Dialog buttons QDialogButtonBox *buttons = new QDialogButtonBox(this); mainLayout->addWidget(buttons); QPushButton *ignoreButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-warning")), i18n("&Ignore Changes")); ignoreButton->setToolTip(i18n("Remove modified flag from selected documents")); buttons->addButton(ignoreButton, QDialogButtonBox::RejectRole); connect(ignoreButton, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotIgnore); QPushButton *overwriteButton = new QPushButton; KGuiItem::assign(overwriteButton, KStandardGuiItem::overwrite()); overwriteButton->setToolTip(i18n("Overwrite selected documents, discarding disk changes")); buttons->addButton(overwriteButton, QDialogButtonBox::DestructiveRole); connect(overwriteButton, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotOverwrite); QPushButton *reloadButton = new QPushButton(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("&Reload")); reloadButton->setDefault(true); reloadButton->setToolTip(i18n("Reload selected documents from disk")); buttons->addButton(reloadButton, QDialogButtonBox::DestructiveRole); connect(reloadButton, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotReload); slotSelectionChanged(nullptr, nullptr); } KateMwModOnHdDialog::~KateMwModOnHdDialog() { KateMainWindow::unsetModifiedOnDiscDialogIfIf(this); if (m_proc) { m_proc->kill(); m_proc->waitForFinished(); delete m_proc; m_proc = Q_NULLPTR; } if (m_diffFile) { m_diffFile->setAutoRemove(true); delete m_diffFile; m_diffFile = Q_NULLPTR; } } void KateMwModOnHdDialog::slotIgnore() { handleSelected(Ignore); } void KateMwModOnHdDialog::slotOverwrite() { handleSelected(Overwrite); } void KateMwModOnHdDialog::slotReload() { handleSelected(Reload); } void KateMwModOnHdDialog::handleSelected(int action) { // don't alter the treewidget via addDocument, we modify it here! m_blockAddDocument = true; // collect all items we can remove QList itemsToDelete; for (QTreeWidgetItemIterator it(twDocuments); *it; ++it) { KateDocItem *item = (KateDocItem *) * it; if (item->checkState(0) == Qt::Checked) { KTextEditor::ModificationInterface::ModifiedOnDiskReason reason = KateApp::self()->documentManager()->documentInfo(item->document)->modifiedOnDiscReason; bool success = true; if (KTextEditor::ModificationInterface *iface = qobject_cast(item->document)) { iface->setModifiedOnDisk(KTextEditor::ModificationInterface::OnDiskUnmodified); } switch (action) { case Overwrite: success = item->document->save(); if (! success) { KMessageBox::sorry(this, i18n("Could not save the document \n'%1'", item->document->url().toString())); } break; case Reload: item->document->documentReload(); break; default: break; } if (success) { itemsToDelete.append(item); } else { if (KTextEditor::ModificationInterface *iface = qobject_cast(item->document)) { iface->setModifiedOnDisk(reason); } } } } // remove the marked items, addDocument is blocked, this is save! for (int i = 0; i < itemsToDelete.count(); ++i) { delete itemsToDelete[i]; } // any documents left unhandled? if (! twDocuments->topLevelItemCount()) { accept(); } // allow addDocument again m_blockAddDocument = false; } void KateMwModOnHdDialog::slotSelectionChanged(QTreeWidgetItem *current, QTreeWidgetItem *) { KateDocItem *currentDocItem = static_cast(current); // set the diff button enabled btnDiff->setEnabled(currentDocItem && KateApp::self()->documentManager()->documentInfo(currentDocItem->document)->modifiedOnDiscReason != KTextEditor::ModificationInterface::OnDiskDeleted); } // ### the code below is slightly modified from kdelibs/kate/part/katedialogs, // class KateModOnHdPrompt. void KateMwModOnHdDialog::slotDiff() { if (!btnDiff->isEnabled()) { // diff button already pressed, proc not finished yet return; } if (! twDocuments->currentItem()) { return; } KTextEditor::Document *doc = (static_cast(twDocuments->currentItem()))->document; // don't try to diff a deleted file if (KateApp::self()->documentManager()->documentInfo(doc)->modifiedOnDiscReason == KTextEditor::ModificationInterface::OnDiskDeleted) { return; } if (m_diffFile) { return; } m_diffFile = new QTemporaryFile(); m_diffFile->open(); // Start a KProcess that creates a diff m_proc = new KProcess(this); m_proc->setOutputChannelMode(KProcess::MergedChannels); *m_proc << QStringLiteral("diff") << QStringLiteral("-ub") << QStringLiteral("-") << doc->url().toLocalFile(); connect(m_proc, &KProcess::readyRead, this, &KateMwModOnHdDialog::slotDataAvailable); connect(m_proc, static_cast(&KProcess::finished), this, &KateMwModOnHdDialog::slotPDone); setCursor(Qt::WaitCursor); btnDiff->setEnabled(false); m_proc->start(); QTextStream ts(m_proc); int lastln = doc->lines() - 1; for (int l = 0; l < lastln; ++l) { ts << doc->line(l) << QLatin1Char('\n'); } ts << doc->line(lastln); ts.flush(); m_proc->closeWriteChannel(); } void KateMwModOnHdDialog::slotDataAvailable() { m_diffFile->write(m_proc->readAll()); } void KateMwModOnHdDialog::slotPDone() { setCursor(Qt::ArrowCursor); slotSelectionChanged(twDocuments->currentItem(), nullptr); const QProcess::ExitStatus es = m_proc->exitStatus(); delete m_proc; m_proc = nullptr; if (es != QProcess::NormalExit) { KMessageBox::sorry(this, i18n("The diff command failed. Please make sure that " "diff(1) is installed and in your PATH."), i18n("Error Creating Diff")); delete m_diffFile; m_diffFile = nullptr; return; } if (m_diffFile->size() == 0) { KMessageBox::information(this, i18n("Ignoring amount of white space changed, the files are identical."), i18n("Diff Output")); delete m_diffFile; m_diffFile = nullptr; return; } m_diffFile->setAutoRemove(false); QUrl url = QUrl::fromLocalFile(m_diffFile->fileName()); delete m_diffFile; m_diffFile = nullptr; // KRun::runUrl should delete the file, once the client exits KRun::runUrl(url, QStringLiteral("text/x-patch"), this, KRun::RunFlags(KRun::DeleteTemporaryFiles)); } void KateMwModOnHdDialog::addDocument(KTextEditor::Document *doc) { // guard this e.g. during handleSelected if (m_blockAddDocument) return; for (QTreeWidgetItemIterator it(twDocuments); *it; ++it) { KateDocItem *item = (KateDocItem *) * it; if (item->document == doc) { delete item; break; } } uint reason = (uint)KateApp::self()->documentManager()->documentInfo(doc)->modifiedOnDiscReason; if (reason) { new KateDocItem(doc, m_stateTexts[reason], twDocuments); } if (! twDocuments->topLevelItemCount()) { accept(); } } void KateMwModOnHdDialog::keyPressEvent(QKeyEvent *event) { if (event->modifiers() == 0) { if (event->key() == Qt::Key_Escape) { event->accept(); return; } } QDialog::keyPressEvent(event); } void KateMwModOnHdDialog::closeEvent(QCloseEvent *e) { if (! twDocuments->topLevelItemCount()) { QDialog::closeEvent(e); } else { e->ignore(); } } diff --git a/kate/katepluginmanager.cpp b/kate/katepluginmanager.cpp index ba4089394..9ed5507be 100644 --- a/kate/katepluginmanager.cpp +++ b/kate/katepluginmanager.cpp @@ -1,360 +1,360 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #include "katepluginmanager.h" #include "kateapp.h" #include "katemainwindow.h" #include "katedebug.h" #include #include #include #include #include #include #include QString KatePluginInfo::saveName() const { return QFileInfo(metaData.fileName()).baseName(); } bool KatePluginInfo::operator<(const KatePluginInfo &other) const { if (sortOrder != other.sortOrder) return sortOrder < other.sortOrder; return saveName() < other.saveName(); } KatePluginManager::KatePluginManager(QObject *parent) : QObject(parent) { setupPluginList(); } KatePluginManager::~KatePluginManager() { unloadAllPlugins(); } void KatePluginManager::setupPluginList() { // activate a hand-picked list of plugins per default, give them a hand-picked sort order for loading const QMap defaultPlugins { { QStringLiteral("katefiletreeplugin"), -1000 } , { QStringLiteral("katesearchplugin"), -900 } , { QStringLiteral("kateprojectplugin"), -800 } , { QStringLiteral("tabswitcherplugin"), -100 } , { QStringLiteral("textfilterplugin"), -100 } #ifndef WIN32 , { QStringLiteral("katefilebrowserplugin"), -100 } // currently works badly on Windows , { QStringLiteral("katekonsoleplugin"), -100 } // currently does not work on Windows at all #endif }; // handle all install KTextEditor plugins m_pluginList.clear(); QSet unique; const QVector plugins = KPluginLoader::findPlugins(QStringLiteral("ktexteditor"), [](const KPluginMetaData & md) { return md.serviceTypes().contains(QStringLiteral("KTextEditor/Plugin")); }); for (const auto &pluginMetaData : plugins) { KatePluginInfo info; info.metaData = pluginMetaData; // only load plugins once, even if found multiple times! if (unique.contains(info.saveName())) continue; info.defaultLoad = defaultPlugins.contains(info.saveName()); info.sortOrder = defaultPlugins.value(info.saveName()); info.load = false; info.plugin = nullptr; m_pluginList.push_back(info); unique.insert (info.saveName()); } // sort to ensure some deterministic plugin load order, this is important for tool-view creation order std::sort(m_pluginList.begin(), m_pluginList.end()); // construct fast lookup map, do this after vector has final size, resize will invalidate the pointers! m_name2Plugin.clear(); - for (int i = 0; i < m_pluginList.size(); ++i) { - m_name2Plugin[m_pluginList[i].saveName()] = &(m_pluginList[i]); + for (auto& pluginInfo : m_pluginList) { + m_name2Plugin[pluginInfo.saveName()] = &pluginInfo; } } void KatePluginManager::loadConfig(KConfig *config) { // first: unload the plugins unloadAllPlugins(); /** * ask config object */ if (config) { KConfigGroup cg = KConfigGroup(config, QStringLiteral("Kate Plugins")); // disable all plugin if no config, beside the ones marked as default load - for (int i = 0; i < m_pluginList.size(); ++i) { - m_pluginList[i].load = cg.readEntry(m_pluginList[i].saveName(), m_pluginList[i].defaultLoad); + for (auto& pluginInfo: m_pluginList) { + pluginInfo.load = cg.readEntry(pluginInfo.saveName(), pluginInfo.defaultLoad); } } /** * load plugins */ - for (KatePluginList::iterator it = m_pluginList.begin(); it != m_pluginList.end(); ++it) { - if (it->load) { + for (auto& pluginInfo : m_pluginList) { + if (pluginInfo.load) { /** * load plugin + trigger update of GUI for already existing main windows */ - loadPlugin(&(*it)); - enablePluginGUI(&(*it)); + loadPlugin(&pluginInfo); + enablePluginGUI(&pluginInfo); // restore config - if (auto interface = qobject_cast (it->plugin)) { - KConfigGroup group(config, QStringLiteral("Plugin:%1:").arg(it->saveName())); + if (auto interface = qobject_cast (pluginInfo.plugin)) { + KConfigGroup group(config, QStringLiteral("Plugin:%1:").arg(pluginInfo.saveName())); interface->readSessionConfig(group); } } } } void KatePluginManager::writeConfig(KConfig *config) { Q_ASSERT(config); KConfigGroup cg = KConfigGroup(config, QStringLiteral("Kate Plugins")); foreach(const KatePluginInfo & plugin, m_pluginList) { QString saveName = plugin.saveName(); cg.writeEntry(saveName, plugin.load); // save config if (auto interface = qobject_cast (plugin.plugin)) { KConfigGroup group(config, QStringLiteral("Plugin:%1:").arg(saveName)); interface->writeSessionConfig(group); } } } void KatePluginManager::unloadAllPlugins() { - for (KatePluginList::iterator it = m_pluginList.begin(); it != m_pluginList.end(); ++it) { - if (it->plugin) { - unloadPlugin(&(*it)); + for (auto& pluginInfo : m_pluginList) { + if (pluginInfo.plugin) { + unloadPlugin(&pluginInfo); } } } void KatePluginManager::enableAllPluginsGUI(KateMainWindow *win, KConfigBase *config) { - for (KatePluginList::iterator it = m_pluginList.begin(); it != m_pluginList.end(); ++it) { - if (it->plugin) { - enablePluginGUI(&(*it), win, config); + for (auto& pluginInfo : m_pluginList) { + if (pluginInfo.plugin) { + enablePluginGUI(&pluginInfo, win, config); } } } void KatePluginManager::disableAllPluginsGUI(KateMainWindow *win) { - for (KatePluginList::iterator it = m_pluginList.begin(); it != m_pluginList.end(); ++it) { - if (it->plugin) { - disablePluginGUI(&(*it), win); + for (auto& pluginInfo : m_pluginList) { + if (pluginInfo.plugin) { + disablePluginGUI(&pluginInfo, win); } } } bool KatePluginManager::loadPlugin(KatePluginInfo *item) { /** * try to load the plugin */ auto factory = KPluginLoader(item->metaData.fileName()).factory(); if (factory) { item->plugin = factory->create(this, QVariantList() << item->saveName()); } item->load = item->plugin != nullptr; /** * tell the world about the success */ if (item->plugin) { emit KateApp::self()->wrapper()->pluginCreated(item->saveName(), item->plugin); } return item->plugin != nullptr; } void KatePluginManager::unloadPlugin(KatePluginInfo *item) { disablePluginGUI(item); delete item->plugin; KTextEditor::Plugin *plugin = item->plugin; item->plugin = nullptr; item->load = false; emit KateApp::self()->wrapper()->pluginDeleted(item->saveName(), plugin); } void KatePluginManager::enablePluginGUI(KatePluginInfo *item, KateMainWindow *win, KConfigBase *config) { // plugin around at all? if (!item->plugin) { return; } // lookup if there is already a view for it.. QObject *createdView = nullptr; if (!win->pluginViews().contains(item->plugin)) { // create the view + try to correctly load shortcuts, if it's a GUI Client createdView = item->plugin->createView(win->wrapper()); if (createdView) { win->pluginViews().insert(item->plugin, createdView); } } // load session config if needed if (config && win->pluginViews().contains(item->plugin)) { if (auto interface = qobject_cast (win->pluginViews().value(item->plugin))) { KConfigGroup group(config, QStringLiteral("Plugin:%1:MainWindow:0").arg(item->saveName())); interface->readSessionConfig(group); } } if (createdView) { emit win->wrapper()->pluginViewCreated(item->saveName(), createdView); } } void KatePluginManager::enablePluginGUI(KatePluginInfo *item) { // plugin around at all? if (!item->plugin) { return; } // enable the gui for all mainwindows... for (int i = 0; i < KateApp::self()->mainWindowsCount(); i++) { enablePluginGUI(item, KateApp::self()->mainWindow(i), nullptr); } } void KatePluginManager::disablePluginGUI(KatePluginInfo *item, KateMainWindow *win) { // plugin around at all? if (!item->plugin) { return; } // lookup if there is a view for it.. if (!win->pluginViews().contains(item->plugin)) { return; } // really delete the view of this plugin QObject *deletedView = win->pluginViews().value(item->plugin); delete deletedView; win->pluginViews().remove(item->plugin); emit win->wrapper()->pluginViewDeleted(item->saveName(), deletedView); } void KatePluginManager::disablePluginGUI(KatePluginInfo *item) { // plugin around at all? if (!item->plugin) { return; } // disable the gui for all mainwindows... for (int i = 0; i < KateApp::self()->mainWindowsCount(); i++) { disablePluginGUI(item, KateApp::self()->mainWindow(i)); } } KTextEditor::Plugin *KatePluginManager::plugin(const QString &name) { /** * name known? */ if (!m_name2Plugin.contains(name)) { return nullptr; } /** * real plugin instance, if any ;) */ return m_name2Plugin.value(name)->plugin; } bool KatePluginManager::pluginAvailable(const QString &name) { return m_name2Plugin.contains(name); } class KTextEditor::Plugin *KatePluginManager::loadPlugin(const QString &name, bool permanent) { /** * name known? */ if (!m_name2Plugin.contains(name)) { return nullptr; } /** * load, bail out on error */ loadPlugin(m_name2Plugin.value(name)); if (!m_name2Plugin.value(name)->plugin) { return nullptr; } /** * perhaps patch not load again back to "ok, load it once again on next loadConfig" */ m_name2Plugin.value(name)->load = permanent; return m_name2Plugin.value(name)->plugin; } void KatePluginManager::unloadPlugin(const QString &name, bool permanent) { /** * name known? */ if (!m_name2Plugin.contains(name)) { return; } /** * unload */ unloadPlugin(m_name2Plugin.value(name)); /** * perhaps patch load again back to "ok, load it once again on next loadConfig" */ m_name2Plugin.value(name)->load = !permanent; } diff --git a/kate/kateviewmanager.cpp b/kate/kateviewmanager.cpp index e9b8d12ca..f27a64e00 100644 --- a/kate/kateviewmanager.cpp +++ b/kate/kateviewmanager.cpp @@ -1,1256 +1,1256 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN Includes #include "kateviewmanager.h" #include "kateapp.h" #include "katemainwindow.h" #include "kateviewspace.h" #include "kateupdatedisabler.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KActivities_FOUND #include #endif #include #include //END Includes static const qint64 FileSizeAboveToAskUserIfProceedWithOpen = 10 * 1024 * 1024; // 10MB should suffice KateViewManager::KateViewManager(QWidget *parentW, KateMainWindow *parent) : QSplitter(parentW) , m_mainWindow(parent) , m_blockViewCreationAndActivation(false) , m_activeViewRunning(false) , m_minAge(0) , m_guiMergedView(nullptr) { // while init m_init = true; // we don't allow full collapse, see bug 366014 setChildrenCollapsible(false); // important, set them up, as we use them in other methodes setupActions(); KateViewSpace *vs = new KateViewSpace(this, nullptr); addWidget(vs); vs->setActive(true); m_viewSpaceList.append(vs); connect(this, &KateViewManager::viewChanged, this, &KateViewManager::slotViewChanged); connect(KateApp::self()->documentManager(), &KateDocManager::documentCreatedViewManager, this, &KateViewManager::documentCreated); /** * before document is really deleted: cleanup all views! */ connect(KateApp::self()->documentManager(), &KateDocManager::documentWillBeDeleted , this, &KateViewManager::documentWillBeDeleted); /** * handle document deletion transactions * disable view creation in between * afterwards ensure we have views ;) */ connect(KateApp::self()->documentManager(), &KateDocManager::aboutToDeleteDocuments , this, &KateViewManager::aboutToDeleteDocuments); connect(KateApp::self()->documentManager(), &KateDocManager::documentsDeleted , this, &KateViewManager::documentsDeleted); // register all already existing documents m_blockViewCreationAndActivation = true; const QList &docs = KateApp::self()->documentManager()->documentList(); foreach(KTextEditor::Document * doc, docs) { documentCreated(doc); } m_blockViewCreationAndActivation = false; // init done m_init = false; } KateViewManager::~KateViewManager() { /** * remove the single client that is registered at the factory, if any */ if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } } void KateViewManager::setupActions() { /** * view splitting */ m_splitViewVert = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_vert")); m_splitViewVert->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); m_splitViewVert->setText(i18n("Split Ve&rtical")); m_mainWindow->actionCollection()->setDefaultShortcut(m_splitViewVert, Qt::CTRL + Qt::SHIFT + Qt::Key_L); connect(m_splitViewVert, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceVert); m_splitViewVert->setWhatsThis(i18n("Split the currently active view vertically into two views.")); m_splitViewHoriz = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_horiz")); m_splitViewHoriz->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom"))); m_splitViewHoriz->setText(i18n("Split &Horizontal")); m_mainWindow->actionCollection()->setDefaultShortcut(m_splitViewHoriz, Qt::CTRL + Qt::SHIFT + Qt::Key_T); connect(m_splitViewHoriz, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceHoriz); m_splitViewHoriz->setWhatsThis(i18n("Split the currently active view horizontally into two views.")); m_closeView = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_close_current_space")); m_closeView->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); m_closeView->setText(i18n("Cl&ose Current View")); m_mainWindow->actionCollection()->setDefaultShortcut(m_closeView, Qt::CTRL + Qt::SHIFT + Qt::Key_R); connect(m_closeView, &QAction::triggered, this, &KateViewManager::slotCloseCurrentViewSpace, Qt::QueuedConnection); m_closeView->setWhatsThis(i18n("Close the currently active split view")); m_closeOtherViews = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_close_others")); m_closeOtherViews->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); m_closeOtherViews->setText(i18n("Close Inactive Views")); connect(m_closeOtherViews, &QAction::triggered, this, &KateViewManager::slotCloseOtherViews, Qt::QueuedConnection); m_closeOtherViews->setWhatsThis(i18n("Close every view but the active one")); m_hideOtherViews = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_hide_others")); m_hideOtherViews->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); m_hideOtherViews->setText(i18n("Hide Inactive Views")); m_hideOtherViews->setCheckable(true); connect(m_hideOtherViews, &QAction::triggered, this, &KateViewManager::slotHideOtherViews, Qt::QueuedConnection); m_hideOtherViews->setWhatsThis(i18n("Hide every view but the active one")); m_toggleSplitterOrientation = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_toggle")); m_toggleSplitterOrientation->setText(i18n("Toggle Orientation")); connect(m_toggleSplitterOrientation, &QAction::triggered, this, &KateViewManager::toggleSplitterOrientation, Qt::QueuedConnection); m_toggleSplitterOrientation->setWhatsThis(i18n("Toggles the orientation of the current split view")); goNext = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_next_split_view")); goNext->setText(i18n("Next Split View")); m_mainWindow->actionCollection()->setDefaultShortcut(goNext, Qt::Key_F8); connect(goNext, &QAction::triggered, this, &KateViewManager::activateNextView); goNext->setWhatsThis(i18n("Make the next split view the active one.")); goPrev = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_prev_split_view")); goPrev->setText(i18n("Previous Split View")); m_mainWindow->actionCollection()->setDefaultShortcut(goPrev, Qt::SHIFT + Qt::Key_F8); connect(goPrev, &QAction::triggered, this, &KateViewManager::activatePrevView); goPrev->setWhatsThis(i18n("Make the previous split view the active one.")); QAction * a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_right")); a->setText(i18n("Move Splitter Right")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterRight); a->setWhatsThis(i18n("Move the splitter of the current view to the right")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_left")); a->setText(i18n("Move Splitter Left")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterLeft); a->setWhatsThis(i18n("Move the splitter of the current view to the left")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_up")); a->setText(i18n("Move Splitter Up")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterUp); a->setWhatsThis(i18n("Move the splitter of the current view up")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_down")); a->setText(i18n("Move Splitter Down")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterDown); a->setWhatsThis(i18n("Move the splitter of the current view down")); } void KateViewManager::updateViewSpaceActions() { m_closeView->setEnabled(m_viewSpaceList.count() > 1); m_closeOtherViews->setEnabled(m_viewSpaceList.count() > 1); m_toggleSplitterOrientation->setEnabled(m_viewSpaceList.count() > 1); goNext->setEnabled(m_viewSpaceList.count() > 1); goPrev->setEnabled(m_viewSpaceList.count() > 1); } void KateViewManager::slotDocumentNew() { createView(); } void KateViewManager::slotDocumentOpen() { // try to start dialog in useful dir: either dir of current doc or last used one KTextEditor::View * const cv = activeView(); QUrl startUrl = cv ? cv->document()->url() : QUrl(); if (startUrl.isValid()) { m_lastOpenDialogUrl = startUrl; } else { startUrl = m_lastOpenDialogUrl; } const QList urls = QFileDialog::getOpenFileUrls(m_mainWindow, i18n("Open File"), startUrl); /** * emit size warning, for local files */ QString fileListWithTooLargeFiles; Q_FOREACH(const QUrl & url, urls) { if (!url.isLocalFile()) { continue; } const auto size = QFile(url.toLocalFile()).size(); if (size > FileSizeAboveToAskUserIfProceedWithOpen) { fileListWithTooLargeFiles += QStringLiteral("
  • %1 (%2MB)
  • ").arg(url.fileName()).arg(size / 1024 / 1024); } } if (!fileListWithTooLargeFiles.isEmpty()) { const QString text = i18n("

    You are attempting to open one or more large files:

      %1

    Do you want to proceed?

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

    " , fileListWithTooLargeFiles); const auto ret = KMessageBox::warningYesNo(this, text, i18n("Opening Large File"), KStandardGuiItem::cont(), KStandardGuiItem::stop()); if (ret == KMessageBox::No) { return; } } // activate view of last opened document KateDocumentInfo docInfo; docInfo.openedByUser = true; if (KTextEditor::Document *lastID = openUrls(urls, QString(), false, docInfo)) { activateView(lastID); } } void KateViewManager::slotDocumentClose(KTextEditor::Document *document) { bool shutdownKate = m_mainWindow->modCloseAfterLast() && KateApp::self()->documentManager()->documentList().size() == 1; // close document if (KateApp::self()->documentManager()->closeDocument(document) && shutdownKate) { KateApp::self()->shutdownKate(m_mainWindow); } } void KateViewManager::slotDocumentClose() { // no active view, do nothing if (!activeView()) { return; } slotDocumentClose(activeView()->document()); } KTextEditor::Document *KateViewManager::openUrl(const QUrl &url, const QString &encoding, bool activate, bool isTempFile, const KateDocumentInfo &docInfo) { KTextEditor::Document *doc = KateApp::self()->documentManager()->openUrl(url, encoding, isTempFile, docInfo); if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } if (activate) { activateView(doc); } return doc; } KTextEditor::Document *KateViewManager::openUrls(const QList &urls, const QString &encoding, bool isTempFile, const KateDocumentInfo &docInfo) { QList docs = KateApp::self()->documentManager()->openUrls(urls, encoding, isTempFile, docInfo); foreach(const KTextEditor::Document * doc, docs) { if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } } return docs.isEmpty() ? nullptr : docs.last(); } KTextEditor::View *KateViewManager::openUrlWithView(const QUrl &url, const QString &encoding) { KTextEditor::Document *doc = KateApp::self()->documentManager()->openUrl(url, encoding); if (!doc) { return nullptr; } if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } activateView(doc); return activeView(); } void KateViewManager::openUrl(const QUrl &url) { openUrl(url, QString()); } KateMainWindow *KateViewManager::mainWindow() { return m_mainWindow; } void KateViewManager::documentCreated(KTextEditor::Document *doc) { // forward to currently active view space activeViewSpace()->registerDocument(doc); // to update open recent files on saving connect(doc, &KTextEditor::Document::documentSavedOrUploaded, this, &KateViewManager::documentSavedOrUploaded); if (m_blockViewCreationAndActivation) { return; } if (!activeView()) { activateView(doc); } /** * check if we have any empty viewspaces and give them a view */ Q_FOREACH(KateViewSpace * vs, m_viewSpaceList) { if (!vs->currentView()) { createView(activeView()->document(), vs); } } } void KateViewManager::aboutToDeleteDocuments(const QList &) { /** * block view creation until the transaction is done * this shall not stack! */ Q_ASSERT (!m_blockViewCreationAndActivation); m_blockViewCreationAndActivation = true; /** * disable updates hard (we can't use KateUpdateDisabler here, we have delayed signal */ mainWindow()->setUpdatesEnabled(false); } void KateViewManager::documentsDeleted(const QList &) { /** * again allow view creation */ m_blockViewCreationAndActivation = false; /** * try to have active view around! */ if (!activeView() && !KateApp::self()->documentManager()->documentList().isEmpty()) { createView(KateApp::self()->documentManager()->documentList().last()); } /** * if we have one now, show them in all viewspaces that got empty! */ if (KTextEditor::View *const newActiveView = activeView()) { /** * check if we have any empty viewspaces and give them a view */ Q_FOREACH(KateViewSpace * vs, m_viewSpaceList) { if (!vs->currentView()) { createView(newActiveView->document(), vs); } } emit viewChanged(newActiveView); } /** * enable updates hard (we can't use KateUpdateDisabler here, we have delayed signal */ mainWindow()->setUpdatesEnabled(true); } void KateViewManager::documentSavedOrUploaded(KTextEditor::Document *doc, bool) { if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } } KTextEditor::View *KateViewManager::createView(KTextEditor::Document *doc, KateViewSpace *vs) { if (m_blockViewCreationAndActivation) { return nullptr; } // create doc if (!doc) { doc = KateApp::self()->documentManager()->createDoc(); } /** * create view, registers its XML gui itself * pass the view the correct main window */ KTextEditor::View *view = (vs ? vs : activeViewSpace())->createView(doc); /** * remember this view, active == false, min age set * create activity resource */ m_views[view].active = false; m_views[view].lruAge = m_minAge--; #ifdef KActivities_FOUND m_views[view].activityResource = new KActivities::ResourceInstance(view->window()->winId(), view); m_views[view].activityResource->setUri(doc->url()); #endif // disable settings dialog action delete view->actionCollection()->action(QStringLiteral("set_confdlg")); delete view->actionCollection()->action(QStringLiteral("editor_options")); connect(view, SIGNAL(dropEventPass(QDropEvent*)), mainWindow(), SLOT(slotDropEvent(QDropEvent*))); connect(view, &KTextEditor::View::focusIn, this, &KateViewManager::activateSpace); viewCreated(view); if (!vs) { activateView(view); } return view; } bool KateViewManager::deleteView(KTextEditor::View *view) { if (!view) { return true; } KateViewSpace *viewspace = static_cast(view->parentWidget()->parentWidget()); viewspace->removeView(view); /** * deregister if needed */ if (m_guiMergedView == view) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } // remove view from mapping and memory !! m_views.remove(view); delete view; return true; } KateViewSpace *KateViewManager::activeViewSpace() { for (QList::const_iterator it = m_viewSpaceList.constBegin(); it != m_viewSpaceList.constEnd(); ++it) { if ((*it)->isActiveSpace()) { return *it; } } // none active, so use the first we grab if (!m_viewSpaceList.isEmpty()) { m_viewSpaceList.first()->setActive(true); return m_viewSpaceList.first(); } Q_ASSERT(false); return nullptr; } KTextEditor::View *KateViewManager::activeView() { if (m_activeViewRunning) { return nullptr; } m_activeViewRunning = true; QHashIterator it(m_views); while (it.hasNext()) { it.next(); if (it.value().active) { m_activeViewRunning = false; return it.key(); } } // if we get to here, no view isActive() // first, try to get one from activeViewSpace() KateViewSpace *vs = activeViewSpace(); if (vs && vs->currentView()) { activateView(vs->currentView()); m_activeViewRunning = false; return vs->currentView(); } // last attempt: just pick first if (!m_views.isEmpty()) { KTextEditor::View *v = m_views.begin().key(); activateView(v); m_activeViewRunning = false; return v; } m_activeViewRunning = false; // no views exists! return nullptr; } void KateViewManager::setActiveSpace(KateViewSpace *vs) { if (activeViewSpace()) { activeViewSpace()->setActive(false); } vs->setActive(true); } void KateViewManager::setActiveView(KTextEditor::View *view) { if (activeView()) { m_views[activeView()].active = false; } if (view) { m_views[view].active = true; } } void KateViewManager::activateSpace(KTextEditor::View *v) { if (!v) { return; } KateViewSpace *vs = static_cast(v->parentWidget()->parentWidget()); if (!vs->isActiveSpace()) { setActiveSpace(vs); activateView(v); } } void KateViewManager::reactivateActiveView() { KTextEditor::View *view = activeView(); if (view) { m_views[view].active = false; activateView(view); } } void KateViewManager::activateView(KTextEditor::View *view) { if (!view) { return; } Q_ASSERT (m_views.contains(view)); if (!m_views[view].active) { // avoid flicker KateUpdateDisabler disableUpdates (mainWindow()); if (!activeViewSpace()->showView(view)) { // since it wasn't found, give'em a new one createView(view->document()); return; } setActiveView(view); bool toolbarVisible = mainWindow()->toolBar()->isVisible(); if (toolbarVisible) { mainWindow()->toolBar()->hide(); // hide to avoid toolbar flickering } if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } if (!m_blockViewCreationAndActivation) { mainWindow()->guiFactory()->addClient(view); m_guiMergedView = view; } if (toolbarVisible) { mainWindow()->toolBar()->show(); } // remember age of this view m_views[view].lruAge = m_minAge--; emit viewChanged(view); #ifdef KActivities_FOUND // inform activity manager m_views[view].activityResource->setUri(view->document()->url()); m_views[view].activityResource->notifyFocusedIn(); #endif } } KTextEditor::View *KateViewManager::activateView(KTextEditor::Document *d) { // no doc with this id found if (!d) { return activeView(); } // activate existing view if possible if (activeViewSpace()->showView(d)) { activateView(activeViewSpace()->currentView()); return activeView(); } // create new view otherwise createView(d); return activeView(); } void KateViewManager::slotViewChanged() { if (activeView() && !activeView()->hasFocus()) { activeView()->setFocus(); } } void KateViewManager::activateNextView() { int i = m_viewSpaceList.indexOf(activeViewSpace()) + 1; if (i >= m_viewSpaceList.count()) { i = 0; } setActiveSpace(m_viewSpaceList.at(i)); activateView(m_viewSpaceList.at(i)->currentView()); } void KateViewManager::activatePrevView() { int i = m_viewSpaceList.indexOf(activeViewSpace()) - 1; if (i < 0) { i = m_viewSpaceList.count() - 1; } setActiveSpace(m_viewSpaceList.at(i)); activateView(m_viewSpaceList.at(i)->currentView()); } void KateViewManager::documentWillBeDeleted(KTextEditor::Document *doc) { /** * collect all views of that document that belong to this manager */ QList closeList; Q_FOREACH (KTextEditor::View *v, doc->views()) { if (m_views.contains(v)) { closeList.append(v); } } while (!closeList.isEmpty()) { deleteView(closeList.takeFirst()); } } void KateViewManager::closeView(KTextEditor::View *view) { /** * kill view we want to kill */ deleteView(view); /** * try to have active view around! */ if (!activeView() && !KateApp::self()->documentManager()->documentList().isEmpty()) { createView(KateApp::self()->documentManager()->documentList().last()); } /** * if we have one now, show them in all viewspaces that got empty! */ if (KTextEditor::View *const newActiveView = activeView()) { /** * check if we have any empty viewspaces and give them a view */ Q_FOREACH(KateViewSpace * vs, m_viewSpaceList) { if (!vs->currentView()) { createView(newActiveView->document(), vs); } } emit viewChanged(newActiveView); } } void KateViewManager::splitViewSpace(KateViewSpace *vs, // = 0 Qt::Orientation o) // = Qt::Horizontal { // emergency: fallback to activeViewSpace, and if still invalid, abort if (!vs) { vs = activeViewSpace(); } if (!vs) { return; } // get current splitter, and abort if null QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter) { return; } // avoid flicker KateUpdateDisabler disableUpdates (mainWindow()); // index where to insert new splitter/viewspace const int index = currentSplitter->indexOf(vs); // create new viewspace KateViewSpace *vsNew = new KateViewSpace(this); // only 1 children -> we are the root container. So simply set the orientation // and add the new view space, then correct the sizes to 50%:50% if (currentSplitter->count() == 1) { if (currentSplitter->orientation() != o) { currentSplitter->setOrientation(o); } QList sizes = currentSplitter->sizes(); sizes << (sizes.first() - currentSplitter->handleWidth()) / 2; sizes[0] = sizes[1]; currentSplitter->insertWidget(index, vsNew); currentSplitter->setSizes(sizes); } else { // create a new QSplitter and replace vs with the splitter. vs and newVS are // the new children of the new QSplitter QSplitter *newContainer = new QSplitter(o); // we don't allow full collapse, see bug 366014 newContainer->setChildrenCollapsible(false); QList currentSizes = currentSplitter->sizes(); newContainer->addWidget(vs); newContainer->addWidget(vsNew); currentSplitter->insertWidget(index, newContainer); newContainer->show(); // fix sizes of children of old and new splitter currentSplitter->setSizes(currentSizes); QList newSizes = newContainer->sizes(); newSizes[0] = (newSizes[0] + newSizes[1] - newContainer->handleWidth()) / 2; newSizes[1] = newSizes[0]; newContainer->setSizes(newSizes); } m_viewSpaceList.append(vsNew); activeViewSpace()->setActive(false); vsNew->setActive(true); vsNew->show(); createView((KTextEditor::Document *)activeView()->document()); updateViewSpaceActions(); } void KateViewManager::closeViewSpace(KTextEditor::View *view) { KateViewSpace *space; if (view) { space = static_cast(view->parentWidget()->parentWidget()); } else { space = activeViewSpace(); } removeViewSpace(space); } void KateViewManager::toggleSplitterOrientation() { KateViewSpace *vs = activeViewSpace(); if (!vs) { return; } // get current splitter, and abort if null QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter || (currentSplitter->count() == 1)) { return; } // avoid flicker KateUpdateDisabler disableUpdates (mainWindow()); // toggle orientation if (currentSplitter->orientation() == Qt::Horizontal) { currentSplitter->setOrientation(Qt::Vertical); } else { currentSplitter->setOrientation(Qt::Horizontal); } } bool KateViewManager::viewsInSameViewSpace(KTextEditor::View *view1, KTextEditor::View *view2) { if (!view1 || !view2) { return false; } if (m_viewSpaceList.size() == 1) { return true; } KateViewSpace *vs1 = static_cast(view1->parentWidget()->parentWidget()); KateViewSpace *vs2 = static_cast(view2->parentWidget()->parentWidget()); return vs1 && (vs1 == vs2); } void KateViewManager::removeViewSpace(KateViewSpace *viewspace) { // abort if viewspace is 0 if (!viewspace) { return; } // abort if this is the last viewspace if (m_viewSpaceList.count() < 2) { return; } // get current splitter QSplitter *currentSplitter = qobject_cast(viewspace->parentWidget()); // no splitter found, bah if (!currentSplitter) { return; } // // 1. get LRU document list from current viewspace // 2. delete current view space // 3. add LRU documents from deleted viewspace to new active viewspace // // backup LRU list const QVector lruDocumntsList = viewspace->lruDocumentList(); // avoid flicker KateUpdateDisabler disableUpdates (mainWindow()); // delete views of the viewspace while (viewspace->currentView()) { deleteView(viewspace->currentView()); } // cu viewspace m_viewSpaceList.removeAt(m_viewSpaceList.indexOf(viewspace)); delete viewspace; // at this point, the splitter has exactly 1 child Q_ASSERT(currentSplitter->count() == 1); // if we are not the root splitter, move the child one level up and delete // the splitter then. if (currentSplitter != this) { // get parent splitter QSplitter *parentSplitter = qobject_cast(currentSplitter->parentWidget()); // only do magic if found ;) if (parentSplitter) { int index = parentSplitter->indexOf(currentSplitter); // save current splitter size, as the removal of currentSplitter looses the info QList parentSizes = parentSplitter->sizes(); parentSplitter->insertWidget(index, currentSplitter->widget(0)); delete currentSplitter; // now restore the sizes again parentSplitter->setSizes(parentSizes); } } else if (QSplitter *splitter = qobject_cast(currentSplitter->widget(0))) { // we are the root splitter and have only one child, which is also a splitter // -> eliminate the redundant splitter and move both children into the root splitter QList sizes = splitter->sizes(); // adapt splitter orientation to the splitter we are about to delete currentSplitter->setOrientation(splitter->orientation()); currentSplitter->addWidget(splitter->widget(0)); currentSplitter->addWidget(splitter->widget(0)); delete splitter; currentSplitter->setSizes(sizes); } // merge docuemnts of closed view space activeViewSpace()->mergeLruList(lruDocumntsList); // find the view that is now active. KTextEditor::View *v = activeViewSpace()->currentView(); if (v) { activateView(v); } updateViewSpaceActions(); emit viewChanged(v); } void KateViewManager::slotCloseOtherViews() { // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); const KateViewSpace *active = activeViewSpace(); foreach(KateViewSpace * v, m_viewSpaceList) { if (active != v) { removeViewSpace(v); } } } void KateViewManager::slotHideOtherViews(bool hideOthers) { // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); const KateViewSpace *active = activeViewSpace(); foreach(KateViewSpace * v, m_viewSpaceList) { if (active != v) { v->setVisible(!hideOthers); } } // disable the split actions, if we are in single-view-mode m_splitViewVert->setDisabled(hideOthers); m_splitViewHoriz->setDisabled(hideOthers); m_closeView->setDisabled(hideOthers); m_closeOtherViews->setDisabled(hideOthers); m_toggleSplitterOrientation->setDisabled(hideOthers); } /** * session config functions */ void KateViewManager::saveViewConfiguration(KConfigGroup &config) { // set Active ViewSpace to 0, just in case there is none active (would be // strange) and config somehow has previous value set config.writeEntry("Active ViewSpace", 0); m_splitterIndex = 0; saveSplitterConfig(this, config.config(), config.name()); } void KateViewManager::restoreViewConfiguration(const KConfigGroup &config) { /** * remove the single client that is registered at the factory, if any */ if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } /** * delete viewspaces, they will delete the views */ qDeleteAll(m_viewSpaceList); m_viewSpaceList.clear(); /** * delete mapping of now deleted views */ m_views.clear(); /** * kill all previous existing sub-splitters, just to be sure * e.g. important if one restores a config in an existing window with some splitters */ while (count() > 0) { delete widget(0); } // reset lru history, too! m_minAge = 0; // start recursion for the root splitter (Splitter 0) restoreSplitter(config.config(), config.name() + QStringLiteral("-Splitter 0"), this, config.name()); // finally, make the correct view from the last session active int lastViewSpace = config.readEntry("Active ViewSpace", 0); if (lastViewSpace > m_viewSpaceList.size()) { lastViewSpace = 0; } if (lastViewSpace >= 0 && lastViewSpace < m_viewSpaceList.size()) { setActiveSpace(m_viewSpaceList.at(lastViewSpace)); // activate correct view (wish #195435, #188764) activateView(m_viewSpaceList.at(lastViewSpace)->currentView()); // give view the focus to avoid focus stealing by toolviews / plugins m_viewSpaceList.at(lastViewSpace)->currentView()->setFocus(); } // emergency if (m_viewSpaceList.empty()) { // kill bad children while (count()) { delete widget(0); } KateViewSpace *vs = new KateViewSpace(this, nullptr); addWidget(vs); vs->setActive(true); m_viewSpaceList.append(vs); /** * activate at least one document! */ activateView(KateApp::self()->documentManager()->documentList().last()); if (!vs->currentView()) { createView(activeView()->document(), vs); } } updateViewSpaceActions(); } QString KateViewManager::saveSplitterConfig(QSplitter *s, KConfigBase *configBase, const QString &viewConfGrp) { /** * avoid to export invisible view spaces * else they will stick around for ever in sessions * bug 358266 - code initially done during load * bug 381433 - moved code to save */ /** * create new splitter name, might be not used */ const auto grp = QString(viewConfGrp + QStringLiteral("-Splitter %1")).arg(m_splitterIndex); ++m_splitterIndex; // a QSplitter has two children, either QSplitters and/or KateViewSpaces // special case: root splitter might have only one child (just for info) QStringList childList; const auto sizes = s->sizes(); for (int it = 0; it < s->count(); ++it) { // skip empty sized invisible ones, if not last one, we need one thing at least if ((sizes[it] == 0) && ((it + 1 < s->count()) || !childList.empty())) continue; // For KateViewSpaces, ask them to save the file list. auto obj = s->widget(it); if (auto kvs = qobject_cast(obj)) { childList.append(QString(viewConfGrp + QStringLiteral("-ViewSpace %1")).arg(m_viewSpaceList.indexOf(kvs))); kvs->saveConfig(configBase, m_viewSpaceList.indexOf(kvs), viewConfGrp); // save active viewspace if (kvs->isActiveSpace()) { KConfigGroup viewConfGroup(configBase, viewConfGrp); viewConfGroup.writeEntry("Active ViewSpace", m_viewSpaceList.indexOf(kvs)); } } // for QSplitters, recurse else if (auto splitter = qobject_cast(obj)) { childList.append(saveSplitterConfig(splitter, configBase, viewConfGrp)); } } // if only one thing, skip splitter config export, if not top splitter if ((s != this) && (childList.size() == 1)) return childList.at(0); // Save sizes, orient, children for this splitter KConfigGroup config(configBase, grp); config.writeEntry("Sizes", sizes); config.writeEntry("Orientation", int(s->orientation())); config.writeEntry("Children", childList); return grp; } void KateViewManager::restoreSplitter(const KConfigBase *configBase, const QString &group, QSplitter *parent, const QString &viewConfGrp) { KConfigGroup config(configBase, group); parent->setOrientation((Qt::Orientation)config.readEntry("Orientation", int(Qt::Horizontal))); - QStringList children = config.readEntry("Children", QStringList()); - for (QStringList::Iterator it = children.begin(); it != children.end(); ++it) { + const QStringList children = config.readEntry("Children", QStringList()); + for (const auto& str : children) { // for a viewspace, create it and open all documents therein. - if ((*it).startsWith(viewConfGrp + QStringLiteral("-ViewSpace"))) { + if (str.startsWith(viewConfGrp + QStringLiteral("-ViewSpace"))) { KateViewSpace *vs = new KateViewSpace(this, nullptr); m_viewSpaceList.append(vs); // make active so that the view created in restoreConfig has this // new view space as parent. setActiveSpace(vs); parent->addWidget(vs); - vs->restoreConfig(this, configBase, *it); + vs->restoreConfig(this, configBase, str); vs->show(); } else { // for a splitter, recurse auto newContainer = new QSplitter(parent); // we don't allow full collapse, see bug 366014 newContainer->setChildrenCollapsible(false); - restoreSplitter(configBase, *it, newContainer, viewConfGrp); + restoreSplitter(configBase, str, newContainer, viewConfGrp); } } // set sizes parent->setSizes(config.readEntry("Sizes", QList())); parent->show(); } void KateViewManager::moveSplitter(Qt::Key key, int repeats) { if (repeats < 1) { return; } KateViewSpace *vs = activeViewSpace(); if (!vs) { return; } QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter) { return; } if (currentSplitter->count() == 1) { return; } int move = 4 * repeats; // try to use font height in pixel to move splitter { KTextEditor::Attribute::Ptr attrib(vs->currentView()->defaultStyleAttribute(KTextEditor::dsNormal)); QFontMetrics fm(attrib->font()); move = fm.height() * repeats; } QWidget *currentWidget = (QWidget *)vs; bool foundSplitter = false; // find correct splitter to be moved while (currentSplitter && currentSplitter->count() != 1) { if (currentSplitter->orientation() == Qt::Horizontal && (key == Qt::Key_Right || key == Qt::Key_Left)) { foundSplitter = true; } if (currentSplitter->orientation() == Qt::Vertical && (key == Qt::Key_Up || key == Qt::Key_Down)) { foundSplitter = true; } // if the views within the current splitter can be resized, resize them if (foundSplitter) { QList currentSizes = currentSplitter->sizes(); int index = currentSplitter->indexOf(currentWidget); if ((index == 0 && (key == Qt::Key_Left || key == Qt::Key_Up)) || (index == 1 && (key == Qt::Key_Right || key == Qt::Key_Down))) { currentSizes[index] -= move; } if ((index == 0 && (key == Qt::Key_Right || key == Qt::Key_Down)) || (index == 1 && (key == Qt::Key_Left || key == Qt::Key_Up))) { currentSizes[index] += move; } if (index == 0 && (key == Qt::Key_Right || key == Qt::Key_Down)) { currentSizes[index + 1] -= move; } if (index == 0 && (key == Qt::Key_Left || key == Qt::Key_Up)) { currentSizes[index + 1] += move; } if (index == 1 && (key == Qt::Key_Right || key == Qt::Key_Down)) { currentSizes[index - 1] += move; } if (index == 1 && (key == Qt::Key_Left || key == Qt::Key_Up)) { currentSizes[index - 1] -= move; } currentSplitter->setSizes(currentSizes); break; } currentWidget = (QWidget *)currentSplitter; // the parent of the current splitter will become the current splitter currentSplitter = qobject_cast(currentSplitter->parentWidget()); } }

    foo test bar X *