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.

827 lines
28KB

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