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"
};

View File

@ -0,0 +1,112 @@
/*
==============================================================================
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 ContentViewComponent : public Component
{
public:
ContentViewComponent()
{
setTitle ("Content");
setFocusContainerType (Component::FocusContainerType::focusContainer);
addAndMakeVisible (logoComponent);
addAndMakeVisible (fileNameLabel);
fileNameLabel.setJustificationType (Justification::centred);
}
void resized() override
{
auto bounds = getLocalBounds();
fileNameLabel.setBounds (bounds.removeFromTop (15));
if (content != nullptr)
content->setBounds (bounds);
else
logoComponent.setBounds (bounds);
}
Component* getCurrentComponent() noexcept
{
return content.get();
}
void setContent (std::unique_ptr<Component> newContent,
const String& labelText)
{
content = std::move (newContent);
addAndMakeVisible (content.get());
fileNameLabel.setVisible (labelText.isNotEmpty());
fileNameLabel.setText (labelText, dontSendNotification);
resized();
}
private:
class LogoComponent : public Component
{
public:
void paint (Graphics& g) override
{
g.setColour (findColour (defaultTextColourId));
auto bounds = getLocalBounds();
bounds.reduce (bounds.getWidth() / 6, bounds.getHeight() / 6);
g.setFont (15.0f);
g.drawFittedText (versionInfo, bounds.removeFromBottom (50), Justification::centredBottom, 3);
if (logo != nullptr)
logo->drawWithin (g, bounds.withTrimmedBottom (bounds.getHeight() / 4).toFloat(),
RectanglePlacement (RectanglePlacement::centred), 1.0f);
}
private:
std::unique_ptr<Drawable> logo = []() -> std::unique_ptr<Drawable>
{
if (auto svg = parseXML (BinaryData::background_logo_svg))
return Drawable::createFromSVG (*svg);
jassertfalse;
return {};
}();
String versionInfo = SystemStats::getJUCEVersion()
+ newLine
+ ProjucerApplication::getApp().getVersionDescription();
};
std::unique_ptr<Component> content;
LogoComponent logoComponent;
Label fileNameLabel;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentViewComponent)
};

View File

@ -0,0 +1,446 @@
/*
==============================================================================
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 "../../Utility/UI/PropertyComponents/jucer_LabelPropertyComponent.h"
//==============================================================================
struct ContentViewHeader : public Component
{
ContentViewHeader (String headerName, Icon headerIcon)
: name (headerName), icon (headerIcon)
{
setTitle (name);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (contentHeaderBackgroundColourId));
auto bounds = getLocalBounds().reduced (20, 0);
icon.withColour (Colours::white).draw (g, bounds.toFloat().removeFromRight (30), false);
g.setColour (Colours::white);
g.setFont (Font (18.0f));
g.drawFittedText (name, bounds, Justification::centredLeft, 1);
}
String name;
Icon icon;
};
//==============================================================================
class ListBoxHeader : public Component
{
public:
ListBoxHeader (Array<String> columnHeaders)
{
for (auto s : columnHeaders)
{
addAndMakeVisible (headers.add (new Label (s, s)));
widths.add (1.0f / (float) columnHeaders.size());
}
setSize (200, 40);
}
ListBoxHeader (Array<String> columnHeaders, Array<float> columnWidths)
{
jassert (columnHeaders.size() == columnWidths.size());
auto index = 0;
for (auto s : columnHeaders)
{
addAndMakeVisible (headers.add (new Label (s, s)));
widths.add (columnWidths.getUnchecked (index++));
}
recalculateWidths();
setSize (200, 40);
}
void resized() override
{
auto bounds = getLocalBounds();
auto width = bounds.getWidth();
auto index = 0;
for (auto h : headers)
{
auto headerWidth = roundToInt ((float) width * widths.getUnchecked (index));
h->setBounds (bounds.removeFromLeft (headerWidth));
++index;
}
}
void setColumnHeaderWidth (int index, float proportionOfWidth)
{
if (! (isPositiveAndBelow (index, headers.size()) && isPositiveAndNotGreaterThan (proportionOfWidth, 1.0f)))
{
jassertfalse;
return;
}
widths.set (index, proportionOfWidth);
recalculateWidths (index);
}
int getColumnX (int index)
{
auto prop = 0.0f;
for (int i = 0; i < index; ++i)
prop += widths.getUnchecked (i);
return roundToInt (prop * (float) getWidth());
}
float getProportionAtIndex (int index)
{
jassert (isPositiveAndBelow (index, widths.size()));
return widths.getUnchecked (index);
}
private:
OwnedArray<Label> headers;
Array<float> widths;
void recalculateWidths (int indexToIgnore = -1)
{
auto total = 0.0f;
for (auto w : widths)
total += w;
if (total == 1.0f)
return;
auto diff = 1.0f - total;
auto amount = diff / static_cast<float> (indexToIgnore == -1 ? widths.size() : widths.size() - 1);
for (int i = 0; i < widths.size(); ++i)
{
if (i != indexToIgnore)
{
auto val = widths.getUnchecked (i);
widths.set (i, val + amount);
}
}
}
};
//==============================================================================
class InfoButton : public Button
{
public:
InfoButton (const String& infoToDisplay = {})
: Button ({})
{
setTitle ("Info");
if (infoToDisplay.isNotEmpty())
setInfoToDisplay (infoToDisplay);
setSize (20, 20);
}
void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
{
auto bounds = getLocalBounds().toFloat().reduced (2);
auto& icon = getIcons().info;
g.setColour (findColour (treeIconColourId).withMultipliedAlpha (isMouseOverButton || isButtonDown ? 1.0f : 0.5f));
if (isButtonDown)
g.fillEllipse (bounds);
else
g.fillPath (icon, RectanglePlacement (RectanglePlacement::centred)
.getTransformToFit (icon.getBounds(), bounds));
}
void clicked() override
{
auto w = std::make_unique<InfoWindow> (info);
w->setSize (width, w->getHeight() * numLines + 10);
CallOutBox::launchAsynchronously (std::move (w), getScreenBounds(), nullptr);
}
using Button::clicked;
void setInfoToDisplay (const String& infoToDisplay)
{
if (infoToDisplay.isNotEmpty())
{
info = infoToDisplay;
auto stringWidth = roundToInt (Font (14.0f).getStringWidthFloat (info));
width = jmin (300, stringWidth);
numLines += static_cast<int> (stringWidth / width);
setHelpText (info);
}
}
void setAssociatedComponent (Component* comp) { associatedComponent = comp; }
Component* getAssociatedComponent() { return associatedComponent; }
private:
String info;
Component* associatedComponent = nullptr;
int width;
int numLines = 1;
//==============================================================================
struct InfoWindow : public Component
{
InfoWindow (const String& s)
: stringToDisplay (s)
{
setSize (150, 14);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
g.setColour (findColour (defaultTextColourId));
g.setFont (Font (14.0f));
g.drawFittedText (stringToDisplay, getLocalBounds(), Justification::centred, 15, 0.75f);
}
String stringToDisplay;
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InfoButton)
};
//==============================================================================
class PropertyGroupComponent : public Component,
private TextPropertyComponent::Listener
{
public:
PropertyGroupComponent (String name, Icon icon, String desc = {})
: header (name, icon),
description (desc)
{
addAndMakeVisible (header);
}
void setProperties (const PropertyListBuilder& newProps)
{
clearProperties();
if (description.isNotEmpty())
properties.push_back (std::make_unique<LabelPropertyComponent> (description, 16, Font (16.0f),
Justification::centredLeft));
for (auto* comp : newProps.components)
properties.push_back (std::unique_ptr<PropertyComponent> (comp));
for (auto& prop : properties)
{
const auto propertyTooltip = prop->getTooltip();
if (propertyTooltip.isNotEmpty())
{
// set the tooltip to empty so it only displays when its button is clicked
prop->setTooltip ({});
auto infoButton = std::make_unique<InfoButton> (propertyTooltip);
infoButton->setAssociatedComponent (prop.get());
auto propertyAndInfoWrapper = std::make_unique<PropertyAndInfoWrapper> (*prop, *infoButton.get());
addAndMakeVisible (propertyAndInfoWrapper.get());
propertyComponentsWithInfo.push_back (std::move (propertyAndInfoWrapper));
infoButtons.push_back (std::move (infoButton));
}
else
{
addAndMakeVisible (prop.get());
}
if (auto* multiChoice = dynamic_cast<MultiChoicePropertyComponent*> (prop.get()))
multiChoice->onHeightChange = [this] { updateSize(); };
if (auto* text = dynamic_cast<TextPropertyComponent*> (prop.get()))
if (text->isTextEditorMultiLine())
text->addListener (this);
}
}
int updateSize (int x, int y, int width)
{
header.setBounds (0, 0, width, headerSize);
auto height = header.getBottom() + 10;
for (auto& pp : properties)
{
const auto propertyHeight = pp->getPreferredHeight()
+ (getHeightMultiplier (pp.get()) * pp->getPreferredHeight());
auto iter = std::find_if (propertyComponentsWithInfo.begin(), propertyComponentsWithInfo.end(),
[&pp] (const std::unique_ptr<PropertyAndInfoWrapper>& w) { return &w->propertyComponent == pp.get(); });
if (iter != propertyComponentsWithInfo.end())
(*iter)->setBounds (0, height, width - 10, propertyHeight);
else
pp->setBounds (40, height, width - 50, propertyHeight);
if (shouldResizePropertyComponent (pp.get()))
resizePropertyComponent (pp.get());
height += pp->getHeight() + 10;
}
height += 16;
setBounds (x, y, width, jmax (height, getParentHeight()));
return height;
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
const std::vector<std::unique_ptr<PropertyComponent>>& getProperties() const noexcept
{
return properties;
}
void clearProperties()
{
propertyComponentsWithInfo.clear();
infoButtons.clear();
properties.clear();
}
private:
//==============================================================================
struct PropertyAndInfoWrapper : public Component
{
PropertyAndInfoWrapper (PropertyComponent& c, InfoButton& i)
: propertyComponent (c),
infoButton (i)
{
setFocusContainerType (FocusContainerType::focusContainer);
setTitle (propertyComponent.getName());
addAndMakeVisible (propertyComponent);
addAndMakeVisible (infoButton);
}
void resized() override
{
auto bounds = getLocalBounds();
bounds.removeFromLeft (40);
bounds.removeFromRight (10);
propertyComponent.setBounds (bounds);
infoButton.setCentrePosition (20, bounds.getHeight() / 2);
}
PropertyComponent& propertyComponent;
InfoButton& infoButton;
};
//==============================================================================
void textPropertyComponentChanged (TextPropertyComponent* comp) override
{
auto fontHeight = [comp]
{
Label tmpLabel;
return comp->getLookAndFeel().getLabelFont (tmpLabel).getHeight();
}();
auto lines = StringArray::fromLines (comp->getText());
comp->setPreferredHeight (jmax (100, 10 + roundToInt (fontHeight * (float) lines.size())));
updateSize();
}
void updateSize()
{
updateSize (getX(), getY(), getWidth());
if (auto* parent = getParentComponent())
parent->parentSizeChanged();
}
bool shouldResizePropertyComponent (PropertyComponent* p)
{
if (auto* textComp = dynamic_cast<TextPropertyComponent*> (p))
return ! textComp->isTextEditorMultiLine();
return (dynamic_cast<ChoicePropertyComponent*> (p) != nullptr
|| dynamic_cast<ButtonPropertyComponent*> (p) != nullptr
|| dynamic_cast<BooleanPropertyComponent*> (p) != nullptr);
}
void resizePropertyComponent (PropertyComponent* pp)
{
for (auto i = pp->getNumChildComponents() - 1; i >= 0; --i)
{
auto* child = pp->getChildComponent (i);
auto bounds = child->getBounds();
child->setBounds (bounds.withSizeKeepingCentre (child->getWidth(), pp->getPreferredHeight()));
}
}
int getHeightMultiplier (PropertyComponent* pp)
{
auto availableTextWidth = ProjucerLookAndFeel::getTextWidthForPropertyComponent (pp);
auto font = ProjucerLookAndFeel::getPropertyComponentFont();
auto nameWidth = font.getStringWidthFloat (pp->getName());
if (availableTextWidth == 0)
return 0;
return static_cast<int> (nameWidth / (float) availableTextWidth);
}
//==============================================================================
static constexpr int headerSize = 40;
std::vector<std::unique_ptr<PropertyComponent>> properties;
std::vector<std::unique_ptr<InfoButton>> infoButtons;
std::vector<std::unique_ptr<PropertyAndInfoWrapper>> propertyComponentsWithInfo;
ContentViewHeader header;
String description;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyGroupComponent)
};

View File

@ -0,0 +1,363 @@
/*
==============================================================================
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 FileGroupInformationComponent : public Component,
private ListBoxModel,
private ValueTree::Listener
{
public:
FileGroupInformationComponent (const Project::Item& group)
: item (group),
header (item.getName(), { getIcons().openFolder, Colours::transparentBlack })
{
list.setHeaderComponent (std::make_unique<ListBoxHeader> (Array<String> { "File", "Binary Resource", "Xcode Resource", "Compile", "Skip PCH", "Compiler Flag Scheme" },
Array<float> { 0.25f, 0.125f, 0.125f, 0.125f, 0.125f, 0.25f }));
list.setModel (this);
list.setColour (ListBox::backgroundColourId, Colours::transparentBlack);
addAndMakeVisible (list);
list.updateContent();
list.setRowHeight (30);
item.state.addListener (this);
lookAndFeelChanged();
addAndMakeVisible (header);
}
~FileGroupInformationComponent() override
{
item.state.removeListener (this);
}
//==============================================================================
void paint (Graphics& g) override
{
g.setColour (findColour (secondaryBackgroundColourId));
g.fillRect (getLocalBounds().reduced (12, 0));
}
void resized() override
{
auto bounds = getLocalBounds().reduced (12, 0);
header.setBounds (bounds.removeFromTop (40));
list.setBounds (bounds.reduced (10, 4));
}
void parentSizeChanged() override
{
setSize (jmax (550, getParentWidth()), getParentHeight());
}
int getNumRows() override
{
return item.getNumChildren();
}
void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool /*rowIsSelected*/) override
{
g.setColour (findColour (rowNumber % 2 == 0 ? widgetBackgroundColourId
: secondaryWidgetBackgroundColourId));
g.fillRect (0, 0, width, height - 1);
}
Component* refreshComponentForRow (int rowNumber, bool /*isRowSelected*/, Component* existingComponentToUpdate) override
{
std::unique_ptr<Component> existing (existingComponentToUpdate);
if (rowNumber < getNumRows())
{
auto child = item.getChild (rowNumber);
if (existingComponentToUpdate == nullptr
|| dynamic_cast<FileOptionComponent*> (existing.get())->item != child)
{
existing.reset();
existing.reset (new FileOptionComponent (child, dynamic_cast<ListBoxHeader*> (list.getHeaderComponent())));
}
}
return existing.release();
}
String getGroupPath() const { return item.getFile().getFullPathName(); }
//==============================================================================
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { itemChanged(); }
void valueTreeChildAdded (ValueTree&, ValueTree&) override { itemChanged(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { itemChanged(); }
void valueTreeChildOrderChanged (ValueTree&, int, int) override { itemChanged(); }
void valueTreeParentChanged (ValueTree&) override { itemChanged(); }
private:
Project::Item item;
ListBox list;
ContentViewHeader header;
void itemChanged()
{
list.updateContent();
repaint();
}
//==============================================================================
class FileOptionComponent : public Component
{
public:
FileOptionComponent (const Project::Item& fileItem, ListBoxHeader* listBoxHeader)
: item (fileItem),
header (listBoxHeader),
compilerFlagSchemeSelector (item)
{
if (item.isFile())
{
auto isSourceFile = item.isSourceFile();
if (isSourceFile)
{
addAndMakeVisible (compileButton);
compileButton.getToggleStateValue().referTo (item.getShouldCompileValue());
compileButton.onStateChange = [this] { compileEnablementChanged(); };
}
addAndMakeVisible (binaryResourceButton);
binaryResourceButton.getToggleStateValue().referTo (item.getShouldAddToBinaryResourcesValue());
addAndMakeVisible (xcodeResourceButton);
xcodeResourceButton.getToggleStateValue().referTo (item.getShouldAddToXcodeResourcesValue());
if (isSourceFile)
{
addChildComponent (skipPCHButton);
skipPCHButton.getToggleStateValue().referTo (item.getShouldSkipPCHValue());
addChildComponent (compilerFlagSchemeSelector);
compileEnablementChanged();
}
}
}
void paint (Graphics& g) override
{
if (header != nullptr)
{
auto textBounds = getLocalBounds().removeFromLeft (roundToInt (header->getProportionAtIndex (0) * (float) getWidth()));
auto iconBounds = textBounds.removeFromLeft (25);
if (item.isImageFile())
iconBounds.reduce (5, 5);
item.getIcon().withColour (findColour (treeIconColourId)).draw (g, iconBounds.toFloat(), item.isIconCrossedOut());
g.setColour (findColour (widgetTextColourId));
g.drawText (item.getName(), textBounds, Justification::centredLeft);
}
}
void resized() override
{
if (header != nullptr)
{
auto bounds = getLocalBounds();
auto width = (float) getWidth();
bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (0) * width));
binaryResourceButton.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (1) * width)));
xcodeResourceButton.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (2) * width)));
compileButton.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (3) * width)));
skipPCHButton.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (4) * width)));
compilerFlagSchemeSelector.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (5) * width)));
}
}
Project::Item item;
private:
//==============================================================================
class CompilerFlagSchemeSelector : public Component,
private Value::Listener
{
public:
CompilerFlagSchemeSelector (Project::Item& it)
: item (it)
{
schemeBox.setTextWhenNothingSelected ("None");
updateCompilerFlagSchemeComboBox();
schemeBox.onChange = [this] { handleComboBoxSelection(); };
addAndMakeVisible (schemeBox);
addChildComponent (newSchemeLabel);
newSchemeLabel.setEditable (true);
newSchemeLabel.setJustificationType (Justification::centredLeft);
newSchemeLabel.onEditorHide = [this]
{
newSchemeLabel.setVisible (false);
schemeBox.setVisible (true);
auto newScheme = newSchemeLabel.getText();
item.project.addCompilerFlagScheme (newScheme);
if (item.getCompilerFlagSchemeString().isEmpty())
item.setCompilerFlagScheme (newScheme);
updateCompilerFlagSchemeComboBox();
};
selectScheme (item.getCompilerFlagSchemeString());
projectCompilerFlagSchemesValue = item.project.getProjectValue (Ids::compilerFlagSchemes);
projectCompilerFlagSchemesValue.addListener (this);
lookAndFeelChanged();
}
void resized() override
{
auto b = getLocalBounds();
schemeBox.setBounds (b);
newSchemeLabel.setBounds (b);
}
private:
void valueChanged (Value&) override { updateCompilerFlagSchemeComboBox(); }
void lookAndFeelChanged() override
{
schemeBox.setColour (ComboBox::outlineColourId, Colours::transparentBlack);
schemeBox.setColour (ComboBox::textColourId, findColour (defaultTextColourId));
}
void updateCompilerFlagSchemeComboBox()
{
auto itemScheme = item.getCompilerFlagSchemeString();
auto allSchemes = item.project.getCompilerFlagSchemes();
if (itemScheme.isNotEmpty() && ! allSchemes.contains (itemScheme))
{
item.clearCurrentCompilerFlagScheme();
itemScheme = {};
}
schemeBox.clear();
schemeBox.addItemList (allSchemes, 1);
schemeBox.addSeparator();
schemeBox.addItem ("Add a new scheme...", -1);
schemeBox.addItem ("Delete selected scheme", -2);
schemeBox.addItem ("Clear", -3);
selectScheme (itemScheme);
}
void handleComboBoxSelection()
{
auto selectedID = schemeBox.getSelectedId();
if (selectedID > 0)
{
item.setCompilerFlagScheme (schemeBox.getItemText (selectedID - 1));
}
else if (selectedID == -1)
{
newSchemeLabel.setText ("NewScheme", dontSendNotification);
schemeBox.setVisible (false);
newSchemeLabel.setVisible (true);
newSchemeLabel.showEditor();
if (auto* ed = newSchemeLabel.getCurrentTextEditor())
ed->setInputRestrictions (64, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_");
}
else if (selectedID == -2)
{
auto currentScheme = item.getCompilerFlagSchemeString();
if (currentScheme.isNotEmpty())
{
item.project.removeCompilerFlagScheme (currentScheme);
item.clearCurrentCompilerFlagScheme();
}
updateCompilerFlagSchemeComboBox();
}
else if (selectedID == -3)
{
schemeBox.setSelectedId (0);
item.clearCurrentCompilerFlagScheme();
}
}
void selectScheme (const String& schemeToSelect)
{
if (schemeToSelect.isNotEmpty())
{
for (int i = 0; i < schemeBox.getNumItems(); ++i)
{
if (schemeBox.getItemText (i) == schemeToSelect)
{
schemeBox.setSelectedItemIndex (i);
return;
}
}
}
schemeBox.setSelectedId (0);
}
Project::Item& item;
Value projectCompilerFlagSchemesValue;
ComboBox schemeBox;
Label newSchemeLabel;
};
void compileEnablementChanged()
{
auto shouldBeCompiled = compileButton.getToggleState();
skipPCHButton.setVisible (shouldBeCompiled);
compilerFlagSchemeSelector.setVisible (shouldBeCompiled);
}
//==============================================================================
ListBoxHeader* header;
ToggleButton compileButton, binaryResourceButton, xcodeResourceButton, skipPCHButton;
CompilerFlagSchemeSelector compilerFlagSchemeSelector;
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileGroupInformationComponent)
};

View File

@ -0,0 +1,279 @@
/*
==============================================================================
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.
==============================================================================
*/
#include "jucer_HeaderComponent.h"
#include "../../Application/jucer_Application.h"
#include "../../ProjectSaving/jucer_ProjectExporter.h"
#include "../../Project/UI/jucer_ProjectContentComponent.h"
//==============================================================================
HeaderComponent::HeaderComponent (ProjectContentComponent* pcc)
: projectContentComponent (pcc)
{
setTitle ("Header");
setFocusContainerType (FocusContainerType::focusContainer);
addAndMakeVisible (configLabel);
addAndMakeVisible (exporterBox);
exporterBox.onChange = [this] { updateExporterButton(); };
juceIcon.setImage (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize), RectanglePlacement::centred);
addAndMakeVisible (juceIcon);
addAndMakeVisible (userAvatar);
userAvatar.addChangeListener (this);
projectNameLabel.setText ({}, dontSendNotification);
addAndMakeVisible (projectNameLabel);
initialiseButtons();
}
//==============================================================================
void HeaderComponent::resized()
{
auto bounds = getLocalBounds();
configLabel.setFont ({ (float) bounds.getHeight() / 3.0f });
{
auto headerBounds = bounds.removeFromLeft (tabsWidth);
const int buttonSize = 25;
auto buttonBounds = headerBounds.removeFromRight (buttonSize);
projectSettingsButton.setBounds (buttonBounds.removeFromBottom (buttonSize).reduced (2));
juceIcon.setBounds (headerBounds.removeFromLeft (headerBounds.getHeight()).reduced (2));
headerBounds.removeFromRight (5);
projectNameLabel.setBounds (headerBounds);
}
{
auto exporterWidth = jmin (400, bounds.getWidth() / 2);
Rectangle<int> exporterBounds (0, 0, exporterWidth, bounds.getHeight());
exporterBounds.setCentre (bounds.getCentre());
saveAndOpenInIDEButton.setBounds (exporterBounds.removeFromRight (exporterBounds.getHeight()).reduced (2));
exporterBounds.removeFromRight (5);
exporterBox.setBounds (exporterBounds.removeFromBottom (roundToInt ((float) exporterBounds.getHeight() / 1.8f)));
configLabel.setBounds (exporterBounds);
}
userAvatar.setBounds (bounds.removeFromRight (userAvatar.isDisplaingGPLLogo() ? roundToInt ((float) bounds.getHeight() * 1.9f)
: bounds.getHeight()).reduced (2));
}
void HeaderComponent::paint (Graphics& g)
{
g.fillAll (findColour (backgroundColourId));
}
//==============================================================================
void HeaderComponent::setCurrentProject (Project* newProject)
{
stopTimer();
repaint();
projectNameLabel.setText ({}, dontSendNotification);
project = newProject;
if (project != nullptr)
{
exportersTree = project->getExporters();
exportersTree.addListener (this);
updateExporters();
projectNameValue.referTo (project->getProjectValue (Ids::name));
projectNameValue.addListener (this);
updateName();
}
}
//==============================================================================
void HeaderComponent::updateExporters()
{
auto selectedExporter = getSelectedExporter();
exporterBox.clear();
auto preferredExporterIndex = -1;
int i = 0;
for (Project::ExporterIterator exporter (*project); exporter.next(); ++i)
{
auto exporterName = exporter->getUniqueName();
exporterBox.addItem (exporterName, i + 1);
if (selectedExporter != nullptr && exporterName == selectedExporter->getUniqueName())
exporterBox.setSelectedId (i + 1);
if (exporterName.contains (ProjectExporter::getCurrentPlatformExporterTypeInfo().displayName) && preferredExporterIndex == -1)
preferredExporterIndex = i;
}
if (exporterBox.getSelectedItemIndex() == -1)
{
if (preferredExporterIndex == -1)
{
i = 0;
for (Project::ExporterIterator exporter (*project); exporter.next(); ++i)
{
if (exporter->canLaunchProject())
{
preferredExporterIndex = i;
break;
}
}
}
exporterBox.setSelectedItemIndex (preferredExporterIndex != -1 ? preferredExporterIndex : 0);
}
updateExporterButton();
}
std::unique_ptr<ProjectExporter> HeaderComponent::getSelectedExporter() const
{
if (project != nullptr)
{
int i = 0;
auto selectedIndex = exporterBox.getSelectedItemIndex();
for (Project::ExporterIterator exporter (*project); exporter.next();)
if (i++ == selectedIndex)
return std::move (exporter.exporter);
}
return nullptr;
}
bool HeaderComponent::canCurrentExporterLaunchProject() const
{
if (project != nullptr)
{
if (auto selectedExporter = getSelectedExporter())
{
for (Project::ExporterIterator exporter (*project); exporter.next();)
if (exporter->canLaunchProject() && exporter->getUniqueName() == selectedExporter->getUniqueName())
return true;
}
}
return false;
}
//==============================================================================
void HeaderComponent::sidebarTabsWidthChanged (int newWidth)
{
tabsWidth = newWidth;
resized();
}
//==============================================================================
void HeaderComponent::changeListenerCallback (ChangeBroadcaster* source)
{
if (source == &userAvatar)
resized();
}
void HeaderComponent::valueChanged (Value&)
{
updateName();
}
void HeaderComponent::timerCallback()
{
repaint();
}
//==============================================================================
void HeaderComponent::initialiseButtons()
{
addAndMakeVisible (projectSettingsButton);
projectSettingsButton.onClick = [this] { projectContentComponent->showProjectSettings(); };
addAndMakeVisible (saveAndOpenInIDEButton);
saveAndOpenInIDEButton.setBackgroundColour (Colours::white);
saveAndOpenInIDEButton.setIconInset (7);
saveAndOpenInIDEButton.onClick = [this]
{
if (project == nullptr)
return;
if (! project->isSaveAndExportDisabled())
{
projectContentComponent->openInSelectedIDE (true);
return;
}
auto setWarningVisible = [this] (const Identifier& identifier)
{
auto child = project->getProjectMessages().getChildWithName (ProjectMessages::Ids::warning)
.getChildWithName (identifier);
if (child.isValid())
child.setProperty (ProjectMessages::Ids::isVisible, true, nullptr);
};
if (project->hasIncompatibleLicenseTypeAndSplashScreenSetting())
setWarningVisible (ProjectMessages::Ids::incompatibleLicense);
if (project->isFileModificationCheckPending())
setWarningVisible (ProjectMessages::Ids::jucerFileModified);
};
updateExporterButton();
}
void HeaderComponent::updateName()
{
if (project != nullptr)
projectNameLabel.setText (project->getDocumentTitle(), dontSendNotification);
}
void HeaderComponent::updateExporterButton()
{
if (auto selectedExporter = getSelectedExporter())
{
auto selectedName = selectedExporter->getUniqueName();
for (auto info : ProjectExporter::getExporterTypeInfos())
{
if (selectedName.contains (info.displayName))
{
saveAndOpenInIDEButton.setImage (info.icon);
saveAndOpenInIDEButton.repaint();
saveAndOpenInIDEButton.setEnabled (canCurrentExporterLaunchProject());
}
}
}
}

View File

@ -0,0 +1,101 @@
/*
==============================================================================
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 "../../Application/jucer_Headers.h"
#include "../../Utility/UI/jucer_IconButton.h"
#include "jucer_UserAvatarComponent.h"
class Project;
class ProjectContentComponent;
class ProjectExporter;
//==============================================================================
class HeaderComponent : public Component,
private ValueTree::Listener,
private ChangeListener,
private Value::Listener,
private Timer
{
public:
HeaderComponent (ProjectContentComponent* projectContentComponent);
//==============================================================================
void resized() override;
void paint (Graphics&) override;
//==============================================================================
void setCurrentProject (Project*);
void updateExporters();
std::unique_ptr<ProjectExporter> getSelectedExporter() const;
bool canCurrentExporterLaunchProject() const;
void sidebarTabsWidthChanged (int newWidth);
private:
//==============================================================================
void changeListenerCallback (ChangeBroadcaster* source) override;
void valueChanged (Value&) override;
void timerCallback() override;
//==============================================================================
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { updateIfNeeded (parentTree); }
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { updateIfNeeded (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { updateIfNeeded (parentTree); }
void updateIfNeeded (ValueTree tree)
{
if (tree == exportersTree)
updateExporters();
}
//==============================================================================
void initialiseButtons();
void updateName();
void updateExporterButton();
//==============================================================================
int tabsWidth = 200;
ProjectContentComponent* projectContentComponent = nullptr;
Project* project = nullptr;
ValueTree exportersTree;
Value projectNameValue;
ComboBox exporterBox;
Label configLabel { "Config Label", "Selected exporter" }, projectNameLabel;
ImageComponent juceIcon;
UserAvatarComponent userAvatar { true };
IconButton projectSettingsButton { "Project Settings", getIcons().settings },
saveAndOpenInIDEButton { "Save and Open in IDE", Image() };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HeaderComponent)
};

View File

@ -0,0 +1,341 @@
/*
==============================================================================
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 ModulesInformationComponent : public Component,
private ListBoxModel,
private ValueTree::Listener
{
public:
ModulesInformationComponent (Project& p)
: project (p),
modulesValueTree (project.getEnabledModules().getState())
{
auto tempHeader = std::make_unique<ListBoxHeader> (Array<String> { "Module", "Version", "Make Local Copy", "Paths" },
Array<float> { 0.25f, 0.2f, 0.2f, 0.35f });
listHeader = tempHeader.get();
list.setHeaderComponent (std::move (tempHeader));
list.setModel (this);
list.setColour (ListBox::backgroundColourId, Colours::transparentBlack);
addAndMakeVisible (list);
list.updateContent();
list.setRowHeight (30);
list.setMultipleSelectionEnabled (true);
addAndMakeVisible (header);
addAndMakeVisible (setCopyModeButton);
setCopyModeButton.setTriggeredOnMouseDown (true);
setCopyModeButton.onClick = [this] { showCopyModeMenu(); };
addAndMakeVisible (copyPathButton);
copyPathButton.setTriggeredOnMouseDown (true);
copyPathButton.onClick = [this] { showSetPathsMenu(); };
addAndMakeVisible (globalPathsButton);
globalPathsButton.onClick = [this] { showGlobalPathsMenu(); };
modulesValueTree.addListener (this);
lookAndFeelChanged();
}
void paint (Graphics& g) override
{
g.setColour (findColour (secondaryBackgroundColourId));
g.fillRect (getLocalBounds().reduced (12, 0));
}
void resized() override
{
auto bounds = getLocalBounds().reduced (12, 0);
header.setBounds (bounds.removeFromTop (40));
bounds.reduce (10, 0);
list.setBounds (bounds.removeFromTop (list.getRowPosition (getNumRows() - 1, true).getBottom() + 20));
if (bounds.getHeight() < 35)
{
parentSizeChanged();
}
else
{
auto buttonRow = bounds.removeFromTop (35);
setCopyModeButton.setBounds (buttonRow.removeFromLeft (jmin (200, bounds.getWidth() / 3)));
buttonRow.removeFromLeft (8);
copyPathButton.setBounds (buttonRow.removeFromLeft (jmin (200, bounds.getWidth() / 3)));
buttonRow.removeFromLeft (8);
globalPathsButton.setBounds (buttonRow.removeFromLeft (jmin (200, bounds.getWidth() / 3)));
}
}
void parentSizeChanged() override
{
auto width = jmax (550, getParentWidth());
auto y = list.getRowPosition (getNumRows() - 1, true).getBottom() + 200;
y = jmax (getParentHeight(), y);
setSize (width, y);
}
int getNumRows() override
{
return project.getEnabledModules().getNumModules();
}
void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override
{
ignoreUnused (height);
Rectangle<int> bounds (0, 0, width, height);
g.setColour (rowIsSelected ? findColour (defaultHighlightColourId) : findColour (rowNumber % 2 == 0 ? widgetBackgroundColourId
: secondaryWidgetBackgroundColourId));
g.fillRect (bounds.withTrimmedBottom (1));
bounds.removeFromLeft (5);
g.setColour (rowIsSelected ? findColour (defaultHighlightedTextColourId) : findColour (widgetTextColourId));
//==============================================================================
auto moduleID = project.getEnabledModules().getModuleID (rowNumber);
g.drawFittedText (moduleID, bounds.removeFromLeft (roundToInt (listHeader->getProportionAtIndex (0) * (float) width)), Justification::centredLeft, 1);
//==============================================================================
auto version = project.getEnabledModules().getModuleInfo (moduleID).getVersion();
if (version.isEmpty())
version = "?";
g.drawFittedText (version, bounds.removeFromLeft (roundToInt (listHeader->getProportionAtIndex (1) * (float) width)), Justification::centredLeft, 1);
//==============================================================================
g.drawFittedText (String (project.getEnabledModules().shouldCopyModuleFilesLocally (moduleID) ? "Yes" : "No"),
bounds.removeFromLeft (roundToInt (listHeader->getProportionAtIndex (2) * (float) width)), Justification::centredLeft, 1);
//==============================================================================
String pathText;
if (project.getEnabledModules().shouldUseGlobalPath (moduleID))
{
pathText = "Global";
}
else
{
StringArray paths;
for (Project::ExporterIterator exporter (project); exporter.next();)
paths.addIfNotAlreadyThere (exporter->getPathForModuleString (moduleID).trim());
pathText = paths.joinIntoString (", ");
}
g.drawFittedText (pathText, bounds.removeFromLeft (roundToInt (listHeader->getProportionAtIndex (3) * (float) width)), Justification::centredLeft, 1);
}
void listBoxItemDoubleClicked (int row, const MouseEvent&) override
{
auto moduleID = project.getEnabledModules().getModuleID (row);
if (moduleID.isNotEmpty())
if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
pcc->showModule (moduleID);
}
void deleteKeyPressed (int row) override
{
project.getEnabledModules().removeModule (project.getEnabledModules().getModuleID (row));
}
void lookAndFeelChanged() override
{
setCopyModeButton.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
copyPathButton.setColour (TextButton::buttonColourId, findColour (defaultButtonBackgroundColourId));
globalPathsButton.setColour (TextButton::buttonColourId, findColour (defaultButtonBackgroundColourId));
}
private:
enum
{
nameCol = 1,
versionCol,
copyCol,
pathCol
};
Project& project;
ValueTree modulesValueTree;
ContentViewHeader header { "Modules", { getIcons().modules, Colours::transparentBlack } };
ListBox list;
ListBoxHeader* listHeader;
TextButton setCopyModeButton { "Set copy-mode for all modules..." };
TextButton copyPathButton { "Set paths for all modules..." };
TextButton globalPathsButton { "Enable/disable global path for modules..." };
std::map<String, var> modulePathClipboard;
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { itemChanged(); }
void valueTreeChildAdded (ValueTree&, ValueTree&) override { itemChanged(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { itemChanged(); }
void valueTreeChildOrderChanged (ValueTree&, int, int) override { itemChanged(); }
void valueTreeParentChanged (ValueTree&) override { itemChanged(); }
void itemChanged()
{
list.updateContent();
resized();
repaint();
}
static void setLocalCopyModeForAllModules (Project& project, bool copyLocally)
{
auto& modules = project.getEnabledModules();
for (auto i = modules.getNumModules(); --i >= 0;)
modules.shouldCopyModuleFilesLocallyValue (modules.getModuleID (i)) = copyLocally;
}
void showCopyModeMenu()
{
PopupMenu m;
m.addItem (PopupMenu::Item ("Set all modules to copy locally")
.setAction ([&] { setLocalCopyModeForAllModules (project, true); }));
m.addItem (PopupMenu::Item ("Set all modules to not copy locally")
.setAction ([&] { setLocalCopyModeForAllModules (project, false); }));
m.showMenuAsync (PopupMenu::Options().withTargetComponent (setCopyModeButton));
}
static void setAllModulesToUseGlobalPaths (Project& project, bool useGlobal)
{
auto& modules = project.getEnabledModules();
for (auto moduleID : modules.getAllModules())
modules.shouldUseGlobalPathValue (moduleID) = useGlobal;
}
static void setSelectedModulesToUseGlobalPaths (Project& project, SparseSet<int> selected, bool useGlobal)
{
auto& modules = project.getEnabledModules();
for (int i = 0; i < selected.size(); ++i)
modules.shouldUseGlobalPathValue (modules.getModuleID (selected[i])) = useGlobal;
}
void showGlobalPathsMenu()
{
PopupMenu m;
m.addItem (PopupMenu::Item ("Set all modules to use global paths")
.setAction ([&] { setAllModulesToUseGlobalPaths (project, true); }));
m.addItem (PopupMenu::Item ("Set all modules to not use global paths")
.setAction ([&] { setAllModulesToUseGlobalPaths (project, false); }));
m.addItem (PopupMenu::Item ("Set selected modules to use global paths")
.setEnabled (list.getNumSelectedRows() > 0)
.setAction ([&] { setSelectedModulesToUseGlobalPaths (project, list.getSelectedRows(), true); }));
m.addItem (PopupMenu::Item ("Set selected modules to not use global paths")
.setEnabled (list.getNumSelectedRows() > 0)
.setAction ([&] { setSelectedModulesToUseGlobalPaths (project, list.getSelectedRows(), false); }));
m.showMenuAsync (PopupMenu::Options().withTargetComponent (globalPathsButton));
}
void showSetPathsMenu()
{
PopupMenu m;
auto moduleToCopy = project.getEnabledModules().getModuleID (list.getSelectedRow());
if (moduleToCopy.isNotEmpty())
{
m.addItem (PopupMenu::Item ("Copy the paths from the module '" + moduleToCopy + "' to all other modules")
.setAction ([this, moduleToCopy]
{
auto& modulesList = project.getEnabledModules();
for (Project::ExporterIterator exporter (project); exporter.next();)
{
for (int i = 0; i < modulesList.getNumModules(); ++i)
{
auto modID = modulesList.getModuleID (i);
if (modID != moduleToCopy)
exporter->getPathForModuleValue (modID) = exporter->getPathForModuleValue (moduleToCopy).get();
}
}
list.repaint();
}));
m.addItem (PopupMenu::Item ("Copy paths from selected module")
.setEnabled (list.getNumSelectedRows() == 1)
.setAction ([this, moduleToCopy]
{
modulePathClipboard.clear();
for (Project::ExporterIterator exporter (project); exporter.next();)
modulePathClipboard[exporter->getUniqueName()] = exporter->getPathForModuleValue (moduleToCopy).get();
list.repaint();
}));
m.addItem (PopupMenu::Item ("Paste paths to selected modules")
.setEnabled (! modulePathClipboard.empty())
.setAction ([this]
{
for (int selectionId = 0; selectionId < list.getNumSelectedRows(); ++selectionId)
{
auto rowNumber = list.getSelectedRow (selectionId);
auto modID = project.getEnabledModules().getModuleID (rowNumber);
for (Project::ExporterIterator exporter (project); exporter.next();)
exporter->getPathForModuleValue (modID) = modulePathClipboard[exporter->getUniqueName()];
}
list.repaint();
}));
}
else
{
m.addItem (PopupMenu::Item ("(Select a module in the list above to use this option)")
.setEnabled (false));
}
m.showMenuAsync (PopupMenu::Options()
.withDeletionCheck (*this)
.withTargetComponent (copyPathButton));
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModulesInformationComponent)
};

View File

@ -0,0 +1,910 @@
/*
==============================================================================
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.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_ProjectContentComponent.h"
#include "Sidebar/jucer_Sidebar.h"
struct WizardHolder
{
std::unique_ptr<NewFileWizard::Type> wizard;
};
NewFileWizard::Type* createGUIComponentWizard (Project&);
//==============================================================================
ProjectContentComponent::ProjectContentComponent()
: sidebar (std::make_unique<Sidebar> (project))
{
setOpaque (true);
setWantsKeyboardFocus (true);
addAndMakeVisible (headerComponent);
addAndMakeVisible (projectMessagesComponent);
addAndMakeVisible (contentViewComponent);
sidebarSizeConstrainer.setMinimumWidth (200);
sidebarSizeConstrainer.setMaximumWidth (500);
ProjucerApplication::getApp().openDocumentManager.addListener (this);
getGlobalProperties().addChangeListener (this);
}
ProjectContentComponent::~ProjectContentComponent()
{
getGlobalProperties().removeChangeListener (this);
ProjucerApplication::getApp().openDocumentManager.removeListener (this);
setProject (nullptr);
removeChildComponent (&bubbleMessage);
}
void ProjectContentComponent::paint (Graphics& g)
{
g.fillAll (findColour (backgroundColourId));
}
void ProjectContentComponent::resized()
{
auto r = getLocalBounds();
r.removeFromRight (10);
r.removeFromLeft (15);
r.removeFromTop (5);
projectMessagesComponent.setBounds (r.removeFromBottom (40).withWidth (100).reduced (0, 5));
headerComponent.setBounds (r.removeFromTop (40));
r.removeFromTop (10);
auto sidebarArea = r.removeFromLeft (sidebar != nullptr && sidebar->getWidth() != 0 ? sidebar->getWidth()
: r.getWidth() / 4);
if (sidebar != nullptr && sidebar->isVisible())
sidebar->setBounds (sidebarArea);
if (resizerBar != nullptr)
resizerBar->setBounds (r.withWidth (4));
contentViewComponent.setBounds (r);
headerComponent.sidebarTabsWidthChanged (sidebarArea.getWidth());
}
void ProjectContentComponent::lookAndFeelChanged()
{
repaint();
if (translationTool != nullptr)
translationTool->repaint();
}
void ProjectContentComponent::childBoundsChanged (Component* child)
{
if (child == sidebar.get())
resized();
}
void ProjectContentComponent::setProject (Project* newProject)
{
if (project != newProject)
{
if (project != nullptr)
project->removeChangeListener (this);
hideEditor();
resizerBar = nullptr;
sidebar = nullptr;
project = newProject;
if (project != nullptr)
{
sidebar = std::make_unique<Sidebar> (project);
addAndMakeVisible (sidebar.get());
//==============================================================================
resizerBar = std::make_unique<ResizableEdgeComponent> (sidebar.get(), &sidebarSizeConstrainer,
ResizableEdgeComponent::rightEdge);
addAndMakeVisible (resizerBar.get());
resizerBar->setAlwaysOnTop (true);
project->addChangeListener (this);
updateMissingFileStatuses();
headerComponent.setVisible (true);
headerComponent.setCurrentProject (project);
projectMessagesComponent.setVisible (true);
}
else
{
headerComponent.setVisible (false);
projectMessagesComponent.setVisible (false);
}
projectMessagesComponent.setProject (project);
resized();
}
}
void ProjectContentComponent::saveOpenDocumentList()
{
if (project != nullptr)
{
std::unique_ptr<XmlElement> xml (recentDocumentList.createXML());
if (xml != nullptr)
project->getStoredProperties().setValue ("lastDocs", xml.get());
}
}
void ProjectContentComponent::reloadLastOpenDocuments()
{
if (project != nullptr)
{
if (auto xml = project->getStoredProperties().getXmlValue ("lastDocs"))
{
recentDocumentList.restoreFromXML (*project, *xml);
showDocument (recentDocumentList.getCurrentDocument(), true);
}
}
}
bool ProjectContentComponent::documentAboutToClose (OpenDocumentManager::Document* document)
{
hideDocument (document);
return true;
}
void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster* broadcaster)
{
if (broadcaster == project)
updateMissingFileStatuses();
}
void ProjectContentComponent::refreshProjectTreeFileStatuses()
{
if (sidebar != nullptr)
if (auto* fileTree = sidebar->getFileTreePanel())
fileTree->repaint();
}
void ProjectContentComponent::updateMissingFileStatuses()
{
if (sidebar != nullptr)
if (auto* tree = sidebar->getFileTreePanel())
tree->updateMissingFileStatuses();
}
bool ProjectContentComponent::showEditorForFile (const File& fileToShow, bool grabFocus)
{
if (getCurrentFile() != fileToShow)
return showDocument (ProjucerApplication::getApp().openDocumentManager.openFile (project, fileToShow), grabFocus);
return true;
}
bool ProjectContentComponent::hasFileInRecentList (const File& f) const
{
return recentDocumentList.contains (f);
}
File ProjectContentComponent::getCurrentFile() const
{
return currentDocument != nullptr ? currentDocument->getFile()
: File();
}
bool ProjectContentComponent::showDocument (OpenDocumentManager::Document* doc, bool grabFocus)
{
if (doc == nullptr)
return false;
if (doc->hasFileBeenModifiedExternally())
doc->reloadFromFile();
if (doc != getCurrentDocument())
{
recentDocumentList.newDocumentOpened (doc);
setEditorDocument (doc->createEditor(), doc);
}
if (grabFocus && contentViewComponent.isShowing())
contentViewComponent.grabKeyboardFocus();
return true;
}
void ProjectContentComponent::hideEditor()
{
currentDocument = nullptr;
contentViewComponent.setContent ({}, {});
ProjucerApplication::getCommandManager().commandStatusChanged();
resized();
}
void ProjectContentComponent::hideDocument (OpenDocumentManager::Document* doc)
{
if (doc != currentDocument)
return;
if (auto* replacement = recentDocumentList.getClosestPreviousDocOtherThan (currentDocument))
showDocument (replacement, true);
else
hideEditor();
}
void ProjectContentComponent::setScrollableEditorComponent (std::unique_ptr<Component> component)
{
jassert (component.get() != nullptr);
class ContentViewport : public Component
{
public:
ContentViewport (std::unique_ptr<Component> content)
{
contentViewport.setViewedComponent (content.release(), true);
addAndMakeVisible (contentViewport);
}
void resized() override
{
contentViewport.setBounds (getLocalBounds());
}
private:
Viewport contentViewport;
};
contentViewComponent.setContent (std::make_unique<ContentViewport> (std::move (component)), {});
currentDocument = nullptr;
ProjucerApplication::getCommandManager().commandStatusChanged();
}
void ProjectContentComponent::setEditorDocument (std::unique_ptr<Component> component, OpenDocumentManager::Document* doc)
{
currentDocument = doc;
contentViewComponent.setContent (std::move (component),
currentDocument != nullptr ? currentDocument->getFile().getFileName()
: String());
ProjucerApplication::getCommandManager().commandStatusChanged();
}
Component* ProjectContentComponent::getEditorComponent()
{
return contentViewComponent.getCurrentComponent();
}
void ProjectContentComponent::closeDocument()
{
if (currentDocument != nullptr)
{
ProjucerApplication::getApp().openDocumentManager
.closeDocumentAsync (currentDocument, OpenDocumentManager::SaveIfNeeded::yes, nullptr);
return;
}
if (! goToPreviousFile())
hideEditor();
}
static void showSaveWarning (OpenDocumentManager::Document* currentDocument)
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
TRANS("Save failed!"),
TRANS("Couldn't save the file:")
+ "\n" + currentDocument->getFile().getFullPathName());
}
void ProjectContentComponent::saveDocumentAsync()
{
if (currentDocument != nullptr)
{
currentDocument->saveAsync ([parent = SafePointer<ProjectContentComponent> { this }] (bool savedSuccessfully)
{
if (parent == nullptr)
return;
if (! savedSuccessfully)
showSaveWarning (parent->currentDocument);
parent->refreshProjectTreeFileStatuses();
});
}
else
{
saveProjectAsync();
}
}
void ProjectContentComponent::saveAsAsync()
{
if (currentDocument != nullptr)
{
currentDocument->saveAsAsync ([parent = SafePointer<ProjectContentComponent> { this }] (bool savedSuccessfully)
{
if (parent == nullptr)
return;
if (! savedSuccessfully)
showSaveWarning (parent->currentDocument);
parent->refreshProjectTreeFileStatuses();
});
}
}
bool ProjectContentComponent::goToPreviousFile()
{
auto* doc = recentDocumentList.getCurrentDocument();
if (doc == nullptr || doc == getCurrentDocument())
doc = recentDocumentList.getPrevious();
return showDocument (doc, true);
}
bool ProjectContentComponent::goToNextFile()
{
return showDocument (recentDocumentList.getNext(), true);
}
bool ProjectContentComponent::canGoToCounterpart() const
{
return currentDocument != nullptr
&& currentDocument->getCounterpartFile().exists();
}
bool ProjectContentComponent::goToCounterpart()
{
if (currentDocument != nullptr)
{
auto file = currentDocument->getCounterpartFile();
if (file.exists())
return showEditorForFile (file, true);
}
return false;
}
void ProjectContentComponent::saveProjectAsync()
{
if (project != nullptr)
project->saveAsync (true, true, nullptr);
}
void ProjectContentComponent::closeProject()
{
if (auto* mw = findParentComponentOfClass<MainWindow>())
mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes, nullptr);
}
void ProjectContentComponent::showProjectSettings()
{
setScrollableEditorComponent (std::make_unique<ProjectSettingsComponent> (*project));
}
void ProjectContentComponent::showCurrentExporterSettings()
{
if (auto selected = headerComponent.getSelectedExporter())
showExporterSettings (selected->getUniqueName());
}
void ProjectContentComponent::showExporterSettings (const String& exporterName)
{
if (exporterName.isEmpty())
return;
showExportersPanel();
if (sidebar == nullptr)
return;
if (auto* exportersPanel = sidebar->getExportersTreePanel())
{
if (auto* exporters = dynamic_cast<TreeItemTypes::ExportersTreeRoot*> (exportersPanel->rootItem.get()))
{
for (auto i = exporters->getNumSubItems(); i >= 0; --i)
{
if (auto* e = dynamic_cast<TreeItemTypes::ExporterItem*> (exporters->getSubItem (i)))
{
if (e->getDisplayName() == exporterName)
{
if (e->isSelected())
e->setSelected (false, true);
e->setSelected (true, true);
}
}
}
}
}
}
void ProjectContentComponent::showModule (const String& moduleID)
{
showModulesPanel();
if (sidebar == nullptr)
return;
if (auto* modsPanel = sidebar->getModuleTreePanel())
{
if (auto* mods = dynamic_cast<TreeItemTypes::EnabledModulesItem*> (modsPanel->rootItem.get()))
{
for (auto i = mods->getNumSubItems(); --i >= 0;)
{
if (auto* m = dynamic_cast<TreeItemTypes::ModuleItem*> (mods->getSubItem (i)))
{
if (m->moduleID == moduleID)
{
if (m->isSelected())
m->setSelected (false, true);
m->setSelected (true, true);
}
}
}
}
}
}
StringArray ProjectContentComponent::getExportersWhichCanLaunch() const
{
StringArray s;
if (project != nullptr)
for (Project::ExporterIterator exporter (*project); exporter.next();)
if (exporter->canLaunchProject())
s.add (exporter->getUniqueName());
return s;
}
void ProjectContentComponent::openInSelectedIDE (bool saveFirst)
{
if (project == nullptr)
return;
if (auto selectedExporter = headerComponent.getSelectedExporter())
{
if (saveFirst)
{
SafePointer<ProjectContentComponent> safeThis { this };
project->saveAsync (true, true, [safeThis] (Project::SaveResult r)
{
if (safeThis != nullptr && r == Project::SaveResult::savedOk)
safeThis->openInSelectedIDE (false);
});
return;
}
project->openProjectInIDE (*selectedExporter);
}
}
void ProjectContentComponent::showNewExporterMenu()
{
if (project != nullptr)
{
PopupMenu menu;
menu.addSectionHeader ("Create a new export target:");
SafePointer<ProjectContentComponent> safeThis (this);
for (auto& exporterInfo : ProjectExporter::getExporterTypeInfos())
{
PopupMenu::Item item;
item.itemID = -1;
item.text = exporterInfo.displayName;
item.image = [exporterInfo]
{
auto drawableImage = std::make_unique<DrawableImage>();
drawableImage->setImage (exporterInfo.icon);
return drawableImage;
}();
item.action = [safeThis, exporterInfo]
{
if (safeThis != nullptr)
if (auto* p = safeThis->getProject())
p->addNewExporter (exporterInfo.identifier);
};
menu.addItem (item);
}
menu.showMenuAsync ({});
}
}
void ProjectContentComponent::deleteSelectedTreeItems()
{
if (sidebar != nullptr)
if (auto* tree = sidebar->getTreeWithSelectedItems())
tree->deleteSelectedItems();
}
void ProjectContentComponent::showBubbleMessage (Rectangle<int> pos, const String& text)
{
addChildComponent (bubbleMessage);
bubbleMessage.setColour (BubbleComponent::backgroundColourId, Colours::white.withAlpha (0.7f));
bubbleMessage.setColour (BubbleComponent::outlineColourId, Colours::black.withAlpha (0.8f));
bubbleMessage.setAlwaysOnTop (true);
bubbleMessage.showAt (pos, AttributedString (text), 3000, true, false);
}
//==============================================================================
void ProjectContentComponent::showTranslationTool()
{
if (translationTool != nullptr)
{
translationTool->toFront (true);
}
else if (project != nullptr)
{
new FloatingToolWindow ("Translation File Builder",
"transToolWindowPos",
new TranslationToolComponent(),
translationTool, true,
600, 700,
600, 400, 10000, 10000);
}
}
//==============================================================================
struct AsyncCommandRetrier : public Timer
{
AsyncCommandRetrier (const ApplicationCommandTarget::InvocationInfo& i) : info (i)
{
info.originatingComponent = nullptr;
startTimer (500);
}
void timerCallback() override
{
stopTimer();
ProjucerApplication::getCommandManager().invoke (info, true);
delete this;
}
ApplicationCommandTarget::InvocationInfo info;
JUCE_DECLARE_NON_COPYABLE (AsyncCommandRetrier)
};
static bool reinvokeCommandAfterCancellingModalComps (const ApplicationCommandTarget::InvocationInfo& info)
{
if (ModalComponentManager::getInstance()->cancelAllModalComponents())
{
new AsyncCommandRetrier (info);
return true;
}
return false;
}
//==============================================================================
ApplicationCommandTarget* ProjectContentComponent::getNextCommandTarget()
{
return findFirstTargetParentComponent();
}
void ProjectContentComponent::getAllCommands (Array <CommandID>& commands)
{
commands.addArray ({ CommandIDs::saveProject,
CommandIDs::closeProject,
CommandIDs::saveDocument,
CommandIDs::saveDocumentAs,
CommandIDs::closeDocument,
CommandIDs::goToPreviousDoc,
CommandIDs::goToNextDoc,
CommandIDs::goToCounterpart,
CommandIDs::showProjectSettings,
CommandIDs::showFileExplorerPanel,
CommandIDs::showModulesPanel,
CommandIDs::showExportersPanel,
CommandIDs::showExporterSettings,
CommandIDs::openInIDE,
CommandIDs::saveAndOpenInIDE,
CommandIDs::createNewExporter,
CommandIDs::deleteSelectedItem,
CommandIDs::showTranslationTool,
CommandIDs::addNewGUIFile });
}
void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
{
String documentName;
if (currentDocument != nullptr)
documentName = " '" + currentDocument->getName().substring (0, 32) + "'";
#if JUCE_MAC
auto cmdCtrl = (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier);
#else
auto cmdCtrl = (ModifierKeys::ctrlModifier | ModifierKeys::altModifier);
#endif
switch (commandID)
{
case CommandIDs::saveProject:
result.setInfo ("Save Project",
"Saves the current project",
CommandCategories::general, 0);
result.setActive (project != nullptr && ! project->isSaveAndExportDisabled() && ! project->isCurrentlySaving());
result.defaultKeypresses.add ({ 'p', ModifierKeys::commandModifier, 0 });
break;
case CommandIDs::closeProject:
result.setInfo ("Close Project",
"Closes the current project",
CommandCategories::general, 0);
result.setActive (project != nullptr);
break;
case CommandIDs::saveDocument:
result.setInfo ("Save" + documentName,
"Saves the current document",
CommandCategories::general, 0);
result.setActive (currentDocument != nullptr || (project != nullptr && ! project->isCurrentlySaving()));
result.defaultKeypresses.add ({ 's', ModifierKeys::commandModifier, 0 });
break;
case CommandIDs::saveDocumentAs:
result.setInfo ("Save As...",
"Saves the current document to a new location",
CommandCategories::general, 0);
result.setActive (currentDocument != nullptr);
result.defaultKeypresses.add ({ 's', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0 });
break;
case CommandIDs::closeDocument:
result.setInfo ("Close" + documentName,
"Closes the current document",
CommandCategories::general, 0);
result.setActive (currentDocument != nullptr);
result.defaultKeypresses.add ({ 'w', cmdCtrl, 0 });
break;
case CommandIDs::goToPreviousDoc:
result.setInfo ("Previous Document",
"Go to previous document",
CommandCategories::general, 0);
result.setActive (recentDocumentList.canGoToPrevious());
result.defaultKeypresses.add ({ KeyPress::leftKey, cmdCtrl, 0 });
break;
case CommandIDs::goToNextDoc:
result.setInfo ("Next Document",
"Go to next document",
CommandCategories::general, 0);
result.setActive (recentDocumentList.canGoToNext());
result.defaultKeypresses.add ({ KeyPress::rightKey, cmdCtrl, 0 });
break;
case CommandIDs::goToCounterpart:
result.setInfo ("Open Counterpart File",
"Open corresponding header or cpp file",
CommandCategories::general, 0);
result.setActive (canGoToCounterpart());
result.defaultKeypresses.add ({ KeyPress::upKey, cmdCtrl, 0 });
break;
case CommandIDs::showProjectSettings:
result.setInfo ("Show Project Settings",
"Shows the main project options page",
CommandCategories::general, 0);
result.setActive (project != nullptr);
result.defaultKeypresses.add ({ 'x', cmdCtrl, 0 });
break;
case CommandIDs::showFileExplorerPanel:
result.setInfo ("Show File Explorer Panel",
"Shows the panel containing the tree of files for this project",
CommandCategories::general, 0);
result.setActive (project != nullptr);
result.defaultKeypresses.add ({ 'f', cmdCtrl, 0 });
break;
case CommandIDs::showModulesPanel:
result.setInfo ("Show Modules Panel",
"Shows the panel containing the project's list of modules",
CommandCategories::general, 0);
result.setActive (project != nullptr);
result.defaultKeypresses.add ({ 'm', cmdCtrl, 0 });
break;
case CommandIDs::showExportersPanel:
result.setInfo ("Show Exporters Panel",
"Shows the panel containing the project's list of exporters",
CommandCategories::general, 0);
result.setActive (project != nullptr);
result.defaultKeypresses.add ({ 'e', cmdCtrl, 0 });
break;
case CommandIDs::showExporterSettings:
result.setInfo ("Show Exporter Settings",
"Shows the settings page for the currently selected exporter",
CommandCategories::general, 0);
result.setActive (project != nullptr);
result.defaultKeypresses.add ({ 'e', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0 });
break;
case CommandIDs::openInIDE:
result.setInfo ("Open in IDE...",
"Launches the project in an external IDE",
CommandCategories::general, 0);
result.setActive (ProjectExporter::canProjectBeLaunched (project) && ! project->isSaveAndExportDisabled());
break;
case CommandIDs::saveAndOpenInIDE:
result.setInfo ("Save Project and Open in IDE...",
"Saves the project and launches it in an external IDE",
CommandCategories::general, 0);
result.setActive (ProjectExporter::canProjectBeLaunched (project) && ! project->isSaveAndExportDisabled() && ! project->isCurrentlySaving());
result.defaultKeypresses.add ({ 'l', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0 });
break;
case CommandIDs::createNewExporter:
result.setInfo ("Create New Exporter...",
"Creates a new exporter for a compiler type",
CommandCategories::general, 0);
result.setActive (project != nullptr);
break;
case CommandIDs::deleteSelectedItem:
result.setInfo ("Delete Selected File",
String(),
CommandCategories::general, 0);
result.defaultKeypresses.add ({ KeyPress::deleteKey, 0, 0 });
result.defaultKeypresses.add ({ KeyPress::backspaceKey, 0, 0 });
break;
case CommandIDs::showTranslationTool:
result.setInfo ("Translation File Builder",
"Shows the translation file helper tool",
CommandCategories::general, 0);
break;
case CommandIDs::addNewGUIFile:
result.setInfo ("Add new GUI Component...",
"Adds a new GUI Component file to the project",
CommandCategories::general,
(! ProjucerApplication::getApp().isGUIEditorEnabled() ? ApplicationCommandInfo::isDisabled : 0));
break;
default:
break;
}
}
bool ProjectContentComponent::perform (const InvocationInfo& info)
{
// don't allow the project to be saved again if it's currently saving
if (isSaveCommand (info.commandID) && project != nullptr && project->isCurrentlySaving())
return false;
switch (info.commandID)
{
case CommandIDs::saveProject:
case CommandIDs::closeProject:
case CommandIDs::saveDocument:
case CommandIDs::saveDocumentAs:
case CommandIDs::closeDocument:
case CommandIDs::goToPreviousDoc:
case CommandIDs::goToNextDoc:
case CommandIDs::goToCounterpart:
case CommandIDs::saveAndOpenInIDE:
if (reinvokeCommandAfterCancellingModalComps (info))
{
grabKeyboardFocus(); // to force any open labels to close their text editors
return true;
}
break;
default:
break;
}
if (isCurrentlyBlockedByAnotherModalComponent())
return false;
switch (info.commandID)
{
case CommandIDs::saveProject: saveProjectAsync(); break;
case CommandIDs::closeProject: closeProject(); break;
case CommandIDs::saveDocument: saveDocumentAsync(); break;
case CommandIDs::saveDocumentAs: saveAsAsync(); break;
case CommandIDs::closeDocument: closeDocument(); break;
case CommandIDs::goToPreviousDoc: goToPreviousFile(); break;
case CommandIDs::goToNextDoc: goToNextFile(); break;
case CommandIDs::goToCounterpart: goToCounterpart(); break;
case CommandIDs::showProjectSettings: showProjectSettings(); break;
case CommandIDs::showFileExplorerPanel: showFilesPanel(); break;
case CommandIDs::showModulesPanel: showModulesPanel(); break;
case CommandIDs::showExportersPanel: showExportersPanel(); break;
case CommandIDs::showExporterSettings: showCurrentExporterSettings(); break;
case CommandIDs::openInIDE: openInSelectedIDE (false); break;
case CommandIDs::saveAndOpenInIDE: openInSelectedIDE (true); break;
case CommandIDs::createNewExporter: showNewExporterMenu(); break;
case CommandIDs::deleteSelectedItem: deleteSelectedTreeItems(); break;
case CommandIDs::showTranslationTool: showTranslationTool(); break;
case CommandIDs::addNewGUIFile: addNewGUIFile(); break;
default:
return false;
}
return true;
}
bool ProjectContentComponent::isSaveCommand (const CommandID id)
{
return (id == CommandIDs::saveProject || id == CommandIDs::saveDocument || id == CommandIDs::saveAndOpenInIDE);
}
void ProjectContentComponent::getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
OwnedArray<Project::Item>& selectedNodes)
{
TreeItemTypes::FileTreeItemBase::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
}
void ProjectContentComponent::addNewGUIFile()
{
if (project != nullptr)
{
wizardHolder = std::make_unique<WizardHolder>();
wizardHolder->wizard.reset (createGUIComponentWizard (*project));
wizardHolder->wizard->createNewFile (*project, project->getMainGroup());
}
}
//==============================================================================
void ProjectContentComponent::showProjectPanel (const int index)
{
if (sidebar != nullptr)
sidebar->showPanel (index);
}

View File

@ -0,0 +1,153 @@
/*
==============================================================================
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 "../../CodeEditor/jucer_OpenDocumentManager.h"
#include "jucer_HeaderComponent.h"
#include "jucer_ProjectMessagesComponent.h"
#include "jucer_ContentViewComponent.h"
class Sidebar;
struct WizardHolder;
//==============================================================================
class ProjectContentComponent : public Component,
public ApplicationCommandTarget,
private ChangeListener,
private OpenDocumentManager::DocumentCloseListener
{
public:
//==============================================================================
ProjectContentComponent();
~ProjectContentComponent() override;
Project* getProject() const noexcept { return project; }
void setProject (Project*);
void saveOpenDocumentList();
void reloadLastOpenDocuments();
bool showEditorForFile (const File&, bool grabFocus);
bool hasFileInRecentList (const File&) const;
File getCurrentFile() const;
bool showDocument (OpenDocumentManager::Document*, bool grabFocus);
void hideDocument (OpenDocumentManager::Document*);
OpenDocumentManager::Document* getCurrentDocument() const { return currentDocument; }
void closeDocument();
void saveDocumentAsync();
void saveAsAsync();
void hideEditor();
void setScrollableEditorComponent (std::unique_ptr<Component> component);
void setEditorDocument (std::unique_ptr<Component> component, OpenDocumentManager::Document* doc);
Component* getEditorComponent();
Component& getSidebarComponent();
bool goToPreviousFile();
bool goToNextFile();
bool canGoToCounterpart() const;
bool goToCounterpart();
void saveProjectAsync();
void closeProject();
void openInSelectedIDE (bool saveFirst);
void showNewExporterMenu();
void showFilesPanel() { showProjectPanel (0); }
void showModulesPanel() { showProjectPanel (1); }
void showExportersPanel() { showProjectPanel (2); }
void showProjectSettings();
void showCurrentExporterSettings();
void showExporterSettings (const String& exporterName);
void showModule (const String& moduleID);
void showUserSettings();
void deleteSelectedTreeItems();
void refreshProjectTreeFileStatuses();
void updateMissingFileStatuses();
void addNewGUIFile();
void showBubbleMessage (Rectangle<int>, const String&);
StringArray getExportersWhichCanLaunch() const;
static void getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails&,
OwnedArray<Project::Item>& selectedNodes);
//==============================================================================
ApplicationCommandTarget* getNextCommandTarget() override;
void getAllCommands (Array<CommandID>&) override;
void getCommandInfo (CommandID, ApplicationCommandInfo&) override;
bool perform (const InvocationInfo&) override;
bool isSaveCommand (const CommandID id);
void paint (Graphics&) override;
void resized() override;
void childBoundsChanged (Component*) override;
void lookAndFeelChanged() override;
ProjectMessagesComponent& getProjectMessagesComponent() { return projectMessagesComponent; }
static String getProjectTabName() { return "Project"; }
private:
//==============================================================================
bool documentAboutToClose (OpenDocumentManager::Document*) override;
void changeListenerCallback (ChangeBroadcaster*) override;
void showTranslationTool();
//==============================================================================
void showProjectPanel (const int index);
bool canSelectedProjectBeLaunch();
//==============================================================================
Project* project = nullptr;
OpenDocumentManager::Document* currentDocument = nullptr;
RecentDocumentList recentDocumentList;
HeaderComponent headerComponent { this };
std::unique_ptr<Sidebar> sidebar;
ProjectMessagesComponent projectMessagesComponent;
ContentViewComponent contentViewComponent;
std::unique_ptr<ResizableEdgeComponent> resizerBar;
ComponentBoundsConstrainer sidebarSizeConstrainer;
std::unique_ptr<Component> translationTool;
BubbleMessageComponent bubbleMessage;
bool isForeground = false;
int lastViewedTab = 0;
std::unique_ptr<WizardHolder> wizardHolder;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectContentComponent)
};

View File

@ -0,0 +1,581 @@
/*
==============================================================================
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 "../../Application/jucer_CommonHeaders.h"
#include "../../Application/jucer_Application.h"
//==============================================================================
class MessagesPopupWindow : public Component,
private ComponentMovementWatcher
{
public:
MessagesPopupWindow (Component& target, Component& parent, Project& project)
: ComponentMovementWatcher (&parent),
targetComponent (target),
parentComponent (parent),
messagesListComponent (*this, project)
{
parentComponent.addAndMakeVisible (this);
setAlwaysOnTop (true);
addAndMakeVisible (viewport);
viewport.setScrollBarsShown (true, false);
viewport.setViewedComponent (&messagesListComponent, false);
viewport.setWantsKeyboardFocus (false);
setOpaque (true);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
void resized() override
{
viewport.setBounds (getLocalBounds());
}
bool isListShowing() const
{
return messagesListComponent.getRequiredHeight() > 0;
}
void updateBounds (bool animate)
{
auto targetBounds = parentComponent.getLocalArea (&targetComponent, targetComponent.getLocalBounds());
auto height = jmin (messagesListComponent.getRequiredHeight(), maxHeight);
auto yPos = jmax (indent, targetBounds.getY() - height);
Rectangle<int> bounds (targetBounds.getX(), yPos,
jmin (width, parentComponent.getWidth() - targetBounds.getX() - indent), targetBounds.getY() - yPos);
auto& animator = Desktop::getInstance().getAnimator();
if (animate)
{
setBounds (bounds.withY (targetBounds.getY()));
animator.animateComponent (this, bounds, 1.0f, 150, false, 1.0, 1.0);
}
else
{
if (animator.isAnimating (this))
animator.cancelAnimation (this, false);
setBounds (bounds);
}
messagesListComponent.resized();
}
private:
//==============================================================================
class MessagesListComponent : public Component,
private ValueTree::Listener,
private AsyncUpdater
{
public:
MessagesListComponent (MessagesPopupWindow& o, Project& currentProject)
: owner (o),
project (currentProject)
{
messagesTree = project.getProjectMessages();
messagesTree.addListener (this);
setOpaque (true);
messagesChanged();
}
void resized() override
{
auto bounds = getLocalBounds();
auto numMessages = messages.size();
for (size_t i = 0; i < numMessages; ++i)
{
messages[i]->setBounds (bounds.removeFromTop (messageHeight));
if (numMessages > 1 && i != (numMessages - 1))
bounds.removeFromTop (messageSpacing);
}
}
void paint (Graphics& g) override
{
g.fillAll (findColour (backgroundColourId).contrasting (0.2f));
}
int getRequiredHeight() const
{
auto numMessages = (int) messages.size();
if (numMessages > 0)
return (numMessages * messageHeight) + ((numMessages - 1) * messageSpacing);
return 0;
}
void updateSize (int parentWidth)
{
setSize (parentWidth, getRequiredHeight());
}
private:
static constexpr int messageHeight = 65;
static constexpr int messageSpacing = 2;
//==============================================================================
struct MessageComponent : public Component
{
MessageComponent (MessagesListComponent& listComponent,
const Identifier& messageToDisplay,
std::vector<ProjectMessages::MessageAction> messageActions)
: message (messageToDisplay)
{
for (auto& action : messageActions)
{
auto button = std::make_unique<TextButton> (action.first);
addAndMakeVisible (*button);
button->onClick = action.second;
buttons.push_back (std::move (button));
}
icon = (ProjectMessages::getTypeForMessage (message) == ProjectMessages::Ids::warning ? getIcons().warning : getIcons().info);
messageTitleLabel.setText (ProjectMessages::getTitleForMessage (message), dontSendNotification);
messageTitleLabel.setFont (Font (11.0f).boldened());
addAndMakeVisible (messageTitleLabel);
messageDescriptionLabel.setText (ProjectMessages::getDescriptionForMessage (message), dontSendNotification);
messageDescriptionLabel.setFont (Font (11.0f));
messageDescriptionLabel.setJustificationType (Justification::topLeft);
addAndMakeVisible (messageDescriptionLabel);
dismissButton.setShape (getLookAndFeel().getCrossShape (1.0f), false, true, false);
addAndMakeVisible (dismissButton);
dismissButton.onClick = [this, &listComponent]
{
listComponent.messagesTree.getChildWithName (ProjectMessages::getTypeForMessage (message))
.getChildWithName (message)
.setProperty (ProjectMessages::Ids::isVisible, false, nullptr);
};
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId).contrasting (0.1f));
auto bounds = getLocalBounds().reduced (5);
g.setColour (findColour (defaultIconColourId));
g.fillPath (icon, icon.getTransformToScaleToFit (bounds.removeFromTop (messageTitleHeight)
.removeFromLeft (messageTitleHeight).toFloat(), true));
}
void resized() override
{
auto bounds = getLocalBounds().reduced (5);
auto topSlice = bounds.removeFromTop (messageTitleHeight);
topSlice.removeFromLeft (messageTitleHeight + 5);
topSlice.removeFromRight (5);
dismissButton.setBounds (topSlice.removeFromRight (messageTitleHeight));
messageTitleLabel.setBounds (topSlice);
bounds.removeFromTop (5);
auto numButtons = (int) buttons.size();
if (numButtons > 0)
{
auto buttonBounds = bounds.removeFromBottom (buttonHeight);
auto buttonWidth = roundToInt ((float) buttonBounds.getWidth() / 3.5f);
auto requiredWidth = (numButtons * buttonWidth) + ((numButtons - 1) * buttonSpacing);
buttonBounds.reduce ((buttonBounds.getWidth() - requiredWidth) / 2, 0);
for (auto& b : buttons)
{
b->setBounds (buttonBounds.removeFromLeft (buttonWidth));
buttonBounds.removeFromLeft (buttonSpacing);
}
bounds.removeFromBottom (5);
}
messageDescriptionLabel.setBounds (bounds);
}
static constexpr int messageTitleHeight = 11;
static constexpr int buttonHeight = messageHeight / 4;
static constexpr int buttonSpacing = 5;
Identifier message;
Path icon;
Label messageTitleLabel, messageDescriptionLabel;
std::vector<std::unique_ptr<TextButton>> buttons;
ShapeButton dismissButton { {},
findColour (treeIconColourId),
findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.2f)),
findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.4f)) };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageComponent)
};
//==============================================================================
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { messagesChanged(); }
void valueTreeChildAdded (ValueTree&, ValueTree&) override { messagesChanged(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { messagesChanged(); }
void valueTreeChildOrderChanged (ValueTree&, int, int) override { messagesChanged(); }
void valueTreeParentChanged (ValueTree&) override { messagesChanged(); }
void valueTreeRedirected (ValueTree&) override { messagesChanged(); }
void handleAsyncUpdate() override
{
messagesChanged();
}
void messagesChanged()
{
auto listWasShowing = (getHeight() > 0);
auto warningsTree = messagesTree.getChildWithName (ProjectMessages::Ids::warning);
auto notificationsTree = messagesTree.getChildWithName (ProjectMessages::Ids::notification);
auto removePredicate = [warningsTree, notificationsTree] (std::unique_ptr<MessageComponent>& messageComponent)
{
for (int i = 0; i < warningsTree.getNumChildren(); ++i)
{
auto child = warningsTree.getChild (i);
if (child.getType() == messageComponent->message
&& child.getProperty (ProjectMessages::Ids::isVisible))
{
return false;
}
}
for (int i = 0; i < notificationsTree.getNumChildren(); ++i)
{
auto child = notificationsTree.getChild (i);
if (child.getType() == messageComponent->message
&& child.getProperty (ProjectMessages::Ids::isVisible))
{
return false;
}
}
return true;
};
messages.erase (std::remove_if (std::begin (messages), std::end (messages), removePredicate),
std::end (messages));
for (int i = 0; i < warningsTree.getNumChildren(); ++i)
{
auto child = warningsTree.getChild (i);
if (! child.getProperty (ProjectMessages::Ids::isVisible))
continue;
if (std::find_if (std::begin (messages), std::end (messages),
[child] (const std::unique_ptr<MessageComponent>& messageComponent) { return messageComponent->message == child.getType(); })
== std::end (messages))
{
messages.push_back (std::make_unique<MessageComponent> (*this, child.getType(), project.getMessageActions (child.getType())));
addAndMakeVisible (*messages.back());
}
}
for (int i = 0; i < notificationsTree.getNumChildren(); ++i)
{
auto child = notificationsTree.getChild (i);
if (! child.getProperty (ProjectMessages::Ids::isVisible))
continue;
if (std::find_if (std::begin (messages), std::end (messages),
[child] (const std::unique_ptr<MessageComponent>& messageComponent) { return messageComponent->message == child.getType(); })
== std::end (messages))
{
messages.push_back (std::make_unique<MessageComponent> (*this, child.getType(), project.getMessageActions (child.getType())));
addAndMakeVisible (*messages.back());
}
}
auto isNowShowing = (messages.size() > 0);
owner.updateBounds (isNowShowing != listWasShowing);
updateSize (owner.getWidth());
}
//==============================================================================
MessagesPopupWindow& owner;
Project& project;
ValueTree messagesTree;
std::vector<std::unique_ptr<MessageComponent>> messages;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessagesListComponent)
};
//==============================================================================
void componentMovedOrResized (bool, bool) override
{
if (isListShowing())
updateBounds (false);
}
using ComponentMovementWatcher::componentMovedOrResized;
void componentPeerChanged() override
{
if (isListShowing())
updateBounds (false);
}
void componentVisibilityChanged() override
{
if (isListShowing())
updateBounds (false);
}
using ComponentMovementWatcher::componentVisibilityChanged;
//==============================================================================
static constexpr int maxHeight = 500, width = 350, indent = 20;
Component& targetComponent;
Component& parentComponent;
Viewport viewport;
MessagesListComponent messagesListComponent;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessagesPopupWindow)
};
//==============================================================================
class ProjectMessagesComponent : public Component
{
public:
ProjectMessagesComponent()
{
setFocusContainerType (FocusContainerType::focusContainer);
setTitle ("Project Messages");
addAndMakeVisible (warningsComponent);
addAndMakeVisible (notificationsComponent);
warningsComponent.addMouseListener (this, true);
notificationsComponent.addMouseListener (this, true);
setOpaque (true);
}
//==============================================================================
void resized() override
{
auto b = getLocalBounds();
warningsComponent.setBounds (b.removeFromLeft (b.getWidth() / 2).reduced (5));
notificationsComponent.setBounds (b.reduced (5));
}
void paint (Graphics& g) override
{
auto backgroundColour = findColour (backgroundColourId);
if (isMouseDown || isMouseOver)
backgroundColour = backgroundColour.overlaidWith (findColour (defaultHighlightColourId)
.withAlpha (isMouseDown ? 1.0f : 0.8f));
g.fillAll (backgroundColour);
}
//==============================================================================
void mouseEnter (const MouseEvent&) override
{
isMouseOver = true;
repaint();
}
void mouseExit (const MouseEvent&) override
{
isMouseOver = false;
repaint();
}
void mouseDown (const MouseEvent&) override
{
isMouseDown = true;
repaint();
}
void mouseUp (const MouseEvent&) override
{
isMouseDown = false;
repaint();
showOrHideMessagesWindow();
}
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return std::make_unique<AccessibilityHandler> (*this,
AccessibilityRole::button,
AccessibilityActions().addAction (AccessibilityActionType::press,
[this] { showOrHideMessagesWindow(); }));
}
//==============================================================================
void setProject (Project* newProject)
{
if (currentProject != newProject)
{
currentProject = newProject;
if (currentProject != nullptr)
{
if (auto* projectWindow = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (currentProject->getFile()))
messagesWindow = std::make_unique<MessagesPopupWindow> (*this, *projectWindow, *currentProject);
auto projectMessagesTree = currentProject->getProjectMessages();
warningsComponent.setTree (projectMessagesTree.getChildWithName (ProjectMessages::Ids::warning));
notificationsComponent.setTree (projectMessagesTree.getChildWithName (ProjectMessages::Ids::notification));
}
else
{
warningsComponent.setTree ({});
notificationsComponent.setTree ({});
}
}
}
void numMessagesChanged()
{
const auto total = warningsComponent.getNumMessages()
+ notificationsComponent.getNumMessages();
setHelpText (String (total) + (total == 1 ? " message" : " messages"));
}
void showOrHideMessagesWindow()
{
if (messagesWindow != nullptr)
showOrHideAllMessages (! messagesWindow->isListShowing());
}
private:
//==============================================================================
struct MessageCountComponent : public Component,
private ValueTree::Listener
{
MessageCountComponent (ProjectMessagesComponent& o, Path pathToUse)
: owner (o),
path (pathToUse)
{
setInterceptsMouseClicks (false, false);
}
void paint (Graphics& g) override
{
auto b = getLocalBounds().toFloat();
g.setColour (findColour ((owner.isMouseDown || owner.isMouseOver) ? defaultHighlightedTextColourId : treeIconColourId));
g.fillPath (path, path.getTransformToScaleToFit (b.removeFromLeft (b.getWidth() / 2.0f), true));
b.removeFromLeft (5);
g.drawFittedText (String (numMessages), b.getSmallestIntegerContainer(), Justification::centredLeft, 1);
}
void setTree (ValueTree tree)
{
messagesTree = tree;
if (messagesTree.isValid())
messagesTree.addListener (this);
updateNumMessages();
}
void updateNumMessages()
{
numMessages = messagesTree.getNumChildren();
owner.numMessagesChanged();
repaint();
}
int getNumMessages() const noexcept { return numMessages; }
private:
void valueTreeChildAdded (ValueTree&, ValueTree&) override { updateNumMessages(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { updateNumMessages(); }
ProjectMessagesComponent& owner;
ValueTree messagesTree;
Path path;
int numMessages = 0;
};
void showOrHideAllMessages (bool shouldBeVisible)
{
if (currentProject != nullptr)
{
auto messagesTree = currentProject->getProjectMessages();
auto setVisible = [shouldBeVisible] (ValueTree subTree)
{
for (int i = 0; i < subTree.getNumChildren(); ++i)
subTree.getChild (i).setProperty (ProjectMessages::Ids::isVisible, shouldBeVisible, nullptr);
};
setVisible (messagesTree.getChildWithName (ProjectMessages::Ids::warning));
setVisible (messagesTree.getChildWithName (ProjectMessages::Ids::notification));
}
}
//==============================================================================
Project* currentProject = nullptr;
bool isMouseOver = false, isMouseDown = false;
MessageCountComponent warningsComponent { *this, getIcons().warning },
notificationsComponent { *this, getIcons().info };
std::unique_ptr<MessagesPopupWindow> messagesWindow;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectMessagesComponent)
};

View File

@ -0,0 +1,171 @@
/*
==============================================================================
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 "../../Application/jucer_Application.h"
//==============================================================================
class UserAvatarComponent : public Component,
public SettableTooltipClient,
public ChangeBroadcaster,
private LicenseController::LicenseStateListener
{
public:
UserAvatarComponent (bool isInteractive)
: interactive (isInteractive)
{
ProjucerApplication::getApp().getLicenseController().addListener (this);
lookAndFeelChanged();
}
~UserAvatarComponent() override
{
ProjucerApplication::getApp().getLicenseController().removeListener (this);
}
void paint (Graphics& g) override
{
auto bounds = getLocalBounds();
if (! isGPL)
{
bounds = bounds.removeFromRight (bounds.getHeight());
Path ellipse;
ellipse.addEllipse (bounds.toFloat());
g.reduceClipRegion (ellipse);
}
g.drawImage (currentAvatar, bounds.toFloat(), RectanglePlacement::fillDestination);
}
void mouseUp (const MouseEvent&) override
{
triggerClick();
}
void triggerClick()
{
if (interactive)
{
PopupMenu menu;
menu.addCommandItem (ProjucerApplication::getApp().commandManager.get(), CommandIDs::loginLogout);
menu.showMenuAsync (PopupMenu::Options().withTargetComponent (this));
}
}
bool isDisplaingGPLLogo() const noexcept { return isGPL; }
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return interactive ? std::make_unique<AccessibilityHandler> (*this,
AccessibilityRole::button,
AccessibilityActions().addAction (AccessibilityActionType::press,
[this] { triggerClick(); }))
: nullptr;
}
private:
//==============================================================================
static Image createGPLAvatarImage()
{
if (auto logo = Drawable::createFromImageData (BinaryData::gpl_logo_svg, BinaryData::gpl_logo_svgSize))
{
auto bounds = logo->getDrawableBounds();
Image image (Image::ARGB, roundToInt (bounds.getWidth()), roundToInt (bounds.getHeight()), true);
Graphics g (image);
logo->draw (g, 1.0f);
return image;
}
jassertfalse;
return {};
}
Image createStandardAvatarImage()
{
Image image (Image::ARGB, 250, 250, true);
Graphics g (image);
g.setColour (findColour (defaultButtonBackgroundColourId));
g.fillAll();
g.setColour (findColour (defaultIconColourId));
auto path = getIcons().user;
g.fillPath (path, RectanglePlacement (RectanglePlacement::centred)
.getTransformToFit (path.getBounds(), image.getBounds().reduced (image.getHeight() / 5).toFloat()));
return image;
}
//==============================================================================
void licenseStateChanged() override
{
auto state = ProjucerApplication::getApp().getLicenseController().getCurrentState();
isGPL = ProjucerApplication::getApp().getLicenseController().getCurrentState().isGPL();
if (interactive)
{
auto formattedUserString = [state]() -> String
{
if (state.isSignedIn())
return (state.isGPL() ? "" : (state.username + " - ")) + state.getLicenseTypeString();
return "Not logged in";
}();
setTooltip (formattedUserString);
}
currentAvatar = isGPL ? gplAvatarImage
: state.isSignedIn() ? standardAvatarImage : signedOutAvatarImage;
repaint();
sendChangeMessage();
}
void lookAndFeelChanged() override
{
standardAvatarImage = createStandardAvatarImage();
signedOutAvatarImage = createStandardAvatarImage();
if (interactive)
signedOutAvatarImage.multiplyAllAlphas (0.4f);
licenseStateChanged();
repaint();
}
//==============================================================================
Image standardAvatarImage, signedOutAvatarImage, gplAvatarImage { createGPLAvatarImage() }, currentAvatar;
bool isGPL = false, interactive = false;
};