Add some categories to better handle menus with represented objects, then use them...
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Mon, 4 Mar 2013 22:00:21 +0000 (23:00 +0100)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Mon, 4 Mar 2013 22:00:21 +0000 (23:00 +0100)
Enjoyable.xcodeproj/project.pbxproj
EnjoyableApplicationDelegate.m
Enjoyable_Prefix.pch
NJMappingsController.m
NJOutputController.m
NSMenu+RepresentedObjectAccessors.h [new file with mode: 0644]
NSMenu+RepresentedObjectAccessors.m [new file with mode: 0644]

index ccdd256..c2f3246 100644 (file)
@@ -34,6 +34,7 @@
                D5F80A9D0FB0A2FF0006A4DE /* icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = D5617A080FAEAF8300928B3A /* icon.icns */; };
                EE1D7C9216E01E7000B000EB /* NSView+FirstResponder.m in Sources */ = {isa = PBXBuildFile; fileRef = EE1D7C9116E01E7000B000EB /* NSView+FirstResponder.m */; };
                EE1D7C9616E0ECCF00B000EB /* NSError+Description.m in Sources */ = {isa = PBXBuildFile; fileRef = EE1D7C9516E0ECCF00B000EB /* NSError+Description.m */; };
+               EE96929416E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m in Sources */ = {isa = PBXBuildFile; fileRef = EE96929316E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
@@ -89,6 +90,8 @@
                EE1D7C9116E01E7000B000EB /* NSView+FirstResponder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSView+FirstResponder.m"; sourceTree = "<group>"; };
                EE1D7C9416E0ECCF00B000EB /* NSError+Description.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+Description.h"; sourceTree = "<group>"; };
                EE1D7C9516E0ECCF00B000EB /* NSError+Description.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+Description.m"; sourceTree = "<group>"; };
+               EE96929216E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMenu+RepresentedObjectAccessors.h"; sourceTree = "<group>"; };
+               EE96929316E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMenu+RepresentedObjectAccessors.m"; 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 */
                                EE1D7C9516E0ECCF00B000EB /* NSError+Description.m */,
                                EE1D7C9016E01E7000B000EB /* NSView+FirstResponder.h */,
                                EE1D7C9116E01E7000B000EB /* NSView+FirstResponder.m */,
+                               EE96929216E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.h */,
+                               EE96929316E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m */,
                        );
                        name = Categories;
                        sourceTree = "<group>";
                                8BEFADA015C476DC00823AEC /* NJOutputSwitchMouseMode.m in Sources */,
                                EE1D7C9216E01E7000B000EB /* NSView+FirstResponder.m in Sources */,
                                EE1D7C9616E0ECCF00B000EB /* NSError+Description.m in Sources */,
+                               EE96929416E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index 8afbb5b..388103b 100644 (file)
@@ -13,9 +13,7 @@
 #import "NJOutputController.h"
 #import "NJEvents.h"
 
-@implementation EnjoyableApplicationDelegate {
-    NSInteger mappingsMenuIndex;
-}
+@implementation EnjoyableApplicationDelegate
 
 - (void)didSwitchApplication:(NSNotification *)notification {
     NSRunningApplication *currentApp = notification.userInfo[NSWorkspaceApplicationKey];
@@ -44,9 +42,6 @@
         name:NJEventTranslationDeactivated
         object:nil];
 
-    while (![dockMenuBase itemAtIndex:mappingsMenuIndex++].tag);
-    
-    self.outputController.enabled = NO;
     [self.inputController setup];
     [self.mappingsController load];
 }
 
 - (void)mappingListDidChange:(NSNotification *)note {
     NSArray *mappings = note.object;
-    NSInteger removeFrom = mappingsMenuIndex;
-    while (dockMenuBase.numberOfItems > removeFrom)
-        [dockMenuBase removeItemAtIndex:dockMenuBase.numberOfItems - 1];
+    while (dockMenuBase.lastItem.representedObject)
+        [dockMenuBase removeLastItem];
     int added = 0;
     for (NJMapping *mapping in mappings) {
         NSString *keyEquiv = ++added < 10 ? @(added).stringValue : @"";
-        [dockMenuBase addItemWithTitle:mapping.name
-                                action:@selector(chooseMapping:)
-                         keyEquivalent:keyEquiv];
-        
+        NSMenuItem *item = [dockMenuBase addItemWithTitle:mapping.name
+                                                   action:@selector(chooseMapping:)
+                                            keyEquivalent:keyEquiv];
+        item.representedObject = mapping;
     }
     [_outputController refreshMappings];
 }
 
 - (void)mappingDidChange:(NSNotification *)note {
     NJMapping *current = note.object;
-    NSArray *mappings = self.mappingsController.mappings;
-    for (NSUInteger i = 0; i < mappings.count; ++i)
-        [dockMenuBase itemAtIndex:i + mappingsMenuIndex].state = mappings[i] == current;
+    for (NSMenuItem *item in dockMenuBase.itemArray)
+        if (item.representedObject)
+            item.state = item.representedObject == current;
 }
 
-- (void)chooseMapping:(id)sender {
-    NSInteger idx = [dockMenuBase indexOfItem:sender] - mappingsMenuIndex;
-    NJMapping *chosen = self.mappingsController[idx];
-    [_mappingsController activateMapping:chosen];
+- (void)chooseMapping:(NSMenuItem *)sender {
+    NJMapping *chosen = sender.representedObject;
+    [self.mappingsController activateMapping:chosen];
 }
 
 @end
index 86806fe..1057a2c 100644 (file)
@@ -8,5 +8,6 @@
 
 #import <IOKit/hid/IOHIDLib.h>
 
-#import "NSView+FirstResponder.h"
 #import "NSError+Description.h"
+#import "NSMenu+RepresentedObjectAccessors.h"
+#import "NSView+FirstResponder.h"
index e2ac93c..26ef35f 100644 (file)
@@ -42,6 +42,7 @@
 - (void)mappingsChanged {
     [self save];
     [tableView reloadData];
+    popoverActivate.title = _currentMapping.name;
     [NSNotificationCenter.defaultCenter
         postNotificationName:NJEventMappingListChanged
         object:_mappings];
 - (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];
     [outputController loadCurrent];
     popoverActivate.title = _currentMapping.name;
+    NSUInteger selected = [_mappings indexOfObject:mapping];
+    [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selected] byExtendingSelection:NO];
+    [NSUserDefaults.standardUserDefaults setInteger:selected forKey:@"selected"];
     [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingChanged
                                                       object:_currentMapping];
-    [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:[_mappings indexOfObject:mapping]] byExtendingSelection:NO];
 }
 
 - (IBAction)addPressed:(id)sender {
 }
 
 - (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)load {
+    NSUInteger selected = [NSUserDefaults.standardUserDefaults integerForKey:@"selected"];
+    NSArray *mappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"];
+    [self loadAllFrom:mappings andActivate:selected];
 }
 
-- (void)loadAllFrom:(NSDictionary*)envelope {
-    NSArray *storedMappings = envelope[@"mappings"];
+- (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
     }
     
     if (newMappings.count) {
-        unsigned current = [envelope[@"selected"] unsignedIntValue];
-        if (current >= newMappings.count)
-            current = 0;
         _mappings = newMappings;
+        if (selected >= newMappings.count)
+            selected = 0;
         [self mappingsChanged];
-        [self activateMapping:_mappings[current]];
+        [self activateMapping:_mappings[selected]];
     }
 }
 
index ed8fe2d..5ceccba 100644 (file)
         keyInput.keyCode = [(NJOutputKeyPress*)output vk];
     } else if ([output isKindOfClass:NJOutputMapping.class]) {
         [radioButtons selectCellAtRow:2 column:0];
-        NSUInteger idx = [mappingPopup indexOfItemWithRepresentedObject:[(NJOutputMapping *)output mapping]];
-        if (idx == NSNotFound) {
+        NSMenuItem *item = [mappingPopup itemWithRepresentedObject:[(NJOutputMapping *)output mapping]];
+        [mappingPopup selectItem:item];
+        if (!item)
             [radioButtons selectCellAtRow:self.enabled ? 0 : -1 column:0];
-            [mappingPopup selectItemAtIndex:-1];
-        } else
-            [mappingPopup selectItemAtIndex:idx];
     }
     else if ([output isKindOfClass:NJOutputMouseMove.class]) {
         [radioButtons selectCellAtRow:3 column:0];
         item.representedObject = mapping;
         [mappingPopup.menu addItem:item];
     }
-    [mappingPopup selectItemAtIndex:[mappingPopup indexOfItemWithRepresentedObject:current]];
+    [mappingPopup selectItemWithRepresentedObject:current];
 }
 
 @end
diff --git a/NSMenu+RepresentedObjectAccessors.h b/NSMenu+RepresentedObjectAccessors.h
new file mode 100644 (file)
index 0000000..ecb28a6
--- /dev/null
@@ -0,0 +1,47 @@
+//
+//  NSMenu+RepresentedObjectAccessors.h
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/4/13.
+//
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface NSMenu (RepresentedObjectAccessors)
+    // Helpers for using represented objects in menu items.
+
+- (NSMenuItem *)itemWithRepresentedObject:(id)object;
+    // Returns the first menu item in the receiver that has a given
+    // represented object.
+
+- (void)removeItemWithRepresentedObject:(id)object;
+    // Removes the first menu item representing the given object in the
+    // receiver.
+    //
+    // After it removes the menu item, this method posts an
+    // NSMenuDidRemoveItemNotification.
+
+- (NSMenuItem *)lastItem;
+    // Return the last menu item in the receiver, or nil if the menu
+    // has no items.
+
+- (void)removeLastItem;
+    // Removes the last menu item in the receiver, if there is one.
+    //
+    // After and if it removes the menu item, this method posts an
+    // NSMenuDidRemoveItemNotification.
+
+@end
+
+@interface NSPopUpButton (RepresentedObjectAccessors)
+
+- (NSMenuItem *)itemWithRepresentedObject:(id)object;
+    // Returns the first item in the receiver's menu that has a given
+    // represented object.
+
+- (void)selectItemWithRepresentedObject:(id)object;
+    // Selects the first item in the receiver's menu that has a give
+    // represented object.
+
+@end
diff --git a/NSMenu+RepresentedObjectAccessors.m b/NSMenu+RepresentedObjectAccessors.m
new file mode 100644 (file)
index 0000000..a63f083
--- /dev/null
@@ -0,0 +1,48 @@
+//
+//  NSMenu+RepresentedObjectAccessors.m
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/4/13.
+//
+//
+
+#import "NSMenu+RepresentedObjectAccessors.h"
+
+@implementation NSMenu (RepresentedObjectAccessors)
+
+- (NSMenuItem *)itemWithRepresentedObject:(id)object {
+    for (NSMenuItem *item in self.itemArray)
+        if ([item.representedObject isEqual:object])
+            return item;
+    return nil;
+}
+
+- (void)removeItemWithRepresentedObject:(id)object {
+    NSInteger idx = [self indexOfItemWithRepresentedObject:object];
+    if (idx != -1)
+        [self removeItemAtIndex:idx];
+}
+
+- (NSMenuItem *)lastItem {
+    return self.itemArray.lastObject;
+}
+
+- (void)removeLastItem {
+    if (self.numberOfItems)
+        [self removeItemAtIndex:self.numberOfItems - 1];
+}
+
+@end
+
+@implementation NSPopUpButton (RepresentedObjectAccessors)
+
+- (NSMenuItem *)itemWithRepresentedObject:(id)object {
+    return [self.menu itemWithRepresentedObject:object];
+}
+
+- (void)selectItemWithRepresentedObject:(id)object {
+    [self selectItemAtIndex:[self indexOfItemWithRepresentedObject:object]];
+}
+
+
+@end