aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-12-12 15:24:34 +0000
committerGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-12-12 15:24:34 +0000
commit2e8516354aacef064d01425808da06d2cdcb4791 (patch)
tree9da4758828930280d32f18d54ece7a249df742c7
parent9f64d056dd70f2f938ac6f5adb8e75b650dc2e1a (diff)
- GTMStackTrace works on 10.5+ (and iPhone) using NSThread to build the call stack.
- Added GTM_EXTERN that makes it easier to mix and match objc and objc++ code. - Added GTMHotKeysTextField for display and editing of hot key settings. - Added GTMCarbonEvent for dealing with Carbon Events and HotKeys in a ObjC like way. - Backported the Atomic Barrier Swap functions for Objective C back to Tiger. - Added a variety of new functions to GTMUnitTestingUtilities for checking if the screensaver is in the way, waiting on user events, and generating keystrokes. - If you are using any Carbon routines that log (DebugStr, AssertMacros.h) and use GTMUnitTestDevLog, the log routines now go through _GTMDevLog so that they can be caught in GTMUnitTestDevLog and verified like any _GTMDevLog calls you may make. For an example of this in action see GTMCarbonEventTest.m. - Added GTMFileSystemKQueue. It provides a simple wrapper for kqueuing something in the file system and tracking changes to it. - RunIPhoneUnitTest.sh now cleans up the user home directory and creates a documents directory within it, used when requesting a NSDocumentDirectory. - Added GTMNSFileManager+Carbon which contains routines for path <-> Alias conversion and path <-> FSRef conversion. - Added GTMNSArray+Merge for merging one array into another with or without a custom merging function, returning a new array with the merged contents.
-rw-r--r--AppKit/GTMCarbonEvent.h380
-rw-r--r--AppKit/GTMCarbonEvent.m709
-rw-r--r--AppKit/GTMCarbonEventTest.m360
-rw-r--r--AppKit/GTMGetURLHandler.m82
-rw-r--r--AppKit/GTMGetURLHandlerTest.m85
-rw-r--r--AppKit/GTMHotKeyTextField.h127
-rw-r--r--AppKit/GTMHotKeyTextField.m1009
-rw-r--r--AppKit/GTMHotKeyTextFieldTest.m204
-rw-r--r--AppKit/GTMLoginItems.h7
-rw-r--r--Foundation/GTMFileSystemKQueue.h78
-rw-r--r--Foundation/GTMFileSystemKQueue.m250
-rw-r--r--Foundation/GTMFileSystemKQueueTest.m394
-rw-r--r--Foundation/GTMHTTPFetcher.h2
-rw-r--r--Foundation/GTMHTTPServer.h2
-rw-r--r--Foundation/GTMLightweightProxy.m4
-rw-r--r--Foundation/GTMLightweightProxyTest.m24
-rw-r--r--Foundation/GTMNSAppleScript+HandlerTest.m9
-rw-r--r--Foundation/GTMNSArray+Merge.h47
-rw-r--r--Foundation/GTMNSArray+Merge.m112
-rw-r--r--Foundation/GTMNSArray+MergeTest.m219
-rw-r--r--Foundation/GTMNSFileManager+Carbon.h60
-rw-r--r--Foundation/GTMNSFileManager+Carbon.m99
-rw-r--r--Foundation/GTMNSFileManager+CarbonTest.m69
-rw-r--r--Foundation/GTMObjC2Runtime.h41
-rw-r--r--Foundation/GTMRegex.h2
-rw-r--r--Foundation/GTMStackTrace.h10
-rw-r--r--Foundation/GTMStackTrace.m108
-rw-r--r--Foundation/GTMStackTraceTest.m8
-rw-r--r--Foundation/GTMSystemVersion.h15
-rw-r--r--Foundation/GTMSystemVersion.m1
-rw-r--r--GTM.xcodeproj/project.pbxproj128
-rw-r--r--GTMDefines.h141
-rw-r--r--GTMiPhone.xcodeproj/project.pbxproj10
-rw-r--r--ReleaseNotes.txt37
-rw-r--r--UnitTesting/GTMIPhoneUnitTestDelegate.m10
-rw-r--r--UnitTesting/GTMNSObject+UnitTesting.h4
-rw-r--r--UnitTesting/GTMSenTestCase.h6
-rw-r--r--UnitTesting/GTMUIUnitTestingHarness/Info.plist43
-rw-r--r--UnitTesting/GTMUnitTestDevLog.m33
-rw-r--r--UnitTesting/GTMUnitTestingUtilities.h48
-rw-r--r--UnitTesting/GTMUnitTestingUtilities.m172
-rwxr-xr-xUnitTesting/RunIPhoneUnitTest.sh7
-rw-r--r--XcodeConfig/subconfig/Debug.xcconfig6
-rw-r--r--XcodeConfig/subconfig/General.xcconfig6
-rw-r--r--XcodeConfig/subconfig/iPhone20.xcconfig5
-rw-r--r--XcodeConfig/subconfig/iPhone21.xcconfig5
-rw-r--r--iPhone/GTMABAddressBook.h4
47 files changed, 4985 insertions, 197 deletions
diff --git a/AppKit/GTMCarbonEvent.h b/AppKit/GTMCarbonEvent.h
new file mode 100644
index 0000000..83482e9
--- /dev/null
+++ b/AppKit/GTMCarbonEvent.h
@@ -0,0 +1,380 @@
+//
+// GTMCarbonEvent.h
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Foundation/Foundation.h>
+#import <Carbon/Carbon.h>
+
+#import "GTMDefines.h"
+
+@class GTMCarbonEventHandler;
+
+// Objective C wrapper for a Carbon Event
+@interface GTMCarbonEvent : NSObject <NSCopying> {
+ @private
+ EventRef event_; //Event we are wrapping. STRONG
+}
+
+
+// Create an event of class |inClass| and kind |inKind|
+//
+// Returns:
+// Autoreleased GTMCarbonEvent
+//
++ (id)eventWithClass:(UInt32)inClass kind:(UInt32)kind;
+
+// Create an event based on |event|. Retains |event|.
+//
+// Returns:
+// Autoreleased GTMCarbonEvent
+//
++ (id)eventWithEvent:(EventRef)event;
+
+// Create an event based on the event currently being handled.
+//
+// Returns:
+// Autoreleased GTMCarbonEvent
+//
++ (id)currentEvent;
+
+// Create an event of class |inClass| and kind |inKind|
+//
+// Returns:
+// GTMCarbonEvent
+//
+- (id)initWithClass:(UInt32)inClass kind:(UInt32)kind;
+
+// Create an event based on |event|. Retains |event|.
+//
+// Returns:
+// GTMCarbonEvent
+//
+- (id)initWithEvent:(EventRef)event;
+
+// Get the event's class.
+//
+// Returns:
+// event class
+//
+- (UInt32)eventClass;
+
+// Get the event's kind.
+//
+// Returns:
+// event kind
+//
+- (UInt32)eventKind;
+
+// Set the event's time.
+//
+// Arguments:
+// time - the time you want associated with the event
+//
+- (void)setTime:(EventTime)eventTime;
+
+// Get the event's time.
+//
+// Returns:
+// the time associated with the event
+//
+- (EventTime)time;
+
+// Get the event's eventref for passing to other carbon functions.
+//
+// Returns:
+// the event ref associated with the event
+//
+- (EventRef)event;
+
+// Sets (or adds) a parameter to an event. Try not to use this function
+// directly. Look at the PARAM_TEMPLATE_DECL/DEFN macros below.
+//
+// Arguments:
+// name - the parameter name.
+// type - the parameter type.
+// size - the size of the data that |data| points to.
+// data - pointer to the data you want to set the parameter to.
+//
+- (void)setParameterNamed:(EventParamName)name
+ type:(EventParamType)type
+ size:(ByteCount)size
+ data:(const void *)data;
+
+
+// Gets a parameter from an event. Try not to use this function
+// directly. Look at the PARAM_TEMPLATE_DECL/DEFN macros below.
+//
+// Arguments:
+// name - the parameter name.
+// type - the parameter type.
+// size - the size of the data that |data| points to.
+// data - pointer to the buffer that you want to fill with your data.
+//
+// Returns:
+// YES is parameter is retrieved successfully. NO if parameter doesn't exist.
+//
+- (BOOL)getParameterNamed:(EventParamName)name
+ type:(EventParamType)type
+ size:(ByteCount)size
+ data:(void *)data;
+
+// Gets a the size of a parameter from an event.
+//
+// Arguments:
+// name - the parameter name.
+// type - the parameter type.
+//
+// Returns:
+// The size of the buffer required to hold the parameter. 0 if parameter
+// doesn't exist.
+//
+- (ByteCount)sizeOfParameterNamed:(EventParamName)name
+ type:(EventParamType)type;
+
+// Sends event to an event target with options
+//
+// Arguments:
+// target - target to send event to.
+// options - options to send event. See SendEventToEventTargetWithOptions
+// for details.
+//
+// Returns:
+// OSStatus value.
+//
+- (OSStatus)sendToTarget:(GTMCarbonEventHandler *)target
+ options:(OptionBits)options;
+
+// Post event to an event queue.
+//
+// Arguments:
+// queue - queue to post it to.
+// priority - priority to post it with
+//
+// Returns:
+// OSStatus value.
+//
+- (OSStatus)postToQueue:(EventQueueRef)queue priority:(EventPriority)priority;
+
+// Post event to current queue with standard priority.
+//
+- (void)postToCurrentQueue;
+
+// Post event to main queue with standard priority.
+//
+- (void)postToMainQueue;
+
+@end
+
+// Macros for defining simple set/get parameter methods for GTMCarbonEvent. See
+// the category GTMCarbonEvent (GTMCarbonEventGettersAndSetters) for an example
+// of their use. GTM_PARAM_TEMPLATE_DECL2/DEFN2 is for the case where the
+// parameter name is different than the parameter type (rare, but it does
+// occur...e.g. for a Rect, the name is typeQDRectangle, and the type is Rect,
+// so it would be GTM_PARAM_TEMPLATE_DECL2(QDRectangle, Rect) ). In most cases
+// you will just use GTM_PARAM_TEMPLATE_DECL/DEFN.
+#define GTM_PARAM_TEMPLATE_DECL2(paramName, paramType) \
+- (void)set##paramName##ParameterNamed:(EventParamName)name data:(paramType *)data; \
+- (BOOL)get##paramName##ParameterNamed:(EventParamName)name data:(paramType *)data;
+
+#define GTM_PARAM_TEMPLATE_DEFN2(paramName, paramType) \
+- (void)set##paramName##ParameterNamed:(EventParamName)name data:(paramType *)data { \
+[self setParameterNamed:name type:type##paramName size:sizeof(paramType) data:data]; \
+} \
+- (BOOL)get##paramName##ParameterNamed:(EventParamName)name data:(paramType *)data { \
+return [self getParameterNamed:name type:type##paramName size:sizeof(paramType) data:data]; \
+}
+
+#define GTM_PARAM_TEMPLATE_DECL(paramType) GTM_PARAM_TEMPLATE_DECL2(paramType, paramType)
+#define GTM_PARAM_TEMPLATE_DEFN(paramType) GTM_PARAM_TEMPLATE_DEFN2(paramType, paramType)
+
+
+// Category defining some basic types that we want to be able to easily set and
+// get from GTMCarbonEvents
+@interface GTMCarbonEvent (GTMCarbonEventGettersAndSetters)
+GTM_PARAM_TEMPLATE_DECL(UInt32)
+GTM_PARAM_TEMPLATE_DECL(EventHotKeyID)
+@end
+
+// Utility function for converting between modifier types
+// Arguments:
+// inCocoaModifiers - keyboard modifiers in carbon form
+// (NSCommandKeyMask etc)
+// Returns:
+// Carbon modifiers equivalent to |inCocoaModifiers| (cmdKey etc)
+GTM_EXTERN UInt32 GTMCocoaToCarbonKeyModifiers(NSUInteger inCocoaModifiers);
+
+// Utility function for converting between modifier types
+// Arguments:
+// inCarbonModifiers - keyboard modifiers in carbon form (cmdKey etc)
+// Returns:
+// cocoa modifiers equivalent to |inCocoaModifiers| (NSCommandKeyMask etc)
+GTM_EXTERN NSUInteger GTMCarbonToCocoaKeyModifiers(UInt32 inCarbonModifiers);
+
+// An "abstract" superclass for objects that handle events such as
+// menus, HIObjects, etc.
+//
+// Subclasses are expected to override the eventTarget and
+// handleEvent:handler: methods to customize them.
+@interface GTMCarbonEventHandler : NSObject {
+ @private
+ // handler we are wrapping
+ // lazily created in the eventHandler method
+ EventHandlerRef eventHandler_;
+ __weak id delegate_; // Our delegate
+ // Does our delegate respond to the gtm_eventHandler:receivedEvent:handler:
+ // selector? Cached for performance reasons.
+ BOOL delegateRespondsToHandleEvent_;
+}
+
+// Registers the event handler to listen for |events|.
+//
+// Arguments:
+// events - an array of EventTypeSpec. The events to register for.
+// count - the number of EventTypeSpecs in events.
+//
+- (void)registerForEvents:(const EventTypeSpec *)events count:(size_t)count;
+
+// Causes the event handler to stop listening for |events|.
+//
+// Arguments:
+// events - an array of EventTypeSpec. The events to register for.
+// count - the number of EventTypeSpecs in events.
+//
+- (void)unregisterForEvents:(const EventTypeSpec *)events count:(size_t)count;
+
+// To be overridden by subclasses to respond to events.
+//
+// All subclasses should call [super handleEvent:handler:] if they
+// don't handle the event themselves.
+//
+// Arguments:
+// event - the event to be handled
+// handler - the call ref in case you want to call CallNextEventHandler
+// in your method
+// Returns:
+// OSStatus - usually either noErr or eventNotHandledErr
+//
+- (OSStatus)handleEvent:(GTMCarbonEvent *)event
+ handler:(EventHandlerCallRef)handler;
+
+// To be overridden by subclasses to return the event target for the class.
+// GTMCarbonEventHandler's implementation returns NULL.
+//
+// Returns:
+// The event target ref.
+//
+- (EventTargetRef)eventTarget;
+
+// Gets the underlying EventHandlerRef for that this class wraps.
+//
+// Returns:
+// The EventHandlerRef this class wraps.
+//
+- (EventHandlerRef)eventHandler;
+
+// Gets the delegate for the handler
+//
+// Returns:
+// the delegate
+- (id)delegate;
+
+// Sets the delegate for the handler
+//
+// Arguments:
+// delegate - the delegate to set to
+- (void)setDelegate:(id)delegate;
+
+@end
+
+// Category for methods that a delegate of GTMCarbonEventHandlerDelegate may
+// want to implement.
+@interface NSObject (GTMCarbonEventHandlerDelegate)
+
+// If a delegate implements this method it gets called before every event
+// that the handler gets sent. If it returns anything but eventNotHandledErr,
+// the handlers handlerEvent:handler: method will not be called, and
+// the return value returned by the delegate will be returned back to the
+// carbon event dispatch system. This allows you to override any method
+// that a handler may implement.
+//
+// Arguments:
+// delegate - the delegate to set to
+//
+- (OSStatus)gtm_eventHandler:(GTMCarbonEventHandler *)sender
+ receivedEvent:(GTMCarbonEvent *)event
+ handler:(EventHandlerCallRef)handler;
+
+@end
+
+// A general OSType for use when setting properties on GTMCarbonEvent objects.
+// This is the "signature" as part of commandIDs, controlsIDs, and properties.
+// 'GooG'
+GTM_EXTERN const OSType kGTMCarbonFrameworkSignature;
+
+// An event handler class representing the event monitor event handler
+//
+// there is only one of these per application. This way you can put
+// event handlers directly on the dispatcher if necessary.
+@interface GTMCarbonEventMonitorHandler : GTMCarbonEventHandler
+// Accessor to get the GTMCarbonEventMonitorHandler singleton.
+//
+// Returns:
+// pointer to the GTMCarbonEventMonitorHandler singleton.
++ (GTMCarbonEventMonitorHandler *)sharedEventMonitorHandler;
+@end
+
+// An event handler class representing the toolbox dispatcher event handler
+//
+// there is only one of these per application. This way you can put
+// event handlers directly on the dispatcher if necessary.
+@interface GTMCarbonEventDispatcherHandler : GTMCarbonEventHandler {
+ @private
+ NSMutableDictionary *hotkeys_; // Collection of registered hotkeys
+}
+
+// Accessor to get the GTMCarbonEventDispatcherHandler singleton.
+//
+// Returns:
+// pointer to the GTMCarbonEventDispatcherHandler singleton.
++ (GTMCarbonEventDispatcherHandler *)sharedEventDispatcherHandler;
+
+// Registers a hotkey. When the hotkey is executed by the user, target will be
+// called with selector.
+// Arguments:
+// keyCode - the virtual keycode of the hotkey
+// cocoaModifiers - the modifiers that need to be used with |keyCode|. NB
+// that these are cocoa modifiers, so NSCommandKeyMask etc.
+// target - instance that will get |action| called when the hotkey fires
+// action - the method to call on |target| when the hotkey fires
+// onPress - is YES, the hotkey fires on the keydown (usual) otherwise
+// it fires on the key up.
+// Returns:
+// a EventHotKeyRef that you can use with other Carbon functions, or for
+// unregistering the hotkey. Note that all hotkeys are unregistered
+// automatically when an app quits. Will be NULL on failure.
+- (EventHotKeyRef)registerHotKey:(NSUInteger)keyCode
+ modifiers:(NSUInteger)cocoaModifiers
+ target:(id)target
+ action:(SEL)action
+ whenPressed:(BOOL)onPress;
+
+// Unregisters a hotkey previously registered with registerHotKey.
+// Arguments:
+// keyRef - the EventHotKeyRef to unregister
+- (void)unregisterHotKey:(EventHotKeyRef)keyRef;
+
+@end
diff --git a/AppKit/GTMCarbonEvent.m b/AppKit/GTMCarbonEvent.m
new file mode 100644
index 0000000..270a078
--- /dev/null
+++ b/AppKit/GTMCarbonEvent.m
@@ -0,0 +1,709 @@
+//
+// GTMCarbonEvent.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMCarbonEvent.h"
+#import "GTMObjectSingleton.h"
+#import "GTMDebugSelectorValidation.h"
+
+// Wrapper for all the info we need about a hotkey that we can store in a
+// Foundation storage class. We expecct selector to have this signature:
+// - (void)hitHotKey:sender;
+@interface GTMCarbonHotKey : NSObject {
+ @private
+ EventHotKeyID id_; // EventHotKeyID for this hotkey.
+ id target_; // Object we are going to call when the hotkey is hit
+ SEL selector_; // Selector we are going to call on target_
+ BOOL onKeyDown_; // Do we do it on key down or on key up?
+}
+
+// Create a HotKey record
+// Arguments:
+// keyID - id of the hotkey
+// target - object we are going to call when the hotkey is hit
+// action - selector we are going to call on target
+// whenPressed - do we do it on key down or key up?
+// Returns:
+// a hotkey record, or nil on failure
+- (id)initWithHotKey:(EventHotKeyID)keyID
+ target:(id)target
+ action:(SEL)selector
+ whenPressed:(BOOL)onKeyDown;
+
+// Does this record match key |keyID|
+// Arguments:
+// keyID - the id to match against
+// Returns:
+// Yes if we match this key id
+- (BOOL)matchesHotKeyID:(EventHotKeyID)keyID;
+
+// Make target perform selector
+// Returns:
+// Yes if handled
+- (BOOL)sendAction:(id)sender;
+
+// Do we do it on key down or key up?
+// Returns:
+// Yes if on keydown
+- (BOOL)onKeyDown;
+
+@end
+
+@implementation GTMCarbonEvent
+
+// Create an event of class |inClass| and kind |inKind|
+//
+// Returns:
+// Autoreleased GTMCarbonEvent
+//
++ (id)eventWithClass:(UInt32)inClass kind:(UInt32)kind {
+ return [[[[self class] alloc] initWithClass:inClass kind:kind] autorelease];
+}
+
+
+// Create an event based on |event|. Retains |event|.
+//
+// Returns:
+// Autoreleased GTMCarbonEvent
+//
++ (id)eventWithEvent:(EventRef)event {
+ return [[[[self class] alloc] initWithEvent:event] autorelease];
+}
+
+
+// Create an event based on the event currently being handled.
+//
+// Returns:
+// Autoreleased GTMCarbonEvent
+//
++ (id)currentEvent {
+ return [[self class] eventWithEvent:GetCurrentEvent()];
+}
+
+
+// Create an event of class |inClass| and kind |inKind|
+//
+// Returns:
+// GTMCarbonEvent
+//
+- (id)initWithClass:(UInt32)inClass kind:(UInt32)kind {
+ if ((self = [super init])) {
+ verify_noerr(CreateEvent(kCFAllocatorDefault, inClass, kind,
+ 0, kEventAttributeNone, &event_));
+ }
+ return self;
+}
+
+
+// Create an event based on |event|. Retains |event|.
+//
+// Returns:
+// GTMCarbonEvent
+//
+- (id)initWithEvent:(EventRef)event {
+ if ((self = [super init])) {
+ if (event) {
+ event_ = RetainEvent(event);
+ }
+ }
+ return self;
+}
+
+
+// This does a proper event copy, but ignores the |zone|. No way to do a copy
+// of an event into a specific zone.
+//
+// Arguments:
+// zone - the zone to copy to
+// Returns:
+// the copied event. nil on failure
+- (id)copyWithZone:(NSZone *)zone {
+ GTMCarbonEvent *carbonEvent = nil;
+ EventRef newEvent = CopyEvent([self event]);
+ if (newEvent) {
+ carbonEvent = [[[self class] allocWithZone:zone] initWithEvent:newEvent];
+ ReleaseEvent(newEvent);
+ }
+ return carbonEvent;
+}
+
+- (void)finalize {
+ if (event_) {
+ ReleaseEvent(event_);
+ event_ = NULL;
+ }
+ [super finalize];
+}
+
+// releases our retained event
+//
+- (void)dealloc {
+ if (event_) {
+ ReleaseEvent(event_);
+ event_ = NULL;
+ }
+ [super dealloc];
+}
+
+// description utliity for debugging
+//
+- (NSString *)description {
+ char cls[5];
+ UInt32 kind;
+
+ // Need everything bigendian if we are printing out the class as a "string"
+ *((UInt32 *)cls) = CFSwapInt32HostToBig([self eventClass]);
+ kind = [self eventKind];
+ cls[4] = 0;
+ return [NSString stringWithFormat:@"GTMCarbonEvent '%s' %d", cls, kind];
+}
+
+
+// Get the event's class.
+//
+// Returns:
+// event class
+//
+- (UInt32)eventClass {
+ return GetEventClass(event_);
+}
+
+
+// Get the event's kind.
+//
+// Returns:
+// event kind
+//
+- (UInt32)eventKind {
+ return GetEventKind(event_);
+}
+
+
+// Set the event's time.
+//
+// Arguments:
+// time - the time you want associated with the event
+//
+- (void)setTime:(EventTime)eventTime {
+ verify_noerr(SetEventTime(event_, eventTime));
+}
+
+
+// Get the event's time.
+//
+// Returns:
+// the time associated with the event
+//
+- (EventTime)time {
+ return GetEventTime(event_);
+}
+
+
+// Get the event's eventref for passing to other carbon functions.
+//
+// Returns:
+// the event ref associated with the event
+//
+- (EventRef)event {
+ return event_;
+}
+
+
+// Sends event to an event target with options
+//
+// Arguments:
+// target - target to send event to.
+// options - options to send event. See SendEventToEventTargetWithOptions
+// for details.
+//
+// Returns:
+// OSStatus value.
+//
+- (OSStatus)sendToTarget:(GTMCarbonEventHandler *)target
+ options:(OptionBits)options {
+ return SendEventToEventTargetWithOptions(event_,
+ [target eventTarget], options);
+}
+
+// Post event to an event queue.
+//
+// Arguments:
+// queue - queue to post it to.
+// priority - priority to post it with
+//
+// Returns:
+// OSStatus value.
+//
+- (OSStatus)postToQueue:(EventQueueRef)queue priority:(EventPriority)priority {
+ return PostEventToQueue(queue, event_, priority);
+}
+
+
+// Post event to current queue with standard priority.
+//
+- (void)postToCurrentQueue {
+ verify_noerr([self postToQueue:GetCurrentEventQueue()
+ priority:kEventPriorityStandard]);
+}
+
+
+// Post event to main queue with standard priority.
+//
+- (void)postToMainQueue {
+ verify_noerr([self postToQueue:GetMainEventQueue()
+ priority:kEventPriorityStandard]);
+}
+
+
+// Sets (or adds) a parameter to an event. Try not to use this function
+// directly. Look at the PARAM_TEMPLATE_DECL/DEFN macros below.
+//
+// Arguments:
+// name - the parameter name.
+// type - the parameter type.
+// size - the size of the data that |data| points to.
+// data - pointer to the data you want to set the parameter to.
+//
+- (void)setParameterNamed:(EventParamName)name
+ type:(EventParamType)type
+ size:(ByteCount)size
+ data:(const void *)data {
+ verify_noerr(SetEventParameter(event_, name, type, size, data));
+}
+
+
+// Gets a parameter from an event. Try not to use this function
+// directly. Look at the PARAM_TEMPLATE_DECL/DEFN macros below.
+//
+// Arguments:
+// name - the parameter name.
+// type - the parameter type.
+// size - the size of the data that |data| points to.
+// data - pointer to the buffer that you want to fill with your data.
+//
+// Returns:
+// YES is parameter is retrieved successfully. NO if parameter doesn't exist.
+//
+- (BOOL)getParameterNamed:(EventParamName)name
+ type:(EventParamType)type
+ size:(ByteCount)size
+ data:(void *)data {
+ OSStatus status = GetEventParameter(event_, name, type,
+ NULL, size, NULL, data);
+ return status == noErr;
+}
+
+
+// Gets a the size of a parameter from an event.
+//
+// Arguments:
+// name - the parameter name.
+// type - the parameter type.
+//
+// Returns:
+// The size of the buffer required to hold the parameter. 0 if parameter
+// doesn't exist.
+//
+- (ByteCount)sizeOfParameterNamed:(EventParamName)name
+ type:(EventParamType)type {
+ ByteCount size = 0;
+ verify_noerr(GetEventParameter(event_, name, type, NULL, 0, &size, NULL));
+ return size;
+}
+
+@end
+
+@implementation GTMCarbonEvent (GTMCarbonEventGettersAndSetters)
+GTM_PARAM_TEMPLATE_DEFN(UInt32)
+GTM_PARAM_TEMPLATE_DEFN(EventHotKeyID)
+@end
+
+UInt32 GTMCocoaToCarbonKeyModifiers(NSUInteger inCocoaModifiers) {
+ UInt32 carbModifiers = 0;
+ if (inCocoaModifiers & NSAlphaShiftKeyMask) carbModifiers |= alphaLock;
+ if (inCocoaModifiers & NSShiftKeyMask) carbModifiers |= shiftKey;
+ if (inCocoaModifiers & NSControlKeyMask) carbModifiers |= controlKey;
+ if (inCocoaModifiers & NSAlternateKeyMask) carbModifiers |= optionKey;
+ if (inCocoaModifiers & NSCommandKeyMask) carbModifiers |= cmdKey;
+ return carbModifiers;
+}
+
+NSUInteger GTMCarbonToCocoaKeyModifiers(UInt32 inCarbonModifiers) {
+ NSUInteger nsModifiers = 0;
+ if (inCarbonModifiers & alphaLock) nsModifiers |= NSAlphaShiftKeyMask;
+ if (inCarbonModifiers & shiftKey) nsModifiers |= NSShiftKeyMask;
+ if (inCarbonModifiers & controlKey) nsModifiers |= NSControlKeyMask;
+ if (inCarbonModifiers & optionKey) nsModifiers |= NSAlternateKeyMask;
+ if (inCarbonModifiers & cmdKey) nsModifiers |= NSCommandKeyMask;
+ return nsModifiers;
+}
+
+const OSType kGTMCarbonFrameworkSignature = 'GTM ';
+
+@implementation GTMCarbonEventHandler
+
+// Does our delegate respond to eventHandler:receivedEvent:handler:
+//
+// Returns:
+// YES if delegate responds to eventHandler:receivedEvent:handler:
+- (BOOL) delegateRespondsToHandleEvent {
+ return delegateRespondsToHandleEvent_;
+}
+
+// Registers the event handler to listen for |events|.
+//
+// Arguments:
+// events - an array of EventTypeSpec. The events to register for.
+// count - the number of EventTypeSpecs in events.
+//
+- (void)registerForEvents:(const EventTypeSpec *)events count:(size_t)count {
+ verify_noerr(AddEventTypesToHandler([self eventHandler], count, events));
+}
+
+// Causes the event handler to stop listening for |events|.
+//
+// Arguments:
+// events - an array of EventTypeSpec. The events to register for.
+// count - the number of EventTypeSpecs in events.
+//
+- (void)unregisterForEvents:(const EventTypeSpec *)events count:(size_t)count {
+ verify_noerr(RemoveEventTypesFromHandler([self eventHandler], count, events));
+}
+
+// To be overridden by subclasses to respond to events. All subclasses should
+// call [super handleEvent:handler:] if they don't handle the event themselves.
+//
+// Arguments:
+// event - the event to be handled
+// handler - the call ref in case you want to call CallNextEventHandler
+// in your method
+// Returns:
+// OSStatus - usually either noErr or eventNotHandledErr
+//
+- (OSStatus)handleEvent:(GTMCarbonEvent *)event
+ handler:(EventHandlerCallRef)handler {
+ OSStatus status = eventNotHandledErr;
+ require(event, CantUseParams);
+ require(handler, CantUseParams);
+ require([event event], CantUseParams);
+ status = CallNextEventHandler(handler, [event event]);
+CantUseParams:
+ return status;
+}
+
+// To be overridden by subclasses to return the event target for the class.
+// GTMCarbonEventHandler's implementation returns NULL.
+//
+// Returns:
+// The event target ref.
+//
+- (EventTargetRef)eventTarget {
+ // Defaults implementation needs to be overridden
+ return NULL;
+}
+
+// C callback for our registered EventHandlerUPP.
+//
+// Arguments:
+// inHandler - handler given to us from Carbon Event system
+// inEvent - the event we are handling
+// inUserData - refcon that we gave to the carbon event system. Is a
+// GTMCarbonEventHandler in disguise.
+// Returns:
+// status of event handler
+//
+static OSStatus EventHandler(EventHandlerCallRef inHandler,
+ EventRef inEvent,
+ void *inUserData) {
+ GTMCarbonEvent *event = [GTMCarbonEvent eventWithEvent:inEvent];
+ GTMCarbonEventHandler *handler= (GTMCarbonEventHandler *)inUserData;
+ check([handler isKindOfClass:[GTMCarbonEventHandler class]]);
+
+ // First check to see if our delegate cares about this event. If the delegate
+ // handles it (i.e responds to it and does not return eventNotHandledErr) we
+ // do not pass it on to default handling.
+ OSStatus status = eventNotHandledErr;
+ if ([handler delegateRespondsToHandleEvent]) {
+ status = [[handler delegate] gtm_eventHandler:handler
+ receivedEvent:event
+ handler:inHandler];
+ }
+ if (status == eventNotHandledErr) {
+ status = [handler handleEvent:event handler:inHandler];
+ }
+ return status;
+}
+
+// Gets the underlying EventHandlerRef for that this class wraps.
+//
+// Returns:
+// The EventHandlerRef this class wraps.
+//
+- (EventHandlerRef)eventHandler {
+ if (!eventHandler_) {
+ static EventHandlerUPP sHandlerProc = NULL;
+ if ( sHandlerProc == NULL ) {
+ sHandlerProc = NewEventHandlerUPP(EventHandler);
+ }
+ verify_noerr(InstallEventHandler([self eventTarget],
+ sHandlerProc, 0,
+ NULL, self, &eventHandler_));
+ }
+ return eventHandler_;
+}
+
+// Gets the delegate for the handler
+//
+// Returns:
+// the delegate
+- (id)delegate {
+ return delegate_;
+}
+
+// Sets the delegate for the handler and caches whether it responds to
+// the eventHandler:receivedEvent:handler: selector for performance purposes.
+//
+// Arguments:
+// delegate - the delegate for the handler
+- (void)setDelegate:(id)delegate {
+ delegate_ = delegate;
+ SEL selector = @selector(gtm_eventHandler:receivedEvent:handler:);
+ delegateRespondsToHandleEvent_ = [delegate respondsToSelector:selector];
+}
+
+@end
+
+@implementation GTMCarbonEventMonitorHandler
+
+GTMOBJECT_SINGLETON_BOILERPLATE(GTMCarbonEventMonitorHandler,
+ sharedEventMonitorHandler);
+
+- (EventTargetRef)eventTarget {
+ return GetEventMonitorTarget();
+}
+
+@end
+
+@implementation GTMCarbonEventDispatcherHandler
+
+GTMOBJECT_SINGLETON_BOILERPLATE(GTMCarbonEventDispatcherHandler,
+ sharedEventDispatcherHandler);
+
+// Register for the events we handle, and set up the dictionaries we need
+// to keep track of the hotkeys and commands that we handle.
+// Returns:
+// GTMCarbonApplication or nil on failure
+- (id)init {
+ if ((self = [super init])) {
+ static EventTypeSpec events[] = {
+ { kEventClassKeyboard, kEventHotKeyPressed },
+ { kEventClassKeyboard, kEventHotKeyReleased },
+ };
+ [self registerForEvents:events count:GetEventTypeCount(events)];
+ hotkeys_ = [[NSMutableDictionary alloc] initWithCapacity:0];
+ }
+ return self;
+}
+
+// COV_NF_START
+// Singleton, we never get released. Just here for completeness.
+- (void)dealloc {
+ [hotkeys_ release];
+ [super dealloc];
+}
+// COV_NF_END
+
+- (EventTargetRef)eventTarget {
+ return GetEventDispatcherTarget();
+}
+
+// Registers a hotkey. When the hotkey is executed by the user, target will be
+// called with selector.
+// Arguments:
+// keyCode - the virtual keycode of the hotkey
+// cocoaModifiers - the modifiers that need to be used with |keyCode|. NB
+// that these are cocoa modifiers, so NSCommandKeyMask etc.
+// target - instance that will get |action| called when the hotkey fires
+// action - the method to call on |target| when the hotkey fires
+// onKeyDown - is YES, the hotkey fires on the keydown (usual) otherwise
+// it fires on the key up.
+// Returns:
+// a EventHotKeyRef that you can use with other Carbon functions, or for
+// unregistering the hotkey. Note that all hotkeys are unregistered
+// automatically when an app quits. Will be NULL on failure.
+- (EventHotKeyRef)registerHotKey:(NSUInteger)keyCode
+ modifiers:(NSUInteger)cocoaModifiers
+ target:(id)target
+ action:(SEL)selector
+ whenPressed:(BOOL)onKeyDown {
+ static UInt32 sCurrentID = 0;
+
+ EventHotKeyRef theRef = NULL;
+ EventHotKeyID keyID;
+ keyID.signature = kGTMCarbonFrameworkSignature;
+ keyID.id = ++sCurrentID;
+ GTMCarbonHotKey *newKey = [[[GTMCarbonHotKey alloc] initWithHotKey:keyID
+ target:target
+ action:selector
+ whenPressed:onKeyDown]
+ autorelease];
+ require(newKey, CantCreateKey);
+ require_noerr(RegisterEventHotKey((UInt32)keyCode,
+ GTMCocoaToCarbonKeyModifiers(cocoaModifiers),
+ keyID,
+ [self eventTarget],
+ 0,
+ &theRef), CantRegisterHotkey);
+
+
+ [hotkeys_ setObject:newKey forKey:[NSValue valueWithPointer:theRef]];
+CantCreateKey:
+CantRegisterHotkey:
+ return theRef;
+}
+
+// Unregisters a hotkey previously registered with registerHotKey.
+// Arguments:
+// keyRef - the EventHotKeyRef to unregister
+- (void)unregisterHotKey:(EventHotKeyRef)keyRef {
+ NSValue *key = [NSValue valueWithPointer:keyRef];
+ check([hotkeys_ objectForKey:key] != nil);
+ [hotkeys_ removeObjectForKey:key];
+ verify_noerr(UnregisterEventHotKey(keyRef));
+}
+
+// A hotkey has been hit. See if it is one of ours, and if so fire it.
+// Arguments:
+// event - the hotkey even that was received
+// Returns:
+// Yes if handled.
+- (BOOL)handleHotKeyEvent:(GTMCarbonEvent *)event {
+ EventHotKeyID keyID;
+ BOOL handled = [event getEventHotKeyIDParameterNamed:kEventParamDirectObject
+ data:&keyID];
+ if (handled) {
+ NSEnumerator *dictEnumerator = [hotkeys_ objectEnumerator];
+ GTMCarbonHotKey *hotkey;
+ while ((hotkey = [dictEnumerator nextObject])) {
+ if ([hotkey matchesHotKeyID:keyID]) {
+ EventKind kind = [event eventKind];
+ BOOL onKeyDown = [hotkey onKeyDown];
+ if ((kind == kEventHotKeyPressed && onKeyDown) ||
+ (kind == kEventHotKeyReleased && !onKeyDown)) {
+ handled = [hotkey sendAction:self];
+ }
+ break;
+ }
+ }
+ }
+ return handled;
+}
+
+// Currently we handle hotkey and command events here. If we get one of them
+// we dispatch them off to the handlers above. Otherwise we just call up to
+// super.
+// Arguments:
+// event - the event to check
+// handler - the handler call ref
+// Returns:
+// OSStatus
+- (OSStatus)handleEvent:(GTMCarbonEvent *)event
+ handler:(EventHandlerCallRef)handler {
+ OSStatus theStatus = eventNotHandledErr;
+ if ([event eventClass] == kEventClassKeyboard) {
+ EventKind kind = [event eventKind];
+ if (kind == kEventHotKeyPressed || kind == kEventHotKeyReleased) {
+ theStatus = [self handleHotKeyEvent:event] ? noErr : eventNotHandledErr;
+ }
+ }
+ // We didn't handle it, maybe somebody upstairs will.
+ if (theStatus == eventNotHandledErr) {
+ theStatus = [super handleEvent:event handler:handler];
+ }
+ return theStatus;
+}
+
+@end
+
+@implementation GTMCarbonHotKey
+
+// Init a HotKey record. In debug version make sure that the selector we are
+// passed matches what we expect. (
+// Arguments:
+// keyID - id of the hotkey
+// target - object we are going to call when the hotkey is hit
+// action - selector we are going to call on target
+// whenPressed - do we do it on key down or key up?
+// Returns:
+// a hotkey record, or nil on failure
+- (id)initWithHotKey:(EventHotKeyID)keyID
+ target:(id)target
+ action:(SEL)selector
+ whenPressed:(BOOL)onKeyDown {
+ if ((self = [super init])) {
+ if(!target || !selector) {
+ [self release];
+ return nil;
+ }
+ id_ = keyID;
+ target_ = [target retain];
+ selector_ = selector;
+ onKeyDown_ = onKeyDown;
+ GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(target,
+ selector,
+ @encode(void),
+ @encode(id),
+ NULL);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [target_ release];
+ [super dealloc];
+}
+
+// Does this record match key |keyID|
+// Arguments:
+// keyID - the id to match against
+// Returns:
+// Yes if we match this key id
+- (BOOL)matchesHotKeyID:(EventHotKeyID)keyID {
+ return (id_.signature == keyID.signature) && (id_.id == keyID.id);
+}
+
+- (BOOL)sendAction:(id)sender {
+ BOOL handled = NO;
+ @try {
+ [target_ performSelector:selector_ withObject:sender];
+ handled = YES;
+ }
+ @catch (NSException * e) {
+ handled = NO;
+ _GTMDevLog(@"Exception fired in hotkey: %@ (%@)", [e name], [e reason]);
+ } // COV_NF_LINE
+ return handled;
+}
+
+- (BOOL)onKeyDown {
+ return onKeyDown_;
+}
+
+@end
+
+
+
diff --git a/AppKit/GTMCarbonEventTest.m b/AppKit/GTMCarbonEventTest.m
new file mode 100644
index 0000000..0615271
--- /dev/null
+++ b/AppKit/GTMCarbonEventTest.m
@@ -0,0 +1,360 @@
+//
+// GTMCarbonEventTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMSenTestCase.h"
+#import "GTMCarbonEvent.h"
+#import "GTMUnitTestingUtilities.h"
+#import "GTMUnitTestDevLog.h"
+
+@interface GTMCarbonEventTest : GTMTestCase {
+ @private
+ GTMCarbonEvent *event_;
+}
+@end
+
+@interface GTMCarbonEventHandlerTest : GTMTestCase {
+ @private
+ GTMCarbonEventHandler *handler_;
+}
+@end
+
+@interface GTMCarbonEventMonitorHandlerTest : GTMTestCase
+@end
+
+@interface GTMCarbonEventDispatcherHandlerTest : GTMTestCase {
+ @private
+ BOOL hotKeyHit_;
+}
+@end
+
+static const UInt32 kTestClass = 'foo ';
+static const UInt32 kTestKind = 'bar ';
+static const UInt32 kTestParameterName = 'baz ';
+static const UInt32 kTestBadParameterName = 'bom ';
+static const UInt32 kTestParameterValue = 'bam ';
+
+@implementation GTMCarbonEventTest
+- (void)setUp {
+ event_ = [[GTMCarbonEvent eventWithClass:kTestClass kind:kTestKind] retain];
+}
+
+- (void)tearDown {
+ [event_ release];
+}
+
+- (void)testCopy {
+ GTMCarbonEvent *event2 = [[event_ copy] autorelease];
+ STAssertNotNil(event2, nil);
+}
+
+- (void)testEventWithClassAndKind {
+ STAssertEquals([event_ eventClass], kTestClass, nil);
+ STAssertEquals([event_ eventKind], kTestKind, nil);
+}
+
+- (void)testEventWithEvent {
+ GTMCarbonEvent *event2 = [GTMCarbonEvent eventWithEvent:[event_ event]];
+ STAssertEquals([event2 event], [event_ event], nil);
+}
+
+- (void)testCurrentEvent {
+ EventRef eventRef = GetCurrentEvent();
+ GTMCarbonEvent *event = [GTMCarbonEvent currentEvent];
+ STAssertEquals([event event], eventRef, nil);
+}
+
+- (void)testEventClass {
+ [self testEventWithClassAndKind];
+}
+
+- (void)testEventKind {
+ [self testEventWithClassAndKind];
+}
+
+- (void)testSetTime {
+ EventTime eventTime = [event_ time];
+ STAssertNotEquals(eventTime, kEventDurationNoWait, nil);
+ STAssertNotEquals(eventTime, kEventDurationForever, nil);
+ [event_ setTime:kEventDurationForever];
+ EventTime testTime = [event_ time];
+ STAssertEquals(testTime, kEventDurationForever, nil);
+ [event_ setTime:eventTime];
+ STAssertEquals([event_ time], eventTime, nil);
+}
+
+- (void)testTime {
+ [self testSetTime];
+}
+
+- (void)testEvent {
+ [self testEventWithEvent];
+}
+
+- (void)testSetParameterNamed {
+ UInt32 theData = kTestParameterValue;
+ [event_ setUInt32ParameterNamed:kTestParameterName data:&theData];
+ theData = 0;
+ STAssertEquals([event_ sizeOfParameterNamed:kTestParameterName
+ type:typeUInt32],
+ sizeof(UInt32), nil);
+ STAssertTrue([event_ getUInt32ParameterNamed:kTestParameterName
+ data:&theData], nil);
+ STAssertEquals(theData, kTestParameterValue, nil);
+}
+
+- (void)testGetParameterNamed {
+ [self testSetParameterNamed];
+ UInt32 theData = kTestParameterValue;
+ STAssertFalse([event_ getUInt32ParameterNamed:kTestBadParameterName
+ data:&theData], nil);
+ STAssertFalse([event_ getUInt32ParameterNamed:kTestBadParameterName
+ data:NULL], nil);
+
+}
+
+- (void)testSizeOfParameterNamed {
+ [self testSetParameterNamed];
+}
+
+- (void)testHasParameterNamed {
+ [self testSetParameterNamed];
+}
+
+- (OSStatus)gtm_eventHandler:(GTMCarbonEventHandler *)sender
+ receivedEvent:(GTMCarbonEvent *)event
+ handler:(EventHandlerCallRef)handler {
+ OSStatus status = eventNotHandledErr;
+ if ([event eventClass] == kTestClass && [event eventKind] == kTestKind) {
+ status = noErr;
+ }
+ return status;
+}
+
+- (void)testSendToTarget {
+ EventTypeSpec types = { kTestClass, kTestKind };
+ GTMCarbonEventDispatcherHandler *handler
+ = [[GTMCarbonEventDispatcherHandler sharedEventDispatcherHandler]
+ autorelease];
+ [handler registerForEvents:&types count:1];
+ OSStatus status = [event_ sendToTarget:handler options:0];
+ STAssertErr(status, eventNotHandledErr, @"status: %ld", status);
+ [handler setDelegate:self];
+ status = [event_ sendToTarget:handler options:0];
+ STAssertNoErr(status, @"status: %ld", status);
+ [handler unregisterForEvents:&types count:1];
+}
+
+- (void)testPostToQueue {
+ EventQueueRef eventQueue = GetMainEventQueue();
+ [event_ postToMainQueue];
+ OSStatus status = [event_ postToQueue:eventQueue
+ priority:kEventPriorityStandard];
+ STAssertErr(status, eventAlreadyPostedErr, @"status: %ld", status);
+ EventTypeSpec types = { kTestClass, kTestKind };
+ status = FlushEventsMatchingListFromQueue(eventQueue, 1, &types);
+ STAssertNoErr(status, @"status: %ld", status);
+
+ eventQueue = GetCurrentEventQueue();
+ [event_ postToCurrentQueue];
+ status = [event_ postToQueue:eventQueue priority:kEventPriorityStandard];
+ STAssertErr(status, eventAlreadyPostedErr, @"status: %ld", status);
+ status = FlushEventsMatchingListFromQueue(eventQueue, 1, &types);
+ STAssertNoErr(status, @"status: %ld", status);
+}
+
+- (void)testPostToMainQueue {
+ [self testPostToQueue];
+}
+
+- (void)testPostToCurrentQueue {
+ STAssertEquals(GetCurrentEventQueue(), GetMainEventQueue(), nil);
+ [self testPostToMainQueue];
+}
+
+- (void)testDescription {
+ NSString *descString
+ = [NSString stringWithFormat:@"GTMCarbonEvent 'foo ' %d", kTestKind];
+ STAssertEqualObjects([event_ description], descString, nil);
+}
+@end
+
+@implementation GTMCarbonEventHandlerTest
+
+- (void)setUp {
+ handler_ = [[GTMCarbonEventHandler alloc] init];
+}
+
+- (void)tearDown {
+ [handler_ release];
+}
+
+- (void)testEventTarget {
+ STAssertNULL([handler_ eventTarget], nil);
+}
+
+- (void)testEventHandler {
+ [GTMUnitTestDevLog expectPattern:
+ @"DebugAssert: GoogleToolboxForMac: event CantUseParams .*"];
+ STAssertErr([handler_ handleEvent:nil handler:nil], eventNotHandledErr, nil);
+}
+
+- (void)testDelegate {
+ [handler_ setDelegate:self];
+ STAssertEqualObjects([handler_ delegate], self, nil);
+ [handler_ setDelegate:nil];
+ STAssertNil([handler_ delegate], nil);
+}
+
+
+- (void)testSetDelegate {
+ [self testDelegate];
+}
+
+@end
+
+@implementation GTMCarbonEventMonitorHandlerTest
+
+- (void)testEventHandler {
+ GTMCarbonEventMonitorHandler *monitor
+ = [GTMCarbonEventMonitorHandler sharedEventMonitorHandler];
+ STAssertEquals([monitor eventTarget], GetEventMonitorTarget(), nil);
+}
+
+@end
+
+@implementation GTMCarbonEventDispatcherHandlerTest
+
+- (void)testEventHandler {
+ GTMCarbonEventDispatcherHandler *dispatcher
+ = [GTMCarbonEventDispatcherHandler sharedEventDispatcherHandler];
+ STAssertEquals([dispatcher eventTarget], GetEventDispatcherTarget(), nil);
+}
+
+- (void)hitHotKey:(id)sender {
+ hotKeyHit_ = YES;
+ [NSApp stop:self];
+}
+
+- (void)hitExceptionalHotKey:(id)sender {
+ [NSException raise:@"foo" format:@"bar"];
+}
+
+- (void)testRegisterHotKeyModifiersTargetActionWhenPressed {
+
+ // This test can't be run if the screen saver is active because the security
+ // agent blocks us from sending events via remote operations
+ if (![GTMUnitTestingUtilities isScreenSaverActive]) {
+ GTMCarbonEventDispatcherHandler *dispatcher
+ = [GTMCarbonEventDispatcherHandler sharedEventDispatcherHandler];
+ STAssertNotNil(dispatcher, @"Unable to acquire singleton");
+ UInt32 keyMods = (NSShiftKeyMask | NSControlKeyMask
+ | NSAlternateKeyMask | NSCommandKeyMask);
+ EventHotKeyRef hotKey;
+ [GTMUnitTestDevLog expectPattern:@"DebugAssert: GoogleToolboxForMac: "
+ @"newKey CantCreateKey .*"];
+ STAssertNULL([dispatcher registerHotKey:0x5
+ modifiers:keyMods
+ target:nil
+ action:nil
+ whenPressed:YES],
+ @"Shouldn't have created hotkey");
+ STAssertThrowsSpecificNamed([dispatcher registerHotKey:0x5
+ modifiers:keyMods
+ target:self
+ action:@selector(badSelector:)
+ whenPressed:YES],
+ NSException, NSInternalInconsistencyException,
+ hotKey, @"Shouldn't have created hotkey");
+ hotKey = [dispatcher registerHotKey:0x5
+ modifiers:keyMods
+ target:self
+ action:@selector(hitHotKey:)
+ whenPressed:YES];
+ STAssertNotNULL(hotKey, @"Unable to create hotkey");
+
+ hotKeyHit_ = NO;
+
+ // Post the hotkey combo to the event queue. If everything is working
+ // correctly hitHotKey: should get called, and hotKeyHit_ will be set for
+ // us. We run the event loop for a set amount of time waiting for this to
+ // happen.
+ [GTMUnitTestingUtilities postTypeCharacterEvent:'g' modifiers:keyMods];
+ NSDate* future = [NSDate dateWithTimeIntervalSinceNow:1.0f];
+ [GTMUnitTestingUtilities runUntilDate:future];
+ STAssertTrue(hotKeyHit_, @"Hot key never got fired.");
+ [dispatcher unregisterHotKey:hotKey];
+ }
+}
+
+- (void)testRegisterHotKeyModifiersTargetActionWhenPressedException {
+
+ // This test can't be run if the screen saver is active because the security
+ // agent blocks us from sending events via remote operations
+ if (![GTMUnitTestingUtilities isScreenSaverActive]) {
+ GTMCarbonEventDispatcherHandler *dispatcher
+ = [GTMCarbonEventDispatcherHandler sharedEventDispatcherHandler];
+ STAssertNotNil(dispatcher, @"Unable to acquire singleton");
+ UInt32 keyMods = (NSShiftKeyMask | NSControlKeyMask
+ | NSAlternateKeyMask | NSCommandKeyMask);
+ EventHotKeyRef hotKey = [dispatcher registerHotKey:0x5
+ modifiers:keyMods
+ target:self
+ action:@selector(hitExceptionalHotKey:)
+ whenPressed:YES];
+ STAssertTrue(hotKey != nil, @"Unable to create hotkey");
+
+ // Post the hotkey combo to the event queue. If everything is working correctly
+ // hitHotKey: should get called, and hotKeyHit_ will be set for us.
+ // We run the event loop for a set amount of time waiting for this to happen.
+ [GTMUnitTestingUtilities postTypeCharacterEvent:'g' modifiers:keyMods];
+ NSDate* future = [NSDate dateWithTimeIntervalSinceNow:1.0f];
+ [GTMUnitTestDevLog expectString:@"Exception fired in hotkey: foo (bar)"];
+ [GTMUnitTestingUtilities runUntilDate:future];
+ [dispatcher unregisterHotKey:hotKey];
+ }
+}
+
+- (void)testKeyModifiers {
+ struct {
+ NSUInteger cocoaKey_;
+ UInt32 carbonKey_;
+ } keyMap[] = {
+ { NSAlphaShiftKeyMask, alphaLock},
+ { NSShiftKeyMask, shiftKey},
+ { NSControlKeyMask, controlKey},
+ { NSAlternateKeyMask, optionKey},
+ { NSCommandKeyMask, cmdKey},
+ };
+ size_t combos = pow(2, sizeof(keyMap) / sizeof(keyMap[0]));
+ for (size_t i = 0; i < combos; i++) {
+ NSUInteger cocoaMods = 0;
+ UInt32 carbonMods = 0;
+ for (size_t j = 0; j < 32 && j < sizeof(keyMap) / sizeof(keyMap[0]); j++) {
+ if (i & 1 << j) {
+ cocoaMods |= keyMap[j].cocoaKey_;
+ carbonMods |= keyMap[j].carbonKey_;
+ }
+ }
+ STAssertEquals(GTMCocoaToCarbonKeyModifiers(cocoaMods), carbonMods, nil);
+ STAssertEquals(GTMCarbonToCocoaKeyModifiers(carbonMods), cocoaMods, nil);
+ }
+}
+
+
+@end
+
diff --git a/AppKit/GTMGetURLHandler.m b/AppKit/GTMGetURLHandler.m
index a35dd95..66c02eb 100644
--- a/AppKit/GTMGetURLHandler.m
+++ b/AppKit/GTMGetURLHandler.m
@@ -93,7 +93,8 @@ withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
withDescription:(NSString*)string
toDescriptor:(NSAppleEventDescriptor *)desc;
+ (id)handlerForBundle:(NSBundle *)bundle;
-+ (void)appFinishedLaunchingHandler:(NSNotification*)notification;
++ (void)getUrl:(NSAppleEventDescriptor *)event
+withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
@end
@implementation GTMGetURLHandler
@@ -102,32 +103,28 @@ GTM_METHOD_CHECK(NSString, gtm_appleEventDescriptor);
+ (void)load {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
- [nc addObserver:self
- selector:@selector(appFinishedLaunchingHandler:)
- name:NSApplicationDidFinishLaunchingNotification
- object:nil];
+ NSAppleEventManager *man = [NSAppleEventManager sharedAppleEventManager];
+ [man setEventHandler:self
+ andSelector:@selector(getUrl:withReplyEvent:)
+ forEventClass:kInternetEventClass
+ andEventID:kAEGetURL];
[pool release];
}
-+ (void)appFinishedLaunchingHandler:(NSNotification*)notification {
- NSBundle *bundle = [NSBundle mainBundle];
- GTMGetURLHandler *handler = [GTMGetURLHandler handlerForBundle:bundle];
- if (handler) {
- [handler retain];
- GTMNSMakeUncollectable(handler);
- NSAppleEventManager *man = [NSAppleEventManager sharedAppleEventManager];
- [man setEventHandler:handler
- andSelector:@selector(getUrl:withReplyEvent:)
- forEventClass:kInternetEventClass
- andEventID:kAEGetURL];
- }
- NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
- [nc removeObserver:self
- name:NSApplicationDidFinishLaunchingNotification
- object:nil];
++ (void)getUrl:(NSAppleEventDescriptor *)event
+withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
+ static GTMGetURLHandler *sHandler = nil;
+ if (!sHandler) {
+ NSBundle *bundle = [NSBundle mainBundle];
+ sHandler = [GTMGetURLHandler handlerForBundle:bundle];
+ if (sHandler) {
+ [sHandler retain];
+ GTMNSMakeUncollectable(sHandler);
+ }
+ }
+ [sHandler getUrl:event withReplyEvent:replyEvent];
}
-
+
+ (id)handlerForBundle:(NSBundle *)bundle {
GTMGetURLHandler *handler = nil;
NSArray *urlTypes
@@ -135,8 +132,11 @@ GTM_METHOD_CHECK(NSString, gtm_appleEventDescriptor);
if (urlTypes) {
handler = [[[GTMGetURLHandler alloc] initWithTypes:urlTypes] autorelease];
} else {
+ // COV_NF_START
+ // Hard to test it if we don't have it.
_GTMDevLog(@"If you don't have CFBundleURLTypes in your plist, you may want"
@" to remove GTMGetURLHandler.m from your project");
+ // COV_NF_END
}
return handler;
}
@@ -146,7 +146,9 @@ GTM_METHOD_CHECK(NSString, gtm_appleEventDescriptor);
urlTypes_ = [urlTypes retain];
#if GTM_CHECK_BUNDLE_URL_CLASSES
// Some debug handling to check to make sure we can handle the
- // classes properly.
+ // classes properly. We check here instead of at init in case some of the
+ // handlers are being handled by plugins or other imported code that are
+ // loaded after we have been initialized.
NSEnumerator *enumerator = [urlTypes_ objectEnumerator];
NSDictionary *urlType;
while ((urlType = [enumerator nextObject])) {
@@ -156,14 +158,14 @@ GTM_METHOD_CHECK(NSString, gtm_appleEventDescriptor);
if (cls) {
if (![cls respondsToSelector:@selector(gtm_openURL:)]) {
_GTMDevLog(@"Class %@ for URL handler %@ "
- "(URL schemes: %@) doesn't respond to openURL:",
+ @"(URL schemes: %@) doesn't respond to openURL:",
className,
[urlType objectForKey:kGTMCFBundleURLNameKey],
[urlType objectForKey:kGTMCFBundleURLSchemesKey]);
}
} else {
_GTMDevLog(@"Unable to get class %@ for URL handler %@ "
- "(URL schemes: %@)",
+ @"(URL schemes: %@)",
className,
[urlType objectForKey:kGTMCFBundleURLNameKey],
[urlType objectForKey:kGTMCFBundleURLSchemesKey]);
@@ -173,7 +175,7 @@ GTM_METHOD_CHECK(NSString, gtm_appleEventDescriptor);
if ([role caseInsensitiveCompare:kGTMCFBundleViewerRole] == NSOrderedSame ||
[role caseInsensitiveCompare:kGTMCFBundleEditorRole] == NSOrderedSame) {
_GTMDevLog(@"Missing %@ for URL handler %@ "
- "(URL schemes: %@)",
+ @"(URL schemes: %@)",
kGTMBundleURLClassKey,
[urlType objectForKey:kGTMCFBundleURLNameKey],
[urlType objectForKey:kGTMCFBundleURLSchemesKey]);
@@ -185,10 +187,14 @@ GTM_METHOD_CHECK(NSString, gtm_appleEventDescriptor);
return self;
}
+// COV_NF_START
+// Singleton is never dealloc'd
- (void)dealloc {
[urlTypes_ release];
[super dealloc];
}
+// COV_NF_END
+
- (NSURL*)extractURLFromEvent:(NSAppleEventDescriptor*)event
withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
@@ -197,9 +203,12 @@ GTM_METHOD_CHECK(NSString, gtm_appleEventDescriptor);
NSString *urlstring = [desc stringValue];
NSURL *url = [NSURL URLWithString:urlstring];
if (!url) {
+ // COV_NF_START
+ // Can't convince the OS to give me a bad URL
[self addError:errAECoercionFail
withDescription:@"Unable to extract url from key direct object."
toDescriptor:replyEvent];
+ // COV_NF_END
}
return url;
}
@@ -227,25 +236,40 @@ GTM_METHOD_CHECK(NSString, gtm_appleEventDescriptor);
if (!cls) {
NSString *errorString
= [NSString stringWithFormat:@"Unable to instantiate class for "
- "%@:%@ for scheme:%@.",
+ @"%@:%@ for scheme:%@.",
kGTMBundleURLClassKey, class, typeScheme];
[self addError:errAECorruptData
withDescription:errorString
toDescriptor:replyEvent];
+ } else {
+ if (![cls respondsToSelector:@selector(gtm_openURL:)]) {
+ NSString *errorString
+ = [NSString stringWithFormat:@"Class %@:%@ for scheme:%@ does not"
+ @"respond to gtm_openURL:",
+ kGTMBundleURLClassKey, class, typeScheme];
+ [self addError:errAECorruptData
+ withDescription:errorString
+ toDescriptor:replyEvent];
+ cls = Nil;
+ }
}
} else {
+ // COV_NF_START
+ // Don't know how to force an URL that we don't respond to upon ourselves.
NSString *errorString
= [NSString stringWithFormat:@"Unable to find handler for scheme %@.",
scheme];
[self addError:errAECorruptData
withDescription:errorString
toDescriptor:replyEvent];
+ // COV_NF_END
+
}
return cls;
}
- (void)getUrl:(NSAppleEventDescriptor *)event
-withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
+withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
NSURL *url = [self extractURLFromEvent:event withReplyEvent:replyEvent];
if (!url) {
return;
diff --git a/AppKit/GTMGetURLHandlerTest.m b/AppKit/GTMGetURLHandlerTest.m
new file mode 100644
index 0000000..98d3c89
--- /dev/null
+++ b/AppKit/GTMGetURLHandlerTest.m
@@ -0,0 +1,85 @@
+// GTMGetURLHandlerTest.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMSenTestCase.h"
+#import "GTMUnitTestingUtilities.h"
+#import "GTMUnitTestDevLog.h"
+
+static BOOL sURLHandlerWasHit;
+
+@interface GTMGetURLHandlerBadClassWarning : NSObject
+@end
+
+@implementation GTMGetURLHandlerBadClassWarning : NSObject
+@end
+
+@interface GTMGetURLHandlerTest : GTMTestCase
+@end
+
+@implementation GTMGetURLHandlerTest
+- (BOOL)openURLString:(NSString *)url {
+ ProcessSerialNumber psn = { 0, kCurrentProcess };
+ NSAppleEventDescriptor *currentProcess
+ = [NSAppleEventDescriptor descriptorWithDescriptorType:typeProcessSerialNumber
+ bytes:&psn
+ length:sizeof(ProcessSerialNumber)];
+ NSAppleEventDescriptor *event
+ = [NSAppleEventDescriptor appleEventWithEventClass:kInternetEventClass
+ eventID:kAEGetURL
+ targetDescriptor:currentProcess
+ returnID:kAutoGenerateReturnID
+ transactionID:kAnyTransactionID];
+ NSAppleEventDescriptor *keyDesc
+ = [NSAppleEventDescriptor descriptorWithString:url];
+ [event setParamDescriptor:keyDesc forKeyword:keyDirectObject];
+ AppleEvent replyEvent = { typeNull, NULL };
+ OSStatus err = AESendMessage([event aeDesc], &replyEvent, kAEWaitReply, 60);
+ return err == noErr ? YES : NO;
+}
+
++ (BOOL)gtm_openURL:(NSURL*)url {
+ sURLHandlerWasHit = !sURLHandlerWasHit;
+ return sURLHandlerWasHit;
+}
+
+- (void)testURLCall {
+ sURLHandlerWasHit = NO;
+
+ [GTMUnitTestDevLog expectPattern:@"Class GTMGetURLHandlerBadClassWarning "
+ @"for URL handler GTMGetURLHandlerBadClassURL .*"];
+ [GTMUnitTestDevLog expectPattern:@"Unable to get class "
+ @"GTMGetURLHandlerMissingClassWarning for URL handler "
+ @"GTMGetURLHandlerMissingClassURL .*"];
+ [GTMUnitTestDevLog expectPattern:@"Missing GTMBundleURLClass for URL handler "
+ @"GTMGetURLHandlerMissingHandlerURL .*"];
+ STAssertTrue([self openURLString:@"gtmgeturlhandlertest://test.foo"], nil);
+ STAssertTrue(sURLHandlerWasHit, @"URL handler not called");
+
+ STAssertTrue([self openURLString:@"gtmgeturlhandlertest://test.foo"], nil);
+ STAssertFalse(sURLHandlerWasHit, @"URL handler not called 2");
+
+ // test the two URL schemes with bad entries
+ STAssertTrue([self openURLString:@"gtmgeturlhandlerbadclasstest://test.foo"],
+ nil);
+
+ STAssertTrue([self openURLString:@"gtmgeturlhandlermissingclasstest://test.foo"],
+ nil);
+
+ STAssertTrue([self openURLString:@"gtmgeturlhandlermissinghandlerurl://test.foo"],
+ nil);
+}
+@end
diff --git a/AppKit/GTMHotKeyTextField.h b/AppKit/GTMHotKeyTextField.h
new file mode 100644
index 0000000..4177f49
--- /dev/null
+++ b/AppKit/GTMHotKeyTextField.h
@@ -0,0 +1,127 @@
+//
+// GTMHotKeyTextField.h
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+// Text field for capturing hot key entry. This is intended to be similar to the
+// Apple key editor in their Keyboard pref pane.
+
+// NOTE: There are strings that need to be localized to use this field. See the
+// code in stringForKeycode the the keys. The keys are all the English versions
+// so you'll get reasonable things if you don't have a strings file.
+
+#import <Cocoa/Cocoa.h>
+#import "GTMDefines.h"
+
+// Dictionary key for hot key configuration information modifier flags.
+// NSNumber of a unsigned int. Modifier flags are stored using Cocoa constants
+// (same as NSEvent) you will need to translate them to Carbon modifier flags
+// for use with RegisterEventHotKey()
+#define kGTMHotKeyModifierFlagsKey @"Modifiers"
+
+// Dictionary key for hot key configuration of virtual key code. NSNumber of
+// unsigned int. For double-modifier hotkeys (see below) this value is ignored.
+#define kGTMHotKeyKeyCodeKey @"KeyCode"
+
+// Dictionary key for hot key configuration of double-modifier tap. NSNumber
+// BOOL value. Double-tap modifier keys cannot be used with
+// RegisterEventHotKey(), you must implement your own Carbon event handler.
+#define kGTMHotKeyDoubledModifierKey @"DoubleModifier"
+
+// Custom text field class used for hot key entry. In order to use this class
+// you will need to configure your window's delegate, to return the related
+// field editor.
+//
+// Sample window delegate method:
+//
+// -(id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)anObject {
+//
+// if ([anObject isKindOfClass:[GTMHotKeyTextField class]]) {
+// return [GTMHotKeyFieldEditor sharedHotKeyFieldEditor];
+// } else {
+// return nil; // Window will use the AppKit shared editor
+// }
+//
+// }
+//
+//
+// Other notes:
+// - Though you are free to implement control:textShouldEndEditing: in your
+// delegate its return is always ignored. The field always accepts only
+// one hotkey keystroke before editing ends.
+// - The "value" binding of this control is to the dictionary describing the
+// hotkey. At this time binding options are not supported.
+// - The field does not attempt to consume all hotkeys. Hotkeys which are
+// already bound in Apple prefs or other applications will have their
+// normal effect.
+//
+
+@interface GTMHotKeyTextField : NSTextField {
+ @private
+ NSDictionary *hotKeyDict_;
+ // Bindings
+ NSObject *boundObject_;
+ NSString *boundKeyPath_;
+}
+
+// Set/Get the hot key dictionary for the field. See above for key names.
+- (void)setHotKeyValue:(NSDictionary *)hotKey;
+- (NSDictionary *)hotKeyValue;
+
+// Convert Cocoa modifier flags (-[NSEvent modifierFlags]) into a string for
+// display. Modifiers are represented in the string in the same order they would
+// appear in the Menu Manager.
+//
+// Args:
+// flags: -[NSEvent modifierFlags]
+//
+// Returns:
+// Autoreleased NSString
+//
++ (NSString *)stringForModifierFlags:(unsigned int)flags;
+
+// Convert a keycode into a string that would result from typing the keycode in
+// the current keyboard layout. This may be one or more characters.
+//
+// Args:
+// keycode: Virtual keycode such as one obtained from NSEvent
+// useGlyph: In many cases the glyphs are confusing, and a string is clearer.
+// However, if you want to display in a menu item, use must
+// have a glyph. Set useGlyph to FALSE to get localized strings
+// which are better for UI display in places other than menus.
+// bundle: Localization bundle to use for localizable key names
+//
+// Returns:
+// Autoreleased NSString
+//
++ (NSString *)stringForKeycode:(UInt16)keycode
+ useGlyph:(BOOL)useGlyph
+ resourceBundle:(NSBundle *)bundle;
+
+@end
+
+// Custom field editor for use with hotkey entry fields (GTMHotKeyTextField).
+// See the GTMHotKeyTextField for instructions on using from the window
+// delegate.
+@interface GTMHotKeyFieldEditor : NSTextView {
+ @private
+ NSDictionary *hotKeyDict_; // strong
+}
+
+// Get the shared field editor for all hot key fields
++ (GTMHotKeyFieldEditor *)sharedHotKeyFieldEditor;
+
+@end
diff --git a/AppKit/GTMHotKeyTextField.m b/AppKit/GTMHotKeyTextField.m
new file mode 100644
index 0000000..3a604be
--- /dev/null
+++ b/AppKit/GTMHotKeyTextField.m
@@ -0,0 +1,1009 @@
+// GTMHotKeyTextField.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMHotKeyTextField.h"
+
+#import <Carbon/Carbon.h>
+#import "GTMSystemVersion.h"
+#import "GTMObjectSingleton.h"
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
+typedef struct __TISInputSource* TISInputSourceRef;
+static TISInputSourceRef(*GTM_TISCopyCurrentKeyboardLayoutInputSource)(void) = NULL;
+static void * (*GTM_TISGetInputSourceProperty)(TISInputSourceRef inputSource,
+ CFStringRef propertyKey) = NULL;
+static CFStringRef kGTM_TISPropertyUnicodeKeyLayoutData = NULL;
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
+
+
+@interface GTMHotKeyTextField (PrivateMethods)
+- (void)setupBinding:(id)bound withPath:(NSString *)path;
+- (void)updateDisplayedPrettyString;
++ (BOOL)isValidHotKey:(NSDictionary *)hotKey;
++ (NSString *)displayStringForHotKey:(NSDictionary *)hotKey;
+@end
+
+@interface GTMHotKeyFieldEditor (PrivateMethods)
+- (NSDictionary *)hotKeyDictionary;
+- (void)setHotKeyDictionary:(NSDictionary *)hotKey;
+- (BOOL)shouldBypassEvent:(NSEvent *)theEvent;
+- (void)processEventToHotKeyAndString:(NSEvent *)theEvent;
+- (void)windowResigned:(NSNotification *)notification;
+- (NSDictionary *)hotKeyDictionaryForEvent:(NSEvent *)event;
+@end
+
+@implementation GTMHotKeyTextField
+
+- (void)finalize {
+ if (boundObject_ && boundKeyPath_) {
+ [boundObject_ removeObserver:self forKeyPath:boundKeyPath_];
+ }
+ [super finalize];
+}
+
+- (void)dealloc {
+
+ if (boundObject_ && boundKeyPath_) {
+ [boundObject_ removeObserver:self forKeyPath:boundKeyPath_];
+ }
+ [boundObject_ release];
+ [boundKeyPath_ release];
+ [hotKeyDict_ release];
+ [super dealloc];
+
+}
+
+#pragma mark Bindings
+
+
+- (void)bind:(NSString *)binding toObject:(id)observableController
+ withKeyPath:(NSString *)keyPath
+ options:(NSDictionary *)options {
+
+ if ([binding isEqualToString:NSValueBinding]) {
+ // Update to our new binding
+ [self setupBinding:observableController withPath:keyPath];
+ // TODO: Should deal with the bind options
+ }
+ [super bind:binding
+ toObject:observableController
+ withKeyPath:keyPath
+ options:options];
+
+}
+
+- (void)unbind:(NSString *)binding {
+
+ // Clean up value on unbind
+ if ([binding isEqualToString:NSValueBinding]) {
+ if (boundObject_ && boundKeyPath_) {
+ [boundObject_ removeObserver:self forKeyPath:boundKeyPath_];
+ }
+ [boundObject_ release];
+ boundObject_ = nil;
+ [boundKeyPath_ release];
+ boundKeyPath_ = nil;
+ }
+ [super unbind:binding];
+
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
+ change:(NSDictionary *)change context:(void *)context {
+
+ if ((object == boundObject_) && [boundKeyPath_ isEqualToString:keyPath]) {
+ // Our binding has changed, update
+ id changedValue = [change objectForKey:NSKeyValueChangeNewKey];
+ // NSUserDefaultsController does not appear to pass on the new object and,
+ // perhaps other controllers may not, so if we get a nil or NSNull back
+ // here let's directly retrieve the hotKeyDict_ from the object.
+ if (!changedValue || changedValue == [NSNull null]) {
+ changedValue = [object valueForKeyPath:keyPath];
+ }
+ [hotKeyDict_ autorelease];
+ hotKeyDict_ = [changedValue copy];
+ [self updateDisplayedPrettyString];
+ }
+
+}
+
+// Private convenience method for attaching to a new binding
+- (void)setupBinding:(id)bound withPath:(NSString *)path {
+
+ // Release previous
+ if (boundObject_ && boundKeyPath_) {
+ [boundObject_ removeObserver:self forKeyPath:boundKeyPath_];
+ }
+ [boundObject_ release];
+ [boundKeyPath_ release];
+ // Set new
+ boundObject_ = [bound retain];
+ boundKeyPath_ = [path copy];
+ // Make ourself an observer
+ [boundObject_ addObserver:self
+ forKeyPath:boundKeyPath_
+ options:NSKeyValueObservingOptionNew
+ context:nil];
+ // Pull in any current value
+ [hotKeyDict_ autorelease];
+ hotKeyDict_ = [[boundObject_ valueForKeyPath:boundKeyPath_] copy];
+ // Update the display string
+ [self updateDisplayedPrettyString];
+
+}
+
+#pragma mark Defeating NSControl
+
+- (double)doubleValue {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return 0.0;
+
+}
+
+- (void)setDoubleValue:(double)value {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return;
+
+}
+
+- (float)floatValue {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return 0.0f;
+
+}
+
+- (void)setFloatValue:(float)value {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return;
+
+}
+
+- (int)intValue {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return 0;
+
+}
+
+- (void)setIntValue:(int)value {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return;
+
+}
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
+- (int)integerValue {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return 0;
+
+}
+
+- (void)setIntegerValue:(NSInteger)value {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return;
+
+}
+
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
+- (id)objectValue {
+
+ return [self hotKeyValue];
+
+}
+
+- (void)setObjectValue:(id)object {
+
+ [self setHotKeyValue:object];
+
+}
+
+- (NSString *)stringValue {
+
+ return [[self class] displayStringForHotKey:hotKeyDict_];
+
+}
+
+- (void)setStringValue:(NSString *)string {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields want dictionaries, not strings.");
+ return;
+
+}
+
+- (NSAttributedString *)attributedStringValue {
+
+ NSString *prettyString = [self stringValue];
+ if (!prettyString) return nil;
+ return [[[NSAttributedString alloc] initWithString:prettyString] autorelease];
+
+}
+
+- (void)setAttributedStringValue:(NSAttributedString *)string {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields want dictionaries, not strings.");
+ return;
+
+}
+
+- (void)takeDoubleValueFrom:(id)sender {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return;
+
+}
+
+- (void)takeFloatValueFrom:(id)sender {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return;
+
+}
+
+- (void)takeIntValueFrom:(id)sender {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't take numbers.");
+ return;
+
+}
+
+- (void)takeObjectValueFrom:(id)sender {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO,
+ @"Hot key fields want dictionaries via bindings, not from controls.");
+ return;
+
+}
+
+- (void)takeStringValueFrom:(id)sender {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields want dictionaries, not strings.");
+ return;
+
+}
+
+- (id)formatter {
+
+ return nil;
+
+}
+
+- (void)setFormatter:(NSFormatter *)newFormatter {
+
+ // Defeating NSControl
+ _GTMDevAssert(NO, @"Hot key fields don't accept formatters.");
+ return;
+
+}
+
+#pragma mark Hot Key Support
+
++ (BOOL)isValidHotKey:(NSDictionary *)hotKeyDict {
+ if (!hotKeyDict ||
+ ![hotKeyDict isKindOfClass:[NSDictionary class]] ||
+ ![hotKeyDict objectForKey:kGTMHotKeyModifierFlagsKey] ||
+ ![hotKeyDict objectForKey:kGTMHotKeyKeyCodeKey] ||
+ ![hotKeyDict objectForKey:kGTMHotKeyDoubledModifierKey]) {
+ return NO;
+ }
+ return YES;
+}
+
+- (void)setHotKeyValue:(NSDictionary *)hotKey {
+
+ // Sanity only if set, nil is OK
+ if (hotKey && ![[self class] isValidHotKey:hotKey]) {
+ return;
+ }
+
+ // If we are bound we want to round trip through that interface
+ if (boundObject_ && boundKeyPath_) {
+ // If the change is accepted this will call us back as an observer
+ [boundObject_ setValue:hotKey forKeyPath:boundKeyPath_];
+ return;
+ }
+
+ // Otherwise we directly update ourself
+ [hotKeyDict_ autorelease];
+ hotKeyDict_ = [hotKey copy];
+ [self updateDisplayedPrettyString];
+
+}
+
+- (NSDictionary *)hotKeyValue {
+
+ return hotKeyDict_;
+
+}
+
+// Private method to update the displayed text of the field with the
+// user-readable representation.
+- (void)updateDisplayedPrettyString {
+
+ // Basic validation
+ if (![[self class] isValidHotKey:hotKeyDict_]) {
+ [super setStringValue:@""];
+ return;
+ }
+
+ // Pretty string
+ NSString *prettyString = [[self class] displayStringForHotKey:hotKeyDict_];
+ if (!prettyString) {
+ prettyString = @"";
+ }
+ [super setStringValue:prettyString];
+
+}
+
++ (NSString *)displayStringForHotKey:(NSDictionary *)hotKeyDict {
+
+ if (!hotKeyDict) return nil;
+
+ NSBundle *bundle = [NSBundle bundleForClass:[self class]];
+
+ // Modifiers
+ unsigned int flags
+ = [[hotKeyDict objectForKey:kGTMHotKeyModifierFlagsKey] unsignedIntValue];
+ NSString *mods = [GTMHotKeyTextField stringForModifierFlags:flags];
+ if (!mods || ![mods length]) return nil;
+ // Handle double modifier case
+ if ([[hotKeyDict objectForKey:kGTMHotKeyDoubledModifierKey] boolValue]) {
+ return [NSString stringWithFormat:@"%@ + %@", mods, mods];
+ }
+ // Keycode
+ unsigned int keycode
+ = [[hotKeyDict objectForKey:kGTMHotKeyKeyCodeKey] unsignedIntValue];
+ NSString *keystroke = [GTMHotKeyTextField stringForKeycode:keycode
+ useGlyph:NO
+ resourceBundle:bundle];
+ if (!keystroke || ![keystroke length]) return nil;
+ return [NSString stringWithFormat:@"%@%@", mods, keystroke];
+
+}
+
+
+#pragma mark Field Editor Callbacks
+
+- (BOOL)textShouldBeginEditing:(GTMHotKeyFieldEditor *)fieldEditor {
+
+ // Sanity
+ if (![fieldEditor isKindOfClass:[GTMHotKeyFieldEditor class]]) {
+ _GTMDevLog(@"Field editor not appropriate for field, check window delegate");
+ return NO;
+ }
+
+ // We don't call super from here, because we are defeating default behavior
+ // as a result we have to call the delegate ourself.
+ id myDelegate = [self delegate];
+ SEL selector = @selector(control:textShouldBeginEditing:);
+ if ([myDelegate respondsToSelector:selector]) {
+ if (![myDelegate control:self textShouldBeginEditing:fieldEditor]) return NO;
+ }
+
+ // Update the field editor internal hotkey representation
+ [fieldEditor setHotKeyDictionary:hotKeyDict_]; // OK if its nil
+ return YES;
+
+}
+
+- (void)textDidChange:(NSNotification *)notification {
+
+ // Sanity
+ GTMHotKeyFieldEditor *fieldEditor = [notification object];
+ if (![fieldEditor isKindOfClass:[GTMHotKeyFieldEditor class]]) {
+ _GTMDevLog(@"Field editor not appropriate for field, check window delegate");
+ return;
+ }
+
+ // When the field changes we want to read in the current hotkey value so
+ // bindings can validate
+ [self setHotKeyValue:[fieldEditor hotKeyDictionary]];
+
+ // Let super handle the notifications
+ [super textDidChange:notification];
+
+}
+
+- (BOOL)textShouldEndEditing:(GTMHotKeyFieldEditor *)fieldEditor {
+
+ // Sanity
+ if (![fieldEditor isKindOfClass:[GTMHotKeyFieldEditor class]]) {
+ _GTMDevLog(@"Field editor not appropriate for field, check window delegate");
+ return NO;
+ }
+
+ // Again we are defeating default behavior so we have to do delegate handling
+ // ourself. In this case our goal is simply to prevent the superclass from
+ // doing its own KVO, but we can also skip [[self cell] isEntryAcceptable:].
+ // We'll also ignore the delegate control:textShouldEndEditing:. The field
+ // editor is done whether they like it or not.
+ id myDelegate = [self delegate];
+ SEL selector = @selector(control:textShouldEndEditing:);
+ if ([myDelegate respondsToSelector:selector]) {
+ [myDelegate control:self textShouldEndEditing:fieldEditor];
+ }
+
+ // The end is always allowed, so set new value
+ [self setHotKeyValue:[fieldEditor hotKeyDictionary]];
+
+ return YES;
+
+}
+
+#pragma mark Class methods building strings for use w/in the UI.
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
++ (void)initialize {
+ if (!GTM_TISCopyCurrentKeyboardLayoutInputSource
+ && [GTMSystemVersion isLeopardOrGreater]) {
+ CFBundleRef hiToolbox
+ = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.HIToolbox"));
+ if (hiToolbox) {
+ kGTM_TISPropertyUnicodeKeyLayoutData
+ = *(CFStringRef*)CFBundleGetDataPointerForName(hiToolbox,
+ CFSTR("kTISPropertyUnicodeKeyLayoutData"));
+ GTM_TISCopyCurrentKeyboardLayoutInputSource
+ = CFBundleGetFunctionPointerForName(hiToolbox,
+ CFSTR("TISCopyCurrentKeyboardLayoutInputSource"));
+ GTM_TISGetInputSourceProperty
+ = CFBundleGetFunctionPointerForName(hiToolbox,
+ CFSTR("TISGetInputSourceProperty"));
+ }
+ }
+}
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
+
+#pragma mark Useful String Class Methods
+
+// These are not in a category on NSString because this class could be used
+// within multiple preference panes at the same time. If we put it in a category
+// it would require setting up some magic so that the categories didn't conflict
+// between the multiple pref panes. By putting it in the class, you can just
+// #define the class name to something else, and then you won't have any
+// conflicts.
+
++ (NSString *)stringForModifierFlags:(unsigned int)flags {
+
+ UniChar modChars[4]; // We only look for 4 flags
+ unsigned int charCount = 0;
+ // These are in the same order as the menu manager shows them
+ if (flags & NSControlKeyMask) modChars[charCount++] = kControlUnicode;
+ if (flags & NSAlternateKeyMask) modChars[charCount++] = kOptionUnicode;
+ if (flags & NSShiftKeyMask) modChars[charCount++] = kShiftUnicode;
+ if (flags & NSCommandKeyMask) modChars[charCount++] = kCommandUnicode;
+ if (charCount == 0) return nil;
+ return [NSString stringWithCharacters:modChars length:charCount];
+
+}
+
++ (NSString *)stringForKeycode:(UInt16)keycode
+ useGlyph:(BOOL)useGlyph
+ resourceBundle:(NSBundle *)bundle {
+
+ // Some keys never move in any layout (to the best of our knowledge at least)
+ // so we can hard map them.
+ UniChar key = 0;
+ NSString *localizedKey = nil;
+
+ switch (keycode) {
+
+ // Of the hard mapped keys some can be represented with pretty and obvioous
+ // Unicode or simple strings without localization.
+
+ // Arrow keys
+ case 123: key = NSLeftArrowFunctionKey; break;
+ case 124: key = NSRightArrowFunctionKey; break;
+ case 125: key = NSDownArrowFunctionKey; break;
+ case 126: key = NSUpArrowFunctionKey; break;
+ case 122: key = NSF1FunctionKey; break;
+ case 120: key = NSF2FunctionKey; break;
+ case 99: key = NSF3FunctionKey; break;
+ case 118: key = NSF4FunctionKey; break;
+ case 96: key = NSF5FunctionKey; break;
+ case 97: key = NSF6FunctionKey; break;
+ case 98: key = NSF7FunctionKey; break;
+ case 100: key = NSF8FunctionKey; break;
+ case 101: key = NSF9FunctionKey; break;
+ case 109: key = NSF10FunctionKey; break;
+ case 103: key = NSF11FunctionKey; break;
+ case 111: key = NSF12FunctionKey; break;
+ case 105: key = NSF13FunctionKey; break;
+ case 107: key = NSF14FunctionKey; break;
+ case 113: key = NSF15FunctionKey; break;
+ case 106: key = NSF16FunctionKey; break;
+ // Forward delete is a terrible name so we'll use the glyph Apple puts on
+ // their current keyboards
+ case 117: key = 0x2326; break;
+
+ // Now we have keys that can be hard coded but don't have good glyph
+ // representations. Sure, the Apple menu manager has glyphs for them, but
+ // an informal poll of Google developers shows no one really knows what
+ // they mean, so its probably a good idea to use strings. Unfortunately
+ // this also means localization (*sigh*). We'll use the real English
+ // strings here as keys so that even if localization is missed we'll do OK
+ // in output.
+
+ // Whitespace
+ case 36: key = '\r'; localizedKey = @"Return"; break;
+ case 76: key = 0x3; localizedKey = @"Enter"; break;
+ case 48: key = 0x9; localizedKey = @"Tab"; break;
+ case 49: key = 0xA0; localizedKey = @"Space"; break;
+ // Control keys
+ case 51: key = 0x8; localizedKey = @"Delete"; break;
+ case 71: key = NSClearDisplayFunctionKey; localizedKey = @"Clear"; break;
+ case 53: key = 0x1B; localizedKey = @"Esc"; break;
+ case 115: key = NSHomeFunctionKey; localizedKey = @"Home"; break;
+ case 116: key = NSPageUpFunctionKey; localizedKey = @"Page Up"; break;
+ case 119: key = NSEndFunctionKey; localizedKey = @"End"; break;
+ case 121: key = NSPageDownFunctionKey; localizedKey = @"Page Down"; break;
+ case 114: key = NSHelpFunctionKey; localizedKey = @"Help"; break;
+ // Keypad keys
+ // There is no good way we could find to glyph these. We tried a variety
+ // of Unicode glyphs, and the menu manager wouldn't take them. We tried
+ // subscript numbers, circled numbers and superscript numbers with no
+ // luck. It may be a bit confusing to the user, but we're happy to hear
+ // any suggestions.
+ case 65: key = '.'; localizedKey = @"Keypad ."; break;
+ case 67: key = '*'; localizedKey = @"Keypad *"; break;
+ case 69: key = '+'; localizedKey = @"Keypad +"; break;
+ case 75: key = '/'; localizedKey = @"Keypad /"; break;
+ case 78: key = '-'; localizedKey = @"Keypad -"; break;
+ case 81: key = '='; localizedKey = @"Keypad ="; break;
+ case 82: key = '0'; localizedKey = @"Keypad 0"; break;
+ case 83: key = '1'; localizedKey = @"Keypad 1"; break;
+ case 84: key = '2'; localizedKey = @"Keypad 2"; break;
+ case 85: key = '3'; localizedKey = @"Keypad 3"; break;
+ case 86: key = '4'; localizedKey = @"Keypad 4"; break;
+ case 87: key = '5'; localizedKey = @"Keypad 5"; break;
+ case 88: key = '6'; localizedKey = @"Keypad 6"; break;
+ case 89: key = '7'; localizedKey = @"Keypad 7"; break;
+ case 91: key = '8'; localizedKey = @"Keypad 8"; break;
+ case 92: key = '9'; localizedKey = @"Keypad 9"; break;
+
+ }
+
+ // If they asked for strings, and we have one return it. Otherwise, return
+ // any key we've picked.
+ if (!useGlyph && localizedKey) {
+ return NSLocalizedStringFromTableInBundle(localizedKey, @"KeyCode",
+ bundle, @"");
+ } else if (key != 0) {
+ return [NSString stringWithFormat:@"%C", key];
+ }
+
+ // Everything else should be printable so look it up in the current keyboard
+ UCKeyboardLayout *uchrData = NULL;
+
+ OSStatus err = noErr;
+#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
+ // layout
+ KeyboardLayoutRef currentLayout = NULL;
+ // Get the layout kind
+ SInt32 currentLayoutKind = -1;
+ if ([GTMSystemVersion isLeopardOrGreater]
+ && kGTM_TISPropertyUnicodeKeyLayoutData
+ && GTM_TISGetInputSourceProperty
+ && GTM_TISCopyCurrentKeyboardLayoutInputSource) {
+ // On Leopard we use the new improved TIS interfaces which work for input
+ // sources as well as keyboard layouts.
+ TISInputSourceRef inputSource
+ = GTM_TISCopyCurrentKeyboardLayoutInputSource();
+ if (inputSource) {
+ CFDataRef uchrDataRef
+ = GTM_TISGetInputSourceProperty(inputSource,
+ kGTM_TISPropertyUnicodeKeyLayoutData);
+ if(uchrDataRef) {
+ uchrData = (UCKeyboardLayout*)CFDataGetBytePtr(uchrDataRef);
+ }
+ CFRelease(inputSource);
+ }
+ } else {
+ // Tiger we use keyboard layouts as it's the best we can officially do.
+ err = KLGetCurrentKeyboardLayout(&currentLayout);
+ if (err != noErr) { // COV_NF_START
+ _GTMDevLog(@"failed to fetch the keyboard layout, err=%d", err);
+ return nil;
+ } // COV_NF_END
+
+ err = KLGetKeyboardLayoutProperty(currentLayout,
+ kKLKind,
+ (const void **)&currentLayoutKind);
+ if (err != noErr) { // COV_NF_START
+ _GTMDevLog(@"failed to fetch the keyboard layout kind property, err=%d",
+ err);
+ return nil;
+ } // COV_NF_END
+
+ if (currentLayoutKind != kKLKCHRKind) {
+ err = KLGetKeyboardLayoutProperty(currentLayout,
+ kKLuchrData,
+ (const void **)&uchrData);
+ if (err != noErr) { // COV_NF_START
+ _GTMDevLog(@"failed to fetch the keyboard layout uchar data, err=%d",
+ err);
+ return nil;
+ } // COV_NF_END
+ }
+ }
+#else
+ TISInputSourceRef inputSource = TISCopyCurrentKeyboardLayoutInputSource();
+ if (inputSource) {
+ CFDataRef uchrDataRef
+ = TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData);
+ if(uchrDataRef) {
+ uchrData = (UCKeyboardLayout*)CFDataGetBytePtr(uchrDataRef);
+ }
+ CFRelease(inputSource);
+ }
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
+
+ NSString *keystrokeString = nil;
+ if (uchrData) {
+ // uchr layout data is available, this is our preference
+ UniCharCount uchrCharLength = 0;
+ UniChar uchrChars[256] = { 0 };
+ UInt32 uchrDeadKeyState = 0;
+ err = UCKeyTranslate(uchrData,
+ keycode,
+ kUCKeyActionDisplay,
+ 0, // No modifiers
+ LMGetKbdType(),
+ kUCKeyTranslateNoDeadKeysMask,
+ &uchrDeadKeyState,
+ sizeof(uchrChars) / sizeof(UniChar),
+ &uchrCharLength,
+ uchrChars);
+ if (err != noErr) { // COV_NF_START
+ _GTMDevLog(@"failed to translate the keycode, err=%d", err);
+ return nil;
+ } // COV_NF_END
+ if (uchrCharLength < 1) return nil;
+ keystrokeString = [NSString stringWithCharacters:uchrChars
+ length:uchrCharLength];
+ }
+#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
+ else if (currentLayoutKind == kKLKCHRKind) {
+ // Only KCHR layout data is available, go old school
+ void *KCHRData = NULL;
+ err = KLGetKeyboardLayoutProperty(currentLayout, kKLKCHRData,
+ (const void **)&KCHRData);
+ if (err != noErr) { // COV_NF_START
+ _GTMDevLog(@"failed to fetch the keyboard layout uchar data, err=%d",
+ err);
+ return nil;
+ } // COV_NF_END
+ // Turn into character code
+ UInt32 keyTranslateState = 0;
+ UInt32 twoKCHRChars = KeyTranslate(KCHRData, keycode, &keyTranslateState);
+ if (!twoKCHRChars) return nil;
+ // Unpack the fields
+ char firstChar = (char)((twoKCHRChars & 0x00FF0000) >> 16);
+ char secondChar = (char)(twoKCHRChars & 0x000000FF);
+ // May have one or two characters
+ if (firstChar && secondChar) {
+ NSString *str1
+ = [[[NSString alloc] initWithBytes:&firstChar
+ length:1
+ encoding:NSMacOSRomanStringEncoding] autorelease];
+ NSString *str2
+ = [[[NSString alloc] initWithBytes:&secondChar
+ length:1
+ encoding:NSMacOSRomanStringEncoding] autorelease];
+ keystrokeString = [NSString stringWithFormat:@"%@%@",
+ [str1 uppercaseString],
+ [str2 uppercaseString]];
+ } else {
+ keystrokeString = [[[NSString alloc] initWithBytes:&secondChar
+ length:1
+ encoding:NSMacOSRomanStringEncoding] autorelease];
+ [keystrokeString uppercaseString];
+ }
+ }
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
+
+ // Sanity we got a stroke
+ if (!keystrokeString || ![keystrokeString length]) return nil;
+
+ // Sanity check the keystroke string for unprintable characters
+ NSMutableCharacterSet *validChars
+ = [[[NSCharacterSet alphanumericCharacterSet] mutableCopy] autorelease];
+ [validChars formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
+ [validChars formUnionWithCharacterSet:[NSCharacterSet symbolCharacterSet]];
+ for (unsigned int i = 0; i < [keystrokeString length]; i++) {
+ if (![validChars characterIsMember:[keystrokeString characterAtIndex:i]]) {
+ return nil;
+ }
+ }
+
+ if (!useGlyph) {
+ // menus want glyphs in the original lowercase forms, so we only upper this
+ // if we aren't using it as a glyph.
+ keystrokeString = [keystrokeString uppercaseString];
+ }
+
+ return keystrokeString;
+
+}
+
+@end
+
+@implementation GTMHotKeyFieldEditor
+
+GTMOBJECT_SINGLETON_BOILERPLATE(GTMHotKeyFieldEditor, sharedHotKeyFieldEditor)
+
+- (id)init {
+
+ self = [super init];
+ if (!self) return nil;
+ [self setFieldEditor:YES]; // We are a field editor
+
+ return self;
+
+}
+
+- (void)dealloc {
+
+ [hotKeyDict_ release];
+ [super dealloc];
+
+}
+
+- (NSArray *)acceptableDragTypes {
+
+ // Don't take drags
+ return [NSArray array];
+
+}
+
+- (NSArray *)readablePasteboardTypes {
+
+ // No pasting
+ return [NSArray array];
+
+}
+
+- (NSArray *)writablePasteboardTypes {
+
+ // No copying
+ return [NSArray array];
+
+}
+
+- (BOOL)becomeFirstResponder {
+
+ // We need to lose focus any time the window is not key
+ NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
+ [dc addObserver:self
+ selector:@selector(windowResigned:)
+ name:NSWindowDidResignKeyNotification
+ object:[self window]];
+ return [super becomeFirstResponder];
+
+}
+
+- (BOOL)resignFirstResponder {
+
+ // No longer interested in window resign
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ return [super resignFirstResponder];
+
+}
+
+// Private method we use to get out of global hotkey capture when the window
+// is no longer front
+- (void)windowResigned:(NSNotification *)notification {
+
+ // Lose our focus
+ [[self window] makeFirstResponder:[self window]];
+
+}
+
+- (BOOL)shouldDrawInsertionPoint {
+
+ // Show an insertion point, because we'll kill our own focus after
+ // each entry
+ return YES;
+
+}
+
+- (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange
+ granularity:(NSSelectionGranularity)granularity {
+
+ // Always select everything
+ return NSMakeRange(0, [[self textStorage] length]);
+
+}
+
+- (void)keyDown:(NSEvent *)theEvent {
+
+ if ([self shouldBypassEvent:theEvent]) {
+ [super keyDown:theEvent];
+ } else {
+ // Try to eat the event
+ [self processEventToHotKeyAndString:theEvent];
+ }
+
+}
+
+- (BOOL)performKeyEquivalent:(NSEvent *)theEvent {
+
+ if ([self shouldBypassEvent:theEvent]) {
+ return [super performKeyEquivalent:theEvent];
+ } else {
+ // We always eat these key strokes while we have focus
+ [self processEventToHotKeyAndString:theEvent];
+ return YES;
+ }
+
+}
+
+// Private do method that tell us to ignore certain events
+- (BOOL)shouldBypassEvent:(NSEvent *)theEvent {
+
+ UInt16 keyCode = [theEvent keyCode];
+ NSUInteger modifierFlags
+ = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
+
+ // Ignore all events containing tabs. They have special meaning to fields
+ // and some (Cmd Tab variants) are always consumed by the Dock, so users
+ // just shouldn't be able to use them.
+ if (keyCode == 48) { // Tab
+ // Just to be extra clear if the user is trying to use Dock hotkeys beep
+ // at them
+ if ((modifierFlags == NSCommandKeyMask) ||
+ (modifierFlags == (NSCommandKeyMask | NSShiftKeyMask))) {
+ NSBeep();
+ }
+ return YES;
+ }
+
+ // Don't eat Cmd-Q. Users could have it as a hotkey, but its more likely
+ // they're trying to quit
+ if ((keyCode == 12) && (modifierFlags == NSCommandKeyMask)) {
+ return YES;
+ }
+ // Same for Cmd-W, user is probably trying to close the window
+ if ((keyCode == 13) && (modifierFlags == NSCommandKeyMask)) {
+ return YES;
+ }
+
+ return NO;
+
+}
+
+// Private method that turns events into strings and dictionaries for our
+// hotkey plumbing.
+- (void)processEventToHotKeyAndString:(NSEvent *)theEvent {
+
+ // Construct a dictionary of the event as a hotkey pref
+ NSDictionary *newHotKey = [self hotKeyDictionaryForEvent:theEvent];
+ if (!newHotKey) {
+ NSBeep();
+ return; // No action, but don't give up focus
+ }
+ NSString *prettyString = [GTMHotKeyTextField displayStringForHotKey:newHotKey];
+ if (!prettyString) {
+ NSBeep();
+ return;
+ }
+
+ // Replacement range
+ NSRange replaceRange = NSMakeRange(0, [[self textStorage] length]);
+
+ // Ask for permission to replace
+ if (![self shouldChangeTextInRange:replaceRange
+ replacementString:prettyString]) {
+ // If replacement was disallowed, change nothing, including hotKeyDict_
+ NSBeep();
+ return;
+ }
+
+ // Replacement was allowed, update
+ [hotKeyDict_ autorelease];
+ hotKeyDict_ = [newHotKey retain];
+
+ // Set string on self, allowing super to handle attribute copying
+ [self setString:prettyString];
+
+ // Finish the change
+ [self didChangeText];
+
+ // Force editing to end. This sends focus off into space slightly, but
+ // its better than constantly capturing user events. This is exactly
+ // like the Apple editor in their Keyboard pref pane.
+ [[[self delegate] cell] endEditing:self];
+
+}
+
+- (NSDictionary *)hotKeyDictionary {
+
+ return hotKeyDict_;
+
+}
+
+- (void)setHotKeyDictionary:(NSDictionary *)hotKey {
+
+ [hotKeyDict_ autorelease];
+ hotKeyDict_ = [hotKey copy];
+ // Update content
+ NSString *prettyString = nil;
+ if (hotKeyDict_) {
+ prettyString = [GTMHotKeyTextField displayStringForHotKey:hotKey];
+ }
+ if (!prettyString) {
+ prettyString = @"";
+ }
+ [self setString:prettyString];
+
+}
+
+- (NSDictionary *)hotKeyDictionaryForEvent:(NSEvent *)event{
+
+ if (!event) return nil;
+
+ // Check event
+ NSUInteger flags = [event modifierFlags];
+ UInt16 keycode = [event keyCode];
+ // If the event has no modifiers do nothing
+ NSUInteger allModifiers = (NSCommandKeyMask | NSAlternateKeyMask |
+ NSControlKeyMask | NSShiftKeyMask);
+ if (!(flags & allModifiers)) return nil;
+ // If the event has high bits in keycode do nothing
+ if (keycode & 0xFF00) return nil;
+
+ // Clean the flags to only contain things we care about
+ UInt32 cleanFlags = 0;
+ if (flags & NSCommandKeyMask) cleanFlags |= NSCommandKeyMask;
+ if (flags & NSAlternateKeyMask) cleanFlags |= NSAlternateKeyMask;
+ if (flags & NSControlKeyMask) cleanFlags |= NSControlKeyMask;
+ if (flags & NSShiftKeyMask) cleanFlags |= NSShiftKeyMask;
+
+ return [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:NO],
+ kGTMHotKeyDoubledModifierKey,
+ [NSNumber numberWithUnsignedInt:keycode],
+ kGTMHotKeyKeyCodeKey,
+ [NSNumber numberWithUnsignedInt:cleanFlags],
+ kGTMHotKeyModifierFlagsKey,
+ nil];
+
+}
+@end
+
diff --git a/AppKit/GTMHotKeyTextFieldTest.m b/AppKit/GTMHotKeyTextFieldTest.m
new file mode 100644
index 0000000..ee1bfc2
--- /dev/null
+++ b/AppKit/GTMHotKeyTextFieldTest.m
@@ -0,0 +1,204 @@
+// GTMHotKeyTextFieldTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMHotKeyTextField.h"
+
+#import "GTMSenTestCase.h"
+
+@interface GTMHotKeyTextField (PrivateMethods)
+// Private methods which we want to access to test
++ (BOOL)isValidHotKey:(NSDictionary *)hotKey;
++ (NSString *)displayStringForHotKey:(NSDictionary *)hotKey;
+@end
+
+@interface GTMHotKeyTextFieldTest : GTMTestCase
+@end
+
+@implementation GTMHotKeyTextFieldTest
+
+- (void)testStringForModifierFlags {
+
+ // Make sure only the flags we expect generate things in their strings
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:NSAlphaShiftKeyMask] length],
+ (NSUInteger)0, nil);
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:NSShiftKeyMask] length],
+ (NSUInteger)1, nil);
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:NSControlKeyMask] length],
+ (NSUInteger)1, nil);
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:NSAlternateKeyMask] length],
+ (NSUInteger)1, nil);
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:NSCommandKeyMask] length],
+ (NSUInteger)1, nil);
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:NSNumericPadKeyMask] length],
+ (NSUInteger)0, nil);
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:NSHelpKeyMask] length],
+ (NSUInteger)0, nil);
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:NSFunctionKeyMask] length],
+ (NSUInteger)0, nil);
+
+ // And some quick checks combining flags to make sure the string gets longer
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:(NSShiftKeyMask |
+ NSAlternateKeyMask)] length],
+ (NSUInteger)2, nil);
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:(NSShiftKeyMask |
+ NSAlternateKeyMask |
+ NSCommandKeyMask)] length],
+ (NSUInteger)3, nil);
+ STAssertEquals([[GTMHotKeyTextField stringForModifierFlags:(NSShiftKeyMask |
+ NSAlternateKeyMask |
+ NSCommandKeyMask |
+ NSControlKeyMask)] length],
+ (NSUInteger)4, nil);
+
+}
+
+- (void)testStringForKeycode_useGlyph_resourceBundle {
+ NSBundle *bundle = [NSBundle bundleForClass:[self class]];
+ STAssertNotNil(bundle, @"failed to get our bundle?");
+ NSString *str;
+
+ // We need a better test, but for now, we'll just loop through things we know
+ // we handle.
+
+ // TODO: we need to force the pre leopard code path during tests.
+
+ UInt16 testData[] = {
+ 123, 124, 125, 126, 122, 120, 99, 118, 96, 97, 98, 100, 101, 109, 103, 111,
+ 105, 107, 113, 106, 117, 36, 76, 48, 49, 51, 71, 53, 115, 116, 119, 121,
+ 114, 65, 67, 69, 75, 78, 81, 82, 83, 84, 85, 86, 87, 88, 89, 91, 92,
+ };
+ for (int useGlyph = 0 ; useGlyph < 2 ; ++useGlyph) {
+ for (size_t i = 0; i < (sizeof(testData) / sizeof(UInt16)); ++i) {
+ UInt16 keycode = testData[i];
+
+ str = [GTMHotKeyTextField stringForKeycode:keycode
+ useGlyph:useGlyph
+ resourceBundle:bundle];
+ STAssertNotNil(str,
+ @"failed to get a string for keycode %u (useGlyph:%@)",
+ keycode, (useGlyph ? @"YES" : @"NO"));
+ STAssertGreaterThan([str length], (NSUInteger)0,
+ @"got an empty string for keycode %u (useGlyph:%@)",
+ keycode, (useGlyph ? @"YES" : @"NO"));
+ }
+ }
+}
+
+- (void)testGTMHotKeyPrettyString {
+ NSDictionary *hkDict;
+
+ hkDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:NO],
+ kGTMHotKeyDoubledModifierKey,
+ [NSNumber numberWithUnsignedInt:114],
+ kGTMHotKeyKeyCodeKey,
+ [NSNumber numberWithUnsignedInt:NSCommandKeyMask],
+ kGTMHotKeyModifierFlagsKey,
+ nil];
+ STAssertNotNil(hkDict, nil);
+ STAssertNotNil([GTMHotKeyTextField displayStringForHotKey:hkDict], nil);
+
+ hkDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithUnsignedInt:114],
+ kGTMHotKeyKeyCodeKey,
+ [NSNumber numberWithUnsignedInt:NSCommandKeyMask],
+ kGTMHotKeyModifierFlagsKey,
+ nil];
+ STAssertNotNil(hkDict, nil);
+ STAssertNotNil([GTMHotKeyTextField displayStringForHotKey:hkDict], nil);
+
+ hkDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:NO],
+ kGTMHotKeyDoubledModifierKey,
+ [NSNumber numberWithUnsignedInt:NSCommandKeyMask],
+ kGTMHotKeyModifierFlagsKey,
+ nil];
+ STAssertNotNil(hkDict, nil);
+ STAssertNotNil([GTMHotKeyTextField displayStringForHotKey:hkDict], nil);
+
+ hkDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:NO],
+ kGTMHotKeyDoubledModifierKey,
+ [NSNumber numberWithUnsignedInt:114],
+ kGTMHotKeyKeyCodeKey,
+ nil];
+ STAssertNotNil(hkDict, nil);
+ STAssertNil([GTMHotKeyTextField displayStringForHotKey:hkDict], nil);
+
+ hkDict = [NSDictionary dictionary];
+ STAssertNotNil(hkDict, nil);
+ STAssertNil([GTMHotKeyTextField displayStringForHotKey:hkDict], nil);
+
+ STAssertNil([GTMHotKeyTextField displayStringForHotKey:nil], nil);
+
+}
+
+- (void)testGTMHotKeyDictionaryAppearsValid {
+ NSDictionary *hkDict;
+
+ hkDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:NO],
+ kGTMHotKeyDoubledModifierKey,
+ [NSNumber numberWithUnsignedInt:114],
+ kGTMHotKeyKeyCodeKey,
+ [NSNumber numberWithUnsignedInt:NSCommandKeyMask],
+ kGTMHotKeyModifierFlagsKey,
+ nil];
+ STAssertNotNil(hkDict, nil);
+ STAssertTrue([GTMHotKeyTextField isValidHotKey:hkDict], nil);
+
+ hkDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithUnsignedInt:114],
+ kGTMHotKeyKeyCodeKey,
+ [NSNumber numberWithUnsignedInt:NSCommandKeyMask],
+ kGTMHotKeyModifierFlagsKey,
+ nil];
+ STAssertNotNil(hkDict, nil);
+ STAssertFalse([GTMHotKeyTextField isValidHotKey:hkDict], nil);
+
+ hkDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:NO],
+ kGTMHotKeyDoubledModifierKey,
+ [NSNumber numberWithUnsignedInt:NSCommandKeyMask],
+ kGTMHotKeyModifierFlagsKey,
+ nil];
+ STAssertNotNil(hkDict, nil);
+ STAssertFalse([GTMHotKeyTextField isValidHotKey:hkDict], nil);
+
+ hkDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:NO],
+ kGTMHotKeyDoubledModifierKey,
+ [NSNumber numberWithUnsignedInt:114],
+ kGTMHotKeyKeyCodeKey,
+ nil];
+ STAssertNotNil(hkDict, nil);
+ STAssertFalse([GTMHotKeyTextField isValidHotKey:hkDict], nil);
+
+ hkDict = [NSDictionary dictionary];
+ STAssertNotNil(hkDict, nil);
+ STAssertFalse([GTMHotKeyTextField isValidHotKey:hkDict], nil);
+
+ STAssertFalse([GTMHotKeyTextField isValidHotKey:nil], nil);
+
+ // Make sure it doesn't choke w/ an object of the wrong time (since the dicts
+ // have to be saved/reloaded.
+ hkDict = (id)[NSString string];
+ STAssertNotNil(hkDict, nil);
+ STAssertFalse([GTMHotKeyTextField isValidHotKey:hkDict], nil);
+}
+
+@end
diff --git a/AppKit/GTMLoginItems.h b/AppKit/GTMLoginItems.h
index 1531bd0..b4375b4 100644
--- a/AppKit/GTMLoginItems.h
+++ b/AppKit/GTMLoginItems.h
@@ -17,15 +17,16 @@
//
#import <Foundation/Foundation.h>
+#import "GTMDefines.h"
/// Login items key constants, used as keys in |+loginItems|
//
// Item name
-extern NSString * const kGTMLoginItemsNameKey;
+GTM_EXTERN NSString * const kGTMLoginItemsNameKey;
// Item path
-extern NSString * const kGTMLoginItemsPathKey;
+GTM_EXTERN NSString * const kGTMLoginItemsPathKey;
// Hidden (NSNumber bool)
-extern NSString * const kGTMLoginItemsHiddenKey;
+GTM_EXTERN NSString * const kGTMLoginItemsHiddenKey;
/// GTMLoginItems
//
diff --git a/Foundation/GTMFileSystemKQueue.h b/Foundation/GTMFileSystemKQueue.h
new file mode 100644
index 0000000..a81d433
--- /dev/null
+++ b/Foundation/GTMFileSystemKQueue.h
@@ -0,0 +1,78 @@
+//
+// GTMFileSystemKQueue.h
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Foundation/Foundation.h>
+
+#import <sys/event.h> // for kqueue() and kevent and the NOTE_* constants
+
+// Event constants
+enum {
+ kGTMFileSystemKQueueDeleteEvent = NOTE_DELETE,
+ kGTMFileSystemKQueueWriteEvent = NOTE_WRITE,
+ kGTMFileSystemKQueueExtendEvent = NOTE_EXTEND,
+ kGTMFileSystemKQueueAttributeChangeEvent = NOTE_ATTRIB,
+ kGTMFileSystemKQueueLinkChangeEvent = NOTE_LINK,
+ kGTMFileSystemKQueueRenameEvent = NOTE_RENAME,
+ kGTMFileSystemKQueueRevokeEvent = NOTE_REVOKE,
+ kGTMFileSystemKQueueAllEvents = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND |
+ NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME |
+ NOTE_REVOKE,
+};
+typedef unsigned int GTMFileSystemKQueueEvents;
+
+// GTMFileSystemKQueue.
+//
+// This is a very simple, easy-to-use class for registering handlers that get
+// called when a events happen to a given file system path.
+//
+// The default runloop for the first path kqueued is used for notification
+// delivery, so keep that in mind when you're using this class. This class
+// explicitly does not handle arbitrary runloops and threading.
+//
+@interface GTMFileSystemKQueue : NSObject {
+ @private
+ NSString *path_;
+ int fd_;
+ GTMFileSystemKQueueEvents events_;
+ BOOL acrossReplace_;
+ __weak id target_;
+ SEL action_;
+}
+
+// |path| is the full path to monitor. |events| is a combination of events
+// listed above that you want notification of. |acrossReplace| will cause this
+// object to reattach when a the file is deleted & recreated or moved out of the
+// way and a new one put in place. |selector| should be of the signature:
+// - (void)fileSystemKQueue:(GTMFileSystemKQueue *)fskq
+// events:(GTMFileSystemKQueueEvents)events;
+// where the events can be one or more of the events listed above ORed together.
+//
+// NOTE: |acrossReplace| is not fool proof. If the file is renamed/deleted,
+// then the object will make one attempt at the time it gets the rename/delete
+// to reopen the file. If the new file has not been created, no more action is
+// taken. To handle the file coming into existance later, you need to monitor
+// the directory in some other way.
+- (id)initWithPath:(NSString *)path
+ forEvents:(GTMFileSystemKQueueEvents)events
+ acrossReplace:(BOOL)acrossReplace
+ target:(id)target
+ action:(SEL)action;
+
+- (NSString *)path;
+
+@end
diff --git a/Foundation/GTMFileSystemKQueue.m b/Foundation/GTMFileSystemKQueue.m
new file mode 100644
index 0000000..e2c399e
--- /dev/null
+++ b/Foundation/GTMFileSystemKQueue.m
@@ -0,0 +1,250 @@
+//
+// GTMFileSystemKQueue.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMFileSystemKQueue.h"
+#import <unistd.h>
+
+#import "GTMDebugSelectorValidation.h"
+
+
+// File descriptor for the kqueue that will hold all of our file system events.
+static int gFileSystemKQueueFileDescriptor = 0;
+
+// A wrapper around the kqueue file descriptor so we can put it into a
+// runloop.
+static CFSocketRef gRunLoopSocket = NULL;
+
+
+@interface GTMFileSystemKQueue (PrivateMethods)
+- (void)notify:(GTMFileSystemKQueueEvents)eventFFlags;
+- (void)addFileDescriptorMonitor:(int)fd;
+- (int)registerWithKQueue;
+- (void)unregisterWithKQueue;
+@end
+
+
+@implementation GTMFileSystemKQueue
+
+-(id)init {
+ // Folks shouldn't call init directly, so they get what they deserve.
+ _GTMDevLog(@"Don't call init, use "
+ @"initWithPath:forEvents:acrossReplace:target:action:");
+ return [self initWithPath:nil forEvents:0 acrossReplace:NO
+ target:nil action:nil];
+}
+
+
+- (id)initWithPath:(NSString *)path
+ forEvents:(GTMFileSystemKQueueEvents)events
+ acrossReplace:(BOOL)acrossReplace
+ target:(id)target
+ action:(SEL)action {
+
+ if ((self = [super init])) {
+
+ fd_ = -1;
+ path_ = [path copy];
+ events_ = events;
+ acrossReplace_ = acrossReplace;
+
+ target_ = target; // Don't retain since target will most likely retain us.
+ action_ = action;
+ if ([path_ length] == 0 || !events_ || !target_ || !action_) {
+ [self release];
+ return nil;
+ }
+
+ // Make sure it imples what we expect
+ GTMAssertSelectorNilOrImplementedWithArguments(target_,
+ action_,
+ @encode(GTMFileSystemKQueue*),
+ @encode(GTMFileSystemKQueueEvents),
+ NULL);
+
+ fd_ = [self registerWithKQueue];
+ if (fd_ < 0) {
+ [self release];
+ return nil;
+ }
+ }
+ return self;
+}
+
+- (void)finalize {
+ [self unregisterWithKQueue];
+
+ [super finalize];
+}
+
+- (void)dealloc {
+ [self unregisterWithKQueue];
+ [path_ release];
+
+ [super dealloc];
+}
+
+- (NSString *)path {
+ return path_;
+}
+
+// Cribbed from Advanced Mac OS X Programming.
+static void SocketCallBack(CFSocketRef socketref, CFSocketCallBackType type,
+ CFDataRef address, const void *data, void *info) {
+ struct kevent event;
+
+ if (kevent(gFileSystemKQueueFileDescriptor, NULL, 0, &event, 1, NULL) == -1) {
+ _GTMDevLog(@"could not pick up kqueue event. Errno %d", errno); // COV_NF_LINE
+ } else {
+ GTMFileSystemKQueue *fskq = (GTMFileSystemKQueue *)event.udata;
+ [fskq notify:event.fflags];
+ }
+
+}
+
+// Cribbed from Advanced Mac OS X Programming
+- (void)addFileDescriptorMonitor:(int)fd {
+ _GTMDevAssert(gRunLoopSocket == NULL, @"socket should be NULL at this point");
+
+ CFSocketContext context = { 0, NULL, NULL, NULL, NULL };
+ gRunLoopSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
+ fd,
+ kCFSocketReadCallBack,
+ SocketCallBack,
+ &context);
+ if (gRunLoopSocket == NULL) {
+ _GTMDevLog(@"could not CFSocketCreateWithNative"); // COV_NF_LINE
+ goto bailout; // COV_NF_LINE
+ }
+
+ CFRunLoopSourceRef rls;
+ rls = CFSocketCreateRunLoopSource(NULL, gRunLoopSocket, 0);
+ if (rls == NULL) {
+ _GTMDevLog(@"could not create a run loop source"); // COV_NF_LINE
+ goto bailout; // COV_NF_LINE
+ }
+
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), rls,
+ kCFRunLoopDefaultMode);
+ CFRelease(rls);
+
+ bailout:
+ return;
+
+}
+
+// Returns the FD we got in registering
+- (int)registerWithKQueue {
+
+ // Make sure we have our kqueue.
+ if (gFileSystemKQueueFileDescriptor == 0) {
+ gFileSystemKQueueFileDescriptor = kqueue();
+
+ if (gFileSystemKQueueFileDescriptor == -1) {
+ // COV_NF_START
+ _GTMDevLog(@"could not make filesystem kqueue. Errno %d", errno);
+ return -1;
+ // COV_NF_END
+ }
+
+ // Add the kqueue file descriptor to the runloop.
+ [self addFileDescriptorMonitor:gFileSystemKQueueFileDescriptor];
+ }
+
+ int newFD = open([path_ fileSystemRepresentation], O_EVTONLY, 0);
+ if (newFD >= 0) {
+
+ // Add a new event for the file.
+ struct kevent filter;
+ EV_SET(&filter, newFD, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
+ events_, 0, self);
+
+ const struct timespec noWait = { 0, 0 };
+ if (kevent(gFileSystemKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) == -1) {
+ // COV_NF_START
+ _GTMDevLog(@"could not add event for path %@. Errno %d", path_, errno);
+ close(newFD);
+ newFD = -1;
+ // COV_NF_END
+ }
+ }
+
+ return newFD;
+}
+
+- (void)unregisterWithKQueue {
+ // Short-circuit cases where we didn't actually register a kqueue event.
+ if (fd_ < 0) return;
+
+ struct kevent filter;
+ EV_SET(&filter, fd_, EVFILT_VNODE, EV_DELETE, 0, 0, self);
+
+ const struct timespec noWait = { 0, 0 };
+ if (kevent(gFileSystemKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) {
+ _GTMDevLog(@"could not remove event for path %@. Errno %d", path_, errno); // COV_NF_LINE
+ }
+
+ // Now close the file down
+ close(fd_);
+ fd_ = -1;
+}
+
+
+- (void)notify:(GTMFileSystemKQueueEvents)eventFFlags {
+
+ // Some notifications get a little bit of overhead first
+
+ if (eventFFlags & NOTE_REVOKE) {
+ // COV_NF_START - no good way to do this in a unittest
+ // Assume revoke means unmount and give up
+ [self unregisterWithKQueue];
+ // COV_NF_END
+ }
+
+ if (eventFFlags & NOTE_DELETE) {
+ [self unregisterWithKQueue];
+ if (acrossReplace_) {
+ fd_ = [self registerWithKQueue];
+ }
+ }
+
+ if (eventFFlags & NOTE_RENAME) {
+ // If we're doing it across replace, we move to the new fd for the new file
+ // that might have come onto the path. if we aren't doing accross replace,
+ // nothing to do, just stay on the file.
+ if (acrossReplace_) {
+ int newFD = [self registerWithKQueue];
+ if (newFD >= 0) {
+ [self unregisterWithKQueue];
+ fd_ = newFD;
+ }
+ }
+ }
+
+ // Now, fire the selector
+ NSMethodSignature *methodSig = [target_ methodSignatureForSelector:action_];
+ _GTMDevAssert(methodSig != nil, @"failed to get the signature?");
+ NSInvocation *invocation
+ = [NSInvocation invocationWithMethodSignature:methodSig];
+ [invocation setTarget:target_];
+ [invocation setSelector:action_];
+ [invocation setArgument:self atIndex:2];
+ [invocation setArgument:&eventFFlags atIndex:3];
+ [invocation invoke];
+}
+
+@end
diff --git a/Foundation/GTMFileSystemKQueueTest.m b/Foundation/GTMFileSystemKQueueTest.m
new file mode 100644
index 0000000..c2ded10
--- /dev/null
+++ b/Foundation/GTMFileSystemKQueueTest.m
@@ -0,0 +1,394 @@
+//
+// GTMFileSystemKQueueTest.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMSenTestCase.h"
+#import "GTMFileSystemKQueue.h"
+#import "GTMUnitTestDevLog.h"
+
+@interface GTMFileSystemKQueueTest : GTMTestCase {
+ @private
+ NSString *testPath_;
+ NSString *testPath2_;
+}
+@end
+
+
+// Helper class to serve as callback target of the kqueue test
+@interface GTMFSKQTestHelper : NSObject {
+ @private
+ int writes_, renames_, deletes_;
+}
+@end
+
+@implementation GTMFSKQTestHelper
+
+- (void)callbackForQueue:(GTMFileSystemKQueue *)queue
+ events:(GTMFileSystemKQueueEvents)event {
+ if (event & kGTMFileSystemKQueueWriteEvent) {
+ ++writes_;
+ }
+ if (event & kGTMFileSystemKQueueDeleteEvent) {
+ ++deletes_;
+ }
+ if (event & kGTMFileSystemKQueueRenameEvent) {
+ ++renames_;
+ }
+}
+- (int)totals {
+ return writes_ + renames_ + deletes_;
+}
+- (int)writes {
+ return writes_;
+}
+- (int)renames {
+ return renames_;
+}
+- (int)deletes {
+ return deletes_;
+}
+@end
+
+
+@implementation GTMFileSystemKQueueTest
+
+- (void)setUp {
+ NSString *temp = NSTemporaryDirectory();
+ testPath_
+ = [[temp stringByAppendingPathComponent:
+ @"GTMFileSystemKQueueTest.testfile"] retain];
+ testPath2_ = [[testPath_ stringByAppendingPathExtension:@"2"] retain];
+
+ // make sure the files aren't in the way of the test
+ NSFileManager *fm = [NSFileManager defaultManager];
+ [fm removeFileAtPath:testPath_ handler:nil];
+ [fm removeFileAtPath:testPath2_ handler:nil];
+}
+
+- (void)tearDown {
+ // make sure we clean up the files from a failed test
+ NSFileManager *fm = [NSFileManager defaultManager];
+ [fm removeFileAtPath:testPath_ handler:nil];
+ [fm removeFileAtPath:testPath2_ handler:nil];
+
+ [testPath_ release];
+ testPath_ = nil;
+ [testPath2_ release];
+ testPath2_ = nil;
+}
+
+- (void)testInit {
+ GTMFileSystemKQueue *testKQ;
+ GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper, nil);
+
+ // init should fail
+ [GTMUnitTestDevLog expectString:@"Don't call init, use "
+ @"initWithPath:forEvents:acrossReplace:target:action:"];
+ testKQ = [[[GTMFileSystemKQueue alloc] init] autorelease];
+ STAssertNil(testKQ, nil);
+
+ // no path
+ testKQ
+ = [[[GTMFileSystemKQueue alloc] initWithPath:nil
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)] autorelease];
+ STAssertNil(testKQ, nil);
+
+ // not events
+ testKQ
+ = [[[GTMFileSystemKQueue alloc] initWithPath:@"/var/log/system.log"
+ forEvents:0
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)] autorelease];
+ STAssertNil(testKQ, nil);
+
+ // no target
+ testKQ
+ = [[[GTMFileSystemKQueue alloc] initWithPath:@"/var/log/system.log"
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:nil
+ action:@selector(callbackForQueue:events:)] autorelease];
+ STAssertNil(testKQ, nil);
+
+ // no handler
+ testKQ
+ = [[[GTMFileSystemKQueue alloc] initWithPath:@"/var/log/system.log"
+ forEvents:0
+ acrossReplace:YES
+ target:helper
+ action:nil] autorelease];
+ STAssertNil(testKQ, nil);
+
+
+ // path that doesn't exist
+ testKQ
+ = [[[GTMFileSystemKQueue alloc] initWithPath:@"/path/that/does/not/exist"
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)] autorelease];
+ STAssertNil(testKQ, nil);
+}
+
+- (void)spinForEvents:(GTMFSKQTestHelper *)helper {
+ // Spin the runloop for a second so that the helper callbacks fire
+ unsigned int attempts = 0;
+ int initialTotals = [helper totals];
+ while (([helper totals] == initialTotals) && (attempts < 10)) { // Try for up to 2s
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]];
+ attempts++;
+ }
+}
+
+- (void)testWriteAndDelete {
+
+ NSFileManager *fm = [NSFileManager defaultManager];
+ GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper, nil);
+
+ STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
+ NSFileHandle *testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
+ STAssertNotNil(testFH, nil);
+
+ // Start monitoring the file
+ GTMFileSystemKQueue *testKQ
+ = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)];
+ STAssertNotNil(testKQ, nil);
+ STAssertEqualObjects([testKQ path], testPath_, nil);
+
+ // Write to the file
+ [testFH writeData:[@"doh!" dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 1, nil);
+
+ // Close and delete
+ [testFH closeFile];
+ STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
+
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 2, nil);
+
+ // Clean up the kqueue
+ [testKQ release];
+ testKQ = nil;
+
+ STAssertEquals([helper writes], 1, nil);
+ STAssertEquals([helper deletes], 1, nil);
+ STAssertEquals([helper renames], 0, nil);
+}
+
+- (void)testWriteAndDeleteAndWrite {
+
+ // One will pass YES to |acrossReplace|, the other will pass NO.
+
+ NSFileManager *fm = [NSFileManager defaultManager];
+ GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper, nil);
+ GTMFSKQTestHelper *helper2 = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper, nil);
+
+ // Create a temp file path
+ STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
+ NSFileHandle *testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
+ STAssertNotNil(testFH, nil);
+
+ // Start monitoring the file
+ GTMFileSystemKQueue *testKQ
+ = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)];
+ STAssertNotNil(testKQ, nil);
+ STAssertEqualObjects([testKQ path], testPath_, nil);
+ GTMFileSystemKQueue *testKQ2
+ = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:NO
+ target:helper2
+ action:@selector(callbackForQueue:events:)];
+ STAssertNotNil(testKQ2, nil);
+ STAssertEqualObjects([testKQ2 path], testPath_, nil);
+
+ // Write to the file
+ [testFH writeData:[@"doh!" dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 1, nil);
+ STAssertEquals([helper2 totals], 1, nil);
+
+ // Close and delete
+ [testFH closeFile];
+ STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
+
+ // Recreate
+ STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
+ testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
+ STAssertNotNil(testFH, nil);
+ [testFH writeData:[@"ha!" dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 2, nil);
+ STAssertEquals([helper2 totals], 2, nil);
+
+ // Write to it again
+ [testFH writeData:[@"continued..." dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 3, nil);
+ STAssertEquals([helper2 totals], 2, nil);
+
+ // Close and delete
+ [testFH closeFile];
+ STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 4, nil);
+ STAssertEquals([helper2 totals], 2, nil);
+
+ // Clean up the kqueue
+ [testKQ release];
+ testKQ = nil;
+ [testKQ2 release];
+ testKQ2 = nil;
+
+ STAssertEquals([helper writes], 2, nil);
+ STAssertEquals([helper deletes], 2, nil);
+ STAssertEquals([helper renames], 0, nil);
+ STAssertEquals([helper2 writes], 1, nil);
+ STAssertEquals([helper2 deletes], 1, nil);
+ STAssertEquals([helper2 renames], 0, nil);
+}
+
+- (void)testWriteAndRenameAndWrite {
+
+ // One will pass YES to |acrossReplace|, the other will pass NO.
+
+ NSFileManager *fm = [NSFileManager defaultManager];
+ GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper, nil);
+ GTMFSKQTestHelper *helper2 = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper2, nil);
+
+ // Create a temp file path
+ STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
+ NSFileHandle *testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
+ STAssertNotNil(testFH, nil);
+
+ // Start monitoring the file
+ GTMFileSystemKQueue *testKQ
+ = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)];
+ STAssertNotNil(testKQ, nil);
+ STAssertEqualObjects([testKQ path], testPath_, nil);
+ GTMFileSystemKQueue *testKQ2
+ = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:NO
+ target:helper2
+ action:@selector(callbackForQueue:events:)];
+ STAssertNotNil(testKQ2, nil);
+ STAssertEqualObjects([testKQ2 path], testPath_, nil);
+
+ // Write to the file
+ [testFH writeData:[@"doh!" dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 1, nil);
+ STAssertEquals([helper2 totals], 1, nil);
+
+ // Move it and create the file again
+ STAssertTrue([fm movePath:testPath_ toPath:testPath2_ handler:nil], nil);
+ STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
+ NSFileHandle *testFHPrime
+ = [NSFileHandle fileHandleForWritingAtPath:testPath_];
+ STAssertNotNil(testFHPrime, nil);
+ [testFHPrime writeData:[@"eh?" dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 2, nil);
+ STAssertEquals([helper2 totals], 2, nil);
+
+ // Write to the new file
+ [testFHPrime writeData:[@"continue..." dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 3, nil);
+ STAssertEquals([helper2 totals], 2, nil);
+
+ // Write to the old file
+ [testFH writeData:[@"continue old..." dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 3, nil);
+ STAssertEquals([helper2 totals], 3, nil);
+
+ // and now close old
+ [testFH closeFile];
+ STAssertTrue([fm removeFileAtPath:testPath2_ handler:nil], nil);
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 3, nil);
+ STAssertEquals([helper2 totals], 4, nil);
+
+ // and now close new
+ [testFHPrime closeFile];
+ STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 4, nil);
+ STAssertEquals([helper2 totals], 4, nil);
+
+ // Clean up the kqueue
+ [testKQ release];
+ testKQ = nil;
+ [testKQ2 release];
+ testKQ2 = nil;
+
+ STAssertEquals([helper writes], 2, nil);
+ STAssertEquals([helper deletes], 1, nil);
+ STAssertEquals([helper renames], 1, nil);
+ STAssertEquals([helper2 writes], 2, nil);
+ STAssertEquals([helper2 deletes], 1, nil);
+ STAssertEquals([helper2 renames], 1, nil);
+}
+
+@end
diff --git a/Foundation/GTMHTTPFetcher.h b/Foundation/GTMHTTPFetcher.h
index fadd9b0..5abbe1a 100644
--- a/Foundation/GTMHTTPFetcher.h
+++ b/Foundation/GTMHTTPFetcher.h
@@ -202,7 +202,7 @@
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
-#define _EXTERN extern
+#define _EXTERN GTM_EXTERN
#define _INITIALIZE_AS(x)
#endif
diff --git a/Foundation/GTMHTTPServer.h b/Foundation/GTMHTTPServer.h
index 71522df..0caa149 100644
--- a/Foundation/GTMHTTPServer.h
+++ b/Foundation/GTMHTTPServer.h
@@ -43,7 +43,7 @@
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
-#define _EXTERN extern
+#define _EXTERN GTM_EXTERN
#define _INITIALIZE_AS(x)
#endif
diff --git a/Foundation/GTMLightweightProxy.m b/Foundation/GTMLightweightProxy.m
index ad7e0a1..39f5f5c 100644
--- a/Foundation/GTMLightweightProxy.m
+++ b/Foundation/GTMLightweightProxy.m
@@ -48,9 +48,9 @@
// Even though we don't retain this object, we hang it on the lifetime
// of the calling threads pool so it's lifetime is safe for at least that
// long.
- repObject = [[representedObject_ retain] autorelease];
+ repObject = [representedObject_ retain];
}
- return repObject;
+ return [repObject autorelease];
}
- (void)setRepresentedObject:(id)object {
diff --git a/Foundation/GTMLightweightProxyTest.m b/Foundation/GTMLightweightProxyTest.m
index ad0961e..e50a9dc 100644
--- a/Foundation/GTMLightweightProxyTest.m
+++ b/Foundation/GTMLightweightProxyTest.m
@@ -31,13 +31,16 @@
@implementation GTMLightweightProxyTest
- (void)testProxy {
- id proxy = [[GTMLightweightProxy alloc] initWithRepresentedObject:self];
- STAssertEqualObjects(self, [proxy representedObject], @"Represented object setup failed");
+ id proxy
+ = [[[GTMLightweightProxy alloc] initWithRepresentedObject:self] autorelease];
+ STAssertEqualObjects(self, [proxy representedObject],
+ @"Represented object setup failed");
// Check that it identifies itself as a proxy.
STAssertTrue([proxy isProxy], @"Should identify as a proxy");
// Check that it passes class requests on
- STAssertTrue([proxy isMemberOfClass:[self class]], @"Should pass class requests through");
+ STAssertTrue([proxy isMemberOfClass:[self class]],
+ @"Should pass class requests through");
// Check that it claims to respond to its selectors.
STAssertTrue([proxy respondsToSelector:@selector(initWithRepresentedObject:)],
@@ -50,7 +53,8 @@
STAssertTrue([proxy respondsToSelector:@selector(returnYes)],
@"Claims not to respond to returnYes");
// ... but not to made up selectors.
- STAssertThrows([proxy someMadeUpMethod], @"Calling a bogus method should throw");
+ STAssertThrows([proxy someMadeUpMethod],
+ @"Calling a bogus method should throw");
// Check that callthrough works.
STAssertTrue([proxy returnYes],
@@ -59,14 +63,18 @@
// Check that nilling out the represented object works.
[proxy setRepresentedObject:nil];
STAssertTrue([proxy respondsToSelector:@selector(setRepresentedObject:)],
- @"Claims not to respond to setRepresentedObject: after nilling out represented object");
+ @"Claims not to respond to setRepresentedObject: after nilling"
+ @" out represented object");
STAssertFalse([proxy respondsToSelector:@selector(returnYes)],
- @"Claims to respond to returnYes after nilling out represented object");
+ @"Claims to respond to returnYes after nilling out represented"
+ @" object");
// Calling through once the represented object is nil should fail silently
STAssertNoThrow([proxy returnYes],
- @"Calling through without a represented object should fail silently");
+ @"Calling through without a represented object should fail"
+ @" silently");
// ... even when they are made up.
- STAssertNoThrow([proxy someMadeUpMethod], @"Calling a bogus method on a nilled proxy should not throw");
+ STAssertNoThrow([proxy someMadeUpMethod],
+ @"Calling a bogus method on a nilled proxy should not throw");
}
// Simple method to test calling through the proxy.
diff --git a/Foundation/GTMNSAppleScript+HandlerTest.m b/Foundation/GTMNSAppleScript+HandlerTest.m
index 02f62e0..16bbd81 100644
--- a/Foundation/GTMNSAppleScript+HandlerTest.m
+++ b/Foundation/GTMNSAppleScript+HandlerTest.m
@@ -455,15 +455,20 @@
NSAppleScript *script
= [[[NSAppleScript alloc] initWithSource:@"david hasselhoff"] autorelease];
[GTMUnitTestDevLog expectPattern:@"Unable to compile script: .*"];
- [GTMUnitTestDevLog expectPattern:@"Unable to coerce script -2147450879"];
+ [GTMUnitTestDevLog expectString:@"Unable to coerce script -2147450879"];
NSSet *handlers = [script gtm_handlers];
STAssertEquals([handlers count], (NSUInteger)0, @"Should have no handlers");
[GTMUnitTestDevLog expectPattern:@"Unable to compile script: .*"];
- [GTMUnitTestDevLog expectPattern:@"Unable to coerce script -2147450879"];
+ [GTMUnitTestDevLog expectString:@"Unable to coerce script -2147450879"];
NSSet *properties = [script gtm_properties];
STAssertEquals([properties count],
(NSUInteger)0,
@"Should have no properties");
+ [GTMUnitTestDevLog expectPattern:@"Unable to compile script: .*"];
+ [GTMUnitTestDevLog expectString:@"Unable to get script info about "
+ @"open handler -2147450879"];
+ STAssertFalse([script gtm_hasOpenDocumentsHandler],
+ @"Has an opendoc handler?");
}
- (void)testScriptDescriptors {
diff --git a/Foundation/GTMNSArray+Merge.h b/Foundation/GTMNSArray+Merge.h
new file mode 100644
index 0000000..8140f80
--- /dev/null
+++ b/Foundation/GTMNSArray+Merge.h
@@ -0,0 +1,47 @@
+//
+// GTMNSArray+Merge.h
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Foundation/Foundation.h>
+
+// Extension to NSArray to allow merging of arrays.
+//
+@interface NSArray (GTMNSArrayMergingAdditions)
+
+// Merge our array with |newArray| by sorting each array then merging the
+// two arrays. If |merger| is provided then call that method on any old
+// items that compare as equal to a new item, passing the new item as
+// the only argument. If |merger| is not provided, then insert new items
+// in front of matching old items. If neither array has any items then
+// nil is returned.
+//
+// The signature of the |merger| is:
+// - (id)merge:(id)newItem;
+//
+// Returns a new, sorted array.
+- (NSArray *)gtm_mergeArray:(NSArray *)newArray
+ mergeSelector:(SEL)merger;
+
+// Same as above, only |comparer| is used to sort/compare the objects, just like
+// -[NSArray sortedArrayUsingSelector]. If |comparer| is nil, nil is returned.
+- (NSArray *)gtm_mergeArray:(NSArray *)newArray
+ compareSelector:(SEL)comparer
+ mergeSelector:(SEL)merger;
+
+@end
+
+
diff --git a/Foundation/GTMNSArray+Merge.m b/Foundation/GTMNSArray+Merge.m
new file mode 100644
index 0000000..725aa8a
--- /dev/null
+++ b/Foundation/GTMNSArray+Merge.m
@@ -0,0 +1,112 @@
+//
+// GTMNSArray+Merge.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+//
+
+#import "GTMNSArray+Merge.h"
+
+#import "GTMDefines.h"
+
+#if GTM_IPHONE_SDK
+#import <objc/message.h>
+#else // GTM_IPHONE_SDK
+#import <objc/objc-runtime.h>
+#endif // GTM_IPHONE_SDK
+
+@implementation NSArray (GTMNSArrayMergingAdditions)
+
+- (NSArray *)gtm_mergeArray:(NSArray *)newArray
+ mergeSelector:(SEL)merger {
+ return [self gtm_mergeArray:newArray
+ compareSelector:@selector(compare:)
+ mergeSelector:merger];
+}
+
+- (NSArray *)gtm_mergeArray:(NSArray *)newArray
+ compareSelector:(SEL)comparer
+ mergeSelector:(SEL)merger {
+ // must have a compare selector
+ if (!comparer) return nil;
+
+ // Sort and merge the contents of |self| with |newArray|.
+ NSArray *sortedMergedArray = nil;
+ if ([self count] && [newArray count]) {
+ NSMutableArray *mergingArray = [self mutableCopy];
+ [mergingArray sortUsingSelector:comparer];
+ NSArray *sortedNewArray
+ = [newArray sortedArrayUsingSelector:comparer];
+
+ NSUInteger oldIndex = 0;
+ NSUInteger oldCount = [mergingArray count];
+ id oldItem = (oldIndex < oldCount)
+ ? [mergingArray objectAtIndex:0]
+ : nil;
+
+ id newItem = nil;
+ NSEnumerator *itemEnum = [sortedNewArray objectEnumerator];
+ while ((newItem = [itemEnum nextObject])) {
+ BOOL stillLooking = YES;
+ while (oldIndex < oldCount && stillLooking) {
+ // We must take care here, since Intel leaves junk in high bytes of
+ // return register for predicates that return BOOL.
+ // For details see:
+ // http://developer.apple.com/documentation/MacOSX/Conceptual/universal_binary/universal_binary_tips/chapter_5_section_23.html
+ // and
+ // http://www.red-sweater.com/blog/320/abusing-objective-c-with-class#comment-83187
+ NSComparisonResult result
+ = ((NSComparisonResult (*)(id, SEL, id))objc_msgSend)(newItem, comparer, oldItem);
+ if (result == NSOrderedSame && merger) {
+ // It's a match!
+ id repItem = [oldItem performSelector:merger
+ withObject:newItem];
+ [mergingArray replaceObjectAtIndex:oldIndex
+ withObject:repItem];
+ ++oldIndex;
+ oldItem = (oldIndex < oldCount)
+ ? [mergingArray objectAtIndex:oldIndex]
+ : nil;
+ stillLooking = NO;
+ } else if (result == NSOrderedAscending
+ || (result == NSOrderedSame && !merger)) {
+ // This is either a new item and belongs right here, or it's
+ // a match to an existing item but we're not merging.
+ [mergingArray insertObject:newItem
+ atIndex:oldIndex];
+ ++oldIndex;
+ ++oldCount;
+ stillLooking = NO;
+ } else {
+ ++oldIndex;
+ oldItem = (oldIndex < oldCount)
+ ? [mergingArray objectAtIndex:oldIndex]
+ : nil;
+ }
+ }
+ if (stillLooking) {
+ // Once we get here, the rest of the new items get appended.
+ [mergingArray addObject:newItem];
+ }
+ }
+ sortedMergedArray = mergingArray;
+ } else if ([self count]) {
+ sortedMergedArray = [self sortedArrayUsingSelector:comparer];
+ } else if ([newArray count]) {
+ sortedMergedArray = [newArray sortedArrayUsingSelector:comparer];
+ }
+ return sortedMergedArray;
+}
+
+@end
diff --git a/Foundation/GTMNSArray+MergeTest.m b/Foundation/GTMNSArray+MergeTest.m
new file mode 100644
index 0000000..0d8eb81
--- /dev/null
+++ b/Foundation/GTMNSArray+MergeTest.m
@@ -0,0 +1,219 @@
+//
+// GTMNSArray+MergeTest.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+//
+
+#import "GTMSenTestCase.h"
+#import "GTMNSArray+Merge.h"
+
+@interface GTMNSArray_MergeTest : GTMTestCase
+@end
+
+
+@interface NSString (GTMStringMergingTestAdditions)
+
+- (NSString *)mergeString:(NSString *)stringB;
+
+@end
+
+
+@implementation GTMNSArray_MergeTest
+
+- (void)testMergingTwoEmptyArrays {
+ NSArray *emptyArrayA = [NSArray array];
+ NSArray *emptyArrayB = [NSArray array];
+ NSArray *mergedArray = [emptyArrayA gtm_mergeArray:emptyArrayB
+ mergeSelector:nil];
+ STAssertNil(mergedArray,
+ @"merge of two empty arrays with no merger should render nil");
+}
+
+- (void)testMergingTwoEmptyArraysWithMerger {
+ NSArray *emptyArrayA = [NSArray array];
+ NSArray *emptyArrayB = [NSArray array];
+ NSArray *mergedArray
+ = [emptyArrayA gtm_mergeArray:emptyArrayB
+ mergeSelector:@selector(mergeString:)];
+ STAssertNil(mergedArray,
+ @"merge of two empty arrays with merger should render nil");
+}
+
+- (void)testMergingEmptyWithNilArray {
+ NSArray *emptyArrayA = [NSArray array];
+ NSArray *nilArrayB = nil;
+ NSArray *mergedArray = [emptyArrayA gtm_mergeArray:nilArrayB
+ mergeSelector:nil];
+ STAssertNil(mergedArray,
+ @"merge of empty with nil array with no merger should render nil");
+}
+
+- (void)testMergingEmptyWithNilArrayWithMerger {
+ NSArray *emptyArrayA = [NSArray array];
+ NSArray *nilArrayB = nil;
+ NSArray *mergedArray
+ = [emptyArrayA gtm_mergeArray:nilArrayB
+ mergeSelector:@selector(mergeObject:)];
+ STAssertNil(mergedArray,
+ @"merge of empty with nil array with merger should render nil");
+}
+
+- (void)testMergingTwoOneItemArraysThatDontMatch {
+ NSArray *arrayA = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *arrayB = [NSArray arrayWithObject:@"abc.ghi"];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:nil];
+ STAssertNotNil(mergedArray,
+ @"merge of two non empty arrays with no merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)2,
+ @"merged array should have two items");
+ STAssertEqualObjects([mergedArray objectAtIndex:0], @"abc.def", nil);
+ STAssertEqualObjects([mergedArray objectAtIndex:1], @"abc.ghi", nil);
+}
+
+- (void)testMergingTwoOneItemArraysThatDontMatchWithMerger {
+ NSArray *arrayA = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *arrayB = [NSArray arrayWithObject:@"abc.ghi"];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:@selector(mergeString:)];
+ STAssertNotNil(mergedArray,
+ @"merge of two non empty arrays with merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)2,
+ @"merged array should have two items");
+ STAssertEqualObjects([mergedArray objectAtIndex:0], @"abc.def", nil);
+ STAssertEqualObjects([mergedArray objectAtIndex:1], @"abc.ghi", nil);
+}
+
+- (void)testMergingTwoOneItemArraysThatMatch {
+ NSArray *arrayA = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *arrayB = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:nil];
+ STAssertNotNil(mergedArray,
+ @"merge of two matching arrays with no merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)2,
+ @"merged array with no merger should have two items");
+ STAssertEqualObjects([mergedArray objectAtIndex:0], @"abc.def", nil);
+ STAssertEqualObjects([mergedArray objectAtIndex:1], @"abc.def", nil);
+}
+
+- (void)testMergingTwoOneItemArraysThatMatchWithMerger {
+ NSArray *arrayA = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *arrayB = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:@selector(mergeString:)];
+ STAssertNotNil(mergedArray,
+ @"merge of two matching arrays with merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)1,
+ @"merged array with merger should have one items");
+ STAssertEqualObjects([mergedArray objectAtIndex:0], @"abc.def", nil);
+}
+
+- (void)testMergingMultipleItemArray {
+ NSArray *arrayA = [NSArray arrayWithObjects:
+ @"Kansas",
+ @"Arkansas",
+ @"Wisconson",
+ @"South Carolina",
+ nil];
+ NSArray *arrayB = [NSArray arrayWithObjects:
+ @"South Carolina",
+ @"Quebec",
+ @"British Columbia",
+ @"Arkansas",
+ @"South Hamptom",
+ nil];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:nil];
+ STAssertNotNil(mergedArray,
+ @"merge of two non empty arrays with no merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)9,
+ @"merged array should have 9 items");
+}
+
+- (void)testMergingMultipleItemArrayWithMerger {
+ NSArray *arrayA = [NSArray arrayWithObjects:
+ @"Kansas",
+ @"Arkansas",
+ @"Wisconson",
+ @"South Carolina",
+ nil];
+ NSArray *arrayB = [NSArray arrayWithObjects:
+ @"South Carolina",
+ @"Quebec",
+ @"British Columbia",
+ @"Arkansas",
+ @"South Hamptom",
+ nil];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:@selector(mergeString:)];
+ STAssertNotNil(mergedArray,
+ @"merge of two non empty arrays with merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)7,
+ @"merged array should have 7 items");
+}
+
+- (void)testMergeWithEmptyArrays {
+ NSArray *arrayA = [NSArray arrayWithObjects:@"xyz", @"abc", @"mno", nil];
+ NSArray *arrayB = [NSArray array];
+ NSArray *expected = [NSArray arrayWithObjects:@"abc", @"mno", @"xyz", nil];
+ STAssertNotNil(arrayA, nil);
+ STAssertNotNil(arrayB, nil);
+ STAssertNotNil(expected, nil);
+ NSArray *mergedArray;
+
+ // no merger
+ mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:nil];
+ STAssertNotNil(mergedArray, nil);
+ STAssertEqualObjects(mergedArray, expected, nil);
+
+ // w/ merger
+ mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:@selector(mergeString:)];
+ STAssertNotNil(mergedArray, nil);
+ STAssertEqualObjects(mergedArray, expected, nil);
+
+ // no merger and array args reversed
+ mergedArray = [arrayB gtm_mergeArray:arrayA
+ mergeSelector:nil];
+ STAssertNotNil(mergedArray, nil);
+ STAssertEqualObjects(mergedArray, expected, nil);
+
+ // w/ merger and array args reversed
+ mergedArray = [arrayB gtm_mergeArray:arrayA
+ mergeSelector:@selector(mergeString:)];
+ STAssertNotNil(mergedArray, nil);
+ STAssertEqualObjects(mergedArray, expected, nil);
+
+}
+
+@end
+
+
+@implementation NSString (GTMStringMergingTestAdditions)
+
+- (NSString *)mergeString:(NSString *)stringB {
+ return stringB;
+}
+
+@end
+
diff --git a/Foundation/GTMNSFileManager+Carbon.h b/Foundation/GTMNSFileManager+Carbon.h
new file mode 100644
index 0000000..86215ea
--- /dev/null
+++ b/Foundation/GTMNSFileManager+Carbon.h
@@ -0,0 +1,60 @@
+//
+// GTMNSFileManager+Carbon.h
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Foundation/Foundation.h>
+
+
+// A few useful methods for dealing with paths and carbon structures
+@interface NSFileManager (GTMFileManagerCarbonAdditions)
+
+// Converts a path to an alias
+// Args:
+// path - the path to convert
+//
+// Returns:
+// An alias wrapped up in an autoreleased NSData. Nil on failure.
+//
+- (NSData *)gtm_aliasDataForPath:(NSString *)path;
+
+// Converts an alias to a path
+// Args:
+// alias - an alias wrapped up in an NSData
+//
+// Returns:
+// The path. Nil on failure.
+//
+- (NSString *)gtm_pathFromAliasData:(NSData *)alias;
+
+// Converts a path to an FSRef *
+// Args:
+// path - the path to convert
+//
+// Returns:
+// An autoreleased FSRef *. Nil on failure.
+//
+- (FSRef *)gtm_FSRefForPath:(NSString *)path;
+
+// Converts an FSRef to a path
+// Args:
+// fsRef - the FSRef to convert
+//
+// Returns:
+// The path. Nil on failure.
+//
+- (NSString *)gtm_pathFromFSRef:(FSRef *)fsRef;
+@end
diff --git a/Foundation/GTMNSFileManager+Carbon.m b/Foundation/GTMNSFileManager+Carbon.m
new file mode 100644
index 0000000..4f8ba8b
--- /dev/null
+++ b/Foundation/GTMNSFileManager+Carbon.m
@@ -0,0 +1,99 @@
+//
+// GTMNSFileManager+Carbon.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMNSFileManager+Carbon.h"
+#import <CoreServices/CoreServices.h>
+#import <sys/param.h>
+#import "GTMDefines.h"
+
+@implementation NSFileManager (GTMFileManagerCarbonAdditions)
+
+- (NSData *)gtm_aliasDataForPath:(NSString *)path {
+ NSData *data = nil;
+ FSRef ref;
+ AliasHandle alias = NULL;
+
+ require_quiet([path length], CantUseParams);
+ require_noerr(FSPathMakeRef((UInt8 *)[path fileSystemRepresentation],
+ &ref, NULL), CantMakeRef);
+ require_noerr(FSNewAlias(NULL, &ref, &alias), CantMakeAlias);
+
+ Size length = GetAliasSize(alias);
+ data = [NSData dataWithBytes:*alias length:length];
+
+ DisposeHandle((Handle)alias);
+
+CantMakeAlias:
+CantMakeRef:
+CantUseParams:
+ return data;
+}
+
+- (NSString *)gtm_pathFromAliasData:(NSData *)data {
+ NSString *path = nil;
+ require_quiet(data, CantUseParams);
+
+ AliasHandle alias;
+ const void *bytes = [data bytes];
+ NSUInteger length = [data length];
+ require_noerr(PtrToHand(bytes, (Handle *)&alias, length), CantMakeHandle);
+
+ FSRef ref;
+ Boolean wasChanged;
+ // we don't use a require here because it is quite legitimate for an alias
+ // resolve to fail.
+ if (FSResolveAlias(NULL, alias, &ref, &wasChanged) == noErr) {
+ path = [self gtm_pathFromFSRef:&ref];
+ }
+ DisposeHandle((Handle)alias);
+CantMakeHandle:
+CantUseParams:
+ return path;
+}
+
+- (FSRef *)gtm_FSRefForPath:(NSString *)path {
+ FSRef* fsRef = NULL;
+ require_quiet([path length], CantUseParams);
+ NSMutableData *fsRefData = [NSMutableData dataWithLength:sizeof(FSRef)];
+ require(fsRefData, CantAllocateFSRef);
+ fsRef = (FSRef*)[fsRefData mutableBytes];
+ Boolean isDir = FALSE;
+ const UInt8 *filePath = (const UInt8 *)[path fileSystemRepresentation];
+ require_noerr_action(FSPathMakeRef(filePath, fsRef, &isDir),
+ CantMakeRef, fsRef = NULL);
+CantMakeRef:
+CantAllocateFSRef:
+CantUseParams:
+ return fsRef;
+}
+
+- (NSString *)gtm_pathFromFSRef:(FSRef *)fsRef {
+ NSString *nsPath = nil;
+ require_quiet(fsRef, CantUseParams);
+
+ char path[MAXPATHLEN];
+ require_noerr(FSRefMakePath(fsRef, (UInt8 *)path, MAXPATHLEN), CantMakePath);
+ nsPath = [self stringWithFileSystemRepresentation:path length:strlen(path)];
+ nsPath = [nsPath stringByStandardizingPath];
+
+CantMakePath:
+CantUseParams:
+ return nsPath;
+}
+
+@end
diff --git a/Foundation/GTMNSFileManager+CarbonTest.m b/Foundation/GTMNSFileManager+CarbonTest.m
new file mode 100644
index 0000000..ae58840
--- /dev/null
+++ b/Foundation/GTMNSFileManager+CarbonTest.m
@@ -0,0 +1,69 @@
+//
+// GTMNSFileManager+CarbonTest.m
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMSenTestCase.h"
+#import "GTMNSFileManager+Carbon.h"
+#import "GTMUnitTestDevLog.h"
+#import <CoreServices/CoreServices.h>
+
+@interface GTMNSFileManager_CarbonTest : GTMTestCase
+@end
+
+@implementation GTMNSFileManager_CarbonTest
+
+- (void)testAliasPathFSRefConversion {
+ NSString *path = NSHomeDirectory();
+ STAssertNotNil(path, nil);
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ FSRef *fsRef = [fileManager gtm_FSRefForPath:path];
+ STAssertNotNULL(fsRef, nil);
+ AliasHandle alias;
+ STAssertNoErr(FSNewAlias(nil, fsRef, &alias), nil);
+ STAssertNotNULL(alias, nil);
+ NSData *aliasData = [NSData dataWithBytes:*alias
+ length:GetAliasSize(alias)];
+ STAssertNotNil(aliasData, nil);
+ NSString *path2 = [fileManager gtm_pathFromAliasData:aliasData];
+ STAssertEqualObjects(path, path2, nil);
+ NSData *aliasData2 = [fileManager gtm_aliasDataForPath:path2];
+ STAssertNotNil(aliasData2, nil);
+ NSString *path3 = [fileManager gtm_pathFromAliasData:aliasData2];
+ STAssertEqualObjects(path2, path3, nil);
+ NSString *path4 = [fileManager gtm_pathFromFSRef:fsRef];
+ STAssertEqualObjects(path, path4, nil);
+
+ // Failure cases
+ [GTMUnitTestDevLog expectPattern:@"DebugAssert: "
+ @"GoogleToolboxForMac: FSPathMakeRef.*"];
+ STAssertNULL([fileManager gtm_FSRefForPath:@"/ptah/taht/dosent/esixt/"],
+ nil);
+
+ STAssertNULL([fileManager gtm_FSRefForPath:@""], nil);
+ STAssertNULL([fileManager gtm_FSRefForPath:nil], nil);
+ STAssertNil([fileManager gtm_pathFromFSRef:nil], nil);
+ STAssertNil([fileManager gtm_pathFromAliasData:nil], nil);
+ STAssertNil([fileManager gtm_pathFromAliasData:[NSData data]], nil);
+
+ [GTMUnitTestDevLog expectPattern:@"DebugAssert: "
+ @"GoogleToolboxForMac: FSPathMakeRef.*"];
+ STAssertNil([fileManager gtm_aliasDataForPath:@"/ptah/taht/dosent/esixt/"], nil);
+ STAssertNil([fileManager gtm_aliasDataForPath:@""], nil);
+ STAssertNil([fileManager gtm_aliasDataForPath:nil], nil);
+}
+
+@end
diff --git a/Foundation/GTMObjC2Runtime.h b/Foundation/GTMObjC2Runtime.h
index f901d1e..8d62abf 100644
--- a/Foundation/GTMObjC2Runtime.h
+++ b/Foundation/GTMObjC2Runtime.h
@@ -49,11 +49,8 @@
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
#import "objc/Protocol.h"
+#import <libkern/OSAtomic.h>
-#ifdef __cplusplus
-extern "C" {
-#endif
-
OBJC_EXPORT Class object_getClass(id obj);
OBJC_EXPORT const char *class_getName(Class cls);
OBJC_EXPORT BOOL class_conformsToProtocol(Class cls, Protocol *protocol);
@@ -67,7 +64,41 @@ OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protoco
SEL aSel,
BOOL isRequiredMethod,
BOOL isInstanceMethod);
-#ifdef __cplusplus
+
+// If building for 10.4 but using the 10.5 SDK, don't include these.
+#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
+// atomics
+// On Leopard these are GC aware
+// Intentionally did not include the non-barrier versions, because I couldn't
+// come up with a case personally where you wouldn't want to use the
+// barrier versions.
+GTM_INLINE bool OSAtomicCompareAndSwapPtrBarrier(void *predicate,
+ void *replacement,
+ volatile void *theValue) {
+#if defined(__LP64__) && __LP64__
+ return OSAtomicCompareAndSwap64Barrier((int64_t)predicate,
+ (int64_t)replacement,
+ (int64_t *)theValue);
+#else // defined(__LP64__) && __LP64__
+ return OSAtomicCompareAndSwap32Barrier((int32_t)predicate,
+ (int32_t)replacement,
+ (int32_t *)theValue);
+#endif // defined(__LP64__) && __LP64__
+}
+
+GTM_INLINE BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate,
+ id replacement,
+ volatile id *objectLocation) {
+ return OSAtomicCompareAndSwapPtrBarrier(predicate,
+ replacement,
+ objectLocation);
+}
+GTM_INLINE BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate,
+ id replacement,
+ volatile id *objectLocation) {
+ return OSAtomicCompareAndSwapPtrBarrier(predicate,
+ replacement,
+ objectLocation);
}
#endif
diff --git a/Foundation/GTMRegex.h b/Foundation/GTMRegex.h
index 3313e0e..c32eee2 100644
--- a/Foundation/GTMRegex.h
+++ b/Foundation/GTMRegex.h
@@ -60,7 +60,7 @@ typedef NSUInteger GTMRegexOptions;
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
-#define _EXTERN extern
+#define _EXTERN GTM_EXTERN
#define _INITIALIZE_AS(x)
#endif
diff --git a/Foundation/GTMStackTrace.h b/Foundation/GTMStackTrace.h
index 9726da5..d8cc642 100644
--- a/Foundation/GTMStackTrace.h
+++ b/Foundation/GTMStackTrace.h
@@ -51,9 +51,7 @@ struct GTMAddressDescriptor {
// #6 0x000025b9 tart () [/Users/me/./StackLog]
//
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
NSString *GTMStackTrace(void);
-#endif
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
// Returns a string containing a nicely formatted stack trace from the
@@ -63,12 +61,15 @@ NSString *GTMStackTrace(void);
NSString *GTMStackTraceFromException(NSException *e);
#endif
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
// Returns an array of program counters from the current thread's stack.
// *** You should probably use GTMStackTrace() instead of this function ***
// However, if you actually want all the PCs in "void *" form, then this
// funtion is more convenient. This will include PCs of GTMStaceTrace and
// its inner utility functions that you may want to strip out.
//
+// You can use +[NSThread callStackReturnAddresses] in 10.5 or later.
+//
// Args:
// outPcs - an array of "void *" pointers to the program counters found on the
// current thread's stack.
@@ -77,9 +78,8 @@ NSString *GTMStackTraceFromException(NSException *e);
// Returns:
// The number of program counters actually added to outPcs.
//
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count);
-#endif
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
// Returns an array of GTMAddressDescriptors from the current thread's stack.
// *** You should probably use GTMStackTrace() instead of this function ***
@@ -97,10 +97,8 @@ NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count);
// Returns:
// The number of program counters actually added to outPcs.
//
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[],
NSUInteger count);
-#endif
#ifdef __cplusplus
}
diff --git a/Foundation/GTMStackTrace.m b/Foundation/GTMStackTrace.m
index c22c153..0b28743 100644
--- a/Foundation/GTMStackTrace.m
+++ b/Foundation/GTMStackTrace.m
@@ -22,16 +22,6 @@
#include "GTMStackTrace.h"
#include "GTMObjC2Runtime.h"
-// Structure representing a small portion of a stack, starting from the saved
-// frame pointer, and continuing through the saved program counter.
-struct GTMStackFrame {
- void *saved_fp;
-#if defined (__ppc__) || defined(__ppc64__)
- void *padding;
-#endif
- void *saved_pc;
-};
-
struct GTMClassDescription {
const char *class_name;
Method *class_methods;
@@ -187,13 +177,26 @@ static NSString *GTMStackTraceFromAddressDescriptors(struct GTMAddressDescriptor
#pragma mark Public functions
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+// Before 10.5, we have to do this ourselves. 10.5 adds
+// +[NSThread callStackReturnAddresses].
+
+// Structure representing a small portion of a stack, starting from the saved
+// frame pointer, and continuing through the saved program counter.
+struct GTMStackFrame {
+ void *saved_fp;
+#if defined (__ppc__) || defined(__ppc64__)
+ void *padding;
+#endif
+ void *saved_pc;
+};
+
// __builtin_frame_address(0) is a gcc builtin that returns a pointer to the
// current frame pointer. We then use the frame pointer to walk the stack
// picking off program counters and other saved frame pointers. This works
// great on i386, but PPC requires a little more work because the PC (or link
// register) isn't always stored on the stack.
//
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count) {
if (!outPcs || (count < 1)) return 0;
@@ -220,41 +223,106 @@ NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count) {
return level;
}
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[],
NSUInteger count) {
if (count < 1 || !outDescs) return 0;
+ NSUInteger result = 0;
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+ // Before 10.5, we collect the stack ourselves.
+
void **pcs = calloc(count, sizeof(void*));
if (!pcs) return 0;
NSUInteger newSize = GTMGetStackProgramCounters(pcs, count);
- NSUInteger result
- = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, newSize);
+ result = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, newSize);
free(pcs);
+
+#else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+ // Use +[NSThread callStackReturnAddresses]
+
+ NSArray *addresses = [NSThread callStackReturnAddresses];
+ NSUInteger addrCount = [addresses count];
+ if (addrCount) {
+ void **pcs = calloc(addrCount, sizeof(void*));
+ if (pcs) {
+ void **pcsScanner = pcs;
+ for (NSNumber *address in addresses) {
+ NSUInteger addr = [address unsignedIntegerValue];
+ *pcsScanner = (void *)addr;
+ ++pcsScanner;
+ }
+ if (count < addrCount) {
+ addrCount = count;
+ }
+ // Fill in the desc structures
+ result = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, addrCount);
+ }
+ if (pcs) free(pcs);
+ }
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
return result;
}
NSString *GTMStackTrace(void) {
+ // If we don't have enough frames, return an empty string
+ NSString *result = @"";
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+ // Before 10.5, we collect the stack ourselves.
+
// The maximum number of stack frames that we will walk. We limit this so
// that super-duper recursive functions (or bugs) don't send us for an
// infinite loop.
struct GTMAddressDescriptor descs[100];
size_t depth = sizeof(descs) / sizeof(struct GTMAddressDescriptor);
depth = GTMGetStackAddressDescriptors(descs, depth);
-
+
// Start at the second item so that GTMStackTrace and it's utility calls (of
// which there is currently 1) is not included in the output.
const size_t kTracesToStrip = 2;
if (depth > kTracesToStrip) {
- return GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip],
- (depth - kTracesToStrip));
+ result = GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip],
+ (depth - kTracesToStrip));
}
- // If we didn't have enough frames, return an empty string
- return @"";
+#else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+ // Use +[NSThread callStackReturnAddresses]
+
+ NSArray *addresses = [NSThread callStackReturnAddresses];
+ NSUInteger count = [addresses count];
+ if (count) {
+ void **pcs = calloc(count, sizeof(void*));
+ struct GTMAddressDescriptor *descs
+ = calloc(count, sizeof(struct GTMAddressDescriptor));
+ if (pcs && descs) {
+ void **pcsScanner = pcs;
+ for (NSNumber *address in addresses) {
+ NSUInteger addr = [address unsignedIntegerValue];
+ *pcsScanner = (void *)addr;
+ ++pcsScanner;
+ }
+ // Fill in the desc structures
+ count = GTMGetStackAddressDescriptorsForAddresses(pcs, descs, count);
+ // Build the trace
+ // We skip 1 frame because the +[NSThread callStackReturnAddresses] will
+ // start w/ this frame.
+ const size_t kTracesToStrip = 1;
+ if (count > kTracesToStrip) {
+ result = GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip],
+ (count - kTracesToStrip));
+ }
+ }
+ if (pcs) free(pcs);
+ if (descs) free(descs);
+ }
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
+ return result;
}
-#endif // GTM_MACOS_SDK
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
@@ -287,4 +355,4 @@ NSString *GTMStackTraceFromException(NSException *e) {
return trace;
}
-#endif // MAC_OS_X_VERSION_MIN_REQUIRED
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
diff --git a/Foundation/GTMStackTraceTest.m b/Foundation/GTMStackTraceTest.m
index 01b02a3..457f3e9 100644
--- a/Foundation/GTMStackTraceTest.m
+++ b/Foundation/GTMStackTraceTest.m
@@ -25,7 +25,6 @@
@implementation GTMStackTraceTest
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
- (void)testStackTraceBasic {
NSString *stacktrace = GTMStackTrace();
NSArray *stacklines = [stacktrace componentsSeparatedByString:@"\n"];
@@ -45,7 +44,6 @@
@"First frame should contain #0, stack trace: %@",
stacktrace);
}
-#endif // GTM_MACOS_SDK
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
@@ -84,7 +82,8 @@
#endif
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+
- (void)testProgramCountersBasic {
void *pcs[10];
NSUInteger depth = 10;
@@ -120,6 +119,7 @@
void *current_pc = __builtin_return_address(0);
STAssertEquals(pcs2[1], current_pc, @"pcs[1] should equal the current PC");
}
-#endif // GTM_MACOS_SDK
+
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
@end
diff --git a/Foundation/GTMSystemVersion.h b/Foundation/GTMSystemVersion.h
index 959d2af..07580a7 100644
--- a/Foundation/GTMSystemVersion.h
+++ b/Foundation/GTMSystemVersion.h
@@ -72,14 +72,15 @@
// Architecture Strings
// TODO: Should probably break iPhone up into iPhone_ARM and iPhone_Simulator
// but haven't found a need yet.
-extern NSString *const kGTMArch_iPhone;
-extern NSString *const kGTMArch_ppc;
-extern NSString *const kGTMArch_ppc64;
-extern NSString *const kGTMArch_x86_64;
-extern NSString *const kGTMArch_i386;
+GTM_EXTERN NSString *const kGTMArch_iPhone;
+GTM_EXTERN NSString *const kGTMArch_ppc;
+GTM_EXTERN NSString *const kGTMArch_ppc64;
+GTM_EXTERN NSString *const kGTMArch_x86_64;
+GTM_EXTERN NSString *const kGTMArch_i386;
// System Build Number constants
-extern NSString *const kGTMSystemBuild10_5_5;
-extern NSString *const kGTMSystemBuild10_6_0_WWDC;
+GTM_EXTERN NSString *const kGTMSystemBuild10_5_5;
+GTM_EXTERN NSString *const kGTMSystemBuild10_6_0_WWDC;
+GTM_EXTERN NSString *const kGTMSystemBuild10_6_0_10A190;
diff --git a/Foundation/GTMSystemVersion.m b/Foundation/GTMSystemVersion.m
index 868f6b8..f9c7861 100644
--- a/Foundation/GTMSystemVersion.m
+++ b/Foundation/GTMSystemVersion.m
@@ -37,6 +37,7 @@ static NSString *const kSystemVersionPlistPath = @"/System/Library/CoreServices/
NSString *const kGTMSystemBuild10_5_5 = @"9F33";
NSString *const kGTMSystemBuild10_6_0_WWDC = @"10A96";
+NSString *const kGTMSystemBuild10_6_0_10A190 = @"10A190";
@implementation GTMSystemVersion
+ (void)initialize {
diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj
index 745f920..8b241aa 100644
--- a/GTM.xcodeproj/project.pbxproj
+++ b/GTM.xcodeproj/project.pbxproj
@@ -42,6 +42,9 @@
33C374380DD8D44800E97817 /* GTMNSDictionary+URLArguments.h in Headers */ = {isa = PBXBuildFile; fileRef = 33C374360DD8D44800E97817 /* GTMNSDictionary+URLArguments.h */; settings = {ATTRIBUTES = (Public, ); }; };
33C374390DD8D44800E97817 /* GTMNSDictionary+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 33C374370DD8D44800E97817 /* GTMNSDictionary+URLArguments.m */; };
33C3745F0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 33C3745E0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m */; };
+ 629445400EDDF647009295EA /* GTMNSArray+Merge.h in Headers */ = {isa = PBXBuildFile; fileRef = 6294453E0EDDF647009295EA /* GTMNSArray+Merge.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 629445410EDDF647009295EA /* GTMNSArray+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 6294453F0EDDF647009295EA /* GTMNSArray+Merge.m */; };
+ 6294454C0EDDF89A009295EA /* GTMNSArray+MergeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6294454B0EDDF89A009295EA /* GTMNSArray+MergeTest.m */; };
7F3EB38E0E5E09C700A7A75E /* GTMNSImage+Scaling.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F3EB38C0E5E09C700A7A75E /* GTMNSImage+Scaling.h */; settings = {ATTRIBUTES = (Public, ); }; };
7F3EB38F0E5E09C700A7A75E /* GTMNSImage+Scaling.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F3EB38D0E5E09C700A7A75E /* GTMNSImage+Scaling.m */; };
7F3EB3940E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F3EB3930E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m */; };
@@ -62,13 +65,16 @@
8B3344210DBF7A36009FD32C /* GTMNSAppleScript+HandlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3344170DBF7A36009FD32C /* GTMNSAppleScript+HandlerTest.m */; };
8B3344230DBF7A36009FD32C /* GTMNSAppleEventDescriptor+HandlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B33441A0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+HandlerTest.m */; };
8B3344250DBF7A36009FD32C /* GTMNSAppleEventDescriptor+FoundationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B33441D0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+FoundationTest.m */; };
- 8B33455E0DBF8844009FD32C /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */; };
8B3345890DBF8A55009FD32C /* GTMNSAppleEvent+HandlerTest.applescript in AppleScript */ = {isa = PBXBuildFile; fileRef = 8B3344200DBF7A36009FD32C /* GTMNSAppleEvent+HandlerTest.applescript */; settings = {ATTRIBUTES = (Debug, ); }; };
8B3590160E8190FA0041E21C /* GTMTestTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B3590150E8190FA0041E21C /* GTMTestTimer.h */; settings = {ATTRIBUTES = (Public, ); }; };
8B35901B0E8191750041E21C /* GTMTestTimerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B35901A0E8191750041E21C /* GTMTestTimerTest.m */; };
8B3AA9F10E033E23007E31B5 /* GTMValidatingContainers.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B3AA9EF0E033E23007E31B5 /* GTMValidatingContainers.h */; settings = {ATTRIBUTES = (Public, ); }; };
8B3AA9F20E033E23007E31B5 /* GTMValidatingContainers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9F00E033E23007E31B5 /* GTMValidatingContainers.m */; };
8B3AA9F80E033E5F007E31B5 /* GTMValidatingContainersTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9F70E033E5F007E31B5 /* GTMValidatingContainersTest.m */; };
+ 8B3E292E0EEB53F8000681D8 /* GTMCarbonEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3E292A0EEB53F3000681D8 /* GTMCarbonEvent.m */; };
+ 8B3E292F0EEB53F8000681D8 /* GTMCarbonEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B3E292B0EEB53F3000681D8 /* GTMCarbonEvent.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8B3E2A3F0EEB564D000681D8 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */; };
+ 8B3E2A410EEB565B000681D8 /* GTMUnitTestingUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B45A2680DA498A0001148C5 /* GTMUnitTestingUtilities.m */; };
8B45A03A0DA46A2A001148C5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; };
8B45A0B80DA46A2F001148C5 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */; };
8B45A0D50DA46A57001148C5 /* GTMNSObject+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE29C0D198D36009257D2 /* GTMNSObject+UnitTesting.m */; };
@@ -120,6 +126,14 @@
8B7DCE1B0DFF39850017E983 /* GTMSenTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCE180DFF39850017E983 /* GTMSenTestCase.m */; };
8B7DCEF10E002C210017E983 /* GTMDevLogUnitTestingBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCBE10DFF18720017E983 /* GTMDevLogUnitTestingBridge.m */; };
8B7E35750E048E2D00EF70C8 /* GTMHTTPFetcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F435E3930DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m */; };
+ 8B8B10290EEB8B1600E543D0 /* GTMHotKeyTextFieldTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F4A420EE0EDDF8E000397A11 /* GTMHotKeyTextFieldTest.m */; };
+ 8B8B102A0EEB8B2900E543D0 /* GTMCarbonEventTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3E29290EEB53F3000681D8 /* GTMCarbonEventTest.m */; };
+ 8B8B10F90EEB8B9E00E543D0 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */; };
+ 8B8B10FD0EEB8BC300E543D0 /* GTMUnitTestingUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B45A2680DA498A0001148C5 /* GTMUnitTestingUtilities.m */; };
+ 8B8B11000EEB8CD000E543D0 /* GTMGetURLHandlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B8B10FF0EEB8CD000E543D0 /* GTMGetURLHandlerTest.m */; };
+ 8B8EC87D0EF17C270044D13F /* GTMNSFileManager+Carbon.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B8EC87B0EF17C270044D13F /* GTMNSFileManager+Carbon.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8B8EC87E0EF17C270044D13F /* GTMNSFileManager+Carbon.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B8EC87C0EF17C270044D13F /* GTMNSFileManager+Carbon.m */; };
+ 8B8EC8800EF17C2F0044D13F /* GTMNSFileManager+CarbonTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B8EC87F0EF17C2F0044D13F /* GTMNSFileManager+CarbonTest.m */; };
8BC045C20DAE899100C2D1CA /* GTMGeometryUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */; };
8BC046B90DAE8C4B00C2D1CA /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BC046B80DAE8C4B00C2D1CA /* ApplicationServices.framework */; };
8BC04CD80DB003D800C2D1CA /* GTMMethodCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6F31F40DA3489B0052CA40 /* GTMMethodCheck.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -219,6 +233,11 @@
F47F1D300D4914AD00925B8F /* GTMCalculatedRange.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1D2D0D4914AD00925B8F /* GTMCalculatedRange.h */; settings = {ATTRIBUTES = (Public, ); }; };
F47F1D310D4914AD00925B8F /* GTMCalculatedRange.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1D2E0D4914AD00925B8F /* GTMCalculatedRange.m */; };
F47F1D350D4914B600925B8F /* GTMCalculatedRangeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1D2F0D4914AD00925B8F /* GTMCalculatedRangeTest.m */; };
+ F49FA8440EEF2AB700077669 /* GTMFileSystemKQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = F49FA8420EEF2AB700077669 /* GTMFileSystemKQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ F49FA8450EEF2AB700077669 /* GTMFileSystemKQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = F49FA8430EEF2AB700077669 /* GTMFileSystemKQueue.m */; };
+ F49FA88B0EEF303D00077669 /* GTMFileSystemKQueueTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F49FA88A0EEF303D00077669 /* GTMFileSystemKQueueTest.m */; };
+ F4A420EF0EDDF8E000397A11 /* GTMHotKeyTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = F4A420EC0EDDF8E000397A11 /* GTMHotKeyTextField.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ F4A420F00EDDF8E000397A11 /* GTMHotKeyTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = F4A420ED0EDDF8E000397A11 /* GTMHotKeyTextField.m */; };
F4BC1C880DDDD45D00108B7D /* GTMHTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = F4BC1C860DDDD45D00108B7D /* GTMHTTPServer.h */; settings = {ATTRIBUTES = (Public, ); }; };
F4BC1C890DDDD45D00108B7D /* GTMHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = F4BC1C870DDDD45D00108B7D /* GTMHTTPServer.m */; };
F4BC1E8D0DE1FC4A00108B7D /* GTMHTTPServerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F4BC1E8C0DE1FC4A00108B7D /* GTMHTTPServerTest.m */; };
@@ -309,6 +328,9 @@
33C374360DD8D44800E97817 /* GTMNSDictionary+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSDictionary+URLArguments.h"; sourceTree = "<group>"; };
33C374370DD8D44800E97817 /* GTMNSDictionary+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArguments.m"; sourceTree = "<group>"; };
33C3745E0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArgumentsTest.m"; sourceTree = "<group>"; };
+ 6294453E0EDDF647009295EA /* GTMNSArray+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSArray+Merge.h"; sourceTree = "<group>"; };
+ 6294453F0EDDF647009295EA /* GTMNSArray+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSArray+Merge.m"; sourceTree = "<group>"; };
+ 6294454B0EDDF89A009295EA /* GTMNSArray+MergeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSArray+MergeTest.m"; sourceTree = "<group>"; };
7F3EB38C0E5E09C700A7A75E /* GTMNSImage+Scaling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSImage+Scaling.h"; sourceTree = "<group>"; };
7F3EB38D0E5E09C700A7A75E /* GTMNSImage+Scaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSImage+Scaling.m"; sourceTree = "<group>"; };
7F3EB3930E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSImage+ScalingTest.m"; sourceTree = "<group>"; };
@@ -345,6 +367,9 @@
8B3AA9EF0E033E23007E31B5 /* GTMValidatingContainers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMValidatingContainers.h; sourceTree = "<group>"; };
8B3AA9F00E033E23007E31B5 /* GTMValidatingContainers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMValidatingContainers.m; sourceTree = "<group>"; };
8B3AA9F70E033E5F007E31B5 /* GTMValidatingContainersTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMValidatingContainersTest.m; sourceTree = "<group>"; };
+ 8B3E29290EEB53F3000681D8 /* GTMCarbonEventTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCarbonEventTest.m; sourceTree = "<group>"; };
+ 8B3E292A0EEB53F3000681D8 /* GTMCarbonEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCarbonEvent.m; sourceTree = "<group>"; };
+ 8B3E292B0EEB53F3000681D8 /* GTMCarbonEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMCarbonEvent.h; sourceTree = "<group>"; };
8B45A0280DA4696C001148C5 /* UnitTest - UnitTesting.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UnitTest - UnitTesting.octest"; sourceTree = BUILT_PRODUCTS_DIR; };
8B45A1990DA46AAA001148C5 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = /System/Library/Frameworks/QuartzCore.framework; sourceTree = "<absolute>"; };
8B45A2670DA498A0001148C5 /* GTMUnitTestingUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMUnitTestingUtilities.h; sourceTree = "<group>"; };
@@ -381,6 +406,10 @@
8B7DCBEC0DFF1A4F0017E983 /* GTMUnitTestDevLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMUnitTestDevLog.m; sourceTree = "<group>"; };
8B7DCBF00DFF1A610017E983 /* GTMUnitTestDevLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMUnitTestDevLog.h; sourceTree = "<group>"; };
8B7DCE180DFF39850017E983 /* GTMSenTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSenTestCase.m; sourceTree = "<group>"; };
+ 8B8B10FF0EEB8CD000E543D0 /* GTMGetURLHandlerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMGetURLHandlerTest.m; sourceTree = "<group>"; };
+ 8B8EC87B0EF17C270044D13F /* GTMNSFileManager+Carbon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSFileManager+Carbon.h"; sourceTree = "<group>"; };
+ 8B8EC87C0EF17C270044D13F /* GTMNSFileManager+Carbon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSFileManager+Carbon.m"; sourceTree = "<group>"; };
+ 8B8EC87F0EF17C2F0044D13F /* GTMNSFileManager+CarbonTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSFileManager+CarbonTest.m"; sourceTree = "<group>"; };
8BC046B80DAE8C4B00C2D1CA /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = /System/Library/Frameworks/ApplicationServices.framework; sourceTree = "<absolute>"; };
8BC04D140DB0061300C2D1CA /* RunMacOSUnitTests.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = RunMacOSUnitTests.sh; sourceTree = "<group>"; };
8BE2836B0DED0F130035B3F8 /* GTMFourCharCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMFourCharCode.m; sourceTree = "<group>"; };
@@ -488,6 +517,12 @@
F48FE29C0D198D36009257D2 /* GTMNSObject+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSObject+UnitTesting.m"; sourceTree = "<group>"; };
F48FE29F0D198D36009257D2 /* GTMSenTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMSenTestCase.h; sourceTree = "<group>"; };
F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersionTest.m; sourceTree = "<group>"; };
+ F49FA8420EEF2AB700077669 /* GTMFileSystemKQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMFileSystemKQueue.h; sourceTree = "<group>"; };
+ F49FA8430EEF2AB700077669 /* GTMFileSystemKQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMFileSystemKQueue.m; sourceTree = "<group>"; };
+ F49FA88A0EEF303D00077669 /* GTMFileSystemKQueueTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMFileSystemKQueueTest.m; sourceTree = "<group>"; };
+ F4A420EC0EDDF8E000397A11 /* GTMHotKeyTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHotKeyTextField.h; sourceTree = "<group>"; };
+ F4A420ED0EDDF8E000397A11 /* GTMHotKeyTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHotKeyTextField.m; sourceTree = "<group>"; };
+ F4A420EE0EDDF8E000397A11 /* GTMHotKeyTextFieldTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHotKeyTextFieldTest.m; sourceTree = "<group>"; };
F4BC1C860DDDD45D00108B7D /* GTMHTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPServer.h; sourceTree = "<group>"; };
F4BC1C870DDDD45D00108B7D /* GTMHTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPServer.m; sourceTree = "<group>"; };
F4BC1E8C0DE1FC4A00108B7D /* GTMHTTPServerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPServerTest.m; sourceTree = "<group>"; };
@@ -525,6 +560,7 @@
8B45A03A0DA46A2A001148C5 /* Foundation.framework in Frameworks */,
8B45A19A0DA46AAA001148C5 /* QuartzCore.framework in Frameworks */,
8B7DCBA50DFF0EFF0017E983 /* GoogleToolboxForMac.framework in Frameworks */,
+ 8B3E2A3F0EEB564D000681D8 /* Carbon.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -544,7 +580,6 @@
F42E08800D199AB500D5DDE0 /* GoogleToolboxForMac.framework in Frameworks */,
F42E089D0D199B1800D5DDE0 /* SenTestingKit.framework in Frameworks */,
8BC046B90DAE8C4B00C2D1CA /* ApplicationServices.framework in Frameworks */,
- 8B33455E0DBF8844009FD32C /* Carbon.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -566,6 +601,7 @@
F42E08610D199A2B00D5DDE0 /* Cocoa.framework in Frameworks */,
F42E087F0D199AB400D5DDE0 /* GoogleToolboxForMac.framework in Frameworks */,
F42E089C0D199B1800D5DDE0 /* SenTestingKit.framework in Frameworks */,
+ 8B8B10F90EEB8B9E00E543D0 /* Carbon.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -702,9 +738,16 @@
F48FE26F0D198CBA009257D2 /* AppKit */ = {
isa = PBXGroup;
children = (
+ 8B3E292B0EEB53F3000681D8 /* GTMCarbonEvent.h */,
+ 8B3E292A0EEB53F3000681D8 /* GTMCarbonEvent.m */,
+ 8B3E29290EEB53F3000681D8 /* GTMCarbonEventTest.m */,
F48FE27C0D198D0E009257D2 /* GTMDelegatingTableColumn.h */,
F48FE27D0D198D0E009257D2 /* GTMDelegatingTableColumn.m */,
8B58E9940E547EB000A0E02E /* GTMGetURLHandler.m */,
+ 8B8B10FF0EEB8CD000E543D0 /* GTMGetURLHandlerTest.m */,
+ F4A420EC0EDDF8E000397A11 /* GTMHotKeyTextField.h */,
+ F4A420ED0EDDF8E000397A11 /* GTMHotKeyTextField.m */,
+ F4A420EE0EDDF8E000397A11 /* GTMHotKeyTextFieldTest.m */,
8B1801A10E2533D500280961 /* GTMLargeTypeWindow.h */,
8B1801A00E2533D500280961 /* GTMLargeTypeWindow.m */,
8B1801A40E2533DB00280961 /* GTMLargeTypeWindowTest.m */,
@@ -751,6 +794,9 @@
8B1B49160E5F8E2100A08972 /* GTMExceptionalInlines.h */,
8B1B49170E5F8E2100A08972 /* GTMExceptionalInlines.m */,
8B1B491B0E5F904C00A08972 /* GTMExceptionalInlinesTest.m */,
+ F49FA8420EEF2AB700077669 /* GTMFileSystemKQueue.h */,
+ F49FA8430EEF2AB700077669 /* GTMFileSystemKQueue.m */,
+ F49FA88A0EEF303D00077669 /* GTMFileSystemKQueueTest.m */,
8BE2836B0DED0F130035B3F8 /* GTMFourCharCode.m */,
8BE2836C0DED0F130035B3F8 /* GTMFourCharCodeTest.m */,
8BE2836D0DED0F130035B3F8 /* GTMFourCharCode.h */,
@@ -779,12 +825,18 @@
F95803F60E2FB0760049A088 /* GTMLoggerRingBufferWriter.h */,
F95803F70E2FB0760049A088 /* GTMLoggerRingBufferWriter.m */,
F95803F80E2FB0760049A088 /* GTMLoggerRingBufferWriterTest.m */,
+ 6294453E0EDDF647009295EA /* GTMNSArray+Merge.h */,
+ 6294453F0EDDF647009295EA /* GTMNSArray+Merge.m */,
+ 6294454B0EDDF89A009295EA /* GTMNSArray+MergeTest.m */,
33C374360DD8D44800E97817 /* GTMNSDictionary+URLArguments.h */,
33C374370DD8D44800E97817 /* GTMNSDictionary+URLArguments.m */,
33C3745E0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m */,
F43E4DD60D4E56320041161F /* GTMNSEnumerator+Filter.h */,
F43E4DD70D4E56320041161F /* GTMNSEnumerator+Filter.m */,
F43E4DD80D4E56320041161F /* GTMNSEnumerator+FilterTest.m */,
+ 8B8EC87B0EF17C270044D13F /* GTMNSFileManager+Carbon.h */,
+ 8B8EC87C0EF17C270044D13F /* GTMNSFileManager+Carbon.m */,
+ 8B8EC87F0EF17C2F0044D13F /* GTMNSFileManager+CarbonTest.m */,
F413908C0D75F63C00F72B31 /* GTMNSFileManager+Path.h */,
F413908D0D75F63C00F72B31 /* GTMNSFileManager+Path.m */,
F413908E0D75F63C00F72B31 /* GTMNSFileManager+PathTest.m */,
@@ -968,6 +1020,11 @@
8B3590160E8190FA0041E21C /* GTMTestTimer.h in Headers */,
8B6F4B630E8856CA00425D9F /* GTMDebugThreadValidation.h in Headers */,
F41711350ECDFBD500B9B276 /* GTMLightweightProxy.h in Headers */,
+ 629445400EDDF647009295EA /* GTMNSArray+Merge.h in Headers */,
+ F4A420EF0EDDF8E000397A11 /* GTMHotKeyTextField.h in Headers */,
+ 8B3E292F0EEB53F8000681D8 /* GTMCarbonEvent.h in Headers */,
+ F49FA8440EEF2AB700077669 /* GTMFileSystemKQueue.h in Headers */,
+ 8B8EC87D0EF17C270044D13F /* GTMNSFileManager+Carbon.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1247,6 +1304,7 @@
8B7DCBEF0DFF1A4F0017E983 /* GTMUnitTestDevLog.m in Sources */,
8B7DCE1B0DFF39850017E983 /* GTMSenTestCase.m in Sources */,
8B35901B0E8191750041E21C /* GTMTestTimerTest.m in Sources */,
+ 8B3E2A410EEB565B000681D8 /* GTMUnitTestingUtilities.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1305,6 +1363,9 @@
8B1B49260E5F97C800A08972 /* GTMExceptionalInlinesTest.m in Sources */,
8BE839AA0E8AF72E00C611B0 /* GTMDebugThreadValidationTest.m in Sources */,
F41711380ECDFBE100B9B276 /* GTMLightweightProxyTest.m in Sources */,
+ 6294454C0EDDF89A009295EA /* GTMNSArray+MergeTest.m in Sources */,
+ F49FA88B0EEF303D00077669 /* GTMFileSystemKQueueTest.m in Sources */,
+ 8B8EC8800EF17C2F0044D13F /* GTMNSFileManager+CarbonTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1356,6 +1417,11 @@
7F3EB38F0E5E09C700A7A75E /* GTMNSImage+Scaling.m in Sources */,
8B6F4B640E8856CA00425D9F /* GTMDebugThreadValidation.m in Sources */,
F41711360ECDFBD500B9B276 /* GTMLightweightProxy.m in Sources */,
+ 629445410EDDF647009295EA /* GTMNSArray+Merge.m in Sources */,
+ F4A420F00EDDF8E000397A11 /* GTMHotKeyTextField.m in Sources */,
+ 8B3E292E0EEB53F8000681D8 /* GTMCarbonEvent.m in Sources */,
+ F49FA8450EEF2AB700077669 /* GTMFileSystemKQueue.m in Sources */,
+ 8B8EC87E0EF17C270044D13F /* GTMNSFileManager+Carbon.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1375,6 +1441,10 @@
8B7DCE190DFF39850017E983 /* GTMSenTestCase.m in Sources */,
7F3EB3940E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m in Sources */,
7F3EB5870E5F0CBB00A7A75E /* GTMLargeTypeWindowTest.m in Sources */,
+ 8B8B10290EEB8B1600E543D0 /* GTMHotKeyTextFieldTest.m in Sources */,
+ 8B8B102A0EEB8B2900E543D0 /* GTMCarbonEventTest.m in Sources */,
+ 8B8B10FD0EEB8BC300E543D0 /* GTMUnitTestingUtilities.m in Sources */,
+ 8B8B11000EEB8CD000E543D0 /* GTMGetURLHandlerTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1441,6 +1511,11 @@
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COMPONENT_SIGNATURE=\"\\'GTM \\'\"",
+ "kComponentSignatureString=\"\\\"GoogleToolboxForMac\\\"\"",
+ );
GCC_WARN_SHADOW = YES;
GTM_EXTRA_WARNING_OVERRIDE_CFLAGS = "-Wno-unused-parameter";
};
@@ -1452,6 +1527,11 @@
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COMPONENT_SIGNATURE=\"\\'GTM \\'\"",
+ "kComponentSignatureString=\"\\\"GoogleToolboxForMac\\\"\"",
+ );
GCC_WARN_SHADOW = YES;
GTM_EXTRA_WARNING_OVERRIDE_CFLAGS = "-Wno-unused-parameter";
};
@@ -1463,6 +1543,11 @@
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COMPONENT_SIGNATURE=\"\\'GTM \\'\"",
+ "kComponentSignatureString=\"\\\"GoogleToolboxForMac\\\"\"",
+ );
GCC_WARN_SHADOW = YES;
GTM_EXTRA_WARNING_OVERRIDE_CFLAGS = "-Wno-unused-parameter";
};
@@ -1499,6 +1584,7 @@
GCC_ENABLE_OBJC_GC = supported;
INFOPLIST_FILE = "UnitTest-Info.plist";
PRODUCT_NAME = "UnitTest - AppKit";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness";
};
name = "SnowLeopardOrLater-Debug";
};
@@ -1563,6 +1649,11 @@
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COMPONENT_SIGNATURE=\"\\'GTM \\'\"",
+ "kComponentSignatureString=\"\\\"GoogleToolboxForMac\\\"\"",
+ );
GCC_WARN_SHADOW = YES;
GTM_EXTRA_WARNING_OVERRIDE_CFLAGS = "-Wno-unused-parameter";
OTHER_LDFLAGS = "-lgcov";
@@ -1575,6 +1666,11 @@
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COMPONENT_SIGNATURE=\"\\'GTM \\'\"",
+ "kComponentSignatureString=\"\\\"GoogleToolboxForMac\\\"\"",
+ );
GCC_WARN_SHADOW = YES;
GTM_EXTRA_WARNING_OVERRIDE_CFLAGS = "-Wno-unused-parameter";
};
@@ -1631,6 +1727,7 @@
GCC_ENABLE_OBJC_GC = supported;
INFOPLIST_FILE = "UnitTest-Info.plist";
PRODUCT_NAME = "UnitTest - AppKit";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness";
};
name = "SnowLeopardOrLater-Debug-gcov";
};
@@ -1645,6 +1742,7 @@
GCC_ENABLE_OBJC_GC = supported;
INFOPLIST_FILE = "UnitTest-Info.plist";
PRODUCT_NAME = "UnitTest - AppKit";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness";
};
name = "SnowLeopardOrLater-Release";
};
@@ -1980,6 +2078,11 @@
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COMPONENT_SIGNATURE=\"\\'GTM \\'\"",
+ "kComponentSignatureString=\"\\\"GoogleToolboxForMac\\\"\"",
+ );
GCC_WARN_SHADOW = YES;
GTM_EXTRA_WARNING_OVERRIDE_CFLAGS = "-Wno-unused-parameter";
LIBRARY_SEARCH_PATHS = (
@@ -2020,6 +2123,7 @@
);
INFOPLIST_FILE = "UnitTest-Info.plist";
PRODUCT_NAME = "UnitTest - AppKit";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness";
};
name = "TigerOrLater-Debug-gcov";
};
@@ -2047,6 +2151,7 @@
);
INFOPLIST_FILE = "UnitTest-Info.plist";
PRODUCT_NAME = "UnitTest - AppKit";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness";
};
name = "TigerOrLater-Debug";
};
@@ -2060,6 +2165,7 @@
);
INFOPLIST_FILE = "UnitTest-Info.plist";
PRODUCT_NAME = "UnitTest - AppKit";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness";
};
name = "TigerOrLater-Release";
};
@@ -2074,6 +2180,11 @@
);
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COMPONENT_SIGNATURE=\"\\'GTM \\'\"",
+ "kComponentSignatureString=\"\\\"GoogleToolboxForMac\\\"\"",
+ );
GCC_WARN_SHADOW = YES;
GTM_EXTRA_WARNING_OVERRIDE_CFLAGS = "-Wno-unused-parameter";
};
@@ -2110,6 +2221,7 @@
GCC_ENABLE_OBJC_GC = supported;
INFOPLIST_FILE = "UnitTest-Info.plist";
PRODUCT_NAME = "UnitTest - AppKit";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness";
};
name = "LeopardOrLater-Debug";
};
@@ -2165,6 +2277,11 @@
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COMPONENT_SIGNATURE=\"\\'GTM \\'\"",
+ "kComponentSignatureString=\"\\\"GoogleToolboxForMac\\\"\"",
+ );
GCC_WARN_SHADOW = YES;
GTM_EXTRA_WARNING_OVERRIDE_CFLAGS = "-Wno-unused-parameter";
OTHER_LDFLAGS = "-lgcov";
@@ -2202,6 +2319,7 @@
GCC_ENABLE_OBJC_GC = supported;
INFOPLIST_FILE = "UnitTest-Info.plist";
PRODUCT_NAME = "UnitTest - AppKit";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness";
};
name = "LeopardOrLater-Debug-gcov";
};
@@ -2255,6 +2373,11 @@
);
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COMPONENT_SIGNATURE=\"\\'GTM \\'\"",
+ "kComponentSignatureString=\"\\\"GoogleToolboxForMac\\\"\"",
+ );
GCC_WARN_SHADOW = YES;
GTM_EXTRA_WARNING_OVERRIDE_CFLAGS = "-Wno-unused-parameter";
};
@@ -2291,6 +2414,7 @@
GCC_ENABLE_OBJC_GC = supported;
INFOPLIST_FILE = "UnitTest-Info.plist";
PRODUCT_NAME = "UnitTest - AppKit";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness";
};
name = "LeopardOrLater-Release";
};
diff --git a/GTMDefines.h b/GTMDefines.h
index bd37b3e..968f868 100644
--- a/GTMDefines.h
+++ b/GTMDefines.h
@@ -22,10 +22,10 @@
// Not all MAC_OS_X_VERSION_10_X macros defined in past SDKs
#ifndef MAC_OS_X_VERSION_10_5
-# define MAC_OS_X_VERSION_10_5 1050
+ #define MAC_OS_X_VERSION_10_5 1050
#endif
#ifndef MAC_OS_X_VERSION_10_6
-# define MAC_OS_X_VERSION_10_6 1060
+ #define MAC_OS_X_VERSION_10_6 1060
#endif
// ----------------------------------------------------------------------------
@@ -39,10 +39,10 @@
// the code by providing your own definitions for these w/in a prefix header.
//
#ifndef GTM_HTTPFETCHER_ENABLE_LOGGING
-# define GTM_HTTPFETCHER_ENABLE_LOGGING 1
+ #define GTM_HTTPFETCHER_ENABLE_LOGGING 1
#endif // GTM_HTTPFETCHER_ENABLE_LOGGING
#ifndef GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING
-# define GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING 0
+ #define GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING 0
#endif // GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING
// By setting the GTM_CONTAINERS_VALIDATION_FAILED_LOG and
@@ -50,7 +50,28 @@
// when a validation fails. If you implement your own validators, you may want
// to control their internals using the same macros for consistency.
#ifndef GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
-#define GTM_CONTAINERS_VALIDATION_FAILED_ASSERT 0
+ #define GTM_CONTAINERS_VALIDATION_FAILED_ASSERT 0
+#endif
+
+// Give ourselves a consistent way to do inlines. Apple's macros even use
+// a few different actual definitions, so we're based off of the foundation
+// one.
+#if !defined(GTM_INLINE)
+ #if defined (__GNUC__) && (__GNUC__ == 4)
+ #define GTM_INLINE static __inline__ __attribute__((always_inline))
+ #else
+ #define GTM_INLINE static __inline__
+ #endif
+#endif
+
+// Give ourselves a consistent way of doing externs that links up nicely
+// when mixing objc and objc++
+#if !defined (GTM_EXTERN)
+ #if defined __cplusplus
+ #define GTM_EXTERN extern "C"
+ #else
+ #define GTM_EXTERN extern
+ #endif
#endif
// _GTMDevLog & _GTMDevAssert
@@ -76,9 +97,9 @@
#ifndef _GTMDevLog
#ifdef DEBUG
- #define _GTMDevLog(...) NSLog(__VA_ARGS__)
+ #define _GTMDevLog(...) NSLog(__VA_ARGS__)
#else
- #define _GTMDevLog(...) do { } while (0)
+ #define _GTMDevLog(...) do { } while (0)
#endif
#endif // _GTMDevLog
@@ -86,24 +107,24 @@
// Declared here so that it can easily be used for logging tracking if
// necessary. See GTMUnitTestDevLog.h for details.
@class NSString;
-extern void _GTMUnitTestDevLog(NSString *format, ...);
+GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...);
#ifndef _GTMDevAssert
// we directly invoke the NSAssert handler so we can pass on the varargs
// (NSAssert doesn't have a macro we can use that takes varargs)
#if !defined(NS_BLOCK_ASSERTIONS)
-#define _GTMDevAssert(condition, ...) \
- do { \
- if (!(condition)) { \
- [[NSAssertionHandler currentHandler] \
- handleFailureInFunction:[NSString stringWithCString:__PRETTY_FUNCTION__] \
- file:[NSString stringWithCString:__FILE__] \
- lineNumber:__LINE__ \
- description:__VA_ARGS__]; \
- } \
- } while(0)
+ #define _GTMDevAssert(condition, ...) \
+ do { \
+ if (!(condition)) { \
+ [[NSAssertionHandler currentHandler] \
+ handleFailureInFunction:[NSString stringWithCString:__PRETTY_FUNCTION__] \
+ file:[NSString stringWithCString:__FILE__] \
+ lineNumber:__LINE__ \
+ description:__VA_ARGS__]; \
+ } \
+ } while(0)
#else // !defined(NS_BLOCK_ASSERTIONS)
-#define _GTMDevAssert(condition, ...) do { } while (0)
+ #define _GTMDevAssert(condition, ...) do { } while (0)
#endif // !defined(NS_BLOCK_ASSERTIONS)
#endif // _GTMDevAssert
@@ -119,13 +140,13 @@ extern void _GTMUnitTestDevLog(NSString *format, ...);
// Wrapping this in an #ifndef allows external groups to define their own
// compile time assert scheme.
#ifndef _GTMCompileAssert
-// We got this technique from here:
-// http://unixjunkie.blogspot.com/2007/10/better-compile-time-asserts_29.html
+ // We got this technique from here:
+ // http://unixjunkie.blogspot.com/2007/10/better-compile-time-asserts_29.html
-#define _GTMCompileAssertSymbolInner(line, msg) _GTMCOMPILEASSERT ## line ## __ ## msg
-#define _GTMCompileAssertSymbol(line, msg) _GTMCompileAssertSymbolInner(line, msg)
-#define _GTMCompileAssert(test, msg) \
- typedef char _GTMCompileAssertSymbol(__LINE__, msg) [ ((test) ? 1 : -1) ]
+ #define _GTMCompileAssertSymbolInner(line, msg) _GTMCOMPILEASSERT ## line ## __ ## msg
+ #define _GTMCompileAssertSymbol(line, msg) _GTMCompileAssertSymbolInner(line, msg)
+ #define _GTMCompileAssert(test, msg) \
+ typedef char _GTMCompileAssertSymbol(__LINE__, msg) [ ((test) ? 1 : -1) ]
#endif // _GTMCompileAssert
// ============================================================================
@@ -156,45 +177,33 @@ extern void _GTMUnitTestDevLog(NSString *format, ...);
// defines for non Leopard SDKs
#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
// NSInteger/NSUInteger and Max/Mins
- #ifndef NSINTEGER_DEFINED
- #if __LP64__ || NS_BUILD_32_LIKE_64
- typedef long NSInteger;
- typedef unsigned long NSUInteger;
- #else
- typedef int NSInteger;
- typedef unsigned int NSUInteger;
- #endif
- #define NSIntegerMax LONG_MAX
- #define NSIntegerMin LONG_MIN
- #define NSUIntegerMax ULONG_MAX
- #define NSINTEGER_DEFINED 1
- #endif // NSINTEGER_DEFINED
- // CGFloat
- #ifndef CGFLOAT_DEFINED
- #if defined(__LP64__) && __LP64__
- // This really is an untested path (64bit on Tiger?)
- typedef double CGFloat;
- #define CGFLOAT_MIN DBL_MIN
- #define CGFLOAT_MAX DBL_MAX
- #define CGFLOAT_IS_DOUBLE 1
- #else /* !defined(__LP64__) || !__LP64__ */
- typedef float CGFloat;
- #define CGFLOAT_MIN FLT_MIN
- #define CGFLOAT_MAX FLT_MAX
- #define CGFLOAT_IS_DOUBLE 0
- #endif /* !defined(__LP64__) || !__LP64__ */
- #define CGFLOAT_DEFINED 1
- #endif // CGFLOAT_DEFINED
+ #ifndef NSINTEGER_DEFINED
+ #if __LP64__ || NS_BUILD_32_LIKE_64
+ typedef long NSInteger;
+ typedef unsigned long NSUInteger;
+ #else
+ typedef int NSInteger;
+ typedef unsigned int NSUInteger;
+ #endif
+ #define NSIntegerMax LONG_MAX
+ #define NSIntegerMin LONG_MIN
+ #define NSUIntegerMax ULONG_MAX
+ #define NSINTEGER_DEFINED 1
+ #endif // NSINTEGER_DEFINED
+ // CGFloat
+ #ifndef CGFLOAT_DEFINED
+ #if defined(__LP64__) && __LP64__
+ // This really is an untested path (64bit on Tiger?)
+ typedef double CGFloat;
+ #define CGFLOAT_MIN DBL_MIN
+ #define CGFLOAT_MAX DBL_MAX
+ #define CGFLOAT_IS_DOUBLE 1
+ #else /* !defined(__LP64__) || !__LP64__ */
+ typedef float CGFloat;
+ #define CGFLOAT_MIN FLT_MIN
+ #define CGFLOAT_MAX FLT_MAX
+ #define CGFLOAT_IS_DOUBLE 0
+ #endif /* !defined(__LP64__) || !__LP64__ */
+ #define CGFLOAT_DEFINED 1
+ #endif // CGFLOAT_DEFINED
#endif // MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
-
-
-// Give ourselves a consistent way to do inlines. Apple's macros even use
-// a few different actual definitions, so we're based off of the foundation
-// one.
-#if !defined(GTM_INLINE)
- #if defined (__GNUC__) && (__GNUC__ == 4)
- #define GTM_INLINE static __inline__ __attribute__((always_inline))
- #else
- #define GTM_INLINE static __inline__
- #endif
-#endif
diff --git a/GTMiPhone.xcodeproj/project.pbxproj b/GTMiPhone.xcodeproj/project.pbxproj
index 9c548ec..64e0671 100644
--- a/GTMiPhone.xcodeproj/project.pbxproj
+++ b/GTMiPhone.xcodeproj/project.pbxproj
@@ -24,6 +24,8 @@
1D3623EC0D0F72F000981E51 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */; };
1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
+ 6294461C0EDE178D009295EA /* GTMNSArray+MergeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 629446190EDE177A009295EA /* GTMNSArray+MergeTest.m */; };
+ 6294461D0EDE17A0009295EA /* GTMNSArray+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 629446180EDE177A009295EA /* GTMNSArray+Merge.m */; };
67A7820C0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 67A7820B0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m */; };
8B308BCE0DAD0B8400183556 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B308BCD0DAD0B8400183556 /* QuartzCore.framework */; };
8B3AA8F30E032FC7007E31B5 /* GTMNSString+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA8F10E032FC7007E31B5 /* GTMNSString+URLArguments.m */; };
@@ -113,6 +115,9 @@
1D6058910D05DD3D006BFB54 /* GTMiPhoneTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GTMiPhoneTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
32CA4F630368D1EE00C91783 /* GTM_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTM_Prefix.pch; sourceTree = "<group>"; };
+ 629446170EDE177A009295EA /* GTMNSArray+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSArray+Merge.h"; sourceTree = "<group>"; };
+ 629446180EDE177A009295EA /* GTMNSArray+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSArray+Merge.m"; sourceTree = "<group>"; };
+ 629446190EDE177A009295EA /* GTMNSArray+MergeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSArray+MergeTest.m"; sourceTree = "<group>"; };
67A7820A0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMIPhoneUnitTestDelegate.h; sourceTree = "<group>"; };
67A7820B0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMIPhoneUnitTestDelegate.m; sourceTree = "<group>"; };
8B308BCD0DAD0B8400183556 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
@@ -349,6 +354,9 @@
F418AFB10E755B4D004FB565 /* GTMLoggerRingBufferWriter.h */,
F418AFB20E755B4D004FB565 /* GTMLoggerRingBufferWriter.m */,
F418AFB30E755B4D004FB565 /* GTMLoggerRingBufferWriterTest.m */,
+ 629446170EDE177A009295EA /* GTMNSArray+Merge.h */,
+ 629446180EDE177A009295EA /* GTMNSArray+Merge.m */,
+ 629446190EDE177A009295EA /* GTMNSArray+MergeTest.m */,
8BC0477E0DAE928A00C2D1CA /* GTMNSData+zlib.h */,
8BC0477F0DAE928A00C2D1CA /* GTMNSData+zlib.m */,
8BC047800DAE928A00C2D1CA /* GTMNSData+zlibTest.m */,
@@ -626,6 +634,8 @@
F4EF8AD80EBFF814008DD6DA /* GTMStackTraceTest.m in Sources */,
F417115A0ECDFF0400B9B276 /* GTMLightweightProxy.m in Sources */,
F417115B0ECDFF0400B9B276 /* GTMLightweightProxyTest.m in Sources */,
+ 6294461C0EDE178D009295EA /* GTMNSArray+MergeTest.m in Sources */,
+ 6294461D0EDE17A0009295EA /* GTMNSArray+Merge.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt
index 45ec5dc..b0c06e3 100644
--- a/ReleaseNotes.txt
+++ b/ReleaseNotes.txt
@@ -7,7 +7,12 @@ Discussion group: http://groups.google.com/group/google-toolbox-for-mac
Release ?.?.?
Changes since 1.5.1
-- Added GTMSignalHandler for simple signal handling (via kqueue/runloop).
+- Added GTMNSArray+Merge for merging one array into another with or without
+ a custom merging function, returning a new array with the merged contents.
+
+- Added GTMSignalHandler for simple signal handling (via kqueue/runloop). This
+ has gotten an api tweak, so some code that started using it will need
+ updating.
- Fixed up GTMIPhoneUnitTestDelegate to be pickier about which tests it runs
@@ -160,9 +165,39 @@ Changes since 1.5.1
- GTMStackTrace support for building a trace from the call stack in an
NSException (for 10.5+ and iPhone).
+
+- GTMStackTrace works on 10.5+ (and iPhone) using NSThread to build the call
+ stack.
- GTMLightweightProxy for breaking retain cycles.
+- Added GTM_EXTERN that makes it easier to mix and match objc and objc++ code.
+
+- Added GTMHotKeysTextField for display and editing of hot key settings.
+
+- Added GTMCarbonEvent for dealing with Carbon Events and HotKeys in a ObjC
+ like way.
+
+- Backported the Atomic Barrier Swap functions for Objective C back to Tiger.
+
+- Added a variety of new functions to GTMUnitTestingUtilities for checking
+ if the screensaver is in the way, waiting on user events, and generating
+ keystrokes.
+
+- If you are using any Carbon routines that log (DebugStr, AssertMacros.h) and
+ use GTMUnitTestDevLog, the log routines now go through _GTMDevLog so that
+ they can be caught in GTMUnitTestDevLog and verified like any _GTMDevLog calls
+ you may make. For an example of this in action see GTMCarbonEventTest.m.
+
+- Added GTMFileSystemKQueue. It provides a simple wrapper for kqueuing
+ something in the file system and tracking changes to it.
+
+- RunIPhoneUnitTest.sh now cleans up the user home directory and creates
+ a documents directory within it, used when requesting a NSDocumentDirectory.
+
+- Added GTMNSFileManager+Carbon which contains routines for path <-> Alias
+ conversion and path <-> FSRef conversion.
+
Release 1.5.1
Changes since 1.5.0
diff --git a/UnitTesting/GTMIPhoneUnitTestDelegate.m b/UnitTesting/GTMIPhoneUnitTestDelegate.m
index 7ef46fc..ab33932 100644
--- a/UnitTesting/GTMIPhoneUnitTestDelegate.m
+++ b/UnitTesting/GTMIPhoneUnitTestDelegate.m
@@ -34,11 +34,6 @@ static int MethodSort(const void *a, const void *b) {
return strcmp(nameA, nameB);
}
-@interface UIApplication (iPhoneUnitTestAdditions)
-// "Private" method that we need
-- (void)terminateWithSuccess;
-@end
-
@implementation GTMIPhoneUnitTestDelegate
// Return YES if class is subclass (1 or more generations) of SenTestCase
@@ -62,10 +57,7 @@ static int MethodSort(const void *a, const void *b) {
// Using private call to end our tests
if (!getenv("GTM_DISABLE_TERMINATION")) {
- // I call this delayed just to make sure that the stack is clean
- [application performSelector:@selector(terminateWithSuccess)
- withObject:nil
- afterDelay:0.00];
+ exit(0);
}
}
diff --git a/UnitTesting/GTMNSObject+UnitTesting.h b/UnitTesting/GTMNSObject+UnitTesting.h
index 68fab04..57b53bf 100644
--- a/UnitTesting/GTMNSObject+UnitTesting.h
+++ b/UnitTesting/GTMNSObject+UnitTesting.h
@@ -414,8 +414,8 @@ do { \
// notification so that objects who want to add data to the encoded objects unit
// test state can do so. The Coder will be in the userInfo dictionary for the
// notification under the GTMUnitTestingEncoderKey key.
-extern NSString *const GTMUnitTestingEncodedObjectNotification;
+GTM_EXTERN NSString *const GTMUnitTestingEncodedObjectNotification;
// Key for finding the encoder in the userInfo dictionary for
// GTMUnitTestingEncodedObjectNotification notifications.
-extern NSString *const GTMUnitTestingEncoderKey;
+GTM_EXTERN NSString *const GTMUnitTestingEncoderKey;
diff --git a/UnitTesting/GTMSenTestCase.h b/UnitTesting/GTMSenTestCase.h
index 99f520a..1d35591 100644
--- a/UnitTesting/GTMSenTestCase.h
+++ b/UnitTesting/GTMSenTestCase.h
@@ -990,10 +990,10 @@ do { \
- (void)failWithException:(NSException*)exception;
@end
-extern NSString *const SenTestFailureException;
+GTM_EXTERN NSString *const SenTestFailureException;
-extern NSString *const SenTestFilenameKey;
-extern NSString *const SenTestLineNumberKey;
+GTM_EXTERN NSString *const SenTestFilenameKey;
+GTM_EXTERN NSString *const SenTestLineNumberKey;
#endif // GTM_IPHONE_SDK
diff --git a/UnitTesting/GTMUIUnitTestingHarness/Info.plist b/UnitTesting/GTMUIUnitTestingHarness/Info.plist
index fe3e712..11355f7 100644
--- a/UnitTesting/GTMUIUnitTestingHarness/Info.plist
+++ b/UnitTesting/GTMUIUnitTestingHarness/Info.plist
@@ -22,6 +22,49 @@
<string>1.0</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
+ <!-- The CFBundleURLTypes are in here specifically to test
+ GTMGetURLHandler. See GTMGetURLHandlerTest for details -->
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>GTMUIUnitTestingHarnessURL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>gtmgeturlhandlertest</string>
+ </array>
+ <key>GTMBundleURLClass</key>
+ <string>GTMGetURLHandlerTest</string>
+ </dict>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>GTMGetURLHandlerBadClassURL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>gtmgeturlhandlerbadclasstest</string>
+ </array>
+ <key>GTMBundleURLClass</key>
+ <string>GTMGetURLHandlerBadClassWarning</string>
+ </dict>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>GTMGetURLHandlerMissingClassURL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>gtmgeturlhandlermissingclasstest</string>
+ </array>
+ <key>GTMBundleURLClass</key>
+ <string>GTMGetURLHandlerMissingClassWarning</string>
+ </dict>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>GTMGetURLHandlerMissingHandlerURL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>gtmgeturlhandlermissinghandlerurl</string>
+ </array>
+ </dict>
+ </array>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
diff --git a/UnitTesting/GTMUnitTestDevLog.m b/UnitTesting/GTMUnitTestDevLog.m
index 30ab13b..a0c31b3 100644
--- a/UnitTesting/GTMUnitTestDevLog.m
+++ b/UnitTesting/GTMUnitTestDevLog.m
@@ -17,9 +17,40 @@
//
#import "GTMUnitTestDevLog.h"
+
+
#import "GTMRegex.h"
#import "GTMSenTestCase.h"
+#if !GTM_IPHONE_SDK
+// Add support for grabbing messages from Carbon.
+#import <CoreServices/CoreServices.h>
+static void GTMDevLogDebugAssert(OSType componentSignature,
+ UInt32 options,
+ const char *assertionString,
+ const char *exceptionLabelString,
+ const char *errorString,
+ const char *fileName,
+ long lineNumber,
+ void *value,
+ ConstStr255Param outputMsg) {
+ NSString *outLog = [[[NSString alloc] initWithBytes:&(outputMsg[1])
+ length:StrLength(outputMsg)
+ encoding:NSMacOSRomanStringEncoding]
+ autorelease];
+ _GTMDevLog(outLog);
+}
+static inline void GTMInstallDebugAssertOutputHandler(void) {
+ InstallDebugAssertOutputHandler(GTMDevLogDebugAssert);
+}
+static inline void GTMUninstallDebugAssertOutputHandler(void) {
+ InstallDebugAssertOutputHandler(NULL);
+}
+#else // GTM_IPHONE_SDK
+static inline void GTMInstallDebugAssertOutputHandler(void) {};
+static inline void GTMUninstallDebugAssertOutputHandler(void) {};
+#endif // GTM_IPHONE_SDK
+
@implementation GTMUnitTestDevLog
// If unittests are ever being run on separate threads, this may need to be
// made a thread local variable.
@@ -38,10 +69,12 @@ static BOOL gTrackingEnabled = NO;
}
+ (void)enableTracking {
+ GTMInstallDebugAssertOutputHandler();
gTrackingEnabled = YES;
}
+ (void)disableTracking {
+ GTMUninstallDebugAssertOutputHandler();
gTrackingEnabled = NO;
}
diff --git a/UnitTesting/GTMUnitTestingUtilities.h b/UnitTesting/GTMUnitTestingUtilities.h
index eca0825..128a4c4 100644
--- a/UnitTesting/GTMUnitTestingUtilities.h
+++ b/UnitTesting/GTMUnitTestingUtilities.h
@@ -39,5 +39,53 @@
// }
+ (void)setUpForUIUnitTestsIfBeingTested;
+// Check if the screen saver is running. Some unit tests don't work when
+// the screen saver is active.
++ (BOOL)isScreenSaverActive;
+
+// Allows for posting either a keydown or a keyup with all the modifiers being
+// applied. Passing a 'g' with NSKeyDown and NSShiftKeyMask
+// generates two events (a shift key key down and a 'g' key keydown). Make sure
+// to balance this with a keyup, or things could get confused. Events get posted
+// using the CGRemoteOperation events which means that it gets posted in the
+// system event queue. Thus you can affect other applications if your app isn't
+// the active app (or in some cases, such as hotkeys, even if it is).
+// Arguments:
+// type - Event type. Currently accepts NSKeyDown and NSKeyUp
+// keyChar - character on the keyboard to type. Make sure it is lower case.
+// If you need upper case, pass in the NSShiftKeyMask in the
+// modifiers. i.e. to generate "G" pass in 'g' and NSShiftKeyMask.
+// to generate "+" pass in '=' and NSShiftKeyMask.
+// cocoaModifiers - an int made up of bit masks. Handles NSAlphaShiftKeyMask,
+// NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask, and
+// NSCommandKeyMask
++ (void)postKeyEvent:(NSEventType)type
+ character:(CGCharCode)keyChar
+ modifiers:(UInt32)cocoaModifiers;
+
+// Syntactic sugar for posting a keydown immediately followed by a key up event
+// which is often what you really want.
+// Arguments:
+// keyChar - character on the keyboard to type. Make sure it is lower case.
+// If you need upper case, pass in the NSShiftKeyMask in the
+// modifiers. i.e. to generate "G" pass in 'g' and NSShiftKeyMask.
+// to generate "+" pass in '=' and NSShiftKeyMask.
+// cocoaModifiers - an int made up of bit masks. Handles NSAlphaShiftKeyMask,
+// NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask, and
+// NSCommandKeyMask
++ (void)postTypeCharacterEvent:(CGCharCode)keyChar
+ modifiers:(UInt32)cocoaModifiers;
+
+// Runs the event loop in NSDefaultRunLoopMode until date. Can be useful for
+// testing user interface responses in a controlled timed event loop. For most
+// uses using:
+// [[NSRunLoop currentRunLoop] runUntilDate:date]
+// will do. The only reason you would want to use this is if you were
+// using the postKeyEvent:character:modifiers to send events and wanted to
+// receive user input.
+// Arguments:
+// date - end of execution time
++ (void)runUntilDate:(NSDate*)date;
+
@end
diff --git a/UnitTesting/GTMUnitTestingUtilities.m b/UnitTesting/GTMUnitTestingUtilities.m
index 39534f7..e72a921 100644
--- a/UnitTesting/GTMUnitTestingUtilities.m
+++ b/UnitTesting/GTMUnitTestingUtilities.m
@@ -19,16 +19,19 @@
#import "GTMUnitTestingUtilities.h"
#import <AppKit/AppKit.h>
#import "GTMDefines.h"
+#import "GTMGarbageCollection.h"
// The Users profile before we change it on them
-static CMProfileRef gCurrentColorProfile = NULL;
+static CMProfileRef gGTMCurrentColorProfile = NULL;
// Compares two color profiles
-static BOOL AreCMProfilesEqual(CMProfileRef a, CMProfileRef b);
+static BOOL GTMAreCMProfilesEqual(CMProfileRef a, CMProfileRef b);
// Stores the user's color profile away, and changes over to generic.
-static void SetColorProfileToGenericRGB();
+static void GTMSetColorProfileToGenericRGB();
// Restores the users profile.
-static void RestoreColorProfile(void);
+static void GTMRestoreColorProfile(void);
+
+static CGKeyCode GTMKeyCodeForCharCode(CGCharCode charCode);
@implementation GTMUnitTestingUtilities
@@ -81,7 +84,7 @@ static void RestoreColorProfile(void);
[defaults setFloat:.001f forKey:@"NSWindowResizeTime"];
// Switch over the screen profile to "generic rgb". This installs an
// atexit handler to return our profile back when we are done.
- SetColorProfileToGenericRGB();
+ GTMSetColorProfileToGenericRGB();
}
+ (void)setUpForUIUnitTestsIfBeingTested {
@@ -91,10 +94,98 @@ static void RestoreColorProfile(void);
}
[pool release];
}
-@end
++ (BOOL)isScreenSaverActive {
+ BOOL answer = NO;
+ ProcessSerialNumber psn;
+ if (GetFrontProcess(&psn) == noErr) {
+ CFDictionaryRef cfProcessInfo
+ = ProcessInformationCopyDictionary(&psn,
+ kProcessDictionaryIncludeAllInformationMask);
+ NSDictionary *processInfo = GTMCFAutorelease(cfProcessInfo);
+
+ NSString *bundlePath = [processInfo objectForKey:@"BundlePath"];
+ // ScreenSaverEngine is the frontmost app if the screen saver is actually
+ // running Security Agent is the frontmost app if the "enter password"
+ // dialog is showing
+ NSString *bundleName = [bundlePath lastPathComponent];
+ answer = ([bundleName isEqualToString:@"ScreenSaverEngine.app"]
+ || [bundleName isEqualToString:@"SecurityAgent.app"]);
+ }
+ return answer;
+}
+
+// Allows for posting either a keydown or a keyup with all the modifiers being
+// applied. Passing a 'g' with NSKeyDown and NSShiftKeyMask
+// generates two events (a shift key key down and a 'g' key keydown). Make sure
+// to balance this with a keyup, or things could get confused. Events get posted
+// using the CGRemoteOperation events which means that it gets posted in the
+// system event queue. Thus you can affect other applications if your app isn't
+// the active app (or in some cases, such as hotkeys, even if it is).
+// Arguments:
+// type - Event type. Currently accepts NSKeyDown and NSKeyUp
+// keyChar - character on the keyboard to type. Make sure it is lower case.
+// If you need upper case, pass in the NSShiftKeyMask in the
+// modifiers. i.e. to generate "G" pass in 'g' and NSShiftKeyMask.
+// to generate "+" pass in '=' and NSShiftKeyMask.
+// cocoaModifiers - an int made up of bit masks. Handles NSAlphaShiftKeyMask,
+// NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask, and
+// NSCommandKeyMask
++ (void)postKeyEvent:(NSEventType)type
+ character:(CGCharCode)keyChar
+ modifiers:(UInt32)cocoaModifiers {
+ require(![self isScreenSaverActive], CantWorkWithScreenSaver);
+ require(type == NSKeyDown || type == NSKeyUp, CantDoEvent);
+ CGKeyCode code = GTMKeyCodeForCharCode(keyChar);
+ verify(code != 256);
+ CGEventRef event = CGEventCreateKeyboardEvent(NULL, code, type == NSKeyDown);
+ require(event, CantCreateEvent);
+ CGEventSetFlags(event, cocoaModifiers);
+ CGEventPost(kCGSessionEventTap, event);
+ CFRelease(event);
+CantCreateEvent:
+CantDoEvent:
+CantWorkWithScreenSaver:
+ return;
+}
+
+// Syntactic sugar for posting a keydown immediately followed by a key up event
+// which is often what you really want.
+// Arguments:
+// keyChar - character on the keyboard to type. Make sure it is lower case.
+// If you need upper case, pass in the NSShiftKeyMask in the
+// modifiers. i.e. to generate "G" pass in 'g' and NSShiftKeyMask.
+// to generate "+" pass in '=' and NSShiftKeyMask.
+// cocoaModifiers - an int made up of bit masks. Handles NSAlphaShiftKeyMask,
+// NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask, and
+// NSCommandKeyMask
++ (void)postTypeCharacterEvent:(CGCharCode)keyChar modifiers:(UInt32)cocoaModifiers {
+ [self postKeyEvent:NSKeyDown character:keyChar modifiers:cocoaModifiers];
+ [self postKeyEvent:NSKeyUp character:keyChar modifiers:cocoaModifiers];
+}
+
+// Runs the event loop in NSDefaultRunLoopMode until date. Can be useful for
+// testing user interface responses in a controlled timed event loop. For most
+// uses using:
+// [[NSRunLoop currentRunLoop] runUntilDate:date]
+// will do. The only reason you would want to use this is if you were
+// using the postKeyEvent:character:modifiers to send events and wanted to
+// receive user input.
+// Arguments:
+// date - end of execution time
++ (void)runUntilDate:(NSDate*)date {
+ NSEvent *event;
+ while ((event = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:date
+ inMode:NSDefaultRunLoopMode
+ dequeue:YES])) {
+ [NSApp sendEvent:event];
+ }
+}
+
+@end
-BOOL AreCMProfilesEqual(CMProfileRef a, CMProfileRef b) {
+BOOL GTMAreCMProfilesEqual(CMProfileRef a, CMProfileRef b) {
BOOL equal = YES;
if (a != b) {
CMProfileMD5 aMD5;
@@ -102,17 +193,18 @@ BOOL AreCMProfilesEqual(CMProfileRef a, CMProfileRef b) {
CMError aMD5Err = CMGetProfileMD5(a, aMD5);
CMError bMD5Err = CMGetProfileMD5(b, bMD5);
equal = (!aMD5Err &&
- !bMD5Err &&
- !memcmp(aMD5, bMD5, sizeof(CMProfileMD5))) ? YES : NO;
+ !bMD5Err &&
+ !memcmp(aMD5, bMD5, sizeof(CMProfileMD5))) ? YES : NO;
}
return equal;
}
-static void RestoreColorProfile(void) {
- if (gCurrentColorProfile) {
+void GTMRestoreColorProfile(void) {
+ if (gGTMCurrentColorProfile) {
CGDirectDisplayID displayID = CGMainDisplayID();
- CMError error = CMSetProfileByAVID((UInt32)displayID, gCurrentColorProfile);
- CMCloseProfile(gCurrentColorProfile);
+ CMError error = CMSetProfileByAVID((UInt32)displayID,
+ gGTMCurrentColorProfile);
+ CMCloseProfile(gGTMCurrentColorProfile);
if (error) {
// COV_NF_START
// No way to force this case in a unittest.
@@ -123,11 +215,11 @@ static void RestoreColorProfile(void) {
} else {
_GTMDevLog(@"Color profile restored");
}
- gCurrentColorProfile = NULL;
+ gGTMCurrentColorProfile = NULL;
}
}
-void SetColorProfileToGenericRGB(void) {
+void GTMSetColorProfileToGenericRGB(void) {
NSColorSpace *genericSpace = [NSColorSpace genericRGBColorSpace];
CMProfileRef genericProfile = (CMProfileRef)[genericSpace colorSyncProfile];
CMProfileRef previousProfile;
@@ -143,7 +235,7 @@ void SetColorProfileToGenericRGB(void) {
return;
// COV_NF_END
}
- if (AreCMProfilesEqual(genericProfile, previousProfile)) {
+ if (GTMAreCMProfilesEqual(genericProfile, previousProfile)) {
CMCloseProfile(previousProfile);
return;
}
@@ -166,9 +258,53 @@ void SetColorProfileToGenericRGB(void) {
"a result. (Error: %i)", genericProfileName, error);
// COV_NF_END
} else {
- gCurrentColorProfile = previousProfile;
- atexit(RestoreColorProfile);
+ gGTMCurrentColorProfile = previousProfile;
+ atexit(GTMRestoreColorProfile);
}
CFRelease(previousProfileName);
CFRelease(genericProfileName);
}
+
+// Returns a virtual key code for a given charCode. Handles all of the
+// NS*FunctionKeys as well.
+static CGKeyCode GTMKeyCodeForCharCode(CGCharCode charCode) {
+ // character map taken from http://classicteck.com/rbarticles/mackeyboard.php
+ int characters[] = {
+ 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x', 'c', 'v', 256, 'b', 'q', 'w',
+ 'e', 'r', 'y', 't', '1', '2', '3', '4', '6', '5', '=', '9', '7', '-',
+ '8', '0', ']', 'o', 'u', '[', 'i', 'p', '\n', 'l', 'j', '\'', 'k', ';',
+ '\\', ',', '/', 'n', 'm', '.', '\t', ' ', '`', '\b', 256, '\e'
+ };
+
+ // function key map taken from
+ // file:///Developer/ADC%20Reference%20Library/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Classes/NSEvent.html
+ int functionKeys[] = {
+ // NSUpArrowFunctionKey - NSF12FunctionKey
+ 126, 125, 123, 124, 122, 120, 99, 118, 96, 97, 98, 100, 101, 109, 103, 111,
+ // NSF13FunctionKey - NSF28FunctionKey
+ 105, 107, 113, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
+ // NSF29FunctionKey - NSScrollLockFunctionKey
+ 256, 256, 256, 256, 256, 256, 256, 256, 117, 115, 256, 119, 116, 121, 256, 256,
+ // NSPauseFunctionKey - NSPrevFunctionKey
+ 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
+ // NSNextFunctionKey - NSModeSwitchFunctionKey
+ 256, 256, 256, 256, 256, 256, 114, 1
+ };
+
+ CGKeyCode outCode = 0;
+
+ // Look in the function keys
+ if (charCode >= NSUpArrowFunctionKey && charCode <= NSModeSwitchFunctionKey) {
+ outCode = functionKeys[charCode - NSUpArrowFunctionKey];
+ } else {
+ // Look in our character map
+ for (size_t i = 0; i < (sizeof(characters) / sizeof (int)); i++) {
+ if (characters[i] == charCode) {
+ outCode = i;
+ break;
+ }
+ }
+ }
+ return outCode;
+}
+
diff --git a/UnitTesting/RunIPhoneUnitTest.sh b/UnitTesting/RunIPhoneUnitTest.sh
index 0b6fe69..bf25714 100755
--- a/UnitTesting/RunIPhoneUnitTest.sh
+++ b/UnitTesting/RunIPhoneUnitTest.sh
@@ -75,6 +75,13 @@ if [ "$PLATFORM_NAME" == "iphonesimulator" ]; then
export NSZombieEnabled=YES
fi
+ # Cleanup user home and documents directory
+ if [ -d "$CFFIXED_USER_HOME" ]; then
+ rm -rf "$CFFIXED_USER_HOME"
+ fi
+ mkdir "$CFFIXED_USER_HOME"
+ mkdir "$CFFIXED_USER_HOME/Documents"
+
# 6251475 iPhone simulator leaks @ CFHTTPCookieStore shutdown if
# CFFIXED_USER_HOME empty
GTM_LEAKS_SYMBOLS_TO_IGNORE="CFHTTPCookieStore"
diff --git a/XcodeConfig/subconfig/Debug.xcconfig b/XcodeConfig/subconfig/Debug.xcconfig
index d8fbd1c..7d54d61 100644
--- a/XcodeConfig/subconfig/Debug.xcconfig
+++ b/XcodeConfig/subconfig/Debug.xcconfig
@@ -40,4 +40,8 @@ GTM_CONFIGURATION_GCC_PREPROCESSOR_DEFINITIONS = _GLIBCXX_DEBUG_PEDANTIC _GLIBCX
// Turns on stack protection on debug builds for Leopard and above
GTM_CONFIGURATION_OTHER_CFLAGS = -fstack-protector -fstack-protector-all
// Stack protection doesn't exist on Tiger
-GTM_CONFIGURATION_OTHER_CFLAGS[sdk=macosx10.4*] =
+GTM_CONFIGURATION_OTHER_CFLAGS[sdk=macosx10.4*] =
+
+// And is really broken on the iPhone Device. Works on simulator though.
+// rdar://639430 Xcode generates bad Arm code if -fstack-protector -fstack-protector-all
+GTM_CONFIGURATION_OTHER_CFLAGS[sdk=iphoneos*] =
diff --git a/XcodeConfig/subconfig/General.xcconfig b/XcodeConfig/subconfig/General.xcconfig
index 2aea15f..dc3e64f 100644
--- a/XcodeConfig/subconfig/General.xcconfig
+++ b/XcodeConfig/subconfig/General.xcconfig
@@ -46,6 +46,12 @@ ARCHS[sdk=iphonesimulator*] = i386
// Build only the active architecture on iphone device targets
ONLY_ACTIVE_ARCH[sdk=iphoneos*] = YES
+// iPhone currently deploys on 10.5 only
+MACOSX_DEPLOYMENT_TARGET[sdk=iphone*] = 10.5
+
+// We want our pngs compressed when they are copied
+COMPRESS_PNG_FILES = YES
+
// Zerolink prevents link warnings so turn it off
ZERO_LINK = NO
diff --git a/XcodeConfig/subconfig/iPhone20.xcconfig b/XcodeConfig/subconfig/iPhone20.xcconfig
index 2509ba8..0f92c95 100644
--- a/XcodeConfig/subconfig/iPhone20.xcconfig
+++ b/XcodeConfig/subconfig/iPhone20.xcconfig
@@ -17,7 +17,8 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Default SDK and minimum OS version is the iphone SDK.
+// Set default SDK.
SDKROOT = iphoneos2.0
+
+// iPhone currently deploys on 10.5 only
MACOSX_DEPLOYMENT_TARGET = 10.5
-GCC_VERSION = 4.0
diff --git a/XcodeConfig/subconfig/iPhone21.xcconfig b/XcodeConfig/subconfig/iPhone21.xcconfig
index e543ce5..61c59ed 100644
--- a/XcodeConfig/subconfig/iPhone21.xcconfig
+++ b/XcodeConfig/subconfig/iPhone21.xcconfig
@@ -17,7 +17,8 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Default SDK and minimum OS version is the iphone SDK.
+// Set default SDK.
SDKROOT = iphoneos2.1
+
+// iPhone currently deploys on 10.5 only
MACOSX_DEPLOYMENT_TARGET = 10.5
-GCC_VERSION = 4.0
diff --git a/iPhone/GTMABAddressBook.h b/iPhone/GTMABAddressBook.h
index cdcfa7c..828f05a 100644
--- a/iPhone/GTMABAddressBook.h
+++ b/iPhone/GTMABAddressBook.h
@@ -1,5 +1,5 @@
//
-// GTMAddressBook.h
+// GTMABAddressBook.h
//
// Copyright 2008 Google Inc.
//
@@ -74,7 +74,7 @@
@class GTMABGroup;
@class GTMABRecord;
-extern NSString *const kGTMABUnknownPropertyName;
+GTM_EXTERN NSString *const kGTMABUnknownPropertyName;
// Wrapper for an AddressBook on iPhone
@interface GTMABAddressBook : NSObject {