Generalize and share mapping menu (main, status, dock) behavior.
[enjoyable.git] / Classes / NJMappingsController.m
index ea8137f..ad257cf 100644 (file)
 #import "NJMapping.h"
 #import "NJMappingsController.h"
 #import "NJOutput.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;
 #import "NJEvents.h"
 
 #define PB_ROW @"com.yukkurigames.Enjoyable.MappingRow"
 
 @implementation NJMappingsController {
     NSMutableArray *_mappings;
-    NJMapping *manualMapping;
-    NSString *draggingName;
+    NJMapping *_manualMapping;
 }
 
 - (id)init {
     if ((self = [super init])) {
         _mappings = [[NSMutableArray alloc] init];
         _currentMapping = [[NJMapping alloc] initWithName:@"(default)"];
 }
 
 - (id)init {
     if ((self = [super init])) {
         _mappings = [[NSMutableArray alloc] init];
         _currentMapping = [[NJMapping alloc] initWithName:@"(default)"];
-        manualMapping = _currentMapping;
+        _manualMapping = _currentMapping;
         [_mappings addObject:_currentMapping];
     }
     return self;
         [_mappings addObject:_currentMapping];
     }
     return self;
 - (void)mappingsChanged {
     [self save];
     [tableView reloadData];
 - (void)mappingsChanged {
     [self save];
     [tableView reloadData];
-    popoverActivate.title = _currentMapping.name;
     [self updateInterfaceForCurrentMapping];
     [NSNotificationCenter.defaultCenter
         postNotificationName:NJEventMappingListChanged
     [self updateInterfaceForCurrentMapping];
     [NSNotificationCenter.defaultCenter
         postNotificationName:NJEventMappingListChanged
-        object:_mappings];
+                      object:self
+                    userInfo:@{ @"mappings": _mappings,
+                                @"mapping": _currentMapping }];
 }
 
 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
 }
 
 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
                                             count:len];
 }
 
                                             count:len];
 }
 
-- (void)activateMappingForProcess:(NSString *)processName {
-    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)activateMappingForProcess:(NSRunningApplication *)app {
+    NJMapping *oldMapping = _manualMapping;
+    NSArray *names = app.possibleMappingNames;
+    BOOL found = NO;
+    for (NSString *name in names) {
+        NJMapping *mapping = self[name];
+        if (mapping) {
+            [self activateMapping:mapping];
+            found = YES;
+            break;
+        }
+    }
+
+    if (!found) {
+        [self activateMapping:oldMapping];
+        if ([oldMapping.name.lowercaseString isEqualToString:@"@application"]) {
+            oldMapping.name = app.bestMappingName;
+            [self mappingsChanged];
+        }
     }
     }
+    _manualMapping = oldMapping;
 }
 
 - (void)updateInterfaceForCurrentMapping {
     NSUInteger selected = [_mappings indexOfObject:_currentMapping];
 }
 
 - (void)updateInterfaceForCurrentMapping {
     NSUInteger selected = [_mappings indexOfObject:_currentMapping];
-    [removeButton setEnabled:selected != 0];
-    [moveDown setEnabled:selected && selected != _mappings.count - 1];
-    [moveUp setEnabled:selected > 1];
+    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"];
     popoverActivate.title = _currentMapping.name;
     [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selected] byExtendingSelection:NO];
     [NSUserDefaults.standardUserDefaults setInteger:selected forKey:@"selected"];
 
 - (void)activateMapping:(NJMapping *)mapping {
     if (!mapping)
 
 - (void)activateMapping:(NJMapping *)mapping {
     if (!mapping)
-        mapping = manualMapping;
+        mapping = _manualMapping;
     if (mapping == _currentMapping)
         return;
     NSLog(@"Switching to mapping %@.", mapping.name);
     if (mapping == _currentMapping)
         return;
     NSLog(@"Switching to mapping %@.", mapping.name);
-    manualMapping = mapping;
+    _manualMapping = mapping;
     _currentMapping = mapping;
     [self updateInterfaceForCurrentMapping];
     _currentMapping = mapping;
     [self updateInterfaceForCurrentMapping];
-    [outputController loadCurrent];
-    [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingChanged
-                                                      object:_currentMapping];
+    [NSNotificationCenter.defaultCenter
+         postNotificationName:NJEventMappingChanged
+         object:self
+         userInfo:@{ @"mapping": _currentMapping }];
 }
 
 - (IBAction)addPressed:(id)sender {
 }
 
 - (IBAction)addPressed:(id)sender {
 - (void)loadAllFrom:(NSArray *)storedMappings andActivate:(NSUInteger)selected {
     NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count];
 
 - (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
+    // 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) {
     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) {
     }
     
     if (newMappings.count) {
     }
 }
 
     }
 }
 
-- (NJMapping *)mappingWithURL:(NSURL *)url error:(NSError **)error {
-    NSInputStream *stream = [NSInputStream inputStreamWithURL:url];
-    [stream open];
-    NSDictionary *serialization = !*error
-        ? [NSJSONSerialization JSONObjectWithStream:stream options:0 error:error]
-        : nil;
-    [stream close];
-    
-    if (!([serialization isKindOfClass:NSDictionary.class]
-          && [serialization[@"name"] isKindOfClass:NSString.class]
-          && [serialization[@"entries"] isKindOfClass:NSDictionary.class])) {
-        *error = [NSError errorWithDomain:@"Enjoyable"
-                                     code:0
-                              description:@"This isn't a valid mapping file."];
-        return nil;
-    }
-
-    NSDictionary *entries = serialization[@"entries"];
-    NJMapping *mapping = [[NJMapping alloc] initWithName:serialization[@"name"]];
-    for (id key in entries) {
-        NSDictionary *value = entries[key];
-        if ([key isKindOfClass:NSString.class]) {
-            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;
 - (void)addMappingWithContentsOfURL:(NSURL *)url {
     NSWindow *window = popoverActivate.window;
     NSError *error;
                                                        error:&error];
     
     if (mapping && !error) {
                                                        error:&error];
     
     if (mapping && !error) {
-        BOOL conflict = NO;
         NJMapping *mergeInto = self[mapping.name];
         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;
-            }
-        }
+        BOOL conflict = [mergeInto hasConflictWith:mapping];
         
         if (conflict) {
             NSAlert *conflictAlert = [[NSAlert alloc] init];
         
         if (conflict) {
             NSAlert *conflictAlert = [[NSAlert alloc] init];
         }
         
         if (mergeInto) {
         }
         
         if (mergeInto) {
-            [mergeInto.entries addEntriesFromDictionary:mapping.entries];
+            [mergeInto mergeEntriesFrom:mapping];
             mapping = mergeInto;
         } else {
             [_mappings addObject:mapping];
             mapping = mergeInto;
         } else {
             [_mappings addObject:mapping];
 }
 
 - (IBAction)moveUpPressed:(id)sender {
 }
 
 - (IBAction)moveUpPressed:(id)sender {
-    NSUInteger idx = [_mappings indexOfObject:_currentMapping];
-    if (idx > 1 && idx != NSNotFound) {
-        [_mappings exchangeObjectAtIndex:idx withObjectAtIndex:idx - 1];
+    if ([_mappings moveFirstwards:_currentMapping upTo:1])
         [self mappingsChanged];
         [self mappingsChanged];
-    }
 }
 
 - (IBAction)moveDownPressed:(id)sender {
 }
 
 - (IBAction)moveDownPressed:(id)sender {
-    NSUInteger idx = [_mappings indexOfObject:_currentMapping];
-    if (idx < _mappings.count - 1) {
-        [_mappings exchangeObjectAtIndex:idx withObjectAtIndex:idx + 1];
+    if ([_mappings moveLastwards:_currentMapping])
         [self mappingsChanged];
         [self mappingsChanged];
-    }
 }
 
 - (BOOL)tableView:(NSTableView *)tableView_
 }
 
 - (BOOL)tableView:(NSTableView *)tableView_