From bdd3587bb3eadc9ddfe2ae385134de25bc13d770 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Wed, 13 Mar 2013 18:32:10 +0100 Subject: [PATCH 01/16] Style issues. Remove dead code. --- Classes/NJDevice.m | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Classes/NJDevice.m b/Classes/NJDevice.m index a5efd96..3b0cc6a 100644 --- a/Classes/NJDevice.m +++ b/Classes/NJDevice.m @@ -61,16 +61,16 @@ static NSArray *InputsForElement(IOHIDDeviceRef device, id base) { } @implementation NJDevice { - int vendorId; - int productId; + int _vendorId; + int _productId; } - (id)initWithDevice:(IOHIDDeviceRef)dev { if ((self = [super initWithName:nil did:nil base:nil])) { 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]; + _vendorId = [(__bridge NSNumber *)IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDVendorIDKey)) intValue]; + _productId = [(__bridge NSNumber *)IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDProductIDKey)) intValue]; self.children = InputsForElement(dev, self); } return self; @@ -80,12 +80,8 @@ static NSArray *InputsForElement(IOHIDDeviceRef device, id base) { return [NSString stringWithFormat:@"%@ #%d", _productName, _index]; } -- (id)base { - return nil; -} - - (NSString *)uid { - return [NSString stringWithFormat: @"%d:%d:%d", vendorId, productId, _index]; + return [NSString stringWithFormat: @"%d:%d:%d", _vendorId, _productId, _index]; } - (NJInput *)findInputByCookie:(IOHIDElementCookie)cookie { -- 2.20.1 From 9923a1899abb5f85259cc912a43435dd5a3ae6a7 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Wed, 13 Mar 2013 18:33:46 +0100 Subject: [PATCH 02/16] Add mapping for 6180 the moon. --- Website/mappings/6180 the moon.enjoyable | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Website/mappings/6180 the moon.enjoyable diff --git a/Website/mappings/6180 the moon.enjoyable b/Website/mappings/6180 the moon.enjoyable new file mode 100644 index 0000000..047f1fa --- /dev/null +++ b/Website/mappings/6180 the moon.enjoyable @@ -0,0 +1,45 @@ +{ + "name" : "6180 the moon", + "entries" : { + "1356:616:1~Button 8" : { + "type" : "key press", + "key" : 123 + }, + "1356:616:1~Button 7" : { + "type" : "key press", + "key" : 125 + }, + "1356:616:1~Button 6" : { + "type" : "key press", + "key" : 124 + }, + "1356:616:1~Button 5" : { + "type" : "key press", + "key" : 126 + }, + "1356:616:1~Button 16" : { + "type" : "key press", + "key" : 49 + }, + "1356:616:1~Button 4" : { + "type" : "key press", + "key" : 53 + }, + "1356:616:1~Button 15" : { + "type" : "key press", + "key" : 126 + }, + "1356:616:1~Button 1" : { + "type" : "key press", + "key" : 15 + }, + "1356:616:1~Button 12" : { + "type" : "key press", + "key" : 49 + }, + "1356:616:1~Button 14" : { + "type" : "key press", + "key" : 49 + } + } +} \ No newline at end of file -- 2.20.1 From f8ae7787fe3fe272eaf04586e3007339c907fb33 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Wed, 13 Mar 2013 18:34:41 +0100 Subject: [PATCH 03/16] Support relaunching in the background as part of resume/launch items. --- Categories/NSRunningApplication+LoginItem.h | 19 +++++ Categories/NSRunningApplication+LoginItem.m | 89 +++++++++++++++++++++ Classes/EnjoyableApplicationDelegate.m | 14 +++- Enjoyable.xcodeproj/project.pbxproj | 6 ++ Info.plist | 2 +- Other Sources/Enjoyable_Prefix.pch | 1 + Resources/English.lproj/MainMenu.xib | 6 +- Website/index.html | 1 + 8 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 Categories/NSRunningApplication+LoginItem.h create mode 100644 Categories/NSRunningApplication+LoginItem.m diff --git a/Categories/NSRunningApplication+LoginItem.h b/Categories/NSRunningApplication+LoginItem.h new file mode 100644 index 0000000..da5dd19 --- /dev/null +++ b/Categories/NSRunningApplication+LoginItem.h @@ -0,0 +1,19 @@ +// +// NSRunningApplication+LoginItem.h +// Enjoyable +// +// Created by Joe Wreschnig on 3/13/13. +// +// + +#import + +@interface NSRunningApplication (LoginItem) + // Don't be a jerk. Ask the user before doing this. + +- (BOOL)isLoginItem; +- (void)addToLoginItems; +- (void)removeFromLoginItems; +- (BOOL)wasLaunchedAsLoginItemOrResume; + +@end diff --git a/Categories/NSRunningApplication+LoginItem.m b/Categories/NSRunningApplication+LoginItem.m new file mode 100644 index 0000000..53a9521 --- /dev/null +++ b/Categories/NSRunningApplication+LoginItem.m @@ -0,0 +1,89 @@ +// +// NSApplication+LoginItem.m +// Enjoyable +// +// Created by Joe Wreschnig on 3/13/13. +// +// + +#import "NSRunningApplication+LoginItem.h" + +#import + +static const UInt32 RESOLVE_FLAGS = kLSSharedFileListNoUserInteraction + | kLSSharedFileListDoNotMountVolumes; + +@implementation NSRunningApplication (LoginItem) + +- (BOOL)isLoginItem { + LSSharedFileListRef loginItems = LSSharedFileListCreate( + NULL, kLSSharedFileListSessionLoginItems, NULL); + NSURL *myURL = self.bundleURL; + BOOL found = NO; + UInt32 seed = 0; + NSArray *currentLoginItems = CFBridgingRelease( + LSSharedFileListCopySnapshot(loginItems, &seed)); + for (id obj in currentLoginItems) { + LSSharedFileListItemRef item = (__bridge LSSharedFileListItemRef)obj; + CFURLRef itemURL = NULL; + if (!LSSharedFileListItemResolve(item, RESOLVE_FLAGS, &itemURL, NULL)) { + found = CFEqual(itemURL, (__bridge CFURLRef)myURL); + CFRelease(itemURL); + } + if (found) + break; + } + CFRelease(loginItems); + return found; +} + +- (void)addToLoginItems { + if (!self.isLoginItem) { + NSURL *myURL = self.bundleURL; + LSSharedFileListRef loginItems = LSSharedFileListCreate( + NULL, kLSSharedFileListSessionLoginItems, NULL); + LSSharedFileListInsertItemURL( + loginItems, kLSSharedFileListItemBeforeFirst, + NULL, NULL, (__bridge CFURLRef)myURL, NULL, NULL); + CFRelease(loginItems); + } +} + +- (void)removeFromLoginItems { + LSSharedFileListRef loginItems = LSSharedFileListCreate( + NULL, kLSSharedFileListSessionLoginItems, NULL); + NSURL *myURL = self.bundleURL; + UInt32 seed = 0; + NSArray *currentLoginItems = CFBridgingRelease( + LSSharedFileListCopySnapshot(loginItems, &seed)); + for (id obj in currentLoginItems) { + LSSharedFileListItemRef item = (__bridge LSSharedFileListItemRef)obj; + CFURLRef itemURL = NULL; + if (!LSSharedFileListItemResolve(item, RESOLVE_FLAGS, &itemURL, NULL)) { + if (CFEqual(itemURL, (__bridge CFURLRef)myURL)) + LSSharedFileListItemRemove(loginItems, item); + CFRelease(itemURL); + } + } + CFRelease(loginItems); +} + +- (BOOL)wasLaunchedAsLoginItemOrResume { + ProcessSerialNumber psn = { 0, kCurrentProcess }; + NSDictionary *processInfo = CFBridgingRelease( + ProcessInformationCopyDictionary( + &psn, kProcessDictionaryIncludeAllInformationMask)); + long long parent = [processInfo[@"ParentPSN"] longLongValue]; + ProcessSerialNumber parentPsn = { + (parent >> 32) & 0x00000000FFFFFFFFLL, + parent & 0x00000000FFFFFFFFLL + }; + + NSDictionary *parentInfo = CFBridgingRelease( + ProcessInformationCopyDictionary( + &parentPsn, kProcessDictionaryIncludeAllInformationMask)); + return [parentInfo[@"FileCreator"] isEqualToString:@"lgnw"]; +} + + +@end diff --git a/Classes/EnjoyableApplicationDelegate.m b/Classes/EnjoyableApplicationDelegate.m index f151399..f02ec90 100644 --- a/Classes/EnjoyableApplicationDelegate.m +++ b/Classes/EnjoyableApplicationDelegate.m @@ -48,7 +48,14 @@ } - (void)applicationDidFinishLaunching:(NSNotification *)notification { - [window makeKeyAndOrderFront:nil]; + if (NSRunningApplication.currentApplication.wasLaunchedAsLoginItemOrResume + && [NSUserDefaults.standardUserDefaults boolForKey:@"hidden in status item"]) { + [self transformIntoElement:self]; + NSApplication *app = notification.object; + [app deactivate]; + } else { + [window makeKeyAndOrderFront:nil]; + } } - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication @@ -65,15 +72,18 @@ [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(transformIntoElement:) object:self]; + [NSUserDefaults.standardUserDefaults setBool:NO forKey:@"hidden in status item"]; } - (void)applicationWillBecomeActive:(NSNotification *)notification { - [self restoreToForeground:notification]; + if (window.isVisible) + [self restoreToForeground:notification]; } - (void)transformIntoElement:(id)sender { ProcessSerialNumber psn = { 0, kCurrentProcess }; TransformProcessType(&psn, kProcessTransformToUIElementApplication); + [NSUserDefaults.standardUserDefaults setBool:YES forKey:@"hidden in status item"]; } - (void)flashStatusItem { diff --git a/Enjoyable.xcodeproj/project.pbxproj b/Enjoyable.xcodeproj/project.pbxproj index eee41a1..d428220 100644 --- a/Enjoyable.xcodeproj/project.pbxproj +++ b/Enjoyable.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ EE3D897F16EA817E00596D1F /* Status Menu Icon Disabled.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897D16EA817E00596D1F /* Status Menu Icon Disabled.png */; }; EE3D898016EA817E00596D1F /* Status Menu Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897E16EA817E00596D1F /* Status Menu Icon.png */; }; EE6A122E16E8F46300EDBD32 /* Icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = EE6A122D16E8F46300EDBD32 /* Icon.icns */; }; + EE8455DD16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8455DC16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m */; }; EED4CE6E16ED692400C65AA8 /* NJMappingMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */; }; EED4CE7716EE195100C65AA8 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EED4CE7616EE195100C65AA8 /* Sparkle.framework */; }; EED4CE7816EE195B00C65AA8 /* Sparkle.framework in Copy Sparkle Framework */ = {isa = PBXBuildFile; fileRef = EED4CE7616EE195100C65AA8 /* Sparkle.framework */; }; @@ -78,6 +79,8 @@ EE3D897D16EA817E00596D1F /* Status Menu Icon Disabled.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon Disabled.png"; path = "Resources/Status Menu Icon Disabled.png"; sourceTree = ""; }; EE3D897E16EA817E00596D1F /* Status Menu Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon.png"; path = "Resources/Status Menu Icon.png"; sourceTree = ""; }; EE6A122D16E8F46300EDBD32 /* Icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Icon.icns; sourceTree = ""; }; + EE8455DB16F0E46B00F32A01 /* NSRunningApplication+LoginItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSRunningApplication+LoginItem.h"; path = "Categories/NSRunningApplication+LoginItem.h"; sourceTree = ""; }; + EE8455DC16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSRunningApplication+LoginItem.m"; path = "Categories/NSRunningApplication+LoginItem.m"; sourceTree = ""; }; EED4CE6C16ED692400C65AA8 /* NJMappingMenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NJMappingMenuController.h; path = Classes/NJMappingMenuController.h; sourceTree = ""; }; EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NJMappingMenuController.m; path = Classes/NJMappingMenuController.m; sourceTree = ""; }; EED4CE7616EE195100C65AA8 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = ""; }; @@ -283,6 +286,8 @@ EE1D5F8B16E403D600749C36 /* Categories */ = { isa = PBXGroup; children = ( + EE8455DB16F0E46B00F32A01 /* NSRunningApplication+LoginItem.h */, + EE8455DC16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m */, EEF17D2716E8E2E100D7DC4D /* NSError+Description.h */, EEF17D2816E8E2E100D7DC4D /* NSError+Description.m */, EEF17D2916E8E2E100D7DC4D /* NSFileManager+UniqueNames.h */, @@ -436,6 +441,7 @@ EED4CE6E16ED692400C65AA8 /* NJMappingMenuController.m in Sources */, EEE703DC16F089FE002FDD69 /* NJHIDManager.m in Sources */, EEE703DE16F0B3F6002FDD69 /* NJInputPathElement.m in Sources */, + EE8455DD16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Info.plist b/Info.plist index f3cc683..6191eaa 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 248 + 258 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright diff --git a/Other Sources/Enjoyable_Prefix.pch b/Other Sources/Enjoyable_Prefix.pch index 61d6aee..b0d1f40 100644 --- a/Other Sources/Enjoyable_Prefix.pch +++ b/Other Sources/Enjoyable_Prefix.pch @@ -15,3 +15,4 @@ #import "NSFileManager+UniqueNames.h" #import "NSString+FixFilename.h" #import "NSRunningApplication+NJPossibleNames.h" +#import "NSRunningApplication+LoginItem.h" diff --git a/Resources/English.lproj/MainMenu.xib b/Resources/English.lproj/MainMenu.xib index c63659b..98b5cef 100644 --- a/Resources/English.lproj/MainMenu.xib +++ b/Resources/English.lproj/MainMenu.xib @@ -567,7 +567,7 @@ aW5nLg {232, 321} - + YES NO YES @@ -697,7 +697,7 @@ aW5nLg {234, 323} - + 150034 @@ -3026,7 +3026,7 @@ aW5nLg com.apple.InterfaceBuilder.CocoaPlugin {{114, 276}, {770, 487}} - + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin diff --git a/Website/index.html b/Website/index.html index 6525b71..03d3468 100644 --- a/Website/index.html +++ b/Website/index.html @@ -51,6 +51,7 @@ them.

    +
  • 6180 the moon
  • Anodyne
  • BasketBelle
  • Canabalt
  • -- 2.20.1 From 466bd075d9b9da440240a02ef82ce39253fb4220 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Wed, 13 Mar 2013 19:06:45 +0100 Subject: [PATCH 04/16] UI element applications don't restore correctly, this needs more work/testing. --- Classes/EnjoyableApplicationDelegate.m | 9 +-------- Info.plist | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Classes/EnjoyableApplicationDelegate.m b/Classes/EnjoyableApplicationDelegate.m index f02ec90..411067e 100644 --- a/Classes/EnjoyableApplicationDelegate.m +++ b/Classes/EnjoyableApplicationDelegate.m @@ -48,14 +48,7 @@ } - (void)applicationDidFinishLaunching:(NSNotification *)notification { - if (NSRunningApplication.currentApplication.wasLaunchedAsLoginItemOrResume - && [NSUserDefaults.standardUserDefaults boolForKey:@"hidden in status item"]) { - [self transformIntoElement:self]; - NSApplication *app = notification.object; - [app deactivate]; - } else { - [window makeKeyAndOrderFront:nil]; - } + [window makeKeyAndOrderFront:nil]; } - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication diff --git a/Info.plist b/Info.plist index 6191eaa..2b34274 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 258 + 259 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright -- 2.20.1 From b417eb4ae97804070277eb19cc596bdb6bb9d281 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Wed, 13 Mar 2013 21:10:54 +0100 Subject: [PATCH 05/16] Second try at login items. If the user hides Enjoyable in the status bar, explain and ask what to do. Try to have least-annoying interactions with Sparkle updater's additional prompt. --- Classes/EnjoyableApplicationDelegate.h | 3 +- Classes/EnjoyableApplicationDelegate.m | 49 +++++++++++++++++++- Info.plist | 4 +- Resources/English.lproj/Localizable.strings | Bin 6424 -> 7520 bytes Resources/English.lproj/MainMenu.xib | 10 +++- 5 files changed, 61 insertions(+), 5 deletions(-) diff --git a/Classes/EnjoyableApplicationDelegate.h b/Classes/EnjoyableApplicationDelegate.h index f61578b..acea8a9 100644 --- a/Classes/EnjoyableApplicationDelegate.h +++ b/Classes/EnjoyableApplicationDelegate.h @@ -11,7 +11,8 @@ #import "NJMappingMenuController.h" @interface EnjoyableApplicationDelegate : NSObject { + NJMappingMenuDelegate, + NSWindowDelegate> { IBOutlet NSMenu *dockMenu; IBOutlet NSMenu *statusItemMenu; IBOutlet NSWindow *window; diff --git a/Classes/EnjoyableApplicationDelegate.m b/Classes/EnjoyableApplicationDelegate.m index 411067e..b1d9907 100644 --- a/Classes/EnjoyableApplicationDelegate.m +++ b/Classes/EnjoyableApplicationDelegate.m @@ -5,6 +5,8 @@ // Created by Sam McCall on 4/05/09. // +#import + #import "EnjoyableApplicationDelegate.h" #import "NJMapping.h" @@ -48,7 +50,11 @@ } - (void)applicationDidFinishLaunching:(NSNotification *)notification { - [window makeKeyAndOrderFront:nil]; + if ([NSUserDefaults.standardUserDefaults boolForKey:@"hidden in status item"] + && NSRunningApplication.currentApplication.wasLaunchedAsLoginItemOrResume) + [self transformIntoElement:nil]; + else + [window makeKeyAndOrderFront:nil]; } - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication @@ -143,5 +149,46 @@ [self.mappingsController mappingPressed:self]; } +- (void)loginItemPromptDidEnd:(NSWindow *)sheet + returnCode:(int)returnCode + contextInfo:(void *)contextInfo { + if (returnCode == NSAlertDefaultReturn) { + [NSRunningApplication.currentApplication addToLoginItems]; + // If we're going to automatically start, don't bug the user + // about automatic updates next boot - they probably want it, + // and if they don't they probably want a prompt for it less. + SUUpdater.sharedUpdater.automaticallyChecksForUpdates = YES; + } +} + +- (void)loginItemPromptDidDismiss:(NSWindow *)sheet + returnCode:(int)returnCode + contextInfo:(void *)contextInfo { + [NSUserDefaults.standardUserDefaults setBool:YES forKey:@"explained login items"]; + [window performClose:sheet]; +} + +- (BOOL)windowShouldClose:(NSWindow *)sender { + if (sender != window + || NSRunningApplication.currentApplication.isLoginItem + || [NSUserDefaults.standardUserDefaults boolForKey:@"explained login items"]) + return YES; + NSBeginAlertSheet( + NSLocalizedString(@"login items prompt", @"alert prompt for adding to login items"), + NSLocalizedString(@"login items add button", @"button to add to login items"), + NSLocalizedString(@"login items don't add button", @"button to not add to login items"), + nil, window, self, + @selector(loginItemPromptDidEnd:returnCode:contextInfo:), + @selector(loginItemPromptDidDismiss:returnCode:contextInfo:), + NULL, + NSLocalizedString(@"login items explanation", @"a brief explanation of login items") + ); + for (int i = 0; i < 10; ++i) + [self performSelector:@selector(flashStatusItem) + withObject:self + afterDelay:0.5 * i]; + return NO; +} + @end diff --git a/Info.plist b/Info.plist index 2b34274..285b858 100644 --- a/Info.plist +++ b/Info.plist @@ -42,11 +42,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 1.1 CFBundleSignature ???? CFBundleVersion - 259 + 294 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright diff --git a/Resources/English.lproj/Localizable.strings b/Resources/English.lproj/Localizable.strings index 564ef88d1ae705702f756a03d3bddd1b4785727c..8b9a71782f2bf1dcc87e4fa301c7df7b43d4a61f 100644 GIT binary patch delta 953 zcmbu8ze)o^5XJ|q7(0uAg}B5*2ok|kku+LqAy|kY!sYIw(PR_uE@-MegIuAnU?=zn zR(8IEkK%9kviXAnK{@tzcXsESZ)U!I-u>Epy{g;=(vesa$;3#XxhGv_-L+UJ%z;FF zZLxCAF2ic@>9JFr;h%xfm6UZ&cBCxT#pn5Fd988A?O+`52>)A(-lXp zBC3$kQB$Hy&?70`h*Q;Wiv8Fh%y9Z~IYodf=#ZQYq7KNsk443w67-T4BY#@nczj>A h`g%Gj)pIP~(V_swTgjZd{q6aTe$~FbomM`Uz5z(5uK54} delta 16 YcmaE0HN$8_oY>@DEFznCNiAan06ho>od5s; diff --git a/Resources/English.lproj/MainMenu.xib b/Resources/English.lproj/MainMenu.xib index 98b5cef..2a95667 100644 --- a/Resources/English.lproj/MainMenu.xib +++ b/Resources/English.lproj/MainMenu.xib @@ -1629,6 +1629,14 @@ aW5nLg 941 + + + delegate + + + + 977 + delegate @@ -3259,7 +3267,7 @@ aW5nLg - 976 + 977 -- 2.20.1 From 561fa1774ce33b5a4a8112ba5802cc33a2ca819d Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Thu, 14 Mar 2013 14:02:06 +0100 Subject: [PATCH 06/16] Write proper constructors for NJInput and subclasses. Rename base to parent to match children, d(evice internal)id to e(lement)id to avoid confusion with actual device IDs/UID. --- Classes/NJDevice.m | 40 ++++++++++++++++++------------------ Classes/NJDeviceController.m | 4 ++-- Classes/NJInput.h | 9 +++++--- Classes/NJInput.m | 14 ++++++++----- Classes/NJInputAnalog.h | 4 +++- Classes/NJInputAnalog.m | 34 ++++++++++++++++-------------- Classes/NJInputButton.h | 4 +++- Classes/NJInputButton.m | 18 ++++++++-------- Classes/NJInputHat.h | 4 +++- Classes/NJInputHat.m | 37 +++++++++++++++++++-------------- Classes/NJInputPathElement.h | 6 +++--- Classes/NJInputPathElement.m | 12 +++++------ Classes/NJOutputController.m | 2 +- Info.plist | 2 +- 14 files changed, 108 insertions(+), 82 deletions(-) diff --git a/Classes/NJDevice.m b/Classes/NJDevice.m index 3b0cc6a..e4c6b3d 100644 --- a/Classes/NJDevice.m +++ b/Classes/NJDevice.m @@ -12,7 +12,7 @@ #import "NJInputHat.h" #import "NJInputButton.h" -static NSArray *InputsForElement(IOHIDDeviceRef device, id base) { +static NSArray *InputsForElement(IOHIDDeviceRef device, id parent) { CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone); NSMutableArray *children = [NSMutableArray arrayWithCapacity:CFArrayGetCount(elements)]; @@ -20,14 +20,13 @@ static NSArray *InputsForElement(IOHIDDeviceRef device, id base) { int axes = 0; int hats = 0; - for (int i = 0; i < CFArrayGetCount(elements); i++) { + for (CFIndex 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); + IOHIDElementType type = IOHIDElementGetType(element); + uint32_t usage = IOHIDElementGetUsage(element); + uint32_t usagePage = IOHIDElementGetUsagePage(element); + CFIndex max = IOHIDElementGetPhysicalMax(element); + CFIndex min = IOHIDElementGetPhysicalMin(element); NJInput *input = nil; @@ -36,23 +35,24 @@ static NSArray *InputsForElement(IOHIDDeviceRef device, id base) { || 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]; + if (max - min == 1 + || usagePage == kHIDPage_Button + || type == kIOHIDElementTypeInput_Button) { + input = [[NJInputButton alloc] initWithElement:element + index:++buttons + parent:parent]; } else if (usage == kHIDUsage_GD_Hatswitch) { - input = [[NJInputHat alloc] initWithIndex:++hats]; + input = [[NJInputHat alloc] initWithElement:element + index:++hats + parent:parent]; } else if (usage >= kHIDUsage_GD_X && usage <= kHIDUsage_GD_Rz) { - input = [[NJInputAnalog alloc] initWithIndex:++axes - rawMin:min - rawMax:max]; + input = [[NJInputAnalog alloc] initWithElement:element + index:++axes + parent:parent]; } else { continue; } - // TODO(jfw): Should be moved into better constructors. - input.base = base; - input.cookie = IOHIDElementGetCookie(element); [children addObject:input]; } @@ -66,7 +66,7 @@ static NSArray *InputsForElement(IOHIDDeviceRef device, id base) { } - (id)initWithDevice:(IOHIDDeviceRef)dev { - if ((self = [super initWithName:nil did:nil base:nil])) { + if ((self = [super initWithName:nil eid:nil parent:nil])) { self.device = dev; self.productName = (__bridge NSString *)IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDProductKey)); _vendorId = [(__bridge NSNumber *)IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDVendorIDKey)) intValue]; diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index 6fc4d13..8cc5f2a 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -83,7 +83,7 @@ - (void)expandRecursive:(NJInputPathElement *)pathElement { if (pathElement) { - [self expandRecursive:pathElement.base]; + [self expandRecursive:pathElement.parent]; [outlineView expandItem:pathElement]; } } @@ -251,7 +251,7 @@ static int findAvailableIndex(NSArray *list, NJDevice *dev) { - (NJInput *)selectedInput { NJInputPathElement *item = [outlineView itemAtRow:outlineView.selectedRow]; - return (NJInput *)((!item.children && item.base) ? item : nil); + return (NJInput *)((!item.children && item.parent) ? item : nil); } - (NSInteger)outlineView:(NSOutlineView *)outlineView diff --git a/Classes/NJInput.h b/Classes/NJInput.h index b9d2dca..db47121 100644 --- a/Classes/NJInput.h +++ b/Classes/NJInput.h @@ -10,10 +10,13 @@ @interface NJInput : NJInputPathElement +#define NJINPUT_DID(name, index) [[NSString alloc] initWithFormat:@"%s %d", name, index] +#define NJINPUT_NAME(name, index) [[NSString alloc] initWithFormat:name, index] + - (id)initWithName:(NSString *)name - did:(NSString *)did - cookie:(IOHIDElementCookie)cookie - base:(NJInputPathElement *)base; + eid:(NSString *)eid + element:(IOHIDElementRef)element + parent:(NJInputPathElement *)parent; @property (nonatomic, assign) IOHIDElementCookie cookie; @property (nonatomic, assign) BOOL active; diff --git a/Classes/NJInput.m b/Classes/NJInput.m index 78376e6..82d5993 100644 --- a/Classes/NJInput.m +++ b/Classes/NJInput.m @@ -10,11 +10,15 @@ @implementation NJInput - (id)initWithName:(NSString *)name - did:(NSString *)did - cookie:(IOHIDElementCookie)cookie - base:(NJInputPathElement *)base { - if ((self = [super initWithName:name did:did base:base])) { - self.cookie = cookie; + eid:(NSString *)eid + element:(IOHIDElementRef)element + parent:(NJInputPathElement *)parent +{ + NSString *elementName = (__bridge NSString *)(IOHIDElementGetName(element)); + if (elementName.length) + name = [name stringByAppendingFormat:@"- %@", elementName]; + if ((self = [super initWithName:name eid:eid parent:parent])) { + self.cookie = IOHIDElementGetCookie(element); } return self; } diff --git a/Classes/NJInputAnalog.h b/Classes/NJInputAnalog.h index e79ed89..de35e3d 100644 --- a/Classes/NJInputAnalog.h +++ b/Classes/NJInputAnalog.h @@ -12,6 +12,8 @@ @interface NJInputAnalog : NJInput -- (id)initWithIndex:(int)index rawMin:(long)rawMin rawMax:(long)rawMax; +- (id)initWithElement:(IOHIDElementRef)element + index:(int)index + parent:(NJInputPathElement *)parent; @end diff --git a/Classes/NJInputAnalog.m b/Classes/NJInputAnalog.m index af64954..3746c11 100644 --- a/Classes/NJInputAnalog.m +++ b/Classes/NJInputAnalog.m @@ -9,33 +9,37 @@ #import "NJInputAnalog.h" -static float normalize(long p, long min, long max) { +static float normalize(CFIndex p, CFIndex min, CFIndex max) { return 2 * (p - min) / (float)(max - min) - 1; } @implementation NJInputAnalog { - long rawMin; - long rawMax; + CFIndex _rawMin; + CFIndex _rawMax; } -- (id)initWithIndex:(int)index rawMin:(long)rawMin_ rawMax:(long)rawMax_ { - NSString *name = [[NSString alloc] initWithFormat:NSLocalizedString(@"axis %d", @"axis name"), index]; - NSString *did = [[NSString alloc] initWithFormat:@"Axis %d", index]; - if ((self = [super initWithName:name did:did base:nil])) { +- (id)initWithElement:(IOHIDElementRef)element + index:(int)index + parent:(NJInputPathElement *)parent +{ + if ((self = [super initWithName:NJINPUT_NAME(NSLocalizedString(@"axis %d", @"axis name"), index) + eid:NJINPUT_DID("Axis", index) + element:element + parent:nil])) { self.children = @[[[NJInput alloc] initWithName:NSLocalizedString(@"axis low", @"axis low trigger") - did:@"Low" - base:self], + eid:@"Low" + parent:self], [[NJInput alloc] initWithName:NSLocalizedString(@"axis high", @"axis high trigger") - did:@"High" - base:self]]; - rawMax = rawMax_; - rawMin = rawMin_; + eid:@"High" + parent:self]]; + _rawMax = IOHIDElementGetPhysicalMax(element); + _rawMin = IOHIDElementGetPhysicalMin(element); } return self; } - (id)findSubInputForValue:(IOHIDValueRef)value { - float mag = normalize(IOHIDValueGetIntegerValue(value), rawMin, rawMax); + float mag = normalize(IOHIDValueGetIntegerValue(value), _rawMin, _rawMax); if (mag < -DEAD_ZONE) return self.children[0]; else if (mag > DEAD_ZONE) @@ -45,7 +49,7 @@ static float normalize(long p, long min, long max) { } - (void)notifyEvent:(IOHIDValueRef)value { - float magnitude = self.magnitude = normalize(IOHIDValueGetIntegerValue(value), rawMin, rawMax); + float magnitude = self.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]; diff --git a/Classes/NJInputButton.h b/Classes/NJInputButton.h index 31cba73..e9abdd3 100644 --- a/Classes/NJInputButton.h +++ b/Classes/NJInputButton.h @@ -10,6 +10,8 @@ @interface NJInputButton : NJInput -- (id)initWithName:(NSString *)name idx:(int)idx max:(long)max; +- (id)initWithElement:(IOHIDElementRef)element + index:(int)index + parent:(NJInputPathElement *)parent; @end diff --git a/Classes/NJInputButton.m b/Classes/NJInputButton.m index caa759b..354220a 100644 --- a/Classes/NJInputButton.m +++ b/Classes/NJInputButton.m @@ -8,16 +8,18 @@ #import "NJInputButton.h" @implementation NJInputButton { - long _max; + CFIndex _max; } -- (id)initWithName:(NSString *)name idx:(int)idx max:(long)max { - NSString *fullname = [NSString stringWithFormat:NSLocalizedString(@"button %d", @"button name"), idx]; - if (name.length) - fullname = [fullname stringByAppendingFormat:@"- %@", name]; - NSString *did = [[NSString alloc] initWithFormat:@"Button %d", idx]; - if ((self = [super initWithName:fullname did:did base:nil])) { - _max = max; +- (id)initWithElement:(IOHIDElementRef)element + index:(int)index + parent:(NJInputPathElement *)parent +{ + if ((self = [super initWithName:NJINPUT_NAME(NSLocalizedString(@"button %d", @"button name"), index) + eid:NJINPUT_DID("Button", index) + element:element + parent:parent])) { + _max = IOHIDElementGetLogicalMax(element); } return self; } diff --git a/Classes/NJInputHat.h b/Classes/NJInputHat.h index 5290d71..67bb21d 100644 --- a/Classes/NJInputHat.h +++ b/Classes/NJInputHat.h @@ -10,6 +10,8 @@ @interface NJInputHat : NJInput -- (id)initWithIndex:(int)index; +- (id)initWithElement:(IOHIDElementRef)element + index:(int)index + parent:(NJInputPathElement *)parent; @end diff --git a/Classes/NJInputHat.m b/Classes/NJInputHat.m index 46ea94f..0a5afa2 100644 --- a/Classes/NJInputHat.m +++ b/Classes/NJInputHat.m @@ -27,31 +27,38 @@ static BOOL active_fourway[20] = { NO, NO, YES, NO , // W }; -@implementation NJInputHat +@implementation NJInputHat { + CFIndex _max; +} -- (id)initWithIndex:(int)index { - NSString *name = [NSString stringWithFormat:NSLocalizedString(@"hat switch %d", @"hat switch name"), index]; - NSString *did = [NSString stringWithFormat:@"Hat Switch %d", index]; - if ((self = [super initWithName:name did:did base:nil])) { +- (id)initWithElement:(IOHIDElementRef)element + index:(int)index + parent:(NJInputPathElement *)parent +{ + if ((self = [super initWithName:NJINPUT_NAME(NSLocalizedString(@"hat switch %d", @"hat switch name"), index) + eid:NJINPUT_DID("Hat Switch", index) + element:element + parent:parent])) { self.children = @[[[NJInput alloc] initWithName:NSLocalizedString(@"hat up", @"hat switch up state") - did:@"Up" - base:self], + eid:@"Up" + parent:self], [[NJInput alloc] initWithName:NSLocalizedString(@"hat down", @"hat switch down state") - did:@"Down" - base:self], + eid:@"Down" + parent:self], [[NJInput alloc] initWithName:NSLocalizedString(@"hat left", @"hat switch left state") - did:@"Left" - base:self], + eid:@"Left" + parent:self], [[NJInput alloc] initWithName:NSLocalizedString(@"hat right", @"hat switch right state") - did:@"Right" - base:self]]; + eid:@"Right" + parent:self]]; + _max = IOHIDElementGetLogicalMax(element); } return self; } - (id)findSubInputForValue:(IOHIDValueRef)value { long parsed = IOHIDValueGetIntegerValue(value); - switch (IOHIDElementGetLogicalMax(IOHIDValueGetElement(value))) { + switch (_max) { case 7: // 8-way switch, 0-7. switch (parsed) { case 0: return self.children[0]; @@ -91,7 +98,7 @@ static BOOL active_fourway[20] = { - (void)notifyEvent:(IOHIDValueRef)value { long parsed = IOHIDValueGetIntegerValue(value); - long size = IOHIDElementGetLogicalMax(IOHIDValueGetElement(value)); + long size = _max; // Skip first row in table if 0 is not neutral. if (size & 1) { parsed++; diff --git a/Classes/NJInputPathElement.h b/Classes/NJInputPathElement.h index 3ad50c9..98c96fd 100644 --- a/Classes/NJInputPathElement.h +++ b/Classes/NJInputPathElement.h @@ -8,10 +8,10 @@ @interface NJInputPathElement : NSObject - (id)initWithName:(NSString *)name - did:(NSString *)did - base:(NJInputPathElement *)base; + eid:(NSString *)eid + parent:(NJInputPathElement *)parent; -@property (nonatomic, weak) NJInputPathElement *base; +@property (nonatomic, weak) NJInputPathElement *parent; @property (nonatomic, copy) NSString *name; @property (nonatomic, readonly) NSString *uid; @property (nonatomic, strong) NSArray *children; diff --git a/Classes/NJInputPathElement.m b/Classes/NJInputPathElement.m index eb5bfa7..ca9265e 100644 --- a/Classes/NJInputPathElement.m +++ b/Classes/NJInputPathElement.m @@ -9,16 +9,16 @@ #include "NJInputPathElement.h" @implementation NJInputPathElement { - NSString *_did; + NSString *_eid; } - (id)initWithName:(NSString *)name - did:(NSString *)did - base:(NJInputPathElement *)base { + eid:(NSString *)eid + parent:(NJInputPathElement *)parent { if ((self = [super init])) { self.name = name; - self.base = base; - _did = did; + self.parent = parent; + _eid = eid; } return self; } @@ -33,7 +33,7 @@ } - (NSString *)uid { - return [NSString stringWithFormat:@"%@~%@", _base.uid, _did]; + return [NSString stringWithFormat:@"%@~%@", _parent.uid, _eid]; } - (NJInputPathElement *)elementForUID:(NSString *)uid { diff --git a/Classes/NJOutputController.m b/Classes/NJOutputController.m index 4c8ce64..95d28c4 100644 --- a/Classes/NJOutputController.m +++ b/Classes/NJOutputController.m @@ -229,7 +229,7 @@ } else { self.enabled = YES; NSString *inpFullName = input.name; - for (NJInputPathElement *cur = input.base; cur; cur = cur.base) { + for (NJInputPathElement *cur = input.parent; cur; cur = cur.parent) { inpFullName = [[NSString alloc] initWithFormat:@"%@ ▸ %@", cur.name, inpFullName]; } title.stringValue = inpFullName; diff --git a/Info.plist b/Info.plist index 285b858..bc56ad4 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 294 + 296 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright -- 2.20.1 From eb9ae7c6c0504f07fe058e9be43ec26763be4591 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Thu, 14 Mar 2013 20:05:05 +0100 Subject: [PATCH 07/16] Some more renames. --- Classes/EnjoyableApplicationDelegate.m | 12 ++-- Classes/NJDevice.m | 2 +- Classes/NJDeviceController.h | 4 +- Classes/NJDeviceController.m | 26 ++++---- Classes/NJInput.h | 2 +- Classes/NJInput.m | 4 +- Classes/NJInputAnalog.m | 2 +- Classes/NJMappingMenuController.h | 4 +- Classes/NJMappingMenuController.m | 20 +++--- Info.plist | 2 +- Other Sources/NJEvents.h | 4 +- Resources/English.lproj/Localizable.strings | Bin 7520 -> 7516 bytes Resources/English.lproj/MainMenu.xib | 66 +++++++------------- 13 files changed, 63 insertions(+), 85 deletions(-) diff --git a/Classes/EnjoyableApplicationDelegate.m b/Classes/EnjoyableApplicationDelegate.m index b1d9907..db62f53 100644 --- a/Classes/EnjoyableApplicationDelegate.m +++ b/Classes/EnjoyableApplicationDelegate.m @@ -31,13 +31,13 @@ object:nil]; [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(eventTranslationActivated:) - name:NJEventTranslationActivated + selector:@selector(eventSimulationStarted:) + name:NJEventSimulationStarted object:nil]; [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(eventTranslationDeactivated:) - name:NJEventTranslationDeactivated + selector:@selector(eventSimulationStopped:) + name:NJEventSimulationStopped object:nil]; [self.mappingsController load]; @@ -104,7 +104,7 @@ return NO; } -- (void)eventTranslationActivated:(NSNotification *)note { +- (void)eventSimulationStarted:(NSNotification *)note { statusItem.image = [NSImage imageNamed:@"Status Menu Icon"]; [NSWorkspace.sharedWorkspace.notificationCenter addObserver:self @@ -113,7 +113,7 @@ object:nil]; } -- (void)eventTranslationDeactivated:(NSNotification *)note { +- (void)eventSimulationStopped:(NSNotification *)note { statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"]; [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self diff --git a/Classes/NJDevice.m b/Classes/NJDevice.m index e4c6b3d..b03898f 100644 --- a/Classes/NJDevice.m +++ b/Classes/NJDevice.m @@ -81,7 +81,7 @@ static NSArray *InputsForElement(IOHIDDeviceRef device, id parent) { } - (NSString *)uid { - return [NSString stringWithFormat: @"%d:%d:%d", _vendorId, _productId, _index]; + return [NSString stringWithFormat:@"%d:%d:%d", _vendorId, _productId, _index]; } - (NJInput *)findInputByCookie:(IOHIDElementCookie)cookie { diff --git a/Classes/NJDeviceController.h b/Classes/NJDeviceController.h index f49d647..1391121 100644 --- a/Classes/NJDeviceController.h +++ b/Classes/NJDeviceController.h @@ -26,8 +26,8 @@ @property (nonatomic, readonly) NJInput *selectedInput; @property (nonatomic, assign) NSPoint mouseLoc; -@property (nonatomic, assign) BOOL translatingEvents; +@property (nonatomic, assign) BOOL simulatingEvents; -- (IBAction)translatingEventsChanged:(NSButton *)sender; +- (IBAction)simulatingEventsChanged:(NSButton *)sender; @end diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index 8cc5f2a..39ec389 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -144,7 +144,7 @@ - (void)hidManager:(NJHIDManager *)manager valueChanged:(IOHIDValueRef)value fromDevice:(IOHIDDeviceRef)device { - if (self.translatingEvents) { + if (self.simulatingEvents) { [self runOutputForDevice:device value:value]; } else { [self showOutputForDevice:device value:value]; @@ -222,7 +222,7 @@ static int findAvailableIndex(NSArray *list, NJDevice *dev) { didPresentSelector:nil contextInfo:nil]; } - self.translatingEvents = NO; + self.simulatingEvents = NO; if (manager.running) [self hidManagerDidStart:manager]; else @@ -312,18 +312,18 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn forKey:@"expanded rows"]; } -- (void)setTranslatingEvents:(BOOL)translatingEvents { - if (translatingEvents != _translatingEvents) { - _translatingEvents = translatingEvents; - NSInteger state = translatingEvents ? NSOnState : NSOffState; +- (void)setSimulatingEvents:(BOOL)simulatingEvents { + if (simulatingEvents != _simulatingEvents) { + _simulatingEvents = simulatingEvents; + NSInteger state = simulatingEvents ? NSOnState : NSOffState; translatingEventsButton.state = state; - NSString *name = translatingEvents - ? NJEventTranslationActivated - : NJEventTranslationDeactivated; + NSString *name = simulatingEvents + ? NJEventSimulationStarted + : NJEventSimulationStopped; [NSNotificationCenter.defaultCenter postNotificationName:name object:self]; - if (!translatingEvents && !NSApplication.sharedApplication.isActive) + if (!simulatingEvents && !NSApplication.sharedApplication.isActive) [self stopHid]; else [self startHid]; @@ -343,12 +343,12 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn } - (void)stopHidIfDisabled:(NSNotification *)application { - if (!self.translatingEvents) + if (!self.simulatingEvents) [self stopHid]; } -- (IBAction)translatingEventsChanged:(NSButton *)sender { - self.translatingEvents = sender.state == NSOnState; +- (IBAction)simulatingEventsChanged:(NSButton *)sender { + self.simulatingEvents = sender.state == NSOnState; } @end diff --git a/Classes/NJInput.h b/Classes/NJInput.h index db47121..b530c37 100644 --- a/Classes/NJInput.h +++ b/Classes/NJInput.h @@ -18,7 +18,7 @@ element:(IOHIDElementRef)element parent:(NJInputPathElement *)parent; -@property (nonatomic, assign) IOHIDElementCookie cookie; +@property (nonatomic, readonly) IOHIDElementCookie cookie; @property (nonatomic, assign) BOOL active; @property (nonatomic, assign) float magnitude; diff --git a/Classes/NJInput.m b/Classes/NJInput.m index 82d5993..d7144a0 100644 --- a/Classes/NJInput.m +++ b/Classes/NJInput.m @@ -14,11 +14,11 @@ element:(IOHIDElementRef)element parent:(NJInputPathElement *)parent { - NSString *elementName = (__bridge NSString *)(IOHIDElementGetName(element)); + NSString *elementName = (__bridge NSString *)IOHIDElementGetName(element); if (elementName.length) name = [name stringByAppendingFormat:@"- %@", elementName]; if ((self = [super initWithName:name eid:eid parent:parent])) { - self.cookie = IOHIDElementGetCookie(element); + _cookie = IOHIDElementGetCookie(element); } return self; } diff --git a/Classes/NJInputAnalog.m b/Classes/NJInputAnalog.m index 3746c11..1f65f48 100644 --- a/Classes/NJInputAnalog.m +++ b/Classes/NJInputAnalog.m @@ -25,7 +25,7 @@ static float normalize(CFIndex p, CFIndex min, CFIndex max) { if ((self = [super initWithName:NJINPUT_NAME(NSLocalizedString(@"axis %d", @"axis name"), index) eid:NJINPUT_DID("Axis", index) element:element - parent:nil])) { + parent:parent])) { self.children = @[[[NJInput alloc] initWithName:NSLocalizedString(@"axis low", @"axis low trigger") eid:@"Low" parent:self], diff --git a/Classes/NJMappingMenuController.h b/Classes/NJMappingMenuController.h index 9c9432a..a2de7e9 100644 --- a/Classes/NJMappingMenuController.h +++ b/Classes/NJMappingMenuController.h @@ -42,8 +42,8 @@ @property (nonatomic, assign) BOOL hasKeyEquivalents; // Whether or not to add key equivalents to the menu items. -@property (nonatomic, strong) IBOutlet NSMenuItem *eventTranslationToggle; - // A menu item representing the current event translation state. +@property (nonatomic, strong) IBOutlet NSMenuItem *eventSimulationToggle; + // A menu item representing the current event simulation state. // This outlet is optional. diff --git a/Classes/NJMappingMenuController.m b/Classes/NJMappingMenuController.m index 03ce31d..6d9250f 100644 --- a/Classes/NJMappingMenuController.m +++ b/Classes/NJMappingMenuController.m @@ -27,12 +27,12 @@ name:NJEventMappingChanged object:nil]; [center addObserver:self - selector:@selector(eventTranslationActivated:) - name:NJEventTranslationActivated + selector:@selector(eventSimulationStarted:) + name:NJEventSimulationStarted object:nil]; [center addObserver:self - selector:@selector(eventTranslationDeactivated:) - name:NJEventTranslationDeactivated + selector:@selector(eventSimulationStopped:) + name:NJEventSimulationStopped object:nil]; } return self; @@ -99,14 +99,14 @@ item.state = mapping == item.representedObject; } -- (void)eventTranslationActivated:(NSNotification *)note { - self.eventTranslationToggle.title = NSLocalizedString(@"Disable", - @"menu item text to disable event translation"); +- (void)eventSimulationStarted:(NSNotification *)note { + self.eventSimulationToggle.title = NSLocalizedString(@"Disable", + @"menu item text to disable event simulation"); } -- (void)eventTranslationDeactivated:(NSNotification *)note { - self.eventTranslationToggle.title = NSLocalizedString(@"Enable", - @"menu item text to enable event translation"); +- (void)eventSimulationStopped:(NSNotification *)note { + self.eventSimulationToggle.title = NSLocalizedString(@"Enable", + @"menu item text to enable event simulation"); } @end diff --git a/Info.plist b/Info.plist index bc56ad4..0ea7a95 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 296 + 298 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright diff --git a/Other Sources/NJEvents.h b/Other Sources/NJEvents.h index 5485154..204a270 100644 --- a/Other Sources/NJEvents.h +++ b/Other Sources/NJEvents.h @@ -8,8 +8,8 @@ #define NJEventMappingChanged @"com.yukkurigames.Enjoyable.MappingChanged" #define NJEventMappingListChanged @"com.yukkurigames.Enjoyable.MappingListChanged" -#define NJEventTranslationActivated @"com.yukkurigames.Enjoyable.EventTranslationActivated" -#define NJEventTranslationDeactivated @"com.yukkurigames.Enjoyable.EventTranslationDeactivated" +#define NJEventSimulationStarted @"com.yukkurigames.Enjoyable.EventSimulationStarted" +#define NJEventSimulationStopped @"com.yukkurigames.Enjoyable.EventSimulationStopped" #define NJMappingKey @"com.yukkurigames.Enjoyable.Mapping" #define NJMappingListKey @"com.yukkurigames.Enjoyable.MappingList" diff --git a/Resources/English.lproj/Localizable.strings b/Resources/English.lproj/Localizable.strings index 8b9a71782f2bf1dcc87e4fa301c7df7b43d4a61f..0e30ce1842093ae8662c75ed023e88a319e3faa0 100644 GIT binary patch delta 36 lcmaE0b;oK$8Vh?dLncEmL+NBjR`JPgEJZN>W=7U{2>{lI3Pb<^ delta 36 jcmca(^}uRF8VhF$LlHwFLmor% {664, 323} - + 256 @@ -501,7 +501,6 @@ -2147483374 {{20, 20}, {194, 283}} - _NS:22 YES @@ -531,7 +530,6 @@ Lg 274 {{20, 20}, {194, 283}} - _NS:22 YES @@ -566,7 +564,6 @@ aW5nLg {232, 321} - YES NO @@ -662,7 +659,6 @@ aW5nLg {{1, 1}, {232, 321}} - @@ -673,7 +669,6 @@ aW5nLg -2147483392 {{1, 1}, {8, 298}} - NO @@ -685,7 +680,6 @@ aW5nLg -2147483392 {{-100, -100}, {473, 15}} - NO 1 @@ -696,7 +690,6 @@ aW5nLg {234, 323} - 150034 @@ -710,7 +703,6 @@ aW5nLg {234, 323} - _NS:9 NSView @@ -724,7 +716,6 @@ aW5nLg 265 {{189, 117}, {224, 20}} - _NS:9 YES @@ -766,7 +757,6 @@ aW5nLg 268 {{343, 31}, {70, 18}} - _NS:9 YES @@ -797,7 +787,6 @@ aW5nLg 265 {{189, 33}, {150, 20}} - _NS:9 YES @@ -842,7 +831,6 @@ aW5nLg 265 {{191, 24}, {146, 16}} - _NS:9 YES @@ -868,7 +856,6 @@ aW5nLg 265 {{189, 70}, {224, 24}} - _NS:9 YES @@ -922,7 +909,6 @@ aW5nLg 265 {{191, 108}, {220, 16}} - _NS:9 YES @@ -948,7 +934,6 @@ aW5nLg 265 {{191, 196}, {220, 23}} - _NS:9 NJKeyInputField @@ -958,7 +943,6 @@ aW5nLg 265 {{188, 153}, {226, 26}} - YES @@ -991,7 +975,6 @@ aW5nLg 268 {{24, 20}, {163, 250}} - NO 6 @@ -1122,7 +1105,6 @@ aW5nLg 266 {{9, 286}, {412, 17}} - YES @@ -1146,7 +1128,6 @@ aW5nLg 10 {{12, 276}, {406, 5}} - {0, 0} @@ -1173,15 +1154,12 @@ aW5nLg {{233, 0}, {431, 323}} - _NS:9 NSView {664, 323} - - {{0, 0}, {1440, 878}} @@ -1669,14 +1647,6 @@ aW5nLg 826 - - - translatingEventsChanged: - - - - 878 - translatingEventsButton @@ -1709,6 +1679,14 @@ aW5nLg 976 + + + simulatingEventsChanged: + + + + 978 + mappingsController @@ -2101,14 +2079,6 @@ aW5nLg 933 - - - eventTranslationToggle - - - - 951 - menu @@ -2125,6 +2095,14 @@ aW5nLg 956 + + + eventSimulationToggle + + + + 980 + menu @@ -2143,11 +2121,11 @@ aW5nLg - eventTranslationToggle + eventSimulationToggle - 959 + 979 @@ -2167,11 +2145,11 @@ aW5nLg - eventTranslationToggle + eventSimulationToggle - 965 + 981 @@ -3267,7 +3245,7 @@ aW5nLg - 977 + 981 -- 2.20.1 From 48c014b3f30482f32f50a3a60e88584930265277 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Thu, 14 Mar 2013 21:06:40 +0100 Subject: [PATCH 08/16] Mappings list doesn't need to be public anymore. --- Classes/NJMappingsController.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Classes/NJMappingsController.h b/Classes/NJMappingsController.h index dcb44b0..a4d9c18 100644 --- a/Classes/NJMappingsController.h +++ b/Classes/NJMappingsController.h @@ -24,7 +24,6 @@ } @property (nonatomic, readonly) NJMapping *currentMapping; -@property (nonatomic, readonly) NSArray *mappings; - (NJMapping *)objectForKeyedSubscript:(NSString *)name; - (NJMapping *)objectAtIndexedSubscript:(NSUInteger)idx; -- 2.20.1 From 6dddafbcf505939751ddb2d66ffb586d150aaaa8 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Thu, 14 Mar 2013 21:10:22 +0100 Subject: [PATCH 09/16] Don't let the app control itself, it only leads to accidents or mouse bugs. Don't auto-focus the key field when simulating either. --- Classes/NJDeviceController.m | 6 ++++-- Info.plist | 2 +- .../Contents/Resources/English.lproj/pgs/mouse.html | 10 ---------- .../Contents/Resources/English.lproj/pgs/whatsnew.html | 5 +++++ 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index 39ec389..0b0fe92 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -138,13 +138,15 @@ [self expandRecursive:handler]; [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[outlineView rowForItem:handler]] byExtendingSelection: NO]; - [outputController focusKey]; + if (!self.simulatingEvents) + [outputController focusKey]; } - (void)hidManager:(NJHIDManager *)manager valueChanged:(IOHIDValueRef)value fromDevice:(IOHIDDeviceRef)device { - if (self.simulatingEvents) { + if (self.simulatingEvents + && !NSApplication.sharedApplication.isActive) { [self runOutputForDevice:device value:value]; } else { [self showOutputForDevice:device value:value]; diff --git a/Info.plist b/Info.plist index 0ea7a95..ed9e83f 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 298 + 301 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright diff --git a/Resources/Help/Contents/Resources/English.lproj/pgs/mouse.html b/Resources/Help/Contents/Resources/English.lproj/pgs/mouse.html index 5436540..f52c5b6 100644 --- a/Resources/Help/Contents/Resources/English.lproj/pgs/mouse.html +++ b/Resources/Help/Contents/Resources/English.lproj/pgs/mouse.html @@ -76,15 +76,5 @@ change this globally in  > System Preferences… > Mouse and  > System Preferences… > Trackpad.

    - -

    Known Issues

    -

    - Enjoyable is unable to send most mouse events to itself - the - application will freeze until you click on it with a real mouse - or touchpad. If you find another appliation that has problems - with Enjoyable's mouse support please - file a ticket in the issue tracker. -

    diff --git a/Resources/Help/Contents/Resources/English.lproj/pgs/whatsnew.html b/Resources/Help/Contents/Resources/English.lproj/pgs/whatsnew.html index 961e84c..3506443 100644 --- a/Resources/Help/Contents/Resources/English.lproj/pgs/whatsnew.html +++ b/Resources/Help/Contents/Resources/English.lproj/pgs/whatsnew.html @@ -59,6 +59,11 @@ Enjoyable uses fewer resources when running in the background. +
  • + To prevent accidentally rebinding keys right after + enabling simulation, Enjoyable can no longer control + itself. +
  • -- 2.20.1 From 7438479aa735a56f22645c226d5f1db6ba8ef0a2 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Thu, 14 Mar 2013 21:25:47 +0100 Subject: [PATCH 10/16] Fix another bad property name. --- Classes/NJOutputController.m | 4 ++-- Classes/NJOutputKeyPress.h | 2 +- Classes/NJOutputKeyPress.m | 22 +++++++++++++--------- Info.plist | 2 +- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Classes/NJOutputController.m b/Classes/NJOutputController.m index 95d28c4..07c077b 100644 --- a/Classes/NJOutputController.m +++ b/Classes/NJOutputController.m @@ -166,7 +166,7 @@ case 1: if (keyInput.hasKeyCode) { NJOutputKeyPress *k = [[NJOutputKeyPress alloc] init]; - k.vk = keyInput.keyCode; + k.keyCode = keyInput.keyCode; return k; } else { return nil; @@ -237,7 +237,7 @@ if ([output isKindOfClass:NJOutputKeyPress.class]) { [radioButtons selectCellAtRow:1 column:0]; - keyInput.keyCode = [(NJOutputKeyPress*)output vk]; + keyInput.keyCode = [(NJOutputKeyPress*)output keyCode]; } else if ([output isKindOfClass:NJOutputMapping.class]) { [radioButtons selectCellAtRow:2 column:0]; NSMenuItem *item = [mappingPopup itemWithRepresentedObject:[(NJOutputMapping *)output mapping]]; diff --git a/Classes/NJOutputKeyPress.h b/Classes/NJOutputKeyPress.h index 052b010..ce98668 100644 --- a/Classes/NJOutputKeyPress.h +++ b/Classes/NJOutputKeyPress.h @@ -10,6 +10,6 @@ @interface NJOutputKeyPress : NJOutput -@property (nonatomic, assign) CGKeyCode vk; +@property (nonatomic, assign) CGKeyCode keyCode; @end diff --git a/Classes/NJOutputKeyPress.m b/Classes/NJOutputKeyPress.m index 622cf41..1cc7e65 100644 --- a/Classes/NJOutputKeyPress.m +++ b/Classes/NJOutputKeyPress.m @@ -16,28 +16,32 @@ } - (NSDictionary *)serialize { - return _vk != NJKeyInputFieldEmpty - ? @{ @"type": self.class.serializationCode, @"key": @(_vk) } + return _keyCode != NJKeyInputFieldEmpty + ? @{ @"type": self.class.serializationCode, @"key": @(_keyCode) } : nil; } + (NJOutput *)outputDeserialize:(NSDictionary *)serialization withMappings:(NSArray *)mappings { NJOutputKeyPress *output = [[NJOutputKeyPress alloc] init]; - output.vk = [serialization[@"key"] intValue]; + output.keyCode = [serialization[@"key"] intValue]; return output; } - (void)trigger { - CGEventRef keyDown = CGEventCreateKeyboardEvent(NULL, _vk, YES); - CGEventPost(kCGHIDEventTap, keyDown); - CFRelease(keyDown); + if (_keyCode != NJKeyInputFieldEmpty) { + CGEventRef keyDown = CGEventCreateKeyboardEvent(NULL, _keyCode, YES); + CGEventPost(kCGHIDEventTap, keyDown); + CFRelease(keyDown); + } } - (void)untrigger { - CGEventRef keyUp = CGEventCreateKeyboardEvent(NULL, _vk, NO); - CGEventPost(kCGHIDEventTap, keyUp); - CFRelease(keyUp); + if (_keyCode != NJKeyInputFieldEmpty) { + CGEventRef keyUp = CGEventCreateKeyboardEvent(NULL, _keyCode, NO); + CGEventPost(kCGHIDEventTap, keyUp); + CFRelease(keyUp); + } } @end diff --git a/Info.plist b/Info.plist index ed9e83f..1ddd7ca 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 301 + 303 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright -- 2.20.1 From 764667945312ea9762fba3127ec175387fa0a930 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Fri, 15 Mar 2013 01:36:26 +0100 Subject: [PATCH 11/16] Don't re-save mappings immediately upon load. --- Classes/NJMappingsController.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Classes/NJMappingsController.m b/Classes/NJMappingsController.m index 4a9c3ff..fa851b1 100644 --- a/Classes/NJMappingsController.m +++ b/Classes/NJMappingsController.m @@ -46,8 +46,7 @@ return idx < _mappings.count ? _mappings[idx] : nil; } -- (void)mappingsChanged { - [self save]; +- (void)mappingsSet { [tableView reloadData]; [self updateInterfaceForCurrentMapping]; [NSNotificationCenter.defaultCenter @@ -57,6 +56,11 @@ NJMappingKey: _currentMapping }]; } +- (void)mappingsChanged { + [self save]; + [self mappingsSet]; +} + - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id [])buffer count:(NSUInteger)len { @@ -168,11 +172,7 @@ - (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 { + NSArray *storedMappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"]; NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count]; // Requires two passes to deal with inter-mapping references. First make @@ -195,7 +195,7 @@ if (selected >= newMappings.count) selected = 0; [self activateMapping:_mappings[selected]]; - [self mappingsChanged]; + [self mappingsSet]; } } -- 2.20.1 From 91ea24909c2c38d9f3d319e6440b362222fdfd87 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Fri, 15 Mar 2013 01:36:46 +0100 Subject: [PATCH 12/16] Missed a translating -> simulating. --- Classes/NJDeviceController.h | 2 +- Classes/NJDeviceController.m | 4 ++-- Classes/NJMappingMenuController.h | 2 +- Info.plist | 2 +- Resources/English.lproj/MainMenu.xib | 18 +++++++++--------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Classes/NJDeviceController.h b/Classes/NJDeviceController.h index 1391121..1d13a98 100644 --- a/Classes/NJDeviceController.h +++ b/Classes/NJDeviceController.h @@ -19,7 +19,7 @@ IBOutlet NSOutlineView *outlineView; IBOutlet NJOutputController *outputController; IBOutlet NJMappingsController *mappingsController; - IBOutlet NSButton *translatingEventsButton; + IBOutlet NSButton *simulatingEventsButton; IBOutlet NSView *connectDevicePrompt; IBOutlet NSView *hidSleepingPrompt; } diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index 0b0fe92..4a6d3dc 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -57,7 +57,7 @@ // nothing if a noisy device is plugged in (the % of that // spent in input_callback is negligible, so it's not // something we can make faster). I don't really think that's - // acceptable, CPU/power wise. So if translation is disabled + // acceptable, CPU/power wise. So if simulation is disabled // and the window is closed, just switch off the HID manager // entirely. This probably also has some marginal benefits for // compatibility with other applications that want exclusive @@ -318,7 +318,7 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn if (simulatingEvents != _simulatingEvents) { _simulatingEvents = simulatingEvents; NSInteger state = simulatingEvents ? NSOnState : NSOffState; - translatingEventsButton.state = state; + simulatingEventsButton.state = state; NSString *name = simulatingEvents ? NJEventSimulationStarted : NJEventSimulationStopped; diff --git a/Classes/NJMappingMenuController.h b/Classes/NJMappingMenuController.h index a2de7e9..33c06bb 100644 --- a/Classes/NJMappingMenuController.h +++ b/Classes/NJMappingMenuController.h @@ -24,7 +24,7 @@ @interface NJMappingMenuController : NSObject // Mapping menu controllers manage the contents of a menu that // shows a list of all available mappings, as well as the current - // event translation state. The menu may have other items in it as + // event simulation state. The menu may have other items in it as // well, but none at an adjacent index that also have NJMappings // as represented objects. diff --git a/Info.plist b/Info.plist index 1ddd7ca..6dba917 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 303 + 305 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright diff --git a/Resources/English.lproj/MainMenu.xib b/Resources/English.lproj/MainMenu.xib index 39b227e..be18595 100644 --- a/Resources/English.lproj/MainMenu.xib +++ b/Resources/English.lproj/MainMenu.xib @@ -1647,14 +1647,6 @@ aW5nLg
    826
    - - - translatingEventsButton - - - - 879 - outlineView @@ -1687,6 +1679,14 @@ aW5nLg 978 + + + simulatingEventsButton + + + + 982 + mappingsController @@ -3245,7 +3245,7 @@ aW5nLg - 981 + 982 -- 2.20.1 From 5c56b988cdf5079dd5f3f256012e0d0a384f96f7 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Fri, 15 Mar 2013 23:45:27 +0100 Subject: [PATCH 13/16] Whether error was set or not, if there's no mapping, that's an error. --- Classes/NJMappingsController.m | 6 ++---- Info.plist | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Classes/NJMappingsController.m b/Classes/NJMappingsController.m index fa851b1..c3209ac 100644 --- a/Classes/NJMappingsController.m +++ b/Classes/NJMappingsController.m @@ -231,7 +231,7 @@ mappings:_mappings error:&error]; - if (mapping && !error) { + if (mapping) { NJMapping *mergeInto = self[mapping.name]; if ([mergeInto hasConflictWith:mapping]) { NSAlert *conflictAlert = [[NSAlert alloc] init]; @@ -256,9 +256,7 @@ [self activateMapping:mapping]; [self mappingsChanged]; } - } - - if (error) { + } else { [window presentError:error modalForWindow:window delegate:nil diff --git a/Info.plist b/Info.plist index 6dba917..b17e626 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 305 + 306 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright -- 2.20.1 From 58ce8f7f50f382f18e7b416eca737803af284868 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Sat, 16 Mar 2013 20:23:42 +0100 Subject: [PATCH 14/16] Split view management out of NJDeviceController. Right now this probably just makes things worse, but once all the views are out, all the regular controllers can just be merged. --- Categories/NSOutlineView+ItemAccessors.h | 16 ++ Categories/NSOutlineView+ItemAccessors.m | 27 ++++ Classes/NJDevice.m | 6 + Classes/NJDeviceController.h | 9 +- Classes/NJDeviceController.m | 188 ++++++----------------- Classes/NJDeviceViewController.h | 49 ++++++ Classes/NJDeviceViewController.m | 181 ++++++++++++++++++++++ Enjoyable.xcodeproj/project.pbxproj | 12 ++ Info.plist | 2 +- Other Sources/Enjoyable_Prefix.pch | 1 + Resources/English.lproj/MainMenu.xib | 170 +++++++++++++++----- 11 files changed, 471 insertions(+), 190 deletions(-) create mode 100644 Categories/NSOutlineView+ItemAccessors.h create mode 100644 Categories/NSOutlineView+ItemAccessors.m create mode 100644 Classes/NJDeviceViewController.h create mode 100644 Classes/NJDeviceViewController.m diff --git a/Categories/NSOutlineView+ItemAccessors.h b/Categories/NSOutlineView+ItemAccessors.h new file mode 100644 index 0000000..bd7bc88 --- /dev/null +++ b/Categories/NSOutlineView+ItemAccessors.h @@ -0,0 +1,16 @@ +// +// NSOutlineView+ItemAccessors.h +// Enjoyable +// +// Created by Joe Wreschnig on 3/16/13. +// +// + +#import + +@interface NSOutlineView (ItemAccessors) + +- (void)selectItem:(id)item; +- (id)selectedItem; + +@end diff --git a/Categories/NSOutlineView+ItemAccessors.m b/Categories/NSOutlineView+ItemAccessors.m new file mode 100644 index 0000000..8f1a9be --- /dev/null +++ b/Categories/NSOutlineView+ItemAccessors.m @@ -0,0 +1,27 @@ +// +// NSOutlineView+ItemAccessors.m +// Enjoyable +// +// Created by Joe Wreschnig on 3/16/13. +// +// + +#import "NSOutlineView+ItemAccessors.h" + +@implementation NSOutlineView (ItemAccessors) + +- (void)selectItem:(id)item { + NSInteger row = [self rowForItem:item]; + if (row >= 0) { + [self selectRowIndexes:[[NSIndexSet alloc] initWithIndex:row] + byExtendingSelection:NO]; + } else { + [self deselectAll:nil]; + } +} + +- (id)selectedItem { + return self.selectedRow >= 0 ? [self itemAtRow:self.selectedRow] : nil; +} + +@end diff --git a/Classes/NJDevice.m b/Classes/NJDevice.m index b03898f..ddc6cde 100644 --- a/Classes/NJDevice.m +++ b/Classes/NJDevice.m @@ -76,6 +76,12 @@ static NSArray *InputsForElement(IOHIDDeviceRef device, id parent) { return self; } +- (BOOL)isEqual:(id)object { + return [object isKindOfClass:NJDevice.class] + && [[(NJDevice *)object productName] isEqualToString:self.productName] + && [(NJDevice *)object index] == self.index; +} + - (NSString *)name { return [NSString stringWithFormat:@"%@ #%d", _productName, _index]; } diff --git a/Classes/NJDeviceController.h b/Classes/NJDeviceController.h index 1d13a98..fcc09ef 100644 --- a/Classes/NJDeviceController.h +++ b/Classes/NJDeviceController.h @@ -7,21 +7,20 @@ // #import "NJHIDManager.h" +#import "NJDeviceViewController.h" @class NJDevice; @class NJInput; @class NJMappingsController; @class NJOutputController; +@class NJDeviceViewController; -@interface NJDeviceController : NSObject { - IBOutlet NSOutlineView *outlineView; IBOutlet NJOutputController *outputController; IBOutlet NJMappingsController *mappingsController; IBOutlet NSButton *simulatingEventsButton; - IBOutlet NSView *connectDevicePrompt; - IBOutlet NSView *hidSleepingPrompt; + IBOutlet NJDeviceViewController *devicesViewController; } @property (nonatomic, readonly) NJInput *selectedInput; diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index 4a6d3dc..b0e8de3 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -14,16 +14,15 @@ #import "NJOutput.h" #import "NJOutputController.h" #import "NJEvents.h" +#import "NJDeviceViewController.h" @implementation NJDeviceController { NJHIDManager *_hidManager; NSTimer *_continuousOutputsTick; NSMutableArray *_continousOutputs; NSMutableArray *_devices; - NSMutableArray *_expanded; } -#define EXPANDED_MEMORY_MAX_SIZE 100 #define NSSTR(e) ((NSString *)CFSTR(e)) - (id)init { @@ -31,12 +30,6 @@ _devices = [[NSMutableArray alloc] initWithCapacity:16]; _continousOutputs = [[NSMutableArray alloc] initWithCapacity:32]; - NSArray *expanded = [NSUserDefaults.standardUserDefaults objectForKey:@"expanded rows"]; - if (![expanded isKindOfClass:NSArray.class]) - expanded = @[]; - _expanded = [[NSMutableArray alloc] initWithCapacity:MAX(16, _expanded.count)]; - [_expanded addObjectsFromArray:expanded]; - _hidManager = [[NJHIDManager alloc] initWithCriteria:@[ @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) }, @@ -47,12 +40,6 @@ ] delegate:self]; - [NSNotificationCenter.defaultCenter - addObserver:self - selector:@selector(startHid) - name:NSApplicationDidFinishLaunchingNotification - object:nil]; - // The HID manager uses 5-10ms per second doing basically // nothing if a noisy device is plugged in (the % of that // spent in input_callback is negligible, so it's not @@ -81,13 +68,6 @@ [_continuousOutputsTick invalidate]; } -- (void)expandRecursive:(NJInputPathElement *)pathElement { - if (pathElement) { - [self expandRecursive:pathElement.parent]; - [outlineView expandItem:pathElement]; - } -} - - (id)elementForUID:(NSString *)uid { for (NJDevice *dev in _devices) { id item = [dev elementForUID:uid]; @@ -97,10 +77,6 @@ return nil; } -- (void)expandRecursiveByUID:(NSString *)uid { - [self expandRecursive:[self elementForUID:uid]]; -} - - (void)addRunningOutput:(NJOutput *)output { // Axis events will trigger every small movement, don't keep // re-adding them or they trigger multiple times each time. @@ -135,11 +111,8 @@ if (!handler) return; - [self expandRecursive:handler]; - [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[outlineView rowForItem:handler]] - byExtendingSelection: NO]; - if (!self.simulatingEvents) - [outputController focusKey]; + [devicesViewController expandAndSelectItem:handler]; + [outputController focusKey]; } - (void)hidManager:(NJHIDManager *)manager @@ -153,29 +126,22 @@ } } -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) { +- (void)hidManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device { + NJDevice *match = [[NJDevice alloc] initWithDevice:device]; + match.index = 1; + BOOL available; + do { + available = YES; + for (NJDevice *used in _devices) { + if ([used isEqual:match]) { + match.index += 1; available = NO; - break; } } - if (available) - return index; - } -} + } while (!available); -- (void)hidManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device { - NJDevice *match = [[NJDevice alloc] initWithDevice:device]; - match.index = findAvailableIndex(_devices, match); [_devices addObject:match]; - [outlineView reloadData]; - [self reexpandAll]; - hidSleepingPrompt.hidden = YES; - connectDevicePrompt.hidden = !!_devices.count; + [devicesViewController addedDevice:match atIndex:_devices.count - 1]; } - (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device { @@ -189,13 +155,10 @@ static int findAvailableIndex(NSArray *list, NJDevice *dev) { NJDevice *match = [self findDeviceByRef:device]; IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL); if (match) { - [_devices removeObject:match]; - [outlineView reloadData]; - connectDevicePrompt.hidden = !!_devices.count; - hidSleepingPrompt.hidden = YES; + NSInteger idx = [_devices indexOfObjectIdenticalTo:match]; + [_devices removeObjectAtIndex:idx]; + [devicesViewController removedDevice:match atIndex:idx]; } - if (_devices.count == 1) - [outlineView expandItem:_devices[0]]; } - (void)updateContinuousOutputs:(NSTimer *)timer { @@ -215,32 +178,25 @@ static int findAvailableIndex(NSArray *list, NJDevice *dev) { // Since the error shows the window, it can trigger another attempt // to re-open the HID manager, which will also probably fail and error, // so don't bother repeating ourselves. - if (!outlineView.window.attachedSheet) { + if (!simulatingEventsButton.window.attachedSheet) { [NSApplication.sharedApplication activateIgnoringOtherApps:YES]; - [outlineView.window makeKeyAndOrderFront:nil]; - [outlineView.window presentError:error - modalForWindow:outlineView.window - delegate:nil - didPresentSelector:nil - contextInfo:nil]; + [simulatingEventsButton.window makeKeyAndOrderFront:nil]; + [simulatingEventsButton.window presentError:error + modalForWindow:simulatingEventsButton.window + delegate:nil + didPresentSelector:nil + contextInfo:nil]; } self.simulatingEvents = NO; - if (manager.running) - [self hidManagerDidStart:manager]; - else - [self hidManagerDidStop:manager]; } - (void)hidManagerDidStart:(NJHIDManager *)manager { - hidSleepingPrompt.hidden = YES; - connectDevicePrompt.hidden = !!_devices.count; + [devicesViewController hidStarted]; } - (void)hidManagerDidStop:(NJHIDManager *)manager { [_devices removeAllObjects]; - [outlineView reloadData]; - hidSleepingPrompt.hidden = NO; - connectDevicePrompt.hidden = YES; + [devicesViewController hidStopped]; } - (void)startHid { @@ -252,66 +208,7 @@ static int findAvailableIndex(NSArray *list, NJDevice *dev) { } - (NJInput *)selectedInput { - NJInputPathElement *item = [outlineView itemAtRow:outlineView.selectedRow]; - return (NJInput *)((!item.children && item.parent) ? item : nil); -} - -- (NSInteger)outlineView:(NSOutlineView *)outlineView - numberOfChildrenOfItem:(NJInputPathElement *)item { - return item ? item.children.count : _devices.count; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView - isItemExpandable:(NJInputPathElement *)item { - return item ? [[item children] count] > 0: YES; -} - -- (id)outlineView:(NSOutlineView *)outlineView - child:(NSInteger)index - ofItem:(NJInputPathElement *)item { - return item ? item.children[index] : _devices[index]; -} - -- (id)outlineView:(NSOutlineView *)outlineView -objectValueForTableColumn:(NSTableColumn *)tableColumn - byItem:(NJInputPathElement *)item { - return item ? item.name : @"root"; -} - -- (void)outlineViewSelectionDidChange:(NSNotification *)notification { - NJInputPathElement *item = [outlineView itemAtRow:outlineView.selectedRow]; - if (item) - [NSUserDefaults.standardUserDefaults setObject:item.uid - forKey:@"selected input"]; - [outputController loadCurrent]; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView - isGroupItem:(NJInputPathElement *)item { - return [item isKindOfClass:NJDevice.class]; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView_ - shouldSelectItem:(NJInputPathElement *)item { - return ![self outlineView:outlineView_ isGroupItem:item]; -} - -- (void)outlineViewItemDidExpand:(NSNotification *)notification { - NJInputPathElement *item = notification.userInfo[@"NSObject"]; - NSString *uid = item.uid; - if (![_expanded containsObject:uid]) - [_expanded addObject:uid]; - while (_expanded.count > EXPANDED_MEMORY_MAX_SIZE) - [_expanded removeObjectAtIndex:0]; - [NSUserDefaults.standardUserDefaults setObject:_expanded - forKey:@"expanded rows"]; -} - -- (void)outlineViewItemDidCollapse:(NSNotification *)notification { - NJInputPathElement *item = notification.userInfo[@"NSObject"]; - [_expanded removeObject:item.uid]; - [NSUserDefaults.standardUserDefaults setObject:_expanded - forKey:@"expanded rows"]; + return (NJInput *)devicesViewController.selectedHandler; } - (void)setSimulatingEvents:(BOOL)simulatingEvents { @@ -332,18 +229,6 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn } } -- (void)reexpandAll { - for (NSString *uid in [_expanded copy]) - [self expandRecursiveByUID:uid]; - if (outlineView.selectedRow == -1) { - NSString *selectedUid = [NSUserDefaults.standardUserDefaults objectForKey:@"selected input"]; - id item = [self elementForUID:selectedUid]; - NSInteger row = [outlineView rowForItem:item]; - if (row >= 0) - [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; - } -} - - (void)stopHidIfDisabled:(NSNotification *)application { if (!self.simulatingEvents) [self stopHid]; @@ -353,4 +238,23 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn self.simulatingEvents = sender.state == NSOnState; } +- (void)deviceViewControllerDidSelectNothing:(NJDeviceViewController *)devices { + [outputController loadCurrent]; +} + +- (void)deviceViewController:(NJDeviceViewController *)devices + didSelectBranch:(NJInputPathElement *)handler { + [outputController loadCurrent]; +} + +- (void)deviceViewController:(NJDeviceViewController *)devices + didSelectHandler:(NJInputPathElement *)handler { + [outputController loadCurrent]; +} + +- (void)deviceViewController:(NJDeviceViewController *)devices + didSelectDevice:(NJInputPathElement *)device { + [outputController loadCurrent]; +} + @end diff --git a/Classes/NJDeviceViewController.h b/Classes/NJDeviceViewController.h new file mode 100644 index 0000000..9e1a43f --- /dev/null +++ b/Classes/NJDeviceViewController.h @@ -0,0 +1,49 @@ +// +// NJDeviceViewController.h +// Enjoyable +// +// Created by Joe Wreschnig on 3/16/13. +// +// + +@class NJDevice; +@class NJInputPathElement; + +@protocol NJDeviceViewControllerDelegate; + +@interface NJDeviceViewController : NSObject + +@property (nonatomic, strong) IBOutlet NSOutlineView *inputsTree; +@property (nonatomic, strong) IBOutlet NSView *noDevicesNotice; +@property (nonatomic, strong) IBOutlet NSView *hidStoppedNotice; + +@property (nonatomic, weak) IBOutlet id delegate; + +@property (nonatomic, copy) NSArray *devices; + // Assigning directly will trigger a full reload. + +- (void)addedDevice:(NJDevice *)device atIndex:(NSUInteger)idx; +- (void)removedDevice:(NJDevice *)device atIndex:(NSUInteger)idx; + // But using these will animate nicely. + +- (void)hidStarted; +- (void)hidStopped; + +- (void)expandAndSelectItem:(NJInputPathElement *)item; + +- (NJInputPathElement *)selectedHandler; + +@end + +@protocol NJDeviceViewControllerDelegate + +- (void)deviceViewController:(NJDeviceViewController *)devices + didSelectDevice:(NJInputPathElement *)device; +- (void)deviceViewController:(NJDeviceViewController *)devices + didSelectBranch:(NJInputPathElement *)handler; +- (void)deviceViewController:(NJDeviceViewController *)devices + didSelectHandler:(NJInputPathElement *)handler; +- (void)deviceViewControllerDidSelectNothing:(NJDeviceViewController *)devices; + +@end diff --git a/Classes/NJDeviceViewController.m b/Classes/NJDeviceViewController.m new file mode 100644 index 0000000..3d20e65 --- /dev/null +++ b/Classes/NJDeviceViewController.m @@ -0,0 +1,181 @@ +// +// NJDeviceViewController.m +// Enjoyable +// +// Created by Joe Wreschnig on 3/16/13. +// +// + +#import "NJDeviceViewController.h" + +#import "NJInputPathElement.h" + +@implementation NJDeviceViewController { + NSMutableArray *_devices; + NSMutableArray *_expanded; +} + +- (id)init { + if ((self = [super init])) { + NSArray *expanded = [NSUserDefaults.standardUserDefaults objectForKey:@"expanded rows"]; + if (![expanded isKindOfClass:NSArray.class]) + expanded = @[]; + _expanded = [[NSMutableArray alloc] initWithCapacity:MAX(16, _expanded.count)]; + [_expanded addObjectsFromArray:expanded]; + _devices = [[NSMutableArray alloc] initWithCapacity:16]; + } + return self; +} + +- (void)expandRecursive:(NJInputPathElement *)pathElement { + if (pathElement) { + [self expandRecursive:pathElement.parent]; + [self.inputsTree expandItem:pathElement]; + } +} + +- (id)elementForUID:(NSString *)uid { + for (NJInputPathElement *dev in _devices) { + id item = [dev elementForUID:uid]; + if (item) + return item; + } + return nil; +} + +- (void)expandRecursiveByUID:(NSString *)uid { + [self expandRecursive:[self elementForUID:uid]]; +} + +- (void)reexpandAll { + for (NSString *uid in [_expanded copy]) + [self expandRecursiveByUID:uid]; + if (self.inputsTree.selectedRow == -1) { + NSString *selectedUid = [NSUserDefaults.standardUserDefaults objectForKey:@"selected input"]; + id item = [self elementForUID:selectedUid]; + [self.inputsTree selectItem:item]; + } +} + +- (void)setDevices:(NSArray *)devices { + _devices = [devices mutableCopy]; + id item = self.inputsTree.selectedItem; + [self.inputsTree selectItem:nil]; + [self.inputsTree reloadData]; + [self reexpandAll]; + [self.inputsTree selectItem:item]; + self.noDevicesNotice.hidden = self.devices.count || !self.hidStoppedNotice.isHidden; +} + +- (void)addedDevice:(NJDevice *)device atIndex:(NSUInteger)idx { + [_devices insertObject:device atIndex:idx]; + [self.inputsTree beginUpdates]; + [self.inputsTree insertItemsAtIndexes:[[NSIndexSet alloc] initWithIndex:idx] + inParent:nil + withAnimation:NSTableViewAnimationSlideLeft]; + [self reexpandAll]; + [self.inputsTree endUpdates]; + self.noDevicesNotice.hidden = YES; +} + +- (void)removedDevice:(NJDevice *)device atIndex:(NSUInteger)idx { + [_devices removeObjectAtIndex:idx]; + [self.inputsTree beginUpdates]; + [self.inputsTree removeItemsAtIndexes:[[NSIndexSet alloc] initWithIndex:idx] + inParent:nil + withAnimation:NSTableViewAnimationSlideLeft]; + [self.inputsTree endUpdates]; + self.noDevicesNotice.hidden = self.devices.count || !self.hidStoppedNotice.isHidden; +} + +- (void)hidStarted { + self.noDevicesNotice.hidden = !!self.devices.count; + self.hidStoppedNotice.hidden = YES; +} + +- (void)hidStopped { + self.hidStoppedNotice.hidden = NO; + self.devices = @[]; +} + +- (void)expandAndSelectItem:(NJInputPathElement *)item { + [self expandRecursive:item]; + NSInteger row = [self.inputsTree rowForItem:item]; + if (row >= 0) { + [self.inputsTree selectRowIndexes:[[NSIndexSet alloc] initWithIndex:row] + byExtendingSelection:NO]; + [self.inputsTree scrollRowToVisible:row]; + } +} + +- (NSInteger)outlineView:(NSOutlineView *)outlineView + numberOfChildrenOfItem:(NJInputPathElement *)item { + return item ? item.children.count : _devices.count; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView + isItemExpandable:(NJInputPathElement *)item { + return item ? [[item children] count] > 0: YES; +} + +- (id)outlineView:(NSOutlineView *)outlineView + child:(NSInteger)index + ofItem:(NJInputPathElement *)item { + return item ? item.children[index] : _devices[index]; +} + +- (id)outlineView:(NSOutlineView *)outlineView +objectValueForTableColumn:(NSTableColumn *)tableColumn + byItem:(NJInputPathElement *)item { + return item ? item.name : @"root"; +} + +- (void)outlineViewSelectionDidChange:(NSNotification *)notification { + NSOutlineView *outlineView = notification.object; + NJInputPathElement *item = outlineView.selectedItem; + if (item) { + [NSUserDefaults.standardUserDefaults setObject:item.uid + forKey:@"selected input"]; + if (!item.children) + [self.delegate deviceViewController:self + didSelectHandler:item]; + else if (!item.parent) + [self.delegate deviceViewController:self + didSelectDevice:item]; + else + [self.delegate deviceViewController:self + didSelectBranch:item]; + } else { + [self.delegate deviceViewControllerDidSelectNothing:self]; + } +} + +- (void)outlineViewItemDidExpand:(NSNotification *)notification { + NSString *uid = [notification.userInfo[@"NSObject"] uid]; + if (![_expanded containsObject:uid]) + [_expanded addObject:uid]; + +} + +- (void)outlineViewItemDidCollapse:(NSNotification *)notification { + [_expanded removeObject:[notification.userInfo[@"NSObject"] uid]]; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView + isGroupItem:(NJInputPathElement *)item { + return !item.parent; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView + shouldSelectItem:(NJInputPathElement *)item { + return ![self outlineView:outlineView isGroupItem:item]; +} + +- (NJInputPathElement *)selectedHandler { + NJInputPathElement *element = self.inputsTree.selectedItem; + return element.children ? nil : element; +} + + + +@end diff --git a/Enjoyable.xcodeproj/project.pbxproj b/Enjoyable.xcodeproj/project.pbxproj index d428220..e633c35 100644 --- a/Enjoyable.xcodeproj/project.pbxproj +++ b/Enjoyable.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ EE3D897C16EA806E00596D1F /* Status Menu Icon Disabled@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897B16EA806E00596D1F /* Status Menu Icon Disabled@2x.png */; }; EE3D897F16EA817E00596D1F /* Status Menu Icon Disabled.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897D16EA817E00596D1F /* Status Menu Icon Disabled.png */; }; EE3D898016EA817E00596D1F /* Status Menu Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897E16EA817E00596D1F /* Status Menu Icon.png */; }; + EE52145C16F3E8BD00E3C574 /* NSOutlineView+ItemAccessors.m in Sources */ = {isa = PBXBuildFile; fileRef = EE52145B16F3E8BD00E3C574 /* NSOutlineView+ItemAccessors.m */; }; + EE52145F16F404D500E3C574 /* NJDeviceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EE52145E16F404D500E3C574 /* NJDeviceViewController.m */; }; EE6A122E16E8F46300EDBD32 /* Icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = EE6A122D16E8F46300EDBD32 /* Icon.icns */; }; EE8455DD16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8455DC16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m */; }; EED4CE6E16ED692400C65AA8 /* NJMappingMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */; }; @@ -78,6 +80,10 @@ EE3D897B16EA806E00596D1F /* Status Menu Icon Disabled@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon Disabled@2x.png"; path = "Resources/Status Menu Icon Disabled@2x.png"; sourceTree = ""; }; EE3D897D16EA817E00596D1F /* Status Menu Icon Disabled.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon Disabled.png"; path = "Resources/Status Menu Icon Disabled.png"; sourceTree = ""; }; EE3D897E16EA817E00596D1F /* Status Menu Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon.png"; path = "Resources/Status Menu Icon.png"; sourceTree = ""; }; + EE52145A16F3E8BD00E3C574 /* NSOutlineView+ItemAccessors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSOutlineView+ItemAccessors.h"; path = "Categories/NSOutlineView+ItemAccessors.h"; sourceTree = ""; }; + EE52145B16F3E8BD00E3C574 /* NSOutlineView+ItemAccessors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSOutlineView+ItemAccessors.m"; path = "Categories/NSOutlineView+ItemAccessors.m"; sourceTree = ""; }; + EE52145D16F404D500E3C574 /* NJDeviceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NJDeviceViewController.h; path = Classes/NJDeviceViewController.h; sourceTree = ""; }; + EE52145E16F404D500E3C574 /* NJDeviceViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NJDeviceViewController.m; path = Classes/NJDeviceViewController.m; sourceTree = ""; }; EE6A122D16E8F46300EDBD32 /* Icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Icon.icns; sourceTree = ""; }; EE8455DB16F0E46B00F32A01 /* NSRunningApplication+LoginItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSRunningApplication+LoginItem.h"; path = "Categories/NSRunningApplication+LoginItem.h"; sourceTree = ""; }; EE8455DC16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSRunningApplication+LoginItem.m"; path = "Categories/NSRunningApplication+LoginItem.m"; sourceTree = ""; }; @@ -202,6 +208,8 @@ EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */, EEE703DA16F089FE002FDD69 /* NJHIDManager.h */, EEE703DB16F089FE002FDD69 /* NJHIDManager.m */, + EE52145D16F404D500E3C574 /* NJDeviceViewController.h */, + EE52145E16F404D500E3C574 /* NJDeviceViewController.m */, ); name = Classes; sourceTree = ""; @@ -302,6 +310,8 @@ EEF17D3216E8E2E100D7DC4D /* NSView+FirstResponder.m */, EEE73B1416EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.h */, EEE73B1516EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m */, + EE52145A16F3E8BD00E3C574 /* NSOutlineView+ItemAccessors.h */, + EE52145B16F3E8BD00E3C574 /* NSOutlineView+ItemAccessors.m */, ); name = Categories; sourceTree = ""; @@ -442,6 +452,8 @@ EEE703DC16F089FE002FDD69 /* NJHIDManager.m in Sources */, EEE703DE16F0B3F6002FDD69 /* NJInputPathElement.m in Sources */, EE8455DD16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m in Sources */, + EE52145C16F3E8BD00E3C574 /* NSOutlineView+ItemAccessors.m in Sources */, + EE52145F16F404D500E3C574 /* NJDeviceViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Info.plist b/Info.plist index b17e626..0b9f61d 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 306 + 328 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright diff --git a/Other Sources/Enjoyable_Prefix.pch b/Other Sources/Enjoyable_Prefix.pch index b0d1f40..e074f78 100644 --- a/Other Sources/Enjoyable_Prefix.pch +++ b/Other Sources/Enjoyable_Prefix.pch @@ -16,3 +16,4 @@ #import "NSString+FixFilename.h" #import "NSRunningApplication+NJPossibleNames.h" #import "NSRunningApplication+LoginItem.h" +#import "NSOutlineView+ItemAccessors.h" diff --git a/Resources/English.lproj/MainMenu.xib b/Resources/English.lproj/MainMenu.xib index be18595..48ed439 100644 --- a/Resources/English.lproj/MainMenu.xib +++ b/Resources/English.lproj/MainMenu.xib @@ -2,10 +2,10 @@ 1080 - 12C2034 + 12D78 3084 - 1187.34 - 625.00 + 1187.37 + 626.00 com.apple.InterfaceBuilder.CocoaPlugin 3084 @@ -489,7 +489,7 @@ {664, 323} - + 256 @@ -501,6 +501,7 @@ -2147483374 {{20, 20}, {194, 283}} + _NS:22 YES @@ -530,6 +531,7 @@ Lg 274 {{20, 20}, {194, 283}} + _NS:22 YES @@ -564,7 +566,8 @@ aW5nLg {232, 321} - + + YES NO YES @@ -659,6 +662,7 @@ aW5nLg {{1, 1}, {232, 321}} + @@ -669,6 +673,7 @@ aW5nLg -2147483392 {{1, 1}, {8, 298}} + NO @@ -680,6 +685,7 @@ aW5nLg -2147483392 {{-100, -100}, {473, 15}} + NO 1 @@ -690,7 +696,8 @@ aW5nLg {234, 323} - + + 150034 @@ -703,6 +710,7 @@ aW5nLg {234, 323} + _NS:9 NSView @@ -716,6 +724,7 @@ aW5nLg 265 {{189, 117}, {224, 20}} + _NS:9 YES @@ -757,6 +766,8 @@ aW5nLg 268 {{343, 31}, {70, 18}} + + _NS:9 YES @@ -787,6 +798,7 @@ aW5nLg 265 {{189, 33}, {150, 20}} + _NS:9 YES @@ -831,6 +843,7 @@ aW5nLg 265 {{191, 24}, {146, 16}} + _NS:9 YES @@ -856,6 +869,7 @@ aW5nLg 265 {{189, 70}, {224, 24}} + _NS:9 YES @@ -909,6 +923,7 @@ aW5nLg 265 {{191, 108}, {220, 16}} + _NS:9 YES @@ -934,6 +949,7 @@ aW5nLg 265 {{191, 196}, {220, 23}} + _NS:9 NJKeyInputField @@ -943,6 +959,7 @@ aW5nLg 265 {{188, 153}, {226, 26}} + YES @@ -975,6 +992,7 @@ aW5nLg 268 {{24, 20}, {163, 250}} + NO 6 @@ -1105,6 +1123,7 @@ aW5nLg 266 {{9, 286}, {412, 17}} + YES @@ -1128,6 +1147,7 @@ aW5nLg 10 {{12, 276}, {406, 5}} + {0, 0} @@ -1154,12 +1174,15 @@ aW5nLg {{233, 0}, {431, 323}} + _NS:9 NSView {664, 323} + + {{0, 0}, {1440, 878}} @@ -1516,6 +1539,9 @@ aW5nLg SUUpdater + + NJDeviceViewController + @@ -1647,14 +1673,6 @@ aW5nLg 826 - - - outlineView - - - - 648 - connectDevicePrompt @@ -1687,6 +1705,14 @@ aW5nLg 982 + + + devicesViewController + + + + 992 + mappingsController @@ -1851,17 +1877,17 @@ aW5nLg dataSource - + - 647 + 990 delegate - + - 696 + 991 @@ -2159,6 +2185,38 @@ aW5nLg 969 + + + delegate + + + + 984 + + + + hidStoppedNotice + + + + 987 + + + + noDevicesNotice + + + + 988 + + + + inputsTree + + + + 989 + @@ -2989,6 +3047,11 @@ aW5nLg + + 983 + + + @@ -3240,12 +3303,13 @@ aW5nLg com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin - 982 + 992 @@ -3296,47 +3360,37 @@ aW5nLg NJDeviceController NSObject - translatingEventsChanged: + simulatingEventsChanged: NSButton - translatingEventsChanged: + simulatingEventsChanged: - translatingEventsChanged: + simulatingEventsChanged: NSButton - NSView - NSView + NJDeviceViewController NJMappingsController - NSOutlineView NJOutputController - NSButton + NSButton - - connectDevicePrompt - NSView - - - hidSleepingPrompt - NSView + + devicesViewController + NJDeviceViewController mappingsController NJMappingsController - - outlineView - NSOutlineView - outputController NJOutputController - - translatingEventsButton + + simulatingEventsButton NSButton @@ -3345,6 +3399,38 @@ aW5nLg ./Classes/NJDeviceController.h + + NJDeviceViewController + NSObject + + id + NSView + NSOutlineView + NSView + + + + delegate + id + + + hidStoppedNotice + NSView + + + inputsTree + NSOutlineView + + + noDevicesNotice + NSView + + + + IBProjectSource + ./Classes/NJDeviceViewController.h + + NJKeyInputField NSControl @@ -3369,7 +3455,7 @@ aW5nLg NSObject id - NSMenuItem + NSMenuItem NSMenu @@ -3377,8 +3463,8 @@ aW5nLg delegate id - - eventTranslationToggle + + eventSimulationToggle NSMenuItem -- 2.20.1 From b14d8429658dd6f3f7b4904301031addb502b22c Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Sun, 17 Mar 2013 18:06:08 +0100 Subject: [PATCH 15/16] Split NJMappingController view handling off into NJMappingViewController. This is still buggy. --- Categories/NSMutableArray+MoveObject.h | 5 +- Categories/NSMutableArray+MoveObject.m | 2 +- Classes/EnjoyableApplicationDelegate.h | 2 + Classes/EnjoyableApplicationDelegate.m | 68 ++++- Classes/NJDeviceController.h | 2 - Classes/NJDeviceController.m | 36 ++- Classes/NJDeviceViewController.h | 18 +- Classes/NJDeviceViewController.m | 54 ++-- Classes/NJMapping.h | 4 +- Classes/NJMapping.m | 4 +- Classes/NJMappingsController.h | 28 +- Classes/NJMappingsController.m | 279 +++++------------ Classes/NJMappingsViewController.h | 72 +++++ Classes/NJMappingsViewController.m | 233 ++++++++++++++ Classes/NJOutput.h | 2 +- Classes/NJOutput.m | 2 +- Classes/NJOutputKeyPress.m | 2 +- Classes/NJOutputMapping.m | 2 +- Classes/NJOutputMouseButton.m | 2 +- Classes/NJOutputMouseMove.m | 2 +- Classes/NJOutputMouseScroll.m | 2 +- Enjoyable.xcodeproj/project.pbxproj | 6 + Info.plist | 2 +- Resources/English.lproj/MainMenu.xib | 403 ++++++++++++++----------- 24 files changed, 751 insertions(+), 481 deletions(-) create mode 100644 Classes/NJMappingsViewController.h create mode 100644 Classes/NJMappingsViewController.m diff --git a/Categories/NSMutableArray+MoveObject.h b/Categories/NSMutableArray+MoveObject.h index ce62234..e8ac0b0 100644 --- a/Categories/NSMutableArray+MoveObject.h +++ b/Categories/NSMutableArray+MoveObject.h @@ -11,10 +11,9 @@ @interface NSMutableArray (MoveObject) - (void)moveObjectAtIndex:(NSUInteger)src toIndex:(NSUInteger)dst; - // Move the object at index src to (pre-move) index dst. Other + // Move the object at index src to (post-move) index dst. Other // objects shift up or down as necessary to make room, as in - // insertObject:atIndex:. Because the object is also removed from - // the source index, its resulting index may be one less than dst. + // insertObject:atIndex:. - (BOOL)moveFirstwards:(id)object upTo:(NSUInteger)minIndex; - (BOOL)moveLastwards:(id)object upTo:(NSUInteger)maxIndex; diff --git a/Categories/NSMutableArray+MoveObject.m b/Categories/NSMutableArray+MoveObject.m index 6c7987e..a02b98b 100644 --- a/Categories/NSMutableArray+MoveObject.m +++ b/Categories/NSMutableArray+MoveObject.m @@ -13,7 +13,7 @@ - (void)moveObjectAtIndex:(NSUInteger)src toIndex:(NSUInteger)dst { id obj = self[src]; [self removeObjectAtIndex:src]; - [self insertObject:obj atIndex:dst > src ? dst - 1 : dst]; + [self insertObject:obj atIndex:dst]; } - (BOOL)moveFirstwards:(id)object upTo:(NSUInteger)minIndex { diff --git a/Classes/EnjoyableApplicationDelegate.h b/Classes/EnjoyableApplicationDelegate.h index acea8a9..b28269b 100644 --- a/Classes/EnjoyableApplicationDelegate.h +++ b/Classes/EnjoyableApplicationDelegate.h @@ -21,5 +21,7 @@ @property (nonatomic, strong) IBOutlet NJMappingsController *mappingsController; - (IBAction)restoreToForeground:(id)sender; +- (IBAction)importMappingClicked:(id)sender; +- (IBAction)exportMappingClicked:(id)sender; @end diff --git a/Classes/EnjoyableApplicationDelegate.m b/Classes/EnjoyableApplicationDelegate.m index db62f53..fe781ef 100644 --- a/Classes/EnjoyableApplicationDelegate.m +++ b/Classes/EnjoyableApplicationDelegate.m @@ -135,9 +135,22 @@ - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { [self restoreToForeground:sender]; - NSURL *url = [NSURL fileURLWithPath:filename]; - [self.mappingsController addMappingWithContentsOfURL:url]; - return YES; + NSError *error; + NSURL *URL = [NSURL fileURLWithPath:filename]; + NJMapping *mapping = [NJMapping mappingWithContentsOfURL:URL + mappings:self.mappingsController + error:&error]; + if (mapping) { + [self.mappingsController addOrMergeMapping:mapping]; + return YES; + } else { + [window presentError:error + modalForWindow:window + delegate:nil + didPresentSelector:nil + contextInfo:nil]; + return NO; + } } - (void)mappingWasChosen:(NJMapping *)mapping { @@ -146,7 +159,7 @@ - (void)mappingListShouldOpen { [self restoreToForeground:self]; - [self.mappingsController mappingPressed:self]; + [self.mappingsController.mvc mappingTriggerClicked:self]; } - (void)loginItemPromptDidEnd:(NSWindow *)sheet @@ -190,5 +203,52 @@ return NO; } +- (void)importMappingClicked:(id)sender { + NSOpenPanel *panel = [NSOpenPanel openPanel]; + panel.allowedFileTypes = @[ @"enjoyable", @"json", @"txt" ]; + [panel beginSheetModalForWindow:window + completionHandler:^(NSInteger result) { + if (result != NSFileHandlingPanelOKButton) + return; + [panel close]; + NSError *error; + NJMapping *mapping = [NJMapping mappingWithContentsOfURL:panel.URL + mappings:self.mappingsController + error:&error]; + if (mapping) { + [self.mappingsController addOrMergeMapping:mapping]; + } else { + [window presentError:error + modalForWindow:window + delegate:nil + didPresentSelector:nil + contextInfo:nil]; + } + }]; + +} + +- (void)exportMappingClicked:(id)sender { + NSSavePanel *panel = [NSSavePanel savePanel]; + panel.allowedFileTypes = @[ @"enjoyable" ]; + NJMapping *mapping = self.mappingsController.currentMapping; + panel.nameFieldStringValue = [mapping.name stringByFixingPathComponent]; + [panel beginSheetModalForWindow:window + completionHandler:^(NSInteger result) { + if (result != NSFileHandlingPanelOKButton) + return; + [panel close]; + NSError *error; + if (![mapping writeToURL:panel.URL error:&error]) { + [window presentError:error + modalForWindow:window + delegate:nil + didPresentSelector:nil + contextInfo:nil]; + } + }]; +} + + @end diff --git a/Classes/NJDeviceController.h b/Classes/NJDeviceController.h index fcc09ef..29b8b38 100644 --- a/Classes/NJDeviceController.h +++ b/Classes/NJDeviceController.h @@ -9,11 +9,9 @@ #import "NJHIDManager.h" #import "NJDeviceViewController.h" -@class NJDevice; @class NJInput; @class NJMappingsController; @class NJOutputController; -@class NJDeviceViewController; @interface NJDeviceController : NSObject { diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index b0e8de3..329b353 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -68,15 +68,6 @@ [_continuousOutputsTick invalidate]; } -- (id)elementForUID:(NSString *)uid { - for (NJDevice *dev in _devices) { - id item = [dev elementForUID:uid]; - if (item) - return item; - } - return nil; -} - - (void)addRunningOutput:(NJOutput *)output { // Axis events will trigger every small movement, don't keep // re-adding them or they trigger multiple times each time. @@ -238,21 +229,40 @@ self.simulatingEvents = sender.state == NSOnState; } -- (void)deviceViewControllerDidSelectNothing:(NJDeviceViewController *)devices { +- (NSInteger)numberOfDevicesInDeviceList:(NJDeviceViewController *)dvc { + return _devices.count; +} + +- (NJDevice *)deviceViewController:(NJDeviceViewController *)dvc + deviceForIndex:(NSUInteger)idx { + return _devices[idx]; +} + +- (id)deviceViewController:(NJDeviceViewController *)dvc + elementForUID:(NSString *)uid { + for (NJDevice *dev in _devices) { + id item = [dev elementForUID:uid]; + if (item) + return item; + } + return nil; +} + +- (void)deviceViewControllerDidSelectNothing:(NJDeviceViewController *)dvc { [outputController loadCurrent]; } -- (void)deviceViewController:(NJDeviceViewController *)devices +- (void)deviceViewController:(NJDeviceViewController *)dvc didSelectBranch:(NJInputPathElement *)handler { [outputController loadCurrent]; } -- (void)deviceViewController:(NJDeviceViewController *)devices +- (void)deviceViewController:(NJDeviceViewController *)dvc didSelectHandler:(NJInputPathElement *)handler { [outputController loadCurrent]; } -- (void)deviceViewController:(NJDeviceViewController *)devices +- (void)deviceViewController:(NJDeviceViewController *)dvc didSelectDevice:(NJInputPathElement *)device { [outputController loadCurrent]; } diff --git a/Classes/NJDeviceViewController.h b/Classes/NJDeviceViewController.h index 9e1a43f..6d039af 100644 --- a/Classes/NJDeviceViewController.h +++ b/Classes/NJDeviceViewController.h @@ -20,9 +20,6 @@ @property (nonatomic, weak) IBOutlet id delegate; -@property (nonatomic, copy) NSArray *devices; - // Assigning directly will trigger a full reload. - - (void)addedDevice:(NJDevice *)device atIndex:(NSUInteger)idx; - (void)removedDevice:(NJDevice *)device atIndex:(NSUInteger)idx; // But using these will animate nicely. @@ -38,12 +35,19 @@ @protocol NJDeviceViewControllerDelegate -- (void)deviceViewController:(NJDeviceViewController *)devices +- (NSInteger)numberOfDevicesInDeviceList:(NJDeviceViewController *)dvc; +- (NJDevice *)deviceViewController:(NJDeviceViewController *)dvc + deviceForIndex:(NSUInteger)idx; +- (NJInputPathElement *)deviceViewController:(NJDeviceViewController *)dvc + elementForUID:(NSString *)uid; + + +- (void)deviceViewController:(NJDeviceViewController *)dvc didSelectDevice:(NJInputPathElement *)device; -- (void)deviceViewController:(NJDeviceViewController *)devices +- (void)deviceViewController:(NJDeviceViewController *)dvc didSelectBranch:(NJInputPathElement *)handler; -- (void)deviceViewController:(NJDeviceViewController *)devices +- (void)deviceViewController:(NJDeviceViewController *)dvc didSelectHandler:(NJInputPathElement *)handler; -- (void)deviceViewControllerDidSelectNothing:(NJDeviceViewController *)devices; +- (void)deviceViewControllerDidSelectNothing:(NJDeviceViewController *)dvc; @end diff --git a/Classes/NJDeviceViewController.m b/Classes/NJDeviceViewController.m index 3d20e65..dfb2dca 100644 --- a/Classes/NJDeviceViewController.m +++ b/Classes/NJDeviceViewController.m @@ -11,7 +11,6 @@ #import "NJInputPathElement.h" @implementation NJDeviceViewController { - NSMutableArray *_devices; NSMutableArray *_expanded; } @@ -22,7 +21,6 @@ expanded = @[]; _expanded = [[NSMutableArray alloc] initWithCapacity:MAX(16, _expanded.count)]; [_expanded addObjectsFromArray:expanded]; - _devices = [[NSMutableArray alloc] initWithCapacity:16]; } return self; } @@ -34,17 +32,8 @@ } } -- (id)elementForUID:(NSString *)uid { - for (NJInputPathElement *dev in _devices) { - id item = [dev elementForUID:uid]; - if (item) - return item; - } - return nil; -} - - (void)expandRecursiveByUID:(NSString *)uid { - [self expandRecursive:[self elementForUID:uid]]; + [self expandRecursive:[self.delegate deviceViewController:self elementForUID:uid]]; } - (void)reexpandAll { @@ -52,23 +41,12 @@ [self expandRecursiveByUID:uid]; if (self.inputsTree.selectedRow == -1) { NSString *selectedUid = [NSUserDefaults.standardUserDefaults objectForKey:@"selected input"]; - id item = [self elementForUID:selectedUid]; + id item = [self.delegate deviceViewController:self elementForUID:selectedUid]; [self.inputsTree selectItem:item]; } } -- (void)setDevices:(NSArray *)devices { - _devices = [devices mutableCopy]; - id item = self.inputsTree.selectedItem; - [self.inputsTree selectItem:nil]; - [self.inputsTree reloadData]; - [self reexpandAll]; - [self.inputsTree selectItem:item]; - self.noDevicesNotice.hidden = self.devices.count || !self.hidStoppedNotice.isHidden; -} - - (void)addedDevice:(NJDevice *)device atIndex:(NSUInteger)idx { - [_devices insertObject:device atIndex:idx]; [self.inputsTree beginUpdates]; [self.inputsTree insertItemsAtIndexes:[[NSIndexSet alloc] initWithIndex:idx] inParent:nil @@ -79,23 +57,24 @@ } - (void)removedDevice:(NJDevice *)device atIndex:(NSUInteger)idx { - [_devices removeObjectAtIndex:idx]; + BOOL anyDevices = !![self.delegate numberOfDevicesInDeviceList:self]; [self.inputsTree beginUpdates]; [self.inputsTree removeItemsAtIndexes:[[NSIndexSet alloc] initWithIndex:idx] inParent:nil withAnimation:NSTableViewAnimationSlideLeft]; [self.inputsTree endUpdates]; - self.noDevicesNotice.hidden = self.devices.count || !self.hidStoppedNotice.isHidden; + self.noDevicesNotice.hidden = anyDevices || !self.hidStoppedNotice.isHidden; } - (void)hidStarted { - self.noDevicesNotice.hidden = !!self.devices.count; + self.noDevicesNotice.hidden = !![self.delegate numberOfDevicesInDeviceList:self]; self.hidStoppedNotice.hidden = YES; } - (void)hidStopped { + self.noDevicesNotice.hidden = YES; self.hidStoppedNotice.hidden = NO; - self.devices = @[]; + [self.inputsTree reloadData]; } - (void)expandAndSelectItem:(NJInputPathElement *)item { @@ -110,18 +89,25 @@ - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(NJInputPathElement *)item { - return item ? item.children.count : _devices.count; + if (item) + return item.children.count; + else + return [self.delegate numberOfDevicesInDeviceList:self]; + } - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(NJInputPathElement *)item { - return item ? [[item children] count] > 0: YES; + return item ? item.children.count > 0: YES; } - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(NJInputPathElement *)item { - return item ? item.children[index] : _devices[index]; + if (item) + return item.children[index]; + else + return [self.delegate deviceViewController:self deviceForIndex:index]; } - (id)outlineView:(NSOutlineView *)outlineView @@ -152,13 +138,15 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn - (void)outlineViewItemDidExpand:(NSNotification *)notification { NSString *uid = [notification.userInfo[@"NSObject"] uid]; - if (![_expanded containsObject:uid]) + if (![_expanded containsObject:uid]) { [_expanded addObject:uid]; - + [NSUserDefaults.standardUserDefaults setObject:_expanded forKey:@"expanded rows"]; + } } - (void)outlineViewItemDidCollapse:(NSNotification *)notification { [_expanded removeObject:[notification.userInfo[@"NSObject"] uid]]; + [NSUserDefaults.standardUserDefaults setObject:_expanded forKey:@"expanded rows"]; } - (BOOL)outlineView:(NSOutlineView *)outlineView diff --git a/Classes/NJMapping.h b/Classes/NJMapping.h index 8b80012..0d5db89 100644 --- a/Classes/NJMapping.h +++ b/Classes/NJMapping.h @@ -15,12 +15,12 @@ @property (nonatomic, readonly) NSUInteger count; + (id)mappingWithContentsOfURL:(NSURL *)url - mappings:(NSArray *)mappings + mappings:(id )mappings error:(NSError **)error; - (id)initWithName:(NSString *)name; - (id)initWithSerialization:(NSDictionary *)serialization - mappings:(NSArray *)mappings; + mappings:(id )mappings; - (NJOutput *)objectForKeyedSubscript:(NJInput *)input; - (void)setObject:(NJOutput *)output forKeyedSubscript:(NJInput *)input; diff --git a/Classes/NJMapping.m b/Classes/NJMapping.m index 0e67cd3..04f6e68 100644 --- a/Classes/NJMapping.m +++ b/Classes/NJMapping.m @@ -34,7 +34,7 @@ } - (id)initWithSerialization:(NSDictionary *)serialization - mappings:(NSArray *)mappings { + mappings:(id )mappings { if ((self = [self initWithName:serialization[@"name"]])) { NSDictionary *entries = serialization[@"entries"]; if ([entries isKindOfClass:NSDictionary.class]) { @@ -100,7 +100,7 @@ return NO; } -+ (id)mappingWithContentsOfURL:(NSURL *)url mappings:(NSArray *)mappings error:(NSError **)error { ++ (id)mappingWithContentsOfURL:(NSURL *)url mappings:(id )mappings error:(NSError **)error { NSInputStream *stream = [NSInputStream inputStreamWithURL:url]; [stream open]; NSDictionary *serialization = stream && !*error diff --git a/Classes/NJMappingsController.h b/Classes/NJMappingsController.h index a4d9c18..39aaf79 100644 --- a/Classes/NJMappingsController.h +++ b/Classes/NJMappingsController.h @@ -9,36 +9,20 @@ @class NJMapping; @class NJOutputController; -@interface NJMappingsController : NSObject -{ - IBOutlet NSButton *removeButton; - IBOutlet NSTableView *tableView; - IBOutlet NSButton *popoverActivate; - IBOutlet NSPopover *popover; - IBOutlet NSButton *moveUp; - IBOutlet NSButton *moveDown; -} +#import "NJMappingsViewController.h" + +@interface NJMappingsController : NSObject @property (nonatomic, readonly) NJMapping *currentMapping; +@property (nonatomic, strong) IBOutlet NJMappingsViewController *mvc; - (NJMapping *)objectForKeyedSubscript:(NSString *)name; - (NJMapping *)objectAtIndexedSubscript:(NSUInteger)idx; -- (void)addMappingWithContentsOfURL:(NSURL *)url; - (void)activateMapping:(NJMapping *)mapping; - (void)activateMappingForProcess:(NSRunningApplication *)app; +- (void)addOrMergeMapping:(NJMapping *)mapping; - (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 index c3209ac..531de12 100644 --- a/Classes/NJMappingsController.m +++ b/Classes/NJMappingsController.m @@ -30,11 +30,6 @@ 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]) @@ -47,13 +42,12 @@ } - (void)mappingsSet { - [tableView reloadData]; - [self updateInterfaceForCurrentMapping]; [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingListChanged object:self userInfo:@{ NJMappingListKey: _mappings, NJMappingKey: _currentMapping }]; + [self.mvc changedActiveMappingToIndex:[_mappings indexOfObjectIdenticalTo:_currentMapping]]; } - (void)mappingsChanged { @@ -94,16 +88,6 @@ _manualMapping = oldMapping; } -- (void)updateInterfaceForCurrentMapping { - NSUInteger selected = [_mappings indexOfObject:_currentMapping]; - removeButton.enabled = selected != 0; - moveUp.enabled = selected > 1; - moveDown.enabled = selected && selected != _mappings.count - 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; @@ -112,56 +96,13 @@ NSLog(@"Switching to mapping %@.", mapping.name); _manualMapping = mapping; _currentMapping = mapping; - [self updateInterfaceForCurrentMapping]; + [self.mvc changedActiveMappingToIndex:[_mappings indexOfObjectIdenticalTo:_currentMapping]]; [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingChanged object:self userInfo:@{ NJMappingKey : _currentMapping }]; } -- (IBAction)addPressed:(id)sender { - [self mappingPressed:sender]; - NJMapping *newMapping = [[NJMapping alloc] init]; - [_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]; @@ -194,6 +135,7 @@ _mappings = newMappings; if (selected >= newMappings.count) selected = 0; + [self.mvc reloadData]; [self activateMapping:_mappings[selected]]; [self mappingsSet]; } @@ -208,29 +150,29 @@ switch (returnCode) { case NSAlertFirstButtonReturn: // Merge [oldMapping mergeEntriesFrom:newMapping]; + _currentMapping = nil; [self activateMapping:oldMapping]; [self mappingsChanged]; break; case NSAlertThirdButtonReturn: // New Mapping + [self.mvc.mappingList beginUpdates]; [_mappings addObject:newMapping]; + [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:NO]; + [self.mvc.mappingList endUpdates]; [self activateMapping:newMapping]; [self mappingsChanged]; - [self mappingPressed:alert]; - [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:_mappings.count - 1] byExtendingSelection:NO]; - [tableView editColumn:0 row:_mappings.count - 1 withEvent:nil select:YES]; break; default: // Cancel, other. break; } } -- (void)addMappingWithContentsOfURL:(NSURL *)url { - NSWindow *window = popoverActivate.window; - NSError *error; - NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url - mappings:_mappings - error:&error]; - +- (void)addOrMergeMapping:(NJMapping *)mapping { + [self addOrMergeMapping:mapping atIndex:-1]; +} + +- (void)addOrMergeMapping:(NJMapping *)mapping atIndex:(NSInteger)idx { + NSWindow *window = NSApplication.sharedApplication.keyWindow; if (mapping) { NJMapping *mergeInto = self[mapping.name]; if ([mergeInto hasConflictWith:mapping]) { @@ -242,7 +184,7 @@ [conflictAlert addButtonWithTitle:NSLocalizedString(@"import and merge", @"button to merge imported mappings")]; [conflictAlert addButtonWithTitle:NSLocalizedString(@"cancel import", @"button to cancel import")]; [conflictAlert addButtonWithTitle:NSLocalizedString(@"import new mapping", @"button to import as new mapping")]; - [conflictAlert beginSheetModalForWindow:popoverActivate.window + [conflictAlert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(mappingConflictDidResolve:returnCode:contextInfo:) contextInfo:(void *)CFBridgingRetain(@{ @"old mapping": mergeInto, @@ -252,163 +194,86 @@ [self activateMapping:mergeInto]; [self mappingsChanged]; } else { - [_mappings addObject:mapping]; + if (idx == -1) + idx = _mappings.count - 1; + [self.mvc.mappingList beginUpdates]; + [_mappings insertObject:mapping atIndex:idx]; + [self.mvc addedMappingAtIndex:idx startEditing:NO]; + [self.mvc.mappingList endUpdates]; [self activateMapping:mapping]; [self mappingsChanged]; } - } else { - [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]; +- (NSInteger)numberOfMappings:(NJMappingsViewController *)dvc { + return _mappings.count; } -- (void)popoverWillShow:(NSNotification *)notification { - popoverActivate.state = NSOnState; +- (NJMapping *)mappingsViewController:(NJMappingsViewController *)dvc + mappingForIndex:(NSUInteger)idx { + return _mappings[idx]; } -- (void)popoverWillClose:(NSNotification *)notification { - popoverActivate.state = NSOffState; +- (void)mappingsViewController:(NJMappingsViewController *)mvc + editedMappingAtIndex:(NSInteger)index { + [self mappingsChanged]; } -- (IBAction)moveUpPressed:(id)sender { - if ([_mappings moveFirstwards:_currentMapping upTo:1]) - [self mappingsChanged]; +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + canMoveMappingFromIndex:(NSInteger)fromIdx + toIndex:(NSInteger)toIdx { + return fromIdx != toIdx && fromIdx != 0 && toIdx != 0 && toIdx < (NSInteger)_mappings.count; } -- (IBAction)moveDownPressed:(id)sender { - if ([_mappings moveLastwards:_currentMapping]) - [self mappingsChanged]; +- (void)mappingsViewController:(NJMappingsViewController *)mvc + moveMappingFromIndex:(NSInteger)fromIdx + toIndex:(NSInteger)toIdx { + [_mappings moveObjectAtIndex:fromIdx toIndex:toIdx]; + [self mappingsChanged]; } -- (BOOL)tableView:(NSTableView *)tableView_ - acceptDrop:(id )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; - } +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + canRemoveMappingAtIndex:(NSInteger)idx { + return idx != 0; } -- (NSDragOperation)tableView:(NSTableView *)tableView_ - validateDrop:(id )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; - } +- (void)mappingsViewController:(NJMappingsViewController *)mvc + removeMappingAtIndex:(NSInteger)idx { + NJMapping *old = self[idx]; + [self.mvc.mappingList beginUpdates]; + [_mappings removeObjectAtIndex:idx]; + [self.mvc removedMappingAtIndex:idx]; + [self.mvc.mappingList endUpdates]; + if (old == _currentMapping) + [self activateMapping:self[MIN(idx, _mappings.count - 1)]]; + [self mappingsChanged]; } -- (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)mappingsViewController:(NJMappingsViewController *)mvc + importMappingFromURL:(NSURL *)url + atIndex:(NSInteger)index + error:(NSError **)error { + NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url + mappings:_mappings + error:error]; + [self addOrMergeMapping:mapping atIndex:index]; + return !!mapping; +} + +- (void)mappingsViewController:(NJMappingsViewController *)mvc + addMapping:(NJMapping *)mapping { + [self.mvc.mappingList beginUpdates]; + [_mappings addObject:mapping]; + [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:YES]; + [self.mvc.mappingList endUpdates]; + [self activateMapping:mapping]; + [self mappingsChanged]; } -- (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; - } +- (void)mappingsViewController:(NJMappingsViewController *)mvc + choseMappingAtIndex:(NSInteger)idx { + [self activateMapping:self[idx]]; } @end diff --git a/Classes/NJMappingsViewController.h b/Classes/NJMappingsViewController.h new file mode 100644 index 0000000..2d0bd25 --- /dev/null +++ b/Classes/NJMappingsViewController.h @@ -0,0 +1,72 @@ +// +// NJMappingsViewController.h +// Enjoyable +// +// Created by Joe Wreschnig on 3/17/13. +// +// + +@class NJMapping; +@protocol NJMappingsViewControllerDelegate; + +@interface NJMappingsViewController : NSViewController + +@property (nonatomic, weak) IBOutlet id delegate; + +@property (nonatomic, strong) IBOutlet NSButton *removeMapping; +@property (nonatomic, strong) IBOutlet NSTableView *mappingList; +@property (nonatomic, strong) IBOutlet NSButton *mappingListTrigger; +@property (nonatomic, strong) IBOutlet NSPopover *mappingListPopover; +@property (nonatomic, strong) IBOutlet NSButton *moveUp; +@property (nonatomic, strong) IBOutlet NSButton *moveDown; + +- (IBAction)addClicked:(id)sender; +- (IBAction)removeClicked:(id)sender; +- (IBAction)moveUpClicked:(id)sender; +- (IBAction)moveDownClicked:(id)sender; +- (IBAction)mappingTriggerClicked:(id)sender; + +- (void)addedMappingAtIndex:(NSInteger)index startEditing:(BOOL)startEditing; +- (void)removedMappingAtIndex:(NSInteger)index; +- (void)changedActiveMappingToIndex:(NSInteger)index; + +- (void)reloadData; + +@end + +@protocol NJMappingsViewControllerDelegate + +- (NSInteger)numberOfMappings:(NJMappingsViewController *)dvc; +- (NJMapping *)mappingsViewController:(NJMappingsViewController *)dvc + mappingForIndex:(NSUInteger)idx; + + +- (void)mappingsViewController:(NJMappingsViewController *)mvc + editedMappingAtIndex:(NSInteger)index; + +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + canMoveMappingFromIndex:(NSInteger)fromIdx + toIndex:(NSInteger)toIdx; +- (void)mappingsViewController:(NJMappingsViewController *)mvc + moveMappingFromIndex:(NSInteger)fromIdx + toIndex:(NSInteger)toIdx; + +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + canRemoveMappingAtIndex:(NSInteger)idx; +- (void)mappingsViewController:(NJMappingsViewController *)mvc + removeMappingAtIndex:(NSInteger)idx; + +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + importMappingFromURL:(NSURL *)url + atIndex:(NSInteger)index + error:(NSError **)error; +- (void)mappingsViewController:(NJMappingsViewController *)mvc + addMapping:(NJMapping *)mapping; + +- (void)mappingsViewController:(NJMappingsViewController *)mvc + choseMappingAtIndex:(NSInteger)idx; + +@end diff --git a/Classes/NJMappingsViewController.m b/Classes/NJMappingsViewController.m new file mode 100644 index 0000000..fd9ce41 --- /dev/null +++ b/Classes/NJMappingsViewController.m @@ -0,0 +1,233 @@ +// +// NJMappingsViewController.m +// Enjoyable +// +// Created by Joe Wreschnig on 3/17/13. +// +// + +#import "NJMappingsViewController.h" + +#import "NJMapping.h" + +#define PB_ROW @"com.yukkurigames.Enjoyable.MappingRow" + + +@implementation NJMappingsViewController + +- (void)awakeFromNib { + [self.mappingList registerForDraggedTypes:@[PB_ROW, NSURLPboardType]]; + [self.mappingList setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; +} + +- (IBAction)addClicked:(id)sender { + NJMapping *newMapping = [[NJMapping alloc] init]; + [self.delegate mappingsViewController:self addMapping:newMapping]; +} + +- (IBAction)removeClicked:(id)sender { + [self.delegate mappingsViewController:self + removeMappingAtIndex:self.mappingList.selectedRow]; +} + +- (IBAction)moveUpClicked:(id)sender { + NSInteger fromIdx = self.mappingList.selectedRow; + NSInteger toIdx = fromIdx - 1; + [self.delegate mappingsViewController:self + moveMappingFromIndex:fromIdx + toIndex:toIdx]; + [self.mappingList beginUpdates]; + [self.mappingList moveRowAtIndex:fromIdx toIndex:toIdx]; + [self.mappingList endUpdates]; + [self.mappingList scrollRowToVisible:toIdx]; + [self.mappingList selectRowIndexes:[[NSIndexSet alloc] initWithIndex:toIdx] + byExtendingSelection:NO]; +} + +- (IBAction)moveDownClicked:(id)sender { + NSInteger fromIdx = self.mappingList.selectedRow; + NSInteger toIdx = fromIdx + 1; + [self.delegate mappingsViewController:self + moveMappingFromIndex:fromIdx + toIndex:toIdx]; + [self.mappingList beginUpdates]; + [self.mappingList moveRowAtIndex:fromIdx toIndex:toIdx]; + [self.mappingList endUpdates]; + [self.mappingList scrollRowToVisible:toIdx]; + [self.mappingList selectRowIndexes:[[NSIndexSet alloc] initWithIndex:toIdx] + byExtendingSelection:NO]; +} + +- (IBAction)mappingTriggerClicked:(id)sender { + [self.mappingListPopover showRelativeToRect:self.mappingListTrigger.bounds + ofView:self.mappingListTrigger + preferredEdge:NSMinXEdge]; + self.mappingListTrigger.state = NSOnState; +} + +- (void)popoverWillShow:(NSNotification *)notification { + self.mappingListTrigger.state = NSOnState; +} + +- (void)popoverWillClose:(NSNotification *)notification { + self.mappingListTrigger.state = NSOffState; +} + +- (void)addedMappingAtIndex:(NSInteger)index startEditing:(BOOL)startEditing { + [self.mappingList abortEditing]; + [self.mappingList beginUpdates]; + [self.mappingList insertRowsAtIndexes:[[NSIndexSet alloc] initWithIndex:index] + withAnimation:startEditing ? 0 : NSTableViewAnimationSlideLeft]; + [self.mappingList endUpdates]; + if (startEditing) { + [self.mappingListTrigger performClick:self]; + [self.mappingList editColumn:0 row:index withEvent:nil select:YES]; + [self.mappingList scrollRowToVisible:index]; + } +} + +- (void)removedMappingAtIndex:(NSInteger)index { + [self.mappingList abortEditing]; + [self.mappingList beginUpdates]; + [self.mappingList removeRowsAtIndexes:[[NSIndexSet alloc] initWithIndex:index] + withAnimation:NSTableViewAnimationEffectFade]; + [self.mappingList endUpdates]; +} + +- (void)changedActiveMappingToIndex:(NSInteger)index { + NJMapping *mapping = [self.delegate mappingsViewController:self + mappingForIndex:index]; + self.removeMapping.enabled = [self.delegate mappingsViewController:self + canRemoveMappingAtIndex:index]; + self.moveUp.enabled = [self.delegate mappingsViewController:self + canMoveMappingFromIndex:index toIndex:index - 1]; + self.moveDown.enabled = [self.delegate mappingsViewController:self + canMoveMappingFromIndex:index toIndex:index + 1]; + self.mappingListTrigger.title = mapping.name; + [self.mappingList selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO]; + [self.mappingList scrollRowToVisible:index]; + [NSUserDefaults.standardUserDefaults setInteger:index forKey:@"selected"]; + +} + +- (void)tableViewSelectionDidChange:(NSNotification *)note { + [self.mappingList abortEditing]; + NSTableView *tableView = note.object; + [self.delegate mappingsViewController:self + choseMappingAtIndex:tableView.selectedRow]; +} + +- (id)tableView:(NSTableView *)view objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)index { + return [self.delegate mappingsViewController:self + mappingForIndex:index].name; +} + +- (void)tableView:(NSTableView *)view + setObjectValue:(NSString *)obj + forTableColumn:(NSTableColumn *)col + row:(NSInteger)index { + NJMapping *mapping = [self.delegate mappingsViewController:self + mappingForIndex:index]; + mapping.name = obj; + [self.delegate mappingsViewController:self editedMappingAtIndex:index]; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { + return [self.delegate numberOfMappings:self]; +} + +- (BOOL)tableView:(NSTableView *)tableView + acceptDrop:(id )info + row:(NSInteger)row + dropOperation:(NSTableViewDropOperation)dropOperation { + NSPasteboard *pboard = [info draggingPasteboard]; + if ([pboard.types containsObject:PB_ROW]) { + NSString *value = [pboard stringForType:PB_ROW]; + NSInteger srcRow = [value intValue]; + row -= srcRow < row; + [self.mappingList beginUpdates]; + [self.mappingList moveRowAtIndex:srcRow toIndex:row]; + [self.mappingList endUpdates]; + [self.delegate mappingsViewController:self + moveMappingFromIndex:srcRow + toIndex:row]; + return YES; + } else if ([pboard.types containsObject:NSURLPboardType]) { + NSURL *url = [NSURL URLFromPasteboard:pboard]; + NSError *error; + if (![self.delegate mappingsViewController:self + importMappingFromURL:url + atIndex:row + error:&error]) { + [tableView presentError:error]; + return NO; + } else { + return YES; + } + } else { + return NO; + } +} + +- (NSDragOperation)tableView:(NSTableView *)tableView + validateDrop:(id )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.delegate mappingsViewController:self + mappingForIndex: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; + } +} + +- (void)reloadData { + [self.mappingList reloadData]; +} + +@end + diff --git a/Classes/NJOutput.h b/Classes/NJOutput.h index b82c4e7..5d4d470 100644 --- a/Classes/NJOutput.h +++ b/Classes/NJOutput.h @@ -20,7 +20,7 @@ - (NSDictionary *)serialize; + (NJOutput *)outputDeserialize:(NSDictionary *)serialization - withMappings:(NSArray *)mappings; + withMappings:(id )mappings; + (NSString *)serializationCode; @end diff --git a/Classes/NJOutput.m b/Classes/NJOutput.m index 409b3e6..35a56d9 100644 --- a/Classes/NJOutput.m +++ b/Classes/NJOutput.m @@ -37,7 +37,7 @@ } + (NJOutput *)outputDeserialize:(NSDictionary *)serialization - withMappings:(NSArray *)mappings { + withMappings:(id )mappings { // Don't crash loading old/bad mappings (but don't load them either). if (![serialization isKindOfClass:NSDictionary.class]) return nil; diff --git a/Classes/NJOutputKeyPress.m b/Classes/NJOutputKeyPress.m index 1cc7e65..e80aba5 100644 --- a/Classes/NJOutputKeyPress.m +++ b/Classes/NJOutputKeyPress.m @@ -22,7 +22,7 @@ } + (NJOutput *)outputDeserialize:(NSDictionary *)serialization - withMappings:(NSArray *)mappings { + withMappings:(id )mappings { NJOutputKeyPress *output = [[NJOutputKeyPress alloc] init]; output.keyCode = [serialization[@"key"] intValue]; return output; diff --git a/Classes/NJOutputMapping.m b/Classes/NJOutputMapping.m index fa8fda1..28a84ab 100644 --- a/Classes/NJOutputMapping.m +++ b/Classes/NJOutputMapping.m @@ -24,7 +24,7 @@ } + (NJOutputMapping *)outputDeserialize:(NSDictionary *)serialization - withMappings:(NSArray *)mappings { + withMappings:(id )mappings { NSString *name = serialization[@"name"]; NJOutputMapping *output = [[NJOutputMapping alloc] init]; for (NJMapping *mapping in mappings) { diff --git a/Classes/NJOutputMouseButton.m b/Classes/NJOutputMouseButton.m index 5ddb69d..e01e763 100644 --- a/Classes/NJOutputMouseButton.m +++ b/Classes/NJOutputMouseButton.m @@ -37,7 +37,7 @@ } + (NJOutput *)outputDeserialize:(NSDictionary *)serialization - withMappings:(NSArray *)mappings { + withMappings:(id )mappings { NJOutputMouseButton *output = [[NJOutputMouseButton alloc] init]; output.button = [serialization[@"button"] intValue]; return output; diff --git a/Classes/NJOutputMouseMove.m b/Classes/NJOutputMouseMove.m index be8b076..8591923 100644 --- a/Classes/NJOutputMouseMove.m +++ b/Classes/NJOutputMouseMove.m @@ -23,7 +23,7 @@ } + (NJOutput *)outputDeserialize:(NSDictionary *)serialization - withMappings:(NSArray *)mappings { + withMappings:(id )mappings { NJOutputMouseMove *output = [[NJOutputMouseMove alloc] init]; output.axis = [serialization[@"axis"] intValue]; output.speed = [serialization[@"speed"] floatValue]; diff --git a/Classes/NJOutputMouseScroll.m b/Classes/NJOutputMouseScroll.m index b97a3e7..6fdc918 100644 --- a/Classes/NJOutputMouseScroll.m +++ b/Classes/NJOutputMouseScroll.m @@ -22,7 +22,7 @@ } + (NJOutput *)outputDeserialize:(NSDictionary *)serialization - withMappings:(NSArray *)mappings { + withMappings:(id )mappings { NJOutputMouseScroll *output = [[NJOutputMouseScroll alloc] init]; output.direction = [serialization[@"direction"] intValue]; output.speed = [serialization[@"speed"] floatValue]; diff --git a/Enjoyable.xcodeproj/project.pbxproj b/Enjoyable.xcodeproj/project.pbxproj index e633c35..27ca44a 100644 --- a/Enjoyable.xcodeproj/project.pbxproj +++ b/Enjoyable.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ EE52145C16F3E8BD00E3C574 /* NSOutlineView+ItemAccessors.m in Sources */ = {isa = PBXBuildFile; fileRef = EE52145B16F3E8BD00E3C574 /* NSOutlineView+ItemAccessors.m */; }; EE52145F16F404D500E3C574 /* NJDeviceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EE52145E16F404D500E3C574 /* NJDeviceViewController.m */; }; EE6A122E16E8F46300EDBD32 /* Icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = EE6A122D16E8F46300EDBD32 /* Icon.icns */; }; + EE83ACEC16F545EA00083E94 /* NJMappingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EE83ACEB16F545EA00083E94 /* NJMappingsViewController.m */; }; EE8455DD16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8455DC16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m */; }; EED4CE6E16ED692400C65AA8 /* NJMappingMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */; }; EED4CE7716EE195100C65AA8 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EED4CE7616EE195100C65AA8 /* Sparkle.framework */; }; @@ -85,6 +86,8 @@ EE52145D16F404D500E3C574 /* NJDeviceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NJDeviceViewController.h; path = Classes/NJDeviceViewController.h; sourceTree = ""; }; EE52145E16F404D500E3C574 /* NJDeviceViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NJDeviceViewController.m; path = Classes/NJDeviceViewController.m; sourceTree = ""; }; EE6A122D16E8F46300EDBD32 /* Icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Icon.icns; sourceTree = ""; }; + EE83ACEA16F545EA00083E94 /* NJMappingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NJMappingsViewController.h; path = Classes/NJMappingsViewController.h; sourceTree = ""; }; + EE83ACEB16F545EA00083E94 /* NJMappingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NJMappingsViewController.m; path = Classes/NJMappingsViewController.m; sourceTree = ""; }; EE8455DB16F0E46B00F32A01 /* NSRunningApplication+LoginItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSRunningApplication+LoginItem.h"; path = "Categories/NSRunningApplication+LoginItem.h"; sourceTree = ""; }; EE8455DC16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSRunningApplication+LoginItem.m"; path = "Categories/NSRunningApplication+LoginItem.m"; sourceTree = ""; }; EED4CE6C16ED692400C65AA8 /* NJMappingMenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NJMappingMenuController.h; path = Classes/NJMappingMenuController.h; sourceTree = ""; }; @@ -210,6 +213,8 @@ EEE703DB16F089FE002FDD69 /* NJHIDManager.m */, EE52145D16F404D500E3C574 /* NJDeviceViewController.h */, EE52145E16F404D500E3C574 /* NJDeviceViewController.m */, + EE83ACEA16F545EA00083E94 /* NJMappingsViewController.h */, + EE83ACEB16F545EA00083E94 /* NJMappingsViewController.m */, ); name = Classes; sourceTree = ""; @@ -454,6 +459,7 @@ EE8455DD16F0E46B00F32A01 /* NSRunningApplication+LoginItem.m in Sources */, EE52145C16F3E8BD00E3C574 /* NSOutlineView+ItemAccessors.m in Sources */, EE52145F16F404D500E3C574 /* NJDeviceViewController.m in Sources */, + EE83ACEC16F545EA00083E94 /* NJMappingsViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Info.plist b/Info.plist index 0b9f61d..673fcbd 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 328 + 444 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright diff --git a/Resources/English.lproj/MainMenu.xib b/Resources/English.lproj/MainMenu.xib index 48ed439..6afbc50 100644 --- a/Resources/English.lproj/MainMenu.xib +++ b/Resources/English.lproj/MainMenu.xib @@ -37,7 +37,6 @@ NSToolbarFlexibleSpaceItem NSToolbarItem NSView - NSViewController NSWindowTemplate @@ -567,7 +566,7 @@ aW5nLg {232, 321} - + YES NO YES @@ -697,7 +696,7 @@ aW5nLg {234, 323} - + 150034 @@ -767,7 +766,6 @@ aW5nLg {{343, 31}, {70, 18}} - _NS:9 YES @@ -1192,7 +1190,7 @@ aW5nLg YES - + 256 @@ -1208,6 +1206,7 @@ aW5nLg 256 {198, 198} + YES NO @@ -1265,6 +1264,7 @@ aW5nLg {{1, 1}, {198, 198}} + @@ -1275,6 +1275,7 @@ aW5nLg -2147483392 {{306, 1}, {15, 403}} + NO @@ -1286,6 +1287,7 @@ aW5nLg -2147483392 {{-100, -100}, {366, 16}} + NO 1 @@ -1296,6 +1298,7 @@ aW5nLg {{0, 20}, {200, 200}} + 150034 @@ -1311,6 +1314,7 @@ aW5nLg 268 {{66, -1}, {68, 23}} + _NS:22 YES @@ -1339,6 +1343,7 @@ aW5nLg 292 {{0, -1}, {34, 23}} + YES @@ -1365,6 +1370,7 @@ aW5nLg 292 {{166, -1}, {34, 23}} + YES 67108864 @@ -1386,6 +1392,7 @@ aW5nLg 292 {{133, -1}, {34, 23}} + YES @@ -1408,6 +1415,7 @@ aW5nLg 292 {{33, -1}, {34, 23}} + YES @@ -1431,6 +1439,8 @@ aW5nLg {200, 220} + + NSView @@ -1446,7 +1456,6 @@ aW5nLg NJOutputController - 0 @@ -1542,6 +1551,9 @@ aW5nLg NJDeviceViewController + + NJMappingsViewController + @@ -1643,19 +1655,19 @@ aW5nLg - delegate + dataSource - + - 517 + 1001 - dataSource + delegate - + - 518 + 1002 @@ -1763,115 +1775,35 @@ aW5nLg - removePressed: - - - - 516 - - - - removeButton - - - - 519 - - - - tableView - - - - 520 - - - - exportPressed: - + exportMappingClicked: + - 815 + 1015 - importPressed: - + importMappingClicked: + - 816 - - - - mappingPressed: - - - - 855 - - - - popover - - - - 856 - - - - popoverActivate - - - - 857 - - - - addPressed: - - - - 515 + 1016 - moveUpPressed: - - - - 899 - - - - moveDownPressed: - - - - 900 - - - - moveUp - - + performClick: + + - 901 + 1012 - moveDown - - - - 902 - - - - addPressed: + mvc - + - 971 + 995 @@ -2057,29 +1989,21 @@ aW5nLg 871 - - - view - - - - 854 - contentViewController - + - 852 + 996 delegate - + - 853 + 1004 @@ -2217,6 +2141,110 @@ aW5nLg 989 + + + delegate + + + + 994 + + + + view + + + + 997 + + + + mappingTriggerClicked: + + + + 998 + + + + mappingListTrigger + + + + 999 + + + + mappingListPopover + + + + 1000 + + + + mappingList + + + + 1003 + + + + removeClicked: + + + + 1005 + + + + removeMapping + + + + 1006 + + + + addClicked: + + + + 1007 + + + + moveDownClicked: + + + + 1008 + + + + moveUpClicked: + + + + 1009 + + + + moveDown + + + + 1010 + + + + moveUp + + + + 1011 + @@ -2555,12 +2583,6 @@ aW5nLg - - 850 - - - Popover View Controller - 851 @@ -3052,6 +3074,11 @@ aW5nLg + + 993 + + + @@ -3163,7 +3190,6 @@ aW5nLg com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -3304,29 +3330,38 @@ aW5nLg com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin - 992 + 1016 EnjoyableApplicationDelegate NSObject - - restoreToForeground: - id - - - restoreToForeground: - + + id + id + id + + + + exportMappingClicked: + id + + + importMappingClicked: + id + + restoreToForeground: id - + NSMenu NJMappingsController @@ -3480,54 +3515,80 @@ aW5nLg NJMappingsController NSObject + + mvc + NJMappingsViewController + + + mvc + + mvc + NJMappingsViewController + + + + IBProjectSource + ./Classes/NJMappingsController.h + + + + NJMappingsViewController + NSViewController - id - id - id - id - id - id - id + id + id + id + id + id - - addPressed: + + addClicked: id - - exportPressed: + + mappingTriggerClicked: id - - importPressed: + + moveDownClicked: id - - mappingPressed: + + moveUpClicked: id - - moveDownPressed: - id - - - moveUpPressed: - id - - - removePressed: + + removeClicked: id + id + NSTableView + NSPopover + NSButton NSButton NSButton - NSPopover - NSButton - NSButton - NSTableView + NSButton + + delegate + id + + + mappingList + NSTableView + + + mappingListPopover + NSPopover + + + mappingListTrigger + NSButton + moveDown NSButton @@ -3536,26 +3597,14 @@ aW5nLg moveUp NSButton - - popover - NSPopover - - - popoverActivate - NSButton - - - removeButton + + removeMapping NSButton - - tableView - NSTableView - IBProjectSource - ./Classes/NJMappingsController.h + ./Classes/NJMappingsViewController.h -- 2.20.1 From a3d6f991d110dcccb70e137f43cbafc60f7ecee7 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Sun, 17 Mar 2013 18:31:52 +0100 Subject: [PATCH 16/16] Fix an off-by-one error. Make the popover work correctly when conflicts occur. --- Classes/NJMappingsController.m | 5 +++-- Info.plist | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Classes/NJMappingsController.m b/Classes/NJMappingsController.m index 531de12..4859970 100644 --- a/Classes/NJMappingsController.m +++ b/Classes/NJMappingsController.m @@ -147,6 +147,7 @@ NSDictionary *userInfo = CFBridgingRelease(contextInfo); NJMapping *oldMapping = userInfo[@"old mapping"]; NJMapping *newMapping = userInfo[@"new mapping"]; + [alert.window orderOut:nil]; switch (returnCode) { case NSAlertFirstButtonReturn: // Merge [oldMapping mergeEntriesFrom:newMapping]; @@ -157,7 +158,7 @@ case NSAlertThirdButtonReturn: // New Mapping [self.mvc.mappingList beginUpdates]; [_mappings addObject:newMapping]; - [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:NO]; + [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:YES]; [self.mvc.mappingList endUpdates]; [self activateMapping:newMapping]; [self mappingsChanged]; @@ -195,7 +196,7 @@ [self mappingsChanged]; } else { if (idx == -1) - idx = _mappings.count - 1; + idx = _mappings.count; [self.mvc.mappingList beginUpdates]; [_mappings insertObject:mapping atIndex:idx]; [self.mvc addedMappingAtIndex:idx startEditing:NO]; diff --git a/Info.plist b/Info.plist index 673fcbd..94c5825 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 444 + 448 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright -- 2.20.1