From ac1d6955e7c6e31e590b1462e18c473f9ad20ec1 Mon Sep 17 00:00:00 2001 From: jules Date: Tue, 29 May 2012 10:30:41 +0100 Subject: [PATCH] Rewrote all internal OSX obj-C classes as dynamically-created classes. A side-effect of this is that plugins will no longer suffer from obj-C name-collision problems, and the old JUCE_ObjCExtraSuffix value is no longer needed. --- .../jucer_ProjectExport_XCode.h | 16 - .../juce_audio_devices/juce_audio_devices.cpp | 1 - .../native/juce_mac_AudioCDBurner.mm | 567 ++-- .../AU/juce_AU_Wrapper.mm | 299 +- .../utility/juce_CheckSettingMacros.h | 4 - .../utility/juce_IncludeSystemHeaders.h | 3 + modules/juce_core/juce_core.cpp | 1 - modules/juce_core/native/juce_mac_Network.mm | 438 ++- .../juce_core/native/juce_mac_ObjCSuffix.h | 52 - .../juce_core/native/juce_osx_ObjCHelpers.h | 64 +- modules/juce_events/juce_events.cpp | 1 - .../native/juce_mac_MessageManager.mm | 51 +- modules/juce_graphics/juce_graphics.cpp | 1 - modules/juce_gui_basics/juce_gui_basics.cpp | 1 - .../native/juce_mac_FileChooser.mm | 137 +- .../native/juce_mac_MainMenu.mm | 205 +- .../native/juce_mac_NSViewComponentPeer.mm | 2835 ++++++++--------- modules/juce_gui_extra/juce_gui_extra.cpp | 1 - .../native/juce_mac_WebBrowserComponent.mm | 107 +- modules/juce_opengl/juce_opengl.cpp | 1 - modules/juce_opengl/native/juce_OpenGL_osx.h | 152 +- modules/juce_video/juce_video.cpp | 1 - .../native/juce_mac_CameraDevice.mm | 223 +- .../juce_mac_QuickTimeMovieComponent.mm | 62 +- 24 files changed, 2497 insertions(+), 2726 deletions(-) delete mode 100644 modules/juce_core/native/juce_mac_ObjCSuffix.h diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h index 213eab9590..d631b2d6a9 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h @@ -61,8 +61,6 @@ public: if (getTargetLocationString().isEmpty()) getTargetLocationValue() = getDefaultBuildsRootFolder() + (iOS ? "iOS" : "MacOSX"); - - setValueIfVoid (getObjCSuffixValue(), createAlphaNumericUID()); } static XCodeProjectExporter* createForSettings (Project& project, const ValueTree& settings) @@ -76,9 +74,6 @@ public: } //============================================================================== - Value getObjCSuffixValue() { return getSetting ("objCExtraSuffix"); } - String getObjCSuffixString() const { return settings ["objCExtraSuffix"]; } - Value getPListToMergeValue() { return getSetting ("customPList"); } String getPListToMergeString() const { return settings ["customPList"]; } @@ -113,11 +108,6 @@ public: { ProjectExporter::createPropertyEditors (props); - props.add (new TextPropertyComponent (getObjCSuffixValue(), "Objective-C class name suffix", 64, false), - "Because objective-C linkage is done by string-matching, you can get horrible linkage mix-ups when different modules containing the " - "same class-names are loaded simultaneously. This setting lets you provide a unique string that will be used in naming " - "the obj-C classes in your executable to avoid this."); - if (projectType.isGUIApplication() && ! iOS) { props.add (new TextPropertyComponent (getSetting ("documentExtensions"), "Document file extensions", 128, false), @@ -746,12 +736,6 @@ private: s.add ("GCC_SYMBOLS_PRIVATE_EXTERN = YES"); } - { - const String objCSuffix (getObjCSuffixString().trim()); - if (objCSuffix.isNotEmpty()) - defines.set ("JUCE_ObjCExtraSuffix", replacePreprocessorTokens (config, objCSuffix)); - } - { defines = mergePreprocessorDefs (defines, getAllPreprocessorDefs (config)); diff --git a/modules/juce_audio_devices/juce_audio_devices.cpp b/modules/juce_audio_devices/juce_audio_devices.cpp index d379789c28..776f84bd0c 100644 --- a/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/modules/juce_audio_devices/juce_audio_devices.cpp @@ -163,7 +163,6 @@ namespace juce //============================================================================== #if JUCE_MAC #include "../juce_core/native/juce_osx_ObjCHelpers.h" - #include "../juce_core/native/juce_mac_ObjCSuffix.h" #include "native/juce_mac_CoreAudio.cpp" #include "native/juce_mac_CoreMidi.cpp" diff --git a/modules/juce_audio_devices/native/juce_mac_AudioCDBurner.mm b/modules/juce_audio_devices/native/juce_mac_AudioCDBurner.mm index 4fabfa7817..ed931e3dd0 100644 --- a/modules/juce_audio_devices/native/juce_mac_AudioCDBurner.mm +++ b/modules/juce_audio_devices/native/juce_mac_AudioCDBurner.mm @@ -25,305 +25,256 @@ const int kilobytesPerSecond1x = 176; -} // (juce namespace) - -#define OpenDiskDevice MakeObjCClassName(OpenDiskDevice) - -@interface OpenDiskDevice : NSObject +struct AudioTrackProducerClass : public ObjCClass { -@public + AudioTrackProducerClass() : ObjCClass ("JUCEAudioTrackProducer_") + { + addIvar ("source"); + + addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder, "@@:^v"); + addMethod (@selector (cleanupTrackAfterBurn:), cleanupTrackAfterBurn, "v@:@"); + addMethod (@selector (cleanupTrackAfterVerification:), cleanupTrackAfterVerification, "c@:@"); + addMethod (@selector (estimateLengthOfTrack:), estimateLengthOfTrack, "Q@:@"); + addMethod (@selector (prepareTrack:forBurn:toMedia:), prepareTrack, "c@:@@@"); + addMethod (@selector (prepareTrackForVerification:), prepareTrackForVerification, "c@:@"); + addMethod (@selector (produceDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), + produceDataForTrack, "I@:@^cIQI^I"); + addMethod (@selector (producePreGapForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), + produceDataForTrack, "I@:@^cIQI^I"); + addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), + produceDataForTrack, "I@:@^cIQI^I"); + + registerClass(); + } + + struct AudioSourceHolder + { + AudioSourceHolder (AudioSource* source_, int numFrames) + : source (source_), readPosition (0), lengthInFrames (numFrames) + { + } + + ~AudioSourceHolder() + { + if (source != nullptr) + source->releaseResources(); + } + + ScopedPointer source; + int readPosition, lengthInFrames; + }; + +private: + static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source) + { + self = sendSuperclassMessage (self, @selector (init)); + object_setInstanceVariable (self, "source", source); + return self; + } + + static AudioSourceHolder* getSource (id self) + { + return getIvar (self, "source"); + } + + static void dealloc (id self, SEL) + { + delete getSource (self); + sendSuperclassMessage (self, @selector (dealloc)); + } + + static void cleanupTrackAfterBurn (id self, SEL, DRTrack*) {} + static BOOL cleanupTrackAfterVerification (id self, SEL, DRTrack*) { return true; } + + static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*) + { + return getSource (self)->lengthInFrames; + } + + static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*) + { + AudioSourceHolder* const source = getSource (self); + + if (source != nullptr) + { + source->source->prepareToPlay (44100 / 75, 44100); + source->readPosition = 0; + } + + return true; + } + + static BOOL prepareTrackForVerification (id self, SEL, DRTrack*) + { + AudioSourceHolder* const source = getSource (self); + + if (source != nullptr) + source->source->prepareToPlay (44100 / 75, 44100); + + return true; + } + + static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer, + uint32_t bufferLength, uint64_t /*address*/, + uint32_t /*blockSize*/, uint32_t* /*flags*/) + { + AudioSourceHolder* const source = getSource (self); + + if (source != nullptr) + { + const int numSamples = jmin ((int) bufferLength / 4, + (source->lengthInFrames * (44100 / 75)) - source->readPosition); + + if (numSamples > 0) + { + AudioSampleBuffer tempBuffer (2, numSamples); + AudioSourceChannelInfo info (tempBuffer); + + source->source->getNextAudioBlock (info); + + typedef AudioData::Pointer CDSampleFormat; + + typedef AudioData::Pointer SourceSampleFormat; + CDSampleFormat left (buffer, 2); + left.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (0)), numSamples); + CDSampleFormat right (buffer + 2, 2); + right.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (1)), numSamples); + + source->readPosition += numSamples; + } + + return numSamples * 4; + } + + return 0; + } + + static uint32_t producePreGapForTrack (id self, SEL, DRTrack*, char* buffer, + uint32_t bufferLength, uint64_t /*address*/, + uint32_t /*blockSize*/, uint32_t* /*flags*/) + { + zeromem (buffer, bufferLength); + return bufferLength; + } + + static BOOL verifyDataForTrack (id self, SEL, DRTrack*, const char*, + uint32_t /*bufferLength*/, uint64_t /*address*/, + uint32_t /*blockSize*/, uint32_t* /*flags*/) + { + return true; + } +}; + +struct OpenDiskDevice +{ + OpenDiskDevice (DRDevice* device_) + : device (device_), + tracks ([[NSMutableArray alloc] init]), + underrunProtection (true) + { + } + + ~OpenDiskDevice() + { + [tracks release]; + } + + void addSourceTrack (AudioSource* source, int numSamples) + { + if (source != nullptr) + { + const int numFrames = (numSamples + 587) / 588; + + static AudioTrackProducerClass cls; + + NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:) + withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)]; + DRTrack* track = [[DRTrack alloc] initWithProducer: producer]; + + { + NSMutableDictionary* p = [[track properties] mutableCopy]; + [p setObject: [DRMSF msfWithFrames: numFrames] forKey: DRTrackLengthKey]; + [p setObject: [NSNumber numberWithUnsignedShort: 2352] forKey: DRBlockSizeKey]; + [p setObject: [NSNumber numberWithInt: 0] forKey: DRDataFormKey]; + [p setObject: [NSNumber numberWithInt: 0] forKey: DRBlockTypeKey]; + [p setObject: [NSNumber numberWithInt: 0] forKey: DRTrackModeKey]; + [p setObject: [NSNumber numberWithInt: 0] forKey: DRSessionFormatKey]; + [track setProperties: p]; + [p release]; + } + + [tracks addObject: track]; + + [track release]; + [producer release]; + } + } + + String burn (AudioCDBurner::BurnProgressListener* listener, + bool shouldEject, bool peformFakeBurnForTesting, int burnSpeed) + { + DRBurn* burn = [DRBurn burnForDevice: device]; + + if (! [device acquireExclusiveAccess]) + return "Couldn't open or write to the CD device"; + + [device acquireMediaReservation]; + + NSMutableDictionary* d = [[burn properties] mutableCopy]; + [d autorelease]; + [d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; + [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; + [d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey]; + + if (burnSpeed > 0) + [d setObject: [NSNumber numberWithFloat: burnSpeed * kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey]; + + if (! underrunProtection) + [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey]; + + [burn setProperties: d]; + + [burn writeLayout: tracks]; + + for (;;) + { + Thread::sleep (300); + float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue]; + + if (listener != nullptr && listener->audioCDBurnProgress (progress)) + { + [burn abort]; + return "User cancelled the write operation"; + } + + if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed]) + return "Write operation failed"; + + if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone]) + break; + + NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey] + objectForKey: DRErrorStatusErrorStringKey]; + + if ([err length] > 0) + return CharPointer_UTF8 ([err UTF8String]); + } + + [device releaseMediaReservation]; + [device releaseExclusiveAccess]; + return String::empty; + } + DRDevice* device; NSMutableArray* tracks; bool underrunProtection; -} - -- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device; -- (void) dealloc; -- (void) addSourceTrack: (juce::AudioSource*) source numSamples: (int) numSamples_; -- (void) burn: (juce::AudioCDBurner::BurnProgressListener*) listener errorString: (juce::String*) error - ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed; -@end - -//============================================================================== -#define AudioTrackProducer MakeObjCClassName(AudioTrackProducer) - -@interface AudioTrackProducer : NSObject -{ - juce::AudioSource* source; - int readPosition, lengthInFrames; -} - -- (AudioTrackProducer*) init: (int) lengthInFrames; -- (AudioTrackProducer*) initWithAudioSource: (juce::AudioSource*) source numSamples: (int) lengthInSamples; -- (void) dealloc; -- (void) setupTrackProperties: (DRTrack*) track; - -- (void) cleanupTrackAfterBurn: (DRTrack*) track; -- (BOOL) cleanupTrackAfterVerification:(DRTrack*)track; -- (uint64_t) estimateLengthOfTrack:(DRTrack*)track; -- (BOOL) prepareTrack:(DRTrack*)track forBurn:(DRBurn*)burn - toMedia:(NSDictionary*)mediaInfo; -- (BOOL) prepareTrackForVerification:(DRTrack*)track; -- (uint32_t) produceDataForTrack:(DRTrack*)track intoBuffer:(char*)buffer - length:(uint32_t)bufferLength atAddress:(uint64_t)address - blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags; -- (uint32_t) producePreGapForTrack:(DRTrack*)track - intoBuffer:(char*)buffer length:(uint32_t)bufferLength - atAddress:(uint64_t)address blockSize:(uint32_t)blockSize - ioFlags:(uint32_t*)flags; -- (BOOL) verifyDataForTrack:(DRTrack*)track inBuffer:(const char*)buffer - length:(uint32_t)bufferLength atAddress:(uint64_t)address - blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags; -- (uint32_t) producePreGapForTrack:(DRTrack*)track - intoBuffer:(char*)buffer length:(uint32_t)bufferLength - atAddress:(uint64_t)address blockSize:(uint32_t)blockSize - ioFlags:(uint32_t*)flags; -@end - -//============================================================================== -@implementation OpenDiskDevice - -- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device_ -{ - [super init]; - - device = device_; - tracks = [[NSMutableArray alloc] init]; - underrunProtection = true; - return self; -} - -- (void) dealloc -{ - [tracks release]; - [super dealloc]; -} - -- (void) addSourceTrack: (juce::AudioSource*) source_ numSamples: (int) numSamples_ -{ - AudioTrackProducer* p = [[AudioTrackProducer alloc] initWithAudioSource: source_ numSamples: numSamples_]; - DRTrack* t = [[DRTrack alloc] initWithProducer: p]; - [p setupTrackProperties: t]; - - [tracks addObject: t]; - - [t release]; - [p release]; -} - -- (void) burn: (juce::AudioCDBurner::BurnProgressListener*) listener errorString: (juce::String*) error - ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed -{ - DRBurn* burn = [DRBurn burnForDevice: device]; - - if (! [device acquireExclusiveAccess]) - { - *error = "Couldn't open or write to the CD device"; - return; - } - - [device acquireMediaReservation]; - - NSMutableDictionary* d = [[burn properties] mutableCopy]; - [d autorelease]; - [d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; - [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; - [d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey]; - - if (burnSpeed > 0) - [d setObject: [NSNumber numberWithFloat: burnSpeed * juce::kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey]; - - if (! underrunProtection) - [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey]; - - [burn setProperties: d]; - - [burn writeLayout: tracks]; - - for (;;) - { - juce::Thread::sleep (300); - float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue]; - - if (listener != nullptr && listener->audioCDBurnProgress (progress)) - { - [burn abort]; - *error = "User cancelled the write operation"; - break; - } - - if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed]) - { - *error = "Write operation failed"; - break; - } - else if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone]) - { - break; - } - - NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey] - objectForKey: DRErrorStatusErrorStringKey]; - - if ([err length] > 0) - { - *error = juce::CharPointer_UTF8 ([err UTF8String]); - break; - } - } - - [device releaseMediaReservation]; - [device releaseExclusiveAccess]; -} -@end - -//============================================================================== -@implementation AudioTrackProducer - -- (AudioTrackProducer*) init: (int) lengthInFrames_ -{ - lengthInFrames = lengthInFrames_; - readPosition = 0; - return self; -} - -- (void) setupTrackProperties: (DRTrack*) track -{ - NSMutableDictionary* p = [[track properties] mutableCopy]; - [p setObject:[DRMSF msfWithFrames: lengthInFrames] forKey: DRTrackLengthKey]; - [p setObject:[NSNumber numberWithUnsignedShort:2352] forKey: DRBlockSizeKey]; - [p setObject:[NSNumber numberWithInt:0] forKey: DRDataFormKey]; - [p setObject:[NSNumber numberWithInt:0] forKey: DRBlockTypeKey]; - [p setObject:[NSNumber numberWithInt:0] forKey: DRTrackModeKey]; - [p setObject:[NSNumber numberWithInt:0] forKey: DRSessionFormatKey]; - - - [track setProperties: p]; - [p release]; -} - -- (AudioTrackProducer*) initWithAudioSource: (juce::AudioSource*) source_ numSamples: (int) lengthInSamples -{ - AudioTrackProducer* s = [self init: (lengthInSamples + 587) / 588]; - - if (s != nil) - s->source = source_; - - return s; -} - -- (void) dealloc -{ - if (source != nullptr) - { - source->releaseResources(); - delete source; - } - - [super dealloc]; -} - -- (void) cleanupTrackAfterBurn: (DRTrack*) track -{ - (void) track; -} - -- (BOOL) cleanupTrackAfterVerification: (DRTrack*) track -{ - (void) track; - return true; -} - -- (uint64_t) estimateLengthOfTrack: (DRTrack*) track -{ - (void) track; - return lengthInFrames; -} - -- (BOOL) prepareTrack: (DRTrack*) track forBurn: (DRBurn*) burn - toMedia: (NSDictionary*) mediaInfo -{ - (void) track; (void) burn; (void) mediaInfo; - - if (source != nullptr) - source->prepareToPlay (44100 / 75, 44100); - - readPosition = 0; - return true; -} - -- (BOOL) prepareTrackForVerification: (DRTrack*) track -{ - (void) track; - if (source != nullptr) - source->prepareToPlay (44100 / 75, 44100); - - return true; -} - -- (uint32_t) produceDataForTrack: (DRTrack*) track intoBuffer: (char*) buffer - length: (uint32_t) bufferLength atAddress: (uint64_t) address - blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags -{ - (void) track; (void) address; (void) blockSize; (void) flags; - - if (source != nullptr) - { - const int numSamples = juce::jmin ((int) bufferLength / 4, (lengthInFrames * (44100 / 75)) - readPosition); - - if (numSamples > 0) - { - juce::AudioSampleBuffer tempBuffer (2, numSamples); - juce::AudioSourceChannelInfo info (tempBuffer); - - source->getNextAudioBlock (info); - - typedef juce::AudioData::Pointer CDSampleFormat; - - typedef juce::AudioData::Pointer SourceSampleFormat; - CDSampleFormat left (buffer, 2); - left.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (0)), numSamples); - CDSampleFormat right (buffer + 2, 2); - right.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (1)), numSamples); - - readPosition += numSamples; - } - - return numSamples * 4; - } - - return 0; -} - -- (uint32_t) producePreGapForTrack: (DRTrack*) track - intoBuffer: (char*) buffer length: (uint32_t) bufferLength - atAddress: (uint64_t) address blockSize: (uint32_t) blockSize - ioFlags: (uint32_t*) flags -{ - (void) track; (void) address; (void) blockSize; (void) flags; - zeromem (buffer, bufferLength); - return bufferLength; -} - -- (BOOL) verifyDataForTrack: (DRTrack*) track inBuffer: (const char*) buffer - length: (uint32_t) bufferLength atAddress: (uint64_t) address - blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags -{ - (void) track; (void) buffer; (void) bufferLength; (void) address; (void) blockSize; (void) flags; - return true; -} - -@end - - -namespace juce -{ +}; //============================================================================== class AudioCDBurner::Pimpl : public Timer @@ -333,9 +284,10 @@ public: : device (0), owner (owner_) { DRDevice* dev = [[DRDevice devices] objectAtIndex: deviceIndex]; + if (dev != nil) { - device = [[OpenDiskDevice alloc] initWithDRDevice: dev]; + device = new OpenDiskDevice (dev); lastState = getDiskState(); startTimer (1000); } @@ -344,7 +296,6 @@ public: ~Pimpl() { stopTimer(); - [device release]; } void timerCallback() @@ -386,7 +337,7 @@ public: return unknown; } - bool openTray() { return [device->device isValid] && [device->device ejectMedia]; } + bool openTray() { return [device->device isValid] && [device->device ejectMedia]; } Array getAvailableWriteSpeeds() const { @@ -422,7 +373,7 @@ public: objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; } - OpenDiskDevice* device; + ScopedPointer device; private: DiskState lastState; @@ -528,30 +479,20 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) { if ([pimpl->device->device isValid]) { - [pimpl->device addSourceTrack: source numSamples: numSamps]; + pimpl->device->addSourceTrack (source, numSamps); return true; } return false; } -String AudioCDBurner::burn (juce::AudioCDBurner::BurnProgressListener* listener, +String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, bool ejectDiscAfterwards, bool performFakeBurnForTesting, int writeSpeed) { - String error ("Couldn't open or write to the CD device"); - if ([pimpl->device->device isValid]) - { - error = String::empty; + return pimpl->device->burn (listener, ejectDiscAfterwards, performFakeBurnForTesting, writeSpeed); - [pimpl->device burn: listener - errorString: &error - ejectAfterwards: ejectDiscAfterwards - isFake: performFakeBurnForTesting - speed: writeSpeed]; - } - - return error; + return "Couldn't open or write to the CD device"; } diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index 6d49c18e2d..9c51557415 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -77,8 +77,11 @@ #include "../utility/juce_FakeMouseMoveGenerator.h" #include "../utility/juce_CarbonVisibility.h" #include "../utility/juce_PluginHostType.h" +#include "../../juce_core/native/juce_osx_ObjCHelpers.h" //============================================================================== +#define JuceUICreationClass JucePlugin_AUCocoaViewClassName + #define juceFilterObjectPropertyID 0x1a45ffe9 static Array activePlugins, activeUIs; @@ -97,52 +100,6 @@ static const int numChannelConfigs = sizeof (channelConfigs) / sizeof (*channelC */ extern AudioProcessor* JUCE_CALLTYPE createPluginFilter(); -//============================================================================== -#define appendMacro1(a, b, c, d) a ## _ ## b ## _ ## c ## _ ## d -#define appendMacro2(a, b, c, d) appendMacro1(a, b, c, d) -#define MakeObjCClassName(rootName) appendMacro2 (rootName, JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JucePlugin_AUExportPrefix) - -#define JuceUICreationClass JucePlugin_AUCocoaViewClassName -#define JuceUIViewClass MakeObjCClassName(JuceUIViewClass) - -class JuceAU; -class EditorCompHolder; - -//============================================================================== -@interface JuceUICreationClass : NSObject -{ -} - -- (JuceUICreationClass*) init; -- (void) dealloc; -- (unsigned) interfaceVersion; -- (NSString*) description; -- (NSView*) uiViewForAudioUnit: (AudioUnit) inAudioUnit - withSize: (NSSize) inPreferredSize; -@end - -//============================================================================== -@interface JuceUIViewClass : NSView -{ - AudioProcessor* filter; - JuceAU* au; - EditorCompHolder* editorComp; -} - -- (JuceUIViewClass*) initWithFilter: (AudioProcessor*) filter - withAU: (JuceAU*) au - withComponent: (AudioProcessorEditor*) editorComp; -- (void) dealloc; -- (void) shutdown; -- (void) applicationWillTerminate: (NSNotification*) aNotification; -- (void) viewDidMoveToWindow; -- (BOOL) mouseDownCanMoveWindow; -- (void) filterBeingDeleted: (JuceAU*) au_; -- (void) deleteEditor; - -@end - - //============================================================================== class JuceAU : public JuceAUBaseClass, public AudioProcessorListener, @@ -187,9 +144,7 @@ public: ~JuceAU() { - for (int i = activeUIs.size(); --i >= 0;) - [((JuceUIViewClass*) activeUIs.getUnchecked(i)) filterBeingDeleted: this]; - + deleteActiveEditors(); juceFilter = nullptr; jassert (activePlugins.contains (this)); @@ -199,6 +154,8 @@ public: shutdownJuce_GUI(); } + void deleteActiveEditors(); + //============================================================================== ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, AudioUnitScope inScope, @@ -281,7 +238,7 @@ public: NSString* bundlePath = [NSString stringWithUTF8String: (const char*) bundleFile.getFullPathName().toUTF8()]; NSBundle* b = [NSBundle bundleWithPath: bundlePath]; - info->mCocoaAUViewClass[0] = (CFStringRef) [NSStringFromClass ([JuceUICreationClass class]) retain]; + info->mCocoaAUViewClass[0] = (CFStringRef) [nsStringLiteral (JUCE_STRINGIFY (JuceUICreationClass)) retain]; info->mCocoaAUViewBundleLocation = (CFURLRef) [[NSURL fileURLWithPath: [b bundlePath]] retain]; return noErr; @@ -974,6 +931,32 @@ public: // have been transferred to another parent which takes over ownership. } + static NSView* createViewFor (AudioProcessor* filter, JuceAU* au, AudioProcessorEditor* const editor) + { + EditorCompHolder* editorCompHolder = new EditorCompHolder (editor); + NSRect r = NSMakeRect (0, 0, editorCompHolder->getWidth(), editorCompHolder->getHeight()); + + static JuceUIViewClass cls; + NSView* view = [[cls.createInstance() initWithFrame: r] autorelease]; + + JuceUIViewClass::setFilter (view, filter); + JuceUIViewClass::setAU (view, au); + JuceUIViewClass::setEditor (view, editorCompHolder); + + [view setHidden: NO]; + [view setPostsFrameChangedNotifications: YES]; + + [[NSNotificationCenter defaultCenter] addObserver: view + selector: @selector (applicationWillTerminate:) + name: NSApplicationWillTerminateNotification + object: nil]; + activeUIs.add (view); + + editorCompHolder->addToDesktop (0, (void*) view); + editorCompHolder->setVisible (view); + return view; + } + void childBoundsChanged (Component*) { Component* editor = getChildComponent(0); @@ -996,117 +979,128 @@ public: } } + //============================================================================== + struct JuceUIViewClass : public ObjCClass + { + JuceUIViewClass() : ObjCClass ("JUCEAUView_") + { + addIvar ("filter"); + addIvar ("au"); + addIvar ("editor"); + + addMethod (@selector (dealloc), dealloc, "v@:"); + addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); + addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow, "v@:"); + addMethod (@selector (mouseDownCanMoveWindow), mouseDownCanMoveWindow, "c@:"); + registerClass(); + } + + static void deleteEditor (id self) + { + ScopedPointer editorComp (getEditor (self)); + + if (editorComp != nullptr) + { + JuceAU* const au = getAU (self); + + if (editorComp->getChildComponent(0) != nullptr) + { + if (activePlugins.contains (au)) // plugin may have been deleted before the UI + { + AudioProcessor* const filter = getIvar (self, "filter"); + filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0)); + } + } + + editorComp = nullptr; + setEditor (self, nullptr); + } + } + + static JuceAU* getAU (id self) { return getIvar (self, "au"); } + static EditorCompHolder* getEditor (id self) { return getIvar (self, "editor"); } + + static void setFilter (id self, AudioProcessor* filter) { object_setInstanceVariable (self, "filter", filter); } + static void setAU (id self, JuceAU* au) { object_setInstanceVariable (self, "au", au); } + static void setEditor (id self, EditorCompHolder* e) { object_setInstanceVariable (self, "editor", e); } + + private: + static void dealloc (id self, SEL) + { + if (activeUIs.contains (self)) + shutdown (self); + + sendSuperclassMessage (self, @selector (dealloc)); + } + + static void applicationWillTerminate (id self, SEL, NSNotification*) + { + shutdown (self); + } + + static void shutdown (id self) + { + // there's some kind of component currently modal, but the host + // is trying to delete our plugin.. + jassert (Component::getCurrentlyModalComponent() == nullptr); + + [[NSNotificationCenter defaultCenter] removeObserver: self]; + deleteEditor (self); + + jassert (activeUIs.contains (self)); + activeUIs.removeValue (self); + if (activePlugins.size() + activeUIs.size() == 0) + shutdownJuce_GUI(); + } + + static void viewDidMoveToWindow (id self, SEL) + { + NSWindow* w = [(NSView*) self window]; + + if (w != nil) + { + [w setAcceptsMouseMovedEvents: YES]; + + EditorCompHolder* const editorComp = getEditor (self); + if (editorComp != nullptr) + [w makeFirstResponder: (NSView*) editorComp->getWindowHandle()]; + } + } + + static BOOL mouseDownCanMoveWindow (id self, SEL) + { + return NO; + } + }; + private: JUCE_DECLARE_NON_COPYABLE (EditorCompHolder); }; +void JuceAU::deleteActiveEditors() +{ + for (int i = activeUIs.size(); --i >= 0;) + { + id ui = (id) activeUIs.getUnchecked(i); + + if (EditorCompHolder::JuceUIViewClass::getAU (ui) == this) + EditorCompHolder::JuceUIViewClass::deleteEditor (ui); + } +} + //============================================================================== -@implementation JuceUIViewClass - -- (JuceUIViewClass*) initWithFilter: (AudioProcessor*) filter_ - withAU: (JuceAU*) au_ - withComponent: (AudioProcessorEditor*) editorComp_ +@interface JuceUICreationClass : NSObject { - filter = filter_; - au = au_; - editorComp = new EditorCompHolder (editorComp_); - - [super initWithFrame: NSMakeRect (0, 0, editorComp_->getWidth(), editorComp_->getHeight())]; - [self setHidden: NO]; - [self setPostsFrameChangedNotifications: YES]; - - [[NSNotificationCenter defaultCenter] addObserver: self - selector: @selector (applicationWillTerminate:) - name: NSApplicationWillTerminateNotification - object: nil]; - activeUIs.add (self); - - editorComp->addToDesktop (0, (void*) self); - editorComp->setVisible (true); - - return self; -} - -- (void) dealloc -{ - if (activeUIs.contains (self)) - [self shutdown]; - - [super dealloc]; -} - -- (void) applicationWillTerminate: (NSNotification*) aNotification -{ - (void) aNotification; - [self shutdown]; -} - -- (void) shutdown -{ - // there's some kind of component currently modal, but the host - // is trying to delete our plugin.. - jassert (Component::getCurrentlyModalComponent() == nullptr); - - [[NSNotificationCenter defaultCenter] removeObserver: self]; - [self deleteEditor]; - - jassert (activeUIs.contains (self)); - activeUIs.removeValue (self); - if (activePlugins.size() + activeUIs.size() == 0) - shutdownJuce_GUI(); -} - -- (void) viewDidMoveToWindow -{ - if ([self window] != nil) - { - [[self window] setAcceptsMouseMovedEvents: YES]; - - if (editorComp != nullptr) - [[self window] makeFirstResponder: (NSView*) editorComp->getWindowHandle()]; - } -} - -- (BOOL) mouseDownCanMoveWindow -{ - return NO; -} - -- (void) deleteEditor -{ - if (editorComp != nullptr) - { - if (editorComp->getChildComponent(0) != nullptr) - if (activePlugins.contains ((void*) au)) // plugin may have been deleted before the UI - filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0)); - - deleteAndZero (editorComp); - } - - editorComp = nullptr; -} - -- (void) filterBeingDeleted: (JuceAU*) au_ -{ - if (au_ == au) - [self deleteEditor]; } +- (unsigned) interfaceVersion; +- (NSString*) description; +- (NSView*) uiViewForAudioUnit: (AudioUnit) inAudioUnit + withSize: (NSSize) inPreferredSize; @end -//============================================================================== @implementation JuceUICreationClass -- (JuceUICreationClass*) init -{ - return [super init]; -} - -- (void) dealloc -{ - [super dealloc]; -} - - (unsigned) interfaceVersion { return 0; @@ -1114,7 +1108,7 @@ private: - (NSString*) description { - return [NSString stringWithString: @JucePlugin_Name]; + return [NSString stringWithString: nsStringLiteral (JucePlugin_Name)]; } - (NSView*) uiViewForAudioUnit: (AudioUnit) inAudioUnit @@ -1138,13 +1132,10 @@ private: return nil; AudioProcessorEditor* editorComp = filter->createEditorIfNeeded(); - if (editorComp == nullptr) return nil; - return [[[JuceUIViewClass alloc] initWithFilter: filter - withAU: au - withComponent: editorComp] autorelease]; + return EditorCompHolder::createViewFor (filter, au, editorComp); } @end diff --git a/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h b/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h index 0f73543589..b7678582f2 100644 --- a/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h +++ b/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h @@ -97,10 +97,6 @@ #error "You need to define the JucePlugin_AUCocoaViewClassName value!" #endif -#if (defined(__APPLE_CPP__) || defined(__APPLE_CC__)) && ! defined (JUCE_ObjCExtraSuffix) - #error "To avoid objective-C name clashes with other plugins, you need to define the JUCE_ObjCExtraSuffix value as a global definition for your project!" -#endif - #if JucePlugin_Build_LV2 && ! defined (JucePlugin_LV2URI) #error "You need to define the JucePlugin_LV2URI value!" #endif diff --git a/modules/juce_audio_plugin_client/utility/juce_IncludeSystemHeaders.h b/modules/juce_audio_plugin_client/utility/juce_IncludeSystemHeaders.h index 92bce512e9..52cf6b6ef3 100644 --- a/modules/juce_audio_plugin_client/utility/juce_IncludeSystemHeaders.h +++ b/modules/juce_audio_plugin_client/utility/juce_IncludeSystemHeaders.h @@ -61,4 +61,7 @@ #else #include #endif + #include + #include + #include #endif diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp index 0b7f906d7d..bd5aa4c81a 100644 --- a/modules/juce_core/juce_core.cpp +++ b/modules/juce_core/juce_core.cpp @@ -152,7 +152,6 @@ namespace juce //============================================================================== #if JUCE_MAC || JUCE_IOS #include "native/juce_osx_ObjCHelpers.h" -#include "native/juce_mac_ObjCSuffix.h" #endif #if JUCE_ANDROID diff --git a/modules/juce_core/native/juce_mac_Network.mm b/modules/juce_core/native/juce_mac_Network.mm index fb8a1f0088..79e576b5c4 100644 --- a/modules/juce_core/native/juce_mac_Network.mm +++ b/modules/juce_core/native/juce_mac_Network.mm @@ -98,236 +98,213 @@ bool Process::openEmailWithAttachments (const String& targetEmailAddress, } //============================================================================== -} // (juce namespace) - -using namespace juce; - -//============================================================================== -#define JuceURLConnection MakeObjCClassName(JuceURLConnection) - -@interface JuceURLConnection : NSObject -{ -@public - NSURLRequest* request; - NSURLConnection* connection; - NSMutableData* data; - Thread* runLoopThread; - bool initialised, hasFailed, hasFinished; - int position; - int64 contentLength; - NSDictionary* headers; - NSLock* dataLock; -} - -- (JuceURLConnection*) initWithRequest: (NSURLRequest*) req withCallback: (URL::OpenStreamProgressCallback*) callback withContext: (void*) context; -- (void) dealloc; -- (void) connection: (NSURLConnection*) connection didReceiveResponse: (NSURLResponse*) response; -- (void) connection: (NSURLConnection*) connection didFailWithError: (NSError*) error; -- (void) connection: (NSURLConnection*) connection didReceiveData: (NSData*) data; -- (void) connectionDidFinishLoading: (NSURLConnection*) connection; - -- (BOOL) isOpen; -- (int) read: (char*) dest numBytes: (int) num; -- (int) readPosition; -- (void) stop; -- (void) createConnection; - -@end - -class JuceURLConnectionMessageThread : public Thread +class URLConnectionState : public Thread { public: - JuceURLConnectionMessageThread (JuceURLConnection* owner_) + URLConnectionState (NSObject* owner_, NSURLRequest* req) : Thread ("http connection"), - owner (owner_) + contentLength (-1), + owner (owner_), + request ([req retain]), + connection (nil), + data ([[NSMutableData data] retain]), + headers (nil), + initialised (false), + hasFailed (false), + hasFinished (false) { } - ~JuceURLConnectionMessageThread() + ~URLConnectionState() { stopThread (10000); + [connection release]; + [data release]; + [request release]; + [headers release]; + } + + bool start (URL::OpenStreamProgressCallback* callback, void* context) + { + startThread(); + + while (isThreadRunning() && ! initialised) + { + if (callback != nullptr) + callback (context, -1, (int) [[request HTTPBody] length]); + + Thread::sleep (1); + } + + return connection != nil && ! hasFailed; + } + + void stop() + { + [connection cancel]; + stopThread (10000); + } + + int read (char* dest, int numBytes) + { + int numDone = 0; + + while (numBytes > 0) + { + const int available = jmin (numBytes, (int) [data length]); + + if (available > 0) + { + const ScopedLock sl (dataLock); + [data getBytes: dest length: available]; + [data replaceBytesInRange: NSMakeRange (0, available) withBytes: nil length: 0]; + + numDone += available; + numBytes -= available; + dest += available; + } + else + { + if (hasFailed || hasFinished) + break; + + Thread::sleep (1); + } + } + + return numDone; + } + + void didReceiveResponse (NSURLResponse* response) + { + { + const ScopedLock sl (dataLock); + [data setLength: 0]; + } + + initialised = true; + contentLength = [response expectedContentLength]; + + [headers release]; + headers = nil; + + if ([response isKindOfClass: [NSHTTPURLResponse class]]) + headers = [[((NSHTTPURLResponse*) response) allHeaderFields] retain]; + } + + void didFailWithError (NSError* error) + { + DBG (nsStringToJuce ([error description])); (void) error; + hasFailed = true; + initialised = true; + signalThreadShouldExit(); + } + + void didReceiveData (NSData* newData) + { + const ScopedLock sl (dataLock); + [data appendData: newData]; + initialised = true; + } + + void finishedLoading() + { + hasFinished = true; + initialised = true; + signalThreadShouldExit(); } void run() { - [owner createConnection]; + NSUInteger oldRetainCount = [owner retainCount]; + connection = [[NSURLConnection alloc] initWithRequest: request + delegate: owner]; - while (! threadShouldExit()) + if (oldRetainCount == [owner retainCount]) + [owner retain]; // newer SDK should already retain this, but there were problems in older versions.. + + if (connection != nil) { - JUCE_AUTORELEASEPOOL - [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; + while (! threadShouldExit()) + { + JUCE_AUTORELEASEPOOL + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; + } } } + int64 contentLength; + CriticalSection dataLock; + NSObject* owner; + NSURLRequest* request; + NSURLConnection* connection; + NSMutableData* data; + NSDictionary* headers; + bool initialised, hasFailed, hasFinished; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState); +}; + +//============================================================================== +struct URLConnectionDelegateClass : public ObjCClass +{ + URLConnectionDelegateClass() : ObjCClass ("JUCEAppDelegate_") + { + addIvar ("state"); + + addMethod (@selector (dealloc), dealloc, "v@:"); + addMethod (@selector (connection:didReceiveResponse:), didReceiveResponse, "v@:@@"); + addMethod (@selector (connection:didFailWithError:), didFailWithError, "v@:@@"); + addMethod (@selector (connection:didReceiveData:), didReceiveData, "v@:@@"); + addMethod (@selector (connectionDidFinishLoading:), connectionDidFinishLoading, "v@:@"); + + registerClass(); + } + + static void setState (id self, URLConnectionState* state) + { + object_setInstanceVariable (self, "state", state); + } + + static URLConnectionState* getState (id self) + { + return getIvar (self, "state"); + } + private: - JuceURLConnection* owner; + static void dealloc (id self, SEL sel) + { + getState (self)->stop(); + delete getState (self); + sendSuperclassMessage (self, @selector (dealloc)); + } + + static void didReceiveResponse (id self, SEL, NSURLConnection*, NSURLResponse* response) + { + getState (self)->didReceiveResponse (response); + } + + static void didFailWithError (id self, SEL, NSURLConnection*, NSError* error) + { + getState (self)->didFailWithError (error); + } + + static void didReceiveData (id self, SEL, NSURLConnection*, NSData* newData) + { + getState (self)->didReceiveData (newData); + } + + static void connectionDidFinishLoading (id self, SEL, NSURLConnection*) + { + getState (self)->finishedLoading(); + } }; - -@implementation JuceURLConnection - -- (JuceURLConnection*) initWithRequest: (NSURLRequest*) req - withCallback: (URL::OpenStreamProgressCallback*) callback - withContext: (void*) context; -{ - [super init]; - request = req; - [request retain]; - data = [[NSMutableData data] retain]; - dataLock = [[NSLock alloc] init]; - connection = nil; - initialised = false; - hasFailed = false; - hasFinished = false; - contentLength = -1; - headers = nil; - - runLoopThread = new JuceURLConnectionMessageThread (self); - runLoopThread->startThread(); - - while (runLoopThread->isThreadRunning() && ! initialised) - { - if (callback != nullptr) - callback (context, -1, (int) [[request HTTPBody] length]); - - Thread::sleep (1); - } - - return self; -} - -- (void) dealloc -{ - [self stop]; - - deleteAndZero (runLoopThread); - [connection release]; - [data release]; - [dataLock release]; - [request release]; - [headers release]; - [super dealloc]; -} - -- (void) createConnection -{ - NSUInteger oldRetainCount = [self retainCount]; - connection = [[NSURLConnection alloc] initWithRequest: request - delegate: self]; - - if (oldRetainCount == [self retainCount]) - [self retain]; // newer SDK should already retain this, but there were problems in older versions.. - - if (connection == nil) - runLoopThread->signalThreadShouldExit(); -} - -- (void) connection: (NSURLConnection*) conn didReceiveResponse: (NSURLResponse*) response -{ - (void) conn; - [dataLock lock]; - [data setLength: 0]; - [dataLock unlock]; - initialised = true; - contentLength = [response expectedContentLength]; - - [headers release]; - headers = nil; - - if ([response isKindOfClass: [NSHTTPURLResponse class]]) - headers = [[((NSHTTPURLResponse*) response) allHeaderFields] retain]; -} - -- (void) connection: (NSURLConnection*) conn didFailWithError: (NSError*) error -{ - (void) conn; - DBG (nsStringToJuce ([error description])); - hasFailed = true; - initialised = true; - - if (runLoopThread != nullptr) - runLoopThread->signalThreadShouldExit(); -} - -- (void) connection: (NSURLConnection*) conn didReceiveData: (NSData*) newData -{ - (void) conn; - [dataLock lock]; - [data appendData: newData]; - [dataLock unlock]; - initialised = true; -} - -- (void) connectionDidFinishLoading: (NSURLConnection*) conn -{ - (void) conn; - hasFinished = true; - initialised = true; - - if (runLoopThread != nullptr) - runLoopThread->signalThreadShouldExit(); -} - -- (BOOL) isOpen -{ - return connection != nil && ! hasFailed; -} - -- (int) readPosition -{ - return position; -} - -- (int) read: (char*) dest numBytes: (int) numNeeded -{ - int numDone = 0; - - while (numNeeded > 0) - { - int available = jmin (numNeeded, (int) [data length]); - - if (available > 0) - { - [dataLock lock]; - [data getBytes: dest length: available]; - [data replaceBytesInRange: NSMakeRange (0, available) withBytes: nil length: 0]; - [dataLock unlock]; - - numDone += available; - numNeeded -= available; - dest += available; - } - else - { - if (hasFailed || hasFinished) - break; - - Thread::sleep (1); - } - } - - position += numDone; - return numDone; -} - -- (void) stop -{ - [connection cancel]; - - if (runLoopThread != nullptr) - runLoopThread->stopThread (10000); -} - -@end - -namespace juce -{ - //============================================================================== class WebInputStream : public InputStream { public: - //============================================================================== WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) @@ -338,14 +315,19 @@ public: JUCE_AUTORELEASEPOOL connection = createConnection (progressCallback, progressCallbackContext); - if (responseHeaders != nullptr && connection != nil && connection->headers != nil) + if (responseHeaders != nullptr && connection != nil) { - NSEnumerator* enumerator = [connection->headers keyEnumerator]; - NSString* key; + URLConnectionState* const state = URLConnectionDelegateClass::getState (connection); - while ((key = [enumerator nextObject]) != nil) - responseHeaders->set (nsStringToJuce (key), - nsStringToJuce ((NSString*) [connection->headers objectForKey: key])); + if (state->headers != nil) + { + NSEnumerator* enumerator = [state->headers keyEnumerator]; + NSString* key; + + while ((key = [enumerator nextObject]) != nil) + responseHeaders->set (nsStringToJuce (key), + nsStringToJuce ((NSString*) [state->headers objectForKey: key])); + } } } @@ -356,7 +338,7 @@ public: //============================================================================== bool isError() const { return connection == nil; } - int64 getTotalLength() { return connection == nil ? -1 : connection->contentLength; } + int64 getTotalLength() { return connection == nil ? -1 : URLConnectionDelegateClass::getState (connection)->contentLength; } bool isExhausted() { return finished; } int64 getPosition() { return position; } @@ -371,7 +353,10 @@ public: else { JUCE_AUTORELEASEPOOL - const int bytesRead = [connection read: static_cast (buffer) numBytes: bytesToRead]; + + URLConnectionState* const state = URLConnectionDelegateClass::getState (connection); + + const int bytesRead = state->read (static_cast (buffer), bytesToRead); position += bytesRead; if (bytesRead == 0) @@ -402,7 +387,7 @@ public: //============================================================================== private: - JuceURLConnection* connection; + NSObject* connection; String address, headers; MemoryBlock postData; int64 position; @@ -412,13 +397,16 @@ private: void close() { - [connection stop]; - [connection release]; - connection = nil; + if (connection != nil) + { + URLConnectionDelegateClass::getState (connection)->stop(); + [connection release]; + connection = nil; + } } - JuceURLConnection* createConnection (URL::OpenStreamProgressCallback* progressCallback, - void* progressCallbackContext) + NSObject* createConnection (URL::OpenStreamProgressCallback* progressCallback, + void* progressCallbackContext) { NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (address)] cachePolicy: NSURLRequestReloadIgnoringLocalCacheData @@ -447,11 +435,13 @@ private: [req setHTTPBody: [NSData dataWithBytes: postData.getData() length: postData.getSize()]]; - JuceURLConnection* const s = [[JuceURLConnection alloc] initWithRequest: req - withCallback: progressCallback - withContext: progressCallbackContext]; + static URLConnectionDelegateClass cls; + NSObject* const s = [cls.createInstance() init]; - if ([s isOpen]) + URLConnectionState* state = new URLConnectionState (s, req); + URLConnectionDelegateClass::setState (s, state); + + if (state->start (progressCallback, progressCallbackContext)) return s; [s release]; diff --git a/modules/juce_core/native/juce_mac_ObjCSuffix.h b/modules/juce_core/native/juce_mac_ObjCSuffix.h deleted file mode 100644 index 835825ef68..0000000000 --- a/modules/juce_core/native/juce_mac_ObjCSuffix.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-11 by Raw Material Software Ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the GNU General - Public License (Version 2), as published by the Free Software Foundation. - A copy of the license is included in the JUCE distribution, or can be found - online 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.rawmaterialsoftware.com/juce for more information. - - ============================================================================== -*/ - -#ifndef __JUCE_MAC_OBJCSUFFIX_JUCEHEADER__ -#define __JUCE_MAC_OBJCSUFFIX_JUCEHEADER__ - -/** This suffix is used for naming all Obj-C classes that are used inside juce. - - Because of the flat naming structure used by Obj-C, you can get horrible situations where - two DLLs are loaded into a host, each of which uses classes with the same names, and these get - cross-linked so that when you make a call to a class that you thought was private, it ends up - actually calling into a similarly named class in the other module's address space. - - By changing this macro to a unique value, you ensure that all the obj-C classes in your app - have unique names, and should avoid this problem. - - If you're using the amalgamated version, you can just set this macro to something unique before - you include juce_amalgamated.cpp. -*/ -#ifndef JUCE_ObjCExtraSuffix - #define JUCE_ObjCExtraSuffix 3 -#endif - -#ifndef DOXYGEN - #define appendMacro1(a, b, c, d, e) a ## _ ## b ## _ ## c ## _ ## d ## _ ## e - #define appendMacro2(a, b, c, d, e) appendMacro1(a, b, c, d, e) - #define MakeObjCClassName(rootName) appendMacro2 (rootName, JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER, JUCE_ObjCExtraSuffix) -#endif - -#endif // __JUCE_MAC_OBJCSUFFIX_JUCEHEADER__ diff --git a/modules/juce_core/native/juce_osx_ObjCHelpers.h b/modules/juce_core/native/juce_osx_ObjCHelpers.h index 59f8248430..e77f5a2ce9 100644 --- a/modules/juce_core/native/juce_osx_ObjCHelpers.h +++ b/modules/juce_core/native/juce_osx_ObjCHelpers.h @@ -55,18 +55,27 @@ namespace } //============================================================================== -class ObjCClassBuilder +template +struct ObjCClass { -public: - ObjCClassBuilder (Class superClass, const String& name) - : cls (objc_allocateClassPair (superClass, name.toUTF8(), 0)) + ObjCClass (const char* nameRoot) + : cls (objc_allocateClassPair ([SuperclassType class], getRandomisedName (nameRoot).toUTF8(), 0)) { } - Class getClass() + ~ObjCClass() + { + objc_disposeClassPair (cls); + } + + void registerClass() { objc_registerClassPair (cls); - return cls; + } + + SuperclassType* createInstance() const + { + return class_createInstance (cls, 0); } template @@ -83,21 +92,54 @@ public: jassert (b); (void) b; } + template + void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2) + { + addMethod (selector, callbackFn, (String (sig1) + sig2).toUTF8()); + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2, const char* sig3) + { + addMethod (selector, callbackFn, (String (sig1) + sig2 + sig3).toUTF8()); + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2, const char* sig3, const char* sig4) + { + addMethod (selector, callbackFn, (String (sig1) + sig2 + sig3 + sig4).toUTF8()); + } + + void addProtocol (Protocol* protocol) + { + BOOL b = class_addProtocol (cls, protocol); + jassert (b); (void) b; + } + + Class cls; + static id sendSuperclassMessage (id self, SEL selector) { - objc_super s = { self, [NSObject class] }; + objc_super s = { self, [SuperclassType class] }; return objc_msgSendSuper (&s, selector); } + template + static Type getIvar (id self, const char* name) + { + Type v = Type(); + object_getInstanceVariable (self, name, (void**) &v); + return v; + } + +private: static String getRandomisedName (const char* root) { return root + String::toHexString (Random::getSystemRandom().nextInt64()); } -private: - Class cls; - - JUCE_DECLARE_NON_COPYABLE (ObjCClassBuilder); + JUCE_DECLARE_NON_COPYABLE (ObjCClass); }; + #endif // __JUCE_OSX_OBJCHELPERS_JUCEHEADER__ diff --git a/modules/juce_events/juce_events.cpp b/modules/juce_events/juce_events.cpp index de42626ae2..4ea7ccf8e8 100644 --- a/modules/juce_events/juce_events.cpp +++ b/modules/juce_events/juce_events.cpp @@ -81,7 +81,6 @@ namespace juce //============================================================================== #if JUCE_MAC #include "../juce_core/native/juce_osx_ObjCHelpers.h" - #include "../juce_core/native/juce_mac_ObjCSuffix.h" #include "native/juce_osx_MessageQueue.h" #include "native/juce_mac_MessageManager.mm" diff --git a/modules/juce_events/native/juce_mac_MessageManager.mm b/modules/juce_events/native/juce_mac_MessageManager.mm index 477f8baf6b..3cf25273cb 100644 --- a/modules/juce_events/native/juce_mac_MessageManager.mm +++ b/modules/juce_events/native/juce_mac_MessageManager.mm @@ -30,33 +30,24 @@ typedef bool (*CheckEventBlockedByModalComps) (NSEvent*); CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; //============================================================================== -struct AppDelegateClass +struct AppDelegateClass : public ObjCClass { - static Class createClass() + AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") { - ObjCClassBuilder c ([NSObject class], ObjCClassBuilder::getRandomisedName ("JUCEAppDelegate_")); + addMethod (@selector (init), init, "@@:"); + addMethod (@selector (dealloc), dealloc, "v@:"); + addMethod (@selector (unregisterObservers), unregisterObservers, "v@:"); + addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); + addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); + addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); + addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); + addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); + addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); + addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); + addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); + addMethod (@selector (dummyMethod), dummyMethod, "v@:"); - c.addMethod (@selector (init), init, "@@:"); - c.addMethod (@selector (dealloc), dealloc, "v@:"); - c.addMethod (@selector (unregisterObservers), unregisterObservers, "v@:"); - c.addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); - c.addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); - c.addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); - c.addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); - c.addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); - c.addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); - c.addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); - c.addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); - c.addMethod (@selector (dummyMethod), dummyMethod, "v@:"); - - return c.getClass(); - } - - static NSObject* createInstance() - { - static Class c = createClass(); - jassert (c != nullptr); - return class_createInstance (c, 0); + registerClass(); } static NSString* getBroacastEventName() @@ -64,10 +55,10 @@ struct AppDelegateClass return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); } - //============================================================================== +private: static id init (id self, SEL) { - self = ObjCClassBuilder::sendSuperclassMessage (self, @selector (init)); + self = sendSuperclassMessage (self, @selector (init)); if (JUCEApplicationBase::isStandaloneApp()) { @@ -97,7 +88,7 @@ struct AppDelegateClass static void dealloc (id self, SEL) { - ObjCClassBuilder::sendSuperclassMessage (self, @selector (dealloc)); + sendSuperclassMessage (self, @selector (dealloc)); } static void unregisterObservers (id self, SEL) @@ -265,8 +256,10 @@ struct AppDelegateHolder { public: AppDelegateHolder() - : delegate ([AppDelegateClass::createInstance() init]) - {} + { + static AppDelegateClass cls; + delegate = [cls.createInstance() init]; + } ~AppDelegateHolder() { diff --git a/modules/juce_graphics/juce_graphics.cpp b/modules/juce_graphics/juce_graphics.cpp index 8599a28fa3..c05e75e0ef 100644 --- a/modules/juce_graphics/juce_graphics.cpp +++ b/modules/juce_graphics/juce_graphics.cpp @@ -111,7 +111,6 @@ namespace juce //============================================================================== #if JUCE_MAC || JUCE_IOS #include "../juce_core/native/juce_osx_ObjCHelpers.h" - #include "../juce_core/native/juce_mac_ObjCSuffix.h" #include "native/juce_mac_CoreGraphicsHelpers.h" #include "native/juce_mac_Fonts.mm" #include "native/juce_mac_CoreGraphicsContext.mm" diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp index c495af7fb4..7a7786d711 100644 --- a/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/modules/juce_gui_basics/juce_gui_basics.cpp @@ -256,7 +256,6 @@ namespace juce #if JUCE_MAC || JUCE_IOS #include "../juce_core/native/juce_osx_ObjCHelpers.h" - #include "../juce_core/native/juce_mac_ObjCSuffix.h" #include "../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" #include "../juce_graphics/native/juce_mac_CoreGraphicsContext.h" diff --git a/modules/juce_gui_basics/native/juce_mac_FileChooser.mm b/modules/juce_gui_basics/native/juce_mac_FileChooser.mm index 9aa153a41f..d7bae0982b 100644 --- a/modules/juce_gui_basics/native/juce_mac_FileChooser.mm +++ b/modules/juce_gui_basics/native/juce_mac_FileChooser.mm @@ -25,81 +25,69 @@ #if JUCE_MAC -} // (juce namespace) - -using namespace juce; - -#define JuceFileChooserDelegate MakeObjCClassName(JuceFileChooserDelegate) - -#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 -@interface JuceFileChooserDelegate : NSObject -#else -@interface JuceFileChooserDelegate : NSObject -#endif +struct FileChooserDelegateClass : public ObjCClass { - StringArray* filters; -} - -- (JuceFileChooserDelegate*) initWithFilters: (StringArray*) filters_; -- (void) dealloc; -- (BOOL) panel: (id) sender shouldShowFilename: (NSString*) filename; - -@end - -@implementation JuceFileChooserDelegate -- (JuceFileChooserDelegate*) initWithFilters: (StringArray*) filters_ -{ - [super init]; - filters = filters_; - return self; -} - -- (void) dealloc -{ - delete filters; - [super dealloc]; -} - -- (BOOL) panel: (id) sender shouldShowFilename: (NSString*) filename -{ - (void) sender; - const File f (nsStringToJuce (filename)); - - for (int i = filters->size(); --i >= 0;) - if (f.getFileName().matchesWildcard ((*filters)[i], true)) - return true; - - #if (! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 - NSError* error; - NSString* name = [[NSWorkspace sharedWorkspace] typeOfFile: filename error: &error]; - - if ([name isEqualToString: nsStringLiteral ("com.apple.alias-file")]) + FileChooserDelegateClass() : ObjCClass ("JUCEFileChooser_") { - FSRef ref; - FSPathMakeRef ((const UInt8*) [filename fileSystemRepresentation], &ref, nullptr); + addIvar ("filters"); - Boolean targetIsFolder = false, wasAliased = false; - FSResolveAliasFileWithMountFlags (&ref, true, &targetIsFolder, &wasAliased, 0); + addMethod (@selector (initWithFilters:), initWithFilters, "@@:^v"); + addMethod (@selector (dealloc), dealloc, "v@:"); + addMethod (@selector (panel:shouldShowFilename:), shouldShowFilename, "c@:@@"); - return wasAliased && targetIsFolder; + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + addProtocol (@protocol (NSOpenSavePanelDelegate)); + #endif + + registerClass(); } - #endif - return f.isDirectory() - && ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: filename]; -} -@end +private: + static id initWithFilters (id self, SEL, StringArray* filters) + { + self = sendSuperclassMessage (self, @selector (init)); + object_setInstanceVariable (self, "filters", filters); + return self; + } + static void dealloc (id self, SEL) + { + delete getIvar (self, "filters"); + sendSuperclassMessage (self, @selector (dealloc)); + } -namespace juce -{ + static BOOL shouldShowFilename (id self, SEL, id /*sender*/, NSString* filename) + { + StringArray* const filters = getIvar (self, "filters"); + + const File f (nsStringToJuce (filename)); + + for (int i = filters->size(); --i >= 0;) + if (f.getFileName().matchesWildcard ((*filters)[i], true)) + return true; + + #if (! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 + NSError* error; + NSString* name = [[NSWorkspace sharedWorkspace] typeOfFile: filename error: &error]; + + if ([name isEqualToString: nsStringLiteral ("com.apple.alias-file")]) + { + FSRef ref; + FSPathMakeRef ((const UInt8*) [filename fileSystemRepresentation], &ref, nullptr); + + Boolean targetIsFolder = false, wasAliased = false; + FSResolveAliasFileWithMountFlags (&ref, true, &targetIsFolder, &wasAliased, 0); + + return wasAliased && targetIsFolder; + } + #endif + + return f.isDirectory() + && ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: filename]; + } +}; //============================================================================== -bool FileChooser::isPlatformDialogAvailable() -{ - return true; -} - class TemporaryMainMenuWithStandardCommands { public: @@ -127,7 +115,7 @@ public: [item release]; item = [[NSApp mainMenu] addItemWithTitle: NSLocalizedString (nsStringLiteral ("Edit"), nil) - action: nil keyEquivalent: nsEmptyString()]; + action: nil keyEquivalent: nsEmptyString()]; [[NSApp mainMenu] setSubmenu: menu forItem: item]; [menu release]; } @@ -141,6 +129,7 @@ private: MenuBarModel* oldMenu; }; +//============================================================================== void FileChooser::showPlatformDialog (Array& results, const String& title, const File& currentFileOrDirectory, @@ -161,8 +150,15 @@ void FileChooser::showPlatformDialog (Array& results, filters->trim(); filters->removeEmptyStrings(); - JuceFileChooserDelegate* delegate = [[JuceFileChooserDelegate alloc] initWithFilters: filters]; - [delegate autorelease]; + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + typedef NSObject DelegateType; + #else + typedef NSObject DelegateType; + #endif + + static FileChooserDelegateClass cls; + DelegateType* delegate = [[cls.createInstance() performSelector: @selector (initWithFilters:) + withObject: (id) filters] autorelease]; NSSavePanel* panel = isSaveDialogue ? [NSSavePanel savePanel] : [NSOpenPanel openPanel]; @@ -222,6 +218,11 @@ void FileChooser::showPlatformDialog (Array& results, [panel setDelegate: nil]; } +bool FileChooser::isPlatformDialogAvailable() +{ + return true; +} + #else //============================================================================== diff --git a/modules/juce_gui_basics/native/juce_mac_MainMenu.mm b/modules/juce_gui_basics/native/juce_mac_MainMenu.mm index 5879843a72..deb2ee172f 100644 --- a/modules/juce_gui_basics/native/juce_mac_MainMenu.mm +++ b/modules/juce_gui_basics/native/juce_mac_MainMenu.mm @@ -23,43 +23,17 @@ ============================================================================== */ -class JuceMainMenuHandler; - -} // (juce namespace) - -using namespace juce; - -#define JuceMenuCallback MakeObjCClassName(JuceMenuCallback) - -#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 -@interface JuceMenuCallback : NSObject -#else -@interface JuceMenuCallback : NSObject -#endif -{ - JuceMainMenuHandler* owner; -} - -- (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_; -- (void) dealloc; -- (void) menuItemInvoked: (id) menu; -- (void) menuNeedsUpdate: (NSMenu*) menu; -@end - -namespace juce -{ - -//============================================================================== class JuceMainMenuHandler : private MenuBarModel::Listener, private DeletedAtShutdown { public: - //============================================================================== JuceMainMenuHandler() : currentModel (nullptr), lastUpdateTime (0) { - callback = [[JuceMenuCallback alloc] initWithOwner: this]; + static JuceMenuCallbackClass cls; + callback = [cls.createInstance() performSelector: @selector (initWithOwner:) + withObject: (id) this]; } ~JuceMainMenuHandler() @@ -234,17 +208,38 @@ public: } else if (iter.subMenu != nullptr) { - NSMenuItem* item = [menuToAddTo addItemWithTitle: text - action: nil - keyEquivalent: nsEmptyString()]; + if (iter.itemName.containsIgnoreCase ("recent")) + { + NSMenuItem* item = [menuToAddTo addItemWithTitle: NSLocalizedString (@"Open Recent", nil) + action: nil + keyEquivalent: @""]; - [item setTag: iter.itemId]; - [item setEnabled: iter.isEnabled]; + NSMenu* openRecentMenu = [[[NSMenu alloc] initWithTitle: @"Open Recent"] autorelease]; + [openRecentMenu performSelector: @selector(_setMenuName:) + withObject: @"NSRecentDocumentsMenu"]; - NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex); - [sub setDelegate: nil]; - [menuToAddTo setSubmenu: sub forItem: item]; - [sub release]; + [menuToAddTo setSubmenu: openRecentMenu forItem: item]; + + item = [openRecentMenu addItemWithTitle: NSLocalizedString(@"Clear Menu", nil) + action: @selector(clearRecentDocuments:) + keyEquivalent: @""]; + + [openRecentMenu update]; + } + else + { + NSMenuItem* item = [menuToAddTo addItemWithTitle: text + action: nil + keyEquivalent: nsEmptyString()]; + + [item setTag: iter.itemId]; + [item setEnabled: iter.isEnabled]; + + NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex); + [sub setDelegate: nil]; + [menuToAddTo setSubmenu: sub forItem: item]; + [sub release]; + } } else { @@ -289,7 +284,7 @@ public: MenuBarModel* currentModel; uint32 lastUpdateTime; - JuceMenuCallback* callback; + NSObject* callback; private: //============================================================================== @@ -301,7 +296,7 @@ private: NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)]; [m setAutoenablesItems: false]; - [m setDelegate: callback]; + [m setDelegate: (id) callback]; for (PopupMenu::MenuItemIterator iter (menu); iter.next();) addMenuItem (iter, m, topLevelMenuId, topLevelIndex); @@ -385,8 +380,8 @@ private: void messageCallback() { - if (JuceMainMenuHandler::instance != nullptr) - JuceMainMenuHandler::instance->menuBarItemsChanged (nullptr); + if (instance != nullptr) + instance->menuBarItemsChanged (nullptr); } private: @@ -402,8 +397,8 @@ private: void messageCallback() { - if (JuceMainMenuHandler::instance != nullptr) - JuceMainMenuHandler::instance->invokeDirectly (commandId, topLevelIndex); + if (instance != nullptr) + instance->invokeDirectly (commandId, topLevelIndex); } private: @@ -411,74 +406,82 @@ private: JUCE_DECLARE_NON_COPYABLE (AsyncCommandInvoker); }; -}; -JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr; - -} // (juce namespace) - -//============================================================================== -@implementation JuceMenuCallback - -- (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_ -{ - [super init]; - owner = owner_; - return self; -} - -- (void) dealloc -{ - [super dealloc]; -} - -- (void) menuItemInvoked: (id) menu -{ - NSMenuItem* item = (NSMenuItem*) menu; - - if ([[item representedObject] isKindOfClass: [NSArray class]]) + //============================================================================== + struct JuceMenuCallbackClass : public ObjCClass { - // If the menu is being triggered by a keypress, the OS will have picked it up before we had a chance to offer it to - // our own components, which may have wanted to intercept it. So, rather than dispatching directly, we'll feed it back - // into the focused component and let it trigger the menu item indirectly. - NSEvent* e = [NSApp currentEvent]; - if ([e type] == NSKeyDown || [e type] == NSKeyUp) + JuceMenuCallbackClass() + : ObjCClass ("JUCEMainMenu_") { - if (juce::Component::getCurrentlyFocusedComponent() != nullptr) + addIvar ("owner"); + + addMethod (@selector (initWithOwner:), initWithOwner, "@@:^v"); + addMethod (@selector (menuItemInvoked:), menuItemInvoked, "v@:@"); + addMethod (@selector (menuNeedsUpdate:), menuNeedsUpdate, "v@:@"); + + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + addProtocol (@protocol (NSMenuDelegate)); + #endif + + registerClass(); + } + + private: + static id initWithOwner (id self, SEL, JuceMainMenuHandler* owner) + { + self = sendSuperclassMessage (self, @selector (init)); + object_setInstanceVariable (self, "owner", owner); + return self; + } + + static void menuItemInvoked (id self, SEL, id menu) + { + JuceMainMenuHandler* const owner = getIvar (self, "owner"); + + NSMenuItem* item = (NSMenuItem*) menu; + + if ([[item representedObject] isKindOfClass: [NSArray class]]) { - juce::NSViewComponentPeer* peer = dynamic_cast (juce::Component::getCurrentlyFocusedComponent()->getPeer()); - - if (peer != nullptr) + // If the menu is being triggered by a keypress, the OS will have picked it up before we had a chance to offer it to + // our own components, which may have wanted to intercept it. So, rather than dispatching directly, we'll feed it back + // into the focused component and let it trigger the menu item indirectly. + NSEvent* e = [NSApp currentEvent]; + if ([e type] == NSKeyDown || [e type] == NSKeyUp) { - if ([e type] == NSKeyDown) - peer->redirectKeyDown (e); - else - peer->redirectKeyUp (e); + if (juce::Component::getCurrentlyFocusedComponent() != nullptr) + { + juce::NSViewComponentPeer* peer = dynamic_cast (juce::Component::getCurrentlyFocusedComponent()->getPeer()); - return; + if (peer != nullptr) + { + if ([e type] == NSKeyDown) + peer->redirectKeyDown (e); + else + peer->redirectKeyUp (e); + + return; + } + } } + + NSArray* info = (NSArray*) [item representedObject]; + + owner->invoke ((int) [item tag], + (ApplicationCommandManager*) (pointer_sized_int) + [((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue], + (int) [((NSNumber*) [info objectAtIndex: 1]) intValue]); } } - NSArray* info = (NSArray*) [item representedObject]; + static void menuNeedsUpdate (id self, SEL, NSMenu* menu) + { + if (instance != nullptr) + instance->updateMenus (menu); + } + }; +}; - owner->invoke ((int) [item tag], - (ApplicationCommandManager*) (pointer_sized_int) - [((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue], - (int) [((NSNumber*) [info objectAtIndex: 1]) intValue]); - } -} - -- (void) menuNeedsUpdate: (NSMenu*) menu; -{ - if (JuceMainMenuHandler::instance != nullptr) - JuceMainMenuHandler::instance->updateMenus (menu); -} - -@end - -namespace juce -{ +JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr; //============================================================================== namespace MainMenuHelpers diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index 96f1bae27b..d364885340 100644 --- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -23,209 +23,814 @@ ============================================================================== */ -class NSViewComponentPeer; - typedef void (*AppFocusChangeCallback)(); extern AppFocusChangeCallback appFocusChangeCallback; typedef bool (*CheckEventBlockedByModalComps) (NSEvent*); extern CheckEventBlockedByModalComps isEventBlockedByModalComps; +//============================================================================== +#if ! (defined (MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7) } // (juce namespace) @interface NSEvent (JuceDeviceDelta) - - (CGFloat) deviceDeltaX; - - (CGFloat) deviceDeltaY; - -#if ! (defined (MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7) - - (CGFloat) scrollingDeltaX; - (CGFloat) scrollingDeltaX; + - (CGFloat) scrollingDeltaY; - (BOOL) hasPreciseScrollingDeltas; - (BOOL) isDirectionInvertedFromDevice; -#endif @end -#define JuceNSView MakeObjCClassName(JuceNSView) - -@interface JuceNSView : NSView -{ -@public - NSViewComponentPeer* owner; - NSNotificationCenter* notificationCenter; - String* stringBeingComposed; - bool textWasInserted; -} - -- (JuceNSView*) initWithOwner: (NSViewComponentPeer*) owner withFrame: (NSRect) frame; -- (void) dealloc; - -- (BOOL) isOpaque; -- (void) drawRect: (NSRect) r; - -- (void) mouseDown: (NSEvent*) ev; -- (void) asyncMouseDown: (NSEvent*) ev; -- (void) mouseUp: (NSEvent*) ev; -- (void) asyncMouseUp: (NSEvent*) ev; -- (void) mouseDragged: (NSEvent*) ev; -- (void) mouseMoved: (NSEvent*) ev; -- (void) mouseEntered: (NSEvent*) ev; -- (void) mouseExited: (NSEvent*) ev; -- (void) rightMouseDown: (NSEvent*) ev; -- (void) rightMouseDragged: (NSEvent*) ev; -- (void) rightMouseUp: (NSEvent*) ev; -- (void) otherMouseDown: (NSEvent*) ev; -- (void) otherMouseDragged: (NSEvent*) ev; -- (void) otherMouseUp: (NSEvent*) ev; -- (void) scrollWheel: (NSEvent*) ev; -- (BOOL) acceptsFirstMouse: (NSEvent*) ev; -- (void) frameChanged: (NSNotification*) n; -- (void) viewDidMoveToWindow; - -- (void) keyDown: (NSEvent*) ev; -- (void) keyUp: (NSEvent*) ev; - -// NSTextInput Methods -- (void) insertText: (id) aString; -- (void) doCommandBySelector: (SEL) aSelector; -- (void) setMarkedText: (id) aString selectedRange: (NSRange) selRange; -- (void) unmarkText; -- (BOOL) hasMarkedText; -- (long) conversationIdentifier; -- (NSAttributedString*) attributedSubstringFromRange: (NSRange) theRange; -- (NSRange) markedRange; -- (NSRange) selectedRange; -- (NSRect) firstRectForCharacterRange: (NSRange) theRange; -- (NSUInteger) characterIndexForPoint: (NSPoint) thePoint; -- (NSArray*) validAttributesForMarkedText; - -- (void) flagsChanged: (NSEvent*) ev; -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 -- (BOOL) performKeyEquivalent: (NSEvent*) ev; +namespace juce { #endif -- (BOOL) becomeFirstResponder; -- (BOOL) resignFirstResponder; -- (BOOL) acceptsFirstResponder; - -- (NSArray*) getSupportedDragTypes; -- (BOOL) sendDragCallback: (int) type sender: (id ) sender; -- (NSDragOperation) draggingEntered: (id ) sender; -- (NSDragOperation) draggingUpdated: (id ) sender; -- (void) draggingEnded: (id ) sender; -- (void) draggingExited: (id ) sender; -- (BOOL) prepareForDragOperation: (id ) sender; -- (BOOL) performDragOperation: (id ) sender; -- (void) concludeDragOperation: (id ) sender; - -@end - -//============================================================================== -#define JuceNSWindow MakeObjCClassName(JuceNSWindow) - -#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 -@interface JuceNSWindow : NSWindow -#else -@interface JuceNSWindow : NSWindow -#endif -{ -@private - NSViewComponentPeer* owner; - bool isZooming; -} - -- (void) setOwner: (NSViewComponentPeer*) owner; -- (BOOL) canBecomeKeyWindow; -- (void) becomeKeyWindow; -- (BOOL) windowShouldClose: (id) window; -- (NSRect) constrainFrameRect: (NSRect) frameRect toScreen: (NSScreen*) screen; -- (NSSize) windowWillResize: (NSWindow*) window toSize: (NSSize) proposedFrameSize; -- (void) zoom: (id) sender; -@end - -namespace juce -{ - //============================================================================== class NSViewComponentPeer : public ComponentPeer { public: - NSViewComponentPeer (Component* const component, - const int windowStyleFlags, - NSView* viewToAttachTo); + NSViewComponentPeer (Component* const comp, const int windowStyleFlags, NSView* viewToAttachTo) + : ComponentPeer (comp, windowStyleFlags), + window (nil), + view (nil), + isSharedWindow (viewToAttachTo != nil), + fullScreen (false), + insideDrawRect (false), + #if USE_COREGRAPHICS_RENDERING + usingCoreGraphics (true), + #else + usingCoreGraphics (false), + #endif + recursiveToFrontCall (false), + isZooming (false), + textWasInserted (false), + notificationCenter (nil) + { + appFocusChangeCallback = appFocusChanged; + isEventBlockedByModalComps = checkEventBlockedByModalComps; - ~NSViewComponentPeer(); + NSRect r = NSMakeRect (0, 0, (CGFloat) component->getWidth(), (CGFloat) component->getHeight()); + + view = [createViewInstance() initWithFrame: r]; + setOwner (view, this); + + [view registerForDraggedTypes: getSupportedDragTypes()]; + + notificationCenter = [NSNotificationCenter defaultCenter]; + + [notificationCenter addObserver: view + selector: @selector (frameChanged:) + name: NSViewFrameDidChangeNotification + object: view]; + + if (! isSharedWindow) + [notificationCenter addObserver: view + selector: @selector (frameChanged:) + name: NSWindowDidMoveNotification + object: window]; + + [view setPostsFrameChangedNotifications: YES]; + + if (isSharedWindow) + { + window = [viewToAttachTo window]; + [viewToAttachTo addSubview: view]; + } + else + { + r.origin.x = (CGFloat) component->getX(); + r.origin.y = (CGFloat) component->getY(); + r.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - (r.origin.y + r.size.height); + + window = [createWindowInstance() initWithContentRect: r + styleMask: getNSWindowStyleMask (windowStyleFlags) + backing: NSBackingStoreBuffered + defer: YES]; + + setOwner (window, this); + [window orderOut: nil]; + [window setDelegate: (id) window]; + [window setOpaque: component->isOpaque()]; + [window setHasShadow: ((windowStyleFlags & windowHasDropShadow) != 0)]; + + if (component->isAlwaysOnTop()) + [window setLevel: NSFloatingWindowLevel]; + + [window setContentView: view]; + [window setAutodisplay: YES]; + [window setAcceptsMouseMovedEvents: YES]; + + // We'll both retain and also release this on closing because plugin hosts can unexpectedly + // close the window for us, and also tend to get cause trouble if setReleasedWhenClosed is NO. + [window setReleasedWhenClosed: YES]; + [window retain]; + + [window setExcludedFromWindowsMenu: (windowStyleFlags & windowIsTemporary) != 0]; + [window setIgnoresMouseEvents: (windowStyleFlags & windowIgnoresMouseClicks) != 0]; + + #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7) + if ((windowStyleFlags & (windowHasMaximiseButton | windowHasTitleBar)) == (windowHasMaximiseButton | windowHasTitleBar)) + [window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary]; + #endif + } + + const float alpha = component->getAlpha(); + if (alpha < 1.0f) + setAlpha (alpha); + + setTitle (component->getName()); + } + + ~NSViewComponentPeer() + { + [notificationCenter removeObserver: view]; + setOwner (view, nullptr); + [view removeFromSuperview]; + [view release]; + + if (! isSharedWindow) + { + setOwner (window, nullptr); + [window close]; + [window release]; + } + } //============================================================================== - void* getNativeHandle() const; - void setVisible (bool shouldBeVisible); - void setTitle (const String& title); - void setPosition (int x, int y); - void setSize (int w, int h); - void setBounds (int x, int y, int w, int h, const bool isNowFullScreen); - Rectangle getBounds (const bool global) const; - Rectangle getBounds() const; - Point getScreenPosition() const; - Point localToGlobal (const Point& relativePosition); - Point globalToLocal (const Point& screenPosition); - void setAlpha (float newAlpha); - void setMinimised (bool shouldBeMinimised); - bool isMinimised() const; - void setFullScreen (bool shouldBeFullScreen); - bool isFullScreen() const; - void updateFullscreenStatus(); - bool contains (const Point& position, bool trueIfInAChildWindow) const; - bool hasNativeTitleBar() const { return (getStyleFlags() & windowHasTitleBar) != 0; } - BorderSize getFrameSize() const; - bool setAlwaysOnTop (bool alwaysOnTop); - void toFront (bool makeActiveWindow); - void toBehind (ComponentPeer* other); - void setIcon (const Image& newIcon); - StringArray getAvailableRenderingEngines(); - int getCurrentRenderingEngine() const; - void setCurrentRenderingEngine (int index); + void* getNativeHandle() const { return view; } - /* When you use multiple DLLs which share similarly-named obj-c classes - like - for example having more than one juce plugin loaded into a host, then when a - method is called, the actual code that runs might actually be in a different module - than the one you expect... So any calls to library functions or statics that are - made inside obj-c methods will probably end up getting executed in a different DLL's - memory space. Not a great thing to happen - this obviously leads to bizarre crashes. + void setVisible (bool shouldBeVisible) + { + if (isSharedWindow) + { + [view setHidden: ! shouldBeVisible]; + } + else + { + if (shouldBeVisible) + { + [window orderFront: nil]; + handleBroughtToFront(); + } + else + { + [window orderOut: nil]; + } + } + } - To work around this insanity, I'm only allowing obj-c methods to make calls to - virtual methods of an object that's known to live inside the right module's space. - */ - virtual void redirectMouseDown (NSEvent* ev); - virtual void redirectMouseUp (NSEvent* ev); - virtual void redirectMouseDrag (NSEvent* ev); - virtual void redirectMouseMove (NSEvent* ev); - virtual void redirectMouseEnter (NSEvent* ev); - virtual void redirectMouseExit (NSEvent* ev); - virtual void redirectMouseWheel (NSEvent* ev); - void sendMouseEvent (NSEvent* ev); + void setTitle (const String& title) + { + JUCE_AUTORELEASEPOOL + + if (! isSharedWindow) + [window setTitle: juceStringToNS (title)]; + } + + void setPosition (int x, int y) + { + setBounds (x, y, component->getWidth(), component->getHeight(), false); + } + + void setSize (int w, int h) + { + setBounds (component->getX(), component->getY(), w, h, false); + } + + void setBounds (int x, int y, int w, int h, bool isNowFullScreen) + { + fullScreen = isNowFullScreen; + + NSRect r = NSMakeRect ((CGFloat) x, (CGFloat) y, (CGFloat) jmax (0, w), (CGFloat) jmax (0, h)); + + if (isSharedWindow) + { + r.origin.y = [[view superview] frame].size.height - (r.origin.y + r.size.height); + + if ([view frame].size.width != r.size.width + || [view frame].size.height != r.size.height) + { + [view setNeedsDisplay: true]; + } + + [view setFrame: r]; + } + else + { + r.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - (r.origin.y + r.size.height); + + [window setFrame: [window frameRectForContentRect: r] + display: true]; + } + } + + Rectangle getBounds (const bool global) const + { + NSRect r = [view frame]; + + if (global && [view window] != nil) + { + r = [view convertRect: r toView: nil]; + NSRect wr = [[view window] frame]; + r.origin.x += wr.origin.x; + r.origin.y += wr.origin.y; + r.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - r.origin.y - r.size.height; + } + else + { + r.origin.y = [[view superview] frame].size.height - r.origin.y - r.size.height; + } + + return Rectangle (convertToRectInt (r)); + } + + Rectangle getBounds() const + { + return getBounds (! isSharedWindow); + } + + Point getScreenPosition() const + { + return getBounds (true).getPosition(); + } + + Point localToGlobal (const Point& relativePosition) + { + return relativePosition + getScreenPosition(); + } + + Point globalToLocal (const Point& screenPosition) + { + return screenPosition - getScreenPosition(); + } + + void setAlpha (float newAlpha) + { + if (! isSharedWindow) + { + [window setAlphaValue: (CGFloat) newAlpha]; + } + else + { + #if defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_5 + [view setAlphaValue: (CGFloat) newAlpha]; + #else + if ([view respondsToSelector: @selector (setAlphaValue:)]) + { + // PITA dynamic invocation for 10.4 builds.. + NSInvocation* inv = [NSInvocation invocationWithMethodSignature: [view methodSignatureForSelector: @selector (setAlphaValue:)]]; + [inv setSelector: @selector (setAlphaValue:)]; + [inv setTarget: view]; + CGFloat cgNewAlpha = (CGFloat) newAlpha; + [inv setArgument: &cgNewAlpha atIndex: 2]; + [inv invoke]; + } + #endif + } + } + + void setMinimised (bool shouldBeMinimised) + { + if (! isSharedWindow) + { + if (shouldBeMinimised) + [window miniaturize: nil]; + else + [window deminiaturize: nil]; + } + } + + bool isMinimised() const + { + return [window isMiniaturized]; + } + + void setFullScreen (bool shouldBeFullScreen) + { + if (! isSharedWindow) + { + Rectangle r (lastNonFullscreenBounds); + + if (isMinimised()) + setMinimised (false); + + if (fullScreen != shouldBeFullScreen) + { + if (shouldBeFullScreen && hasNativeTitleBar()) + { + fullScreen = true; + [window performZoom: nil]; + } + else + { + if (shouldBeFullScreen) + r = component->getParentMonitorArea(); + + // (can't call the component's setBounds method because that'll reset our fullscreen flag) + if (r != getComponent()->getBounds() && ! r.isEmpty()) + setBounds (r.getX(), r.getY(), r.getWidth(), r.getHeight(), shouldBeFullScreen); + } + } + } + } + + bool isFullScreen() const + { + return fullScreen; + } + + bool contains (const Point& position, bool trueIfInAChildWindow) const + { + if (! (isPositiveAndBelow (position.getX(), component->getWidth()) + && isPositiveAndBelow (position.getY(), component->getHeight()))) + return false; + + NSRect frameRect = [view frame]; + + NSView* v = [view hitTest: NSMakePoint (frameRect.origin.x + position.getX(), + frameRect.origin.y + frameRect.size.height - position.getY())]; + + if (trueIfInAChildWindow) + return v != nil; + + return v == view; + } + + BorderSize getFrameSize() const + { + BorderSize b; + + if (! isSharedWindow) + { + NSRect v = [view convertRect: [view frame] toView: nil]; + NSRect w = [window frame]; + + b.setTop ((int) (w.size.height - (v.origin.y + v.size.height))); + b.setBottom ((int) v.origin.y); + b.setLeft ((int) v.origin.x); + b.setRight ((int) (w.size.width - (v.origin.x + v.size.width))); + } + + return b; + } + + void updateFullscreenStatus() + { + if (hasNativeTitleBar()) + { + const Rectangle screen (getFrameSize().subtractedFrom (component->getParentMonitorArea())); + const Rectangle window (component->getScreenBounds()); + + fullScreen = std::abs (screen.getX() - window.getX()) <= 2 + && std::abs (screen.getY() - window.getY()) <= 2 + && std::abs (screen.getRight() - window.getRight()) <= 2 + && std::abs (screen.getBottom() - window.getBottom()) <= 2; + } + } + + bool hasNativeTitleBar() const + { + return (getStyleFlags() & windowHasTitleBar) != 0; + } + + bool setAlwaysOnTop (bool alwaysOnTop) + { + if (! isSharedWindow) + [window setLevel: alwaysOnTop ? NSFloatingWindowLevel + : NSNormalWindowLevel]; + return true; + } + + void toFront (bool makeActiveWindow) + { + if (isSharedWindow) + [[view superview] addSubview: view + positioned: NSWindowAbove + relativeTo: nil]; + + if (window != nil && component->isVisible()) + { + if (makeActiveWindow) + [window makeKeyAndOrderFront: nil]; + else + [window orderFront: nil]; + + if (! recursiveToFrontCall) + { + recursiveToFrontCall = true; + Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); + handleBroughtToFront(); + recursiveToFrontCall = false; + } + } + } + + void toBehind (ComponentPeer* other) + { + NSViewComponentPeer* const otherPeer = dynamic_cast (other); + jassert (otherPeer != nullptr); // wrong type of window? + + if (otherPeer != nullptr) + { + if (isSharedWindow) + { + [[view superview] addSubview: view + positioned: NSWindowBelow + relativeTo: otherPeer->view]; + } + else + { + [window orderWindow: NSWindowBelow + relativeTo: [otherPeer->window windowNumber]]; + } + } + } + + void setIcon (const Image&) + { + // to do.. + } + + StringArray getAvailableRenderingEngines() + { + StringArray s (ComponentPeer::getAvailableRenderingEngines()); + + #if USE_COREGRAPHICS_RENDERING + s.add ("CoreGraphics Renderer"); + #endif + + return s; + } + + int getCurrentRenderingEngine() const + { + return usingCoreGraphics ? 1 : 0; + } + + void setCurrentRenderingEngine (int index) + { + #if USE_COREGRAPHICS_RENDERING + if (usingCoreGraphics != (index > 0)) + { + usingCoreGraphics = index > 0; + [view setNeedsDisplay: true]; + } + #endif + } + + void redirectMouseDown (NSEvent* ev) + { + currentModifiers = currentModifiers.withFlags (getModifierForButtonNumber ([ev buttonNumber])); + sendMouseEvent (ev); + } + + void redirectMouseUp (NSEvent* ev) + { + currentModifiers = currentModifiers.withoutFlags (getModifierForButtonNumber ([ev buttonNumber])); + sendMouseEvent (ev); + showArrowCursorIfNeeded(); + } + + void redirectMouseDrag (NSEvent* ev) + { + currentModifiers = currentModifiers.withFlags (getModifierForButtonNumber ([ev buttonNumber])); + sendMouseEvent (ev); + } + + void redirectMouseMove (NSEvent* ev) + { + currentModifiers = currentModifiers.withoutMouseButtons(); + sendMouseEvent (ev); + showArrowCursorIfNeeded(); + } + + void redirectMouseEnter (NSEvent* ev) + { + Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); + currentModifiers = currentModifiers.withoutMouseButtons(); + sendMouseEvent (ev); + } + + void redirectMouseExit (NSEvent* ev) + { + currentModifiers = currentModifiers.withoutMouseButtons(); + sendMouseEvent (ev); + } + + void redirectMouseWheel (NSEvent* ev) + { + updateModifiers (ev); + + MouseWheelDetails wheel; + wheel.deltaX = 0; + wheel.deltaY = 0; + wheel.isReversed = false; + wheel.isSmooth = false; + + @try + { + #if defined (MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if ([ev respondsToSelector: @selector (isDirectionInvertedFromDevice)]) + wheel.isReversed = [ev isDirectionInvertedFromDevice]; + + if ([ev respondsToSelector: @selector (hasPreciseScrollingDeltas)]) + { + if ([ev hasPreciseScrollingDeltas]) + { + const float scale = 0.5f / 256.0f; + wheel.deltaX = [ev scrollingDeltaX] * scale; + wheel.deltaY = [ev scrollingDeltaY] * scale; + wheel.isSmooth = true; + } + } + else + #endif + if ([ev respondsToSelector: @selector (deviceDeltaX)]) + { + const float scale = 0.5f / 256.0f; + wheel.deltaX = scale * (float) objc_msgSend_fpret (ev, @selector (deviceDeltaX)); + wheel.deltaY = scale * (float) objc_msgSend_fpret (ev, @selector (deviceDeltaY)); + } + } + @catch (...) + {} + + if (wheel.deltaX == 0 && wheel.deltaY == 0) + { + const float scale = 10.0f / 256.0f; + wheel.deltaX = [ev deltaX] * scale; + wheel.deltaY = [ev deltaY] * scale; + } + + handleMouseWheel (0, getMousePos (ev, view), getMouseTime (ev), wheel); + } + + void sendMouseEvent (NSEvent* ev) + { + updateModifiers (ev); + handleMouseEvent (0, getMousePos (ev, view), currentModifiers, getMouseTime (ev)); + } + + bool handleKeyEvent (NSEvent* ev, bool isKeyDown) + { + String unicode (nsStringToJuce ([ev characters])); + String unmodified (nsStringToJuce ([ev charactersIgnoringModifiers])); + int keyCode = getKeyCodeFromEvent (ev); + + //DBG ("unicode: " + unicode + " " + String::toHexString ((int) unicode[0])); + //DBG ("unmodified: " + unmodified + " " + String::toHexString ((int) unmodified[0])); + + if (unicode.isNotEmpty() || keyCode != 0) + { + if (isKeyDown) + { + bool used = false; + + while (unicode.length() > 0) + { + juce_wchar textCharacter = unicode[0]; + unicode = unicode.substring (1); + + if (([ev modifierFlags] & NSCommandKeyMask) != 0) + textCharacter = 0; + + used = handleKeyUpOrDown (true) || used; + used = handleKeyPress (keyCode, textCharacter) || used; + } + + return used; + } + else + { + if (handleKeyUpOrDown (false)) + return true; + } + } + + return false; + } + + bool redirectKeyDown (NSEvent* ev) + { + updateKeysDown (ev, true); + bool used = handleKeyEvent (ev, true); + + if (([ev modifierFlags] & NSCommandKeyMask) != 0) + { + // for command keys, the key-up event is thrown away, so simulate one.. + updateKeysDown (ev, false); + used = (isValidPeer (this) && handleKeyEvent (ev, false)) || used; + } + + // (If we're running modally, don't allow unused keystrokes to be passed + // along to other blocked views..) + if (Component::getCurrentlyModalComponent() != nullptr) + used = true; + + return used; + } + + bool redirectKeyUp (NSEvent* ev) + { + updateKeysDown (ev, false); + return handleKeyEvent (ev, false) + || Component::getCurrentlyModalComponent() != nullptr; + } + + void redirectModKeyChange (NSEvent* ev) + { + keysCurrentlyDown.clear(); + handleKeyUpOrDown (true); + + updateModifiers (ev); + handleModifierKeysChange(); + } - bool handleKeyEvent (NSEvent* ev, bool isKeyDown); - virtual bool redirectKeyDown (NSEvent* ev); - virtual bool redirectKeyUp (NSEvent* ev); - virtual void redirectModKeyChange (NSEvent* ev); #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 - virtual bool redirectPerformKeyEquivalent (NSEvent* ev); + bool redirectPerformKeyEquivalent (NSEvent* ev) + { + if ([ev type] == NSKeyDown) + return redirectKeyDown (ev); + else if ([ev type] == NSKeyUp) + return redirectKeyUp (ev); + + return false; + } #endif - virtual bool isOpaque(); - virtual void drawRect (NSRect r); + bool isOpaque() + { + return component == nullptr || component->isOpaque(); + } - virtual bool canBecomeKeyWindow(); - virtual void becomeKeyWindow(); - virtual bool windowShouldClose(); + void drawRect (NSRect r) + { + if (r.size.width < 1.0f || r.size.height < 1.0f) + return; - virtual void redirectMovedOrResized(); - virtual void viewMovedToWindow(); + CGContextRef cg = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; - virtual NSRect constrainRect (NSRect r); + if (! component->isOpaque()) + CGContextClearRect (cg, CGContextGetClipBoundingBox (cg)); - static void showArrowCursorIfNeeded(); - static void updateModifiers (NSEvent* e); - static void updateModifiers (NSUInteger); - static void updateKeysDown (NSEvent* ev, bool isKeyDown); + #if USE_COREGRAPHICS_RENDERING + if (usingCoreGraphics) + { + CoreGraphicsContext context (cg, (float) [view frame].size.height); + + insideDrawRect = true; + handlePaint (context); + insideDrawRect = false; + } + else + #endif + { + const int xOffset = -roundToInt (r.origin.x); + const int yOffset = -roundToInt ([view frame].size.height - (r.origin.y + r.size.height)); + const int clipW = (int) (r.size.width + 0.5f); + const int clipH = (int) (r.size.height + 0.5f); + + RectangleList clip; + getClipRects (clip, xOffset, yOffset, clipW, clipH); + + if (! clip.isEmpty()) + { + Image temp (getComponent()->isOpaque() ? Image::RGB : Image::ARGB, + clipW, clipH, ! getComponent()->isOpaque()); + + { + ScopedPointer context (component->getLookAndFeel() + .createGraphicsContext (temp, Point (xOffset, yOffset), clip)); + + insideDrawRect = true; + handlePaint (*context); + insideDrawRect = false; + } + + CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); + CGImageRef image = juce_createCoreGraphicsImage (temp, false, colourSpace, false); + CGColorSpaceRelease (colourSpace); + CGContextDrawImage (cg, CGRectMake (r.origin.x, r.origin.y, clipW, clipH), image); + CGImageRelease (image); + } + } + } + + bool canBecomeKeyWindow() + { + return (getStyleFlags() & juce::ComponentPeer::windowIgnoresKeyPresses) == 0; + } + + void becomeKeyWindow() + { + handleBroughtToFront(); + grabFocus(); + } + + bool windowShouldClose() + { + if (! isValidPeer (this)) + return YES; + + handleUserClosingWindow(); + return NO; + } + + void redirectMovedOrResized() + { + updateFullscreenStatus(); + handleMovedOrResized(); + } + + void viewMovedToWindow() + { + if (isSharedWindow) + window = [view window]; + } + + NSRect constrainRect (NSRect r) + { + if (constrainer != nullptr + #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7) + && ([window styleMask] & NSFullScreenWindowMask) == 0 + #endif + ) + { + NSRect current = [window frame]; + current.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - current.origin.y - current.size.height; + + r.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - r.origin.y - r.size.height; + + Rectangle pos (convertToRectInt (r)); + Rectangle original (convertToRectInt (current)); + const Rectangle screenBounds (Desktop::getInstance().getDisplays().getTotalBounds (true)); + + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_6 + if ([window inLiveResize]) + #else + if ([window respondsToSelector: @selector (inLiveResize)] + && [window performSelector: @selector (inLiveResize)]) + #endif + { + constrainer->checkBounds (pos, original, screenBounds, + false, false, true, true); + } + else + { + constrainer->checkBounds (pos, original, screenBounds, + pos.getY() != original.getY() && pos.getBottom() == original.getBottom(), + pos.getX() != original.getX() && pos.getRight() == original.getRight(), + pos.getY() == original.getY() && pos.getBottom() != original.getBottom(), + pos.getX() == original.getX() && pos.getRight() != original.getRight()); + } + + r.origin.x = pos.getX(); + r.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - r.size.height - pos.getY(); + r.size.width = pos.getWidth(); + r.size.height = pos.getHeight(); + } + + return r; + } + + static void showArrowCursorIfNeeded() + { + MouseInputSource& mouse = Desktop::getInstance().getMainMouseSource(); + + if (mouse.getComponentUnderMouse() == nullptr + && Desktop::getInstance().findComponentAt (mouse.getScreenPosition()) == nullptr) + { + [[NSCursor arrowCursor] set]; + } + } + + static void updateModifiers (NSEvent* e) + { + updateModifiers ([e modifierFlags]); + } + + static void updateModifiers (const NSUInteger flags) + { + int m = 0; + + if ((flags & NSShiftKeyMask) != 0) m |= ModifierKeys::shiftModifier; + if ((flags & NSControlKeyMask) != 0) m |= ModifierKeys::ctrlModifier; + if ((flags & NSAlternateKeyMask) != 0) m |= ModifierKeys::altModifier; + if ((flags & NSCommandKeyMask) != 0) m |= ModifierKeys::commandModifier; + + currentModifiers = currentModifiers.withOnlyMouseButtons().withFlags (m); + } + + static void updateKeysDown (NSEvent* ev, bool isKeyDown) + { + updateModifiers (ev); + int keyCode = getKeyCodeFromEvent (ev); + + if (keyCode != 0) + { + if (isKeyDown) + keysCurrentlyDown.addIfNotAlreadyThere (keyCode); + else + keysCurrentlyDown.removeValue (keyCode); + } + } static int getKeyCodeFromEvent (NSEvent* ev) { @@ -288,10 +893,15 @@ public: return style; } - virtual BOOL sendDragCallback (const int type, id sender) + static NSArray* getSupportedDragTypes() + { + return [NSArray arrayWithObjects: NSFilenamesPboardType, NSFilesPromisePboardType, NSStringPboardType, nil]; + } + + BOOL sendDragCallback (const int type, id sender) { NSPasteboard* pasteboard = [sender draggingPasteboard]; - NSString* contentType = [pasteboard availableTypeFromArray: [view getSupportedDragTypes]]; + NSString* contentType = [pasteboard availableTypeFromArray: getSupportedDragTypes()]; if (contentType == nil) return false; @@ -362,26 +972,120 @@ public: } //============================================================================== - virtual void viewFocusGain(); - virtual void viewFocusLoss(); - bool isFocused() const; - void grabFocus(); - void textInputRequired (const Point& position); + void viewFocusGain() + { + if (currentlyFocusedPeer != this) + { + if (ComponentPeer::isValidPeer (currentlyFocusedPeer)) + currentlyFocusedPeer->handleFocusLoss(); + + currentlyFocusedPeer = this; + handleFocusGain(); + } + } + + void viewFocusLoss() + { + if (currentlyFocusedPeer == this) + { + currentlyFocusedPeer = nullptr; + handleFocusLoss(); + } + } + + bool isFocused() const + { + return isSharedWindow ? this == currentlyFocusedPeer + : [window isKeyWindow]; + } + + void grabFocus() + { + if (window != nil) + { + [window makeKeyWindow]; + [window makeFirstResponder: view]; + + viewFocusGain(); + } + } + + void textInputRequired (const Point&) {} //============================================================================== - void repaint (const Rectangle& area); - void performAnyPendingRepaintsNow(); + void repaint (const Rectangle& area) + { + if (insideDrawRect) + { + class AsyncRepaintMessage : public CallbackMessage + { + public: + AsyncRepaintMessage (NSViewComponentPeer* const peer_, const Rectangle& rect_) + : peer (peer_), rect (rect_) + {} + + void messageCallback() + { + if (ComponentPeer::isValidPeer (peer)) + peer->repaint (rect); + } + + private: + NSViewComponentPeer* const peer; + const Rectangle rect; + }; + + (new AsyncRepaintMessage (this, area))->post(); + } + else + { + [view setNeedsDisplayInRect: NSMakeRect ((CGFloat) area.getX(), [view frame].size.height - (CGFloat) area.getBottom(), + (CGFloat) area.getWidth(), (CGFloat) area.getHeight())]; + } + } + + void performAnyPendingRepaintsNow() + { + [view displayIfNeeded]; + } //============================================================================== NSWindow* window; - JuceNSView* view; - bool isSharedWindow, fullScreen, insideDrawRect, usingCoreGraphics, recursiveToFrontCall; + NSView* view; + bool isSharedWindow, fullScreen, insideDrawRect; + bool usingCoreGraphics, recursiveToFrontCall, isZooming, textWasInserted; + String stringBeingComposed; + NSNotificationCenter* notificationCenter; static ModifierKeys currentModifiers; static ComponentPeer* currentlyFocusedPeer; static Array keysCurrentlyDown; private: + static NSView* createViewInstance(); + static NSWindow* createWindowInstance(); + + static void setOwner (id viewOrWindow, NSViewComponentPeer* newOwner) + { + object_setInstanceVariable (viewOrWindow, "owner", newOwner); + } + + void getClipRects (RectangleList& clip, const int xOffset, const int yOffset, const int clipW, const int clipH) + { + const NSRect* rects = nullptr; + NSInteger numRects = 0; + [view getRectsBeingDrawn: &rects count: &numRects]; + + const Rectangle clipBounds (clipW, clipH); + const CGFloat viewH = [view frame].size.height; + + for (int i = 0; i < numRects; ++i) + clip.addWithoutMerging (clipBounds.getIntersection (Rectangle (roundToInt (rects[i].origin.x) + xOffset, + roundToInt (viewH - (rects[i].origin.y + rects[i].size.height)) + yOffset, + roundToInt (rects[i].size.width), + roundToInt (rects[i].size.height)))); + } + static void appFocusChanged() { keysCurrentlyDown.clear(); @@ -487,440 +1191,533 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponentPeer); }; -} // (juce namespace) - //============================================================================== -@implementation JuceNSView - -- (JuceNSView*) initWithOwner: (NSViewComponentPeer*) owner_ - withFrame: (NSRect) frame +struct JuceNSViewClass : public ObjCClass { - [super initWithFrame: frame]; - owner = owner_; - stringBeingComposed = nullptr; - textWasInserted = false; - - notificationCenter = [NSNotificationCenter defaultCenter]; - - [notificationCenter addObserver: self - selector: @selector (frameChanged:) - name: NSViewFrameDidChangeNotification - object: self]; - - if (! owner_->isSharedWindow) + JuceNSViewClass() : ObjCClass ("JUCEView_") { - [notificationCenter addObserver: self - selector: @selector (frameChanged:) - name: NSWindowDidMoveNotification - object: owner_->window]; + addIvar ("owner"); + + addMethod (@selector (isOpaque), isOpaque, "c@:"); + addMethod (@selector (drawRect:), drawRect, "v@:", @encode (NSRect)); + addMethod (@selector (mouseDown:), mouseDown, "v@:@"); + addMethod (@selector (asyncMouseDown:), asyncMouseDown, "v@:@"); + addMethod (@selector (mouseUp:), mouseUp, "v@:@"); + addMethod (@selector (asyncMouseUp:), asyncMouseUp, "v@:@"); + addMethod (@selector (mouseDragged:), mouseDragged, "v@:@"); + addMethod (@selector (mouseMoved:), mouseMoved, "v@:@"); + addMethod (@selector (mouseEntered:), mouseEntered, "v@:@"); + addMethod (@selector (mouseExited:), mouseExited, "v@:@"); + addMethod (@selector (rightMouseDown:), rightMouseDown, "v@:@"); + addMethod (@selector (rightMouseDragged:), rightMouseDragged, "v@:@"); + addMethod (@selector (rightMouseUp:), rightMouseUp, "v@:@"); + addMethod (@selector (otherMouseDown:), otherMouseDown, "v@:@"); + addMethod (@selector (otherMouseDragged:), otherMouseDragged, "v@:@"); + addMethod (@selector (otherMouseUp:), otherMouseUp, "v@:@"); + addMethod (@selector (scrollWheel:), scrollWheel, "v@:@"); + addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse, "v@:@"); + addMethod (@selector (frameChanged:), frameChanged, "v@:@"); + addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow, "v@:"); + addMethod (@selector (keyDown:), keyDown, "v@:@"); + addMethod (@selector (keyUp:), keyUp, "v@:@"); + addMethod (@selector (insertText:), insertText, "v@:@"); + addMethod (@selector (doCommandBySelector:), doCommandBySelector, "v@::"); + addMethod (@selector (setMarkedText:selectedRange:), setMarkedText, "v@:@", @encode (NSRange)); + addMethod (@selector (unmarkText), unmarkText, "v@:"); + addMethod (@selector (hasMarkedText), hasMarkedText, "c@:"); + addMethod (@selector (conversationIdentifier), conversationIdentifier, "l@:"); + addMethod (@selector (attributedSubstringFromRange:), attributedSubstringFromRange, "@@:", @encode (NSRange)); + addMethod (@selector (markedRange), markedRange, @encode (NSRange), "@:"); + addMethod (@selector (selectedRange), selectedRange, @encode (NSRange), "@:"); + addMethod (@selector (firstRectForCharacterRange:), firstRectForCharacterRange, @encode (NSRect), "@:", @encode (NSRange)); + addMethod (@selector (validAttributesForMarkedText), validAttributesForMarkedText, "@@:"); + addMethod (@selector (flagsChanged:), flagsChanged, "v@:@"); + + addMethod (@selector (becomeFirstResponder), becomeFirstResponder, "c@:"); + addMethod (@selector (resignFirstResponder), resignFirstResponder, "c@:"); + addMethod (@selector (acceptsFirstResponder), acceptsFirstResponder, "c@:"); + + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, "c@:@"); + #endif + + addMethod (@selector (draggingEntered:), draggingEntered, @encode (NSDragOperation), "@:@"); + addMethod (@selector (draggingUpdated:), draggingUpdated, @encode (NSDragOperation), "@:@"); + addMethod (@selector (draggingEnded:), draggingEnded, "v@:@"); + addMethod (@selector (draggingExited:), draggingExited, "v@:@"); + addMethod (@selector (prepareForDragOperation:), prepareForDragOperation, "c@:@"); + addMethod (@selector (performDragOperation:), performDragOperation, "c@:@"); + addMethod (@selector (concludeDragOperation:), concludeDragOperation, "v@:@"); + + addProtocol (@protocol (NSTextInput)); + + registerClass(); } - [self registerForDraggedTypes: [self getSupportedDragTypes]]; +private: + static NSViewComponentPeer* getOwner (id self) + { + return getIvar (self, "owner"); + } - return self; -} + //============================================================================== + static void drawRect (id self, SEL, NSRect r) + { + NSViewComponentPeer* const owner = getOwner (self); -- (void) dealloc -{ - [notificationCenter removeObserver: self]; - delete stringBeingComposed; - [super dealloc]; -} + if (owner != nullptr) + owner->drawRect (r); + } + + static BOOL isOpaque (id self, SEL) + { + NSViewComponentPeer* const owner = getOwner (self); + return owner == nullptr || owner->isOpaque(); + } + + //============================================================================== + static void mouseDown (id self, SEL, NSEvent* ev) + { + if (JUCEApplication::isStandaloneApp()) + [self performSelector: @selector (asyncMouseDown:) + withObject: ev]; + else + // In some host situations, the host will stop modal loops from working + // correctly if they're called from a mouse event, so we'll trigger + // the event asynchronously.. + [self performSelectorOnMainThread: @selector (asyncMouseDown:) + withObject: ev + waitUntilDone: NO]; + } + + static void asyncMouseDown (id self, SEL, NSEvent* ev) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr) + owner->redirectMouseDown (ev); + } + + static void mouseUp (id self, SEL, NSEvent* ev) + { + if (! JUCEApplication::isStandaloneApp()) + [self performSelector: @selector (asyncMouseUp:) + withObject: ev]; + else + // In some host situations, the host will stop modal loops from working + // correctly if they're called from a mouse event, so we'll trigger + // the event asynchronously.. + [self performSelectorOnMainThread: @selector (asyncMouseUp:) + withObject: ev + waitUntilDone: NO]; + } + + static void asyncMouseUp (id self, SEL, NSEvent* ev) { NSViewComponentPeer* const owner = getOwner (self); if (owner != nullptr) owner->redirectMouseUp (ev); } + static void mouseDragged (id self, SEL, NSEvent* ev) { NSViewComponentPeer* const owner = getOwner (self); if (owner != nullptr) owner->redirectMouseDrag (ev); } + static void mouseMoved (id self, SEL, NSEvent* ev) { NSViewComponentPeer* const owner = getOwner (self); if (owner != nullptr) owner->redirectMouseMove (ev); } + static void mouseEntered (id self, SEL, NSEvent* ev) { NSViewComponentPeer* const owner = getOwner (self); if (owner != nullptr) owner->redirectMouseEnter (ev); } + static void mouseExited (id self, SEL, NSEvent* ev) { NSViewComponentPeer* const owner = getOwner (self); if (owner != nullptr) owner->redirectMouseExit (ev); } + static void scrollWheel (id self, SEL, NSEvent* ev) { NSViewComponentPeer* const owner = getOwner (self); if (owner != nullptr) owner->redirectMouseWheel (ev); } + + static void rightMouseDown (id self, SEL, NSEvent* ev) { [(NSView*) self mouseDown: ev]; } + static void rightMouseDragged (id self, SEL, NSEvent* ev) { [(NSView*) self mouseDragged: ev]; } + static void rightMouseUp (id self, SEL, NSEvent* ev) { [(NSView*) self mouseUp: ev]; } + static void otherMouseDown (id self, SEL, NSEvent* ev) { [(NSView*) self mouseDown: ev]; } + static void otherMouseDragged (id self, SEL, NSEvent* ev) { [(NSView*) self mouseDragged: ev]; } + static void otherMouseUp (id self, SEL, NSEvent* ev) { [(NSView*) self mouseUp: ev]; } + + static BOOL acceptsFirstMouse (id, SEL, NSEvent*) { return YES; } + + static void frameChanged (id self, SEL, NSNotification*) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr) + owner->redirectMovedOrResized(); + } + + static void viewDidMoveToWindow (id self, SEL) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr) + owner->viewMovedToWindow(); + } + + //============================================================================== + static void keyDown (id self, SEL, NSEvent* ev) + { + NSViewComponentPeer* const owner = getOwner (self); + + TextInputTarget* const target = owner->findCurrentTextInputTarget(); + owner->textWasInserted = false; + + if (target != nullptr) + [(NSView*) self interpretKeyEvents: [NSArray arrayWithObject: ev]]; + else + owner->stringBeingComposed = String::empty; + + if ((! owner->textWasInserted) && (owner == nullptr || ! owner->redirectKeyDown (ev))) + { + objc_super s = { self, [NSView class] }; + objc_msgSendSuper (&s, @selector (keyDown:), ev); + } + } + + static void keyUp (id self, SEL, NSEvent* ev) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner == nullptr || ! owner->redirectKeyUp (ev)) + { + objc_super s = { self, [NSView class] }; + objc_msgSendSuper (&s, @selector (keyUp:), ev); + } + } + + //============================================================================== + static void insertText (id self, SEL, id aString) + { + // This commits multi-byte text when return is pressed, or after every keypress for western keyboards + NSViewComponentPeer* const owner = getOwner (self); + + NSString* newText = [aString isKindOfClass: [NSAttributedString class]] ? [aString string] : aString; + + if ([newText length] > 0) + { + TextInputTarget* const target = owner->findCurrentTextInputTarget(); + + if (target != nullptr) + { + target->insertTextAtCaret (nsStringToJuce (newText)); + owner->textWasInserted = true; + } + } + + owner->stringBeingComposed = String::empty; + } + + static void doCommandBySelector (id, SEL, SEL) {} + + static void setMarkedText (id self, SEL, id aString, NSRange) + { + NSViewComponentPeer* const owner = getOwner (self); + + owner->stringBeingComposed = nsStringToJuce ([aString isKindOfClass: [NSAttributedString class]] + ? [aString string] : aString); + + TextInputTarget* const target = owner->findCurrentTextInputTarget(); + + if (target != nullptr) + { + const Range currentHighlight (target->getHighlightedRegion()); + target->insertTextAtCaret (owner->stringBeingComposed); + target->setHighlightedRegion (currentHighlight.withLength (owner->stringBeingComposed.length())); + owner->textWasInserted = true; + } + } + + static void unmarkText (id self, SEL) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner->stringBeingComposed.isNotEmpty()) + { + TextInputTarget* const target = owner->findCurrentTextInputTarget(); + + if (target != nullptr) + { + target->insertTextAtCaret (owner->stringBeingComposed); + owner->textWasInserted = true; + } + + owner->stringBeingComposed = String::empty; + } + } + + static BOOL hasMarkedText (id self, SEL) + { + return getOwner (self)->stringBeingComposed.isNotEmpty(); + } + + static long conversationIdentifier (id self, SEL) + { + return (long) (pointer_sized_int) self; + } + + static NSAttributedString* attributedSubstringFromRange (id self, SEL, NSRange theRange) + { + NSViewComponentPeer* const owner = getOwner (self); + TextInputTarget* const target = owner->findCurrentTextInputTarget(); + + if (target != nullptr) + { + const Range r ((int) theRange.location, + (int) (theRange.location + theRange.length)); + + return [[[NSAttributedString alloc] initWithString: juceStringToNS (target->getTextInRange (r))] autorelease]; + } + + return nil; + } + + static NSRange markedRange (id self, SEL) + { + NSViewComponentPeer* const owner = getOwner (self); + return owner->stringBeingComposed.isNotEmpty() ? NSMakeRange (0, owner->stringBeingComposed.length()) + : NSMakeRange (NSNotFound, 0); + } + + static NSRange selectedRange (id self, SEL) + { + NSViewComponentPeer* const owner = getOwner (self); + TextInputTarget* const target = owner->findCurrentTextInputTarget(); + + if (target != nullptr) + { + const Range highlight (target->getHighlightedRegion()); + + if (! highlight.isEmpty()) + return NSMakeRange (highlight.getStart(), highlight.getLength()); + } + + return NSMakeRange (NSNotFound, 0); + } + + static NSRect firstRectForCharacterRange (id self, SEL, NSRange) + { + NSViewComponentPeer* const owner = getOwner (self); + Component* const comp = dynamic_cast (owner->findCurrentTextInputTarget()); + + if (comp == nullptr) + return NSMakeRect (0, 0, 0, 0); + + const Rectangle bounds (comp->getScreenBounds()); + + return NSMakeRect (bounds.getX(), + [[[NSScreen screens] objectAtIndex: 0] frame].size.height - bounds.getY(), + bounds.getWidth(), + bounds.getHeight()); + } + + static NSUInteger characterIndexForPoint (id, SEL, NSPoint) { return NSNotFound; } + static NSArray* validAttributesForMarkedText (id, SEL) { return [NSArray array]; } + + //============================================================================== + static void flagsChanged (id self, SEL, NSEvent* ev) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr) + owner->redirectModKeyChange (ev); + } + + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + static BOOL performKeyEquivalent (id self, SEL, NSEvent* ev) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr && owner->redirectPerformKeyEquivalent (ev)) + return true; + + objc_super s = { self, [NSView class] }; + return objc_msgSendSuper (&s, @selector (performKeyEquivalent:), ev); + } + #endif + + static BOOL becomeFirstResponder (id self, SEL) + { + NSViewComponentPeer* const owner = getOwner (self); + if (owner != nullptr) + owner->viewFocusGain(); + + return YES; + } + + static BOOL resignFirstResponder (id self, SEL) + { + NSViewComponentPeer* const owner = getOwner (self); + if (owner != nullptr) + owner->viewFocusLoss(); + + return YES; + } + + static BOOL acceptsFirstResponder (id self, SEL) + { + NSViewComponentPeer* const owner = getOwner (self); + return owner != nullptr && owner->canBecomeKeyWindow(); + } + + //============================================================================== + static NSDragOperation draggingEntered (id self, SEL, id sender) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr && owner->sendDragCallback (0, sender)) + return NSDragOperationCopy | NSDragOperationMove | NSDragOperationGeneric; + else + return NSDragOperationNone; + } + + static NSDragOperation draggingUpdated (id self, SEL, id sender) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr && owner->sendDragCallback (0, sender)) + return NSDragOperationCopy | NSDragOperationMove | NSDragOperationGeneric; + else + return NSDragOperationNone; + } + + static void draggingEnded (id self, SEL, id sender) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr) + owner->sendDragCallback (1, sender); + } + + static void draggingExited (id self, SEL, id sender) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr) + owner->sendDragCallback (1, sender); + } + + static BOOL prepareForDragOperation (id self, SEL, id ) + { + return YES; + } + + static BOOL performDragOperation (id self, SEL, id sender) + { + NSViewComponentPeer* const owner = getOwner (self); + return owner != nullptr && owner->sendDragCallback (2, sender); + } + + static void concludeDragOperation (id, SEL, id ) {} +}; //============================================================================== -- (void) drawRect: (NSRect) r +struct JuceNSWindowClass : public ObjCClass { - if (owner != nullptr) - owner->drawRect (r); -} + JuceNSWindowClass() : ObjCClass ("JUCEWindow_") + { + addIvar ("owner"); -- (BOOL) isOpaque -{ - return owner == nullptr || owner->isOpaque(); -} + addMethod (@selector (canBecomeKeyWindow), canBecomeKeyWindow, "c@:"); + addMethod (@selector (becomeKeyWindow), becomeKeyWindow, "v@:"); + addMethod (@selector (windowShouldClose:), windowShouldClose, "c@:@"); + addMethod (@selector (constrainFrameRect:toScreen:), constrainFrameRect, @encode (NSRect), "@:", @encode (NSRect*), "@"); + addMethod (@selector (windowWillResize:toSize:), windowWillResize, @encode (NSSize), "@:@", @encode (NSSize)); + addMethod (@selector (zoom), zoom, "v@:@"); + addMethod (@selector (windowWillMove), windowWillMove, "v@:@"); -//============================================================================== -- (void) mouseDown: (NSEvent*) ev -{ - if (JUCEApplication::isStandaloneApp()) - [self asyncMouseDown: ev]; - else - // In some host situations, the host will stop modal loops from working - // correctly if they're called from a mouse event, so we'll trigger - // the event asynchronously.. - [self performSelectorOnMainThread: @selector (asyncMouseDown:) - withObject: ev - waitUntilDone: NO]; -} + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + addProtocol (@protocol (NSWindowDelegate)); + #endif -- (void) asyncMouseDown: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseDown (ev); -} + registerClass(); + } -- (void) mouseUp: (NSEvent*) ev -{ - if (! JUCEApplication::isStandaloneApp()) - [self asyncMouseUp: ev]; - else - // In some host situations, the host will stop modal loops from working - // correctly if they're called from a mouse event, so we'll trigger - // the event asynchronously.. - [self performSelectorOnMainThread: @selector (asyncMouseUp:) - withObject: ev - waitUntilDone: NO]; -} +private: + static NSViewComponentPeer* getOwner (id self) + { + return getIvar (self, "owner"); + } -- (void) asyncMouseUp: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseUp (ev); } -- (void) mouseDragged: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseDrag (ev); } -- (void) mouseMoved: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseMove (ev); } -- (void) mouseEntered: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseEnter (ev); } -- (void) mouseExited: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseExit (ev); } -- (void) scrollWheel: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseWheel (ev); } + //============================================================================== + static BOOL canBecomeKeyWindow (id self, SEL) + { + NSViewComponentPeer* const owner = getOwner (self); + return owner != nullptr && owner->canBecomeKeyWindow(); + } -- (void) rightMouseDown: (NSEvent*) ev { [self mouseDown: ev]; } -- (void) rightMouseDragged: (NSEvent*) ev { [self mouseDragged: ev]; } -- (void) rightMouseUp: (NSEvent*) ev { [self mouseUp: ev]; } -- (void) otherMouseDown: (NSEvent*) ev { [self mouseDown: ev]; } -- (void) otherMouseDragged: (NSEvent*) ev { [self mouseDragged: ev]; } -- (void) otherMouseUp: (NSEvent*) ev { [self mouseUp: ev]; } + static void becomeKeyWindow (id self, SEL) + { + sendSuperclassMessage (self, @selector (becomeKeyWindow)); -- (BOOL) acceptsFirstMouse: (NSEvent*) ev -{ - (void) ev; - return YES; -} + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr) + owner->becomeKeyWindow(); + } + + static BOOL windowShouldClose (id self, SEL, id /*window*/) + { + NSViewComponentPeer* const owner = getOwner (self); + return owner == nullptr || owner->windowShouldClose(); + } + + static NSRect constrainFrameRect (id self, SEL, NSRect frameRect, NSScreen*) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner != nullptr) + frameRect = owner->constrainRect (frameRect); + + return frameRect; + } + + static NSSize windowWillResize (id self, SEL, NSWindow*, NSSize proposedFrameSize) + { + NSViewComponentPeer* const owner = getOwner (self); + + if (owner->isZooming) + return proposedFrameSize; + + NSRect frameRect = [(NSWindow*) self frame]; + frameRect.origin.y -= proposedFrameSize.height - frameRect.size.height; + frameRect.size = proposedFrameSize; + + if (owner != nullptr) + frameRect = owner->constrainRect (frameRect); + + if (juce::Component::getCurrentlyModalComponent() != nullptr + && owner->getComponent()->isCurrentlyBlockedByAnotherModalComponent() + && owner->hasNativeTitleBar()) + juce::Component::getCurrentlyModalComponent()->inputAttemptWhenModal(); + + return frameRect.size; + } + + static void zoom (id self, SEL, id sender) + { + NSViewComponentPeer* const owner = getOwner (self); + + owner->isZooming = true; + objc_super s = { self, [NSWindow class] }; + objc_msgSendSuper (&s, @selector(zoom), sender); + owner->isZooming = false; -- (void) frameChanged: (NSNotification*) n -{ - (void) n; - if (owner != nullptr) owner->redirectMovedOrResized(); -} - -- (void) viewDidMoveToWindow -{ - if (owner != nullptr) - owner->viewMovedToWindow(); -} - -//============================================================================== -- (void) keyDown: (NSEvent*) ev -{ - TextInputTarget* const target = owner->findCurrentTextInputTarget(); - textWasInserted = false; - - if (target != nullptr) - [self interpretKeyEvents: [NSArray arrayWithObject: ev]]; - else - deleteAndZero (stringBeingComposed); - - if ((! textWasInserted) && (owner == nullptr || ! owner->redirectKeyDown (ev))) - [super keyDown: ev]; -} - -- (void) keyUp: (NSEvent*) ev -{ - if (owner == nullptr || ! owner->redirectKeyUp (ev)) - [super keyUp: ev]; -} - -//============================================================================== -- (void) insertText: (id) aString -{ - // This commits multi-byte text when return is pressed, or after every keypress for western keyboards - NSString* newText = [aString isKindOfClass: [NSAttributedString class]] ? [aString string] : aString; - - if ([newText length] > 0) - { - TextInputTarget* const target = owner->findCurrentTextInputTarget(); - - if (target != nullptr) - { - target->insertTextAtCaret (nsStringToJuce (newText)); - textWasInserted = true; - } } - deleteAndZero (stringBeingComposed); -} - -- (void) doCommandBySelector: (SEL) aSelector -{ - (void) aSelector; -} - -- (void) setMarkedText: (id) aString selectedRange: (NSRange) selectionRange -{ - (void) selectionRange; - - if (stringBeingComposed == 0) - stringBeingComposed = new String(); - - *stringBeingComposed = nsStringToJuce ([aString isKindOfClass:[NSAttributedString class]] ? [aString string] : aString); - - TextInputTarget* const target = owner->findCurrentTextInputTarget(); - - if (target != nullptr) + static void windowWillMove (id self, SEL, NSNotification*) { - const Range currentHighlight (target->getHighlightedRegion()); - target->insertTextAtCaret (*stringBeingComposed); - target->setHighlightedRegion (currentHighlight.withLength (stringBeingComposed->length())); - textWasInserted = true; + NSViewComponentPeer* const owner = getOwner (self); + + if (juce::Component::getCurrentlyModalComponent() != nullptr + && owner->getComponent()->isCurrentlyBlockedByAnotherModalComponent() + && owner->hasNativeTitleBar()) + juce::Component::getCurrentlyModalComponent()->inputAttemptWhenModal(); } +}; + +NSView* NSViewComponentPeer::createViewInstance() +{ + static JuceNSViewClass cls; + return cls.createInstance(); } -- (void) unmarkText +NSWindow* NSViewComponentPeer::createWindowInstance() { - if (stringBeingComposed != nullptr) - { - TextInputTarget* const target = owner->findCurrentTextInputTarget(); - - if (target != nullptr) - { - target->insertTextAtCaret (*stringBeingComposed); - textWasInserted = true; - } - } - - deleteAndZero (stringBeingComposed); + static JuceNSWindowClass cls; + return cls.createInstance(); } -- (BOOL) hasMarkedText -{ - return stringBeingComposed != nullptr; -} - -- (long) conversationIdentifier -{ - return (long) (pointer_sized_int) self; -} - -- (NSAttributedString*) attributedSubstringFromRange: (NSRange) theRange -{ - TextInputTarget* const target = owner->findCurrentTextInputTarget(); - - if (target != nullptr) - { - const Range r ((int) theRange.location, - (int) (theRange.location + theRange.length)); - - return [[[NSAttributedString alloc] initWithString: juceStringToNS (target->getTextInRange (r))] autorelease]; - } - - return nil; -} - -- (NSRange) markedRange -{ - return stringBeingComposed != nullptr ? NSMakeRange (0, stringBeingComposed->length()) - : NSMakeRange (NSNotFound, 0); -} - -- (NSRange) selectedRange -{ - TextInputTarget* const target = owner->findCurrentTextInputTarget(); - - if (target != nullptr) - { - const Range highlight (target->getHighlightedRegion()); - - if (! highlight.isEmpty()) - return NSMakeRange (highlight.getStart(), highlight.getLength()); - } - - return NSMakeRange (NSNotFound, 0); -} - -- (NSRect) firstRectForCharacterRange: (NSRange) theRange -{ - (void) theRange; - juce::Component* const comp = dynamic_cast (owner->findCurrentTextInputTarget()); - - if (comp == 0) - return NSMakeRect (0, 0, 0, 0); - - const Rectangle bounds (comp->getScreenBounds()); - - return NSMakeRect (bounds.getX(), - [[[NSScreen screens] objectAtIndex: 0] frame].size.height - bounds.getY(), - bounds.getWidth(), - bounds.getHeight()); -} - -- (NSUInteger) characterIndexForPoint: (NSPoint) thePoint -{ - (void) thePoint; - return NSNotFound; -} - -- (NSArray*) validAttributesForMarkedText -{ - return [NSArray array]; -} - -//============================================================================== -- (void) flagsChanged: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectModKeyChange (ev); -} - -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 -- (BOOL) performKeyEquivalent: (NSEvent*) ev -{ - if (owner != nullptr && owner->redirectPerformKeyEquivalent (ev)) - return true; - - return [super performKeyEquivalent: ev]; -} -#endif - -- (BOOL) becomeFirstResponder { if (owner != nullptr) owner->viewFocusGain(); return YES; } -- (BOOL) resignFirstResponder { if (owner != nullptr) owner->viewFocusLoss(); return YES; } - -- (BOOL) acceptsFirstResponder { return owner != nullptr && owner->canBecomeKeyWindow(); } - -//============================================================================== -- (NSArray*) getSupportedDragTypes -{ - return [NSArray arrayWithObjects: NSFilenamesPboardType, NSFilesPromisePboardType, NSStringPboardType, nil]; -} - -- (BOOL) sendDragCallback: (int) type sender: (id ) sender -{ - return owner != nullptr && owner->sendDragCallback (type, sender); -} - -- (NSDragOperation) draggingEntered: (id ) sender -{ - if ([self sendDragCallback: 0 sender: sender]) - return NSDragOperationCopy | NSDragOperationMove | NSDragOperationGeneric; - else - return NSDragOperationNone; -} - -- (NSDragOperation) draggingUpdated: (id ) sender -{ - if ([self sendDragCallback: 0 sender: sender]) - return NSDragOperationCopy | NSDragOperationMove | NSDragOperationGeneric; - else - return NSDragOperationNone; -} - -- (void) draggingEnded: (id ) sender -{ - [self sendDragCallback: 1 sender: sender]; -} - -- (void) draggingExited: (id ) sender -{ - [self sendDragCallback: 1 sender: sender]; -} - -- (BOOL) prepareForDragOperation: (id ) sender -{ - (void) sender; - return YES; -} - -- (BOOL) performDragOperation: (id ) sender -{ - return [self sendDragCallback: 2 sender: sender]; -} - -- (void) concludeDragOperation: (id ) sender -{ - (void) sender; -} - -@end - -//============================================================================== -@implementation JuceNSWindow - -- (void) setOwner: (NSViewComponentPeer*) owner_ -{ - owner = owner_; - isZooming = false; -} - -- (BOOL) canBecomeKeyWindow -{ - return owner != nullptr && owner->canBecomeKeyWindow(); -} - -- (void) becomeKeyWindow -{ - [super becomeKeyWindow]; - - if (owner != nullptr) - owner->becomeKeyWindow(); -} - -- (BOOL) windowShouldClose: (id) window -{ - (void) window; - return owner == nullptr || owner->windowShouldClose(); -} - -- (NSRect) constrainFrameRect: (NSRect) frameRect toScreen: (NSScreen*) screen -{ - (void) screen; - if (owner != nullptr) - frameRect = owner->constrainRect (frameRect); - - return frameRect; -} - -- (NSSize) windowWillResize: (NSWindow*) window toSize: (NSSize) proposedFrameSize -{ - (void) window; - if (isZooming) - return proposedFrameSize; - - NSRect frameRect = [self frame]; - frameRect.origin.y -= proposedFrameSize.height - frameRect.size.height; - frameRect.size = proposedFrameSize; - - if (owner != nullptr) - frameRect = owner->constrainRect (frameRect); - - if (juce::Component::getCurrentlyModalComponent() != nullptr - && owner->getComponent()->isCurrentlyBlockedByAnotherModalComponent() - && owner->hasNativeTitleBar()) - juce::Component::getCurrentlyModalComponent()->inputAttemptWhenModal(); - - return frameRect.size; -} - -- (void) zoom: (id) sender -{ - isZooming = true; - [super zoom: sender]; - isZooming = false; - - owner->redirectMovedOrResized(); -} - -- (void) windowWillMove: (NSNotification*) notification -{ - (void) notification; - - if (juce::Component::getCurrentlyModalComponent() != nullptr - && owner->getComponent()->isCurrentlyBlockedByAnotherModalComponent() - && owner->hasNativeTitleBar()) - juce::Component::getCurrentlyModalComponent()->inputAttemptWhenModal(); -} - -@end - - -//============================================================================== -//============================================================================== -namespace juce -{ //============================================================================== ModifierKeys NSViewComponentPeer::currentModifiers; @@ -944,36 +1741,6 @@ bool KeyPress::isKeyCurrentlyDown (const int keyCode) return false; } -void NSViewComponentPeer::updateModifiers (const NSUInteger flags) -{ - int m = 0; - - if ((flags & NSShiftKeyMask) != 0) m |= ModifierKeys::shiftModifier; - if ((flags & NSControlKeyMask) != 0) m |= ModifierKeys::ctrlModifier; - if ((flags & NSAlternateKeyMask) != 0) m |= ModifierKeys::altModifier; - if ((flags & NSCommandKeyMask) != 0) m |= ModifierKeys::commandModifier; - - currentModifiers = currentModifiers.withOnlyMouseButtons().withFlags (m); -} - -void NSViewComponentPeer::updateModifiers (NSEvent* e) -{ - updateModifiers ([e modifierFlags]); -} - -void NSViewComponentPeer::updateKeysDown (NSEvent* ev, bool isKeyDown) -{ - updateModifiers (ev); - int keyCode = getKeyCodeFromEvent (ev); - - if (keyCode != 0) - { - if (isKeyDown) - keysCurrentlyDown.addIfNotAlreadyThere (keyCode); - else - keysCurrentlyDown.removeValue (keyCode); - } -} ModifierKeys ModifierKeys::getCurrentModifiersRealtime() noexcept { @@ -988,801 +1755,6 @@ void ModifierKeys::updateCurrentModifiers() noexcept currentModifiers = NSViewComponentPeer::currentModifiers; } -//============================================================================== -NSViewComponentPeer::NSViewComponentPeer (Component* const component_, - const int windowStyleFlags, - NSView* viewToAttachTo) - : ComponentPeer (component_, windowStyleFlags), - window (nil), - view (nil), - isSharedWindow (viewToAttachTo != nil), - fullScreen (false), - insideDrawRect (false), - #if USE_COREGRAPHICS_RENDERING - usingCoreGraphics (true), - #else - usingCoreGraphics (false), - #endif - recursiveToFrontCall (false) -{ - appFocusChangeCallback = appFocusChanged; - isEventBlockedByModalComps = checkEventBlockedByModalComps; - - NSRect r = NSMakeRect (0, 0, (CGFloat) component->getWidth(), (CGFloat) component->getHeight()); - - view = [[JuceNSView alloc] initWithOwner: this withFrame: r]; - [view setPostsFrameChangedNotifications: YES]; - - if (isSharedWindow) - { - window = [viewToAttachTo window]; - [viewToAttachTo addSubview: view]; - } - else - { - r.origin.x = (CGFloat) component->getX(); - r.origin.y = (CGFloat) component->getY(); - r.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - (r.origin.y + r.size.height); - - window = [[JuceNSWindow alloc] initWithContentRect: r - styleMask: getNSWindowStyleMask (windowStyleFlags) - backing: NSBackingStoreBuffered - defer: YES]; - - [((JuceNSWindow*) window) setOwner: this]; - [window orderOut: nil]; - [window setDelegate: (JuceNSWindow*) window]; - [window setOpaque: component->isOpaque()]; - [window setHasShadow: ((windowStyleFlags & windowHasDropShadow) != 0)]; - - if (component->isAlwaysOnTop()) - [window setLevel: NSFloatingWindowLevel]; - - [window setContentView: view]; - [window setAutodisplay: YES]; - [window setAcceptsMouseMovedEvents: YES]; - - // We'll both retain and also release this on closing because plugin hosts can unexpectedly - // close the window for us, and also tend to get cause trouble if setReleasedWhenClosed is NO. - [window setReleasedWhenClosed: YES]; - [window retain]; - - [window setExcludedFromWindowsMenu: (windowStyleFlags & windowIsTemporary) != 0]; - [window setIgnoresMouseEvents: (windowStyleFlags & windowIgnoresMouseClicks) != 0]; - - #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7) - if ((windowStyleFlags & (windowHasMaximiseButton | windowHasTitleBar)) == (windowHasMaximiseButton | windowHasTitleBar)) - [window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary]; - #endif - } - - const float alpha = component->getAlpha(); - if (alpha < 1.0f) - setAlpha (alpha); - - setTitle (component->getName()); -} - -NSViewComponentPeer::~NSViewComponentPeer() -{ - view->owner = nullptr; - [view removeFromSuperview]; - [view release]; - - if (! isSharedWindow) - { - [((JuceNSWindow*) window) setOwner: 0]; - [window close]; - [window release]; - } -} - -//============================================================================== -void* NSViewComponentPeer::getNativeHandle() const -{ - return view; -} - -void NSViewComponentPeer::setVisible (bool shouldBeVisible) -{ - if (isSharedWindow) - { - [view setHidden: ! shouldBeVisible]; - } - else - { - if (shouldBeVisible) - { - [window orderFront: nil]; - handleBroughtToFront(); - } - else - { - [window orderOut: nil]; - } - } -} - -void NSViewComponentPeer::setTitle (const String& title) -{ - JUCE_AUTORELEASEPOOL - - if (! isSharedWindow) - [window setTitle: juceStringToNS (title)]; -} - -void NSViewComponentPeer::setPosition (int x, int y) -{ - setBounds (x, y, component->getWidth(), component->getHeight(), false); -} - -void NSViewComponentPeer::setSize (int w, int h) -{ - setBounds (component->getX(), component->getY(), w, h, false); -} - -void NSViewComponentPeer::setBounds (int x, int y, int w, int h, bool isNowFullScreen) -{ - fullScreen = isNowFullScreen; - - NSRect r = NSMakeRect ((CGFloat) x, (CGFloat) y, (CGFloat) jmax (0, w), (CGFloat) jmax (0, h)); - - if (isSharedWindow) - { - r.origin.y = [[view superview] frame].size.height - (r.origin.y + r.size.height); - - if ([view frame].size.width != r.size.width - || [view frame].size.height != r.size.height) - { - [view setNeedsDisplay: true]; - } - - [view setFrame: r]; - } - else - { - r.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - (r.origin.y + r.size.height); - - [window setFrame: [window frameRectForContentRect: r] - display: true]; - } -} - -Rectangle NSViewComponentPeer::getBounds (const bool global) const -{ - NSRect r = [view frame]; - - if (global && [view window] != nil) - { - r = [view convertRect: r toView: nil]; - NSRect wr = [[view window] frame]; - r.origin.x += wr.origin.x; - r.origin.y += wr.origin.y; - r.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - r.origin.y - r.size.height; - } - else - { - r.origin.y = [[view superview] frame].size.height - r.origin.y - r.size.height; - } - - return Rectangle (convertToRectInt (r)); -} - -Rectangle NSViewComponentPeer::getBounds() const -{ - return getBounds (! isSharedWindow); -} - -Point NSViewComponentPeer::getScreenPosition() const -{ - return getBounds (true).getPosition(); -} - -Point NSViewComponentPeer::localToGlobal (const Point& relativePosition) -{ - return relativePosition + getScreenPosition(); -} - -Point NSViewComponentPeer::globalToLocal (const Point& screenPosition) -{ - return screenPosition - getScreenPosition(); -} - -NSRect NSViewComponentPeer::constrainRect (NSRect r) -{ - if (constrainer != nullptr - #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7) - && ([window styleMask] & NSFullScreenWindowMask) == 0 - #endif - ) - { - NSRect current = [window frame]; - current.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - current.origin.y - current.size.height; - - r.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - r.origin.y - r.size.height; - - Rectangle pos (convertToRectInt (r)); - Rectangle original (convertToRectInt (current)); - const Rectangle screenBounds (Desktop::getInstance().getDisplays().getTotalBounds (true)); - - #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_6 - if ([window inLiveResize]) - #else - if ([window respondsToSelector: @selector (inLiveResize)] - && [window performSelector: @selector (inLiveResize)]) - #endif - { - constrainer->checkBounds (pos, original, screenBounds, - false, false, true, true); - } - else - { - constrainer->checkBounds (pos, original, screenBounds, - pos.getY() != original.getY() && pos.getBottom() == original.getBottom(), - pos.getX() != original.getX() && pos.getRight() == original.getRight(), - pos.getY() == original.getY() && pos.getBottom() != original.getBottom(), - pos.getX() == original.getX() && pos.getRight() != original.getRight()); - } - - r.origin.x = pos.getX(); - r.origin.y = [[[NSScreen screens] objectAtIndex: 0] frame].size.height - r.size.height - pos.getY(); - r.size.width = pos.getWidth(); - r.size.height = pos.getHeight(); - } - - return r; -} - -void NSViewComponentPeer::setAlpha (float newAlpha) -{ - if (! isSharedWindow) - { - [window setAlphaValue: (CGFloat) newAlpha]; - } - else - { - #if defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_5 - [view setAlphaValue: (CGFloat) newAlpha]; - #else - if ([view respondsToSelector: @selector (setAlphaValue:)]) - { - // PITA dynamic invocation for 10.4 builds.. - NSInvocation* inv = [NSInvocation invocationWithMethodSignature: [view methodSignatureForSelector: @selector (setAlphaValue:)]]; - [inv setSelector: @selector (setAlphaValue:)]; - [inv setTarget: view]; - CGFloat cgNewAlpha = (CGFloat) newAlpha; - [inv setArgument: &cgNewAlpha atIndex: 2]; - [inv invoke]; - } - #endif - } -} - -void NSViewComponentPeer::setMinimised (bool shouldBeMinimised) -{ - if (! isSharedWindow) - { - if (shouldBeMinimised) - [window miniaturize: nil]; - else - [window deminiaturize: nil]; - } -} - -bool NSViewComponentPeer::isMinimised() const -{ - return [window isMiniaturized]; -} - -void NSViewComponentPeer::setFullScreen (bool shouldBeFullScreen) -{ - if (! isSharedWindow) - { - Rectangle r (lastNonFullscreenBounds); - - if (isMinimised()) - setMinimised (false); - - if (fullScreen != shouldBeFullScreen) - { - if (shouldBeFullScreen && hasNativeTitleBar()) - { - fullScreen = true; - [window performZoom: nil]; - } - else - { - if (shouldBeFullScreen) - r = component->getParentMonitorArea(); - - // (can't call the component's setBounds method because that'll reset our fullscreen flag) - if (r != getComponent()->getBounds() && ! r.isEmpty()) - setBounds (r.getX(), r.getY(), r.getWidth(), r.getHeight(), shouldBeFullScreen); - } - } - } -} - -bool NSViewComponentPeer::isFullScreen() const -{ - return fullScreen; -} - -bool NSViewComponentPeer::contains (const Point& position, bool trueIfInAChildWindow) const -{ - if (! (isPositiveAndBelow (position.getX(), component->getWidth()) - && isPositiveAndBelow (position.getY(), component->getHeight()))) - return false; - - NSRect frameRect = [view frame]; - - NSView* v = [view hitTest: NSMakePoint (frameRect.origin.x + position.getX(), - frameRect.origin.y + frameRect.size.height - position.getY())]; - - if (trueIfInAChildWindow) - return v != nil; - - return v == view; -} - -BorderSize NSViewComponentPeer::getFrameSize() const -{ - BorderSize b; - - if (! isSharedWindow) - { - NSRect v = [view convertRect: [view frame] toView: nil]; - NSRect w = [window frame]; - - b.setTop ((int) (w.size.height - (v.origin.y + v.size.height))); - b.setBottom ((int) v.origin.y); - b.setLeft ((int) v.origin.x); - b.setRight ((int) (w.size.width - (v.origin.x + v.size.width))); - } - - return b; -} - -bool NSViewComponentPeer::setAlwaysOnTop (bool alwaysOnTop) -{ - if (! isSharedWindow) - [window setLevel: alwaysOnTop ? NSFloatingWindowLevel - : NSNormalWindowLevel]; - return true; -} - -void NSViewComponentPeer::toFront (bool makeActiveWindow) -{ - if (isSharedWindow) - [[view superview] addSubview: view - positioned: NSWindowAbove - relativeTo: nil]; - - if (window != nil && component->isVisible()) - { - if (makeActiveWindow) - [window makeKeyAndOrderFront: nil]; - else - [window orderFront: nil]; - - if (! recursiveToFrontCall) - { - recursiveToFrontCall = true; - Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); - handleBroughtToFront(); - recursiveToFrontCall = false; - } - } -} - -void NSViewComponentPeer::toBehind (ComponentPeer* other) -{ - NSViewComponentPeer* const otherPeer = dynamic_cast (other); - jassert (otherPeer != nullptr); // wrong type of window? - - if (otherPeer != nullptr) - { - if (isSharedWindow) - { - [[view superview] addSubview: view - positioned: NSWindowBelow - relativeTo: otherPeer->view]; - } - else - { - [window orderWindow: NSWindowBelow - relativeTo: [otherPeer->window windowNumber]]; - } - } -} - -void NSViewComponentPeer::setIcon (const Image& /*newIcon*/) -{ - // to do.. -} - -//============================================================================== -void NSViewComponentPeer::viewFocusGain() -{ - if (currentlyFocusedPeer != this) - { - if (ComponentPeer::isValidPeer (currentlyFocusedPeer)) - currentlyFocusedPeer->handleFocusLoss(); - - currentlyFocusedPeer = this; - handleFocusGain(); - } -} - -void NSViewComponentPeer::viewFocusLoss() -{ - if (currentlyFocusedPeer == this) - { - currentlyFocusedPeer = nullptr; - handleFocusLoss(); - } -} - -bool NSViewComponentPeer::isFocused() const -{ - return isSharedWindow ? this == currentlyFocusedPeer - : [window isKeyWindow]; -} - -void NSViewComponentPeer::grabFocus() -{ - if (window != nil) - { - [window makeKeyWindow]; - [window makeFirstResponder: view]; - - viewFocusGain(); - } -} - -void NSViewComponentPeer::textInputRequired (const Point&) -{ -} - -bool NSViewComponentPeer::handleKeyEvent (NSEvent* ev, bool isKeyDown) -{ - String unicode (nsStringToJuce ([ev characters])); - String unmodified (nsStringToJuce ([ev charactersIgnoringModifiers])); - int keyCode = getKeyCodeFromEvent (ev); - - //DBG ("unicode: " + unicode + " " + String::toHexString ((int) unicode[0])); - //DBG ("unmodified: " + unmodified + " " + String::toHexString ((int) unmodified[0])); - - if (unicode.isNotEmpty() || keyCode != 0) - { - if (isKeyDown) - { - bool used = false; - - while (unicode.length() > 0) - { - juce_wchar textCharacter = unicode[0]; - unicode = unicode.substring (1); - - if (([ev modifierFlags] & NSCommandKeyMask) != 0) - textCharacter = 0; - - used = handleKeyUpOrDown (true) || used; - used = handleKeyPress (keyCode, textCharacter) || used; - } - - return used; - } - else - { - if (handleKeyUpOrDown (false)) - return true; - } - } - - return false; -} - -bool NSViewComponentPeer::redirectKeyDown (NSEvent* ev) -{ - updateKeysDown (ev, true); - bool used = handleKeyEvent (ev, true); - - if (([ev modifierFlags] & NSCommandKeyMask) != 0) - { - // for command keys, the key-up event is thrown away, so simulate one.. - updateKeysDown (ev, false); - used = (isValidPeer (this) && handleKeyEvent (ev, false)) || used; - } - - // (If we're running modally, don't allow unused keystrokes to be passed - // along to other blocked views..) - if (Component::getCurrentlyModalComponent() != nullptr) - used = true; - - return used; -} - -bool NSViewComponentPeer::redirectKeyUp (NSEvent* ev) -{ - updateKeysDown (ev, false); - return handleKeyEvent (ev, false) - || Component::getCurrentlyModalComponent() != nullptr; -} - -void NSViewComponentPeer::redirectModKeyChange (NSEvent* ev) -{ - keysCurrentlyDown.clear(); - handleKeyUpOrDown (true); - - updateModifiers (ev); - handleModifierKeysChange(); -} - -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 -bool NSViewComponentPeer::redirectPerformKeyEquivalent (NSEvent* ev) -{ - if ([ev type] == NSKeyDown) - return redirectKeyDown (ev); - else if ([ev type] == NSKeyUp) - return redirectKeyUp (ev); - - return false; -} -#endif - -//============================================================================== -void NSViewComponentPeer::sendMouseEvent (NSEvent* ev) -{ - updateModifiers (ev); - handleMouseEvent (0, getMousePos (ev, view), currentModifiers, getMouseTime (ev)); -} - -void NSViewComponentPeer::redirectMouseDown (NSEvent* ev) -{ - currentModifiers = currentModifiers.withFlags (getModifierForButtonNumber ([ev buttonNumber])); - sendMouseEvent (ev); -} - -void NSViewComponentPeer::redirectMouseUp (NSEvent* ev) -{ - currentModifiers = currentModifiers.withoutFlags (getModifierForButtonNumber ([ev buttonNumber])); - sendMouseEvent (ev); - showArrowCursorIfNeeded(); -} - -void NSViewComponentPeer::redirectMouseDrag (NSEvent* ev) -{ - currentModifiers = currentModifiers.withFlags (getModifierForButtonNumber ([ev buttonNumber])); - sendMouseEvent (ev); -} - -void NSViewComponentPeer::redirectMouseMove (NSEvent* ev) -{ - currentModifiers = currentModifiers.withoutMouseButtons(); - sendMouseEvent (ev); - showArrowCursorIfNeeded(); -} - -void NSViewComponentPeer::redirectMouseEnter (NSEvent* ev) -{ - Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); - currentModifiers = currentModifiers.withoutMouseButtons(); - sendMouseEvent (ev); -} - -void NSViewComponentPeer::redirectMouseExit (NSEvent* ev) -{ - currentModifiers = currentModifiers.withoutMouseButtons(); - sendMouseEvent (ev); -} - -void NSViewComponentPeer::redirectMouseWheel (NSEvent* ev) -{ - updateModifiers (ev); - - MouseWheelDetails wheel; - wheel.deltaX = 0; - wheel.deltaY = 0; - wheel.isReversed = false; - wheel.isSmooth = false; - - @try - { - #if defined (MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 - if ([ev respondsToSelector: @selector (isDirectionInvertedFromDevice)]) - wheel.isReversed = [ev isDirectionInvertedFromDevice]; - - if ([ev respondsToSelector: @selector (hasPreciseScrollingDeltas)]) - { - if ([ev hasPreciseScrollingDeltas]) - { - const float scale = 0.5f / 256.0f; - wheel.deltaX = [ev scrollingDeltaX] * scale; - wheel.deltaY = [ev scrollingDeltaY] * scale; - wheel.isSmooth = true; - } - } - else - #endif - { - const float scale = 0.5f / 256.0f; - wheel.deltaX = [ev deviceDeltaX] * scale; - wheel.deltaY = [ev deviceDeltaY] * scale; - } - } - @catch (...) - {} - - if (wheel.deltaX == 0 && wheel.deltaY == 0) - { - const float scale = 10.0f / 256.0f; - wheel.deltaX = [ev deltaX] * scale; - wheel.deltaY = [ev deltaY] * scale; - } - - handleMouseWheel (0, getMousePos (ev, view), getMouseTime (ev), wheel); -} - -void NSViewComponentPeer::showArrowCursorIfNeeded() -{ - MouseInputSource& mouse = Desktop::getInstance().getMainMouseSource(); - - if (mouse.getComponentUnderMouse() == nullptr - && Desktop::getInstance().findComponentAt (mouse.getScreenPosition()) == nullptr) - { - [[NSCursor arrowCursor] set]; - } -} - -//============================================================================== -bool NSViewComponentPeer::isOpaque() -{ - return component == nullptr || component->isOpaque(); -} - -static void getClipRects (RectangleList& clip, NSView* view, - const int xOffset, const int yOffset, const int clipW, const int clipH) -{ - const NSRect* rects = nullptr; - NSInteger numRects = 0; - [view getRectsBeingDrawn: &rects count: &numRects]; - - const Rectangle clipBounds (clipW, clipH); - const CGFloat viewH = [view frame].size.height; - - for (int i = 0; i < numRects; ++i) - clip.addWithoutMerging (clipBounds.getIntersection (Rectangle (roundToInt (rects[i].origin.x) + xOffset, - roundToInt (viewH - (rects[i].origin.y + rects[i].size.height)) + yOffset, - roundToInt (rects[i].size.width), - roundToInt (rects[i].size.height)))); -} - -void NSViewComponentPeer::drawRect (NSRect r) -{ - if (r.size.width < 1.0f || r.size.height < 1.0f) - return; - - CGContextRef cg = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; - - if (! component->isOpaque()) - CGContextClearRect (cg, CGContextGetClipBoundingBox (cg)); - - #if USE_COREGRAPHICS_RENDERING - if (usingCoreGraphics) - { - CoreGraphicsContext context (cg, (float) [view frame].size.height); - - insideDrawRect = true; - handlePaint (context); - insideDrawRect = false; - } - else - #endif - { - const int xOffset = -roundToInt (r.origin.x); - const int yOffset = -roundToInt ([view frame].size.height - (r.origin.y + r.size.height)); - const int clipW = (int) (r.size.width + 0.5f); - const int clipH = (int) (r.size.height + 0.5f); - - RectangleList clip; - getClipRects (clip, view, xOffset, yOffset, clipW, clipH); - - if (! clip.isEmpty()) - { - Image temp (getComponent()->isOpaque() ? Image::RGB : Image::ARGB, - clipW, clipH, ! getComponent()->isOpaque()); - - { - ScopedPointer context (component->getLookAndFeel() - .createGraphicsContext (temp, Point (xOffset, yOffset), clip)); - - insideDrawRect = true; - handlePaint (*context); - insideDrawRect = false; - } - - CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); - CGImageRef image = juce_createCoreGraphicsImage (temp, false, colourSpace, false); - CGColorSpaceRelease (colourSpace); - CGContextDrawImage (cg, CGRectMake (r.origin.x, r.origin.y, clipW, clipH), image); - CGImageRelease (image); - } - } -} - -StringArray NSViewComponentPeer::getAvailableRenderingEngines() -{ - StringArray s (ComponentPeer::getAvailableRenderingEngines()); - - #if USE_COREGRAPHICS_RENDERING - s.add ("CoreGraphics Renderer"); - #endif - - return s; -} - -int NSViewComponentPeer::getCurrentRenderingEngine() const -{ - return usingCoreGraphics ? 1 : 0; -} - -void NSViewComponentPeer::setCurrentRenderingEngine (int index) -{ - #if USE_COREGRAPHICS_RENDERING - if (usingCoreGraphics != (index > 0)) - { - usingCoreGraphics = index > 0; - [view setNeedsDisplay: true]; - } - #endif -} - -bool NSViewComponentPeer::canBecomeKeyWindow() -{ - return (getStyleFlags() & juce::ComponentPeer::windowIgnoresKeyPresses) == 0; -} - -void NSViewComponentPeer::becomeKeyWindow() -{ - handleBroughtToFront(); - grabFocus(); -} - -bool NSViewComponentPeer::windowShouldClose() -{ - if (! isValidPeer (this)) - return YES; - - handleUserClosingWindow(); - return NO; -} - -void NSViewComponentPeer::updateFullscreenStatus() -{ - if (hasNativeTitleBar()) - { - const Rectangle screen (getFrameSize().subtractedFrom (component->getParentMonitorArea())); - const Rectangle window (component->getScreenBounds()); - - fullScreen = std::abs (screen.getX() - window.getX()) <= 2 - && std::abs (screen.getY() - window.getY()) <= 2 - && std::abs (screen.getRight() - window.getRight()) <= 2 - && std::abs (screen.getBottom() - window.getBottom()) <= 2; - } -} - -void NSViewComponentPeer::redirectMovedOrResized() -{ - updateFullscreenStatus(); - handleMovedOrResized(); -} - -void NSViewComponentPeer::viewMovedToWindow() -{ - if (isSharedWindow) - window = [view window]; -} //============================================================================== void Desktop::createMouseInputSources() @@ -1847,43 +1819,6 @@ void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDis } //============================================================================== -void NSViewComponentPeer::repaint (const Rectangle& area) -{ - if (insideDrawRect) - { - class AsyncRepaintMessage : public CallbackMessage - { - public: - AsyncRepaintMessage (NSViewComponentPeer* const peer_, const Rectangle& rect_) - : peer (peer_), rect (rect_) - { - } - - void messageCallback() - { - if (ComponentPeer::isValidPeer (peer)) - peer->repaint (rect); - } - - private: - NSViewComponentPeer* const peer; - const Rectangle rect; - }; - - (new AsyncRepaintMessage (this, area))->post(); - } - else - { - [view setNeedsDisplayInRect: NSMakeRect ((CGFloat) area.getX(), [view frame].size.height - (CGFloat) area.getBottom(), - (CGFloat) area.getWidth(), (CGFloat) area.getHeight())]; - } -} - -void NSViewComponentPeer::performAnyPendingRepaintsNow() -{ - [view displayIfNeeded]; -} - ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo) { return new NSViewComponentPeer (this, styleFlags, (NSView*) windowToAttachTo); diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp index d8816f8382..c030c12e4d 100644 --- a/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/modules/juce_gui_extra/juce_gui_extra.cpp @@ -106,7 +106,6 @@ namespace juce //============================================================================== #if JUCE_MAC || JUCE_IOS #include "../juce_core/native/juce_osx_ObjCHelpers.h" - #include "../juce_core/native/juce_mac_ObjCSuffix.h" #include "../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" #if JUCE_MAC diff --git a/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm b/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm index 5ebd6bf6ce..a6f38681b7 100644 --- a/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm +++ b/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm @@ -23,66 +23,57 @@ ============================================================================== */ -} // (juce namespace) - -class WebBrowserComponentInternal; - #if JUCE_MAC -#define DownloadClickDetector MakeObjCClassName(DownloadClickDetector) - -@interface DownloadClickDetector : NSObject +struct DownloadClickDetectorClass : public ObjCClass { - juce::WebBrowserComponent* ownerComponent; -} - -- (DownloadClickDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent; - -- (void) webView: (WebView*) webView decidePolicyForNavigationAction: (NSDictionary*) actionInformation - request: (NSURLRequest*) request - frame: (WebFrame*) frame - decisionListener: (id ) listener; -- (void) webView: (WebView*) webView didFinishLoadForFrame: (WebFrame*) frame; - -@end - -@implementation DownloadClickDetector - -- (DownloadClickDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent_ -{ - [super init]; - ownerComponent = ownerComponent_; - return self; -} - -- (void) webView: (WebView*) sender decidePolicyForNavigationAction: (NSDictionary*) actionInformation - request: (NSURLRequest*) request - frame: (WebFrame*) frame - decisionListener: (id ) listener -{ - (void) sender; (void) request; (void) frame; - - NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")]; - - if (ownerComponent->pageAboutToLoad (nsStringToJuce ([url absoluteString]))) - [listener use]; - else - [listener ignore]; -} - -- (void) webView: (WebView*) sender didFinishLoadForFrame: (WebFrame*) frame -{ - if ([frame isEqual: [sender mainFrame]]) + DownloadClickDetectorClass() : ObjCClass ("JUCEWebClickDetector_") { - NSURL* url = [[[frame dataSource] request] URL]; - ownerComponent->pageFinishedLoading (nsStringToJuce ([url absoluteString])); - } -} + addIvar ("owner"); -@end + addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:), + decidePolicyForNavigationAction, "v@:@@@@@"); + addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@"); + + registerClass(); + } + + static void setOwner (id self, WebBrowserComponent* owner) + { + object_setInstanceVariable (self, "owner", owner); + } + +private: + static WebBrowserComponent* getOwner (id self) + { + return getIvar (self, "owner"); + } + + static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation, + NSURLRequest*, WebFrame*, id listener) + { + NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")]; + + if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([url absoluteString]))) + [listener use]; + else + [listener ignore]; + } + + static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame) + { + if ([frame isEqual: [sender mainFrame]]) + { + NSURL* url = [[[frame dataSource] request] URL]; + getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString])); + } + } +}; #else +} // (juce namespace) + //============================================================================== @interface WebViewTapDetector : NSObject { @@ -126,10 +117,10 @@ class WebBrowserComponentInternal; return ownerComponent->pageAboutToLoad (nsStringToJuce (request.URL.absoluteString)); } @end -#endif -namespace juce -{ +namespace juce { + +#endif //============================================================================== class WebBrowserComponentInternal @@ -148,7 +139,9 @@ public: groupName: nsEmptyString()]; setView (webView); - clickListener = [[DownloadClickDetector alloc] initWithWebBrowserOwner: owner]; + static DownloadClickDetectorClass cls; + clickListener = [cls.createInstance() init]; + DownloadClickDetectorClass::setOwner (clickListener, owner); [webView setPolicyDelegate: clickListener]; [webView setFrameLoadDelegate: clickListener]; #else @@ -238,7 +231,7 @@ public: private: #if JUCE_MAC WebView* webView; - DownloadClickDetector* clickListener; + NSObject* clickListener; #else UIWebView* webView; WebViewTapDetector* tapDetector; diff --git a/modules/juce_opengl/juce_opengl.cpp b/modules/juce_opengl/juce_opengl.cpp index d31c5e417a..034d244d60 100644 --- a/modules/juce_opengl/juce_opengl.cpp +++ b/modules/juce_opengl/juce_opengl.cpp @@ -171,7 +171,6 @@ static void clearGLError() //============================================================================== #if JUCE_MAC || JUCE_IOS #include "../juce_core/native/juce_osx_ObjCHelpers.h" - #include "../juce_core/native/juce_mac_ObjCSuffix.h" #include "../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" #if JUCE_MAC diff --git a/modules/juce_opengl/native/juce_OpenGL_osx.h b/modules/juce_opengl/native/juce_OpenGL_osx.h index 339df6c4b7..b0cc3c6520 100644 --- a/modules/juce_opengl/native/juce_OpenGL_osx.h +++ b/modules/juce_opengl/native/juce_OpenGL_osx.h @@ -23,92 +23,78 @@ ============================================================================== */ -} // (juce namespace) -#define ThreadSafeNSOpenGLView MakeObjCClassName(ThreadSafeNSOpenGLView) - -//============================================================================== -@interface ThreadSafeNSOpenGLView : NSOpenGLView +struct ThreadSafeNSOpenGLViewClass : public ObjCClass { - juce::CriticalSection* contextLock; - bool needsUpdate; -} - -- (id) initWithFrame: (NSRect) frameRect pixelFormat: (NSOpenGLPixelFormat*) format; -- (bool) makeActive; -- (void) reshape; -- (void) rightMouseDown: (NSEvent*) ev; -- (void) rightMouseUp: (NSEvent*) ev; -@end - -@implementation ThreadSafeNSOpenGLView - -- (id) initWithFrame: (NSRect) frameRect - pixelFormat: (NSOpenGLPixelFormat*) format -{ - contextLock = new juce::CriticalSection(); - self = [super initWithFrame: frameRect pixelFormat: format]; - needsUpdate = true; - - if (self != nil) - [[NSNotificationCenter defaultCenter] addObserver: self - selector: @selector (_surfaceNeedsUpdate:) - name: NSViewGlobalFrameDidChangeNotification - object: self]; - return self; -} - -- (void) dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver: self]; - delete contextLock; - [super dealloc]; -} - -- (bool) makeActive -{ - const juce::ScopedLock sl (*contextLock); - - if ([self openGLContext] == nil) - return false; - - [[self openGLContext] makeCurrentContext]; - - if (needsUpdate) + ThreadSafeNSOpenGLViewClass() : ObjCClass ("JUCEGLView_") { - [super update]; - needsUpdate = false; + addIvar ("lock"); + addIvar ("needsUpdate"); + + addMethod (@selector (update), update, "v@:"); + addMethod (@selector (reshape), reshape, "v@:"); + addMethod (@selector (_surfaceNeedsUpdate:), surfaceNeedsUpdate, "v@:@"); + addMethod (@selector (rightMouseDown:), rightMouseDown, "v@:@"); + addMethod (@selector (rightMouseUp:), rightMouseUp, "v@:@"); + + registerClass(); } - return true; -} + static void init (id self) + { + object_setInstanceVariable (self, "lock", new CriticalSection()); + setNeedsUpdate (self, YES); + } -- (void) _surfaceNeedsUpdate: (NSNotification*) notification -{ - (void) notification; - const juce::ScopedLock sl (*contextLock); - needsUpdate = true; -} + static bool makeActive (id self) + { + const ScopedLock sl (*getLock (self)); -- (void) update -{ - const juce::ScopedLock sl (*contextLock); - needsUpdate = true; -} + if ([(NSOpenGLView*) self openGLContext] == nil) + return false; -- (void) reshape -{ - const juce::ScopedLock sl (*contextLock); - needsUpdate = true; -} + [[(NSOpenGLView*) self openGLContext] makeCurrentContext]; -- (void) rightMouseDown: (NSEvent*) ev { [[self superview] rightMouseDown: ev]; } -- (void) rightMouseUp: (NSEvent*) ev { [[self superview] rightMouseUp: ev]; } + if (getIvar (self, "needsUpdate")) + { + sendSuperclassMessage (self, @selector (update)); + setNeedsUpdate (self, NO); + } -@end + return true; + } + +private: + static CriticalSection* getLock (id self) + { + return getIvar (self, "lock"); + } + + static void setNeedsUpdate (id self, BOOL b) + { + object_setInstanceVariable (self, "needsUpdate", (void*) b); + } + + static void setNeedsUpdateLocked (id self, BOOL b) + { + const ScopedLock sl (*getLock (self)); + setNeedsUpdate (self, b); + } + + static void dealloc (id self, SEL) + { + delete getLock (self); + sendSuperclassMessage (self, @selector (dealloc)); + } + + static void surfaceNeedsUpdate (id self, SEL, NSNotification*) { setNeedsUpdateLocked (self, YES); } + static void update (id self, SEL) { setNeedsUpdateLocked (self, YES); } + static void reshape (id self, SEL) { setNeedsUpdateLocked (self, YES); } + + static void rightMouseDown (id self, SEL, NSEvent* ev) { [[(NSOpenGLView*) self superview] rightMouseDown: ev]; } + static void rightMouseUp (id self, SEL, NSEvent* ev) { [[(NSOpenGLView*) self superview] rightMouseUp: ev]; } +}; -namespace juce -{ //============================================================================== class OpenGLContext::NativeContext @@ -137,8 +123,15 @@ public: NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes: attribs]; - view = [[ThreadSafeNSOpenGLView alloc] initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f) - pixelFormat: format]; + static ThreadSafeNSOpenGLViewClass cls; + view = [cls.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f) + pixelFormat: format]; + ThreadSafeNSOpenGLViewClass::init (view); + + [[NSNotificationCenter defaultCenter] addObserver: view + selector: @selector (_surfaceNeedsUpdate:) + name: NSViewGlobalFrameDidChangeNotification + object: view]; renderContext = [[[NSOpenGLContext alloc] initWithFormat: format shareContext: (NSOpenGLContext*) contextToShareWith] autorelease]; @@ -153,6 +146,7 @@ public: ~NativeContext() { + [[NSNotificationCenter defaultCenter] removeObserver: view]; [renderContext clearDrawable]; [renderContext setView: nil]; [view setOpenGLContext: nil]; @@ -173,7 +167,7 @@ public: if ([renderContext view] != view) [renderContext setView: view]; - [view makeActive]; + ThreadSafeNSOpenGLViewClass::makeActive (view); return true; } @@ -228,7 +222,7 @@ public: private: NSOpenGLContext* renderContext; - ThreadSafeNSOpenGLView* view; + NSOpenGLView* view; ReferenceCountedObjectPtr viewAttachment; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext); diff --git a/modules/juce_video/juce_video.cpp b/modules/juce_video/juce_video.cpp index bd58751a64..6c7c8139cc 100644 --- a/modules/juce_video/juce_video.cpp +++ b/modules/juce_video/juce_video.cpp @@ -108,7 +108,6 @@ namespace juce #if JUCE_MAC || JUCE_IOS #include "../juce_core/native/juce_osx_ObjCHelpers.h" - #include "../juce_core/native/juce_mac_ObjCSuffix.h" #if JUCE_USE_CAMERA #include "native/juce_mac_CameraDevice.mm" diff --git a/modules/juce_video/native/juce_mac_CameraDevice.mm b/modules/juce_video/native/juce_mac_CameraDevice.mm index 817fc377f6..e061f5e98e 100644 --- a/modules/juce_video/native/juce_mac_CameraDevice.mm +++ b/modules/juce_video/native/juce_mac_CameraDevice.mm @@ -27,51 +27,21 @@ #error "On the Mac, cameras use Quicktime, so if you turn on JUCE_USE_CAMERA, you also need to enable JUCE_QUICKTIME" #endif -//============================================================================== -#define QTCaptureCallbackDelegate MakeObjCClassName(QTCaptureCallbackDelegate) - -class QTCameraDeviceInteral; - -} // (juce namespace) - -@interface QTCaptureCallbackDelegate : NSObject -{ -@public - CameraDevice* owner; - QTCameraDeviceInteral* internal; - int64 firstPresentationTime; - int64 averageTimeOffset; -} - -- (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner internalDev: (QTCameraDeviceInteral*) d; -- (void) dealloc; - -- (void) captureOutput: (QTCaptureOutput*) captureOutput - didOutputVideoFrame: (CVImageBufferRef) videoFrame - withSampleBuffer: (QTSampleBuffer*) sampleBuffer - fromConnection: (QTCaptureConnection*) connection; - -- (void) captureOutput: (QTCaptureFileOutput*) captureOutput - didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer - fromConnection: (QTCaptureConnection*) connection; -@end - -namespace juce -{ - extern Image juce_createImageFromCIImage (CIImage* im, int w, int h); //============================================================================== -class QTCameraDeviceInteral +class QTCameraDeviceInternal { public: - QTCameraDeviceInteral (CameraDevice* owner, const int index) + QTCameraDeviceInternal (CameraDevice* owner, const int index) : input (nil), audioDevice (nil), audioInput (nil), session (nil), fileOutput (nil), - imageOutput (nil) + imageOutput (nil), + firstPresentationTime (0), + averageTimeOffset (0) { JUCE_AUTORELEASEPOOL @@ -79,8 +49,10 @@ public: NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo]; device = (QTCaptureDevice*) [devs objectAtIndex: index]; - callbackDelegate = [[QTCaptureCallbackDelegate alloc] initWithOwner: owner - internalDev: this]; + + static DelegateClass cls; + callbackDelegate = [cls.createInstance() init]; + DelegateClass::setOwner (callbackDelegate, this); NSError* err = nil; [device retain]; @@ -112,7 +84,7 @@ public: DBG (openingError); } - ~QTCameraDeviceInteral() + ~QTCameraDeviceInternal() { [session stopRunning]; [session removeOutput: imageOutput]; @@ -194,6 +166,33 @@ public: } } + void captureBuffer (QTSampleBuffer* sampleBuffer) + { + const Time now (Time::getCurrentTime()); + + #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: QTSampleBufferHostTimeAttribute]; + #else + NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: nsStringLiteral ("hostTime")]; + #endif + + int64 presentationTime = (hosttime != nil) + ? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40) + : (([sampleBuffer presentationTime].timeValue * 1000) / [sampleBuffer presentationTime].timeScale + 50); + + const int64 timeDiff = now.toMilliseconds() - presentationTime; + + if (firstPresentationTime == 0) + { + firstPresentationTime = presentationTime; + averageTimeOffset = timeDiff; + } + else + { + averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128; + } + } + QTCaptureDevice* device; QTCaptureDeviceInput* input; QTCaptureDevice* audioDevice; @@ -201,87 +200,69 @@ public: QTCaptureSession* session; QTCaptureMovieFileOutput* fileOutput; QTCaptureDecompressedVideoOutput* imageOutput; - QTCaptureCallbackDelegate* callbackDelegate; + NSObject* callbackDelegate; String openingError; + int64 firstPresentationTime; + int64 averageTimeOffset; Array listeners; CriticalSection listenerLock; + +private: + //============================================================================== + struct DelegateClass : public ObjCClass + { + DelegateClass() : ObjCClass ("JUCEAppDelegate_") + { + addIvar ("owner"); + + addMethod (@selector (captureOutput:didOutputVideoFrame:withSampleBuffer:fromConnection:), + didOutputVideoFrame, "v@:@", @encode (CVImageBufferRef), "@@"); + addMethod (@selector (captureOutput:didOutputSampleBuffer:fromConnection:), + didOutputVideoFrame, "v@:@@@"); + + registerClass(); + } + + static void setOwner (id self, QTCameraDeviceInternal* owner) + { + object_setInstanceVariable (self, "owner", owner); + } + + private: + static QTCameraDeviceInternal* getOwner (id self) + { + return getIvar (self, "owner"); + } + + static void didOutputVideoFrame (id self, SEL, QTCaptureOutput* captureOutput, + CVImageBufferRef videoFrame, QTSampleBuffer* sampleBuffer, + QTCaptureConnection* connection) + { + QTCameraDeviceInternal* const internal = getOwner (self); + + if (internal->listeners.size() > 0) + { + JUCE_AUTORELEASEPOOL + + internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame], + CVPixelBufferGetWidth (videoFrame), + CVPixelBufferGetHeight (videoFrame)); + } + } + + static void didOutputSampleBuffer (id self, SEL, QTCaptureFileOutput*, QTSampleBuffer* sampleBuffer, QTCaptureConnection*) + { + getOwner (self)->captureBuffer (sampleBuffer); + } + }; }; -} // (juce namespace) - -@implementation QTCaptureCallbackDelegate - -- (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner_ - internalDev: (QTCameraDeviceInteral*) d -{ - [super init]; - owner = owner_; - internal = d; - firstPresentationTime = 0; - averageTimeOffset = 0; - return self; -} - -- (void) dealloc -{ - [super dealloc]; -} - -- (void) captureOutput: (QTCaptureOutput*) captureOutput - didOutputVideoFrame: (CVImageBufferRef) videoFrame - withSampleBuffer: (QTSampleBuffer*) sampleBuffer - fromConnection: (QTCaptureConnection*) connection -{ - if (internal->listeners.size() > 0) - { - JUCE_AUTORELEASEPOOL - - internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame], - CVPixelBufferGetWidth (videoFrame), - CVPixelBufferGetHeight (videoFrame)); - } -} - -- (void) captureOutput: (QTCaptureFileOutput*) captureOutput - didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer - fromConnection: (QTCaptureConnection*) connection -{ - const Time now (Time::getCurrentTime()); - - #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 - NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: QTSampleBufferHostTimeAttribute]; - #else - NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: nsStringLiteral ("hostTime")]; - #endif - - int64 presentationTime = (hosttime != nil) - ? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40) - : (([sampleBuffer presentationTime].timeValue * 1000) / [sampleBuffer presentationTime].timeScale + 50); - - const int64 timeDiff = now.toMilliseconds() - presentationTime; - - if (firstPresentationTime == 0) - { - firstPresentationTime = presentationTime; - averageTimeOffset = timeDiff; - } - else - { - averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128; - } -} - -@end - -namespace juce -{ - //============================================================================== class QTCaptureViewerComp : public NSViewComponent { public: - QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInteral* const internal) + QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInternal* const internal) { JUCE_AUTORELEASEPOOL captureView = [[QTCaptureView alloc] init]; @@ -306,19 +287,19 @@ CameraDevice::CameraDevice (const String& name_, int index) : name (name_) { isRecording = false; - internal = new QTCameraDeviceInteral (this, index); + internal = new QTCameraDeviceInternal (this, index); } CameraDevice::~CameraDevice() { stopRecording(); - delete static_cast (internal); + delete static_cast (internal); internal = nullptr; } Component* CameraDevice::createViewerComponent() { - return new QTCaptureViewerComp (this, static_cast (internal)); + return new QTCaptureViewerComp (this, static_cast (internal)); } String CameraDevice::getFileExtension() @@ -330,8 +311,8 @@ void CameraDevice::startRecordingToFile (const File& file, int quality) { stopRecording(); - QTCameraDeviceInteral* const d = static_cast (internal); - d->callbackDelegate->firstPresentationTime = 0; + QTCameraDeviceInternal* const d = static_cast (internal); + d->firstPresentationTime = 0; file.deleteFile(); // In some versions of QT (e.g. on 10.5), if you record video without audio, the speed comes @@ -367,9 +348,9 @@ void CameraDevice::startRecordingToFile (const File& file, int quality) Time CameraDevice::getTimeOfFirstRecordedFrame() const { - QTCameraDeviceInteral* const d = static_cast (internal); - if (d->callbackDelegate->firstPresentationTime != 0) - return Time (d->callbackDelegate->firstPresentationTime + d->callbackDelegate->averageTimeOffset); + QTCameraDeviceInternal* const d = static_cast (internal); + if (d->firstPresentationTime != 0) + return Time (d->firstPresentationTime + d->averageTimeOffset); return Time(); } @@ -378,7 +359,7 @@ void CameraDevice::stopRecording() { if (isRecording) { - static_cast (internal)->resetFile(); + static_cast (internal)->resetFile(); isRecording = false; } } @@ -386,13 +367,13 @@ void CameraDevice::stopRecording() void CameraDevice::addListener (Listener* listenerToAdd) { if (listenerToAdd != nullptr) - static_cast (internal)->addListener (listenerToAdd); + static_cast (internal)->addListener (listenerToAdd); } void CameraDevice::removeListener (Listener* listenerToRemove) { if (listenerToRemove != nullptr) - static_cast (internal)->removeListener (listenerToRemove); + static_cast (internal)->removeListener (listenerToRemove); } //============================================================================== @@ -418,7 +399,7 @@ CameraDevice* CameraDevice::openDevice (int index, { ScopedPointer d (new CameraDevice (getAvailableDevices() [index], index)); - if (static_cast (d->internal)->openingError.isEmpty()) + if (static_cast (d->internal)->openingError.isEmpty()) return d.release(); return nullptr; diff --git a/modules/juce_video/native/juce_mac_QuickTimeMovieComponent.mm b/modules/juce_video/native/juce_mac_QuickTimeMovieComponent.mm index c1a8057b90..c7b806b55a 100644 --- a/modules/juce_video/native/juce_mac_QuickTimeMovieComponent.mm +++ b/modules/juce_video/native/juce_mac_QuickTimeMovieComponent.mm @@ -25,49 +25,31 @@ #if JUCE_QUICKTIME -} // (juce namespace) - -//============================================================================== -#define NonInterceptingQTMovieView MakeObjCClassName(NonInterceptingQTMovieView) - -@interface NonInterceptingQTMovieView : QTMovieView +struct NonInterceptingQTMovieViewClass : public ObjCClass { -} + NonInterceptingQTMovieViewClass() : ObjCClass ("JUCEQTMovieView_") + { + addMethod (@selector (hitTest:), hitTest, "@@:", @encode (NSPoint)); + addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse, "c@:@"); -- (id) initWithFrame: (NSRect) frame; -- (BOOL) acceptsFirstMouse: (NSEvent*) theEvent; -- (NSView*) hitTest: (NSPoint) p; + registerClass(); + } -@end +private: + static NSView* hitTest (id self, SEL, NSPoint point) + { + if (! [(QTMovieView*) self isControllerVisible]) + return nil; -@implementation NonInterceptingQTMovieView + objc_super s = { self, [QTMovieView class] }; + return objc_msgSendSuper (&s, @selector (hitTest:), point); + } -- (id) initWithFrame: (NSRect) frame -{ - self = [super initWithFrame: frame]; - [self setNextResponder: [self superview]]; - return self; -} - -- (void) dealloc -{ - [super dealloc]; -} - -- (NSView*) hitTest: (NSPoint) point -{ - return [self isControllerVisible] ? [super hitTest: point] : nil; -} - -- (BOOL) acceptsFirstMouse: (NSEvent*) theEvent -{ - return YES; -} - -@end - -namespace juce -{ + static BOOL acceptsFirstMouse (id, SEL, NSEvent*) + { + return YES; + } +}; //============================================================================== #define theMovie (static_cast (movie)) @@ -79,8 +61,10 @@ QuickTimeMovieComponent::QuickTimeMovieComponent() setOpaque (true); setVisible (true); - QTMovieView* view = [[NonInterceptingQTMovieView alloc] initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]; + static NonInterceptingQTMovieViewClass cls; + QTMovieView* view = [cls.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]; setView (view); + [view setNextResponder: [view superview]]; [view setWantsLayer: YES]; // prevents the view failing to redraw correctly when paused. [view release]; }