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.

492 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 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 "jucer_OpenDocumentManager.h"
  20. //==============================================================================
  21. ProjectTreeViewBase::ProjectTreeViewBase (const Project::Item& item_)
  22. : item (item_), isFileMissing (false)
  23. {
  24. item.getNode().addListener (this);
  25. item.createUIDIfMissing();
  26. }
  27. ProjectTreeViewBase::~ProjectTreeViewBase()
  28. {
  29. item.getNode().removeListener (this);
  30. }
  31. //==============================================================================
  32. const String ProjectTreeViewBase::getDisplayName() const
  33. {
  34. return item.getName().toString();
  35. }
  36. void ProjectTreeViewBase::setName (const String& newName)
  37. {
  38. if (item.isMainGroup())
  39. item.getProject().setTitle (newName);
  40. else
  41. item.getName() = newName;
  42. }
  43. //==============================================================================
  44. const File ProjectTreeViewBase::getFile() const
  45. {
  46. return item.getFile();
  47. }
  48. void ProjectTreeViewBase::browseToAddExistingFiles()
  49. {
  50. const File location (item.isGroup() ? item.determineGroupFolder() : getFile());
  51. FileChooser fc ("Add Files to Jucer Project", location, String::empty, false);
  52. if (fc.browseForMultipleFilesOrDirectories())
  53. {
  54. StringArray files;
  55. for (int i = 0; i < fc.getResults().size(); ++i)
  56. files.add (fc.getResults().getReference(i).getFullPathName());
  57. addFiles (files, 0);
  58. }
  59. }
  60. void ProjectTreeViewBase::addFiles (const StringArray& files, int insertIndex)
  61. {
  62. ProjectTreeViewBase* p = dynamic_cast <ProjectTreeViewBase*> (getParentItem());
  63. if (p != 0)
  64. p->addFiles (files, insertIndex);
  65. }
  66. void ProjectTreeViewBase::moveSelectedItemsTo (OwnedArray <Project::Item>& selectedNodes, int insertIndex)
  67. {
  68. jassertfalse;
  69. }
  70. //==============================================================================
  71. ProjectTreeViewBase* ProjectTreeViewBase::findTreeViewItem (const Project::Item& itemToFind)
  72. {
  73. if (item == itemToFind)
  74. {
  75. return this;
  76. }
  77. else
  78. {
  79. const bool wasOpen = isOpen();
  80. setOpen (true);
  81. for (int i = getNumSubItems(); --i >= 0;)
  82. {
  83. ProjectTreeViewBase* pg = dynamic_cast <ProjectTreeViewBase*> (getSubItem(i));
  84. if (pg != 0)
  85. {
  86. pg = pg->findTreeViewItem (itemToFind);
  87. if (pg != 0)
  88. return pg;
  89. }
  90. }
  91. setOpen (wasOpen);
  92. }
  93. return 0;
  94. }
  95. //==============================================================================
  96. class RenameMessage : public CallbackMessage
  97. {
  98. public:
  99. RenameMessage (TreeView* const tree_, const Project::Item& itemToRename_)
  100. : tree (tree_), itemToRename (itemToRename_) {}
  101. ~RenameMessage() throw() {}
  102. void messageCallback()
  103. {
  104. if (tree->isValidComponent())
  105. {
  106. ProjectTreeViewBase* pg = dynamic_cast <ProjectTreeViewBase*> (tree->getRootItem());
  107. if (pg != 0)
  108. {
  109. pg = pg->findTreeViewItem (itemToRename);
  110. if (pg != 0)
  111. pg->showRenameBox();
  112. }
  113. }
  114. }
  115. TreeView* tree;
  116. Project::Item itemToRename;
  117. };
  118. void ProjectTreeViewBase::triggerAsyncRename (const Project::Item& itemToRename)
  119. {
  120. (new RenameMessage (getOwnerView(), itemToRename))->post();
  121. }
  122. //==============================================================================
  123. void ProjectTreeViewBase::checkFileStatus()
  124. {
  125. const File file (getFile());
  126. const bool nowMissing = file != File::nonexistent && ! file.exists();
  127. if (nowMissing != isFileMissing)
  128. {
  129. isFileMissing = nowMissing;
  130. repaintItem();
  131. }
  132. }
  133. void ProjectTreeViewBase::revealInFinder() const
  134. {
  135. getFile().revealToUser();
  136. }
  137. void ProjectTreeViewBase::deleteItem()
  138. {
  139. item.removeItemFromProject();
  140. }
  141. void ProjectTreeViewBase::deleteAllSelectedItems()
  142. {
  143. TreeView* tree = getOwnerView();
  144. const int numSelected = tree->getNumSelectedItems();
  145. OwnedArray <File> filesToTrash;
  146. OwnedArray <Project::Item> itemsToRemove;
  147. int i;
  148. for (i = 0; i < numSelected; ++i)
  149. {
  150. const ProjectTreeViewBase* const p = dynamic_cast <ProjectTreeViewBase*> (tree->getSelectedItem (i));
  151. if (p != 0)
  152. {
  153. itemsToRemove.add (new Project::Item (p->item));
  154. if (p->getFile().existsAsFile())
  155. filesToTrash.add (new File (p->getFile()));
  156. }
  157. }
  158. if (filesToTrash.size() > 0)
  159. {
  160. String fileList;
  161. const int maxFilesToList = 10;
  162. for (i = jmin (maxFilesToList, filesToTrash.size()); --i >= 0;)
  163. fileList << filesToTrash.getUnchecked(i)->getFullPathName() << "\n";
  164. if (filesToTrash.size() > maxFilesToList)
  165. fileList << "\n...plus " << (filesToTrash.size() - maxFilesToList) << " more files...";
  166. int r = AlertWindow::showYesNoCancelBox (AlertWindow::NoIcon, "Delete Project Items",
  167. "As well as removing the selected item(s) from the project, do you also want to move their files to the trash:\n\n"
  168. + fileList,
  169. "Just remove references",
  170. "Also move files to Trash",
  171. "Cancel",
  172. tree->getTopLevelComponent());
  173. if (r == 0)
  174. return;
  175. if (r != 2)
  176. filesToTrash.clear();
  177. }
  178. ProjectTreeViewBase* treeRootItem = dynamic_cast <ProjectTreeViewBase*> (tree->getRootItem());
  179. jassert (treeRootItem != 0);
  180. if (treeRootItem != 0)
  181. {
  182. for (i = filesToTrash.size(); --i >= 0;)
  183. {
  184. const File f (*filesToTrash.getUnchecked(i));
  185. OpenDocumentManager::getInstance()->closeFile (f, false);
  186. if (! f.moveToTrash())
  187. {
  188. // xxx
  189. }
  190. }
  191. for (i = itemsToRemove.size(); --i >= 0;)
  192. {
  193. ProjectTreeViewBase* item = treeRootItem->findTreeViewItem (*itemsToRemove.getUnchecked(i));
  194. if (item != 0)
  195. item->deleteItem();
  196. }
  197. }
  198. }
  199. static int indexOfNode (const ValueTree& parent, const ValueTree& child)
  200. {
  201. for (int i = parent.getNumChildren(); --i >= 0;)
  202. if (parent.getChild (i) == child)
  203. return i;
  204. return -1;
  205. }
  206. void ProjectTreeViewBase::moveItems (OwnedArray <Project::Item>& selectedNodes,
  207. Project::Item destNode, int insertIndex)
  208. {
  209. int i;
  210. for (i = selectedNodes.size(); --i >= 0;)
  211. {
  212. Project::Item* const n = selectedNodes.getUnchecked(i);
  213. if (destNode == *n || destNode.getNode().isAChildOf (n->getNode())) // Check for recursion.
  214. return;
  215. if (! destNode.canContain (*n))
  216. selectedNodes.remove (i);
  217. }
  218. // Don't include any nodes that are children of other selected nodes..
  219. for (i = selectedNodes.size(); --i >= 0;)
  220. {
  221. Project::Item* const n = selectedNodes.getUnchecked(i);
  222. for (int j = selectedNodes.size(); --j >= 0;)
  223. {
  224. if (j != i && n->getNode().isAChildOf (selectedNodes.getUnchecked(j)->getNode()))
  225. {
  226. selectedNodes.remove (i);
  227. break;
  228. }
  229. }
  230. }
  231. // Remove and re-insert them one at a time..
  232. for (i = 0; i < selectedNodes.size(); ++i)
  233. {
  234. Project::Item* selectedNode = selectedNodes.getUnchecked(i);
  235. if (selectedNode->getNode().getParent() == destNode.getNode()
  236. && indexOfNode (destNode.getNode(), selectedNode->getNode()) < insertIndex)
  237. --insertIndex;
  238. selectedNode->removeItemFromProject();
  239. destNode.addChild (*selectedNode, insertIndex++);
  240. }
  241. }
  242. //==============================================================================
  243. bool ProjectTreeViewBase::isInterestedInFileDrag (const StringArray& files)
  244. {
  245. return acceptsFileDrop (files);
  246. }
  247. void ProjectTreeViewBase::filesDropped (const StringArray& files, int insertIndex)
  248. {
  249. addFiles (files, insertIndex);
  250. }
  251. static void getAllSelectedNodesInTree (Component* componentInTree, OwnedArray <Project::Item>& selectedNodes)
  252. {
  253. TreeView* tree = dynamic_cast <TreeView*> (componentInTree);
  254. if (tree == 0)
  255. tree = componentInTree->findParentComponentOfClass ((TreeView*) 0);
  256. if (tree != 0)
  257. {
  258. const int numSelected = tree->getNumSelectedItems();
  259. for (int i = 0; i < numSelected; ++i)
  260. {
  261. const ProjectTreeViewBase* const p = dynamic_cast <ProjectTreeViewBase*> (tree->getSelectedItem (i));
  262. if (p != 0)
  263. selectedNodes.add (new Project::Item (p->item));
  264. }
  265. }
  266. }
  267. bool ProjectTreeViewBase::isInterestedInDragSource (const String& sourceDescription, Component* sourceComponent)
  268. {
  269. if (sourceDescription != String (projectItemDragType))
  270. return false;
  271. OwnedArray <Project::Item> selectedNodes;
  272. getAllSelectedNodesInTree (sourceComponent, selectedNodes);
  273. return selectedNodes.size() > 0 && acceptsDragItems (selectedNodes);
  274. }
  275. void ProjectTreeViewBase::itemDropped (const String& sourceDescription, Component* sourceComponent, int insertIndex)
  276. {
  277. OwnedArray <Project::Item> selectedNodes;
  278. getAllSelectedNodesInTree (sourceComponent, selectedNodes);
  279. if (selectedNodes.size() > 0)
  280. {
  281. TreeView* tree = getOwnerView();
  282. ScopedPointer <XmlElement> openness (tree->getOpennessState (false));
  283. moveSelectedItemsTo (selectedNodes, insertIndex);
  284. if (openness != 0)
  285. tree->restoreOpennessState (*openness);
  286. }
  287. }
  288. //==============================================================================
  289. void ProjectTreeViewBase::valueTreePropertyChanged (ValueTree& tree, const var::identifier& property)
  290. {
  291. if (tree == item.getNode())
  292. repaintItem();
  293. }
  294. void ProjectTreeViewBase::valueTreeChildrenChanged (ValueTree& tree)
  295. {
  296. if (tree == item.getNode())
  297. {
  298. refreshSubItems();
  299. treeHasChanged();
  300. setOpen (true);
  301. }
  302. }
  303. void ProjectTreeViewBase::valueTreeParentChanged (ValueTree& tree)
  304. {
  305. }
  306. //==============================================================================
  307. bool ProjectTreeViewBase::mightContainSubItems()
  308. {
  309. return item.getNumChildren() > 0;
  310. }
  311. const String ProjectTreeViewBase::getUniqueName() const
  312. {
  313. jassert (item.getID().isNotEmpty());
  314. return item.getID();
  315. }
  316. void ProjectTreeViewBase::itemOpennessChanged (bool isNowOpen)
  317. {
  318. if (isNowOpen)
  319. refreshSubItems();
  320. }
  321. void ProjectTreeViewBase::addSubItems()
  322. {
  323. for (int i = 0; i < item.getNumChildren(); ++i)
  324. {
  325. ProjectTreeViewBase* p = createSubItem (item.getChild(i));
  326. if (p != 0)
  327. addSubItem (p);
  328. }
  329. }
  330. void ProjectTreeViewBase::refreshSubItems()
  331. {
  332. ScopedPointer <XmlElement> openness (getOpennessState());
  333. clearSubItems();
  334. addSubItems();
  335. if (openness != 0)
  336. restoreOpennessState (*openness);
  337. }
  338. void ProjectTreeViewBase::showMultiSelectionPopupMenu()
  339. {
  340. PopupMenu m;
  341. m.addItem (6, "Delete");
  342. switch (m.show())
  343. {
  344. case 6: deleteAllSelectedItems(); break;
  345. default: break;
  346. }
  347. }
  348. void ProjectTreeViewBase::itemClicked (const MouseEvent& e)
  349. {
  350. if (e.mods.isPopupMenu())
  351. {
  352. if (getOwnerView()->getNumSelectedItems() > 1)
  353. showMultiSelectionPopupMenu();
  354. else
  355. showPopupMenu();
  356. }
  357. }
  358. void ProjectTreeViewBase::itemDoubleClicked (const MouseEvent& e)
  359. {
  360. showDocument();
  361. }
  362. void ProjectTreeViewBase::itemSelectionChanged (bool isNowSelected)
  363. {
  364. if (isNowSelected)
  365. showDocument();
  366. }
  367. const String ProjectTreeViewBase::getTooltip()
  368. {
  369. return String::empty;
  370. }
  371. const String ProjectTreeViewBase::getDragSourceDescription()
  372. {
  373. return projectItemDragType;
  374. }
  375. //==============================================================================
  376. ProjectTreeViewBase* ProjectTreeViewBase::getParentProjectItem() const
  377. {
  378. return dynamic_cast <ProjectTreeViewBase*> (getParentItem());
  379. }
  380. ProjectContentComponent* ProjectTreeViewBase::getProjectContentComponent() const
  381. {
  382. Component* c = getOwnerView();
  383. while (c != 0)
  384. {
  385. ProjectContentComponent* pcc = dynamic_cast <ProjectContentComponent*> (c);
  386. if (pcc != 0)
  387. return pcc;
  388. c = c->getParentComponent();
  389. }
  390. return 0;
  391. }