- (id)init {
if ((self = [super init])) {
_mappings = [[NSMutableArray alloc] init];
- _currentMapping = [[NJMapping alloc] initWithName:@"(default)"];
+ _currentMapping = [[NJMapping alloc] initWithName:
+ NSLocalizedString(@"(default)", @"default name for first the mapping")];
_manualMapping = _currentMapping;
[_mappings addObject:_currentMapping];
}
return idx < _mappings.count ? _mappings[idx] : nil;
}
-- (void)mappingsChanged {
- [self save];
+- (void)mappingsSet {
[tableView reloadData];
[self updateInterfaceForCurrentMapping];
[NSNotificationCenter.defaultCenter
postNotificationName:NJEventMappingListChanged
- object:_mappings];
+ object:self
+ userInfo:@{ NJMappingListKey: _mappings,
+ NJMappingKey: _currentMapping }];
+}
+
+- (void)mappingsChanged {
+ [self save];
+ [self mappingsSet];
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
if (!found) {
[self activateMapping:oldMapping];
- if ([oldMapping.name.lowercaseString isEqualToString:@"@application"]) {
+ if ([oldMapping.name.lowercaseString isEqualToString:@"@application"]
+ || [oldMapping.name.lowercaseString isEqualToString:
+ NSLocalizedString(@"@Application", nil).lowercaseString]) {
oldMapping.name = app.bestMappingName;
[self mappingsChanged];
}
_manualMapping = mapping;
_currentMapping = mapping;
[self updateInterfaceForCurrentMapping];
- [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingChanged
- object:_currentMapping];
+ [NSNotificationCenter.defaultCenter
+ postNotificationName:NJEventMappingChanged
+ object:self
+ userInfo:@{ NJMappingKey : _currentMapping }];
}
- (IBAction)addPressed:(id)sender {
- NJMapping *newMapping = [[NJMapping alloc] initWithName:@"Untitled"];
+ [self mappingPressed:sender];
+ NJMapping *newMapping = [[NJMapping alloc] init];
[_mappings addObject:newMapping];
[self activateMapping:newMapping];
[self mappingsChanged];
- (void)load {
NSUInteger selected = [NSUserDefaults.standardUserDefaults integerForKey:@"selected"];
- NSArray *mappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"];
- [self loadAllFrom:mappings andActivate:selected];
-}
-
-- (void)loadAllFrom:(NSArray *)storedMappings andActivate:(NSUInteger)selected {
+ NSArray *storedMappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"];
NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count];
- // have to do two passes in case mapping1 refers to mapping2 via a NJOutputMapping
+ // Requires two passes to deal with inter-mapping references. First make
+ // an empty mapping for each serialized mapping. Then, deserialize the
+ // data pointing to the empty mappings. Then merge that data back into
+ // its equivalent empty one, which is the one we finally use.
for (NSDictionary *storedMapping in storedMappings) {
NJMapping *mapping = [[NJMapping alloc] initWithName:storedMapping[@"name"]];
[newMappings addObject:mapping];
}
for (unsigned i = 0; i < storedMappings.count; ++i) {
- NSDictionary *entries = storedMappings[i][@"entries"];
- NJMapping *mapping = newMappings[i];
- for (id key in entries) {
- NJOutput *output = [NJOutput outputDeserialize:entries[key]
- withMappings:newMappings];
- if (output)
- mapping.entries[key] = output;
- }
+ NJMapping *realMapping = [[NJMapping alloc] initWithSerialization:storedMappings[i]
+ mappings:newMappings];
+ [newMappings[i] mergeEntriesFrom:realMapping];
}
if (newMappings.count) {
if (selected >= newMappings.count)
selected = 0;
[self activateMapping:_mappings[selected]];
- [self mappingsChanged];
+ [self mappingsSet];
+ }
+}
+
+- (void)mappingConflictDidResolve:(NSAlert *)alert
+ returnCode:(NSInteger)returnCode
+ contextInfo:(void *)contextInfo {
+ NSDictionary *userInfo = CFBridgingRelease(contextInfo);
+ NJMapping *oldMapping = userInfo[@"old mapping"];
+ NJMapping *newMapping = userInfo[@"new mapping"];
+ switch (returnCode) {
+ case NSAlertFirstButtonReturn: // Merge
+ [oldMapping mergeEntriesFrom:newMapping];
+ [self activateMapping:oldMapping];
+ [self mappingsChanged];
+ break;
+ case NSAlertThirdButtonReturn: // New Mapping
+ [_mappings addObject:newMapping];
+ [self activateMapping:newMapping];
+ [self mappingsChanged];
+ [self mappingPressed:alert];
+ [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:_mappings.count - 1] byExtendingSelection:NO];
+ [tableView editColumn:0 row:_mappings.count - 1 withEvent:nil select:YES];
+ break;
+ default: // Cancel, other.
+ break;
}
}
mappings:_mappings
error:&error];
- if (mapping && !error) {
- BOOL conflict = NO;
+ if (mapping) {
NJMapping *mergeInto = self[mapping.name];
- for (id key in mapping.entries) {
- if (mergeInto.entries[key]
- && ![mergeInto.entries[key] isEqual:mapping.entries[key]]) {
- conflict = YES;
- break;
- }
- }
-
- if (conflict) {
+ if ([mergeInto hasConflictWith:mapping]) {
NSAlert *conflictAlert = [[NSAlert alloc] init];
- conflictAlert.messageText = @"Replace existing mappings?";
+ conflictAlert.messageText = NSLocalizedString(@"import conflict prompt", @"Title of import conflict alert");
conflictAlert.informativeText =
- [NSString stringWithFormat:
- @"This file contains inputs you've already mapped in \"%@\". Do you "
- @"want to merge them and replace your existing mappings, or import this "
- @"as a separate mapping?", mapping.name];
- [conflictAlert addButtonWithTitle:@"Merge"];
- [conflictAlert addButtonWithTitle:@"Cancel"];
- [conflictAlert addButtonWithTitle:@"New Mapping"];
- NSInteger res = [conflictAlert runModal];
- if (res == NSAlertSecondButtonReturn)
- return;
- else if (res == NSAlertThirdButtonReturn)
- mergeInto = nil;
- }
-
- if (mergeInto) {
- [mergeInto.entries addEntriesFromDictionary:mapping.entries];
- mapping = mergeInto;
+ [NSString stringWithFormat:NSLocalizedString(@"import conflict in %@", @"Explanation of import conflict"),
+ mapping.name];
+ [conflictAlert addButtonWithTitle:NSLocalizedString(@"import and merge", @"button to merge imported mappings")];
+ [conflictAlert addButtonWithTitle:NSLocalizedString(@"cancel import", @"button to cancel import")];
+ [conflictAlert addButtonWithTitle:NSLocalizedString(@"import new mapping", @"button to import as new mapping")];
+ [conflictAlert beginSheetModalForWindow:popoverActivate.window
+ modalDelegate:self
+ didEndSelector:@selector(mappingConflictDidResolve:returnCode:contextInfo:)
+ contextInfo:(void *)CFBridgingRetain(@{ @"old mapping": mergeInto,
+ @"new mapping": mapping })];
+ } else if (mergeInto) {
+ [mergeInto mergeEntriesFrom:mapping];
+ [self activateMapping:mergeInto];
+ [self mappingsChanged];
} else {
[_mappings addObject:mapping];
+ [self activateMapping:mapping];
+ [self mappingsChanged];
}
-
- [self activateMapping:mapping];
- [self mappingsChanged];
-
- if (conflict && !mergeInto) {
- [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:_mappings.count - 1] byExtendingSelection:NO];
- [tableView editColumn:0 row:_mappings.count - 1 withEvent:nil select:YES];
- }
- }
-
- if (error) {
+ } else {
[window presentError:error
modalForWindow:window
delegate:nil