diff --git a/data/copy-juce-carla b/data/copy-juce-carla index f1e99c518..76f4efb8f 100755 --- a/data/copy-juce-carla +++ b/data/copy-juce-carla @@ -2,10 +2,10 @@ set -e -JUCE_MODULES_DIR="/home/falktx/Personal/FOSS/GIT/DISTRHO/libs/juce/source/modules/" -CARLA_MODULES_DIR="/home/falktx/Personal/FOSS/GIT/Carla/source/modules" +JUCE_MODULES_DIR="/home/falktx/Personal/FOSS/GIT/distrho/DISTRHO/libs/juce/source/modules/" +CARLA_MODULES_DIR="/home/falktx/Personal/FOSS/GIT/falktx/Carla/source/modules/" -MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics") +MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") for M in $MODULES; do echo $M; diff --git a/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp b/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp index c11140450..71ac41c5c 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp +++ b/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp @@ -941,6 +941,11 @@ double MidiMessage::getMidiNoteInHertz (int noteNumber, const double frequencyOf return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); } +bool MidiMessage::isMidiNoteBlack (int noteNumber) noexcept +{ + return ((1 << (noteNumber % 12)) & 0x054a) != 0; +} + const char* MidiMessage::getGMInstrumentName (const int n) { static const char* names[] = diff --git a/source/modules/juce_audio_basics/midi/juce_MidiMessage.h b/source/modules/juce_audio_basics/midi/juce_MidiMessage.h index c775883d3..44eee7c26 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiMessage.h +++ b/source/modules/juce_audio_basics/midi/juce_MidiMessage.h @@ -889,6 +889,9 @@ public: */ static double getMidiNoteInHertz (int noteNumber, const double frequencyOfA = 440.0) noexcept; + /** Returns true if the given midi note number is a black key. */ + static bool isMidiNoteBlack (int noteNumber) noexcept; + /** Returns the standard name of a GM instrument, or nullptr if unknown for this index. @param midiInstrumentNumber the program number 0 to 127 diff --git a/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index af91342ab..74e7a6c22 100644 --- a/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -231,24 +231,15 @@ public: NSString* nameNSString = nil; size = sizeof (nameNSString); - #if JUCE_CLANG - // Very irritating that AudioDeviceGetProperty is marked as deprecated, since - // there seems to be no replacement way of getting the channel names. - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - #endif + pa.mSelector = kAudioObjectPropertyElementName; + pa.mElement = chanNum + 1; - if (AudioDeviceGetProperty (deviceID, chanNum + 1, input, kAudioDevicePropertyChannelNameCFString, - &size, &nameNSString) == noErr) + if (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &nameNSString) == noErr) { name = nsStringToJuce (nameNSString); [nameNSString release]; } - #if JUCE_CLANG - #pragma clang diagnostic pop - #endif - if ((input ? activeInputChans : activeOutputChans) [chanNum]) { CallbackDetailsForChannel info = { i, (int) j, (int) b.mNumberChannels }; diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h index 0c2f4fb29..8b4a4ad60 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -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 (string)); } static juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast (string)); } -static void toString (Steinberg::Vst::String128 result, const juce::String& source) +static void toString128 (Steinberg::Vst::String128 result, const juce::String& source) { Steinberg::UString (result, 128).fromAscii (source.toUTF8()); } @@ -69,6 +69,13 @@ static Steinberg::Vst::TChar* toString (const juce::String& source) noexcept return reinterpret_cast (source.toUTF16().getAddress()); } +#if JUCE_WINDOWS +static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeHWND; +#else +static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeNSView; +#endif + + //============================================================================== /** The equivalent numChannels and speaker arrangements should always match between this function and fillWithCorrespondingSpeakerArrangements(). @@ -81,22 +88,32 @@ static Steinberg::Vst::SpeakerArrangement getArrangementForNumChannels (int numC { using namespace Steinberg::Vst::SpeakerArr; - if (numChannels >= 14) return k131; - if (numChannels >= 13) return k130; - if (numChannels >= 12) return k111; - if (numChannels >= 11) return k101; - if (numChannels >= 10) return k91; - if (numChannels >= 9) return k90; - if (numChannels >= 8) return k71CineFullFront; - if (numChannels >= 7) return k61Cine; - if (numChannels >= 6) return k51; - if (numChannels >= 5) return k50; - if (numChannels >= 4) return k31Cine; - if (numChannels >= 3) return k30Cine; - if (numChannels >= 2) return kStereo; - if (numChannels >= 1) return kMono; - - return kEmpty; + switch (numChannels) + { + case 0: return kEmpty; + case 1: return kMono; + case 2: return kStereo; + case 3: return k30Cine; + case 4: return k31Cine; + case 5: return k50; + case 6: return k51; + case 7: return k61Cine; + case 8: return k71CineFullFront; + case 9: return k90; + case 10: return k91; + case 11: return k101; + case 12: return k111; + case 13: return k130; + case 14: return k131; + case 24: return (Steinberg::Vst::SpeakerArrangement) 1929904127; // k222 + default: break; + } + + jassert (numChannels >= 0); + + juce::BigInteger bi; + bi.setRange (0, jmin (numChannels, (int) (sizeof (Steinberg::Vst::SpeakerArrangement) * 8)), true); + return (Steinberg::Vst::SpeakerArrangement) bi.toInt64(); } /** The equivalent numChannels and speaker arrangements should always @@ -119,10 +136,17 @@ static void fillWithCorrespondingSpeakerArrangements (Array 24) + { + juce::BigInteger bi; + bi.setRange (0, jmin (numChannels, (int) (sizeof (Steinberg::Vst::SpeakerArrangement) * 8)), true); + destination.add ((Steinberg::Vst::SpeakerArrangement) bi.toInt64()); + } + + if (numChannels >= 24) destination.add ((Steinberg::Vst::SpeakerArrangement) 1929904127); // k222 if (numChannels >= 14) destination.add (k131); if (numChannels >= 13) destination.add (k130); if (numChannels >= 12) destination.add (k111); @@ -225,7 +249,9 @@ public: //============================================================================== static void toMidiBuffer (MidiBuffer& result, Steinberg::Vst::IEventList& eventList) { - for (Steinberg::int32 i = 0; i < eventList.getEventCount(); ++i) + const int32 numEvents = eventList.getEventCount(); + + for (Steinberg::int32 i = 0; i < numEvents; ++i) { Steinberg::Vst::Event e; @@ -407,4 +433,4 @@ namespace VST3BufferExchange } } -#endif //JUCE_VST3COMMON_H_INCLUDED +#endif // JUCE_VST3COMMON_H_INCLUDED diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h b/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h index be1538238..612e850a3 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h +++ b/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h @@ -22,8 +22,8 @@ ============================================================================== */ -#ifndef JUCE_VST3HEADER_H_INCLUDED -#define JUCE_VST3HEADER_H_INCLUDED +#ifndef JUCE_VST3HEADERS_H_INCLUDED +#define JUCE_VST3HEADERS_H_INCLUDED #undef Point #undef Component @@ -159,7 +159,6 @@ namespace Steinberg #undef OBJ_METHODS #undef QUERY_INTERFACE #undef LICENCE_UID -#undef DEF_CLASS_IID #undef BEGIN_FACTORY #undef DEF_CLASS #undef DEF_CLASS1 @@ -169,4 +168,4 @@ namespace Steinberg #undef Point #undef Component -#endif //JUCE_VST3HEADER_H_INCLUDED +#endif // JUCE_VST3HEADERS_H_INCLUDED diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 8de72a186..55361fc2a 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -998,7 +998,8 @@ private: void releaseFactory() { - const Steinberg::FReleaser releaser (factory); + if (factory != nullptr) + factory->release(); } #if JUCE_WINDOWS @@ -1219,10 +1220,9 @@ public: #if JUCE_MAC dummyComponent.setView (nullptr); - [pluginHandle release]; #endif - const Steinberg::FReleaser releaser (view); + view = nullptr; } JUCE_DECLARE_VST3_COM_REF_METHODS @@ -1284,7 +1284,8 @@ public: dummyComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); #endif - Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); // Some plugins don't update their cursor correctly when mousing out the window + // Some plugins don't update their cursor correctly when mousing out the window + Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); recursiveResize = false; } @@ -1316,7 +1317,7 @@ public: private: //============================================================================== Atomic 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 view; #if JUCE_WINDOWS class ChildComponent : public Component @@ -1335,14 +1336,14 @@ private: ScopedPointer peer; typedef HWND HandleFormat; #elif JUCE_MAC - NSViewComponent dummyComponent; + AutoResizingNSViewComponentWithParent dummyComponent; typedef NSView* HandleFormat; #else Component dummyComponent; typedef void* HandleFormat; #endif - HandleFormat pluginHandle; // Don't delete this + HandleFormat pluginHandle; bool recursiveResize; //============================================================================== @@ -1368,17 +1369,12 @@ private: #elif JUCE_MAC dummyComponent.setBounds (getBounds().withZeroOrigin()); addAndMakeVisible (dummyComponent); - pluginHandle = [[NSView alloc] init]; - dummyComponent.setView (pluginHandle); + pluginHandle = (NSView*) dummyComponent.getView(); + jassert (pluginHandle != nil); #endif if (pluginHandle != nullptr) - view->attached (pluginHandle, - #if JUCE_WINDOWS - kPlatformTypeHWND); - #else - kPlatformTypeNSView); - #endif + warnOnFailure (view->attached (pluginHandle, defaultVST3WindowType)); } } @@ -1452,7 +1448,8 @@ public: if (! fetchComponentAndController (factory, factory->countClasses())) return false; - editController->initialize (host->getFUnknown()); // (May return an error if the plugin combines the IComponent and IEditController implementations) + // (May return an error if the plugin combines the IComponent and IEditController implementations) + editController->initialize (host->getFUnknown()); isControllerInitialised = true; editController->setComponentHandler (host); diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index be3b3629a..3d29db048 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -187,7 +187,6 @@ static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstI static int shellUIDToCreate = 0; static int insideVSTCallback = 0; -#ifndef JUCE_PLUGIN_HOST_NO_UI class IdleCallRecursionPreventer { public: @@ -211,9 +210,6 @@ private: }; class VSTPluginWindow; -#else -struct IdleCallRecursionPreventer{}; -#endif //============================================================================== // Change this to disable logging of various VST activities @@ -240,7 +236,6 @@ static void* NewCFMFromMachO (void* const machofp) noexcept } #endif -#ifndef JUCE_PLUGIN_HOST_NO_UI //============================================================================== #if JUCE_LINUX @@ -345,7 +340,6 @@ namespace } } -#endif #endif //============================================================================== @@ -716,13 +710,9 @@ static const int defaultVSTBlockSizeValue = 512; //============================================================================== //============================================================================== -#ifndef JUCE_PLUGIN_HOST_NO_UI class VSTPluginInstance : public AudioPluginInstance, private Timer, private AsyncUpdater -#else -class VSTPluginInstance : public AudioPluginInstance -#endif { public: VSTPluginInstance (const ModuleHandle::Ptr& module_) @@ -793,10 +783,8 @@ public: UseResFile (module->resFileId); #endif -#ifndef JUCE_PLUGIN_HOST_NO_UI // Must delete any editors before deleting the plugin instance! jassert (getActiveEditor() == 0); -#endif _fpreset(); // some dodgy plugs fuck around with this @@ -1254,7 +1242,6 @@ public: void setCurrentProgramStateInformation (const void* data, int size) override { loadFromFXBFile (data, size); } //============================================================================== -#ifndef JUCE_PLUGIN_HOST_NO_UI void timerCallback() override { if (dispatch (effIdle, 0, 0, 0, 0) == 0) @@ -1266,7 +1253,6 @@ public: // indicates that something about the plugin has changed.. updateHostDisplay(); } -#endif VstIntPtr handleCallback (VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) { @@ -1284,7 +1270,6 @@ public: #pragma warning (pop) #endif -#ifndef JUCE_PLUGIN_HOST_NO_UI case audioMasterIdle: if (insideVSTCallback == 0 && MessageManager::getInstance()->isThisTheMessageThread()) { @@ -1313,7 +1298,6 @@ public: case audioMasterUpdateDisplay: triggerAsyncUpdate(); break; case audioMasterIOChanged: setLatencySamples (effect->initialDelay); break; case audioMasterNeedIdle: startTimer (50); break; -#endif case audioMasterGetSampleRate: return (VstIntPtr) (getSampleRate() > 0 ? getSampleRate() : defaultVSTSampleRateValue); case audioMasterGetBlockSize: return (VstIntPtr) (getBlockSize() > 0 ? getBlockSize() : defaultVSTBlockSizeValue); @@ -1399,12 +1383,10 @@ public: case audioMasterGetVendorString: case audioMasterGetProductString: { - String hostName ("Carla"); + String hostName ("Juce VST Host"); -#ifndef JUCE_PLUGIN_HOST_NO_UI if (JUCEApplicationBase* app = JUCEApplicationBase::getInstance()) hostName = app->getApplicationName(); -#endif hostName.copyToUTF8 ((char*) ptr, (size_t) jmin (kVstMaxVendorStrLen, kVstMaxProductStrLen) - 1); break; @@ -1911,7 +1893,6 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginInstance) }; -#ifndef JUCE_PLUGIN_HOST_NO_UI //============================================================================== static Array activeVSTWindows; @@ -1946,17 +1927,10 @@ public: #elif JUCE_MAC #if JUCE_SUPPORT_CARBON if (! plug.usesCocoaNSView) - { addAndMakeVisible (carbonWrapper = new CarbonWrapperComponent (*this)); - } else #endif - { - addAndMakeVisible (cocoaWrapper = new AutoResizingNSViewComponent()); - NSView* innerView = [[NSView alloc] init]; - cocoaWrapper->setView (innerView); - [innerView release]; - } + addAndMakeVisible (cocoaWrapper = new AutoResizingNSViewComponentWithParent()); #endif activeVSTWindows.add (this); @@ -2619,7 +2593,7 @@ private: ScopedPointer carbonWrapper; #endif - ScopedPointer cocoaWrapper; + ScopedPointer cocoaWrapper; void resized() override { @@ -2635,17 +2609,12 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginWindow) }; -#endif //============================================================================== AudioProcessorEditor* VSTPluginInstance::createEditor() { -#ifndef JUCE_PLUGIN_HOST_NO_UI return hasEditor() ? new VSTPluginWindow (*this) : nullptr; -#else - return nullptr; -#endif } //============================================================================== diff --git a/source/modules/juce_audio_processors/juce_audio_processors.cpp b/source/modules/juce_audio_processors/juce_audio_processors.cpp index 2b719f928..474198a89 100644 --- a/source/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/source/modules/juce_audio_processors/juce_audio_processors.cpp @@ -57,6 +57,11 @@ #undef KeyPress #endif +#if ! JUCE_WINDOWS && ! JUCE_MAC + #undef JUCE_PLUGINHOST_VST3 + #define JUCE_PLUGINHOST_VST3 0 +#endif + //============================================================================== namespace juce { @@ -72,6 +77,7 @@ static inline bool arrayContainsPlugin (const OwnedArray& lis } #if JUCE_MAC +//============================================================================== struct AutoResizingNSViewComponent : public NSViewComponent, private AsyncUpdater { @@ -95,6 +101,38 @@ struct AutoResizingNSViewComponent : public NSViewComponent, bool recursive; }; + +//============================================================================== +struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent, + private Timer +{ + AutoResizingNSViewComponentWithParent() + { + NSView* v = [[NSView alloc] init]; + setView (v); + [v release]; + + startTimer (100); + } + + void timerCallback() override + { + if (NSView* parent = (NSView*) getView()) + { + if (NSView* child = [[parent subviews] firstObject]) + { + NSRect f = [parent frame]; + NSSize newSize = [child frame].size; + + if (f.size.width != newSize.width || f.size.height != newSize.height) + { + f.size = newSize; + [parent setFrame: f]; + } + } + } + } +}; #endif #if JUCE_CLANG diff --git a/source/modules/juce_audio_processors/juce_audio_processors.h b/source/modules/juce_audio_processors/juce_audio_processors.h index 78dc79f0f..3854d2af5 100644 --- a/source/modules/juce_audio_processors/juce_audio_processors.h +++ b/source/modules/juce_audio_processors/juce_audio_processors.h @@ -44,7 +44,7 @@ Enables the VST3 audio plugin hosting classes. This requires the Steinberg VST3 SDK to be installed on your machine. - @see VSTPluginFormat, VVST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_AU + @see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_AU */ #ifndef JUCE_PLUGINHOST_VST3 #define JUCE_PLUGINHOST_VST3 0 @@ -60,7 +60,7 @@ #endif #if ! (JUCE_PLUGINHOST_AU || JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3) -// #error "You need to set either the JUCE_PLUGINHOST_AU anr/or JUCE_PLUGINHOST_VST flags if you're using this module!" +// #error "You need to set either the JUCE_PLUGINHOST_AU and/or JUCE_PLUGINHOST_VST and/or JUCE_PLUGINHOST_VST3 flags if you're using this module!" #endif #if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT) diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index 1dd0d46aa..501fedf43 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -44,11 +44,9 @@ AudioProcessor::AudioProcessor() AudioProcessor::~AudioProcessor() { -#ifndef JUCE_PLUGIN_HOST_NO_UI // ooh, nasty - the editor should have been deleted before the filter // that it refers to is deleted.. jassert (activeEditor == nullptr); -#endif #if JUCE_DEBUG // This will fail if you've called beginParameterChangeGesture() for one @@ -221,15 +219,12 @@ void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noe { const ScopedLock sl (callbackLock); -#ifndef JUCE_PLUGIN_HOST_NO_UI if (activeEditor == editor) activeEditor = nullptr; -#endif } AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() { -#ifndef JUCE_PLUGIN_HOST_NO_UI if (activeEditor != nullptr) return activeEditor; @@ -248,9 +243,6 @@ AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() } return ed; -#else - return nullptr; -#endif } //============================================================================== diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 8ed464a6e..ee3064cf1 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -366,13 +366,11 @@ public: */ virtual bool hasEditor() const = 0; -#ifndef JUCE_PLUGIN_HOST_NO_UI //============================================================================== /** Returns the active editor, if there is one. Bear in mind this can return nullptr, even if an editor has previously been opened. */ AudioProcessorEditor* getActiveEditor() const noexcept { return activeEditor; } -#endif /** Returns the active editor, or if there isn't one, it will create one. This may call createEditor() internally to create the component. @@ -616,6 +614,7 @@ public: { wrapperType_Undefined = 0, wrapperType_VST, + wrapperType_VST3, wrapperType_AudioUnit, wrapperType_RTAS, wrapperType_AAX, @@ -658,9 +657,7 @@ protected: private: Array listeners; -#ifndef JUCE_PLUGIN_HOST_NO_UI Component::SafePointer activeEditor; -#endif double sampleRate; int blockSize, numInputChannels, numOutputChannels, latencySamples; bool suspended, nonRealtime; diff --git a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp index 2ea3c9824..afbf23e5e 100644 --- a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp +++ b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp @@ -202,6 +202,14 @@ void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& f } } } + + scanFinished(); +} + +void KnownPluginList::scanFinished() +{ + if (scanner != nullptr) + scanner->scanFinished(); } const StringArray& KnownPluginList::getBlacklistedFiles() const @@ -280,10 +288,16 @@ void KnownPluginList::sort (const SortMethod method, bool forwards) { if (method != defaultOrder) { + Array oldOrder, newOrder; + oldOrder.addArray (types); + PluginSorter sorter (method, forwards); types.sort (sorter, true); - sendChangeMessage(); + newOrder.addArray (types); + + if (oldOrder != newOrder) + sendChangeMessage(); } } @@ -523,3 +537,13 @@ int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const //============================================================================== KnownPluginList::CustomScanner::CustomScanner() {} KnownPluginList::CustomScanner::~CustomScanner() {} + +void KnownPluginList::CustomScanner::scanFinished() {} + +bool KnownPluginList::CustomScanner::shouldExit() const noexcept +{ + if (ThreadPoolJob* job = ThreadPoolJob::getCurrentThreadPoolJob()) + return job->shouldExit(); + + return false; +} diff --git a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h index 5f606437e..3c8df25fd 100644 --- a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h +++ b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h @@ -97,6 +97,9 @@ public: OwnedArray & typesFound, AudioPluginFormat& formatToUse); + /** Tells a custom scanner that a scan has finished, and it can release any resources. */ + void scanFinished(); + /** Returns true if the specified file is already known about and if it hasn't been modified since our entry was created. */ @@ -170,8 +173,8 @@ public: struct PluginTree { String folder; /**< The name of this folder in the tree */ - OwnedArray subFolders; - Array plugins; + OwnedArray subFolders; + Array plugins; }; /** Creates a PluginTree object containing all the known plugins. */ @@ -190,9 +193,21 @@ public: virtual bool findPluginTypesFor (AudioPluginFormat& format, OwnedArray & result, const String& fileOrIdentifier) = 0; + + /** Called when a scan has finished, to allow clean-up of resources. */ + virtual void scanFinished(); + + /** Returns true if the current scan should be abandoned. + Any blocking methods should check this value repeatedly and return if + if becomes true. + */ + bool shouldExit() const noexcept; }; - void setCustomScanner (CustomScanner* scanner); + /** Supplies a custom scanner to be used in future scans. + The KnownPluginList will take ownership of the object passed in. + */ + void setCustomScanner (CustomScanner*); private: //============================================================================== diff --git a/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp b/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp index 5502f00a9..a7beea733 100644 --- a/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp +++ b/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp @@ -63,6 +63,7 @@ PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, PluginDirectoryScanner::~PluginDirectoryScanner() { + list.scanFinished(); } //============================================================================== diff --git a/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp index 65cb60d18..a0c2388cf 100644 --- a/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp +++ b/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -162,6 +162,7 @@ PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, Kno setSize (400, 600); list.addChangeListener (this); updateList(); + table.getHeader().reSortTable(); PluginDirectoryScanner::applyBlacklistingsFromDeadMansPedal (list, deadMansPedalFile); deadMansPedalFile.deleteFile(); @@ -196,6 +197,7 @@ void PluginListComponent::resized() void PluginListComponent::changeListenerCallback (ChangeBroadcaster*) { + table.getHeader().reSortTable(); updateList(); } diff --git a/source/modules/juce_core/containers/juce_HashMap.h b/source/modules/juce_core/containers/juce_HashMap.h index a10942195..e663d3228 100644 --- a/source/modules/juce_core/containers/juce_HashMap.h +++ b/source/modules/juce_core/containers/juce_HashMap.h @@ -60,7 +60,7 @@ struct DefaultHashFunctions @code struct MyHashGenerator { - int generateHash (MyKeyType key, int upperLimit) + int generateHash (MyKeyType key, int upperLimit) const { // The function must return a value 0 <= x < upperLimit return someFunctionOfMyKeyType (key) % upperLimit; diff --git a/source/modules/juce_core/containers/juce_NamedValueSet.cpp b/source/modules/juce_core/containers/juce_NamedValueSet.cpp index ed30af2d4..0d866b1b8 100644 --- a/source/modules/juce_core/containers/juce_NamedValueSet.cpp +++ b/source/modules/juce_core/containers/juce_NamedValueSet.cpp @@ -141,7 +141,7 @@ int NamedValueSet::size() const noexcept return values.size(); } -const var& NamedValueSet::operator[] (const Identifier name) const +const var& NamedValueSet::operator[] (Identifier name) const { for (NamedValue* i = values; i != nullptr; i = i->nextListItem) if (i->name == name) @@ -150,7 +150,7 @@ const var& NamedValueSet::operator[] (const Identifier name) const return var::null; } -var NamedValueSet::getWithDefault (const Identifier name, const var& defaultReturnValue) const +var NamedValueSet::getWithDefault (Identifier name, const var& defaultReturnValue) const { if (const var* const v = getVarPointer (name)) return *v; @@ -158,7 +158,7 @@ var NamedValueSet::getWithDefault (const Identifier name, const var& defaultRetu return defaultReturnValue; } -var* NamedValueSet::getVarPointer (const Identifier name) const noexcept +var* NamedValueSet::getVarPointer (Identifier name) const noexcept { for (NamedValue* i = values; i != nullptr; i = i->nextListItem) if (i->name == name) @@ -168,7 +168,7 @@ var* NamedValueSet::getVarPointer (const Identifier name) const noexcept } #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS -bool NamedValueSet::set (const Identifier name, var&& newValue) +bool NamedValueSet::set (Identifier name, var&& newValue) { LinkedListPointer* i = &values; @@ -193,7 +193,7 @@ bool NamedValueSet::set (const Identifier name, var&& newValue) } #endif -bool NamedValueSet::set (const Identifier name, const var& newValue) +bool NamedValueSet::set (Identifier name, const var& newValue) { LinkedListPointer* i = &values; @@ -217,12 +217,27 @@ bool NamedValueSet::set (const Identifier name, const var& newValue) return true; } -bool NamedValueSet::contains (const Identifier name) const +bool NamedValueSet::contains (Identifier name) const { return getVarPointer (name) != nullptr; } -bool NamedValueSet::remove (const Identifier name) +int NamedValueSet::indexOf (Identifier name) const noexcept +{ + int index = 0; + + for (NamedValue* i = values; i != nullptr; i = i->nextListItem) + { + if (i->name == name) + return index; + + ++index; + } + + return -1; +} + +bool NamedValueSet::remove (Identifier name) { LinkedListPointer* i = &values; @@ -245,7 +260,7 @@ bool NamedValueSet::remove (const Identifier name) return false; } -const Identifier NamedValueSet::getName (const int index) const +Identifier NamedValueSet::getName (const int index) const { const NamedValue* const v = values[index]; jassert (v != nullptr); diff --git a/source/modules/juce_core/containers/juce_NamedValueSet.h b/source/modules/juce_core/containers/juce_NamedValueSet.h index 2ac2b8630..6043e2ab9 100644 --- a/source/modules/juce_core/containers/juce_NamedValueSet.h +++ b/source/modules/juce_core/containers/juce_NamedValueSet.h @@ -67,46 +67,49 @@ public: If the name isn't found, this will return a void variant. @see getProperty */ - const var& operator[] (const Identifier name) const; + const var& operator[] (Identifier name) const; /** Tries to return the named value, but if no such value is found, this will instead return the supplied default value. */ - var getWithDefault (const Identifier name, const var& defaultReturnValue) const; + var getWithDefault (Identifier name, const var& defaultReturnValue) const; /** Changes or adds a named value. @returns true if a value was changed or added; false if the value was already set the the value passed-in. */ - bool set (const Identifier name, const var& newValue); + bool set (Identifier name, const var& newValue); #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS /** Changes or adds a named value. @returns true if a value was changed or added; false if the value was already set the the value passed-in. */ - bool set (const Identifier name, var&& newValue); + bool set (Identifier name, var&& newValue); #endif /** Returns true if the set contains an item with the specified name. */ - bool contains (const Identifier name) const; + bool contains (Identifier name) const; /** Removes a value from the set. @returns true if a value was removed; false if there was no value with the name that was given. */ - bool remove (const Identifier name); + bool remove (Identifier name); /** Returns the name of the value at a given index. The index must be between 0 and size() - 1. */ - const Identifier getName (int index) const; + Identifier getName (int index) const; /** Returns the value of the item at a given index. The index must be between 0 and size() - 1. */ const var& getValueAt (int index) const; + /** Returns the index of the given name, or -1 if it's not found. */ + int indexOf (Identifier name) const noexcept; + /** Removes all values. */ void clear(); @@ -117,7 +120,7 @@ public: Do not use this method unless you really need access to the internal var object for some reason - for normal reading and writing always prefer operator[]() and set(). */ - var* getVarPointer (const Identifier name) const noexcept; + var* getVarPointer (Identifier name) const noexcept; //============================================================================== /** Sets properties to the values of all of an XML element's attributes. */ diff --git a/source/modules/juce_core/juce_core.cpp b/source/modules/juce_core/juce_core.cpp index 8288e91a5..be6b6ebf5 100644 --- a/source/modules/juce_core/juce_core.cpp +++ b/source/modules/juce_core/juce_core.cpp @@ -151,7 +151,6 @@ namespace juce #include "text/juce_StringPairArray.cpp" #include "text/juce_StringPool.cpp" #include "text/juce_TextDiff.cpp" -#include "threads/juce_ChildProcess.cpp" #include "threads/juce_ReadWriteLock.cpp" #include "threads/juce_Thread.cpp" #include "threads/juce_ThreadPool.cpp" @@ -218,6 +217,7 @@ namespace juce #endif +#include "threads/juce_ChildProcess.cpp" #include "threads/juce_HighResolutionTimer.cpp" } diff --git a/source/modules/juce_core/native/juce_posix_SharedCode.h b/source/modules/juce_core/native/juce_posix_SharedCode.h index 46d2382d6..402662a15 100644 --- a/source/modules/juce_core/native/juce_posix_SharedCode.h +++ b/source/modules/juce_core/native/juce_posix_SharedCode.h @@ -1053,7 +1053,7 @@ public: close (pipeHandle); } - bool isRunning() const + bool isRunning() const noexcept { if (childPID != 0) { @@ -1065,7 +1065,7 @@ public: return false; } - int read (void* const dest, const int numBytes) + int read (void* const dest, const int numBytes) noexcept { jassert (dest != nullptr); @@ -1082,11 +1082,25 @@ public: return 0; } - bool killProcess() const + bool killProcess() const noexcept { return ::kill (childPID, SIGKILL) == 0; } + uint32 getExitCode() const noexcept + { + if (childPID != 0) + { + int childState = 0; + const int pid = waitpid (childPID, &childState, WNOHANG); + + if (pid >= 0 && WIFEXITED (childState)) + return WEXITSTATUS (childState); + } + + return 0; + } + int childPID; private: @@ -1114,21 +1128,6 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) return activeProcess != nullptr; } -bool ChildProcess::isRunning() const -{ - return activeProcess != nullptr && activeProcess->isRunning(); -} - -int ChildProcess::readProcessOutput (void* dest, int numBytes) -{ - return activeProcess != nullptr ? activeProcess->read (dest, numBytes) : 0; -} - -bool ChildProcess::kill() -{ - return activeProcess == nullptr || activeProcess->killProcess(); -} - //============================================================================== struct HighResolutionTimer::Pimpl { diff --git a/source/modules/juce_core/native/juce_win32_Threads.cpp b/source/modules/juce_core/native/juce_win32_Threads.cpp index d6de0f804..4537bce8b 100644 --- a/source/modules/juce_core/native/juce_win32_Threads.cpp +++ b/source/modules/juce_core/native/juce_win32_Threads.cpp @@ -482,12 +482,12 @@ public: CloseHandle (writePipe); } - bool isRunning() const + bool isRunning() const noexcept { return WaitForSingleObject (processInfo.hProcess, 0) != WAIT_OBJECT_0; } - int read (void* dest, int numNeeded) const + int read (void* dest, int numNeeded) const noexcept { int total = 0; @@ -522,11 +522,18 @@ public: return total; } - bool killProcess() const + bool killProcess() const noexcept { return TerminateProcess (processInfo.hProcess, 0) != FALSE; } + uint32 getExitCode() const noexcept + { + DWORD exitCode = 0; + GetExitCodeProcess (processInfo.hProcess, &exitCode); + return (uint32) exitCode; + } + bool ok; private: @@ -551,21 +558,6 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) return start (args.joinIntoString (" "), streamFlags); } -bool ChildProcess::isRunning() const -{ - return activeProcess != nullptr && activeProcess->isRunning(); -} - -int ChildProcess::readProcessOutput (void* dest, int numBytes) -{ - return activeProcess != nullptr ? activeProcess->read (dest, numBytes) : 0; -} - -bool ChildProcess::kill() -{ - return activeProcess == nullptr || activeProcess->killProcess(); -} - //============================================================================== struct HighResolutionTimer::Pimpl { diff --git a/source/modules/juce_core/streams/juce_OutputStream.cpp b/source/modules/juce_core/streams/juce_OutputStream.cpp index 47c2f0686..7c6d0bd32 100644 --- a/source/modules/juce_core/streams/juce_OutputStream.cpp +++ b/source/modules/juce_core/streams/juce_OutputStream.cpp @@ -174,12 +174,16 @@ bool OutputStream::writeDoubleBigEndian (double value) bool OutputStream::writeString (const String& text) { + #if (JUCE_STRING_UTF_TYPE == 8) + return write (text.toRawUTF8(), text.getNumBytesAsUTF8() + 1); + #else // (This avoids using toUTF8() to prevent the memory bloat that it would leave behind // if lots of large, persistent strings were to be written to streams). const size_t numBytes = text.getNumBytesAsUTF8() + 1; HeapBlock temp (numBytes); text.copyToUTF8 (temp, numBytes); return write (temp, numBytes); + #endif } bool OutputStream::writeText (const String& text, const bool asUTF16, diff --git a/source/modules/juce_core/text/juce_String.h b/source/modules/juce_core/text/juce_String.h index fb6d45ddf..7b1c17bc8 100644 --- a/source/modules/juce_core/text/juce_String.h +++ b/source/modules/juce_core/text/juce_String.h @@ -84,7 +84,7 @@ public: because there's no other way to represent unicode strings in a way that isn't dependent on the compiler, source code editor and platform. - This will use up the the first maxChars characters of the string (or less if the string + This will use up to the first maxChars characters of the string (or less if the string is actually shorter). */ String (const char* text, size_t maxChars); diff --git a/source/modules/juce_core/threads/juce_ChildProcess.cpp b/source/modules/juce_core/threads/juce_ChildProcess.cpp index 7edab2a5f..4566b13f1 100644 --- a/source/modules/juce_core/threads/juce_ChildProcess.cpp +++ b/source/modules/juce_core/threads/juce_ChildProcess.cpp @@ -29,6 +29,26 @@ ChildProcess::ChildProcess() {} ChildProcess::~ChildProcess() {} +bool ChildProcess::isRunning() const +{ + return activeProcess != nullptr && activeProcess->isRunning(); +} + +int ChildProcess::readProcessOutput (void* dest, int numBytes) +{ + return activeProcess != nullptr ? activeProcess->read (dest, numBytes) : 0; +} + +bool ChildProcess::kill() +{ + return activeProcess == nullptr || activeProcess->killProcess(); +} + +uint32 ChildProcess::getExitCode() const +{ + return activeProcess != nullptr ? activeProcess->getExitCode() : 0; +} + bool ChildProcess::waitForProcessToFinish (const int timeoutMs) const { const uint32 timeoutTime = Time::getMillisecondCounter() + (uint32) timeoutMs; diff --git a/source/modules/juce_core/threads/juce_ChildProcess.h b/source/modules/juce_core/threads/juce_ChildProcess.h index c5a245ff2..0adcb57b7 100644 --- a/source/modules/juce_core/threads/juce_ChildProcess.h +++ b/source/modules/juce_core/threads/juce_ChildProcess.h @@ -97,6 +97,9 @@ public: /** Blocks until the process is no longer running. */ bool waitForProcessToFinish (int timeoutMs) const; + /** If the process has finished, this returns its exit code. */ + uint32 getExitCode() const; + /** Attempts to kill the child process. Returns true if it succeeded. Trying to read from the process after calling this may result in undefined behaviour. diff --git a/source/modules/juce_core/threads/juce_ThreadPool.cpp b/source/modules/juce_core/threads/juce_ThreadPool.cpp index 257572a54..e6b0b9f9a 100644 --- a/source/modules/juce_core/threads/juce_ThreadPool.cpp +++ b/source/modules/juce_core/threads/juce_ThreadPool.cpp @@ -26,12 +26,31 @@ ============================================================================== */ +class ThreadPool::ThreadPoolThread : public Thread +{ +public: + ThreadPoolThread (ThreadPool& p) + : Thread ("Pool"), currentJob (nullptr), pool (p) + { + } + + void run() override + { + while (! threadShouldExit()) + if (! pool.runNextJob (*this)) + wait (500); + } + + ThreadPoolJob* volatile currentJob; + ThreadPool& pool; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) +}; + +//============================================================================== ThreadPoolJob::ThreadPoolJob (const String& name) - : jobName (name), - pool (nullptr), - shouldStop (false), - isActive (false), - shouldBeDeleted (false) + : jobName (name), pool (nullptr), + shouldStop (false), isActive (false), shouldBeDeleted (false) { } @@ -57,30 +76,13 @@ void ThreadPoolJob::signalJobShouldExit() shouldStop = true; } -//============================================================================== -class ThreadPool::ThreadPoolThread : public Thread +ThreadPoolJob* ThreadPoolJob::getCurrentThreadPoolJob() { -public: - ThreadPoolThread (ThreadPool& pool_) - : Thread ("Pool"), - pool (pool_) - { - } - - void run() override - { - while (! threadShouldExit()) - { - if (! pool.runNextJob()) - wait (500); - } - } + if (ThreadPool::ThreadPoolThread* t = dynamic_cast (Thread::getCurrentThread())) + return t->currentJob; -private: - ThreadPool& pool; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) -}; + return nullptr; +} //============================================================================== ThreadPool::ThreadPool (const int numThreads) @@ -164,8 +166,7 @@ bool ThreadPool::isJobRunning (const ThreadPoolJob* const job) const return jobs.contains (const_cast (job)) && job->isActive; } -bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, - const int timeOutMs) const +bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, const int timeOutMs) const { if (job != nullptr) { @@ -215,7 +216,7 @@ bool ThreadPool::removeJob (ThreadPoolJob* const job, } bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, - ThreadPool::JobSelector* selectedJobsToRemove) + ThreadPool::JobSelector* const selectedJobsToRemove) { Array jobsToWaitFor; @@ -328,46 +329,49 @@ ThreadPoolJob* ThreadPool::pickNextJobToRun() return nullptr; } -bool ThreadPool::runNextJob() +bool ThreadPool::runNextJob (ThreadPoolThread& thread) { - ThreadPoolJob* const job = pickNextJobToRun(); - - if (job == nullptr) - return false; - - ThreadPoolJob::JobStatus result = ThreadPoolJob::jobHasFinished; - - JUCE_TRY + if (ThreadPoolJob* const job = pickNextJobToRun()) { - result = job->runJob(); - } - JUCE_CATCH_ALL_ASSERT + ThreadPoolJob::JobStatus result = ThreadPoolJob::jobHasFinished; + thread.currentJob = job; - OwnedArray deletionList; + JUCE_TRY + { + result = job->runJob(); + } + JUCE_CATCH_ALL_ASSERT - { - const ScopedLock sl (lock); + thread.currentJob = nullptr; + + OwnedArray 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& deletionList, ThreadPoolJob* const job) const diff --git a/source/modules/juce_core/threads/juce_ThreadPool.h b/source/modules/juce_core/threads/juce_ThreadPool.h index ed820e806..3d5f7622b 100644 --- a/source/modules/juce_core/threads/juce_ThreadPool.h +++ b/source/modules/juce_core/threads/juce_ThreadPool.h @@ -119,6 +119,12 @@ public: */ void signalJobShouldExit(); + //============================================================================== + /** If the calling thread is being invoked inside a runJob() method, this will + return the ThreadPoolJob that it belongs to. + */ + static ThreadPoolJob* getCurrentThreadPoolJob(); + //============================================================================== private: friend class ThreadPool; @@ -290,6 +296,7 @@ private: Array jobs; class ThreadPoolThread; + friend class ThreadPoolJob; friend class ThreadPoolThread; friend struct ContainerDeletePolicy; OwnedArray threads; @@ -297,7 +304,7 @@ private: CriticalSection lock; WaitableEvent jobFinishedSignal; - bool runNextJob(); + bool runNextJob (ThreadPoolThread&); ThreadPoolJob* pickNextJobToRun(); void addToDeleteList (OwnedArray&, ThreadPoolJob*) const; void createThreads (int numThreads); diff --git a/source/modules/juce_core/xml/juce_XmlDocument.cpp b/source/modules/juce_core/xml/juce_XmlDocument.cpp index 6243d3e0b..3ccebd0e8 100644 --- a/source/modules/juce_core/xml/juce_XmlDocument.cpp +++ b/source/modules/juce_core/xml/juce_XmlDocument.cpp @@ -371,8 +371,8 @@ void XmlDocument::readQuotedString (String& result) } else if (character == 0) { - outOfData = true; setLastError ("unmatched quotes", false); + outOfData = true; break; } @@ -432,7 +432,7 @@ XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) ++input; if (alsoParseSubElements) - readChildElements (node); + readChildElements (*node); break; } @@ -487,9 +487,9 @@ XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) return node; } -void XmlDocument::readChildElements (XmlElement* parent) +void XmlDocument::readChildElements (XmlElement& parent) { - LinkedListPointer::Appender childAppender (parent->firstChildElement); + LinkedListPointer::Appender childAppender (parent.firstChildElement); for (;;) { @@ -563,7 +563,25 @@ void XmlDocument::readChildElements (XmlElement* parent) const juce_wchar c = *input; if (c == '<') + { + if (input[1] == '!' && input[2] == '-' && input[3] == '-') + { + input += 4; + const int closeComment = input.indexOf (CharPointer_ASCII ("-->")); + + if (closeComment < 0) + { + setLastError ("unterminated comment", false); + outOfData = true; + return; + } + + input += closeComment + 3; + continue; + } + break; + } if (c == 0) { diff --git a/source/modules/juce_core/xml/juce_XmlDocument.h b/source/modules/juce_core/xml/juce_XmlDocument.h index 582c23f39..d2a5b49e0 100644 --- a/source/modules/juce_core/xml/juce_XmlDocument.h +++ b/source/modules/juce_core/xml/juce_XmlDocument.h @@ -156,23 +156,23 @@ private: String lastError, dtdText; StringArray tokenisedDTD; bool needToLoadDTD, ignoreEmptyTextElements; - ScopedPointer inputSource; + ScopedPointer inputSource; XmlElement* parseDocumentElement (String::CharPointerType, bool outer); - void setLastError (const String& desc, bool carryOn); + void setLastError (const String&, bool carryOn); bool parseHeader(); bool parseDTD(); void skipNextWhiteSpace(); juce_wchar readNextChar() noexcept; XmlElement* readNextElement (bool alsoParseSubElements); - void readChildElements (XmlElement* parent); - void readQuotedString (String& result); - void readEntity (String& result); - - String getFileContents (const String& filename) const; - String expandEntity (const String& entity); - String expandExternalEntity (const String& entity); - String getParameterEntity (const String& entity); + void readChildElements (XmlElement&); + void readQuotedString (String&); + void readEntity (String&); + + String getFileContents (const String&) const; + String expandEntity (const String&); + String expandExternalEntity (const String&); + String getParameterEntity (const String&); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlDocument) }; diff --git a/source/modules/juce_core/xml/juce_XmlElement.cpp b/source/modules/juce_core/xml/juce_XmlElement.cpp index 0a0a72593..886c98d12 100644 --- a/source/modules/juce_core/xml/juce_XmlElement.cpp +++ b/source/modules/juce_core/xml/juce_XmlElement.cpp @@ -55,7 +55,7 @@ XmlElement::XmlElement (const String& tag) noexcept jassert (tag.containsNonWhitespaceChars()) // The tag can't contain spaces or other characters that would create invalid XML! - jassert (! tag.containsAnyOf (" <>/&")); + jassert (! tag.containsAnyOf (" <>/&(){}")); } XmlElement::XmlElement (int /*dummy*/) noexcept diff --git a/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp b/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp index 3c1c0d42c..f20b1447c 100644 --- a/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp +++ b/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp @@ -215,18 +215,18 @@ bool PropertiesFile::loadAsXml() bool PropertiesFile::saveAsXml() { XmlElement doc (PropertyFileConstants::fileTag); + const StringPairArray& props = getAllProperties(); - for (int i = 0; i < getAllProperties().size(); ++i) + for (int i = 0; i < props.size(); ++i) { XmlElement* const e = doc.createNewChildElement (PropertyFileConstants::valueTag); - e->setAttribute (PropertyFileConstants::nameAttribute, getAllProperties().getAllKeys() [i]); + e->setAttribute (PropertyFileConstants::nameAttribute, props.getAllKeys() [i]); // if the value seems to contain xml, store it as such.. - if (XmlElement* const childElement = XmlDocument::parse (getAllProperties().getAllValues() [i])) + if (XmlElement* const childElement = XmlDocument::parse (props.getAllValues() [i])) e->addChildElement (childElement); else - e->setAttribute (PropertyFileConstants::valueAttribute, - getAllProperties().getAllValues() [i]); + e->setAttribute (PropertyFileConstants::valueAttribute, props.getAllValues() [i]); } ProcessScopedLock pl (createProcessLock()); @@ -311,14 +311,17 @@ bool PropertiesFile::saveAsBinary() out->writeInt (PropertyFileConstants::magicNumber); } - const int numProperties = getAllProperties().size(); + const StringPairArray& props = getAllProperties(); + const int numProperties = props.size(); + const StringArray& keys = props.getAllKeys(); + const StringArray& values = props.getAllValues(); out->writeInt (numProperties); for (int i = 0; i < numProperties; ++i) { - out->writeString (getAllProperties().getAllKeys() [i]); - out->writeString (getAllProperties().getAllValues() [i]); + out->writeString (keys[i]); + out->writeString (values[i]); } out = nullptr; diff --git a/source/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp b/source/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp new file mode 100644 index 000000000..79356f7c2 --- /dev/null +++ b/source/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp @@ -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 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; +} diff --git a/source/modules/juce_events/interprocess/juce_ConnectedChildProcess.h b/source/modules/juce_events/interprocess/juce_ConnectedChildProcess.h new file mode 100644 index 000000000..0b3ec6b04 --- /dev/null +++ b/source/modules/juce_events/interprocess/juce_ConnectedChildProcess.h @@ -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; + ScopedPointer 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; + ScopedPointer connection; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessMaster) +}; + + +#endif // JUCE_CONNECTEDCHILDPROCESS_H_INCLUDED diff --git a/source/modules/juce_events/interprocess/juce_InterprocessConnection.cpp b/source/modules/juce_events/interprocess/juce_InterprocessConnection.cpp index e22ff5b1d..b1b72f79b 100644 --- a/source/modules/juce_events/interprocess/juce_InterprocessConnection.cpp +++ b/source/modules/juce_events/interprocess/juce_InterprocessConnection.cpp @@ -22,14 +22,27 @@ ============================================================================== */ +struct InterprocessConnection::ConnectionThread : public Thread +{ + ConnectionThread (InterprocessConnection& c) : Thread ("JUCE IPC"), owner (c) {} + + void run() override { owner.runThread(); } + +private: + InterprocessConnection& owner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionThread); +}; + +//============================================================================== InterprocessConnection::InterprocessConnection (const bool callbacksOnMessageThread, const uint32 magicMessageHeaderNumber) - : Thread ("Juce IPC connection"), - callbackConnectionState (false), + : callbackConnectionState (false), useMessageThread (callbacksOnMessageThread), magicMessageHeader (magicMessageHeaderNumber), pipeReceiveMessageTimeout (-1) { + thread = new ConnectionThread (*this); } InterprocessConnection::~InterprocessConnection() @@ -37,9 +50,9 @@ InterprocessConnection::~InterprocessConnection() callbackConnectionState = false; disconnect(); masterReference.clear(); + thread = nullptr; } - //============================================================================== bool InterprocessConnection::connectToSocket (const String& hostName, const int portNumber, @@ -53,7 +66,7 @@ bool InterprocessConnection::connectToSocket (const String& hostName, if (socket->connect (hostName, portNumber, timeOutMillisecs)) { connectionMadeInt(); - startThread(); + thread->startThread(); return true; } else @@ -99,7 +112,7 @@ bool InterprocessConnection::createPipe (const String& pipeName, const int timeo void InterprocessConnection::disconnect() { - signalThreadShouldExit(); + thread->signalThreadShouldExit(); { const ScopedLock sl (pipeAndSocketLock); @@ -107,7 +120,7 @@ void InterprocessConnection::disconnect() if (pipe != nullptr) pipe->close(); } - stopThread (4000); + thread->stopThread (4000); deletePipeAndSocket(); connectionLostInt(); } @@ -125,7 +138,7 @@ bool InterprocessConnection::isConnected() const return ((socket != nullptr && socket->isConnected()) || (pipe != nullptr && pipe->isOpen())) - && isThreadRunning(); + && thread->isThreadRunning(); } String InterprocessConnection::getConnectedHostName() const @@ -173,7 +186,7 @@ void InterprocessConnection::initialiseWithSocket (StreamingSocket* newSocket) jassert (socket == nullptr && pipe == nullptr); socket = newSocket; connectionMadeInt(); - startThread(); + thread->startThread(); } void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) @@ -181,7 +194,7 @@ void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) jassert (socket == nullptr && pipe == nullptr); pipe = newPipe; connectionMadeInt(); - startThread(); + thread->startThread(); } //============================================================================== @@ -279,7 +292,7 @@ bool InterprocessConnection::readNextMessageInt() while (bytesInMessage > 0) { - if (threadShouldExit()) + if (thread->threadShouldExit()) return false; const int numThisTime = jmin (bytesInMessage, 65536); @@ -311,9 +324,9 @@ bool InterprocessConnection::readNextMessageInt() return true; } -void InterprocessConnection::run() +void InterprocessConnection::runThread() { - while (! threadShouldExit()) + while (! thread->threadShouldExit()) { if (socket != nullptr) { @@ -328,7 +341,7 @@ void InterprocessConnection::run() if (ready == 0) { - wait (1); + thread->wait (1); continue; } } @@ -346,7 +359,7 @@ void InterprocessConnection::run() break; } - if (threadShouldExit() || ! readNextMessageInt()) + if (thread->threadShouldExit() || ! readNextMessageInt()) break; } } diff --git a/source/modules/juce_events/interprocess/juce_InterprocessConnection.h b/source/modules/juce_events/interprocess/juce_InterprocessConnection.h index 25aa50268..8f21a29b3 100644 --- a/source/modules/juce_events/interprocess/juce_InterprocessConnection.h +++ b/source/modules/juce_events/interprocess/juce_InterprocessConnection.h @@ -47,7 +47,7 @@ class MemoryBlock; @see InterprocessConnectionServer, Socket, NamedPipe */ -class JUCE_API InterprocessConnection : private Thread +class JUCE_API InterprocessConnection { public: //============================================================================== @@ -71,7 +71,7 @@ public: uint32 magicMessageHeaderNumber = 0xf2b49e2c); /** Destructor. */ - ~InterprocessConnection(); + virtual ~InterprocessConnection(); //============================================================================== /** Tries to connect this object to a socket. @@ -195,7 +195,12 @@ private: void connectionLostInt(); void deliverDataInt (const MemoryBlock&); bool readNextMessageInt(); - void run() override; + + struct ConnectionThread; + friend struct ConnectionThread; + friend struct ContainerDeletePolicy; + ScopedPointer thread; + void runThread(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnection) }; diff --git a/source/modules/juce_events/juce_events.cpp b/source/modules/juce_events/juce_events.cpp index aa11627a5..2110d0a5d 100644 --- a/source/modules/juce_events/juce_events.cpp +++ b/source/modules/juce_events/juce_events.cpp @@ -73,6 +73,7 @@ namespace juce #include "timers/juce_Timer.cpp" #include "interprocess/juce_InterprocessConnection.cpp" #include "interprocess/juce_InterprocessConnectionServer.cpp" +#include "interprocess/juce_ConnectedChildProcess.cpp" //============================================================================== #if JUCE_MAC diff --git a/source/modules/juce_events/juce_events.h b/source/modules/juce_events/juce_events.h index 799adc026..02f7c6393 100644 --- a/source/modules/juce_events/juce_events.h +++ b/source/modules/juce_events/juce_events.h @@ -49,6 +49,7 @@ namespace juce #include "timers/juce_MultiTimer.h" #include "interprocess/juce_InterprocessConnection.h" #include "interprocess/juce_InterprocessConnectionServer.h" +#include "interprocess/juce_ConnectedChildProcess.h" #include "native/juce_ScopedXLock.h" } diff --git a/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp b/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp index e43cf79bb..3442b1d40 100644 --- a/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp +++ b/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp @@ -281,28 +281,30 @@ void Graphics::drawMultiLineText (const String& text, const int startX, } } -void Graphics::drawText (const String& text, const Rectangle& area, - Justification justificationType, - const bool useEllipsesIfTooBig) const +void Graphics::drawText (const String& text, const Rectangle& area, + Justification justificationType, bool useEllipsesIfTooBig) const { - if (text.isNotEmpty() && context.clipRegionIntersects (area)) + if (text.isNotEmpty() && context.clipRegionIntersects (area.getSmallestIntegerContainer())) { GlyphArrangement arr; - arr.addCurtailedLineOfText (context.getFont(), text, - 0.0f, 0.0f, (float) area.getWidth(), - useEllipsesIfTooBig); + arr.addCurtailedLineOfText (context.getFont(), text, 0.0f, 0.0f, + area.getWidth(), useEllipsesIfTooBig); arr.justifyGlyphs (0, arr.getNumGlyphs(), - (float) area.getX(), (float) area.getY(), - (float) area.getWidth(), (float) area.getHeight(), + area.getX(), area.getY(), area.getWidth(), area.getHeight(), justificationType); arr.draw (*this); } } +void Graphics::drawText (const String& text, const Rectangle& area, + Justification justificationType, bool useEllipsesIfTooBig) const +{ + drawText (text, area.toFloat(), justificationType, useEllipsesIfTooBig); +} + void Graphics::drawText (const String& text, const int x, const int y, const int width, const int height, - Justification justificationType, - const bool useEllipsesIfTooBig) const + Justification justificationType, const bool useEllipsesIfTooBig) const { drawText (text, Rectangle (x, y, width, height), justificationType, useEllipsesIfTooBig); } diff --git a/source/modules/juce_graphics/contexts/juce_GraphicsContext.h b/source/modules/juce_graphics/contexts/juce_GraphicsContext.h index 221cfcb5c..d59bcd2ba 100644 --- a/source/modules/juce_graphics/contexts/juce_GraphicsContext.h +++ b/source/modules/juce_graphics/contexts/juce_GraphicsContext.h @@ -174,6 +174,20 @@ public: Justification justificationType, bool useEllipsesIfTooBig) const; + /** Draws a line of text within a specified rectangle. + + The text will be positioned within the rectangle based on the justification + flags passed-in. If the string is too long to fit inside the rectangle, it will + either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig + flag is true). + + @see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText + */ + void drawText (const String& text, + const Rectangle& area, + Justification justificationType, + bool useEllipsesIfTooBig) const; + /** Tries to draw a text string inside a given space. This does its best to make the given text readable within the specified rectangle, diff --git a/source/modules/juce_graphics/native/juce_RenderingHelpers.h b/source/modules/juce_graphics/native/juce_RenderingHelpers.h index d85a4a0a4..10fb9dc64 100644 --- a/source/modules/juce_graphics/native/juce_RenderingHelpers.h +++ b/source/modules/juce_graphics/native/juce_RenderingHelpers.h @@ -806,16 +806,19 @@ namespace EdgeTableFillers alphaLevel = (alphaLevel * extraAlpha) >> 8; x -= xOffset; - jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); - - if (alphaLevel < 0xfe) + if (repeatPattern) { - JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (repeatPattern ? (x++ % srcData.width) : x++), (uint32) alphaLevel)) + if (alphaLevel < 0xfe) + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width), (uint32) alphaLevel)) + else + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) } else { - if (repeatPattern) - JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) + jassert (x >= 0 && x + width <= srcData.width); + + if (alphaLevel < 0xfe) + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++), (uint32) alphaLevel)) else copyRow (dest, getSrcPixel (x), width); } @@ -826,16 +829,19 @@ namespace EdgeTableFillers DestPixelType* dest = getDestPixel (x); x -= xOffset; - jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); - - if (extraAlpha < 0xfe) + if (repeatPattern) { - JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (repeatPattern ? (x++ % srcData.width) : x++), (uint32) extraAlpha)) + if (extraAlpha < 0xfe) + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width), (uint32) extraAlpha)) + else + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) } else { - if (repeatPattern) - JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) + jassert (x >= 0 && x + width <= srcData.width); + + if (extraAlpha < 0xfe) + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++), (uint32) extraAlpha)) else copyRow (dest, getSrcPixel (x), width); } diff --git a/source/modules/juce_gui_basics/drawables/juce_Drawable.cpp b/source/modules/juce_gui_basics/drawables/juce_Drawable.cpp index bd7e19d29..3e8831880 100644 --- a/source/modules/juce_gui_basics/drawables/juce_Drawable.cpp +++ b/source/modules/juce_gui_basics/drawables/juce_Drawable.cpp @@ -107,6 +107,18 @@ void Drawable::setBoundsToEnclose (const Rectangle& area) setBounds (newBounds); } +//============================================================================== +bool Drawable::replaceColour (Colour original, Colour replacement) +{ + bool changed = false; + + for (int i = getNumChildComponents(); --i >= 0;) + if (Drawable* d = dynamic_cast (getChildComponent(i))) + changed = d->replaceColour (original, replacement) || changed; + + return changed; +} + //============================================================================== void Drawable::setOriginWithOriginalSize (Point originWithinParent) { diff --git a/source/modules/juce_gui_basics/drawables/juce_Drawable.h b/source/modules/juce_gui_basics/drawables/juce_Drawable.h index 6a59c59c5..b8956d5a0 100644 --- a/source/modules/juce_gui_basics/drawables/juce_Drawable.h +++ b/source/modules/juce_gui_basics/drawables/juce_Drawable.h @@ -175,6 +175,11 @@ public: */ virtual Rectangle getDrawableBounds() const = 0; + /** Recursively replaces a colour that might be used for filling or stroking. + return true if any instances of this colour were found. + */ + virtual bool replaceColour (Colour originalColour, Colour replacementColour); + //============================================================================== /** Internal class used to manage ValueTrees that represent Drawables. */ class ValueTreeWrapperBase diff --git a/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp b/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp index 308ac1cbb..5813493c8 100644 --- a/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp +++ b/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp @@ -452,3 +452,21 @@ void DrawableShape::FillAndStrokeState::setStrokeType (const PathStrokeType& new state.setProperty (capStyle, newStrokeType.getEndStyle() == PathStrokeType::butt ? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager); } + +static bool replaceColourInFill (DrawableShape::RelativeFillType& fill, Colour original, Colour replacement) +{ + if (fill.fill.colour == original && fill.fill.isColour()) + { + fill = FillType (replacement); + return true; + } + + return false; +} + +bool DrawableShape::replaceColour (Colour original, Colour replacement) +{ + bool changed1 = replaceColourInFill (mainFill, original, replacement); + bool changed2 = replaceColourInFill (strokeFill, original, replacement); + return changed1 || changed2; +} diff --git a/source/modules/juce_gui_basics/drawables/juce_DrawableShape.h b/source/modules/juce_gui_basics/drawables/juce_DrawableShape.h index 14278d2dd..ed893f14c 100644 --- a/source/modules/juce_gui_basics/drawables/juce_DrawableShape.h +++ b/source/modules/juce_gui_basics/drawables/juce_DrawableShape.h @@ -147,6 +147,8 @@ public: void paint (Graphics&) override; /** @internal */ bool hitTest (int x, int y) override; + /** @internal */ + bool replaceColour (Colour originalColour, Colour replacementColour) override; protected: //============================================================================== diff --git a/source/modules/juce_gui_basics/layout/juce_Viewport.h b/source/modules/juce_gui_basics/layout/juce_Viewport.h index 94404a626..f67b6b06b 100644 --- a/source/modules/juce_gui_basics/layout/juce_Viewport.h +++ b/source/modules/juce_gui_basics/layout/juce_Viewport.h @@ -131,10 +131,12 @@ public: */ bool autoScroll (int mouseX, int mouseY, int distanceFromEdge, int maximumSpeed); - /** Returns the position within the child component of the top-left of its visible area. - */ + /** Returns the position within the child component of the top-left of its visible area. */ Point getViewPosition() const noexcept { return lastVisibleArea.getPosition(); } + /** Returns the visible area of the child component, relative to its top-left */ + Rectangle getViewArea() const noexcept { return lastVisibleArea; } + /** Returns the position within the child component of the top-left of its visible area. @see getViewWidth, setViewPosition */ diff --git a/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp index 930bf55cf..f3a26b5af 100644 --- a/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp +++ b/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp @@ -198,7 +198,7 @@ void FileChooser::showPlatformDialog (Array& results, const String& title_ } else { - DWORD flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY; + DWORD flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING; if (warnAboutOverwritingExistingFiles) flags |= OFN_OVERWRITEPROMPT; diff --git a/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp b/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp index 0ace5e109..abbc10205 100644 --- a/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp @@ -1424,47 +1424,40 @@ void TextEditor::scrollToMakeSureCursorIsVisible() if (keepCaretOnScreen) { - int x = viewport->getViewPositionX(); - int y = viewport->getViewPositionY(); + Point viewPos (viewport->getViewPosition()); + const Rectangle caretRect (getCaretRectangle()); - const Rectangle caretPos (getCaretRectangle()); + const Point relativeCursor = caretRect.getPosition() - viewPos; - const int relativeCursorX = caretPos.getX() - x; - const int relativeCursorY = caretPos.getY() - y; - - if (relativeCursorX < jmax (1, proportionOfWidth (0.05f))) + if (relativeCursor.x < jmax (1, proportionOfWidth (0.05f))) { - x += relativeCursorX - proportionOfWidth (0.2f); + viewPos.x += relativeCursor.x - proportionOfWidth (0.2f); } - else if (relativeCursorX > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10))) + else if (relativeCursor.x > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10))) { - x += relativeCursorX + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth(); + viewPos.x += relativeCursor.x + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth(); } - x = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), x); + viewPos.x = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), viewPos.x); if (! isMultiLine()) { - y = (getHeight() - textHolder->getHeight() - topIndent) / -2; + viewPos.y = (getHeight() - textHolder->getHeight() - topIndent) / -2; } - else + else if (relativeCursor.y < 0) { - if (relativeCursorY < 0) - { - y = jmax (0, relativeCursorY + y); - } - else if (relativeCursorY > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - caretPos.getHeight())) - { - y += relativeCursorY + 2 + caretPos.getHeight() + topIndent - viewport->getMaximumVisibleHeight(); - } + viewPos.y = jmax (0, relativeCursor.y + viewPos.y); + } + else if (relativeCursor.y > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - caretRect.getHeight())) + { + viewPos.y += relativeCursor.y + 2 + caretRect.getHeight() + topIndent - viewport->getMaximumVisibleHeight(); } - viewport->setViewPosition (x, y); + viewport->setViewPosition (viewPos); } } -void TextEditor::moveCaretTo (const int newPosition, - const bool isSelecting) +void TextEditor::moveCaretTo (const int newPosition, const bool isSelecting) { if (isSelecting) { diff --git a/source/modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm b/source/modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm index ff9cdca8d..b31eb8616 100644 --- a/source/modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm +++ b/source/modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm @@ -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 + { + ViewFrameChangeCallbackClass() : ObjCClass ("JUCE_NSViewCallback_") + { + addIvar ("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 (self, "target")) + target->viewResized(); + } + + JUCE_DECLARE_NON_COPYABLE (ViewFrameChangeCallbackClass); + }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewResizeWatcher) +}; + +//============================================================================== class NSViewAttachment : public ReferenceCountedObject, - public ComponentMovementWatcher + public ComponentMovementWatcher, + private NSViewResizeWatcher { public: NSViewAttachment (NSView* const v, Component& comp) : ComponentMovementWatcher (&comp), view (v), owner (comp), - currentPeer (nullptr), frameChangeCallback (nullptr) + currentPeer (nullptr) { [view retain]; [view setPostsFrameChangedNotifications: YES]; @@ -37,21 +105,12 @@ public: if (owner.isShowing()) componentPeerChanged(); - static ViewFrameChangeCallbackClass cls; - frameChangeCallback = [cls.createInstance() init]; - ViewFrameChangeCallbackClass::setTarget (frameChangeCallback, &owner); - - [[NSNotificationCenter defaultCenter] addObserver: frameChangeCallback - selector: @selector (frameChanged:) - name: NSViewFrameDidChangeNotification - object: view]; + attachViewWatcher (view); } ~NSViewAttachment() { - [[NSNotificationCenter defaultCenter] removeObserver: frameChangeCallback]; - [frameChangeCallback release]; - + detachViewWatcher(); removeFromParent(); [view release]; } @@ -103,12 +162,16 @@ public: componentPeerChanged(); } + void viewResized() override + { + owner.childBoundsChanged (nullptr); + } + NSView* const view; private: Component& owner; ComponentPeer* currentPeer; - id frameChangeCallback; void removeFromParent() { @@ -117,31 +180,6 @@ private: // override the call and use it as a sign that they're being deleted, which breaks everything.. } - //============================================================================== - struct ViewFrameChangeCallbackClass : public ObjCClass - { - ViewFrameChangeCallbackClass() : ObjCClass ("JUCE_NSViewCallback_") - { - addIvar ("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 (self, "target")) - target->childBoundsChanged (nullptr); - } - - JUCE_DECLARE_NON_COPYABLE (ViewFrameChangeCallbackClass); - }; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewAttachment) };