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.

529 lines
15KB

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