X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=blobdiff_plain;f=Classes%2FNJMappingsController.m;h=2b08a52a3db05228e687b96a89d1e4e504b02d47;hp=c3209ac931a20b365002b88fc96d9494e390c25a;hb=30f40c368cf11684956e992cb13a8f70b5c5e0ce;hpb=5c56b988cdf5079dd5f3f256012e0d0a384f96f7 diff --git a/Classes/NJMappingsController.m b/Classes/NJMappingsController.m index c3209ac..2b08a52 100644 --- a/Classes/NJMappingsController.m +++ b/Classes/NJMappingsController.m @@ -30,11 +30,6 @@ 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]) @@ -47,13 +42,13 @@ } - (void)mappingsSet { - [tableView reloadData]; - [self updateInterfaceForCurrentMapping]; + [self postLoadProcess]; [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingListChanged object:self userInfo:@{ NJMappingListKey: _mappings, NJMappingKey: _currentMapping }]; + [self.mvc changedActiveMappingToIndex:[_mappings indexOfObjectIdenticalTo:_currentMapping]]; } - (void)mappingsChanged { @@ -94,16 +89,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; @@ -112,56 +97,13 @@ NSLog(@"Switching to mapping %@.", mapping.name); _manualMapping = mapping; _currentMapping = mapping; - [self updateInterfaceForCurrentMapping]; + [self.mvc changedActiveMappingToIndex:[_mappings indexOfObjectIdenticalTo:_currentMapping]]; [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingChanged object:self userInfo:@{ NJMappingKey : _currentMapping }]; } -- (IBAction)addPressed:(id)sender { - [self mappingPressed:sender]; - NJMapping *newMapping = [[NJMapping alloc] init]; - [_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; -} - - (void)save { NSLog(@"Saving mappings to defaults."); NSMutableArray *ary = [[NSMutableArray alloc] initWithCapacity:_mappings.count]; @@ -170,245 +112,185 @@ [NSUserDefaults.standardUserDefaults setObject:ary forKey:@"mappings"]; } +- (void)postLoadProcess { + for (NJMapping *mapping in self) + [mapping postLoadProcess:self]; +} + - (void)load { NSUInteger selected = [NSUserDefaults.standardUserDefaults integerForKey:@"selected"]; NSArray *storedMappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"]; NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count]; - // 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) { - NJMapping *realMapping = [[NJMapping alloc] initWithSerialization:storedMappings[i] - mappings:newMappings]; - [newMappings[i] mergeEntriesFrom:realMapping]; + NJMapping *mapping = [[NJMapping alloc] initWithSerialization:storedMappings[i]]; + [newMappings addObject:mapping]; } + if (newMappings.count) { _mappings = newMappings; if (selected >= newMappings.count) selected = 0; + [self.mvc reloadData]; [self activateMapping:_mappings[selected]]; [self mappingsSet]; } } +- (NSInteger)indexOfMapping:(NJMapping *)mapping { + return [_mappings indexOfObjectIdenticalTo:mapping]; +} + +- (void)mergeMapping:(NJMapping *)mapping intoMapping:(NJMapping *)existing { + [existing mergeEntriesFrom:mapping]; + [self mappingsChanged]; + if (existing == _currentMapping) { + // FIXME: Hack to trigger updates when renaming. + _currentMapping = nil; + NJMapping *manual = _manualMapping; + [self activateMapping:existing]; + _manualMapping = manual; + } +} + +- (void)addMapping:(NJMapping *)mapping { + [self insertMapping:mapping atIndex:_mappings.count]; +} + +- (void)insertMapping:(NJMapping *)mapping atIndex:(NSInteger)idx { + [_mappings insertObject:mapping atIndex:idx]; + [self mappingsChanged]; +} + +- (void)removeMappingAtIndex:(NSInteger)idx { + NSInteger currentIdx = [self indexOfMapping:_currentMapping]; + [_mappings removeObjectAtIndex:idx]; + [self activateMapping:self[MIN(currentIdx, _mappings.count - 1)]]; + [self mappingsChanged]; +} + +- (void)moveMoveMappingFromIndex:(NSInteger)fromIdx toIndex:(NSInteger)toIdx { + [_mappings moveObjectAtIndex:fromIdx toIndex:toIdx]; + [self mappingsChanged]; +} + +- (NSUInteger)count { + return _mappings.count; +} + - (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]; + [self mergeMapping:newMapping intoMapping:oldMapping]; [self activateMapping:oldMapping]; - [self mappingsChanged]; break; case NSAlertThirdButtonReturn: // New Mapping - [_mappings addObject:newMapping]; + [self.mvc.mappingList beginUpdates]; + [self addMapping:newMapping]; + [self.mvc addedMappingAtIndex:_mappings.count - 1 startEditing:YES]; + [self.mvc.mappingList endUpdates]; [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; } } -- (void)addMappingWithContentsOfURL:(NSURL *)url { - NSWindow *window = popoverActivate.window; - NSError *error; - NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url - mappings:_mappings - error:&error]; - - 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: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]; - } - } else { - [window presentError:error - modalForWindow:window - delegate:nil - didPresentSelector:nil - contextInfo:nil]; - } -} - -- (void)importPressed:(id)sender { - NSOpenPanel *panel = [NSOpenPanel openPanel]; - panel.allowedFileTypes = @[ @"enjoyable", @"json", @"txt" ]; +- (void)promptForMapping:(NJMapping *)mapping atIndex:(NSInteger)idx { NSWindow *window = NSApplication.sharedApplication.keyWindow; - [panel beginSheetModalForWindow:window - completionHandler:^(NSInteger result) { - if (result != NSFileHandlingPanelOKButton) - return; - [panel close]; - [self addMappingWithContentsOfURL:panel.URL]; - }]; - + NJMapping *mergeInto = self[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(@{ @"old mapping": mergeInto, + @"new mapping": mapping })]; } -- (void)exportPressed:(id)sender { - NSSavePanel *panel = [NSSavePanel savePanel]; - panel.allowedFileTypes = @[ @"enjoyable" ]; - NJMapping *mapping = _currentMapping; - panel.nameFieldStringValue = [mapping.name stringByFixingPathComponent]; - 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]; - } - }]; +- (NSInteger)numberOfMappings:(NJMappingsViewController *)mvc { + return self.count; } -- (IBAction)mappingPressed:(id)sender { - [popover showRelativeToRect:popoverActivate.bounds - ofView:popoverActivate - preferredEdge:NSMinXEdge]; +- (NJMapping *)mappingsViewController:(NJMappingsViewController *)mvc + mappingForIndex:(NSUInteger)idx { + return self[idx]; } -- (void)popoverWillShow:(NSNotification *)notification { - popoverActivate.state = NSOnState; +- (void)mappingsViewController:(NJMappingsViewController *)mvc + editedMappingAtIndex:(NSInteger)index { + [self mappingsChanged]; } -- (void)popoverWillClose:(NSNotification *)notification { - popoverActivate.state = NSOffState; +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + canMoveMappingFromIndex:(NSInteger)fromIdx + toIndex:(NSInteger)toIdx { + return fromIdx != toIdx && fromIdx != 0 && toIdx != 0; } -- (IBAction)moveUpPressed:(id)sender { - if ([_mappings moveFirstwards:_currentMapping upTo:1]) - [self mappingsChanged]; +- (void)mappingsViewController:(NJMappingsViewController *)mvc + moveMappingFromIndex:(NSInteger)fromIdx + toIndex:(NSInteger)toIdx { + [mvc.mappingList beginUpdates]; + [mvc.mappingList moveRowAtIndex:fromIdx toIndex:toIdx]; + [self moveMoveMappingFromIndex:fromIdx toIndex:toIdx]; + [mvc.mappingList endUpdates]; } -- (IBAction)moveDownPressed:(id)sender { - if ([_mappings moveLastwards:_currentMapping]) - [self mappingsChanged]; +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + canRemoveMappingAtIndex:(NSInteger)idx { + return idx != 0; } -- (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; - } +- (void)mappingsViewController:(NJMappingsViewController *)mvc + removeMappingAtIndex:(NSInteger)idx { + [mvc.mappingList beginUpdates]; + [mvc removedMappingAtIndex:idx]; + [self removeMappingAtIndex:idx]; + [mvc.mappingList endUpdates]; } -- (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; +- (BOOL)mappingsViewController:(NJMappingsViewController *)mvc + importMappingFromURL:(NSURL *)url + atIndex:(NSInteger)index + error:(NSError **)error { + NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url + error:error]; + if ([self[mapping.name] hasConflictWith:mapping]) { + [self promptForMapping:mapping atIndex:index]; + } else if (self[mapping.name]) { + [self[mapping.name] mergeEntriesFrom:mapping]; + } else if (mapping) { + [self insertMapping:mapping atIndex:index]; } + return !!mapping; } -- (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]; - } +- (void)mappingsViewController:(NJMappingsViewController *)mvc + addMapping:(NJMapping *)mapping { + [mvc.mappingList beginUpdates]; + [mvc addedMappingAtIndex:_mappings.count startEditing:YES]; + [self addMapping:mapping]; + [mvc.mappingList endUpdates]; + [self activateMapping:mapping]; } -- (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