mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Rewrote all internal OSX obj-C classes as dynamically-created classes. A side-effect of this is that plugins will no longer suffer from obj-C name-collision problems, and the old JUCE_ObjCExtraSuffix value is no longer needed.
This commit is contained in:
parent
2cf53557f6
commit
ac1d6955e7
24 changed files with 2497 additions and 2726 deletions
|
|
@ -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,102 +25,204 @@
|
|||
|
||||
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;
|
||||
}
|
||||
AudioTrackProducerClass() : ObjCClass ("JUCEAudioTrackProducer_")
|
||||
{
|
||||
addIvar<AudioSourceHolder*> ("source");
|
||||
|
||||
- (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
|
||||
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");
|
||||
|
||||
//==============================================================================
|
||||
#define AudioTrackProducer MakeObjCClassName(AudioTrackProducer)
|
||||
registerClass();
|
||||
}
|
||||
|
||||
@interface AudioTrackProducer : NSObject
|
||||
{
|
||||
juce::AudioSource* source;
|
||||
struct AudioSourceHolder
|
||||
{
|
||||
AudioSourceHolder (AudioSource* source_, int numFrames)
|
||||
: source (source_), readPosition (0), lengthInFrames (numFrames)
|
||||
{
|
||||
}
|
||||
|
||||
~AudioSourceHolder()
|
||||
{
|
||||
if (source != nullptr)
|
||||
source->releaseResources();
|
||||
}
|
||||
|
||||
ScopedPointer<AudioSource> source;
|
||||
int readPosition, lengthInFrames;
|
||||
}
|
||||
};
|
||||
|
||||
- (AudioTrackProducer*) init: (int) lengthInFrames;
|
||||
- (AudioTrackProducer*) initWithAudioSource: (juce::AudioSource*) source numSamples: (int) lengthInSamples;
|
||||
- (void) dealloc;
|
||||
- (void) setupTrackProperties: (DRTrack*) track;
|
||||
|
||||
- (void) cleanupTrackAfterBurn: (DRTrack*) track;
|
||||
- (BOOL) cleanupTrackAfterVerification:(DRTrack*)track;
|
||||
- (uint64_t) estimateLengthOfTrack:(DRTrack*)track;
|
||||
- (BOOL) prepareTrack:(DRTrack*)track forBurn:(DRBurn*)burn
|
||||
toMedia:(NSDictionary*)mediaInfo;
|
||||
- (BOOL) prepareTrackForVerification:(DRTrack*)track;
|
||||
- (uint32_t) produceDataForTrack:(DRTrack*)track intoBuffer:(char*)buffer
|
||||
length:(uint32_t)bufferLength atAddress:(uint64_t)address
|
||||
blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags;
|
||||
- (uint32_t) producePreGapForTrack:(DRTrack*)track
|
||||
intoBuffer:(char*)buffer length:(uint32_t)bufferLength
|
||||
atAddress:(uint64_t)address blockSize:(uint32_t)blockSize
|
||||
ioFlags:(uint32_t*)flags;
|
||||
- (BOOL) verifyDataForTrack:(DRTrack*)track inBuffer:(const char*)buffer
|
||||
length:(uint32_t)bufferLength atAddress:(uint64_t)address
|
||||
blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags;
|
||||
- (uint32_t) producePreGapForTrack:(DRTrack*)track
|
||||
intoBuffer:(char*)buffer length:(uint32_t)bufferLength
|
||||
atAddress:(uint64_t)address blockSize:(uint32_t)blockSize
|
||||
ioFlags:(uint32_t*)flags;
|
||||
@end
|
||||
|
||||
//==============================================================================
|
||||
@implementation OpenDiskDevice
|
||||
|
||||
- (OpenDiskDevice*) initWithDRDevice: (DRDevice*) device_
|
||||
{
|
||||
[super init];
|
||||
|
||||
device = device_;
|
||||
tracks = [[NSMutableArray alloc] init];
|
||||
underrunProtection = true;
|
||||
private:
|
||||
static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source)
|
||||
{
|
||||
self = sendSuperclassMessage (self, @selector (init));
|
||||
object_setInstanceVariable (self, "source", source);
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
static AudioSourceHolder* getSource (id self)
|
||||
{
|
||||
return getIvar<AudioSourceHolder*> (self, "source");
|
||||
}
|
||||
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
delete getSource (self);
|
||||
sendSuperclassMessage (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
static void cleanupTrackAfterBurn (id self, SEL, DRTrack*) {}
|
||||
static BOOL cleanupTrackAfterVerification (id self, SEL, DRTrack*) { return true; }
|
||||
|
||||
static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*)
|
||||
{
|
||||
return getSource (self)->lengthInFrames;
|
||||
}
|
||||
|
||||
static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*)
|
||||
{
|
||||
AudioSourceHolder* const source = getSource (self);
|
||||
|
||||
if (source != nullptr)
|
||||
{
|
||||
source->source->prepareToPlay (44100 / 75, 44100);
|
||||
source->readPosition = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static BOOL prepareTrackForVerification (id self, SEL, DRTrack*)
|
||||
{
|
||||
AudioSourceHolder* const source = getSource (self);
|
||||
|
||||
if (source != nullptr)
|
||||
source->source->prepareToPlay (44100 / 75, 44100);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer,
|
||||
uint32_t bufferLength, uint64_t /*address*/,
|
||||
uint32_t /*blockSize*/, uint32_t* /*flags*/)
|
||||
{
|
||||
AudioSourceHolder* const source = getSource (self);
|
||||
|
||||
if (source != nullptr)
|
||||
{
|
||||
const int numSamples = jmin ((int) bufferLength / 4,
|
||||
(source->lengthInFrames * (44100 / 75)) - source->readPosition);
|
||||
|
||||
if (numSamples > 0)
|
||||
{
|
||||
AudioSampleBuffer tempBuffer (2, numSamples);
|
||||
AudioSourceChannelInfo info (tempBuffer);
|
||||
|
||||
source->source->getNextAudioBlock (info);
|
||||
|
||||
typedef AudioData::Pointer <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;
|
||||
}
|
||||
|
||||
return numSamples * 4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t producePreGapForTrack (id self, SEL, DRTrack*, char* buffer,
|
||||
uint32_t bufferLength, uint64_t /*address*/,
|
||||
uint32_t /*blockSize*/, uint32_t* /*flags*/)
|
||||
{
|
||||
zeromem (buffer, bufferLength);
|
||||
return bufferLength;
|
||||
}
|
||||
|
||||
static BOOL verifyDataForTrack (id self, SEL, DRTrack*, const char*,
|
||||
uint32_t /*bufferLength*/, uint64_t /*address*/,
|
||||
uint32_t /*blockSize*/, uint32_t* /*flags*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct OpenDiskDevice
|
||||
{
|
||||
OpenDiskDevice (DRDevice* device_)
|
||||
: device (device_),
|
||||
tracks ([[NSMutableArray alloc] init]),
|
||||
underrunProtection (true)
|
||||
{
|
||||
}
|
||||
|
||||
~OpenDiskDevice()
|
||||
{
|
||||
[tracks release];
|
||||
[super dealloc];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) addSourceTrack: (juce::AudioSource*) source_ numSamples: (int) numSamples_
|
||||
{
|
||||
AudioTrackProducer* p = [[AudioTrackProducer alloc] initWithAudioSource: source_ numSamples: numSamples_];
|
||||
DRTrack* t = [[DRTrack alloc] initWithProducer: p];
|
||||
[p setupTrackProperties: t];
|
||||
void addSourceTrack (AudioSource* source, int numSamples)
|
||||
{
|
||||
if (source != nullptr)
|
||||
{
|
||||
const int numFrames = (numSamples + 587) / 588;
|
||||
|
||||
[tracks addObject: t];
|
||||
static AudioTrackProducerClass cls;
|
||||
|
||||
[t release];
|
||||
NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:)
|
||||
withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)];
|
||||
DRTrack* track = [[DRTrack alloc] initWithProducer: producer];
|
||||
|
||||
{
|
||||
NSMutableDictionary* p = [[track properties] mutableCopy];
|
||||
[p setObject: [DRMSF msfWithFrames: numFrames] forKey: DRTrackLengthKey];
|
||||
[p setObject: [NSNumber numberWithUnsignedShort: 2352] forKey: DRBlockSizeKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRDataFormKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRBlockTypeKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRTrackModeKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRSessionFormatKey];
|
||||
[track setProperties: p];
|
||||
[p release];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) burn: (juce::AudioCDBurner::BurnProgressListener*) listener errorString: (juce::String*) error
|
||||
ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting speed: (int) burnSpeed
|
||||
{
|
||||
[tracks addObject: track];
|
||||
|
||||
[track release];
|
||||
[producer release];
|
||||
}
|
||||
}
|
||||
|
||||
String burn (AudioCDBurner::BurnProgressListener* listener,
|
||||
bool shouldEject, bool peformFakeBurnForTesting, int burnSpeed)
|
||||
{
|
||||
DRBurn* burn = [DRBurn burnForDevice: device];
|
||||
|
||||
if (! [device acquireExclusiveAccess])
|
||||
{
|
||||
*error = "Couldn't open or write to the CD device";
|
||||
return;
|
||||
}
|
||||
return "Couldn't open or write to the CD device";
|
||||
|
||||
[device acquireMediaReservation];
|
||||
|
||||
|
|
@ -131,7 +233,7 @@ const int kilobytesPerSecond1x = 176;
|
|||
[d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey];
|
||||
|
||||
if (burnSpeed > 0)
|
||||
[d setObject: [NSNumber numberWithFloat: burnSpeed * juce::kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey];
|
||||
[d setObject: [NSNumber numberWithFloat: burnSpeed * kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey];
|
||||
|
||||
if (! underrunProtection)
|
||||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey];
|
||||
|
|
@ -142,188 +244,37 @@ const int kilobytesPerSecond1x = 176;
|
|||
|
||||
for (;;)
|
||||
{
|
||||
juce::Thread::sleep (300);
|
||||
Thread::sleep (300);
|
||||
float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue];
|
||||
|
||||
if (listener != nullptr && listener->audioCDBurnProgress (progress))
|
||||
{
|
||||
[burn abort];
|
||||
*error = "User cancelled the write operation";
|
||||
break;
|
||||
return "User cancelled the write operation";
|
||||
}
|
||||
|
||||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed])
|
||||
{
|
||||
*error = "Write operation failed";
|
||||
return "Write operation failed";
|
||||
|
||||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone])
|
||||
break;
|
||||
}
|
||||
else if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone])
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey]
|
||||
objectForKey: DRErrorStatusErrorStringKey];
|
||||
|
||||
if ([err length] > 0)
|
||||
{
|
||||
*error = juce::CharPointer_UTF8 ([err UTF8String]);
|
||||
break;
|
||||
}
|
||||
return CharPointer_UTF8 ([err UTF8String]);
|
||||
}
|
||||
|
||||
[device releaseMediaReservation];
|
||||
[device releaseExclusiveAccess];
|
||||
}
|
||||
@end
|
||||
|
||||
//==============================================================================
|
||||
@implementation AudioTrackProducer
|
||||
|
||||
- (AudioTrackProducer*) init: (int) lengthInFrames_
|
||||
{
|
||||
lengthInFrames = lengthInFrames_;
|
||||
readPosition = 0;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) setupTrackProperties: (DRTrack*) track
|
||||
{
|
||||
NSMutableDictionary* p = [[track properties] mutableCopy];
|
||||
[p setObject:[DRMSF msfWithFrames: lengthInFrames] forKey: DRTrackLengthKey];
|
||||
[p setObject:[NSNumber numberWithUnsignedShort:2352] forKey: DRBlockSizeKey];
|
||||
[p setObject:[NSNumber numberWithInt:0] forKey: DRDataFormKey];
|
||||
[p setObject:[NSNumber numberWithInt:0] forKey: DRBlockTypeKey];
|
||||
[p setObject:[NSNumber numberWithInt:0] forKey: DRTrackModeKey];
|
||||
[p setObject:[NSNumber numberWithInt:0] forKey: DRSessionFormatKey];
|
||||
|
||||
|
||||
[track setProperties: p];
|
||||
[p release];
|
||||
}
|
||||
|
||||
- (AudioTrackProducer*) initWithAudioSource: (juce::AudioSource*) source_ numSamples: (int) lengthInSamples
|
||||
{
|
||||
AudioTrackProducer* s = [self init: (lengthInSamples + 587) / 588];
|
||||
|
||||
if (s != nil)
|
||||
s->source = source_;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
if (source != nullptr)
|
||||
{
|
||||
source->releaseResources();
|
||||
delete source;
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) cleanupTrackAfterBurn: (DRTrack*) track
|
||||
{
|
||||
(void) track;
|
||||
}
|
||||
|
||||
- (BOOL) cleanupTrackAfterVerification: (DRTrack*) track
|
||||
{
|
||||
(void) track;
|
||||
return true;
|
||||
}
|
||||
|
||||
- (uint64_t) estimateLengthOfTrack: (DRTrack*) track
|
||||
{
|
||||
(void) track;
|
||||
return lengthInFrames;
|
||||
}
|
||||
|
||||
- (BOOL) prepareTrack: (DRTrack*) track forBurn: (DRBurn*) burn
|
||||
toMedia: (NSDictionary*) mediaInfo
|
||||
{
|
||||
(void) track; (void) burn; (void) mediaInfo;
|
||||
|
||||
if (source != nullptr)
|
||||
source->prepareToPlay (44100 / 75, 44100);
|
||||
|
||||
readPosition = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
- (BOOL) prepareTrackForVerification: (DRTrack*) track
|
||||
{
|
||||
(void) track;
|
||||
if (source != nullptr)
|
||||
source->prepareToPlay (44100 / 75, 44100);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
- (uint32_t) produceDataForTrack: (DRTrack*) track intoBuffer: (char*) buffer
|
||||
length: (uint32_t) bufferLength atAddress: (uint64_t) address
|
||||
blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags
|
||||
{
|
||||
(void) track; (void) address; (void) blockSize; (void) flags;
|
||||
|
||||
if (source != nullptr)
|
||||
{
|
||||
const int numSamples = juce::jmin ((int) bufferLength / 4, (lengthInFrames * (44100 / 75)) - readPosition);
|
||||
|
||||
if (numSamples > 0)
|
||||
{
|
||||
juce::AudioSampleBuffer tempBuffer (2, numSamples);
|
||||
juce::AudioSourceChannelInfo info (tempBuffer);
|
||||
|
||||
source->getNextAudioBlock (info);
|
||||
|
||||
typedef juce::AudioData::Pointer <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;
|
||||
}
|
||||
|
||||
return numSamples * 4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (uint32_t) producePreGapForTrack: (DRTrack*) track
|
||||
intoBuffer: (char*) buffer length: (uint32_t) bufferLength
|
||||
atAddress: (uint64_t) address blockSize: (uint32_t) blockSize
|
||||
ioFlags: (uint32_t*) flags
|
||||
{
|
||||
(void) track; (void) address; (void) blockSize; (void) flags;
|
||||
zeromem (buffer, bufferLength);
|
||||
return bufferLength;
|
||||
}
|
||||
|
||||
- (BOOL) verifyDataForTrack: (DRTrack*) track inBuffer: (const char*) buffer
|
||||
length: (uint32_t) bufferLength atAddress: (uint64_t) address
|
||||
blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags
|
||||
{
|
||||
(void) track; (void) buffer; (void) bufferLength; (void) address; (void) blockSize; (void) flags;
|
||||
return true;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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()
|
||||
|
|
@ -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;
|
||||
return pimpl->device->burn (listener, ejectDiscAfterwards, performFakeBurnForTesting, writeSpeed);
|
||||
|
||||
[pimpl->device burn: listener
|
||||
errorString: &error
|
||||
ejectAfterwards: ejectDiscAfterwards
|
||||
isFake: performFakeBurnForTesting
|
||||
speed: writeSpeed];
|
||||
}
|
||||
|
||||
return error;
|
||||
return "Couldn't open or write to the CD device";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
//==============================================================================
|
||||
@implementation JuceUIViewClass
|
||||
addMethod (@selector (dealloc), dealloc, "v@:");
|
||||
addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@");
|
||||
addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow, "v@:");
|
||||
addMethod (@selector (mouseDownCanMoveWindow), mouseDownCanMoveWindow, "c@:");
|
||||
registerClass();
|
||||
}
|
||||
|
||||
- (JuceUIViewClass*) initWithFilter: (AudioProcessor*) filter_
|
||||
withAU: (JuceAU*) au_
|
||||
withComponent: (AudioProcessorEditor*) editorComp_
|
||||
{
|
||||
filter = filter_;
|
||||
au = au_;
|
||||
editorComp = new EditorCompHolder (editorComp_);
|
||||
static void deleteEditor (id self)
|
||||
{
|
||||
ScopedPointer<EditorCompHolder> editorComp (getEditor (self));
|
||||
|
||||
[super initWithFrame: NSMakeRect (0, 0, editorComp_->getWidth(), editorComp_->getHeight())];
|
||||
[self setHidden: NO];
|
||||
[self setPostsFrameChangedNotifications: YES];
|
||||
if (editorComp != nullptr)
|
||||
{
|
||||
JuceAU* const au = getAU (self);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector (applicationWillTerminate:)
|
||||
name: NSApplicationWillTerminateNotification
|
||||
object: nil];
|
||||
activeUIs.add (self);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
editorComp->addToDesktop (0, (void*) self);
|
||||
editorComp->setVisible (true);
|
||||
editorComp = nullptr;
|
||||
setEditor (self, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
static JuceAU* getAU (id self) { return getIvar<JuceAU*> (self, "au"); }
|
||||
static EditorCompHolder* getEditor (id self) { return getIvar<EditorCompHolder*> (self, "editor"); }
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
static void setFilter (id self, AudioProcessor* filter) { object_setInstanceVariable (self, "filter", filter); }
|
||||
static void setAU (id self, JuceAU* au) { object_setInstanceVariable (self, "au", au); }
|
||||
static void setEditor (id self, EditorCompHolder* e) { object_setInstanceVariable (self, "editor", e); }
|
||||
|
||||
private:
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
if (activeUIs.contains (self))
|
||||
[self shutdown];
|
||||
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
|
||||
{
|
||||
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];
|
||||
deleteEditor (self);
|
||||
|
||||
jassert (activeUIs.contains (self));
|
||||
activeUIs.removeValue (self);
|
||||
if (activePlugins.size() + activeUIs.size() == 0)
|
||||
shutdownJuce_GUI();
|
||||
}
|
||||
|
||||
- (void) viewDidMoveToWindow
|
||||
{
|
||||
if ([self window] != nil)
|
||||
{
|
||||
[[self window] setAcceptsMouseMovedEvents: YES];
|
||||
|
||||
if (editorComp != nullptr)
|
||||
[[self window] makeFirstResponder: (NSView*) editorComp->getWindowHandle()];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) mouseDownCanMoveWindow
|
||||
{
|
||||
static void viewDidMoveToWindow (id self, SEL)
|
||||
{
|
||||
NSWindow* w = [(NSView*) self window];
|
||||
|
||||
if (w != nil)
|
||||
{
|
||||
[w setAcceptsMouseMovedEvents: YES];
|
||||
|
||||
EditorCompHolder* const editorComp = getEditor (self);
|
||||
if (editorComp != nullptr)
|
||||
[w makeFirstResponder: (NSView*) editorComp->getWindowHandle()];
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL mouseDownCanMoveWindow (id self, SEL)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void) deleteEditor
|
||||
{
|
||||
if (editorComp != nullptr)
|
||||
{
|
||||
if (editorComp->getChildComponent(0) != nullptr)
|
||||
if (activePlugins.contains ((void*) au)) // plugin may have been deleted before the UI
|
||||
filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0));
|
||||
|
||||
deleteAndZero (editorComp);
|
||||
}
|
||||
};
|
||||
|
||||
editorComp = nullptr;
|
||||
}
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE (EditorCompHolder);
|
||||
};
|
||||
|
||||
- (void) filterBeingDeleted: (JuceAU*) au_
|
||||
void JuceAU::deleteActiveEditors()
|
||||
{
|
||||
if (au_ == au)
|
||||
[self deleteEditor];
|
||||
}
|
||||
for (int i = activeUIs.size(); --i >= 0;)
|
||||
{
|
||||
id ui = (id) activeUIs.getUnchecked(i);
|
||||
|
||||
@end
|
||||
if (EditorCompHolder::JuceUIViewClass::getAU (ui) == this)
|
||||
EditorCompHolder::JuceUIViewClass::deleteEditor (ui);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@interface JuceUICreationClass : NSObject <AUCocoaUIBase>
|
||||
{
|
||||
}
|
||||
|
||||
- (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,94 +98,37 @@ 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())
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
JuceURLConnection* owner;
|
||||
};
|
||||
|
||||
|
||||
@implementation JuceURLConnection
|
||||
|
||||
- (JuceURLConnection*) initWithRequest: (NSURLRequest*) req
|
||||
withCallback: (URL::OpenStreamProgressCallback*) callback
|
||||
withContext: (void*) context;
|
||||
{
|
||||
[super init];
|
||||
request = req;
|
||||
[request retain];
|
||||
data = [[NSMutableData data] retain];
|
||||
dataLock = [[NSLock alloc] init];
|
||||
connection = nil;
|
||||
initialised = false;
|
||||
hasFailed = false;
|
||||
hasFinished = false;
|
||||
contentLength = -1;
|
||||
headers = nil;
|
||||
|
||||
runLoopThread = new JuceURLConnectionMessageThread (self);
|
||||
runLoopThread->startThread();
|
||||
|
||||
while (runLoopThread->isThreadRunning() && ! initialised)
|
||||
while (isThreadRunning() && ! initialised)
|
||||
{
|
||||
if (callback != nullptr)
|
||||
callback (context, -1, (int) [[request HTTPBody] length]);
|
||||
|
|
@ -193,108 +136,31 @@ private:
|
|||
Thread::sleep (1);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[self stop];
|
||||
|
||||
deleteAndZero (runLoopThread);
|
||||
[connection release];
|
||||
[data release];
|
||||
[dataLock release];
|
||||
[request release];
|
||||
[headers release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) createConnection
|
||||
{
|
||||
NSUInteger oldRetainCount = [self retainCount];
|
||||
connection = [[NSURLConnection alloc] initWithRequest: request
|
||||
delegate: self];
|
||||
|
||||
if (oldRetainCount == [self retainCount])
|
||||
[self retain]; // newer SDK should already retain this, but there were problems in older versions..
|
||||
|
||||
if (connection == nil)
|
||||
runLoopThread->signalThreadShouldExit();
|
||||
}
|
||||
|
||||
- (void) connection: (NSURLConnection*) conn didReceiveResponse: (NSURLResponse*) response
|
||||
{
|
||||
(void) conn;
|
||||
[dataLock lock];
|
||||
[data setLength: 0];
|
||||
[dataLock unlock];
|
||||
initialised = true;
|
||||
contentLength = [response expectedContentLength];
|
||||
|
||||
[headers release];
|
||||
headers = nil;
|
||||
|
||||
if ([response isKindOfClass: [NSHTTPURLResponse class]])
|
||||
headers = [[((NSHTTPURLResponse*) response) allHeaderFields] retain];
|
||||
}
|
||||
|
||||
- (void) connection: (NSURLConnection*) conn didFailWithError: (NSError*) error
|
||||
{
|
||||
(void) conn;
|
||||
DBG (nsStringToJuce ([error description]));
|
||||
hasFailed = true;
|
||||
initialised = true;
|
||||
|
||||
if (runLoopThread != nullptr)
|
||||
runLoopThread->signalThreadShouldExit();
|
||||
}
|
||||
|
||||
- (void) connection: (NSURLConnection*) conn didReceiveData: (NSData*) newData
|
||||
{
|
||||
(void) conn;
|
||||
[dataLock lock];
|
||||
[data appendData: newData];
|
||||
[dataLock unlock];
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
- (void) connectionDidFinishLoading: (NSURLConnection*) conn
|
||||
{
|
||||
(void) conn;
|
||||
hasFinished = true;
|
||||
initialised = true;
|
||||
|
||||
if (runLoopThread != nullptr)
|
||||
runLoopThread->signalThreadShouldExit();
|
||||
}
|
||||
|
||||
- (BOOL) isOpen
|
||||
{
|
||||
return connection != nil && ! hasFailed;
|
||||
}
|
||||
}
|
||||
|
||||
- (int) readPosition
|
||||
{
|
||||
return position;
|
||||
}
|
||||
void stop()
|
||||
{
|
||||
[connection cancel];
|
||||
stopThread (10000);
|
||||
}
|
||||
|
||||
- (int) read: (char*) dest numBytes: (int) numNeeded
|
||||
{
|
||||
int read (char* dest, int numBytes)
|
||||
{
|
||||
int numDone = 0;
|
||||
|
||||
while (numNeeded > 0)
|
||||
while (numBytes > 0)
|
||||
{
|
||||
int available = jmin (numNeeded, (int) [data length]);
|
||||
const int available = jmin (numBytes, (int) [data length]);
|
||||
|
||||
if (available > 0)
|
||||
{
|
||||
[dataLock lock];
|
||||
const ScopedLock sl (dataLock);
|
||||
[data getBytes: dest length: available];
|
||||
[data replaceBytesInRange: NSMakeRange (0, available) withBytes: nil length: 0];
|
||||
[dataLock unlock];
|
||||
|
||||
numDone += available;
|
||||
numNeeded -= available;
|
||||
numBytes -= available;
|
||||
dest += available;
|
||||
}
|
||||
else
|
||||
|
|
@ -306,28 +172,139 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
position += numDone;
|
||||
return numDone;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) stop
|
||||
void didReceiveResponse (NSURLResponse* response)
|
||||
{
|
||||
{
|
||||
const ScopedLock sl (dataLock);
|
||||
[data setLength: 0];
|
||||
}
|
||||
|
||||
initialised = true;
|
||||
contentLength = [response expectedContentLength];
|
||||
|
||||
[headers release];
|
||||
headers = nil;
|
||||
|
||||
if ([response isKindOfClass: [NSHTTPURLResponse class]])
|
||||
headers = [[((NSHTTPURLResponse*) response) allHeaderFields] retain];
|
||||
}
|
||||
|
||||
void didFailWithError (NSError* error)
|
||||
{
|
||||
DBG (nsStringToJuce ([error description])); (void) error;
|
||||
hasFailed = true;
|
||||
initialised = true;
|
||||
signalThreadShouldExit();
|
||||
}
|
||||
|
||||
void didReceiveData (NSData* newData)
|
||||
{
|
||||
const ScopedLock sl (dataLock);
|
||||
[data appendData: newData];
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
void finishedLoading()
|
||||
{
|
||||
hasFinished = true;
|
||||
initialised = true;
|
||||
signalThreadShouldExit();
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
NSUInteger oldRetainCount = [owner retainCount];
|
||||
connection = [[NSURLConnection alloc] initWithRequest: request
|
||||
delegate: owner];
|
||||
|
||||
if (oldRetainCount == [owner retainCount])
|
||||
[owner retain]; // newer SDK should already retain this, but there were problems in older versions..
|
||||
|
||||
if (connection != nil)
|
||||
{
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64 contentLength;
|
||||
CriticalSection dataLock;
|
||||
NSObject* owner;
|
||||
NSURLRequest* request;
|
||||
NSURLConnection* connection;
|
||||
NSMutableData* data;
|
||||
NSDictionary* headers;
|
||||
bool initialised, hasFailed, hasFinished;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState);
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct URLConnectionDelegateClass : public ObjCClass<NSObject>
|
||||
{
|
||||
[connection cancel];
|
||||
URLConnectionDelegateClass() : ObjCClass ("JUCEAppDelegate_")
|
||||
{
|
||||
addIvar <URLConnectionState*> ("state");
|
||||
|
||||
if (runLoopThread != nullptr)
|
||||
runLoopThread->stopThread (10000);
|
||||
}
|
||||
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@:@");
|
||||
|
||||
@end
|
||||
registerClass();
|
||||
}
|
||||
|
||||
namespace juce
|
||||
{
|
||||
static void setState (id self, URLConnectionState* state)
|
||||
{
|
||||
object_setInstanceVariable (self, "state", state);
|
||||
}
|
||||
|
||||
static URLConnectionState* getState (id self)
|
||||
{
|
||||
return getIvar<URLConnectionState*> (self, "state");
|
||||
}
|
||||
|
||||
private:
|
||||
static void dealloc (id self, SEL sel)
|
||||
{
|
||||
getState (self)->stop();
|
||||
delete getState (self);
|
||||
sendSuperclassMessage (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
static void didReceiveResponse (id self, SEL, NSURLConnection*, NSURLResponse* response)
|
||||
{
|
||||
getState (self)->didReceiveResponse (response);
|
||||
}
|
||||
|
||||
static void didFailWithError (id self, SEL, NSURLConnection*, NSError* error)
|
||||
{
|
||||
getState (self)->didFailWithError (error);
|
||||
}
|
||||
|
||||
static void didReceiveData (id self, SEL, NSURLConnection*, NSData* newData)
|
||||
{
|
||||
getState (self)->didReceiveData (newData);
|
||||
}
|
||||
|
||||
static void connectionDidFinishLoading (id self, SEL, NSURLConnection*)
|
||||
{
|
||||
getState (self)->finishedLoading();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
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];
|
||||
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]));
|
||||
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,12 +397,15 @@ private:
|
|||
|
||||
void close()
|
||||
{
|
||||
[connection stop];
|
||||
if (connection != nil)
|
||||
{
|
||||
URLConnectionDelegateClass::getState (connection)->stop();
|
||||
[connection release];
|
||||
connection = nil;
|
||||
}
|
||||
}
|
||||
|
||||
JuceURLConnection* createConnection (URL::OpenStreamProgressCallback* progressCallback,
|
||||
NSObject* createConnection (URL::OpenStreamProgressCallback* progressCallback,
|
||||
void* progressCallbackContext)
|
||||
{
|
||||
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (address)]
|
||||
|
|
@ -447,11 +435,13 @@ private:
|
|||
[req setHTTPBody: [NSData dataWithBytes: postData.getData()
|
||||
length: postData.getSize()]];
|
||||
|
||||
JuceURLConnection* const s = [[JuceURLConnection alloc] initWithRequest: req
|
||||
withCallback: progressCallback
|
||||
withContext: progressCallbackContext];
|
||||
static URLConnectionDelegateClass cls;
|
||||
NSObject* const s = [cls.createInstance() init];
|
||||
|
||||
if ([s isOpen])
|
||||
URLConnectionState* state = new URLConnectionState (s, req);
|
||||
URLConnectionDelegateClass::setState (s, state);
|
||||
|
||||
if (state->start (progressCallback, progressCallbackContext))
|
||||
return s;
|
||||
|
||||
[s release];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
static Type getIvar (id self, const char* name)
|
||||
{
|
||||
Type v = Type();
|
||||
object_getInstanceVariable (self, name, (void**) &v);
|
||||
return v;
|
||||
}
|
||||
|
||||
private:
|
||||
static String getRandomisedName (const char* root)
|
||||
{
|
||||
return root + String::toHexString (Random::getSystemRandom().nextInt64());
|
||||
}
|
||||
|
||||
private:
|
||||
Class cls;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ObjCClassBuilder);
|
||||
JUCE_DECLARE_NON_COPYABLE (ObjCClass);
|
||||
};
|
||||
|
||||
|
||||
#endif // __JUCE_OSX_OBJCHELPERS_JUCEHEADER__
|
||||
|
|
|
|||
|
|
@ -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_"));
|
||||
addMethod (@selector (init), init, "@@:");
|
||||
addMethod (@selector (dealloc), dealloc, "v@:");
|
||||
addMethod (@selector (unregisterObservers), unregisterObservers, "v@:");
|
||||
addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@");
|
||||
addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@");
|
||||
addMethod (@selector (application:openFile:), application_openFile, "c@:@@");
|
||||
addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@");
|
||||
addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@");
|
||||
addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@");
|
||||
addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@");
|
||||
addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@");
|
||||
addMethod (@selector (dummyMethod), dummyMethod, "v@:");
|
||||
|
||||
c.addMethod (@selector (init), init, "@@:");
|
||||
c.addMethod (@selector (dealloc), dealloc, "v@:");
|
||||
c.addMethod (@selector (unregisterObservers), unregisterObservers, "v@:");
|
||||
c.addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@");
|
||||
c.addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@");
|
||||
c.addMethod (@selector (application:openFile:), application_openFile, "c@:@@");
|
||||
c.addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@");
|
||||
c.addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@");
|
||||
c.addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@");
|
||||
c.addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@");
|
||||
c.addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@");
|
||||
c.addMethod (@selector (dummyMethod), dummyMethod, "v@:");
|
||||
|
||||
return c.getClass();
|
||||
}
|
||||
|
||||
static NSObject* createInstance()
|
||||
{
|
||||
static Class c = createClass();
|
||||
jassert (c != nullptr);
|
||||
return class_createInstance (c, 0);
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static NSString* getBroacastEventName()
|
||||
|
|
@ -64,10 +55,10 @@ struct AppDelegateClass
|
|||
return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64()));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
static id init (id self, SEL)
|
||||
{
|
||||
self = ObjCClassBuilder::sendSuperclassMessage (self, @selector (init));
|
||||
self = sendSuperclassMessage (self, @selector (init));
|
||||
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
{
|
||||
|
|
@ -97,7 +88,7 @@ struct AppDelegateClass
|
|||
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
ObjCClassBuilder::sendSuperclassMessage (self, @selector (dealloc));
|
||||
sendSuperclassMessage (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
static void unregisterObservers (id self, SEL)
|
||||
|
|
@ -265,8 +256,10 @@ struct AppDelegateHolder
|
|||
{
|
||||
public:
|
||||
AppDelegateHolder()
|
||||
: delegate ([AppDelegateClass::createInstance() init])
|
||||
{}
|
||||
{
|
||||
static AppDelegateClass cls;
|
||||
delegate = [cls.createInstance() init];
|
||||
}
|
||||
|
||||
~AppDelegateHolder()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,44 +25,41 @@
|
|||
|
||||
#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_;
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static id initWithFilters (id self, SEL, StringArray* filters)
|
||||
{
|
||||
self = sendSuperclassMessage (self, @selector (init));
|
||||
object_setInstanceVariable (self, "filters", filters);
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
delete filters;
|
||||
[super dealloc];
|
||||
}
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
delete getIvar<StringArray*> (self, "filters");
|
||||
sendSuperclassMessage (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
static BOOL shouldShowFilename (id self, SEL, id /*sender*/, NSString* filename)
|
||||
{
|
||||
StringArray* const filters = getIvar<StringArray*> (self, "filters");
|
||||
|
||||
- (BOOL) panel: (id) sender shouldShowFilename: (NSString*) filename
|
||||
{
|
||||
(void) sender;
|
||||
const File f (nsStringToJuce (filename));
|
||||
|
||||
for (int i = filters->size(); --i >= 0;)
|
||||
|
|
@ -87,19 +84,10 @@ using namespace juce;
|
|||
|
||||
return f.isDirectory()
|
||||
&& ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: filename];
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
namespace juce
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
class TemporaryMainMenuWithStandardCommands
|
||||
{
|
||||
public:
|
||||
|
|
@ -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()
|
||||
|
|
@ -233,6 +207,26 @@ public:
|
|||
[item setEnabled: false];
|
||||
}
|
||||
else if (iter.subMenu != nullptr)
|
||||
{
|
||||
if (iter.itemName.containsIgnoreCase ("recent"))
|
||||
{
|
||||
NSMenuItem* item = [menuToAddTo addItemWithTitle: NSLocalizedString (@"Open Recent", nil)
|
||||
action: nil
|
||||
keyEquivalent: @""];
|
||||
|
||||
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
|
||||
|
|
@ -246,6 +240,7 @@ public:
|
|||
[menuToAddTo setSubmenu: sub forItem: item];
|
||||
[sub release];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMenuItem* item = [menuToAddTo addItemWithTitle: text
|
||||
|
|
@ -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,29 +406,38 @@ private:
|
|||
|
||||
JUCE_DECLARE_NON_COPYABLE (AsyncCommandInvoker);
|
||||
};
|
||||
};
|
||||
|
||||
JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr;
|
||||
//==============================================================================
|
||||
struct JuceMenuCallbackClass : public ObjCClass <NSObject>
|
||||
{
|
||||
JuceMenuCallbackClass()
|
||||
: ObjCClass ("JUCEMainMenu_")
|
||||
{
|
||||
addIvar<JuceMainMenuHandler*> ("owner");
|
||||
|
||||
} // (juce namespace)
|
||||
addMethod (@selector (initWithOwner:), initWithOwner, "@@:^v");
|
||||
addMethod (@selector (menuItemInvoked:), menuItemInvoked, "v@:@");
|
||||
addMethod (@selector (menuNeedsUpdate:), menuNeedsUpdate, "v@:@");
|
||||
|
||||
//==============================================================================
|
||||
@implementation JuceMenuCallback
|
||||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
|
||||
addProtocol (@protocol (NSMenuDelegate));
|
||||
#endif
|
||||
|
||||
- (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_
|
||||
{
|
||||
[super init];
|
||||
owner = owner_;
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static id initWithOwner (id self, SEL, JuceMainMenuHandler* owner)
|
||||
{
|
||||
self = sendSuperclassMessage (self, @selector (init));
|
||||
object_setInstanceVariable (self, "owner", owner);
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[super dealloc];
|
||||
}
|
||||
static void menuItemInvoked (id self, SEL, id menu)
|
||||
{
|
||||
JuceMainMenuHandler* const owner = getIvar<JuceMainMenuHandler*> (self, "owner");
|
||||
|
||||
- (void) menuItemInvoked: (id) menu
|
||||
{
|
||||
NSMenuItem* item = (NSMenuItem*) menu;
|
||||
|
||||
if ([[item representedObject] isKindOfClass: [NSArray class]])
|
||||
|
|
@ -467,18 +471,17 @@ JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr;
|
|||
[((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue],
|
||||
(int) [((NSNumber*) [info objectAtIndex: 1]) intValue]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) menuNeedsUpdate: (NSMenu*) menu;
|
||||
{
|
||||
if (JuceMainMenuHandler::instance != nullptr)
|
||||
JuceMainMenuHandler::instance->updateMenus (menu);
|
||||
}
|
||||
static void menuNeedsUpdate (id self, SEL, NSMenu* menu)
|
||||
{
|
||||
if (instance != nullptr)
|
||||
instance->updateMenus (menu);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@end
|
||||
|
||||
namespace juce
|
||||
{
|
||||
JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
namespace MainMenuHelpers
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
}
|
||||
DownloadClickDetectorClass() : ObjCClass ("JUCEWebClickDetector_")
|
||||
{
|
||||
addIvar <WebBrowserComponent*> ("owner");
|
||||
|
||||
- (DownloadClickDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent;
|
||||
addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:),
|
||||
decidePolicyForNavigationAction, "v@:@@@@@");
|
||||
addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@");
|
||||
|
||||
- (void) webView: (WebView*) webView decidePolicyForNavigationAction: (NSDictionary*) actionInformation
|
||||
request: (NSURLRequest*) request
|
||||
frame: (WebFrame*) frame
|
||||
decisionListener: (id <WebPolicyDecisionListener>) listener;
|
||||
- (void) webView: (WebView*) webView didFinishLoadForFrame: (WebFrame*) frame;
|
||||
registerClass();
|
||||
}
|
||||
|
||||
@end
|
||||
static void setOwner (id self, WebBrowserComponent* owner)
|
||||
{
|
||||
object_setInstanceVariable (self, "owner", owner);
|
||||
}
|
||||
|
||||
@implementation DownloadClickDetector
|
||||
|
||||
- (DownloadClickDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent_
|
||||
{
|
||||
[super init];
|
||||
ownerComponent = ownerComponent_;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) webView: (WebView*) sender decidePolicyForNavigationAction: (NSDictionary*) actionInformation
|
||||
request: (NSURLRequest*) request
|
||||
frame: (WebFrame*) frame
|
||||
decisionListener: (id <WebPolicyDecisionListener>) listener
|
||||
{
|
||||
(void) sender; (void) request; (void) frame;
|
||||
private:
|
||||
static WebBrowserComponent* getOwner (id self)
|
||||
{
|
||||
return getIvar<WebBrowserComponent*> (self, "owner");
|
||||
}
|
||||
|
||||
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])))
|
||||
if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([url absoluteString])))
|
||||
[listener use];
|
||||
else
|
||||
[listener ignore];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) webView: (WebView*) sender didFinishLoadForFrame: (WebFrame*) frame
|
||||
{
|
||||
static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame)
|
||||
{
|
||||
if ([frame isEqual: [sender mainFrame]])
|
||||
{
|
||||
NSURL* url = [[[frame dataSource] request] URL];
|
||||
ownerComponent->pageFinishedLoading (nsStringToJuce ([url absoluteString]));
|
||||
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];
|
||||
}
|
||||
|
||||
- (bool) makeActive
|
||||
{
|
||||
const juce::ScopedLock sl (*contextLock);
|
||||
|
||||
if ([self openGLContext] == nil)
|
||||
if ([(NSOpenGLView*) self openGLContext] == nil)
|
||||
return false;
|
||||
|
||||
[[self openGLContext] makeCurrentContext];
|
||||
[[(NSOpenGLView*) self openGLContext] makeCurrentContext];
|
||||
|
||||
if (needsUpdate)
|
||||
if (getIvar<BOOL> (self, "needsUpdate"))
|
||||
{
|
||||
[super update];
|
||||
needsUpdate = false;
|
||||
sendSuperclassMessage (self, @selector (update));
|
||||
setNeedsUpdate (self, NO);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) _surfaceNeedsUpdate: (NSNotification*) notification
|
||||
{
|
||||
(void) notification;
|
||||
const juce::ScopedLock sl (*contextLock);
|
||||
needsUpdate = true;
|
||||
}
|
||||
private:
|
||||
static CriticalSection* getLock (id self)
|
||||
{
|
||||
return getIvar<CriticalSection*> (self, "lock");
|
||||
}
|
||||
|
||||
- (void) update
|
||||
{
|
||||
const juce::ScopedLock sl (*contextLock);
|
||||
needsUpdate = true;
|
||||
}
|
||||
static void setNeedsUpdate (id self, BOOL b)
|
||||
{
|
||||
object_setInstanceVariable (self, "needsUpdate", (void*) b);
|
||||
}
|
||||
|
||||
- (void) reshape
|
||||
{
|
||||
const juce::ScopedLock sl (*contextLock);
|
||||
needsUpdate = true;
|
||||
}
|
||||
static void setNeedsUpdateLocked (id self, BOOL b)
|
||||
{
|
||||
const ScopedLock sl (*getLock (self));
|
||||
setNeedsUpdate (self, b);
|
||||
}
|
||||
|
||||
- (void) rightMouseDown: (NSEvent*) ev { [[self superview] rightMouseDown: ev]; }
|
||||
- (void) rightMouseUp: (NSEvent*) ev { [[self superview] rightMouseUp: ev]; }
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
delete getLock (self);
|
||||
sendSuperclassMessage (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
@end
|
||||
static void surfaceNeedsUpdate (id self, SEL, NSNotification*) { setNeedsUpdateLocked (self, YES); }
|
||||
static void update (id self, SEL) { setNeedsUpdateLocked (self, YES); }
|
||||
static void reshape (id self, SEL) { setNeedsUpdateLocked (self, YES); }
|
||||
|
||||
static void rightMouseDown (id self, SEL, NSEvent* ev) { [[(NSOpenGLView*) self superview] rightMouseDown: ev]; }
|
||||
static void rightMouseUp (id self, SEL, NSEvent* ev) { [[(NSOpenGLView*) self superview] rightMouseUp: ev]; }
|
||||
};
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class OpenGLContext::NativeContext
|
||||
|
|
@ -137,8 +123,15 @@ public:
|
|||
|
||||
NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes: attribs];
|
||||
|
||||
view = [[ThreadSafeNSOpenGLView alloc] initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)
|
||||
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,59 +166,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
QTCaptureDevice* device;
|
||||
QTCaptureDeviceInput* input;
|
||||
QTCaptureDevice* audioDevice;
|
||||
QTCaptureDeviceInput* audioInput;
|
||||
QTCaptureSession* session;
|
||||
QTCaptureMovieFileOutput* fileOutput;
|
||||
QTCaptureDecompressedVideoOutput* imageOutput;
|
||||
QTCaptureCallbackDelegate* callbackDelegate;
|
||||
String openingError;
|
||||
|
||||
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)
|
||||
void captureBuffer (QTSampleBuffer* sampleBuffer)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
|
||||
internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame],
|
||||
CVPixelBufferGetWidth (videoFrame),
|
||||
CVPixelBufferGetHeight (videoFrame));
|
||||
}
|
||||
}
|
||||
|
||||
- (void) captureOutput: (QTCaptureFileOutput*) captureOutput
|
||||
didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer
|
||||
fromConnection: (QTCaptureConnection*) connection
|
||||
{
|
||||
const Time now (Time::getCurrentTime());
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
||||
|
|
@ -270,18 +191,78 @@ public:
|
|||
{
|
||||
averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
QTCaptureDevice* device;
|
||||
QTCaptureDeviceInput* input;
|
||||
QTCaptureDevice* audioDevice;
|
||||
QTCaptureDeviceInput* audioInput;
|
||||
QTCaptureSession* session;
|
||||
QTCaptureMovieFileOutput* fileOutput;
|
||||
QTCaptureDecompressedVideoOutput* imageOutput;
|
||||
NSObject* callbackDelegate;
|
||||
String openingError;
|
||||
int64 firstPresentationTime;
|
||||
int64 averageTimeOffset;
|
||||
|
||||
namespace juce
|
||||
{
|
||||
Array<CameraDevice::Listener*> listeners;
|
||||
CriticalSection listenerLock;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct DelegateClass : public ObjCClass <NSObject>
|
||||
{
|
||||
DelegateClass() : ObjCClass ("JUCEAppDelegate_")
|
||||
{
|
||||
addIvar<QTCameraDeviceInternal*> ("owner");
|
||||
|
||||
addMethod (@selector (captureOutput:didOutputVideoFrame:withSampleBuffer:fromConnection:),
|
||||
didOutputVideoFrame, "v@:@", @encode (CVImageBufferRef), "@@");
|
||||
addMethod (@selector (captureOutput:didOutputSampleBuffer:fromConnection:),
|
||||
didOutputVideoFrame, "v@:@@@");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setOwner (id self, QTCameraDeviceInternal* owner)
|
||||
{
|
||||
object_setInstanceVariable (self, "owner", owner);
|
||||
}
|
||||
|
||||
private:
|
||||
static QTCameraDeviceInternal* getOwner (id self)
|
||||
{
|
||||
return getIvar<QTCameraDeviceInternal*> (self, "owner");
|
||||
}
|
||||
|
||||
static void didOutputVideoFrame (id self, SEL, QTCaptureOutput* captureOutput,
|
||||
CVImageBufferRef videoFrame, QTSampleBuffer* sampleBuffer,
|
||||
QTCaptureConnection* connection)
|
||||
{
|
||||
QTCameraDeviceInternal* const internal = getOwner (self);
|
||||
|
||||
if (internal->listeners.size() > 0)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
|
||||
internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame],
|
||||
CVPixelBufferGetWidth (videoFrame),
|
||||
CVPixelBufferGetHeight (videoFrame));
|
||||
}
|
||||
}
|
||||
|
||||
static void didOutputSampleBuffer (id self, SEL, QTCaptureFileOutput*, QTSampleBuffer* sampleBuffer, QTCaptureConnection*)
|
||||
{
|
||||
getOwner (self)->captureBuffer (sampleBuffer);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
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
|
||||
struct NonInterceptingQTMovieViewClass : public ObjCClass <QTMovieView>
|
||||
{
|
||||
}
|
||||
NonInterceptingQTMovieViewClass() : ObjCClass ("JUCEQTMovieView_")
|
||||
{
|
||||
addMethod (@selector (hitTest:), hitTest, "@@:", @encode (NSPoint));
|
||||
addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse, "c@:@");
|
||||
|
||||
- (id) initWithFrame: (NSRect) frame;
|
||||
- (BOOL) acceptsFirstMouse: (NSEvent*) theEvent;
|
||||
- (NSView*) hitTest: (NSPoint) p;
|
||||
registerClass();
|
||||
}
|
||||
|
||||
@end
|
||||
private:
|
||||
static NSView* hitTest (id self, SEL, NSPoint point)
|
||||
{
|
||||
if (! [(QTMovieView*) self isControllerVisible])
|
||||
return nil;
|
||||
|
||||
@implementation NonInterceptingQTMovieView
|
||||
objc_super s = { self, [QTMovieView class] };
|
||||
return objc_msgSendSuper (&s, @selector (hitTest:), point);
|
||||
}
|
||||
|
||||
- (id) initWithFrame: (NSRect) frame
|
||||
{
|
||||
self = [super initWithFrame: frame];
|
||||
[self setNextResponder: [self superview]];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSView*) hitTest: (NSPoint) point
|
||||
{
|
||||
return [self isControllerVisible] ? [super hitTest: point] : nil;
|
||||
}
|
||||
|
||||
- (BOOL) acceptsFirstMouse: (NSEvent*) theEvent
|
||||
{
|
||||
static BOOL acceptsFirstMouse (id, SEL, NSEvent*)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace juce
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
#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];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue