The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

336 lines
9.7KB

  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. namespace juce
  20. {
  21. Image juce_createIconForFile (const File&);
  22. //==============================================================================
  23. class FileListTreeItem : public TreeViewItem,
  24. private TimeSliceClient,
  25. private AsyncUpdater,
  26. private ChangeListener
  27. {
  28. public:
  29. FileListTreeItem (FileTreeComponent& treeComp,
  30. DirectoryContentsList* parentContents,
  31. int indexInContents,
  32. const File& f,
  33. TimeSliceThread& t)
  34. : file (f),
  35. owner (treeComp),
  36. parentContentsList (parentContents),
  37. indexInContentsList (indexInContents),
  38. subContentsList (nullptr, false),
  39. thread (t)
  40. {
  41. DirectoryContentsList::FileInfo fileInfo;
  42. if (parentContents != nullptr
  43. && parentContents->getFileInfo (indexInContents, fileInfo))
  44. {
  45. fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize);
  46. modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M");
  47. isDirectory = fileInfo.isDirectory;
  48. }
  49. else
  50. {
  51. isDirectory = true;
  52. }
  53. }
  54. ~FileListTreeItem() override
  55. {
  56. thread.removeTimeSliceClient (this);
  57. clearSubItems();
  58. removeSubContentsList();
  59. }
  60. //==============================================================================
  61. bool mightContainSubItems() override { return isDirectory; }
  62. String getUniqueName() const override { return file.getFullPathName(); }
  63. int getItemHeight() const override { return owner.getItemHeight(); }
  64. var getDragSourceDescription() override { return owner.getDragAndDropDescription(); }
  65. void itemOpennessChanged (bool isNowOpen) override
  66. {
  67. if (isNowOpen)
  68. {
  69. clearSubItems();
  70. isDirectory = file.isDirectory();
  71. if (isDirectory)
  72. {
  73. if (subContentsList == nullptr)
  74. {
  75. jassert (parentContentsList != nullptr);
  76. auto l = new DirectoryContentsList (parentContentsList->getFilter(), thread);
  77. l->setDirectory (file,
  78. parentContentsList->isFindingDirectories(),
  79. parentContentsList->isFindingFiles());
  80. setSubContentsList (l, true);
  81. }
  82. changeListenerCallback (nullptr);
  83. }
  84. }
  85. }
  86. void removeSubContentsList()
  87. {
  88. if (subContentsList != nullptr)
  89. {
  90. subContentsList->removeChangeListener (this);
  91. subContentsList.reset();
  92. }
  93. }
  94. void setSubContentsList (DirectoryContentsList* newList, const bool canDeleteList)
  95. {
  96. removeSubContentsList();
  97. OptionalScopedPointer<DirectoryContentsList> newPointer (newList, canDeleteList);
  98. subContentsList = newPointer;
  99. newList->addChangeListener (this);
  100. }
  101. bool selectFile (const File& target)
  102. {
  103. if (file == target)
  104. {
  105. setSelected (true, true);
  106. return true;
  107. }
  108. if (target.isAChildOf (file))
  109. {
  110. setOpen (true);
  111. for (int maxRetries = 500; --maxRetries > 0;)
  112. {
  113. for (int i = 0; i < getNumSubItems(); ++i)
  114. if (auto* f = dynamic_cast<FileListTreeItem*> (getSubItem (i)))
  115. if (f->selectFile (target))
  116. return true;
  117. // if we've just opened and the contents are still loading, wait for it..
  118. if (subContentsList != nullptr && subContentsList->isStillLoading())
  119. {
  120. Thread::sleep (10);
  121. rebuildItemsFromContentList();
  122. }
  123. else
  124. {
  125. break;
  126. }
  127. }
  128. }
  129. return false;
  130. }
  131. void changeListenerCallback (ChangeBroadcaster*) override
  132. {
  133. rebuildItemsFromContentList();
  134. }
  135. void rebuildItemsFromContentList()
  136. {
  137. clearSubItems();
  138. if (isOpen() && subContentsList != nullptr)
  139. {
  140. for (int i = 0; i < subContentsList->getNumFiles(); ++i)
  141. addSubItem (new FileListTreeItem (owner, subContentsList, i,
  142. subContentsList->getFile(i), thread));
  143. }
  144. }
  145. void paintItem (Graphics& g, int width, int height) override
  146. {
  147. ScopedLock lock (iconUpdate);
  148. if (file != File())
  149. {
  150. updateIcon (true);
  151. if (icon.isNull())
  152. thread.addTimeSliceClient (this);
  153. }
  154. owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
  155. file, file.getFileName(),
  156. &icon, fileSize, modTime,
  157. isDirectory, isSelected(),
  158. indexInContentsList, owner);
  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