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.

841 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() override
  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. //==============================================================================
  201. bool mightContainSubItems() override { return item.getNumChildren() > 0; }
  202. String getUniqueName() const override { jassert (item.getID().isNotEmpty()); return item.getID(); }
  203. bool canBeSelected() const override { return true; }
  204. String getTooltip() override { return {}; }
  205. File getDraggableFile() const override { return getFile(); }
  206. var getDragSourceDescription() override
  207. {
  208. cancelDelayedSelectionTimer();
  209. return projectItemDragType;
  210. }
  211. void addSubItems() override
  212. {
  213. for (int i = 0; i < item.getNumChildren(); ++i)
  214. if (auto* p = createSubItem (item.getChild(i)))
  215. addSubItem (p);
  216. }
  217. void itemOpennessChanged (bool isNowOpen) override
  218. {
  219. if (isNowOpen)
  220. refreshSubItems();
  221. }
  222. //==============================================================================
  223. bool isInterestedInFileDrag (const StringArray& files) override
  224. {
  225. return acceptsFileDrop (files);
  226. }
  227. void filesDropped (const StringArray& files, int insertIndex) override
  228. {
  229. if (files.size() == 1 && File (files[0]).hasFileExtension (Project::projectFileExtension))
  230. ProjucerApplication::getApp().openFile (files[0]);
  231. else
  232. addFilesAtIndex (files, insertIndex);
  233. }
  234. bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
  235. {
  236. OwnedArray<Project::Item> selectedNodes;
  237. getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
  238. return selectedNodes.size() > 0 && acceptsDragItems (selectedNodes);
  239. }
  240. void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override
  241. {
  242. OwnedArray<Project::Item> selectedNodes;
  243. getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
  244. if (selectedNodes.size() > 0)
  245. {
  246. auto* tree = getOwnerView();
  247. std::unique_ptr<XmlElement> oldOpenness (tree->getOpennessState (false));
  248. moveSelectedItemsTo (selectedNodes, insertIndex);
  249. if (oldOpenness != nullptr)
  250. tree->restoreOpennessState (*oldOpenness, false);
  251. }
  252. }
  253. int getMillisecsAllowedForDragGesture() override
  254. {
  255. // for images, give the user longer to start dragging before assuming they're
  256. // clicking to select it for previewing..
  257. return item.isImageFile() ? 250 : JucerTreeViewBase::getMillisecsAllowedForDragGesture();
  258. }
  259. static void getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
  260. OwnedArray<Project::Item>& selectedNodes)
  261. {
  262. if (dragSourceDetails.description == projectItemDragType)
  263. {
  264. auto* tree = dynamic_cast<TreeView*> (dragSourceDetails.sourceComponent.get());
  265. if (tree == nullptr)
  266. tree = dragSourceDetails.sourceComponent->findParentComponentOfClass<TreeView>();
  267. if (tree != nullptr)
  268. {
  269. auto numSelected = tree->getNumSelectedItems();
  270. for (int i = 0; i < numSelected; ++i)
  271. if (auto* p = dynamic_cast<FileTreeItemBase*> (tree->getSelectedItem (i)))
  272. selectedNodes.add (new Project::Item (p->item));
  273. }
  274. }
  275. }
  276. FileTreeItemBase* getParentProjectItem() const
  277. {
  278. return dynamic_cast<FileTreeItemBase*> (getParentItem());
  279. }
  280. //==============================================================================
  281. Project::Item item;
  282. protected:
  283. bool isFileMissing;
  284. virtual FileTreeItemBase* createSubItem (const Project::Item& node) = 0;
  285. Icon getIcon() const override
  286. {
  287. auto colour = getOwnerView()->findColour (isSelected() ? defaultHighlightedTextColourId
  288. : treeIconColourId);
  289. return item.getIcon (isOpen()).withColour (colour);
  290. }
  291. bool isIconCrossedOut() const override { return item.isIconCrossedOut(); }
  292. void treeChildrenChanged (const ValueTree& parentTree)
  293. {
  294. if (parentTree == item.state)
  295. {
  296. refreshSubItems();
  297. treeHasChanged();
  298. setOpen (true);
  299. }
  300. }
  301. void triggerAsyncRename (const Project::Item& itemToRename)
  302. {
  303. struct RenameMessage : public CallbackMessage
  304. {
  305. RenameMessage (TreeView* const t, const Project::Item& i)
  306. : tree (t), itemToRename (i) {}
  307. void messageCallback() override
  308. {
  309. if (tree != nullptr)
  310. if (auto* root = dynamic_cast<FileTreeItemBase*> (tree->getRootItem()))
  311. if (auto* found = root->findTreeViewItem (itemToRename))
  312. found->showRenameBox();
  313. }
  314. private:
  315. Component::SafePointer<TreeView> tree;
  316. Project::Item itemToRename;
  317. };
  318. (new RenameMessage (getOwnerView(), itemToRename))->post();
  319. }
  320. static void moveItems (OwnedArray<Project::Item>& selectedNodes, Project::Item destNode, int insertIndex)
  321. {
  322. for (auto i = selectedNodes.size(); --i >= 0;)
  323. {
  324. auto* n = selectedNodes.getUnchecked(i);
  325. if (destNode == *n || destNode.state.isAChildOf (n->state)) // Check for recursion.
  326. return;
  327. if (! destNode.canContain (*n))
  328. selectedNodes.remove (i);
  329. }
  330. // Don't include any nodes that are children of other selected nodes..
  331. for (auto i = selectedNodes.size(); --i >= 0;)
  332. {
  333. auto* n = selectedNodes.getUnchecked(i);
  334. for (auto j = selectedNodes.size(); --j >= 0;)
  335. {
  336. if (j != i && n->state.isAChildOf (selectedNodes.getUnchecked(j)->state))
  337. {
  338. selectedNodes.remove (i);
  339. break;
  340. }
  341. }
  342. }
  343. // Remove and re-insert them one at a time..
  344. for (int i = 0; i < selectedNodes.size(); ++i)
  345. {
  346. auto* selectedNode = selectedNodes.getUnchecked(i);
  347. if (selectedNode->state.getParent() == destNode.state
  348. && indexOfNode (destNode.state, selectedNode->state) < insertIndex)
  349. --insertIndex;
  350. selectedNode->removeItemFromProject();
  351. destNode.addChild (*selectedNode, insertIndex++);
  352. }
  353. }
  354. static int indexOfNode (const ValueTree& parent, const ValueTree& child)
  355. {
  356. for (auto i = parent.getNumChildren(); --i >= 0;)
  357. if (parent.getChild (i) == child)
  358. return i;
  359. return -1;
  360. }
  361. };
  362. //==============================================================================
  363. class SourceFileItem : public FileTreeItemBase
  364. {
  365. public:
  366. SourceFileItem (const Project::Item& projectItem)
  367. : FileTreeItemBase (projectItem)
  368. {
  369. }
  370. bool acceptsFileDrop (const StringArray&) const override { return false; }
  371. bool acceptsDragItems (const OwnedArray <Project::Item>&) override { return false; }
  372. String getDisplayName() const override
  373. {
  374. return getFile().getFileName();
  375. }
  376. void paintItem (Graphics& g, int width, int height) override
  377. {
  378. JucerTreeViewBase::paintItem (g, width, height);
  379. if (item.needsSaving())
  380. {
  381. auto bounds = g.getClipBounds().withY (0).withHeight (height);
  382. g.setFont (getFont());
  383. g.setColour (getContentColour (false));
  384. g.drawFittedText ("*", bounds.removeFromLeft (height), Justification::centred, 1);
  385. }
  386. }
  387. static File findCorrespondingHeaderOrCpp (const File& f)
  388. {
  389. if (f.hasFileExtension (sourceFileExtensions)) return f.withFileExtension (".h");
  390. if (f.hasFileExtension (headerFileExtensions)) return f.withFileExtension (".cpp");
  391. return {};
  392. }
  393. void setName (const String& newName) override
  394. {
  395. if (newName != File::createLegalFileName (newName))
  396. {
  397. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
  398. "That filename contained some illegal characters!");
  399. triggerAsyncRename (item);
  400. return;
  401. }
  402. auto oldFile = getFile();
  403. auto newFile = oldFile.getSiblingFile (newName);
  404. auto correspondingFile = findCorrespondingHeaderOrCpp (oldFile);
  405. if (correspondingFile.exists() && newFile.hasFileExtension (oldFile.getFileExtension()))
  406. {
  407. auto correspondingItem = item.project.getMainGroup().findItemForFile (correspondingFile);
  408. if (correspondingItem.isValid())
  409. {
  410. if (AlertWindow::showOkCancelBox (AlertWindow::NoIcon, "File Rename",
  411. "Do you also want to rename the corresponding file \"" + correspondingFile.getFileName()
  412. + "\" to match?"))
  413. {
  414. if (! item.renameFile (newFile))
  415. {
  416. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
  417. "Failed to rename \"" + oldFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
  418. return;
  419. }
  420. if (! correspondingItem.renameFile (newFile.withFileExtension (correspondingFile.getFileExtension())))
  421. {
  422. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
  423. "Failed to rename \"" + correspondingFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
  424. }
  425. }
  426. }
  427. }
  428. if (! item.renameFile (newFile))
  429. {
  430. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
  431. "Failed to rename the file!\n\nCheck your file permissions!");
  432. }
  433. }
  434. FileTreeItemBase* createSubItem (const Project::Item&) override
  435. {
  436. jassertfalse;
  437. return nullptr;
  438. }
  439. void showDocument() override
  440. {
  441. auto f = getFile();
  442. if (f.exists())
  443. if (auto* pcc = getProjectContentComponent())
  444. pcc->showEditorForFile (f, false);
  445. }
  446. void showPopupMenu() override
  447. {
  448. PopupMenu m;
  449. m.addItem (1, "Open in external editor");
  450. m.addItem (2,
  451. #if JUCE_MAC
  452. "Reveal in Finder");
  453. #else
  454. "Reveal in Explorer");
  455. #endif
  456. m.addItem (4, "Rename File...");
  457. m.addSeparator();
  458. if (auto* group = dynamic_cast<GroupItem*> (getParentItem()))
  459. {
  460. if (group->isRoot())
  461. {
  462. m.addItem (5, "Binary Resource", true, item.shouldBeAddedToBinaryResources());
  463. m.addItem (6, "Xcode Resource", true, item.shouldBeAddedToXcodeResources());
  464. m.addItem (7, "Compile", true, item.shouldBeCompiled());
  465. m.addSeparator();
  466. }
  467. }
  468. m.addItem (3, "Delete");
  469. launchPopupMenu (m);
  470. }
  471. void showAddMenu() override
  472. {
  473. if (auto* group = dynamic_cast<GroupItem*> (getParentItem()))
  474. group->showAddMenu();
  475. }
  476. void handlePopupMenuResult (int resultCode) override
  477. {
  478. switch (resultCode)
  479. {
  480. case 1: getFile().startAsProcess(); break;
  481. case 2: revealInFinder(); break;
  482. case 3: deleteAllSelectedItems(); break;
  483. case 4: triggerAsyncRename (item); break;
  484. case 5: item.getShouldAddToBinaryResourcesValue().setValue (! item.shouldBeAddedToBinaryResources()); break;
  485. case 6: item.getShouldAddToXcodeResourcesValue().setValue (! item.shouldBeAddedToXcodeResources()); break;
  486. case 7: item.getShouldCompileValue().setValue (! item.shouldBeCompiled()); break;
  487. default:
  488. if (auto* parentGroup = dynamic_cast<GroupItem*> (getParentProjectItem()))
  489. parentGroup->processCreateFileMenuItem (resultCode);
  490. break;
  491. }
  492. }
  493. };
  494. //==============================================================================
  495. class GroupItem : public FileTreeItemBase
  496. {
  497. public:
  498. GroupItem (const Project::Item& projectItem, const String& filter = {})
  499. : FileTreeItemBase (projectItem),
  500. searchFilter (filter)
  501. {
  502. }
  503. bool isRoot() const override { return item.isMainGroup(); }
  504. bool acceptsFileDrop (const StringArray&) const override { return true; }
  505. void addNewGroup()
  506. {
  507. auto newGroup = item.addNewSubGroup ("New Group", 0);
  508. triggerAsyncRename (newGroup);
  509. }
  510. bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) override
  511. {
  512. for (auto i = selectedNodes.size(); --i >= 0;)
  513. if (item.canContain (*selectedNodes.getUnchecked(i)))
  514. return true;
  515. return false;
  516. }
  517. void addFilesAtIndex (const StringArray& files, int insertIndex) override
  518. {
  519. for (auto f : files)
  520. {
  521. if (item.addFileAtIndex (f, insertIndex, true))
  522. ++insertIndex;
  523. }
  524. }
  525. void addFilesRetainingSortOrder (const StringArray& files) override
  526. {
  527. for (auto i = files.size(); --i >= 0;)
  528. item.addFileRetainingSortOrder (files[i], true);
  529. }
  530. void moveSelectedItemsTo (OwnedArray<Project::Item>& selectedNodes, int insertIndex) override
  531. {
  532. moveItems (selectedNodes, item, insertIndex);
  533. }
  534. void checkFileStatus() override
  535. {
  536. for (int i = 0; i < getNumSubItems(); ++i)
  537. if (auto* p = dynamic_cast<FileTreeItemBase*> (getSubItem(i)))
  538. p->checkFileStatus();
  539. }
  540. bool isGroupEmpty (const Project::Item& group) // recursive
  541. {
  542. for (int i = 0; i < group.getNumChildren(); ++i)
  543. {
  544. auto child = group.getChild (i);
  545. if ((child.isGroup() && ! isGroupEmpty (child))
  546. || (child.isFile() && child.getName().containsIgnoreCase (searchFilter)))
  547. return false;
  548. }
  549. return true;
  550. }
  551. FileTreeItemBase* createSubItem (const Project::Item& child) override
  552. {
  553. if (child.isGroup())
  554. {
  555. if (searchFilter.isNotEmpty() && isGroupEmpty (child))
  556. return nullptr;
  557. return new GroupItem (child, searchFilter);
  558. }
  559. if (child.isFile())
  560. {
  561. if (child.getName().containsIgnoreCase (searchFilter))
  562. return new SourceFileItem (child);
  563. return nullptr;
  564. }
  565. jassertfalse;
  566. return nullptr;
  567. }
  568. void showDocument() override
  569. {
  570. if (auto* pcc = getProjectContentComponent())
  571. pcc->setEditorComponent (new FileGroupInformationComponent (item), nullptr);
  572. }
  573. static void openAllGroups (TreeViewItem* root)
  574. {
  575. for (int i = 0; i < root->getNumSubItems(); ++i)
  576. if (auto* sub = root->getSubItem (i))
  577. openOrCloseAllSubGroups (*sub, true);
  578. }
  579. static void closeAllGroups (TreeViewItem* root)
  580. {
  581. for (int i = 0; i < root->getNumSubItems(); ++i)
  582. if (auto* sub = root->getSubItem (i))
  583. openOrCloseAllSubGroups (*sub, false);
  584. }
  585. static void openOrCloseAllSubGroups (TreeViewItem& treeItem, bool shouldOpen)
  586. {
  587. treeItem.setOpen (shouldOpen);
  588. for (auto i = treeItem.getNumSubItems(); --i >= 0;)
  589. if (auto* sub = treeItem.getSubItem (i))
  590. openOrCloseAllSubGroups (*sub, shouldOpen);
  591. }
  592. static void setFilesToCompile (Project::Item projectItem, const bool shouldCompile)
  593. {
  594. if (projectItem.isFile() && (projectItem.getFile().hasFileExtension (fileTypesToCompileByDefault)))
  595. projectItem.getShouldCompileValue() = shouldCompile;
  596. for (auto i = projectItem.getNumChildren(); --i >= 0;)
  597. setFilesToCompile (projectItem.getChild (i), shouldCompile);
  598. }
  599. void showPopupMenu() override
  600. {
  601. PopupMenu m;
  602. addCreateFileMenuItems (m);
  603. m.addSeparator();
  604. m.addItem (1, "Collapse all Groups");
  605. m.addItem (2, "Expand all Groups");
  606. if (! isRoot())
  607. {
  608. if (isOpen())
  609. m.addItem (3, "Collapse all Sub-groups");
  610. else
  611. m.addItem (4, "Expand all Sub-groups");
  612. }
  613. m.addSeparator();
  614. m.addItem (5, "Enable compiling of all enclosed files");
  615. m.addItem (6, "Disable compiling of all enclosed files");
  616. m.addSeparator();
  617. m.addItem (7, "Sort Items Alphabetically");
  618. m.addItem (8, "Sort Items Alphabetically (Groups first)");
  619. m.addSeparator();
  620. if (! isRoot())
  621. {
  622. m.addItem (9, "Rename...");
  623. m.addItem (10, "Delete");
  624. }
  625. launchPopupMenu (m);
  626. }
  627. void showAddMenu() override
  628. {
  629. PopupMenu m;
  630. addCreateFileMenuItems (m);
  631. launchPopupMenu (m);
  632. }
  633. void handlePopupMenuResult (int resultCode) override
  634. {
  635. switch (resultCode)
  636. {
  637. case 1: closeAllGroups (getOwnerView()->getRootItem()); break;
  638. case 2: openAllGroups (getOwnerView()->getRootItem()); break;
  639. case 3: openOrCloseAllSubGroups (*this, false); break;
  640. case 4: openOrCloseAllSubGroups (*this, true); break;
  641. case 5: setFilesToCompile (item, true); break;
  642. case 6: setFilesToCompile (item, false); break;
  643. case 7: item.sortAlphabetically (false, false); break;
  644. case 8: item.sortAlphabetically (true, false); break;
  645. case 9: triggerAsyncRename (item); break;
  646. case 10: deleteAllSelectedItems(); break;
  647. default: processCreateFileMenuItem (resultCode); break;
  648. }
  649. }
  650. void addCreateFileMenuItems (PopupMenu& m)
  651. {
  652. m.addItem (1001, "Add New Group");
  653. m.addItem (1002, "Add Existing Files...");
  654. m.addSeparator();
  655. NewFileWizard().addWizardsToMenu (m);
  656. }
  657. void processCreateFileMenuItem (int menuID)
  658. {
  659. switch (menuID)
  660. {
  661. case 1001: addNewGroup(); break;
  662. case 1002: browseToAddExistingFiles(); break;
  663. default:
  664. jassert (getProject() != nullptr);
  665. NewFileWizard().runWizardFromMenu (menuID, *getProject(), item);
  666. break;
  667. }
  668. }
  669. Project* getProject()
  670. {
  671. if (auto* tv = getOwnerView())
  672. if (auto* pcc = tv->findParentComponentOfClass<ProjectContentComponent>())
  673. return pcc->getProject();
  674. return nullptr;
  675. }
  676. void setSearchFilter (const String& filter) override
  677. {
  678. searchFilter = filter;
  679. refreshSubItems();
  680. }
  681. String searchFilter;
  682. };