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,215 @@
/*
==============================================================================
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_ModuleDescription.h"
//==============================================================================
class AvailableModulesList : private AsyncUpdater
{
public:
using ModuleIDAndFolder = std::pair<String, File>;
using ModuleIDAndFolderList = std::vector<ModuleIDAndFolder>;
AvailableModulesList() = default;
//==============================================================================
void scanPaths (const Array<File>& paths)
{
auto job = createScannerJob (paths);
auto& ref = *job;
removePendingAndAddJob (std::move (job));
scanPool.waitForJobToFinish (&ref, -1);
}
void scanPathsAsync (const Array<File>& paths)
{
removePendingAndAddJob (createScannerJob (paths));
}
//==============================================================================
ModuleIDAndFolderList getAllModules() const
{
const ScopedLock readLock (lock);
return modulesList;
}
ModuleIDAndFolder getModuleWithID (const String& id) const
{
const ScopedLock readLock (lock);
for (auto& mod : modulesList)
if (mod.first == id)
return mod;
return {};
}
//==============================================================================
void removeDuplicates (const ModuleIDAndFolderList& other)
{
const ScopedLock readLock (lock);
const auto predicate = [&] (const ModuleIDAndFolder& entry)
{
return std::find (other.begin(), other.end(), entry) != other.end();
};
modulesList.erase (std::remove_if (modulesList.begin(), modulesList.end(), predicate),
modulesList.end());
}
//==============================================================================
struct Listener
{
virtual ~Listener() = default;
virtual void availableModulesChanged (AvailableModulesList* listThatHasChanged) = 0;
};
void addListener (Listener* listenerToAdd) { listeners.add (listenerToAdd); }
void removeListener (Listener* listenerToRemove) { listeners.remove (listenerToRemove); }
private:
//==============================================================================
struct ModuleScannerJob : public ThreadPoolJob
{
ModuleScannerJob (const Array<File>& paths,
std::function<void (const ModuleIDAndFolderList&)>&& callback)
: ThreadPoolJob ("ModuleScannerJob"),
pathsToScan (paths),
completionCallback (std::move (callback))
{
}
JobStatus runJob() override
{
ModuleIDAndFolderList list;
for (auto& p : pathsToScan)
addAllModulesInFolder (p, list);
if (! shouldExit())
{
std::sort (list.begin(), list.end(), [] (const ModuleIDAndFolder& m1,
const ModuleIDAndFolder& m2)
{
return m1.first.compareIgnoreCase (m2.first) < 0;
});
completionCallback (list);
}
return jobHasFinished;
}
static bool tryToAddModuleFromFolder (const File& path, ModuleIDAndFolderList& list)
{
ModuleDescription m (path);
if (m.isValid()
&& std::find_if (list.begin(), list.end(),
[&m] (const ModuleIDAndFolder& element) { return element.first == m.getID(); }) == std::end (list))
{
list.push_back ({ m.getID(), path });
return true;
}
return false;
}
static void addAllModulesInFolder (const File& topLevelPath, ModuleIDAndFolderList& list)
{
struct FileAndDepth
{
File file;
int depth;
};
std::queue<FileAndDepth> pathsToCheck;
pathsToCheck.push ({ topLevelPath, 0 });
while (! pathsToCheck.empty())
{
const auto path = pathsToCheck.front();
pathsToCheck.pop();
if (tryToAddModuleFromFolder (path.file, list) || path.depth == 3)
continue;
for (const auto& iter : RangedDirectoryIterator (path.file, false, "*", File::findDirectories))
{
if (auto* job = ThreadPoolJob::getCurrentThreadPoolJob())
if (job->shouldExit())
return;
pathsToCheck.push({ iter.getFile(), path.depth + 1 });
}
}
}
Array<File> pathsToScan;
std::function<void (const ModuleIDAndFolderList&)> completionCallback;
};
//==============================================================================
void handleAsyncUpdate() override
{
listeners.call ([this] (Listener& l) { l.availableModulesChanged (this); });
}
std::unique_ptr<ThreadPoolJob> createScannerJob (const Array<File>& paths)
{
return std::make_unique<ModuleScannerJob> (paths, [this] (ModuleIDAndFolderList scannedModulesList)
{
if (scannedModulesList == modulesList)
return;
{
const ScopedLock swapLock (lock);
modulesList.swap (scannedModulesList);
}
triggerAsyncUpdate();
});
}
void removePendingAndAddJob (std::unique_ptr<ThreadPoolJob> jobToAdd)
{
scanPool.removeAllJobs (false, 100);
scanPool.addJob (jobToAdd.release(), true);
}
//==============================================================================
ThreadPool scanPool { 1 };
ModuleIDAndFolderList modulesList;
ListenerList<Listener> listeners;
CriticalSection lock;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AvailableModulesList)
};

View File

@ -0,0 +1,93 @@
/*
==============================================================================
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 ModuleDescription
{
public:
ModuleDescription() = default;
ModuleDescription (const File& folder)
: moduleFolder (folder),
moduleInfo (parseJUCEHeaderMetadata (getHeader()))
{
}
bool isValid() const { return getID().isNotEmpty(); }
String getID() const { return moduleInfo [Ids::ID_uppercase].toString(); }
String getVendor() const { return moduleInfo [Ids::vendor].toString(); }
String getVersion() const { return moduleInfo [Ids::version].toString(); }
String getName() const { return moduleInfo [Ids::name].toString(); }
String getDescription() const { return moduleInfo [Ids::description].toString(); }
String getLicense() const { return moduleInfo [Ids::license].toString(); }
String getMinimumCppStandard() const { return moduleInfo [Ids::minimumCppStandard].toString(); }
String getPreprocessorDefs() const { return moduleInfo [Ids::defines].toString(); }
String getExtraSearchPaths() const { return moduleInfo [Ids::searchpaths].toString(); }
var getModuleInfo() const { return moduleInfo; }
File getModuleFolder() const { return moduleFolder; }
File getFolder() const
{
jassert (moduleFolder != File());
return moduleFolder;
}
File getHeader() const
{
if (moduleFolder != File())
{
static const char* extensions[] = { ".h", ".hpp", ".hxx" };
for (auto e : extensions)
{
auto header = moduleFolder.getChildFile (moduleFolder.getFileName() + e);
if (header.existsAsFile())
return header;
}
}
return {};
}
StringArray getDependencies() const
{
auto moduleDependencies = StringArray::fromTokens (moduleInfo ["dependencies"].toString(), " \t;,", "\"'");
moduleDependencies.trim();
moduleDependencies.removeEmptyStrings();
return moduleDependencies;
}
private:
File moduleFolder;
var moduleInfo;
URL url;
};

View File

@ -0,0 +1,713 @@
/*
==============================================================================
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 "../../ProjectSaving/jucer_ProjectSaver.h"
#include "../../ProjectSaving/jucer_ProjectExport_Xcode.h"
#include "../../Application/jucer_Application.h"
//==============================================================================
LibraryModule::LibraryModule (const ModuleDescription& d)
: moduleInfo (d)
{
}
void LibraryModule::writeIncludes (ProjectSaver& projectSaver, OutputStream& out)
{
auto& project = projectSaver.getProject();
auto& modules = project.getEnabledModules();
auto moduleID = getID();
if (modules.shouldCopyModuleFilesLocally (moduleID))
{
auto juceModuleFolder = moduleInfo.getFolder();
auto localModuleFolder = project.getLocalModuleFolder (moduleID);
localModuleFolder.createDirectory();
projectSaver.copyFolder (juceModuleFolder, localModuleFolder);
}
out << "#include <" << moduleInfo.getModuleFolder().getFileName() << "/"
<< moduleInfo.getHeader().getFileName()
<< ">" << newLine;
}
void LibraryModule::addSearchPathsToExporter (ProjectExporter& exporter) const
{
auto moduleRelativePath = exporter.getModuleFolderRelativeToProject (getID());
exporter.addToExtraSearchPaths (moduleRelativePath.getParentDirectory());
const auto libDirPlatform = [&]() -> String
{
if (exporter.isLinux())
return "Linux";
if (exporter.isCodeBlocks() && exporter.isWindows())
return "MinGW";
return exporter.getTypeInfoForExporter (exporter.getExporterIdentifier()).targetFolder;
}();
auto libSubdirPath = moduleRelativePath.toUnixStyle() + "/libs/" + libDirPlatform;
auto moduleLibDir = exporter.getProject().resolveFilename (libSubdirPath);
if (moduleLibDir.exists())
exporter.addToModuleLibPaths ({ libSubdirPath, moduleRelativePath.getRoot() });
auto extraInternalSearchPaths = moduleInfo.getExtraSearchPaths().trim();
if (extraInternalSearchPaths.isNotEmpty())
{
auto paths = StringArray::fromTokens (extraInternalSearchPaths, true);
for (auto& path : paths)
exporter.addToExtraSearchPaths (moduleRelativePath.getChildFile (path.unquoted()));
}
}
void LibraryModule::addDefinesToExporter (ProjectExporter& exporter) const
{
auto extraDefs = moduleInfo.getPreprocessorDefs().trim();
if (extraDefs.isNotEmpty())
exporter.getExporterPreprocessorDefsValue() = exporter.getExporterPreprocessorDefsString() + "\n" + extraDefs;
}
void LibraryModule::addCompileUnitsToExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const
{
auto& project = exporter.getProject();
auto& modules = project.getEnabledModules();
auto moduleID = getID();
auto localModuleFolder = modules.shouldCopyModuleFilesLocally (moduleID) ? project.getLocalModuleFolder (moduleID)
: moduleInfo.getFolder();
Array<File> compiled;
findAndAddCompiledUnits (exporter, &projectSaver, compiled);
if (modules.shouldShowAllModuleFilesInProject (moduleID))
addBrowseableCode (exporter, compiled, localModuleFolder);
}
void LibraryModule::addLibsToExporter (ProjectExporter& exporter) const
{
auto parseAndAddLibsToList = [] (StringArray& libList, const String& libs)
{
libList.addTokens (libs, ", ", {});
libList.trim();
libList.removeDuplicates (false);
};
auto& project = exporter.getProject();
if (exporter.isXcode())
{
auto& xcodeExporter = dynamic_cast<XcodeProjectExporter&> (exporter);
if (project.isAUPluginHost())
{
xcodeExporter.xcodeFrameworks.add ("CoreAudioKit");
if (xcodeExporter.isOSX())
xcodeExporter.xcodeFrameworks.add ("AudioUnit");
}
auto frameworks = moduleInfo.getModuleInfo() [xcodeExporter.isOSX() ? "OSXFrameworks" : "iOSFrameworks"].toString();
xcodeExporter.xcodeFrameworks.addTokens (frameworks, ", ", {});
parseAndAddLibsToList (xcodeExporter.xcodeLibs, moduleInfo.getModuleInfo() [exporter.isOSX() ? "OSXLibs" : "iOSLibs"].toString());
}
else if (exporter.isLinux())
{
parseAndAddLibsToList (exporter.linuxLibs, moduleInfo.getModuleInfo() ["linuxLibs"].toString());
parseAndAddLibsToList (exporter.linuxPackages, moduleInfo.getModuleInfo() ["linuxPackages"].toString());
}
else if (exporter.isWindows())
{
if (exporter.isCodeBlocks())
parseAndAddLibsToList (exporter.mingwLibs, moduleInfo.getModuleInfo() ["mingwLibs"].toString());
else
parseAndAddLibsToList (exporter.windowsLibs, moduleInfo.getModuleInfo() ["windowsLibs"].toString());
}
else if (exporter.isAndroid())
{
parseAndAddLibsToList (exporter.androidLibs, moduleInfo.getModuleInfo() ["androidLibs"].toString());
}
}
void LibraryModule::addSettingsForModuleToExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const
{
addSearchPathsToExporter (exporter);
addDefinesToExporter (exporter);
addCompileUnitsToExporter (exporter, projectSaver);
addLibsToExporter (exporter);
}
void LibraryModule::getConfigFlags (Project& project, OwnedArray<Project::ConfigFlag>& flags) const
{
auto header = moduleInfo.getHeader();
jassert (header.exists());
StringArray lines;
header.readLines (lines);
for (int i = 0; i < lines.size(); ++i)
{
auto line = lines[i].trim();
if (line.startsWith ("/**") && line.containsIgnoreCase ("Config:"))
{
auto config = std::make_unique<Project::ConfigFlag>();
config->sourceModuleID = getID();
config->symbol = line.fromFirstOccurrenceOf (":", false, false).trim();
if (config->symbol.length() > 2)
{
++i;
while (! (lines[i].contains ("*/") || lines[i].contains ("@see")))
{
if (lines[i].trim().isNotEmpty())
config->description = config->description.trim() + " " + lines[i].trim();
++i;
}
config->description = config->description.upToFirstOccurrenceOf ("*/", false, false);
config->value = project.getConfigFlag (config->symbol);
i += 2;
if (lines[i].contains ("#define " + config->symbol))
{
auto value = lines[i].fromFirstOccurrenceOf ("#define " + config->symbol, false, true).trim();
config->value.setDefault (value == "0" ? false : true);
}
auto currentValue = config->value.get().toString();
if (currentValue == "enabled") config->value = true;
else if (currentValue == "disabled") config->value = false;
flags.add (std::move (config));
}
}
}
}
static void addFileWithGroups (Project::Item& group, const build_tools::RelativePath& file, const String& path)
{
auto slash = path.indexOfChar (File::getSeparatorChar());
if (slash >= 0)
{
auto topLevelGroup = path.substring (0, slash);
auto remainingPath = path.substring (slash + 1);
auto newGroup = group.getOrCreateSubGroup (topLevelGroup);
addFileWithGroups (newGroup, file, remainingPath);
}
else
{
if (! group.containsChildForFile (file))
group.addRelativeFile (file, -1, false);
}
}
struct FileSorter
{
static int compareElements (const File& f1, const File& f2)
{
return f1.getFileName().compareNatural (f2.getFileName());
}
};
void LibraryModule::findBrowseableFiles (const File& folder, Array<File>& filesFound) const
{
Array<File> tempList;
FileSorter sorter;
for (const auto& iter : RangedDirectoryIterator (folder, true, "*", File::findFiles))
if (! iter.isHidden() && iter.getFile().hasFileExtension (browseableFileExtensions))
tempList.addSorted (sorter, iter.getFile());
filesFound.addArray (tempList);
}
bool LibraryModule::CompileUnit::isNeededForExporter (ProjectExporter& exporter) const
{
if ((hasSuffix (file, "_OSX") && ! exporter.isOSX())
|| (hasSuffix (file, "_iOS") && ! exporter.isiOS())
|| (hasSuffix (file, "_Windows") && ! exporter.isWindows())
|| (hasSuffix (file, "_Linux") && ! exporter.isLinux())
|| (hasSuffix (file, "_Android") && ! exporter.isAndroid()))
return false;
auto targetType = Project::getTargetTypeFromFilePath (file, false);
if (targetType != build_tools::ProjectType::Target::unspecified && ! exporter.shouldBuildTargetType (targetType))
return false;
return exporter.usesMMFiles() ? isCompiledForObjC
: isCompiledForNonObjC;
}
String LibraryModule::CompileUnit::getFilenameForProxyFile() const
{
return "include_" + file.getFileName();
}
bool LibraryModule::CompileUnit::hasSuffix (const File& f, const char* suffix)
{
auto fileWithoutSuffix = f.getFileNameWithoutExtension() + ".";
return fileWithoutSuffix.containsIgnoreCase (suffix + String ("."))
|| fileWithoutSuffix.containsIgnoreCase (suffix + String ("_"));
}
Array<LibraryModule::CompileUnit> LibraryModule::getAllCompileUnits (build_tools::ProjectType::Target::Type forTarget) const
{
auto files = getFolder().findChildFiles (File::findFiles, false);
FileSorter sorter;
files.sort (sorter);
Array<LibraryModule::CompileUnit> units;
for (auto& file : files)
{
if (file.getFileName().startsWithIgnoreCase (getID())
&& file.hasFileExtension (sourceFileExtensions))
{
if (forTarget == build_tools::ProjectType::Target::unspecified
|| forTarget == Project::getTargetTypeFromFilePath (file, true))
{
CompileUnit cu;
cu.file = file;
units.add (std::move (cu));
}
}
}
for (auto& cu : units)
{
cu.isCompiledForObjC = true;
cu.isCompiledForNonObjC = ! cu.file.hasFileExtension ("mm;m;metal");
if (cu.isCompiledForNonObjC)
if (cu.file.withFileExtension ("mm").existsAsFile())
cu.isCompiledForObjC = false;
jassert (cu.isCompiledForObjC || cu.isCompiledForNonObjC);
}
return units;
}
void LibraryModule::findAndAddCompiledUnits (ProjectExporter& exporter,
ProjectSaver* projectSaver,
Array<File>& result,
build_tools::ProjectType::Target::Type forTarget) const
{
for (auto& cu : getAllCompileUnits (forTarget))
{
if (cu.isNeededForExporter (exporter))
{
auto localFile = exporter.getProject().getGeneratedCodeFolder()
.getChildFile (cu.getFilenameForProxyFile());
result.add (localFile);
if (projectSaver != nullptr)
projectSaver->addFileToGeneratedGroup (localFile);
}
}
}
void LibraryModule::addBrowseableCode (ProjectExporter& exporter, const Array<File>& compiled, const File& localModuleFolder) const
{
if (sourceFiles.isEmpty())
findBrowseableFiles (localModuleFolder, sourceFiles);
auto sourceGroup = Project::Item::createGroup (exporter.getProject(), getID(), "__mainsourcegroup" + getID(), false);
auto moduleFromProject = exporter.getModuleFolderRelativeToProject (getID());
auto moduleHeader = moduleInfo.getHeader();
auto& project = exporter.getProject();
if (project.getEnabledModules().shouldCopyModuleFilesLocally (getID()))
moduleHeader = project.getLocalModuleFolder (getID()).getChildFile (moduleHeader.getFileName());
auto isModuleHeader = [&] (const File& f) { return f.getFileName() == moduleHeader.getFileName(); };
for (auto& sourceFile : sourceFiles)
{
auto pathWithinModule = build_tools::getRelativePathFrom (sourceFile, localModuleFolder);
// (Note: in exporters like MSVC we have to avoid adding the same file twice, even if one of those instances
// is flagged as being excluded from the build, because this overrides the other and it fails to compile)
if ((exporter.canCopeWithDuplicateFiles() || ! compiled.contains (sourceFile)) && ! isModuleHeader (sourceFile))
addFileWithGroups (sourceGroup, moduleFromProject.getChildFile (pathWithinModule), pathWithinModule);
}
sourceGroup.sortAlphabetically (true, true);
sourceGroup.addFileAtIndex (moduleHeader, -1, false);
exporter.getModulesGroup().state.appendChild (sourceGroup.state.createCopy(), nullptr);
}
//==============================================================================
EnabledModulesList::EnabledModulesList (Project& p, const ValueTree& s)
: project (p), state (s)
{
}
StringArray EnabledModulesList::getAllModules() const
{
StringArray moduleIDs;
for (int i = 0; i < getNumModules(); ++i)
moduleIDs.add (getModuleID (i));
return moduleIDs;
}
void EnabledModulesList::createRequiredModules (OwnedArray<LibraryModule>& modules)
{
for (int i = 0; i < getNumModules(); ++i)
modules.add (new LibraryModule (getModuleInfo (getModuleID (i))));
}
void EnabledModulesList::sortAlphabetically()
{
struct ModuleTreeSorter
{
static int compareElements (const ValueTree& m1, const ValueTree& m2)
{
return m1[Ids::ID].toString().compareIgnoreCase (m2[Ids::ID]);
}
};
ModuleTreeSorter sorter;
const ScopedLock sl (stateLock);
state.sort (sorter, getUndoManager(), false);
}
File EnabledModulesList::getDefaultModulesFolder() const
{
File globalPath (getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString());
if (globalPath.exists())
return globalPath;
for (auto& exporterPathModule : project.getExporterPathsModulesList().getAllModules())
{
auto f = exporterPathModule.second;
if (f.isDirectory())
return f.getParentDirectory();
}
return File::getCurrentWorkingDirectory();
}
ModuleDescription EnabledModulesList::getModuleInfo (const String& moduleID) const
{
return ModuleDescription (project.getModuleWithID (moduleID).second);
}
bool EnabledModulesList::isModuleEnabled (const String& moduleID) const
{
const ScopedLock sl (stateLock);
return state.getChildWithProperty (Ids::ID, moduleID).isValid();
}
static void getDependencies (Project& project, const String& moduleID, StringArray& dependencies)
{
auto info = project.getEnabledModules().getModuleInfo (moduleID);
for (auto uid : info.getDependencies())
{
if (! dependencies.contains (uid, true))
{
dependencies.add (uid);
getDependencies (project, uid, dependencies);
}
}
}
StringArray EnabledModulesList::getExtraDependenciesNeeded (const String& moduleID) const
{
StringArray dependencies, extraDepsNeeded;
getDependencies (project, moduleID, dependencies);
for (auto dep : dependencies)
if (dep != moduleID && ! isModuleEnabled (dep))
extraDepsNeeded.add (dep);
return extraDepsNeeded;
}
bool EnabledModulesList::tryToFixMissingDependencies (const String& moduleID)
{
auto copyLocally = areMostModulesCopiedLocally();
auto useGlobalPath = areMostModulesUsingGlobalPath();
StringArray missing;
for (auto missingModule : getExtraDependenciesNeeded (moduleID))
{
auto mod = project.getModuleWithID (missingModule);
if (mod.second != File())
addModule (mod.second, copyLocally, useGlobalPath);
else
missing.add (missingModule);
}
return (missing.size() == 0);
}
bool EnabledModulesList::doesModuleHaveHigherCppStandardThanProject (const String& moduleID) const
{
auto projectCppStandard = project.getCppStandardString();
if (projectCppStandard == Project::getCppStandardVars().getLast().toString())
return false;
auto moduleCppStandard = getModuleInfo (moduleID).getMinimumCppStandard();
return (moduleCppStandard.getIntValue() > projectCppStandard.getIntValue());
}
bool EnabledModulesList::shouldUseGlobalPath (const String& moduleID) const
{
const ScopedLock sl (stateLock);
return (bool) shouldUseGlobalPathValue (moduleID).getValue();
}
Value EnabledModulesList::shouldUseGlobalPathValue (const String& moduleID) const
{
const ScopedLock sl (stateLock);
return state.getChildWithProperty (Ids::ID, moduleID)
.getPropertyAsValue (Ids::useGlobalPath, getUndoManager());
}
bool EnabledModulesList::shouldShowAllModuleFilesInProject (const String& moduleID) const
{
return (bool) shouldShowAllModuleFilesInProjectValue (moduleID).getValue();
}
Value EnabledModulesList::shouldShowAllModuleFilesInProjectValue (const String& moduleID) const
{
const ScopedLock sl (stateLock);
return state.getChildWithProperty (Ids::ID, moduleID)
.getPropertyAsValue (Ids::showAllCode, getUndoManager());
}
bool EnabledModulesList::shouldCopyModuleFilesLocally (const String& moduleID) const
{
return (bool) shouldCopyModuleFilesLocallyValue (moduleID).getValue();
}
Value EnabledModulesList::shouldCopyModuleFilesLocallyValue (const String& moduleID) const
{
const ScopedLock sl (stateLock);
return state.getChildWithProperty (Ids::ID, moduleID)
.getPropertyAsValue (Ids::useLocalCopy, getUndoManager());
}
bool EnabledModulesList::areMostModulesUsingGlobalPath() const
{
int numYes = 0, numNo = 0;
for (auto i = getNumModules(); --i >= 0;)
{
if (shouldUseGlobalPath (getModuleID (i)))
++numYes;
else
++numNo;
}
return numYes > numNo;
}
bool EnabledModulesList::areMostModulesCopiedLocally() const
{
int numYes = 0, numNo = 0;
for (auto i = getNumModules(); --i >= 0;)
{
if (shouldCopyModuleFilesLocally (getModuleID (i)))
++numYes;
else
++numNo;
}
return numYes > numNo;
}
StringArray EnabledModulesList::getModulesWithHigherCppStandardThanProject() const
{
StringArray list;
for (auto& module : getAllModules())
if (doesModuleHaveHigherCppStandardThanProject (module))
list.add (module);
return list;
}
StringArray EnabledModulesList::getModulesWithMissingDependencies() const
{
StringArray list;
for (auto& module : getAllModules())
if (getExtraDependenciesNeeded (module).size() > 0)
list.add (module);
return list;
}
String EnabledModulesList::getHighestModuleCppStandard() const
{
auto highestCppStandard = Project::getCppStandardVars()[0].toString();
for (auto& mod : getAllModules())
{
auto moduleCppStandard = getModuleInfo (mod).getMinimumCppStandard();
if (moduleCppStandard == "latest")
return moduleCppStandard;
if (moduleCppStandard.getIntValue() > highestCppStandard.getIntValue())
highestCppStandard = moduleCppStandard;
}
return highestCppStandard;
}
void EnabledModulesList::addModule (const File& moduleFolder, bool copyLocally, bool useGlobalPath)
{
ModuleDescription info (moduleFolder);
if (info.isValid())
{
auto moduleID = info.getID();
if (! isModuleEnabled (moduleID))
{
ValueTree module (Ids::MODULE);
module.setProperty (Ids::ID, moduleID, getUndoManager());
{
const ScopedLock sl (stateLock);
state.appendChild (module, getUndoManager());
}
sortAlphabetically();
shouldShowAllModuleFilesInProjectValue (moduleID) = true;
shouldCopyModuleFilesLocallyValue (moduleID) = copyLocally;
shouldUseGlobalPathValue (moduleID) = useGlobalPath;
build_tools::RelativePath path (moduleFolder.getParentDirectory(),
project.getProjectFolder(),
build_tools::RelativePath::projectFolder);
for (Project::ExporterIterator exporter (project); exporter.next();)
exporter->getPathForModuleValue (moduleID) = path.toUnixStyle();
if (! useGlobalPath)
project.rescanExporterPathModules (false);
}
}
}
void EnabledModulesList::addModuleInteractive (const String& moduleID)
{
auto f = project.getModuleWithID (moduleID).second;
if (f != File())
{
addModule (f, areMostModulesCopiedLocally(), areMostModulesUsingGlobalPath());
return;
}
addModuleFromUserSelectedFile();
}
void EnabledModulesList::addModuleFromUserSelectedFile()
{
chooser = std::make_unique<FileChooser> ("Select a module to add...", getDefaultModulesFolder(), "");
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
chooser->launchAsync (flags, [this] (const FileChooser& fc)
{
if (fc.getResult() == File{})
return;
addModuleOfferingToCopy (fc.getResult(), true);
});
}
void EnabledModulesList::addModuleOfferingToCopy (const File& f, bool isFromUserSpecifiedFolder)
{
ModuleDescription m (f);
if (! m.isValid())
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon,
"Add Module", "This wasn't a valid module folder!");
return;
}
if (isModuleEnabled (m.getID()))
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon,
"Add Module", "The project already contains this module!");
return;
}
addModule (m.getModuleFolder(), areMostModulesCopiedLocally(),
isFromUserSpecifiedFolder ? false : areMostModulesUsingGlobalPath());
}
void EnabledModulesList::removeModule (String moduleID) // must be pass-by-value, and not a const ref!
{
{
const ScopedLock sl (stateLock);
for (auto i = state.getNumChildren(); --i >= 0;)
if (state.getChild(i) [Ids::ID] == moduleID)
state.removeChild (i, getUndoManager());
}
for (Project::ExporterIterator exporter (project); exporter.next();)
exporter->removePathForModule (moduleID);
}

View File

@ -0,0 +1,148 @@
/*
==============================================================================
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_Project.h"
class ProjectExporter;
class ProjectSaver;
//==============================================================================
class LibraryModule
{
public:
LibraryModule (const ModuleDescription&);
bool isValid() const { return moduleInfo.isValid(); }
String getID() const { return moduleInfo.getID(); }
String getVendor() const { return moduleInfo.getVendor(); }
String getVersion() const { return moduleInfo.getVersion(); }
String getName() const { return moduleInfo.getName(); }
String getDescription() const { return moduleInfo.getDescription(); }
String getLicense() const { return moduleInfo.getLicense(); }
String getMinimumCppStandard() const { return moduleInfo.getMinimumCppStandard(); }
File getFolder() const { return moduleInfo.getFolder(); }
void writeIncludes (ProjectSaver&, OutputStream&);
void addSettingsForModuleToExporter (ProjectExporter&, ProjectSaver&) const;
void getConfigFlags (Project&, OwnedArray<Project::ConfigFlag>& flags) const;
void findBrowseableFiles (const File& localModuleFolder, Array<File>& files) const;
struct CompileUnit
{
File file;
bool isCompiledForObjC = false, isCompiledForNonObjC = false;
bool isNeededForExporter (ProjectExporter&) const;
String getFilenameForProxyFile() const;
static bool hasSuffix (const File&, const char*);
};
Array<CompileUnit> getAllCompileUnits (build_tools::ProjectType::Target::Type forTarget =
build_tools::ProjectType::Target::unspecified) const;
void findAndAddCompiledUnits (ProjectExporter&, ProjectSaver*, Array<File>& result,
build_tools::ProjectType::Target::Type forTarget =
build_tools::ProjectType::Target::unspecified) const;
ModuleDescription moduleInfo;
private:
void addSearchPathsToExporter (ProjectExporter&) const;
void addDefinesToExporter (ProjectExporter&) const;
void addCompileUnitsToExporter (ProjectExporter&, ProjectSaver&) const;
void addLibsToExporter (ProjectExporter&) const;
void addBrowseableCode (ProjectExporter&, const Array<File>& compiled, const File& localModuleFolder) const;
mutable Array<File> sourceFiles;
OwnedArray<Project::ConfigFlag> configFlags;
};
//==============================================================================
class EnabledModulesList
{
public:
EnabledModulesList (Project&, const ValueTree&);
//==============================================================================
ValueTree getState() const { return state; }
StringArray getAllModules() const;
void createRequiredModules (OwnedArray<LibraryModule>& modules);
void sortAlphabetically();
File getDefaultModulesFolder() const;
int getNumModules() const { return state.getNumChildren(); }
String getModuleID (int index) const { return state.getChild (index) [Ids::ID].toString(); }
ModuleDescription getModuleInfo (const String& moduleID) const;
bool isModuleEnabled (const String& moduleID) const;
StringArray getExtraDependenciesNeeded (const String& moduleID) const;
bool tryToFixMissingDependencies (const String& moduleID);
bool doesModuleHaveHigherCppStandardThanProject (const String& moduleID) const;
bool shouldUseGlobalPath (const String& moduleID) const;
Value shouldUseGlobalPathValue (const String& moduleID) const;
bool shouldShowAllModuleFilesInProject (const String& moduleID) const;
Value shouldShowAllModuleFilesInProjectValue (const String& moduleID) const;
bool shouldCopyModuleFilesLocally (const String& moduleID) const;
Value shouldCopyModuleFilesLocallyValue (const String& moduleID) const;
bool areMostModulesUsingGlobalPath() const;
bool areMostModulesCopiedLocally() const;
StringArray getModulesWithHigherCppStandardThanProject() const;
StringArray getModulesWithMissingDependencies() const;
String getHighestModuleCppStandard() const;
//==============================================================================
void addModule (const File& moduleManifestFile, bool copyLocally, bool useGlobalPath);
void addModuleInteractive (const String& moduleID);
void addModuleFromUserSelectedFile();
void addModuleOfferingToCopy (const File&, bool isFromUserSpecifiedFolder);
void removeModule (String moduleID);
private:
UndoManager* getUndoManager() const { return project.getUndoManagerFor (state); }
Project& project;
CriticalSection stateLock;
ValueTree state;
std::unique_ptr<FileChooser> chooser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesList)
};

View File

@ -0,0 +1,401 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ExporterItem : public ProjectTreeItemBase,
private Value::Listener
{
public:
ExporterItem (Project& p, ProjectExporter* e, int index)
: project (p), exporter (e), configListTree (exporter->getConfigurations()),
exporterIndex (index)
{
exporter->initialiseDependencyPathValues();
configListTree.addListener (this);
targetLocationValue.referTo (exporter->getTargetLocationValue());
targetLocationValue.addListener (this);
}
int getItemHeight() const override { return 25; }
bool canBeSelected() const override { return true; }
bool mightContainSubItems() override { return exporter->getNumConfigurations() > 0; }
String getUniqueName() const override { return "exporter_" + String (exporterIndex); }
String getRenamingName() const override { return getDisplayName(); }
String getDisplayName() const override { return exporter->getUniqueName(); }
void setName (const String&) override {}
bool isMissing() const override { return false; }
String getTooltip() override { return getDisplayName(); }
static Icon getIconForExporter (ProjectExporter* e)
{
if (e != nullptr)
{
if (e->isXcode()) return Icon (getIcons().xcode, Colours::transparentBlack);
else if (e->isVisualStudio()) return Icon (getIcons().visualStudio, Colours::transparentBlack);
else if (e->isAndroid()) return Icon (getIcons().android, Colours::transparentBlack);
else if (e->isCodeBlocks()) return Icon (getIcons().codeBlocks, Colours::transparentBlack);
else if (e->isMakefile()) return Icon (getIcons().linux, Colours::transparentBlack);
else if (e->isCLion()) return Icon (getIcons().clion, Colours::transparentBlack);
}
return Icon();
}
Icon getIcon() const override
{
return getIconForExporter (exporter.get()).withColour (getContentColour (true));
}
void showDocument() override
{
showSettingsPage (new SettingsComp (*exporter));
}
void deleteItem() override
{
auto resultCallback = [safeThis = WeakReference<ExporterItem> { this }] (int result)
{
if (safeThis == nullptr || result == 0)
return;
safeThis->closeSettingsPage();
auto parent = safeThis->exporter->settings.getParent();
parent.removeChild (safeThis->exporter->settings,
safeThis->project.getUndoManagerFor (parent));
};
AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon,
"Delete Exporter",
"Are you sure you want to delete this export target?",
"",
"",
nullptr,
ModalCallbackFunction::create (std::move (resultCallback)));
}
void addSubItems() override
{
for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
addSubItem (new ConfigItem (config.config, *exporter));
}
void showPopupMenu (Point<int> p) override
{
PopupMenu menu;
menu.addItem (1, "Add a new configuration", exporter->supportsUserDefinedConfigurations());
menu.addItem (2, "Save this exporter");
menu.addSeparator();
menu.addItem (3, "Delete this exporter");
launchPopupMenu (menu, p);
}
void showAddMenu (Point<int> p) override
{
PopupMenu menu;
menu.addItem (1, "Add a new configuration", exporter->supportsUserDefinedConfigurations());
launchPopupMenu (menu, p);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode == 1)
exporter->addNewConfiguration (false);
else if (resultCode == 2)
project.saveProject (Async::yes, exporter.get(), nullptr);
else if (resultCode == 3)
deleteAllSelectedItems();
}
var getDragSourceDescription() override
{
return getParentItem()->getUniqueName() + "/" + String (exporterIndex);
}
bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
{
return dragSourceDetails.description.toString().startsWith (getUniqueName());
}
void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override
{
auto oldIndex = indexOfConfig (dragSourceDetails.description.toString().fromLastOccurrenceOf ("||", false, false));
if (oldIndex >= 0)
configListTree.moveChild (oldIndex, insertIndex, project.getUndoManagerFor (configListTree));
}
int indexOfConfig (const String& configName)
{
int i = 0;
for (ProjectExporter::ConfigIterator config (*exporter); config.next(); ++i)
if (config->getName() == configName)
return i;
return -1;
}
//==============================================================================
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { refreshIfNeeded (parentTree); }
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { refreshIfNeeded (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { refreshIfNeeded (parentTree); }
void refreshIfNeeded (ValueTree& changedTree)
{
if (changedTree == configListTree)
refreshSubItems();
}
private:
Project& project;
std::unique_ptr<ProjectExporter> exporter;
ValueTree configListTree;
int exporterIndex;
Value targetLocationValue;
void valueChanged (Value& value) override
{
if (value == exporter->getTargetLocationValue())
refreshSubItems();
}
//==============================================================================
struct SettingsComp : public Component
{
SettingsComp (ProjectExporter& exp)
: group (exp.getUniqueName(),
ExporterItem::getIconForExporter (&exp),
exp.getDescription())
{
addAndMakeVisible (group);
PropertyListBuilder props;
exp.createPropertyEditors (props);
group.setProperties (props);
parentSizeChanged();
}
void parentSizeChanged() override { updateSize (*this, group); }
void resized() override { group.setBounds (getLocalBounds().withTrimmedLeft (12)); }
PropertyGroupComponent group;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SettingsComp)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterItem)
JUCE_DECLARE_WEAK_REFERENCEABLE (ExporterItem)
};
//==============================================================================
class ConfigItem : public ProjectTreeItemBase
{
public:
ConfigItem (const ProjectExporter::BuildConfiguration::Ptr& conf, ProjectExporter& e)
: config (conf), exporter (e), configTree (config->config)
{
jassert (config != nullptr);
configTree.addListener (this);
}
bool isMissing() const override { return false; }
bool canBeSelected() const override { return true; }
bool mightContainSubItems() override { return false; }
String getUniqueName() const override { return "config_" + config->getName(); }
String getRenamingName() const override { return getDisplayName(); }
String getDisplayName() const override { return config->getName(); }
void setName (const String&) override {}
Icon getIcon() const override { return Icon (getIcons().config, getContentColour (true)); }
void itemOpennessChanged (bool) override {}
void showDocument() override
{
showSettingsPage (new SettingsComp (*config));
}
void deleteItem() override
{
AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon,
"Delete Configuration",
"Are you sure you want to delete this configuration?",
"",
"",
nullptr,
ModalCallbackFunction::create ([parent = WeakReference<ConfigItem> { this }] (int result)
{
if (parent == nullptr)
return;
if (result == 0)
return;
parent->closeSettingsPage();
parent->config->removeFromExporter();
}));
}
void showPopupMenu (Point<int> p) override
{
bool enabled = exporter.supportsUserDefinedConfigurations();
PopupMenu menu;
menu.addItem (1, "Create a copy of this configuration", enabled);
menu.addSeparator();
menu.addItem (2, "Delete this configuration", enabled);
launchPopupMenu (menu, p);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode == 1)
exporter.addNewConfigurationFromExisting (*config);
else if (resultCode == 2)
deleteAllSelectedItems();
}
var getDragSourceDescription() override
{
return getParentItem()->getUniqueName() + "||" + config->getName();
}
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { repaintItem(); }
private:
ProjectExporter::BuildConfiguration::Ptr config;
ProjectExporter& exporter;
ValueTree configTree;
//==============================================================================
class SettingsComp : public Component
{
public:
SettingsComp (ProjectExporter::BuildConfiguration& conf)
: group (conf.exporter.getUniqueName() + " - " + conf.getName(), Icon (getIcons().config, Colours::transparentBlack))
{
addAndMakeVisible (group);
PropertyListBuilder props;
conf.createPropertyEditors (props);
group.setProperties (props);
parentSizeChanged();
}
void parentSizeChanged() override { updateSize (*this, group); }
void resized() override { group.setBounds (getLocalBounds().withTrimmedLeft (12)); }
private:
PropertyGroupComponent group;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SettingsComp)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConfigItem)
JUCE_DECLARE_WEAK_REFERENCEABLE (ConfigItem)
};
//==============================================================================
class ExportersTreeRoot : public ProjectTreeItemBase
{
public:
ExportersTreeRoot (Project& p)
: project (p),
exportersTree (project.getExporters())
{
exportersTree.addListener (this);
}
bool isRoot() const override { return true; }
bool canBeSelected() const override { return true; }
bool isMissing() const override { return false; }
bool mightContainSubItems() override { return project.getNumExporters() > 0; }
String getUniqueName() const override { return "exporters"; }
String getRenamingName() const override { return getDisplayName(); }
String getDisplayName() const override { return "Exporters"; }
void setName (const String&) override {}
Icon getIcon() const override { return project.getMainGroup().getIcon (isOpen()).withColour (getContentColour (true)); }
void showPopupMenu (Point<int>) override
{
if (auto* pcc = getProjectContentComponent())
pcc->showNewExporterMenu();
}
void addSubItems() override
{
int i = 0;
for (Project::ExporterIterator exporter (project); exporter.next(); ++i)
addSubItem (new TreeItemTypes::ExporterItem (project, exporter.exporter.release(), i));
}
bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
{
return dragSourceDetails.description.toString().startsWith (getUniqueName());
}
void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override
{
int oldIndex = dragSourceDetails.description.toString().getTrailingIntValue();
exportersTree.moveChild (oldIndex, jmax (0, insertIndex), project.getUndoManagerFor (exportersTree));
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen)
refreshSubItems();
}
void removeExporter (int index)
{
if (auto* exporter = dynamic_cast<TreeItemTypes::ExporterItem*> (getSubItem (index)))
exporter->deleteItem();
}
private:
Project& project;
ValueTree exportersTree;
//==============================================================================
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { refreshIfNeeded (parentTree); }
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { refreshIfNeeded (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { refreshIfNeeded (parentTree); }
void refreshIfNeeded (ValueTree& changedTree)
{
if (changedTree == exportersTree)
refreshSubItems();
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExportersTreeRoot)
};

View File

@ -0,0 +1,882 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class FileTreeItemBase : public JucerTreeViewBase,
private ValueTree::Listener
{
public:
FileTreeItemBase (const Project::Item& projectItem)
: item (projectItem), isFileMissing (false)
{
item.state.addListener (this);
}
~FileTreeItemBase() override
{
item.state.removeListener (this);
}
//==============================================================================
virtual bool acceptsFileDrop (const StringArray& files) const = 0;
virtual bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) = 0;
//==============================================================================
String getDisplayName() const override { return item.getName(); }
String getRenamingName() const override { return getDisplayName(); }
void setName (const String& newName) override { item.getNameValue() = newName; }
bool isMissing() const override { return isFileMissing; }
virtual File getFile() const { return item.getFile(); }
void deleteItem() override { item.removeItemFromProject(); }
void deleteAllSelectedItems() override
{
auto* tree = getOwnerView();
Array<File> filesToTrash;
Array<Project::Item> itemsToRemove;
for (int i = 0; i < tree->getNumSelectedItems(); ++i)
{
if (auto* p = dynamic_cast<FileTreeItemBase*> (tree->getSelectedItem (i)))
{
itemsToRemove.add (p->item);
if (p->item.isGroup())
{
for (int j = 0; j < p->item.getNumChildren(); ++j)
{
auto associatedFile = p->item.getChild (j).getFile();
if (associatedFile.existsAsFile())
filesToTrash.addIfNotAlreadyThere (associatedFile);
}
}
else if (p->getFile().existsAsFile())
{
filesToTrash.addIfNotAlreadyThere (p->getFile());
}
}
}
WeakReference<FileTreeItemBase> treeRootItem { dynamic_cast<FileTreeItemBase*> (tree->getRootItem()) };
if (treeRootItem == nullptr)
{
jassertfalse;
return;
}
auto doDelete = [treeRootItem, itemsToRemove] (const Array<File>& fsToTrash)
{
if (treeRootItem == nullptr)
return;
auto& om = ProjucerApplication::getApp().openDocumentManager;
for (auto i = fsToTrash.size(); --i >= 0;)
{
auto f = fsToTrash.getUnchecked(i);
om.closeFileWithoutSaving (f);
if (! f.moveToTrash())
{
// xxx
}
}
for (auto i = itemsToRemove.size(); --i >= 0;)
{
if (auto itemToRemove = treeRootItem->findTreeViewItem (itemsToRemove.getUnchecked (i)))
{
if (auto* pcc = treeRootItem->getProjectContentComponent())
{
if (auto* fileInfoComp = dynamic_cast<FileGroupInformationComponent*> (pcc->getEditorComponent()))
if (fileInfoComp->getGroupPath() == itemToRemove->getFile().getFullPathName())
pcc->hideEditor();
}
om.closeFileWithoutSaving (itemToRemove->getFile());
itemToRemove->deleteItem();
}
}
};
if (! filesToTrash.isEmpty())
{
String fileList;
auto maxFilesToList = 10;
for (auto i = jmin (maxFilesToList, filesToTrash.size()); --i >= 0;)
fileList << filesToTrash.getUnchecked(i).getFullPathName() << "\n";
if (filesToTrash.size() > maxFilesToList)
fileList << "\n...plus " << (filesToTrash.size() - maxFilesToList) << " more files...";
AlertWindow::showYesNoCancelBox (MessageBoxIconType::NoIcon,
"Delete Project Items",
"As well as removing the selected item(s) from the project, do you also want to move their files to the trash:\n\n"
+ fileList,
"Just remove references",
"Also move files to Trash",
"Cancel",
tree->getTopLevelComponent(),
ModalCallbackFunction::create ([treeRootItem, filesToTrash, doDelete] (int r) mutable
{
if (treeRootItem == nullptr)
return;
if (r == 0)
return;
if (r != 2)
filesToTrash.clear();
doDelete (filesToTrash);
}));
return;
}
doDelete (filesToTrash);
}
virtual void revealInFinder() const
{
getFile().revealToUser();
}
virtual void browseToAddExistingFiles()
{
auto location = item.isGroup() ? item.determineGroupFolder() : getFile();
chooser = std::make_unique<FileChooser> ("Add Files to Jucer Project", location, "");
auto flags = FileBrowserComponent::openMode
| FileBrowserComponent::canSelectFiles
| FileBrowserComponent::canSelectDirectories
| FileBrowserComponent::canSelectMultipleItems;
chooser->launchAsync (flags, [this] (const FileChooser& fc)
{
if (fc.getResults().isEmpty())
return;
StringArray files;
for (int i = 0; i < fc.getResults().size(); ++i)
files.add (fc.getResults().getReference(i).getFullPathName());
addFilesRetainingSortOrder (files);
});
}
virtual void checkFileStatus() // (recursive)
{
auto file = getFile();
auto nowMissing = (file != File() && ! file.exists());
if (nowMissing != isFileMissing)
{
isFileMissing = nowMissing;
repaintItem();
}
}
virtual void addFilesAtIndex (const StringArray& files, int insertIndex)
{
if (auto* p = getParentProjectItem())
p->addFilesAtIndex (files, insertIndex);
}
virtual void addFilesRetainingSortOrder (const StringArray& files)
{
if (auto* p = getParentProjectItem())
p->addFilesRetainingSortOrder (files);
}
virtual void moveSelectedItemsTo (OwnedArray<Project::Item>&, int /*insertIndex*/)
{
jassertfalse;
}
void showMultiSelectionPopupMenu (Point<int> p) override
{
PopupMenu m;
m.addItem (1, "Delete");
m.showMenuAsync (PopupMenu::Options().withTargetScreenArea ({ p.x, p.y, 1, 1 }),
ModalCallbackFunction::create (treeViewMultiSelectItemChosen, this));
}
static void treeViewMultiSelectItemChosen (int resultCode, FileTreeItemBase* item)
{
switch (resultCode)
{
case 1: item->deleteAllSelectedItems(); break;
default: break;
}
}
virtual FileTreeItemBase* findTreeViewItem (const Project::Item& itemToFind)
{
if (item == itemToFind)
return this;
auto wasOpen = isOpen();
setOpen (true);
for (auto i = getNumSubItems(); --i >= 0;)
{
if (auto* pg = dynamic_cast<FileTreeItemBase*> (getSubItem(i)))
if (auto* found = pg->findTreeViewItem (itemToFind))
return found;
}
setOpen (wasOpen);
return nullptr;
}
//==============================================================================
bool mightContainSubItems() override { return item.getNumChildren() > 0; }
String getUniqueName() const override { jassert (item.getID().isNotEmpty()); return item.getID(); }
bool canBeSelected() const override { return true; }
String getTooltip() override { return {}; }
File getDraggableFile() const override { return getFile(); }
var getDragSourceDescription() override
{
cancelDelayedSelectionTimer();
return projectItemDragType;
}
void addSubItems() override
{
for (int i = 0; i < item.getNumChildren(); ++i)
if (auto* p = createSubItem (item.getChild(i)))
addSubItem (p);
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen)
refreshSubItems();
}
//==============================================================================
bool isInterestedInFileDrag (const StringArray& files) override
{
return acceptsFileDrop (files);
}
void filesDropped (const StringArray& files, int insertIndex) override
{
if (files.size() == 1 && File (files[0]).hasFileExtension (Project::projectFileExtension))
ProjucerApplication::getApp().openFile (files[0], [] (bool) {});
else
addFilesAtIndex (files, insertIndex);
}
bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
{
OwnedArray<Project::Item> selectedNodes;
getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
return selectedNodes.size() > 0 && acceptsDragItems (selectedNodes);
}
void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override
{
OwnedArray<Project::Item> selectedNodes;
getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
if (selectedNodes.size() > 0)
{
auto* tree = getOwnerView();
std::unique_ptr<XmlElement> oldOpenness (tree->getOpennessState (false));
moveSelectedItemsTo (selectedNodes, insertIndex);
if (oldOpenness != nullptr)
tree->restoreOpennessState (*oldOpenness, false);
}
}
int getMillisecsAllowedForDragGesture() override
{
// for images, give the user longer to start dragging before assuming they're
// clicking to select it for previewing..
return item.isImageFile() ? 250 : JucerTreeViewBase::getMillisecsAllowedForDragGesture();
}
static void getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
OwnedArray<Project::Item>& selectedNodes)
{
if (dragSourceDetails.description == projectItemDragType)
{
auto* tree = dynamic_cast<TreeView*> (dragSourceDetails.sourceComponent.get());
if (tree == nullptr)
tree = dragSourceDetails.sourceComponent->findParentComponentOfClass<TreeView>();
if (tree != nullptr)
{
auto numSelected = tree->getNumSelectedItems();
for (int i = 0; i < numSelected; ++i)
if (auto* p = dynamic_cast<FileTreeItemBase*> (tree->getSelectedItem (i)))
selectedNodes.add (new Project::Item (p->item));
}
}
}
FileTreeItemBase* getParentProjectItem() const
{
return dynamic_cast<FileTreeItemBase*> (getParentItem());
}
//==============================================================================
Project::Item item;
protected:
//==============================================================================
void valueTreePropertyChanged (ValueTree& tree, const Identifier&) override
{
if (tree == item.state)
repaintItem();
}
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { treeChildrenChanged (parentTree); }
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { treeChildrenChanged (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { treeChildrenChanged (parentTree); }
bool isFileMissing;
virtual FileTreeItemBase* createSubItem (const Project::Item& node) = 0;
Icon getIcon() const override
{
auto colour = getOwnerView()->findColour (isSelected() ? defaultHighlightedTextColourId
: treeIconColourId);
return item.getIcon (isOpen()).withColour (colour);
}
bool isIconCrossedOut() const override { return item.isIconCrossedOut(); }
void treeChildrenChanged (const ValueTree& parentTree)
{
if (parentTree == item.state)
{
refreshSubItems();
treeHasChanged();
setOpen (true);
}
}
void triggerAsyncRename (const Project::Item& itemToRename)
{
struct RenameMessage : public CallbackMessage
{
RenameMessage (TreeView* const t, const Project::Item& i)
: tree (t), itemToRename (i) {}
void messageCallback() override
{
if (tree != nullptr)
if (auto* root = dynamic_cast<FileTreeItemBase*> (tree->getRootItem()))
if (auto* found = root->findTreeViewItem (itemToRename))
found->showRenameBox();
}
private:
Component::SafePointer<TreeView> tree;
Project::Item itemToRename;
};
(new RenameMessage (getOwnerView(), itemToRename))->post();
}
static void moveItems (OwnedArray<Project::Item>& selectedNodes, Project::Item destNode, int insertIndex)
{
for (auto i = selectedNodes.size(); --i >= 0;)
{
auto* n = selectedNodes.getUnchecked(i);
if (destNode == *n || destNode.state.isAChildOf (n->state)) // Check for recursion.
return;
if (! destNode.canContain (*n))
selectedNodes.remove (i);
}
// Don't include any nodes that are children of other selected nodes..
for (auto i = selectedNodes.size(); --i >= 0;)
{
auto* n = selectedNodes.getUnchecked(i);
for (auto j = selectedNodes.size(); --j >= 0;)
{
if (j != i && n->state.isAChildOf (selectedNodes.getUnchecked(j)->state))
{
selectedNodes.remove (i);
break;
}
}
}
// Remove and re-insert them one at a time..
for (int i = 0; i < selectedNodes.size(); ++i)
{
auto* selectedNode = selectedNodes.getUnchecked(i);
if (selectedNode->state.getParent() == destNode.state
&& indexOfNode (destNode.state, selectedNode->state) < insertIndex)
--insertIndex;
selectedNode->removeItemFromProject();
destNode.addChild (*selectedNode, insertIndex++);
}
}
static int indexOfNode (const ValueTree& parent, const ValueTree& child)
{
for (auto i = parent.getNumChildren(); --i >= 0;)
if (parent.getChild (i) == child)
return i;
return -1;
}
private:
std::unique_ptr<FileChooser> chooser;
JUCE_DECLARE_WEAK_REFERENCEABLE (FileTreeItemBase)
};
//==============================================================================
class SourceFileItem : public FileTreeItemBase
{
public:
SourceFileItem (const Project::Item& projectItem)
: FileTreeItemBase (projectItem)
{
}
bool acceptsFileDrop (const StringArray&) const override { return false; }
bool acceptsDragItems (const OwnedArray<Project::Item>&) override { return false; }
String getDisplayName() const override
{
return getFile().getFileName();
}
void paintItem (Graphics& g, int width, int height) override
{
JucerTreeViewBase::paintItem (g, width, height);
if (item.needsSaving())
{
auto bounds = g.getClipBounds().withY (0).withHeight (height);
g.setFont (getFont());
g.setColour (getContentColour (false));
g.drawFittedText ("*", bounds.removeFromLeft (height), Justification::centred, 1);
}
}
static File findCorrespondingHeaderOrCpp (const File& f)
{
if (f.hasFileExtension (sourceFileExtensions)) return f.withFileExtension (".h");
if (f.hasFileExtension (headerFileExtensions)) return f.withFileExtension (".cpp");
return {};
}
void setName (const String& newName) override
{
if (newName != File::createLegalFileName (newName))
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"File Rename",
"That filename contained some illegal characters!");
triggerAsyncRename (item);
return;
}
auto oldFile = getFile();
auto newFile = oldFile.getSiblingFile (newName);
auto correspondingFile = findCorrespondingHeaderOrCpp (oldFile);
if (correspondingFile.exists() && newFile.hasFileExtension (oldFile.getFileExtension()))
{
auto correspondingItem = item.project.getMainGroup().findItemForFile (correspondingFile);
if (correspondingItem.isValid())
{
AlertWindow::showOkCancelBox (MessageBoxIconType::NoIcon,
"File Rename",
"Do you also want to rename the corresponding file \"" + correspondingFile.getFileName() + "\" to match?",
{},
{},
nullptr,
ModalCallbackFunction::create ([parent = WeakReference<SourceFileItem> { this },
oldFile, newFile, correspondingFile, correspondingItem] (int result) mutable
{
if (parent == nullptr || result == 0)
return;
if (! parent->item.renameFile (newFile))
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"File Rename",
"Failed to rename \"" + oldFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
return;
}
if (! correspondingItem.renameFile (newFile.withFileExtension (correspondingFile.getFileExtension())))
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"File Rename",
"Failed to rename \"" + correspondingFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
}
}));
}
}
if (! item.renameFile (newFile))
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"File Rename",
"Failed to rename the file!\n\nCheck your file permissions!");
}
}
FileTreeItemBase* createSubItem (const Project::Item&) override
{
jassertfalse;
return nullptr;
}
void showDocument() override
{
auto f = getFile();
if (f.exists())
if (auto* pcc = getProjectContentComponent())
pcc->showEditorForFile (f, false);
}
void showPopupMenu (Point<int> p) override
{
PopupMenu m;
m.addItem (1, "Open in external editor");
m.addItem (2,
#if JUCE_MAC
"Reveal in Finder");
#else
"Reveal in Explorer");
#endif
m.addItem (4, "Rename File...");
m.addSeparator();
m.addItem (5, "Binary Resource", true, item.shouldBeAddedToBinaryResources());
m.addItem (6, "Xcode Resource", true, item.shouldBeAddedToXcodeResources());
m.addItem (7, "Compile", item.isSourceFile(), item.shouldBeCompiled());
m.addItem (8, "Skip PCH", item.shouldBeCompiled(), item.shouldSkipPCH());
m.addSeparator();
m.addItem (3, "Delete");
launchPopupMenu (m, p);
}
void showAddMenu (Point<int> p) override
{
if (auto* group = dynamic_cast<GroupItem*> (getParentItem()))
group->showAddMenu (p);
}
void handlePopupMenuResult (int resultCode) override
{
switch (resultCode)
{
case 1: getFile().startAsProcess(); break;
case 2: revealInFinder(); break;
case 3: deleteAllSelectedItems(); break;
case 4: triggerAsyncRename (item); break;
case 5: item.getShouldAddToBinaryResourcesValue().setValue (! item.shouldBeAddedToBinaryResources()); break;
case 6: item.getShouldAddToXcodeResourcesValue().setValue (! item.shouldBeAddedToXcodeResources()); break;
case 7: item.getShouldCompileValue().setValue (! item.shouldBeCompiled()); break;
case 8: item.getShouldSkipPCHValue().setValue (! item.shouldSkipPCH()); break;
default:
if (auto* parentGroup = dynamic_cast<GroupItem*> (getParentProjectItem()))
parentGroup->processCreateFileMenuItem (resultCode);
break;
}
}
JUCE_DECLARE_WEAK_REFERENCEABLE (SourceFileItem)
};
//==============================================================================
class GroupItem : public FileTreeItemBase
{
public:
GroupItem (const Project::Item& projectItem, const String& filter = {})
: FileTreeItemBase (projectItem),
searchFilter (filter)
{
}
bool isRoot() const override { return item.isMainGroup(); }
bool acceptsFileDrop (const StringArray&) const override { return true; }
void addNewGroup()
{
auto newGroup = item.addNewSubGroup ("New Group", 0);
triggerAsyncRename (newGroup);
}
bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) override
{
for (auto i = selectedNodes.size(); --i >= 0;)
if (item.canContain (*selectedNodes.getUnchecked(i)))
return true;
return false;
}
void addFilesAtIndex (const StringArray& files, int insertIndex) override
{
for (auto f : files)
{
if (item.addFileAtIndex (f, insertIndex, true))
++insertIndex;
}
}
void addFilesRetainingSortOrder (const StringArray& files) override
{
for (auto i = files.size(); --i >= 0;)
item.addFileRetainingSortOrder (files[i], true);
}
void moveSelectedItemsTo (OwnedArray<Project::Item>& selectedNodes, int insertIndex) override
{
moveItems (selectedNodes, item, insertIndex);
}
void checkFileStatus() override
{
for (int i = 0; i < getNumSubItems(); ++i)
if (auto* p = dynamic_cast<FileTreeItemBase*> (getSubItem(i)))
p->checkFileStatus();
}
bool isGroupEmpty (const Project::Item& group) // recursive
{
for (int i = 0; i < group.getNumChildren(); ++i)
{
auto child = group.getChild (i);
if ((child.isGroup() && ! isGroupEmpty (child))
|| (child.isFile() && child.getName().containsIgnoreCase (searchFilter)))
return false;
}
return true;
}
FileTreeItemBase* createSubItem (const Project::Item& child) override
{
if (child.isGroup())
{
if (searchFilter.isNotEmpty() && isGroupEmpty (child))
return nullptr;
return new GroupItem (child, searchFilter);
}
if (child.isFile())
{
if (child.getName().containsIgnoreCase (searchFilter))
return new SourceFileItem (child);
return nullptr;
}
jassertfalse;
return nullptr;
}
void showDocument() override
{
if (auto* pcc = getProjectContentComponent())
pcc->setScrollableEditorComponent (std::make_unique<FileGroupInformationComponent> (item));
}
static void openAllGroups (TreeViewItem* root)
{
for (int i = 0; i < root->getNumSubItems(); ++i)
if (auto* sub = root->getSubItem (i))
openOrCloseAllSubGroups (*sub, true);
}
static void closeAllGroups (TreeViewItem* root)
{
for (int i = 0; i < root->getNumSubItems(); ++i)
if (auto* sub = root->getSubItem (i))
openOrCloseAllSubGroups (*sub, false);
}
static void openOrCloseAllSubGroups (TreeViewItem& treeItem, bool shouldOpen)
{
treeItem.setOpen (shouldOpen);
for (auto i = treeItem.getNumSubItems(); --i >= 0;)
if (auto* sub = treeItem.getSubItem (i))
openOrCloseAllSubGroups (*sub, shouldOpen);
}
static void setFilesToCompile (Project::Item projectItem, const bool shouldCompile)
{
if (projectItem.isFile() && (projectItem.getFile().hasFileExtension (fileTypesToCompileByDefault)))
projectItem.getShouldCompileValue() = shouldCompile;
for (auto i = projectItem.getNumChildren(); --i >= 0;)
setFilesToCompile (projectItem.getChild (i), shouldCompile);
}
void showPopupMenu (Point<int> p) override
{
PopupMenu m;
addCreateFileMenuItems (m);
m.addSeparator();
m.addItem (1, "Collapse all Groups");
m.addItem (2, "Expand all Groups");
if (! isRoot())
{
if (isOpen())
m.addItem (3, "Collapse all Sub-groups");
else
m.addItem (4, "Expand all Sub-groups");
}
m.addSeparator();
m.addItem (5, "Enable compiling of all enclosed files");
m.addItem (6, "Disable compiling of all enclosed files");
m.addSeparator();
m.addItem (7, "Sort Items Alphabetically");
m.addItem (8, "Sort Items Alphabetically (Groups first)");
m.addSeparator();
if (! isRoot())
{
m.addItem (9, "Rename...");
m.addItem (10, "Delete");
}
launchPopupMenu (m, p);
}
void showAddMenu (Point<int> p) override
{
PopupMenu m;
addCreateFileMenuItems (m);
launchPopupMenu (m, p);
}
void handlePopupMenuResult (int resultCode) override
{
switch (resultCode)
{
case 1: closeAllGroups (getOwnerView()->getRootItem()); break;
case 2: openAllGroups (getOwnerView()->getRootItem()); break;
case 3: openOrCloseAllSubGroups (*this, false); break;
case 4: openOrCloseAllSubGroups (*this, true); break;
case 5: setFilesToCompile (item, true); break;
case 6: setFilesToCompile (item, false); break;
case 7: item.sortAlphabetically (false, false); break;
case 8: item.sortAlphabetically (true, false); break;
case 9: triggerAsyncRename (item); break;
case 10: deleteAllSelectedItems(); break;
default: processCreateFileMenuItem (resultCode); break;
}
}
void addCreateFileMenuItems (PopupMenu& m)
{
m.addItem (1001, "Add New Group");
m.addItem (1002, "Add Existing Files...");
m.addSeparator();
wizard.addWizardsToMenu (m);
}
void processCreateFileMenuItem (int menuID)
{
switch (menuID)
{
case 1001: addNewGroup(); break;
case 1002: browseToAddExistingFiles(); break;
default:
jassert (getProject() != nullptr);
wizard.runWizardFromMenu (menuID, *getProject(), item);
break;
}
}
Project* getProject()
{
if (auto* tv = getOwnerView())
if (auto* pcc = tv->findParentComponentOfClass<ProjectContentComponent>())
return pcc->getProject();
return nullptr;
}
void setSearchFilter (const String& filter) override
{
searchFilter = filter;
refreshSubItems();
}
String searchFilter;
NewFileWizard wizard;
};

View File

@ -0,0 +1,694 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ModuleItem : public ProjectTreeItemBase
{
public:
ModuleItem (Project& p, const String& modID)
: project (p), moduleID (modID)
{
missingDependencies = project.getEnabledModules().getExtraDependenciesNeeded (moduleID).size() > 0;
cppStandardHigherThanProject = project.getEnabledModules().doesModuleHaveHigherCppStandardThanProject (moduleID);
moduleInfo = project.getEnabledModules().getModuleInfo (moduleID);
}
bool canBeSelected() const override { return true; }
bool mightContainSubItems() override { return false; }
String getUniqueName() const override { return "module_" + moduleID; }
String getDisplayName() const override { return moduleID; }
String getRenamingName() const override { return getDisplayName(); }
void setName (const String&) override {}
bool isMissing() const override { return missingDependencies; }
bool hasWarnings() const override { return cppStandardHigherThanProject; }
void showDocument() override
{
showSettingsPage (new ModuleSettingsPanel (project, moduleID, getOwnerView()));
}
void deleteItem() override
{
closeSettingsPage();
project.getEnabledModules().removeModule (moduleID);
}
Icon getIcon() const override
{
auto iconColour = getOwnerView()->findColour (isSelected() ? defaultHighlightedTextColourId
: treeIconColourId);
if (! isSelected())
{
if (moduleInfo.isValid() && moduleInfo.getVendor() == "juce")
{
if (moduleInfo.getLicense() == "ISC")
iconColour = Colours::lightblue;
else if (moduleInfo.getLicense() == "GPL/Commercial")
iconColour = Colours::orange;
}
}
return Icon (getIcons().singleModule, iconColour);
}
void showAddMenu (Point<int> p) override
{
if (auto* parent = dynamic_cast<EnabledModulesItem*> (getParentItem()))
parent->showPopupMenu (p);
}
void showPopupMenu (Point<int> p) override
{
PopupMenu menu;
menu.addItem (1, "Remove this module");
launchPopupMenu (menu, p);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode == 1)
deleteItem();
}
bool checkCppStandard()
{
auto oldVal = cppStandardHigherThanProject;
cppStandardHigherThanProject = project.getEnabledModules().doesModuleHaveHigherCppStandardThanProject (moduleID);
if (oldVal != cppStandardHigherThanProject)
return true;
return false;
}
Project& project;
String moduleID;
private:
ModuleDescription moduleInfo;
bool missingDependencies = false;
bool cppStandardHigherThanProject = false;
//==============================================================================
class ModuleSettingsPanel : public Component,
private Value::Listener,
private Timer
{
public:
ModuleSettingsPanel (Project& p, const String& modID, TreeView* tree)
: group (p.getEnabledModules().getModuleInfo (modID).getID(),
Icon (getIcons().singleModule, Colours::transparentBlack)),
project (p),
modulesTree (tree),
moduleID (modID)
{
addAndMakeVisible (group);
refresh();
}
void refresh()
{
auto& modules = project.getEnabledModules();
setEnabled (modules.isModuleEnabled (moduleID));
PropertyListBuilder props;
props.add (new ModuleInfoComponent (project, moduleID));
if (modules.getExtraDependenciesNeeded (moduleID).size() > 0)
props.add (new MissingDependenciesComponent (project, moduleID));
if (modules.doesModuleHaveHigherCppStandardThanProject (moduleID))
props.add (new CppStandardWarningComponent());
group.clearProperties();
exporterModulePathDefaultValues.clear();
exporterModulePathValues.clear();
globalPathValues.clear();
for (Project::ExporterIterator exporter (project); exporter.next();)
{
if (exporter->isCLion())
continue;
exporterModulePathDefaultValues.add (exporter->getPathForModuleValue (moduleID));
auto& defaultValue = exporterModulePathDefaultValues.getReference (exporterModulePathDefaultValues.size() - 1);
exporterModulePathValues.add (defaultValue.getPropertyAsValue());
auto pathComponent = std::make_unique<FilePathPropertyComponent> (defaultValue,
"Path for " + exporter->getUniqueName().quoted(),
true,
exporter->getTargetOSForExporter() == TargetOS::getThisOS(),
"*",
project.getProjectFolder());
pathComponent->setEnabled (! modules.shouldUseGlobalPath (moduleID));
props.add (pathComponent.release(),
"A path to the folder that contains the " + moduleID + " module when compiling the "
+ exporter->getUniqueName().quoted() + " target. "
"This can be an absolute path, or relative to the jucer project folder, but it "
"must be valid on the filesystem of the target machine that will be performing this build. If this "
"is empty then the global path will be used.");
globalPathValues.add (getAppSettings().getStoredPath (isJUCEModule (moduleID) ? Ids::defaultJuceModulePath : Ids::defaultUserModulePath,
exporter->getTargetOSForExporter()).getPropertyAsValue());
}
for (int i = 0; i < exporterModulePathDefaultValues.size(); ++i)
{
exporterModulePathDefaultValues.getReference (i).onDefaultChange = [this] { startTimer (50); };
exporterModulePathValues.getReference (i).addListener (this);
globalPathValues.getReference (i).addListener (this);
}
useGlobalPathValue.removeListener (this);
useGlobalPathValue.referTo (modules.shouldUseGlobalPathValue (moduleID));
useGlobalPathValue.addListener (this);
auto menuItemString = (TargetOS::getThisOS() == TargetOS::osx ? "\"Projucer->Global Paths...\""
: "\"File->Global Paths...\"");
props.add (new BooleanPropertyComponent (useGlobalPathValue,
"Use global path", "Use global path for this module"),
String ("If this is enabled, then the locally-stored global path (set in the ") + menuItemString + " menu item) "
"will be used as the path to this module. "
"This means that if this Projucer project is opened on another machine it will use that machine's global path as the path to this module.");
props.add (new BooleanPropertyComponent (modules.shouldCopyModuleFilesLocallyValue (moduleID),
"Create local copy", "Copy the module into the project folder"),
"If this is enabled, then a local copy of the entire module will be made inside your project (in the auto-generated JuceLibraryFiles folder), "
"so that your project will be self-contained, and won't need to contain any references to files in other folders. "
"This also means that you can check the module into your source-control system to make sure it is always in sync with your own code.");
props.add (new BooleanPropertyComponent (modules.shouldShowAllModuleFilesInProjectValue (moduleID),
"Add source to project", "Make module files browsable in projects"),
"If this is enabled, then the entire source tree from this module will be shown inside your project, "
"making it easy to browse/edit the module's classes. If disabled, then only the minimum number of files "
"required to compile it will appear inside your project.");
auto info = modules.getModuleInfo (moduleID);
if (info.isValid())
{
configFlags.clear();
LibraryModule (info).getConfigFlags (project, configFlags);
for (auto* flag : configFlags)
{
auto* c = new ChoicePropertyComponent (flag->value, flag->symbol);
c->setTooltip (flag->description);
props.add (c);
}
}
group.setProperties (props);
parentSizeChanged();
}
void parentSizeChanged() override { updateSize (*this, group); }
void resized() override { group.setBounds (getLocalBounds().withTrimmedLeft (12)); }
String getModuleID() const noexcept { return moduleID; }
private:
void valueChanged (Value& v) override
{
auto isExporterPathValue = [&]
{
for (auto& exporterValue : exporterModulePathValues)
if (exporterValue.refersToSameSourceAs (v))
return true;
return false;
}();
if (isExporterPathValue)
project.rescanExporterPathModules();
startTimer (50);
}
void timerCallback() override
{
stopTimer();
refresh();
}
//==============================================================================
Array<ValueWithDefault> exporterModulePathDefaultValues;
Array<Value> exporterModulePathValues, globalPathValues;
Value useGlobalPathValue;
OwnedArray<Project::ConfigFlag> configFlags;
PropertyGroupComponent group;
Project& project;
SafePointer<TreeView> modulesTree;
String moduleID;
//==============================================================================
class ModuleInfoComponent : public PropertyComponent,
private Value::Listener
{
public:
ModuleInfoComponent (Project& p, const String& modID)
: PropertyComponent ("Module", 150), project (p), moduleID (modID)
{
for (Project::ExporterIterator exporter (project); exporter.next();)
listeningValues.add (new Value (exporter->getPathForModuleValue (moduleID).getPropertyAsValue()))->addListener (this);
refresh();
}
void refresh() override
{
info = project.getEnabledModules().getModuleInfo (moduleID);
repaint();
}
private:
void paint (Graphics& g) override
{
auto bounds = getLocalBounds().reduced (10);
bounds.removeFromTop (5);
if (info.isValid())
{
auto topSlice = bounds.removeFromTop (bounds.getHeight() / 2);
bounds.removeFromTop (bounds.getHeight() / 6);
auto bottomSlice = bounds;
g.setColour (findColour (defaultTextColourId));
g.drawFittedText (info.getName(), topSlice.removeFromTop (topSlice.getHeight() / 4), Justification::centredLeft, 1);
g.drawFittedText ("Version: " + info.getVersion(), topSlice.removeFromTop (topSlice.getHeight() / 3), Justification::centredLeft, 1);
g.drawFittedText ("License: " + info.getLicense(), topSlice.removeFromTop (topSlice.getHeight() / 2), Justification::centredLeft, 1);
g.drawFittedText ("Location: " + info.getFolder().getParentDirectory().getFullPathName(),
topSlice.removeFromTop (topSlice.getHeight()), Justification::centredLeft, 1);
g.drawFittedText (info.getDescription(), bottomSlice, Justification::topLeft, 3, 1.0f);
}
else
{
g.setColour (Colours::red);
g.drawFittedText ("Cannot find this module at the specified path!", bounds, Justification::centred, 1);
}
}
void valueChanged (Value&) override
{
refresh();
}
Project& project;
String moduleID;
OwnedArray<Value> listeningValues;
ModuleDescription info;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModuleInfoComponent)
};
//==============================================================================
class MissingDependenciesComponent : public PropertyComponent
{
public:
MissingDependenciesComponent (Project& p, const String& modID)
: PropertyComponent ("Dependencies", 100),
project (p), moduleID (modID),
missingDependencies (project.getEnabledModules().getExtraDependenciesNeeded (modID))
{
addAndMakeVisible (fixButton);
fixButton.setColour (TextButton::buttonColourId, Colours::red);
fixButton.setColour (TextButton::textColourOffId, Colours::white);
fixButton.onClick = [this] { fixDependencies(); };
}
void refresh() override {}
void paint (Graphics& g) override
{
String text ("This module has missing dependencies!\n\n"
"To build correctly, it requires the following modules to be added:\n");
text << missingDependencies.joinIntoString (", ");
g.setColour (Colours::red);
g.drawFittedText (text, getLocalBounds().reduced (10), Justification::topLeft, 3);
}
void fixDependencies()
{
auto& enabledModules = project.getEnabledModules();
if (enabledModules.tryToFixMissingDependencies (moduleID))
{
missingDependencies.clear();
}
else
{
missingDependencies = enabledModules.getExtraDependenciesNeeded (moduleID);
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Adding Missing Dependencies",
"Couldn't locate some of these modules - you'll need to find their "
"folders manually and add them to the list.");
}
}
void resized() override
{
fixButton.setBounds (getWidth() - 168, getHeight() - 26, 160, 22);
}
private:
Project& project;
String moduleID;
StringArray missingDependencies;
TextButton fixButton { "Add Required Modules" };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MissingDependenciesComponent)
};
//==============================================================================
struct CppStandardWarningComponent : public PropertyComponent
{
CppStandardWarningComponent()
: PropertyComponent ("CppStandard", 100)
{
}
void refresh() override {}
void paint (Graphics& g) override
{
auto text = String ("This module has a higher C++ language standard requirement than your project!\n\n"
"To use this module you need to increase the C++ standard of the project.\n");
g.setColour (findColour (defaultHighlightColourId));
g.drawFittedText (text, getLocalBounds().reduced (10), Justification::topLeft, 3);
}
StringArray configsToWarnAbout;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CppStandardWarningComponent)
};
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModuleItem)
};
//==============================================================================
class EnabledModulesItem : public ProjectTreeItemBase,
private Value::Listener,
private AvailableModulesList::Listener
{
public:
EnabledModulesItem (Project& p)
: project (p),
modulesListTree (project.getEnabledModules().getState())
{
modulesListTree.addListener (this);
projectCppStandardValue.referTo (project.getProjectValue (Ids::cppLanguageStandard));
projectCppStandardValue.addListener (this);
ProjucerApplication::getApp().getJUCEPathModulesList().addListener (this);
ProjucerApplication::getApp().getUserPathsModulesList().addListener (this);
project.getExporterPathsModulesList().addListener (this);
}
~EnabledModulesItem() override
{
ProjucerApplication::getApp().getJUCEPathModulesList().removeListener (this);
ProjucerApplication::getApp().getUserPathsModulesList().removeListener (this);
project.getExporterPathsModulesList().removeListener (this);
}
int getItemHeight() const override { return 22; }
bool isModulesList() const override { return true; }
bool canBeSelected() const override { return true; }
bool mightContainSubItems() override { return true; }
String getUniqueName() const override { return "modules"; }
String getRenamingName() const override { return getDisplayName(); }
String getDisplayName() const override { return "Modules"; }
void setName (const String&) override {}
bool isMissing() const override { return false; }
Icon getIcon() const override { return Icon (getIcons().graph, getContentColour (true)); }
void showDocument() override
{
if (auto* pcc = getProjectContentComponent())
pcc->setScrollableEditorComponent (std::make_unique<ModulesInformationComponent> (project));
}
static File getModuleFolder (const File& draggedFile)
{
if (draggedFile.hasFileExtension (headerFileExtensions))
return draggedFile.getParentDirectory();
return draggedFile;
}
bool isInterestedInFileDrag (const StringArray& files) override
{
for (auto i = files.size(); --i >= 0;)
if (ModuleDescription (getModuleFolder (files[i])).isValid())
return true;
return false;
}
void filesDropped (const StringArray& files, int /*insertIndex*/) override
{
Array<ModuleDescription> modules;
for (auto f : files)
{
ModuleDescription m (getModuleFolder (f));
if (m.isValid())
modules.add (m);
}
for (int i = 0; i < modules.size(); ++i)
project.getEnabledModules().addModule (modules.getReference (i).getModuleFolder(),
project.getEnabledModules().areMostModulesCopiedLocally(),
project.getEnabledModules().areMostModulesUsingGlobalPath());
}
void addSubItems() override
{
for (int i = 0; i < project.getEnabledModules().getNumModules(); ++i)
addSubItem (new ModuleItem (project, project.getEnabledModules().getModuleID (i)));
}
void showPopupMenu (Point<int> p) override
{
auto& enabledModules = project.getEnabledModules();
PopupMenu allModules;
int index = 100;
// JUCE path
PopupMenu jucePathModules;
for (auto& mod : ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules())
jucePathModules.addItem (index++, mod.first, ! enabledModules.isModuleEnabled (mod.first));
jucePathModules.addSeparator();
jucePathModules.addItem (-1, "Re-scan path");
allModules.addSubMenu ("Global JUCE modules path", jucePathModules);
// User path
index = 200;
PopupMenu userPathModules;
for (auto& mod : ProjucerApplication::getApp().getUserPathsModulesList().getAllModules())
userPathModules.addItem (index++, mod.first, ! enabledModules.isModuleEnabled (mod.first));
userPathModules.addSeparator();
userPathModules.addItem (-2, "Re-scan path");
allModules.addSubMenu ("Global user modules path", userPathModules);
// Exporter path
index = 300;
PopupMenu exporterPathModules;
for (auto& mod : project.getExporterPathsModulesList().getAllModules())
exporterPathModules.addItem (index++, mod.first, ! enabledModules.isModuleEnabled (mod.first));
exporterPathModules.addSeparator();
exporterPathModules.addItem (-3, "Re-scan path");
allModules.addSubMenu ("Exporter paths", exporterPathModules);
PopupMenu menu;
menu.addSubMenu ("Add a module", allModules);
menu.addSeparator();
menu.addItem (1001, "Add a module from a specified folder...");
launchPopupMenu (menu, p);
}
void handlePopupMenuResult (int resultCode) override
{
if (resultCode == 1001)
{
project.getEnabledModules().addModuleFromUserSelectedFile();
}
else if (resultCode < 0)
{
if (resultCode == -1) ProjucerApplication::getApp().rescanJUCEPathModules();
else if (resultCode == -2) ProjucerApplication::getApp().rescanUserPathModules();
else if (resultCode == -3) project.rescanExporterPathModules();
}
else if (resultCode > 0)
{
std::vector<AvailableModulesList::ModuleIDAndFolder> list;
int offset = -1;
if (resultCode < 200)
{
list = ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules();
offset = 100;
}
else if (resultCode < 300)
{
list = ProjucerApplication::getApp().getUserPathsModulesList().getAllModules();
offset = 200;
}
else if (resultCode < 400)
{
list = project.getExporterPathsModulesList().getAllModules();
offset = 300;
}
if (offset != -1)
project.getEnabledModules().addModuleInteractive (list[(size_t) (resultCode - offset)].first);
}
}
//==============================================================================
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { refreshIfNeeded (parentTree); }
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { refreshIfNeeded (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { refreshIfNeeded (parentTree); }
void refreshIfNeeded (ValueTree& changedTree)
{
if (changedTree == modulesListTree)
{
auto selectedID = getSelectedItemID();
refreshSubItems();
if (selectedID.isNotEmpty())
setSelectedItem (selectedID);
}
}
private:
Project& project;
ValueTree modulesListTree;
Value projectCppStandardValue;
//==============================================================================
void valueChanged (Value& v) override
{
if (v == projectCppStandardValue)
{
for (int i = 0; i < getNumSubItems(); ++i)
{
if (auto* moduleItem = dynamic_cast<ModuleItem*> (getSubItem (i)))
{
if (moduleItem->checkCppStandard())
{
refreshSubItems();
return;
}
}
}
}
}
void removeDuplicateModules()
{
auto jucePathModulesList = ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules();
auto& userPathModules = ProjucerApplication::getApp().getUserPathsModulesList();
userPathModules.removeDuplicates (jucePathModulesList);
auto& exporterPathModules = project.getExporterPathsModulesList();
exporterPathModules.removeDuplicates (jucePathModulesList);
exporterPathModules.removeDuplicates (userPathModules.getAllModules());
}
void availableModulesChanged (AvailableModulesList*) override
{
removeDuplicateModules();
refreshSubItems();
}
String getSelectedItemID() const
{
for (int i = 0; i < getNumSubItems(); ++i)
if (auto* item = getSubItem (i))
if (item->isSelected())
return item->getUniqueName();
return {};
}
void setSelectedItem (const String& itemID)
{
for (int i = 0; i < getNumSubItems(); ++i)
{
if (auto* item = getSubItem (i))
{
if (item->getUniqueName() == itemID)
{
item->setSelected (true, true);
return;
}
}
}
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesItem)
};

View File

@ -0,0 +1,81 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
struct ProjectTreeItemBase : public JucerTreeViewBase,
public ValueTree::Listener
{
ProjectTreeItemBase() {}
void showSettingsPage (Component* content)
{
content->setComponentID (getUniqueName());
std::unique_ptr<Component> comp (content);
if (auto* pcc = getProjectContentComponent())
pcc->setScrollableEditorComponent (std::move (comp));
}
void closeSettingsPage()
{
if (auto* pcc = getProjectContentComponent())
if (auto* content = pcc->getEditorComponent())
if (content->getComponentID() == getUniqueName())
pcc->hideEditor();
}
void deleteAllSelectedItems() override
{
auto* tree = getOwnerView();
jassert (tree->getNumSelectedItems() <= 1); // multi-select should be disabled
if (auto* s = dynamic_cast<ProjectTreeItemBase*> (tree->getSelectedItem (0)))
s->deleteItem();
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen)
refreshSubItems();
}
virtual bool isProjectSettings() const { return false; }
virtual bool isModulesList() const { return false; }
static void updateSize (Component& comp, PropertyGroupComponent& group)
{
auto width = jmax (550, comp.getParentWidth() - 12);
auto y = 0;
y += group.updateSize (12, y, width - 12);
y = jmax (comp.getParentHeight(), y);
comp.setSize (width, y);
}
};

View File

@ -0,0 +1,560 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ConcertinaHeader : public Component,
public ChangeBroadcaster
{
public:
ConcertinaHeader (String n, Path p)
: Component (n), name (n), iconPath (p)
{
setTitle (getName());
panelIcon = Icon (iconPath, Colours::white);
nameLabel.setText (name, dontSendNotification);
nameLabel.setJustificationType (Justification::centredLeft);
nameLabel.setInterceptsMouseClicks (false, false);
nameLabel.setAccessible (false);
nameLabel.setColour (Label::textColourId, Colours::white);
addAndMakeVisible (nameLabel);
}
void resized() override
{
auto b = getLocalBounds().toFloat();
iconBounds = b.removeFromLeft (b.getHeight()).reduced (7, 7);
arrowBounds = b.removeFromRight (b.getHeight());
nameLabel.setBounds (b.toNearestInt());
}
void paint (Graphics& g) override
{
g.setColour (findColour (defaultButtonBackgroundColourId));
g.fillRoundedRectangle (getLocalBounds().reduced (2, 3).toFloat(), 2.0f);
g.setColour (Colours::white);
g.fillPath (ProjucerLookAndFeel::getArrowPath (arrowBounds,
getParentComponent()->getBoundsInParent().getY() == yPosition ? 2 : 0,
true, Justification::centred));
panelIcon.draw (g, iconBounds.toFloat(), false);
}
void mouseUp (const MouseEvent& e) override
{
if (! e.mouseWasDraggedSinceMouseDown())
sendChangeMessage();
}
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return std::make_unique<AccessibilityHandler> (*this,
AccessibilityRole::button,
AccessibilityActions().addAction (AccessibilityActionType::press,
[this] { sendChangeMessage(); }));
}
int direction = 0;
int yPosition = 0;
private:
String name;
Label nameLabel;
Path iconPath;
Icon panelIcon;
Rectangle<float> arrowBounds, iconBounds;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaHeader)
};
//==============================================================================
class FindPanel : public Component,
private Timer,
private FocusChangeListener
{
public:
FindPanel (std::function<void (const String&)> cb)
: callback (cb)
{
addAndMakeVisible (editor);
editor.onTextChange = [this] { startTimer (250); };
editor.onFocusLost = [this]
{
isFocused = false;
repaint();
};
Desktop::getInstance().addFocusChangeListener (this);
lookAndFeelChanged();
}
~FindPanel() override
{
Desktop::getInstance().removeFocusChangeListener (this);
}
void paintOverChildren (Graphics& g) override
{
if (! isFocused)
return;
g.setColour (findColour (defaultHighlightColourId));
Path p;
p.addRoundedRectangle (getLocalBounds().reduced (2), 3.0f);
g.strokePath (p, PathStrokeType (2.0f));
}
void resized() override
{
editor.setBounds (getLocalBounds().reduced (2));
}
private:
TextEditor editor;
bool isFocused = false;
std::function<void (const String&)> callback;
//==============================================================================
void lookAndFeelChanged() override
{
editor.setTextToShowWhenEmpty ("Filter...", findColour (widgetTextColourId).withAlpha (0.3f));
}
void globalFocusChanged (Component* focusedComponent) override
{
if (focusedComponent == &editor)
{
isFocused = true;
repaint();
}
}
void timerCallback() override
{
stopTimer();
callback (editor.getText());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FindPanel)
};
//==============================================================================
class ConcertinaTreeComponent : public Component
{
public:
class AdditionalComponents
{
public:
enum Type
{
addButton = (1 << 0),
settingsButton = (1 << 1),
findPanel = (1 << 2)
};
AdditionalComponents with (Type t)
{
auto copy = *this;
copy.componentTypes |= t;
return copy;
}
bool has (Type t) const noexcept
{
return (componentTypes & t) != 0;
}
private:
int componentTypes = 0;
};
ConcertinaTreeComponent (const String& name,
TreePanelBase* tree,
AdditionalComponents additionalComponents)
: Component (name),
treeToDisplay (tree)
{
setTitle (getName());
setFocusContainerType (FocusContainerType::focusContainer);
if (additionalComponents.has (AdditionalComponents::addButton))
{
addButton = std::make_unique<IconButton> ("Add", getIcons().plus);
addAndMakeVisible (addButton.get());
addButton->onClick = [this] { showAddMenu(); };
}
if (additionalComponents.has (AdditionalComponents::settingsButton))
{
settingsButton = std::make_unique<IconButton> ("Settings", getIcons().settings);
addAndMakeVisible (settingsButton.get());
settingsButton->onClick = [this] { showSettings(); };
}
if (additionalComponents.has (AdditionalComponents::findPanel))
{
findPanel = std::make_unique<FindPanel> ([this] (const String& filter) { treeToDisplay->rootItem->setSearchFilter (filter); });
addAndMakeVisible (findPanel.get());
}
addAndMakeVisible (treeToDisplay.get());
}
void resized() override
{
auto bounds = getLocalBounds();
if (addButton != nullptr || settingsButton != nullptr || findPanel != nullptr)
{
auto bottomSlice = bounds.removeFromBottom (25);
bottomSlice.removeFromRight (3);
if (addButton != nullptr)
addButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
if (settingsButton != nullptr)
settingsButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
if (findPanel != nullptr)
findPanel->setBounds (bottomSlice.reduced (2));
}
treeToDisplay->setBounds (bounds);
}
TreePanelBase* getTree() const noexcept { return treeToDisplay.get(); }
private:
std::unique_ptr<TreePanelBase> treeToDisplay;
std::unique_ptr<IconButton> addButton, settingsButton;
std::unique_ptr<FindPanel> findPanel;
void showAddMenu()
{
auto numSelected = treeToDisplay->tree.getNumSelectedItems();
if (numSelected > 1)
return;
if (numSelected == 0)
{
if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
root->showPopupMenu (addButton->getScreenBounds().getCentre());
}
else
{
if (auto* item = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getSelectedItem (0)))
item->showAddMenu (addButton->getScreenBounds().getCentre());
}
}
void showSettings()
{
if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
{
treeToDisplay->tree.clearSelectedItems();
root->showDocument();
}
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaTreeComponent)
};
//==============================================================================
struct ProjectSettingsComponent : public Component,
private ChangeListener
{
ProjectSettingsComponent (Project& p)
: project (p),
group (project.getProjectFilenameRootString(),
Icon (getIcons().settings, Colours::transparentBlack))
{
setTitle ("Project Settings");
setFocusContainerType (FocusContainerType::focusContainer);
addAndMakeVisible (group);
updatePropertyList();
project.addChangeListener (this);
}
~ProjectSettingsComponent() override
{
project.removeChangeListener (this);
}
void resized() override
{
group.updateSize (12, 0, getWidth() - 24);
group.setBounds (getLocalBounds().reduced (12, 0));
}
void updatePropertyList()
{
PropertyListBuilder props;
project.createPropertyEditors (props);
group.setProperties (props);
group.setName ("Project Settings");
lastProjectType = project.getProjectTypeString();
parentSizeChanged();
}
void changeListenerCallback (ChangeBroadcaster*) override
{
if (lastProjectType != project.getProjectTypeString())
updatePropertyList();
}
void parentSizeChanged() override
{
auto width = jmax (550, getParentWidth());
auto y = group.updateSize (12, 0, width - 12);
y = jmax (getParentHeight(), y);
setSize (width, y);
}
Project& project;
var lastProjectType;
PropertyGroupComponent group;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSettingsComponent)
};
//==============================================================================
struct FileTreePanel : public TreePanelBase
{
FileTreePanel (Project& p)
: TreePanelBase (&p, "fileTreeState")
{
tree.setMultiSelectEnabled (true);
setRoot (std::make_unique<TreeItemTypes::GroupItem> (p.getMainGroup()));
tree.setRootItemVisible (false);
}
void updateMissingFileStatuses()
{
if (auto* p = dynamic_cast<TreeItemTypes::FileTreeItemBase*> (rootItem.get()))
p->checkFileStatus();
}
};
struct ModuleTreePanel : public TreePanelBase
{
ModuleTreePanel (Project& p)
: TreePanelBase (&p, "moduleTreeState")
{
tree.setMultiSelectEnabled (false);
setRoot (std::make_unique<TreeItemTypes::EnabledModulesItem> (p));
tree.setRootItemVisible (false);
}
};
struct ExportersTreePanel : public TreePanelBase
{
ExportersTreePanel (Project& p)
: TreePanelBase (&p, "exportersTreeState")
{
tree.setMultiSelectEnabled (false);
setRoot (std::make_unique<TreeItemTypes::ExportersTreeRoot> (p));
tree.setRootItemVisible (false);
}
};
//==============================================================================
class Sidebar : public Component,
private ChangeListener
{
public:
Sidebar (Project* p)
: project (p)
{
setFocusContainerType (FocusContainerType::focusContainer);
if (project != nullptr)
buildConcertina();
}
~Sidebar() override
{
TreePanelBase* panels[] = { getFileTreePanel(), getModuleTreePanel(), getExportersTreePanel() };
for (auto* panel : panels)
if (panel != nullptr)
panel->saveOpenness();
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
void resized() override
{
concertinaPanel.setBounds (getLocalBounds().withTrimmedBottom (3));
}
TreePanelBase* getTreeWithSelectedItems()
{
for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
{
if (auto* treeComponent = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (i)))
{
if (auto* base = treeComponent->getTree())
if (base->tree.getNumSelectedItems() != 0)
return base;
}
}
return nullptr;
}
FileTreePanel* getFileTreePanel() { return getPanel<FileTreePanel> (0); }
ModuleTreePanel* getModuleTreePanel() { return getPanel<ModuleTreePanel> (1); }
ExportersTreePanel* getExportersTreePanel() { return getPanel<ExportersTreePanel> (2); }
void showPanel (int panelIndex)
{
jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
concertinaPanel.expandPanelFully (concertinaPanel.getPanel (panelIndex), true);
}
private:
//==============================================================================
template <typename PanelType>
PanelType* getPanel (int panelIndex)
{
if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (panelIndex)))
return dynamic_cast<PanelType*> (panel->getTree());
return nullptr;
}
void changeListenerCallback (ChangeBroadcaster* source) override
{
const auto pointerMatches = [source] (const std::unique_ptr<ConcertinaHeader>& header) { return header.get() == source; };
const auto it = std::find_if (headers.begin(), headers.end(), pointerMatches);
const auto index = (int) std::distance (headers.begin(), it);
if (index != (int) headers.size())
concertinaPanel.expandPanelFully (concertinaPanel.getPanel (index), true);
}
void buildConcertina()
{
for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0 ; --i)
concertinaPanel.removePanel (concertinaPanel.getPanel (i));
headers.clear();
auto addPanel = [this] (const String& name,
TreePanelBase* tree,
ConcertinaTreeComponent::AdditionalComponents components,
const Path& icon)
{
if (project != nullptr)
concertinaPanel.addPanel (-1, new ConcertinaTreeComponent (name, tree, components), true);
headers.push_back (std::make_unique<ConcertinaHeader> (name, icon));
};
using AdditionalComponents = ConcertinaTreeComponent::AdditionalComponents;
addPanel ("File Explorer", new FileTreePanel (*project),
AdditionalComponents{}
.with (AdditionalComponents::addButton)
.with (AdditionalComponents::findPanel),
getIcons().fileExplorer);
addPanel ("Modules", new ModuleTreePanel (*project),
AdditionalComponents{}
.with (AdditionalComponents::addButton)
.with (AdditionalComponents::settingsButton),
getIcons().modules);
addPanel ("Exporters", new ExportersTreePanel (*project),
AdditionalComponents{}.with (AdditionalComponents::addButton),
getIcons().exporter);
for (int i = 0; i < concertinaPanel.getNumPanels(); ++i)
{
auto* p = concertinaPanel.getPanel (i);
auto* h = headers[(size_t) i].get();
p->addMouseListener (this, true);
h->addChangeListener (this);
h->yPosition = i * 30;
concertinaPanel.setCustomPanelHeader (p, h, false);
concertinaPanel.setPanelHeaderSize (p, 30);
}
addAndMakeVisible (concertinaPanel);
}
void mouseDown (const MouseEvent& e) override
{
for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
{
if (auto* p = concertinaPanel.getPanel (i))
{
if (! (p->isParentOf (e.eventComponent)))
{
auto* base = dynamic_cast<TreePanelBase*> (p);
if (base == nullptr)
if (auto* concertina = dynamic_cast<ConcertinaTreeComponent*> (p))
base = concertina->getTree();
if (base != nullptr)
base->tree.clearSelectedItems();
}
}
}
}
//==============================================================================
ConcertinaPanel concertinaPanel;
std::vector<std::unique_ptr<ConcertinaHeader>> headers;
Project* project = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Sidebar)
};

View File

@ -0,0 +1,42 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../../ProjectSaving/jucer_ProjectExporter.h"
#include "../../../Application/Windows/jucer_TranslationToolWindowComponent.h"
#include "../../../Utility/UI/jucer_JucerTreeViewBase.h"
#include "../../../Utility/Helpers/jucer_NewFileWizard.h"
#include "../jucer_ContentViewComponents.h"
#include "../jucer_FileGroupInformationComponent.h"
#include "../jucer_ModulesInformationComponent.h"
struct TreeItemTypes
{
#include "jucer_ProjectTreeItemBase.h"
#include "jucer_ExporterTreeItems.h"
#include "jucer_ModuleTreeItems.h"
#include "jucer_FileTreeItems.h"
};

View File

@ -0,0 +1,112 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ContentViewComponent : public Component
{
public:
ContentViewComponent()
{
setTitle ("Content");
setFocusContainerType (Component::FocusContainerType::focusContainer);
addAndMakeVisible (logoComponent);
addAndMakeVisible (fileNameLabel);
fileNameLabel.setJustificationType (Justification::centred);
}
void resized() override
{
auto bounds = getLocalBounds();
fileNameLabel.setBounds (bounds.removeFromTop (15));
if (content != nullptr)
content->setBounds (bounds);
else
logoComponent.setBounds (bounds);
}
Component* getCurrentComponent() noexcept
{
return content.get();
}
void setContent (std::unique_ptr<Component> newContent,
const String& labelText)
{
content = std::move (newContent);
addAndMakeVisible (content.get());
fileNameLabel.setVisible (labelText.isNotEmpty());
fileNameLabel.setText (labelText, dontSendNotification);
resized();
}
private:
class LogoComponent : public Component
{
public:
void paint (Graphics& g) override
{
g.setColour (findColour (defaultTextColourId));
auto bounds = getLocalBounds();
bounds.reduce (bounds.getWidth() / 6, bounds.getHeight() / 6);
g.setFont (15.0f);
g.drawFittedText (versionInfo, bounds.removeFromBottom (50), Justification::centredBottom, 3);
if (logo != nullptr)
logo->drawWithin (g, bounds.withTrimmedBottom (bounds.getHeight() / 4).toFloat(),
RectanglePlacement (RectanglePlacement::centred), 1.0f);
}
private:
std::unique_ptr<Drawable> logo = []() -> std::unique_ptr<Drawable>
{
if (auto svg = parseXML (BinaryData::background_logo_svg))
return Drawable::createFromSVG (*svg);
jassertfalse;
return {};
}();
String versionInfo = SystemStats::getJUCEVersion()
+ newLine
+ ProjucerApplication::getApp().getVersionDescription();
};
std::unique_ptr<Component> content;
LogoComponent logoComponent;
Label fileNameLabel;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentViewComponent)
};

View File

@ -0,0 +1,446 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../Utility/UI/PropertyComponents/jucer_LabelPropertyComponent.h"
//==============================================================================
struct ContentViewHeader : public Component
{
ContentViewHeader (String headerName, Icon headerIcon)
: name (headerName), icon (headerIcon)
{
setTitle (name);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (contentHeaderBackgroundColourId));
auto bounds = getLocalBounds().reduced (20, 0);
icon.withColour (Colours::white).draw (g, bounds.toFloat().removeFromRight (30), false);
g.setColour (Colours::white);
g.setFont (Font (18.0f));
g.drawFittedText (name, bounds, Justification::centredLeft, 1);
}
String name;
Icon icon;
};
//==============================================================================
class ListBoxHeader : public Component
{
public:
ListBoxHeader (Array<String> columnHeaders)
{
for (auto s : columnHeaders)
{
addAndMakeVisible (headers.add (new Label (s, s)));
widths.add (1.0f / (float) columnHeaders.size());
}
setSize (200, 40);
}
ListBoxHeader (Array<String> columnHeaders, Array<float> columnWidths)
{
jassert (columnHeaders.size() == columnWidths.size());
auto index = 0;
for (auto s : columnHeaders)
{
addAndMakeVisible (headers.add (new Label (s, s)));
widths.add (columnWidths.getUnchecked (index++));
}
recalculateWidths();
setSize (200, 40);
}
void resized() override
{
auto bounds = getLocalBounds();
auto width = bounds.getWidth();
auto index = 0;
for (auto h : headers)
{
auto headerWidth = roundToInt ((float) width * widths.getUnchecked (index));
h->setBounds (bounds.removeFromLeft (headerWidth));
++index;
}
}
void setColumnHeaderWidth (int index, float proportionOfWidth)
{
if (! (isPositiveAndBelow (index, headers.size()) && isPositiveAndNotGreaterThan (proportionOfWidth, 1.0f)))
{
jassertfalse;
return;
}
widths.set (index, proportionOfWidth);
recalculateWidths (index);
}
int getColumnX (int index)
{
auto prop = 0.0f;
for (int i = 0; i < index; ++i)
prop += widths.getUnchecked (i);
return roundToInt (prop * (float) getWidth());
}
float getProportionAtIndex (int index)
{
jassert (isPositiveAndBelow (index, widths.size()));
return widths.getUnchecked (index);
}
private:
OwnedArray<Label> headers;
Array<float> widths;
void recalculateWidths (int indexToIgnore = -1)
{
auto total = 0.0f;
for (auto w : widths)
total += w;
if (total == 1.0f)
return;
auto diff = 1.0f - total;
auto amount = diff / static_cast<float> (indexToIgnore == -1 ? widths.size() : widths.size() - 1);
for (int i = 0; i < widths.size(); ++i)
{
if (i != indexToIgnore)
{
auto val = widths.getUnchecked (i);
widths.set (i, val + amount);
}
}
}
};
//==============================================================================
class InfoButton : public Button
{
public:
InfoButton (const String& infoToDisplay = {})
: Button ({})
{
setTitle ("Info");
if (infoToDisplay.isNotEmpty())
setInfoToDisplay (infoToDisplay);
setSize (20, 20);
}
void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
{
auto bounds = getLocalBounds().toFloat().reduced (2);
auto& icon = getIcons().info;
g.setColour (findColour (treeIconColourId).withMultipliedAlpha (isMouseOverButton || isButtonDown ? 1.0f : 0.5f));
if (isButtonDown)
g.fillEllipse (bounds);
else
g.fillPath (icon, RectanglePlacement (RectanglePlacement::centred)
.getTransformToFit (icon.getBounds(), bounds));
}
void clicked() override
{
auto w = std::make_unique<InfoWindow> (info);
w->setSize (width, w->getHeight() * numLines + 10);
CallOutBox::launchAsynchronously (std::move (w), getScreenBounds(), nullptr);
}
using Button::clicked;
void setInfoToDisplay (const String& infoToDisplay)
{
if (infoToDisplay.isNotEmpty())
{
info = infoToDisplay;
auto stringWidth = roundToInt (Font (14.0f).getStringWidthFloat (info));
width = jmin (300, stringWidth);
numLines += static_cast<int> (stringWidth / width);
setHelpText (info);
}
}
void setAssociatedComponent (Component* comp) { associatedComponent = comp; }
Component* getAssociatedComponent() { return associatedComponent; }
private:
String info;
Component* associatedComponent = nullptr;
int width;
int numLines = 1;
//==============================================================================
struct InfoWindow : public Component
{
InfoWindow (const String& s)
: stringToDisplay (s)
{
setSize (150, 14);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
g.setColour (findColour (defaultTextColourId));
g.setFont (Font (14.0f));
g.drawFittedText (stringToDisplay, getLocalBounds(), Justification::centred, 15, 0.75f);
}
String stringToDisplay;
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InfoButton)
};
//==============================================================================
class PropertyGroupComponent : public Component,
private TextPropertyComponent::Listener
{
public:
PropertyGroupComponent (String name, Icon icon, String desc = {})
: header (name, icon),
description (desc)
{
addAndMakeVisible (header);
}
void setProperties (const PropertyListBuilder& newProps)
{
clearProperties();
if (description.isNotEmpty())
properties.push_back (std::make_unique<LabelPropertyComponent> (description, 16, Font (16.0f),
Justification::centredLeft));
for (auto* comp : newProps.components)
properties.push_back (std::unique_ptr<PropertyComponent> (comp));
for (auto& prop : properties)
{
const auto propertyTooltip = prop->getTooltip();
if (propertyTooltip.isNotEmpty())
{
// set the tooltip to empty so it only displays when its button is clicked
prop->setTooltip ({});
auto infoButton = std::make_unique<InfoButton> (propertyTooltip);
infoButton->setAssociatedComponent (prop.get());
auto propertyAndInfoWrapper = std::make_unique<PropertyAndInfoWrapper> (*prop, *infoButton.get());
addAndMakeVisible (propertyAndInfoWrapper.get());
propertyComponentsWithInfo.push_back (std::move (propertyAndInfoWrapper));
infoButtons.push_back (std::move (infoButton));
}
else
{
addAndMakeVisible (prop.get());
}
if (auto* multiChoice = dynamic_cast<MultiChoicePropertyComponent*> (prop.get()))
multiChoice->onHeightChange = [this] { updateSize(); };
if (auto* text = dynamic_cast<TextPropertyComponent*> (prop.get()))
if (text->isTextEditorMultiLine())
text->addListener (this);
}
}
int updateSize (int x, int y, int width)
{
header.setBounds (0, 0, width, headerSize);
auto height = header.getBottom() + 10;
for (auto& pp : properties)
{
const auto propertyHeight = pp->getPreferredHeight()
+ (getHeightMultiplier (pp.get()) * pp->getPreferredHeight());
auto iter = std::find_if (propertyComponentsWithInfo.begin(), propertyComponentsWithInfo.end(),
[&pp] (const std::unique_ptr<PropertyAndInfoWrapper>& w) { return &w->propertyComponent == pp.get(); });
if (iter != propertyComponentsWithInfo.end())
(*iter)->setBounds (0, height, width - 10, propertyHeight);
else
pp->setBounds (40, height, width - 50, propertyHeight);
if (shouldResizePropertyComponent (pp.get()))
resizePropertyComponent (pp.get());
height += pp->getHeight() + 10;
}
height += 16;
setBounds (x, y, width, jmax (height, getParentHeight()));
return height;
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
const std::vector<std::unique_ptr<PropertyComponent>>& getProperties() const noexcept
{
return properties;
}
void clearProperties()
{
propertyComponentsWithInfo.clear();
infoButtons.clear();
properties.clear();
}
private:
//==============================================================================
struct PropertyAndInfoWrapper : public Component
{
PropertyAndInfoWrapper (PropertyComponent& c, InfoButton& i)
: propertyComponent (c),
infoButton (i)
{
setFocusContainerType (FocusContainerType::focusContainer);
setTitle (propertyComponent.getName());
addAndMakeVisible (propertyComponent);
addAndMakeVisible (infoButton);
}
void resized() override
{
auto bounds = getLocalBounds();
bounds.removeFromLeft (40);
bounds.removeFromRight (10);
propertyComponent.setBounds (bounds);
infoButton.setCentrePosition (20, bounds.getHeight() / 2);
}
PropertyComponent& propertyComponent;
InfoButton& infoButton;
};
//==============================================================================
void textPropertyComponentChanged (TextPropertyComponent* comp) override
{
auto fontHeight = [comp]
{
Label tmpLabel;
return comp->getLookAndFeel().getLabelFont (tmpLabel).getHeight();
}();
auto lines = StringArray::fromLines (comp->getText());
comp->setPreferredHeight (jmax (100, 10 + roundToInt (fontHeight * (float) lines.size())));
updateSize();
}
void updateSize()
{
updateSize (getX(), getY(), getWidth());
if (auto* parent = getParentComponent())
parent->parentSizeChanged();
}
bool shouldResizePropertyComponent (PropertyComponent* p)
{
if (auto* textComp = dynamic_cast<TextPropertyComponent*> (p))
return ! textComp->isTextEditorMultiLine();
return (dynamic_cast<ChoicePropertyComponent*> (p) != nullptr
|| dynamic_cast<ButtonPropertyComponent*> (p) != nullptr
|| dynamic_cast<BooleanPropertyComponent*> (p) != nullptr);
}
void resizePropertyComponent (PropertyComponent* pp)
{
for (auto i = pp->getNumChildComponents() - 1; i >= 0; --i)
{
auto* child = pp->getChildComponent (i);
auto bounds = child->getBounds();
child->setBounds (bounds.withSizeKeepingCentre (child->getWidth(), pp->getPreferredHeight()));
}
}
int getHeightMultiplier (PropertyComponent* pp)
{
auto availableTextWidth = ProjucerLookAndFeel::getTextWidthForPropertyComponent (pp);
auto font = ProjucerLookAndFeel::getPropertyComponentFont();
auto nameWidth = font.getStringWidthFloat (pp->getName());
if (availableTextWidth == 0)
return 0;
return static_cast<int> (nameWidth / (float) availableTextWidth);
}
//==============================================================================
static constexpr int headerSize = 40;
std::vector<std::unique_ptr<PropertyComponent>> properties;
std::vector<std::unique_ptr<InfoButton>> infoButtons;
std::vector<std::unique_ptr<PropertyAndInfoWrapper>> propertyComponentsWithInfo;
ContentViewHeader header;
String description;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyGroupComponent)
};

View File

@ -0,0 +1,363 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class FileGroupInformationComponent : public Component,
private ListBoxModel,
private ValueTree::Listener
{
public:
FileGroupInformationComponent (const Project::Item& group)
: item (group),
header (item.getName(), { getIcons().openFolder, Colours::transparentBlack })
{
list.setHeaderComponent (std::make_unique<ListBoxHeader> (Array<String> { "File", "Binary Resource", "Xcode Resource", "Compile", "Skip PCH", "Compiler Flag Scheme" },
Array<float> { 0.25f, 0.125f, 0.125f, 0.125f, 0.125f, 0.25f }));
list.setModel (this);
list.setColour (ListBox::backgroundColourId, Colours::transparentBlack);
addAndMakeVisible (list);
list.updateContent();
list.setRowHeight (30);
item.state.addListener (this);
lookAndFeelChanged();
addAndMakeVisible (header);
}
~FileGroupInformationComponent() override
{
item.state.removeListener (this);
}
//==============================================================================
void paint (Graphics& g) override
{
g.setColour (findColour (secondaryBackgroundColourId));
g.fillRect (getLocalBounds().reduced (12, 0));
}
void resized() override
{
auto bounds = getLocalBounds().reduced (12, 0);
header.setBounds (bounds.removeFromTop (40));
list.setBounds (bounds.reduced (10, 4));
}
void parentSizeChanged() override
{
setSize (jmax (550, getParentWidth()), getParentHeight());
}
int getNumRows() override
{
return item.getNumChildren();
}
void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool /*rowIsSelected*/) override
{
g.setColour (findColour (rowNumber % 2 == 0 ? widgetBackgroundColourId
: secondaryWidgetBackgroundColourId));
g.fillRect (0, 0, width, height - 1);
}
Component* refreshComponentForRow (int rowNumber, bool /*isRowSelected*/, Component* existingComponentToUpdate) override
{
std::unique_ptr<Component> existing (existingComponentToUpdate);
if (rowNumber < getNumRows())
{
auto child = item.getChild (rowNumber);
if (existingComponentToUpdate == nullptr
|| dynamic_cast<FileOptionComponent*> (existing.get())->item != child)
{
existing.reset();
existing.reset (new FileOptionComponent (child, dynamic_cast<ListBoxHeader*> (list.getHeaderComponent())));
}
}
return existing.release();
}
String getGroupPath() const { return item.getFile().getFullPathName(); }
//==============================================================================
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { itemChanged(); }
void valueTreeChildAdded (ValueTree&, ValueTree&) override { itemChanged(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { itemChanged(); }
void valueTreeChildOrderChanged (ValueTree&, int, int) override { itemChanged(); }
void valueTreeParentChanged (ValueTree&) override { itemChanged(); }
private:
Project::Item item;
ListBox list;
ContentViewHeader header;
void itemChanged()
{
list.updateContent();
repaint();
}
//==============================================================================
class FileOptionComponent : public Component
{
public:
FileOptionComponent (const Project::Item& fileItem, ListBoxHeader* listBoxHeader)
: item (fileItem),
header (listBoxHeader),
compilerFlagSchemeSelector (item)
{
if (item.isFile())
{
auto isSourceFile = item.isSourceFile();
if (isSourceFile)
{
addAndMakeVisible (compileButton);
compileButton.getToggleStateValue().referTo (item.getShouldCompileValue());
compileButton.onStateChange = [this] { compileEnablementChanged(); };
}
addAndMakeVisible (binaryResourceButton);
binaryResourceButton.getToggleStateValue().referTo (item.getShouldAddToBinaryResourcesValue());
addAndMakeVisible (xcodeResourceButton);
xcodeResourceButton.getToggleStateValue().referTo (item.getShouldAddToXcodeResourcesValue());
if (isSourceFile)
{
addChildComponent (skipPCHButton);
skipPCHButton.getToggleStateValue().referTo (item.getShouldSkipPCHValue());
addChildComponent (compilerFlagSchemeSelector);
compileEnablementChanged();
}
}
}
void paint (Graphics& g) override
{
if (header != nullptr)
{
auto textBounds = getLocalBounds().removeFromLeft (roundToInt (header->getProportionAtIndex (0) * (float) getWidth()));
auto iconBounds = textBounds.removeFromLeft (25);
if (item.isImageFile())
iconBounds.reduce (5, 5);
item.getIcon().withColour (findColour (treeIconColourId)).draw (g, iconBounds.toFloat(), item.isIconCrossedOut());
g.setColour (findColour (widgetTextColourId));
g.drawText (item.getName(), textBounds, Justification::centredLeft);
}
}
void resized() override
{
if (header != nullptr)
{
auto bounds = getLocalBounds();
auto width = (float) getWidth();
bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (0) * width));
binaryResourceButton.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (1) * width)));
xcodeResourceButton.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (2) * width)));
compileButton.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (3) * width)));
skipPCHButton.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (4) * width)));
compilerFlagSchemeSelector.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (5) * width)));
}
}
Project::Item item;
private:
//==============================================================================
class CompilerFlagSchemeSelector : public Component,
private Value::Listener
{
public:
CompilerFlagSchemeSelector (Project::Item& it)
: item (it)
{
schemeBox.setTextWhenNothingSelected ("None");
updateCompilerFlagSchemeComboBox();
schemeBox.onChange = [this] { handleComboBoxSelection(); };
addAndMakeVisible (schemeBox);
addChildComponent (newSchemeLabel);
newSchemeLabel.setEditable (true);
newSchemeLabel.setJustificationType (Justification::centredLeft);
newSchemeLabel.onEditorHide = [this]
{
newSchemeLabel.setVisible (false);
schemeBox.setVisible (true);
auto newScheme = newSchemeLabel.getText();
item.project.addCompilerFlagScheme (newScheme);
if (item.getCompilerFlagSchemeString().isEmpty())
item.setCompilerFlagScheme (newScheme);
updateCompilerFlagSchemeComboBox();
};
selectScheme (item.getCompilerFlagSchemeString());
projectCompilerFlagSchemesValue = item.project.getProjectValue (Ids::compilerFlagSchemes);
projectCompilerFlagSchemesValue.addListener (this);
lookAndFeelChanged();
}
void resized() override
{
auto b = getLocalBounds();
schemeBox.setBounds (b);
newSchemeLabel.setBounds (b);
}
private:
void valueChanged (Value&) override { updateCompilerFlagSchemeComboBox(); }
void lookAndFeelChanged() override
{
schemeBox.setColour (ComboBox::outlineColourId, Colours::transparentBlack);
schemeBox.setColour (ComboBox::textColourId, findColour (defaultTextColourId));
}
void updateCompilerFlagSchemeComboBox()
{
auto itemScheme = item.getCompilerFlagSchemeString();
auto allSchemes = item.project.getCompilerFlagSchemes();
if (itemScheme.isNotEmpty() && ! allSchemes.contains (itemScheme))
{
item.clearCurrentCompilerFlagScheme();
itemScheme = {};
}
schemeBox.clear();
schemeBox.addItemList (allSchemes, 1);
schemeBox.addSeparator();
schemeBox.addItem ("Add a new scheme...", -1);
schemeBox.addItem ("Delete selected scheme", -2);
schemeBox.addItem ("Clear", -3);
selectScheme (itemScheme);
}
void handleComboBoxSelection()
{
auto selectedID = schemeBox.getSelectedId();
if (selectedID > 0)
{
item.setCompilerFlagScheme (schemeBox.getItemText (selectedID - 1));
}
else if (selectedID == -1)
{
newSchemeLabel.setText ("NewScheme", dontSendNotification);
schemeBox.setVisible (false);
newSchemeLabel.setVisible (true);
newSchemeLabel.showEditor();
if (auto* ed = newSchemeLabel.getCurrentTextEditor())
ed->setInputRestrictions (64, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_");
}
else if (selectedID == -2)
{
auto currentScheme = item.getCompilerFlagSchemeString();
if (currentScheme.isNotEmpty())
{
item.project.removeCompilerFlagScheme (currentScheme);
item.clearCurrentCompilerFlagScheme();
}
updateCompilerFlagSchemeComboBox();
}
else if (selectedID == -3)
{
schemeBox.setSelectedId (0);
item.clearCurrentCompilerFlagScheme();
}
}
void selectScheme (const String& schemeToSelect)
{
if (schemeToSelect.isNotEmpty())
{
for (int i = 0; i < schemeBox.getNumItems(); ++i)
{
if (schemeBox.getItemText (i) == schemeToSelect)
{
schemeBox.setSelectedItemIndex (i);
return;
}
}
}
schemeBox.setSelectedId (0);
}
Project::Item& item;
Value projectCompilerFlagSchemesValue;
ComboBox schemeBox;
Label newSchemeLabel;
};
void compileEnablementChanged()
{
auto shouldBeCompiled = compileButton.getToggleState();
skipPCHButton.setVisible (shouldBeCompiled);
compilerFlagSchemeSelector.setVisible (shouldBeCompiled);
}
//==============================================================================
ListBoxHeader* header;
ToggleButton compileButton, binaryResourceButton, xcodeResourceButton, skipPCHButton;
CompilerFlagSchemeSelector compilerFlagSchemeSelector;
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileGroupInformationComponent)
};

View File

@ -0,0 +1,279 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "jucer_HeaderComponent.h"
#include "../../Application/jucer_Application.h"
#include "../../ProjectSaving/jucer_ProjectExporter.h"
#include "../../Project/UI/jucer_ProjectContentComponent.h"
//==============================================================================
HeaderComponent::HeaderComponent (ProjectContentComponent* pcc)
: projectContentComponent (pcc)
{
setTitle ("Header");
setFocusContainerType (FocusContainerType::focusContainer);
addAndMakeVisible (configLabel);
addAndMakeVisible (exporterBox);
exporterBox.onChange = [this] { updateExporterButton(); };
juceIcon.setImage (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize), RectanglePlacement::centred);
addAndMakeVisible (juceIcon);
addAndMakeVisible (userAvatar);
userAvatar.addChangeListener (this);
projectNameLabel.setText ({}, dontSendNotification);
addAndMakeVisible (projectNameLabel);
initialiseButtons();
}
//==============================================================================
void HeaderComponent::resized()
{
auto bounds = getLocalBounds();
configLabel.setFont ({ (float) bounds.getHeight() / 3.0f });
{
auto headerBounds = bounds.removeFromLeft (tabsWidth);
const int buttonSize = 25;
auto buttonBounds = headerBounds.removeFromRight (buttonSize);
projectSettingsButton.setBounds (buttonBounds.removeFromBottom (buttonSize).reduced (2));
juceIcon.setBounds (headerBounds.removeFromLeft (headerBounds.getHeight()).reduced (2));
headerBounds.removeFromRight (5);
projectNameLabel.setBounds (headerBounds);
}
{
auto exporterWidth = jmin (400, bounds.getWidth() / 2);
Rectangle<int> exporterBounds (0, 0, exporterWidth, bounds.getHeight());
exporterBounds.setCentre (bounds.getCentre());
saveAndOpenInIDEButton.setBounds (exporterBounds.removeFromRight (exporterBounds.getHeight()).reduced (2));
exporterBounds.removeFromRight (5);
exporterBox.setBounds (exporterBounds.removeFromBottom (roundToInt ((float) exporterBounds.getHeight() / 1.8f)));
configLabel.setBounds (exporterBounds);
}
userAvatar.setBounds (bounds.removeFromRight (userAvatar.isDisplaingGPLLogo() ? roundToInt ((float) bounds.getHeight() * 1.9f)
: bounds.getHeight()).reduced (2));
}
void HeaderComponent::paint (Graphics& g)
{
g.fillAll (findColour (backgroundColourId));
}
//==============================================================================
void HeaderComponent::setCurrentProject (Project* newProject)
{
stopTimer();
repaint();
projectNameLabel.setText ({}, dontSendNotification);
project = newProject;
if (project != nullptr)
{
exportersTree = project->getExporters();
exportersTree.addListener (this);
updateExporters();
projectNameValue.referTo (project->getProjectValue (Ids::name));
projectNameValue.addListener (this);
updateName();
}
}
//==============================================================================
void HeaderComponent::updateExporters()
{
auto selectedExporter = getSelectedExporter();
exporterBox.clear();
auto preferredExporterIndex = -1;
int i = 0;
for (Project::ExporterIterator exporter (*project); exporter.next(); ++i)
{
auto exporterName = exporter->getUniqueName();
exporterBox.addItem (exporterName, i + 1);
if (selectedExporter != nullptr && exporterName == selectedExporter->getUniqueName())
exporterBox.setSelectedId (i + 1);
if (exporterName.contains (ProjectExporter::getCurrentPlatformExporterTypeInfo().displayName) && preferredExporterIndex == -1)
preferredExporterIndex = i;
}
if (exporterBox.getSelectedItemIndex() == -1)
{
if (preferredExporterIndex == -1)
{
i = 0;
for (Project::ExporterIterator exporter (*project); exporter.next(); ++i)
{
if (exporter->canLaunchProject())
{
preferredExporterIndex = i;
break;
}
}
}
exporterBox.setSelectedItemIndex (preferredExporterIndex != -1 ? preferredExporterIndex : 0);
}
updateExporterButton();
}
std::unique_ptr<ProjectExporter> HeaderComponent::getSelectedExporter() const
{
if (project != nullptr)
{
int i = 0;
auto selectedIndex = exporterBox.getSelectedItemIndex();
for (Project::ExporterIterator exporter (*project); exporter.next();)
if (i++ == selectedIndex)
return std::move (exporter.exporter);
}
return nullptr;
}
bool HeaderComponent::canCurrentExporterLaunchProject() const
{
if (project != nullptr)
{
if (auto selectedExporter = getSelectedExporter())
{
for (Project::ExporterIterator exporter (*project); exporter.next();)
if (exporter->canLaunchProject() && exporter->getUniqueName() == selectedExporter->getUniqueName())
return true;
}
}
return false;
}
//==============================================================================
void HeaderComponent::sidebarTabsWidthChanged (int newWidth)
{
tabsWidth = newWidth;
resized();
}
//==============================================================================
void HeaderComponent::changeListenerCallback (ChangeBroadcaster* source)
{
if (source == &userAvatar)
resized();
}
void HeaderComponent::valueChanged (Value&)
{
updateName();
}
void HeaderComponent::timerCallback()
{
repaint();
}
//==============================================================================
void HeaderComponent::initialiseButtons()
{
addAndMakeVisible (projectSettingsButton);
projectSettingsButton.onClick = [this] { projectContentComponent->showProjectSettings(); };
addAndMakeVisible (saveAndOpenInIDEButton);
saveAndOpenInIDEButton.setBackgroundColour (Colours::white);
saveAndOpenInIDEButton.setIconInset (7);
saveAndOpenInIDEButton.onClick = [this]
{
if (project == nullptr)
return;
if (! project->isSaveAndExportDisabled())
{
projectContentComponent->openInSelectedIDE (true);
return;
}
auto setWarningVisible = [this] (const Identifier& identifier)
{
auto child = project->getProjectMessages().getChildWithName (ProjectMessages::Ids::warning)
.getChildWithName (identifier);
if (child.isValid())
child.setProperty (ProjectMessages::Ids::isVisible, true, nullptr);
};
if (project->hasIncompatibleLicenseTypeAndSplashScreenSetting())
setWarningVisible (ProjectMessages::Ids::incompatibleLicense);
if (project->isFileModificationCheckPending())
setWarningVisible (ProjectMessages::Ids::jucerFileModified);
};
updateExporterButton();
}
void HeaderComponent::updateName()
{
if (project != nullptr)
projectNameLabel.setText (project->getDocumentTitle(), dontSendNotification);
}
void HeaderComponent::updateExporterButton()
{
if (auto selectedExporter = getSelectedExporter())
{
auto selectedName = selectedExporter->getUniqueName();
for (auto info : ProjectExporter::getExporterTypeInfos())
{
if (selectedName.contains (info.displayName))
{
saveAndOpenInIDEButton.setImage (info.icon);
saveAndOpenInIDEButton.repaint();
saveAndOpenInIDEButton.setEnabled (canCurrentExporterLaunchProject());
}
}
}
}

View File

@ -0,0 +1,101 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../Application/jucer_Headers.h"
#include "../../Utility/UI/jucer_IconButton.h"
#include "jucer_UserAvatarComponent.h"
class Project;
class ProjectContentComponent;
class ProjectExporter;
//==============================================================================
class HeaderComponent : public Component,
private ValueTree::Listener,
private ChangeListener,
private Value::Listener,
private Timer
{
public:
HeaderComponent (ProjectContentComponent* projectContentComponent);
//==============================================================================
void resized() override;
void paint (Graphics&) override;
//==============================================================================
void setCurrentProject (Project*);
void updateExporters();
std::unique_ptr<ProjectExporter> getSelectedExporter() const;
bool canCurrentExporterLaunchProject() const;
void sidebarTabsWidthChanged (int newWidth);
private:
//==============================================================================
void changeListenerCallback (ChangeBroadcaster* source) override;
void valueChanged (Value&) override;
void timerCallback() override;
//==============================================================================
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { updateIfNeeded (parentTree); }
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { updateIfNeeded (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { updateIfNeeded (parentTree); }
void updateIfNeeded (ValueTree tree)
{
if (tree == exportersTree)
updateExporters();
}
//==============================================================================
void initialiseButtons();
void updateName();
void updateExporterButton();
//==============================================================================
int tabsWidth = 200;
ProjectContentComponent* projectContentComponent = nullptr;
Project* project = nullptr;
ValueTree exportersTree;
Value projectNameValue;
ComboBox exporterBox;
Label configLabel { "Config Label", "Selected exporter" }, projectNameLabel;
ImageComponent juceIcon;
UserAvatarComponent userAvatar { true };
IconButton projectSettingsButton { "Project Settings", getIcons().settings },
saveAndOpenInIDEButton { "Save and Open in IDE", Image() };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HeaderComponent)
};

View File

@ -0,0 +1,341 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ModulesInformationComponent : public Component,
private ListBoxModel,
private ValueTree::Listener
{
public:
ModulesInformationComponent (Project& p)
: project (p),
modulesValueTree (project.getEnabledModules().getState())
{
auto tempHeader = std::make_unique<ListBoxHeader> (Array<String> { "Module", "Version", "Make Local Copy", "Paths" },
Array<float> { 0.25f, 0.2f, 0.2f, 0.35f });
listHeader = tempHeader.get();
list.setHeaderComponent (std::move (tempHeader));
list.setModel (this);
list.setColour (ListBox::backgroundColourId, Colours::transparentBlack);
addAndMakeVisible (list);
list.updateContent();
list.setRowHeight (30);
list.setMultipleSelectionEnabled (true);
addAndMakeVisible (header);
addAndMakeVisible (setCopyModeButton);
setCopyModeButton.setTriggeredOnMouseDown (true);
setCopyModeButton.onClick = [this] { showCopyModeMenu(); };
addAndMakeVisible (copyPathButton);
copyPathButton.setTriggeredOnMouseDown (true);
copyPathButton.onClick = [this] { showSetPathsMenu(); };
addAndMakeVisible (globalPathsButton);
globalPathsButton.onClick = [this] { showGlobalPathsMenu(); };
modulesValueTree.addListener (this);
lookAndFeelChanged();
}
void paint (Graphics& g) override
{
g.setColour (findColour (secondaryBackgroundColourId));
g.fillRect (getLocalBounds().reduced (12, 0));
}
void resized() override
{
auto bounds = getLocalBounds().reduced (12, 0);
header.setBounds (bounds.removeFromTop (40));
bounds.reduce (10, 0);
list.setBounds (bounds.removeFromTop (list.getRowPosition (getNumRows() - 1, true).getBottom() + 20));
if (bounds.getHeight() < 35)
{
parentSizeChanged();
}
else
{
auto buttonRow = bounds.removeFromTop (35);
setCopyModeButton.setBounds (buttonRow.removeFromLeft (jmin (200, bounds.getWidth() / 3)));
buttonRow.removeFromLeft (8);
copyPathButton.setBounds (buttonRow.removeFromLeft (jmin (200, bounds.getWidth() / 3)));
buttonRow.removeFromLeft (8);
globalPathsButton.setBounds (buttonRow.removeFromLeft (jmin (200, bounds.getWidth() / 3)));
}
}
void parentSizeChanged() override
{
auto width = jmax (550, getParentWidth());
auto y = list.getRowPosition (getNumRows() - 1, true).getBottom() + 200;
y = jmax (getParentHeight(), y);
setSize (width, y);
}
int getNumRows() override
{
return project.getEnabledModules().getNumModules();
}
void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override
{
ignoreUnused (height);
Rectangle<int> bounds (0, 0, width, height);
g.setColour (rowIsSelected ? findColour (defaultHighlightColourId) : findColour (rowNumber % 2 == 0 ? widgetBackgroundColourId
: secondaryWidgetBackgroundColourId));
g.fillRect (bounds.withTrimmedBottom (1));
bounds.removeFromLeft (5);
g.setColour (rowIsSelected ? findColour (defaultHighlightedTextColourId) : findColour (widgetTextColourId));
//==============================================================================
auto moduleID = project.getEnabledModules().getModuleID (rowNumber);
g.drawFittedText (moduleID, bounds.removeFromLeft (roundToInt (listHeader->getProportionAtIndex (0) * (float) width)), Justification::centredLeft, 1);
//==============================================================================
auto version = project.getEnabledModules().getModuleInfo (moduleID).getVersion();
if (version.isEmpty())
version = "?";
g.drawFittedText (version, bounds.removeFromLeft (roundToInt (listHeader->getProportionAtIndex (1) * (float) width)), Justification::centredLeft, 1);
//==============================================================================
g.drawFittedText (String (project.getEnabledModules().shouldCopyModuleFilesLocally (moduleID) ? "Yes" : "No"),
bounds.removeFromLeft (roundToInt (listHeader->getProportionAtIndex (2) * (float) width)), Justification::centredLeft, 1);
//==============================================================================
String pathText;
if (project.getEnabledModules().shouldUseGlobalPath (moduleID))
{
pathText = "Global";
}
else
{
StringArray paths;
for (Project::ExporterIterator exporter (project); exporter.next();)
paths.addIfNotAlreadyThere (exporter->getPathForModuleString (moduleID).trim());
pathText = paths.joinIntoString (", ");
}
g.drawFittedText (pathText, bounds.removeFromLeft (roundToInt (listHeader->getProportionAtIndex (3) * (float) width)), Justification::centredLeft, 1);
}
void listBoxItemDoubleClicked (int row, const MouseEvent&) override
{
auto moduleID = project.getEnabledModules().getModuleID (row);
if (moduleID.isNotEmpty())
if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
pcc->showModule (moduleID);
}
void deleteKeyPressed (int row) override
{
project.getEnabledModules().removeModule (project.getEnabledModules().getModuleID (row));
}
void lookAndFeelChanged() override
{
setCopyModeButton.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
copyPathButton.setColour (TextButton::buttonColourId, findColour (defaultButtonBackgroundColourId));
globalPathsButton.setColour (TextButton::buttonColourId, findColour (defaultButtonBackgroundColourId));
}
private:
enum
{
nameCol = 1,
versionCol,
copyCol,
pathCol
};
Project& project;
ValueTree modulesValueTree;
ContentViewHeader header { "Modules", { getIcons().modules, Colours::transparentBlack } };
ListBox list;
ListBoxHeader* listHeader;
TextButton setCopyModeButton { "Set copy-mode for all modules..." };
TextButton copyPathButton { "Set paths for all modules..." };
TextButton globalPathsButton { "Enable/disable global path for modules..." };
std::map<String, var> modulePathClipboard;
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { itemChanged(); }
void valueTreeChildAdded (ValueTree&, ValueTree&) override { itemChanged(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { itemChanged(); }
void valueTreeChildOrderChanged (ValueTree&, int, int) override { itemChanged(); }
void valueTreeParentChanged (ValueTree&) override { itemChanged(); }
void itemChanged()
{
list.updateContent();
resized();
repaint();
}
static void setLocalCopyModeForAllModules (Project& project, bool copyLocally)
{
auto& modules = project.getEnabledModules();
for (auto i = modules.getNumModules(); --i >= 0;)
modules.shouldCopyModuleFilesLocallyValue (modules.getModuleID (i)) = copyLocally;
}
void showCopyModeMenu()
{
PopupMenu m;
m.addItem (PopupMenu::Item ("Set all modules to copy locally")
.setAction ([&] { setLocalCopyModeForAllModules (project, true); }));
m.addItem (PopupMenu::Item ("Set all modules to not copy locally")
.setAction ([&] { setLocalCopyModeForAllModules (project, false); }));
m.showMenuAsync (PopupMenu::Options().withTargetComponent (setCopyModeButton));
}
static void setAllModulesToUseGlobalPaths (Project& project, bool useGlobal)
{
auto& modules = project.getEnabledModules();
for (auto moduleID : modules.getAllModules())
modules.shouldUseGlobalPathValue (moduleID) = useGlobal;
}
static void setSelectedModulesToUseGlobalPaths (Project& project, SparseSet<int> selected, bool useGlobal)
{
auto& modules = project.getEnabledModules();
for (int i = 0; i < selected.size(); ++i)
modules.shouldUseGlobalPathValue (modules.getModuleID (selected[i])) = useGlobal;
}
void showGlobalPathsMenu()
{
PopupMenu m;
m.addItem (PopupMenu::Item ("Set all modules to use global paths")
.setAction ([&] { setAllModulesToUseGlobalPaths (project, true); }));
m.addItem (PopupMenu::Item ("Set all modules to not use global paths")
.setAction ([&] { setAllModulesToUseGlobalPaths (project, false); }));
m.addItem (PopupMenu::Item ("Set selected modules to use global paths")
.setEnabled (list.getNumSelectedRows() > 0)
.setAction ([&] { setSelectedModulesToUseGlobalPaths (project, list.getSelectedRows(), true); }));
m.addItem (PopupMenu::Item ("Set selected modules to not use global paths")
.setEnabled (list.getNumSelectedRows() > 0)
.setAction ([&] { setSelectedModulesToUseGlobalPaths (project, list.getSelectedRows(), false); }));
m.showMenuAsync (PopupMenu::Options().withTargetComponent (globalPathsButton));
}
void showSetPathsMenu()
{
PopupMenu m;
auto moduleToCopy = project.getEnabledModules().getModuleID (list.getSelectedRow());
if (moduleToCopy.isNotEmpty())
{
m.addItem (PopupMenu::Item ("Copy the paths from the module '" + moduleToCopy + "' to all other modules")
.setAction ([this, moduleToCopy]
{
auto& modulesList = project.getEnabledModules();
for (Project::ExporterIterator exporter (project); exporter.next();)
{
for (int i = 0; i < modulesList.getNumModules(); ++i)
{
auto modID = modulesList.getModuleID (i);
if (modID != moduleToCopy)
exporter->getPathForModuleValue (modID) = exporter->getPathForModuleValue (moduleToCopy).get();
}
}
list.repaint();
}));
m.addItem (PopupMenu::Item ("Copy paths from selected module")
.setEnabled (list.getNumSelectedRows() == 1)
.setAction ([this, moduleToCopy]
{
modulePathClipboard.clear();
for (Project::ExporterIterator exporter (project); exporter.next();)
modulePathClipboard[exporter->getUniqueName()] = exporter->getPathForModuleValue (moduleToCopy).get();
list.repaint();
}));
m.addItem (PopupMenu::Item ("Paste paths to selected modules")
.setEnabled (! modulePathClipboard.empty())
.setAction ([this]
{
for (int selectionId = 0; selectionId < list.getNumSelectedRows(); ++selectionId)
{
auto rowNumber = list.getSelectedRow (selectionId);
auto modID = project.getEnabledModules().getModuleID (rowNumber);
for (Project::ExporterIterator exporter (project); exporter.next();)
exporter->getPathForModuleValue (modID) = modulePathClipboard[exporter->getUniqueName()];
}
list.repaint();
}));
}
else
{
m.addItem (PopupMenu::Item ("(Select a module in the list above to use this option)")
.setEnabled (false));
}
m.showMenuAsync (PopupMenu::Options()
.withDeletionCheck (*this)
.withTargetComponent (copyPathButton));
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModulesInformationComponent)
};

View File

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

View File

@ -0,0 +1,153 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../CodeEditor/jucer_OpenDocumentManager.h"
#include "jucer_HeaderComponent.h"
#include "jucer_ProjectMessagesComponent.h"
#include "jucer_ContentViewComponent.h"
class Sidebar;
struct WizardHolder;
//==============================================================================
class ProjectContentComponent : public Component,
public ApplicationCommandTarget,
private ChangeListener,
private OpenDocumentManager::DocumentCloseListener
{
public:
//==============================================================================
ProjectContentComponent();
~ProjectContentComponent() override;
Project* getProject() const noexcept { return project; }
void setProject (Project*);
void saveOpenDocumentList();
void reloadLastOpenDocuments();
bool showEditorForFile (const File&, bool grabFocus);
bool hasFileInRecentList (const File&) const;
File getCurrentFile() const;
bool showDocument (OpenDocumentManager::Document*, bool grabFocus);
void hideDocument (OpenDocumentManager::Document*);
OpenDocumentManager::Document* getCurrentDocument() const { return currentDocument; }
void closeDocument();
void saveDocumentAsync();
void saveAsAsync();
void hideEditor();
void setScrollableEditorComponent (std::unique_ptr<Component> component);
void setEditorDocument (std::unique_ptr<Component> component, OpenDocumentManager::Document* doc);
Component* getEditorComponent();
Component& getSidebarComponent();
bool goToPreviousFile();
bool goToNextFile();
bool canGoToCounterpart() const;
bool goToCounterpart();
void saveProjectAsync();
void closeProject();
void openInSelectedIDE (bool saveFirst);
void showNewExporterMenu();
void showFilesPanel() { showProjectPanel (0); }
void showModulesPanel() { showProjectPanel (1); }
void showExportersPanel() { showProjectPanel (2); }
void showProjectSettings();
void showCurrentExporterSettings();
void showExporterSettings (const String& exporterName);
void showModule (const String& moduleID);
void showUserSettings();
void deleteSelectedTreeItems();
void refreshProjectTreeFileStatuses();
void updateMissingFileStatuses();
void addNewGUIFile();
void showBubbleMessage (Rectangle<int>, const String&);
StringArray getExportersWhichCanLaunch() const;
static void getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails&,
OwnedArray<Project::Item>& selectedNodes);
//==============================================================================
ApplicationCommandTarget* getNextCommandTarget() override;
void getAllCommands (Array<CommandID>&) override;
void getCommandInfo (CommandID, ApplicationCommandInfo&) override;
bool perform (const InvocationInfo&) override;
bool isSaveCommand (const CommandID id);
void paint (Graphics&) override;
void resized() override;
void childBoundsChanged (Component*) override;
void lookAndFeelChanged() override;
ProjectMessagesComponent& getProjectMessagesComponent() { return projectMessagesComponent; }
static String getProjectTabName() { return "Project"; }
private:
//==============================================================================
bool documentAboutToClose (OpenDocumentManager::Document*) override;
void changeListenerCallback (ChangeBroadcaster*) override;
void showTranslationTool();
//==============================================================================
void showProjectPanel (const int index);
bool canSelectedProjectBeLaunch();
//==============================================================================
Project* project = nullptr;
OpenDocumentManager::Document* currentDocument = nullptr;
RecentDocumentList recentDocumentList;
HeaderComponent headerComponent { this };
std::unique_ptr<Sidebar> sidebar;
ProjectMessagesComponent projectMessagesComponent;
ContentViewComponent contentViewComponent;
std::unique_ptr<ResizableEdgeComponent> resizerBar;
ComponentBoundsConstrainer sidebarSizeConstrainer;
std::unique_ptr<Component> translationTool;
BubbleMessageComponent bubbleMessage;
bool isForeground = false;
int lastViewedTab = 0;
std::unique_ptr<WizardHolder> wizardHolder;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectContentComponent)
};

View File

@ -0,0 +1,581 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../Application/jucer_CommonHeaders.h"
#include "../../Application/jucer_Application.h"
//==============================================================================
class MessagesPopupWindow : public Component,
private ComponentMovementWatcher
{
public:
MessagesPopupWindow (Component& target, Component& parent, Project& project)
: ComponentMovementWatcher (&parent),
targetComponent (target),
parentComponent (parent),
messagesListComponent (*this, project)
{
parentComponent.addAndMakeVisible (this);
setAlwaysOnTop (true);
addAndMakeVisible (viewport);
viewport.setScrollBarsShown (true, false);
viewport.setViewedComponent (&messagesListComponent, false);
viewport.setWantsKeyboardFocus (false);
setOpaque (true);
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
void resized() override
{
viewport.setBounds (getLocalBounds());
}
bool isListShowing() const
{
return messagesListComponent.getRequiredHeight() > 0;
}
void updateBounds (bool animate)
{
auto targetBounds = parentComponent.getLocalArea (&targetComponent, targetComponent.getLocalBounds());
auto height = jmin (messagesListComponent.getRequiredHeight(), maxHeight);
auto yPos = jmax (indent, targetBounds.getY() - height);
Rectangle<int> bounds (targetBounds.getX(), yPos,
jmin (width, parentComponent.getWidth() - targetBounds.getX() - indent), targetBounds.getY() - yPos);
auto& animator = Desktop::getInstance().getAnimator();
if (animate)
{
setBounds (bounds.withY (targetBounds.getY()));
animator.animateComponent (this, bounds, 1.0f, 150, false, 1.0, 1.0);
}
else
{
if (animator.isAnimating (this))
animator.cancelAnimation (this, false);
setBounds (bounds);
}
messagesListComponent.resized();
}
private:
//==============================================================================
class MessagesListComponent : public Component,
private ValueTree::Listener,
private AsyncUpdater
{
public:
MessagesListComponent (MessagesPopupWindow& o, Project& currentProject)
: owner (o),
project (currentProject)
{
messagesTree = project.getProjectMessages();
messagesTree.addListener (this);
setOpaque (true);
messagesChanged();
}
void resized() override
{
auto bounds = getLocalBounds();
auto numMessages = messages.size();
for (size_t i = 0; i < numMessages; ++i)
{
messages[i]->setBounds (bounds.removeFromTop (messageHeight));
if (numMessages > 1 && i != (numMessages - 1))
bounds.removeFromTop (messageSpacing);
}
}
void paint (Graphics& g) override
{
g.fillAll (findColour (backgroundColourId).contrasting (0.2f));
}
int getRequiredHeight() const
{
auto numMessages = (int) messages.size();
if (numMessages > 0)
return (numMessages * messageHeight) + ((numMessages - 1) * messageSpacing);
return 0;
}
void updateSize (int parentWidth)
{
setSize (parentWidth, getRequiredHeight());
}
private:
static constexpr int messageHeight = 65;
static constexpr int messageSpacing = 2;
//==============================================================================
struct MessageComponent : public Component
{
MessageComponent (MessagesListComponent& listComponent,
const Identifier& messageToDisplay,
std::vector<ProjectMessages::MessageAction> messageActions)
: message (messageToDisplay)
{
for (auto& action : messageActions)
{
auto button = std::make_unique<TextButton> (action.first);
addAndMakeVisible (*button);
button->onClick = action.second;
buttons.push_back (std::move (button));
}
icon = (ProjectMessages::getTypeForMessage (message) == ProjectMessages::Ids::warning ? getIcons().warning : getIcons().info);
messageTitleLabel.setText (ProjectMessages::getTitleForMessage (message), dontSendNotification);
messageTitleLabel.setFont (Font (11.0f).boldened());
addAndMakeVisible (messageTitleLabel);
messageDescriptionLabel.setText (ProjectMessages::getDescriptionForMessage (message), dontSendNotification);
messageDescriptionLabel.setFont (Font (11.0f));
messageDescriptionLabel.setJustificationType (Justification::topLeft);
addAndMakeVisible (messageDescriptionLabel);
dismissButton.setShape (getLookAndFeel().getCrossShape (1.0f), false, true, false);
addAndMakeVisible (dismissButton);
dismissButton.onClick = [this, &listComponent]
{
listComponent.messagesTree.getChildWithName (ProjectMessages::getTypeForMessage (message))
.getChildWithName (message)
.setProperty (ProjectMessages::Ids::isVisible, false, nullptr);
};
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId).contrasting (0.1f));
auto bounds = getLocalBounds().reduced (5);
g.setColour (findColour (defaultIconColourId));
g.fillPath (icon, icon.getTransformToScaleToFit (bounds.removeFromTop (messageTitleHeight)
.removeFromLeft (messageTitleHeight).toFloat(), true));
}
void resized() override
{
auto bounds = getLocalBounds().reduced (5);
auto topSlice = bounds.removeFromTop (messageTitleHeight);
topSlice.removeFromLeft (messageTitleHeight + 5);
topSlice.removeFromRight (5);
dismissButton.setBounds (topSlice.removeFromRight (messageTitleHeight));
messageTitleLabel.setBounds (topSlice);
bounds.removeFromTop (5);
auto numButtons = (int) buttons.size();
if (numButtons > 0)
{
auto buttonBounds = bounds.removeFromBottom (buttonHeight);
auto buttonWidth = roundToInt ((float) buttonBounds.getWidth() / 3.5f);
auto requiredWidth = (numButtons * buttonWidth) + ((numButtons - 1) * buttonSpacing);
buttonBounds.reduce ((buttonBounds.getWidth() - requiredWidth) / 2, 0);
for (auto& b : buttons)
{
b->setBounds (buttonBounds.removeFromLeft (buttonWidth));
buttonBounds.removeFromLeft (buttonSpacing);
}
bounds.removeFromBottom (5);
}
messageDescriptionLabel.setBounds (bounds);
}
static constexpr int messageTitleHeight = 11;
static constexpr int buttonHeight = messageHeight / 4;
static constexpr int buttonSpacing = 5;
Identifier message;
Path icon;
Label messageTitleLabel, messageDescriptionLabel;
std::vector<std::unique_ptr<TextButton>> buttons;
ShapeButton dismissButton { {},
findColour (treeIconColourId),
findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.2f)),
findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.4f)) };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageComponent)
};
//==============================================================================
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { messagesChanged(); }
void valueTreeChildAdded (ValueTree&, ValueTree&) override { messagesChanged(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { messagesChanged(); }
void valueTreeChildOrderChanged (ValueTree&, int, int) override { messagesChanged(); }
void valueTreeParentChanged (ValueTree&) override { messagesChanged(); }
void valueTreeRedirected (ValueTree&) override { messagesChanged(); }
void handleAsyncUpdate() override
{
messagesChanged();
}
void messagesChanged()
{
auto listWasShowing = (getHeight() > 0);
auto warningsTree = messagesTree.getChildWithName (ProjectMessages::Ids::warning);
auto notificationsTree = messagesTree.getChildWithName (ProjectMessages::Ids::notification);
auto removePredicate = [warningsTree, notificationsTree] (std::unique_ptr<MessageComponent>& messageComponent)
{
for (int i = 0; i < warningsTree.getNumChildren(); ++i)
{
auto child = warningsTree.getChild (i);
if (child.getType() == messageComponent->message
&& child.getProperty (ProjectMessages::Ids::isVisible))
{
return false;
}
}
for (int i = 0; i < notificationsTree.getNumChildren(); ++i)
{
auto child = notificationsTree.getChild (i);
if (child.getType() == messageComponent->message
&& child.getProperty (ProjectMessages::Ids::isVisible))
{
return false;
}
}
return true;
};
messages.erase (std::remove_if (std::begin (messages), std::end (messages), removePredicate),
std::end (messages));
for (int i = 0; i < warningsTree.getNumChildren(); ++i)
{
auto child = warningsTree.getChild (i);
if (! child.getProperty (ProjectMessages::Ids::isVisible))
continue;
if (std::find_if (std::begin (messages), std::end (messages),
[child] (const std::unique_ptr<MessageComponent>& messageComponent) { return messageComponent->message == child.getType(); })
== std::end (messages))
{
messages.push_back (std::make_unique<MessageComponent> (*this, child.getType(), project.getMessageActions (child.getType())));
addAndMakeVisible (*messages.back());
}
}
for (int i = 0; i < notificationsTree.getNumChildren(); ++i)
{
auto child = notificationsTree.getChild (i);
if (! child.getProperty (ProjectMessages::Ids::isVisible))
continue;
if (std::find_if (std::begin (messages), std::end (messages),
[child] (const std::unique_ptr<MessageComponent>& messageComponent) { return messageComponent->message == child.getType(); })
== std::end (messages))
{
messages.push_back (std::make_unique<MessageComponent> (*this, child.getType(), project.getMessageActions (child.getType())));
addAndMakeVisible (*messages.back());
}
}
auto isNowShowing = (messages.size() > 0);
owner.updateBounds (isNowShowing != listWasShowing);
updateSize (owner.getWidth());
}
//==============================================================================
MessagesPopupWindow& owner;
Project& project;
ValueTree messagesTree;
std::vector<std::unique_ptr<MessageComponent>> messages;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessagesListComponent)
};
//==============================================================================
void componentMovedOrResized (bool, bool) override
{
if (isListShowing())
updateBounds (false);
}
using ComponentMovementWatcher::componentMovedOrResized;
void componentPeerChanged() override
{
if (isListShowing())
updateBounds (false);
}
void componentVisibilityChanged() override
{
if (isListShowing())
updateBounds (false);
}
using ComponentMovementWatcher::componentVisibilityChanged;
//==============================================================================
static constexpr int maxHeight = 500, width = 350, indent = 20;
Component& targetComponent;
Component& parentComponent;
Viewport viewport;
MessagesListComponent messagesListComponent;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessagesPopupWindow)
};
//==============================================================================
class ProjectMessagesComponent : public Component
{
public:
ProjectMessagesComponent()
{
setFocusContainerType (FocusContainerType::focusContainer);
setTitle ("Project Messages");
addAndMakeVisible (warningsComponent);
addAndMakeVisible (notificationsComponent);
warningsComponent.addMouseListener (this, true);
notificationsComponent.addMouseListener (this, true);
setOpaque (true);
}
//==============================================================================
void resized() override
{
auto b = getLocalBounds();
warningsComponent.setBounds (b.removeFromLeft (b.getWidth() / 2).reduced (5));
notificationsComponent.setBounds (b.reduced (5));
}
void paint (Graphics& g) override
{
auto backgroundColour = findColour (backgroundColourId);
if (isMouseDown || isMouseOver)
backgroundColour = backgroundColour.overlaidWith (findColour (defaultHighlightColourId)
.withAlpha (isMouseDown ? 1.0f : 0.8f));
g.fillAll (backgroundColour);
}
//==============================================================================
void mouseEnter (const MouseEvent&) override
{
isMouseOver = true;
repaint();
}
void mouseExit (const MouseEvent&) override
{
isMouseOver = false;
repaint();
}
void mouseDown (const MouseEvent&) override
{
isMouseDown = true;
repaint();
}
void mouseUp (const MouseEvent&) override
{
isMouseDown = false;
repaint();
showOrHideMessagesWindow();
}
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return std::make_unique<AccessibilityHandler> (*this,
AccessibilityRole::button,
AccessibilityActions().addAction (AccessibilityActionType::press,
[this] { showOrHideMessagesWindow(); }));
}
//==============================================================================
void setProject (Project* newProject)
{
if (currentProject != newProject)
{
currentProject = newProject;
if (currentProject != nullptr)
{
if (auto* projectWindow = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (currentProject->getFile()))
messagesWindow = std::make_unique<MessagesPopupWindow> (*this, *projectWindow, *currentProject);
auto projectMessagesTree = currentProject->getProjectMessages();
warningsComponent.setTree (projectMessagesTree.getChildWithName (ProjectMessages::Ids::warning));
notificationsComponent.setTree (projectMessagesTree.getChildWithName (ProjectMessages::Ids::notification));
}
else
{
warningsComponent.setTree ({});
notificationsComponent.setTree ({});
}
}
}
void numMessagesChanged()
{
const auto total = warningsComponent.getNumMessages()
+ notificationsComponent.getNumMessages();
setHelpText (String (total) + (total == 1 ? " message" : " messages"));
}
void showOrHideMessagesWindow()
{
if (messagesWindow != nullptr)
showOrHideAllMessages (! messagesWindow->isListShowing());
}
private:
//==============================================================================
struct MessageCountComponent : public Component,
private ValueTree::Listener
{
MessageCountComponent (ProjectMessagesComponent& o, Path pathToUse)
: owner (o),
path (pathToUse)
{
setInterceptsMouseClicks (false, false);
}
void paint (Graphics& g) override
{
auto b = getLocalBounds().toFloat();
g.setColour (findColour ((owner.isMouseDown || owner.isMouseOver) ? defaultHighlightedTextColourId : treeIconColourId));
g.fillPath (path, path.getTransformToScaleToFit (b.removeFromLeft (b.getWidth() / 2.0f), true));
b.removeFromLeft (5);
g.drawFittedText (String (numMessages), b.getSmallestIntegerContainer(), Justification::centredLeft, 1);
}
void setTree (ValueTree tree)
{
messagesTree = tree;
if (messagesTree.isValid())
messagesTree.addListener (this);
updateNumMessages();
}
void updateNumMessages()
{
numMessages = messagesTree.getNumChildren();
owner.numMessagesChanged();
repaint();
}
int getNumMessages() const noexcept { return numMessages; }
private:
void valueTreeChildAdded (ValueTree&, ValueTree&) override { updateNumMessages(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { updateNumMessages(); }
ProjectMessagesComponent& owner;
ValueTree messagesTree;
Path path;
int numMessages = 0;
};
void showOrHideAllMessages (bool shouldBeVisible)
{
if (currentProject != nullptr)
{
auto messagesTree = currentProject->getProjectMessages();
auto setVisible = [shouldBeVisible] (ValueTree subTree)
{
for (int i = 0; i < subTree.getNumChildren(); ++i)
subTree.getChild (i).setProperty (ProjectMessages::Ids::isVisible, shouldBeVisible, nullptr);
};
setVisible (messagesTree.getChildWithName (ProjectMessages::Ids::warning));
setVisible (messagesTree.getChildWithName (ProjectMessages::Ids::notification));
}
}
//==============================================================================
Project* currentProject = nullptr;
bool isMouseOver = false, isMouseDown = false;
MessageCountComponent warningsComponent { *this, getIcons().warning },
notificationsComponent { *this, getIcons().info };
std::unique_ptr<MessagesPopupWindow> messagesWindow;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectMessagesComponent)
};

View File

@ -0,0 +1,171 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../Application/jucer_Application.h"
//==============================================================================
class UserAvatarComponent : public Component,
public SettableTooltipClient,
public ChangeBroadcaster,
private LicenseController::LicenseStateListener
{
public:
UserAvatarComponent (bool isInteractive)
: interactive (isInteractive)
{
ProjucerApplication::getApp().getLicenseController().addListener (this);
lookAndFeelChanged();
}
~UserAvatarComponent() override
{
ProjucerApplication::getApp().getLicenseController().removeListener (this);
}
void paint (Graphics& g) override
{
auto bounds = getLocalBounds();
if (! isGPL)
{
bounds = bounds.removeFromRight (bounds.getHeight());
Path ellipse;
ellipse.addEllipse (bounds.toFloat());
g.reduceClipRegion (ellipse);
}
g.drawImage (currentAvatar, bounds.toFloat(), RectanglePlacement::fillDestination);
}
void mouseUp (const MouseEvent&) override
{
triggerClick();
}
void triggerClick()
{
if (interactive)
{
PopupMenu menu;
menu.addCommandItem (ProjucerApplication::getApp().commandManager.get(), CommandIDs::loginLogout);
menu.showMenuAsync (PopupMenu::Options().withTargetComponent (this));
}
}
bool isDisplaingGPLLogo() const noexcept { return isGPL; }
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return interactive ? std::make_unique<AccessibilityHandler> (*this,
AccessibilityRole::button,
AccessibilityActions().addAction (AccessibilityActionType::press,
[this] { triggerClick(); }))
: nullptr;
}
private:
//==============================================================================
static Image createGPLAvatarImage()
{
if (auto logo = Drawable::createFromImageData (BinaryData::gpl_logo_svg, BinaryData::gpl_logo_svgSize))
{
auto bounds = logo->getDrawableBounds();
Image image (Image::ARGB, roundToInt (bounds.getWidth()), roundToInt (bounds.getHeight()), true);
Graphics g (image);
logo->draw (g, 1.0f);
return image;
}
jassertfalse;
return {};
}
Image createStandardAvatarImage()
{
Image image (Image::ARGB, 250, 250, true);
Graphics g (image);
g.setColour (findColour (defaultButtonBackgroundColourId));
g.fillAll();
g.setColour (findColour (defaultIconColourId));
auto path = getIcons().user;
g.fillPath (path, RectanglePlacement (RectanglePlacement::centred)
.getTransformToFit (path.getBounds(), image.getBounds().reduced (image.getHeight() / 5).toFloat()));
return image;
}
//==============================================================================
void licenseStateChanged() override
{
auto state = ProjucerApplication::getApp().getLicenseController().getCurrentState();
isGPL = ProjucerApplication::getApp().getLicenseController().getCurrentState().isGPL();
if (interactive)
{
auto formattedUserString = [state]() -> String
{
if (state.isSignedIn())
return (state.isGPL() ? "" : (state.username + " - ")) + state.getLicenseTypeString();
return "Not logged in";
}();
setTooltip (formattedUserString);
}
currentAvatar = isGPL ? gplAvatarImage
: state.isSignedIn() ? standardAvatarImage : signedOutAvatarImage;
repaint();
sendChangeMessage();
}
void lookAndFeelChanged() override
{
standardAvatarImage = createStandardAvatarImage();
signedOutAvatarImage = createStandardAvatarImage();
if (interactive)
signedOutAvatarImage.multiplyAllAlphas (0.4f);
licenseStateChanged();
repaint();
}
//==============================================================================
Image standardAvatarImage, signedOutAvatarImage, gplAvatarImage { createGPLAvatarImage() }, currentAvatar;
bool isGPL = false, interactive = false;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,635 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../Application/UserAccount/jucer_LicenseController.h"
#include "Modules/jucer_AvailableModulesList.h"
class ProjectExporter;
class LibraryModule;
class EnabledModulesList;
class ProjectSaver;
namespace ProjectMessages
{
namespace Ids
{
#define DECLARE_ID(name) static const Identifier name (#name)
DECLARE_ID (projectMessages);
DECLARE_ID (incompatibleLicense);
DECLARE_ID (cppStandard);
DECLARE_ID (moduleNotFound);
DECLARE_ID (jucePath);
DECLARE_ID (jucerFileModified);
DECLARE_ID (missingModuleDependencies);
DECLARE_ID (oldProjucer);
DECLARE_ID (cLion);
DECLARE_ID (newVersionAvailable);
DECLARE_ID (notification);
DECLARE_ID (warning);
DECLARE_ID (isVisible);
#undef DECLARE_ID
}
inline Identifier getTypeForMessage (const Identifier& message)
{
static Identifier warnings[] = { Ids::incompatibleLicense, Ids::cppStandard, Ids::moduleNotFound,
Ids::jucePath, Ids::jucerFileModified, Ids::missingModuleDependencies,
Ids::oldProjucer, Ids::cLion };
if (std::find (std::begin (warnings), std::end (warnings), message) != std::end (warnings))
return Ids::warning;
if (message == Ids::newVersionAvailable)
return Ids::notification;
jassertfalse;
return {};
}
inline String getTitleForMessage (const Identifier& message)
{
if (message == Ids::incompatibleLicense) return "Incompatible License and Splash Screen Setting";
if (message == Ids::cppStandard) return "C++ Standard";
if (message == Ids::moduleNotFound) return "Module Not Found";
if (message == Ids::jucePath) return "JUCE Path";
if (message == Ids::jucerFileModified) return "Project File Modified";
if (message == Ids::missingModuleDependencies) return "Missing Module Dependencies";
if (message == Ids::oldProjucer) return "Projucer Out of Date";
if (message == Ids::newVersionAvailable) return "New Version Available";
if (message == Ids::cLion) return "Deprecated Exporter";
jassertfalse;
return {};
}
inline String getDescriptionForMessage (const Identifier& message)
{
if (message == Ids::incompatibleLicense) return "Save and export is disabled.";
if (message == Ids::cppStandard) return "Module(s) have a higher C++ standard requirement than the project.";
if (message == Ids::moduleNotFound) return "Module(s) could not be found at the specified paths.";
if (message == Ids::jucePath) return "The path to your JUCE folder is incorrect.";
if (message == Ids::jucerFileModified) return "The .jucer file has been modified since the last save.";
if (message == Ids::missingModuleDependencies) return "Module(s) have missing dependencies.";
if (message == Ids::oldProjucer) return "The version of the Projucer you are using is out of date.";
if (message == Ids::newVersionAvailable) return "A new version of JUCE is available to download.";
if (message == Ids::cLion) return "The CLion exporter is deprecated. Use JUCE's CMake support instead.";
jassertfalse;
return {};
}
using MessageAction = std::pair<String, std::function<void()>>;
}
enum class Async { no, yes };
//==============================================================================
class Project : public FileBasedDocument,
private ValueTree::Listener,
private LicenseController::LicenseStateListener,
private ChangeListener,
private AvailableModulesList::Listener
{
public:
//==============================================================================
Project (const File&);
~Project() override;
//==============================================================================
String getDocumentTitle() override;
Result loadDocument (const File& file) override;
Result saveDocument (const File& file) override;
void saveDocumentAsync (const File& file, std::function<void (Result)> callback) override;
void saveProject (Async, ProjectExporter* exporterToSave, std::function<void (Result)> onCompletion);
Result saveResourcesOnly();
void openProjectInIDE (ProjectExporter& exporterToOpen);
File getLastDocumentOpened() override;
void setLastDocumentOpened (const File& file) override;
void setTitle (const String& newTitle);
//==============================================================================
File getProjectFolder() const { return getFile().getParentDirectory(); }
File getGeneratedCodeFolder() const { return getFile().getSiblingFile ("JuceLibraryCode"); }
File getSourceFilesFolder() const { return getProjectFolder().getChildFile ("Source"); }
File getLocalModulesFolder() const { return getGeneratedCodeFolder().getChildFile ("modules"); }
File getLocalModuleFolder (const String& moduleID) const { return getLocalModulesFolder().getChildFile (moduleID); }
File getAppIncludeFile() const { return getGeneratedCodeFolder().getChildFile (getJuceSourceHFilename()); }
File getBinaryDataCppFile (int index) const;
File getBinaryDataHeaderFile() const { return getBinaryDataCppFile (0).withFileExtension (".h"); }
static String getAppConfigFilename() { return "AppConfig.h"; }
static String getPluginDefinesFilename() { return "JucePluginDefines.h"; }
static String getJuceSourceHFilename() { return "JuceHeader.h"; }
//==============================================================================
template <class FileType>
bool shouldBeAddedToBinaryResourcesByDefault (const FileType& file)
{
return ! file.hasFileExtension (sourceOrHeaderFileExtensions);
}
File resolveFilename (String filename) const;
String getRelativePathForFile (const File& file) const;
//==============================================================================
// Creates editors for the project settings
void createPropertyEditors (PropertyListBuilder&);
//==============================================================================
ValueTree getProjectRoot() const { return projectRoot; }
Value getProjectValue (const Identifier& name) { return projectRoot.getPropertyAsValue (name, getUndoManagerFor (projectRoot)); }
var getProjectVar (const Identifier& name) const { return projectRoot.getProperty (name); }
const build_tools::ProjectType& getProjectType() const;
String getProjectTypeString() const { return projectTypeValue.get(); }
void setProjectType (const String& newProjectType) { projectTypeValue = newProjectType; }
String getProjectNameString() const { return projectNameValue.get(); }
String getProjectFilenameRootString() { return File::createLegalFileName (getDocumentTitle()); }
String getProjectUIDString() const { return projectUIDValue.get(); }
String getProjectLineFeed() const { return projectLineFeedValue.get(); }
String getVersionString() const { return versionValue.get(); }
String getVersionAsHex() const { return build_tools::getVersionAsHex (getVersionString()); }
int getVersionAsHexInteger() const { return build_tools::getVersionAsHexInteger (getVersionString()); }
void setProjectVersion (const String& newVersion) { versionValue = newVersion; }
String getBundleIdentifierString() const { return bundleIdentifierValue.get(); }
String getDefaultBundleIdentifierString() const;
String getDefaultAAXIdentifierString() const { return getDefaultBundleIdentifierString(); }
String getDefaultPluginManufacturerString() const;
String getCompanyNameString() const { return companyNameValue.get(); }
String getCompanyCopyrightString() const { return companyCopyrightValue.get(); }
String getCompanyWebsiteString() const { return companyWebsiteValue.get(); }
String getCompanyEmailString() const { return companyEmailValue.get(); }
String getHeaderSearchPathsString() const { return headerSearchPathsValue.get(); }
StringPairArray getPreprocessorDefs() const { return parsedPreprocessorDefs; }
int getMaxBinaryFileSize() const { return maxBinaryFileSizeValue.get(); }
bool shouldIncludeBinaryInJuceHeader() const { return includeBinaryDataInJuceHeaderValue.get(); }
String getBinaryDataNamespaceString() const { return binaryDataNamespaceValue.get(); }
bool shouldDisplaySplashScreen() const { return displaySplashScreenValue.get(); }
String getSplashScreenColourString() const { return splashScreenColourValue.get(); }
static StringArray getCppStandardStrings() { return { "C++14", "C++17", "C++20", "Use Latest" }; }
static Array<var> getCppStandardVars() { return { "14", "17", "20", "latest" }; }
static String getLatestNumberedCppStandardString()
{
auto cppStandardVars = getCppStandardVars();
return cppStandardVars[cppStandardVars.size() - 2];
}
String getCppStandardString() const { return cppStandardValue.get(); }
StringArray getCompilerFlagSchemes() const;
void addCompilerFlagScheme (const String&);
void removeCompilerFlagScheme (const String&);
String getPostExportShellCommandPosixString() const { return postExportShellCommandPosixValue.get(); }
String getPostExportShellCommandWinString() const { return postExportShellCommandWinValue.get(); }
bool shouldUseAppConfig() const { return useAppConfigValue.get(); }
bool shouldAddUsingNamespaceToJuceHeader() const { return addUsingNamespaceToJuceHeader.get(); }
//==============================================================================
String getPluginNameString() const { return pluginNameValue.get(); }
String getPluginDescriptionString() const { return pluginDescriptionValue.get();}
String getPluginManufacturerString() const { return pluginManufacturerValue.get(); }
String getPluginManufacturerCodeString() const { return pluginManufacturerCodeValue.get(); }
String getPluginCodeString() const { return pluginCodeValue.get(); }
String getPluginChannelConfigsString() const { return pluginChannelConfigsValue.get(); }
String getAAXIdentifierString() const { return pluginAAXIdentifierValue.get(); }
String getPluginAUExportPrefixString() const { return pluginAUExportPrefixValue.get(); }
String getVSTNumMIDIInputsString() const { return pluginVSTNumMidiInputsValue.get(); }
String getVSTNumMIDIOutputsString() const { return pluginVSTNumMidiOutputsValue.get(); }
static bool checkMultiChoiceVar (const ValueWithDefault& valueToCheck, Identifier idToCheck) noexcept
{
if (! valueToCheck.get().isArray())
return false;
auto v = valueToCheck.get();
if (auto* varArray = v.getArray())
return varArray->contains (idToCheck.toString());
return false;
}
bool isAudioPluginProject() const { return getProjectType().isAudioPlugin(); }
bool shouldBuildVST() const { return isAudioPluginProject() && checkMultiChoiceVar (pluginFormatsValue, Ids::buildVST); }
bool shouldBuildVST3() const { return isAudioPluginProject() && checkMultiChoiceVar (pluginFormatsValue, Ids::buildVST3); }
bool shouldBuildAU() const { return isAudioPluginProject() && checkMultiChoiceVar (pluginFormatsValue, Ids::buildAU); }
bool shouldBuildAUv3() const { return isAudioPluginProject() && checkMultiChoiceVar (pluginFormatsValue, Ids::buildAUv3); }
bool shouldBuildRTAS() const { return isAudioPluginProject() && checkMultiChoiceVar (pluginFormatsValue, Ids::buildRTAS); }
bool shouldBuildAAX() const { return isAudioPluginProject() && checkMultiChoiceVar (pluginFormatsValue, Ids::buildAAX); }
bool shouldBuildStandalonePlugin() const { return isAudioPluginProject() && checkMultiChoiceVar (pluginFormatsValue, Ids::buildStandalone); }
bool shouldBuildUnityPlugin() const { return isAudioPluginProject() && checkMultiChoiceVar (pluginFormatsValue, Ids::buildUnity); }
bool shouldEnableIAA() const { return isAudioPluginProject() && checkMultiChoiceVar (pluginFormatsValue, Ids::enableIAA); }
bool isPluginSynth() const { return checkMultiChoiceVar (pluginCharacteristicsValue, Ids::pluginIsSynth); }
bool pluginWantsMidiInput() const { return checkMultiChoiceVar (pluginCharacteristicsValue, Ids::pluginWantsMidiIn); }
bool pluginProducesMidiOutput() const { return checkMultiChoiceVar (pluginCharacteristicsValue, Ids::pluginProducesMidiOut); }
bool isPluginMidiEffect() const { return checkMultiChoiceVar (pluginCharacteristicsValue, Ids::pluginIsMidiEffectPlugin); }
bool pluginEditorNeedsKeyFocus() const { return checkMultiChoiceVar (pluginCharacteristicsValue, Ids::pluginEditorRequiresKeys); }
bool isPluginRTASBypassDisabled() const { return checkMultiChoiceVar (pluginCharacteristicsValue, Ids::pluginRTASDisableBypass); }
bool isPluginRTASMultiMonoDisabled() const { return checkMultiChoiceVar (pluginCharacteristicsValue, Ids::pluginRTASDisableMultiMono); }
bool isPluginAAXBypassDisabled() const { return checkMultiChoiceVar (pluginCharacteristicsValue, Ids::pluginAAXDisableBypass); }
bool isPluginAAXMultiMonoDisabled() const { return checkMultiChoiceVar (pluginCharacteristicsValue, Ids::pluginAAXDisableMultiMono); }
static StringArray getAllAUMainTypeStrings() noexcept;
static Array<var> getAllAUMainTypeVars() noexcept;
Array<var> getDefaultAUMainTypes() const noexcept;
static StringArray getAllVSTCategoryStrings() noexcept;
Array<var> getDefaultVSTCategories() const noexcept;
static StringArray getAllVST3CategoryStrings() noexcept;
Array<var> getDefaultVST3Categories() const noexcept;
static StringArray getAllAAXCategoryStrings() noexcept;
static Array<var> getAllAAXCategoryVars() noexcept;
Array<var> getDefaultAAXCategories() const noexcept;
static StringArray getAllRTASCategoryStrings() noexcept;
static Array<var> getAllRTASCategoryVars() noexcept;
Array<var> getDefaultRTASCategories() const noexcept;
String getAUMainTypeString() const noexcept;
bool isAUSandBoxSafe() const noexcept;
String getVSTCategoryString() const noexcept;
String getVST3CategoryString() const noexcept;
int getAAXCategory() const noexcept;
int getRTASCategory() const noexcept;
String getIAATypeCode() const;
String getIAAPluginName() const;
String getUnityScriptName() const { return addUnityPluginPrefixIfNecessary (getProjectNameString()) + "_UnityScript.cs"; }
static String addUnityPluginPrefixIfNecessary (const String& name)
{
if (! name.startsWithIgnoreCase ("audioplugin"))
return "audioplugin_" + name;
return name;
}
//==============================================================================
bool isAUPluginHost();
bool isVSTPluginHost();
bool isVST3PluginHost();
//==============================================================================
bool shouldBuildTargetType (build_tools::ProjectType::Target::Type targetType) const noexcept;
static build_tools::ProjectType::Target::Type getTargetTypeFromFilePath (const File& file, bool returnSharedTargetIfNoValidSuffix);
//==============================================================================
void updateDeprecatedProjectSettingsInteractively();
StringPairArray getAppConfigDefs();
StringPairArray getAudioPluginFlags() const;
//==============================================================================
class Item
{
public:
//==============================================================================
Item (Project& project, const ValueTree& itemNode, bool isModuleCode);
Item (const Item& other);
static Item createGroup (Project& project, const String& name, const String& uid, bool isModuleCode);
void initialiseMissingProperties();
//==============================================================================
bool isValid() const { return state.isValid(); }
bool operator== (const Item& other) const { return state == other.state && &project == &other.project; }
bool operator!= (const Item& other) const { return ! operator== (other); }
//==============================================================================
bool isFile() const;
bool isGroup() const;
bool isMainGroup() const;
bool isImageFile() const;
bool isSourceFile() const;
String getID() const;
void setID (const String& newID);
Item findItemWithID (const String& targetId) const; // (recursive search)
String getImageFileID() const;
std::unique_ptr<Drawable> loadAsImageFile() const;
//==============================================================================
Value getNameValue();
String getName() const;
File getFile() const;
String getFilePath() const;
void setFile (const File& file);
void setFile (const build_tools::RelativePath& file);
File determineGroupFolder() const;
bool renameFile (const File& newFile);
bool shouldBeAddedToTargetProject() const;
bool shouldBeAddedToTargetExporter (const ProjectExporter&) const;
bool shouldBeCompiled() const;
Value getShouldCompileValue();
bool shouldBeAddedToBinaryResources() const;
Value getShouldAddToBinaryResourcesValue();
bool shouldBeAddedToXcodeResources() const;
Value getShouldAddToXcodeResourcesValue();
Value getShouldInhibitWarningsValue();
bool shouldInhibitWarnings() const;
bool isModuleCode() const;
Value getShouldSkipPCHValue();
bool shouldSkipPCH() const;
Value getCompilerFlagSchemeValue();
String getCompilerFlagSchemeString() const;
void setCompilerFlagScheme (const String&);
void clearCurrentCompilerFlagScheme();
//==============================================================================
bool canContain (const Item& child) const;
int getNumChildren() const { return state.getNumChildren(); }
Item getChild (int index) const { return Item (project, state.getChild (index), belongsToModule); }
Item addNewSubGroup (const String& name, int insertIndex);
Item getOrCreateSubGroup (const String& name);
void addChild (const Item& newChild, int insertIndex);
bool addFileAtIndex (const File& file, int insertIndex, bool shouldCompile);
bool addFileRetainingSortOrder (const File& file, bool shouldCompile);
void addFileUnchecked (const File& file, int insertIndex, bool shouldCompile);
bool addRelativeFile (const build_tools::RelativePath& file, int insertIndex, bool shouldCompile);
void removeItemFromProject();
void sortAlphabetically (bool keepGroupsAtStart, bool recursive);
Item findItemForFile (const File& file) const;
bool containsChildForFile (const build_tools::RelativePath& file) const;
Item getParent() const;
Item createCopy();
UndoManager* getUndoManager() const { return project.getUndoManagerFor (state); }
Icon getIcon (bool isOpen = false) const;
bool isIconCrossedOut() const;
bool needsSaving() const noexcept;
Project& project;
ValueTree state;
private:
Item& operator= (const Item&);
bool belongsToModule;
};
Item getMainGroup();
void findAllImageItems (OwnedArray<Item>& items);
//==============================================================================
ValueTree getExporters();
int getNumExporters();
std::unique_ptr<ProjectExporter> createExporter (int index);
void addNewExporter (const Identifier& exporterIdentifier);
void createExporterForCurrentPlatform();
struct ExporterIterator
{
ExporterIterator (Project& project);
bool next();
ProjectExporter& operator*() const { return *exporter; }
ProjectExporter* operator->() const { return exporter.get(); }
std::unique_ptr<ProjectExporter> exporter;
int index;
private:
Project& project;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterIterator)
};
//==============================================================================
struct ConfigFlag
{
String symbol, description, sourceModuleID;
ValueWithDefault value;
};
ValueWithDefault getConfigFlag (const String& name);
bool isConfigFlagEnabled (const String& name, bool defaultIsEnabled = false) const;
//==============================================================================
EnabledModulesList& getEnabledModules();
AvailableModulesList& getExporterPathsModulesList() { return exporterPathsModulesList; }
void rescanExporterPathModules (bool async = false);
std::pair<String, File> getModuleWithID (const String&);
//==============================================================================
PropertiesFile& getStoredProperties() const;
//==============================================================================
UndoManager* getUndoManagerFor (const ValueTree&) const { return nullptr; }
UndoManager* getUndoManager() const { return nullptr; }
//==============================================================================
static const char* projectFileExtension;
//==============================================================================
bool updateCachedFileState();
String getCachedFileStateContent() const noexcept { return cachedFileState.second; }
String serialiseProjectXml (std::unique_ptr<XmlElement>) const;
//==============================================================================
String getUniqueTargetFolderSuffixForExporter (const Identifier& exporterIdentifier, const String& baseTargetFolder);
//==============================================================================
bool isCurrentlySaving() const noexcept { return saver != nullptr; }
bool isTemporaryProject() const noexcept { return tempDirectory != File(); }
File getTemporaryDirectory() const noexcept { return tempDirectory; }
void setTemporaryDirectory (const File&) noexcept;
//==============================================================================
ValueTree getProjectMessages() const { return projectMessages; }
void addProjectMessage (const Identifier& messageToAdd, std::vector<ProjectMessages::MessageAction>&& messageActions);
void removeProjectMessage (const Identifier& messageToRemove);
std::vector<ProjectMessages::MessageAction> getMessageActions (const Identifier& message);
//==============================================================================
bool hasIncompatibleLicenseTypeAndSplashScreenSetting() const;
bool isFileModificationCheckPending() const;
bool isSaveAndExportDisabled() const;
private:
//==============================================================================
void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
void valueTreeChildAdded (ValueTree&, ValueTree&) override;
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override;
void valueTreeChildOrderChanged (ValueTree&, int, int) override;
//==============================================================================
struct ProjectFileModificationPoller : private Timer
{
ProjectFileModificationPoller (Project& p);
bool isCheckPending() const noexcept { return pending; }
private:
void timerCallback() override;
void reset();
void resaveProject();
void reloadProjectFromDisk();
Project& project;
bool pending = false;
};
//==============================================================================
ValueTree projectRoot { Ids::JUCERPROJECT };
ValueWithDefault projectNameValue, projectUIDValue, projectLineFeedValue, projectTypeValue, versionValue, bundleIdentifierValue, companyNameValue,
companyCopyrightValue, companyWebsiteValue, companyEmailValue, displaySplashScreenValue, splashScreenColourValue, cppStandardValue,
headerSearchPathsValue, preprocessorDefsValue, userNotesValue, maxBinaryFileSizeValue, includeBinaryDataInJuceHeaderValue, binaryDataNamespaceValue,
compilerFlagSchemesValue, postExportShellCommandPosixValue, postExportShellCommandWinValue, useAppConfigValue, addUsingNamespaceToJuceHeader;
ValueWithDefault pluginFormatsValue, pluginNameValue, pluginDescriptionValue, pluginManufacturerValue, pluginManufacturerCodeValue,
pluginCodeValue, pluginChannelConfigsValue, pluginCharacteristicsValue, pluginAUExportPrefixValue, pluginAAXIdentifierValue,
pluginAUMainTypeValue, pluginAUSandboxSafeValue, pluginRTASCategoryValue, pluginVSTCategoryValue, pluginVST3CategoryValue, pluginAAXCategoryValue,
pluginVSTNumMidiInputsValue, pluginVSTNumMidiOutputsValue;
//==============================================================================
std::unique_ptr<EnabledModulesList> enabledModulesList;
AvailableModulesList exporterPathsModulesList;
//==============================================================================
void updateDeprecatedProjectSettings();
//==============================================================================
bool shouldWriteLegacyPluginFormatSettings = false;
bool shouldWriteLegacyPluginCharacteristicsSettings = false;
static Array<Identifier> getLegacyPluginFormatIdentifiers() noexcept;
static Array<Identifier> getLegacyPluginCharacteristicsIdentifiers() noexcept;
void writeLegacyPluginFormatSettings();
void writeLegacyPluginCharacteristicsSettings();
void coalescePluginFormatValues();
void coalescePluginCharacteristicsValues();
void updatePluginCategories();
//==============================================================================
File tempDirectory;
std::pair<Time, String> cachedFileState;
void saveAndMoveTemporaryProject (bool openInIDE);
//==============================================================================
friend class Item;
StringPairArray parsedPreprocessorDefs;
//==============================================================================
void initialiseProjectValues();
void initialiseMainGroup();
void initialiseAudioPluginValues();
bool setCppVersionFromOldExporterSettings();
void createAudioPluginPropertyEditors (PropertyListBuilder& props);
//==============================================================================
void updateTitleDependencies();
void updateCompanyNameDependencies();
void updateProjectSettings();
ValueTree getConfigurations() const;
ValueTree getConfigNode();
void updateOldStyleConfigList();
void moveOldPropertyFromProjectToAllExporters (Identifier name);
void removeDefunctExporters();
void updateOldModulePaths();
//==============================================================================
void licenseStateChanged() override;
void changeListenerCallback (ChangeBroadcaster*) override;
void availableModulesChanged (AvailableModulesList*) override;
void updateLicenseWarning();
void updateJUCEPathWarning();
void updateModuleWarnings();
void updateExporterWarnings();
void updateCppStandardWarning (bool showWarning);
void updateMissingModuleDependenciesWarning (bool showWarning);
void updateOldProjucerWarning (bool showWarning);
void updateCLionWarning (bool showWarning);
void updateModuleNotFoundWarning (bool showWarning);
ValueTree projectMessages { ProjectMessages::Ids::projectMessages, {},
{ { ProjectMessages::Ids::notification, {} }, { ProjectMessages::Ids::warning, {} } } };
std::map<Identifier, std::vector<ProjectMessages::MessageAction>> messageActions;
ProjectFileModificationPoller fileModificationPoller { *this };
std::unique_ptr<FileChooser> chooser;
std::unique_ptr<ProjectSaver> saver;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Project)
JUCE_DECLARE_WEAK_REFERENCEABLE (Project)
};