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.

440 lines
13KB

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