/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ #include "../jucer_Headers.h" #include "jucer_TreeItemTypes.h" //============================================================================== class ConcertinaHeader : public Component, public ChangeBroadcaster { public: ConcertinaHeader (String n, Path p) : Component (n), name (n), iconPath (p) { panelIcon = Icon (iconPath, Colours::white); nameLabel.setText (name, dontSendNotification); nameLabel.setJustificationType (Justification::centredLeft); nameLabel.setInterceptsMouseClicks (false, false); nameLabel.setColour (Label::textColourId, Colours::white); addAndMakeVisible (nameLabel); } void resized() override { auto b = getLocalBounds().toFloat(); iconBounds = b.removeFromLeft (b.getHeight()).reduced (7, 7); arrowBounds = b.removeFromRight (b.getHeight()); nameLabel.setBounds (b.toNearestInt()); } void paint (Graphics& g) override { g.setColour (findColour (defaultButtonBackgroundColourId)); g.fillRoundedRectangle (getLocalBounds().reduced (2, 3).toFloat(), 2.0f); g.setColour (Colours::white); g.fillPath (arrowPath = ProjucerLookAndFeel::getArrowPath (arrowBounds, getParentComponent()->getBoundsInParent().getY() == yPosition ? 2 : 0, true, Justification::centred)); panelIcon.draw (g, iconBounds.toFloat(), false); } void mouseUp (const MouseEvent& e) override { if (arrowPath.getBounds().expanded (3).contains (e.getPosition().toFloat())) sendChangeMessage(); } int direction = 0; int yPosition = 0; private: String name; Label nameLabel; Path iconPath; Icon panelIcon; Rectangle arrowBounds, iconBounds; Path arrowPath; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaHeader) }; //============================================================================== class ExportersTreeRoot : public JucerTreeViewBase, private ValueTree::Listener { public: ExportersTreeRoot (Project& p) : project (p), exportersTree (project.getExporters()) { exportersTree.addListener (this); } bool isRoot() const override { return true; } bool canBeSelected() const override { return true; } bool isMissing() const override { return false; } bool mightContainSubItems() override { return project.getNumExporters() > 0; } String getUniqueName() const override { return "exporters"; } String getRenamingName() const override { return getDisplayName(); } String getDisplayName() const override { return "Exporters"; } void setName (const String&) override {} Icon getIcon() const override { return project.getMainGroup().getIcon (isOpen()).withColour (getContentColour (true)); } void showPopupMenu() override { if (auto* pcc = getProjectContentComponent()) pcc->showNewExporterMenu(); } void addSubItems() override { int i = 0; for (Project::ExporterIterator exporter (project); exporter.next(); ++i) addSubItem (new ConfigTreeItemTypes::ExporterItem (project, exporter.exporter.release(), i)); } bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override { return dragSourceDetails.description.toString().startsWith (getUniqueName()); } void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override { int oldIndex = dragSourceDetails.description.toString().getTrailingIntValue(); exportersTree.moveChild (oldIndex, jmax (0, insertIndex), project.getUndoManagerFor (exportersTree)); } void itemOpennessChanged (bool isNowOpen) override { if (isNowOpen) refreshSubItems(); } void removeExporter (int index) { if (auto* exporter = dynamic_cast (getSubItem (index))) exporter->deleteItem(); } private: Project& project; ValueTree exportersTree; //========================================================================== void valueTreePropertyChanged (ValueTree&, const Identifier&) override {} void valueTreeParentChanged (ValueTree&) override {} void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { refreshIfNeeded (parentTree); } void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { refreshIfNeeded (parentTree); } void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { refreshIfNeeded (parentTree); } void refreshIfNeeded (ValueTree& changedTree) { if (changedTree == exportersTree) refreshSubItems(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExportersTreeRoot) }; struct FileTreePanel : public TreePanelBase { FileTreePanel (Project& p) : TreePanelBase (&p, "fileTreeState") { tree.setMultiSelectEnabled (true); setRoot (new FileTreeItemTypes::GroupItem (p.getMainGroup())); tree.setRootItemVisible (false); } void updateMissingFileStatuses() { if (auto* p = dynamic_cast (rootItem.get())) p->checkFileStatus(); } }; struct ModuleTreePanel : public TreePanelBase { ModuleTreePanel (Project& p) : TreePanelBase (&p, "moduleTreeState") { tree.setMultiSelectEnabled (false); setRoot (new ConfigTreeItemTypes::EnabledModulesItem (p)); tree.setRootItemVisible (false); } }; struct ExportersTreePanel : public TreePanelBase { ExportersTreePanel (Project& p) : TreePanelBase (&p, "exportersTreeState") { tree.setMultiSelectEnabled (false); setRoot (new ExportersTreeRoot (p)); tree.setRootItemVisible (false); } void deleteSelectedItems() override { for (int i = rootItem->getNumSubItems() - 1; i >= 0; --i) if (rootItem->getSubItem (i)->isSelected()) if (auto* root = dynamic_cast (rootItem.get())) root->removeExporter (i); } }; //============================================================================== class ConcertinaTreeComponent : public Component, private Button::Listener { public: ConcertinaTreeComponent (TreePanelBase* tree, bool showSettings = false) : treeToDisplay (tree), showSettingsButton (showSettings) { addAndMakeVisible (popupMenuButton = new IconButton ("Add", &getIcons().plus)); popupMenuButton->addListener (this); if (showSettingsButton) { addAndMakeVisible (settingsButton = new IconButton ("Settings", &getIcons().settings)); settingsButton->addListener (this); } addAndMakeVisible (treeToDisplay); } ~ConcertinaTreeComponent() { treeToDisplay = nullptr; popupMenuButton = nullptr; } void resized() override { auto bounds = getLocalBounds(); auto bottomSlice = bounds.removeFromBottom (25); bottomSlice.removeFromRight (5); popupMenuButton->setBounds (bottomSlice.removeFromRight (25).reduced (2)); if (showSettingsButton) settingsButton->setBounds (bottomSlice.removeFromRight(25).reduced (2)); treeToDisplay->setBounds (bounds); } TreePanelBase* getTree() const noexcept { return treeToDisplay.get(); } private: ScopedPointer treeToDisplay; ScopedPointer popupMenuButton, settingsButton; bool showSettingsButton; void buttonClicked (Button* b) override { if (b == popupMenuButton) { auto numSelected = treeToDisplay->tree.getNumSelectedItems(); if (numSelected > 1) return; if (numSelected == 0) { if (auto* root = dynamic_cast (treeToDisplay->tree.getRootItem())) root->showPopupMenu(); } else { auto* selectedItem = treeToDisplay->tree.getSelectedItem (0); if (auto* fileItem = dynamic_cast (selectedItem)) fileItem->showPlusMenu(); else if (auto* exporterItem = dynamic_cast (selectedItem)) exporterItem->showPlusMenu(); } } else if (b == settingsButton) { if (auto* root = dynamic_cast (treeToDisplay->tree.getRootItem())) { treeToDisplay->tree.clearSelectedItems(); root->showDocument(); } } } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaTreeComponent) }; //============================================================================== class ProjectTab : public Component, private ChangeListener { public: ProjectTab (Project* p) : project (p) { addAndMakeVisible (concertinaPanel); buildConcertina(); } ~ProjectTab() { getFileTreePanel()->saveOpenness(); getModuleTreePanel()->saveOpenness(); getExportersTreePanel()->saveOpenness(); } void paint (Graphics& g) override { g.fillAll (findColour (secondaryBackgroundColourId)); } void resized() override { concertinaPanel.setBounds (getLocalBounds()); } TreePanelBase* getTreeWithSelectedItems() { for (int i = concertinaPanel.getNumPanels() - 1; i >= 0; --i) { if (auto* treeComponent = dynamic_cast (concertinaPanel.getPanel (i))) { if (auto* base = treeComponent->getTree()) if (base->tree.getNumSelectedItems() != 0) return base; } } return nullptr; } FileTreePanel* getFileTreePanel() { if (auto* panel = dynamic_cast (concertinaPanel.getPanel (0))) return dynamic_cast (panel->getTree()); return nullptr; } ModuleTreePanel* getModuleTreePanel() { if (auto* panel = dynamic_cast (concertinaPanel.getPanel (1))) return dynamic_cast (panel->getTree()); return nullptr; } ExportersTreePanel* getExportersTreePanel() { if (auto* panel = dynamic_cast (concertinaPanel.getPanel (2))) return dynamic_cast (panel->getTree()); return nullptr; } void showPanel (int panelIndex) { jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels())); concertinaPanel.expandPanelFully (concertinaPanel.getPanel (panelIndex), true); } void setPanelHeightProportion (int panelIndex, float prop) { jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels())); concertinaPanel.setPanelSize (concertinaPanel.getPanel (panelIndex), roundToInt (prop * (concertinaPanel.getHeight() - 90)), false); } float getPanelHeightProportion (int panelIndex) { jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels())); return ((float) (concertinaPanel.getPanel (panelIndex)->getHeight()) / (concertinaPanel.getHeight() - 90)); } private: ConcertinaPanel concertinaPanel; OwnedArray headers; Project* project = nullptr; //============================================================================== void changeListenerCallback (ChangeBroadcaster* source) override { if (auto* header = dynamic_cast (source)) { auto index = headers.indexOf (header); concertinaPanel.expandPanelFully (concertinaPanel.getPanel (index), true); } } void buildConcertina() { for (int i = concertinaPanel.getNumPanels() - 1; i >= 0 ; --i) concertinaPanel.removePanel (concertinaPanel.getPanel (i)); headers.clear(); if (project != nullptr) { concertinaPanel.addPanel (0, new ConcertinaTreeComponent (new FileTreePanel (*project)), true); concertinaPanel.addPanel (1, new ConcertinaTreeComponent (new ModuleTreePanel (*project), true), true); concertinaPanel.addPanel (2, new ConcertinaTreeComponent (new ExportersTreePanel (*project)), true); } headers.add (new ConcertinaHeader ("File explorer", getIcons().fileExplorer)); headers.add (new ConcertinaHeader ("Modules", getIcons().modules)); headers.add (new ConcertinaHeader ("Exporters", getIcons().exporter)); for (int i = 0; i < concertinaPanel.getNumPanels(); ++i) { auto* p = concertinaPanel.getPanel (i); auto* h = headers.getUnchecked (i); p->addMouseListener (this, true); h->addChangeListener (this); h->yPosition = i * 30; concertinaPanel.setCustomPanelHeader (p, h, false); concertinaPanel.setPanelHeaderSize (p, 30); } } void mouseDown (const MouseEvent& e) override { for (int i = concertinaPanel.getNumPanels() - 1; i >= 0; --i) { auto* p = concertinaPanel.getPanel (i); if (! (p->isParentOf (e.eventComponent))) { auto* base = dynamic_cast (p); if (base == nullptr) base = dynamic_cast (p)->getTree(); if (base != nullptr) base->tree.clearSelectedItems(); } } } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectTab) };