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 switch (returnCode) {
151 case NSAlertFirstButtonReturn: // Merge
152 [oldMapping mergeEntriesFrom:newMapping];
153 _currentMapping = nil;
154 [self activateMapping:oldMapping];
155 [self mappingsChanged];
157 case NSAlertThirdButtonReturn: // New Mapping
158 [self.mvc.mappingList beginUpdates];
159 [_mappings addObject:newMapping];
160 [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:NO];
161 [self.mvc.mappingList endUpdates];
162 [self activateMapping:newMapping];
163 [self mappingsChanged];
165 default: // Cancel, other.
170 - (void)addOrMergeMapping:(NJMapping *)mapping {
171 [self addOrMergeMapping:mapping atIndex:-1];
174 - (void)addOrMergeMapping:(NJMapping *)mapping atIndex:(NSInteger)idx {
175 NSWindow *window = NSApplication.sharedApplication.keyWindow;
177 NJMapping *mergeInto = self[mapping.name];
178 if ([mergeInto hasConflictWith:mapping]) {
179 NSAlert *conflictAlert = [[NSAlert alloc] init];
180 conflictAlert.messageText = NSLocalizedString(@"import conflict prompt", @"Title of import conflict alert");
181 conflictAlert.informativeText =
182 [NSString stringWithFormat:NSLocalizedString(@"import conflict in %@", @"Explanation of import conflict"),
184 [conflictAlert addButtonWithTitle:NSLocalizedString(@"import and merge", @"button to merge imported mappings")];
185 [conflictAlert addButtonWithTitle:NSLocalizedString(@"cancel import", @"button to cancel import")];
186 [conflictAlert addButtonWithTitle:NSLocalizedString(@"import new mapping", @"button to import as new mapping")];
187 [conflictAlert beginSheetModalForWindow:window
189 didEndSelector:@selector(mappingConflictDidResolve:returnCode:contextInfo:)
190 contextInfo:(void *)CFBridgingRetain(@{ @"old mapping": mergeInto,
191 @"new mapping": mapping })];
192 } else if (mergeInto) {
193 [mergeInto mergeEntriesFrom:mapping];
194 [self activateMapping:mergeInto];
195 [self mappingsChanged];
198 idx = _mappings.count - 1;
199 [self.mvc.mappingList beginUpdates];
200 [_mappings insertObject:mapping atIndex:idx];
201 [self.mvc addedMappingAtIndex:idx startEditing:NO];
202 [self.mvc.mappingList endUpdates];
203 [self activateMapping:mapping];
204 [self mappingsChanged];
209 - (NSInteger)numberOfMappings:(NJMappingsViewController *)dvc {
210 return _mappings.count;
213 - (NJMapping *)mappingsViewController:(NJMappingsViewController *)dvc
214 mappingForIndex:(NSUInteger)idx {
215 return _mappings[idx];
218 - (void)mappingsViewController:(NJMappingsViewController *)mvc
219 editedMappingAtIndex:(NSInteger)index {
220 [self mappingsChanged];
223 - (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
224 canMoveMappingFromIndex:(NSInteger)fromIdx
225 toIndex:(NSInteger)toIdx {
226 return fromIdx != toIdx && fromIdx != 0 && toIdx != 0 && toIdx < (NSInteger)_mappings.count;
229 - (void)mappingsViewController:(NJMappingsViewController *)mvc
230 moveMappingFromIndex:(NSInteger)fromIdx
231 toIndex:(NSInteger)toIdx {
232 [_mappings moveObjectAtIndex:fromIdx toIndex:toIdx];
233 [self mappingsChanged];
236 - (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
237 canRemoveMappingAtIndex:(NSInteger)idx {
241 - (void)mappingsViewController:(NJMappingsViewController *)mvc
242 removeMappingAtIndex:(NSInteger)idx {
243 NJMapping *old = self[idx];
244 [self.mvc.mappingList beginUpdates];
245 [_mappings removeObjectAtIndex:idx];
246 [self.mvc removedMappingAtIndex:idx];
247 [self.mvc.mappingList endUpdates];
248 if (old == _currentMapping)
249 [self activateMapping:self[MIN(idx, _mappings.count - 1)]];
250 [self mappingsChanged];
253 - (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
254 importMappingFromURL:(NSURL *)url
255 atIndex:(NSInteger)index
256 error:(NSError **)error {
257 NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url
260 [self addOrMergeMapping:mapping atIndex:index];
264 - (void)mappingsViewController:(NJMappingsViewController *)mvc
265 addMapping:(NJMapping *)mapping {
266 [self.mvc.mappingList beginUpdates];
267 [_mappings addObject:mapping];
268 [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:YES];
269 [self.mvc.mappingList endUpdates];
270 [self activateMapping:mapping];
271 [self mappingsChanged];
274 - (void)mappingsViewController:(NJMappingsViewController *)mvc
275 choseMappingAtIndex:(NSInteger)idx {
276 [self activateMapping:self[idx]];