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.)

> - (void)rebuildHistoryMenusDueToChange:(NSDictionary *)change {
>     NSMenu * const          browseMenu = self.earlierToday.menu;
>     NSInteger  beyondEarlierTodayIndex = [browseMenu 
> indexOfItem:self.earlierToday] + 1;
>     NSIndexSet * const  indexesChanged = change[NSKeyValueChangeIndexesKey];  
> // May be nil, depending on 'changeType'.
> 
>     NSParameterAssert(beyondEarlierTodayIndex != -1 + 1);
>     switch ((NSKeyValueChange)[change[NSKeyValueChangeKindKey] 
> unsignedIntegerValue]) {
>         default:
>         case NSKeyValueChangeSetting: {
>             // Do wholesale replacement; get rid of the current menu items 
> and install the new ones.
>             //...
>             [self prepareTodayHistoryMenu];  // Rebuild today's history menus.
>             break;
>         }
> 
>         case NSKeyValueChangeRemoval: {
>             // Purge the menus of the deleted indexes.
>             NSParameterAssert(indexesChanged);
>             [indexesChanged enumerateIndexesWithOptions:NSEnumerationReverse 
> usingBlock:^(NSUInteger idx, BOOL *stop) {
>                 [browseMenu removeItemAtIndex:(beyondEarlierTodayIndex + 
> (NSInteger)idx)];
>             }];
>             if ([indexesChanged containsIndex:0u]) {  // Only if today was 
> deleted,...
>                 [self prepareTodayHistoryMenu];  // ...rebuild today's 
> history menus.
>             }
>             break;
>         }
>             
>         case NSKeyValueChangeInsertion: {
>             // Place the menus of the added indexes.
>             NSParameterAssert(indexesChanged);
>             [[browseMenu itemAtIndex:beyondEarlierTodayIndex] setHidden:NO];
>               // If index 0 shifts, we'll lose track of this and therefore 
> can't restore it.
>             [indexesChanged enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL 
> *stop) { /*…*/ }];
>             if ([indexesChanged containsIndex:0u]) {  // Gained or still have 
> a today menu item, so just update.
>                 [self prepareTodayHistoryMenu];
>             } else {
>                 [[browseMenu itemAtIndex:beyondEarlierTodayIndex] 
> setHidden:YES];  // Undo non-hiding from second line.
>             }
>             break;
>         }
> 
>         case NSKeyValueChangeReplacement: {
>             // Since NSMenu doesn't have a replacement API, do a removal & 
> insert.
>             NSParameterAssert(indexesChanged);
>             [[browseMenu itemAtIndex:beyondEarlierTodayIndex] setHidden:NO];
>               // If index 0 shifts, we'll lose track of this and therefore 
> can't restore it.
>             [indexesChanged enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL 
> *stop) { /*…*/ }];
>             if ([indexesChanged containsIndex:0u]) {  // Still have a today 
> menu item, so just update.
>                 [self prepareTodayHistoryMenu];
>             } else {
>                 [[browseMenu itemAtIndex:beyondEarlierTodayIndex] 
> setHidden:YES];  // Undo non-hiding from second line.
>             }
>             break;
>         }
>     }
> }


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).

— 
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