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.

323 lines
9.6KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. Image juce_createIconForFile (const File&);
  18. //==============================================================================
  19. class FileListTreeItem : public TreeViewItem,
  20. private TimeSliceClient,
  21. private AsyncUpdater,
  22. private ChangeListener
  23. {
  24. public:
  25. FileListTreeItem (FileTreeComponent& treeComp,
  26. DirectoryContentsList* const parentContents,
  27. const int indexInContents,
  28. const File& f,
  29. TimeSliceThread& t)
  30. : file (f),
  31. owner (treeComp),
  32. parentContentsList (parentContents),
  33. indexInContentsList (indexInContents),
  34. subContentsList (nullptr, false),
  35. thread (t)
  36. {
  37. DirectoryContentsList::FileInfo fileInfo;
  38. if (parentContents != nullptr
  39. && parentContents->getFileInfo (indexInContents, fileInfo))
  40. {
  41. fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize);
  42. modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M");
  43. isDirectory = fileInfo.isDirectory;
  44. }
  45. else
  46. {
  47. isDirectory = true;
  48. }
  49. }
  50. ~FileListTreeItem()
  51. {
  52. thread.removeTimeSliceClient (this);
  53. clearSubItems();
  54. removeSubContentsList();
  55. }
  56. //==============================================================================
  57. bool mightContainSubItems() override { return isDirectory; }
  58. String getUniqueName() const override { return file.getFullPathName(); }
  59. int getItemHeight() const override { return owner.getItemHeight(); }
  60. var getDragSourceDescription() override { return owner.getDragAndDropDescription(); }
  61. void itemOpennessChanged (bool isNowOpen) override
  62. {
  63. if (isNowOpen)
  64. {
  65. clearSubItems();
  66. isDirectory = file.isDirectory();
  67. if (isDirectory)
  68. {
  69. if (subContentsList == nullptr)
  70. {
  71. jassert (parentContentsList != nullptr);
  72. DirectoryContentsList* const l = new DirectoryContentsList (parentContentsList->getFilter(), thread);
  73. l->setDirectory (file,
  74. parentContentsList->isFindingDirectories(),
  75. parentContentsList->isFindingFiles());
  76. setSubContentsList (l, true);
  77. }
  78. changeListenerCallback (nullptr);
  79. }
  80. }
  81. }
  82. void removeSubContentsList()
  83. {
  84. if (subContentsList != nullptr)
  85. {
  86. subContentsList->removeChangeListener (this);
  87. subContentsList.clear();
  88. }
  89. }
  90. void setSubContentsList (DirectoryContentsList* newList, const bool canDeleteList)
  91. {
  92. removeSubContentsList();
  93. OptionalScopedPointer<DirectoryContentsList> newPointer (newList, canDeleteList);
  94. subContentsList = newPointer;
  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 (FileListTreeItem* 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. if (file != File::nonexistent)
  144. {
  145. updateIcon (true);
  146. if (icon.isNull())
  147. thread.addTimeSliceClient (this);
  148. }
  149. owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
  150. file.getFileName(),
  151. &icon, fileSize, modTime,
  152. isDirectory, isSelected(),
  153. indexInContentsList, owner);
  154. }
  155. void itemClicked (const MouseEvent& e) override
  156. {
  157. owner.sendMouseClickMessage (file, e);
  158. }
  159. void itemDoubleClicked (const MouseEvent& e) override
  160. {
  161. TreeViewItem::itemDoubleClicked (e);
  162. owner.sendDoubleClickMessage (file);
  163. }
  164. void itemSelectionChanged (bool) override
  165. {
  166. owner.sendSelectionChangeMessage();
  167. }
  168. int useTimeSlice() override
  169. {
  170. updateIcon (false);
  171. return -1;
  172. }
  173. void handleAsyncUpdate() override
  174. {
  175. owner.repaint();
  176. }
  177. const File file;
  178. private:
  179. FileTreeComponent& owner;
  180. DirectoryContentsList* parentContentsList;
  181. int indexInContentsList;
  182. OptionalScopedPointer<DirectoryContentsList> subContentsList;
  183. bool isDirectory;
  184. TimeSliceThread& thread;
  185. Image icon;
  186. String fileSize, modTime;
  187. void updateIcon (const bool onlyUpdateIfCached)
  188. {
  189. if (icon.isNull())
  190. {
  191. const int hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
  192. Image im (ImageCache::getFromHashCode (hashCode));
  193. if (im.isNull() && ! onlyUpdateIfCached)
  194. {
  195. im = juce_createIconForFile (file);
  196. if (im.isValid())
  197. ImageCache::addImageToCache (im, hashCode);
  198. }
  199. if (im.isValid())
  200. {
  201. icon = im;
  202. triggerAsyncUpdate();
  203. }
  204. }
  205. }
  206. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListTreeItem)
  207. };
  208. //==============================================================================
  209. FileTreeComponent::FileTreeComponent (DirectoryContentsList& listToShow)
  210. : DirectoryContentsDisplayComponent (listToShow),
  211. itemHeight (22)
  212. {
  213. setRootItemVisible (false);
  214. refresh();
  215. }
  216. FileTreeComponent::~FileTreeComponent()
  217. {
  218. deleteRootItem();
  219. }
  220. void FileTreeComponent::refresh()
  221. {
  222. deleteRootItem();
  223. FileListTreeItem* const root
  224. = new FileListTreeItem (*this, nullptr, 0, fileList.getDirectory(),
  225. fileList.getTimeSliceThread());
  226. root->setSubContentsList (&fileList, false);
  227. setRootItem (root);
  228. }
  229. //==============================================================================
  230. File FileTreeComponent::getSelectedFile (const int index) const
  231. {
  232. if (const FileListTreeItem* const item = dynamic_cast<const FileListTreeItem*> (getSelectedItem (index)))
  233. return item->file;
  234. return File::nonexistent;
  235. }
  236. void FileTreeComponent::deselectAllFiles()
  237. {
  238. clearSelectedItems();
  239. }
  240. void FileTreeComponent::scrollToTop()
  241. {
  242. getViewport()->getVerticalScrollBar()->setCurrentRangeStart (0);
  243. }
  244. void FileTreeComponent::setDragAndDropDescription (const String& description)
  245. {
  246. dragAndDropDescription = description;
  247. }
  248. void FileTreeComponent::setSelectedFile (const File& target)
  249. {
  250. if (FileListTreeItem* t = dynamic_cast<FileListTreeItem*> (getRootItem()))
  251. if (! t->selectFile (target))
  252. clearSelectedItems();
  253. }
  254. void FileTreeComponent::setItemHeight (int newHeight)
  255. {
  256. if (itemHeight != newHeight)
  257. {
  258. itemHeight = newHeight;
  259. if (TreeViewItem* root = getRootItem())
  260. root->treeHasChanged();
  261. }
  262. }