From: Joe Wreschnig Date: Fri, 8 Mar 2013 23:03:03 +0000 (+0100) Subject: Add status item. Disable automatic termination, but still hide from the dock when... X-Git-Tag: version-1.1~81 X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=commitdiff_plain;h=22efc1e5000d462091f3156ee5a8959be32cfb11 Add status item. Disable automatic termination, but still hide from the dock when closed by transforming the process type. Share mapping menu generation between the (now three) menus that need it. --- diff --git a/Classes/EnjoyableApplicationDelegate.h b/Classes/EnjoyableApplicationDelegate.h index dfb82fc..c4d8e3b 100644 --- a/Classes/EnjoyableApplicationDelegate.h +++ b/Classes/EnjoyableApplicationDelegate.h @@ -10,11 +10,15 @@ @class NJMappingsController; @interface EnjoyableApplicationDelegate : NSObject { - IBOutlet NSMenu *dockMenuBase; + IBOutlet NSMenu *dockMenu; + IBOutlet NSMenu *statusItemMenu; + IBOutlet NSMenu *mappingsMenu; IBOutlet NSWindow *window; } @property (nonatomic, strong) IBOutlet NJDeviceController *inputController; @property (nonatomic, strong) IBOutlet NJMappingsController *mappingsController; +- (IBAction)restoreToForeground:(id)sender; + @end diff --git a/Classes/EnjoyableApplicationDelegate.m b/Classes/EnjoyableApplicationDelegate.m index 78b930b..aaa9f00 100644 --- a/Classes/EnjoyableApplicationDelegate.m +++ b/Classes/EnjoyableApplicationDelegate.m @@ -13,7 +13,9 @@ #import "NJOutputController.h" #import "NJEvents.h" -@implementation EnjoyableApplicationDelegate +@implementation EnjoyableApplicationDelegate { + NSStatusItem *statusItem; +} - (void)didSwitchApplication:(NSNotification *)note { NSRunningApplication *activeApp = note.userInfo[NSWorkspaceApplicationKey]; @@ -21,7 +23,7 @@ [self.mappingsController activateMappingForProcess:activeApp]; } -- (void)applicationDidFinishLaunching:(NSNotification *)notification { +- (void)applicationWillFinishLaunching:(NSNotification *)notification { [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(mappingDidChange:) @@ -43,60 +45,131 @@ name:NJEventTranslationDeactivated object:nil]; - [self.inputController setup]; [self.mappingsController load]; + + statusItem = [NSStatusBar.systemStatusBar statusItemWithLength:36]; + statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"]; + statusItem.highlightMode = YES; + statusItem.menu = statusItemMenu; + statusItem.target = self; } -- (void)applicationDidBecomeActive:(NSNotification *)notification { +- (void)applicationDidFinishLaunching:(NSNotification *)notification { + [self.inputController setup]; [window makeKeyAndOrderFront:nil]; } - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag { - [window makeKeyAndOrderFront:nil]; + [self restoreToForeground:theApplication]; + return NO; +} + +- (void)restoreToForeground:(id)sender { + ProcessSerialNumber psn = { 0, kCurrentProcess }; + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + [NSApplication.sharedApplication activateIgnoringOtherApps:YES]; + [window makeKeyAndOrderFront:sender]; + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(transformIntoElement:) + object:self]; +} + +- (void)transformIntoElement:(id)sender { + ProcessSerialNumber psn = { 0, kCurrentProcess }; + TransformProcessType(&psn, kProcessTransformToUIElementApplication); +} + +- (void)flashStatusItem { + if ([statusItem.image.name isEqualToString:@"Status Menu Icon"]) { + statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"]; + } else { + statusItem.image = [NSImage imageNamed:@"Status Menu Icon"]; + } + +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { + [theApplication hide:theApplication]; + // If we turn into a UIElement right away, the application cancels + // the deactivation events. The dock icon disappears, but an + // unresponsive menu bar remains until the user clicks somewhere. + // So delay just long enough to be past the end handling that. + [self performSelector:@selector(transformIntoElement:) withObject:self afterDelay:0.001]; return NO; } - (void)eventTranslationActivated:(NSNotification *)note { - [NSProcessInfo.processInfo disableAutomaticTermination:@"Input translation is active."]; + [dockMenu itemAtIndex:0].state = NSOnState; + [statusItemMenu itemAtIndex:0].state = NSOnState; + statusItem.image = [NSImage imageNamed:@"Status Menu Icon"]; [NSWorkspace.sharedWorkspace.notificationCenter addObserver:self selector:@selector(didSwitchApplication:) name:NSWorkspaceDidActivateApplicationNotification object:nil]; - NSLog(@"Listening for application changes."); } - (void)eventTranslationDeactivated:(NSNotification *)note { - [NSProcessInfo.processInfo enableAutomaticTermination:@"Input translation is active."]; + [dockMenu itemAtIndex:0].state = NSOffState; + [statusItemMenu itemAtIndex:0].state = NSOffState; + statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"]; [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self name:NSWorkspaceDidActivateApplicationNotification object:nil]; - NSLog(@"Ignoring application changes."); } -- (void)mappingListDidChange:(NSNotification *)note { - NSArray *mappings = note.object; - while (dockMenuBase.lastItem.representedObject) - [dockMenuBase removeLastItem]; +- (void)restoreWindowAndShowMappings:(id)sender { + [self restoreToForeground:sender]; + [self.mappingsController mappingPressed:sender]; +} + +- (void)addMappingsToMenu:(NSMenu *)menu withKeys:(BOOL)withKeys atIndex:(NSInteger)index { + static const NSUInteger MAXIMUM_ITEMS = 5; int added = 0; - for (NJMapping *mapping in mappings) { - NSString *keyEquiv = ++added < 10 ? @(added).stringValue : @""; + for (NJMapping *mapping in self.mappingsController) { + NSString *keyEquiv = (++added < 10 && withKeys) ? @(added).stringValue : @""; NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:mapping.name action:@selector(chooseMapping:) keyEquivalent:keyEquiv]; item.representedObject = mapping; item.state = mapping == self.mappingsController.currentMapping; - [dockMenuBase addItem:item]; - } + [menu insertItem:item atIndex:index++]; + if (added == MAXIMUM_ITEMS && self.mappingsController.mappings.count > MAXIMUM_ITEMS + 1) { + NSMenuItem *end = [[NSMenuItem alloc] initWithTitle:@"…" + action:@selector(restoreWindowAndShowMappings:) + keyEquivalent:@""]; + end.target = self; + [menu insertItem:end atIndex:index++]; + break; + } + } +} + +- (void)mappingListDidChange:(NSNotification *)note { + while (mappingsMenu.lastItem.representedObject) + [mappingsMenu removeLastItem]; + [self addMappingsToMenu:mappingsMenu withKeys:YES atIndex:mappingsMenu.numberOfItems]; + while ([statusItemMenu itemAtIndex:2].representedObject) + [statusItemMenu removeItemAtIndex:2]; + [self addMappingsToMenu:statusItemMenu withKeys:NO atIndex:2]; } - (void)mappingDidChange:(NSNotification *)note { NJMapping *current = note.object; - for (NSMenuItem *item in dockMenuBase.itemArray) + for (NSMenuItem *item in mappingsMenu.itemArray) + if (item.representedObject) + item.state = item.representedObject == current; + for (NSMenuItem *item in statusItemMenu.itemArray) if (item.representedObject) item.state = item.representedObject == current; + + if (!window.isVisible) + for (int i = 0; i < 4; ++i) + [self performSelector:@selector(flashStatusItem) + withObject:self + afterDelay:0.2 * i]; } - (void)chooseMapping:(NSMenuItem *)sender { @@ -105,21 +178,14 @@ } - (NSMenu *)applicationDockMenu:(NSApplication *)sender { - NSMenu *menu = [[NSMenu alloc] init]; - int added = 0; - for (NJMapping *mapping in self.mappingsController) { - NSString *keyEquiv = ++added < 10 ? @(added).stringValue : @""; - NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:mapping.name - action:@selector(chooseMapping:) - keyEquivalent:keyEquiv]; - item.representedObject = mapping; - item.state = mapping == self.mappingsController.currentMapping; - [menu addItem:item]; - } - return menu; + while (dockMenu.lastItem.representedObject) + [dockMenu removeLastItem]; + [self addMappingsToMenu:dockMenu withKeys:NO atIndex:dockMenu.numberOfItems]; + return dockMenu; } - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { + [self restoreToForeground:sender]; NSURL *url = [NSURL fileURLWithPath:filename]; [self.mappingsController addMappingWithContentsOfURL:url]; return YES; diff --git a/Enjoyable.xcodeproj/project.pbxproj b/Enjoyable.xcodeproj/project.pbxproj index bf9c0d9..bf04acf 100644 --- a/Enjoyable.xcodeproj/project.pbxproj +++ b/Enjoyable.xcodeproj/project.pbxproj @@ -9,6 +9,10 @@ /* Begin PBXBuildFile section */ 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; D594BF000FAE7397007A85F2 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D594BEFF0FAE7397007A85F2 /* IOKit.framework */; }; + EE3D897A16EA7EFC00596D1F /* Status Menu Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897916EA7EFC00596D1F /* Status Menu Icon@2x.png */; }; + 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 */; }; EE6A122E16E8F46300EDBD32 /* Icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = EE6A122D16E8F46300EDBD32 /* Icon.icns */; }; EEE73B1616EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE73B1516EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m */; }; EEF17D1916E8E21A00D7DC4D /* com.yukkurigames.Enjoyable.mapping.icns in Resources */ = {isa = PBXBuildFile; fileRef = EEF17D1716E8E21A00D7DC4D /* com.yukkurigames.Enjoyable.mapping.icns */; }; @@ -48,6 +52,10 @@ 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8D1107320486CEB800E47090 /* Enjoyable.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Enjoyable.app; sourceTree = BUILT_PRODUCTS_DIR; }; D594BEFF0FAE7397007A85F2 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = ""; }; + EE3D897916EA7EFC00596D1F /* Status Menu Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon@2x.png"; path = "Resources/Status Menu Icon@2x.png"; sourceTree = ""; }; + 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 = ""; }; EE6A122D16E8F46300EDBD32 /* Icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Icon.icns; sourceTree = ""; }; EEE73B1416EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSRunningApplication+NJPossibleNames.h"; path = "Categories/NSRunningApplication+NJPossibleNames.h"; sourceTree = ""; }; EEE73B1516EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSRunningApplication+NJPossibleNames.m"; path = "Categories/NSRunningApplication+NJPossibleNames.m"; sourceTree = ""; }; @@ -215,6 +223,10 @@ 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( + EE3D897D16EA817E00596D1F /* Status Menu Icon Disabled.png */, + EE3D897E16EA817E00596D1F /* Status Menu Icon.png */, + EE3D897B16EA806E00596D1F /* Status Menu Icon Disabled@2x.png */, + EE3D897916EA7EFC00596D1F /* Status Menu Icon@2x.png */, EE6A122D16E8F46300EDBD32 /* Icon.icns */, EEF17D2116E8E24400D7DC4D /* Help */, EEF17D1B16E8E23A00D7DC4D /* InfoPlist.strings */, @@ -313,6 +325,10 @@ EEF17D2016E8E23A00D7DC4D /* MainMenu.xib in Resources */, EEF17D2216E8E24400D7DC4D /* Help in Resources */, EE6A122E16E8F46300EDBD32 /* Icon.icns in Resources */, + EE3D897A16EA7EFC00596D1F /* Status Menu Icon@2x.png in Resources */, + EE3D897C16EA806E00596D1F /* Status Menu Icon Disabled@2x.png in Resources */, + EE3D897F16EA817E00596D1F /* Status Menu Icon Disabled.png in Resources */, + EE3D898016EA817E00596D1F /* Status Menu Icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Resources/English.lproj/MainMenu.xib b/Resources/English.lproj/MainMenu.xib index 895e1d2..22b83f7 100644 --- a/Resources/English.lproj/MainMenu.xib +++ b/Resources/English.lproj/MainMenu.xib @@ -132,20 +132,20 @@ - + - Hide Others - h - 1572864 + Hide From Dock + w + 1048576 2147483647 - + - Show All - - 1048576 + Hide Others + h + 1572864 2147483647 @@ -287,15 +287,6 @@ - - - Bring All to Front - - 1048576 - 2147483647 - - - _NSWindowsMenu @@ -1110,7 +1101,7 @@ aW5nLg YES - + 256 @@ -1126,7 +1117,8 @@ aW5nLg 256 {198, 198} - + + YES NO YES @@ -1183,6 +1175,7 @@ aW5nLg {{1, 1}, {198, 198}} + @@ -1193,6 +1186,7 @@ aW5nLg -2147483392 {{306, 1}, {15, 403}} + NO @@ -1204,6 +1198,7 @@ aW5nLg -2147483392 {{-100, -100}, {366, 16}} + NO 1 @@ -1214,7 +1209,8 @@ aW5nLg {{0, 20}, {200, 200}} - + + 150034 @@ -1229,6 +1225,7 @@ aW5nLg 268 {{66, -1}, {68, 23}} + _NS:22 YES @@ -1257,6 +1254,7 @@ aW5nLg 292 {{0, -1}, {34, 23}} + YES @@ -1283,6 +1281,8 @@ aW5nLg 292 {{166, -1}, {34, 23}} + + YES 67108864 @@ -1304,6 +1304,7 @@ aW5nLg 292 {{133, -1}, {34, 23}} + YES @@ -1326,6 +1327,7 @@ aW5nLg 292 {{33, -1}, {34, 23}} + YES @@ -1349,6 +1351,8 @@ aW5nLg {200, 220} + + NSView @@ -1373,6 +1377,78 @@ aW5nLg 0.0 YES + + + + + + Enable + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Show Enjoyable + + 2147483647 + + + + + + Quit Enjoyable + + 2147483647 + + + + + + + + + + + Enable + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + @@ -1408,6 +1484,14 @@ aW5nLg 870 + + + terminate: + + + + 937 + orderFrontStandardAboutPanel: @@ -1424,14 +1508,6 @@ aW5nLg 37 - - - arrangeInFront: - - - - 39 - performZoom: @@ -1458,11 +1534,11 @@ aW5nLg - unhideAllApplications: - - + performClose: + + - 370 + 941 @@ -1536,14 +1612,6 @@ aW5nLg 915 - - - dockMenuBase - - - - 726 - inputController @@ -1568,6 +1636,38 @@ aW5nLg 865 + + + statusItemMenu + + + + 928 + + + + dockMenu + + + + 930 + + + + mappingsMenu + + + + 931 + + + + restoreToForeground: + + + + 939 + removePressed: @@ -1880,6 +1980,22 @@ aW5nLg 880 + + + performClick: + + + + 932 + + + + performClick: + + + + 933 + @@ -1962,13 +2078,13 @@ aW5nLg - + @@ -1982,16 +2098,11 @@ aW5nLg - - 150 - - - 136 - Quit Enjoy + Quit Enjoyable 144 @@ -2031,7 +2142,6 @@ aW5nLg - @@ -2042,11 +2152,6 @@ aW5nLg - - 5 - - - 239 @@ -2594,6 +2699,70 @@ aW5nLg + + 918 + + + + + + + + + + Status Item Menu + + + 919 + + + + + 922 + + + + + 923 + + + + + + + Dock Menu + + + 926 + + + + + 924 + + + + + 925 + + + + + 936 + + + Quit Enjoyable + + + 938 + + + + + 940 + + + @@ -2607,7 +2776,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 com.apple.InterfaceBuilder.CocoaPlugin @@ -2630,7 +2798,6 @@ aW5nLg com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin ToolTip @@ -2769,28 +2936,51 @@ 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 + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin - 917 + 941 EnjoyableApplicationDelegate NSObject + + restoreToForeground: + id + + + restoreToForeground: + + restoreToForeground: + id + + - NSMenu + NSMenu NJDeviceController NJMappingsController + NSMenu + NSMenu NSWindow - - dockMenuBase + + dockMenu NSMenu @@ -2801,6 +2991,14 @@ aW5nLg mappingsController NJMappingsController + + mappingsMenu + NSMenu + + + statusItemMenu + NSMenu + window NSWindow diff --git a/Resources/Status Menu Icon Disabled.png b/Resources/Status Menu Icon Disabled.png new file mode 100644 index 0000000..8523aae Binary files /dev/null and b/Resources/Status Menu Icon Disabled.png differ diff --git a/Resources/Status Menu Icon Disabled@2x.png b/Resources/Status Menu Icon Disabled@2x.png new file mode 100644 index 0000000..0e150b0 Binary files /dev/null and b/Resources/Status Menu Icon Disabled@2x.png differ diff --git a/Resources/Status Menu Icon.png b/Resources/Status Menu Icon.png new file mode 100644 index 0000000..2b26cf6 Binary files /dev/null and b/Resources/Status Menu Icon.png differ diff --git a/Resources/Status Menu Icon@2x.png b/Resources/Status Menu Icon@2x.png new file mode 100644 index 0000000..850151d Binary files /dev/null and b/Resources/Status Menu Icon@2x.png differ