| @@ -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) | |||
| }; | |||