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.

333 lines
9.6KB

  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. //==============================================================================
  22. class FileListTreeItem : public TreeViewItem,
  23. private TimeSliceClient,
  24. private AsyncUpdater,
  25. private ChangeListener
  26. {
  27. public:
  28. FileListTreeItem (FileTreeComponent& treeComp,
  29. DirectoryContentsList* parentContents,
  30. int indexInContents,
  31. const File& f,
  32. TimeSliceThread& t)
  33. : file (f),
  34. owner (treeComp),
  35. parentContentsList (parentContents),
  36. indexInContentsList (indexInContents),
  37. subContentsList (nullptr, false),
  38. thread (t)
  39. {
  40. DirectoryContentsList::FileInfo fileInfo;
  41. if (parentContents != nullptr
  42. && parentContents->getFileInfo (indexInContents, fileInfo))
  43. {
  44. fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize);
  45. modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M");
  46. isDirectory = fileInfo.isDirectory;
  47. }
  48. else
  49. {
  50. isDirectory = true;
  51. }
  52. }
  53. ~FileListTreeItem() override
  54. {
  55. thread.removeTimeSliceClient (this);
  56. clearSubItems();
  57. removeSubContentsList();
  58. }
  59. //==============================================================================
  60. bool mightContainSubItems() override { return isDirectory; }
  61. String getUniqueName() const override { return file.getFullPathName(); }
  62. int getItemHeight() const override { return owner.getItemHeight(); }
  63. var getDragSourceDescription() override { return owner.getDragAndDropDescription(); }
  64. void itemOpennessChanged (bool isNowOpen) override
  65. {
  66. if (isNowOpen)
  67. {
  68. clearSubItems();
  69. isDirectory = file.isDirectory();
  70. if (isDirectory)
  71. {
  72. if (subContentsList == nullptr)
  73. {
  74. jassert (parentContentsList != nullptr);
  75. auto l = new DirectoryContentsList (parentContentsList->getFilter(), thread);
  76. l->setDirectory (file,
  77. parentContentsList->isFindingDirectories(),
  78. parentContentsList->isFindingFiles());
  79. setSubContentsList (l, true);
  80. }
  81. changeListenerCallback (nullptr);
  82. }
  83. }
  84. }
  85. void removeSubContentsList()
  86. {
  87. if (subContentsList != nullptr)
  88. {
  89. subContentsList->removeChangeListener (this);
  90. subContentsList.reset();
  91. }
  92. }
  93. void setSubContentsList (DirectoryContentsList* newList, const bool canDeleteList)
  94. {
  95. removeSubContentsList();
  96. subContentsList = OptionalScopedPointer<DirectoryContentsList> (newList, canDeleteList);
  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. ScopedLock lock (iconUpdate);
  146. if (file != File())
  147. {
  148. updateIcon (true);
  149. if (icon.isNull())
  150. thread.addTimeSliceClient (this);
  151. }
  152. owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
  153. file, file.getFileName(),
  154. &icon, fileSize, modTime,
  155. isDirectory, isSelected(),
  156. indexInContentsList, owner);
  157. }
  158. void itemClicked (const MouseEvent& e) override
  159. {
  160. owner.sendMouseClickMessage (file, e);
  161. }
  162. void itemDoubleClicked (const MouseEvent& e) override
  163. {
  164. TreeViewItem::itemDoubleClicked (e);
  165. owner.sendDoubleClickMessage (file);
  166. }
  167. void itemSelectionChanged (bool) override
  168. {
  169. owner.sendSelectionChangeMessage();
  170. }
  171. int useTimeSlice() override
  172. {
  173. updateIcon (false);
  174. return -1;
  175. }
  176. void handleAsyncUpdate() override
  177. {
  178. owner.repaint();
  179. }
  180. const File file;
  181. private:
  182. FileTreeComponent& owner;
  183. DirectoryContentsList* parentContentsList;
  184. int indexInContentsList;
  185. OptionalScopedPointer<DirectoryContentsList> subContentsList;
  186. bool isDirectory;
  187. TimeSliceThread& thread;
  188. CriticalSection iconUpdate;
  189. Image icon;
  190. String fileSize, modTime;
  191. void updateIcon (const bool onlyUpdateIfCached)
  192. {
  193. if (icon.isNull())
  194. {
  195. auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
  196. auto im = ImageCache::getFromHashCode (hashCode);
  197. if (im.isNull() && ! onlyUpdateIfCached)
  198. {
  199. im = juce_createIconForFile (file);
  200. if (im.isValid())
  201. ImageCache::addImageToCache (im, hashCode);
  202. }
  203. if (im.isValid())
  204. {
  205. {
  206. ScopedLock lock (iconUpdate);
  207. icon = im;
  208. }
  209. triggerAsyncUpdate();
  210. }
  211. }
  212. }
  213. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListTreeItem)
  214. };
  215. //==============================================================================
  216. FileTreeComponent::FileTreeComponent (DirectoryContentsList& listToShow)
  217. : DirectoryContentsDisplayComponent (listToShow),
  218. itemHeight (22)
  219. {
  220. setRootItemVisible (false);
  221. refresh();
  222. }
  223. FileTreeComponent::~FileTreeComponent()
  224. {
  225. deleteRootItem();
  226. }
  227. void FileTreeComponent::refresh()
  228. {
  229. deleteRootItem();
  230. auto root = new FileListTreeItem (*this, nullptr, 0, directoryContentsList.getDirectory(),
  231. directoryContentsList.getTimeSliceThread());
  232. root->setSubContentsList (&directoryContentsList, false);
  233. setRootItem (root);
  234. }
  235. //==============================================================================
  236. File FileTreeComponent::getSelectedFile (const int index) const
  237. {
  238. if (auto* item = dynamic_cast<const FileListTreeItem*> (getSelectedItem (index)))
  239. return item->file;
  240. return {};
  241. }
  242. void FileTreeComponent::deselectAllFiles()
  243. {
  244. clearSelectedItems();
  245. }
  246. void FileTreeComponent::scrollToTop()
  247. {
  248. getViewport()->getVerticalScrollBar().setCurrentRangeStart (0);
  249. }
  250. void FileTreeComponent::setDragAndDropDescription (const String& description)
  251. {
  252. dragAndDropDescription = description;
  253. }
  254. void FileTreeComponent::setSelectedFile (const File& target)
  255. {
  256. if (auto* t = dynamic_cast<FileListTreeItem*> (getRootItem()))
  257. if (! t->selectFile (target))
  258. clearSelectedItems();
  259. }
  260. void FileTreeComponent::setItemHeight (int newHeight)
  261. {
  262. if (itemHeight != newHeight)
  263. {
  264. itemHeight = newHeight;
  265. if (auto* root = getRootItem())
  266. root->treeHasChanged();
  267. }
  268. }
  269. } // namespace juce