git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,401 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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.
==============================================================================
*/
#pragma once
//==============================================================================
class ExporterItem : public ProjectTreeItemBase,
private Value::Listener
{
public:
ExporterItem (Project& p, ProjectExporter* e, int index)
: project (p), exporter (e), configListTree (exporter->getConfigurations()),
exporterIndex (index)
{
exporter->initialiseDependencyPathValues();
configListTree.addListener (this);
targetLocationValue.referTo (exporter->getTargetLocationValue());
targetLocationValue.addListener (this);
}
int getItemHeight() const override { return 25; }
bool canBeSelected() const override { return true; }
bool mightContainSubItems() override { return exporter->getNumConfigurations() > 0; }
String getUniqueName() const override { return "exporter_" + String (exporterIndex); }
String getRenamingName() const override { return getDisplayName(); }
String getDisplayName() const override { return exporter->getUniqueName(); }
void setName (const String&) override {}
bool isMissing() const override { return false; }
String getTooltip() override { return getDisplayName(); }
static Icon getIconForExporter (ProjectExporter* e)
{
if (e != nullptr)
{
if (e->isXcode()) return Icon (getIcons().xcode, Colours::transparentBlack);
else if (e->isVisualStudio()) return Icon (getIcons().visualStudio, Colours::transparentBlack);
else if (e->isAndroid()) return Icon (getIcons().android, Colours::transparentBlack);
else if (e->isCodeBlocks()) return Icon (getIcons().codeBlocks, Colours::transparentBlack);
else if (e->isMakefile()) return Icon (getIcons().linux, Colours::transparentBlack);
else if (e->isCLion()) return Icon (getIcons().clion, Colours::transparentBlack);
}
return Icon();
}
Icon getIcon() const override
{
return getIconForExporter (exporter.get()).withColour (getContentColour (true));
}
void showDocument() override
{
showSettingsPage (new SettingsComp (*exporter));
}
void deleteItem() override
{
auto resultCallback = [safeThis = WeakReference<ExporterItem> { this }] (int result)
{
if (safeThis == nullptr || result == 0)
return;
safeThis->closeSettingsPage();
auto parent = safeThis->exporter->settings.getParent();
parent.removeChild (safeThis->exporter->settings,
safeThis->project.getUndoManagerFor (parent));
};
AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon,
"Delete Exporter",
"Are you sure you want to delete this export target?",
"",
"",
nullptr,
ModalCallbackFunction::create (std::move (resultCallback)));
}
void addSubItems() override
{
for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
addSubItem (new ConfigItem (config.config, *exporter));
}
void showPopupMenu (Point<int> p) override
{
PopupMenu menu;
menu.addItem (1, "Add a new configuration", exporter->supportsUserDefinedConfigurations());
menu.addItem (2, "Save this exporter");
menu.addSeparator();
menu.addItem (3, "Delete this exporter");
launchPopupMenu (menu, p);
}
void showAddMenu (Point<int> p) override
{
PopupMenu menu;
menu.addItem (1, "Add a new configuration", exporter->supportsUserDefinedConfigurations());
launchPopupMenu (menu, p);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode == 1)
exporter->addNewConfiguration (false);
else if (resultCode == 2)
project.saveProject (Async::yes, exporter.get(), nullptr);
else if (resultCode == 3)
deleteAllSelectedItems();
}
var getDragSourceDescription() override
{
return getParentItem()->getUniqueName() + "/" + String (exporterIndex);
}
bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
{
return dragSourceDetails.description.toString().startsWith (getUniqueName());
}
void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override
{
auto oldIndex = indexOfConfig (dragSourceDetails.description.toString().fromLastOccurrenceOf ("||", false, false));
if (oldIndex >= 0)
configListTree.moveChild (oldIndex, insertIndex, project.getUndoManagerFor (configListTree));
}
int indexOfConfig (const String& configName)
{
int i = 0;
for (ProjectExporter::ConfigIterator config (*exporter); config.next(); ++i)
if (config->getName() == configName)
return i;
return -1;
}
//==============================================================================
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 == configListTree)
refreshSubItems();
}
private:
Project& project;
std::unique_ptr<ProjectExporter> exporter;
ValueTree configListTree;
int exporterIndex;
Value targetLocationValue;
void valueChanged (Value& value) override
{
if (value == exporter->getTargetLocationValue())
refreshSubItems();
}
//==============================================================================
struct SettingsComp : public Component
{
SettingsComp (ProjectExporter& exp)
: group (exp.getUniqueName(),
ExporterItem::getIconForExporter (&exp),
exp.getDescription())
{
addAndMakeVisible (group);
PropertyListBuilder props;
exp.createPropertyEditors (props);
group.setProperties (props);
parentSizeChanged();
}
void parentSizeChanged() override { updateSize (*this, group); }
void resized() override { group.setBounds (getLocalBounds().withTrimmedLeft (12)); }
PropertyGroupComponent group;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SettingsComp)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterItem)
JUCE_DECLARE_WEAK_REFERENCEABLE (ExporterItem)
};
//==============================================================================
class ConfigItem : public ProjectTreeItemBase
{
public:
ConfigItem (const ProjectExporter::BuildConfiguration::Ptr& conf, ProjectExporter& e)
: config (conf), exporter (e), configTree (config->config)
{
jassert (config != nullptr);
configTree.addListener (this);
}
bool isMissing() const override { return false; }
bool canBeSelected() const override { return true; }
bool mightContainSubItems() override { return false; }
String getUniqueName() const override { return "config_" + config->getName(); }
String getRenamingName() const override { return getDisplayName(); }
String getDisplayName() const override { return config->getName(); }
void setName (const String&) override {}
Icon getIcon() const override { return Icon (getIcons().config, getContentColour (true)); }
void itemOpennessChanged (bool) override {}
void showDocument() override
{
showSettingsPage (new SettingsComp (*config));
}
void deleteItem() override
{
AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon,
"Delete Configuration",
"Are you sure you want to delete this configuration?",
"",
"",
nullptr,
ModalCallbackFunction::create ([parent = WeakReference<ConfigItem> { this }] (int result)
{
if (parent == nullptr)
return;
if (result == 0)
return;
parent->closeSettingsPage();
parent->config->removeFromExporter();
}));
}
void showPopupMenu (Point<int> p) override
{
bool enabled = exporter.supportsUserDefinedConfigurations();
PopupMenu menu;
menu.addItem (1, "Create a copy of this configuration", enabled);
menu.addSeparator();
menu.addItem (2, "Delete this configuration", enabled);
launchPopupMenu (menu, p);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode == 1)
exporter.addNewConfigurationFromExisting (*config);
else if (resultCode == 2)
deleteAllSelectedItems();
}
var getDragSourceDescription() override
{
return getParentItem()->getUniqueName() + "||" + config->getName();
}
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { repaintItem(); }
private:
ProjectExporter::BuildConfiguration::Ptr config;
ProjectExporter& exporter;
ValueTree configTree;
//==============================================================================
class SettingsComp : public Component
{
public:
SettingsComp (ProjectExporter::BuildConfiguration& conf)
: group (conf.exporter.getUniqueName() + " - " + conf.getName(), Icon (getIcons().config, Colours::transparentBlack))
{
addAndMakeVisible (group);
PropertyListBuilder props;
conf.createPropertyEditors (props);
group.setProperties (props);
parentSizeChanged();
}
void parentSizeChanged() override { updateSize (*this, group); }
void resized() override { group.setBounds (getLocalBounds().withTrimmedLeft (12)); }
private:
PropertyGroupComponent group;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SettingsComp)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConfigItem)
JUCE_DECLARE_WEAK_REFERENCEABLE (ConfigItem)
};
//==============================================================================
class ExportersTreeRoot : public ProjectTreeItemBase
{
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 (Point<int>) override
{
if (auto* pcc = getProjectContentComponent())
pcc->showNewExporterMenu();
}
void addSubItems() override
{
int i = 0;
for (Project::ExporterIterator exporter (project); exporter.next(); ++i)
addSubItem (new TreeItemTypes::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<TreeItemTypes::ExporterItem*> (getSubItem (index)))
exporter->deleteItem();
}
private:
Project& project;
ValueTree exportersTree;
//==============================================================================
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)
};

View File

@ -0,0 +1,882 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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.
==============================================================================
*/
#pragma once
//==============================================================================
class FileTreeItemBase : public JucerTreeViewBase,
private ValueTree::Listener
{
public:
FileTreeItemBase (const Project::Item& projectItem)
: item (projectItem), isFileMissing (false)
{
item.state.addListener (this);
}
~FileTreeItemBase() override
{
item.state.removeListener (this);
}
//==============================================================================
virtual bool acceptsFileDrop (const StringArray& files) const = 0;
virtual bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) = 0;
//==============================================================================
String getDisplayName() const override { return item.getName(); }
String getRenamingName() const override { return getDisplayName(); }
void setName (const String& newName) override { item.getNameValue() = newName; }
bool isMissing() const override { return isFileMissing; }
virtual File getFile() const { return item.getFile(); }
void deleteItem() override { item.removeItemFromProject(); }
void deleteAllSelectedItems() override
{
auto* tree = getOwnerView();
Array<File> filesToTrash;
Array<Project::Item> itemsToRemove;
for (int i = 0; i < tree->getNumSelectedItems(); ++i)
{
if (auto* p = dynamic_cast<FileTreeItemBase*> (tree->getSelectedItem (i)))
{
itemsToRemove.add (p->item);
if (p->item.isGroup())
{
for (int j = 0; j < p->item.getNumChildren(); ++j)
{
auto associatedFile = p->item.getChild (j).getFile();
if (associatedFile.existsAsFile())
filesToTrash.addIfNotAlreadyThere (associatedFile);
}
}
else if (p->getFile().existsAsFile())
{
filesToTrash.addIfNotAlreadyThere (p->getFile());
}
}
}
WeakReference<FileTreeItemBase> treeRootItem { dynamic_cast<FileTreeItemBase*> (tree->getRootItem()) };
if (treeRootItem == nullptr)
{
jassertfalse;
return;
}
auto doDelete = [treeRootItem, itemsToRemove] (const Array<File>& fsToTrash)
{
if (treeRootItem == nullptr)
return;
auto& om = ProjucerApplication::getApp().openDocumentManager;
for (auto i = fsToTrash.size(); --i >= 0;)
{
auto f = fsToTrash.getUnchecked(i);
om.closeFileWithoutSaving (f);
if (! f.moveToTrash())
{
// xxx
}
}
for (auto i = itemsToRemove.size(); --i >= 0;)
{
if (auto itemToRemove = treeRootItem->findTreeViewItem (itemsToRemove.getUnchecked (i)))
{
if (auto* pcc = treeRootItem->getProjectContentComponent())
{
if (auto* fileInfoComp = dynamic_cast<FileGroupInformationComponent*> (pcc->getEditorComponent()))
if (fileInfoComp->getGroupPath() == itemToRemove->getFile().getFullPathName())
pcc->hideEditor();
}
om.closeFileWithoutSaving (itemToRemove->getFile());
itemToRemove->deleteItem();
}
}
};
if (! filesToTrash.isEmpty())
{
String fileList;
auto maxFilesToList = 10;
for (auto i = jmin (maxFilesToList, filesToTrash.size()); --i >= 0;)
fileList << filesToTrash.getUnchecked(i).getFullPathName() << "\n";
if (filesToTrash.size() > maxFilesToList)
fileList << "\n...plus " << (filesToTrash.size() - maxFilesToList) << " more files...";
AlertWindow::showYesNoCancelBox (MessageBoxIconType::NoIcon,
"Delete Project Items",
"As well as removing the selected item(s) from the project, do you also want to move their files to the trash:\n\n"
+ fileList,
"Just remove references",
"Also move files to Trash",
"Cancel",
tree->getTopLevelComponent(),
ModalCallbackFunction::create ([treeRootItem, filesToTrash, doDelete] (int r) mutable
{
if (treeRootItem == nullptr)
return;
if (r == 0)
return;
if (r != 2)
filesToTrash.clear();
doDelete (filesToTrash);
}));
return;
}
doDelete (filesToTrash);
}
virtual void revealInFinder() const
{
getFile().revealToUser();
}
virtual void browseToAddExistingFiles()
{
auto location = item.isGroup() ? item.determineGroupFolder() : getFile();
chooser = std::make_unique<FileChooser> ("Add Files to Jucer Project", location, "");
auto flags = FileBrowserComponent::openMode
| FileBrowserComponent::canSelectFiles
| FileBrowserComponent::canSelectDirectories
| FileBrowserComponent::canSelectMultipleItems;
chooser->launchAsync (flags, [this] (const FileChooser& fc)
{
if (fc.getResults().isEmpty())
return;
StringArray files;
for (int i = 0; i < fc.getResults().size(); ++i)
files.add (fc.getResults().getReference(i).getFullPathName());
addFilesRetainingSortOrder (files);
});
}
virtual void checkFileStatus() // (recursive)
{
auto file = getFile();
auto nowMissing = (file != File() && ! file.exists());
if (nowMissing != isFileMissing)
{
isFileMissing = nowMissing;
repaintItem();
}
}
virtual void addFilesAtIndex (const StringArray& files, int insertIndex)
{
if (auto* p = getParentProjectItem())
p->addFilesAtIndex (files, insertIndex);
}
virtual void addFilesRetainingSortOrder (const StringArray& files)
{
if (auto* p = getParentProjectItem())
p->addFilesRetainingSortOrder (files);
}
virtual void moveSelectedItemsTo (OwnedArray<Project::Item>&, int /*insertIndex*/)
{
jassertfalse;
}
void showMultiSelectionPopupMenu (Point<int> p) override
{
PopupMenu m;
m.addItem (1, "Delete");
m.showMenuAsync (PopupMenu::Options().withTargetScreenArea ({ p.x, p.y, 1, 1 }),
ModalCallbackFunction::create (treeViewMultiSelectItemChosen, this));
}
static void treeViewMultiSelectItemChosen (int resultCode, FileTreeItemBase* item)
{
switch (resultCode)
{
case 1: item->deleteAllSelectedItems(); break;
default: break;
}
}
virtual FileTreeItemBase* findTreeViewItem (const Project::Item& itemToFind)
{
if (item == itemToFind)
return this;
auto wasOpen = isOpen();
setOpen (true);
for (auto i = getNumSubItems(); --i >= 0;)
{
if (auto* pg = dynamic_cast<FileTreeItemBase*> (getSubItem(i)))
if (auto* found = pg->findTreeViewItem (itemToFind))
return found;
}
setOpen (wasOpen);
return nullptr;
}
//==============================================================================
bool mightContainSubItems() override { return item.getNumChildren() > 0; }
String getUniqueName() const override { jassert (item.getID().isNotEmpty()); return item.getID(); }
bool canBeSelected() const override { return true; }
String getTooltip() override { return {}; }
File getDraggableFile() const override { return getFile(); }
var getDragSourceDescription() override
{
cancelDelayedSelectionTimer();
return projectItemDragType;
}
void addSubItems() override
{
for (int i = 0; i < item.getNumChildren(); ++i)
if (auto* p = createSubItem (item.getChild(i)))
addSubItem (p);
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen)
refreshSubItems();
}
//==============================================================================
bool isInterestedInFileDrag (const StringArray& files) override
{
return acceptsFileDrop (files);
}
void filesDropped (const StringArray& files, int insertIndex) override
{
if (files.size() == 1 && File (files[0]).hasFileExtension (Project::projectFileExtension))
ProjucerApplication::getApp().openFile (files[0], [] (bool) {});
else
addFilesAtIndex (files, insertIndex);
}
bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
{
OwnedArray<Project::Item> selectedNodes;
getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
return selectedNodes.size() > 0 && acceptsDragItems (selectedNodes);
}
void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override
{
OwnedArray<Project::Item> selectedNodes;
getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
if (selectedNodes.size() > 0)
{
auto* tree = getOwnerView();
std::unique_ptr<XmlElement> oldOpenness (tree->getOpennessState (false));
moveSelectedItemsTo (selectedNodes, insertIndex);
if (oldOpenness != nullptr)
tree->restoreOpennessState (*oldOpenness, false);
}
}
int getMillisecsAllowedForDragGesture() override
{
// for images, give the user longer to start dragging before assuming they're
// clicking to select it for previewing..
return item.isImageFile() ? 250 : JucerTreeViewBase::getMillisecsAllowedForDragGesture();
}
static void getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
OwnedArray<Project::Item>& selectedNodes)
{
if (dragSourceDetails.description == projectItemDragType)
{
auto* tree = dynamic_cast<TreeView*> (dragSourceDetails.sourceComponent.get());
if (tree == nullptr)
tree = dragSourceDetails.sourceComponent->findParentComponentOfClass<TreeView>();
if (tree != nullptr)
{
auto numSelected = tree->getNumSelectedItems();
for (int i = 0; i < numSelected; ++i)
if (auto* p = dynamic_cast<FileTreeItemBase*> (tree->getSelectedItem (i)))
selectedNodes.add (new Project::Item (p->item));
}
}
}
FileTreeItemBase* getParentProjectItem() const
{
return dynamic_cast<FileTreeItemBase*> (getParentItem());
}
//==============================================================================
Project::Item item;
protected:
//==============================================================================
void valueTreePropertyChanged (ValueTree& tree, const Identifier&) override
{
if (tree == item.state)
repaintItem();
}
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { treeChildrenChanged (parentTree); }
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { treeChildrenChanged (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { treeChildrenChanged (parentTree); }
bool isFileMissing;
virtual FileTreeItemBase* createSubItem (const Project::Item& node) = 0;
Icon getIcon() const override
{
auto colour = getOwnerView()->findColour (isSelected() ? defaultHighlightedTextColourId
: treeIconColourId);
return item.getIcon (isOpen()).withColour (colour);
}
bool isIconCrossedOut() const override { return item.isIconCrossedOut(); }
void treeChildrenChanged (const ValueTree& parentTree)
{
if (parentTree == item.state)
{
refreshSubItems();
treeHasChanged();
setOpen (true);
}
}
void triggerAsyncRename (const Project::Item& itemToRename)
{
struct RenameMessage : public CallbackMessage
{
RenameMessage (TreeView* const t, const Project::Item& i)
: tree (t), itemToRename (i) {}
void messageCallback() override
{
if (tree != nullptr)
if (auto* root = dynamic_cast<FileTreeItemBase*> (tree->getRootItem()))
if (auto* found = root->findTreeViewItem (itemToRename))
found->showRenameBox();
}
private:
Component::SafePointer<TreeView> tree;
Project::Item itemToRename;
};
(new RenameMessage (getOwnerView(), itemToRename))->post();
}
static void moveItems (OwnedArray<Project::Item>& selectedNodes, Project::Item destNode, int insertIndex)
{
for (auto i = selectedNodes.size(); --i >= 0;)
{
auto* n = selectedNodes.getUnchecked(i);
if (destNode == *n || destNode.state.isAChildOf (n->state)) // Check for recursion.
return;
if (! destNode.canContain (*n))
selectedNodes.remove (i);
}
// Don't include any nodes that are children of other selected nodes..
for (auto i = selectedNodes.size(); --i >= 0;)
{
auto* n = selectedNodes.getUnchecked(i);
for (auto j = selectedNodes.size(); --j >= 0;)
{
if (j != i && n->state.isAChildOf (selectedNodes.getUnchecked(j)->state))
{
selectedNodes.remove (i);
break;
}
}
}
// Remove and re-insert them one at a time..
for (int i = 0; i < selectedNodes.size(); ++i)
{
auto* selectedNode = selectedNodes.getUnchecked(i);
if (selectedNode->state.getParent() == destNode.state
&& indexOfNode (destNode.state, selectedNode->state) < insertIndex)
--insertIndex;
selectedNode->removeItemFromProject();
destNode.addChild (*selectedNode, insertIndex++);
}
}
static int indexOfNode (const ValueTree& parent, const ValueTree& child)
{
for (auto i = parent.getNumChildren(); --i >= 0;)
if (parent.getChild (i) == child)
return i;
return -1;
}
private:
std::unique_ptr<FileChooser> chooser;
JUCE_DECLARE_WEAK_REFERENCEABLE (FileTreeItemBase)
};
//==============================================================================
class SourceFileItem : public FileTreeItemBase
{
public:
SourceFileItem (const Project::Item& projectItem)
: FileTreeItemBase (projectItem)
{
}
bool acceptsFileDrop (const StringArray&) const override { return false; }
bool acceptsDragItems (const OwnedArray<Project::Item>&) override { return false; }
String getDisplayName() const override
{
return getFile().getFileName();
}
void paintItem (Graphics& g, int width, int height) override
{
JucerTreeViewBase::paintItem (g, width, height);
if (item.needsSaving())
{
auto bounds = g.getClipBounds().withY (0).withHeight (height);
g.setFont (getFont());
g.setColour (getContentColour (false));
g.drawFittedText ("*", bounds.removeFromLeft (height), Justification::centred, 1);
}
}
static File findCorrespondingHeaderOrCpp (const File& f)
{
if (f.hasFileExtension (sourceFileExtensions)) return f.withFileExtension (".h");
if (f.hasFileExtension (headerFileExtensions)) return f.withFileExtension (".cpp");
return {};
}
void setName (const String& newName) override
{
if (newName != File::createLegalFileName (newName))
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"File Rename",
"That filename contained some illegal characters!");
triggerAsyncRename (item);
return;
}
auto oldFile = getFile();
auto newFile = oldFile.getSiblingFile (newName);
auto correspondingFile = findCorrespondingHeaderOrCpp (oldFile);
if (correspondingFile.exists() && newFile.hasFileExtension (oldFile.getFileExtension()))
{
auto correspondingItem = item.project.getMainGroup().findItemForFile (correspondingFile);
if (correspondingItem.isValid())
{
AlertWindow::showOkCancelBox (MessageBoxIconType::NoIcon,
"File Rename",
"Do you also want to rename the corresponding file \"" + correspondingFile.getFileName() + "\" to match?",
{},
{},
nullptr,
ModalCallbackFunction::create ([parent = WeakReference<SourceFileItem> { this },
oldFile, newFile, correspondingFile, correspondingItem] (int result) mutable
{
if (parent == nullptr || result == 0)
return;
if (! parent->item.renameFile (newFile))
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"File Rename",
"Failed to rename \"" + oldFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
return;
}
if (! correspondingItem.renameFile (newFile.withFileExtension (correspondingFile.getFileExtension())))
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"File Rename",
"Failed to rename \"" + correspondingFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
}
}));
}
}
if (! item.renameFile (newFile))
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"File Rename",
"Failed to rename the file!\n\nCheck your file permissions!");
}
}
FileTreeItemBase* createSubItem (const Project::Item&) override
{
jassertfalse;
return nullptr;
}
void showDocument() override
{
auto f = getFile();
if (f.exists())
if (auto* pcc = getProjectContentComponent())
pcc->showEditorForFile (f, false);
}
void showPopupMenu (Point<int> p) override
{
PopupMenu m;
m.addItem (1, "Open in external editor");
m.addItem (2,
#if JUCE_MAC
"Reveal in Finder");
#else
"Reveal in Explorer");
#endif
m.addItem (4, "Rename File...");
m.addSeparator();
m.addItem (5, "Binary Resource", true, item.shouldBeAddedToBinaryResources());
m.addItem (6, "Xcode Resource", true, item.shouldBeAddedToXcodeResources());
m.addItem (7, "Compile", item.isSourceFile(), item.shouldBeCompiled());
m.addItem (8, "Skip PCH", item.shouldBeCompiled(), item.shouldSkipPCH());
m.addSeparator();
m.addItem (3, "Delete");
launchPopupMenu (m, p);
}
void showAddMenu (Point<int> p) override
{
if (auto* group = dynamic_cast<GroupItem*> (getParentItem()))
group->showAddMenu (p);
}
void handlePopupMenuResult (int resultCode) override
{
switch (resultCode)
{
case 1: getFile().startAsProcess(); break;
case 2: revealInFinder(); break;
case 3: deleteAllSelectedItems(); break;
case 4: triggerAsyncRename (item); break;
case 5: item.getShouldAddToBinaryResourcesValue().setValue (! item.shouldBeAddedToBinaryResources()); break;
case 6: item.getShouldAddToXcodeResourcesValue().setValue (! item.shouldBeAddedToXcodeResources()); break;
case 7: item.getShouldCompileValue().setValue (! item.shouldBeCompiled()); break;
case 8: item.getShouldSkipPCHValue().setValue (! item.shouldSkipPCH()); break;
default:
if (auto* parentGroup = dynamic_cast<GroupItem*> (getParentProjectItem()))
parentGroup->processCreateFileMenuItem (resultCode);
break;
}
}
JUCE_DECLARE_WEAK_REFERENCEABLE (SourceFileItem)
};
//==============================================================================
class GroupItem : public FileTreeItemBase
{
public:
GroupItem (const Project::Item& projectItem, const String& filter = {})
: FileTreeItemBase (projectItem),
searchFilter (filter)
{
}
bool isRoot() const override { return item.isMainGroup(); }
bool acceptsFileDrop (const StringArray&) const override { return true; }
void addNewGroup()
{
auto newGroup = item.addNewSubGroup ("New Group", 0);
triggerAsyncRename (newGroup);
}
bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) override
{
for (auto i = selectedNodes.size(); --i >= 0;)
if (item.canContain (*selectedNodes.getUnchecked(i)))
return true;
return false;
}
void addFilesAtIndex (const StringArray& files, int insertIndex) override
{
for (auto f : files)
{
if (item.addFileAtIndex (f, insertIndex, true))
++insertIndex;
}
}
void addFilesRetainingSortOrder (const StringArray& files) override
{
for (auto i = files.size(); --i >= 0;)
item.addFileRetainingSortOrder (files[i], true);
}
void moveSelectedItemsTo (OwnedArray<Project::Item>& selectedNodes, int insertIndex) override
{
moveItems (selectedNodes, item, insertIndex);
}
void checkFileStatus() override
{
for (int i = 0; i < getNumSubItems(); ++i)
if (auto* p = dynamic_cast<FileTreeItemBase*> (getSubItem(i)))
p->checkFileStatus();
}
bool isGroupEmpty (const Project::Item& group) // recursive
{
for (int i = 0; i < group.getNumChildren(); ++i)
{
auto child = group.getChild (i);
if ((child.isGroup() && ! isGroupEmpty (child))
|| (child.isFile() && child.getName().containsIgnoreCase (searchFilter)))
return false;
}
return true;
}
FileTreeItemBase* createSubItem (const Project::Item& child) override
{
if (child.isGroup())
{
if (searchFilter.isNotEmpty() && isGroupEmpty (child))
return nullptr;
return new GroupItem (child, searchFilter);
}
if (child.isFile())
{
if (child.getName().containsIgnoreCase (searchFilter))
return new SourceFileItem (child);
return nullptr;
}
jassertfalse;
return nullptr;
}
void showDocument() override
{
if (auto* pcc = getProjectContentComponent())
pcc->setScrollableEditorComponent (std::make_unique<FileGroupInformationComponent> (item));
}
static void openAllGroups (TreeViewItem* root)
{
for (int i = 0; i < root->getNumSubItems(); ++i)
if (auto* sub = root->getSubItem (i))
openOrCloseAllSubGroups (*sub, true);
}
static void closeAllGroups (TreeViewItem* root)
{
for (int i = 0; i < root->getNumSubItems(); ++i)
if (auto* sub = root->getSubItem (i))
openOrCloseAllSubGroups (*sub, false);
}
static void openOrCloseAllSubGroups (TreeViewItem& treeItem, bool shouldOpen)
{
treeItem.setOpen (shouldOpen);
for (auto i = treeItem.getNumSubItems(); --i >= 0;)
if (auto* sub = treeItem.getSubItem (i))
openOrCloseAllSubGroups (*sub, shouldOpen);
}
static void setFilesToCompile (Project::Item projectItem, const bool shouldCompile)
{
if (projectItem.isFile() && (projectItem.getFile().hasFileExtension (fileTypesToCompileByDefault)))
projectItem.getShouldCompileValue() = shouldCompile;
for (auto i = projectItem.getNumChildren(); --i >= 0;)
setFilesToCompile (projectItem.getChild (i), shouldCompile);
}
void showPopupMenu (Point<int> p) override
{
PopupMenu m;
addCreateFileMenuItems (m);
m.addSeparator();
m.addItem (1, "Collapse all Groups");
m.addItem (2, "Expand all Groups");
if (! isRoot())
{
if (isOpen())
m.addItem (3, "Collapse all Sub-groups");
else
m.addItem (4, "Expand all Sub-groups");
}
m.addSeparator();
m.addItem (5, "Enable compiling of all enclosed files");
m.addItem (6, "Disable compiling of all enclosed files");
m.addSeparator();
m.addItem (7, "Sort Items Alphabetically");
m.addItem (8, "Sort Items Alphabetically (Groups first)");
m.addSeparator();
if (! isRoot())
{
m.addItem (9, "Rename...");
m.addItem (10, "Delete");
}
launchPopupMenu (m, p);
}
void showAddMenu (Point<int> p) override
{
PopupMenu m;
addCreateFileMenuItems (m);
launchPopupMenu (m, p);
}
void handlePopupMenuResult (int resultCode) override
{
switch (resultCode)
{
case 1: closeAllGroups (getOwnerView()->getRootItem()); break;
case 2: openAllGroups (getOwnerView()->getRootItem()); break;
case 3: openOrCloseAllSubGroups (*this, false); break;
case 4: openOrCloseAllSubGroups (*this, true); break;
case 5: setFilesToCompile (item, true); break;
case 6: setFilesToCompile (item, false); break;
case 7: item.sortAlphabetically (false, false); break;
case 8: item.sortAlphabetically (true, false); break;
case 9: triggerAsyncRename (item); break;
case 10: deleteAllSelectedItems(); break;
default: processCreateFileMenuItem (resultCode); break;
}
}
void addCreateFileMenuItems (PopupMenu& m)
{
m.addItem (1001, "Add New Group");
m.addItem (1002, "Add Existing Files...");
m.addSeparator();
wizard.addWizardsToMenu (m);
}
void processCreateFileMenuItem (int menuID)
{
switch (menuID)
{
case 1001: addNewGroup(); break;
case 1002: browseToAddExistingFiles(); break;
default:
jassert (getProject() != nullptr);
wizard.runWizardFromMenu (menuID, *getProject(), item);
break;
}
}
Project* getProject()
{
if (auto* tv = getOwnerView())
if (auto* pcc = tv->findParentComponentOfClass<ProjectContentComponent>())
return pcc->getProject();
return nullptr;
}
void setSearchFilter (const String& filter) override
{
searchFilter = filter;
refreshSubItems();
}
String searchFilter;
NewFileWizard wizard;
};

View File

@ -0,0 +1,694 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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.
==============================================================================
*/
#pragma once
//==============================================================================
class ModuleItem : public ProjectTreeItemBase
{
public:
ModuleItem (Project& p, const String& modID)
: project (p), moduleID (modID)
{
missingDependencies = project.getEnabledModules().getExtraDependenciesNeeded (moduleID).size() > 0;
cppStandardHigherThanProject = project.getEnabledModules().doesModuleHaveHigherCppStandardThanProject (moduleID);
moduleInfo = project.getEnabledModules().getModuleInfo (moduleID);
}
bool canBeSelected() const override { return true; }
bool mightContainSubItems() override { return false; }
String getUniqueName() const override { return "module_" + moduleID; }
String getDisplayName() const override { return moduleID; }
String getRenamingName() const override { return getDisplayName(); }
void setName (const String&) override {}
bool isMissing() const override { return missingDependencies; }
bool hasWarnings() const override { return cppStandardHigherThanProject; }
void showDocument() override
{
showSettingsPage (new ModuleSettingsPanel (project, moduleID, getOwnerView()));
}
void deleteItem() override
{
closeSettingsPage();
project.getEnabledModules().removeModule (moduleID);
}
Icon getIcon() const override
{
auto iconColour = getOwnerView()->findColour (isSelected() ? defaultHighlightedTextColourId
: treeIconColourId);
if (! isSelected())
{
if (moduleInfo.isValid() && moduleInfo.getVendor() == "juce")
{
if (moduleInfo.getLicense() == "ISC")
iconColour = Colours::lightblue;
else if (moduleInfo.getLicense() == "GPL/Commercial")
iconColour = Colours::orange;
}
}
return Icon (getIcons().singleModule, iconColour);
}
void showAddMenu (Point<int> p) override
{
if (auto* parent = dynamic_cast<EnabledModulesItem*> (getParentItem()))
parent->showPopupMenu (p);
}
void showPopupMenu (Point<int> p) override
{
PopupMenu menu;
menu.addItem (1, "Remove this module");
launchPopupMenu (menu, p);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode == 1)
deleteItem();
}
bool checkCppStandard()
{
auto oldVal = cppStandardHigherThanProject;
cppStandardHigherThanProject = project.getEnabledModules().doesModuleHaveHigherCppStandardThanProject (moduleID);
if (oldVal != cppStandardHigherThanProject)
return true;
return false;
}
Project& project;
String moduleID;
private:
ModuleDescription moduleInfo;
bool missingDependencies = false;
bool cppStandardHigherThanProject = false;
//==============================================================================
class ModuleSettingsPanel : public Component,
private Value::Listener,
private Timer
{
public:
ModuleSettingsPanel (Project& p, const String& modID, TreeView* tree)
: group (p.getEnabledModules().getModuleInfo (modID).getID(),
Icon (getIcons().singleModule, Colours::transparentBlack)),
project (p),
modulesTree (tree),
moduleID (modID)
{
addAndMakeVisible (group);
refresh();
}
void refresh()
{
auto& modules = project.getEnabledModules();
setEnabled (modules.isModuleEnabled (moduleID));
PropertyListBuilder props;
props.add (new ModuleInfoComponent (project, moduleID));
if (modules.getExtraDependenciesNeeded (moduleID).size() > 0)
props.add (new MissingDependenciesComponent (project, moduleID));
if (modules.doesModuleHaveHigherCppStandardThanProject (moduleID))
props.add (new CppStandardWarningComponent());
group.clearProperties();
exporterModulePathDefaultValues.clear();
exporterModulePathValues.clear();
globalPathValues.clear();
for (Project::ExporterIterator exporter (project); exporter.next();)
{
if (exporter->isCLion())
continue;
exporterModulePathDefaultValues.add (exporter->getPathForModuleValue (moduleID));
auto& defaultValue = exporterModulePathDefaultValues.getReference (exporterModulePathDefaultValues.size() - 1);
exporterModulePathValues.add (defaultValue.getPropertyAsValue());
auto pathComponent = std::make_unique<FilePathPropertyComponent> (defaultValue,
"Path for " + exporter->getUniqueName().quoted(),
true,
exporter->getTargetOSForExporter() == TargetOS::getThisOS(),
"*",
project.getProjectFolder());
pathComponent->setEnabled (! modules.shouldUseGlobalPath (moduleID));
props.add (pathComponent.release(),
"A path to the folder that contains the " + moduleID + " module when compiling the "
+ exporter->getUniqueName().quoted() + " target. "
"This can be an absolute path, or relative to the jucer project folder, but it "
"must be valid on the filesystem of the target machine that will be performing this build. If this "
"is empty then the global path will be used.");
globalPathValues.add (getAppSettings().getStoredPath (isJUCEModule (moduleID) ? Ids::defaultJuceModulePath : Ids::defaultUserModulePath,
exporter->getTargetOSForExporter()).getPropertyAsValue());
}
for (int i = 0; i < exporterModulePathDefaultValues.size(); ++i)
{
exporterModulePathDefaultValues.getReference (i).onDefaultChange = [this] { startTimer (50); };
exporterModulePathValues.getReference (i).addListener (this);
globalPathValues.getReference (i).addListener (this);
}
useGlobalPathValue.removeListener (this);
useGlobalPathValue.referTo (modules.shouldUseGlobalPathValue (moduleID));
useGlobalPathValue.addListener (this);
auto menuItemString = (TargetOS::getThisOS() == TargetOS::osx ? "\"Projucer->Global Paths...\""
: "\"File->Global Paths...\"");
props.add (new BooleanPropertyComponent (useGlobalPathValue,
"Use global path", "Use global path for this module"),
String ("If this is enabled, then the locally-stored global path (set in the ") + menuItemString + " menu item) "
"will be used as the path to this module. "
"This means that if this Projucer project is opened on another machine it will use that machine's global path as the path to this module.");
props.add (new BooleanPropertyComponent (modules.shouldCopyModuleFilesLocallyValue (moduleID),
"Create local copy", "Copy the module into the project folder"),
"If this is enabled, then a local copy of the entire module will be made inside your project (in the auto-generated JuceLibraryFiles folder), "
"so that your project will be self-contained, and won't need to contain any references to files in other folders. "
"This also means that you can check the module into your source-control system to make sure it is always in sync with your own code.");
props.add (new BooleanPropertyComponent (modules.shouldShowAllModuleFilesInProjectValue (moduleID),
"Add source to project", "Make module files browsable in projects"),
"If this is enabled, then the entire source tree from this module will be shown inside your project, "
"making it easy to browse/edit the module's classes. If disabled, then only the minimum number of files "
"required to compile it will appear inside your project.");
auto info = modules.getModuleInfo (moduleID);
if (info.isValid())
{
configFlags.clear();
LibraryModule (info).getConfigFlags (project, configFlags);
for (auto* flag : configFlags)
{
auto* c = new ChoicePropertyComponent (flag->value, flag->symbol);
c->setTooltip (flag->description);
props.add (c);
}
}
group.setProperties (props);
parentSizeChanged();
}
void parentSizeChanged() override { updateSize (*this, group); }
void resized() override { group.setBounds (getLocalBounds().withTrimmedLeft (12)); }
String getModuleID() const noexcept { return moduleID; }
private:
void valueChanged (Value& v) override
{
auto isExporterPathValue = [&]
{
for (auto& exporterValue : exporterModulePathValues)
if (exporterValue.refersToSameSourceAs (v))
return true;
return false;
}();
if (isExporterPathValue)
project.rescanExporterPathModules();
startTimer (50);
}
void timerCallback() override
{
stopTimer();
refresh();
}
//==============================================================================
Array<ValueWithDefault> exporterModulePathDefaultValues;
Array<Value> exporterModulePathValues, globalPathValues;
Value useGlobalPathValue;
OwnedArray<Project::ConfigFlag> configFlags;
PropertyGroupComponent group;
Project& project;
SafePointer<TreeView> modulesTree;
String moduleID;
//==============================================================================
class ModuleInfoComponent : public PropertyComponent,
private Value::Listener
{
public:
ModuleInfoComponent (Project& p, const String& modID)
: PropertyComponent ("Module", 150), project (p), moduleID (modID)
{
for (Project::ExporterIterator exporter (project); exporter.next();)
listeningValues.add (new Value (exporter->getPathForModuleValue (moduleID).getPropertyAsValue()))->addListener (this);
refresh();
}
void refresh() override
{
info = project.getEnabledModules().getModuleInfo (moduleID);
repaint();
}
private:
void paint (Graphics& g) override
{
auto bounds = getLocalBounds().reduced (10);
bounds.removeFromTop (5);
if (info.isValid())
{
auto topSlice = bounds.removeFromTop (bounds.getHeight() / 2);
bounds.removeFromTop (bounds.getHeight() / 6);
auto bottomSlice = bounds;
g.setColour (findColour (defaultTextColourId));
g.drawFittedText (info.getName(), topSlice.removeFromTop (topSlice.getHeight() / 4), Justification::centredLeft, 1);
g.drawFittedText ("Version: " + info.getVersion(), topSlice.removeFromTop (topSlice.getHeight() / 3), Justification::centredLeft, 1);
g.drawFittedText ("License: " + info.getLicense(), topSlice.removeFromTop (topSlice.getHeight() / 2), Justification::centredLeft, 1);
g.drawFittedText ("Location: " + info.getFolder().getParentDirectory().getFullPathName(),
topSlice.removeFromTop (topSlice.getHeight()), Justification::centredLeft, 1);
g.drawFittedText (info.getDescription(), bottomSlice, Justification::topLeft, 3, 1.0f);
}
else
{
g.setColour (Colours::red);
g.drawFittedText ("Cannot find this module at the specified path!", bounds, Justification::centred, 1);
}
}
void valueChanged (Value&) override
{
refresh();
}
Project& project;
String moduleID;
OwnedArray<Value> listeningValues;
ModuleDescription info;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModuleInfoComponent)
};
//==============================================================================
class MissingDependenciesComponent : public PropertyComponent
{
public:
MissingDependenciesComponent (Project& p, const String& modID)
: PropertyComponent ("Dependencies", 100),
project (p), moduleID (modID),
missingDependencies (project.getEnabledModules().getExtraDependenciesNeeded (modID))
{
addAndMakeVisible (fixButton);
fixButton.setColour (TextButton::buttonColourId, Colours::red);
fixButton.setColour (TextButton::textColourOffId, Colours::white);
fixButton.onClick = [this] { fixDependencies(); };
}
void refresh() override {}
void paint (Graphics& g) override
{
String text ("This module has missing dependencies!\n\n"
"To build correctly, it requires the following modules to be added:\n");
text << missingDependencies.joinIntoString (", ");
g.setColour (Colours::red);
g.drawFittedText (text, getLocalBounds().reduced (10), Justification::topLeft, 3);
}
void fixDependencies()
{
auto& enabledModules = project.getEnabledModules();
if (enabledModules.tryToFixMissingDependencies (moduleID))
{
missingDependencies.clear();
}
else
{
missingDependencies = enabledModules.getExtraDependenciesNeeded (moduleID);
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Adding Missing Dependencies",
"Couldn't locate some of these modules - you'll need to find their "
"folders manually and add them to the list.");
}
}
void resized() override
{
fixButton.setBounds (getWidth() - 168, getHeight() - 26, 160, 22);
}
private:
Project& project;
String moduleID;
StringArray missingDependencies;
TextButton fixButton { "Add Required Modules" };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MissingDependenciesComponent)
};
//==============================================================================
struct CppStandardWarningComponent : public PropertyComponent
{
CppStandardWarningComponent()
: PropertyComponent ("CppStandard", 100)
{
}
void refresh() override {}
void paint (Graphics& g) override
{
auto text = String ("This module has a higher C++ language standard requirement than your project!\n\n"
"To use this module you need to increase the C++ standard of the project.\n");
g.setColour (findColour (defaultHighlightColourId));
g.drawFittedText (text, getLocalBounds().reduced (10), Justification::topLeft, 3);
}
StringArray configsToWarnAbout;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CppStandardWarningComponent)
};
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModuleItem)
};
//==============================================================================
class EnabledModulesItem : public ProjectTreeItemBase,
private Value::Listener,
private AvailableModulesList::Listener
{
public:
EnabledModulesItem (Project& p)
: project (p),
modulesListTree (project.getEnabledModules().getState())
{
modulesListTree.addListener (this);
projectCppStandardValue.referTo (project.getProjectValue (Ids::cppLanguageStandard));
projectCppStandardValue.addListener (this);
ProjucerApplication::getApp().getJUCEPathModulesList().addListener (this);
ProjucerApplication::getApp().getUserPathsModulesList().addListener (this);
project.getExporterPathsModulesList().addListener (this);
}
~EnabledModulesItem() override
{
ProjucerApplication::getApp().getJUCEPathModulesList().removeListener (this);
ProjucerApplication::getApp().getUserPathsModulesList().removeListener (this);
project.getExporterPathsModulesList().removeListener (this);
}
int getItemHeight() const override { return 22; }
bool isModulesList() const override { return true; }
bool canBeSelected() const override { return true; }
bool mightContainSubItems() override { return true; }
String getUniqueName() const override { return "modules"; }
String getRenamingName() const override { return getDisplayName(); }
String getDisplayName() const override { return "Modules"; }
void setName (const String&) override {}
bool isMissing() const override { return false; }
Icon getIcon() const override { return Icon (getIcons().graph, getContentColour (true)); }
void showDocument() override
{
if (auto* pcc = getProjectContentComponent())
pcc->setScrollableEditorComponent (std::make_unique<ModulesInformationComponent> (project));
}
static File getModuleFolder (const File& draggedFile)
{
if (draggedFile.hasFileExtension (headerFileExtensions))
return draggedFile.getParentDirectory();
return draggedFile;
}
bool isInterestedInFileDrag (const StringArray& files) override
{
for (auto i = files.size(); --i >= 0;)
if (ModuleDescription (getModuleFolder (files[i])).isValid())
return true;
return false;
}
void filesDropped (const StringArray& files, int /*insertIndex*/) override
{
Array<ModuleDescription> modules;
for (auto f : files)
{
ModuleDescription m (getModuleFolder (f));
if (m.isValid())
modules.add (m);
}
for (int i = 0; i < modules.size(); ++i)
project.getEnabledModules().addModule (modules.getReference (i).getModuleFolder(),
project.getEnabledModules().areMostModulesCopiedLocally(),
project.getEnabledModules().areMostModulesUsingGlobalPath());
}
void addSubItems() override
{
for (int i = 0; i < project.getEnabledModules().getNumModules(); ++i)
addSubItem (new ModuleItem (project, project.getEnabledModules().getModuleID (i)));
}
void showPopupMenu (Point<int> p) override
{
auto& enabledModules = project.getEnabledModules();
PopupMenu allModules;
int index = 100;
// JUCE path
PopupMenu jucePathModules;
for (auto& mod : ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules())
jucePathModules.addItem (index++, mod.first, ! enabledModules.isModuleEnabled (mod.first));
jucePathModules.addSeparator();
jucePathModules.addItem (-1, "Re-scan path");
allModules.addSubMenu ("Global JUCE modules path", jucePathModules);
// User path
index = 200;
PopupMenu userPathModules;
for (auto& mod : ProjucerApplication::getApp().getUserPathsModulesList().getAllModules())
userPathModules.addItem (index++, mod.first, ! enabledModules.isModuleEnabled (mod.first));
userPathModules.addSeparator();
userPathModules.addItem (-2, "Re-scan path");
allModules.addSubMenu ("Global user modules path", userPathModules);
// Exporter path
index = 300;
PopupMenu exporterPathModules;
for (auto& mod : project.getExporterPathsModulesList().getAllModules())
exporterPathModules.addItem (index++, mod.first, ! enabledModules.isModuleEnabled (mod.first));
exporterPathModules.addSeparator();
exporterPathModules.addItem (-3, "Re-scan path");
allModules.addSubMenu ("Exporter paths", exporterPathModules);
PopupMenu menu;
menu.addSubMenu ("Add a module", allModules);
menu.addSeparator();
menu.addItem (1001, "Add a module from a specified folder...");
launchPopupMenu (menu, p);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode == 1001)
{
project.getEnabledModules().addModuleFromUserSelectedFile();
}
else if (resultCode < 0)
{
if (resultCode == -1) ProjucerApplication::getApp().rescanJUCEPathModules();
else if (resultCode == -2) ProjucerApplication::getApp().rescanUserPathModules();
else if (resultCode == -3) project.rescanExporterPathModules();
}
else if (resultCode > 0)
{
std::vector<AvailableModulesList::ModuleIDAndFolder> list;
int offset = -1;
if (resultCode < 200)
{
list = ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules();
offset = 100;
}
else if (resultCode < 300)
{
list = ProjucerApplication::getApp().getUserPathsModulesList().getAllModules();
offset = 200;
}
else if (resultCode < 400)
{
list = project.getExporterPathsModulesList().getAllModules();
offset = 300;
}
if (offset != -1)
project.getEnabledModules().addModuleInteractive (list[(size_t) (resultCode - offset)].first);
}
}
//==============================================================================
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 == modulesListTree)
{
auto selectedID = getSelectedItemID();
refreshSubItems();
if (selectedID.isNotEmpty())
setSelectedItem (selectedID);
}
}
private:
Project& project;
ValueTree modulesListTree;
Value projectCppStandardValue;
//==============================================================================
void valueChanged (Value& v) override
{
if (v == projectCppStandardValue)
{
for (int i = 0; i < getNumSubItems(); ++i)
{
if (auto* moduleItem = dynamic_cast<ModuleItem*> (getSubItem (i)))
{
if (moduleItem->checkCppStandard())
{
refreshSubItems();
return;
}
}
}
}
}
void removeDuplicateModules()
{
auto jucePathModulesList = ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules();
auto& userPathModules = ProjucerApplication::getApp().getUserPathsModulesList();
userPathModules.removeDuplicates (jucePathModulesList);
auto& exporterPathModules = project.getExporterPathsModulesList();
exporterPathModules.removeDuplicates (jucePathModulesList);
exporterPathModules.removeDuplicates (userPathModules.getAllModules());
}
void availableModulesChanged (AvailableModulesList*) override
{
removeDuplicateModules();
refreshSubItems();
}
String getSelectedItemID() const
{
for (int i = 0; i < getNumSubItems(); ++i)
if (auto* item = getSubItem (i))
if (item->isSelected())
return item->getUniqueName();
return {};
}
void setSelectedItem (const String& itemID)
{
for (int i = 0; i < getNumSubItems(); ++i)
{
if (auto* item = getSubItem (i))
{
if (item->getUniqueName() == itemID)
{
item->setSelected (true, true);
return;
}
}
}
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesItem)
};

View File

@ -0,0 +1,81 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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.
==============================================================================
*/
#pragma once
//==============================================================================
struct ProjectTreeItemBase : public JucerTreeViewBase,
public ValueTree::Listener
{
ProjectTreeItemBase() {}
void showSettingsPage (Component* content)
{
content->setComponentID (getUniqueName());
std::unique_ptr<Component> comp (content);
if (auto* pcc = getProjectContentComponent())
pcc->setScrollableEditorComponent (std::move (comp));
}
void closeSettingsPage()
{
if (auto* pcc = getProjectContentComponent())
if (auto* content = pcc->getEditorComponent())
if (content->getComponentID() == getUniqueName())
pcc->hideEditor();
}
void deleteAllSelectedItems() override
{
auto* tree = getOwnerView();
jassert (tree->getNumSelectedItems() <= 1); // multi-select should be disabled
if (auto* s = dynamic_cast<ProjectTreeItemBase*> (tree->getSelectedItem (0)))
s->deleteItem();
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen)
refreshSubItems();
}
virtual bool isProjectSettings() const { return false; }
virtual bool isModulesList() const { return false; }
static void updateSize (Component& comp, PropertyGroupComponent& group)
{
auto width = jmax (550, comp.getParentWidth() - 12);
auto y = 0;
y += group.updateSize (12, y, width - 12);
y = jmax (comp.getParentHeight(), y);
comp.setSize (width, y);
}
};

View File

@ -0,0 +1,560 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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.
==============================================================================
*/
#pragma once
//==============================================================================
class ConcertinaHeader : public Component,
public ChangeBroadcaster
{
public:
ConcertinaHeader (String n, Path p)
: Component (n), name (n), iconPath (p)
{
setTitle (getName());
panelIcon = Icon (iconPath, Colours::white);
nameLabel.setText (name, dontSendNotification);
nameLabel.setJustificationType (Justification::centredLeft);
nameLabel.setInterceptsMouseClicks (false, false);
nameLabel.setAccessible (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 (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 (! e.mouseWasDraggedSinceMouseDown())
sendChangeMessage();
}
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return std::make_unique<AccessibilityHandler> (*this,
AccessibilityRole::button,
AccessibilityActions().addAction (AccessibilityActionType::press,
[this] { sendChangeMessage(); }));
}
int direction = 0;
int yPosition = 0;
private:
String name;
Label nameLabel;
Path iconPath;
Icon panelIcon;
Rectangle<float> arrowBounds, iconBounds;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaHeader)
};
//==============================================================================
class FindPanel : public Component,
private Timer,
private FocusChangeListener
{
public:
FindPanel (std::function<void (const String&)> cb)
: callback (cb)
{
addAndMakeVisible (editor);
editor.onTextChange = [this] { startTimer (250); };
editor.onFocusLost = [this]
{
isFocused = false;
repaint();
};
Desktop::getInstance().addFocusChangeListener (this);
lookAndFeelChanged();
}
~FindPanel() override
{
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));
}
private:
TextEditor editor;
bool isFocused = false;
std::function<void (const String&)> callback;
//==============================================================================
void lookAndFeelChanged() override
{
editor.setTextToShowWhenEmpty ("Filter...", findColour (widgetTextColourId).withAlpha (0.3f));
}
void globalFocusChanged (Component* focusedComponent) override
{
if (focusedComponent == &editor)
{
isFocused = true;
repaint();
}
}
void timerCallback() override
{
stopTimer();
callback (editor.getText());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FindPanel)
};
//==============================================================================
class ConcertinaTreeComponent : public Component
{
public:
class AdditionalComponents
{
public:
enum Type
{
addButton = (1 << 0),
settingsButton = (1 << 1),
findPanel = (1 << 2)
};
AdditionalComponents with (Type t)
{
auto copy = *this;
copy.componentTypes |= t;
return copy;
}
bool has (Type t) const noexcept
{
return (componentTypes & t) != 0;
}
private:
int componentTypes = 0;
};
ConcertinaTreeComponent (const String& name,
TreePanelBase* tree,
AdditionalComponents additionalComponents)
: Component (name),
treeToDisplay (tree)
{
setTitle (getName());
setFocusContainerType (FocusContainerType::focusContainer);
if (additionalComponents.has (AdditionalComponents::addButton))
{
addButton = std::make_unique<IconButton> ("Add", getIcons().plus);
addAndMakeVisible (addButton.get());
addButton->onClick = [this] { showAddMenu(); };
}
if (additionalComponents.has (AdditionalComponents::settingsButton))
{
settingsButton = std::make_unique<IconButton> ("Settings", getIcons().settings);
addAndMakeVisible (settingsButton.get());
settingsButton->onClick = [this] { showSettings(); };
}
if (additionalComponents.has (AdditionalComponents::findPanel))
{
findPanel = std::make_unique<FindPanel> ([this] (const String& filter) { treeToDisplay->rootItem->setSearchFilter (filter); });
addAndMakeVisible (findPanel.get());
}
addAndMakeVisible (treeToDisplay.get());
}
void resized() override
{
auto bounds = getLocalBounds();
if (addButton != nullptr || settingsButton != nullptr || findPanel != nullptr)
{
auto bottomSlice = bounds.removeFromBottom (25);
bottomSlice.removeFromRight (3);
if (addButton != nullptr)
addButton->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(); }
private:
std::unique_ptr<TreePanelBase> treeToDisplay;
std::unique_ptr<IconButton> addButton, settingsButton;
std::unique_ptr<FindPanel> findPanel;
void showAddMenu()
{
auto numSelected = treeToDisplay->tree.getNumSelectedItems();
if (numSelected > 1)
return;
if (numSelected == 0)
{
if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
root->showPopupMenu (addButton->getScreenBounds().getCentre());
}
else
{
if (auto* item = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getSelectedItem (0)))
item->showAddMenu (addButton->getScreenBounds().getCentre());
}
}
void showSettings()
{
if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
{
treeToDisplay->tree.clearSelectedItems();
root->showDocument();
}
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaTreeComponent)
};
//==============================================================================
struct ProjectSettingsComponent : public Component,
private ChangeListener
{
ProjectSettingsComponent (Project& p)
: project (p),
group (project.getProjectFilenameRootString(),
Icon (getIcons().settings, Colours::transparentBlack))
{
setTitle ("Project Settings");
setFocusContainerType (FocusContainerType::focusContainer);
addAndMakeVisible (group);
updatePropertyList();
project.addChangeListener (this);
}
~ProjectSettingsComponent() override
{
project.removeChangeListener (this);
}
void resized() override
{
group.updateSize (12, 0, getWidth() - 24);
group.setBounds (getLocalBounds().reduced (12, 0));
}
void updatePropertyList()
{
PropertyListBuilder props;
project.createPropertyEditors (props);
group.setProperties (props);
group.setName ("Project Settings");
lastProjectType = project.getProjectTypeString();
parentSizeChanged();
}
void changeListenerCallback (ChangeBroadcaster*) override
{
if (lastProjectType != project.getProjectTypeString())
updatePropertyList();
}
void parentSizeChanged() override
{
auto width = jmax (550, getParentWidth());
auto y = group.updateSize (12, 0, width - 12);
y = jmax (getParentHeight(), y);
setSize (width, y);
}
Project& project;
var lastProjectType;
PropertyGroupComponent group;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSettingsComponent)
};
//==============================================================================
struct FileTreePanel : public TreePanelBase
{
FileTreePanel (Project& p)
: TreePanelBase (&p, "fileTreeState")
{
tree.setMultiSelectEnabled (true);
setRoot (std::make_unique<TreeItemTypes::GroupItem> (p.getMainGroup()));
tree.setRootItemVisible (false);
}
void updateMissingFileStatuses()
{
if (auto* p = dynamic_cast<TreeItemTypes::FileTreeItemBase*> (rootItem.get()))
p->checkFileStatus();
}
};
struct ModuleTreePanel : public TreePanelBase
{
ModuleTreePanel (Project& p)
: TreePanelBase (&p, "moduleTreeState")
{
tree.setMultiSelectEnabled (false);
setRoot (std::make_unique<TreeItemTypes::EnabledModulesItem> (p));
tree.setRootItemVisible (false);
}
};
struct ExportersTreePanel : public TreePanelBase
{
ExportersTreePanel (Project& p)
: TreePanelBase (&p, "exportersTreeState")
{
tree.setMultiSelectEnabled (false);
setRoot (std::make_unique<TreeItemTypes::ExportersTreeRoot> (p));
tree.setRootItemVisible (false);
}
};
//==============================================================================
class Sidebar : public Component,
private ChangeListener
{
public:
Sidebar (Project* p)
: project (p)
{
setFocusContainerType (FocusContainerType::focusContainer);
if (project != nullptr)
buildConcertina();
}
~Sidebar() override
{
TreePanelBase* panels[] = { getFileTreePanel(), getModuleTreePanel(), getExportersTreePanel() };
for (auto* panel : panels)
if (panel != nullptr)
panel->saveOpenness();
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
void resized() override
{
concertinaPanel.setBounds (getLocalBounds().withTrimmedBottom (3));
}
TreePanelBase* getTreeWithSelectedItems()
{
for (auto 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() { return getPanel<FileTreePanel> (0); }
ModuleTreePanel* getModuleTreePanel() { return getPanel<ModuleTreePanel> (1); }
ExportersTreePanel* getExportersTreePanel() { return getPanel<ExportersTreePanel> (2); }
void showPanel (int panelIndex)
{
jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
concertinaPanel.expandPanelFully (concertinaPanel.getPanel (panelIndex), true);
}
private:
//==============================================================================
template <typename PanelType>
PanelType* getPanel (int panelIndex)
{
if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (panelIndex)))
return dynamic_cast<PanelType*> (panel->getTree());
return nullptr;
}
void changeListenerCallback (ChangeBroadcaster* source) override
{
const auto pointerMatches = [source] (const std::unique_ptr<ConcertinaHeader>& header) { return header.get() == source; };
const auto it = std::find_if (headers.begin(), headers.end(), pointerMatches);
const auto index = (int) std::distance (headers.begin(), it);
if (index != (int) headers.size())
concertinaPanel.expandPanelFully (concertinaPanel.getPanel (index), true);
}
void buildConcertina()
{
for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0 ; --i)
concertinaPanel.removePanel (concertinaPanel.getPanel (i));
headers.clear();
auto addPanel = [this] (const String& name,
TreePanelBase* tree,
ConcertinaTreeComponent::AdditionalComponents components,
const Path& icon)
{
if (project != nullptr)
concertinaPanel.addPanel (-1, new ConcertinaTreeComponent (name, tree, components), true);
headers.push_back (std::make_unique<ConcertinaHeader> (name, icon));
};
using AdditionalComponents = ConcertinaTreeComponent::AdditionalComponents;
addPanel ("File Explorer", new FileTreePanel (*project),
AdditionalComponents{}
.with (AdditionalComponents::addButton)
.with (AdditionalComponents::findPanel),
getIcons().fileExplorer);
addPanel ("Modules", new ModuleTreePanel (*project),
AdditionalComponents{}
.with (AdditionalComponents::addButton)
.with (AdditionalComponents::settingsButton),
getIcons().modules);
addPanel ("Exporters", new ExportersTreePanel (*project),
AdditionalComponents{}.with (AdditionalComponents::addButton),
getIcons().exporter);
for (int i = 0; i < concertinaPanel.getNumPanels(); ++i)
{
auto* p = concertinaPanel.getPanel (i);
auto* h = headers[(size_t) i].get();
p->addMouseListener (this, true);
h->addChangeListener (this);
h->yPosition = i * 30;
concertinaPanel.setCustomPanelHeader (p, h, false);
concertinaPanel.setPanelHeaderSize (p, 30);
}
addAndMakeVisible (concertinaPanel);
}
void mouseDown (const MouseEvent& e) override
{
for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
{
if (auto* p = concertinaPanel.getPanel (i))
{
if (! (p->isParentOf (e.eventComponent)))
{
auto* base = dynamic_cast<TreePanelBase*> (p);
if (base == nullptr)
if (auto* concertina = dynamic_cast<ConcertinaTreeComponent*> (p))
base = concertina->getTree();
if (base != nullptr)
base->tree.clearSelectedItems();
}
}
}
}
//==============================================================================
ConcertinaPanel concertinaPanel;
std::vector<std::unique_ptr<ConcertinaHeader>> headers;
Project* project = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Sidebar)
};

View File

@ -0,0 +1,42 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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.
==============================================================================
*/
#pragma once
#include "../../../ProjectSaving/jucer_ProjectExporter.h"
#include "../../../Application/Windows/jucer_TranslationToolWindowComponent.h"
#include "../../../Utility/UI/jucer_JucerTreeViewBase.h"
#include "../../../Utility/Helpers/jucer_NewFileWizard.h"
#include "../jucer_ContentViewComponents.h"
#include "../jucer_FileGroupInformationComponent.h"
#include "../jucer_ModulesInformationComponent.h"
struct TreeItemTypes
{
#include "jucer_ProjectTreeItemBase.h"
#include "jucer_ExporterTreeItems.h"
#include "jucer_ModuleTreeItems.h"
#include "jucer_FileTreeItems.h"
};