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.

811 lines
27KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #pragma once
  20. //==============================================================================
  21. class FileTreeItemBase : public JucerTreeViewBase,
  22. public ValueTree::Listener
  23. {
  24. public:
  25. FileTreeItemBase (const Project::Item& projectItem)
  26. : item (projectItem), isFileMissing (false)
  27. {
  28. item.state.addListener (this);
  29. }
  30. ~FileTreeItemBase()
  31. {
  32. item.state.removeListener (this);
  33. }
  34. //==============================================================================
  35. virtual bool acceptsFileDrop (const StringArray& files) const = 0;
  36. virtual bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) = 0;
  37. //==============================================================================
  38. String getDisplayName() const override { return item.getName(); }
  39. String getRenamingName() const override { return getDisplayName(); }
  40. void setName (const String& newName) override { item.getNameValue() = newName; }
  41. bool isMissing() const override { return isFileMissing; }
  42. virtual File getFile() const { return item.getFile(); }
  43. void deleteItem() override { item.removeItemFromProject(); }
  44. virtual void deleteAllSelectedItems() override
  45. {
  46. TreeView* tree = getOwnerView();
  47. const int numSelected = tree->getNumSelectedItems();
  48. OwnedArray<File> filesToTrash;
  49. OwnedArray<Project::Item> itemsToRemove;
  50. for (int i = 0; i < numSelected; ++i)
  51. {
  52. if (auto* p = dynamic_cast<FileTreeItemBase*> (tree->getSelectedItem (i)))
  53. {
  54. itemsToRemove.add (new Project::Item (p->item));
  55. if (p->getFile().existsAsFile())
  56. filesToTrash.add (new File (p->getFile()));
  57. }
  58. }
  59. if (filesToTrash.size() > 0)
  60. {
  61. String fileList;
  62. const int maxFilesToList = 10;
  63. for (int i = jmin (maxFilesToList, filesToTrash.size()); --i >= 0;)
  64. fileList << filesToTrash.getUnchecked(i)->getFullPathName() << "\n";
  65. if (filesToTrash.size() > maxFilesToList)
  66. fileList << "\n...plus " << (filesToTrash.size() - maxFilesToList) << " more files...";
  67. int r = AlertWindow::showYesNoCancelBox (AlertWindow::NoIcon, "Delete Project Items",
  68. "As well as removing the selected item(s) from the project, do you also want to move their files to the trash:\n\n"
  69. + fileList,
  70. "Just remove references",
  71. "Also move files to Trash",
  72. "Cancel",
  73. tree->getTopLevelComponent());
  74. if (r == 0)
  75. return;
  76. if (r != 2)
  77. filesToTrash.clear();
  78. }
  79. if (auto* treeRootItem = dynamic_cast<FileTreeItemBase*> (tree->getRootItem()))
  80. {
  81. OpenDocumentManager& om = ProjucerApplication::getApp().openDocumentManager;
  82. for (int i = filesToTrash.size(); --i >= 0;)
  83. {
  84. const File f (*filesToTrash.getUnchecked(i));
  85. om.closeFile (f, false);
  86. if (! f.moveToTrash())
  87. {
  88. // xxx
  89. }
  90. }
  91. for (int i = itemsToRemove.size(); --i >= 0;)
  92. {
  93. if (auto* itemToRemove = treeRootItem->findTreeViewItem (*itemsToRemove.getUnchecked(i)))
  94. {
  95. om.closeFile (itemToRemove->getFile(), false);
  96. itemToRemove->deleteItem();
  97. }
  98. }
  99. }
  100. else
  101. {
  102. jassertfalse;
  103. }
  104. }
  105. virtual void revealInFinder() const
  106. {
  107. getFile().revealToUser();
  108. }
  109. virtual void browseToAddExistingFiles()
  110. {
  111. const File location (item.isGroup() ? item.determineGroupFolder() : getFile());
  112. FileChooser fc ("Add Files to Jucer Project", location, String());
  113. if (fc.browseForMultipleFilesOrDirectories())
  114. {
  115. StringArray files;
  116. for (int i = 0; i < fc.getResults().size(); ++i)
  117. files.add (fc.getResults().getReference(i).getFullPathName());
  118. addFilesRetainingSortOrder (files);
  119. }
  120. }
  121. virtual void checkFileStatus() // (recursive)
  122. {
  123. const File file (getFile());
  124. const bool nowMissing = file != File() && ! file.exists();
  125. if (nowMissing != isFileMissing)
  126. {
  127. isFileMissing = nowMissing;
  128. repaintItem();
  129. }
  130. }
  131. virtual void addFilesAtIndex (const StringArray& files, int insertIndex)
  132. {
  133. if (auto* p = getParentProjectItem())
  134. p->addFilesAtIndex (files, insertIndex);
  135. }
  136. virtual void addFilesRetainingSortOrder (const StringArray& files)
  137. {
  138. if (auto* p = getParentProjectItem())
  139. p->addFilesRetainingSortOrder (files);
  140. }
  141. virtual void moveSelectedItemsTo (OwnedArray <Project::Item>&, int /*insertIndex*/)
  142. {
  143. jassertfalse;
  144. }
  145. void showMultiSelectionPopupMenu() override
  146. {
  147. PopupMenu m;
  148. m.addItem (1, "Delete");
  149. m.showMenuAsync (PopupMenu::Options(),
  150. ModalCallbackFunction::create (treeViewMultiSelectItemChosen, this));
  151. }
  152. static void treeViewMultiSelectItemChosen (int resultCode, FileTreeItemBase* item)
  153. {
  154. switch (resultCode)
  155. {
  156. case 1: item->deleteAllSelectedItems(); break;
  157. default: break;
  158. }
  159. }
  160. virtual FileTreeItemBase* findTreeViewItem (const Project::Item& itemToFind)
  161. {
  162. if (item == itemToFind)
  163. return this;
  164. const bool wasOpen = isOpen();
  165. setOpen (true);
  166. for (int i = getNumSubItems(); --i >= 0;)
  167. {
  168. if (auto* pg = dynamic_cast<FileTreeItemBase*> (getSubItem(i)))
  169. if (auto* found = pg->findTreeViewItem (itemToFind))
  170. return found;
  171. }
  172. setOpen (wasOpen);
  173. return nullptr;
  174. }
  175. //==============================================================================
  176. void valueTreePropertyChanged (ValueTree& tree, const Identifier&) override
  177. {
  178. if (tree == item.state)
  179. repaintItem();
  180. }
  181. void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { treeChildrenChanged (parentTree); }
  182. void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { treeChildrenChanged (parentTree); }
  183. void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { treeChildrenChanged (parentTree); }
  184. void valueTreeParentChanged (ValueTree&) override {}
  185. //==============================================================================
  186. bool mightContainSubItems() override { return item.getNumChildren() > 0; }
  187. String getUniqueName() const override { jassert (item.getID().isNotEmpty()); return item.getID(); }
  188. bool canBeSelected() const override { return true; }
  189. String getTooltip() override { return {}; }
  190. File getDraggableFile() const override { return getFile(); }
  191. var getDragSourceDescription() override
  192. {
  193. cancelDelayedSelectionTimer();
  194. return projectItemDragType;
  195. }
  196. void addSubItems() override
  197. {
  198. for (int i = 0; i < item.getNumChildren(); ++i)
  199. if (auto* p = createSubItem (item.getChild(i)))
  200. addSubItem (p);
  201. }
  202. void itemOpennessChanged (bool isNowOpen) override
  203. {
  204. if (isNowOpen)
  205. refreshSubItems();
  206. }
  207. //==============================================================================
  208. bool isInterestedInFileDrag (const StringArray& files) override
  209. {
  210. return acceptsFileDrop (files);
  211. }
  212. void filesDropped (const StringArray& files, int insertIndex) override
  213. {
  214. if (files.size() == 1 && File (files[0]).hasFileExtension (Project::projectFileExtension))
  215. ProjucerApplication::getApp().openFile (files[0]);
  216. else
  217. addFilesAtIndex (files, insertIndex);
  218. }
  219. bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
  220. {
  221. OwnedArray<Project::Item> selectedNodes;
  222. getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
  223. return selectedNodes.size() > 0 && acceptsDragItems (selectedNodes);
  224. }
  225. void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override
  226. {
  227. OwnedArray<Project::Item> selectedNodes;
  228. getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
  229. if (selectedNodes.size() > 0)
  230. {
  231. TreeView* tree = getOwnerView();
  232. ScopedPointer<XmlElement> oldOpenness (tree->getOpennessState (false));
  233. moveSelectedItemsTo (selectedNodes, insertIndex);
  234. if (oldOpenness != nullptr)
  235. tree->restoreOpennessState (*oldOpenness, false);
  236. }
  237. }
  238. int getMillisecsAllowedForDragGesture() override
  239. {
  240. // for images, give the user longer to start dragging before assuming they're
  241. // clicking to select it for previewing..
  242. return item.isImageFile() ? 250 : JucerTreeViewBase::getMillisecsAllowedForDragGesture();
  243. }
  244. static void getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
  245. OwnedArray<Project::Item>& selectedNodes)
  246. {
  247. if (dragSourceDetails.description == projectItemDragType)
  248. {
  249. TreeView* tree = dynamic_cast<TreeView*> (dragSourceDetails.sourceComponent.get());
  250. if (tree == nullptr)
  251. tree = dragSourceDetails.sourceComponent->findParentComponentOfClass<TreeView>();
  252. if (tree != nullptr)
  253. {
  254. const int numSelected = tree->getNumSelectedItems();
  255. for (int i = 0; i < numSelected; ++i)
  256. if (auto* p = dynamic_cast<FileTreeItemBase*> (tree->getSelectedItem (i)))
  257. selectedNodes.add (new Project::Item (p->item));
  258. }
  259. }
  260. }
  261. FileTreeItemBase* getParentProjectItem() const
  262. {
  263. return dynamic_cast<FileTreeItemBase*> (getParentItem());
  264. }
  265. //==============================================================================
  266. Project::Item item;
  267. protected:
  268. bool isFileMissing;
  269. virtual FileTreeItemBase* createSubItem (const Project::Item& node) = 0;
  270. Icon getIcon() const override
  271. {
  272. auto colour = getOwnerView()->findColour (isSelected() ? defaultHighlightedTextColourId
  273. : treeIconColourId);
  274. return item.getIcon (isOpen()).withColour (colour);
  275. }
  276. bool isIconCrossedOut() const override { return item.isIconCrossedOut(); }
  277. void treeChildrenChanged (const ValueTree& parentTree)
  278. {
  279. if (parentTree == item.state)
  280. {
  281. refreshSubItems();
  282. treeHasChanged();
  283. setOpen (true);
  284. }
  285. }
  286. void triggerAsyncRename (const Project::Item& itemToRename)
  287. {
  288. struct RenameMessage : public CallbackMessage
  289. {
  290. RenameMessage (TreeView* const t, const Project::Item& i)
  291. : tree (t), itemToRename (i) {}
  292. void messageCallback() override
  293. {
  294. if (tree != nullptr)
  295. if (auto* root = dynamic_cast<FileTreeItemBase*> (tree->getRootItem()))
  296. if (auto* found = root->findTreeViewItem (itemToRename))
  297. found->showRenameBox();
  298. }
  299. private:
  300. Component::SafePointer<TreeView> tree;
  301. Project::Item itemToRename;
  302. };
  303. (new RenameMessage (getOwnerView(), itemToRename))->post();
  304. }
  305. static void moveItems (OwnedArray<Project::Item>& selectedNodes, Project::Item destNode, int insertIndex)
  306. {
  307. for (int i = selectedNodes.size(); --i >= 0;)
  308. {
  309. Project::Item* const n = selectedNodes.getUnchecked(i);
  310. if (destNode == *n || destNode.state.isAChildOf (n->state)) // Check for recursion.
  311. return;
  312. if (! destNode.canContain (*n))
  313. selectedNodes.remove (i);
  314. }
  315. // Don't include any nodes that are children of other selected nodes..
  316. for (int i = selectedNodes.size(); --i >= 0;)
  317. {
  318. Project::Item* const n = selectedNodes.getUnchecked(i);
  319. for (int j = selectedNodes.size(); --j >= 0;)
  320. {
  321. if (j != i && n->state.isAChildOf (selectedNodes.getUnchecked(j)->state))
  322. {
  323. selectedNodes.remove (i);
  324. break;
  325. }
  326. }
  327. }
  328. // Remove and re-insert them one at a time..
  329. for (int i = 0; i < selectedNodes.size(); ++i)
  330. {
  331. Project::Item* selectedNode = selectedNodes.getUnchecked(i);
  332. if (selectedNode->state.getParent() == destNode.state
  333. && indexOfNode (destNode.state, selectedNode->state) < insertIndex)
  334. --insertIndex;
  335. selectedNode->removeItemFromProject();
  336. destNode.addChild (*selectedNode, insertIndex++);
  337. }
  338. }
  339. static int indexOfNode (const ValueTree& parent, const ValueTree& child)
  340. {
  341. for (int i = parent.getNumChildren(); --i >= 0;)
  342. if (parent.getChild (i) == child)
  343. return i;
  344. return -1;
  345. }
  346. };
  347. //==============================================================================
  348. class SourceFileItem : public FileTreeItemBase
  349. {
  350. public:
  351. SourceFileItem (const Project::Item& projectItem)
  352. : FileTreeItemBase (projectItem)
  353. {
  354. }
  355. bool acceptsFileDrop (const StringArray&) const override { return false; }
  356. bool acceptsDragItems (const OwnedArray <Project::Item>&) override { return false; }
  357. String getDisplayName() const override
  358. {
  359. return getFile().getFileName();
  360. }
  361. static File findCorrespondingHeaderOrCpp (const File& f)
  362. {
  363. if (f.hasFileExtension (sourceFileExtensions)) return f.withFileExtension (".h");
  364. if (f.hasFileExtension (headerFileExtensions)) return f.withFileExtension (".cpp");
  365. return {};
  366. }
  367. void setName (const String& newName) override
  368. {
  369. if (newName != File::createLegalFileName (newName))
  370. {
  371. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
  372. "That filename contained some illegal characters!");
  373. triggerAsyncRename (item);
  374. return;
  375. }
  376. File oldFile (getFile());
  377. File newFile (oldFile.getSiblingFile (newName));
  378. File correspondingFile (findCorrespondingHeaderOrCpp (oldFile));
  379. if (correspondingFile.exists() && newFile.hasFileExtension (oldFile.getFileExtension()))
  380. {
  381. Project::Item correspondingItem (item.project.getMainGroup().findItemForFile (correspondingFile));
  382. if (correspondingItem.isValid())
  383. {
  384. if (AlertWindow::showOkCancelBox (AlertWindow::NoIcon, "File Rename",
  385. "Do you also want to rename the corresponding file \"" + correspondingFile.getFileName()
  386. + "\" to match?"))
  387. {
  388. if (! item.renameFile (newFile))
  389. {
  390. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
  391. "Failed to rename \"" + oldFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
  392. return;
  393. }
  394. if (! correspondingItem.renameFile (newFile.withFileExtension (correspondingFile.getFileExtension())))
  395. {
  396. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
  397. "Failed to rename \"" + correspondingFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
  398. }
  399. }
  400. }
  401. }
  402. if (! item.renameFile (newFile))
  403. {
  404. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
  405. "Failed to rename the file!\n\nCheck your file permissions!");
  406. }
  407. }
  408. FileTreeItemBase* createSubItem (const Project::Item&) override
  409. {
  410. jassertfalse;
  411. return nullptr;
  412. }
  413. void showDocument() override
  414. {
  415. const File f (getFile());
  416. if (f.exists())
  417. if (ProjectContentComponent* pcc = getProjectContentComponent())
  418. pcc->showEditorForFile (f, false);
  419. }
  420. void showPopupMenu() override
  421. {
  422. PopupMenu m;
  423. m.addItem (1, "Open in external editor");
  424. m.addItem (2,
  425. #if JUCE_MAC
  426. "Reveal in Finder");
  427. #else
  428. "Reveal in Explorer");
  429. #endif
  430. m.addItem (4, "Rename File...");
  431. m.addSeparator();
  432. if (auto* group = dynamic_cast<GroupItem*> (getParentItem()))
  433. {
  434. if (group->isRoot())
  435. {
  436. m.addItem (5, "Binary Resource", true, item.shouldBeAddedToBinaryResources());
  437. m.addItem (6, "Xcode Resource", true, item.shouldBeAddedToXcodeResources());
  438. m.addItem (7, "Compile", true, item.shouldBeCompiled());
  439. m.addSeparator();
  440. }
  441. }
  442. m.addItem (3, "Delete");
  443. launchPopupMenu (m);
  444. }
  445. void showAddMenu() override
  446. {
  447. if (auto* group = dynamic_cast<GroupItem*> (getParentItem()))
  448. group->showAddMenu();
  449. }
  450. void handlePopupMenuResult (int resultCode) override
  451. {
  452. switch (resultCode)
  453. {
  454. case 1: getFile().startAsProcess(); break;
  455. case 2: revealInFinder(); break;
  456. case 3: deleteAllSelectedItems(); break;
  457. case 4: triggerAsyncRename (item); break;
  458. case 5: item.getShouldAddToBinaryResourcesValue().setValue (! item.shouldBeAddedToBinaryResources()); break;
  459. case 6: item.getShouldAddToXcodeResourcesValue().setValue (! item.shouldBeAddedToXcodeResources()); break;
  460. case 7: item.getShouldCompileValue().setValue (! item.shouldBeCompiled()); break;
  461. default:
  462. if (GroupItem* parentGroup = dynamic_cast<GroupItem*> (getParentProjectItem()))
  463. parentGroup->processCreateFileMenuItem (resultCode);
  464. break;
  465. }
  466. }
  467. };
  468. //==============================================================================
  469. class GroupItem : public FileTreeItemBase
  470. {
  471. public:
  472. GroupItem (const Project::Item& projectItem, const String& filter = String())
  473. : FileTreeItemBase (projectItem),
  474. searchFilter (filter)
  475. {
  476. }
  477. bool isRoot() const override { return item.isMainGroup(); }
  478. bool acceptsFileDrop (const StringArray&) const override { return true; }
  479. void addNewGroup()
  480. {
  481. Project::Item newGroup (item.addNewSubGroup ("New Group", 0));
  482. triggerAsyncRename (newGroup);
  483. }
  484. bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) override
  485. {
  486. for (int i = selectedNodes.size(); --i >= 0;)
  487. if (item.canContain (*selectedNodes.getUnchecked(i)))
  488. return true;
  489. return false;
  490. }
  491. void addFilesAtIndex (const StringArray& files, int insertIndex) override
  492. {
  493. for (int i = 0; i < files.size(); ++i)
  494. {
  495. const File file (files[i]);
  496. if (item.addFileAtIndex (file, insertIndex, true))
  497. ++insertIndex;
  498. }
  499. }
  500. void addFilesRetainingSortOrder (const StringArray& files) override
  501. {
  502. for (int i = files.size(); --i >= 0;)
  503. item.addFileRetainingSortOrder (files[i], true);
  504. }
  505. void moveSelectedItemsTo (OwnedArray<Project::Item>& selectedNodes, int insertIndex) override
  506. {
  507. moveItems (selectedNodes, item, insertIndex);
  508. }
  509. void checkFileStatus() override
  510. {
  511. for (int i = 0; i < getNumSubItems(); ++i)
  512. if (auto* p = dynamic_cast<FileTreeItemBase*> (getSubItem(i)))
  513. p->checkFileStatus();
  514. }
  515. bool isGroupEmpty (const Project::Item& group) // recursive
  516. {
  517. for (auto i = 0; i < group.getNumChildren(); ++i)
  518. {
  519. auto child = group.getChild (i);
  520. if ((child.isGroup() && ! isGroupEmpty (child))
  521. || (child.isFile() && child.getName().containsIgnoreCase (searchFilter)))
  522. return false;
  523. }
  524. return true;
  525. }
  526. FileTreeItemBase* createSubItem (const Project::Item& child) override
  527. {
  528. if (child.isGroup())
  529. {
  530. if (searchFilter.isNotEmpty() && isGroupEmpty (child))
  531. return nullptr;
  532. return new GroupItem (child, searchFilter);
  533. }
  534. if (child.isFile())
  535. {
  536. if (child.getName().containsIgnoreCase (searchFilter))
  537. return new SourceFileItem (child);
  538. return nullptr;
  539. }
  540. jassertfalse;
  541. return nullptr;
  542. }
  543. void showDocument() override
  544. {
  545. if (ProjectContentComponent* pcc = getProjectContentComponent())
  546. pcc->setEditorComponent (new FileGroupInformationComponent (item), nullptr);
  547. }
  548. static void openAllGroups (TreeViewItem* root)
  549. {
  550. for (auto i = 0; i < root->getNumSubItems(); ++i)
  551. if (auto* sub = root->getSubItem (i))
  552. openOrCloseAllSubGroups (*sub, true);
  553. }
  554. static void closeAllGroups (TreeViewItem* root)
  555. {
  556. for (auto i = 0; i < root->getNumSubItems(); ++i)
  557. if (auto* sub = root->getSubItem (i))
  558. openOrCloseAllSubGroups (*sub, false);
  559. }
  560. static void openOrCloseAllSubGroups (TreeViewItem& item, bool shouldOpen)
  561. {
  562. item.setOpen (shouldOpen);
  563. for (int i = item.getNumSubItems(); --i >= 0;)
  564. if (auto* sub = item.getSubItem (i))
  565. openOrCloseAllSubGroups (*sub, shouldOpen);
  566. }
  567. static void setFilesToCompile (Project::Item item, const bool shouldCompile)
  568. {
  569. if (item.isFile())
  570. item.getShouldCompileValue() = shouldCompile;
  571. for (int i = item.getNumChildren(); --i >= 0;)
  572. setFilesToCompile (item.getChild (i), shouldCompile);
  573. }
  574. void showPopupMenu() override
  575. {
  576. PopupMenu m;
  577. addCreateFileMenuItems (m);
  578. m.addSeparator();
  579. m.addItem (1, "Collapse all Groups");
  580. m.addItem (2, "Expand all Groups");
  581. if (! isRoot())
  582. {
  583. if (isOpen())
  584. m.addItem (3, "Collapse all Sub-groups");
  585. else
  586. m.addItem (4, "Expand all Sub-groups");
  587. }
  588. m.addSeparator();
  589. m.addItem (5, "Enable compiling of all enclosed files");
  590. m.addItem (6, "Disable compiling of all enclosed files");
  591. m.addSeparator();
  592. m.addItem (7, "Sort Items Alphabetically");
  593. m.addItem (8, "Sort Items Alphabetically (Groups first)");
  594. m.addSeparator();
  595. if (! isRoot())
  596. {
  597. m.addItem (9, "Rename...");
  598. m.addItem (10, "Delete");
  599. }
  600. launchPopupMenu (m);
  601. }
  602. void showAddMenu() override
  603. {
  604. PopupMenu m;
  605. addCreateFileMenuItems (m);
  606. launchPopupMenu (m);
  607. }
  608. void handlePopupMenuResult (int resultCode) override
  609. {
  610. switch (resultCode)
  611. {
  612. case 1: closeAllGroups (getOwnerView()->getRootItem()); break;
  613. case 2: openAllGroups (getOwnerView()->getRootItem()); break;
  614. case 3: openOrCloseAllSubGroups (*this, false); break;
  615. case 4: openOrCloseAllSubGroups (*this, true); break;
  616. case 5: setFilesToCompile (item, true); break;
  617. case 6: setFilesToCompile (item, false); break;
  618. case 7: item.sortAlphabetically (false, false); break;
  619. case 8: item.sortAlphabetically (true, false); break;
  620. case 9: triggerAsyncRename (item); break;
  621. case 10: deleteAllSelectedItems(); break;
  622. default: processCreateFileMenuItem (resultCode); break;
  623. }
  624. }
  625. void addCreateFileMenuItems (PopupMenu& m)
  626. {
  627. m.addItem (1001, "Add New Group");
  628. m.addItem (1002, "Add Existing Files...");
  629. m.addSeparator();
  630. NewFileWizard().addWizardsToMenu (m);
  631. }
  632. void processCreateFileMenuItem (int menuID)
  633. {
  634. switch (menuID)
  635. {
  636. case 1001: addNewGroup(); break;
  637. case 1002: browseToAddExistingFiles(); break;
  638. default:
  639. jassert (getProject() != nullptr);
  640. NewFileWizard().runWizardFromMenu (menuID, *getProject(), item);
  641. break;
  642. }
  643. }
  644. Project* getProject()
  645. {
  646. if (TreeView* tv = getOwnerView())
  647. if (ProjectContentComponent* pcc = tv->findParentComponentOfClass<ProjectContentComponent>())
  648. return pcc->getProject();
  649. return nullptr;
  650. }
  651. void setSearchFilter (const String& filter) override
  652. {
  653. searchFilter = filter;
  654. refreshSubItems();
  655. }
  656. String searchFilter;
  657. };