Clean up root folder.
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Thu, 7 Mar 2013 13:56:08 +0000 (14:56 +0100)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Thu, 7 Mar 2013 13:56:08 +0000 (14:56 +0100)
128 files changed:
Categories/NSError+Description.h [new file with mode: 0644]
Categories/NSError+Description.m [new file with mode: 0644]
Categories/NSFileManager+UniqueNames.h [new file with mode: 0644]
Categories/NSFileManager+UniqueNames.m [new file with mode: 0644]
Categories/NSMenu+RepresentedObjectAccessors.h [new file with mode: 0644]
Categories/NSMenu+RepresentedObjectAccessors.m [new file with mode: 0644]
Categories/NSMutableArray+MoveObject.h [new file with mode: 0644]
Categories/NSMutableArray+MoveObject.m [new file with mode: 0644]
Categories/NSString+FixFilename.h [new file with mode: 0644]
Categories/NSString+FixFilename.m [new file with mode: 0644]
Categories/NSView+FirstResponder.h [new file with mode: 0644]
Categories/NSView+FirstResponder.m [new file with mode: 0644]
Classes/EnjoyableApplicationDelegate.h [new file with mode: 0644]
Classes/EnjoyableApplicationDelegate.m [new file with mode: 0644]
Classes/NJDevice.h [new file with mode: 0644]
Classes/NJDevice.m [new file with mode: 0644]
Classes/NJDeviceController.h [new file with mode: 0644]
Classes/NJDeviceController.m [new file with mode: 0644]
Classes/NJInput.h [new file with mode: 0644]
Classes/NJInput.m [new file with mode: 0644]
Classes/NJInputAnalog.h [new file with mode: 0644]
Classes/NJInputAnalog.m [new file with mode: 0644]
Classes/NJInputButton.h [new file with mode: 0644]
Classes/NJInputButton.m [new file with mode: 0644]
Classes/NJInputHat.h [new file with mode: 0644]
Classes/NJInputHat.m [new file with mode: 0644]
Classes/NJInputPathElement.h [new file with mode: 0644]
Classes/NJKeyInputField.h [new file with mode: 0644]
Classes/NJKeyInputField.m [new file with mode: 0644]
Classes/NJMapping.h [new file with mode: 0644]
Classes/NJMapping.m [new file with mode: 0644]
Classes/NJMappingsController.h [new file with mode: 0644]
Classes/NJMappingsController.m [new file with mode: 0644]
Classes/NJOutput.h [new file with mode: 0644]
Classes/NJOutput.m [new file with mode: 0644]
Classes/NJOutputController.h [new file with mode: 0644]
Classes/NJOutputController.m [new file with mode: 0644]
Classes/NJOutputKeyPress.h [new file with mode: 0644]
Classes/NJOutputKeyPress.m [new file with mode: 0644]
Classes/NJOutputMapping.h [new file with mode: 0644]
Classes/NJOutputMapping.m [new file with mode: 0644]
Classes/NJOutputMouseButton.h [new file with mode: 0644]
Classes/NJOutputMouseButton.m [new file with mode: 0644]
Classes/NJOutputMouseMove.h [new file with mode: 0644]
Classes/NJOutputMouseMove.m [new file with mode: 0644]
Classes/NJOutputMouseScroll.h [new file with mode: 0644]
Classes/NJOutputMouseScroll.m [new file with mode: 0644]
English.lproj/InfoPlist.strings [deleted file]
English.lproj/MainMenu.xib [deleted file]
EnjoyableApplicationDelegate.h [deleted file]
EnjoyableApplicationDelegate.m [deleted file]
Enjoyable_Prefix.pch [deleted file]
Help/Contents/Info.plist [deleted file]
Help/Contents/Resources/English.lproj/Makefile [deleted file]
Help/Contents/Resources/English.lproj/gfx/Icon.png [deleted file]
Help/Contents/Resources/English.lproj/index.html [deleted file]
Help/Contents/Resources/English.lproj/pgs/boring.html [deleted file]
Help/Contents/Resources/English.lproj/pgs/keyboard.html [deleted file]
Help/Contents/Resources/English.lproj/pgs/mappings.html [deleted file]
Help/Contents/Resources/English.lproj/pgs/mouse.html [deleted file]
Help/Contents/Resources/English.lproj/pgs/problems.html [deleted file]
Help/Contents/Resources/English.lproj/sty/default.css [deleted file]
NJDevice.h [deleted file]
NJDevice.m [deleted file]
NJDeviceController.h [deleted file]
NJDeviceController.m [deleted file]
NJEvents.h [deleted file]
NJInput.h [deleted file]
NJInput.m [deleted file]
NJInputAnalog.h [deleted file]
NJInputAnalog.m [deleted file]
NJInputButton.h [deleted file]
NJInputButton.m [deleted file]
NJInputHat.h [deleted file]
NJInputHat.m [deleted file]
NJInputPathElement.h [deleted file]
NJKeyInputField.h [deleted file]
NJKeyInputField.m [deleted file]
NJMapping.h [deleted file]
NJMapping.m [deleted file]
NJMappingsController.h [deleted file]
NJMappingsController.m [deleted file]
NJOutput.h [deleted file]
NJOutput.m [deleted file]
NJOutputController.h [deleted file]
NJOutputController.m [deleted file]
NJOutputKeyPress.h [deleted file]
NJOutputKeyPress.m [deleted file]
NJOutputMapping.h [deleted file]
NJOutputMapping.m [deleted file]
NJOutputMouseButton.h [deleted file]
NJOutputMouseButton.m [deleted file]
NJOutputMouseMove.h [deleted file]
NJOutputMouseMove.m [deleted file]
NJOutputMouseScroll.h [deleted file]
NJOutputMouseScroll.m [deleted file]
NSError+Description.h [deleted file]
NSError+Description.m [deleted file]
NSFileManager+UniqueNames.h [deleted file]
NSFileManager+UniqueNames.m [deleted file]
NSMenu+RepresentedObjectAccessors.h [deleted file]
NSMenu+RepresentedObjectAccessors.m [deleted file]
NSMutableArray+MoveObject.h [deleted file]
NSMutableArray+MoveObject.m [deleted file]
NSString+FixFilename.h [deleted file]
NSString+FixFilename.m [deleted file]
NSView+FirstResponder.h [deleted file]
NSView+FirstResponder.m [deleted file]
Other Sources/Enjoyable_Prefix.pch [new file with mode: 0644]
Other Sources/NJEvents.h [new file with mode: 0644]
Other Sources/main.m [new file with mode: 0644]
Resources/English.lproj/InfoPlist.strings [new file with mode: 0644]
Resources/English.lproj/MainMenu.xib [new file with mode: 0644]
Resources/Help/Contents/Info.plist [new file with mode: 0644]
Resources/Help/Contents/Resources/English.lproj/Makefile [new file with mode: 0644]
Resources/Help/Contents/Resources/English.lproj/gfx/Icon.png [new file with mode: 0644]
Resources/Help/Contents/Resources/English.lproj/index.html [new file with mode: 0644]
Resources/Help/Contents/Resources/English.lproj/pgs/boring.html [new file with mode: 0644]
Resources/Help/Contents/Resources/English.lproj/pgs/keyboard.html [new file with mode: 0644]
Resources/Help/Contents/Resources/English.lproj/pgs/mappings.html [new file with mode: 0644]
Resources/Help/Contents/Resources/English.lproj/pgs/mouse.html [new file with mode: 0644]
Resources/Help/Contents/Resources/English.lproj/pgs/problems.html [new file with mode: 0644]
Resources/Help/Contents/Resources/English.lproj/sty/default.css [new file with mode: 0644]
Resources/Icon.icns [new file with mode: 0644]
Resources/com.yukkurigames.Enjoyable.mapping.icns [new file with mode: 0644]
com.yukkurigames.Enjoyable.mapping.icns [deleted file]
icon.icns [deleted file]
main.m [deleted file]

diff --git a/Categories/NSError+Description.h b/Categories/NSError+Description.h
new file mode 100644 (file)
index 0000000..a722e9e
--- /dev/null
@@ -0,0 +1,9 @@
+#import <Foundation/Foundation.h>
+
+@interface NSError (Description)
+
++ (NSError *)errorWithDomain:(NSString *)domain
+                        code:(NSInteger)code
+                 description:(NSString *)description;
+
+@end
diff --git a/Categories/NSError+Description.m b/Categories/NSError+Description.m
new file mode 100644 (file)
index 0000000..126bb17
--- /dev/null
@@ -0,0 +1,13 @@
+#import "NSError+Description.h"
+
+@implementation NSError (Description)
+
++ (NSError *)errorWithDomain:(NSString *)domain
+                        code:(NSInteger)code
+                 description:(NSString *)description {
+    NSDictionary *errorDict = @{ NSLocalizedDescriptionKey : description };
+    return [NSError errorWithDomain:domain code:code userInfo:errorDict];
+
+}
+
+@end
diff --git a/Categories/NSFileManager+UniqueNames.h b/Categories/NSFileManager+UniqueNames.h
new file mode 100644 (file)
index 0000000..82c57d4
--- /dev/null
@@ -0,0 +1,26 @@
+//
+//  NSFileManager+UniqueNames.h
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/7/13.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSFileManager (UniqueNames)
+
+- (NSURL *)generateUniqueURLWithBase:(NSURL *)canonical;
+    // Generate a probably-unique URL by trying sequential indices, e.g.
+    //     file://Test.txt
+    //     file://Test (1).txt
+    //     file://Test (2).txt
+    // and so on.
+    //
+    // The URL is only probably unique. It is subject to the usual
+    // race conditions associated with generating a filename before
+    // actually opening it. It also does not check remote resources,
+    // as it operates synchronously. Finally, it gives up after 10,000
+    // indices.
+
+@end
diff --git a/Categories/NSFileManager+UniqueNames.m b/Categories/NSFileManager+UniqueNames.m
new file mode 100644 (file)
index 0000000..c7eb768
--- /dev/null
@@ -0,0 +1,31 @@
+//
+//  NSFileManager+UniqueNames.m
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/7/13.
+//
+//
+
+#import "NSFileManager+UniqueNames.h"
+
+@implementation NSFileManager (UniqueNames)
+
+- (NSURL *)generateUniqueURLWithBase:(NSURL *)canonical {
+    // Punt for cases that are just too hard.
+    if (!canonical.isFileURL)
+        return canonical;
+
+    NSString *trying = canonical.path;
+    NSString *dirname = [trying stringByDeletingLastPathComponent];
+    NSString *basename = [trying.lastPathComponent stringByDeletingPathExtension];
+    NSString *extension = trying.pathExtension;
+    int index = 1;
+    while ([self fileExistsAtPath:trying] && index < 10000) {
+        NSString *indexName = [NSString stringWithFormat:@"%@ (%d)", basename, index++];
+        indexName = [indexName stringByAppendingPathExtension:extension];
+        trying = [dirname stringByAppendingPathComponent:indexName];
+    }
+    return [NSURL fileURLWithPath:trying];
+}
+
+@end
diff --git a/Categories/NSMenu+RepresentedObjectAccessors.h b/Categories/NSMenu+RepresentedObjectAccessors.h
new file mode 100644 (file)
index 0000000..ecb28a6
--- /dev/null
@@ -0,0 +1,47 @@
+//
+//  NSMenu+RepresentedObjectAccessors.h
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/4/13.
+//
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface NSMenu (RepresentedObjectAccessors)
+    // Helpers for using represented objects in menu items.
+
+- (NSMenuItem *)itemWithRepresentedObject:(id)object;
+    // Returns the first menu item in the receiver that has a given
+    // represented object.
+
+- (void)removeItemWithRepresentedObject:(id)object;
+    // Removes the first menu item representing the given object in the
+    // receiver.
+    //
+    // After it removes the menu item, this method posts an
+    // NSMenuDidRemoveItemNotification.
+
+- (NSMenuItem *)lastItem;
+    // Return the last menu item in the receiver, or nil if the menu
+    // has no items.
+
+- (void)removeLastItem;
+    // Removes the last menu item in the receiver, if there is one.
+    //
+    // After and if it removes the menu item, this method posts an
+    // NSMenuDidRemoveItemNotification.
+
+@end
+
+@interface NSPopUpButton (RepresentedObjectAccessors)
+
+- (NSMenuItem *)itemWithRepresentedObject:(id)object;
+    // Returns the first item in the receiver's menu that has a given
+    // represented object.
+
+- (void)selectItemWithRepresentedObject:(id)object;
+    // Selects the first item in the receiver's menu that has a give
+    // represented object.
+
+@end
diff --git a/Categories/NSMenu+RepresentedObjectAccessors.m b/Categories/NSMenu+RepresentedObjectAccessors.m
new file mode 100644 (file)
index 0000000..a63f083
--- /dev/null
@@ -0,0 +1,48 @@
+//
+//  NSMenu+RepresentedObjectAccessors.m
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/4/13.
+//
+//
+
+#import "NSMenu+RepresentedObjectAccessors.h"
+
+@implementation NSMenu (RepresentedObjectAccessors)
+
+- (NSMenuItem *)itemWithRepresentedObject:(id)object {
+    for (NSMenuItem *item in self.itemArray)
+        if ([item.representedObject isEqual:object])
+            return item;
+    return nil;
+}
+
+- (void)removeItemWithRepresentedObject:(id)object {
+    NSInteger idx = [self indexOfItemWithRepresentedObject:object];
+    if (idx != -1)
+        [self removeItemAtIndex:idx];
+}
+
+- (NSMenuItem *)lastItem {
+    return self.itemArray.lastObject;
+}
+
+- (void)removeLastItem {
+    if (self.numberOfItems)
+        [self removeItemAtIndex:self.numberOfItems - 1];
+}
+
+@end
+
+@implementation NSPopUpButton (RepresentedObjectAccessors)
+
+- (NSMenuItem *)itemWithRepresentedObject:(id)object {
+    return [self.menu itemWithRepresentedObject:object];
+}
+
+- (void)selectItemWithRepresentedObject:(id)object {
+    [self selectItemAtIndex:[self indexOfItemWithRepresentedObject:object]];
+}
+
+
+@end
diff --git a/Categories/NSMutableArray+MoveObject.h b/Categories/NSMutableArray+MoveObject.h
new file mode 100644 (file)
index 0000000..c1c60d2
--- /dev/null
@@ -0,0 +1,15 @@
+//
+//  NSMutableArray+MoveObject.h
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/7/13.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSMutableArray (MoveObject)
+
+- (void)moveObjectAtIndex:(NSUInteger)src toIndex:(NSUInteger)dst;
+
+@end
diff --git a/Categories/NSMutableArray+MoveObject.m b/Categories/NSMutableArray+MoveObject.m
new file mode 100644 (file)
index 0000000..8d9a4ee
--- /dev/null
@@ -0,0 +1,19 @@
+//
+//  NSMutableArray+MoveObject.m
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/7/13.
+//
+//
+
+#import "NSMutableArray+MoveObject.h"
+
+@implementation NSMutableArray (MoveObject)
+
+- (void)moveObjectAtIndex:(NSUInteger)src toIndex:(NSUInteger)dst {
+    id obj = self[src];
+    [self removeObjectAtIndex:src];
+    [self insertObject:obj atIndex:dst > src ? dst - 1 : dst];
+}
+
+@end
diff --git a/Categories/NSString+FixFilename.h b/Categories/NSString+FixFilename.h
new file mode 100644 (file)
index 0000000..b3e318f
--- /dev/null
@@ -0,0 +1,28 @@
+//
+//  NSString+FixFilename.h
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/7/13.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSCharacterSet (FixFilename)
+
++ (NSCharacterSet *)invalidPathComponentCharacterSet;
+    // A character set containing the characters that are invalid to
+    // use in path components on common filesystems.
+
+@end
+
+@interface NSString (FixFilename)
+
+- (NSString *)stringByFixingPathComponent;
+    // Does various operations to make this string suitable for use as
+    // a single path component of a normal filename. Removes
+    // characters that are invalid. Strips whitespace from the
+    // beginning and end. If the first character is a . or a -, a _ is
+    // added to the front.
+
+@end
diff --git a/Categories/NSString+FixFilename.m b/Categories/NSString+FixFilename.m
new file mode 100644 (file)
index 0000000..06a837c
--- /dev/null
@@ -0,0 +1,35 @@
+//
+//  NSString+FixFilename.m
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/7/13.
+//
+//
+
+#import "NSString+FixFilename.h"
+
+@implementation NSCharacterSet (FixFilename)
+
++ (NSCharacterSet *)invalidPathComponentCharacterSet {
+    return [NSCharacterSet characterSetWithCharactersInString:@"\"\\/:*?<>|"];
+}
+
+@end
+
+@implementation NSString (FixFilename)
+
+- (NSString *)stringByFixingPathComponent {
+    NSCharacterSet *invalid = NSCharacterSet.invalidPathComponentCharacterSet;
+    NSCharacterSet *whitespace = NSCharacterSet.whitespaceAndNewlineCharacterSet;
+    NSArray *parts = [self componentsSeparatedByCharactersInSet:invalid];
+    NSString *name = [parts componentsJoinedByString:@"_"];
+    name = [name stringByTrimmingCharactersInSet:whitespace];
+    if (!name.length)
+        return @"_";
+    unichar first = [name characterAtIndex:0];
+    if (first == '.' || first == '-')
+        name = [@"_" stringByAppendingString:name];
+    return name;
+}
+
+@end
diff --git a/Categories/NSView+FirstResponder.h b/Categories/NSView+FirstResponder.h
new file mode 100644 (file)
index 0000000..cd89aa5
--- /dev/null
@@ -0,0 +1,11 @@
+#import <Cocoa/Cocoa.h>
+
+@interface NSView (FirstResponder)
+
+- (BOOL)resignIfFirstResponder;
+    // Resign first responder status if this view is the active first
+    // responder in its window. Returns whether first responder status
+    // was resigned; YES if it was and NO if refused or the view was
+    // not the first responder.
+
+@end
diff --git a/Categories/NSView+FirstResponder.m b/Categories/NSView+FirstResponder.m
new file mode 100644 (file)
index 0000000..0575b0d
--- /dev/null
@@ -0,0 +1,12 @@
+#import "NSView+FirstResponder.h"
+
+@implementation NSView (FirstResponder)
+
+- (BOOL)resignIfFirstResponder {
+    NSWindow *window = self.window;
+    return window.firstResponder == self
+        ? [window makeFirstResponder:nil]
+        : NO;
+}
+
+@end
diff --git a/Classes/EnjoyableApplicationDelegate.h b/Classes/EnjoyableApplicationDelegate.h
new file mode 100644 (file)
index 0000000..b795a92
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  EnjoyableApplicationDelegate.h
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+@class NJDeviceController;
+@class NJOutputController;
+@class NJMappingsController;
+
+@interface EnjoyableApplicationDelegate : NSObject <NSApplicationDelegate,
+                                                    NSSplitViewDelegate>
+{
+    IBOutlet NSMenu *dockMenuBase;
+    IBOutlet NSWindow *window;
+}
+
+@property (nonatomic, strong) IBOutlet NJDeviceController *inputController;
+@property (nonatomic, strong) IBOutlet NJMappingsController *mappingsController;
+
+@end
diff --git a/Classes/EnjoyableApplicationDelegate.m b/Classes/EnjoyableApplicationDelegate.m
new file mode 100644 (file)
index 0000000..c477511
--- /dev/null
@@ -0,0 +1,155 @@
+//
+//  EnjoyableApplicationDelegate.m
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//
+
+#import "EnjoyableApplicationDelegate.h"
+
+#import "NJMapping.h"
+#import "NJMappingsController.h"
+#import "NJDeviceController.h"
+#import "NJOutputController.h"
+#import "NJEvents.h"
+
+@implementation EnjoyableApplicationDelegate
+
+- (void)didSwitchApplication:(NSNotification *)note {
+    NSRunningApplication *activeApp = note.userInfo[NSWorkspaceApplicationKey];
+    NSString *name = activeApp.localizedName;
+    if (!name)
+        name = activeApp.bundleIdentifier;
+    if (name && ![name isEqualToString:NSRunningApplication.currentApplication.localizedName])
+        [self.mappingsController activateMappingForProcess:name];
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)notification {
+    [NSNotificationCenter.defaultCenter
+        addObserver:self
+        selector:@selector(mappingDidChange:)
+        name:NJEventMappingChanged
+        object:nil];
+    [NSNotificationCenter.defaultCenter
+        addObserver:self
+        selector:@selector(mappingListDidChange:)
+        name:NJEventMappingListChanged
+        object:nil];
+    [NSNotificationCenter.defaultCenter
+        addObserver:self
+        selector:@selector(eventTranslationActivated:)
+        name:NJEventTranslationActivated
+        object:nil];
+    [NSNotificationCenter.defaultCenter
+        addObserver:self
+        selector:@selector(eventTranslationDeactivated:)
+        name:NJEventTranslationDeactivated
+        object:nil];
+
+    [self.inputController setup];
+    [self.mappingsController load];
+}
+
+- (void)applicationDidBecomeActive:(NSNotification *)notification {
+    [window makeKeyAndOrderFront:nil];
+}
+
+- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication
+                    hasVisibleWindows:(BOOL)flag {
+    [window makeKeyAndOrderFront:nil];
+    return NO;
+}
+
+- (void)eventTranslationActivated:(NSNotification *)note {
+    [NSProcessInfo.processInfo disableAutomaticTermination:@"Input translation is active."];
+    [NSWorkspace.sharedWorkspace.notificationCenter
+        addObserver:self
+        selector:@selector(didSwitchApplication:)
+        name:NSWorkspaceDidActivateApplicationNotification
+        object:nil];
+    NSLog(@"Listening for application changes.");
+}
+
+- (void)eventTranslationDeactivated:(NSNotification *)note {
+    [NSProcessInfo.processInfo enableAutomaticTermination:@"Input translation is active."];
+    [NSWorkspace.sharedWorkspace.notificationCenter
+        removeObserver:self
+        name:NSWorkspaceDidActivateApplicationNotification
+        object:nil];
+    NSLog(@"Ignoring application changes.");
+}
+
+- (void)mappingListDidChange:(NSNotification *)note {
+    NSArray *mappings = note.object;
+    while (dockMenuBase.lastItem.representedObject)
+        [dockMenuBase removeLastItem];
+    int added = 0;
+    for (NJMapping *mapping in mappings) {
+        NSString *keyEquiv = ++added < 10 ? @(added).stringValue : @"";
+        NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:mapping.name
+                                                      action:@selector(chooseMapping:)
+                                               keyEquivalent:keyEquiv];
+        item.representedObject = mapping;
+        item.state = mapping == self.mappingsController.currentMapping;
+        [dockMenuBase addItem:item];
+    }
+}
+
+- (void)mappingDidChange:(NSNotification *)note {
+    NJMapping *current = note.object;
+    for (NSMenuItem *item in dockMenuBase.itemArray)
+        if (item.representedObject)
+            item.state = item.representedObject == current;
+}
+
+- (void)chooseMapping:(NSMenuItem *)sender {
+    NJMapping *chosen = sender.representedObject;
+    [self.mappingsController activateMapping:chosen];
+}
+
+#define OUTPUT_PANE_MIN_WIDTH 390
+
+- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset {
+    return proposedMax - OUTPUT_PANE_MIN_WIDTH;
+}
+
+- (void)splitView:(NSSplitView *)splitView resizeSubviewsWithOldSize:(NSSize)oldSize {
+    NSView *inputView = splitView.subviews[0];
+    NSView *outputView = splitView.subviews[1];
+    if (outputView.frame.size.width < OUTPUT_PANE_MIN_WIDTH) {
+        NSSize frameSize = splitView.frame.size;
+        CGFloat inputWidth = frameSize.width - OUTPUT_PANE_MIN_WIDTH - splitView.dividerThickness;
+        inputView.frame = NSMakeRect(inputWidth, frameSize.height,
+                                    inputView.frame.size.width,
+                                    inputView.frame.size.height);
+        outputView.frame = NSMakeRect(inputWidth + splitView.dividerThickness,
+                                     0,
+                                     OUTPUT_PANE_MIN_WIDTH,
+                                     frameSize.height);
+    } else
+        [splitView adjustSubviews];
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
+    NSMenu *menu = [[NSMenu alloc] init];
+    int added = 0;
+    for (NJMapping *mapping in self.mappingsController) {
+        NSString *keyEquiv = ++added < 10 ? @(added).stringValue : @"";
+        NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:mapping.name
+                                                      action:@selector(chooseMapping:)
+                                               keyEquivalent:keyEquiv];
+        item.representedObject = mapping;
+        item.state = mapping == self.mappingsController.currentMapping;
+        [menu addItem:item];
+    }
+    return menu;
+}
+
+- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
+    NSURL *url = [NSURL fileURLWithPath:filename];
+    [self.mappingsController addMappingWithContentsOfURL:url];
+    return YES;
+}
+
+
+@end
diff --git a/Classes/NJDevice.h b/Classes/NJDevice.h
new file mode 100644 (file)
index 0000000..ec607ac
--- /dev/null
@@ -0,0 +1,26 @@
+//
+//  NJDevice.h
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+#import "NJInputPathElement.h"
+
+@class NJInput;
+
+@interface NJDevice : NSObject <NJInputPathElement>
+
+@property (nonatomic, assign) int index;
+@property (nonatomic, copy) NSString *productName;
+@property (nonatomic, assign) IOHIDDeviceRef device;
+@property (nonatomic, copy) NSArray *children;
+@property (nonatomic, readonly) NSString *name;
+@property (readonly) NSString *uid;
+
+- (id)initWithDevice:(IOHIDDeviceRef)device;
+- (NJInput *)handlerForEvent:(IOHIDValueRef)value;
+- (NJInput *)inputForEvent:(IOHIDValueRef)value;
+
+@end
diff --git a/Classes/NJDevice.m b/Classes/NJDevice.m
new file mode 100644 (file)
index 0000000..69775d8
--- /dev/null
@@ -0,0 +1,109 @@
+//
+//  NJDevice.m
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//
+
+#import "NJDevice.h"
+
+#import "NJInput.h"
+#import "NJInputAnalog.h"
+#import "NJInputHat.h"
+#import "NJInputButton.h"
+
+static NSArray *InputsForElement(IOHIDDeviceRef device, id base) {
+    CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
+    NSMutableArray *children = [NSMutableArray arrayWithCapacity:CFArrayGetCount(elements)];
+    
+    int buttons = 0;
+    int axes = 0;
+    int hats = 0;
+    
+    for (int i = 0; i < CFArrayGetCount(elements); i++) {
+        IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
+        int type = IOHIDElementGetType(element);
+        unsigned usage = IOHIDElementGetUsage(element);
+        unsigned usagePage = IOHIDElementGetUsagePage(element);
+        long max = IOHIDElementGetPhysicalMax(element);
+        long min = IOHIDElementGetPhysicalMin(element);
+        CFStringRef elName = IOHIDElementGetName(element);
+        
+        NJInput *input = nil;
+        
+        if (!(type == kIOHIDElementTypeInput_Misc
+              || type == kIOHIDElementTypeInput_Axis
+              || type == kIOHIDElementTypeInput_Button))
+             continue;
+        
+        if (max - min == 1 || usagePage == kHIDPage_Button || type == kIOHIDElementTypeInput_Button) {
+            input = [[NJInputButton alloc] initWithName:(__bridge NSString *)elName
+                                                    idx:++buttons
+                                                    max:max];
+        } else if (usage == kHIDUsage_GD_Hatswitch) {
+            input = [[NJInputHat alloc] initWithIndex:++hats];
+        } else if (usage >= kHIDUsage_GD_X && usage <= kHIDUsage_GD_Rz) {
+            input = [[NJInputAnalog alloc] initWithIndex:++axes
+                                                  rawMin:min
+                                                  rawMax:max];
+        } else {
+            continue;
+        }
+        
+        // TODO(jfw): Should be moved into better constructors.
+        input.base = base;
+        input.cookie = IOHIDElementGetCookie(element);
+        [children addObject:input];
+    }
+
+    CFRelease(elements);
+    return children;
+}
+
+@implementation NJDevice {
+    int vendorId;
+    int productId;
+}
+
+- (id)initWithDevice:(IOHIDDeviceRef)dev {
+    if ((self = [super init])) {
+        self.device = dev;
+        self.productName = (__bridge NSString *)IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDProductKey));
+        vendorId = [(__bridge NSNumber *)IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDVendorIDKey)) intValue];
+        productId = [(__bridge NSNumber *)IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDProductIDKey)) intValue];
+        self.children = InputsForElement(dev, self);
+    }
+    return self;
+}
+
+- (NSString *)name {
+    return [NSString stringWithFormat:@"%@ #%d", _productName, _index];
+}
+
+- (id)base {
+    return nil;
+}
+
+- (NSString *)uid {
+    return [NSString stringWithFormat: @"%d:%d:%d", vendorId, productId, _index];
+}
+
+- (NJInput *)findInputByCookie:(IOHIDElementCookie)cookie {
+    for (NJInput *child in _children)
+        if (child.cookie == cookie)
+            return child;
+    return nil;
+}
+
+- (NJInput *)handlerForEvent:(IOHIDValueRef)value {
+    NJInput *mainInput = [self inputForEvent:value];
+    return [mainInput findSubInputForValue:value];
+}
+
+- (NJInput *)inputForEvent:(IOHIDValueRef)value {
+    IOHIDElementRef elt = IOHIDValueGetElement(value);
+    IOHIDElementCookie cookie = IOHIDElementGetCookie(elt);
+    return [self findInputByCookie:cookie];
+}
+
+@end
diff --git a/Classes/NJDeviceController.h b/Classes/NJDeviceController.h
new file mode 100644 (file)
index 0000000..7658c64
--- /dev/null
@@ -0,0 +1,31 @@
+//
+//  NJDeviceController.h
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+@class NJDevice;
+@class NJInput;
+@class NJMappingsController;
+@class NJOutputController;
+
+@interface NJDeviceController : NSObject <NSOutlineViewDataSource, NSOutlineViewDelegate> {
+    IBOutlet NSOutlineView *outlineView;
+    IBOutlet NJOutputController *outputController;
+    IBOutlet NJMappingsController *mappingsController;
+    IBOutlet NSButton *translatingEventsButton;
+    IBOutlet NSMenuItem *translatingEventsMenu;
+}
+
+@property (nonatomic, readonly) NJInput *selectedInput;
+@property (nonatomic, assign) NSPoint mouseLoc;
+@property (nonatomic, assign) BOOL translatingEvents;
+
+- (void)setup;
+- (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device;
+
+- (IBAction)translatingEventsChanged:(id)sender;
+
+@end
diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m
new file mode 100644 (file)
index 0000000..d553d7d
--- /dev/null
@@ -0,0 +1,243 @@
+//
+//  NJDeviceController.m
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//
+
+#import "NJDeviceController.h"
+
+#import "NJMapping.h"
+#import "NJMappingsController.h"
+#import "NJDevice.h"
+#import "NJInput.h"
+#import "NJOutput.h"
+#import "NJOutputController.h"
+#import "NJEvents.h"
+
+@implementation NJDeviceController {
+    IOHIDManagerRef hidManager;
+    NSTimer *continuousTimer;
+    NSMutableArray *runningOutputs;
+    NSMutableArray *_devices;
+}
+
+- (id)init {
+    if ((self = [super init])) {
+        _devices = [[NSMutableArray alloc] initWithCapacity:16];
+        runningOutputs = [[NSMutableArray alloc] initWithCapacity:32];
+    }
+    return self;
+}
+
+- (void)dealloc {
+    [continuousTimer invalidate];
+    IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
+    CFRelease(hidManager);
+}
+
+- (void)expandRecursive:(id <NJInputPathElement>)pathElement {
+    if (pathElement) {
+        [self expandRecursive:pathElement.base];
+        [outlineView expandItem:pathElement];
+    }
+}
+
+- (void)addRunningOutput:(NJOutput *)output {
+    if (![runningOutputs containsObject:output]) {
+        [runningOutputs addObject:output];
+    }
+    if (!continuousTimer) {
+        continuousTimer = [NSTimer scheduledTimerWithTimeInterval:1.f/60.f
+                                                           target:self
+                                                         selector:@selector(updateContinuousInputs:)
+                                                         userInfo:nil
+                                                          repeats:YES];
+        NSLog(@"Scheduled continuous output timer.");
+    }
+}
+
+- (void)runOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
+    NJDevice *dev = [self findDeviceByRef:device];
+    NJInput *mainInput = [dev inputForEvent:value];
+    [mainInput notifyEvent:value];
+    NSArray *children = mainInput.children ? mainInput.children : mainInput ? @[mainInput] : @[];
+    for (NJInput *subInput in children) {
+        NJOutput *output = mappingsController.currentMapping[subInput];
+        output.magnitude = subInput.magnitude;
+        output.running = subInput.active;
+        if ((output.running || output.magnitude) && output.isContinuous)
+            [self addRunningOutput:output];
+    }
+}
+
+- (void)showOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
+    NJDevice *dev = [self findDeviceByRef:device];
+    NJInput *handler = [dev handlerForEvent:value];
+    if (!handler)
+        return;
+    
+    [self expandRecursive:handler];
+    [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[outlineView rowForItem:handler]] byExtendingSelection: NO];
+    [outputController focusKey];
+}
+
+static void input_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDValueRef value) {
+    NJDeviceController *controller = (__bridge NJDeviceController *)ctx;
+    IOHIDDeviceRef device = IOHIDQueueGetDevice(inSender);
+    
+    if (controller.translatingEvents) {
+        [controller runOutputForDevice:device value:value];
+    } else if ([NSApplication sharedApplication].mainWindow.isVisible) {
+        [controller showOutputForDevice:device value:value];
+    }
+}
+
+static int findAvailableIndex(NSArray *list, NJDevice *dev) {
+    for (int index = 1; ; index++) {
+        BOOL available = YES;
+        for (NJDevice *used in list) {
+            if ([used.productName isEqualToString:dev.productName] && used.index == index) {
+                available = NO;
+                break;
+            }
+        }
+        if (available)
+            return index;
+    }
+}
+
+- (void)addDeviceForDevice:(IOHIDDeviceRef)device {
+    IOHIDDeviceRegisterInputValueCallback(device, input_callback, (__bridge void*)self);
+    NJDevice *dev = [[NJDevice alloc] initWithDevice:device];
+    dev.index = findAvailableIndex(_devices, dev);
+    [_devices addObject:dev];
+    [outlineView reloadData];
+}
+
+static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
+    NJDeviceController *controller = (__bridge NJDeviceController *)ctx;
+    [controller addDeviceForDevice:device];
+}
+
+- (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device {
+    for (NJDevice *dev in _devices)
+        if (dev.device == device)
+            return dev;
+    return nil;
+}
+
+static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
+    NJDeviceController *controller = (__bridge NJDeviceController *)ctx;
+    [controller removeDeviceForDevice:device];
+}
+
+- (void)removeDeviceForDevice:(IOHIDDeviceRef)device {
+    NJDevice *match = [self findDeviceByRef:device];
+    IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL);
+    if (match) {
+        [_devices removeObject:match];
+        [outlineView reloadData];
+    }
+    
+}
+
+- (void)updateContinuousInputs:(NSTimer *)timer {
+    self.mouseLoc = [NSEvent mouseLocation];
+    for (NJOutput *output in [runningOutputs copy]) {
+        if (![output update:self]) {
+            [runningOutputs removeObject:output];
+        }
+    }
+    if (!runningOutputs.count) {
+        [continuousTimer invalidate];
+        continuousTimer = nil;
+        NSLog(@"Unscheduled continuous output timer.");
+    }
+}
+
+#define NSSTR(e) ((NSString *)CFSTR(e))
+
+- (void)setup {
+    hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
+    NSArray *criteria = @[ @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
+                              NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) },
+                           @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
+                              NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) },
+                           @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
+                              NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) }
+                           ];
+    IOHIDManagerSetDeviceMatchingMultiple(hidManager, (__bridge CFArrayRef)criteria);
+    
+    IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+    IOReturn ret = IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone);
+    if (ret != kIOReturnSuccess) {
+        [[NSAlert alertWithMessageText:@"Input devices are unavailable"
+                         defaultButton:nil
+                       alternateButton:nil
+                           otherButton:nil
+             informativeTextWithFormat:@"Error 0x%08x occured trying to access your devices. "
+                                       @"Input may not be correctly detected or mapped.",
+                                       ret]
+         beginSheetModalForWindow:outlineView.window
+                    modalDelegate:nil
+                   didEndSelector:nil
+                      contextInfo:nil];
+    }
+    
+    IOHIDManagerRegisterDeviceMatchingCallback(hidManager, add_callback, (__bridge void *)self);
+    IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (__bridge void *)self);
+}
+
+- (NJInput *)selectedInput {
+    id <NJInputPathElement> item = [outlineView itemAtRow:outlineView.selectedRow];
+    return (!item.children && item.base) ? item : nil;
+}
+
+- (NSInteger)outlineView:(NSOutlineView *)outlineView
+  numberOfChildrenOfItem:(id <NJInputPathElement>)item {
+    return item ? item.children.count : _devices.count;
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView
+   isItemExpandable:(id <NJInputPathElement>)item {
+    return item ? [[item children] count] > 0: YES;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView
+            child:(NSInteger)index
+           ofItem:(id <NJInputPathElement>)item {
+    return item ? item.children[index] : _devices[index];
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView
+objectValueForTableColumn:(NSTableColumn *)tableColumn
+           byItem:(id <NJInputPathElement>)item  {
+    return item ? item.name : @"root";
+}
+
+- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
+    
+    [outputController loadCurrent];
+}
+
+- (void)setTranslatingEvents:(BOOL)translatingEvents {
+    if (translatingEvents != _translatingEvents) {
+        _translatingEvents = translatingEvents;
+        NSInteger state = translatingEvents ? NSOnState : NSOffState;
+        translatingEventsButton.state = state;
+        translatingEventsMenu.title = translatingEvents ? @"Disable" : @"Enable";
+        NSString *name = translatingEvents
+            ? NJEventTranslationActivated
+            : NJEventTranslationDeactivated;
+        [NSNotificationCenter.defaultCenter postNotificationName:name
+                                                          object:self];
+    }
+}
+
+- (IBAction)translatingEventsChanged:(NSButton *)sender {
+    self.translatingEvents = sender.state == NSOnState;
+}
+
+
+@end
diff --git a/Classes/NJInput.h b/Classes/NJInput.h
new file mode 100644 (file)
index 0000000..380e858
--- /dev/null
@@ -0,0 +1,26 @@
+//
+//  NJInput.h
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+#import "NJInputPathElement.h"
+
+@interface NJInput : NSObject <NJInputPathElement>
+
+@property (nonatomic, assign) IOHIDElementCookie cookie;
+@property (nonatomic, copy) NSArray *children;
+@property (nonatomic, weak) id base;
+@property (nonatomic, copy) NSString *name;
+@property (nonatomic, assign) BOOL active;
+@property (nonatomic, assign) float magnitude;
+@property (readonly) NSString *uid;
+
+- (id)initWithName:(NSString *)newName base:(id <NJInputPathElement>)newBase;
+
+- (void)notifyEvent:(IOHIDValueRef)value;
+- (id)findSubInputForValue:(IOHIDValueRef)value;
+
+@end
diff --git a/Classes/NJInput.m b/Classes/NJInput.m
new file mode 100644 (file)
index 0000000..077dac8
--- /dev/null
@@ -0,0 +1,32 @@
+//
+//  NJInput.m
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//
+
+#import "NJInput.h"
+
+@implementation NJInput
+
+- (id)initWithName:(NSString *)newName base:(id <NJInputPathElement>)newBase {
+    if ((self = [super init])) {
+        self.name = newName;
+        self.base = newBase;
+    }
+    return self;
+}
+
+- (id)findSubInputForValue:(IOHIDValueRef)value {
+    return NULL;
+}
+
+- (NSString *)uid {
+    return [NSString stringWithFormat:@"%@~%@", [_base uid], _name];
+}
+
+- (void)notifyEvent:(IOHIDValueRef)value {
+    [self doesNotRecognizeSelector:_cmd];
+}
+
+@end
diff --git a/Classes/NJInputAnalog.h b/Classes/NJInputAnalog.h
new file mode 100644 (file)
index 0000000..e79ed89
--- /dev/null
@@ -0,0 +1,17 @@
+//
+//  NJInputAnalog.h
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+#import "NJInput.h"
+
+@interface NJInputAnalog : NJInput
+
+- (id)initWithIndex:(int)index rawMin:(long)rawMin rawMax:(long)rawMax;
+
+@end
diff --git a/Classes/NJInputAnalog.m b/Classes/NJInputAnalog.m
new file mode 100644 (file)
index 0000000..7b4b15a
--- /dev/null
@@ -0,0 +1,55 @@
+//
+//  NJInputAnalog.m
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//
+
+#define DEAD_ZONE 0.3
+
+#import "NJInputAnalog.h"
+
+static float normalize(long p, long min, long max) {
+    return 2 * (p - min) / (float)(max - min) - 1;
+}
+
+@implementation NJInputAnalog {
+    float magnitude;
+    long rawMin;
+    long rawMax;
+}
+
+- (id)initWithIndex:(int)index rawMin:(long)rawMin_ rawMax:(long)rawMax_ {
+    if ((self = [super init])) {
+        self.name = [[NSString alloc] initWithFormat: @"Axis %d", index];
+        self.children = @[[[NJInput alloc] initWithName:@"Low" base:self],
+                          [[NJInput alloc] initWithName:@"High" base:self]];
+        rawMax = rawMax_;
+        rawMin = rawMin_;
+    }
+    return self;
+}
+
+- (id)findSubInputForValue:(IOHIDValueRef)value {
+    float mag = normalize(IOHIDValueGetIntegerValue(value), rawMin, rawMax);
+    if (mag < -DEAD_ZONE)
+        return self.children[0];
+    else if (mag > DEAD_ZONE)
+        return self.children[1];
+    else
+        return nil;
+}
+
+- (void)notifyEvent:(IOHIDValueRef)value {
+    magnitude = normalize(IOHIDValueGetIntegerValue(value), rawMin, rawMax);
+    [self.children[0] setMagnitude:fabsf(MIN(magnitude, 0))];
+    [self.children[1] setMagnitude:fabsf(MAX(magnitude, 0))];
+    [self.children[0] setActive:magnitude < -DEAD_ZONE];
+    [self.children[1] setActive:magnitude > DEAD_ZONE];
+}
+
+- (float)magnitude {
+    return magnitude;
+}
+
+@end
diff --git a/Classes/NJInputButton.h b/Classes/NJInputButton.h
new file mode 100644 (file)
index 0000000..31cba73
--- /dev/null
@@ -0,0 +1,15 @@
+//
+//  NJInputButton.h
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+#import "NJInput.h"
+
+@interface NJInputButton : NJInput
+
+- (id)initWithName:(NSString *)name idx:(int)idx max:(long)max;
+
+@end
diff --git a/Classes/NJInputButton.m b/Classes/NJInputButton.m
new file mode 100644 (file)
index 0000000..0c3b0ce
--- /dev/null
@@ -0,0 +1,34 @@
+//
+//  NJInputButton.m
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//
+
+#import "NJInputButton.h"
+
+@implementation NJInputButton {
+    long _max;
+}
+
+- (id)initWithName:(NSString *)name idx:(int)idx max:(long)max {
+    if ((self = [super init])) {
+        _max = max;
+        if (name.length)
+            self.name = [NSString stringWithFormat:@"Button %d - %@", idx, name];
+        else
+            self.name = [NSString stringWithFormat:@"Button %d", idx];
+    }
+    return self;
+}
+
+- (id)findSubInputForValue:(IOHIDValueRef)val {
+    return (IOHIDValueGetIntegerValue(val) == _max) ? self : nil;
+}
+
+- (void)notifyEvent:(IOHIDValueRef)value {
+    self.active = IOHIDValueGetIntegerValue(value) == _max;
+    self.magnitude = IOHIDValueGetIntegerValue(value) / (float)_max;
+}
+
+@end
diff --git a/Classes/NJInputHat.h b/Classes/NJInputHat.h
new file mode 100644 (file)
index 0000000..5290d71
--- /dev/null
@@ -0,0 +1,15 @@
+//
+//  NJInputHat.h
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+#import "NJInput.h"
+
+@interface NJInputHat : NJInput
+
+- (id)initWithIndex:(int)index;
+
+@end
diff --git a/Classes/NJInputHat.m b/Classes/NJInputHat.m
new file mode 100644 (file)
index 0000000..e0e127f
--- /dev/null
@@ -0,0 +1,99 @@
+//
+//  NJInputHat.m
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//
+
+#import "NJInputHat.h"
+
+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 NJInputHat
+
+- (id)initWithIndex:(int)index {
+    if ((self = [super init])) {
+        self.children = @[[[NJInput alloc] initWithName:@"Up" base:self],
+                          [[NJInput alloc] initWithName:@"Down" base:self],
+                          [[NJInput alloc] initWithName:@"Left" base:self],
+                          [[NJInput alloc] initWithName:@"Right" base:self]];
+        self.name = [NSString stringWithFormat:@"Hat Switch %d", index];
+    }
+    return self;
+}
+
+- (id)findSubInputForValue:(IOHIDValueRef)value {
+    long parsed = IOHIDValueGetIntegerValue(value);
+    switch (IOHIDElementGetLogicalMax(IOHIDValueGetElement(value))) {
+        case 7: // 8-way switch, 0-7.
+            switch (parsed) {
+                case 0: return self.children[0];
+                case 4: return self.children[1];
+                case 6: return self.children[2];
+                case 2: return self.children[3];
+                default: return nil;
+            }
+        case 8: // 8-way switch, 1-8 (neutral 0).
+            switch (parsed) {
+                case 1: return self.children[0];
+                case 5: return self.children[1];
+                case 7: return self.children[2];
+                case 3: return self.children[3];
+                default: return nil;
+            }
+        case 3: // 4-way switch, 0-3.
+            switch (parsed) {
+                case 0: return self.children[0];
+                case 2: return self.children[1];
+                case 3: return self.children[2];
+                case 1: return self.children[3];
+                default: return nil;
+            }
+        case 4: // 4-way switch, 1-4 (neutral 0).
+            switch (parsed) {
+                case 1: return self.children[0];
+                case 3: return self.children[1];
+                case 4: return self.children[2];
+                case 2: return self.children[3];
+                default: return nil;
+            }
+        default:
+            return nil;
+    }
+}
+
+- (void)notifyEvent:(IOHIDValueRef)value {
+    long parsed = IOHIDValueGetIntegerValue(value);
+    long size = IOHIDElementGetLogicalMax(IOHIDValueGetElement(value));
+    // Skip first row in table if 0 is not neutral.
+    if (size & 1) {
+        parsed++;
+        size++;
+    }
+    BOOL *activechildren = (size == 8) ? active_eightway : active_fourway;
+    for (unsigned i = 0; i < 4; i++) {
+        BOOL active = activechildren[parsed * 4 + i];
+        [self.children[i] setActive:active];
+        [self.children[i] setMagnitude:active];
+    }
+}
+
+@end
diff --git a/Classes/NJInputPathElement.h b/Classes/NJInputPathElement.h
new file mode 100644 (file)
index 0000000..8fe5c65
--- /dev/null
@@ -0,0 +1,9 @@
+#import <Foundation/Foundation.h>
+
+@protocol NJInputPathElement <NSObject>
+
+- (NSArray *)children;
+- (id <NJInputPathElement>) base;
+- (NSString *)name;
+
+@end
diff --git a/Classes/NJKeyInputField.h b/Classes/NJKeyInputField.h
new file mode 100644 (file)
index 0000000..ad026d3
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  NJKeyInputField.h
+//  Enjoyable
+//
+//  Copyright 2013 Joe Wreschnig.
+//
+
+#import <Cocoa/Cocoa.h>
+
+extern CGKeyCode NJKeyInputFieldEmpty;
+
+@protocol NJKeyInputFieldDelegate;
+
+@interface NJKeyInputField : NSTextField
+    // An NJKeyInputField is a NSTextField-like widget that receives
+    // exactly one key press, and displays the name of that key, then
+    // resigns its first responder status. It can also inform a
+    // special delegate when its content changes.
+
++ (NSString *)stringForKeyCode:(CGKeyCode)keyCode;
+    // Give the string name for a virtual key code.
+
+@property (nonatomic, weak) IBOutlet id <NJKeyInputFieldDelegate> keyDelegate;
+
+@property (nonatomic, assign) CGKeyCode keyCode;
+    // The currently displayed key code, or NJKeyInputFieldEmpty if no
+    // key is active.  Changing this will update the display but not
+    // inform the delegate.
+
+@property (nonatomic, readonly) BOOL hasKeyCode;
+    // True if any key is active, false otherwise.
+
+- (void)clear;
+    // Clear the currently active key and call the delegate.
+
+@end
+
+@protocol NJKeyInputFieldDelegate <NSObject>
+
+- (void)keyInputField:(NJKeyInputField *)keyInput
+         didChangeKey:(CGKeyCode)keyCode;
+- (void)keyInputFieldDidClear:(NJKeyInputField *)keyInput;
+
+@end
+
diff --git a/Classes/NJKeyInputField.m b/Classes/NJKeyInputField.m
new file mode 100644 (file)
index 0000000..ec08150
--- /dev/null
@@ -0,0 +1,213 @@
+//
+//  NJKeyInputField.h
+//  Enjoyable
+//
+//  Copyright 2013 Joe Wreschnig.
+//
+
+#import "NJKeyInputField.h"
+
+CGKeyCode NJKeyInputFieldEmpty = 0xFFFF;
+
+@implementation NJKeyInputField
+
+- (id)initWithFrame:(NSRect)frameRect {
+    if ((self = [super initWithFrame:frameRect])) {
+        self.alignment = NSCenterTextAlignment;
+        [self setEditable:NO];
+        [self setSelectable:NO];
+    }
+    return self;
+}
+
+- (void)clear {
+    self.keyCode = NJKeyInputFieldEmpty;
+    [self.keyDelegate keyInputFieldDidClear:self];
+    [self resignIfFirstResponder];
+}
+
+- (BOOL)hasKeyCode {
+    return self.keyCode != NJKeyInputFieldEmpty;
+}
+
++ (NSString *)stringForKeyCode:(CGKeyCode)keyCode {
+    switch (keyCode) {
+        case 0xffff: return @"";
+        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 0x36: return @"Right Command";
+        case 0x37: return @"Left Command";
+        case 0x38: return @"Left Shift";
+        case 0x39: return @"Caps Lock";
+        case 0x3a: return @"Left Option";
+        case 0x3b: return @"Left Control";
+        case 0x3c: return @"Right Shift";
+        case 0x3d: return @"Right Option";
+        case 0x3e: return @"Right Control";
+            
+        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";
+        default:
+            return [[NSString alloc] initWithFormat:@"Key 0x%x", keyCode];
+    }
+}
+
+- (BOOL)acceptsFirstResponder {
+    return self.isEnabled;
+}
+
+- (BOOL)becomeFirstResponder {
+    self.backgroundColor = NSColor.selectedTextBackgroundColor;
+    return [super becomeFirstResponder];
+}
+
+- (BOOL)resignFirstResponder {
+    self.backgroundColor = NSColor.textBackgroundColor;
+    return [super resignFirstResponder];
+}
+
+- (void)setKeyCode:(CGKeyCode)keyCode {
+    _keyCode = keyCode;
+    self.stringValue = [NJKeyInputField stringForKeyCode:keyCode];
+}
+
+- (void)keyDown:(NSEvent *)theEvent {
+    if (!theEvent.isARepeat) {
+        if ((theEvent.modifierFlags & NSAlternateKeyMask)
+            && theEvent.keyCode == 0x33) {
+            // Allow Alt+Backspace to clear the field.
+            self.keyCode = NJKeyInputFieldEmpty;
+            [self.keyDelegate keyInputFieldDidClear:self];
+        } else if ((theEvent.modifierFlags & NSAlternateKeyMask)
+                && theEvent.keyCode == 0x35) {
+                // Allow Alt+Escape to cancel.
+            ;
+        } else {
+            self.keyCode = theEvent.keyCode;
+            [self.keyDelegate keyInputField:self didChangeKey:_keyCode];
+        }
+        [self resignIfFirstResponder];
+    }
+}
+
+- (void)mouseDown:(NSEvent *)theEvent {
+    if (self.acceptsFirstResponder)
+        [self.window makeFirstResponder:self];
+}
+
+- (void)flagsChanged:(NSEvent *)theEvent {
+    // Many keys are only available on MacBook keyboards by using the
+    // Fn modifier key (e.g. Fn+Left for Home), so delay processing
+    // modifiers until the up event is received in order to let the
+    // user type these virtual keys. However, there is no actual event
+    // for modifier key up - so detect it by checking to see if any
+    // modifiers are still down.
+    if (!(theEvent.modifierFlags & NSDeviceIndependentModifierFlagsMask)) {
+        self.keyCode = theEvent.keyCode;
+        [self.keyDelegate keyInputField:self didChangeKey:_keyCode];
+    }
+}
+
+@end
diff --git a/Classes/NJMapping.h b/Classes/NJMapping.h
new file mode 100644 (file)
index 0000000..5653745
--- /dev/null
@@ -0,0 +1,25 @@
+//
+//  NJMapping.h
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+@class NJOutput;
+@class NJInput;
+
+@interface NJMapping : NSObject
+
+@property (nonatomic, copy) NSString *name;
+@property (nonatomic, readonly) NSMutableDictionary *entries;
+
+- (id)initWithName:(NSString *)name;
+- (NJOutput *)objectForKeyedSubscript:(NJInput *)input;
+- (void)setObject:(NJOutput *)output forKeyedSubscript:(NJInput *)input;
+- (NSDictionary *)serialize;
+- (BOOL)writeToURL:(NSURL *)url error:(NSError **)error;
+
++ (id)mappingWithContentsOfURL:(NSURL *)url mappings:(NSArray *)mappings error:(NSError **)error;
+
+@end
diff --git a/Classes/NJMapping.m b/Classes/NJMapping.m
new file mode 100644 (file)
index 0000000..8cc5724
--- /dev/null
@@ -0,0 +1,91 @@
+//
+//  NJMapping.m
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//
+
+#import "NJMapping.h"
+
+#import "NJInput.h"
+#import "NJOutput.h"
+
+@implementation NJMapping
+
+- (id)initWithName:(NSString *)name {
+    if ((self = [super init])) {
+        self.name = name ? name : @"Untitled";
+        _entries = [[NSMutableDictionary alloc] init];
+    }
+    return self;
+}
+
+- (NJOutput *)objectForKeyedSubscript:(NJInput *)input {
+    return input ? _entries[input.uid] : nil;
+}
+
+- (void)setObject:(NJOutput *)output forKeyedSubscript:(NJInput *)input {
+    if (input) {
+        if (output)
+            _entries[input.uid] = output;
+        else
+            [_entries removeObjectForKey:input.uid];
+    }
+}
+
+- (NSDictionary *)serialize {
+    NSMutableDictionary *entries = [[NSMutableDictionary alloc] initWithCapacity:_entries.count];
+    for (id key in _entries) {
+        id serialized = [_entries[key] serialize];
+        if (serialized)
+            entries[key] = serialized;
+    }
+    return @{ @"name": _name, @"entries": entries };
+}
+
+- (BOOL)writeToURL:(NSURL *)url error:(NSError **)error {
+    [NSProcessInfo.processInfo disableSuddenTermination];
+    NSDictionary *serialization = [self serialize];
+    NSData *json = [NSJSONSerialization dataWithJSONObject:serialization
+                                                   options:NSJSONWritingPrettyPrinted
+                                                     error:error];
+    BOOL success = json && [json writeToURL:url options:NSDataWritingAtomic error:error];
+    [NSProcessInfo.processInfo enableSuddenTermination];
+    return success;
+}
+
++ (id)mappingWithContentsOfURL:(NSURL *)url mappings:(NSArray *)mappings error:(NSError **)error {
+    NSInputStream *stream = [NSInputStream inputStreamWithURL:url];
+    [stream open];
+    NSDictionary *serialization = stream && !*error
+        ? [NSJSONSerialization JSONObjectWithStream:stream options:0 error:error]
+        : nil;
+    [stream close];
+    
+    if (!serialization && error)
+        return nil;
+    
+    if (!([serialization isKindOfClass:NSDictionary.class]
+          && [serialization[@"name"] isKindOfClass:NSString.class]
+          && [serialization[@"entries"] isKindOfClass:NSDictionary.class])) {
+        *error = [NSError errorWithDomain:@"Enjoyable"
+                                     code:0
+                              description:@"This isn't a valid mapping file."];
+        return nil;
+    }
+    
+    NSDictionary *entries = serialization[@"entries"];
+    NJMapping *mapping = [[NJMapping alloc] initWithName:serialization[@"name"]];
+    for (id key in entries) {
+        NSDictionary *value = entries[key];
+        if ([key isKindOfClass:NSString.class]) {
+            NJOutput *output = [NJOutput outputDeserialize:value
+                                              withMappings:mappings];
+            if (output)
+                mapping.entries[key] = output;
+        }
+    }
+    return mapping;
+}
+
+@end
diff --git a/Classes/NJMappingsController.h b/Classes/NJMappingsController.h
new file mode 100644 (file)
index 0000000..b252904
--- /dev/null
@@ -0,0 +1,46 @@
+//
+//  NJMappingsController.h
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+@class NJMapping;
+@class NJOutputController;
+
+@interface NJMappingsController : NSObject <NSTableViewDataSource,
+                                            NSTableViewDelegate,
+                                            NSOpenSavePanelDelegate,
+                                            NSPopoverDelegate,
+                                            NSFastEnumeration>
+{
+    IBOutlet NSButton *removeButton;
+    IBOutlet NSTableView *tableView;
+    IBOutlet NJOutputController *outputController;
+    IBOutlet NSButton *popoverActivate;
+    IBOutlet NSPopover *popover;
+    IBOutlet NSButton *moveUp;
+    IBOutlet NSButton *moveDown;
+}
+
+@property (nonatomic, readonly) NJMapping *currentMapping;
+@property (nonatomic, readonly) NSArray *mappings;
+
+- (NJMapping *)objectForKeyedSubscript:(NSString *)name;
+- (NJMapping *)objectAtIndexedSubscript:(NSUInteger)idx;
+- (void)addMappingWithContentsOfURL:(NSURL *)url;
+- (void)activateMapping:(NJMapping *)mapping;
+- (void)activateMappingForProcess:(NSString *)processName;
+- (void)save;
+- (void)load;
+
+- (IBAction)mappingPressed:(id)sender;
+- (IBAction)addPressed:(id)sender;
+- (IBAction)removePressed:(id)sender;
+- (IBAction)moveUpPressed:(id)sender;
+- (IBAction)moveDownPressed:(id)sender;
+- (IBAction)importPressed:(id)sender;
+- (IBAction)exportPressed:(id)sender;
+
+@end
diff --git a/Classes/NJMappingsController.m b/Classes/NJMappingsController.m
new file mode 100644 (file)
index 0000000..ea8137f
--- /dev/null
@@ -0,0 +1,436 @@
+//
+//  NJMappingsController.m
+//  Enjoy
+//
+//  Created by Sam McCall on 4/05/09.
+//
+
+#import "NJMappingsController.h"
+
+#import "NJMapping.h"
+#import "NJMappingsController.h"
+#import "NJOutput.h"
+#import "NJOutputController.h"
+#import "NJEvents.h"
+
+#define PB_ROW @"com.yukkurigames.Enjoyable.MappingRow"
+
+@implementation NJMappingsController {
+    NSMutableArray *_mappings;
+    NJMapping *manualMapping;
+    NSString *draggingName;
+}
+
+- (id)init {
+    if ((self = [super init])) {
+        _mappings = [[NSMutableArray alloc] init];
+        _currentMapping = [[NJMapping alloc] initWithName:@"(default)"];
+        manualMapping = _currentMapping;
+        [_mappings addObject:_currentMapping];
+    }
+    return self;
+}
+
+- (void)awakeFromNib {
+    [tableView registerForDraggedTypes:@[PB_ROW, NSURLPboardType]];
+    [tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
+}
+
+- (NJMapping *)objectForKeyedSubscript:(NSString *)name {
+    for (NJMapping *mapping in _mappings)
+        if ([name isEqualToString:mapping.name])
+            return mapping;
+    return nil;
+}
+
+- (NJMapping *)objectAtIndexedSubscript:(NSUInteger)idx {
+    return idx < _mappings.count ? _mappings[idx] : nil;
+}
+
+- (void)mappingsChanged {
+    [self save];
+    [tableView reloadData];
+    popoverActivate.title = _currentMapping.name;
+    [self updateInterfaceForCurrentMapping];
+    [NSNotificationCenter.defaultCenter
+        postNotificationName:NJEventMappingListChanged
+        object:_mappings];
+}
+
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
+                                  objects:(__unsafe_unretained id [])buffer
+                                    count:(NSUInteger)len {
+    return [_mappings countByEnumeratingWithState:state
+                                          objects:buffer
+                                            count:len];
+}
+
+- (void)activateMappingForProcess:(NSString *)processName {
+    if ([manualMapping.name.lowercaseString isEqualToString:@"@application"]) {
+        manualMapping.name = processName;
+        [self mappingsChanged];
+    } else {
+        NJMapping *oldMapping = manualMapping;
+        NJMapping *newMapping = self[processName];
+        if (!newMapping)
+            newMapping = oldMapping;
+        if (newMapping != _currentMapping)
+            [self activateMapping:newMapping];
+        manualMapping = oldMapping;
+    }
+}
+
+- (void)updateInterfaceForCurrentMapping {
+    NSUInteger selected = [_mappings indexOfObject:_currentMapping];
+    [removeButton setEnabled:selected != 0];
+    [moveDown setEnabled:selected && selected != _mappings.count - 1];
+    [moveUp setEnabled:selected > 1];
+    popoverActivate.title = _currentMapping.name;
+    [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selected] byExtendingSelection:NO];
+    [NSUserDefaults.standardUserDefaults setInteger:selected forKey:@"selected"];
+}
+
+- (void)activateMapping:(NJMapping *)mapping {
+    if (!mapping)
+        mapping = manualMapping;
+    if (mapping == _currentMapping)
+        return;
+    NSLog(@"Switching to mapping %@.", mapping.name);
+    manualMapping = mapping;
+    _currentMapping = mapping;
+    [self updateInterfaceForCurrentMapping];
+    [outputController loadCurrent];
+    [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingChanged
+                                                      object:_currentMapping];
+}
+
+- (IBAction)addPressed:(id)sender {
+    NJMapping *newMapping = [[NJMapping alloc] initWithName:@"Untitled"];
+    [_mappings addObject:newMapping];
+    [self activateMapping:newMapping];
+    [self mappingsChanged];
+    [tableView editColumn:0 row:_mappings.count - 1 withEvent:nil select:YES];
+}
+
+- (IBAction)removePressed:(id)sender {
+    if (tableView.selectedRow == 0)
+        return;
+    
+    NSInteger selectedRow = tableView.selectedRow;
+    [_mappings removeObjectAtIndex:selectedRow];
+    [self activateMapping:_mappings[MIN(selectedRow, _mappings.count - 1)]];
+    [self mappingsChanged];
+}
+
+- (void)tableViewSelectionDidChange:(NSNotification *)notify {
+    [self activateMapping:self[tableView.selectedRow]];
+}
+
+- (id)tableView:(NSTableView *)view objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)index {
+    return self[index].name;
+}
+
+- (void)tableView:(NSTableView *)view
+   setObjectValue:(NSString *)obj
+   forTableColumn:(NSTableColumn *)col
+              row:(NSInteger)index {
+    self[index].name = obj;
+    [self mappingsChanged];
+}
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
+    return _mappings.count;
+}
+
+- (BOOL)tableView:(NSTableView *)view shouldEditTableColumn:(NSTableColumn *)column row:(NSInteger)index {
+    return YES;
+}
+
+- (void)save {
+    NSLog(@"Saving mappings to defaults.");
+    NSMutableArray *ary = [[NSMutableArray alloc] initWithCapacity:_mappings.count];
+    for (NJMapping *mapping in _mappings)
+        [ary addObject:[mapping serialize]];
+    [NSUserDefaults.standardUserDefaults setObject:ary forKey:@"mappings"];
+}
+
+- (void)load {
+    NSUInteger selected = [NSUserDefaults.standardUserDefaults integerForKey:@"selected"];
+    NSArray *mappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"];
+    [self loadAllFrom:mappings andActivate:selected];
+}
+
+- (void)loadAllFrom:(NSArray *)storedMappings andActivate:(NSUInteger)selected {
+    NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count];
+
+    // have to do two passes in case mapping1 refers to mapping2 via a NJOutputMapping
+    for (NSDictionary *storedMapping in storedMappings) {
+        NJMapping *mapping = [[NJMapping alloc] initWithName:storedMapping[@"name"]];
+        [newMappings addObject:mapping];
+    }
+
+    for (unsigned i = 0; i < storedMappings.count; ++i) {
+        NSDictionary *entries = storedMappings[i][@"entries"];
+        NJMapping *mapping = newMappings[i];
+        for (id key in entries) {
+            NJOutput *output = [NJOutput outputDeserialize:entries[key]
+                                              withMappings:newMappings];
+            if (output)
+                mapping.entries[key] = output;
+        }
+    }
+    
+    if (newMappings.count) {
+        _mappings = newMappings;
+        if (selected >= newMappings.count)
+            selected = 0;
+        [self activateMapping:_mappings[selected]];
+        [self mappingsChanged];
+    }
+}
+
+- (NJMapping *)mappingWithURL:(NSURL *)url error:(NSError **)error {
+    NSInputStream *stream = [NSInputStream inputStreamWithURL:url];
+    [stream open];
+    NSDictionary *serialization = !*error
+        ? [NSJSONSerialization JSONObjectWithStream:stream options:0 error:error]
+        : nil;
+    [stream close];
+    
+    if (!([serialization isKindOfClass:NSDictionary.class]
+          && [serialization[@"name"] isKindOfClass:NSString.class]
+          && [serialization[@"entries"] isKindOfClass:NSDictionary.class])) {
+        *error = [NSError errorWithDomain:@"Enjoyable"
+                                     code:0
+                              description:@"This isn't a valid mapping file."];
+        return nil;
+    }
+
+    NSDictionary *entries = serialization[@"entries"];
+    NJMapping *mapping = [[NJMapping alloc] initWithName:serialization[@"name"]];
+    for (id key in entries) {
+        NSDictionary *value = entries[key];
+        if ([key isKindOfClass:NSString.class]) {
+            NJOutput *output = [NJOutput outputDeserialize:value
+                                              withMappings:_mappings];
+            if (output)
+                mapping.entries[key] = output;
+        }
+    }
+    return mapping;
+}
+
+- (void)addMappingWithContentsOfURL:(NSURL *)url {
+    NSWindow *window = popoverActivate.window;
+    NSError *error;
+    NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url
+                                                    mappings:_mappings
+                                                       error:&error];
+    
+    if (mapping && !error) {
+        BOOL conflict = NO;
+        NJMapping *mergeInto = self[mapping.name];
+        for (id key in mapping.entries) {
+            if (mergeInto.entries[key]
+                && ![mergeInto.entries[key] isEqual:mapping.entries[key]]) {
+                conflict = YES;
+                break;
+            }
+        }
+        
+        if (conflict) {
+            NSAlert *conflictAlert = [[NSAlert alloc] init];
+            conflictAlert.messageText = @"Replace existing mappings?";
+            conflictAlert.informativeText =
+            [NSString stringWithFormat:
+             @"This file contains inputs you've already mapped in \"%@\". Do you "
+             @"want to merge them and replace your existing mappings, or import this "
+             @"as a separate mapping?", mapping.name];
+            [conflictAlert addButtonWithTitle:@"Merge"];
+            [conflictAlert addButtonWithTitle:@"Cancel"];
+            [conflictAlert addButtonWithTitle:@"New Mapping"];
+            NSInteger res = [conflictAlert runModal];
+            if (res == NSAlertSecondButtonReturn)
+                return;
+            else if (res == NSAlertThirdButtonReturn)
+                mergeInto = nil;
+        }
+        
+        if (mergeInto) {
+            [mergeInto.entries addEntriesFromDictionary:mapping.entries];
+            mapping = mergeInto;
+        } else {
+            [_mappings addObject:mapping];
+        }
+        
+        [self activateMapping:mapping];
+        [self mappingsChanged];
+        
+        if (conflict && !mergeInto) {
+            [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:_mappings.count - 1] byExtendingSelection:NO];
+            [tableView editColumn:0 row:_mappings.count - 1 withEvent:nil select:YES];
+        }
+    }
+    
+    if (error) {
+        [window presentError:error
+              modalForWindow:window
+                    delegate:nil
+          didPresentSelector:nil
+                 contextInfo:nil];
+    }
+}
+
+- (void)importPressed:(id)sender {
+    NSOpenPanel *panel = [NSOpenPanel openPanel];
+    panel.allowedFileTypes = @[ @"enjoyable", @"json", @"txt" ];
+    NSWindow *window = NSApplication.sharedApplication.keyWindow;
+    [panel beginSheetModalForWindow:window
+                  completionHandler:^(NSInteger result) {
+                      if (result != NSFileHandlingPanelOKButton)
+                          return;
+                      [panel close];
+                      [self addMappingWithContentsOfURL:panel.URL];
+                  }];
+    
+}
+
+- (void)exportPressed:(id)sender {
+    NSSavePanel *panel = [NSSavePanel savePanel];
+    panel.allowedFileTypes = @[ @"enjoyable" ];
+    NJMapping *mapping = _currentMapping;
+    panel.nameFieldStringValue = [mapping.name stringByFixingPathComponent];
+    NSWindow *window = NSApplication.sharedApplication.keyWindow;
+    [panel beginSheetModalForWindow:window
+                  completionHandler:^(NSInteger result) {
+                      if (result != NSFileHandlingPanelOKButton)
+                          return;
+                      [panel close];
+                      NSError *error;
+                      [mapping writeToURL:panel.URL error:&error];
+                      if (error) {
+                          [window presentError:error
+                                modalForWindow:window
+                                      delegate:nil
+                            didPresentSelector:nil
+                                   contextInfo:nil];
+                      }
+                  }];
+}
+
+- (IBAction)mappingPressed:(id)sender {
+    [popover showRelativeToRect:popoverActivate.bounds
+                         ofView:popoverActivate
+                  preferredEdge:NSMinXEdge];
+}
+
+- (void)popoverWillShow:(NSNotification *)notification {
+    popoverActivate.state = NSOnState;
+}
+
+- (void)popoverWillClose:(NSNotification *)notification {
+    popoverActivate.state = NSOffState;
+}
+
+- (IBAction)moveUpPressed:(id)sender {
+    NSUInteger idx = [_mappings indexOfObject:_currentMapping];
+    if (idx > 1 && idx != NSNotFound) {
+        [_mappings exchangeObjectAtIndex:idx withObjectAtIndex:idx - 1];
+        [self mappingsChanged];
+    }
+}
+
+- (IBAction)moveDownPressed:(id)sender {
+    NSUInteger idx = [_mappings indexOfObject:_currentMapping];
+    if (idx < _mappings.count - 1) {
+        [_mappings exchangeObjectAtIndex:idx withObjectAtIndex:idx + 1];
+        [self mappingsChanged];
+    }
+}
+
+- (BOOL)tableView:(NSTableView *)tableView_
+       acceptDrop:(id <NSDraggingInfo>)info
+              row:(NSInteger)row
+    dropOperation:(NSTableViewDropOperation)dropOperation {
+    NSPasteboard *pboard = [info draggingPasteboard];
+    if ([pboard.types containsObject:PB_ROW]) {
+        NSString *value = [pboard stringForType:PB_ROW];
+        NSUInteger srcRow = [value intValue];
+        [_mappings moveObjectAtIndex:srcRow toIndex:row];
+        [self mappingsChanged];
+        return YES;
+    } else if ([pboard.types containsObject:NSURLPboardType]) {
+        NSURL *url = [NSURL URLFromPasteboard:pboard];
+        NSError *error;
+        NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url
+                                                        mappings:_mappings
+                                                           error:&error];
+        if (error) {
+            [tableView_ presentError:error];
+            return NO;
+        } else {
+            [_mappings insertObject:mapping atIndex:row];
+            [self mappingsChanged];
+            return YES;
+        }
+    } else {
+        return NO;
+    }
+}
+
+- (NSDragOperation)tableView:(NSTableView *)tableView_
+                validateDrop:(id <NSDraggingInfo>)info
+                 proposedRow:(NSInteger)row
+       proposedDropOperation:(NSTableViewDropOperation)dropOperation {
+    NSPasteboard *pboard = [info draggingPasteboard];
+    if ([pboard.types containsObject:PB_ROW]) {
+        [tableView_ setDropRow:MAX(1, row) dropOperation:NSTableViewDropAbove];
+        return NSDragOperationMove;
+    } else if ([pboard.types containsObject:NSURLPboardType]) {
+        NSURL *url = [NSURL URLFromPasteboard:pboard];
+        if ([url.pathExtension isEqualToString:@"enjoyable"]) {
+            [tableView_ setDropRow:MAX(1, row) dropOperation:NSTableViewDropAbove];
+            return NSDragOperationCopy;
+        } else {
+            return NSDragOperationNone;
+        }
+    } else {
+        return NSDragOperationNone;
+    }
+}
+
+- (NSArray *)tableView:(NSTableView *)tableView_
+namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
+forDraggedRowsWithIndexes:(NSIndexSet *)indexSet {
+    NJMapping *toSave = self[indexSet.firstIndex];
+    NSString *filename = [[toSave.name stringByFixingPathComponent]
+                          stringByAppendingPathExtension:@"enjoyable"];
+    NSURL *dst = [dropDestination URLByAppendingPathComponent:filename];
+    dst = [NSFileManager.defaultManager generateUniqueURLWithBase:dst];     
+    NSError *error;
+    if (![toSave writeToURL:dst error:&error]) {
+        [tableView_ presentError:error];
+        return @[];
+    } else {
+        return @[dst.lastPathComponent];
+    }
+}
+
+- (BOOL)tableView:(NSTableView *)tableView_
+writeRowsWithIndexes:(NSIndexSet *)rowIndexes
+     toPasteboard:(NSPasteboard *)pboard {
+    if (rowIndexes.count == 1 && rowIndexes.firstIndex != 0) {
+        [pboard declareTypes:@[PB_ROW, NSFilesPromisePboardType] owner:nil];
+        [pboard setString:@(rowIndexes.firstIndex).stringValue forType:PB_ROW];
+        [pboard setPropertyList:@[@"enjoyable"] forType:NSFilesPromisePboardType];
+        return YES;
+    } else if (rowIndexes.count == 1 && rowIndexes.firstIndex == 0) {
+        [pboard declareTypes:@[NSFilesPromisePboardType] owner:nil];
+        [pboard setPropertyList:@[@"enjoyable"] forType:NSFilesPromisePboardType];
+        return YES;
+    } else {
+        return NO;
+    }
+}
+
+@end
diff --git a/Classes/NJOutput.h b/Classes/NJOutput.h
new file mode 100644 (file)
index 0000000..b82c4e7
--- /dev/null
@@ -0,0 +1,26 @@
+//
+//  NJOutput.h
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+@class NJDeviceController;
+
+@interface NJOutput : NSObject
+
+@property (nonatomic, assign) float magnitude;
+@property (nonatomic, assign) BOOL running;
+@property (nonatomic, readonly) BOOL isContinuous;
+
+- (void)trigger;
+- (void)untrigger;
+- (BOOL)update:(NJDeviceController *)jc;
+
+- (NSDictionary *)serialize;
++ (NJOutput *)outputDeserialize:(NSDictionary *)serialization
+                   withMappings:(NSArray *)mappings;
++ (NSString *)serializationCode;
+
+@end
diff --git a/Classes/NJOutput.m b/Classes/NJOutput.m
new file mode 100644 (file)
index 0000000..409b3e6
--- /dev/null
@@ -0,0 +1,87 @@
+//
+//  NJOutput.m
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//
+
+#import "NJOutput.h"
+
+#import "NJOutputKeyPress.h"
+#import "NJOutputMapping.h"
+#import "NJOutputMouseMove.h"
+#import "NJOutputMouseButton.h"
+#import "NJOutputMouseScroll.h"
+
+@implementation NJOutput {
+    BOOL running;
+}
+
++ (NSString *)serializationCode {
+    [self doesNotRecognizeSelector:_cmd];
+    return nil;
+}
+
+- (NSDictionary *)serialize {
+    [self doesNotRecognizeSelector:_cmd];
+    return nil;    
+}
+
+- (BOOL)isEqual:(id)object {
+    return [object isKindOfClass:NJOutput.class]
+        && [[self serialize] isEqual:[object serialize]];
+}
+
+- (NSUInteger)hash {
+    return [[self serialize] hash];
+}
+
++ (NJOutput *)outputDeserialize:(NSDictionary *)serialization
+                  withMappings:(NSArray *)mappings {
+    // Don't crash loading old/bad mappings (but don't load them either).
+    if (![serialization isKindOfClass:NSDictionary.class])
+        return nil;
+    NSString *type = serialization[@"type"];
+    for (Class cls in @[NJOutputKeyPress.class,
+                        NJOutputMapping.class,
+                        NJOutputMouseMove.class,
+                        NJOutputMouseButton.class,
+                        NJOutputMouseScroll.class
+         ]) {
+        if ([type isEqualToString:cls.serializationCode])
+            return [cls outputDeserialize:serialization withMappings:mappings];
+    }
+    
+    return nil;
+}
+
+- (void)trigger {
+}
+
+- (void)untrigger {
+}
+
+- (BOOL)update:(NJDeviceController *)jc {
+    return NO;
+}
+
+- (BOOL)isContinuous {
+    return NO;
+}
+
+- (BOOL)running {
+    return running;
+}
+
+- (void)setRunning:(BOOL)newRunning {
+    if (running != newRunning) {
+        running = newRunning;
+        if (running)
+            [self trigger];
+        else
+            [self untrigger];
+    }
+}
+
+
+@end
diff --git a/Classes/NJOutputController.h b/Classes/NJOutputController.h
new file mode 100644 (file)
index 0000000..f8052e7
--- /dev/null
@@ -0,0 +1,42 @@
+//
+//  NJOutputController.h
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+#import "NJKeyInputField.h"
+
+@class NJMappingsController;
+@class NJDeviceController;
+@class NJOutput;
+@class NJOutputMouseMove;
+
+@interface NJOutputController : NSObject <NJKeyInputFieldDelegate> {
+    IBOutlet NJKeyInputField *keyInput;
+    IBOutlet NSMatrix *radioButtons;
+    IBOutlet NSSegmentedControl *mouseDirSelect;
+    IBOutlet NSSlider *mouseSpeedSlider;
+    IBOutlet NSSegmentedControl *mouseBtnSelect;
+    IBOutlet NSSegmentedControl *scrollDirSelect;
+    IBOutlet NSSlider *scrollSpeedSlider;
+    IBOutlet NSTextField *title;
+    IBOutlet NSPopUpButton *mappingPopup;
+    IBOutlet NJMappingsController *mappingsController;
+    IBOutlet NJDeviceController *inputController;
+}
+
+@property (assign) BOOL enabled;
+
+- (void)loadCurrent;
+- (IBAction)radioChanged:(id)sender;
+- (IBAction)mdirChanged:(id)sender;
+- (IBAction)mbtnChanged:(id)sender;
+- (IBAction)sdirChanged:(id)sender;
+- (IBAction)mouseSpeedChanged:(id)sender;
+- (IBAction)scrollSpeedChanged:(id)sender;
+
+- (void)focusKey;
+
+@end
diff --git a/Classes/NJOutputController.m b/Classes/NJOutputController.m
new file mode 100644 (file)
index 0000000..bcafe0d
--- /dev/null
@@ -0,0 +1,283 @@
+//
+//  NJOutputController.m
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//
+
+#import "NJOutputController.h"
+
+#import "NJMappingsController.h"
+#import "NJMapping.h"
+#import "NJInput.h"
+#import "NJEvents.h"
+#import "NJDeviceController.h"
+#import "NJKeyInputField.h"
+#import "NJOutputMapping.h"
+#import "NJOutputController.h"
+#import "NJOutputKeyPress.h"
+#import "NJOutputMouseButton.h"
+#import "NJOutputMouseMove.h"
+#import "NJOutputMouseScroll.h"
+
+@implementation NJOutputController
+
+- (id)init {
+    if ((self = [super init])) {
+        [NSNotificationCenter.defaultCenter
+            addObserver:self
+            selector:@selector(mappingListDidChange:)
+            name:NJEventMappingListChanged
+            object:nil];
+    }
+    return self;
+}
+
+- (void)dealloc {
+    [NSNotificationCenter.defaultCenter removeObserver:self];
+}
+
+- (void)cleanUpInterface {
+    NSInteger row = radioButtons.selectedRow;
+    
+    if (row != 1) {
+        keyInput.keyCode = NJKeyInputFieldEmpty;
+        [keyInput resignIfFirstResponder];
+    }
+    
+    if (row != 2) {
+        [mappingPopup selectItemAtIndex:-1];
+        [mappingPopup resignIfFirstResponder];
+    } else if (!mappingPopup.selectedItem)
+        [mappingPopup selectItemAtIndex:0];
+    
+    if (row != 3) {
+        mouseDirSelect.selectedSegment = -1;
+        mouseSpeedSlider.floatValue = mouseSpeedSlider.minValue;
+        [mouseDirSelect resignIfFirstResponder];
+    } else {
+        if (mouseDirSelect.selectedSegment == -1)
+            mouseDirSelect.selectedSegment = 0;
+        if (!mouseSpeedSlider.floatValue)
+            mouseSpeedSlider.floatValue = 4;
+    }
+    
+    if (row != 4) {
+        mouseBtnSelect.selectedSegment = -1;
+        [mouseBtnSelect resignIfFirstResponder];
+    } else if (mouseBtnSelect.selectedSegment == -1)
+        mouseBtnSelect.selectedSegment = 0;
+    
+    if (row != 5) {
+        scrollDirSelect.selectedSegment = -1;
+        scrollSpeedSlider.floatValue = scrollSpeedSlider.minValue;
+        [scrollDirSelect resignIfFirstResponder];
+    } else {
+        if (scrollDirSelect.selectedSegment == -1)
+            scrollDirSelect.selectedSegment = 0;
+        if (scrollDirSelect.selectedSegment < 2
+            && !scrollSpeedSlider.floatValue)
+            scrollSpeedSlider.floatValue = 15.f;
+        else if (scrollDirSelect.selectedSegment >= 2
+                 && scrollSpeedSlider.floatValue)
+            scrollSpeedSlider.floatValue = scrollSpeedSlider.minValue;
+    }
+        
+}
+
+- (IBAction)radioChanged:(NSView *)sender {
+    [sender.window makeFirstResponder:sender];
+    if (radioButtons.selectedRow == 1)
+        [keyInput.window makeFirstResponder:keyInput];
+    [self commit];
+}
+
+- (void)keyInputField:(NJKeyInputField *)keyInput didChangeKey:(CGKeyCode)keyCode {
+    [radioButtons selectCellAtRow:1 column:0];
+    [radioButtons.window makeFirstResponder:radioButtons];
+    [self commit];
+}
+
+- (void)keyInputFieldDidClear:(NJKeyInputField *)keyInput {
+    [radioButtons selectCellAtRow:0 column:0];
+    [self commit];
+}
+
+- (void)mappingChosen:(id)sender {
+    [radioButtons selectCellAtRow:2 column:0];
+    [mappingPopup.window makeFirstResponder:mappingPopup];
+    [self commit];
+}
+
+- (void)mdirChanged:(NSView *)sender {
+    [radioButtons selectCellAtRow:3 column:0];
+    [sender.window makeFirstResponder:sender];
+    [self commit];
+}
+
+- (void)mouseSpeedChanged:(NSSlider *)sender {
+    [radioButtons selectCellAtRow:3 column:0];
+    [sender.window makeFirstResponder:sender];
+    [self commit];
+}
+
+- (void)mbtnChanged:(NSView *)sender {
+    [radioButtons selectCellAtRow:4 column:0];
+    [sender.window makeFirstResponder:sender];
+    [self commit];
+}
+
+- (void)sdirChanged:(NSView *)sender {
+    [radioButtons selectCellAtRow:5 column:0];
+    [sender.window makeFirstResponder:sender];
+    [self commit];
+}
+
+- (void)scrollSpeedChanged:(NSSlider *)sender {
+    [radioButtons selectCellAtRow:5 column:0];
+    [sender.window makeFirstResponder:sender];
+    if (!sender.floatValue && scrollDirSelect.selectedSegment < 2)
+        scrollDirSelect.selectedSegment += 2;
+    else if (sender.floatValue && scrollDirSelect.selectedSegment >= 2)
+        scrollDirSelect.selectedSegment -= 2;
+    [self commit];
+}
+
+- (NJOutput *)currentOutput {
+    return mappingsController.currentMapping[inputController.selectedInput];
+}
+
+- (NJOutput *)makeOutput {
+    switch (radioButtons.selectedRow) {
+        case 0:
+            return nil;
+        case 1:
+            if (keyInput.hasKeyCode) {
+                NJOutputKeyPress *k = [[NJOutputKeyPress alloc] init];
+                k.vk = keyInput.keyCode;
+                return k;
+            } else {
+                return nil;
+            }
+            break;
+        case 2: {
+            NJOutputMapping *c = [[NJOutputMapping alloc] init];
+            c.mapping = mappingsController[mappingPopup.indexOfSelectedItem];
+            return c;
+        }
+        case 3: {
+            NJOutputMouseMove *mm = [[NJOutputMouseMove alloc] init];
+            mm.axis = mouseDirSelect.selectedSegment;
+            mm.speed = mouseSpeedSlider.floatValue;
+            return mm;
+        }
+        case 4: {
+            NJOutputMouseButton *mb = [[NJOutputMouseButton alloc] init];
+            mb.button = mouseBtnSelect.selectedSegment == 0 ? kCGMouseButtonLeft : kCGMouseButtonRight;
+            return mb;
+        }
+        case 5: {
+            NJOutputMouseScroll *ms = [[NJOutputMouseScroll alloc] init];
+            ms.direction = (scrollDirSelect.selectedSegment & 1) ? 1 : -1;
+            ms.speed = scrollDirSelect.selectedSegment < 2
+                ? scrollSpeedSlider.floatValue
+                : 0.f;
+            return ms;
+        }
+        default:
+            return nil;
+    }
+}
+
+- (void)commit {
+    [self cleanUpInterface];
+    mappingsController.currentMapping[inputController.selectedInput] = [self makeOutput];
+    [mappingsController save];
+}
+
+- (BOOL)enabled {
+    return [radioButtons isEnabled];
+}
+
+- (void)setEnabled:(BOOL)enabled {
+    [radioButtons setEnabled:enabled];
+    [keyInput setEnabled:enabled];
+    [mappingPopup setEnabled:enabled];
+    [mouseDirSelect setEnabled:enabled];
+    [mouseSpeedSlider setEnabled:enabled];
+    [mouseBtnSelect setEnabled:enabled];
+    [scrollDirSelect setEnabled:enabled];
+    [scrollSpeedSlider setEnabled:enabled];
+}
+
+- (void)loadOutput:(NJOutput *)output forInput:(NJInput *)input {
+    if (!input) {
+        self.enabled = NO;
+        title.stringValue = @"";
+    } else {
+        self.enabled = YES;
+        NSString *inpFullName = input.name;
+        for (id <NJInputPathElement> cur = input.base; cur; cur = cur.base) {
+            inpFullName = [[NSString alloc] initWithFormat:@"%@ > %@", cur.name, inpFullName];
+        }
+        title.stringValue = inpFullName;
+    }
+
+    if ([output isKindOfClass:NJOutputKeyPress.class]) {
+        [radioButtons selectCellAtRow:1 column:0];
+        keyInput.keyCode = [(NJOutputKeyPress*)output vk];
+    } else if ([output isKindOfClass:NJOutputMapping.class]) {
+        [radioButtons selectCellAtRow:2 column:0];
+        NSMenuItem *item = [mappingPopup itemWithRepresentedObject:[(NJOutputMapping *)output mapping]];
+        [mappingPopup selectItem:item];
+        if (!item)
+            [radioButtons selectCellAtRow:self.enabled ? 0 : -1 column:0];
+    }
+    else if ([output isKindOfClass:NJOutputMouseMove.class]) {
+        [radioButtons selectCellAtRow:3 column:0];
+        mouseDirSelect.selectedSegment = [(NJOutputMouseMove *)output axis];
+        mouseSpeedSlider.floatValue = [(NJOutputMouseMove *)output speed];
+    }
+    else if ([output isKindOfClass:NJOutputMouseButton.class]) {
+        [radioButtons selectCellAtRow:4 column:0];
+        mouseBtnSelect.selectedSegment = [(NJOutputMouseButton *)output button] == kCGMouseButtonLeft ? 0 : 1;
+    }
+    else if ([output isKindOfClass:NJOutputMouseScroll.class]) {
+        [radioButtons selectCellAtRow:5 column:0];
+        int direction = [(NJOutputMouseScroll *)output direction];
+        float speed = [(NJOutputMouseScroll *)output speed];
+        scrollDirSelect.selectedSegment = (direction > 0) + !speed * 2;
+        scrollSpeedSlider.floatValue = speed;
+    } else {
+        [radioButtons selectCellAtRow:self.enabled ? 0 : -1 column:0];
+    }
+    [self cleanUpInterface];
+}
+
+- (void)loadCurrent {
+    [self loadOutput:self.currentOutput forInput:inputController.selectedInput];
+}
+
+- (void)focusKey {
+    if (radioButtons.selectedRow <= 1)
+        [keyInput.window makeFirstResponder:keyInput];
+    else
+        [keyInput resignIfFirstResponder];
+}
+
+- (void)mappingListDidChange:(NSNotification *)note {
+    NSArray *mappings = note.object;
+    NJMapping *current = mappingPopup.selectedItem.representedObject;
+    [mappingPopup.menu removeAllItems];
+    for (NJMapping *mapping in mappings) {
+        NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:mapping.name
+                                                      action:@selector(mappingChosen:)
+                                               keyEquivalent:@""];
+        item.target = self;
+        item.representedObject = mapping;
+        [mappingPopup.menu addItem:item];
+    }
+    [mappingPopup selectItemWithRepresentedObject:current];
+}
+
+@end
diff --git a/Classes/NJOutputKeyPress.h b/Classes/NJOutputKeyPress.h
new file mode 100644 (file)
index 0000000..052b010
--- /dev/null
@@ -0,0 +1,15 @@
+//
+//  NJOutputKeyPress.h
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+#import "NJOutput.h"
+
+@interface NJOutputKeyPress : NJOutput
+
+@property (nonatomic, assign) CGKeyCode vk;
+
+@end
diff --git a/Classes/NJOutputKeyPress.m b/Classes/NJOutputKeyPress.m
new file mode 100644 (file)
index 0000000..622cf41
--- /dev/null
@@ -0,0 +1,43 @@
+//
+//  NJOutputKeyPress.m
+//  Enjoy
+//
+//  Created by Sam McCall on 5/05/09.
+//
+
+#import "NJOutputKeyPress.h"
+
+#import "NJKeyInputField.h"
+
+@implementation NJOutputKeyPress
+
++ (NSString *)serializationCode {
+    return @"key press";
+}
+
+- (NSDictionary *)serialize {
+    return _vk != NJKeyInputFieldEmpty
+        ? @{ @"type": self.class.serializationCode, @"key": @(_vk) }
+        : nil;
+}
+
++ (NJOutput *)outputDeserialize:(NSDictionary *)serialization
+                  withMappings:(NSArray *)mappings {
+    NJOutputKeyPress *output = [[NJOutputKeyPress alloc] init];
+    output.vk = [serialization[@"key"] intValue];
+    return output;
+}
+
+- (void)trigger {
+    CGEventRef keyDown = CGEventCreateKeyboardEvent(NULL, _vk, YES);
+    CGEventPost(kCGHIDEventTap, keyDown);
+    CFRelease(keyDown);
+}
+
+- (void)untrigger {
+    CGEventRef keyUp = CGEventCreateKeyboardEvent(NULL, _vk, NO);
+    CGEventPost(kCGHIDEventTap, keyUp);
+    CFRelease(keyUp);
+}
+
+@end
diff --git a/Classes/NJOutputMapping.h b/Classes/NJOutputMapping.h
new file mode 100644 (file)
index 0000000..e8c3029
--- /dev/null
@@ -0,0 +1,17 @@
+//
+//  NJOutputMapping.h
+//  Enjoy
+//
+//  Created by Sam McCall on 6/05/09.
+//  Copyright 2009 University of Otago. All rights reserved.
+//
+
+#import "NJOutput.h"
+
+@class NJMapping;
+
+@interface NJOutputMapping : NJOutput
+
+@property (nonatomic, weak) NJMapping *mapping;
+
+@end
diff --git a/Classes/NJOutputMapping.m b/Classes/NJOutputMapping.m
new file mode 100644 (file)
index 0000000..fa8fda1
--- /dev/null
@@ -0,0 +1,44 @@
+//
+//  NJOutputMapping.m
+//  Enjoy
+//
+//  Created by Sam McCall on 6/05/09.
+//
+
+#import "NJOutputMapping.h"
+
+#import "EnjoyableApplicationDelegate.h"
+#import "NJMapping.h"
+#import "NJMappingsController.h"
+
+@implementation NJOutputMapping
+
++ (NSString *)serializationCode {
+    return @"mapping";
+}
+
+- (NSDictionary *)serialize {
+    return _mapping
+        ? @{ @"type": self.class.serializationCode, @"name": _mapping.name }
+        : nil;
+}
+
++ (NJOutputMapping *)outputDeserialize:(NSDictionary *)serialization
+                        withMappings:(NSArray *)mappings {
+    NSString *name = serialization[@"name"];
+    NJOutputMapping *output = [[NJOutputMapping alloc] init];
+    for (NJMapping *mapping in mappings) {
+        if ([mapping.name isEqualToString:name]) {
+            output.mapping = mapping;
+            return output;
+        }
+    }
+    return nil;
+}
+
+- (void)trigger {
+    EnjoyableApplicationDelegate *ctrl = (EnjoyableApplicationDelegate *)NSApplication.sharedApplication.delegate;
+    [ctrl.mappingsController activateMapping:_mapping];
+}
+
+@end
diff --git a/Classes/NJOutputMouseButton.h b/Classes/NJOutputMouseButton.h
new file mode 100644 (file)
index 0000000..4d790e5
--- /dev/null
@@ -0,0 +1,14 @@
+//
+//  NJOutputMouseButton.h
+//  Enjoy
+//
+//  Created by Yifeng Huang on 7/27/12.
+//
+
+#import "NJOutput.h"
+
+@interface NJOutputMouseButton : NJOutput
+
+@property (nonatomic, assign) CGMouseButton button;
+
+@end
diff --git a/Classes/NJOutputMouseButton.m b/Classes/NJOutputMouseButton.m
new file mode 100644 (file)
index 0000000..d5b1f16
--- /dev/null
@@ -0,0 +1,51 @@
+//
+//  NJOutputMouseButton.m
+//  Enjoy
+//
+//  Created by Yifeng Huang on 7/27/12.
+//
+
+#import "NJOutputMouseButton.h"
+
+@implementation NJOutputMouseButton
+
++ (NSString *)serializationCode {
+    return @"mouse button";
+}
+
+- (NSDictionary *)serialize {
+    return @{ @"type": self.class.serializationCode, @"button": @(_button) };
+}
+
++ (NJOutput *)outputDeserialize:(NSDictionary *)serialization
+                  withMappings:(NSArray *)mappings {
+    NJOutputMouseButton *output = [[NJOutputMouseButton alloc] init];
+    output.button = [serialization[@"button"] intValue];
+    return output;
+}
+
+- (void)trigger {
+    CGFloat height = NSScreen.mainScreen.frame.size.height;
+    NSPoint mouseLoc = NSEvent.mouseLocation;
+    CGEventType eventType = (_button == kCGMouseButtonLeft) ? kCGEventLeftMouseDown : kCGEventRightMouseDown;
+    CGEventRef click = CGEventCreateMouseEvent(NULL,
+                                               eventType,
+                                               CGPointMake(mouseLoc.x, height - mouseLoc.y),
+                                               _button);
+    CGEventPost(kCGHIDEventTap, click);
+    CFRelease(click);
+}
+
+- (void)untrigger {
+    CGFloat height = NSScreen.mainScreen.frame.size.height;
+    NSPoint mouseLoc = NSEvent.mouseLocation;
+    CGEventType eventType = (_button == kCGMouseButtonLeft) ? kCGEventLeftMouseUp : kCGEventRightMouseUp;
+    CGEventRef click = CGEventCreateMouseEvent(NULL,
+                                               eventType,
+                                               CGPointMake(mouseLoc.x, height - mouseLoc.y),
+                                               _button);
+    CGEventPost(kCGHIDEventTap, click);
+    CFRelease(click);
+}
+
+@end
diff --git a/Classes/NJOutputMouseMove.h b/Classes/NJOutputMouseMove.h
new file mode 100644 (file)
index 0000000..428416f
--- /dev/null
@@ -0,0 +1,15 @@
+//
+//  NJOutputMouseMove.h
+//  Enjoy
+//
+//  Created by Yifeng Huang on 7/26/12.
+//
+
+#import "NJOutput.h"
+
+@interface NJOutputMouseMove : NJOutput
+
+@property (nonatomic, assign) int axis;
+@property (nonatomic, assign) float speed;
+
+@end
diff --git a/Classes/NJOutputMouseMove.m b/Classes/NJOutputMouseMove.m
new file mode 100644 (file)
index 0000000..884a3d3
--- /dev/null
@@ -0,0 +1,76 @@
+//
+//  NJOutputMouseMove.m
+//  Enjoy
+//
+//  Created by Yifeng Huang on 7/26/12.
+//
+
+#import "NJOutputMouseMove.h"
+
+#import "NJDeviceController.h"
+
+@implementation NJOutputMouseMove
+
++ (NSString *)serializationCode {
+    return @"mouse move";
+}
+
+- (NSDictionary *)serialize {
+    return @{ @"type": self.class.serializationCode,
+              @"axis": @(_axis),
+              @"speed": @(_speed),
+              };
+}
+
++ (NJOutput *)outputDeserialize:(NSDictionary *)serialization
+                  withMappings:(NSArray *)mappings {
+    NJOutputMouseMove *output = [[NJOutputMouseMove alloc] init];
+    output.axis = [serialization[@"axis"] intValue];
+    output.speed = [serialization[@"speed"] floatValue];
+    if (!output.speed)
+        output.speed = 4;
+    return output;
+}
+
+- (BOOL)isContinuous {
+    return YES;
+}
+
+- (BOOL)update:(NJDeviceController *)jc {
+    if (self.magnitude < 0.05)
+        return NO; // dead zone
+    
+    CGFloat height = NSScreen.mainScreen.frame.size.height;
+    
+    float dx = 0.f, dy = 0.f;
+    switch (_axis) {
+        case 0:
+            dx = -self.magnitude * _speed;
+            break;
+        case 1:
+            dx = self.magnitude * _speed;
+            break;
+        case 2:
+            dy = -self.magnitude * _speed;
+            break;
+        case 3:
+            dy = self.magnitude * _speed;
+            break;
+    }
+    NSPoint mouseLoc = jc.mouseLoc;
+    mouseLoc.x += dx;
+    mouseLoc.y -= dy;
+    jc.mouseLoc = mouseLoc;
+    
+    CGEventRef move = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved,
+                                              CGPointMake(mouseLoc.x, height - mouseLoc.y),
+                                              0);
+    CGEventSetType(move, kCGEventMouseMoved);
+    CGEventSetIntegerValueField(move, kCGMouseEventDeltaX, (int)dx);
+    CGEventSetIntegerValueField(move, kCGMouseEventDeltaY, (int)dy);
+    CGEventPost(kCGHIDEventTap, move);
+    CFRelease(move);
+    return YES;
+}
+
+@end
diff --git a/Classes/NJOutputMouseScroll.h b/Classes/NJOutputMouseScroll.h
new file mode 100644 (file)
index 0000000..dd0444a
--- /dev/null
@@ -0,0 +1,15 @@
+//
+//  NJOutputMouseScroll.h
+//  Enjoy
+//
+//  Created by Yifeng Huang on 7/28/12.
+//
+
+#import "NJOutput.h"
+
+@interface NJOutputMouseScroll : NJOutput
+
+@property (nonatomic, assign) int direction;
+@property (nonatomic, assign) float speed;
+
+@end
diff --git a/Classes/NJOutputMouseScroll.m b/Classes/NJOutputMouseScroll.m
new file mode 100644 (file)
index 0000000..812685f
--- /dev/null
@@ -0,0 +1,61 @@
+//
+//  NJOutputMouseScroll.m
+//  Enjoy
+//
+//  Created by Yifeng Huang on 7/28/12.
+//
+
+#import "NJOutputMouseScroll.h"
+
+@implementation NJOutputMouseScroll
+
++ (NSString *)serializationCode {
+    return @"mouse scroll";
+}
+
+- (NSDictionary *)serialize {
+    return @{ @"type": self.class.serializationCode,
+              @"direction": @(_direction),
+              @"speed": @(_speed)
+              };
+}
+
++ (NJOutput *)outputDeserialize:(NSDictionary *)serialization
+                  withMappings:(NSArray *)mappings {
+    NJOutputMouseScroll *output = [[NJOutputMouseScroll alloc] init];
+    output.direction = [serialization[@"direction"] intValue];
+    output.speed = [serialization[@"direction"] floatValue];
+    return output;
+}
+
+- (BOOL)isContinuous {
+    return !!self.speed;
+}
+
+- (void)trigger {
+    if (!self.speed) {
+        CGEventRef scroll = CGEventCreateScrollWheelEvent(NULL,
+                                                          kCGScrollEventUnitLine,
+                                                          1,
+                                                          _direction);
+        CGEventPost(kCGHIDEventTap, scroll);
+        CFRelease(scroll);
+    }
+}
+
+- (BOOL)update:(NJDeviceController *)jc {
+    if (self.magnitude < 0.05f)
+        return NO; // dead zone
+    
+    int amount = (int)(_speed * self.magnitude * _direction);
+    CGEventRef scroll = CGEventCreateScrollWheelEvent(NULL,
+                                                      kCGScrollEventUnitPixel,
+                                                      1,
+                                                      amount);
+    CGEventPost(kCGHIDEventTap, scroll);
+    CFRelease(scroll);
+
+    return YES;
+}
+
+@end
diff --git a/English.lproj/InfoPlist.strings b/English.lproj/InfoPlist.strings
deleted file mode 100644 (file)
index 84fe32f..0000000
Binary files a/English.lproj/InfoPlist.strings and /dev/null differ
diff --git a/English.lproj/MainMenu.xib b/English.lproj/MainMenu.xib
deleted file mode 100644 (file)
index cd9a1b9..0000000
+++ /dev/null
@@ -1,3084 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
-       <data>
-               <int key="IBDocument.SystemTarget">1080</int>
-               <string key="IBDocument.SystemVersion">12C2034</string>
-               <string key="IBDocument.InterfaceBuilderVersion">3084</string>
-               <string key="IBDocument.AppKitVersion">1187.34</string>
-               <string key="IBDocument.HIToolboxVersion">625.00</string>
-               <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
-                       <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
-                       <string key="NS.object.0">3084</string>
-               </object>
-               <array key="IBDocument.IntegratedClassDependencies">
-                       <string>NSBox</string>
-                       <string>NSButton</string>
-                       <string>NSButtonCell</string>
-                       <string>NSCustomObject</string>
-                       <string>NSCustomView</string>
-                       <string>NSMatrix</string>
-                       <string>NSMenu</string>
-                       <string>NSMenuItem</string>
-                       <string>NSOutlineView</string>
-                       <string>NSPopUpButton</string>
-                       <string>NSPopUpButtonCell</string>
-                       <string>NSPopover</string>
-                       <string>NSScrollView</string>
-                       <string>NSScroller</string>
-                       <string>NSSegmentedCell</string>
-                       <string>NSSegmentedControl</string>
-                       <string>NSSlider</string>
-                       <string>NSSliderCell</string>
-                       <string>NSSplitView</string>
-                       <string>NSTableColumn</string>
-                       <string>NSTableView</string>
-                       <string>NSTextField</string>
-                       <string>NSTextFieldCell</string>
-                       <string>NSToolbar</string>
-                       <string>NSToolbarFlexibleSpaceItem</string>
-                       <string>NSToolbarItem</string>
-                       <string>NSView</string>
-                       <string>NSViewController</string>
-                       <string>NSWindowTemplate</string>
-               </array>
-               <array key="IBDocument.PluginDependencies">
-                       <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
-               </array>
-               <object class="NSMutableDictionary" key="IBDocument.Metadata">
-                       <string key="NS.key.0">PluginDependencyRecalculationVersion</string>
-                       <integer value="1" key="NS.object.0"/>
-               </object>
-               <array class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
-                       <object class="NSCustomObject" id="1021">
-                               <string key="NSClassName">NSApplication</string>
-                       </object>
-                       <object class="NSCustomObject" id="1014">
-                               <string key="NSClassName">FirstResponder</string>
-                       </object>
-                       <object class="NSCustomObject" id="1050">
-                               <string key="NSClassName">NSApplication</string>
-                       </object>
-                       <object class="NSMenu" id="649796088">
-                               <string key="NSTitle">AMainMenu</string>
-                               <array class="NSMutableArray" key="NSMenuItems">
-                                       <object class="NSMenuItem" id="694149608">
-                                               <reference key="NSMenu" ref="649796088"/>
-                                               <string key="NSTitle">Enjoyable</string>
-                                               <string type="base64-UTF8" key="NSKeyEquiv">CA</string>
-                                               <int key="NSKeyEquivModMask">1048576</int>
-                                               <int key="NSMnemonicLoc">2147483647</int>
-                                               <object class="NSCustomResource" key="NSOnImage" id="35465992">
-                                                       <string key="NSClassName">NSImage</string>
-                                                       <string key="NSResourceName">NSMenuCheckmark</string>
-                                               </object>
-                                               <object class="NSCustomResource" key="NSMixedImage" id="502551668">
-                                                       <string key="NSClassName">NSImage</string>
-                                                       <string key="NSResourceName">NSMenuMixedState</string>
-                                               </object>
-                                               <string key="NSAction">submenuAction:</string>
-                                               <object class="NSMenu" key="NSSubmenu" id="110575045">
-                                                       <string key="NSTitle">Enjoyable</string>
-                                                       <array class="NSMutableArray" key="NSMenuItems">
-                                                               <object class="NSMenuItem" id="238522557">
-                                                                       <reference key="NSMenu" ref="110575045"/>
-                                                                       <string key="NSTitle">About Enjoyable</string>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="304266470">
-                                                                       <reference key="NSMenu" ref="110575045"/>
-                                                                       <bool key="NSIsDisabled">YES</bool>
-                                                                       <bool key="NSIsSeparator">YES</bool>
-                                                                       <string key="NSTitle"/>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="1046388886">
-                                                                       <reference key="NSMenu" ref="110575045"/>
-                                                                       <string key="NSTitle">Services</string>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                                       <string key="NSAction">submenuAction:</string>
-                                                                       <object class="NSMenu" key="NSSubmenu" id="752062318">
-                                                                               <string key="NSTitle">Services</string>
-                                                                               <array class="NSMutableArray" key="NSMenuItems"/>
-                                                                               <string key="NSName">_NSServicesMenu</string>
-                                                                       </object>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="646227648">
-                                                                       <reference key="NSMenu" ref="110575045"/>
-                                                                       <bool key="NSIsDisabled">YES</bool>
-                                                                       <bool key="NSIsSeparator">YES</bool>
-                                                                       <string key="NSTitle"/>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="755159360">
-                                                                       <reference key="NSMenu" ref="110575045"/>
-                                                                       <string key="NSTitle">Hide Enjoyable</string>
-                                                                       <string key="NSKeyEquiv">h</string>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="342932134">
-                                                                       <reference key="NSMenu" ref="110575045"/>
-                                                                       <string key="NSTitle">Hide Others</string>
-                                                                       <string key="NSKeyEquiv">h</string>
-                                                                       <int key="NSKeyEquivModMask">1572864</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="908899353">
-                                                                       <reference key="NSMenu" ref="110575045"/>
-                                                                       <string key="NSTitle">Show All</string>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="1056857174">
-                                                                       <reference key="NSMenu" ref="110575045"/>
-                                                                       <bool key="NSIsDisabled">YES</bool>
-                                                                       <bool key="NSIsSeparator">YES</bool>
-                                                                       <string key="NSTitle"/>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="632727374">
-                                                                       <reference key="NSMenu" ref="110575045"/>
-                                                                       <string key="NSTitle">Quit Enjoyable</string>
-                                                                       <string key="NSKeyEquiv">q</string>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                       </array>
-                                                       <string key="NSName">_NSAppleMenu</string>
-                                               </object>
-                                       </object>
-                                       <object class="NSMenuItem" id="379814623">
-                                               <reference key="NSMenu" ref="649796088"/>
-                                               <string key="NSTitle">Mappings</string>
-                                               <string key="NSKeyEquiv"/>
-                                               <int key="NSKeyEquivModMask">1048576</int>
-                                               <int key="NSMnemonicLoc">2147483647</int>
-                                               <reference key="NSOnImage" ref="35465992"/>
-                                               <reference key="NSMixedImage" ref="502551668"/>
-                                               <string key="NSAction">submenuAction:</string>
-                                               <object class="NSMenu" key="NSSubmenu" id="720053764">
-                                                       <string key="NSTitle">Mappings</string>
-                                                       <array class="NSMutableArray" key="NSMenuItems">
-                                                               <object class="NSMenuItem" id="632598200">
-                                                                       <reference key="NSMenu" ref="720053764"/>
-                                                                       <string key="NSTitle">Enable</string>
-                                                                       <string key="NSKeyEquiv">r</string>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="580069611">
-                                                                       <reference key="NSMenu" ref="720053764"/>
-                                                                       <bool key="NSIsDisabled">YES</bool>
-                                                                       <bool key="NSIsSeparator">YES</bool>
-                                                                       <string key="NSTitle"/>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="914355947">
-                                                                       <reference key="NSMenu" ref="720053764"/>
-                                                                       <string key="NSTitle">List</string>
-                                                                       <string key="NSKeyEquiv">l</string>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="187155117">
-                                                                       <reference key="NSMenu" ref="720053764"/>
-                                                                       <string key="NSTitle">Import…</string>
-                                                                       <string key="NSKeyEquiv">o</string>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="888617891">
-                                                                       <reference key="NSMenu" ref="720053764"/>
-                                                                       <string key="NSTitle">Export…</string>
-                                                                       <string key="NSKeyEquiv">s</string>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="773548144">
-                                                                       <reference key="NSMenu" ref="720053764"/>
-                                                                       <bool key="NSIsDisabled">YES</bool>
-                                                                       <bool key="NSIsSeparator">YES</bool>
-                                                                       <string key="NSTitle"/>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                                       <int key="NSTag">1</int>
-                                                               </object>
-                                                       </array>
-                                               </object>
-                                       </object>
-                                       <object class="NSMenuItem" id="713487014">
-                                               <reference key="NSMenu" ref="649796088"/>
-                                               <string key="NSTitle">Window</string>
-                                               <string key="NSKeyEquiv"/>
-                                               <int key="NSKeyEquivModMask">1048576</int>
-                                               <int key="NSMnemonicLoc">2147483647</int>
-                                               <reference key="NSOnImage" ref="35465992"/>
-                                               <reference key="NSMixedImage" ref="502551668"/>
-                                               <string key="NSAction">submenuAction:</string>
-                                               <object class="NSMenu" key="NSSubmenu" id="835318025">
-                                                       <string key="NSTitle">Window</string>
-                                                       <array class="NSMutableArray" key="NSMenuItems">
-                                                               <object class="NSMenuItem" id="1011231497">
-                                                                       <reference key="NSMenu" ref="835318025"/>
-                                                                       <string key="NSTitle">Minimize</string>
-                                                                       <string key="NSKeyEquiv">m</string>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="575023229">
-                                                                       <reference key="NSMenu" ref="835318025"/>
-                                                                       <string key="NSTitle">Zoom</string>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="299356726">
-                                                                       <reference key="NSMenu" ref="835318025"/>
-                                                                       <bool key="NSIsDisabled">YES</bool>
-                                                                       <bool key="NSIsSeparator">YES</bool>
-                                                                       <string key="NSTitle"/>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                               <object class="NSMenuItem" id="625202149">
-                                                                       <reference key="NSMenu" ref="835318025"/>
-                                                                       <string key="NSTitle">Bring All to Front</string>
-                                                                       <string key="NSKeyEquiv"/>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                       </array>
-                                                       <string key="NSName">_NSWindowsMenu</string>
-                                               </object>
-                                       </object>
-                                       <object class="NSMenuItem" id="693056251">
-                                               <reference key="NSMenu" ref="649796088"/>
-                                               <string key="NSTitle">Help</string>
-                                               <string key="NSKeyEquiv"/>
-                                               <int key="NSMnemonicLoc">2147483647</int>
-                                               <reference key="NSOnImage" ref="35465992"/>
-                                               <reference key="NSMixedImage" ref="502551668"/>
-                                               <string key="NSAction">submenuAction:</string>
-                                               <object class="NSMenu" key="NSSubmenu" id="997802319">
-                                                       <string key="NSTitle">Help</string>
-                                                       <array class="NSMutableArray" key="NSMenuItems">
-                                                               <object class="NSMenuItem" id="842970531">
-                                                                       <reference key="NSMenu" ref="997802319"/>
-                                                                       <string key="NSTitle">Enjoyable Help</string>
-                                                                       <string key="NSKeyEquiv">?</string>
-                                                                       <int key="NSKeyEquivModMask">1048576</int>
-                                                                       <int key="NSMnemonicLoc">2147483647</int>
-                                                                       <reference key="NSOnImage" ref="35465992"/>
-                                                                       <reference key="NSMixedImage" ref="502551668"/>
-                                                               </object>
-                                                       </array>
-                                                       <string key="NSName">_NSHelpMenu</string>
-                                               </object>
-                                       </object>
-                               </array>
-                               <string key="NSName">_NSMainMenu</string>
-                       </object>
-                       <object class="NSWindowTemplate" id="808667431">
-                               <int key="NSWindowStyleMask">15</int>
-                               <int key="NSWindowBacking">2</int>
-                               <string key="NSWindowRect">{{355, 59}, {640, 320}}</string>
-                               <int key="NSWTFlags">1685585920</int>
-                               <string key="NSWindowTitle">Enjoyable</string>
-                               <string key="NSWindowClass">NSWindow</string>
-                               <object class="NSToolbar" key="NSViewClass" id="1043384830">
-                                       <object class="NSMutableString" key="NSToolbarIdentifier">
-                                               <characters key="NS.bytes">AC1F5C48-4C16-4C9D-9779-B783AF35E2E1</characters>
-                                       </object>
-                                       <nil key="NSToolbarDelegate"/>
-                                       <bool key="NSToolbarPrefersToBeShown">YES</bool>
-                                       <bool key="NSToolbarShowsBaselineSeparator">YES</bool>
-                                       <bool key="NSToolbarAllowsUserCustomization">NO</bool>
-                                       <bool key="NSToolbarAutosavesConfiguration">YES</bool>
-                                       <int key="NSToolbarDisplayMode">2</int>
-                                       <int key="NSToolbarSizeMode">1</int>
-                                       <dictionary class="NSMutableDictionary" key="NSToolbarIBIdentifiedItems">
-                                               <object class="NSToolbarItem" key="2CB21E35-9CF1-4C67-9670-31139C914D10" id="985167622">
-                                                       <object class="NSMutableString" key="NSToolbarItemIdentifier">
-                                                               <characters key="NS.bytes">2CB21E35-9CF1-4C67-9670-31139C914D10</characters>
-                                                       </object>
-                                                       <string key="NSToolbarItemLabel">Enabled</string>
-                                                       <string key="NSToolbarItemPaletteLabel">Enabled</string>
-                                                       <nil key="NSToolbarItemToolTip"/>
-                                                       <object class="NSButton" key="NSToolbarItemView" id="385218002">
-                                                               <nil key="NSNextResponder"/>
-                                                               <int key="NSvFlags">268</int>
-                                                               <string key="NSFrame">{{7, 14}, {36, 25}}</string>
-                                                               <string key="NSReuseIdentifierKey">_NS:9</string>
-                                                               <bool key="NSEnabled">YES</bool>
-                                                               <object class="NSButtonCell" key="NSCell" id="422366518">
-                                                                       <int key="NSCellFlags">67108864</int>
-                                                                       <int key="NSCellFlags2">134217728</int>
-                                                                       <string key="NSContents"/>
-                                                                       <object class="NSFont" key="NSSupport" id="45863614">
-                                                                               <string key="NSName">LucidaGrande</string>
-                                                                               <double key="NSSize">13</double>
-                                                                               <int key="NSfFlags">1044</int>
-                                                                       </object>
-                                                                       <string key="NSCellIdentifier">_NS:9</string>
-                                                                       <reference key="NSControlView" ref="385218002"/>
-                                                                       <int key="NSButtonFlags">-1228128256</int>
-                                                                       <int key="NSButtonFlags2">163</int>
-                                                                       <object class="NSCustomResource" key="NSNormalImage" id="80448349">
-                                                                               <string key="NSClassName">NSImage</string>
-                                                                               <string key="NSResourceName">NSRightFacingTriangleTemplate</string>
-                                                                       </object>
-                                                                       <string key="NSAlternateContents"/>
-                                                                       <string key="NSKeyEquivalent"/>
-                                                                       <int key="NSPeriodicDelay">200</int>
-                                                                       <int key="NSPeriodicInterval">25</int>
-                                                               </object>
-                                                               <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                       </object>
-                                                       <reference key="NSToolbarItemImage" ref="80448349"/>
-                                                       <nil key="NSToolbarItemTarget"/>
-                                                       <nil key="NSToolbarItemAction"/>
-                                                       <string key="NSToolbarItemMinSize">{36, 25}</string>
-                                                       <string key="NSToolbarItemMaxSize">{36, 25}</string>
-                                                       <bool key="NSToolbarItemEnabled">YES</bool>
-                                                       <bool key="NSToolbarItemAutovalidates">YES</bool>
-                                                       <int key="NSToolbarItemTag">0</int>
-                                                       <bool key="NSToolbarIsUserRemovable">YES</bool>
-                                                       <int key="NSToolbarItemVisibilityPriority">0</int>
-                                               </object>
-                                               <object class="NSToolbarItem" key="4AC66688-76E8-47ED-AC0A-7462220A4019" id="496378711">
-                                                       <object class="NSMutableString" key="NSToolbarItemIdentifier">
-                                                               <characters key="NS.bytes">4AC66688-76E8-47ED-AC0A-7462220A4019</characters>
-                                                       </object>
-                                                       <string key="NSToolbarItemLabel">Mapping Selector</string>
-                                                       <string key="NSToolbarItemPaletteLabel">Mapping Selector</string>
-                                                       <nil key="NSToolbarItemToolTip"/>
-                                                       <object class="NSButton" key="NSToolbarItemView" id="227597319">
-                                                               <nil key="NSNextResponder"/>
-                                                               <int key="NSvFlags">268</int>
-                                                               <string key="NSFrame">{{0, 14}, {140, 25}}</string>
-                                                               <string key="NSReuseIdentifierKey">_NS:9</string>
-                                                               <bool key="NSEnabled">YES</bool>
-                                                               <object class="NSButtonCell" key="NSCell" id="850080795">
-                                                                       <int key="NSCellFlags">67108864</int>
-                                                                       <int key="NSCellFlags2">134217728</int>
-                                                                       <string key="NSContents">(default)</string>
-                                                                       <reference key="NSSupport" ref="45863614"/>
-                                                                       <string key="NSCellIdentifier">_NS:9</string>
-                                                                       <reference key="NSControlView" ref="227597319"/>
-                                                                       <int key="NSButtonFlags">918306816</int>
-                                                                       <int key="NSButtonFlags2">163</int>
-                                                                       <object class="NSCustomResource" key="NSNormalImage" id="13197350">
-                                                                               <string key="NSClassName">NSImage</string>
-                                                                               <string key="NSResourceName">NSListViewTemplate</string>
-                                                                       </object>
-                                                                       <string key="NSAlternateContents"/>
-                                                                       <string key="NSKeyEquivalent"/>
-                                                                       <int key="NSPeriodicDelay">400</int>
-                                                                       <int key="NSPeriodicInterval">75</int>
-                                                               </object>
-                                                               <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                       </object>
-                                                       <reference key="NSToolbarItemImage" ref="13197350"/>
-                                                       <nil key="NSToolbarItemTarget"/>
-                                                       <nil key="NSToolbarItemAction"/>
-                                                       <string key="NSToolbarItemMinSize">{13, 25}</string>
-                                                       <string key="NSToolbarItemMaxSize">{141, 25}</string>
-                                                       <bool key="NSToolbarItemEnabled">YES</bool>
-                                                       <bool key="NSToolbarItemAutovalidates">NO</bool>
-                                                       <int key="NSToolbarItemTag">0</int>
-                                                       <bool key="NSToolbarIsUserRemovable">YES</bool>
-                                                       <int key="NSToolbarItemVisibilityPriority">0</int>
-                                               </object>
-                                               <object class="NSToolbarFlexibleSpaceItem" key="NSToolbarFlexibleSpaceItem" id="658903347">
-                                                       <string key="NSToolbarItemIdentifier">NSToolbarFlexibleSpaceItem</string>
-                                                       <string key="NSToolbarItemLabel"/>
-                                                       <string key="NSToolbarItemPaletteLabel">Flexible Space</string>
-                                                       <nil key="NSToolbarItemToolTip"/>
-                                                       <nil key="NSToolbarItemView"/>
-                                                       <nil key="NSToolbarItemImage"/>
-                                                       <nil key="NSToolbarItemTarget"/>
-                                                       <nil key="NSToolbarItemAction"/>
-                                                       <string key="NSToolbarItemMinSize">{1, 5}</string>
-                                                       <string key="NSToolbarItemMaxSize">{20000, 32}</string>
-                                                       <bool key="NSToolbarItemEnabled">YES</bool>
-                                                       <bool key="NSToolbarItemAutovalidates">YES</bool>
-                                                       <int key="NSToolbarItemTag">-1</int>
-                                                       <bool key="NSToolbarIsUserRemovable">YES</bool>
-                                                       <int key="NSToolbarItemVisibilityPriority">0</int>
-                                                       <object class="NSMenuItem" key="NSToolbarItemMenuFormRepresentation">
-                                                               <bool key="NSIsDisabled">YES</bool>
-                                                               <bool key="NSIsSeparator">YES</bool>
-                                                               <string key="NSTitle"/>
-                                                               <string key="NSKeyEquiv"/>
-                                                               <int key="NSKeyEquivModMask">1048576</int>
-                                                               <int key="NSMnemonicLoc">2147483647</int>
-                                                               <reference key="NSOnImage" ref="35465992"/>
-                                                               <reference key="NSMixedImage" ref="502551668"/>
-                                                       </object>
-                                               </object>
-                                       </dictionary>
-                                       <array key="NSToolbarIBAllowedItems">
-                                               <reference ref="496378711"/>
-                                               <reference ref="658903347"/>
-                                               <reference ref="985167622"/>
-                                       </array>
-                                       <array key="NSToolbarIBDefaultItems">
-                                               <reference ref="496378711"/>
-                                               <reference ref="658903347"/>
-                                               <reference ref="985167622"/>
-                                       </array>
-                                       <array key="NSToolbarIBSelectableItems" id="0"/>
-                               </object>
-                               <nil key="NSUserInterfaceItemIdentifier"/>
-                               <string key="NSWindowContentMinSize">{640, 320}</string>
-                               <object class="NSView" key="NSWindowView" id="177223957">
-                                       <reference key="NSNextResponder"/>
-                                       <int key="NSvFlags">256</int>
-                                       <array class="NSMutableArray" key="NSSubviews">
-                                               <object class="NSSplitView" id="206489479">
-                                                       <reference key="NSNextResponder" ref="177223957"/>
-                                                       <int key="NSvFlags">274</int>
-                                                       <array class="NSMutableArray" key="NSSubviews">
-                                                               <object class="NSCustomView" id="977242492">
-                                                                       <reference key="NSNextResponder" ref="206489479"/>
-                                                                       <int key="NSvFlags">256</int>
-                                                                       <array class="NSMutableArray" key="NSSubviews">
-                                                                               <object class="NSScrollView" id="364857164">
-                                                                                       <reference key="NSNextResponder" ref="977242492"/>
-                                                                                       <int key="NSvFlags">274</int>
-                                                                                       <array class="NSMutableArray" key="NSSubviews">
-                                                                                               <object class="NSClipView" id="698362889">
-                                                                                                       <reference key="NSNextResponder" ref="364857164"/>
-                                                                                                       <int key="NSvFlags">2304</int>
-                                                                                                       <array class="NSMutableArray" key="NSSubviews">
-                                                                                                               <object class="NSOutlineView" id="365506042">
-                                                                                                                       <reference key="NSNextResponder" ref="698362889"/>
-                                                                                                                       <int key="NSvFlags">256</int>
-                                                                                                                       <string key="NSFrameSize">{200, 318}</string>
-                                                                                                                       <reference key="NSSuperview" ref="698362889"/>
-                                                                                                                       <reference key="NSWindow"/>
-                                                                                                                       <reference key="NSNextKeyView" ref="1036252745"/>
-                                                                                                                       <bool key="NSEnabled">YES</bool>
-                                                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                                                                       <bool key="NSControlAllowsExpansionToolTips">YES</bool>
-                                                                                                                       <object class="_NSCornerView" key="NSCornerView">
-                                                                                                                               <nil key="NSNextResponder"/>
-                                                                                                                               <int key="NSvFlags">256</int>
-                                                                                                                               <string key="NSFrame">{{474, 0}, {16, 17}}</string>
-                                                                                                                       </object>
-                                                                                                                       <array class="NSMutableArray" key="NSTableColumns">
-                                                                                                                               <object class="NSTableColumn" id="290995057">
-                                                                                                                                       <double key="NSWidth">197</double>
-                                                                                                                                       <double key="NSMinWidth">16</double>
-                                                                                                                                       <double key="NSMaxWidth">1000</double>
-                                                                                                                                       <object class="NSTableHeaderCell" key="NSHeaderCell">
-                                                                                                                                               <int key="NSCellFlags">75497536</int>
-                                                                                                                                               <int key="NSCellFlags2">2048</int>
-                                                                                                                                               <string key="NSContents"/>
-                                                                                                                                               <object class="NSFont" key="NSSupport" id="26">
-                                                                                                                                                       <string key="NSName">LucidaGrande</string>
-                                                                                                                                                       <double key="NSSize">11</double>
-                                                                                                                                                       <int key="NSfFlags">3100</int>
-                                                                                                                                               </object>
-                                                                                                                                               <object class="NSColor" key="NSBackgroundColor">
-                                                                                                                                                       <int key="NSColorSpace">3</int>
-                                                                                                                                                       <bytes key="NSWhite">MC4zMzMzMzI5OQA</bytes>
-                                                                                                                                               </object>
-                                                                                                                                               <object class="NSColor" key="NSTextColor" id="809574366">
-                                                                                                                                                       <int key="NSColorSpace">6</int>
-                                                                                                                                                       <string key="NSCatalogName">System</string>
-                                                                                                                                                       <string key="NSColorName">headerTextColor</string>
-                                                                                                                                                       <object class="NSColor" key="NSColor" id="838935852">
-                                                                                                                                                               <int key="NSColorSpace">3</int>
-                                                                                                                                                               <bytes key="NSWhite">MAA</bytes>
-                                                                                                                                                       </object>
-                                                                                                                                               </object>
-                                                                                                                                       </object>
-                                                                                                                                       <object class="NSTextFieldCell" key="NSDataCell" id="158646067">
-                                                                                                                                               <int key="NSCellFlags">67108928</int>
-                                                                                                                                               <int key="NSCellFlags2">2624</int>
-                                                                                                                                               <string key="NSContents">Text Cell</string>
-                                                                                                                                               <reference key="NSSupport" ref="45863614"/>
-                                                                                                                                               <reference key="NSControlView" ref="365506042"/>
-                                                                                                                                               <object class="NSColor" key="NSBackgroundColor" id="834857663">
-                                                                                                                                                       <int key="NSColorSpace">6</int>
-                                                                                                                                                       <string key="NSCatalogName">System</string>
-                                                                                                                                                       <string key="NSColorName">controlBackgroundColor</string>
-                                                                                                                                                       <object class="NSColor" key="NSColor" id="783809746">
-                                                                                                                                                               <int key="NSColorSpace">3</int>
-                                                                                                                                                               <bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
-                                                                                                                                                       </object>
-                                                                                                                                               </object>
-                                                                                                                                               <object class="NSColor" key="NSTextColor" id="813255721">
-                                                                                                                                                       <int key="NSColorSpace">6</int>
-                                                                                                                                                       <string key="NSCatalogName">System</string>
-                                                                                                                                                       <string key="NSColorName">controlTextColor</string>
-                                                                                                                                                       <reference key="NSColor" ref="838935852"/>
-                                                                                                                                               </object>
-                                                                                                                                       </object>
-                                                                                                                                       <int key="NSResizingMask">1</int>
-                                                                                                                                       <bool key="NSIsResizeable">YES</bool>
-                                                                                                                                       <reference key="NSTableView" ref="365506042"/>
-                                                                                                                               </object>
-                                                                                                                       </array>
-                                                                                                                       <double key="NSIntercellSpacingWidth">3</double>
-                                                                                                                       <double key="NSIntercellSpacingHeight">2</double>
-                                                                                                                       <object class="NSColor" key="NSBackgroundColor" id="214000480">
-                                                                                                                               <int key="NSColorSpace">3</int>
-                                                                                                                               <bytes key="NSWhite">MQA</bytes>
-                                                                                                                       </object>
-                                                                                                                       <object class="NSColor" key="NSGridColor" id="133906332">
-                                                                                                                               <int key="NSColorSpace">6</int>
-                                                                                                                               <string key="NSCatalogName">System</string>
-                                                                                                                               <string key="NSColorName">gridColor</string>
-                                                                                                                               <object class="NSColor" key="NSColor">
-                                                                                                                                       <int key="NSColorSpace">3</int>
-                                                                                                                                       <bytes key="NSWhite">MC41AA</bytes>
-                                                                                                                               </object>
-                                                                                                                       </object>
-                                                                                                                       <double key="NSRowHeight">20</double>
-                                                                                                                       <int key="NSTvFlags">306184192</int>
-                                                                                                                       <reference key="NSDelegate"/>
-                                                                                                                       <reference key="NSDataSource"/>
-                                                                                                                       <int key="NSColumnAutoresizingStyle">4</int>
-                                                                                                                       <int key="NSDraggingSourceMaskForLocal">15</int>
-                                                                                                                       <int key="NSDraggingSourceMaskForNonLocal">0</int>
-                                                                                                                       <bool key="NSAllowsTypeSelect">YES</bool>
-                                                                                                                       <int key="NSTableViewDraggingDestinationStyle">0</int>
-                                                                                                                       <int key="NSTableViewGroupRowStyle">1</int>
-                                                                                                                       <int key="NSTableViewRowSizeStyle">-1</int>
-                                                                                                                       <bool key="NSOutlineViewAutoresizesOutlineColumnKey">NO</bool>
-                                                                                                               </object>
-                                                                                                       </array>
-                                                                                                       <string key="NSFrame">{{1, 1}, {200, 318}}</string>
-                                                                                                       <reference key="NSSuperview" ref="364857164"/>
-                                                                                                       <reference key="NSWindow"/>
-                                                                                                       <reference key="NSNextKeyView" ref="365506042"/>
-                                                                                                       <reference key="NSDocView" ref="365506042"/>
-                                                                                                       <reference key="NSBGColor" ref="834857663"/>
-                                                                                                       <int key="NScvFlags">4</int>
-                                                                                               </object>
-                                                                                               <object class="NSScroller" id="1036252745">
-                                                                                                       <reference key="NSNextResponder" ref="364857164"/>
-                                                                                                       <int key="NSvFlags">-2147483392</int>
-                                                                                                       <string key="NSFrame">{{1, 1}, {8, 298}}</string>
-                                                                                                       <reference key="NSSuperview" ref="364857164"/>
-                                                                                                       <reference key="NSWindow"/>
-                                                                                                       <reference key="NSNextKeyView" ref="606740242"/>
-                                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                                                       <reference key="NSTarget" ref="364857164"/>
-                                                                                                       <string key="NSAction">_doScroller:</string>
-                                                                                                       <double key="NSPercent">0.4210526</double>
-                                                                                               </object>
-                                                                                               <object class="NSScroller" id="892486973">
-                                                                                                       <reference key="NSNextResponder" ref="364857164"/>
-                                                                                                       <int key="NSvFlags">-2147483392</int>
-                                                                                                       <string key="NSFrame">{{-100, -100}, {473, 15}}</string>
-                                                                                                       <reference key="NSSuperview" ref="364857164"/>
-                                                                                                       <reference key="NSWindow"/>
-                                                                                                       <reference key="NSNextKeyView" ref="698362889"/>
-                                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                                                       <int key="NSsFlags">1</int>
-                                                                                                       <reference key="NSTarget" ref="364857164"/>
-                                                                                                       <string key="NSAction">_doScroller:</string>
-                                                                                                       <double key="NSPercent">0.99789030000000001</double>
-                                                                                               </object>
-                                                                                       </array>
-                                                                                       <string key="NSFrameSize">{202, 320}</string>
-                                                                                       <reference key="NSSuperview" ref="977242492"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView" ref="892486973"/>
-                                                                                       <int key="NSsFlags">150034</int>
-                                                                                       <reference key="NSVScroller" ref="1036252745"/>
-                                                                                       <reference key="NSHScroller" ref="892486973"/>
-                                                                                       <reference key="NSContentView" ref="698362889"/>
-                                                                                       <bytes key="NSScrollAmts">QSAAAEEgAABBsAAAQbAAAA</bytes>
-                                                                                       <double key="NSMinMagnification">0.25</double>
-                                                                                       <double key="NSMaxMagnification">4</double>
-                                                                                       <double key="NSMagnification">1</double>
-                                                                               </object>
-                                                                       </array>
-                                                                       <string key="NSFrameSize">{202, 320}</string>
-                                                                       <reference key="NSSuperview" ref="206489479"/>
-                                                                       <reference key="NSWindow"/>
-                                                                       <reference key="NSNextKeyView" ref="364857164"/>
-                                                                       <string key="NSClassName">NSView</string>
-                                                               </object>
-                                                               <object class="NSCustomView" id="606740242">
-                                                                       <reference key="NSNextResponder" ref="206489479"/>
-                                                                       <int key="NSvFlags">256</int>
-                                                                       <array class="NSMutableArray" key="NSSubviews">
-                                                                               <object class="NSSlider" id="792189805">
-                                                                                       <reference key="NSNextResponder" ref="606740242"/>
-                                                                                       <int key="NSvFlags">265</int>
-                                                                                       <string key="NSFrame">{{228, 21}, {130, 12}}</string>
-                                                                                       <reference key="NSSuperview" ref="606740242"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView"/>
-                                                                                       <string key="NSReuseIdentifierKey">_NS:9</string>
-                                                                                       <bool key="NSEnabled">YES</bool>
-                                                                                       <object class="NSSliderCell" key="NSCell" id="423057230">
-                                                                                               <int key="NSCellFlags">-2080374784</int>
-                                                                                               <int key="NSCellFlags2">262144</int>
-                                                                                               <string key="NSContents"/>
-                                                                                               <string key="NSCellIdentifier">_NS:9</string>
-                                                                                               <reference key="NSControlView" ref="792189805"/>
-                                                                                               <double key="NSMaxValue">30</double>
-                                                                                               <double key="NSMinValue">0.0</double>
-                                                                                               <double key="NSValue">15</double>
-                                                                                               <double key="NSAltIncValue">0.0</double>
-                                                                                               <int key="NSNumberOfTickMarks">0</int>
-                                                                                               <int key="NSTickMarkPosition">1</int>
-                                                                                               <bool key="NSAllowsTickMarkValuesOnly">NO</bool>
-                                                                                               <bool key="NSVertical">NO</bool>
-                                                                                       </object>
-                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                               </object>
-                                                                               <object class="NSSlider" id="385416822">
-                                                                                       <reference key="NSNextResponder" ref="606740242"/>
-                                                                                       <int key="NSvFlags">265</int>
-                                                                                       <string key="NSFrame">{{228, 107}, {176, 12}}</string>
-                                                                                       <reference key="NSSuperview" ref="606740242"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView" ref="125828224"/>
-                                                                                       <string key="NSReuseIdentifierKey">_NS:9</string>
-                                                                                       <bool key="NSEnabled">YES</bool>
-                                                                                       <object class="NSSliderCell" key="NSCell" id="5417367">
-                                                                                               <int key="NSCellFlags">-2080374784</int>
-                                                                                               <int key="NSCellFlags2">262144</int>
-                                                                                               <string key="NSContents"/>
-                                                                                               <string key="NSCellIdentifier">_NS:9</string>
-                                                                                               <reference key="NSControlView" ref="385416822"/>
-                                                                                               <double key="NSMaxValue">20</double>
-                                                                                               <double key="NSMinValue">0.0</double>
-                                                                                               <double key="NSValue">4</double>
-                                                                                               <double key="NSAltIncValue">0.0</double>
-                                                                                               <int key="NSNumberOfTickMarks">0</int>
-                                                                                               <int key="NSTickMarkPosition">1</int>
-                                                                                               <bool key="NSAllowsTickMarkValuesOnly">NO</bool>
-                                                                                               <bool key="NSVertical">NO</bool>
-                                                                                       </object>
-                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                               </object>
-                                                                               <object class="NSSegmentedControl" id="875916470">
-                                                                                       <reference key="NSNextResponder" ref="606740242"/>
-                                                                                       <int key="NSvFlags">265</int>
-                                                                                       <string key="NSFrame">{{226, 117}, {180, 20}}</string>
-                                                                                       <reference key="NSSuperview" ref="606740242"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView" ref="385416822"/>
-                                                                                       <string key="NSReuseIdentifierKey">_NS:9</string>
-                                                                                       <bool key="NSEnabled">YES</bool>
-                                                                                       <object class="NSSegmentedCell" key="NSCell" id="241270212">
-                                                                                               <int key="NSCellFlags">67108864</int>
-                                                                                               <int key="NSCellFlags2">131072</int>
-                                                                                               <reference key="NSSupport" ref="26"/>
-                                                                                               <string key="NSCellIdentifier">_NS:9</string>
-                                                                                               <reference key="NSControlView" ref="875916470"/>
-                                                                                               <array class="NSMutableArray" key="NSSegmentImages">
-                                                                                                       <object class="NSSegmentItem">
-                                                                                                               <double key="NSSegmentItemWidth">44</double>
-                                                                                                               <string key="NSSegmentItemLabel">←</string>
-                                                                                                               <bool key="NSSegmentItemSelected">YES</bool>
-                                                                                                               <int key="NSSegmentItemImageScaling">0</int>
-                                                                                                       </object>
-                                                                                                       <object class="NSSegmentItem">
-                                                                                                               <double key="NSSegmentItemWidth">44</double>
-                                                                                                               <string key="NSSegmentItemLabel">→</string>
-                                                                                                               <int key="NSSegmentItemTag">1</int>
-                                                                                                               <int key="NSSegmentItemImageScaling">0</int>
-                                                                                                       </object>
-                                                                                                       <object class="NSSegmentItem">
-                                                                                                               <double key="NSSegmentItemWidth">42</double>
-                                                                                                               <string key="NSSegmentItemLabel">↑</string>
-                                                                                                               <int key="NSSegmentItemImageScaling">0</int>
-                                                                                                       </object>
-                                                                                                       <object class="NSSegmentItem">
-                                                                                                               <double key="NSSegmentItemWidth">41</double>
-                                                                                                               <string key="NSSegmentItemLabel">↓</string>
-                                                                                                               <int key="NSSegmentItemImageScaling">0</int>
-                                                                                                       </object>
-                                                                                               </array>
-                                                                                               <int key="NSSegmentStyle">1</int>
-                                                                                       </object>
-                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                               </object>
-                                                                               <object class="NSSegmentedControl" id="921829691">
-                                                                                       <reference key="NSNextResponder" ref="606740242"/>
-                                                                                       <int key="NSvFlags">265</int>
-                                                                                       <string key="NSFrame">{{226, 31}, {180, 20}}</string>
-                                                                                       <reference key="NSSuperview" ref="606740242"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView" ref="792189805"/>
-                                                                                       <string key="NSReuseIdentifierKey">_NS:9</string>
-                                                                                       <bool key="NSEnabled">YES</bool>
-                                                                                       <object class="NSSegmentedCell" key="NSCell" id="301345285">
-                                                                                               <int key="NSCellFlags">67108864</int>
-                                                                                               <int key="NSCellFlags2">131072</int>
-                                                                                               <reference key="NSSupport" ref="26"/>
-                                                                                               <string key="NSCellIdentifier">_NS:9</string>
-                                                                                               <reference key="NSControlView" ref="921829691"/>
-                                                                                               <array class="NSMutableArray" key="NSSegmentImages">
-                                                                                                       <object class="NSSegmentItem">
-                                                                                                               <double key="NSSegmentItemWidth">64</double>
-                                                                                                               <string key="NSSegmentItemLabel">↑</string>
-                                                                                                               <string key="NSSegmentItemTooltip">Scroll up continuously</string>
-                                                                                                               <bool key="NSSegmentItemSelected">YES</bool>
-                                                                                                               <int key="NSSegmentItemImageScaling">0</int>
-                                                                                                       </object>
-                                                                                                       <object class="NSSegmentItem">
-                                                                                                               <double key="NSSegmentItemWidth">63</double>
-                                                                                                               <string key="NSSegmentItemLabel">↓</string>
-                                                                                                               <string key="NSSegmentItemTooltip">Scroll down continuously</string>
-                                                                                                               <int key="NSSegmentItemTag">1</int>
-                                                                                                               <int key="NSSegmentItemImageScaling">0</int>
-                                                                                                       </object>
-                                                                                                       <object class="NSSegmentItem">
-                                                                                                               <string key="NSSegmentItemLabel">⤒</string>
-                                                                                                               <string key="NSSegmentItemTooltip">Scroll up one step</string>
-                                                                                                               <int key="NSSegmentItemImageScaling">0</int>
-                                                                                                       </object>
-                                                                                                       <object class="NSSegmentItem">
-                                                                                                               <string key="NSSegmentItemLabel">⤓</string>
-                                                                                                               <string key="NSSegmentItemTooltip">Scroll down one step</string>
-                                                                                                               <int key="NSSegmentItemImageScaling">0</int>
-                                                                                                       </object>
-                                                                                               </array>
-                                                                                               <int key="NSSegmentStyle">1</int>
-                                                                                       </object>
-                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                               </object>
-                                                                               <object class="NSSegmentedControl" id="125828224">
-                                                                                       <reference key="NSNextResponder" ref="606740242"/>
-                                                                                       <int key="NSvFlags">265</int>
-                                                                                       <string key="NSFrame">{{226, 67}, {180, 24}}</string>
-                                                                                       <reference key="NSSuperview" ref="606740242"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView" ref="921829691"/>
-                                                                                       <string key="NSReuseIdentifierKey">_NS:9</string>
-                                                                                       <bool key="NSEnabled">YES</bool>
-                                                                                       <object class="NSSegmentedCell" key="NSCell" id="514491330">
-                                                                                               <int key="NSCellFlags">67108864</int>
-                                                                                               <int key="NSCellFlags2">0</int>
-                                                                                               <object class="NSFont" key="NSSupport">
-                                                                                                       <string key="NSName">LucidaGrande</string>
-                                                                                                       <double key="NSSize">13</double>
-                                                                                                       <int key="NSfFlags">16</int>
-                                                                                               </object>
-                                                                                               <string key="NSCellIdentifier">_NS:9</string>
-                                                                                               <reference key="NSControlView" ref="125828224"/>
-                                                                                               <array class="NSMutableArray" key="NSSegmentImages">
-                                                                                                       <object class="NSSegmentItem">
-                                                                                                               <double key="NSSegmentItemWidth">87</double>
-                                                                                                               <string key="NSSegmentItemLabel">Left</string>
-                                                                                                               <bool key="NSSegmentItemSelected">YES</bool>
-                                                                                                               <int key="NSSegmentItemImageScaling">0</int>
-                                                                                                       </object>
-                                                                                                       <object class="NSSegmentItem">
-                                                                                                               <double key="NSSegmentItemWidth">86</double>
-                                                                                                               <string key="NSSegmentItemLabel">Right</string>
-                                                                                                               <int key="NSSegmentItemTag">1</int>
-                                                                                                               <int key="NSSegmentItemImageScaling">0</int>
-                                                                                                       </object>
-                                                                                               </array>
-                                                                                               <int key="NSSegmentStyle">1</int>
-                                                                                       </object>
-                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                               </object>
-                                                                               <object class="NSCustomView" id="57697638">
-                                                                                       <reference key="NSNextResponder" ref="606740242"/>
-                                                                                       <int key="NSvFlags">265</int>
-                                                                                       <string key="NSFrame">{{228, 197}, {176, 24}}</string>
-                                                                                       <reference key="NSSuperview" ref="606740242"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView" ref="194275224"/>
-                                                                                       <string key="NSReuseIdentifierKey">_NS:9</string>
-                                                                                       <string key="NSClassName">NJKeyInputField</string>
-                                                                               </object>
-                                                                               <object class="NSPopUpButton" id="194275224">
-                                                                                       <reference key="NSNextResponder" ref="606740242"/>
-                                                                                       <int key="NSvFlags">265</int>
-                                                                                       <string key="NSFrame">{{225, 152}, {182, 26}}</string>
-                                                                                       <reference key="NSSuperview" ref="606740242"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView" ref="875916470"/>
-                                                                                       <bool key="NSEnabled">YES</bool>
-                                                                                       <object class="NSPopUpButtonCell" key="NSCell" id="74311158">
-                                                                                               <int key="NSCellFlags">-1539309504</int>
-                                                                                               <int key="NSCellFlags2">2048</int>
-                                                                                               <reference key="NSSupport" ref="45863614"/>
-                                                                                               <reference key="NSControlView" ref="194275224"/>
-                                                                                               <int key="NSButtonFlags">109199360</int>
-                                                                                               <int key="NSButtonFlags2">129</int>
-                                                                                               <string key="NSAlternateContents"/>
-                                                                                               <string key="NSKeyEquivalent"/>
-                                                                                               <int key="NSPeriodicDelay">400</int>
-                                                                                               <int key="NSPeriodicInterval">75</int>
-                                                                                               <nil key="NSMenuItem"/>
-                                                                                               <bool key="NSMenuItemRespectAlignment">YES</bool>
-                                                                                               <object class="NSMenu" key="NSMenu" id="19633006">
-                                                                                                       <string key="NSTitle">OtherViews</string>
-                                                                                                       <array class="NSMutableArray" key="NSMenuItems"/>
-                                                                                               </object>
-                                                                                               <int key="NSSelectedIndex">-1</int>
-                                                                                               <int key="NSPreferredEdge">1</int>
-                                                                                               <bool key="NSUsesItemFromMenu">YES</bool>
-                                                                                               <bool key="NSAltersState">YES</bool>
-                                                                                               <int key="NSArrowPosition">2</int>
-                                                                                       </object>
-                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                               </object>
-                                                                               <object class="NSMatrix" id="120408205">
-                                                                                       <reference key="NSNextResponder" ref="606740242"/>
-                                                                                       <int key="NSvFlags">268</int>
-                                                                                       <string key="NSFrame">{{20, 16}, {200, 256}}</string>
-                                                                                       <reference key="NSSuperview" ref="606740242"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView" ref="57697638"/>
-                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                                       <int key="NSNumRows">6</int>
-                                                                                       <int key="NSNumCols">1</int>
-                                                                                       <array class="NSMutableArray" key="NSCells">
-                                                                                               <object class="NSButtonCell" id="177186415">
-                                                                                                       <int key="NSCellFlags">603979776</int>
-                                                                                                       <int key="NSCellFlags2">0</int>
-                                                                                                       <string key="NSContents">Do nothing</string>
-                                                                                                       <reference key="NSSupport" ref="45863614"/>
-                                                                                                       <reference key="NSControlView" ref="120408205"/>
-                                                                                                       <int key="NSTag">1</int>
-                                                                                                       <int key="NSButtonFlags">1211912448</int>
-                                                                                                       <int key="NSButtonFlags2">0</int>
-                                                                                                       <object class="NSCustomResource" key="NSNormalImage" id="421587711">
-                                                                                                               <string key="NSClassName">NSImage</string>
-                                                                                                               <string key="NSResourceName">NSRadioButton</string>
-                                                                                                       </object>
-                                                                                                       <object class="NSButtonImageSource" key="NSAlternateImage" id="68833793">
-                                                                                                               <string key="NSImageName">NSRadioButton</string>
-                                                                                                       </object>
-                                                                                                       <string key="NSAlternateContents"/>
-                                                                                                       <string key="NSKeyEquivalent"/>
-                                                                                                       <int key="NSPeriodicDelay">200</int>
-                                                                                                       <int key="NSPeriodicInterval">25</int>
-                                                                                               </object>
-                                                                                               <object class="NSButtonCell" id="387494389">
-                                                                                                       <int key="NSCellFlags">603979776</int>
-                                                                                                       <int key="NSCellFlags2">0</int>
-                                                                                                       <string key="NSContents">Press a key</string>
-                                                                                                       <reference key="NSSupport" ref="45863614"/>
-                                                                                                       <reference key="NSControlView" ref="120408205"/>
-                                                                                                       <int key="NSButtonFlags">1211912448</int>
-                                                                                                       <int key="NSButtonFlags2">0</int>
-                                                                                                       <reference key="NSNormalImage" ref="421587711"/>
-                                                                                                       <reference key="NSAlternateImage" ref="68833793"/>
-                                                                                                       <string key="NSAlternateContents"/>
-                                                                                                       <int key="NSPeriodicDelay">400</int>
-                                                                                                       <int key="NSPeriodicInterval">75</int>
-                                                                                               </object>
-                                                                                               <object class="NSButtonCell" id="339215895">
-                                                                                                       <int key="NSCellFlags">603979776</int>
-                                                                                                       <int key="NSCellFlags2">0</int>
-                                                                                                       <string key="NSContents">Switch to mapping</string>
-                                                                                                       <reference key="NSSupport" ref="45863614"/>
-                                                                                                       <reference key="NSControlView" ref="120408205"/>
-                                                                                                       <int key="NSButtonFlags">1211912448</int>
-                                                                                                       <int key="NSButtonFlags2">0</int>
-                                                                                                       <reference key="NSNormalImage" ref="421587711"/>
-                                                                                                       <reference key="NSAlternateImage" ref="68833793"/>
-                                                                                                       <string key="NSAlternateContents"/>
-                                                                                                       <int key="NSPeriodicDelay">400</int>
-                                                                                                       <int key="NSPeriodicInterval">75</int>
-                                                                                               </object>
-                                                                                               <object class="NSButtonCell" id="820968178">
-                                                                                                       <int key="NSCellFlags">603979776</int>
-                                                                                                       <int key="NSCellFlags2">0</int>
-                                                                                                       <string key="NSContents">Move the mouse</string>
-                                                                                                       <reference key="NSSupport" ref="45863614"/>
-                                                                                                       <reference key="NSControlView" ref="120408205"/>
-                                                                                                       <int key="NSButtonFlags">1211912448</int>
-                                                                                                       <int key="NSButtonFlags2">0</int>
-                                                                                                       <reference key="NSNormalImage" ref="421587711"/>
-                                                                                                       <reference key="NSAlternateImage" ref="68833793"/>
-                                                                                                       <string key="NSAlternateContents"/>
-                                                                                                       <int key="NSPeriodicDelay">400</int>
-                                                                                                       <int key="NSPeriodicInterval">75</int>
-                                                                                               </object>
-                                                                                               <object class="NSButtonCell" id="275466816">
-                                                                                                       <int key="NSCellFlags">603979776</int>
-                                                                                                       <int key="NSCellFlags2">0</int>
-                                                                                                       <string key="NSContents">Press a mouse button</string>
-                                                                                                       <reference key="NSSupport" ref="45863614"/>
-                                                                                                       <reference key="NSControlView" ref="120408205"/>
-                                                                                                       <int key="NSButtonFlags">1211912448</int>
-                                                                                                       <int key="NSButtonFlags2">0</int>
-                                                                                                       <reference key="NSNormalImage" ref="421587711"/>
-                                                                                                       <reference key="NSAlternateImage" ref="68833793"/>
-                                                                                                       <string key="NSAlternateContents"/>
-                                                                                                       <int key="NSPeriodicDelay">400</int>
-                                                                                                       <int key="NSPeriodicInterval">75</int>
-                                                                                               </object>
-                                                                                               <object class="NSButtonCell" id="134694197">
-                                                                                                       <int key="NSCellFlags">603979776</int>
-                                                                                                       <int key="NSCellFlags2">0</int>
-                                                                                                       <string key="NSContents">Scroll the mouse</string>
-                                                                                                       <reference key="NSSupport" ref="45863614"/>
-                                                                                                       <reference key="NSControlView" ref="120408205"/>
-                                                                                                       <int key="NSButtonFlags">1211912448</int>
-                                                                                                       <int key="NSButtonFlags2">0</int>
-                                                                                                       <reference key="NSNormalImage" ref="421587711"/>
-                                                                                                       <reference key="NSAlternateImage" ref="68833793"/>
-                                                                                                       <string key="NSAlternateContents"/>
-                                                                                                       <int key="NSPeriodicDelay">400</int>
-                                                                                                       <int key="NSPeriodicInterval">75</int>
-                                                                                               </object>
-                                                                                       </array>
-                                                                                       <string key="NSCellSize">{200, 41}</string>
-                                                                                       <string key="NSIntercellSpacing">{4, 2}</string>
-                                                                                       <int key="NSMatrixFlags">1353195520</int>
-                                                                                       <string key="NSCellClass">NSActionCell</string>
-                                                                                       <object class="NSButtonCell" key="NSProtoCell" id="632642090">
-                                                                                               <int key="NSCellFlags">603979776</int>
-                                                                                               <int key="NSCellFlags2">0</int>
-                                                                                               <string key="NSContents">Radio</string>
-                                                                                               <reference key="NSSupport" ref="45863614"/>
-                                                                                               <int key="NSButtonFlags">1211912448</int>
-                                                                                               <int key="NSButtonFlags2">0</int>
-                                                                                               <reference key="NSNormalImage" ref="421587711"/>
-                                                                                               <reference key="NSAlternateImage" ref="68833793"/>
-                                                                                               <string key="NSAlternateContents"/>
-                                                                                               <int key="NSPeriodicDelay">400</int>
-                                                                                               <int key="NSPeriodicInterval">75</int>
-                                                                                       </object>
-                                                                                       <int key="NSSelectedRow">-1</int>
-                                                                                       <int key="NSSelectedCol">-1</int>
-                                                                                       <object class="NSColor" key="NSBackgroundColor" id="473846075">
-                                                                                               <int key="NSColorSpace">6</int>
-                                                                                               <string key="NSCatalogName">System</string>
-                                                                                               <string key="NSColorName">controlColor</string>
-                                                                                               <reference key="NSColor" ref="783809746"/>
-                                                                                       </object>
-                                                                                       <reference key="NSCellBackgroundColor" ref="214000480"/>
-                                                                                       <reference key="NSFont" ref="45863614"/>
-                                                                               </object>
-                                                                               <object class="NSTextField" id="1016088174">
-                                                                                       <reference key="NSNextResponder" ref="606740242"/>
-                                                                                       <int key="NSvFlags">266</int>
-                                                                                       <string key="NSFrame">{{0, 289}, {429, 17}}</string>
-                                                                                       <reference key="NSSuperview" ref="606740242"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView" ref="497528019"/>
-                                                                                       <bool key="NSEnabled">YES</bool>
-                                                                                       <object class="NSTextFieldCell" key="NSCell" id="853503577">
-                                                                                               <int key="NSCellFlags">67108928</int>
-                                                                                               <int key="NSCellFlags2">138414656</int>
-                                                                                               <string key="NSContents"/>
-                                                                                               <object class="NSFont" key="NSSupport">
-                                                                                                       <string key="NSName">LucidaGrande-Bold</string>
-                                                                                                       <double key="NSSize">13</double>
-                                                                                                       <int key="NSfFlags">16</int>
-                                                                                               </object>
-                                                                                               <string key="NSPlaceholderString">No input selected</string>
-                                                                                               <reference key="NSControlView" ref="1016088174"/>
-                                                                                               <reference key="NSBackgroundColor" ref="473846075"/>
-                                                                                               <reference key="NSTextColor" ref="813255721"/>
-                                                                                       </object>
-                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                               </object>
-                                                                               <object class="NSBox" id="497528019">
-                                                                                       <reference key="NSNextResponder" ref="606740242"/>
-                                                                                       <int key="NSvFlags">10</int>
-                                                                                       <string key="NSFrame">{{12, 278}, {405, 5}}</string>
-                                                                                       <reference key="NSSuperview" ref="606740242"/>
-                                                                                       <reference key="NSWindow"/>
-                                                                                       <reference key="NSNextKeyView" ref="120408205"/>
-                                                                                       <string key="NSOffsets">{0, 0}</string>
-                                                                                       <object class="NSTextFieldCell" key="NSTitleCell">
-                                                                                               <int key="NSCellFlags">67108864</int>
-                                                                                               <int key="NSCellFlags2">0</int>
-                                                                                               <string key="NSContents">Box</string>
-                                                                                               <reference key="NSSupport" ref="45863614"/>
-                                                                                               <object class="NSColor" key="NSBackgroundColor">
-                                                                                                       <int key="NSColorSpace">6</int>
-                                                                                                       <string key="NSCatalogName">System</string>
-                                                                                                       <string key="NSColorName">textBackgroundColor</string>
-                                                                                                       <reference key="NSColor" ref="214000480"/>
-                                                                                               </object>
-                                                                                               <object class="NSColor" key="NSTextColor">
-                                                                                                       <int key="NSColorSpace">3</int>
-                                                                                                       <bytes key="NSWhite">MCAwLjgwMDAwMDAxAA</bytes>
-                                                                                               </object>
-                                                                                       </object>
-                                                                                       <int key="NSBorderType">3</int>
-                                                                                       <int key="NSBoxType">2</int>
-                                                                                       <int key="NSTitlePosition">0</int>
-                                                                                       <bool key="NSTransparent">NO</bool>
-                                                                               </object>
-                                                                       </array>
-                                                                       <string key="NSFrame">{{211, 0}, {429, 320}}</string>
-                                                                       <reference key="NSSuperview" ref="206489479"/>
-                                                                       <reference key="NSWindow"/>
-                                                                       <reference key="NSNextKeyView" ref="1016088174"/>
-                                                                       <string key="NSClassName">NSView</string>
-                                                               </object>
-                                                       </array>
-                                                       <string key="NSFrameSize">{640, 320}</string>
-                                                       <reference key="NSSuperview" ref="177223957"/>
-                                                       <reference key="NSWindow"/>
-                                                       <reference key="NSNextKeyView" ref="977242492"/>
-                                                       <bool key="NSIsVertical">YES</bool>
-                                                       <string key="NSAutosaveName">Main Split</string>
-                                                       <object class="NSMutableDictionary" key="NSHoldingPriorities">
-                                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                                               <array key="dict.sortedKeys">
-                                                                       <integer value="0"/>
-                                                                       <integer value="1"/>
-                                                               </array>
-                                                               <array key="dict.values">
-                                                                       <real value="250"/>
-                                                                       <real value="250"/>
-                                                               </array>
-                                                       </object>
-                                               </object>
-                                       </array>
-                                       <string key="NSFrameSize">{640, 320}</string>
-                                       <reference key="NSSuperview"/>
-                                       <reference key="NSWindow"/>
-                                       <reference key="NSNextKeyView" ref="206489479"/>
-                               </object>
-                               <string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
-                               <string key="NSMinSize">{640, 375}</string>
-                               <string key="NSMaxSize">{10000000000000, 10000000000000}</string>
-                               <string key="NSFrameAutosaveName">Enjoyable</string>
-                               <bool key="NSWindowIsRestorable">YES</bool>
-                       </object>
-                       <object class="NSCustomView" id="671181514">
-                               <reference key="NSNextResponder"/>
-                               <int key="NSvFlags">256</int>
-                               <array class="NSMutableArray" key="NSSubviews">
-                                       <object class="NSScrollView" id="443618264">
-                                               <reference key="NSNextResponder" ref="671181514"/>
-                                               <int key="NSvFlags">274</int>
-                                               <array class="NSMutableArray" key="NSSubviews">
-                                                       <object class="NSClipView" id="947403733">
-                                                               <reference key="NSNextResponder" ref="443618264"/>
-                                                               <int key="NSvFlags">2304</int>
-                                                               <array class="NSMutableArray" key="NSSubviews">
-                                                                       <object class="NSTableView" id="762432499">
-                                                                               <reference key="NSNextResponder" ref="947403733"/>
-                                                                               <int key="NSvFlags">256</int>
-                                                                               <string key="NSFrameSize">{198, 198}</string>
-                                                                               <reference key="NSSuperview" ref="947403733"/>
-                                                                               <reference key="NSWindow"/>
-                                                                               <reference key="NSNextKeyView" ref="968378655"/>
-                                                                               <bool key="NSEnabled">YES</bool>
-                                                                               <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
-                                                                               <bool key="NSControlAllowsExpansionToolTips">YES</bool>
-                                                                        &nb