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