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.

883 lines
30KB

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