X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=blobdiff_plain;f=NJMappingsController.m;h=45085b5c79dc56abb97c1c7f2e494c753e9a276c;hp=e5b2b7d55d51cf3b34aa255c3dc03e27920413ec;hb=b8c30e0c06effb8f4a937378e5c4cf8a22f40c59;hpb=e2a4d830dd9817f6a515a3b1b6aa152d3bb98c2b diff --git a/NJMappingsController.m b/NJMappingsController.m index e5b2b7d..45085b5 100644 --- a/NJMappingsController.m +++ b/NJMappingsController.m @@ -7,16 +7,18 @@ #import "NJMappingsController.h" -#import "ApplicationController.h" #import "NJMapping.h" #import "NJMappingsController.h" -#import "Target.h" -#import "TargetController.h" +#import "NJOutput.h" +#import "NJOutputController.h" #import "NJEvents.h" +#define PB_ROW @"com.yukkurigames.Enjoyable.MappingRow" + @implementation NJMappingsController { NSMutableArray *_mappings; NJMapping *manualMapping; + NSString *draggingName; } - (id)init { @@ -29,6 +31,11 @@ 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]) @@ -36,63 +43,99 @@ return nil; } +- (NJMapping *)objectAtIndexedSubscript:(NSUInteger)idx { + return idx < _mappings.count ? _mappings[idx] : nil; +} + +- (void)mappingsChanged { + [self save]; + [tableView reloadData]; + popoverActivate.title = _currentMapping.name; + [self updateInterfaceForCurrentMapping]; + [NSNotificationCenter.defaultCenter + postNotificationName:NJEventMappingListChanged + object:_mappings]; +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(__unsafe_unretained id [])buffer + count:(NSUInteger)len { + return [_mappings countByEnumeratingWithState:state + objects:buffer + count:len]; +} + - (void)activateMappingForProcess:(NSString *)processName { - NJMapping *oldMapping = manualMapping; - NJMapping *newMapping = self[processName]; - if (!newMapping) - newMapping = oldMapping; - if (newMapping != _currentMapping) - [self activateMapping:newMapping]; - manualMapping = oldMapping; + if ([manualMapping.name.lowercaseString isEqualToString:@"@application"]) { + manualMapping.name = processName; + [self mappingsChanged]; + } else { + NJMapping *oldMapping = manualMapping; + NJMapping *newMapping = self[processName]; + if (!newMapping) + newMapping = oldMapping; + if (newMapping != _currentMapping) + [self activateMapping:newMapping]; + manualMapping = oldMapping; + } +} + +- (void)updateInterfaceForCurrentMapping { + NSUInteger selected = [_mappings indexOfObject:_currentMapping]; + [removeButton setEnabled:selected != 0]; + [moveDown setEnabled:selected && selected != _mappings.count - 1]; + [moveUp setEnabled:selected > 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; + if (mapping == _currentMapping) + return; NSLog(@"Switching to mapping %@.", mapping.name); manualMapping = mapping; _currentMapping = mapping; - [removeButton setEnabled:_mappings[0] != mapping]; - [targetController loadCurrent]; + [self updateInterfaceForCurrentMapping]; + [outputController loadCurrent]; [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingChanged object:_currentMapping]; - [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:[_mappings indexOfObject:mapping]] byExtendingSelection:NO]; } - (IBAction)addPressed:(id)sender { NJMapping *newMapping = [[NJMapping alloc] initWithName:@"Untitled"]; [_mappings addObject:newMapping]; - [(ApplicationController *)NSApplication.sharedApplication.delegate mappingsChanged]; - [tableView reloadData]; - [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:_mappings.count - 1] byExtendingSelection:NO]; - [tableView editColumn:0 row:_mappings.count - 1 withEvent:nil select:YES]; [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; - [_mappings removeObjectAtIndex:tableView.selectedRow]; - [tableView reloadData]; - [(ApplicationController *)NSApplication.sharedApplication.delegate mappingsChanged]; - [self activateMapping:_mappings[0]]; - [self save]; + NSInteger selectedRow = tableView.selectedRow; + [_mappings removeObjectAtIndex:selectedRow]; + [self activateMapping:_mappings[MIN(selectedRow, _mappings.count - 1)]]; + [self mappingsChanged]; } --(void)tableViewSelectionDidChange:(NSNotification *)notify { - if (tableView.selectedRow >= 0) - [self activateMapping:_mappings[tableView.selectedRow]]; +- (void)tableViewSelectionDidChange:(NSNotification *)notify { + [self activateMapping:self[tableView.selectedRow]]; } - (id)tableView:(NSTableView *)view objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)index { - return [_mappings[index] name]; + return self[index].name; } -- (void)tableView:(NSTableView *)view setObjectValue:(NSString *)obj forTableColumn:(NSTableColumn *)col row:(NSInteger)index { - [(NJMapping *)_mappings[index] setName:obj]; - [tableView reloadData]; - [(ApplicationController *)NSApplication.sharedApplication.delegate mappingsChanged]; +- (void)tableView:(NSTableView *)view + setObjectValue:(NSString *)obj + forTableColumn:(NSTableColumn *)col + row:(NSInteger)index { + self[index].name = obj; + [self mappingsChanged]; } - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { @@ -100,31 +143,27 @@ } - (BOOL)tableView:(NSTableView *)view shouldEditTableColumn:(NSTableColumn *)column row:(NSInteger)index { - return index > 0; + return YES; } - (void)save { NSLog(@"Saving mappings to defaults."); - [NSUserDefaults.standardUserDefaults setValuesForKeysWithDictionary:[self dumpAll]]; -} - -- (void)load { - [self loadAllFrom:NSUserDefaults.standardUserDefaults.dictionaryRepresentation]; -} - -- (NSDictionary *)dumpAll { NSMutableArray *ary = [[NSMutableArray alloc] initWithCapacity:_mappings.count]; for (NJMapping *mapping in _mappings) [ary addObject:[mapping serialize]]; - NSUInteger current = _currentMapping ? [_mappings indexOfObject:_currentMapping] : 0; - return @{ @"mappings": ary, @"selected": @(current) }; + [NSUserDefaults.standardUserDefaults setObject:ary forKey:@"mappings"]; +} + +- (void)load { + NSUInteger selected = [NSUserDefaults.standardUserDefaults integerForKey:@"selected"]; + NSArray *mappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"]; + [self loadAllFrom:mappings andActivate:selected]; } -- (void)loadAllFrom:(NSDictionary*)envelope { - NSArray *storedMappings = envelope[@"mappings"]; +- (void)loadAllFrom:(NSArray *)storedMappings andActivate:(NSUInteger)selected { NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count]; - // have to do two passes in case mapping1 refers to mapping2 via a TargetMapping + // have to do two passes in case mapping1 refers to mapping2 via a NJOutputMapping for (NSDictionary *storedMapping in storedMappings) { NJMapping *mapping = [[NJMapping alloc] initWithName:storedMapping[@"name"]]; [newMappings addObject:mapping]; @@ -134,21 +173,19 @@ NSDictionary *entries = storedMappings[i][@"entries"]; NJMapping *mapping = newMappings[i]; for (id key in entries) { - Target *target = [Target targetDeserialize:entries[key] - withMappings:newMappings]; - if (target) - mapping.entries[key] = target; + NJOutput *output = [NJOutput outputDeserialize:entries[key] + withMappings:newMappings]; + if (output) + mapping.entries[key] = output; } } if (newMappings.count) { - unsigned current = [envelope[@"selected"] unsignedIntValue]; - if (current >= newMappings.count) - current = 0; _mappings = newMappings; - [tableView reloadData]; - [(ApplicationController *)NSApplication.sharedApplication.delegate mappingsChanged]; - [self activateMapping:_mappings[current]]; + if (selected >= newMappings.count) + selected = 0; + [self activateMapping:_mappings[selected]]; + [self mappingsChanged]; } } @@ -174,15 +211,76 @@ for (id key in entries) { NSDictionary *value = entries[key]; if ([key isKindOfClass:NSString.class]) { - Target *target = [Target targetDeserialize:value - withMappings:_mappings]; - if (target) - mapping.entries[key] = target; + NJOutput *output = [NJOutput outputDeserialize:value + withMappings:_mappings]; + if (output) + mapping.entries[key] = output; } } return mapping; } +- (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)importPressed:(id)sender { NSOpenPanel *panel = [NSOpenPanel openPanel]; panel.allowedFileTypes = @[ @"enjoyable", @"json", @"txt" ]; @@ -191,68 +289,10 @@ completionHandler:^(NSInteger result) { if (result != NSFileHandlingPanelOKButton) return; - [panel close]; - NSError *error; - NJMapping *mapping = [self mappingWithURL:panel.URL error:&error]; - - if (!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]; - [tableView reloadData]; - } - - [self save]; - [(ApplicationController *)NSApplication.sharedApplication.delegate mappingsChanged]; - [self activateMapping:mapping]; - [targetController loadCurrent]; - - 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]; - } + [self addMappingWithContentsOfURL:panel.URL]; }]; - + } - (void)exportPressed:(id)sender { @@ -267,13 +307,7 @@ return; [panel close]; NSError *error; - NSDictionary *serialization = [mapping serialize]; - NSData *json = [NSJSONSerialization dataWithJSONObject:serialization - options:NSJSONWritingPrettyPrinted - error:&error]; - if (!error) - [json writeToURL:panel.URL options:NSDataWritingAtomic error:&error]; - + [mapping writeToURL:panel.URL error:&error]; if (error) { [window presentError:error modalForWindow:window @@ -284,4 +318,115 @@ }]; } +- (IBAction)mappingPressed:(id)sender { + [popover showRelativeToRect:popoverActivate.bounds + ofView:popoverActivate + preferredEdge:NSMinXEdge]; +} + +- (void)popoverWillShow:(NSNotification *)notification { + popoverActivate.state = NSOnState; +} + +- (void)popoverWillClose:(NSNotification *)notification { + popoverActivate.state = NSOffState; +} + +- (IBAction)moveUpPressed:(id)sender { + NSUInteger idx = [_mappings indexOfObject:_currentMapping]; + if (idx > 1 && idx != NSNotFound) { + [_mappings exchangeObjectAtIndex:idx withObjectAtIndex:idx - 1]; + [self mappingsChanged]; + } +} + +- (IBAction)moveDownPressed:(id)sender { + NSUInteger idx = [_mappings indexOfObject:_currentMapping]; + if (idx < _mappings.count - 1) { + [_mappings exchangeObjectAtIndex:idx withObjectAtIndex:idx + 1]; + [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; + } +} + +- (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; + } +} + +- (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)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 { + return NO; + } +} + @end