From: Frank Huang Date: Fri, 27 Jul 2012 18:55:00 +0000 (-0700) Subject: Forked Enjoy, mouse movement X-Git-Tag: version-1.0~132 X-Git-Url: https://git.yukkurigames.com/?a=commitdiff_plain;h=530009447c5bbd360ac5023979cffc6d32a28df3;p=enjoyable.git Forked Enjoy, mouse movement --- diff --git a/ApplicationController.h b/ApplicationController.h new file mode 100644 index 0000000..9d1607f --- /dev/null +++ b/ApplicationController.h @@ -0,0 +1,34 @@ +// +// ApplicationController.h +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class JoystickController; +@class TargetController; +@class ConfigsController; + +@interface ApplicationController : NSObject { + IBOutlet JoystickController *jsController; + IBOutlet TargetController *targetController; + IBOutlet ConfigsController *configsController; + + IBOutlet NSDrawer *drawer; + IBOutlet NSWindow *mainWindow; + IBOutlet NSToolbarItem* activeButton; + IBOutlet NSMenuItem* activeMenuItem; + IBOutlet NSMenu* dockMenuBase; +} + +@property(readwrite) BOOL active; +@property(readonly) JoystickController * jsController; +@property(readonly) TargetController * targetController; +@property(readonly) ConfigsController * configsController; +-(IBAction) toggleActivity: (id)sender; +-(void) configsChanged; +-(void) configChanged; + +@end diff --git a/ApplicationController.m b/ApplicationController.m new file mode 100644 index 0000000..c87a6ee --- /dev/null +++ b/ApplicationController.m @@ -0,0 +1,85 @@ +// +// ApplicationController.m +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// +#include + +@implementation ApplicationController + +@synthesize jsController, targetController, configsController; + +static BOOL active; + +pascal OSStatus appSwitch(EventHandlerCallRef handlerChain, EventRef event, void* userData); + +-(void) applicationDidFinishLaunching: (NSNotification*) notification { + [jsController setup]; + [drawer open]; + [targetController setEnabled: false]; + [self setActive: NO]; + [configsController load]; + EventTypeSpec et; + et.eventClass = kEventClassApplication; + et.eventKind = kEventAppFrontSwitched; + EventHandlerUPP handler = NewEventHandlerUPP(appSwitch); + InstallApplicationEventHandler(handler, 1, &et, self, NULL); +} + +-(void) applicationWillTerminate: (NSNotification *)aNotification { + [configsController save]; +} + +- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication + hasVisibleWindows:(BOOL)flag +{ + [mainWindow makeKeyAndOrderFront:self]; + return YES; +} + +pascal OSStatus appSwitch(EventHandlerCallRef handlerChain, EventRef event, void* userData) { + ApplicationController* self = (ApplicationController*)userData; + NSDictionary* currentApp = [[NSWorkspace sharedWorkspace] activeApplication]; + ProcessSerialNumber psn; + psn.lowLongOfPSN = [[currentApp objectForKey:@"NSApplicationProcessSerialNumberLow"] longValue]; + psn.highLongOfPSN = [[currentApp objectForKey:@"NSApplicationProcessSerialNumberHigh"] longValue]; + [self->configsController applicationSwitchedTo: [currentApp objectForKey:@"NSApplicationName"] withPsn: psn]; + return noErr; +} + +-(BOOL) active { + return active; +} + +-(void) setActive: (BOOL) newActive { + [activeButton setLabel: (newActive ? @"Stop" : @"Start")]; + [activeButton setImage: [NSImage imageNamed: (newActive ? @"NSStopProgressFreestandingTemplate" : @"NSGoRightTemplate" )]]; + [activeMenuItem setState: (newActive ? 1 : 0)]; + active = newActive; +} + +-(IBAction) toggleActivity: (id)sender { + [self setActive: ![self active]]; +} + +-(void) configsChanged { + while([dockMenuBase numberOfItems] > 2) + [dockMenuBase removeItemAtIndex: ([dockMenuBase numberOfItems] - 1)]; + + for(Config* config in [configsController configs]) { + [dockMenuBase addItemWithTitle:[config name] action:@selector(chooseConfig:) keyEquivalent:@""]; + } + [self configChanged]; +} +-(void) configChanged { + Config* current = [configsController currentConfig]; + NSArray* configs = [configsController configs]; + for(int i=0; i<[configs count]; i++) + [[dockMenuBase itemAtIndex: (2+i)] setState: (([configs objectAtIndex:i] == current) ? YES : NO)]; +} + +-(void) chooseConfig: (id) sender { + [configsController activateConfig: [[configsController configs] objectAtIndex: ([dockMenuBase indexOfItem: sender]-2)] forApplication: NULL]; +} +@end diff --git a/Config.h b/Config.h new file mode 100644 index 0000000..1120ede --- /dev/null +++ b/Config.h @@ -0,0 +1,25 @@ +// +// Config.h +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class Target; + +@interface Config : NSObject { + NSString *name; + BOOL protect; + NSMutableDictionary *entries; +} + +@property(readwrite) BOOL protect; +@property(readwrite, copy) NSString* name; +@property(readonly) NSMutableDictionary* entries; + +-(void) setTarget:(Target*)target forAction:(id)jsa; +-(Target*) getTargetForAction: (id) jsa; + +@end diff --git a/Config.m b/Config.m new file mode 100644 index 0000000..de3c1cc --- /dev/null +++ b/Config.m @@ -0,0 +1,26 @@ +// +// Config.m +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// + +@implementation Config + +-(id) init { + if(self=[super init]) { + entries = [[NSMutableDictionary alloc] init]; + } + return self; +} + +@synthesize protect, name, entries; + +-(void) setTarget:(Target*)target forAction:(id)jsa { + [entries setValue:target forKey: [jsa stringify]]; +} +-(Target*) getTargetForAction: (id) jsa { + return [entries objectForKey: [jsa stringify]]; +} + +@end diff --git a/ConfigsController.h b/ConfigsController.h new file mode 100644 index 0000000..39f3a0d --- /dev/null +++ b/ConfigsController.h @@ -0,0 +1,39 @@ +// +// ConfigsController.h +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class Config; +@class TargetController; + +@interface ConfigsController : NSObject { + NSMutableArray* configs; + IBOutlet NSButton* removeButton; + IBOutlet NSTableView* tableView; + IBOutlet TargetController* targetController; + Config* currentConfig; + Config* neutralConfig; /* last config to be manually selected */ + ProcessSerialNumber attachedApplication; +} + +-(IBAction) addPressed: (id)sender; +-(IBAction) removePressed: (id)sender; +-(void) activateConfig: (Config*)config forApplication: (ProcessSerialNumber*) psn; + +-(NSDictionary*) dumpAll; +-(void) loadAllFrom: (NSDictionary*) dict; + +@property(readonly) Config* currentConfig; +@property(readonly) Config* currentNeutralConfig; +@property(readonly) NSArray* configs; +@property(readonly) ProcessSerialNumber* targetApplication; +-(void) save; +-(void) load; + +-(void) applicationSwitchedTo: (NSString*) name withPsn: (ProcessSerialNumber) psn; + +@end diff --git a/ConfigsController.m b/ConfigsController.m new file mode 100644 index 0000000..43c92d3 --- /dev/null +++ b/ConfigsController.m @@ -0,0 +1,196 @@ +// +// ConfigsController.m +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// + +@implementation ConfigsController + +@synthesize configs; + +-(id) init { + if(self = [super init]) { + configs = [[NSMutableArray alloc] init]; + currentConfig = [[Config alloc] init]; + [currentConfig setName: @"(default)"]; + [currentConfig setProtect: YES]; + [configs addObject: currentConfig]; + } + return self; +} + +-(void) restoreNeutralConfig { + if(!neutralConfig) + return; + if([configs indexOfObject:neutralConfig] < 0) {// deleted, keep what we have + neutralConfig = NULL; + return; + } + [self activateConfig: neutralConfig forApplication: NULL]; +} + +-(void) activateConfig: (Config*)config forApplication: (ProcessSerialNumber*) psn { + if(currentConfig == config) + return; + + if(psn) { + if(!neutralConfig) + neutralConfig = currentConfig; + attachedApplication = *psn; + } else { + neutralConfig = NULL; + } + + if(currentConfig != NULL) { + [targetController reset]; + } + currentConfig = config; + [removeButton setEnabled: ![config protect]]; + [targetController load]; + [[[NSApplication sharedApplication] delegate] configChanged]; + [tableView selectRow: [configs indexOfObject: config] byExtendingSelection: NO]; +} + +-(IBAction) addPressed: (id)sender { + Config* newConfig = [[Config alloc] init]; + [newConfig setName: @"untitled"]; + [configs addObject: newConfig]; + [[[NSApplication sharedApplication] delegate] configsChanged]; + [tableView reloadData]; + [tableView selectRow: ([configs count]-1) byExtendingSelection: NO]; + [tableView editColumn: 0 row:([configs count]-1) withEvent:nil select:YES]; +} +-(IBAction) removePressed: (id)sender { + // save changes first + [tableView reloadData]; + Config* current_config = [configs objectAtIndex: [tableView selectedRow]]; + if([current_config protect]) + return; + [configs removeObjectAtIndex: [tableView selectedRow]]; + + // remove all "switch to configuration" actions + for(int i=0; i<[configs count]; i++) { + NSMutableDictionary* entries = [(Config*)[configs objectAtIndex:i] entries]; + for(id key in entries) { + Target* target = (Target*) [entries objectForKey: key]; + if([target isKindOfClass: [TargetConfig class]] && [(TargetConfig*)target config] == current_config) + [entries removeObjectForKey: key]; + } + } + [[[NSApplication sharedApplication] delegate] configsChanged]; + + [tableView reloadData]; +} + +-(void)tableViewSelectionDidChange:(NSNotification*) notify { + [self activateConfig: (Config*)[configs objectAtIndex:[tableView selectedRow]] forApplication: NULL]; +} + +-(id) tableView: (NSTableView*)view objectValueForTableColumn: (NSTableColumn*) column row: (int) index { + NSParameterAssert(index >= 0 && index < [configs count]); + return [[configs objectAtIndex: index] name]; +} + +-(void) tableView: (NSTableView*) view setObjectValue:obj forTableColumn:(NSTableColumn*) col row: (int)index { + NSParameterAssert(index >= 0 && index < [configs count]); + /* ugly hack so stringification doesn't fail */ + NSString* newName = [(NSString*)obj stringByReplacingOccurrencesOfString: @"~" withString: @""]; + [(Config*)[configs objectAtIndex: index] setName: newName]; + [targetController refreshConfigsPreservingSelection:YES]; + [tableView reloadData]; + [[[NSApplication sharedApplication] delegate] configsChanged]; +} + +-(int)numberOfRowsInTableView: (NSTableView*)table { + return [configs count]; +} + +-(BOOL)tableView: (NSTableView*)view shouldEditTableColumn: (NSTableColumn*) column row: (int) index { + return ![[configs objectAtIndex: index] protect]; +} + +-(Config*) currentConfig { + return currentConfig; +} + +-(Config*) currentNeutralConfig { + if(neutralConfig) + return neutralConfig; + return currentConfig; +} + +-(void) save { + [[NSUserDefaults standardUserDefaults] setObject:[self dumpAll] forKey:@"configurations"]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} +-(void) load { + [self loadAllFrom: [[NSUserDefaults standardUserDefaults] objectForKey:@"configurations"]]; +} + +-(NSDictionary*) dumpAll { + NSMutableDictionary *envelope = [[NSMutableDictionary alloc] init]; + NSMutableArray* ary = [[NSMutableArray alloc] init]; + for(Config* config in configs) { + NSMutableDictionary* cfgInfo = [[NSMutableDictionary alloc] init]; + [cfgInfo setObject:[config name] forKey:@"name"]; + NSMutableDictionary* cfgEntries = [[NSMutableDictionary alloc] init]; + for(id key in [config entries]) { + [cfgEntries setObject:[[[config entries]objectForKey:key]stringify] forKey: key]; + } + [cfgInfo setObject: cfgEntries forKey: @"entries"]; + [ary addObject: cfgInfo]; + } + [envelope setObject: ary forKey: @"configurationList"]; + [envelope setObject: [NSNumber numberWithInt: [configs indexOfObject: [self currentNeutralConfig] ] ] forKey: @"selectedIndex"]; + return envelope; +} +-(void) loadAllFrom: (NSDictionary*) envelope{ + if(envelope == NULL) + return; + NSArray* ary = [envelope objectForKey: @"configurationList"]; + + NSMutableArray* newConfigs = [[NSMutableArray alloc] init]; + // have to do two passes in case config1 refers to config2 via a TargetConfig + for(int i=0; i<[ary count]; i++) { + Config* cfg = [[Config alloc] init]; + [cfg setName: [[ary objectAtIndex:i] objectForKey:@"name"]]; + [newConfigs addObject: cfg]; + } + [[configs objectAtIndex:0] setProtect: YES]; + for(int i=0; i<[ary count]; i++) { + NSDictionary* dict = [[ary objectAtIndex:i] objectForKey:@"entries"]; + for(id key in dict) { + [[[newConfigs objectAtIndex:i] entries] + setObject: [Target unstringify: [dict objectForKey: key] withConfigList: newConfigs] + forKey: key]; + } + } + + configs = newConfigs; + [tableView reloadData]; + currentConfig = NULL; + [[[NSApplication sharedApplication] delegate] configsChanged]; + + int index = [[envelope objectForKey: @"selectedIndex"] intValue]; + [self activateConfig: [configs objectAtIndex:index] forApplication: NULL]; +} + +-(void) applicationSwitchedTo: (NSString*) name withPsn: (ProcessSerialNumber) psn { + for(int i=0; i<[configs count]; i++) { + Config* cfg = [configs objectAtIndex:i]; + if([[cfg name] isEqualToString: name]) { + [self activateConfig: cfg forApplication: &psn]; + return; + } + } + [self restoreNeutralConfig]; +} + +-(ProcessSerialNumber*) targetApplication { + if(neutralConfig) + return &attachedApplication; + return NULL; +} + +@end diff --git a/Credits.rtf b/Credits.rtf new file mode 100644 index 0000000..d05d232 --- /dev/null +++ b/Credits.rtf @@ -0,0 +1,13 @@ +{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf430 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\paperw11900\paperh16840\margl1440\margr1440\vieww9800\viewh11760\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural + +\f0\fs24 \cf0 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ +\ +The above copyright notice and this permission notice shall be included in\ +all copies or substantial portions of the Software.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ +} \ No newline at end of file diff --git a/English.lproj/InfoPlist.strings b/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..5e45963 Binary files /dev/null and b/English.lproj/InfoPlist.strings differ diff --git a/English.lproj/MainMenu.xib b/English.lproj/MainMenu.xib new file mode 100644 index 0000000..a7843f1 --- /dev/null +++ b/English.lproj/MainMenu.xib @@ -0,0 +1,4018 @@ + + + + 1050 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 2182 + + + YES + NSTextView + NSMatrix + NSMenu + NSToolbarItem + NSButton + NSPopUpButton + NSToolbarFlexibleSpaceItem + NSCustomObject + NSSplitView + NSTableView + NSCustomView + NSTextField + NSToolbarSeparatorItem + NSWindowTemplate + NSTextFieldCell + NSButtonCell + NSTableColumn + NSSegmentedControl + NSToolbarSpaceItem + NSView + NSOutlineView + NSPopUpButtonCell + NSToolbar + NSScrollView + NSBox + NSSegmentedCell + NSScroller + NSMenuItem + NSDrawer + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + + + PluginDependencyRecalculationVersion + + + + YES + + NSApplication + + + FirstResponder + + + NSApplication + + + AMainMenu + + YES + + + Enjoy + + 1048576 + 2147483647 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + submenuAction: + + Enjoy + + YES + + + About Enjoy + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Preferences… + , + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Services + + 1048576 + 2147483647 + + + submenuAction: + + Services + + YES + + _NSServicesMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Hide NewApplication + h + 1048576 + 2147483647 + + + + + + Hide Others + h + 1572864 + 2147483647 + + + + + + Show All + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Quit Enjoy + q + 1048576 + 2147483647 + + + + + _NSAppleMenu + + + + + File + + 1048576 + 2147483647 + + + submenuAction: + + File + + YES + + + Joysticks active + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + + + + Edit + + 1048576 + 2147483647 + + + submenuAction: + + Edit + + YES + + + Undo + z + 1048576 + 2147483647 + + + + + + Redo + Z + 1179648 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Cut + x + 1048576 + 2147483647 + + + + + + Copy + c + 1048576 + 2147483647 + + + + + + Paste + v + 1048576 + 2147483647 + + + + + + Delete + + 1048576 + 2147483647 + + + + + + Select All + a + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Find + + 1048576 + 2147483647 + + + submenuAction: + + Find + + YES + + + Find… + f + 1048576 + 2147483647 + + + 1 + + + + Find Next + g + 1048576 + 2147483647 + + + 2 + + + + Find Previous + G + 1179648 + 2147483647 + + + 3 + + + + Use Selection for Find + e + 1048576 + 2147483647 + + + 7 + + + + Jump to Selection + j + 1048576 + 2147483647 + + + + + + + + + Spelling and Grammar + + 1048576 + 2147483647 + + + submenuAction: + + Spelling and Grammar + + YES + + + Show Spelling… + : + 1048576 + 2147483647 + + + + + + Check Spelling + ; + 1048576 + 2147483647 + + + + + + Check Spelling While Typing + + 1048576 + 2147483647 + + + + + + Check Grammar With Spelling + + 1048576 + 2147483647 + + + + + + + + + Substitutions + + 1048576 + 2147483647 + + + submenuAction: + + Substitutions + + YES + + + Smart Copy/Paste + f + 1048576 + 2147483647 + + + 1 + + + + Smart Quotes + g + 1048576 + 2147483647 + + + 2 + + + + Smart Links + G + 1179648 + 2147483647 + + + 3 + + + + + + + Speech + + 1048576 + 2147483647 + + + submenuAction: + + Speech + + YES + + + Start Speaking + + 1048576 + 2147483647 + + + + + + Stop Speaking + + 1048576 + 2147483647 + + + + + + + + + + + + View + + 1048576 + 2147483647 + + + submenuAction: + + View + + YES + + + Show Toolbar + t + 1572864 + 2147483647 + + + + + + Customize Toolbar… + + 1048576 + 2147483647 + + + + + + + + + Window + + 1048576 + 2147483647 + + + submenuAction: + + Window + + YES + + + Minimize + m + 1048576 + 2147483647 + + + + + + Zoom + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Bring All to Front + + 1048576 + 2147483647 + + + + + _NSWindowsMenu + + + + + Help + + 1048576 + 2147483647 + + + submenuAction: + + Help + + YES + + + NewApplication Help + ? + 1048576 + 2147483647 + + + + + + + + _NSMainMenu + + + NSFontManager + + + 15 + 2 + {{355, 463}, {770, 487}} + 1677721600 + Enjoy + NSWindow + + + AC1F5C48-4C16-4C9D-9779-B783AF35E2E1 + + + YES + YES + NO + YES + 1 + 2 + + YES + + YES + 42CA6E7F-AC4A-4681-98B6-B9901269E463 + EC49C240-1D96-41C2-AD75-D75EEB29EDEA + NSToolbarFlexibleSpaceItem + NSToolbarSeparatorItem + NSToolbarSpaceItem + + + YES + + + 42CA6E7F-AC4A-4681-98B6-B9901269E463 + + Start + Start/Stop + + + + NSImage + NSGoRightTemplate + + + + {0, 0} + {0, 0} + YES + YES + -1 + YES + 0 + + + + EC49C240-1D96-41C2-AD75-D75EEB29EDEA + + Configurations + Configurations + + + + NSImage + NSMultipleDocuments + + + + {0, 0} + {0, 0} + YES + YES + -1 + YES + 0 + + + NSToolbarFlexibleSpaceItem + + Flexible Space + + + + + + {1, 5} + {20000, 32} + YES + YES + -1 + YES + 0 + + YES + YES + + + 1048576 + 2147483647 + + + + + + NSToolbarSeparatorItem + + Separator + + + + + + {12, 5} + {12, 1000} + YES + YES + -1 + YES + 0 + + YES + YES + + + 1048576 + 2147483647 + + + + + + NSToolbarSpaceItem + + Space + + + + + + {32, 5} + {32, 32} + YES + YES + -1 + YES + 0 + + YES + YES + + + 1048576 + 2147483647 + + + + + + + + YES + + + + + + + + YES + + + + + + YES + + + + + + 256 + + YES + + + 274 + + YES + + + 256 + + YES + + + 274 + + YES + + + 2304 + + YES + + + 256 + {242, 485} + + + + YES + + + 256 + {{474, 0}, {16, 17}} + + + YES + + 239 + 16 + 1000 + + 75628096 + 2048 + + + LucidaGrande + 11 + 3100 + + + 3 + MC4zMzMzMzI5OQA + + + 6 + System + headerTextColor + + 3 + MAA + + + + + 337772096 + 2048 + Text Cell + + LucidaGrande + 13 + 1044 + + + + 6 + System + controlBackgroundColor + + 3 + MC42NjY2NjY2NjY3AA + + + + 6 + System + controlTextColor + + + + 3 + YES + + + + 3 + 2 + + 3 + MQA + + + 6 + System + gridColor + + 3 + MC41AA + + + 17 + -767557632 + + + 4 + 15 + 0 + YES + 0 + 1 + + + {{1, 1}, {242, 485}} + + + + + + 4 + + + + -2147483392 + {{1, 1}, {8, 298}} + + + + + _doScroller: + 0.4210526 + + + + -2147483392 + {{-100, -100}, {473, 15}} + + + + 1 + + _doScroller: + 0.99789030000000001 + + + {244, 487} + + + + 133650 + + + + QSAAAEEgAABBmAAAQZgAAA + + + {244, 487} + + + + NSView + + + + 256 + + YES + + + 268 + {{227, 57}, {180, 24}} + + + + _NS:9 + YES + + 67239424 + 0 + + LucidaGrande + 13 + 16 + + _NS:9 + + + YES + + 87 + Left + YES + 0 + + + 86 + Right + 1 + 0 + + + 1 + + + + + 268 + {{20, 39}, {201, 388}} + + + + 6 + 1 + + YES + + -1543373312 + 0 + Do nothing + + + 1 + 1211912703 + 128 + + NSRadioButton + + + + 200 + 25 + + + 604110336 + 0 + Press a key: + + + 1211912703 + 128 + + 549453824 + {18, 18} + + YES + + YES + + + + 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 +AAAAAF1KAAA0kQAACCVYWVogAAAAAAAAdCAAALRgAAAjPVhZWiAAAAAAAAAlbAAAFyoAAKfDWFlaIAAA +AAAAAPNSAAEAAAABFs9zZjMyAAAAAAABDEIAAAXe///zJgAAB5IAAP2R///7ov///aMAAAPcAADAbGN1 +cnYAAAAAAAAAAQHNAABjdXJ2AAAAAAAAAAEBzQAAY3VydgAAAAAAAAABAc0AAHZjZ3QAAAAAAAAAAAAD +AQAAAQACBAUGBwkKCw0ODxASExQWFxgaGxweHyAiIyQmJygpKywtLzAxMjM1Njc4OTs8PT5AQUJDREZH +SElKS0xOT1BRUlNUVVZXWFlaW1xdXl9hYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gIGCg4SF +hoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnZ6foKGio6SlpqanqKmqq6ytra6vsLGysrO0tba3uLi5uru8 +vL2+v8DBwcLDxMXGxsfIycrKy8zNzs7P0NHS0tPU1dbW19jZ2drb3Nzd3t/g4eLi4+Tl5ufo6enq6+zt +7u/w8fHy8/T19vf4+fr7/P3+/v8AAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR8gISIjJCUnKCkq +Ky0uLzAxMzQ1Njc4OTo7PD0/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaWltcXV5fYGFiY2RlZmdo +aWprbG1ub3BxcnN0dXZ3d3h5ent8fH1+f4CBgoKDhIWGh4iIiYqLjI2Oj5CRkpOUlJWWl5iZmpucnZ2e +n6ChoqOkpaamp6ipqqusra6vsLCxsrO0tba3uLm5uru8vb6/wMHCw8TFx8jJysvMzc7P0NDR0tPU1dbX +2Nna29ze3+Dh4uPk5ebn6err7O3u7/Hy8/T19vf5+vv8/f7/AAIDAwQFBgcICQoKCwwNDg8QERITFBUW +FxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODg5Ojs8PT4+P0BBQkNDREVGR0hJSUpLTE1O +Tk9QUVJSU1RVVVZXWFhZWltbXF1eXl9gYWFiY2RkZWZnZ2hpaWprbGxtbm5vcHFxcnNzdHV1dnd4eHl6 +ent8fH1+fn+AgYGCg4SEhYaHiImJiouMjY6Oj5CRkpOTlJWWl5iZmZqbnJ2en6ChoqOkpaanqKmqq6yt +rq+xsrO0tba3uLq7vL2+wMHDxMbHycrMzs/R0tTW19nb3d7g4uTm6Ors7vDy9Pb4+vz+/wAAbmRpbgAA +AAAAAAY2AACXGgAAVjoAAFPKAACJ3gAAJ8IAABaoAABQDQAAVDkAAiuFAAIZmQABeFEAAwEAAAIAAAAA +AAEABgANABcAIwAxAEAAUgBlAHsAkwCrAMUA4gD/AR8BPwFhAYUBqgHQAfgCIAJLAncCpQLSAwIDMwNl +A5gDzgQFBD0EdQSvBOsFKQVnBacF6AYqBm4GtQb8B0UHkgfkCDkIkAjnCT4JmAn0ClAKrQsLC2sLygwq +DIwM8Q1XDcAOKA6SDv4PbA/bEE0QxBE7EbQSMRKwEzITuRREFNAVYBXxFocXHhfAGGIZBBmsGlQa+RuU +HC4czh1yHhQeux9jIA0gvCFoIhkizyOJJEEk+SW6JnknOygFKMspkypiKzIsASzXLawuhy9gMD4xGzH8 +MtszvzSgNYY2cjdcOEw5OTorOxs8CD0EPfU+6z/nQOFB2ELUQ9VE00XcRttH5EjxSgBLCUwdTTFOUE9v +UI9Rt1LdVAVVNlZsV6VY4FohW21ct135X09goGH0Y0tkqGYFZ19oxGova5ptCG54b/BxbnLsdG119Xd/ +eQh6knwqfcV/W4D4gpSEO4Xih4CJKorYjIqOOY/jkZuTWJUOlsyYiZpSnB6d4Z+soX+jWqUvpxOo+6rj +rMuuwLC4sra0rra0uL+60LzfvwDBHcLdxLXGhchYyi7MCs3lz7rRmtOA1WPXR9kq2xPc/97s4M/iveSn +5o3obupT7ELuLPAM8fLz0PW396H5f/tZ/T3//wAAAAEAAwALABYAJQA3AE0AZQCBAJ8AwQDlAQsBNQFh +AZABwQH1AisCZAKfAtwDHANfA6MD6gQ0BH8EzQT1BR0FcAXEBhsGdAbPBy0HXAeMB+4IUgi4CSAJVAmK +CfYKZArVC0cLgQu8DDIMqw0mDaIOIQ6hDyQPqRAvELgQ/RFDEc8SXRLuE4AUFRSrFUMV3RZ5FxcXthhY +GPwZoRpIGvEbnBxJHPgdqB5bHw8fxSB9ITch8iKwJDAk8yW3Jn4nRigQKNwpqSp5K0osHCzxLccuoC95 +MFUxMzISMvMz1TS5NaA2hzdxOFw5STo4Oyg8Gj4DPvs/9EDuQepD6ETpRexG8Uf3SP9LFEwhTTBOQE9S +UGZSklOrVMVV4Vb/WB5ZP1phW4Vcq13SXvthUmJ/Y69k4GYSZ0dofGm0au1tZG6ib+FxInJlc6l073Y2 +d396FXtjfLJ+A39VgKmB/4NWhK+GCYjCiiGLgYzjjkePrJESknuT5Ja8mCuZm5sMnH+d9J9qoOGiWqPV +pVGmz6eOqE6pzqtRrNSuWq/gsWmy8rR+tgu5Kbq6vE294b93wQ7Cp8RBxd3He8kZyrrLisxbzf/Po9FK +0vHUm9ZF1/HZn9tO3Cbc/96x4GTiGePQ5YjnQegf6Pzquex27jbv9/G583z0X/VC9wj40Pqa/GX+Mf// +AAAAAQADAAsAJQA3AE0AZQCBAJ8AwQELATUBYQGQAcEB9QIrAmQCnwLcAxwDXwOjA+oENAR/BM0FHQVw +BcQGGwZ0Bs8HLQeMB+4IUgi4CSAJign2CmQK1QtHC7wMMgyrDSYNog4hDqEPJA+pEC8QuBFDEl0S7hOA +FBUUqxVDFnkXFxe2GFgY/BpIGvEbnBxJHPgdqB8PH8UgfSE3IfIjbyQwJPMltydGKBAo3Cp5K0osHC3H +LqAveTEzMhIy8zS5NaA2hzhcOUk6ODwaPQ4+Az/0QO5C6EPoROlG8Uf3SglLFEwhTkBPUlF7UpJUxVXh +Vv9ZP1phXKtd0mAlYVJjr2TgZhJofGm0au1tZG6ib+FxInJldO92Nnd/eMl6FXyyfgN/VYCpgf+Er4YJ +h2WIwoohi4GOR4+skRKSe5PklVCWvJgrmZubDJx/nfSfaqDholqj1aVRps+oTqnOq1Gs1K2Xrlqv4LFp +svK0frYLt5m5Kbnxurq8Tb3hv3fBDsHawqfEQcUPxd3He8hKyRnKusuKzFvN/87Rz6PQdtFK0vHTxtSb +1kXXG9fx2MjZn9tO3Cbc/93Y3rHfiuBk4hni9ePQ5KzliOZk50HoH+j86drqueuX7HbtVu427xbv9/DX +8bnymvN89F/1QvYl9wj37PjQ+bX6mvt//GX9S/4x//8AAGRlc2MAAAAAAAAACkNvbG9yIExDRAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABIAAAAcAEMAbwBsAG8AcgAgAEwAQwBE +AABtbW9kAAAAAAAABhAAAJxOAAAAAL5zkQAAAAAAAAAAAAAAAAAAAAAAdGV4dAAAAABDb3B5cmlnaHQg +QXBwbGUgQ29tcHV0ZXIsIEluYy4sIDIwMDUAAAAAA + + + + + + 3 + MCAwAA + + + + 400 + 75 + + + 604110336 + 0 + Switch to configuration + + + 1211912703 + 128 + + 549453824 + {18, 18} + + YES + + YES + + + + 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 +AAAAAHebAAA+KgAAAWRYWVogAAAAAAAAWbgAAK3VAAAZCFhZWiAAAAAAAAAlggAAFBwAALi5WFlaIAAA +AAAAAPNSAAEAAAABFs9zZjMyAAAAAAABDEIAAAXe///zJgAAB5IAAP2R///7ov///aMAAAPcAADAbGN1 +cnYAAAAAAAAAAQHNAABjdXJ2AAAAAAAAAAEBzQAAY3VydgAAAAAAAAABAc0AAHZjZ3QAAAAAAAAAAAAD +AQAAAgAAABAAOAB0AMIBMgHaArQDvgT0BnEIEAnrC+YN9hAXEkYUaRaHGJUanRyjHqkgsSK7JM8m5Cju +KvwtCC8UMQ4zBjT5Ntw4tDp9PEQ99D+fQUdC1ERURalHA0hVSalK+ExBTYpOy1AMUUtShFO7VO5WH1dP +WIBZrlrbXAddNV5dX5BgwmHvYxtkRWVqZopnqWjEadxq8mwGbRhuK286cEpxXnJvc390k3Wjdqh3pnij +eaB6nnudfJt9mX6Yf5uAnYGhgqaDr4S7hcaG1ofoiP2KEYsljD2NV45uj4iQoJG+ktmT9JURliyXRJhb +mW2af5uTnJudp56un7Ggs6GxoqyjsqSwpbOmsKevqKipnqqWq4eseK1lrlKvPLAjsQix7bLRs7C0kLVy +tle3Q7gyuSC6Drr6u+S8y72wvpS/d8BZwTvCHML7w9zEvMWdxn3HYMhCyR3J68qny2LMHMzXzZPOUM8N +z8vQjNFO0hHS19Oe1GbVL9X71sXXk9hj2S7Z7tqV2zvb4dyN3Trd596Y30nf/OCx4WXiGuLP44LkNeTo +5ZfmRub0557oNOi86TvpuOo16rPrMuux7DPstu067b/uRe7M71Tv3fBo8PPxffII8pTzIfOy9ET01/Vo +9fn2jPcf97H4RPjZ+W/6Bfqd+zj71fxz/RP9t/5b/wD/f///AAAADwA0AGsAtAERAaYCWgM3BE0Fogcl +CN8KzwzSDukRBRMZFSYXHBkGGucc0x61IKMikySEJm4oYCpJLDUuEy/rMbwzgjU8Nug4kDopO7Y9Qz61 +QBpBXEKgQ91FGUZUR4VItUniSwxMMk1UTnRPj1CoUcBS11PtVQFWElcnWDpZXlqCW6JcwV3fXvlgEGEm +YjhjSGRVZWJmamd4aH9pimqUa59sqW21brxvtnCocZhyhnN1dGd1V3ZIdzl4LHkhehZ7DHwFfQB9+375 +f/uA/YH/gwCEAYUAhf6G/ogAiQaKDYsUjB6NKY40jz6QRZFNklaTWpRelWCWX5dcmFaZUJpQm02cS51I +nkOfPKAzoSmiHKMNo/yk66XZpsGnqKiPqXaqV6s6rB2tBK30ruev27DOsb+yr7OctIi1c7Zdt0e4MLkY +uf+65rvNvLW9m76Ev2vAT8EswgDC0MOkxHTFRMYUxuTHtciGyVfKKcr7y87Moc10zkjPHc/z0MzRo9Jz +0z7UB9TO1ZzWaNc12AHYzdmb2mfbMtv83MTdid5N3w/fz+CN4UniAuK643HkKOTg5ZLmReb356XoUuj/ +6anqUer465zsP+zh7YPuJO7F72XwBvCu8WjyLfL187j0fPU/9gD2vPd3+DD45/ma+kv6+/up/Fb9Af2t +/lf+//9///8AAAANAC4AYAChAPEBbgIFAskDsQTGBfsHXgjaCmsMCQ2pD0oQ5BJ1E/kVghcHGIsaFhul +HToexCBOIdojYyTnJmIn2SlJKq8sCS1iLqov7TEwMl0zfTSINZA2lDeXOJc5kzqMO4E8dj1oPlc/REAu +QRVB/ELhQ8dEqkWNRnFHU0hJST9KMksmTBlNCU34TuVP0lC+UapSlVN/VGxVV1ZEVzNYIVkPWf9a6VvE +XJVdZF4xXwBfz2CfYW5iPWMNY95krmWAZlRnKGf8aNNprWqHa19sNW0Dbc1uk29ccCZw83HBco9zX3Qv +dP51y3aWd2B4KXjuebF6c3sue+h8o31mflF/O4AmgRCB9oLZg7qEmoV2hlCHKYgAiNeJq4p+i1KMJYz3 +jcmOno90kFqRQpIpkw+T9JTYlbuWnJd8mF2ZPpohmwSb6JzNnbSenZ+HoHShYqJKoyWj9aTApZCmXqcu +qACo0qmoqoKrXqw8rR6uAq7or8+wvbGnspazibR4tVq2MbcEt9i4tLmRunC7Urw3vR6+B77xv9vAxcGt +wpbDfcRixUbGJ8cGx+fI2cnKysLLs8ypzZ3OkM+C0HfRatJc007UP9Ux1iHXEtgE2PTZ49rU28fc4N4G +3zDgXOGP4svkE+Vo5sroR+nU637tPO8X8Q/zHPVE94f53Pw//pP//wAAbmRpbgAAAAAAAAY2AACl8AAA +VwMAAEopAACaUQAAJhgAABL1AABQDQAAVDkAAtR6AAJ9cAABq4UAAwEAAAIAAAAxAFIAbgCIAKEAtwDO +AOQA+QEOASMBOAFNAWMBeAGPAaUBvAHUAewCBgIfAjoCVgJyApACrQLMAusDCwMrA0wDbQOPA7ED0wP2 +BBkEPQRiBIgErgTUBPsFIwVLBXQFnwXKBfYGIwZQBn8GrwbhBxMHSAd+B7QH7AgnCGMIoAjfCR4JYgmo +CfEKRAqXCusLQwuaC/QMUAyvDQ4NcQ3WDjwOpA8PD3wP6xBdENERRhG8EjUSrxMsE6oUKBSrFSoVqxYu +FrUXPRfHGFUY5xl6GhEaqxtIG+ccih0uHdQefh8oH9IgfyEvId0ijiNJJAwk0SWZJmInKyf3KMUplCpi +KzIsAizULaQucy9FMBUw5DGzMoMzVjQpNPk1zDaiN3c4TzkjOfw61TuwPIs9aj5MPzFAHEEHQfJC6UPe +RNlF2kbdR+ZI9Un5SwhME00lTjhPUlByUZJSvFPoVRtWUFeLWM9aFVtcXK5eAV9TYKFh42MqZHFlv2cQ +aGhpxWsnbItt9G9fcNByQHO1dSl2oHgVeY57JnzwfsGAloJnhD2GEIfeia6LeI1BjwyQz5KZlFyWHJf9 +mjKccp6eoMui8KUTpzOpVKt6raiv2LIWtFy2trmrvO3AKcNhxofJqczEz9rS6dX+2RPcD98I4gzlDegV +6xbuEvEH8+v2yfmT/F7//wAAADUAWgB7AJgAsgDKAOIA+QEPASQBOgFQAWYBfAGTAaoBwgHbAfQCDgIp +AkYCYwKCAqECwgLjAwQDJgNKA20DkQO1A9oD/wQlBEwEdAScBMQE7gUYBUMFbgWbBckF+AYoBlkGiwa/ +BvQHKwdkB54H2QgWCFYImAjaCR4JZwmyCgIKWwqzCw8LbQvMDC0Mkgz5DWINzg48Dq0PIQ+XEBAQjREL +EYsSDhKTExoTpRQuFLsVQBXHFlIW3xdtF/4YkxkrGcQaYhsCG6YcSxz1HZ0eSx75H6ggWSEMIcAidSM2 +JAMk0yWnJnwnUSgpKQMp3iq5K5Uscy1RLi4vDC/rMMgxpTKEM2Y0STUvNhk3AzfuONc5vzqqO5Q8fj1p +PlY/R0A7QS9CI0MhRB1FHkYlRy5IPUlMSlZLaEx3TYxOo0/AUOBSBFMtVFtVjFa/V/xZPVqAW8ZdF15l +X7dg+WI4Y3hkuWYBZ0pommnua0Vsn238b1twwXImc5B0+XZmd9F5QXq7fEx97H+HgS2C0oR9hieH0omB +iy+M346QkEKR9JOolVaXCpjVmqycip5boDOiC6PqpcSnp6mOq4Cte698sYqznrXEt++6I7xWvp3A5sNC +xaXIF8qXzSbPwNJf1QfXttpH3HzequDn4yblb+fJ6i7soO8l8bX0Ufb7+ab8YP//AAAAOwBlAIkAqgDI +AOYBAgEeAToBVgFzAZABrgHNAe0CDgIxAlUCewKjAssC9AMfA0sDdwOkA9EEAAQuBF8EkQTEBPcFLAVi +BZkF0gYNBkkGhwbHBwkHTgeWB94IKwh7CM0JIAl6CdgKQQqtCx0LkAwFDH8M/A1+DgIOig8WD6YQOhDS +EW0SCxKsE1IT+BSjFUIV4xaIFy8X2BiGGTcZ6xqiG1scFxzXHZYeWR8dH+EgqCFxIjkjDyP7JOwl4ibZ +J9IozynOKs4r0SzWLdou4C/oMO0x9DL/NBA1MDZcN4c4sjnbOwY8Mj1fPpE/zEEKQk5DnUTzRlZHwkke +SjtLYkyITbVO6FAiUV9SplPwVUFWlFfvWU9ar1wUXX5e5WBLYZdi52Q5ZY9m6mhIaaxrE2x6beNvS3C2 +ch9ziXTwdlh3vHkhepR8Kn3Vf3eBIILFhGqGCoekiT2K0Ixgje2Pe5D/koqUD5WPlxiYwpqDnEed+5+u +oV6jCaSxplan+6mgq0is9a6ksFeyELPPtZi3ZLkMuri8Wb4Jv7DBXsMQxMPGdMgqyePLoM1dzx3Q4dKi +1GjWNNf82cPbSdzD3jnfsOEj4o3j7+VJ5prn4ekf6kzrcOyO7Z3up++q8KHxlPJ782H0QfUf9fX2yfeV ++F/5J/ns+rD7cfwz/O/9qf6S//8AAGRlc2MAAAAAAAAABWlNYWMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1s +dWMAAAAAAAAAEgAAAAxuYk5PAAAACAAAAOhwdFBUAAAACAAAAOhzdlNFAAAACAAAAOhmaUZJAAAACAAA +AOhkYURLAAAACAAAAOh6aENOAAAACAAAAOhmckZSAAAACAAAAOhqYUpQAAAACAAAAOhlblVTAAAACAAA +AOhwbFBMAAAACAAAAOhwdEJSAAAACAAAAOhlc0VTAAAACAAAAOh6aFRXAAAACAAAAOhydVJVAAAACAAA +AOhrb0tSAAAACAAAAOhkZURFAAAACAAAAOhubE5MAAAACAAAAOhpdElUAAAACAAAAOgAaQBNAGEAY21t +b2QAAAAAAAAGEAAAnGsAAAAAv9OKeAAAAAAAAAAAAAAAAAAAAAB0ZXh0AAAAAENvcHlyaWdodCBBcHBs +ZSwgSW5jLiwgMjAwOQA + + + + + + + + 400 + 75 + + + 604110336 + 0 + Mouse X + + + 1211912703 + 128 + + 400 + 75 + + + 604110336 + 0 + Mouse Y + + + 1211912703 + 128 + + 400 + 75 + + + 604110336 + 0 + Mouse button + + + 1211912703 + 128 + + 400 + 75 + + + {201, 63} + {4, 2} + 1151868928 + NSActionCell + + 604110336 + 0 + Radio + + 1211912703 + 128 + + 549453824 + {18, 18} + + YES + + YES + + + + TU0AKgAABRgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAADAAAAAwAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAADwRERGLJycnySsrK/A1NTXw +IyMjyRwcHIsJCQk8AAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFFRUVdVBQUOCoqKj/ +29vb//n5+f/6+vr/2tra/6qqqv9UVFTgHx8fdQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUZGRl5 +dXV198PDw//8/Pz////////////////////////////U1NT/fHx89yUlJXkAAAAFAAAAAAAAAAAAAAAA +AAAAAxEREUZqamrmtbW1/+3t7f/+/v7//v7+//7+/v/9/f3//f39//39/f/39/f/xMTE/3d3d+YZGRlG +AAAAAwAAAAAAAAAAAAAACkJCQqGtra3/xsbG/+vr6//y8vL/9fX1//X19f/z8/P/9fX1//Ly8v/u7u7/ +0tLS/6+vr/9KSkqhAAAACgAAAAAAAAAAAAAAF3h4eN2/v7//z8/P/93d3f/q6ur/7+/v/+/v7//w8PD/ +7e3t/+3t7f/i4uL/zs7O/8XFxf98fHzdAAAAFwAAAAAAAAADAAAAJKSkpPjOzs7/2dnZ/+Dg4P/i4uL/ +5eXl/+bm5v/n5+f/5eXl/+Li4v/e3t7/2tra/9DQ0P+srKz4AAAAJAAAAAMAAAADAAAALrCwsPrW1tb/ +3t7e/+Tk5P/p6en/6+vr/+zs7P/p6en/6+vr/+fn5//k5OT/4ODg/9nZ2f+zs7P6AAAALgAAAAMAAAAD +AAAALp2dnezg4OD/5eXl/+rq6v/u7u7/8PDw//Dw8P/x8fH/8PDw/+7u7v/q6ur/5ubm/+Hh4f+ZmZns +AAAALgAAAAMAAAADAAAAJG5ubs/l5eX/6enp/+/v7//y8vL/9vb2//r6+v/5+fn/9/f3//b29v/x8fH/ +6+vr/+Tk5P9ra2vPAAAAJAAAAAMAAAAAAAAAFy4uLpPCwsL67Ozs//Pz8//5+fn//v7+//7+/v/+/v7/ +/v7+//v7+//19fX/8PDw/8LCwvosLCyTAAAAFwAAAAAAAAAAAAAACgAAAENfX1/S5OTk/vn5+f/+/v7/ +///////////////////////////8/Pz/5ubm/l9fX9IAAABDAAAACgAAAAAAAAAAAAAAAwAAABcAAABl +YmJi3NLS0v3////////////////////////////////V1dX9ZGRk3AAAAGUAAAAXAAAAAwAAAAAAAAAA +AAAAAAAAAAUAAAAfAAAAZTMzM8KAgIDwv7+//O3t7f/t7e3/v7+//ICAgPAzMzPCAAAAZQAAAB8AAAAF +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAFwAAAEMAAAB3AAAAnwAAALMAAACzAAAAnwAAAHcAAABD +AAAAFwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAAAXAAAAJAAAAC4AAAAu +AAAAJAAAABcAAAAKAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAwAAAAMAAAADAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQEAAAMAAAABABIAAAEB +AAMAAAABABIAAAECAAMAAAAEAAAFugEDAAMAAAABAAEAAAEGAAMAAAABAAIAAAERAAQAAAABAAAACAES +AAMAAAABAAEAAAEVAAMAAAABAAQAAAEWAAMAAAABABIAAAEXAAQAAAABAAAFEAEcAAMAAAABAAEAAAFS +AAMAAAABAAEAAAFTAAMAAAAEAAAFwgAAAAAACAAIAAgACAABAAEAAQABA + + + + + + + + 400 + 75 + + + + 6 + System + controlColor + + + + + + + + 8460 + + YES + + + 2304 + + YES + + + 2322 + {172, 14} + + + + + + + + + + + + + YES + + + 6 + + + + 172 + 1 + + + 67111184 + 0 + + + + YES + + YES + NSBackgroundColor + NSColor + + + YES + + 6 + System + selectedTextBackgroundColor + + + + 6 + System + selectedTextColor + + + + + + + YES + + YES + NSColor + NSUnderline + + + YES + + 1 + MCAwIDEAA + + + + + + + 0 + + 6 + {463, 10000000} + {138, 0} + + + + {{2, 2}, {172, 20}} + + + + + + + {5, 5} + 0 + + 4 + + + + -2147483392 + {{-100, -100}, {15, 133}} + + + + + _doScroller: + 0.73888889999999996 + + + + -2147483392 + {{-100, -100}, {87, 18}} + + + + 1 + + _doScroller: + 1 + 0.94565220000000005 + + + {{229, 318}, {176, 24}} + + + + 133123 + + + + + + + 268 + {{226, 251}, {182, 26}} + + + + YES + + -1539178944 + 2048 + + + 109199615 + 129 + + + 400 + 75 + + YES + + OtherViews + + YES + + + -1 + 1 + YES + YES + 2 + + + + + 268 + {{5, 456}, {507, 17}} + + + + YES + + 68288064 + 138413056 + + + LucidaGrande-Bold + 13 + 16 + + No action selected + + + + + + + + 12 + {{12, 445}, {493, 5}} + + + + {0, 0} + + 67239424 + 0 + Box + + + 6 + System + textBackgroundColor + + + + 3 + MCAwLjgwMDAwMDAxAA + + + 3 + 2 + 0 + NO + + + {{253, 0}, {517, 487}} + + + + NSView + + + {770, 487} + + + + YES + + + {770, 487} + + + + + {{0, 0}, {1920, 1178}} + {10000000000000, 10000000000000} + YES + + + + 256 + + YES + + + 274 + + YES + + + 2304 + + YES + + + 256 + {320, 418} + + + + YES + + + 256 + {{306, 0}, {16, 17}} + + + YES + + 317 + 40 + 1000 + + 75628096 + 2048 + + + + 3 + MC4zMzMzMzI5OQA + + + + + 337772096 + 2048 + Text Cell + + + + + + 3 + YES + YES + + + + 3 + 2 + + + 17 + 46170112 + + + 1 + 15 + 0 + NO + 0 + 1 + + + {{1, 1}, {320, 418}} + + + + + + 4 + + + + -2147483392 + {{306, 1}, {15, 403}} + + + + + _doScroller: + 0.99766359999999998 + + + + -2147483392 + {{1, 404}, {305, 15}} + + + + 1 + + _doScroller: + 0.99688480000000002 + + + {{0, 34}, {322, 420}} + + + + 133682 + + + + QSAAAEEgAABBmAAAQZgAAA + + + + 260 + {{10, 4}, {39, 28}} + + + + YES + + 67239424 + 134217728 + + + + -2033958657 + 162 + + NSImage + NSAddTemplate + + + + 400 + 75 + + + + + 260 + {{57, 4}, {39, 28}} + + + + YES + + 604110336 + 134217728 + + + + -2033958657 + 162 + + NSImage + NSRemoveTemplate + + + + 400 + 75 + + + + {322, 454} + + + + NSView + + + + {200, 100} + {150, 0} + {300, 10000} + 2 + 0.0 + 15 + + + + + ApplicationController + + + ConfigsController + + + JoystickController + + + 67239424 + 131072 + Check + + _NS:9 + 1211912703 + 2 + + NSImage + NSSwitch + + + NSSwitch + + + + 200 + 25 + + + TargetController + + + + + YES + + + terminate: + + + + 449 + + + + delegate + + + + 483 + + + + dockMenu + + + + 732 + + + + orderFrontStandardAboutPanel: + + + + 142 + + + + performMiniaturize: + + + + 37 + + + + arrangeInFront: + + + + 39 + + + + toggleContinuousSpellChecking: + + + + 222 + + + + undo: + + + + 223 + + + + copy: + + + + 224 + + + + checkSpelling: + + + + 225 + + + + paste: + + + + 226 + + + + stopSpeaking: + + + + 227 + + + + cut: + + + + 228 + + + + showGuessPanel: + + + + 230 + + + + redo: + + + + 231 + + + + selectAll: + + + + 232 + + + + startSpeaking: + + + + 233 + + + + delete: + + + + 235 + + + + performZoom: + + + + 240 + + + + performFindPanelAction: + + + + 241 + + + + centerSelectionInVisibleArea: + + + + 245 + + + + toggleGrammarChecking: + + + + 347 + + + + toggleSmartInsertDelete: + + + + 355 + + + + toggleAutomaticQuoteSubstitution: + + + + 356 + + + + toggleAutomaticLinkDetection: + + + + 357 + + + + showHelp: + + + + 360 + + + + runToolbarCustomizationPalette: + + + + 365 + + + + toggleToolbarShown: + + + + 366 + + + + hide: + + + + 367 + + + + hideOtherApplications: + + + + 368 + + + + unhideAllApplications: + + + + 370 + + + + parentWindow + + + + 454 + + + + contentView + + + + 455 + + + + toggle: + + + + 498 + + + + delegate + + + + 517 + + + + dataSource + + + + 518 + + + + outlineView + + + + 648 + + + + targetController + + + + 695 + + + + configsController + + + + 717 + + + + jsController + + + + 484 + + + + drawer + + + + 486 + + + + mainWindow + + + + 500 + + + + activeMenuItem + + + + 607 + + + + activeButton + + + + 609 + + + + toggleActivity: + + + + 610 + + + + toggleActivity: + + + + 611 + + + + targetController + + + + 712 + + + + configsController + + + + 716 + + + + dockMenuBase + + + + 726 + + + + addPressed: + + + + 515 + + + + removePressed: + + + + 516 + + + + removeButton + + + + 519 + + + + tableView + + + + 520 + + + + targetController + + + + 697 + + + + dataSource + + + + 647 + + + + delegate + + + + 696 + + + + window + + + + 684 + + + + targetController + + + + 688 + + + + keyInput + + + + 689 + + + + radioKey + + + + 690 + + + + radioNoAction + + + + 691 + + + + radioButtons + + + + 692 + + + + configsController + + + + 693 + + + + joystickController + + + + 694 + + + + title + + + + 709 + + + + configPopup + + + + 713 + + + + configChosen: + + + + 714 + + + + radioConfig + + + + 715 + + + + radioChanged: + + + + 731 + + + + + YES + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 29 + + + YES + + + + + + + + + MainMenu + + + 19 + + + YES + + + + + + 56 + + + YES + + + + + + 103 + + + YES + + + + 1 + + + 217 + + + YES + + + + + + 83 + + + YES + + + + + + 81 + + + YES + + + + + + + 205 + + + YES + + + + + + + + + + + + + + + + + + 202 + + + + + 198 + + + + + 207 + + + + + 214 + + + + + 199 + + + + + 203 + + + + + 197 + + + + + 206 + + + + + 215 + + + + + 218 + + + YES + + + + + + 216 + + + YES + + + + + + 200 + + + YES + + + + + + + + + 219 + + + + + 201 + + + + + 204 + + + + + 220 + + + YES + + + + + + + + + + 213 + + + + + 210 + + + + + 221 + + + + + 208 + + + + + 209 + + + + + 106 + + + YES + + + + 2 + + + 111 + + + + + 57 + + + YES + + + + + + + + + + + + + + + + 58 + + + + + 134 + + + + + 150 + + + + + 136 + + + 1111 + + + 144 + + + + + 129 + + + 121 + + + 143 + + + + + 236 + + + + + 131 + + + YES + + + + + + 149 + + + + + 145 + + + + + 130 + + + + + 24 + + + YES + + + + + + + + + 92 + + + + + 5 + + + + + 239 + + + + + 23 + + + + + 295 + + + YES + + + + + + 296 + + + YES + + + + + + + 297 + + + + + 298 + + + + + 211 + + + YES + + + + + + 212 + + + YES + + + + + + + 195 + + + + + 196 + + + + + 346 + + + + + 348 + + + YES + + + + + + 349 + + + YES + + + + + + + + 350 + + + + + 351 + + + + + 354 + + + + + 420 + + + + + 450 + + + YES + + + + + + + 451 + + + YES + + + + + + Drawer Content View + + + 452 + + + + + 453 + + + YES + + + + + + 456 + + + YES + + + + + + + + 457 + + + + + 458 + + + + + 459 + + + YES + + + + + + 461 + + + YES + + + + + + 464 + + + + + 482 + + + ApplicationController + + + 487 + + + YES + + + + + + + + + + 490 + + + + + 492 + + + + + 493 + + + + + 495 + + + + + 507 + + + YES + + + + + + 508 + + + + + 511 + + + YES + + + + + + 512 + + + + + 514 + + + ConfigsController + + + 479 + + + JoystickController + + + 606 + + + + + 608 + + + + + 652 + + + YES + + + + + + + 653 + + + YES + + + + + + 634 + + + YES + + + + + + + + 637 + + + YES + + + + + + 636 + + + + + 635 + + + + + 639 + + + YES + + + + + + 642 + + + + + 654 + + + YES + + + + + + + + + + + 686 + + + + + 680 + + + YES + + + + + + + + 681 + + + + + 682 + + + + + 683 + + + + + 656 + + + YES + + + + + + + + + + + + 657 + + + + + 658 + + + + + 699 + + + + + 700 + + + YES + + + + + + 701 + + + YES + + + + + + 702 + + + YES + + + + + 706 + + + YES + + + + + + 707 + + + + + 708 + + + + + 723 + + + + + 733 + + + + + 734 + + + + + 735 + + + + + 659 + + + + + 736 + + + + + 744 + + + YES + + + + + + 745 + + + + + + + YES + + YES + -1.IBPluginDependency + -2.IBPluginDependency + -3.IBPluginDependency + 103.IBPluginDependency + 106.IBPluginDependency + 111.IBPluginDependency + 129.IBPluginDependency + 130.IBPluginDependency + 131.IBPluginDependency + 134.IBPluginDependency + 136.IBPluginDependency + 143.IBPluginDependency + 144.IBPluginDependency + 145.IBPluginDependency + 149.IBPluginDependency + 150.IBPluginDependency + 19.IBPluginDependency + 195.IBPluginDependency + 196.IBPluginDependency + 197.IBPluginDependency + 198.IBPluginDependency + 199.IBPluginDependency + 200.IBPluginDependency + 201.IBPluginDependency + 202.IBPluginDependency + 203.IBPluginDependency + 204.IBPluginDependency + 205.IBPluginDependency + 206.IBPluginDependency + 207.IBPluginDependency + 208.IBPluginDependency + 209.IBPluginDependency + 210.IBPluginDependency + 211.IBPluginDependency + 212.IBPluginDependency + 213.IBPluginDependency + 214.IBPluginDependency + 215.IBPluginDependency + 216.IBPluginDependency + 217.IBPluginDependency + 218.IBPluginDependency + 219.IBPluginDependency + 220.IBPluginDependency + 221.IBPluginDependency + 23.IBPluginDependency + 236.IBPluginDependency + 239.IBPluginDependency + 24.IBPluginDependency + 29.IBPluginDependency + 295.IBPluginDependency + 296.IBPluginDependency + 297.IBPluginDependency + 298.IBPluginDependency + 346.IBPluginDependency + 348.IBPluginDependency + 349.IBPluginDependency + 350.IBPluginDependency + 351.IBPluginDependency + 354.IBPluginDependency + 420.IBPluginDependency + 450.IBPluginDependency + 450.IBWindowTemplateEditedContentRect + 450.NSWindowTemplate.visibleAtLaunch + 451.IBPluginDependency + 452.IBPluginDependency + 453.IBPluginDependency + 456.IBPluginDependency + 457.IBPluginDependency + 458.IBPluginDependency + 459.IBPluginDependency + 461.IBPluginDependency + 464.IBPluginDependency + 479.IBPluginDependency + 482.IBPluginDependency + 487.IBPluginDependency + 490.IBPluginDependency + 492.IBPluginDependency + 493.IBPluginDependency + 495.IBPluginDependency + 5.IBPluginDependency + 507.IBPluginDependency + 508.IBPluginDependency + 511.IBPluginDependency + 512.IBPluginDependency + 514.IBPluginDependency + 56.IBPluginDependency + 57.IBPluginDependency + 58.IBPluginDependency + 606.IBPluginDependency + 608.IBPluginDependency + 634.IBPluginDependency + 635.IBPluginDependency + 636.IBPluginDependency + 637.IBPluginDependency + 639.IBPluginDependency + 642.IBPluginDependency + 652.IBPluginDependency + 653.IBPluginDependency + 654.IBPluginDependency + 656.IBPluginDependency + 657.IBPluginDependency + 658.IBPluginDependency + 659.IBPluginDependency + 680.IBPluginDependency + 681.IBPluginDependency + 682.IBPluginDependency + 683.CustomClassName + 683.IBPluginDependency + 686.IBPluginDependency + 699.IBPluginDependency + 700.IBPluginDependency + 701.IBPluginDependency + 702.IBPluginDependency + 706.IBPluginDependency + 707.IBPluginDependency + 708.IBPluginDependency + 723.IBPluginDependency + 733.IBPluginDependency + 734.IBPluginDependency + 735.IBPluginDependency + 736.IBPluginDependency + 744.IBPluginDependency + 745.IBNSSegmentedControlInspectorSelectedSegmentMetadataKey + 745.IBPluginDependency + 81.IBPluginDependency + 83.IBPluginDependency + 92.IBPluginDependency + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{114, 276}, {770, 487}} + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + KeyInputTextView + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + YES + + + + + + YES + + + + + 745 + + + + YES + + ApplicationController + NSObject + + toggleActivity: + id + + + toggleActivity: + + toggleActivity: + id + + + + YES + + YES + activeButton + activeMenuItem + configsController + dockMenuBase + drawer + jsController + mainWindow + targetController + + + YES + NSToolbarItem + NSMenuItem + ConfigsController + NSMenu + NSDrawer + JoystickController + NSWindow + TargetController + + + + YES + + YES + activeButton + activeMenuItem + configsController + dockMenuBase + drawer + jsController + mainWindow + targetController + + + YES + + activeButton + NSToolbarItem + + + activeMenuItem + NSMenuItem + + + configsController + ConfigsController + + + dockMenuBase + NSMenu + + + drawer + NSDrawer + + + jsController + JoystickController + + + mainWindow + NSWindow + + + targetController + TargetController + + + + + IBProjectSource + ./Classes/ApplicationController.h + + + + ConfigsController + NSObject + + YES + + YES + addPressed: + removePressed: + + + YES + id + id + + + + YES + + YES + addPressed: + removePressed: + + + YES + + addPressed: + id + + + removePressed: + id + + + + + YES + + YES + removeButton + tableView + targetController + + + YES + NSButton + NSTableView + TargetController + + + + YES + + YES + removeButton + tableView + targetController + + + YES + + removeButton + NSButton + + + tableView + NSTableView + + + targetController + TargetController + + + + + IBProjectSource + ./Classes/ConfigsController.h + + + + JoystickController + NSObject + + YES + + YES + configsController + outlineView + targetController + + + YES + ConfigsController + NSOutlineView + TargetController + + + + YES + + YES + configsController + outlineView + targetController + + + YES + + configsController + ConfigsController + + + outlineView + NSOutlineView + + + targetController + TargetController + + + + + IBProjectSource + ./Classes/JoystickController.h + + + + KeyInputTextView + NSTextView + + YES + + YES + targetController + window + + + YES + TargetController + NSWindow + + + + YES + + YES + targetController + window + + + YES + + targetController + TargetController + + + window + NSWindow + + + + + IBProjectSource + ./Classes/KeyInputTextView.h + + + + TargetController + NSObject + + YES + + YES + configChosen: + radioChanged: + + + YES + id + id + + + + YES + + YES + configChosen: + radioChanged: + + + YES + + configChosen: + id + + + radioChanged: + id + + + + + YES + + YES + configPopup + configsController + joystickController + keyInput + mouseBtnRadio + radioButtons + radioConfig + radioKey + radioNoAction + title + + + YES + NSPopUpButton + ConfigsController + JoystickController + KeyInputTextView + NSMatrix + NSMatrix + NSButtonCell + NSButtonCell + NSButtonCell + NSTextField + + + + YES + + YES + configPopup + configsController + joystickController + keyInput + mouseBtnRadio + radioButtons + radioConfig + radioKey + radioNoAction + title + + + YES + + configPopup + NSPopUpButton + + + configsController + ConfigsController + + + joystickController + JoystickController + + + keyInput + KeyInputTextView + + + mouseBtnRadio + NSMatrix + + + radioButtons + NSMatrix + + + radioConfig + NSButtonCell + + + radioKey + NSButtonCell + + + radioNoAction + NSButtonCell + + + title + NSTextField + + + + + IBProjectSource + ./Classes/TargetController.h + + + + + 0 + IBCocoaFramework + + com.apple.InterfaceBuilder.CocoaPlugin.macosx + + + + com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 + + + YES + 3 + + YES + + YES + NSAddTemplate + NSGoRightTemplate + NSMenuCheckmark + NSMenuMixedState + NSMultipleDocuments + NSRemoveTemplate + NSSwitch + + + YES + {8, 8} + {9, 9} + {11, 11} + {10, 3} + {32, 32} + {8, 8} + {15, 15} + + + + diff --git a/Enjoy_Prefix.pch b/Enjoy_Prefix.pch new file mode 100644 index 0000000..b915d20 --- /dev/null +++ b/Enjoy_Prefix.pch @@ -0,0 +1,27 @@ +// +// Prefix header for all source files of the 'Enjoy' target in the 'Enjoy' project +// + +#ifdef __OBJC__ + #import +#endif + +#import + +#import "ApplicationController.h" +#import "Config.h" +#import "ConfigsController.h" +#import "Joystick.h" +#import "JoystickController.h" +#import "JSAction.h" +#import "JSActionAnalog.h" +#import "JSActionButton.h" +#import "JSActionHat.h" +#import "KeyInputTextView.h" +#import "SubAction.h" +#import "Target.h" +#import "TargetConfig.h" +#import "TargetController.h" +#import "TargetKeyboard.h" +#import "TargetMouseMove.h" +#import "TargetMouseBtn.h" \ No newline at end of file diff --git a/Info.plist b/Info.plist new file mode 100644 index 0000000..1d8efb9 --- /dev/null +++ b/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + icon.icns + CFBundleIdentifier + net.tunah.${PRODUCT_NAME:identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + NSHumanReadableCopyright + Copyright (c) 2009 Sam McCall + + diff --git a/JSAction.h b/JSAction.h new file mode 100644 index 0000000..50e37e2 --- /dev/null +++ b/JSAction.h @@ -0,0 +1,33 @@ +// +// JSAction.h +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +#import + +@interface JSAction : NSObject { + int usage, index; + void* cookie; + NSArray* subActions; + id base; + NSString* name; +} + +@property(readwrite) int usage; +@property(readwrite) void* cookie; +@property(readonly) int index; +@property(readonly) NSArray* subActions; +@property(readwrite, retain) id base; +@property(readonly) NSString* name; +@property(readonly) BOOL active; + +-(void) notifyEvent: (IOHIDValueRef) value; +-(NSString*) stringify; +-(NSArray*) subActions; +-(id) findSubActionForValue: (IOHIDValueRef) value; + +@end diff --git a/JSAction.m b/JSAction.m new file mode 100644 index 0000000..a0fab80 --- /dev/null +++ b/JSAction.m @@ -0,0 +1,27 @@ +// +// JSAction.m +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// + +@implementation JSAction + +@synthesize usage, cookie, index, subActions, base, name; + +-(id) findSubActionForValue: (IOHIDValueRef) value { + return NULL; +} + +-(NSString*) stringify { + return [[NSString alloc] initWithFormat: @"%@~%d",[base stringify],(int)cookie]; +} +-(void) notifyEvent: (IOHIDValueRef) value { + [self doesNotRecognizeSelector:_cmd]; +} +-(BOOL) active { + [self doesNotRecognizeSelector:_cmd]; + return NO; +} + +@end diff --git a/JSActionAnalog.h b/JSActionAnalog.h new file mode 100644 index 0000000..5087582 --- /dev/null +++ b/JSActionAnalog.h @@ -0,0 +1,19 @@ +// +// JSActionAnalog.h +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class JSAction; + +@interface JSActionAnalog : JSAction { + double offset, scale; +} + +@property(readwrite) double offset; +@property(readwrite) double scale; + +@end diff --git a/JSActionAnalog.m b/JSActionAnalog.m new file mode 100644 index 0000000..a1c57e8 --- /dev/null +++ b/JSActionAnalog.m @@ -0,0 +1,56 @@ +// +// JSActionAnalog.m +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// + +@implementation JSActionAnalog + +- (id) initWithIndex: (int)newIndex { + if(self = [super init]) { + subActions = [NSArray arrayWithObjects: + [[SubAction alloc] initWithIndex: 0 name: @"Low" base: self], + [[SubAction alloc] initWithIndex: 1 name: @"High" base: self], + [[SubAction alloc] initWithIndex: 2 name: @"Analog" base: self], + nil + ]; + [subActions retain]; + index = newIndex; + name = [[NSString alloc] initWithFormat: @"Axis %d", (index+1)]; + } + return self; +} + +-(id) findSubActionForValue: (IOHIDValueRef) value { + if ([[subActions objectAtIndex: 2] active]) { + return [subActions objectAtIndex: 2]; // TODO? + } + + //Target* target = [[base->configsController currentConfig] getTargetForAction: [subActions objectAtIndex: 0]]; + + int raw = IOHIDValueGetIntegerValue(value); + double parsed = offset + scale * raw; + + if(parsed < -0.3) // fixed?! + return [subActions objectAtIndex: 0]; + else if(parsed > 0.3) + return [subActions objectAtIndex: 1]; + return NULL; +} + +-(void) notifyEvent: (IOHIDValueRef) value { + // Analog action is always active + [[subActions objectAtIndex: 2] setActive: true]; + + int raw = IOHIDValueGetIntegerValue(value); + double parsed = offset + scale * raw; + + [[subActions objectAtIndex: 0] setActive: (parsed < -0.3)]; + [[subActions objectAtIndex: 1] setActive: (parsed > 0.3)]; +} + +@synthesize offset, scale; + + +@end diff --git a/JSActionButton.h b/JSActionButton.h new file mode 100644 index 0000000..4720b73 --- /dev/null +++ b/JSActionButton.h @@ -0,0 +1,20 @@ +// +// JSActionButton.h +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class JSAction; + +@interface JSActionButton : JSAction { + int max; + BOOL active; +} + +-(id)initWithIndex: (int)newIndex andName: (NSString*)newName; +@property(readwrite) int max; + +@end diff --git a/JSActionButton.m b/JSActionButton.m new file mode 100644 index 0000000..98a186a --- /dev/null +++ b/JSActionButton.m @@ -0,0 +1,31 @@ +// +// JSActionButton.m +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// + +@implementation JSActionButton + +@synthesize max, active; + +-(id)initWithIndex: (int)newIndex andName: (NSString *)newName { + if(self= [ super init]) { + subActions = NULL; + index = newIndex; + name = [[NSString alloc] initWithFormat: @"Button %d %@", (index+1), newName]; + } + return self; +} + +-(id) findSubActionForValue: (IOHIDValueRef) val { + if(IOHIDValueGetIntegerValue(val) == max) + return self; + return NULL; +} + +-(void) notifyEvent: (IOHIDValueRef) value { + active = IOHIDValueGetIntegerValue(value) == max; +} + +@end diff --git a/JSActionHat.h b/JSActionHat.h new file mode 100644 index 0000000..bab893f --- /dev/null +++ b/JSActionHat.h @@ -0,0 +1,16 @@ +// +// JSActionHat.h +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class JSAction; + +@interface JSActionHat : JSAction { + +} + +@end diff --git a/JSActionHat.m b/JSActionHat.m new file mode 100644 index 0000000..1ea30af --- /dev/null +++ b/JSActionHat.m @@ -0,0 +1,94 @@ +// +// JSActionHat.m +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// + +static BOOL active_eightway[36] = { +NO, NO, NO, NO , // center +YES, NO, NO, NO , // N +YES, NO, NO, YES, // NE +NO, NO, NO, YES, // E +NO, YES, NO, YES, // SE +NO, YES, NO, NO , // S +NO, YES, YES, NO , // SW +NO, NO, YES, NO , // W +YES, NO, YES, NO , // NW +}; +static BOOL active_fourway[20] = { +NO, NO, NO, NO , // center +YES, NO, NO, NO , // N +NO, NO, NO, YES, // E +NO, YES, NO, NO , // S +NO, NO, YES, NO , // W +}; + +@implementation JSActionHat + +- (id) init { + if(self = [super init]) { + subActions = [NSArray arrayWithObjects: + [[SubAction alloc] initWithIndex: 0 name: @"Up" base: self], + [[SubAction alloc] initWithIndex: 1 name: @"Down" base: self], + [[SubAction alloc] initWithIndex: 2 name: @"Left" base: self], + [[SubAction alloc] initWithIndex: 3 name: @"Right" base: self], + nil + ]; + [subActions retain]; + name = @"Hat switch"; + } + return self; +} + +-(id) findSubActionForValue: (IOHIDValueRef) value { + int parsed = IOHIDValueGetIntegerValue(value); + if(IOHIDElementGetLogicalMax(IOHIDValueGetElement(value)) == 7) { + // 8-way + switch(parsed) { + case 0: return [subActions objectAtIndex: 0]; + case 4: return [subActions objectAtIndex: 1]; + case 6: return [subActions objectAtIndex: 2]; + case 2: return [subActions objectAtIndex: 3]; + } + } else if(IOHIDElementGetLogicalMax(IOHIDValueGetElement(value)) == 8) { + // 8-way + switch(parsed) { + case 1: return [subActions objectAtIndex: 0]; + case 5: return [subActions objectAtIndex: 1]; + case 7: return [subActions objectAtIndex: 2]; + case 3: return [subActions objectAtIndex: 3]; + } + } else if(IOHIDElementGetLogicalMax(IOHIDValueGetElement(value)) == 3) { + // 4-way + switch(parsed) { + case 0: return [subActions objectAtIndex: 0]; + case 2: return [subActions objectAtIndex: 1]; + case 3: return [subActions objectAtIndex: 2]; + case 1: return [subActions objectAtIndex: 3]; + } + } else if(IOHIDElementGetLogicalMax(IOHIDValueGetElement(value)) == 4) { + // 4-way + switch(parsed) { + case 1: return [subActions objectAtIndex: 0]; + case 3: return [subActions objectAtIndex: 1]; + case 4: return [subActions objectAtIndex: 2]; + case 2: return [subActions objectAtIndex: 3]; + } + } + return NULL; +} + +-(void) notifyEvent: (IOHIDValueRef) value { + int parsed = IOHIDValueGetIntegerValue(value); + int size = IOHIDElementGetLogicalMax(IOHIDValueGetElement(value)); + if(size == 7 || size == 3) { + parsed++; + size++; + } + BOOL* activeSubactions = (size == 8) ? active_eightway : active_fourway; + for(int i=0; i<4; i++) + [[subActions objectAtIndex: i] setActive: activeSubactions[parsed * 4 + i]]; +} + +@end diff --git a/Joystick.h b/Joystick.h new file mode 100644 index 0000000..587ad3f --- /dev/null +++ b/Joystick.h @@ -0,0 +1,36 @@ +// +// Joystick.h +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class JSAction; + +@interface Joystick : NSObject { + int vendorId; + int productId; + int index; + NSString* productName; + IOHIDDeviceRef device; + NSMutableArray* children; + NSString* name; +} + +@property(readwrite) int vendorId; +@property(readwrite) int productId; +@property(readwrite) int index; +@property(readwrite, copy) NSString* productName; +@property(readwrite) IOHIDDeviceRef device; +@property(readonly) NSArray* children; +@property(readonly) NSString* name; + +-(void) populateActions; +-(void) invalidate; +-(id) handlerForEvent: (IOHIDValueRef) value; +-(id)initWithDevice: (IOHIDDeviceRef) newDevice; +-(JSAction*) actionForEvent: (IOHIDValueRef) value; + +@end diff --git a/Joystick.m b/Joystick.m new file mode 100644 index 0000000..8fa6791 --- /dev/null +++ b/Joystick.m @@ -0,0 +1,116 @@ +// +// Joystick.m +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// + +@implementation Joystick + + +@synthesize vendorId, productId, productName, name, index, device, children; + +-(id)initWithDevice: (IOHIDDeviceRef) newDevice { + if(self=[super init]) { + children = [[NSMutableArray alloc]init]; + + device = newDevice; + productName = (NSString*)IOHIDDeviceGetProperty( device, CFSTR(kIOHIDProductKey) ); + vendorId = [(NSNumber*)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)) intValue]; + productId = [(NSNumber*)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)) intValue]; + + name = productName; + } + return self; +} + +-(void) setIndex: (int) newIndex { + index = newIndex; + name = [[NSString alloc] initWithFormat: @"%@ #%d", productName, (index+1)]; +} +-(int) index { + return index; +} + +-(void) invalidate { + IOHIDDeviceClose(device, kIOHIDOptionsTypeNone); + NSLog(@"Removed a device: %@", [self name]); +} + +-(id) base { + return NULL; +} + +-(void) populateActions { + NSArray* elements = (NSArray*)IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone); + + int buttons = 0; + int axes = 0; + + for(int i=0; i<[elements count]; i++) { + IOHIDElementRef element = (IOHIDElementRef)[elements objectAtIndex: i]; + int type = IOHIDElementGetType(element); + int usage = IOHIDElementGetUsage(element); + int usagePage = IOHIDElementGetUsagePage(element); + int max = IOHIDElementGetPhysicalMax(element); + int min = IOHIDElementGetPhysicalMin(element); + CFStringRef elName = IOHIDElementGetName(element); + +// if(usagePage != 1 || usagePage == 9) { +// NSLog(@"Skipping usage page %x usage %x", usagePage, usage); +// continue; +// } + + JSAction* action = NULL; + + if(!(type == kIOHIDElementTypeInput_Misc || type == kIOHIDElementTypeInput_Axis || + type == kIOHIDElementTypeInput_Button)) { + + continue; + } + + if((max - min == 1) || usagePage == kHIDPage_Button || type == kIOHIDElementTypeInput_Button) { + action = [[JSActionButton alloc] initWithIndex: buttons++ andName: (NSString *)elName]; + [(JSActionButton*)action setMax: max]; + } else if(usage == 0x39) + action = [[JSActionHat alloc] init]; + else { + if(usage >= 0x30 && usage < 0x36) { + action = [[JSActionAnalog alloc] initWithIndex: axes++]; + [(JSActionAnalog*)action setOffset: (double)-1.0]; + [(JSActionAnalog*)action setScale: (double)2.0/(max - min)]; + } else + continue; + } + + [action setBase: self]; + [action setUsage: usage]; + [action setCookie: IOHIDElementGetCookie(element)]; + [children addObject:action]; + } +} + +-(NSString*) stringify { + return [[NSString alloc] initWithFormat: @"%d~%d~%d", vendorId, productId, index]; +} + +- (JSAction*) findActionByCookie: (void*) cookie { + for(int i=0; i<[children count]; i++) + if([[children objectAtIndex:i]cookie] == cookie) + return (JSAction*)[children objectAtIndex:i]; + return NULL; +} + +-(id) handlerForEvent: (IOHIDValueRef) value { + JSAction* mainAction = [self actionForEvent: value]; + if(!mainAction) + return NULL; + return [mainAction findSubActionForValue: value]; +} +-(JSAction*) actionForEvent: (IOHIDValueRef) value { + IOHIDElementRef elt = IOHIDValueGetElement(value); + void* cookie = IOHIDElementGetCookie(elt); + return [self findActionByCookie: cookie]; +} + +@end diff --git a/JoystickController.h b/JoystickController.h new file mode 100644 index 0000000..4c878f2 --- /dev/null +++ b/JoystickController.h @@ -0,0 +1,32 @@ +// +// JoystickController.h +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +#import +@class Joystick; +@class ConfigsController; + +@class TargetController; + +@interface JoystickController : NSObject { + NSMutableArray *joysticks; + IOHIDManagerRef hidManager; + IBOutlet NSOutlineView* outlineView; + IBOutlet TargetController* targetController; + IBOutlet ConfigsController* configsController; + id selectedAction; + BOOL programmaticallySelecting; +} + +-(void) setup; +-(Joystick*) findJoystickByRef: (IOHIDDeviceRef) device; + +@property(readonly) id selectedAction; +@property(readonly) NSMutableArray *joysticks; + +@end diff --git a/JoystickController.m b/JoystickController.m new file mode 100644 index 0000000..b510848 --- /dev/null +++ b/JoystickController.m @@ -0,0 +1,212 @@ +// +// JoystickController.m +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// + +@implementation JoystickController + +@synthesize joysticks, selectedAction; + +-(id) init { + if(self=[super init]) { + joysticks = [[NSMutableArray alloc]init]; + programmaticallySelecting = NO; + } + return self; +} + +-(void) finalize { + for(int i=0; i<[joysticks count]; i++) { + [[joysticks objectAtIndex:i] invalidate]; + } + IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone); + CFRelease(hidManager); + [super finalize]; +} + +static NSMutableDictionary* create_criterion( UInt32 inUsagePage, UInt32 inUsage ) +{ + NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; + [dict setObject: [NSNumber numberWithInt: inUsagePage] forKey: (NSString*)CFSTR(kIOHIDDeviceUsagePageKey)]; + [dict setObject: [NSNumber numberWithInt: inUsage] forKey: (NSString*)CFSTR(kIOHIDDeviceUsageKey)]; + return dict; +} + +-(void) expandRecursive: (id) handler { + if([handler base]) + [self expandRecursive: [handler base]]; + [outlineView expandItem: handler]; +} + +void input_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) { + JoystickController* self = (JoystickController*)inContext; + IOHIDDeviceRef device = IOHIDQueueGetDevice((IOHIDQueueRef) inSender); + + Joystick* js = [self findJoystickByRef: device]; + if([[[NSApplication sharedApplication] delegate] active]) { + // for reals + JSAction* mainAction = [js actionForEvent: value]; + if(!mainAction) + return; + + [mainAction notifyEvent: value]; + NSArray* subactions = [mainAction subActions]; + if(!subactions) + subactions = [NSArray arrayWithObject:mainAction]; + for(id subaction in subactions) { + Target* target = [[self->configsController currentConfig] getTargetForAction:subaction]; + if(!target) + continue; + /* target application? doesn't seem to be any need since we are only active when it's in front */ + /* might be required for some strange actions */ + [target setRunning: [subaction active]]; + [target setInputValue: IOHIDValueGetIntegerValue(value)]; + } + } else if([[NSApplication sharedApplication] isActive] && [[[NSApplication sharedApplication]mainWindow]isVisible]) { + // joysticks not active, use it to select stuff + id handler = [js handlerForEvent: value]; + if(!handler) + return; + + [self expandRecursive: handler]; + self->programmaticallySelecting = YES; + [self->outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: [self->outlineView rowForItem: handler]] byExtendingSelection: NO]; + } +} + +int findAvailableIndex(id list, Joystick* js) { + BOOL available; + Joystick* js2; + for(int index=0;;index++) { + available = YES; + for(int i=0; i<[list count]; i++) { + js2 = [list objectAtIndex: i]; + if([js2 vendorId] == [js vendorId] && [js2 productId] == [js productId] && [js index] == index) { + available = NO; + break; + } + } + if(available) + return index; + } +} + +void add_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) { + JoystickController* self = (JoystickController*)inContext; + + IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone); + IOHIDDeviceRegisterInputValueCallback(device, input_callback, (void*) self); + + Joystick *js = [[Joystick alloc] initWithDevice: device]; + [js setIndex: findAvailableIndex([self joysticks], js)]; + + [js populateActions]; + + [[self joysticks] addObject: js]; + [self->outlineView reloadData]; +} + +-(Joystick*) findJoystickByRef: (IOHIDDeviceRef) device { + for(int i=0; i<[joysticks count]; i++) + if([[joysticks objectAtIndex:i] device] == device) + return [joysticks objectAtIndex:i]; + return NULL; +} + +void remove_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) { + JoystickController* self = (JoystickController*)inContext; + + Joystick* match = [self findJoystickByRef: device]; + if(!match) + return; + + [[self joysticks] removeObject: match]; + + [match invalidate]; + [self->outlineView reloadData]; +} + +-(void) setup { + hidManager = IOHIDManagerCreate( kCFAllocatorDefault, kIOHIDOptionsTypeNone); + NSArray *criteria = [NSArray arrayWithObjects: + create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick), + create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad), + create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController), + create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard), + nil]; + + IOHIDManagerSetDeviceMatchingMultiple(hidManager, (CFArrayRef)criteria); + + IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode ); + IOReturn tIOReturn = IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone ); + (void)tIOReturn; + + IOHIDManagerRegisterDeviceMatchingCallback( hidManager, add_callback, (void*)self ); + IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (void*) self); +// IOHIDManagerRegisterInputValueCallback(hidManager, input_callback, (void*)self); +// register individually so we can find the device more easily +} + +-(id) determineSelectedAction { + id item = [outlineView itemAtRow: [outlineView selectedRow]]; + if(!item) + return NULL; + if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL) + return NULL; + if([item isKindOfClass: [Joystick class]]) + return NULL; + return item; +} + +/* outline view */ + +- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { + if(item == nil) + return [joysticks count]; + if([item isKindOfClass: [Joystick class]]) + return [[item children] count]; + if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL) + return [[item subActions] count]; + return 0; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { + if(item == nil) + return YES; + if([item isKindOfClass: [Joystick class]]) + return YES; + if([item isKindOfClass: [JSAction class]]) + return [item subActions]==NULL ? NO : YES; + return NO; +} + +- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item { + if(item == nil) + return [joysticks objectAtIndex: index]; + + if([item isKindOfClass: [Joystick class]]) + return [[item children] objectAtIndex: index]; + + if([item isKindOfClass: [JSAction class]]) + return [[item subActions] objectAtIndex:index]; + + return NULL; +} +- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { + if(item == nil) + return @"root"; + return [item name]; +} + +- (void)outlineViewSelectionDidChange: (NSNotification*) notification { + [targetController reset]; + selectedAction = [self determineSelectedAction]; + [targetController load]; + if(programmaticallySelecting) + [targetController focusKey]; + programmaticallySelecting = NO; +} + +@end diff --git a/KeyInputTextView.h b/KeyInputTextView.h new file mode 100644 index 0000000..69d8f10 --- /dev/null +++ b/KeyInputTextView.h @@ -0,0 +1,28 @@ +// +// KeyInputTextField.h +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class TargetController; + +@interface KeyInputTextView: NSTextView { + IBOutlet NSWindow* window; + IBOutlet TargetController* targetController; + BOOL hasKey; + int vk; + NSString* descr; + BOOL enabled; +} + +@property(readonly) BOOL hasKey; +@property(readwrite) int vk; +@property(readonly) NSString* descr; +@property(readwrite) BOOL enabled; + +-(void) clear; + +@end diff --git a/KeyInputTextView.m b/KeyInputTextView.m new file mode 100644 index 0000000..9b6bc37 --- /dev/null +++ b/KeyInputTextView.m @@ -0,0 +1,209 @@ +// +// KeyInputTextField.m +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// + +@implementation KeyInputTextView + +@synthesize descr, hasKey; + +-(id) init { + if(self = [super init]) { + setEnabled: NO; + } + return self; +} + +-(void) clear { + [self setString: [NSString string]]; + vk = -1; + hasKey = NO; + descr = NULL; +} + +-(NSString*) stringForKeyCode: (int) keycode { + switch(keycode) { + case 0x7a : return @"F1"; + case 0x78 : return @"F2"; + case 0x63 : return @"F3"; + case 0x76 : return @"F4"; + case 0x60 : return @"F5"; + case 0x61 : return @"F6"; + case 0x62 : return @"F7"; + case 0x64 : return @"F8"; + case 0x65 : return @"F9"; + case 0x6d : return @"F10"; + case 0x67 : return @"F11"; + case 0x6f : return @"F12"; + case 0x69 : return @"F13"; + case 0x6b : return @"F14"; + case 0x71 : return @"F15"; + case 0x6a : return @"F16"; + case 0x40 : return @"F17"; + case 0x4f : return @"F18"; + case 0x50 : return @"F19"; + + case 0x35 : return @"Esc"; + case 0x32 : return @"`"; + + case 0x12 : return @"1"; + case 0x13 : return @"2"; + case 0x14 : return @"3"; + case 0x15 : return @"4"; + case 0x17 : return @"5"; + case 0x16 : return @"6"; + case 0x1a : return @"7"; + case 0x1c : return @"8"; + case 0x19 : return @"9"; + case 0x1d : return @"0"; + case 0x1b : return @"-"; + case 0x18 : return @"="; + + case 0x3f : return @"Fn"; + case 0x39 : return @"Caps Lock"; + case 0x38 : return @"Left Shift"; + case 0x3b : return @"Left Control"; + case 0x3a : return @"Left Option"; + case 0x37 : return @"Left Command"; + case 0x36 : return @"Right Command"; + case 0x3d : return @"Right Option"; + case 0x3e : return @"Right Control"; + case 0x3c : return @"Right Shift"; + + case 0x73 : return @"Home"; + case 0x74 : return @"Page Up"; + case 0x75 : return @"Delete"; + case 0x77 : return @"End"; + case 0x79 : return @"Page Down"; + + case 0x30 : return @"Tab"; + case 0x33 : return @"Backspace"; + case 0x24 : return @"Return"; + case 0x31 : return @"Space"; + + case 0x0c : return @"Q"; + case 0x0d : return @"W"; + case 0x0e : return @"E"; + case 0x0f : return @"R"; + case 0x11 : return @"T"; + case 0x10 : return @"Y"; + case 0x20 : return @"U"; + case 0x22 : return @"I"; + case 0x1f : return @"O"; + case 0x23 : return @"P"; + case 0x21 : return @"["; + case 0x1e : return @"]"; + case 0x2a : return @"\\"; + case 0x00 : return @"A"; + case 0x01 : return @"S"; + case 0x02 : return @"D"; + case 0x03 : return @"F"; + case 0x05 : return @"G"; + case 0x04 : return @"H"; + case 0x26 : return @"J"; + case 0x28 : return @"K"; + case 0x25 : return @"L"; + case 0x29 : return @";"; + case 0x27 : return @"'"; + case 0x06 : return @"Z"; + case 0x07 : return @"X"; + case 0x08 : return @"C"; + case 0x09 : return @"V"; + case 0x0b : return @"B"; + case 0x2d : return @"N"; + case 0x2e : return @"M"; + case 0x2b : return @","; + case 0x2f : return @"."; + case 0x2c : return @"/"; + + case 0x47 : return @"Clear"; + case 0x51 : return @"Keypad ="; + case 0x4b : return @"Keypad /"; + case 0x43 : return @"Keypad *"; + case 0x59 : return @"Keypad 7"; + case 0x5b : return @"Keypad 8"; + case 0x5c : return @"Keypad 9"; + case 0x4e : return @"Keypad -"; + case 0x56 : return @"Keypad 4"; + case 0x57 : return @"Keypad 5"; + case 0x58 : return @"Keypad 6"; + case 0x45 : return @"Keypad +"; + case 0x53 : return @"Keypad 1"; + case 0x54 : return @"Keypad 2"; + case 0x55 : return @"Keypad 3"; + case 0x52 : return @"Keypad 0"; + case 0x41 : return @"Keypad ."; + case 0x4c : return @"Enter"; + + case 0x7e : return @"Up"; + case 0x7d : return @"Down"; + case 0x7b : return @"Left"; + case 0x7c : return @"Right"; + } + return [[NSString alloc] initWithFormat: @"Key 0x%x",keycode]; +} + +-(BOOL) acceptsFirstResponder { + return enabled; +} + +-(BOOL) becomeFirstResponder { + [self setBackgroundColor: [NSColor selectedTextBackgroundColor]]; + return YES; +} + +-(BOOL) resignFirstResponder { + [self setBackgroundColor: [NSColor textBackgroundColor]]; + return YES; +} + +-(void) pressed:(int) keycode { + [self setVk: keycode]; + [[self window] makeFirstResponder: nil]; + [targetController keyChanged]; +} + +-(void) setVk: (int) key { + vk=key; + hasKey = YES; + descr = [self stringForKeyCode: key]; + [self setString: descr]; +} +-(int) vk { + return vk; +} + +- (void)keyDown:(NSEvent *)evt { + if([evt isARepeat]) + return; + [self pressed: [evt keyCode]]; +} + +-(void) flagsChanged:(NSEvent*)evt { + // XXX sometimes it's key up + [self pressed: [evt keyCode]]; +} + +-(void) setEnabled: (BOOL) newEnabled { + enabled = newEnabled; + if(!newEnabled && [window firstResponder] == self) + [window makeFirstResponder: NULL]; + + + if(enabled) { + if([window firstResponder] == self) + [self setBackgroundColor: [NSColor selectedTextBackgroundColor]]; + else + [self setBackgroundColor: [NSColor textBackgroundColor]]; + } else { + [self setBackgroundColor: [NSColor textBackgroundColor]]; + } +} +-(BOOL) enabled { + return enabled; +} + + +@end diff --git a/SubAction.h b/SubAction.h new file mode 100644 index 0000000..48969ca --- /dev/null +++ b/SubAction.h @@ -0,0 +1,26 @@ +// +// SubAction.h +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class JSAction; + +@interface SubAction : NSObject { + JSAction *base; + NSString *name; + int index; + BOOL active; +} + +-(id) initWithIndex:(int)newIndex name: (NSString*)newName base: (JSAction*)newBase; + +@property(readwrite, assign) JSAction* base; +@property(readwrite, copy) NSString* name; +@property(readwrite) int index; +@property(readwrite) BOOL active; + +@end diff --git a/SubAction.m b/SubAction.m new file mode 100644 index 0000000..cfca78b --- /dev/null +++ b/SubAction.m @@ -0,0 +1,25 @@ +// +// SubAction.m +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// + +@implementation SubAction + +@synthesize base, name, index, active; + +-(id) initWithIndex:(int)newIndex name: (NSString*)newName base: (JSAction*)newBase { + if(self = [super init]) { + [self setName: newName]; + [self setBase: newBase]; + [self setIndex: newIndex]; + } + return self; +} + +-(NSString*) stringify { + return [[NSString alloc] initWithFormat: @"%@~%d", [base stringify], index]; +} + +@end diff --git a/Target.h b/Target.h new file mode 100644 index 0000000..215245f --- /dev/null +++ b/Target.h @@ -0,0 +1,23 @@ +// +// Target.h +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import + +@interface Target : NSObject { + BOOL running; + int inputValue; +} + +@property(readwrite) BOOL running; +@property(readwrite) int inputValue; +-(void) trigger; +-(void) untrigger; +-(NSString*) stringify; ++(Target*) unstringify: (NSString*) str withConfigList: (NSArray*) configs; + +@end diff --git a/Target.m b/Target.m new file mode 100644 index 0000000..35ebfc2 --- /dev/null +++ b/Target.m @@ -0,0 +1,55 @@ +// +// Target.m +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// + +@implementation Target + ++(Target*) unstringify: (NSString*) str withConfigList: (NSArray*) configs { + NSArray* components = [str componentsSeparatedByString:@"~"]; + NSParameterAssert([components count]); + NSString* typeTag = [components objectAtIndex:0]; + if([typeTag isEqualToString:@"key"]) + return [TargetKeyboard unstringifyImpl:components]; + if([typeTag isEqualToString:@"cfg"]) + return [TargetConfig unstringifyImpl:components withConfigList:configs]; + if([typeTag isEqualToString:@"mmove"]) + return [TargetMouseMove unstringifyImpl:components]; + if([typeTag isEqualToString:@"mbtn"]) + return [TargetMouseBtn unstringifyImpl:components]; + + NSParameterAssert(NO); + return NULL; +} + +-(NSString*) stringify { + [self doesNotRecognizeSelector:_cmd]; + return NULL; +} + +-(void) trigger { + [self doesNotRecognizeSelector:_cmd]; +} + +-(void) untrigger { + // no-op by default +} + +-(BOOL) running { + return running; +} +-(void) setRunning: (BOOL) newRunning { + if(newRunning == running) + return; + if(newRunning) + [self trigger]; + else + [self untrigger]; + running = newRunning; +} + +@synthesize inputValue; + +@end diff --git a/TargetConfig.h b/TargetConfig.h new file mode 100644 index 0000000..0f164b9 --- /dev/null +++ b/TargetConfig.h @@ -0,0 +1,20 @@ +// +// TargetConfig.h +// Enjoy +// +// Created by Sam McCall on 6/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class Config; +@class Target; + +@interface TargetConfig : Target { + Config *config; +} + +@property(readwrite, retain) Config* config; ++(TargetConfig*) unstringifyImpl: (NSArray*) comps withConfigList: (NSArray*) configs; + +@end diff --git a/TargetConfig.m b/TargetConfig.m new file mode 100644 index 0000000..73d4563 --- /dev/null +++ b/TargetConfig.m @@ -0,0 +1,36 @@ +// +// TargetConfig.m +// Enjoy +// +// Created by Sam McCall on 6/05/09. +// + +#import "TargetConfig.h" + + +@implementation TargetConfig + +@synthesize config; + +-(NSString*) stringify { + return [[NSString alloc] initWithFormat: @"cfg~%@", [config name]]; +} + ++(TargetConfig*) unstringifyImpl: (NSArray*) comps withConfigList: (NSArray*) configs { + NSParameterAssert([comps count] == 2); + NSString* name = [comps objectAtIndex: 1]; + TargetConfig* target = [[TargetConfig alloc] init]; + for(int i=0; i<[configs count]; i++) + if([[[configs objectAtIndex:i] name] isEqualToString:name]) { + [target setConfig: [configs objectAtIndex:i]]; + return target; + } + NSLog(@"Warning: couldn't find matching config to restore from: %@",name); + return NULL; +} + +-(void) trigger { + [[[[NSApplication sharedApplication] delegate] configsController] activateConfig:config forApplication: NULL]; +} + +@end diff --git a/TargetController.h b/TargetController.h new file mode 100644 index 0000000..f264210 --- /dev/null +++ b/TargetController.h @@ -0,0 +1,41 @@ +// +// TargetController.h +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class KeyInputTextView; +@class ConfigsController; +@class JoystickController; +@class Target; + +@class TargetMouseMove; + +@interface TargetController : NSObject { + IBOutlet KeyInputTextView* keyInput; + IBOutlet NSButtonCell *radioNoAction, *radioKey, *radioConfig; + IBOutlet NSMatrix* radioButtons; + IBOutlet NSMatrix* mouseBtnRadio; + IBOutlet NSTextField* title; + IBOutlet NSPopUpButton* configPopup; + IBOutlet ConfigsController* configsController; + IBOutlet JoystickController* joystickController; + id currentJsaction; +} + +-(void) keyChanged; +-(void) load; +-(void) commit; +-(void) reset; +-(Target*) state; +-(void) refreshConfigsPreservingSelection: (BOOL) preserve; +-(IBAction)configChosen:(id)sender; +-(IBAction)radioChanged:(id)sender; +-(void) focusKey; + +@property(readwrite) BOOL enabled; + +@end diff --git a/TargetController.m b/TargetController.m new file mode 100644 index 0000000..70dc1e8 --- /dev/null +++ b/TargetController.m @@ -0,0 +1,143 @@ +// +// TargetController.m +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// + +@implementation TargetController + +-(void) keyChanged { + [radioButtons setState: 1 atRow: 1 column: 0 ]; + [self commit]; +} +-(IBAction)radioChanged:(id)sender { + [[[NSApplication sharedApplication] mainWindow] makeFirstResponder: sender]; + [self commit]; +} + + +-(Target*) state { + switch([radioButtons selectedRow]) { + case 0: // none + return NULL; + case 1: // key + if([keyInput hasKey]) { + TargetKeyboard* k = [[TargetKeyboard alloc] init]; + [k setVk: [keyInput vk]]; + [k setDescr: [keyInput descr]]; + return k; + } + break; + case 2: + { + TargetConfig* c = [[TargetConfig alloc] init]; + [c setConfig: [[configsController configs] objectAtIndex: [configPopup indexOfSelectedItem]]]; + return c; + } + case 3: { + // mouse X + TargetMouseMove *mm = [[TargetMouseMove alloc] init]; + [mm setDir: 0]; + return mm; + } + case 4: { + // mouse Y + TargetMouseMove *mm = [[TargetMouseMove alloc] init]; + [mm setDir: 1]; + return mm; + } + case 5: { + // mouse button + TargetMouseBtn *mb = [[TargetMouseBtn alloc] init]; + [mb setWhich: [mouseBtnRadio selectedCol]]; + return mb; + } + } + return NULL; +} + +-(void)configChosen:(id)sender { + [radioButtons setState: 1 atRow: 2 column: 0]; + [self commit]; +} + +-(void) commit { + id action = [joystickController selectedAction]; + if(action) { + Target* target = [self state]; + [[configsController currentConfig] setTarget: target forAction: action]; + } +} + +-(void) reset { + [keyInput clear]; + [radioButtons setState: 1 atRow: 0 column: 0]; + [self refreshConfigsPreservingSelection: NO]; +} + +-(void) setEnabled: (BOOL) enabled { + [radioButtons setEnabled: enabled]; + [keyInput setEnabled: enabled]; + [configPopup setEnabled: enabled]; +} +-(BOOL) enabled { + return [radioButtons isEnabled]; +} + +-(void) load { + id jsaction = [joystickController selectedAction]; + currentJsaction = jsaction; + if(!jsaction) { + [self setEnabled: NO]; + [title setStringValue: @""]; + return; + } else { + [self setEnabled: YES]; + } + Target* target = [[configsController currentConfig] getTargetForAction: jsaction]; + + id act = jsaction; + NSString* actFullName = [act name]; + while([act base]) { + act = [act base]; + actFullName = [[NSString alloc] initWithFormat: @"%@ > %@", [act name], actFullName]; + } + [title setStringValue: [[NSString alloc] initWithFormat: @"%@ > %@", [[configsController currentConfig] name], actFullName]]; + + if(!target) { + // already reset + } else if([target isKindOfClass: [TargetKeyboard class]]) { + [radioButtons setState:1 atRow: 1 column: 0]; + [keyInput setVk: [(TargetKeyboard*)target vk]]; + } else if([target isKindOfClass: [TargetConfig class]]) { + [radioButtons setState:1 atRow: 2 column: 0]; + [configPopup selectItemAtIndex: [[configsController configs] indexOfObject: [(TargetConfig*)target config]]]; + } else if ([target isKindOfClass: [TargetMouseMove class]]) { + if ([(TargetMouseMove *)target dir] == 0) + [radioButtons setState:1 atRow: 3 column: 0]; + else + [radioButtons setState:1 atRow: 4 column: 0]; + } else { + [NSException raise:@"Unknown target subclass" format:@"Unknown target subclass"]; + } +} + +-(void) focusKey { + [[[NSApplication sharedApplication] mainWindow] makeFirstResponder: keyInput]; +} + +-(void) refreshConfigsPreservingSelection: (BOOL) preserve { + int initialIndex = [configPopup indexOfSelectedItem]; + + NSArray* configs = [configsController configs]; + [configPopup removeAllItems]; + for(int i=0; i<[configs count]; i++) { + [configPopup addItemWithTitle: [[configs objectAtIndex:i]name]]; + } + if(preserve) + [configPopup selectItemAtIndex:initialIndex]; + +} + +@end diff --git a/TargetKeyboard.h b/TargetKeyboard.h new file mode 100644 index 0000000..a968a9a --- /dev/null +++ b/TargetKeyboard.h @@ -0,0 +1,22 @@ +// +// TargetKeyboard.h +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// Copyright 2009 University of Otago. All rights reserved. +// + +#import +@class Target; + +@interface TargetKeyboard : Target { + CGKeyCode vk; + NSString* descr; +} + +@property (readwrite) CGKeyCode vk; +@property (readwrite, copy) NSString* descr; + ++(TargetKeyboard*) unstringifyImpl: (NSArray*) comps; + +@end diff --git a/TargetKeyboard.m b/TargetKeyboard.m new file mode 100644 index 0000000..7bf9f83 --- /dev/null +++ b/TargetKeyboard.m @@ -0,0 +1,36 @@ +// +// TargetKeyboard.m +// Enjoy +// +// Created by Sam McCall on 5/05/09. +// + +@implementation TargetKeyboard + +@synthesize vk, descr; + +-(NSString*) stringify { + return [[NSString alloc] initWithFormat: @"key~%d~%@", vk, descr]; +} + ++(TargetKeyboard*) unstringifyImpl: (NSArray*) comps { + NSParameterAssert([comps count] == 3); + TargetKeyboard* target = [[TargetKeyboard alloc] init]; + [target setVk: [[comps objectAtIndex:1] integerValue]]; + [target setDescr: [comps objectAtIndex:2]]; + return target; +} + +-(void) trigger { + CGEventRef keyDown = CGEventCreateKeyboardEvent(NULL, vk, true); + CGEventPost(kCGHIDEventTap, keyDown); + CFRelease(keyDown); +} + +-(void) untrigger { + CGEventRef keyUp = CGEventCreateKeyboardEvent(NULL, vk, false); + CGEventPost(kCGHIDEventTap, keyUp); + CFRelease(keyUp); +} + +@end diff --git a/TargetMouseBtn.h b/TargetMouseBtn.h new file mode 100644 index 0000000..921f29c --- /dev/null +++ b/TargetMouseBtn.h @@ -0,0 +1,19 @@ +// +// TargetMouseBtn.h +// Enjoy +// +// Created by Yifeng Huang on 7/27/12. +// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// + +#import "Target.h" + +@interface TargetMouseBtn : Target { + int which; +} + +@property(readwrite) int which; + ++(TargetMouseBtn*) unstringifyImpl: (NSArray*) comps; + +@end diff --git a/TargetMouseBtn.m b/TargetMouseBtn.m new file mode 100644 index 0000000..5137f2b --- /dev/null +++ b/TargetMouseBtn.m @@ -0,0 +1,48 @@ +// +// TargetMouseBtn.m +// Enjoy +// +// Created by Yifeng Huang on 7/27/12. +// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// + +#import "TargetMouseBtn.h" + +@implementation TargetMouseBtn + +@synthesize which; + +-(NSString*) stringify { + return [[NSString alloc] initWithFormat: @"mbtn~%d", which]; +} + ++(TargetMouseBtn*) unstringifyImpl: (NSArray*) comps { + NSParameterAssert([comps count] == 2); + TargetMouseBtn* target = [[TargetMouseBtn alloc] init]; + [target setWhich: [[comps objectAtIndex:1] integerValue]]; + return target; +} + +-(void) trigger { + NSPoint mouseLoc = [NSEvent mouseLocation]; + CGEventType eventType = (which == kCGMouseButtonLeft) ? kCGEventLeftMouseDown : kCGEventRightMouseDown; + CGEventRef click = CGEventCreateMouseEvent(NULL, + eventType, + CGPointMake(mouseLoc.x, mouseLoc.y), + which); + CGEventPost(kCGHIDEventTap, click); + CFRelease(click); +} + +-(void) untrigger { + NSPoint mouseLoc = [NSEvent mouseLocation]; + CGEventType eventType = (which == kCGMouseButtonLeft) ? kCGEventLeftMouseUp : kCGEventRightMouseUp; + CGEventRef click = CGEventCreateMouseEvent(NULL, + eventType, + CGPointMake(mouseLoc.x, mouseLoc.y), + which); + CGEventPost(kCGHIDEventTap, click); + CFRelease(click); +} + +@end diff --git a/TargetMouseMove.h b/TargetMouseMove.h new file mode 100644 index 0000000..a3e2fdf --- /dev/null +++ b/TargetMouseMove.h @@ -0,0 +1,20 @@ +// +// TargetMouseMove.h +// Enjoy +// +// Created by Yifeng Huang on 7/26/12. +// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// + +#import +#import "Target.h" + +@interface TargetMouseMove : Target { + int dir; +} + +@property(readwrite) int dir; + ++(TargetMouseMove*) unstringifyImpl: (NSArray*) comps; + +@end diff --git a/TargetMouseMove.m b/TargetMouseMove.m new file mode 100644 index 0000000..dd726e5 --- /dev/null +++ b/TargetMouseMove.m @@ -0,0 +1,46 @@ +// +// TargetMouseMove.m +// Enjoy +// +// Created by Yifeng Huang on 7/26/12. +// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// + +#import "TargetMouseMove.h" + +@implementation TargetMouseMove + +-(void) setInputValue: (int) newIV { + NSPoint mouseLoc = [NSEvent mouseLocation]; + if (dir == 0) + mouseLoc.x += newIV; + else + mouseLoc.y += newIV; + + CGEventRef move = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, CGPointMake(mouseLoc.x, mouseLoc.y), kCGMouseButtonLeft); + CGEventPost(kCGHIDEventTap, move); + CFRelease(move); +} + +@synthesize dir; + +-(NSString*) stringify { + return [[NSString alloc] initWithFormat: @"mmove~%d", dir]; +} + ++(TargetMouseMove*) unstringifyImpl: (NSArray*) comps { + NSParameterAssert([comps count] == 2); + TargetMouseMove* target = [[TargetMouseMove alloc] init]; + [target setDir: [[comps objectAtIndex:1] integerValue]]; + return target; +} + +-(void) trigger { + return; +} + +-(void) untrigger { + return; +} + +@end diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..88fb42c --- /dev/null +++ b/license.txt @@ -0,0 +1,19 @@ +Copyright (c) 2009 Sam McCall + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/main.m b/main.m new file mode 100644 index 0000000..9fbf37b --- /dev/null +++ b/main.m @@ -0,0 +1,13 @@ +// +// main.m +// Enjoy +// +// Created by Sam McCall on 4/05/09. +// + +#import + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char **) argv); +}