diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index e78809ff850..a8461a61f33 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -71,6 +71,7 @@ add_qtc_library(Utils htmldocextractor.cpp htmldocextractor.h icon.cpp icon.h id.cpp id.h + indexedcontainerproxyconstiterator.h infobar.cpp infobar.h infolabel.cpp infolabel.h itemviews.cpp itemviews.h diff --git a/src/libs/utils/indexedcontainerproxyconstiterator.h b/src/libs/utils/indexedcontainerproxyconstiterator.h new file mode 100644 index 00000000000..7e9dd4c82a6 --- /dev/null +++ b/src/libs/utils/indexedcontainerproxyconstiterator.h @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "utils_global.h" + +#include + +#include + +namespace Utils { + +/// A class useful for the implementation of the -> operator of proxy iterators. +template +struct ArrowProxy { + Reference r; + Reference *operator->() { + return &r; + } +}; + +/// Random-access const iterator over elements of a container providing an overloaded operator[] +/// (which may return a proxy object, like std::vector, rather than a reference). +template +class IndexedContainerProxyConstIterator +{ +public: + typedef std::random_access_iterator_tag iterator_category; + typedef typename std::make_signed::type difference_type; + typedef typename Container::value_type value_type; + typedef value_type reference; + typedef ArrowProxy pointer; + typedef typename Container::size_type size_type; + + IndexedContainerProxyConstIterator() + : m_container(nullptr), m_index(0) + {} + + IndexedContainerProxyConstIterator(const Container &container, size_type index) + : m_container(&container), m_index(index) + {} + + reference operator*() const + { + Q_ASSERT(m_container); + return (*m_container)[m_index]; + } + + pointer operator->() const + { + Q_ASSERT(m_container); + return pointer{(*m_container)[m_index]}; + } + + reference operator[](difference_type j) const + { + Q_ASSERT(m_container); + return (*m_container)[m_index + j]; + } + + bool operator==(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index == other.m_index; + } + + bool operator!=(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index != other.m_index; + } + + bool operator<(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index < other.m_index; + } + + bool operator<=(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index <= other.m_index; + } + + bool operator>(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index > other.m_index; + } + + bool operator>=(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index >= other.m_index; + } + + IndexedContainerProxyConstIterator &operator++() + { + ++m_index; + return *this; + } + + IndexedContainerProxyConstIterator operator++(int) + { + IndexedContainerProxyConstIterator copy(*this); + ++m_index; + return copy; + } + + IndexedContainerProxyConstIterator &operator--() + { + --m_index; + return *this; + } + + IndexedContainerProxyConstIterator operator--(int) + { + IndexedContainerProxyConstIterator copy(*this); + --m_index; + return copy; + } + + IndexedContainerProxyConstIterator &operator+=(difference_type j) + { + m_index += j; + return *this; + } + + IndexedContainerProxyConstIterator &operator-=(difference_type j) + { + m_index -= j; + return *this; + } + + IndexedContainerProxyConstIterator operator+(difference_type j) const + { + IndexedContainerProxyConstIterator result(*this); + result += j; + return result; + } + + IndexedContainerProxyConstIterator operator-(difference_type j) const + { + IndexedContainerProxyConstIterator result(*this); + result -= j; + return result; + } + + friend IndexedContainerProxyConstIterator operator+( + difference_type j, const IndexedContainerProxyConstIterator &other) + { + return other + j; + } + + difference_type operator-(const IndexedContainerProxyConstIterator &other) const + { + return static_cast(m_index) - static_cast(other.m_index); + } + +private: + const Container *m_container; + size_type m_index; +}; + +} // namespace Utils diff --git a/src/libs/utils/treemodel.h b/src/libs/utils/treemodel.h index 53a6a3900ea..7240d8d6b6b 100644 --- a/src/libs/utils/treemodel.h +++ b/src/libs/utils/treemodel.h @@ -26,6 +26,7 @@ #pragma once #include "utils_global.h" +#include "indexedcontainerproxyconstiterator.h" #include @@ -119,6 +120,15 @@ public: }); } + using value_type = ChildType *; + using const_iterator = IndexedContainerProxyConstIterator; + using size_type = int; + + ChildType *operator[](int index) const { return childAt(index); } + int size() const { return childCount(); } + const_iterator begin() const { return const_iterator(*this, 0); } + const_iterator end() const { return const_iterator(*this, size()); } + template void forAllChildren(const Predicate &pred) const { const auto pred0 = [pred](TreeItem *treeItem) { pred(static_cast(treeItem)); }; diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri index 872675f306d..b3d6a7a6dd0 100644 --- a/src/libs/utils/utils-lib.pri +++ b/src/libs/utils/utils-lib.pri @@ -150,6 +150,7 @@ HEADERS += \ $$PWD/environmentfwd.h \ $$PWD/genericconstants.h \ $$PWD/globalfilechangeblocker.h \ + $$PWD/indexedcontainerproxyconstiterator.h \ $$PWD/benchmarker.h \ $$PWD/displayname.h \ $$PWD/environment.h \ diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index d9211602086..7a78cb1f414 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -149,6 +149,7 @@ Project { "icon.h", "id.cpp", "id.h", + "indexedcontainerproxyconstiterator.h", "infobar.cpp", "infobar.h", "infolabel.cpp", diff --git a/src/plugins/projectexplorer/projectmodels.cpp b/src/plugins/projectexplorer/projectmodels.cpp index 1ba9625ecf2..0565edcc523 100644 --- a/src/plugins/projectexplorer/projectmodels.cpp +++ b/src/plugins/projectexplorer/projectmodels.cpp @@ -61,6 +61,7 @@ #include #include +#include #include #include @@ -69,6 +70,28 @@ using namespace Utils; namespace ProjectExplorer { namespace Internal { +/// An output iterator whose assignment operator appends a clone of the operand to the list of +/// children of the WrapperNode passed to the constructor. +class Appender : public std::iterator +{ +public: + explicit Appender(WrapperNode *parent) : m_parent(parent) {} + + Appender &operator=(const WrapperNode *node) + { + if (node) + m_parent->appendClone(*node); + return *this; + } + + Appender &operator*() { return *this; } + Appender &operator++() { return *this; } + Appender &operator++(int) { return *this; } + +private: + WrapperNode *m_parent; +}; + bool compareNodes(const Node *n1, const Node *n2) { if (n1->priority() > n2->priority()) @@ -82,9 +105,7 @@ bool compareNodes(const Node *n1, const Node *n2) const int filePathResult = caseFriendlyCompare(n1->filePath().toString(), n2->filePath().toString()); - if (filePathResult != 0) - return filePathResult < 0; - return n1 < n2; // sort by pointer value + return filePathResult < 0; } static bool sortWrapperNodes(const WrapperNode *w1, const WrapperNode *w2) @@ -92,6 +113,71 @@ static bool sortWrapperNodes(const WrapperNode *w1, const WrapperNode *w2) return compareNodes(w1->m_node, w2->m_node); } +/// Appends to `dest` clones of children of `first` and `second`, removing duplicates (recursively). +/// +/// \param first, second +/// Nodes with children sorted by sortWrapperNodes. +/// \param dest +/// Node to which to append clones of children of `first` and `second`, with duplicates removed. +static void appendMergedChildren(const WrapperNode *first, const WrapperNode *second, WrapperNode *dest) +{ + setUnionMerge(first->begin(), first->end(), + second->begin(), second->end(), + Appender(dest), + [dest](const WrapperNode *childOfFirst, const WrapperNode *childOfSecond) + -> const WrapperNode * { + if (childOfSecond->hasChildren()) { + if (childOfFirst->hasChildren()) { + WrapperNode *mergeResult = new WrapperNode(childOfFirst->m_node); + dest->appendChild(mergeResult); + appendMergedChildren(childOfFirst, childOfSecond, mergeResult); + // mergeResult has already been appended to the parent's list of + // children -- there's no need for the Appender to do it again. + // That's why we return a null pointer. + return nullptr; + } else { + return childOfSecond; + } + } else { + return childOfFirst; + } + }, + sortWrapperNodes); +} + +/// Given a node `parent` with children sorted by the criteria defined in sortWrapperNodes(), merge +/// any children that are equal according to those criteria. +static void mergeDuplicates(WrapperNode *parent) +{ + // We assume all descendants of 'parent' are sorted + int childIndex = 0; + while (childIndex + 1 < parent->childCount()) { + const WrapperNode *child = parent->childAt(childIndex); + const WrapperNode *nextChild = parent->childAt(childIndex + 1); + Q_ASSERT_X(!sortWrapperNodes(nextChild, child), __func__, "Children are not sorted"); + if (!sortWrapperNodes(child, nextChild)) { + // child and nextChild must have the same priorities, display names and folder paths. + // Replace them by a single node 'mergeResult` containing the union of their children. + auto mergeResult = new WrapperNode(child->m_node); + parent->insertChild(childIndex, mergeResult); + appendMergedChildren(child, nextChild, mergeResult); + // Now we can remove the original children + parent->removeChildAt(childIndex + 2); + parent->removeChildAt(childIndex + 1); + } else { + ++childIndex; + } + } +} + +void WrapperNode::appendClone(const WrapperNode &node) +{ + WrapperNode *clone = new WrapperNode(node.m_node); + appendChild(clone); + for (const WrapperNode *child : node) + clone->appendClone(*child); +} + FlatModel::FlatModel(QObject *parent) : TreeModel(new WrapperNode(nullptr), parent) { @@ -393,6 +479,8 @@ void FlatModel::saveExpandData() void FlatModel::addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet *seen) { + bool hasHiddenSourcesOrHeaders = false; + for (Node *node : folderNode->nodes()) { if (m_filterGeneratedFiles && node->isGenerated()) continue; @@ -403,8 +491,10 @@ void FlatModel::addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet< if (!m_showSourceGroups) { if (subFolderNode->isVirtualFolderType()) { auto vnode = static_cast(subFolderNode); - if (vnode->isSourcesOrHeaders()) + if (vnode->isSourcesOrHeaders()) { isHidden = true; + hasHiddenSourcesOrHeaders = true; + } } } if (!isHidden && !seen->contains(subFolderNode)) { @@ -423,6 +513,11 @@ void FlatModel::addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet< } } } + + if (hasHiddenSourcesOrHeaders) { + parent->sortChildren(&sortWrapperNodes); + mergeDuplicates(parent); + } } bool FlatModel::trimEmptyDirectories(WrapperNode *parent) diff --git a/src/plugins/projectexplorer/projectmodels.h b/src/plugins/projectexplorer/projectmodels.h index 4c7b4012706..2da85b1a858 100644 --- a/src/plugins/projectexplorer/projectmodels.h +++ b/src/plugins/projectexplorer/projectmodels.h @@ -52,6 +52,8 @@ class WrapperNode : public Utils::TypedTreeItem public: explicit WrapperNode(Node *node) : m_node(node) {} Node *m_node = nullptr; + + void appendClone(const WrapperNode &node); }; class FlatModel : public Utils::TreeModel diff --git a/src/plugins/projectexplorer/projectnodes.h b/src/plugins/projectexplorer/projectnodes.h index bba13fe5f15..b739a93f1ca 100644 --- a/src/plugins/projectexplorer/projectnodes.h +++ b/src/plugins/projectexplorer/projectnodes.h @@ -361,7 +361,7 @@ public: void setIsSourcesOrHeaders(bool on) { m_isSourcesOrHeaders = on; } private: - bool m_isSourcesOrHeaders; // "Sources" or "Headers" + bool m_isSourcesOrHeaders = false; // "Sources" or "Headers" }; // Documentation inside. diff --git a/tests/auto/utils/CMakeLists.txt b/tests/auto/utils/CMakeLists.txt index 4b341d6b3c1..63eb475063b 100644 --- a/tests/auto/utils/CMakeLists.txt +++ b/tests/auto/utils/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(ansiescapecodehandler) add_subdirectory(fileutils) add_subdirectory(fuzzymatcher) +add_subdirectory(indexedcontainerproxyconstiterator) add_subdirectory(persistentsettings) add_subdirectory(qtcprocess) add_subdirectory(settings) diff --git a/tests/auto/utils/indexedcontainerproxyconstiterator/CMakeLists.txt b/tests/auto/utils/indexedcontainerproxyconstiterator/CMakeLists.txt new file mode 100644 index 00000000000..00a6adb4ed9 --- /dev/null +++ b/tests/auto/utils/indexedcontainerproxyconstiterator/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_utils_indexedcontainerproxyconstiterator + DEPENDS Utils + SOURCES tst_indexedcontainerproxyconstiterator.cpp +) diff --git a/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.pro b/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.pro new file mode 100644 index 00000000000..b0f9d7cc7e3 --- /dev/null +++ b/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.pro @@ -0,0 +1,4 @@ +QTC_LIB_DEPENDS += utils +include(../../qttest.pri) + +SOURCES += tst_indexedcontainerproxyconstiterator.cpp diff --git a/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs b/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs new file mode 100644 index 00000000000..461c89e4866 --- /dev/null +++ b/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs @@ -0,0 +1,7 @@ +import qbs + +QtcAutotest { + name: "IndexedContainerProxyConstIterator autotest" + Depends { name: "Utils" } + files: "tst_indexedcontainerproxyconstiterator.cpp" +} diff --git a/tests/auto/utils/indexedcontainerproxyconstiterator/tst_indexedcontainerproxyconstiterator.cpp b/tests/auto/utils/indexedcontainerproxyconstiterator/tst_indexedcontainerproxyconstiterator.cpp new file mode 100644 index 00000000000..f0dceb0eb24 --- /dev/null +++ b/tests/auto/utils/indexedcontainerproxyconstiterator/tst_indexedcontainerproxyconstiterator.cpp @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include + +#include + +#include +#include + +using namespace Utils; + +class tst_IndexedContainerProxyConstIterator : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testConstruction(); + void testDereference(); + void testIndexing(); + void testComparisons(); + void testIncrement(); + void testDecrement(); + void testPlus(); + void testMinus(); + void testIteration(); + +private: + using StringContainer = std::vector; + using StringIterator = IndexedContainerProxyConstIterator; + StringContainer strings; + + using BoolContainer = std::vector; + using BoolIterator = IndexedContainerProxyConstIterator; + BoolContainer bools; +}; + +void tst_IndexedContainerProxyConstIterator::initTestCase() +{ + strings = {"abc", "defgh", "ijk"}; + bools = {false, true, false}; +} + +void tst_IndexedContainerProxyConstIterator::testConstruction() +{ + StringIterator strIt(strings, 0); + QCOMPARE(*strIt, "abc"); + + StringIterator strIt2(strings, 1); + QCOMPARE(*strIt2, "defgh"); + + BoolIterator boolIt(bools, 0); + QCOMPARE(*boolIt, false); + + BoolIterator boolIt2(bools, 1); + QCOMPARE(*boolIt2, true); +} + +void tst_IndexedContainerProxyConstIterator::testDereference() +{ + StringIterator strIt(strings, 0); + QCOMPARE(*strIt, "abc"); + QCOMPARE(strIt->length(), 3); + + BoolIterator boolIt(bools, 0); + QCOMPARE(*boolIt, false); +} + +void tst_IndexedContainerProxyConstIterator::testIndexing() +{ + StringIterator strIt(strings, 0); + QCOMPARE(strIt[2], "ijk"); + + BoolIterator boolIt(bools, 0); + QCOMPARE(boolIt[2], false); +} + +void tst_IndexedContainerProxyConstIterator::testComparisons() +{ + StringIterator strIt(strings, 0); + StringIterator strIt2(strings, 0); + StringIterator strIt3(strings, 1); + + QVERIFY(strIt == strIt); + QVERIFY(!(strIt != strIt)); + QVERIFY(!(strIt < strIt)); + QVERIFY(strIt <= strIt); + QVERIFY(!(strIt > strIt)); + QVERIFY(strIt >= strIt); + + QVERIFY(strIt == strIt2); + QVERIFY(!(strIt != strIt2)); + QVERIFY(!(strIt < strIt2)); + QVERIFY(strIt <= strIt2); + QVERIFY(!(strIt > strIt2)); + QVERIFY(strIt >= strIt2); + + QVERIFY(!(strIt == strIt3)); + QVERIFY(strIt != strIt3); + QVERIFY(strIt < strIt3); + QVERIFY(strIt <= strIt3); + QVERIFY(!(strIt > strIt3)); + QVERIFY(!(strIt >= strIt3)); + + QVERIFY(!(strIt3 == strIt)); + QVERIFY(strIt3 != strIt); + QVERIFY(!(strIt3 < strIt)); + QVERIFY(!(strIt3 <= strIt)); + QVERIFY(strIt3 > strIt); + QVERIFY(strIt3 >= strIt); +} + +void tst_IndexedContainerProxyConstIterator::testIncrement() +{ + StringIterator strIt(strings, 0); + QCOMPARE(*(++strIt), "defgh"); + QCOMPARE(*(strIt++), "defgh"); + QCOMPARE(*strIt, "ijk"); + + BoolIterator boolIt(bools, 0); + QCOMPARE(*(++boolIt), true); + QCOMPARE(*(boolIt++), true); + QCOMPARE(*boolIt, false); +} + +void tst_IndexedContainerProxyConstIterator::testDecrement() +{ + StringIterator strIt(strings, 3); + QCOMPARE(*(--strIt), "ijk"); + QCOMPARE(*(strIt--), "ijk"); + QCOMPARE(*strIt, "defgh"); + + BoolIterator boolIt(bools, 3); + QCOMPARE(*(--boolIt), false); + QCOMPARE(*(boolIt--), false); + QCOMPARE(*boolIt, true); +} + +void tst_IndexedContainerProxyConstIterator::testPlus() +{ + StringIterator strIt(strings, 1); + QCOMPARE(*(strIt + 1), "ijk"); + QCOMPARE(*(1 + strIt), "ijk"); + strIt += 1; + QCOMPARE(*strIt, "ijk"); + + BoolIterator boolIt(bools, 1); + QCOMPARE(*(boolIt + 1), false); + QCOMPARE(*(1 + boolIt), false); + boolIt += 1; + QCOMPARE(*boolIt, false); +} + +void tst_IndexedContainerProxyConstIterator::testMinus() +{ + StringIterator strIt(strings, 1); + QCOMPARE(*(strIt - 1), "abc"); + strIt -= 1; + QCOMPARE(*strIt, "abc"); + + BoolIterator boolIt(bools, 1); + QCOMPARE(*(boolIt - 1), false); + boolIt -= 1; + QCOMPARE(*boolIt, false); +} + +void tst_IndexedContainerProxyConstIterator::testIteration() +{ + StringIterator strBegin(strings, 0); + StringIterator strEnd(strings, strings.size()); + StringContainer stringsCopy; + for (StringIterator it = strBegin; it != strEnd; ++it) + stringsCopy.push_back(*it); + QCOMPARE(stringsCopy, strings); + + BoolIterator boolBegin(bools, 0); + BoolIterator boolEnd(bools, bools.size()); + BoolContainer boolsCopy; + for (BoolIterator it = boolBegin; it != boolEnd; ++it) + boolsCopy.push_back(*it); + QCOMPARE(boolsCopy, bools); +} + +QTEST_MAIN(tst_IndexedContainerProxyConstIterator) + +#include "tst_indexedcontainerproxyconstiterator.moc" diff --git a/tests/auto/utils/utils.pro b/tests/auto/utils/utils.pro index 2e15c4379ed..e0f630ad2b5 100644 --- a/tests/auto/utils/utils.pro +++ b/tests/auto/utils/utils.pro @@ -4,6 +4,7 @@ SUBDIRS = \ fileutils \ ansiescapecodehandler \ fuzzymatcher \ + indexedcontainerproxyconstiterator \ persistentsettings \ qtcprocess \ settings \ diff --git a/tests/auto/utils/utils.qbs b/tests/auto/utils/utils.qbs index 5b11f46e7bc..b9a4f9a6116 100644 --- a/tests/auto/utils/utils.qbs +++ b/tests/auto/utils/utils.qbs @@ -6,6 +6,7 @@ Project { "fileutils/fileutils.qbs", "ansiescapecodehandler/ansiescapecodehandler.qbs", "fuzzymatcher/fuzzymatcher.qbs", + "indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs", "persistentsettings/persistentsettings.qbs", "qtcprocess/qtcprocess.qbs", "settings/settings.qbs",