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.

352 lines
9.9KB

  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. void selectFile (const File& target)
  98. {
  99. if (file == target)
  100. {
  101. setSelected (true, true);
  102. return;
  103. }
  104. if (subContentsList != nullptr && subContentsList->isStillLoading())
  105. {
  106. pendingFileSelection.emplace (*this, target);
  107. return;
  108. }
  109. pendingFileSelection.reset();
  110. if (! target.isAChildOf (file))
  111. return;
  112. setOpen (true);
  113. for (int i = 0; i < getNumSubItems(); ++i)
  114. if (auto* f = dynamic_cast<FileListTreeItem*> (getSubItem (i)))
  115. f->selectFile (target);
  116. }
  117. void changeListenerCallback (ChangeBroadcaster*) override
  118. {
  119. rebuildItemsFromContentList();
  120. }
  121. void rebuildItemsFromContentList()
  122. {
  123. clearSubItems();
  124. if (isOpen() && subContentsList != nullptr)
  125. {
  126. for (int i = 0; i < subContentsList->getNumFiles(); ++i)
  127. addSubItem (new FileListTreeItem (owner, subContentsList, i,
  128. subContentsList->getFile(i), thread));
  129. }
  130. }
  131. void paintItem (Graphics& g, int width, int height) override
  132. {
  133. ScopedLock lock (iconUpdate);
  134. if (file != File())
  135. {
  136. updateIcon (true);
  137. if (icon.isNull())
  138. thread.addTimeSliceClient (this);
  139. }
  140. owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
  141. file, file.getFileName(),
  142. &icon, fileSize, modTime,
  143. isDirectory, isSelected(),
  144. indexInContentsList, owner);
  145. }
  146. String getAccessibilityName() override
  147. {
  148. return file.getFileName();
  149. }
  150. void itemClicked (const MouseEvent& e) override
  151. {
  152. owner.sendMouseClickMessage (file, e);
  153. }
  154. void itemDoubleClicked (const MouseEvent& e) override
  155. {
  156. TreeViewItem::itemDoubleClicked (e);
  157. owner.sendDoubleClickMessage (file);
  158. }
  159. void itemSelectionChanged (bool) override
  160. {
  161. owner.sendSelectionChangeMessage();
  162. }
  163. int useTimeSlice() override
  164. {
  165. updateIcon (false);
  166. return -1;
  167. }
  168. void handleAsyncUpdate() override
  169. {
  170. owner.repaint();
  171. }
  172. const File file;
  173. private:
  174. class PendingFileSelection : private Timer
  175. {
  176. public:
  177. PendingFileSelection (FileListTreeItem& item, const File& f)
  178. : owner (item), fileToSelect (f)
  179. {
  180. startTimer (10);
  181. }
  182. ~PendingFileSelection() override
  183. {
  184. stopTimer();
  185. }
  186. private:
  187. void timerCallback() override
  188. {
  189. // Take a copy of the file here, in case this PendingFileSelection
  190. // object is destroyed during the call to selectFile.
  191. owner.selectFile (File { fileToSelect });
  192. }
  193. FileListTreeItem& owner;
  194. File fileToSelect;
  195. };
  196. Optional<PendingFileSelection> pendingFileSelection;
  197. FileTreeComponent& owner;
  198. DirectoryContentsList* parentContentsList;
  199. int indexInContentsList;
  200. OptionalScopedPointer<DirectoryContentsList> subContentsList;
  201. bool isDirectory;
  202. TimeSliceThread& thread;
  203. CriticalSection iconUpdate;
  204. Image icon;
  205. String fileSize, modTime;
  206. void updateIcon (const bool onlyUpdateIfCached)
  207. {
  208. if (icon.isNull())
  209. {
  210. auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
  211. auto im = ImageCache::getFromHashCode (hashCode);
  212. if (im.isNull() && ! onlyUpdateIfCached)
  213. {
  214. im = juce_createIconForFile (file);
  215. if (im.isValid())
  216. ImageCache::addImageToCache (im, hashCode);
  217. }
  218. if (im.isValid())
  219. {
  220. {
  221. ScopedLock lock (iconUpdate);
  222. icon = im;
  223. }
  224. triggerAsyncUpdate();
  225. }
  226. }
  227. }
  228. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListTreeItem)
  229. };
  230. //==============================================================================
  231. FileTreeComponent::FileTreeComponent (DirectoryContentsList& listToShow)
  232. : DirectoryContentsDisplayComponent (listToShow),
  233. itemHeight (22)
  234. {
  235. setRootItemVisible (false);
  236. refresh();
  237. }
  238. FileTreeComponent::~FileTreeComponent()
  239. {
  240. deleteRootItem();
  241. }
  242. void FileTreeComponent::refresh()
  243. {
  244. deleteRootItem();
  245. auto root = new FileListTreeItem (*this, nullptr, 0, directoryContentsList.getDirectory(),
  246. directoryContentsList.getTimeSliceThread());
  247. root->setSubContentsList (&directoryContentsList, false);
  248. setRootItem (root);
  249. }
  250. //==============================================================================
  251. File FileTreeComponent::getSelectedFile (const int index) const
  252. {
  253. if (auto* item = dynamic_cast<const FileListTreeItem*> (getSelectedItem (index)))
  254. return item->file;
  255. return {};
  256. }
  257. void FileTreeComponent::deselectAllFiles()
  258. {
  259. clearSelectedItems();
  260. }
  261. void FileTreeComponent::scrollToTop()
  262. {
  263. getViewport()->getVerticalScrollBar().setCurrentRangeStart (0);
  264. }
  265. void FileTreeComponent::setDragAndDropDescription (const String& description)
  266. {
  267. dragAndDropDescription = description;
  268. }
  269. void FileTreeComponent::setSelectedFile (const File& target)
  270. {
  271. if (auto* t = dynamic_cast<FileListTreeItem*> (getRootItem()))
  272. t->selectFile (target);
  273. }
  274. void FileTreeComponent::setItemHeight (int newHeight)
  275. {
  276. if (itemHeight != newHeight)
  277. {
  278. itemHeight = newHeight;
  279. if (auto* root = getRootItem())
  280. root->treeHasChanged();
  281. }
  282. }
  283. } // namespace juce