- clean working copy for leaving SVN

This commit is contained in:
2022-06-28 19:04:46 +02:00
parent 62a2d47cb3
commit 762b8f0e7d
1966 changed files with 699872 additions and 126 deletions
@@ -0,0 +1,671 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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.
==============================================================================
*/
#ifndef __JUCER_AUDIOPLUGINMODULE_JUCEHEADER__
#define __JUCER_AUDIOPLUGINMODULE_JUCEHEADER__
//==============================================================================
namespace
{
Value shouldBuildVST (Project& project) { return project.getProjectValue ("buildVST"); }
Value shouldBuildVST3 (Project& project) { return project.getProjectValue ("buildVST3"); }
Value shouldBuildAU (Project& project) { return project.getProjectValue ("buildAU"); }
Value shouldBuildRTAS (Project& project) { return project.getProjectValue ("buildRTAS"); }
Value shouldBuildAAX (Project& project) { return project.getProjectValue ("buildAAX"); }
Value getPluginName (Project& project) { return project.getProjectValue ("pluginName"); }
Value getPluginDesc (Project& project) { return project.getProjectValue ("pluginDesc"); }
Value getPluginManufacturer (Project& project) { return project.getProjectValue ("pluginManufacturer"); }
Value getPluginManufacturerCode (Project& project) { return project.getProjectValue ("pluginManufacturerCode"); }
Value getPluginCode (Project& project) { return project.getProjectValue ("pluginCode"); }
Value getPluginChannelConfigs (Project& project) { return project.getProjectValue ("pluginChannelConfigs"); }
Value getPluginIsSynth (Project& project) { return project.getProjectValue ("pluginIsSynth"); }
Value getPluginWantsMidiInput (Project& project) { return project.getProjectValue ("pluginWantsMidiIn"); }
Value getPluginProducesMidiOut (Project& project) { return project.getProjectValue ("pluginProducesMidiOut"); }
Value getPluginSilenceInProducesSilenceOut (Project& project) { return project.getProjectValue ("pluginSilenceInIsSilenceOut"); }
Value getPluginEditorNeedsKeyFocus (Project& project) { return project.getProjectValue ("pluginEditorRequiresKeys"); }
Value getPluginVSTCategory (Project& project) { return project.getProjectValue ("pluginVSTCategory"); }
Value getPluginAUExportPrefix (Project& project) { return project.getProjectValue ("pluginAUExportPrefix"); }
Value getPluginAUMainType (Project& project) { return project.getProjectValue ("pluginAUMainType"); }
Value getPluginRTASCategory (Project& project) { return project.getProjectValue ("pluginRTASCategory"); }
Value getPluginRTASBypassDisabled (Project& project) { return project.getProjectValue ("pluginRTASDisableBypass"); }
Value getPluginRTASMultiMonoDisabled (Project& project) { return project.getProjectValue ("pluginRTASDisableMultiMono"); }
Value getPluginAAXCategory (Project& project) { return project.getProjectValue ("pluginAAXCategory"); }
Value getPluginAAXBypassDisabled (Project& project) { return project.getProjectValue ("pluginAAXDisableBypass"); }
Value getPluginAAXMultiMonoDisabled (Project& project) { return project.getProjectValue ("pluginAAXDisableMultiMono"); }
String getPluginRTASCategoryCode (Project& project)
{
if (static_cast <bool> (getPluginIsSynth (project).getValue()))
return "ePlugInCategory_SWGenerators";
String s (getPluginRTASCategory (project).toString());
if (s.isEmpty())
s = "ePlugInCategory_None";
return s;
}
String getAUMainTypeString (Project& project)
{
String s (getPluginAUMainType (project).toString());
if (s.isEmpty())
{
if (getPluginIsSynth (project).getValue()) s = "kAudioUnitType_MusicDevice";
else if (getPluginWantsMidiInput (project).getValue()) s = "kAudioUnitType_MusicEffect";
else s = "kAudioUnitType_Effect";
}
return s;
}
String getAUMainTypeCode (Project& project)
{
String s (getPluginAUMainType (project).toString());
if (s.isEmpty())
{
if (getPluginIsSynth (project).getValue()) s = "aumu";
else if (getPluginWantsMidiInput (project).getValue()) s = "aumf";
else s = "aufx";
}
return s;
}
String getPluginVSTCategoryString (Project& project)
{
String s (getPluginVSTCategory (project).toString().trim());
if (s.isEmpty())
s = static_cast<bool> (getPluginIsSynth (project).getValue()) ? "kPlugCategSynth"
: "kPlugCategEffect";
return s;
}
int countMaxPluginChannels (const String& configString, bool isInput)
{
StringArray configs;
configs.addTokens (configString, ", {}", StringRef());
configs.trim();
configs.removeEmptyStrings();
jassert ((configs.size() & 1) == 0); // looks like a syntax error in the configs?
int maxVal = 0;
for (int i = (isInput ? 0 : 1); i < configs.size(); i += 2)
maxVal = jmax (maxVal, configs[i].getIntValue());
return maxVal;
}
String valueToBool (const Value& v)
{
return static_cast<bool> (v.getValue()) ? "1" : "0";
}
String valueToStringLiteral (const var& v)
{
return CppTokeniserFunctions::addEscapeChars (v.toString()).quoted();
}
String valueToCharLiteral (const var& v)
{
return CppTokeniserFunctions::addEscapeChars (v.toString().trim().substring (0, 4)).quoted ('\'');
}
void writePluginCharacteristicsFile (ProjectSaver& projectSaver)
{
Project& project = projectSaver.project;
StringPairArray flags;
//flags.set ("JUCE_MODAL_LOOPS_PERMITTED", "0");
flags.set ("JucePlugin_Build_VST", valueToBool (shouldBuildVST (project)));
flags.set ("JucePlugin_Build_VST3", valueToBool (shouldBuildVST3 (project)));
flags.set ("JucePlugin_Build_AU", valueToBool (shouldBuildAU (project)));
flags.set ("JucePlugin_Build_RTAS", valueToBool (shouldBuildRTAS (project)));
flags.set ("JucePlugin_Build_AAX", valueToBool (shouldBuildAAX (project)));
flags.set ("JucePlugin_Name", valueToStringLiteral (getPluginName (project)));
flags.set ("JucePlugin_Desc", valueToStringLiteral (getPluginDesc (project)));
flags.set ("JucePlugin_Manufacturer", valueToStringLiteral (getPluginManufacturer (project)));
flags.set ("JucePlugin_ManufacturerWebsite", valueToStringLiteral (project.getCompanyWebsite()));
flags.set ("JucePlugin_ManufacturerEmail", valueToStringLiteral (project.getCompanyEmail()));
flags.set ("JucePlugin_ManufacturerCode", valueToCharLiteral (getPluginManufacturerCode (project)));
flags.set ("JucePlugin_PluginCode", valueToCharLiteral (getPluginCode (project)));
flags.set ("JucePlugin_MaxNumInputChannels", String (countMaxPluginChannels (getPluginChannelConfigs (project).toString(), true)));
flags.set ("JucePlugin_MaxNumOutputChannels", String (countMaxPluginChannels (getPluginChannelConfigs (project).toString(), false)));
flags.set ("JucePlugin_PreferredChannelConfigurations", getPluginChannelConfigs (project).toString());
flags.set ("JucePlugin_IsSynth", valueToBool (getPluginIsSynth (project)));
flags.set ("JucePlugin_WantsMidiInput", valueToBool (getPluginWantsMidiInput (project)));
flags.set ("JucePlugin_ProducesMidiOutput", valueToBool (getPluginProducesMidiOut (project)));
flags.set ("JucePlugin_SilenceInProducesSilenceOut", valueToBool (getPluginSilenceInProducesSilenceOut (project)));
flags.set ("JucePlugin_EditorRequiresKeyboardFocus", valueToBool (getPluginEditorNeedsKeyFocus (project)));
flags.set ("JucePlugin_Version", project.getVersionString());
flags.set ("JucePlugin_VersionCode", project.getVersionAsHex());
flags.set ("JucePlugin_VersionString", valueToStringLiteral (project.getVersionString()));
flags.set ("JucePlugin_VSTUniqueID", "JucePlugin_PluginCode");
flags.set ("JucePlugin_VSTCategory", getPluginVSTCategoryString (project));
flags.set ("JucePlugin_AUMainType", getAUMainTypeString (project));
flags.set ("JucePlugin_AUSubType", "JucePlugin_PluginCode");
flags.set ("JucePlugin_AUExportPrefix", getPluginAUExportPrefix (project).toString());
flags.set ("JucePlugin_AUExportPrefixQuoted", valueToStringLiteral (getPluginAUExportPrefix (project)));
flags.set ("JucePlugin_AUManufacturerCode", "JucePlugin_ManufacturerCode");
flags.set ("JucePlugin_CFBundleIdentifier", project.getBundleIdentifier().toString());
flags.set ("JucePlugin_RTASCategory", getPluginRTASCategoryCode (project));
flags.set ("JucePlugin_RTASManufacturerCode", "JucePlugin_ManufacturerCode");
flags.set ("JucePlugin_RTASProductId", "JucePlugin_PluginCode");
flags.set ("JucePlugin_RTASDisableBypass", valueToBool (getPluginRTASBypassDisabled (project)));
flags.set ("JucePlugin_RTASDisableMultiMono", valueToBool (getPluginRTASMultiMonoDisabled (project)));
flags.set ("JucePlugin_AAXIdentifier", project.getAAXIdentifier().toString());
flags.set ("JucePlugin_AAXManufacturerCode", "JucePlugin_ManufacturerCode");
flags.set ("JucePlugin_AAXProductId", "JucePlugin_PluginCode");
flags.set ("JucePlugin_AAXCategory", getPluginAAXCategory (project).toString());
flags.set ("JucePlugin_AAXDisableBypass", valueToBool (getPluginAAXBypassDisabled (project)));
flags.set ("JucePlugin_AAXDisableMultiMono", valueToBool (getPluginAAXMultiMonoDisabled (project)));
MemoryOutputStream mem;
mem << "//==============================================================================" << newLine
<< "// Audio plugin settings.." << newLine
<< newLine;
for (int i = 0; i < flags.size(); ++i)
{
mem << "#ifndef " << flags.getAllKeys()[i] << newLine
<< " #define " << flags.getAllKeys()[i].paddedRight (' ', 32) << " "
<< flags.getAllValues()[i] << newLine
<< "#endif" << newLine;
}
projectSaver.setExtraAppConfigFileContent (mem.toString());
}
static void fixMissingXcodePostBuildScript (ProjectExporter& exporter)
{
if (exporter.isXcode() && exporter.settings [Ids::postbuildCommand].toString().isEmpty())
exporter.getSetting (Ids::postbuildCommand) = String::fromUTF8 (BinaryData::AudioPluginXCodeScript_txt,
BinaryData::AudioPluginXCodeScript_txtSize);
}
String createEscapedStringForVersion (ProjectExporter& exporter, const String& text)
{
// (VS10 automatically adds escape characters to the quotes for this definition)
return exporter.getVisualStudioVersion() < 10 ? CppTokeniserFunctions::addEscapeChars (text.quoted())
: CppTokeniserFunctions::addEscapeChars (text).quoted();
}
String createRebasedPath (ProjectExporter& exporter, const RelativePath& path)
{
return createEscapedStringForVersion (exporter,
exporter.rebaseFromProjectFolderToBuildTarget (path)
.toWindowsStyle());
}
}
//==============================================================================
namespace VSTHelpers
{
static Value getVSTFolder (ProjectExporter& exporter, bool isVST3)
{
return exporter.getSetting (isVST3 ? Ids::vst3Folder
: Ids::vstFolder);
}
static void addVSTFolderToPath (ProjectExporter& exporter, bool isVST3)
{
const String vstFolder (getVSTFolder (exporter, isVST3).toString());
if (vstFolder.isNotEmpty())
{
RelativePath path (exporter.rebaseFromProjectFolderToBuildTarget (RelativePath (vstFolder, RelativePath::projectFolder)));
if (exporter.isVisualStudio())
exporter.extraSearchPaths.add (path.toWindowsStyle());
else if (exporter.isLinux() || exporter.isXcode())
exporter.extraSearchPaths.insert (0, path.toUnixStyle());
}
}
static void createVSTPathEditor (ProjectExporter& exporter, PropertyListBuilder& props, bool isVST3)
{
const String vstFormat (isVST3 ? "VST3" : "VST");
props.add (new TextPropertyComponent (getVSTFolder (exporter, isVST3), vstFormat + " Folder", 1024, false),
"If you're building a " + vstFormat + ", this must be the folder containing the " + vstFormat + " SDK. This should be an absolute path.");
}
static void fixMissingVSTValues (ProjectExporter& exporter, bool isVST3)
{
if (getVSTFolder (exporter, isVST3).toString().isEmpty())
getVSTFolder (exporter, isVST3) = exporter.isWindows() ? (isVST3 ? "c:\\SDKs\\VST3 SDK" : "c:\\SDKs\\vstsdk2.4")
: (isVST3 ? "~/SDKs/VST3 SDK" : "~/SDKs/vstsdk2.4");
fixMissingXcodePostBuildScript (exporter);
}
static inline void prepareExporter (ProjectExporter& exporter, ProjectSaver& projectSaver, bool isVST3)
{
fixMissingVSTValues (exporter, isVST3);
writePluginCharacteristicsFile (projectSaver);
exporter.makefileTargetSuffix = ".so";
Project::Item group (Project::Item::createGroup (const_cast<ProjectExporter&> (exporter).getProject(),
"Juce VST Wrapper", "__jucevstfiles"));
RelativePath juceWrapperFolder (exporter.getProject().getGeneratedCodeFolder(),
exporter.getTargetFolder(), RelativePath::buildTargetFolder);
addVSTFolderToPath (exporter, isVST3);
if (exporter.isWindows())
exporter.extraSearchPaths.add (juceWrapperFolder.toWindowsStyle());
else if (exporter.isLinux())
exporter.extraSearchPaths.add (juceWrapperFolder.toUnixStyle());
if (exporter.isVisualStudio())
{
if (! exporter.getExtraLinkerFlagsString().contains ("/FORCE:multiple"))
exporter.getExtraLinkerFlags() = exporter.getExtraLinkerFlags().toString() + " /FORCE:multiple";
RelativePath modulePath (exporter.rebaseFromProjectFolderToBuildTarget (RelativePath (exporter.getPathForModuleString ("juce_audio_plugin_client"),
RelativePath::projectFolder)
.getChildFile ("juce_audio_plugin_client")
.getChildFile ("VST3")));
for (ProjectExporter::ConfigIterator config (exporter); config.next();)
{
if (config->getValue (Ids::useRuntimeLibDLL).getValue().isVoid())
config->getValue (Ids::useRuntimeLibDLL) = true;
if (isVST3)
if (config->getValue (Ids::postbuildCommand).toString().isEmpty())
config->getValue (Ids::postbuildCommand) = "copy /Y \"$(OutDir)\\$(TargetFileName)\" \"$(OutDir)\\$(TargetName).vst3\"";
}
}
}
static inline void createPropertyEditors (ProjectExporter& exporter, PropertyListBuilder& props, bool isVST3)
{
fixMissingVSTValues (exporter, isVST3);
createVSTPathEditor (exporter, props, isVST3);
}
}
//==============================================================================
namespace RTASHelpers
{
static Value getRTASFolder (ProjectExporter& exporter) { return exporter.getSetting (Ids::rtasFolder); }
static RelativePath getRTASFolderPath (ProjectExporter& exporter) { return RelativePath (exporter.getSettingString (Ids::rtasFolder),
RelativePath::projectFolder); }
static bool isExporterSupported (ProjectExporter& exporter) { return exporter.isVisualStudio() || exporter.isXcode(); }
static void fixMissingRTASValues (ProjectExporter& exporter)
{
if (getRTASFolder (exporter).toString().isEmpty())
{
if (exporter.isVisualStudio())
getRTASFolder (exporter) = "c:\\SDKs\\PT_80_SDK";
else
getRTASFolder (exporter) = "~/SDKs/PT_80_SDK";
}
fixMissingXcodePostBuildScript (exporter);
}
static void addExtraSearchPaths (ProjectExporter& exporter)
{
RelativePath rtasFolder (getRTASFolderPath (exporter));
if (exporter.isVisualStudio())
{
RelativePath juceWrapperFolder (exporter.getProject().getGeneratedCodeFolder(),
exporter.getTargetFolder(), RelativePath::buildTargetFolder);
exporter.extraSearchPaths.add (juceWrapperFolder.toWindowsStyle());
static const char* p[] = { "AlturaPorts/TDMPlugins/PluginLibrary/EffectClasses",
"AlturaPorts/TDMPlugins/PluginLibrary/ProcessClasses",
"AlturaPorts/TDMPlugins/PluginLibrary/ProcessClasses/Interfaces",
"AlturaPorts/TDMPlugins/PluginLibrary/Utilities",
"AlturaPorts/TDMPlugins/PluginLibrary/RTASP_Adapt",
"AlturaPorts/TDMPlugins/PluginLibrary/CoreClasses",
"AlturaPorts/TDMPlugins/PluginLibrary/Controls",
"AlturaPorts/TDMPlugins/PluginLibrary/Meters",
"AlturaPorts/TDMPlugins/PluginLibrary/ViewClasses",
"AlturaPorts/TDMPlugins/PluginLibrary/DSPClasses",
"AlturaPorts/TDMPlugins/PluginLibrary/Interfaces",
"AlturaPorts/TDMPlugins/common",
"AlturaPorts/TDMPlugins/common/Platform",
"AlturaPorts/TDMPlugins/common/Macros",
"AlturaPorts/TDMPlugins/SignalProcessing/Public",
"AlturaPorts/TDMPlugIns/DSPManager/Interfaces",
"AlturaPorts/SADriver/Interfaces",
"AlturaPorts/DigiPublic/Interfaces",
"AlturaPorts/DigiPublic",
"AlturaPorts/Fic/Interfaces/DAEClient",
"AlturaPorts/NewFileLibs/Cmn",
"AlturaPorts/NewFileLibs/DOA",
"AlturaPorts/AlturaSource/PPC_H",
"AlturaPorts/AlturaSource/AppSupport",
"AvidCode/AVX2sdk/AVX/avx2/avx2sdk/inc",
"xplat/AVX/avx2/avx2sdk/inc" };
for (int i = 0; i < numElementsInArray (p); ++i)
exporter.addToExtraSearchPaths (rtasFolder.getChildFile (p[i]));
}
else if (exporter.isXcode())
{
exporter.extraSearchPaths.add ("$(DEVELOPER_DIR)/Headers/FlatCarbon");
exporter.extraSearchPaths.add ("$(SDKROOT)/Developer/Headers/FlatCarbon");
static const char* p[] = { "AlturaPorts/TDMPlugIns/PlugInLibrary/Controls",
"AlturaPorts/TDMPlugIns/PlugInLibrary/CoreClasses",
"AlturaPorts/TDMPlugIns/PlugInLibrary/DSPClasses",
"AlturaPorts/TDMPlugIns/PlugInLibrary/EffectClasses",
"AlturaPorts/TDMPlugIns/PlugInLibrary/MacBuild",
"AlturaPorts/TDMPlugIns/PlugInLibrary/Meters",
"AlturaPorts/TDMPlugIns/PlugInLibrary/ProcessClasses",
"AlturaPorts/TDMPlugIns/PlugInLibrary/ProcessClasses/Interfaces",
"AlturaPorts/TDMPlugIns/PlugInLibrary/RTASP_Adapt",
"AlturaPorts/TDMPlugIns/PlugInLibrary/Utilities",
"AlturaPorts/TDMPlugIns/PlugInLibrary/ViewClasses",
"AlturaPorts/TDMPlugIns/DSPManager/**",
"AlturaPorts/TDMPlugIns/SupplementalPlugInLib/Encryption",
"AlturaPorts/TDMPlugIns/SupplementalPlugInLib/GraphicsExtensions",
"AlturaPorts/TDMPlugIns/common/**",
"AlturaPorts/TDMPlugIns/common/PI_LibInterface",
"AlturaPorts/TDMPlugIns/PACEProtection/**",
"AlturaPorts/TDMPlugIns/SignalProcessing/**",
"AlturaPorts/OMS/Headers",
"AlturaPorts/Fic/Interfaces/**",
"AlturaPorts/Fic/Source/SignalNets",
"AlturaPorts/DSIPublicInterface/PublicHeaders",
"DAEWin/Include",
"AlturaPorts/DigiPublic/Interfaces",
"AlturaPorts/DigiPublic",
"AlturaPorts/NewFileLibs/DOA",
"AlturaPorts/NewFileLibs/Cmn",
"xplat/AVX/avx2/avx2sdk/inc",
"xplat/AVX/avx2/avx2sdk/utils" };
for (int i = 0; i < numElementsInArray (p); ++i)
exporter.addToExtraSearchPaths (rtasFolder.getChildFile (p[i]));
}
}
static inline void prepareExporter (ProjectExporter& exporter, ProjectSaver& projectSaver)
{
if (isExporterSupported (exporter))
{
fixMissingRTASValues (exporter);
const RelativePath rtasFolder (getRTASFolderPath (exporter));
if (exporter.isVisualStudio())
{
exporter.msvcTargetSuffix = ".dpm";
exporter.msvcExtraPreprocessorDefs.set ("JucePlugin_WinBag_path",
createRebasedPath (exporter,
rtasFolder.getChildFile ("WinBag")));
exporter.msvcDelayLoadedDLLs = "DAE.dll; DigiExt.dll; DSI.dll; PluginLib.dll; "
"DSPManager.dll; DSPManager.dll; DSPManagerClientLib.dll; RTASClientLib.dll";
if (! exporter.getExtraLinkerFlagsString().contains ("/FORCE:multiple"))
exporter.getExtraLinkerFlags() = exporter.getExtraLinkerFlags().toString() + " /FORCE:multiple";
RelativePath modulePath (exporter.rebaseFromProjectFolderToBuildTarget (RelativePath (exporter.getPathForModuleString ("juce_audio_plugin_client"),
RelativePath::projectFolder)
.getChildFile ("juce_audio_plugin_client")
.getChildFile ("RTAS")));
for (ProjectExporter::ConfigIterator config (exporter); config.next();)
{
config->getValue (Ids::msvcModuleDefinitionFile) = modulePath.getChildFile ("juce_RTAS_WinExports.def").toWindowsStyle();
if (config->getValue (Ids::useRuntimeLibDLL).getValue().isVoid())
config->getValue (Ids::useRuntimeLibDLL) = true;
if (config->getValue (Ids::postbuildCommand).toString().isEmpty())
config->getValue (Ids::postbuildCommand)
= "copy /Y "
+ modulePath.getChildFile ("juce_RTAS_WinResources.rsr").toWindowsStyle().quoted()
+ " \"$(TargetPath)\".rsr";
}
}
else
{
exporter.xcodeCanUseDwarf = false;
exporter.xcodeExtraLibrariesDebug.add (rtasFolder.getChildFile ("MacBag/Libs/Debug/libPluginLibrary.a"));
exporter.xcodeExtraLibrariesRelease.add (rtasFolder.getChildFile ("MacBag/Libs/Release/libPluginLibrary.a"));
}
writePluginCharacteristicsFile (projectSaver);
addExtraSearchPaths (exporter);
}
}
static inline void createPropertyEditors (ProjectExporter& exporter, PropertyListBuilder& props)
{
if (isExporterSupported (exporter))
{
fixMissingRTASValues (exporter);
props.add (new TextPropertyComponent (getRTASFolder (exporter), "RTAS Folder", 1024, false),
"If you're building an RTAS, this must be the folder containing the RTAS SDK. This should be an absolute path.");
}
}
}
//==============================================================================
namespace AUHelpers
{
static inline void prepareExporter (ProjectExporter& exporter, ProjectSaver& projectSaver)
{
writePluginCharacteristicsFile (projectSaver);
if (exporter.isXcode())
{
exporter.extraSearchPaths.add ("$(DEVELOPER_DIR)/Extras/CoreAudio/PublicUtility");
exporter.extraSearchPaths.add ("$(DEVELOPER_DIR)/Extras/CoreAudio/AudioUnits/AUPublic/Utility");
exporter.extraSearchPaths.add ("$(DEVELOPER_DIR)/Extras/CoreAudio/AudioUnits/AUPublic/AUBase");
exporter.xcodeFrameworks.addTokens ("AudioUnit CoreAudioKit", false);
exporter.xcodeExcludedFiles64Bit = "\"*Carbon*.cpp\"";
Project::Item subGroup (projectSaver.getGeneratedCodeGroup().addNewSubGroup ("Juce AU Wrapper", -1));
subGroup.setID ("__juceappleaufiles");
{
#define JUCE_AU_PUBLICUTILITY "${DEVELOPER_DIR}/Extras/CoreAudio/PublicUtility/"
#define JUCE_AU_PUBLIC "${DEVELOPER_DIR}/Extras/CoreAudio/AudioUnits/AUPublic/"
static const char* appleAUFiles[] =
{
JUCE_AU_PUBLICUTILITY "CADebugMacros.h",
JUCE_AU_PUBLICUTILITY "CAAUParameter.cpp",
JUCE_AU_PUBLICUTILITY "CAAUParameter.h",
JUCE_AU_PUBLICUTILITY "CAAudioChannelLayout.cpp",
JUCE_AU_PUBLICUTILITY "CAAudioChannelLayout.h",
JUCE_AU_PUBLICUTILITY "CAMutex.cpp",
JUCE_AU_PUBLICUTILITY "CAMutex.h",
JUCE_AU_PUBLICUTILITY "CAStreamBasicDescription.cpp",
JUCE_AU_PUBLICUTILITY "CAStreamBasicDescription.h",
JUCE_AU_PUBLICUTILITY "CAVectorUnitTypes.h",
JUCE_AU_PUBLICUTILITY "CAVectorUnit.cpp",
JUCE_AU_PUBLICUTILITY "CAVectorUnit.h",
JUCE_AU_PUBLIC "AUViewBase/AUViewLocalizedStringKeys.h",
JUCE_AU_PUBLIC "AUCarbonViewBase/AUCarbonViewDispatch.cpp",
JUCE_AU_PUBLIC "AUCarbonViewBase/AUCarbonViewControl.cpp",
JUCE_AU_PUBLIC "AUCarbonViewBase/AUCarbonViewControl.h",
JUCE_AU_PUBLIC "AUCarbonViewBase/CarbonEventHandler.cpp",
JUCE_AU_PUBLIC "AUCarbonViewBase/CarbonEventHandler.h",
JUCE_AU_PUBLIC "AUCarbonViewBase/AUCarbonViewBase.cpp",
JUCE_AU_PUBLIC "AUCarbonViewBase/AUCarbonViewBase.h",
JUCE_AU_PUBLIC "AUBase/AUBase.cpp",
JUCE_AU_PUBLIC "AUBase/AUBase.h",
JUCE_AU_PUBLIC "AUBase/AUDispatch.cpp",
JUCE_AU_PUBLIC "AUBase/AUDispatch.h",
JUCE_AU_PUBLIC "AUBase/AUInputElement.cpp",
JUCE_AU_PUBLIC "AUBase/AUInputElement.h",
JUCE_AU_PUBLIC "AUBase/AUOutputElement.cpp",
JUCE_AU_PUBLIC "AUBase/AUOutputElement.h",
JUCE_AU_PUBLIC "AUBase/AUResources.r",
JUCE_AU_PUBLIC "AUBase/AUScopeElement.cpp",
JUCE_AU_PUBLIC "AUBase/AUScopeElement.h",
JUCE_AU_PUBLIC "AUBase/ComponentBase.cpp",
JUCE_AU_PUBLIC "AUBase/ComponentBase.h",
JUCE_AU_PUBLIC "OtherBases/AUMIDIBase.cpp",
JUCE_AU_PUBLIC "OtherBases/AUMIDIBase.h",
JUCE_AU_PUBLIC "OtherBases/AUMIDIEffectBase.cpp",
JUCE_AU_PUBLIC "OtherBases/AUMIDIEffectBase.h",
JUCE_AU_PUBLIC "OtherBases/AUOutputBase.cpp",
JUCE_AU_PUBLIC "OtherBases/AUOutputBase.h",
JUCE_AU_PUBLIC "OtherBases/MusicDeviceBase.cpp",
JUCE_AU_PUBLIC "OtherBases/MusicDeviceBase.h",
JUCE_AU_PUBLIC "OtherBases/AUEffectBase.cpp",
JUCE_AU_PUBLIC "OtherBases/AUEffectBase.h",
JUCE_AU_PUBLIC "Utility/AUBuffer.cpp",
JUCE_AU_PUBLIC "Utility/AUBuffer.h",
JUCE_AU_PUBLIC "Utility/AUInputFormatConverter.h",
JUCE_AU_PUBLIC "Utility/AUSilentTimeout.h",
JUCE_AU_PUBLIC "Utility/AUTimestampGenerator.h",
nullptr
};
for (const char** f = appleAUFiles; *f != nullptr; ++f)
{
const RelativePath file (*f, RelativePath::projectFolder);
subGroup.addRelativeFile (file, -1, file.hasFileExtension ("cpp;mm"));
subGroup.getChild (subGroup.getNumChildren() - 1).getShouldInhibitWarningsValue() = true;
}
}
if (exporter.isXcode())
{
XmlElement plistKey ("key");
plistKey.addTextElement ("AudioComponents");
XmlElement plistEntry ("array");
XmlElement* dict = plistEntry.createNewChildElement ("dict");
Project& project = exporter.getProject();
addPlistDictionaryKey (dict, "name", getPluginManufacturer (project).toString()
+ ": " + getPluginName (project).toString());
addPlistDictionaryKey (dict, "description", getPluginDesc (project).toString());
addPlistDictionaryKey (dict, "factoryFunction", getPluginAUExportPrefix (project).toString() + "Factory");
addPlistDictionaryKey (dict, "manufacturer", getPluginManufacturerCode (project).toString().trim().substring (0, 4));
addPlistDictionaryKey (dict, "type", getAUMainTypeCode (project));
addPlistDictionaryKey (dict, "subtype", getPluginCode (project).toString().trim().substring (0, 4));
addPlistDictionaryKeyInt (dict, "version", project.getVersionAsHexInteger());
exporter.xcodeExtraPListEntries.add (plistKey);
exporter.xcodeExtraPListEntries.add (plistEntry);
}
}
}
}
//==============================================================================
namespace AAXHelpers
{
static Value getAAXFolder (ProjectExporter& exporter) { return exporter.getSetting (Ids::aaxFolder); }
static RelativePath getAAXFolderPath (ProjectExporter& exporter) { return RelativePath (exporter.getSettingString (Ids::aaxFolder),
RelativePath::projectFolder); }
static bool isExporterSupported (ProjectExporter& exporter) { return exporter.isVisualStudio() || exporter.isXcode(); }
static void fixMissingAAXValues (ProjectExporter& exporter)
{
if (getAAXFolder (exporter).toString().isEmpty())
{
if (exporter.isVisualStudio())
getAAXFolder (exporter) = "c:\\SDKs\\AAX";
else
getAAXFolder (exporter) = "~/SDKs/AAX";
}
fixMissingXcodePostBuildScript (exporter);
}
static void addExtraSearchPaths (ProjectExporter& exporter)
{
const RelativePath aaxFolder (getAAXFolderPath (exporter));
exporter.addToExtraSearchPaths (aaxFolder);
exporter.addToExtraSearchPaths (aaxFolder.getChildFile ("Interfaces"));
exporter.addToExtraSearchPaths (aaxFolder.getChildFile ("Interfaces").getChildFile ("ACF"));
}
static inline void prepareExporter (ProjectExporter& exporter, ProjectSaver& projectSaver)
{
if (isExporterSupported (exporter))
{
fixMissingAAXValues (exporter);
const RelativePath aaxLibsFolder (getAAXFolderPath (exporter).getChildFile ("Libs"));
if (exporter.isVisualStudio())
{
exporter.msvcTargetSuffix = ".aaxplugin";
for (ProjectExporter::ConfigIterator config (exporter); config.next();)
if (config->getValue (Ids::useRuntimeLibDLL).getValue().isVoid())
config->getValue (Ids::useRuntimeLibDLL) = true;
exporter.msvcExtraPreprocessorDefs.set ("JucePlugin_AAXLibs_path",
createRebasedPath (exporter, aaxLibsFolder));
}
else
{
exporter.xcodeExtraLibrariesDebug.add (aaxLibsFolder.getChildFile ("Debug/libAAXLibrary.a"));
exporter.xcodeExtraLibrariesRelease.add (aaxLibsFolder.getChildFile ("Release/libAAXLibrary.a"));
}
writePluginCharacteristicsFile (projectSaver);
addExtraSearchPaths (exporter);
}
}
static inline void createPropertyEditors (ProjectExporter& exporter, PropertyListBuilder& props)
{
if (isExporterSupported (exporter))
{
fixMissingAAXValues (exporter);
props.add (new TextPropertyComponent (getAAXFolder (exporter), "AAX SDK Folder", 1024, false),
"If you're building an AAX, this must be the folder containing the AAX SDK. This should be an absolute path.");
}
}
}
#endif // __JUCER_AUDIOPLUGINMODULE_JUCEHEADER__
@@ -0,0 +1,296 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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 PropertyGroupComponent : public Component
{
public:
PropertyGroupComponent() {}
void setProperties (const PropertyListBuilder& newProps)
{
properties.clear();
properties.addArray (newProps.components);
for (int i = properties.size(); --i >= 0;)
addAndMakeVisible (properties.getUnchecked(i));
}
int updateSize (int x, int y, int width)
{
int height = 38;
for (int i = 0; i < properties.size(); ++i)
{
PropertyComponent* pp = properties.getUnchecked(i);
pp->setBounds (10, height, width - 20, pp->getPreferredHeight());
height += pp->getHeight();
}
height += 16;
setBounds (x, y, width, height);
return height;
}
void paint (Graphics& g) override
{
const Colour bkg (findColour (mainBackgroundColourId));
g.setColour (Colours::white.withAlpha (0.35f));
g.fillRect (0, 30, getWidth(), getHeight() - 38);
g.setFont (Font (15.0f, Font::bold));
g.setColour (bkg.contrasting (0.7f));
g.drawFittedText (getName(), 12, 0, getWidth() - 16, 25, Justification::bottomLeft, 1);
}
OwnedArray<PropertyComponent> properties;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyGroupComponent)
};
//==============================================================================
class ConfigTreeItemBase : public JucerTreeViewBase,
public ValueTree::Listener
{
public:
ConfigTreeItemBase() {}
void showSettingsPage (Component* content)
{
content->setComponentID (getUniqueName());
ScopedPointer<Component> comp (content);
if (ProjectContentComponent* pcc = getProjectContentComponent())
pcc->setEditorComponent (new PropertyPanelViewport (comp.release()), nullptr);
}
void closeSettingsPage()
{
if (ProjectContentComponent* pcc = getProjectContentComponent())
{
if (PropertyPanelViewport* ppv = dynamic_cast<PropertyPanelViewport*> (pcc->getEditorComponent()))
if (ppv->viewport.getViewedComponent()->getComponentID() == getUniqueName())
pcc->hideEditor();
}
}
void deleteAllSelectedItems() override
{
TreeView* const tree = getOwnerView();
jassert (tree->getNumSelectedItems() <= 1); // multi-select should be disabled
if (ConfigTreeItemBase* s = dynamic_cast<ConfigTreeItemBase*> (tree->getSelectedItem (0)))
s->deleteItem();
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen)
refreshSubItems();
}
void valueTreePropertyChanged (ValueTree&, const Identifier&) override {}
void valueTreeChildAdded (ValueTree&, ValueTree&) override {}
void valueTreeChildRemoved (ValueTree&, ValueTree&) override {}
void valueTreeChildOrderChanged (ValueTree&) override {}
void valueTreeParentChanged (ValueTree&) override {}
virtual bool isProjectSettings() const { return false; }
virtual bool isModulesList() const { return false; }
static void updateSize (Component& comp, PropertyGroupComponent& group)
{
const int width = jmax (550, comp.getParentWidth() - 20);
int y = 0;
y += group.updateSize (12, y, width - 12);
comp.setSize (width, y);
}
private:
//==============================================================================
class PropertyPanelViewport : public Component
{
public:
PropertyPanelViewport (Component* content)
{
addAndMakeVisible (viewport);
addAndMakeVisible (rolloverHelp);
viewport.setViewedComponent (content, true);
}
void paint (Graphics& g) override
{
IntrojucerLookAndFeel::fillWithBackgroundTexture (*this, g);
}
void resized() override
{
Rectangle<int> r (getLocalBounds());
rolloverHelp.setBounds (r.removeFromBottom (70).reduced (10, 0));
viewport.setBounds (r);
}
Viewport viewport;
RolloverHelpComp rolloverHelp;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyPanelViewport)
};
};
//==============================================================================
class RootItem : public ConfigTreeItemBase
{
public:
RootItem (Project& p)
: project (p), exportersTree (p.getExporters())
{
exportersTree.addListener (this);
}
bool isProjectSettings() const override { return true; }
String getRenamingName() const override { return getDisplayName(); }
String getDisplayName() const override { return project.getTitle(); }
void setName (const String&) override {}
bool isMissing() override { return false; }
Icon getIcon() const override { return project.getMainGroup().getIcon().withContrastingColourTo (getBackgroundColour()); }
void showDocument() override { showSettingsPage (new SettingsComp (project)); }
bool canBeSelected() const override { return true; }
bool mightContainSubItems() override { return project.getNumExporters() > 0; }
String getUniqueName() const override { return "config_root"; }
void addSubItems() override
{
addSubItem (new EnabledModulesItem (project));
IntrojucerApp::getApp().addExtraConfigItems (project, *this);
int i = 0;
for (Project::ExporterIterator exporter (project); exporter.next(); ++i)
addSubItem (new ExporterItem (project, exporter.exporter.release(), i));
}
void showPopupMenu() override
{
PopupMenu menu;
const StringArray exporters (ProjectExporter::getExporterNames());
for (int i = 0; i < exporters.size(); ++i)
menu.addItem (i + 1, "Create a new " + exporters[i] + " target");
launchPopupMenu (menu);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode > 0)
{
String exporterName (ProjectExporter::getExporterNames() [resultCode - 1]);
if (exporterName.isNotEmpty())
project.addNewExporter (exporterName);
}
}
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 - 1), project.getUndoManagerFor (exportersTree));
}
//==============================================================================
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { refreshIfNeeded (parentTree); }
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&) override { refreshIfNeeded (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree) override { refreshIfNeeded (parentTree); }
void refreshIfNeeded (ValueTree& changedTree)
{
if (changedTree == exportersTree)
refreshSubItems();
}
private:
Project& project;
ValueTree exportersTree;
//==============================================================================
class SettingsComp : public Component,
private ChangeListener
{
public:
SettingsComp (Project& p) : project (p)
{
addAndMakeVisible (group);
updatePropertyList();
project.addChangeListener (this);
}
~SettingsComp()
{
project.removeChangeListener (this);
}
void parentSizeChanged() override
{
updateSize (*this, group);
}
void updatePropertyList()
{
PropertyListBuilder props;
project.createPropertyEditors (props);
group.setProperties (props);
group.setName ("Project Settings");
lastProjectType = project.getProjectTypeValue().getValue();
parentSizeChanged();
}
void changeListenerCallback (ChangeBroadcaster*) override
{
if (lastProjectType != project.getProjectTypeValue().getValue())
updatePropertyList();
}
private:
Project& project;
var lastProjectType;
PropertyGroupComponent group;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SettingsComp)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RootItem)
};
@@ -0,0 +1,251 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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 ExporterItem : public ConfigTreeItemBase
{
public:
ExporterItem (Project& p, ProjectExporter* e, int index)
: project (p), exporter (e), configListTree (exporter->getConfigurations()),
exporterIndex (index)
{
configListTree.addListener (this);
jassert (exporter != nullptr);
}
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->getName(); }
void setName (const String&) override {}
bool isMissing() override { return false; }
Icon getIcon() const override { return Icon (getIcons().exporter, getContrastingColour (0.5f)); }
void showDocument() override { showSettingsPage (new SettingsComp (exporter)); }
void deleteItem() override
{
if (AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Delete Exporter",
"Are you sure you want to delete this export target?"))
{
closeSettingsPage();
ValueTree parent (exporter->settings.getParent());
parent.removeChild (exporter->settings, project.getUndoManagerFor (parent));
}
}
void addSubItems() override
{
for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
addSubItem (new ConfigItem (config.config, exporter->getName()));
}
void showPopupMenu() override
{
PopupMenu menu;
menu.addItem (1, "Add a new configuration");
menu.addSeparator();
menu.addItem (2, "Delete this exporter");
launchPopupMenu (menu);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode == 2)
deleteAllSelectedItems();
else if (resultCode == 1)
exporter->addNewConfiguration (nullptr);
}
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
{
const int 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&) override { refreshIfNeeded (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree) override { refreshIfNeeded (parentTree); }
void refreshIfNeeded (ValueTree& changedTree)
{
if (changedTree == configListTree)
refreshSubItems();
}
private:
Project& project;
ScopedPointer<ProjectExporter> exporter;
ValueTree configListTree;
int exporterIndex;
//==============================================================================
class SettingsComp : public Component
{
public:
SettingsComp (ProjectExporter* exp)
{
addAndMakeVisible (group);
PropertyListBuilder props;
exp->createPropertyEditors (props);
group.setProperties (props);
group.setName ("Export target: " + exp->getName());
parentSizeChanged();
}
void parentSizeChanged() override { updateSize (*this, group); }
private:
PropertyGroupComponent group;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SettingsComp)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterItem)
};
//==============================================================================
class ConfigItem : public ConfigTreeItemBase
{
public:
ConfigItem (const ProjectExporter::BuildConfiguration::Ptr& conf, const String& expName)
: config (conf), exporterName (expName), configTree (config->config)
{
jassert (config != nullptr);
configTree.addListener (this);
}
bool isMissing() 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, getContrastingColour (Colours::green, 0.5f)); }
void showDocument() override { showSettingsPage (new SettingsComp (config, exporterName)); }
void itemOpennessChanged (bool) override {}
void deleteItem() override
{
if (AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Delete Configuration",
"Are you sure you want to delete this configuration?"))
{
closeSettingsPage();
config->removeFromExporter();
}
}
void showPopupMenu() override
{
PopupMenu menu;
menu.addItem (1, "Create a copy of this configuration");
menu.addSeparator();
menu.addItem (2, "Delete this configuration");
launchPopupMenu (menu);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode == 2)
{
deleteAllSelectedItems();
}
else if (resultCode == 1)
{
for (Project::ExporterIterator exporter (config->project); exporter.next();)
{
if (config->config.isAChildOf (exporter->settings))
{
exporter->addNewConfiguration (config);
break;
}
}
}
}
var getDragSourceDescription()
{
return getParentItem()->getUniqueName() + "||" + config->getName();
}
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { repaintItem(); }
private:
ProjectExporter::BuildConfiguration::Ptr config;
String exporterName;
ValueTree configTree;
//==============================================================================
class SettingsComp : public Component
{
public:
SettingsComp (ProjectExporter::BuildConfiguration* conf, const String& expName)
{
addAndMakeVisible (group);
PropertyListBuilder props;
conf->createPropertyEditors (props);
group.setProperties (props);
group.setName (expName + " / " + conf->getName());
parentSizeChanged();
}
void parentSizeChanged() override { updateSize (*this, group); }
private:
PropertyGroupComponent group;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SettingsComp)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConfigItem)
};
@@ -0,0 +1,384 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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(); }
Icon getIcon() const override { return Icon (getIcons().jigsaw, getContrastingColour (Colours::red, 0.5f)); }
void showDocument() override { showSettingsPage (new ModuleSettingsPanel (project, moduleID)); }
void deleteItem() override { project.getModules().removeModule (moduleID); }
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 TextPropertyComponent (exporter->getPathForModuleValue (moduleID),
"Path for " + exporter->getName().quoted(), 1024, false),
"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<var> mappings;
mappings.add (Project::configFlagDefault);
mappings.add (Project::configFlagEnabled);
mappings.add (Project::configFlagDisabled);
ModuleDescription info (project.getModules().getModuleInfo (moduleID));
if (info.isValid())
{
OwnedArray <Project::ConfigFlag> 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
{
public:
ModuleInfoComponent (Project& p, const String& modID)
: PropertyComponent ("Module", 150), project (p), moduleID (modID)
{
}
void refresh() {}
void paint (Graphics& g)
{
g.setColour (Colours::white.withAlpha (0.4f));
g.fillRect (0, 0, getWidth(), getHeight() - 1);
AttributedString s;
s.setJustification (Justification::topLeft);
Font f (14.0f);
ModuleDescription info (project.getModules().getModuleInfo (moduleID));
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());
}
private:
Project& project;
String moduleID;
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() {}
void paint (Graphics& g)
{
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*)
{
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->manifestFile, project.getModules().areMostModulesCopiedLocally());
else
anyFailed = true;
}
if (ModuleSettingsPanel* p = findParentComponentOfClass<ModuleSettingsPanel>())
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()
{
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);
}
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::red, 0.5f)); }
void showDocument()
{
if (ProjectContentComponent* pcc = getProjectContentComponent())
pcc->setEditorComponent (new ModulesPanel (project), nullptr);
}
static File getManifestFile (const File& draggedFile)
{
if (draggedFile.getFileName() == ModuleDescription::getManifestFileName())
return draggedFile;
return draggedFile.getChildFile (ModuleDescription::getManifestFileName());
}
bool isInterestedInFileDrag (const StringArray& files) override
{
for (int i = files.size(); --i >= 0;)
if (ModuleDescription (getManifestFile (files[i])).isValid())
return true;
return false;
}
void filesDropped (const StringArray& files, int /*insertIndex*/) override
{
Array<ModuleDescription> modules;
for (int i = files.size(); --i >= 0;)
{
ModuleDescription m (getManifestFile (files[i]));
if (m.isValid())
modules.add (m);
}
for (int i = 0; i < modules.size(); ++i)
project.getModules().addModule (modules.getReference(i).manifestFile,
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&) override { refreshIfNeeded (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree) override { refreshIfNeeded (parentTree); }
void refreshIfNeeded (ValueTree& changedTree)
{
if (changedTree == moduleListTree)
refreshSubItems();
}
private:
Project& project;
ValueTree moduleListTree;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesItem)
};
@@ -0,0 +1,167 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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.
==============================================================================
*/
#ifndef __JUCER_GROUPINFORMATIONCOMPONENT_JUCEHEADER__
#define __JUCER_GROUPINFORMATIONCOMPONENT_JUCEHEADER__
#include "../jucer_Headers.h"
#include "../Project/jucer_Project.h"
//==============================================================================
class GroupInformationComponent : public Component,
private ListBoxModel,
private ValueTree::Listener
{
public:
GroupInformationComponent (const Project::Item& group)
: item (group)
{
list.setModel (this);
list.setColour (ListBox::backgroundColourId, Colours::transparentBlack);
addAndMakeVisible (list);
list.updateContent();
list.setRowHeight (20);
item.state.addListener (this);
lookAndFeelChanged();
}
~GroupInformationComponent()
{
item.state.removeListener (this);
}
//==============================================================================
void paint (Graphics& g) override
{
IntrojucerLookAndFeel::fillWithBackgroundTexture (*this, g);
}
void resized() override
{
list.setBounds (getLocalBounds().reduced (5, 4));
}
int getNumRows() override
{
return item.getNumChildren();
}
void paintListBoxItem (int /*rowNumber*/, Graphics& g, int width, int height, bool /*rowIsSelected*/) override
{
g.setColour (Colours::white.withAlpha (0.4f));
g.fillRect (0, 0, width, height - 1);
}
Component* refreshComponentForRow (int rowNumber, bool /*isRowSelected*/, Component* existingComponentToUpdate) override
{
ScopedPointer<Component> existing (existingComponentToUpdate);
if (rowNumber < getNumRows())
{
Project::Item child (item.getChild (rowNumber));
if (existingComponentToUpdate == nullptr
|| dynamic_cast <FileOptionComponent*> (existing.get())->item != child)
{
existing = nullptr;
existing = new FileOptionComponent (child);
}
}
return existing.release();
}
//==============================================================================
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { itemChanged(); }
void valueTreeChildAdded (ValueTree&, ValueTree&) override { itemChanged(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&) override { itemChanged(); }
void valueTreeChildOrderChanged (ValueTree&) override { itemChanged(); }
void valueTreeParentChanged (ValueTree&) override { itemChanged(); }
private:
Project::Item item;
ListBox list;
void itemChanged()
{
list.updateContent();
repaint();
}
//==============================================================================
class FileOptionComponent : public Component
{
public:
FileOptionComponent (const Project::Item& fileItem)
: item (fileItem),
compileButton ("Compile"),
resourceButton ("Add to Binary Resources")
{
if (item.isFile())
{
addAndMakeVisible (compileButton);
compileButton.getToggleStateValue().referTo (item.getShouldCompileValue());
addAndMakeVisible (resourceButton);
resourceButton.getToggleStateValue().referTo (item.getShouldAddToResourceValue());
}
}
void paint (Graphics& g) override
{
int x = getHeight() + 6;
item.getIcon().withContrastingColourTo (Colours::grey)
.draw (g, Rectangle<float> (3.0f, 2.0f, x - 6.0f, getHeight() - 4.0f),
item.isIconCrossedOut());
g.setColour (Colours::black);
g.setFont (getHeight() * 0.6f);
const int x2 = compileButton.isVisible() ? compileButton.getX() - 4
: getWidth() - 4;
g.drawText (item.getName(), x, 0, x2 - x, getHeight(), Justification::centredLeft, true);
}
void resized() override
{
int w = 180;
resourceButton.setBounds (getWidth() - w, 1, w, getHeight() - 2);
w = 100;
compileButton.setBounds (resourceButton.getX() - w, 1, w, getHeight() - 2);
}
Project::Item item;
private:
ToggleButton compileButton, resourceButton;
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GroupInformationComponent)
};
#endif // __JUCER_GROUPINFORMATIONCOMPONENT_JUCEHEADER__
@@ -0,0 +1,907 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "jucer_Module.h"
#include "jucer_ProjectType.h"
#include "../Project Saving/jucer_ProjectExporter.h"
#include "../Project Saving/jucer_ProjectSaver.h"
#include "jucer_AudioPluginModule.h"
ModuleDescription::ModuleDescription (const File& manifest)
: moduleInfo (JSON::parse (manifest)), manifestFile (manifest)
{
if (moduleInfo.isVoid() && manifestFile.exists())
{
var json;
Result r (JSON::parse (manifestFile.loadFileAsString(), json));
if (r.failed() && manifestFile.loadFileAsString().isNotEmpty())
{
DBG (r.getErrorMessage());
jassertfalse; // broken JSON in a module manifest.
}
}
}
//==============================================================================
ModuleList::ModuleList()
{
}
ModuleList::ModuleList (const ModuleList& other)
{
operator= (other);
}
ModuleList& ModuleList::operator= (const ModuleList& other)
{
modules.clear();
modules.addCopiesOf (other.modules);
return *this;
}
const ModuleDescription* ModuleList::getModuleWithID (const String& moduleID) const
{
for (int i = 0; i < modules.size(); ++i)
{
ModuleDescription* m = modules.getUnchecked(i);
if (m->getID() == moduleID)
return m;
}
return nullptr;
}
struct ModuleSorter
{
static int compareElements (const ModuleDescription* m1, const ModuleDescription* m2)
{
return m1->getID().compareIgnoreCase (m2->getID());
}
};
void ModuleList::sort()
{
ModuleSorter sorter;
modules.sort (sorter);
}
StringArray ModuleList::getIDs() const
{
StringArray results;
for (int i = 0; i < modules.size(); ++i)
results.add (modules.getUnchecked(i)->getID());
results.sort (true);
return results;
}
Result ModuleList::addAllModulesInFolder (const File& path)
{
const File moduleDef (path.getChildFile (ModuleDescription::getManifestFileName()));
if (moduleDef.exists())
{
ModuleDescription m (moduleDef);
if (! m.isValid())
return Result::fail ("Failed to load module manifest: " + moduleDef.getFullPathName());
modules.add (new ModuleDescription (m));
}
else
{
for (DirectoryIterator iter (path, false, "*", File::findDirectories); iter.next();)
{
Result r = addAllModulesInFolder (iter.getFile().getLinkedTarget());
if (r.failed())
return r;
}
}
return Result::ok();
}
static Array<File> getAllPossibleModulePaths (Project& project)
{
StringArray paths;
for (Project::ExporterIterator exporter (project); exporter.next();)
{
for (int i = 0; i < project.getModules().getNumModules(); ++i)
{
const String path (exporter->getPathForModuleString (project.getModules().getModuleID (i)));
if (path.isNotEmpty())
paths.addIfNotAlreadyThere (path);
}
String oldPath (exporter->getLegacyModulePath());
if (oldPath.isNotEmpty())
paths.addIfNotAlreadyThere (oldPath);
}
Array<File> files;
for (int i = 0; i < paths.size(); ++i)
{
const File f (project.resolveFilename (paths[i]));
if (f.isDirectory())
{
files.add (f);
if (f.getChildFile ("modules").isDirectory())
files.addIfNotAlreadyThere (f.getChildFile ("modules"));
}
}
return files;
}
Result ModuleList::scanAllKnownFolders (Project& project)
{
modules.clear();
Result result (Result::ok());
const Array<File> modulePaths (getAllPossibleModulePaths (project));
for (int i = 0; i < modulePaths.size(); ++i)
{
result = addAllModulesInFolder (modulePaths.getReference(i));
if (result.failed())
break;
}
sort();
return result;
}
bool ModuleList::loadFromWebsite()
{
modules.clear();
URL baseURL ("http://www.juce.com/juce/modules");
URL url (baseURL.getChildURL ("modulelist.php"));
const ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String::empty, 4000));
if (in == nullptr)
return false;
var infoList (JSON::parse (in->readEntireStreamAsString()));
if (! infoList.isArray())
return false;
const Array<var>* moduleList = infoList.getArray();
for (int i = 0; i < moduleList->size(); ++i)
{
const var& m = moduleList->getReference(i);
const String file (m [Ids::file].toString());
if (file.isNotEmpty())
{
ModuleDescription lm (m [Ids::info]);
if (lm.isValid())
{
lm.url = baseURL.getChildURL (file);
modules.add (new ModuleDescription (lm));
}
}
}
sort();
return true;
}
//==============================================================================
LibraryModule::LibraryModule (const ModuleDescription& d)
: moduleInfo (d)
{
}
bool LibraryModule::isAUPluginHost (const Project& project) const { return getID() == "juce_audio_processors" && project.isConfigFlagEnabled ("JUCE_PLUGINHOST_AU"); }
bool LibraryModule::isVSTPluginHost (const Project& project) const { return getID() == "juce_audio_processors" && project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST"); }
bool LibraryModule::isVST3PluginHost (const Project& project) const { return getID() == "juce_audio_processors" && project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST3"); }
File LibraryModule::getModuleHeaderFile (const File& folder) const
{
return folder.getChildFile (moduleInfo.getHeaderName());
}
//==============================================================================
void LibraryModule::writeIncludes (ProjectSaver& projectSaver, OutputStream& out)
{
const File localModuleFolder (projectSaver.getLocalModuleFolder (getID()));
const File localHeader (getModuleHeaderFile (localModuleFolder));
localModuleFolder.createDirectory();
if (projectSaver.project.getModules().shouldCopyModuleFilesLocally (getID()).getValue())
{
projectSaver.copyFolder (moduleInfo.getFolder(), localModuleFolder);
}
else
{
localModuleFolder.createDirectory();
createLocalHeaderWrapper (projectSaver, getModuleHeaderFile (moduleInfo.getFolder()), localHeader);
}
out << CodeHelpers::createIncludeStatement (localHeader, projectSaver.getGeneratedCodeFolder()
.getChildFile ("AppConfig.h")) << newLine;
}
static void writeGuardedInclude (OutputStream& out, StringArray paths, StringArray guards)
{
StringArray uniquePaths (paths);
uniquePaths.removeDuplicates (false);
if (uniquePaths.size() == 1)
{
out << "#include " << paths[0] << newLine;
}
else
{
for (int i = paths.size(); --i >= 0;)
{
for (int j = i; --j >= 0;)
{
if (paths[i] == paths[j] && guards[i] == guards[j])
{
paths.remove (i);
guards.remove (i);
}
}
}
for (int i = 0; i < paths.size(); ++i)
{
out << (i == 0 ? "#if " : "#elif ") << guards[i] << newLine
<< " #include " << paths[i] << newLine;
}
out << "#else" << newLine
<< " #error \"This file is designed to be used in an Introjucer-generated project!\"" << newLine
<< "#endif" << newLine;
}
}
void LibraryModule::createLocalHeaderWrapper (ProjectSaver& projectSaver, const File& originalHeader, const File& localHeader) const
{
Project& project = projectSaver.project;
MemoryOutputStream out;
out << "// This is an auto-generated file to redirect any included" << newLine
<< "// module headers to the correct external folder." << newLine
<< newLine;
StringArray paths, guards;
for (Project::ExporterIterator exporter (project); exporter.next();)
{
const RelativePath headerFromProject (exporter->getModuleFolderRelativeToProject (getID(), projectSaver)
.getChildFile (originalHeader.getFileName()));
const RelativePath fileFromHere (headerFromProject.rebased (project.getProjectFolder(),
localHeader.getParentDirectory(), RelativePath::unknown));
paths.add (fileFromHere.toUnixStyle().quoted());
guards.add ("defined (" + exporter->getExporterIdentifierMacro() + ")");
}
writeGuardedInclude (out, paths, guards);
out << newLine;
projectSaver.replaceFileIfDifferent (localHeader, out);
}
//==============================================================================
void LibraryModule::prepareExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const
{
Project& project = exporter.getProject();
exporter.addToExtraSearchPaths (exporter.getModuleFolderRelativeToProject (getID(), projectSaver).getParentDirectory());
const String extraDefs (moduleInfo.getPreprocessorDefs().trim());
if (extraDefs.isNotEmpty())
exporter.getExporterPreprocessorDefs() = exporter.getExporterPreprocessorDefsString() + "\n" + extraDefs;
{
Array<File> compiled;
const File localModuleFolder = project.getModules().shouldCopyModuleFilesLocally (getID()).getValue()
? projectSaver.getLocalModuleFolder (getID())
: moduleInfo.getFolder();
findAndAddCompiledCode (exporter, projectSaver, localModuleFolder, compiled);
if (project.getModules().shouldShowAllModuleFilesInProject (getID()).getValue())
addBrowsableCode (exporter, projectSaver, compiled, localModuleFolder);
}
if (isVSTPluginHost (project)) VSTHelpers::addVSTFolderToPath (exporter, false);
if (isVST3PluginHost (project)) VSTHelpers::addVSTFolderToPath (exporter, true);
if (exporter.isXcode())
{
if (isAUPluginHost (project))
exporter.xcodeFrameworks.addTokens ("AudioUnit CoreAudioKit", false);
const String frameworks (moduleInfo.moduleInfo [exporter.isOSX() ? "OSXFrameworks" : "iOSFrameworks"].toString());
exporter.xcodeFrameworks.addTokens (frameworks, ", ", StringRef());
}
else if (exporter.isLinux())
{
const String libs (moduleInfo.moduleInfo ["LinuxLibs"].toString());
exporter.linuxLibs.addTokens (libs, ", ", StringRef());
exporter.linuxLibs.trim();
exporter.linuxLibs.sort (false);
exporter.linuxLibs.removeDuplicates (false);
}
else if (exporter.isCodeBlocks())
{
const String libs (moduleInfo.moduleInfo ["mingwLibs"].toString());
exporter.mingwLibs.addTokens (libs, ", ", StringRef());
exporter.mingwLibs.trim();
exporter.mingwLibs.sort (false);
exporter.mingwLibs.removeDuplicates (false);
}
if (moduleInfo.isPluginClient())
{
if (shouldBuildVST (project).getValue()) VSTHelpers::prepareExporter (exporter, projectSaver, false);
if (shouldBuildVST3 (project).getValue()) VSTHelpers::prepareExporter (exporter, projectSaver, true);
if (shouldBuildAU (project).getValue()) AUHelpers::prepareExporter (exporter, projectSaver);
if (shouldBuildAAX (project).getValue()) AAXHelpers::prepareExporter (exporter, projectSaver);
if (shouldBuildRTAS (project).getValue()) RTASHelpers::prepareExporter (exporter, projectSaver);
}
}
void LibraryModule::createPropertyEditors (ProjectExporter& exporter, PropertyListBuilder& props) const
{
if (isVSTPluginHost (exporter.getProject())
&& ! (moduleInfo.isPluginClient() && shouldBuildVST (exporter.getProject()).getValue()))
VSTHelpers::createVSTPathEditor (exporter, props, false);
if (isVST3PluginHost (exporter.getProject())
&& ! (moduleInfo.isPluginClient() && shouldBuildVST3 (exporter.getProject()).getValue()))
VSTHelpers::createVSTPathEditor (exporter, props, true);
if (moduleInfo.isPluginClient())
{
if (shouldBuildVST (exporter.getProject()).getValue()) VSTHelpers::createPropertyEditors (exporter, props, false);
if (shouldBuildVST3 (exporter.getProject()).getValue()) VSTHelpers::createPropertyEditors (exporter, props, true);
if (shouldBuildRTAS (exporter.getProject()).getValue()) RTASHelpers::createPropertyEditors (exporter, props);
if (shouldBuildAAX (exporter.getProject()).getValue()) AAXHelpers::createPropertyEditors (exporter, props);
}
}
void LibraryModule::getConfigFlags (Project& project, OwnedArray<Project::ConfigFlag>& flags) const
{
const File header (getModuleHeaderFile (moduleInfo.getFolder()));
jassert (header.exists());
StringArray lines;
header.readLines (lines);
for (int i = 0; i < lines.size(); ++i)
{
String line (lines[i].trim());
if (line.startsWith ("/**") && line.containsIgnoreCase ("Config:"))
{
ScopedPointer <Project::ConfigFlag> config (new Project::ConfigFlag());
config->sourceModuleID = getID();
config->symbol = line.fromFirstOccurrenceOf (":", false, false).trim();
if (config->symbol.length() > 2)
{
++i;
while (! (lines[i].contains ("*/") || lines[i].contains ("@see")))
{
if (lines[i].trim().isNotEmpty())
config->description = config->description.trim() + " " + lines[i].trim();
++i;
}
config->description = config->description.upToFirstOccurrenceOf ("*/", false, false);
config->value.referTo (project.getConfigFlag (config->symbol));
flags.add (config.release());
}
}
}
}
//==============================================================================
static bool exporterTargetMatches (const String& test, String target)
{
StringArray validTargets;
validTargets.addTokens (target, ",;", "");
validTargets.trim();
validTargets.removeEmptyStrings();
if (validTargets.size() == 0)
return true;
for (int i = validTargets.size(); --i >= 0;)
{
const String& targetName = validTargets[i];
if (targetName == test
|| (targetName.startsWithChar ('!') && test != targetName.substring (1).trimStart()))
return true;
}
return false;
}
struct FileSorter
{
static int compareElements (const File& f1, const File& f2)
{
return f1.getFileName().compareNatural (f2.getFileName());
}
};
void LibraryModule::findWildcardMatches (const File& localModuleFolder, const String& wildcardPath, Array<File>& result) const
{
String path (wildcardPath.upToLastOccurrenceOf ("/", false, false));
String wildCard (wildcardPath.fromLastOccurrenceOf ("/", false, false));
Array<File> tempList;
FileSorter sorter;
DirectoryIterator iter (localModuleFolder.getChildFile (path), false, wildCard);
bool isHiddenFile;
while (iter.next (nullptr, &isHiddenFile, nullptr, nullptr, nullptr, nullptr))
if (! isHiddenFile)
tempList.addSorted (sorter, iter.getFile());
result.addArray (tempList);
}
static bool fileTargetMatches (ProjectExporter& exporter, const String& target)
{
if (exporter.isXcode()) return exporterTargetMatches ("xcode", target);
if (exporter.isWindows()) return exporterTargetMatches ("msvc", target);
if (exporter.isLinux()) return exporterTargetMatches ("linux", target);
if (exporter.isAndroid()) return exporterTargetMatches ("android", target);
if (exporter.isCodeBlocks()) return exporterTargetMatches ("mingw", target);
return target.isEmpty();
}
static bool fileShouldBeAdded (ProjectExporter& exporter, const var& properties)
{
if (! fileTargetMatches (exporter, properties["target"].toString()))
return false;
if (properties["RTASOnly"] && ! shouldBuildRTAS (exporter.getProject()).getValue())
return false;
if (properties["AudioUnitOnly"] && ! shouldBuildAU (exporter.getProject()).getValue())
return false;
return true;
}
void LibraryModule::findAndAddCompiledCode (ProjectExporter& exporter, ProjectSaver& projectSaver,
const File& localModuleFolder, Array<File>& result) const
{
const var compileArray (moduleInfo.moduleInfo ["compile"]); // careful to keep this alive while the array is in use!
if (const Array<var>* const files = compileArray.getArray())
{
for (int i = 0; i < files->size(); ++i)
{
const var& file = files->getReference(i);
const String filename (file ["file"].toString());
if (filename.isNotEmpty() && fileShouldBeAdded (exporter, file))
{
const File compiledFile (localModuleFolder.getChildFile (filename));
result.add (compiledFile);
Project::Item item (projectSaver.addFileToGeneratedGroup (compiledFile));
if (file ["warnings"].toString().equalsIgnoreCase ("disabled"))
item.getShouldInhibitWarningsValue() = true;
if (file ["stdcall"])
item.getShouldUseStdCallValue() = true;
}
}
}
}
static void addFileWithGroups (Project::Item& group, const RelativePath& file, const String& path)
{
const int slash = path.indexOfChar (File::separator);
if (slash >= 0)
{
const String topLevelGroup (path.substring (0, slash));
const String remainingPath (path.substring (slash + 1));
Project::Item newGroup (group.getOrCreateSubGroup (topLevelGroup));
addFileWithGroups (newGroup, file, remainingPath);
}
else
{
if (! group.containsChildForFile (file))
group.addRelativeFile (file, -1, false);
}
}
void LibraryModule::findBrowseableFiles (const File& localModuleFolder, Array<File>& filesFound) const
{
const var filesArray (moduleInfo.moduleInfo ["browse"]);
if (const Array<var>* const files = filesArray.getArray())
for (int i = 0; i < files->size(); ++i)
findWildcardMatches (localModuleFolder, files->getReference(i), filesFound);
}
void LibraryModule::addBrowsableCode (ProjectExporter& exporter, ProjectSaver& projectSaver,
const Array<File>& compiled, const File& localModuleFolder) const
{
if (sourceFiles.size() == 0)
findBrowseableFiles (localModuleFolder, sourceFiles);
Project::Item sourceGroup (Project::Item::createGroup (exporter.getProject(), getID(), "__mainsourcegroup" + getID()));
const RelativePath moduleFromProject (exporter.getModuleFolderRelativeToProject (getID(), projectSaver));
for (int i = 0; i < sourceFiles.size(); ++i)
{
const String pathWithinModule (FileHelpers::getRelativePathFrom (sourceFiles.getReference(i), localModuleFolder));
// (Note: in exporters like MSVC we have to avoid adding the same file twice, even if one of those instances
// is flagged as being excluded from the build, because this overrides the other and it fails to compile)
if (exporter.canCopeWithDuplicateFiles() || ! compiled.contains (sourceFiles.getReference(i)))
addFileWithGroups (sourceGroup,
moduleFromProject.getChildFile (pathWithinModule),
pathWithinModule);
}
sourceGroup.addFile (localModuleFolder.getChildFile (FileHelpers::getRelativePathFrom (moduleInfo.manifestFile,
moduleInfo.getFolder())), -1, false);
sourceGroup.addFile (getModuleHeaderFile (localModuleFolder), -1, false);
exporter.getModulesGroup().state.addChild (sourceGroup.state.createCopy(), -1, nullptr);
}
//==============================================================================
EnabledModuleList::EnabledModuleList (Project& p, const ValueTree& s)
: project (p), state (s)
{
}
ModuleDescription EnabledModuleList::getModuleInfo (const String& moduleID)
{
return ModuleDescription (getModuleInfoFile (moduleID));
}
bool EnabledModuleList::isModuleEnabled (const String& moduleID) const
{
for (int i = 0; i < state.getNumChildren(); ++i)
if (state.getChild(i) [Ids::ID] == moduleID)
return true;
return false;
}
bool EnabledModuleList::isAudioPluginModuleMissing() const
{
return project.getProjectType().isAudioPlugin()
&& ! isModuleEnabled ("juce_audio_plugin_client");
}
Value EnabledModuleList::shouldShowAllModuleFilesInProject (const String& moduleID)
{
return state.getChildWithProperty (Ids::ID, moduleID)
.getPropertyAsValue (Ids::showAllCode, getUndoManager());
}
File EnabledModuleList::findLocalModuleInfoFile (const String& moduleID, bool useExportersForOtherOSes)
{
for (Project::ExporterIterator exporter (project); exporter.next();)
{
if (useExportersForOtherOSes || exporter->mayCompileOnCurrentOS())
{
const String path (exporter->getPathForModuleString (moduleID));
if (path.isNotEmpty())
{
const File moduleFolder (project.resolveFilename (path));
if (moduleFolder.exists())
{
File f (moduleFolder.getChildFile (ModuleDescription::getManifestFileName()));
if (f.exists())
return f;
f = moduleFolder.getChildFile (moduleID)
.getChildFile (ModuleDescription::getManifestFileName());
if (f.exists())
return f;
f = moduleFolder.getChildFile ("modules")
.getChildFile (moduleID)
.getChildFile (ModuleDescription::getManifestFileName());
if (f.exists())
return f;
}
}
}
}
return File::nonexistent;
}
File EnabledModuleList::getModuleInfoFile (const String& moduleID)
{
const File f (findLocalModuleInfoFile (moduleID, false));
if (f != File::nonexistent)
return f;
return findLocalModuleInfoFile (moduleID, true);
}
File EnabledModuleList::getModuleFolder (const String& moduleID)
{
const File infoFile (getModuleInfoFile (moduleID));
return infoFile.exists() ? infoFile.getParentDirectory()
: File::nonexistent;
}
struct ModuleTreeSorter
{
static int compareElements (const ValueTree& m1, const ValueTree& m2)
{
return m1[Ids::ID].toString().compareIgnoreCase (m2[Ids::ID]);
}
};
void EnabledModuleList::sortAlphabetically()
{
ModuleTreeSorter sorter;
state.sort (sorter, getUndoManager(), false);
}
Value EnabledModuleList::shouldCopyModuleFilesLocally (const String& moduleID) const
{
return state.getChildWithProperty (Ids::ID, moduleID)
.getPropertyAsValue (Ids::useLocalCopy, getUndoManager());
}
void EnabledModuleList::addModule (const File& moduleManifestFile, bool copyLocally)
{
ModuleDescription info (moduleManifestFile);
if (info.isValid())
{
const String moduleID (info.getID());
if (! isModuleEnabled (moduleID))
{
ValueTree module (Ids::MODULES);
module.setProperty (Ids::ID, moduleID, nullptr);
state.addChild (module, -1, getUndoManager());
sortAlphabetically();
shouldShowAllModuleFilesInProject (moduleID) = true;
shouldCopyModuleFilesLocally (moduleID) = copyLocally;
RelativePath path (moduleManifestFile.getParentDirectory().getParentDirectory(),
project.getProjectFolder(), RelativePath::projectFolder);
for (Project::ExporterIterator exporter (project); exporter.next();)
exporter->getPathForModuleValue (moduleID) = path.toUnixStyle();
}
}
}
void EnabledModuleList::removeModule (String moduleID) // must be pass-by-value, and not a const ref!
{
for (int i = state.getNumChildren(); --i >= 0;)
if (state.getChild(i) [Ids::ID] == moduleID)
state.removeChild (i, getUndoManager());
for (Project::ExporterIterator exporter (project); exporter.next();)
exporter->removePathForModule (moduleID);
}
void EnabledModuleList::createRequiredModules (OwnedArray<LibraryModule>& modules)
{
for (int i = 0; i < getNumModules(); ++i)
{
ModuleDescription info (getModuleInfo (getModuleID (i)));
if (info.isValid())
modules.add (new LibraryModule (info));
}
}
StringArray EnabledModuleList::getAllModules() const
{
StringArray moduleIDs;
for (int i = 0; i < getNumModules(); ++i)
moduleIDs.add (getModuleID(i));
return moduleIDs;
}
static void getDependencies (Project& project, const String& moduleID, StringArray& dependencies)
{
ModuleDescription info (project.getModules().getModuleInfo (moduleID));
if (info.isValid())
{
const var depsArray (info.moduleInfo ["dependencies"]);
if (const Array<var>* const deps = depsArray.getArray())
{
for (int i = 0; i < deps->size(); ++i)
{
const var& d = deps->getReference(i);
String uid (d [Ids::ID].toString());
String version (d [Ids::version].toString());
if (! dependencies.contains (uid, true))
{
dependencies.add (uid);
getDependencies (project, uid, dependencies);
}
}
}
}
}
StringArray EnabledModuleList::getExtraDependenciesNeeded (const String& moduleID) const
{
StringArray dependencies, extraDepsNeeded;
getDependencies (project, moduleID, dependencies);
for (int i = 0; i < dependencies.size(); ++i)
if ((! isModuleEnabled (dependencies[i])) && dependencies[i] != moduleID)
extraDepsNeeded.add (dependencies[i]);
return extraDepsNeeded;
}
bool EnabledModuleList::areMostModulesCopiedLocally() const
{
int numYes = 0, numNo = 0;
for (int i = getNumModules(); --i >= 0;)
{
if (shouldCopyModuleFilesLocally (getModuleID (i)).getValue())
++numYes;
else
++numNo;
}
return numYes > numNo;
}
void EnabledModuleList::setLocalCopyModeForAllModules (bool copyLocally)
{
for (int i = getNumModules(); --i >= 0;)
shouldCopyModuleFilesLocally (project.getModules().getModuleID (i)) = copyLocally;
}
File EnabledModuleList::findDefaultModulesFolder (Project& project)
{
ModuleList available;
available.scanAllKnownFolders (project);
for (int i = available.modules.size(); --i >= 0;)
{
File f (available.modules.getUnchecked(i)->getFolder());
if (f.isDirectory())
return f.getParentDirectory();
}
return File::getCurrentWorkingDirectory();
}
void EnabledModuleList::addModuleFromUserSelectedFile()
{
static File lastLocation (findDefaultModulesFolder (project));
FileChooser fc ("Select a module to add...", lastLocation, String::empty, false);
if (fc.browseForDirectory())
{
lastLocation = fc.getResult();
addModuleOfferingToCopy (lastLocation);
}
}
void EnabledModuleList::addModuleInteractive (const String& moduleID)
{
ModuleList list;
list.scanAllKnownFolders (project);
if (const ModuleDescription* info = list.getModuleWithID (moduleID))
addModule (info->manifestFile, areMostModulesCopiedLocally());
else
addModuleFromUserSelectedFile();
}
void EnabledModuleList::addModuleOfferingToCopy (const File& f)
{
ModuleDescription m (f);
if (! m.isValid())
m = ModuleDescription (f.getChildFile (ModuleDescription::getManifestFileName()));
if (! m.isValid())
{
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
"Add Module", "This wasn't a valid module folder!");
return;
}
if (isModuleEnabled (m.getID()))
{
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
"Add Module", "The project already contains this module!");
return;
}
addModule (m.manifestFile, areMostModulesCopiedLocally());
}
bool isJuceFolder (const File& f)
{
return isJuceModulesFolder (f.getChildFile ("modules"));
}
bool isJuceModulesFolder (const File& f)
{
return f.isDirectory() && f.getChildFile ("juce_core").isDirectory();
}
@@ -0,0 +1,169 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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.
==============================================================================
*/
#ifndef __JUCER_MODULE_JUCEHEADER__
#define __JUCER_MODULE_JUCEHEADER__
#include "../jucer_Headers.h"
#include "jucer_Project.h"
class ProjectExporter;
class ProjectSaver;
//==============================================================================
File findDefaultModulesFolder (bool mustContainJuceCoreModule = true);
bool isJuceModulesFolder (const File&);
bool isJuceFolder (const File&);
//==============================================================================
struct ModuleDescription
{
ModuleDescription() {}
ModuleDescription (const File& manifest);
ModuleDescription (const var& info) : moduleInfo (info) {}
bool isValid() const { return getID().isNotEmpty(); }
String getID() const { return moduleInfo [Ids::ID].toString(); }
String getVersion() const { return moduleInfo [Ids::version].toString(); }
String getName() const { return moduleInfo [Ids::name].toString(); }
String getDescription() const { return moduleInfo [Ids::description].toString(); }
String getLicense() const { return moduleInfo [Ids::license].toString(); }
String getHeaderName() const { return moduleInfo [Ids::include].toString(); }
String getPreprocessorDefs() const { return moduleInfo [Ids::defines].toString(); }
File getFolder() const { jassert (manifestFile != File::nonexistent); return manifestFile.getParentDirectory(); }
bool isPluginClient() const { return getID() == "juce_audio_plugin_client"; }
static const char* getManifestFileName() { return "juce_module_info"; }
var moduleInfo;
File manifestFile;
URL url;
};
//==============================================================================
struct ModuleList
{
ModuleList();
ModuleList (const ModuleList&);
ModuleList& operator= (const ModuleList&);
const ModuleDescription* getModuleWithID (const String& moduleID) const;
StringArray getIDs() const;
void sort();
Result addAllModulesInFolder (const File&);
Result scanAllKnownFolders (Project&);
bool loadFromWebsite();
OwnedArray<ModuleDescription> modules;
};
//==============================================================================
class LibraryModule
{
public:
LibraryModule (const ModuleDescription&);
bool isValid() const { return moduleInfo.isValid(); }
String getID() const { return moduleInfo.getID(); }
String getVersion() const { return moduleInfo.getVersion(); }
String getName() const { return moduleInfo.getName(); }
String getDescription() const { return moduleInfo.getDescription(); }
String getLicense() const { return moduleInfo.getLicense(); }
File getFolder() const { return moduleInfo.getFolder(); }
void writeIncludes (ProjectSaver&, OutputStream&);
void prepareExporter (ProjectExporter&, ProjectSaver&) const;
void createPropertyEditors (ProjectExporter&, PropertyListBuilder&) const;
void getConfigFlags (Project&, OwnedArray<Project::ConfigFlag>& flags) const;
void findBrowseableFiles (const File& localModuleFolder, Array<File>& files) const;
ModuleDescription moduleInfo;
private:
mutable Array<File> sourceFiles;
File getModuleHeaderFile (const File& folder) const;
void findWildcardMatches (const File& localModuleFolder, const String& wildcardPath, Array<File>& result) const;
void findAndAddCompiledCode (ProjectExporter&, ProjectSaver&, const File& localModuleFolder, Array<File>& result) const;
void addBrowsableCode (ProjectExporter&, ProjectSaver&, const Array<File>& compiled, const File& localModuleFolder) const;
void createLocalHeaderWrapper (ProjectSaver&, const File& originalHeader, const File& localHeader) const;
bool isAUPluginHost (const Project&) const;
bool isVSTPluginHost (const Project&) const;
bool isVST3PluginHost (const Project&) const;
};
//==============================================================================
class EnabledModuleList
{
public:
EnabledModuleList (Project&, const ValueTree&);
bool isModuleEnabled (const String& moduleID) const;
Value shouldShowAllModuleFilesInProject (const String& moduleID);
Value shouldCopyModuleFilesLocally (const String& moduleID) const;
void removeModule (String moduleID);
bool isAudioPluginModuleMissing() const;
ModuleDescription getModuleInfo (const String& moduleID);
File getModuleInfoFile (const String& moduleID);
File getModuleFolder (const String& moduleID);
void addModule (const File& moduleManifestFile, bool copyLocally);
void addModuleInteractive (const String& moduleID);
void addModuleFromUserSelectedFile();
void addModuleOfferingToCopy (const File&);
StringArray getAllModules() const;
StringArray getExtraDependenciesNeeded (const String& moduleID) const;
void createRequiredModules (OwnedArray<LibraryModule>& modules);
int getNumModules() const { return state.getNumChildren(); }
String getModuleID (int index) const { return state.getChild (index) [Ids::ID].toString(); }
bool areMostModulesCopiedLocally() const;
void setLocalCopyModeForAllModules (bool copyLocally);
void sortAlphabetically();
static File findDefaultModulesFolder (Project&);
Project& project;
ValueTree state;
private:
UndoManager* getUndoManager() const { return project.getUndoManagerFor (state); }
File findLocalModuleInfoFile (const String& moduleID, bool useExportersForOtherOSes);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModuleList)
};
#endif // __JUCER_MODULE_JUCEHEADER__
@@ -0,0 +1,541 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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 ModulesPanel : public Component,
private TableListBoxModel,
private ValueTree::Listener,
private Button::Listener
{
public:
ModulesPanel (Project& p)
: project (p),
modulesValueTree (p.getModules().state),
addWebModuleButton ("Download and add a module..."),
updateModuleButton ("Install updates to modules..."),
setCopyModeButton ("Set copy-mode for all modules..."),
copyPathButton ("Set paths for all modules...")
{
table.getHeader().addColumn ("Module", nameCol, 180, 100, 400, TableHeaderComponent::notSortable);
table.getHeader().addColumn ("Installed Version", versionCol, 100, 100, 100, TableHeaderComponent::notSortable);
table.getHeader().addColumn ("Available Version", updateCol, 100, 100, 100, TableHeaderComponent::notSortable);
table.getHeader().addColumn ("Make Local Copy", copyCol, 100, 100, 100, TableHeaderComponent::notSortable);
table.getHeader().addColumn ("Paths", pathCol, 250, 100, 600, TableHeaderComponent::notSortable);
table.setModel (this);
table.setColour (TableListBox::backgroundColourId, Colours::transparentBlack);
addAndMakeVisible (table);
table.updateContent();
table.setRowHeight (20);
addAndMakeVisible (addWebModuleButton);
addAndMakeVisible (updateModuleButton);
addAndMakeVisible (setCopyModeButton);
addAndMakeVisible (copyPathButton);
addWebModuleButton.addListener (this);
updateModuleButton.addListener (this);
updateModuleButton.setEnabled (false);
setCopyModeButton.addListener (this);
setCopyModeButton.setTriggeredOnMouseDown (true);
copyPathButton.addListener (this);
copyPathButton.setTriggeredOnMouseDown (true);
modulesValueTree.addListener (this);
lookAndFeelChanged();
}
void paint (Graphics& g) override
{
if (webUpdateThread == nullptr)
webUpdateThread = new WebsiteUpdateFetchThread (*this);
IntrojucerLookAndFeel::fillWithBackgroundTexture (*this, g);
}
void resized() override
{
Rectangle<int> r (getLocalBounds().reduced (5, 4));
table.setBounds (r.removeFromTop (table.getRowPosition (getNumRows() - 1, true).getBottom() + 20));
Rectangle<int> buttonRow (r.removeFromTop (32).removeFromBottom (28));
addWebModuleButton.setBounds (buttonRow.removeFromLeft (jmin (260, r.getWidth() / 3)));
buttonRow.removeFromLeft (8);
updateModuleButton.setBounds (buttonRow.removeFromLeft (jmin (260, r.getWidth() / 3)));
buttonRow.removeFromLeft (8);
buttonRow = r.removeFromTop (34).removeFromBottom (28);
setCopyModeButton.setBounds (buttonRow.removeFromLeft (jmin (260, r.getWidth() / 3)));
buttonRow.removeFromLeft (8);
copyPathButton.setBounds (buttonRow.removeFromLeft (jmin (260, r.getWidth() / 3)));
}
int getNumRows() override
{
return project.getModules().getNumModules();
}
void paintRowBackground (Graphics& g, int /*rowNumber*/, int width, int height, bool rowIsSelected) override
{
g.setColour (rowIsSelected ? Colours::lightblue.withAlpha (0.4f)
: Colours::white.withAlpha (0.4f));
g.fillRect (0, 0, width, height - 1);
}
void paintCell (Graphics& g, int rowNumber, int columnId, int width, int height, bool /*rowIsSelected*/) override
{
String text;
const String moduleID (project.getModules().getModuleID (rowNumber));
if (columnId == nameCol)
{
text = moduleID;
}
else if (columnId == versionCol)
{
text = project.getModules().getModuleInfo (moduleID).getVersion();
if (text.isEmpty())
text = "?";
}
else if (columnId == updateCol)
{
if (listFromWebsite != nullptr)
{
if (const ModuleDescription* m = listFromWebsite->getModuleWithID (moduleID))
{
if (m->getVersion() != project.getModules().getModuleInfo (moduleID).getVersion())
text = m->getVersion() + " available";
else
text = "Up-to-date";
}
else
text = "?";
}
else
{
text = "-";
}
}
else if (columnId == copyCol)
{
text = project.getModules().shouldCopyModuleFilesLocally (moduleID).getValue()
? "Yes" : "No";
}
else if (columnId == pathCol)
{
StringArray paths;
for (Project::ExporterIterator exporter (project); exporter.next();)
paths.addIfNotAlreadyThere (exporter->getPathForModuleString (moduleID).trim());
text = paths.joinIntoString (", ");
}
g.setColour (Colours::black);
g.setFont (height * 0.65f);
g.drawText (text, Rectangle<int> (width, height).reduced (4, 0), Justification::centredLeft, true);
}
void cellDoubleClicked (int rowNumber, int, const MouseEvent&) override
{
const String moduleID (project.getModules().getModuleID (rowNumber));
if (moduleID.isNotEmpty())
if (ProjectContentComponent* pcc = findParentComponentOfClass<ProjectContentComponent>())
pcc->showModule (moduleID);
}
void deleteKeyPressed (int row) override
{
project.getModules().removeModule (project.getModules().getModuleID (row));
}
void webUpdateFinished (const ModuleList& newList)
{
listFromWebsite = new ModuleList (newList);
table.updateContent();
table.repaint();
updateModuleButton.setEnabled (getUpdatableModules().size() != 0);
}
void buttonClicked (Button* b)
{
if (b == &addWebModuleButton) showAddModuleMenu();
else if (b == &updateModuleButton) showUpdateModulesMenu();
else if (b == &setCopyModeButton) showCopyModeMenu();
else if (b == &copyPathButton) showSetPathsMenu();
}
private:
enum
{
nameCol = 1,
versionCol,
updateCol,
copyCol,
pathCol
};
Project& project;
ValueTree modulesValueTree;
TableListBox table;
TextButton addWebModuleButton, updateModuleButton, setCopyModeButton, copyPathButton;
ScopedPointer<ModuleList> listFromWebsite;
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { itemChanged(); }
void valueTreeChildAdded (ValueTree&, ValueTree&) override { itemChanged(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&) override { itemChanged(); }
void valueTreeChildOrderChanged (ValueTree&) override { itemChanged(); }
void valueTreeParentChanged (ValueTree&) override { itemChanged(); }
void itemChanged()
{
table.updateContent();
resized();
repaint();
}
StringArray getUpdatableModules() const
{
StringArray result;
if (listFromWebsite != nullptr)
{
for (int i = 0; i < listFromWebsite->modules.size(); ++i)
{
const ModuleDescription* m = listFromWebsite->modules.getUnchecked(i);
const String v1 (m->getVersion());
const String v2 (project.getModules().getModuleInfo (m->getID()).getVersion());
if (v1 != v2 && v1.isNotEmpty() && v2.isNotEmpty())
result.add (m->getID());
}
}
return result;
}
StringArray getAddableModules() const
{
StringArray result;
if (listFromWebsite != nullptr)
{
for (int i = 0; i < listFromWebsite->modules.size(); ++i)
{
const ModuleDescription* m = listFromWebsite->modules.getUnchecked(i);
if (! project.getModules().isModuleEnabled (m->getID()))
result.add (m->getID());
}
}
return result;
}
void showUpdateModulesMenu()
{
StringArray mods (getUpdatableModules());
PopupMenu m;
m.addItem (1000, "Update all modules");
m.addSeparator();
for (int i = 0; i < mods.size(); ++i)
m.addItem (1 + i, "Update " + mods[i]);
int res = m.showAt (&updateModuleButton);
if (res > 0 && listFromWebsite != nullptr)
{
if (res != 1000)
mods = StringArray (mods[res - 1]);
Array<ModuleDescription> modsToUpdate;
for (int i = 0; i < mods.size(); ++i)
{
if (const ModuleDescription* md = listFromWebsite->getModuleWithID (mods[i]))
{
ModuleDescription modToUpdate (*md);
modToUpdate.manifestFile = project.getModules().getModuleInfo (modToUpdate.getID()).manifestFile;
modsToUpdate.add (modToUpdate);
}
}
DownloadAndInstallThread::updateModulesFromWeb (project, modsToUpdate);
}
}
void showAddModuleMenu()
{
if (listFromWebsite == nullptr)
{
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"Couldn't contact the website!",
"Failed to get the latest module list from juce.com - "
"maybe network or server problems - try again soon!");
return;
}
StringArray mods (getAddableModules());
if (mods.size() == 0)
{
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"No modules to add!",
"Couldn't find any new modules that aren't already in your project!");
return;
}
PopupMenu m;
for (int i = 0; i < mods.size(); ++i)
m.addItem (i + 1, "Install " + mods[i]);
int res = m.showAt (&addWebModuleButton);
if (res > 0 && listFromWebsite != nullptr)
if (const ModuleDescription* md = listFromWebsite->getModuleWithID (mods[res - 1]))
DownloadAndInstallThread::addModuleFromWebsite (project, *md);
}
void showCopyModeMenu()
{
PopupMenu m;
m.addItem (1, "Set all modules to copy locally");
m.addItem (2, "Set all modules to not copy locally");
int res = m.showAt (&setCopyModeButton);
if (res != 0)
project.getModules().setLocalCopyModeForAllModules (res == 1);
}
void showSetPathsMenu()
{
EnabledModuleList& moduleList = project.getModules();
const String moduleToCopy (moduleList.getModuleID (table.getSelectedRow()));
if (moduleToCopy.isNotEmpty())
{
PopupMenu m;
m.addItem (1, "Copy the paths from the module '" + moduleToCopy + "' to all other modules");
int res = m.showAt (&copyPathButton);
if (res != 0)
{
for (Project::ExporterIterator exporter (project); exporter.next();)
{
for (int i = 0; i < moduleList.getNumModules(); ++i)
{
String modID = moduleList.getModuleID (i);
if (modID != moduleToCopy)
exporter->getPathForModuleValue (modID) = exporter->getPathForModuleValue (moduleToCopy).getValue();
}
}
}
table.repaint();
}
else
{
PopupMenu m;
m.addItem (1, "Copy the paths from the selected module to all other modules", false);
m.showAt (&copyPathButton);
}
}
struct WebsiteUpdateFetchThread : private Thread,
private AsyncUpdater
{
WebsiteUpdateFetchThread (ModulesPanel& p) : Thread ("Web Updater"), panel (p)
{
startThread (3);
}
~WebsiteUpdateFetchThread()
{
stopThread (15000);
}
void run() override
{
static Time lastDownloadTime;
static ModuleList lastList;
if (Time::getCurrentTime() < lastDownloadTime + RelativeTime::minutes (2.0))
{
list = lastList;
triggerAsyncUpdate();
}
else
{
if (list.loadFromWebsite() && ! threadShouldExit())
{
lastList = list;
lastDownloadTime = Time::getCurrentTime();
triggerAsyncUpdate();
}
}
}
void handleAsyncUpdate() override
{
panel.webUpdateFinished (list);
}
private:
ModuleList list;
ModulesPanel& panel;
};
ScopedPointer<WebsiteUpdateFetchThread> webUpdateThread;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModulesPanel)
};
//==============================================================================
class DownloadAndInstallThread : public ThreadWithProgressWindow
{
public:
DownloadAndInstallThread (const Array<ModuleDescription>& modulesToInstall)
: ThreadWithProgressWindow ("Installing New Modules", true, true),
result (Result::ok()),
modules (modulesToInstall)
{
}
static void updateModulesFromWeb (Project& project, const Array<ModuleDescription>& mods)
{
DownloadAndInstallThread d (mods);
if (d.runThread())
{
if (d.result.failed())
{
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"Module Install Failed",
d.result.getErrorMessage());
}
else
{
for (int i = 0; i < d.modules.size(); ++i)
project.getModules().addModule (d.modules.getReference(i).manifestFile,
project.getModules().areMostModulesCopiedLocally());
}
}
}
static void addModuleFromWebsite (Project& project, const ModuleDescription& module)
{
Array<ModuleDescription> mods;
mods.add (module);
static File lastLocation (EnabledModuleList::findDefaultModulesFolder (project));
FileChooser fc ("Select the parent folder for the new module...", lastLocation, String::empty, false);
if (fc.browseForDirectory())
{
lastLocation = fc.getResult();
if (lastLocation.getChildFile (ModuleDescription::getManifestFileName()).exists())
{
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"Adding Module",
"You chose a folder that appears to be a module.\n\n"
"You need to select the *parent* folder inside which the new modules will be created.");
return;
}
for (int i = 0; i < mods.size(); ++i)
mods.getReference(i).manifestFile = lastLocation.getChildFile (mods.getReference(i).getID())
.getChildFile (ModuleDescription::getManifestFileName());
updateModulesFromWeb (project, mods);
}
}
void run() override
{
for (int i = 0; i < modules.size(); ++i)
{
const ModuleDescription& m = modules.getReference(i);
setProgress (i / (double) modules.size());
MemoryBlock downloaded;
result = download (m, downloaded);
if (result.failed() || threadShouldExit())
break;
result = unzip (m, downloaded);
if (result.failed() || threadShouldExit())
break;
}
}
Result download (const ModuleDescription& m, MemoryBlock& dest)
{
setStatusMessage ("Downloading " + m.getID() + "...");
const ScopedPointer<InputStream> in (m.url.createInputStream (false, nullptr, nullptr, String::empty, 10000));
if (in != nullptr && in->readIntoMemoryBlock (dest))
return Result::ok();
return Result::fail ("Failed to download from: " + m.url.toString (false));
}
Result unzip (const ModuleDescription& m, const MemoryBlock& data)
{
setStatusMessage ("Installing " + m.getID() + "...");
MemoryInputStream input (data, false);
ZipFile zip (input);
if (zip.getNumEntries() == 0)
return Result::fail ("The downloaded file wasn't a valid module file!");
if (! m.getFolder().deleteRecursively())
return Result::fail ("Couldn't delete the existing folder:\n" + m.getFolder().getFullPathName());
return zip.uncompressTo (m.getFolder().getParentDirectory(), true);
}
Result result;
Array<ModuleDescription> modules;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadAndInstallThread)
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,290 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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.
==============================================================================
*/
#ifndef __JUCER_PROJECT_JUCEHEADER__
#define __JUCER_PROJECT_JUCEHEADER__
#include "../jucer_Headers.h"
class ProjectExporter;
class ProjectType;
class LibraryModule;
class EnabledModuleList;
//==============================================================================
class Project : public FileBasedDocument,
public ValueTree::Listener
{
public:
//==============================================================================
Project (const File& file);
~Project();
//==============================================================================
// FileBasedDocument stuff..
String getDocumentTitle() override;
Result loadDocument (const File& file) override;
Result saveDocument (const File& file) override;
Result saveProject (const File& file, bool isCommandLineApp);
Result saveResourcesOnly (const File& file);
File getLastDocumentOpened() override;
void setLastDocumentOpened (const File& file) override;
void setTitle (const String& newTitle);
//==============================================================================
File getProjectFolder() const { return getFile().getParentDirectory(); }
ValueTree getProjectRoot() const { return projectRoot; }
String getTitle() const;
Value getProjectNameValue() { return getMainGroup().getNameValue(); }
String getProjectFilenameRoot() { return File::createLegalFileName (getDocumentTitle()); }
String getProjectUID() const { return projectRoot [Ids::ID]; }
//==============================================================================
template <class FileType>
bool shouldBeAddedToBinaryResourcesByDefault (const FileType& file)
{
return ! file.hasFileExtension (sourceOrHeaderFileExtensions);
}
File resolveFilename (String filename) const;
String getRelativePathForFile (const File& file) const;
//==============================================================================
// Creates editors for the project settings
void createPropertyEditors (PropertyListBuilder&);
//==============================================================================
// project types
const ProjectType& getProjectType() const;
Value getProjectTypeValue() { return getProjectValue (Ids::projectType); }
String getProjectTypeString() const { return projectRoot [Ids::projectType]; }
Value getVersionValue() { return getProjectValue (Ids::version); }
String getVersionString() const { return projectRoot [Ids::version]; }
String getVersionAsHex() const;
int getVersionAsHexInteger() const;
Value getBundleIdentifier() { return getProjectValue (Ids::bundleIdentifier); }
String getDefaultBundleIdentifier() { return "com.yourcompany." + CodeHelpers::makeValidIdentifier (getTitle(), false, true, false); }
Value getAAXIdentifier() { return getProjectValue (Ids::aaxIdentifier); }
String getDefaultAAXIdentifier() { return getDefaultBundleIdentifier(); }
Value getCompanyName() { return getProjectValue (Ids::companyName); }
Value getCompanyWebsite() { return getProjectValue (Ids::companyWebsite); }
Value getCompanyEmail() { return getProjectValue (Ids::companyEmail); }
//==============================================================================
Value getProjectValue (const Identifier& name) { return projectRoot.getPropertyAsValue (name, getUndoManagerFor (projectRoot)); }
Value getProjectPreprocessorDefs() { return getProjectValue (Ids::defines); }
StringPairArray getPreprocessorDefs() const;
Value getProjectUserNotes() { return getProjectValue (Ids::userNotes); }
//==============================================================================
File getGeneratedCodeFolder() const { return getFile().getSiblingFile ("JuceLibraryCode"); }
File getAppIncludeFile() const { return getGeneratedCodeFolder().getChildFile (getJuceSourceHFilename()); }
File getBinaryDataCppFile (int index) const;
File getBinaryDataHeaderFile() const { return getBinaryDataCppFile (0).withFileExtension (".h"); }
Value getMaxBinaryFileSize() { return getProjectValue (Ids::maxBinaryFileSize); }
Value shouldIncludeBinaryInAppConfig() { return getProjectValue (Ids::includeBinaryInAppConfig); }
//==============================================================================
String getAmalgamatedHeaderFileName() const { return "juce_amalgamated.h"; }
String getAmalgamatedMMFileName() const { return "juce_amalgamated.mm"; }
String getAmalgamatedCppFileName() const { return "juce_amalgamated.cpp"; }
String getAppConfigFilename() const { return "AppConfig.h"; }
String getJuceSourceFilenameRoot() const { return "JuceLibraryCode"; }
int getNumSeparateAmalgamatedFiles() const { return 4; }
String getJuceSourceHFilename() const { return "JuceHeader.h"; }
//==============================================================================
class Item
{
public:
//==============================================================================
Item (Project& project, const ValueTree& itemNode);
Item (const Item& other);
static Item createGroup (Project& project, const String& name, const String& uid);
void initialiseMissingProperties();
//==============================================================================
bool isValid() const { return state.isValid(); }
bool operator== (const Item& other) const { return state == other.state && &project == &other.project; }
bool operator!= (const Item& other) const { return ! operator== (other); }
//==============================================================================
bool isFile() const;
bool isGroup() const;
bool isMainGroup() const;
bool isImageFile() const;
String getID() const;
void setID (const String& newID);
Item findItemWithID (const String& targetId) const; // (recursive search)
String getImageFileID() const;
Drawable* loadAsImageFile() const;
//==============================================================================
Value getNameValue();
String getName() const;
File getFile() const;
String getFilePath() const;
void setFile (const File& file);
void setFile (const RelativePath& file);
File determineGroupFolder() const;
bool renameFile (const File& newFile);
bool shouldBeAddedToTargetProject() const;
bool shouldBeCompiled() const;
Value getShouldCompileValue();
bool shouldBeAddedToBinaryResources() const;
Value getShouldAddToResourceValue();
Value getShouldInhibitWarningsValue();
bool shouldInhibitWarnings() const;
Value getShouldUseStdCallValue();
bool shouldUseStdCall() const;
//==============================================================================
bool canContain (const Item& child) const;
int getNumChildren() const { return state.getNumChildren(); }
Item getChild (int index) const { return Item (project, state.getChild (index)); }
Item addNewSubGroup (const String& name, int insertIndex);
Item getOrCreateSubGroup (const String& name);
void addChild (const Item& newChild, int insertIndex);
bool addFile (const File& file, int insertIndex, bool shouldCompile);
void addFileUnchecked (const File& file, int insertIndex, bool shouldCompile);
bool addRelativeFile (const RelativePath& file, int insertIndex, bool shouldCompile);
void removeItemFromProject();
void sortAlphabetically (bool keepGroupsAtStart);
Item findItemForFile (const File& file) const;
bool containsChildForFile (const RelativePath& file) const;
Item getParent() const;
Item createCopy();
UndoManager* getUndoManager() const { return project.getUndoManagerFor (state); }
Icon getIcon() const;
bool isIconCrossedOut() const;
Project& project;
ValueTree state;
private:
Item& operator= (const Item&);
};
Item getMainGroup();
void findAllImageItems (OwnedArray<Item>& items);
//==============================================================================
ValueTree getExporters();
int getNumExporters();
ProjectExporter* createExporter (int index);
void addNewExporter (const String& exporterName);
void createExporterForCurrentPlatform();
struct ExporterIterator
{
ExporterIterator (Project& project);
~ExporterIterator();
bool next();
ProjectExporter& operator*() const { return *exporter; }
ProjectExporter* operator->() const { return exporter; }
ScopedPointer<ProjectExporter> exporter;
int index;
private:
Project& project;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterIterator)
};
//==============================================================================
struct ConfigFlag
{
String symbol, description, sourceModuleID;
Value value; // 1 = true, 2 = false, anything else = use default
};
static const char* const configFlagDefault;
static const char* const configFlagEnabled;
static const char* const configFlagDisabled;
Value getConfigFlag (const String& name);
bool isConfigFlagEnabled (const String& name) const;
//==============================================================================
EnabledModuleList& getModules();
//==============================================================================
String getFileTemplate (const String& templateName);
//==============================================================================
PropertiesFile& getStoredProperties() const;
//==============================================================================
void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
void valueTreeChildAdded (ValueTree&, ValueTree&) override;
void valueTreeChildRemoved (ValueTree&, ValueTree&) override;
void valueTreeChildOrderChanged (ValueTree&) override;
void valueTreeParentChanged (ValueTree&) override;
//==============================================================================
UndoManager* getUndoManagerFor (const ValueTree&) const { return nullptr; }
//==============================================================================
static const char* projectFileExtension;
private:
friend class Item;
ValueTree projectRoot;
ScopedPointer<EnabledModuleList> enabledModulesList;
void updateProjectSettings();
void sanitiseConfigFlags();
void setMissingDefaultValues();
ValueTree getConfigurations() const;
ValueTree getConfigNode();
void updateOldStyleConfigList();
void moveOldPropertyFromProjectToAllExporters (Identifier name);
void removeDefunctExporters();
void updateOldModulePaths();
void warnAboutOldIntrojucerVersion();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Project)
};
#endif // __JUCER_PROJECT_JUCEHEADER__
@@ -0,0 +1,937 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "jucer_ProjectContentComponent.h"
#include "jucer_Module.h"
#include "../Application/jucer_MainWindow.h"
#include "../Application/jucer_Application.h"
#include "../Code Editor/jucer_SourceCodeEditor.h"
#include "../Project Saving/jucer_ProjectExporter.h"
#include "../Utility/jucer_TranslationTool.h"
#include "../Utility/jucer_JucerTreeViewBase.h"
#include "../Wizards/jucer_NewFileWizard.h"
#include "jucer_GroupInformationComponent.h"
//==============================================================================
class FileTreePanel : public TreePanelBase
{
public:
FileTreePanel (Project& p)
: TreePanelBase (&p, "fileTreeState")
{
tree.setMultiSelectEnabled (true);
setRoot (new GroupItem (p.getMainGroup()));
}
void updateMissingFileStatuses()
{
if (ProjectTreeItemBase* p = dynamic_cast<ProjectTreeItemBase*> (rootItem.get()))
p->checkFileStatus();
}
#include "jucer_ProjectTree_Base.h"
#include "jucer_ProjectTree_Group.h"
#include "jucer_ProjectTree_File.h"
};
//==============================================================================
class ConfigTreePanel : public TreePanelBase
{
public:
ConfigTreePanel (Project& p)
: TreePanelBase (&p, "settingsTreeState")
{
tree.setMultiSelectEnabled (false);
setRoot (new RootItem (p));
if (tree.getNumSelectedItems() == 0)
tree.getRootItem()->setSelected (true, true);
#if JUCE_MAC || JUCE_WINDOWS
ApplicationCommandManager& commandManager = IntrojucerApp::getCommandManager();
addAndMakeVisible (openProjectButton);
openProjectButton.setCommandToTrigger (&commandManager, CommandIDs::openInIDE, true);
openProjectButton.setButtonText (commandManager.getNameOfCommand (CommandIDs::openInIDE));
openProjectButton.setColour (TextButton::buttonColourId, Colours::white.withAlpha (0.5f));
addAndMakeVisible (saveAndOpenButton);
saveAndOpenButton.setCommandToTrigger (&commandManager, CommandIDs::saveAndOpenInIDE, true);
saveAndOpenButton.setButtonText (commandManager.getNameOfCommand (CommandIDs::saveAndOpenInIDE));
saveAndOpenButton.setColour (TextButton::buttonColourId, Colours::white.withAlpha (0.5f));
#endif
}
void resized()
{
Rectangle<int> r (getAvailableBounds());
r.removeFromBottom (6);
if (saveAndOpenButton.isVisible())
saveAndOpenButton.setBounds (r.removeFromBottom (30).reduced (16, 4));
if (openProjectButton.isVisible())
openProjectButton.setBounds (r.removeFromBottom (30).reduced (16, 4));
tree.setBounds (r);
}
void showProjectSettings()
{
if (ConfigTreeItemBase* root = dynamic_cast<ConfigTreeItemBase*> (rootItem.get()))
if (root->isProjectSettings())
root->setSelected (true, true);
}
void showModules()
{
if (ConfigTreeItemBase* mods = getModulesItem())
mods->setSelected (true, true);
}
void showModule (const String& moduleID)
{
if (ConfigTreeItemBase* mods = getModulesItem())
{
mods->setOpen (true);
for (int i = mods->getNumSubItems(); --i >= 0;)
if (ModuleItem* m = dynamic_cast<ModuleItem*> (mods->getSubItem (i)))
if (m->moduleID == moduleID)
m->setSelected (true, true);
}
}
TextButton openProjectButton, saveAndOpenButton;
private:
#include "jucer_ConfigTree_Base.h"
#include "jucer_ConfigTree_Modules.h"
#include "jucer_ConfigTree_Exporter.h"
#include "jucer_ModulesPanel.h"
ConfigTreeItemBase* getModulesItem()
{
if (ConfigTreeItemBase* root = dynamic_cast<ConfigTreeItemBase*> (rootItem.get()))
if (root->isProjectSettings())
if (ConfigTreeItemBase* mods = dynamic_cast<ConfigTreeItemBase*> (root->getSubItem (0)))
if (mods->isModulesList())
return mods;
return nullptr;
}
};
//==============================================================================
struct LogoComponent : public Component
{
void paint (Graphics& g)
{
g.setColour (findColour (mainBackgroundColourId).contrasting (0.3f));
Rectangle<int> r (getLocalBounds());
g.setFont (15.0f);
g.drawFittedText (getVersionInfo(), r.removeFromBottom (30), Justification::centred, 2);
const Path& logo = getIcons().mainJuceLogo;
g.fillPath (logo, RectanglePlacement (RectanglePlacement::centred)
.getTransformToFit (logo.getBounds(), r.toFloat()));
}
static String getVersionInfo()
{
const Time buildDate (Time::getCompilationDate());
String s;
s << SystemStats::getJUCEVersion() << newLine
<< "Introjucer built: " << buildDate.getDayOfMonth()
<< " " << Time::getMonthName (buildDate.getMonth(), true)
<< " " << buildDate.getYear();
return s;
}
};
//==============================================================================
ProjectContentComponent::ProjectContentComponent()
: project (nullptr),
currentDocument (nullptr),
treeViewTabs (TabbedButtonBar::TabsAtTop)
{
setOpaque (true);
setWantsKeyboardFocus (true);
addAndMakeVisible (logo = new LogoComponent());
treeSizeConstrainer.setMinimumWidth (200);
treeSizeConstrainer.setMaximumWidth (500);
treeViewTabs.setOutline (0);
treeViewTabs.getTabbedButtonBar().setMinimumTabScaleFactor (0.3);
IntrojucerApp::getApp().openDocumentManager.addListener (this);
}
ProjectContentComponent::~ProjectContentComponent()
{
IntrojucerApp::getApp().openDocumentManager.removeListener (this);
logo = nullptr;
setProject (nullptr);
contentView = nullptr;
removeChildComponent (&bubbleMessage);
jassert (getNumChildComponents() <= 1);
}
void ProjectContentComponent::paint (Graphics& g)
{
IntrojucerLookAndFeel::fillWithBackgroundTexture (*this, g);
}
void ProjectContentComponent::paintOverChildren (Graphics& g)
{
if (resizerBar != nullptr)
{
const int shadowSize = 15;
const int x = resizerBar->getX();
ColourGradient cg (Colours::black.withAlpha (0.25f), (float) x, 0,
Colours::transparentBlack, (float) (x - shadowSize), 0, false);
cg.addColour (0.4, Colours::black.withAlpha (0.07f));
cg.addColour (0.6, Colours::black.withAlpha (0.02f));
g.setGradientFill (cg);
g.fillRect (x - shadowSize, 0, shadowSize, getHeight());
}
}
void ProjectContentComponent::resized()
{
Rectangle<int> r (getLocalBounds());
if (treeViewTabs.isVisible())
treeViewTabs.setBounds (r.removeFromLeft (treeViewTabs.getWidth()));
if (resizerBar != nullptr)
resizerBar->setBounds (r.withWidth (4));
if (contentView != nullptr)
contentView->setBounds (r);
if (logo != nullptr)
logo->setBounds (r.reduced (r.getWidth() / 4, r.getHeight() / 4));
}
void ProjectContentComponent::lookAndFeelChanged()
{
repaint();
}
void ProjectContentComponent::childBoundsChanged (Component* child)
{
if (child == &treeViewTabs)
resized();
}
void ProjectContentComponent::setProject (Project* newProject)
{
if (project != newProject)
{
if (project != nullptr)
project->removeChangeListener (this);
contentView = nullptr;
resizerBar = nullptr;
deleteProjectTabs();
project = newProject;
rebuildProjectTabs();
}
}
void ProjectContentComponent::rebuildProjectTabs()
{
deleteProjectTabs();
if (project != nullptr)
{
addAndMakeVisible (treeViewTabs);
createProjectTabs();
PropertiesFile& settings = project->getStoredProperties();
const String lastTabName (settings.getValue ("lastTab"));
int lastTabIndex = treeViewTabs.getTabNames().indexOf (lastTabName);
if (lastTabIndex < 0 || lastTabIndex > treeViewTabs.getNumTabs())
lastTabIndex = 1;
treeViewTabs.setCurrentTabIndex (lastTabIndex);
int lastTreeWidth = settings.getValue ("projectPanelWidth").getIntValue();
if (lastTreeWidth < 150)
lastTreeWidth = 240;
treeViewTabs.setBounds (0, 0, lastTreeWidth, getHeight());
addAndMakeVisible (resizerBar = new ResizableEdgeComponent (&treeViewTabs, &treeSizeConstrainer,
ResizableEdgeComponent::rightEdge));
resizerBar->setAlwaysOnTop (true);
project->addChangeListener (this);
updateMissingFileStatuses();
}
else
{
treeViewTabs.setVisible (false);
}
resized();
}
void ProjectContentComponent::createProjectTabs()
{
jassert (project != nullptr);
const Colour tabColour (Colours::transparentBlack);
treeViewTabs.addTab ("Files", tabColour, new FileTreePanel (*project), true);
treeViewTabs.addTab ("Config", tabColour, new ConfigTreePanel (*project), true);
}
void ProjectContentComponent::deleteProjectTabs()
{
if (project != nullptr && treeViewTabs.isShowing())
{
PropertiesFile& settings = project->getStoredProperties();
if (treeViewTabs.getWidth() > 0)
settings.setValue ("projectPanelWidth", treeViewTabs.getWidth());
if (treeViewTabs.getNumTabs() > 0)
settings.setValue ("lastTab", treeViewTabs.getCurrentTabName());
}
treeViewTabs.clearTabs();
}
void ProjectContentComponent::saveTreeViewState()
{
for (int i = treeViewTabs.getNumTabs(); --i >= 0;)
if (TreePanelBase* t = dynamic_cast<TreePanelBase*> (treeViewTabs.getTabContentComponent (i)))
t->saveOpenness();
}
void ProjectContentComponent::saveOpenDocumentList()
{
if (project != nullptr)
{
ScopedPointer<XmlElement> xml (recentDocumentList.createXML());
if (xml != nullptr)
project->getStoredProperties().setValue ("lastDocs", xml);
}
}
void ProjectContentComponent::reloadLastOpenDocuments()
{
if (project != nullptr)
{
ScopedPointer<XmlElement> xml (project->getStoredProperties().getXmlValue ("lastDocs"));
if (xml != nullptr)
{
recentDocumentList.restoreFromXML (*project, *xml);
showDocument (recentDocumentList.getCurrentDocument(), true);
}
}
}
bool ProjectContentComponent::documentAboutToClose (OpenDocumentManager::Document* document)
{
hideDocument (document);
return true;
}
void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster*)
{
updateMissingFileStatuses();
}
void ProjectContentComponent::updateMissingFileStatuses()
{
if (FileTreePanel* tree = dynamic_cast<FileTreePanel*> (treeViewTabs.getTabContentComponent (0)))
tree->updateMissingFileStatuses();
}
bool ProjectContentComponent::showEditorForFile (const File& f, bool grabFocus)
{
return getCurrentFile() == f
|| showDocument (IntrojucerApp::getApp().openDocumentManager.openFile (project, f), grabFocus);
}
File ProjectContentComponent::getCurrentFile() const
{
return currentDocument != nullptr ? currentDocument->getFile()
: File::nonexistent;
}
bool ProjectContentComponent::showDocument (OpenDocumentManager::Document* doc, bool grabFocus)
{
if (doc == nullptr)
return false;
if (doc->hasFileBeenModifiedExternally())
doc->reloadFromFile();
if (doc == getCurrentDocument() && contentView != nullptr)
{
if (grabFocus)
contentView->grabKeyboardFocus();
return true;
}
recentDocumentList.newDocumentOpened (doc);
bool opened = setEditorComponent (doc->createEditor(), doc);
if (opened && grabFocus)
contentView->grabKeyboardFocus();
return opened;
}
void ProjectContentComponent::hideEditor()
{
currentDocument = nullptr;
contentView = nullptr;
updateMainWindowTitle();
IntrojucerApp::getCommandManager().commandStatusChanged();
resized();
}
void ProjectContentComponent::hideDocument (OpenDocumentManager::Document* doc)
{
if (doc == currentDocument)
{
if (OpenDocumentManager::Document* replacement = recentDocumentList.getClosestPreviousDocOtherThan (doc))
showDocument (replacement, true);
else
hideEditor();
}
}
bool ProjectContentComponent::setEditorComponent (Component* editor,
OpenDocumentManager::Document* doc)
{
if (editor != nullptr)
{
contentView = nullptr;
contentView = editor;
currentDocument = doc;
addAndMakeVisible (editor);
resized();
updateMainWindowTitle();
IntrojucerApp::getCommandManager().commandStatusChanged();
return true;
}
updateMainWindowTitle();
return false;
}
void ProjectContentComponent::closeDocument()
{
if (currentDocument != nullptr)
IntrojucerApp::getApp().openDocumentManager.closeDocument (currentDocument, true);
else if (contentView != nullptr)
if (! goToPreviousFile())
hideEditor();
}
static void showSaveWarning (OpenDocumentManager::Document* currentDocument)
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
TRANS("Save failed!"),
TRANS("Couldn't save the file:")
+ "\n" + currentDocument->getFile().getFullPathName());
}
void ProjectContentComponent::saveDocument()
{
if (currentDocument != nullptr)
{
if (! currentDocument->save())
showSaveWarning (currentDocument);
}
else
saveProject();
updateMainWindowTitle();
}
void ProjectContentComponent::saveAs()
{
if (currentDocument != nullptr && ! currentDocument->saveAs())
showSaveWarning (currentDocument);
}
bool ProjectContentComponent::goToPreviousFile()
{
OpenDocumentManager::Document* 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)
{
const File file (currentDocument->getCounterpartFile());
if (file.exists())
return showEditorForFile (file, true);
}
return false;
}
bool ProjectContentComponent::saveProject()
{
return project != nullptr
&& project->save (true, true) == FileBasedDocument::savedOk;
}
void ProjectContentComponent::closeProject()
{
if (MainWindow* const mw = findParentComponentOfClass<MainWindow>())
mw->closeCurrentProject();
}
void ProjectContentComponent::showFilesTab()
{
treeViewTabs.setCurrentTabIndex (0);
}
void ProjectContentComponent::showConfigTab()
{
treeViewTabs.setCurrentTabIndex (1);
}
void ProjectContentComponent::showProjectSettings()
{
showConfigTab();
if (ConfigTreePanel* const tree = dynamic_cast<ConfigTreePanel*> (treeViewTabs.getCurrentContentComponent()))
tree->showProjectSettings();
}
void ProjectContentComponent::showModules()
{
showConfigTab();
if (ConfigTreePanel* const tree = dynamic_cast<ConfigTreePanel*> (treeViewTabs.getCurrentContentComponent()))
tree->showModules();
}
void ProjectContentComponent::showModule (const String& moduleID)
{
showConfigTab();
if (ConfigTreePanel* const tree = dynamic_cast<ConfigTreePanel*> (treeViewTabs.getCurrentContentComponent()))
tree->showModule (moduleID);
}
StringArray ProjectContentComponent::getExportersWhichCanLaunch() const
{
StringArray s;
if (project != nullptr)
for (Project::ExporterIterator exporter (*project); exporter.next();)
if (exporter->canLaunchProject())
s.add (exporter->getName());
return s;
}
void ProjectContentComponent::openInIDE (int exporterIndex)
{
int i = 0;
if (project != nullptr)
for (Project::ExporterIterator exporter (*project); exporter.next();)
if (exporter->canLaunchProject())
if (i++ == exporterIndex && exporter->launchProject())
break;
}
static void openIDEMenuCallback (int result, ProjectContentComponent* comp)
{
if (comp != nullptr && result > 0)
comp->openInIDE (result - 1);
}
void ProjectContentComponent::openInIDE()
{
if (project != nullptr)
{
StringArray possibleExporters = getExportersWhichCanLaunch();
if (possibleExporters.size() > 1)
{
PopupMenu menu;
for (int i = 0; i < possibleExporters.size(); ++i)
menu.addItem (i + 1, possibleExporters[i]);
menu.showMenuAsync (PopupMenu::Options(),
ModalCallbackFunction::forComponent (openIDEMenuCallback, this));
}
else
{
openInIDE (0);
}
}
}
void ProjectContentComponent::deleteSelectedTreeItems()
{
if (TreePanelBase* const tree = dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent()))
tree->deleteSelectedItems();
}
void ProjectContentComponent::updateMainWindowTitle()
{
if (MainWindow* mw = findParentComponentOfClass<MainWindow>())
{
String title;
File file;
bool edited = false;
if (currentDocument != nullptr)
{
title = currentDocument->getName();
edited = currentDocument->needsSaving();
file = currentDocument->getFile();
}
if (ComponentPeer* peer = mw->getPeer())
{
if (! peer->setDocumentEditedStatus (edited))
if (edited)
title << "*";
peer->setRepresentedFile (file);
}
mw->updateTitle (title);
}
}
void ProjectContentComponent::showBubbleMessage (const 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,
600, 700,
600, 400, 10000, 10000);
}
}
//==============================================================================
ApplicationCommandTarget* ProjectContentComponent::getNextCommandTarget()
{
return findFirstTargetParentComponent();
}
void ProjectContentComponent::getAllCommands (Array <CommandID>& commands)
{
const CommandID ids[] = { CommandIDs::saveDocument,
CommandIDs::saveDocumentAs,
CommandIDs::closeDocument,
CommandIDs::saveProject,
CommandIDs::closeProject,
CommandIDs::openInIDE,
CommandIDs::saveAndOpenInIDE,
CommandIDs::showFilePanel,
CommandIDs::showConfigPanel,
CommandIDs::showProjectSettings,
CommandIDs::showProjectModules,
CommandIDs::goToPreviousDoc,
CommandIDs::goToNextDoc,
CommandIDs::goToCounterpart,
CommandIDs::deleteSelectedItem,
CommandIDs::showTranslationTool };
commands.addArray (ids, numElementsInArray (ids));
}
void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
{
String documentName;
if (currentDocument != nullptr)
documentName = " '" + currentDocument->getName().substring (0, 32) + "'";
#if JUCE_MAC
const ModifierKeys cmdCtrl (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier);
#else
const ModifierKeys 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);
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);
result.defaultKeypresses.add (KeyPress ('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 || project != nullptr);
result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
break;
case CommandIDs::closeDocument:
result.setInfo ("Close" + documentName,
"Closes the current document",
CommandCategories::general, 0);
result.setActive (contentView != nullptr);
result.defaultKeypresses.add (KeyPress ('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 (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 (KeyPress::rightKey, cmdCtrl, 0));
break;
case CommandIDs::goToCounterpart:
result.setInfo ("Open corresponding header or cpp file", "Open counterpart file", CommandCategories::general, 0);
result.setActive (canGoToCounterpart());
result.defaultKeypresses.add (KeyPress (KeyPress::upKey, cmdCtrl, 0));
break;
case CommandIDs::openInIDE:
#if JUCE_MAC
result.setInfo ("Open in Xcode...",
#elif JUCE_WINDOWS
result.setInfo ("Open in Visual Studio...",
#else
result.setInfo ("Open as a Makefile...",
#endif
"Launches the project in an external IDE",
CommandCategories::general, 0);
result.setActive (ProjectExporter::canProjectBeLaunched (project));
break;
case CommandIDs::saveAndOpenInIDE:
#if JUCE_MAC
result.setInfo ("Save Project and Open in Xcode...",
#elif JUCE_WINDOWS
result.setInfo ("Save Project and Open in Visual Studio...",
#else
result.setInfo ("Save Project and Open as a Makefile...",
#endif
"Saves the project and launches it in an external IDE",
CommandCategories::general, 0);
result.setActive (ProjectExporter::canProjectBeLaunched (project));
result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
break;
case CommandIDs::showFilePanel:
result.setInfo ("Show File Panel",
"Shows the tree of files for this project",
CommandCategories::general, 0);
result.setActive (project != nullptr);
result.defaultKeypresses.add (KeyPress ('p', ModifierKeys::commandModifier, 0));
break;
case CommandIDs::showConfigPanel:
result.setInfo ("Show Config Panel",
"Shows the build options for the project",
CommandCategories::general, 0);
result.setActive (project != nullptr);
result.defaultKeypresses.add (KeyPress ('i', ModifierKeys::commandModifier, 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 (KeyPress ('i', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
break;
case CommandIDs::showProjectModules:
result.setInfo ("Show Project Modules",
"Shows the project's list of modules",
CommandCategories::general, 0);
result.setActive (project != nullptr);
result.defaultKeypresses.add (KeyPress ('m', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
break;
case CommandIDs::deleteSelectedItem:
result.setInfo ("Delete Selected File", String::empty, CommandCategories::general, 0);
result.defaultKeypresses.add (KeyPress (KeyPress::deleteKey, 0, 0));
result.defaultKeypresses.add (KeyPress (KeyPress::backspaceKey, 0, 0));
result.setActive (dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent()) != nullptr);
break;
case CommandIDs::showTranslationTool:
result.setInfo ("Translation File Builder", "Shows the translation file helper tool", CommandCategories::general, 0);
break;
default:
break;
}
}
bool ProjectContentComponent::perform (const InvocationInfo& info)
{
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;
}
switch (info.commandID)
{
case CommandIDs::saveProject: saveProject(); break;
case CommandIDs::closeProject: closeProject(); break;
case CommandIDs::saveDocument: saveDocument(); break;
case CommandIDs::saveDocumentAs: saveAs(); break;
case CommandIDs::closeDocument: closeDocument(); break;
case CommandIDs::goToPreviousDoc: goToPreviousFile(); break;
case CommandIDs::goToNextDoc: goToNextFile(); break;
case CommandIDs::goToCounterpart: goToCounterpart(); break;
case CommandIDs::showFilePanel: showFilesTab(); break;
case CommandIDs::showConfigPanel: showConfigTab(); break;
case CommandIDs::showProjectSettings: showProjectSettings(); break;
case CommandIDs::showProjectModules: showModules(); break;
case CommandIDs::openInIDE: openInIDE(); break;
case CommandIDs::deleteSelectedItem: deleteSelectedTreeItems(); break;
case CommandIDs::saveAndOpenInIDE:
if (saveProject())
openInIDE();
break;
case CommandIDs::showTranslationTool: showTranslationTool(); break;
default:
return false;
}
return true;
}
void ProjectContentComponent::getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
OwnedArray<Project::Item>& selectedNodes)
{
FileTreePanel::ProjectTreeItemBase::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
}
@@ -0,0 +1,132 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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.
==============================================================================
*/
#ifndef __JUCER_PROJECTCONTENTCOMPONENT_JUCEHEADER__
#define __JUCER_PROJECTCONTENTCOMPONENT_JUCEHEADER__
#include "jucer_Project.h"
#include "../Application/jucer_OpenDocumentManager.h"
//==============================================================================
/**
*/
class ProjectContentComponent : public Component,
public ApplicationCommandTarget,
private ChangeListener,
private OpenDocumentManager::DocumentCloseListener
{
public:
//==============================================================================
ProjectContentComponent();
~ProjectContentComponent();
Project* getProject() const noexcept { return project; }
virtual void setProject (Project* project);
void saveTreeViewState();
void saveOpenDocumentList();
void reloadLastOpenDocuments();
bool showEditorForFile (const File& f, bool grabFocus);
File getCurrentFile() const;
bool showDocument (OpenDocumentManager::Document* doc, bool grabFocus);
void hideDocument (OpenDocumentManager::Document* doc);
OpenDocumentManager::Document* getCurrentDocument() const { return currentDocument; }
void closeDocument();
void saveDocument();
void saveAs();
void hideEditor();
bool setEditorComponent (Component* editor, OpenDocumentManager::Document* doc);
Component* getEditorComponent() const { return contentView; }
bool goToPreviousFile();
bool goToNextFile();
bool canGoToCounterpart() const;
bool goToCounterpart();
bool saveProject();
void closeProject();
void openInIDE();
void openInIDE (int exporterIndex);
void showFilesTab();
void showConfigTab();
void showProjectSettings();
void showModules();
void showModule (const String& moduleID);
void deleteSelectedTreeItems();
void updateMainWindowTitle();
void updateMissingFileStatuses();
virtual void createProjectTabs();
virtual void deleteProjectTabs();
void rebuildProjectTabs();
void showBubbleMessage (const Rectangle<int>&, const String&);
StringArray getExportersWhichCanLaunch() const;
static void getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
OwnedArray<Project::Item>& selectedNodes);
//==============================================================================
ApplicationCommandTarget* getNextCommandTarget() override;
void getAllCommands (Array <CommandID>& commands) override;
void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) override;
bool perform (const InvocationInfo& info) override;
void paint (Graphics&) override;
void paintOverChildren (Graphics&) override;
void resized() override;
void childBoundsChanged (Component*) override;
void lookAndFeelChanged() override;
protected:
Project* project;
OpenDocumentManager::Document* currentDocument;
RecentDocumentList recentDocumentList;
ScopedPointer<Component> logo;
ScopedPointer<Component> translationTool;
TabbedComponent treeViewTabs;
ScopedPointer<ResizableEdgeComponent> resizerBar;
ScopedPointer<Component> contentView;
ComponentBoundsConstrainer treeSizeConstrainer;
BubbleMessageComponent bubbleMessage;
bool documentAboutToClose (OpenDocumentManager::Document*) override;
void changeListenerCallback (ChangeBroadcaster*) override;
void showTranslationTool();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectContentComponent)
};
#endif // __JUCER_PROJECTCONTENTCOMPONENT_JUCEHEADER__
@@ -0,0 +1,423 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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 ProjectTreeItemBase : public JucerTreeViewBase,
public ValueTree::Listener
{
public:
ProjectTreeItemBase (const Project::Item& projectItem)
: item (projectItem), isFileMissing (false)
{
item.state.addListener (this);
}
~ProjectTreeItemBase()
{
item.state.removeListener (this);
}
//==============================================================================
virtual bool isRoot() const { return false; }
virtual bool acceptsFileDrop (const StringArray& files) const = 0;
virtual bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) = 0;
//==============================================================================
virtual String getDisplayName() const { return item.getName(); }
virtual String getRenamingName() const { return getDisplayName(); }
virtual void setName (const String& newName)
{
if (item.isMainGroup())
item.project.setTitle (newName);
else
item.getNameValue() = newName;
}
virtual bool isMissing() { return isFileMissing; }
virtual File getFile() const { return item.getFile(); }
virtual void deleteItem() { item.removeItemFromProject(); }
virtual void deleteAllSelectedItems()
{
TreeView* tree = getOwnerView();
const int numSelected = tree->getNumSelectedItems();
OwnedArray <File> filesToTrash;
OwnedArray <Project::Item> itemsToRemove;
for (int i = 0; i < numSelected; ++i)
{
if (const ProjectTreeItemBase* const p = dynamic_cast<ProjectTreeItemBase*> (tree->getSelectedItem (i)))
{
itemsToRemove.add (new Project::Item (p->item));
if (p->getFile().existsAsFile())
filesToTrash.add (new File (p->getFile()));
}
}
if (filesToTrash.size() > 0)
{
String fileList;
const int maxFilesToList = 10;
for (int 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...";
int r = AlertWindow::showYesNoCancelBox (AlertWindow::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());
if (r == 0)
return;
if (r != 2)
filesToTrash.clear();
}
if (ProjectTreeItemBase* treeRootItem = dynamic_cast<ProjectTreeItemBase*> (tree->getRootItem()))
{
OpenDocumentManager& om = IntrojucerApp::getApp().openDocumentManager;
for (int i = filesToTrash.size(); --i >= 0;)
{
const File f (*filesToTrash.getUnchecked(i));
om.closeFile (f, false);
if (! f.moveToTrash())
{
// xxx
}
}
for (int i = itemsToRemove.size(); --i >= 0;)
{
if (ProjectTreeItemBase* itemToRemove = treeRootItem->findTreeViewItem (*itemsToRemove.getUnchecked(i)))
{
om.closeFile (itemToRemove->getFile(), false);
itemToRemove->deleteItem();
}
}
}
else
{
jassertfalse;
}
}
virtual void revealInFinder() const
{
getFile().revealToUser();
}
virtual void browseToAddExistingFiles()
{
const File location (item.isGroup() ? item.determineGroupFolder() : getFile());
FileChooser fc ("Add Files to Jucer Project", location, String::empty, false);
if (fc.browseForMultipleFilesOrDirectories())
{
StringArray files;
for (int i = 0; i < fc.getResults().size(); ++i)
files.add (fc.getResults().getReference(i).getFullPathName());
addFiles (files, 0);
}
}
virtual void checkFileStatus() // (recursive)
{
const File file (getFile());
const bool nowMissing = file != File::nonexistent && ! file.exists();
if (nowMissing != isFileMissing)
{
isFileMissing = nowMissing;
repaintItem();
}
}
virtual void addFiles (const StringArray& files, int insertIndex)
{
if (ProjectTreeItemBase* p = getParentProjectItem())
p->addFiles (files, insertIndex);
}
virtual void moveSelectedItemsTo (OwnedArray <Project::Item>&, int /*insertIndex*/)
{
jassertfalse;
}
virtual void showMultiSelectionPopupMenu()
{
PopupMenu m;
m.addItem (1, "Delete");
m.showMenuAsync (PopupMenu::Options(),
ModalCallbackFunction::create (treeViewMultiSelectItemChosen, this));
}
static void treeViewMultiSelectItemChosen (int resultCode, ProjectTreeItemBase* item)
{
switch (resultCode)
{
case 1: item->deleteAllSelectedItems(); break;
default: break;
}
}
virtual ProjectTreeItemBase* findTreeViewItem (const Project::Item& itemToFind)
{
if (item == itemToFind)
return this;
const bool wasOpen = isOpen();
setOpen (true);
for (int i = getNumSubItems(); --i >= 0;)
{
if (ProjectTreeItemBase* pg = dynamic_cast<ProjectTreeItemBase*> (getSubItem(i)))
if (ProjectTreeItemBase* found = pg->findTreeViewItem (itemToFind))
return found;
}
setOpen (wasOpen);
return nullptr;
}
//==============================================================================
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&) override { treeChildrenChanged (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree) override { treeChildrenChanged (parentTree); }
void valueTreeParentChanged (ValueTree&) override {}
//==============================================================================
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 String::empty; }
File getDraggableFile() const override { return getFile(); }
var getDragSourceDescription() override
{
cancelDelayedSelectionTimer();
return projectItemDragType;
}
void addSubItems() override
{
for (int i = 0; i < item.getNumChildren(); ++i)
if (ProjectTreeItemBase* 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))
IntrojucerApp::getApp().openFile (files[0]);
else
addFiles (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)
{
TreeView* tree = getOwnerView();
ScopedPointer<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)
{
TreeView* tree = dynamic_cast<TreeView*> (dragSourceDetails.sourceComponent.get());
if (tree == nullptr)
tree = dragSourceDetails.sourceComponent->findParentComponentOfClass<TreeView>();
if (tree != nullptr)
{
const int numSelected = tree->getNumSelectedItems();
for (int i = 0; i < numSelected; ++i)
if (const ProjectTreeItemBase* const p = dynamic_cast<ProjectTreeItemBase*> (tree->getSelectedItem (i)))
selectedNodes.add (new Project::Item (p->item));
}
}
}
ProjectTreeItemBase* getParentProjectItem() const
{
return dynamic_cast<ProjectTreeItemBase*> (getParentItem());
}
//==============================================================================
Project::Item item;
protected:
bool isFileMissing;
virtual ProjectTreeItemBase* createSubItem (const Project::Item& node) = 0;
Icon getIcon() const override { return item.getIcon().withContrastingColourTo (getBackgroundColour()); }
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)
{
class RenameMessage : public CallbackMessage
{
public:
RenameMessage (TreeView* const t, const Project::Item& i)
: tree (t), itemToRename (i) {}
void messageCallback() override
{
if (tree != nullptr)
if (ProjectTreeItemBase* root = dynamic_cast<ProjectTreeItemBase*> (tree->getRootItem()))
if (ProjectTreeItemBase* 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 (int i = selectedNodes.size(); --i >= 0;)
{
Project::Item* const 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 (int i = selectedNodes.size(); --i >= 0;)
{
Project::Item* const n = selectedNodes.getUnchecked(i);
for (int 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)
{
Project::Item* 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 (int i = parent.getNumChildren(); --i >= 0;)
if (parent.getChild (i) == child)
return i;
return -1;
}
};
@@ -0,0 +1,154 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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 SourceFileItem : public ProjectTreeItemBase
{
public:
SourceFileItem (const Project::Item& projectItem)
: ProjectTreeItemBase (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();
}
static File findCorrespondingHeaderOrCpp (const File& f)
{
if (f.hasFileExtension (sourceFileExtensions))
return f.withFileExtension (".h");
else if (f.hasFileExtension (headerFileExtensions))
return f.withFileExtension (".cpp");
return File::nonexistent;
}
void setName (const String& newName) override
{
if (newName != File::createLegalFileName (newName))
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
"That filename contained some illegal characters!");
triggerAsyncRename (item);
return;
}
File oldFile (getFile());
File newFile (oldFile.getSiblingFile (newName));
File correspondingFile (findCorrespondingHeaderOrCpp (oldFile));
if (correspondingFile.exists() && newFile.hasFileExtension (oldFile.getFileExtension()))
{
Project::Item correspondingItem (item.project.getMainGroup().findItemForFile (correspondingFile));
if (correspondingItem.isValid())
{
if (AlertWindow::showOkCancelBox (AlertWindow::NoIcon, "File Rename",
"Do you also want to rename the corresponding file \"" + correspondingFile.getFileName()
+ "\" to match?"))
{
if (! item.renameFile (newFile))
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
"Failed to rename \"" + oldFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
return;
}
if (! correspondingItem.renameFile (newFile.withFileExtension (correspondingFile.getFileExtension())))
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
"Failed to rename \"" + correspondingFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
}
}
}
}
if (! item.renameFile (newFile))
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
"Failed to rename the file!\n\nCheck your file permissions!");
}
}
ProjectTreeItemBase* createSubItem (const Project::Item&) override
{
jassertfalse;
return nullptr;
}
void showDocument() override
{
const File f (getFile());
if (f.exists())
if (ProjectContentComponent* pcc = getProjectContentComponent())
pcc->showEditorForFile (f, false);
}
void showPopupMenu() override
{
PopupMenu m;
if (GroupItem* parentGroup = dynamic_cast<GroupItem*> (getParentProjectItem()))
{
parentGroup->addCreateFileMenuItems (m);
m.addSeparator();
}
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 (3, "Delete");
launchPopupMenu (m);
}
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;
default:
if (GroupItem* parentGroup = dynamic_cast<GroupItem*> (getParentProjectItem()))
parentGroup->processCreateFileMenuItem (resultCode);
break;
}
}
};
@@ -0,0 +1,172 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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 GroupItem : public ProjectTreeItemBase
{
public:
GroupItem (const Project::Item& projectItem)
: ProjectTreeItemBase (projectItem)
{
}
bool isRoot() const override { return item.isMainGroup(); }
bool acceptsFileDrop (const StringArray&) const override { return true; }
void addNewGroup()
{
Project::Item newGroup (item.addNewSubGroup ("New Group", 0));
triggerAsyncRename (newGroup);
}
bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) override
{
for (int i = selectedNodes.size(); --i >= 0;)
if (item.canContain (*selectedNodes.getUnchecked(i)))
return true;
return false;
}
void addFiles (const StringArray& files, int insertIndex) override
{
for (int i = 0; i < files.size(); ++i)
{
const File file (files[i]);
if (item.addFile (file, insertIndex, true))
++insertIndex;
}
}
void moveSelectedItemsTo (OwnedArray<Project::Item>& selectedNodes, int insertIndex) override
{
moveItems (selectedNodes, item, insertIndex);
}
void checkFileStatus() override
{
for (int i = 0; i < getNumSubItems(); ++i)
if (ProjectTreeItemBase* p = dynamic_cast<ProjectTreeItemBase*> (getSubItem(i)))
p->checkFileStatus();
}
ProjectTreeItemBase* createSubItem (const Project::Item& child) override
{
if (child.isGroup()) return new GroupItem (child);
if (child.isFile()) return new SourceFileItem (child);
jassertfalse;
return nullptr;
}
void showDocument() override
{
if (ProjectContentComponent* pcc = getProjectContentComponent())
pcc->setEditorComponent (new GroupInformationComponent (item), nullptr);
}
static void openOrCloseAllSubGroups (TreeViewItem& item, bool shouldOpen)
{
item.setOpen (shouldOpen);
for (int i = item.getNumSubItems(); --i >= 0;)
if (TreeViewItem* sub = item.getSubItem(i))
openOrCloseAllSubGroups (*sub, shouldOpen);
}
static void setFilesToCompile (Project::Item item, const bool shouldCompile)
{
if (item.isFile())
item.getShouldCompileValue() = shouldCompile;
for (int i = item.getNumChildren(); --i >= 0;)
setFilesToCompile (item.getChild (i), shouldCompile);
}
void showPopupMenu() override
{
PopupMenu m;
addCreateFileMenuItems (m);
m.addSeparator();
if (isOpen())
m.addItem (1, "Collapse all Sub-groups");
else
m.addItem (2, "Expand all Sub-groups");
m.addSeparator();
m.addItem (3, "Enable compiling of all enclosed files");
m.addItem (4, "Disable compiling of all enclosed files");
m.addSeparator();
m.addItem (5, "Sort Items Alphabetically");
m.addItem (6, "Sort Items Alphabetically (Groups first)");
m.addSeparator();
m.addItem (7, "Rename...");
if (! isRoot())
m.addItem (8, "Delete");
launchPopupMenu (m);
}
void handlePopupMenuResult (int resultCode) override
{
switch (resultCode)
{
case 1: openOrCloseAllSubGroups (*this, false); break;
case 2: openOrCloseAllSubGroups (*this, true); break;
case 3: setFilesToCompile (item, true); break;
case 4: setFilesToCompile (item, false); break;
case 5: item.sortAlphabetically (false); break;
case 6: item.sortAlphabetically (true); break;
case 7: triggerAsyncRename (item); break;
case 8: deleteAllSelectedItems(); break;
default: processCreateFileMenuItem (resultCode); break;
}
}
void addCreateFileMenuItems (PopupMenu& m)
{
m.addItem (1001, "Add New Group");
m.addItem (1002, "Add Existing Files...");
m.addSeparator();
NewFileWizard().addWizardsToMenu (m);
}
void processCreateFileMenuItem (int menuID)
{
switch (menuID)
{
case 1001: addNewGroup(); break;
case 1002: browseToAddExistingFiles(); break;
default:
NewFileWizard().runWizardFromMenu (menuID, item);
break;
}
}
};
@@ -0,0 +1,364 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "jucer_ProjectType.h"
#include "../Project Saving/jucer_ProjectExporter.h"
#include "../Project Saving/jucer_ProjectSaver.h"
#include "jucer_AudioPluginModule.h"
//==============================================================================
ProjectType::ProjectType (const String& t, const String& d)
: type (t), desc (d)
{
getAllTypes().add (this);
}
ProjectType::~ProjectType()
{
getAllTypes().removeFirstMatchingValue (this);
}
Array<ProjectType*>& ProjectType::getAllTypes()
{
static Array<ProjectType*> types;
return types;
}
const ProjectType* ProjectType::findType (const String& typeCode)
{
const Array<ProjectType*>& types = getAllTypes();
for (int i = types.size(); --i >= 0;)
if (types.getUnchecked(i)->getType() == typeCode)
return types.getUnchecked(i);
jassertfalse;
return nullptr;
}
//==============================================================================
class ProjectType_GUIApp : public ProjectType
{
public:
ProjectType_GUIApp() : ProjectType (getTypeName(), "Application (GUI)") {}
static const char* getTypeName() noexcept { return "guiapp"; }
bool isGUIApplication() const { return true; }
void setMissingProjectProperties (Project&) const
{
}
void createPropertyEditors (Project&, PropertyListBuilder&) const
{
}
void prepareExporter (ProjectExporter& exporter) const
{
exporter.xcodePackageType = "APPL";
exporter.xcodeBundleSignature = "????";
exporter.xcodeCreatePList = true;
exporter.xcodeFileType = "wrapper.application";
exporter.xcodeBundleExtension = ".app";
exporter.xcodeProductType = "com.apple.product-type.application";
exporter.xcodeProductInstallPath = "$(HOME)/Applications";
exporter.msvcIsWindowsSubsystem = true;
exporter.msvcTargetSuffix = ".exe";
}
};
//==============================================================================
class ProjectType_ConsoleApp : public ProjectType
{
public:
ProjectType_ConsoleApp() : ProjectType (getTypeName(), "Application (Non-GUI)") {}
static const char* getTypeName() noexcept { return "consoleapp"; }
bool isCommandLineApp() const { return true; }
void setMissingProjectProperties (Project&) const
{
}
void createPropertyEditors (Project&, PropertyListBuilder&) const
{
}
void prepareExporter (ProjectExporter& exporter) const
{
exporter.xcodeCreatePList = false;
exporter.xcodeFileType = "compiled.mach-o.executable";
exporter.xcodeBundleExtension = String::empty;
exporter.xcodeProductType = "com.apple.product-type.tool";
exporter.xcodeProductInstallPath = "/usr/bin";
exporter.msvcIsWindowsSubsystem = false;
exporter.msvcTargetSuffix = ".exe";
exporter.msvcExtraPreprocessorDefs.set ("_CONSOLE", "");
}
};
//==============================================================================
class ProjectType_StaticLibrary : public ProjectType
{
public:
ProjectType_StaticLibrary() : ProjectType (getTypeName(), "Static Library") {}
static const char* getTypeName() noexcept { return "library"; }
bool isStaticLibrary() const { return true; }
void setMissingProjectProperties (Project&) const
{
}
void createPropertyEditors (Project&, PropertyListBuilder&) const
{
}
void prepareExporter (ProjectExporter& exporter) const
{
exporter.xcodeCreatePList = false;
exporter.xcodeFileType = "archive.ar";
exporter.xcodeProductType = "com.apple.product-type.library.static";
exporter.xcodeProductInstallPath = String::empty;
exporter.makefileTargetSuffix = ".a";
exporter.msvcTargetSuffix = ".lib";
exporter.msvcExtraPreprocessorDefs.set ("_LIB", "");
}
};
//==============================================================================
class ProjectType_DLL : public ProjectType
{
public:
ProjectType_DLL() : ProjectType (getTypeName(), "Dynamic Library") {}
static const char* getTypeName() noexcept { return "dll"; }
bool isDynamicLibrary() const { return true; }
void setMissingProjectProperties (Project&) const
{
}
void createPropertyEditors (Project&, PropertyListBuilder&) const
{
}
void prepareExporter (ProjectExporter& exporter) const
{
exporter.xcodeCreatePList = false;
exporter.xcodeFileType = "compiled.mach-o.dylib";
exporter.xcodeProductType = "com.apple.product-type.library.dynamic";
exporter.xcodeBundleExtension = ".dylib";
exporter.xcodeProductInstallPath = String::empty;
exporter.makefileTargetSuffix = ".so";
exporter.msvcTargetSuffix = ".dll";
exporter.msvcExtraPreprocessorDefs.set ("_LIB", "");
}
};
//==============================================================================
class ProjectType_AudioPlugin : public ProjectType
{
public:
ProjectType_AudioPlugin() : ProjectType (getTypeName(), "Audio Plug-in") {}
static const char* getTypeName() noexcept { return "audioplug"; }
bool isAudioPlugin() const { return true; }
void setMissingProjectProperties (Project& project) const
{
const String sanitisedProjectName (CodeHelpers::makeValidIdentifier (project.getTitle(), false, true, false));
setValueIfVoid (shouldBuildVST (project), true);
setValueIfVoid (shouldBuildVST3 (project), false);
setValueIfVoid (shouldBuildAU (project), true);
setValueIfVoid (shouldBuildRTAS (project), false);
setValueIfVoid (shouldBuildAAX (project), false);
setValueIfVoid (getPluginName (project), project.getTitle());
setValueIfVoid (getPluginDesc (project), project.getTitle());
setValueIfVoid (getPluginManufacturer (project), "yourcompany");
setValueIfVoid (getPluginManufacturerCode (project), "Manu");
setValueIfVoid (getPluginCode (project), "Plug");
setValueIfVoid (getPluginChannelConfigs (project), "{1, 1}, {2, 2}");
setValueIfVoid (getPluginIsSynth (project), false);
setValueIfVoid (getPluginWantsMidiInput (project), false);
setValueIfVoid (getPluginProducesMidiOut (project), false);
setValueIfVoid (getPluginSilenceInProducesSilenceOut (project), false);
setValueIfVoid (getPluginEditorNeedsKeyFocus (project), false);
setValueIfVoid (getPluginAUExportPrefix (project), sanitisedProjectName + "AU");
setValueIfVoid (getPluginRTASCategory (project), String::empty);
setValueIfVoid (project.getBundleIdentifier(), project.getDefaultBundleIdentifier());
setValueIfVoid (project.getAAXIdentifier(), project.getDefaultAAXIdentifier());
setValueIfVoid (getPluginAAXCategory (project), "AAX_ePlugInCategory_Dynamics");
}
void createPropertyEditors (Project& project, PropertyListBuilder& props) const
{
props.add (new BooleanPropertyComponent (shouldBuildVST (project), "Build VST", "Enabled"),
"Whether the project should produce a VST plugin.");
props.add (new BooleanPropertyComponent (shouldBuildVST3 (project), "Build VST3", "Enabled"),
"Whether the project should produce a VST3 plugin.");
props.add (new BooleanPropertyComponent (shouldBuildAU (project), "Build AudioUnit", "Enabled"),
"Whether the project should produce an AudioUnit plugin.");
props.add (new BooleanPropertyComponent (shouldBuildRTAS (project), "Build RTAS", "Enabled"),
"Whether the project should produce an RTAS plugin.");
props.add (new BooleanPropertyComponent (shouldBuildAAX (project), "Build AAX", "Enabled"),
"Whether the project should produce an AAX plugin.");
props.add (new TextPropertyComponent (getPluginName (project), "Plugin Name", 128, false),
"The name of your plugin (keep it short!)");
props.add (new TextPropertyComponent (getPluginDesc (project), "Plugin Description", 256, false),
"A short description of your plugin.");
props.add (new TextPropertyComponent (getPluginManufacturer (project), "Plugin Manufacturer", 256, false),
"The name of your company (cannot be blank).");
props.add (new TextPropertyComponent (getPluginManufacturerCode (project), "Plugin Manufacturer Code", 4, false),
"A four-character unique ID for your company. Note that for AU compatibility, this must contain at least one upper-case letter!");
props.add (new TextPropertyComponent (getPluginCode (project), "Plugin Code", 4, false),
"A four-character unique ID for your plugin. Note that for AU compatibility, this must contain at least one upper-case letter!");
props.add (new TextPropertyComponent (getPluginChannelConfigs (project), "Plugin Channel Configurations", 1024, false),
"This is the set of input/output channel configurations that your plugin can handle. The list is a comma-separated set of pairs of values in the form { numInputs, numOutputs }, and each "
"pair indicates a valid configuration that the plugin can handle. So for example, {1, 1}, {2, 2} means that the plugin can be used in just two configurations: either with 1 input "
"and 1 output, or with 2 inputs and 2 outputs.");
props.add (new BooleanPropertyComponent (getPluginIsSynth (project), "Plugin is a Synth", "Is a Synth"),
"Enable this if you want your plugin to be treated as a synth or generator. It doesn't make much difference to the plugin itself, but some hosts treat synths differently to other plugins.");
props.add (new BooleanPropertyComponent (getPluginWantsMidiInput (project), "Plugin Midi Input", "Plugin wants midi input"),
"Enable this if you want your plugin to accept midi messages.");
props.add (new BooleanPropertyComponent (getPluginProducesMidiOut (project), "Plugin Midi Output", "Plugin produces midi output"),
"Enable this if your plugin is going to produce midi messages.");
props.add (new BooleanPropertyComponent (getPluginSilenceInProducesSilenceOut (project), "Silence", "Silence in produces silence out"),
"Enable this if your plugin has no tail - i.e. if passing a silent buffer to it will always result in a silent buffer being produced.");
props.add (new BooleanPropertyComponent (getPluginEditorNeedsKeyFocus (project), "Key Focus", "Plugin editor requires keyboard focus"),
"Enable this if your plugin needs keyboard input - some hosts can be a bit funny about keyboard focus..");
props.add (new TextPropertyComponent (getPluginAUExportPrefix (project), "Plugin AU Export Prefix", 64, false),
"A prefix for the names of exported entry-point functions that the component exposes - typically this will be a version of your plugin's name that can be used as part of a C++ token.");
props.add (new TextPropertyComponent (getPluginAUMainType (project), "Plugin AU Main Type", 128, false),
"In an AU, this is the value that is set as JucePlugin_AUMainType. Leave it blank unless you want to use a custom value.");
props.add (new TextPropertyComponent (getPluginVSTCategory (project), "VST Category", 64, false),
"In a VST, this is the value that is set as JucePlugin_VSTCategory. Leave it blank unless you want to use a custom value.");
props.add (new TextPropertyComponent (getPluginRTASCategory (project), "Plugin RTAS Category", 64, false),
"(Leave this blank if your plugin is a synth). This is one of the RTAS categories from FicPluginEnums.h, such as: ePlugInCategory_None, ePlugInCategory_EQ, ePlugInCategory_Dynamics, "
"ePlugInCategory_PitchShift, ePlugInCategory_Reverb, ePlugInCategory_Delay, "
"ePlugInCategory_Modulation, ePlugInCategory_Harmonic, ePlugInCategory_NoiseReduction, "
"ePlugInCategory_Dither, ePlugInCategory_SoundField");
props.add (new TextPropertyComponent (getPluginAAXCategory (project), "Plugin AAX Category", 64, false),
"This is one of the categories from the AAX_EPlugInCategory enum");
props.add (new TextPropertyComponent (project.getAAXIdentifier(), "Plugin AAX Identifier", 256, false),
"The value to use for the JucePlugin_AAXIdentifier setting");
}
void prepareExporter (ProjectExporter& exporter) const
{
exporter.xcodeIsBundle = true;
exporter.xcodeCreatePList = true;
exporter.xcodePackageType = "TDMw";
exporter.xcodeBundleSignature = "PTul";
exporter.xcodeFileType = "wrapper.cfbundle";
exporter.xcodeBundleExtension = ".component";
exporter.xcodeProductType = "com.apple.product-type.bundle";
exporter.xcodeProductInstallPath = "$(HOME)/Library/Audio/Plug-Ins/Components/";
exporter.xcodeOtherRezFlags = "-d ppc_$ppc -d i386_$i386 -d ppc64_$ppc64 -d x86_64_$x86_64"
" -I /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Versions/A/Headers"
" -I \\\"$(DEVELOPER_DIR)/Extras/CoreAudio/AudioUnits/AUPublic/AUBase\\\"";
exporter.msvcTargetSuffix = ".dll";
exporter.msvcIsDLL = true;
exporter.makefileIsDLL = true;
}
};
//==============================================================================
class ProjectType_BrowserPlugin : public ProjectType
{
public:
ProjectType_BrowserPlugin() : ProjectType (getTypeName(), "Browser Plug-in") {}
static const char* getTypeName() noexcept { return "browserplug"; }
bool isBrowserPlugin() const { return true; }
void prepareExporter (ProjectExporter& exporter) const
{
exporter.xcodeIsBundle = true;
exporter.xcodeCreatePList = true;
exporter.xcodeFileType = "wrapper.cfbundle";
exporter.xcodeBundleExtension = ".plugin";
exporter.xcodeProductType = "com.apple.product-type.bundle";
exporter.xcodeProductInstallPath = "$(HOME)/Library/Internet Plug-Ins//";
{
XmlElement mimeTypesKey ("key");
mimeTypesKey.setText ("WebPluginMIMETypes");
XmlElement mimeTypesEntry ("dict");
const String exeName (exporter.getProject().getProjectFilenameRoot().toLowerCase());
mimeTypesEntry.createNewChildElement ("key")->setText ("application/" + exeName + "-plugin");
XmlElement* d = mimeTypesEntry.createNewChildElement ("dict");
d->createNewChildElement ("key")->setText ("WebPluginExtensions");
d->createNewChildElement ("array")
->createNewChildElement ("string")->setText (exeName);
d->createNewChildElement ("key")->setText ("WebPluginTypeDescription");
d->createNewChildElement ("string")->setText (exporter.getProject().getTitle());
exporter.xcodeExtraPListEntries.add (mimeTypesKey);
exporter.xcodeExtraPListEntries.add (mimeTypesEntry);
}
exporter.msvcTargetSuffix = ".dll";
exporter.msvcIsDLL = true;
exporter.makefileIsDLL = true;
}
};
//==============================================================================
static ProjectType_GUIApp guiType;
static ProjectType_ConsoleApp consoleType;
static ProjectType_StaticLibrary libraryType;
static ProjectType_DLL dllType;
static ProjectType_AudioPlugin audioPluginType;
//==============================================================================
const char* ProjectType::getGUIAppTypeName() { return ProjectType_GUIApp::getTypeName(); }
const char* ProjectType::getConsoleAppTypeName() { return ProjectType_ConsoleApp::getTypeName(); }
const char* ProjectType::getStaticLibTypeName() { return ProjectType_StaticLibrary::getTypeName(); }
const char* ProjectType::getDynamicLibTypeName() { return ProjectType_DLL::getTypeName(); }
const char* ProjectType::getAudioPluginTypeName() { return ProjectType_AudioPlugin::getTypeName(); }
@@ -0,0 +1,75 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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.
==============================================================================
*/
#ifndef __JUCER_PROJECTTYPE_JUCEHEADER__
#define __JUCER_PROJECTTYPE_JUCEHEADER__
#include "../jucer_Headers.h"
class Project;
class ProjectExporter;
//==============================================================================
class ProjectType
{
public:
//==============================================================================
virtual ~ProjectType();
const String& getType() const noexcept { return type; }
const String& getDescription() const noexcept { return desc; }
//==============================================================================
static Array<ProjectType*>& getAllTypes();
static const ProjectType* findType (const String& typeCode);
//==============================================================================
virtual bool isStaticLibrary() const { return false; }
virtual bool isDynamicLibrary() const { return false; }
virtual bool isGUIApplication() const { return false; }
virtual bool isCommandLineApp() const { return false; }
virtual bool isAudioPlugin() const { return false; }
virtual bool isBrowserPlugin() const { return false; }
static const char* getGUIAppTypeName();
static const char* getConsoleAppTypeName();
static const char* getStaticLibTypeName();
static const char* getDynamicLibTypeName();
static const char* getAudioPluginTypeName();
virtual void setMissingProjectProperties (Project&) const = 0;
virtual void createPropertyEditors (Project&, PropertyListBuilder&) const = 0;
virtual void prepareExporter (ProjectExporter&) const = 0;
protected:
ProjectType (const String& type, const String& desc);
private:
const String type, desc;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectType)
};
#endif // __JUCER_PROJECTTYPE_JUCEHEADER__