Generalize and share mapping menu (main, status, dock) behavior.
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Mon, 11 Mar 2013 02:20:29 +0000 (03:20 +0100)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Mon, 11 Mar 2013 02:20:29 +0000 (03:20 +0100)
Classes/EnjoyableApplicationDelegate.h
Classes/EnjoyableApplicationDelegate.m
Classes/NJMappingMenuController.h [new file with mode: 0644]
Classes/NJMappingMenuController.m [new file with mode: 0644]
Classes/NJMappingsController.m
Enjoyable.xcodeproj/project.pbxproj
Info.plist
Resources/English.lproj/MainMenu.xib

index 36374d3..f61578b 100644 (file)
@@ -6,13 +6,14 @@
 //  Copyright 2009 University of Otago. All rights reserved.
 //
 
-@class NJDeviceController;
 @class NJMappingsController;
 
-@interface EnjoyableApplicationDelegate : NSObject <NSApplicationDelegate> {
+#import "NJMappingMenuController.h"
+
+@interface EnjoyableApplicationDelegate : NSObject <NSApplicationDelegate,
+                                                    NJMappingMenuDelegate> {
     IBOutlet NSMenu *dockMenu;
     IBOutlet NSMenu *statusItemMenu;
-    IBOutlet NSMenu *mappingsMenu;
     IBOutlet NSWindow *window;
 }
 
index c66cf6b..b45771b 100644 (file)
         selector:@selector(mappingDidChange:)
         name:NJEventMappingChanged
         object:nil];
-    [NSNotificationCenter.defaultCenter
-        addObserver:self
-        selector:@selector(mappingListDidChange:)
-        name:NJEventMappingListChanged
-        object:nil];
     [NSNotificationCenter.defaultCenter
         addObserver:self
         selector:@selector(eventTranslationActivated:)
@@ -99,8 +94,6 @@
 }
 
 - (void)eventTranslationActivated:(NSNotification *)note {
-    [dockMenu itemAtIndex:0].state = NSOnState;
-    [statusItemMenu itemAtIndex:0].state = NSOnState;
     statusItem.image = [NSImage imageNamed:@"Status Menu Icon"];
     [NSWorkspace.sharedWorkspace.notificationCenter
         addObserver:self
 }
 
 - (void)eventTranslationDeactivated:(NSNotification *)note {
-    [dockMenu itemAtIndex:0].state = NSOffState;
-    [statusItemMenu itemAtIndex:0].state = NSOffState;
     statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
     [NSWorkspace.sharedWorkspace.notificationCenter
         removeObserver:self
         object:nil];
 }
 
-- (void)restoreWindowAndShowMappings:(id)sender {
-    [self restoreToForeground:sender];
-    [self.mappingsController mappingPressed:sender];
-}
-
-- (void)addMappings:(NSArray *)mappings
-             toMenu:(NSMenu *)menu
-           withKeys:(BOOL)withKeys
-            atIndex:(NSInteger)index {
-    static const NSUInteger MAXIMUM_ITEMS = 15;
-    int added = 0;
-    for (NJMapping *mapping in mappings) {
-        NSString *keyEquiv = (++added < 10 && withKeys) ? @(added).stringValue : @"";
-        NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:mapping.name
-                                                      action:@selector(chooseMapping:)
-                                               keyEquivalent:keyEquiv];
-        item.representedObject = mapping;
-        item.state = mapping == self.mappingsController.currentMapping;
-        [menu insertItem:item atIndex:index++];
-        if (added == MAXIMUM_ITEMS && self.mappingsController.mappings.count > MAXIMUM_ITEMS + 1) {
-            NSString *msg = [NSString stringWithFormat:@"(and %lu more…)",
-                             self.mappingsController.mappings.count - MAXIMUM_ITEMS];
-            NSMenuItem *end = [[NSMenuItem alloc] initWithTitle:msg
-                                                         action:@selector(restoreWindowAndShowMappings:)
-                                                  keyEquivalent:@""];
-            // There must be a represented object here so the item gets
-            // removed correctly when the menus are regenerated.
-            end.representedObject = mappings;
-            end.target = self;
-            [menu insertItem:end atIndex:index++];
-            break;
-        }
-    }    
-}
-
-- (void)mappingListDidChange:(NSNotification *)note {
-    NSArray *mappings = note.userInfo[@"mappings"];
-    while (mappingsMenu.lastItem.representedObject)
-        [mappingsMenu removeLastItem];
-    [self addMappings:mappings
-               toMenu:mappingsMenu
-             withKeys:YES
-              atIndex:mappingsMenu.numberOfItems];
-    while ([statusItemMenu itemAtIndex:2].representedObject)
-        [statusItemMenu removeItemAtIndex:2];
-    [self addMappings:mappings toMenu:statusItemMenu withKeys:NO atIndex:2];
-}
-
 - (void)mappingDidChange:(NSNotification *)note {
-    NJMapping *current = note.userInfo[@"mapping"];
-    for (NSMenuItem *item in mappingsMenu.itemArray)
-        if (item.representedObject)
-            item.state = item.representedObject == current;
-    for (NSMenuItem *item in statusItemMenu.itemArray)
-        if (item.representedObject)
-            item.state = item.representedObject == current;
-    
     if (!window.isVisible)
         for (int i = 0; i < 4; ++i)
             [self performSelector:@selector(flashStatusItem)
                        afterDelay:0.2 * i];
 }
 
-- (void)chooseMapping:(NSMenuItem *)sender {
-    NJMapping *chosen = sender.representedObject;
-    [self.mappingsController activateMapping:chosen];
-}
-
 - (NSMenu *)applicationDockMenu:(NSApplication *)sender {
-    while (dockMenu.lastItem.representedObject)
-        [dockMenu removeLastItem];
-    [self addMappings:self.mappingsController.mappings
-               toMenu:dockMenu
-             withKeys:NO
-              atIndex:dockMenu.numberOfItems];
     return dockMenu;
 }
 
     return YES;
 }
 
+- (void)mappingWasChosen:(NJMapping *)mapping {
+    [self.mappingsController activateMapping:mapping];
+}
+
+- (void)mappingListShouldOpen {
+    [self restoreToForeground:self];
+    [self.mappingsController mappingPressed:self];
+}
+
 
 @end
diff --git a/Classes/NJMappingMenuController.h b/Classes/NJMappingMenuController.h
new file mode 100644 (file)
index 0000000..9c9432a
--- /dev/null
@@ -0,0 +1,50 @@
+//
+//  NJMappingMenuController.h
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/11/13.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@class NJMapping;
+
+@protocol NJMappingMenuDelegate
+
+- (void)mappingWasChosen:(NJMapping *)mapping;
+    // Called when a menu item created by the controller is chosen.
+
+- (void)mappingListShouldOpen;
+    // Called when the "overflow" menu item is chosen, this means the
+    // user should be presented with the list of available mappings.
+
+@end
+
+@interface NJMappingMenuController : NSObject
+    // Mapping menu controllers manage the contents of a menu that
+    // shows a list of all available mappings, as well as the current
+    // event translation state. The menu may have other items in it as
+    // well, but none at an adjacent index that also have NJMappings
+    // as represented objects.
+
+@property (nonatomic, strong) IBOutlet NSMenu *menu;
+    // The menu to put mapping items in.
+
+@property (nonatomic, weak) IBOutlet id <NJMappingMenuDelegate> delegate;
+    // The delegate to inform about requested mapping changes.
+
+@property (nonatomic, assign) NSInteger firstMappingIndex;
+    // The index in the menu to insert mappings into. The menu can
+    // have other dynamic items as long as you update this index as
+    // you add or remove them.
+
+@property (nonatomic, assign) BOOL hasKeyEquivalents;
+    // Whether or not to add key equivalents to the menu items.
+
+@property (nonatomic, strong) IBOutlet NSMenuItem *eventTranslationToggle;
+    // A menu item representing the current event translation state.
+    // This outlet is optional.
+
+
+@end
diff --git a/Classes/NJMappingMenuController.m b/Classes/NJMappingMenuController.m
new file mode 100644 (file)
index 0000000..0fa6183
--- /dev/null
@@ -0,0 +1,111 @@
+//
+//  NJMappingMenuController.m
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/11/13.
+//
+//
+
+#import "NJMappingMenuController.h"
+
+#import "NJEvents.h"
+#import "NJMapping.h"
+
+#define MAXIMUM_MAPPINGS_IN_MENU 15
+
+@implementation NJMappingMenuController
+
+- (id)init {
+    if ((self = [super init])) {
+        NSNotificationCenter *center = NSNotificationCenter.defaultCenter;
+        [center addObserver:self
+                   selector:@selector(mappingsListDidChange:)
+                       name:NJEventMappingListChanged
+                     object:nil];
+        [center addObserver:self
+                   selector:@selector(mappingDidChange:)
+                       name:NJEventMappingChanged
+                     object:nil];
+        [center addObserver:self
+                   selector:@selector(eventTranslationActivated:)
+                       name:NJEventTranslationActivated
+                     object:nil];
+        [center addObserver:self
+                   selector:@selector(eventTranslationDeactivated:)
+                       name:NJEventTranslationDeactivated
+                     object:nil];
+    }
+    return self;
+}
+
+- (void)dealloc {
+    [NSNotificationCenter.defaultCenter removeObserver:self];
+}
+
+- (void)_mappingWasChosen:(NSMenuItem *)sender {
+    NJMapping *mapping = sender.representedObject;
+    [self.delegate mappingWasChosen:mapping];
+}
+
+- (void)_mappingListWasChosen:(NSMenuItem *)sender {
+    [self.delegate mappingListShouldOpen];
+}
+
+- (void)mappingsListDidChange:(NSNotification *)note {
+    NSArray *mappings = note.userInfo[@"mappings"];
+    NJMapping *currentMapping = note.userInfo[@"mapping"];
+    NSMenuItem *toRemove;
+    while (self.menu.numberOfItems > self.firstMappingIndex
+           && (toRemove = [self.menu itemAtIndex:self.firstMappingIndex])
+           && ([toRemove.representedObject isKindOfClass:NJMapping.class]
+               || toRemove.representedObject == self.class))
+        [self.menu removeItemAtIndex:self.firstMappingIndex];    
+    
+    int added = 0;
+    NSUInteger index = self.firstMappingIndex;
+    for (NJMapping *mapping in mappings) {
+        NSString *keyEquiv = (++added < 10 && self.hasKeyEquivalents)
+            ? @(added).stringValue
+            : @"";
+        NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:mapping.name
+                                                      action:@selector(_mappingWasChosen:)
+                                               keyEquivalent:keyEquiv];
+        item.representedObject = mapping;
+        item.state = mapping == currentMapping;
+        item.target = self;
+        [self.menu insertItem:item atIndex:index++];
+        if (added == MAXIMUM_MAPPINGS_IN_MENU
+            && mappings.count > MAXIMUM_MAPPINGS_IN_MENU + 1) {
+            NSString *msg = [NSString stringWithFormat:@"(and %lu more…)",
+                             mappings.count - MAXIMUM_MAPPINGS_IN_MENU];
+            NSMenuItem *end = [[NSMenuItem alloc] initWithTitle:msg
+                                                         action:@selector(_mappingListWasChosen:)
+                                                  keyEquivalent:@""];
+            // There must be a represented object here so the item gets
+            // removed correctly when the menus are regenerated.
+            end.representedObject = self.class;
+            end.target = self;
+            [self.menu insertItem:end atIndex:index];
+            break;
+        }
+    }
+}
+
+- (void)mappingDidChange:(NSNotification *)note {
+    NJMapping *mapping = note.userInfo[@"mapping"];
+    for (NSMenuItem *item in self.menu.itemArray)
+        if ([item.representedObject isKindOfClass:NJMapping.class])
+            item.state = mapping == item.representedObject;
+}
+
+- (void)eventTranslationActivated:(NSNotification *)note {
+    self.eventTranslationToggle.title = @"Disable";
+}
+
+- (void)eventTranslationDeactivated:(NSNotification *)note {
+    self.eventTranslationToggle.title = @"Enable";    
+}
+
+
+
+@end
index ad88d73..ad257cf 100644 (file)
@@ -52,7 +52,8 @@
     [NSNotificationCenter.defaultCenter
         postNotificationName:NJEventMappingListChanged
                       object:self
-                    userInfo:@{ @"mappings": _mappings }];
+                    userInfo:@{ @"mappings": _mappings,
+                                @"mapping": _currentMapping }];
 }
 
 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
index 837baa9..3d6cb56 100644 (file)
@@ -14,6 +14,7 @@
                EE3D897F16EA817E00596D1F /* Status Menu Icon Disabled.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897D16EA817E00596D1F /* Status Menu Icon Disabled.png */; };
                EE3D898016EA817E00596D1F /* Status Menu Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897E16EA817E00596D1F /* Status Menu Icon.png */; };
                EE6A122E16E8F46300EDBD32 /* Icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = EE6A122D16E8F46300EDBD32 /* Icon.icns */; };
+               EED4CE6E16ED692400C65AA8 /* NJMappingMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */; };
                EEE73B1616EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE73B1516EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m */; };
                EEF17D1916E8E21A00D7DC4D /* com.yukkurigames.Enjoyable.mapping.icns in Resources */ = {isa = PBXBuildFile; fileRef = EEF17D1716E8E21A00D7DC4D /* com.yukkurigames.Enjoyable.mapping.icns */; };
                EEF17D1F16E8E23A00D7DC4D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EEF17D1B16E8E23A00D7DC4D /* InfoPlist.strings */; };
@@ -57,6 +58,8 @@
                EE3D897D16EA817E00596D1F /* Status Menu Icon Disabled.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon Disabled.png"; path = "Resources/Status Menu Icon Disabled.png"; sourceTree = "<group>"; };
                EE3D897E16EA817E00596D1F /* Status Menu Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon.png"; path = "Resources/Status Menu Icon.png"; sourceTree = "<group>"; };
                EE6A122D16E8F46300EDBD32 /* Icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Icon.icns; sourceTree = "<group>"; };
+               EED4CE6C16ED692400C65AA8 /* NJMappingMenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NJMappingMenuController.h; path = Classes/NJMappingMenuController.h; sourceTree = "<group>"; };
+               EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NJMappingMenuController.m; path = Classes/NJMappingMenuController.m; sourceTree = "<group>"; };
                EEE73B1416EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSRunningApplication+NJPossibleNames.h"; path = "Categories/NSRunningApplication+NJPossibleNames.h"; sourceTree = "<group>"; };
                EEE73B1516EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSRunningApplication+NJPossibleNames.m"; path = "Categories/NSRunningApplication+NJPossibleNames.m"; sourceTree = "<group>"; };
                EEF17D1716E8E21A00D7DC4D /* com.yukkurigames.Enjoyable.mapping.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = com.yukkurigames.Enjoyable.mapping.icns; path = Resources/com.yukkurigames.Enjoyable.mapping.icns; sourceTree = "<group>"; };
                                EEF17D5916E8E2EF00D7DC4D /* NJOutputMouseMove.m */,
                                EEF17D5A16E8E2EF00D7DC4D /* NJOutputMouseScroll.h */,
                                EEF17D5B16E8E2EF00D7DC4D /* NJOutputMouseScroll.m */,
+                               EED4CE6C16ED692400C65AA8 /* NJMappingMenuController.h */,
+                               EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */,
                        );
                        name = Classes;
                        sourceTree = "<group>";
                                EEF17D6B16E8E2EF00D7DC4D /* NJOutputMouseMove.m in Sources */,
                                EEF17D6C16E8E2EF00D7DC4D /* NJOutputMouseScroll.m in Sources */,
                                EEE73B1616EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m in Sources */,
+                               EED4CE6E16ED692400C65AA8 /* NJMappingMenuController.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index ebc0a1d..bc0cc7d 100644 (file)
@@ -46,7 +46,7 @@
        <key>CFBundleSignature</key>
        <string>????</string>
        <key>CFBundleVersion</key>
-       <string>109</string>
+       <string>122</string>
        <key>LSApplicationCategoryType</key>
        <string>public.app-category.utilities</string>
        <key>NSHumanReadableCopyright</key>
index 2cf1266..0a20186 100644 (file)
@@ -524,7 +524,7 @@ aW5nLg</string>
                                                                                                        <string key="NSFrameSize">{232, 321}</string>
                                                                                                        <reference key="NSSuperview" ref="698362889"/>
                                                                                                        <reference key="NSWindow"/>
-                                                                                                       <reference key="NSNextKeyView" ref="892486973"/>
+                                                                                                       <reference key="NSNextKeyView" ref="1036252745"/>
                                                                                                        <bool key="NSEnabled">YES</bool>
                                                                                                        <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
                                                                                                        <bool key="NSControlAllowsExpansionToolTips">YES</bool>
@@ -654,7 +654,7 @@ aW5nLg</string>
                                                                        <string key="NSFrameSize">{234, 323}</string>
                                                                        <reference key="NSSuperview" ref="734312853"/>
                                                                        <reference key="NSWindow"/>
-                                                                       <reference key="NSNextKeyView" ref="698362889"/>
+                                                                       <reference key="NSNextKeyView" ref="892486973"/>
                                                                        <int key="NSsFlags">150034</int>
                                                                        <reference key="NSVScroller" ref="1036252745"/>
                                                                        <reference key="NSHScroller" ref="892486973"/>
@@ -1483,6 +1483,15 @@ aW5nLg</string>
                                        </object>
                                </array>
                        </object>
+                       <object class="NSCustomObject" id="9563326">
+                               <string key="NSClassName">NJMappingMenuController</string>
+                       </object>
+                       <object class="NSCustomObject" id="427912652">
+                               <string key="NSClassName">NJMappingMenuController</string>
+                       </object>
+                       <object class="NSCustomObject" id="452957078">
+                               <string key="NSClassName">NJMappingMenuController</string>
+                       </object>
                </array>
                <object class="IBObjectContainer" key="IBDocument.Objects">
                        <array class="NSMutableArray" key="connectionRecords">
@@ -2030,6 +2039,78 @@ aW5nLg</string>
                                        </object>
                                        <int key="connectionID">933</int>
                                </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">eventTranslationToggle</string>
+                                               <reference key="source" ref="9563326"/>
+                                               <reference key="destination" ref="632598200"/>
+                                       </object>
+                                       <int key="connectionID">951</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">menu</string>
+                                               <reference key="source" ref="9563326"/>
+                                               <reference key="destination" ref="720053764"/>
+                                       </object>
+                                       <int key="connectionID">952</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">delegate</string>
+                                               <reference key="source" ref="9563326"/>
+                                               <reference key="destination" ref="207406104"/>
+                                       </object>
+                                       <int key="connectionID">956</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">menu</string>
+                                               <reference key="source" ref="427912652"/>
+                                               <reference key="destination" ref="330397624"/>
+                                       </object>
+                                       <int key="connectionID">957</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">delegate</string>
+                                               <reference key="source" ref="427912652"/>
+                                               <reference key="destination" ref="207406104"/>
+                                       </object>
+                                       <int key="connectionID">958</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">eventTranslationToggle</string>
+                                               <reference key="source" ref="427912652"/>
+                                               <reference key="destination" ref="803553950"/>
+                                       </object>
+                                       <int key="connectionID">959</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">delegate</string>
+                                               <reference key="source" ref="452957078"/>
+                                               <reference key="destination" ref="207406104"/>
+                                       </object>
+                                       <int key="connectionID">961</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">menu</string>
+                                               <reference key="source" ref="452957078"/>
+                                               <reference key="destination" ref="388664617"/>
+                                       </object>
+                                       <int key="connectionID">964</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">eventTranslationToggle</string>
+                                               <reference key="source" ref="452957078"/>
+                                               <reference key="destination" ref="679462300"/>
+                                       </object>
+                                       <int key="connectionID">965</int>
+                               </object>
                        </array>
                        <object class="IBMutableOrderedSet" key="objectRecords">
                                <array key="orderedObjects">
@@ -2811,6 +2892,24 @@ aW5nLg</string>
                                                <reference key="object" ref="868379451"/>
                                                <reference key="parent" ref="20704797"/>
                                        </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">950</int>
+                                               <reference key="object" ref="9563326"/>
+                                               <reference key="parent" ref="0"/>
+                                               <string key="objectName">Main Mappings Menu Controller</string>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">953</int>
+                                               <reference key="object" ref="427912652"/>
+                                               <reference key="parent" ref="0"/>
+                                               <string key="objectName">Dock Menu Controller</string>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">960</int>
+                                               <reference key="object" ref="452957078"/>
+                                               <reference key="parent" ref="0"/>
+                                               <string key="objectName">Status Bar Menu Controller</string>
+                                       </object>
                                </array>
                        </object>
                        <dictionary class="NSMutableDictionary" key="flattenedProperties">
@@ -2997,12 +3096,72 @@ aW5nLg</string>
                                <string key="940.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
                                <string key="946.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
                                <string key="947.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <object class="NSMutableDictionary" key="950.IBAttributePlaceholdersKey">
+                                       <string key="NS.key.0">IBUserDefinedRuntimeAttributesPlaceholderName</string>
+                                       <object class="IBUserDefinedRuntimeAttributesPlaceholder" key="NS.object.0">
+                                               <string key="name">IBUserDefinedRuntimeAttributesPlaceholderName</string>
+                                               <reference key="object" ref="9563326"/>
+                                               <array key="userDefinedRuntimeAttributes">
+                                                       <object class="IBUserDefinedRuntimeAttribute">
+                                                               <string key="typeIdentifier">com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.number</string>
+                                                               <string key="keyPath">firstMappingIndex</string>
+                                                               <integer value="6" key="value"/>
+                                                       </object>
+                                                       <object class="IBUserDefinedRuntimeAttribute">
+                                                               <string key="typeIdentifier">com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.boolean</string>
+                                                               <string key="keyPath">hasKeyEquivalents</string>
+                                                               <boolean value="YES" key="value"/>
+                                                       </object>
+                                               </array>
+                                       </object>
+                               </object>
+                               <string key="950.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <object class="NSMutableDictionary" key="953.IBAttributePlaceholdersKey">
+                                       <string key="NS.key.0">IBUserDefinedRuntimeAttributesPlaceholderName</string>
+                                       <object class="IBUserDefinedRuntimeAttributesPlaceholder" key="NS.object.0">
+                                               <string key="name">IBUserDefinedRuntimeAttributesPlaceholderName</string>
+                                               <reference key="object" ref="427912652"/>
+                                               <array key="userDefinedRuntimeAttributes">
+                                                       <object class="IBUserDefinedRuntimeAttribute">
+                                                               <string key="typeIdentifier">com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.number</string>
+                                                               <string key="keyPath">firstMappingIndex</string>
+                                                               <integer value="2" key="value"/>
+                                                       </object>
+                                                       <object class="IBUserDefinedRuntimeAttribute">
+                                                               <string key="typeIdentifier">com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.boolean</string>
+                                                               <string key="keyPath">hasKeyEquivalents</string>
+                                                               <boolean value="NO" key="value"/>
+                                                       </object>
+                                               </array>
+                                       </object>
+                               </object>
+                               <string key="953.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <object class="NSMutableDictionary" key="960.IBAttributePlaceholdersKey">
+                                       <string key="NS.key.0">IBUserDefinedRuntimeAttributesPlaceholderName</string>
+                                       <object class="IBUserDefinedRuntimeAttributesPlaceholder" key="NS.object.0">
+                                               <string key="name">IBUserDefinedRuntimeAttributesPlaceholderName</string>
+                                               <reference key="object" ref="452957078"/>
+                                               <array key="userDefinedRuntimeAttributes">
+                                                       <object class="IBUserDefinedRuntimeAttribute">
+                                                               <string key="typeIdentifier">com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.number</string>
+                                                               <string key="keyPath">firstMappingIndex</string>
+                                                               <integer value="2" key="value"/>
+                                                       </object>
+                                                       <object class="IBUserDefinedRuntimeAttribute">
+                                                               <string key="typeIdentifier">com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.boolean</string>
+                                                               <string key="keyPath">hasKeyEquivalents</string>
+                                                               <boolean value="NO" key="value"/>
+                                                       </object>
+                                               </array>
+                                       </object>
+                               </object>
+                               <string key="960.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
                        </dictionary>
                        <dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
                        <nil key="activeLocalization"/>
                        <dictionary class="NSMutableDictionary" key="localizations"/>
                        <nil key="sourceID"/>
-                       <int key="maxID">949</int>
+                       <int key="maxID">965</int>
                </object>
                <object class="IBClassDescriber" key="IBDocument.Classes">
                        <array class="NSMutableArray" key="referencedPartialClassDescriptions">
@@ -3023,7 +3182,6 @@ aW5nLg</string>
                                        <dictionary class="NSMutableDictionary" key="outlets">
                                                <string key="dockMenu">NSMenu</string>
                                                <string key="mappingsController">NJMappingsController</string>
-                                               <string key="mappingsMenu">NSMenu</string>
                                                <string key="statusItemMenu">NSMenu</string>
                                                <string key="window">NSWindow</string>
                                        </dictionary>
@@ -3036,10 +3194,6 @@ aW5nLg</string>
                                                        <string key="name">mappingsController</string>
                                                        <string key="candidateClassName">NJMappingsController</string>
                                                </object>
-                                               <object class="IBToOneOutletInfo" key="mappingsMenu">
-                                                       <string key="name">mappingsMenu</string>
-                                                       <string key="candidateClassName">NSMenu</string>
-                                               </object>
                                                <object class="IBToOneOutletInfo" key="statusItemMenu">
                                                        <string key="name">statusItemMenu</string>
                                                        <string key="candidateClassName">NSMenu</string>
@@ -3126,6 +3280,33 @@ aW5nLg</string>
                                                <string key="minorKey">./Classes/NJKeyInputField.h</string>
                                        </object>
                                </object>
+                               <object class="IBPartialClassDescription">
+                                       <string key="className">NJMappingMenuController</string>
+                                       <string key="superclassName">NSObject</string>
+                                       <dictionary class="NSMutableDictionary" key="outlets">
+                                               <string key="delegate">id</string>
+                                               <string key="eventTranslationToggle">NSMenuItem</string>
+                                               <string key="menu">NSMenu</string>
+                                       </dictionary>
+                                       <dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
+                                               <object class="IBToOneOutletInfo" key="delegate">
+                                                       <string key="name">delegate</string>
+                                                       <string key="candidateClassName">id</string>
+                                               </object>
+                                               <object class="IBToOneOutletInfo" key="eventTranslationToggle">
+                                                       <string key="name">eventTranslationToggle</string>
+                                                       <string key="candidateClassName">NSMenuItem</string>
+                                               </object>
+                                               <object class="IBToOneOutletInfo" key="menu">
+                                                       <string key="name">menu</string>
+                                                       <string key="candidateClassName">NSMenu</string>
+                                               </object>
+                                       </dictionary>
+                                       <object class="IBClassDescriptionSource" key="sourceIdentifier">
+                                               <string key="majorKey">IBProjectSource</string>
+                                               <string key="minorKey">./Classes/NJMappingMenuController.h</string>
+                                       </object>
+                               </object>
                                <object class="IBPartialClassDescription">
                                        <string key="className">NJMappingsController</string>
                                        <string key="superclassName">NSObject</string>