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

Reply via email to