Swift: Right/Left Click on Nsstatusbar

Detect left and right click events on NSStatusItem (Swift)

Have you tried:

button.sendAction(on: [.leftMouseUp, .rightMouseUp])

Then seeing which mouse button was pressed in the doSomeAction() function?

So it will look something like...

let statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength)

func applicationDidFinishLaunching(_ aNotification: Notification) {

if let button = statusItem.button {
button.action = #selector(self.doSomeAction(sender:))
button.sendAction(on: [.leftMouseUp, .rightMouseUp])
}

}

func doSomeAction(sender: NSStatusItem) {

let event = NSApp.currentEvent!

if event.type == NSEvent.EventType.rightMouseUp {
// Right button click
} else {
// Left button click
}

}

Thanks to @dbrownjave for noticing the change in Swift 4 from NSEventType.rightMouseUp to NSEvent.EventType.rightMouseUp.

https://github.com/craigfrancis/datetime/blob/master/xcode/DateTime/AppDelegate.swift

Call Action when NSStatusBarButton is right-clicked

You can subclass and override the mouseDown method, but since Mac OS X 10.10 (Yosemite), there has been an easier way: NSGestureRecognizer and its subclasses:

func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
if let button = statusItem.button {
button.image = NSImage(named: "myImage")
button.alternateImage = NSImage(named: "myImage")
button.action = Selector("myAction")

// Add right click functionality
let gesture = NSClickGestureRecognizer()
gesture.buttonMask = 0x2 // right mouse
gesture.target = self
gesture.action = "myRightClickAction:"
button.addGestureRecognizer(gesture)
}
}

func myRightClickAction(sender: NSGestureRecognizer) {
if let button = sender.view as? NSButton {
// Handle your right click event here
}
}

Objective-C: NSStatusBar right and left click

To detect the mouse buttons that are currently pressed, you can use [NSEvent pressedMouseButtons].

To detect the status bar icon being clicked, you can detect the menu appearing. Just before a menu opens, it sends a menuWillOpen: message to its delegate (if it has one). So implement something like this:

- (void)menuWillOpen:(NSMenu *)menu
{
NSLog(@"%d",[NSEvent pressedMouseButtons]);
}

You will also need to set the delegate for the menu, for example by

[menu setDelegate:self];

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

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.

Show NSMenu only on NSStatusBarButton right click?

The usual way a to show a menu is to assign a menu to the status item, where it will be shown when the status item button is clicked. Since popUpMenu is deprecated, another way is needed to show the menu under different conditions. If you want the right click to use an actual status item menu instead of just showing a contextual menu at the status item location, the status item menu property can be kept nil until you want to show it.

I've adapted your code to keep the statusBarItem and statusBarMenu references separate, only adding the menu to the status item in the clicked action method. In the action method, once the menu is added, a normal click is performed on the status button to drop the menu. Since the status item will then always show its menu when the button is clicked, an NSMenuDelegate method is added to set the menu property to nil when the menu is closed, restoring the original operation:

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
// keep status item and menu separate
var statusBarItem: NSStatusItem!
var statusBarMenu: NSMenu!

func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "br>
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])

statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.delegate = self
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
}

@objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click!")
statusBarItem.menu = statusBarMenu // add menu to button...
statusBarItem.button?.performClick(nil) // ...and click
} else {
print("Left click!")
}
}

@objc func menuDidClose(_ menu: NSMenu) {
statusBarItem.menu = nil // remove menu so button works as before
}

@objc func orderAnApple() {
print("Ordering a apple!")
}

@objc func cancelAppleOrder() {
print("Canceling your order :(")
}

}

NSStatusItem right click menu

NSStatusItem popUpStatusItemMenu: did the trick. I am calling it from my right click action and passing in the menu I want to show and it's showing it! This is not what I would have expected this function to do, but it's working.

Here's the important parts of what my code looks like:

- (void)showMenu{
// check if we are showing the highlighted state of the custom status item view
if(self.statusItemView.clicked){
// show the right click menu
[self.statusItem popUpStatusItemMenu:self.rightClickMenu];
}
}

// menu delegate method to unhighlight the custom status bar item view
- (void)menuDidClose:(NSMenu *)menu{
[self.statusItemView setHighlightState:NO];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
// setup custom view that implements mouseDown: and rightMouseDown:
self.statusItemView = [[ISStatusItemView alloc] init];
self.statusItemView.image = [NSImage imageNamed:@"menu.png"];
self.statusItemView.alternateImage = [NSImage imageNamed:@"menu_alt.png"];
self.statusItemView.target = self;
self.statusItemView.action = @selector(mainAction);
self.statusItemView.rightAction = @selector(showMenu);

// set menu delegate
[self.rightClickMenu setDelegate:self];

// use the custom view in the status bar item
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
[self.statusItem setView:self.statusItemView];
}

Here is the implementation for the custom view:

@implementation ISStatusItemView

@synthesize image = _image;
@synthesize alternateImage = _alternateImage;
@synthesize clicked = _clicked;
@synthesize action = _action;
@synthesize rightAction = _rightAction;
@synthesize target = _target;

- (void)setHighlightState:(BOOL)state{
if(self.clicked != state){
self.clicked = state;
[self setNeedsDisplay:YES];
}
}

- (void)drawImage:(NSImage *)aImage centeredInRect:(NSRect)aRect{
NSRect imageRect = NSMakeRect((CGFloat)round(aRect.size.width*0.5f-aImage.size.width*0.5f),
(CGFloat)round(aRect.size.height*0.5f-aImage.size.height*0.5f),
aImage.size.width,
aImage.size.height);
[aImage drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0f];
}

- (void)drawRect:(NSRect)rect{
if(self.clicked){
[[NSColor selectedMenuItemColor] set];
NSRectFill(rect);
if(self.alternateImage){
[self drawImage:self.alternateImage centeredInRect:rect];
}else if(self.image){
[self drawImage:self.image centeredInRect:rect];
}
}else if(self.image){
[self drawImage:self.image centeredInRect:rect];
}
}

- (void)mouseDown:(NSEvent *)theEvent{
[super mouseDown:theEvent];
[self setHighlightState:!self.clicked];
if ([theEvent modifierFlags] & NSCommandKeyMask){
[self.target performSelectorOnMainThread:self.rightAction withObject:nil waitUntilDone:NO];
}else{
[self.target performSelectorOnMainThread:self.action withObject:nil waitUntilDone:NO];
}
}

- (void)rightMouseDown:(NSEvent *)theEvent{
[super rightMouseDown:theEvent];
[self setHighlightState:!self.clicked];
[self.target performSelectorOnMainThread:self.rightAction withObject:nil waitUntilDone:NO];
}

- (void)dealloc{
self.target = nil;
self.action = nil;
self.rightAction = nil;
[super dealloc];
}

@end


Related Topics



Leave a reply



Submit