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.

842 lines
28KB

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