diff --git a/plugins/outlineview/outlinemodel.cpp b/plugins/outlineview/outlinemodel.cpp index c0ea7ea826..bccda5c09f 100644 --- a/plugins/outlineview/outlinemodel.cpp +++ b/plugins/outlineview/outlinemodel.cpp @@ -1,215 +1,216 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "outlinemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include "debug_outline.h" #include "outlinenode.h" using namespace KDevelop; OutlineModel::OutlineModel(QObject* parent) - : QAbstractItemModel(parent), m_lastDoc(nullptr) + : QAbstractItemModel(parent) + , m_lastDoc(nullptr) { auto docController = ICore::self()->documentController(); // build the initial outline now rebuildOutline(docController->activeDocument()); // we must always have a valid root node Q_ASSERT(m_rootNode); // we want to rebuild the outline whenever the current document has been reparsed connect(ICore::self()->languageController()->backgroundParser(), &BackgroundParser::parseJobFinished, this, &OutlineModel::onParseJobFinished); // and also when we switch the current document connect(docController, &IDocumentController::documentActivated, this, &OutlineModel::rebuildOutline); connect(docController, &IDocumentController::documentUrlChanged, this, [this](IDocument* doc) { if (doc == m_lastDoc) { m_lastUrl = IndexedString(doc->url()); } }); } OutlineModel::~OutlineModel() { } Qt::ItemFlags OutlineModel::flags(const QModelIndex& index) const { if (!index.isValid()) { return Qt::NoItemFlags; } else { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } } int OutlineModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } QVariant OutlineModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.column() != 0) { return QVariant(); } OutlineNode* node = static_cast(index.internalPointer()); Q_ASSERT(node); if (role == Qt::DecorationRole) { return node->icon(); } if (role == Qt::DisplayRole) { return node->text(); } return QVariant(); } bool OutlineModel::hasChildren(const QModelIndex& parent) const { return rowCount(parent) > 0; } int OutlineModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { Q_ASSERT(m_rootNode); return m_rootNode->childCount(); } else if (parent.column() != 0) { return 0; } else { const OutlineNode* node = static_cast(parent.internalPointer()); return node->childCount(); } } QModelIndex OutlineModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } if (!parent.isValid()) { // topLevelItem if (row < m_rootNode->childCount()) { return createIndex(row, column, const_cast(m_rootNode->childAt(row))); } return QModelIndex(); } else { if (parent.column() != 0) { return QModelIndex(); //only column 0 should have children } OutlineNode* node = static_cast(parent.internalPointer()); if (row < node->childCount()) { return createIndex(row, column, const_cast(node->childAt(row))); } return QModelIndex(); // out of range } return QModelIndex(); } QModelIndex OutlineModel::parent(const QModelIndex& index) const { if (!index.isValid()) { return QModelIndex(); } const OutlineNode* node = static_cast(index.internalPointer()); const OutlineNode* parentNode = node->parent(); Q_ASSERT(parentNode); if (parentNode == m_rootNode.get()) { return QModelIndex(); //node is a top level item } // parent node was not m_rootNode -> parent() must be valid const OutlineNode* parentParentNode = parentNode->parent(); Q_ASSERT(parentNode); const int row = parentParentNode->indexOf(parentNode); return createIndex(row, 0, const_cast(parentNode)); } void OutlineModel::onParseJobFinished(KDevelop::ParseJob* job) { if (job->document() == m_lastUrl) { rebuildOutline(m_lastDoc); } } void OutlineModel::rebuildOutline(IDocument* doc) { emit beginResetModel(); if (!doc) { m_rootNode = OutlineNode::dummyNode(); } else { // TODO: do this in a separate thread? Might take a while for large documents // and we really shouldn't be blocking the GUI thread! DUChainReadLocker lock; TopDUContext* topContext = DUChainUtils::standardContextForUrl(doc->url()); if (topContext) { m_rootNode = OutlineNode::fromTopContext(topContext); } else { m_rootNode = OutlineNode::dummyNode(); } } if (doc != m_lastDoc) { m_lastUrl = doc ? IndexedString(doc->url()) : IndexedString(); m_lastDoc = doc; } emit endResetModel(); } void OutlineModel::activate(const QModelIndex& realIndex) { if (!realIndex.isValid()) { qCWarning(PLUGIN_OUTLINE) << "attempting to activate invalid item!"; return; } OutlineNode* node = static_cast(realIndex.internalPointer()); KTextEditor::Range range; { DUChainReadLocker lock; const DUChainBase* dcb = node->duChainObject(); if (!dcb) { qCDebug(PLUGIN_OUTLINE) << "No declaration exists for node:" << node->text(); return; } //foreground thread == GUI thread? if so then we are fine range = dcb->rangeInCurrentRevision(); //outline view should ALWAYS correspond to currently active document Q_ASSERT(dcb->url().toUrl() == ICore::self()->documentController()->activeDocument()->url()); // lock should be released before activating the document } ICore::self()->documentController()->activateDocument(m_lastDoc, range); } diff --git a/plugins/outlineview/outlinenode.h b/plugins/outlineview/outlinenode.h index 9a857550a5..64e9327d79 100644 --- a/plugins/outlineview/outlinenode.h +++ b/plugins/outlineview/outlinenode.h @@ -1,161 +1,161 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include #include #include #include #include #include #include namespace KDevelop { class Declaration; class DUContext; } class OutlineNode { Q_DISABLE_COPY(OutlineNode) void appendContext(KDevelop::DUContext* ctx, KDevelop::TopDUContext* top); void sortByLocation(bool requiresSorting); public: OutlineNode(const QString& text, OutlineNode* parent); OutlineNode(OutlineNode&& other) noexcept; OutlineNode& operator=(OutlineNode&& other) noexcept; OutlineNode(KDevelop::Declaration* decl, OutlineNode* parent); OutlineNode(KDevelop::DUContext* ctx, const QString& name, OutlineNode* parent); virtual ~OutlineNode(); QIcon icon() const; QString text() const; const OutlineNode* parent() const; const std::vector& children() const; int childCount() const; const OutlineNode* childAt(int index) const; int indexOf(const OutlineNode* child) const; static std::unique_ptr fromTopContext(KDevelop::TopDUContext* ctx); static std::unique_ptr dummyNode(); KDevelop::DUChainBase* duChainObject(); friend void swap(OutlineNode& n1, OutlineNode& n2); private: QString m_cachedText; QIcon m_cachedIcon; KDevelop::DUChainBasePointer m_declOrContext; OutlineNode* m_parent; std::vector m_children; }; inline int OutlineNode::childCount() const { return m_children.size(); } inline const std::vector& OutlineNode::children() const { return m_children; } inline const OutlineNode* OutlineNode::childAt(int index) const { return &m_children.at(index); } inline const OutlineNode* OutlineNode::parent() const { return m_parent; } inline int OutlineNode::indexOf(const OutlineNode* child) const { const auto max = m_children.size(); // Comparing the address here is only fine since we never modify the vector after initial creation for (size_t i = 0; i < max; i++) { - if (child== &m_children[i]) { + if (child == &m_children[i]) { return i; } } return -1; } inline QIcon OutlineNode::icon() const { return m_cachedIcon; } inline QString OutlineNode::text() const { return m_cachedText; } inline KDevelop::DUChainBase* OutlineNode::duChainObject() { Q_ASSERT(KDevelop::DUChain::lock()->currentThreadHasReadLock()); return m_declOrContext.data(); } inline OutlineNode::OutlineNode(OutlineNode&& other) noexcept : m_cachedText(std::move(other.m_cachedText)) , m_cachedIcon(std::move(other.m_cachedIcon)) , m_declOrContext(std::move(other.m_declOrContext)) , m_parent(std::move(other.m_parent)) , m_children(std::move(other.m_children)) { // qDebug("Move ctor %p -> %p", &other, this); other.m_parent = nullptr; other.m_declOrContext = nullptr; for (OutlineNode& child : m_children) { // when we are moved the parent pointer has to be updated for the children! child.m_parent = this; } } inline OutlineNode& OutlineNode::operator=(OutlineNode&& other) noexcept { if (this == &other) { return *this; } m_cachedText = std::move(other.m_cachedText); m_cachedIcon = std::move(other.m_cachedIcon); m_declOrContext = std::move(other.m_declOrContext); m_parent = std::move(other.m_parent); m_children = std::move(other.m_children); // qDebug("Move assignment %p -> %p", &other, this); other.m_parent = nullptr; other.m_declOrContext = nullptr; for (OutlineNode& child : m_children) { // when we are moved the parent pointer has to be updated for the children! child.m_parent = this; } return *this; } inline void swap(OutlineNode& n1, OutlineNode& n2) { // For some reason std::sort only sometimes calls swap and mostly uses move ctor + assign. // Probably it uses different algorithms for different sequence sizes // qDebug("Swapping %p and %p", &n1, &n2); std::swap(n1.m_cachedText, n2.m_cachedText); std::swap(n1.m_cachedIcon, n2.m_cachedIcon); std::swap(n1.m_declOrContext, n2.m_declOrContext); std::swap(n1.m_parent, n2.m_parent); std::swap(n1.m_children, n2.m_children); } diff --git a/plugins/outlineview/outlinewidget.cpp b/plugins/outlineview/outlinewidget.cpp index 2fbbeb9d27..8a49cd6c63 100644 --- a/plugins/outlineview/outlinewidget.cpp +++ b/plugins/outlineview/outlinewidget.cpp @@ -1,106 +1,105 @@ /* * KDevelop outline view * Copyright 2010, 2015 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "outlinewidget.h" #include #include #include #include #include #include #include #include #include #include #include #include "outlineviewplugin.h" #include "outlinemodel.h" using namespace KDevelop; OutlineWidget::OutlineWidget(QWidget* parent, OutlineViewPlugin* plugin) : QWidget(parent) , m_plugin(plugin) , m_model(new OutlineModel(this)) , m_tree(new QTreeView(this)) , m_proxy(new KRecursiveFilterProxyModel(this)) , m_filter(new QLineEdit(this)) { setObjectName("Outline View"); setWindowTitle(i18n("Outline")); setWhatsThis(i18n("Outline View")); setWindowIcon(QIcon::fromTheme("code-class")); //TODO: better icon? m_proxy->setSourceModel(m_model); m_proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive); m_proxy->setDynamicSortFilter(false); m_tree->setModel(m_proxy); m_tree->setHeaderHidden(true); //filter connect(m_filter, &QLineEdit::textChanged, m_proxy, &KRecursiveFilterProxyModel::setFilterFixedString); connect(m_tree, &QTreeView::activated, this, &OutlineWidget::activated); QHBoxLayout* filterLayout = new QHBoxLayout(); m_filter->setPlaceholderText(i18n("Filter...")); m_sortAlphabetically = new QPushButton(QIcon::fromTheme("view-sort-ascending"), QString(), this); m_sortAlphabetically->setToolTip(i18n("Sort alphabetically")); m_sortAlphabetically->setCheckable(true); connect(m_sortAlphabetically, &QPushButton::toggled, this, [this](bool sort) { - qDebug("Set sorting: %d", sort); // calling sort with -1 will restore the original order m_proxy->sort(sort ? 0 : -1, Qt::AscendingOrder); m_sortAlphabetically->setChecked(sort); }); filterLayout->addWidget(m_sortAlphabetically); filterLayout->addWidget(m_filter); setFocusProxy(m_filter); QVBoxLayout* vbox = new QVBoxLayout(this); vbox->setMargin(0); vbox->addLayout(filterLayout); vbox->addWidget(m_tree); setLayout(vbox); expandFirstLevel(); connect(m_model, &QAbstractItemModel::modelReset, this, &OutlineWidget::expandFirstLevel); } void OutlineWidget::activated(QModelIndex index) { QModelIndex realIndex = m_proxy->mapToSource(index); m_model->activate(realIndex); } OutlineWidget::~OutlineWidget() { } void OutlineWidget::expandFirstLevel() { for (int i = 0; i < m_proxy->rowCount(); i++) { m_tree->expand(m_proxy->index(i, 0)); } }