Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juce_FileTreeComponent.cpp 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. //==============================================================================
  21. class FileListTreeItem : public TreeViewItem,
  22. private TimeSliceClient,
  23. private AsyncUpdater,
  24. private ChangeListener
  25. {
  26. public:
  27. FileListTreeItem (FileTreeComponent& treeComp,
  28. DirectoryContentsList* parentContents,
  29. int indexInContents,
  30. const File& f,
  31. TimeSliceThread& t)
  32. : file (f),
  33. owner (treeComp),
  34. parentContentsList (parentContents),
  35. indexInContentsList (indexInContents),
  36. subContentsList (nullptr, false),
  37. thread (t)
  38. {
  39. DirectoryContentsList::FileInfo fileInfo;
  40. if (parentContents != nullptr
  41. && parentContents->getFileInfo (indexInContents, fileInfo))
  42. {
  43. fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize);
  44. modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M");
  45. isDirectory = fileInfo.isDirectory;
  46. }
  47. else
  48. {
  49. isDirectory = true;
  50. }
  51. }
  52. ~FileListTreeItem() override
  53. {
  54. thread.removeTimeSliceClient (this);
  55. clearSubItems();
  56. removeSubContentsList();
  57. }
  58. //==============================================================================
  59. bool mightContainSubItems() override { return isDirectory; }
  60. String getUniqueName() const override { return file.getFullPathName(); }
  61. int getItemHeight() const override { return owner.getItemHeight(); }
  62. var getDragSourceDescription() override { return owner.getDragAndDropDescription(); }
  63. void itemOpennessChanged (bool isNowOpen) override
  64. {
  65. if (isNowOpen)
  66. {
  67. clearSubItems();
  68. isDirectory = file.isDirectory();
  69. if (isDirectory)
  70. {
  71. if (subContentsList == nullptr && parentContentsList != nullptr)
  72. {
  73. auto l = new DirectoryContentsList (parentContentsList->getFilter(), thread);
  74. l->setDirectory (file,
  75. parentContentsList->isFindingDirectories(),
  76. parentContentsList->isFindingFiles());
  77. setSubContentsList (l, true);
  78. }
  79. changeListenerCallback (nullptr);
  80. }
  81. }
  82. }
  83. void removeSubContentsList()
  84. {
  85. if (subContentsList != nullptr)
  86. {
  87. subContentsList->removeChangeListener (this);
  88. subContentsList.reset();
  89. }
  90. }
  91. void setSubContentsList (DirectoryContentsList* newList, const bool canDeleteList)
  92. {
  93. removeSubContentsList();
  94. subContentsList = OptionalScopedPointer<DirectoryContentsList> (newList, canDeleteList);
  95. newList->addChangeListener (this);
  96. }
  97. bool selectFile (const File& target)
  98. {
  99. if (file == target)
  100. {
  101. setSelected (true, true);
  102. return true;
  103. }
  104. if (target.isAChildOf (file))
  105. {
  106. setOpen (true);
  107. for (int maxRetries = 500; --maxRetries > 0;)
  108. {
  109. for (int i = 0; i < getNumSubItems(); ++i)
  110. if (auto* f = dynamic_cast<FileListTreeItem*> (getSubItem (i)))
  111. if (f->selectFile (target))
  112. return true;
  113. // if we've just opened and the contents are still loading, wait for it..
  114. if (subContentsList != nullptr && subContentsList->isStillLoading())
  115. {
  116. Thread::sleep (10);
  117. rebuildItemsFromContentList();
  118. }
  119. else
  120. {
  121. break;
  122. }
  123. }
  124. }
  125. return false;
  126. }
  127. void changeListenerCallback (ChangeBroadcaster*) override
  128. {
  129. rebuildItemsFromContentList();
  130. }
  131. void rebuildItemsFromContentList()
  132. {
  133. clearSubItems();
  134. if (isOpen() && subContentsList != nullptr)
  135. {
  136. for (int i = 0; i < subContentsList->getNumFiles(); ++i)
  137. addSubItem (new FileListTreeItem (owner, subContentsList, i,
  138. subContentsList->getFile(i), thread));
  139. }
  140. }
  141. void paintItem (Graphics& g, int width, int height) override
  142. {
  143. ScopedLock lock (iconUpdate);
  144. if (file != File())
  145. {
  146. updateIcon (true);
  147. if (icon.isNull())
  148. thread.addTimeSliceClient (this);
  149. }
  150. owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
  151. file, file.getFileName(),
  152. &icon, fileSize, modTime,
  153. isDirectory, isSelected(),
  154. indexInContentsList, owner);
  155. }
  156. String getAccessibilityName() override
  157. {
  158. return file.getFileName();
  159. }
  160. void itemClicked (const MouseEvent& e) override
  161. {
  162. owner.sendMouseClickMessage (file, e);
  163. }
  164. void itemDoubleClicked (const MouseEvent& e) override
  165. {
  166. TreeViewItem::itemDoubleClicked (e);
  167. owner.sendDoubleClickMessage (file);
  168. }
  169. void itemSelectionChanged (bool) override
  170. {
  171. owner.sendSelectionChangeMessage();
  172. }
  173. int useTimeSlice() override
  174. {
  175. updateIcon (false);
  176. return -1;
  177. }
  178. void handleAsyncUpdate() override
  179. {
  180. owner.repaint();
  181. }
  182. const File file;
  183. private:
  184. FileTreeComponent& owner;
  185. DirectoryContentsList* parentContentsList;
  186. int indexInContentsList;
  187. OptionalScopedPointer<DirectoryContentsList> subContentsList;
  188. bool isDirectory;
  189. TimeSliceThread& thread;
  190. CriticalSection iconUpdate;
  191. Image icon;
  192. String fileSize, modTime;
  193. void updateIcon (const bool onlyUpdateIfCached)
  194. {
  195. if (icon.isNull())
  196. {
  197. auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
  198. auto im = ImageCache::getFromHashCode (hashCode);
  199. if (im.isNull() && ! onlyUpdateIfCached)
  200. {
  201. im = juce_createIconForFile (file);
  202. if (im.isValid())
  203. ImageCache::addImageToCache (im, hashCode);
  204. }
  205. if (im.isValid())
  206. {
  207. {
  208. ScopedLock lock (iconUpdate);
  209. icon = im;
  210. }
  211. triggerAsyncUpdate();
  212. }
  213. }
  214. }
  215. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListTreeItem)
  216. };
  217. //==============================================================================
  218. FileTreeComponent::FileTreeComponent (DirectoryContentsList& listToShow)
  219. : DirectoryContentsDisplayComponent (listToShow),
  220. itemHeight (22)
  221. {
  222. setRootItemVisible (false);
  223. refresh();
  224. }
  225. FileTreeComponent::~FileTreeComponent()
  226. {
  227. deleteRootItem();
  228. }
  229. void FileTreeComponent::refresh()
  230. {
  231. deleteRootItem();
  232. auto root = new FileListTreeItem (*this, nullptr, 0, directoryContentsList.getDirectory(),
  233. directoryContentsList.getTimeSliceThread());
  234. root->setSubContentsList (&directoryContentsList, false);
  235. setRootItem (root);
  236. }
  237. //==============================================================================
  238. File FileTreeComponent::getSelectedFile (const int index) const
  239. {
  240. if (auto* item = dynamic_cast<const FileListTreeItem*> (getSelectedItem (index)))
  241. return item->file;
  242. return {};
  243. }
  244. void FileTreeComponent::deselectAllFiles()
  245. {
  246. clearSelectedItems();
  247. }
  248. void FileTreeComponent::scrollToTop()
  249. {
  250. getViewport()->getVerticalScrollBar().setCurrentRangeStart (0);
  251. }
  252. void FileTreeComponent::setDragAndDropDescription (const String& description)
  253. {
  254. dragAndDropDescription = description;
  255. }
  256. void FileTreeComponent::setSelectedFile (const File& target)
  257. {
  258. if (auto* t = dynamic_cast<FileListTreeItem*> (getRootItem()))
  259. if (! t->selectFile (target))
  260. clearSelectedItems();
  261. }
  262. void FileTreeComponent::setItemHeight (int newHeight)
  263. {
  264. if (itemHeight != newHeight)
  265. {
  266. itemHeight = newHeight;
  267. if (auto* root = getRootItem())
  268. root->treeHasChanged();
  269. }
  270. }
  271. } // namespace juce