Swift: NSStatusItem menu behaviour in 10.10 (e.g. show only on right mouse click)

孤人 提交于 2021-01-21 04:06:26

问题


I am writing a simple status bar app in Swift, and attempting to use the new NSStatusItem API introduced in OS X 10.10.

The interface I'm aiming for is a simple left mouse click on the statusItem to toggle a core feature on and off, with a right mouse click (or control-click) option to show a settings menu. I have no need for a custom view or popover for this functionality.

By default, if a NSMenu is assigned to a NSStatusItem, it will display the menu on both a left and right click. I want to change the behaviour to only show the menu on a right click, or as a workaround, prevent the menu popping up on a left click

Previously, it seems that to get control of mouse events on a NSStatusItem, one had to set a custom view with overridden mouse events (see this related question).

In the new NSStatusItem API introduced in 10.10, methods for setting a custom view have been deprecated, and it looks like this behaviour is discouraged. According to @Taylor in this answer a few of the deprecated behaviours should be used through the NSStatusBarButton object returned by statusItemObject.button() but as of writing there is no documentation for NSStatusBarButton, and the object returned is read-only and cannot be replaced with a custom button with overridden mouse event handlers.

Is there a way to bring some level of control to whether an NSMenu attached to an NSStatusItem (or an NSStatusBarButton) is displayed with regard to mouse events?


回答1:


Here's the solution I came up with. It works fairly well, though there's one thing I'm not happy with: the status item stays highlighted after you choose an option in the right-click menu. The highlight goes away as soon as you interact with something else.

Also note that popUpStatusItemMenu: is "softly deprecated" as of OS X 10.10 (Yosemite), and will be formally deprecated in a future release. For now, it works and won't give you any warnings. Hopefully we'll have a fully supported way to do this before it's formally deprecated—I'd recommend filing a bug report if you agree.

First you'll need a few properties and an enum:

typedef NS_ENUM(NSUInteger,JUNStatusItemActionType) {
    JUNStatusItemActionNone,
    JUNStatusItemActionPrimary,
    JUNStatusItemActionSecondary
};

@property (nonatomic, strong) NSStatusItem *statusItem;
@property (nonatomic, strong) NSMenu *statusItemMenu;
@property (nonatomic) JUNStatusItemActionType statusItemAction;

Then at some point you'll want to set up the status item:

NSStatusItem *item = [[NSStatusBar systemStatusBar] statusItemWithLength:29.0];
NSStatusBarButton *button = item.button;
button.image = [NSImage imageNamed:@"Menu-Icon"];
button.target = self;
button.action = @selector(handleStatusItemAction:);
[button sendActionOn:(NSLeftMouseDownMask|NSRightMouseDownMask|NSLeftMouseUpMask|NSRightMouseUpMask)];
self.statusItem = item;

Then you just need to handle the actions sent by the status item button:

- (void)handleStatusItemAction:(id)sender {

    const NSUInteger buttonMask = [NSEvent pressedMouseButtons];
    BOOL primaryDown = ((buttonMask & (1 << 0)) != 0);
    BOOL secondaryDown = ((buttonMask & (1 << 1)) != 0);
    // Treat a control-click as a secondary click
    if (primaryDown && ([NSEvent modifierFlags] & NSControlKeyMask)) {
        primaryDown = NO;
        secondaryDown = YES;
    }

    if (primaryDown) {
        self.statusItemAction = JUNStatusItemActionPrimary;
    } else if (secondaryDown) {
        self.statusItemAction = JUNStatusItemActionSecondary;
        if (self.statusItemMenu == nil) {
            NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
            [menu addItemWithTitle:NSLocalizedString(@"Quit",nil) action:@selector(terminate:) keyEquivalent:@""];
            self.statusItemMenu = menu;
        }
        [self.statusItem popUpStatusItemMenu:self.statusItemMenu];
    } else {
        self.statusItemAction = JUNStatusItemActionNone;
        if (self.statusItemAction == JUNStatusItemActionPrimary) {
            // TODO: add whatever you like for the primary action here
        }
    }

}

So basically, handleStatusItemAction: is called on mouse down and mouse up for both mouse buttons. When a button is down, it keeps track of whether it should do the primary or secondary action. If it's a secondary action, that's handled immediately, since menus normally appear on mouse down. If it's a primary action, that's handled on mouse up.




回答2:


This is deprecated in 10.10, but will continue to work:

[self.statusItem setTarget:self]; // Otherwise this goes to the first responder
[self.statusItem setAction:@selector(statusItemClicked:)];
[self.statusItem sendActionOn:(NSRightMouseUpMask)];

You can set other events in setActionOn by bitmasking them. So for instance if you wanted left and right click:

[self.statusItem sendActionOn:(NSLeftMouseUpMask | NSRightMouseUpMask)];

(Excuse the objC, you should be able to translate it into swift and it should work)



来源:https://stackoverflow.com/questions/26924454/swift-nsstatusitem-menu-behaviour-in-10-10-e-g-show-only-on-right-mouse-click

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!