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.

840 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. if (auto* group = dynamic_cast<GroupItem*> (getParentItem()))
  458. {
  459. if (group->isRoot())
  460. {
  461. m.addItem (5, "Binary Resource", true, item.shouldBeAddedToBinaryResources());
  462. m.addItem (6, "Xcode Resource", true, item.shouldBeAddedToXcodeResources());
  463. m.addItem (7, "Compile", true, item.shouldBeCompiled());
  464. m.addSeparator();
  465. }
  466. }
  467. m.addItem (3, "Delete");
  468. launchPopupMenu (m);
  469. }
  470. void showAddMenu() override
  471. {
  472. if (auto* group = dynamic_cast<GroupItem*> (getParentItem()))
  473. group->showAddMenu();
  474. }
  475. void handlePopupMenuResult (int resultCode) override
  476. {
  477. switch (resultCode)
  478. {
  479. case 1: getFile().startAsProcess(); break;
  480. case 2: revealInFinder(); break;
  481. case 3: deleteAllSelectedItems(); break;
  482. case 4: triggerAsyncRename (item); break;
  483. case 5: item.getShouldAddToBinaryResourcesValue().setValue (! item.shouldBeAddedToBinaryResources()); break;
  484. case 6: item.getShouldAddToXcodeResourcesValue().setValue (! item.shouldBeAddedToXcodeResources()); break;
  485. case 7: item.getShouldCompileValue().setValue (! item.shouldBeCompiled()); break;
  486. default:
  487. if (auto* parentGroup = dynamic_cast<GroupItem*> (getParentProjectItem()))
  488. parentGroup->processCreateFileMenuItem (resultCode);
  489. break;
  490. }
  491. }
  492. };
  493. //==============================================================================
  494. class GroupItem : public FileTreeItemBase
  495. {
  496. public:
  497. GroupItem (const Project::Item& projectItem, const String& filter = {})
  498. : FileTreeItemBase (projectItem),
  499. searchFilter (filter)
  500. {
  501. }
  502. bool isRoot() const override { return item.isMainGroup(); }
  503. bool acceptsFileDrop (const StringArray&) const override { return true; }
  504. void addNewGroup()
  505. {
  506. auto newGroup = item.addNewSubGroup ("New Group", 0);
  507. triggerAsyncRename (newGroup);
  508. }
  509. bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) override
  510. {
  511. for (auto i = selectedNodes.size(); --i >= 0;)
  512. if (item.canContain (*selectedNodes.getUnchecked(i)))
  513. return true;
  514. return false;
  515. }
  516. void addFilesAtIndex (const StringArray& files, int insertIndex) override
  517. {
  518. for (auto f : files)
  519. {
  520. if (item.addFileAtIndex (f, insertIndex, true))
  521. ++insertIndex;
  522. }
  523. }
  524. void addFilesRetainingSortOrder (const StringArray& files) override
  525. {
  526. for (auto i = files.size(); --i >= 0;)
  527. item.addFileRetainingSortOrder (files[i], true);
  528. }
  529. void moveSelectedItemsTo (OwnedArray<Project::Item>& selectedNodes, int insertIndex) override
  530. {
  531. moveItems (selectedNodes, item, insertIndex);
  532. }
  533. void checkFileStatus() override
  534. {
  535. for (int i = 0; i < getNumSubItems(); ++i)
  536. if (auto* p = dynamic_cast<FileTreeItemBase*> (getSubItem(i)))
  537. p->checkFileStatus();
  538. }
  539. bool isGroupEmpty (const Project::Item& group) // recursive
  540. {
  541. for (int i = 0; i < group.getNumChildren(); ++i)
  542. {
  543. auto child = group.getChild (i);
  544. if ((child.isGroup() && ! isGroupEmpty (child))
  545. || (child.isFile() && child.getName().containsIgnoreCase (searchFilter)))
  546. return false;
  547. }
  548. return true;
  549. }
  550. FileTreeItemBase* createSubItem (const Project::Item& child) override
  551. {
  552. if (child.isGroup())
  553. {
  554. if (searchFilter.isNotEmpty() && isGroupEmpty (child))
  555. return nullptr;
  556. return new GroupItem (child, searchFilter);
  557. }
  558. if (child.isFile())
  559. {
  560. if (child.getName().containsIgnoreCase (searchFilter))
  561. return new SourceFileItem (child);
  562. return nullptr;
  563. }
  564. jassertfalse;
  565. return nullptr;
  566. }
  567. void showDocument() override
  568. {
  569. if (auto* pcc = getProjectContentComponent())
  570. pcc->setEditorComponent (new FileGroupInformationComponent (item), nullptr);
  571. }
  572. static void openAllGroups (TreeViewItem* root)
  573. {
  574. for (int i = 0; i < root->getNumSubItems(); ++i)
  575. if (auto* sub = root->getSubItem (i))
  576. openOrCloseAllSubGroups (*sub, true);
  577. }
  578. static void closeAllGroups (TreeViewItem* root)
  579. {
  580. for (int i = 0; i < root->getNumSubItems(); ++i)
  581. if (auto* sub = root->getSubItem (i))
  582. openOrCloseAllSubGroups (*sub, false);
  583. }
  584. static void openOrCloseAllSubGroups (TreeViewItem& treeItem, bool shouldOpen)
  585. {
  586. treeItem.setOpen (shouldOpen);
  587. for (auto i = treeItem.getNumSubItems(); --i >= 0;)
  588. if (auto* sub = treeItem.getSubItem (i))
  589. openOrCloseAllSubGroups (*sub, shouldOpen);
  590. }
  591. static void setFilesToCompile (Project::Item projectItem, const bool shouldCompile)
  592. {
  593. if (projectItem.isFile() && (projectItem.getFile().hasFileExtension (fileTypesToCompileByDefault)))
  594. projectItem.getShouldCompileValue() = shouldCompile;
  595. for (auto i = projectItem.getNumChildren(); --i >= 0;)
  596. setFilesToCompile (projectItem.getChild (i), shouldCompile);
  597. }
  598. void showPopupMenu() override
  599. {
  600. PopupMenu m;
  601. addCreateFileMenuItems (m);
  602. m.addSeparator();
  603. m.addItem (1, "Collapse all Groups");
  604. m.addItem (2, "Expand all Groups");
  605. if (! isRoot())
  606. {
  607. if (isOpen())
  608. m.addItem (3, "Collapse all Sub-groups");
  609. else
  610. m.addItem (4, "Expand all Sub-groups");
  611. }
  612. m.addSeparator();
  613. m.addItem (5, "Enable compiling of all enclosed files");
  614. m.addItem (6, "Disable compiling of all enclosed files");
  615. m.addSeparator();
  616. m.addItem (7, "Sort Items Alphabetically");
  617. m.addItem (8, "Sort Items Alphabetically (Groups first)");
  618. m.addSeparator();
  619. if (! isRoot())
  620. {
  621. m.addItem (9, "Rename...");
  622. m.addItem (10, "Delete");
  623. }
  624. launchPopupMenu (m);
  625. }
  626. void showAddMenu() override
  627. {
  628. PopupMenu m;
  629. addCreateFileMenuItems (m);
  630. launchPopupMenu (m);
  631. }
  632. void handlePopupMenuResult (int resultCode) override
  633. {
  634. switch (resultCode)
  635. {
  636. case 1: closeAllGroups (getOwnerView()->getRootItem()); break;
  637. case 2: openAllGroups (getOwnerView()->getRootItem()); break;
  638. case 3: openOrCloseAllSubGroups (*this, false); break;
  639. case 4: openOrCloseAllSubGroups (*this, true); break;
  640. case 5: setFilesToCompile (item, true); break;
  641. case 6: setFilesToCompile (item, false); break;
  642. case 7: item.sortAlphabetically (false, false); break;
  643. case 8: item.sortAlphabetically (true, false); break;
  644. case 9: triggerAsyncRename (item); break;
  645. case 10: deleteAllSelectedItems(); break;
  646. default: processCreateFileMenuItem (resultCode); break;
  647. }
  648. }
  649. void addCreateFileMenuItems (PopupMenu& m)
  650. {
  651. m.addItem (1001, "Add New Group");
  652. m.addItem (1002, "Add Existing Files...");
  653. m.addSeparator();
  654. NewFileWizard().addWizardsToMenu (m);
  655. }
  656. void processCreateFileMenuItem (int menuID)
  657. {
  658. switch (menuID)
  659. {
  660. case 1001: addNewGroup(); break;
  661. case 1002: browseToAddExistingFiles(); break;
  662. default:
  663. jassert (getProject() != nullptr);
  664. NewFileWizard().runWizardFromMenu (menuID, *getProject(), item);
  665. break;
  666. }
  667. }
  668. Project* getProject()
  669. {
  670. if (auto* tv = getOwnerView())
  671. if (auto* pcc = tv->findParentComponentOfClass<ProjectContentComponent>())
  672. return pcc->getProject();
  673. return nullptr;
  674. }
  675. void setSearchFilter (const String& filter) override
  676. {
  677. searchFilter = filter;
  678. refreshSubItems();
  679. }
  680. String searchFilter;
  681. };