X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=blobdiff_plain;f=Classes%2FEnjoyableApplicationDelegate.m;h=634f3621ab9ce5f4eaeb02d65d6534971dbb5f24;hp=5c6847645b8be5f28587c0977f6bd0f7f9865dbf;hb=1d9578185de7fb08cf1f4b1e42812e87d8e18040;hpb=18160be57e656a3733fc29878caddcda5081a2c2 diff --git a/Classes/EnjoyableApplicationDelegate.m b/Classes/EnjoyableApplicationDelegate.m index 5c68476..634f362 100644 --- a/Classes/EnjoyableApplicationDelegate.m +++ b/Classes/EnjoyableApplicationDelegate.m @@ -13,18 +13,17 @@ #import "NJOutputController.h" #import "NJEvents.h" -@implementation EnjoyableApplicationDelegate +@implementation EnjoyableApplicationDelegate { + NSStatusItem *statusItem; +} - (void)didSwitchApplication:(NSNotification *)note { NSRunningApplication *activeApp = note.userInfo[NSWorkspaceApplicationKey]; - NSString *name = activeApp.localizedName; - if (!name) - name = activeApp.bundleIdentifier; - if (name && ![name isEqualToString:NSRunningApplication.currentApplication.localizedName]) - [self.mappingsController activateMappingForProcess:name]; + if (activeApp) + [self.mappingsController activateMappingForProcess:activeApp]; } -- (void)applicationDidFinishLaunching:(NSNotification *)notification { +- (void)applicationWillFinishLaunching:(NSNotification *)notification { [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(mappingDidChange:) @@ -46,60 +45,135 @@ 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 { [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 = 15; 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) { + NSString *msg = [NSString stringWithFormat:@"(and %lu more…)", + self.mappingsController.mappings.count - MAXIMUM_ITEMS]; + NSMenuItem *end = [[NSMenuItem alloc] initWithTitle:msg + action:@selector(restoreWindowAndShowMappings:) + keyEquivalent:@""]; + // There must be a represented object here so the item gets + // removed correctly when the menus are regenerated. + end.representedObject = self.mappingsController.mappings; + 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 { @@ -107,50 +181,15 @@ [self.mappingsController activateMapping:chosen]; } -#define OUTPUT_PANE_MIN_WIDTH 390 -#define INPUT_PANE_MIN_WIDTH 160 - -- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex { - return INPUT_PANE_MIN_WIDTH; -} - -- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset { - return proposedMax - OUTPUT_PANE_MIN_WIDTH; -} - -- (void)splitView:(NSSplitView *)splitView resizeSubviewsWithOldSize:(NSSize)oldSize { - NSView *inputView = splitView.subviews[0]; - NSView *outputView = splitView.subviews[1]; - if (outputView.frame.size.width < OUTPUT_PANE_MIN_WIDTH) { - NSSize frameSize = splitView.frame.size; - CGFloat inputWidth = frameSize.width - OUTPUT_PANE_MIN_WIDTH - splitView.dividerThickness; - inputView.frame = NSMakeRect(inputWidth, frameSize.height, - inputView.frame.size.width, - inputView.frame.size.height); - outputView.frame = NSMakeRect(inputWidth + splitView.dividerThickness, - 0, - OUTPUT_PANE_MIN_WIDTH, - frameSize.height); - } else - [splitView adjustSubviews]; -} - - (NSMenu *)applicationDockMenu:(NSApplication *)sender { - NSMenu *menu = [[NSMenu alloc] init]; - int added = 0; - for (NJMapping *mapping in self.mappingsController) { - NSString *keyEquiv = ++added < 10 ? @(added).stringValue : @""; - NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:mapping.name - action:@selector(chooseMapping:) - keyEquivalent:keyEquiv]; - item.representedObject = mapping; - item.state = mapping == self.mappingsController.currentMapping; - [menu addItem:item]; - } - return menu; + 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;