Mouse improvements. Segment and snap the mouse move and scroll speed for easier match...
[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 [self.inputController setup];
59 [window makeKeyAndOrderFront:nil];
60 }
61
62 - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication
63 hasVisibleWindows:(BOOL)flag {
64 [self restoreToForeground:theApplication];
65 return NO;
66 }
67
68 - (void)restoreToForeground:(id)sender {
69 ProcessSerialNumber psn = { 0, kCurrentProcess };
70 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
71 [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
72 [window makeKeyAndOrderFront:sender];
73 [NSObject cancelPreviousPerformRequestsWithTarget:self
74 selector:@selector(transformIntoElement:)
75 object:self];
76 }
77
78 - (void)transformIntoElement:(id)sender {
79 ProcessSerialNumber psn = { 0, kCurrentProcess };
80 TransformProcessType(&psn, kProcessTransformToUIElementApplication);
81 }
82
83 - (void)flashStatusItem {
84 if ([statusItem.image.name isEqualToString:@"Status Menu Icon"]) {
85 statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
86 } else {
87 statusItem.image = [NSImage imageNamed:@"Status Menu Icon"];
88 }
89
90 }
91
92 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
93 [theApplication hide:theApplication];
94 // If we turn into a UIElement right away, the application cancels
95 // the deactivation events. The dock icon disappears, but an
96 // unresponsive menu bar remains until the user clicks somewhere.
97 // So delay just long enough to be past the end handling that.
98 [self performSelector:@selector(transformIntoElement:) withObject:self afterDelay:0.001];
99 return NO;
100 }
101
102 - (void)eventTranslationActivated:(NSNotification *)note {
103 [dockMenu itemAtIndex:0].state = NSOnState;
104 [statusItemMenu itemAtIndex:0].state = NSOnState;
105 statusItem.image = [NSImage imageNamed:@"Status Menu Icon"];
106 [NSWorkspace.sharedWorkspace.notificationCenter
107 addObserver:self
108 selector:@selector(didSwitchApplication:)
109 name:NSWorkspaceDidActivateApplicationNotification
110 object:nil];
111 }
112
113 - (void)eventTranslationDeactivated:(NSNotification *)note {
114 [dockMenu itemAtIndex:0].state = NSOffState;
115 [statusItemMenu itemAtIndex:0].state = NSOffState;
116 statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
117 [NSWorkspace.sharedWorkspace.notificationCenter
118 removeObserver:self
119 name:NSWorkspaceDidActivateApplicationNotification
120 object:nil];
121 }
122
123 - (void)restoreWindowAndShowMappings:(id)sender {
124 [self restoreToForeground:sender];
125 [self.mappingsController mappingPressed:sender];
126 }
127
128 - (void)addMappingsToMenu:(NSMenu *)menu withKeys:(BOOL)withKeys atIndex:(NSInteger)index {
129 static const NSUInteger MAXIMUM_ITEMS = 15;
130 int added = 0;
131 for (NJMapping *mapping in self.mappingsController) {
132 NSString *keyEquiv = (++added < 10 && withKeys) ? @(added).stringValue : @"";
133 NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:mapping.name
134 action:@selector(chooseMapping:)
135 keyEquivalent:keyEquiv];
136 item.representedObject = mapping;
137 item.state = mapping == self.mappingsController.currentMapping;
138 [menu insertItem:item atIndex:index++];
139 if (added == MAXIMUM_ITEMS && self.mappingsController.mappings.count > MAXIMUM_ITEMS + 1) {
140 NSString *msg = [NSString stringWithFormat:@"(and %lu moreā€¦)",
141 self.mappingsController.mappings.count - MAXIMUM_ITEMS];
142 NSMenuItem *end = [[NSMenuItem alloc] initWithTitle:msg
143 action:@selector(restoreWindowAndShowMappings:)
144 keyEquivalent:@""];
145 // There must be a represented object here so the item gets
146 // removed correctly when the menus are regenerated.
147 end.representedObject = self.mappingsController.mappings;
148 end.target = self;
149 [menu insertItem:end atIndex:index++];
150 break;
151 }
152 }
153 }
154
155 - (void)mappingListDidChange:(NSNotification *)note {
156 while (mappingsMenu.lastItem.representedObject)
157 [mappingsMenu removeLastItem];
158 [self addMappingsToMenu:mappingsMenu withKeys:YES atIndex:mappingsMenu.numberOfItems];
159 while ([statusItemMenu itemAtIndex:2].representedObject)
160 [statusItemMenu removeItemAtIndex:2];
161 [self addMappingsToMenu:statusItemMenu withKeys:NO atIndex:2];
162 }
163
164 - (void)mappingDidChange:(NSNotification *)note {
165 NJMapping *current = note.object;
166 for (NSMenuItem *item in mappingsMenu.itemArray)
167 if (item.representedObject)
168 item.state = item.representedObject == current;
169 for (NSMenuItem *item in statusItemMenu.itemArray)
170 if (item.representedObject)
171 item.state = item.representedObject == current;
172
173 if (!window.isVisible)
174 for (int i = 0; i < 4; ++i)
175 [self performSelector:@selector(flashStatusItem)
176 withObject:self
177 afterDelay:0.2 * i];
178 }
179
180 - (void)chooseMapping:(NSMenuItem *)sender {
181 NJMapping *chosen = sender.representedObject;
182 [self.mappingsController activateMapping:chosen];
183 }
184
185 - (NSMenu *)applicationDockMenu:(NSApplication *)sender {
186 while (dockMenu.lastItem.representedObject)
187 [dockMenu removeLastItem];
188 [self addMappingsToMenu:dockMenu withKeys:NO atIndex:dockMenu.numberOfItems];
189 return dockMenu;
190 }
191
192 - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
193 [self restoreToForeground:sender];
194 NSURL *url = [NSURL fileURLWithPath:filename];
195 [self.mappingsController addMappingWithContentsOfURL:url];
196 return YES;
197 }
198
199
200 @end