diff --git a/plugins/quickopen/documentationquickopenprovider.cpp b/plugins/quickopen/documentationquickopenprovider.cpp index 63db4d3500..2ac2403681 100644 --- a/plugins/quickopen/documentationquickopenprovider.cpp +++ b/plugins/quickopen/documentationquickopenprovider.cpp @@ -1,149 +1,149 @@ /* * This file is part of KDevelop * * Copyright 2013 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentationquickopenprovider.h" #include #include #include #include #include #include using namespace KDevelop; class DocumentationQuickOpenItem : public QuickOpenDataBase { public: DocumentationQuickOpenItem(const QModelIndex& data, IDocumentationProvider* p) : QuickOpenDataBase() , m_data(data) , m_provider(p) {} QString text() const override { return m_data.data().toString(); } QString htmlDescription() const override { return i18n("Documentation in the %1", m_provider->name()); } bool execute(QString&) override { IDocumentation::Ptr docu = m_provider->documentationForIndex(m_data); if (docu) { ICore::self()->documentationController()->showDocumentation(docu); } return docu; } QIcon icon() const override { return m_provider->icon(); } private: QModelIndex m_data; IDocumentationProvider* m_provider; }; namespace { uint recursiveRowCount(const QAbstractItemModel* m, const QModelIndex& idx) { uint rows = m->rowCount(idx); uint ret = rows; for (uint i = 0; i < rows; i++) { ret += recursiveRowCount(m, m->index(i, 0, idx)); } return ret; } void matchingIndexes(const QAbstractItemModel* m, const QString& match, const QModelIndex& idx, QList& ret, int& preferred) { if (m->hasChildren(idx)) { for (int i = 0, rows = m->rowCount(); i < rows; i++) { matchingIndexes(m, match, m->index(i, 0, idx), ret, preferred); } } else { int index = idx.data().toString().indexOf(match, 0, Qt::CaseInsensitive); if (index == 0) { ret.insert(preferred++, idx); } else if (index > 0) { ret.append(idx); } } } } DocumentationQuickOpenProvider::DocumentationQuickOpenProvider() { connect(ICore::self()->documentationController(), &IDocumentationController::providersChanged, this, &DocumentationQuickOpenProvider::reset); } void DocumentationQuickOpenProvider::setFilterText(const QString& text) { if (text.size() < 2) { return; } m_results.clear(); int split = 0; const QList providers = ICore::self()->documentationController()->documentationProviders(); for (IDocumentationProvider* p : providers) { QList idxs; int internalSplit = 0; int i = 0; matchingIndexes(p->indexModel(), text, QModelIndex(), idxs, internalSplit); - foreach (const QModelIndex& idx, idxs) { + for (const QModelIndex& idx : qAsConst(idxs)) { m_results.insert(split + i, QuickOpenDataPointer(new DocumentationQuickOpenItem(idx, p))); i++; } split += internalSplit; } } uint DocumentationQuickOpenProvider::unfilteredItemCount() const { uint ret = 0; const QList providers = ICore::self()->documentationController()->documentationProviders(); for (IDocumentationProvider* p : providers) { ret += recursiveRowCount(p->indexModel(), QModelIndex()); } return ret; } QuickOpenDataPointer DocumentationQuickOpenProvider::data(uint row) const { return m_results.at(row); } uint DocumentationQuickOpenProvider::itemCount() const { return m_results.size(); } void DocumentationQuickOpenProvider::reset() { m_results.clear(); } diff --git a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp index 3f70a947db..9ac8cfaed0 100644 --- a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp +++ b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp @@ -1,601 +1,601 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 "expandingwidgetmodel.h" #include #include #include #include #include #include #include #include #include "expandingdelegate.h" #include QIcon ExpandingWidgetModel::m_expandedIcon; QIcon ExpandingWidgetModel::m_collapsedIcon; using namespace KTextEditor; inline QModelIndex firstColumn(const QModelIndex& index) { return index.sibling(index.row(), 0); } ExpandingWidgetModel::ExpandingWidgetModel(QWidget* parent) : QAbstractTableModel(parent) { } ExpandingWidgetModel::~ExpandingWidgetModel() { clearExpanding(); } static QColor doAlternate(const QColor& color) { QColor background = QApplication::palette().window().color(); return KColorUtils::mix(color, background, 0.15); } uint ExpandingWidgetModel::matchColor(const QModelIndex& index) const { int matchQuality = contextMatchQuality(index.sibling(index.row(), 0)); if (matchQuality > 0) { bool alternate = index.row() & 1; QColor badMatchColor(0xff00aa44); //Blue-ish green QColor goodMatchColor(0xff00ff00); //Green QColor background = treeView()->palette().light().color(); QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, (( float )matchQuality) / 10.0); if (alternate) { totalColor = doAlternate(totalColor); } const float dynamicTint = 0.2f; const float minimumTint = 0.2f; double tintStrength = (dynamicTint * matchQuality) / 10; if (tintStrength) { tintStrength += minimumTint; //Some minimum tinting strength, else it's not visible any more } return KColorUtils::tint(background, totalColor, tintStrength).rgb(); } else { return 0; } } QVariant ExpandingWidgetModel::data(const QModelIndex& index, int role) const { switch (role) { case Qt::BackgroundRole: { if (index.column() == 0) { //Highlight by match-quality uint color = matchColor(index); if (color) { return QBrush(color); } } //Use a special background-color for expanded items if (isExpanded(index)) { if (index.row() & 1) { return doAlternate(treeView()->palette().toolTipBase().color()); } else { return treeView()->palette().toolTipBase(); } } } } return QVariant(); } QModelIndex ExpandingWidgetModel::mapFromSource(const QModelIndex& index) const { const auto proxyModel = qobject_cast(treeView()->model()); Q_ASSERT(proxyModel); Q_ASSERT(!index.isValid() || index.model() == this); return proxyModel->mapFromSource(index); } QModelIndex ExpandingWidgetModel::mapToSource(const QModelIndex& index) const { const auto proxyModel = qobject_cast(treeView()->model()); Q_ASSERT(proxyModel); Q_ASSERT(!index.isValid() || index.model() == proxyModel); return proxyModel->mapToSource(index); } void ExpandingWidgetModel::clearMatchQualities() { m_contextMatchQualities.clear(); } QModelIndex ExpandingWidgetModel::partiallyExpandedRow() const { if (m_partiallyExpanded.isEmpty()) { return QModelIndex(); } else { return m_partiallyExpanded.constBegin().key(); } } void ExpandingWidgetModel::clearExpanding() { clearMatchQualities(); QMap oldExpandState = m_expandState; - foreach (QPointer widget, m_expandingWidgets) { + for (auto& widget : qAsConst(m_expandingWidgets)) { delete widget; } m_expandingWidgets.clear(); m_expandState.clear(); m_partiallyExpanded.clear(); for (QMap::const_iterator it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it) { if (it.value() == Expanded) { emit dataChanged(it.key(), it.key()); } } } ExpandingWidgetModel::ExpansionType ExpandingWidgetModel::isPartiallyExpanded(const QModelIndex& index) const { const auto expansionIt = m_partiallyExpanded.find(firstColumn(index)); if (expansionIt != m_partiallyExpanded.end()) { return *expansionIt; } else { return NotExpanded; } } void ExpandingWidgetModel::partiallyUnExpand(const QModelIndex& idx_) { QModelIndex index(firstColumn(idx_)); m_partiallyExpanded.remove(index); m_partiallyExpanded.remove(idx_); } int ExpandingWidgetModel::partiallyExpandWidgetHeight() const { return 60; ///@todo use font-metrics text-height*2 for 2 lines } void ExpandingWidgetModel::rowSelected(const QModelIndex& idx_) { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); if (!m_partiallyExpanded.contains(idx)) { QModelIndex oldIndex = partiallyExpandedRow(); //Unexpand the previous partially expanded row if (!m_partiallyExpanded.isEmpty()) { ///@todo allow multiple partially expanded rows while (!m_partiallyExpanded.isEmpty()) m_partiallyExpanded.erase(m_partiallyExpanded.begin()); //partiallyUnExpand( m_partiallyExpanded.begin().key() ); } //Notify the underlying models that the item was selected, and eventually get back the text for the expanding widget. if (!idx.isValid()) { //All items have been unselected if (oldIndex.isValid()) { emit dataChanged(oldIndex, oldIndex); } } else { QVariant variant = data(idx, CodeCompletionModel::ItemSelected); if (!isExpanded(idx) && variant.type() == QVariant::String) { //Either expand upwards or downwards, choose in a way that //the visible fields of the new selected entry are not moved. if (oldIndex.isValid() && (oldIndex < idx || (!(oldIndex < idx) && oldIndex.parent() < idx.parent()))) { m_partiallyExpanded.insert(idx, ExpandUpwards); } else { m_partiallyExpanded.insert(idx, ExpandDownwards); } //Say that one row above until one row below has changed, so no items will need to be moved(the space that is taken from one item is given to the other) if (oldIndex.isValid() && oldIndex < idx) { emit dataChanged(oldIndex, idx); if (treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem) { const QModelIndex viewIndex = mapFromSource(idx); //Qt fails to correctly scroll in ScrollPerItem mode, so the selected index is completely visible, //so we do the scrolling by hand. QRect selectedRect = treeView()->visualRect(viewIndex); QRect frameRect = treeView()->frameRect(); if (selectedRect.bottom() > frameRect.bottom()) { int diff = selectedRect.bottom() - frameRect.bottom(); //We need to scroll down QModelIndex newTopIndex = viewIndex; QModelIndex nextTopIndex = viewIndex; QRect nextRect = treeView()->visualRect(nextTopIndex); while (nextTopIndex.isValid() && nextRect.isValid() && nextRect.top() >= diff) { newTopIndex = nextTopIndex; nextTopIndex = treeView()->indexAbove(nextTopIndex); if (nextTopIndex.isValid()) { nextRect = treeView()->visualRect(nextTopIndex); } } treeView()->scrollTo(newTopIndex, QAbstractItemView::PositionAtTop); } } //This is needed to keep the item we are expanding completely visible. Qt does not scroll the view to keep the item visible. //But we must make sure that it isn't too expensive. //We need to make sure that scrolling is efficient, and the whole content is not repainted. //Since we are scrolling anyway, we can keep the next line visible, which might be a cool feature. //Since this also doesn't work smoothly, leave it for now //treeView()->scrollTo( nextLine, QAbstractItemView::EnsureVisible ); } else if (oldIndex.isValid() && idx < oldIndex) { emit dataChanged(idx, oldIndex); //For consistency with the down-scrolling, we keep one additional line visible above the current visible. //Since this also doesn't work smoothly, leave it for now /* QModelIndex prevLine = idx.sibling(idx.row()-1, idx.column()); if( prevLine.isValid() ) treeView()->scrollTo( prevLine );*/ } else { emit dataChanged(idx, idx); } } else if (oldIndex.isValid()) { //We are not partially expanding a new row, but we previously had a partially expanded row. So signalize that it has been unexpanded. emit dataChanged(oldIndex, oldIndex); } } } else { qCDebug(PLUGIN_QUICKOPEN) << "ExpandingWidgetModel::rowSelected: Row is already partially expanded"; } } QString ExpandingWidgetModel::partialExpandText(const QModelIndex& idx) const { Q_ASSERT(idx.model() == this); if (!idx.isValid()) { return QString(); } return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString(); } QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); if (!idx.isValid()) { return QRect(); } ExpansionType expansion = ExpandDownwards; if (m_partiallyExpanded.find(idx) != m_partiallyExpanded.constEnd()) { expansion = m_partiallyExpanded[idx]; } //Get the whole rectangle of the row: const QModelIndex viewIndex = mapFromSource(idx); QModelIndex rightMostIndex = viewIndex; QModelIndex tempIndex = viewIndex; while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) rightMostIndex = tempIndex; QRect rect = treeView()->visualRect(viewIndex); QRect rightMostRect = treeView()->visualRect(rightMostIndex); rect.setLeft(rect.left() + 20); rect.setRight(rightMostRect.right() - 5); //These offsets must match exactly those used in ExpandingDelegate::sizeHint() int top = rect.top() + 5; int bottom = rightMostRect.bottom() - 5; if (expansion == ExpandDownwards) { top += basicRowHeight(viewIndex); } else { bottom -= basicRowHeight(viewIndex); } rect.setTop(top); rect.setBottom(bottom); return rect; } bool ExpandingWidgetModel::isExpandable(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); auto expandStateIt = m_expandState.find(idx); if (expandStateIt == m_expandState.end()) { expandStateIt = m_expandState.insert(idx, NotExpandable); QVariant v = data(idx, CodeCompletionModel::IsExpandable); if (v.canConvert() && v.toBool()) { *expandStateIt = Expandable; } } return *expandStateIt != NotExpandable; } bool ExpandingWidgetModel::isExpanded(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); return m_expandState.contains(idx) && m_expandState[idx] == Expanded; } void ExpandingWidgetModel::setExpanded(const QModelIndex& idx_, bool expanded) { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); qCDebug(PLUGIN_QUICKOPEN) << "Setting expand-state of row " << idx.row() << " to " << expanded; if (!idx.isValid()) { return; } if (isExpandable(idx)) { if (!expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx]) { m_expandingWidgets[idx]->hide(); } m_expandState[idx] = expanded ? Expanded : Expandable; if (expanded) { partiallyUnExpand(idx); } if (expanded && !m_expandingWidgets.contains(idx)) { QVariant v = data(idx, CodeCompletionModel::ExpandingWidget); if (v.canConvert()) { m_expandingWidgets[idx] = v.value(); } else if (v.canConvert()) { //Create a html widget that shows the given string KTextEdit* edit = new KTextEdit(v.toString()); edit->setReadOnly(true); edit->resize(200, 50); //Make the widget small so it embeds nicely. m_expandingWidgets[idx] = edit; } else { m_expandingWidgets[idx] = nullptr; } } //Eventually partially expand the row if (!expanded && firstColumn(mapToSource(treeView()->currentIndex())) == idx && (isPartiallyExpanded(idx) == ExpandingWidgetModel::ExpansionType::NotExpanded)) { rowSelected(idx); //Partially expand the row. } emit dataChanged(idx, idx); if (treeView()) { treeView()->scrollTo(mapFromSource(idx)); } } } int ExpandingWidgetModel::basicRowHeight(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == treeView()->model()); QModelIndex idx(firstColumn(idx_)); auto* delegate = qobject_cast(treeView()->itemDelegate(idx)); if (!delegate || !idx.isValid()) { qCDebug(PLUGIN_QUICKOPEN) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate"; return 15; } return delegate->basicSizeHint(idx).height(); } void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex& idx_) { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); QWidget* w = nullptr; const auto widgetIt = m_expandingWidgets.constFind(idx); if (widgetIt != m_expandingWidgets.constEnd()) { w = *widgetIt; } if (w && isExpanded(idx)) { if (!idx.isValid()) { return; } const QModelIndex viewIndex = mapFromSource(idx_); QRect rect = treeView()->visualRect(viewIndex); if (!rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height()) { //The item is currently not visible w->hide(); return; } QModelIndex rightMostIndex = viewIndex; QModelIndex tempIndex = viewIndex; while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) rightMostIndex = tempIndex; QRect rightMostRect = treeView()->visualRect(rightMostIndex); //Find out the basic height of the row rect.setLeft(rect.left() + 20); rect.setRight(rightMostRect.right() - 5); //These offsets must match exactly those used in KateCompletionDeleage::sizeHint() rect.setTop(rect.top() + basicRowHeight(viewIndex) + 5); rect.setHeight(w->height()); if (w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible()) { w->setParent(treeView()->viewport()); w->setGeometry(rect); w->show(); } } } void ExpandingWidgetModel::placeExpandingWidgets() { for (QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { placeExpandingWidget(it.key()); } } int ExpandingWidgetModel::expandingWidgetsHeight() const { int sum = 0; for (QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { if (isExpanded(it.key()) && (*it)) { sum += (*it)->height(); } } return sum; } QWidget* ExpandingWidgetModel::expandingWidget(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); const auto widgetIt = m_expandingWidgets.find(idx); if (widgetIt != m_expandingWidgets.end()) { return *widgetIt; } else { return nullptr; } } void ExpandingWidgetModel::cacheIcons() const { if (m_expandedIcon.isNull()) { m_expandedIcon = QIcon::fromTheme(QStringLiteral("arrow-down")); } if (m_collapsedIcon.isNull()) { m_collapsedIcon = QIcon::fromTheme(QStringLiteral("arrow-right")); } } QList mergeCustomHighlighting(int leftSize, const QList& left, int rightSize, const QList& right) { QList ret = left; if (left.isEmpty()) { ret << QVariant(0); ret << QVariant(leftSize); ret << QTextFormat(QTextFormat::CharFormat); } if (right.isEmpty()) { ret << QVariant(leftSize); ret << QVariant(rightSize); ret << QTextFormat(QTextFormat::CharFormat); } else { QList::const_iterator it = right.constBegin(); while (it != right.constEnd()) { { QList::const_iterator testIt = it; for (int a = 0; a < 2; a++) { ++testIt; if (testIt == right.constEnd()) { qCWarning(PLUGIN_QUICKOPEN) << "Length of input is not multiple of 3"; break; } } } ret << QVariant((*it).toInt() + leftSize); ++it; ret << QVariant((*it).toInt()); ++it; ret << *it; if (!(*it).value().isValid()) { qCDebug(PLUGIN_QUICKOPEN) << "Text-format is invalid"; } ++it; } } return ret; } //It is assumed that between each two strings, one space is inserted QList mergeCustomHighlighting(const QStringList& strings_, const QList& highlights_, int grapBetweenStrings) { QStringList strings(strings_); QList highlights(highlights_); if (strings.isEmpty()) { qCWarning(PLUGIN_QUICKOPEN) << "List of strings is empty"; return QList(); } if (highlights.isEmpty()) { qCWarning(PLUGIN_QUICKOPEN) << "List of highlightings is empty"; return QList(); } if (strings.count() != highlights.count()) { qCWarning(PLUGIN_QUICKOPEN) << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same"; return QList(); } //Merge them together QString totalString = strings[0]; QVariantList totalHighlighting = highlights[0]; strings.pop_front(); highlights.pop_front(); while (!strings.isEmpty()) { const int stringLength = strings[0].length(); totalHighlighting = mergeCustomHighlighting(totalString.length(), totalHighlighting, stringLength, highlights[0]); totalString.reserve(totalString.size() + stringLength + grapBetweenStrings); totalString += strings[0]; for (int a = 0; a < grapBetweenStrings; a++) { totalString += QLatin1Char(' '); } strings.pop_front(); highlights.pop_front(); } //Combine the custom-highlightings return totalHighlighting; } diff --git a/plugins/quickopen/projectfilequickopen.cpp b/plugins/quickopen/projectfilequickopen.cpp index 991b5d0ca7..43192bc8cf 100644 --- a/plugins/quickopen/projectfilequickopen.cpp +++ b/plugins/quickopen/projectfilequickopen.cpp @@ -1,368 +1,372 @@ /* This file is part of the KDE libraries Copyright (C) 2007 David Nolden 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 "projectfilequickopen.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../openwith/iopenwith.h" using namespace KDevelop; namespace { QSet openFiles() { QSet openFiles; const QList& docs = ICore::self()->documentController()->openDocuments(); openFiles.reserve(docs.size()); for (IDocument* doc : docs) { openFiles << IndexedString(doc->url()); } return openFiles; } QString iconNameForUrl(const IndexedString& url) { if (url.isEmpty()) { return QStringLiteral("tab-duplicate"); } ProjectBaseItem* item = ICore::self()->projectController()->projectModel()->itemForPath(url); if (item) { return item->iconName(); } return QStringLiteral("unknown"); } } ProjectFileData::ProjectFileData(const ProjectFile& file) : m_file(file) { } QString ProjectFileData::text() const { return m_file.projectPath.relativePath(m_file.path); } QString ProjectFileData::htmlDescription() const { return QLatin1String("") + i18nc("%1: project name", "Project %1", project()) + QLatin1String(""); } bool ProjectFileData::execute(QString& filterText) { const QUrl url = m_file.path.toUrl(); IOpenWith::openFiles(QList() << url); auto cursor = KTextEditorHelpers::extractCursor(filterText); if (cursor.isValid()) { IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (doc) { doc->setCursorPosition(cursor); } } return true; } bool ProjectFileData::isExpandable() const { return true; } QList ProjectFileData::highlighting() const { QTextCharFormat boldFormat; boldFormat.setFontWeight(QFont::Bold); QTextCharFormat normalFormat; QString txt = text(); int fileNameLength = m_file.path.lastPathSegment().length(); const QList ret{ 0, txt.length() - fileNameLength, QVariant(normalFormat), txt.length() - fileNameLength, fileNameLength, QVariant(boldFormat), }; return ret; } QWidget* ProjectFileData::expandingWidget() const { const QUrl url = m_file.path.toUrl(); DUChainReadLocker lock; ///Find a du-chain for the document const QList contexts = DUChain::self()->chainsForDocument(url); ///Pick a non-proxy context TopDUContext* chosen = nullptr; for (TopDUContext* ctx : contexts) { if (!(ctx->parsingEnvironmentFile() && ctx->parsingEnvironmentFile()->isProxyContext())) { chosen = ctx; } } if (chosen) { // TODO: show project name, by introducing a generic wrapper widget that supports QuickOpenEmbeddedWidgetInterface return chosen->createNavigationWidget(); } else { auto* ret = new QTextBrowser(); ret->resize(400, 100); ret->setText( QLatin1String("") + i18nc("%1: project name", "Project %1", project()) + QLatin1String("
") + i18n("Not parsed yet") + QLatin1String("
")); return ret; } return nullptr; } QIcon ProjectFileData::icon() const { const QString& iconName = iconNameForUrl(m_file.indexedPath); /** * FIXME: Move this cache into a more central place and reuse it elsewhere. * The project model e.g. could reuse this as well. * * Note: We cache here since otherwise displaying and esp. scrolling * in a large list of quickopen items becomes very slow. */ static QHash iconCache; QHash::const_iterator it = iconCache.constFind(iconName); if (it != iconCache.constEnd()) { return it.value(); } const QPixmap& pixmap = KIconLoader::global()->loadIcon(iconName, KIconLoader::Small); iconCache.insert(iconName, pixmap); return pixmap; } QString ProjectFileData::project() const { const IProject* project = ICore::self()->projectController()->findProjectForUrl(m_file.path.toUrl()); if (project) { return project->name(); } else { return i18n("none"); } } Path ProjectFileData::projectPath() const { return m_file.projectPath; } BaseFileDataProvider::BaseFileDataProvider() { } void BaseFileDataProvider::setFilterText(const QString& text) { int pathLength; KTextEditorHelpers::extractCursor(text, &pathLength); QString path(text.mid(0, pathLength)); if (path.startsWith(QLatin1String("./")) || path.startsWith(QLatin1String("../"))) { // assume we want to filter relative to active document's url IDocument* doc = ICore::self()->documentController()->activeDocument(); if (doc) { path = Path(Path(doc->url()).parent(), path).pathOrUrl(); } } setFilter(path.split(QLatin1Char('/'), QString::SkipEmptyParts)); } uint BaseFileDataProvider::itemCount() const { return filteredItems().count(); } uint BaseFileDataProvider::unfilteredItemCount() const { return items().count(); } QuickOpenDataPointer BaseFileDataProvider::data(uint row) const { return QuickOpenDataPointer(new ProjectFileData(filteredItems().at(row))); } ProjectFileDataProvider::ProjectFileDataProvider() { auto projectController = ICore::self()->projectController(); connect(projectController, &IProjectController::projectClosing, this, &ProjectFileDataProvider::projectClosing); connect(projectController, &IProjectController::projectOpened, this, &ProjectFileDataProvider::projectOpened); - foreach (const auto project, projectController->projects()) { + const auto projects = projectController->projects(); + for (auto* project : projects) { projectOpened(project); } } void ProjectFileDataProvider::projectClosing(IProject* project) { - foreach (ProjectFileItem* file, KDevelop::allFiles(project->projectItem())) { + const auto files = KDevelop::allFiles(project->projectItem()); + for (ProjectFileItem* file : files) { fileRemovedFromSet(file); } } void ProjectFileDataProvider::projectOpened(IProject* project) { const int processAfter = 1000; int processed = 0; - foreach (ProjectFileItem* file, KDevelop::allFiles(project->projectItem())) { + const auto files = KDevelop::allFiles(project->projectItem()); + for (ProjectFileItem* file : files) { fileAddedToSet(file); if (++processed == processAfter) { // prevent UI-lockup when a huge project was imported QApplication::processEvents(); processed = 0; } } connect(project, &IProject::fileAddedToSet, this, &ProjectFileDataProvider::fileAddedToSet); connect(project, &IProject::fileRemovedFromSet, this, &ProjectFileDataProvider::fileRemovedFromSet); } void ProjectFileDataProvider::fileAddedToSet(ProjectFileItem* file) { ProjectFile f; f.projectPath = file->project()->path(); f.path = file->path(); f.indexedPath = file->indexedPath(); f.outsideOfProject = !f.projectPath.isParentOf(f.path); auto it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), f); if (it == m_projectFiles.end() || it->path != f.path) { m_projectFiles.insert(it, f); } } void ProjectFileDataProvider::fileRemovedFromSet(ProjectFileItem* file) { ProjectFile item; item.path = file->path(); // fast-path for non-generated files // NOTE: figuring out whether something is generated is expensive... and since // generated files are rare we apply this two-step algorithm here auto it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), item); if (it != m_projectFiles.end() && !(item < *it)) { m_projectFiles.erase(it); return; } // last try: maybe it was generated item.outsideOfProject = true; it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), item); if (it != m_projectFiles.end() && !(item < *it)) { m_projectFiles.erase(it); return; } } void ProjectFileDataProvider::reset() { clearFilter(); QVector projectFiles = m_projectFiles; const auto& open = openFiles(); for (QVector::iterator it = projectFiles.begin(); it != projectFiles.end(); ) { if (open.contains(it->indexedPath)) { it = projectFiles.erase(it); } else { ++it; } } setItems(projectFiles); } QSet ProjectFileDataProvider::files() const { QSet ret; - foreach (IProject* project, ICore::self()->projectController()->projects()) { + const auto projects = ICore::self()->projectController()->projects(); + for (IProject* project : projects) { ret += project->fileSet(); } return ret - openFiles(); } void OpenFilesDataProvider::reset() { clearFilter(); IProjectController* projCtrl = ICore::self()->projectController(); IDocumentController* docCtrl = ICore::self()->documentController(); const QList& docs = docCtrl->openDocuments(); QVector currentFiles; currentFiles.reserve(docs.size()); for (IDocument* doc : docs) { ProjectFile f; f.path = Path(doc->url()); IProject* project = projCtrl->findProjectForUrl(doc->url()); if (project) { f.projectPath = project->path(); } currentFiles << f; } std::sort(currentFiles.begin(), currentFiles.end()); setItems(currentFiles); } QSet OpenFilesDataProvider::files() const { return openFiles(); } diff --git a/plugins/quickopen/projectitemquickopen.cpp b/plugins/quickopen/projectitemquickopen.cpp index 0387978cdd..027d22a760 100644 --- a/plugins/quickopen/projectitemquickopen.cpp +++ b/plugins/quickopen/projectitemquickopen.cpp @@ -1,376 +1,377 @@ /* This file is part of the KDE libraries Copyright (C) 2007 David Nolden 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 "projectitemquickopen.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { struct SubstringCache { explicit SubstringCache(const QString& string = QString()) : substring(string) { } inline int containedIn(const Identifier& id) const { int index = id.index(); QHash::const_iterator it = cache.constFind(index); if (it != cache.constEnd()) { return *it; } const QString idStr = id.identifier().str(); int result = idStr.lastIndexOf(substring, -1, Qt::CaseInsensitive); if (result < 0 && !idStr.isEmpty() && !substring.isEmpty()) { // no match; try abbreviations result = matchesAbbreviation(idStr.midRef(0), substring) ? 0 : -1; } //here we shift the values if the matched string is bigger than the substring, //so closer matches will appear first if (result >= 0) { result = result + (idStr.size() - substring.size()); } cache[index] = result; return result; } QString substring; mutable QHash cache; }; struct ClosestMatchToText { explicit ClosestMatchToText(const QHash& _cache) : cache(_cache) { } /** @brief Calculates the distance to two pre-filtered match items * * @param a The CodeModelView witch represents the first item to be tested * @param b The CodeModelView witch represents the second item to be tested * * @b */ inline bool operator()(const CodeModelViewItem& a, const CodeModelViewItem& b) const { const int height_a = cache.value(a.m_id.index(), -1); const int height_b = cache.value(b.m_id.index(), -1); Q_ASSERT(height_a != -1); Q_ASSERT(height_b != -1); if (height_a == height_b) { // stable sorting for equal items based on index // TODO: fast string-based sorting in such cases? return a.m_id.index() < b.m_id.index(); } return height_a < height_b; } private: const QHash& cache; }; Path findProjectForForPath(const IndexedString& path) { const auto model = ICore::self()->projectController()->projectModel(); const auto item = model->itemForPath(path); return item ? item->project()->path() : Path(); } uint addedItems(const AddedItems& items) { uint add = 0; for(auto it = items.constBegin(); it != items.constEnd(); ++it) { add += it.value().count(); } return add; } } ProjectItemDataProvider::ProjectItemDataProvider(KDevelop::IQuickOpen* quickopen) : m_itemTypes(NoItems) , m_quickopen(quickopen) , m_addedItemsCountCache([this]() { return addedItems(m_addedItems); }) { } void ProjectItemDataProvider::setFilterText(const QString& text) { m_addedItems.clear(); m_addedItemsCountCache.markDirty(); QStringList search(text.split(QStringLiteral("::"), QString::SkipEmptyParts)); for (int a = 0; a < search.count(); ++a) { if (search[a].endsWith(QLatin1Char(':'))) { //Don't get confused while the :: is being typed search[a].chop(1); } } if (!search.isEmpty() && search.back().endsWith(QLatin1Char('('))) { search.back().chop(1); } if (text.isEmpty() || search.isEmpty()) { m_filteredItems = m_currentItems; return; } KDevVarLengthArray cache; - foreach (const QString& searchPart, search) { + for (const QString& searchPart : qAsConst(search)) { cache.append(SubstringCache(searchPart)); } if (!text.startsWith(m_currentFilter)) { m_filteredItems = m_currentItems; } m_currentFilter = text; const QVector oldFiltered = m_filteredItems; QHash heights; m_filteredItems.clear(); for (const CodeModelViewItem& item : oldFiltered) { const QualifiedIdentifier& currentId = item.m_id; int last_pos = currentId.count() - 1; int current_height = 0; int distance = 0; //iter over each search item from last to first //this makes easier to calculate the distance based on where we hit the result or nothing //Iterating from the last item to the first is more efficient, as we want to match the //class/function name, which is the last item on the search fields and on the identifier. for (int b = search.count() - 1; b >= 0; --b) { //iter over each id for the current identifier, from last to first for (; last_pos >= 0; --last_pos, distance++) { // the more distant we are from the class definition, the less priority it will have current_height += distance * 10000; int result; //if the current search item is contained on the current identifier if ((result = cache[b].containedIn(currentId.at(last_pos))) >= 0) { //when we find a hit, whe add the distance to the searched word. //so the closest item will be displayed first current_height += result; if (b == 0) { heights[currentId.index()] = current_height; m_filteredItems << item; } break; } } } } //then, for the last part, we use the already built cache to sort the items according with their distance std::sort(m_filteredItems.begin(), m_filteredItems.end(), ClosestMatchToText(heights)); } KDevelop::QuickOpenDataPointer ProjectItemDataProvider::data(uint pos) const { //Check whether this position falls into an appended item-list, else apply the offset uint filteredItemOffset = 0; for (AddedItems::const_iterator it = m_addedItems.constBegin(); it != m_addedItems.constEnd(); ++it) { int offsetInAppended = pos - (it.key() + 1); if (offsetInAppended >= 0 && offsetInAppended < it.value().count()) { return it.value()[offsetInAppended]; } if (it.key() >= pos) { break; } filteredItemOffset += it.value().count(); } const uint a = pos - filteredItemOffset; if (a > ( uint )m_filteredItems.size()) { return KDevelop::QuickOpenDataPointer(); } const auto& filteredItem = m_filteredItems[a]; QList ret; KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* ctx = DUChainUtils::standardContextForUrl(filteredItem.m_file.toUrl()); if (ctx) { QList decls = ctx->findDeclarations(filteredItem.m_id, CursorInRevision::invalid(), AbstractType::Ptr(), nullptr, DUContext::DirectQualifiedLookup); //Filter out forward-declarations or duplicate imported declarations - foreach (Declaration* decl, decls) { + const auto unfilteredDecls = decls; + for (Declaration* decl : unfilteredDecls) { bool filter = false; if (decls.size() > 1 && decl->isForwardDeclaration()) { filter = true; } else if (decl->url() != filteredItem.m_file && m_files.contains(decl->url())) { filter = true; } if (filter) { decls.removeOne(decl); } } ret.reserve(ret.size() + decls.size()); - foreach (Declaration* decl, decls) { + for (Declaration* decl : qAsConst(decls)) { DUChainItem item; item.m_item = decl; item.m_text = decl->qualifiedIdentifier().toString(); item.m_projectPath = findProjectForForPath(filteredItem.m_file); ret << QuickOpenDataPointer(new DUChainItemData(item)); } if (decls.isEmpty()) { DUChainItem item; item.m_text = filteredItem.m_id.toString(); item.m_projectPath = findProjectForForPath(filteredItem.m_file); ret << QuickOpenDataPointer(new DUChainItemData(item)); } } else { qCDebug(PLUGIN_QUICKOPEN) << "Could not find standard-context"; } if (!ret.isEmpty()) { QList append = ret.mid(1); if (!append.isEmpty()) { m_addedItemsCountCache.markDirty(); AddedItems addMap; for (AddedItems::iterator it = m_addedItems.begin(); it != m_addedItems.end(); ) { if (it.key() == pos) { //There already is appended data stored, nothing to do return ret.first(); } else if (it.key() > pos) { addMap[it.key() + append.count()] = it.value(); it = m_addedItems.erase(it); } else { ++it; } } m_addedItems.insert(pos, append); for (AddedItems::const_iterator it = addMap.constBegin(); it != addMap.constEnd(); ++it) { m_addedItems.insert(it.key(), it.value()); } } return ret.first(); } else { return KDevelop::QuickOpenDataPointer(); } } void ProjectItemDataProvider::reset() { m_files = m_quickopen->fileSet(); m_currentItems.clear(); m_addedItems.clear(); m_addedItemsCountCache.markDirty(); KDevelop::DUChainReadLocker lock(DUChain::lock()); - foreach (const IndexedString& u, m_files) { + for (const IndexedString& u : qAsConst(m_files)) { uint count; const KDevelop::CodeModelItem* items; CodeModel::self().items(u, count, items); for (uint a = 0; a < count; ++a) { if (!items[a].id.isValid() || items[a].kind & CodeModelItem::ForwardDeclaration) { continue; } if (((m_itemTypes & Classes) && (items[a].kind & CodeModelItem::Class)) || ((m_itemTypes & Functions) && (items[a].kind & CodeModelItem::Function))) { QualifiedIdentifier id = items[a].id.identifier(); if (id.isEmpty() || id.at(0).identifier().isEmpty()) { // id.isEmpty() not always hit when .toString() is actually empty... // anyhow, this makes sure that we don't show duchain items without // any name that could be searched for. This happens e.g. in the c++ // plugin for anonymous structs or sometimes for declarations in macro // expressions continue; } m_currentItems << CodeModelViewItem(u, id); } } } m_filteredItems = m_currentItems; m_currentFilter.clear(); } uint ProjectItemDataProvider::itemCount() const { return m_filteredItems.count() + m_addedItemsCountCache.cachedResult(); } uint ProjectItemDataProvider::unfilteredItemCount() const { return m_currentItems.count() + m_addedItemsCountCache.cachedResult(); } QStringList ProjectItemDataProvider::supportedItemTypes() { const QStringList ret{ i18n("Classes"), i18n("Functions"), }; return ret; } void ProjectItemDataProvider::enableData(const QStringList& items, const QStringList& scopes) { //FIXME: property support different scopes if (scopes.contains(i18n("Project"))) { m_itemTypes = NoItems; if (items.contains(i18n("Classes"))) { m_itemTypes = ( ItemTypes )(m_itemTypes | Classes); } if (items.contains(i18n("Functions"))) { m_itemTypes = ( ItemTypes )(m_itemTypes | Functions); } } else { m_itemTypes = NoItems; } } diff --git a/plugins/quickopen/quickopenmodel.cpp b/plugins/quickopen/quickopenmodel.cpp index f008101255..aa153d9e32 100644 --- a/plugins/quickopen/quickopenmodel.cpp +++ b/plugins/quickopen/quickopenmodel.cpp @@ -1,493 +1,491 @@ /* This file is part of the KDE libraries Copyright (C) 2007 David Nolden 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 "quickopenmodel.h" #include "debug.h" #include #include #include #include #include "expandingtree/expandingtree.h" #include "projectfilequickopen.h" #include "duchainitemquickopen.h" #define QUICKOPEN_USE_ITEM_CACHING using namespace KDevelop; QuickOpenModel::QuickOpenModel(QWidget* parent) : ExpandingWidgetModel(parent) , m_treeView(nullptr) , m_expandingWidgetHeightIncrease(0) , m_resetBehindRow(0) { m_resetTimer = new QTimer(this); m_resetTimer->setSingleShot(true); m_resetTimer->setInterval(0); connect(m_resetTimer, &QTimer::timeout, this, &QuickOpenModel::resetTimer); } void QuickOpenModel::setExpandingWidgetHeightIncrease(int pixels) { m_expandingWidgetHeightIncrease = pixels; } QStringList QuickOpenModel::allScopes() const { QStringList scopes; for (const ProviderEntry& provider : m_providers) { - foreach (const QString& scope, provider.scopes) { + for (const QString& scope : provider.scopes) { if (!scopes.contains(scope)) { scopes << scope; } } } return scopes; } QStringList QuickOpenModel::allTypes() const { QSet types; for (const ProviderEntry& provider : m_providers) { types += provider.types; } return types.toList(); } void QuickOpenModel::registerProvider(const QStringList& scopes, const QStringList& types, KDevelop::QuickOpenDataProviderBase* provider) { ProviderEntry e; e.scopes = QSet::fromList(scopes); e.types = QSet::fromList(types); e.provider = provider; m_providers << e; //.insert( types, e ); connect(provider, &QuickOpenDataProviderBase::destroyed, this, &QuickOpenModel::destroyed); restart(true); } bool QuickOpenModel::removeProvider(KDevelop::QuickOpenDataProviderBase* provider) { bool ret = false; for (ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it) { if ((*it).provider == provider) { m_providers.erase(it); disconnect(provider, &QuickOpenDataProviderBase::destroyed, this, &QuickOpenModel::destroyed); ret = true; break; } } restart(true); return ret; } void QuickOpenModel::enableProviders(const QStringList& _items, const QStringList& _scopes) { QSet items = QSet::fromList(_items); QSet scopes = QSet::fromList(_scopes); if (m_enabledItems == items && m_enabledScopes == scopes && !items.isEmpty() && !scopes.isEmpty()) { return; } m_enabledItems = items; m_enabledScopes = scopes; qCDebug(PLUGIN_QUICKOPEN) << "params " << items << " " << scopes; //We use 2 iterations here: In the first iteration, all providers that implement QuickOpenFileSetInterface are initialized, then the other ones. //The reason is that the second group can refer to the first one. for (ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it) { if (!qobject_cast((*it).provider)) { continue; } qCDebug(PLUGIN_QUICKOPEN) << "comparing" << (*it).scopes << (*it).types; if ((scopes.isEmpty() || !(scopes & (*it).scopes).isEmpty()) && (!(items & (*it).types).isEmpty() || items.isEmpty())) { qCDebug(PLUGIN_QUICKOPEN) << "enabling " << (*it).types << " " << (*it).scopes; (*it).enabled = true; (*it).provider->enableData(_items, _scopes); } else { qCDebug(PLUGIN_QUICKOPEN) << "disabling " << (*it).types << " " << (*it).scopes; (*it).enabled = false; if ((scopes.isEmpty() || !(scopes & (*it).scopes).isEmpty())) { (*it).provider->enableData(_items, _scopes); //The provider may still provide files } } } for (ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it) { if (qobject_cast((*it).provider)) { continue; } qCDebug(PLUGIN_QUICKOPEN) << "comparing" << (*it).scopes << (*it).types; if ((scopes.isEmpty() || !(scopes & (*it).scopes).isEmpty()) && (!(items & (*it).types).isEmpty() || items.isEmpty())) { qCDebug(PLUGIN_QUICKOPEN) << "enabling " << (*it).types << " " << (*it).scopes; (*it).enabled = true; (*it).provider->enableData(_items, _scopes); } else { qCDebug(PLUGIN_QUICKOPEN) << "disabling " << (*it).types << " " << (*it).scopes; (*it).enabled = false; } } restart(true); } void QuickOpenModel::textChanged(const QString& str) { if (m_filterText == str) { return; } beginResetModel(); m_filterText = str; - foreach (const ProviderEntry& provider, m_providers) { + for (const ProviderEntry& provider : qAsConst(m_providers)) { if (provider.enabled) { provider.provider->setFilterText(str); } } m_cachedData.clear(); clearExpanding(); //Get the 50 first items, so the data-providers notice changes without ui-glitches due to resetting for (int a = 0; a < 50 && a < rowCount(QModelIndex()); ++a) { getItem(a, true); } endResetModel(); } void QuickOpenModel::restart(bool keepFilterText) { // make sure we do not restart recursively which could lead to // recursive loading of provider plugins e.g. (happened for the cpp plugin) QMetaObject::invokeMethod(this, "restart_internal", Qt::QueuedConnection, Q_ARG(bool, keepFilterText)); } void QuickOpenModel::restart_internal(bool keepFilterText) { if (!keepFilterText) { m_filterText.clear(); } - bool anyEnabled = false; - - foreach (const ProviderEntry& e, m_providers) { - anyEnabled |= e.enabled; - } + bool anyEnabled = std::any_of(m_providers.constBegin(), m_providers.constEnd(), [](const ProviderEntry& e) { + return e.enabled; + }); if (!anyEnabled) { return; } - foreach (const ProviderEntry& provider, m_providers) { + for (const ProviderEntry& provider : qAsConst(m_providers)) { if (!qobject_cast(provider.provider)) { continue; } ///Always reset providers that implement QuickOpenFileSetInterface and have some matchign scopes, because they may be needed by other providers. if (m_enabledScopes.isEmpty() || !(m_enabledScopes & provider.scopes).isEmpty()) { provider.provider->reset(); } } - foreach (const ProviderEntry& provider, m_providers) { + for (const ProviderEntry& provider : qAsConst(m_providers)) { if (qobject_cast(provider.provider)) { continue; } if (provider.enabled && provider.provider) { provider.provider->reset(); } } if (keepFilterText) { textChanged(m_filterText); } else { beginResetModel(); m_cachedData.clear(); clearExpanding(); endResetModel(); } } void QuickOpenModel::destroyed(QObject* obj) { removeProvider(static_cast(obj)); } QModelIndex QuickOpenModel::index(int row, int column, const QModelIndex& /*parent*/) const { if (column >= columnCount() || row >= rowCount(QModelIndex())) { return QModelIndex(); } if (row < 0 || column < 0) { return QModelIndex(); } return createIndex(row, column); } QModelIndex QuickOpenModel::parent(const QModelIndex&) const { return QModelIndex(); } int QuickOpenModel::rowCount(const QModelIndex& i) const { if (i.isValid()) { return 0; } int count = 0; for (const ProviderEntry& provider : m_providers) { if (provider.enabled) { count += provider.provider->itemCount(); } } return count; } int QuickOpenModel::unfilteredRowCount() const { int count = 0; for (const ProviderEntry& provider : m_providers) { if (provider.enabled) { count += provider.provider->unfilteredItemCount(); } } return count; } int QuickOpenModel::columnCount() const { return 2; } int QuickOpenModel::columnCount(const QModelIndex& index) const { if (index.parent().isValid()) { return 0; } else { return columnCount(); } } QVariant QuickOpenModel::data(const QModelIndex& index, int role) const { QuickOpenDataPointer d = getItem(index.row()); if (!d) { return QVariant(); } switch (role) { case KTextEditor::CodeCompletionModel::ItemSelected: { QString desc = d->htmlDescription(); if (desc.isEmpty()) { return QVariant(); } else { return desc; } } case KTextEditor::CodeCompletionModel::IsExpandable: return d->isExpandable(); case KTextEditor::CodeCompletionModel::ExpandingWidget: { QVariant v; QWidget* w = d->expandingWidget(); if (w && m_expandingWidgetHeightIncrease) { w->resize(w->width(), w->height() + m_expandingWidgetHeightIncrease); } v.setValue(w); return v; } case ExpandingTree::ProjectPathRole: // TODO: put this into the QuickOpenDataBase API // we cannot do this in 5.0, cannot change ABI if (auto projectFile = dynamic_cast(d.constData())) { return QVariant::fromValue(projectFile->projectPath()); } else if (auto duchainItem = dynamic_cast(d.constData())) { return QVariant::fromValue(duchainItem->projectPath()); } } if (index.column() == 1) { //This column contains the actual content switch (role) { case Qt::DecorationRole: return d->icon(); case Qt::DisplayRole: return d->text(); case KTextEditor::CodeCompletionModel::HighlightingMethod: return KTextEditor::CodeCompletionModel::CustomHighlighting; case KTextEditor::CodeCompletionModel::CustomHighlight: return d->highlighting(); } } else if (index.column() == 0) { //This column only contains the expanded/not expanded icon switch (role) { case Qt::DecorationRole: { if (isExpandable(index)) { //Show the expanded/unexpanded handles cacheIcons(); if (isExpanded(index)) { return m_expandedIcon; } else { return m_collapsedIcon; } } } } } return ExpandingWidgetModel::data(index, role); } void QuickOpenModel::resetTimer() { int currentRow = treeView() ? mapToSource(treeView()->currentIndex()).row() : -1; beginResetModel(); //Remove all cached data behind row m_resetBehindRow for (DataList::iterator it = m_cachedData.begin(); it != m_cachedData.end(); ) { if (it.key() > m_resetBehindRow) { it = m_cachedData.erase(it); } else { ++it; } } endResetModel(); if (currentRow != -1) { treeView()->setCurrentIndex(mapFromSource(index(currentRow, 0, QModelIndex()))); //Preserve the current index } m_resetBehindRow = 0; } QuickOpenDataPointer QuickOpenModel::getItem(int row, bool noReset) const { ///@todo mix all the models alphabetically here. For now, they are simply ordered. ///@todo Deal with unexpected item-counts, like for example in the case of overloaded function-declarations #ifdef QUICKOPEN_USE_ITEM_CACHING const auto dataIt = m_cachedData.constFind(row); if (dataIt != m_cachedData.constEnd()) { return *dataIt; } #endif int rowOffset = 0; Q_ASSERT(row < rowCount(QModelIndex())); for (const ProviderEntry& provider : m_providers) { if (!provider.enabled) { continue; } uint itemCount = provider.provider->itemCount(); if (( uint )row < itemCount) { QuickOpenDataPointer item = provider.provider->data(row); if (!noReset && provider.provider->itemCount() != itemCount) { qCDebug(PLUGIN_QUICKOPEN) << "item-count in provider has changed, resetting model"; m_resetTimer->start(); m_resetBehindRow = rowOffset + row; //Don't reset everything, only everything behind this position } #ifdef QUICKOPEN_USE_ITEM_CACHING m_cachedData[row + rowOffset] = item; #endif return item; } else { row -= provider.provider->itemCount(); rowOffset += provider.provider->itemCount(); } } // qWarning() << "No item for row " << row; return QuickOpenDataPointer(); } QSet QuickOpenModel::fileSet() const { QSet merged; for (const ProviderEntry& provider : m_providers) { if (m_enabledScopes.isEmpty() || !(m_enabledScopes & provider.scopes).isEmpty()) { if (auto* iface = qobject_cast(provider.provider)) { QSet ifiles = iface->files(); //qCDebug(PLUGIN_QUICKOPEN) << "got file-list with" << ifiles.count() << "entries from data-provider" << typeid(*iface).name(); merged += ifiles; } } } return merged; } QTreeView* QuickOpenModel::treeView() const { return m_treeView; } bool QuickOpenModel::indexIsItem(const QModelIndex& index) const { Q_ASSERT(index.model() == this); Q_UNUSED(index); return true; } void QuickOpenModel::setTreeView(QTreeView* view) { m_treeView = view; } int QuickOpenModel::contextMatchQuality(const QModelIndex& /*index*/) const { return -1; } bool QuickOpenModel::execute(const QModelIndex& index, QString& filterText) { qCDebug(PLUGIN_QUICKOPEN) << "executing model"; if (!index.isValid()) { qCWarning(PLUGIN_QUICKOPEN) << "Invalid index executed"; return false; } QuickOpenDataPointer item = getItem(index.row()); if (item) { return item->execute(filterText); } else { qCWarning(PLUGIN_QUICKOPEN) << "Got no item for row " << index.row() << " "; } return false; } diff --git a/plugins/quickopen/quickopenplugin.cpp b/plugins/quickopen/quickopenplugin.cpp index 7794fb5ee2..a997ab3a7a 100644 --- a/plugins/quickopen/quickopenplugin.cpp +++ b/plugins/quickopen/quickopenplugin.cpp @@ -1,1173 +1,1173 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "quickopenplugin.h" #include "quickopenwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "quickopenmodel.h" #include "projectfilequickopen.h" #include "projectitemquickopen.h" #include "declarationlistquickopen.h" #include "documentationquickopenprovider.h" #include "actionsquickopenprovider.h" #include "debug.h" #include #include #include using namespace KDevelop; const bool noHtmlDestriptionInOutline = true; class QuickOpenWidgetCreator { public: virtual ~QuickOpenWidgetCreator() { } virtual QuickOpenWidget* createWidget() = 0; virtual QString objectNameForLine() = 0; virtual void widgetShown() { } }; class StandardQuickOpenWidgetCreator : public QuickOpenWidgetCreator { public: StandardQuickOpenWidgetCreator(const QStringList& items, const QStringList& scopes) : m_items(items) , m_scopes(scopes) { } QString objectNameForLine() override { return QStringLiteral("Quickopen"); } void setItems(const QStringList& scopes, const QStringList& items) { m_scopes = scopes; m_items = items; } QuickOpenWidget* createWidget() override { QStringList useItems = m_items; if (useItems.isEmpty()) { useItems = QuickOpenPlugin::self()->lastUsedItems; } QStringList useScopes = m_scopes; if (useScopes.isEmpty()) { useScopes = QuickOpenPlugin::self()->lastUsedScopes; } return new QuickOpenWidget(i18n("Quick Open"), QuickOpenPlugin::self()->m_model, QuickOpenPlugin::self()->lastUsedItems, useScopes, false, true); } QStringList m_items; QStringList m_scopes; }; class OutlineFilter : public DUChainUtils::DUChainItemFilter { public: enum OutlineMode { Functions, FunctionsAndClasses }; explicit OutlineFilter(QVector& _items, OutlineMode _mode = FunctionsAndClasses) : items(_items) , mode(_mode) { } bool accept(Declaration* decl) override { if (decl->range().isEmpty()) { return false; } bool collectable = mode == Functions ? decl->isFunctionDeclaration() : (decl->isFunctionDeclaration() || (decl->internalContext() && decl->internalContext()->type() == DUContext::Class)); if (collectable) { DUChainItem item; item.m_item = IndexedDeclaration(decl); item.m_text = decl->toString(); items << item; return true; } else { return false; } } bool accept(DUContext* ctx) override { if (ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper) { return true; } else { return false; } } QVector& items; OutlineMode mode; }; K_PLUGIN_FACTORY_WITH_JSON(KDevQuickOpenFactory, "kdevquickopen.json", registerPlugin(); ) Declaration * cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } KDevelop::DUChainReadLocker lock(DUChain::lock()); return DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); } ///The first definition that belongs to a context that surrounds the current cursor Declaration* cursorContextDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* ctx = DUChainUtils::standardContextForUrl(view->document()->url()); if (!ctx) { return nullptr; } KTextEditor::Cursor cursor(view->cursorPosition()); DUContext* subCtx = ctx->findContext(ctx->transformToLocalRevision(cursor)); while (subCtx && !subCtx->owner()) subCtx = subCtx->parentContext(); Declaration* definition = nullptr; if (!subCtx || !subCtx->owner()) { definition = DUChainUtils::declarationInLine(cursor, ctx); } else { definition = subCtx->owner(); } if (!definition) { return nullptr; } return definition; } //Returns only the name, no template-parameters or scope QString cursorItemText() { KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { return QString(); } IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { return QString(); } TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return QString(); } AbstractType::Ptr t = decl->abstractType(); auto* idType = dynamic_cast(t.data()); if (idType && idType->declaration(context)) { decl = idType->declaration(context); } if (!decl->qualifiedIdentifier().isEmpty()) { return decl->qualifiedIdentifier().last().identifier().str(); } return QString(); } QuickOpenLineEdit* QuickOpenPlugin::createQuickOpenLineWidget() { return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(QStringList(), QStringList())); } QuickOpenLineEdit* QuickOpenPlugin::quickOpenLine(const QString& name) { const QList lines = ICore::self()->uiController()->activeMainWindow()->findChildren(name); for (QuickOpenLineEdit* line : lines) { if (line->isVisible()) { return line; } } return nullptr; } static QuickOpenPlugin* staticQuickOpenPlugin = nullptr; QuickOpenPlugin* QuickOpenPlugin::self() { return staticQuickOpenPlugin; } void QuickOpenPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevquickopen.rc"); QAction* quickOpen = actions.addAction(QStringLiteral("quick_open")); quickOpen->setText(i18n("&Quick Open")); quickOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen"))); actions.setDefaultShortcut(quickOpen, Qt::CTRL | Qt::ALT | Qt::Key_Q); connect(quickOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpen); QAction* quickOpenFile = actions.addAction(QStringLiteral("quick_open_file")); quickOpenFile->setText(i18n("Quick Open &File")); quickOpenFile->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file"))); actions.setDefaultShortcut(quickOpenFile, Qt::CTRL | Qt::ALT | Qt::Key_O); connect(quickOpenFile, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFile); QAction* quickOpenClass = actions.addAction(QStringLiteral("quick_open_class")); quickOpenClass->setText(i18n("Quick Open &Class")); quickOpenClass->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-class"))); actions.setDefaultShortcut(quickOpenClass, Qt::CTRL | Qt::ALT | Qt::Key_C); connect(quickOpenClass, &QAction::triggered, this, &QuickOpenPlugin::quickOpenClass); QAction* quickOpenFunction = actions.addAction(QStringLiteral("quick_open_function")); quickOpenFunction->setText(i18n("Quick Open &Function")); quickOpenFunction->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-function"))); actions.setDefaultShortcut(quickOpenFunction, Qt::CTRL | Qt::ALT | Qt::Key_M); connect(quickOpenFunction, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFunction); QAction* quickOpenAlreadyOpen = actions.addAction(QStringLiteral("quick_open_already_open")); quickOpenAlreadyOpen->setText(i18n("Quick Open &Already Open File")); quickOpenAlreadyOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file"))); connect(quickOpenAlreadyOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpenOpenFile); QAction* quickOpenDocumentation = actions.addAction(QStringLiteral("quick_open_documentation")); quickOpenDocumentation->setText(i18n("Quick Open &Documentation")); quickOpenDocumentation->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-documentation"))); actions.setDefaultShortcut(quickOpenDocumentation, Qt::CTRL | Qt::ALT | Qt::Key_D); connect(quickOpenDocumentation, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDocumentation); QAction* quickOpenActions = actions.addAction(QStringLiteral("quick_open_actions")); quickOpenActions->setText(i18n("Quick Open &Actions")); actions.setDefaultShortcut(quickOpenActions, Qt::CTRL | Qt::ALT | Qt::Key_A); connect(quickOpenActions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenActions); m_quickOpenDeclaration = actions.addAction(QStringLiteral("quick_open_jump_declaration")); m_quickOpenDeclaration->setText(i18n("Jump to Declaration")); m_quickOpenDeclaration->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-declaration"))); actions.setDefaultShortcut(m_quickOpenDeclaration, Qt::CTRL | Qt::Key_Period); connect(m_quickOpenDeclaration, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDeclaration, Qt::QueuedConnection); m_quickOpenDefinition = actions.addAction(QStringLiteral("quick_open_jump_definition")); m_quickOpenDefinition->setText(i18n("Jump to Definition")); m_quickOpenDefinition->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-definition"))); actions.setDefaultShortcut(m_quickOpenDefinition, Qt::CTRL | Qt::Key_Comma); connect(m_quickOpenDefinition, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDefinition, Qt::QueuedConnection); auto* quickOpenLine = new QWidgetAction(this); quickOpenLine->setText(i18n("Embedded Quick Open")); // actions.setDefaultShortcut( quickOpenLine, Qt::CTRL | Qt::ALT | Qt::Key_E ); // connect(quickOpenLine, SIGNAL(triggered(bool)), this, SLOT(quickOpenLine(bool))); quickOpenLine->setDefaultWidget(createQuickOpenLineWidget()); actions.addAction(QStringLiteral("quick_open_line"), quickOpenLine); QAction* quickOpenNextFunction = actions.addAction(QStringLiteral("quick_open_next_function")); quickOpenNextFunction->setText(i18n("Next Function")); actions.setDefaultShortcut(quickOpenNextFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageDown); connect(quickOpenNextFunction, &QAction::triggered, this, &QuickOpenPlugin::nextFunction); QAction* quickOpenPrevFunction = actions.addAction(QStringLiteral("quick_open_prev_function")); quickOpenPrevFunction->setText(i18n("Previous Function")); actions.setDefaultShortcut(quickOpenPrevFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageUp); connect(quickOpenPrevFunction, &QAction::triggered, this, &QuickOpenPlugin::previousFunction); QAction* quickOpenNavigateFunctions = actions.addAction(QStringLiteral("quick_open_outline")); quickOpenNavigateFunctions->setText(i18n("Outline")); actions.setDefaultShortcut(quickOpenNavigateFunctions, Qt::CTRL | Qt::ALT | Qt::Key_N); connect(quickOpenNavigateFunctions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenNavigateFunctions); } QuickOpenPlugin::QuickOpenPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevquickopen"), parent) { staticQuickOpenPlugin = this; m_model = new QuickOpenModel(nullptr); KConfigGroup quickopengrp = KSharedConfig::openConfig()->group("QuickOpen"); lastUsedScopes = quickopengrp.readEntry("SelectedScopes", QStringList{ i18n("Project"), i18n("Includes"), i18n("Includers"), i18n("Currently Open")}); lastUsedItems = quickopengrp.readEntry("SelectedItems", QStringList()); { m_openFilesData = new OpenFilesDataProvider(); QStringList scopes, items; scopes << i18n("Currently Open"); items << i18n("Files"); m_model->registerProvider(scopes, items, m_openFilesData); } { m_projectFileData = new ProjectFileDataProvider(); QStringList scopes, items; scopes << i18n("Project"); items << i18n("Files"); m_model->registerProvider(scopes, items, m_projectFileData); } { m_projectItemData = new ProjectItemDataProvider(this); QStringList scopes, items; scopes << i18n("Project"); items << ProjectItemDataProvider::supportedItemTypes(); m_model->registerProvider(scopes, items, m_projectItemData); } { m_documentationItemData = new DocumentationQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Documentation"); m_model->registerProvider(scopes, items, m_documentationItemData); } { m_actionsItemData = new ActionsQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Actions"); m_model->registerProvider(scopes, items, m_actionsItemData); } } QuickOpenPlugin::~QuickOpenPlugin() { freeModel(); delete m_model; delete m_projectFileData; delete m_projectItemData; delete m_openFilesData; delete m_documentationItemData; delete m_actionsItemData; } void QuickOpenPlugin::unload() { } ContextMenuExtension QuickOpenPlugin::contextMenuExtension(Context* context, QWidget* parent) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension(context, parent); auto* codeContext = dynamic_cast(context); if (!codeContext) { return menuExt; } DUChainReadLocker readLock; Declaration* decl(codeContext->declaration().data()); if (decl) { const bool isDef = FunctionDefinition::definition(decl); if (codeContext->use().isValid() || !isDef) { menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDeclaration); } if (isDef) { menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDefinition); } } return menuExt; } void QuickOpenPlugin::showQuickOpen(const QStringList& items) { if (!freeModel()) { return; } QStringList initialItems = items; QStringList useScopes = lastUsedScopes; if (!useScopes.contains(i18n("Currently Open"))) { useScopes << i18n("Currently Open"); } showQuickOpenWidget(initialItems, useScopes, false); } void QuickOpenPlugin::showQuickOpen(ModelTypes modes) { if (!freeModel()) { return; } QStringList initialItems; if (modes & Files || modes & OpenFiles) { initialItems << i18n("Files"); } if (modes & Functions) { initialItems << i18n("Functions"); } if (modes & Classes) { initialItems << i18n("Classes"); } QStringList useScopes; if (modes != OpenFiles) { useScopes = lastUsedScopes; } if ((modes & OpenFiles) && !useScopes.contains(i18n("Currently Open"))) { useScopes << i18n("Currently Open"); } bool preselectText = (!(modes & Files) || modes == QuickOpenPlugin::All); showQuickOpenWidget(initialItems, useScopes, preselectText); } void QuickOpenPlugin::showQuickOpenWidget(const QStringList& items, const QStringList& scopes, bool preselectText) { QuickOpenWidgetDialog* dialog = new QuickOpenWidgetDialog(i18n("Quick Open"), m_model, items, scopes); m_currentWidgetHandler = dialog; if (preselectText) { KDevelop::IDocument* currentDoc = core()->documentController()->activeDocument(); if (currentDoc && currentDoc->isTextDocument()) { QString preselected = currentDoc->textSelection().isEmpty() ? currentDoc->textWord() : currentDoc->textDocument()->text(currentDoc->textSelection()); dialog->widget()->setPreselectedText(preselected); } } connect(dialog->widget(), &QuickOpenWidget::scopesChanged, this, &QuickOpenPlugin::storeScopes); //Not connecting itemsChanged to storeItems, as showQuickOpen doesn't use lastUsedItems and so shouldn't store item changes //connect( dialog->widget(), SIGNAL(itemsChanged(QStringList)), this, SLOT(storeItems(QStringList)) ); dialog->widget()->ui.itemsButton->setEnabled(false); if (quickOpenLine()) { quickOpenLine()->showWithWidget(dialog->widget()); dialog->deleteLater(); } else { dialog->run(); } } void QuickOpenPlugin::storeScopes(const QStringList& scopes) { lastUsedScopes = scopes; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry("SelectedScopes", scopes); } void QuickOpenPlugin::storeItems(const QStringList& items) { lastUsedItems = items; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry("SelectedItems", items); } void QuickOpenPlugin::quickOpen() { if (quickOpenLine()) { //Same as clicking on Quick Open quickOpenLine()->setFocus(); } else { showQuickOpen(All); } } void QuickOpenPlugin::quickOpenFile() { showQuickOpen(( ModelTypes )(Files | OpenFiles)); } void QuickOpenPlugin::quickOpenFunction() { showQuickOpen(Functions); } void QuickOpenPlugin::quickOpenClass() { showQuickOpen(Classes); } void QuickOpenPlugin::quickOpenOpenFile() { showQuickOpen(OpenFiles); } void QuickOpenPlugin::quickOpenDocumentation() { showQuickOpenWidget(QStringList(i18n("Documentation")), QStringList(i18n("Includes")), true); } void QuickOpenPlugin::quickOpenActions() { showQuickOpenWidget(QStringList(i18n("Actions")), QStringList(i18n("Includes")), true); } QSet QuickOpenPlugin::fileSet() const { return m_model->fileSet(); } void QuickOpenPlugin::registerProvider(const QStringList& scope, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider) { m_model->registerProvider(scope, type, provider); } bool QuickOpenPlugin::removeProvider(KDevelop::QuickOpenDataProviderBase* provider) { m_model->removeProvider(provider); return true; } void QuickOpenPlugin::quickOpenDeclaration() { if (jumpToSpecialObject()) { return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } decl->activateSpecialization(); IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if (u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } QWidget* QuickOpenPlugin::specialObjectNavigationWidget() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); for (const auto language : languages) { QWidget* w = language->specialLanguageObjectNavigationWidget(url, view->cursorPosition()).first; if (w) { return w; } } return nullptr; } QPair QuickOpenPlugin::specialObjectJumpPosition() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return qMakePair(QUrl(), KTextEditor::Cursor()); } QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); for (const auto language : languages) { QPair pos = language->specialLanguageObjectJumpCursor(url, KTextEditor::Cursor(view->cursorPosition())); if (pos.second.isValid()) { return pos; } } return qMakePair(QUrl(), KTextEditor::Cursor::invalid()); } bool QuickOpenPlugin::jumpToSpecialObject() { QPair pos = specialObjectJumpPosition(); if (pos.second.isValid()) { if (pos.first.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for special language object"; return false; } ICore::self()->documentController()->openDocument(pos.first, pos.second); return true; } return false; } void QuickOpenPlugin::quickOpenDefinition() { if (jumpToSpecialObject()) { return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if (FunctionDefinition* def = FunctionDefinition::definition(decl)) { def->activateSpecialization(); u = def->url(); c = def->rangeInCurrentRevision().start(); } else { qCDebug(PLUGIN_QUICKOPEN) << "Found no definition for declaration"; decl->activateSpecialization(); } if (u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } bool QuickOpenPlugin::freeModel() { if (m_currentWidgetHandler) { delete m_currentWidgetHandler; } m_currentWidgetHandler = nullptr; return true; } void QuickOpenPlugin::nextFunction() { jumpToNearestFunction(NextFunction); } void QuickOpenPlugin::previousFunction() { jumpToNearestFunction(PreviousFunction); } void QuickOpenPlugin::jumpToNearestFunction(QuickOpenPlugin::FunctionJumpDirection direction) { IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } QVector items; OutlineFilter filter(items, OutlineFilter::Functions); DUChainUtils::collectItems(context, filter); CursorInRevision cursor = context->transformToLocalRevision(KTextEditor::Cursor(doc->cursorPosition())); if (!cursor.isValid()) { return; } Declaration* nearestDeclBefore = nullptr; int distanceBefore = INT_MIN; Declaration* nearestDeclAfter = nullptr; int distanceAfter = INT_MAX; for (int i = 0; i < items.count(); ++i) { Declaration* decl = items[i].m_item.data(); int distance = decl->range().start.line - cursor.line; if (distance < 0 && distance >= distanceBefore) { distanceBefore = distance; nearestDeclBefore = decl; } else if (distance > 0 && distance <= distanceAfter) { distanceAfter = distance; nearestDeclAfter = decl; } } CursorInRevision c = CursorInRevision::invalid(); if (direction == QuickOpenPlugin::NextFunction && nearestDeclAfter) { c = nearestDeclAfter->range().start; } else if (direction == QuickOpenPlugin::PreviousFunction && nearestDeclBefore) { c = nearestDeclBefore->range().start; } KTextEditor::Cursor textCursor = KTextEditor::Cursor::invalid(); if (c.isValid()) { textCursor = context->transformFromLocalRevision(c); } lock.unlock(); if (textCursor.isValid()) { core()->documentController()->openDocument(doc->url(), textCursor); } else { qCDebug(PLUGIN_QUICKOPEN) << "No declaration to jump to"; } } struct CreateOutlineDialog { CreateOutlineDialog() : dialog(nullptr) , cursorDecl(nullptr) , model(nullptr) { } void start() { if (!QuickOpenPlugin::self()->freeModel()) { return; } IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } model = new QuickOpenModel(nullptr); OutlineFilter filter(items); DUChainUtils::collectItems(context, filter); if (noHtmlDestriptionInOutline) { for (int a = 0; a < items.size(); ++a) { items[a].m_noHtmlDestription = true; } } cursorDecl = cursorContextDeclaration(); model->registerProvider(QStringList(), QStringList(), new DeclarationListDataProvider(QuickOpenPlugin::self(), items, true)); dialog = new QuickOpenWidgetDialog(i18n("Outline"), model, QStringList(), QStringList(), true); dialog->widget()->setSortingEnabled(true); model->setParent(dialog->widget()); } void finish() { //Select the declaration that contains the cursor if (cursorDecl && dialog) { int num = 0; - foreach (const DUChainItem& item, items) { + for (const DUChainItem& item : qAsConst(items)) { if (item.m_item.data() == cursorDecl) { QModelIndex index(model->index(num, 0, QModelIndex())); // Need to invoke the scrolling later. If we did it now, then it wouldn't have any effect, // apparently because the widget internals aren't initialized yet properly (although we've // already called 'widget->show()'. auto list = dialog->widget()->ui.list; QMetaObject::invokeMethod(list, "setCurrentIndex", Qt::QueuedConnection, Q_ARG(QModelIndex, index)); QMetaObject::invokeMethod(list, "scrollTo", Qt::QueuedConnection, Q_ARG(QModelIndex, index), Q_ARG(QAbstractItemView::ScrollHint, QAbstractItemView::PositionAtCenter)); } ++num; } } } QPointer dialog; Declaration* cursorDecl; QVector items; QuickOpenModel* model; }; class OutlineQuickopenWidgetCreator : public QuickOpenWidgetCreator { public: OutlineQuickopenWidgetCreator(const QStringList& /*scopes*/, const QStringList& /*items*/) : m_creator(nullptr) { } ~OutlineQuickopenWidgetCreator() override { delete m_creator; } QuickOpenWidget* createWidget() override { delete m_creator; m_creator = new CreateOutlineDialog; m_creator->start(); if (!m_creator->dialog) { return nullptr; } m_creator->dialog->deleteLater(); return m_creator->dialog->widget(); } void widgetShown() override { if (m_creator) { m_creator->finish(); delete m_creator; m_creator = nullptr; } } QString objectNameForLine() override { return QStringLiteral("Outline"); } CreateOutlineDialog* m_creator; }; void QuickOpenPlugin::quickOpenNavigateFunctions() { CreateOutlineDialog create; create.start(); if (!create.dialog) { return; } m_currentWidgetHandler = create.dialog; QuickOpenLineEdit* line = quickOpenLine(QStringLiteral("Outline")); if (!line) { line = quickOpenLine(); } if (line) { line->showWithWidget(create.dialog->widget()); create.dialog->deleteLater(); } else { create.dialog->run(); } create.finish(); } QuickOpenLineEdit::QuickOpenLineEdit(QuickOpenWidgetCreator* creator) : m_widget(nullptr) , m_forceUpdate(false) , m_widgetCreator(creator) { setFont(qApp->font("QToolButton")); setMinimumWidth(200); setMaximumWidth(400); deactivate(); setDefaultText(i18n("Quick Open...")); setToolTip(i18n("Search for files, classes, functions and more," " allowing you to quickly navigate in your source code.")); setObjectName(m_widgetCreator->objectNameForLine()); setFocusPolicy(Qt::ClickFocus); } QuickOpenLineEdit::~QuickOpenLineEdit() { delete m_widget; delete m_widgetCreator; } bool QuickOpenLineEdit::insideThis(QObject* object) { while (object) { qCDebug(PLUGIN_QUICKOPEN) << object; if (object == this || object == m_widget) { return true; } object = object->parent(); } return false; } void QuickOpenLineEdit::widgetDestroyed(QObject* obj) { Q_UNUSED(obj); // need to use a queued connection here, because this function is called in ~QWidget! // => QuickOpenWidget instance is half-destructed => connections are not yet cleared // => clear() will trigger signals which will operate on the invalid QuickOpenWidget // So, just wait until properly destructed QMetaObject::invokeMethod(this, "deactivate", Qt::QueuedConnection); } void QuickOpenLineEdit::showWithWidget(QuickOpenWidget* widget) { connect(widget, &QuickOpenWidget::destroyed, this, &QuickOpenLineEdit::widgetDestroyed); qCDebug(PLUGIN_QUICKOPEN) << "storing widget" << widget; deactivate(); if (m_widget) { qCDebug(PLUGIN_QUICKOPEN) << "deleting" << m_widget; delete m_widget; } m_widget = widget; m_forceUpdate = true; setFocus(); } void QuickOpenLineEdit::focusInEvent(QFocusEvent* ev) { QLineEdit::focusInEvent(ev); // delete m_widget; qCDebug(PLUGIN_QUICKOPEN) << "got focus"; qCDebug(PLUGIN_QUICKOPEN) << "old widget" << m_widget << "force update:" << m_forceUpdate; if (m_widget && !m_forceUpdate) { return; } if (!m_forceUpdate && !QuickOpenPlugin::self()->freeModel()) { deactivate(); return; } m_forceUpdate = false; if (!m_widget) { m_widget = m_widgetCreator->createWidget(); if (!m_widget) { deactivate(); return; } } activate(); m_widget->showStandardButtons(false); m_widget->showSearchField(false); m_widget->setParent(nullptr, Qt::ToolTip); m_widget->setFocusPolicy(Qt::NoFocus); m_widget->setAlternativeSearchField(this); QuickOpenPlugin::self()->m_currentWidgetHandler = m_widget; connect(m_widget.data(), &QuickOpenWidget::ready, this, &QuickOpenLineEdit::deactivate); connect(m_widget.data(), &QuickOpenWidget::scopesChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeScopes); connect(m_widget.data(), &QuickOpenWidget::itemsChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeItems); Q_ASSERT(m_widget->ui.searchLine == this); m_widget->prepareShow(); QRect widgetGeometry = QRect(mapToGlobal(QPoint(0, height())), mapToGlobal(QPoint(width(), height() + 400))); widgetGeometry.setWidth(700); ///@todo Waste less space QRect screenGeom = QApplication::desktop()->screenGeometry(this); if (widgetGeometry.right() > screenGeom.right()) { widgetGeometry.moveRight(screenGeom.right()); } if (widgetGeometry.bottom() > screenGeom.bottom()) { widgetGeometry.moveBottom(mapToGlobal(QPoint(0, 0)).y()); } m_widget->setGeometry(widgetGeometry); m_widget->show(); m_widgetCreator->widgetShown(); } void QuickOpenLineEdit::hideEvent(QHideEvent* ev) { QWidget::hideEvent(ev); if (m_widget) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } // deactivate(); } bool QuickOpenLineEdit::eventFilter(QObject* obj, QEvent* e) { if (!m_widget) { return IQuickOpenLine::eventFilter(obj, e); } switch (e->type()) { case QEvent::KeyPress: case QEvent::ShortcutOverride: if (static_cast(e)->key() == Qt::Key_Escape) { deactivate(); e->accept(); return true; // eat event } break; case QEvent::WindowActivate: case QEvent::WindowDeactivate: QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); break; // handle bug 260657 - "Outline menu doesn't follow main window on its move" case QEvent::Move: { if (QWidget* widget = qobject_cast(obj)) { // close the outline menu in case a parent widget moved if (widget->isAncestorOf(this)) { qCDebug(PLUGIN_QUICKOPEN) << "closing because of parent widget move"; deactivate(); } } break; } case QEvent::FocusIn: if (qobject_cast(obj)) { auto* focusEvent = dynamic_cast(e); Q_ASSERT(focusEvent); //Eat the focus event, keep the focus qCDebug(PLUGIN_QUICKOPEN) << "focus change" << "inside this: " << insideThis(obj) << "this" << this << "obj" << obj; if (obj == this) { break; } qCDebug(PLUGIN_QUICKOPEN) << "reason" << focusEvent->reason(); if (focusEvent->reason() != Qt::MouseFocusReason && focusEvent->reason() != Qt::ActiveWindowFocusReason) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); break; } if (!insideThis(obj)) { deactivate(); } } else if (obj != this) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } break; default: break; } return IQuickOpenLine::eventFilter(obj, e); } void QuickOpenLineEdit::activate() { qCDebug(PLUGIN_QUICKOPEN) << "activating"; setText(QString()); setStyleSheet(QString()); qApp->installEventFilter(this); } void QuickOpenLineEdit::deactivate() { qCDebug(PLUGIN_QUICKOPEN) << "deactivating"; clear(); if (m_widget || hasFocus()) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } if (m_widget) { m_widget->deleteLater(); } m_widget = nullptr; qApp->removeEventFilter(this); } void QuickOpenLineEdit::checkFocus() { qCDebug(PLUGIN_QUICKOPEN) << "checking focus" << m_widget; if (m_widget) { QWidget* focusWidget = QApplication::focusWidget(); bool focusWidgetInsideThis = focusWidget ? insideThis(focusWidget) : false; if (QApplication::focusWindow() && isVisible() && !isHidden() && (!focusWidget || (focusWidget && focusWidgetInsideThis))) { qCDebug(PLUGIN_QUICKOPEN) << "setting focus to line edit"; activateWindow(); setFocus(); } else { qCDebug(PLUGIN_QUICKOPEN) << "deactivating because check failed, focusWidget" << focusWidget << "insideThis" << focusWidgetInsideThis; deactivate(); } } else { if (ICore::self()->documentController()->activeDocument()) { ICore::self()->documentController()->activateDocument(ICore::self()->documentController()->activeDocument()); } //Make sure the focus is somewhere else, even if there is no active document setEnabled(false); setEnabled(true); } } IQuickOpenLine* QuickOpenPlugin::createQuickOpenLine(const QStringList& scopes, const QStringList& type, IQuickOpen::QuickOpenType kind) { if (kind == Outline) { return new QuickOpenLineEdit(new OutlineQuickopenWidgetCreator(scopes, type)); } else { return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(scopes, type)); } } #include "quickopenplugin.moc" diff --git a/plugins/quickopen/quickopenwidget.cpp b/plugins/quickopen/quickopenwidget.cpp index d9e0645a67..3b44081f2e 100644 --- a/plugins/quickopen/quickopenwidget.cpp +++ b/plugins/quickopen/quickopenwidget.cpp @@ -1,563 +1,563 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * Copyright 2016 Kevin Funk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "quickopenwidget.h" #include "debug.h" #include "expandingtree/expandingdelegate.h" #include "quickopenmodel.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; class QuickOpenDelegate : public ExpandingDelegate { Q_OBJECT public: explicit QuickOpenDelegate(ExpandingWidgetModel* model, QObject* parent = nullptr) : ExpandingDelegate(model, parent) { } QVector createHighlighting(const QModelIndex& index, QStyleOptionViewItem& option) const override { QList highlighting = index.data(KTextEditor::CodeCompletionModel::CustomHighlight).toList(); if (!highlighting.isEmpty()) { return highlightingFromVariantList(highlighting); } return ExpandingDelegate::createHighlighting(index, option); } }; QuickOpenWidget::QuickOpenWidget(const QString& title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly, bool noSearchField) : m_model(model) , m_expandedTemporary(false) , m_hadNoCommandSinceAlt(true) { m_filterTimer.setSingleShot(true); connect(&m_filterTimer, &QTimer::timeout, this, &QuickOpenWidget::applyFilter); Q_UNUSED(title); ui.setupUi(this); ui.list->header()->hide(); ui.list->setRootIsDecorated(false); ui.list->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); connect(ui.list->verticalScrollBar(), &QScrollBar::valueChanged, m_model, &QuickOpenModel::placeExpandingWidgets); ui.searchLine->setFocus(); ui.list->setItemDelegate(new QuickOpenDelegate(m_model, ui.list)); if (!listOnly) { const QStringList allTypes = m_model->allTypes(); const QStringList allScopes = m_model->allScopes(); auto* itemsMenu = new QMenu(this); for (const QString& type : allTypes) { auto* action = new QAction(type, itemsMenu); action->setCheckable(true); action->setChecked(initialItems.isEmpty() || initialItems.contains(type)); connect(action, &QAction::toggled, this, &QuickOpenWidget::updateProviders, Qt::QueuedConnection); itemsMenu->addAction(action); } ui.itemsButton->setMenu(itemsMenu); auto* scopesMenu = new QMenu(this); for (const QString& scope : allScopes) { auto* action = new QAction(scope, scopesMenu); action->setCheckable(true); action->setChecked(initialScopes.isEmpty() || initialScopes.contains(scope)); connect(action, &QAction::toggled, this, &QuickOpenWidget::updateProviders, Qt::QueuedConnection); scopesMenu->addAction(action); } ui.scopesButton->setMenu(scopesMenu); } else { ui.list->setFocusPolicy(Qt::StrongFocus); ui.scopesButton->hide(); ui.itemsButton->hide(); ui.label->hide(); ui.label_2->hide(); } showSearchField(!noSearchField); ui.okButton->hide(); ui.cancelButton->hide(); ui.searchLine->installEventFilter(this); ui.list->installEventFilter(this); ui.list->setFocusPolicy(Qt::NoFocus); ui.scopesButton->setFocusPolicy(Qt::NoFocus); ui.itemsButton->setFocusPolicy(Qt::NoFocus); connect(ui.searchLine, &QLineEdit::textChanged, this, &QuickOpenWidget::textChanged); connect(ui.list, &ExpandingTree::doubleClicked, this, &QuickOpenWidget::doubleClicked); connect(ui.okButton, &QPushButton::clicked, this, &QuickOpenWidget::accept); connect(ui.okButton, &QPushButton::clicked, this, &QuickOpenWidget::ready); connect(ui.cancelButton, &QPushButton::clicked, this, &QuickOpenWidget::ready); updateProviders(); updateTimerInterval(true); // no need to call this, it's done by updateProviders already // m_model->restart(); } void QuickOpenWidget::showStandardButtons(bool show) { if (show) { ui.okButton->show(); ui.cancelButton->show(); } else { ui.okButton->hide(); ui.cancelButton->hide(); } } bool QuickOpenWidget::sortingEnabled() const { return m_sortingEnabled; } void QuickOpenWidget::setSortingEnabled(bool enabled) { m_sortingEnabled = enabled; } void QuickOpenWidget::updateTimerInterval(bool cheapFilterChange) { const int MAX_ITEMS = 10000; if (cheapFilterChange && m_model->rowCount(QModelIndex()) < MAX_ITEMS) { // cheap change and there are currently just a few items, // so apply filter instantly m_filterTimer.setInterval(0); } else if (m_model->unfilteredRowCount() < MAX_ITEMS) { // not a cheap change, but there are generally // just a few items in the list: apply filter instantly m_filterTimer.setInterval(0); } else { // otherwise use a timer to prevent sluggishness while typing m_filterTimer.setInterval(300); } } void QuickOpenWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); // The column width only has an effect _after_ the widget has been shown ui.list->setColumnWidth(0, 20); } void QuickOpenWidget::setAlternativeSearchField(QLineEdit* alterantiveSearchField) { ui.searchLine = alterantiveSearchField; ui.searchLine->installEventFilter(this); connect(ui.searchLine, &QLineEdit::textChanged, this, &QuickOpenWidget::textChanged); } void QuickOpenWidget::showSearchField(bool b) { if (b) { ui.searchLine->show(); ui.searchLabel->show(); } else { ui.searchLine->hide(); ui.searchLabel->hide(); } } void QuickOpenWidget::prepareShow() { ui.list->setModel(nullptr); ui.list->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); m_model->setTreeView(ui.list); // set up proxy filter delete m_proxy; m_proxy = nullptr; if (sortingEnabled()) { auto sortFilterProxyModel = new QSortFilterProxyModel(this); sortFilterProxyModel->setDynamicSortFilter(true); m_proxy = sortFilterProxyModel; } else { m_proxy = new QIdentityProxyModel(this); } m_proxy->setSourceModel(m_model); if (sortingEnabled()) { m_proxy->sort(1); } ui.list->setModel(m_proxy); m_filterTimer.stop(); m_filter = QString(); if (!m_preselectedText.isEmpty()) { ui.searchLine->setText(m_preselectedText); ui.searchLine->selectAll(); } m_model->restart(false); connect(ui.list->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &QuickOpenWidget::callRowSelected); connect(ui.list->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QuickOpenWidget::callRowSelected); } void QuickOpenWidgetDialog::run() { m_widget->prepareShow(); m_dialog->show(); } QuickOpenWidget::~QuickOpenWidget() { m_model->setTreeView(nullptr); } QuickOpenWidgetDialog::QuickOpenWidgetDialog(const QString& title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly, bool noSearchField) { m_widget = new QuickOpenWidget(title, model, initialItems, initialScopes, listOnly, noSearchField); // the QMenu might close on esc and we want to close the whole dialog then connect(m_widget, &QuickOpenWidget::aboutToHide, this, &QuickOpenWidgetDialog::deleteLater); m_dialog = new QDialog(ICore::self()->uiController()->activeMainWindow()); m_dialog->resize(QSize(800, 400)); m_dialog->setWindowTitle(title); auto* layout = new QVBoxLayout(m_dialog); layout->addWidget(m_widget); m_widget->showStandardButtons(true); connect(m_widget, &QuickOpenWidget::ready, m_dialog, &QDialog::close); connect(m_dialog, &QDialog::accepted, m_widget, &QuickOpenWidget::accept); } QuickOpenWidgetDialog::~QuickOpenWidgetDialog() { delete m_dialog; } void QuickOpenWidget::setPreselectedText(const QString& text) { m_preselectedText = text; } void QuickOpenWidget::updateProviders() { if (QAction* action = (sender() ? qobject_cast(sender()) : nullptr)) { auto* menu = qobject_cast(action->parentWidget()); if (menu) { menu->show(); menu->setActiveAction(action); } } QStringList checkedItems; if (ui.itemsButton->menu()) { - foreach (QObject* obj, ui.itemsButton->menu()->children()) { + for (QObject* obj : ui.itemsButton->menu()->children()) { auto* box = qobject_cast(obj); if (box) { if (box->isChecked()) { checkedItems << box->text().remove(QLatin1Char('&')); } } } ui.itemsButton->setText(checkedItems.join(QStringLiteral(", "))); } QStringList checkedScopes; if (ui.scopesButton->menu()) { - foreach (QObject* obj, ui.scopesButton->menu()->children()) { + for (QObject* obj : ui.scopesButton->menu()->children()) { auto* box = qobject_cast(obj); if (box) { if (box->isChecked()) { checkedScopes << box->text().remove(QLatin1Char('&')); } } } ui.scopesButton->setText(checkedScopes.join(QStringLiteral(", "))); } emit itemsChanged(checkedItems); emit scopesChanged(checkedScopes); m_model->enableProviders(checkedItems, checkedScopes); } void QuickOpenWidget::textChanged(const QString& str) { QString strTrimmed = str.trimmed(); // "cheap" when something was just appended to the current filter updateTimerInterval(strTrimmed.startsWith(m_filter)); m_filter = strTrimmed; m_filterTimer.start(); } void QuickOpenWidget::applyFilter() { m_model->textChanged(m_filter); QModelIndex currentIndex = m_model->index(0, 0, QModelIndex()); ui.list->selectionModel()->setCurrentIndex(m_proxy->mapFromSource(currentIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current); callRowSelected(); } void QuickOpenWidget::callRowSelected() { const QModelIndex currentIndex = ui.list->currentIndex(); if (currentIndex.isValid()) { m_model->rowSelected(m_proxy->mapToSource(currentIndex)); } else { qCDebug(PLUGIN_QUICKOPEN) << "current index is not valid"; } } void QuickOpenWidget::accept() { QString filterText = ui.searchLine->text(); m_model->execute(m_proxy->mapToSource(ui.list->currentIndex()), filterText); } void QuickOpenWidget::doubleClicked(const QModelIndex& index) { // crash guard: https://bugs.kde.org/show_bug.cgi?id=297178 ui.list->setCurrentIndex(index); QMetaObject::invokeMethod(this, "accept", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "ready", Qt::QueuedConnection); } void QuickOpenWidget::avoidMenuAltFocus() { // send an invalid key event to the main menu bar. The menu bar will // stop listening when observing another key than ALT between the press // and the release. QKeyEvent event1(QEvent::KeyPress, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event1); QKeyEvent event2(QEvent::KeyRelease, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event2); } bool QuickOpenWidget::eventFilter(QObject* watched, QEvent* event) { auto getInterface = [this]() { const QModelIndex index = m_proxy->mapToSource(ui.list->currentIndex()); QWidget* widget = m_model->expandingWidget(index); return dynamic_cast(widget); }; auto* keyEvent = dynamic_cast(event); if (event->type() == QEvent::KeyRelease) { if (keyEvent->key() == Qt::Key_Alt) { if ((m_expandedTemporary && m_altDownTime.msecsTo(QTime::currentTime()) > 300) || (!m_expandedTemporary && m_altDownTime.msecsTo(QTime::currentTime()) < 300 && m_hadNoCommandSinceAlt)) { //Unexpand the item QModelIndex row = m_proxy->mapToSource(ui.list->selectionModel()->currentIndex()); if (row.isValid()) { row = row.sibling(row.row(), 0); if (m_model->isExpanded(row)) { m_model->setExpanded(row, false); } } } m_expandedTemporary = false; } } if (event->type() == QEvent::KeyPress) { m_hadNoCommandSinceAlt = false; if (keyEvent->key() == Qt::Key_Alt) { avoidMenuAltFocus(); m_hadNoCommandSinceAlt = true; //Expand QModelIndex row = m_proxy->mapToSource(ui.list->selectionModel()->currentIndex()); if (row.isValid()) { row = row.sibling(row.row(), 0); m_altDownTime = QTime::currentTime(); if (!m_model->isExpanded(row)) { m_expandedTemporary = true; m_model->setExpanded(row, true); } } } switch (keyEvent->key()) { case Qt::Key_Tab: if (keyEvent->modifiers() == Qt::NoModifier) { // Tab should work just like Down QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier)); QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Down, Qt::NoModifier)); return true; // eat event } break; case Qt::Key_Backtab: if (keyEvent->modifiers() == Qt::ShiftModifier) { // Shift + Tab should work just like Up QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier)); QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Up, Qt::NoModifier)); return true; // eat event } break; case Qt::Key_Backspace: if (keyEvent->modifiers() == Qt::AltModifier) { if (auto interface = getInterface()) { interface->back(); return true; // eat event } } break; case Qt::Key_Down: case Qt::Key_Up: if (keyEvent->modifiers() == Qt::AltModifier) { if (auto interface = getInterface()) { if (keyEvent->key() == Qt::Key_Down) { interface->down(); } else { interface->up(); } return true; // eat event } break; } Q_FALLTHROUGH(); case Qt::Key_PageUp: case Qt::Key_PageDown: if (watched == ui.list) { break; } QApplication::sendEvent(ui.list, event); //callRowSelected(); return true; // eat event case Qt::Key_Left: { //Expand/unexpand if (keyEvent->modifiers() == Qt::AltModifier) { //Eventually Send action to the widget if (auto interface = getInterface()) { interface->previous(); return true; // eat event } } else { QModelIndex row = m_proxy->mapToSource(ui.list->currentIndex()); if (row.isValid()) { row = row.sibling(row.row(), 0); if (m_model->isExpanded(row)) { m_model->setExpanded(row, false); return true; // eat event } } } break; } case Qt::Key_Right: { //Expand/unexpand if (keyEvent->modifiers() == Qt::AltModifier) { //Eventually Send action to the widget if (auto interface = getInterface()) { interface->next(); return true; // eat event } } else { QModelIndex row = m_proxy->mapToSource(ui.list->selectionModel()->currentIndex()); if (row.isValid()) { row = row.sibling(row.row(), 0); if (!m_model->isExpanded(row)) { m_model->setExpanded(row, true); return true; // eat event } } } break; } case Qt::Key_Return: case Qt::Key_Enter: { if (m_filterTimer.isActive()) { m_filterTimer.stop(); applyFilter(); } if (keyEvent->modifiers() == Qt::AltModifier) { //Eventually Send action to the widget if (auto interface = getInterface()) { interface->accept(); return true; // eat event } } else { QString filterText = ui.searchLine->text(); //Safety: Track whether this object is deleted. When execute() is called, a dialog may be opened, //which kills the quickopen widget. QPointer stillExists(this); if (m_model->execute(m_proxy->mapToSource(ui.list->currentIndex()), filterText)) { if (!stillExists) { return true; // eat event } if (!(keyEvent->modifiers() & Qt::ShiftModifier)) { emit ready(); } } else { //Maybe the filter-text was changed: if (filterText != ui.searchLine->text()) { ui.searchLine->setText(filterText); } } } return true; // eat event } } } return QMenu::eventFilter(watched, event); } #include "quickopenwidget.moc"