How to force an NSWindow to be always active/focused? - objective-c

I have a transparent NSWindow that follows the user's screen everywhere he goes (the NSWindowstays in front of every app, no matter what, even fullscreen apps).
In that NSWindow i have a mouseDown event that shows a popup. Let's say i'm on safari in fullscreen mode and i have my Window in front of it, i click on safari and i click again on my Window: nothing happens, the mouseDown doesn't occur. I have to click again so the mouseDown event is triggered.
How can i force my NSWindow to be always active so i don't have to click it 2x to trigger the mouseDown when i click on a background app and click in my window again?
Thank you!

I'm not sure if this is exactly what you want (it's not quite a window wide setting), but, from the documentation:
By default, a mouse-down event in a window that isn’t the key window
simply brings the window forward and makes it key; the event isn’t
sent to the NSView object over which the mouse click occurs. The
NSView can claim an initial mouse-down event, however, by overriding
acceptsFirstMouse: to return YES.
The argument of this method is the
mouse-down event that occurred in the non-key window, which the view
object can examine to determine whether it wants to receive the mouse
event and potentially become first responder. You want the default
behavior of this method in, for example, a control that affects the
selected object in a window.
However, in certain cases it’s
appropriate to override this behavior, such as for controls that
should receive mouseDown: messages even when the window is inactive.
Examples of controls that support this click-through behavior are the
title-bar buttons of a window.
Or you could try fiddling with
- (void)sendEvent:(NSEvent *)theEvent
and see if you can handle events in a custom way.

If you add a borderless NSButton instance to your window's view and set your image as the button's image (and as its alternate image, to make it more beautiful), it will work out of the box: Just connect the button's action method to your app delegate (or the object where you want to process the click action). A click on the image (i.e. the button) will then trigger the button's action method, no matter which window is active.

This worked for me, hope that will be helpful, This will keep your window always on Top of all applications
[self.window makeKeyAndOrderFront:nil];
[self.window setLevel:NSStatusWindowLevel];

I think what you really should do is use an NSPanel (a floating palette -- a special kind of NSWindow) that will do exactly what you want in a way that's consistent with the OS rather than trying to fight intended behavior.
Here's the NSPanel documentation:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nspanel_Class/Reference/Reference.html
And here's some helpful and pithy information:
http://cocoadev.com/wiki/NSPanel
By default, an NSPanel will disappear when the application is inactive, but you can turn this off.
I apologize for not laying it out more fully ... pressed for time.
Edit:
Note that you can probably get your window to behave as desired simply:
"The NSView can claim an initial mouse-down event, however, by overriding acceptsFirstMouse: to return YES."
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/EventOverview/HandlingMouseEvents/HandlingMouseEvents.html
You'll need to do this with any NSView subclass to skip the "activation click".

Related

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:
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.
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.
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?
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.

Cocoa HUD panel shows again after calling orderOut:

I'm using a NSPanel with HUD style to display some information.
There's a button inside the HUD panel, when the user clicks the button, I'll open a new window by calling:
[anotherWindowController showWindow:self];
[anotherWindowController.window makeKeyAndOrderFront];
And I want the panel disappear when the window shows, so I set the delegate of the main window, and in the windowDidResignMain callback, I called [hudPanel orderOut:nil].
The HUD panel did disappear (I can see it), but right after it closed, it reopens.
I've checked all possible orderFront: code, and none of them get called. So my hands are really tied. Is this a system level behaviour? Can anyone guide me through this?
EDIT:
I forgot to mention that, the button resides in a NSPopover. So, basically, there's a NSButton in the HUD panel. When user clicks the button, a NSPopover will show up, inside which, there's the button to bring up the new window.
Big thanks!
I had the problem. The following solved it:
[NSApp endSheet:yourPanel];
[yourPanel orderOut:self];
Use
[hudPanel performClose:nil]
(in Swift I have to use self instead of nil). I had a problem using orderOut with a popover and it was solved by using the above method.
Please add [hudPanel close] after [hudPanel orderOut:nil]
swift: hudPanel.close()
from the apple docs:
If the window is the key or main window, the window object immediately behind it is made key or main in its place. Calling orderOut(_:) causes the window to be removed from the screen, but does not cause it to be released. See the close() method for information on when a window is released.
Sometimes the window reappears during window controller inner logic, I think. I have an issue when long pressing keyboard button kills window, but shot keyDown event only hides it on the split second. After using close all goes smoothly.

How to check if a NSWindow is visible

Is there a way to check if a NSWindow is visible or not? I want to display a sheet controller once the first window of my app became visible (the animation on 10.7 ended and the user can see the window!). If I just show the sheet in windowDidLoad, it results in a stupid looking animation (sheet rolling out, window popping out from the back). I know that NSWindowDelegate provides two methods which are invoked when a window either became the key window or the main window, however, this doesn't have to mean that the window is already fully visible at the time. This is even more noticeable on Lion where windows tend to pop up with this stupid animation.
I would go for something like this:
if ([myWindow isVisible]) {
// Do stuff
}
Or an an observer for this key path to be notified when the change occurs.
For what it's worth, you can also bind to the window.visible property. Xcode 4 may squawk at you, saying it's not a bindable property, but it will work.
This can be useful if you are trying enable/disable show/hide NSStatusItem based on whether the window is visible, as well as other approaches.
i.e. in Interface Builder:
Bind to: App Delegate
Model Key Path: self.window.visible

How can I get the value of an NSSlider continuously?

It seems like NSSlider in Cocoa does not provide a delegate to receive an event like Value Changed for a UISlider.
How can I get the value of an NSSlider continuously and display it in an NSTextField, for example?
You need to research Cocoa's Target/Action mechanism. This is a basic Cocoa concept you'll need to understand. The slider (and any other control) can be given a target (some controller object) and an action (the method to call against that controller object).
The action is fired when the user stops dragging by default. Check the slider's Continuous property in Interface Builder to cause it to trigger the action as you're sliding it.
One advantage of using the timer approach is that it works for the case of using the keyboard rather than the mouse to adjust the slider. If the user has "Full Keyboard Access" turned on in System Preferences, they can use the Tab key to give the slider focus. They can then hold down an arrow key so that autorepeat kicks in, whereupon you have a similar situation to dragging with the mouse: the target/action is firing repeatedly, and you want to wait for a moment of calm before saving to the database.
You do need to be careful not to delete your NSTimer prematurely. For example, if the user quits the app during those couple of seconds you probably want to "flush" the slider value to the database before terminating the process.
Programmatical solution based on the answer of Joshua Nozzi:
Swift
slider.isContinuous = true
Objective-C
slider.continuous = YES;

Growl notification like nswindow level

I am writing a notification system just like growl. The notification is a window and it's level is set to NSModalPanelWindowLevel. The only problem is, I have a button in that window and when the user wants to click that button, he/she has to click it twice. But, for example, in growl, no matter what window you have opened, you just simply click the notification and it registers a click.
So is it a level problem? If so, what should I set it to? Thanks
In your custom controls that make up the view content of the window, you will likely want to override NSView's -acceptsFirstMouse: method to return YES:
Discussion
The receiver can either
return a value unconditionally or use
the location of theEvent to determine
whether or not it wants the event. The
default implementation ignores
theEvent and returns NO.
Override this method in a subclass to
allow instances to respond to
click-through. This allows the user to
click on a view in an inactive window,
activating the view with one click,
instead of clicking first to make the
window active and then clicking the
view. Most view objects refuse a
click-through attempt, so the event
simply activates the window. Many
control objects, however, such as
instances of NSButton and NSSlider, do
accept them, so the user can
immediately manipulate the control
without having to release the mouse
button.
Not sure if this is what Growl does, but you might be able to listen for mouse over events in the window and use them to activate/deactivate the window prior to the click. I suspect your issue is that the first click is being eaten by the activation of the window.
Just a guess on that though.
To learn how to handle mouse over events, check out this documentation:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/EventOverview/TrackingAreaObjects/TrackingAreaObjects.html