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.

424 lines
15KB

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