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.

451 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "jucer_ProjectTreeViewBase.h"
  19. #include "../Application/jucer_Application.h"
  20. //==============================================================================
  21. ProjectTreeViewBase::ProjectTreeViewBase (const Project::Item& item_)
  22. : item (item_), isFileMissing (false)
  23. {
  24. item.state.addListener (this);
  25. }
  26. ProjectTreeViewBase::~ProjectTreeViewBase()
  27. {
  28. item.state.removeListener (this);
  29. }
  30. //==============================================================================
  31. String ProjectTreeViewBase::getDisplayName() const
  32. {
  33. return item.getName();
  34. }
  35. void ProjectTreeViewBase::setName (const String& newName)
  36. {
  37. if (item.isMainGroup())
  38. item.project.setTitle (newName);
  39. else
  40. item.getNameValue() = newName;
  41. }
  42. //==============================================================================
  43. File ProjectTreeViewBase::getFile() const
  44. {
  45. return item.getFile();
  46. }
  47. void ProjectTreeViewBase::browseToAddExistingFiles()
  48. {
  49. const File location (item.isGroup() ? item.determineGroupFolder() : getFile());
  50. FileChooser fc ("Add Files to Jucer Project", location, String::empty, false);
  51. if (fc.browseForMultipleFilesOrDirectories())
  52. {
  53. StringArray files;
  54. for (int i = 0; i < fc.getResults().size(); ++i)
  55. files.add (fc.getResults().getReference(i).getFullPathName());
  56. addFiles (files, 0);
  57. }
  58. }
  59. void ProjectTreeViewBase::addFiles (const StringArray& files, int insertIndex)
  60. {
  61. if (ProjectTreeViewBase* p = dynamic_cast <ProjectTreeViewBase*> (getParentItem()))
  62. p->addFiles (files, insertIndex);
  63. }
  64. void ProjectTreeViewBase::moveSelectedItemsTo (OwnedArray <Project::Item>& selectedNodes, int insertIndex)
  65. {
  66. jassertfalse;
  67. }
  68. //==============================================================================
  69. ProjectTreeViewBase* ProjectTreeViewBase::findTreeViewItem (const Project::Item& itemToFind)
  70. {
  71. if (item == itemToFind)
  72. return this;
  73. const bool wasOpen = isOpen();
  74. setOpen (true);
  75. for (int i = getNumSubItems(); --i >= 0;)
  76. {
  77. if (ProjectTreeViewBase* pg = dynamic_cast <ProjectTreeViewBase*> (getSubItem(i)))
  78. {
  79. pg = pg->findTreeViewItem (itemToFind);
  80. if (pg != nullptr)
  81. return pg;
  82. }
  83. }
  84. setOpen (wasOpen);
  85. return nullptr;
  86. }
  87. //==============================================================================
  88. void ProjectTreeViewBase::triggerAsyncRename (const Project::Item& itemToRename)
  89. {
  90. class RenameMessage : public CallbackMessage
  91. {
  92. public:
  93. RenameMessage (TreeView* const t, const Project::Item& item)
  94. : tree (t), itemToRename (item) {}
  95. void messageCallback()
  96. {
  97. if (tree != nullptr)
  98. {
  99. if (ProjectTreeViewBase* pg = dynamic_cast <ProjectTreeViewBase*> (tree->getRootItem()))
  100. {
  101. pg = pg->findTreeViewItem (itemToRename);
  102. if (pg != nullptr)
  103. pg->showRenameBox();
  104. }
  105. }
  106. }
  107. private:
  108. Component::SafePointer<TreeView> tree;
  109. Project::Item itemToRename;
  110. };
  111. (new RenameMessage (getOwnerView(), itemToRename))->post();
  112. }
  113. //==============================================================================
  114. void ProjectTreeViewBase::checkFileStatus()
  115. {
  116. const File file (getFile());
  117. const bool nowMissing = file != File::nonexistent && ! file.exists();
  118. if (nowMissing != isFileMissing)
  119. {
  120. isFileMissing = nowMissing;
  121. repaintItem();
  122. }
  123. }
  124. void ProjectTreeViewBase::revealInFinder() const
  125. {
  126. getFile().revealToUser();
  127. }
  128. void ProjectTreeViewBase::deleteItem()
  129. {
  130. item.removeItemFromProject();
  131. }
  132. void ProjectTreeViewBase::deleteAllSelectedItems()
  133. {
  134. TreeView* tree = getOwnerView();
  135. const int numSelected = tree->getNumSelectedItems();
  136. OwnedArray <File> filesToTrash;
  137. OwnedArray <Project::Item> itemsToRemove;
  138. for (int i = 0; i < numSelected; ++i)
  139. {
  140. if (const ProjectTreeViewBase* const p = dynamic_cast <ProjectTreeViewBase*> (tree->getSelectedItem (i)))
  141. {
  142. itemsToRemove.add (new Project::Item (p->item));
  143. if (p->getFile().existsAsFile())
  144. filesToTrash.add (new File (p->getFile()));
  145. }
  146. }
  147. if (filesToTrash.size() > 0)
  148. {
  149. String fileList;
  150. const int maxFilesToList = 10;
  151. for (int i = jmin (maxFilesToList, filesToTrash.size()); --i >= 0;)
  152. fileList << filesToTrash.getUnchecked(i)->getFullPathName() << "\n";
  153. if (filesToTrash.size() > maxFilesToList)
  154. fileList << "\n...plus " << (filesToTrash.size() - maxFilesToList) << " more files...";
  155. int r = AlertWindow::showYesNoCancelBox (AlertWindow::NoIcon, "Delete Project Items",
  156. "As well as removing the selected item(s) from the project, do you also want to move their files to the trash:\n\n"
  157. + fileList,
  158. "Just remove references",
  159. "Also move files to Trash",
  160. "Cancel",
  161. tree->getTopLevelComponent());
  162. if (r == 0)
  163. return;
  164. if (r != 2)
  165. filesToTrash.clear();
  166. }
  167. if (ProjectTreeViewBase* treeRootItem = dynamic_cast <ProjectTreeViewBase*> (tree->getRootItem()))
  168. {
  169. OpenDocumentManager& om = IntrojucerApp::getApp().openDocumentManager;
  170. for (int i = filesToTrash.size(); --i >= 0;)
  171. {
  172. const File f (*filesToTrash.getUnchecked(i));
  173. om.closeFile (f, false);
  174. if (! f.moveToTrash())
  175. {
  176. // xxx
  177. }
  178. }
  179. for (int i = itemsToRemove.size(); --i >= 0;)
  180. {
  181. if (ProjectTreeViewBase* itemToRemove = treeRootItem->findTreeViewItem (*itemsToRemove.getUnchecked(i)))
  182. {
  183. om.closeFile (itemToRemove->getFile(), false);
  184. itemToRemove->deleteItem();
  185. }
  186. }
  187. }
  188. else
  189. {
  190. jassertfalse;
  191. }
  192. }
  193. static int indexOfNode (const ValueTree& parent, const ValueTree& child)
  194. {
  195. for (int i = parent.getNumChildren(); --i >= 0;)
  196. if (parent.getChild (i) == child)
  197. return i;
  198. return -1;
  199. }
  200. void ProjectTreeViewBase::moveItems (OwnedArray <Project::Item>& selectedNodes,
  201. Project::Item destNode, int insertIndex)
  202. {
  203. for (int i = selectedNodes.size(); --i >= 0;)
  204. {
  205. Project::Item* const n = selectedNodes.getUnchecked(i);
  206. if (destNode == *n || destNode.state.isAChildOf (n->state)) // Check for recursion.
  207. return;
  208. if (! destNode.canContain (*n))
  209. selectedNodes.remove (i);
  210. }
  211. // Don't include any nodes that are children of other selected nodes..
  212. for (int i = selectedNodes.size(); --i >= 0;)
  213. {
  214. Project::Item* const n = selectedNodes.getUnchecked(i);
  215. for (int j = selectedNodes.size(); --j >= 0;)
  216. {
  217. if (j != i && n->state.isAChildOf (selectedNodes.getUnchecked(j)->state))
  218. {
  219. selectedNodes.remove (i);
  220. break;
  221. }
  222. }
  223. }
  224. // Remove and re-insert them one at a time..
  225. for (int i = 0; i < selectedNodes.size(); ++i)
  226. {
  227. Project::Item* selectedNode = selectedNodes.getUnchecked(i);
  228. if (selectedNode->state.getParent() == destNode.state
  229. && indexOfNode (destNode.state, selectedNode->state) < insertIndex)
  230. --insertIndex;
  231. selectedNode->removeItemFromProject();
  232. destNode.addChild (*selectedNode, insertIndex++);
  233. }
  234. }
  235. //==============================================================================
  236. bool ProjectTreeViewBase::isInterestedInFileDrag (const StringArray& files)
  237. {
  238. return acceptsFileDrop (files);
  239. }
  240. void ProjectTreeViewBase::filesDropped (const StringArray& files, int insertIndex)
  241. {
  242. addFiles (files, insertIndex);
  243. }
  244. void ProjectTreeViewBase::getAllSelectedNodesInTree (Component* componentInTree, OwnedArray <Project::Item>& selectedNodes)
  245. {
  246. TreeView* tree = dynamic_cast <TreeView*> (componentInTree);
  247. if (tree == nullptr)
  248. tree = componentInTree->findParentComponentOfClass<TreeView>();
  249. if (tree != nullptr)
  250. {
  251. const int numSelected = tree->getNumSelectedItems();
  252. for (int i = 0; i < numSelected; ++i)
  253. if (const ProjectTreeViewBase* const p = dynamic_cast <ProjectTreeViewBase*> (tree->getSelectedItem (i)))
  254. selectedNodes.add (new Project::Item (p->item));
  255. }
  256. }
  257. bool ProjectTreeViewBase::isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails)
  258. {
  259. if (dragSourceDetails.description != projectItemDragType)
  260. return false;
  261. OwnedArray <Project::Item> selectedNodes;
  262. getAllSelectedNodesInTree (dragSourceDetails.sourceComponent, selectedNodes);
  263. return selectedNodes.size() > 0 && acceptsDragItems (selectedNodes);
  264. }
  265. void ProjectTreeViewBase::itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex)
  266. {
  267. OwnedArray <Project::Item> selectedNodes;
  268. getAllSelectedNodesInTree (dragSourceDetails.sourceComponent, selectedNodes);
  269. if (selectedNodes.size() > 0)
  270. {
  271. TreeView* tree = getOwnerView();
  272. ScopedPointer <XmlElement> oldOpenness (tree->getOpennessState (false));
  273. moveSelectedItemsTo (selectedNodes, insertIndex);
  274. if (oldOpenness != nullptr)
  275. tree->restoreOpennessState (*oldOpenness, false);
  276. }
  277. }
  278. //==============================================================================
  279. void ProjectTreeViewBase::treeChildrenChanged (const ValueTree& parentTree)
  280. {
  281. if (parentTree == item.state)
  282. {
  283. refreshSubItems();
  284. treeHasChanged();
  285. setOpen (true);
  286. }
  287. }
  288. void ProjectTreeViewBase::valueTreePropertyChanged (ValueTree& tree, const Identifier& property)
  289. {
  290. if (tree == item.state)
  291. repaintItem();
  292. }
  293. void ProjectTreeViewBase::valueTreeChildAdded (ValueTree& parentTree, ValueTree& childWhichHasBeenAdded)
  294. {
  295. treeChildrenChanged (parentTree);
  296. }
  297. void ProjectTreeViewBase::valueTreeChildRemoved (ValueTree& parentTree, ValueTree& childWhichHasBeenRemoved)
  298. {
  299. treeChildrenChanged (parentTree);
  300. }
  301. void ProjectTreeViewBase::valueTreeChildOrderChanged (ValueTree& parentTree)
  302. {
  303. treeChildrenChanged (parentTree);
  304. }
  305. void ProjectTreeViewBase::valueTreeParentChanged (ValueTree& tree)
  306. {
  307. }
  308. //==============================================================================
  309. bool ProjectTreeViewBase::mightContainSubItems()
  310. {
  311. return item.getNumChildren() > 0;
  312. }
  313. String ProjectTreeViewBase::getUniqueName() const
  314. {
  315. jassert (item.getID().isNotEmpty());
  316. return item.getID();
  317. }
  318. void ProjectTreeViewBase::itemOpennessChanged (bool isNowOpen)
  319. {
  320. if (isNowOpen)
  321. refreshSubItems();
  322. }
  323. void ProjectTreeViewBase::addSubItems()
  324. {
  325. for (int i = 0; i < item.getNumChildren(); ++i)
  326. if (ProjectTreeViewBase* p = createSubItem (item.getChild(i)))
  327. addSubItem (p);
  328. }
  329. static void treeViewMultiSelectItemChosen (int resultCode, ProjectTreeViewBase* item)
  330. {
  331. switch (resultCode)
  332. {
  333. case 1: item->deleteAllSelectedItems(); break;
  334. default: break;
  335. }
  336. }
  337. void ProjectTreeViewBase::showMultiSelectionPopupMenu()
  338. {
  339. PopupMenu m;
  340. m.addItem (1, "Delete");
  341. m.showMenuAsync (PopupMenu::Options(),
  342. ModalCallbackFunction::create (treeViewMultiSelectItemChosen, this));
  343. }
  344. String ProjectTreeViewBase::getTooltip()
  345. {
  346. return String::empty;
  347. }
  348. var ProjectTreeViewBase::getDragSourceDescription()
  349. {
  350. cancelDelayedSelectionTimer();
  351. return projectItemDragType;
  352. }
  353. int ProjectTreeViewBase::getMillisecsAllowedForDragGesture()
  354. {
  355. // for images, give the user longer to start dragging before assuming they're
  356. // clicking to select it for previewing..
  357. return item.isImageFile() ? 250 : JucerTreeViewBase::getMillisecsAllowedForDragGesture();
  358. }
  359. //==============================================================================
  360. ProjectTreeViewBase* ProjectTreeViewBase::getParentProjectItem() const
  361. {
  362. return dynamic_cast <ProjectTreeViewBase*> (getParentItem());
  363. }