@@ -70,7 +70,6 @@ namespace juce | |||||
#include "buffers/juce_AudioSampleBuffer.cpp" | #include "buffers/juce_AudioSampleBuffer.cpp" | ||||
#include "buffers/juce_FloatVectorOperations.cpp" | #include "buffers/juce_FloatVectorOperations.cpp" | ||||
#include "effects/juce_IIRFilter.cpp" | #include "effects/juce_IIRFilter.cpp" | ||||
#include "effects/juce_IIRFilterOld.cpp" | |||||
#include "effects/juce_LagrangeInterpolator.cpp" | #include "effects/juce_LagrangeInterpolator.cpp" | ||||
#include "midi/juce_MidiBuffer.cpp" | #include "midi/juce_MidiBuffer.cpp" | ||||
#include "midi/juce_MidiFile.cpp" | #include "midi/juce_MidiFile.cpp" | ||||
@@ -36,7 +36,6 @@ namespace juce | |||||
#include "buffers/juce_FloatVectorOperations.h" | #include "buffers/juce_FloatVectorOperations.h" | ||||
#include "effects/juce_Decibels.h" | #include "effects/juce_Decibels.h" | ||||
#include "effects/juce_IIRFilter.h" | #include "effects/juce_IIRFilter.h" | ||||
#include "effects/juce_IIRFilterOld.h" | |||||
#include "effects/juce_LagrangeInterpolator.h" | #include "effects/juce_LagrangeInterpolator.h" | ||||
#include "effects/juce_Reverb.h" | #include "effects/juce_Reverb.h" | ||||
#include "midi/juce_MidiMessage.h" | #include "midi/juce_MidiMessage.h" | ||||
@@ -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 | ||||
@@ -160,8 +160,8 @@ void AudioDeviceManager::createAudioDeviceTypes (OwnedArray <AudioIODeviceType>& | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO()); | addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO()); | ||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); | addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); | ||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); | addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); | ||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_JACK()); | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); | addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); | ||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_JACK()); | |||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_OpenSLES()); | addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_OpenSLES()); | ||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android()); | addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android()); | ||||
} | } | ||||
@@ -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 }; | ||||
@@ -535,7 +535,7 @@ public: | |||||
#if BUILD_AU_CARBON_UI | #if BUILD_AU_CARBON_UI | ||||
int GetNumCustomUIComponents() override | int GetNumCustomUIComponents() override | ||||
{ | { | ||||
return PluginHostType().isDigitalPerformer() ? 0 : 1; | |||||
return getHostType().isDigitalPerformer() ? 0 : 1; | |||||
} | } | ||||
void GetUIComponentDescs (ComponentDescription* inDescArray) override | void GetUIComponentDescs (ComponentDescription* inDescArray) override | ||||
@@ -1095,7 +1095,7 @@ public: | |||||
bool keyPressed (const KeyPress&) override | bool keyPressed (const KeyPress&) override | ||||
{ | { | ||||
if (PluginHostType().isAbletonLive()) | |||||
if (getHostType().isAbletonLive()) | |||||
{ | { | ||||
static NSTimeInterval lastEventTime = 0; // check we're not recursively sending the same event | static NSTimeInterval lastEventTime = 0; // check we're not recursively sending the same event | ||||
NSTimeInterval eventTime = [[NSApp currentEvent] timestamp]; | NSTimeInterval eventTime = [[NSApp currentEvent] timestamp]; | ||||
@@ -96,7 +96,6 @@ | |||||
#include "../utility/juce_IncludeModuleHeaders.h" | #include "../utility/juce_IncludeModuleHeaders.h" | ||||
#include "../utility/juce_FakeMouseMoveGenerator.h" | #include "../utility/juce_FakeMouseMoveGenerator.h" | ||||
#include "modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h" | |||||
#ifdef _MSC_VER | #ifdef _MSC_VER | ||||
#pragma pack (pop) | #pragma pack (pop) | ||||
@@ -374,7 +373,7 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
bool getEffectName (char* name) override | bool getEffectName (char* name) override | ||||
{ | { | ||||
String (filter->getName()).copyToUTF8 (name, 64); | |||||
String (JucePlugin_Name).copyToUTF8 (name, 64); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -1282,12 +1281,6 @@ public: | |||||
} | } | ||||
} | } | ||||
static PluginHostType& getHostType() | |||||
{ | |||||
static PluginHostType hostType; | |||||
return hostType; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
// A component to hold the AudioProcessorEditor, and cope with some housekeeping | // A component to hold the AudioProcessorEditor, and cope with some housekeeping | ||||
// chores when it changes or repaints. | // chores when it changes or repaints. | ||||
@@ -28,7 +28,7 @@ | |||||
#include "../utility/juce_CheckSettingMacros.h" | #include "../utility/juce_CheckSettingMacros.h" | ||||
#if JucePlugin_Build_VST | |||||
#if JucePlugin_Build_VST || JucePlugin_Build_VST3 | |||||
#define JUCE_MAC_WINDOW_VISIBITY_BODGE 1 | #define JUCE_MAC_WINDOW_VISIBITY_BODGE 1 | ||||
@@ -204,7 +204,7 @@ void detachComponentFromWindowRef (Component* comp, void* window, bool isNSView) | |||||
[hostWindow release]; | [hostWindow release]; | ||||
static bool needToRunMessageLoop = ! PluginHostType().isReaper(); | |||||
static bool needToRunMessageLoop = ! getHostType().isReaper(); | |||||
// The event loop needs to be run between closing the window and deleting the plugin, | // The event loop needs to be run between closing the window and deleting the plugin, | ||||
// presumably to let the cocoa objects get tidied up. Leaving out this line causes crashes | // presumably to let the cocoa objects get tidied up. Leaving out this line causes crashes | ||||
@@ -0,0 +1,269 @@ | |||||
/* | |||||
============================================================================== | |||||
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. | |||||
============================================================================== | |||||
*/ | |||||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||||
// and your header search path must make it accessible to the module's files. | |||||
#include "AppConfig.h" | |||||
#include "../utility/juce_CheckSettingMacros.h" | |||||
#if JucePlugin_Build_VST3 | |||||
#define JUCE_MAC_WINDOW_VISIBITY_BODGE 1 | |||||
#include "../utility/juce_IncludeSystemHeaders.h" | |||||
#include "../utility/juce_IncludeModuleHeaders.h" | |||||
#include "../utility/juce_FakeMouseMoveGenerator.h" | |||||
#include "../utility/juce_CarbonVisibility.h" | |||||
#undef Component | |||||
#undef Point | |||||
//============================================================================== | |||||
namespace juce | |||||
{ | |||||
static void initialiseMac() | |||||
{ | |||||
#if ! JUCE_64BIT | |||||
NSApplicationLoad(); | |||||
#endif | |||||
} | |||||
#if ! JUCE_64BIT | |||||
static void updateComponentPos (Component* const comp) | |||||
{ | |||||
DBG ("updateComponentPos()"); | |||||
HIViewRef dummyView = (HIViewRef) (void*) (pointer_sized_int) | |||||
comp->getProperties() ["dummyViewRef"].toString().getHexValue64(); | |||||
HIRect r; | |||||
HIViewGetFrame (dummyView, &r); | |||||
HIViewRef root; | |||||
HIViewFindByID (HIViewGetRoot (HIViewGetWindow (dummyView)), kHIViewWindowContentID, &root); | |||||
HIViewConvertRect (&r, HIViewGetSuperview (dummyView), root); | |||||
Rect windowPos; | |||||
GetWindowBounds (HIViewGetWindow (dummyView), kWindowContentRgn, &windowPos); | |||||
comp->setTopLeftPosition ((int) (windowPos.left + r.origin.x), | |||||
(int) (windowPos.top + r.origin.y)); | |||||
} | |||||
static pascal OSStatus viewBoundsChangedEvent (EventHandlerCallRef, EventRef, void* user) | |||||
{ | |||||
updateComponentPos ((Component*) user); | |||||
return noErr; | |||||
} | |||||
#endif | |||||
static void* attachComponentToWindowRef (Component* comp, void* windowRef, bool isHIView) | |||||
{ | |||||
DBG ("attachComponentToWindowRef()"); | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
#if JUCE_64BIT | |||||
NSView* parentView = (NSView*) windowRef; | |||||
#if JucePlugin_EditorRequiresKeyboardFocus | |||||
comp->addToDesktop (0, parentView); | |||||
#else | |||||
comp->addToDesktop (ComponentPeer::windowIgnoresKeyPresses, parentView); | |||||
#endif | |||||
// (this workaround is because Wavelab provides a zero-size parent view..) | |||||
if ([parentView frame].size.height == 0) | |||||
[((NSView*) comp->getWindowHandle()) setFrameOrigin: NSZeroPoint]; | |||||
comp->setVisible (true); | |||||
comp->toFront (false); | |||||
[[parentView window] setAcceptsMouseMovedEvents: YES]; | |||||
return parentView; | |||||
#else | |||||
//treat NSView like 64bit | |||||
if (! isHIView) | |||||
{ | |||||
NSView* parentView = (NSView*) windowRef; | |||||
#if JucePlugin_EditorRequiresKeyboardFocus | |||||
comp->addToDesktop (0, parentView); | |||||
#else | |||||
comp->addToDesktop (ComponentPeer::windowIgnoresKeyPresses, parentView); | |||||
#endif | |||||
// (this workaround is because Wavelab provides a zero-size parent view..) | |||||
if ([parentView frame].size.height == 0) | |||||
[((NSView*) comp->getWindowHandle()) setFrameOrigin: NSZeroPoint]; | |||||
comp->setVisible (true); | |||||
comp->toFront (false); | |||||
[[parentView window] setAcceptsMouseMovedEvents: YES]; | |||||
return parentView; | |||||
} | |||||
NSWindow* hostWindow = [[NSWindow alloc] initWithWindowRef: windowRef]; | |||||
[hostWindow retain]; | |||||
[hostWindow setCanHide: YES]; | |||||
[hostWindow setReleasedWhenClosed: YES]; | |||||
HIViewRef parentView = nullptr; | |||||
WindowAttributes attributes; | |||||
GetWindowAttributes ((WindowRef) windowRef, &attributes); | |||||
if ((attributes & kWindowCompositingAttribute) != 0) | |||||
{ | |||||
HIViewRef root = HIViewGetRoot ((WindowRef) windowRef); | |||||
HIViewFindByID (root, kHIViewWindowContentID, &parentView); | |||||
if (parentView == nullptr) | |||||
parentView = root; | |||||
} | |||||
else | |||||
{ | |||||
GetRootControl ((WindowRef) windowRef, (ControlRef*) &parentView); | |||||
if (parentView == nullptr) | |||||
CreateRootControl ((WindowRef) windowRef, (ControlRef*) &parentView); | |||||
} | |||||
// It seems that the only way to successfully position our overlaid window is by putting a dummy | |||||
// HIView into the host's carbon window, and then catching events to see when it gets repositioned | |||||
HIViewRef dummyView = 0; | |||||
HIImageViewCreate (0, &dummyView); | |||||
HIRect r = { {0, 0}, { (float) comp->getWidth(), (float) comp->getHeight()} }; | |||||
HIViewSetFrame (dummyView, &r); | |||||
HIViewAddSubview (parentView, dummyView); | |||||
comp->getProperties().set ("dummyViewRef", String::toHexString ((pointer_sized_int) (void*) dummyView)); | |||||
EventHandlerRef ref; | |||||
const EventTypeSpec kControlBoundsChangedEvent = { kEventClassControl, kEventControlBoundsChanged }; | |||||
InstallEventHandler (GetControlEventTarget (dummyView), NewEventHandlerUPP (viewBoundsChangedEvent), 1, &kControlBoundsChangedEvent, (void*) comp, &ref); | |||||
comp->getProperties().set ("boundsEventRef", String::toHexString ((pointer_sized_int) (void*) ref)); | |||||
updateComponentPos (comp); | |||||
#if ! JucePlugin_EditorRequiresKeyboardFocus | |||||
comp->addToDesktop (ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresKeyPresses); | |||||
#else | |||||
comp->addToDesktop (ComponentPeer::windowIsTemporary); | |||||
#endif | |||||
comp->setVisible (true); | |||||
comp->toFront (false); | |||||
NSView* pluginView = (NSView*) comp->getWindowHandle(); | |||||
NSWindow* pluginWindow = [pluginView window]; | |||||
[pluginWindow setExcludedFromWindowsMenu: YES]; | |||||
[pluginWindow setCanHide: YES]; | |||||
[hostWindow addChildWindow: pluginWindow | |||||
ordered: NSWindowAbove]; | |||||
[hostWindow orderFront: nil]; | |||||
[pluginWindow orderFront: nil]; | |||||
attachWindowHidingHooks (comp, (WindowRef) windowRef, hostWindow); | |||||
return hostWindow; | |||||
#endif | |||||
} | |||||
} | |||||
static void detachComponentFromWindowRef (Component* comp, void* nsWindow, bool isHIView) | |||||
{ | |||||
#if JUCE_64BIT | |||||
comp->removeFromDesktop(); | |||||
#else | |||||
//treat NSView like 64bit | |||||
if (! isHIView) | |||||
{ | |||||
comp->removeFromDesktop(); | |||||
} | |||||
else | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
EventHandlerRef ref = (EventHandlerRef) (void*) (pointer_sized_int) | |||||
comp->getProperties() ["boundsEventRef"].toString().getHexValue64(); | |||||
RemoveEventHandler (ref); | |||||
removeWindowHidingHooks (comp); | |||||
HIViewRef dummyView = (HIViewRef) (void*) (pointer_sized_int) | |||||
comp->getProperties() ["dummyViewRef"].toString().getHexValue64(); | |||||
if (HIViewIsValid (dummyView)) | |||||
CFRelease (dummyView); | |||||
NSWindow* hostWindow = (NSWindow*) nsWindow; | |||||
NSView* pluginView = (NSView*) comp->getWindowHandle(); | |||||
NSWindow* pluginWindow = [pluginView window]; | |||||
[hostWindow removeChildWindow: pluginWindow]; | |||||
comp->removeFromDesktop(); | |||||
[hostWindow release]; | |||||
} | |||||
// The event loop needs to be run between closing the window and deleting the plugin, | |||||
// presumably to let the cocoa objects get tidied up. Leaving out this line causes crashes | |||||
// in Live and Reaper when you delete the plugin with its window open. | |||||
// (Doing it this way rather than using a single longer timout means that we can guarantee | |||||
// how many messages will be dispatched, which seems to be vital in Reaper) | |||||
for (int i = 20; --i >= 0;) | |||||
MessageManager::getInstance()->runDispatchLoopUntil (1); | |||||
} | |||||
#endif | |||||
} | |||||
static void setNativeHostWindowSize (void* nsWindow, Component* component, int newWidth, int newHeight, bool isHIView) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
#if JUCE_64BIT | |||||
component->setSize (newWidth, newHeight); | |||||
#else | |||||
if (! isHIView) | |||||
{ //Treat NSView like 64bit: | |||||
component->setSize (newWidth, newHeight); | |||||
} | |||||
else if (HIViewRef dummyView = (HIViewRef) (void*) (pointer_sized_int) | |||||
component->getProperties() ["dummyViewRef"].toString().getHexValue64()) | |||||
{ | |||||
HIRect frameRect; | |||||
HIViewGetFrame (dummyView, &frameRect); | |||||
frameRect.size.width = newWidth; | |||||
frameRect.size.height = newHeight; | |||||
HIViewSetFrame (dummyView, &frameRect); | |||||
} | |||||
#endif | |||||
} | |||||
} | |||||
} // (juce namespace) | |||||
#endif |
@@ -2,7 +2,7 @@ | |||||
"id": "juce_audio_plugin_client", | "id": "juce_audio_plugin_client", | ||||
"name": "JUCE audio plugin wrapper classes", | "name": "JUCE audio plugin wrapper classes", | ||||
"version": "3.0.2", | "version": "3.0.2", | ||||
"description": "Classes for building VST, RTAS and AU plugins.", | |||||
"description": "Classes for building VST, VST3, RTAS, AAX and AU plugins.", | |||||
"website": "http://www.juce.com/juce", | "website": "http://www.juce.com/juce", | ||||
"license": "GPL/Commercial", | "license": "GPL/Commercial", | ||||
@@ -14,6 +14,8 @@ | |||||
"compile": [ { "file": "VST/juce_VST_Wrapper.cpp" }, | "compile": [ { "file": "VST/juce_VST_Wrapper.cpp" }, | ||||
{ "file": "VST/juce_VST_Wrapper.mm", "target": "xcode" }, | { "file": "VST/juce_VST_Wrapper.mm", "target": "xcode" }, | ||||
{ "file": "VST3/juce_VST3_Wrapper.cpp" }, | |||||
{ "file": "VST3/juce_VST3_Wrapper.mm", "target": "xcode" }, | |||||
{ "file": "RTAS/juce_RTAS_DigiCode1.cpp", "warnings": "disabled", "stdcall": "1", "target": "xcode, msvc" }, | { "file": "RTAS/juce_RTAS_DigiCode1.cpp", "warnings": "disabled", "stdcall": "1", "target": "xcode, msvc" }, | ||||
{ "file": "RTAS/juce_RTAS_DigiCode2.cpp", "warnings": "disabled", "stdcall": "1", "target": "xcode, msvc" }, | { "file": "RTAS/juce_RTAS_DigiCode2.cpp", "warnings": "disabled", "stdcall": "1", "target": "xcode, msvc" }, | ||||
{ "file": "RTAS/juce_RTAS_DigiCode3.cpp", "warnings": "disabled", "stdcall": "1", "target": "xcode, msvc" }, | { "file": "RTAS/juce_RTAS_DigiCode3.cpp", "warnings": "disabled", "stdcall": "1", "target": "xcode, msvc" }, | ||||
@@ -36,6 +38,7 @@ | |||||
"RTAS/*.mm", | "RTAS/*.mm", | ||||
"RTAS/*.h", | "RTAS/*.h", | ||||
"VST/*", | "VST/*", | ||||
"VST3/*", | |||||
"AAX/*", | "AAX/*", | ||||
"utility/*" | "utility/*" | ||||
] | ] | ||||
@@ -25,7 +25,8 @@ | |||||
// The following checks should cause a compile error if you've forgotten to | // The following checks should cause a compile error if you've forgotten to | ||||
// define all your plugin settings properly.. | // define all your plugin settings properly.. | ||||
#if ! (JucePlugin_Build_VST || JucePlugin_Build_AU || JucePlugin_Build_RTAS || JucePlugin_Build_AAX \ | |||||
#if ! (JucePlugin_Build_VST || JucePlugin_Build_VST3 \ | |||||
|| JucePlugin_Build_AU || JucePlugin_Build_RTAS || JucePlugin_Build_AAX \ | |||||
|| JucePlugin_Build_Standalone || JucePlugin_Build_LV2) | || JucePlugin_Build_Standalone || JucePlugin_Build_LV2) | ||||
#error "You need to enable at least one plugin format!" | #error "You need to enable at least one plugin format!" | ||||
#endif | #endif | ||||
@@ -75,11 +76,16 @@ | |||||
#endif | #endif | ||||
//============================================================================== | //============================================================================== | ||||
#if _WIN64 || (__LP64__ && (defined(__APPLE_CPP__) || defined(__APPLE_CC__))) | |||||
#if _WIN64 || (__LP64__ && (defined (__APPLE_CPP__) || defined (__APPLE_CC__))) | |||||
#undef JucePlugin_Build_RTAS | #undef JucePlugin_Build_RTAS | ||||
#define JucePlugin_Build_RTAS 0 | #define JucePlugin_Build_RTAS 0 | ||||
#endif | #endif | ||||
#if ! (defined (_MSC_VER) || defined (__APPLE_CPP__) || defined (__APPLE_CC__)) | |||||
#undef JucePlugin_Build_VST3 | |||||
#define JucePlugin_Build_VST3 0 | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
#if JucePlugin_Build_RTAS && _MSC_VER && ! defined (JucePlugin_WinBag_path) | #if JucePlugin_Build_RTAS && _MSC_VER && ! defined (JucePlugin_WinBag_path) | ||||
#error "You need to define the JucePlugin_WinBag_path value!" | #error "You need to define the JucePlugin_WinBag_path value!" | ||||
@@ -26,14 +26,21 @@ | |||||
using namespace juce; | using namespace juce; | ||||
#if JUCE_MAC && ! DOXYGEN | |||||
#define Point juce::Point | |||||
#define Component juce::Component | |||||
namespace juce | |||||
{ | |||||
void repostCurrentNSEvent(); | |||||
} | |||||
#endif | |||||
namespace juce | |||||
{ | |||||
#if JUCE_MAC && ! DOXYGEN | |||||
#define Point juce::Point | |||||
#define Component juce::Component | |||||
void repostCurrentNSEvent(); | |||||
#endif | |||||
//============================================================================== | |||||
inline const PluginHostType& getHostType() | |||||
{ | |||||
static PluginHostType hostType; | |||||
return hostType; | |||||
} | |||||
} | |||||
extern AudioProcessor* JUCE_CALLTYPE createPluginFilterOfType (AudioProcessor::WrapperType); | extern AudioProcessor* JUCE_CALLTYPE createPluginFilterOfType (AudioProcessor::WrapperType); |
@@ -39,14 +39,19 @@ public: | |||||
AbletonLive7, | AbletonLive7, | ||||
AbletonLive8, | AbletonLive8, | ||||
AbletonLiveGeneric, | AbletonLiveGeneric, | ||||
AdobeAudition, | |||||
AdobePremierePro, | AdobePremierePro, | ||||
AppleLogic, | AppleLogic, | ||||
Ardour, | |||||
CakewalkSonar8, | CakewalkSonar8, | ||||
CakewalkSonarGeneric, | CakewalkSonarGeneric, | ||||
DigidesignProTools, | DigidesignProTools, | ||||
DigitalPerformer, | |||||
FruityLoops, | |||||
MagixSamplitude, | |||||
MergingPyramix, | |||||
MuseReceptorGeneric, | |||||
Reaper, | Reaper, | ||||
Tracktion3, | |||||
TracktionGeneric, | |||||
SteinbergCubase4, | SteinbergCubase4, | ||||
SteinbergCubase5, | SteinbergCubase5, | ||||
SteinbergCubase5Bridged, | SteinbergCubase5Bridged, | ||||
@@ -62,13 +67,11 @@ public: | |||||
SteinbergWavelab7, | SteinbergWavelab7, | ||||
SteinbergWavelab8, | SteinbergWavelab8, | ||||
SteinbergWavelabGeneric, | SteinbergWavelabGeneric, | ||||
MuseReceptorGeneric, | |||||
MagixSamplitude, | |||||
FruityLoops, | |||||
WaveBurner, | |||||
DigitalPerformer, | |||||
SteinbergTestHost, | |||||
StudioOne, | StudioOne, | ||||
MergingPyramix, | |||||
Tracktion3, | |||||
TracktionGeneric, | |||||
WaveBurner, | |||||
VBVSTScanner | VBVSTScanner | ||||
}; | }; | ||||
@@ -76,25 +79,28 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
bool isAbletonLive() const noexcept { return type == AbletonLive6 || type == AbletonLive7 || type == AbletonLive8 || type == AbletonLiveGeneric; } | bool isAbletonLive() const noexcept { return type == AbletonLive6 || type == AbletonLive7 || type == AbletonLive8 || type == AbletonLiveGeneric; } | ||||
bool isNuendo() const noexcept { return type == SteinbergNuendo3 || type == SteinbergNuendo4 || type == SteinbergNuendo5 || type == SteinbergNuendoGeneric; } | |||||
bool isAdobeAudition() const noexcept { return type == AdobeAudition; } | |||||
bool isArdour() const noexcept { return type == Ardour; } | |||||
bool isDigitalPerformer() const noexcept { return type == DigitalPerformer; } | |||||
bool isCubase() const noexcept { return type == SteinbergCubase4 || type == SteinbergCubase5 || type == SteinbergCubase5Bridged || type == SteinbergCubase6 || type == SteinbergCubase7 || type == SteinbergCubaseGeneric; } | bool isCubase() const noexcept { return type == SteinbergCubase4 || type == SteinbergCubase5 || type == SteinbergCubase5Bridged || type == SteinbergCubase6 || type == SteinbergCubase7 || type == SteinbergCubaseGeneric; } | ||||
bool isCubaseBridged() const noexcept { return type == SteinbergCubase5Bridged; } | bool isCubaseBridged() const noexcept { return type == SteinbergCubase5Bridged; } | ||||
bool isSteinberg() const noexcept { return isCubase() || isNuendo() || isWavelab(); } | |||||
bool isTracktion() const noexcept { return type == Tracktion3 || type == TracktionGeneric; } | |||||
bool isSonar() const noexcept { return type == CakewalkSonar8 || type == CakewalkSonarGeneric; } | |||||
bool isWavelab() const noexcept { return isWavelabLegacy() || type == SteinbergWavelab7 || type == SteinbergWavelab8 || type == SteinbergWavelabGeneric; } | |||||
bool isWavelabLegacy() const noexcept { return type == SteinbergWavelab5 || type == SteinbergWavelab6; } | |||||
bool isPremiere() const noexcept { return type == AdobePremierePro; } | |||||
bool isLogic() const noexcept { return type == AppleLogic; } | bool isLogic() const noexcept { return type == AppleLogic; } | ||||
bool isReceptor() const noexcept { return type == MuseReceptorGeneric; } | |||||
bool isSamplitude() const noexcept { return type == MagixSamplitude; } | |||||
bool isFruityLoops() const noexcept { return type == FruityLoops; } | bool isFruityLoops() const noexcept { return type == FruityLoops; } | ||||
bool isWaveBurner() const noexcept { return type == WaveBurner; } | |||||
bool isDigitalPerformer() const noexcept { return type == DigitalPerformer; } | |||||
bool isNuendo() const noexcept { return type == SteinbergNuendo3 || type == SteinbergNuendo4 || type == SteinbergNuendo5 || type == SteinbergNuendoGeneric; } | |||||
bool isPremiere() const noexcept { return type == AdobePremierePro; } | |||||
bool isPyramix() const noexcept { return type == MergingPyramix; } | |||||
bool isReceptor() const noexcept { return type == MuseReceptorGeneric; } | |||||
bool isReaper() const noexcept { return type == Reaper; } | bool isReaper() const noexcept { return type == Reaper; } | ||||
bool isSamplitude() const noexcept { return type == MagixSamplitude; } | |||||
bool isSonar() const noexcept { return type == CakewalkSonar8 || type == CakewalkSonarGeneric; } | |||||
bool isSteinbergTestHost() const noexcept{ return type == SteinbergTestHost; } | |||||
bool isSteinberg() const noexcept { return isCubase() || isNuendo() || isWavelab() || isSteinbergTestHost(); } | |||||
bool isStudioOne() const noexcept { return type == StudioOne; } | bool isStudioOne() const noexcept { return type == StudioOne; } | ||||
bool isPyramix() const noexcept { return type == MergingPyramix; } | |||||
bool isTracktion() const noexcept { return type == Tracktion3 || type == TracktionGeneric; } | |||||
bool isVBVSTScanner() const noexcept { return type == VBVSTScanner; } | bool isVBVSTScanner() const noexcept { return type == VBVSTScanner; } | ||||
bool isWaveBurner() const noexcept { return type == WaveBurner; } | |||||
bool isWavelab() const noexcept { return isWavelabLegacy() || type == SteinbergWavelab7 || type == SteinbergWavelab8 || type == SteinbergWavelabGeneric; } | |||||
bool isWavelabLegacy() const noexcept { return type == SteinbergWavelab5 || type == SteinbergWavelab6; } | |||||
//============================================================================== | //============================================================================== | ||||
const char* getHostDescription() const noexcept | const char* getHostDescription() const noexcept | ||||
@@ -105,14 +111,18 @@ public: | |||||
case AbletonLive7: return "Ableton Live 7"; | case AbletonLive7: return "Ableton Live 7"; | ||||
case AbletonLive8: return "Ableton Live 8"; | case AbletonLive8: return "Ableton Live 8"; | ||||
case AbletonLiveGeneric: return "Ableton Live"; | case AbletonLiveGeneric: return "Ableton Live"; | ||||
case AdobeAudition: return "Adobe Audition"; | |||||
case AdobePremierePro: return "Adobe Premiere"; | case AdobePremierePro: return "Adobe Premiere"; | ||||
case AppleLogic: return "Apple Logic"; | case AppleLogic: return "Apple Logic"; | ||||
case CakewalkSonar8: return "Cakewalk Sonar 8"; | case CakewalkSonar8: return "Cakewalk Sonar 8"; | ||||
case CakewalkSonarGeneric: return "Cakewalk Sonar"; | case CakewalkSonarGeneric: return "Cakewalk Sonar"; | ||||
case DigidesignProTools: return "ProTools"; | case DigidesignProTools: return "ProTools"; | ||||
case DigitalPerformer: return "DigitalPerformer"; | |||||
case FruityLoops: return "FruityLoops"; | |||||
case MagixSamplitude: return "Magix Samplitude"; | |||||
case MergingPyramix: return "Pyramix"; | |||||
case MuseReceptorGeneric: return "Muse Receptor"; | |||||
case Reaper: return "Reaper"; | case Reaper: return "Reaper"; | ||||
case Tracktion3: return "Tracktion 3"; | |||||
case TracktionGeneric: return "Tracktion"; | |||||
case SteinbergCubase4: return "Steinberg Cubase 4"; | case SteinbergCubase4: return "Steinberg Cubase 4"; | ||||
case SteinbergCubase5: return "Steinberg Cubase 5"; | case SteinbergCubase5: return "Steinberg Cubase 5"; | ||||
case SteinbergCubase5Bridged: return "Steinberg Cubase 5 Bridged"; | case SteinbergCubase5Bridged: return "Steinberg Cubase 5 Bridged"; | ||||
@@ -128,14 +138,12 @@ public: | |||||
case SteinbergWavelab7: return "Steinberg Wavelab 7"; | case SteinbergWavelab7: return "Steinberg Wavelab 7"; | ||||
case SteinbergWavelab8: return "Steinberg Wavelab 8"; | case SteinbergWavelab8: return "Steinberg Wavelab 8"; | ||||
case SteinbergWavelabGeneric: return "Steinberg Wavelab"; | case SteinbergWavelabGeneric: return "Steinberg Wavelab"; | ||||
case MuseReceptorGeneric: return "Muse Receptor"; | |||||
case MagixSamplitude: return "Magix Samplitude"; | |||||
case FruityLoops: return "FruityLoops"; | |||||
case WaveBurner: return "WaveBurner"; | |||||
case DigitalPerformer: return "DigitalPerformer"; | |||||
case SteinbergTestHost: return "Steinberg TestHost"; | |||||
case StudioOne: return "Studio One"; | case StudioOne: return "Studio One"; | ||||
case MergingPyramix: return "Pyramix"; | |||||
case Tracktion3: return "Tracktion 3"; | |||||
case TracktionGeneric: return "Tracktion"; | |||||
case VBVSTScanner: return "VBVSTScanner"; | case VBVSTScanner: return "VBVSTScanner"; | ||||
case WaveBurner: return "WaveBurner"; | |||||
default: break; | default: break; | ||||
} | } | ||||
@@ -155,7 +163,7 @@ private: | |||||
const String hostPath (getHostPath()); | const String hostPath (getHostPath()); | ||||
const String hostFilename (File (hostPath).getFileName()); | const String hostFilename (File (hostPath).getFileName()); | ||||
#if JUCE_MAC | |||||
#if JUCE_MAC | |||||
if (hostPath.containsIgnoreCase ("Live 6.")) return AbletonLive6; | if (hostPath.containsIgnoreCase ("Live 6.")) return AbletonLive6; | ||||
if (hostPath.containsIgnoreCase ("Live 7.")) return AbletonLive7; | if (hostPath.containsIgnoreCase ("Live 7.")) return AbletonLive7; | ||||
if (hostPath.containsIgnoreCase ("Live 8.")) return AbletonLive8; | if (hostPath.containsIgnoreCase ("Live 8.")) return AbletonLive8; | ||||
@@ -182,11 +190,12 @@ private: | |||||
if (hostPath.containsIgnoreCase ("Tracktion 3")) return Tracktion3; | if (hostPath.containsIgnoreCase ("Tracktion 3")) return Tracktion3; | ||||
if (hostFilename.containsIgnoreCase ("Tracktion")) return TracktionGeneric; | if (hostFilename.containsIgnoreCase ("Tracktion")) return TracktionGeneric; | ||||
#elif JUCE_WINDOWS | |||||
#elif JUCE_WINDOWS | |||||
if (hostFilename.containsIgnoreCase ("Live 6.")) return AbletonLive6; | if (hostFilename.containsIgnoreCase ("Live 6.")) return AbletonLive6; | ||||
if (hostFilename.containsIgnoreCase ("Live 7.")) return AbletonLive7; | if (hostFilename.containsIgnoreCase ("Live 7.")) return AbletonLive7; | ||||
if (hostFilename.containsIgnoreCase ("Live 8.")) return AbletonLive8; | if (hostFilename.containsIgnoreCase ("Live 8.")) return AbletonLive8; | ||||
if (hostFilename.containsIgnoreCase ("Live ")) return AbletonLiveGeneric; | if (hostFilename.containsIgnoreCase ("Live ")) return AbletonLiveGeneric; | ||||
if (hostFilename.containsIgnoreCase ("Audition")) return AdobeAudition; | |||||
if (hostFilename.containsIgnoreCase ("Adobe Premiere")) return AdobePremierePro; | if (hostFilename.containsIgnoreCase ("Adobe Premiere")) return AdobePremierePro; | ||||
if (hostFilename.containsIgnoreCase ("ProTools")) return DigidesignProTools; | if (hostFilename.containsIgnoreCase ("ProTools")) return DigidesignProTools; | ||||
if (hostPath.containsIgnoreCase ("SONAR 8")) return CakewalkSonar8; | if (hostPath.containsIgnoreCase ("SONAR 8")) return CakewalkSonar8; | ||||
@@ -207,6 +216,7 @@ private: | |||||
if (hostPath.containsIgnoreCase ("Wavelab 8")) return SteinbergWavelab8; | if (hostPath.containsIgnoreCase ("Wavelab 8")) return SteinbergWavelab8; | ||||
if (hostPath.containsIgnoreCase ("Nuendo")) return SteinbergNuendoGeneric; | if (hostPath.containsIgnoreCase ("Nuendo")) return SteinbergNuendoGeneric; | ||||
if (hostFilename.containsIgnoreCase ("Wavelab")) return SteinbergWavelabGeneric; | if (hostFilename.containsIgnoreCase ("Wavelab")) return SteinbergWavelabGeneric; | ||||
if (hostFilename.containsIgnoreCase ("TestHost")) return SteinbergTestHost; | |||||
if (hostFilename.containsIgnoreCase ("rm-host")) return MuseReceptorGeneric; | if (hostFilename.containsIgnoreCase ("rm-host")) return MuseReceptorGeneric; | ||||
if (hostFilename.startsWith ("FL")) return FruityLoops; | if (hostFilename.startsWith ("FL")) return FruityLoops; | ||||
if (hostPath.containsIgnoreCase ("Studio One")) return StudioOne; | if (hostPath.containsIgnoreCase ("Studio One")) return StudioOne; | ||||
@@ -216,7 +226,8 @@ private: | |||||
if (hostFilename.startsWithIgnoreCase ("Sam")) return MagixSamplitude; | if (hostFilename.startsWithIgnoreCase ("Sam")) return MagixSamplitude; | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
jassertfalse // not yet done! | |||||
if (hostFilename.containsIgnoreCase ("Ardour")) return Ardour; | |||||
#else | #else | ||||
#error | #error | ||||
#endif | #endif | ||||
@@ -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); | ||||
@@ -1927,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); | ||||
@@ -2600,7 +2593,7 @@ private: | |||||
ScopedPointer<CarbonWrapperComponent> carbonWrapper; | ScopedPointer<CarbonWrapperComponent> carbonWrapper; | ||||
#endif | #endif | ||||
ScopedPointer<NSViewComponent> cocoaWrapper; | |||||
ScopedPointer<AutoResizingNSViewComponentWithParent> cocoaWrapper; | |||||
void resized() override | void resized() override | ||||
{ | { | ||||
@@ -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) | ||||
@@ -579,11 +579,6 @@ public: | |||||
/** This method is called when the number of input or output channels is changed. */ | /** This method is called when the number of input or output channels is changed. */ | ||||
virtual void numChannelsChanged(); | virtual void numChannelsChanged(); | ||||
//============================================================================== | |||||
/** LV2 specific calls, saving/restore as string. */ | |||||
virtual String getStateInformationString () { return String::empty; } | |||||
virtual void setStateInformationString (const String& data) {} | |||||
//============================================================================== | //============================================================================== | ||||
/** Adds a listener that will be called when an aspect of this processor changes. */ | /** Adds a listener that will be called when an aspect of this processor changes. */ | ||||
virtual void addListener (AudioProcessorListener* newListener); | virtual void addListener (AudioProcessorListener* newListener); | ||||
@@ -614,6 +609,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, | ||||
@@ -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(); | ||||
} | } | ||||
@@ -40,12 +40,19 @@ public: | |||||
void timerCallback() override | void timerCallback() override | ||||
{ | { | ||||
const float newLevel = (float) manager.getCurrentInputLevel(); | |||||
if (isShowing()) | |||||
{ | |||||
const float newLevel = (float) manager.getCurrentInputLevel(); | |||||
if (std::abs (level - newLevel) > 0.005f) | |||||
if (std::abs (level - newLevel) > 0.005f) | |||||
{ | |||||
level = newLevel; | |||||
repaint(); | |||||
} | |||||
} | |||||
else | |||||
{ | { | ||||
level = newLevel; | |||||
repaint(); | |||||
level = 0; | |||||
} | } | ||||
} | } | ||||
@@ -211,19 +211,11 @@ void MidiKeyboardComponent::getKeyPosition (int midiNoteNumber, const float keyW | |||||
5.0f, 6 - blackNoteWidth * 0.3f, | 5.0f, 6 - blackNoteWidth * 0.3f, | ||||
6.0f }; | 6.0f }; | ||||
static const float widths[] = { 1.0f, blackNoteWidth, | |||||
1.0f, blackNoteWidth, | |||||
1.0f, | |||||
1.0f, blackNoteWidth, | |||||
1.0f, blackNoteWidth, | |||||
1.0f, blackNoteWidth, | |||||
1.0f }; | |||||
const int octave = midiNoteNumber / 12; | const int octave = midiNoteNumber / 12; | ||||
const int note = midiNoteNumber % 12; | const int note = midiNoteNumber % 12; | ||||
x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_); | x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_); | ||||
w = roundToInt (widths [note] * keyWidth_); | |||||
w = roundToInt (MidiMessage::isMidiNoteBlack (note) ? blackNoteWidth * keyWidth_ : keyWidth_); | |||||
} | } | ||||
void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const | void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const | ||||
@@ -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" | ||||
} | } |
@@ -87,7 +87,7 @@ | |||||
#define STRICT 1 | #define STRICT 1 | ||||
#define WIN32_LEAN_AND_MEAN 1 | #define WIN32_LEAN_AND_MEAN 1 | ||||
#if JUCE_MINGW | #if JUCE_MINGW | ||||
#define _WIN32_WINNT 0x0502 | |||||
#define _WIN32_WINNT 0x0501 | |||||
#else | #else | ||||
#define _WIN32_WINNT 0x0600 | #define _WIN32_WINNT 0x0600 | ||||
#endif | #endif | ||||
@@ -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" | ||||
} | } | ||||
@@ -89,7 +89,7 @@ public: | |||||
#else | #else | ||||
#if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
#if defined (WINAPI) || defined (_WINDOWS_) || defined(JUCE_MINGW) | |||||
#if defined (WINAPI) || defined (_WINDOWS_) | |||||
#define JUCE_MAIN_FUNCTION int __stdcall WinMain (HINSTANCE, HINSTANCE, const LPSTR, int) | #define JUCE_MAIN_FUNCTION int __stdcall WinMain (HINSTANCE, HINSTANCE, const LPSTR, int) | ||||
#elif defined (_UNICODE) | #elif defined (_UNICODE) | ||||
#define JUCE_MAIN_FUNCTION int __stdcall WinMain (void*, void*, const wchar_t*, int) | #define JUCE_MAIN_FUNCTION int __stdcall WinMain (void*, void*, const wchar_t*, int) | ||||
@@ -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) | ||||
{ | { | ||||
@@ -127,8 +127,7 @@ void DialogWindow::showDialog (const String& dialogTitle, | |||||
Colour backgroundColour, | Colour backgroundColour, | ||||
const bool escapeKeyTriggersCloseButton, | const bool escapeKeyTriggersCloseButton, | ||||
const bool resizable, | const bool resizable, | ||||
const bool useBottomRightCornerResizer, | |||||
const bool useNativeTitleBar) | |||||
const bool useBottomRightCornerResizer) | |||||
{ | { | ||||
LaunchOptions o; | LaunchOptions o; | ||||
o.dialogTitle = dialogTitle; | o.dialogTitle = dialogTitle; | ||||
@@ -136,9 +135,9 @@ void DialogWindow::showDialog (const String& dialogTitle, | |||||
o.componentToCentreAround = componentToCentreAround; | o.componentToCentreAround = componentToCentreAround; | ||||
o.dialogBackgroundColour = backgroundColour; | o.dialogBackgroundColour = backgroundColour; | ||||
o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton; | o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton; | ||||
o.useNativeTitleBar = false; | |||||
o.resizable = resizable; | o.resizable = resizable; | ||||
o.useBottomRightCornerResizer = useBottomRightCornerResizer; | o.useBottomRightCornerResizer = useBottomRightCornerResizer; | ||||
o.useNativeTitleBar = useNativeTitleBar; | |||||
o.launchAsync(); | o.launchAsync(); | ||||
} | } | ||||
@@ -150,8 +149,7 @@ int DialogWindow::showModalDialog (const String& dialogTitle, | |||||
Colour backgroundColour, | Colour backgroundColour, | ||||
const bool escapeKeyTriggersCloseButton, | const bool escapeKeyTriggersCloseButton, | ||||
const bool resizable, | const bool resizable, | ||||
const bool useBottomRightCornerResizer, | |||||
const bool useNativeTitleBar) | |||||
const bool useBottomRightCornerResizer) | |||||
{ | { | ||||
LaunchOptions o; | LaunchOptions o; | ||||
o.dialogTitle = dialogTitle; | o.dialogTitle = dialogTitle; | ||||
@@ -159,9 +157,9 @@ int DialogWindow::showModalDialog (const String& dialogTitle, | |||||
o.componentToCentreAround = componentToCentreAround; | o.componentToCentreAround = componentToCentreAround; | ||||
o.dialogBackgroundColour = backgroundColour; | o.dialogBackgroundColour = backgroundColour; | ||||
o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton; | o.escapeKeyTriggersCloseButton = escapeKeyTriggersCloseButton; | ||||
o.useNativeTitleBar = false; | |||||
o.resizable = resizable; | o.resizable = resizable; | ||||
o.useBottomRightCornerResizer = useBottomRightCornerResizer; | o.useBottomRightCornerResizer = useBottomRightCornerResizer; | ||||
o.useNativeTitleBar = useNativeTitleBar; | |||||
return o.runModal(); | return o.runModal(); | ||||
} | } | ||||
@@ -193,8 +193,7 @@ public: | |||||
Colour backgroundColour, | Colour backgroundColour, | ||||
bool escapeKeyTriggersCloseButton, | bool escapeKeyTriggersCloseButton, | ||||
bool shouldBeResizable = false, | bool shouldBeResizable = false, | ||||
bool useBottomRightCornerResizer = false, | |||||
bool useNativeTitleBar = false); | |||||
bool useBottomRightCornerResizer = false); | |||||
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN | #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN | ||||
/** Easy way of quickly showing a dialog box containing a given component. | /** Easy way of quickly showing a dialog box containing a given component. | ||||
@@ -242,8 +241,7 @@ public: | |||||
Colour backgroundColour, | Colour backgroundColour, | ||||
bool escapeKeyTriggersCloseButton, | bool escapeKeyTriggersCloseButton, | ||||
bool shouldBeResizable = false, | bool shouldBeResizable = false, | ||||
bool useBottomRightCornerResizer = false, | |||||
bool useNativeTitleBar = false); | |||||
bool useBottomRightCornerResizer = false); | |||||
#endif | #endif | ||||
@@ -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) | ||||
}; | }; | ||||