Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keeping an NSStatusBarButton highlighted while popover is displayed

Practically all of NSStatusItem has been deprecated for 10.10 and the behavior of the underlying NSStatusBarButton seems to be confusing.

Currently I am working on a menu bar application. When the user clicks the menu bar icon for the app, a method in my application delegate is called via target-action which displays an NSPopover (or closes it if it's already visible) with some information.

Normally, if you've associated, say, an NSMenu with an NSStatusItem when the user clicks on the menu bar icon that icon remains highlighted blue until the menu is closed. Similarly clicking the system volume icon pops down a slider and highlights its icon blue until the view containing the slider disappears.

However, since I'm the one opening the NSPopover, the system instead highlights the icon blue on mouse down, then returns it to normal on mouse up after my method has been called. Meaning there's nothing I can seem to do on that loop to maintain the highlight. I want the icon to continue being highlighted on mouse up and only return to normal when I tell it to (ie. when I close my popover.)

I have no idea how to do this. I've tried using

[self.statusItem.button setHighlighted: YES];
//or [self.statusItem.button highlight: YES];

when I receive the mouse up event in my app delegate and open the popover. The problem is the system still has it, apparently, highlighted this frame/loop from the earlier mouse down and immediately after I set it to highlighted, it sets it to unhighlighted due to the mouse up. I can get around this by encapsulating this in a method and running the method using a timer or delayed selector a split second later. This allows me to keep the icon highlighted but introduces a flicker; the icon is highlighted automatically as the mouse goes down, as the mouse goes up it unhighlights it for a frame, then my method re-highlights it.

I also figured perhaps I could use the deprecated setHighlightMode: and set it to NO to prevent the icon from being highlighted automatically on click, then using setHighlighted: / highlighted: to set it manually but that doesn't work either. Similarly, I thought maybe this would work as well:

 NSButtonCell* cell = (NSButtonCell*)self.statusItem.button.cell;
cell.highlightsBy = NSNoCellMask;

But regardless clicking it automatically highlights the icon and dehighlights it on mouse up right after my method is called.

Basically:

  1. The undesirable automatic highlighting behavior of NSStatusBarButton interferes with manually setting the highlight state, unless I delay manually setting it which introduces a short flicker.
  2. The only thing that seems to successfully disable this automatic behavior is the deprecated setHighlightMode:, but this seems to prevent all highlighting, manual or not.
  3. The only work around seems to be to add a subview to the NSButtonCell, add an event listener for mouse up and then set the highlight state of the superview as per here: NSStatusBarButton keep highlighted but I would think there'd be a simpler way to just... disable the automatic highlighting altogether.

tl;dr: Is there a way for me to easily obtain full control over when and when not my menu bar icon is highlighted, so that I can have it highlight naturally while my NSPopover is displayed?

like image 703
Metabble Avatar asked Jul 18 '15 21:07

Metabble


1 Answers

I ended up solving this by not setting the NSStatusItem's action selector property. Instead I used NSEvent's addLocalMonitorForEventsMatchingMask:handler:. In the handler block I check if the event.locationInWindow is within my status item's .bounds. If so I send the message the .action would have manually and then return nil to prevent the event from being passed on. If it's not within the status icon's bounds I return event so it gets passed on normally. In my click handling method I use [self.statusItem.button highlight: YES/NO] when my popover is displayed/closed.

TL;DR:

In applicationDidFinishLaunching:

__block AppDelegate* appDelegate = self;
[NSEvent addLocalMonitorForEventsMatchingMask: NSEventMaskFromType(NSLeftMouseDown) handler:^NSEvent* (NSEvent* event){
    if (NSPointInRect(event.locationInWindow, appDelegate.statusItem.button.bounds)){
        [appDelegate clickedMenuBarIcon: event];
        return nil;
    }
    return event;
 }];

In clickedMenuBarIcon: I can then set the highlight state. Since I returned nil in my handler block it prevented the event from getting passed on so the automatic highlighting never occurs and I can do it manually.


If there's any bugs associated with this I'd appreciate any advice.

like image 79
Metabble Avatar answered Sep 29 '22 07:09

Metabble