From c79ca4e8152ab25091cc2c8ea35a30d7b94432e9 Mon Sep 17 00:00:00 2001 From: attila Date: Fri, 2 Jun 2023 13:33:16 +0200 Subject: [PATCH] FileTreeComponent: Order items according to OS specific rules This makes the ordering consistent with other view modes of the FileBrowserComponent and restores the behaviour prior to a400d3ebe08ca74d448d9de4f416b186868b3464. --- .../filebrowser/juce_FileTreeComponent.cpp | 184 +++++++++++++++++- 1 file changed, 178 insertions(+), 6 deletions(-) diff --git a/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp b/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp index 926d3085f0..3ebc44c686 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp @@ -26,6 +26,56 @@ namespace juce { +template +int threeWayCompare (const T& a, const T& b) +{ + if (a < b) return -1; + if (b < a) return 1; + return 0; +} + +int threeWayCompare (const String& a, const String& b); +int threeWayCompare (const String& a, const String& b) +{ + return a.compare (b); +} + +struct ReverseCompareString +{ + String value; +}; + +int threeWayCompare (const ReverseCompareString& a, const ReverseCompareString& b); +int threeWayCompare (const ReverseCompareString& a, const ReverseCompareString& b) +{ + return b.value.compare (a.value); +} + +template +constexpr int threeWayCompareImpl (const std::tuple& a, const std::tuple& b) +{ + if constexpr (position == sizeof... (Ts)) + { + ignoreUnused (a, b); + return 0; + } + else + { + const auto head = threeWayCompare (std::get (a), std::get (b)); + + if (head != 0) + return head; + + return threeWayCompareImpl (a, b); + } +} + +template +constexpr int threeWayCompare (const std::tuple& a, const std::tuple& b) +{ + return threeWayCompareImpl<0> (a, b); +} + //============================================================================== class FileListTreeItem : public TreeViewItem, private TimeSliceClient, @@ -255,6 +305,56 @@ private: std::map contentsLists; }; +struct FileEntry +{ + String path; + bool isDirectory; + + int compareWindows (const FileEntry& other) const + { + const auto toTuple = [] (const auto& x) { return std::tuple (! x.isDirectory, x.path.toLowerCase()); }; + return threeWayCompare (toTuple (*this), toTuple (other)); + } + + int compareLinux (const FileEntry& other) const + { + const auto toTuple = [] (const auto& x) { return std::tuple (x.path.toUpperCase(), ReverseCompareString { x.path }); }; + return threeWayCompare (toTuple (*this), toTuple (other)); + } + + int compareDefault (const FileEntry& other) const + { + return threeWayCompare (path.toLowerCase(), other.path.toLowerCase()); + } +}; + +class OSDependentFileComparisonRules +{ +public: + explicit OSDependentFileComparisonRules (SystemStats::OperatingSystemType systemTypeIn) + : systemType (systemTypeIn) + {} + + int compare (const FileEntry& first, const FileEntry& second) const + { + if ((systemType & SystemStats::OperatingSystemType::Windows) != 0) + return first.compareWindows (second); + + if ((systemType & SystemStats::OperatingSystemType::Linux) != 0) + return first.compareLinux (second); + + return first.compareDefault (second); + } + + bool operator() (const FileEntry& first, const FileEntry& second) const + { + return compare (first, second) < 0; + } + +private: + SystemStats::OperatingSystemType systemType; +}; + class FileTreeComponent::Controller : private DirectoryScanner::Listener { public: @@ -373,6 +473,10 @@ private: struct Comparator { + // The different OSes compare and order files in different ways. This function aims + // to match these different rules of comparison to mimic other FileBrowserComponent + // view modes where we don't need to order the results, and can just rely on the + // ordering of the list provided by the OS. static int compareElements (TreeViewItem* first, TreeViewItem* second) { auto* item1 = dynamic_cast (first); @@ -381,13 +485,10 @@ private: if (item1 == nullptr || item2 == nullptr) return 0; - if (item1->file < item2->file) - return -1; + static const OSDependentFileComparisonRules comparisonRules { SystemStats::getOperatingSystemType() }; - if (item1->file > item2->file) - return 1; - - return 0; + return comparisonRules.compare ({ item1->file.getFullPathName(), item1->file.isDirectory() }, + { item2->file.getFullPathName(), item2->file.isDirectory() }); } }; @@ -506,4 +607,75 @@ void FileTreeComponent::setItemHeight (int newHeight) } } +#if JUCE_UNIT_TESTS + +class FileTreeComponentTests : public UnitTest +{ +public: + //============================================================================== + FileTreeComponentTests() : UnitTest ("FileTreeComponentTests", UnitTestCategories::gui) {} + + void runTest() override + { + const auto checkOrder = [] (const auto& orderedFiles, const std::vector& expected) + { + return std::equal (orderedFiles.begin(), orderedFiles.end(), + expected.begin(), expected.end(), + [] (const auto& entry, const auto& expectedPath) { return entry.path == expectedPath; }); + }; + + const auto doSort = [] (const auto platform, auto& range) + { + std::sort (range.begin(), range.end(), OSDependentFileComparisonRules { platform }); + }; + + beginTest ("Test Linux filename ordering"); + { + std::vector filesToOrder { { "_test", false }, + { "Atest", false }, + { "atest", false } }; + + doSort (SystemStats::OperatingSystemType::Linux, filesToOrder); + + expect (checkOrder (filesToOrder, { "atest", "Atest", "_test" })); + } + + beginTest ("Test Windows filename ordering"); + { + std::vector filesToOrder { { "cmake_install.cmake", false }, + { "CMakeFiles", true }, + { "JUCEConfig.cmake", false }, + { "tools", true }, + { "cmakefiles.cmake", false } }; + + doSort (SystemStats::OperatingSystemType::Windows, filesToOrder); + + expect (checkOrder (filesToOrder, { "CMakeFiles", + "tools", + "cmake_install.cmake", + "cmakefiles.cmake", + "JUCEConfig.cmake" })); + } + + beginTest ("Test MacOS filename ordering"); + { + std::vector filesToOrder { { "cmake_install.cmake", false }, + { "CMakeFiles", true }, + { "tools", true }, + { "JUCEConfig.cmake", false } }; + + doSort (SystemStats::OperatingSystemType::MacOSX, filesToOrder); + + expect (checkOrder (filesToOrder, { "cmake_install.cmake", + "CMakeFiles", + "JUCEConfig.cmake", + "tools" })); + } + } +}; + +static FileTreeComponentTests fileTreeComponentTests; + +#endif + } // namespace juce