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

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

View File

@ -0,0 +1,326 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../ProjectSaving/jucer_ProjectExporter.h"
#include "../../Utility/UI/PropertyComponents/jucer_FilePathPropertyComponent.h"
#include "../../Utility/Helpers/jucer_ValueWithDefaultWrapper.h"
#include "jucer_NewProjectWizard.h"
//==============================================================================
class ItemHeader : public Component
{
public:
ItemHeader (StringRef name, StringRef description, const char* iconSvgData)
: nameLabel ({}, name),
descriptionLabel ({}, description),
icon (makeIcon (iconSvgData))
{
addAndMakeVisible (nameLabel);
nameLabel.setFont (18.0f);
nameLabel.setMinimumHorizontalScale (1.0f);
addAndMakeVisible (descriptionLabel);
descriptionLabel.setMinimumHorizontalScale (1.0f);
}
void resized() override
{
auto bounds = getLocalBounds();
auto topSlice = bounds.removeFromTop (50);
iconBounds = topSlice.removeFromRight (75);
nameLabel.setBounds (topSlice);
bounds.removeFromTop (10);
descriptionLabel.setBounds (bounds.removeFromTop (50));
bounds.removeFromTop (20);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
if (icon != nullptr)
icon->drawWithin (g, iconBounds.toFloat(), RectanglePlacement::centred, 1.0f);
}
private:
static std::unique_ptr<Drawable> makeIcon (const char* iconSvgData)
{
if (auto svg = XmlDocument::parse (iconSvgData))
return Drawable::createFromSVG (*svg);
return {};
}
Label nameLabel, descriptionLabel;
Rectangle<int> iconBounds;
std::unique_ptr<Drawable> icon;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemHeader)
};
//==============================================================================
class TemplateComponent : public Component
{
public:
TemplateComponent (const NewProjectTemplates::ProjectTemplate& temp,
std::function<void (std::unique_ptr<Project>)>&& createdCallback)
: projectTemplate (temp),
projectCreatedCallback (std::move (createdCallback)),
header (projectTemplate.displayName, projectTemplate.description, projectTemplate.icon)
{
createProjectButton.onClick = [this]
{
chooser = std::make_unique<FileChooser> ("Save Project", NewProjectWizard::getLastWizardFolder());
auto browserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
chooser->launchAsync (browserFlags, [this] (const FileChooser& fc)
{
auto dir = fc.getResult();
if (dir == File{})
return;
SafePointer<TemplateComponent> safeThis { this };
NewProjectWizard::createNewProject (projectTemplate,
dir.getChildFile (projectNameValue.get().toString()),
projectNameValue.get(),
modulesValue.get(),
exportersValue.get(),
fileOptionsValue.get(),
modulePathValue.getCurrentValue(),
modulePathValue.getWrappedValueWithDefault().isUsingDefault(),
[safeThis, dir] (std::unique_ptr<Project> project)
{
if (safeThis == nullptr)
return;
safeThis->projectCreatedCallback (std::move (project));
getAppSettings().lastWizardFolder = dir;
});
});
};
addAndMakeVisible (createProjectButton);
addAndMakeVisible (header);
modulePathValue.init ({ settingsTree, Ids::defaultJuceModulePath, nullptr },
getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()),
TargetOS::getThisOS());
panel.addProperties (buildPropertyList(), 2);
addAndMakeVisible (panel);
}
void resized() override
{
auto bounds = getLocalBounds().reduced (10);
header.setBounds (bounds.removeFromTop (150));
createProjectButton.setBounds (bounds.removeFromBottom (30).removeFromRight (150));
bounds.removeFromBottom (5);
panel.setBounds (bounds);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
private:
NewProjectTemplates::ProjectTemplate projectTemplate;
std::unique_ptr<FileChooser> chooser;
std::function<void (std::unique_ptr<Project>)> projectCreatedCallback;
ItemHeader header;
TextButton createProjectButton { "Create Project..." };
ValueTree settingsTree { "NewProjectSettings" };
ValueWithDefault projectNameValue { settingsTree, Ids::name, nullptr, "NewProject" },
modulesValue { settingsTree, Ids::dependencies_, nullptr, projectTemplate.requiredModules, "," },
exportersValue { settingsTree, Ids::exporters, nullptr, StringArray (ProjectExporter::getCurrentPlatformExporterTypeInfo().identifier.toString()), "," },
fileOptionsValue { settingsTree, Ids::file, nullptr, NewProjectTemplates::getVarForFileOption (projectTemplate.defaultFileOption) };
ValueWithDefaultWrapper modulePathValue;
PropertyPanel panel;
//==============================================================================
PropertyComponent* createProjectNamePropertyComponent()
{
return new TextPropertyComponent (projectNameValue, "Project Name", 1024, false);
}
PropertyComponent* createModulesPropertyComponent()
{
Array<var> moduleVars;
var requiredModules;
for (auto& m : getJUCEModules())
{
moduleVars.add (m);
if (projectTemplate.requiredModules.contains (m))
requiredModules.append (m);
}
modulesValue = requiredModules;
return new MultiChoicePropertyComponent (modulesValue, "Modules",
getJUCEModules(), moduleVars);
}
PropertyComponent* createModulePathPropertyComponent()
{
return new FilePathPropertyComponent (modulePathValue.getWrappedValueWithDefault(), "Path to Modules", true);
}
PropertyComponent* createExportersPropertyValue()
{
Array<var> exporterVars;
StringArray exporterNames;
for (auto& exporterTypeInfo : ProjectExporter::getExporterTypeInfos())
{
exporterVars.add (exporterTypeInfo.identifier.toString());
exporterNames.add (exporterTypeInfo.displayName);
}
return new MultiChoicePropertyComponent (exportersValue, "Exporters", exporterNames, exporterVars);
}
PropertyComponent* createFileCreationOptionsPropertyComponent()
{
Array<var> optionVars;
StringArray optionStrings;
for (auto& opt : projectTemplate.fileOptionsAndFiles)
{
optionVars.add (NewProjectTemplates::getVarForFileOption (opt.first));
optionStrings.add (NewProjectTemplates::getStringForFileOption (opt.first));
}
return new ChoicePropertyComponent (fileOptionsValue, "File Creation Options", optionStrings, optionVars);
}
Array<PropertyComponent*> buildPropertyList()
{
PropertyListBuilder builder;
builder.add (createProjectNamePropertyComponent());
builder.add (createModulesPropertyComponent());
builder.add (createModulePathPropertyComponent());
builder.add (createExportersPropertyValue());
if (! projectTemplate.fileOptionsAndFiles.empty())
builder.add (createFileCreationOptionsPropertyComponent());
return builder.components;
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemplateComponent)
};
//==============================================================================
class ExampleComponent : public Component
{
public:
ExampleComponent (const File& f, std::function<void (const File&)> selectedCallback)
: exampleFile (f),
metadata (parseJUCEHeaderMetadata (exampleFile)),
exampleSelectedCallback (std::move (selectedCallback)),
header (metadata[Ids::name].toString(), metadata[Ids::description].toString(), BinaryData::background_logo_svg),
codeViewer (doc, &cppTokeniser)
{
setTitle (exampleFile.getFileName());
setFocusContainerType (FocusContainerType::focusContainer);
addAndMakeVisible (header);
openExampleButton.onClick = [this] { exampleSelectedCallback (exampleFile); };
addAndMakeVisible (openExampleButton);
setupCodeViewer();
addAndMakeVisible (codeViewer);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
void resized() override
{
auto bounds = getLocalBounds().reduced (10);
header.setBounds (bounds.removeFromTop (125));
openExampleButton.setBounds (bounds.removeFromBottom (30).removeFromRight (150));
codeViewer.setBounds (bounds);
}
private:
void setupCodeViewer()
{
auto fileString = exampleFile.loadFileAsString();
doc.replaceAllContent (fileString);
codeViewer.setScrollbarThickness (6);
codeViewer.setReadOnly (true);
codeViewer.setTitle ("Code");
getAppSettings().appearance.applyToCodeEditor (codeViewer);
codeViewer.scrollToLine (findBestLineToScrollToForClass (StringArray::fromLines (fileString),
metadata[Ids::name].toString(),
metadata[Ids::type] == "AudioProcessor"));
}
//==============================================================================
File exampleFile;
var metadata;
std::function<void (const File&)> exampleSelectedCallback;
ItemHeader header;
CPlusPlusCodeTokeniser cppTokeniser;
CodeDocument doc;
CodeEditorComponent codeViewer;
TextButton openExampleButton { "Open Example..." };
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExampleComponent)
};

View File

@ -0,0 +1,251 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../Utility/Helpers/jucer_MiscUtilities.h"
//==============================================================================
namespace NewProjectTemplates
{
enum class ProjectCategory
{
application,
plugin,
library
};
inline String getProjectCategoryString (ProjectCategory category)
{
if (category == ProjectCategory::application) return "Application";
if (category == ProjectCategory::plugin) return "Plug-In";
if (category == ProjectCategory::library) return "Library";
jassertfalse;
return "Unknown";
}
enum class FileCreationOptions
{
noFiles,
main,
header,
headerAndCpp,
processorAndEditor
};
using FilenameAndContent = std::pair<String, String>;
using OptionAndFilenameAndContent = std::pair<FileCreationOptions, std::vector<FilenameAndContent>>;
using OptionsAndFiles = std::vector<OptionAndFilenameAndContent>;
struct ProjectTemplate
{
ProjectCategory category;
String displayName, description, projectTypeString;
const char* icon;
StringArray requiredModules;
OptionsAndFiles fileOptionsAndFiles;
FileCreationOptions defaultFileOption;
std::vector<FilenameAndContent> getFilesForOption (FileCreationOptions option) const
{
auto iter = std::find_if (fileOptionsAndFiles.begin(), fileOptionsAndFiles.end(),
[option] (const OptionAndFilenameAndContent& opt) { return opt.first == option; });
if (iter != fileOptionsAndFiles.end())
return iter->second;
return {};
}
};
inline bool isApplication (const ProjectTemplate& t) noexcept { return t.category == ProjectCategory::application; }
inline bool isPlugin (const ProjectTemplate& t) noexcept { return t.category == ProjectCategory::plugin; }
inline bool isLibrary (const ProjectTemplate& t) noexcept { return t.category == ProjectCategory::library; }
//==============================================================================
inline var getVarForFileOption (FileCreationOptions opt)
{
if (opt == FileCreationOptions::noFiles) return "none";
if (opt == FileCreationOptions::main) return "main";
if (opt == FileCreationOptions::header) return "header";
if (opt == FileCreationOptions::headerAndCpp) return "headercpp";
if (opt == FileCreationOptions::processorAndEditor) return "processoreditor";
jassertfalse;
return {};
}
inline FileCreationOptions getFileOptionForVar (var opt)
{
if (opt == "none") return FileCreationOptions::noFiles;
if (opt == "main") return FileCreationOptions::main;
if (opt == "header") return FileCreationOptions::header;
if (opt == "headercpp") return FileCreationOptions::headerAndCpp;
if (opt == "processoreditor") return FileCreationOptions::processorAndEditor;
jassertfalse;
return {};
}
inline String getStringForFileOption (FileCreationOptions opt)
{
if (opt == FileCreationOptions::noFiles) return "No Files";
if (opt == FileCreationOptions::main) return "Main.cpp";
if (opt == FileCreationOptions::header) return "Main.cpp + .h";
if (opt == FileCreationOptions::headerAndCpp) return "Main.cpp + .h/.cpp ";
if (opt == FileCreationOptions::processorAndEditor) return "Processor and Editor";
jassertfalse;
return {};
}
//==============================================================================
template <typename... Strings>
inline StringArray addAndReturn (StringArray arr, Strings... strings)
{
arr.addArray ({ strings... });
return arr;
}
inline std::vector<ProjectTemplate> getAllTemplates()
{
return
{
{ ProjectCategory::application,
"Blank", "Creates a blank JUCE GUI application.",
build_tools::ProjectType_GUIApp::getTypeName(),
BinaryData::wizard_GUI_svg,
getModulesRequiredForComponent(),
{},
FileCreationOptions::noFiles
},
{ ProjectCategory::application,
"GUI", "Creates a blank JUCE GUI application with a single window component.",
build_tools::ProjectType_GUIApp::getTypeName(),
BinaryData::wizard_GUI_svg,
getModulesRequiredForComponent(),
{
{ FileCreationOptions::noFiles, {} },
{ FileCreationOptions::main, { { "Main.cpp", "jucer_MainTemplate_NoWindow_cpp" } } },
{ FileCreationOptions::header, { { "Main.cpp", "jucer_MainTemplate_Window_cpp" },
{ "MainComponent.h", "jucer_ContentCompSimpleTemplate_h" } } },
{ FileCreationOptions::headerAndCpp, { { "Main.cpp", "jucer_MainTemplate_Window_cpp" },
{ "MainComponent.h", "jucer_ContentCompTemplate_h" },
{ "MainComponent.cpp", "jucer_ContentCompTemplate_cpp" } } }
},
FileCreationOptions::headerAndCpp },
{ ProjectCategory::application,
"Audio", "Creates a blank JUCE GUI application with a single window component and audio and MIDI in/out functions.",
build_tools::ProjectType_GUIApp::getTypeName(),
BinaryData::wizard_AudioApp_svg,
addAndReturn (getModulesRequiredForComponent(), "juce_audio_basics", "juce_audio_devices", "juce_audio_formats",
"juce_audio_processors", "juce_audio_utils", "juce_gui_extra"),
{
{ FileCreationOptions::header, { { "Main.cpp", "jucer_MainTemplate_Window_cpp" },
{ "MainComponent.h", "jucer_AudioComponentSimpleTemplate_h" } } },
{ FileCreationOptions::headerAndCpp, { { "Main.cpp", "jucer_MainTemplate_Window_cpp" },
{ "MainComponent.h", "jucer_AudioComponentTemplate_h" },
{ "MainComponent.cpp", "jucer_AudioComponentTemplate_cpp" } } }
},
FileCreationOptions::headerAndCpp },
{ ProjectCategory::application,
"Console", "Creates a command-line application without GUI support.",
build_tools::ProjectType_ConsoleApp::getTypeName(),
BinaryData::wizard_ConsoleApp_svg,
getModulesRequiredForConsole(),
{
{ FileCreationOptions::noFiles, {} },
{ FileCreationOptions::main, { { "Main.cpp", "jucer_MainConsoleAppTemplate_cpp" } } }
},
FileCreationOptions::main },
{ ProjectCategory::application,
"Animated", "Creates a JUCE GUI application which draws an animated graphical display.",
build_tools::ProjectType_GUIApp::getTypeName(),
BinaryData::wizard_AnimatedApp_svg,
addAndReturn (getModulesRequiredForComponent(), "juce_gui_extra"),
{
{ FileCreationOptions::header, { { "Main.cpp", "jucer_MainTemplate_Window_cpp" },
{ "MainComponent.h", "jucer_AudioComponentSimpleTemplate_h" } } },
{ FileCreationOptions::headerAndCpp, { { "Main.cpp", "jucer_MainTemplate_Window_cpp" },
{ "MainComponent.h", "jucer_AnimatedComponentTemplate_h" },
{ "MainComponent.cpp", "jucer_AnimatedComponentTemplate_cpp" } } }
},
FileCreationOptions::headerAndCpp },
{ ProjectCategory::application,
"OpenGL", "Creates a blank JUCE application with a single window component. "
"This component supports openGL drawing features including 3D model import and GLSL shaders.",
build_tools::ProjectType_GUIApp::getTypeName(),
BinaryData::wizard_OpenGL_svg,
addAndReturn (getModulesRequiredForComponent(), "juce_gui_extra", "juce_opengl"),
{
{ FileCreationOptions::header, { { "Main.cpp", "jucer_MainTemplate_Window_cpp" },
{ "MainComponent.h", "jucer_AudioComponentSimpleTemplate_h" } } },
{ FileCreationOptions::headerAndCpp, { { "Main.cpp", "jucer_MainTemplate_Window_cpp" },
{ "MainComponent.h", "jucer_OpenGLComponentTemplate_h" },
{ "MainComponent.cpp", "jucer_OpenGLComponentTemplate_cpp" } } }
},
FileCreationOptions::headerAndCpp },
{ ProjectCategory::plugin,
"Basic", "Creates an audio plug-in with a single window GUI and audio/MIDI IO functions.",
build_tools::ProjectType_AudioPlugin::getTypeName(),
BinaryData::wizard_AudioPlugin_svg,
getModulesRequiredForAudioProcessor(),
{
{ FileCreationOptions::processorAndEditor, { { "PluginProcessor.cpp", "jucer_AudioPluginFilterTemplate_cpp" },
{ "PluginProcessor.h", "jucer_AudioPluginFilterTemplate_h" },
{ "PluginEditor.cpp", "jucer_AudioPluginEditorTemplate_cpp" },
{ "PluginEditor.h", "jucer_AudioPluginEditorTemplate_h" } } }
},
FileCreationOptions::processorAndEditor
},
{ ProjectCategory::library,
"Static Library", "Creates a static library.",
build_tools::ProjectType_StaticLibrary::getTypeName(),
BinaryData::wizard_StaticLibrary_svg,
getModulesRequiredForConsole(),
{},
FileCreationOptions::noFiles
},
{ ProjectCategory::library,
"Dynamic Library", "Creates a dynamic library.",
build_tools::ProjectType_DLL::getTypeName(),
BinaryData::wizard_DLL_svg,
getModulesRequiredForConsole(),
{},
FileCreationOptions::noFiles
}
};
}
}

View File

@ -0,0 +1,310 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../jucer_Headers.h"
#include "../jucer_Application.h"
#include "../../ProjectSaving/jucer_ProjectExporter.h"
#include "jucer_NewProjectWizard.h"
//==============================================================================
static String getFileTemplate (const String& templateName)
{
int dataSize = 0;
if (auto* data = BinaryData::getNamedResource (templateName.toUTF8(), dataSize))
return String::fromUTF8 (data, dataSize);
jassertfalse;
return {};
}
static String getJuceHeaderInclude()
{
return CodeHelpers::createIncludePathIncludeStatement (Project::getJuceSourceHFilename());
}
static String getContentComponentName()
{
return "MainComponent";
}
using Opts = NewProjectTemplates::FileCreationOptions;
static bool shouldCreateHeaderFile (Opts opts) noexcept { return opts == Opts::header || opts == Opts::headerAndCpp; }
static bool shouldCreateCppFile (Opts opts) noexcept { return opts == Opts::headerAndCpp; }
static void doBasicProjectSetup (Project& project, const NewProjectTemplates::ProjectTemplate& projectTemplate, const String& name)
{
project.setTitle (name);
project.setProjectType (projectTemplate.projectTypeString);
project.getMainGroup().addNewSubGroup ("Source", 0);
project.getConfigFlag ("JUCE_STRICT_REFCOUNTEDPOINTER") = true;
project.getProjectValue (Ids::useAppConfig) = false;
project.getProjectValue (Ids::addUsingNamespaceToJuceHeader) = false;
if (! ProjucerApplication::getApp().getLicenseController().getCurrentState().canUnlockFullFeatures())
project.getProjectValue (Ids::displaySplashScreen) = true;
if (NewProjectTemplates::isPlugin (projectTemplate))
project.getConfigFlag ("JUCE_VST3_CAN_REPLACE_VST2") = 0;
}
static std::map<String, String> getSharedFileTokenReplacements()
{
return { { "%%app_headers%%", getJuceHeaderInclude() } };
}
static std::map<String, String> getApplicationFileTokenReplacements (const String& name,
NewProjectTemplates::FileCreationOptions fileOptions,
const File& sourceFolder)
{
auto tokenReplacements = getSharedFileTokenReplacements();
tokenReplacements.insert ({ "%%app_class_name%%",
build_tools::makeValidIdentifier (name + "Application", false, true, false) });
tokenReplacements.insert ({ "%%content_component_class%%",
getContentComponentName() });
tokenReplacements.insert ({ "%%include_juce%%",
getJuceHeaderInclude() });
if (shouldCreateHeaderFile (fileOptions))
tokenReplacements["%%app_headers%%"] << newLine
<< CodeHelpers::createIncludeStatement (sourceFolder.getChildFile ("MainComponent.h"),
sourceFolder.getChildFile ("Main.cpp"));
if (shouldCreateCppFile (fileOptions))
tokenReplacements.insert ({ "%%include_corresponding_header%%",
CodeHelpers::createIncludeStatement (sourceFolder.getChildFile ("MainComponent.h"),
sourceFolder.getChildFile ("MainComponent.cpp")) });
return tokenReplacements;
}
static std::map<String, String> getPluginFileTokenReplacements (const String& name,
const File& sourceFolder)
{
auto tokenReplacements = getSharedFileTokenReplacements();
auto processorCppFile = sourceFolder.getChildFile ("PluginProcessor.cpp");
auto processorHFile = processorCppFile.withFileExtension (".h");
auto editorCppFile = sourceFolder.getChildFile ("PluginEditor.cpp");
auto editorHFile = editorCppFile.withFileExtension (".h");
auto processorHInclude = CodeHelpers::createIncludeStatement (processorHFile, processorCppFile);
auto editorHInclude = CodeHelpers::createIncludeStatement (editorHFile, processorCppFile);
auto processorClassName = build_tools::makeValidIdentifier (name, false, true, false) + "AudioProcessor";
processorClassName = processorClassName.substring (0, 1).toUpperCase() + processorClassName.substring (1);
auto editorClassName = processorClassName + "Editor";
tokenReplacements.insert ({"%%filter_headers%%", processorHInclude + newLine + editorHInclude });
tokenReplacements.insert ({"%%filter_class_name%%", processorClassName });
tokenReplacements.insert ({"%%editor_class_name%%", editorClassName });
tokenReplacements.insert ({"%%editor_cpp_headers%%", processorHInclude + newLine + editorHInclude });
tokenReplacements.insert ({"%%editor_headers%%", getJuceHeaderInclude() + newLine + processorHInclude });
return tokenReplacements;
}
static bool addFiles (Project& project, const NewProjectTemplates::ProjectTemplate& projectTemplate,
const String& name, var fileOptionsVar, StringArray& failedFiles)
{
auto sourceFolder = project.getFile().getSiblingFile ("Source");
if (! sourceFolder.createDirectory())
{
failedFiles.add (sourceFolder.getFullPathName());
return false;
}
auto fileOptions = NewProjectTemplates::getFileOptionForVar (fileOptionsVar);
if (fileOptions == Opts::noFiles)
return true;
auto tokenReplacements = [&]() -> std::map<String, String>
{
if (NewProjectTemplates::isApplication (projectTemplate))
return getApplicationFileTokenReplacements (name, fileOptions, sourceFolder);
if (NewProjectTemplates::isPlugin (projectTemplate))
return getPluginFileTokenReplacements (name, sourceFolder);
jassertfalse;
return {};
}();
auto sourceGroup = project.getMainGroup().getOrCreateSubGroup ("Source");
for (auto& files : projectTemplate.getFilesForOption (fileOptions))
{
auto file = sourceFolder.getChildFile (files.first);
auto fileContent = getFileTemplate (files.second);
for (auto& tokenReplacement : tokenReplacements)
fileContent = fileContent.replace (tokenReplacement.first, tokenReplacement.second, false);
if (! build_tools::overwriteFileWithNewDataIfDifferent (file, fileContent))
{
failedFiles.add (file.getFullPathName());
return false;
}
sourceGroup.addFileAtIndex (file, -1, (file.hasFileExtension (sourceFileExtensions)));
}
return true;
}
static void addModules (Project& project, Array<var> modules, const String& modulePath, bool useGlobalPath)
{
AvailableModulesList list;
list.scanPaths ({ modulePath });
auto& projectModules = project.getEnabledModules();
for (auto& mod : list.getAllModules())
if (modules.contains (mod.first))
projectModules.addModule (mod.second, false, useGlobalPath);
for (auto& mod : projectModules.getModulesWithMissingDependencies())
projectModules.tryToFixMissingDependencies (mod);
}
static void addExporters (Project& project, Array<var> exporters)
{
for (auto exporter : exporters)
project.addNewExporter (exporter.toString());
for (Project::ExporterIterator exporter (project); exporter.next();)
for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
config->getValue (Ids::targetName) = project.getProjectFilenameRootString();
}
//==============================================================================
File NewProjectWizard::getLastWizardFolder()
{
if (getAppSettings().lastWizardFolder.isDirectory())
return getAppSettings().lastWizardFolder;
#if JUCE_WINDOWS
static File lastFolderFallback (File::getSpecialLocation (File::userDocumentsDirectory));
#else
static File lastFolderFallback (File::getSpecialLocation (File::userHomeDirectory));
#endif
return lastFolderFallback;
}
static void displayFailedFilesMessage (const StringArray& failedFiles)
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
TRANS("Errors in Creating Project!"),
TRANS("The following files couldn't be written:")
+ "\n\n"
+ failedFiles.joinIntoString ("\n", 0, 10));
}
template <typename Callback>
static void prepareDirectory (const File& targetFolder, Callback&& callback)
{
StringArray failedFiles;
if (! targetFolder.exists())
{
if (! targetFolder.createDirectory())
{
displayFailedFilesMessage ({ targetFolder.getFullPathName() });
return;
}
}
else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder))
{
AlertWindow::showOkCancelBox (MessageBoxIconType::InfoIcon,
TRANS("New JUCE Project"),
TRANS("You chose the folder:\n\nXFLDRX\n\n").replace ("XFLDRX", targetFolder.getFullPathName())
+ TRANS("This folder isn't empty - are you sure you want to create the project there?")
+ "\n\n"
+ TRANS("Any existing files with the same names may be overwritten by the new files."),
{},
{},
nullptr,
ModalCallbackFunction::create ([callback] (int result)
{
if (result != 0)
callback();
}));
return;
}
callback();
}
void NewProjectWizard::createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate,
const File& targetFolder, const String& name, var modules, var exporters, var fileOptions,
const String& modulePath, bool useGlobalModulePath,
std::function<void (std::unique_ptr<Project>)> callback)
{
prepareDirectory (targetFolder, [=]
{
auto project = std::make_unique<Project> (targetFolder.getChildFile (File::createLegalFileName (name))
.withFileExtension (Project::projectFileExtension));
doBasicProjectSetup (*project, projectTemplate, name);
StringArray failedFiles;
if (addFiles (*project, projectTemplate, name, fileOptions, failedFiles))
{
addExporters (*project, *exporters.getArray());
addModules (*project, *modules.getArray(), modulePath, useGlobalModulePath);
auto sharedProject = std::make_shared<std::unique_ptr<Project>> (std::move (project));
(*sharedProject)->saveAsync (false, true, [sharedProject, failedFiles, callback] (FileBasedDocument::SaveResult r)
{
auto uniqueProject = std::move (*sharedProject.get());
if (r == FileBasedDocument::savedOk)
{
uniqueProject->setChangedFlag (false);
uniqueProject->loadFrom (uniqueProject->getFile(), true);
callback (std::move (uniqueProject));
return;
}
auto failedFilesCopy = failedFiles;
failedFilesCopy.add (uniqueProject->getFile().getFullPathName());
displayFailedFilesMessage (failedFilesCopy);
});
return;
}
displayFailedFilesMessage (failedFiles);
});
}

View File

@ -0,0 +1,39 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_NewProjectTemplates.h"
//==============================================================================
namespace NewProjectWizard
{
File getLastWizardFolder();
void createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate,
const File& targetFolder, const String& name, var modules, var exporters, var fileOptions,
const String& modulePath, bool useGlobalModulePath,
std::function<void (std::unique_ptr<Project>)> callback);
}

View File

@ -0,0 +1,288 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../jucer_Headers.h"
#include "../jucer_Application.h"
#include "jucer_StartPageComponent.h"
#include "jucer_StartPageTreeHolder.h"
#include "jucer_NewProjectTemplates.h"
#include "jucer_ContentComponents.h"
//==============================================================================
struct ContentComponent : public Component
{
ContentComponent()
{
setTitle ("Content");
setFocusContainerType (FocusContainerType::focusContainer);
}
void resized() override
{
if (content != nullptr)
content->setBounds (getLocalBounds());
}
void setContent (std::unique_ptr<Component>&& newContent)
{
if (content.get() != newContent.get())
{
content = std::move (newContent);
addAndMakeVisible (content.get());
resized();
}
}
private:
std::unique_ptr<Component> content;
//==============================================================================
JUCE_LEAK_DETECTOR (ContentComponent)
};
//==============================================================================
static File findExampleFile (int dirIndex, int index)
{
auto dir = ProjucerApplication::getSortedExampleDirectories()[dirIndex];
return ProjucerApplication::getSortedExampleFilesInDirectory (dir)[index];
}
static std::unique_ptr<Component> createExampleProjectsTab (ContentComponent& content, std::function<void (const File&)> cb)
{
StringArray exampleCategories;
std::vector<StringArray> examples;
for (auto& dir : ProjucerApplication::getSortedExampleDirectories())
{
exampleCategories.add (dir.getFileName());
StringArray ex;
for (auto& f : ProjucerApplication::getSortedExampleFilesInDirectory (dir))
ex.add (f.getFileNameWithoutExtension());
examples.push_back (ex);
}
if (exampleCategories.isEmpty())
return nullptr;
auto selectedCallback = [&, cb] (int category, int index) mutable
{
content.setContent (std::make_unique<ExampleComponent> (findExampleFile (category, index), cb));
};
return std::make_unique<StartPageTreeHolder> ("Examples",
exampleCategories,
examples,
std::move (selectedCallback),
StartPageTreeHolder::Open::no);
}
//==============================================================================
static StringArray getAllTemplateCategoryStrings()
{
StringArray categories;
for (auto& t : NewProjectTemplates::getAllTemplates())
categories.addIfNotAlreadyThere (NewProjectTemplates::getProjectCategoryString (t.category));
return categories;
}
static std::vector<NewProjectTemplates::ProjectTemplate> getTemplatesInCategory (const String& category)
{
std::vector<NewProjectTemplates::ProjectTemplate> templates;
for (auto& t : NewProjectTemplates::getAllTemplates())
if (NewProjectTemplates::getProjectCategoryString (t.category) == category)
templates.push_back (t);
return templates;
}
static StringArray getAllTemplateNamesForCategory (const String& category)
{
StringArray types;
for (auto& t : getTemplatesInCategory (category))
types.add (t.displayName);
return types;
}
static std::unique_ptr<Component> createProjectTemplatesTab (ContentComponent& content,
std::function<void (std::unique_ptr<Project>&&)>&& cb)
{
auto categories = getAllTemplateCategoryStrings();
std::vector<StringArray> templateNames;
for (auto& c : categories)
templateNames.push_back (getAllTemplateNamesForCategory (c));
auto selectedCallback = [&, cb] (int category, int index)
{
auto categoryString = getAllTemplateCategoryStrings()[category];
auto templates = getTemplatesInCategory (categoryString);
content.setContent (std::make_unique<TemplateComponent> (templates[(size_t) index], std::move (cb)));
};
auto holder = std::make_unique<StartPageTreeHolder> ("Templates",
categories,
templateNames,
std::move (selectedCallback),
StartPageTreeHolder::Open::yes);
holder->setSelectedItem (categories[0], 1);
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wredundant-move")
return std::move (holder);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
//==============================================================================
struct ProjectTemplatesAndExamples : public TabbedComponent
{
ProjectTemplatesAndExamples (ContentComponent& c,
std::function<void (std::unique_ptr<Project>&&)>&& newProjectCb,
std::function<void (const File&)>&& exampleCb)
: TabbedComponent (TabbedButtonBar::Orientation::TabsAtTop),
content (c),
exampleSelectedCallback (std::move (exampleCb))
{
setTitle ("Templates and Examples");
setFocusContainerType (FocusContainerType::focusContainer);
addTab ("New Project",
Colours::transparentBlack,
createProjectTemplatesTab (content, std::move (newProjectCb)).release(),
true);
refreshExamplesTab();
}
void refreshExamplesTab()
{
auto wasOpen = (getCurrentTabIndex() == 1);
removeTab (1);
auto exampleTabs = createExampleProjectsTab (content, exampleSelectedCallback);
addTab ("Open Example",
Colours::transparentBlack,
exampleTabs == nullptr ? new SetJUCEPathComponent (*this) : exampleTabs.release(),
true);
if (wasOpen)
setCurrentTabIndex (1);
}
private:
//==============================================================================
struct SetJUCEPathComponent : public Component,
private ChangeListener
{
explicit SetJUCEPathComponent (ProjectTemplatesAndExamples& o)
: owner (o)
{
getGlobalProperties().addChangeListener (this);
setPathButton.setButtonText ("Set path to JUCE...");
setPathButton.onClick = [] { ProjucerApplication::getApp().showPathsWindow (true); };
addAndMakeVisible (setPathButton);
}
~SetJUCEPathComponent() override
{
getGlobalProperties().removeChangeListener (this);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
void resized() override
{
auto bounds = getLocalBounds().reduced (5);
bounds.removeFromTop (25);
setPathButton.setBounds (bounds.removeFromTop (25));
}
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
if (isValidJUCEExamplesDirectory (ProjucerApplication::getJUCEExamplesDirectoryPathFromGlobal()))
owner.refreshExamplesTab();
}
ProjectTemplatesAndExamples& owner;
TextButton setPathButton;
};
ContentComponent& content;
std::function<void (const File&)> exampleSelectedCallback;
};
//==============================================================================
StartPageComponent::StartPageComponent (std::function<void (std::unique_ptr<Project>&&)>&& newProjectCb,
std::function<void (const File&)>&& exampleCb)
: content (std::make_unique<ContentComponent>()),
tabs (std::make_unique<ProjectTemplatesAndExamples> (*content, std::move (newProjectCb), std::move (exampleCb)))
{
tabs->setOutline (0);
addAndMakeVisible (*tabs);
addAndMakeVisible (openExistingButton);
openExistingButton.setCommandToTrigger (&ProjucerApplication::getCommandManager(), CommandIDs::open, true);
addAndMakeVisible (*content);
setSize (900, 600);
}
void StartPageComponent::paint (Graphics& g)
{
g.fillAll (findColour (backgroundColourId));
}
void StartPageComponent::resized()
{
auto bounds = getLocalBounds().reduced (10);
auto tabBounds = bounds.removeFromLeft (bounds.getWidth() / 3);
openExistingButton.setBounds (tabBounds.removeFromBottom (30).reduced (10, 0));
tabBounds.removeFromBottom (5);
tabs->setBounds (tabBounds);
bounds.removeFromLeft (10);
content->setBounds (bounds);
}

View File

@ -0,0 +1,50 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
struct ContentComponent;
struct ProjectTemplatesAndExamples;
//==============================================================================
class StartPageComponent : public Component
{
public:
StartPageComponent (std::function<void (std::unique_ptr<Project>&&)>&& newProjectCb,
std::function<void (const File&)>&& exampleCb);
void paint (Graphics& g) override;
void resized() override;
private:
//==============================================================================
std::unique_ptr<ContentComponent> content;
std::unique_ptr<ProjectTemplatesAndExamples> tabs;
TextButton openExistingButton { "Open Existing Project..." };
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StartPageComponent)
};

View File

@ -0,0 +1,182 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class StartPageTreeHolder : public Component
{
public:
enum class Open { no, yes };
StartPageTreeHolder (const String& title,
const StringArray& headerNames,
const std::vector<StringArray>& itemNames,
std::function<void (int, int)>&& selectedCallback,
Open shouldBeOpen)
: headers (headerNames),
items (itemNames),
itemSelectedCallback (std::move (selectedCallback))
{
jassert (headers.size() == (int) items.size());
tree.setTitle (title);
tree.setRootItem (new TreeRootItem (*this));
tree.setRootItemVisible (false);
tree.setIndentSize (15);
tree.setDefaultOpenness (shouldBeOpen == Open::yes);
addAndMakeVisible (tree);
}
~StartPageTreeHolder() override
{
tree.deleteRootItem();
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
void resized() override
{
tree.setBounds (getLocalBounds());
}
void setSelectedItem (const String& category, int index)
{
auto* root = tree.getRootItem();
for (int i = root->getNumSubItems(); --i >=0;)
{
if (auto* item = root->getSubItem (i))
{
if (item->getUniqueName() == category)
item->getSubItem (index)->setSelected (true, true);
}
}
}
private:
//==============================================================================
class TreeSubItem : public TreeViewItem
{
public:
TreeSubItem (StartPageTreeHolder& o, const String& n, const StringArray& subItemsIn)
: owner (o), name (n), isHeader (subItemsIn.size() > 0)
{
for (auto& s : subItemsIn)
addSubItem (new TreeSubItem (owner, s, {}));
}
bool mightContainSubItems() override { return isHeader; }
bool canBeSelected() const override { return ! isHeader; }
int getItemWidth() const override { return -1; }
int getItemHeight() const override { return 25; }
String getUniqueName() const override { return name; }
String getAccessibilityName() override { return getUniqueName(); }
void paintOpenCloseButton (Graphics& g, const Rectangle<float>& area, Colour, bool isMouseOver) override
{
g.setColour (getOwnerView()->findColour (isSelected() ? defaultHighlightedTextColourId
: treeIconColourId));
TreeViewItem::paintOpenCloseButton (g, area, getOwnerView()->findColour (defaultIconColourId), isMouseOver);
}
void paintItem (Graphics& g, int w, int h) override
{
Rectangle<int> bounds (w, h);
auto shouldBeHighlighted = isSelected();
if (shouldBeHighlighted)
{
g.setColour (getOwnerView()->findColour (defaultHighlightColourId));
g.fillRect (bounds);
}
g.setColour (shouldBeHighlighted ? getOwnerView()->findColour (defaultHighlightedTextColourId)
: getOwnerView()->findColour (defaultTextColourId));
g.drawFittedText (name, bounds.reduced (5).withTrimmedLeft (10), Justification::centredLeft, 1);
}
void itemClicked (const MouseEvent& e) override
{
if (isSelected())
itemSelectionChanged (true);
if (e.mods.isPopupMenu() && mightContainSubItems())
setOpen (! isOpen());
}
void itemSelectionChanged (bool isNowSelected) override
{
jassert (! isHeader);
if (isNowSelected)
owner.itemSelectedCallback (getParentItem()->getIndexInParent(), getIndexInParent());
}
private:
StartPageTreeHolder& owner;
String name;
bool isHeader = false;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeSubItem)
};
struct TreeRootItem : public TreeViewItem
{
explicit TreeRootItem (StartPageTreeHolder& o)
: owner (o)
{
for (int i = 0; i < owner.headers.size(); ++i)
addSubItem (new TreeSubItem (owner, owner.headers[i], owner.items[(size_t) i]));
}
bool mightContainSubItems() override { return ! owner.headers.isEmpty();}
StartPageTreeHolder& owner;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeRootItem)
};
//==============================================================================
TreeView tree;
StringArray headers;
std::vector<StringArray> items;
std::function<void (int, int)> itemSelectedCallback;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StartPageTreeHolder)
};

View File

@ -0,0 +1,224 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_LicenseState.h"
#include "jucer_LicenseQueryThread.h"
//==============================================================================
class LicenseController : private Timer
{
public:
LicenseController()
{
checkLicense();
}
//==============================================================================
static LicenseState getGPLState()
{
return { LicenseState::Type::gpl, projucerMajorVersion, {}, {} };
}
LicenseState getCurrentState() const noexcept
{
return state;
}
void setState (const LicenseState& newState)
{
if (state != newState)
{
state = newState;
licenseStateToSettings (state, getGlobalProperties());
stateListeners.call ([] (LicenseStateListener& l) { l.licenseStateChanged(); });
}
}
void resetState()
{
setState ({});
}
void signIn (const String& email, const String& password,
std::function<void (const String&)> completionCallback)
{
licenseQueryThread.doSignIn (email, password,
[this, completionCallback] (LicenseQueryThread::ErrorMessageAndType error,
LicenseState newState)
{
completionCallback (error.first);
setState (newState);
});
}
void cancelSignIn()
{
licenseQueryThread.cancelRunningJobs();
}
//==============================================================================
struct LicenseStateListener
{
virtual ~LicenseStateListener() = default;
virtual void licenseStateChanged() = 0;
};
void addListener (LicenseStateListener* listenerToAdd)
{
stateListeners.add (listenerToAdd);
}
void removeListener (LicenseStateListener* listenerToRemove)
{
stateListeners.remove (listenerToRemove);
}
private:
//==============================================================================
static const char* getLicenseStateValue (LicenseState::Type type)
{
switch (type)
{
case LicenseState::Type::gpl: return "GPL";
case LicenseState::Type::personal: return "personal";
case LicenseState::Type::educational: return "edu";
case LicenseState::Type::indie: return "indie";
case LicenseState::Type::pro: return "pro";
case LicenseState::Type::none:
default: break;
}
return nullptr;
}
static LicenseState::Type getLicenseTypeFromValue (const String& d)
{
if (d == getLicenseStateValue (LicenseState::Type::gpl)) return LicenseState::Type::gpl;
if (d == getLicenseStateValue (LicenseState::Type::personal)) return LicenseState::Type::personal;
if (d == getLicenseStateValue (LicenseState::Type::educational)) return LicenseState::Type::educational;
if (d == getLicenseStateValue (LicenseState::Type::indie)) return LicenseState::Type::indie;
if (d == getLicenseStateValue (LicenseState::Type::pro)) return LicenseState::Type::pro;
return LicenseState::Type::none;
}
static LicenseState licenseStateFromSettings (PropertiesFile& props)
{
if (auto licenseXml = props.getXmlValue ("license"))
{
// this is here for backwards compatibility with old-style settings files using XML text elements
if (licenseXml->getChildElementAllSubText ("type", {}).isNotEmpty())
{
auto stateFromOldSettings = [&licenseXml]() -> LicenseState
{
return { getLicenseTypeFromValue (licenseXml->getChildElementAllSubText ("type", {})),
licenseXml->getChildElementAllSubText ("version", "-1").getIntValue(),
licenseXml->getChildElementAllSubText ("username", {}),
licenseXml->getChildElementAllSubText ("authToken", {}) };
}();
licenseStateToSettings (stateFromOldSettings, props);
return stateFromOldSettings;
}
return { getLicenseTypeFromValue (licenseXml->getStringAttribute ("type", {})),
licenseXml->getIntAttribute ("version", -1),
licenseXml->getStringAttribute ("username", {}),
licenseXml->getStringAttribute ("authToken", {}) };
}
return {};
}
static void licenseStateToSettings (const LicenseState& state, PropertiesFile& props)
{
props.removeValue ("license");
if (state.isSignedIn())
{
XmlElement licenseXml ("license");
if (auto* typeString = getLicenseStateValue (state.type))
licenseXml.setAttribute ("type", typeString);
licenseXml.setAttribute ("version", state.version);
licenseXml.setAttribute ("username", state.username);
licenseXml.setAttribute ("authToken", state.authToken);
props.setValue ("license", &licenseXml);
}
props.saveIfNeeded();
}
//==============================================================================
void checkLicense()
{
if (state.authToken.isNotEmpty() && ! state.isGPL())
{
auto completionCallback = [this] (LicenseQueryThread::ErrorMessageAndType error,
LicenseState updatedState)
{
if (error == LicenseQueryThread::ErrorMessageAndType())
{
setState (updatedState);
}
else if ((error.second == LicenseQueryThread::ErrorType::busy
|| error.second == LicenseQueryThread::ErrorType::cancelled
|| error.second == LicenseQueryThread::ErrorType::connectionError)
&& ! hasRetriedLicenseCheck)
{
hasRetriedLicenseCheck = true;
startTimer (10000);
}
};
licenseQueryThread.checkLicenseValidity (state, std::move (completionCallback));
}
}
void timerCallback() override
{
stopTimer();
checkLicense();
}
//==============================================================================
#if JUCER_ENABLE_GPL_MODE
LicenseState state = getGPLState();
#else
LicenseState state = licenseStateFromSettings (getGlobalProperties());
#endif
ListenerList<LicenseStateListener> stateListeners;
LicenseQueryThread licenseQueryThread;
bool hasRetriedLicenseCheck = false;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseController)
};

View File

@ -0,0 +1,371 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
namespace LicenseHelpers
{
inline LicenseState::Type licenseTypeForString (const String& licenseString)
{
if (licenseString == "juce-pro") return LicenseState::Type::pro;
if (licenseString == "juce-indie") return LicenseState::Type::indie;
if (licenseString == "juce-edu") return LicenseState::Type::educational;
if (licenseString == "juce-personal") return LicenseState::Type::personal;
jassertfalse; // unknown type
return LicenseState::Type::none;
}
using LicenseVersionAndType = std::pair<int, LicenseState::Type>;
inline LicenseVersionAndType findBestLicense (std::vector<LicenseVersionAndType>&& licenses)
{
if (licenses.size() == 1)
return licenses[0];
auto getValueForLicenceType = [] (LicenseState::Type type)
{
switch (type)
{
case LicenseState::Type::pro: return 4;
case LicenseState::Type::indie: return 3;
case LicenseState::Type::educational: return 2;
case LicenseState::Type::personal: return 1;
case LicenseState::Type::gpl:
case LicenseState::Type::none:
default: return -1;
}
};
std::sort (licenses.begin(), licenses.end(),
[getValueForLicenceType] (const LicenseVersionAndType& l1, const LicenseVersionAndType& l2)
{
if (l1.first > l2.first)
return true;
if (l1.first == l2.first)
return getValueForLicenceType (l1.second) > getValueForLicenceType (l2.second);
return false;
});
auto findFirstLicense = [&licenses] (bool isPaid)
{
auto iter = std::find_if (licenses.begin(), licenses.end(),
[isPaid] (const LicenseVersionAndType& l)
{
auto proOrIndie = (l.second == LicenseState::Type::pro || l.second == LicenseState::Type::indie);
return isPaid ? proOrIndie : ! proOrIndie;
});
return iter != licenses.end() ? *iter
: LicenseVersionAndType();
};
auto newestPaid = findFirstLicense (true);
auto newestFree = findFirstLicense (false);
if (newestPaid.first >= projucerMajorVersion || newestPaid.first >= newestFree.first)
return newestPaid;
return newestFree;
}
}
//==============================================================================
class LicenseQueryThread
{
public:
enum class ErrorType
{
busy,
cancelled,
connectionError,
webResponseError
};
using ErrorMessageAndType = std::pair<String, ErrorType>;
using LicenseQueryCallback = std::function<void (ErrorMessageAndType, LicenseState)>;
//==============================================================================
LicenseQueryThread() = default;
void checkLicenseValidity (const LicenseState& state, LicenseQueryCallback completionCallback)
{
if (jobPool.getNumJobs() > 0)
{
completionCallback ({ {}, ErrorType::busy }, {});
return;
}
jobPool.addJob ([this, state, completionCallback]
{
auto updatedState = state;
auto result = runTask (std::make_unique<UserLicenseQuery> (state.authToken), updatedState);
WeakReference<LicenseQueryThread> weakThis (this);
MessageManager::callAsync ([weakThis, result, updatedState, completionCallback]
{
if (weakThis != nullptr)
completionCallback (result, updatedState);
});
});
}
void doSignIn (const String& email, const String& password, LicenseQueryCallback completionCallback)
{
cancelRunningJobs();
jobPool.addJob ([this, email, password, completionCallback]
{
LicenseState state;
auto result = runTask (std::make_unique<UserLogin> (email, password), state);
if (result == ErrorMessageAndType())
result = runTask (std::make_unique<UserLicenseQuery> (state.authToken), state);
if (result != ErrorMessageAndType())
state = {};
WeakReference<LicenseQueryThread> weakThis (this);
MessageManager::callAsync ([weakThis, result, state, completionCallback]
{
if (weakThis != nullptr)
completionCallback (result, state);
});
});
}
void cancelRunningJobs()
{
jobPool.removeAllJobs (true, 500);
}
private:
//==============================================================================
struct AccountEnquiryBase
{
virtual ~AccountEnquiryBase() = default;
virtual bool isPOSTLikeRequest() const = 0;
virtual String getEndpointURLSuffix() const = 0;
virtual StringPairArray getParameterNamesAndValues() const = 0;
virtual String getExtraHeaders() const = 0;
virtual int getSuccessCode() const = 0;
virtual String errorCodeToString (int) const = 0;
virtual bool parseServerResponse (const String&, LicenseState&) = 0;
};
struct UserLogin : public AccountEnquiryBase
{
UserLogin (const String& e, const String& p)
: userEmail (e), userPassword (p)
{
}
bool isPOSTLikeRequest() const override { return true; }
String getEndpointURLSuffix() const override { return "/authenticate/projucer"; }
int getSuccessCode() const override { return 200; }
StringPairArray getParameterNamesAndValues() const override
{
StringPairArray namesAndValues;
namesAndValues.set ("email", userEmail);
namesAndValues.set ("password", userPassword);
return namesAndValues;
}
String getExtraHeaders() const override
{
return "Content-Type: application/json";
}
String errorCodeToString (int errorCode) const override
{
switch (errorCode)
{
case 400: return "Please enter your email and password to sign in.";
case 401: return "Your email and password are incorrect.";
case 451: return "Access denied.";
default: return "Something went wrong, please try again.";
}
}
bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
{
auto json = JSON::parse (serverResponse);
licenseState.authToken = json.getProperty ("token", {}).toString();
licenseState.username = json.getProperty ("user", {}).getProperty ("username", {}).toString();
return (licenseState.authToken.isNotEmpty() && licenseState.username.isNotEmpty());
}
String userEmail, userPassword;
};
struct UserLicenseQuery : public AccountEnquiryBase
{
UserLicenseQuery (const String& authToken)
: userAuthToken (authToken)
{
}
bool isPOSTLikeRequest() const override { return false; }
String getEndpointURLSuffix() const override { return "/user/licences/projucer"; }
int getSuccessCode() const override { return 200; }
StringPairArray getParameterNamesAndValues() const override
{
return {};
}
String getExtraHeaders() const override
{
return "x-access-token: " + userAuthToken;
}
String errorCodeToString (int errorCode) const override
{
switch (errorCode)
{
case 401: return "User not found or could not be verified.";
default: return "User licenses info fetch failed (unknown error).";
}
}
bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
{
auto json = JSON::parse (serverResponse);
if (auto* licensesJson = json.getArray())
{
std::vector<LicenseHelpers::LicenseVersionAndType> licenses;
for (auto& license : *licensesJson)
{
auto version = license.getProperty ("product_version", {}).toString().trim();
auto type = license.getProperty ("licence_type", {}).toString();
auto status = license.getProperty ("status", {}).toString();
if (status == "active" && type.isNotEmpty() && version.isNotEmpty())
licenses.push_back ({ version.getIntValue(), LicenseHelpers::licenseTypeForString (type) });
}
if (! licenses.empty())
{
auto bestLicense = LicenseHelpers::findBestLicense (std::move (licenses));
licenseState.version = bestLicense.first;
licenseState.type = bestLicense.second;
}
return true;
}
return false;
}
String userAuthToken;
};
//==============================================================================
static String postDataStringAsJSON (const StringPairArray& parameters)
{
DynamicObject::Ptr d (new DynamicObject());
for (auto& key : parameters.getAllKeys())
d->setProperty (key, parameters[key]);
return JSON::toString (var (d.get()));
}
static ErrorMessageAndType runTask (std::unique_ptr<AccountEnquiryBase> accountEnquiryTask, LicenseState& state)
{
const ErrorMessageAndType cancelledError ("Cancelled.", ErrorType::cancelled);
const String endpointURL ("https://api.juce.com/api/v1");
URL url (endpointURL + accountEnquiryTask->getEndpointURLSuffix());
auto isPOST = accountEnquiryTask->isPOSTLikeRequest();
if (isPOST)
url = url.withPOSTData (postDataStringAsJSON (accountEnquiryTask->getParameterNamesAndValues()));
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
int statusCode = 0;
auto urlStream = url.createInputStream (URL::InputStreamOptions (isPOST ? URL::ParameterHandling::inPostData
: URL::ParameterHandling::inAddress)
.withExtraHeaders (accountEnquiryTask->getExtraHeaders())
.withConnectionTimeoutMs (5000)
.withStatusCode (&statusCode));
if (urlStream == nullptr)
return { "Failed to connect to the web server.", ErrorType::connectionError };
if (statusCode != accountEnquiryTask->getSuccessCode())
return { accountEnquiryTask->errorCodeToString (statusCode), ErrorType::webResponseError };
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
String response;
for (;;)
{
char buffer [8192] = "";
auto num = urlStream->read (buffer, sizeof (buffer));
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
if (num <= 0)
break;
response += buffer;
}
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
if (! accountEnquiryTask->parseServerResponse (response, state))
return { "Failed to parse server response.", ErrorType::webResponseError };
return {};
}
//==============================================================================
ThreadPool jobPool { 1 };
//==============================================================================
JUCE_DECLARE_WEAK_REFERENCEABLE (LicenseQueryThread)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseQueryThread)
};

View File

@ -0,0 +1,91 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
struct LicenseState
{
enum class Type
{
none,
gpl,
personal,
educational,
indie,
pro
};
LicenseState() = default;
LicenseState (Type t, int v, String user, String token)
: type (t), version (v), username (user), authToken (token)
{
}
bool operator== (const LicenseState& other) const noexcept
{
return type == other.type
&& version == other.version
&& username == other.username
&& authToken == other.authToken;
}
bool operator != (const LicenseState& other) const noexcept
{
return ! operator== (other);
}
bool isSignedIn() const noexcept { return isGPL() || (version > 0 && username.isNotEmpty()); }
bool isOldLicense() const noexcept { return isSignedIn() && version < projucerMajorVersion; }
bool isGPL() const noexcept { return type == Type::gpl; }
bool canUnlockFullFeatures() const noexcept
{
return isGPL() || (isSignedIn() && ! isOldLicense() && (type == Type::indie || type == Type::pro));
}
String getLicenseTypeString() const
{
switch (type)
{
case Type::none: return "No license";
case Type::gpl: return "GPL";
case Type::personal: return "Personal";
case Type::educational: return "Educational";
case Type::indie: return "Indie";
case Type::pro: return "Pro";
default: break;
};
jassertfalse;
return {};
}
Type type = Type::none;
int version = -1;
String username, authToken;
};

View File

@ -0,0 +1,283 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../Project/UI/jucer_UserAvatarComponent.h"
//==============================================================================
class LoginFormComponent : public Component
{
public:
LoginFormComponent (MainWindow& window)
: mainWindow (window)
{
setTitle ("Login");
setFocusContainerType (FocusContainerType::focusContainer);
addAndMakeVisible (emailBox);
emailBox.setTextToShowWhenEmpty ("Email", Colours::black.withAlpha (0.2f));
emailBox.setJustification (Justification::centredLeft);
emailBox.onReturnKey = [this] { submitDetails(); };
emailBox.setTitle ("Email");
addAndMakeVisible (passwordBox);
passwordBox.setTextToShowWhenEmpty ("Password", Colours::black.withAlpha (0.2f));
passwordBox.setPasswordCharacter ((juce_wchar) 0x2022);
passwordBox.setJustification (Justification::centredLeft);
passwordBox.onReturnKey = [this] { submitDetails(); };
passwordBox.setTitle ("Password");
addAndMakeVisible (logInButton);
logInButton.onClick = [this] { submitDetails(); };
addAndMakeVisible (enableGPLButton);
enableGPLButton.onClick = [this]
{
ProjucerApplication::getApp().getLicenseController().setState (LicenseController::getGPLState());
mainWindow.hideLoginFormOverlay();
};
addAndMakeVisible (userAvatar);
addAndMakeVisible (createAccountLabel);
createAccountLabel.setFont (Font (14.0f, Font::underlined));
createAccountLabel.addMouseListener (this, false);
createAccountLabel.setMouseCursor (MouseCursor::PointingHandCursor);
addAndMakeVisible (errorMessageLabel);
errorMessageLabel.setMinimumHorizontalScale (1.0f);
errorMessageLabel.setFont (12.0f);
errorMessageLabel.setColour (Label::textColourId, Colours::red);
errorMessageLabel.setVisible (false);
dismissButton.setShape (getLookAndFeel().getCrossShape (1.0f), false, true, false);
addAndMakeVisible (dismissButton);
dismissButton.onClick = [this] { mainWindow.hideLoginFormOverlay(); };
dismissButton.setTitle ("Dismiss");
setWantsKeyboardFocus (true);
setOpaque (true);
lookAndFeelChanged();
setSize (300, 350);
}
~LoginFormComponent() override
{
ProjucerApplication::getApp().getLicenseController().cancelSignIn();
}
void resized() override
{
auto bounds = getLocalBounds().reduced (20);
auto spacing = bounds.getHeight() / 20;
userAvatar.setBounds (bounds.removeFromTop (iconHeight).reduced ((bounds.getWidth() / 2) - (iconHeight / 2), 0));
errorMessageLabel.setBounds (bounds.removeFromTop (spacing));
bounds.removeFromTop (spacing / 2);
auto textEditorHeight = bounds.getHeight() / 5;
emailBox.setBounds (bounds.removeFromTop (textEditorHeight));
bounds.removeFromTop (spacing);
passwordBox.setBounds (bounds.removeFromTop (textEditorHeight));
bounds.removeFromTop (spacing * 2);
emailBox.setFont (Font ((float) textEditorHeight / 2.5f));
passwordBox.setFont (Font ((float) textEditorHeight / 2.5f));
logInButton.setBounds (bounds.removeFromTop (textEditorHeight));
auto slice = bounds.removeFromTop (textEditorHeight);
createAccountLabel.setBounds (slice.removeFromLeft (createAccountLabel.getFont().getStringWidth (createAccountLabel.getText()) + 5));
slice.removeFromLeft (15);
enableGPLButton.setBounds (slice.reduced (0, 5));
dismissButton.setBounds (getLocalBounds().reduced (10).removeFromTop (20).removeFromRight (20));
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId).contrasting (0.1f));
}
void mouseUp (const MouseEvent& event) override
{
if (event.eventComponent == &createAccountLabel)
URL ("https://juce.com/verification/register").launchInDefaultBrowser();
}
void lookAndFeelChanged() override
{
enableGPLButton.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
}
private:
class ProgressButton : public TextButton,
private Timer
{
public:
ProgressButton (const String& buttonName)
: TextButton (buttonName), text (buttonName)
{
}
void setBusy (bool shouldBeBusy)
{
isInProgress = shouldBeBusy;
if (isInProgress)
{
setEnabled (false);
setButtonText ({});
startTimerHz (30);
}
else
{
setEnabled (true);
setButtonText (text);
stopTimer();
}
}
void paint (Graphics& g) override
{
TextButton::paint (g);
if (isInProgress)
{
auto size = getHeight() - 10;
auto halfSize = size / 2;
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::white,
(getWidth() / 2) - halfSize, (getHeight() / 2) - halfSize,
size, size);
}
}
private:
void timerCallback() override
{
repaint();
}
String text;
bool isInProgress = false;
};
//==============================================================================
void updateLoginButtonStates (bool isLoggingIn)
{
logInButton.setBusy (isLoggingIn);
emailBox.setEnabled (! isLoggingIn);
passwordBox.setEnabled (! isLoggingIn);
}
void submitDetails()
{
auto loginFormError = checkLoginFormsAreValid();
if (loginFormError.isNotEmpty())
{
showErrorMessage (loginFormError);
return;
}
updateLoginButtonStates (true);
auto completionCallback = [weakThis = SafePointer<LoginFormComponent> { this }] (const String& errorMessage)
{
if (weakThis == nullptr)
return;
weakThis->updateLoginButtonStates (false);
if (errorMessage.isNotEmpty())
{
weakThis->showErrorMessage (errorMessage);
}
else
{
weakThis->hideErrorMessage();
weakThis->mainWindow.hideLoginFormOverlay();
ProjucerApplication::getApp().getCommandManager().commandStatusChanged();
}
};
ProjucerApplication::getApp().getLicenseController().signIn (emailBox.getText(), passwordBox.getText(),
std::move (completionCallback));
}
String checkLoginFormsAreValid() const
{
auto email = emailBox.getText();
if (email.isEmpty() || email.indexOfChar ('@') < 0)
return "Please enter a valid email.";
auto password = passwordBox.getText();
if (password.isEmpty() || password.length() < 8)
return "Please enter a valid password.";
return {};
}
void showErrorMessage (const String& errorMessage)
{
errorMessageLabel.setText (errorMessage, dontSendNotification);
errorMessageLabel.setVisible (true);
}
void hideErrorMessage()
{
errorMessageLabel.setText ({}, dontSendNotification);
errorMessageLabel.setVisible (false);
}
//==============================================================================
static constexpr int iconHeight = 50;
MainWindow& mainWindow;
TextEditor emailBox, passwordBox;
ProgressButton logInButton { "Sign In" };
TextButton enableGPLButton { "Enable GPL Mode" };
ShapeButton dismissButton { {},
findColour (treeIconColourId),
findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.2f)),
findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.4f)) };
UserAvatarComponent userAvatar { false };
Label createAccountLabel { {}, "Create an account" },
errorMessageLabel { {}, {} };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LoginFormComponent)
};

View File

@ -0,0 +1,103 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class AboutWindowComponent : public Component
{
public:
AboutWindowComponent()
{
addAndMakeVisible (titleLabel);
titleLabel.setJustificationType (Justification::centred);
titleLabel.setFont (Font (35.0f, Font::FontStyleFlags::bold));
auto buildDate = Time::getCompilationDate();
addAndMakeVisible (versionLabel);
versionLabel.setText ("JUCE v" + ProjucerApplication::getApp().getApplicationVersion()
+ "\nBuild date: " + String (buildDate.getDayOfMonth())
+ " " + Time::getMonthName (buildDate.getMonth(), true)
+ " " + String (buildDate.getYear()),
dontSendNotification);
versionLabel.setJustificationType (Justification::centred);
addAndMakeVisible (copyrightLabel);
copyrightLabel.setJustificationType (Justification::centred);
addAndMakeVisible (aboutButton);
aboutButton.setTooltip ( {} );
}
void resized() override
{
auto bounds = getLocalBounds();
bounds.removeFromBottom (20);
auto leftSlice = bounds.removeFromLeft (150);
auto centreSlice = bounds.withTrimmedRight (150);
juceLogoBounds = leftSlice.removeFromTop (150).toFloat();
juceLogoBounds.setWidth (juceLogoBounds.getWidth() + 100);
juceLogoBounds.setHeight (juceLogoBounds.getHeight() + 100);
auto titleHeight = 40;
centreSlice.removeFromTop ((centreSlice.getHeight() / 2) - (titleHeight / 2));
titleLabel.setBounds (centreSlice.removeFromTop (titleHeight));
centreSlice.removeFromTop (10);
versionLabel.setBounds (centreSlice.removeFromTop (40));
centreSlice.removeFromTop (10);
aboutButton.setBounds (centreSlice.removeFromTop (20));
copyrightLabel.setBounds (getLocalBounds().removeFromBottom (50));
}
void paint (Graphics& g) override
{
g.fillAll (findColour (backgroundColourId));
if (juceLogo != nullptr)
juceLogo->drawWithin (g, juceLogoBounds.translated (-75, -75), RectanglePlacement::centred, 1.0);
}
private:
Label titleLabel { "title", "PROJUCER" },
versionLabel { "version" },
copyrightLabel { "copyright", String (CharPointer_UTF8 ("\xc2\xa9")) + String (" 2020 Raw Material Software Limited") };
HyperlinkButton aboutButton { "About Us", URL ("https://juce.com") };
Rectangle<float> juceLogoBounds;
std::unique_ptr<Drawable> juceLogo { Drawable::createFromImageData (BinaryData::juce_icon_png,
BinaryData::juce_icon_pngSize) };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AboutWindowComponent)
};

View File

@ -0,0 +1,347 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../Utility/UI/PropertyComponents/jucer_ColourPropertyComponent.h"
//==============================================================================
class EditorColourSchemeWindowComponent : public Component
{
public:
EditorColourSchemeWindowComponent()
{
if (getAppSettings().monospacedFontNames.size() == 0)
changeContent (new AppearanceEditor::FontScanPanel());
else
changeContent (new AppearanceEditor::EditorPanel());
}
void paint (Graphics& g) override
{
g.fillAll (findColour (backgroundColourId));
}
void resized() override
{
content->setBounds (getLocalBounds());
}
void changeContent (Component* newContent)
{
content.reset (newContent);
addAndMakeVisible (newContent);
content->setBounds (getLocalBounds().reduced (10));
}
private:
std::unique_ptr<Component> content;
//==============================================================================
struct AppearanceEditor
{
struct FontScanPanel : public Component,
private Timer
{
FontScanPanel()
{
fontsToScan = Font::findAllTypefaceNames();
startTimer (1);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (backgroundColourId));
g.setFont (14.0f);
g.setColour (findColour (defaultTextColourId));
g.drawFittedText ("Scanning for fonts..", getLocalBounds(), Justification::centred, 2);
const auto size = 30;
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::white, (getWidth() - size) / 2, getHeight() / 2 - 50, size, size);
}
void timerCallback() override
{
repaint();
if (fontsToScan.size() == 0)
{
getAppSettings().monospacedFontNames = fontsFound;
if (auto* owner = findParentComponentOfClass<EditorColourSchemeWindowComponent>())
owner->changeContent (new EditorPanel());
}
else
{
if (isMonospacedTypeface (fontsToScan[0]))
fontsFound.add (fontsToScan[0]);
fontsToScan.remove (0);
}
}
// A rather hacky trick to select only the fixed-pitch fonts..
// This is unfortunately a bit slow, but will work on all platforms.
static bool isMonospacedTypeface (const String& name)
{
const Font font (name, 20.0f, Font::plain);
const auto width = font.getStringWidth ("....");
return width == font.getStringWidth ("WWWW")
&& width == font.getStringWidth ("0000")
&& width == font.getStringWidth ("1111")
&& width == font.getStringWidth ("iiii");
}
StringArray fontsToScan, fontsFound;
};
//==============================================================================
struct EditorPanel : public Component
{
EditorPanel()
: loadButton ("Load Scheme..."),
saveButton ("Save Scheme...")
{
rebuildProperties();
addAndMakeVisible (panel);
addAndMakeVisible (loadButton);
addAndMakeVisible (saveButton);
loadButton.onClick = [this] { loadScheme(); };
saveButton.onClick = [this] { saveScheme (false); };
lookAndFeelChanged();
saveSchemeState();
}
~EditorPanel() override
{
if (hasSchemeBeenModifiedSinceSave())
saveScheme (true);
}
void rebuildProperties()
{
auto& scheme = getAppSettings().appearance;
Array<PropertyComponent*> props;
auto fontValue = scheme.getCodeFontValue();
props.add (FontNameValueSource::createProperty ("Code Editor Font", fontValue));
props.add (FontSizeValueSource::createProperty ("Font Size", fontValue));
const auto colourNames = scheme.getColourNames();
for (int i = 0; i < colourNames.size(); ++i)
props.add (new ColourPropertyComponent (nullptr, colourNames[i],
scheme.getColourValue (colourNames[i]),
Colours::white, false));
panel.clear();
panel.addProperties (props);
}
void resized() override
{
auto r = getLocalBounds();
panel.setBounds (r.removeFromTop (getHeight() - 28).reduced (10, 2));
loadButton.setBounds (r.removeFromLeft (getWidth() / 2).reduced (10, 1));
saveButton.setBounds (r.reduced (10, 1));
}
private:
PropertyPanel panel;
TextButton loadButton, saveButton;
Font codeFont;
Array<var> colourValues;
void saveScheme (bool isExit)
{
chooser = std::make_unique<FileChooser> ("Select a file in which to save this colour-scheme...",
getAppSettings().appearance.getSchemesFolder()
.getNonexistentChildFile ("Scheme", AppearanceSettings::getSchemeFileSuffix()),
AppearanceSettings::getSchemeFileWildCard());
auto chooserFlags = FileBrowserComponent::saveMode
| FileBrowserComponent::canSelectFiles
| FileBrowserComponent::warnAboutOverwriting;
chooser->launchAsync (chooserFlags, [this, isExit] (const FileChooser& fc)
{
if (fc.getResult() == File{})
{
if (isExit)
restorePreviousScheme();
return;
}
File file (fc.getResult().withFileExtension (AppearanceSettings::getSchemeFileSuffix()));
getAppSettings().appearance.writeToFile (file);
getAppSettings().appearance.refreshPresetSchemeList();
saveSchemeState();
ProjucerApplication::getApp().selectEditorColourSchemeWithName (file.getFileNameWithoutExtension());
});
}
void loadScheme()
{
chooser = std::make_unique<FileChooser> ("Please select a colour-scheme file to load...",
getAppSettings().appearance.getSchemesFolder(),
AppearanceSettings::getSchemeFileWildCard());
auto chooserFlags = FileBrowserComponent::openMode
| FileBrowserComponent::canSelectFiles;
chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
{
if (fc.getResult() == File{})
return;
if (getAppSettings().appearance.readFromFile (fc.getResult()))
{
rebuildProperties();
saveSchemeState();
}
});
}
void lookAndFeelChanged() override
{
loadButton.setColour (TextButton::buttonColourId,
findColour (secondaryButtonBackgroundColourId));
}
void saveSchemeState()
{
auto& appearance = getAppSettings().appearance;
const auto colourNames = appearance.getColourNames();
codeFont = appearance.getCodeFont();
colourValues.clear();
for (int i = 0; i < colourNames.size(); ++i)
colourValues.add (appearance.getColourValue (colourNames[i]).getValue());
}
bool hasSchemeBeenModifiedSinceSave()
{
auto& appearance = getAppSettings().appearance;
const auto colourNames = appearance.getColourNames();
if (codeFont != appearance.getCodeFont())
return true;
for (int i = 0; i < colourNames.size(); ++i)
if (colourValues[i] != appearance.getColourValue (colourNames[i]).getValue())
return true;
return false;
}
void restorePreviousScheme()
{
auto& appearance = getAppSettings().appearance;
const auto colourNames = appearance.getColourNames();
appearance.getCodeFontValue().setValue (codeFont.toString());
for (int i = 0; i < colourNames.size(); ++i)
appearance.getColourValue (colourNames[i]).setValue (colourValues[i]);
}
std::unique_ptr<FileChooser> chooser;
JUCE_DECLARE_NON_COPYABLE (EditorPanel)
};
//==============================================================================
struct FontNameValueSource : public ValueSourceFilter
{
FontNameValueSource (const Value& source) : ValueSourceFilter (source) {}
var getValue() const override
{
return Font::fromString (sourceValue.toString()).getTypefaceName();
}
void setValue (const var& newValue) override
{
auto font = Font::fromString (sourceValue.toString());
font.setTypefaceName (newValue.toString().isEmpty() ? Font::getDefaultMonospacedFontName()
: newValue.toString());
sourceValue = font.toString();
}
static ChoicePropertyComponent* createProperty (const String& title, const Value& value)
{
auto fontNames = getAppSettings().monospacedFontNames;
Array<var> values;
values.add (Font::getDefaultMonospacedFontName());
values.add (var());
for (int i = 0; i < fontNames.size(); ++i)
values.add (fontNames[i]);
StringArray names;
names.add ("<Default Monospaced>");
names.add (String());
names.addArray (getAppSettings().monospacedFontNames);
return new ChoicePropertyComponent (Value (new FontNameValueSource (value)),
title, names, values);
}
};
//==============================================================================
struct FontSizeValueSource : public ValueSourceFilter
{
FontSizeValueSource (const Value& source) : ValueSourceFilter (source) {}
var getValue() const override
{
return Font::fromString (sourceValue.toString()).getHeight();
}
void setValue (const var& newValue) override
{
sourceValue = Font::fromString (sourceValue.toString()).withHeight (newValue).toString();
}
static PropertyComponent* createProperty (const String& title, const Value& value)
{
return new SliderPropertyComponent (Value (new FontSizeValueSource (value)),
title, 5.0, 40.0, 0.1, 0.5);
}
};
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorColourSchemeWindowComponent)
};

View File

@ -0,0 +1,89 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
struct FloatingToolWindow : public DialogWindow
{
FloatingToolWindow (const String& title,
const String& windowPosPropertyName,
Component* content,
std::unique_ptr<Component>& ownerPointer,
bool shouldBeResizable,
int defaultW, int defaultH,
int minW, int minH,
int maxW, int maxH)
: DialogWindow (title, content->findColour (secondaryBackgroundColourId), true, true),
windowPosProperty (windowPosPropertyName),
owner (ownerPointer)
{
setUsingNativeTitleBar (true);
setResizable (shouldBeResizable, shouldBeResizable);
setResizeLimits (minW, minH, maxW, maxH);
setContentOwned (content, false);
String windowState;
if (windowPosProperty.isNotEmpty())
windowState = getGlobalProperties().getValue (windowPosProperty);
if (windowState.isNotEmpty())
restoreWindowStateFromString (windowState);
else
centreAroundComponent (Component::getCurrentlyFocusedComponent(), defaultW, defaultH);
setVisible (true);
owner.reset (this);
}
~FloatingToolWindow() override
{
if (windowPosProperty.isNotEmpty())
getGlobalProperties().setValue (windowPosProperty, getWindowStateAsString());
}
void closeButtonPressed() override
{
owner.reset();
}
bool escapeKeyPressed() override
{
closeButtonPressed();
return true;
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
private:
String windowPosProperty;
std::unique_ptr<Component>& owner;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FloatingToolWindow)
};

View File

@ -0,0 +1,318 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../Utility/UI/PropertyComponents/jucer_LabelPropertyComponent.h"
//==============================================================================
class GlobalPathsWindowComponent : public Component,
private Timer,
private Value::Listener,
private ChangeListener
{
public:
GlobalPathsWindowComponent()
{
addChildComponent (rescanJUCEPathButton);
rescanJUCEPathButton.onClick = [this]
{
ProjucerApplication::getApp().rescanJUCEPathModules();
lastJUCEModulePath = getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get();
};
addChildComponent (rescanUserPathButton);
rescanUserPathButton.onClick = [this]
{
ProjucerApplication::getApp().rescanUserPathModules();
lastUserModulePath = getAppSettings().getStoredPath (Ids::defaultUserModulePath, TargetOS::getThisOS()).get();
};
addChildComponent (warnAboutJUCEPathButton);
warnAboutJUCEPathButton.setToggleState (ProjucerApplication::getApp().shouldPromptUserAboutIncorrectJUCEPath(),
dontSendNotification);
warnAboutJUCEPathButton.onClick = [this]
{
ProjucerApplication::getApp().setShouldPromptUserAboutIncorrectJUCEPath (warnAboutJUCEPathButton.getToggleState());
};
getGlobalProperties().addChangeListener (this);
addAndMakeVisible (resetToDefaultsButton);
resetToDefaultsButton.onClick = [this] { resetCurrentOSPathsToDefaults(); };
addAndMakeVisible (propertyViewport);
propertyViewport.setViewedComponent (&propertyGroup, false);
auto os = TargetOS::getThisOS();
if (os == TargetOS::osx) selectedOSValue = "osx";
else if (os == TargetOS::windows) selectedOSValue = "windows";
else if (os == TargetOS::linux) selectedOSValue = "linux";
selectedOSValue.addListener (this);
buildProps();
lastJUCEModulePath = getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get();
lastUserModulePath = getAppSettings().getStoredPath (Ids::defaultUserModulePath, TargetOS::getThisOS()).get();
}
~GlobalPathsWindowComponent() override
{
getGlobalProperties().removeChangeListener (this);
auto juceValue = getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS());
auto userValue = getAppSettings().getStoredPath (Ids::defaultUserModulePath, TargetOS::getThisOS());
if (juceValue.get() != lastJUCEModulePath) ProjucerApplication::getApp().rescanJUCEPathModules();
if (userValue.get() != lastUserModulePath) ProjucerApplication::getApp().rescanUserPathModules();
}
void paint (Graphics& g) override
{
g.fillAll (findColour (backgroundColourId));
}
void paintOverChildren (Graphics& g) override
{
g.setColour (findColour (defaultHighlightColourId).withAlpha (flashAlpha));
g.fillRect (boundsToHighlight);
}
void resized() override
{
auto b = getLocalBounds().reduced (10);
auto bottomBounds = b.removeFromBottom (80);
auto buttonBounds = bottomBounds.removeFromBottom (50);
rescanJUCEPathButton.setBounds (buttonBounds.removeFromLeft (150).reduced (5, 10));
rescanUserPathButton.setBounds (buttonBounds.removeFromLeft (150).reduced (5, 10));
resetToDefaultsButton.setBounds (buttonBounds.removeFromRight (150).reduced (5, 10));
warnAboutJUCEPathButton.setBounds (bottomBounds.reduced (0, 5));
warnAboutJUCEPathButton.changeWidthToFitText();
propertyGroup.updateSize (0, 0, getWidth() - 20 - propertyViewport.getScrollBarThickness());
propertyViewport.setBounds (b);
}
void highlightJUCEPath()
{
if (isTimerRunning() || ! isSelectedOSThisOS())
return;
const auto findJucePathPropertyComponent = [this]() -> PropertyComponent*
{
for (const auto& prop : propertyGroup.getProperties())
if (prop->getName() == "Path to JUCE")
return prop.get();
return nullptr;
};
if (auto* propComponent = findJucePathPropertyComponent())
{
boundsToHighlight = getLocalArea (nullptr, propComponent->getScreenBounds());
flashAlpha = 0.0f;
hasFlashed = false;
startTimer (25);
}
}
private:
//==============================================================================
void timerCallback() override
{
flashAlpha += (hasFlashed ? -0.05f : 0.05f);
if (flashAlpha > 0.75f)
{
hasFlashed = true;
}
else if (flashAlpha < 0.0f)
{
flashAlpha = 0.0f;
boundsToHighlight = {};
stopTimer();
}
repaint();
}
void valueChanged (Value&) override
{
buildProps();
resized();
}
void changeListenerCallback (ChangeBroadcaster*) override
{
warnAboutJUCEPathButton.setToggleState (ProjucerApplication::getApp().shouldPromptUserAboutIncorrectJUCEPath(),
dontSendNotification);
}
//==============================================================================
bool isSelectedOSThisOS() { return TargetOS::getThisOS() == getSelectedOS(); }
TargetOS::OS getSelectedOS() const
{
auto val = selectedOSValue.getValue();
if (val == "osx") return TargetOS::osx;
else if (val == "windows") return TargetOS::windows;
else if (val == "linux") return TargetOS::linux;
jassertfalse;
return TargetOS::unknown;
}
//==============================================================================
void buildProps()
{
updateValues();
PropertyListBuilder builder;
auto isThisOS = isSelectedOSThisOS();
builder.add (new ChoicePropertyComponent (selectedOSValue, "OS", { "OSX", "Windows", "Linux" }, { "osx", "windows", "linux" }),
"Use this dropdown to set the global paths for different OSes. "
"\nN.B. These paths are stored locally and will only be used when "
"saving a project on this machine. Other machines will have their own "
"locally stored paths.");
builder.add (new LabelPropertyComponent ("JUCE"), {});
builder.add (new FilePathPropertyComponent (jucePathValue, "Path to JUCE", true, isThisOS),
"This should be the path to the top-level directory of your JUCE folder. "
"This path will be used when searching for the JUCE examples and DemoRunner application.");
builder.add (new FilePathPropertyComponent (juceModulePathValue, "JUCE Modules", true, isThisOS),
String ("This should be the path to the folder containing the JUCE modules that you wish to use, typically the \"modules\" directory of your JUCE folder.")
+ (isThisOS ? " Use the button below to re-scan a new path." : ""));
builder.add (new FilePathPropertyComponent (userModulePathValue, "User Modules", true, isThisOS),
String ("A path to a folder containing any custom modules that you wish to use.")
+ (isThisOS ? " Use the button below to re-scan new paths." : ""));
builder.add (new LabelPropertyComponent ("SDKs"), {});
builder.add (new FilePathPropertyComponent (vstPathValue, "VST (Legacy) SDK", true, isThisOS),
"If you are building a legacy VST plug-in then this path should point to a VST2 SDK. "
"The VST2 SDK can be obtained from the vstsdk3610_11_06_2018_build_37 (or older) VST3 SDK or JUCE version 5.3.2. "
"You also need a VST2 license from Steinberg to distribute VST2 plug-ins.");
if (getSelectedOS() != TargetOS::linux)
{
builder.add (new FilePathPropertyComponent (aaxPathValue, "AAX SDK", true, isThisOS),
"If you are building AAX plug-ins, this should be the path to the AAX SDK folder.");
builder.add (new FilePathPropertyComponent (rtasPathValue, "RTAS SDK (deprecated)", true, isThisOS),
"If you are building RTAS plug-ins, this should be the path to the RTAS SDK folder.");
}
builder.add (new FilePathPropertyComponent (androidSDKPathValue, "Android SDK", true, isThisOS),
"This path will be used when writing the local.properties file of an Android project and should point to the Android SDK folder.");
if (isThisOS)
{
builder.add (new LabelPropertyComponent ("Other"), {});
#if JUCE_MAC
String exeLabel ("app");
#elif JUCE_WINDOWS
String exeLabel ("executable");
#else
String exeLabel ("startup script");
#endif
builder.add (new FilePathPropertyComponent (clionExePathValue, "CLion " + exeLabel, false, isThisOS),
"This path will be used for the \"Save Project and Open in IDE...\" option of the CLion exporter.");
builder.add (new FilePathPropertyComponent (androidStudioExePathValue, "Android Studio " + exeLabel, false, isThisOS),
"This path will be used for the \"Save Project and Open in IDE...\" option of the Android Studio exporter.");
}
rescanJUCEPathButton.setVisible (isThisOS);
rescanUserPathButton.setVisible (isThisOS);
warnAboutJUCEPathButton.setVisible (isThisOS);
propertyGroup.setProperties (builder);
}
void updateValues()
{
auto& settings = getAppSettings();
auto os = getSelectedOS();
jucePathValue = settings.getStoredPath (Ids::jucePath, os);
juceModulePathValue = settings.getStoredPath (Ids::defaultJuceModulePath, os);
userModulePathValue = settings.getStoredPath (Ids::defaultUserModulePath, os);
vstPathValue = settings.getStoredPath (Ids::vstLegacyPath, os);
rtasPathValue = settings.getStoredPath (Ids::rtasPath, os);
aaxPathValue = settings.getStoredPath (Ids::aaxPath, os);
androidSDKPathValue = settings.getStoredPath (Ids::androidSDKPath, os);
clionExePathValue = settings.getStoredPath (Ids::clionExePath, os);
androidStudioExePathValue = settings.getStoredPath (Ids::androidStudioExePath, os);
}
void resetCurrentOSPathsToDefaults()
{
jucePathValue .resetToDefault();
juceModulePathValue .resetToDefault();
userModulePathValue .resetToDefault();
vstPathValue .resetToDefault();
rtasPathValue .resetToDefault();
aaxPathValue .resetToDefault();
androidSDKPathValue .resetToDefault();
clionExePathValue .resetToDefault();
androidStudioExePathValue.resetToDefault();
repaint();
}
//==============================================================================
Value selectedOSValue;
ValueWithDefault jucePathValue, juceModulePathValue, userModulePathValue,
vstPathValue, rtasPathValue, aaxPathValue, androidSDKPathValue, clionExePathValue, androidStudioExePathValue;
Viewport propertyViewport;
PropertyGroupComponent propertyGroup { "Global Paths", { getIcons().openFolder, Colours::transparentBlack } };
ToggleButton warnAboutJUCEPathButton { "Warn about incorrect JUCE path" };
TextButton rescanJUCEPathButton { "Re-scan JUCE Modules" },
rescanUserPathButton { "Re-scan User Modules" },
resetToDefaultsButton { "Reset to Defaults" };
Rectangle<int> boundsToHighlight;
float flashAlpha = 0.0f;
bool hasFlashed = false;
var lastJUCEModulePath, lastUserModulePath;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlobalPathsWindowComponent)
};

View File

@ -0,0 +1,347 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
static String getWidthLimitedStringFromVarArray (const var& varArray) noexcept
{
if (! varArray.isArray())
return {};
int numLines = 1;
const int lineWidth = 100;
const String indent (" ");
String str;
if (auto* arr = varArray.getArray())
{
for (auto& v : *arr)
{
if ((str.length() + v.toString().length()) > (lineWidth * numLines))
{
str += newLine;
str += indent;
++numLines;
}
str += v.toString() + (arr->indexOf (v) != arr->size() - 1 ? ", " : "");
}
}
return str;
}
//==============================================================================
class PIPCreatorWindowComponent : public Component,
private ValueTree::Listener
{
public:
PIPCreatorWindowComponent()
{
lf.reset (new PIPCreatorLookAndFeel());
setLookAndFeel (lf.get());
addAndMakeVisible (propertyViewport);
propertyViewport.setViewedComponent (&propertyGroup, false);
buildProps();
addAndMakeVisible (createButton);
createButton.onClick = [this]
{
chooser = std::make_unique<FileChooser> ("Save PIP File",
File::getSpecialLocation (File::SpecialLocationType::userDesktopDirectory)
.getChildFile (nameValue.get().toString() + ".h"));
auto browserFlags = FileBrowserComponent::saveMode
| FileBrowserComponent::canSelectFiles
| FileBrowserComponent::warnAboutOverwriting;
chooser->launchAsync (browserFlags, [this] (const FileChooser& fc)
{
const auto result = fc.getResult();
if (result != File{})
createPIPFile (result);
});
};
pipTree.addListener (this);
}
~PIPCreatorWindowComponent() override
{
setLookAndFeel (nullptr);
}
void resized() override
{
auto bounds = getLocalBounds();
createButton.setBounds (bounds.removeFromBottom (50).reduced (100, 10));
propertyGroup.updateSize (0, 0, getWidth() - propertyViewport.getScrollBarThickness());
propertyViewport.setBounds (bounds);
}
private:
//==============================================================================
struct PIPCreatorLookAndFeel : public ProjucerLookAndFeel
{
PIPCreatorLookAndFeel() {}
Rectangle<int> getPropertyComponentContentPosition (PropertyComponent& component)
{
auto textW = jmin (200, component.getWidth() / 3);
return { textW, 0, component.getWidth() - textW, component.getHeight() - 1 };
}
};
void lookAndFeelChanged() override
{
lf->setColourScheme (ProjucerApplication::getApp().lookAndFeel.getCurrentColourScheme());
lf->setupColours();
}
//==============================================================================
void buildProps()
{
PropertyListBuilder builder;
builder.add (new TextPropertyComponent (nameValue, "Name", 256, false),
"The name of your JUCE project.");
builder.add (new TextPropertyComponent (versionValue, "Version", 16, false),
"This will be used for the \"Project Version\" field in the Projucer.");
builder.add (new TextPropertyComponent (vendorValue, "Vendor", 2048, false),
"This will be used for the \"Company Name\" field in the Projucer.");
builder.add (new TextPropertyComponent (websiteValue, "Website", 2048, false),
"This will be used for the \"Company Website\" field in the Projucer");
builder.add (new TextPropertyComponent (descriptionValue, "Description", 2048, true),
"A short description of your JUCE project.");
{
Array<var> moduleVars;
for (auto& m : getJUCEModules())
moduleVars.add (m);
builder.add (new MultiChoicePropertyComponent (dependenciesValue, "Dependencies",
getJUCEModules(), moduleVars),
"The JUCE modules that should be added to your project.");
}
{
Array<var> exporterVars;
StringArray exporterNames;
for (auto& exporterTypeInfo : ProjectExporter::getExporterTypeInfos())
{
exporterVars.add (exporterTypeInfo.identifier.toString());
exporterNames.add (exporterTypeInfo.displayName);
}
builder.add (new MultiChoicePropertyComponent (exportersValue, "Exporters", exporterNames, exporterVars),
"The exporters that should be added to your project.");
}
builder.add (new TextPropertyComponent (moduleFlagsValue, "Module Flags", 2048, true),
"Use this to set one, or many, of the JUCE module flags");
builder.add (new TextPropertyComponent (definesValue, "Defines", 2048, true),
"This sets some global preprocessor definitions for your project. Used to populate the \"Preprocessor Definitions\" field in the Projucer.");
builder.add (new ChoicePropertyComponent (typeValue, "Type",
{ "Component", "Plugin", "Console Application" },
{ "Component", "AudioProcessor", "Console" }),
"The project type.");
builder.add (new TextPropertyComponent (mainClassValue, "Main Class", 2048, false),
"The name of the main class that should be instantiated. "
"There can only be one main class and it must have a default constructor. "
"Depending on the type, this may need to inherit from a specific JUCE class");
builder.add (new ChoicePropertyComponent (useLocalCopyValue, "Use Local Copy"),
"Enable this to specify that the PIP file should be copied to the generated project directory instead of just referred to.");
propertyGroup.setProperties (builder);
}
//==============================================================================
void valueTreePropertyChanged (ValueTree&, const Identifier& identifier) override
{
if (identifier == Ids::type)
{
auto type = typeValue.get().toString();
if (type == "Component")
{
nameValue.setDefault ("MyComponentPIP");
dependenciesValue.setDefault (getModulesRequiredForComponent());
mainClassValue.setDefault ("MyComponent");
}
else if (type == "AudioProcessor")
{
nameValue.setDefault ("MyPluginPIP");
dependenciesValue.setDefault (getModulesRequiredForAudioProcessor());
mainClassValue.setDefault ("MyPlugin");
}
else if (type == "Console")
{
nameValue.setDefault ("MyConsolePIP");
dependenciesValue.setDefault (getModulesRequiredForConsole());
mainClassValue.setDefault ({});
}
MessageManager::callAsync ([this]
{
buildProps();
resized();
});
}
}
//==============================================================================
String getFormattedMetadataString() const noexcept
{
StringArray metadata;
{
StringArray section;
if (nameValue.get().toString().isNotEmpty()) section.add (" name: " + nameValue.get().toString());
if (versionValue.get().toString().isNotEmpty()) section.add (" version: " + versionValue.get().toString());
if (vendorValue.get().toString().isNotEmpty()) section.add (" vendor: " + vendorValue.get().toString());
if (websiteValue.get().toString().isNotEmpty()) section.add (" website: " + websiteValue.get().toString());
if (descriptionValue.get().toString().isNotEmpty()) section.add (" description: " + descriptionValue.get().toString());
if (! section.isEmpty())
metadata.add (section.joinIntoString (getPreferredLineFeed()));
}
{
StringArray section;
auto dependenciesString = getWidthLimitedStringFromVarArray (dependenciesValue.get());
if (dependenciesString.isNotEmpty()) section.add (" dependencies: " + dependenciesString);
auto exportersString = getWidthLimitedStringFromVarArray (exportersValue.get());
if (exportersString.isNotEmpty()) section.add (" exporters: " + exportersString);
if (! section.isEmpty())
metadata.add (section.joinIntoString (getPreferredLineFeed()));
}
{
StringArray section;
if (moduleFlagsValue.get().toString().isNotEmpty()) section.add (" moduleFlags: " + moduleFlagsValue.get().toString());
if (definesValue.get().toString().isNotEmpty()) section.add (" defines: " + definesValue.get().toString());
if (! section.isEmpty())
metadata.add (section.joinIntoString (getPreferredLineFeed()));
}
{
StringArray section;
if (typeValue.get().toString().isNotEmpty()) section.add (" type: " + typeValue.get().toString());
if (mainClassValue.get().toString().isNotEmpty()) section.add (" mainClass: " + mainClassValue.get().toString());
if (! section.isEmpty())
metadata.add (section.joinIntoString (getPreferredLineFeed()));
}
{
StringArray section;
if (useLocalCopyValue.get()) section.add (" useLocalCopy: " + useLocalCopyValue.get().toString());
if (! section.isEmpty())
metadata.add (section.joinIntoString (getPreferredLineFeed()));
}
return metadata.joinIntoString (String (getPreferredLineFeed()) + getPreferredLineFeed());
}
void createPIPFile (File fileToSave)
{
String fileTemplate (BinaryData::jucer_PIPTemplate_h);
fileTemplate = fileTemplate.replace ("%%pip_metadata%%", getFormattedMetadataString());
auto type = typeValue.get().toString();
if (type == "Component")
{
String componentCode (BinaryData::jucer_ContentCompSimpleTemplate_h);
componentCode = componentCode.substring (componentCode.indexOf ("class %%content_component_class%%"))
.replace ("%%content_component_class%%", mainClassValue.get().toString());
fileTemplate = fileTemplate.replace ("%%pip_code%%", componentCode);
}
else if (type == "AudioProcessor")
{
String audioProcessorCode (BinaryData::jucer_PIPAudioProcessorTemplate_h);
audioProcessorCode = audioProcessorCode.replace ("%%class_name%%", mainClassValue.get().toString())
.replace ("%%name%%", nameValue.get().toString());
fileTemplate = fileTemplate.replace ("%%pip_code%%", audioProcessorCode);
}
else if (type == "Console")
{
String consoleCode (BinaryData::jucer_MainConsoleAppTemplate_cpp);
consoleCode = consoleCode.substring (consoleCode.indexOf ("int main (int argc, char* argv[])"));
fileTemplate = fileTemplate.replace ("%%pip_code%%", consoleCode);
}
if (fileToSave.create())
fileToSave.replaceWithText (fileTemplate);
}
//==============================================================================
ValueTree pipTree { "PIPSettings" };
ValueWithDefault nameValue { pipTree, Ids::name, nullptr, "MyComponentPIP" },
versionValue { pipTree, Ids::version, nullptr },
vendorValue { pipTree, Ids::vendor, nullptr },
websiteValue { pipTree, Ids::website, nullptr },
descriptionValue { pipTree, Ids::description, nullptr },
dependenciesValue { pipTree, Ids::dependencies_, nullptr, getModulesRequiredForComponent(), "," },
exportersValue { pipTree, Ids::exporters, nullptr, StringArray (ProjectExporter::getCurrentPlatformExporterTypeInfo().identifier.toString()), "," },
moduleFlagsValue { pipTree, Ids::moduleFlags, nullptr, "JUCE_STRICT_REFCOUNTEDPOINTER=1" },
definesValue { pipTree, Ids::defines, nullptr },
typeValue { pipTree, Ids::type, nullptr, "Component" },
mainClassValue { pipTree, Ids::mainClass, nullptr, "MyComponent" },
useLocalCopyValue { pipTree, Ids::useLocalCopy, nullptr, false };
std::unique_ptr<PIPCreatorLookAndFeel> lf;
Viewport propertyViewport;
PropertyGroupComponent propertyGroup { "PIP Creator", {} };
TextButton createButton { "Create PIP" };
std::unique_ptr<FileChooser> chooser;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPCreatorWindowComponent)
};

View File

@ -0,0 +1,218 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class SVGPathDataComponent : public Component,
public FileDragAndDropTarget
{
public:
SVGPathDataComponent()
{
desc.setJustificationType (Justification::centred);
addAndMakeVisible (desc);
userText.setFont (getAppSettings().appearance.getCodeFont().withHeight (13.0f));
userText.setMultiLine (true, true);
userText.setReturnKeyStartsNewLine (true);
addAndMakeVisible (userText);
userText.onTextChange = [this] { update(); };
userText.onEscapeKey = [this] { getTopLevelComponent()->exitModalState (0); };
resultText.setFont (getAppSettings().appearance.getCodeFont().withHeight (13.0f));
resultText.setMultiLine (true, true);
resultText.setReadOnly (true);
resultText.setSelectAllWhenFocused (true);
addAndMakeVisible (resultText);
userText.setText (getLastText());
addAndMakeVisible (copyButton);
copyButton.onClick = [this] { SystemClipboard::copyTextToClipboard (resultText.getText()); };
addAndMakeVisible (closeSubPathButton);
closeSubPathButton.onClick = [this] { update(); };
closeSubPathButton.setToggleState (true, NotificationType::dontSendNotification);
addAndMakeVisible (fillPathButton);
fillPathButton.onClick = [this] { update(); };
fillPathButton.setToggleState (true, NotificationType::dontSendNotification);
}
void update()
{
getLastText() = userText.getText();
auto text = getLastText().trim().unquoted().trim();
path = Drawable::parseSVGPath (text);
if (path.isEmpty())
path = pathFromPoints (text);
String result = "No path generated.. Not a valid SVG path string?";
if (! path.isEmpty())
{
MemoryOutputStream data;
path.writePathToStream (data);
MemoryOutputStream out;
out << "static const unsigned char pathData[] = ";
build_tools::writeDataAsCppLiteral (data.getMemoryBlock(), out, false, true);
out << newLine
<< newLine
<< "Path path;" << newLine
<< "path.loadPathFromData (pathData, sizeof (pathData));" << newLine;
result = out.toString();
}
resultText.setText (result, false);
repaint (previewPathArea);
}
void resized() override
{
auto r = getLocalBounds().reduced (8);
auto bottomSection = r.removeFromBottom (30);
copyButton.setBounds (bottomSection.removeFromLeft (50));
bottomSection.removeFromLeft (25);
fillPathButton.setBounds (bottomSection.removeFromLeft (bottomSection.getWidth() / 2));
closeSubPathButton.setBounds (bottomSection);
r.removeFromBottom (5);
desc.setBounds (r.removeFromTop (44));
r.removeFromTop (8);
userText.setBounds (r.removeFromTop (r.getHeight() / 2));
r.removeFromTop (8);
previewPathArea = r.removeFromRight (r.getHeight());
resultText.setBounds (r);
}
void paint (Graphics& g) override
{
if (dragOver)
{
g.setColour (findColour (secondaryBackgroundColourId).brighter());
g.fillAll();
}
g.setColour (findColour (defaultTextColourId));
path.applyTransform (path.getTransformToScaleToFit (previewPathArea.reduced (4).toFloat(), true));
if (fillPathButton.getToggleState())
g.fillPath (path);
else
g.strokePath (path, PathStrokeType (2.0f));
}
void lookAndFeelChanged() override
{
userText.applyFontToAllText (userText.getFont());
resultText.applyFontToAllText (resultText.getFont());
}
bool isInterestedInFileDrag (const StringArray& files) override
{
return files.size() == 1
&& File (files[0]).hasFileExtension ("svg");
}
void fileDragEnter (const StringArray&, int, int) override
{
dragOver = true;
repaint();
}
void fileDragExit (const StringArray&) override
{
dragOver = false;
repaint();
}
void filesDropped (const StringArray& files, int, int) override
{
dragOver = false;
repaint();
if (auto element = parseXML (File (files[0])))
{
if (auto* ePath = element->getChildByName ("path"))
userText.setText (ePath->getStringAttribute ("d"), true);
else if (auto* ePolygon = element->getChildByName ("polygon"))
userText.setText (ePolygon->getStringAttribute ("points"), true);
}
}
Path pathFromPoints (String pointsText)
{
auto points = StringArray::fromTokens (pointsText, " ,", "");
points.removeEmptyStrings();
jassert (points.size() % 2 == 0);
Path p;
for (int i = 0; i < points.size() / 2; i++)
{
auto x = points[i * 2].getFloatValue();
auto y = points[i * 2 + 1].getFloatValue();
if (i == 0)
p.startNewSubPath ({ x, y });
else
p.lineTo ({ x, y });
}
if (closeSubPathButton.getToggleState())
p.closeSubPath();
return p;
}
private:
Label desc { {}, "Paste an SVG path string into the top box, and it'll be converted to some C++ "
"code that will load it as a Path object.." };
TextButton copyButton { "Copy" };
TextEditor userText, resultText;
ToggleButton closeSubPathButton { "Close sub-path" };
ToggleButton fillPathButton { "Fill path" };
Rectangle<int> previewPathArea;
Path path;
bool dragOver = false;
String& getLastText()
{
static String t;
return t;
}
};

View File

@ -0,0 +1,198 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../Utility/Helpers/jucer_TranslationHelpers.h"
//==============================================================================
class TranslationToolComponent : public Component
{
public:
TranslationToolComponent()
: editorOriginal (documentOriginal, nullptr),
editorPre (documentPre, nullptr),
editorPost (documentPost, nullptr),
editorResult (documentResult, nullptr)
{
instructionsLabel.setText (
"This utility converts translation files to/from a format that can be passed to automatic translation tools."
"\n\n"
"First, choose whether to scan the current project for all TRANS() macros, or "
"pick an existing translation file to load:", dontSendNotification);
addAndMakeVisible (instructionsLabel);
label1.setText ("..then copy-and-paste this annotated text into Google Translate or some other translator:", dontSendNotification);
addAndMakeVisible (label1);
label2.setText ("...then, take the translated result and paste it into the box below:", dontSendNotification);
addAndMakeVisible (label2);
label3.setText ("Finally, click the 'Generate' button, and a translation file will be created below. "
"Remember to update its language code at the top!", dontSendNotification);
addAndMakeVisible (label3);
label4.setText ("If you load an existing file the already translated strings will be removed. Ensure this box is empty to create a fresh translation", dontSendNotification);
addAndMakeVisible (label4);
addAndMakeVisible (editorOriginal);
addAndMakeVisible (editorPre);
addAndMakeVisible (editorPost);
addAndMakeVisible (editorResult);
addAndMakeVisible (generateButton);
generateButton.onClick = [this] { generate(); };
addAndMakeVisible (scanProjectButton);
scanProjectButton.onClick = [this] { scanProject(); };
addAndMakeVisible (scanFolderButton);
scanFolderButton.onClick = [this] { scanFolder(); };
addAndMakeVisible (loadTranslationButton);
loadTranslationButton.onClick = [this] { loadFile(); };
}
void paint (Graphics& g) override
{
g.fillAll (findColour (backgroundColourId));
}
void resized() override
{
const int m = 6;
const int textH = 44;
const int extraH = (7 * textH);
const int editorH = (getHeight() - extraH) / 4;
const int numButtons = 3;
Rectangle<int> r (getLocalBounds().withTrimmedBottom (m));
const int buttonWidth = r.getWidth() / numButtons;
instructionsLabel.setBounds (r.removeFromTop (textH * 2).reduced (m));
r.removeFromTop (m);
Rectangle<int> r2 (r.removeFromTop (textH - (2 * m)));
scanProjectButton .setBounds (r2.removeFromLeft (buttonWidth).reduced (m, 0));
scanFolderButton .setBounds (r2.removeFromLeft (buttonWidth).reduced (m, 0));
loadTranslationButton.setBounds (r2.reduced (m, 0));
label1 .setBounds (r.removeFromTop (textH) .reduced (m));
editorPre.setBounds (r.removeFromTop (editorH).reduced (m, 0));
label2 .setBounds (r.removeFromTop (textH) .reduced (m));
editorPost.setBounds (r.removeFromTop (editorH).reduced (m, 0));
r2 = r.removeFromTop (textH);
generateButton.setBounds (r2.removeFromRight (152).reduced (m));
label3 .setBounds (r2.reduced (m));
editorResult .setBounds (r.removeFromTop (editorH).reduced (m, 0));
label4 .setBounds (r.removeFromTop (textH).reduced (m));
editorOriginal.setBounds (r.reduced (m, 0));
}
private:
//==============================================================================
void generate()
{
StringArray preStrings (TranslationHelpers::breakApart (documentPre.getAllContent()));
StringArray postStrings (TranslationHelpers::breakApart (documentPost.getAllContent()));
if (postStrings.size() != preStrings.size())
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
TRANS("Error"),
TRANS("The pre- and post-translation text doesn't match!\n\n"
"Perhaps it got mangled by the translator?"));
return;
}
const LocalisedStrings originalTranslation (documentOriginal.getAllContent(), false);
documentResult.replaceAllContent (TranslationHelpers::createFinishedTranslationFile (preStrings, postStrings, originalTranslation));
}
void scanProject()
{
if (Project* project = ProjucerApplication::getApp().mainWindowList.getFrontmostProject())
setPreTranslationText (TranslationHelpers::getPreTranslationText (*project));
else
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon, "Translation Tool",
"This will only work when you have a project open!");
}
void scanFolder()
{
chooser = std::make_unique<FileChooser> ("Choose the root folder to search for the TRANS macros",
File(), "*");
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
{
if (fc.getResult() == File{})
return;
StringArray strings;
TranslationHelpers::scanFolderForTranslations (strings, fc.getResult());
setPreTranslationText (TranslationHelpers::mungeStrings(strings));
});
}
void loadFile()
{
chooser = std::make_unique<FileChooser> ("Choose a translation file to load", File(), "*");
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles;
chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
{
if (fc.getResult() == File{})
return;
const LocalisedStrings loadedStrings (fc.getResult(), false);
documentOriginal.replaceAllContent (fc.getResult().loadFileAsString().trim());
setPreTranslationText (TranslationHelpers::getPreTranslationText (loadedStrings));
});
}
void setPreTranslationText (const String& text)
{
documentPre.replaceAllContent (text);
editorPre.grabKeyboardFocus();
editorPre.selectAll();
}
//==============================================================================
CodeDocument documentOriginal, documentPre, documentPost, documentResult;
CodeEditorComponent editorOriginal, editorPre, editorPost, editorResult;
Label label1, label2, label3, label4;
Label instructionsLabel;
TextButton generateButton { TRANS("Generate") },
scanProjectButton { "Scan project for TRANS macros" },
scanFolderButton { "Scan folder for TRANS macros" },
loadTranslationButton { "Load existing translation file..."};
std::unique_ptr<FileChooser> chooser;
};

View File

@ -0,0 +1,87 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class UTF8Component : public Component
{
public:
UTF8Component()
: desc (String(),
"Type any string into the box, and it'll be shown below as a portable UTF-8 literal, "
"ready to cut-and-paste into your source-code...")
{
desc.setJustificationType (Justification::centred);
addAndMakeVisible (desc);
userText.setMultiLine (true, true);
userText.setReturnKeyStartsNewLine (true);
addAndMakeVisible (userText);
userText.onTextChange = [this] { update(); };
userText.onEscapeKey = [this] { getTopLevelComponent()->exitModalState (0); };
resultText.setFont (getAppSettings().appearance.getCodeFont().withHeight (13.0f));
resultText.setMultiLine (true, true);
resultText.setReadOnly (true);
resultText.setSelectAllWhenFocused (true);
addAndMakeVisible (resultText);
userText.setText (getLastText());
}
void update()
{
getLastText() = userText.getText();
resultText.setText (CodeHelpers::stringLiteral (getLastText(), 100), false);
}
void resized() override
{
auto r = getLocalBounds().reduced (8);
desc.setBounds (r.removeFromTop (44));
r.removeFromTop (8);
userText.setBounds (r.removeFromTop (r.getHeight() / 2));
r.removeFromTop (8);
resultText.setBounds (r);
}
void lookAndFeelChanged() override
{
userText.applyFontToAllText (userText.getFont());
resultText.applyFontToAllText (resultText.getFont());
}
private:
Label desc;
TextEditor userText, resultText;
String& getLastText()
{
static String t;
return t;
}
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,224 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "UserAccount/jucer_LicenseController.h"
#include "jucer_MainWindow.h"
#include "../Project/Modules/jucer_Modules.h"
#include "jucer_AutoUpdater.h"
#include "../CodeEditor/jucer_SourceCodeEditor.h"
#include "../Utility/UI/jucer_ProjucerLookAndFeel.h"
//==============================================================================
class ProjucerApplication : public JUCEApplication,
private AsyncUpdater
{
public:
ProjucerApplication() = default;
static ProjucerApplication& getApp();
static ApplicationCommandManager& getCommandManager();
//==============================================================================
void initialise (const String& commandLine) override;
void shutdown() override;
void systemRequestedQuit() override;
void deleteLogger();
const String getApplicationName() override { return "Projucer"; }
const String getApplicationVersion() override { return ProjectInfo::versionString; }
String getVersionDescription() const;
bool moreThanOneInstanceAllowed() override { return true; } // this is handled manually in initialise()
void anotherInstanceStarted (const String& commandLine) override;
//==============================================================================
MenuBarModel* getMenuModel();
void getAllCommands (Array<CommandID>&) override;
void getCommandInfo (CommandID commandID, ApplicationCommandInfo&) override;
bool perform (const InvocationInfo&) override;
bool isGUIEditorEnabled() const;
//==============================================================================
void openFile (const File&, std::function<void (bool)>);
void showPathsWindow (bool highlightJUCEPath = false);
PropertiesFile::Options getPropertyFileOptionsFor (const String& filename, bool isProjectSettings);
void selectEditorColourSchemeWithName (const String& schemeName);
//==============================================================================
void rescanJUCEPathModules();
void rescanUserPathModules();
AvailableModulesList& getJUCEPathModulesList() { return jucePathModulesList; }
AvailableModulesList& getUserPathsModulesList() { return userPathsModulesList; }
LicenseController& getLicenseController() { return *licenseController; }
bool isAutomaticVersionCheckingEnabled() const;
void setAutomaticVersionCheckingEnabled (bool shouldBeEnabled);
bool shouldPromptUserAboutIncorrectJUCEPath() const;
void setShouldPromptUserAboutIncorrectJUCEPath (bool shouldPrompt);
static File getJUCEExamplesDirectoryPathFromGlobal() noexcept;
static Array<File> getSortedExampleDirectories() noexcept;
static Array<File> getSortedExampleFilesInDirectory (const File&) noexcept;
//==============================================================================
ProjucerLookAndFeel lookAndFeel;
std::unique_ptr<StoredSettings> settings;
std::unique_ptr<Icons> icons;
struct MainMenuModel;
std::unique_ptr<MainMenuModel> menuModel;
MainWindowList mainWindowList;
OpenDocumentManager openDocumentManager;
std::unique_ptr<ApplicationCommandManager> commandManager;
bool isRunningCommandLine = false;
private:
//==============================================================================
void handleAsyncUpdate() override;
void doBasicApplicationSetup();
void initCommandManager();
bool initialiseLogger (const char* filePrefix);
void initialiseWindows (const String& commandLine);
void createNewProject();
void createNewProjectFromClipboard();
void createNewPIP();
void askUserToOpenFile();
void saveAllDocuments();
void closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave);
void closeAllMainWindows (std::function<void (bool)>);
void closeAllMainWindowsAndQuitIfNeeded();
void clearRecentFiles();
StringArray getMenuNames();
PopupMenu createMenu (const String& menuName);
PopupMenu createFileMenu();
PopupMenu createEditMenu();
PopupMenu createViewMenu();
void createColourSchemeItems (PopupMenu&);
PopupMenu createWindowMenu();
PopupMenu createDocumentMenu();
PopupMenu createToolsMenu();
PopupMenu createHelpMenu();
PopupMenu createExtraAppleMenuItems();
void handleMainMenuCommand (int menuItemID);
PopupMenu createExamplesPopupMenu() noexcept;
void findAndLaunchExample (int);
void checkIfGlobalJUCEPathHasChanged();
File tryToFindDemoRunnerExecutable();
File tryToFindDemoRunnerProject();
void launchDemoRunner();
void setColourScheme (int index, bool saveSetting);
void setEditorColourScheme (int index, bool saveSetting);
void updateEditorColourSchemeIfNeeded();
void showUTF8ToolWindow();
void showSVGPathDataToolWindow();
void showAboutWindow();
void showEditorColourSchemeWindow();
void showPIPCreatorWindow();
void launchForumBrowser();
void launchModulesBrowser();
void launchClassesBrowser();
void launchTutorialsBrowser();
void doLoginOrLogout();
void showLoginForm();
void enableOrDisableGUIEditor();
//==============================================================================
#if JUCE_MAC
class AppleMenuRebuildListener : private MenuBarModel::Listener
{
public:
AppleMenuRebuildListener()
{
if (auto* model = ProjucerApplication::getApp().getMenuModel())
model->addListener (this);
}
~AppleMenuRebuildListener() override
{
if (auto* model = ProjucerApplication::getApp().getMenuModel())
model->removeListener (this);
}
private:
void menuBarItemsChanged (MenuBarModel*) override {}
void menuCommandInvoked (MenuBarModel*,
const ApplicationCommandTarget::InvocationInfo& info) override
{
if (info.commandID == CommandIDs::enableNewVersionCheck)
Timer::callAfterDelay (50, [] { ProjucerApplication::getApp().rebuildAppleMenu(); });
}
};
void rebuildAppleMenu();
std::unique_ptr<AppleMenuRebuildListener> appleMenuRebuildListener;
#endif
//==============================================================================
std::unique_ptr<LicenseController> licenseController;
std::unique_ptr<TooltipWindow> tooltipWindow;
AvailableModulesList jucePathModulesList, userPathsModulesList;
std::unique_ptr<Component> utf8Window, svgPathWindow, aboutWindow, pathsWindow,
editorColourSchemeWindow, pipCreatorWindow;
std::unique_ptr<FileLogger> logger;
int numExamples = 0;
std::unique_ptr<AlertWindow> demoRunnerAlert;
bool hasScannedForDemoRunnerExecutable = false, hasScannedForDemoRunnerProject = false;
File lastJUCEPath, lastDemoRunnerExectuableFile, lastDemoRunnerProjectFile;
int selectedColourSchemeIndex = 0, selectedEditorColourSchemeIndex = 0;
std::unique_ptr<FileChooser> chooser;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjucerApplication)
JUCE_DECLARE_WEAK_REFERENCEABLE (ProjucerApplication)
};

View File

@ -0,0 +1,560 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../Application/jucer_Headers.h"
#include "jucer_Application.h"
#include "jucer_AutoUpdater.h"
//==============================================================================
LatestVersionCheckerAndUpdater::LatestVersionCheckerAndUpdater()
: Thread ("VersionChecker")
{
}
LatestVersionCheckerAndUpdater::~LatestVersionCheckerAndUpdater()
{
stopThread (6000);
clearSingletonInstance();
}
void LatestVersionCheckerAndUpdater::checkForNewVersion (bool background)
{
if (! isThreadRunning())
{
backgroundCheck = background;
startThread (3);
}
}
//==============================================================================
void LatestVersionCheckerAndUpdater::run()
{
auto info = VersionInfo::fetchLatestFromUpdateServer();
if (info == nullptr)
{
if (! backgroundCheck)
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Update Server Communication Error",
"Failed to communicate with the JUCE update server.\n"
"Please try again in a few minutes.\n\n"
"If this problem persists you can download the latest version of JUCE from juce.com");
return;
}
if (! info->isNewerVersionThanCurrent())
{
if (! backgroundCheck)
AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon,
"No New Version Available",
"Your JUCE version is up to date.");
return;
}
auto osString = []
{
#if JUCE_MAC
return "osx";
#elif JUCE_WINDOWS
return "windows";
#elif JUCE_LINUX
return "linux";
#elif JUCE_BSD
return "bsd";
#else
jassertfalse;
return "Unknown";
#endif
}();
String requiredFilename ("juce-" + info->versionString + "-" + osString + ".zip");
for (auto& asset : info->assets)
{
if (asset.name == requiredFilename)
{
auto versionString = info->versionString;
auto releaseNotes = info->releaseNotes;
MessageManager::callAsync ([this, versionString, releaseNotes, asset]
{
askUserAboutNewVersion (versionString, releaseNotes, asset);
});
return;
}
}
if (! backgroundCheck)
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Failed to find any new downloads",
"Please try again in a few minutes.");
}
//==============================================================================
class UpdateDialog : public Component
{
public:
UpdateDialog (const String& newVersion, const String& releaseNotes)
{
titleLabel.setText ("JUCE version " + newVersion, dontSendNotification);
titleLabel.setFont ({ 15.0f, Font::bold });
titleLabel.setJustificationType (Justification::centred);
addAndMakeVisible (titleLabel);
contentLabel.setText ("A new version of JUCE is available - would you like to download it?", dontSendNotification);
contentLabel.setFont (15.0f);
contentLabel.setJustificationType (Justification::topLeft);
addAndMakeVisible (contentLabel);
releaseNotesLabel.setText ("Release notes:", dontSendNotification);
releaseNotesLabel.setFont (15.0f);
releaseNotesLabel.setJustificationType (Justification::topLeft);
addAndMakeVisible (releaseNotesLabel);
releaseNotesEditor.setMultiLine (true);
releaseNotesEditor.setReadOnly (true);
releaseNotesEditor.setText (releaseNotes);
addAndMakeVisible (releaseNotesEditor);
addAndMakeVisible (chooseButton);
chooseButton.onClick = [this] { exitModalStateWithResult (1); };
addAndMakeVisible (cancelButton);
cancelButton.onClick = [this]
{
ProjucerApplication::getApp().setAutomaticVersionCheckingEnabled (! dontAskAgainButton.getToggleState());
exitModalStateWithResult (-1);
};
dontAskAgainButton.setToggleState (! ProjucerApplication::getApp().isAutomaticVersionCheckingEnabled(), dontSendNotification);
addAndMakeVisible (dontAskAgainButton);
juceIcon = Drawable::createFromImageData (BinaryData::juce_icon_png,
BinaryData::juce_icon_pngSize);
lookAndFeelChanged();
setSize (500, 280);
}
void resized() override
{
auto b = getLocalBounds().reduced (10);
auto topSlice = b.removeFromTop (juceIconBounds.getHeight())
.withTrimmedLeft (juceIconBounds.getWidth());
titleLabel.setBounds (topSlice.removeFromTop (25));
topSlice.removeFromTop (5);
contentLabel.setBounds (topSlice.removeFromTop (25));
auto buttonBounds = b.removeFromBottom (60);
buttonBounds.removeFromBottom (25);
chooseButton.setBounds (buttonBounds.removeFromLeft (buttonBounds.getWidth() / 2).reduced (20, 0));
cancelButton.setBounds (buttonBounds.reduced (20, 0));
dontAskAgainButton.setBounds (cancelButton.getBounds().withY (cancelButton.getBottom() + 5).withHeight (20));
releaseNotesEditor.setBounds (b.reduced (0, 10));
}
void paint (Graphics& g) override
{
g.fillAll (findColour (backgroundColourId));
if (juceIcon != nullptr)
juceIcon->drawWithin (g, juceIconBounds.toFloat(),
RectanglePlacement::stretchToFit, 1.0f);
}
static std::unique_ptr<DialogWindow> launchDialog (const String& newVersionString,
const String& releaseNotes)
{
DialogWindow::LaunchOptions options;
options.dialogTitle = "Download JUCE version " + newVersionString + "?";
options.resizable = false;
auto* content = new UpdateDialog (newVersionString, releaseNotes);
options.content.set (content, true);
std::unique_ptr<DialogWindow> dialog (options.create());
content->setParentWindow (dialog.get());
dialog->enterModalState (true, nullptr, true);
return dialog;
}
private:
void lookAndFeelChanged() override
{
cancelButton.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
releaseNotesEditor.applyFontToAllText (releaseNotesEditor.getFont());
}
void setParentWindow (DialogWindow* parent)
{
parentWindow = parent;
}
void exitModalStateWithResult (int result)
{
if (parentWindow != nullptr)
parentWindow->exitModalState (result);
}
Label titleLabel, contentLabel, releaseNotesLabel;
TextEditor releaseNotesEditor;
TextButton chooseButton { "Choose Location..." }, cancelButton { "Cancel" };
ToggleButton dontAskAgainButton { "Don't ask again" };
std::unique_ptr<Drawable> juceIcon;
Rectangle<int> juceIconBounds { 10, 10, 64, 64 };
DialogWindow* parentWindow = nullptr;
};
void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const VersionInfo::Asset& asset)
{
chooser = std::make_unique<FileChooser> ("Please select the location into which you would like to install the new version",
File { getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get() });
chooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories,
[weakThis = WeakReference<LatestVersionCheckerAndUpdater> { this }, asset] (const FileChooser& fc)
{
auto targetFolder = fc.getResult();
if (targetFolder == File{})
return;
// By default we will install into 'targetFolder/JUCE', but we should install into
// 'targetFolder' if that is an existing JUCE directory.
bool willOverwriteJuceFolder = [&targetFolder]
{
if (isJUCEFolder (targetFolder))
return true;
targetFolder = targetFolder.getChildFile ("JUCE");
return isJUCEFolder (targetFolder);
}();
auto targetFolderPath = targetFolder.getFullPathName();
const auto onResult = [weakThis, asset, targetFolder] (int result)
{
if (weakThis == nullptr || result == 0)
return;
weakThis->downloadAndInstall (asset, targetFolder);
};
if (willOverwriteJuceFolder)
{
if (targetFolder.getChildFile (".git").isDirectory())
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon, "Downloading New JUCE Version",
targetFolderPath + "\n\nis a GIT repository!\n\nYou should use a \"git pull\" to update it to the latest version.");
return;
}
AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon,
"Overwrite Existing JUCE Folder?",
"Do you want to replace the folder\n\n" + targetFolderPath + "\n\nwith the latest version from juce.com?\n\n"
"This will move the existing folder to " + targetFolderPath + "_old.\n\n"
"Replacing the folder that contains the currently running Projucer executable may not work on Windows.",
{},
{},
nullptr,
ModalCallbackFunction::create (onResult));
return;
}
if (targetFolder.exists())
{
AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon,
"Existing File Or Directory",
"Do you want to move\n\n" + targetFolderPath + "\n\nto\n\n" + targetFolderPath + "_old?",
{},
{},
nullptr,
ModalCallbackFunction::create (onResult));
return;
}
if (weakThis != nullptr)
weakThis->downloadAndInstall (asset, targetFolder);
});
}
void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVersionString,
const String& releaseNotes,
const VersionInfo::Asset& asset)
{
if (backgroundCheck)
addNotificationToOpenProjects (asset);
else
showDialogWindow (newVersionString, releaseNotes, asset);
}
void LatestVersionCheckerAndUpdater::showDialogWindow (const String& newVersionString,
const String& releaseNotes,
const VersionInfo::Asset& asset)
{
dialogWindow = UpdateDialog::launchDialog (newVersionString, releaseNotes);
if (auto* mm = ModalComponentManager::getInstance())
{
mm->attachCallback (dialogWindow.get(),
ModalCallbackFunction::create ([this, asset] (int result)
{
if (result == 1)
askUserForLocationToDownload (asset);
dialogWindow.reset();
}));
}
}
void LatestVersionCheckerAndUpdater::addNotificationToOpenProjects (const VersionInfo::Asset& asset)
{
for (auto* window : ProjucerApplication::getApp().mainWindowList.windows)
{
if (auto* project = window->getProject())
{
auto ignore = [safeWindow = Component::SafePointer<MainWindow> { window }]
{
if (safeWindow != nullptr)
safeWindow->getProject()->removeProjectMessage (ProjectMessages::Ids::newVersionAvailable);
};
auto dontAskAgain = [ignore]
{
ignore();
ProjucerApplication::getApp().setAutomaticVersionCheckingEnabled (false);
};
project->addProjectMessage (ProjectMessages::Ids::newVersionAvailable,
{ { "Download", [this, asset] { askUserForLocationToDownload (asset); } },
{ "Ignore", std::move (ignore) },
{ "Don't ask again", std::move (dontAskAgain) } });
}
}
}
//==============================================================================
class DownloadAndInstallThread : private ThreadWithProgressWindow
{
public:
DownloadAndInstallThread (const VersionInfo::Asset& a, const File& t, std::function<void()>&& cb)
: ThreadWithProgressWindow ("Downloading New Version", true, true),
asset (a), targetFolder (t), completionCallback (std::move (cb))
{
launchThread (3);
}
private:
void run() override
{
setProgress (-1.0);
MemoryBlock zipData;
auto result = download (zipData);
if (result.wasOk() && ! threadShouldExit())
result = install (zipData);
if (result.failed())
MessageManager::callAsync ([result] { AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Installation Failed",
result.getErrorMessage()); });
else
MessageManager::callAsync (completionCallback);
}
Result download (MemoryBlock& dest)
{
setStatusMessage ("Downloading...");
int statusCode = 0;
auto inStream = VersionInfo::createInputStreamForAsset (asset, statusCode);
if (inStream != nullptr && statusCode == 200)
{
int64 total = 0;
MemoryOutputStream mo (dest, true);
for (;;)
{
if (threadShouldExit())
return Result::fail ("Cancelled");
auto written = mo.writeFromInputStream (*inStream, 8192);
if (written == 0)
break;
total += written;
setStatusMessage ("Downloading... " + File::descriptionOfSizeInBytes (total));
}
return Result::ok();
}
return Result::fail ("Failed to download from: " + asset.url);
}
Result install (const MemoryBlock& data)
{
setStatusMessage ("Installing...");
MemoryInputStream input (data, false);
ZipFile zip (input);
if (zip.getNumEntries() == 0)
return Result::fail ("The downloaded file was not a valid JUCE file!");
struct ScopedDownloadFolder
{
explicit ScopedDownloadFolder (const File& installTargetFolder)
{
folder = installTargetFolder.getSiblingFile (installTargetFolder.getFileNameWithoutExtension() + "_download").getNonexistentSibling();
jassert (folder.createDirectory());
}
~ScopedDownloadFolder() { folder.deleteRecursively(); }
File folder;
};
ScopedDownloadFolder unzipTarget (targetFolder);
if (! unzipTarget.folder.isDirectory())
return Result::fail ("Couldn't create a temporary folder to unzip the new version!");
auto r = zip.uncompressTo (unzipTarget.folder);
if (r.failed())
return r;
if (threadShouldExit())
return Result::fail ("Cancelled");
#if JUCE_LINUX || JUCE_BSD || JUCE_MAC
r = setFilePermissions (unzipTarget.folder, zip);
if (r.failed())
return r;
if (threadShouldExit())
return Result::fail ("Cancelled");
#endif
if (targetFolder.exists())
{
auto oldFolder = targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling();
if (! targetFolder.moveFileTo (oldFolder))
return Result::fail ("Could not remove the existing folder!\n\n"
"This may happen if you are trying to download into a directory that requires administrator privileges to modify.\n"
"Please select a folder that is writable by the current user.");
}
if (! unzipTarget.folder.getChildFile ("JUCE").moveFileTo (targetFolder))
return Result::fail ("Could not overwrite the existing folder!\n\n"
"This may happen if you are trying to download into a directory that requires administrator privileges to modify.\n"
"Please select a folder that is writable by the current user.");
return Result::ok();
}
Result setFilePermissions (const File& root, const ZipFile& zip)
{
constexpr uint32 executableFlag = (1 << 22);
for (int i = 0; i < zip.getNumEntries(); ++i)
{
auto* entry = zip.getEntry (i);
if ((entry->externalFileAttributes & executableFlag) != 0 && entry->filename.getLastCharacter() != '/')
{
auto exeFile = root.getChildFile (entry->filename);
if (! exeFile.exists())
return Result::fail ("Failed to find executable file when setting permissions " + exeFile.getFileName());
if (! exeFile.setExecutePermission (true))
return Result::fail ("Failed to set executable file permission for " + exeFile.getFileName());
}
}
return Result::ok();
}
VersionInfo::Asset asset;
File targetFolder;
std::function<void()> completionCallback;
};
static void restartProcess (const File& targetFolder)
{
#if JUCE_MAC || JUCE_LINUX || JUCE_BSD
#if JUCE_MAC
auto newProcess = targetFolder.getChildFile ("Projucer.app").getChildFile ("Contents").getChildFile ("MacOS").getChildFile ("Projucer");
#elif JUCE_LINUX || JUCE_BSD
auto newProcess = targetFolder.getChildFile ("Projucer");
#endif
StringArray command ("/bin/sh", "-c", "while killall -0 Projucer; do sleep 5; done; " + newProcess.getFullPathName().quoted());
#elif JUCE_WINDOWS
auto newProcess = targetFolder.getChildFile ("Projucer.exe");
auto command = "cmd.exe /c\"@echo off & for /l %a in (0) do ( tasklist | find \"Projucer\" >nul & ( if errorlevel 1 ( "
+ targetFolder.getChildFile ("Projucer.exe").getFullPathName().quoted() + " & exit /b ) else ( timeout /t 10 >nul ) ) )\"";
#endif
if (newProcess.existsAsFile())
{
ChildProcess restartProcess;
restartProcess.start (command, 0);
ProjucerApplication::getApp().systemRequestedQuit();
}
}
void LatestVersionCheckerAndUpdater::downloadAndInstall (const VersionInfo::Asset& asset, const File& targetFolder)
{
installer.reset (new DownloadAndInstallThread (asset, targetFolder,
[this, targetFolder]
{
installer.reset();
restartProcess (targetFolder);
}));
}
//==============================================================================
JUCE_IMPLEMENT_SINGLETON (LatestVersionCheckerAndUpdater)

View File

@ -0,0 +1,62 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../Utility/Helpers/jucer_VersionInfo.h"
class DownloadAndInstallThread;
class LatestVersionCheckerAndUpdater : public DeletedAtShutdown,
private Thread
{
public:
LatestVersionCheckerAndUpdater();
~LatestVersionCheckerAndUpdater() override;
void checkForNewVersion (bool isBackgroundCheck);
//==============================================================================
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (LatestVersionCheckerAndUpdater)
private:
//==============================================================================
void run() override;
void askUserAboutNewVersion (const String&, const String&, const VersionInfo::Asset&);
void askUserForLocationToDownload (const VersionInfo::Asset&);
void downloadAndInstall (const VersionInfo::Asset&, const File&);
void showDialogWindow (const String&, const String&, const VersionInfo::Asset&);
void addNotificationToOpenProjects (const VersionInfo::Asset&);
//==============================================================================
bool backgroundCheck = false;
std::unique_ptr<DownloadAndInstallThread> installer;
std::unique_ptr<Component> dialogWindow;
std::unique_ptr<FileChooser> chooser;
JUCE_DECLARE_WEAK_REFERENCEABLE (LatestVersionCheckerAndUpdater)
};

View File

@ -0,0 +1,108 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
/**
A namespace to hold all the possible command IDs.
*/
namespace CommandIDs
{
enum
{
newProject = 0x300000,
newProjectFromClipboard = 0x300001,
newPIP = 0x300002,
open = 0x300003,
closeDocument = 0x300004,
saveDocument = 0x300005,
saveDocumentAs = 0x300006,
launchDemoRunner = 0x300007,
closeProject = 0x300010,
saveProject = 0x300011,
saveAll = 0x300012,
openInIDE = 0x300013,
saveAndOpenInIDE = 0x300014,
createNewExporter = 0x300015,
showUTF8Tool = 0x300020,
showGlobalPathsWindow = 0x300021,
showTranslationTool = 0x300022,
showSVGPathTool = 0x300023,
showAboutWindow = 0x300024,
checkForNewVersion = 0x300025,
enableNewVersionCheck = 0x300026,
enableGUIEditor = 0x300027,
showProjectSettings = 0x300030,
showFileExplorerPanel = 0x300033,
showModulesPanel = 0x300034,
showExportersPanel = 0x300035,
showExporterSettings = 0x300036,
closeWindow = 0x300040,
closeAllWindows = 0x300041,
closeAllDocuments = 0x300042,
goToPreviousDoc = 0x300043,
goToNextDoc = 0x300044,
goToCounterpart = 0x300045,
deleteSelectedItem = 0x300046,
goToPreviousWindow = 0x300047,
goToNextWindow = 0x300048,
clearRecentFiles = 0x300049,
showFindPanel = 0x300050,
findSelection = 0x300051,
findNext = 0x300052,
findPrevious = 0x300053,
enableSnapToGrid = 0x300070,
zoomIn = 0x300071,
zoomOut = 0x300072,
zoomNormal = 0x300073,
spaceBarDrag = 0x300074,
loginLogout = 0x300090,
showForum = 0x300100,
showAPIModules = 0x300101,
showAPIClasses = 0x300102,
showTutorials = 0x300103,
addNewGUIFile = 0x300200,
lastCommandIDEntry
};
}
namespace CommandCategories
{
static const char* const general = "General";
static const char* const editing = "Editing";
static const char* const view = "View";
static const char* const windows = "Windows";
}

View File

@ -0,0 +1,933 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "jucer_Headers.h"
#include "jucer_Application.h"
#include "../Utility/Helpers/jucer_TranslationHelpers.h"
#include "jucer_CommandLine.h"
//==============================================================================
const char* preferredLineFeed = "\r\n";
const char* getPreferredLineFeed() { return preferredLineFeed; }
//==============================================================================
namespace
{
static void hideDockIcon()
{
#if JUCE_MAC
Process::setDockIconVisible (false);
#endif
}
static Array<File> findAllSourceFiles (const File& folder)
{
Array<File> files;
for (const auto& di : RangedDirectoryIterator (folder, true, "*.cpp;*.cxx;*.cc;*.c;*.h;*.hpp;*.hxx;*.hpp;*.mm;*.m;*.java;*.dox;*.soul;*.js", File::findFiles))
if (! di.getFile().isSymbolicLink())
files.add (di.getFile());
return files;
}
static void replaceFile (const File& file, const String& newText, const String& message)
{
std::cout << message << file.getFullPathName() << std::endl;
TemporaryFile temp (file);
if (! temp.getFile().replaceWithText (newText, false, false, nullptr))
ConsoleApplication::fail ("!!! ERROR Couldn't write to temp file!");
if (! temp.overwriteTargetFileWithTemporary())
ConsoleApplication::fail ("!!! ERROR Couldn't write to file!");
}
//==============================================================================
struct LoadedProject
{
explicit LoadedProject (const ArgumentList::Argument& fileToLoad)
{
hideDockIcon();
auto projectFile = fileToLoad.resolveAsExistingFile();
if (! projectFile.hasFileExtension (Project::projectFileExtension))
ConsoleApplication::fail (projectFile.getFullPathName() + " isn't a valid jucer project file!");
project.reset (new Project (projectFile));
if (! project->loadFrom (projectFile, true, false))
{
project.reset();
ConsoleApplication::fail ("Failed to load the project file: " + projectFile.getFullPathName());
}
preferredLineFeed = project->getProjectLineFeed().toRawUTF8();
}
void save (bool justSaveResources, bool fixMissingDependencies)
{
if (project != nullptr)
{
if (! justSaveResources)
rescanModulePathsIfNecessary();
if (fixMissingDependencies)
tryToFixMissingModuleDependencies();
const auto onCompletion = [this] (Result result)
{
project.reset();
if (result.failed())
ConsoleApplication::fail ("Error when saving: " + result.getErrorMessage());
};
if (justSaveResources)
onCompletion (project->saveResourcesOnly());
else
project->saveProject (Async::no, nullptr, onCompletion);
}
}
void rescanModulePathsIfNecessary()
{
bool scanJUCEPath = false, scanUserPaths = false;
const auto& modules = project->getEnabledModules();
for (auto i = modules.getNumModules(); --i >= 0;)
{
const auto& id = modules.getModuleID (i);
if (isJUCEModule (id) && ! scanJUCEPath)
{
if (modules.shouldUseGlobalPath (id))
scanJUCEPath = true;
}
else if (! scanUserPaths)
{
if (modules.shouldUseGlobalPath (id))
scanUserPaths = true;
}
}
if (scanJUCEPath)
ProjucerApplication::getApp().rescanJUCEPathModules();
if (scanUserPaths)
ProjucerApplication::getApp().rescanUserPathModules();
}
void tryToFixMissingModuleDependencies()
{
auto& modules = project->getEnabledModules();
for (const auto& m : modules.getModulesWithMissingDependencies())
modules.tryToFixMissingDependencies (m);
}
std::unique_ptr<Project> project;
};
//==============================================================================
/* Running a command-line of the form "projucer --resave foobar.jucer" will try to load
that project and re-export all of its targets.
*/
static void resaveProject (const ArgumentList& args, bool justSaveResources)
{
args.checkMinNumArguments (2);
LoadedProject proj (args[1]);
std::cout << (justSaveResources ? "Re-saving project resources: "
: "Re-saving file: ")
<< proj.project->getFile().getFullPathName() << std::endl;
proj.save (justSaveResources, args.containsOption ("--fix-missing-dependencies"));
}
//==============================================================================
static void getVersion (const ArgumentList& args)
{
args.checkMinNumArguments (2);
LoadedProject proj (args[1]);
std::cout << proj.project->getVersionString() << std::endl;
}
//==============================================================================
static void setVersion (const ArgumentList& args)
{
args.checkMinNumArguments (2);
LoadedProject proj (args[2]);
String version (args[1].text.trim());
std::cout << "Setting project version: " << version << std::endl;
proj.project->setProjectVersion (version);
proj.save (false, false);
}
//==============================================================================
static void bumpVersion (const ArgumentList& args)
{
args.checkMinNumArguments (2);
LoadedProject proj (args[1]);
String version = proj.project->getVersionString();
version = version.upToLastOccurrenceOf (".", true, false)
+ String (version.getTrailingIntValue() + 1);
std::cout << "Bumping project version to: " << version << std::endl;
proj.project->setProjectVersion (version);
proj.save (false, false);
}
static void gitTag (const ArgumentList& args)
{
args.checkMinNumArguments (2);
LoadedProject proj (args[1]);
String version (proj.project->getVersionString());
if (version.trim().isEmpty())
ConsoleApplication::fail ("Cannot read version number from project!");
StringArray command;
command.add ("git");
command.add ("tag");
command.add ("-a");
command.add (version);
command.add ("-m");
command.add (version.quoted());
std::cout << "Performing command: " << command.joinIntoString(" ") << std::endl;
ChildProcess c;
if (! c.start (command, 0))
ConsoleApplication::fail ("Cannot run git!");
c.waitForProcessToFinish (10000);
if (c.getExitCode() != 0)
ConsoleApplication::fail ("git command failed!");
}
//==============================================================================
static void showStatus (const ArgumentList& args)
{
args.checkMinNumArguments (2);
LoadedProject proj (args[1]);
std::cout << "Project file: " << proj.project->getFile().getFullPathName() << std::endl
<< "Name: " << proj.project->getProjectNameString() << std::endl
<< "UID: " << proj.project->getProjectUIDString() << std::endl;
auto& modules = proj.project->getEnabledModules();
if (int numModules = modules.getNumModules())
{
std::cout << "Modules:" << std::endl;
for (int i = 0; i < numModules; ++i)
std::cout << " " << modules.getModuleID (i) << std::endl;
}
}
//==============================================================================
static String getModulePackageName (const LibraryModule& module)
{
return module.getID() + ".jucemodule";
}
static void zipModule (const File& targetFolder, const File& moduleFolder)
{
jassert (targetFolder.isDirectory());
auto moduleFolderParent = moduleFolder.getParentDirectory();
LibraryModule module (moduleFolder);
if (! module.isValid())
ConsoleApplication::fail (moduleFolder.getFullPathName() + " is not a valid module folder!");
auto targetFile = targetFolder.getChildFile (getModulePackageName (module));
ZipFile::Builder zip;
{
for (const auto& i : RangedDirectoryIterator (moduleFolder, true, "*", File::findFiles))
if (! i.getFile().isHidden())
zip.addFile (i.getFile(), 9, i.getFile().getRelativePathFrom (moduleFolderParent));
}
std::cout << "Writing: " << targetFile.getFullPathName() << std::endl;
TemporaryFile temp (targetFile);
{
FileOutputStream out (temp.getFile());
if (! (out.openedOk() && zip.writeToStream (out, nullptr)))
ConsoleApplication::fail ("Failed to write to the target file: " + targetFile.getFullPathName());
}
if (! temp.overwriteTargetFileWithTemporary())
ConsoleApplication::fail ("Failed to write to the target file: " + targetFile.getFullPathName());
}
static void buildModules (const ArgumentList& args, const bool buildAllWithIndex)
{
hideDockIcon();
args.checkMinNumArguments (3);
auto targetFolder = args[1].resolveAsFile();
if (! targetFolder.isDirectory())
ConsoleApplication::fail ("The first argument must be the directory to put the result.");
if (buildAllWithIndex)
{
auto folderToSearch = args[2].resolveAsFile();
var infoList;
for (const auto& i : RangedDirectoryIterator (folderToSearch, false, "*", File::findDirectories))
{
LibraryModule module (i.getFile());
if (module.isValid())
{
zipModule (targetFolder, i.getFile());
var moduleInfo (new DynamicObject());
moduleInfo.getDynamicObject()->setProperty ("file", getModulePackageName (module));
moduleInfo.getDynamicObject()->setProperty ("info", module.moduleInfo.getModuleInfo());
infoList.append (moduleInfo);
}
}
auto indexFile = targetFolder.getChildFile ("modulelist");
std::cout << "Writing: " << indexFile.getFullPathName() << std::endl;
indexFile.replaceWithText (JSON::toString (infoList), false, false);
}
else
{
for (int i = 2; i < args.size(); ++i)
zipModule (targetFolder, args[i].resolveAsFile());
}
}
//==============================================================================
struct CleanupOptions
{
bool removeTabs;
bool fixDividerComments;
};
static void cleanWhitespace (const File& file, CleanupOptions options)
{
auto content = file.loadFileAsString();
auto isProjucerTemplateFile = [file, content]
{
return file.getFullPathName().contains ("Templates")
&& content.contains ("%""%") && content.contains ("//[");
}();
if (isProjucerTemplateFile)
return;
StringArray lines;
lines.addLines (content);
bool anyTabsRemoved = false;
for (int i = 0; i < lines.size(); ++i)
{
String& line = lines.getReference (i);
if (options.removeTabs && line.containsChar ('\t'))
{
anyTabsRemoved = true;
for (;;)
{
const int tabPos = line.indexOfChar ('\t');
if (tabPos < 0)
break;
const int spacesPerTab = 4;
const int spacesNeeded = spacesPerTab - (tabPos % spacesPerTab);
line = line.replaceSection (tabPos, 1, String::repeatedString (" ", spacesNeeded));
}
}
if (options.fixDividerComments)
{
auto afterIndent = line.trim();
if (afterIndent.startsWith ("//") && afterIndent.length() > 20)
{
afterIndent = afterIndent.substring (2);
if (afterIndent.containsOnly ("=")
|| afterIndent.containsOnly ("/")
|| afterIndent.containsOnly ("-"))
{
line = line.substring (0, line.indexOfChar ('/'))
+ "//" + String::repeatedString ("=", 78);
}
}
}
line = line.trimEnd();
}
if (options.removeTabs && ! anyTabsRemoved)
return;
auto newText = joinLinesIntoSourceFile (lines);
if (newText != content && newText != content + getPreferredLineFeed())
replaceFile (file, newText, options.removeTabs ? "Removing tabs in: "
: "Cleaning file: ");
}
static void scanFilesForCleanup (const ArgumentList& args, CleanupOptions options)
{
args.checkMinNumArguments (2);
for (auto it = args.arguments.begin() + 1; it < args.arguments.end(); ++it)
{
auto target = it->resolveAsFile();
Array<File> files;
if (target.isDirectory())
files = findAllSourceFiles (target);
else
files.add (target);
for (int i = 0; i < files.size(); ++i)
cleanWhitespace (files.getReference (i), options);
}
}
static void cleanWhitespace (const ArgumentList& args, bool replaceTabs)
{
CleanupOptions options = { replaceTabs, false };
scanFilesForCleanup (args, options);
}
static void tidyDividerComments (const ArgumentList& args)
{
CleanupOptions options = { false, true };
scanFilesForCleanup (args, options);
}
//==============================================================================
static File findSimilarlyNamedHeader (const Array<File>& allFiles, const String& name, const File& sourceFile)
{
File result;
for (auto& f : allFiles)
{
if (f.getFileName().equalsIgnoreCase (name) && f != sourceFile)
{
if (result.exists())
return {}; // multiple possible results, so don't change it!
result = f;
}
}
return result;
}
static void fixIncludes (const File& file, const Array<File>& allFiles)
{
const String content (file.loadFileAsString());
StringArray lines;
lines.addLines (content);
bool hasChanged = false;
for (auto& line : lines)
{
if (line.trimStart().startsWith ("#include \""))
{
auto includedFile = line.fromFirstOccurrenceOf ("\"", true, false)
.upToLastOccurrenceOf ("\"", true, false)
.trim()
.unquoted();
auto target = file.getSiblingFile (includedFile);
if (! target.exists())
{
auto header = findSimilarlyNamedHeader (allFiles, target.getFileName(), file);
if (header.exists())
{
line = line.upToFirstOccurrenceOf ("#include \"", true, false)
+ header.getRelativePathFrom (file.getParentDirectory())
.replaceCharacter ('\\', '/')
+ "\"";
hasChanged = true;
}
}
}
}
if (hasChanged)
{
auto newText = joinLinesIntoSourceFile (lines);
if (newText != content && newText != content + getPreferredLineFeed())
replaceFile (file, newText, "Fixing includes in: ");
}
}
static void fixRelativeIncludePaths (const ArgumentList& args)
{
args.checkMinNumArguments (2);
auto target = args[1].resolveAsExistingFolder();
auto files = findAllSourceFiles (target);
for (int i = 0; i < files.size(); ++i)
fixIncludes (files.getReference(i), files);
}
//==============================================================================
static String getStringConcatenationExpression (Random& rng, int start, int length)
{
jassert (length > 0);
if (length == 1)
return "s" + String (start);
int breakPos = jlimit (1, length - 1, (length / 3) + rng.nextInt (jmax (1, length / 3)));
return "(" + getStringConcatenationExpression (rng, start, breakPos)
+ " + " + getStringConcatenationExpression (rng, start + breakPos, length - breakPos) + ")";
}
static void generateObfuscatedStringCode (const ArgumentList& args)
{
args.checkMinNumArguments (2);
auto originalText = args[1].text.unquoted();
struct Section
{
String text;
int position, index;
void writeGenerator (MemoryOutputStream& out) const
{
String name ("s" + String (index));
out << " String " << name << "; " << name;
auto escapeIfSingleQuote = [] (const String& s) -> String
{
if (s == "\'")
return "\\'";
return s;
};
for (int i = 0; i < text.length(); ++i)
out << " << '" << escapeIfSingleQuote (String::charToString (text[i])) << "'";
out << ";" << preferredLineFeed;
}
};
Array<Section> sections;
String text = originalText;
Random rng;
while (text.isNotEmpty())
{
int pos = jmax (0, text.length() - (1 + rng.nextInt (6)));
Section s = { text.substring (pos), pos, 0 };
sections.insert (0, s);
text = text.substring (0, pos);
}
for (int i = 0; i < sections.size(); ++i)
sections.getReference(i).index = i;
for (int i = 0; i < sections.size(); ++i)
sections.swap (i, rng.nextInt (sections.size()));
MemoryOutputStream out;
out << "String createString()" << preferredLineFeed
<< "{" << preferredLineFeed;
for (int i = 0; i < sections.size(); ++i)
sections.getReference(i).writeGenerator (out);
out << preferredLineFeed
<< " String result = " << getStringConcatenationExpression (rng, 0, sections.size()) << ";" << preferredLineFeed
<< preferredLineFeed
<< " jassert (result == " << originalText.quoted() << ");" << preferredLineFeed
<< " return result;" << preferredLineFeed
<< "}" << preferredLineFeed;
std::cout << out.toString() << std::endl;
}
static void scanFoldersForTranslationFiles (const ArgumentList& args)
{
args.checkMinNumArguments (2);
StringArray translations;
for (auto it = args.arguments.begin() + 1; it != args.arguments.end(); ++it)
{
auto directoryToSearch = it->resolveAsExistingFolder();
TranslationHelpers::scanFolderForTranslations (translations, directoryToSearch);
}
std::cout << TranslationHelpers::mungeStrings (translations) << std::endl;
}
static void createFinishedTranslationFile (const ArgumentList& args)
{
args.checkMinNumArguments (3);
auto preTranslated = args[1].resolveAsExistingFile().loadFileAsString();
auto postTranslated = args[2].resolveAsExistingFile().loadFileAsString();
auto localisedContent = (args.size() > 3 ? args[3].resolveAsExistingFile().loadFileAsString() : String());
auto localised = LocalisedStrings (localisedContent, false);
using TH = TranslationHelpers;
std::cout << TH::createFinishedTranslationFile (TH::withTrimmedEnds (TH::breakApart (preTranslated)),
TH::withTrimmedEnds (TH::breakApart (postTranslated)),
localised) << std::endl;
}
//==============================================================================
static void encodeBinary (const ArgumentList& args)
{
args.checkMinNumArguments (3);
auto source = args[1].resolveAsExistingFile();
auto target = args[2].resolveAsExistingFile();
MemoryOutputStream literal;
size_t dataSize = 0;
{
MemoryBlock data;
FileInputStream input (source);
input.readIntoMemoryBlock (data);
build_tools::writeDataAsCppLiteral (data, literal, true, true);
dataSize = data.getSize();
}
auto variableName = build_tools::makeBinaryDataIdentifierName (source);
MemoryOutputStream header, cpp;
header << "// Auto-generated binary data by the Projucer" << preferredLineFeed
<< "// Source file: " << source.getRelativePathFrom (target.getParentDirectory()) << preferredLineFeed
<< preferredLineFeed;
cpp << header.toString();
if (target.hasFileExtension (headerFileExtensions))
{
header << "static constexpr unsigned char " << variableName << "[] =" << preferredLineFeed
<< literal.toString() << preferredLineFeed
<< preferredLineFeed;
replaceFile (target, header.toString(), "Writing: ");
}
else if (target.hasFileExtension (cppFileExtensions))
{
header << "extern const char* " << variableName << ";" << preferredLineFeed
<< "const unsigned int " << variableName << "Size = " << (int) dataSize << ";" << preferredLineFeed
<< preferredLineFeed;
cpp << CodeHelpers::createIncludeStatement (target.withFileExtension (".h").getFileName()) << preferredLineFeed
<< preferredLineFeed
<< "static constexpr unsigned char " << variableName << "_local[] =" << preferredLineFeed
<< literal.toString() << preferredLineFeed
<< preferredLineFeed
<< "const char* " << variableName << " = (const char*) " << variableName << "_local;" << preferredLineFeed;
replaceFile (target, cpp.toString(), "Writing: ");
replaceFile (target.withFileExtension (".h"), header.toString(), "Writing: ");
}
else
{
ConsoleApplication::fail ("You need to specify a .h or .cpp file as the target");
}
}
//==============================================================================
static bool isThisOS (const String& os)
{
auto targetOS = TargetOS::unknown;
if (os == "osx") targetOS = TargetOS::osx;
else if (os == "windows") targetOS = TargetOS::windows;
else if (os == "linux") targetOS = TargetOS::linux;
if (targetOS == TargetOS::unknown)
ConsoleApplication::fail ("You need to specify a valid OS! Use osx, windows or linux");
return targetOS == TargetOS::getThisOS();
}
static bool isValidPathIdentifier (const String& id, const String& os)
{
return id == "vstLegacyPath" || (id == "aaxPath" && os != "linux") || (id == "rtasPath" && os != "linux")
|| id == "androidSDKPath" || id == "defaultJuceModulePath" || id == "defaultUserModulePath";
}
static void setGlobalPath (const ArgumentList& args)
{
args.checkMinNumArguments (3);
if (! isValidPathIdentifier (args[2].text, args[1].text))
ConsoleApplication::fail ("Identifier " + args[2].text + " is not valid for the OS " + args[1].text);
auto userAppData = File::getSpecialLocation (File::userApplicationDataDirectory);
#if JUCE_MAC
userAppData = userAppData.getChildFile ("Application Support");
#endif
auto settingsFile = userAppData.getChildFile ("Projucer").getChildFile ("Projucer.settings");
auto xml = parseXML (settingsFile);
if (xml == nullptr)
ConsoleApplication::fail ("Settings file not valid!");
auto settingsTree = ValueTree::fromXml (*xml);
if (! settingsTree.isValid())
ConsoleApplication::fail ("Settings file not valid!");
ValueTree childToSet;
if (isThisOS (args[1].text))
{
childToSet = settingsTree.getChildWithProperty (Ids::name, "PROJECT_DEFAULT_SETTINGS")
.getOrCreateChildWithName ("PROJECT_DEFAULT_SETTINGS", nullptr);
}
else
{
childToSet = settingsTree.getChildWithProperty (Ids::name, "FALLBACK_PATHS")
.getOrCreateChildWithName ("FALLBACK_PATHS", nullptr)
.getOrCreateChildWithName (args[1].text + "Fallback", nullptr);
}
if (! childToSet.isValid())
ConsoleApplication::fail ("Failed to set the requested setting!");
childToSet.setProperty (args[2].text, args[3].resolveAsFile().getFullPathName(), nullptr);
settingsFile.replaceWithText (settingsTree.toXmlString());
}
static void createProjectFromPIP (const ArgumentList& args)
{
args.checkMinNumArguments (3);
auto pipFile = args[1].resolveAsFile();
if (! pipFile.existsAsFile())
ConsoleApplication::fail ("PIP file doesn't exist.");
auto outputDir = args[2].resolveAsFile();
if (! outputDir.exists())
{
auto res = outputDir.createDirectory();
std::cout << "Creating directory " << outputDir.getFullPathName() << std::endl;
}
File juceModulesPath, userModulesPath;
if (args.size() > 3)
{
juceModulesPath = args[3].resolveAsFile();
if (! juceModulesPath.exists())
ConsoleApplication::fail ("Specified JUCE modules directory doesn't exist.");
if (args.size() == 5)
{
userModulesPath = args[4].resolveAsFile();
if (! userModulesPath.exists())
ConsoleApplication::fail ("Specified JUCE modules directory doesn't exist.");
}
}
PIPGenerator generator (pipFile, outputDir, juceModulesPath, userModulesPath);
auto createJucerFileResult = generator.createJucerFile();
if (! createJucerFileResult)
ConsoleApplication::fail (createJucerFileResult.getErrorMessage());
auto createMainCppResult = generator.createMainCpp();
if (! createMainCppResult)
ConsoleApplication::fail (createMainCppResult.getErrorMessage());
}
//==============================================================================
static void showHelp()
{
hideDockIcon();
auto appName = JUCEApplication::getInstance()->getApplicationName();
std::cout << appName << std::endl
<< std::endl
<< "Usage: " << std::endl
<< std::endl
<< " " << appName << " --resave project_file" << std::endl
<< " Resaves all files and resources in a project. Add the \"--fix-missing-dependencies\" option to automatically fix any missing module dependencies." << std::endl
<< std::endl
<< " " << appName << " --resave-resources project_file" << std::endl
<< " Resaves just the binary resources for a project." << std::endl
<< std::endl
<< " " << appName << " --get-version project_file" << std::endl
<< " Returns the version number of a project." << std::endl
<< std::endl
<< " " << appName << " --set-version version_number project_file" << std::endl
<< " Updates the version number in a project." << std::endl
<< std::endl
<< " " << appName << " --bump-version project_file" << std::endl
<< " Updates the minor version number in a project by 1." << std::endl
<< std::endl
<< " " << appName << " --git-tag-version project_file" << std::endl
<< " Invokes 'git tag' to attach the project's version number to the current git repository." << std::endl
<< std::endl
<< " " << appName << " --status project_file" << std::endl
<< " Displays information about a project." << std::endl
<< std::endl
<< " " << appName << " --buildmodule target_folder module_folder" << std::endl
<< " Zips a module into a downloadable file format." << std::endl
<< std::endl
<< " " << appName << " --buildallmodules target_folder module_folder" << std::endl
<< " Zips all modules in a given folder and creates an index for them." << std::endl
<< std::endl
<< " " << appName << " --trim-whitespace target_folder" << std::endl
<< " Scans the given folder for C/C++ source files (recursively), and trims any trailing whitespace from their lines, as well as normalising their line-endings to CR-LF." << std::endl
<< std::endl
<< " " << appName << " --remove-tabs target_folder" << std::endl
<< " Scans the given folder for C/C++ source files (recursively), and replaces any tab characters with 4 spaces." << std::endl
<< std::endl
<< " " << appName << " --tidy-divider-comments target_folder" << std::endl
<< " Scans the given folder for C/C++ source files (recursively), and normalises any juce-style comment division lines (i.e. any lines that look like //===== or //------- or /////////// will be replaced)." << std::endl
<< std::endl
<< " " << appName << " --fix-broken-include-paths target_folder" << std::endl
<< " Scans the given folder for C/C++ source files (recursively). Where a file contains an #include of one of the other filenames, it changes it to use the optimum relative path. Helpful for auto-fixing includes when re-arranging files and folders in a project." << std::endl
<< std::endl
<< " " << appName << " --obfuscated-string-code string_to_obfuscate" << std::endl
<< " Generates a C++ function which returns the given string, but in an obfuscated way." << std::endl
<< std::endl
<< " " << appName << " --encode-binary source_binary_file target_cpp_file" << std::endl
<< " Converts a binary file to a C++ file containing its contents as a block of data. Provide a .h file as the target if you want a single output file, or a .cpp file if you want a pair of .h/.cpp files." << std::endl
<< std::endl
<< " " << appName << " --trans target_folders..." << std::endl
<< " Scans each of the given folders (recursively) for any NEEDS_TRANS macros, and generates a translation file that can be used with Projucer's translation file builder" << std::endl
<< std::endl
<< " " << appName << " --trans-finish pre_translated_file post_translated_file optional_existing_translation_file" << std::endl
<< " Creates a completed translations mapping file, that can be used to initialise a LocalisedStrings object. This allows you to localise the strings in your project" << std::endl
<< std::endl
<< " " << appName << " --set-global-search-path os identifier_to_set new_path" << std::endl
<< " Sets the global path for a specified os and identifier. The os should be either osx, windows or linux and the identifiers can be any of the following: "
<< "defaultJuceModulePath, defaultUserModulePath, vstLegacyPath, aaxPath (not valid on linux), rtasPath (not valid on linux), or androidSDKPath. " << std::endl
<< std::endl
<< " " << appName << " --create-project-from-pip path/to/PIP path/to/output path/to/JUCE/modules (optional) path/to/user/modules (optional)" << std::endl
<< " Generates a folder containing a JUCE project in the specified output path using the specified PIP file. Use the optional JUCE and user module paths to override "
"the global module paths." << std::endl
<< std::endl
<< "Note that for any of the file-rewriting commands, add the option \"--lf\" if you want it to use LF linefeeds instead of CRLF" << std::endl
<< std::endl;
}
}
//==============================================================================
int performCommandLine (const ArgumentList& args)
{
return ConsoleApplication::invokeCatchingFailures ([&]() -> int
{
if (args.containsOption ("--lf"))
preferredLineFeed = "\n";
auto command = args[0];
auto matchCommand = [&] (StringRef name) -> bool
{
return command == name || command.isLongOption (name);
};
if (matchCommand ("help")) { showHelp(); return 0; }
if (matchCommand ("h")) { showHelp(); return 0; }
if (matchCommand ("resave")) { resaveProject (args, false); return 0; }
if (matchCommand ("resave-resources")) { resaveProject (args, true); return 0; }
if (matchCommand ("get-version")) { getVersion (args); return 0; }
if (matchCommand ("set-version")) { setVersion (args); return 0; }
if (matchCommand ("bump-version")) { bumpVersion (args); return 0; }
if (matchCommand ("git-tag-version")) { gitTag (args); return 0; }
if (matchCommand ("buildmodule")) { buildModules (args, false); return 0; }
if (matchCommand ("buildallmodules")) { buildModules (args, true); return 0; }
if (matchCommand ("status")) { showStatus (args); return 0; }
if (matchCommand ("trim-whitespace")) { cleanWhitespace (args, false); return 0; }
if (matchCommand ("remove-tabs")) { cleanWhitespace (args, true); return 0; }
if (matchCommand ("tidy-divider-comments")) { tidyDividerComments (args); return 0; }
if (matchCommand ("fix-broken-include-paths")) { fixRelativeIncludePaths (args); return 0; }
if (matchCommand ("obfuscated-string-code")) { generateObfuscatedStringCode (args); return 0; }
if (matchCommand ("encode-binary")) { encodeBinary (args); return 0; }
if (matchCommand ("trans")) { scanFoldersForTranslationFiles (args); return 0; }
if (matchCommand ("trans-finish")) { createFinishedTranslationFile (args); return 0; }
if (matchCommand ("set-global-search-path")) { setGlobalPath (args); return 0; }
if (matchCommand ("create-project-from-pip")) { createProjectFromPIP (args); return 0; }
if (command.isLongOption() || command.isShortOption())
ConsoleApplication::fail ("Unrecognised command: " + command.text.quoted());
return commandLineNotPerformed;
});
}

View File

@ -0,0 +1,30 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
int performCommandLine (const ArgumentList&);
enum { commandLineNotPerformed = 0x72346231 };

View File

@ -0,0 +1,99 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
// The GCC extensions define linux somewhere in the headers, so undef it here...
#if JUCE_GCC
#undef linux
#endif
struct TargetOS
{
enum OS
{
windows = 0,
osx,
linux,
unknown
};
static OS getThisOS() noexcept
{
#if JUCE_WINDOWS
return windows;
#elif JUCE_MAC
return osx;
#elif JUCE_LINUX || JUCE_BSD
return linux;
#else
return unknown;
#endif
}
};
typedef TargetOS::OS DependencyPathOS;
//==============================================================================
#include "../Settings/jucer_StoredSettings.h"
#include "../Utility/UI/jucer_Icons.h"
#include "../Utility/Helpers/jucer_MiscUtilities.h"
#include "../Utility/Helpers/jucer_CodeHelpers.h"
#include "../Utility/Helpers/jucer_FileHelpers.h"
#include "../Utility/Helpers/jucer_ValueSourceHelpers.h"
#include "../Utility/Helpers/jucer_PresetIDs.h"
#include "jucer_CommandIDs.h"
//==============================================================================
const char* const projectItemDragType = "Project Items";
const char* const drawableItemDragType = "Drawable Items";
const char* const componentItemDragType = "Components";
enum ColourIds
{
backgroundColourId = 0x2340000,
secondaryBackgroundColourId = 0x2340001,
defaultTextColourId = 0x2340002,
widgetTextColourId = 0x2340003,
defaultButtonBackgroundColourId = 0x2340004,
secondaryButtonBackgroundColourId = 0x2340005,
userButtonBackgroundColourId = 0x2340006,
defaultIconColourId = 0x2340007,
treeIconColourId = 0x2340008,
defaultHighlightColourId = 0x2340009,
defaultHighlightedTextColourId = 0x234000a,
codeEditorLineNumberColourId = 0x234000b,
activeTabIconColourId = 0x234000c,
inactiveTabBackgroundColourId = 0x234000d,
inactiveTabIconColourId = 0x234000e,
contentHeaderBackgroundColourId = 0x234000f,
widgetBackgroundColourId = 0x2340010,
secondaryWidgetBackgroundColourId = 0x2340011,
};
//==============================================================================
static constexpr int projucerMajorVersion = ProjectInfo::versionNumber >> 16;

View File

@ -0,0 +1,29 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include <JuceHeader.h>
#include "jucer_CommonHeaders.h"

View File

@ -0,0 +1,48 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "jucer_Headers.h"
#include "jucer_Application.h"
#include "../CodeEditor/jucer_OpenDocumentManager.h"
#include "../CodeEditor/jucer_SourceCodeEditor.h"
#include "../Utility/UI/PropertyComponents/jucer_FilePathPropertyComponent.h"
#include "../Project/UI/jucer_ProjectContentComponent.h"
#include "../Project/UI/Sidebar/jucer_TreeItemTypes.h"
#include "Windows/jucer_UTF8WindowComponent.h"
#include "Windows/jucer_SVGPathDataWindowComponent.h"
#include "Windows/jucer_AboutWindowComponent.h"
#include "Windows/jucer_EditorColourSchemeWindowComponent.h"
#include "Windows/jucer_GlobalPathsWindowComponent.h"
#include "Windows/jucer_PIPCreatorWindowComponent.h"
#include "Windows/jucer_FloatingToolWindow.h"
#include "jucer_CommandLine.h"
#include "../Project/UI/jucer_ProjectContentComponent.cpp"
#include "jucer_Application.cpp"
START_JUCE_APPLICATION (ProjucerApplication)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,146 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../Utility/PIPs/jucer_PIPGenerator.h"
#include "../Project/jucer_Project.h"
#include "../CodeEditor/jucer_OpenDocumentManager.h"
class ProjectContentComponent;
//==============================================================================
/**
The big top-level window where everything happens.
*/
class MainWindow : public DocumentWindow,
public ApplicationCommandTarget,
public FileDragAndDropTarget,
public DragAndDropContainer,
private Value::Listener
{
public:
//==============================================================================
MainWindow();
~MainWindow() override;
enum class OpenInIDE { no, yes };
//==============================================================================
void closeButtonPressed() override;
//==============================================================================
bool canOpenFile (const File& file) const;
void openFile (const File& file, std::function<void (bool)> callback);
void setProject (std::unique_ptr<Project> newProject);
Project* getProject() const { return currentProject.get(); }
void makeVisible();
void restoreWindowPosition();
void updateTitleBarIcon();
void closeCurrentProject (OpenDocumentManager::SaveIfNeeded askToSave, std::function<void (bool)> callback);
void moveProject (File newProjectFile, OpenInIDE openInIDE);
void showStartPage();
void showLoginFormOverlay();
void hideLoginFormOverlay();
bool isShowingLoginForm() const noexcept { return loginFormOpen; }
bool isInterestedInFileDrag (const StringArray& files) override;
void filesDropped (const StringArray& filenames, int mouseX, int mouseY) override;
void activeWindowStatusChanged() override;
ProjectContentComponent* getProjectContentComponent() const;
//==============================================================================
ApplicationCommandTarget* getNextCommandTarget() override;
void getAllCommands (Array <CommandID>& commands) override;
void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) override;
bool perform (const InvocationInfo& info) override;
bool shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
StringArray& files, bool& canMoveFiles) override;
private:
void valueChanged (Value&) override;
static const char* getProjectWindowPosName() { return "projectWindowPos"; }
void createProjectContentCompIfNeeded();
void openPIP (const File&, std::function<void (bool)> callback);
void setupTemporaryPIPProject (PIPGenerator&);
void initialiseProjectWindow();
std::unique_ptr<Project> currentProject;
Value projectNameValue;
std::unique_ptr<Component> blurOverlayComponent;
bool loginFormOpen = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
//==============================================================================
class MainWindowList
{
public:
MainWindowList();
void forceCloseAllWindows();
void askAllWindowsToClose (std::function<void (bool)> callback);
void closeWindow (MainWindow*);
void goToSiblingWindow (MainWindow*, int delta);
void createWindowIfNoneAreOpen();
void openDocument (OpenDocumentManager::Document*, bool grabFocus);
void openFile (const File& file, std::function<void (bool)> callback, bool openInBackground = false);
MainWindow* createNewMainWindow();
MainWindow* getFrontmostWindow (bool createIfNotFound = true);
MainWindow* getOrCreateEmptyWindow();
MainWindow* getMainWindowForFile (const File&);
MainWindow* getMainWindowWithLoginFormOpen();
Project* getFrontmostProject();
void reopenLastProjects();
void saveCurrentlyOpenProjectList();
void checkWindowBounds (MainWindow&);
void sendLookAndFeelChange();
OwnedArray<MainWindow> windows;
private:
bool isInReopenLastProjects = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindowList)
JUCE_DECLARE_WEAK_REFERENCEABLE (MainWindowList)
};