Remove website, now in yukkurigames.com repository.
[enjoyable.git] / Classes / EnjoyableApplicationDelegate.m
index 41d068a..017926a 100644 (file)
@@ -5,22 +5,21 @@
 //  Created by Sam McCall on 4/05/09.
 //
 
-#import <Sparkle/Sparkle.h>
-
 #import "EnjoyableApplicationDelegate.h"
 
 #import "NJMapping.h"
-#import "NJMappingsController.h"
+#import "NJInput.h"
 #import "NJEvents.h"
 
 @implementation EnjoyableApplicationDelegate {
     NSStatusItem *statusItem;
+    NSMutableArray *_errors;
 }
 
 - (void)didSwitchApplication:(NSNotification *)note {
     NSRunningApplication *activeApp = note.userInfo[NSWorkspaceApplicationKey];
     if (activeApp)
-        [self.mappingsController activateMappingForProcess:activeApp];
+        [self.ic activateMappingForProcess:activeApp];
 }
 
 - (void)applicationWillFinishLaunching:(NSNotification *)notification {
         name:NJEventSimulationStopped
         object:nil];
 
-    [self.mappingsController load];
+    [self.ic load];
     [self.mvc.mappingList reloadData];
     [self.mvc changedActiveMappingToIndex:
-     [self.mappingsController indexOfMapping:
-      self.mappingsController.currentMapping]];
+     [self.ic indexOfMapping:
+      self.ic.currentMapping]];
 
     statusItem = [NSStatusBar.systemStatusBar statusItemWithLength:36];
     statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
 - (void)eventSimulationStarted:(NSNotification *)note {
     self.simulatingEventsButton.state = NSOnState;
     statusItem.image = [NSImage imageNamed:@"Status Menu Icon"];
+    [NSProcessInfo.processInfo
+        disableAutomaticTermination:@"Event simulation running."];
     [NSWorkspace.sharedWorkspace.notificationCenter
         addObserver:self
         selector:@selector(didSwitchApplication:)
 - (void)eventSimulationStopped:(NSNotification *)note {
     self.simulatingEventsButton.state = NSOffState;
     statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
+    [NSProcessInfo.processInfo
+        enableAutomaticTermination:@"Event simulation running."];
     [NSWorkspace.sharedWorkspace.notificationCenter
         removeObserver:self
         name:NSWorkspaceDidActivateApplicationNotification
             [self performSelector:@selector(flashStatusItem)
                        withObject:self
                        afterDelay:0.2 * i];
+    [self loadOutputForInput:self.dvc.selectedHandler];
 }
 
 - (NSMenu *)applicationDockMenu:(NSApplication *)sender {
     return self.dockMenu;
 }
 
+- (void)showNextError {
+    if (!self.window.attachedSheet && _errors.count) {
+        NSError *error = _errors.lastObject;
+        [_errors removeLastObject];
+        [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
+        [self.window makeKeyAndOrderFront:nil];
+        [self.window presentError:error
+                   modalForWindow:self.window
+                         delegate:self
+               didPresentSelector:@selector(didPresentErrorWithRecovery:contextInfo:)
+                      contextInfo:nil];
+    }
+}
+
+- (void)didPresentErrorWithRecovery:(BOOL)didRecover
+                        contextInfo:(void *)contextInfo {
+    [self showNextError];
+}
+
+- (void)presentErrorSheet:(NSError *)error {
+    if (!_errors)
+        _errors = [[NSMutableArray alloc] initWithCapacity:1];
+    [_errors insertObject:error atIndex:0];
+    [self showNextError];
+}
+
 - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
     [self restoreToForeground:sender];
     NSError *error;
     NSURL *URL = [NSURL fileURLWithPath:filename];
     NJMapping *mapping = [NJMapping mappingWithContentsOfURL:URL
                                                        error:&error];
-    if ([self.mappingsController[mapping.name] hasConflictWith:mapping]) {
-        [self promptForMapping:mapping atIndex:self.mappingsController.count];
-    } else if (self.mappingsController[mapping.name]) {
-        [self.mappingsController[mapping.name] mergeEntriesFrom:mapping];
+    if ([[self.ic mappingForKey:mapping.name] hasConflictWith:mapping]) {
+        [self promptForMapping:mapping atIndex:self.ic.mappings.count];
+    } else if ([self.ic  mappingForKey:mapping.name]) {
+        [[self.ic mappingForKey:mapping.name] mergeEntriesFrom:mapping];
     } else if (mapping) {
         [self.mvc beginUpdates];
-        [self.mappingsController addMapping:mapping];
-        [self.mvc addedMappingAtIndex:self.mappingsController.count - 1 startEditing:NO];
+        [self.ic addMapping:mapping];
+        [self.mvc addedMappingAtIndex:self.ic.mappings.count - 1 startEditing:NO];
         [self.mvc endUpdates];
-        [self.mappingsController activateMapping:mapping];
+        [self.ic activateMapping:mapping];
     } else {
-        [self.window presentError:error
-                   modalForWindow:self.window
-                         delegate:nil
-               didPresentSelector:nil
-                      contextInfo:nil];
+        [self presentErrorSheet:error];
     }
     return !!mapping;
 }
 
 - (void)mappingWasChosen:(NJMapping *)mapping {
-    [self.mappingsController activateMapping:mapping];
+    [self.ic activateMapping:mapping];
 }
 
 - (void)mappingListShouldOpen {
     [self.mvc mappingTriggerClicked:self];
 }
 
-- (void)loginItemPromptDidEnd:(NSWindow *)sheet
-                   returnCode:(int)returnCode
-                  contextInfo:(void *)contextInfo {
-    if (returnCode == NSAlertDefaultReturn) {
-        [NSRunningApplication.currentApplication addToLoginItems];
-        // If we're going to automatically start, don't bug the user
-        // about automatic updates next boot - they probably want it,
-        // and if they don't they probably want a prompt for it less.
-        SUUpdater.sharedUpdater.automaticallyChecksForUpdates = YES;
-    }
-}
-
-- (void)loginItemPromptDidDismiss:(NSWindow *)sheet
-                       returnCode:(int)returnCode
-                      contextInfo:(void *)contextInfo {
-    [NSUserDefaults.standardUserDefaults setBool:YES forKey:@"explained login items"];
-    [self.window performClose:sheet];
-}
-
-- (BOOL)windowShouldClose:(NSWindow *)sender {
-    if (sender != self.window
-        || NSRunningApplication.currentApplication.isLoginItem
-        || [NSUserDefaults.standardUserDefaults boolForKey:@"explained login items"])
-        return YES;
-    NSBeginAlertSheet(
-        NSLocalizedString(@"login items prompt", @"alert prompt for adding to login items"),
-        NSLocalizedString(@"login items add button", @"button to add to login items"),
-        NSLocalizedString(@"login items don't add button", @"button to not add to login items"),
-        nil, self.window, self,
-        @selector(loginItemPromptDidEnd:returnCode:contextInfo:),
-        @selector(loginItemPromptDidDismiss:returnCode:contextInfo:),
-        NULL,
-        NSLocalizedString(@"login items explanation", @"a brief explanation of login items")
-        );
-    for (int i = 0; i < 10; ++i)
-        [self performSelector:@selector(flashStatusItem)
-                   withObject:self
-                   afterDelay:0.5 * i];
-    return NO;
-}
-
 - (void)importMappingClicked:(id)sender {
     NSOpenPanel *panel = [NSOpenPanel openPanel];
     panel.allowedFileTypes = @[ @"enjoyable", @"json", @"txt" ];
                       NSError *error;
                       NJMapping *mapping = [NJMapping mappingWithContentsOfURL:panel.URL
                                                                          error:&error];
-                      if ([self.mappingsController[mapping.name] hasConflictWith:mapping]) {
-                          [self promptForMapping:mapping atIndex:self.mappingsController.count];
-                      } else if (self.mappingsController[mapping.name]) {
-                          [self.mappingsController[mapping.name] mergeEntriesFrom:mapping];
+                      if ([[self.ic mappingForKey:mapping.name] hasConflictWith:mapping]) {
+                          [self promptForMapping:mapping atIndex:self.ic.mappings.count];
+                      } else if ([self.ic mappingForKey:mapping.name]) {
+                          [[self.ic mappingForKey:mapping.name] mergeEntriesFrom:mapping];
                       } else if (mapping) {
-                          [self.mappingsController addMapping:mapping];
+                          [self.ic addMapping:mapping];
                       } else {
-                          [self.window presentError:error
-                                     modalForWindow:self.window
-                                           delegate:nil
-                                 didPresentSelector:nil
-                                        contextInfo:nil];
+                          [self presentErrorSheet:error];
                       }
                   }];
     
 - (void)exportMappingClicked:(id)sender {
     NSSavePanel *panel = [NSSavePanel savePanel];
     panel.allowedFileTypes = @[ @"enjoyable" ];
-    NJMapping *mapping = self.mappingsController.currentMapping;
+    NJMapping *mapping = self.ic.currentMapping;
     panel.nameFieldStringValue = [mapping.name stringByFixingPathComponent];
     [panel beginSheetModalForWindow:self.window
                   completionHandler:^(NSInteger result) {
                       [panel close];
                       NSError *error;
                       if (![mapping writeToURL:panel.URL error:&error]) {
-                          [self.window presentError:error
-                                     modalForWindow:self.window
-                                           delegate:nil
-                                 didPresentSelector:nil
-                                        contextInfo:nil];
+                          [self presentErrorSheet:error];
                       }
                   }];
 }
     [alert.window orderOut:nil];
     switch (returnCode) {
         case NSAlertFirstButtonReturn: // Merge
-            [self.mappingsController mergeMapping:newMapping intoMapping:oldMapping];
-            [self.mappingsController activateMapping:oldMapping];
+            [self.ic mergeMapping:newMapping intoMapping:oldMapping];
+            [self.ic activateMapping:oldMapping];
             break;
         case NSAlertThirdButtonReturn: // New Mapping
             [self.mvc beginUpdates];
-            [self.mappingsController addMapping:newMapping];
+            [self.ic addMapping:newMapping];
             [self.mvc addedMappingAtIndex:idx startEditing:YES];
             [self.mvc endUpdates];
-            [self.mappingsController activateMapping:newMapping];
+            [self.ic activateMapping:newMapping];
             break;
         default: // Cancel, other.
             break;
 }
 
 - (void)promptForMapping:(NJMapping *)mapping atIndex:(NSInteger)idx {
-    NJMapping *mergeInto = self.mappingsController[mapping.name];
+    NJMapping *mergeInto = [self.ic mappingForKey:mapping.name];
     NSAlert *conflictAlert = [[NSAlert alloc] init];
     conflictAlert.messageText = NSLocalizedString(@"import conflict prompt", @"Title of import conflict alert");
     conflictAlert.informativeText =
 }
 
 - (NSInteger)numberOfMappings:(NJMappingsViewController *)mvc {
-    return self.mappingsController.count;
+    return self.ic.mappings.count;
 }
 
 - (NJMapping *)mappingsViewController:(NJMappingsViewController *)mvc
                       mappingForIndex:(NSUInteger)idx {
-    return self.mappingsController[idx];
+    return self.ic.mappings[idx];
 }
 
 - (void)mappingsViewController:(NJMappingsViewController *)mvc
           renameMappingAtIndex:(NSInteger)index
                         toName:(NSString *)name {
-    [self.mappingsController renameMapping:self.mappingsController[index]
-                                        to:name];
+    [self.ic renameMapping:self.ic.mappings[index] to:name];
 }
 
 - (BOOL)mappingsViewController:(NJMappingsViewController *)mvc
        canMoveMappingFromIndex:(NSInteger)fromIdx
                        toIndex:(NSInteger)toIdx {
     return fromIdx != toIdx && fromIdx != 0 && toIdx != 0
-            && toIdx < (NSInteger)self.mappingsController.count;
+            && toIdx < (NSInteger)self.ic.mappings.count;
 }
 
 - (void)mappingsViewController:(NJMappingsViewController *)mvc
                        toIndex:(NSInteger)toIdx {
     [mvc beginUpdates];
     [mvc.mappingList moveRowAtIndex:fromIdx toIndex:toIdx];
-    [self.mappingsController moveMoveMappingFromIndex:fromIdx toIndex:toIdx];
+    [self.ic moveMoveMappingFromIndex:fromIdx toIndex:toIdx];
     [mvc endUpdates];
 }
 
           removeMappingAtIndex:(NSInteger)idx {
     [mvc beginUpdates];
     [mvc removedMappingAtIndex:idx];
-    [self.mappingsController removeMappingAtIndex:idx];
+    [self.ic removeMappingAtIndex:idx];
     [mvc endUpdates];
 }
 
                          error:(NSError **)error {
     NJMapping *mapping = [NJMapping mappingWithContentsOfURL:url
                                                        error:error];
-    if ([self.mappingsController[mapping.name] hasConflictWith:mapping]) {
+    if ([[self.ic mappingForKey:mapping.name] hasConflictWith:mapping]) {
         [self promptForMapping:mapping atIndex:index];
-    } else if (self.mappingsController[mapping.name]) {
-        [self.mappingsController[mapping.name] mergeEntriesFrom:mapping];
+    } else if ([self.ic mappingForKey:mapping.name]) {
+        [[self.ic mappingForKey:mapping.name] mergeEntriesFrom:mapping];
     } else if (mapping) {
         [self.mvc beginUpdates];
         [self.mvc addedMappingAtIndex:index startEditing:NO];
-        [self.mappingsController insertMapping:mapping atIndex:index];
+        [self.ic insertMapping:mapping atIndex:index];
         [self.mvc endUpdates];
     }
     return !!mapping;
 - (void)mappingsViewController:(NJMappingsViewController *)mvc
                     addMapping:(NJMapping *)mapping {
     [mvc beginUpdates];
-    [mvc addedMappingAtIndex:self.mappingsController.count startEditing:YES];
-    [self.mappingsController addMapping:mapping];
+    [mvc addedMappingAtIndex:self.ic.mappings.count startEditing:YES];
+    [self.ic addMapping:mapping];
     [mvc endUpdates];
-    [self.mappingsController activateMapping:mapping];
+    [self.ic activateMapping:mapping];
 }
 
 - (void)mappingsViewController:(NJMappingsViewController *)mvc
            choseMappingAtIndex:(NSInteger)idx {
-    [self.mappingsController activateMapping:self.mappingsController[idx]];
+    [self.ic activateMapping:self.ic.mappings[idx]];
 }
 
 - (id)deviceViewController:(NJDeviceViewController *)dvc
              elementForUID:(NSString *)uid {
-    return self.deviceController[uid];
+    return [self.ic elementForUID:uid];
+}
+
+- (void)loadOutputForInput:(NJInput *)input {
+    NJOutput *output = self.ic.currentMapping[input];
+    [self.oc loadOutput:output forInput:input];
 }
 
 - (void)deviceViewControllerDidSelectNothing:(NJDeviceViewController *)dvc {
-    [self.outputController loadInput:dvc.selectedHandler];
+    [self loadOutputForInput:nil];
 }
 
 - (void)deviceViewController:(NJDeviceViewController *)dvc
              didSelectBranch:(NJInputPathElement *)handler {
-    [self.outputController loadInput:dvc.selectedHandler];
+    [self loadOutputForInput:dvc.selectedHandler];
 }
 
 - (void)deviceViewController:(NJDeviceViewController *)dvc
             didSelectHandler:(NJInputPathElement *)handler {
-    [self.outputController loadInput:dvc.selectedHandler];
+    [self loadOutputForInput:dvc.selectedHandler];
 }
 
 - (void)deviceViewController:(NJDeviceViewController *)dvc
              didSelectDevice:(NJInputPathElement *)device {
-    [self.outputController loadInput:dvc.selectedHandler];
+    [self loadOutputForInput:dvc.selectedHandler];
 }
 
-- (void)deviceController:(NJDeviceController *)dc
-            didAddDevice:(NJDevice *)device {
-    [self.dvc addedDevice:device atIndex:dc.count - 1];
+- (void)inputController:(NJInputController *)ic
+           didAddDevice:(NJDevice *)device {
+    [self.dvc addedDevice:device atIndex:ic.devices.count - 1];
 }
 
-- (void)deviceController:(NJDeviceController *)dc
 didRemoveDeviceAtIndex:(NSInteger)idx {
+- (void)inputController:(NJInputController *)ic
+ didRemoveDeviceAtIndex:(NSInteger)idx {
     [self.dvc removedDeviceAtIndex:idx];
 }
 
-- (void)deviceControllerDidStartHID:(NJDeviceController *)dc {
+- (void)inputControllerDidStartHID:(NJInputController *)ic {
     [self.dvc hidStarted];
 }
 
-- (void)deviceControllerDidStopHID:(NJDeviceController *)dc {
+- (void)inputControllerDidStopHID:(NJInputController *)ic {
     [self.dvc hidStopped];
 }
 
-- (void)deviceController:(NJDeviceController *)dc didInput:(NJInput *)input {
-    [self.outputController loadInput:input];
-    [self.outputController focusKey];
+- (void)inputController:(NJInputController *)ic didInput:(NJInput *)input {
+    [self.dvc expandAndSelectItem:input];
+    [self loadOutputForInput:input];
+    [self.oc focusKey];
 }
 
-- (void)deviceController:(NJDeviceController *)dc didError:(NSError *)error {
-    // Since the error shows the window, it can trigger another attempt
-    // to re-open the HID manager, which will also probably fail and error,
-    // so don't bother repeating ourselves.
-    if (!self.window.attachedSheet) {
-        [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
-        [self.window makeKeyAndOrderFront:nil];
-        [self.window presentError:error
-                   modalForWindow:self.window
-                         delegate:nil
-               didPresentSelector:nil
-                      contextInfo:nil];
-    }
+- (void)inputController:(NJInputController *)ic didError:(NSError *)error {
+    [self presentErrorSheet:error];
 }
 
 - (NSInteger)numberOfDevicesInDeviceList:(NJDeviceViewController *)dvc {
-    return self.deviceController.count;
+    return self.ic.devices.count;
 }
 
 - (NJDevice *)deviceViewController:(NJDeviceViewController *)dvc
                     deviceForIndex:(NSUInteger)idx {
-    return self.deviceController[idx];
+    return self.ic.devices[idx];
 }
 
 - (IBAction)simulatingEventsChanged:(NSButton *)sender {
-    self.deviceController.simulatingEvents = sender.state == NSOnState;
+    self.ic.simulatingEvents = sender.state == NSOnState;
+}
+
+- (void)outputViewController:(NJOutputViewController *)ovc
+                   setOutput:(NJOutput *)output
+                    forInput:(NJInput *)input {
+    self.ic.currentMapping[input] = output;
+    [self.ic save];
+}
+
+- (NJMapping *)outputViewController:(NJOutputViewController *)ovc
+                    mappingForIndex:(NSUInteger)index {
+    return self.ic.mappings[index];
 }
 
 @end