diff --git a/addons/filebrowser/katefilebrowser.cpp b/addons/filebrowser/katefilebrowser.cpp index bfb6446c0..c0d0835ad 100644 --- a/addons/filebrowser/katefilebrowser.cpp +++ b/addons/filebrowser/katefilebrowser.cpp @@ -1,337 +1,339 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund Copyright (C) 2007 Mirko Stocker Copyright (C) 2009 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // BEGIN Includes #include "katefilebrowser.h" #include "katebookmarkhandler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // END Includes KateFileBrowser::KateFileBrowser(KTextEditor::MainWindow *mainWindow, QWidget *parent) : QWidget(parent) , m_mainWindow(mainWindow) { QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); m_toolbar = new KToolBar(this); m_toolbar->setMovable(false); m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); m_toolbar->setContextMenuPolicy(Qt::NoContextMenu); mainLayout->addWidget(m_toolbar); // includes some actions, but not hooked into the shortcut dialog atm m_actionCollection = new KActionCollection(this); m_actionCollection->addAssociatedWidget(this); KFilePlacesModel *model = new KFilePlacesModel(this); m_urlNavigator = new KUrlNavigator(model, QUrl::fromLocalFile(QDir::homePath()), this); connect(m_urlNavigator, &KUrlNavigator::urlChanged, this, &KateFileBrowser::updateDirOperator); mainLayout->addWidget(m_urlNavigator); m_dirOperator = new KDirOperator(QUrl(), this); // Default to a view with only one column since columns are auto-sized m_dirOperator->setView(KFile::Tree); m_dirOperator->view()->setSelectionMode(QAbstractItemView::ExtendedSelection); m_dirOperator->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); mainLayout->addWidget(m_dirOperator); // Mime filter for the KDirOperator QStringList filter; filter << QStringLiteral("text/plain") << QStringLiteral("text/html") << QStringLiteral("inode/directory") << QStringLiteral("application/x-zerosize"); m_dirOperator->setNewFileMenuSupportedMimeTypes(filter); setFocusProxy(m_dirOperator); connect(m_dirOperator, &KDirOperator::viewChanged, this, &KateFileBrowser::selectorViewChanged); connect(m_urlNavigator, &KUrlNavigator::returnPressed, m_dirOperator, static_cast(&KDirOperator::setFocus)); // now all actions exist in dir operator and we can use them in the toolbar setupActions(); setupToolbar(); m_filter = new KHistoryComboBox(true, this); m_filter->setMaxCount(10); m_filter->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); m_filter->lineEdit()->setPlaceholderText(i18n("Search")); mainLayout->addWidget(m_filter); connect(m_filter, &KHistoryComboBox::editTextChanged, this, &KateFileBrowser::slotFilterChange); connect(m_filter, static_cast(&KHistoryComboBox::returnPressed), m_filter, &KHistoryComboBox::addToHistory); connect(m_filter, static_cast(&KHistoryComboBox::returnPressed), m_dirOperator, static_cast(&KDirOperator::setFocus)); connect(m_dirOperator, &KDirOperator::urlEntered, this, &KateFileBrowser::updateUrlNavigator); // Connect the bookmark handler connect(m_bookmarkHandler, &KateBookmarkHandler::openUrl, this, static_cast(&KateFileBrowser::setDir)); m_filter->setWhatsThis(i18n("Enter a name filter to limit which files are displayed.")); connect(m_dirOperator, &KDirOperator::fileSelected, this, &KateFileBrowser::fileSelected); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateFileBrowser::autoSyncFolder); } KateFileBrowser::~KateFileBrowser() { } // END Constructor/Destructor // BEGIN Public Methods void KateFileBrowser::setupToolbar() { KConfigGroup config(KSharedConfig::openConfig(), "filebrowser"); QStringList actions = config.readEntry("toolbar actions", QStringList()); if (actions.isEmpty()) // default toolbar actions << QStringLiteral("back") << QStringLiteral("forward") << QStringLiteral("bookmarks") << QStringLiteral("sync_dir") << QStringLiteral("configure"); // remove all actions from the toolbar (there should be none) m_toolbar->clear(); // now add all actions to the toolbar - foreach (const QString &it, actions) { + for (const QString &it : qAsConst(actions)) { QAction *ac = nullptr; if (it.isEmpty()) continue; if (it == QLatin1String("bookmarks") || it == QLatin1String("sync_dir") || it == QLatin1String("configure")) ac = actionCollection()->action(it); else ac = m_dirOperator->actionCollection()->action(it); if (ac) m_toolbar->addAction(ac); } } void KateFileBrowser::readSessionConfig(const KConfigGroup &cg) { m_dirOperator->readConfig(cg); m_dirOperator->setView(KFile::Default); m_urlNavigator->setLocationUrl(cg.readEntry("location", QUrl::fromLocalFile(QDir::homePath()))); setDir(cg.readEntry("location", QUrl::fromLocalFile(QDir::homePath()))); m_autoSyncFolder->setChecked(cg.readEntry("auto sync folder", false)); m_filter->setHistoryItems(cg.readEntry("filter history", QStringList()), true); } void KateFileBrowser::writeSessionConfig(KConfigGroup &cg) { m_dirOperator->writeConfig(cg); cg.writeEntry("location", m_urlNavigator->locationUrl().url()); cg.writeEntry("auto sync folder", m_autoSyncFolder->isChecked()); cg.writeEntry("filter history", m_filter->historyItems()); } // END Public Methods // BEGIN Public Slots void KateFileBrowser::slotFilterChange(const QString &nf) { QString f = nf.trimmed(); const bool empty = f.isEmpty() || f == QLatin1String("*"); if (empty) { m_dirOperator->clearFilter(); } else { m_dirOperator->setNameFilter(f); } m_dirOperator->updateDir(); } bool kateFileSelectorIsReadable(const QUrl &url) { if (!url.isLocalFile()) return true; // what else can we say? QDir dir(url.toLocalFile()); return dir.exists(); } void KateFileBrowser::setDir(const QUrl &u) { QUrl newurl; if (!u.isValid()) newurl = QUrl::fromLocalFile(QDir::homePath()); else newurl = u; QString path(newurl.path()); if (!path.endsWith(QLatin1Char('/'))) path += QLatin1Char('/'); newurl.setPath(path); if (!kateFileSelectorIsReadable(newurl)) { newurl.setPath(newurl.path() + QStringLiteral("../")); newurl = newurl.adjusted(QUrl::NormalizePathSegments); } if (!kateFileSelectorIsReadable(newurl)) { newurl = QUrl::fromLocalFile(QDir::homePath()); } m_dirOperator->setUrl(newurl, true); } // END Public Slots // BEGIN Private Slots void KateFileBrowser::fileSelected(const KFileItem & /*file*/) { openSelectedFiles(); } void KateFileBrowser::openSelectedFiles() { const KFileItemList list = m_dirOperator->selectedItems(); if (list.count() > 20) { if (KMessageBox::questionYesNo(this, i18np("You are trying to open 1 file, are you sure?", "You are trying to open %1 files, are you sure?", list.count())) == KMessageBox::No) return; } - foreach (const KFileItem &item, list) { + for (const KFileItem &item : list) { m_mainWindow->openUrl(item.url()); } m_dirOperator->view()->selectionModel()->clear(); } void KateFileBrowser::updateDirOperator(const QUrl &u) { m_dirOperator->setUrl(u, true); } void KateFileBrowser::updateUrlNavigator(const QUrl &u) { m_urlNavigator->setLocationUrl(u); } void KateFileBrowser::setActiveDocumentDir() { QUrl u = activeDocumentUrl(); if (!u.isEmpty()) setDir(KIO::upUrl(u)); } void KateFileBrowser::autoSyncFolder() { if (m_autoSyncFolder->isChecked()) { setActiveDocumentDir(); } } void KateFileBrowser::selectorViewChanged(QAbstractItemView *newView) { newView->setSelectionMode(QAbstractItemView::ExtendedSelection); } // END Private Slots // BEGIN Protected QUrl KateFileBrowser::activeDocumentUrl() { KTextEditor::View *v = m_mainWindow->activeView(); if (v) return v->document()->url(); return QUrl(); } void KateFileBrowser::setupActions() { // bookmarks action! KActionMenu *acmBookmarks = new KActionMenu(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks"), this); acmBookmarks->setDelayed(false); m_bookmarkHandler = new KateBookmarkHandler(this, acmBookmarks->menu()); acmBookmarks->setShortcutContext(Qt::WidgetWithChildrenShortcut); // action for synchronizing the dir operator with the current document path QAction *syncFolder = new QAction(this); syncFolder->setShortcutContext(Qt::WidgetWithChildrenShortcut); syncFolder->setText(i18n("Current Document Folder")); syncFolder->setIcon(QIcon::fromTheme(QStringLiteral("system-switch-user"))); connect(syncFolder, &QAction::triggered, this, &KateFileBrowser::setActiveDocumentDir); m_actionCollection->addAction(QStringLiteral("sync_dir"), syncFolder); m_actionCollection->addAction(QStringLiteral("bookmarks"), acmBookmarks); // section for settings menu KActionMenu *optionsMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("configure")), i18n("Options"), this); optionsMenu->setDelayed(false); optionsMenu->addAction(m_dirOperator->actionCollection()->action(QStringLiteral("short view"))); optionsMenu->addAction(m_dirOperator->actionCollection()->action(QStringLiteral("detailed view"))); optionsMenu->addAction(m_dirOperator->actionCollection()->action(QStringLiteral("tree view"))); optionsMenu->addAction(m_dirOperator->actionCollection()->action(QStringLiteral("detailed tree view"))); optionsMenu->addSeparator(); optionsMenu->addAction(m_dirOperator->actionCollection()->action(QStringLiteral("show hidden"))); // action for synchronising the dir operator with the current document path m_autoSyncFolder = new QAction(this); m_autoSyncFolder->setCheckable(true); m_autoSyncFolder->setText(i18n("Automatically synchronize with current document")); m_autoSyncFolder->setIcon(QIcon::fromTheme(QStringLiteral("system-switch-user"))); connect(m_autoSyncFolder, &QAction::triggered, this, &KateFileBrowser::autoSyncFolder); optionsMenu->addAction(m_autoSyncFolder); m_actionCollection->addAction(QStringLiteral("configure"), optionsMenu); // // Remove all shortcuts due to shortcut clashes (e.g. F5: reload, Ctrl+B: bookmark) // BUGS: #188954, #236368 // - foreach (QAction *a, m_actionCollection->actions()) { + const auto actions = m_actionCollection->actions(); + for (QAction *a : actions) { a->setShortcut(QKeySequence()); } - foreach (QAction *a, m_dirOperator->actionCollection()->actions()) { + const auto dirActions = m_dirOperator->actionCollection()->actions(); + for (QAction *a : dirActions) { a->setShortcut(QKeySequence()); } } // END Protected // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/filebrowser/katefilebrowserconfig.cpp b/addons/filebrowser/katefilebrowserconfig.cpp index c923e478d..565fa52bf 100644 --- a/addons/filebrowser/katefilebrowserconfig.cpp +++ b/addons/filebrowser/katefilebrowserconfig.cpp @@ -1,196 +1,196 @@ /* 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) { 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) { + const QList list = acSel->selectedListWidget()->findItems(QStringLiteral("*"), Qt::MatchWildcard); + for (QListWidgetItem *item : list) { aItem = static_cast(item); l << aItem->idstring(); } config.writeEntry("toolbar actions", l); fileBrowser->setupToolbar(); } void KateFileBrowserConfigPage::reset() { // hmm, what is this supposed to do, actually?? init(); m_changed = false; } void KateFileBrowserConfigPage::init() { KConfigGroup config(KSharedConfig::openConfig(), "filebrowser"); // toolbar QStringList l = config.readEntry("toolbar actions", QStringList()); if (l.isEmpty()) // default toolbar l << QStringLiteral("back") << QStringLiteral("forward") << QStringLiteral("bookmarks") << QStringLiteral("sync_dir") << QStringLiteral("configure"); // actions from diroperator + two of our own const QStringList allActions {QStringLiteral("up"), QStringLiteral("back"), QStringLiteral("forward"), QStringLiteral("home"), QStringLiteral("reload"), QStringLiteral("mkdir"), QStringLiteral("delete"), QStringLiteral("short view"), QStringLiteral("detailed view"), QStringLiteral("tree view"), QStringLiteral("detailed tree view"), QStringLiteral("show hidden"), // QStringLiteral("view menu"), // QStringLiteral("properties"), QStringLiteral("bookmarks"), QStringLiteral("sync_dir"), QStringLiteral("configure")}; QRegularExpression re(QStringLiteral("&(?=[^&])")); QAction *ac = nullptr; QListWidget *lb; for (const auto &actionName : allActions) { lb = l.contains(actionName) ? acSel->selectedListWidget() : acSel->availableListWidget(); if (actionName == QLatin1String("bookmarks") || actionName == QLatin1String("sync_dir") || actionName == QLatin1String("configure")) ac = fileBrowser->actionCollection()->action(actionName); else ac = fileBrowser->dirOperator()->actionCollection()->action(actionName); if (ac) { QString text = ac->text().remove(re); // CJK languages need a filtering message for action texts in lists, // to remove special accelerators that they use. // The exact same filtering message exists in kdelibs; hence, // avoid extraction here and let it be sourced from kdelibs. #define i18ncX i18nc text = i18ncX("@item:intable Action name in toolbar editor", "%1", text); new ActionLBItem(lb, ac->icon(), text, actionName); } } } void KateFileBrowserConfigPage::slotMyChanged() { m_changed = true; emit changed(); } // END KateFileBrowserConfigPage // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/filetree/katefiletree.cpp b/addons/filetree/katefiletree.cpp index 44345e5ee..3d1a35a55 100644 --- a/addons/filetree/katefiletree.cpp +++ b/addons/filetree/katefiletree.cpp @@ -1,709 +1,709 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom Copyright (C) 2014 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // BEGIN Includes #include "katefiletree.h" #include "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; const KService::List offers = KMimeTypeTrader::self()->query(mime.name(), QStringLiteral("Application")); // for each one, insert a menu item... for (const auto &service : offers) { if (service->name() == QLatin1String("Kate")) { continue; } a = menu->addAction(QIcon::fromTheme(service->icon()), service->name()); a->setData(service->entryPath()); } // append "Other..." to call the KDE "open with" dialog. a = menu->addAction(i18n("&Other...")); a->setData(QString()); } void KateFileTree::slotOpenWithMenuAction(QAction *a) { QList list; KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } list.append(doc->url()); const QString openWith = a->data().toString(); if (openWith.isEmpty()) { // display "open with" dialog KOpenWithDialog dlg(list); if (dlg.exec()) { KRun::runService(*dlg.service(), list, this); } return; } KService::Ptr app = KService::serviceByDesktopPath(openWith); if (app) { KRun::runService(*app, list, this); } else { KMessageBox::error(this, i18n("Application '%1' not found.", openWith), i18n("Application not found")); } } Q_DECLARE_METATYPE(QList) void KateFileTree::slotDocumentClose() { m_previouslySelected = QModelIndex(); QVariant v = m_indexContextMenu.data(KateFileTreeModel::DocumentTreeRole); if (!v.isValid()) { return; } QList closingDocuments = v.value>(); KTextEditor::Editor::instance()->application()->closeDocuments(closingDocuments); } void KateFileTree::slotExpandRecursive() { if (!m_indexContextMenu.isValid()) { return; } // Work list for DFS walk over sub tree QList worklist = {m_indexContextMenu}; while (!worklist.isEmpty()) { QPersistentModelIndex index = worklist.takeLast(); // Expand current item expand(index); // Append all children of current item for (int i = 0; i < model()->rowCount(index); ++i) { worklist.append(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) { + const QList docs = v.value>(); + for (KTextEditor::Document *doc : docs) { doc->documentReload(); } } void KateFileTree::slotOpenContainingFolder() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (doc) { KIO::highlightInFileManager({doc->url()}); } } void KateFileTree::slotCopyFilename() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here // (make sure that the mentioned bug 381052 does not reappear) if (doc) { // ensure we prefer native separators, bug 381052 if (doc->url().isLocalFile()) { QApplication::clipboard()->setText(QDir::toNativeSeparators(doc->url().toLocalFile())); } else { QApplication::clipboard()->setText(doc->url().url()); } } } void KateFileTree::slotRenameFile() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here if (!doc) { return; } const QUrl oldFileUrl = doc->url(); const QString oldFileName = doc->url().fileName(); bool ok; QString newFileName = QInputDialog::getText(this, i18n("Rename file"), i18n("New file name"), QLineEdit::Normal, oldFileName, &ok); if (!ok) { return; } QUrl newFileUrl = oldFileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); newFileUrl.setPath(newFileUrl.path() + QLatin1Char('/') + newFileName); if (!newFileUrl.isValid()) { return; } if (!doc->closeUrl()) { return; } doc->waitSaveComplete(); KIO::CopyJob *job = KIO::move(oldFileUrl, newFileUrl); QSharedPointer sc(new QMetaObject::Connection()); auto success = [doc, sc](KIO::Job *, const QUrl &, const QUrl &realNewFileUrl, const QDateTime &, bool, bool) { doc->openUrl(realNewFileUrl); doc->documentSavedOrUploaded(doc, true); QObject::disconnect(*sc); }; *sc = connect(job, &KIO::CopyJob::copyingDone, doc, success); if (!job->exec()) { KMessageBox::sorry(this, i18n("File \"%1\" could not be moved to \"%2\"", oldFileUrl.toDisplayString(), newFileUrl.toDisplayString())); doc->openUrl(oldFileUrl); } } void KateFileTree::slotDocumentFirst() { KTextEditor::Document *doc = model()->data(model()->index(0, 0), KateFileTreeModel::DocumentRole).value(); if (doc) { emit activateDocument(doc); } } void KateFileTree::slotDocumentLast() { int count = model()->rowCount(model()->parent(currentIndex())); KTextEditor::Document *doc = model()->data(model()->index(count - 1, 0), KateFileTreeModel::DocumentRole).value(); if (doc) { emit activateDocument(doc); } } void KateFileTree::slotDocumentPrev() { KateFileTreeProxyModel *ftpm = static_cast(model()); QModelIndex current_index = currentIndex(); QModelIndex prev; // scan up the tree skipping any dir nodes while (current_index.isValid()) { if (current_index.row() > 0) { current_index = ftpm->sibling(current_index.row() - 1, current_index.column(), current_index); if (!current_index.isValid()) { break; } if (ftpm->isDir(current_index)) { // try and select the last child in this parent int children = ftpm->rowCount(current_index); current_index = ftpm->index(children - 1, 0, current_index); if (ftpm->isDir(current_index)) { // since we're a dir, keep going while (ftpm->isDir(current_index)) { children = ftpm->rowCount(current_index); current_index = ftpm->index(children - 1, 0, current_index); } if (!ftpm->isDir(current_index)) { prev = current_index; break; } continue; } else { // we're the previous file, set prev prev = current_index; break; } } else { // found document item prev = current_index; break; } } else { // just select the parent, the logic above will handle the rest current_index = ftpm->parent(current_index); if (!current_index.isValid()) { // paste the root node here, try and wrap around int children = ftpm->rowCount(current_index); QModelIndex last_index = ftpm->index(children - 1, 0, current_index); if (!last_index.isValid()) { break; } if (ftpm->isDir(last_index)) { // last node is a dir, select last child row int last_children = ftpm->rowCount(last_index); prev = ftpm->index(last_children - 1, 0, last_index); // bug here? break; } else { // got last file node prev = last_index; break; } } } } if (prev.isValid()) { KTextEditor::Document *doc = model()->data(prev, KateFileTreeModel::DocumentRole).value(); emit activateDocument(doc); } } void KateFileTree::slotDocumentNext() { KateFileTreeProxyModel *ftpm = static_cast(model()); QModelIndex current_index = currentIndex(); int parent_row_count = ftpm->rowCount(ftpm->parent(current_index)); QModelIndex next; // scan down the tree skipping any dir nodes while (current_index.isValid()) { if (current_index.row() < parent_row_count - 1) { current_index = ftpm->sibling(current_index.row() + 1, current_index.column(), current_index); if (!current_index.isValid()) { break; } if (ftpm->isDir(current_index)) { // we have a dir node while (ftpm->isDir(current_index)) { current_index = ftpm->index(0, 0, current_index); } parent_row_count = ftpm->rowCount(ftpm->parent(current_index)); if (!ftpm->isDir(current_index)) { next = current_index; break; } } else { // found document item next = current_index; break; } } else { // select the parent's next sibling QModelIndex parent_index = ftpm->parent(current_index); int grandparent_row_count = ftpm->rowCount(ftpm->parent(parent_index)); current_index = parent_index; parent_row_count = grandparent_row_count; // at least if we're not past the last node if (!current_index.isValid()) { // paste the root node here, try and wrap around QModelIndex last_index = ftpm->index(0, 0, QModelIndex()); if (!last_index.isValid()) { break; } if (ftpm->isDir(last_index)) { // last node is a dir, select first child row while (ftpm->isDir(last_index)) { if (ftpm->rowCount(last_index)) { // has children, select first last_index = ftpm->index(0, 0, last_index); } } next = last_index; break; } else { // got first file node next = last_index; break; } } } } if (next.isValid()) { KTextEditor::Document *doc = model()->data(next, KateFileTreeModel::DocumentRole).value(); emit activateDocument(doc); } } void KateFileTree::slotPrintDocument() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } doc->print(); } void KateFileTree::slotPrintDocumentPreview() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); if (!doc) { return; } doc->printPreview(); } void KateFileTree::slotResetHistory() { KateFileTreeProxyModel *ftpm = static_cast(model()); KateFileTreeModel *ftm = static_cast(ftpm->sourceModel()); ftm->resetHistory(); } void KateFileTree::slotDocumentDelete() { KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value(); // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here if (!doc) { return; } QUrl url = doc->url(); bool go = (KMessageBox::warningContinueCancel( this, i18n("Do you really want to delete file \"%1\" from storage?", url.toDisplayString()), i18n("Delete file?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("filetreedeletefile")) == KMessageBox::Continue); if (!go) { return; } if (!KTextEditor::Editor::instance()->application()->closeDocument(doc)) { return; // no extra message, the internals of ktexteditor should take care of that. } if (url.isValid()) { KIO::DeleteJob *job = KIO::del(url); if (!job->exec()) { KMessageBox::sorry(this, i18n("File \"%1\" could not be deleted.", url.toDisplayString())); } } } // END KateFileTree diff --git a/addons/filetree/katefiletreemodel.cpp b/addons/filetree/katefiletreemodel.cpp index 6ade9d394..a0fdaa2b1 100644 --- a/addons/filetree/katefiletreemodel.cpp +++ b/addons/filetree/katefiletreemodel.cpp @@ -1,1338 +1,1339 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "katefiletreemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katefiletreedebug.h" class ProxyItemDir; class ProxyItem { friend class KateFileTreeModel; public: enum Flag { None = 0, Dir = 1, Modified = 2, ModifiedExternally = 4, DeletedExternally = 8, Empty = 16, ShowFullPath = 32, Host = 64 }; Q_DECLARE_FLAGS(Flags, Flag) ProxyItem(const QString &n, ProxyItemDir *p = nullptr, Flags f = ProxyItem::None); ~ProxyItem(); int addChild(ProxyItem *p); void remChild(ProxyItem *p); ProxyItemDir *parent() const; ProxyItem *child(int idx) const; int childCount() const; int row() const; const QString &display() const; const QString &documentName() const; const QString &path() const; void setPath(const QString &str); void setHost(const QString &host); const QString &host() const; void setIcon(const QIcon &i); const QIcon &icon() const; const QList &children() const; QList &children(); void setDoc(KTextEditor::Document *doc); KTextEditor::Document *doc() const; /** * the view usess this to close all the documents under the folder * @returns list of all the (nested) documents under this node */ QList docTree() const; void setFlags(Flags flags); void setFlag(Flag flag); void clearFlag(Flag flag); bool flag(Flag flag) const; private: QString m_path; QString m_documentName; ProxyItemDir *m_parent; QList m_children; int m_row; Flags m_flags; QString m_display; QIcon m_icon; KTextEditor::Document *m_doc; QString m_host; protected: void updateDisplay(); void updateDocumentName(); }; QDebug operator<<(QDebug dbg, ProxyItem *item) { if (!item) { dbg.nospace() << "ProxyItem(0x0) "; return dbg.maybeSpace(); } const void *parent = static_cast(item->parent()); dbg.nospace() << "ProxyItem(" << (void *)item << ","; dbg.nospace() << parent << "," << item->row() << ","; dbg.nospace() << item->doc() << "," << item->path() << ") "; return dbg.maybeSpace(); } class ProxyItemDir : public ProxyItem { public: ProxyItemDir(const QString &n, ProxyItemDir *p = nullptr) : ProxyItem(n, p) { setFlag(ProxyItem::Dir); updateDisplay(); setIcon(QIcon::fromTheme(QStringLiteral("folder"))); } }; QDebug operator<<(QDebug dbg, ProxyItemDir *item) { if (!item) { dbg.nospace() << "ProxyItemDir(0x0) "; return dbg.maybeSpace(); } const void *parent = static_cast(item->parent()); dbg.nospace() << "ProxyItemDir(" << (void *)item << ","; dbg.nospace() << parent << "," << item->row() << ","; dbg.nospace() << item->path() << ", children:" << item->childCount() << ") "; return dbg.maybeSpace(); } Q_DECLARE_OPERATORS_FOR_FLAGS(ProxyItem::Flags) // BEGIN ProxyItem ProxyItem::ProxyItem(const QString &d, ProxyItemDir *p, ProxyItem::Flags f) : m_path(d) , m_parent(Q_NULLPTR) , m_row(-1) , m_flags(f) , m_doc(nullptr) { updateDisplay(); /** * add to parent, if parent passed * we assigned above nullptr to parent to not trigger * remove from old parent here! */ if (p) { p->addChild(this); } } ProxyItem::~ProxyItem() { qDeleteAll(m_children); } void ProxyItem::updateDisplay() { // triggers only if this is a top level node and the root has the show full path flag set. if (flag(ProxyItem::Dir) && m_parent && !m_parent->m_parent && m_parent->flag(ProxyItem::ShowFullPath)) { m_display = m_path; if (m_display.startsWith(QDir::homePath())) { m_display.replace(0, QDir::homePath().length(), QStringLiteral("~")); } } else { m_display = m_path.section(QLatin1Char('/'), -1, -1); if (flag(ProxyItem::Host) && (!m_parent || (m_parent && !m_parent->m_parent))) { const QString hostPrefix = QStringLiteral("[%1]").arg(host()); if (hostPrefix != m_display) { m_display = hostPrefix + m_display; } } } } int ProxyItem::addChild(ProxyItem *item) { // remove from old parent, is any if (item->m_parent) { item->m_parent->remChild(item); } const int item_row = m_children.count(); item->m_row = item_row; m_children.append(item); item->m_parent = static_cast(this); item->updateDisplay(); return item_row; } void ProxyItem::remChild(ProxyItem *item) { const int idx = m_children.indexOf(item); Q_ASSERT(idx != -1); m_children.removeAt(idx); for (int i = idx; i < m_children.count(); i++) { m_children[i]->m_row = i; } item->m_parent = nullptr; } ProxyItemDir *ProxyItem::parent() const { return m_parent; } ProxyItem *ProxyItem::child(int idx) const { return (idx < 0 || idx >= m_children.count()) ? nullptr : m_children[idx]; } int ProxyItem::childCount() const { return m_children.count(); } int ProxyItem::row() const { return m_row; } const QIcon &ProxyItem::icon() const { return m_icon; } void ProxyItem::setIcon(const QIcon &i) { m_icon = i; } const QString &ProxyItem::documentName() const { return m_documentName; } const QString &ProxyItem::display() const { return m_display; } const QString &ProxyItem::path() const { return m_path; } void ProxyItem::setPath(const QString &p) { m_path = p; updateDisplay(); } const QList &ProxyItem::children() const { return m_children; } QList &ProxyItem::children() { return m_children; } void ProxyItem::setDoc(KTextEditor::Document *doc) { Q_ASSERT(doc); m_doc = doc; updateDocumentName(); } KTextEditor::Document *ProxyItem::doc() const { return m_doc; } QList ProxyItem::docTree() const { QList result; if (m_doc) { result.append(m_doc); return result; } - foreach (const ProxyItem *item, m_children) { + for (const ProxyItem *item : qAsConst(m_children)) { result.append(item->docTree()); } return result; } bool ProxyItem::flag(Flag f) const { return m_flags & f; } void ProxyItem::setFlag(Flag f) { m_flags |= f; } void ProxyItem::setFlags(Flags f) { m_flags = f; } void ProxyItem::clearFlag(Flag f) { m_flags &= ~f; } void ProxyItem::setHost(const QString &host) { m_host = host; if (host.isEmpty()) { clearFlag(Host); } else { setFlag(Host); } updateDocumentName(); updateDisplay(); } const QString &ProxyItem::host() const { return m_host; } void ProxyItem::updateDocumentName() { const QString docName = m_doc ? m_doc->documentName() : QString(); if (flag(ProxyItem::Host)) { m_documentName = QStringLiteral("[%1]%2").arg(m_host, docName); } else { m_documentName = docName; } } // END ProxyItem KateFileTreeModel::KateFileTreeModel(QObject *p) : QAbstractItemModel(p) , m_root(new ProxyItemDir(QStringLiteral("m_root"), nullptr)) { // setup default settings // session init will set these all soon const KColorScheme colors(QPalette::Active); const QColor bg = colors.background().color(); m_editShade = KColorUtils::tint(bg, colors.foreground(KColorScheme::ActiveText).color(), 0.5); m_viewShade = KColorUtils::tint(bg, colors.foreground(KColorScheme::VisitedText).color(), 0.5); m_shadingEnabled = true; m_listMode = false; initModel(); } KateFileTreeModel::~KateFileTreeModel() { delete m_root; } bool KateFileTreeModel::shadingEnabled() const { return m_shadingEnabled; } void KateFileTreeModel::setShadingEnabled(bool se) { if (m_shadingEnabled != se) { updateBackgrounds(true); m_shadingEnabled = se; } } const QColor &KateFileTreeModel::editShade() const { return m_editShade; } void KateFileTreeModel::setEditShade(const QColor &es) { m_editShade = es; } const QColor &KateFileTreeModel::viewShade() const { return m_viewShade; } void KateFileTreeModel::setViewShade(const QColor &vs) { m_viewShade = vs; } bool KateFileTreeModel::showFullPathOnRoots(void) const { return m_root->flag(ProxyItem::ShowFullPath); } void KateFileTreeModel::setShowFullPathOnRoots(bool s) { if (s) { m_root->setFlag(ProxyItem::ShowFullPath); } else { m_root->clearFlag(ProxyItem::ShowFullPath); } foreach (ProxyItem *root, m_root->children()) { root->updateDisplay(); } } void KateFileTreeModel::initModel() { // add already existing documents foreach (KTextEditor::Document *doc, KTextEditor::Editor::instance()->application()->documents()) { documentOpened(doc); } } void KateFileTreeModel::clearModel() { // remove all items // can safely ignore documentClosed here beginRemoveRows(QModelIndex(), 0, qMax(m_root->childCount() - 1, 0)); delete m_root; m_root = new ProxyItemDir(QStringLiteral("m_root"), nullptr); m_docmap.clear(); m_viewHistory.clear(); m_editHistory.clear(); m_brushes.clear(); endRemoveRows(); } void KateFileTreeModel::connectDocument(const KTextEditor::Document *doc) { connect(doc, &KTextEditor::Document::documentNameChanged, this, &KateFileTreeModel::documentNameChanged); connect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateFileTreeModel::documentNameChanged); connect(doc, &KTextEditor::Document::modifiedChanged, this, &KateFileTreeModel::documentModifiedChanged); connect(doc, SIGNAL(modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(documentModifiedOnDisc(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } QModelIndex KateFileTreeModel::docIndex(const KTextEditor::Document *doc) const { if (!m_docmap.contains(doc)) { return QModelIndex(); } ProxyItem *item = m_docmap[doc]; return createIndex(item->row(), 0, item); } Qt::ItemFlags KateFileTreeModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = Qt::ItemIsEnabled; if (!index.isValid()) { return nullptr; } const ProxyItem *item = static_cast(index.internalPointer()); if (item) { if (!item->childCount()) { flags |= Qt::ItemIsSelectable; } if (item->doc() && item->doc()->url().isValid()) { flags |= Qt::ItemIsDragEnabled; } } return flags; } Q_DECLARE_METATYPE(QList) QVariant KateFileTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } ProxyItem *item = static_cast(index.internalPointer()); if (!item) { return QVariant(); } switch (role) { case KateFileTreeModel::PathRole: // allow to sort with hostname + path, bug 271488 return (item->doc() && !item->doc()->url().isEmpty()) ? item->doc()->url().toString() : item->path(); case KateFileTreeModel::DocumentRole: return QVariant::fromValue(item->doc()); case KateFileTreeModel::OpeningOrderRole: return item->row(); case KateFileTreeModel::DocumentTreeRole: return QVariant::fromValue(item->docTree()); case Qt::DisplayRole: // in list mode we want to use kate's fancy names. if (m_listMode) { return item->documentName(); } else { return item->display(); } case Qt::DecorationRole: return item->icon(); case Qt::ToolTipRole: { QString tooltip = item->path(); if (item->flag(ProxyItem::DeletedExternally) || item->flag(ProxyItem::ModifiedExternally)) { tooltip = i18nc("%1 is the full path", "

%1

The document has been modified by another application.

", item->path()); } return tooltip; } case Qt::ForegroundRole: { const KColorScheme colors(QPalette::Active); if (!item->flag(ProxyItem::Dir) && (!item->doc() || item->doc()->openingError())) { return colors.foreground(KColorScheme::InactiveText).color(); } } break; case Qt::BackgroundRole: // TODO: do that funky shading the file list does... if (m_shadingEnabled && m_brushes.contains(item)) { return m_brushes[item]; } break; } return QVariant(); } QMimeData *KateFileTreeModel::mimeData(const QModelIndexList &indexes) const { QList urls; for (const auto &index : indexes) { ProxyItem *item = static_cast(index.internalPointer()); if (!item || !item->doc() || !item->doc()->url().isValid()) { continue; } urls.append(item->doc()->url()); } if (urls.isEmpty()) { return nullptr; } QMimeData *mimeData = new QMimeData(); mimeData->setUrls(urls); return mimeData; } QVariant KateFileTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); Q_UNUSED(role); if (section == 0) { return QLatin1String("name"); } return QVariant(); } int KateFileTreeModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return m_root->childCount(); } const ProxyItem *item = static_cast(parent.internalPointer()); if (!item) { return 0; } return item->childCount(); } int KateFileTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } QModelIndex KateFileTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } const ProxyItem *item = static_cast(index.internalPointer()); if (!item) { return QModelIndex(); } if (!item->parent()) { return QModelIndex(); } if (item->parent() == m_root) { return QModelIndex(); } return createIndex(item->parent()->row(), 0, item->parent()); } QModelIndex KateFileTreeModel::index(int row, int column, const QModelIndex &parent) const { const ProxyItem *p = nullptr; if (column != 0) { return QModelIndex(); } if (!parent.isValid()) { p = m_root; } else { p = static_cast(parent.internalPointer()); } if (!p) { return QModelIndex(); } if (row < 0 || row >= p->childCount()) { return QModelIndex(); } return createIndex(row, 0, p->child(row)); } bool KateFileTreeModel::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) { return m_root->childCount() > 0; } const ProxyItem *item = static_cast(parent.internalPointer()); if (!item) { return false; } return item->childCount() > 0; } bool KateFileTreeModel::isDir(const QModelIndex &index) const { if (!index.isValid()) { return true; } const ProxyItem *item = static_cast(index.internalPointer()); if (!item) { return false; } return item->flag(ProxyItem::Dir); } bool KateFileTreeModel::listMode() const { return m_listMode; } void KateFileTreeModel::setListMode(bool lm) { if (lm != m_listMode) { m_listMode = lm; clearModel(); initModel(); } } void KateFileTreeModel::documentOpened(KTextEditor::Document *doc) { ProxyItem *item = new ProxyItem(QString()); item->setDoc(doc); updateItemPathAndHost(item); setupIcon(item); handleInsert(item); m_docmap[doc] = item; connectDocument(doc); } void KateFileTreeModel::documentsOpened(const QList &docs) { - foreach (KTextEditor::Document *doc, docs) { + for (KTextEditor::Document *doc : docs) { if (m_docmap.contains(doc)) { documentNameChanged(doc); } else { documentOpened(doc); } } } void KateFileTreeModel::documentModifiedChanged(KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; if (doc->isModified()) { item->setFlag(ProxyItem::Modified); } else { item->clearFlag(ProxyItem::Modified); item->clearFlag(ProxyItem::ModifiedExternally); item->clearFlag(ProxyItem::DeletedExternally); } setupIcon(item); const QModelIndex idx = createIndex(item->row(), 0, item); emit dataChanged(idx, idx); } void KateFileTreeModel::documentModifiedOnDisc(KTextEditor::Document *doc, bool modified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { Q_UNUSED(modified); if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; // This didn't do what I thought it did, on an ignore // we'd get !modified causing the warning icons to disappear if (!modified) { item->clearFlag(ProxyItem::ModifiedExternally); item->clearFlag(ProxyItem::DeletedExternally); } else { if (reason == KTextEditor::ModificationInterface::OnDiskDeleted) { item->setFlag(ProxyItem::DeletedExternally); } else if (reason == KTextEditor::ModificationInterface::OnDiskModified) { item->setFlag(ProxyItem::ModifiedExternally); } else if (reason == KTextEditor::ModificationInterface::OnDiskCreated) { // with out this, on "reload" we don't get the icons removed :( item->clearFlag(ProxyItem::ModifiedExternally); item->clearFlag(ProxyItem::DeletedExternally); } } setupIcon(item); const QModelIndex idx = createIndex(item->row(), 0, item); emit dataChanged(idx, idx); } void KateFileTreeModel::documentActivated(const KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; m_viewHistory.removeAll(item); m_viewHistory.prepend(item); while (m_viewHistory.count() > 10) { m_viewHistory.removeLast(); } updateBackgrounds(); } void KateFileTreeModel::documentEdited(const KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; m_editHistory.removeAll(item); m_editHistory.prepend(item); while (m_editHistory.count() > 10) { m_editHistory.removeLast(); } updateBackgrounds(); } void KateFileTreeModel::slotAboutToDeleteDocuments(const QList &docs) { - foreach (const KTextEditor::Document *doc, docs) { + for (const KTextEditor::Document *doc : docs) { disconnect(doc, &KTextEditor::Document::documentNameChanged, this, &KateFileTreeModel::documentNameChanged); disconnect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateFileTreeModel::documentNameChanged); disconnect(doc, &KTextEditor::Document::modifiedChanged, this, &KateFileTreeModel::documentModifiedChanged); disconnect(doc, SIGNAL(modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(documentModifiedOnDisc(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } } void KateFileTreeModel::slotDocumentsDeleted(const QList &docs) { - foreach (const KTextEditor::Document *doc, docs) { + for (const KTextEditor::Document *doc : docs) { connectDocument(doc); } } class EditViewCount { public: EditViewCount() = default; int edit = 0; int view = 0; }; void KateFileTreeModel::updateBackgrounds(bool force) { if (!m_shadingEnabled && !force) { return; } QMap helper; int i = 1; - foreach (ProxyItem *item, m_viewHistory) { + for (ProxyItem *item : qAsConst(m_viewHistory)) { helper[item].view = i; i++; } i = 1; - foreach (ProxyItem *item, m_editHistory) { + for (ProxyItem *item : qAsConst(m_editHistory)) { helper[item].edit = i; i++; } QMap oldBrushes = m_brushes; m_brushes.clear(); const int hc = m_viewHistory.count(); const int ec = m_editHistory.count(); for (QMap::iterator it = helper.begin(); it != helper.end(); ++it) { QColor shade(m_viewShade); QColor eshade(m_editShade); if (it.value().edit > 0) { int v = hc - it.value().view; int e = ec - it.value().edit + 1; e = e * e; const int n = qMax(v + e, 1); shade.setRgb(((shade.red() * v) + (eshade.red() * e)) / n, ((shade.green() * v) + (eshade.green() * e)) / n, ((shade.blue() * v) + (eshade.blue() * e)) / n); } // blend in the shade color; latest is most colored. const double t = double(hc - it.value().view + 1) / double(hc); m_brushes[it.key()] = QBrush(KColorUtils::mix(QPalette().color(QPalette::Base), shade, t)); } for (auto it = m_brushes.constBegin(), end = m_brushes.constEnd(); it != end; ++it) { ProxyItem *item = it.key(); oldBrushes.remove(item); const QModelIndex idx = createIndex(item->row(), 0, item); dataChanged(idx, idx); } for (auto it = oldBrushes.constBegin(), end = oldBrushes.constEnd(); it != end; ++it) { ProxyItem *item = it.key(); const QModelIndex idx = createIndex(item->row(), 0, item); dataChanged(idx, idx); } } void KateFileTreeModel::handleEmptyParents(ProxyItemDir *item) { Q_ASSERT(item != nullptr); if (!item->parent()) { return; } ProxyItemDir *parent = item->parent(); while (parent) { if (!item->childCount()) { const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent); beginRemoveRows(parent_index, item->row(), item->row()); parent->remChild(item); endRemoveRows(); delete item; } else { // breakout early, if this node isn't empty, theres no use in checking its parents return; } item = parent; parent = item->parent(); } } void KateFileTreeModel::documentClosed(KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } if (m_shadingEnabled) { ProxyItem *toRemove = m_docmap[doc]; if (m_brushes.contains(toRemove)) { m_brushes.remove(toRemove); } if (m_viewHistory.contains(toRemove)) { m_viewHistory.removeAll(toRemove); } if (m_editHistory.contains(toRemove)) { m_editHistory.removeAll(toRemove); } } ProxyItem *node = m_docmap[doc]; ProxyItemDir *parent = node->parent(); const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent); beginRemoveRows(parent_index, node->row(), node->row()); node->parent()->remChild(node); endRemoveRows(); delete node; handleEmptyParents(parent); m_docmap.remove(doc); } void KateFileTreeModel::documentNameChanged(KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; if (m_shadingEnabled) { ProxyItem *toRemove = m_docmap[doc]; if (m_brushes.contains(toRemove)) { QBrush brush = m_brushes[toRemove]; m_brushes.remove(toRemove); m_brushes.insert(item, brush); } if (m_viewHistory.contains(toRemove)) { const int idx = m_viewHistory.indexOf(toRemove); if (idx != -1) { m_viewHistory.replace(idx, item); } } if (m_editHistory.contains(toRemove)) { const int idx = m_editHistory.indexOf(toRemove); if (idx != -1) { m_editHistory.replace(idx, item); } } } handleNameChange(item); emit triggerViewChangeAfterNameChange(); // FIXME: heh, non-standard signal? } ProxyItemDir *KateFileTreeModel::findRootNode(const QString &name, const int r) const { - foreach (ProxyItem *item, m_root->children()) { + const auto rootChildren = m_root->children(); + for (ProxyItem *item : rootChildren) { if (!item->flag(ProxyItem::Dir)) { continue; } // make sure we're actually matching against the right dir, // previously the check below would match /foo/xy against /foo/x // and return /foo/x rather than /foo/xy // this seems a bit hackish, but is the simplest way to solve the // current issue. QString path = item->path().section(QLatin1Char('/'), 0, -r) + QLatin1Char('/'); if (name.startsWith(path)) { return static_cast(item); } } return nullptr; } ProxyItemDir *KateFileTreeModel::findChildNode(const ProxyItemDir *parent, const QString &name) const { Q_ASSERT(parent != nullptr); Q_ASSERT(!name.isEmpty()); if (!parent->childCount()) { return nullptr; } foreach (ProxyItem *item, parent->children()) { if (!item->flag(ProxyItem::Dir)) { continue; } if (item->display() == name) { return static_cast(item); } } return nullptr; } void KateFileTreeModel::insertItemInto(ProxyItemDir *root, ProxyItem *item) { Q_ASSERT(root != nullptr); Q_ASSERT(item != nullptr); QString tail = item->path(); tail.remove(0, root->path().length()); QStringList parts = tail.split(QLatin1Char('/'), QString::SkipEmptyParts); ProxyItemDir *ptr = root; QStringList current_parts; current_parts.append(root->path()); // seems this can be empty, see bug 286191 if (!parts.isEmpty()) { parts.pop_back(); } - foreach (const QString &part, parts) { + for (const QString &part : qAsConst(parts)) { current_parts.append(part); ProxyItemDir *find = findChildNode(ptr, part); if (!find) { const QString new_name = current_parts.join(QLatin1Char('/')); const QModelIndex parent_index = (ptr == m_root) ? QModelIndex() : createIndex(ptr->row(), 0, ptr); beginInsertRows(parent_index, ptr->childCount(), ptr->childCount()); ptr = new ProxyItemDir(new_name, ptr); endInsertRows(); } else { ptr = find; } } const QModelIndex parent_index = (ptr == m_root) ? QModelIndex() : createIndex(ptr->row(), 0, ptr); beginInsertRows(parent_index, ptr->childCount(), ptr->childCount()); ptr->addChild(item); endInsertRows(); } void KateFileTreeModel::handleInsert(ProxyItem *item) { Q_ASSERT(item != nullptr); if (m_listMode || item->flag(ProxyItem::Empty)) { beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(item); endInsertRows(); return; } // case (item.path > root.path) ProxyItemDir *root = findRootNode(item->path()); if (root) { insertItemInto(root, item); return; } // trim off trailing file and dir QString base = item->path().section(QLatin1Char('/'), 0, -2); // create new root ProxyItemDir *new_root = new ProxyItemDir(base); new_root->setHost(item->host()); // add new root to m_root beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(new_root); endInsertRows(); // same fix as in findRootNode, try to match a full dir, instead of a partial path base += QLatin1Char('/'); // try and merge existing roots with the new root node (new_root.path < root.path) foreach (ProxyItem *root, m_root->children()) { if (root == new_root || !root->flag(ProxyItem::Dir)) { continue; } if (root->path().startsWith(base)) { beginRemoveRows(QModelIndex(), root->row(), root->row()); m_root->remChild(root); endRemoveRows(); // beginInsertRows(new_root_index, new_root->childCount(), new_root->childCount()); // this can't use new_root->addChild directly, or it'll potentially miss a bunch of subdirs insertItemInto(new_root, root); // endInsertRows(); } } // add item to new root // have to call begin/endInsertRows here, or the new item won't show up. const QModelIndex new_root_index = createIndex(new_root->row(), 0, new_root); beginInsertRows(new_root_index, new_root->childCount(), new_root->childCount()); new_root->addChild(item); endInsertRows(); handleDuplicitRootDisplay(new_root); } void KateFileTreeModel::handleDuplicitRootDisplay(ProxyItemDir *init) { QStack rootsToCheck; rootsToCheck.push(init); // make sure the roots don't match (recursively) while (!rootsToCheck.isEmpty()) { ProxyItemDir *check_root = rootsToCheck.pop(); if (check_root->parent() != m_root) { continue; } foreach (ProxyItem *root, m_root->children()) { if (root == check_root || !root->flag(ProxyItem::Dir)) { continue; } if (check_root->display() == root->display()) { bool changed = false; bool check_root_removed = false; const QString rdir = root->path().section(QLatin1Char('/'), 0, -2); if (!rdir.isEmpty()) { beginRemoveRows(QModelIndex(), root->row(), root->row()); m_root->remChild(root); endRemoveRows(); ProxyItemDir *irdir = new ProxyItemDir(rdir); beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(irdir); endInsertRows(); insertItemInto(irdir, root); foreach (ProxyItem *node, m_root->children()) { if (node == irdir || !root->flag(ProxyItem::Dir)) { continue; } const QString xy = rdir + QLatin1Char('/'); if (node->path().startsWith(xy)) { beginRemoveRows(QModelIndex(), node->row(), node->row()); // check_root_removed must be sticky check_root_removed = check_root_removed || (node == check_root); m_root->remChild(node); endRemoveRows(); insertItemInto(irdir, node); } } rootsToCheck.push(irdir); changed = true; } if (!check_root_removed) { const QString nrdir = check_root->path().section(QLatin1Char('/'), 0, -2); if (!nrdir.isEmpty()) { beginRemoveRows(QModelIndex(), check_root->row(), check_root->row()); m_root->remChild(check_root); endRemoveRows(); ProxyItemDir *irdir = new ProxyItemDir(nrdir); beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(irdir); endInsertRows(); insertItemInto(irdir, check_root); rootsToCheck.push(irdir); changed = true; } } if (changed) { break; // restart } } } // foreach root } } void KateFileTreeModel::handleNameChange(ProxyItem *item) { Q_ASSERT(item != nullptr); Q_ASSERT(item->parent()); updateItemPathAndHost(item); if (m_listMode) { const QModelIndex idx = createIndex(item->row(), 0, item); setupIcon(item); emit dataChanged(idx, idx); return; } // in either case (new/change) we want to remove the item from its parent ProxyItemDir *parent = item->parent(); const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent); beginRemoveRows(parent_index, item->row(), item->row()); parent->remChild(item); endRemoveRows(); handleEmptyParents(parent); // clear all but Empty flag if (item->flag(ProxyItem::Empty)) { item->setFlags(ProxyItem::Empty); } else { item->setFlags(ProxyItem::None); } setupIcon(item); handleInsert(item); } void KateFileTreeModel::updateItemPathAndHost(ProxyItem *item) const { const KTextEditor::Document *doc = item->doc(); Q_ASSERT(doc); // this method should not be called at directory items QString path = doc->url().path(); QString host; if (doc->url().isEmpty()) { path = doc->documentName(); item->setFlag(ProxyItem::Empty); } else { item->clearFlag(ProxyItem::Empty); host = doc->url().host(); if (!host.isEmpty()) { path = QStringLiteral("[%1]%2").arg(host, path); } } // for some reason we get useless name changes [should be fixed in 5.0] if (item->path() == path) { return; } item->setPath(path); item->setHost(host); } void KateFileTreeModel::setupIcon(ProxyItem *item) const { Q_ASSERT(item != nullptr); QString icon_name; if (item->flag(ProxyItem::Modified)) { icon_name = QStringLiteral("document-save"); } else { const QUrl url(item->path()); icon_name = QMimeDatabase().mimeTypeForFile(url.path(), QMimeDatabase::MatchExtension).iconName(); } QIcon icon = QIcon::fromTheme(icon_name); if (item->flag(ProxyItem::ModifiedExternally) || item->flag(ProxyItem::DeletedExternally)) { icon = KIconUtils::addOverlay(icon, QIcon(QLatin1String("emblem-important")), Qt::TopLeftCorner); } item->setIcon(icon); } void KateFileTreeModel::resetHistory() { QSet list = QSet::fromList(m_viewHistory); list += QSet::fromList(m_editHistory); m_viewHistory.clear(); m_editHistory.clear(); m_brushes.clear(); - foreach (ProxyItem *item, list) { + for (ProxyItem *item : qAsConst(list)) { QModelIndex idx = createIndex(item->row(), 0, item); dataChanged(idx, idx, QVector(1, Qt::BackgroundRole)); } } diff --git a/addons/filetree/katefiletreeplugin.cpp b/addons/filetree/katefiletreeplugin.cpp index 92bea3b61..335ddf560 100644 --- a/addons/filetree/katefiletreeplugin.cpp +++ b/addons/filetree/katefiletreeplugin.cpp @@ -1,450 +1,450 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // BEGIN Includes #include "katefiletreeplugin.h" #include "katefiletree.h" #include "katefiletreemodel.h" #include "katefiletreeproxymodel.h" #include "katefiletreeconfigpage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katefiletreedebug.h" // END Includes K_PLUGIN_FACTORY_WITH_JSON(KateFileTreeFactory, "katefiletreeplugin.json", registerPlugin();) Q_LOGGING_CATEGORY(FILETREE, "kate-filetree", QtWarningMsg) // BEGIN KateFileTreePlugin KateFileTreePlugin::KateFileTreePlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { } KateFileTreePlugin::~KateFileTreePlugin() { m_settings.save(); } QObject *KateFileTreePlugin::createView(KTextEditor::MainWindow *mainWindow) { KateFileTreePluginView *view = new KateFileTreePluginView(mainWindow, this); connect(view, &KateFileTreePluginView::destroyed, this, &KateFileTreePlugin::viewDestroyed); m_views.append(view); return view; } void KateFileTreePlugin::viewDestroyed(QObject *view) { // do not access the view pointer, since it is partially destroyed already m_views.removeAll(static_cast(view)); } int KateFileTreePlugin::configPages() const { return 1; } KTextEditor::ConfigPage *KateFileTreePlugin::configPage(int number, QWidget *parent) { if (number != 0) { return nullptr; } KateFileTreeConfigPage *page = new KateFileTreeConfigPage(parent, this); return page; } const KateFileTreePluginSettings &KateFileTreePlugin::settings() { return m_settings; } void KateFileTreePlugin::applyConfig(bool shadingEnabled, const QColor &viewShade, const QColor &editShade, bool listMode, int sortRole, bool showFullPath) { // save to settings m_settings.setShadingEnabled(shadingEnabled); m_settings.setViewShade(viewShade); m_settings.setEditShade(editShade); m_settings.setListMode(listMode); m_settings.setSortRole(sortRole); m_settings.setShowFullPathOnRoots(showFullPath); m_settings.save(); // update views - foreach (KateFileTreePluginView *view, m_views) { + for (KateFileTreePluginView *view : qAsConst(m_views)) { view->setHasLocalPrefs(false); view->model()->setShadingEnabled(shadingEnabled); view->model()->setViewShade(viewShade); view->model()->setEditShade(editShade); view->setListMode(listMode); view->proxy()->setSortRole(sortRole); view->model()->setShowFullPathOnRoots(showFullPath); } } // END KateFileTreePlugin // BEGIN KateFileTreePluginView KateFileTreePluginView::KateFileTreePluginView(KTextEditor::MainWindow *mainWindow, KateFileTreePlugin *plug) : QObject(mainWindow) , m_loadingDocuments(false) , m_plug(plug) , m_mainWindow(mainWindow) { KXMLGUIClient::setComponentName(QStringLiteral("katefiletree"), i18n("Kate File Tree")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = mainWindow->createToolView(plug, QStringLiteral("kate_private_plugin_katefiletreeplugin"), KTextEditor::MainWindow::Left, QIcon::fromTheme(QStringLiteral("document-open")), i18n("Documents")); Q_ASSERT(m_toolView->layout()); m_toolView->layout()->setContentsMargins(0, 0, 0, 0); m_toolView->layout()->setSpacing(0); auto mainLayout = m_toolView->layout(); // create toolbar m_toolbar = new KToolBar(m_toolView); m_toolbar->setMovable(false); m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); m_toolbar->setContextMenuPolicy(Qt::NoContextMenu); mainLayout->addWidget(m_toolbar); // create filetree m_fileTree = new KateFileTree(m_toolView); m_fileTree->setSortingEnabled(true); mainLayout->addWidget(m_fileTree); connect(m_fileTree, &KateFileTree::activateDocument, this, &KateFileTreePluginView::activateDocument); connect(m_fileTree, &KateFileTree::viewModeChanged, this, &KateFileTreePluginView::viewModeChanged); connect(m_fileTree, &KateFileTree::sortRoleChanged, this, &KateFileTreePluginView::sortRoleChanged); m_documentModel = new KateFileTreeModel(this); m_proxyModel = new KateFileTreeProxyModel(this); m_proxyModel->setSourceModel(m_documentModel); m_proxyModel->setDynamicSortFilter(true); m_documentModel->setShowFullPathOnRoots(m_plug->settings().showFullPathOnRoots()); m_documentModel->setShadingEnabled(m_plug->settings().shadingEnabled()); m_documentModel->setViewShade(m_plug->settings().viewShade()); m_documentModel->setEditShade(m_plug->settings().editShade()); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentWillBeDeleted, m_documentModel, &KateFileTreeModel::documentClosed); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentCreated, this, &KateFileTreePluginView::documentOpened); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentWillBeDeleted, this, &KateFileTreePluginView::documentClosed); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::aboutToCreateDocuments, this, &KateFileTreePluginView::slotAboutToCreateDocuments); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentsCreated, this, &KateFileTreePluginView::slotDocumentsCreated); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::aboutToDeleteDocuments, m_documentModel, &KateFileTreeModel::slotAboutToDeleteDocuments); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentsDeleted, m_documentModel, &KateFileTreeModel::slotDocumentsDeleted); connect(m_documentModel, &KateFileTreeModel::triggerViewChangeAfterNameChange, [=] { KateFileTreePluginView::viewChanged(); }); m_fileTree->setModel(m_proxyModel); m_fileTree->setDragEnabled(false); m_fileTree->setDragDropMode(QAbstractItemView::InternalMove); m_fileTree->setDropIndicatorShown(false); m_fileTree->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_fileTree->selectionModel(), &QItemSelectionModel::currentChanged, m_fileTree, &KateFileTree::slotCurrentChanged); connect(mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateFileTreePluginView::viewChanged); // // actions // setupActions(); mainWindow->guiFactory()->addClient(this); m_proxyModel->setSortRole(Qt::DisplayRole); m_proxyModel->sort(0, Qt::AscendingOrder); m_proxyModel->invalidate(); } KateFileTreePluginView::~KateFileTreePluginView() { m_mainWindow->guiFactory()->removeClient(this); // clean up tree and toolview delete m_fileTree->parentWidget(); // delete m_toolView; // and TreeModel delete m_documentModel; } void KateFileTreePluginView::setupActions() { auto aPrev = actionCollection()->addAction(QStringLiteral("filetree_prev_document")); aPrev->setText(i18n("Previous Document")); aPrev->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); actionCollection()->setDefaultShortcut(aPrev, Qt::ALT + Qt::Key_Up); connect(aPrev, &QAction::triggered, m_fileTree, &KateFileTree::slotDocumentPrev); auto aNext = actionCollection()->addAction(QStringLiteral("filetree_next_document")); aNext->setText(i18n("Next Document")); aNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); actionCollection()->setDefaultShortcut(aNext, Qt::ALT + Qt::Key_Down); connect(aNext, &QAction::triggered, m_fileTree, &KateFileTree::slotDocumentNext); auto aShowActive = actionCollection()->addAction(QStringLiteral("filetree_show_active_document")); aShowActive->setText(i18n("&Show Active")); aShowActive->setIcon(QIcon::fromTheme(QStringLiteral("folder-sync"))); connect(aShowActive, &QAction::triggered, this, &KateFileTreePluginView::showActiveDocument); auto aSave = actionCollection()->addAction(QStringLiteral("filetree_save"), this, SLOT(slotDocumentSave())); aSave->setText(i18n("Save Current Document")); aSave->setToolTip(i18n("Save the current document")); aSave->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); auto aSaveAs = actionCollection()->addAction(QStringLiteral("filetree_save_as"), this, SLOT(slotDocumentSaveAs())); aSaveAs->setText(i18n("Save Current Document As")); aSaveAs->setToolTip(i18n("Save current document under new name")); aSaveAs->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); /** * add new & open, if hosting application has it */ if (KXmlGuiWindow *parentClient = qobject_cast(m_mainWindow->window())) { bool newOrOpen = false; if (auto a = parentClient->action("file_new")) { m_toolbar->addAction(a); newOrOpen = true; } if (auto a = parentClient->action("file_open")) { m_toolbar->addAction(a); newOrOpen = true; } if (newOrOpen) { m_toolbar->addSeparator(); } } /** * add own actions */ m_toolbar->addAction(aPrev); m_toolbar->addAction(aNext); m_toolbar->addSeparator(); m_toolbar->addAction(aSave); m_toolbar->addAction(aSaveAs); } KateFileTreeModel *KateFileTreePluginView::model() { return m_documentModel; } KateFileTreeProxyModel *KateFileTreePluginView::proxy() { return m_proxyModel; } KateFileTree *KateFileTreePluginView::tree() { return m_fileTree; } void KateFileTreePluginView::documentOpened(KTextEditor::Document *doc) { if (m_loadingDocuments) { return; } m_documentModel->documentOpened(doc); m_proxyModel->invalidate(); } void KateFileTreePluginView::documentClosed(KTextEditor::Document *doc) { Q_UNUSED(doc); m_proxyModel->invalidate(); } void KateFileTreePluginView::viewChanged(KTextEditor::View *) { KTextEditor::View *view = m_mainWindow->activeView(); if (!view) { return; } KTextEditor::Document *doc = view->document(); QModelIndex index = m_proxyModel->docIndex(doc); QString display = m_proxyModel->data(index, Qt::DisplayRole).toString(); // update the model on which doc is active m_documentModel->documentActivated(doc); m_fileTree->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); m_fileTree->scrollTo(index); while (index != QModelIndex()) { m_fileTree->expand(index); index = index.parent(); } } void KateFileTreePluginView::setListMode(bool listMode) { if (listMode) { m_documentModel->setListMode(true); m_fileTree->setRootIsDecorated(false); } else { m_documentModel->setListMode(false); m_fileTree->setRootIsDecorated(true); } m_proxyModel->sort(0, Qt::AscendingOrder); m_proxyModel->invalidate(); } void KateFileTreePluginView::viewModeChanged(bool listMode) { setHasLocalPrefs(true); setListMode(listMode); } void KateFileTreePluginView::sortRoleChanged(int role) { setHasLocalPrefs(true); m_proxyModel->setSortRole(role); m_proxyModel->invalidate(); } void KateFileTreePluginView::activateDocument(KTextEditor::Document *doc) { m_mainWindow->activateView(doc); } void KateFileTreePluginView::showToolView() { m_mainWindow->showToolView(m_toolView); m_toolView->setFocus(); } void KateFileTreePluginView::hideToolView() { m_mainWindow->hideToolView(m_toolView); } void KateFileTreePluginView::showActiveDocument() { // hack? viewChanged(); // make the tool view show if it was hidden showToolView(); } bool KateFileTreePluginView::hasLocalPrefs() { return m_hasLocalPrefs; } void KateFileTreePluginView::setHasLocalPrefs(bool h) { m_hasLocalPrefs = h; } void KateFileTreePluginView::readSessionConfig(const KConfigGroup &g) { if (g.exists()) { m_hasLocalPrefs = true; } else { m_hasLocalPrefs = false; } // we chain to the global settings by using them as the defaults // here in the session view config loading. const KateFileTreePluginSettings &defaults = m_plug->settings(); bool listMode = g.readEntry("listMode", defaults.listMode()); setListMode(listMode); int sortRole = g.readEntry("sortRole", defaults.sortRole()); m_proxyModel->setSortRole(sortRole); } void KateFileTreePluginView::writeSessionConfig(KConfigGroup &g) { if (m_hasLocalPrefs) { g.writeEntry("listMode", QVariant(m_documentModel->listMode())); g.writeEntry("sortRole", int(m_proxyModel->sortRole())); } else { g.deleteEntry("listMode"); g.deleteEntry("sortRole"); } g.sync(); } void KateFileTreePluginView::slotAboutToCreateDocuments() { m_loadingDocuments = true; } void KateFileTreePluginView::slotDocumentsCreated(const QList &docs) { m_documentModel->documentsOpened(docs); m_loadingDocuments = false; viewChanged(); } void KateFileTreePluginView::slotDocumentSave() { if (auto view = m_mainWindow->activeView()) { view->document()->documentSave(); } } void KateFileTreePluginView::slotDocumentSaveAs() { if (auto view = m_mainWindow->activeView()) { view->document()->documentSaveAs(); } } // END KateFileTreePluginView #include "katefiletreeplugin.moc" diff --git a/addons/gdbplugin/debugview.cpp b/addons/gdbplugin/debugview.cpp index 6650b7678..311877587 100644 --- a/addons/gdbplugin/debugview.cpp +++ b/addons/gdbplugin/debugview.cpp @@ -1,600 +1,600 @@ // // debugview.cpp // // Description: Manages the interaction with GDB // // // Copyright (c) 2008-2010 Ian Wakeling // Copyright (c) 2011 Kåre Särs // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License version 2 as published by the Free Software Foundation. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public License // along with this library; see the file COPYING.LIB. If not, write to // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, // Boston, MA 02110-1301, USA. #include "debugview.h" #include #include #include #include #include #include #include #include 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 (const auto &breakpoint : qAsConst(m_breakPointList)) { if ((url == breakpoint.file) && (line == breakpoint.line)) { return true; } } return false; } void DebugView::toggleBreakpoint(QUrl const &url, int line) { if (m_state == ready) { QString cmd; if (hasBreakpoint(url, line)) { cmd = QStringLiteral("clear %1:%2").arg(url.path()).arg(line); } else { cmd = QStringLiteral("break %1:%2").arg(url.path()).arg(line); } issueCommand(cmd); } } void DebugView::slotError() { KMessageBox::sorry(nullptr, i18n("Could not start debugger process")); } void DebugView::slotReadDebugStdOut() { m_outBuffer += QString::fromLocal8Bit(m_debugProcess.readAllStandardOutput().data()); int end = 0; // handle one line at a time do { end = m_outBuffer.indexOf(QLatin1Char('\n')); if (end < 0) break; processLine(m_outBuffer.mid(0, end)); m_outBuffer.remove(0, end + 1); } while (1); if (m_outBuffer == QLatin1String("(gdb) ") || m_outBuffer == QLatin1String(">")) { m_outBuffer.clear(); processLine(PromptStr); } } void DebugView::slotReadDebugStdErr() { m_errBuffer += QString::fromLocal8Bit(m_debugProcess.readAllStandardError().data()); int end = 0; // add whole lines at a time to the error list do { end = m_errBuffer.indexOf(QLatin1Char('\n')); if (end < 0) break; m_errorList << m_errBuffer.mid(0, end); m_errBuffer.remove(0, end + 1); } while (1); processErrors(); } void DebugView::slotDebugFinished(int /*exitCode*/, QProcess::ExitStatus status) { if (status != QProcess::NormalExit) { emit outputText(i18n("*** gdb exited normally ***") + QLatin1Char('\n')); } m_state = none; emit readyForInput(false); // remove all old breakpoints BreakPoint bPoint; while (!m_breakPointList.empty()) { bPoint = m_breakPointList.takeFirst(); emit breakPointCleared(bPoint.file, bPoint.line - 1); } emit gdbEnded(); } void DebugView::movePC(QUrl const &url, int line) { if (m_state == ready) { QString cmd = QStringLiteral("tbreak %1:%2").arg(url.path()).arg(line); m_nextCommands << QStringLiteral("jump %1:%2").arg(url.path()).arg(line); issueCommand(cmd); } } void DebugView::runToCursor(QUrl const &url, int line) { if (m_state == ready) { QString cmd = QStringLiteral("tbreak %1:%2").arg(url.path()).arg(line); m_nextCommands << QStringLiteral("continue"); issueCommand(cmd); } } void DebugView::slotInterrupt() { if (m_state == executingCmd) { m_debugLocationChanged = true; } int pid = m_debugProcess.pid(); if (pid != 0) { ::kill(pid, SIGINT); } } void DebugView::slotKill() { if (m_state != ready) { slotInterrupt(); m_state = ready; } issueCommand(QStringLiteral("kill")); } void DebugView::slotReRun() { slotKill(); m_nextCommands << QStringLiteral("file %1").arg(m_targetConf.executable); m_nextCommands << QStringLiteral("set args %1 %2").arg(m_targetConf.arguments).arg(m_ioPipeString); m_nextCommands << QStringLiteral("set inferior-tty /dev/null"); m_nextCommands << m_targetConf.customInit; m_nextCommands << QStringLiteral("(Q) info breakpoints"); m_nextCommands << QStringLiteral("tbreak main"); m_nextCommands << QStringLiteral("run"); m_nextCommands << QStringLiteral("p setvbuf(stdout, 0, %1, 1024)").arg(_IOLBF); m_nextCommands << QStringLiteral("continue"); } void DebugView::slotStepInto() { issueCommand(QStringLiteral("step")); } void DebugView::slotStepOver() { issueCommand(QStringLiteral("next")); } void DebugView::slotStepOut() { issueCommand(QStringLiteral("finish")); } void DebugView::slotContinue() { issueCommand(QStringLiteral("continue")); } static QRegExp breakpointList(QStringLiteral("Num\\s+Type\\s+Disp\\s+Enb\\s+Address\\s+What.*")); static QRegExp breakpointListed(QStringLiteral("(\\d)\\s+breakpoint\\s+keep\\sy\\s+0x[\\da-f]+\\sin\\s.+\\sat\\s([^:]+):(\\d+).*")); static QRegExp stackFrameAny(QStringLiteral("#(\\d+)\\s(.*)")); static QRegExp stackFrameFile(QStringLiteral("#(\\d+)\\s+(?:0x[\\da-f]+\\s*in\\s)*(\\S+)(\\s\\(.*\\)) at ([^:]+):(\\d+).*")); static QRegExp changeFile(QStringLiteral("(?:(?:Temporary\\sbreakpoint|Breakpoint)\\s*\\d+,\\s*|0x[\\da-f]+\\s*in\\s*)?[^\\s]+\\s*\\([^)]*\\)\\s*at\\s*([^:]+):(\\d+).*")); static QRegExp changeLine(QStringLiteral("(\\d+)\\s+.*")); static QRegExp breakPointReg(QStringLiteral("Breakpoint\\s+(\\d+)\\s+at\\s+0x[\\da-f]+:\\s+file\\s+([^\\,]+)\\,\\s+line\\s+(\\d+).*")); static QRegExp breakPointMultiReg(QStringLiteral("Breakpoint\\s+(\\d+)\\s+at\\s+0x[\\da-f]+:\\s+([^\\,]+):(\\d+).*")); static QRegExp breakPointDel(QStringLiteral("Deleted\\s+breakpoint.*")); static QRegExp exitProgram(QStringLiteral("(?:Program|.*Inferior.*)\\s+exited.*")); static QRegExp threadLine(QStringLiteral("\\**\\s+(\\d+)\\s+Thread.*")); void DebugView::processLine(QString line) { if (line.isEmpty()) return; switch (m_state) { case none: case ready: if (PromptStr == line) { // we get here after initialization QTimer::singleShot(0, this, &DebugView::issueNextCommand); } break; case executingCmd: if (breakpointList.exactMatch(line)) { m_state = listingBreakpoints; emit clearBreakpointMarks(); m_breakPointList.clear(); } else if (line.contains(QLatin1String("No breakpoints or watchpoints."))) { emit clearBreakpointMarks(); m_breakPointList.clear(); } else if (stackFrameAny.exactMatch(line)) { if (m_lastCommand.contains(QLatin1String("info stack"))) { emit stackFrameInfo(stackFrameAny.cap(1), stackFrameAny.cap(2)); } else { m_subState = (m_subState == normal) ? stackFrameSeen : stackTraceSeen; m_newFrameLevel = stackFrameAny.cap(1).toInt(); if (stackFrameFile.exactMatch(line)) { m_newFrameFile = stackFrameFile.cap(4); } } } else if (changeFile.exactMatch(line)) { m_currentFile = changeFile.cap(1).trimmed(); int lineNum = changeFile.cap(2).toInt(); if (!m_nextCommands.contains(QLatin1String("continue"))) { // GDB uses 1 based line numbers, kate uses 0 based... emit debugLocationChanged(resolveFileName(m_currentFile), lineNum - 1); } m_debugLocationChanged = true; } else if (changeLine.exactMatch(line)) { int lineNum = changeLine.cap(1).toInt(); if (m_subState == stackFrameSeen) { m_currentFile = m_newFrameFile; } if (!m_nextCommands.contains(QLatin1String("continue"))) { // GDB uses 1 based line numbers, kate uses 0 based... emit debugLocationChanged(resolveFileName(m_currentFile), lineNum - 1); } m_debugLocationChanged = true; } else if (breakPointReg.exactMatch(line)) { BreakPoint breakPoint; breakPoint.number = breakPointReg.cap(1).toInt(); breakPoint.file = resolveFileName(breakPointReg.cap(2)); breakPoint.line = breakPointReg.cap(3).toInt(); m_breakPointList << breakPoint; emit breakPointSet(breakPoint.file, breakPoint.line - 1); } else if (breakPointMultiReg.exactMatch(line)) { BreakPoint breakPoint; breakPoint.number = breakPointMultiReg.cap(1).toInt(); breakPoint.file = resolveFileName(breakPointMultiReg.cap(2)); breakPoint.line = breakPointMultiReg.cap(3).toInt(); m_breakPointList << breakPoint; emit breakPointSet(breakPoint.file, breakPoint.line - 1); } else if (breakPointDel.exactMatch(line)) { line.remove(QStringLiteral("Deleted breakpoint")); line.remove(QLatin1Char('s')); // in case of multiple breakpoints QStringList numbers = line.split(QLatin1Char(' '), QString::SkipEmptyParts); for (int i = 0; i < numbers.size(); i++) { for (int j = 0; j < m_breakPointList.size(); j++) { if (numbers[i].toInt() == m_breakPointList[j].number) { emit breakPointCleared(m_breakPointList[j].file, m_breakPointList[j].line - 1); m_breakPointList.removeAt(j); break; } } } } else if (exitProgram.exactMatch(line) || line.contains(QLatin1String("The program no longer exists")) || line.contains(QLatin1String("Kill the program being debugged"))) { // if there are still commands to execute remove them to remove unneeded output // except if the "kill was for "re-run" if ((!m_nextCommands.empty()) && !m_nextCommands[0].contains(QLatin1String("file"))) { m_nextCommands.clear(); } m_debugLocationChanged = false; // do not insert (Q) commands emit programEnded(); } else if (PromptStr == line) { if (m_subState == stackFrameSeen) { emit stackFrameChanged(m_newFrameLevel); } m_state = ready; // Give the error a possibility get noticed since stderr and stdout are not in sync QTimer::singleShot(0, this, &DebugView::issueNextCommand); } break; case listingBreakpoints: if (breakpointListed.exactMatch(line)) { BreakPoint breakPoint; breakPoint.number = breakpointListed.cap(1).toInt(); breakPoint.file = resolveFileName(breakpointListed.cap(2)); breakPoint.line = breakpointListed.cap(3).toInt(); m_breakPointList << breakPoint; emit breakPointSet(breakPoint.file, breakPoint.line - 1); } else if (PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } break; case infoArgs: if (PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else { emit infoLocal(line); } break; case printThis: if (PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else { emit infoLocal(line); } break; case infoLocals: if (PromptStr == line) { m_state = ready; emit infoLocal(QString()); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else { emit infoLocal(line); } break; case infoStack: if (PromptStr == line) { m_state = ready; emit stackFrameInfo(QString(), QString()); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if (stackFrameAny.exactMatch(line)) { emit stackFrameInfo(stackFrameAny.cap(1), stackFrameAny.cap(2)); } break; case infoThreads: if (PromptStr == line) { m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if (threadLine.exactMatch(line)) { emit threadInfo(threadLine.cap(1).toInt(), (line[0] == QLatin1Char('*'))); } break; } outputTextMaybe(line); } void DebugView::processErrors() { QString error; while (!m_errorList.empty()) { error = m_errorList.takeFirst(); // qDebug() << error; if (error == QLatin1String("The program is not being run.")) { if (m_lastCommand == QLatin1String("continue")) { m_nextCommands.clear(); m_nextCommands << QStringLiteral("tbreak main"); m_nextCommands << QStringLiteral("run"); m_nextCommands << QStringLiteral("p setvbuf(stdout, 0, %1, 1024)").arg(_IOLBF); m_nextCommands << QStringLiteral("continue"); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if ((m_lastCommand == QLatin1String("step")) || (m_lastCommand == QLatin1String("next")) || (m_lastCommand == QLatin1String("finish"))) { m_nextCommands.clear(); m_nextCommands << QStringLiteral("tbreak main"); m_nextCommands << QStringLiteral("run"); m_nextCommands << QStringLiteral("p setvbuf(stdout, 0, %1, 1024)").arg(_IOLBF); QTimer::singleShot(0, this, &DebugView::issueNextCommand); } else if ((m_lastCommand == QLatin1String("kill"))) { if (!m_nextCommands.empty()) { if (!m_nextCommands[0].contains(QLatin1String("file"))) { m_nextCommands.clear(); m_nextCommands << QStringLiteral("quit"); } // else continue with "ReRun" } else { m_nextCommands << QStringLiteral("quit"); } m_state = ready; QTimer::singleShot(0, this, &DebugView::issueNextCommand); } // else do nothing } else if (error.contains(QLatin1String("No line ")) || error.contains(QLatin1String("No source file named"))) { // setting a breakpoint failed. Do not continue. m_nextCommands.clear(); emit readyForInput(true); } else if (error.contains(QLatin1String("No stack"))) { m_nextCommands.clear(); emit programEnded(); } if ((m_lastCommand == QLatin1String("(Q)print *this")) && error.contains(QLatin1String("No symbol \"this\" in current context."))) { continue; } emit outputError(error + QLatin1Char('\n')); } } void DebugView::issueCommand(QString const &cmd) { if (m_state == ready) { emit readyForInput(false); m_state = executingCmd; if (cmd == QLatin1String("(Q)info locals")) { m_state = infoLocals; } else if (cmd == QLatin1String("(Q)info args")) { m_state = infoArgs; } else if (cmd == QLatin1String("(Q)print *this")) { m_state = printThis; } else if (cmd == QLatin1String("(Q)info stack")) { m_state = infoStack; } else if (cmd == QLatin1String("(Q)info thread")) { emit threadInfo(-1, false); m_state = infoThreads; } m_subState = normal; m_lastCommand = cmd; if (cmd.startsWith(QLatin1String("(Q)"))) { m_debugProcess.write(qPrintable(cmd.mid(3))); } else { emit outputText(QStringLiteral("(gdb) ") + cmd + QLatin1Char('\n')); m_debugProcess.write(qPrintable(cmd)); } m_debugProcess.write("\n"); } } void DebugView::issueNextCommand() { if (m_state == ready) { if (!m_nextCommands.empty()) { QString cmd = m_nextCommands.takeFirst(); // qDebug() << "Next command" << cmd; issueCommand(cmd); } else { // FIXME "thread" needs a better generic solution if (m_debugLocationChanged || m_lastCommand.startsWith(QLatin1String("thread"))) { m_debugLocationChanged = false; if (m_queryLocals && !m_lastCommand.startsWith(QLatin1String("(Q)"))) { m_nextCommands << QStringLiteral("(Q)info stack"); m_nextCommands << QStringLiteral("(Q)frame"); m_nextCommands << QStringLiteral("(Q)info args"); m_nextCommands << QStringLiteral("(Q)print *this"); m_nextCommands << QStringLiteral("(Q)info locals"); m_nextCommands << QStringLiteral("(Q)info thread"); issueNextCommand(); return; } } emit readyForInput(true); } } } QUrl DebugView::resolveFileName(const QString &fileName) { QUrl url; QFileInfo fInfo = QFileInfo(fileName); // did we end up with an absolute path or a relative one? if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } if (fInfo.isAbsolute()) { // we can not do anything just return the fileName return QUrl::fromUserInput(fileName); } // Now try to add the working path fInfo = QFileInfo(m_targetConf.workDir + fileName); if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } // now try the executable path fInfo = QFileInfo(QFileInfo(m_targetConf.executable).absolutePath() + fileName); if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } - foreach (QString srcPath, m_targetConf.srcPaths) { + for (const QString &srcPath : qAsConst(m_targetConf.srcPaths)) { fInfo = QFileInfo(srcPath + QDir::separator() + fileName); if (fInfo.exists()) { return QUrl::fromUserInput(fInfo.absoluteFilePath()); } } // we can not do anything just return the fileName return QUrl::fromUserInput(fileName); } void DebugView::outputTextMaybe(const QString &text) { if (!m_lastCommand.startsWith(QLatin1String("(Q)")) && !text.contains(PromptStr)) { emit outputText(text + QLatin1Char('\n')); } } void DebugView::slotQueryLocals(bool query) { m_queryLocals = query; if (query && (m_state == ready) && (m_nextCommands.empty())) { m_nextCommands << QStringLiteral("(Q)info stack"); m_nextCommands << QStringLiteral("(Q)frame"); m_nextCommands << QStringLiteral("(Q)info args"); m_nextCommands << QStringLiteral("(Q)print *this"); m_nextCommands << QStringLiteral("(Q)info locals"); m_nextCommands << QStringLiteral("(Q)info thread"); issueNextCommand(); } } diff --git a/addons/gdbplugin/plugin_kategdb.cpp b/addons/gdbplugin/plugin_kategdb.cpp index 07b6b42f6..4f872126b 100644 --- a/addons/gdbplugin/plugin_kategdb.cpp +++ b/addons/gdbplugin/plugin_kategdb.cpp @@ -1,714 +1,715 @@ // // Description: Kate Plugin for GDB integration // // // Copyright (c) 2010 Ian Wakeling // Copyright (c) 2010-2014 Kåre Särs // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License version 2 as published by the Free Software Foundation. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public License // along with this library; see the file COPYING.LIB. If not, write to // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, // Boston, MA 02110-1301, USA. #include "plugin_kategdb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KatePluginGDBFactory, "kategdbplugin.json", registerPlugin();) KatePluginGDB::KatePluginGDB(QObject *parent, const VariantList &) : KTextEditor::Plugin(parent) { // FIXME KF5 KGlobal::locale()->insertCatalog("kategdbplugin"); } KatePluginGDB::~KatePluginGDB() { } QObject *KatePluginGDB::createView(KTextEditor::MainWindow *mainWindow) { return new KatePluginGDBView(this, mainWindow); } KatePluginGDBView::KatePluginGDBView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_mainWin(mainWin) { m_lastExecUrl = QUrl(); m_lastExecLine = -1; m_lastExecFrame = 0; m_kateApplication = KTextEditor::Editor::instance()->application(); m_focusOnInput = true; m_activeThread = -1; KXMLGUIClient::setComponentName(QStringLiteral("kategdb"), i18n("Kate GDB")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = m_mainWin->createToolView(plugin, i18n("Debug View"), KTextEditor::MainWindow::Bottom, QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")), i18n("Debug View")); m_localsStackToolView = m_mainWin->createToolView(plugin, i18n("Locals and Stack"), KTextEditor::MainWindow::Right, QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")), i18n("Locals and Stack")); m_tabWidget = new QTabWidget(m_toolView); // Output m_outputArea = new QTextEdit(); m_outputArea->setAcceptRichText(false); m_outputArea->setReadOnly(true); m_outputArea->setUndoRedoEnabled(false); // fixed wide font, like konsole m_outputArea->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); // alternate color scheme, like konsole KColorScheme schemeView(QPalette::Active, KColorScheme::View); m_outputArea->setTextBackgroundColor(schemeView.foreground().color()); m_outputArea->setTextColor(schemeView.background().color()); QPalette p = m_outputArea->palette(); p.setColor(QPalette::Base, schemeView.foreground().color()); m_outputArea->setPalette(p); // input m_inputArea = new KHistoryComboBox(true); connect(m_inputArea, static_cast(&KHistoryComboBox::returnPressed), this, &KatePluginGDBView::slotSendCommand); QHBoxLayout *inputLayout = new QHBoxLayout(); inputLayout->addWidget(m_inputArea, 10); inputLayout->setContentsMargins(0, 0, 0, 0); m_outputArea->setFocusProxy(m_inputArea); // take the focus from the outputArea m_gdbPage = new QWidget(); QVBoxLayout *layout = new QVBoxLayout(m_gdbPage); layout->addWidget(m_outputArea); layout->addLayout(inputLayout); layout->setStretch(0, 10); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); // stack page QWidget *stackContainer = new QWidget(); QVBoxLayout *stackLayout = new QVBoxLayout(stackContainer); m_threadCombo = new QComboBox(); m_stackTree = new QTreeWidget(); stackLayout->addWidget(m_threadCombo); stackLayout->addWidget(m_stackTree); stackLayout->setStretch(0, 10); stackLayout->setContentsMargins(0, 0, 0, 0); stackLayout->setSpacing(0); QStringList headers; headers << QStringLiteral(" ") << i18nc("Column label (frame number)", "Nr") << i18nc("Column label", "Frame"); m_stackTree->setHeaderLabels(headers); m_stackTree->setRootIsDecorated(false); m_stackTree->resizeColumnToContents(0); m_stackTree->resizeColumnToContents(1); m_stackTree->setAutoScroll(false); connect(m_stackTree, &QTreeWidget::itemActivated, this, &KatePluginGDBView::stackFrameSelected); connect(m_threadCombo, static_cast(&QComboBox::currentIndexChanged), this, &KatePluginGDBView::threadSelected); m_localsView = new LocalsView(); QSplitter *locStackSplitter = new QSplitter(m_localsStackToolView); locStackSplitter->addWidget(m_localsView); locStackSplitter->addWidget(stackContainer); locStackSplitter->setOrientation(Qt::Vertical); // config page m_configView = new ConfigView(nullptr, mainWin); m_ioView = new IOView(); connect(m_configView, &ConfigView::showIO, this, &KatePluginGDBView::showIO); m_tabWidget->addTab(m_gdbPage, i18nc("Tab label", "GDB Output")); m_tabWidget->addTab(m_configView, i18nc("Tab label", "Settings")); m_debugView = new DebugView(this); connect(m_debugView, &DebugView::readyForInput, this, &KatePluginGDBView::enableDebugActions); connect(m_debugView, &DebugView::outputText, this, &KatePluginGDBView::addOutputText); connect(m_debugView, &DebugView::outputError, this, &KatePluginGDBView::addErrorText); connect(m_debugView, &DebugView::debugLocationChanged, this, &KatePluginGDBView::slotGoTo); connect(m_debugView, &DebugView::breakPointSet, this, &KatePluginGDBView::slotBreakpointSet); connect(m_debugView, &DebugView::breakPointCleared, this, &KatePluginGDBView::slotBreakpointCleared); connect(m_debugView, &DebugView::clearBreakpointMarks, this, &KatePluginGDBView::clearMarks); connect(m_debugView, &DebugView::programEnded, this, &KatePluginGDBView::programEnded); connect(m_debugView, &DebugView::gdbEnded, this, &KatePluginGDBView::programEnded); connect(m_debugView, &DebugView::gdbEnded, this, &KatePluginGDBView::gdbEnded); connect(m_debugView, &DebugView::stackFrameInfo, this, &KatePluginGDBView::insertStackFrame); connect(m_debugView, &DebugView::stackFrameChanged, this, &KatePluginGDBView::stackFrameChanged); connect(m_debugView, &DebugView::infoLocal, m_localsView, &LocalsView::addLocal); connect(m_debugView, &DebugView::threadInfo, this, &KatePluginGDBView::insertThread); connect(m_localsView, &LocalsView::localsVisible, m_debugView, &DebugView::slotQueryLocals); // Actions m_configView->registerActions(actionCollection()); QAction *a = actionCollection()->addAction(QStringLiteral("debug")); a->setText(i18n("Start Debugging")); a->setIcon(QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotDebug); a = actionCollection()->addAction(QStringLiteral("kill")); a->setText(i18n("Kill / Stop Debugging")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotKill); a = actionCollection()->addAction(QStringLiteral("rerun")); a->setText(i18n("Restart Debugging")); a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRestart); a = actionCollection()->addAction(QStringLiteral("toggle_breakpoint")); a->setText(i18n("Toggle Breakpoint / Break")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotToggleBreakpoint); a = actionCollection()->addAction(QStringLiteral("step_in")); a->setText(i18n("Step In")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-into"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepInto); a = actionCollection()->addAction(QStringLiteral("step_over")); a->setText(i18n("Step Over")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-over"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepOver); a = actionCollection()->addAction(QStringLiteral("step_out")); a->setText(i18n("Step Out")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-out"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepOut); a = actionCollection()->addAction(QStringLiteral("move_pc")); a->setText(i18nc("Move Program Counter (next execution)", "Move PC")); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotMovePC); a = actionCollection()->addAction(QStringLiteral("run_to_cursor")); a->setText(i18n("Run To Cursor")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-run-cursor"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRunToCursor); a = actionCollection()->addAction(QStringLiteral("continue")); a->setText(i18n("Continue")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotContinue); a = actionCollection()->addAction(QStringLiteral("print_value")); a->setText(i18n("Print Value")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotValue); // popup context m_menu m_menu = new KActionMenu(i18n("Debug"), this); actionCollection()->addAction(QStringLiteral("popup_gdb"), m_menu); connect(m_menu->menu(), &QMenu::aboutToShow, this, &KatePluginGDBView::aboutToShowMenu); m_breakpoint = m_menu->menu()->addAction(i18n("popup_breakpoint"), this, &KatePluginGDBView::slotToggleBreakpoint); QAction *popupAction = m_menu->menu()->addAction(i18n("popup_run_to_cursor"), this, &KatePluginGDBView::slotRunToCursor); popupAction->setText(i18n("Run To Cursor")); popupAction = m_menu->menu()->addAction(QStringLiteral("move_pc"), this, &KatePluginGDBView::slotMovePC); popupAction->setText(i18nc("Move Program Counter (next execution)", "Move PC")); enableDebugActions(false); connect(m_mainWin, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KatePluginGDBView::handleEsc); m_toolView->installEventFilter(this); m_mainWin->guiFactory()->addClient(this); } KatePluginGDBView::~KatePluginGDBView() { m_mainWin->guiFactory()->removeClient(this); delete m_toolView; delete m_localsStackToolView; } void KatePluginGDBView::readSessionConfig(const KConfigGroup &config) { m_configView->readConfig(config); } void KatePluginGDBView::writeSessionConfig(KConfigGroup &config) { m_configView->writeConfig(config); } void KatePluginGDBView::slotDebug() { disconnect(m_ioView, &IOView::stdOutText, nullptr, nullptr); disconnect(m_ioView, &IOView::stdErrText, nullptr, nullptr); if (m_configView->showIOTab()) { connect(m_ioView, &IOView::stdOutText, m_ioView, &IOView::addStdOutText); connect(m_ioView, &IOView::stdErrText, m_ioView, &IOView::addStdErrText); } else { connect(m_ioView, &IOView::stdOutText, this, &KatePluginGDBView::addOutputText); connect(m_ioView, &IOView::stdErrText, this, &KatePluginGDBView::addErrorText); } QStringList ioFifos; ioFifos << m_ioView->stdinFifo(); ioFifos << m_ioView->stdoutFifo(); ioFifos << m_ioView->stderrFifo(); enableDebugActions(true); m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); m_localsView->clear(); m_debugView->runDebugger(m_configView->currentTarget(), ioFifos); } void KatePluginGDBView::slotRestart() { m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); m_localsView->clear(); m_debugView->slotReRun(); } void KatePluginGDBView::aboutToShowMenu() { if (!m_debugView->debuggerRunning() || m_debugView->debuggerBusy()) { m_breakpoint->setText(i18n("Insert breakpoint")); m_breakpoint->setDisabled(true); return; } m_breakpoint->setDisabled(false); KTextEditor::View *editView = m_mainWin->activeView(); QUrl url = editView->document()->url(); int line = editView->cursorPosition().line(); line++; // GDB uses 1 based line numbers, kate uses 0 based... if (m_debugView->hasBreakpoint(url, line)) { m_breakpoint->setText(i18n("Remove breakpoint")); } else { m_breakpoint->setText(i18n("Insert breakpoint")); } } void KatePluginGDBView::slotToggleBreakpoint() { if (!actionCollection()->action(QStringLiteral("continue"))->isEnabled()) { m_debugView->slotInterrupt(); } else { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); int line = editView->cursorPosition().line(); m_debugView->toggleBreakpoint(currURL, line + 1); } } void KatePluginGDBView::slotBreakpointSet(const QUrl &file, int line) { KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(file)); if (iface) { iface->setMarkDescription(KTextEditor::MarkInterface::BreakpointActive, i18n("Breakpoint")); iface->setMarkPixmap(KTextEditor::MarkInterface::BreakpointActive, QIcon::fromTheme(QStringLiteral("media-playback-pause")).pixmap(10, 10)); iface->addMark(line, KTextEditor::MarkInterface::BreakpointActive); } } void KatePluginGDBView::slotBreakpointCleared(const QUrl &file, int line) { KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(file)); if (iface) { iface->removeMark(line, KTextEditor::MarkInterface::BreakpointActive); } } void KatePluginGDBView::slotMovePC() { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); KTextEditor::Cursor cursor = editView->cursorPosition(); m_debugView->movePC(currURL, cursor.line() + 1); } void KatePluginGDBView::slotRunToCursor() { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); KTextEditor::Cursor cursor = editView->cursorPosition(); // GDB starts lines from 1, kate returns lines starting from 0 (displaying 1) m_debugView->runToCursor(currURL, cursor.line() + 1); } void KatePluginGDBView::slotGoTo(const QUrl &url, int lineNum) { // skip not existing files if (!QFile::exists(url.toLocalFile())) { m_lastExecLine = -1; return; } m_lastExecUrl = url; m_lastExecLine = lineNum; KTextEditor::View *editView = m_mainWin->openUrl(m_lastExecUrl); editView->setCursorPosition(KTextEditor::Cursor(m_lastExecLine, 0)); m_mainWin->window()->raise(); m_mainWin->window()->setFocus(); } void KatePluginGDBView::enableDebugActions(bool enable) { actionCollection()->action(QStringLiteral("step_in"))->setEnabled(enable); actionCollection()->action(QStringLiteral("step_over"))->setEnabled(enable); actionCollection()->action(QStringLiteral("step_out"))->setEnabled(enable); actionCollection()->action(QStringLiteral("move_pc"))->setEnabled(enable); actionCollection()->action(QStringLiteral("run_to_cursor"))->setEnabled(enable); actionCollection()->action(QStringLiteral("popup_gdb"))->setEnabled(enable); actionCollection()->action(QStringLiteral("continue"))->setEnabled(enable); actionCollection()->action(QStringLiteral("print_value"))->setEnabled(enable); // "toggle breakpoint" doubles as interrupt while the program is running actionCollection()->action(QStringLiteral("toggle_breakpoint"))->setEnabled(m_debugView->debuggerRunning()); actionCollection()->action(QStringLiteral("kill"))->setEnabled(m_debugView->debuggerRunning()); actionCollection()->action(QStringLiteral("rerun"))->setEnabled(m_debugView->debuggerRunning()); m_inputArea->setEnabled(enable); m_threadCombo->setEnabled(enable); m_stackTree->setEnabled(enable); m_localsView->setEnabled(enable); if (enable) { m_inputArea->setFocusPolicy(Qt::WheelFocus); if (m_focusOnInput || m_configView->takeFocusAlways()) { m_inputArea->setFocus(); m_focusOnInput = false; } else { m_mainWin->activeView()->setFocus(); } } else { m_inputArea->setFocusPolicy(Qt::NoFocus); if (m_mainWin->activeView()) m_mainWin->activeView()->setFocus(); } m_ioView->enableInput(!enable && m_debugView->debuggerRunning()); if ((m_lastExecLine > -1)) { KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(m_lastExecUrl)); if (iface) { if (enable) { iface->setMarkDescription(KTextEditor::MarkInterface::Execution, i18n("Execution point")); iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(10, 10)); iface->addMark(m_lastExecLine, KTextEditor::MarkInterface::Execution); } else { iface->removeMark(m_lastExecLine, KTextEditor::MarkInterface::Execution); } } } } void KatePluginGDBView::programEnded() { // don't set the execution mark on exit m_lastExecLine = -1; m_stackTree->clear(); m_localsView->clear(); m_threadCombo->clear(); // Indicate the state change by showing the debug outputArea m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); } void KatePluginGDBView::gdbEnded() { m_outputArea->clear(); m_localsView->clear(); m_ioView->clearOutput(); clearMarks(); } void KatePluginGDBView::clearMarks() { KTextEditor::MarkInterface *iface; - foreach (KTextEditor::Document *doc, m_kateApplication->documents()) { + const auto documents = m_kateApplication->documents(); + for (KTextEditor::Document *doc : documents) { iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if ((i.value()->type == KTextEditor::MarkInterface::Execution) || (i.value()->type == KTextEditor::MarkInterface::BreakpointActive)) { iface->removeMark(i.value()->line, i.value()->type); } } } } } void KatePluginGDBView::slotSendCommand() { QString cmd = m_inputArea->currentText(); if (cmd.isEmpty()) cmd = m_lastCommand; m_inputArea->addToHistory(cmd); m_inputArea->setCurrentItem(QString()); m_focusOnInput = true; m_lastCommand = cmd; m_debugView->issueCommand(cmd); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); } void KatePluginGDBView::insertStackFrame(QString const &level, QString const &info) { if (level.isEmpty() && info.isEmpty()) { m_stackTree->resizeColumnToContents(2); return; } if (level == QLatin1Char('0')) { m_stackTree->clear(); } QStringList columns; columns << QStringLiteral(" "); // icon place holder columns << level; int lastSpace = info.lastIndexOf(QLatin1Char(' ')); QString shortInfo = info.mid(lastSpace); columns << shortInfo; QTreeWidgetItem *item = new QTreeWidgetItem(columns); item->setToolTip(2, QStringLiteral("%1").arg(info)); m_stackTree->insertTopLevelItem(level.toInt(), item); } void KatePluginGDBView::stackFrameSelected() { m_debugView->issueCommand(QStringLiteral("(Q)f %1").arg(m_stackTree->currentIndex().row())); } void KatePluginGDBView::stackFrameChanged(int level) { QTreeWidgetItem *current = m_stackTree->topLevelItem(m_lastExecFrame); QTreeWidgetItem *next = m_stackTree->topLevelItem(level); if (current) current->setIcon(0, QIcon()); if (next) next->setIcon(0, QIcon::fromTheme(QStringLiteral("arrow-right"))); m_lastExecFrame = level; } void KatePluginGDBView::insertThread(int number, bool active) { if (number < 0) { m_threadCombo->clear(); m_activeThread = -1; return; } if (!active) { m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("")).pixmap(10, 10), i18n("Thread %1", number), number); } else { m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(10, 10), i18n("Thread %1", number), number); m_activeThread = m_threadCombo->count() - 1; } m_threadCombo->setCurrentIndex(m_activeThread); } void KatePluginGDBView::threadSelected(int thread) { m_debugView->issueCommand(QStringLiteral("thread %1").arg(m_threadCombo->itemData(thread).toInt())); } QString KatePluginGDBView::currentWord() { KTextEditor::View *kv = m_mainWin->activeView(); if (!kv) { qDebug() << "no KTextEditor::View" << endl; return QString(); } if (!kv->cursorPosition().isValid()) { qDebug() << "cursor not valid!" << endl; return QString(); } int line = kv->cursorPosition().line(); int col = kv->cursorPosition().column(); QString linestr = kv->document()->line(line); int startPos = qMax(qMin(col, linestr.length() - 1), 0); int lindex = linestr.length() - 1; int endPos = startPos; while (startPos >= 0 && (linestr[startPos].isLetterOrNumber() || linestr[startPos] == QLatin1Char('_') || linestr[startPos] == QLatin1Char('~') || ((startPos > 1) && (linestr[startPos] == QLatin1Char('.')) && !linestr[startPos - 1].isSpace()) || ((startPos > 2) && (linestr[startPos] == QLatin1Char('>')) && (linestr[startPos - 1] == QLatin1Char('-')) && !linestr[startPos - 2].isSpace()))) { if (linestr[startPos] == QLatin1Char('>')) { startPos--; } startPos--; } while (endPos < (int)linestr.length() && (linestr[endPos].isLetterOrNumber() || linestr[endPos] == QLatin1Char('_') || ((endPos < lindex - 1) && (linestr[endPos] == QLatin1Char('.')) && !linestr[endPos + 1].isSpace()) || ((endPos < lindex - 2) && (linestr[endPos] == QLatin1Char('-')) && (linestr[endPos + 1] == QLatin1Char('>')) && !linestr[endPos + 2].isSpace()) || ((endPos > 1) && (linestr[endPos - 1] == QLatin1Char('-')) && (linestr[endPos] == QLatin1Char('>'))))) { if (linestr[endPos] == QLatin1Char('-')) { endPos++; } endPos++; } if (startPos == endPos) { qDebug() << "no word found!" << endl; return QString(); } // qDebug() << linestr.mid(startPos+1, endPos-startPos-1); return linestr.mid(startPos + 1, endPos - startPos - 1); } void KatePluginGDBView::slotValue() { QString variable; KTextEditor::View *editView = m_mainWin->activeView(); if (editView && editView->selection() && editView->selectionRange().onSingleLine()) { variable = editView->selectionText(); } if (variable.isEmpty()) variable = currentWord(); if (variable.isEmpty()) return; QString cmd = QStringLiteral("print %1").arg(variable); m_debugView->issueCommand(cmd); m_inputArea->addToHistory(cmd); m_inputArea->setCurrentItem(QString()); m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); } void KatePluginGDBView::showIO(bool show) { if (show) { m_tabWidget->addTab(m_ioView, i18n("IO")); } else { m_tabWidget->removeTab(m_tabWidget->indexOf(m_ioView)); } } void KatePluginGDBView::addOutputText(QString const &text) { QScrollBar *scrollb = m_outputArea->verticalScrollBar(); if (!scrollb) return; bool atEnd = (scrollb->value() == scrollb->maximum()); QTextCursor cursor = m_outputArea->textCursor(); if (!cursor.atEnd()) cursor.movePosition(QTextCursor::End); cursor.insertText(text); if (atEnd) { scrollb->setValue(scrollb->maximum()); } } void KatePluginGDBView::addErrorText(QString const &text) { m_outputArea->setFontItalic(true); addOutputText(text); m_outputArea->setFontItalic(false); } bool KatePluginGDBView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_mainWin->hideToolView(m_toolView); event->accept(); return true; } } return QObject::eventFilter(obj, event); } void KatePluginGDBView::handleEsc(QEvent *e) { if (!m_mainWin) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_toolView->isVisible()) { m_mainWin->hideToolView(m_toolView); } } } #include "plugin_kategdb.moc" diff --git a/addons/katebuild-plugin/plugin_katebuild.cpp b/addons/katebuild-plugin/plugin_katebuild.cpp index 5b1c5db42..ede6f5e6c 100644 --- a/addons/katebuild-plugin/plugin_katebuild.cpp +++ b/addons/katebuild-plugin/plugin_katebuild.cpp @@ -1,1254 +1,1254 @@ /* plugin_katebuild.c Kate Plugin ** ** Copyright (C) 2013 by Alexander Neundorf ** Copyright (C) 2006-2015 by Kåre Särs ** Copyright (C) 2011 by Ian Wakeling ** ** This code is mostly a modification of the GPL'ed Make plugin ** by Adriaan de Groot. */ /* ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program in a file called COPYING; if not, write to ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, ** MA 02110-1301, USA. */ #include "plugin_katebuild.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SelectTargetView.h" K_PLUGIN_FACTORY_WITH_JSON(KateBuildPluginFactory, "katebuildplugin.json", registerPlugin();) static const QString DefConfigCmd = QStringLiteral("cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr/local ../"); static const QString DefConfClean; static const QString DefTargetName = QStringLiteral("all"); static const QString DefBuildCmd = QStringLiteral("make"); static const QString DefCleanCmd = QStringLiteral("make clean"); static const QString NinjaPrefix = QStringLiteral("[ninja]"); static QIcon messageIcon(KateBuildView::ErrorCategory severity) { #define RETURN_CACHED_ICON(name) \ { \ static QIcon icon(QIcon::fromTheme(QStringLiteral(name))); \ return icon; \ } switch (severity) { case KateBuildView::CategoryError: RETURN_CACHED_ICON("dialog-error") case KateBuildView::CategoryWarning: RETURN_CACHED_ICON("dialog-warning") default: break; } return QIcon(); } struct ItemData { // ensure destruction, but not inadvertently so by a variant value copy QSharedPointer cursor; }; Q_DECLARE_METATYPE(ItemData) /******************************************************************/ KateBuildPlugin::KateBuildPlugin(QObject *parent, const VariantList &) : KTextEditor::Plugin(parent) { // KF5 FIXME KGlobal::locale()->insertCatalog("katebuild-plugin"); } /******************************************************************/ QObject *KateBuildPlugin::createView(KTextEditor::MainWindow *mainWindow) { return new KateBuildView(this, mainWindow); } /******************************************************************/ KateBuildView::KateBuildView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mw) : QObject(mw) , m_win(mw) , m_buildWidget(nullptr) , m_outputWidgetWidth(0) , m_proc(this) , m_stdOut() , m_stdErr() , m_buildCancelled(false) , m_displayModeBeforeBuild(1) // NOTE this will not allow spaces in file names. // e.g. from gcc: "main.cpp:14: error: cannot convert ‘std::string’ to ‘int’ in return" , m_filenameDetector(QStringLiteral("(([a-np-zA-Z]:[\\\\/])?[a-zA-Z0-9_\\.\\+\\-/\\\\]+\\.[a-zA-Z0-9]+):([0-9]+)(.*)")) // e.g. from icpc: "main.cpp(14): error: no suitable conversion function from "std::string" to "int" exists" , m_filenameDetectorIcpc(QStringLiteral("(([a-np-zA-Z]:[\\\\/])?[a-zA-Z0-9_\\.\\+\\-/\\\\]+\\.[a-zA-Z0-9]+)\\(([0-9]+)\\)(:.*)")) , m_filenameDetectorGccWorked(false) , m_newDirDetector(QStringLiteral("make\\[.+\\]: .+ `.*'")) { KXMLGUIClient::setComponentName(QStringLiteral("katebuild"), i18n("Kate Build Plugin")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = mw->createToolView(plugin, QStringLiteral("kate_plugin_katebuildplugin"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), i18n("Build Output")); QAction *a = actionCollection()->addAction(QStringLiteral("select_target")); a->setText(i18n("Select Target...")); a->setIcon(QIcon::fromTheme(QStringLiteral("select"))); connect(a, &QAction::triggered, this, &KateBuildView::slotSelectTarget); a = actionCollection()->addAction(QStringLiteral("build_default_target")); a->setText(i18n("Build Default Target")); connect(a, &QAction::triggered, this, &KateBuildView::slotBuildDefaultTarget); a = actionCollection()->addAction(QStringLiteral("build_previous_target")); a->setText(i18n("Build Previous Target")); connect(a, &QAction::triggered, this, &KateBuildView::slotBuildPreviousTarget); a = actionCollection()->addAction(QStringLiteral("stop")); a->setText(i18n("Stop")); a->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); connect(a, &QAction::triggered, this, &KateBuildView::slotStop); a = actionCollection()->addAction(QStringLiteral("goto_next")); a->setText(i18n("Next Error")); a->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); actionCollection()->setDefaultShortcut(a, Qt::SHIFT + Qt::ALT + Qt::Key_Right); connect(a, &QAction::triggered, this, &KateBuildView::slotNext); a = actionCollection()->addAction(QStringLiteral("goto_prev")); a->setText(i18n("Previous Error")); a->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); actionCollection()->setDefaultShortcut(a, Qt::SHIFT + Qt::ALT + Qt::Key_Left); connect(a, &QAction::triggered, this, &KateBuildView::slotPrev); m_showMarks = a = actionCollection()->addAction(QStringLiteral("show_marks")); a->setText(i18n("Show Marks")); a->setCheckable(true); connect(a, &QAction::triggered, this, &KateBuildView::slotDisplayOption); m_buildWidget = new QWidget(m_toolView); m_buildUi.setupUi(m_buildWidget); m_targetsUi = new TargetsUi(this, m_buildUi.u_tabWidget); m_buildUi.u_tabWidget->insertTab(0, m_targetsUi, i18nc("Tab label", "Target Settings")); m_buildUi.u_tabWidget->setCurrentWidget(m_targetsUi); m_buildWidget->installEventFilter(this); m_buildUi.buildAgainButton->setVisible(true); m_buildUi.cancelBuildButton->setVisible(true); m_buildUi.buildStatusLabel->setVisible(true); m_buildUi.buildAgainButton2->setVisible(false); m_buildUi.cancelBuildButton2->setVisible(false); m_buildUi.buildStatusLabel2->setVisible(false); m_buildUi.extraLineLayout->setAlignment(Qt::AlignRight); m_buildUi.cancelBuildButton->setEnabled(false); m_buildUi.cancelBuildButton2->setEnabled(false); connect(m_buildUi.errTreeWidget, &QTreeWidget::itemClicked, this, &KateBuildView::slotErrorSelected); m_buildUi.plainTextEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_buildUi.plainTextEdit->setReadOnly(true); slotDisplayMode(FullOutput); connect(m_buildUi.displayModeSlider, &QSlider::valueChanged, this, &KateBuildView::slotDisplayMode); connect(m_buildUi.buildAgainButton, &QPushButton::clicked, this, &KateBuildView::slotBuildPreviousTarget); connect(m_buildUi.cancelBuildButton, &QPushButton::clicked, this, &KateBuildView::slotStop); connect(m_buildUi.buildAgainButton2, &QPushButton::clicked, this, &KateBuildView::slotBuildPreviousTarget); connect(m_buildUi.cancelBuildButton2, &QPushButton::clicked, this, &KateBuildView::slotStop); connect(m_targetsUi->newTarget, &QToolButton::clicked, this, &KateBuildView::targetSetNew); connect(m_targetsUi->copyTarget, &QToolButton::clicked, this, &KateBuildView::targetOrSetCopy); connect(m_targetsUi->deleteTarget, &QToolButton::clicked, this, &KateBuildView::targetDelete); connect(m_targetsUi->addButton, &QToolButton::clicked, this, &KateBuildView::slotAddTargetClicked); connect(m_targetsUi->buildButton, &QToolButton::clicked, this, &KateBuildView::slotBuildActiveTarget); connect(m_targetsUi, &TargetsUi::enterPressed, this, &KateBuildView::slotBuildActiveTarget); m_proc.setOutputChannelMode(KProcess::SeparateChannels); connect(&m_proc, static_cast(&QProcess::finished), this, &KateBuildView::slotProcExited); connect(&m_proc, &KProcess::readyReadStandardError, this, &KateBuildView::slotReadReadyStdErr); connect(&m_proc, &KProcess::readyReadStandardOutput, this, &KateBuildView::slotReadReadyStdOut); connect(m_win, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KateBuildView::handleEsc); connect(m_win, &KTextEditor::MainWindow::viewChanged, this, &KateBuildView::slotViewChanged); m_toolView->installEventFilter(this); m_win->guiFactory()->addClient(this); // watch for project plugin view creation/deletion connect(m_win, &KTextEditor::MainWindow::pluginViewCreated, this, &KateBuildView::slotPluginViewCreated); connect(m_win, &KTextEditor::MainWindow::pluginViewDeleted, this, &KateBuildView::slotPluginViewDeleted); // Connect signals from project plugin to our slots m_projectPluginView = m_win->pluginView(QStringLiteral("kateprojectplugin")); slotPluginViewCreated(QStringLiteral("kateprojectplugin"), m_projectPluginView); } /******************************************************************/ KateBuildView::~KateBuildView() { m_win->guiFactory()->removeClient(this); delete m_toolView; } /******************************************************************/ void KateBuildView::readSessionConfig(const KConfigGroup &cg) { int numTargets = cg.readEntry(QStringLiteral("NumTargets"), 0); m_targetsUi->targetsModel.clear(); int tmpIndex; int tmpCmd; if (numTargets == 0) { // either the config is empty or uses the older format m_targetsUi->targetsModel.addTargetSet(i18n("Target Set"), QString()); m_targetsUi->targetsModel.addCommand(0, i18n("build"), cg.readEntry(QStringLiteral("Make Command"), DefBuildCmd)); m_targetsUi->targetsModel.addCommand(0, i18n("clean"), cg.readEntry(QStringLiteral("Clean Command"), DefCleanCmd)); m_targetsUi->targetsModel.addCommand(0, i18n("config"), DefConfigCmd); QString quickCmd = cg.readEntry(QStringLiteral("Quick Compile Command")); if (!quickCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(0, i18n("quick"), quickCmd); } tmpIndex = 0; tmpCmd = 0; } else { for (int i = 0; i < numTargets; i++) { QStringList targetNames = cg.readEntry(QStringLiteral("%1 Target Names").arg(i), QStringList()); QString targetSetName = cg.readEntry(QStringLiteral("%1 Target").arg(i), QString()); QString buildDir = cg.readEntry(QStringLiteral("%1 BuildPath").arg(i), QString()); m_targetsUi->targetsModel.addTargetSet(targetSetName, buildDir); if (targetNames.isEmpty()) { QString quickCmd = cg.readEntry(QStringLiteral("%1 QuickCmd").arg(i)); m_targetsUi->targetsModel.addCommand(i, i18n("build"), cg.readEntry(QStringLiteral("%1 BuildCmd"), DefBuildCmd)); m_targetsUi->targetsModel.addCommand(i, i18n("clean"), cg.readEntry(QStringLiteral("%1 CleanCmd"), DefCleanCmd)); if (!quickCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(i, i18n("quick"), quickCmd); } m_targetsUi->targetsModel.setDefaultCmd(i, i18n("build")); } else { for (int tn = 0; tn < targetNames.size(); ++tn) { const QString &targetName = targetNames.at(tn); m_targetsUi->targetsModel.addCommand(i, targetName, cg.readEntry(QStringLiteral("%1 BuildCmd %2").arg(i).arg(targetName), DefBuildCmd)); } QString defCmd = cg.readEntry(QStringLiteral("%1 Target Default").arg(i), QString()); m_targetsUi->targetsModel.setDefaultCmd(i, defCmd); } } tmpIndex = cg.readEntry(QStringLiteral("Active Target Index"), 0); tmpCmd = cg.readEntry(QStringLiteral("Active Target Command"), 0); } m_targetsUi->targetsView->expandAll(); m_targetsUi->targetsView->resizeColumnToContents(0); m_targetsUi->targetsView->collapseAll(); QModelIndex root = m_targetsUi->targetsModel.index(tmpIndex); QModelIndex cmdIndex = m_targetsUi->targetsModel.index(tmpCmd, 0, root); m_targetsUi->targetsView->setCurrentIndex(cmdIndex); auto showMarks = cg.readEntry(QStringLiteral("Show Marks"), false); m_showMarks->setChecked(showMarks); // Add project targets, if any slotAddProjectTarget(); } /******************************************************************/ void KateBuildView::writeSessionConfig(KConfigGroup &cg) { // Don't save project targets, is not our area of accountability m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); QList targets = m_targetsUi->targetsModel.targetSets(); cg.writeEntry("NumTargets", targets.size()); for (int i = 0; i < targets.size(); i++) { cg.writeEntry(QStringLiteral("%1 Target").arg(i), targets[i].name); cg.writeEntry(QStringLiteral("%1 BuildPath").arg(i), targets[i].workDir); QStringList cmdNames; for (int j = 0; j < targets[i].commands.count(); j++) { const QString &cmdName = targets[i].commands[j].first; const QString &buildCmd = targets[i].commands[j].second; cmdNames << cmdName; cg.writeEntry(QStringLiteral("%1 BuildCmd %2").arg(i).arg(cmdName), buildCmd); } cg.writeEntry(QStringLiteral("%1 Target Names").arg(i), cmdNames); cg.writeEntry(QStringLiteral("%1 Target Default").arg(i), targets[i].defaultCmd); } int setRow = 0; int set = 0; QModelIndex ind = m_targetsUi->targetsView->currentIndex(); if (ind.internalId() == TargetModel::InvalidIndex) { set = ind.row(); } else { set = ind.internalId(); setRow = ind.row(); } if (setRow < 0) setRow = 0; cg.writeEntry(QStringLiteral("Active Target Index"), set); cg.writeEntry(QStringLiteral("Active Target Command"), setRow); cg.writeEntry(QStringLiteral("Show Marks"), m_showMarks->isChecked()); // Restore project targets, if any slotAddProjectTarget(); } /******************************************************************/ void KateBuildView::slotNext() { const int itemCount = m_buildUi.errTreeWidget->topLevelItemCount(); if (itemCount == 0) { return; } QTreeWidgetItem *item = m_buildUi.errTreeWidget->currentItem(); if (item && item->isHidden()) item = nullptr; int i = (item == nullptr) ? -1 : m_buildUi.errTreeWidget->indexOfTopLevelItem(item); while (++i < itemCount) { item = m_buildUi.errTreeWidget->topLevelItem(i); // Search item which fit view settings and has desired data if (!item->text(1).isEmpty() && !item->isHidden() && item->data(1, Qt::UserRole).toInt()) { m_buildUi.errTreeWidget->setCurrentItem(item); m_buildUi.errTreeWidget->scrollToItem(item); slotErrorSelected(item); return; } } } /******************************************************************/ void KateBuildView::slotPrev() { const int itemCount = m_buildUi.errTreeWidget->topLevelItemCount(); if (itemCount == 0) { return; } QTreeWidgetItem *item = m_buildUi.errTreeWidget->currentItem(); if (item && item->isHidden()) item = nullptr; int i = (item == nullptr) ? itemCount : m_buildUi.errTreeWidget->indexOfTopLevelItem(item); while (--i >= 0) { item = m_buildUi.errTreeWidget->topLevelItem(i); // Search item which fit view settings and has desired data if (!item->text(1).isEmpty() && !item->isHidden() && item->data(1, Qt::UserRole).toInt()) { m_buildUi.errTreeWidget->setCurrentItem(item); m_buildUi.errTreeWidget->scrollToItem(item); slotErrorSelected(item); return; } } } /******************************************************************/ void KateBuildView::slotErrorSelected(QTreeWidgetItem *item) { // any view active? if (!m_win->activeView()) { return; } // Avoid garish highlighting of the selected line m_win->activeView()->setFocus(); // Search the item where the data we need is stored while (!item->data(1, Qt::UserRole).toInt()) { item = m_buildUi.errTreeWidget->itemAbove(item); if (!item) { return; } } // get stuff const QString filename = item->data(0, Qt::UserRole).toString(); if (filename.isEmpty()) { return; } int line = item->data(1, Qt::UserRole).toInt(); int column = item->data(2, Qt::UserRole).toInt(); // check with moving cursor auto data = item->data(0, DataRole).value(); if (data.cursor) { line = data.cursor->line(); column = data.cursor->column(); } // open file (if needed, otherwise, this will activate only the right view...) m_win->openUrl(QUrl::fromLocalFile(filename)); // do it ;) m_win->activeView()->setCursorPosition(KTextEditor::Cursor(line - 1, column - 1)); } /******************************************************************/ void KateBuildView::addError(const QString &filename, const QString &line, const QString &column, const QString &message) { ErrorCategory errorCategory = CategoryInfo; QTreeWidgetItem *item = new QTreeWidgetItem(m_buildUi.errTreeWidget); item->setBackground(1, Qt::gray); // The strings are twice in case kate is translated but not make. if (message.contains(QLatin1String("error")) || message.contains(i18nc("The same word as 'make' uses to mark an error.", "error")) || message.contains(QLatin1String("undefined reference")) || message.contains(i18nc("The same word as 'ld' uses to mark an ...", "undefined reference"))) { errorCategory = CategoryError; item->setForeground(1, Qt::red); m_numErrors++; item->setHidden(false); } if (message.contains(QLatin1String("warning")) || message.contains(i18nc("The same word as 'make' uses to mark a warning.", "warning"))) { errorCategory = CategoryWarning; item->setForeground(1, Qt::yellow); m_numWarnings++; item->setHidden(m_buildUi.displayModeSlider->value() > 2); } item->setTextAlignment(1, Qt::AlignRight); // visible text // remove path from visible file name QFileInfo file(filename); item->setText(0, file.fileName()); item->setText(1, line); item->setText(2, message.trimmed()); // used to read from when activating an item item->setData(0, Qt::UserRole, filename); item->setData(1, Qt::UserRole, line); item->setData(2, Qt::UserRole, column); if (errorCategory == CategoryInfo) { item->setHidden(m_buildUi.displayModeSlider->value() > 1); } item->setData(0, ErrorRole, errorCategory); // add tooltips in all columns // The enclosing ... enables word-wrap for long error messages item->setData(0, Qt::ToolTipRole, filename); item->setData(1, Qt::ToolTipRole, QStringLiteral("%1").arg(message)); item->setData(2, Qt::ToolTipRole, QStringLiteral("%1").arg(message)); } void KateBuildView::clearMarks() { for (auto &doc : m_markedDocs) { if (!doc) { continue; } KTextEditor::MarkInterface *iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); auto markType = KTextEditor::MarkInterface::Error | KTextEditor::MarkInterface::Warning; if (i.value()->type & markType) { iface->removeMark(i.value()->line, markType); } } } } m_markedDocs.clear(); } void KateBuildView::addMarks(KTextEditor::Document *doc, bool mark) { KTextEditor::MarkInterface *iface = qobject_cast(doc); KTextEditor::MovingInterface *miface = qobject_cast(doc); if (!iface || m_markedDocs.contains(doc)) return; QTreeWidgetItemIterator it(m_buildUi.errTreeWidget, QTreeWidgetItemIterator::All); while (*it) { QTreeWidgetItem *item = *it; ++it; auto filename = item->data(0, Qt::UserRole).toString(); auto url = QUrl::fromLocalFile(filename); if (url != doc->url()) continue; auto line = item->data(1, Qt::UserRole).toInt(); if (mark) { ErrorCategory category = (ErrorCategory)item->data(0, ErrorRole).toInt(); KTextEditor::MarkInterface::MarkTypes markType {}; switch (category) { case CategoryError: { markType = KTextEditor::MarkInterface::Error; iface->setMarkDescription(markType, i18n("Error")); break; } case CategoryWarning: { markType = KTextEditor::MarkInterface::Warning; iface->setMarkDescription(markType, i18n("Warning")); break; } default: break; } if (markType) { const int ps = 32; iface->setMarkPixmap(markType, messageIcon(category).pixmap(ps, ps)); iface->addMark(line - 1, markType); } m_markedDocs.insert(doc, doc); } // add moving cursor so link between message and location // is not broken by document changes if (miface) { auto data = item->data(0, DataRole).value(); if (!data.cursor) { auto column = item->data(2, Qt::UserRole).toInt(); data.cursor.reset(miface->newMovingCursor({line, column})); QVariant var; var.setValue(data); item->setData(0, DataRole, var); } } } // ensure cleanup if (miface) { auto conn = connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(slotInvalidateMoving(KTextEditor::Document *)), Qt::UniqueConnection); conn = connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(slotInvalidateMoving(KTextEditor::Document *)), Qt::UniqueConnection); } connect(doc, SIGNAL(markClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), this, SLOT(slotMarkClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), Qt::UniqueConnection); } void KateBuildView::slotInvalidateMoving(KTextEditor::Document *doc) { QTreeWidgetItemIterator it(m_buildUi.errTreeWidget, QTreeWidgetItemIterator::All); while (*it) { QTreeWidgetItem *item = *it; ++it; auto data = item->data(0, DataRole).value(); if (data.cursor && data.cursor->document() == doc) { item->setData(0, DataRole, 0); } } } void KateBuildView::slotMarkClicked(KTextEditor::Document *doc, KTextEditor::Mark mark, bool &handled) { auto tree = m_buildUi.errTreeWidget; QTreeWidgetItemIterator it(tree, QTreeWidgetItemIterator::All); while (*it) { QTreeWidgetItem *item = *it; ++it; auto filename = item->data(0, Qt::UserRole).toString(); auto line = item->data(1, Qt::UserRole).toInt(); // prefer moving cursor's opinion if so available auto data = item->data(0, DataRole).value(); if (data.cursor) { line = data.cursor->line(); } if (line - 1 == mark.line && QUrl::fromLocalFile(filename) == doc->url()) { tree->blockSignals(true); tree->setCurrentItem(item); tree->scrollToItem(item, QAbstractItemView::PositionAtCenter); tree->blockSignals(false); handled = true; break; } } } void KateBuildView::slotViewChanged() { KTextEditor::View *activeView = m_win->activeView(); auto doc = activeView ? activeView->document() : nullptr; if (doc) { addMarks(doc, m_showMarks->isChecked()); } } void KateBuildView::slotDisplayOption() { if (m_showMarks) { if (!m_showMarks->isChecked()) { clearMarks(); } else { slotViewChanged(); } } } /******************************************************************/ QUrl KateBuildView::docUrl() { KTextEditor::View *kv = m_win->activeView(); if (!kv) { qDebug() << "no KTextEditor::View" << endl; return QUrl(); } if (kv->document()->isModified()) kv->document()->save(); return kv->document()->url(); } /******************************************************************/ bool KateBuildView::checkLocal(const QUrl &dir) { if (dir.path().isEmpty()) { KMessageBox::sorry(nullptr, i18n("There is no file or directory specified for building.")); return false; } else if (!dir.isLocalFile()) { KMessageBox::sorry(nullptr, i18n("The file \"%1\" is not a local file. " "Non-local files cannot be compiled.", dir.path())); return false; } return true; } /******************************************************************/ void KateBuildView::clearBuildResults() { clearMarks(); m_buildUi.plainTextEdit->clear(); m_buildUi.errTreeWidget->clear(); m_stdOut.clear(); m_stdErr.clear(); m_numErrors = 0; m_numWarnings = 0; m_make_dir_stack.clear(); } /******************************************************************/ bool KateBuildView::startProcess(const QString &dir, const QString &command) { if (m_proc.state() != QProcess::NotRunning) { return false; } // clear previous runs clearBuildResults(); // activate the output tab m_buildUi.u_tabWidget->setCurrentIndex(1); m_displayModeBeforeBuild = m_buildUi.displayModeSlider->value(); m_buildUi.displayModeSlider->setValue(0); m_win->showToolView(m_toolView); // set working directory m_make_dir = dir; m_make_dir_stack.push(m_make_dir); if (!QFile::exists(m_make_dir)) { KMessageBox::error(nullptr, i18n("Cannot run command: %1\nWork path does not exist: %2", command, m_make_dir)); return false; } // ninja build tool sends all output to stdout, // so follow https://github.com/ninja-build/ninja/issues/1537 to separate ninja and compiler output auto env = QProcessEnvironment::systemEnvironment(); const auto nstatus = QStringLiteral("NINJA_STATUS"); auto curr = env.value(nstatus, QStringLiteral("[%f/%t] ")); // add marker to search on later on env.insert(nstatus, NinjaPrefix + curr); m_ninjaBuildDetected = false; m_proc.setProcessEnvironment(env); m_proc.setWorkingDirectory(m_make_dir); m_proc.setShellCommand(command); m_proc.start(); if (!m_proc.waitForStarted(500)) { KMessageBox::error(nullptr, i18n("Failed to run \"%1\". exitStatus = %2", command, m_proc.exitStatus())); return false; } m_buildUi.cancelBuildButton->setEnabled(true); m_buildUi.cancelBuildButton2->setEnabled(true); m_buildUi.buildAgainButton->setEnabled(false); m_buildUi.buildAgainButton2->setEnabled(false); QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); return true; } /******************************************************************/ bool KateBuildView::slotStop() { if (m_proc.state() != QProcess::NotRunning) { m_buildCancelled = true; QString msg = i18n("Building %1 cancelled", m_currentlyBuildingTarget); m_buildUi.buildStatusLabel->setText(msg); m_buildUi.buildStatusLabel2->setText(msg); m_proc.terminate(); return true; } return false; } /******************************************************************/ void KateBuildView::slotBuildActiveTarget() { if (!m_targetsUi->targetsView->currentIndex().isValid()) { slotSelectTarget(); } else { buildCurrentTarget(); } } /******************************************************************/ void KateBuildView::slotBuildPreviousTarget() { if (!m_previousIndex.isValid()) { slotSelectTarget(); } else { m_targetsUi->targetsView->setCurrentIndex(m_previousIndex); buildCurrentTarget(); } } /******************************************************************/ void KateBuildView::slotBuildDefaultTarget() { QModelIndex defaultTarget = m_targetsUi->targetsModel.defaultTarget(m_targetsUi->targetsView->currentIndex()); m_targetsUi->targetsView->setCurrentIndex(defaultTarget); buildCurrentTarget(); } /******************************************************************/ void KateBuildView::slotSelectTarget() { SelectTargetView *dialog = new SelectTargetView(&(m_targetsUi->targetsModel)); dialog->setCurrentIndex(m_targetsUi->targetsView->currentIndex()); int result = dialog->exec(); if (result == QDialog::Accepted) { m_targetsUi->targetsView->setCurrentIndex(dialog->currentIndex()); buildCurrentTarget(); } delete dialog; dialog = nullptr; } /******************************************************************/ bool KateBuildView::buildCurrentTarget() { if (m_proc.state() != QProcess::NotRunning) { displayBuildResult(i18n("Already building..."), KTextEditor::Message::Warning); return false; } QFileInfo docFInfo = docUrl().toLocalFile(); // docUrl() saves the current document QModelIndex ind = m_targetsUi->targetsView->currentIndex(); m_previousIndex = ind; if (!ind.isValid()) { KMessageBox::sorry(nullptr, i18n("No target available for building.")); return false; } QString buildCmd = m_targetsUi->targetsModel.command(ind); QString cmdName = m_targetsUi->targetsModel.cmdName(ind); QString workDir = m_targetsUi->targetsModel.workDir(ind); QString targetSet = m_targetsUi->targetsModel.targetName(ind); QString dir = workDir; if (workDir.isEmpty()) { dir = docFInfo.absolutePath(); if (dir.isEmpty()) { KMessageBox::sorry(nullptr, i18n("There is no local file or directory specified for building.")); return false; } } // a single target can serve to build lots of projects with similar directory layout if (m_projectPluginView) { QFileInfo baseDir = m_projectPluginView->property("projectBaseDir").toString(); dir.replace(QStringLiteral("%B"), baseDir.absoluteFilePath()); dir.replace(QStringLiteral("%b"), baseDir.baseName()); } // Check if the command contains the file name or directory if (buildCmd.contains(QLatin1String("%f")) || buildCmd.contains(QLatin1String("%d")) || buildCmd.contains(QLatin1String("%n"))) { if (docFInfo.absoluteFilePath().isEmpty()) { return false; } buildCmd.replace(QStringLiteral("%n"), docFInfo.baseName()); buildCmd.replace(QStringLiteral("%f"), docFInfo.absoluteFilePath()); buildCmd.replace(QStringLiteral("%d"), docFInfo.absolutePath()); } m_filenameDetectorGccWorked = false; m_currentlyBuildingTarget = QStringLiteral("%1: %2").arg(targetSet, cmdName); m_buildCancelled = false; QString msg = i18n("Building target %1 ...", m_currentlyBuildingTarget); m_buildUi.buildStatusLabel->setText(msg); m_buildUi.buildStatusLabel2->setText(msg); return startProcess(dir, buildCmd); } /******************************************************************/ void KateBuildView::displayBuildResult(const QString &msg, KTextEditor::Message::MessageType level) { KTextEditor::View *kv = m_win->activeView(); if (!kv) return; delete m_infoMessage; m_infoMessage = new KTextEditor::Message(xi18nc("@info", "Make Results:%1", msg), level); m_infoMessage->setWordWrap(true); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(5000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(kv); kv->document()->postMessage(m_infoMessage); } /******************************************************************/ void KateBuildView::slotProcExited(int exitCode, QProcess::ExitStatus) { QApplication::restoreOverrideCursor(); m_buildUi.cancelBuildButton->setEnabled(false); m_buildUi.cancelBuildButton2->setEnabled(false); m_buildUi.buildAgainButton->setEnabled(true); m_buildUi.buildAgainButton2->setEnabled(true); QString buildStatus = i18n("Building %1 completed.", m_currentlyBuildingTarget); // did we get any errors? if (m_numErrors || m_numWarnings || (exitCode != 0)) { m_buildUi.u_tabWidget->setCurrentIndex(1); if (m_buildUi.displayModeSlider->value() == 0) { m_buildUi.displayModeSlider->setValue(m_displayModeBeforeBuild > 0 ? m_displayModeBeforeBuild : 1); } m_buildUi.errTreeWidget->resizeColumnToContents(0); m_buildUi.errTreeWidget->resizeColumnToContents(1); m_buildUi.errTreeWidget->resizeColumnToContents(2); m_buildUi.errTreeWidget->horizontalScrollBar()->setValue(0); // m_buildUi.errTreeWidget->setSortingEnabled(true); m_win->showToolView(m_toolView); } if (m_numErrors || m_numWarnings) { QStringList msgs; if (m_numErrors) { msgs << i18np("Found one error.", "Found %1 errors.", m_numErrors); buildStatus = i18n("Building %1 had errors.", m_currentlyBuildingTarget); } else if (m_numWarnings) { msgs << i18np("Found one warning.", "Found %1 warnings.", m_numWarnings); buildStatus = i18n("Building %1 had warnings.", m_currentlyBuildingTarget); } displayBuildResult(msgs.join(QLatin1Char('\n')), m_numErrors ? KTextEditor::Message::Error : KTextEditor::Message::Warning); } else if (exitCode != 0) { displayBuildResult(i18n("Build failed."), KTextEditor::Message::Warning); } else { displayBuildResult(i18n("Build completed without problems."), KTextEditor::Message::Positive); } if (!m_buildCancelled) { m_buildUi.buildStatusLabel->setText(buildStatus); m_buildUi.buildStatusLabel2->setText(buildStatus); m_buildCancelled = false; // add marks slotViewChanged(); } } /******************************************************************/ void KateBuildView::slotReadReadyStdOut() { // read data from procs stdout and add // the text to the end of the output // FIXME This works for utf8 but not for all charsets QString l = QString::fromUtf8(m_proc.readAllStandardOutput()); l.remove(QLatin1Char('\r')); m_stdOut += l; // handle one line at a time do { const int end = m_stdOut.indexOf(QLatin1Char('\n')); if (end < 0) break; QString line = m_stdOut.mid(0, end); const bool ninjaOutput = line.startsWith(NinjaPrefix); m_ninjaBuildDetected |= ninjaOutput; if (ninjaOutput) { line = line.mid(NinjaPrefix.length()); } m_buildUi.plainTextEdit->appendPlainText(line); // qDebug() << line; if (m_newDirDetector.match(line).hasMatch()) { // qDebug() << "Enter/Exit dir found"; int open = line.indexOf(QLatin1Char('`')); int close = line.indexOf(QLatin1Char('\'')); QString newDir = line.mid(open + 1, close - open - 1); // qDebug () << "New dir = " << newDir; if ((m_make_dir_stack.size() > 1) && (m_make_dir_stack.top() == newDir)) { m_make_dir_stack.pop(); newDir = m_make_dir_stack.top(); } else { m_make_dir_stack.push(newDir); } m_make_dir = newDir; } else if (m_ninjaBuildDetected && !ninjaOutput) { processLine(line); } m_stdOut.remove(0, end + 1); } while (1); } /******************************************************************/ void KateBuildView::slotReadReadyStdErr() { // FIXME This works for utf8 but not for all charsets QString l = QString::fromUtf8(m_proc.readAllStandardError()); l.remove(QLatin1Char('\r')); m_stdErr += l; do { const int end = m_stdErr.indexOf(QLatin1Char('\n')); if (end < 0) break; const QString line = m_stdErr.mid(0, end); m_buildUi.plainTextEdit->appendPlainText(line); processLine(line); m_stdErr.remove(0, end + 1); } while (1); } /******************************************************************/ void KateBuildView::processLine(const QString &line) { // qDebug() << line ; // look for a filename QRegularExpressionMatch match = m_filenameDetector.match(line); if (match.hasMatch()) { m_filenameDetectorGccWorked = true; } else { if (!m_filenameDetectorGccWorked) { // let's see whether the icpc regexp works: // so for icpc users error detection will be a bit slower, // since always both regexps are checked. // But this should be the minority, for gcc and clang users // both regexes will only be checked until the first regex // matched the first time. match = m_filenameDetectorIcpc.match(line); } } if (!match.hasMatch()) { addError(QString(), QStringLiteral("0"), QString(), line); // kDebug() << "A filename was not found in the line "; return; } QString filename = match.captured(1); const QString line_n = match.captured(3); const QString msg = match.captured(4); // qDebug() << "File Name:"<targetsView->currentIndex(); if (current.parent().isValid()) { current = current.parent(); } QModelIndex index = m_targetsUi->targetsModel.addCommand(current.row(), DefTargetName, DefBuildCmd); m_targetsUi->targetsView->setCurrentIndex(index); } /******************************************************************/ void KateBuildView::targetSetNew() { int row = m_targetsUi->targetsModel.addTargetSet(i18n("Target Set"), QString()); QModelIndex buildIndex = m_targetsUi->targetsModel.addCommand(row, i18n("Build"), DefBuildCmd); m_targetsUi->targetsModel.addCommand(row, i18n("Clean"), DefCleanCmd); m_targetsUi->targetsModel.addCommand(row, i18n("Config"), DefConfigCmd); m_targetsUi->targetsModel.addCommand(row, i18n("ConfigClean"), DefConfClean); m_targetsUi->targetsView->setCurrentIndex(buildIndex); } /******************************************************************/ void KateBuildView::targetOrSetCopy() { QModelIndex newIndex = m_targetsUi->targetsModel.copyTargetOrSet(m_targetsUi->targetsView->currentIndex()); if (m_targetsUi->targetsModel.hasChildren(newIndex)) { m_targetsUi->targetsView->setCurrentIndex(newIndex.child(0, 0)); return; } m_targetsUi->targetsView->setCurrentIndex(newIndex); } /******************************************************************/ void KateBuildView::targetDelete() { QModelIndex current = m_targetsUi->targetsView->currentIndex(); m_targetsUi->targetsModel.deleteItem(current); if (m_targetsUi->targetsModel.rowCount() == 0) { targetSetNew(); } } /******************************************************************/ void KateBuildView::slotDisplayMode(int mode) { QTreeWidget *tree = m_buildUi.errTreeWidget; tree->setVisible(mode != 0); m_buildUi.plainTextEdit->setVisible(mode == 0); QString modeText; switch (mode) { case OnlyErrors: modeText = i18n("Only Errors"); break; case ErrorsAndWarnings: modeText = i18n("Errors and Warnings"); break; case ParsedOutput: modeText = i18n("Parsed Output"); break; case FullOutput: modeText = i18n("Full Output"); break; } m_buildUi.displayModeLabel->setText(modeText); if (mode < 1) { return; } const int itemCount = tree->topLevelItemCount(); for (int i = 0; i < itemCount; i++) { QTreeWidgetItem *item = tree->topLevelItem(i); const ErrorCategory errorCategory = static_cast(item->data(0, ErrorRole).toInt()); switch (errorCategory) { case CategoryInfo: item->setHidden(mode > 1); break; case CategoryWarning: item->setHidden(mode > 2); break; case CategoryError: item->setHidden(false); break; } } } /******************************************************************/ void KateBuildView::slotPluginViewCreated(const QString &name, QObject *pluginView) { // add view if (pluginView && name == QLatin1String("kateprojectplugin")) { m_projectPluginView = pluginView; slotAddProjectTarget(); connect(pluginView, SIGNAL(projectMapChanged()), this, SLOT(slotProjectMapChanged()), Qt::UniqueConnection); } } /******************************************************************/ void KateBuildView::slotPluginViewDeleted(const QString &name, QObject *) { // remove view if (name == QLatin1String("kateprojectplugin")) { m_projectPluginView = nullptr; m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); } } /******************************************************************/ void KateBuildView::slotProjectMapChanged() { // only do stuff with valid project if (!m_projectPluginView) { return; } m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); slotAddProjectTarget(); } /******************************************************************/ void KateBuildView::slotAddProjectTarget() { // only do stuff with valid project if (!m_projectPluginView) { return; } // query new project map QVariantMap projectMap = m_projectPluginView->property("projectMap").toMap(); // do we have a valid map for build settings? QVariantMap buildMap = projectMap.value(QStringLiteral("build")).toMap(); if (buildMap.isEmpty()) { return; } // Delete any old project plugin targets m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); int set = m_targetsUi->targetsModel.addTargetSet(i18n("Project Plugin Targets"), buildMap.value(QStringLiteral("directory")).toString()); - QVariantList targetsets = buildMap.value(QStringLiteral("targets")).toList(); - foreach (const QVariant &targetVariant, targetsets) { + const QVariantList targetsets = buildMap.value(QStringLiteral("targets")).toList(); + for (const QVariant &targetVariant : targetsets) { QVariantMap targetMap = targetVariant.toMap(); QString tgtName = targetMap[QStringLiteral("name")].toString(); QString buildCmd = targetMap[QStringLiteral("build_cmd")].toString(); if (tgtName.isEmpty() || buildCmd.isEmpty()) { continue; } m_targetsUi->targetsModel.addCommand(set, tgtName, buildCmd); } QModelIndex ind = m_targetsUi->targetsModel.index(set); if (!ind.child(0, 0).data().isValid()) { QString buildCmd = buildMap.value(QStringLiteral("build")).toString(); QString cleanCmd = buildMap.value(QStringLiteral("clean")).toString(); QString quickCmd = buildMap.value(QStringLiteral("quick")).toString(); if (!buildCmd.isEmpty()) { // we have loaded an "old" project file (<= 4.12) m_targetsUi->targetsModel.addCommand(set, i18n("build"), buildCmd); } if (!cleanCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(set, i18n("clean"), cleanCmd); } if (!quickCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(set, i18n("quick"), quickCmd); } } } /******************************************************************/ bool KateBuildView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_win->hideToolView(m_toolView); event->accept(); return true; } } if ((event->type() == QEvent::Resize) && (obj == m_buildWidget)) { if (m_buildUi.u_tabWidget->currentIndex() == 1) { if ((m_outputWidgetWidth == 0) && m_buildUi.buildAgainButton->isVisible()) { QSize msh = m_buildWidget->minimumSizeHint(); m_outputWidgetWidth = msh.width(); } } bool useVertLayout = (m_buildWidget->width() < m_outputWidgetWidth); m_buildUi.buildAgainButton->setVisible(!useVertLayout); m_buildUi.cancelBuildButton->setVisible(!useVertLayout); m_buildUi.buildStatusLabel->setVisible(!useVertLayout); m_buildUi.buildAgainButton2->setVisible(useVertLayout); m_buildUi.cancelBuildButton2->setVisible(useVertLayout); m_buildUi.buildStatusLabel2->setVisible(useVertLayout); } return QObject::eventFilter(obj, event); } /******************************************************************/ void KateBuildView::handleEsc(QEvent *e) { if (!m_win) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_toolView->isVisible()) { m_win->hideToolView(m_toolView); } } } #include "plugin_katebuild.moc" // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/katesql/dataoutputwidget.cpp b/addons/katesql/dataoutputwidget.cpp index d05484fc9..67f56a3fb 100644 --- a/addons/katesql/dataoutputwidget.cpp +++ b/addons/katesql/dataoutputwidget.cpp @@ -1,352 +1,352 @@ /* Copyright (C) 2010 Marco Mentasti This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dataoutputwidget.h" #include "dataoutputmodel.h" #include "dataoutputview.h" #include "exportwizard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include DataOutputWidget::DataOutputWidget(QWidget *parent) : QWidget(parent) , m_model(new DataOutputModel(this)) , m_view(new DataOutputView(this)) , m_isEmpty(true) { m_view->setModel(m_model); QHBoxLayout *layout = new QHBoxLayout(this); m_dataLayout = new QVBoxLayout(); KToolBar *toolbar = new KToolBar(this); toolbar->setOrientation(Qt::Vertical); toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); toolbar->setIconSize(QSize(16, 16)); /// TODO: disable actions if no results are displayed or selected QAction *action; action = new QAction(QIcon::fromTheme(QStringLiteral("distribute-horizontal-x")), i18nc("@action:intoolbar", "Resize columns to contents"), this); toolbar->addAction(action); connect(action, &QAction::triggered, this, &DataOutputWidget::resizeColumnsToContents); action = new QAction(QIcon::fromTheme(QStringLiteral("distribute-vertical-y")), i18nc("@action:intoolbar", "Resize rows to contents"), this); toolbar->addAction(action); connect(action, &QAction::triggered, this, &DataOutputWidget::resizeRowsToContents); action = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:intoolbar", "Copy"), this); toolbar->addAction(action); m_view->addAction(action); connect(action, &QAction::triggered, this, &DataOutputWidget::slotCopySelected); action = new QAction(QIcon::fromTheme(QStringLiteral("document-export-table")), i18nc("@action:intoolbar", "Export..."), this); toolbar->addAction(action); m_view->addAction(action); connect(action, &QAction::triggered, this, &DataOutputWidget::slotExport); action = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18nc("@action:intoolbar", "Clear"), this); toolbar->addAction(action); connect(action, &QAction::triggered, this, &DataOutputWidget::clearResults); toolbar->addSeparator(); KToggleAction *toggleAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("applications-education-language")), i18nc("@action:intoolbar", "Use system locale"), this); toolbar->addAction(toggleAction); connect(toggleAction, &QAction::triggered, this, &DataOutputWidget::slotToggleLocale); m_dataLayout->addWidget(m_view); layout->addWidget(toolbar); layout->addLayout(m_dataLayout); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); } DataOutputWidget::~DataOutputWidget() { } void DataOutputWidget::showQueryResultSets(QSqlQuery &query) { /// TODO: loop resultsets if > 1 /// NOTE from Qt Documentation: /// When one of the statements is a non-select statement a count of affected rows /// may be available instead of a result set. if (!query.isSelect() || query.lastError().isValid()) return; m_model->setQuery(query); m_isEmpty = false; QTimer::singleShot(0, this, &DataOutputWidget::resizeColumnsToContents); raise(); } void DataOutputWidget::clearResults() { // avoid crash when calling QSqlQueryModel::clear() after removing connection from the QSqlDatabase list if (m_isEmpty) return; m_model->clear(); m_isEmpty = true; /// HACK needed to refresh headers. please correct if there's a better way m_view->horizontalHeader()->hide(); m_view->verticalHeader()->hide(); m_view->horizontalHeader()->show(); m_view->verticalHeader()->show(); } void DataOutputWidget::resizeColumnsToContents() { if (m_model->rowCount() == 0) return; m_view->resizeColumnsToContents(); } void DataOutputWidget::resizeRowsToContents() { if (m_model->rowCount() == 0) return; m_view->resizeRowsToContents(); int h = m_view->rowHeight(0); if (h > 0) m_view->verticalHeader()->setDefaultSectionSize(h); } void DataOutputWidget::slotToggleLocale() { m_model->setUseSystemLocale(!m_model->useSystemLocale()); } void DataOutputWidget::slotCopySelected() { if (m_model->rowCount() <= 0) return; while (m_model->canFetchMore()) m_model->fetchMore(); if (!m_view->selectionModel()->hasSelection()) m_view->selectAll(); QString text; QTextStream stream(&text); exportData(stream); if (!text.isEmpty()) QApplication::clipboard()->setText(text); } void DataOutputWidget::slotExport() { if (m_model->rowCount() <= 0) return; while (m_model->canFetchMore()) m_model->fetchMore(); if (!m_view->selectionModel()->hasSelection()) m_view->selectAll(); ExportWizard wizard(this); if (wizard.exec() != QDialog::Accepted) return; bool outputInDocument = wizard.field(QStringLiteral("outDocument")).toBool(); bool outputInClipboard = wizard.field(QStringLiteral("outClipboard")).toBool(); bool outputInFile = wizard.field(QStringLiteral("outFile")).toBool(); bool exportColumnNames = wizard.field(QStringLiteral("exportColumnNames")).toBool(); bool exportLineNumbers = wizard.field(QStringLiteral("exportLineNumbers")).toBool(); Options opt = NoOptions; if (exportColumnNames) opt |= ExportColumnNames; if (exportLineNumbers) opt |= ExportLineNumbers; bool quoteStrings = wizard.field(QStringLiteral("checkQuoteStrings")).toBool(); bool quoteNumbers = wizard.field(QStringLiteral("checkQuoteNumbers")).toBool(); QChar stringsQuoteChar = (quoteStrings) ? wizard.field(QStringLiteral("quoteStringsChar")).toString().at(0) : QLatin1Char('\0'); QChar numbersQuoteChar = (quoteNumbers) ? wizard.field(QStringLiteral("quoteNumbersChar")).toString().at(0) : QLatin1Char('\0'); QString fieldDelimiter = wizard.field(QStringLiteral("fieldDelimiter")).toString(); if (outputInDocument) { KTextEditor::MainWindow *mw = KTextEditor::Editor::instance()->application()->activeMainWindow(); KTextEditor::View *kv = mw->activeView(); if (!kv) return; QString text; QTextStream stream(&text); exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt); kv->insertText(text); kv->setFocus(); } else if (outputInClipboard) { QString text; QTextStream stream(&text); exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt); QApplication::clipboard()->setText(text); } else if (outputInFile) { QString url = wizard.field(QStringLiteral("outFileUrl")).toString(); QFile data(url); if (data.open(QFile::WriteOnly | QFile::Truncate)) { QTextStream stream(&data); exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt); stream.flush(); } else { KMessageBox::error(this, xi18nc("@info", "Unable to open file %1", url)); } } } void DataOutputWidget::exportData(QTextStream &stream, const QChar stringsQuoteChar, const QChar numbersQuoteChar, const QString &fieldDelimiter, const Options opt) { QItemSelectionModel *selectionModel = m_view->selectionModel(); if (!selectionModel->hasSelection()) return; QString fixedFieldDelimiter = fieldDelimiter; /// FIXME: ugly workaround... fixedFieldDelimiter.replace(QLatin1String("\\t"), QLatin1String("\t")); fixedFieldDelimiter.replace(QLatin1String("\\r"), QLatin1String("\r")); fixedFieldDelimiter.replace(QLatin1String("\\n"), QLatin1String("\n")); QElapsedTimer t; t.start(); QSet columns; QSet rows; QHash, QString> snapshot; const QModelIndexList selectedIndexes = selectionModel->selectedIndexes(); snapshot.reserve(selectedIndexes.count()); - foreach (const QModelIndex &index, selectedIndexes) { + for (const QModelIndex &index : selectedIndexes) { const QVariant data = index.data(Qt::UserRole); const int col = index.column(); const int row = index.row(); if (!columns.contains(col)) columns.insert(col); if (!rows.contains(row)) rows.insert(row); if (data.type() < 7) // is numeric or boolean { if (numbersQuoteChar != QLatin1Char('\0')) snapshot[qMakePair(row, col)] = numbersQuoteChar + data.toString() + numbersQuoteChar; else snapshot[qMakePair(row, col)] = data.toString(); } else { if (stringsQuoteChar != QLatin1Char('\0')) snapshot[qMakePair(row, col)] = stringsQuoteChar + data.toString() + stringsQuoteChar; else snapshot[qMakePair(row, col)] = data.toString(); } } if (opt.testFlag(ExportColumnNames)) { if (opt.testFlag(ExportLineNumbers)) stream << fixedFieldDelimiter; QSetIterator j(columns); while (j.hasNext()) { const QVariant data = m_model->headerData(j.next(), Qt::Horizontal); if (stringsQuoteChar != QLatin1Char('\0')) stream << stringsQuoteChar + data.toString() + stringsQuoteChar; else stream << data.toString(); if (j.hasNext()) stream << fixedFieldDelimiter; } stream << "\n"; } - foreach (const int row, rows) { + for (const int row : qAsConst(rows)) { if (opt.testFlag(ExportLineNumbers)) stream << row + 1 << fixedFieldDelimiter; QSetIterator j(columns); while (j.hasNext()) { stream << snapshot.value(qMakePair(row, j.next())); if (j.hasNext()) stream << fixedFieldDelimiter; } stream << "\n"; } qDebug() << "Export in" << t.elapsed() << "msecs"; } diff --git a/addons/katesql/schemawidget.cpp b/addons/katesql/schemawidget.cpp index 0aeeea025..8a70045ab 100644 --- a/addons/katesql/schemawidget.cpp +++ b/addons/katesql/schemawidget.cpp @@ -1,370 +1,370 @@ /* Copyright (C) 2010 Marco Mentasti This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "schemawidget.h" #include "sqlmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SchemaWidget::SchemaWidget(QWidget *parent, SQLManager *manager) : QTreeWidget(parent) , m_manager(manager) { m_tablesLoaded = false; m_viewsLoaded = false; setHeaderLabels(QStringList() << i18nc("@title:column", "Database schema")); setContextMenuPolicy(Qt::CustomContextMenu); setDragDropMode(QAbstractItemView::DragOnly); setDragEnabled(true); setAcceptDrops(false); connect(this, &SchemaWidget::customContextMenuRequested, this, &SchemaWidget::slotCustomContextMenuRequested); connect(this, &SchemaWidget::itemExpanded, this, &SchemaWidget::slotItemExpanded); } SchemaWidget::~SchemaWidget() { } bool SchemaWidget::isConnectionValidAndOpen() { return m_manager->isValidAndOpen(m_connectionName); } void SchemaWidget::deleteChildren(QTreeWidgetItem *item) { - QList items = item->takeChildren(); + const QList items = item->takeChildren(); - foreach (QTreeWidgetItem *i, items) + for (QTreeWidgetItem *i : items) delete i; } void SchemaWidget::buildTree(const QString &connection) { m_connectionName = connection; clear(); m_tablesLoaded = false; m_viewsLoaded = false; if (!m_connectionName.isEmpty()) buildDatabase(new QTreeWidgetItem(this)); } void SchemaWidget::refresh() { buildTree(m_connectionName); } void SchemaWidget::buildDatabase(QTreeWidgetItem *databaseItem) { QSqlDatabase db = QSqlDatabase::database(m_connectionName); QString dbname = (db.isValid() ? db.databaseName() : m_connectionName); databaseItem->setText(0, dbname); databaseItem->setIcon(0, QIcon::fromTheme(QStringLiteral("server-database"))); QTreeWidgetItem *tablesItem = new QTreeWidgetItem(databaseItem, TablesFolderType); tablesItem->setText(0, i18nc("@title Folder name", "Tables")); tablesItem->setIcon(0, QIcon::fromTheme(QStringLiteral("folder"))); tablesItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); QTreeWidgetItem *viewsItem = new QTreeWidgetItem(databaseItem, ViewsFolderType); viewsItem->setText(0, i18nc("@title Folder name", "Views")); viewsItem->setIcon(0, QIcon::fromTheme(QStringLiteral("folder"))); viewsItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); databaseItem->setExpanded(true); } void SchemaWidget::buildTables(QTreeWidgetItem *tablesItem) { if (!isConnectionValidAndOpen()) return; QTreeWidgetItem *systemTablesItem = new QTreeWidgetItem(tablesItem, SystemTablesFolderType); systemTablesItem->setText(0, i18nc("@title Folder name", "System Tables")); systemTablesItem->setIcon(0, QIcon::fromTheme(QStringLiteral("folder"))); systemTablesItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); QSqlDatabase db = QSqlDatabase::database(m_connectionName); QStringList tables = db.tables(QSql::SystemTables); - foreach (const QString &table, tables) { + for (const QString &table : qAsConst(tables)) { QTreeWidgetItem *item = new QTreeWidgetItem(systemTablesItem, SystemTableType); item->setText(0, table); item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-table.png"))); item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); } tables = db.tables(QSql::Tables); - foreach (const QString &table, tables) { + for (const QString &table : qAsConst(tables)) { QTreeWidgetItem *item = new QTreeWidgetItem(tablesItem, TableType); item->setText(0, table); item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-table.png"))); item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); } m_tablesLoaded = true; } void SchemaWidget::buildViews(QTreeWidgetItem *viewsItem) { if (!isConnectionValidAndOpen()) return; QSqlDatabase db = QSqlDatabase::database(m_connectionName); const QStringList views = db.tables(QSql::Views); - foreach (const QString &view, views) { + for (const QString &view : views) { QTreeWidgetItem *item = new QTreeWidgetItem(viewsItem, ViewType); item->setText(0, view); item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-view.png"))); item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); } m_viewsLoaded = true; } void SchemaWidget::buildFields(QTreeWidgetItem *tableItem) { if (!isConnectionValidAndOpen()) return; QSqlDatabase db = QSqlDatabase::database(m_connectionName); QString tableName = tableItem->text(0); QSqlIndex pk = db.primaryIndex(tableName); QSqlRecord rec = db.record(tableName); for (int i = 0; i < rec.count(); ++i) { QSqlField f = rec.field(i); QString fieldName = f.name(); QTreeWidgetItem *item = new QTreeWidgetItem(tableItem, FieldType); item->setText(0, fieldName); if (pk.contains(fieldName)) item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-field-pk.png"))); else item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-field.png"))); } } void SchemaWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) m_dragStartPosition = event->pos(); QTreeWidget::mousePressEvent(event); } void SchemaWidget::mouseMoveEvent(QMouseEvent *event) { if (!(event->buttons() & Qt::LeftButton)) return; if ((event->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance()) return; // QTreeWidgetItem *item = currentItem(); QTreeWidgetItem *item = itemAt(event->pos()); if (!item) return; if (item->type() != SchemaWidget::SystemTableType && item->type() != SchemaWidget::TableType && item->type() != SchemaWidget::ViewType && item->type() != SchemaWidget::FieldType) return; QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; if (item->type() == SchemaWidget::FieldType) mimeData->setText(QStringLiteral("%1.%2").arg(item->parent()->text(0), item->text(0))); else mimeData->setText(item->text(0)); drag->setMimeData(mimeData); drag->exec(Qt::CopyAction); QTreeWidget::mouseMoveEvent(event); } void SchemaWidget::slotItemExpanded(QTreeWidgetItem *item) { if (!item) return; switch (item->type()) { case SchemaWidget::TablesFolderType: { if (!m_tablesLoaded) buildTables(item); } break; case SchemaWidget::ViewsFolderType: { if (!m_viewsLoaded) buildViews(item); } break; case SchemaWidget::TableType: case SchemaWidget::SystemTableType: case SchemaWidget::ViewType: { if (item->childCount() == 0) buildFields(item); } break; default: break; } } void SchemaWidget::slotCustomContextMenuRequested(const QPoint &pos) { QMenu menu; menu.addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu Context menu", "Refresh"), this, &SchemaWidget::refresh); QTreeWidgetItem *item = itemAt(pos); if (item) { if (item->type() == SchemaWidget::SystemTableType || item->type() == SchemaWidget::TableType || item->type() == SchemaWidget::ViewType || item->type() == SchemaWidget::FieldType) { menu.addSeparator(); QMenu *submenu = menu.addMenu(QIcon::fromTheme(QStringLiteral("tools-wizard")), i18nc("@action:inmenu Submenu title", "Generate")); submenu->addAction(i18n("SELECT"), this, &SchemaWidget::generateSelect); submenu->addAction(i18n("UPDATE"), this, &SchemaWidget::generateUpdate); submenu->addAction(i18n("INSERT"), this, &SchemaWidget::generateInsert); submenu->addAction(i18n("DELETE"), this, &SchemaWidget::generateDelete); } } menu.exec(QCursor::pos()); } void SchemaWidget::generateStatement(QSqlDriver::StatementType statementType) { if (!isConnectionValidAndOpen()) return; QSqlDatabase db = QSqlDatabase::database(m_connectionName); QSqlDriver *drv = db.driver(); if (!drv) return; QTreeWidgetItem *item = currentItem(); if (!item) return; QString statement; switch (item->type()) { case TableType: case SystemTableType: case ViewType: { QString tableName = item->text(0); QSqlRecord rec = db.record(tableName); // set all fields to a value (NULL) // values are needed to generate update and insert statements if (statementType == QSqlDriver::UpdateStatement || statementType == QSqlDriver::InsertStatement) for (int i = 0, n = rec.count(); i < n; ++i) rec.setNull(i); statement = drv->sqlStatement(statementType, tableName, rec, false); } break; case FieldType: { QString tableName = item->parent()->text(0); QSqlRecord rec = db.record(tableName); // get the selected column... QSqlField field = rec.field(item->text(0)); // ...and set its value to NULL field.clear(); // clear all columns and re-append the selected one rec.clear(); rec.append(field); statement = drv->sqlStatement(statementType, tableName, rec, false); if (statementType == QSqlDriver::DeleteStatement) statement += QLatin1Char(' ') + drv->sqlStatement(QSqlDriver::WhereStatement, tableName, rec, false).replace(QLatin1String(" IS NULL"), QLatin1String("=?")); } break; } KTextEditor::MainWindow *mw = KTextEditor::Editor::instance()->application()->activeMainWindow(); KTextEditor::View *kv = mw->activeView(); // replace NULL with a more generic '?' statement.replace(QLatin1String("NULL"), QLatin1String("?")); if (kv) { // paste statement in the active view kv->insertText(statement); kv->setFocus(); } qDebug() << "Generated statement:" << statement; } void SchemaWidget::generateSelect() { generateStatement(QSqlDriver::SelectStatement); } void SchemaWidget::generateUpdate() { generateStatement(QSqlDriver::UpdateStatement); } void SchemaWidget::generateInsert() { generateStatement(QSqlDriver::InsertStatement); } void SchemaWidget::generateDelete() { generateStatement(QSqlDriver::DeleteStatement); }