1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-11 23:54:18 +00:00
JUCE/extras/Projucer/Source/Project/jucer_ProjectTab.h

567 lines
18 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#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<float> 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<ConfigTreeItemTypes::ExporterItem*> (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<FileTreeItemTypes::ProjectTreeItemBase*> (rootItem.get()))
p->checkFileStatus();
}
void setSearchFilter (const String& filter)
{
if (auto* p = dynamic_cast<FileTreeItemTypes::GroupItem*> (rootItem.get()))
p->setSearchFilter (filter);
}
};
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<ExportersTreeRoot*> (rootItem.get()))
root->removeExporter (i);
}
};
//==============================================================================
class ConcertinaTreeComponent : public Component,
private Button::Listener,
private ChangeListener
{
public:
ConcertinaTreeComponent (TreePanelBase* tree, bool showSettingsButton = false, bool showFindPanel = false)
: treeToDisplay (tree)
{
addAndMakeVisible (popupMenuButton = new IconButton ("Add", &getIcons().plus));
popupMenuButton->addListener (this);
if (showSettingsButton)
{
addAndMakeVisible (settingsButton = new IconButton ("Settings", &getIcons().settings));
settingsButton->addListener (this);
}
if (showFindPanel)
{
addAndMakeVisible (findPanel = new FindPanel());
findPanel->addChangeListener (this);
}
addAndMakeVisible (treeToDisplay);
}
~ConcertinaTreeComponent()
{
treeToDisplay = nullptr;
popupMenuButton = nullptr;
findPanel = nullptr;
settingsButton = nullptr;
}
void resized() override
{
auto bounds = getLocalBounds();
auto bottomSlice = bounds.removeFromBottom (25);
bottomSlice.removeFromRight (5);
popupMenuButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
if (settingsButton != nullptr)
settingsButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
if (findPanel != nullptr)
findPanel->setBounds (bottomSlice.reduced (2));
treeToDisplay->setBounds (bounds);
}
TreePanelBase* getTree() const noexcept { return treeToDisplay.get(); }
void grabFindFocus()
{
if (findPanel != nullptr)
findPanel->grabKeyboardFocus();
}
private:
ScopedPointer<TreePanelBase> treeToDisplay;
ScopedPointer<IconButton> popupMenuButton, settingsButton;
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<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
root->showPopupMenu();
}
else
{
auto* selectedItem = treeToDisplay->tree.getSelectedItem (0);
if (auto* fileItem = dynamic_cast<FileTreeItemTypes::ProjectTreeItemBase*> (selectedItem))
fileItem->showPlusMenu();
else if (auto* exporterItem = dynamic_cast<ConfigTreeItemTypes::ExporterItem*> (selectedItem))
exporterItem->showPlusMenu();
}
}
else if (b == settingsButton)
{
if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
{
treeToDisplay->tree.clearSelectedItems();
root->showDocument();
}
}
}
void changeListenerCallback (ChangeBroadcaster* source) override
{
if (source == findPanel)
if (auto* fileTree = dynamic_cast<FileTreePanel*> (treeToDisplay.get()))
fileTree->setSearchFilter (findPanel->editor.getText());
}
class FindPanel : public Component,
public ChangeBroadcaster,
private TextEditor::Listener,
private Timer,
private FocusChangeListener
{
public:
FindPanel()
{
addAndMakeVisible (editor);
editor.addListener (this);
Desktop::getInstance().addFocusChangeListener (this);
lookAndFeelChanged();
}
~FindPanel()
{
Desktop::getInstance().removeFocusChangeListener (this);
}
void paintOverChildren (Graphics& g) override
{
if (! isFocused)
return;
g.setColour (findColour (defaultHighlightColourId));
Path p;
p.addRoundedRectangle (getLocalBounds().reduced (2), 3.0f);
g.strokePath (p, PathStrokeType (2.0f));
}
void resized() override
{
editor.setBounds (getLocalBounds().reduced (2));
}
TextEditor editor;
private:
void lookAndFeelChanged() override
{
editor.setTextToShowWhenEmpty ("Filter...", findColour (widgetTextColourId).withAlpha (0.3f));
}
void textEditorTextChanged (TextEditor&) override
{
startTimer (250);
}
void textEditorFocusLost (TextEditor&) override
{
isFocused = false;
repaint();
}
void globalFocusChanged (Component* focusedComponent) override
{
if (focusedComponent == &editor)
{
isFocused = true;
repaint();
}
}
void timerCallback() override
{
stopTimer();
sendChangeMessage();
}
bool isFocused = false;
};
ScopedPointer<FindPanel> findPanel;
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<ConcertinaTreeComponent*> (concertinaPanel.getPanel (i)))
{
if (auto* base = treeComponent->getTree())
if (base->tree.getNumSelectedItems() != 0)
return base;
}
}
return nullptr;
}
FileTreePanel* getFileTreePanel()
{
if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (0)))
return dynamic_cast<FileTreePanel*> (panel->getTree());
return nullptr;
}
ModuleTreePanel* getModuleTreePanel()
{
if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (1)))
return dynamic_cast<ModuleTreePanel*> (panel->getTree());
return nullptr;
}
ExportersTreePanel* getExportersTreePanel()
{
if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (2)))
return dynamic_cast<ExportersTreePanel*> (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<ConcertinaHeader> headers;
Project* project = nullptr;
//==============================================================================
void changeListenerCallback (ChangeBroadcaster* source) override
{
if (auto* header = dynamic_cast<ConcertinaHeader*> (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), false, true), 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<TreePanelBase*> (p);
if (base == nullptr)
base = dynamic_cast<ConcertinaTreeComponent*> (p)->getTree();
if (base != nullptr)
base->tree.clearSelectedItems();
}
}
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectTab)
};