9c7b641e848ab9e7eec9b7d611a4d89670272afe
[enjoyable.git] / Classes / NJMappingsController.m
1 //
2 // NJMappingsController.m
3 // Enjoy
4 //
5 // Created by Sam McCall on 4/05/09.
6 //
7
8 #import "NJMappingsController.h"
9
10 #import "NJMapping.h"
11 #import "NJMappingsController.h"
12 #import "NJOutput.h"
13 #import "NJEvents.h"
14
15 #define PB_ROW @"com.yukkurigames.Enjoyable.MappingRow"
16
17 @implementation NJMappingsController {
18 NSMutableArray *_mappings;
19 NJMapping *_manualMapping;
20 }
21
22 - (id)init {
23 if ((self = [super init])) {
24 _mappings = [[NSMutableArray alloc] init];
25 _currentMapping = [[NJMapping alloc] initWithName:
26 NSLocalizedString(@"(default)", @"default name for first the mapping")];
27 _manualMapping = _currentMapping;
28 [_mappings addObject:_currentMapping];
29 }
30 return self;
31 }
32
33 - (NJMapping *)objectForKeyedSubscript:(NSString *)name {
34 for (NJMapping *mapping in _mappings)
35 if ([name isEqualToString:mapping.name])
36 return mapping;
37 return nil;
38 }
39
40 - (NJMapping *)objectAtIndexedSubscript:(NSUInteger)idx {
41 return idx < _mappings.count ? _mappings[idx] : nil;
42 }
43
44 - (void)mappingsSet {
45 [self postLoadProcess];
46 [NSNotificationCenter.defaultCenter
47 postNotificationName:NJEventMappingListChanged
48 object:self
49 userInfo:@{ NJMappingListKey: _mappings,
50 NJMappingKey: _currentMapping }];
51 [self.mvc changedActiveMappingToIndex:[_mappings indexOfObjectIdenticalTo:_currentMapping]];
52 }
53
54 - (void)mappingsChanged {
55 [self save];
56 [self mappingsSet];
57 }
58
59 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
60 objects:(__unsafe_unretained id [])buffer
61 count:(NSUInteger)len {
62 return [_mappings countByEnumeratingWithState:state
63 objects:buffer
64 count:len];
65 }
66
67 - (void)activateMappingForProcess:(NSRunningApplication *)app {
68 NJMapping *oldMapping = _manualMapping;
69 NSArray *names = app.possibleMappingNames;
70 BOOL found = NO;
71 for (NSString *name in names) {
72 NJMapping *mapping = self[name];
73 if (mapping) {
74 [self activateMapping:mapping];
75 found = YES;
76 break;
77 }
78 }
79
80 if (!found) {
81 [self activateMapping:oldMapping];
82 if ([oldMapping.name.lowercaseString isEqualToString:@"@application"]
83 || [oldMapping.name.lowercaseString isEqualToString:
84 NSLocalizedString(@"@Application", nil).lowercaseString]) {
85 oldMapping.name = app.bestMappingName;
86 [self mappingsChanged];
87 }
88 }
89 _manualMapping = oldMapping;
90 }
91
92 - (void)activateMapping:(NJMapping *)mapping {
93 if (!mapping)
94 mapping = _manualMapping;
95 if (mapping == _currentMapping)
96 return;
97 NSLog(@"Switching to mapping %@.", mapping.name);
98 _manualMapping = mapping;
99 _currentMapping = mapping;
100 [self.mvc changedActiveMappingToIndex:[_mappings indexOfObjectIdenticalTo:_currentMapping]];
101 [NSNotificationCenter.defaultCenter
102 postNotificationName:NJEventMappingChanged
103 object:self
104 userInfo:@{ NJMappingKey : _currentMapping }];
105 }
106
107 - (void)save {
108 NSLog(@"Saving mappings to defaults.");
109 NSMutableArray *ary = [[NSMutableArray alloc] initWithCapacity:_mappings.count];
110 for (NJMapping *mapping in _mappings)
111 [ary addObject:[mapping serialize]];
112 [NSUserDefaults.standardUserDefaults setObject:ary forKey:@"mappings"];
113 }
114
115 - (void)postLoadProcess {
116 for (NJMapping *mapping in self)
117 [mapping postLoadProcess:self];
118 }
119
120 - (void)load {
121 NSUInteger selected = [NSUserDefaults.standardUserDefaults integerForKey:@"selected"];
122 NSArray *storedMappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"];
123 NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count];
124
125 for (unsigned i = 0; i < storedMappings.count; ++i) {
126 NJMapping *mapping = [[NJMapping alloc] initWithSerialization:storedMappings[i]];
127 [newMappings addObject:mapping];
128 }
129
130
131 if (newMappings.count) {
132 _mappings = newMappings;
133 if (selected >= newMappings.count)
134 selected = 0;
135 [self.mvc reloadData];
136 [self activateMapping:_mappings[selected]];
137 [self mappingsSet];
138 }
139 }
140
141 - (void)mappingConflictDidResolve:(NSAlert *)alert
142 returnCode:(NSInteger)returnCode
143 contextInfo:(void *)contextInfo {
144 NSDictionary *userInfo = CFBridgingRelease(contextInfo);
145 NJMapping *oldMapping = userInfo[@"old mapping"];
146 NJMapping *newMapping = userInfo[@"new mapping"];
147 [alert.window orderOut:nil];
148 switch (returnCode) {
149 case NSAlertFirstButtonReturn: // Merge
150 [oldMapping mergeEntriesFrom:newMapping];
151 _currentMapping = nil;
152 [self activateMapping:oldMapping];
153 [self mappingsChanged];
154 break;
155 case NSAlertThirdButtonReturn: // New Mapping
156 [self.mvc.mappingList beginUpdates];
157 [_mappings addObject:newMapping];
158 [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:YES];
159 [self.mvc.mappingList endUpdates];
160 [self activateMapping:newMapping];
161 [self mappingsChanged];
162 break;
163 default: // Cancel, other.
164 break;
165 }
166 }
167
168 - (void)addOrMergeMapping:(NJMapping *)mapping {
169 [self addOrMergeMapping:mapping atIndex:-1];
170 }
171
172 - (void)addOrMergeMapping:(NJMapping *)mapping atIndex:(NSInteger)idx {
173 NSWindow *window = NSApplication.sharedApplication.keyWindow;
174 if (mapping) {
175 NJMapping *mergeInto = self[mapping.name];
176 if ([mergeInto hasConflictWith:mapping]) {
177 NSAlert *conflictAlert = [[NSAlert alloc] init];
178 conflictAlert.messageText = NSLocalizedString(@"import conflict prompt", @"Title of import conflict alert");
179 conflictAlert.informativeText =
180 [NSString stringWithFormat:NSLocalizedString(@"import conflict in %@", @"Explanation of import conflict"),
181 mapping.name];
182 [conflictAlert addButtonWithTitle:NSLocalizedString(@"import and merge", @"button to merge imported mappings")];
183 [conflictAlert addButtonWithTitle:NSLocalizedString(@"cancel import", @"button to cancel import")];
184 [conflictAlert addButtonWithTitle:NSLocalizedString(@"import new mapping", @"button to import as new mapping")];
185 [conflictAlert beginSheetModalForWindow:window
186 modalDelegate:self
187 didEndSelector:@selector(mappingConflictDidResolve:returnCode:contextInfo:)
188 contextInfo:(void *)CFBridgingRetain(@{ @"old mapping": mergeInto,
189 @"new mapping": mapping })];
190 } else if (mergeInto) {
191 [mergeInto mergeEntriesFrom:mapping];
192 [self activateMapping:mergeInto];
193 [self mappingsChanged];
194 } else {
195 if (idx == -1)
196 idx = _mappings.count;
197 [self.mvc.mappingList beginUpdates];
198 [_mappings insertObject:mapping atIndex:idx];
199 [self.mvc addedMappingAtIndex:idx startEditing:NO];
200 [self.mvc.mappingList endUpdates];
201 [self activateMapping:mapping];
202 [self mappingsChanged];
203 }
204 }
205 }
206
207 - (NSInteger)numberOfMappings:(NJMappingsViewController *)dvc {
208 return _mappings.count;
209 }
210
211 - (NJMapping *)mappingsViewController:(NJMappingsViewController *)dvc
212 mappingForIndex:(NSUInteger)idx {
213 return _mappings[idx];
214 }
215
216 - (void)mappingsViewController:(NJMappingsViewController *)mvc
217 editedMappingAtIndex:(NSInteger)index {
218 [self mappingsChanged];
219 }
220
221 - (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
222 canMoveMappingFromIndex:(NSInteger)fromIdx
223 toIndex:(NSInteger)toIdx {
224 return fromIdx != toIdx && fromIdx != 0 && toIdx != 0 && toIdx < (NSInteger)_mappings.count;
225 }
226
227 - (void)mappingsViewController:(NJMappingsViewController *)mvc
228 moveMappingFromIndex:(NSInteger)fromIdx
229 toIndex:(NSInteger)toIdx {
230 [_mappings moveObjectAtIndex:fromIdx toIndex:toIdx];
231 [self mappingsChanged];
232 }
233
234 - (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
235 canRemoveMappingAtIndex:(NSInteger)idx {
236 return idx != 0;
237 }
238
239 - (void)mappingsViewController:(NJMappingsViewController *)mvc
240 removeMappingAtIndex:(NSInteger)idx {
241 NJMapping *old = self[idx];
242 [self.mvc.mappingList beginUpdates];
243 [_mappings removeObjectAtIndex:idx];
244 [self.mvc removedMappingAtIndex:idx];
245 [self.mvc.mappingList endUpdates];
246 if (old == _currentMapping)
247 [self activateMapping:self[MIN(idx, _mappings.count - 1)]];
248 [self mappingsChanged];
249 }
250
251 - (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
252 importMappingFromURL:(NSURL *)url
253 atIndex:(NSInteger)index
254 error:(NSError **)error {
255 NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url
256 error:error];
257 [self addOrMergeMapping:mapping atIndex:index];
258 return !!mapping;
259 }
260
261 - (void)mappingsViewController:(NJMappingsViewController *)mvc
262 addMapping:(NJMapping *)mapping {
263 [self.mvc.mappingList beginUpdates];
264 [_mappings addObject:mapping];
265 [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:YES];
266 [self.mvc.mappingList endUpdates];
267 [self activateMapping:mapping];
268 [self mappingsChanged];
269 }
270
271 - (void)mappingsViewController:(NJMappingsViewController *)mvc
272 choseMappingAtIndex:(NSInteger)idx {
273 [self activateMapping:self[idx]];
274 }
275
276 @end