Re: Add bindings for custom menu items' isHidden to (an attribute of) a custom object?
Re: Add bindings for custom menu items' isHidden to (an attribute of) a custom object?
- Subject: Re: Add bindings for custom menu items' isHidden to (an attribute of) a custom object?
- From: Daryle Walker <email@hidden>
- Date: Mon, 08 Sep 2014 03:10:02 -0400
On Sep 8, 2014, at 1:12 AM, Daryle Walker <email@hidden> wrote:
> Yesterday, I had a thread (<email@hidden> “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 (email@hidden)
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:
This email sent to email@hidden