Hide / Show the menu item in the main menu of the application by pressing the Option key

I want to add a menu item to the main menu of the application, which will be used quite rarely. I want it to be hidden by default and only show when the user holds the Option key. How to do it?

It seems that I should handle flagsChanged: but is this an NSResponder method and NSMenu not inheriting from NSResponder ? I tried it in the main window controller, and it works when I press the Option key before clicking on the menu. The following usage example does not work: click on a menu item (no item), press the select key - my item will appear, release the option key - the item should disappear.

I also tried NSEvent addLocalMonitorForEventsMatchingMask:handler: and addGlobalMonitorForEventsMatchingMask:handler: for NSFlagsChangedMask , but when the select key is pressed when the main menu is open, neither local nor global handlers start.

How can i do this?

+6
source share
5 answers

Add the following to applicationDidFinishLaunching.

 // Dynamically update QCServer menu when option key is pressed NSMenu *submenu = [[[NSApp mainMenu] itemWithTitle:@"QCServer"] submenu]; NSTimer *t = [NSTimer timerWithTimeInterval:0.1 target:self selector:@selector(updateMenu:) userInfo:submenu repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:t forMode:NSEventTrackingRunLoopMode]; 

then add

 - (void)updateMenu:(NSTimer *)t { static NSMenuItem *menuItem = nil; static BOOL isShowing = YES; // Get global modifier key flag, [[NSApp currentEvent] modifierFlags] doesn't update while menus are down CGEventRef event = CGEventCreate (NULL); CGEventFlags flags = CGEventGetFlags (event); BOOL optionKeyIsPressed = (flags & kCGEventFlagMaskAlternate) == kCGEventFlagMaskAlternate; CFRelease(event); NSMenu *menu = [t userInfo]; if (!menuItem) { // View Batch Jobs... menuItem = [menu itemAtIndex:6]; [menuItem retain]; } if (!isShowing && optionKeyIsPressed) { [menu insertItem:menuItem atIndex:6]; [menuItem setEnabled:YES]; isShowing = YES; } else if (isShowing && !optionKeyIsPressed) { [menu removeItem:menuItem]; isShowing = NO; } NSLog(@"optionKeyIsPressed %d", optionKeyIsPressed); } 

The timer only works when tracking controls, so it does not affect performance too much.

+5
source

The best way to achieve this is to use two menu items, the first menu item uses a custom view of height 0 and is disabled, and then there is an β€œalternative” item below it. (You will need to set this keyEquivalentModifierMask element to NSAlternateKeyMask ). With this arrangement, when you press the select key, NSMenu will automatically replace the zero-height menu item with an alternative element that will have the effect of creating the item’s menu in a magical way.

No need for timers, updates or flag change notifications.

This functionality is described in the documentation here: Alternative Management

+10
source

When creating the menu, an optional element is included and mark it as hidden. Then set the class instance as a menu delegate and add a loop loop observer when the menu is open to control the hidden state of the optional item.

 @implementation AppController { CFRunLoopObserverRef _menuObserver; } - (void)updateMenu { BOOL hideOptionalMenuItems = ([NSEvent modifierFlags] & NSAlternateKeyMask) != NSAlternateKeyMask; [self.optionalMenuItem setHidden:hideOptionalMenuItems]; } - (void)menuWillOpen:(NSMenu *)menu { [self updateMenu]; if (_menuObserver == NULL) { _menuObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { [self updateMenu]; }); CFRunLoopAddObserver(CFRunLoopGetCurrent(), _menuObserver, kCFRunLoopCommonModes); } } - (void)menuDidClose:(NSMenu *)menu { if (_menuObserver != NULL) { CFRunLoopObserverInvalidate(_menuObserver); CFRelease(_menuObserver); _menuObserver = NULL; } } 
+9
source

Since the NSMenuDelegate menuNeedsUpdate: method is called before display, it can be overridden, check if the [NSEvent modifierFlags] alternative bit is set, and use this to show / hide the secret menu items.

Here is an example copied from Show functionality with key modifiers that covers just this topic:

 #pragma NSMenu delegate methods - (void) menuNeedsUpdate: (NSMenu *)menu { NSLog(@"menuNeedsUpdate: %@", menu); NSUInteger flags = ([NSEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask); // We negate the value below because, if flags == NSAlternateKeyMask is TRUE, that // means the user has the Option key held, and wants to see the secret menu, so we // need shoudHideSecretMenu to be FALSE, so we just negate the value. BOOL shoudHideSecretMenu = !(flags == NSAlternateKeyMask); NSLog(@"Flags: 0x%lx (0x%x), shoudHideSecretMenu = %d", flags, NSAlternateKeyMask, shoudHideSecretMenu); [secretMenuItem setHidden:shoudHideSecretMenu]; } 
+1
source

There are some complicated answers here, but in fact it is very simple:

Create 2 menu items. The first is the default with any key character and name you want. The second is what will be shown when the modifier key is omitted - again with a separate key and symbol. In the second menu item, turn on "Alternate", and everything else will happen automatically.

The required modifier is determined by comparing two key values.

0
source

Source: https://habr.com/ru/post/919033/


All Articles