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.

836 lines
29KB

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