diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h index a5ac41d60b..feec576781 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h @@ -85,18 +85,18 @@ public: { } - void createPropertyEditors (Array & props) + void createPropertyEditors (PropertyListBuilder& props) { ProjectExporter::createPropertyEditors (props); - props.add (new TextPropertyComponent (getSDKPath(), "Android SDK Path", 1024, false)); - props.getLast()->setTooltip ("The path to the Android SDK folder on the target build machine"); + props.add (new TextPropertyComponent (getSDKPath(), "Android SDK Path", 1024, false), + "The path to the Android SDK folder on the target build machine"); - props.add (new TextPropertyComponent (getNDKPath(), "Android NDK Path", 1024, false)); - props.getLast()->setTooltip ("The path to the Android NDK folder on the target build machine"); + props.add (new TextPropertyComponent (getNDKPath(), "Android NDK Path", 1024, false), + "The path to the Android NDK folder on the target build machine"); - props.add (new BooleanPropertyComponent (getInternetNeeded(), "Internet Access", "Specify internet access permission in the manifest")); - props.getLast()->setTooltip ("If enabled, this will set the android.permission.INTERNET flag in the manifest."); + props.add (new BooleanPropertyComponent (getInternetNeeded(), "Internet Access", "Specify internet access permission in the manifest"), + "If enabled, this will set the android.permission.INTERNET flag in the manifest."); } Value getSDKPath() const { return getSetting (Ids::androidSDKPath); } diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_MSVC.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_MSVC.h index 8b7301dddd..5871541890 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_MSVC.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_MSVC.h @@ -53,7 +53,7 @@ public: bool isVisualStudio() const { return true; } bool canCopeWithDuplicateFiles() { return false; } - void createPropertyEditors (Array & props) + void createPropertyEditors (PropertyListBuilder& props) { ProjectExporter::createPropertyEditors (props); @@ -63,11 +63,11 @@ public: const int libTypeValues[] = { 1, 2, 0 }; props.add (new ChoicePropertyComponent (getLibraryType(), "Library Type", StringArray (libTypes), Array (libTypeValues))); - props.add (new TextPropertyComponent (getSetting (Ids::libraryName_Debug), "Library Name (Debug)", 128, false)); - props.getLast()->setTooltip ("If set, this name will override the binary name specified in the configuration settings, for a debug build. You must include the .lib or .dll suffix on this filename."); + props.add (new TextPropertyComponent (getSetting (Ids::libraryName_Debug), "Library Name (Debug)", 128, false), + "If set, this name will override the binary name specified in the configuration settings, for a debug build. You must include the .lib or .dll suffix on this filename."); - props.add (new TextPropertyComponent (getSetting (Ids::libraryName_Release), "Library Name (Release)", 128, false)); - props.getLast()->setTooltip ("If set, this name will override the binary name specified in the configuration settings, for a release build. You must include the .lib or .dll suffix on this filename."); + props.add (new TextPropertyComponent (getSetting (Ids::libraryName_Release), "Library Name (Release)", 128, false), + "If set, this name will override the binary name specified in the configuration settings, for a release build. You must include the .lib or .dll suffix on this filename."); } props.add (new TextPropertyComponent (getPrebuildCommand(), "Pre-build Command", 2048, false)); diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Make.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Make.h index 6bbb9fecb5..25278a328a 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Make.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Make.h @@ -76,7 +76,7 @@ public: // what to do on linux? } - void createPropertyEditors (Array & props) + void createPropertyEditors (PropertyListBuilder& props) { ProjectExporter::createPropertyEditors (props); } diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h index 6dde17c63c..0259315906 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h @@ -64,6 +64,7 @@ public: //============================================================================== Value getObjCSuffix() { return getSetting ("objCExtraSuffix"); } + Value getPListToMerge() { return getSetting ("customPList"); } int getLaunchPreferenceOrderForCurrentOS() { @@ -89,27 +90,33 @@ public: bool isOSX() const { return ! iOS; } bool canCopeWithDuplicateFiles() { return true; } - void createPropertyEditors (Array & props) + void createPropertyEditors (PropertyListBuilder& props) { ProjectExporter::createPropertyEditors (props); - props.add (new TextPropertyComponent (getObjCSuffix(), "Objective-C class name suffix", 64, false)); - props.getLast()->setTooltip ("Because objective-C linkage is done by string-matching, you can get horrible linkage mix-ups when different modules containing the " - "same class-names are loaded simultaneously. This setting lets you provide a unique string that will be used in naming the obj-C classes in your executable to avoid this."); + props.add (new TextPropertyComponent (getObjCSuffix(), "Objective-C class name suffix", 64, false), + "Because objective-C linkage is done by string-matching, you can get horrible linkage mix-ups when different modules containing the " + "same class-names are loaded simultaneously. This setting lets you provide a unique string that will be used in naming " + "the obj-C classes in your executable to avoid this."); if (projectType.isGUIApplication() && ! iOS) { - props.add (new TextPropertyComponent (getSetting ("documentExtensions"), "Document file extensions", 128, false)); - props.getLast()->setTooltip ("A comma-separated list of file extensions for documents that your app can open."); + props.add (new TextPropertyComponent (getSetting ("documentExtensions"), "Document file extensions", 128, false), + "A comma-separated list of file extensions for documents that your app can open."); } else if (iOS) { - props.add (new BooleanPropertyComponent (getSetting ("UIFileSharingEnabled"), "File Sharing Enabled", "Enabled")); - props.getLast()->setTooltip ("Enable this to expose your app's files to iTunes."); + props.add (new BooleanPropertyComponent (getSetting ("UIFileSharingEnabled"), "File Sharing Enabled", "Enabled"), + "Enable this to expose your app's files to iTunes."); - props.add (new BooleanPropertyComponent (getSetting ("UIStatusBarHidden"), "Status Bar Hidden", "Enabled")); - props.getLast()->setTooltip ("Enable this to disable the status bar in your app."); + props.add (new BooleanPropertyComponent (getSetting ("UIStatusBarHidden"), "Status Bar Hidden", "Enabled"), + "Enable this to disable the status bar in your app."); } + + props.add (new TextPropertyComponent (getPListToMerge(), "Custom PList", 8192, true), + "You can paste the contents of an XML PList file in here, and the settings that it contains will override any " + "settings that the Introjucer creates. BEWARE! When doing this, be careful to remove from the XML any " + "values that you DO want the introjucer to change!"); } void launchProject() @@ -344,8 +351,15 @@ private: if (! xcodeCreatePList) return; - XmlElement plist ("plist"); - XmlElement* dict = plist.createNewChildElement ("dict"); + ScopedPointer plist (XmlDocument::parse (getPListToMerge().toString())); + + if (plist == nullptr || ! plist->hasTagName ("plist")) + plist = new XmlElement ("plist"); + + XmlElement* dict = plist->getChildByName ("dict"); + + if (dict == nullptr) + dict = plist->createNewChildElement ("dict"); if (iOS) addPlistDictionaryKeyBool (dict, "LSRequiresIPhoneOS", true); @@ -394,7 +408,7 @@ private: dict->addChildElement (new XmlElement (xcodeExtraPListEntries.getReference(i))); MemoryOutputStream mo; - plist.writeToStream (mo, ""); + plist->writeToStream (mo, ""); overwriteFileIfDifferentOrThrow (infoPlistFile, mo); } @@ -672,7 +686,24 @@ private: static void addPlistDictionaryKey (XmlElement* xml, const String& key, const String& value) { - xml->createNewChildElement ("key")->addTextElement (key); + forEachXmlChildElementWithTagName (*xml, e, "key") + { + if (e->getAllSubText().trim().equalsIgnoreCase (key)) + { + if (e->getNextElement() != nullptr && e->getNextElement()->hasTagName ("key")) + { + // try to fix broken plist format.. + xml->removeChildElement (e, true); + break; + } + else + { + return; // (value already exists) + } + } + } + + xml->createNewChildElement ("key") ->addTextElement (key); xml->createNewChildElement ("string")->addTextElement (value); } diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.cpp b/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.cpp index 65d31ff5b8..a93a90b17f 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.cpp +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.cpp @@ -200,13 +200,13 @@ bool ProjectExporter::shouldFileBeCompiledByDefault (const RelativePath& file) c return file.hasFileExtension ("cpp;cc;c;cxx"); } -void ProjectExporter::createPropertyEditors (Array & props) +void ProjectExporter::createPropertyEditors (PropertyListBuilder& props) { - props.add (new TextPropertyComponent (getTargetLocation(), "Target Project Folder", 1024, false)); - props.getLast()->setTooltip ("The location of the folder in which the " + name + " project will be created. This path can be absolute, but it's much more sensible to make it relative to the jucer project directory."); + props.add (new TextPropertyComponent (getTargetLocation(), "Target Project Folder", 1024, false), + "The location of the folder in which the " + name + " project will be created. This path can be absolute, but it's much more sensible to make it relative to the jucer project directory."); - props.add (new TextPropertyComponent (getJuceFolder(), "Local JUCE folder", 1024, false)); - props.getLast()->setTooltip ("The location of the Juce library folder that the " + name + " project will use to when compiling. This can be an absolute path, or relative to the jucer project folder, but it must be valid on the filesystem of the machine you use to actually do the compiling."); + props.add (new TextPropertyComponent (getJuceFolder(), "Local JUCE folder", 1024, false), + "The location of the Juce library folder that the " + name + " project will use to when compiling. This can be an absolute path, or relative to the jucer project folder, but it must be valid on the filesystem of the machine you use to actually do the compiling."); OwnedArray modules; ModuleList moduleList; @@ -215,13 +215,13 @@ void ProjectExporter::createPropertyEditors (Array & props) for (int i = 0; i < modules.size(); ++i) modules.getUnchecked(i)->createPropertyEditors (*this, props); - props.add (new TextPropertyComponent (getExporterPreprocessorDefs(), "Extra Preprocessor Definitions", 32768, false)); - props.getLast()->setTooltip ("Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace or commas to separate the items - to include a space or comma in a definition, precede it with a backslash."); + props.add (new TextPropertyComponent (getExporterPreprocessorDefs(), "Extra Preprocessor Definitions", 32768, false), + "Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace or commas to separate the items - to include a space or comma in a definition, precede it with a backslash."); - props.add (new TextPropertyComponent (getExtraCompilerFlags(), "Extra compiler flags", 2048, false)); - props.getLast()->setTooltip ("Extra command-line flags to be passed to the compiler. This string can contain references to preprocessor definitions in the form ${NAME_OF_DEFINITION}, which will be replaced with their values."); - props.add (new TextPropertyComponent (getExtraLinkerFlags(), "Extra linker flags", 2048, false)); - props.getLast()->setTooltip ("Extra command-line flags to be passed to the linker. You might want to use this for adding additional libraries. This string can contain references to preprocessor definitions in the form ${NAME_OF_VALUE}, which will be replaced with their values."); + props.add (new TextPropertyComponent (getExtraCompilerFlags(), "Extra compiler flags", 2048, false), + "Extra command-line flags to be passed to the compiler. This string can contain references to preprocessor definitions in the form ${NAME_OF_DEFINITION}, which will be replaced with their values."); + props.add (new TextPropertyComponent (getExtraLinkerFlags(), "Extra linker flags", 2048, false), + "Extra command-line flags to be passed to the linker. You might want to use this for adding additional libraries. This string can contain references to preprocessor definitions in the form ${NAME_OF_VALUE}, which will be replaced with their values."); } StringPairArray ProjectExporter::getAllPreprocessorDefs (const Project::BuildConfiguration& config) const diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.h index b3b7a7d6fe..cb52dffa31 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.h @@ -34,20 +34,18 @@ //============================================================================== class ProjectExporter { -protected: - //============================================================================== - ProjectExporter (Project& project, const ValueTree& settings); - public: + ProjectExporter (Project&, const ValueTree& settings); virtual ~ProjectExporter(); static int getNumExporters(); static StringArray getExporterNames(); - static ProjectExporter* createNewExporter (Project& project, const int index); - static ProjectExporter* createNewExporter (Project& project, const String& name); - static ProjectExporter* createExporter (Project& project, const ValueTree& settings); - static ProjectExporter* createPlatformDefaultExporter (Project& project); + static ProjectExporter* createNewExporter (Project&, const int index); + static ProjectExporter* createNewExporter (Project&, const String& name); + static ProjectExporter* createExporter (Project&, const ValueTree& settings); + static ProjectExporter* createPlatformDefaultExporter (Project&); + static StringArray getDefaultExporters(); //============================================================================= @@ -55,7 +53,7 @@ public: virtual int getLaunchPreferenceOrderForCurrentOS() = 0; virtual bool isPossibleForCurrentProject() = 0; virtual bool usesMMFiles() const = 0; - virtual void createPropertyEditors (Array & props); + virtual void createPropertyEditors (PropertyListBuilder&); virtual void launchProject() = 0; virtual void create() = 0; // may throw a SaveError virtual bool shouldFileBeCompiledByDefault (const RelativePath& path) const; @@ -67,7 +65,7 @@ public: virtual bool isOSX() const { return false; } //============================================================================== - String getName() const { return name; } + String getName() const { return name; } File getTargetFolder() const; Project& getProject() noexcept { return project; } @@ -76,11 +74,11 @@ public: const ValueTree& getSettings() const { return settings; } Value getSetting (const Identifier& name_) const { return settings.getPropertyAsValue (name_, project.getUndoManagerFor (settings)); } - Value getJuceFolder() const { return getSetting (Ids::juceFolder); } - Value getTargetLocation() const { return getSetting (Ids::targetFolder); } + Value getJuceFolder() const { return getSetting (Ids::juceFolder); } + Value getTargetLocation() const { return getSetting (Ids::targetFolder); } - Value getExtraCompilerFlags() const { return getSetting (Ids::extraCompilerFlags); } - Value getExtraLinkerFlags() const { return getSetting (Ids::extraLinkerFlags); } + Value getExtraCompilerFlags() const { return getSetting (Ids::extraCompilerFlags); } + Value getExtraLinkerFlags() const { return getSetting (Ids::extraLinkerFlags); } Value getExporterPreprocessorDefs() const { return getSetting (Ids::extraDefs); } @@ -89,8 +87,7 @@ public: // includes exporter + project defs.. StringPairArray getAllPreprocessorDefs() const; - String replacePreprocessorTokens (const Project::BuildConfiguration& config, - const String& sourceString) const; + String replacePreprocessorTokens (const Project::BuildConfiguration&, const String& sourceString) const; // This adds the quotes, and may return angle-brackets, eg: or normal quotes. String getIncludePathForFileInJuceFolder (const String& pathFromJuceFolder, const File& targetIncludeFile) const; diff --git a/extras/Introjucer/Source/Project/jucer_AudioPluginModule.h b/extras/Introjucer/Source/Project/jucer_AudioPluginModule.h index 653657aaa8..43f62d6fb6 100644 --- a/extras/Introjucer/Source/Project/jucer_AudioPluginModule.h +++ b/extras/Introjucer/Source/Project/jucer_AudioPluginModule.h @@ -157,10 +157,10 @@ namespace VSTHelpers } } - static void createVSTPathEditor (const ProjectExporter& exporter, Array & props) + static void createVSTPathEditor (const ProjectExporter& exporter, PropertyListBuilder& props) { - props.add (new TextPropertyComponent (getVSTFolder (exporter), "VST Folder", 1024, false)); - props.getLast()->setTooltip ("If you're building a VST, this must be the folder containing the VST SDK. This should be an absolute path."); + props.add (new TextPropertyComponent (getVSTFolder (exporter), "VST Folder", 1024, false), + "If you're building a VST, this must be the folder containing the VST SDK. This should be an absolute path."); } static void fixMissingVSTValues (const ProjectExporter& exporter) @@ -191,7 +191,7 @@ namespace VSTHelpers exporter.extraSearchPaths.add (juceWrapperFolder.toUnixStyle()); } - static void createPropertyEditors (const ProjectExporter& exporter, Array & props) + static void createPropertyEditors (const ProjectExporter& exporter, PropertyListBuilder& props) { fixMissingVSTValues (exporter); createVSTPathEditor (exporter, props); @@ -325,14 +325,14 @@ namespace RTASHelpers addExtraSearchPaths (exporter); } - static void createPropertyEditors (const ProjectExporter& exporter, Array & props) + static void createPropertyEditors (const ProjectExporter& exporter, PropertyListBuilder& props) { if (exporter.isXcode() || exporter.isVisualStudio()) { fixMissingRTASValues (exporter); - props.add (new TextPropertyComponent (getRTASFolder (exporter), "RTAS Folder", 1024, false)); - props.getLast()->setTooltip ("If you're building an RTAS, this must be the folder containing the RTAS SDK. This should be an absolute path."); + 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."); } } } diff --git a/extras/Introjucer/Source/Project/jucer_Module.cpp b/extras/Introjucer/Source/Project/jucer_Module.cpp index 0396dd9e0f..dcdcec0948 100644 --- a/extras/Introjucer/Source/Project/jucer_Module.cpp +++ b/extras/Introjucer/Source/Project/jucer_Module.cpp @@ -516,7 +516,7 @@ void LibraryModule::prepareExporter (ProjectExporter& exporter, ProjectSaver& pr } } -void LibraryModule::createPropertyEditors (const ProjectExporter& exporter, Array & props) const +void LibraryModule::createPropertyEditors (const ProjectExporter& exporter, PropertyListBuilder& props) const { if (isVSTPluginHost (exporter.getProject())) VSTHelpers::createVSTPathEditor (exporter, props); diff --git a/extras/Introjucer/Source/Project/jucer_Module.h b/extras/Introjucer/Source/Project/jucer_Module.h index dd52c372cc..e73e0ee7aa 100644 --- a/extras/Introjucer/Source/Project/jucer_Module.h +++ b/extras/Introjucer/Source/Project/jucer_Module.h @@ -45,12 +45,12 @@ public: String getName() const { return moduleInfo ["name"].toString(); } String getDescription() const { return moduleInfo ["description"].toString(); } - void writeIncludes (ProjectSaver& projectSaver, OutputStream& out); - void prepareExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const; - void createPropertyEditors (const ProjectExporter& exporter, Array & props) const; - void getConfigFlags (Project& project, OwnedArray& flags) const; + void writeIncludes (ProjectSaver&, OutputStream&); + void prepareExporter (ProjectExporter&, ProjectSaver&) const; + void createPropertyEditors (const ProjectExporter&, PropertyListBuilder&) const; + void getConfigFlags (Project&, OwnedArray& flags) const; - static String getInfoFileName() { return "juce_module_info"; } + static String getInfoFileName() { return "juce_module_info"; } var moduleInfo; @@ -71,15 +71,15 @@ private: }; void findWildcardMatches (const File& localModuleFolder, const String& wildcardPath, Array& result) const; - void findAndAddCompiledCode (ProjectExporter& exporter, ProjectSaver& projectSaver, const File& localModuleFolder, Array& result) const; - void addBrowsableCode (ProjectExporter& exporter, const Array& compiled, const File& localModuleFolder) const; - void createLocalHeaderWrapper (ProjectSaver& projectSaver, const File& originalHeader, const File& localHeader) const; - RelativePath getModuleRelativeToProject (ProjectExporter& exporter) const; - RelativePath getModuleOrLocalCopyRelativeToProject (ProjectExporter& exporter, const File& localModuleFolder) const; + void findAndAddCompiledCode (ProjectExporter&, ProjectSaver&, const File& localModuleFolder, Array& result) const; + void addBrowsableCode (ProjectExporter&, const Array& compiled, const File& localModuleFolder) const; + void createLocalHeaderWrapper (ProjectSaver&, const File& originalHeader, const File& localHeader) const; + RelativePath getModuleRelativeToProject (ProjectExporter&) const; + RelativePath getModuleOrLocalCopyRelativeToProject (ProjectExporter&, const File& localModuleFolder) const; bool isPluginClient() const; - bool isAUPluginHost (const Project& project) const; - bool isVSTPluginHost (const Project& project) const; + bool isAUPluginHost (const Project&) const; + bool isVSTPluginHost (const Project&) const; }; //============================================================================== @@ -125,15 +125,15 @@ public: static bool isModulesFolder (const File& folder); static bool isJuceOrModulesFolder (const File& folder); - static File getDefaultModulesFolder (Project* project); + static File getDefaultModulesFolder (Project*); static bool isLocalModulesFolderValid(); - static File getLocalModulesFolder (Project* project); + static File getLocalModulesFolder (Project*); static void setLocalModulesFolder (const File& newFile); static File getModulesFolderForJuceOrModulesFolder (const File& f); - StringArray getExtraDependenciesNeeded (Project& project, const Module& m); + StringArray getExtraDependenciesNeeded (Project&, const Module&); //============================================================================== OwnedArray modules; diff --git a/extras/Introjucer/Source/Project/jucer_Project.cpp b/extras/Introjucer/Source/Project/jucer_Project.cpp index 42b497691a..d1d211eecd 100644 --- a/extras/Introjucer/Source/Project/jucer_Project.cpp +++ b/extras/Introjucer/Source/Project/jucer_Project.cpp @@ -282,13 +282,13 @@ const ProjectType& Project::getProjectType() const } //============================================================================== -void Project::createPropertyEditors (Array & props) +void Project::createPropertyEditors (PropertyListBuilder& props) { - props.add (new TextPropertyComponent (getProjectName(), "Project Name", 256, false)); - props.getLast()->setTooltip ("The name of the project."); + props.add (new TextPropertyComponent (getProjectName(), "Project Name", 256, false), + "The name of the project."); - props.add (new TextPropertyComponent (getVersion(), "Project Version", 16, false)); - props.getLast()->setTooltip ("The project's version number, This should be in the format major.minor.point"); + props.add (new TextPropertyComponent (getVersion(), "Project Version", 16, false), + "The project's version number, This should be in the format major.minor.point"); { StringArray projectTypeNames; @@ -305,8 +305,8 @@ void Project::createPropertyEditors (Array & props) props.add (new ChoicePropertyComponent (getProjectTypeValue(), "Project Type", projectTypeNames, projectTypeCodes)); } - props.add (new TextPropertyComponent (getBundleIdentifier(), "Bundle Identifier", 256, false)); - props.getLast()->setTooltip ("A unique identifier for this product, mainly for use in Mac builds. It should be something like 'com.yourcompanyname.yourproductname'"); + props.add (new TextPropertyComponent (getBundleIdentifier(), "Bundle Identifier", 256, false), + "A unique identifier for this product, mainly for use in Mac builds. It should be something like 'com.yourcompanyname.yourproductname'"); { OwnedArray images; @@ -326,20 +326,19 @@ void Project::createPropertyEditors (Array & props) ids.add (images.getUnchecked(i)->getID()); } - props.add (new ChoicePropertyComponent (getSmallIconImageItemID(), "Icon (small)", choices, ids)); - props.getLast()->setTooltip ("Sets an icon to use for the executable."); + props.add (new ChoicePropertyComponent (getSmallIconImageItemID(), "Icon (small)", choices, ids), + "Sets an icon to use for the executable."); - props.add (new ChoicePropertyComponent (getBigIconImageItemID(), "Icon (large)", choices, ids)); - props.getLast()->setTooltip ("Sets an icon to use for the executable."); + props.add (new ChoicePropertyComponent (getBigIconImageItemID(), "Icon (large)", choices, ids), + "Sets an icon to use for the executable."); } getProjectType().createPropertyEditors(*this, props); - props.add (new TextPropertyComponent (getProjectPreprocessorDefs(), "Preprocessor definitions", 32768, false)); - props.getLast()->setTooltip ("Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace or commas to separate the items - to include a space or comma in a definition, precede it with a backslash."); + props.add (new TextPropertyComponent (getProjectPreprocessorDefs(), "Preprocessor definitions", 32768, false), + "Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace or commas to separate the items - to include a space or comma in a definition, precede it with a backslash."); - for (int i = props.size(); --i >= 0;) - props.getUnchecked(i)->setPreferredHeight (22); + props.setPreferredHeight (22); } String Project::getVersionAsHex() const @@ -990,30 +989,30 @@ const char* const Project::BuildConfiguration::osxArch_32BitUniversal = "32BitUn const char* const Project::BuildConfiguration::osxArch_64BitUniversal = "64BitUniversal"; const char* const Project::BuildConfiguration::osxArch_64Bit = "64BitIntel"; -void Project::BuildConfiguration::createPropertyEditors (Array & props) +void Project::BuildConfiguration::createPropertyEditors (PropertyListBuilder& props) { - props.add (new TextPropertyComponent (getName(), "Name", 96, false)); - props.getLast()->setTooltip ("The name of this configuration."); + props.add (new TextPropertyComponent (getName(), "Name", 96, false), + "The name of this configuration."); - props.add (new BooleanPropertyComponent (isDebug(), "Debug mode", "Debugging enabled")); - props.getLast()->setTooltip ("If enabled, this means that the configuration should be built with debug synbols."); + props.add (new BooleanPropertyComponent (isDebug(), "Debug mode", "Debugging enabled"), + "If enabled, this means that the configuration should be built with debug synbols."); const char* optimisationLevels[] = { "No optimisation", "Optimise for size and speed", "Optimise for maximum speed", 0 }; const int optimisationLevelValues[] = { 1, 2, 3, 0 }; - props.add (new ChoicePropertyComponent (getOptimisationLevel(), "Optimisation", StringArray (optimisationLevels), Array (optimisationLevelValues))); - props.getLast()->setTooltip ("The optimisation level for this configuration"); + props.add (new ChoicePropertyComponent (getOptimisationLevel(), "Optimisation", StringArray (optimisationLevels), Array (optimisationLevelValues)), + "The optimisation level for this configuration"); - props.add (new TextPropertyComponent (getTargetBinaryName(), "Binary name", 256, false)); - props.getLast()->setTooltip ("The filename to use for the destination binary executable file. Don't add a suffix to this, because platform-specific suffixes will be added for each target platform."); + props.add (new TextPropertyComponent (getTargetBinaryName(), "Binary name", 256, false), + "The filename to use for the destination binary executable file. Don't add a suffix to this, because platform-specific suffixes will be added for each target platform."); - props.add (new TextPropertyComponent (getTargetBinaryRelativePath(), "Binary location", 1024, false)); - props.getLast()->setTooltip ("The folder in which the finished binary should be placed. Leave this blank to cause the binary to be placed in its default location in the build folder."); + props.add (new TextPropertyComponent (getTargetBinaryRelativePath(), "Binary location", 1024, false), + "The folder in which the finished binary should be placed. Leave this blank to cause the binary to be placed in its default location in the build folder."); - props.add (new TextPropertyComponent (getHeaderSearchPath(), "Header search path", 16384, false)); - props.getLast()->setTooltip ("Extra header search paths. Use semi-colons to separate multiple paths."); + props.add (new TextPropertyComponent (getHeaderSearchPath(), "Header search path", 16384, false), + "Extra header search paths. Use semi-colons to separate multiple paths."); - props.add (new TextPropertyComponent (getBuildConfigPreprocessorDefs(), "Preprocessor definitions", 32768, false)); - props.getLast()->setTooltip ("Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace or commas to separate the items - to include a space or comma in a definition, precede it with a backslash."); + props.add (new TextPropertyComponent (getBuildConfigPreprocessorDefs(), "Preprocessor definitions", 32768, false), + "Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace or commas to separate the items - to include a space or comma in a definition, precede it with a backslash."); if (getMacSDKVersion().toString().isEmpty()) getMacSDKVersion() = osxVersionDefault; @@ -1021,14 +1020,14 @@ void Project::BuildConfiguration::createPropertyEditors (Array (osxVersionValues))); - props.getLast()->setTooltip ("The version of OSX to link against in the XCode build."); + props.add (new ChoicePropertyComponent (getMacSDKVersion(), "OSX Base SDK Version", StringArray (osxVersions), Array (osxVersionValues)), + "The version of OSX to link against in the XCode build."); if (getMacCompatibilityVersion().toString().isEmpty()) getMacCompatibilityVersion() = osxVersionDefault; - props.add (new ChoicePropertyComponent (getMacCompatibilityVersion(), "OSX Compatibility Version", StringArray (osxVersions), Array (osxVersionValues))); - props.getLast()->setTooltip ("The minimum version of OSX that the target binary will be compatible with."); + props.add (new ChoicePropertyComponent (getMacCompatibilityVersion(), "OSX Compatibility Version", StringArray (osxVersions), Array (osxVersionValues)), + "The minimum version of OSX that the target binary will be compatible with."); const char* osxArch[] = { "Use Default", "Native architecture of build machine", "Universal Binary (32-bit)", "Universal Binary (64-bit)", "64-bit Intel", 0 }; const char* osxArchValues[] = { osxArch_Default, osxArch_Native, osxArch_32BitUniversal, osxArch_64BitUniversal, osxArch_64Bit, 0 }; @@ -1036,11 +1035,10 @@ void Project::BuildConfiguration::createPropertyEditors (Array (osxArchValues))); - props.getLast()->setTooltip ("The type of OSX binary that will be produced."); + props.add (new ChoicePropertyComponent (getMacArchitecture(), "OSX Architecture", StringArray (osxArch), Array (osxArchValues)), + "The type of OSX binary that will be produced."); - for (int i = props.size(); --i >= 0;) - props.getUnchecked(i)->setPreferredHeight (22); + props.setPreferredHeight (22); } StringPairArray Project::BuildConfiguration::getAllPreprocessorDefs() const diff --git a/extras/Introjucer/Source/Project/jucer_Project.h b/extras/Introjucer/Source/Project/jucer_Project.h index f1f646322b..99ef6da33c 100644 --- a/extras/Introjucer/Source/Project/jucer_Project.h +++ b/extras/Introjucer/Source/Project/jucer_Project.h @@ -69,7 +69,7 @@ public: //============================================================================== // Creates editors for the project settings - void createPropertyEditors (Array & properties); + void createPropertyEditors (PropertyListBuilder&); //============================================================================== // project types @@ -197,7 +197,7 @@ public: //============================================================================== Project& getProject() const { return *project; } - void createPropertyEditors (Array & properties); + void createPropertyEditors (PropertyListBuilder&); //============================================================================== Value getName() const { return getValue (Ids::name); } diff --git a/extras/Introjucer/Source/Project/jucer_ProjectInformationComponent.cpp b/extras/Introjucer/Source/Project/jucer_ProjectInformationComponent.cpp index 9e1f57b49e..dbd3fbd527 100644 --- a/extras/Introjucer/Source/Project/jucer_ProjectInformationComponent.cpp +++ b/extras/Introjucer/Source/Project/jucer_ProjectInformationComponent.cpp @@ -224,7 +224,7 @@ public: setEnabled (project.isModuleEnabled (moduleID)); clear(); - Array props; + PropertyListBuilder props; ScopedPointer module (moduleList.loadModule (moduleID)); @@ -240,16 +240,16 @@ public: } props.add (new BooleanPropertyComponent (project.shouldShowAllModuleFilesInProject (moduleID), - "Add source to project", "Make module files browsable in projects")); - props.getLast()->setTooltip ("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."); + "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."); props.add (new BooleanPropertyComponent (project.shouldCopyModuleFilesLocally (moduleID), - "Create local copy", "Copy the module into the project folder")); - props.getLast()->setTooltip ("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."); + "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."); StringArray possibleValues; possibleValues.add ("(Use Default)"); @@ -273,7 +273,7 @@ public: } } - addProperties (props); + addProperties (props.components); } private: @@ -406,7 +406,7 @@ public: addAndMakeVisible (&configs); addAndMakeVisible (&exporters); - Array props; + PropertyListBuilder props; props.add (new ModulesPanel (project)); modulesPanelGroup.setProperties (props); modulesPanelGroup.setName ("Modules"); @@ -441,7 +441,7 @@ public: void refreshAll() { { - Array props; + PropertyListBuilder props; project.createPropertyEditors (props); mainProjectInfoPanel.setProperties (props); mainProjectInfoPanel.setName ("Project Settings"); @@ -452,7 +452,7 @@ public: { PropertyGroup& pp = *configs.groups.getUnchecked(i); - Array props; + PropertyListBuilder props; project.getConfiguration (i).createPropertyEditors (props); pp.setProperties (props); } @@ -460,17 +460,13 @@ public: for (i = exporters.groups.size(); --i >= 0;) { PropertyGroup& pp = *exporters.groups.getUnchecked(i); - Array props; + PropertyListBuilder props; ScopedPointer exp (project.createExporter (i)); jassert (exp != nullptr); if (exp != nullptr) { exp->createPropertyEditors (props); - - for (int j = props.size(); --j >= 0;) - props.getUnchecked(j)->setPreferredHeight (22); - pp.setProperties (props); } } @@ -586,7 +582,7 @@ private: { public: PropertyGroup() - : deleteButton ("Delete"), preferredHeight (0) + : deleteButton ("Delete") { deleteButton.addListener (this); } @@ -602,37 +598,28 @@ private: deleteButton.setTooltip (tooltip); } - void setProperties (const Array& newProps) + void setProperties (const PropertyListBuilder& newProps) { properties.clear(); - properties.addArray (newProps); + properties.addArray (newProps.components); - preferredHeight = 32; for (int i = properties.size(); --i >= 0;) - { addAndMakeVisible (properties.getUnchecked(i)); - preferredHeight += properties.getUnchecked(i)->getPreferredHeight(); - } - } - - int getPreferredHeight() const - { - return preferredHeight; } int updateSize (int y, int width) { - setBounds (0, y, width, preferredHeight); + int height = 32; - y = 30; for (int i = 0; i < properties.size(); ++i) { PropertyComponent* pp = properties.getUnchecked(i); - pp->setBounds (10, y, width - 20, pp->getPreferredHeight()); - y += pp->getHeight(); + pp->setBounds (10, height, width - 20, pp->getPreferredHeight()); + height += pp->getHeight(); } - return preferredHeight; + setBounds (0, y, width, height); + return height; } void paint (Graphics& g) @@ -652,7 +639,6 @@ private: private: OwnedArray properties; TextButton deleteButton; - int preferredHeight; }; //============================================================================== diff --git a/extras/Introjucer/Source/Project/jucer_ProjectType.cpp b/extras/Introjucer/Source/Project/jucer_ProjectType.cpp index e9e586b0d4..cf5a4cfb37 100644 --- a/extras/Introjucer/Source/Project/jucer_ProjectType.cpp +++ b/extras/Introjucer/Source/Project/jucer_ProjectType.cpp @@ -72,7 +72,7 @@ public: { } - void createPropertyEditors (const Project& project, Array & props) const + void createPropertyEditors (const Project&, PropertyListBuilder&) const { } @@ -104,7 +104,7 @@ public: { } - void createPropertyEditors (const Project& project, Array & props) const + void createPropertyEditors (const Project& project, PropertyListBuilder& props) const { } @@ -135,7 +135,7 @@ public: { } - void createPropertyEditors (const Project& project, Array & props) const + void createPropertyEditors (const Project&, PropertyListBuilder&) const { } @@ -191,62 +191,62 @@ public: } } - void createPropertyEditors (const Project& project, Array & props) const + void createPropertyEditors (const Project& project, PropertyListBuilder& props) const { - props.add (new BooleanPropertyComponent (shouldBuildVST (project), "Build VST", "Enabled")); - props.getLast()->setTooltip ("Whether the project should produce a VST plugin."); - props.add (new BooleanPropertyComponent (shouldBuildAU (project), "Build AudioUnit", "Enabled")); - props.getLast()->setTooltip ("Whether the project should produce an AudioUnit plugin."); - props.add (new BooleanPropertyComponent (shouldBuildRTAS (project), "Build RTAS", "Enabled")); - props.getLast()->setTooltip ("Whether the project should produce an RTAS plugin."); + props.add (new BooleanPropertyComponent (shouldBuildVST (project), "Build VST", "Enabled"), + "Whether the project should produce a VST 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 TextPropertyComponent (getPluginName (project), "Plugin Name", 128, false)); - props.getLast()->setTooltip ("The name of your plugin (keep it short!)"); - props.add (new TextPropertyComponent (getPluginDesc (project), "Plugin Description", 256, false)); - props.getLast()->setTooltip ("A short description of your 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)); - props.getLast()->setTooltip ("The name of your company (cannot be blank)."); - props.add (new TextPropertyComponent (getPluginManufacturerCode (project), "Plugin Manufacturer Code", 4, false)); - props.getLast()->setTooltip ("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)); - props.getLast()->setTooltip ("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 (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", 256, false)); - props.getLast()->setTooltip ("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 TextPropertyComponent (getPluginChannelConfigs (project), "Plugin Channel Configurations", 256, 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")); - props.getLast()->setTooltip ("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 (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")); - props.getLast()->setTooltip ("Enable this if you want your plugin to accept midi messages."); + 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")); - props.getLast()->setTooltip ("Enable this if your plugin is going to produce 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")); - props.getLast()->setTooltip ("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 (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 TextPropertyComponent (getPluginTailLengthSeconds (project), "Tail Length (in seconds)", 12, false)); - props.getLast()->setTooltip ("This indicates the length, in seconds, of the plugin's tail. This information may or may not be used by the host."); + props.add (new TextPropertyComponent (getPluginTailLengthSeconds (project), "Tail Length (in seconds)", 12, false), + "This indicates the length, in seconds, of the plugin's tail. This information may or may not be used by the host."); - props.add (new BooleanPropertyComponent (getPluginEditorNeedsKeyFocus (project), "Key Focus", "Plugin editor requires keyboard focus")); - props.getLast()->setTooltip ("Enable this if your plugin needs keyboard input - some hosts can be a bit funny about keyboard focus.."); + 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)); - props.getLast()->setTooltip ("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 (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 (getPluginAUCocoaViewClassName (project), "Plugin AU Cocoa View Name", 64, false)); - props.getLast()->setTooltip ("In an AU, this is the name of Cocoa class that creates the UI. Some hosts bizarrely display the class-name, so you might want to make it reflect your plugin. But the name must be " - "UNIQUE to this exact version of your plugin, to avoid objective-C linkage mix-ups that happen when different plugins containing the same class-name are loaded simultaneously."); + props.add (new TextPropertyComponent (getPluginAUCocoaViewClassName (project), "Plugin AU Cocoa View Name", 64, false), + "In an AU, this is the name of Cocoa class that creates the UI. Some hosts bizarrely display the class-name, so you might want to make it reflect your plugin. But the name must be " + "UNIQUE to this exact version of your plugin, to avoid objective-C linkage mix-ups that happen when different plugins containing the same class-name are loaded simultaneously."); - props.add (new TextPropertyComponent (getPluginRTASCategory (project), "Plugin RTAS Category", 64, false)); - props.getLast()->setTooltip ("(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 (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"); } void prepareExporter (ProjectExporter& exporter) const diff --git a/extras/Introjucer/Source/Project/jucer_ProjectType.h b/extras/Introjucer/Source/Project/jucer_ProjectType.h index 0a509b1a35..fb18b084b9 100644 --- a/extras/Introjucer/Source/Project/jucer_ProjectType.h +++ b/extras/Introjucer/Source/Project/jucer_ProjectType.h @@ -56,7 +56,7 @@ public: static const char* getAudioPluginTypeName(); virtual void setMissingProjectProperties (Project&) const = 0; - virtual void createPropertyEditors (const Project&, Array &) const = 0; + virtual void createPropertyEditors (const Project&, PropertyListBuilder&) const = 0; virtual void prepareExporter (ProjectExporter&) const = 0; protected: diff --git a/extras/Introjucer/Source/Utility/jucer_MiscUtilities.h b/extras/Introjucer/Source/Utility/jucer_MiscUtilities.h index 1d66c174df..a856dca58c 100644 --- a/extras/Introjucer/Source/Utility/jucer_MiscUtilities.h +++ b/extras/Introjucer/Source/Utility/jucer_MiscUtilities.h @@ -61,10 +61,10 @@ public: void timerCallback(); private: - static String findTip (Component*); - Component* lastComp; String lastTip; + + static String findTip (Component*); }; //============================================================================== @@ -79,6 +79,35 @@ public: RolloverHelpComp rollover; }; +//============================================================================== +class PropertyListBuilder +{ +public: + PropertyListBuilder() {} + + void add (PropertyComponent* propertyComp) + { + components.add (propertyComp); + } + + void add (PropertyComponent* propertyComp, const String& tooltip) + { + propertyComp->setTooltip (tooltip); + add (propertyComp); + } + + void setPreferredHeight (int height) + { + for (int j = components.size(); --j >= 0;) + components.getUnchecked(j)->setPreferredHeight (height); + } + + Array components; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyListBuilder); +}; + //============================================================================== class FloatingLabelComponent : public Component {