On Sep 8, 2014, at 1:12 AM, Daryle Walker <dary...@mac.com> wrote: > Yesterday, I had a thread (<b6bbfa38-4761-4d25-bdfa-d6e5d71c1...@mac.com> > “Bindings to enable a menu item based on an array's element count”) on this > list on how to add a Binding to a menu item’s Hidden flag based on the length > of a custom object’s array-based property. I got the code working. > > There is a menu item for each day that has WebHistoryItem instances. (Each > menu item has a submenu with items for each web-history entry.) The custom > object is pointed to one of those menu items’ submenu and creates two arrays > of menu items, one has copies of the first few menu items of the source menu, > the other copies of the remaining trailing menu items. With KVO signaling, > the first array’s items are directly dumped in the top-level menu and the > second array’s items are dumped into a submenu of the menu item following the > direct items. The Binding from the previous thread hid that menu item when > the corresponding array was empty. > > Now whenever the custom object has at least one non-empty array, I want to > hide the menu item that contains the source submenu, so I don’t have two > copies visible. Right now, I handle hiding and un-hiding manually: > >> - (void)prepareTodayHistoryMenu { >> NSMenu * const browseMenu = self.earlierToday.menu; >> NSMenuItem * const beyondEarlierTodayMenuItem = [browseMenu >> itemAtIndex:(1 + [browseMenu indexOfItem:self.earlierToday])]; >> >> self.todayHistoryHandler.sourceMenu = >> (beyondEarlierTodayMenuItem.isSeparatorItem || ![[NSCalendar >> autoupdatingCurrentCalendar] >> isDateInToday:beyondEarlierTodayMenuItem.representedObject]) ? nil : >> beyondEarlierTodayMenuItem.submenu; >> [beyondEarlierTodayMenuItem >> setHidden:!!self.todayHistoryHandler.sourceMenu]; >> } > > I grouped the source-menu assignment and the hiding of the original menu item > together. > >> - (void)notifyOnNewDay:(NSNotification *)notification { >> NSMenu * const browseMenu = self.earlierToday.menu; >> NSInteger const beyondEarlierTodayIndex = [browseMenu >> indexOfItem:self.earlierToday] + 1; >> >> [[browseMenu itemAtIndex:beyondEarlierTodayIndex] setHidden:NO]; >> // If the "today" menu item shifts, we'll lose track of this and >> therefore can't restore it. >> [self prepareTodayHistoryMenu]; >> } > > This makes sure to disconnect the menu item’s submenu from being the source > menu, since it no longer belongs to Today. I assume that the latest per-day > menu item has the source submenu. If there are no per-day items, then the > following separator item gets a no-op set-visible action. Note that the > affected menu item isn’t tracked, so I have to make it (which may be a no-op > on the following separator item) re-visible before the prepare action sets > its source menu to NIL. (A menu item and submenu for the new Today get > created as-needed in the following method.) [SNIP big example] > I was thinking that if I used Bindings for one menu item, could I add > Bindings between the new menu items and some attribute of the custom object, > so the menu items hide themselves when being mirrored. (At most one will be > hidden.) I added a NSValueTransformer to my custom object. > >> @interface MyOverflowMenuController : NSObject >> >> //! Starts as nil; when set, this instance stores copies of the menu’s >> //! items and tracks the menu for item insertions, removals, and renames. >> @property (nonatomic) NSMenu * sourceMenu; >> //! Starts as zero; if the menu has more menu items that this value, >> //! the copies of the menu's latter items are stored in the overflow >> //! array instead of the direct array. >> @property (nonatomic, assign) NSUInteger maxDirectCount; >> >> //! Starts as empty; updated to mirror the source menu's menu-items. >> //! Keeps at most 'maxDirectCount' items. KVO-compliant. >> @property (nonatomic, readonly) NSArray * directMenuItems; >> //! Starts as empty; updated to mirror the source menu's menu-items. >> //! Keeps the overflow from 'directMenuItems'. KVO-compliant. >> @property (nonatomic, readonly) NSArray * overflowMenuItems; >> >> //! Transforms a NSMenu to a NSNumber with a BOOL value that's YES >> //! when the given menu is self.sourceMenu. >> @property (nonatomic, readonly) NSValueTransformer * >> isSourceMenuTransformer; >> >> @end > > > That last property is of a custom NSValueTransformer subclass. (Is not using > a new .h/.m file pair for the subclass OK?) The subclass holds a pointer to > the source menu, and gets updated when the outer class changes that property > in the setter. The custom object should out-live the per-day history menu > items. So how would connect each menu item’s Hidden attribute to the custom > object and the value transformer? Am I using the right kind of transformer? > The source-menu attribute should be KVO-compliant since I use the automatic > getter and a custom setter (that mutate the two arrays).
I tried: > static inline > NSMenuItem * CreateMenuItemForDay(NSCalendarDate *day, NSDateFormatter > *format) { > NSString * const dayTitle = [format stringFromDate:day]; > NSMenu * const daySubmenu = [[NSMenu alloc] initWithTitle:dayTitle]; > NSMenuItem * const dayItem = [[NSMenuItem alloc] initWithTitle:dayTitle > action:NULL keyEquivalent:@""]; > > dayItem.representedObject = day; > dayItem.submenu = daySubmenu; > > // Attach a binding to let the menu item auto-hide when used as the Today > menu item. > MyAppDelegate * const appDelegate = [NSApp delegate]; > > [dayItem bind:NSHiddenBinding > toObject:appDelegate.myOverflowMenuController.sourceMenu > withKeyPath:@"sourceMenu" options:@{NSValueTransformerBindingOption: > appDelegate.myOverflowMenuController.isSourceMenuTransformer}]; > return dayItem; > } (Good thing I already #imported my application delegate header to get access to the load-URL-from-menu-item action.) I crashed with: > 2014-09-08 02:43:31.216 MyApp[28296:303] Controller cannot be nil > 2014-09-08 02:43:31.279 MyApp[28296:303] ( > 0 CoreFoundation 0x00007fff8557625c > __exceptionPreprocess + 172 > 1 libobjc.A.dylib 0x00007fff8d741e75 > objc_exception_throw + 43 > 2 CoreFoundation 0x00007fff8557610c > +[NSException raise:format:] + 204 > 3 AppKit 0x00007fff8cc37499 -[NSBinder > addBinding:toController:withKeyPath:valueTransformer:options:] + 337 > 4 AppKit 0x00007fff8cc41c38 > -[NSEditableBinder > addBinding:toController:withKeyPath:valueTransformer:options:] + 51 > 5 AppKit 0x00007fff8cc32efb > -[NSObject(NSKeyValueBindingCreation) bind:toObject:withKeyPath:options:] + > 639 > 6 Prairie 0x0000000100006246 > CreateMenuItemForDay + 678 > 7 Prairie 0x0000000100005a44 > -[MyHistoryMenus notifyOnHistoryLoad:] + 708 > 8 CoreFoundation 0x00007fff85544e0c > __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12 (Trimming out the reset of the stack.) The “sourceMenu” starts off as NIL and gets set by -prepareTodayHistoryMenu, which gets called when the array of WebHistory per-day menu items (and sub-menus) gets updated. … In the definition, instead of ‘@“sourceMenu”’, I used a constant. Looking at that in this e-mail, I realized I specified that property twice. I changed the second argument to “appDelegate.myOverflowMenuController” and it doesn’t crash. Now, none of the per-day web-history menus show up, not just the one for Today. (The one page I visited today does show up directly in the History, proving that the just-today web-history menu item mirroring system still works. All of the per-day menu items return YES through the custom value-transformer, or I screwed up the binding. > @interface MyCustomTargettingTransformer : NSValueTransformer > > //! Starts as nil; the menu instance to be compared. > @property (nonatomic) NSMenu * targetMenu; > > @end > > @implementation MyCustomTargettingTransformer > > + (Class)transformedValueClass { > return [NSNumber class]; > } > > + (BOOL)allowsReverseTransformation { > return NO; > } > > - (id)transformedValue:(id)value { > return [NSNumber numberWithBool:(self.targetMenu == value)]; > } > > @end (This is within the *.m file for MyOverflowMenuController.) In the setter for the “sourceMenu” property of MyOverflowMenuController, I set the “targetMenu” property of the “isSourceMenuTransformer” property of MyOverflowMenuController. When a using a custom setter for a property, are KVO notifications sent? — Daryle Walker Mac, Internet, and Video Game Junkie darylew AT mac DOT com _______________________________________________ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com