@@ -61,8 +61,6 @@ public: | |||
if (getTargetLocationString().isEmpty()) | |||
getTargetLocationValue() = getDefaultBuildsRootFolder() + (iOS ? "iOS" : "MacOSX"); | |||
setValueIfVoid (getObjCSuffixValue(), createAlphaNumericUID()); | |||
} | |||
static XCodeProjectExporter* createForSettings (Project& project, const ValueTree& settings) | |||
@@ -76,9 +74,6 @@ public: | |||
} | |||
//============================================================================== | |||
Value getObjCSuffixValue() { return getSetting ("objCExtraSuffix"); } | |||
String getObjCSuffixString() const { return settings ["objCExtraSuffix"]; } | |||
Value getPListToMergeValue() { return getSetting ("customPList"); } | |||
String getPListToMergeString() const { return settings ["customPList"]; } | |||
@@ -113,11 +108,6 @@ public: | |||
{ | |||
ProjectExporter::createPropertyEditors (props); | |||
props.add (new TextPropertyComponent (getObjCSuffixValue(), "Objective-C class name suffix", 64, false), | |||
"Because objective-C linkage is done by string-matching, you can get horrible linkage mix-ups when different modules containing the " | |||
"same class-names are loaded simultaneously. This setting lets you provide a unique string that will be used in naming " | |||
"the obj-C classes in your executable to avoid this."); | |||
if (projectType.isGUIApplication() && ! iOS) | |||
{ | |||
props.add (new TextPropertyComponent (getSetting ("documentExtensions"), "Document file extensions", 128, false), | |||
@@ -746,12 +736,6 @@ private: | |||
s.add ("GCC_SYMBOLS_PRIVATE_EXTERN = YES"); | |||
} | |||
{ | |||
const String objCSuffix (getObjCSuffixString().trim()); | |||
if (objCSuffix.isNotEmpty()) | |||
defines.set ("JUCE_ObjCExtraSuffix", replacePreprocessorTokens (config, objCSuffix)); | |||
} | |||
{ | |||
defines = mergePreprocessorDefs (defines, getAllPreprocessorDefs (config)); | |||
@@ -163,7 +163,6 @@ namespace juce | |||
//============================================================================== | |||
#if JUCE_MAC | |||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||
#include "../juce_core/native/juce_mac_ObjCSuffix.h" | |||
#include "native/juce_mac_CoreAudio.cpp" | |||
#include "native/juce_mac_CoreMidi.cpp" | |||
@@ -25,305 +25,256 @@ | |||
const int kilobytesPerSecond1x = 176; | |||
} // (juce namespace) | |||
#define OpenDiskDevice MakeObjCClassName(OpenDiskDevice) | |||
@interface OpenDiskDevice : NSObject | |||
struct AudioTrackProducerClass : public ObjCClass <NSObject> | |||
{ | |||
@public | |||
DRDevice* device; | |||
NSMutableArray* tracks; | |||
bool underrunProtection; | |||
} | |||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device; | |||
- (void) dealloc; | |||
- (void) addSourceTrack: (juce::AudioSource*) source numSamples: (int) numSamples_; | |||
- (void) burn: (juce::AudioCDBurner::BurnProgressListener*) listener errorString: (juce::String*) error | |||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed; | |||
@end | |||
//============================================================================== | |||
#define AudioTrackProducer MakeObjCClassName(AudioTrackProducer) | |||
@interface AudioTrackProducer : NSObject | |||
{ | |||
juce::AudioSource* source; | |||
int readPosition, lengthInFrames; | |||
} | |||
- (AudioTrackProducer*) init: (int) lengthInFrames; | |||
- (AudioTrackProducer*) initWithAudioSource: (juce::AudioSource*) source numSamples: (int) lengthInSamples; | |||
- (void) dealloc; | |||
- (void) setupTrackProperties: (DRTrack*) track; | |||
- (void) cleanupTrackAfterBurn: (DRTrack*) track; | |||
- (BOOL) cleanupTrackAfterVerification:(DRTrack*)track; | |||
- (uint64_t) estimateLengthOfTrack:(DRTrack*)track; | |||
- (BOOL) prepareTrack:(DRTrack*)track forBurn:(DRBurn*)burn | |||
toMedia:(NSDictionary*)mediaInfo; | |||
- (BOOL) prepareTrackForVerification:(DRTrack*)track; | |||
- (uint32_t) produceDataForTrack:(DRTrack*)track intoBuffer:(char*)buffer | |||
length:(uint32_t)bufferLength atAddress:(uint64_t)address | |||
blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags; | |||
- (uint32_t) producePreGapForTrack:(DRTrack*)track | |||
intoBuffer:(char*)buffer length:(uint32_t)bufferLength | |||
atAddress:(uint64_t)address blockSize:(uint32_t)blockSize | |||
ioFlags:(uint32_t*)flags; | |||
- (BOOL) verifyDataForTrack:(DRTrack*)track inBuffer:(const char*)buffer | |||
length:(uint32_t)bufferLength atAddress:(uint64_t)address | |||
blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags; | |||
- (uint32_t) producePreGapForTrack:(DRTrack*)track | |||
intoBuffer:(char*)buffer length:(uint32_t)bufferLength | |||
atAddress:(uint64_t)address blockSize:(uint32_t)blockSize | |||
ioFlags:(uint32_t*)flags; | |||
@end | |||
//============================================================================== | |||
@implementation OpenDiskDevice | |||
AudioTrackProducerClass() : ObjCClass ("JUCEAudioTrackProducer_") | |||
{ | |||
addIvar<AudioSourceHolder*> ("source"); | |||
addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder, "@@:^v"); | |||
addMethod (@selector (cleanupTrackAfterBurn:), cleanupTrackAfterBurn, "v@:@"); | |||
addMethod (@selector (cleanupTrackAfterVerification:), cleanupTrackAfterVerification, "c@:@"); | |||
addMethod (@selector (estimateLengthOfTrack:), estimateLengthOfTrack, "Q@:@"); | |||
addMethod (@selector (prepareTrack:forBurn:toMedia:), prepareTrack, "c@:@@@"); | |||
addMethod (@selector (prepareTrackForVerification:), prepareTrackForVerification, "c@:@"); | |||
addMethod (@selector (produceDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), | |||
produceDataForTrack, "I@:@^cIQI^I"); | |||
addMethod (@selector (producePreGapForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), | |||
produceDataForTrack, "I@:@^cIQI^I"); | |||
addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:), | |||
produceDataForTrack, "I@:@^cIQI^I"); | |||
registerClass(); | |||
} | |||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device_ | |||
{ | |||
[super init]; | |||
struct AudioSourceHolder | |||
{ | |||
AudioSourceHolder (AudioSource* source_, int numFrames) | |||
: source (source_), readPosition (0), lengthInFrames (numFrames) | |||
{ | |||
} | |||
device = device_; | |||
tracks = [[NSMutableArray alloc] init]; | |||
underrunProtection = true; | |||
return self; | |||
} | |||
~AudioSourceHolder() | |||
{ | |||
if (source != nullptr) | |||
source->releaseResources(); | |||
} | |||
- (void) dealloc | |||
{ | |||
[tracks release]; | |||
[super dealloc]; | |||
} | |||
ScopedPointer<AudioSource> source; | |||
int readPosition, lengthInFrames; | |||
}; | |||
- (void) addSourceTrack: (juce::AudioSource*) source_ numSamples: (int) numSamples_ | |||
{ | |||
AudioTrackProducer* p = [[AudioTrackProducer alloc] initWithAudioSource: source_ numSamples: numSamples_]; | |||
DRTrack* t = [[DRTrack alloc] initWithProducer: p]; | |||
[p setupTrackProperties: t]; | |||
private: | |||
static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source) | |||
{ | |||
self = sendSuperclassMessage (self, @selector (init)); | |||
object_setInstanceVariable (self, "source", source); | |||
return self; | |||
} | |||
[tracks addObject: t]; | |||
static AudioSourceHolder* getSource (id self) | |||
{ | |||
return getIvar<AudioSourceHolder*> (self, "source"); | |||
} | |||
[t release]; | |||
[p release]; | |||
} | |||
static void dealloc (id self, SEL) | |||
{ | |||
delete getSource (self); | |||
sendSuperclassMessage (self, @selector (dealloc)); | |||
} | |||
- (void) burn: (juce::AudioCDBurner::BurnProgressListener*) listener errorString: (juce::String*) error | |||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed | |||
{ | |||
DRBurn* burn = [DRBurn burnForDevice: device]; | |||
static void cleanupTrackAfterBurn (id self, SEL, DRTrack*) {} | |||
static BOOL cleanupTrackAfterVerification (id self, SEL, DRTrack*) { return true; } | |||
if (! [device acquireExclusiveAccess]) | |||
static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*) | |||
{ | |||
*error = "Couldn't open or write to the CD device"; | |||
return; | |||
return getSource (self)->lengthInFrames; | |||
} | |||
[device acquireMediaReservation]; | |||
static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*) | |||
{ | |||
AudioSourceHolder* const source = getSource (self); | |||
NSMutableDictionary* d = [[burn properties] mutableCopy]; | |||
[d autorelease]; | |||
[d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; | |||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; | |||
[d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey]; | |||
if (source != nullptr) | |||
{ | |||
source->source->prepareToPlay (44100 / 75, 44100); | |||
source->readPosition = 0; | |||
} | |||
if (burnSpeed > 0) | |||
[d setObject: [NSNumber numberWithFloat: burnSpeed * juce::kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey]; | |||
return true; | |||
} | |||
if (! underrunProtection) | |||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey]; | |||
static BOOL prepareTrackForVerification (id self, SEL, DRTrack*) | |||
{ | |||
AudioSourceHolder* const source = getSource (self); | |||
[burn setProperties: d]; | |||
if (source != nullptr) | |||
source->source->prepareToPlay (44100 / 75, 44100); | |||
[burn writeLayout: tracks]; | |||
return true; | |||
} | |||
for (;;) | |||
static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer, | |||
uint32_t bufferLength, uint64_t /*address*/, | |||
uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||
{ | |||
juce::Thread::sleep (300); | |||
float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue]; | |||
AudioSourceHolder* const source = getSource (self); | |||
if (listener != nullptr && listener->audioCDBurnProgress (progress)) | |||
if (source != nullptr) | |||
{ | |||
[burn abort]; | |||
*error = "User cancelled the write operation"; | |||
break; | |||
} | |||
const int numSamples = jmin ((int) bufferLength / 4, | |||
(source->lengthInFrames * (44100 / 75)) - source->readPosition); | |||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed]) | |||
{ | |||
*error = "Write operation failed"; | |||
break; | |||
} | |||
else if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone]) | |||
{ | |||
break; | |||
} | |||
NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey] | |||
objectForKey: DRErrorStatusErrorStringKey]; | |||
if (numSamples > 0) | |||
{ | |||
AudioSampleBuffer tempBuffer (2, numSamples); | |||
AudioSourceChannelInfo info (tempBuffer); | |||
source->source->getNextAudioBlock (info); | |||
typedef AudioData::Pointer <AudioData::Int16, | |||
AudioData::LittleEndian, | |||
AudioData::Interleaved, | |||
AudioData::NonConst> CDSampleFormat; | |||
typedef AudioData::Pointer <AudioData::Float32, | |||
AudioData::NativeEndian, | |||
AudioData::NonInterleaved, | |||
AudioData::Const> SourceSampleFormat; | |||
CDSampleFormat left (buffer, 2); | |||
left.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (0)), numSamples); | |||
CDSampleFormat right (buffer + 2, 2); | |||
right.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (1)), numSamples); | |||
source->readPosition += numSamples; | |||
} | |||
if ([err length] > 0) | |||
{ | |||
*error = juce::CharPointer_UTF8 ([err UTF8String]); | |||
break; | |||
return numSamples * 4; | |||
} | |||
return 0; | |||
} | |||
[device releaseMediaReservation]; | |||
[device releaseExclusiveAccess]; | |||
} | |||
@end | |||
static uint32_t producePreGapForTrack (id self, SEL, DRTrack*, char* buffer, | |||
uint32_t bufferLength, uint64_t /*address*/, | |||
uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||
{ | |||
zeromem (buffer, bufferLength); | |||
return bufferLength; | |||
} | |||
//============================================================================== | |||
@implementation AudioTrackProducer | |||
static BOOL verifyDataForTrack (id self, SEL, DRTrack*, const char*, | |||
uint32_t /*bufferLength*/, uint64_t /*address*/, | |||
uint32_t /*blockSize*/, uint32_t* /*flags*/) | |||
{ | |||
return true; | |||
} | |||
}; | |||
- (AudioTrackProducer*) init: (int) lengthInFrames_ | |||
struct OpenDiskDevice | |||
{ | |||
lengthInFrames = lengthInFrames_; | |||
readPosition = 0; | |||
return self; | |||
} | |||
OpenDiskDevice (DRDevice* device_) | |||
: device (device_), | |||
tracks ([[NSMutableArray alloc] init]), | |||
underrunProtection (true) | |||
{ | |||
} | |||
- (void) setupTrackProperties: (DRTrack*) track | |||
{ | |||
NSMutableDictionary* p = [[track properties] mutableCopy]; | |||
[p setObject:[DRMSF msfWithFrames: lengthInFrames] forKey: DRTrackLengthKey]; | |||
[p setObject:[NSNumber numberWithUnsignedShort:2352] forKey: DRBlockSizeKey]; | |||
[p setObject:[NSNumber numberWithInt:0] forKey: DRDataFormKey]; | |||
[p setObject:[NSNumber numberWithInt:0] forKey: DRBlockTypeKey]; | |||
[p setObject:[NSNumber numberWithInt:0] forKey: DRTrackModeKey]; | |||
[p setObject:[NSNumber numberWithInt:0] forKey: DRSessionFormatKey]; | |||
~OpenDiskDevice() | |||
{ | |||
[tracks release]; | |||
} | |||
void addSourceTrack (AudioSource* source, int numSamples) | |||
{ | |||
if (source != nullptr) | |||
{ | |||
const int numFrames = (numSamples + 587) / 588; | |||
[track setProperties: p]; | |||
[p release]; | |||
} | |||
static AudioTrackProducerClass cls; | |||
- (AudioTrackProducer*) initWithAudioSource: (juce::AudioSource*) source_ numSamples: (int) lengthInSamples | |||
{ | |||
AudioTrackProducer* s = [self init: (lengthInSamples + 587) / 588]; | |||
NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:) | |||
withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)]; | |||
DRTrack* track = [[DRTrack alloc] initWithProducer: producer]; | |||
if (s != nil) | |||
s->source = source_; | |||
{ | |||
NSMutableDictionary* p = [[track properties] mutableCopy]; | |||
[p setObject: [DRMSF msfWithFrames: numFrames] forKey: DRTrackLengthKey]; | |||
[p setObject: [NSNumber numberWithUnsignedShort: 2352] forKey: DRBlockSizeKey]; | |||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRDataFormKey]; | |||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRBlockTypeKey]; | |||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRTrackModeKey]; | |||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRSessionFormatKey]; | |||
[track setProperties: p]; | |||
[p release]; | |||
} | |||
return s; | |||
} | |||
[tracks addObject: track]; | |||
- (void) dealloc | |||
{ | |||
if (source != nullptr) | |||
{ | |||
source->releaseResources(); | |||
delete source; | |||
[track release]; | |||
[producer release]; | |||
} | |||
} | |||
[super dealloc]; | |||
} | |||
- (void) cleanupTrackAfterBurn: (DRTrack*) track | |||
{ | |||
(void) track; | |||
} | |||
- (BOOL) cleanupTrackAfterVerification: (DRTrack*) track | |||
{ | |||
(void) track; | |||
return true; | |||
} | |||
- (uint64_t) estimateLengthOfTrack: (DRTrack*) track | |||
{ | |||
(void) track; | |||
return lengthInFrames; | |||
} | |||
String burn (AudioCDBurner::BurnProgressListener* listener, | |||
bool shouldEject, bool peformFakeBurnForTesting, int burnSpeed) | |||
{ | |||
DRBurn* burn = [DRBurn burnForDevice: device]; | |||
- (BOOL) prepareTrack: (DRTrack*) track forBurn: (DRBurn*) burn | |||
toMedia: (NSDictionary*) mediaInfo | |||
{ | |||
(void) track; (void) burn; (void) mediaInfo; | |||
if (! [device acquireExclusiveAccess]) | |||
return "Couldn't open or write to the CD device"; | |||
if (source != nullptr) | |||
source->prepareToPlay (44100 / 75, 44100); | |||
[device acquireMediaReservation]; | |||
readPosition = 0; | |||
return true; | |||
} | |||
NSMutableDictionary* d = [[burn properties] mutableCopy]; | |||
[d autorelease]; | |||
[d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; | |||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; | |||
[d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey]; | |||
- (BOOL) prepareTrackForVerification: (DRTrack*) track | |||
{ | |||
(void) track; | |||
if (source != nullptr) | |||
source->prepareToPlay (44100 / 75, 44100); | |||
if (burnSpeed > 0) | |||
[d setObject: [NSNumber numberWithFloat: burnSpeed * kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey]; | |||
return true; | |||
} | |||
if (! underrunProtection) | |||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey]; | |||
- (uint32_t) produceDataForTrack: (DRTrack*) track intoBuffer: (char*) buffer | |||
length: (uint32_t) bufferLength atAddress: (uint64_t) address | |||
blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags | |||
{ | |||
(void) track; (void) address; (void) blockSize; (void) flags; | |||
[burn setProperties: d]; | |||
if (source != nullptr) | |||
{ | |||
const int numSamples = juce::jmin ((int) bufferLength / 4, (lengthInFrames * (44100 / 75)) - readPosition); | |||
[burn writeLayout: tracks]; | |||
if (numSamples > 0) | |||
for (;;) | |||
{ | |||
juce::AudioSampleBuffer tempBuffer (2, numSamples); | |||
juce::AudioSourceChannelInfo info (tempBuffer); | |||
source->getNextAudioBlock (info); | |||
typedef juce::AudioData::Pointer <juce::AudioData::Int16, | |||
juce::AudioData::LittleEndian, | |||
juce::AudioData::Interleaved, | |||
juce::AudioData::NonConst> CDSampleFormat; | |||
typedef juce::AudioData::Pointer <juce::AudioData::Float32, | |||
juce::AudioData::NativeEndian, | |||
juce::AudioData::NonInterleaved, | |||
juce::AudioData::Const> SourceSampleFormat; | |||
CDSampleFormat left (buffer, 2); | |||
left.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (0)), numSamples); | |||
CDSampleFormat right (buffer + 2, 2); | |||
right.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (1)), numSamples); | |||
readPosition += numSamples; | |||
} | |||
Thread::sleep (300); | |||
float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue]; | |||
return numSamples * 4; | |||
} | |||
if (listener != nullptr && listener->audioCDBurnProgress (progress)) | |||
{ | |||
[burn abort]; | |||
return "User cancelled the write operation"; | |||
} | |||
return 0; | |||
} | |||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed]) | |||
return "Write operation failed"; | |||
- (uint32_t) producePreGapForTrack: (DRTrack*) track | |||
intoBuffer: (char*) buffer length: (uint32_t) bufferLength | |||
atAddress: (uint64_t) address blockSize: (uint32_t) blockSize | |||
ioFlags: (uint32_t*) flags | |||
{ | |||
(void) track; (void) address; (void) blockSize; (void) flags; | |||
zeromem (buffer, bufferLength); | |||
return bufferLength; | |||
} | |||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone]) | |||
break; | |||
- (BOOL) verifyDataForTrack: (DRTrack*) track inBuffer: (const char*) buffer | |||
length: (uint32_t) bufferLength atAddress: (uint64_t) address | |||
blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags | |||
{ | |||
(void) track; (void) buffer; (void) bufferLength; (void) address; (void) blockSize; (void) flags; | |||
return true; | |||
} | |||
NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey] | |||
objectForKey: DRErrorStatusErrorStringKey]; | |||
@end | |||
if ([err length] > 0) | |||
return CharPointer_UTF8 ([err UTF8String]); | |||
} | |||
[device releaseMediaReservation]; | |||
[device releaseExclusiveAccess]; | |||
return String::empty; | |||
} | |||
namespace juce | |||
{ | |||
DRDevice* device; | |||
NSMutableArray* tracks; | |||
bool underrunProtection; | |||
}; | |||
//============================================================================== | |||
class AudioCDBurner::Pimpl : public Timer | |||
@@ -333,9 +284,10 @@ public: | |||
: device (0), owner (owner_) | |||
{ | |||
DRDevice* dev = [[DRDevice devices] objectAtIndex: deviceIndex]; | |||
if (dev != nil) | |||
{ | |||
device = [[OpenDiskDevice alloc] initWithDRDevice: dev]; | |||
device = new OpenDiskDevice (dev); | |||
lastState = getDiskState(); | |||
startTimer (1000); | |||
} | |||
@@ -344,7 +296,6 @@ public: | |||
~Pimpl() | |||
{ | |||
stopTimer(); | |||
[device release]; | |||
} | |||
void timerCallback() | |||
@@ -386,7 +337,7 @@ public: | |||
return unknown; | |||
} | |||
bool openTray() { return [device->device isValid] && [device->device ejectMedia]; } | |||
bool openTray() { return [device->device isValid] && [device->device ejectMedia]; } | |||
Array<int> getAvailableWriteSpeeds() const | |||
{ | |||
@@ -422,7 +373,7 @@ public: | |||
objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; | |||
} | |||
OpenDiskDevice* device; | |||
ScopedPointer<OpenDiskDevice> device; | |||
private: | |||
DiskState lastState; | |||
@@ -528,30 +479,20 @@ bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) | |||
{ | |||
if ([pimpl->device->device isValid]) | |||
{ | |||
[pimpl->device addSourceTrack: source numSamples: numSamps]; | |||
pimpl->device->addSourceTrack (source, numSamps); | |||
return true; | |||
} | |||
return false; | |||
} | |||
String AudioCDBurner::burn (juce::AudioCDBurner::BurnProgressListener* listener, | |||
String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, | |||
bool ejectDiscAfterwards, | |||
bool performFakeBurnForTesting, | |||
int writeSpeed) | |||
{ | |||
String error ("Couldn't open or write to the CD device"); | |||
if ([pimpl->device->device isValid]) | |||
{ | |||
error = String::empty; | |||
[pimpl->device burn: listener | |||
errorString: &error | |||
ejectAfterwards: ejectDiscAfterwards | |||
isFake: performFakeBurnForTesting | |||
speed: writeSpeed]; | |||
} | |||
return pimpl->device->burn (listener, ejectDiscAfterwards, performFakeBurnForTesting, writeSpeed); | |||
return error; | |||
return "Couldn't open or write to the CD device"; | |||
} |
@@ -77,8 +77,11 @@ | |||
#include "../utility/juce_FakeMouseMoveGenerator.h" | |||
#include "../utility/juce_CarbonVisibility.h" | |||
#include "../utility/juce_PluginHostType.h" | |||
#include "../../juce_core/native/juce_osx_ObjCHelpers.h" | |||
//============================================================================== | |||
#define JuceUICreationClass JucePlugin_AUCocoaViewClassName | |||
#define juceFilterObjectPropertyID 0x1a45ffe9 | |||
static Array<void*> activePlugins, activeUIs; | |||
@@ -97,52 +100,6 @@ static const int numChannelConfigs = sizeof (channelConfigs) / sizeof (*channelC | |||
*/ | |||
extern AudioProcessor* JUCE_CALLTYPE createPluginFilter(); | |||
//============================================================================== | |||
#define appendMacro1(a, b, c, d) a ## _ ## b ## _ ## c ## _ ## d | |||
#define appendMacro2(a, b, c, d) appendMacro1(a, b, c, d) | |||
#define MakeObjCClassName(rootName) appendMacro2 (rootName, JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JucePlugin_AUExportPrefix) | |||
#define JuceUICreationClass JucePlugin_AUCocoaViewClassName | |||
#define JuceUIViewClass MakeObjCClassName(JuceUIViewClass) | |||
class JuceAU; | |||
class EditorCompHolder; | |||
//============================================================================== | |||
@interface JuceUICreationClass : NSObject <AUCocoaUIBase> | |||
{ | |||
} | |||
- (JuceUICreationClass*) init; | |||
- (void) dealloc; | |||
- (unsigned) interfaceVersion; | |||
- (NSString*) description; | |||
- (NSView*) uiViewForAudioUnit: (AudioUnit) inAudioUnit | |||
withSize: (NSSize) inPreferredSize; | |||
@end | |||
//============================================================================== | |||
@interface JuceUIViewClass : NSView | |||
{ | |||
AudioProcessor* filter; | |||
JuceAU* au; | |||
EditorCompHolder* editorComp; | |||
} | |||
- (JuceUIViewClass*) initWithFilter: (AudioProcessor*) filter | |||
withAU: (JuceAU*) au | |||
withComponent: (AudioProcessorEditor*) editorComp; | |||
- (void) dealloc; | |||
- (void) shutdown; | |||
- (void) applicationWillTerminate: (NSNotification*) aNotification; | |||
- (void) viewDidMoveToWindow; | |||
- (BOOL) mouseDownCanMoveWindow; | |||
- (void) filterBeingDeleted: (JuceAU*) au_; | |||
- (void) deleteEditor; | |||
@end | |||
//============================================================================== | |||
class JuceAU : public JuceAUBaseClass, | |||
public AudioProcessorListener, | |||
@@ -187,9 +144,7 @@ public: | |||
~JuceAU() | |||
{ | |||
for (int i = activeUIs.size(); --i >= 0;) | |||
[((JuceUIViewClass*) activeUIs.getUnchecked(i)) filterBeingDeleted: this]; | |||
deleteActiveEditors(); | |||
juceFilter = nullptr; | |||
jassert (activePlugins.contains (this)); | |||
@@ -199,6 +154,8 @@ public: | |||
shutdownJuce_GUI(); | |||
} | |||
void deleteActiveEditors(); | |||
//============================================================================== | |||
ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
@@ -281,7 +238,7 @@ public: | |||
NSString* bundlePath = [NSString stringWithUTF8String: (const char*) bundleFile.getFullPathName().toUTF8()]; | |||
NSBundle* b = [NSBundle bundleWithPath: bundlePath]; | |||
info->mCocoaAUViewClass[0] = (CFStringRef) [NSStringFromClass ([JuceUICreationClass class]) retain]; | |||
info->mCocoaAUViewClass[0] = (CFStringRef) [nsStringLiteral (JUCE_STRINGIFY (JuceUICreationClass)) retain]; | |||
info->mCocoaAUViewBundleLocation = (CFURLRef) [[NSURL fileURLWithPath: [b bundlePath]] retain]; | |||
return noErr; | |||
@@ -974,6 +931,32 @@ public: | |||
// have been transferred to another parent which takes over ownership. | |||
} | |||
static NSView* createViewFor (AudioProcessor* filter, JuceAU* au, AudioProcessorEditor* const editor) | |||
{ | |||
EditorCompHolder* editorCompHolder = new EditorCompHolder (editor); | |||
NSRect r = NSMakeRect (0, 0, editorCompHolder->getWidth(), editorCompHolder->getHeight()); | |||
static JuceUIViewClass cls; | |||
NSView* view = [[cls.createInstance() initWithFrame: r] autorelease]; | |||
JuceUIViewClass::setFilter (view, filter); | |||
JuceUIViewClass::setAU (view, au); | |||
JuceUIViewClass::setEditor (view, editorCompHolder); | |||
[view setHidden: NO]; | |||
[view setPostsFrameChangedNotifications: YES]; | |||
[[NSNotificationCenter defaultCenter] addObserver: view | |||
selector: @selector (applicationWillTerminate:) | |||
name: NSApplicationWillTerminateNotification | |||
object: nil]; | |||
activeUIs.add (view); | |||
editorCompHolder->addToDesktop (0, (void*) view); | |||
editorCompHolder->setVisible (view); | |||
return view; | |||
} | |||
void childBoundsChanged (Component*) | |||
{ | |||
Component* editor = getChildComponent(0); | |||
@@ -996,117 +979,128 @@ public: | |||
} | |||
} | |||
private: | |||
JUCE_DECLARE_NON_COPYABLE (EditorCompHolder); | |||
}; | |||
//============================================================================== | |||
struct JuceUIViewClass : public ObjCClass <NSView> | |||
{ | |||
JuceUIViewClass() : ObjCClass ("JUCEAUView_") | |||
{ | |||
addIvar<AudioProcessor*> ("filter"); | |||
addIvar<JuceAU*> ("au"); | |||
addIvar<EditorCompHolder*> ("editor"); | |||
addMethod (@selector (dealloc), dealloc, "v@:"); | |||
addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); | |||
addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow, "v@:"); | |||
addMethod (@selector (mouseDownCanMoveWindow), mouseDownCanMoveWindow, "c@:"); | |||
registerClass(); | |||
} | |||
//============================================================================== | |||
@implementation JuceUIViewClass | |||
static void deleteEditor (id self) | |||
{ | |||
ScopedPointer<EditorCompHolder> editorComp (getEditor (self)); | |||
- (JuceUIViewClass*) initWithFilter: (AudioProcessor*) filter_ | |||
withAU: (JuceAU*) au_ | |||
withComponent: (AudioProcessorEditor*) editorComp_ | |||
{ | |||
filter = filter_; | |||
au = au_; | |||
editorComp = new EditorCompHolder (editorComp_); | |||
if (editorComp != nullptr) | |||
{ | |||
JuceAU* const au = getAU (self); | |||
[super initWithFrame: NSMakeRect (0, 0, editorComp_->getWidth(), editorComp_->getHeight())]; | |||
[self setHidden: NO]; | |||
[self setPostsFrameChangedNotifications: YES]; | |||
if (editorComp->getChildComponent(0) != nullptr) | |||
{ | |||
if (activePlugins.contains (au)) // plugin may have been deleted before the UI | |||
{ | |||
AudioProcessor* const filter = getIvar<AudioProcessor*> (self, "filter"); | |||
filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0)); | |||
} | |||
} | |||
[[NSNotificationCenter defaultCenter] addObserver: self | |||
selector: @selector (applicationWillTerminate:) | |||
name: NSApplicationWillTerminateNotification | |||
object: nil]; | |||
activeUIs.add (self); | |||
editorComp = nullptr; | |||
setEditor (self, nullptr); | |||
} | |||
} | |||
editorComp->addToDesktop (0, (void*) self); | |||
editorComp->setVisible (true); | |||
static JuceAU* getAU (id self) { return getIvar<JuceAU*> (self, "au"); } | |||
static EditorCompHolder* getEditor (id self) { return getIvar<EditorCompHolder*> (self, "editor"); } | |||
return self; | |||
} | |||
static void setFilter (id self, AudioProcessor* filter) { object_setInstanceVariable (self, "filter", filter); } | |||
static void setAU (id self, JuceAU* au) { object_setInstanceVariable (self, "au", au); } | |||
static void setEditor (id self, EditorCompHolder* e) { object_setInstanceVariable (self, "editor", e); } | |||
- (void) dealloc | |||
{ | |||
if (activeUIs.contains (self)) | |||
[self shutdown]; | |||
private: | |||
static void dealloc (id self, SEL) | |||
{ | |||
if (activeUIs.contains (self)) | |||
shutdown (self); | |||
[super dealloc]; | |||
} | |||
sendSuperclassMessage (self, @selector (dealloc)); | |||
} | |||
- (void) applicationWillTerminate: (NSNotification*) aNotification | |||
{ | |||
(void) aNotification; | |||
[self shutdown]; | |||
} | |||
static void applicationWillTerminate (id self, SEL, NSNotification*) | |||
{ | |||
shutdown (self); | |||
} | |||
- (void) shutdown | |||
{ | |||
// there's some kind of component currently modal, but the host | |||
// is trying to delete our plugin.. | |||
jassert (Component::getCurrentlyModalComponent() == nullptr); | |||
static void shutdown (id self) | |||
{ | |||
// there's some kind of component currently modal, but the host | |||
// is trying to delete our plugin.. | |||
jassert (Component::getCurrentlyModalComponent() == nullptr); | |||
[[NSNotificationCenter defaultCenter] removeObserver: self]; | |||
[self deleteEditor]; | |||
[[NSNotificationCenter defaultCenter] removeObserver: self]; | |||
deleteEditor (self); | |||
jassert (activeUIs.contains (self)); | |||
activeUIs.removeValue (self); | |||
if (activePlugins.size() + activeUIs.size() == 0) | |||
shutdownJuce_GUI(); | |||
} | |||
jassert (activeUIs.contains (self)); | |||
activeUIs.removeValue (self); | |||
if (activePlugins.size() + activeUIs.size() == 0) | |||
shutdownJuce_GUI(); | |||
} | |||
- (void) viewDidMoveToWindow | |||
{ | |||
if ([self window] != nil) | |||
{ | |||
[[self window] setAcceptsMouseMovedEvents: YES]; | |||
static void viewDidMoveToWindow (id self, SEL) | |||
{ | |||
NSWindow* w = [(NSView*) self window]; | |||
if (editorComp != nullptr) | |||
[[self window] makeFirstResponder: (NSView*) editorComp->getWindowHandle()]; | |||
} | |||
} | |||
if (w != nil) | |||
{ | |||
[w setAcceptsMouseMovedEvents: YES]; | |||
- (BOOL) mouseDownCanMoveWindow | |||
{ | |||
return NO; | |||
} | |||
EditorCompHolder* const editorComp = getEditor (self); | |||
if (editorComp != nullptr) | |||
[w makeFirstResponder: (NSView*) editorComp->getWindowHandle()]; | |||
} | |||
} | |||
static BOOL mouseDownCanMoveWindow (id self, SEL) | |||
{ | |||
return NO; | |||
} | |||
}; | |||
private: | |||
JUCE_DECLARE_NON_COPYABLE (EditorCompHolder); | |||
}; | |||
- (void) deleteEditor | |||
void JuceAU::deleteActiveEditors() | |||
{ | |||
if (editorComp != nullptr) | |||
for (int i = activeUIs.size(); --i >= 0;) | |||
{ | |||
if (editorComp->getChildComponent(0) != nullptr) | |||
if (activePlugins.contains ((void*) au)) // plugin may have been deleted before the UI | |||
filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0)); | |||
id ui = (id) activeUIs.getUnchecked(i); | |||
deleteAndZero (editorComp); | |||
if (EditorCompHolder::JuceUIViewClass::getAU (ui) == this) | |||
EditorCompHolder::JuceUIViewClass::deleteEditor (ui); | |||
} | |||
editorComp = nullptr; | |||
} | |||
- (void) filterBeingDeleted: (JuceAU*) au_ | |||
//============================================================================== | |||
@interface JuceUICreationClass : NSObject <AUCocoaUIBase> | |||
{ | |||
if (au_ == au) | |||
[self deleteEditor]; | |||
} | |||
- (unsigned) interfaceVersion; | |||
- (NSString*) description; | |||
- (NSView*) uiViewForAudioUnit: (AudioUnit) inAudioUnit | |||
withSize: (NSSize) inPreferredSize; | |||
@end | |||
//============================================================================== | |||
@implementation JuceUICreationClass | |||
- (JuceUICreationClass*) init | |||
{ | |||
return [super init]; | |||
} | |||
- (void) dealloc | |||
{ | |||
[super dealloc]; | |||
} | |||
- (unsigned) interfaceVersion | |||
{ | |||
return 0; | |||
@@ -1114,7 +1108,7 @@ private: | |||
- (NSString*) description | |||
{ | |||
return [NSString stringWithString: @JucePlugin_Name]; | |||
return [NSString stringWithString: nsStringLiteral (JucePlugin_Name)]; | |||
} | |||
- (NSView*) uiViewForAudioUnit: (AudioUnit) inAudioUnit | |||
@@ -1138,13 +1132,10 @@ private: | |||
return nil; | |||
AudioProcessorEditor* editorComp = filter->createEditorIfNeeded(); | |||
if (editorComp == nullptr) | |||
return nil; | |||
return [[[JuceUIViewClass alloc] initWithFilter: filter | |||
withAU: au | |||
withComponent: editorComp] autorelease]; | |||
return EditorCompHolder::createViewFor (filter, au, editorComp); | |||
} | |||
@end | |||
@@ -97,10 +97,6 @@ | |||
#error "You need to define the JucePlugin_AUCocoaViewClassName value!" | |||
#endif | |||
#if (defined(__APPLE_CPP__) || defined(__APPLE_CC__)) && ! defined (JUCE_ObjCExtraSuffix) | |||
#error "To avoid objective-C name clashes with other plugins, you need to define the JUCE_ObjCExtraSuffix value as a global definition for your project!" | |||
#endif | |||
#if JucePlugin_Build_LV2 && ! defined (JucePlugin_LV2URI) | |||
#error "You need to define the JucePlugin_LV2URI value!" | |||
#endif | |||
@@ -61,4 +61,7 @@ | |||
#else | |||
#include <Cocoa/Cocoa.h> | |||
#endif | |||
#include <objc/runtime.h> | |||
#include <objc/objc.h> | |||
#include <objc/message.h> | |||
#endif |
@@ -152,7 +152,6 @@ namespace juce | |||
//============================================================================== | |||
#if JUCE_MAC || JUCE_IOS | |||
#include "native/juce_osx_ObjCHelpers.h" | |||
#include "native/juce_mac_ObjCSuffix.h" | |||
#endif | |||
#if JUCE_ANDROID | |||
@@ -98,236 +98,213 @@ bool Process::openEmailWithAttachments (const String& targetEmailAddress, | |||
} | |||
//============================================================================== | |||
} // (juce namespace) | |||
using namespace juce; | |||
//============================================================================== | |||
#define JuceURLConnection MakeObjCClassName(JuceURLConnection) | |||
@interface JuceURLConnection : NSObject | |||
{ | |||
@public | |||
NSURLRequest* request; | |||
NSURLConnection* connection; | |||
NSMutableData* data; | |||
Thread* runLoopThread; | |||
bool initialised, hasFailed, hasFinished; | |||
int position; | |||
int64 contentLength; | |||
NSDictionary* headers; | |||
NSLock* dataLock; | |||
} | |||
- (JuceURLConnection*) initWithRequest: (NSURLRequest*) req withCallback: (URL::OpenStreamProgressCallback*) callback withContext: (void*) context; | |||
- (void) dealloc; | |||
- (void) connection: (NSURLConnection*) connection didReceiveResponse: (NSURLResponse*) response; | |||
- (void) connection: (NSURLConnection*) connection didFailWithError: (NSError*) error; | |||
- (void) connection: (NSURLConnection*) connection didReceiveData: (NSData*) data; | |||
- (void) connectionDidFinishLoading: (NSURLConnection*) connection; | |||
- (BOOL) isOpen; | |||
- (int) read: (char*) dest numBytes: (int) num; | |||
- (int) readPosition; | |||
- (void) stop; | |||
- (void) createConnection; | |||
@end | |||
class JuceURLConnectionMessageThread : public Thread | |||
class URLConnectionState : public Thread | |||
{ | |||
public: | |||
JuceURLConnectionMessageThread (JuceURLConnection* owner_) | |||
URLConnectionState (NSObject* owner_, NSURLRequest* req) | |||
: Thread ("http connection"), | |||
owner (owner_) | |||
contentLength (-1), | |||
owner (owner_), | |||
request ([req retain]), | |||
connection (nil), | |||
data ([[NSMutableData data] retain]), | |||
headers (nil), | |||
initialised (false), | |||
hasFailed (false), | |||
hasFinished (false) | |||
{ | |||
} | |||
~JuceURLConnectionMessageThread() | |||
~URLConnectionState() | |||
{ | |||
stopThread (10000); | |||
[connection release]; | |||
[data release]; | |||
[request release]; | |||
[headers release]; | |||
} | |||
void run() | |||
bool start (URL::OpenStreamProgressCallback* callback, void* context) | |||
{ | |||
[owner createConnection]; | |||
startThread(); | |||
while (! threadShouldExit()) | |||
while (isThreadRunning() && ! initialised) | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; | |||
if (callback != nullptr) | |||
callback (context, -1, (int) [[request HTTPBody] length]); | |||
Thread::sleep (1); | |||
} | |||
return connection != nil && ! hasFailed; | |||
} | |||
private: | |||
JuceURLConnection* owner; | |||
}; | |||
void stop() | |||
{ | |||
[connection cancel]; | |||
stopThread (10000); | |||
} | |||
int read (char* dest, int numBytes) | |||
{ | |||
int numDone = 0; | |||
@implementation JuceURLConnection | |||
while (numBytes > 0) | |||
{ | |||
const int available = jmin (numBytes, (int) [data length]); | |||
- (JuceURLConnection*) initWithRequest: (NSURLRequest*) req | |||
withCallback: (URL::OpenStreamProgressCallback*) callback | |||
withContext: (void*) context; | |||
{ | |||
[super init]; | |||
request = req; | |||
[request retain]; | |||
data = [[NSMutableData data] retain]; | |||
dataLock = [[NSLock alloc] init]; | |||
connection = nil; | |||
initialised = false; | |||
hasFailed = false; | |||
hasFinished = false; | |||
contentLength = -1; | |||
headers = nil; | |||
runLoopThread = new JuceURLConnectionMessageThread (self); | |||
runLoopThread->startThread(); | |||
while (runLoopThread->isThreadRunning() && ! initialised) | |||
{ | |||
if (callback != nullptr) | |||
callback (context, -1, (int) [[request HTTPBody] length]); | |||
if (available > 0) | |||
{ | |||
const ScopedLock sl (dataLock); | |||
[data getBytes: dest length: available]; | |||
[data replaceBytesInRange: NSMakeRange (0, available) withBytes: nil length: 0]; | |||
numDone += available; | |||
numBytes -= available; | |||
dest += available; | |||
} | |||
else | |||
{ | |||
if (hasFailed || hasFinished) | |||
break; | |||
Thread::sleep (1); | |||
} | |||
} | |||
Thread::sleep (1); | |||
return numDone; | |||
} | |||
return self; | |||
} | |||
void didReceiveResponse (NSURLResponse* response) | |||
{ | |||
{ | |||
const ScopedLock sl (dataLock); | |||
[data setLength: 0]; | |||
} | |||
- (void) dealloc | |||
{ | |||
[self stop]; | |||
deleteAndZero (runLoopThread); | |||
[connection release]; | |||
[data release]; | |||
[dataLock release]; | |||
[request release]; | |||
[headers release]; | |||
[super dealloc]; | |||
} | |||
initialised = true; | |||
contentLength = [response expectedContentLength]; | |||
- (void) createConnection | |||
{ | |||
NSUInteger oldRetainCount = [self retainCount]; | |||
connection = [[NSURLConnection alloc] initWithRequest: request | |||
delegate: self]; | |||
[headers release]; | |||
headers = nil; | |||
if (oldRetainCount == [self retainCount]) | |||
[self retain]; // newer SDK should already retain this, but there were problems in older versions.. | |||
if ([response isKindOfClass: [NSHTTPURLResponse class]]) | |||
headers = [[((NSHTTPURLResponse*) response) allHeaderFields] retain]; | |||
} | |||
if (connection == nil) | |||
runLoopThread->signalThreadShouldExit(); | |||
} | |||
void didFailWithError (NSError* error) | |||
{ | |||
DBG (nsStringToJuce ([error description])); (void) error; | |||
hasFailed = true; | |||
initialised = true; | |||
signalThreadShouldExit(); | |||
} | |||
- (void) connection: (NSURLConnection*) conn didReceiveResponse: (NSURLResponse*) response | |||
{ | |||
(void) conn; | |||
[dataLock lock]; | |||
[data setLength: 0]; | |||
[dataLock unlock]; | |||
initialised = true; | |||
contentLength = [response expectedContentLength]; | |||
[headers release]; | |||
headers = nil; | |||
if ([response isKindOfClass: [NSHTTPURLResponse class]]) | |||
headers = [[((NSHTTPURLResponse*) response) allHeaderFields] retain]; | |||
} | |||
void didReceiveData (NSData* newData) | |||
{ | |||
const ScopedLock sl (dataLock); | |||
[data appendData: newData]; | |||
initialised = true; | |||
} | |||
- (void) connection: (NSURLConnection*) conn didFailWithError: (NSError*) error | |||
{ | |||
(void) conn; | |||
DBG (nsStringToJuce ([error description])); | |||
hasFailed = true; | |||
initialised = true; | |||
void finishedLoading() | |||
{ | |||
hasFinished = true; | |||
initialised = true; | |||
signalThreadShouldExit(); | |||
} | |||
if (runLoopThread != nullptr) | |||
runLoopThread->signalThreadShouldExit(); | |||
} | |||
void run() | |||
{ | |||
NSUInteger oldRetainCount = [owner retainCount]; | |||
connection = [[NSURLConnection alloc] initWithRequest: request | |||
delegate: owner]; | |||
- (void) connection: (NSURLConnection*) conn didReceiveData: (NSData*) newData | |||
{ | |||
(void) conn; | |||
[dataLock lock]; | |||
[data appendData: newData]; | |||
[dataLock unlock]; | |||
initialised = true; | |||
} | |||
if (oldRetainCount == [owner retainCount]) | |||
[owner retain]; // newer SDK should already retain this, but there were problems in older versions.. | |||
- (void) connectionDidFinishLoading: (NSURLConnection*) conn | |||
{ | |||
(void) conn; | |||
hasFinished = true; | |||
initialised = true; | |||
if (connection != nil) | |||
{ | |||
while (! threadShouldExit()) | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; | |||
} | |||
} | |||
} | |||
if (runLoopThread != nullptr) | |||
runLoopThread->signalThreadShouldExit(); | |||
} | |||
int64 contentLength; | |||
CriticalSection dataLock; | |||
NSObject* owner; | |||
NSURLRequest* request; | |||
NSURLConnection* connection; | |||
NSMutableData* data; | |||
NSDictionary* headers; | |||
bool initialised, hasFailed, hasFinished; | |||
- (BOOL) isOpen | |||
{ | |||
return connection != nil && ! hasFailed; | |||
} | |||
private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState); | |||
}; | |||
- (int) readPosition | |||
//============================================================================== | |||
struct URLConnectionDelegateClass : public ObjCClass<NSObject> | |||
{ | |||
return position; | |||
} | |||
URLConnectionDelegateClass() : ObjCClass ("JUCEAppDelegate_") | |||
{ | |||
addIvar <URLConnectionState*> ("state"); | |||
- (int) read: (char*) dest numBytes: (int) numNeeded | |||
{ | |||
int numDone = 0; | |||
addMethod (@selector (dealloc), dealloc, "v@:"); | |||
addMethod (@selector (connection:didReceiveResponse:), didReceiveResponse, "v@:@@"); | |||
addMethod (@selector (connection:didFailWithError:), didFailWithError, "v@:@@"); | |||
addMethod (@selector (connection:didReceiveData:), didReceiveData, "v@:@@"); | |||
addMethod (@selector (connectionDidFinishLoading:), connectionDidFinishLoading, "v@:@"); | |||
while (numNeeded > 0) | |||
{ | |||
int available = jmin (numNeeded, (int) [data length]); | |||
registerClass(); | |||
} | |||
if (available > 0) | |||
{ | |||
[dataLock lock]; | |||
[data getBytes: dest length: available]; | |||
[data replaceBytesInRange: NSMakeRange (0, available) withBytes: nil length: 0]; | |||
[dataLock unlock]; | |||
numDone += available; | |||
numNeeded -= available; | |||
dest += available; | |||
} | |||
else | |||
{ | |||
if (hasFailed || hasFinished) | |||
break; | |||
static void setState (id self, URLConnectionState* state) | |||
{ | |||
object_setInstanceVariable (self, "state", state); | |||
} | |||
Thread::sleep (1); | |||
} | |||
static URLConnectionState* getState (id self) | |||
{ | |||
return getIvar<URLConnectionState*> (self, "state"); | |||
} | |||
position += numDone; | |||
return numDone; | |||
} | |||
private: | |||
static void dealloc (id self, SEL sel) | |||
{ | |||
getState (self)->stop(); | |||
delete getState (self); | |||
sendSuperclassMessage (self, @selector (dealloc)); | |||
} | |||
- (void) stop | |||
{ | |||
[connection cancel]; | |||
static void didReceiveResponse (id self, SEL, NSURLConnection*, NSURLResponse* response) | |||
{ | |||
getState (self)->didReceiveResponse (response); | |||
} | |||
if (runLoopThread != nullptr) | |||
runLoopThread->stopThread (10000); | |||
} | |||
static void didFailWithError (id self, SEL, NSURLConnection*, NSError* error) | |||
{ | |||
getState (self)->didFailWithError (error); | |||
} | |||
@end | |||
static void didReceiveData (id self, SEL, NSURLConnection*, NSData* newData) | |||
{ | |||
getState (self)->didReceiveData (newData); | |||
} | |||
namespace juce | |||
{ | |||
static void connectionDidFinishLoading (id self, SEL, NSURLConnection*) | |||
{ | |||
getState (self)->finishedLoading(); | |||
} | |||
}; | |||
//============================================================================== | |||
class WebInputStream : public InputStream | |||
{ | |||
public: | |||
//============================================================================== | |||
WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | |||
URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||
const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | |||
@@ -338,14 +315,19 @@ public: | |||
JUCE_AUTORELEASEPOOL | |||
connection = createConnection (progressCallback, progressCallbackContext); | |||
if (responseHeaders != nullptr && connection != nil && connection->headers != nil) | |||
if (responseHeaders != nullptr && connection != nil) | |||
{ | |||
NSEnumerator* enumerator = [connection->headers keyEnumerator]; | |||
NSString* key; | |||
URLConnectionState* const state = URLConnectionDelegateClass::getState (connection); | |||
if (state->headers != nil) | |||
{ | |||
NSEnumerator* enumerator = [state->headers keyEnumerator]; | |||
NSString* key; | |||
while ((key = [enumerator nextObject]) != nil) | |||
responseHeaders->set (nsStringToJuce (key), | |||
nsStringToJuce ((NSString*) [connection->headers objectForKey: key])); | |||
while ((key = [enumerator nextObject]) != nil) | |||
responseHeaders->set (nsStringToJuce (key), | |||
nsStringToJuce ((NSString*) [state->headers objectForKey: key])); | |||
} | |||
} | |||
} | |||
@@ -356,7 +338,7 @@ public: | |||
//============================================================================== | |||
bool isError() const { return connection == nil; } | |||
int64 getTotalLength() { return connection == nil ? -1 : connection->contentLength; } | |||
int64 getTotalLength() { return connection == nil ? -1 : URLConnectionDelegateClass::getState (connection)->contentLength; } | |||
bool isExhausted() { return finished; } | |||
int64 getPosition() { return position; } | |||
@@ -371,7 +353,10 @@ public: | |||
else | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
const int bytesRead = [connection read: static_cast <char*> (buffer) numBytes: bytesToRead]; | |||
URLConnectionState* const state = URLConnectionDelegateClass::getState (connection); | |||
const int bytesRead = state->read (static_cast <char*> (buffer), bytesToRead); | |||
position += bytesRead; | |||
if (bytesRead == 0) | |||
@@ -402,7 +387,7 @@ public: | |||
//============================================================================== | |||
private: | |||
JuceURLConnection* connection; | |||
NSObject* connection; | |||
String address, headers; | |||
MemoryBlock postData; | |||
int64 position; | |||
@@ -412,13 +397,16 @@ private: | |||
void close() | |||
{ | |||
[connection stop]; | |||
[connection release]; | |||
connection = nil; | |||
if (connection != nil) | |||
{ | |||
URLConnectionDelegateClass::getState (connection)->stop(); | |||
[connection release]; | |||
connection = nil; | |||
} | |||
} | |||
JuceURLConnection* createConnection (URL::OpenStreamProgressCallback* progressCallback, | |||
void* progressCallbackContext) | |||
NSObject* createConnection (URL::OpenStreamProgressCallback* progressCallback, | |||
void* progressCallbackContext) | |||
{ | |||
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (address)] | |||
cachePolicy: NSURLRequestReloadIgnoringLocalCacheData | |||
@@ -447,11 +435,13 @@ private: | |||
[req setHTTPBody: [NSData dataWithBytes: postData.getData() | |||
length: postData.getSize()]]; | |||
JuceURLConnection* const s = [[JuceURLConnection alloc] initWithRequest: req | |||
withCallback: progressCallback | |||
withContext: progressCallbackContext]; | |||
static URLConnectionDelegateClass cls; | |||
NSObject* const s = [cls.createInstance() init]; | |||
URLConnectionState* state = new URLConnectionState (s, req); | |||
URLConnectionDelegateClass::setState (s, state); | |||
if ([s isOpen]) | |||
if (state->start (progressCallback, progressCallbackContext)) | |||
return s; | |||
[s release]; | |||
@@ -1,52 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||
Copyright 2004-11 by Raw Material Software Ltd. | |||
------------------------------------------------------------------------------ | |||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||
Public License (Version 2), as published by the Free Software Foundation. | |||
A copy of the license is included in the JUCE distribution, or can be found | |||
online at www.gnu.org/licenses. | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.rawmaterialsoftware.com/juce for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef __JUCE_MAC_OBJCSUFFIX_JUCEHEADER__ | |||
#define __JUCE_MAC_OBJCSUFFIX_JUCEHEADER__ | |||
/** This suffix is used for naming all Obj-C classes that are used inside juce. | |||
Because of the flat naming structure used by Obj-C, you can get horrible situations where | |||
two DLLs are loaded into a host, each of which uses classes with the same names, and these get | |||
cross-linked so that when you make a call to a class that you thought was private, it ends up | |||
actually calling into a similarly named class in the other module's address space. | |||
By changing this macro to a unique value, you ensure that all the obj-C classes in your app | |||
have unique names, and should avoid this problem. | |||
If you're using the amalgamated version, you can just set this macro to something unique before | |||
you include juce_amalgamated.cpp. | |||
*/ | |||
#ifndef JUCE_ObjCExtraSuffix | |||
#define JUCE_ObjCExtraSuffix 3 | |||
#endif | |||
#ifndef DOXYGEN | |||
#define appendMacro1(a, b, c, d, e) a ## _ ## b ## _ ## c ## _ ## d ## _ ## e | |||
#define appendMacro2(a, b, c, d, e) appendMacro1(a, b, c, d, e) | |||
#define MakeObjCClassName(rootName) appendMacro2 (rootName, JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER, JUCE_ObjCExtraSuffix) | |||
#endif | |||
#endif // __JUCE_MAC_OBJCSUFFIX_JUCEHEADER__ |
@@ -55,18 +55,27 @@ namespace | |||
} | |||
//============================================================================== | |||
class ObjCClassBuilder | |||
template <typename SuperclassType> | |||
struct ObjCClass | |||
{ | |||
public: | |||
ObjCClassBuilder (Class superClass, const String& name) | |||
: cls (objc_allocateClassPair (superClass, name.toUTF8(), 0)) | |||
ObjCClass (const char* nameRoot) | |||
: cls (objc_allocateClassPair ([SuperclassType class], getRandomisedName (nameRoot).toUTF8(), 0)) | |||
{ | |||
} | |||
Class getClass() | |||
~ObjCClass() | |||
{ | |||
objc_disposeClassPair (cls); | |||
} | |||
void registerClass() | |||
{ | |||
objc_registerClassPair (cls); | |||
return cls; | |||
} | |||
SuperclassType* createInstance() const | |||
{ | |||
return class_createInstance (cls, 0); | |||
} | |||
template <typename Type> | |||
@@ -83,21 +92,54 @@ public: | |||
jassert (b); (void) b; | |||
} | |||
template <typename FunctionType> | |||
void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2) | |||
{ | |||
addMethod (selector, callbackFn, (String (sig1) + sig2).toUTF8()); | |||
} | |||
template <typename FunctionType> | |||
void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2, const char* sig3) | |||
{ | |||
addMethod (selector, callbackFn, (String (sig1) + sig2 + sig3).toUTF8()); | |||
} | |||
template <typename FunctionType> | |||
void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2, const char* sig3, const char* sig4) | |||
{ | |||
addMethod (selector, callbackFn, (String (sig1) + sig2 + sig3 + sig4).toUTF8()); | |||
} | |||
void addProtocol (Protocol* protocol) | |||
{ | |||
BOOL b = class_addProtocol (cls, protocol); | |||
jassert (b); (void) b; | |||
} | |||
Class cls; | |||
static id sendSuperclassMessage (id self, SEL selector) | |||
{ | |||
objc_super s = { self, [NSObject class] }; | |||
objc_super s = { self, [SuperclassType class] }; | |||
return objc_msgSendSuper (&s, selector); | |||
} | |||
static String getRandomisedName (const char* root) | |||
template <typename Type> | |||
static Type getIvar (id self, const char* name) | |||
{ | |||
return root + String::toHexString (Random::getSystemRandom().nextInt64()); | |||
Type v = Type(); | |||
object_getInstanceVariable (self, name, (void**) &v); | |||
return v; | |||
} | |||
private: | |||
Class cls; | |||
static String getRandomisedName (const char* root) | |||
{ | |||
return root + String::toHexString (Random::getSystemRandom().nextInt64()); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE (ObjCClassBuilder); | |||
JUCE_DECLARE_NON_COPYABLE (ObjCClass); | |||
}; | |||
#endif // __JUCE_OSX_OBJCHELPERS_JUCEHEADER__ |
@@ -81,7 +81,6 @@ namespace juce | |||
//============================================================================== | |||
#if JUCE_MAC | |||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||
#include "../juce_core/native/juce_mac_ObjCSuffix.h" | |||
#include "native/juce_osx_MessageQueue.h" | |||
#include "native/juce_mac_MessageManager.mm" | |||
@@ -30,33 +30,24 @@ typedef bool (*CheckEventBlockedByModalComps) (NSEvent*); | |||
CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; | |||
//============================================================================== | |||
struct AppDelegateClass | |||
struct AppDelegateClass : public ObjCClass <NSObject> | |||
{ | |||
static Class createClass() | |||
AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") | |||
{ | |||
ObjCClassBuilder c ([NSObject class], ObjCClassBuilder::getRandomisedName ("JUCEAppDelegate_")); | |||
c.addMethod (@selector (init), init, "@@:"); | |||
c.addMethod (@selector (dealloc), dealloc, "v@:"); | |||
c.addMethod (@selector (unregisterObservers), unregisterObservers, "v@:"); | |||
c.addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); | |||
c.addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); | |||
c.addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); | |||
c.addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); | |||
c.addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); | |||
c.addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); | |||
c.addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); | |||
c.addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); | |||
c.addMethod (@selector (dummyMethod), dummyMethod, "v@:"); | |||
return c.getClass(); | |||
} | |||
static NSObject* createInstance() | |||
{ | |||
static Class c = createClass(); | |||
jassert (c != nullptr); | |||
return class_createInstance (c, 0); | |||
addMethod (@selector (init), init, "@@:"); | |||
addMethod (@selector (dealloc), dealloc, "v@:"); | |||
addMethod (@selector (unregisterObservers), unregisterObservers, "v@:"); | |||
addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); | |||
addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); | |||
addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); | |||
addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); | |||
addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); | |||
addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); | |||
addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); | |||
addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); | |||
addMethod (@selector (dummyMethod), dummyMethod, "v@:"); | |||
registerClass(); | |||
} | |||
static NSString* getBroacastEventName() | |||
@@ -64,10 +55,10 @@ struct AppDelegateClass | |||
return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); | |||
} | |||
//============================================================================== | |||
private: | |||
static id init (id self, SEL) | |||
{ | |||
self = ObjCClassBuilder::sendSuperclassMessage (self, @selector (init)); | |||
self = sendSuperclassMessage (self, @selector (init)); | |||
if (JUCEApplicationBase::isStandaloneApp()) | |||
{ | |||
@@ -97,7 +88,7 @@ struct AppDelegateClass | |||
static void dealloc (id self, SEL) | |||
{ | |||
ObjCClassBuilder::sendSuperclassMessage (self, @selector (dealloc)); | |||
sendSuperclassMessage (self, @selector (dealloc)); | |||
} | |||
static void unregisterObservers (id self, SEL) | |||
@@ -265,8 +256,10 @@ struct AppDelegateHolder | |||
{ | |||
public: | |||
AppDelegateHolder() | |||
: delegate ([AppDelegateClass::createInstance() init]) | |||
{} | |||
{ | |||
static AppDelegateClass cls; | |||
delegate = [cls.createInstance() init]; | |||
} | |||
~AppDelegateHolder() | |||
{ | |||
@@ -111,7 +111,6 @@ namespace juce | |||
//============================================================================== | |||
#if JUCE_MAC || JUCE_IOS | |||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||
#include "../juce_core/native/juce_mac_ObjCSuffix.h" | |||
#include "native/juce_mac_CoreGraphicsHelpers.h" | |||
#include "native/juce_mac_Fonts.mm" | |||
#include "native/juce_mac_CoreGraphicsContext.mm" | |||
@@ -256,7 +256,6 @@ namespace juce | |||
#if JUCE_MAC || JUCE_IOS | |||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||
#include "../juce_core/native/juce_mac_ObjCSuffix.h" | |||
#include "../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" | |||
#include "../juce_graphics/native/juce_mac_CoreGraphicsContext.h" | |||
@@ -25,81 +25,69 @@ | |||
#if JUCE_MAC | |||
} // (juce namespace) | |||
using namespace juce; | |||
#define JuceFileChooserDelegate MakeObjCClassName(JuceFileChooserDelegate) | |||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 | |||
@interface JuceFileChooserDelegate : NSObject <NSOpenSavePanelDelegate> | |||
#else | |||
@interface JuceFileChooserDelegate : NSObject | |||
#endif | |||
struct FileChooserDelegateClass : public ObjCClass <NSObject> | |||
{ | |||
StringArray* filters; | |||
} | |||
FileChooserDelegateClass() : ObjCClass ("JUCEFileChooser_") | |||
{ | |||
addIvar<StringArray*> ("filters"); | |||
- (JuceFileChooserDelegate*) initWithFilters: (StringArray*) filters_; | |||
- (void) dealloc; | |||
- (BOOL) panel: (id) sender shouldShowFilename: (NSString*) filename; | |||
addMethod (@selector (initWithFilters:), initWithFilters, "@@:^v"); | |||
addMethod (@selector (dealloc), dealloc, "v@:"); | |||
addMethod (@selector (panel:shouldShowFilename:), shouldShowFilename, "c@:@@"); | |||
@end | |||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 | |||
addProtocol (@protocol (NSOpenSavePanelDelegate)); | |||
#endif | |||
@implementation JuceFileChooserDelegate | |||
- (JuceFileChooserDelegate*) initWithFilters: (StringArray*) filters_ | |||
{ | |||
[super init]; | |||
filters = filters_; | |||
return self; | |||
} | |||
registerClass(); | |||
} | |||
- (void) dealloc | |||
{ | |||
delete filters; | |||
[super dealloc]; | |||
} | |||
private: | |||
static id initWithFilters (id self, SEL, StringArray* filters) | |||
{ | |||
self = sendSuperclassMessage (self, @selector (init)); | |||
object_setInstanceVariable (self, "filters", filters); | |||
return self; | |||
} | |||
- (BOOL) panel: (id) sender shouldShowFilename: (NSString*) filename | |||
{ | |||
(void) sender; | |||
const File f (nsStringToJuce (filename)); | |||
static void dealloc (id self, SEL) | |||
{ | |||
delete getIvar<StringArray*> (self, "filters"); | |||
sendSuperclassMessage (self, @selector (dealloc)); | |||
} | |||
for (int i = filters->size(); --i >= 0;) | |||
if (f.getFileName().matchesWildcard ((*filters)[i], true)) | |||
return true; | |||
static BOOL shouldShowFilename (id self, SEL, id /*sender*/, NSString* filename) | |||
{ | |||
StringArray* const filters = getIvar<StringArray*> (self, "filters"); | |||
#if (! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 | |||
NSError* error; | |||
NSString* name = [[NSWorkspace sharedWorkspace] typeOfFile: filename error: &error]; | |||
const File f (nsStringToJuce (filename)); | |||
if ([name isEqualToString: nsStringLiteral ("com.apple.alias-file")]) | |||
{ | |||
FSRef ref; | |||
FSPathMakeRef ((const UInt8*) [filename fileSystemRepresentation], &ref, nullptr); | |||
for (int i = filters->size(); --i >= 0;) | |||
if (f.getFileName().matchesWildcard ((*filters)[i], true)) | |||
return true; | |||
Boolean targetIsFolder = false, wasAliased = false; | |||
FSResolveAliasFileWithMountFlags (&ref, true, &targetIsFolder, &wasAliased, 0); | |||
#if (! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 | |||
NSError* error; | |||
NSString* name = [[NSWorkspace sharedWorkspace] typeOfFile: filename error: &error]; | |||
return wasAliased && targetIsFolder; | |||
} | |||
#endif | |||
if ([name isEqualToString: nsStringLiteral ("com.apple.alias-file")]) | |||
{ | |||
FSRef ref; | |||
FSPathMakeRef ((const UInt8*) [filename fileSystemRepresentation], &ref, nullptr); | |||
return f.isDirectory() | |||
&& ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: filename]; | |||
} | |||
@end | |||
Boolean targetIsFolder = false, wasAliased = false; | |||
FSResolveAliasFileWithMountFlags (&ref, true, &targetIsFolder, &wasAliased, 0); | |||
return wasAliased && targetIsFolder; | |||
} | |||
#endif | |||
namespace juce | |||
{ | |||
return f.isDirectory() | |||
&& ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: filename]; | |||
} | |||
}; | |||
//============================================================================== | |||
bool FileChooser::isPlatformDialogAvailable() | |||
{ | |||
return true; | |||
} | |||
class TemporaryMainMenuWithStandardCommands | |||
{ | |||
public: | |||
@@ -127,7 +115,7 @@ public: | |||
[item release]; | |||
item = [[NSApp mainMenu] addItemWithTitle: NSLocalizedString (nsStringLiteral ("Edit"), nil) | |||
action: nil keyEquivalent: nsEmptyString()]; | |||
action: nil keyEquivalent: nsEmptyString()]; | |||
[[NSApp mainMenu] setSubmenu: menu forItem: item]; | |||
[menu release]; | |||
} | |||
@@ -141,6 +129,7 @@ private: | |||
MenuBarModel* oldMenu; | |||
}; | |||
//============================================================================== | |||
void FileChooser::showPlatformDialog (Array<File>& results, | |||
const String& title, | |||
const File& currentFileOrDirectory, | |||
@@ -161,8 +150,15 @@ void FileChooser::showPlatformDialog (Array<File>& results, | |||
filters->trim(); | |||
filters->removeEmptyStrings(); | |||
JuceFileChooserDelegate* delegate = [[JuceFileChooserDelegate alloc] initWithFilters: filters]; | |||
[delegate autorelease]; | |||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 | |||
typedef NSObject<NSOpenSavePanelDelegate> DelegateType; | |||
#else | |||
typedef NSObject DelegateType; | |||
#endif | |||
static FileChooserDelegateClass cls; | |||
DelegateType* delegate = [[cls.createInstance() performSelector: @selector (initWithFilters:) | |||
withObject: (id) filters] autorelease]; | |||
NSSavePanel* panel = isSaveDialogue ? [NSSavePanel savePanel] | |||
: [NSOpenPanel openPanel]; | |||
@@ -222,6 +218,11 @@ void FileChooser::showPlatformDialog (Array<File>& results, | |||
[panel setDelegate: nil]; | |||
} | |||
bool FileChooser::isPlatformDialogAvailable() | |||
{ | |||
return true; | |||
} | |||
#else | |||
//============================================================================== | |||
@@ -23,43 +23,17 @@ | |||
============================================================================== | |||
*/ | |||
class JuceMainMenuHandler; | |||
} // (juce namespace) | |||
using namespace juce; | |||
#define JuceMenuCallback MakeObjCClassName(JuceMenuCallback) | |||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 | |||
@interface JuceMenuCallback : NSObject <NSMenuDelegate> | |||
#else | |||
@interface JuceMenuCallback : NSObject | |||
#endif | |||
{ | |||
JuceMainMenuHandler* owner; | |||
} | |||
- (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_; | |||
- (void) dealloc; | |||
- (void) menuItemInvoked: (id) menu; | |||
- (void) menuNeedsUpdate: (NSMenu*) menu; | |||
@end | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
class JuceMainMenuHandler : private MenuBarModel::Listener, | |||
private DeletedAtShutdown | |||
{ | |||
public: | |||
//============================================================================== | |||
JuceMainMenuHandler() | |||
: currentModel (nullptr), | |||
lastUpdateTime (0) | |||
{ | |||
callback = [[JuceMenuCallback alloc] initWithOwner: this]; | |||
static JuceMenuCallbackClass cls; | |||
callback = [cls.createInstance() performSelector: @selector (initWithOwner:) | |||
withObject: (id) this]; | |||
} | |||
~JuceMainMenuHandler() | |||
@@ -234,17 +208,38 @@ public: | |||
} | |||
else if (iter.subMenu != nullptr) | |||
{ | |||
NSMenuItem* item = [menuToAddTo addItemWithTitle: text | |||
action: nil | |||
keyEquivalent: nsEmptyString()]; | |||
if (iter.itemName.containsIgnoreCase ("recent")) | |||
{ | |||
NSMenuItem* item = [menuToAddTo addItemWithTitle: NSLocalizedString (@"Open Recent", nil) | |||
action: nil | |||
keyEquivalent: @""]; | |||
[item setTag: iter.itemId]; | |||
[item setEnabled: iter.isEnabled]; | |||
NSMenu* openRecentMenu = [[[NSMenu alloc] initWithTitle: @"Open Recent"] autorelease]; | |||
[openRecentMenu performSelector: @selector(_setMenuName:) | |||
withObject: @"NSRecentDocumentsMenu"]; | |||
[menuToAddTo setSubmenu: openRecentMenu forItem: item]; | |||
item = [openRecentMenu addItemWithTitle: NSLocalizedString(@"Clear Menu", nil) | |||
action: @selector(clearRecentDocuments:) | |||
keyEquivalent: @""]; | |||
[openRecentMenu update]; | |||
} | |||
else | |||
{ | |||
NSMenuItem* item = [menuToAddTo addItemWithTitle: text | |||
action: nil | |||
keyEquivalent: nsEmptyString()]; | |||
NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex); | |||
[sub setDelegate: nil]; | |||
[menuToAddTo setSubmenu: sub forItem: item]; | |||
[sub release]; | |||
[item setTag: iter.itemId]; | |||
[item setEnabled: iter.isEnabled]; | |||
NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex); | |||
[sub setDelegate: nil]; | |||
[menuToAddTo setSubmenu: sub forItem: item]; | |||
[sub release]; | |||
} | |||
} | |||
else | |||
{ | |||
@@ -289,7 +284,7 @@ public: | |||
MenuBarModel* currentModel; | |||
uint32 lastUpdateTime; | |||
JuceMenuCallback* callback; | |||
NSObject* callback; | |||
private: | |||
//============================================================================== | |||
@@ -301,7 +296,7 @@ private: | |||
NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)]; | |||
[m setAutoenablesItems: false]; | |||
[m setDelegate: callback]; | |||
[m setDelegate: (id<NSMenuDelegate>) callback]; | |||
for (PopupMenu::MenuItemIterator iter (menu); iter.next();) | |||
addMenuItem (iter, m, topLevelMenuId, topLevelIndex); | |||
@@ -385,8 +380,8 @@ private: | |||
void messageCallback() | |||
{ | |||
if (JuceMainMenuHandler::instance != nullptr) | |||
JuceMainMenuHandler::instance->menuBarItemsChanged (nullptr); | |||
if (instance != nullptr) | |||
instance->menuBarItemsChanged (nullptr); | |||
} | |||
private: | |||
@@ -402,8 +397,8 @@ private: | |||
void messageCallback() | |||
{ | |||
if (JuceMainMenuHandler::instance != nullptr) | |||
JuceMainMenuHandler::instance->invokeDirectly (commandId, topLevelIndex); | |||
if (instance != nullptr) | |||
instance->invokeDirectly (commandId, topLevelIndex); | |||
} | |||
private: | |||
@@ -411,74 +406,82 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE (AsyncCommandInvoker); | |||
}; | |||
}; | |||
JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr; | |||
} // (juce namespace) | |||
//============================================================================== | |||
struct JuceMenuCallbackClass : public ObjCClass <NSObject> | |||
{ | |||
JuceMenuCallbackClass() | |||
: ObjCClass ("JUCEMainMenu_") | |||
{ | |||
addIvar<JuceMainMenuHandler*> ("owner"); | |||
//============================================================================== | |||
@implementation JuceMenuCallback | |||
addMethod (@selector (initWithOwner:), initWithOwner, "@@:^v"); | |||
addMethod (@selector (menuItemInvoked:), menuItemInvoked, "v@:@"); | |||
addMethod (@selector (menuNeedsUpdate:), menuNeedsUpdate, "v@:@"); | |||
- (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_ | |||
{ | |||
[super init]; | |||
owner = owner_; | |||
return self; | |||
} | |||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 | |||
addProtocol (@protocol (NSMenuDelegate)); | |||
#endif | |||
- (void) dealloc | |||
{ | |||
[super dealloc]; | |||
} | |||
registerClass(); | |||
} | |||
- (void) menuItemInvoked: (id) menu | |||
{ | |||
NSMenuItem* item = (NSMenuItem*) menu; | |||
private: | |||
static id initWithOwner (id self, SEL, JuceMainMenuHandler* owner) | |||
{ | |||
self = sendSuperclassMessage (self, @selector (init)); | |||
object_setInstanceVariable (self, "owner", owner); | |||
return self; | |||
} | |||
if ([[item representedObject] isKindOfClass: [NSArray class]]) | |||
{ | |||
// If the menu is being triggered by a keypress, the OS will have picked it up before we had a chance to offer it to | |||
// our own components, which may have wanted to intercept it. So, rather than dispatching directly, we'll feed it back | |||
// into the focused component and let it trigger the menu item indirectly. | |||
NSEvent* e = [NSApp currentEvent]; | |||
if ([e type] == NSKeyDown || [e type] == NSKeyUp) | |||
static void menuItemInvoked (id self, SEL, id menu) | |||
{ | |||
if (juce::Component::getCurrentlyFocusedComponent() != nullptr) | |||
{ | |||
juce::NSViewComponentPeer* peer = dynamic_cast <juce::NSViewComponentPeer*> (juce::Component::getCurrentlyFocusedComponent()->getPeer()); | |||
JuceMainMenuHandler* const owner = getIvar<JuceMainMenuHandler*> (self, "owner"); | |||
if (peer != nullptr) | |||
NSMenuItem* item = (NSMenuItem*) menu; | |||
if ([[item representedObject] isKindOfClass: [NSArray class]]) | |||
{ | |||
// If the menu is being triggered by a keypress, the OS will have picked it up before we had a chance to offer it to | |||
// our own components, which may have wanted to intercept it. So, rather than dispatching directly, we'll feed it back | |||
// into the focused component and let it trigger the menu item indirectly. | |||
NSEvent* e = [NSApp currentEvent]; | |||
if ([e type] == NSKeyDown || [e type] == NSKeyUp) | |||
{ | |||
if ([e type] == NSKeyDown) | |||
peer->redirectKeyDown (e); | |||
else | |||
peer->redirectKeyUp (e); | |||
if (juce::Component::getCurrentlyFocusedComponent() != nullptr) | |||
{ | |||
juce::NSViewComponentPeer* peer = dynamic_cast <juce::NSViewComponentPeer*> (juce::Component::getCurrentlyFocusedComponent()->getPeer()); | |||
return; | |||
} | |||
} | |||
} | |||
if (peer != nullptr) | |||
{ | |||
if ([e type] == NSKeyDown) | |||
peer->redirectKeyDown (e); | |||
else | |||
peer->redirectKeyUp (e); | |||
NSArray* info = (NSArray*) [item representedObject]; | |||
return; | |||
} | |||
} | |||
} | |||
owner->invoke ((int) [item tag], | |||
(ApplicationCommandManager*) (pointer_sized_int) | |||
[((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue], | |||
(int) [((NSNumber*) [info objectAtIndex: 1]) intValue]); | |||
} | |||
} | |||
NSArray* info = (NSArray*) [item representedObject]; | |||
- (void) menuNeedsUpdate: (NSMenu*) menu; | |||
{ | |||
if (JuceMainMenuHandler::instance != nullptr) | |||
JuceMainMenuHandler::instance->updateMenus (menu); | |||
} | |||
owner->invoke ((int) [item tag], | |||
(ApplicationCommandManager*) (pointer_sized_int) | |||
[((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue], | |||
(int) [((NSNumber*) [info objectAtIndex: 1]) intValue]); | |||
} | |||
} | |||
@end | |||
static void menuNeedsUpdate (id self, SEL, NSMenu* menu) | |||
{ | |||
if (instance != nullptr) | |||
instance->updateMenus (menu); | |||
} | |||
}; | |||
}; | |||
namespace juce | |||
{ | |||
JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr; | |||
//============================================================================== | |||
namespace MainMenuHelpers | |||
@@ -106,7 +106,6 @@ namespace juce | |||
//============================================================================== | |||
#if JUCE_MAC || JUCE_IOS | |||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||
#include "../juce_core/native/juce_mac_ObjCSuffix.h" | |||
#include "../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" | |||
#if JUCE_MAC | |||
@@ -23,66 +23,57 @@ | |||
============================================================================== | |||
*/ | |||
} // (juce namespace) | |||
class WebBrowserComponentInternal; | |||
#if JUCE_MAC | |||
#define DownloadClickDetector MakeObjCClassName(DownloadClickDetector) | |||
@interface DownloadClickDetector : NSObject | |||
struct DownloadClickDetectorClass : public ObjCClass <NSObject> | |||
{ | |||
juce::WebBrowserComponent* ownerComponent; | |||
} | |||
- (DownloadClickDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent; | |||
- (void) webView: (WebView*) webView decidePolicyForNavigationAction: (NSDictionary*) actionInformation | |||
request: (NSURLRequest*) request | |||
frame: (WebFrame*) frame | |||
decisionListener: (id <WebPolicyDecisionListener>) listener; | |||
- (void) webView: (WebView*) webView didFinishLoadForFrame: (WebFrame*) frame; | |||
DownloadClickDetectorClass() : ObjCClass ("JUCEWebClickDetector_") | |||
{ | |||
addIvar <WebBrowserComponent*> ("owner"); | |||
@end | |||
addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:), | |||
decidePolicyForNavigationAction, "v@:@@@@@"); | |||
addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@"); | |||
@implementation DownloadClickDetector | |||
registerClass(); | |||
} | |||
- (DownloadClickDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent_ | |||
{ | |||
[super init]; | |||
ownerComponent = ownerComponent_; | |||
return self; | |||
} | |||
static void setOwner (id self, WebBrowserComponent* owner) | |||
{ | |||
object_setInstanceVariable (self, "owner", owner); | |||
} | |||
- (void) webView: (WebView*) sender decidePolicyForNavigationAction: (NSDictionary*) actionInformation | |||
request: (NSURLRequest*) request | |||
frame: (WebFrame*) frame | |||
decisionListener: (id <WebPolicyDecisionListener>) listener | |||
{ | |||
(void) sender; (void) request; (void) frame; | |||
private: | |||
static WebBrowserComponent* getOwner (id self) | |||
{ | |||
return getIvar<WebBrowserComponent*> (self, "owner"); | |||
} | |||
NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")]; | |||
static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation, | |||
NSURLRequest*, WebFrame*, id <WebPolicyDecisionListener> listener) | |||
{ | |||
NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")]; | |||
if (ownerComponent->pageAboutToLoad (nsStringToJuce ([url absoluteString]))) | |||
[listener use]; | |||
else | |||
[listener ignore]; | |||
} | |||
if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([url absoluteString]))) | |||
[listener use]; | |||
else | |||
[listener ignore]; | |||
} | |||
- (void) webView: (WebView*) sender didFinishLoadForFrame: (WebFrame*) frame | |||
{ | |||
if ([frame isEqual: [sender mainFrame]]) | |||
static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame) | |||
{ | |||
NSURL* url = [[[frame dataSource] request] URL]; | |||
ownerComponent->pageFinishedLoading (nsStringToJuce ([url absoluteString])); | |||
if ([frame isEqual: [sender mainFrame]]) | |||
{ | |||
NSURL* url = [[[frame dataSource] request] URL]; | |||
getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString])); | |||
} | |||
} | |||
} | |||
@end | |||
}; | |||
#else | |||
} // (juce namespace) | |||
//============================================================================== | |||
@interface WebViewTapDetector : NSObject <UIGestureRecognizerDelegate> | |||
{ | |||
@@ -126,10 +117,10 @@ class WebBrowserComponentInternal; | |||
return ownerComponent->pageAboutToLoad (nsStringToJuce (request.URL.absoluteString)); | |||
} | |||
@end | |||
#endif | |||
namespace juce | |||
{ | |||
namespace juce { | |||
#endif | |||
//============================================================================== | |||
class WebBrowserComponentInternal | |||
@@ -148,7 +139,9 @@ public: | |||
groupName: nsEmptyString()]; | |||
setView (webView); | |||
clickListener = [[DownloadClickDetector alloc] initWithWebBrowserOwner: owner]; | |||
static DownloadClickDetectorClass cls; | |||
clickListener = [cls.createInstance() init]; | |||
DownloadClickDetectorClass::setOwner (clickListener, owner); | |||
[webView setPolicyDelegate: clickListener]; | |||
[webView setFrameLoadDelegate: clickListener]; | |||
#else | |||
@@ -238,7 +231,7 @@ public: | |||
private: | |||
#if JUCE_MAC | |||
WebView* webView; | |||
DownloadClickDetector* clickListener; | |||
NSObject* clickListener; | |||
#else | |||
UIWebView* webView; | |||
WebViewTapDetector* tapDetector; | |||
@@ -171,7 +171,6 @@ static void clearGLError() | |||
//============================================================================== | |||
#if JUCE_MAC || JUCE_IOS | |||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||
#include "../juce_core/native/juce_mac_ObjCSuffix.h" | |||
#include "../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" | |||
#if JUCE_MAC | |||
@@ -23,92 +23,78 @@ | |||
============================================================================== | |||
*/ | |||
} // (juce namespace) | |||
#define ThreadSafeNSOpenGLView MakeObjCClassName(ThreadSafeNSOpenGLView) | |||
//============================================================================== | |||
@interface ThreadSafeNSOpenGLView : NSOpenGLView | |||
struct ThreadSafeNSOpenGLViewClass : public ObjCClass <NSOpenGLView> | |||
{ | |||
juce::CriticalSection* contextLock; | |||
bool needsUpdate; | |||
} | |||
ThreadSafeNSOpenGLViewClass() : ObjCClass ("JUCEGLView_") | |||
{ | |||
addIvar <CriticalSection*> ("lock"); | |||
addIvar <BOOL> ("needsUpdate"); | |||
- (id) initWithFrame: (NSRect) frameRect pixelFormat: (NSOpenGLPixelFormat*) format; | |||
- (bool) makeActive; | |||
- (void) reshape; | |||
- (void) rightMouseDown: (NSEvent*) ev; | |||
- (void) rightMouseUp: (NSEvent*) ev; | |||
@end | |||
addMethod (@selector (update), update, "v@:"); | |||
addMethod (@selector (reshape), reshape, "v@:"); | |||
addMethod (@selector (_surfaceNeedsUpdate:), surfaceNeedsUpdate, "v@:@"); | |||
addMethod (@selector (rightMouseDown:), rightMouseDown, "v@:@"); | |||
addMethod (@selector (rightMouseUp:), rightMouseUp, "v@:@"); | |||
@implementation ThreadSafeNSOpenGLView | |||
registerClass(); | |||
} | |||
- (id) initWithFrame: (NSRect) frameRect | |||
pixelFormat: (NSOpenGLPixelFormat*) format | |||
{ | |||
contextLock = new juce::CriticalSection(); | |||
self = [super initWithFrame: frameRect pixelFormat: format]; | |||
needsUpdate = true; | |||
static void init (id self) | |||
{ | |||
object_setInstanceVariable (self, "lock", new CriticalSection()); | |||
setNeedsUpdate (self, YES); | |||
} | |||
if (self != nil) | |||
[[NSNotificationCenter defaultCenter] addObserver: self | |||
selector: @selector (_surfaceNeedsUpdate:) | |||
name: NSViewGlobalFrameDidChangeNotification | |||
object: self]; | |||
return self; | |||
} | |||
static bool makeActive (id self) | |||
{ | |||
const ScopedLock sl (*getLock (self)); | |||
- (void) dealloc | |||
{ | |||
[[NSNotificationCenter defaultCenter] removeObserver: self]; | |||
delete contextLock; | |||
[super dealloc]; | |||
} | |||
if ([(NSOpenGLView*) self openGLContext] == nil) | |||
return false; | |||
- (bool) makeActive | |||
{ | |||
const juce::ScopedLock sl (*contextLock); | |||
[[(NSOpenGLView*) self openGLContext] makeCurrentContext]; | |||
if ([self openGLContext] == nil) | |||
return false; | |||
if (getIvar<BOOL> (self, "needsUpdate")) | |||
{ | |||
sendSuperclassMessage (self, @selector (update)); | |||
setNeedsUpdate (self, NO); | |||
} | |||
[[self openGLContext] makeCurrentContext]; | |||
return true; | |||
} | |||
if (needsUpdate) | |||
private: | |||
static CriticalSection* getLock (id self) | |||
{ | |||
[super update]; | |||
needsUpdate = false; | |||
return getIvar<CriticalSection*> (self, "lock"); | |||
} | |||
return true; | |||
} | |||
- (void) _surfaceNeedsUpdate: (NSNotification*) notification | |||
{ | |||
(void) notification; | |||
const juce::ScopedLock sl (*contextLock); | |||
needsUpdate = true; | |||
} | |||
static void setNeedsUpdate (id self, BOOL b) | |||
{ | |||
object_setInstanceVariable (self, "needsUpdate", (void*) b); | |||
} | |||
- (void) update | |||
{ | |||
const juce::ScopedLock sl (*contextLock); | |||
needsUpdate = true; | |||
} | |||
static void setNeedsUpdateLocked (id self, BOOL b) | |||
{ | |||
const ScopedLock sl (*getLock (self)); | |||
setNeedsUpdate (self, b); | |||
} | |||
- (void) reshape | |||
{ | |||
const juce::ScopedLock sl (*contextLock); | |||
needsUpdate = true; | |||
} | |||
static void dealloc (id self, SEL) | |||
{ | |||
delete getLock (self); | |||
sendSuperclassMessage (self, @selector (dealloc)); | |||
} | |||
- (void) rightMouseDown: (NSEvent*) ev { [[self superview] rightMouseDown: ev]; } | |||
- (void) rightMouseUp: (NSEvent*) ev { [[self superview] rightMouseUp: ev]; } | |||
static void surfaceNeedsUpdate (id self, SEL, NSNotification*) { setNeedsUpdateLocked (self, YES); } | |||
static void update (id self, SEL) { setNeedsUpdateLocked (self, YES); } | |||
static void reshape (id self, SEL) { setNeedsUpdateLocked (self, YES); } | |||
@end | |||
static void rightMouseDown (id self, SEL, NSEvent* ev) { [[(NSOpenGLView*) self superview] rightMouseDown: ev]; } | |||
static void rightMouseUp (id self, SEL, NSEvent* ev) { [[(NSOpenGLView*) self superview] rightMouseUp: ev]; } | |||
}; | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
class OpenGLContext::NativeContext | |||
@@ -137,8 +123,15 @@ public: | |||
NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes: attribs]; | |||
view = [[ThreadSafeNSOpenGLView alloc] initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f) | |||
pixelFormat: format]; | |||
static ThreadSafeNSOpenGLViewClass cls; | |||
view = [cls.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f) | |||
pixelFormat: format]; | |||
ThreadSafeNSOpenGLViewClass::init (view); | |||
[[NSNotificationCenter defaultCenter] addObserver: view | |||
selector: @selector (_surfaceNeedsUpdate:) | |||
name: NSViewGlobalFrameDidChangeNotification | |||
object: view]; | |||
renderContext = [[[NSOpenGLContext alloc] initWithFormat: format | |||
shareContext: (NSOpenGLContext*) contextToShareWith] autorelease]; | |||
@@ -153,6 +146,7 @@ public: | |||
~NativeContext() | |||
{ | |||
[[NSNotificationCenter defaultCenter] removeObserver: view]; | |||
[renderContext clearDrawable]; | |||
[renderContext setView: nil]; | |||
[view setOpenGLContext: nil]; | |||
@@ -173,7 +167,7 @@ public: | |||
if ([renderContext view] != view) | |||
[renderContext setView: view]; | |||
[view makeActive]; | |||
ThreadSafeNSOpenGLViewClass::makeActive (view); | |||
return true; | |||
} | |||
@@ -228,7 +222,7 @@ public: | |||
private: | |||
NSOpenGLContext* renderContext; | |||
ThreadSafeNSOpenGLView* view; | |||
NSOpenGLView* view; | |||
ReferenceCountedObjectPtr<ReferenceCountedObject> viewAttachment; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext); | |||
@@ -108,7 +108,6 @@ namespace juce | |||
#if JUCE_MAC || JUCE_IOS | |||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||
#include "../juce_core/native/juce_mac_ObjCSuffix.h" | |||
#if JUCE_USE_CAMERA | |||
#include "native/juce_mac_CameraDevice.mm" | |||
@@ -27,51 +27,21 @@ | |||
#error "On the Mac, cameras use Quicktime, so if you turn on JUCE_USE_CAMERA, you also need to enable JUCE_QUICKTIME" | |||
#endif | |||
//============================================================================== | |||
#define QTCaptureCallbackDelegate MakeObjCClassName(QTCaptureCallbackDelegate) | |||
class QTCameraDeviceInteral; | |||
} // (juce namespace) | |||
@interface QTCaptureCallbackDelegate : NSObject | |||
{ | |||
@public | |||
CameraDevice* owner; | |||
QTCameraDeviceInteral* internal; | |||
int64 firstPresentationTime; | |||
int64 averageTimeOffset; | |||
} | |||
- (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner internalDev: (QTCameraDeviceInteral*) d; | |||
- (void) dealloc; | |||
- (void) captureOutput: (QTCaptureOutput*) captureOutput | |||
didOutputVideoFrame: (CVImageBufferRef) videoFrame | |||
withSampleBuffer: (QTSampleBuffer*) sampleBuffer | |||
fromConnection: (QTCaptureConnection*) connection; | |||
- (void) captureOutput: (QTCaptureFileOutput*) captureOutput | |||
didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer | |||
fromConnection: (QTCaptureConnection*) connection; | |||
@end | |||
namespace juce | |||
{ | |||
extern Image juce_createImageFromCIImage (CIImage* im, int w, int h); | |||
//============================================================================== | |||
class QTCameraDeviceInteral | |||
class QTCameraDeviceInternal | |||
{ | |||
public: | |||
QTCameraDeviceInteral (CameraDevice* owner, const int index) | |||
QTCameraDeviceInternal (CameraDevice* owner, const int index) | |||
: input (nil), | |||
audioDevice (nil), | |||
audioInput (nil), | |||
session (nil), | |||
fileOutput (nil), | |||
imageOutput (nil) | |||
imageOutput (nil), | |||
firstPresentationTime (0), | |||
averageTimeOffset (0) | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
@@ -79,8 +49,10 @@ public: | |||
NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo]; | |||
device = (QTCaptureDevice*) [devs objectAtIndex: index]; | |||
callbackDelegate = [[QTCaptureCallbackDelegate alloc] initWithOwner: owner | |||
internalDev: this]; | |||
static DelegateClass cls; | |||
callbackDelegate = [cls.createInstance() init]; | |||
DelegateClass::setOwner (callbackDelegate, this); | |||
NSError* err = nil; | |||
[device retain]; | |||
@@ -112,7 +84,7 @@ public: | |||
DBG (openingError); | |||
} | |||
~QTCameraDeviceInteral() | |||
~QTCameraDeviceInternal() | |||
{ | |||
[session stopRunning]; | |||
[session removeOutput: imageOutput]; | |||
@@ -194,6 +166,33 @@ public: | |||
} | |||
} | |||
void captureBuffer (QTSampleBuffer* sampleBuffer) | |||
{ | |||
const Time now (Time::getCurrentTime()); | |||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 | |||
NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: QTSampleBufferHostTimeAttribute]; | |||
#else | |||
NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: nsStringLiteral ("hostTime")]; | |||
#endif | |||
int64 presentationTime = (hosttime != nil) | |||
? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40) | |||
: (([sampleBuffer presentationTime].timeValue * 1000) / [sampleBuffer presentationTime].timeScale + 50); | |||
const int64 timeDiff = now.toMilliseconds() - presentationTime; | |||
if (firstPresentationTime == 0) | |||
{ | |||
firstPresentationTime = presentationTime; | |||
averageTimeOffset = timeDiff; | |||
} | |||
else | |||
{ | |||
averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128; | |||
} | |||
} | |||
QTCaptureDevice* device; | |||
QTCaptureDeviceInput* input; | |||
QTCaptureDevice* audioDevice; | |||
@@ -201,87 +200,69 @@ public: | |||
QTCaptureSession* session; | |||
QTCaptureMovieFileOutput* fileOutput; | |||
QTCaptureDecompressedVideoOutput* imageOutput; | |||
QTCaptureCallbackDelegate* callbackDelegate; | |||
NSObject* callbackDelegate; | |||
String openingError; | |||
int64 firstPresentationTime; | |||
int64 averageTimeOffset; | |||
Array<CameraDevice::Listener*> listeners; | |||
CriticalSection listenerLock; | |||
}; | |||
} // (juce namespace) | |||
@implementation QTCaptureCallbackDelegate | |||
- (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner_ | |||
internalDev: (QTCameraDeviceInteral*) d | |||
{ | |||
[super init]; | |||
owner = owner_; | |||
internal = d; | |||
firstPresentationTime = 0; | |||
averageTimeOffset = 0; | |||
return self; | |||
} | |||
- (void) dealloc | |||
{ | |||
[super dealloc]; | |||
} | |||
- (void) captureOutput: (QTCaptureOutput*) captureOutput | |||
didOutputVideoFrame: (CVImageBufferRef) videoFrame | |||
withSampleBuffer: (QTSampleBuffer*) sampleBuffer | |||
fromConnection: (QTCaptureConnection*) connection | |||
{ | |||
if (internal->listeners.size() > 0) | |||
private: | |||
//============================================================================== | |||
struct DelegateClass : public ObjCClass <NSObject> | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
DelegateClass() : ObjCClass ("JUCEAppDelegate_") | |||
{ | |||
addIvar<QTCameraDeviceInternal*> ("owner"); | |||
internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame], | |||
CVPixelBufferGetWidth (videoFrame), | |||
CVPixelBufferGetHeight (videoFrame)); | |||
} | |||
} | |||
addMethod (@selector (captureOutput:didOutputVideoFrame:withSampleBuffer:fromConnection:), | |||
didOutputVideoFrame, "v@:@", @encode (CVImageBufferRef), "@@"); | |||
addMethod (@selector (captureOutput:didOutputSampleBuffer:fromConnection:), | |||
didOutputVideoFrame, "v@:@@@"); | |||
- (void) captureOutput: (QTCaptureFileOutput*) captureOutput | |||
didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer | |||
fromConnection: (QTCaptureConnection*) connection | |||
{ | |||
const Time now (Time::getCurrentTime()); | |||
registerClass(); | |||
} | |||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 | |||
NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: QTSampleBufferHostTimeAttribute]; | |||
#else | |||
NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: nsStringLiteral ("hostTime")]; | |||
#endif | |||
static void setOwner (id self, QTCameraDeviceInternal* owner) | |||
{ | |||
object_setInstanceVariable (self, "owner", owner); | |||
} | |||
int64 presentationTime = (hosttime != nil) | |||
? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40) | |||
: (([sampleBuffer presentationTime].timeValue * 1000) / [sampleBuffer presentationTime].timeScale + 50); | |||
private: | |||
static QTCameraDeviceInternal* getOwner (id self) | |||
{ | |||
return getIvar<QTCameraDeviceInternal*> (self, "owner"); | |||
} | |||
const int64 timeDiff = now.toMilliseconds() - presentationTime; | |||
static void didOutputVideoFrame (id self, SEL, QTCaptureOutput* captureOutput, | |||
CVImageBufferRef videoFrame, QTSampleBuffer* sampleBuffer, | |||
QTCaptureConnection* connection) | |||
{ | |||
QTCameraDeviceInternal* const internal = getOwner (self); | |||
if (firstPresentationTime == 0) | |||
{ | |||
firstPresentationTime = presentationTime; | |||
averageTimeOffset = timeDiff; | |||
} | |||
else | |||
{ | |||
averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128; | |||
} | |||
} | |||
if (internal->listeners.size() > 0) | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
@end | |||
internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame], | |||
CVPixelBufferGetWidth (videoFrame), | |||
CVPixelBufferGetHeight (videoFrame)); | |||
} | |||
} | |||
namespace juce | |||
{ | |||
static void didOutputSampleBuffer (id self, SEL, QTCaptureFileOutput*, QTSampleBuffer* sampleBuffer, QTCaptureConnection*) | |||
{ | |||
getOwner (self)->captureBuffer (sampleBuffer); | |||
} | |||
}; | |||
}; | |||
//============================================================================== | |||
class QTCaptureViewerComp : public NSViewComponent | |||
{ | |||
public: | |||
QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInteral* const internal) | |||
QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInternal* const internal) | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
captureView = [[QTCaptureView alloc] init]; | |||
@@ -306,19 +287,19 @@ CameraDevice::CameraDevice (const String& name_, int index) | |||
: name (name_) | |||
{ | |||
isRecording = false; | |||
internal = new QTCameraDeviceInteral (this, index); | |||
internal = new QTCameraDeviceInternal (this, index); | |||
} | |||
CameraDevice::~CameraDevice() | |||
{ | |||
stopRecording(); | |||
delete static_cast <QTCameraDeviceInteral*> (internal); | |||
delete static_cast <QTCameraDeviceInternal*> (internal); | |||
internal = nullptr; | |||
} | |||
Component* CameraDevice::createViewerComponent() | |||
{ | |||
return new QTCaptureViewerComp (this, static_cast <QTCameraDeviceInteral*> (internal)); | |||
return new QTCaptureViewerComp (this, static_cast <QTCameraDeviceInternal*> (internal)); | |||
} | |||
String CameraDevice::getFileExtension() | |||
@@ -330,8 +311,8 @@ void CameraDevice::startRecordingToFile (const File& file, int quality) | |||
{ | |||
stopRecording(); | |||
QTCameraDeviceInteral* const d = static_cast <QTCameraDeviceInteral*> (internal); | |||
d->callbackDelegate->firstPresentationTime = 0; | |||
QTCameraDeviceInternal* const d = static_cast <QTCameraDeviceInternal*> (internal); | |||
d->firstPresentationTime = 0; | |||
file.deleteFile(); | |||
// In some versions of QT (e.g. on 10.5), if you record video without audio, the speed comes | |||
@@ -367,9 +348,9 @@ void CameraDevice::startRecordingToFile (const File& file, int quality) | |||
Time CameraDevice::getTimeOfFirstRecordedFrame() const | |||
{ | |||
QTCameraDeviceInteral* const d = static_cast <QTCameraDeviceInteral*> (internal); | |||
if (d->callbackDelegate->firstPresentationTime != 0) | |||
return Time (d->callbackDelegate->firstPresentationTime + d->callbackDelegate->averageTimeOffset); | |||
QTCameraDeviceInternal* const d = static_cast <QTCameraDeviceInternal*> (internal); | |||
if (d->firstPresentationTime != 0) | |||
return Time (d->firstPresentationTime + d->averageTimeOffset); | |||
return Time(); | |||
} | |||
@@ -378,7 +359,7 @@ void CameraDevice::stopRecording() | |||
{ | |||
if (isRecording) | |||
{ | |||
static_cast <QTCameraDeviceInteral*> (internal)->resetFile(); | |||
static_cast <QTCameraDeviceInternal*> (internal)->resetFile(); | |||
isRecording = false; | |||
} | |||
} | |||
@@ -386,13 +367,13 @@ void CameraDevice::stopRecording() | |||
void CameraDevice::addListener (Listener* listenerToAdd) | |||
{ | |||
if (listenerToAdd != nullptr) | |||
static_cast <QTCameraDeviceInteral*> (internal)->addListener (listenerToAdd); | |||
static_cast <QTCameraDeviceInternal*> (internal)->addListener (listenerToAdd); | |||
} | |||
void CameraDevice::removeListener (Listener* listenerToRemove) | |||
{ | |||
if (listenerToRemove != nullptr) | |||
static_cast <QTCameraDeviceInteral*> (internal)->removeListener (listenerToRemove); | |||
static_cast <QTCameraDeviceInternal*> (internal)->removeListener (listenerToRemove); | |||
} | |||
//============================================================================== | |||
@@ -418,7 +399,7 @@ CameraDevice* CameraDevice::openDevice (int index, | |||
{ | |||
ScopedPointer <CameraDevice> d (new CameraDevice (getAvailableDevices() [index], index)); | |||
if (static_cast <QTCameraDeviceInteral*> (d->internal)->openingError.isEmpty()) | |||
if (static_cast <QTCameraDeviceInternal*> (d->internal)->openingError.isEmpty()) | |||
return d.release(); | |||
return nullptr; | |||
@@ -25,49 +25,31 @@ | |||
#if JUCE_QUICKTIME | |||
} // (juce namespace) | |||
//============================================================================== | |||
#define NonInterceptingQTMovieView MakeObjCClassName(NonInterceptingQTMovieView) | |||
@interface NonInterceptingQTMovieView : QTMovieView | |||
{ | |||
} | |||
- (id) initWithFrame: (NSRect) frame; | |||
- (BOOL) acceptsFirstMouse: (NSEvent*) theEvent; | |||
- (NSView*) hitTest: (NSPoint) p; | |||
@end | |||
@implementation NonInterceptingQTMovieView | |||
- (id) initWithFrame: (NSRect) frame | |||
{ | |||
self = [super initWithFrame: frame]; | |||
[self setNextResponder: [self superview]]; | |||
return self; | |||
} | |||
- (void) dealloc | |||
struct NonInterceptingQTMovieViewClass : public ObjCClass <QTMovieView> | |||
{ | |||
[super dealloc]; | |||
} | |||
NonInterceptingQTMovieViewClass() : ObjCClass ("JUCEQTMovieView_") | |||
{ | |||
addMethod (@selector (hitTest:), hitTest, "@@:", @encode (NSPoint)); | |||
addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse, "c@:@"); | |||
- (NSView*) hitTest: (NSPoint) point | |||
{ | |||
return [self isControllerVisible] ? [super hitTest: point] : nil; | |||
} | |||
registerClass(); | |||
} | |||
- (BOOL) acceptsFirstMouse: (NSEvent*) theEvent | |||
{ | |||
return YES; | |||
} | |||
private: | |||
static NSView* hitTest (id self, SEL, NSPoint point) | |||
{ | |||
if (! [(QTMovieView*) self isControllerVisible]) | |||
return nil; | |||
@end | |||
objc_super s = { self, [QTMovieView class] }; | |||
return objc_msgSendSuper (&s, @selector (hitTest:), point); | |||
} | |||
namespace juce | |||
{ | |||
static BOOL acceptsFirstMouse (id, SEL, NSEvent*) | |||
{ | |||
return YES; | |||
} | |||
}; | |||
//============================================================================== | |||
#define theMovie (static_cast <QTMovie*> (movie)) | |||
@@ -79,8 +61,10 @@ QuickTimeMovieComponent::QuickTimeMovieComponent() | |||
setOpaque (true); | |||
setVisible (true); | |||
QTMovieView* view = [[NonInterceptingQTMovieView alloc] initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]; | |||
static NonInterceptingQTMovieViewClass cls; | |||
QTMovieView* view = [cls.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]; | |||
setView (view); | |||
[view setNextResponder: [view superview]]; | |||
[view setWantsLayer: YES]; // prevents the view failing to redraw correctly when paused. | |||
[view release]; | |||
} | |||