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.

833 lines
28KB

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