@@ -61,8 +61,6 @@ public: | |||||
if (getTargetLocationString().isEmpty()) | if (getTargetLocationString().isEmpty()) | ||||
getTargetLocationValue() = getDefaultBuildsRootFolder() + (iOS ? "iOS" : "MacOSX"); | getTargetLocationValue() = getDefaultBuildsRootFolder() + (iOS ? "iOS" : "MacOSX"); | ||||
setValueIfVoid (getObjCSuffixValue(), createAlphaNumericUID()); | |||||
} | } | ||||
static XCodeProjectExporter* createForSettings (Project& project, const ValueTree& settings) | 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"); } | Value getPListToMergeValue() { return getSetting ("customPList"); } | ||||
String getPListToMergeString() const { return settings ["customPList"]; } | String getPListToMergeString() const { return settings ["customPList"]; } | ||||
@@ -113,11 +108,6 @@ public: | |||||
{ | { | ||||
ProjectExporter::createPropertyEditors (props); | 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) | if (projectType.isGUIApplication() && ! iOS) | ||||
{ | { | ||||
props.add (new TextPropertyComponent (getSetting ("documentExtensions"), "Document file extensions", 128, false), | props.add (new TextPropertyComponent (getSetting ("documentExtensions"), "Document file extensions", 128, false), | ||||
@@ -746,12 +736,6 @@ private: | |||||
s.add ("GCC_SYMBOLS_PRIVATE_EXTERN = YES"); | 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)); | defines = mergePreprocessorDefs (defines, getAllPreprocessorDefs (config)); | ||||
@@ -163,7 +163,6 @@ namespace juce | |||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | #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_CoreAudio.cpp" | ||||
#include "native/juce_mac_CoreMidi.cpp" | #include "native/juce_mac_CoreMidi.cpp" | ||||
@@ -25,305 +25,256 @@ | |||||
const int kilobytesPerSecond1x = 176; | 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 | class AudioCDBurner::Pimpl : public Timer | ||||
@@ -333,9 +284,10 @@ public: | |||||
: device (0), owner (owner_) | : device (0), owner (owner_) | ||||
{ | { | ||||
DRDevice* dev = [[DRDevice devices] objectAtIndex: deviceIndex]; | DRDevice* dev = [[DRDevice devices] objectAtIndex: deviceIndex]; | ||||
if (dev != nil) | if (dev != nil) | ||||
{ | { | ||||
device = [[OpenDiskDevice alloc] initWithDRDevice: dev]; | |||||
device = new OpenDiskDevice (dev); | |||||
lastState = getDiskState(); | lastState = getDiskState(); | ||||
startTimer (1000); | startTimer (1000); | ||||
} | } | ||||
@@ -344,7 +296,6 @@ public: | |||||
~Pimpl() | ~Pimpl() | ||||
{ | { | ||||
stopTimer(); | stopTimer(); | ||||
[device release]; | |||||
} | } | ||||
void timerCallback() | void timerCallback() | ||||
@@ -386,7 +337,7 @@ public: | |||||
return unknown; | return unknown; | ||||
} | } | ||||
bool openTray() { return [device->device isValid] && [device->device ejectMedia]; } | |||||
bool openTray() { return [device->device isValid] && [device->device ejectMedia]; } | |||||
Array<int> getAvailableWriteSpeeds() const | Array<int> getAvailableWriteSpeeds() const | ||||
{ | { | ||||
@@ -422,7 +373,7 @@ public: | |||||
objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; | objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; | ||||
} | } | ||||
OpenDiskDevice* device; | |||||
ScopedPointer<OpenDiskDevice> device; | |||||
private: | private: | ||||
DiskState lastState; | DiskState lastState; | ||||
@@ -528,30 +479,20 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||||
{ | { | ||||
if ([pimpl->device->device isValid]) | if ([pimpl->device->device isValid]) | ||||
{ | { | ||||
[pimpl->device addSourceTrack: source numSamples: numSamps]; | |||||
pimpl->device->addSourceTrack (source, numSamps); | |||||
return true; | return true; | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
String AudioCDBurner::burn (juce::AudioCDBurner::BurnProgressListener* listener, | |||||
String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, | |||||
bool ejectDiscAfterwards, | bool ejectDiscAfterwards, | ||||
bool performFakeBurnForTesting, | bool performFakeBurnForTesting, | ||||
int writeSpeed) | int writeSpeed) | ||||
{ | { | ||||
String error ("Couldn't open or write to the CD device"); | |||||
if ([pimpl->device->device isValid]) | 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_FakeMouseMoveGenerator.h" | ||||
#include "../utility/juce_CarbonVisibility.h" | #include "../utility/juce_CarbonVisibility.h" | ||||
#include "../utility/juce_PluginHostType.h" | #include "../utility/juce_PluginHostType.h" | ||||
#include "../../juce_core/native/juce_osx_ObjCHelpers.h" | |||||
//============================================================================== | //============================================================================== | ||||
#define JuceUICreationClass JucePlugin_AUCocoaViewClassName | |||||
#define juceFilterObjectPropertyID 0x1a45ffe9 | #define juceFilterObjectPropertyID 0x1a45ffe9 | ||||
static Array<void*> activePlugins, activeUIs; | static Array<void*> activePlugins, activeUIs; | ||||
@@ -97,52 +100,6 @@ static const int numChannelConfigs = sizeof (channelConfigs) / sizeof (*channelC | |||||
*/ | */ | ||||
extern AudioProcessor* JUCE_CALLTYPE createPluginFilter(); | 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, | class JuceAU : public JuceAUBaseClass, | ||||
public AudioProcessorListener, | public AudioProcessorListener, | ||||
@@ -187,9 +144,7 @@ public: | |||||
~JuceAU() | ~JuceAU() | ||||
{ | { | ||||
for (int i = activeUIs.size(); --i >= 0;) | |||||
[((JuceUIViewClass*) activeUIs.getUnchecked(i)) filterBeingDeleted: this]; | |||||
deleteActiveEditors(); | |||||
juceFilter = nullptr; | juceFilter = nullptr; | ||||
jassert (activePlugins.contains (this)); | jassert (activePlugins.contains (this)); | ||||
@@ -199,6 +154,8 @@ public: | |||||
shutdownJuce_GUI(); | shutdownJuce_GUI(); | ||||
} | } | ||||
void deleteActiveEditors(); | |||||
//============================================================================== | //============================================================================== | ||||
ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, | ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, | ||||
AudioUnitScope inScope, | AudioUnitScope inScope, | ||||
@@ -281,7 +238,7 @@ public: | |||||
NSString* bundlePath = [NSString stringWithUTF8String: (const char*) bundleFile.getFullPathName().toUTF8()]; | NSString* bundlePath = [NSString stringWithUTF8String: (const char*) bundleFile.getFullPathName().toUTF8()]; | ||||
NSBundle* b = [NSBundle bundleWithPath: bundlePath]; | 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]; | info->mCocoaAUViewBundleLocation = (CFURLRef) [[NSURL fileURLWithPath: [b bundlePath]] retain]; | ||||
return noErr; | return noErr; | ||||
@@ -974,6 +931,32 @@ public: | |||||
// have been transferred to another parent which takes over ownership. | // 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*) | void childBoundsChanged (Component*) | ||||
{ | { | ||||
Component* editor = getChildComponent(0); | 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 | @end | ||||
//============================================================================== | |||||
@implementation JuceUICreationClass | @implementation JuceUICreationClass | ||||
- (JuceUICreationClass*) init | |||||
{ | |||||
return [super init]; | |||||
} | |||||
- (void) dealloc | |||||
{ | |||||
[super dealloc]; | |||||
} | |||||
- (unsigned) interfaceVersion | - (unsigned) interfaceVersion | ||||
{ | { | ||||
return 0; | return 0; | ||||
@@ -1114,7 +1108,7 @@ private: | |||||
- (NSString*) description | - (NSString*) description | ||||
{ | { | ||||
return [NSString stringWithString: @JucePlugin_Name]; | |||||
return [NSString stringWithString: nsStringLiteral (JucePlugin_Name)]; | |||||
} | } | ||||
- (NSView*) uiViewForAudioUnit: (AudioUnit) inAudioUnit | - (NSView*) uiViewForAudioUnit: (AudioUnit) inAudioUnit | ||||
@@ -1138,13 +1132,10 @@ private: | |||||
return nil; | return nil; | ||||
AudioProcessorEditor* editorComp = filter->createEditorIfNeeded(); | AudioProcessorEditor* editorComp = filter->createEditorIfNeeded(); | ||||
if (editorComp == nullptr) | if (editorComp == nullptr) | ||||
return nil; | return nil; | ||||
return [[[JuceUIViewClass alloc] initWithFilter: filter | |||||
withAU: au | |||||
withComponent: editorComp] autorelease]; | |||||
return EditorCompHolder::createViewFor (filter, au, editorComp); | |||||
} | } | ||||
@end | @end | ||||
@@ -97,10 +97,6 @@ | |||||
#error "You need to define the JucePlugin_AUCocoaViewClassName value!" | #error "You need to define the JucePlugin_AUCocoaViewClassName value!" | ||||
#endif | #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) | #if JucePlugin_Build_LV2 && ! defined (JucePlugin_LV2URI) | ||||
#error "You need to define the JucePlugin_LV2URI value!" | #error "You need to define the JucePlugin_LV2URI value!" | ||||
#endif | #endif | ||||
@@ -61,4 +61,7 @@ | |||||
#else | #else | ||||
#include <Cocoa/Cocoa.h> | #include <Cocoa/Cocoa.h> | ||||
#endif | #endif | ||||
#include <objc/runtime.h> | |||||
#include <objc/objc.h> | |||||
#include <objc/message.h> | |||||
#endif | #endif |
@@ -152,7 +152,6 @@ namespace juce | |||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_MAC || JUCE_IOS | #if JUCE_MAC || JUCE_IOS | ||||
#include "native/juce_osx_ObjCHelpers.h" | #include "native/juce_osx_ObjCHelpers.h" | ||||
#include "native/juce_mac_ObjCSuffix.h" | |||||
#endif | #endif | ||||
#if JUCE_ANDROID | #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: | public: | ||||
JuceURLConnectionMessageThread (JuceURLConnection* owner_) | |||||
URLConnectionState (NSObject* owner_, NSURLRequest* req) | |||||
: Thread ("http connection"), | : 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); | 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 | class WebInputStream : public InputStream | ||||
{ | { | ||||
public: | public: | ||||
//============================================================================== | |||||
WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | ||||
URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | ||||
const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | ||||
@@ -338,14 +315,19 @@ public: | |||||
JUCE_AUTORELEASEPOOL | JUCE_AUTORELEASEPOOL | ||||
connection = createConnection (progressCallback, progressCallbackContext); | 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; } | 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; } | bool isExhausted() { return finished; } | ||||
int64 getPosition() { return position; } | int64 getPosition() { return position; } | ||||
@@ -371,7 +353,10 @@ public: | |||||
else | else | ||||
{ | { | ||||
JUCE_AUTORELEASEPOOL | 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; | position += bytesRead; | ||||
if (bytesRead == 0) | if (bytesRead == 0) | ||||
@@ -402,7 +387,7 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
private: | private: | ||||
JuceURLConnection* connection; | |||||
NSObject* connection; | |||||
String address, headers; | String address, headers; | ||||
MemoryBlock postData; | MemoryBlock postData; | ||||
int64 position; | int64 position; | ||||
@@ -412,13 +397,16 @@ private: | |||||
void close() | 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)] | NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (address)] | ||||
cachePolicy: NSURLRequestReloadIgnoringLocalCacheData | cachePolicy: NSURLRequestReloadIgnoringLocalCacheData | ||||
@@ -447,11 +435,13 @@ private: | |||||
[req setHTTPBody: [NSData dataWithBytes: postData.getData() | [req setHTTPBody: [NSData dataWithBytes: postData.getData() | ||||
length: postData.getSize()]]; | 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; | return s; | ||||
[s release]; | [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); | objc_registerClassPair (cls); | ||||
return cls; | |||||
} | |||||
SuperclassType* createInstance() const | |||||
{ | |||||
return class_createInstance (cls, 0); | |||||
} | } | ||||
template <typename Type> | template <typename Type> | ||||
@@ -83,21 +92,54 @@ public: | |||||
jassert (b); (void) b; | 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) | static id sendSuperclassMessage (id self, SEL selector) | ||||
{ | { | ||||
objc_super s = { self, [NSObject class] }; | |||||
objc_super s = { self, [SuperclassType class] }; | |||||
return objc_msgSendSuper (&s, selector); | 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: | 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__ | #endif // __JUCE_OSX_OBJCHELPERS_JUCEHEADER__ |
@@ -81,7 +81,6 @@ namespace juce | |||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | #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_osx_MessageQueue.h" | ||||
#include "native/juce_mac_MessageManager.mm" | #include "native/juce_mac_MessageManager.mm" | ||||
@@ -30,33 +30,24 @@ typedef bool (*CheckEventBlockedByModalComps) (NSEvent*); | |||||
CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; | 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() | static NSString* getBroacastEventName() | ||||
@@ -64,10 +55,10 @@ struct AppDelegateClass | |||||
return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); | return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); | ||||
} | } | ||||
//============================================================================== | |||||
private: | |||||
static id init (id self, SEL) | static id init (id self, SEL) | ||||
{ | { | ||||
self = ObjCClassBuilder::sendSuperclassMessage (self, @selector (init)); | |||||
self = sendSuperclassMessage (self, @selector (init)); | |||||
if (JUCEApplicationBase::isStandaloneApp()) | if (JUCEApplicationBase::isStandaloneApp()) | ||||
{ | { | ||||
@@ -97,7 +88,7 @@ struct AppDelegateClass | |||||
static void dealloc (id self, SEL) | static void dealloc (id self, SEL) | ||||
{ | { | ||||
ObjCClassBuilder::sendSuperclassMessage (self, @selector (dealloc)); | |||||
sendSuperclassMessage (self, @selector (dealloc)); | |||||
} | } | ||||
static void unregisterObservers (id self, SEL) | static void unregisterObservers (id self, SEL) | ||||
@@ -265,8 +256,10 @@ struct AppDelegateHolder | |||||
{ | { | ||||
public: | public: | ||||
AppDelegateHolder() | AppDelegateHolder() | ||||
: delegate ([AppDelegateClass::createInstance() init]) | |||||
{} | |||||
{ | |||||
static AppDelegateClass cls; | |||||
delegate = [cls.createInstance() init]; | |||||
} | |||||
~AppDelegateHolder() | ~AppDelegateHolder() | ||||
{ | { | ||||
@@ -111,7 +111,6 @@ namespace juce | |||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_MAC || JUCE_IOS | #if JUCE_MAC || JUCE_IOS | ||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | #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_CoreGraphicsHelpers.h" | ||||
#include "native/juce_mac_Fonts.mm" | #include "native/juce_mac_Fonts.mm" | ||||
#include "native/juce_mac_CoreGraphicsContext.mm" | #include "native/juce_mac_CoreGraphicsContext.mm" | ||||
@@ -256,7 +256,6 @@ namespace juce | |||||
#if JUCE_MAC || JUCE_IOS | #if JUCE_MAC || JUCE_IOS | ||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | #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_CoreGraphicsHelpers.h" | ||||
#include "../juce_graphics/native/juce_mac_CoreGraphicsContext.h" | #include "../juce_graphics/native/juce_mac_CoreGraphicsContext.h" | ||||
@@ -25,81 +25,69 @@ | |||||
#if JUCE_MAC | #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 | class TemporaryMainMenuWithStandardCommands | ||||
{ | { | ||||
public: | public: | ||||
@@ -127,7 +115,7 @@ public: | |||||
[item release]; | [item release]; | ||||
item = [[NSApp mainMenu] addItemWithTitle: NSLocalizedString (nsStringLiteral ("Edit"), nil) | item = [[NSApp mainMenu] addItemWithTitle: NSLocalizedString (nsStringLiteral ("Edit"), nil) | ||||
action: nil keyEquivalent: nsEmptyString()]; | |||||
action: nil keyEquivalent: nsEmptyString()]; | |||||
[[NSApp mainMenu] setSubmenu: menu forItem: item]; | [[NSApp mainMenu] setSubmenu: menu forItem: item]; | ||||
[menu release]; | [menu release]; | ||||
} | } | ||||
@@ -141,6 +129,7 @@ private: | |||||
MenuBarModel* oldMenu; | MenuBarModel* oldMenu; | ||||
}; | }; | ||||
//============================================================================== | |||||
void FileChooser::showPlatformDialog (Array<File>& results, | void FileChooser::showPlatformDialog (Array<File>& results, | ||||
const String& title, | const String& title, | ||||
const File& currentFileOrDirectory, | const File& currentFileOrDirectory, | ||||
@@ -161,8 +150,15 @@ void FileChooser::showPlatformDialog (Array<File>& results, | |||||
filters->trim(); | filters->trim(); | ||||
filters->removeEmptyStrings(); | 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] | NSSavePanel* panel = isSaveDialogue ? [NSSavePanel savePanel] | ||||
: [NSOpenPanel openPanel]; | : [NSOpenPanel openPanel]; | ||||
@@ -222,6 +218,11 @@ void FileChooser::showPlatformDialog (Array<File>& results, | |||||
[panel setDelegate: nil]; | [panel setDelegate: nil]; | ||||
} | } | ||||
bool FileChooser::isPlatformDialogAvailable() | |||||
{ | |||||
return true; | |||||
} | |||||
#else | #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, | class JuceMainMenuHandler : private MenuBarModel::Listener, | ||||
private DeletedAtShutdown | private DeletedAtShutdown | ||||
{ | { | ||||
public: | public: | ||||
//============================================================================== | |||||
JuceMainMenuHandler() | JuceMainMenuHandler() | ||||
: currentModel (nullptr), | : currentModel (nullptr), | ||||
lastUpdateTime (0) | lastUpdateTime (0) | ||||
{ | { | ||||
callback = [[JuceMenuCallback alloc] initWithOwner: this]; | |||||
static JuceMenuCallbackClass cls; | |||||
callback = [cls.createInstance() performSelector: @selector (initWithOwner:) | |||||
withObject: (id) this]; | |||||
} | } | ||||
~JuceMainMenuHandler() | ~JuceMainMenuHandler() | ||||
@@ -234,17 +208,38 @@ public: | |||||
} | } | ||||
else if (iter.subMenu != nullptr) | 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 | else | ||||
{ | { | ||||
@@ -289,7 +284,7 @@ public: | |||||
MenuBarModel* currentModel; | MenuBarModel* currentModel; | ||||
uint32 lastUpdateTime; | uint32 lastUpdateTime; | ||||
JuceMenuCallback* callback; | |||||
NSObject* callback; | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
@@ -301,7 +296,7 @@ private: | |||||
NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)]; | NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)]; | ||||
[m setAutoenablesItems: false]; | [m setAutoenablesItems: false]; | ||||
[m setDelegate: callback]; | |||||
[m setDelegate: (id<NSMenuDelegate>) callback]; | |||||
for (PopupMenu::MenuItemIterator iter (menu); iter.next();) | for (PopupMenu::MenuItemIterator iter (menu); iter.next();) | ||||
addMenuItem (iter, m, topLevelMenuId, topLevelIndex); | addMenuItem (iter, m, topLevelMenuId, topLevelIndex); | ||||
@@ -385,8 +380,8 @@ private: | |||||
void messageCallback() | void messageCallback() | ||||
{ | { | ||||
if (JuceMainMenuHandler::instance != nullptr) | |||||
JuceMainMenuHandler::instance->menuBarItemsChanged (nullptr); | |||||
if (instance != nullptr) | |||||
instance->menuBarItemsChanged (nullptr); | |||||
} | } | ||||
private: | private: | ||||
@@ -402,8 +397,8 @@ private: | |||||
void messageCallback() | void messageCallback() | ||||
{ | { | ||||
if (JuceMainMenuHandler::instance != nullptr) | |||||
JuceMainMenuHandler::instance->invokeDirectly (commandId, topLevelIndex); | |||||
if (instance != nullptr) | |||||
instance->invokeDirectly (commandId, topLevelIndex); | |||||
} | } | ||||
private: | private: | ||||
@@ -411,74 +406,82 @@ private: | |||||
JUCE_DECLARE_NON_COPYABLE (AsyncCommandInvoker); | 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 | namespace MainMenuHelpers | ||||
@@ -106,7 +106,6 @@ namespace juce | |||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_MAC || JUCE_IOS | #if JUCE_MAC || JUCE_IOS | ||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | #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_CoreGraphicsHelpers.h" | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
@@ -23,66 +23,57 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
} // (juce namespace) | |||||
class WebBrowserComponentInternal; | |||||
#if JUCE_MAC | #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 | #else | ||||
} // (juce namespace) | |||||
//============================================================================== | //============================================================================== | ||||
@interface WebViewTapDetector : NSObject <UIGestureRecognizerDelegate> | @interface WebViewTapDetector : NSObject <UIGestureRecognizerDelegate> | ||||
{ | { | ||||
@@ -126,10 +117,10 @@ class WebBrowserComponentInternal; | |||||
return ownerComponent->pageAboutToLoad (nsStringToJuce (request.URL.absoluteString)); | return ownerComponent->pageAboutToLoad (nsStringToJuce (request.URL.absoluteString)); | ||||
} | } | ||||
@end | @end | ||||
#endif | |||||
namespace juce | |||||
{ | |||||
namespace juce { | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
class WebBrowserComponentInternal | class WebBrowserComponentInternal | ||||
@@ -148,7 +139,9 @@ public: | |||||
groupName: nsEmptyString()]; | groupName: nsEmptyString()]; | ||||
setView (webView); | setView (webView); | ||||
clickListener = [[DownloadClickDetector alloc] initWithWebBrowserOwner: owner]; | |||||
static DownloadClickDetectorClass cls; | |||||
clickListener = [cls.createInstance() init]; | |||||
DownloadClickDetectorClass::setOwner (clickListener, owner); | |||||
[webView setPolicyDelegate: clickListener]; | [webView setPolicyDelegate: clickListener]; | ||||
[webView setFrameLoadDelegate: clickListener]; | [webView setFrameLoadDelegate: clickListener]; | ||||
#else | #else | ||||
@@ -238,7 +231,7 @@ public: | |||||
private: | private: | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
WebView* webView; | WebView* webView; | ||||
DownloadClickDetector* clickListener; | |||||
NSObject* clickListener; | |||||
#else | #else | ||||
UIWebView* webView; | UIWebView* webView; | ||||
WebViewTapDetector* tapDetector; | WebViewTapDetector* tapDetector; | ||||
@@ -171,7 +171,6 @@ static void clearGLError() | |||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_MAC || JUCE_IOS | #if JUCE_MAC || JUCE_IOS | ||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | #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_CoreGraphicsHelpers.h" | ||||
#if JUCE_MAC | #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 | class OpenGLContext::NativeContext | ||||
@@ -137,8 +123,15 @@ public: | |||||
NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes: attribs]; | 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 | renderContext = [[[NSOpenGLContext alloc] initWithFormat: format | ||||
shareContext: (NSOpenGLContext*) contextToShareWith] autorelease]; | shareContext: (NSOpenGLContext*) contextToShareWith] autorelease]; | ||||
@@ -153,6 +146,7 @@ public: | |||||
~NativeContext() | ~NativeContext() | ||||
{ | { | ||||
[[NSNotificationCenter defaultCenter] removeObserver: view]; | |||||
[renderContext clearDrawable]; | [renderContext clearDrawable]; | ||||
[renderContext setView: nil]; | [renderContext setView: nil]; | ||||
[view setOpenGLContext: nil]; | [view setOpenGLContext: nil]; | ||||
@@ -173,7 +167,7 @@ public: | |||||
if ([renderContext view] != view) | if ([renderContext view] != view) | ||||
[renderContext setView: view]; | [renderContext setView: view]; | ||||
[view makeActive]; | |||||
ThreadSafeNSOpenGLViewClass::makeActive (view); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -228,7 +222,7 @@ public: | |||||
private: | private: | ||||
NSOpenGLContext* renderContext; | NSOpenGLContext* renderContext; | ||||
ThreadSafeNSOpenGLView* view; | |||||
NSOpenGLView* view; | |||||
ReferenceCountedObjectPtr<ReferenceCountedObject> viewAttachment; | ReferenceCountedObjectPtr<ReferenceCountedObject> viewAttachment; | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext); | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext); | ||||
@@ -108,7 +108,6 @@ namespace juce | |||||
#if JUCE_MAC || JUCE_IOS | #if JUCE_MAC || JUCE_IOS | ||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | #include "../juce_core/native/juce_osx_ObjCHelpers.h" | ||||
#include "../juce_core/native/juce_mac_ObjCSuffix.h" | |||||
#if JUCE_USE_CAMERA | #if JUCE_USE_CAMERA | ||||
#include "native/juce_mac_CameraDevice.mm" | #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" | #error "On the Mac, cameras use Quicktime, so if you turn on JUCE_USE_CAMERA, you also need to enable JUCE_QUICKTIME" | ||||
#endif | #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); | extern Image juce_createImageFromCIImage (CIImage* im, int w, int h); | ||||
//============================================================================== | //============================================================================== | ||||
class QTCameraDeviceInteral | |||||
class QTCameraDeviceInternal | |||||
{ | { | ||||
public: | public: | ||||
QTCameraDeviceInteral (CameraDevice* owner, const int index) | |||||
QTCameraDeviceInternal (CameraDevice* owner, const int index) | |||||
: input (nil), | : input (nil), | ||||
audioDevice (nil), | audioDevice (nil), | ||||
audioInput (nil), | audioInput (nil), | ||||
session (nil), | session (nil), | ||||
fileOutput (nil), | fileOutput (nil), | ||||
imageOutput (nil) | |||||
imageOutput (nil), | |||||
firstPresentationTime (0), | |||||
averageTimeOffset (0) | |||||
{ | { | ||||
JUCE_AUTORELEASEPOOL | JUCE_AUTORELEASEPOOL | ||||
@@ -79,8 +49,10 @@ public: | |||||
NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo]; | NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo]; | ||||
device = (QTCaptureDevice*) [devs objectAtIndex: index]; | 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; | NSError* err = nil; | ||||
[device retain]; | [device retain]; | ||||
@@ -112,7 +84,7 @@ public: | |||||
DBG (openingError); | DBG (openingError); | ||||
} | } | ||||
~QTCameraDeviceInteral() | |||||
~QTCameraDeviceInternal() | |||||
{ | { | ||||
[session stopRunning]; | [session stopRunning]; | ||||
[session removeOutput: imageOutput]; | [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; | QTCaptureDevice* device; | ||||
QTCaptureDeviceInput* input; | QTCaptureDeviceInput* input; | ||||
QTCaptureDevice* audioDevice; | QTCaptureDevice* audioDevice; | ||||
@@ -201,87 +200,69 @@ public: | |||||
QTCaptureSession* session; | QTCaptureSession* session; | ||||
QTCaptureMovieFileOutput* fileOutput; | QTCaptureMovieFileOutput* fileOutput; | ||||
QTCaptureDecompressedVideoOutput* imageOutput; | QTCaptureDecompressedVideoOutput* imageOutput; | ||||
QTCaptureCallbackDelegate* callbackDelegate; | |||||
NSObject* callbackDelegate; | |||||
String openingError; | String openingError; | ||||
int64 firstPresentationTime; | |||||
int64 averageTimeOffset; | |||||
Array<CameraDevice::Listener*> listeners; | Array<CameraDevice::Listener*> listeners; | ||||
CriticalSection listenerLock; | 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 | class QTCaptureViewerComp : public NSViewComponent | ||||
{ | { | ||||
public: | public: | ||||
QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInteral* const internal) | |||||
QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInternal* const internal) | |||||
{ | { | ||||
JUCE_AUTORELEASEPOOL | JUCE_AUTORELEASEPOOL | ||||
captureView = [[QTCaptureView alloc] init]; | captureView = [[QTCaptureView alloc] init]; | ||||
@@ -306,19 +287,19 @@ CameraDevice::CameraDevice (const String& name_, int index) | |||||
: name (name_) | : name (name_) | ||||
{ | { | ||||
isRecording = false; | isRecording = false; | ||||
internal = new QTCameraDeviceInteral (this, index); | |||||
internal = new QTCameraDeviceInternal (this, index); | |||||
} | } | ||||
CameraDevice::~CameraDevice() | CameraDevice::~CameraDevice() | ||||
{ | { | ||||
stopRecording(); | stopRecording(); | ||||
delete static_cast <QTCameraDeviceInteral*> (internal); | |||||
delete static_cast <QTCameraDeviceInternal*> (internal); | |||||
internal = nullptr; | internal = nullptr; | ||||
} | } | ||||
Component* CameraDevice::createViewerComponent() | Component* CameraDevice::createViewerComponent() | ||||
{ | { | ||||
return new QTCaptureViewerComp (this, static_cast <QTCameraDeviceInteral*> (internal)); | |||||
return new QTCaptureViewerComp (this, static_cast <QTCameraDeviceInternal*> (internal)); | |||||
} | } | ||||
String CameraDevice::getFileExtension() | String CameraDevice::getFileExtension() | ||||
@@ -330,8 +311,8 @@ void CameraDevice::startRecordingToFile (const File& file, int quality) | |||||
{ | { | ||||
stopRecording(); | stopRecording(); | ||||
QTCameraDeviceInteral* const d = static_cast <QTCameraDeviceInteral*> (internal); | |||||
d->callbackDelegate->firstPresentationTime = 0; | |||||
QTCameraDeviceInternal* const d = static_cast <QTCameraDeviceInternal*> (internal); | |||||
d->firstPresentationTime = 0; | |||||
file.deleteFile(); | file.deleteFile(); | ||||
// In some versions of QT (e.g. on 10.5), if you record video without audio, the speed comes | // 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 | 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(); | return Time(); | ||||
} | } | ||||
@@ -378,7 +359,7 @@ void CameraDevice::stopRecording() | |||||
{ | { | ||||
if (isRecording) | if (isRecording) | ||||
{ | { | ||||
static_cast <QTCameraDeviceInteral*> (internal)->resetFile(); | |||||
static_cast <QTCameraDeviceInternal*> (internal)->resetFile(); | |||||
isRecording = false; | isRecording = false; | ||||
} | } | ||||
} | } | ||||
@@ -386,13 +367,13 @@ void CameraDevice::stopRecording() | |||||
void CameraDevice::addListener (Listener* listenerToAdd) | void CameraDevice::addListener (Listener* listenerToAdd) | ||||
{ | { | ||||
if (listenerToAdd != nullptr) | if (listenerToAdd != nullptr) | ||||
static_cast <QTCameraDeviceInteral*> (internal)->addListener (listenerToAdd); | |||||
static_cast <QTCameraDeviceInternal*> (internal)->addListener (listenerToAdd); | |||||
} | } | ||||
void CameraDevice::removeListener (Listener* listenerToRemove) | void CameraDevice::removeListener (Listener* listenerToRemove) | ||||
{ | { | ||||
if (listenerToRemove != nullptr) | 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)); | 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 d.release(); | ||||
return nullptr; | return nullptr; | ||||
@@ -25,49 +25,31 @@ | |||||
#if JUCE_QUICKTIME | #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)) | #define theMovie (static_cast <QTMovie*> (movie)) | ||||
@@ -79,8 +61,10 @@ QuickTimeMovieComponent::QuickTimeMovieComponent() | |||||
setOpaque (true); | setOpaque (true); | ||||
setVisible (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); | setView (view); | ||||
[view setNextResponder: [view superview]]; | |||||
[view setWantsLayer: YES]; // prevents the view failing to redraw correctly when paused. | [view setWantsLayer: YES]; // prevents the view failing to redraw correctly when paused. | ||||
[view release]; | [view release]; | ||||
} | } | ||||