@@ -2,10 +2,10 @@ | |||
set -e | |||
JUCE_MODULES_DIR="/home/falktx/Personal/FOSS/GIT/DISTRHO/libs/juce/source/modules/" | |||
CARLA_MODULES_DIR="/home/falktx/Personal/FOSS/GIT/Carla/source/modules" | |||
JUCE_MODULES_DIR="/home/falktx/Personal/FOSS/GIT/distrho/DISTRHO/libs/juce/source/modules/" | |||
CARLA_MODULES_DIR="/home/falktx/Personal/FOSS/GIT/falktx/Carla/source/modules/" | |||
MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics") | |||
MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") | |||
for M in $MODULES; do | |||
echo $M; | |||
@@ -941,6 +941,11 @@ double MidiMessage::getMidiNoteInHertz (int noteNumber, const double frequencyOf | |||
return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); | |||
} | |||
bool MidiMessage::isMidiNoteBlack (int noteNumber) noexcept | |||
{ | |||
return ((1 << (noteNumber % 12)) & 0x054a) != 0; | |||
} | |||
const char* MidiMessage::getGMInstrumentName (const int n) | |||
{ | |||
static const char* names[] = | |||
@@ -889,6 +889,9 @@ public: | |||
*/ | |||
static double getMidiNoteInHertz (int noteNumber, const double frequencyOfA = 440.0) noexcept; | |||
/** Returns true if the given midi note number is a black key. */ | |||
static bool isMidiNoteBlack (int noteNumber) noexcept; | |||
/** Returns the standard name of a GM instrument, or nullptr if unknown for this index. | |||
@param midiInstrumentNumber the program number 0 to 127 | |||
@@ -231,24 +231,15 @@ public: | |||
NSString* nameNSString = nil; | |||
size = sizeof (nameNSString); | |||
#if JUCE_CLANG | |||
// Very irritating that AudioDeviceGetProperty is marked as deprecated, since | |||
// there seems to be no replacement way of getting the channel names. | |||
#pragma clang diagnostic push | |||
#pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
#endif | |||
pa.mSelector = kAudioObjectPropertyElementName; | |||
pa.mElement = chanNum + 1; | |||
if (AudioDeviceGetProperty (deviceID, chanNum + 1, input, kAudioDevicePropertyChannelNameCFString, | |||
&size, &nameNSString) == noErr) | |||
if (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &nameNSString) == noErr) | |||
{ | |||
name = nsStringToJuce (nameNSString); | |||
[nameNSString release]; | |||
} | |||
#if JUCE_CLANG | |||
#pragma clang diagnostic pop | |||
#endif | |||
if ((input ? activeInputChans : activeOutputChans) [chanNum]) | |||
{ | |||
CallbackDetailsForChannel info = { i, (int) j, (int) b.mNumberChannels }; | |||
@@ -59,7 +59,7 @@ static juce::String toString (const Steinberg::char16* string) noexcept { re | |||
static juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | |||
static juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | |||
static void toString (Steinberg::Vst::String128 result, const juce::String& source) | |||
static void toString128 (Steinberg::Vst::String128 result, const juce::String& source) | |||
{ | |||
Steinberg::UString (result, 128).fromAscii (source.toUTF8()); | |||
} | |||
@@ -69,6 +69,13 @@ static Steinberg::Vst::TChar* toString (const juce::String& source) noexcept | |||
return reinterpret_cast<Steinberg::Vst::TChar*> (source.toUTF16().getAddress()); | |||
} | |||
#if JUCE_WINDOWS | |||
static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeHWND; | |||
#else | |||
static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeNSView; | |||
#endif | |||
//============================================================================== | |||
/** The equivalent numChannels and speaker arrangements should always | |||
match between this function and fillWithCorrespondingSpeakerArrangements(). | |||
@@ -81,22 +88,32 @@ static Steinberg::Vst::SpeakerArrangement getArrangementForNumChannels (int numC | |||
{ | |||
using namespace Steinberg::Vst::SpeakerArr; | |||
if (numChannels >= 14) return k131; | |||
if (numChannels >= 13) return k130; | |||
if (numChannels >= 12) return k111; | |||
if (numChannels >= 11) return k101; | |||
if (numChannels >= 10) return k91; | |||
if (numChannels >= 9) return k90; | |||
if (numChannels >= 8) return k71CineFullFront; | |||
if (numChannels >= 7) return k61Cine; | |||
if (numChannels >= 6) return k51; | |||
if (numChannels >= 5) return k50; | |||
if (numChannels >= 4) return k31Cine; | |||
if (numChannels >= 3) return k30Cine; | |||
if (numChannels >= 2) return kStereo; | |||
if (numChannels >= 1) return kMono; | |||
return kEmpty; | |||
switch (numChannels) | |||
{ | |||
case 0: return kEmpty; | |||
case 1: return kMono; | |||
case 2: return kStereo; | |||
case 3: return k30Cine; | |||
case 4: return k31Cine; | |||
case 5: return k50; | |||
case 6: return k51; | |||
case 7: return k61Cine; | |||
case 8: return k71CineFullFront; | |||
case 9: return k90; | |||
case 10: return k91; | |||
case 11: return k101; | |||
case 12: return k111; | |||
case 13: return k130; | |||
case 14: return k131; | |||
case 24: return (Steinberg::Vst::SpeakerArrangement) 1929904127; // k222 | |||
default: break; | |||
} | |||
jassert (numChannels >= 0); | |||
juce::BigInteger bi; | |||
bi.setRange (0, jmin (numChannels, (int) (sizeof (Steinberg::Vst::SpeakerArrangement) * 8)), true); | |||
return (Steinberg::Vst::SpeakerArrangement) bi.toInt64(); | |||
} | |||
/** The equivalent numChannels and speaker arrangements should always | |||
@@ -119,10 +136,17 @@ static void fillWithCorrespondingSpeakerArrangements (Array<Steinberg::Vst::Spea | |||
return; | |||
} | |||
/* | |||
The order of the arrangement checks must be descending, since most plugins test for | |||
the first arrangement to match their number of specified channels. | |||
*/ | |||
// The order of the arrangement checks must be descending, since most plugins test for | |||
/// the first arrangement to match their number of specified channels. | |||
if (numChannels > 24) | |||
{ | |||
juce::BigInteger bi; | |||
bi.setRange (0, jmin (numChannels, (int) (sizeof (Steinberg::Vst::SpeakerArrangement) * 8)), true); | |||
destination.add ((Steinberg::Vst::SpeakerArrangement) bi.toInt64()); | |||
} | |||
if (numChannels >= 24) destination.add ((Steinberg::Vst::SpeakerArrangement) 1929904127); // k222 | |||
if (numChannels >= 14) destination.add (k131); | |||
if (numChannels >= 13) destination.add (k130); | |||
if (numChannels >= 12) destination.add (k111); | |||
@@ -225,7 +249,9 @@ public: | |||
//============================================================================== | |||
static void toMidiBuffer (MidiBuffer& result, Steinberg::Vst::IEventList& eventList) | |||
{ | |||
for (Steinberg::int32 i = 0; i < eventList.getEventCount(); ++i) | |||
const int32 numEvents = eventList.getEventCount(); | |||
for (Steinberg::int32 i = 0; i < numEvents; ++i) | |||
{ | |||
Steinberg::Vst::Event e; | |||
@@ -407,4 +433,4 @@ namespace VST3BufferExchange | |||
} | |||
} | |||
#endif //JUCE_VST3COMMON_H_INCLUDED | |||
#endif // JUCE_VST3COMMON_H_INCLUDED |
@@ -22,8 +22,8 @@ | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_VST3HEADER_H_INCLUDED | |||
#define JUCE_VST3HEADER_H_INCLUDED | |||
#ifndef JUCE_VST3HEADERS_H_INCLUDED | |||
#define JUCE_VST3HEADERS_H_INCLUDED | |||
#undef Point | |||
#undef Component | |||
@@ -159,7 +159,6 @@ namespace Steinberg | |||
#undef OBJ_METHODS | |||
#undef QUERY_INTERFACE | |||
#undef LICENCE_UID | |||
#undef DEF_CLASS_IID | |||
#undef BEGIN_FACTORY | |||
#undef DEF_CLASS | |||
#undef DEF_CLASS1 | |||
@@ -169,4 +168,4 @@ namespace Steinberg | |||
#undef Point | |||
#undef Component | |||
#endif //JUCE_VST3HEADER_H_INCLUDED | |||
#endif // JUCE_VST3HEADERS_H_INCLUDED |
@@ -998,7 +998,8 @@ private: | |||
void releaseFactory() | |||
{ | |||
const Steinberg::FReleaser releaser (factory); | |||
if (factory != nullptr) | |||
factory->release(); | |||
} | |||
#if JUCE_WINDOWS | |||
@@ -1219,10 +1220,9 @@ public: | |||
#if JUCE_MAC | |||
dummyComponent.setView (nullptr); | |||
[pluginHandle release]; | |||
#endif | |||
const Steinberg::FReleaser releaser (view); | |||
view = nullptr; | |||
} | |||
JUCE_DECLARE_VST3_COM_REF_METHODS | |||
@@ -1284,7 +1284,8 @@ public: | |||
dummyComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); | |||
#endif | |||
Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); // Some plugins don't update their cursor correctly when mousing out the window | |||
// Some plugins don't update their cursor correctly when mousing out the window | |||
Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); | |||
recursiveResize = false; | |||
} | |||
@@ -1316,7 +1317,7 @@ public: | |||
private: | |||
//============================================================================== | |||
Atomic<int> refCount; | |||
IPlugView* view; // N.B.: Don't use a ComSmartPtr here! The view should start with a refCount of 1, and does NOT need to be incremented! | |||
ComSmartPtr<IPlugView> view; | |||
#if JUCE_WINDOWS | |||
class ChildComponent : public Component | |||
@@ -1335,14 +1336,14 @@ private: | |||
ScopedPointer<ComponentPeer> peer; | |||
typedef HWND HandleFormat; | |||
#elif JUCE_MAC | |||
NSViewComponent dummyComponent; | |||
AutoResizingNSViewComponentWithParent dummyComponent; | |||
typedef NSView* HandleFormat; | |||
#else | |||
Component dummyComponent; | |||
typedef void* HandleFormat; | |||
#endif | |||
HandleFormat pluginHandle; // Don't delete this | |||
HandleFormat pluginHandle; | |||
bool recursiveResize; | |||
//============================================================================== | |||
@@ -1368,17 +1369,12 @@ private: | |||
#elif JUCE_MAC | |||
dummyComponent.setBounds (getBounds().withZeroOrigin()); | |||
addAndMakeVisible (dummyComponent); | |||
pluginHandle = [[NSView alloc] init]; | |||
dummyComponent.setView (pluginHandle); | |||
pluginHandle = (NSView*) dummyComponent.getView(); | |||
jassert (pluginHandle != nil); | |||
#endif | |||
if (pluginHandle != nullptr) | |||
view->attached (pluginHandle, | |||
#if JUCE_WINDOWS | |||
kPlatformTypeHWND); | |||
#else | |||
kPlatformTypeNSView); | |||
#endif | |||
warnOnFailure (view->attached (pluginHandle, defaultVST3WindowType)); | |||
} | |||
} | |||
@@ -1452,7 +1448,8 @@ public: | |||
if (! fetchComponentAndController (factory, factory->countClasses())) | |||
return false; | |||
editController->initialize (host->getFUnknown()); // (May return an error if the plugin combines the IComponent and IEditController implementations) | |||
// (May return an error if the plugin combines the IComponent and IEditController implementations) | |||
editController->initialize (host->getFUnknown()); | |||
isControllerInitialised = true; | |||
editController->setComponentHandler (host); | |||
@@ -187,7 +187,6 @@ static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstI | |||
static int shellUIDToCreate = 0; | |||
static int insideVSTCallback = 0; | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
class IdleCallRecursionPreventer | |||
{ | |||
public: | |||
@@ -211,9 +210,6 @@ private: | |||
}; | |||
class VSTPluginWindow; | |||
#else | |||
struct IdleCallRecursionPreventer{}; | |||
#endif | |||
//============================================================================== | |||
// Change this to disable logging of various VST activities | |||
@@ -240,7 +236,6 @@ static void* NewCFMFromMachO (void* const machofp) noexcept | |||
} | |||
#endif | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
//============================================================================== | |||
#if JUCE_LINUX | |||
@@ -345,7 +340,6 @@ namespace | |||
} | |||
} | |||
#endif | |||
#endif | |||
//============================================================================== | |||
@@ -716,13 +710,9 @@ static const int defaultVSTBlockSizeValue = 512; | |||
//============================================================================== | |||
//============================================================================== | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
class VSTPluginInstance : public AudioPluginInstance, | |||
private Timer, | |||
private AsyncUpdater | |||
#else | |||
class VSTPluginInstance : public AudioPluginInstance | |||
#endif | |||
{ | |||
public: | |||
VSTPluginInstance (const ModuleHandle::Ptr& module_) | |||
@@ -793,10 +783,8 @@ public: | |||
UseResFile (module->resFileId); | |||
#endif | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
// Must delete any editors before deleting the plugin instance! | |||
jassert (getActiveEditor() == 0); | |||
#endif | |||
_fpreset(); // some dodgy plugs fuck around with this | |||
@@ -1254,7 +1242,6 @@ public: | |||
void setCurrentProgramStateInformation (const void* data, int size) override { loadFromFXBFile (data, size); } | |||
//============================================================================== | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
void timerCallback() override | |||
{ | |||
if (dispatch (effIdle, 0, 0, 0, 0) == 0) | |||
@@ -1266,7 +1253,6 @@ public: | |||
// indicates that something about the plugin has changed.. | |||
updateHostDisplay(); | |||
} | |||
#endif | |||
VstIntPtr handleCallback (VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) | |||
{ | |||
@@ -1284,7 +1270,6 @@ public: | |||
#pragma warning (pop) | |||
#endif | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
case audioMasterIdle: | |||
if (insideVSTCallback == 0 && MessageManager::getInstance()->isThisTheMessageThread()) | |||
{ | |||
@@ -1313,7 +1298,6 @@ public: | |||
case audioMasterUpdateDisplay: triggerAsyncUpdate(); break; | |||
case audioMasterIOChanged: setLatencySamples (effect->initialDelay); break; | |||
case audioMasterNeedIdle: startTimer (50); break; | |||
#endif | |||
case audioMasterGetSampleRate: return (VstIntPtr) (getSampleRate() > 0 ? getSampleRate() : defaultVSTSampleRateValue); | |||
case audioMasterGetBlockSize: return (VstIntPtr) (getBlockSize() > 0 ? getBlockSize() : defaultVSTBlockSizeValue); | |||
@@ -1399,12 +1383,10 @@ public: | |||
case audioMasterGetVendorString: | |||
case audioMasterGetProductString: | |||
{ | |||
String hostName ("Carla"); | |||
String hostName ("Juce VST Host"); | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
if (JUCEApplicationBase* app = JUCEApplicationBase::getInstance()) | |||
hostName = app->getApplicationName(); | |||
#endif | |||
hostName.copyToUTF8 ((char*) ptr, (size_t) jmin (kVstMaxVendorStrLen, kVstMaxProductStrLen) - 1); | |||
break; | |||
@@ -1911,7 +1893,6 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginInstance) | |||
}; | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
//============================================================================== | |||
static Array <VSTPluginWindow*> activeVSTWindows; | |||
@@ -1946,17 +1927,10 @@ public: | |||
#elif JUCE_MAC | |||
#if JUCE_SUPPORT_CARBON | |||
if (! plug.usesCocoaNSView) | |||
{ | |||
addAndMakeVisible (carbonWrapper = new CarbonWrapperComponent (*this)); | |||
} | |||
else | |||
#endif | |||
{ | |||
addAndMakeVisible (cocoaWrapper = new AutoResizingNSViewComponent()); | |||
NSView* innerView = [[NSView alloc] init]; | |||
cocoaWrapper->setView (innerView); | |||
[innerView release]; | |||
} | |||
addAndMakeVisible (cocoaWrapper = new AutoResizingNSViewComponentWithParent()); | |||
#endif | |||
activeVSTWindows.add (this); | |||
@@ -2619,7 +2593,7 @@ private: | |||
ScopedPointer<CarbonWrapperComponent> carbonWrapper; | |||
#endif | |||
ScopedPointer<NSViewComponent> cocoaWrapper; | |||
ScopedPointer<AutoResizingNSViewComponentWithParent> cocoaWrapper; | |||
void resized() override | |||
{ | |||
@@ -2635,17 +2609,12 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginWindow) | |||
}; | |||
#endif | |||
//============================================================================== | |||
AudioProcessorEditor* VSTPluginInstance::createEditor() | |||
{ | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
return hasEditor() ? new VSTPluginWindow (*this) | |||
: nullptr; | |||
#else | |||
return nullptr; | |||
#endif | |||
} | |||
//============================================================================== | |||
@@ -57,6 +57,11 @@ | |||
#undef KeyPress | |||
#endif | |||
#if ! JUCE_WINDOWS && ! JUCE_MAC | |||
#undef JUCE_PLUGINHOST_VST3 | |||
#define JUCE_PLUGINHOST_VST3 0 | |||
#endif | |||
//============================================================================== | |||
namespace juce | |||
{ | |||
@@ -72,6 +77,7 @@ static inline bool arrayContainsPlugin (const OwnedArray<PluginDescription>& lis | |||
} | |||
#if JUCE_MAC | |||
//============================================================================== | |||
struct AutoResizingNSViewComponent : public NSViewComponent, | |||
private AsyncUpdater | |||
{ | |||
@@ -95,6 +101,38 @@ struct AutoResizingNSViewComponent : public NSViewComponent, | |||
bool recursive; | |||
}; | |||
//============================================================================== | |||
struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent, | |||
private Timer | |||
{ | |||
AutoResizingNSViewComponentWithParent() | |||
{ | |||
NSView* v = [[NSView alloc] init]; | |||
setView (v); | |||
[v release]; | |||
startTimer (100); | |||
} | |||
void timerCallback() override | |||
{ | |||
if (NSView* parent = (NSView*) getView()) | |||
{ | |||
if (NSView* child = [[parent subviews] firstObject]) | |||
{ | |||
NSRect f = [parent frame]; | |||
NSSize newSize = [child frame].size; | |||
if (f.size.width != newSize.width || f.size.height != newSize.height) | |||
{ | |||
f.size = newSize; | |||
[parent setFrame: f]; | |||
} | |||
} | |||
} | |||
} | |||
}; | |||
#endif | |||
#if JUCE_CLANG | |||
@@ -44,7 +44,7 @@ | |||
Enables the VST3 audio plugin hosting classes. This requires the Steinberg VST3 SDK to be | |||
installed on your machine. | |||
@see VSTPluginFormat, VVST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_AU | |||
@see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_AU | |||
*/ | |||
#ifndef JUCE_PLUGINHOST_VST3 | |||
#define JUCE_PLUGINHOST_VST3 0 | |||
@@ -60,7 +60,7 @@ | |||
#endif | |||
#if ! (JUCE_PLUGINHOST_AU || JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3) | |||
// #error "You need to set either the JUCE_PLUGINHOST_AU anr/or JUCE_PLUGINHOST_VST flags if you're using this module!" | |||
// #error "You need to set either the JUCE_PLUGINHOST_AU and/or JUCE_PLUGINHOST_VST and/or JUCE_PLUGINHOST_VST3 flags if you're using this module!" | |||
#endif | |||
#if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT) | |||
@@ -44,11 +44,9 @@ AudioProcessor::AudioProcessor() | |||
AudioProcessor::~AudioProcessor() | |||
{ | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
// ooh, nasty - the editor should have been deleted before the filter | |||
// that it refers to is deleted.. | |||
jassert (activeEditor == nullptr); | |||
#endif | |||
#if JUCE_DEBUG | |||
// This will fail if you've called beginParameterChangeGesture() for one | |||
@@ -221,15 +219,12 @@ void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noe | |||
{ | |||
const ScopedLock sl (callbackLock); | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
if (activeEditor == editor) | |||
activeEditor = nullptr; | |||
#endif | |||
} | |||
AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() | |||
{ | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
if (activeEditor != nullptr) | |||
return activeEditor; | |||
@@ -248,9 +243,6 @@ AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() | |||
} | |||
return ed; | |||
#else | |||
return nullptr; | |||
#endif | |||
} | |||
//============================================================================== | |||
@@ -366,13 +366,11 @@ public: | |||
*/ | |||
virtual bool hasEditor() const = 0; | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
//============================================================================== | |||
/** Returns the active editor, if there is one. | |||
Bear in mind this can return nullptr, even if an editor has previously been opened. | |||
*/ | |||
AudioProcessorEditor* getActiveEditor() const noexcept { return activeEditor; } | |||
#endif | |||
/** Returns the active editor, or if there isn't one, it will create one. | |||
This may call createEditor() internally to create the component. | |||
@@ -616,6 +614,7 @@ public: | |||
{ | |||
wrapperType_Undefined = 0, | |||
wrapperType_VST, | |||
wrapperType_VST3, | |||
wrapperType_AudioUnit, | |||
wrapperType_RTAS, | |||
wrapperType_AAX, | |||
@@ -658,9 +657,7 @@ protected: | |||
private: | |||
Array<AudioProcessorListener*> listeners; | |||
#ifndef JUCE_PLUGIN_HOST_NO_UI | |||
Component::SafePointer<AudioProcessorEditor> activeEditor; | |||
#endif | |||
double sampleRate; | |||
int blockSize, numInputChannels, numOutputChannels, latencySamples; | |||
bool suspended, nonRealtime; | |||
@@ -202,6 +202,14 @@ void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& f | |||
} | |||
} | |||
} | |||
scanFinished(); | |||
} | |||
void KnownPluginList::scanFinished() | |||
{ | |||
if (scanner != nullptr) | |||
scanner->scanFinished(); | |||
} | |||
const StringArray& KnownPluginList::getBlacklistedFiles() const | |||
@@ -280,10 +288,16 @@ void KnownPluginList::sort (const SortMethod method, bool forwards) | |||
{ | |||
if (method != defaultOrder) | |||
{ | |||
Array<PluginDescription*> oldOrder, newOrder; | |||
oldOrder.addArray (types); | |||
PluginSorter sorter (method, forwards); | |||
types.sort (sorter, true); | |||
sendChangeMessage(); | |||
newOrder.addArray (types); | |||
if (oldOrder != newOrder) | |||
sendChangeMessage(); | |||
} | |||
} | |||
@@ -523,3 +537,13 @@ int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const | |||
//============================================================================== | |||
KnownPluginList::CustomScanner::CustomScanner() {} | |||
KnownPluginList::CustomScanner::~CustomScanner() {} | |||
void KnownPluginList::CustomScanner::scanFinished() {} | |||
bool KnownPluginList::CustomScanner::shouldExit() const noexcept | |||
{ | |||
if (ThreadPoolJob* job = ThreadPoolJob::getCurrentThreadPoolJob()) | |||
return job->shouldExit(); | |||
return false; | |||
} |
@@ -97,6 +97,9 @@ public: | |||
OwnedArray <PluginDescription>& typesFound, | |||
AudioPluginFormat& formatToUse); | |||
/** Tells a custom scanner that a scan has finished, and it can release any resources. */ | |||
void scanFinished(); | |||
/** Returns true if the specified file is already known about and if it | |||
hasn't been modified since our entry was created. | |||
*/ | |||
@@ -170,8 +173,8 @@ public: | |||
struct PluginTree | |||
{ | |||
String folder; /**< The name of this folder in the tree */ | |||
OwnedArray <PluginTree> subFolders; | |||
Array <const PluginDescription*> plugins; | |||
OwnedArray<PluginTree> subFolders; | |||
Array<const PluginDescription*> plugins; | |||
}; | |||
/** Creates a PluginTree object containing all the known plugins. */ | |||
@@ -190,9 +193,21 @@ public: | |||
virtual bool findPluginTypesFor (AudioPluginFormat& format, | |||
OwnedArray <PluginDescription>& result, | |||
const String& fileOrIdentifier) = 0; | |||
/** Called when a scan has finished, to allow clean-up of resources. */ | |||
virtual void scanFinished(); | |||
/** Returns true if the current scan should be abandoned. | |||
Any blocking methods should check this value repeatedly and return if | |||
if becomes true. | |||
*/ | |||
bool shouldExit() const noexcept; | |||
}; | |||
void setCustomScanner (CustomScanner* scanner); | |||
/** Supplies a custom scanner to be used in future scans. | |||
The KnownPluginList will take ownership of the object passed in. | |||
*/ | |||
void setCustomScanner (CustomScanner*); | |||
private: | |||
//============================================================================== | |||
@@ -63,6 +63,7 @@ PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, | |||
PluginDirectoryScanner::~PluginDirectoryScanner() | |||
{ | |||
list.scanFinished(); | |||
} | |||
//============================================================================== | |||
@@ -162,6 +162,7 @@ PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, Kno | |||
setSize (400, 600); | |||
list.addChangeListener (this); | |||
updateList(); | |||
table.getHeader().reSortTable(); | |||
PluginDirectoryScanner::applyBlacklistingsFromDeadMansPedal (list, deadMansPedalFile); | |||
deadMansPedalFile.deleteFile(); | |||
@@ -196,6 +197,7 @@ void PluginListComponent::resized() | |||
void PluginListComponent::changeListenerCallback (ChangeBroadcaster*) | |||
{ | |||
table.getHeader().reSortTable(); | |||
updateList(); | |||
} | |||
@@ -60,7 +60,7 @@ struct DefaultHashFunctions | |||
@code | |||
struct MyHashGenerator | |||
{ | |||
int generateHash (MyKeyType key, int upperLimit) | |||
int generateHash (MyKeyType key, int upperLimit) const | |||
{ | |||
// The function must return a value 0 <= x < upperLimit | |||
return someFunctionOfMyKeyType (key) % upperLimit; | |||
@@ -141,7 +141,7 @@ int NamedValueSet::size() const noexcept | |||
return values.size(); | |||
} | |||
const var& NamedValueSet::operator[] (const Identifier name) const | |||
const var& NamedValueSet::operator[] (Identifier name) const | |||
{ | |||
for (NamedValue* i = values; i != nullptr; i = i->nextListItem) | |||
if (i->name == name) | |||
@@ -150,7 +150,7 @@ const var& NamedValueSet::operator[] (const Identifier name) const | |||
return var::null; | |||
} | |||
var NamedValueSet::getWithDefault (const Identifier name, const var& defaultReturnValue) const | |||
var NamedValueSet::getWithDefault (Identifier name, const var& defaultReturnValue) const | |||
{ | |||
if (const var* const v = getVarPointer (name)) | |||
return *v; | |||
@@ -158,7 +158,7 @@ var NamedValueSet::getWithDefault (const Identifier name, const var& defaultRetu | |||
return defaultReturnValue; | |||
} | |||
var* NamedValueSet::getVarPointer (const Identifier name) const noexcept | |||
var* NamedValueSet::getVarPointer (Identifier name) const noexcept | |||
{ | |||
for (NamedValue* i = values; i != nullptr; i = i->nextListItem) | |||
if (i->name == name) | |||
@@ -168,7 +168,7 @@ var* NamedValueSet::getVarPointer (const Identifier name) const noexcept | |||
} | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
bool NamedValueSet::set (const Identifier name, var&& newValue) | |||
bool NamedValueSet::set (Identifier name, var&& newValue) | |||
{ | |||
LinkedListPointer<NamedValue>* i = &values; | |||
@@ -193,7 +193,7 @@ bool NamedValueSet::set (const Identifier name, var&& newValue) | |||
} | |||
#endif | |||
bool NamedValueSet::set (const Identifier name, const var& newValue) | |||
bool NamedValueSet::set (Identifier name, const var& newValue) | |||
{ | |||
LinkedListPointer<NamedValue>* i = &values; | |||
@@ -217,12 +217,27 @@ bool NamedValueSet::set (const Identifier name, const var& newValue) | |||
return true; | |||
} | |||
bool NamedValueSet::contains (const Identifier name) const | |||
bool NamedValueSet::contains (Identifier name) const | |||
{ | |||
return getVarPointer (name) != nullptr; | |||
} | |||
bool NamedValueSet::remove (const Identifier name) | |||
int NamedValueSet::indexOf (Identifier name) const noexcept | |||
{ | |||
int index = 0; | |||
for (NamedValue* i = values; i != nullptr; i = i->nextListItem) | |||
{ | |||
if (i->name == name) | |||
return index; | |||
++index; | |||
} | |||
return -1; | |||
} | |||
bool NamedValueSet::remove (Identifier name) | |||
{ | |||
LinkedListPointer<NamedValue>* i = &values; | |||
@@ -245,7 +260,7 @@ bool NamedValueSet::remove (const Identifier name) | |||
return false; | |||
} | |||
const Identifier NamedValueSet::getName (const int index) const | |||
Identifier NamedValueSet::getName (const int index) const | |||
{ | |||
const NamedValue* const v = values[index]; | |||
jassert (v != nullptr); | |||
@@ -67,46 +67,49 @@ public: | |||
If the name isn't found, this will return a void variant. | |||
@see getProperty | |||
*/ | |||
const var& operator[] (const Identifier name) const; | |||
const var& operator[] (Identifier name) const; | |||
/** Tries to return the named value, but if no such value is found, this will | |||
instead return the supplied default value. | |||
*/ | |||
var getWithDefault (const Identifier name, const var& defaultReturnValue) const; | |||
var getWithDefault (Identifier name, const var& defaultReturnValue) const; | |||
/** Changes or adds a named value. | |||
@returns true if a value was changed or added; false if the | |||
value was already set the the value passed-in. | |||
*/ | |||
bool set (const Identifier name, const var& newValue); | |||
bool set (Identifier name, const var& newValue); | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
/** Changes or adds a named value. | |||
@returns true if a value was changed or added; false if the | |||
value was already set the the value passed-in. | |||
*/ | |||
bool set (const Identifier name, var&& newValue); | |||
bool set (Identifier name, var&& newValue); | |||
#endif | |||
/** Returns true if the set contains an item with the specified name. */ | |||
bool contains (const Identifier name) const; | |||
bool contains (Identifier name) const; | |||
/** Removes a value from the set. | |||
@returns true if a value was removed; false if there was no value | |||
with the name that was given. | |||
*/ | |||
bool remove (const Identifier name); | |||
bool remove (Identifier name); | |||
/** Returns the name of the value at a given index. | |||
The index must be between 0 and size() - 1. | |||
*/ | |||
const Identifier getName (int index) const; | |||
Identifier getName (int index) const; | |||
/** Returns the value of the item at a given index. | |||
The index must be between 0 and size() - 1. | |||
*/ | |||
const var& getValueAt (int index) const; | |||
/** Returns the index of the given name, or -1 if it's not found. */ | |||
int indexOf (Identifier name) const noexcept; | |||
/** Removes all values. */ | |||
void clear(); | |||
@@ -117,7 +120,7 @@ public: | |||
Do not use this method unless you really need access to the internal var object | |||
for some reason - for normal reading and writing always prefer operator[]() and set(). | |||
*/ | |||
var* getVarPointer (const Identifier name) const noexcept; | |||
var* getVarPointer (Identifier name) const noexcept; | |||
//============================================================================== | |||
/** Sets properties to the values of all of an XML element's attributes. */ | |||
@@ -151,7 +151,6 @@ namespace juce | |||
#include "text/juce_StringPairArray.cpp" | |||
#include "text/juce_StringPool.cpp" | |||
#include "text/juce_TextDiff.cpp" | |||
#include "threads/juce_ChildProcess.cpp" | |||
#include "threads/juce_ReadWriteLock.cpp" | |||
#include "threads/juce_Thread.cpp" | |||
#include "threads/juce_ThreadPool.cpp" | |||
@@ -218,6 +217,7 @@ namespace juce | |||
#endif | |||
#include "threads/juce_ChildProcess.cpp" | |||
#include "threads/juce_HighResolutionTimer.cpp" | |||
} |
@@ -1053,7 +1053,7 @@ public: | |||
close (pipeHandle); | |||
} | |||
bool isRunning() const | |||
bool isRunning() const noexcept | |||
{ | |||
if (childPID != 0) | |||
{ | |||
@@ -1065,7 +1065,7 @@ public: | |||
return false; | |||
} | |||
int read (void* const dest, const int numBytes) | |||
int read (void* const dest, const int numBytes) noexcept | |||
{ | |||
jassert (dest != nullptr); | |||
@@ -1082,11 +1082,25 @@ public: | |||
return 0; | |||
} | |||
bool killProcess() const | |||
bool killProcess() const noexcept | |||
{ | |||
return ::kill (childPID, SIGKILL) == 0; | |||
} | |||
uint32 getExitCode() const noexcept | |||
{ | |||
if (childPID != 0) | |||
{ | |||
int childState = 0; | |||
const int pid = waitpid (childPID, &childState, WNOHANG); | |||
if (pid >= 0 && WIFEXITED (childState)) | |||
return WEXITSTATUS (childState); | |||
} | |||
return 0; | |||
} | |||
int childPID; | |||
private: | |||
@@ -1114,21 +1128,6 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) | |||
return activeProcess != nullptr; | |||
} | |||
bool ChildProcess::isRunning() const | |||
{ | |||
return activeProcess != nullptr && activeProcess->isRunning(); | |||
} | |||
int ChildProcess::readProcessOutput (void* dest, int numBytes) | |||
{ | |||
return activeProcess != nullptr ? activeProcess->read (dest, numBytes) : 0; | |||
} | |||
bool ChildProcess::kill() | |||
{ | |||
return activeProcess == nullptr || activeProcess->killProcess(); | |||
} | |||
//============================================================================== | |||
struct HighResolutionTimer::Pimpl | |||
{ | |||
@@ -482,12 +482,12 @@ public: | |||
CloseHandle (writePipe); | |||
} | |||
bool isRunning() const | |||
bool isRunning() const noexcept | |||
{ | |||
return WaitForSingleObject (processInfo.hProcess, 0) != WAIT_OBJECT_0; | |||
} | |||
int read (void* dest, int numNeeded) const | |||
int read (void* dest, int numNeeded) const noexcept | |||
{ | |||
int total = 0; | |||
@@ -522,11 +522,18 @@ public: | |||
return total; | |||
} | |||
bool killProcess() const | |||
bool killProcess() const noexcept | |||
{ | |||
return TerminateProcess (processInfo.hProcess, 0) != FALSE; | |||
} | |||
uint32 getExitCode() const noexcept | |||
{ | |||
DWORD exitCode = 0; | |||
GetExitCodeProcess (processInfo.hProcess, &exitCode); | |||
return (uint32) exitCode; | |||
} | |||
bool ok; | |||
private: | |||
@@ -551,21 +558,6 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) | |||
return start (args.joinIntoString (" "), streamFlags); | |||
} | |||
bool ChildProcess::isRunning() const | |||
{ | |||
return activeProcess != nullptr && activeProcess->isRunning(); | |||
} | |||
int ChildProcess::readProcessOutput (void* dest, int numBytes) | |||
{ | |||
return activeProcess != nullptr ? activeProcess->read (dest, numBytes) : 0; | |||
} | |||
bool ChildProcess::kill() | |||
{ | |||
return activeProcess == nullptr || activeProcess->killProcess(); | |||
} | |||
//============================================================================== | |||
struct HighResolutionTimer::Pimpl | |||
{ | |||
@@ -174,12 +174,16 @@ bool OutputStream::writeDoubleBigEndian (double value) | |||
bool OutputStream::writeString (const String& text) | |||
{ | |||
#if (JUCE_STRING_UTF_TYPE == 8) | |||
return write (text.toRawUTF8(), text.getNumBytesAsUTF8() + 1); | |||
#else | |||
// (This avoids using toUTF8() to prevent the memory bloat that it would leave behind | |||
// if lots of large, persistent strings were to be written to streams). | |||
const size_t numBytes = text.getNumBytesAsUTF8() + 1; | |||
HeapBlock<char> temp (numBytes); | |||
text.copyToUTF8 (temp, numBytes); | |||
return write (temp, numBytes); | |||
#endif | |||
} | |||
bool OutputStream::writeText (const String& text, const bool asUTF16, | |||
@@ -84,7 +84,7 @@ public: | |||
because there's no other way to represent unicode strings in a way that isn't dependent | |||
on the compiler, source code editor and platform. | |||
This will use up the the first maxChars characters of the string (or less if the string | |||
This will use up to the first maxChars characters of the string (or less if the string | |||
is actually shorter). | |||
*/ | |||
String (const char* text, size_t maxChars); | |||
@@ -29,6 +29,26 @@ | |||
ChildProcess::ChildProcess() {} | |||
ChildProcess::~ChildProcess() {} | |||
bool ChildProcess::isRunning() const | |||
{ | |||
return activeProcess != nullptr && activeProcess->isRunning(); | |||
} | |||
int ChildProcess::readProcessOutput (void* dest, int numBytes) | |||
{ | |||
return activeProcess != nullptr ? activeProcess->read (dest, numBytes) : 0; | |||
} | |||
bool ChildProcess::kill() | |||
{ | |||
return activeProcess == nullptr || activeProcess->killProcess(); | |||
} | |||
uint32 ChildProcess::getExitCode() const | |||
{ | |||
return activeProcess != nullptr ? activeProcess->getExitCode() : 0; | |||
} | |||
bool ChildProcess::waitForProcessToFinish (const int timeoutMs) const | |||
{ | |||
const uint32 timeoutTime = Time::getMillisecondCounter() + (uint32) timeoutMs; | |||
@@ -97,6 +97,9 @@ public: | |||
/** Blocks until the process is no longer running. */ | |||
bool waitForProcessToFinish (int timeoutMs) const; | |||
/** If the process has finished, this returns its exit code. */ | |||
uint32 getExitCode() const; | |||
/** Attempts to kill the child process. | |||
Returns true if it succeeded. Trying to read from the process after calling this may | |||
result in undefined behaviour. | |||
@@ -26,12 +26,31 @@ | |||
============================================================================== | |||
*/ | |||
class ThreadPool::ThreadPoolThread : public Thread | |||
{ | |||
public: | |||
ThreadPoolThread (ThreadPool& p) | |||
: Thread ("Pool"), currentJob (nullptr), pool (p) | |||
{ | |||
} | |||
void run() override | |||
{ | |||
while (! threadShouldExit()) | |||
if (! pool.runNextJob (*this)) | |||
wait (500); | |||
} | |||
ThreadPoolJob* volatile currentJob; | |||
ThreadPool& pool; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) | |||
}; | |||
//============================================================================== | |||
ThreadPoolJob::ThreadPoolJob (const String& name) | |||
: jobName (name), | |||
pool (nullptr), | |||
shouldStop (false), | |||
isActive (false), | |||
shouldBeDeleted (false) | |||
: jobName (name), pool (nullptr), | |||
shouldStop (false), isActive (false), shouldBeDeleted (false) | |||
{ | |||
} | |||
@@ -57,30 +76,13 @@ void ThreadPoolJob::signalJobShouldExit() | |||
shouldStop = true; | |||
} | |||
//============================================================================== | |||
class ThreadPool::ThreadPoolThread : public Thread | |||
ThreadPoolJob* ThreadPoolJob::getCurrentThreadPoolJob() | |||
{ | |||
public: | |||
ThreadPoolThread (ThreadPool& pool_) | |||
: Thread ("Pool"), | |||
pool (pool_) | |||
{ | |||
} | |||
void run() override | |||
{ | |||
while (! threadShouldExit()) | |||
{ | |||
if (! pool.runNextJob()) | |||
wait (500); | |||
} | |||
} | |||
if (ThreadPool::ThreadPoolThread* t = dynamic_cast<ThreadPool::ThreadPoolThread*> (Thread::getCurrentThread())) | |||
return t->currentJob; | |||
private: | |||
ThreadPool& pool; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) | |||
}; | |||
return nullptr; | |||
} | |||
//============================================================================== | |||
ThreadPool::ThreadPool (const int numThreads) | |||
@@ -164,8 +166,7 @@ bool ThreadPool::isJobRunning (const ThreadPoolJob* const job) const | |||
return jobs.contains (const_cast <ThreadPoolJob*> (job)) && job->isActive; | |||
} | |||
bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, | |||
const int timeOutMs) const | |||
bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, const int timeOutMs) const | |||
{ | |||
if (job != nullptr) | |||
{ | |||
@@ -215,7 +216,7 @@ bool ThreadPool::removeJob (ThreadPoolJob* const job, | |||
} | |||
bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, | |||
ThreadPool::JobSelector* selectedJobsToRemove) | |||
ThreadPool::JobSelector* const selectedJobsToRemove) | |||
{ | |||
Array <ThreadPoolJob*> jobsToWaitFor; | |||
@@ -328,46 +329,49 @@ ThreadPoolJob* ThreadPool::pickNextJobToRun() | |||
return nullptr; | |||
} | |||
bool ThreadPool::runNextJob() | |||
bool ThreadPool::runNextJob (ThreadPoolThread& thread) | |||
{ | |||
ThreadPoolJob* const job = pickNextJobToRun(); | |||
if (job == nullptr) | |||
return false; | |||
ThreadPoolJob::JobStatus result = ThreadPoolJob::jobHasFinished; | |||
JUCE_TRY | |||
if (ThreadPoolJob* const job = pickNextJobToRun()) | |||
{ | |||
result = job->runJob(); | |||
} | |||
JUCE_CATCH_ALL_ASSERT | |||
ThreadPoolJob::JobStatus result = ThreadPoolJob::jobHasFinished; | |||
thread.currentJob = job; | |||
OwnedArray<ThreadPoolJob> deletionList; | |||
JUCE_TRY | |||
{ | |||
result = job->runJob(); | |||
} | |||
JUCE_CATCH_ALL_ASSERT | |||
{ | |||
const ScopedLock sl (lock); | |||
thread.currentJob = nullptr; | |||
OwnedArray<ThreadPoolJob> deletionList; | |||
if (jobs.contains (job)) | |||
{ | |||
job->isActive = false; | |||
const ScopedLock sl (lock); | |||
if (result != ThreadPoolJob::jobNeedsRunningAgain || job->shouldStop) | |||
if (jobs.contains (job)) | |||
{ | |||
jobs.removeFirstMatchingValue (job); | |||
addToDeleteList (deletionList, job); | |||
job->isActive = false; | |||
jobFinishedSignal.signal(); | |||
} | |||
else | |||
{ | |||
// move the job to the end of the queue if it wants another go | |||
jobs.move (jobs.indexOf (job), -1); | |||
if (result != ThreadPoolJob::jobNeedsRunningAgain || job->shouldStop) | |||
{ | |||
jobs.removeFirstMatchingValue (job); | |||
addToDeleteList (deletionList, job); | |||
jobFinishedSignal.signal(); | |||
} | |||
else | |||
{ | |||
// move the job to the end of the queue if it wants another go | |||
jobs.move (jobs.indexOf (job), -1); | |||
} | |||
} | |||
} | |||
return true; | |||
} | |||
return true; | |||
return false; | |||
} | |||
void ThreadPool::addToDeleteList (OwnedArray<ThreadPoolJob>& deletionList, ThreadPoolJob* const job) const | |||
@@ -119,6 +119,12 @@ public: | |||
*/ | |||
void signalJobShouldExit(); | |||
//============================================================================== | |||
/** If the calling thread is being invoked inside a runJob() method, this will | |||
return the ThreadPoolJob that it belongs to. | |||
*/ | |||
static ThreadPoolJob* getCurrentThreadPoolJob(); | |||
//============================================================================== | |||
private: | |||
friend class ThreadPool; | |||
@@ -290,6 +296,7 @@ private: | |||
Array <ThreadPoolJob*> jobs; | |||
class ThreadPoolThread; | |||
friend class ThreadPoolJob; | |||
friend class ThreadPoolThread; | |||
friend struct ContainerDeletePolicy<ThreadPoolThread>; | |||
OwnedArray<ThreadPoolThread> threads; | |||
@@ -297,7 +304,7 @@ private: | |||
CriticalSection lock; | |||
WaitableEvent jobFinishedSignal; | |||
bool runNextJob(); | |||
bool runNextJob (ThreadPoolThread&); | |||
ThreadPoolJob* pickNextJobToRun(); | |||
void addToDeleteList (OwnedArray<ThreadPoolJob>&, ThreadPoolJob*) const; | |||
void createThreads (int numThreads); | |||
@@ -371,8 +371,8 @@ void XmlDocument::readQuotedString (String& result) | |||
} | |||
else if (character == 0) | |||
{ | |||
outOfData = true; | |||
setLastError ("unmatched quotes", false); | |||
outOfData = true; | |||
break; | |||
} | |||
@@ -432,7 +432,7 @@ XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) | |||
++input; | |||
if (alsoParseSubElements) | |||
readChildElements (node); | |||
readChildElements (*node); | |||
break; | |||
} | |||
@@ -487,9 +487,9 @@ XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) | |||
return node; | |||
} | |||
void XmlDocument::readChildElements (XmlElement* parent) | |||
void XmlDocument::readChildElements (XmlElement& parent) | |||
{ | |||
LinkedListPointer<XmlElement>::Appender childAppender (parent->firstChildElement); | |||
LinkedListPointer<XmlElement>::Appender childAppender (parent.firstChildElement); | |||
for (;;) | |||
{ | |||
@@ -563,7 +563,25 @@ void XmlDocument::readChildElements (XmlElement* parent) | |||
const juce_wchar c = *input; | |||
if (c == '<') | |||
{ | |||
if (input[1] == '!' && input[2] == '-' && input[3] == '-') | |||
{ | |||
input += 4; | |||
const int closeComment = input.indexOf (CharPointer_ASCII ("-->")); | |||
if (closeComment < 0) | |||
{ | |||
setLastError ("unterminated comment", false); | |||
outOfData = true; | |||
return; | |||
} | |||
input += closeComment + 3; | |||
continue; | |||
} | |||
break; | |||
} | |||
if (c == 0) | |||
{ | |||
@@ -156,23 +156,23 @@ private: | |||
String lastError, dtdText; | |||
StringArray tokenisedDTD; | |||
bool needToLoadDTD, ignoreEmptyTextElements; | |||
ScopedPointer <InputSource> inputSource; | |||
ScopedPointer<InputSource> inputSource; | |||
XmlElement* parseDocumentElement (String::CharPointerType, bool outer); | |||
void setLastError (const String& desc, bool carryOn); | |||
void setLastError (const String&, bool carryOn); | |||
bool parseHeader(); | |||
bool parseDTD(); | |||
void skipNextWhiteSpace(); | |||
juce_wchar readNextChar() noexcept; | |||
XmlElement* readNextElement (bool alsoParseSubElements); | |||
void readChildElements (XmlElement* parent); | |||
void readQuotedString (String& result); | |||
void readEntity (String& result); | |||
String getFileContents (const String& filename) const; | |||
String expandEntity (const String& entity); | |||
String expandExternalEntity (const String& entity); | |||
String getParameterEntity (const String& entity); | |||
void readChildElements (XmlElement&); | |||
void readQuotedString (String&); | |||
void readEntity (String&); | |||
String getFileContents (const String&) const; | |||
String expandEntity (const String&); | |||
String expandExternalEntity (const String&); | |||
String getParameterEntity (const String&); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlDocument) | |||
}; | |||
@@ -55,7 +55,7 @@ XmlElement::XmlElement (const String& tag) noexcept | |||
jassert (tag.containsNonWhitespaceChars()) | |||
// The tag can't contain spaces or other characters that would create invalid XML! | |||
jassert (! tag.containsAnyOf (" <>/&")); | |||
jassert (! tag.containsAnyOf (" <>/&(){}")); | |||
} | |||
XmlElement::XmlElement (int /*dummy*/) noexcept | |||
@@ -215,18 +215,18 @@ bool PropertiesFile::loadAsXml() | |||
bool PropertiesFile::saveAsXml() | |||
{ | |||
XmlElement doc (PropertyFileConstants::fileTag); | |||
const StringPairArray& props = getAllProperties(); | |||
for (int i = 0; i < getAllProperties().size(); ++i) | |||
for (int i = 0; i < props.size(); ++i) | |||
{ | |||
XmlElement* const e = doc.createNewChildElement (PropertyFileConstants::valueTag); | |||
e->setAttribute (PropertyFileConstants::nameAttribute, getAllProperties().getAllKeys() [i]); | |||
e->setAttribute (PropertyFileConstants::nameAttribute, props.getAllKeys() [i]); | |||
// if the value seems to contain xml, store it as such.. | |||
if (XmlElement* const childElement = XmlDocument::parse (getAllProperties().getAllValues() [i])) | |||
if (XmlElement* const childElement = XmlDocument::parse (props.getAllValues() [i])) | |||
e->addChildElement (childElement); | |||
else | |||
e->setAttribute (PropertyFileConstants::valueAttribute, | |||
getAllProperties().getAllValues() [i]); | |||
e->setAttribute (PropertyFileConstants::valueAttribute, props.getAllValues() [i]); | |||
} | |||
ProcessScopedLock pl (createProcessLock()); | |||
@@ -311,14 +311,17 @@ bool PropertiesFile::saveAsBinary() | |||
out->writeInt (PropertyFileConstants::magicNumber); | |||
} | |||
const int numProperties = getAllProperties().size(); | |||
const StringPairArray& props = getAllProperties(); | |||
const int numProperties = props.size(); | |||
const StringArray& keys = props.getAllKeys(); | |||
const StringArray& values = props.getAllValues(); | |||
out->writeInt (numProperties); | |||
for (int i = 0; i < numProperties; ++i) | |||
{ | |||
out->writeString (getAllProperties().getAllKeys() [i]); | |||
out->writeString (getAllProperties().getAllValues() [i]); | |||
out->writeString (keys[i]); | |||
out->writeString (values[i]); | |||
} | |||
out = nullptr; | |||
@@ -0,0 +1,259 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2013 - Raw Material Software Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
enum { magicMastSlaveConnectionHeader = 0x712baf04 }; | |||
static const char* startMessage = "__ipc_st"; | |||
static const char* killMessage = "__ipc_k_"; | |||
static const char* pingMessage = "__ipc_p_"; | |||
enum { specialMessageSize = 8 }; | |||
static String getCommandLinePrefix (const String& commandLineUniqueID) | |||
{ | |||
return "--" + commandLineUniqueID + ":"; | |||
} | |||
//============================================================================== | |||
// This thread sends and receives ping messages every second, so that it | |||
// can find out if the other process has stopped running. | |||
struct ChildProcessPingThread : public Thread, | |||
private AsyncUpdater | |||
{ | |||
ChildProcessPingThread() : Thread ("IPC ping"), timeoutMs (8000) | |||
{ | |||
pingReceived(); | |||
} | |||
static bool isPingMessage (const MemoryBlock& m) noexcept | |||
{ | |||
return memcmp (m.getData(), pingMessage, specialMessageSize) == 0; | |||
} | |||
void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; } | |||
void triggerConnectionLostMessage() { triggerAsyncUpdate(); } | |||
virtual bool sendPingMessage (const MemoryBlock&) = 0; | |||
virtual void pingFailed() = 0; | |||
int timeoutMs; | |||
private: | |||
Atomic<int> countdown; | |||
void handleAsyncUpdate() override { pingFailed(); } | |||
void run() override | |||
{ | |||
while (! threadShouldExit()) | |||
{ | |||
if (--countdown <= 0 || ! sendPingMessage (MemoryBlock (pingMessage, specialMessageSize))) | |||
{ | |||
triggerConnectionLostMessage(); | |||
break; | |||
} | |||
wait (1000); | |||
} | |||
} | |||
JUCE_DECLARE_NON_COPYABLE (ChildProcessPingThread) | |||
}; | |||
//============================================================================== | |||
struct ChildProcessMaster::Connection : public InterprocessConnection, | |||
private ChildProcessPingThread | |||
{ | |||
Connection (ChildProcessMaster& m, const String& pipeName) | |||
: InterprocessConnection (false, magicMastSlaveConnectionHeader), owner (m) | |||
{ | |||
if (createPipe (pipeName, timeoutMs)) | |||
startThread (4); | |||
} | |||
~Connection() | |||
{ | |||
stopThread (10000); | |||
} | |||
private: | |||
void connectionMade() override {} | |||
void connectionLost() override { owner.handleConnectionLost(); } | |||
bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToSlave (m); } | |||
void pingFailed() override { connectionLost(); } | |||
void messageReceived (const MemoryBlock& m) override | |||
{ | |||
pingReceived(); | |||
if (m.getSize() != specialMessageSize || ! isPingMessage (m)) | |||
owner.handleMessageFromSlave (m); | |||
} | |||
ChildProcessMaster& owner; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection) | |||
}; | |||
//============================================================================== | |||
ChildProcessMaster::ChildProcessMaster() {} | |||
ChildProcessMaster::~ChildProcessMaster() | |||
{ | |||
if (connection != nullptr) | |||
{ | |||
sendMessageToSlave (MemoryBlock (killMessage, specialMessageSize)); | |||
connection->disconnect(); | |||
connection = nullptr; | |||
} | |||
} | |||
void ChildProcessMaster::handleConnectionLost() {} | |||
bool ChildProcessMaster::sendMessageToSlave (const MemoryBlock& mb) | |||
{ | |||
if (connection != nullptr) | |||
return connection->sendMessage (mb); | |||
jassertfalse; // this can only be used when the connection is active! | |||
return false; | |||
} | |||
bool ChildProcessMaster::launchSlaveProcess (const File& executable, const String& commandLineUniqueID) | |||
{ | |||
connection = nullptr; | |||
jassert (childProcess.kill()); | |||
const String pipeName ("p" + String::toHexString (Random().nextInt64())); | |||
StringArray args; | |||
args.add (executable.getFullPathName()); | |||
args.add (getCommandLinePrefix (commandLineUniqueID) + pipeName); | |||
if (childProcess.start (args)) | |||
{ | |||
connection = new Connection (*this, pipeName); | |||
if (connection->isConnected()) | |||
{ | |||
sendMessageToSlave (MemoryBlock (startMessage, specialMessageSize)); | |||
return true; | |||
} | |||
connection = nullptr; | |||
} | |||
return false; | |||
} | |||
//============================================================================== | |||
struct ChildProcessSlave::Connection : public InterprocessConnection, | |||
private ChildProcessPingThread | |||
{ | |||
Connection (ChildProcessSlave& p, const String& pipeName) | |||
: InterprocessConnection (false, magicMastSlaveConnectionHeader), owner (p) | |||
{ | |||
connectToPipe (pipeName, timeoutMs); | |||
startThread (4); | |||
} | |||
~Connection() | |||
{ | |||
stopThread (10000); | |||
} | |||
private: | |||
ChildProcessSlave& owner; | |||
void connectionMade() override {} | |||
void connectionLost() override { owner.handleConnectionLost(); } | |||
bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToMaster (m); } | |||
void pingFailed() override { connectionLost(); } | |||
void messageReceived (const MemoryBlock& m) override | |||
{ | |||
pingReceived(); | |||
if (m.getSize() == specialMessageSize) | |||
{ | |||
if (isPingMessage (m)) | |||
return; | |||
if (memcmp (m.getData(), killMessage, specialMessageSize) == 0) | |||
{ | |||
triggerConnectionLostMessage(); | |||
return; | |||
} | |||
if (memcmp (m.getData(), startMessage, specialMessageSize) == 0) | |||
{ | |||
owner.handleConnectionMade(); | |||
return; | |||
} | |||
} | |||
owner.handleMessageFromMaster (m); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection) | |||
}; | |||
//============================================================================== | |||
ChildProcessSlave::ChildProcessSlave() {} | |||
ChildProcessSlave::~ChildProcessSlave() {} | |||
void ChildProcessSlave::handleConnectionMade() {} | |||
void ChildProcessSlave::handleConnectionLost() {} | |||
bool ChildProcessSlave::sendMessageToMaster (const MemoryBlock& mb) | |||
{ | |||
if (connection != nullptr) | |||
return connection->sendMessage (mb); | |||
jassertfalse; // this can only be used when the connection is active! | |||
return false; | |||
} | |||
bool ChildProcessSlave::initialiseFromCommandLine (const String& commandLine, | |||
const String& commandLineUniqueID) | |||
{ | |||
String prefix (getCommandLinePrefix (commandLineUniqueID)); | |||
if (commandLine.trim().startsWith (prefix)) | |||
{ | |||
String pipeName (commandLine.fromFirstOccurrenceOf (prefix, false, false) | |||
.upToFirstOccurrenceOf (" ", false, false).trim()); | |||
if (pipeName.isNotEmpty()) | |||
{ | |||
connection = new Connection (*this, pipeName); | |||
if (! connection->isConnected()) | |||
connection = nullptr; | |||
} | |||
} | |||
return connection != nullptr; | |||
} |
@@ -0,0 +1,179 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2013 - Raw Material Software Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_CONNECTEDCHILDPROCESS_H_INCLUDED | |||
#define JUCE_CONNECTEDCHILDPROCESS_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Acts as the slave end of a master/slave pair of connected processes. | |||
The ChildProcessSlave and ChildProcessMaster classes make it easy for an app | |||
to spawn a child process, and to manage a 2-way messaging connection to control it. | |||
To use the system, you need to create subclasses of both ChildProcessSlave and | |||
ChildProcessMaster. To instantiate the ChildProcessSlave object, you must | |||
add some code to your main() or JUCEApplication::initialise() function that | |||
calls the initialiseFromCommandLine() method to check the app's command-line | |||
parameters to see whether it's being launched as a child process. If this returns | |||
true then the slave process can be allowed to run, and its handleMessageFromMaster() | |||
method will be called whenever a message arrives. | |||
The juce demo app has a good example of this class in action. | |||
@see ChildProcessMaster, InterprocessConnection, ChildProcess | |||
*/ | |||
class JUCE_API ChildProcessSlave | |||
{ | |||
public: | |||
/** Creates a non-connected slave process. | |||
Use initialiseFromCommandLine to connect to a master process. | |||
*/ | |||
ChildProcessSlave(); | |||
/** Destructor. */ | |||
virtual ~ChildProcessSlave(); | |||
/** This checks some command-line parameters to see whether they were generated by | |||
ChildProcessMaster::launchSlaveProcess(), and if so, connects to that master process. | |||
In an exe that can be used as a child process, you should add some code to your | |||
main() or JUCEApplication::initialise() that calls this method. | |||
The commandLineUniqueID should be a short alphanumeric identifier (no spaces!) | |||
that matches the string passed to ChildProcessMaster::launchSlaveProcess(). | |||
Returns true if the command-line matches and the connection is made successfully. | |||
*/ | |||
bool initialiseFromCommandLine (const String& commandLine, | |||
const String& commandLineUniqueID); | |||
//============================================================================== | |||
/** This will be called to deliver messages from the master process. | |||
The call will probably be made on a background thread, so be careful with your | |||
thread-safety! You may want to respond by sending back a message with | |||
sendMessageToMaster() | |||
*/ | |||
virtual void handleMessageFromMaster (const MemoryBlock&) = 0; | |||
/** This will be called when the master process finishes connecting to this slave. | |||
The call will probably be made on a background thread, so be careful with your thread-safety! | |||
*/ | |||
virtual void handleConnectionMade(); | |||
/** This will be called when the connection to the master process is lost. | |||
The call may be made from any thread (including the message thread). | |||
Typically, if your process only exists to act as a slave, you should probably exit | |||
when this happens. | |||
*/ | |||
virtual void handleConnectionLost(); | |||
/** Tries to send a message to the master process. | |||
This returns true if the message was sent, but doesn't check that it actually gets | |||
delivered at the other end. If successful, the data will emerge in a call to your | |||
ChildProcessMaster::handleMessageFromSlave(). | |||
*/ | |||
bool sendMessageToMaster (const MemoryBlock&); | |||
private: | |||
struct Connection; | |||
friend struct Connection; | |||
friend struct ContainerDeletePolicy<Connection>; | |||
ScopedPointer<Connection> connection; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessSlave) | |||
}; | |||
//============================================================================== | |||
/** | |||
Acts as the master in a master/slave pair of connected processes. | |||
The ChildProcessSlave and ChildProcessMaster classes make it easy for an app | |||
to spawn a child process, and to manage a 2-way messaging connection to control it. | |||
To use the system, you need to create subclasses of both ChildProcessSlave and | |||
ChildProcessMaster. When you want your master process to launch the slave, you | |||
just call launchSlaveProcess(), and it'll attempt to launch the executable that | |||
you specify (which may be the same exe), and assuming it has been set-up to | |||
correctly parse the command-line parameters (see ChildProcessSlave) then a | |||
two-way connection will be created. | |||
The juce demo app has a good example of this class in action. | |||
@see ChildProcessSlave, InterprocessConnection, ChildProcess | |||
*/ | |||
class JUCE_API ChildProcessMaster | |||
{ | |||
public: | |||
/** Creates an uninitialised master process object. | |||
Use launchSlaveProcess to launch and connect to a child process. | |||
*/ | |||
ChildProcessMaster(); | |||
/** Destructor. */ | |||
virtual ~ChildProcessMaster(); | |||
/** Attempts to launch and connect to a slave process. | |||
This will start the given executable, passing it a special command-line | |||
parameter based around the commandLineUniqueID string, which must be a | |||
short alphanumeric string (no spaces!) that identifies your app. The exe | |||
that gets launched must respond by calling ChildProcessSlave::initialiseFromCommandLine() | |||
in its startup code, and must use a matching ID to commandLineUniqueID. | |||
If this all works, the method returns true, and you can begin sending and | |||
receiving messages with the slave process. | |||
*/ | |||
bool launchSlaveProcess (const File& executableToLaunch, | |||
const String& commandLineUniqueID); | |||
/** This will be called to deliver a message from the slave process. | |||
The call will probably be made on a background thread, so be careful with your thread-safety! | |||
*/ | |||
virtual void handleMessageFromSlave (const MemoryBlock&) = 0; | |||
/** This will be called when the slave process dies or is somehow disconnected. | |||
The call will probably be made on a background thread, so be careful with your thread-safety! | |||
*/ | |||
virtual void handleConnectionLost(); | |||
/** Attempts to send a message to the slave process. | |||
This returns true if the message was dispatched, but doesn't check that it actually | |||
gets delivered at the other end. If successful, the data will emerge in a call to | |||
your ChildProcessSlave::handleMessageFromMaster(). | |||
*/ | |||
bool sendMessageToSlave (const MemoryBlock&); | |||
private: | |||
ChildProcess childProcess; | |||
struct Connection; | |||
friend struct Connection; | |||
friend struct ContainerDeletePolicy<Connection>; | |||
ScopedPointer<Connection> connection; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessMaster) | |||
}; | |||
#endif // JUCE_CONNECTEDCHILDPROCESS_H_INCLUDED |
@@ -22,14 +22,27 @@ | |||
============================================================================== | |||
*/ | |||
struct InterprocessConnection::ConnectionThread : public Thread | |||
{ | |||
ConnectionThread (InterprocessConnection& c) : Thread ("JUCE IPC"), owner (c) {} | |||
void run() override { owner.runThread(); } | |||
private: | |||
InterprocessConnection& owner; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionThread); | |||
}; | |||
//============================================================================== | |||
InterprocessConnection::InterprocessConnection (const bool callbacksOnMessageThread, | |||
const uint32 magicMessageHeaderNumber) | |||
: Thread ("Juce IPC connection"), | |||
callbackConnectionState (false), | |||
: callbackConnectionState (false), | |||
useMessageThread (callbacksOnMessageThread), | |||
magicMessageHeader (magicMessageHeaderNumber), | |||
pipeReceiveMessageTimeout (-1) | |||
{ | |||
thread = new ConnectionThread (*this); | |||
} | |||
InterprocessConnection::~InterprocessConnection() | |||
@@ -37,9 +50,9 @@ InterprocessConnection::~InterprocessConnection() | |||
callbackConnectionState = false; | |||
disconnect(); | |||
masterReference.clear(); | |||
thread = nullptr; | |||
} | |||
//============================================================================== | |||
bool InterprocessConnection::connectToSocket (const String& hostName, | |||
const int portNumber, | |||
@@ -53,7 +66,7 @@ bool InterprocessConnection::connectToSocket (const String& hostName, | |||
if (socket->connect (hostName, portNumber, timeOutMillisecs)) | |||
{ | |||
connectionMadeInt(); | |||
startThread(); | |||
thread->startThread(); | |||
return true; | |||
} | |||
else | |||
@@ -99,7 +112,7 @@ bool InterprocessConnection::createPipe (const String& pipeName, const int timeo | |||
void InterprocessConnection::disconnect() | |||
{ | |||
signalThreadShouldExit(); | |||
thread->signalThreadShouldExit(); | |||
{ | |||
const ScopedLock sl (pipeAndSocketLock); | |||
@@ -107,7 +120,7 @@ void InterprocessConnection::disconnect() | |||
if (pipe != nullptr) pipe->close(); | |||
} | |||
stopThread (4000); | |||
thread->stopThread (4000); | |||
deletePipeAndSocket(); | |||
connectionLostInt(); | |||
} | |||
@@ -125,7 +138,7 @@ bool InterprocessConnection::isConnected() const | |||
return ((socket != nullptr && socket->isConnected()) | |||
|| (pipe != nullptr && pipe->isOpen())) | |||
&& isThreadRunning(); | |||
&& thread->isThreadRunning(); | |||
} | |||
String InterprocessConnection::getConnectedHostName() const | |||
@@ -173,7 +186,7 @@ void InterprocessConnection::initialiseWithSocket (StreamingSocket* newSocket) | |||
jassert (socket == nullptr && pipe == nullptr); | |||
socket = newSocket; | |||
connectionMadeInt(); | |||
startThread(); | |||
thread->startThread(); | |||
} | |||
void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) | |||
@@ -181,7 +194,7 @@ void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) | |||
jassert (socket == nullptr && pipe == nullptr); | |||
pipe = newPipe; | |||
connectionMadeInt(); | |||
startThread(); | |||
thread->startThread(); | |||
} | |||
//============================================================================== | |||
@@ -279,7 +292,7 @@ bool InterprocessConnection::readNextMessageInt() | |||
while (bytesInMessage > 0) | |||
{ | |||
if (threadShouldExit()) | |||
if (thread->threadShouldExit()) | |||
return false; | |||
const int numThisTime = jmin (bytesInMessage, 65536); | |||
@@ -311,9 +324,9 @@ bool InterprocessConnection::readNextMessageInt() | |||
return true; | |||
} | |||
void InterprocessConnection::run() | |||
void InterprocessConnection::runThread() | |||
{ | |||
while (! threadShouldExit()) | |||
while (! thread->threadShouldExit()) | |||
{ | |||
if (socket != nullptr) | |||
{ | |||
@@ -328,7 +341,7 @@ void InterprocessConnection::run() | |||
if (ready == 0) | |||
{ | |||
wait (1); | |||
thread->wait (1); | |||
continue; | |||
} | |||
} | |||
@@ -346,7 +359,7 @@ void InterprocessConnection::run() | |||
break; | |||
} | |||
if (threadShouldExit() || ! readNextMessageInt()) | |||
if (thread->threadShouldExit() || ! readNextMessageInt()) | |||
break; | |||
} | |||
} |
@@ -47,7 +47,7 @@ class MemoryBlock; | |||
@see InterprocessConnectionServer, Socket, NamedPipe | |||
*/ | |||
class JUCE_API InterprocessConnection : private Thread | |||
class JUCE_API InterprocessConnection | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -71,7 +71,7 @@ public: | |||
uint32 magicMessageHeaderNumber = 0xf2b49e2c); | |||
/** Destructor. */ | |||
~InterprocessConnection(); | |||
virtual ~InterprocessConnection(); | |||
//============================================================================== | |||
/** Tries to connect this object to a socket. | |||
@@ -195,7 +195,12 @@ private: | |||
void connectionLostInt(); | |||
void deliverDataInt (const MemoryBlock&); | |||
bool readNextMessageInt(); | |||
void run() override; | |||
struct ConnectionThread; | |||
friend struct ConnectionThread; | |||
friend struct ContainerDeletePolicy<ConnectionThread>; | |||
ScopedPointer<ConnectionThread> thread; | |||
void runThread(); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnection) | |||
}; | |||
@@ -73,6 +73,7 @@ namespace juce | |||
#include "timers/juce_Timer.cpp" | |||
#include "interprocess/juce_InterprocessConnection.cpp" | |||
#include "interprocess/juce_InterprocessConnectionServer.cpp" | |||
#include "interprocess/juce_ConnectedChildProcess.cpp" | |||
//============================================================================== | |||
#if JUCE_MAC | |||
@@ -49,6 +49,7 @@ namespace juce | |||
#include "timers/juce_MultiTimer.h" | |||
#include "interprocess/juce_InterprocessConnection.h" | |||
#include "interprocess/juce_InterprocessConnectionServer.h" | |||
#include "interprocess/juce_ConnectedChildProcess.h" | |||
#include "native/juce_ScopedXLock.h" | |||
} | |||
@@ -281,28 +281,30 @@ void Graphics::drawMultiLineText (const String& text, const int startX, | |||
} | |||
} | |||
void Graphics::drawText (const String& text, const Rectangle<int>& area, | |||
Justification justificationType, | |||
const bool useEllipsesIfTooBig) const | |||
void Graphics::drawText (const String& text, const Rectangle<float>& area, | |||
Justification justificationType, bool useEllipsesIfTooBig) const | |||
{ | |||
if (text.isNotEmpty() && context.clipRegionIntersects (area)) | |||
if (text.isNotEmpty() && context.clipRegionIntersects (area.getSmallestIntegerContainer())) | |||
{ | |||
GlyphArrangement arr; | |||
arr.addCurtailedLineOfText (context.getFont(), text, | |||
0.0f, 0.0f, (float) area.getWidth(), | |||
useEllipsesIfTooBig); | |||
arr.addCurtailedLineOfText (context.getFont(), text, 0.0f, 0.0f, | |||
area.getWidth(), useEllipsesIfTooBig); | |||
arr.justifyGlyphs (0, arr.getNumGlyphs(), | |||
(float) area.getX(), (float) area.getY(), | |||
(float) area.getWidth(), (float) area.getHeight(), | |||
area.getX(), area.getY(), area.getWidth(), area.getHeight(), | |||
justificationType); | |||
arr.draw (*this); | |||
} | |||
} | |||
void Graphics::drawText (const String& text, const Rectangle<int>& area, | |||
Justification justificationType, bool useEllipsesIfTooBig) const | |||
{ | |||
drawText (text, area.toFloat(), justificationType, useEllipsesIfTooBig); | |||
} | |||
void Graphics::drawText (const String& text, const int x, const int y, const int width, const int height, | |||
Justification justificationType, | |||
const bool useEllipsesIfTooBig) const | |||
Justification justificationType, const bool useEllipsesIfTooBig) const | |||
{ | |||
drawText (text, Rectangle<int> (x, y, width, height), justificationType, useEllipsesIfTooBig); | |||
} | |||
@@ -174,6 +174,20 @@ public: | |||
Justification justificationType, | |||
bool useEllipsesIfTooBig) const; | |||
/** Draws a line of text within a specified rectangle. | |||
The text will be positioned within the rectangle based on the justification | |||
flags passed-in. If the string is too long to fit inside the rectangle, it will | |||
either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig | |||
flag is true). | |||
@see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText | |||
*/ | |||
void drawText (const String& text, | |||
const Rectangle<float>& area, | |||
Justification justificationType, | |||
bool useEllipsesIfTooBig) const; | |||
/** Tries to draw a text string inside a given space. | |||
This does its best to make the given text readable within the specified rectangle, | |||
@@ -806,16 +806,19 @@ namespace EdgeTableFillers | |||
alphaLevel = (alphaLevel * extraAlpha) >> 8; | |||
x -= xOffset; | |||
jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); | |||
if (alphaLevel < 0xfe) | |||
if (repeatPattern) | |||
{ | |||
JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (repeatPattern ? (x++ % srcData.width) : x++), (uint32) alphaLevel)) | |||
if (alphaLevel < 0xfe) | |||
JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width), (uint32) alphaLevel)) | |||
else | |||
JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) | |||
} | |||
else | |||
{ | |||
if (repeatPattern) | |||
JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) | |||
jassert (x >= 0 && x + width <= srcData.width); | |||
if (alphaLevel < 0xfe) | |||
JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++), (uint32) alphaLevel)) | |||
else | |||
copyRow (dest, getSrcPixel (x), width); | |||
} | |||
@@ -826,16 +829,19 @@ namespace EdgeTableFillers | |||
DestPixelType* dest = getDestPixel (x); | |||
x -= xOffset; | |||
jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); | |||
if (extraAlpha < 0xfe) | |||
if (repeatPattern) | |||
{ | |||
JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (repeatPattern ? (x++ % srcData.width) : x++), (uint32) extraAlpha)) | |||
if (extraAlpha < 0xfe) | |||
JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width), (uint32) extraAlpha)) | |||
else | |||
JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) | |||
} | |||
else | |||
{ | |||
if (repeatPattern) | |||
JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) | |||
jassert (x >= 0 && x + width <= srcData.width); | |||
if (extraAlpha < 0xfe) | |||
JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++), (uint32) extraAlpha)) | |||
else | |||
copyRow (dest, getSrcPixel (x), width); | |||
} | |||
@@ -107,6 +107,18 @@ void Drawable::setBoundsToEnclose (const Rectangle<float>& area) | |||
setBounds (newBounds); | |||
} | |||
//============================================================================== | |||
bool Drawable::replaceColour (Colour original, Colour replacement) | |||
{ | |||
bool changed = false; | |||
for (int i = getNumChildComponents(); --i >= 0;) | |||
if (Drawable* d = dynamic_cast<Drawable*> (getChildComponent(i))) | |||
changed = d->replaceColour (original, replacement) || changed; | |||
return changed; | |||
} | |||
//============================================================================== | |||
void Drawable::setOriginWithOriginalSize (Point<float> originWithinParent) | |||
{ | |||
@@ -175,6 +175,11 @@ public: | |||
*/ | |||
virtual Rectangle<float> getDrawableBounds() const = 0; | |||
/** Recursively replaces a colour that might be used for filling or stroking. | |||
return true if any instances of this colour were found. | |||
*/ | |||
virtual bool replaceColour (Colour originalColour, Colour replacementColour); | |||
//============================================================================== | |||
/** Internal class used to manage ValueTrees that represent Drawables. */ | |||
class ValueTreeWrapperBase | |||
@@ -452,3 +452,21 @@ void DrawableShape::FillAndStrokeState::setStrokeType (const PathStrokeType& new | |||
state.setProperty (capStyle, newStrokeType.getEndStyle() == PathStrokeType::butt | |||
? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager); | |||
} | |||
static bool replaceColourInFill (DrawableShape::RelativeFillType& fill, Colour original, Colour replacement) | |||
{ | |||
if (fill.fill.colour == original && fill.fill.isColour()) | |||
{ | |||
fill = FillType (replacement); | |||
return true; | |||
} | |||
return false; | |||
} | |||
bool DrawableShape::replaceColour (Colour original, Colour replacement) | |||
{ | |||
bool changed1 = replaceColourInFill (mainFill, original, replacement); | |||
bool changed2 = replaceColourInFill (strokeFill, original, replacement); | |||
return changed1 || changed2; | |||
} |
@@ -147,6 +147,8 @@ public: | |||
void paint (Graphics&) override; | |||
/** @internal */ | |||
bool hitTest (int x, int y) override; | |||
/** @internal */ | |||
bool replaceColour (Colour originalColour, Colour replacementColour) override; | |||
protected: | |||
//============================================================================== | |||
@@ -131,10 +131,12 @@ public: | |||
*/ | |||
bool autoScroll (int mouseX, int mouseY, int distanceFromEdge, int maximumSpeed); | |||
/** Returns the position within the child component of the top-left of its visible area. | |||
*/ | |||
/** Returns the position within the child component of the top-left of its visible area. */ | |||
Point<int> getViewPosition() const noexcept { return lastVisibleArea.getPosition(); } | |||
/** Returns the visible area of the child component, relative to its top-left */ | |||
Rectangle<int> getViewArea() const noexcept { return lastVisibleArea; } | |||
/** Returns the position within the child component of the top-left of its visible area. | |||
@see getViewWidth, setViewPosition | |||
*/ | |||
@@ -198,7 +198,7 @@ void FileChooser::showPlatformDialog (Array<File>& results, const String& title_ | |||
} | |||
else | |||
{ | |||
DWORD flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY; | |||
DWORD flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING; | |||
if (warnAboutOverwritingExistingFiles) | |||
flags |= OFN_OVERWRITEPROMPT; | |||
@@ -1424,47 +1424,40 @@ void TextEditor::scrollToMakeSureCursorIsVisible() | |||
if (keepCaretOnScreen) | |||
{ | |||
int x = viewport->getViewPositionX(); | |||
int y = viewport->getViewPositionY(); | |||
Point<int> viewPos (viewport->getViewPosition()); | |||
const Rectangle<int> caretRect (getCaretRectangle()); | |||
const Rectangle<int> caretPos (getCaretRectangle()); | |||
const Point<int> relativeCursor = caretRect.getPosition() - viewPos; | |||
const int relativeCursorX = caretPos.getX() - x; | |||
const int relativeCursorY = caretPos.getY() - y; | |||
if (relativeCursorX < jmax (1, proportionOfWidth (0.05f))) | |||
if (relativeCursor.x < jmax (1, proportionOfWidth (0.05f))) | |||
{ | |||
x += relativeCursorX - proportionOfWidth (0.2f); | |||
viewPos.x += relativeCursor.x - proportionOfWidth (0.2f); | |||
} | |||
else if (relativeCursorX > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10))) | |||
else if (relativeCursor.x > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10))) | |||
{ | |||
x += relativeCursorX + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth(); | |||
viewPos.x += relativeCursor.x + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth(); | |||
} | |||
x = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), x); | |||
viewPos.x = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), viewPos.x); | |||
if (! isMultiLine()) | |||
{ | |||
y = (getHeight() - textHolder->getHeight() - topIndent) / -2; | |||
viewPos.y = (getHeight() - textHolder->getHeight() - topIndent) / -2; | |||
} | |||
else | |||
else if (relativeCursor.y < 0) | |||
{ | |||
if (relativeCursorY < 0) | |||
{ | |||
y = jmax (0, relativeCursorY + y); | |||
} | |||
else if (relativeCursorY > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - caretPos.getHeight())) | |||
{ | |||
y += relativeCursorY + 2 + caretPos.getHeight() + topIndent - viewport->getMaximumVisibleHeight(); | |||
} | |||
viewPos.y = jmax (0, relativeCursor.y + viewPos.y); | |||
} | |||
else if (relativeCursor.y > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - caretRect.getHeight())) | |||
{ | |||
viewPos.y += relativeCursor.y + 2 + caretRect.getHeight() + topIndent - viewport->getMaximumVisibleHeight(); | |||
} | |||
viewport->setViewPosition (x, y); | |||
viewport->setViewPosition (viewPos); | |||
} | |||
} | |||
void TextEditor::moveCaretTo (const int newPosition, | |||
const bool isSelecting) | |||
void TextEditor::moveCaretTo (const int newPosition, const bool isSelecting) | |||
{ | |||
if (isSelecting) | |||
{ | |||
@@ -22,14 +22,82 @@ | |||
============================================================================== | |||
*/ | |||
struct NSViewResizeWatcher | |||
{ | |||
NSViewResizeWatcher() : callback (nil) {} | |||
virtual ~NSViewResizeWatcher() | |||
{ | |||
// must call detachViewWatcher() first | |||
jassert (callback == nil); | |||
} | |||
void attachViewWatcher (NSView* view) | |||
{ | |||
static ViewFrameChangeCallbackClass cls; | |||
callback = [cls.createInstance() init]; | |||
ViewFrameChangeCallbackClass::setTarget (callback, this); | |||
[[NSNotificationCenter defaultCenter] addObserver: callback | |||
selector: @selector (frameChanged:) | |||
name: NSViewFrameDidChangeNotification | |||
object: view]; | |||
} | |||
void detachViewWatcher() | |||
{ | |||
if (callback != nil) | |||
{ | |||
[[NSNotificationCenter defaultCenter] removeObserver: callback]; | |||
[callback release]; | |||
callback = nil; | |||
} | |||
} | |||
virtual void viewResized() = 0; | |||
private: | |||
id callback; | |||
//============================================================================== | |||
struct ViewFrameChangeCallbackClass : public ObjCClass<NSObject> | |||
{ | |||
ViewFrameChangeCallbackClass() : ObjCClass<NSObject> ("JUCE_NSViewCallback_") | |||
{ | |||
addIvar<NSViewResizeWatcher*> ("target"); | |||
addMethod (@selector (frameChanged:), frameChanged, "v@:@"); | |||
registerClass(); | |||
} | |||
static void setTarget (id self, NSViewResizeWatcher* c) | |||
{ | |||
object_setInstanceVariable (self, "target", c); | |||
} | |||
private: | |||
static void frameChanged (id self, SEL, NSNotification*) | |||
{ | |||
if (NSViewResizeWatcher* const target = getIvar<NSViewResizeWatcher*> (self, "target")) | |||
target->viewResized(); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE (ViewFrameChangeCallbackClass); | |||
}; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewResizeWatcher) | |||
}; | |||
//============================================================================== | |||
class NSViewAttachment : public ReferenceCountedObject, | |||
public ComponentMovementWatcher | |||
public ComponentMovementWatcher, | |||
private NSViewResizeWatcher | |||
{ | |||
public: | |||
NSViewAttachment (NSView* const v, Component& comp) | |||
: ComponentMovementWatcher (&comp), | |||
view (v), owner (comp), | |||
currentPeer (nullptr), frameChangeCallback (nullptr) | |||
currentPeer (nullptr) | |||
{ | |||
[view retain]; | |||
[view setPostsFrameChangedNotifications: YES]; | |||
@@ -37,21 +105,12 @@ public: | |||
if (owner.isShowing()) | |||
componentPeerChanged(); | |||
static ViewFrameChangeCallbackClass cls; | |||
frameChangeCallback = [cls.createInstance() init]; | |||
ViewFrameChangeCallbackClass::setTarget (frameChangeCallback, &owner); | |||
[[NSNotificationCenter defaultCenter] addObserver: frameChangeCallback | |||
selector: @selector (frameChanged:) | |||
name: NSViewFrameDidChangeNotification | |||
object: view]; | |||
attachViewWatcher (view); | |||
} | |||
~NSViewAttachment() | |||
{ | |||
[[NSNotificationCenter defaultCenter] removeObserver: frameChangeCallback]; | |||
[frameChangeCallback release]; | |||
detachViewWatcher(); | |||
removeFromParent(); | |||
[view release]; | |||
} | |||
@@ -103,12 +162,16 @@ public: | |||
componentPeerChanged(); | |||
} | |||
void viewResized() override | |||
{ | |||
owner.childBoundsChanged (nullptr); | |||
} | |||
NSView* const view; | |||
private: | |||
Component& owner; | |||
ComponentPeer* currentPeer; | |||
id frameChangeCallback; | |||
void removeFromParent() | |||
{ | |||
@@ -117,31 +180,6 @@ private: | |||
// override the call and use it as a sign that they're being deleted, which breaks everything.. | |||
} | |||
//============================================================================== | |||
struct ViewFrameChangeCallbackClass : public ObjCClass<NSObject> | |||
{ | |||
ViewFrameChangeCallbackClass() : ObjCClass<NSObject> ("JUCE_NSViewCallback_") | |||
{ | |||
addIvar<Component*> ("target"); | |||
addMethod (@selector (frameChanged:), frameChanged, "v@:@"); | |||
registerClass(); | |||
} | |||
static void setTarget (id self, Component* c) | |||
{ | |||
object_setInstanceVariable (self, "target", c); | |||
} | |||
private: | |||
static void frameChanged (id self, SEL, NSNotification*) | |||
{ | |||
if (Component* const target = getIvar<Component*> (self, "target")) | |||
target->childBoundsChanged (nullptr); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE (ViewFrameChangeCallbackClass); | |||
}; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewAttachment) | |||
}; | |||