+- (void)mappingConflictDidResolve:(NSAlert *)alert
+ returnCode:(NSInteger)returnCode
+ contextInfo:(void *)contextInfo {
+ NSDictionary *userInfo = CFBridgingRelease(contextInfo);
+ NJMapping *oldMapping = userInfo[@"old mapping"];
+ NJMapping *newMapping = userInfo[@"new mapping"];
+ NSInteger idx = [userInfo[@"index"] intValue];
+ [alert.window orderOut:nil];
+ switch (returnCode) {
+ case NSAlertFirstButtonReturn: // Merge
+ [self.mappingsController mergeMapping:newMapping intoMapping:oldMapping];
+ [self.mappingsController activateMapping:oldMapping];
+ break;
+ case NSAlertThirdButtonReturn: // New Mapping
+ [self.mvc beginUpdates];
+ [self.mappingsController addMapping:newMapping];
+ [self.mvc addedMappingAtIndex:idx startEditing:YES];
+ [self.mvc endUpdates];
+ [self.mappingsController activateMapping:newMapping];
+ break;
+ default: // Cancel, other.
+ break;
+ }
+}
+
+- (void)promptForMapping:(NJMapping *)mapping atIndex:(NSInteger)idx {
+ NJMapping *mergeInto = self.mappingsController[mapping.name];
+ NSAlert *conflictAlert = [[NSAlert alloc] init];
+ conflictAlert.messageText = NSLocalizedString(@"import conflict prompt", @"Title of import conflict alert");
+ conflictAlert.informativeText =
+ [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:window
+ modalDelegate:self
+ didEndSelector:@selector(mappingConflictDidResolve:returnCode:contextInfo:)
+ contextInfo:(void *)CFBridgingRetain(@{ @"index": @(idx),
+ @"old mapping": mergeInto,
+ @"new mapping": mapping })];
+}
+
+- (NSInteger)numberOfMappings:(NJMappingsViewController *)mvc {
+ return self.mappingsController.count;
+}
+
+- (NJMapping *)mappingsViewController:(NJMappingsViewController *)mvc
+ mappingForIndex:(NSUInteger)idx {
+ return self.mappingsController[idx];
+}
+
+- (void)mappingsViewController:(NJMappingsViewController *)mvc
+ renameMappingAtIndex:(NSInteger)index
+ toName:(NSString *)name {
+ [self.mappingsController renameMapping:self.mappingsController[index]
+ to:name];
+}
+
+- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
+ canMoveMappingFromIndex:(NSInteger)fromIdx
+ toIndex:(NSInteger)toIdx {
+ return fromIdx != toIdx && fromIdx != 0 && toIdx != 0
+ && toIdx < (NSInteger)self.mappingsController.count;
+}
+
+- (void)mappingsViewController:(NJMappingsViewController *)mvc
+ moveMappingFromIndex:(NSInteger)fromIdx
+ toIndex:(NSInteger)toIdx {
+ [mvc beginUpdates];
+ [mvc.mappingList moveRowAtIndex:fromIdx toIndex:toIdx];
+ [self.mappingsController moveMoveMappingFromIndex:fromIdx toIndex:toIdx];
+ [mvc endUpdates];
+}
+
+- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
+ canRemoveMappingAtIndex:(NSInteger)idx {
+ return idx != 0;
+}
+
+- (void)mappingsViewController:(NJMappingsViewController *)mvc
+ removeMappingAtIndex:(NSInteger)idx {
+ [mvc beginUpdates];
+ [mvc removedMappingAtIndex:idx];
+ [self.mappingsController removeMappingAtIndex:idx];
+ [mvc endUpdates];
+}
+
+- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
+ importMappingFromURL:(NSURL *)url
+ atIndex:(NSInteger)index
+ error:(NSError **)error {
+ NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url
+ error:error];
+ if ([self.mappingsController[mapping.name] hasConflictWith:mapping]) {
+ [self promptForMapping:mapping atIndex:index];
+ } else if (self.mappingsController[mapping.name]) {
+ [self.mappingsController[mapping.name] mergeEntriesFrom:mapping];
+ } else if (mapping) {
+ [self.mvc beginUpdates];
+ [self.mvc addedMappingAtIndex:index startEditing:NO];
+ [self.mappingsController insertMapping:mapping atIndex:index];
+ [self.mvc endUpdates];
+ }
+ return !!mapping;
+}
+
+- (void)mappingsViewController:(NJMappingsViewController *)mvc
+ addMapping:(NJMapping *)mapping {
+ [mvc beginUpdates];
+ [mvc addedMappingAtIndex:self.mappingsController.count startEditing:YES];
+ [self.mappingsController addMapping:mapping];
+ [mvc endUpdates];
+ [self.mappingsController activateMapping:mapping];
+}
+
+- (void)mappingsViewController:(NJMappingsViewController *)mvc
+ choseMappingAtIndex:(NSInteger)idx {
+ [self.mappingsController activateMapping:self.mappingsController[idx]];
+}
+
+- (id)deviceViewController:(NJDeviceViewController *)dvc
+ elementForUID:(NSString *)uid {
+ return self.deviceController[uid];
+}
+
+- (void)deviceViewControllerDidSelectNothing:(NJDeviceViewController *)dvc {
+ [self.outputController loadInput:dvc.selectedHandler];
+}
+
+- (void)deviceViewController:(NJDeviceViewController *)dvc
+ didSelectBranch:(NJInputPathElement *)handler {
+ [self.outputController loadInput:dvc.selectedHandler];
+}
+
+- (void)deviceViewController:(NJDeviceViewController *)dvc
+ didSelectHandler:(NJInputPathElement *)handler {
+ [self.outputController loadInput:dvc.selectedHandler];
+}
+
+- (void)deviceViewController:(NJDeviceViewController *)dvc
+ didSelectDevice:(NJInputPathElement *)device {
+ [self.outputController loadInput:dvc.selectedHandler];
+}
+
+- (void)deviceController:(NJDeviceController *)dc
+ didAddDevice:(NJDevice *)device {
+ [self.dvc addedDevice:device atIndex:dc.count - 1];
+}
+
+- (void)deviceController:(NJDeviceController *)dc
+ didRemoveDeviceAtIndex:(NSInteger)idx {
+ [self.dvc removedDeviceAtIndex:idx];
+}
+
+- (void)deviceControllerDidStartHID:(NJDeviceController *)dc {
+ [self.dvc hidStarted];
+}
+
+- (void)deviceControllerDidStopHID:(NJDeviceController *)dc {
+ [self.dvc hidStopped];
+}
+
+- (void)deviceController:(NJDeviceController *)dc didInput:(NJInput *)input {
+ [self.outputController loadInput:input];
+ [self.outputController focusKey];
+}
+
+- (void)deviceController:(NJDeviceController *)dc didError:(NSError *)error {
+ // Since the error shows the window, it can trigger another attempt
+ // to re-open the HID manager, which will also probably fail and error,
+ // so don't bother repeating ourselves.
+ if (!window.attachedSheet) {
+ [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
+ [window makeKeyAndOrderFront:nil];
+ [window presentError:error
+ modalForWindow:window
+ delegate:nil
+ didPresentSelector:nil
+ contextInfo:nil];
+ }
+}
+
+- (NSInteger)numberOfDevicesInDeviceList:(NJDeviceViewController *)dvc {
+ return self.deviceController.count;
+}
+
+- (NJDevice *)deviceViewController:(NJDeviceViewController *)dvc
+ deviceForIndex:(NSUInteger)idx {
+ return self.deviceController[idx];
+}