X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=blobdiff_plain;f=NJMappingsController.m;h=45085b5c79dc56abb97c1c7f2e494c753e9a276c;hp=193edfe670d073dd36d8ccbc613c5225c6d1f80c;hb=b8c30e0c06effb8f4a937378e5c4cf8a22f40c59;hpb=ca998dd950f0b1900b21dc05cf57987d09b4e70e diff --git a/NJMappingsController.m b/NJMappingsController.m index 193edfe..45085b5 100644 --- a/NJMappingsController.m +++ b/NJMappingsController.m @@ -7,16 +7,18 @@ #import "NJMappingsController.h" -#import "EnjoyableApplicationDelegate.h" #import "NJMapping.h" #import "NJMappingsController.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]) @@ -40,6 +47,16 @@ 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 { @@ -48,55 +65,65 @@ 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]; + [self updateInterfaceForCurrentMapping]; [outputController loadCurrent]; - popoverActivate.title = _currentMapping.name; [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]; - [(EnjoyableApplicationDelegate *)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]; - [(EnjoyableApplicationDelegate *)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 { @@ -108,9 +135,7 @@ forTableColumn:(NSTableColumn *)col row:(NSInteger)index { self[index].name = obj; - [self save]; - [tableView reloadData]; - [(EnjoyableApplicationDelegate *)NSApplication.sharedApplication.delegate mappingsChanged]; + [self mappingsChanged]; } - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { @@ -118,28 +143,24 @@ } - (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)loadAllFrom:(NSDictionary*)envelope { - NSArray *storedMappings = envelope[@"mappings"]; +- (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 { NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count]; // have to do two passes in case mapping1 refers to mapping2 via a NJOutputMapping @@ -160,13 +181,11 @@ } if (newMappings.count) { - unsigned current = [envelope[@"selected"] unsignedIntValue]; - if (current >= newMappings.count) - current = 0; _mappings = newMappings; - [tableView reloadData]; - [(EnjoyableApplicationDelegate *)NSApplication.sharedApplication.delegate mappingsChanged]; - [self activateMapping:_mappings[current]]; + if (selected >= newMappings.count) + selected = 0; + [self activateMapping:_mappings[selected]]; + [self mappingsChanged]; } } @@ -201,6 +220,67 @@ 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" ]; @@ -209,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]; - [(EnjoyableApplicationDelegate *)NSApplication.sharedApplication.delegate mappingsChanged]; - [self activateMapping:mapping]; - [outputController 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 { @@ -285,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 @@ -303,7 +319,114 @@ } - (IBAction)mappingPressed:(id)sender { - [popover showRelativeToRect:popoverActivate.bounds ofView:popoverActivate preferredEdge:NSMinXEdge]; + [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