Search code examples
macoskeyboard-shortcutshiddennsmenunsmenuitem

How to create an NSMenu containing an NSMenuItem which only appears while holding a keyboard modifier key?


I'd like to create an NSMenu containing an NSMenuItem which is hidden by default, and only appears while the user is holding a keyboard modifier key.

Basically, I'm looking for the same behaviour as the 'Library' option in the Finder's 'Go' Menu:

Without holding Option (⌥): enter image description here

While holding Option (⌥): enter image description here


I already tried installing a key listener using [NSEvent addGlobalMonitorForEventsMatchingMask: handler:] to hide and unhide the NSMenuItem programmatically by setting it's hidden property. This kind of worked, but the problem is that the hiding/unhiding wouldn't work while the NSMenu was open. Apparently an NSMenu completely takes over the event processing loop while it's open, preventing the key listener from working.
I could probably use a CGEventTap to still receive events while the NSMenu is open, but that seems like complete overkill.

Another thing I discovered which does a similar thing to what I want is the 'alternate' mechanism of NSMenu. But I could only get it to switch out NSMenuItems, not hide/unhide them.

Any help would be greatly appreciated. Thanks!


Solution

  • I found a solution that behaves perfectly!

    1. On the NSMenuItem you want hidable, set the alternate property to YES, and set the keyEquivalentModifierMask property to the keyboard modifiers which you want to unhide the item.

    2. In your NSMenu, right before the NSMenuItem which you want to be hideable, insert another NSMenuItem that has height 0.

      In Objc, you can create an NSMenuItem with height 0 like this:

      NSMenuItem *i = [[NSMenuItem alloc] init];
      i.view = [[NSView alloc] initWithFrame:NSZeroRect];
      

    The hideable NSMenuItem will now be 'alternate' to the zero-height NSMenuItem preceding it. The zero-height item will display by default, but while you hold the keyboard modifier(s) you specified, the zero-height item will be swapped out with the hideable item. Because the zero-height item is invisible, this has the effect of unhiding the hideable item.