From b46786face17680799d7fd9795dead8a801ae9c1 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Mon, 4 Mar 2013 23:00:21 +0100 Subject: [PATCH] Add some categories to better handle menus with represented objects, then use them for simpler menu handling. --- Enjoyable.xcodeproj/project.pbxproj | 6 ++++ EnjoyableApplicationDelegate.m | 33 ++++++++------------ Enjoyable_Prefix.pch | 3 +- NJMappingsController.m | 36 +++++++++++----------- NJOutputController.m | 10 +++--- NSMenu+RepresentedObjectAccessors.h | 47 ++++++++++++++++++++++++++++ NSMenu+RepresentedObjectAccessors.m | 48 +++++++++++++++++++++++++++++ 7 files changed, 138 insertions(+), 45 deletions(-) create mode 100644 NSMenu+RepresentedObjectAccessors.h create mode 100644 NSMenu+RepresentedObjectAccessors.m diff --git a/Enjoyable.xcodeproj/project.pbxproj b/Enjoyable.xcodeproj/project.pbxproj index ccdd256..c2f3246 100644 --- a/Enjoyable.xcodeproj/project.pbxproj +++ b/Enjoyable.xcodeproj/project.pbxproj @@ -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 = ""; }; EE1D7C9416E0ECCF00B000EB /* NSError+Description.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+Description.h"; sourceTree = ""; }; EE1D7C9516E0ECCF00B000EB /* NSError+Description.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+Description.m"; sourceTree = ""; }; + EE96929216E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMenu+RepresentedObjectAccessors.h"; sourceTree = ""; }; + EE96929316E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMenu+RepresentedObjectAccessors.m"; sourceTree = ""; }; EEF86B7316E2241000674B87 /* NJInputPathElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NJInputPathElement.h; sourceTree = ""; }; EEF86B7416E298CD00674B87 /* NJEvents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NJEvents.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -227,6 +230,8 @@ EE1D7C9516E0ECCF00B000EB /* NSError+Description.m */, EE1D7C9016E01E7000B000EB /* NSView+FirstResponder.h */, EE1D7C9116E01E7000B000EB /* NSView+FirstResponder.m */, + EE96929216E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.h */, + EE96929316E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m */, ); name = Categories; sourceTree = ""; @@ -319,6 +324,7 @@ 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; }; diff --git a/EnjoyableApplicationDelegate.m b/EnjoyableApplicationDelegate.m index 8afbb5b..388103b 100644 --- a/EnjoyableApplicationDelegate.m +++ b/EnjoyableApplicationDelegate.m @@ -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]; } @@ -75,31 +70,29 @@ - (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 diff --git a/Enjoyable_Prefix.pch b/Enjoyable_Prefix.pch index 86806fe..1057a2c 100644 --- a/Enjoyable_Prefix.pch +++ b/Enjoyable_Prefix.pch @@ -8,5 +8,6 @@ #import -#import "NSView+FirstResponder.h" #import "NSError+Description.h" +#import "NSMenu+RepresentedObjectAccessors.h" +#import "NSView+FirstResponder.h" diff --git a/NJMappingsController.m b/NJMappingsController.m index e2ac93c..26ef35f 100644 --- a/NJMappingsController.m +++ b/NJMappingsController.m @@ -42,6 +42,7 @@ - (void)mappingsChanged { [self save]; [tableView reloadData]; + popoverActivate.title = _currentMapping.name; [NSNotificationCenter.defaultCenter postNotificationName:NJEventMappingListChanged object:_mappings]; @@ -69,15 +70,19 @@ - (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 { @@ -118,28 +123,24 @@ } - (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 @@ -160,12 +161,11 @@ } 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]]; } } diff --git a/NJOutputController.m b/NJOutputController.m index ed8fe2d..5ceccba 100644 --- a/NJOutputController.m +++ b/NJOutputController.m @@ -180,12 +180,10 @@ 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]; @@ -229,7 +227,7 @@ 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 index 0000000..ecb28a6 --- /dev/null +++ b/NSMenu+RepresentedObjectAccessors.h @@ -0,0 +1,47 @@ +// +// NSMenu+RepresentedObjectAccessors.h +// Enjoyable +// +// Created by Joe Wreschnig on 3/4/13. +// +// + +#import + +@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 index 0000000..a63f083 --- /dev/null +++ b/NSMenu+RepresentedObjectAccessors.m @@ -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 -- 2.20.1