mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-22 01:34:21 +00:00
Added several new features: support for AudioUnit v3, new simplified JUCE module format, deleted the Introjucer and replaced it by the Projucer, various improvements for exporting of iOS and Android projects.
This commit is contained in:
parent
9eb54629f2
commit
70949aa0c6
1979 changed files with 130149 additions and 129257 deletions
|
|
@ -0,0 +1,899 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2015 - ROLI Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "../jucer_Headers.h"
|
||||
#include "../Application/jucer_Application.h"
|
||||
#include "../Project Saving/jucer_ProjectExporter.h"
|
||||
#include "projucer_MessageIDs.h"
|
||||
#include "projucer_CppHelpers.h"
|
||||
#include "projucer_SourceCodeRange.h"
|
||||
#include "projucer_ClassDatabase.h"
|
||||
#include "projucer_DiagnosticMessage.h"
|
||||
#include "projucer_ProjectBuildInfo.h"
|
||||
#include "projucer_ClientServerMessages.h"
|
||||
#include "projucer_CompileEngineClient.h"
|
||||
#include "../LiveBuildEngine/projucer_CompileEngineServer.h"
|
||||
|
||||
#ifndef RUN_CLANG_IN_CHILD_PROCESS
|
||||
#error
|
||||
#endif
|
||||
|
||||
|
||||
//==============================================================================
|
||||
namespace ProjectProperties
|
||||
{
|
||||
const Identifier liveSettingsType ("LIVE_SETTINGS");
|
||||
#if JUCE_MAC
|
||||
const Identifier liveSettingsSubtype ("OSX");
|
||||
#elif JUCE_WINDOWS
|
||||
const Identifier liveSettingsSubtype ("WINDOWS");
|
||||
#elif JUCE_LINUX
|
||||
const Identifier liveSettingsSubtype ("LINUX");
|
||||
#endif
|
||||
|
||||
static ValueTree getLiveSettings (Project& project)
|
||||
{
|
||||
return project.getProjectRoot().getOrCreateChildWithName (liveSettingsType, nullptr)
|
||||
.getOrCreateChildWithName (liveSettingsSubtype, nullptr);
|
||||
}
|
||||
|
||||
static const ValueTree getLiveSettingsConst (Project& project)
|
||||
{
|
||||
return project.getProjectRoot().getChildWithName (liveSettingsType)
|
||||
.getChildWithName (liveSettingsSubtype);
|
||||
}
|
||||
|
||||
static Value getLiveSetting (Project& p, const Identifier& i) { return getLiveSettings (p).getPropertyAsValue (i, p.getUndoManagerFor (getLiveSettings (p))); }
|
||||
static var getLiveSettingVar (Project& p, const Identifier& i) { return getLiveSettingsConst (p) [i]; }
|
||||
|
||||
static Value getUserHeaderPathValue (Project& p) { return getLiveSetting (p, Ids::headerPath); }
|
||||
static String getUserHeaderPathString (Project& p) { return getLiveSettingVar (p, Ids::headerPath); }
|
||||
static Value getSystemHeaderPathValue (Project& p) { return getLiveSetting (p, Ids::systemHeaderPath); }
|
||||
static String getSystemHeaderPathString (Project& p) { return getLiveSettingVar (p, Ids::systemHeaderPath); }
|
||||
static Value getExtraDLLsValue (Project& p) { return getLiveSetting (p, Ids::extraDLLs); }
|
||||
static String getExtraDLLsString (Project& p) { return getLiveSettingVar (p, Ids::extraDLLs); }
|
||||
static Value getExtraCompilerFlagsValue (Project& p) { return getLiveSetting (p, Ids::extraCompilerFlags); }
|
||||
static String getExtraCompilerFlagsString (Project& p) { return getLiveSettingVar (p, Ids::extraCompilerFlags); }
|
||||
static Value getExtraPreprocessorDefsValue (Project& p) { return getLiveSetting (p, Ids::defines); }
|
||||
static String getExtraPreprocessorDefsString (Project& p) { return getLiveSettingVar (p, Ids::defines); }
|
||||
|
||||
static File getProjucerTempFolder()
|
||||
{
|
||||
#if JUCE_MAC
|
||||
return File ("~/Library/Caches/com.juce.projucer");
|
||||
#else
|
||||
return File::getSpecialLocation (File::tempDirectory).getChildFile ("com.juce.projucer");
|
||||
#endif
|
||||
}
|
||||
|
||||
static File getCacheLocation (Project& project)
|
||||
{
|
||||
return getProjucerTempFolder()
|
||||
.getChildFile ("Intermediate Files")
|
||||
.getChildFile (project.getProjectFilenameRoot() + "_" + project.getProjectUID());
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void LiveBuildProjectSettings::getLiveSettings (Project& project, PropertyListBuilder& props)
|
||||
{
|
||||
using namespace ProjectProperties;
|
||||
|
||||
props.addSearchPathProperty (getUserHeaderPathValue (project), "User header paths", "User header search paths.");
|
||||
props.addSearchPathProperty (getSystemHeaderPathValue (project), "System header paths", "System header search paths.");
|
||||
|
||||
props.add (new TextPropertyComponent (getExtraPreprocessorDefsValue (project), "Preprocessor Definitions", 32768, true),
|
||||
"Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace or commas "
|
||||
"to separate the items - to include a space or comma in a definition, precede it with a backslash.");
|
||||
|
||||
props.add (new TextPropertyComponent (getExtraCompilerFlagsValue (project), "Extra compiler flags", 2048, true),
|
||||
"Extra command-line flags to be passed to the compiler. This string can contain references to preprocessor"
|
||||
" definitions in the form ${NAME_OF_DEFINITION}, which will be replaced with their values.");
|
||||
|
||||
props.add (new TextPropertyComponent (getExtraDLLsValue (project), "Extra dynamic libraries", 2048, true),
|
||||
"Extra dynamic libs that the running code may require. Use new-lines or commas to separate the items");
|
||||
}
|
||||
|
||||
void LiveBuildProjectSettings::updateNewlyOpenedProject (Project&) { /* placeholder */ }
|
||||
|
||||
bool LiveBuildProjectSettings::isBuildDisabled (Project& p)
|
||||
{
|
||||
const bool defaultBuildDisabled = true;
|
||||
return p.getStoredProperties().getBoolValue ("buildDisabled", defaultBuildDisabled);
|
||||
}
|
||||
|
||||
void LiveBuildProjectSettings::setBuildDisabled (Project& p, bool b) { p.getStoredProperties().setValue ("buildDisabled", b); }
|
||||
bool LiveBuildProjectSettings::areWarningsDisabled (Project& p) { return p.getStoredProperties().getBoolValue ("warningsDisabled"); }
|
||||
void LiveBuildProjectSettings::setWarningsDisabled (Project& p, bool b) { p.getStoredProperties().setValue ("warningsDisabled", b); }
|
||||
|
||||
//==============================================================================
|
||||
class ClientIPC : public MessageHandler,
|
||||
private InterprocessConnection,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
ClientIPC (CompileEngineChildProcess& cp)
|
||||
: InterprocessConnection (true), owner (cp)
|
||||
{
|
||||
launchServer();
|
||||
}
|
||||
|
||||
~ClientIPC()
|
||||
{
|
||||
#if RUN_CLANG_IN_CHILD_PROCESS
|
||||
if (childProcess.isRunning())
|
||||
killServerPolitely();
|
||||
#endif
|
||||
}
|
||||
|
||||
void launchServer()
|
||||
{
|
||||
DBG ("Client: Launching Server...");
|
||||
const String pipeName ("ipc_" + String::toHexString (Random().nextInt64()));
|
||||
|
||||
const String command (createCommandLineForLaunchingServer (pipeName,
|
||||
owner.project.getProjectUID(),
|
||||
ProjectProperties::getCacheLocation (owner.project)));
|
||||
|
||||
#if RUN_CLANG_IN_CHILD_PROCESS
|
||||
if (! childProcess.start (command))
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
#else
|
||||
server = createClangServer (command);
|
||||
#endif
|
||||
|
||||
bool ok = connectToPipe (pipeName, 10000);
|
||||
jassert (ok);
|
||||
|
||||
if (ok)
|
||||
MessageTypes::sendPing (*this);
|
||||
|
||||
startTimer (serverKeepAliveTimeout);
|
||||
}
|
||||
|
||||
void killServerPolitely()
|
||||
{
|
||||
DBG ("Client: Killing Server...");
|
||||
MessageTypes::sendQuit (*this);
|
||||
|
||||
disconnect();
|
||||
stopTimer();
|
||||
|
||||
#if RUN_CLANG_IN_CHILD_PROCESS
|
||||
childProcess.waitForProcessToFinish (5000);
|
||||
#endif
|
||||
|
||||
killServerWithoutMercy();
|
||||
}
|
||||
|
||||
void killServerWithoutMercy()
|
||||
{
|
||||
disconnect();
|
||||
stopTimer();
|
||||
|
||||
#if RUN_CLANG_IN_CHILD_PROCESS
|
||||
childProcess.kill();
|
||||
#else
|
||||
destroyClangServer (server);
|
||||
server = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void connectionMade()
|
||||
{
|
||||
DBG ("Client: connected");
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
void connectionLost()
|
||||
{
|
||||
DBG ("Client: disconnected");
|
||||
startTimer (100);
|
||||
}
|
||||
|
||||
bool sendMessage (const ValueTree& m)
|
||||
{
|
||||
return InterprocessConnection::sendMessage (MessageHandler::convertMessage (m));
|
||||
}
|
||||
|
||||
void messageReceived (const MemoryBlock& message)
|
||||
{
|
||||
#if RUN_CLANG_IN_CHILD_PROCESS
|
||||
startTimer (serverKeepAliveTimeout);
|
||||
#else
|
||||
stopTimer();
|
||||
#endif
|
||||
MessageTypes::dispatchToClient (owner, MessageHandler::convertMessage (message));
|
||||
}
|
||||
|
||||
enum { serverKeepAliveTimeout = 10000 };
|
||||
|
||||
private:
|
||||
CompileEngineChildProcess& owner;
|
||||
|
||||
#if RUN_CLANG_IN_CHILD_PROCESS
|
||||
ChildProcess childProcess;
|
||||
#else
|
||||
void* server;
|
||||
#endif
|
||||
|
||||
void timerCallback() { owner.handleCrash (String()); }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class CompileEngineChildProcess::ChildProcess : private ValueTree::Listener,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
ChildProcess (CompileEngineChildProcess& proc, Project& p)
|
||||
: owner (proc), project (p)
|
||||
{
|
||||
projectRoot = project.getProjectRoot();
|
||||
|
||||
restartServer();
|
||||
projectRoot.addListener (this);
|
||||
openedOk = true;
|
||||
}
|
||||
|
||||
~ChildProcess()
|
||||
{
|
||||
projectRoot.removeListener (this);
|
||||
|
||||
if (isRunningApp && server != nullptr)
|
||||
server->killServerWithoutMercy();
|
||||
|
||||
server = nullptr;
|
||||
}
|
||||
|
||||
void restartServer()
|
||||
{
|
||||
server = nullptr;
|
||||
server = new ClientIPC (owner);
|
||||
sendRebuild();
|
||||
}
|
||||
|
||||
void sendRebuild()
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
ProjectBuildInfo build;
|
||||
|
||||
if (! doesProjectMatchSavedHeaderState (project))
|
||||
{
|
||||
MessageTypes::sendNewBuild (*server, build);
|
||||
|
||||
owner.errorList.resetToError ("Project structure does not match the saved headers! "
|
||||
"Please re-save your project to enable compilation");
|
||||
return;
|
||||
}
|
||||
|
||||
build.setSystemIncludes (getSystemIncludePaths());
|
||||
build.setUserIncludes (getUserIncludes());
|
||||
|
||||
build.setGlobalDefs (getGlobalDefs (project));
|
||||
build.setCompileFlags (ProjectProperties::getExtraCompilerFlagsString (project).trim());
|
||||
build.setExtraDLLs (getExtraDLLs());
|
||||
build.setJuceModulesFolder (EnabledModuleList::findDefaultModulesFolder (project).getFullPathName());
|
||||
|
||||
build.setUtilsCppInclude (project.getAppIncludeFile().getFullPathName());
|
||||
|
||||
scanForProjectFiles (project, build);
|
||||
|
||||
owner.updateAllEditors();
|
||||
|
||||
MessageTypes::sendNewBuild (*server, build);
|
||||
}
|
||||
|
||||
void cleanAll()
|
||||
{
|
||||
MessageTypes::sendCleanAll (*server);
|
||||
sendRebuild();
|
||||
}
|
||||
|
||||
void reinstantiatePreviews()
|
||||
{
|
||||
MessageTypes::sendReinstantiate (*server);
|
||||
}
|
||||
|
||||
bool launchApp()
|
||||
{
|
||||
MessageTypes::sendLaunchApp (*server);
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopedPointer<ClientIPC> server;
|
||||
|
||||
bool openedOk = false;
|
||||
bool isRunningApp = false;
|
||||
|
||||
private:
|
||||
CompileEngineChildProcess& owner;
|
||||
Project& project;
|
||||
ValueTree projectRoot;
|
||||
|
||||
void projectStructureChanged()
|
||||
{
|
||||
startTimer (100);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
sendRebuild();
|
||||
}
|
||||
|
||||
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { projectStructureChanged(); }
|
||||
void valueTreeChildAdded (ValueTree&, ValueTree&) override { projectStructureChanged(); }
|
||||
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { projectStructureChanged(); }
|
||||
void valueTreeParentChanged (ValueTree&) override { projectStructureChanged(); }
|
||||
void valueTreeChildOrderChanged (ValueTree&, int, int) override {}
|
||||
|
||||
static String getGlobalDefs (Project& proj)
|
||||
{
|
||||
String defs (ProjectProperties::getExtraPreprocessorDefsString (proj));
|
||||
|
||||
for (Project::ExporterIterator exporter (proj); exporter.next();)
|
||||
if (exporter->canLaunchProject())
|
||||
defs << " " << exporter->getExporterIdentifierMacro() << "=1";
|
||||
|
||||
return defs;
|
||||
}
|
||||
|
||||
static void scanProjectItem (const Project::Item& projectItem, Array<File>& compileUnits, Array<File>& userFiles)
|
||||
{
|
||||
if (projectItem.isGroup())
|
||||
{
|
||||
for (int i = 0; i < projectItem.getNumChildren(); ++i)
|
||||
scanProjectItem (projectItem.getChild(i), compileUnits, userFiles);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (projectItem.shouldBeCompiled())
|
||||
{
|
||||
const File f (projectItem.getFile());
|
||||
|
||||
if (f.exists())
|
||||
compileUnits.add (f);
|
||||
}
|
||||
|
||||
if (projectItem.shouldBeAddedToTargetProject() && ! projectItem.shouldBeAddedToBinaryResources())
|
||||
{
|
||||
const File f (projectItem.getFile());
|
||||
|
||||
if (f.exists())
|
||||
userFiles.add (f);
|
||||
}
|
||||
}
|
||||
|
||||
void scanForProjectFiles (Project& proj, ProjectBuildInfo& build)
|
||||
{
|
||||
Array<File> compileUnits, userFiles;
|
||||
scanProjectItem (proj.getMainGroup(), compileUnits, userFiles);
|
||||
|
||||
{
|
||||
OwnedArray<LibraryModule> modules;
|
||||
proj.getModules().createRequiredModules (modules);
|
||||
|
||||
for (Project::ExporterIterator exporter (proj); exporter.next();)
|
||||
{
|
||||
if (exporter->canLaunchProject())
|
||||
{
|
||||
for (const LibraryModule* m : modules)
|
||||
{
|
||||
const File localModuleFolder = proj.getModules().shouldCopyModuleFilesLocally (m->moduleInfo.getID()).getValue()
|
||||
? proj.getLocalModuleFolder (m->moduleInfo.getID())
|
||||
: m->moduleInfo.getFolder();
|
||||
|
||||
m->findAndAddCompiledUnits (*exporter, nullptr, compileUnits);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; ; ++i)
|
||||
{
|
||||
const File binaryDataCpp (proj.getBinaryDataCppFile (i));
|
||||
if (! binaryDataCpp.exists())
|
||||
break;
|
||||
|
||||
compileUnits.add (binaryDataCpp);
|
||||
}
|
||||
|
||||
for (int i = compileUnits.size(); --i >= 0;)
|
||||
if (compileUnits.getReference(i).hasFileExtension (".r"))
|
||||
compileUnits.remove (i);
|
||||
|
||||
build.setFiles (compileUnits, userFiles);
|
||||
}
|
||||
|
||||
static bool doesProjectMatchSavedHeaderState (Project& project)
|
||||
{
|
||||
ValueTree liveModules (project.getProjectRoot().getChildWithName (Ids::MODULES));
|
||||
|
||||
ScopedPointer<XmlElement> xml (XmlDocument::parse (project.getFile()));
|
||||
|
||||
if (xml == nullptr || ! xml->hasTagName (Ids::JUCERPROJECT.toString()))
|
||||
return false;
|
||||
|
||||
ValueTree diskModules (ValueTree::fromXml (*xml).getChildWithName (Ids::MODULES));
|
||||
|
||||
return liveModules.isEquivalentTo (diskModules);
|
||||
}
|
||||
|
||||
StringArray getUserIncludes()
|
||||
{
|
||||
StringArray paths;
|
||||
paths.add (project.getGeneratedCodeFolder().getFullPathName());
|
||||
paths.addArray (getSearchPathsFromString (ProjectProperties::getUserHeaderPathString (project)));
|
||||
return convertSearchPathsToAbsolute (paths);
|
||||
}
|
||||
|
||||
StringArray getSystemIncludePaths()
|
||||
{
|
||||
StringArray paths;
|
||||
paths.addArray (getSearchPathsFromString (ProjectProperties::getSystemHeaderPathString (project)));
|
||||
|
||||
if (project.getProjectType().isAudioPlugin())
|
||||
{
|
||||
paths.add (getAppSettings().getGlobalPath (Ids::vst2Path, TargetOS::getThisOS()).toString());
|
||||
paths.add (getAppSettings().getGlobalPath (Ids::vst3Path, TargetOS::getThisOS()).toString());
|
||||
}
|
||||
|
||||
OwnedArray<LibraryModule> modules;
|
||||
project.getModules().createRequiredModules (modules);
|
||||
|
||||
for (auto* module : modules)
|
||||
paths.addIfNotAlreadyThere (module->getFolder().getParentDirectory().getFullPathName());
|
||||
|
||||
return convertSearchPathsToAbsolute (paths);
|
||||
}
|
||||
|
||||
StringArray convertSearchPathsToAbsolute (const StringArray& paths) const
|
||||
{
|
||||
StringArray s;
|
||||
const File root (project.getProjectFolder());
|
||||
|
||||
for (String p : paths)
|
||||
s.add (root.getChildFile (p).getFullPathName());
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
StringArray getExtraDLLs()
|
||||
{
|
||||
StringArray dlls;
|
||||
dlls.addTokens (ProjectProperties::getExtraDLLsString (project), "\n\r,", StringRef());
|
||||
dlls.trim();
|
||||
dlls.removeEmptyStrings();
|
||||
return dlls;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcess)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
CompileEngineChildProcess::CompileEngineChildProcess (Project& p)
|
||||
: project (p),
|
||||
continuousRebuild (false)
|
||||
{
|
||||
ProjucerApplication::getApp().openDocumentManager.addListener (this);
|
||||
|
||||
createProcess();
|
||||
|
||||
errorList.setWarningsEnabled (! LiveBuildProjectSettings::areWarningsDisabled (project));
|
||||
}
|
||||
|
||||
CompileEngineChildProcess::~CompileEngineChildProcess()
|
||||
{
|
||||
ProjucerApplication::getApp().openDocumentManager.removeListener (this);
|
||||
|
||||
process = nullptr;
|
||||
lastComponentList.clear();
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::createProcess()
|
||||
{
|
||||
jassert (process == nullptr);
|
||||
process = new ChildProcess (*this, project);
|
||||
|
||||
if (! process->openedOk)
|
||||
process = nullptr;
|
||||
|
||||
updateAllEditors();
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::cleanAll()
|
||||
{
|
||||
if (process != nullptr)
|
||||
process->cleanAll();
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::openPreview (const ClassDatabase::Class& comp)
|
||||
{
|
||||
if (process != nullptr)
|
||||
{
|
||||
MainWindow* projectWindow = nullptr;
|
||||
OwnedArray<MainWindow>& windows = ProjucerApplication::getApp().mainWindowList.windows;
|
||||
|
||||
for (int i = 0; i < windows.size(); ++i)
|
||||
{
|
||||
if (MainWindow* w = windows[i])
|
||||
{
|
||||
if (w->getProject() == &project)
|
||||
{
|
||||
projectWindow = w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle<int> mainWindowRect;
|
||||
|
||||
if (projectWindow != nullptr)
|
||||
mainWindowRect = projectWindow->getBounds();
|
||||
|
||||
MessageTypes::sendOpenPreview (*process->server, comp, mainWindowRect);
|
||||
}
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::reinstantiatePreviews()
|
||||
{
|
||||
if (process != nullptr)
|
||||
process->reinstantiatePreviews();
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::processActivationChanged (bool isForeground)
|
||||
{
|
||||
if (process != nullptr)
|
||||
MessageTypes::sendProcessActivationState (*process->server, isForeground);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool CompileEngineChildProcess::canLaunchApp() const
|
||||
{
|
||||
return process != nullptr
|
||||
&& runningAppProcess == nullptr
|
||||
&& activityList.getNumActivities() == 0
|
||||
&& errorList.getNumErrors() == 0;
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::launchApp()
|
||||
{
|
||||
if (process != nullptr)
|
||||
process->launchApp();
|
||||
}
|
||||
|
||||
bool CompileEngineChildProcess::canKillApp() const
|
||||
{
|
||||
return runningAppProcess != nullptr;
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::killApp()
|
||||
{
|
||||
runningAppProcess = nullptr;
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleAppLaunched()
|
||||
{
|
||||
runningAppProcess = process;
|
||||
runningAppProcess->isRunningApp = true;
|
||||
createProcess();
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleAppQuit()
|
||||
{
|
||||
DBG ("handleAppQuit");
|
||||
runningAppProcess = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct CompileEngineChildProcess::Editor : private CodeDocument::Listener,
|
||||
private Timer
|
||||
{
|
||||
Editor (CompileEngineChildProcess& ccp, const File& f, CodeDocument& doc)
|
||||
: owner (ccp), file (f), document (doc), transactionTimer (doc)
|
||||
{
|
||||
sendFullUpdate();
|
||||
document.addListener (this);
|
||||
}
|
||||
|
||||
~Editor()
|
||||
{
|
||||
document.removeListener (this);
|
||||
}
|
||||
|
||||
void codeDocumentTextInserted (const String& newText, int insertIndex) override
|
||||
{
|
||||
CodeChange (Range<int> (insertIndex, insertIndex), newText).addToList (pendingChanges);
|
||||
startEditorChangeTimer();
|
||||
transactionTimer.stopTimer();
|
||||
|
||||
owner.lastComponentList.globalNamespace
|
||||
.nudgeAllCodeRanges (file.getFullPathName(), insertIndex, newText.length());
|
||||
}
|
||||
|
||||
void codeDocumentTextDeleted (int start, int end) override
|
||||
{
|
||||
CodeChange (Range<int> (start, end), String()).addToList (pendingChanges);
|
||||
startEditorChangeTimer();
|
||||
transactionTimer.stopTimer();
|
||||
|
||||
owner.lastComponentList.globalNamespace
|
||||
.nudgeAllCodeRanges (file.getFullPathName(), start, start - end);
|
||||
}
|
||||
|
||||
void sendFullUpdate()
|
||||
{
|
||||
reset();
|
||||
|
||||
if (owner.process != nullptr)
|
||||
MessageTypes::sendFileContentFullUpdate (*owner.process->server, file, document.getAllContent());
|
||||
}
|
||||
|
||||
bool flushEditorChanges()
|
||||
{
|
||||
if (pendingChanges.size() > 0)
|
||||
{
|
||||
if (owner.process != nullptr && owner.process->server != nullptr)
|
||||
MessageTypes::sendFileChanges (*owner.process->server, pendingChanges, file);
|
||||
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
stopTimer();
|
||||
return false;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
stopTimer();
|
||||
pendingChanges.clear();
|
||||
}
|
||||
|
||||
void startTransactionTimer()
|
||||
{
|
||||
transactionTimer.startTimer (1000);
|
||||
}
|
||||
|
||||
void startEditorChangeTimer()
|
||||
{
|
||||
startTimer (200);
|
||||
}
|
||||
|
||||
CompileEngineChildProcess& owner;
|
||||
File file;
|
||||
CodeDocument& document;
|
||||
|
||||
private:
|
||||
Array<CodeChange> pendingChanges;
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (owner.continuousRebuild)
|
||||
flushEditorChanges();
|
||||
else
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
struct TransactionTimer : public Timer
|
||||
{
|
||||
TransactionTimer (CodeDocument& doc) : document (doc) {}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
stopTimer();
|
||||
document.newTransaction();
|
||||
}
|
||||
|
||||
CodeDocument& document;
|
||||
};
|
||||
|
||||
TransactionTimer transactionTimer;
|
||||
};
|
||||
|
||||
void CompileEngineChildProcess::editorOpened (const File& file, CodeDocument& document)
|
||||
{
|
||||
editors.add (new Editor (*this, file, document));
|
||||
}
|
||||
|
||||
bool CompileEngineChildProcess::documentAboutToClose (OpenDocumentManager::Document* document)
|
||||
{
|
||||
for (int i = editors.size(); --i >= 0;)
|
||||
{
|
||||
if (document->getFile() == editors.getUnchecked(i)->file)
|
||||
{
|
||||
const File f (editors.getUnchecked(i)->file);
|
||||
editors.remove (i);
|
||||
|
||||
if (process != nullptr)
|
||||
MessageTypes::sendHandleFileReset (*process->server, f);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::updateAllEditors()
|
||||
{
|
||||
for (int i = editors.size(); --i >= 0;)
|
||||
editors.getUnchecked(i)->sendFullUpdate();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CompileEngineChildProcess::handleCrash (const String& message)
|
||||
{
|
||||
Logger::writeToLog ("*** Child process crashed: " + message);
|
||||
|
||||
if (crashHandler != nullptr)
|
||||
crashHandler (message);
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleNewDiagnosticList (const ValueTree& l) { errorList.setList (l); }
|
||||
void CompileEngineChildProcess::handleActivityListChanged (const StringArray& l) { activityList.setList (l); }
|
||||
|
||||
void CompileEngineChildProcess::handleCloseIDE()
|
||||
{
|
||||
if (JUCEApplication* app = JUCEApplication::getInstance())
|
||||
app->systemRequestedQuit();
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleMissingSystemHeaders()
|
||||
{
|
||||
if (ProjectContentComponent* p = findProjectContentComponent())
|
||||
p->handleMissingSystemHeaders();
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleKeyPress (const String& className, const KeyPress& key)
|
||||
{
|
||||
ApplicationCommandManager& commandManager = ProjucerApplication::getCommandManager();
|
||||
|
||||
CommandID command = commandManager.getKeyMappings()->findCommandForKeyPress (key);
|
||||
|
||||
if (command == StandardApplicationCommandIDs::undo)
|
||||
{
|
||||
handleUndoInEditor (className);
|
||||
}
|
||||
else if (command == StandardApplicationCommandIDs::redo)
|
||||
{
|
||||
handleRedoInEditor (className);
|
||||
}
|
||||
else if (ApplicationCommandTarget* const target = ApplicationCommandManager::findTargetForComponent (findProjectContentComponent()))
|
||||
{
|
||||
commandManager.setFirstCommandTarget (target);
|
||||
commandManager.getKeyMappings()->keyPressed (key, findProjectContentComponent());
|
||||
commandManager.setFirstCommandTarget (nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleUndoInEditor (const String& /*className*/)
|
||||
{
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleRedoInEditor (const String& /*className*/)
|
||||
{
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleClassListChanged (const ValueTree& newList)
|
||||
{
|
||||
lastComponentList = ClassDatabase::ClassList::fromValueTree (newList);
|
||||
activityList.sendClassListChangedMessage (lastComponentList);
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleBuildFailed()
|
||||
{
|
||||
if (errorList.getNumErrors() > 0)
|
||||
ProjucerApplication::getCommandManager().invokeDirectly (CommandIDs::showBuildTab, true);
|
||||
|
||||
ProjucerApplication::getCommandManager().commandStatusChanged();
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleChangeCode (const SourceCodeRange& location, const String& newText)
|
||||
{
|
||||
if (Editor* ed = getOrOpenEditorFor (location.file))
|
||||
{
|
||||
if (ed->flushEditorChanges())
|
||||
return; // client-side editor changes were pending, so deal with them first, and discard
|
||||
// the incoming change, whose position may now be wrong.
|
||||
|
||||
ed->document.deleteSection (location.range.getStart(), location.range.getEnd());
|
||||
ed->document.insertText (location.range.getStart(), newText);
|
||||
|
||||
// deliberately clear the messages that we just added, to avoid these changes being
|
||||
// sent to the server (which will already have processed the same ones locally)
|
||||
ed->reset();
|
||||
ed->startTransactionTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handlePing()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CompileEngineChildProcess::setContinuousRebuild (bool b)
|
||||
{
|
||||
continuousRebuild = b;
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::flushEditorChanges()
|
||||
{
|
||||
for (Editor* ed : editors)
|
||||
ed->flushEditorChanges();
|
||||
}
|
||||
|
||||
ProjectContentComponent* CompileEngineChildProcess::findProjectContentComponent() const
|
||||
{
|
||||
for (MainWindow* mw : ProjucerApplication::getApp().mainWindowList.windows)
|
||||
if (mw->getProject() == &project)
|
||||
return mw->getProjectContentComponent();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CompileEngineChildProcess::Editor* CompileEngineChildProcess::getOrOpenEditorFor (const File& file)
|
||||
{
|
||||
for (Editor* ed : editors)
|
||||
if (ed->file == file)
|
||||
return ed;
|
||||
|
||||
if (ProjectContentComponent* pcc = findProjectContentComponent())
|
||||
if (pcc->showEditorForFile (file, false))
|
||||
return getOrOpenEditorFor (file);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::handleHighlightCode (const SourceCodeRange& location)
|
||||
{
|
||||
ProjectContentComponent* pcc = findProjectContentComponent();
|
||||
|
||||
if (pcc != nullptr && pcc->showEditorForFile (location.file, false))
|
||||
{
|
||||
SourceCodeEditor* sce = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent());
|
||||
|
||||
if (sce != nullptr && sce->editor != nullptr)
|
||||
{
|
||||
sce->highlight (location.range, true);
|
||||
|
||||
Process::makeForegroundProcess();
|
||||
|
||||
CodeEditorComponent& ed = *sce->editor;
|
||||
ed.getTopLevelComponent()->toFront (false);
|
||||
ed.grabKeyboardFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompileEngineChildProcess::cleanAllCachedFilesForProject (Project& p)
|
||||
{
|
||||
File cacheFolder (ProjectProperties::getCacheLocation (p));
|
||||
|
||||
if (cacheFolder.isDirectory())
|
||||
cacheFolder.deleteRecursively();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue