Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1076 lines (816 sloc) 26.492 kb
//
// NSObject+FrankAutomation.m
// Frank
//
// Created by Buckley on 12/5/12.
//
//
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
#import "LoadableCategory.h"
#if !TARGET_OS_IPHONE
#import "FEXTableRow.h"
#import "FEXTableCell.h"
#import "NSScreen+Frank.h"
#endif
MAKE_CATEGORIES_LOADABLE(NSObject_FrankAutomation)
#if TARGET_OS_IPHONE
@implementation NSObject (FrankAutomation)
- (NSString *) FEX_accessibilityLabel
{
NSString* returnValue = nil;
if ([self respondsToSelector: @selector(accessibilityLabel)])
{
returnValue = [self accessibilityLabel];
}
return returnValue;
}
- (CGRect) FEX_accessibilityFrame
{
CGRect returnValue = CGRectZero;
if ([self respondsToSelector: @selector(accessibilityFrame)])
{
returnValue = [self accessibilityFrame];
}
return returnValue;
}
@end
#else
#import "NSApplication+FrankAutomation.h"
static const NSString* FEX_AccessibilityDescriptionAttribute = @"FEX_AccessibilityDescriptionAttribute";
static const NSString* FEX_ParentAttribute = @"FEX_ParentAttribute";
@implementation NSObject (FrankAutomation)
+ (void) load
{
method_exchangeImplementations(class_getInstanceMethod(self, @selector(accessibilitySetOverrideValue:forAttribute:)),
class_getInstanceMethod(self, @selector(FEX_accessibilitySetOverrideValue:forAttribute:)));
}
- (BOOL) FEX_accessibilitySetOverrideValue: (id) value forAttribute: (NSString*) attribute
{
if ([value isKindOfClass: [NSString class]] && [attribute isEqualToString: NSAccessibilityDescriptionAttribute])
{
objc_setAssociatedObject(self, FEX_AccessibilityDescriptionAttribute, value, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
return [self FEX_accessibilitySetOverrideValue: value forAttribute: attribute];
}
- (NSString *) FEX_accessibilityLabel
{
NSString* returnValue = objc_getAssociatedObject(self, FEX_AccessibilityDescriptionAttribute);
if (returnValue == nil || [returnValue isEqualToString: @""])
{
if ([self respondsToSelector: @selector(accessibilityAttributeNames)] &&
[self respondsToSelector: @selector(accessibilityAttributeValue:)])
{
NSArray *candidateAttributes = @[ NSAccessibilityDescriptionAttribute,
NSAccessibilityTitleAttribute ];
for (NSString *candidateAttribute in candidateAttributes)
{
if ([[self accessibilityAttributeNames] containsObject: candidateAttribute])
{
id value = [self accessibilityAttributeValue: candidateAttribute];
if ([value isKindOfClass: [NSString class]]) {
returnValue = value;
break;
}
}
}
}
}
return [[returnValue copy] autorelease];
}
- (void) FEX_setParent: (id) aParent
{
objc_setAssociatedObject(self, FEX_ParentAttribute, aParent, OBJC_ASSOCIATION_ASSIGN);
}
- (id) FEX_parent
{
return objc_getAssociatedObject(self, FEX_ParentAttribute);
}
- (CGRect) FEX_accessibilityFrame
{
NSPoint origin = NSZeroPoint;
NSSize size = NSZeroSize;
NSValue *originValue = nil;
NSValue *sizeValue = nil;
if ([self respondsToSelector: @selector(accessibilityAttributeNames)] &&
[self respondsToSelector: @selector(accessibilityAttributeValue:)])
{
if ([[self accessibilityAttributeNames] containsObject: NSAccessibilityPositionAttribute])
{
originValue = [self accessibilityAttributeValue: NSAccessibilityPositionAttribute];
}
if ([[self accessibilityAttributeNames] containsObject: NSAccessibilitySizeAttribute])
{
sizeValue = [self accessibilityAttributeValue: NSAccessibilitySizeAttribute];
}
}
if (originValue != nil) {
origin = [originValue pointValue];
}
if (sizeValue != nil)
{
size = [sizeValue sizeValue];
}
CGRect accessibilityFrame = CGRectMake(origin.x, origin.y, size.width, size.height);
accessibilityFrame = [NSScreen FEX_flipCoordinates: accessibilityFrame];
return accessibilityFrame;
}
- (BOOL) FEX_performAccessibilityAction: (NSString*) anAction
{
BOOL returnValue = NO;
if ([self respondsToSelector: @selector(accessibilityActionNames)] &&
[[self accessibilityActionNames] containsObject: anAction])
{
[self accessibilityPerformAction: anAction];
returnValue = YES;
}
return returnValue;
}
- (BOOL) FEX_mouseDownX: (CGFloat) x y: (CGFloat) y
{
BOOL returnValue = NO;
CGPoint location = CGPointMake(x, y);
CGEventRef event = CGEventCreateMouseEvent(NULL,
kCGEventLeftMouseDown,
location,
kCGMouseButtonLeft);
if (event != NULL)
{
CGEventPost(kCGSessionEventTap, event);
CFRelease(event);
event = NULL;
returnValue = YES;
}
return returnValue;
}
- (BOOL) FEX_dragToX: (CGFloat) x y: (CGFloat) y
{
BOOL returnValue = NO;
CGPoint location = CGPointMake(x, y);
CGEventRef event = CGEventCreateMouseEvent(NULL,
kCGEventLeftMouseDragged,
location,
kCGMouseButtonLeft);
if (event != NULL)
{
CGEventPost(kCGSessionEventTap, event);
CFRelease(event);
event = NULL;
returnValue = YES;
}
return returnValue;
}
- (BOOL) FEX_mouseUpX: (CGFloat) x y: (CGFloat) y
{
BOOL returnValue = NO;
CGPoint location = CGPointMake(x, y);
CGEventRef event = CGEventCreateMouseEvent(NULL,
kCGEventLeftMouseUp,
location,
kCGMouseButtonLeft);
if (event != NULL)
{
CGEventPost(kCGSessionEventTap, event);
CFRelease(event);
event = NULL;
returnValue = YES;
}
return returnValue;
}
- (BOOL) FEX_simulateClick
{
return [self FEX_performAccessibilityAction: NSAccessibilityPressAction];
}
- (BOOL) FEX_raise
{
return [self FEX_performAccessibilityAction: NSAccessibilityRaiseAction];
}
- (BOOL) FEX_cancel
{
return [self FEX_performAccessibilityAction: NSAccessibilityCancelAction];
}
- (BOOL) FEX_confirm
{
return [self FEX_performAccessibilityAction: NSAccessibilityConfirmAction];
}
- (BOOL) FEX_decrement
{
return [self FEX_performAccessibilityAction: NSAccessibilityDecrementAction];
}
- (BOOL) FEX_delete
{
return [self FEX_performAccessibilityAction: NSAccessibilityDeleteAction];
}
- (BOOL) FEX_increment
{
return [self FEX_performAccessibilityAction: NSAccessibilityIncrementAction];
}
- (BOOL) FEX_pick
{
return [self FEX_performAccessibilityAction: NSAccessibilityPickAction];
}
- (BOOL) FEX_showMenu
{
return [self FEX_performAccessibilityAction: NSAccessibilityShowMenuAction];
}
- (BOOL) FEX_isVisible
{
return YES;
}
- (BOOL) FEX_isFullyVisible
{
return YES;
}
@end
@implementation NSWindow(FrankAutomation)
- (CGRect) FEX_accessibilityFrame
{
CGRect returnValue = NSZeroRect;
if ([self isVisible] && ![self isMiniaturized])
{
returnValue = [super FEX_accessibilityFrame];
}
return returnValue;
}
- (BOOL) FEX_isVisible
{
return [self isVisible];
}
- (BOOL) FEX_isFullyVisible
{
return [[self contentView] FEX_isFullyVisible];
}
- (NSArray*) FEX_children
{
NSMutableArray* children = [NSMutableArray array];
if ([self toolbar] != NULL)
{
// NSToolbars do not keep references to their parent NSWindows
[[self toolbar] FEX_setParent: self];
[children addObject: [self toolbar]];
}
if ([self contentView] != NULL)
{
[children addObject: [self contentView]];
}
return children;
}
- (id) FEX_parent
{
return NSApp;
}
@end
@implementation NSToolbar (FrankAutomation)
- (NSArray*) FEX_children
{
return [self visibleItems];
}
@end
@implementation NSToolbarItem (FrankAutomation)
- (BOOL) FEX_simulateClick
{
id target = [self target];
if (target == nil)
{
target = [NSApplication sharedApplication];
}
BOOL returnValue = [self isEnabled] && target != nil && [self action] != nil && [target respondsToSelector: [self action]];
if (returnValue)
{
[target performSelector: [self action] withObject: self];
}
return returnValue;
}
- (id) FEX_parent
{
return [self toolbar];
}
- (NSString*) FEX_accessibilityLabel
{
NSString* accessibilityLabel = [super FEX_accessibilityLabel];
if (accessibilityLabel == nil)
{
accessibilityLabel = [self label];
}
return accessibilityLabel;
}
@end
@implementation NSControl (FrankAutomation)
- (NSString*) FEX_accessibilityLabel
{
NSString* returnValue = [super FEX_accessibilityLabel];
if (returnValue == nil || [returnValue isEqualToString: @""])
{
returnValue = [[self cell] FEX_accessibilityLabel];
if (returnValue == nil || [returnValue isEqualToString: @""])
{
returnValue = [[self cell] title];
}
}
return returnValue;
}
- (CGRect) FEX_accessibilityFrame
{
CGRect returnValue = [[self cell] FEX_accessibilityFrame];
if (NSEqualRects(returnValue, NSZeroRect))
{
returnValue = [super FEX_accessibilityFrame];
}
return returnValue;
}
- (BOOL) FEX_performAccessibilityAction: (NSString*) anAction
{
BOOL returnValue = [[self cell] FEX_performAccessibilityAction: anAction];
if (returnValue == NO)
{
returnValue = [super FEX_performAccessibilityAction: anAction];
}
return returnValue;
}
@end
@implementation NSMenu (FrankAutomation)
- (NSArray*) FEX_children
{
return [self itemArray];
}
- (id) FEX_parent
{
return NSApp;
}
@end
@implementation NSMenuItem (FrankAutomation)
- (NSString*) FEX_accessibilityLabel
{
NSString* returnValue = nil;
if ([self isSeparatorItem])
{
returnValue = @"Separator";
}
else
{
returnValue = [super FEX_accessibilityLabel];
if (returnValue == nil)
{
returnValue = [self title];
}
}
return returnValue;
}
- (CGRect) FEX_accessibilityFrame
{
CGRect returnValue = NSMakeRect(0, 0, 0, 0);
NSDictionary* menuDict = nil;
if ([[self menu] isEqual: [[NSApplication sharedApplication] mainMenu]])
{
AXUIElementRef app = AXUIElementCreateApplication([[NSRunningApplication currentApplication] processIdentifier]);
AXUIElementRef menu = NULL;
AXUIElementCopyAttributeValue(app, kAXMenuBarAttribute, (CFTypeRef*) &menu);
menuDict = FEX_DictionaryForAXMenu(menu);
}
else
{
NSValue* menuPointer = [NSValue valueWithPointer: [self menu]];
menuDict = [[[NSApplication sharedApplication] FEX_axMenus] objectForKey: menuPointer];
}
if (menuDict != NULL)
{
returnValue = [[menuDict objectForKey: [self title]] rectValue];
}
return returnValue;
}
- (BOOL) FEX_simulateClick
{
BOOL returnValue = NO;
if ([self menu] != nil)
{
if ([self hasSubmenu])
{
returnValue = [super FEX_simulateClick];
}
else
{
NSInteger itemIndex = [[self menu] indexOfItem: self];
if (itemIndex >= 0)
{
[[self menu] performActionForItemAtIndex: itemIndex];
returnValue = YES;
}
}
}
return returnValue;
}
- (NSArray*) FEX_children
{
NSMutableArray *children = [NSMutableArray array];
NSMenu *submenu = [self submenu];
if (submenu != nil) {
[children addObject:submenu];
}
return children;
}
- (id) FEX_parent
{
id parent = [self parentItem];
if (parent == nil)
{
parent = NSApp;
}
return parent;
}
@end
@implementation NSStatusItem (FrankAutomation)
- (NSArray*) FEX_children
{
NSMutableArray* children = [NSMutableArray array];
if ([self menu] != nil)
{
[children addObject: [self menu]];
}
return children;
}
- (void) FEX_simulateClick
{
[self popUpStatusItemMenu: [self menu]];
}
- (NSString*) FEX_accessibilityLabel
{
NSString* accessibilityLabel = [super FEX_accessibilityLabel];
if ([accessibilityLabel length] == 0)
{
accessibilityLabel = [self title];
}
return accessibilityLabel;
}
@end
@implementation NSView (FrankAutomation)
- (BOOL) FEX_raise
{
return [[self window] FEX_raise];
}
- (BOOL) FEX_simulateClick
{
BOOL returnValue = [super FEX_simulateClick];
if (!returnValue)
{
returnValue = [[self window] makeFirstResponder: nil];
if (returnValue)
{
[[self window] makeKeyWindow];
returnValue = [[self window] makeFirstResponder: self];
}
}
return returnValue;
}
- (CGRect) FEX_accessibilityFrame
{
CGRect returnValue = [super FEX_accessibilityFrame];
CGRect visibleRect = [self visibleRect];
returnValue.size.width = visibleRect.size.width;
returnValue.size.height = visibleRect.size.height;
return returnValue;
}
- (NSArray*) FEX_children
{
NSArray* subviews = [[self subviews] mutableCopy];
NSMutableArray* children = [NSMutableArray array];
for (NSView* subview in subviews)
{
CGRect frame = [subview FEX_accessibilityFrame];
if (frame.size.width > 0 && frame.size.height > 0)
{
[children addObject: subview];
}
}
return children;
}
- (id) FEX_parent
{
id parent = [super FEX_parent];
if (parent == nil)
{
parent = [self superview];
}
if (parent == nil)
{
parent = [self window];
}
return parent;
}
- (BOOL) FEX_isExpanded
{
return [[self superview] FEX_isExpanded];
}
- (BOOL) FEX_expand
{
return [[self superview] FEX_expand];
}
- (BOOL) FEX_collapse
{
return [[self superview] FEX_collapse];
}
- (BOOL) FEX_isVisible
{
if ([self isHidden])
{
return NO;
}
if ([self superview] != nil)
{
return [[self superview] FEX_isVisible];
}
else
{
return YES;
}
}
- (BOOL) FEX_isFullyVisible
{
if (![self FEX_isVisible])
{
return NO;
}
CGRect frame = [self frame];
CGRect visibleRect = [self visibleRect];
return NSEqualSizes(frame.size, visibleRect.size);
}
@end
@implementation NSScrollView (FrankAutomation)
- (void) FEX_scrollToTop
{
[[self contentView] scrollToPoint: CGPointZero];
}
- (void) FEX_scrollToBottom
{
CGPoint maxContentOffset = CGPointZero;
maxContentOffset.y = [self contentSize].height - [self frame].size.height;
[[self contentView] scrollToPoint: maxContentOffset];
}
- (void) FEX_setContentOffsetX: (NSInteger) x
y: (NSInteger) y
{
[[self contentView] scrollToPoint: CGPointMake(x, y)];
}
@end
static const NSString* FEX_TableAttribute = @"FEX_TableAttribute";
static const NSString* FEX_IndexAttribute = @"FEX_IndexAttribute";
@implementation NSTableRowView (FrankAutomation)
- (void) FEX_setTable: (NSTableView*) table
{
objc_setAssociatedObject(self, FEX_TableAttribute, table, OBJC_ASSOCIATION_ASSIGN);
}
- (void) FEX_setIndex: (NSUInteger) index
{
objc_setAssociatedObject(self, FEX_IndexAttribute, [NSNumber numberWithInteger: index], OBJC_ASSOCIATION_RETAIN);
}
- (BOOL) FEX_isExpanded
{
BOOL returnValue = NO;
NSTableView* table = objc_getAssociatedObject(self, FEX_TableAttribute);
if ([table isKindOfClass: [NSOutlineView class]])
{
NSUInteger index = [objc_getAssociatedObject(self, FEX_IndexAttribute) integerValue];
id item = [(NSOutlineView*) table itemAtRow: index];
returnValue = [(NSOutlineView*) table isItemExpanded: item];
}
return returnValue;
}
- (BOOL) FEX_expand
{
BOOL returnValue = NO;
NSTableView* table = objc_getAssociatedObject(self, FEX_TableAttribute);
if ([table isKindOfClass: [NSOutlineView class]])
{
NSUInteger index = [objc_getAssociatedObject(self, FEX_IndexAttribute) integerValue];
returnValue = YES;
id item = [(NSOutlineView*) table itemAtRow: index];
if (![(NSOutlineView*) table isItemExpanded: item])
{
[(NSOutlineView*) table expandItem: item];
}
}
return returnValue;
}
- (BOOL) FEX_collapse
{
BOOL returnValue = NO;
NSTableView* table = objc_getAssociatedObject(self, FEX_TableAttribute);
if ([table isKindOfClass: [NSOutlineView class]])
{
NSUInteger index = [objc_getAssociatedObject(self, FEX_IndexAttribute) integerValue];
returnValue = YES;
id item = [(NSOutlineView*) table itemAtRow: index];
if ([(NSOutlineView*) table isItemExpanded: item])
{
[(NSOutlineView*) table collapseItem: item];
}
}
return returnValue;
}
@end
@implementation NSTableView (FrankAutomation)
- (NSArray*) FEX_children
{
NSMutableArray* children = [NSMutableArray array];
if ([self headerView] != nil)
{
for (NSTableColumn* column in [self tableColumns])
{
CGRect frame = [column FEX_accessibilityFrame];
if (frame.size.width > 0 && frame.size.height > 0)
{
[children addObject: column];
}
}
}
CGRect visibleRect = [self visibleRect];
NSRange rowRange = [self rowsInRect: visibleRect];
for (NSUInteger rowNum = rowRange.location; rowNum < rowRange.length; ++rowNum)
{
CGRect rowRect = [self rectOfRow: rowNum];
rowRect = NSIntersectionRect(rowRect, visibleRect);
FEXTableRow* row = [[[FEXTableRow alloc] initWithFrame: rowRect
table: self
index: rowNum] autorelease];
for (NSUInteger colNum = 0; colNum < [self numberOfColumns]; ++colNum)
{
CGRect objectFrame = [self frameOfCellAtColumn: colNum row: rowNum];
objectFrame = NSIntersectionRect(objectFrame, visibleRect);
id cellValue = [self viewAtColumn: colNum
row: rowNum
makeIfNecessary: NO];
if (cellValue != nil)
{
if (colNum == 0)
{
[(NSTableRowView*) [cellValue superview] FEX_setTable: self];
[(NSTableRowView*) [cellValue superview] FEX_setIndex: rowNum];
[children addObject: [cellValue superview]];
}
}
else
{
// We need to wrap the NSTableView cell in an object to
// be accessible to Frank.
id<NSTableViewDataSource> dataSource = [self dataSource];
if (dataSource != nil)
{
cellValue = [dataSource tableView: self
objectValueForTableColumn: colNum
row: rowNum];
}
else
{
cellValue = [[self preparedCellAtColumn: colNum row: rowNum] objectValue];
}
if (cellValue != nil)
{
FEXTableCell* cell = [[FEXTableCell alloc] initWithFrame: objectFrame
row: row
value: cellValue];
if (objectFrame.size.width > 0 && objectFrame.size.height > 0)
{
[row addSubview: cell];
}
[cell release];
}
if (row != nil && colNum == 0)
{
[children addObject: row];
}
}
}
}
return children;
}
- (void) FEX_scrollToRow: (NSInteger) row
inSection: (NSInteger) section
{
[self scrollRowToVisible: row];
}
@end
@implementation NSOutlineView (FrankAutomation)
- (NSArray*) FEX_children
{
NSMutableArray* children = [NSMutableArray array];
if ([self headerView] != nil)
{
for (NSTableColumn* column in [self tableColumns])
{
CGRect frame = [column FEX_accessibilityFrame];
if (frame.size.width > 0 && frame.size.height > 0)
{
[children addObject: column];
}
}
}
CGRect visibleRect = [self visibleRect];
NSRange rowRange = [self rowsInRect: visibleRect];
id<NSOutlineViewDataSource> dataSource = [self dataSource];
for (NSUInteger rowNum = rowRange.location; rowNum < rowRange.length; ++rowNum)
{
NSView* rowView = [self rowViewAtRow: rowNum makeIfNecessary: NO];
if (rowView != nil)
{
[(NSTableRowView*) rowView FEX_setTable: self];
[(NSTableRowView*) rowView FEX_setIndex: rowNum];
[children addObject: rowView];
}
else
{
CGRect rowRect = [self rectOfRow: rowNum];
rowRect = NSIntersectionRect(rowRect, visibleRect);
FEXTableRow* row = [[[FEXTableRow alloc] initWithFrame: rowRect
table: self
index: rowNum] autorelease];
id item = [self itemAtRow: rowNum];
for (NSUInteger colNum = 0; colNum < [self numberOfColumns]; ++colNum)
{
id cellValue = nil;
CGRect colRect = [self frameOfCellAtColumn: colNum row: rowNum];
colRect = NSIntersectionRect(colRect, visibleRect);
if (colRect.size.width > 0 && colRect.size.height > 0)
{
if (dataSource != nil)
{
cellValue = [dataSource outlineView: self
objectValueForTableColumn: [[self tableColumns] objectAtIndex: colNum]
byItem: item];
}
else
{
cellValue = [[self preparedCellAtColumn: colNum row: rowNum] objectValue];
}
}
if (cellValue != nil)
{
FEXTableCell* cell = [[FEXTableCell alloc] initWithFrame: colRect
row: row
value: cellValue];
[row addSubview: cell];
[cell release];
}
}
if (row != nil)
{
[children addObject: row];
}
}
}
return children;
}
@end
@implementation NSTableColumn (FrankAutomation)
- (NSString*) FEX_accessibilityLabel
{
NSString* returnValue = [super FEX_accessibilityLabel];
if (returnValue == nil || [returnValue isEqualToString: @""])
{
returnValue = [[self headerCell] FEX_accessibilityLabel];
if (returnValue == nil || [returnValue isEqualToString: @""])
{
returnValue = [[self headerCell] stringValue];
}
}
return returnValue;
}
- (CGRect) FEX_accessibilityFrame
{
NSTableHeaderView* headerView = [[self tableView] headerView];
CGRect enclosingFrame = [[[headerView tableView] enclosingScrollView] visibleRect];
CGRect headerFrame = [headerView frame];
NSUInteger colNum = [[[self tableView] tableColumns] indexOfObject: self];
CGRect columnFrame = [[self tableView] rectOfColumn: colNum];
headerFrame.origin.x = columnFrame.origin.x;
headerFrame.size.width = columnFrame.size.width;
headerFrame = NSIntersectionRect(headerFrame, enclosingFrame);
CGRect accessibilityFrame = [headerView convertRect: headerFrame toView: nil];
accessibilityFrame = [[headerView window] convertRectToScreen: accessibilityFrame];
accessibilityFrame = [NSScreen FEX_flipCoordinates: accessibilityFrame];
return accessibilityFrame;
}
@end
@implementation NSTabView (FrankAutomation)
- (NSArray*) FEX_children
{
NSMutableArray* children = [NSMutableArray array];
[children addObjectsFromArray: [self tabViewItems]];
[children addObjectsFromArray: [super FEX_children]];
return children;
}
@end
@implementation NSTabViewItem (FrankAutomation)
- (void) FEX_simulateClick
{
[[self tabView] selectTabViewItem: self];
}
- (NSString*) FEX_accessibilityLabel
{
NSString* accessibilityLabel = [super FEX_accessibilityLabel];
if ([accessibilityLabel length] == 0)
{
accessibilityLabel = [self label];
}
return accessibilityLabel;
}
@end
#endif
Jump to Line
Something went wrong with that request. Please try again.