2 // NJMappingsController.m
5 // Created by Sam McCall on 4/05/09.
8 #import "NJMappingsController.h"
11 #import "NJMappingsController.h"
15 #define PB_ROW @"com.yukkurigames.Enjoyable.MappingRow"
17 @implementation NJMappingsController {
18 NSMutableArray *_mappings;
19 NJMapping *_manualMapping;
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];
33 - (NJMapping *)objectForKeyedSubscript:(NSString *)name {
34 for (NJMapping *mapping in _mappings)
35 if ([name isEqualToString:mapping.name])
40 - (NJMapping *)objectAtIndexedSubscript:(NSUInteger)idx {
41 return idx < _mappings.count ? _mappings[idx] : nil;
45 [NSNotificationCenter.defaultCenter
46 postNotificationName:NJEventMappingListChanged
48 userInfo:@{ NJMappingListKey: _mappings,
49 NJMappingKey: _currentMapping }];
50 [self.mvc changedActiveMappingToIndex:[_mappings indexOfObjectIdenticalTo:_currentMapping]];
53 - (void)mappingsChanged {
58 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
59 objects:(__unsafe_unretained id [])buffer
60 count:(NSUInteger)len {
61 return [_mappings countByEnumeratingWithState:state
66 - (void)activateMappingForProcess:(NSRunningApplication *)app {
67 NJMapping *oldMapping = _manualMapping;
68 NSArray *names = app.possibleMappingNames;
70 for (NSString *name in names) {
71 NJMapping *mapping = self[name];
73 [self activateMapping:mapping];
80 [self activateMapping:oldMapping];
81 if ([oldMapping.name.lowercaseString isEqualToString:@"@application"]
82 || [oldMapping.name.lowercaseString isEqualToString:
83 NSLocalizedString(@"@Application", nil).lowercaseString]) {
84 oldMapping.name = app.bestMappingName;
85 [self mappingsChanged];
88 _manualMapping = oldMapping;
91 - (void)activateMapping:(NJMapping *)mapping {
93 mapping = _manualMapping;
94 if (mapping == _currentMapping)
96 NSLog(@"Switching to mapping %@.", mapping.name);
97 _manualMapping = mapping;
98 _currentMapping = mapping;
99 [self.mvc changedActiveMappingToIndex:[_mappings indexOfObjectIdenticalTo:_currentMapping]];
100 [NSNotificationCenter.defaultCenter
101 postNotificationName:NJEventMappingChanged
103 userInfo:@{ NJMappingKey : _currentMapping }];
107 NSLog(@"Saving mappings to defaults.");
108 NSMutableArray *ary = [[NSMutableArray alloc] initWithCapacity:_mappings.count];
109 for (NJMapping *mapping in _mappings)
110 [ary addObject:[mapping serialize]];
111 [NSUserDefaults.standardUserDefaults setObject:ary forKey:@"mappings"];
115 NSUInteger selected = [NSUserDefaults.standardUserDefaults integerForKey:@"selected"];
116 NSArray *storedMappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"];
117 NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count];
119 // Requires two passes to deal with inter-mapping references. First make
120 // an empty mapping for each serialized mapping. Then, deserialize the
121 // data pointing to the empty mappings. Then merge that data back into
122 // its equivalent empty one, which is the one we finally use.
123 for (NSDictionary *storedMapping in storedMappings) {
124 NJMapping *mapping = [[NJMapping alloc] initWithName:storedMapping[@"name"]];
125 [newMappings addObject:mapping];
128 for (unsigned i = 0; i < storedMappings.count; ++i) {
129 NJMapping *realMapping = [[NJMapping alloc] initWithSerialization:storedMappings[i]
130 mappings:newMappings];
131 [newMappings[i] mergeEntriesFrom:realMapping];
134 if (newMappings.count) {
135 _mappings = newMappings;
136 if (selected >= newMappings.count)
138 [self.mvc reloadData];
139 [self activateMapping:_mappings[selected]];
144 - (void)mappingConflictDidResolve:(NSAlert *)alert
145 returnCode:(NSInteger)returnCode
146 contextInfo:(void *)contextInfo {
147 NSDictionary *userInfo = CFBridgingRelease(contextInfo);
148 NJMapping *oldMapping = userInfo[@"old mapping"];
149 NJMapping *newMapping = userInfo[@"new mapping"];
150 [alert.window orderOut:nil];
151 switch (returnCode) {
152 case NSAlertFirstButtonReturn: // Merge
153 [oldMapping mergeEntriesFrom:newMapping];
154 _currentMapping = nil;
155 [self activateMapping:oldMapping];
156 [self mappingsChanged];
158 case NSAlertThirdButtonReturn: // New Mapping
159 [self.mvc.mappingList beginUpdates];
160 [_mappings addObject:newMapping];
161 [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:YES];
162 [self.mvc.mappingList endUpdates];
163 [self activateMapping:newMapping];
164 [self mappingsChanged];
166 default: // Cancel, other.
171 - (void)addOrMergeMapping:(NJMapping *)mapping {
172 [self addOrMergeMapping:mapping atIndex:-1];
175 - (void)addOrMergeMapping:(NJMapping *)mapping atIndex:(NSInteger)idx {
176 NSWindow *window = NSApplication.sharedApplication.keyWindow;
178 NJMapping *mergeInto = self[mapping.name];
179 if ([mergeInto hasConflictWith:mapping]) {
180 NSAlert *conflictAlert = [[NSAlert alloc] init];
181 conflictAlert.messageText = NSLocalizedString(@"import conflict prompt", @"Title of import conflict alert");
182 conflictAlert.informativeText =
183 [NSString stringWithFormat:NSLocalizedString(@"import conflict in %@", @"Explanation of import conflict"),
185 [conflictAlert addButtonWithTitle:NSLocalizedString(@"import and merge", @"button to merge imported mappings")];
186 [conflictAlert addButtonWithTitle:NSLocalizedString(@"cancel import", @"button to cancel import")];
187 [conflictAlert addButtonWithTitle:NSLocalizedString(@"import new mapping", @"button to import as new mapping")];
188 [conflictAlert beginSheetModalForWindow:window
190 didEndSelector:@selector(mappingConflictDidResolve:returnCode:contextInfo:)
191 contextInfo:(void *)CFBridgingRetain(@{ @"old mapping": mergeInto,
192 @"new mapping": mapping })];
193 } else if (mergeInto) {
194 [mergeInto mergeEntriesFrom:mapping];
195 [self activateMapping:mergeInto];
196 [self mappingsChanged];
199 idx = _mappings.count;
200 [self.mvc.mappingList beginUpdates];
201 [_mappings insertObject:mapping atIndex:idx];
202 [self.mvc addedMappingAtIndex:idx startEditing:NO];
203 [self.mvc.mappingList endUpdates];
204 [self activateMapping:mapping];
205 [self mappingsChanged];
210 - (NSInteger)numberOfMappings:(NJMappingsViewController *)dvc {
211 return _mappings.count;
214 - (NJMapping *)mappingsViewController:(NJMappingsViewController *)dvc
215 mappingForIndex:(NSUInteger)idx {
216 return _mappings[idx];
219 - (void)mappingsViewController:(NJMappingsViewController *)mvc
220 editedMappingAtIndex:(NSInteger)index {
221 [self mappingsChanged];
224 - (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
225 canMoveMappingFromIndex:(NSInteger)fromIdx
226 toIndex:(NSInteger)toIdx {
227 return fromIdx != toIdx && fromIdx != 0 && toIdx != 0 && toIdx < (NSInteger)_mappings.count;
230 - (void)mappingsViewController:(NJMappingsViewController *)mvc
231 moveMappingFromIndex:(NSInteger)fromIdx
232 toIndex:(NSInteger)toIdx {
233 [_mappings moveObjectAtIndex:fromIdx toIndex:toIdx];
234 [self mappingsChanged];
237 - (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
238 canRemoveMappingAtIndex:(NSInteger)idx {
242 - (void)mappingsViewController:(NJMappingsViewController *)mvc
243 removeMappingAtIndex:(NSInteger)idx {
244 NJMapping *old = self[idx];
245 [self.mvc.mappingList beginUpdates];
246 [_mappings removeObjectAtIndex:idx];
247 [self.mvc removedMappingAtIndex:idx];
248 [self.mvc.mappingList endUpdates];
249 if (old == _currentMapping)
250 [self activateMapping:self[MIN(idx, _mappings.count - 1)]];
251 [self mappingsChanged];
254 - (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
255 importMappingFromURL:(NSURL *)url
256 atIndex:(NSInteger)index
257 error:(NSError **)error {
258 NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url
261 [self addOrMergeMapping:mapping atIndex:index];
265 - (void)mappingsViewController:(NJMappingsViewController *)mvc
266 addMapping:(NJMapping *)mapping {
267 [self.mvc.mappingList beginUpdates];
268 [_mappings addObject:mapping];
269 [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:YES];
270 [self.mvc.mappingList endUpdates];
271 [self activateMapping:mapping];
272 [self mappingsChanged];
275 - (void)mappingsViewController:(NJMappingsViewController *)mvc
276 choseMappingAtIndex:(NSInteger)idx {
277 [self activateMapping:self[idx]];