Commit 3c743346 authored by hjk's avatar hjk
Browse files

ProjectExplorer: Make the FlatModel a Utils::TreeModel



The FlatModel is essentially a proxy model keeping expansion and
filter state per ProjectTree(View). Using a Utils::TreeModel makes
it fast enough to allow recreation of the proxy structure on
structural changes simplifying the parent/child logic significantly.

The {Session,Project,Folder,File}Node hierarchy still is still primary
information and shared by all views.

Change-Id: Ic08180a19bda37908280ff30e0737d188ed93e92
Reviewed-by: default avatarRobert Loehning <robert.loehning@qt.io>
Reviewed-by: Tobias Hunger's avatarTobias Hunger <tobias.hunger@qt.io>
parent 03a68a91
......@@ -249,13 +249,16 @@ public:
explicit TreeModel(QObject *parent = 0) : BaseTreeModel(new RootItem, parent) {}
explicit TreeModel(RootItem *root, QObject *parent = 0) : BaseTreeModel(root, parent) {}
using BaseTreeModel::canFetchMore;
using BaseTreeModel::clear;
using BaseTreeModel::columnCount;
using BaseTreeModel::data;
using BaseTreeModel::destroyItem;
using BaseTreeModel::fetchMore;
using BaseTreeModel::hasChildren;
using BaseTreeModel::index;
using BaseTreeModel::indexForItem;
using BaseTreeModel::parent;
using BaseTreeModel::rowCount;
using BaseTreeModel::setData;
using BaseTreeModel::setHeader;
......
......@@ -37,9 +37,15 @@ bool ExpandData::operator==(const ExpandData &other) const
return path == other.path && displayName == other.displayName;
}
QStringList ExpandData::toStringList() const
ExpandData ExpandData::fromSettings(const QVariant &v)
{
return { path, displayName };
QStringList list = v.toStringList();
return list.size() == 2 ? ExpandData(list.at(0), list.at(1)) : ExpandData();
}
QVariant ExpandData::toSettings() const
{
return QVariant::fromValue(QStringList({ path, displayName }));
}
int ProjectExplorer::Internal::qHash(const ExpandData &data)
......
......@@ -38,7 +38,9 @@ public:
ExpandData() = default;
ExpandData(const QString &path_, const QString &displayName_);
bool operator==(const ExpandData &other) const;
QStringList toStringList() const;
static ExpandData fromSettings(const QVariant &v);
QVariant toSettings() const;
QString path;
QString displayName;
......
......@@ -25,51 +25,53 @@
#pragma once
#include "expanddata.h"
#include "projectnodes.h"
#include <utils/fileutils.h>
#include <utils/treemodel.h>
#include <QAbstractItemModel>
#include <QPointer>
#include <QSet>
#include <QTimer>
#include <QTreeView>
namespace ProjectExplorer {
class Node;
class FileNode;
class FolderNode;
class Project;
class ProjectNode;
class SessionNode;
namespace Internal {
class FlatModel : public QAbstractItemModel
class WrapperNode : public Utils::TypedTreeItem<WrapperNode>
{
public:
explicit WrapperNode(Node *node) : m_node(node) {}
QPointer<Node> m_node;
};
class FlatModel : public Utils::TreeModel<WrapperNode, WrapperNode>
{
Q_OBJECT
public:
FlatModel(SessionNode *rootNode, QObject *parent);
FlatModel(QObject *parent);
void setView(QTreeView *view);
// QAbstractItemModel
QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
int rowCount(const QModelIndex & parent = QModelIndex()) const override;
int columnCount(const QModelIndex & parent = QModelIndex()) const override;
bool hasChildren(const QModelIndex & parent = QModelIndex()) const override;
bool canFetchMore(const QModelIndex & parent) const override;
void fetchMore(const QModelIndex & parent) override;
void reset();
Qt::DropActions supportedDragActions() const override;
QStringList mimeTypes() const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
void setStartupProject(ProjectNode *projectNode);
Node *nodeForIndex(const QModelIndex &index) const;
WrapperNode *wrapperForNode(const Node *node) const;
QModelIndex indexForNode(const Node *node) const;
bool projectFilterEnabled();
......@@ -81,53 +83,34 @@ signals:
void renamed(const Utils::FileName &oldName, const Utils::FileName &newName);
private:
void aboutToShowInSimpleTreeChanged(ProjectExplorer::FolderNode *node);
void showInSimpleTreeChanged(ProjectExplorer::FolderNode *node);
void foldersAboutToBeAdded(FolderNode *parentFolder, const QList<FolderNode*> &newFolders);
void foldersAdded();
void foldersAboutToBeRemoved(FolderNode *parentFolder, const QList<FolderNode*> &staleFolders);
void foldersRemoved();
// files
void filesAboutToBeAdded(FolderNode *folder, const QList<FileNode*> &newFiles);
void filesAdded();
void filesAboutToBeRemoved(FolderNode *folder, const QList<FileNode*> &staleFiles);
void filesRemoved();
void nodeSortKeyAboutToChange(Node *node);
void nodeSortKeyChanged();
void startupProjectChanged(Project *project);
void nodeUpdated(ProjectExplorer::Node *node);
void added(FolderNode* folderNode, const QList<Node*> &newNodeList);
void removed(FolderNode* parentNode, const QList<Node*> &newNodeList);
void removeFromCache(QList<FolderNode *> list);
void changedSortKey(FolderNode *folderNode, Node *node);
void fetchMore(FolderNode *foldernode) const;
void recursiveAddFolderNodes(FolderNode *startNode, QList<Node *> *list, const QSet<Node *> &blackList = QSet<Node*>()) const;
void recursiveAddFolderNodesImpl(FolderNode *startNode, QList<Node *> *list, const QSet<Node *> &blackList = QSet<Node*>()) const;
void recursiveAddFileNodes(FolderNode *startNode, QList<Node *> *list, const QSet<Node *> &blackList = QSet<Node*>()) const;
QList<Node*> childNodes(FolderNode *parentNode, const QSet<Node*> &blackList = QSet<Node*>()) const;
FolderNode *visibleFolderNode(FolderNode *node) const;
bool filter(Node *node) const;
bool filter(Node *node) const; // Returns true if node is hidden.
bool m_filterProjects = false;
bool m_filterGeneratedFiles = true;
SessionNode *m_rootNode;
mutable QHash<FolderNode*, QList<Node*> > m_childNodes;
ProjectNode *m_startupProject = nullptr;
FolderNode *m_parentFolderForChange = nullptr;
Node *m_nodeForSortKeyChange = nullptr;
static const QLoggingCategory &logger();
friend class FlatModelManager;
void update();
void doUpdate();
void rebuildModel();
void addProjectNode(WrapperNode *parent, ProjectNode *projectNode, QSet<Node *> *seen);
void addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet<Node *> *seen);
ExpandData expandDataForNode(const Node *node) const;
void onExpanded(const QModelIndex &idx);
void onCollapsed(const QModelIndex &idx);
void loadExpandData();
void saveExpandData();
void handleProjectAdded(Project *project);
QTimer m_timer;
QTreeView *m_view = nullptr;
QSet<ExpandData> m_toExpand;
};
int caseFriendlyCompare(const QString &a, const QString &b);
......
......@@ -68,30 +68,21 @@ void Node::setPriority(int p)
m_priority = p;
}
void Node::emitNodeSortKeyAboutToChange()
{
if (parentFolderNode())
ProjectTree::instance()->emitNodeSortKeyAboutToChange(this);
}
void Node::emitNodeSortKeyChanged()
{
if (parentFolderNode())
ProjectTree::instance()->emitNodeSortKeyChanged(this);
}
void Node::setAbsoluteFilePathAndLine(const Utils::FileName &path, int line)
{
if (m_filePath == path && m_line == line)
return;
emitNodeSortKeyAboutToChange();
m_filePath = path;
m_line = line;
emitNodeSortKeyChanged();
emitNodeUpdated();
}
Node::~Node()
{
}
NodeType Node::nodeType() const
{
return m_nodeType;
......@@ -186,7 +177,12 @@ void Node::setEnabled(bool enabled)
void Node::emitNodeUpdated()
{
if (parentFolderNode())
ProjectTree::instance()->emitNodeUpdated(this);
ProjectTree::emitNodeUpdated(this);
}
void Node::emitTreeChanged()
{
ProjectTree::emitDataChanged();
}
Node *Node::trim(const QSet<Node *> &keepers)
......@@ -511,9 +507,7 @@ void FolderNode::setDisplayName(const QString &name)
{
if (m_displayName == name)
return;
emitNodeSortKeyAboutToChange();
m_displayName = name;
emitNodeSortKeyChanged();
emitNodeUpdated();
}
......@@ -588,8 +582,6 @@ void FolderNode::addFileNodes(const QList<FileNode *> &files)
if (files.isEmpty())
return;
ProjectTree::instance()->emitFilesAboutToBeAdded(this, files);
foreach (FileNode *file, files) {
QTC_ASSERT(!file->parentFolderNode(),
qDebug("File node has already a parent folder"));
......@@ -606,7 +598,7 @@ void FolderNode::addFileNodes(const QList<FileNode *> &files)
}
}
ProjectTree::instance()->emitFilesAdded(this);
ProjectTree::emitDataChanged();
}
/*!
......@@ -627,8 +619,6 @@ void FolderNode::removeFileNodes(const QList<FileNode *> &files)
QList<FileNode*> toRemove = files;
Utils::sort(toRemove);
ProjectTree::instance()->emitFilesAboutToBeRemoved(this, toRemove);
auto toRemoveIter = toRemove.constBegin();
auto filesIter = m_fileNodes.begin();
for (; toRemoveIter != toRemove.constEnd(); ++toRemoveIter) {
......@@ -641,7 +631,7 @@ void FolderNode::removeFileNodes(const QList<FileNode *> &files)
filesIter = m_fileNodes.erase(filesIter);
}
ProjectTree::instance()->emitFilesRemoved(this);
ProjectTree::emitDataChanged();
}
/*!
......@@ -655,7 +645,6 @@ void FolderNode::addFolderNodes(const QList<FolderNode*> &subFolders)
if (subFolders.isEmpty())
return;
ProjectTree::instance()->emitFoldersAboutToBeAdded(this, subFolders);
foreach (FolderNode *folder, subFolders) {
QTC_ASSERT(!folder->parentFolderNode(),
qDebug("Project node has already a parent folder"));
......@@ -677,7 +666,7 @@ void FolderNode::addFolderNodes(const QList<FolderNode*> &subFolders)
qDebug("project nodes have to be added via addProjectNodes"));
}
ProjectTree::instance()->emitFoldersAdded(this);
ProjectTree::emitDataChanged();
}
/*!
......@@ -696,8 +685,6 @@ void FolderNode::removeFolderNodes(const QList<FolderNode*> &subFolders)
QList<FolderNode*> toRemove = subFolders;
Utils::sort(toRemove);
ProjectTree::instance()->emitFoldersAboutToBeRemoved(this, toRemove);
auto toRemoveIter = toRemove.constBegin();
auto folderIter = m_folderNodes.begin();
for (; toRemoveIter != toRemove.constEnd(); ++toRemoveIter) {
......@@ -712,7 +699,7 @@ void FolderNode::removeFolderNodes(const QList<FolderNode*> &subFolders)
folderIter = m_folderNodes.erase(folderIter);
}
ProjectTree::instance()->emitFoldersRemoved(this);
ProjectTree::emitDataChanged();
}
bool FolderNode::showInSimpleTree() const
......@@ -867,8 +854,6 @@ void ProjectNode::addProjectNodes(const QList<ProjectNode*> &subProjects)
foreach (ProjectNode *projectNode, subProjects)
folderNodes << projectNode;
ProjectTree::instance()->emitFoldersAboutToBeAdded(this, folderNodes);
foreach (ProjectNode *project, subProjects) {
QTC_ASSERT(!project->parentFolderNode() || project->parentFolderNode() == this,
qDebug("Project node has already a parent"));
......@@ -876,10 +861,9 @@ void ProjectNode::addProjectNodes(const QList<ProjectNode*> &subProjects)
m_folderNodes.append(project);
m_projectNodes.append(project);
}
Utils::sort(m_folderNodes);
Utils::sort(m_projectNodes);
ProjectTree::instance()->emitFoldersAdded(this);
}
}
......@@ -898,8 +882,6 @@ void ProjectNode::removeProjectNodes(const QList<ProjectNode*> &subProjects)
toRemove << projectNode;
Utils::sort(toRemove);
ProjectTree::instance()->emitFoldersAboutToBeRemoved(this, toRemove);
auto toRemoveIter = toRemove.constBegin();
auto folderIter = m_folderNodes.begin();
auto projectIter = m_projectNodes.begin();
......@@ -918,8 +900,6 @@ void ProjectNode::removeProjectNodes(const QList<ProjectNode*> &subProjects)
projectIter = m_projectNodes.erase(projectIter);
folderIter = m_folderNodes.erase(folderIter);
}
ProjectTree::instance()->emitFoldersRemoved(this);
}
}
......@@ -967,12 +947,6 @@ bool SessionNode::showInSimpleTree() const
return true;
}
void SessionNode::projectDisplayNameChanged(Node *node)
{
ProjectTree::instance()->emitNodeSortKeyAboutToChange(node);
ProjectTree::instance()->emitNodeSortKeyChanged(node);
}
QList<ProjectNode*> SessionNode::projectNodes() const
{
return m_projectNodes;
......@@ -990,8 +964,6 @@ void SessionNode::addProjectNodes(const QList<ProjectNode*> &projectNodes)
foreach (ProjectNode *projectNode, projectNodes)
folderNodes << projectNode;
ProjectTree::instance()->emitFoldersAboutToBeAdded(this, folderNodes);
foreach (ProjectNode *project, projectNodes) {
QTC_ASSERT(!project->parentFolderNode(),
qDebug("Project node has already a parent folder"));
......@@ -1002,8 +974,6 @@ void SessionNode::addProjectNodes(const QList<ProjectNode*> &projectNodes)
Utils::sort(m_folderNodes);
Utils::sort(m_projectNodes);
ProjectTree::instance()->emitFoldersAdded(this);
}
}
......@@ -1015,9 +985,6 @@ void SessionNode::removeProjectNodes(const QList<ProjectNode*> &projectNodes)
toRemove << projectNode;
Utils::sort(toRemove);
ProjectTree::instance()->emitFoldersAboutToBeRemoved(this, toRemove);
auto toRemoveIter = toRemove.constBegin();
auto folderIter = m_folderNodes.begin();
auto projectIter = m_projectNodes.begin();
......@@ -1035,8 +1002,6 @@ void SessionNode::removeProjectNodes(const QList<ProjectNode*> &projectNodes)
projectIter = m_projectNodes.erase(projectIter);
folderIter = m_folderNodes.erase(folderIter);
}
ProjectTree::instance()->emitFoldersRemoved(this);
}
}
......
......@@ -104,8 +104,9 @@ class NodesVisitor;
class SessionManager;
// Documentation inside.
class PROJECTEXPLORER_EXPORT Node
class PROJECTEXPLORER_EXPORT Node : public QObject
{
Q_OBJECT
public:
enum PriorityLevel {
DefaultPriority = 0,
......@@ -116,7 +117,7 @@ public:
DefaultProjectFilePriority = 500000
};
virtual ~Node() = default;
virtual ~Node();
NodeType nodeType() const;
int priority() const;
......@@ -141,6 +142,7 @@ public:
void setAbsoluteFilePathAndLine(const Utils::FileName &filePath, int line);
void emitNodeUpdated();
void emitTreeChanged();
virtual Node *trim(const QSet<Node *> &keepers);
......@@ -161,9 +163,6 @@ protected:
void setPriority(int priority);
void setParentFolderNode(FolderNode *parentFolder);
void emitNodeSortKeyAboutToChange();
void emitNodeSortKeyChanged();
private:
FolderNode *m_parentFolderNode = nullptr;
Utils::FileName m_filePath;
......@@ -177,6 +176,7 @@ class PROJECTEXPLORER_EXPORT FileNode : public Node
{
public:
FileNode(const Utils::FileName &filePath, const FileType fileType, bool generated, int line = -1);
FileNode(const FileNode &other) : FileNode(other.filePath(), other.fileType(), true) {}
FileType fileType() const;
bool isGenerated() const;
......@@ -255,8 +255,8 @@ public:
void addFolderNodes(const QList<FolderNode*> &subFolders);
void removeFolderNodes(const QList<FolderNode*> &subFolders);
FolderNode *asFolderNode() final { return this; }
const FolderNode *asFolderNode() const final { return this; }
FolderNode *asFolderNode() override { return this; }
const FolderNode *asFolderNode() const override { return this; }
protected:
QList<FolderNode*> m_folderNodes;
......@@ -330,11 +330,8 @@ public:
SessionNode();
QList<ProjectAction> supportedActions(Node *node) const override;
QList<ProjectNode*> projectNodes() const;
QString addFileFilter() const override;
void accept(NodesVisitor *visitor) override;
bool showInSimpleTree() const override;
......
......@@ -258,167 +258,14 @@ void ProjectTree::updateContext()
void ProjectTree::emitNodeUpdated(Node *node)
{
if (!isInNodeHierarchy(node))
if (!s_instance->isInNodeHierarchy(node))
return;
emit nodeUpdated(node);
emit s_instance->nodeUpdated(node);
}
void ProjectTree::emitAboutToChangeShowInSimpleTree(FolderNode *node)
void ProjectTree::emitDataChanged()
{
if (!isInNodeHierarchy(node))
return;
emit aboutToChangeShowInSimpleTree(node);
}
void ProjectTree::emitShowInSimpleTreeChanged(FolderNode *node)
{
if (!isInNodeHierarchy(node))
return;
emit showInSimpleTreeChanged(node);
}
void ProjectTree::emitFoldersAboutToBeAdded(FolderNode *parentFolder, const QList<FolderNode *> &newFolders)
{
if (!isInNodeHierarchy(parentFolder))
return;
m_foldersAdded = newFolders;
emit foldersAboutToBeAdded(parentFolder, newFolders);
}
void ProjectTree::emitFoldersAdded(FolderNode *folder)
{
if (!isInNodeHierarchy(folder))
return;
emit foldersAdded();
if (Utils::anyOf(m_projectTreeWidgets, &ProjectTreeWidget::hasFocus))
return;
if (!m_currentNode) {
Core::IDocument *document = Core::EditorManager::currentDocument();
const FileName fileName = document ? document->filePath() : FileName();
FindNodesForFileVisitor findNodes(fileName);
foreach (FolderNode *fn, m_foldersAdded)
fn->accept(&findNodes);
Node *bestNode = ProjectTreeWidget::mostExpandedNode(findNodes.nodes());
if (!bestNode)
return;
updateFromNode(bestNode);
}
m_foldersAdded.clear();
}
void ProjectTree::emitFoldersAboutToBeRemoved(FolderNode *parentFolder, const QList<FolderNode *> &staleFolders)
{
if (!isInNodeHierarchy(parentFolder))
return;
Node *n = ProjectTree::currentNode();
while (n) {
if (FolderNode *fn = n->asFolderNode()) {
if (staleFolders.contains(fn)) {
ProjectNode *pn = n->parentProjectNode();
// Make sure the node we are switching too isn't going to be removed also
while (staleFolders.contains(pn))
pn = pn->parentFolderNode()->parentProjectNode();
m_resetCurrentNodeFolder = true;
break;
}
}
n = n->parentFolderNode();
}
emit foldersAboutToBeRemoved(parentFolder, staleFolders);
}
void ProjectTree::emitFoldersRemoved(FolderNode *folder)
{
if (!isInNodeHierarchy(folder))
return;
emit foldersRemoved();
if (m_resetCurrentNodeFolder) {
updateFromFocus(true);
m_resetCurrentNodeFolder = false;
}
}
void ProjectTree::emitFilesAboutToBeAdded(FolderNode *folder, const QList<FileNode *> &newFiles)
{
if (!isInNodeHierarchy(folder))
return;
m_filesAdded = newFiles;
emit filesAboutToBeAdded(folder, newFiles);
}
void ProjectTree::emitFilesAdded(FolderNode *folder)
{
if (!isInNodeHierarchy(folder))
return;
emit filesAdded();
if (Utils::anyOf(m_projectTreeWidgets, &ProjectTreeWidget::hasFocus))
return;
if (!m_currentNode) {
Core::IDocument *document = Core::EditorManager::currentDocument();
const FileName fileName = document ? document->filePath() : FileName();
int index = Utils::indexOf(m_filesAdded, Utils::equal(&FileNode::filePath, fileName));
if (index == -1)
return;
updateFromNode(m_filesAdded.at(index));
}
m_filesAdded.clear();
}