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.

876 lines
30KB

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