How to listen for whether the option key is pressed in the right-click menu?

I use

-(void)contextMenuCallback:(NSMenu *)menu forSelectedLayers:(NSArray *)glyphs event:(NSEvent *)event

create a NSMenuItem,and item.view is a customview,I want to change some control’s status in customview when I pressed down Option key,how to listen for this event?

This depends on what effect you are looking for.

If the menu item just contains some text that changes when holding down the Option key, then this is fairly easy by adding two menu item, setting the alternate option on the second one and assigning it a keyEquivalentModifierMask of NSEventModifierFlagOption.

If you need the custom view for something more complex than just text, then you would need to install an event monitor. (+[NSEvent addLocalMonitorForEventsMatchingMask:handler:], let me know if that is the case and I can provide some sample code.)

Yes, I have added a slightly complex custom view to the right-click menu. After clicking on the right-click menu, I hope that pressing the option key can change some states or properties of some controls in the view. If you can provide me with some sample code, I would really appreciate it!

Can you show a screenshot of what you are trying to do (maybe in a direct message)?

In the picture is a custom view displayed in my right-click menu, which contains many controls. I want some controls, such as buttons, to change color or have other changes when the option key is pressed. How can I write a listening event to monitor the pressing of the option key and then modify the elements in the view? It’s like the color markers in the right-click menu: normally, the color labels of characters are displayed, and when the option key is pressed, the color labels of the layers are displayed.

Your code should look something like this:

// a property that you store somewhere in your class
id _eventMonitorHandle;

// `menuWillOpen:` and `menuDidClose:` from `NSMenuDelegate`
- (void)menuWillOpen:(NSMenu *)menu {
    if (_eventMonitorHandle) {
        [NSEvent removeMonitor:_eventMonitorHandle];
    }
    
    __weak typeof(self) weakSelf = self;
    _eventMonitorHandle = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFlagsChanged
                                                                handler:^NSEvent *(NSEvent *event) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (!strongSelf) {
            return event;
        }
        
        BOOL isOptionDown = ((event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagOption);
        // TODO: update your view
        
        return event;
    }];
}

- (void)menuDidClose:(NSMenu *)menu {
    if (_eventMonitorHandle) {
        [NSEvent removeMonitor:_eventMonitorHandle];
        _eventMonitorHandle = nil;
    }
}

You also need to set your class object as the delegate of the menu. Let me know if need help with any details.

The problem with setting the delegate is that someone else might also try to do something similar on the same menu. And then only one of the two will work. And in this case, the color label selector is setting the delegate …
I don’t found a better solution, yet.

Ah, right. Best to avoid using menuWillOpen: and menuDidClose: and instead adding/removing the event monitor when the view appears/disappears.

I changed my code to use - (void)viewDidAppear and - (void)viewDidDisappear. And that works.

I add NSMenuDelegate,and in this -(void)contextMenuCallback:(NSMenu *)menu forSelectedLayers:(NSArray *)glyphs event:(NSEvent *)event,I set [menu setDelegate:self];

I add your code ,but it didn’t triggered _eventMonitorHandle = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFlagsChanged

                                                            handler:^NSEvent \*(NSEvent \*event) {}\]; when I pressed any key.

Then, what should I do?Can you give me a simple example?

Could you share your code with me so I can have a better look? For a private message, click my profile picture and then Message.

This turned out to bit more tricky than expected. Here we go:

Add a CFRunLoopObserverRef as an instance variable:

CFRunLoopObserverRef _runLoopObserver;

and then use CFRunLoopObserverCreateWithHandler to handle events:

if (_runLoopObserver == NULL) {
	__block BOOL latestIsOptionDown = ([NSEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask & NSEventModifierFlagOption) != 0;
	// add observer for events
	_runLoopObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
		if (self->_runLoopObserver != NULL && [self->_thisView enclosingMenuItem] == nil) {
			// view no longer in menu item: menu is closed: remove observer
			CFRunLoopObserverRef runLoopObserver = self->_runLoopObserver;
			self->_runLoopObserver = NULL;
			CFRunLoopObserverInvalidate(runLoopObserver);
			CFRelease(runLoopObserver);
			return;
		}
		
		BOOL isOptionDown = ([NSEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask & NSEventModifierFlagOption) != 0;
		if (isOptionDown == latestIsOptionDown) {
			// observed event did not affect the Option key: early exit
			return;
		}
		
		// the Option key state did change: update view
		latestIsOptionDown = isOptionDown;
		[self updateMenuItemViewIsOptionDown:isOptionDown];
	});
	CFRunLoopAddObserver(CFRunLoopGetCurrent(), _runLoopObserver, kCFRunLoopCommonModes);
	
	// set up view with initial state
	[self updateMenuItemViewIsOptionDown:latestIsOptionDown];
}

I placed this code in contextMenuCallback:forSelectedLayers:event: after adding the item to the menu. It’s all nicely self-contained, checking whether the view still belongs to a menu item and if not, unregisters its own observation. Then main view update is then performed in the following method:

- (void)updateMenuItemViewIsOptionDown:(BOOL)isOptionDown {
	// ...
}
1 Like

Thank you very much for your example; it works!

  • (void)drawFontViewBackgroundForLayer:(GSLayer *)layer inFrame:(NSRect)frame;

In fontView ,I want to refrash the view that I selected layers through use [NSView setNeedsDisplay:YES],how can I get the view?

I am not quite shire what you are asking for. Maybe this is of help:

I want to update Font View cells manually,Is there an interface?

You should be able to trigger a redraw with this code:

glyph.willChangeValueForKey_("changeCount")
glyph.didChangeValueForKey_("changeCount")

Thank you!