From b604e91f5b5514b12a55610c4edcbbd208972e67 Mon Sep 17 00:00:00 2001
From: Svenn-Arne Dragly <s@dragly.com>
Date: Sun, 1 Nov 2015 02:56:14 +0100
Subject: [PATCH] Show folders as tree view for resources

This patch builds a tree view of the paths to files
in resource files.
Previously all files were shown in a flat structure
under prefixes with the entire path as part of their
displayed name.
Because folders based on prefixes are different from
folders in the paths, a new class, SimpleResourceFolderNode,
was introduced.

Change-Id: Ifc4773cff6a678b50e64b0d56713f80704e12f6f
Reviewed-by: Svenn-Arne Dragly <s@dragly.com>
Reviewed-by: Tobias Hunger <tobias.hunger@theqtcompany.com>
---
 src/plugins/resourceeditor/resourcenode.cpp | 290 +++++++++++++++++---
 src/plugins/resourceeditor/resourcenode.h   |  62 ++++-
 2 files changed, 318 insertions(+), 34 deletions(-)

diff --git a/src/plugins/resourceeditor/resourcenode.cpp b/src/plugins/resourceeditor/resourcenode.cpp
index 78d9aceb9bb..d0cc6697bc7 100644
--- a/src/plugins/resourceeditor/resourcenode.cpp
+++ b/src/plugins/resourceeditor/resourcenode.cpp
@@ -40,6 +40,7 @@
 #include <qmljstools/qmljstoolsconstants.h>
 
 #include <utils/mimetypes/mimedatabase.h>
+#include <utils/algorithm.h>
 
 #include <QCoreApplication>
 #include <QDir>
@@ -135,69 +136,119 @@ ResourceTopLevelNode::~ResourceTopLevelNode()
 
 void ResourceTopLevelNode::update()
 {
-    QList<ProjectExplorer::FolderNode *> newFolderList;
-    QMap<QPair<QString, QString>, QList<ProjectExplorer::FileNode *> > filesToAdd;
+    QList<ProjectExplorer::FolderNode *> newPrefixList;
+    QMap<PrefixFolderLang, QList<ProjectExplorer::FileNode *>> filesToAdd;
+    QMap<PrefixFolderLang, QList<ProjectExplorer::FolderNode *>> foldersToAddToFolders;
+    QMap<PrefixFolderLang, QList<ProjectExplorer::FolderNode *>> foldersToAddToPrefix;
 
     ResourceFile file(filePath().toString());
     if (file.load() == Core::IDocument::OpenResult::Success) {
-        QSet<QPair<QString, QString > > prefixes;
+        QMap<PrefixFolderLang, ProjectExplorer::FolderNode *> prefixNodes;
+        QMap<PrefixFolderLang, ProjectExplorer::FolderNode *> folderNodes;
 
         int prfxcount = file.prefixCount();
         for (int i = 0; i < prfxcount; ++i) {
             const QString &prefix = file.prefix(i);
             const QString &lang = file.lang(i);
             // ensure that we don't duplicate prefixes
-            if (!prefixes.contains(qMakePair(prefix, lang))) {
+            PrefixFolderLang prefixId(prefix, QString(), lang);
+            if (!prefixNodes.contains(prefixId)) {
                 ProjectExplorer::FolderNode *fn = new ResourceFolderNode(file.prefix(i), file.lang(i), this);
-                newFolderList << fn;
+                newPrefixList << fn;
 
-                prefixes.insert(qMakePair(prefix, lang));
+                prefixNodes.insert(prefixId, fn);
             }
+            ResourceFolderNode *currentPrefixNode = static_cast<ResourceFolderNode*>(prefixNodes[prefixId]);
 
             QSet<QString> fileNames;
             int filecount = file.fileCount(i);
             for (int j = 0; j < filecount; ++j) {
                 const QString &fileName = file.file(i, j);
                 QString alias = file.alias(i, j);
-                if (alias.isEmpty())
-                    alias = filePath().toFileInfo().absoluteDir().relativeFilePath(fileName);
                 if (fileNames.contains(fileName)) {
                     // The file name is duplicated, skip it
                     // Note: this is wrong, but the qrceditor doesn't allow it either
                     // only aliases need to be unique
                 } else {
+                    if (alias.isEmpty())
+                        alias = filePath().toFileInfo().absoluteDir().relativeFilePath(fileName);
+
                     QString prefixWithSlash = prefix;
                     if (!prefixWithSlash.endsWith(QLatin1Char('/')))
                         prefixWithSlash.append(QLatin1Char('/'));
+
+                    const QString fullPath = QDir::cleanPath(alias);
+                    QStringList pathList = fullPath.split(QLatin1Char('/'));
+                    const QString displayName = pathList.last();
+                    pathList.removeLast(); // remove file name
+
+                    bool parentIsPrefix = true;
+
+                    QString parentFolderName;
+                    PrefixFolderLang folderId(prefix, QString(), lang);
+                    QStringList currentPathList;
+                    foreach (const QString &pathElement, pathList) {
+                        currentPathList << pathElement;
+                        const QString folderName = currentPathList.join(QLatin1Char('/'));
+                        folderId = PrefixFolderLang(prefix, folderName, lang);
+                        if (!folderNodes.contains(folderId)) {
+                            const QString absoluteFolderName
+                                    = filePath().toFileInfo().absoluteDir().absoluteFilePath(
+                                        currentPathList.join(QLatin1Char('/')));
+                            const Utils::FileName folderPath
+                                    = Utils::FileName::fromString(absoluteFolderName);
+                            ProjectExplorer::FolderNode *newNode
+                                    = new SimpleResourceFolderNode(folderName, pathElement,
+                                                                 prefix, lang, folderPath,
+                                                                 this, currentPrefixNode);
+                            if (parentIsPrefix) {
+                                foldersToAddToPrefix[prefixId] << newNode;
+                            } else {
+                                PrefixFolderLang parentFolderId(prefix, parentFolderName, lang);
+                                foldersToAddToFolders[parentFolderId] << newNode;
+                            }
+                            folderNodes.insert(folderId, newNode);
+                        }
+                        parentIsPrefix = false;
+                        parentFolderName = folderName;
+                    }
+
                     const QString qrcPath = QDir::cleanPath(prefixWithSlash + alias);
                     fileNames.insert(fileName);
-                    filesToAdd[qMakePair(prefix, lang)]
+                    filesToAdd[folderId]
                             << new ResourceFileNode(Utils::FileName::fromString(fileName),
-                                                    qrcPath, this);
+                                                    qrcPath, displayName);
                 }
-
             }
         }
     }
 
-    QList<ProjectExplorer::FolderNode *> oldFolderList = subFolderNodes();
-    QList<ProjectExplorer::FolderNode *> foldersToAdd;
-    QList<ProjectExplorer::FolderNode *> foldersToRemove;
 
-    std::sort(oldFolderList.begin(), oldFolderList.end(), sortByPrefixAndLang);
-    std::sort(newFolderList.begin(), newFolderList.end(), sortByPrefixAndLang);
+    QList<ProjectExplorer::FolderNode *> oldPrefixList = subFolderNodes();
+    QList<ProjectExplorer::FolderNode *> prefixesToAdd;
+    QList<ProjectExplorer::FolderNode *> prefixesToRemove;
 
-    ProjectExplorer::compareSortedLists(oldFolderList, newFolderList, foldersToRemove, foldersToAdd, sortByPrefixAndLang);
+    Utils::sort(oldPrefixList, sortByPrefixAndLang);
+    Utils::sort(newPrefixList, sortByPrefixAndLang);
 
-    removeFolderNodes(foldersToRemove);
-    addFolderNodes(foldersToAdd);
+    ProjectExplorer::compareSortedLists(oldPrefixList, newPrefixList,
+                                        prefixesToRemove, prefixesToAdd, sortByPrefixAndLang);
 
-    // delete nodes that weren't added
-    qDeleteAll(ProjectExplorer::subtractSortedList(newFolderList, foldersToAdd, sortByPrefixAndLang));
+    removeFolderNodes(prefixesToRemove);
+    addFolderNodes(prefixesToAdd);
 
-    foreach (FolderNode *fn, subFolderNodes()) {
-        ResourceFolderNode *rn = static_cast<ResourceFolderNode *>(fn);
-        rn->updateFiles(filesToAdd.value(qMakePair(rn->prefix(), rn->lang())));
+    // delete nodes that weren't added
+    qDeleteAll(ProjectExplorer::subtractSortedList(newPrefixList, prefixesToAdd, sortByPrefixAndLang));
+
+    foreach (FolderNode *sfn, subFolderNodes()) {
+        ResourceFolderNode *srn = static_cast<ResourceFolderNode *>(sfn);
+        PrefixFolderLang folderId(srn->prefix(), QString(), srn->lang());
+        srn->updateFiles(filesToAdd[folderId]);
+        srn->updateFolders(foldersToAddToPrefix[folderId]);
+        foreach (FolderNode* ssfn, sfn->subFolderNodes()) {
+            SimpleResourceFolderNode *sssn = static_cast<SimpleResourceFolderNode *>(ssfn);
+            sssn->addFilesAndSubfolders(filesToAdd, foldersToAddToFolders, srn->prefix(), srn->lang());
+        }
     }
 }
 
@@ -303,6 +354,9 @@ ProjectExplorer::FolderNode::AddNewInformation ResourceTopLevelNode::addNewInfor
         if (ResourceFolderNode *rfn = dynamic_cast<ResourceFolderNode *>(context))
             if (rfn->prefix() == QLatin1String("/") && rfn->parentFolderNode() == this)
                 p = 120;
+        if (SimpleResourceFolderNode *rfn = dynamic_cast<SimpleResourceFolderNode *>(context))
+            if (rfn->prefix() == QLatin1String("/") && rfn->resourceNode() == this)
+                p = 120;
     }
 
     return AddNewInformation(name, p);
@@ -429,6 +483,11 @@ ProjectExplorer::FolderNode::AddNewInformation ResourceFolderNode::addNewInforma
         p = 105; // prefer against .pro and .pri files
         if (context == this)
             p = 120;
+
+        if (SimpleResourceFolderNode *sfn = dynamic_cast<SimpleResourceFolderNode *>(context)) {
+            if (sfn->prefixNode() == this)
+                p = 120;
+        }
     }
 
     return AddNewInformation(name, p);
@@ -462,8 +521,8 @@ void ResourceFolderNode::updateFiles(QList<ProjectExplorer::FileNode *> newList)
     QList<ProjectExplorer::FileNode *> filesToAdd;
     QList<ProjectExplorer::FileNode *> filesToRemove;
 
-    std::sort(oldList.begin(), oldList.end(), sortNodesByPath);
-    std::sort(newList.begin(), newList.end(), sortNodesByPath);
+    Utils::sort(oldList, sortNodesByPath);
+    Utils::sort(newList, sortNodesByPath);
 
     ProjectExplorer::compareSortedLists(oldList, newList, filesToRemove, filesToAdd, sortNodesByPath);
 
@@ -473,6 +532,23 @@ void ResourceFolderNode::updateFiles(QList<ProjectExplorer::FileNode *> newList)
     qDeleteAll(ProjectExplorer::subtractSortedList(newList, filesToAdd, sortNodesByPath));
 }
 
+void ResourceFolderNode::updateFolders(QList<ProjectExplorer::FolderNode *> newList)
+{
+    QList<ProjectExplorer::FolderNode *> oldList = subFolderNodes();
+    QList<ProjectExplorer::FolderNode *> foldersToAdd;
+    QList<ProjectExplorer::FolderNode *> foldersToRemove;
+
+    Utils::sort(oldList, sortNodesByPath);
+    Utils::sort(newList, sortNodesByPath);
+
+    ProjectExplorer::compareSortedLists(oldList, newList, foldersToRemove, foldersToAdd, sortNodesByPath);
+
+    removeFolderNodes(foldersToRemove);
+    addFolderNodes(foldersToAdd);
+
+    qDeleteAll(ProjectExplorer::subtractSortedList(newList, foldersToAdd, sortNodesByPath));
+}
+
 ResourceFileWatcher::ResourceFileWatcher(ResourceTopLevelNode *node)
     : IDocument(0), m_node(node)
 {
@@ -526,13 +602,11 @@ bool ResourceFileWatcher::reload(QString *errorString, ReloadFlag flag, ChangeTy
     return true;
 }
 
-ResourceFileNode::ResourceFileNode(const Utils::FileName &filePath, const QString &qrcPath, ResourceTopLevelNode *topLevel)
-    : ProjectExplorer::FileNode(filePath, ProjectExplorer::UnknownFileType, false),
-      m_qrcPath(qrcPath)
-
+ResourceFileNode::ResourceFileNode(const Utils::FileName &filePath, const QString &qrcPath, const QString &displayName)
+    : ProjectExplorer::FileNode(filePath, ProjectExplorer::UnknownFileType, false)
+    , m_qrcPath(qrcPath)
+    , m_displayName(displayName)
 {
-    QDir baseDir = topLevel->filePath().toFileInfo().absoluteDir();
-    m_displayName = QDir(baseDir).relativeFilePath(filePath.toString());
 }
 
 QString ResourceFileNode::displayName() const
@@ -551,3 +625,155 @@ QList<ProjectExplorer::ProjectAction> ResourceFileNode::supportedActions(Project
     actions.removeOne(ProjectExplorer::HidePathActions);
     return actions;
 }
+
+QString SimpleResourceFolderNode::displayName() const
+{
+    if (!m_displayName.isEmpty())
+        return m_displayName;
+    return FolderNode::displayName();
+}
+
+SimpleResourceFolderNode::SimpleResourceFolderNode(const QString &afolderName, const QString &displayName,
+                                   const QString &prefix, const QString &lang,
+                                   Utils::FileName absolutePath, ResourceTopLevelNode *topLevel, ResourceFolderNode *prefixNode)
+    : ProjectExplorer::FolderNode(absolutePath)
+    , m_folderName(afolderName)
+    , m_displayName(displayName)
+    , m_prefix(prefix)
+    , m_lang(lang)
+    , m_topLevelNode(topLevel)
+    , m_prefixNode(prefixNode)
+{
+
+}
+
+QList<ProjectExplorer::ProjectAction> SimpleResourceFolderNode::supportedActions(ProjectExplorer::Node *node) const
+{
+    Q_UNUSED(node)
+    QList<ProjectExplorer::ProjectAction> actions;
+    actions << ProjectExplorer::AddNewFile
+            << ProjectExplorer::AddExistingFile
+            << ProjectExplorer::AddExistingDirectory
+            << ProjectExplorer::RemoveFile
+            << ProjectExplorer::Rename // Note: only works for the filename, works akwardly for relative file paths
+            << ProjectExplorer::InheritedFromParent; // do not add to list of projects when adding new file
+
+    return actions;
+}
+
+bool SimpleResourceFolderNode::addFiles(const QStringList &filePaths, QStringList *notAdded)
+{
+    return addFilesToResource(m_topLevelNode->filePath(), filePaths, notAdded, m_prefix, m_lang);
+}
+
+bool SimpleResourceFolderNode::removeFiles(const QStringList &filePaths, QStringList *notRemoved)
+{
+    if (notRemoved)
+        *notRemoved = filePaths;
+    ResourceFile file(m_topLevelNode->filePath().toString());
+    if (file.load() != Core::IDocument::OpenResult::Success)
+        return false;
+    int index = file.indexOfPrefix(m_prefix, m_lang);
+    if (index == -1)
+        return false;
+    for (int j = 0; j < file.fileCount(index); ++j) {
+        const QString fileName = file.file(index, j);
+        if (!filePaths.contains(fileName))
+            continue;
+        if (notRemoved)
+            notRemoved->removeOne(fileName);
+        file.removeFile(index, j);
+        --j;
+    }
+    Core::DocumentManager::expectFileChange(m_topLevelNode->filePath().toString());
+    file.save();
+    Core::DocumentManager::unexpectFileChange(m_topLevelNode->filePath().toString());
+
+    return true;
+}
+
+bool SimpleResourceFolderNode::renameFile(const QString &filePath, const QString &newFilePath)
+{
+    ResourceFile file(m_topLevelNode->filePath().toString());
+    if (file.load() != Core::IDocument::OpenResult::Success)
+        return false;
+    int index = file.indexOfPrefix(m_prefix, m_lang);
+    if (index == -1)
+        return false;
+
+    for (int j = 0; j < file.fileCount(index); ++j) {
+        if (file.file(index, j) == filePath) {
+            file.replaceFile(index, j, newFilePath);
+            Core::DocumentManager::expectFileChange(m_topLevelNode->filePath().toString());
+            file.save();
+            Core::DocumentManager::unexpectFileChange(m_topLevelNode->filePath().toString());
+            return true;
+        }
+    }
+
+    return false;
+}
+
+QString SimpleResourceFolderNode::prefix() const
+{
+    return m_prefix;
+}
+
+ResourceTopLevelNode *SimpleResourceFolderNode::resourceNode() const
+{
+    return m_topLevelNode;
+}
+
+ResourceFolderNode *SimpleResourceFolderNode::prefixNode() const
+{
+    return m_prefixNode;
+}
+
+void SimpleResourceFolderNode::updateFiles(QList<ProjectExplorer::FileNode *> newList)
+{
+    QList<ProjectExplorer::FileNode *> oldList = fileNodes();
+    QList<ProjectExplorer::FileNode *> filesToAdd;
+    QList<ProjectExplorer::FileNode *> filesToRemove;
+
+    Utils::sort(oldList, sortNodesByPath);
+    Utils::sort(newList, sortNodesByPath);
+
+    ProjectExplorer::compareSortedLists(oldList, newList, filesToRemove, filesToAdd, sortNodesByPath);
+
+    removeFileNodes(filesToRemove);
+    addFileNodes(filesToAdd);
+
+    qDeleteAll(ProjectExplorer::subtractSortedList(newList, filesToAdd, sortNodesByPath));
+}
+
+void SimpleResourceFolderNode::updateFolders(QList<ProjectExplorer::FolderNode *> newList)
+{
+    QList<ProjectExplorer::FolderNode *> oldList = subFolderNodes();
+    QList<ProjectExplorer::FolderNode *> foldersToAdd;
+    QList<ProjectExplorer::FolderNode *> foldersToRemove;
+
+    Utils::sort(oldList, sortNodesByPath);
+    Utils::sort(newList, sortNodesByPath);
+
+    ProjectExplorer::compareSortedLists(oldList, newList, foldersToRemove, foldersToAdd, sortNodesByPath);
+
+    removeFolderNodes(foldersToRemove);
+    addFolderNodes(foldersToAdd);
+
+    qDeleteAll(ProjectExplorer::subtractSortedList(newList, foldersToAdd, sortNodesByPath));
+}
+
+
+void SimpleResourceFolderNode::addFilesAndSubfolders(QMap<PrefixFolderLang,
+                                                     QList<ProjectExplorer::FileNode *>> filesToAdd,
+                                                     QMap<PrefixFolderLang,
+                                                     QList<ProjectExplorer::FolderNode*> > foldersToAdd,
+                                                     const QString &prefix, const QString &lang)
+{
+    updateFiles(filesToAdd.value(PrefixFolderLang(prefix, m_folderName, lang)));
+    updateFolders(foldersToAdd.value(PrefixFolderLang(prefix, m_folderName, lang)));
+    foreach (FolderNode* subNode, subFolderNodes()) {
+        SimpleResourceFolderNode* sn = static_cast<SimpleResourceFolderNode*>(subNode);
+        sn->addFilesAndSubfolders(filesToAdd, foldersToAdd, prefix, lang);
+    }
+}
diff --git a/src/plugins/resourceeditor/resourcenode.h b/src/plugins/resourceeditor/resourcenode.h
index f503d5e4aef..9deb034fc3c 100644
--- a/src/plugins/resourceeditor/resourcenode.h
+++ b/src/plugins/resourceeditor/resourcenode.h
@@ -67,6 +67,32 @@ private:
 };
 
 namespace Internal {
+
+class PrefixFolderLang
+{
+public:
+    PrefixFolderLang(QString prefix, QString folder, QString lang)
+        : m_prefix(prefix)
+        , m_folder(folder)
+        , m_lang(lang)
+    {}
+
+    bool operator<(const PrefixFolderLang &other) const
+    {
+        if (m_prefix != other.m_prefix)
+            return m_prefix < other.m_prefix;
+        if (m_folder != other.m_folder)
+            return m_folder < other.m_folder;
+        if (m_lang != other.m_lang)
+            return m_lang < other.m_lang;
+        return false;
+    }
+private:
+    QString m_prefix;
+    QString m_folder;
+    QString m_lang;
+};
+
 class ResourceFolderNode : public ProjectExplorer::FolderNode
 {
     friend class ResourceEditor::ResourceTopLevelNode; // for updateFiles
@@ -90,24 +116,56 @@ public:
     QString lang() const;
     ResourceTopLevelNode *resourceNode() const;
 private:
+    void updateFolders(QList<ProjectExplorer::FolderNode *> newList);
     void updateFiles(QList<ProjectExplorer::FileNode *> newList);
     ResourceTopLevelNode *m_topLevelNode;
     QString m_prefix;
     QString m_lang;
 };
 
+class SimpleResourceFolderNode : public ProjectExplorer::FolderNode
+{
+    friend class ResourceEditor::ResourceTopLevelNode;
+public:
+    QString displayName() const;
+    SimpleResourceFolderNode(const QString &afolderName, const QString &displayName,
+                     const QString &prefix, const QString &lang, Utils::FileName absolutePath,
+                     ResourceTopLevelNode *topLevel, ResourceFolderNode *prefixNode);
+    QList<ProjectExplorer::ProjectAction> supportedActions(ProjectExplorer::Node *node) const;
+    void addFilesAndSubfolders(QMap<PrefixFolderLang, QList<ProjectExplorer::FileNode *>> filesToAdd,
+                               QMap<PrefixFolderLang, QList<ProjectExplorer::FolderNode *>> foldersToAdd,
+                               const QString &prefix, const QString &lang);
+    bool addFiles(const QStringList &filePaths, QStringList *notAdded);
+    bool removeFiles(const QStringList &filePaths, QStringList *notRemoved);
+    bool renameFile(const QString &filePath, const QString &newFilePath);
+
+    QString prefix() const;
+    ResourceTopLevelNode *resourceNode() const;
+    ResourceFolderNode *prefixNode() const;
+
+private:
+    void updateFiles(QList<ProjectExplorer::FileNode *> newList);
+    void updateFolders(QList<ProjectExplorer::FolderNode *> newList);
+    QString m_folderName;
+    QString m_displayName;
+    QString m_prefix;
+    QString m_lang;
+    ResourceTopLevelNode *m_topLevelNode;
+    ResourceFolderNode *m_prefixNode;
+};
+
 class ResourceFileNode : public ProjectExplorer::FileNode
 {
 public:
-    ResourceFileNode(const Utils::FileName &filePath, const QString &qrcPath, ResourceTopLevelNode *topLevel);
+    ResourceFileNode(const Utils::FileName &filePath, const QString &qrcPath, const QString &displayName);
 
     QString displayName() const override;
     QString qrcPath() const;
     QList<ProjectExplorer::ProjectAction> supportedActions(Node *node) const override;
 
 private:
-    QString m_displayName;
     QString m_qrcPath;
+    QString m_displayName;
 };
 
 class ResourceFileWatcher : public Core::IDocument
-- 
GitLab