1 | // Copyright (C) 2017 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qquickpointhandler_p.h" |
5 | #include <private/qquickdeliveryagent_p_p.h> |
6 | #include <private/qquickwindow_p.h> |
7 | #include <QDebug> |
8 | #include <QtCore/qpointer.h> |
9 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | /*! |
13 | \qmltype PointHandler |
14 | \nativetype QQuickPointHandler |
15 | \inherits SinglePointHandler |
16 | \inqmlmodule QtQuick |
17 | \ingroup qtquick-input-handlers |
18 | \brief Handler for reacting to a single touchpoint. |
19 | |
20 | PointHandler can be used to show feedback about a touchpoint or the mouse |
21 | position, or to otherwise react to pointer events. |
22 | |
23 | When a press event occurs, each instance of PointHandler chooses a |
24 | single point which is not yet "taken" at that moment: if the press |
25 | occurs within the bounds of the \l {PointerHandler::parent}, and |
26 | no sibling PointHandler within the same \l {PointerHandler::parent} |
27 | has yet acquired a passive grab on that point, and if the other |
28 | constraints such as \l {PointerDeviceHandler::acceptedButtons}{acceptedButtons}, \l {PointerDeviceHandler::acceptedDevices}{acceptedDevices} etc. |
29 | are satisfied, it's |
30 | eligible, and the PointHandler then acquires a passive grab. In |
31 | this way, the \l {PointerHandler::parent} acts like an exclusive |
32 | group: there can be multiple instances of PointHandler, and the |
33 | set of pressed touchpoints will be distributed among them. Each |
34 | PointHandler which has chosen a point to track has its \l active |
35 | property \c true. It then continues to track its chosen point |
36 | until release: the properties of the \l point will be kept |
37 | up-to-date. Any Item can bind to these properties, and thereby |
38 | follow the point's movements. |
39 | |
40 | By being only a passive grabber, it has the ability to keep independent |
41 | oversight of all movements. The passive grab cannot be stolen or overridden |
42 | even when other gestures are detected and exclusive grabs occur. |
43 | |
44 | If your goal is orthogonal surveillance of eventpoints, an older |
45 | alternative was QObject::installEventFilter(), but that has never been a |
46 | built-in QtQuick feature: it requires some C++ code, such as a QQuickItem |
47 | subclass. PointHandler is more efficient than that, because only pointer |
48 | events will be delivered to it, during the course of normal event delivery |
49 | in QQuickWindow; whereas an event filter needs to filter all QEvents of all |
50 | types, and thus sets itself up as a potential event delivery bottleneck. |
51 | |
52 | One possible use case is to add this handler to a transparent Item which is |
53 | on top of the rest of the scene (by having a high \l{Item::z} {z} value), |
54 | so that when a point is freshly pressed, it will be delivered to that Item |
55 | and its handlers first, providing the opportunity to take the passive grab |
56 | as early as possible. Such an item (like a pane of glass over the whole UI) |
57 | can be a convenient parent for other Items which visualize the kind of reactive |
58 | feedback which must always be on top; and likewise it can be the parent for |
59 | popups, popovers, dialogs and so on. If it will be used in that way, it can |
60 | be helpful for your main.cpp to use QQmlContext::setContextProperty() to |
61 | make the "glass pane" accessible by ID to the entire UI, so that other |
62 | Items and PointHandlers can be reparented to it. |
63 | |
64 | \snippet pointerHandlers/pointHandler.qml 0 |
65 | |
66 | Like all input handlers, a PointHandler has a \l target property, which |
67 | may be used as a convenient place to put a point-tracking Item; but |
68 | PointHandler will not automatically manipulate the \c target item in any way. |
69 | You need to use bindings to make it react to the \l point. |
70 | |
71 | \note On macOS, PointHandler does not react to multiple fingers on the |
72 | trackpad by default, although it does react to a pressed point (mouse position). |
73 | That is because macOS can provide either native gesture recognition, or raw |
74 | touchpoints, but not both. We prefer to use the native gesture event in |
75 | PinchHandler, so we do not want to disable it by enabling touch. However |
76 | MultiPointTouchArea does enable touch, thus disabling native gesture |
77 | recognition within the entire window; so it's an alternative if you only |
78 | want to react to all the touchpoints but do not require the smooth |
79 | native-gesture experience. |
80 | |
81 | \sa MultiPointTouchArea, HoverHandler, {Qt Quick Examples - Pointer Handlers} |
82 | */ |
83 | |
84 | QQuickPointHandler::QQuickPointHandler(QQuickItem *parent) |
85 | : QQuickSinglePointHandler(parent) |
86 | { |
87 | setIgnoreAdditionalPoints(); |
88 | } |
89 | |
90 | bool QQuickPointHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) |
91 | { |
92 | // On press, we want it unless a sibling of the same type also does. |
93 | // If a synth-mouse press occurs, and we've already been interested in the original point, stay interested. |
94 | const bool trackedPointId = QQuickSinglePointHandler::point().id() == point.id() && |
95 | QQuickSinglePointHandler::point().device() == point.device(); |
96 | if ((point.state() == QEventPoint::Pressed && QQuickSinglePointHandler::wantsEventPoint(event, point)) |
97 | || (QQuickDeliveryAgentPrivate::isSynthMouse(ev: event) && trackedPointId)) { |
98 | for (const QObject *grabber : event->passiveGrabbers(point)) { |
99 | if (grabber && grabber != this && grabber->parent() == parent() && |
100 | grabber->metaObject()->className() == metaObject()->className()) |
101 | return false; |
102 | } |
103 | return true; |
104 | } |
105 | // If we've already been interested in a point, stay interested, even if it has strayed outside bounds. |
106 | return (point.state() != QEventPoint::Pressed && trackedPointId); |
107 | } |
108 | |
109 | void QQuickPointHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point) |
110 | { |
111 | switch (point.state()) { |
112 | case QEventPoint::Pressed: |
113 | if (acceptedButtons() == Qt::NoButton || !event->isSinglePointEvent() || |
114 | (static_cast<const QSinglePointEvent *>(event)->buttons() & acceptedButtons()) != Qt::NoButton) { |
115 | setPassiveGrab(event, point); |
116 | setActive(true); |
117 | } |
118 | break; |
119 | case QEventPoint::Released: |
120 | if (acceptedButtons() == Qt::NoButton || !event->isSinglePointEvent() || |
121 | (static_cast<const QSinglePointEvent *>(event)->buttons() & acceptedButtons()) == Qt::NoButton) |
122 | setActive(false); |
123 | break; |
124 | default: |
125 | break; |
126 | } |
127 | point.setAccepted(false); // Just lurking... don't interfere with propagation |
128 | emit translationChanged(); |
129 | if (!QQuickDeliveryAgentPrivate::isSynthMouse(ev: event)) |
130 | QQuickSinglePointHandler::handleEventPoint(event, point); |
131 | } |
132 | |
133 | QVector2D QQuickPointHandler::translation() const |
134 | { |
135 | return QVector2D(point().position() - point().pressPosition()); |
136 | } |
137 | |
138 | /*! |
139 | \qmlproperty flags PointHandler::acceptedButtons |
140 | |
141 | The mouse buttons that can activate this PointHandler. |
142 | |
143 | By default, this property is set to \l {QtQuick::MouseEvent::button} {Qt.LeftButton}. |
144 | It can be set to an OR combination of mouse buttons, and will ignore events |
145 | in which other buttons are pressed or held. If it is set to \c Qt.NoButton, |
146 | it means it does not care about buttons at all, and ignores synthetic |
147 | mouse events that come from any device for which it's already handling |
148 | an authentic \l eventPoint. |
149 | |
150 | \snippet pointerHandlers/pointHandlerAcceptedButtons.qml 0 |
151 | |
152 | \note On a touchscreen, there are no buttons, so this property does not |
153 | prevent PointHandler from reacting to touchpoints. |
154 | |
155 | \note By default, when this property holds \c Qt.LeftButton, if a |
156 | non-mouse \l {PointerDevice} (such as a touchscreen or graphics tablet |
157 | stylus) is allowed to generate synthetic mouse events, those usually |
158 | indicate that the left mouse button is pressed, and those events can |
159 | temporarily de-activate the PointHandler which was already reacting to an |
160 | authentic \l eventPoint from the device. It's useful to declare |
161 | \qml |
162 | acceptedButtons: \c Qt.NoButton |
163 | \endqml |
164 | to avoid this issue. See also |
165 | \l Qt::AA_SynthesizeMouseForUnhandledTouchEvents and |
166 | \l Qt::AA_SynthesizeMouseForUnhandledTabletEvents. |
167 | */ |
168 | |
169 | /*! |
170 | \qmlproperty flags PointHandler::acceptedDevices |
171 | |
172 | The types of pointing devices that can activate this PointHandler. |
173 | |
174 | By default, this property is set to |
175 | \l{QInputDevice::DeviceType}{PointerDevice.AllDevices}. |
176 | If you set it to an OR combination of device types, it will ignore events |
177 | from non-matching \l {PointerDevice}{devices}: |
178 | |
179 | \snippet pointerHandlers/pointHandler.qml 1 |
180 | */ |
181 | |
182 | /*! |
183 | \qmlproperty flags PointHandler::acceptedPointerTypes |
184 | |
185 | The types of pointing instruments (finger, stylus, eraser, etc.) |
186 | that can activate this PointHandler. |
187 | |
188 | By default, this property is set to |
189 | \l {QPointingDevice::PointerType} {PointerDevice.AllPointerTypes}. |
190 | If you set it to an OR combination of device types, it will ignore events |
191 | from non-matching \l {PointerDevice}{devices}: |
192 | |
193 | \snippet pointerHandlers/pointHandlerCanvasDrawing.qml 0 |
194 | |
195 | The \l {Qt Quick Examples - Pointer Handlers} includes a more complex example for |
196 | drawing on a Canvas with a graphics tablet. |
197 | */ |
198 | |
199 | /*! |
200 | \qmlproperty flags PointHandler::acceptedModifiers |
201 | |
202 | If this property is set, PointHandler requires the given keyboard modifiers |
203 | to be pressed in order to react to \l {PointerEvent}{PointerEvents}, and |
204 | otherwise ignores them. |
205 | |
206 | If this property is set to \c Qt.KeyboardModifierMask (the default value), |
207 | then PointHandler ignores the modifier keys. |
208 | |
209 | For example, an \l [QML] Item could have two handlers, one of which is |
210 | enabled only if the required keyboard modifier is pressed: |
211 | |
212 | \snippet pointerHandlers/pointHandlerAcceptedModifiers.qml 0 |
213 | |
214 | If you set \c acceptedModifiers to an OR combination of modifier keys, |
215 | it means \e all of those modifiers must be pressed to activate the handler. |
216 | |
217 | The available modifiers are as follows: |
218 | |
219 | \value NoModifier No modifier key is allowed. |
220 | \value ShiftModifier A Shift key on the keyboard must be pressed. |
221 | \value ControlModifier A Ctrl key on the keyboard must be pressed. |
222 | \value AltModifier An Alt key on the keyboard must be pressed. |
223 | \value MetaModifier A Meta key on the keyboard must be pressed. |
224 | \value KeypadModifier A keypad button must be pressed. |
225 | \value GroupSwitchModifier X11 only (unless activated on Windows by a command line argument). |
226 | A Mode_switch key on the keyboard must be pressed. |
227 | \value KeyboardModifierMask The handler does not care which modifiers are pressed. |
228 | |
229 | \sa Qt::KeyboardModifier |
230 | */ |
231 | |
232 | /*! |
233 | \readonly |
234 | \qmlproperty bool PointHandler::active |
235 | |
236 | This holds \c true whenever the constraints are satisfied and this |
237 | PointHandler is reacting. This means that it is keeping its properties |
238 | up-to-date according to the movements of the \l {eventPoint}{eventPoints} |
239 | that satisfy the constraints. |
240 | */ |
241 | |
242 | /*! |
243 | \internal |
244 | \qmlproperty flags PointHandler::dragThreshold |
245 | |
246 | This property is not used in PointHandler. |
247 | */ |
248 | |
249 | /*! |
250 | \qmlproperty real PointHandler::margin |
251 | |
252 | The margin beyond the bounds of the \l {PointerHandler::parent}{parent} |
253 | item within which an \l eventPoint can activate this handler. |
254 | |
255 | The default value is \c 0. |
256 | |
257 | \snippet pointerHandlers/pointHandlerMargin.qml 0 |
258 | */ |
259 | |
260 | /*! |
261 | \qmlproperty real PointHandler::target |
262 | |
263 | A property that can conveniently hold an Item to be manipulated or to show |
264 | feedback. Unlike other \l {Qt Quick Input Handlers}{Pointer Handlers}, |
265 | PointHandler does not do anything with the \c target on its own: you |
266 | usually need to create reactive bindings to properties such as |
267 | \l SinglePointHandler::point and \l PointHandler::active. If you declare |
268 | an Item instance here, you need to explicitly set its \l {Item::}{parent}, |
269 | because PointHandler is not an Item. |
270 | |
271 | By default, it is the same as the \l {PointerHandler::}{parent}, the Item |
272 | within which the handler is declared. |
273 | */ |
274 | |
275 | QT_END_NAMESPACE |
276 | |
277 | #include "moc_qquickpointhandler_p.cpp" |
278 | |