c66cf6b9aecb6d47a465a0c528599c7cf9399887
[enjoyable.git] / Classes / EnjoyableApplicationDelegate.m
1 //
2 // EnjoyableApplicationDelegate.m
3 // Enjoy
4 //
5 // Created by Sam McCall on 4/05/09.
6 //
7
8 #import "EnjoyableApplicationDelegate.h"
9
10 #import "NJMapping.h"
11 #import "NJMappingsController.h"
12 #import "NJDeviceController.h"
13 #import "NJOutputController.h"
14 #import "NJEvents.h"
15
16 @implementation EnjoyableApplicationDelegate {
17 NSStatusItem *statusItem;
18 }
19
20 - (void)didSwitchApplication:(NSNotification *)note {
21 NSRunningApplication *activeApp = note.userInfo[NSWorkspaceApplicationKey];
22 if (activeApp)
23 [self.mappingsController activateMappingForProcess:activeApp];
24 }
25
26 - (void)applicationWillFinishLaunching:(NSNotification *)notification {
27 [NSNotificationCenter.defaultCenter
28 addObserver:self
29 selector:@selector(mappingDidChange:)
30 name:NJEventMappingChanged
31 object:nil];
32 [NSNotificationCenter.defaultCenter
33 addObserver:self
34 selector:@selector(mappingListDidChange:)
35 name:NJEventMappingListChanged
36 object:nil];
37 [NSNotificationCenter.defaultCenter
38 addObserver:self
39 selector:@selector(eventTranslationActivated:)
40 name:NJEventTranslationActivated
41 object:nil];
42 [NSNotificationCenter.defaultCenter
43 addObserver:self
44 selector:@selector(eventTranslationDeactivated:)
45 name:NJEventTranslationDeactivated
46 object:nil];
47
48 [self.mappingsController load];
49
50 statusItem = [NSStatusBar.systemStatusBar statusItemWithLength:36];
51 statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
52 statusItem.highlightMode = YES;
53 statusItem.menu = statusItemMenu;
54 statusItem.target = self;
55 }
56
57 - (void)applicationDidFinishLaunching:(NSNotification *)notification {
58 [window makeKeyAndOrderFront:nil];
59 }
60
61 - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication
62 hasVisibleWindows:(BOOL)flag {
63 [self restoreToForeground:theApplication];
64 return NO;
65 }
66
67 - (void)restoreToForeground:(id)sender {
68 ProcessSerialNumber psn = { 0, kCurrentProcess };
69 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
70 [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
71 [window makeKeyAndOrderFront:sender];
72 [NSObject cancelPreviousPerformRequestsWithTarget:self
73 selector:@selector(transformIntoElement:)
74 object:self];
75 }
76
77 - (void)transformIntoElement:(id)sender {
78 ProcessSerialNumber psn = { 0, kCurrentProcess };
79 TransformProcessType(&psn, kProcessTransformToUIElementApplication);
80 }
81
82 - (void)flashStatusItem {
83 if ([statusItem.image.name isEqualToString:@"Status Menu Icon"]) {
84 statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
85 } else {
86 statusItem.image = [NSImage imageNamed:@"Status Menu Icon"];
87 }
88
89 }
90
91 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
92 [theApplication hide:theApplication];
93 // If we turn into a UIElement right away, the application cancels
94 // the deactivation events. The dock icon disappears, but an
95 // unresponsive menu bar remains until the user clicks somewhere.
96 // So delay just long enough to be past the end handling that.
97 [self performSelector:@selector(transformIntoElement:) withObject:self afterDelay:0.001];
98 return NO;
99 }
100
101 - (void)eventTranslationActivated:(NSNotification *)note {
102 [dockMenu itemAtIndex:0].state = NSOnState;
103 [statusItemMenu itemAtIndex:0].state = NSOnState;
104 statusItem.image = [NSImage imageNamed:@"Status Menu Icon"];
105 [NSWorkspace.sharedWorkspace.notificationCenter
106 addObserver:self
107 selector:@selector(didSwitchApplication:)
108 name:NSWorkspaceDidActivateApplicationNotification
109 object:nil];
110 }
111
112 - (void)eventTranslationDeactivated:(NSNotification *)note {
113 [dockMenu itemAtIndex:0].state = NSOffState;
114 [statusItemMenu itemAtIndex:0].state = NSOffState;
115 statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
116 [NSWorkspace.sharedWorkspace.notificationCenter
117 removeObserver:self
118 name:NSWorkspaceDidActivateApplicationNotification
119 object:nil];
120 }
121
122 - (void)restoreWindowAndShowMappings:(id)sender {
123 [self restoreToForeground:sender];
124 [self.mappingsController mappingPressed:sender];
125 }
126
127 - (void)addMappings:(NSArray *)mappings
128 toMenu:(NSMenu *)menu
129 withKeys:(BOOL)withKeys
130 atIndex:(NSInteger)index {
131 static const NSUInteger MAXIMUM_ITEMS = 15;
132 int added = 0;
133 for (NJMapping *mapping in mappings) {
134 NSString *keyEquiv = (++added < 10 && withKeys) ? @(added).stringValue : @"";
135 NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:mapping.name
136 action:@selector(chooseMapping:)
137 keyEquivalent:keyEquiv];
138 item.representedObject = mapping;
139 item.state = mapping == self.mappingsController.currentMapping;
140 [menu insertItem:item atIndex:index++];
141 if (added == MAXIMUM_ITEMS && self.mappingsController.mappings.count > MAXIMUM_ITEMS + 1) {
142 NSString *msg = [NSString stringWithFormat:@"(and %lu moreā€¦)",
143 self.mappingsController.mappings.count - MAXIMUM_ITEMS];
144 NSMenuItem *end = [[NSMenuItem alloc] initWithTitle:msg
145 action:@selector(restoreWindowAndShowMappings:)
146 keyEquivalent:@""];
147 // There must be a represented object here so the item gets
148 // removed correctly when the menus are regenerated.
149 end.representedObject = mappings;
150 end.target = self;
151 [menu insertItem:end atIndex:index++];
152 break;
153 }
154 }
155 }
156
157 - (void)mappingListDidChange:(NSNotification *)note {
158 NSArray *mappings = note.userInfo[@"mappings"];
159 while (mappingsMenu.lastItem.representedObject)
160 [mappingsMenu removeLastItem];
161 [self addMappings:mappings
162 toMenu:mappingsMenu
163 withKeys:YES
164 atIndex:mappingsMenu.numberOfItems];
165 while ([statusItemMenu itemAtIndex:2].representedObject)
166 [statusItemMenu removeItemAtIndex:2];
167 [self addMappings:mappings toMenu:statusItemMenu withKeys:NO atIndex:2];
168 }
169
170 - (void)mappingDidChange:(NSNotification *)note {
171 NJMapping *current = note.userInfo[@"mapping"];
172 for (NSMenuItem *item in mappingsMenu.itemArray)
173 if (item.representedObject)
174 item.state = item.representedObject == current;
175 for (NSMenuItem *item in statusItemMenu.itemArray)
176 if (item.representedObject)
177 item.state = item.representedObject == current;
178
179 if (!window.isVisible)
180 for (int i = 0; i < 4; ++i)
181 [self performSelector:@selector(flashStatusItem)
182 withObject:self
183 afterDelay:0.2 * i];
184 }
185
186 - (void)chooseMapping:(NSMenuItem *)sender {
187 NJMapping *chosen = sender.representedObject;
188 [self.mappingsController activateMapping:chosen];
189 }
190
191 - (NSMenu *)applicationDockMenu:(NSApplication *)sender {
192 while (dockMenu.lastItem.representedObject)
193 [dockMenu removeLastItem];
194 [self addMappings:self.mappingsController.mappings
195 toMenu:dockMenu
196 withKeys:NO
197 atIndex:dockMenu.numberOfItems];
198 return dockMenu;
199 }
200
201 - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
202 [self restoreToForeground:sender];
203 NSURL *url = [NSURL fileURLWithPath:filename];
204 [self.mappingsController addMappingWithContentsOfURL:url];
205 return YES;
206 }
207
208
209 @end