JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_FileTreeComponent.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29template <typename T>
30int threeWayCompare (const T& a, const T& b)
31{
32 if (a < b) return -1;
33 if (b < a) return 1;
34 return 0;
35}
36
37int threeWayCompare (const String& a, const String& b);
38int threeWayCompare (const String& a, const String& b)
39{
40 return a.compare (b);
41}
42
44{
45 String value;
46};
47
48int threeWayCompare (const ReverseCompareString& a, const ReverseCompareString& b);
49int threeWayCompare (const ReverseCompareString& a, const ReverseCompareString& b)
50{
51 return b.value.compare (a.value);
52}
53
54template <size_t position, typename... Ts>
55constexpr int threeWayCompareImpl (const std::tuple<Ts...>& a, const std::tuple<Ts...>& b)
56{
57 if constexpr (position == sizeof... (Ts))
58 {
59 ignoreUnused (a, b);
60 return 0;
61 }
62 else
63 {
64 const auto head = threeWayCompare (std::get<position> (a), std::get<position> (b));
65
66 if (head != 0)
67 return head;
68
70 }
71}
72
73template <typename... Ts>
74constexpr int threeWayCompare (const std::tuple<Ts...>& a, const std::tuple<Ts...>& b)
75{
76 return threeWayCompareImpl<0> (a, b);
77}
78
79//==============================================================================
81 private TimeSliceClient,
82 private AsyncUpdater
83{
84public:
86 const File& f,
88 : file (f),
89 owner (treeComp),
90 thread (t)
91 {
92 }
93
94 void update (const DirectoryContentsList::FileInfo& fileInfo)
95 {
96 fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize);
97 modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M");
98 isDirectory = fileInfo.isDirectory;
100 }
101
102 ~FileListTreeItem() override
103 {
104 thread.removeTimeSliceClient (this);
106 }
107
108 //==============================================================================
109 bool mightContainSubItems() override { return isDirectory; }
110 String getUniqueName() const override { return file.getFullPathName(); }
111 int getItemHeight() const override { return owner.getItemHeight(); }
112
114
115 void itemOpennessChanged (bool isNowOpen) override
116 {
117 NullCheckedInvocation::invoke (onOpennessChanged, file, isNowOpen);
118 }
119
120 void paintItem (Graphics& g, int width, int height) override
121 {
122 ScopedLock lock (iconUpdate);
123
124 if (file != File())
125 {
126 updateIcon (true);
127
128 if (icon.isNull())
129 thread.addTimeSliceClient (this);
130 }
131
132 owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
133 file, file.getFileName(),
134 &icon, fileSize, modTime,
135 isDirectory, isSelected(),
136 getIndexInParent(), owner);
137 }
138
140 {
141 return file.getFileName();
142 }
143
144 void itemClicked (const MouseEvent& e) override
145 {
146 owner.sendMouseClickMessage (file, e);
147 }
148
149 void itemDoubleClicked (const MouseEvent& e) override
150 {
152
153 owner.sendDoubleClickMessage (file);
154 }
155
156 void itemSelectionChanged (bool) override
157 {
158 owner.sendSelectionChangeMessage();
159 }
160
161 int useTimeSlice() override
162 {
163 updateIcon (false);
164 return -1;
165 }
166
167 void handleAsyncUpdate() override
168 {
169 owner.repaint();
170 }
171
172 const File file;
173 std::function<void (const File&, bool)> onOpennessChanged;
174
175private:
176 FileTreeComponent& owner;
177 bool isDirectory = false;
178 TimeSliceThread& thread;
179 CriticalSection iconUpdate;
180 Image icon;
181 String fileSize, modTime;
182
183 void updateIcon (const bool onlyUpdateIfCached)
184 {
185 if (icon.isNull())
186 {
187 auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
188 auto im = ImageCache::getFromHashCode (hashCode);
189
190 if (im.isNull() && ! onlyUpdateIfCached)
191 {
192 im = detail::WindowingHelpers::createIconForFile (file);
193
194 if (im.isValid())
196 }
197
198 if (im.isValid())
199 {
200 {
201 ScopedLock lock (iconUpdate);
202 icon = im;
203 }
204
206 }
207 }
208 }
209
211};
212
214{
215public:
216 struct Listener
217 {
218 virtual ~Listener() = default;
219
220 virtual void rootChanged() = 0;
221 virtual void directoryChanged (const DirectoryContentsList&) = 0;
222 };
223
225 : root (rootIn), listener (listenerIn)
226 {
227 root.addChangeListener (this);
228 }
229
230 ~DirectoryScanner() override
231 {
232 root.removeChangeListener (this);
233 }
234
235 void refresh()
236 {
237 root.refresh();
238 }
239
240 void open (const File& f)
241 {
242 auto& contentsList = [&]() -> auto&
243 {
244 if (auto it = contentsLists.find (f); it != contentsLists.end())
245 return it->second;
246
247 auto insertion = contentsLists.emplace (std::piecewise_construct,
250 root.getTimeSliceThread()));
251 return insertion.first->second;
252 }();
253
254 contentsList.addChangeListener (this);
255 contentsList.setDirectory (f, true, true);
256 contentsList.refresh();
257 }
258
259 void close (const File& f)
260 {
261 if (auto it = contentsLists.find (f); it != contentsLists.end())
262 contentsLists.erase (it);
263 }
264
265 File getRootDirectory() const
266 {
267 return root.getDirectory();
268 }
269
270 bool isStillLoading() const
271 {
272 return std::any_of (contentsLists.begin(),
273 contentsLists.end(),
274 [] (const auto& it)
275 {
276 return it.second.isStillLoading();
277 });
278 }
279
280private:
281 void changeListenerCallback (ChangeBroadcaster* source) override
282 {
283 auto* sourceList = static_cast<DirectoryContentsList*> (source);
284
285 if (sourceList == &root)
286 {
287 if (std::exchange (lastDirectory, root.getDirectory()) != root.getDirectory())
288 {
289 contentsLists.clear();
290 listener.rootChanged();
291 }
292 else
293 {
294 for (auto& contentsList : contentsLists)
295 contentsList.second.refresh();
296 }
297 }
298
299 listener.directoryChanged (*sourceList);
300 }
301
302 DirectoryContentsList& root;
303 Listener& listener;
304 File lastDirectory;
306};
307
309{
310 String path;
311 bool isDirectory;
312
313 int compareWindows (const FileEntry& other) const
314 {
315 const auto toTuple = [] (const auto& x) { return std::tuple (! x.isDirectory, x.path.toLowerCase()); };
316 return threeWayCompare (toTuple (*this), toTuple (other));
317 }
318
319 int compareLinux (const FileEntry& other) const
320 {
321 const auto toTuple = [] (const auto& x) { return std::tuple (x.path.toUpperCase(), ReverseCompareString { x.path }); };
322 return threeWayCompare (toTuple (*this), toTuple (other));
323 }
324
325 int compareDefault (const FileEntry& other) const
326 {
327 return threeWayCompare (path.toLowerCase(), other.path.toLowerCase());
328 }
329};
330
332{
333public:
335 : systemType (systemTypeIn)
336 {}
337
338 int compare (const FileEntry& first, const FileEntry& second) const
339 {
340 if ((systemType & SystemStats::OperatingSystemType::Windows) != 0)
341 return first.compareWindows (second);
342
343 if ((systemType & SystemStats::OperatingSystemType::Linux) != 0)
344 return first.compareLinux (second);
345
346 return first.compareDefault (second);
347 }
348
349 bool operator() (const FileEntry& first, const FileEntry& second) const
350 {
351 return compare (first, second) < 0;
352 }
353
354private:
356};
357
359{
360public:
362 : owner (ownerIn),
363 scanner (owner.directoryContentsList, *this)
364 {
365 refresh();
366 }
367
368 ~Controller() override
369 {
370 owner.deleteRootItem();
371 }
372
373 void refresh()
374 {
375 scanner.refresh();
376 }
377
378 void selectFile (const File& target)
379 {
380 pendingFileSelection.emplace (target);
381 tryResolvePendingFileSelection();
382 }
383
384private:
385 template <typename ItemCallback>
386 static void forEachItemRecursive (TreeViewItem* item, ItemCallback&& cb)
387 {
388 if (item == nullptr)
389 return;
390
391 if (auto* fileListItem = dynamic_cast<FileListTreeItem*> (item))
393
394 for (int i = 0; i < item->getNumSubItems(); ++i)
395 forEachItemRecursive (item->getSubItem (i), cb);
396 }
397
398 //==============================================================================
399 void rootChanged() override
400 {
401 owner.deleteRootItem();
402 treeItemForFile.clear();
403 owner.setRootItem (createNewItem (scanner.getRootDirectory()).release());
404 }
405
406 void directoryChanged (const DirectoryContentsList& contentsList) override
407 {
408 auto* parentItem = [&]() -> FileListTreeItem*
409 {
410 if (auto it = treeItemForFile.find (contentsList.getDirectory()); it != treeItemForFile.end())
411 return it->second;
412
413 return nullptr;
414 }();
415
416 if (parentItem == nullptr)
417 {
419 return;
420 }
421
422 for (int i = 0; i < contentsList.getNumFiles(); ++i)
423 {
424 auto file = contentsList.getFile (i);
425
427 contentsList.getFileInfo (i, fileInfo);
428
429 auto* item = [&]
430 {
431 if (auto it = treeItemForFile.find (file); it != treeItemForFile.end())
432 return it->second;
433
434 auto* newItem = createNewItem (file).release();
435 parentItem->addSubItem (newItem);
436 return newItem;
437 }();
438
439 if (item->isOpen() && fileInfo.isDirectory)
440 scanner.open (item->file);
441
442 item->update (fileInfo);
443 }
444
445 if (contentsList.isStillLoading())
446 return;
447
449
450 for (int i = 0; i < contentsList.getNumFiles(); ++i)
451 allFiles.insert (contentsList.getFile (i));
452
453 for (int i = 0; i < parentItem->getNumSubItems();)
454 {
455 auto* fileItem = dynamic_cast<FileListTreeItem*> (parentItem->getSubItem (i));
456
457 if (fileItem != nullptr && allFiles.count (fileItem->file) == 0)
458 {
459 forEachItemRecursive (parentItem->getSubItem (i),
460 [this] (auto* item)
461 {
462 scanner.close (item->file);
463 treeItemForFile.erase (item->file);
464 });
465
466 parentItem->removeSubItem (i);
467 }
468 else
469 {
470 ++i;
471 }
472 }
473
474 struct Comparator
475 {
476 // The different OSes compare and order files in different ways. This function aims
477 // to match these different rules of comparison to mimic other FileBrowserComponent
478 // view modes where we don't need to order the results, and can just rely on the
479 // ordering of the list provided by the OS.
480 static int compareElements (TreeViewItem* first, TreeViewItem* second)
481 {
482 auto* item1 = dynamic_cast<FileListTreeItem*> (first);
483 auto* item2 = dynamic_cast<FileListTreeItem*> (second);
484
485 if (item1 == nullptr || item2 == nullptr)
486 return 0;
487
488 static const OSDependentFileComparisonRules comparisonRules { SystemStats::getOperatingSystemType() };
489
490 return comparisonRules.compare ({ item1->file.getFullPathName(), item1->file.isDirectory() },
491 { item2->file.getFullPathName(), item2->file.isDirectory() });
492 }
493 };
494
495 static Comparator comparator;
496 parentItem->sortSubItems (comparator);
497 tryResolvePendingFileSelection();
498 }
499
500 std::unique_ptr<FileListTreeItem> createNewItem (const File& file)
501 {
502 auto newItem = std::make_unique<FileListTreeItem> (owner,
503 file,
504 owner.directoryContentsList.getTimeSliceThread());
505
506 newItem->onOpennessChanged = [this, itemPtr = newItem.get()] (const auto& f, auto isOpen)
507 {
508 if (isOpen)
509 {
510 scanner.open (f);
511 }
512 else
513 {
514 forEachItemRecursive (itemPtr,
515 [this] (auto* item)
516 {
517 scanner.close (item->file);
518 });
519 }
520 };
521
522 treeItemForFile[file] = newItem.get();
523 return newItem;
524 }
525
526 void tryResolvePendingFileSelection()
527 {
528 if (! pendingFileSelection.has_value())
529 return;
530
531 if (auto item = treeItemForFile.find (*pendingFileSelection); item != treeItemForFile.end())
532 {
533 item->second->setSelected (true, true);
534 pendingFileSelection.reset();
535 return;
536 }
537
538 if (owner.directoryContentsList.isStillLoading() || scanner.isStillLoading())
539 return;
540
541 owner.clearSelectedItems();
542 }
543
544 FileTreeComponent& owner;
545 std::map<File, FileListTreeItem*> treeItemForFile;
546 DirectoryScanner scanner;
547 std::optional<File> pendingFileSelection;
548};
549
550//==============================================================================
553 itemHeight (22)
554{
555 controller = std::make_unique<Controller> (*this);
556 setRootItemVisible (false);
557 refresh();
558}
559
564
566{
567 controller->refresh();
568}
569
570//==============================================================================
572{
573 if (auto* item = dynamic_cast<const FileListTreeItem*> (getSelectedItem (index)))
574 return item->file;
575
576 return {};
577}
578
583
588
590{
591 dragAndDropDescription = description;
592}
593
595{
596 controller->selectFile (target);
597}
598
600{
601 if (itemHeight != newHeight)
602 {
603 itemHeight = newHeight;
604
605 if (auto* root = getRootItem())
606 root->treeHasChanged();
607 }
608}
609
610#if JUCE_UNIT_TESTS
611
613{
614public:
615 //==============================================================================
616 FileTreeComponentTests() : UnitTest ("FileTreeComponentTests", UnitTestCategories::gui) {}
617
618 void runTest() override
619 {
620 const auto checkOrder = [] (const auto& orderedFiles, const std::vector<String>& expected)
621 {
622 return std::equal (orderedFiles.begin(), orderedFiles.end(),
623 expected.begin(), expected.end(),
624 [] (const auto& entry, const auto& expectedPath) { return entry.path == expectedPath; });
625 };
626
627 const auto doSort = [] (const auto platform, auto& range)
628 {
629 std::sort (range.begin(), range.end(), OSDependentFileComparisonRules { platform });
630 };
631
632 beginTest ("Test Linux filename ordering");
633 {
634 std::vector<FileEntry> filesToOrder { { "_test", false },
635 { "Atest", false },
636 { "atest", false } };
637
638 doSort (SystemStats::OperatingSystemType::Linux, filesToOrder);
639
640 expect (checkOrder (filesToOrder, { "atest", "Atest", "_test" }));
641 }
642
643 beginTest ("Test Windows filename ordering");
644 {
645 std::vector<FileEntry> filesToOrder { { "cmake_install.cmake", false },
646 { "CMakeFiles", true },
647 { "JUCEConfig.cmake", false },
648 { "tools", true },
649 { "cmakefiles.cmake", false } };
650
652
653 expect (checkOrder (filesToOrder, { "CMakeFiles",
654 "tools",
655 "cmake_install.cmake",
656 "cmakefiles.cmake",
657 "JUCEConfig.cmake" }));
658 }
659
660 beginTest ("Test MacOS filename ordering");
661 {
662 std::vector<FileEntry> filesToOrder { { "cmake_install.cmake", false },
663 { "CMakeFiles", true },
664 { "tools", true },
665 { "JUCEConfig.cmake", false } };
666
668
669 expect (checkOrder (filesToOrder, { "cmake_install.cmake",
670 "CMakeFiles",
671 "JUCEConfig.cmake",
672 "tools" }));
673 }
674 }
675};
676
678
679#endif
680
681} // namespace juce
T any_of(T... args)
Has a callback method that is triggered asynchronously.
void triggerAsyncUpdate()
Causes the callback to be triggered at a later time.
void addChangeListener(ChangeListener *listener)
Registers a listener to receive change callbacks from this broadcaster.
void removeChangeListener(ChangeListener *listener)
Unregisters a listener from the list.
Receives change event callbacks that are sent out by a ChangeBroadcaster.
void repaint()
Marks the whole component as needing to be redrawn.
LookAndFeel & getLookAndFeel() const noexcept
Finds the appropriate look-and-feel to use for this component.
A base class for components that display a list of the files in a directory.
DirectoryContentsList & directoryContentsList
The list that this component is displaying.
A class to asynchronously scan for details about the files in a directory.
const File & getDirectory() const noexcept
Returns the directory that's currently being used.
bool isStillLoading() const
True if the background thread hasn't yet finished scanning for files.
void refresh()
Clears the list and restarts scanning the directory for files.
const FileFilter * getFilter() const noexcept
Returns the file filter being used.
Contains cached information about one of the files in a DirectoryContentsList.
void paintItem(Graphics &g, int width, int height) override
Draws the item's contents.
int getItemHeight() const override
Must return the height required by this item.
void itemSelectionChanged(bool) override
Called when the item is selected or deselected.
void handleAsyncUpdate() override
Called back to do whatever your class needs to do.
var getDragSourceDescription() override
To allow items from your TreeView to be dragged-and-dropped, implement this method.
String getAccessibilityName() override
Use this to set the name for this item that will be read out by accessibility clients.
void itemDoubleClicked(const MouseEvent &e) override
Called when the user double-clicks on this item.
int useTimeSlice() override
Called back by a TimeSliceThread.
void itemOpennessChanged(bool isNowOpen) override
Called when an item is opened or closed.
bool mightContainSubItems() override
Tells the tree whether this item can potentially be opened.
void itemClicked(const MouseEvent &e) override
Called when the user clicks on this item.
String getUniqueName() const override
Returns a string to uniquely identify this item.
A component that displays the files in a directory as a treeview.
File getSelectedFile(int index=0) const override
Returns one of the files that the user has currently selected.
void refresh()
Updates the files in the list.
~FileTreeComponent() override
Destructor.
void setDragAndDropDescription(const String &description)
Setting a name for this allows tree items to be dragged.
void setItemHeight(int newHeight)
Changes the height of the treeview items.
FileTreeComponent(DirectoryContentsList &listToShow)
Creates a listbox to show the contents of a specified directory.
const String & getDragAndDropDescription() const noexcept
Returns the last value that was set by setDragAndDropDescription().
void deselectAllFiles() override
Deselects any files that are currently selected.
void scrollToTop() override
Scrolls the list to the top.
int getItemHeight() const noexcept
Returns the height of the treeview items.
void setSelectedFile(const File &) override
If the specified file is in the list, it will become the only selected item (and if the file isn't in...
Represents a local file or directory.
Definition juce_File.h:45
const String & getFullPathName() const noexcept
Returns the complete, absolute path of this file.
Definition juce_File.h:153
String getFileName() const
Returns the last section of the pathname.
static String descriptionOfSizeInBytes(int64 bytes)
Utility function to convert a file size in bytes to a neat string description.
Automatically locks and unlocks a mutex object.
A graphics context, used for drawing a component or image.
static void addImageToCache(const Image &image, int64 hashCode)
Adds an image to the cache with a user-defined hash-code.
static Image getFromHashCode(int64 hashCode)
Checks the cache for an image with a particular hashcode.
Holds a fixed-size bitmap.
Definition juce_Image.h:58
bool isNull() const noexcept
Returns true if this image is not valid.
Definition juce_Image.h:155
Contains position and status information about a mouse event.
void setCurrentRangeStart(double newStart, NotificationType notification=sendNotificationAsync)
Moves the bar's thumb position.
The JUCE String class!
Definition juce_String.h:53
static String formatted(const String &formatStr, Args... args)
Creates a String from a printf-style parameter list.
String toLowerCase() const
Returns an lower-case version of this string.
int compare(const String &other) const noexcept
Case-sensitive comparison with another string.
OperatingSystemType
The set of possible results of the getOperatingSystemType() method.
@ MacOSX
To test whether any version of OSX is running, you can use the expression ((getOperatingSystemType() ...
@ Windows
To test whether any version of Windows is running, you can use the expression ((getOperatingSystemTyp...
Used by the TimeSliceThread class.
A thread that keeps a list of clients, and calls each one in turn, giving them all a chance to run so...
void removeTimeSliceClient(TimeSliceClient *clientToRemove)
Removes a client from the list.
void addTimeSliceClient(TimeSliceClient *clientToAdd, int millisecondsBeforeStarting=0)
Adds a client to the list.
An item in a TreeView.
bool isSelected() const noexcept
True if this item is currently selected.
void clearSubItems()
Removes any sub-items.
int getNumSubItems() const noexcept
Returns the number of sub-items that have been added to this item.
int getIndexInParent() const noexcept
Returns the index of this item in its parent's sub-items.
virtual void itemDoubleClicked(const MouseEvent &)
Called when the user double-clicks on this item.
bool isOpen() const noexcept
True if this item is currently open in the TreeView.
void repaintItem() const
Sends a repaint message to redraw just this item.
TreeViewItem * getSubItem(int index) const noexcept
Returns one of the item's sub-items.
void setSelected(bool shouldBeSelected, bool deselectOtherItemsFirst, NotificationType shouldNotify=sendNotification)
Selects or deselects the item.
TreeViewItem * getSelectedItem(int index) const noexcept
Returns one of the selected items in the tree.
TreeViewItem * getRootItem() const noexcept
Returns the tree's root item.
Viewport * getViewport() const noexcept
Returns the TreeView's Viewport object.
void clearSelectedItems()
Deselects any items that are currently selected.
void deleteRootItem()
This will remove and delete the current root item.
void setRootItem(TreeViewItem *newRootItem)
Sets the item that is displayed in the TreeView.
void setRootItemVisible(bool shouldBeVisible)
Changes whether the tree's root item is shown or not.
This is a base class for classes that perform a unit test.
ScrollBar & getVerticalScrollBar() noexcept
Returns a reference to the scrollbar component being used.
A variant class, that can be used to hold a range of primitive values.
T equal(T... args)
T exchange(T... args)
T forward_as_tuple(T... args)
T insert(T... args)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
JUCE Namespace.
CriticalSection::ScopedLockType ScopedLock
Automatically locks and unlocks a CriticalSection object.
void ignoreUnused(Types &&...) noexcept
Handy function for avoiding unused variables warning.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
T piecewise_construct
T sort(T... args)