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.

427 lines
15KB

  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. class ProjectTreeItemBase : public JucerTreeViewBase,
  18. public ValueTree::Listener
  19. {
  20. public:
  21. ProjectTreeItemBase (const Project::Item& projectItem)
  22. : item (projectItem), isFileMissing (false)
  23. {
  24. item.state.addListener (this);
  25. }
  26. ~ProjectTreeItemBase()
  27. {
  28. item.state.removeListener (this);
  29. }
  30. //==============================================================================
  31. virtual bool acceptsFileDrop (const StringArray& files) const = 0;
  32. virtual bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) = 0;
  33. //==============================================================================
  34. String getDisplayName() const override { return item.getName(); }
  35. String getRenamingName() const override { return getDisplayName(); }
  36. void setName (const String& newName) override
  37. {
  38. if (item.isMainGroup())
  39. item.project.setTitle (newName);
  40. else
  41. item.getNameValue() = newName;
  42. }
  43. bool isMissing() override { return isFileMissing; }
  44. virtual File getFile() const { return item.getFile(); }
  45. void deleteItem() override { item.removeItemFromProject(); }
  46. virtual void deleteAllSelectedItems() override
  47. {
  48. TreeView* tree = getOwnerView();
  49. const int numSelected = tree->getNumSelectedItems();
  50. OwnedArray<File> filesToTrash;
  51. OwnedArray<Project::Item> itemsToRemove;
  52. for (int i = 0; i < numSelected; ++i)
  53. {
  54. if (const ProjectTreeItemBase* const p = dynamic_cast<ProjectTreeItemBase*> (tree->getSelectedItem (i)))
  55. {
  56. itemsToRemove.add (new Project::Item (p->item));
  57. if (p->getFile().existsAsFile())
  58. filesToTrash.add (new File (p->getFile()));
  59. }
  60. }
  61. if (filesToTrash.size() > 0)
  62. {
  63. String fileList;
  64. const int maxFilesToList = 10;
  65. for (int i = jmin (maxFilesToList, filesToTrash.size()); --i >= 0;)
  66. fileList << filesToTrash.getUnchecked(i)->getFullPathName() << "\n";
  67. if (filesToTrash.size() > maxFilesToList)
  68. fileList << "\n...plus " << (filesToTrash.size() - maxFilesToList) << " more files...";
  69. int r = AlertWindow::showYesNoCancelBox (AlertWindow::NoIcon, "Delete Project Items",
  70. "As well as removing the selected item(s) from the project, do you also want to move their files to the trash:\n\n"
  71. + fileList,
  72. "Just remove references",
  73. "Also move files to Trash",
  74. "Cancel",
  75. tree->getTopLevelComponent());
  76. if (r == 0)
  77. return;
  78. if (r != 2)
  79. filesToTrash.clear();
  80. }
  81. if (ProjectTreeItemBase* treeRootItem = dynamic_cast<ProjectTreeItemBase*> (tree->getRootItem()))
  82. {
  83. OpenDocumentManager& om = ProjucerApplication::getApp().openDocumentManager;
  84. for (int i = filesToTrash.size(); --i >= 0;)
  85. {
  86. const File f (*filesToTrash.getUnchecked(i));
  87. om.closeFile (f, false);
  88. if (! f.moveToTrash())
  89. {
  90. // xxx
  91. }
  92. }
  93. for (int i = itemsToRemove.size(); --i >= 0;)
  94. {
  95. if (ProjectTreeItemBase* itemToRemove = treeRootItem->findTreeViewItem (*itemsToRemove.getUnchecked(i)))
  96. {
  97. om.closeFile (itemToRemove->getFile(), false);
  98. itemToRemove->deleteItem();
  99. }
  100. }
  101. }
  102. else
  103. {
  104. jassertfalse;
  105. }
  106. }
  107. virtual void revealInFinder() const
  108. {
  109. getFile().revealToUser();
  110. }
  111. virtual void browseToAddExistingFiles()
  112. {
  113. const File location (item.isGroup() ? item.determineGroupFolder() : getFile());
  114. FileChooser fc ("Add Files to Jucer Project", location, String(), false);
  115. if (fc.browseForMultipleFilesOrDirectories())
  116. {
  117. StringArray files;
  118. for (int i = 0; i < fc.getResults().size(); ++i)
  119. files.add (fc.getResults().getReference(i).getFullPathName());
  120. addFilesRetainingSortOrder (files);
  121. }
  122. }
  123. virtual void checkFileStatus() // (recursive)
  124. {
  125. const File file (getFile());
  126. const bool nowMissing = file != File() && ! file.exists();
  127. if (nowMissing != isFileMissing)
  128. {
  129. isFileMissing = nowMissing;
  130. repaintItem();
  131. }
  132. }
  133. virtual void addFilesAtIndex (const StringArray& files, int insertIndex)
  134. {
  135. if (ProjectTreeItemBase* p = getParentProjectItem())
  136. p->addFilesAtIndex (files, insertIndex);
  137. }
  138. virtual void addFilesRetainingSortOrder (const StringArray& files)
  139. {
  140. if (ProjectTreeItemBase* p = getParentProjectItem())
  141. p->addFilesRetainingSortOrder (files);
  142. }
  143. virtual void moveSelectedItemsTo (OwnedArray <Project::Item>&, int /*insertIndex*/)
  144. {
  145. jassertfalse;
  146. }
  147. void showMultiSelectionPopupMenu() override
  148. {
  149. PopupMenu m;
  150. m.addItem (1, "Delete");
  151. m.showMenuAsync (PopupMenu::Options(),
  152. ModalCallbackFunction::create (treeViewMultiSelectItemChosen, this));
  153. }
  154. static void treeViewMultiSelectItemChosen (int resultCode, ProjectTreeItemBase* item)
  155. {
  156. switch (resultCode)
  157. {
  158. case 1: item->deleteAllSelectedItems(); break;
  159. default: break;
  160. }
  161. }
  162. virtual ProjectTreeItemBase* findTreeViewItem (const Project::Item& itemToFind)
  163. {
  164. if (item == itemToFind)
  165. return this;
  166. const bool wasOpen = isOpen();
  167. setOpen (true);
  168. for (int i = getNumSubItems(); --i >= 0;)
  169. {
  170. if (ProjectTreeItemBase* pg = dynamic_cast<ProjectTreeItemBase*> (getSubItem(i)))
  171. if (ProjectTreeItemBase* found = pg->findTreeViewItem (itemToFind))
  172. return found;
  173. }
  174. setOpen (wasOpen);
  175. return nullptr;
  176. }
  177. //==============================================================================
  178. void valueTreePropertyChanged (ValueTree& tree, const Identifier&) override
  179. {
  180. if (tree == item.state)
  181. repaintItem();
  182. }
  183. void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { treeChildrenChanged (parentTree); }
  184. void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { treeChildrenChanged (parentTree); }
  185. void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { treeChildrenChanged (parentTree); }
  186. void valueTreeParentChanged (ValueTree&) override {}
  187. //==============================================================================
  188. bool mightContainSubItems() override { return item.getNumChildren() > 0; }
  189. String getUniqueName() const override { jassert (item.getID().isNotEmpty()); return item.getID(); }
  190. bool canBeSelected() const override { return true; }
  191. String getTooltip() override { return String(); }
  192. File getDraggableFile() const override { return getFile(); }
  193. var getDragSourceDescription() override
  194. {
  195. cancelDelayedSelectionTimer();
  196. return projectItemDragType;
  197. }
  198. void addSubItems() override
  199. {
  200. for (int i = 0; i < item.getNumChildren(); ++i)
  201. if (ProjectTreeItemBase* p = createSubItem (item.getChild(i)))
  202. addSubItem (p);
  203. }
  204. void itemOpennessChanged (bool isNowOpen) override
  205. {
  206. if (isNowOpen)
  207. refreshSubItems();
  208. }
  209. //==============================================================================
  210. bool isInterestedInFileDrag (const StringArray& files) override
  211. {
  212. return acceptsFileDrop (files);
  213. }
  214. void filesDropped (const StringArray& files, int insertIndex) override
  215. {
  216. if (files.size() == 1 && File (files[0]).hasFileExtension (Project::projectFileExtension))
  217. ProjucerApplication::getApp().openFile (files[0]);
  218. else
  219. addFilesAtIndex (files, insertIndex);
  220. }
  221. bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
  222. {
  223. OwnedArray<Project::Item> selectedNodes;
  224. getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
  225. return selectedNodes.size() > 0 && acceptsDragItems (selectedNodes);
  226. }
  227. void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override
  228. {
  229. OwnedArray<Project::Item> selectedNodes;
  230. getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
  231. if (selectedNodes.size() > 0)
  232. {
  233. TreeView* tree = getOwnerView();
  234. ScopedPointer<XmlElement> oldOpenness (tree->getOpennessState (false));
  235. moveSelectedItemsTo (selectedNodes, insertIndex);
  236. if (oldOpenness != nullptr)
  237. tree->restoreOpennessState (*oldOpenness, false);
  238. }
  239. }
  240. int getMillisecsAllowedForDragGesture() override
  241. {
  242. // for images, give the user longer to start dragging before assuming they're
  243. // clicking to select it for previewing..
  244. return item.isImageFile() ? 250 : JucerTreeViewBase::getMillisecsAllowedForDragGesture();
  245. }
  246. static void getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
  247. OwnedArray<Project::Item>& selectedNodes)
  248. {
  249. if (dragSourceDetails.description == projectItemDragType)
  250. {
  251. TreeView* tree = dynamic_cast<TreeView*> (dragSourceDetails.sourceComponent.get());
  252. if (tree == nullptr)
  253. tree = dragSourceDetails.sourceComponent->findParentComponentOfClass<TreeView>();
  254. if (tree != nullptr)
  255. {
  256. const int numSelected = tree->getNumSelectedItems();
  257. for (int i = 0; i < numSelected; ++i)
  258. if (const ProjectTreeItemBase* const p = dynamic_cast<ProjectTreeItemBase*> (tree->getSelectedItem (i)))
  259. selectedNodes.add (new Project::Item (p->item));
  260. }
  261. }
  262. }
  263. ProjectTreeItemBase* getParentProjectItem() const
  264. {
  265. return dynamic_cast<ProjectTreeItemBase*> (getParentItem());
  266. }
  267. //==============================================================================
  268. Project::Item item;
  269. protected:
  270. bool isFileMissing;
  271. virtual ProjectTreeItemBase* createSubItem (const Project::Item& node) = 0;
  272. Icon getIcon() const override { return item.getIcon().withContrastingColourTo (getBackgroundColour()); }
  273. bool isIconCrossedOut() const override { return item.isIconCrossedOut(); }
  274. void treeChildrenChanged (const ValueTree& parentTree)
  275. {
  276. if (parentTree == item.state)
  277. {
  278. refreshSubItems();
  279. treeHasChanged();
  280. setOpen (true);
  281. }
  282. }
  283. void triggerAsyncRename (const Project::Item& itemToRename)
  284. {
  285. struct RenameMessage : public CallbackMessage
  286. {
  287. RenameMessage (TreeView* const t, const Project::Item& i)
  288. : tree (t), itemToRename (i) {}
  289. void messageCallback() override
  290. {
  291. if (tree != nullptr)
  292. if (ProjectTreeItemBase* root = dynamic_cast<ProjectTreeItemBase*> (tree->getRootItem()))
  293. if (ProjectTreeItemBase* found = root->findTreeViewItem (itemToRename))
  294. found->showRenameBox();
  295. }
  296. private:
  297. Component::SafePointer<TreeView> tree;
  298. Project::Item itemToRename;
  299. };
  300. (new RenameMessage (getOwnerView(), itemToRename))->post();
  301. }
  302. static void moveItems (OwnedArray<Project::Item>& selectedNodes, Project::Item destNode, int insertIndex)
  303. {
  304. for (int i = selectedNodes.size(); --i >= 0;)
  305. {
  306. Project::Item* const n = selectedNodes.getUnchecked(i);
  307. if (destNode == *n || destNode.state.isAChildOf (n->state)) // Check for recursion.
  308. return;
  309. if (! destNode.canContain (*n))
  310. selectedNodes.remove (i);
  311. }
  312. // Don't include any nodes that are children of other selected nodes..
  313. for (int i = selectedNodes.size(); --i >= 0;)
  314. {
  315. Project::Item* const n = selectedNodes.getUnchecked(i);
  316. for (int j = selectedNodes.size(); --j >= 0;)
  317. {
  318. if (j != i && n->state.isAChildOf (selectedNodes.getUnchecked(j)->state))
  319. {
  320. selectedNodes.remove (i);
  321. break;
  322. }
  323. }
  324. }
  325. // Remove and re-insert them one at a time..
  326. for (int i = 0; i < selectedNodes.size(); ++i)
  327. {
  328. Project::Item* selectedNode = selectedNodes.getUnchecked(i);
  329. if (selectedNode->state.getParent() == destNode.state
  330. && indexOfNode (destNode.state, selectedNode->state) < insertIndex)
  331. --insertIndex;
  332. selectedNode->removeItemFromProject();
  333. destNode.addChild (*selectedNode, insertIndex++);
  334. }
  335. }
  336. static int indexOfNode (const ValueTree& parent, const ValueTree& child)
  337. {
  338. for (int i = parent.getNumChildren(); --i >= 0;)
  339. if (parent.getChild (i) == child)
  340. return i;
  341. return -1;
  342. }
  343. };