views:

240

answers:

3

The iTunes mini-player (to give just one example) supports click-through where the application isn't brought to the front when the play/pause and volume controls are used.

How is this done?

I've been looking through Apple's documentation and have a little to go on, in Cocoa Event-Handling Guide, Event Dispatch it states:

Some events, many of which are defined by the Application Kit (type NSAppKitDefined), have to do with actions controlled by a window or the application object itself. Examples of these events are those related to activating, deactivating, hiding, and showing the application. NSApp filters out these events early in its dispatch routine and handles them itself.

So, from my limited understanding (How an Event Enters a Cocoa, Application) subclassing NSApplication and overriding - (void)sendEvent:(NSEvent *)theEvent should trap every mouse and keyboard event, but still, the window is raised on click. So either the window is raised before the event is seen by NSApplication or I'm missing something else.

I've looked at Matt Gallagher's Demystifying NSApplication by recreating it, unfortunately Matt didn't cover the event queue, so other than that, I'm stumped.

Any help would be appreciated, thanks.

Edited to add: Found a post at Lloyd's Lounge in which he talks about the same problem and links to a post at CocoaBuilder, capture first right mouse down. I'm currently trying out the code supplied there, after some fiddling around and reactivating the NSLog for [theEvent type], the left mouse button activity is being caught.

Now, left clicking on the window to bring it forward produces a sequence of event types, 13, 1, 13, these are NSAppKitDefined, NSLeftMouseDown and NSAppKitDefined again. Can I filter these out or find where they are going?

A: 

Have you checked out acceptsFirstMouse:? Any NSView can return YES to this event to say that the view should handle the event rather than bringing the window to the foreground. Presumably, by intercepting the event, it won't be used to bring the window to the foreground, but I can't vouch for that.

You'll obviously need to subclass any control you want to have this behavior to override acceptsFirstMouse:.

Alex
acceptsFirstMouse: and acceptsFirstResponder: have no effect, it was (and I should have noted) my first attempt at this problem. Thanks though.
neeklamy
+2  A: 

This can be accomplished with an NSPanel that's been set to not hide on deactivate and to become key only as needed. This also assumes that the controls you'll be using return YES to -acceptsFirstMouse:; NSButtons do so by default.

You can turn off the hide on deactivate flag through IB for the panel but you'll need to issue the -setBecomesKeyOnlyIfNeeded:YES message to the panel to keep it and the app from coming forward on button clicks.

Ashley Clark
I want to believe this is the answer, but reading the NSView class reference (where the `acceptsFirstMouse:` method is defined) says that it's set to `NO`. And finally subclassing `NSView` makes no difference anyway.
neeklamy
Just found a brief discussion on CocoaDev, http://www.cocoadev.com/index.pl?PreventWindowOrdering, this does what I want, it seems like a bad way to go about it though.
neeklamy
The docs for `acceptsFirstMouse:` specifically calls out `NSButton` as returning `YES`:"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."
Ashley Clark
If you can't use an `NSPanel` for the window where you want this behavior the method described on CocoaDev seems fine to me.
Ashley Clark
A: 

This isn't quite the answer I was looking for, but for now it works sufficiently well. By subclassing NSView and implementing the following methods, that view can then act as a button.

- (void)mouseDown:(NSEvent *)theEvent {
    [NSApp preventWindowOrdering];
//  [self setNeedsDisplay:YES];
}

- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
    return YES;
}

- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)theEvent {
    return YES;
}

Nothing else is required, that's it. Of course this bypasses all the NSButton goodness that I wanted to keep, I have to design and then take care of drawing and updating the button states, but I'm just glad to have an answer.

neeklamy