NSEvent and Magic Mouse - macos

How do I distinguish whether the event -(void)scrollWheel:(NSEvent *)event was triggered by a Magic Mouse or a trackpad?
The reason I'm asking this question is because I want to assign a different action to the scrolling event when a trackpad is used because the user can pinch to zoom on the trackpad. On the magic mouse, however, the user can't pinch easily, so I want to use the scrolling function as a substitute for pinching.
I can distinguish between a normal mouse and a Magic Mouse using this line:
if (([event momentumPhase] != NSEventPhaseNone) || [event phase] != NSEventPhaseNone)
However this test is passed for both, trackpad and Magic Mouse.

I was able to distinguish between a scroll wheel on a mouse (not a Magic Mouse, but I suspect it will still work) and a trackpad using NSEvent's subtype:
enum {
NSMouseEventSubtype = NX_SUBTYPE_DEFAULT,
NSTabletPointEventSubtype = NX_SUBTYPE_TABLET_POINT,
NSTabletProximityEventSubtype = NX_SUBTYPE_TABLET_PROXIMITY
NSTouchEventSubtype = NX_SUBTYPE_MOUSE_TOUCH
};

You should handle scrollWheel for the Magic Mouse and add a NSMagnificationGestureRecognizer for the pinch gesture on the trackpad. The two do not conflict with each-other but the swipe-scroll on the trackpad will trigger scrollWheel.

Related

How to distinguish real mouse wheel events in Qt6?

I'm trying to get zooming working so that the normal pan/pinch-zoom touchpad features work as expected, and scrolling a real mouse wheel zooms in/out (without any modifiers held).
Unfortunately it seems like touchpad pan gestures get converted into wheel events (there is Qt::PanGesture but it doesn't seem to be used). Therefore I need some way to distinguish real QWheelEvent events from touchpad ones.
The obvious thing is QWheelEvent::device()->type():
if (event->device()->type() == QInputDevice::DeviceType::Mouse)
// zoom
else
// pan
Unfortunately the QWheelEvents from my actual real physical mouse have a device()->type() of DeviceType::TouchPad.
I also tried
event->source() != Qt::MouseEventNotSynthesized
But again both real and touchpad events have that set to Qt::MouseEventSynthesizedBySystem.
Is there any way to do this?

Disable Gesture Recognizers in a *subview* so that superview Gesture Recognizers can run

I have an NSView (the canvas), into which I'm dropping a set of TextView subclasses (small boxes). The TextView classes have GestureRecognizers on them for drag drop (move) and select.
The canvas has two modes:
drawing
text
In text mode, I can interact with the TextView classes - I can click on them, edit etc.
When I switch to drawing mode, I want to disable the TextView boxes completely, and have all events/gestures be captured by the Canvas.
That way I can do Pan/Click events on the whole of the canvas, and the TextView boxes should be visible but otherwise ignored/disabled.
To enter drawing mode I disable the TextView boxes as follows (if the boolean "drawing" is true, then I turn off all event handling in the TextView boxes):
foreach (var tv in textBoxes)
{
tv.Editable = !drawing;
tv.Selectable = !drawing;
#if __IOS__
tv.UserInteractionEnabled = !drawing;
#else
tv.AcceptsTouchEvents = !drawing;
#endif
foreach (var g in tv.GestureRecognizers)
g.Enabled = !drawing;
}
So everything works perfectly between the two modes on iOS. On the Mac everything mostly work with one exception: in drawing mode (when the textview and textview gestures are disabled) if I start a gesture on top of the TextView box, nothing happens. Neither the Canvas gesture handler NOR the TextView box gesture handler are called. Starting a gesture anywhere else works fine and the Canvas gesture handler receives it.
In summary, my question is: how can I completely disable gestures in a subview, such that the superview gesture will be recognised, even if the gesture starts on top of the subview.
There are samples of handing a gesture to a subview, but here I want to hand a gesture to a superview (which should be top of the responder chain I thought).
Any advice would be gratefully accepted.
It seems that the solution is to ensure that the subview you want to receive gestures has to be at the top, otherwise gestures dont seem to reach it (this means it has to be the last item in the Subviews array).
In other words, if another subview "A" sits above your subview "B" - and that subview "A" is completely disabled with no gesture recognisers... it will stop any gestures in "B" from being recognised.
Gesture recognisers are not supposed to use the responder chain... (that is made clear in UIGestureRecognizer specs, but not NSGestureRecognizer specs). Here is a quote from the latter:
Events received by your app are forwarded automatically to any
relevant gesture recognizers before they are sent to the corresponding
view
This doesn't talk about how events flow up the responder chain for gesture recognisers on the Mac.
So as mentioned above - bring your subview to the front to ensure its gestures will be recognised. With the Mac, there is no way to easily order subviews of NSView (unlike UIView which has bringToFront, sendToBack etc). So the best way I have found is to use SortSubviews, with an appropriate ordering function.

How to make NSPopover properly follow the mouse pointer and ignore mouse events?

I would like to display an informational NSPopover that tracks the user's mouse cursor.
For this, I am using an NSTrackingArea to update the popover's positioningRect whenever the mouseMoved event fires.
However, this has two drawbacks:
The popover follows the mouse with a slight delay. How can I reduce this delay to make the popover appear more "glued" to the mouse pointer?
When I move the mouse pointer in the direction of the popover, the tracking area's mouseExited method gets called, which causes the popover to "absorb" the mouse movement events, so that the tracking area's mouseMoved event no longer fires. How can I avoid the popover absorbing the mouse events, or at least keep forwarding these events?
This question is very similar to Any way around this NSTrackingArea quirk?, with the distinction that I am using NSPopover, so I have nothing to set ignoresMouseEvents on.
I took a look at your problem. I was not able to eliminate the lag, but it might reduce if you set popover.animates to false.
Wrong approach:
I was able to solve the mouseExited over popover issue by adding a new border (and shadowless) window on top of the other one. The trackingArea is added to the transparent window, the popover to the original one. Depending on the transparent windows level, it is above the popover and therefore they can't interfere with each other.
In the gif below you can see the results of my tests:
Here is some of my code:
The mouse tracking:
override func mouseMoved(with event: NSEvent) {
let location = self.view.convert(event.locationInWindow, from: nil)
popover.positioningRect.origin.x = location.x
popover.positioningRect.origin.y = location.y
}
The custom window:
transparentWindow.backgroundColor = NSColor.clear
transparentWindow.isOpaque = false
transparentWindow.styleMask = .borderless
transparentWindow.makeKeyAndOrderFront(nil)
Update 11/11/2016:
I just read the question in the link you provided. There is a window to set ignoresMouseEvents on. Even though NSPopover inherits from NSObject, you have a contentViewController, which holds an view object, which holds the popovers window. (as explained here)
So simply set
popover.contentViewController?.view.window?.ignoresMouseEvents = true
after the popover is shown.

NSPageController, deactivate swipe gesture

I'm currently using a NSPageController to navigate between a Master and a Detail View in my OSX Application. I'm basically using it only to support transition animations, since there is no NavigationController on OSX.
However, the default NSPageController is listening on swipe gestures from my magic mouse and trackpad and changes the displayed view on a horizontal swipe. (like going back / forward in your browser). I would like to deactivate this behavior but overriding and intercepting any gesture method in a NSPageController subclass had no effect so far.
It seems like a NSScrollView / NSTableView does absorb these touches since doing a horizontal swipe gesture above any of my TableViews won't result in a view transition. (It only absorbs them for my magic mouse, doing a swipe gesture on my trackpad still results in switching views)
Target OS: Mac OSX 10.9 +
Thanks for your help!
You can make a subclass of NSPageController and overwrite
- (void)scrollWheel:(NSEvent *)theEvent
This should help.

Get position of 2nd finger in touch/pinch

How can I get the position of the 2nd touch in a view in iOS? I am using UITouch and I only know how to get the position of the 1st finger if I am doing a pinch gesture. I am doing the pinch gesture in the simulator via holding the Option Button and dragging on the view.
I don't know if you're using a gesture recognizer or trying to to act on UIResponder.
But in either case the delegate methods are handled an NSSet *touches,
this set can contain more than one object.
So you should enumerate over that set to get all the touches.

Resources