| @@ -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)); | |||
| @@ -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" | |||
| @@ -25,305 +25,256 @@ | |||
| const int kilobytesPerSecond1x = 176; | |||
| } // (juce namespace) | |||
| #define OpenDiskDevice MakeObjCClassName(OpenDiskDevice) | |||
| @interface OpenDiskDevice : NSObject | |||
| struct AudioTrackProducerClass : public ObjCClass <NSObject> | |||
| { | |||
| @public | |||
| 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 | |||
| AudioTrackProducerClass() : ObjCClass ("JUCEAudioTrackProducer_") | |||
| { | |||
| addIvar<AudioSourceHolder*> ("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(); | |||
| } | |||
| - (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device_ | |||
| { | |||
| [super init]; | |||
| struct AudioSourceHolder | |||
| { | |||
| AudioSourceHolder (AudioSource* source_, int numFrames) | |||
| : source (source_), readPosition (0), lengthInFrames (numFrames) | |||
| { | |||
| } | |||
| device = device_; | |||
| tracks = [[NSMutableArray alloc] init]; | |||
| underrunProtection = true; | |||
| return self; | |||
| } | |||
| ~AudioSourceHolder() | |||
| { | |||
| if (source != nullptr) | |||
| source->releaseResources(); | |||
| } | |||
| - (void) dealloc | |||
| { | |||
| [tracks release]; | |||
| [super dealloc]; | |||
| } | |||
| ScopedPointer<AudioSource> source; | |||
| int readPosition, lengthInFrames; | |||
| }; | |||
| - (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]; | |||
| private: | |||
| static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source) | |||
| { | |||
| self = sendSuperclassMessage (self, @selector (init)); | |||
| object_setInstanceVariable (self, "source", source); | |||
| return self; | |||
| } | |||
| [tracks addObject: t]; | |||
| static AudioSourceHolder* getSource (id self) | |||
| { | |||
| return getIvar<AudioSourceHolder*> (self, "source"); | |||
| } | |||
| [t release]; | |||
| [p release]; | |||
| } | |||
| static void dealloc (id self, SEL) | |||
| { | |||
| delete getSource (self); | |||
| sendSuperclassMessage (self, @selector (dealloc)); | |||
| } | |||
| - (void) burn: (juce::AudioCDBurner::BurnProgressListener*) listener errorString: (juce::String*) error | |||
| ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed | |||
| { | |||
| DRBurn* burn = [DRBurn burnForDevice: device]; | |||
| static void cleanupTrackAfterBurn (id self, SEL, DRTrack*) {} | |||
| static BOOL cleanupTrackAfterVerification (id self, SEL, DRTrack*) { return true; } | |||
| if (! [device acquireExclusiveAccess]) | |||
| static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*) | |||
| { | |||
| *error = "Couldn't open or write to the CD device"; | |||
| return; | |||
| return getSource (self)->lengthInFrames; | |||
| } | |||
| [device acquireMediaReservation]; | |||
| static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*) | |||
| { | |||
| AudioSourceHolder* const source = getSource (self); | |||
| 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 (source != nullptr) | |||
| { | |||
| source->source->prepareToPlay (44100 / 75, 44100); | |||
| source->readPosition = 0; | |||
| } | |||
| if (burnSpeed > 0) | |||
| [d setObject: [NSNumber numberWithFloat: burnSpeed * juce::kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey]; | |||
| return true; | |||
| } | |||
| if (! underrunProtection) | |||
| [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey]; | |||
| static BOOL prepareTrackForVerification (id self, SEL, DRTrack*) | |||
| { | |||
| AudioSourceHolder* const source = getSource (self); | |||
| [burn setProperties: d]; | |||
| if (source != nullptr) | |||
| source->source->prepareToPlay (44100 / 75, 44100); | |||
| [burn writeLayout: tracks]; | |||
| return true; | |||
| } | |||
| for (;;) | |||
| static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer, | |||
| uint32_t bufferLength, uint64_t /*address*/, | |||
| uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||
| { | |||
| juce::Thread::sleep (300); | |||
| float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue]; | |||
| AudioSourceHolder* const source = getSource (self); | |||
| if (listener != nullptr && listener->audioCDBurnProgress (progress)) | |||
| if (source != nullptr) | |||
| { | |||
| [burn abort]; | |||
| *error = "User cancelled the write operation"; | |||
| break; | |||
| } | |||
| const int numSamples = jmin ((int) bufferLength / 4, | |||
| (source->lengthInFrames * (44100 / 75)) - source->readPosition); | |||
| 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 (numSamples > 0) | |||
| { | |||
| AudioSampleBuffer tempBuffer (2, numSamples); | |||
| AudioSourceChannelInfo info (tempBuffer); | |||
| source->source->getNextAudioBlock (info); | |||
| typedef AudioData::Pointer <AudioData::Int16, | |||
| AudioData::LittleEndian, | |||
| AudioData::Interleaved, | |||
| AudioData::NonConst> CDSampleFormat; | |||
| typedef AudioData::Pointer <AudioData::Float32, | |||
| AudioData::NativeEndian, | |||
| AudioData::NonInterleaved, | |||
| AudioData::Const> 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; | |||
| } | |||
| if ([err length] > 0) | |||
| { | |||
| *error = juce::CharPointer_UTF8 ([err UTF8String]); | |||
| break; | |||
| return numSamples * 4; | |||
| } | |||
| return 0; | |||
| } | |||
| [device releaseMediaReservation]; | |||
| [device releaseExclusiveAccess]; | |||
| } | |||
| @end | |||
| 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; | |||
| } | |||
| //============================================================================== | |||
| @implementation AudioTrackProducer | |||
| static BOOL verifyDataForTrack (id self, SEL, DRTrack*, const char*, | |||
| uint32_t /*bufferLength*/, uint64_t /*address*/, | |||
| uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||
| { | |||
| return true; | |||
| } | |||
| }; | |||
| - (AudioTrackProducer*) init: (int) lengthInFrames_ | |||
| struct OpenDiskDevice | |||
| { | |||
| lengthInFrames = lengthInFrames_; | |||
| readPosition = 0; | |||
| return self; | |||
| } | |||
| OpenDiskDevice (DRDevice* device_) | |||
| : device (device_), | |||
| tracks ([[NSMutableArray alloc] init]), | |||
| underrunProtection (true) | |||
| { | |||
| } | |||
| - (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]; | |||
| ~OpenDiskDevice() | |||
| { | |||
| [tracks release]; | |||
| } | |||
| void addSourceTrack (AudioSource* source, int numSamples) | |||
| { | |||
| if (source != nullptr) | |||
| { | |||
| const int numFrames = (numSamples + 587) / 588; | |||
| [track setProperties: p]; | |||
| [p release]; | |||
| } | |||
| static AudioTrackProducerClass cls; | |||
| - (AudioTrackProducer*) initWithAudioSource: (juce::AudioSource*) source_ numSamples: (int) lengthInSamples | |||
| { | |||
| AudioTrackProducer* s = [self init: (lengthInSamples + 587) / 588]; | |||
| NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:) | |||
| withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)]; | |||
| DRTrack* track = [[DRTrack alloc] initWithProducer: producer]; | |||
| if (s != nil) | |||
| s->source = source_; | |||
| { | |||
| 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]; | |||
| } | |||
| return s; | |||
| } | |||
| [tracks addObject: track]; | |||
| - (void) dealloc | |||
| { | |||
| if (source != nullptr) | |||
| { | |||
| source->releaseResources(); | |||
| delete source; | |||
| [track release]; | |||
| [producer release]; | |||
| } | |||
| } | |||
| [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; | |||
| } | |||
| String burn (AudioCDBurner::BurnProgressListener* listener, | |||
| bool shouldEject, bool peformFakeBurnForTesting, int burnSpeed) | |||
| { | |||
| DRBurn* burn = [DRBurn burnForDevice: device]; | |||
| - (BOOL) prepareTrack: (DRTrack*) track forBurn: (DRBurn*) burn | |||
| toMedia: (NSDictionary*) mediaInfo | |||
| { | |||
| (void) track; (void) burn; (void) mediaInfo; | |||
| if (! [device acquireExclusiveAccess]) | |||
| return "Couldn't open or write to the CD device"; | |||
| if (source != nullptr) | |||
| source->prepareToPlay (44100 / 75, 44100); | |||
| [device acquireMediaReservation]; | |||
| readPosition = 0; | |||
| return true; | |||
| } | |||
| 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]; | |||
| - (BOOL) prepareTrackForVerification: (DRTrack*) track | |||
| { | |||
| (void) track; | |||
| if (source != nullptr) | |||
| source->prepareToPlay (44100 / 75, 44100); | |||
| if (burnSpeed > 0) | |||
| [d setObject: [NSNumber numberWithFloat: burnSpeed * kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey]; | |||
| return true; | |||
| } | |||
| if (! underrunProtection) | |||
| [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey]; | |||
| - (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; | |||
| [burn setProperties: d]; | |||
| if (source != nullptr) | |||
| { | |||
| const int numSamples = juce::jmin ((int) bufferLength / 4, (lengthInFrames * (44100 / 75)) - readPosition); | |||
| [burn writeLayout: tracks]; | |||
| if (numSamples > 0) | |||
| for (;;) | |||
| { | |||
| juce::AudioSampleBuffer tempBuffer (2, numSamples); | |||
| juce::AudioSourceChannelInfo info (tempBuffer); | |||
| source->getNextAudioBlock (info); | |||
| typedef juce::AudioData::Pointer <juce::AudioData::Int16, | |||
| juce::AudioData::LittleEndian, | |||
| juce::AudioData::Interleaved, | |||
| juce::AudioData::NonConst> CDSampleFormat; | |||
| typedef juce::AudioData::Pointer <juce::AudioData::Float32, | |||
| juce::AudioData::NativeEndian, | |||
| juce::AudioData::NonInterleaved, | |||
| juce::AudioData::Const> 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; | |||
| } | |||
| Thread::sleep (300); | |||
| float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue]; | |||
| return numSamples * 4; | |||
| } | |||
| if (listener != nullptr && listener->audioCDBurnProgress (progress)) | |||
| { | |||
| [burn abort]; | |||
| return "User cancelled the write operation"; | |||
| } | |||
| return 0; | |||
| } | |||
| if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed]) | |||
| return "Write operation failed"; | |||
| - (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; | |||
| } | |||
| if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone]) | |||
| break; | |||
| - (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; | |||
| } | |||
| NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey] | |||
| objectForKey: DRErrorStatusErrorStringKey]; | |||
| @end | |||
| if ([err length] > 0) | |||
| return CharPointer_UTF8 ([err UTF8String]); | |||
| } | |||
| [device releaseMediaReservation]; | |||
| [device releaseExclusiveAccess]; | |||
| return String::empty; | |||
| } | |||
| namespace juce | |||
| { | |||
| DRDevice* device; | |||
| NSMutableArray* tracks; | |||
| bool underrunProtection; | |||
| }; | |||
| //============================================================================== | |||
| 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<int> getAvailableWriteSpeeds() const | |||
| { | |||
| @@ -422,7 +373,7 @@ public: | |||
| objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; | |||
| } | |||
| OpenDiskDevice* device; | |||
| ScopedPointer<OpenDiskDevice> 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; | |||
| [pimpl->device burn: listener | |||
| errorString: &error | |||
| ejectAfterwards: ejectDiscAfterwards | |||
| isFake: performFakeBurnForTesting | |||
| speed: writeSpeed]; | |||
| } | |||
| return pimpl->device->burn (listener, ejectDiscAfterwards, performFakeBurnForTesting, writeSpeed); | |||
| return error; | |||
| return "Couldn't open or write to the CD device"; | |||
| } | |||
| @@ -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<void*> 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 <AUCocoaUIBase> | |||
| { | |||
| } | |||
| - (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: | |||
| } | |||
| } | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE (EditorCompHolder); | |||
| }; | |||
| //============================================================================== | |||
| struct JuceUIViewClass : public ObjCClass <NSView> | |||
| { | |||
| JuceUIViewClass() : ObjCClass ("JUCEAUView_") | |||
| { | |||
| addIvar<AudioProcessor*> ("filter"); | |||
| addIvar<JuceAU*> ("au"); | |||
| addIvar<EditorCompHolder*> ("editor"); | |||
| addMethod (@selector (dealloc), dealloc, "v@:"); | |||
| addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); | |||
| addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow, "v@:"); | |||
| addMethod (@selector (mouseDownCanMoveWindow), mouseDownCanMoveWindow, "c@:"); | |||
| registerClass(); | |||
| } | |||
| //============================================================================== | |||
| @implementation JuceUIViewClass | |||
| static void deleteEditor (id self) | |||
| { | |||
| ScopedPointer<EditorCompHolder> editorComp (getEditor (self)); | |||
| - (JuceUIViewClass*) initWithFilter: (AudioProcessor*) filter_ | |||
| withAU: (JuceAU*) au_ | |||
| withComponent: (AudioProcessorEditor*) editorComp_ | |||
| { | |||
| filter = filter_; | |||
| au = au_; | |||
| editorComp = new EditorCompHolder (editorComp_); | |||
| if (editorComp != nullptr) | |||
| { | |||
| JuceAU* const au = getAU (self); | |||
| [super initWithFrame: NSMakeRect (0, 0, editorComp_->getWidth(), editorComp_->getHeight())]; | |||
| [self setHidden: NO]; | |||
| [self setPostsFrameChangedNotifications: YES]; | |||
| if (editorComp->getChildComponent(0) != nullptr) | |||
| { | |||
| if (activePlugins.contains (au)) // plugin may have been deleted before the UI | |||
| { | |||
| AudioProcessor* const filter = getIvar<AudioProcessor*> (self, "filter"); | |||
| filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0)); | |||
| } | |||
| } | |||
| [[NSNotificationCenter defaultCenter] addObserver: self | |||
| selector: @selector (applicationWillTerminate:) | |||
| name: NSApplicationWillTerminateNotification | |||
| object: nil]; | |||
| activeUIs.add (self); | |||
| editorComp = nullptr; | |||
| setEditor (self, nullptr); | |||
| } | |||
| } | |||
| editorComp->addToDesktop (0, (void*) self); | |||
| editorComp->setVisible (true); | |||
| static JuceAU* getAU (id self) { return getIvar<JuceAU*> (self, "au"); } | |||
| static EditorCompHolder* getEditor (id self) { return getIvar<EditorCompHolder*> (self, "editor"); } | |||
| return self; | |||
| } | |||
| 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); } | |||
| - (void) dealloc | |||
| { | |||
| if (activeUIs.contains (self)) | |||
| [self shutdown]; | |||
| private: | |||
| static void dealloc (id self, SEL) | |||
| { | |||
| if (activeUIs.contains (self)) | |||
| shutdown (self); | |||
| [super dealloc]; | |||
| } | |||
| sendSuperclassMessage (self, @selector (dealloc)); | |||
| } | |||
| - (void) applicationWillTerminate: (NSNotification*) aNotification | |||
| { | |||
| (void) aNotification; | |||
| [self shutdown]; | |||
| } | |||
| static void applicationWillTerminate (id self, SEL, NSNotification*) | |||
| { | |||
| shutdown (self); | |||
| } | |||
| - (void) shutdown | |||
| { | |||
| // there's some kind of component currently modal, but the host | |||
| // is trying to delete our plugin.. | |||
| jassert (Component::getCurrentlyModalComponent() == nullptr); | |||
| 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]; | |||
| [self deleteEditor]; | |||
| [[NSNotificationCenter defaultCenter] removeObserver: self]; | |||
| deleteEditor (self); | |||
| jassert (activeUIs.contains (self)); | |||
| activeUIs.removeValue (self); | |||
| if (activePlugins.size() + activeUIs.size() == 0) | |||
| shutdownJuce_GUI(); | |||
| } | |||
| 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]; | |||
| static void viewDidMoveToWindow (id self, SEL) | |||
| { | |||
| NSWindow* w = [(NSView*) self window]; | |||
| if (editorComp != nullptr) | |||
| [[self window] makeFirstResponder: (NSView*) editorComp->getWindowHandle()]; | |||
| } | |||
| } | |||
| if (w != nil) | |||
| { | |||
| [w setAcceptsMouseMovedEvents: YES]; | |||
| - (BOOL) mouseDownCanMoveWindow | |||
| { | |||
| return NO; | |||
| } | |||
| 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) deleteEditor | |||
| void JuceAU::deleteActiveEditors() | |||
| { | |||
| if (editorComp != nullptr) | |||
| for (int i = activeUIs.size(); --i >= 0;) | |||
| { | |||
| if (editorComp->getChildComponent(0) != nullptr) | |||
| if (activePlugins.contains ((void*) au)) // plugin may have been deleted before the UI | |||
| filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0)); | |||
| id ui = (id) activeUIs.getUnchecked(i); | |||
| deleteAndZero (editorComp); | |||
| if (EditorCompHolder::JuceUIViewClass::getAU (ui) == this) | |||
| EditorCompHolder::JuceUIViewClass::deleteEditor (ui); | |||
| } | |||
| editorComp = nullptr; | |||
| } | |||
| - (void) filterBeingDeleted: (JuceAU*) au_ | |||
| //============================================================================== | |||
| @interface JuceUICreationClass : NSObject <AUCocoaUIBase> | |||
| { | |||
| 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 | |||
| @@ -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 | |||
| @@ -61,4 +61,7 @@ | |||
| #else | |||
| #include <Cocoa/Cocoa.h> | |||
| #endif | |||
| #include <objc/runtime.h> | |||
| #include <objc/objc.h> | |||
| #include <objc/message.h> | |||
| #endif | |||
| @@ -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 | |||
| @@ -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]; | |||
| } | |||
| void run() | |||
| bool start (URL::OpenStreamProgressCallback* callback, void* context) | |||
| { | |||
| [owner createConnection]; | |||
| startThread(); | |||
| while (! threadShouldExit()) | |||
| while (isThreadRunning() && ! initialised) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; | |||
| if (callback != nullptr) | |||
| callback (context, -1, (int) [[request HTTPBody] length]); | |||
| Thread::sleep (1); | |||
| } | |||
| return connection != nil && ! hasFailed; | |||
| } | |||
| private: | |||
| JuceURLConnection* owner; | |||
| }; | |||
| void stop() | |||
| { | |||
| [connection cancel]; | |||
| stopThread (10000); | |||
| } | |||
| int read (char* dest, int numBytes) | |||
| { | |||
| int numDone = 0; | |||
| @implementation JuceURLConnection | |||
| while (numBytes > 0) | |||
| { | |||
| const int available = jmin (numBytes, (int) [data length]); | |||
| - (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]); | |||
| 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); | |||
| } | |||
| } | |||
| Thread::sleep (1); | |||
| return numDone; | |||
| } | |||
| return self; | |||
| } | |||
| void didReceiveResponse (NSURLResponse* response) | |||
| { | |||
| { | |||
| const ScopedLock sl (dataLock); | |||
| [data setLength: 0]; | |||
| } | |||
| - (void) dealloc | |||
| { | |||
| [self stop]; | |||
| deleteAndZero (runLoopThread); | |||
| [connection release]; | |||
| [data release]; | |||
| [dataLock release]; | |||
| [request release]; | |||
| [headers release]; | |||
| [super dealloc]; | |||
| } | |||
| initialised = true; | |||
| contentLength = [response expectedContentLength]; | |||
| - (void) createConnection | |||
| { | |||
| NSUInteger oldRetainCount = [self retainCount]; | |||
| connection = [[NSURLConnection alloc] initWithRequest: request | |||
| delegate: self]; | |||
| [headers release]; | |||
| headers = nil; | |||
| if (oldRetainCount == [self retainCount]) | |||
| [self retain]; // newer SDK should already retain this, but there were problems in older versions.. | |||
| if ([response isKindOfClass: [NSHTTPURLResponse class]]) | |||
| headers = [[((NSHTTPURLResponse*) response) allHeaderFields] retain]; | |||
| } | |||
| if (connection == nil) | |||
| runLoopThread->signalThreadShouldExit(); | |||
| } | |||
| void didFailWithError (NSError* error) | |||
| { | |||
| DBG (nsStringToJuce ([error description])); (void) error; | |||
| hasFailed = true; | |||
| initialised = true; | |||
| 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 didReceiveData (NSData* newData) | |||
| { | |||
| const ScopedLock sl (dataLock); | |||
| [data appendData: newData]; | |||
| initialised = true; | |||
| } | |||
| - (void) connection: (NSURLConnection*) conn didFailWithError: (NSError*) error | |||
| { | |||
| (void) conn; | |||
| DBG (nsStringToJuce ([error description])); | |||
| hasFailed = true; | |||
| initialised = true; | |||
| void finishedLoading() | |||
| { | |||
| hasFinished = true; | |||
| initialised = true; | |||
| signalThreadShouldExit(); | |||
| } | |||
| if (runLoopThread != nullptr) | |||
| runLoopThread->signalThreadShouldExit(); | |||
| } | |||
| void run() | |||
| { | |||
| NSUInteger oldRetainCount = [owner retainCount]; | |||
| connection = [[NSURLConnection alloc] initWithRequest: request | |||
| delegate: owner]; | |||
| - (void) connection: (NSURLConnection*) conn didReceiveData: (NSData*) newData | |||
| { | |||
| (void) conn; | |||
| [dataLock lock]; | |||
| [data appendData: newData]; | |||
| [dataLock unlock]; | |||
| initialised = true; | |||
| } | |||
| if (oldRetainCount == [owner retainCount]) | |||
| [owner retain]; // newer SDK should already retain this, but there were problems in older versions.. | |||
| - (void) connectionDidFinishLoading: (NSURLConnection*) conn | |||
| { | |||
| (void) conn; | |||
| hasFinished = true; | |||
| initialised = true; | |||
| if (connection != nil) | |||
| { | |||
| while (! threadShouldExit()) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; | |||
| } | |||
| } | |||
| } | |||
| if (runLoopThread != nullptr) | |||
| runLoopThread->signalThreadShouldExit(); | |||
| } | |||
| int64 contentLength; | |||
| CriticalSection dataLock; | |||
| NSObject* owner; | |||
| NSURLRequest* request; | |||
| NSURLConnection* connection; | |||
| NSMutableData* data; | |||
| NSDictionary* headers; | |||
| bool initialised, hasFailed, hasFinished; | |||
| - (BOOL) isOpen | |||
| { | |||
| return connection != nil && ! hasFailed; | |||
| } | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState); | |||
| }; | |||
| - (int) readPosition | |||
| //============================================================================== | |||
| struct URLConnectionDelegateClass : public ObjCClass<NSObject> | |||
| { | |||
| return position; | |||
| } | |||
| URLConnectionDelegateClass() : ObjCClass ("JUCEAppDelegate_") | |||
| { | |||
| addIvar <URLConnectionState*> ("state"); | |||
| - (int) read: (char*) dest numBytes: (int) numNeeded | |||
| { | |||
| int numDone = 0; | |||
| 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@:@"); | |||
| while (numNeeded > 0) | |||
| { | |||
| int available = jmin (numNeeded, (int) [data length]); | |||
| registerClass(); | |||
| } | |||
| 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; | |||
| static void setState (id self, URLConnectionState* state) | |||
| { | |||
| object_setInstanceVariable (self, "state", state); | |||
| } | |||
| Thread::sleep (1); | |||
| } | |||
| static URLConnectionState* getState (id self) | |||
| { | |||
| return getIvar<URLConnectionState*> (self, "state"); | |||
| } | |||
| position += numDone; | |||
| return numDone; | |||
| } | |||
| private: | |||
| static void dealloc (id self, SEL sel) | |||
| { | |||
| getState (self)->stop(); | |||
| delete getState (self); | |||
| sendSuperclassMessage (self, @selector (dealloc)); | |||
| } | |||
| - (void) stop | |||
| { | |||
| [connection cancel]; | |||
| static void didReceiveResponse (id self, SEL, NSURLConnection*, NSURLResponse* response) | |||
| { | |||
| getState (self)->didReceiveResponse (response); | |||
| } | |||
| if (runLoopThread != nullptr) | |||
| runLoopThread->stopThread (10000); | |||
| } | |||
| static void didFailWithError (id self, SEL, NSURLConnection*, NSError* error) | |||
| { | |||
| getState (self)->didFailWithError (error); | |||
| } | |||
| @end | |||
| static void didReceiveData (id self, SEL, NSURLConnection*, NSData* newData) | |||
| { | |||
| getState (self)->didReceiveData (newData); | |||
| } | |||
| namespace juce | |||
| { | |||
| static void connectionDidFinishLoading (id self, SEL, NSURLConnection*) | |||
| { | |||
| getState (self)->finishedLoading(); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| 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); | |||
| if (state->headers != nil) | |||
| { | |||
| NSEnumerator* enumerator = [state->headers keyEnumerator]; | |||
| NSString* key; | |||
| while ((key = [enumerator nextObject]) != nil) | |||
| responseHeaders->set (nsStringToJuce (key), | |||
| nsStringToJuce ((NSString*) [connection->headers objectForKey: 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 <char*> (buffer) numBytes: bytesToRead]; | |||
| URLConnectionState* const state = URLConnectionDelegateClass::getState (connection); | |||
| const int bytesRead = state->read (static_cast <char*> (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]; | |||
| URLConnectionState* state = new URLConnectionState (s, req); | |||
| URLConnectionDelegateClass::setState (s, state); | |||
| if ([s isOpen]) | |||
| if (state->start (progressCallback, progressCallbackContext)) | |||
| return s; | |||
| [s release]; | |||
| @@ -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__ | |||
| @@ -55,18 +55,27 @@ namespace | |||
| } | |||
| //============================================================================== | |||
| class ObjCClassBuilder | |||
| template <typename SuperclassType> | |||
| 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 <typename Type> | |||
| @@ -83,21 +92,54 @@ public: | |||
| jassert (b); (void) b; | |||
| } | |||
| template <typename FunctionType> | |||
| void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2) | |||
| { | |||
| addMethod (selector, callbackFn, (String (sig1) + sig2).toUTF8()); | |||
| } | |||
| template <typename FunctionType> | |||
| void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2, const char* sig3) | |||
| { | |||
| addMethod (selector, callbackFn, (String (sig1) + sig2 + sig3).toUTF8()); | |||
| } | |||
| template <typename FunctionType> | |||
| 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); | |||
| } | |||
| static String getRandomisedName (const char* root) | |||
| template <typename Type> | |||
| static Type getIvar (id self, const char* name) | |||
| { | |||
| return root + String::toHexString (Random::getSystemRandom().nextInt64()); | |||
| Type v = Type(); | |||
| object_getInstanceVariable (self, name, (void**) &v); | |||
| return v; | |||
| } | |||
| private: | |||
| Class cls; | |||
| static String getRandomisedName (const char* root) | |||
| { | |||
| return root + String::toHexString (Random::getSystemRandom().nextInt64()); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE (ObjCClassBuilder); | |||
| JUCE_DECLARE_NON_COPYABLE (ObjCClass); | |||
| }; | |||
| #endif // __JUCE_OSX_OBJCHELPERS_JUCEHEADER__ | |||
| @@ -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" | |||
| @@ -30,33 +30,24 @@ typedef bool (*CheckEventBlockedByModalComps) (NSEvent*); | |||
| CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; | |||
| //============================================================================== | |||
| struct AppDelegateClass | |||
| struct AppDelegateClass : public ObjCClass <NSObject> | |||
| { | |||
| static Class createClass() | |||
| AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") | |||
| { | |||
| ObjCClassBuilder c ([NSObject class], ObjCClassBuilder::getRandomisedName ("JUCEAppDelegate_")); | |||
| 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); | |||
| 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@:"); | |||
| 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() | |||
| { | |||
| @@ -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" | |||
| @@ -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" | |||
| @@ -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 <NSOpenSavePanelDelegate> | |||
| #else | |||
| @interface JuceFileChooserDelegate : NSObject | |||
| #endif | |||
| struct FileChooserDelegateClass : public ObjCClass <NSObject> | |||
| { | |||
| StringArray* filters; | |||
| } | |||
| FileChooserDelegateClass() : ObjCClass ("JUCEFileChooser_") | |||
| { | |||
| addIvar<StringArray*> ("filters"); | |||
| - (JuceFileChooserDelegate*) initWithFilters: (StringArray*) filters_; | |||
| - (void) dealloc; | |||
| - (BOOL) panel: (id) sender shouldShowFilename: (NSString*) filename; | |||
| addMethod (@selector (initWithFilters:), initWithFilters, "@@:^v"); | |||
| addMethod (@selector (dealloc), dealloc, "v@:"); | |||
| addMethod (@selector (panel:shouldShowFilename:), shouldShowFilename, "c@:@@"); | |||
| @end | |||
| #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 | |||
| addProtocol (@protocol (NSOpenSavePanelDelegate)); | |||
| #endif | |||
| @implementation JuceFileChooserDelegate | |||
| - (JuceFileChooserDelegate*) initWithFilters: (StringArray*) filters_ | |||
| { | |||
| [super init]; | |||
| filters = filters_; | |||
| return self; | |||
| } | |||
| registerClass(); | |||
| } | |||
| - (void) dealloc | |||
| { | |||
| delete filters; | |||
| [super dealloc]; | |||
| } | |||
| private: | |||
| static id initWithFilters (id self, SEL, StringArray* filters) | |||
| { | |||
| self = sendSuperclassMessage (self, @selector (init)); | |||
| object_setInstanceVariable (self, "filters", filters); | |||
| return self; | |||
| } | |||
| - (BOOL) panel: (id) sender shouldShowFilename: (NSString*) filename | |||
| { | |||
| (void) sender; | |||
| const File f (nsStringToJuce (filename)); | |||
| static void dealloc (id self, SEL) | |||
| { | |||
| delete getIvar<StringArray*> (self, "filters"); | |||
| sendSuperclassMessage (self, @selector (dealloc)); | |||
| } | |||
| for (int i = filters->size(); --i >= 0;) | |||
| if (f.getFileName().matchesWildcard ((*filters)[i], true)) | |||
| return true; | |||
| static BOOL shouldShowFilename (id self, SEL, id /*sender*/, NSString* filename) | |||
| { | |||
| StringArray* const filters = getIvar<StringArray*> (self, "filters"); | |||
| #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]; | |||
| const File f (nsStringToJuce (filename)); | |||
| if ([name isEqualToString: nsStringLiteral ("com.apple.alias-file")]) | |||
| { | |||
| FSRef ref; | |||
| FSPathMakeRef ((const UInt8*) [filename fileSystemRepresentation], &ref, nullptr); | |||
| for (int i = filters->size(); --i >= 0;) | |||
| if (f.getFileName().matchesWildcard ((*filters)[i], true)) | |||
| return true; | |||
| Boolean targetIsFolder = false, wasAliased = false; | |||
| FSResolveAliasFileWithMountFlags (&ref, true, &targetIsFolder, &wasAliased, 0); | |||
| #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]; | |||
| return wasAliased && targetIsFolder; | |||
| } | |||
| #endif | |||
| if ([name isEqualToString: nsStringLiteral ("com.apple.alias-file")]) | |||
| { | |||
| FSRef ref; | |||
| FSPathMakeRef ((const UInt8*) [filename fileSystemRepresentation], &ref, nullptr); | |||
| return f.isDirectory() | |||
| && ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: filename]; | |||
| } | |||
| @end | |||
| Boolean targetIsFolder = false, wasAliased = false; | |||
| FSResolveAliasFileWithMountFlags (&ref, true, &targetIsFolder, &wasAliased, 0); | |||
| return wasAliased && targetIsFolder; | |||
| } | |||
| #endif | |||
| namespace juce | |||
| { | |||
| 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<File>& results, | |||
| const String& title, | |||
| const File& currentFileOrDirectory, | |||
| @@ -161,8 +150,15 @@ void FileChooser::showPlatformDialog (Array<File>& 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<NSOpenSavePanelDelegate> 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<File>& results, | |||
| [panel setDelegate: nil]; | |||
| } | |||
| bool FileChooser::isPlatformDialogAvailable() | |||
| { | |||
| return true; | |||
| } | |||
| #else | |||
| //============================================================================== | |||
| @@ -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 <NSMenuDelegate> | |||
| #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"]; | |||
| [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()]; | |||
| NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex); | |||
| [sub setDelegate: nil]; | |||
| [menuToAddTo setSubmenu: sub forItem: item]; | |||
| [sub release]; | |||
| [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<NSMenuDelegate>) 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) | |||
| //============================================================================== | |||
| struct JuceMenuCallbackClass : public ObjCClass <NSObject> | |||
| { | |||
| JuceMenuCallbackClass() | |||
| : ObjCClass ("JUCEMainMenu_") | |||
| { | |||
| addIvar<JuceMainMenuHandler*> ("owner"); | |||
| //============================================================================== | |||
| @implementation JuceMenuCallback | |||
| addMethod (@selector (initWithOwner:), initWithOwner, "@@:^v"); | |||
| addMethod (@selector (menuItemInvoked:), menuItemInvoked, "v@:@"); | |||
| addMethod (@selector (menuNeedsUpdate:), menuNeedsUpdate, "v@:@"); | |||
| - (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_ | |||
| { | |||
| [super init]; | |||
| owner = owner_; | |||
| return self; | |||
| } | |||
| #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 | |||
| addProtocol (@protocol (NSMenuDelegate)); | |||
| #endif | |||
| - (void) dealloc | |||
| { | |||
| [super dealloc]; | |||
| } | |||
| registerClass(); | |||
| } | |||
| - (void) menuItemInvoked: (id) menu | |||
| { | |||
| NSMenuItem* item = (NSMenuItem*) menu; | |||
| private: | |||
| static id initWithOwner (id self, SEL, JuceMainMenuHandler* owner) | |||
| { | |||
| self = sendSuperclassMessage (self, @selector (init)); | |||
| object_setInstanceVariable (self, "owner", owner); | |||
| return self; | |||
| } | |||
| if ([[item representedObject] isKindOfClass: [NSArray class]]) | |||
| { | |||
| // 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) | |||
| static void menuItemInvoked (id self, SEL, id menu) | |||
| { | |||
| if (juce::Component::getCurrentlyFocusedComponent() != nullptr) | |||
| { | |||
| juce::NSViewComponentPeer* peer = dynamic_cast <juce::NSViewComponentPeer*> (juce::Component::getCurrentlyFocusedComponent()->getPeer()); | |||
| JuceMainMenuHandler* const owner = getIvar<JuceMainMenuHandler*> (self, "owner"); | |||
| if (peer != nullptr) | |||
| NSMenuItem* item = (NSMenuItem*) menu; | |||
| if ([[item representedObject] isKindOfClass: [NSArray class]]) | |||
| { | |||
| // 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::NSViewComponentPeer*> (juce::Component::getCurrentlyFocusedComponent()->getPeer()); | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| if (peer != nullptr) | |||
| { | |||
| if ([e type] == NSKeyDown) | |||
| peer->redirectKeyDown (e); | |||
| else | |||
| peer->redirectKeyUp (e); | |||
| NSArray* info = (NSArray*) [item representedObject]; | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| 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]; | |||
| - (void) menuNeedsUpdate: (NSMenu*) menu; | |||
| { | |||
| if (JuceMainMenuHandler::instance != nullptr) | |||
| JuceMainMenuHandler::instance->updateMenus (menu); | |||
| } | |||
| owner->invoke ((int) [item tag], | |||
| (ApplicationCommandManager*) (pointer_sized_int) | |||
| [((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue], | |||
| (int) [((NSNumber*) [info objectAtIndex: 1]) intValue]); | |||
| } | |||
| } | |||
| @end | |||
| static void menuNeedsUpdate (id self, SEL, NSMenu* menu) | |||
| { | |||
| if (instance != nullptr) | |||
| instance->updateMenus (menu); | |||
| } | |||
| }; | |||
| }; | |||
| namespace juce | |||
| { | |||
| JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr; | |||
| //============================================================================== | |||
| namespace MainMenuHelpers | |||
| @@ -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 | |||
| @@ -23,66 +23,57 @@ | |||
| ============================================================================== | |||
| */ | |||
| } // (juce namespace) | |||
| class WebBrowserComponentInternal; | |||
| #if JUCE_MAC | |||
| #define DownloadClickDetector MakeObjCClassName(DownloadClickDetector) | |||
| @interface DownloadClickDetector : NSObject | |||
| struct DownloadClickDetectorClass : public ObjCClass <NSObject> | |||
| { | |||
| juce::WebBrowserComponent* ownerComponent; | |||
| } | |||
| - (DownloadClickDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent; | |||
| - (void) webView: (WebView*) webView decidePolicyForNavigationAction: (NSDictionary*) actionInformation | |||
| request: (NSURLRequest*) request | |||
| frame: (WebFrame*) frame | |||
| decisionListener: (id <WebPolicyDecisionListener>) listener; | |||
| - (void) webView: (WebView*) webView didFinishLoadForFrame: (WebFrame*) frame; | |||
| DownloadClickDetectorClass() : ObjCClass ("JUCEWebClickDetector_") | |||
| { | |||
| addIvar <WebBrowserComponent*> ("owner"); | |||
| @end | |||
| addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:), | |||
| decidePolicyForNavigationAction, "v@:@@@@@"); | |||
| addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@"); | |||
| @implementation DownloadClickDetector | |||
| registerClass(); | |||
| } | |||
| - (DownloadClickDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent_ | |||
| { | |||
| [super init]; | |||
| ownerComponent = ownerComponent_; | |||
| return self; | |||
| } | |||
| static void setOwner (id self, WebBrowserComponent* owner) | |||
| { | |||
| object_setInstanceVariable (self, "owner", owner); | |||
| } | |||
| - (void) webView: (WebView*) sender decidePolicyForNavigationAction: (NSDictionary*) actionInformation | |||
| request: (NSURLRequest*) request | |||
| frame: (WebFrame*) frame | |||
| decisionListener: (id <WebPolicyDecisionListener>) listener | |||
| { | |||
| (void) sender; (void) request; (void) frame; | |||
| private: | |||
| static WebBrowserComponent* getOwner (id self) | |||
| { | |||
| return getIvar<WebBrowserComponent*> (self, "owner"); | |||
| } | |||
| NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")]; | |||
| static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation, | |||
| NSURLRequest*, WebFrame*, id <WebPolicyDecisionListener> listener) | |||
| { | |||
| NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")]; | |||
| if (ownerComponent->pageAboutToLoad (nsStringToJuce ([url absoluteString]))) | |||
| [listener use]; | |||
| else | |||
| [listener ignore]; | |||
| } | |||
| if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([url absoluteString]))) | |||
| [listener use]; | |||
| else | |||
| [listener ignore]; | |||
| } | |||
| - (void) webView: (WebView*) sender didFinishLoadForFrame: (WebFrame*) frame | |||
| { | |||
| if ([frame isEqual: [sender mainFrame]]) | |||
| static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame) | |||
| { | |||
| NSURL* url = [[[frame dataSource] request] URL]; | |||
| ownerComponent->pageFinishedLoading (nsStringToJuce ([url absoluteString])); | |||
| if ([frame isEqual: [sender mainFrame]]) | |||
| { | |||
| NSURL* url = [[[frame dataSource] request] URL]; | |||
| getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString])); | |||
| } | |||
| } | |||
| } | |||
| @end | |||
| }; | |||
| #else | |||
| } // (juce namespace) | |||
| //============================================================================== | |||
| @interface WebViewTapDetector : NSObject <UIGestureRecognizerDelegate> | |||
| { | |||
| @@ -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; | |||
| @@ -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 | |||
| @@ -23,92 +23,78 @@ | |||
| ============================================================================== | |||
| */ | |||
| } // (juce namespace) | |||
| #define ThreadSafeNSOpenGLView MakeObjCClassName(ThreadSafeNSOpenGLView) | |||
| //============================================================================== | |||
| @interface ThreadSafeNSOpenGLView : NSOpenGLView | |||
| struct ThreadSafeNSOpenGLViewClass : public ObjCClass <NSOpenGLView> | |||
| { | |||
| juce::CriticalSection* contextLock; | |||
| bool needsUpdate; | |||
| } | |||
| ThreadSafeNSOpenGLViewClass() : ObjCClass ("JUCEGLView_") | |||
| { | |||
| addIvar <CriticalSection*> ("lock"); | |||
| addIvar <BOOL> ("needsUpdate"); | |||
| - (id) initWithFrame: (NSRect) frameRect pixelFormat: (NSOpenGLPixelFormat*) format; | |||
| - (bool) makeActive; | |||
| - (void) reshape; | |||
| - (void) rightMouseDown: (NSEvent*) ev; | |||
| - (void) rightMouseUp: (NSEvent*) ev; | |||
| @end | |||
| 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@:@"); | |||
| @implementation ThreadSafeNSOpenGLView | |||
| registerClass(); | |||
| } | |||
| - (id) initWithFrame: (NSRect) frameRect | |||
| pixelFormat: (NSOpenGLPixelFormat*) format | |||
| { | |||
| contextLock = new juce::CriticalSection(); | |||
| self = [super initWithFrame: frameRect pixelFormat: format]; | |||
| needsUpdate = true; | |||
| static void init (id self) | |||
| { | |||
| object_setInstanceVariable (self, "lock", new CriticalSection()); | |||
| setNeedsUpdate (self, YES); | |||
| } | |||
| if (self != nil) | |||
| [[NSNotificationCenter defaultCenter] addObserver: self | |||
| selector: @selector (_surfaceNeedsUpdate:) | |||
| name: NSViewGlobalFrameDidChangeNotification | |||
| object: self]; | |||
| return self; | |||
| } | |||
| static bool makeActive (id self) | |||
| { | |||
| const ScopedLock sl (*getLock (self)); | |||
| - (void) dealloc | |||
| { | |||
| [[NSNotificationCenter defaultCenter] removeObserver: self]; | |||
| delete contextLock; | |||
| [super dealloc]; | |||
| } | |||
| if ([(NSOpenGLView*) self openGLContext] == nil) | |||
| return false; | |||
| - (bool) makeActive | |||
| { | |||
| const juce::ScopedLock sl (*contextLock); | |||
| [[(NSOpenGLView*) self openGLContext] makeCurrentContext]; | |||
| if ([self openGLContext] == nil) | |||
| return false; | |||
| if (getIvar<BOOL> (self, "needsUpdate")) | |||
| { | |||
| sendSuperclassMessage (self, @selector (update)); | |||
| setNeedsUpdate (self, NO); | |||
| } | |||
| [[self openGLContext] makeCurrentContext]; | |||
| return true; | |||
| } | |||
| if (needsUpdate) | |||
| private: | |||
| static CriticalSection* getLock (id self) | |||
| { | |||
| [super update]; | |||
| needsUpdate = false; | |||
| return getIvar<CriticalSection*> (self, "lock"); | |||
| } | |||
| return true; | |||
| } | |||
| - (void) _surfaceNeedsUpdate: (NSNotification*) notification | |||
| { | |||
| (void) notification; | |||
| const juce::ScopedLock sl (*contextLock); | |||
| needsUpdate = true; | |||
| } | |||
| static void setNeedsUpdate (id self, BOOL b) | |||
| { | |||
| object_setInstanceVariable (self, "needsUpdate", (void*) b); | |||
| } | |||
| - (void) update | |||
| { | |||
| const juce::ScopedLock sl (*contextLock); | |||
| needsUpdate = true; | |||
| } | |||
| static void setNeedsUpdateLocked (id self, BOOL b) | |||
| { | |||
| const ScopedLock sl (*getLock (self)); | |||
| setNeedsUpdate (self, b); | |||
| } | |||
| - (void) reshape | |||
| { | |||
| const juce::ScopedLock sl (*contextLock); | |||
| needsUpdate = true; | |||
| } | |||
| static void dealloc (id self, SEL) | |||
| { | |||
| delete getLock (self); | |||
| sendSuperclassMessage (self, @selector (dealloc)); | |||
| } | |||
| - (void) rightMouseDown: (NSEvent*) ev { [[self superview] rightMouseDown: ev]; } | |||
| - (void) rightMouseUp: (NSEvent*) ev { [[self superview] rightMouseUp: ev]; } | |||
| 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); } | |||
| @end | |||
| 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<ReferenceCountedObject> viewAttachment; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext); | |||
| @@ -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" | |||
| @@ -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<CameraDevice::Listener*> listeners; | |||
| CriticalSection listenerLock; | |||
| }; | |||
| } // (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) | |||
| private: | |||
| //============================================================================== | |||
| struct DelegateClass : public ObjCClass <NSObject> | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| DelegateClass() : ObjCClass ("JUCEAppDelegate_") | |||
| { | |||
| addIvar<QTCameraDeviceInternal*> ("owner"); | |||
| internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame], | |||
| CVPixelBufferGetWidth (videoFrame), | |||
| CVPixelBufferGetHeight (videoFrame)); | |||
| } | |||
| } | |||
| addMethod (@selector (captureOutput:didOutputVideoFrame:withSampleBuffer:fromConnection:), | |||
| didOutputVideoFrame, "v@:@", @encode (CVImageBufferRef), "@@"); | |||
| addMethod (@selector (captureOutput:didOutputSampleBuffer:fromConnection:), | |||
| didOutputVideoFrame, "v@:@@@"); | |||
| - (void) captureOutput: (QTCaptureFileOutput*) captureOutput | |||
| didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer | |||
| fromConnection: (QTCaptureConnection*) connection | |||
| { | |||
| const Time now (Time::getCurrentTime()); | |||
| registerClass(); | |||
| } | |||
| #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 | |||
| static void setOwner (id self, QTCameraDeviceInternal* owner) | |||
| { | |||
| object_setInstanceVariable (self, "owner", owner); | |||
| } | |||
| int64 presentationTime = (hosttime != nil) | |||
| ? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40) | |||
| : (([sampleBuffer presentationTime].timeValue * 1000) / [sampleBuffer presentationTime].timeScale + 50); | |||
| private: | |||
| static QTCameraDeviceInternal* getOwner (id self) | |||
| { | |||
| return getIvar<QTCameraDeviceInternal*> (self, "owner"); | |||
| } | |||
| const int64 timeDiff = now.toMilliseconds() - presentationTime; | |||
| static void didOutputVideoFrame (id self, SEL, QTCaptureOutput* captureOutput, | |||
| CVImageBufferRef videoFrame, QTSampleBuffer* sampleBuffer, | |||
| QTCaptureConnection* connection) | |||
| { | |||
| QTCameraDeviceInternal* const internal = getOwner (self); | |||
| if (firstPresentationTime == 0) | |||
| { | |||
| firstPresentationTime = presentationTime; | |||
| averageTimeOffset = timeDiff; | |||
| } | |||
| else | |||
| { | |||
| averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128; | |||
| } | |||
| } | |||
| if (internal->listeners.size() > 0) | |||
| { | |||
| JUCE_AUTORELEASEPOOL | |||
| @end | |||
| internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame], | |||
| CVPixelBufferGetWidth (videoFrame), | |||
| CVPixelBufferGetHeight (videoFrame)); | |||
| } | |||
| } | |||
| namespace juce | |||
| { | |||
| static void didOutputSampleBuffer (id self, SEL, QTCaptureFileOutput*, QTSampleBuffer* sampleBuffer, QTCaptureConnection*) | |||
| { | |||
| getOwner (self)->captureBuffer (sampleBuffer); | |||
| } | |||
| }; | |||
| }; | |||
| //============================================================================== | |||
| 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 <QTCameraDeviceInteral*> (internal); | |||
| delete static_cast <QTCameraDeviceInternal*> (internal); | |||
| internal = nullptr; | |||
| } | |||
| Component* CameraDevice::createViewerComponent() | |||
| { | |||
| return new QTCaptureViewerComp (this, static_cast <QTCameraDeviceInteral*> (internal)); | |||
| return new QTCaptureViewerComp (this, static_cast <QTCameraDeviceInternal*> (internal)); | |||
| } | |||
| String CameraDevice::getFileExtension() | |||
| @@ -330,8 +311,8 @@ void CameraDevice::startRecordingToFile (const File& file, int quality) | |||
| { | |||
| stopRecording(); | |||
| QTCameraDeviceInteral* const d = static_cast <QTCameraDeviceInteral*> (internal); | |||
| d->callbackDelegate->firstPresentationTime = 0; | |||
| QTCameraDeviceInternal* const d = static_cast <QTCameraDeviceInternal*> (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 <QTCameraDeviceInteral*> (internal); | |||
| if (d->callbackDelegate->firstPresentationTime != 0) | |||
| return Time (d->callbackDelegate->firstPresentationTime + d->callbackDelegate->averageTimeOffset); | |||
| QTCameraDeviceInternal* const d = static_cast <QTCameraDeviceInternal*> (internal); | |||
| if (d->firstPresentationTime != 0) | |||
| return Time (d->firstPresentationTime + d->averageTimeOffset); | |||
| return Time(); | |||
| } | |||
| @@ -378,7 +359,7 @@ void CameraDevice::stopRecording() | |||
| { | |||
| if (isRecording) | |||
| { | |||
| static_cast <QTCameraDeviceInteral*> (internal)->resetFile(); | |||
| static_cast <QTCameraDeviceInternal*> (internal)->resetFile(); | |||
| isRecording = false; | |||
| } | |||
| } | |||
| @@ -386,13 +367,13 @@ void CameraDevice::stopRecording() | |||
| void CameraDevice::addListener (Listener* listenerToAdd) | |||
| { | |||
| if (listenerToAdd != nullptr) | |||
| static_cast <QTCameraDeviceInteral*> (internal)->addListener (listenerToAdd); | |||
| static_cast <QTCameraDeviceInternal*> (internal)->addListener (listenerToAdd); | |||
| } | |||
| void CameraDevice::removeListener (Listener* listenerToRemove) | |||
| { | |||
| if (listenerToRemove != nullptr) | |||
| static_cast <QTCameraDeviceInteral*> (internal)->removeListener (listenerToRemove); | |||
| static_cast <QTCameraDeviceInternal*> (internal)->removeListener (listenerToRemove); | |||
| } | |||
| //============================================================================== | |||
| @@ -418,7 +399,7 @@ CameraDevice* CameraDevice::openDevice (int index, | |||
| { | |||
| ScopedPointer <CameraDevice> d (new CameraDevice (getAvailableDevices() [index], index)); | |||
| if (static_cast <QTCameraDeviceInteral*> (d->internal)->openingError.isEmpty()) | |||
| if (static_cast <QTCameraDeviceInternal*> (d->internal)->openingError.isEmpty()) | |||
| return d.release(); | |||
| return nullptr; | |||
| @@ -25,49 +25,31 @@ | |||
| #if JUCE_QUICKTIME | |||
| } // (juce namespace) | |||
| //============================================================================== | |||
| #define NonInterceptingQTMovieView MakeObjCClassName(NonInterceptingQTMovieView) | |||
| @interface NonInterceptingQTMovieView : QTMovieView | |||
| { | |||
| } | |||
| - (id) initWithFrame: (NSRect) frame; | |||
| - (BOOL) acceptsFirstMouse: (NSEvent*) theEvent; | |||
| - (NSView*) hitTest: (NSPoint) p; | |||
| @end | |||
| @implementation NonInterceptingQTMovieView | |||
| - (id) initWithFrame: (NSRect) frame | |||
| { | |||
| self = [super initWithFrame: frame]; | |||
| [self setNextResponder: [self superview]]; | |||
| return self; | |||
| } | |||
| - (void) dealloc | |||
| struct NonInterceptingQTMovieViewClass : public ObjCClass <QTMovieView> | |||
| { | |||
| [super dealloc]; | |||
| } | |||
| NonInterceptingQTMovieViewClass() : ObjCClass ("JUCEQTMovieView_") | |||
| { | |||
| addMethod (@selector (hitTest:), hitTest, "@@:", @encode (NSPoint)); | |||
| addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse, "c@:@"); | |||
| - (NSView*) hitTest: (NSPoint) point | |||
| { | |||
| return [self isControllerVisible] ? [super hitTest: point] : nil; | |||
| } | |||
| registerClass(); | |||
| } | |||
| - (BOOL) acceptsFirstMouse: (NSEvent*) theEvent | |||
| { | |||
| return YES; | |||
| } | |||
| private: | |||
| static NSView* hitTest (id self, SEL, NSPoint point) | |||
| { | |||
| if (! [(QTMovieView*) self isControllerVisible]) | |||
| return nil; | |||
| @end | |||
| objc_super s = { self, [QTMovieView class] }; | |||
| return objc_msgSendSuper (&s, @selector (hitTest:), point); | |||
| } | |||
| namespace juce | |||
| { | |||
| static BOOL acceptsFirstMouse (id, SEL, NSEvent*) | |||
| { | |||
| return YES; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| #define theMovie (static_cast <QTMovie*> (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]; | |||
| } | |||