In Cocoa, how can my NSView receive an event when the mouse is held down (but not moved)? - cocoa

I'm looking for the right way to handle "mouse held down in one spot" events in my NSView subclass.
I am familiar with Cocoa's mouseDragged: event, but it is only triggered when the mouse moves. If the mouse stays in the same position, no drag event is triggered. Similarly, mouseDown: is only fired when the button is first pressed. My view needs to perform an action as long as the mouse is held down in a particular region.
What is the proper way to do this kind of thing?

Can you start performing the action when you receive a mouseDown: event, and stop when you receive mouseUp: (or mouseDragged:, if you want to stop then, too)?

I'm not sure exactly what you're trying to accomplish, but if you want an action to be repeated at set time intervals after the mouseDown:, you could set a recurring NSTimer in the mouseDown: method that gets cancelled as soon as there is a mouseDragged: or mouseUp: event.

Related

rightMouseDown not called until button released

I have an NSView subclass in which I need to detect left and right mouse down events. mouseDown: is working just fine, but rightMouseDown: doesn't fire until the mouse button has been released, at which point both the down and up methods are called in succession. How can I make the right mouse down event trigger its corresponding method immediately?
The problem was that I have a NSPanGestureRecognizer added to the NSView with its buttonMask set to 0x2 (right mouse button). If I remove or disable this gesture recogniser, it allows rightMouseDown: to be called when the right button is pressed down. I'm still trying to figure out why, but at least now I have a starting point.

Is it possible to subclass NSButton, overriding mouseDown and mouseUp events and use super Class drawing methods

Subclassing NSButton to capture mouseUp and mouseDown events but retain the drawing methods of NSButtons super class.
My goal is as stated above, to subclass NSButton and to have it perform it's regular super class functionality whilst allowing me to override mouseDown and mouseUp and send it back to the action with the NSEvent so the button code can then examine what type of even occurred and respond respectively.
As I experimented with the NSButton sub class, I noticed that when you override the mouseDown as below:
override open func mouseDown(with event: NSEvent)
{
super.mouseDown(with: event)
// call the target with left button down
_ = target?.perform(action, with: event)
}
What appears to be happening is the the super.mouseDown captures the mouse events and subsequently your sub class of NSButton will not receive the appropriate mouseUp event.
Seeing as that is how things are working, I simply did an override of mouseDown and mouseUp without calling any of the Super Class functions. This indeed provides an even for mouseDown and mouseUp. I forward the event to the action and let the action code process which event has occurred and everything is fine. The only caveat is that the default behavior for the button state is not occurring. The button will stay in it's original non-selected state. If I change the state and force an update to the button, one would think it would draw the button in it's selected state. This does not happen hence why I am writing for some assistance.
I would love to be able to have the default drawing behavior of NSButton mouseDown event occur. Is there a way to set a property of NSButton aka it's state and force a redraw? I can't seem to be able to do this. If there is no way to do this, then I will be forced to draw the buttons content in it's selected state some how via overriding the draw method.
Any help would be appreciated.
You could inspect the mouseUp event in the #IBAction of the button instead:
#IBAction private func buttonClicked(_: AnyObject?) {
NSLog("Event: \(NSApp.currentEvent)")
}
If you don’t need the mouseDown event then there might be no need to subclass.
Note that the button can be activated with a keyboard, so you won’t receive mouse events, but you will receive the event from the action above.

NSButton subclass still runs mouse event methods even when enabled is NO

The setEnabled: method is not working in my subclass of NSButton in which I have overridden mouse{down,drag,up} and rightMouse{down,drag,up}. I feel the enabling/disabling of the button should be outside these functions -- the button should not be receiving mouse events in the first place when it is disabled.
Do I have to make a check explicitly when I am overriding these functions?
I think that you do need to do your own check.
As explained in Apple's writeup on Cocoa Event Architechture, the window containing your button is sending mouseDown: (or whichever other method is appropriate) to your button in response to recieving an event. In order for the window to decide not to send the message, it would have to first determine that the button is an NSControl subclass (enabled being a property of NSControl, but not NSView) and then check that enabled flag. That is beyond the window's area of responsibility. A control being enabled isn't part of the event dispatch system the way first responder status is.
As an interesting piece of insight, if you take a look at GNUStep's -[NSControl mouseDown:] implementation, they do indeed check [self isEnabled] before handling the event.

When an NSWindow object has a delegate that is a NSWindow subclass, who is responsible to act on received events?

So I'm building a program that features the use of the IKImageBrowserView component as a subview in an NSWindow. As a side note, I have a controller object called ImageBrowserController which subclasses NSWindow and is set as the delegate of the NSWindow object of my app.
I have sent IKImageBrowserView the message setCanControlQuickLookPanel:YES to enable it to automatically use the QuickLook functionality to preview image files when the IKImageBrowserView is a first responder to receive key events. Then it took me a while to figure out how to make the IKImageBrowserView a first responder which I finally got working by overriding acceptsFirstResponder inside my ImageBrowserController.
Now I understand that as the delegate to the NSWindow, ImageBrowserController has a place in the responder chain after the event gets triggered on NSWindow. And I understand that as a subview of NSWindow, IKImageBrowserView is in line to be passed events for event handling. What I don't get is where the connection is between the ImageBrowserController being a first responder and the event somehow making it to the IKImageBrowserView. I didn't set NSWindow or IKImageBrowserView as first responders explicitly. So why isn't it necessary for me to implement event handling inside my ImageBrowserController?
EDIT: So after reading the accepted answer and going back to my code I tried removing the acceptsFirstResponder override in my ImageBrowserController and the QuickLook functionality still triggered just like the accepted answer said it would. Commenting out the setCanControlQuickLookPanel:YES made the app beep at me when I tried to invoke QuickLook functionality via the spacebar. I'm getting the feeling that my troubles were caused by user error of XCode in hitting the RUN button instead of the BUILD button after making changes to my code (sigh).
Some of what you are saying regarding the interactions between your objects does not make sense, and it is hard to address your stated question without some background.
As you say, your window delegate has a place at the end of the responder chain, after the window itself. The key point I think you are missing is that GUI elements, such as your IKImageBrowserView, will be at the beginning of the chain, and any one of them in a given window could be the current firstResponder.
When your application gets an event, it passes it off to the key window (which is just the window which currently accepts "key" (i.e., "keystroke") events). That window begins by asking its firstResponder to handle the event. If that object refuses, it passes the event to its own nextResponder, usually its superview, which either handles it or passes it on, until the event has either been handled or passed all the way up to the window object itself. Only then will the window (if it does not handle the event itself) ask its delegate to handle the event.
This means that the connection between the window delegate and the IKImageBrowserView is only through the Responder Chain, and its nature is simply that if the view declines to handle any given event, the delegate may eventually be asked to handle it, if no other object in between them handles it first.
Your window delegate does not need to be a firstResponder. Nor does overriding acceptsFirstResponder on the window delegate have any effect on one of the window's subviews.*
Your window delegate also does not need to (and, indeed should not) be a subclass of NSWindow. All it needs is to be a subclass of NSObject which implements whatever methods from the NSWindowDelegate Protocol you are interested in, and methods to handle any events you might want to catch if they are not handled by other objects.
So, the answer to your explicit question at the end is (and I do not mean this sarcastically): you only need to implement event handling in your window delegate if you want it to handle events itself.
*: IKImageBrowserView already responds YES to acceptsFirstResponder. If there are no other subviews in your window, it will automatically be the firstResponder when your application starts. You can set this initialFirstResponder explicitly in Interface Builder by connecting that outlet on the window to whatever object you want.

MouseDragged not released when dragging on other views

I have 2 subclasses of NSView that are subviews to a common superview. They dont overlap and they both intercept mousedragged calls. When I drag from one of the subclasses to the other the mousedragged function will be called until I release the mouse button even when I drag all over the screen. I though the default behavior was for the mousedragged function to be called only when the mouse was over the bounds of the receiver.
Iam also using NSTrackingArea for mouse enter, exit and move events, but from what I've been reading does not involve drag events
Thank you for your time,
Jose.
You could subclass the NSWindow and override sendEvent:. That way, you can intercept the NSLeftMouseDragged events and dispatch them in whatever way you wish.

Resources