QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskGestureRecognizer.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskGestureRecognizer.h"
7#include "QskEvent.h"
8#include "QskQuick.h"
9
10#include <qcoreapplication.h>
11#include <qquickitem.h>
12#include <qquickwindow.h>
13#include <qvector.h>
14
15#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
16 QSK_QT_PRIVATE_BEGIN
17 #include <private/qquickwindow_p.h>
18 QSK_QT_PRIVATE_END
19#endif
20
21static QMouseEvent* qskClonedMouseEvent( const QMouseEvent* event )
22{
23 QMouseEvent* clonedEvent;
24
25#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
26 clonedEvent = QQuickWindowPrivate::cloneMouseEvent(
27 const_cast< QMouseEvent* >( event ), nullptr );
28#else
29 clonedEvent = event->clone();
30#endif
31 clonedEvent->setAccepted( false );
32
33 return clonedEvent;
34}
35
36class QskGestureRecognizer::PrivateData
37{
38 public:
39 void startTimer( QskGestureRecognizer* recognizer )
40 {
41 if ( timeoutId >= 0 )
42 {
43 // warning
44 }
45
46 if ( timeout <= 0 )
47 {
48 // warning
49 }
50
51 timeoutId = recognizer->startTimer( timeout );
52 }
53
54 void stopTimer( QskGestureRecognizer* recognizer )
55 {
56 if ( timeoutId >= 0 )
57 {
58 recognizer->killTimer( timeoutId );
59 timeoutId = -1;
60 }
61 }
62
63 inline Qt::MouseButtons effectiveMouseButtons() const
64 {
65 if ( buttons != Qt::NoButton )
66 return buttons;
67
68 return watchedItem->acceptedMouseButtons();
69 }
70
71 QPointer< QQuickItem > watchedItem = nullptr;
72 QPointer< QQuickItem > targetItem = nullptr;
73
74 QVector< QMouseEvent* > pendingEvents;
75
76 quint64 timestampStarted = 0;
77 quint64 timestampProcessed = 0;
78
79 int timeoutId = -1;
80 int timeout = -1; // ms
81
82 Qt::MouseButtons buttons = Qt::NoButton;
83
84 unsigned char state = QskGestureRecognizer::Idle;
85 bool rejectOnTimeout = true;
86 bool expired = false;
87};
88
89QskGestureRecognizer::QskGestureRecognizer( QObject* parent )
90 : QObject( parent )
91 , m_data( new PrivateData() )
92{
93}
94
95QskGestureRecognizer::~QskGestureRecognizer()
96{
97 qDeleteAll( m_data->pendingEvents );
98}
99
100void QskGestureRecognizer::setWatchedItem( QQuickItem* item )
101{
102 if ( m_data->watchedItem )
103 {
104 m_data->watchedItem->removeEventFilter( this );
105 reset();
106 }
107
108 m_data->watchedItem = item;
109
110 if ( m_data->watchedItem )
111 m_data->watchedItem->installEventFilter( this );
112
113 /*
114 // doing those here: ???
115 m_data->watchedItem->setFiltersChildMouseEvents();
116 m_data->watchedItem->setAcceptedMouseButtons();
117 */
118}
119
120QQuickItem* QskGestureRecognizer::watchedItem() const
121{
122 return m_data->watchedItem;
123}
124
125void QskGestureRecognizer::setTargetItem( QQuickItem* item )
126{
127 m_data->targetItem = item;
128}
129
130QQuickItem* QskGestureRecognizer::targetItem() const
131{
132 return m_data->targetItem;
133}
134
135void QskGestureRecognizer::setAcceptedMouseButtons( Qt::MouseButtons buttons )
136{
137 m_data->buttons = buttons;
138}
139
140Qt::MouseButtons QskGestureRecognizer::acceptedMouseButtons() const
141{
142 return m_data->buttons;
143}
144
145bool QskGestureRecognizer::isAcceptedPos( const QPointF& pos ) const
146{
147 return m_data->watchedItem && m_data->watchedItem->contains( pos );
148}
149
150void QskGestureRecognizer::setRejectOnTimeout( bool on )
151{
152 m_data->rejectOnTimeout = on;
153}
154
155bool QskGestureRecognizer::rejectOnTimeout() const
156{
157 return m_data->rejectOnTimeout;
158}
159
160void QskGestureRecognizer::setTimeout( int ms )
161{
162 m_data->timeout = ms;
163}
164
165int QskGestureRecognizer::timeout() const
166{
167 return m_data->timeout;
168}
169
170void QskGestureRecognizer::timerEvent( QTimerEvent* event )
171{
172 if ( event->timerId() == m_data->timeoutId )
173 {
174 m_data->stopTimer( this );
175
176 if ( m_data->rejectOnTimeout )
177 {
178 reject();
179 m_data->expired = true;
180 }
181
182 return;
183 }
184
185 Inherited::timerEvent( event );
186}
187
188quint64 QskGestureRecognizer::timestampStarted() const
189{
190 return m_data->timestampStarted;
191}
192
193void QskGestureRecognizer::setState( State state )
194{
195 if ( state != m_data->state )
196 {
197 const auto oldState = static_cast< State >( m_data->state );
198 m_data->state = state;
199
200 Q_EMIT stateChanged( oldState, state );
201 }
202}
203
204QskGestureRecognizer::State QskGestureRecognizer::state() const
205{
206 return static_cast< QskGestureRecognizer::State >( m_data->state );
207}
208
209bool QskGestureRecognizer::eventFilter( QObject* object, QEvent* event)
210{
211 auto& watchedItem = m_data->watchedItem;
212
213 if ( ( object == watchedItem ) &&
214 ( static_cast< int >( event->type() ) == QskEvent::GestureFilter ) )
215 {
216 if ( !watchedItem->isEnabled() || !watchedItem->isVisible()
217 || watchedItem->window() == nullptr )
218 {
219 reset();
220 return false;
221 }
222
223 auto ev = static_cast< QskGestureFilterEvent* >( event );
224 ev->setMaybeGesture( maybeGesture( ev->item(), ev->event() ) );
225
226 return ev->maybeGesture();
227 }
228
229 return false;
230}
231
232bool QskGestureRecognizer::maybeGesture(
233 const QQuickItem* item, const QEvent* event )
234{
235 switch( static_cast< int >( event->type() ) )
236 {
237 case QEvent::UngrabMouse:
238 {
239 if ( m_data->state != Idle )
240 {
241 // someone took our grab away, we have to give up
242 reset();
243 return true;
244 }
245
246 return false;
247 }
248 case QEvent::MouseButtonPress:
249 {
250 if ( state() != Idle )
251 {
252 qWarning() << "QskGestureRecognizer: pressed, while not being idle";
253 return false;
254 }
255
256 const auto mouseEvent = static_cast< const QMouseEvent* >( event );
257
258 if ( mouseEvent->timestamp() > m_data->timestampProcessed )
259 {
260 m_data->timestampProcessed = mouseEvent->timestamp();
261 }
262 else
263 {
264 /*
265 A mouse event might appear several times:
266
267 - we ran into a timeout and reposted the events
268
269 - we rejected because the event sequence does
270 not match the acceptance criterions and reposted
271 the events
272
273 - another gesture recognizer for a child item
274 has reposted the events because of the reasons above
275
276 For most situations we can simply skip processing already
277 processed events with the exception of a timeout. Here we might
278 have to retry without timeout, when none of the items was
279 accepting the events. This specific situation is indicated by
280 item set to null.
281 */
282
283 if ( item || !m_data->expired )
284 return false;
285 }
286
287 return processMouseEvent( item, mouseEvent );
288 }
289 case QEvent::MouseMove:
290 case QEvent::MouseButtonRelease:
291 {
292 if ( state() <= Idle )
293 return false;
294
295 const auto mouseEvent = static_cast< const QMouseEvent* >( event );
296 return processMouseEvent( item, mouseEvent );
297 }
298 }
299
300 return false;
301}
302
303bool QskGestureRecognizer::processMouseEvent(
304 const QQuickItem* item, const QMouseEvent* event )
305{
306 auto& watchedItem = m_data->watchedItem;
307
308 const auto pos = watchedItem->mapFromScene( qskMouseScenePosition( event ) );
309 const auto timestamp = event->timestamp();
310
311 if ( event->type() == QEvent::MouseButtonPress )
312 {
313 if ( !isAcceptedPos( pos ) )
314 return false;
315
316 if ( m_data->state != Idle )
317 {
318 // should not happen, when using the recognizer correctly
319 qWarning() << "QskGestureRecognizer: pressed, while not being idle";
320 abort();
321 return false;
322 }
323
324 if ( !( m_data->effectiveMouseButtons() & event->button() ) )
325 return false;
326
327 /*
328 We try to grab the mouse for watchedItem and indicate, that we want
329 to keep it. Then all mouse events should end up at watchedItem.
330 */
331 if ( !qskGrabMouse( watchedItem ) )
332 return false;
333
334 m_data->timestampStarted = timestamp;
335
336 if ( m_data->timeout != 0 )
337 {
338 /*
339 We need to be able to replay the press in case we later find
340 out, that we don't want to handle the mouse event sequence,
341 */
342
343 m_data->stopTimer( this );
344
345 if ( m_data->timeout > 0 )
346 m_data->startTimer( this );
347
348 setState( Pending );
349 }
350 else
351 {
352 accept();
353 }
354 }
355
356 if ( m_data->state <= Idle )
357 return false;
358
359 if ( m_data->state == Pending )
360 m_data->pendingEvents += qskClonedMouseEvent( event );
361
362 switch( static_cast< int >( event->type() ) )
363 {
364 case QEvent::MouseButtonPress:
365 processPress( pos, timestamp, item == nullptr );
366 break;
367
368 case QEvent::MouseMove:
369 processMove( pos, timestamp );
370 break;
371
372 case QEvent::MouseButtonRelease:
373 {
374 if ( m_data->state == Pending )
375 {
376 reject();
377 }
378 else
379 {
380 processRelease( pos, timestamp );
381 reset();
382 }
383 break;
384 }
385 }
386
387 return true;
388}
389
390void QskGestureRecognizer::processPress(
391 const QPointF& pos, quint64 timestamp, bool isFinal )
392{
393 Q_UNUSED( pos );
394 Q_UNUSED( timestamp );
395 Q_UNUSED( isFinal );
396}
397
398void QskGestureRecognizer::processMove( const QPointF& pos, quint64 timestamp )
399{
400 Q_UNUSED( pos );
401 Q_UNUSED( timestamp );
402}
403
404void QskGestureRecognizer::processRelease( const QPointF& pos, quint64 timestamp )
405{
406 Q_UNUSED( pos );
407 Q_UNUSED( timestamp );
408}
409
410void QskGestureRecognizer::accept()
411{
412 m_data->stopTimer( this );
413
414 qDeleteAll( m_data->pendingEvents );
415 m_data->pendingEvents.clear();
416
417 setState( Accepted );
418}
419
420void QskGestureRecognizer::reject()
421{
422 /*
423 Moving the events to a local buffer, so that we can clear
424 m_data->pendingEvents before replaying them.
425 */
426 const auto events = m_data->pendingEvents;
427 m_data->pendingEvents.clear();
428
429 reset();
430
431 /*
432 Not 100% sure if we should send or post the events
433
434 Posting the events adds an extra round trip but we avoid
435 recursive event handler calls, that might not be expected
436 from the implementation of a control.
437 */
438
439 if ( !events.isEmpty() )
440 {
441 if ( auto watchedItem = m_data->watchedItem )
442 {
443 if ( const auto window = watchedItem->window() )
444 {
445 for ( auto event : events )
446 QCoreApplication::postEvent( window, event );
447 }
448 }
449
450#if 0
451 // when using QCoreApplication::sendEvent above
452 qDeleteAll( events );
453#endif
454 }
455}
456
457void QskGestureRecognizer::abort()
458{
459 reset();
460}
461
462void QskGestureRecognizer::reset()
463{
464 m_data->stopTimer( this );
465 qskUngrabMouse( m_data->watchedItem );
466
467 qDeleteAll( m_data->pendingEvents );
468 m_data->pendingEvents.clear();
469
470 m_data->timestampStarted = 0;
471 m_data->expired = false;
472
473 setState( Idle );
474}
475
476#include "moc_QskGestureRecognizer.cpp"