/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ class ModuleItem : public ConfigTreeItemBase { public: ModuleItem (Project& p, const String& modID) : project (p), moduleID (modID) { } 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() override { return hasMissingDependencies(); } void showDocument() override { showSettingsPage (new ModuleSettingsPanel (project, moduleID)); } void deleteItem() override { project.getModules().removeModule (moduleID); } Icon getIcon() const override { auto iconColour = Colours::yellow; auto info = project.getModules().getModuleInfo (moduleID); if (info.isValid() && info.getVendor() == "juce") { if (info.getLicense() == "ISC") iconColour = Colours::lightblue; else if (info.getLicense() == "GPL/Commercial") iconColour = Colours::orange; } return Icon (getIcons().jigsaw, getContrastingColour (iconColour, 0.5f)); } void showPopupMenu() override { PopupMenu menu; menu.addItem (1, "Remove this module"); launchPopupMenu (menu); } void handlePopupMenuResult (int resultCode) override { if (resultCode == 1) deleteItem(); } Project& project; String moduleID; private: bool hasMissingDependencies() const { return project.getModules().getExtraDependenciesNeeded (moduleID).size() > 0; } //============================================================================== class ModuleSettingsPanel : public Component { public: ModuleSettingsPanel (Project& p, const String& modID) : project (p), moduleID (modID) { addAndMakeVisible (group); group.setName ("Module: " + moduleID); refresh(); } void refresh() { setEnabled (project.getModules().isModuleEnabled (moduleID)); PropertyListBuilder props; props.add (new ModuleInfoComponent (project, moduleID)); if (project.getModules().getExtraDependenciesNeeded (moduleID).size() > 0) props.add (new MissingDependenciesComponent (project, moduleID)); for (Project::ExporterIterator exporter (project); exporter.next();) props.add (new FilePathPropertyComponent (exporter->getPathForModuleValue (moduleID), "Path for " + exporter->getName().quoted(), true, "*", project.getProjectFolder()), "A path to the folder that contains the " + moduleID + " module when compiling the " + exporter->getName().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."); props.add (new BooleanPropertyComponent (project.getModules().shouldCopyModuleFilesLocally (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 (project.getModules().shouldShowAllModuleFilesInProject (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."); StringArray possibleValues; possibleValues.add ("(Use Default)"); possibleValues.add ("Enabled"); possibleValues.add ("Disabled"); Array mappings; mappings.add (Project::configFlagDefault); mappings.add (Project::configFlagEnabled); mappings.add (Project::configFlagDisabled); ModuleDescription info (project.getModules().getModuleInfo (moduleID)); if (info.isValid()) { OwnedArray configFlags; LibraryModule (info).getConfigFlags (project, configFlags); for (int i = 0; i < configFlags.size(); ++i) { ChoicePropertyComponent* c = new ChoicePropertyComponent (configFlags[i]->value, configFlags[i]->symbol, possibleValues, mappings); c->setTooltip (configFlags[i]->description); props.add (c); } } group.setProperties (props); parentSizeChanged(); } void parentSizeChanged() override { updateSize (*this, group); } private: PropertyGroupComponent group; Project& project; 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))) ->addListener (this); refresh(); } private: void refresh() override { info = project.getModules().getModuleInfo (moduleID); repaint(); } void paint (Graphics& g) override { g.setColour (Colours::white.withAlpha (0.4f)); g.fillRect (getLocalBounds().withTrimmedBottom (1)); AttributedString s; s.setJustification (Justification::topLeft); Font f (14.0f); if (info.isValid()) { s.append (info.getName() + "\n\n", f.boldened()); s.append ("Version: " + info.getVersion() + "\nLicense: " + info.getLicense() + "\n", f.italicised()); s.append ("\n" + info.getDescription(), f); } else { s.append ("Cannot find this module at the specified path!", f.boldened()); s.setColour (Colours::darkred); } s.draw (g, getLocalBounds().reduced (6, 5).toFloat()); } void valueChanged (Value&) override { refresh(); } Project& project; String moduleID; OwnedArray listeningValues; ModuleDescription info; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModuleInfoComponent) }; //============================================================================== class MissingDependenciesComponent : public PropertyComponent, public ButtonListener { public: MissingDependenciesComponent (Project& p, const String& modID) : PropertyComponent ("Dependencies", 100), project (p), moduleID (modID), missingDependencies (project.getModules().getExtraDependenciesNeeded (modID)), fixButton ("Add Required Modules") { addAndMakeVisible (fixButton); fixButton.setColour (TextButton::buttonColourId, Colours::red); fixButton.setColour (TextButton::textColourOffId, Colours::white); fixButton.addListener (this); } void refresh() override {} void paint (Graphics& g) override { g.setColour (Colours::white.withAlpha (0.4f)); g.fillRect (0, 0, getWidth(), getHeight() - 1); String text ("This module has missing dependencies!\n\n" "To build correctly, it requires the following modules to be added:\n"); text << missingDependencies.joinIntoString (", "); AttributedString s; s.setJustification (Justification::topLeft); s.append (text, Font (13.0f), Colours::red.darker()); s.draw (g, getLocalBounds().reduced (4, 16).toFloat()); } void buttonClicked (Button*) override { bool anyFailed = false; ModuleList list; list.scanAllKnownFolders (project); for (int i = missingDependencies.size(); --i >= 0;) { if (const ModuleDescription* info = list.getModuleWithID (missingDependencies[i])) project.getModules().addModule (info->moduleFolder, project.getModules().areMostModulesCopiedLocally()); else anyFailed = true; } if (ModuleSettingsPanel* p = findParentComponentOfClass()) p->refresh(); if (anyFailed) AlertWindow::showMessageBoxAsync (AlertWindow::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; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MissingDependenciesComponent) }; }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModuleItem) }; //============================================================================== class EnabledModulesItem : public ConfigTreeItemBase { public: EnabledModulesItem (Project& p) : project (p), moduleListTree (p.getModules().state) { moduleListTree.addListener (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() override { return false; } Icon getIcon() const override { return Icon (getIcons().graph, getContrastingColour (Colours::orange, 0.5f)); } void showDocument() override { if (ProjectContentComponent* pcc = getProjectContentComponent()) pcc->setEditorComponent (new ModulesPanel (project), nullptr); } static File getModuleFolder (const File& draggedFile) { if (draggedFile.hasFileExtension (headerFileExtensions)) return draggedFile.getParentDirectory(); return draggedFile; } bool isInterestedInFileDrag (const StringArray& files) override { for (int i = files.size(); --i >= 0;) if (ModuleDescription (getModuleFolder (files[i])).isValid()) return true; return false; } void filesDropped (const StringArray& files, int /*insertIndex*/) override { Array modules; for (int i = files.size(); --i >= 0;) { ModuleDescription m (getModuleFolder (files[i])); if (m.isValid()) modules.add (m); } for (int i = 0; i < modules.size(); ++i) project.getModules().addModule (modules.getReference(i).moduleFolder, project.getModules().areMostModulesCopiedLocally()); } void addSubItems() override { for (int i = 0; i < project.getModules().getNumModules(); ++i) addSubItem (new ModuleItem (project, project.getModules().getModuleID (i))); } void showPopupMenu() override { PopupMenu menu, knownModules, copyModeMenu; const StringArray modules (getAvailableModules()); for (int i = 0; i < modules.size(); ++i) knownModules.addItem (1 + i, modules[i], ! project.getModules().isModuleEnabled (modules[i])); menu.addSubMenu ("Add a module", knownModules); menu.addSeparator(); menu.addItem (1001, "Add a module from a specified folder..."); launchPopupMenu (menu); } void handlePopupMenuResult (int resultCode) override { if (resultCode == 1001) project.getModules().addModuleFromUserSelectedFile(); else if (resultCode > 0) project.getModules().addModuleInteractive (getAvailableModules() [resultCode - 1]); } StringArray getAvailableModules() { ModuleList list; list.scanAllKnownFolders (project); return list.getIDs(); } //============================================================================== 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 == moduleListTree) refreshSubItems(); } private: Project& project; ValueTree moduleListTree; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesItem) };