| @@ -2,10 +2,10 @@ | |||||
| set -e | 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 | for M in $MODULES; do | ||||
| echo $M; | echo $M; | ||||
| @@ -941,6 +941,11 @@ double MidiMessage::getMidiNoteInHertz (int noteNumber, const double frequencyOf | |||||
| return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); | 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) | const char* MidiMessage::getGMInstrumentName (const int n) | ||||
| { | { | ||||
| static const char* names[] = | static const char* names[] = | ||||
| @@ -889,6 +889,9 @@ public: | |||||
| */ | */ | ||||
| static double getMidiNoteInHertz (int noteNumber, const double frequencyOfA = 440.0) noexcept; | 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. | /** Returns the standard name of a GM instrument, or nullptr if unknown for this index. | ||||
| @param midiInstrumentNumber the program number 0 to 127 | @param midiInstrumentNumber the program number 0 to 127 | ||||
| @@ -231,24 +231,15 @@ public: | |||||
| NSString* nameNSString = nil; | NSString* nameNSString = nil; | ||||
| size = sizeof (nameNSString); | 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); | name = nsStringToJuce (nameNSString); | ||||
| [nameNSString release]; | [nameNSString release]; | ||||
| } | } | ||||
| #if JUCE_CLANG | |||||
| #pragma clang diagnostic pop | |||||
| #endif | |||||
| if ((input ? activeInputChans : activeOutputChans) [chanNum]) | if ((input ? activeInputChans : activeOutputChans) [chanNum]) | ||||
| { | { | ||||
| CallbackDetailsForChannel info = { i, (int) j, (int) b.mNumberChannels }; | 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::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 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()); | 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()); | 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 | /** The equivalent numChannels and speaker arrangements should always | ||||
| match between this function and fillWithCorrespondingSpeakerArrangements(). | match between this function and fillWithCorrespondingSpeakerArrangements(). | ||||
| @@ -81,22 +88,32 @@ static Steinberg::Vst::SpeakerArrangement getArrangementForNumChannels (int numC | |||||
| { | { | ||||
| using namespace Steinberg::Vst::SpeakerArr; | 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 | /** The equivalent numChannels and speaker arrangements should always | ||||
| @@ -119,10 +136,17 @@ static void fillWithCorrespondingSpeakerArrangements (Array<Steinberg::Vst::Spea | |||||
| return; | 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 >= 14) destination.add (k131); | ||||
| if (numChannels >= 13) destination.add (k130); | if (numChannels >= 13) destination.add (k130); | ||||
| if (numChannels >= 12) destination.add (k111); | if (numChannels >= 12) destination.add (k111); | ||||
| @@ -225,7 +249,9 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| static void toMidiBuffer (MidiBuffer& result, Steinberg::Vst::IEventList& eventList) | 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; | 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 Point | ||||
| #undef Component | #undef Component | ||||
| @@ -159,7 +159,6 @@ namespace Steinberg | |||||
| #undef OBJ_METHODS | #undef OBJ_METHODS | ||||
| #undef QUERY_INTERFACE | #undef QUERY_INTERFACE | ||||
| #undef LICENCE_UID | #undef LICENCE_UID | ||||
| #undef DEF_CLASS_IID | |||||
| #undef BEGIN_FACTORY | #undef BEGIN_FACTORY | ||||
| #undef DEF_CLASS | #undef DEF_CLASS | ||||
| #undef DEF_CLASS1 | #undef DEF_CLASS1 | ||||
| @@ -169,4 +168,4 @@ namespace Steinberg | |||||
| #undef Point | #undef Point | ||||
| #undef Component | #undef Component | ||||
| #endif //JUCE_VST3HEADER_H_INCLUDED | |||||
| #endif // JUCE_VST3HEADERS_H_INCLUDED | |||||
| @@ -998,7 +998,8 @@ private: | |||||
| void releaseFactory() | void releaseFactory() | ||||
| { | { | ||||
| const Steinberg::FReleaser releaser (factory); | |||||
| if (factory != nullptr) | |||||
| factory->release(); | |||||
| } | } | ||||
| #if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
| @@ -1219,10 +1220,9 @@ public: | |||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| dummyComponent.setView (nullptr); | dummyComponent.setView (nullptr); | ||||
| [pluginHandle release]; | |||||
| #endif | #endif | ||||
| const Steinberg::FReleaser releaser (view); | |||||
| view = nullptr; | |||||
| } | } | ||||
| JUCE_DECLARE_VST3_COM_REF_METHODS | JUCE_DECLARE_VST3_COM_REF_METHODS | ||||
| @@ -1284,7 +1284,8 @@ public: | |||||
| dummyComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); | dummyComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); | ||||
| #endif | #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; | recursiveResize = false; | ||||
| } | } | ||||
| @@ -1316,7 +1317,7 @@ public: | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| Atomic<int> refCount; | 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 | #if JUCE_WINDOWS | ||||
| class ChildComponent : public Component | class ChildComponent : public Component | ||||
| @@ -1335,14 +1336,14 @@ private: | |||||
| ScopedPointer<ComponentPeer> peer; | ScopedPointer<ComponentPeer> peer; | ||||
| typedef HWND HandleFormat; | typedef HWND HandleFormat; | ||||
| #elif JUCE_MAC | #elif JUCE_MAC | ||||
| NSViewComponent dummyComponent; | |||||
| AutoResizingNSViewComponentWithParent dummyComponent; | |||||
| typedef NSView* HandleFormat; | typedef NSView* HandleFormat; | ||||
| #else | #else | ||||
| Component dummyComponent; | Component dummyComponent; | ||||
| typedef void* HandleFormat; | typedef void* HandleFormat; | ||||
| #endif | #endif | ||||
| HandleFormat pluginHandle; // Don't delete this | |||||
| HandleFormat pluginHandle; | |||||
| bool recursiveResize; | bool recursiveResize; | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -1368,17 +1369,12 @@ private: | |||||
| #elif JUCE_MAC | #elif JUCE_MAC | ||||
| dummyComponent.setBounds (getBounds().withZeroOrigin()); | dummyComponent.setBounds (getBounds().withZeroOrigin()); | ||||
| addAndMakeVisible (dummyComponent); | addAndMakeVisible (dummyComponent); | ||||
| pluginHandle = [[NSView alloc] init]; | |||||
| dummyComponent.setView (pluginHandle); | |||||
| pluginHandle = (NSView*) dummyComponent.getView(); | |||||
| jassert (pluginHandle != nil); | |||||
| #endif | #endif | ||||
| if (pluginHandle != nullptr) | 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())) | if (! fetchComponentAndController (factory, factory->countClasses())) | ||||
| return false; | 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; | isControllerInitialised = true; | ||||
| editController->setComponentHandler (host); | editController->setComponentHandler (host); | ||||
| @@ -187,7 +187,6 @@ static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstI | |||||
| static int shellUIDToCreate = 0; | static int shellUIDToCreate = 0; | ||||
| static int insideVSTCallback = 0; | static int insideVSTCallback = 0; | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| class IdleCallRecursionPreventer | class IdleCallRecursionPreventer | ||||
| { | { | ||||
| public: | public: | ||||
| @@ -211,9 +210,6 @@ private: | |||||
| }; | }; | ||||
| class VSTPluginWindow; | class VSTPluginWindow; | ||||
| #else | |||||
| struct IdleCallRecursionPreventer{}; | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| // Change this to disable logging of various VST activities | // Change this to disable logging of various VST activities | ||||
| @@ -240,7 +236,6 @@ static void* NewCFMFromMachO (void* const machofp) noexcept | |||||
| } | } | ||||
| #endif | #endif | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_LINUX | #if JUCE_LINUX | ||||
| @@ -345,7 +340,6 @@ namespace | |||||
| } | } | ||||
| } | } | ||||
| #endif | |||||
| #endif | #endif | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -716,13 +710,9 @@ static const int defaultVSTBlockSizeValue = 512; | |||||
| //============================================================================== | //============================================================================== | ||||
| //============================================================================== | //============================================================================== | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| class VSTPluginInstance : public AudioPluginInstance, | class VSTPluginInstance : public AudioPluginInstance, | ||||
| private Timer, | private Timer, | ||||
| private AsyncUpdater | private AsyncUpdater | ||||
| #else | |||||
| class VSTPluginInstance : public AudioPluginInstance | |||||
| #endif | |||||
| { | { | ||||
| public: | public: | ||||
| VSTPluginInstance (const ModuleHandle::Ptr& module_) | VSTPluginInstance (const ModuleHandle::Ptr& module_) | ||||
| @@ -793,10 +783,8 @@ public: | |||||
| UseResFile (module->resFileId); | UseResFile (module->resFileId); | ||||
| #endif | #endif | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| // Must delete any editors before deleting the plugin instance! | // Must delete any editors before deleting the plugin instance! | ||||
| jassert (getActiveEditor() == 0); | jassert (getActiveEditor() == 0); | ||||
| #endif | |||||
| _fpreset(); // some dodgy plugs fuck around with this | _fpreset(); // some dodgy plugs fuck around with this | ||||
| @@ -1254,7 +1242,6 @@ public: | |||||
| void setCurrentProgramStateInformation (const void* data, int size) override { loadFromFXBFile (data, size); } | void setCurrentProgramStateInformation (const void* data, int size) override { loadFromFXBFile (data, size); } | ||||
| //============================================================================== | //============================================================================== | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| void timerCallback() override | void timerCallback() override | ||||
| { | { | ||||
| if (dispatch (effIdle, 0, 0, 0, 0) == 0) | if (dispatch (effIdle, 0, 0, 0, 0) == 0) | ||||
| @@ -1266,7 +1253,6 @@ public: | |||||
| // indicates that something about the plugin has changed.. | // indicates that something about the plugin has changed.. | ||||
| updateHostDisplay(); | updateHostDisplay(); | ||||
| } | } | ||||
| #endif | |||||
| VstIntPtr handleCallback (VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) | VstIntPtr handleCallback (VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) | ||||
| { | { | ||||
| @@ -1284,7 +1270,6 @@ public: | |||||
| #pragma warning (pop) | #pragma warning (pop) | ||||
| #endif | #endif | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| case audioMasterIdle: | case audioMasterIdle: | ||||
| if (insideVSTCallback == 0 && MessageManager::getInstance()->isThisTheMessageThread()) | if (insideVSTCallback == 0 && MessageManager::getInstance()->isThisTheMessageThread()) | ||||
| { | { | ||||
| @@ -1313,7 +1298,6 @@ public: | |||||
| case audioMasterUpdateDisplay: triggerAsyncUpdate(); break; | case audioMasterUpdateDisplay: triggerAsyncUpdate(); break; | ||||
| case audioMasterIOChanged: setLatencySamples (effect->initialDelay); break; | case audioMasterIOChanged: setLatencySamples (effect->initialDelay); break; | ||||
| case audioMasterNeedIdle: startTimer (50); break; | case audioMasterNeedIdle: startTimer (50); break; | ||||
| #endif | |||||
| case audioMasterGetSampleRate: return (VstIntPtr) (getSampleRate() > 0 ? getSampleRate() : defaultVSTSampleRateValue); | case audioMasterGetSampleRate: return (VstIntPtr) (getSampleRate() > 0 ? getSampleRate() : defaultVSTSampleRateValue); | ||||
| case audioMasterGetBlockSize: return (VstIntPtr) (getBlockSize() > 0 ? getBlockSize() : defaultVSTBlockSizeValue); | case audioMasterGetBlockSize: return (VstIntPtr) (getBlockSize() > 0 ? getBlockSize() : defaultVSTBlockSizeValue); | ||||
| @@ -1399,12 +1383,10 @@ public: | |||||
| case audioMasterGetVendorString: | case audioMasterGetVendorString: | ||||
| case audioMasterGetProductString: | case audioMasterGetProductString: | ||||
| { | { | ||||
| String hostName ("Carla"); | |||||
| String hostName ("Juce VST Host"); | |||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| if (JUCEApplicationBase* app = JUCEApplicationBase::getInstance()) | if (JUCEApplicationBase* app = JUCEApplicationBase::getInstance()) | ||||
| hostName = app->getApplicationName(); | hostName = app->getApplicationName(); | ||||
| #endif | |||||
| hostName.copyToUTF8 ((char*) ptr, (size_t) jmin (kVstMaxVendorStrLen, kVstMaxProductStrLen) - 1); | hostName.copyToUTF8 ((char*) ptr, (size_t) jmin (kVstMaxVendorStrLen, kVstMaxProductStrLen) - 1); | ||||
| break; | break; | ||||
| @@ -1911,7 +1893,6 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginInstance) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginInstance) | ||||
| }; | }; | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| //============================================================================== | //============================================================================== | ||||
| static Array <VSTPluginWindow*> activeVSTWindows; | static Array <VSTPluginWindow*> activeVSTWindows; | ||||
| @@ -1946,17 +1927,10 @@ public: | |||||
| #elif JUCE_MAC | #elif JUCE_MAC | ||||
| #if JUCE_SUPPORT_CARBON | #if JUCE_SUPPORT_CARBON | ||||
| if (! plug.usesCocoaNSView) | if (! plug.usesCocoaNSView) | ||||
| { | |||||
| addAndMakeVisible (carbonWrapper = new CarbonWrapperComponent (*this)); | addAndMakeVisible (carbonWrapper = new CarbonWrapperComponent (*this)); | ||||
| } | |||||
| else | else | ||||
| #endif | #endif | ||||
| { | |||||
| addAndMakeVisible (cocoaWrapper = new AutoResizingNSViewComponent()); | |||||
| NSView* innerView = [[NSView alloc] init]; | |||||
| cocoaWrapper->setView (innerView); | |||||
| [innerView release]; | |||||
| } | |||||
| addAndMakeVisible (cocoaWrapper = new AutoResizingNSViewComponentWithParent()); | |||||
| #endif | #endif | ||||
| activeVSTWindows.add (this); | activeVSTWindows.add (this); | ||||
| @@ -2619,7 +2593,7 @@ private: | |||||
| ScopedPointer<CarbonWrapperComponent> carbonWrapper; | ScopedPointer<CarbonWrapperComponent> carbonWrapper; | ||||
| #endif | #endif | ||||
| ScopedPointer<NSViewComponent> cocoaWrapper; | |||||
| ScopedPointer<AutoResizingNSViewComponentWithParent> cocoaWrapper; | |||||
| void resized() override | void resized() override | ||||
| { | { | ||||
| @@ -2635,17 +2609,12 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginWindow) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginWindow) | ||||
| }; | }; | ||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| AudioProcessorEditor* VSTPluginInstance::createEditor() | AudioProcessorEditor* VSTPluginInstance::createEditor() | ||||
| { | { | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| return hasEditor() ? new VSTPluginWindow (*this) | return hasEditor() ? new VSTPluginWindow (*this) | ||||
| : nullptr; | : nullptr; | ||||
| #else | |||||
| return nullptr; | |||||
| #endif | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -57,6 +57,11 @@ | |||||
| #undef KeyPress | #undef KeyPress | ||||
| #endif | #endif | ||||
| #if ! JUCE_WINDOWS && ! JUCE_MAC | |||||
| #undef JUCE_PLUGINHOST_VST3 | |||||
| #define JUCE_PLUGINHOST_VST3 0 | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| @@ -72,6 +77,7 @@ static inline bool arrayContainsPlugin (const OwnedArray<PluginDescription>& lis | |||||
| } | } | ||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| //============================================================================== | |||||
| struct AutoResizingNSViewComponent : public NSViewComponent, | struct AutoResizingNSViewComponent : public NSViewComponent, | ||||
| private AsyncUpdater | private AsyncUpdater | ||||
| { | { | ||||
| @@ -95,6 +101,38 @@ struct AutoResizingNSViewComponent : public NSViewComponent, | |||||
| bool recursive; | 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 | #endif | ||||
| #if JUCE_CLANG | #if JUCE_CLANG | ||||
| @@ -44,7 +44,7 @@ | |||||
| Enables the VST3 audio plugin hosting classes. This requires the Steinberg VST3 SDK to be | Enables the VST3 audio plugin hosting classes. This requires the Steinberg VST3 SDK to be | ||||
| installed on your machine. | 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 | #ifndef JUCE_PLUGINHOST_VST3 | ||||
| #define JUCE_PLUGINHOST_VST3 0 | #define JUCE_PLUGINHOST_VST3 0 | ||||
| @@ -60,7 +60,7 @@ | |||||
| #endif | #endif | ||||
| #if ! (JUCE_PLUGINHOST_AU || JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3) | #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 | #endif | ||||
| #if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT) | #if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT) | ||||
| @@ -44,11 +44,9 @@ AudioProcessor::AudioProcessor() | |||||
| AudioProcessor::~AudioProcessor() | AudioProcessor::~AudioProcessor() | ||||
| { | { | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| // ooh, nasty - the editor should have been deleted before the filter | // ooh, nasty - the editor should have been deleted before the filter | ||||
| // that it refers to is deleted.. | // that it refers to is deleted.. | ||||
| jassert (activeEditor == nullptr); | jassert (activeEditor == nullptr); | ||||
| #endif | |||||
| #if JUCE_DEBUG | #if JUCE_DEBUG | ||||
| // This will fail if you've called beginParameterChangeGesture() for one | // 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); | const ScopedLock sl (callbackLock); | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| if (activeEditor == editor) | if (activeEditor == editor) | ||||
| activeEditor = nullptr; | activeEditor = nullptr; | ||||
| #endif | |||||
| } | } | ||||
| AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() | AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() | ||||
| { | { | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| if (activeEditor != nullptr) | if (activeEditor != nullptr) | ||||
| return activeEditor; | return activeEditor; | ||||
| @@ -248,9 +243,6 @@ AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() | |||||
| } | } | ||||
| return ed; | return ed; | ||||
| #else | |||||
| return nullptr; | |||||
| #endif | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -366,13 +366,11 @@ public: | |||||
| */ | */ | ||||
| virtual bool hasEditor() const = 0; | virtual bool hasEditor() const = 0; | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Returns the active editor, if there is one. | /** Returns the active editor, if there is one. | ||||
| Bear in mind this can return nullptr, even if an editor has previously been opened. | Bear in mind this can return nullptr, even if an editor has previously been opened. | ||||
| */ | */ | ||||
| AudioProcessorEditor* getActiveEditor() const noexcept { return activeEditor; } | AudioProcessorEditor* getActiveEditor() const noexcept { return activeEditor; } | ||||
| #endif | |||||
| /** Returns the active editor, or if there isn't one, it will create one. | /** Returns the active editor, or if there isn't one, it will create one. | ||||
| This may call createEditor() internally to create the component. | This may call createEditor() internally to create the component. | ||||
| @@ -616,6 +614,7 @@ public: | |||||
| { | { | ||||
| wrapperType_Undefined = 0, | wrapperType_Undefined = 0, | ||||
| wrapperType_VST, | wrapperType_VST, | ||||
| wrapperType_VST3, | |||||
| wrapperType_AudioUnit, | wrapperType_AudioUnit, | ||||
| wrapperType_RTAS, | wrapperType_RTAS, | ||||
| wrapperType_AAX, | wrapperType_AAX, | ||||
| @@ -658,9 +657,7 @@ protected: | |||||
| private: | private: | ||||
| Array<AudioProcessorListener*> listeners; | Array<AudioProcessorListener*> listeners; | ||||
| #ifndef JUCE_PLUGIN_HOST_NO_UI | |||||
| Component::SafePointer<AudioProcessorEditor> activeEditor; | Component::SafePointer<AudioProcessorEditor> activeEditor; | ||||
| #endif | |||||
| double sampleRate; | double sampleRate; | ||||
| int blockSize, numInputChannels, numOutputChannels, latencySamples; | int blockSize, numInputChannels, numOutputChannels, latencySamples; | ||||
| bool suspended, nonRealtime; | 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 | const StringArray& KnownPluginList::getBlacklistedFiles() const | ||||
| @@ -280,10 +288,16 @@ void KnownPluginList::sort (const SortMethod method, bool forwards) | |||||
| { | { | ||||
| if (method != defaultOrder) | if (method != defaultOrder) | ||||
| { | { | ||||
| Array<PluginDescription*> oldOrder, newOrder; | |||||
| oldOrder.addArray (types); | |||||
| PluginSorter sorter (method, forwards); | PluginSorter sorter (method, forwards); | ||||
| types.sort (sorter, true); | 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() {} | ||||
| 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, | OwnedArray <PluginDescription>& typesFound, | ||||
| AudioPluginFormat& formatToUse); | 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 | /** Returns true if the specified file is already known about and if it | ||||
| hasn't been modified since our entry was created. | hasn't been modified since our entry was created. | ||||
| */ | */ | ||||
| @@ -170,8 +173,8 @@ public: | |||||
| struct PluginTree | struct PluginTree | ||||
| { | { | ||||
| String folder; /**< The name of this folder in the tree */ | 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. */ | /** Creates a PluginTree object containing all the known plugins. */ | ||||
| @@ -190,9 +193,21 @@ public: | |||||
| virtual bool findPluginTypesFor (AudioPluginFormat& format, | virtual bool findPluginTypesFor (AudioPluginFormat& format, | ||||
| OwnedArray <PluginDescription>& result, | OwnedArray <PluginDescription>& result, | ||||
| const String& fileOrIdentifier) = 0; | 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: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -63,6 +63,7 @@ PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, | |||||
| PluginDirectoryScanner::~PluginDirectoryScanner() | PluginDirectoryScanner::~PluginDirectoryScanner() | ||||
| { | { | ||||
| list.scanFinished(); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -162,6 +162,7 @@ PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, Kno | |||||
| setSize (400, 600); | setSize (400, 600); | ||||
| list.addChangeListener (this); | list.addChangeListener (this); | ||||
| updateList(); | updateList(); | ||||
| table.getHeader().reSortTable(); | |||||
| PluginDirectoryScanner::applyBlacklistingsFromDeadMansPedal (list, deadMansPedalFile); | PluginDirectoryScanner::applyBlacklistingsFromDeadMansPedal (list, deadMansPedalFile); | ||||
| deadMansPedalFile.deleteFile(); | deadMansPedalFile.deleteFile(); | ||||
| @@ -196,6 +197,7 @@ void PluginListComponent::resized() | |||||
| void PluginListComponent::changeListenerCallback (ChangeBroadcaster*) | void PluginListComponent::changeListenerCallback (ChangeBroadcaster*) | ||||
| { | { | ||||
| table.getHeader().reSortTable(); | |||||
| updateList(); | updateList(); | ||||
| } | } | ||||
| @@ -60,7 +60,7 @@ struct DefaultHashFunctions | |||||
| @code | @code | ||||
| struct MyHashGenerator | struct MyHashGenerator | ||||
| { | { | ||||
| int generateHash (MyKeyType key, int upperLimit) | |||||
| int generateHash (MyKeyType key, int upperLimit) const | |||||
| { | { | ||||
| // The function must return a value 0 <= x < upperLimit | // The function must return a value 0 <= x < upperLimit | ||||
| return someFunctionOfMyKeyType (key) % upperLimit; | return someFunctionOfMyKeyType (key) % upperLimit; | ||||
| @@ -141,7 +141,7 @@ int NamedValueSet::size() const noexcept | |||||
| return values.size(); | 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) | for (NamedValue* i = values; i != nullptr; i = i->nextListItem) | ||||
| if (i->name == name) | if (i->name == name) | ||||
| @@ -150,7 +150,7 @@ const var& NamedValueSet::operator[] (const Identifier name) const | |||||
| return var::null; | 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)) | if (const var* const v = getVarPointer (name)) | ||||
| return *v; | return *v; | ||||
| @@ -158,7 +158,7 @@ var NamedValueSet::getWithDefault (const Identifier name, const var& defaultRetu | |||||
| return defaultReturnValue; | 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) | for (NamedValue* i = values; i != nullptr; i = i->nextListItem) | ||||
| if (i->name == name) | if (i->name == name) | ||||
| @@ -168,7 +168,7 @@ var* NamedValueSet::getVarPointer (const Identifier name) const noexcept | |||||
| } | } | ||||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | #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; | LinkedListPointer<NamedValue>* i = &values; | ||||
| @@ -193,7 +193,7 @@ bool NamedValueSet::set (const Identifier name, var&& newValue) | |||||
| } | } | ||||
| #endif | #endif | ||||
| bool NamedValueSet::set (const Identifier name, const var& newValue) | |||||
| bool NamedValueSet::set (Identifier name, const var& newValue) | |||||
| { | { | ||||
| LinkedListPointer<NamedValue>* i = &values; | LinkedListPointer<NamedValue>* i = &values; | ||||
| @@ -217,12 +217,27 @@ bool NamedValueSet::set (const Identifier name, const var& newValue) | |||||
| return true; | return true; | ||||
| } | } | ||||
| bool NamedValueSet::contains (const Identifier name) const | |||||
| bool NamedValueSet::contains (Identifier name) const | |||||
| { | { | ||||
| return getVarPointer (name) != nullptr; | 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; | LinkedListPointer<NamedValue>* i = &values; | ||||
| @@ -245,7 +260,7 @@ bool NamedValueSet::remove (const Identifier name) | |||||
| return false; | return false; | ||||
| } | } | ||||
| const Identifier NamedValueSet::getName (const int index) const | |||||
| Identifier NamedValueSet::getName (const int index) const | |||||
| { | { | ||||
| const NamedValue* const v = values[index]; | const NamedValue* const v = values[index]; | ||||
| jassert (v != nullptr); | jassert (v != nullptr); | ||||
| @@ -67,46 +67,49 @@ public: | |||||
| If the name isn't found, this will return a void variant. | If the name isn't found, this will return a void variant. | ||||
| @see getProperty | @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 | /** Tries to return the named value, but if no such value is found, this will | ||||
| instead return the supplied default value. | 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. | /** Changes or adds a named value. | ||||
| @returns true if a value was changed or added; false if the | @returns true if a value was changed or added; false if the | ||||
| value was already set the the value passed-in. | 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 | #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | ||||
| /** Changes or adds a named value. | /** Changes or adds a named value. | ||||
| @returns true if a value was changed or added; false if the | @returns true if a value was changed or added; false if the | ||||
| value was already set the the value passed-in. | value was already set the the value passed-in. | ||||
| */ | */ | ||||
| bool set (const Identifier name, var&& newValue); | |||||
| bool set (Identifier name, var&& newValue); | |||||
| #endif | #endif | ||||
| /** Returns true if the set contains an item with the specified name. */ | /** 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. | /** Removes a value from the set. | ||||
| @returns true if a value was removed; false if there was no value | @returns true if a value was removed; false if there was no value | ||||
| with the name that was given. | 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. | /** Returns the name of the value at a given index. | ||||
| The index must be between 0 and size() - 1. | 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. | /** Returns the value of the item at a given index. | ||||
| The index must be between 0 and size() - 1. | The index must be between 0 and size() - 1. | ||||
| */ | */ | ||||
| const var& getValueAt (int index) const; | 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. */ | /** Removes all values. */ | ||||
| void clear(); | void clear(); | ||||
| @@ -117,7 +120,7 @@ public: | |||||
| Do not use this method unless you really need access to the internal var object | 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(). | 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. */ | /** 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_StringPairArray.cpp" | ||||
| #include "text/juce_StringPool.cpp" | #include "text/juce_StringPool.cpp" | ||||
| #include "text/juce_TextDiff.cpp" | #include "text/juce_TextDiff.cpp" | ||||
| #include "threads/juce_ChildProcess.cpp" | |||||
| #include "threads/juce_ReadWriteLock.cpp" | #include "threads/juce_ReadWriteLock.cpp" | ||||
| #include "threads/juce_Thread.cpp" | #include "threads/juce_Thread.cpp" | ||||
| #include "threads/juce_ThreadPool.cpp" | #include "threads/juce_ThreadPool.cpp" | ||||
| @@ -218,6 +217,7 @@ namespace juce | |||||
| #endif | #endif | ||||
| #include "threads/juce_ChildProcess.cpp" | |||||
| #include "threads/juce_HighResolutionTimer.cpp" | #include "threads/juce_HighResolutionTimer.cpp" | ||||
| } | } | ||||
| @@ -1053,7 +1053,7 @@ public: | |||||
| close (pipeHandle); | close (pipeHandle); | ||||
| } | } | ||||
| bool isRunning() const | |||||
| bool isRunning() const noexcept | |||||
| { | { | ||||
| if (childPID != 0) | if (childPID != 0) | ||||
| { | { | ||||
| @@ -1065,7 +1065,7 @@ public: | |||||
| return false; | return false; | ||||
| } | } | ||||
| int read (void* const dest, const int numBytes) | |||||
| int read (void* const dest, const int numBytes) noexcept | |||||
| { | { | ||||
| jassert (dest != nullptr); | jassert (dest != nullptr); | ||||
| @@ -1082,11 +1082,25 @@ public: | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| bool killProcess() const | |||||
| bool killProcess() const noexcept | |||||
| { | { | ||||
| return ::kill (childPID, SIGKILL) == 0; | 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; | int childPID; | ||||
| private: | private: | ||||
| @@ -1114,21 +1128,6 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) | |||||
| return activeProcess != nullptr; | 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 | struct HighResolutionTimer::Pimpl | ||||
| { | { | ||||
| @@ -482,12 +482,12 @@ public: | |||||
| CloseHandle (writePipe); | CloseHandle (writePipe); | ||||
| } | } | ||||
| bool isRunning() const | |||||
| bool isRunning() const noexcept | |||||
| { | { | ||||
| return WaitForSingleObject (processInfo.hProcess, 0) != WAIT_OBJECT_0; | 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; | int total = 0; | ||||
| @@ -522,11 +522,18 @@ public: | |||||
| return total; | return total; | ||||
| } | } | ||||
| bool killProcess() const | |||||
| bool killProcess() const noexcept | |||||
| { | { | ||||
| return TerminateProcess (processInfo.hProcess, 0) != FALSE; | return TerminateProcess (processInfo.hProcess, 0) != FALSE; | ||||
| } | } | ||||
| uint32 getExitCode() const noexcept | |||||
| { | |||||
| DWORD exitCode = 0; | |||||
| GetExitCodeProcess (processInfo.hProcess, &exitCode); | |||||
| return (uint32) exitCode; | |||||
| } | |||||
| bool ok; | bool ok; | ||||
| private: | private: | ||||
| @@ -551,21 +558,6 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) | |||||
| return start (args.joinIntoString (" "), 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 | struct HighResolutionTimer::Pimpl | ||||
| { | { | ||||
| @@ -174,12 +174,16 @@ bool OutputStream::writeDoubleBigEndian (double value) | |||||
| bool OutputStream::writeString (const String& text) | 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 | // (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). | // if lots of large, persistent strings were to be written to streams). | ||||
| const size_t numBytes = text.getNumBytesAsUTF8() + 1; | const size_t numBytes = text.getNumBytesAsUTF8() + 1; | ||||
| HeapBlock<char> temp (numBytes); | HeapBlock<char> temp (numBytes); | ||||
| text.copyToUTF8 (temp, numBytes); | text.copyToUTF8 (temp, numBytes); | ||||
| return write (temp, numBytes); | return write (temp, numBytes); | ||||
| #endif | |||||
| } | } | ||||
| bool OutputStream::writeText (const String& text, const bool asUTF16, | 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 | 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. | 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). | is actually shorter). | ||||
| */ | */ | ||||
| String (const char* text, size_t maxChars); | String (const char* text, size_t maxChars); | ||||
| @@ -29,6 +29,26 @@ | |||||
| ChildProcess::ChildProcess() {} | ChildProcess::ChildProcess() {} | ||||
| 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 | bool ChildProcess::waitForProcessToFinish (const int timeoutMs) const | ||||
| { | { | ||||
| const uint32 timeoutTime = Time::getMillisecondCounter() + (uint32) timeoutMs; | const uint32 timeoutTime = Time::getMillisecondCounter() + (uint32) timeoutMs; | ||||
| @@ -97,6 +97,9 @@ public: | |||||
| /** Blocks until the process is no longer running. */ | /** Blocks until the process is no longer running. */ | ||||
| bool waitForProcessToFinish (int timeoutMs) const; | bool waitForProcessToFinish (int timeoutMs) const; | ||||
| /** If the process has finished, this returns its exit code. */ | |||||
| uint32 getExitCode() const; | |||||
| /** Attempts to kill the child process. | /** Attempts to kill the child process. | ||||
| Returns true if it succeeded. Trying to read from the process after calling this may | Returns true if it succeeded. Trying to read from the process after calling this may | ||||
| result in undefined behaviour. | 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) | 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; | 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) | 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; | 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) | if (job != nullptr) | ||||
| { | { | ||||
| @@ -215,7 +216,7 @@ bool ThreadPool::removeJob (ThreadPoolJob* const job, | |||||
| } | } | ||||
| bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, | bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, | ||||
| ThreadPool::JobSelector* selectedJobsToRemove) | |||||
| ThreadPool::JobSelector* const selectedJobsToRemove) | |||||
| { | { | ||||
| Array <ThreadPoolJob*> jobsToWaitFor; | Array <ThreadPoolJob*> jobsToWaitFor; | ||||
| @@ -328,46 +329,49 @@ ThreadPoolJob* ThreadPool::pickNextJobToRun() | |||||
| return nullptr; | 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 | void ThreadPool::addToDeleteList (OwnedArray<ThreadPoolJob>& deletionList, ThreadPoolJob* const job) const | ||||
| @@ -119,6 +119,12 @@ public: | |||||
| */ | */ | ||||
| void signalJobShouldExit(); | 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: | private: | ||||
| friend class ThreadPool; | friend class ThreadPool; | ||||
| @@ -290,6 +296,7 @@ private: | |||||
| Array <ThreadPoolJob*> jobs; | Array <ThreadPoolJob*> jobs; | ||||
| class ThreadPoolThread; | class ThreadPoolThread; | ||||
| friend class ThreadPoolJob; | |||||
| friend class ThreadPoolThread; | friend class ThreadPoolThread; | ||||
| friend struct ContainerDeletePolicy<ThreadPoolThread>; | friend struct ContainerDeletePolicy<ThreadPoolThread>; | ||||
| OwnedArray<ThreadPoolThread> threads; | OwnedArray<ThreadPoolThread> threads; | ||||
| @@ -297,7 +304,7 @@ private: | |||||
| CriticalSection lock; | CriticalSection lock; | ||||
| WaitableEvent jobFinishedSignal; | WaitableEvent jobFinishedSignal; | ||||
| bool runNextJob(); | |||||
| bool runNextJob (ThreadPoolThread&); | |||||
| ThreadPoolJob* pickNextJobToRun(); | ThreadPoolJob* pickNextJobToRun(); | ||||
| void addToDeleteList (OwnedArray<ThreadPoolJob>&, ThreadPoolJob*) const; | void addToDeleteList (OwnedArray<ThreadPoolJob>&, ThreadPoolJob*) const; | ||||
| void createThreads (int numThreads); | void createThreads (int numThreads); | ||||
| @@ -371,8 +371,8 @@ void XmlDocument::readQuotedString (String& result) | |||||
| } | } | ||||
| else if (character == 0) | else if (character == 0) | ||||
| { | { | ||||
| outOfData = true; | |||||
| setLastError ("unmatched quotes", false); | setLastError ("unmatched quotes", false); | ||||
| outOfData = true; | |||||
| break; | break; | ||||
| } | } | ||||
| @@ -432,7 +432,7 @@ XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) | |||||
| ++input; | ++input; | ||||
| if (alsoParseSubElements) | if (alsoParseSubElements) | ||||
| readChildElements (node); | |||||
| readChildElements (*node); | |||||
| break; | break; | ||||
| } | } | ||||
| @@ -487,9 +487,9 @@ XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) | |||||
| return node; | 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 (;;) | for (;;) | ||||
| { | { | ||||
| @@ -563,7 +563,25 @@ void XmlDocument::readChildElements (XmlElement* parent) | |||||
| const juce_wchar c = *input; | const juce_wchar c = *input; | ||||
| if (c == '<') | 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; | break; | ||||
| } | |||||
| if (c == 0) | if (c == 0) | ||||
| { | { | ||||
| @@ -156,23 +156,23 @@ private: | |||||
| String lastError, dtdText; | String lastError, dtdText; | ||||
| StringArray tokenisedDTD; | StringArray tokenisedDTD; | ||||
| bool needToLoadDTD, ignoreEmptyTextElements; | bool needToLoadDTD, ignoreEmptyTextElements; | ||||
| ScopedPointer <InputSource> inputSource; | |||||
| ScopedPointer<InputSource> inputSource; | |||||
| XmlElement* parseDocumentElement (String::CharPointerType, bool outer); | XmlElement* parseDocumentElement (String::CharPointerType, bool outer); | ||||
| void setLastError (const String& desc, bool carryOn); | |||||
| void setLastError (const String&, bool carryOn); | |||||
| bool parseHeader(); | bool parseHeader(); | ||||
| bool parseDTD(); | bool parseDTD(); | ||||
| void skipNextWhiteSpace(); | void skipNextWhiteSpace(); | ||||
| juce_wchar readNextChar() noexcept; | juce_wchar readNextChar() noexcept; | ||||
| XmlElement* readNextElement (bool alsoParseSubElements); | 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) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlDocument) | ||||
| }; | }; | ||||
| @@ -55,7 +55,7 @@ XmlElement::XmlElement (const String& tag) noexcept | |||||
| jassert (tag.containsNonWhitespaceChars()) | jassert (tag.containsNonWhitespaceChars()) | ||||
| // The tag can't contain spaces or other characters that would create invalid XML! | // 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 | XmlElement::XmlElement (int /*dummy*/) noexcept | ||||
| @@ -215,18 +215,18 @@ bool PropertiesFile::loadAsXml() | |||||
| bool PropertiesFile::saveAsXml() | bool PropertiesFile::saveAsXml() | ||||
| { | { | ||||
| XmlElement doc (PropertyFileConstants::fileTag); | 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); | 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 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); | e->addChildElement (childElement); | ||||
| else | else | ||||
| e->setAttribute (PropertyFileConstants::valueAttribute, | |||||
| getAllProperties().getAllValues() [i]); | |||||
| e->setAttribute (PropertyFileConstants::valueAttribute, props.getAllValues() [i]); | |||||
| } | } | ||||
| ProcessScopedLock pl (createProcessLock()); | ProcessScopedLock pl (createProcessLock()); | ||||
| @@ -311,14 +311,17 @@ bool PropertiesFile::saveAsBinary() | |||||
| out->writeInt (PropertyFileConstants::magicNumber); | 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); | out->writeInt (numProperties); | ||||
| for (int i = 0; i < numProperties; ++i) | 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; | 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, | InterprocessConnection::InterprocessConnection (const bool callbacksOnMessageThread, | ||||
| const uint32 magicMessageHeaderNumber) | const uint32 magicMessageHeaderNumber) | ||||
| : Thread ("Juce IPC connection"), | |||||
| callbackConnectionState (false), | |||||
| : callbackConnectionState (false), | |||||
| useMessageThread (callbacksOnMessageThread), | useMessageThread (callbacksOnMessageThread), | ||||
| magicMessageHeader (magicMessageHeaderNumber), | magicMessageHeader (magicMessageHeaderNumber), | ||||
| pipeReceiveMessageTimeout (-1) | pipeReceiveMessageTimeout (-1) | ||||
| { | { | ||||
| thread = new ConnectionThread (*this); | |||||
| } | } | ||||
| InterprocessConnection::~InterprocessConnection() | InterprocessConnection::~InterprocessConnection() | ||||
| @@ -37,9 +50,9 @@ InterprocessConnection::~InterprocessConnection() | |||||
| callbackConnectionState = false; | callbackConnectionState = false; | ||||
| disconnect(); | disconnect(); | ||||
| masterReference.clear(); | masterReference.clear(); | ||||
| thread = nullptr; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| bool InterprocessConnection::connectToSocket (const String& hostName, | bool InterprocessConnection::connectToSocket (const String& hostName, | ||||
| const int portNumber, | const int portNumber, | ||||
| @@ -53,7 +66,7 @@ bool InterprocessConnection::connectToSocket (const String& hostName, | |||||
| if (socket->connect (hostName, portNumber, timeOutMillisecs)) | if (socket->connect (hostName, portNumber, timeOutMillisecs)) | ||||
| { | { | ||||
| connectionMadeInt(); | connectionMadeInt(); | ||||
| startThread(); | |||||
| thread->startThread(); | |||||
| return true; | return true; | ||||
| } | } | ||||
| else | else | ||||
| @@ -99,7 +112,7 @@ bool InterprocessConnection::createPipe (const String& pipeName, const int timeo | |||||
| void InterprocessConnection::disconnect() | void InterprocessConnection::disconnect() | ||||
| { | { | ||||
| signalThreadShouldExit(); | |||||
| thread->signalThreadShouldExit(); | |||||
| { | { | ||||
| const ScopedLock sl (pipeAndSocketLock); | const ScopedLock sl (pipeAndSocketLock); | ||||
| @@ -107,7 +120,7 @@ void InterprocessConnection::disconnect() | |||||
| if (pipe != nullptr) pipe->close(); | if (pipe != nullptr) pipe->close(); | ||||
| } | } | ||||
| stopThread (4000); | |||||
| thread->stopThread (4000); | |||||
| deletePipeAndSocket(); | deletePipeAndSocket(); | ||||
| connectionLostInt(); | connectionLostInt(); | ||||
| } | } | ||||
| @@ -125,7 +138,7 @@ bool InterprocessConnection::isConnected() const | |||||
| return ((socket != nullptr && socket->isConnected()) | return ((socket != nullptr && socket->isConnected()) | ||||
| || (pipe != nullptr && pipe->isOpen())) | || (pipe != nullptr && pipe->isOpen())) | ||||
| && isThreadRunning(); | |||||
| && thread->isThreadRunning(); | |||||
| } | } | ||||
| String InterprocessConnection::getConnectedHostName() const | String InterprocessConnection::getConnectedHostName() const | ||||
| @@ -173,7 +186,7 @@ void InterprocessConnection::initialiseWithSocket (StreamingSocket* newSocket) | |||||
| jassert (socket == nullptr && pipe == nullptr); | jassert (socket == nullptr && pipe == nullptr); | ||||
| socket = newSocket; | socket = newSocket; | ||||
| connectionMadeInt(); | connectionMadeInt(); | ||||
| startThread(); | |||||
| thread->startThread(); | |||||
| } | } | ||||
| void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) | void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) | ||||
| @@ -181,7 +194,7 @@ void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) | |||||
| jassert (socket == nullptr && pipe == nullptr); | jassert (socket == nullptr && pipe == nullptr); | ||||
| pipe = newPipe; | pipe = newPipe; | ||||
| connectionMadeInt(); | connectionMadeInt(); | ||||
| startThread(); | |||||
| thread->startThread(); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -279,7 +292,7 @@ bool InterprocessConnection::readNextMessageInt() | |||||
| while (bytesInMessage > 0) | while (bytesInMessage > 0) | ||||
| { | { | ||||
| if (threadShouldExit()) | |||||
| if (thread->threadShouldExit()) | |||||
| return false; | return false; | ||||
| const int numThisTime = jmin (bytesInMessage, 65536); | const int numThisTime = jmin (bytesInMessage, 65536); | ||||
| @@ -311,9 +324,9 @@ bool InterprocessConnection::readNextMessageInt() | |||||
| return true; | return true; | ||||
| } | } | ||||
| void InterprocessConnection::run() | |||||
| void InterprocessConnection::runThread() | |||||
| { | { | ||||
| while (! threadShouldExit()) | |||||
| while (! thread->threadShouldExit()) | |||||
| { | { | ||||
| if (socket != nullptr) | if (socket != nullptr) | ||||
| { | { | ||||
| @@ -328,7 +341,7 @@ void InterprocessConnection::run() | |||||
| if (ready == 0) | if (ready == 0) | ||||
| { | { | ||||
| wait (1); | |||||
| thread->wait (1); | |||||
| continue; | continue; | ||||
| } | } | ||||
| } | } | ||||
| @@ -346,7 +359,7 @@ void InterprocessConnection::run() | |||||
| break; | break; | ||||
| } | } | ||||
| if (threadShouldExit() || ! readNextMessageInt()) | |||||
| if (thread->threadShouldExit() || ! readNextMessageInt()) | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| @@ -47,7 +47,7 @@ class MemoryBlock; | |||||
| @see InterprocessConnectionServer, Socket, NamedPipe | @see InterprocessConnectionServer, Socket, NamedPipe | ||||
| */ | */ | ||||
| class JUCE_API InterprocessConnection : private Thread | |||||
| class JUCE_API InterprocessConnection | |||||
| { | { | ||||
| public: | public: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -71,7 +71,7 @@ public: | |||||
| uint32 magicMessageHeaderNumber = 0xf2b49e2c); | uint32 magicMessageHeaderNumber = 0xf2b49e2c); | ||||
| /** Destructor. */ | /** Destructor. */ | ||||
| ~InterprocessConnection(); | |||||
| virtual ~InterprocessConnection(); | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Tries to connect this object to a socket. | /** Tries to connect this object to a socket. | ||||
| @@ -195,7 +195,12 @@ private: | |||||
| void connectionLostInt(); | void connectionLostInt(); | ||||
| void deliverDataInt (const MemoryBlock&); | void deliverDataInt (const MemoryBlock&); | ||||
| bool readNextMessageInt(); | 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) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnection) | ||||
| }; | }; | ||||
| @@ -73,6 +73,7 @@ namespace juce | |||||
| #include "timers/juce_Timer.cpp" | #include "timers/juce_Timer.cpp" | ||||
| #include "interprocess/juce_InterprocessConnection.cpp" | #include "interprocess/juce_InterprocessConnection.cpp" | ||||
| #include "interprocess/juce_InterprocessConnectionServer.cpp" | #include "interprocess/juce_InterprocessConnectionServer.cpp" | ||||
| #include "interprocess/juce_ConnectedChildProcess.cpp" | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| @@ -49,6 +49,7 @@ namespace juce | |||||
| #include "timers/juce_MultiTimer.h" | #include "timers/juce_MultiTimer.h" | ||||
| #include "interprocess/juce_InterprocessConnection.h" | #include "interprocess/juce_InterprocessConnection.h" | ||||
| #include "interprocess/juce_InterprocessConnectionServer.h" | #include "interprocess/juce_InterprocessConnectionServer.h" | ||||
| #include "interprocess/juce_ConnectedChildProcess.h" | |||||
| #include "native/juce_ScopedXLock.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; | 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(), | 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); | justificationType); | ||||
| arr.draw (*this); | 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, | 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); | drawText (text, Rectangle<int> (x, y, width, height), justificationType, useEllipsesIfTooBig); | ||||
| } | } | ||||
| @@ -174,6 +174,20 @@ public: | |||||
| Justification justificationType, | Justification justificationType, | ||||
| bool useEllipsesIfTooBig) const; | 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. | /** Tries to draw a text string inside a given space. | ||||
| This does its best to make the given text readable within the specified rectangle, | This does its best to make the given text readable within the specified rectangle, | ||||
| @@ -806,16 +806,19 @@ namespace EdgeTableFillers | |||||
| alphaLevel = (alphaLevel * extraAlpha) >> 8; | alphaLevel = (alphaLevel * extraAlpha) >> 8; | ||||
| x -= xOffset; | 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 | 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 | else | ||||
| copyRow (dest, getSrcPixel (x), width); | copyRow (dest, getSrcPixel (x), width); | ||||
| } | } | ||||
| @@ -826,16 +829,19 @@ namespace EdgeTableFillers | |||||
| DestPixelType* dest = getDestPixel (x); | DestPixelType* dest = getDestPixel (x); | ||||
| x -= xOffset; | 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 | 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 | else | ||||
| copyRow (dest, getSrcPixel (x), width); | copyRow (dest, getSrcPixel (x), width); | ||||
| } | } | ||||
| @@ -107,6 +107,18 @@ void Drawable::setBoundsToEnclose (const Rectangle<float>& area) | |||||
| setBounds (newBounds); | 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) | void Drawable::setOriginWithOriginalSize (Point<float> originWithinParent) | ||||
| { | { | ||||
| @@ -175,6 +175,11 @@ public: | |||||
| */ | */ | ||||
| virtual Rectangle<float> getDrawableBounds() const = 0; | 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. */ | /** Internal class used to manage ValueTrees that represent Drawables. */ | ||||
| class ValueTreeWrapperBase | class ValueTreeWrapperBase | ||||
| @@ -452,3 +452,21 @@ void DrawableShape::FillAndStrokeState::setStrokeType (const PathStrokeType& new | |||||
| state.setProperty (capStyle, newStrokeType.getEndStyle() == PathStrokeType::butt | state.setProperty (capStyle, newStrokeType.getEndStyle() == PathStrokeType::butt | ||||
| ? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager); | ? "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; | void paint (Graphics&) override; | ||||
| /** @internal */ | /** @internal */ | ||||
| bool hitTest (int x, int y) override; | bool hitTest (int x, int y) override; | ||||
| /** @internal */ | |||||
| bool replaceColour (Colour originalColour, Colour replacementColour) override; | |||||
| protected: | protected: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -131,10 +131,12 @@ public: | |||||
| */ | */ | ||||
| bool autoScroll (int mouseX, int mouseY, int distanceFromEdge, int maximumSpeed); | 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(); } | 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. | /** Returns the position within the child component of the top-left of its visible area. | ||||
| @see getViewWidth, setViewPosition | @see getViewWidth, setViewPosition | ||||
| */ | */ | ||||
| @@ -198,7 +198,7 @@ void FileChooser::showPlatformDialog (Array<File>& results, const String& title_ | |||||
| } | } | ||||
| else | 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) | if (warnAboutOverwritingExistingFiles) | ||||
| flags |= OFN_OVERWRITEPROMPT; | flags |= OFN_OVERWRITEPROMPT; | ||||
| @@ -1424,47 +1424,40 @@ void TextEditor::scrollToMakeSureCursorIsVisible() | |||||
| if (keepCaretOnScreen) | 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()) | 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) | 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, | class NSViewAttachment : public ReferenceCountedObject, | ||||
| public ComponentMovementWatcher | |||||
| public ComponentMovementWatcher, | |||||
| private NSViewResizeWatcher | |||||
| { | { | ||||
| public: | public: | ||||
| NSViewAttachment (NSView* const v, Component& comp) | NSViewAttachment (NSView* const v, Component& comp) | ||||
| : ComponentMovementWatcher (&comp), | : ComponentMovementWatcher (&comp), | ||||
| view (v), owner (comp), | view (v), owner (comp), | ||||
| currentPeer (nullptr), frameChangeCallback (nullptr) | |||||
| currentPeer (nullptr) | |||||
| { | { | ||||
| [view retain]; | [view retain]; | ||||
| [view setPostsFrameChangedNotifications: YES]; | [view setPostsFrameChangedNotifications: YES]; | ||||
| @@ -37,21 +105,12 @@ public: | |||||
| if (owner.isShowing()) | if (owner.isShowing()) | ||||
| componentPeerChanged(); | 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() | ~NSViewAttachment() | ||||
| { | { | ||||
| [[NSNotificationCenter defaultCenter] removeObserver: frameChangeCallback]; | |||||
| [frameChangeCallback release]; | |||||
| detachViewWatcher(); | |||||
| removeFromParent(); | removeFromParent(); | ||||
| [view release]; | [view release]; | ||||
| } | } | ||||
| @@ -103,12 +162,16 @@ public: | |||||
| componentPeerChanged(); | componentPeerChanged(); | ||||
| } | } | ||||
| void viewResized() override | |||||
| { | |||||
| owner.childBoundsChanged (nullptr); | |||||
| } | |||||
| NSView* const view; | NSView* const view; | ||||
| private: | private: | ||||
| Component& owner; | Component& owner; | ||||
| ComponentPeer* currentPeer; | ComponentPeer* currentPeer; | ||||
| id frameChangeCallback; | |||||
| void removeFromParent() | void removeFromParent() | ||||
| { | { | ||||
| @@ -117,31 +180,6 @@ private: | |||||
| // override the call and use it as a sign that they're being deleted, which breaks everything.. | // 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) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewAttachment) | ||||
| }; | }; | ||||