Support opening/adding a mapping file directly to import it.
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Thu, 7 Mar 2013 09:40:46 +0000 (10:40 +0100)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Thu, 7 Mar 2013 09:40:46 +0000 (10:40 +0100)
English.lproj/InfoPlist.strings
Enjoyable.xcodeproj/project.pbxproj
EnjoyableApplicationDelegate.m
Info.plist
NJMapping.h
NJMapping.m
NJMappingsController.h
NJMappingsController.m
com.yukkurigames.Enjoyable.mapping.icns [new file with mode: 0644]
icon.icns

index 5e45963c382ba690b781b953a00585212b898ac5..84fe32f201518c6988c9ae2d4a6cf38c31fd2215 100644 (file)
Binary files a/English.lproj/InfoPlist.strings and b/English.lproj/InfoPlist.strings differ
index e8badf986bcd07907815f933b5d41ff2d354f67b..8342944b8eb3a8c9e14c2710fd6814317b7d4bad 100644 (file)
@@ -37,6 +37,7 @@
                EEAA9CE116E808E600256B64 /* NSMutableArray+MoveObject.m in Sources */ = {isa = PBXBuildFile; fileRef = EEAA9CE016E808E600256B64 /* NSMutableArray+MoveObject.m */; };
                EEAA9CE416E816C800256B64 /* NSFileManager+UniqueNames.m in Sources */ = {isa = PBXBuildFile; fileRef = EEAA9CE316E816C800256B64 /* NSFileManager+UniqueNames.m */; };
                EEAA9CE716E81C8400256B64 /* NSString+FixFilename.m in Sources */ = {isa = PBXBuildFile; fileRef = EEAA9CE616E81C8400256B64 /* NSString+FixFilename.m */; };
+               EEDFE52816E8957200CD27E0 /* com.yukkurigames.Enjoyable.mapping.icns in Resources */ = {isa = PBXBuildFile; fileRef = EEDFE52716E8957200CD27E0 /* com.yukkurigames.Enjoyable.mapping.icns */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
@@ -98,6 +99,7 @@
                EEAA9CE316E816C800256B64 /* NSFileManager+UniqueNames.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager+UniqueNames.m"; sourceTree = "<group>"; };
                EEAA9CE516E81C8400256B64 /* NSString+FixFilename.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+FixFilename.h"; sourceTree = "<group>"; };
                EEAA9CE616E81C8400256B64 /* NSString+FixFilename.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+FixFilename.m"; sourceTree = "<group>"; };
+               EEDFE52716E8957200CD27E0 /* com.yukkurigames.Enjoyable.mapping.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = com.yukkurigames.Enjoyable.mapping.icns; sourceTree = "<group>"; };
                EEF86B7316E2241000674B87 /* NJInputPathElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NJInputPathElement.h; sourceTree = "<group>"; };
                EEF86B7416E298CD00674B87 /* NJEvents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NJEvents.h; sourceTree = "<group>"; };
 /* End PBXFileReference section */
                29B97317FDCFA39411CA2CEA /* Resources */ = {
                        isa = PBXGroup;
                        children = (
+                               EEDFE52716E8957200CD27E0 /* com.yukkurigames.Enjoyable.mapping.icns */,
                                EE03150C16E63481002B2DCE /* Help */,
                                D5617A080FAEAF8300928B3A /* icon.icns */,
                                8D1107310486CEB800E47090 /* Info.plist */,
                                1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */,
                                D5F80A9D0FB0A2FF0006A4DE /* icon.icns in Resources */,
                                EE03150D16E63481002B2DCE /* Help in Resources */,
+                               EEDFE52816E8957200CD27E0 /* com.yukkurigames.Enjoyable.mapping.icns in Resources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index 5f79f20f6cf92ac500e664497d99b729597bd226..59c3025e5d137cf6bc5353eb4df501d868f3a6cc 100644 (file)
     return menu;
 }
 
+- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
+    NSURL *url = [NSURL fileURLWithPath:filename];
+    [self.mappingsController addMappingWithContentsOfURL:url];
+    return YES;
+}
+
+
 @end
index a27f386be9212d7f8eff15116e6f729b0084a376..c7a50270b28d6f6a47c289577f8ca3041b80a737 100644 (file)
@@ -2,6 +2,51 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
+       <key>UTExportedTypeDeclarations</key>
+       <array>
+               <dict>
+                       <key>UTTypeIconFile</key>
+                       <string>com.yukkurigames.Enjoyable.mapping.icns</string>
+                       <key>UTTypeDescription</key>
+                       <string>Enjoyable Mapping</string>
+                       <key>UTTypeConformsTo</key>
+                       <array>
+                               <string>public.data</string>
+                       </array>
+                       <key>UTTypeIdentifier</key>
+                       <string>com.yukkurigames.Enjoyable.mapping</string>
+                       <key>UTTypeTagSpecification</key>
+                       <dict>
+                               <key>public.filename-extension</key>
+                               <array>
+                                       <string>enjoyable</string>
+                               </array>
+                               <key>public.mime-type</key>
+                               <string>application/json</string>
+                       </dict>
+               </dict>
+       </array>
+       <key>CFBundleDocumentTypes</key>
+       <array>
+               <dict>
+                       <key>CFBundleTypeIconFile</key>
+                       <string>com.yukkurigames.Enjoyable.mapping.icns</string>
+                       <key>CFBundleTypeExtensions</key>
+                       <array>
+                               <string>enjoyable</string>
+                       </array>
+                       <key>CFBundleTypeRole</key>
+                       <string>Editor</string>
+                       <key>LSItemContentTypes</key>
+                       <array>
+                               <string>com.yukkurigames.Enjoyable.mapping</string>
+                       </array>
+                       <key>CFBundleTypeName</key>
+                       <string>com.yukkurigames.Enjoyable.mapping</string>
+                       <key>LSHandlerRank</key>
+                       <string>Owner</string>
+               </dict>
+       </array>
        <key>CFBundleDevelopmentRegion</key>
        <string>English</string>
        <key>CFBundleExecutable</key>
index 069f132ac9624d9b85e3471ab7b733e6599f6777..5653745a0562fd7532c5f82949ddb3e40978f1b2 100644 (file)
@@ -20,4 +20,6 @@
 - (NSDictionary *)serialize;
 - (BOOL)writeToURL:(NSURL *)url error:(NSError **)error;
 
++ (id)mappingWithContentsOfURL:(NSURL *)url mappings:(NSArray *)mappings error:(NSError **)error;
+
 @end
index 7df321ce232a0da3e96806464fa7cf00cea77be5..8cc572407945ac02b21a1171123e94e884a16995 100644 (file)
@@ -8,6 +8,7 @@
 #import "NJMapping.h"
 
 #import "NJInput.h"
+#import "NJOutput.h"
 
 @implementation NJMapping
 
     return success;
 }
 
++ (id)mappingWithContentsOfURL:(NSURL *)url mappings:(NSArray *)mappings error:(NSError **)error {
+    NSInputStream *stream = [NSInputStream inputStreamWithURL:url];
+    [stream open];
+    NSDictionary *serialization = stream && !*error
+        ? [NSJSONSerialization JSONObjectWithStream:stream options:0 error:error]
+        : nil;
+    [stream close];
+    
+    if (!serialization && error)
+        return nil;
+    
+    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;
+}
+
 @end
index 2f7ede079784d573b5acf4cabe0463b6d1c92728..b252904264e07aee2801a69cac1a7cb1b78789fa 100644 (file)
@@ -29,6 +29,7 @@
 
 - (NJMapping *)objectForKeyedSubscript:(NSString *)name;
 - (NJMapping *)objectAtIndexedSubscript:(NSUInteger)idx;
+- (void)addMappingWithContentsOfURL:(NSURL *)url;
 - (void)activateMapping:(NJMapping *)mapping;
 - (void)activateMappingForProcess:(NSString *)processName;
 - (void)save;
index ff9b42c68bfefa4606a6a1efce34522b054a45e6..0adb1f9868fa644b16b346b907323b2b31e42a8e 100644 (file)
@@ -32,8 +32,8 @@
 }
 
 - (void)awakeFromNib {
-    [tableView registerForDraggedTypes:@[PB_ROW]];
-    [tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; 
+    [tableView registerForDraggedTypes:@[PB_ROW, NSURLPboardType]];
+    [tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
 }
 
 - (NJMapping *)objectForKeyedSubscript:(NSString *)name {
     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" ];
                   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];
-                          }
-                          
-                          [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];
-                      }
+                      [self addMappingWithContentsOfURL:panel.URL];
                   }];
-     
+    
 }
 
 - (void)exportPressed:(id)sender {
     }
 }
 
-- (BOOL)tableView:(NSTableView *)tableView
+- (BOOL)tableView:(NSTableView *)tableView_
        acceptDrop:(id <NSDraggingInfo>)info
               row:(NSInteger)row
     dropOperation:(NSTableViewDropOperation)dropOperation {
         [_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;
     }
     NSPasteboard *pboard = [info draggingPasteboard];
     if ([pboard.types containsObject:PB_ROW]) {
         [tableView_ setDropRow:MAX(1, row) dropOperation:NSTableViewDropAbove];
-        return NSDragOperationGeneric;
+        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;
     }
diff --git a/com.yukkurigames.Enjoyable.mapping.icns b/com.yukkurigames.Enjoyable.mapping.icns
new file mode 100644 (file)
index 0000000..027b5e2
Binary files /dev/null and b/com.yukkurigames.Enjoyable.mapping.icns differ
index d9a900d07a04e029f9f90f07206e77eea061c7c5..c7e3f3684ac0e0c1766e8ee48e70454df603feb0 100644 (file)
Binary files a/icon.icns and b/icon.icns differ