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.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. Image juce_createIconForFile (const File&);
  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()
  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)
  72. {
  73. jassert (parentContentsList != nullptr);
  74. DirectoryContentsList* const l = new DirectoryContentsList (parentContentsList->getFilter(), thread);
  75. l->setDirectory (file,
  76. parentContentsList->isFindingDirectories(),
  77. parentContentsList->isFindingFiles());
  78. setSubContentsList (l, true);
  79. }
  80. changeListenerCallback (nullptr);
  81. }
  82. }
  83. }
  84. void removeSubContentsList()
  85. {
  86. if (subContentsList != nullptr)
  87. {
  88. subContentsList->removeChangeListener (this);
  89. subContentsList.clear();
  90. }
  91. }
  92. void setSubContentsList (DirectoryContentsList* newList, const bool canDeleteList)
  93. {
  94. removeSubContentsList();
  95. OptionalScopedPointer<DirectoryContentsList> newPointer (newList, canDeleteList);
  96. subContentsList = newPointer;
  97. newList->addChangeListener (this);
  98. }
  99. bool selectFile (const File& target)
  100. {
  101. if (file == target)
  102. {
  103. setSelected (true, true);
  104. return true;
  105. }
  106. if (target.isAChildOf (file))
  107. {
  108. setOpen (true);
  109. for (int maxRetries = 500; --maxRetries > 0;)
  110. {
  111. for (int i = 0; i < getNumSubItems(); ++i)
  112. if (auto* f = dynamic_cast<FileListTreeItem*> (getSubItem (i)))
  113. if (f->selectFile (target))
  114. return true;
  115. // if we've just opened and the contents are still loading, wait for it..
  116. if (subContentsList != nullptr && subContentsList->isStillLoading())
  117. {
  118. Thread::sleep (10);
  119. rebuildItemsFromContentList();
  120. }
  121. else
  122. {
  123. break;
  124. }
  125. }
  126. }
  127. return false;
  128. }
  129. void changeListenerCallback (ChangeBroadcaster*) override
  130. {
  131. rebuildItemsFromContentList();
  132. }
  133. void rebuildItemsFromContentList()
  134. {
  135. clearSubItems();
  136. if (isOpen() && subContentsList != nullptr)
  137. {
  138. for (int i = 0; i < subContentsList->getNumFiles(); ++i)
  139. addSubItem (new FileListTreeItem (owner, subContentsList, i,
  140. subContentsList->getFile(i), thread));
  141. }
  142. }
  143. void paintItem (Graphics& g, int width, int height) override
  144. {
  145. if (file != File())
  146. {
  147. updateIcon (true);
  148. if (icon.isNull())
  149. thread.addTimeSliceClient (this);
  150. }
  151. owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
  152. file, file.getFileName(),
  153. &icon, fileSize, modTime,
  154. isDirectory, isSelected(),
  155. indexInContentsList, owner);
  156. }
  157. void itemClicked (const MouseEvent& e) override
  158. {
  159. owner.sendMouseClickMessage (file, e);
  160. }
  161. void itemDoubleClicked (const MouseEvent& e) override
  162. {
  163. TreeViewItem::itemDoubleClicked (e);
  164. owner.sendDoubleClickMessage (file);
  165. }
  166. void itemSelectionChanged (bool) override
  167. {
  168. owner.sendSelectionChangeMessage();
  169. }
  170. int useTimeSlice() override
  171. {
  172. updateIcon (false);
  173. return -1;
  174. }
  175. void handleAsyncUpdate() override
  176. {
  177. owner.repaint();
  178. }
  179. const File file;
  180. private:
  181. FileTreeComponent& owner;
  182. DirectoryContentsList* parentContentsList;
  183. int indexInContentsList;
  184. OptionalScopedPointer<DirectoryContentsList> subContentsList;
  185. bool isDirectory;
  186. TimeSliceThread& thread;
  187. Image icon;
  188. String fileSize, modTime;
  189. void updateIcon (const bool onlyUpdateIfCached)
  190. {
  191. if (icon.isNull())
  192. {
  193. auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
  194. auto im = ImageCache::getFromHashCode (hashCode);
  195. if (im.isNull() && ! onlyUpdateIfCached)
  196. {
  197. im = juce_createIconForFile (file);
  198. if (im.isValid())
  199. ImageCache::addImageToCache (im, hashCode);
  200. }
  201. if (im.isValid())
  202. {
  203. icon = im;
  204. triggerAsyncUpdate();
  205. }
  206. }
  207. }
  208. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListTreeItem)
  209. };
  210. //==============================================================================
  211. FileTreeComponent::FileTreeComponent (DirectoryContentsList& listToShow)
  212. : DirectoryContentsDisplayComponent (listToShow),
  213. itemHeight (22)
  214. {
  215. setRootItemVisible (false);
  216. refresh();
  217. }
  218. FileTreeComponent::~FileTreeComponent()
  219. {
  220. deleteRootItem();
  221. }
  222. void FileTreeComponent::refresh()
  223. {
  224. deleteRootItem();
  225. auto root = new FileListTreeItem (*this, nullptr, 0, directoryContentsList.getDirectory(),
  226. directoryContentsList.getTimeSliceThread());
  227. root->setSubContentsList (&directoryContentsList, false);
  228. setRootItem (root);
  229. }
  230. //==============================================================================
  231. File FileTreeComponent::getSelectedFile (const int index) const
  232. {
  233. if (auto* item = dynamic_cast<const FileListTreeItem*> (getSelectedItem (index)))
  234. return item->file;
  235. return {};
  236. }
  237. void FileTreeComponent::deselectAllFiles()
  238. {
  239. clearSelectedItems();
  240. }
  241. void FileTreeComponent::scrollToTop()
  242. {
  243. getViewport()->getVerticalScrollBar()->setCurrentRangeStart (0);
  244. }
  245. void FileTreeComponent::setDragAndDropDescription (const String& description)
  246. {
  247. dragAndDropDescription = description;
  248. }
  249. void FileTreeComponent::setSelectedFile (const File& target)
  250. {
  251. if (auto* t = dynamic_cast<FileListTreeItem*> (getRootItem()))
  252. if (! t->selectFile (target))
  253. clearSelectedItems();
  254. }
  255. void FileTreeComponent::setItemHeight (int newHeight)
  256. {
  257. if (itemHeight != newHeight)
  258. {
  259. itemHeight = newHeight;
  260. if (auto* root = getRootItem())
  261. root->treeHasChanged();
  262. }
  263. }