On Sep 8, 2014, at 1:12 AM, Daryle Walker <[email protected]> wrote:
> Yesterday, I had a thread (<[email protected]>
> “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 protected])
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 [email protected]