Commit e8ee8988 authored by Daniel Teske's avatar Daniel Teske
Browse files

ProjectExplorer: Remove hack for virtual folders



Previously virtual folders, that is the "Sources", "Headers" folders used a
hack. This patch removes that hack, by introducing the following changes
- The FlatModel and the ProjectExplorer::Nodes now don't require path() to
  be unique. Thus allowing the virtual folders to all return the same for
  path(). [1]

- Introducing a new node type "VirtualFolder" which is sorted according to
  a priority.

- Introducing a few new virtuals for displayName and toolip(), which can
  be overriden.

[1] Note that all the project managers do require path() to be unique for
some types of nodes.

That also fixes:
Task-number: QTCREATORBUG-7100

Change-Id: I76b730f4c4254e2894467603bbe9a30e356a0bcc
Reviewed-by: default avatarTobias Hunger <tobias.hunger@nokia.com>
Reviewed-by: default avatarDaniel Teske <daniel.teske@nokia.com>
parent 3b4c6c03
......@@ -376,13 +376,18 @@ void CMakeProject::gatherFileNodes(ProjectExplorer::FolderNode *parent, QList<Pr
list.append(file);
}
bool sortNodesByPath(Node *a, Node *b)
{
return a->path() < b->path();
}
void CMakeProject::buildTree(CMakeProjectNode *rootNode, QList<ProjectExplorer::FileNode *> newList)
{
// Gather old list
QList<ProjectExplorer::FileNode *> oldList;
gatherFileNodes(rootNode, oldList);
qSort(oldList.begin(), oldList.end(), ProjectExplorer::ProjectNode::sortNodesByPath);
qSort(newList.begin(), newList.end(), ProjectExplorer::ProjectNode::sortNodesByPath);
qSort(oldList.begin(), oldList.end(), sortNodesByPath);
qSort(newList.begin(), newList.end(), sortNodesByPath);
// generate added and deleted list
QList<ProjectExplorer::FileNode *>::const_iterator oldIt = oldList.constBegin();
......
......@@ -2567,12 +2567,11 @@ QString pathOrDirectoryFor(Node *node, bool dir)
QString path = node->path();
QString location;
FolderNode *folder = qobject_cast<FolderNode *>(node);
const int hashPos = path.indexOf(QLatin1Char('#'));
if (hashPos >= 0 && folder) {
if (node->nodeType() == ProjectExplorer::VirtualFolderNodeType && folder) {
// Virtual Folder case
// If there are files directly below or no subfolders, take the folder path
if (!folder->fileNodes().isEmpty() || folder->subFolderNodes().isEmpty()) {
location = path.left(hashPos);
location = path;
} else {
// Otherwise we figure out a commonPath from the subfolders
QStringList list;
......
......@@ -101,6 +101,29 @@ bool sortNodes(Node *n1, Node *n2)
if (n2Type == ProjectNodeType)
return false;
if (n1Type == VirtualFolderNodeType) {
if (n2Type == VirtualFolderNodeType) {
VirtualFolderNode *folder1 = static_cast<VirtualFolderNode *>(n1);
VirtualFolderNode *folder2 = static_cast<VirtualFolderNode *>(n2);
if (folder1->priority() > folder2->priority())
return true;
if (folder1->priority() < folder2->priority())
return false;
int result = caseFriendlyCompare(folder1->path(), folder2->path());
if (result != 0)
return result < 0;
else
return folder1 < folder2;
} else {
return true; // virtual folder is before folder
}
}
if (n2Type == VirtualFolderNodeType)
return false;
if (n1Type == FolderNodeType) {
if (n2Type == FolderNodeType) {
FolderNode *folder1 = static_cast<FolderNode*>(n1);
......@@ -240,14 +263,11 @@ QVariant FlatModel::data(const QModelIndex &index, int role) const
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole: {
if (folderNode)
result = folderNode->displayName();
else
result = QFileInfo(node->path()).fileName(); //TODO cache that?
result = node->displayName();
break;
}
case Qt::ToolTipRole: {
result = QDir::toNativeSeparators(node->path());
result = node->tooltip();
break;
}
case Qt::DecorationRole: {
......
......@@ -102,6 +102,16 @@ QString Node::path() const
return m_path;
}
QString Node::displayName() const
{
return QFileInfo(path()).fileName();
}
QString Node::tooltip() const
{
return QDir::toNativeSeparators(path());
}
void Node::setNodeType(NodeType type)
{
m_nodeType = type;
......@@ -154,8 +164,8 @@ bool FileNode::isGenerated() const
\sa ProjectExplorer::FileNode, ProjectExplorer::ProjectNode
*/
FolderNode::FolderNode(const QString &folderPath) :
Node(FolderNodeType, folderPath),
FolderNode::FolderNode(const QString &folderPath, NodeType nodeType) :
Node(nodeType, folderPath),
m_displayName(QDir::toNativeSeparators(folderPath))
{
}
......@@ -215,6 +225,31 @@ void FolderNode::setIcon(const QIcon &icon)
m_icon = icon;
}
/*!
\class ProjectExplorer::VirtualFolderNode
In-memory presentation of a virtual folder.
Note that the node itself + all children (files and folders) are "managed" by the owning project.
A virtual folder does not correspond to a actual folder on the file system. See for example the
sources, headers and forms folder the qt4projectmanager creates
VirtualFolderNodes are always sorted before FolderNodes and are sorted according to their priority.
\sa ProjectExplorer::FileNode, ProjectExplorer::ProjectNode
*/
VirtualFolderNode::VirtualFolderNode(const QString &folderPath, int priority)
: FolderNode(folderPath, VirtualFolderNodeType), m_priority(priority)
{
}
VirtualFolderNode::~VirtualFolderNode()
{
}
int VirtualFolderNode::priority() const
{
return m_priority;
}
/*!
\class ProjectExplorer::ProjectNode
......@@ -344,10 +379,8 @@ void ProjectNode::addProjectNodes(const QList<ProjectNode*> &subProjects)
m_subFolderNodes.append(project);
m_subProjectNodes.append(project);
}
qSort(m_subFolderNodes.begin(), m_subFolderNodes.end(),
sortNodesByPath);
qSort(m_subProjectNodes.begin(), m_subProjectNodes.end(),
sortNodesByPath);
qSort(m_subFolderNodes.begin(), m_subFolderNodes.end());
qSort(m_subProjectNodes.begin(), m_subProjectNodes.end());
foreach (NodesWatcher *watcher, m_watchers)
emit watcher->foldersAdded();
......@@ -366,7 +399,7 @@ void ProjectNode::removeProjectNodes(const QList<ProjectNode*> &subProjects)
QList<FolderNode*> toRemove;
foreach (ProjectNode *projectNode, subProjects)
toRemove << projectNode;
qSort(toRemove.begin(), toRemove.end(), sortNodesByPath);
qSort(toRemove.begin(), toRemove.end());
foreach (NodesWatcher *watcher, m_watchers)
emit watcher->foldersAboutToBeRemoved(this, toRemove);
......@@ -375,12 +408,12 @@ void ProjectNode::removeProjectNodes(const QList<ProjectNode*> &subProjects)
QList<FolderNode*>::iterator folderIter = m_subFolderNodes.begin();
QList<ProjectNode*>::iterator projectIter = m_subProjectNodes.begin();
for (; toRemoveIter != toRemove.constEnd(); ++toRemoveIter) {
while ((*projectIter)->path() != (*toRemoveIter)->path()) {
while (*projectIter != *toRemoveIter) {
++projectIter;
QTC_ASSERT(projectIter != m_subProjectNodes.end(),
qDebug("Project to remove is not part of specified folder!"));
}
while ((*folderIter)->path() != (*toRemoveIter)->path()) {
while (*folderIter != *toRemoveIter) {
++folderIter;
QTC_ASSERT(folderIter != m_subFolderNodes.end(),
qDebug("Project to remove is not part of specified folder!"));
......@@ -416,22 +449,17 @@ void ProjectNode::addFolderNodes(const QList<FolderNode*> &subFolders, FolderNod
folder->setProjectNode(this);
// Find the correct place to insert
if (parentFolder->m_subFolderNodes.count() == 0 || sortNodesByPath(parentFolder->m_subFolderNodes.last(), folder)) {
if (parentFolder->m_subFolderNodes.count() == 0
|| parentFolder->m_subFolderNodes.last() < folder) {
// empty list or greater then last node
parentFolder->m_subFolderNodes.append(folder);
} else {
// Binary Search for insertion point
int l = 0;
int r = parentFolder->m_subFolderNodes.count();
while (l != r) {
int i = (l + r) / 2;
if (sortNodesByPath(folder, parentFolder->m_subFolderNodes.at(i))) {
r = i;
} else {
l = i + 1;
}
}
parentFolder->m_subFolderNodes.insert(l, folder);
QList<FolderNode*>::iterator it
= qLowerBound(parentFolder->m_subFolderNodes.begin(),
parentFolder->m_subFolderNodes.end(),
folder);
parentFolder->m_subFolderNodes.insert(it, folder);
}
// project nodes have to be added via addProjectNodes
......@@ -459,7 +487,7 @@ void ProjectNode::removeFolderNodes(const QList<FolderNode*> &subFolders,
const bool emitSignals = (parentFolder->projectNode() == this);
QList<FolderNode*> toRemove = subFolders;
qSort(toRemove.begin(), toRemove.end(), sortNodesByPath);
qSort(toRemove.begin(), toRemove.end());
if (emitSignals)
foreach (NodesWatcher *watcher, m_watchers)
......@@ -470,7 +498,7 @@ void ProjectNode::removeFolderNodes(const QList<FolderNode*> &subFolders,
for (; toRemoveIter != toRemove.constEnd(); ++toRemoveIter) {
QTC_ASSERT((*toRemoveIter)->nodeType() != ProjectNodeType,
qDebug("project nodes have to be removed via removeProjectNodes"));
while ((*folderIter)->path() != (*toRemoveIter)->path()) {
while (*folderIter != *toRemoveIter) {
++folderIter;
QTC_ASSERT(folderIter != parentFolder->m_subFolderNodes.end(),
qDebug("Folder to remove is not part of specified folder!"));
......@@ -509,22 +537,16 @@ void ProjectNode::addFileNodes(const QList<FileNode*> &files, FolderNode *folder
file->setParentFolderNode(folder);
file->setProjectNode(this);
// Now find the correct place to insert file
if (folder->m_fileNodes.count() == 0 || sortNodesByPath(folder->m_fileNodes.last(), file)) {
if (folder->m_fileNodes.count() == 0
|| folder->m_fileNodes.last() < file) {
// empty list or greater then last node
folder->m_fileNodes.append(file);
} else {
// Binary Search for insertion point
int l = 0;
int r = folder->m_fileNodes.count();
while (l != r) {
int i = (l + r) / 2;
if (sortNodesByPath(file, folder->m_fileNodes.at(i))) {
r = i;
} else {
l = i + 1;
}
}
folder->m_fileNodes.insert(l, file);
QList<FileNode *>::iterator it
= qLowerBound(folder->m_fileNodes.begin(),
folder->m_fileNodes.end(),
file);
folder->m_fileNodes.insert(it, file);
}
}
......@@ -549,7 +571,7 @@ void ProjectNode::removeFileNodes(const QList<FileNode*> &files, FolderNode *fol
const bool emitSignals = (folder->projectNode() == this);
QList<FileNode*> toRemove = files;
qSort(toRemove.begin(), toRemove.end(), sortNodesByPath);
qSort(toRemove.begin(), toRemove.end());
if (emitSignals)
foreach (NodesWatcher *watcher, m_watchers)
......@@ -558,7 +580,7 @@ void ProjectNode::removeFileNodes(const QList<FileNode*> &files, FolderNode *fol
QList<FileNode*>::const_iterator toRemoveIter = toRemove.constBegin();
QList<FileNode*>::iterator filesIter = folder->m_fileNodes.begin();
for (; toRemoveIter != toRemove.constEnd(); ++toRemoveIter) {
while ((*filesIter)->path() != (*toRemoveIter)->path()) {
while (*filesIter != *toRemoveIter) {
++filesIter;
QTC_ASSERT(filesIter != folder->m_fileNodes.end(),
qDebug("File to remove is not part of specified folder!"));
......@@ -579,12 +601,6 @@ void ProjectNode::watcherDestroyed(QObject *watcher)
unregisterWatcher(static_cast<NodesWatcher*>(watcher));
}
/*!
\brief Sort pointers to FileNodes
*/
bool ProjectNode::sortNodesByPath(Node *n1, Node *n2) {
return n1->path() < n2->path();
}
/*!
\class ProjectExplorer::SessionNode
......@@ -663,6 +679,9 @@ void SessionNode::addProjectNodes(const QList<ProjectNode*> &projectNodes)
m_projectNodes.append(project);
}
qSort(m_subFolderNodes);
qSort(m_projectNodes);
foreach (NodesWatcher *watcher, m_watchers)
emit watcher->foldersAdded();
}
......@@ -675,6 +694,8 @@ void SessionNode::removeProjectNodes(const QList<ProjectNode*> &projectNodes)
foreach (ProjectNode *projectNode, projectNodes)
toRemove << projectNode;
qSort(toRemove);
foreach (NodesWatcher *watcher, m_watchers)
emit watcher->foldersAboutToBeRemoved(this, toRemove);
......@@ -682,12 +703,12 @@ void SessionNode::removeProjectNodes(const QList<ProjectNode*> &projectNodes)
QList<FolderNode*>::iterator folderIter = m_subFolderNodes.begin();
QList<ProjectNode*>::iterator projectIter = m_projectNodes.begin();
for (; toRemoveIter != toRemove.constEnd(); ++toRemoveIter) {
while ((*projectIter)->path() != (*toRemoveIter)->path()) {
while (*projectIter != *toRemoveIter) {
++projectIter;
QTC_ASSERT(projectIter != m_projectNodes.end(),
qDebug("Project to remove is not part of specified folder!"));
}
while ((*folderIter)->path() != (*toRemoveIter)->path()) {
while (*folderIter != *toRemoveIter) {
++folderIter;
QTC_ASSERT(folderIter != m_subFolderNodes.end(),
qDebug("Project to remove is not part of specified folder!"));
......
......@@ -54,6 +54,7 @@ class RunConfiguration;
enum NodeType {
FileNodeType = 1,
FolderNodeType,
VirtualFolderNodeType,
ProjectNodeType,
SessionNodeType
};
......@@ -87,6 +88,8 @@ public:
ProjectNode *projectNode() const; // managing project
FolderNode *parentFolderNode() const; // parent folder or project
QString path() const; // file system path
virtual QString displayName() const;
virtual QString tooltip() const;
protected:
Node(NodeType nodeType, const QString &path);
......@@ -122,7 +125,7 @@ private:
class PROJECTEXPLORER_EXPORT FolderNode : public Node {
Q_OBJECT
public:
explicit FolderNode(const QString &folderPath);
explicit FolderNode(const QString &folderPath, NodeType nodeType = FolderNodeType);
virtual ~FolderNode();
QString displayName() const;
......@@ -147,6 +150,18 @@ private:
mutable QIcon m_icon;
};
class PROJECTEXPLORER_EXPORT VirtualFolderNode : public FolderNode
{
Q_OBJECT
public:
explicit VirtualFolderNode(const QString &folderPath, int priority);
virtual ~VirtualFolderNode();
int priority() const;
private:
int m_priority;
};
// Documentation inside.
class PROJECTEXPLORER_EXPORT ProjectNode : public FolderNode
{
......@@ -213,8 +228,6 @@ public:
void accept(NodesVisitor *visitor);
static bool sortNodesByPath(Node *n1, Node *n2);
protected:
// this is just the in-memory representation, a subclass
// will add the persistent stuff
......
......@@ -107,6 +107,11 @@ static const FileTypeDataStorage fileTypeDataStorage[] = {
":/qt4projectmanager/images/unknown.png" }
};
bool sortNodesByPath(ProjectExplorer::Node *a, ProjectExplorer::Node *b)
{
return a->path() < b->path();
}
class Qt4NodeStaticData {
public:
class FileTypeData {
......@@ -278,20 +283,25 @@ void Qt4PriFileNode::scheduleUpdate()
namespace Internal {
struct InternalNode
{
QMap<QString, InternalNode*> subnodes;
QList<InternalNode *> virtualfolders;
QMap<QString, InternalNode *> subnodes;
QStringList files;
ProjectExplorer::FileType type;
QString displayName;
QString typeName;
int priority;
QString fullPath;
QIcon icon;
InternalNode()
{
type = ProjectExplorer::UnknownFileType;
priority = 0;
}
~InternalNode()
{
qDeleteAll(virtualfolders);
qDeleteAll(subnodes);
}
......@@ -381,62 +391,102 @@ struct InternalNode
subnodes = newSubnodes;
}
FolderNode *createFolderNode(InternalNode *node)
{
FolderNode *newNode = 0;
if (node->typeName.isEmpty())
newNode = new FolderNode(node->fullPath);
else
newNode = new ProVirtualFolderNode(node->fullPath, node->priority, node->typeName);
newNode->setDisplayName(node->displayName);
if (!node->icon.isNull())
newNode->setIcon(node->icon);
return newNode;
}
// Makes the projectNode's subtree below the given folder match this internal node's subtree
void updateSubFolders(Qt4ProjectManager::Qt4PriFileNode *projectNode, ProjectExplorer::FolderNode *folder)
{
updateFiles(projectNode, folder, type);
// update folders
QList<FolderNode *> existingFolderNodes;
foreach (FolderNode *node, folder->subFolderNodes()) {
// updateFolders
QMultiMap<QString, FolderNode *> existingFolderNodes;
foreach (FolderNode *node, folder->subFolderNodes())
if (node->nodeType() != ProjectNodeType)
existingFolderNodes << node;
}
qSort(existingFolderNodes.begin(), existingFolderNodes.end(), ProjectNode::sortNodesByPath);
existingFolderNodes.insert(node->path(), node);
QList<FolderNode *> foldersToRemove;
QList<FolderNode *> foldersToAdd;
typedef QPair<InternalNode *, FolderNode *> NodePair;
QList<NodePair> nodesToUpdate;
// Both lists should be already sorted...
QList<FolderNode*>::const_iterator existingNodeIter = existingFolderNodes.constBegin();
QMap<QString, InternalNode*>::const_iterator newNodeIter = subnodes.constBegin();;
while (existingNodeIter != existingFolderNodes.constEnd()
&& newNodeIter != subnodes.constEnd()) {
if ((*existingNodeIter)->path() < newNodeIter.value()->fullPath) {
foldersToRemove << *existingNodeIter;
++existingNodeIter;
} else if ((*existingNodeIter)->path() > newNodeIter.value()->fullPath) {
FolderNode *newNode = new FolderNode(newNodeIter.value()->fullPath);
newNode->setDisplayName(newNodeIter.value()->displayName);
if (!newNodeIter.value()->icon.isNull())
newNode->setIcon(newNodeIter.value()->icon);
foldersToAdd << newNode;
nodesToUpdate << NodePair(newNodeIter.value(), newNode);
++newNodeIter;
} else { // *existingNodeIter->path() == *newPathIter
nodesToUpdate << NodePair(newNodeIter.value(), *existingNodeIter);
++existingNodeIter;
++newNodeIter;
// Check virtual
{
QList<InternalNode *>::const_iterator it = virtualfolders.constBegin();
QList<InternalNode *>::const_iterator end = virtualfolders.constEnd();
for ( ; it != end; ++it) {
bool found = false;
QString path = (*it)->fullPath;
QMultiMap<QString, FolderNode *>::const_iterator oldit
= existingFolderNodes.constFind(path);
while (oldit != existingFolderNodes.end() && oldit.key() == path) {
if (oldit.value()->nodeType() == ProjectExplorer::VirtualFolderNodeType) {
ProjectExplorer::VirtualFolderNode *vfn
= qobject_cast<ProjectExplorer::VirtualFolderNode *>(oldit.value());
if (vfn->priority() == (*it)->priority) {
found = true;
break;
}
}
++oldit;
}
if (found) {
nodesToUpdate << NodePair(*it, *oldit);
} else {
FolderNode *newNode = createFolderNode(*it);
foldersToAdd << newNode;
nodesToUpdate << NodePair(*it, newNode);
}
}
}
while (existingNodeIter != existingFolderNodes.constEnd()) {
foldersToRemove << *existingNodeIter;
++existingNodeIter;
}
while (newNodeIter != subnodes.constEnd()) {
FolderNode *newNode = new FolderNode(newNodeIter.value()->fullPath);
newNode->setDisplayName(newNodeIter.value()->displayName);
if (!newNodeIter.value()->icon.isNull())
newNode->setIcon(newNodeIter.value()->icon);
foldersToAdd << newNode;
nodesToUpdate << NodePair(newNodeIter.value(), newNode);
++newNodeIter;
// Check subnodes
{
QMap<QString, InternalNode *>::const_iterator it = subnodes.constBegin();
QMap<QString, InternalNode *>::const_iterator end = subnodes.constEnd();
for ( ; it != end; ++it) {
bool found = false;
QString path = it.value()->fullPath;
QMultiMap<QString, FolderNode *>::const_iterator oldit
= existingFolderNodes.constFind(path);
while (oldit != existingFolderNodes.end() && oldit.key() == path) {
if (oldit.value()->nodeType() == ProjectExplorer::FolderNodeType) {
found = true;
break;
}
++oldit;
}
if (found) {
nodesToUpdate << NodePair(it.value(), *oldit);
} else {
FolderNode *newNode = createFolderNode(it.value());
foldersToAdd << newNode;
nodesToUpdate << NodePair(it.value(), newNode);
}
}
}
QSet<FolderNode *> toKeep;
foreach (const NodePair &np, nodesToUpdate)
toKeep << np.second;
QMultiMap<QString, FolderNode *>::const_iterator jit = existingFolderNodes.constBegin();
QMultiMap<QString, FolderNode *>::const_iterator jend = existingFolderNodes.constEnd();
for ( ; jit != jend; ++jit)
if (!toKeep.contains(jit.value()))
foldersToRemove << jit.value();
if (!foldersToRemove.isEmpty())
projectNode->removeFolderNodes(foldersToRemove, folder);
if (!foldersToAdd.isEmpty())
......@@ -459,7 +509,7 @@ struct InternalNode
QList<FileNode*> filesToAdd;
qSort(files);
qSort(existingFileNodes.begin(), existingFileNodes.end(), ProjectNode::sortNodesByPath);
qSort(existingFileNodes.begin(), existingFileNodes.end(), sortNodesByPath);
QList<FileNode*>::const_iterator existingNodeIter = existingFileNodes.constBegin();
QList<QString>::const_iterator newPathIter = files.constBegin();
......@@ -647,10 +697,11 @@ void Qt4PriFileNode::update(ProFile *includeFileExact, QtSupport::ProFileReader
InternalNode *subfolder = new InternalNode;
subfolder->type = type;
subfolder->icon = fileTypes.at(i).icon;
subfolder->fullPath = m_projectDir + QLatin1String("/#")
+ QString::number(i) + fileTypes.at(i).typeName;
subfolder->fullPath = m_projectDir;
subfolder->typeName = fileTypes.at(i).typeName;
subfolder->priority = -i;
subfolder->displayName = fileTypes.at(i).typeName;
contents.subnodes.insert(subfolder->fullPath, subfolder);
contents.virtualfolders.append(subfolder);
// create the hierarchy with subdirectories
subfolder->create(m_projectDir, newFilePaths, type);
}
......@@ -731,10 +782,11 @@ void Qt4PriFileNode::folderChanged(const QString &folder)
InternalNode *subfolder = new InternalNode;
subfolder->type = type;
subfolder->icon = fileTypes.at(i).icon;
subfolder->fullPath = m_projectDir + QLatin1String("/#")
+ QString::number(i) + fileTypes.at(i).typeName;
subfolder->fullPath = m_projectDir;
subfolder->typeName = fileTypes.at(i).typeName;
subfolder->priority = -i;
subfolder->displayName = fileTypes.at(i).typeName;
contents.subnodes.insert(subfolder->fullPath, subfolder);
contents.virtualfolders.append(subfolder);
// create the hierarchy with subdirectories
subfolder->create(m_projectDir, m_files[type], type);
}
......@@ -833,7 +885,7 @@ QList<ProjectNode::ProjectAction> Qt4PriFileNode::supportedActions(Node *node) c
}