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.

468 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. 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. return this;
  74. const bool wasOpen = isOpen();
  75. setOpen (true);
  76. for (int i = getNumSubItems(); --i >= 0;)
  77. {
  78. ProjectTreeViewBase* pg = dynamic_cast <ProjectTreeViewBase*> (getSubItem(i));
  79. if (pg != nullptr)
  80. {
  81. pg = pg->findTreeViewItem (itemToFind);
  82. if (pg != nullptr)
  83. return pg;
  84. }
  85. }
  86. setOpen (wasOpen);
  87. return nullptr;
  88. }
  89. //==============================================================================
  90. void ProjectTreeViewBase::triggerAsyncRename (const Project::Item& itemToRename)
  91. {
  92. class RenameMessage : public CallbackMessage
  93. {
  94. public:
  95. RenameMessage (TreeView* const tree_, const Project::Item& itemToRename_)
  96. : tree (tree_), itemToRename (itemToRename_) {}
  97. void messageCallback()
  98. {
  99. if (tree != nullptr)
  100. {
  101. ProjectTreeViewBase* pg = dynamic_cast <ProjectTreeViewBase*> (tree->getRootItem());
  102. if (pg != nullptr)
  103. {
  104. pg = pg->findTreeViewItem (itemToRename);
  105. if (pg != nullptr)
  106. pg->showRenameBox();
  107. }
  108. }
  109. }
  110. private:
  111. Component::SafePointer<TreeView> tree;
  112. Project::Item itemToRename;
  113. };
  114. (new RenameMessage (getOwnerView(), itemToRename))->post();
  115. }
  116. //==============================================================================
  117. void ProjectTreeViewBase::checkFileStatus()
  118. {
  119. const File file (getFile());
  120. const bool nowMissing = file != File::nonexistent && ! file.exists();
  121. if (nowMissing != isFileMissing)
  122. {
  123. isFileMissing = nowMissing;
  124. repaintItem();
  125. }
  126. }
  127. void ProjectTreeViewBase::revealInFinder() const
  128. {
  129. getFile().revealToUser();
  130. }
  131. void ProjectTreeViewBase::deleteItem()
  132. {
  133. item.removeItemFromProject();
  134. }
  135. void ProjectTreeViewBase::deleteAllSelectedItems()
  136. {
  137. TreeView* tree = getOwnerView();
  138. const int numSelected = tree->getNumSelectedItems();
  139. OwnedArray <File> filesToTrash;
  140. OwnedArray <Project::Item> itemsToRemove;
  141. for (int i = 0; i < numSelected; ++i)
  142. {
  143. const ProjectTreeViewBase* const p = dynamic_cast <ProjectTreeViewBase*> (tree->getSelectedItem (i));
  144. if (p != nullptr)
  145. {
  146. itemsToRemove.add (new Project::Item (p->item));
  147. if (p->getFile().existsAsFile())
  148. filesToTrash.add (new File (p->getFile()));
  149. }
  150. }
  151. if (filesToTrash.size() > 0)
  152. {
  153. String fileList;
  154. const int maxFilesToList = 10;
  155. for (int i = jmin (maxFilesToList, filesToTrash.size()); --i >= 0;)
  156. fileList << filesToTrash.getUnchecked(i)->getFullPathName() << "\n";
  157. if (filesToTrash.size() > maxFilesToList)
  158. fileList << "\n...plus " << (filesToTrash.size() - maxFilesToList) << " more files...";
  159. int r = AlertWindow::showYesNoCancelBox (AlertWindow::NoIcon, "Delete Project Items",
  160. "As well as removing the selected item(s) from the project, do you also want to move their files to the trash:\n\n"
  161. + fileList,
  162. "Just remove references",
  163. "Also move files to Trash",
  164. "Cancel",
  165. tree->getTopLevelComponent());
  166. if (r == 0)
  167. return;
  168. if (r != 2)
  169. filesToTrash.clear();
  170. }
  171. ProjectTreeViewBase* treeRootItem = dynamic_cast <ProjectTreeViewBase*> (tree->getRootItem());
  172. jassert (treeRootItem != nullptr);
  173. if (treeRootItem != nullptr)
  174. {
  175. OpenDocumentManager& om = IntrojucerApp::getApp().openDocumentManager;
  176. for (int i = filesToTrash.size(); --i >= 0;)
  177. {
  178. const File f (*filesToTrash.getUnchecked(i));
  179. om.closeFile (f, false);
  180. if (! f.moveToTrash())
  181. {
  182. // xxx
  183. }
  184. }
  185. for (int i = itemsToRemove.size(); --i >= 0;)
  186. {
  187. ProjectTreeViewBase* itemToRemove = treeRootItem->findTreeViewItem (*itemsToRemove.getUnchecked(i));
  188. if (itemToRemove != nullptr)
  189. {
  190. om.closeFile (itemToRemove->getFile(), false);
  191. itemToRemove->deleteItem();
  192. }
  193. }
  194. }
  195. }
  196. static int indexOfNode (const ValueTree& parent, const ValueTree& child)
  197. {
  198. for (int i = parent.getNumChildren(); --i >= 0;)
  199. if (parent.getChild (i) == child)
  200. return i;
  201. return -1;
  202. }
  203. void ProjectTreeViewBase::moveItems (OwnedArray <Project::Item>& selectedNodes,
  204. Project::Item destNode, int insertIndex)
  205. {
  206. for (int i = selectedNodes.size(); --i >= 0;)
  207. {
  208. Project::Item* const n = selectedNodes.getUnchecked(i);
  209. if (destNode == *n || destNode.state.isAChildOf (n->state)) // Check for recursion.
  210. return;
  211. if (! destNode.canContain (*n))
  212. selectedNodes.remove (i);
  213. }
  214. // Don't include any nodes that are children of other selected nodes..
  215. for (int i = selectedNodes.size(); --i >= 0;)
  216. {
  217. Project::Item* const n = selectedNodes.getUnchecked(i);
  218. for (int j = selectedNodes.size(); --j >= 0;)
  219. {
  220. if (j != i && n->state.isAChildOf (selectedNodes.getUnchecked(j)->state))
  221. {
  222. selectedNodes.remove (i);
  223. break;
  224. }
  225. }
  226. }
  227. // Remove and re-insert them one at a time..
  228. for (int i = 0; i < selectedNodes.size(); ++i)
  229. {
  230. Project::Item* selectedNode = selectedNodes.getUnchecked(i);
  231. if (selectedNode->state.getParent() == destNode.state
  232. && indexOfNode (destNode.state, selectedNode->state) < insertIndex)
  233. --insertIndex;
  234. selectedNode->removeItemFromProject();
  235. destNode.addChild (*selectedNode, insertIndex++);
  236. }
  237. }
  238. //==============================================================================
  239. bool ProjectTreeViewBase::isInterestedInFileDrag (const StringArray& files)
  240. {
  241. return acceptsFileDrop (files);
  242. }
  243. void ProjectTreeViewBase::filesDropped (const StringArray& files, int insertIndex)
  244. {
  245. addFiles (files, insertIndex);
  246. }
  247. void ProjectTreeViewBase::getAllSelectedNodesInTree (Component* componentInTree, OwnedArray <Project::Item>& selectedNodes)
  248. {
  249. TreeView* tree = dynamic_cast <TreeView*> (componentInTree);
  250. if (tree == nullptr)
  251. tree = componentInTree->findParentComponentOfClass<TreeView>();
  252. if (tree != nullptr)
  253. {
  254. const int numSelected = tree->getNumSelectedItems();
  255. for (int i = 0; i < numSelected; ++i)
  256. {
  257. const ProjectTreeViewBase* const p = dynamic_cast <ProjectTreeViewBase*> (tree->getSelectedItem (i));
  258. if (p != nullptr)
  259. selectedNodes.add (new Project::Item (p->item));
  260. }
  261. }
  262. }
  263. bool ProjectTreeViewBase::isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails)
  264. {
  265. if (dragSourceDetails.description != projectItemDragType)
  266. return false;
  267. OwnedArray <Project::Item> selectedNodes;
  268. getAllSelectedNodesInTree (dragSourceDetails.sourceComponent, selectedNodes);
  269. return selectedNodes.size() > 0 && acceptsDragItems (selectedNodes);
  270. }
  271. void ProjectTreeViewBase::itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex)
  272. {
  273. OwnedArray <Project::Item> selectedNodes;
  274. getAllSelectedNodesInTree (dragSourceDetails.sourceComponent, selectedNodes);
  275. if (selectedNodes.size() > 0)
  276. {
  277. TreeView* tree = getOwnerView();
  278. ScopedPointer <XmlElement> oldOpenness (tree->getOpennessState (false));
  279. moveSelectedItemsTo (selectedNodes, insertIndex);
  280. if (oldOpenness != nullptr)
  281. tree->restoreOpennessState (*oldOpenness, false);
  282. }
  283. }
  284. //==============================================================================
  285. void ProjectTreeViewBase::treeChildrenChanged (const ValueTree& parentTree)
  286. {
  287. if (parentTree == item.state)
  288. {
  289. refreshSubItems();
  290. treeHasChanged();
  291. setOpen (true);
  292. }
  293. }
  294. void ProjectTreeViewBase::valueTreePropertyChanged (ValueTree& tree, const Identifier& property)
  295. {
  296. if (tree == item.state)
  297. repaintItem();
  298. }
  299. void ProjectTreeViewBase::valueTreeChildAdded (ValueTree& parentTree, ValueTree& childWhichHasBeenAdded)
  300. {
  301. treeChildrenChanged (parentTree);
  302. }
  303. void ProjectTreeViewBase::valueTreeChildRemoved (ValueTree& parentTree, ValueTree& childWhichHasBeenRemoved)
  304. {
  305. treeChildrenChanged (parentTree);
  306. }
  307. void ProjectTreeViewBase::valueTreeChildOrderChanged (ValueTree& parentTree)
  308. {
  309. treeChildrenChanged (parentTree);
  310. }
  311. void ProjectTreeViewBase::valueTreeParentChanged (ValueTree& tree)
  312. {
  313. }
  314. //==============================================================================
  315. bool ProjectTreeViewBase::mightContainSubItems()
  316. {
  317. return item.getNumChildren() > 0;
  318. }
  319. String ProjectTreeViewBase::getUniqueName() const
  320. {
  321. jassert (item.getID().isNotEmpty());
  322. return item.getID();
  323. }
  324. void ProjectTreeViewBase::itemOpennessChanged (bool isNowOpen)
  325. {
  326. if (isNowOpen)
  327. refreshSubItems();
  328. }
  329. void ProjectTreeViewBase::addSubItems()
  330. {
  331. for (int i = 0; i < item.getNumChildren(); ++i)
  332. {
  333. ProjectTreeViewBase* p = createSubItem (item.getChild(i));
  334. if (p != nullptr)
  335. addSubItem (p);
  336. }
  337. }
  338. static void treeViewMultiSelectItemChosen (int resultCode, ProjectTreeViewBase* item)
  339. {
  340. switch (resultCode)
  341. {
  342. case 1: item->deleteAllSelectedItems(); break;
  343. default: break;
  344. }
  345. }
  346. void ProjectTreeViewBase::showMultiSelectionPopupMenu()
  347. {
  348. PopupMenu m;
  349. m.addItem (1, "Delete");
  350. m.showMenuAsync (PopupMenu::Options(),
  351. ModalCallbackFunction::create (treeViewMultiSelectItemChosen, this));
  352. }
  353. String ProjectTreeViewBase::getTooltip()
  354. {
  355. return String::empty;
  356. }
  357. var ProjectTreeViewBase::getDragSourceDescription()
  358. {
  359. cancelDelayedSelectionTimer();
  360. return projectItemDragType;
  361. }
  362. int ProjectTreeViewBase::getMillisecsAllowedForDragGesture()
  363. {
  364. // for images, give the user longer to start dragging before assuming they're
  365. // clicking to select it for previewing..
  366. return item.isImageFile() ? 250 : JucerTreeViewBase::getMillisecsAllowedForDragGesture();
  367. }
  368. //==============================================================================
  369. ProjectTreeViewBase* ProjectTreeViewBase::getParentProjectItem() const
  370. {
  371. return dynamic_cast <ProjectTreeViewBase*> (getParentItem());
  372. }