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 :(")
}
}
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
}
}
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.
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
How to show Menu on button press instead of right mouse click in os x
I got it working finally using this code:
@IBOutlet var meeenu: NSMenu!
@IBAction func Options(sender: NSButtonCell) {
meeenu.popUpMenuPositioningItem(meeenu.itemAtIndex(0), atLocation: NSEvent.mouseLocation(), inView: nil)
}
Mac OS Menu Bar Add Right Click To Show Another View
Instead of overriding functions in NSStatusBarButton
, you should detect the left and right click behavior within the MenuButtonToggle
function.
First, you should register the right-click action, inside your the if-let inside applicationDidFinishLaunching
, like so:
if let menuButton = statusItem?.button {
menuButton.image = NSImage(systemSymbolName: "note", accessibilityDescription: nil)
menuButton.action = #selector(MenuButtonToggle)
menuButton.sendAction(on: [.leftMouseUp, .rightMouseUp]) // register action on right click too
}
Then, you want to update the MenuButtonToggle
function to act differently depending on left and right clicks.
@objc func menuButtonToggle(sender: AnyObject){
// ... previous logic here
if let event = NSApp.currentEvent {
if event.type == NSEventType.rightMouseUp {
// Right button click
self.popOver.show(...)
} else {
// Left button click
self.popOverOptions.show(...)
}
}
}
Related Topics
Uitextview Change Text Color of Specific Text
Why Does Swift Not Allow Stored Properties in Extensions
Xcode 6, Swift - Read Standard Input (Console) to String
Accessor Gives the Wrong Value in Swift 1.2/2.0 Release Build Only
How to Delete an Item in a Collection View with a Button in the Cell
Any Way to Chain == and || Operands
How to Cast an Any Value with Nil in It to a Any
How to Create a Struct to Match This JSON
Swift: How to Reload Row Height in Uitableviewcell Without Reloading Data
Add a Border with Cornerradius to an Image in Swiftui Xcode Beta 5
Fatal Error: Array Index Out of Range in Swift Xcode6
How to Handle Two Different Types in an Array in Swift for a Uitableview
How to Use Trailing Closure in If Condition
My Uiviewcontroller Is Not Filling the Entire Screen
Swift: Providing a Default Protocol Implementation in a Protocol Extension
What's the Best Way to Iterate Over Results from an API, and Know When It's Finished
Error: Binary Operator '<=' Cannot Be Applied to Operands of Type 'Int' and 'Int'