X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=blobdiff_plain;f=Classes%2FNJMappingsController.m;h=4859970fc63ff07b81788bf655978ec33d63f019;hp=de66fb888f5199bdd348b40289ffa6412da2558a;hb=a3d6f991d110dcccb70e137f43cbafc60f7ecee7;hpb=a7260fe79808efcad6a92cace15a1b3429f14787 diff --git a/Classes/NJMappingsController.m b/Classes/NJMappingsController.m index de66fb8..4859970 100644 --- a/Classes/NJMappingsController.m +++ b/Classes/NJMappingsController.m @@ -22,18 +22,14 @@ - (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 self; } -- (void)awakeFromNib { - [tableView registerForDraggedTypes:@[PB_ROW, NSURLPboardType]]; - [tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; -} - - (NJMapping *)objectForKeyedSubscript:(NSString *)name { for (NJMapping *mapping in _mappings) if ([name isEqualToString:mapping.name]) @@ -45,13 +41,18 @@ return idx < _mappings.count ? _mappings[idx] : nil; } -- (void)mappingsChanged { - [self save]; - [tableView reloadData]; - [self updateInterfaceForCurrentMapping]; +- (void)mappingsSet { [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingListChanged - object:_mappings]; + object:self + userInfo:@{ NJMappingListKey: _mappings, + NJMappingKey: _currentMapping }]; + [self.mvc changedActiveMappingToIndex:[_mappings indexOfObjectIdenticalTo:_currentMapping]]; +} + +- (void)mappingsChanged { + [self save]; + [self mappingsSet]; } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state @@ -77,7 +78,9 @@ 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]; } @@ -85,16 +88,6 @@ _manualMapping = oldMapping; } -- (void)updateInterfaceForCurrentMapping { - NSUInteger selected = [_mappings indexOfObject:_currentMapping]; - removeButton.enabled = selected != 0; - moveUp.enabled = selected > 1; - moveDown.enabled = selected && selected != _mappings.count - 1; - popoverActivate.title = _currentMapping.name; - [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selected] byExtendingSelection:NO]; - [NSUserDefaults.standardUserDefaults setInteger:selected forKey:@"selected"]; -} - - (void)activateMapping:(NJMapping *)mapping { if (!mapping) mapping = _manualMapping; @@ -103,51 +96,11 @@ NSLog(@"Switching to mapping %@.", mapping.name); _manualMapping = mapping; _currentMapping = mapping; - [self updateInterfaceForCurrentMapping]; - [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingChanged - object:_currentMapping]; -} - -- (IBAction)addPressed:(id)sender { - NJMapping *newMapping = [[NJMapping alloc] initWithName:@"Untitled"]; - [_mappings addObject:newMapping]; - [self activateMapping:newMapping]; - [self mappingsChanged]; - [tableView editColumn:0 row:_mappings.count - 1 withEvent:nil select:YES]; -} - -- (IBAction)removePressed:(id)sender { - if (tableView.selectedRow == 0) - return; - - NSInteger selectedRow = tableView.selectedRow; - [_mappings removeObjectAtIndex:selectedRow]; - [self activateMapping:_mappings[MIN(selectedRow, _mappings.count - 1)]]; - [self mappingsChanged]; -} - -- (void)tableViewSelectionDidChange:(NSNotification *)notify { - [self activateMapping:self[tableView.selectedRow]]; -} - -- (id)tableView:(NSTableView *)view objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)index { - return self[index].name; -} - -- (void)tableView:(NSTableView *)view - setObjectValue:(NSString *)obj - forTableColumn:(NSTableColumn *)col - row:(NSInteger)index { - self[index].name = obj; - [self mappingsChanged]; -} - -- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { - return _mappings.count; -} - -- (BOOL)tableView:(NSTableView *)view shouldEditTableColumn:(NSTableColumn *)column row:(NSInteger)index { - return YES; + [self.mvc changedActiveMappingToIndex:[_mappings indexOfObjectIdenticalTo:_currentMapping]]; + [NSNotificationCenter.defaultCenter + postNotificationName:NJEventMappingChanged + object:self + userInfo:@{ NJMappingKey : _currentMapping }]; } - (void)save { @@ -160,244 +113,168 @@ - (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) { _mappings = newMappings; if (selected >= newMappings.count) selected = 0; + [self.mvc reloadData]; [self activateMapping:_mappings[selected]]; - [self mappingsChanged]; + [self mappingsSet]; } } -- (void)addMappingWithContentsOfURL:(NSURL *)url { - NSWindow *window = popoverActivate.window; - NSError *error; - NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url - mappings:_mappings - error:&error]; - - if (mapping && !error) { - BOOL conflict = NO; - 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) { - NSAlert *conflictAlert = [[NSAlert alloc] init]; - conflictAlert.messageText = @"Replace existing mappings?"; - 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; - } else { - [_mappings addObject:mapping]; - } - - [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) { - [window presentError:error - modalForWindow:window - delegate:nil - didPresentSelector:nil - contextInfo:nil]; +- (void)mappingConflictDidResolve:(NSAlert *)alert + returnCode:(NSInteger)returnCode + contextInfo:(void *)contextInfo { + NSDictionary *userInfo = CFBridgingRelease(contextInfo); + NJMapping *oldMapping = userInfo[@"old mapping"]; + NJMapping *newMapping = userInfo[@"new mapping"]; + [alert.window orderOut:nil]; + switch (returnCode) { + case NSAlertFirstButtonReturn: // Merge + [oldMapping mergeEntriesFrom:newMapping]; + _currentMapping = nil; + [self activateMapping:oldMapping]; + [self mappingsChanged]; + break; + case NSAlertThirdButtonReturn: // New Mapping + [self.mvc.mappingList beginUpdates]; + [_mappings addObject:newMapping]; + [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:YES]; + [self.mvc.mappingList endUpdates]; + [self activateMapping:newMapping]; + [self mappingsChanged]; + break; + default: // Cancel, other. + break; } } -- (void)importPressed:(id)sender { - NSOpenPanel *panel = [NSOpenPanel openPanel]; - panel.allowedFileTypes = @[ @"enjoyable", @"json", @"txt" ]; - NSWindow *window = NSApplication.sharedApplication.keyWindow; - [panel beginSheetModalForWindow:window - completionHandler:^(NSInteger result) { - if (result != NSFileHandlingPanelOKButton) - return; - [panel close]; - [self addMappingWithContentsOfURL:panel.URL]; - }]; - +- (void)addOrMergeMapping:(NJMapping *)mapping { + [self addOrMergeMapping:mapping atIndex:-1]; } -- (void)exportPressed:(id)sender { - NSSavePanel *panel = [NSSavePanel savePanel]; - panel.allowedFileTypes = @[ @"enjoyable" ]; - NJMapping *mapping = _currentMapping; - panel.nameFieldStringValue = [mapping.name stringByFixingPathComponent]; +- (void)addOrMergeMapping:(NJMapping *)mapping atIndex:(NSInteger)idx { NSWindow *window = NSApplication.sharedApplication.keyWindow; - [panel beginSheetModalForWindow:window - completionHandler:^(NSInteger result) { - if (result != NSFileHandlingPanelOKButton) - return; - [panel close]; - NSError *error; - [mapping writeToURL:panel.URL error:&error]; - if (error) { - [window presentError:error - modalForWindow:window - delegate:nil - didPresentSelector:nil - contextInfo:nil]; - } - }]; + if (mapping) { + NJMapping *mergeInto = self[mapping.name]; + if ([mergeInto hasConflictWith:mapping]) { + 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(@{ @"old mapping": mergeInto, + @"new mapping": mapping })]; + } else if (mergeInto) { + [mergeInto mergeEntriesFrom:mapping]; + [self activateMapping:mergeInto]; + [self mappingsChanged]; + } else { + if (idx == -1) + idx = _mappings.count; + [self.mvc.mappingList beginUpdates]; + [_mappings insertObject:mapping atIndex:idx]; + [self.mvc addedMappingAtIndex:idx startEditing:NO]; + [self.mvc.mappingList endUpdates]; + [self activateMapping:mapping]; + [self mappingsChanged]; + } + } } -- (IBAction)mappingPressed:(id)sender { - [popover showRelativeToRect:popoverActivate.bounds - ofView:popoverActivate - preferredEdge:NSMinXEdge]; +- (NSInteger)numberOfMappings:(NJMappingsViewController *)dvc { + return _mappings.count; } -- (void)popoverWillShow:(NSNotification *)notification { - popoverActivate.state = NSOnState; +- (NJMapping *)mappingsViewController:(NJMappingsViewController *)dvc + mappingForIndex:(NSUInteger)idx { + return _mappings[idx]; } -- (void)popoverWillClose:(NSNotification *)notification { - popoverActivate.state = NSOffState; +- (void)mappingsViewController:(NJMappingsViewController *)mvc + editedMappingAtIndex:(NSInteger)index { + [self mappingsChanged]; } -- (IBAction)moveUpPressed:(id)sender { - if ([_mappings moveFirstwards:_currentMapping upTo:1]) - [self mappingsChanged]; +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + canMoveMappingFromIndex:(NSInteger)fromIdx + toIndex:(NSInteger)toIdx { + return fromIdx != toIdx && fromIdx != 0 && toIdx != 0 && toIdx < (NSInteger)_mappings.count; } -- (IBAction)moveDownPressed:(id)sender { - if ([_mappings moveLastwards:_currentMapping]) - [self mappingsChanged]; +- (void)mappingsViewController:(NJMappingsViewController *)mvc + moveMappingFromIndex:(NSInteger)fromIdx + toIndex:(NSInteger)toIdx { + [_mappings moveObjectAtIndex:fromIdx toIndex:toIdx]; + [self mappingsChanged]; } -- (BOOL)tableView:(NSTableView *)tableView_ - acceptDrop:(id )info - row:(NSInteger)row - dropOperation:(NSTableViewDropOperation)dropOperation { - NSPasteboard *pboard = [info draggingPasteboard]; - if ([pboard.types containsObject:PB_ROW]) { - NSString *value = [pboard stringForType:PB_ROW]; - NSUInteger srcRow = [value intValue]; - [_mappings moveObjectAtIndex:srcRow toIndex:row]; - [self mappingsChanged]; - return YES; - } else if ([pboard.types containsObject:NSURLPboardType]) { - NSURL *url = [NSURL URLFromPasteboard:pboard]; - NSError *error; - NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url - mappings:_mappings - error:&error]; - if (error) { - [tableView_ presentError:error]; - return NO; - } else { - [_mappings insertObject:mapping atIndex:row]; - [self mappingsChanged]; - return YES; - } - } else { - return NO; - } +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + canRemoveMappingAtIndex:(NSInteger)idx { + return idx != 0; } -- (NSDragOperation)tableView:(NSTableView *)tableView_ - validateDrop:(id )info - proposedRow:(NSInteger)row - proposedDropOperation:(NSTableViewDropOperation)dropOperation { - NSPasteboard *pboard = [info draggingPasteboard]; - if ([pboard.types containsObject:PB_ROW]) { - [tableView_ setDropRow:MAX(1, row) dropOperation:NSTableViewDropAbove]; - return NSDragOperationMove; - } else if ([pboard.types containsObject:NSURLPboardType]) { - NSURL *url = [NSURL URLFromPasteboard:pboard]; - if ([url.pathExtension isEqualToString:@"enjoyable"]) { - [tableView_ setDropRow:MAX(1, row) dropOperation:NSTableViewDropAbove]; - return NSDragOperationCopy; - } else { - return NSDragOperationNone; - } - } else { - return NSDragOperationNone; - } +- (void)mappingsViewController:(NJMappingsViewController *)mvc + removeMappingAtIndex:(NSInteger)idx { + NJMapping *old = self[idx]; + [self.mvc.mappingList beginUpdates]; + [_mappings removeObjectAtIndex:idx]; + [self.mvc removedMappingAtIndex:idx]; + [self.mvc.mappingList endUpdates]; + if (old == _currentMapping) + [self activateMapping:self[MIN(idx, _mappings.count - 1)]]; + [self mappingsChanged]; } -- (NSArray *)tableView:(NSTableView *)tableView_ -namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination -forDraggedRowsWithIndexes:(NSIndexSet *)indexSet { - NJMapping *toSave = self[indexSet.firstIndex]; - NSString *filename = [[toSave.name stringByFixingPathComponent] - stringByAppendingPathExtension:@"enjoyable"]; - NSURL *dst = [dropDestination URLByAppendingPathComponent:filename]; - dst = [NSFileManager.defaultManager generateUniqueURLWithBase:dst]; - NSError *error; - if (![toSave writeToURL:dst error:&error]) { - [tableView_ presentError:error]; - return @[]; - } else { - return @[dst.lastPathComponent]; - } +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + importMappingFromURL:(NSURL *)url + atIndex:(NSInteger)index + error:(NSError **)error { + NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url + mappings:_mappings + error:error]; + [self addOrMergeMapping:mapping atIndex:index]; + return !!mapping; +} + +- (void)mappingsViewController:(NJMappingsViewController *)mvc + addMapping:(NJMapping *)mapping { + [self.mvc.mappingList beginUpdates]; + [_mappings addObject:mapping]; + [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:YES]; + [self.mvc.mappingList endUpdates]; + [self activateMapping:mapping]; + [self mappingsChanged]; } -- (BOOL)tableView:(NSTableView *)tableView_ -writeRowsWithIndexes:(NSIndexSet *)rowIndexes - toPasteboard:(NSPasteboard *)pboard { - if (rowIndexes.count == 1 && rowIndexes.firstIndex != 0) { - [pboard declareTypes:@[PB_ROW, NSFilesPromisePboardType] owner:nil]; - [pboard setString:@(rowIndexes.firstIndex).stringValue forType:PB_ROW]; - [pboard setPropertyList:@[@"enjoyable"] forType:NSFilesPromisePboardType]; - return YES; - } else if (rowIndexes.count == 1 && rowIndexes.firstIndex == 0) { - [pboard declareTypes:@[NSFilesPromisePboardType] owner:nil]; - [pboard setPropertyList:@[@"enjoyable"] forType:NSFilesPromisePboardType]; - return YES; - } else { - return NO; - } +- (void)mappingsViewController:(NJMappingsViewController *)mvc + choseMappingAtIndex:(NSInteger)idx { + [self activateMapping:self[idx]]; } @end