| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the test suite of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | #include "qdeclarativepinchgenerator_p.h" |
| 30 | |
| 31 | #include <QtTest/QtTest> |
| 32 | #include <QtGui/QGuiApplication> |
| 33 | #include <QtGui/qpa/qwindowsysteminterface.h> |
| 34 | #include <QtGui/QStyleHints> |
| 35 | |
| 36 | QT_BEGIN_NAMESPACE |
| 37 | |
| 38 | QDeclarativePinchGenerator::QDeclarativePinchGenerator(): |
| 39 | target_(0), |
| 40 | state_(Invalid), |
| 41 | window_(0), |
| 42 | activeSwipe_(0), |
| 43 | replayTimer_(-1), |
| 44 | replayBookmark_(-1), |
| 45 | masterSwipe_(-1), |
| 46 | replaySpeedFactor_(1.0), |
| 47 | enabled_(true) |
| 48 | { |
| 49 | setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); |
| 50 | swipeTimer_.invalidate(); |
| 51 | device_ = new QTouchDevice; |
| 52 | device_->setType(QTouchDevice::TouchScreen); |
| 53 | QWindowSystemInterface::registerTouchDevice(device: device_); |
| 54 | } |
| 55 | |
| 56 | QDeclarativePinchGenerator::~QDeclarativePinchGenerator() |
| 57 | { |
| 58 | clear(); |
| 59 | } |
| 60 | |
| 61 | void QDeclarativePinchGenerator::componentComplete() |
| 62 | { |
| 63 | QQuickItem::componentComplete(); |
| 64 | } |
| 65 | |
| 66 | void QDeclarativePinchGenerator::mousePressEvent(QMouseEvent *event) |
| 67 | { |
| 68 | if (state_ != Idle || !enabled_) { |
| 69 | event->ignore(); |
| 70 | return; |
| 71 | } |
| 72 | Q_ASSERT(!activeSwipe_); |
| 73 | Q_ASSERT(!swipeTimer_.isValid()); |
| 74 | // Start recording a pinch gesture. |
| 75 | activeSwipe_ = new Swipe; |
| 76 | activeSwipe_->touchPoints << event->pos(); |
| 77 | activeSwipe_->durations << 0; |
| 78 | swipeTimer_.start(); |
| 79 | setState(Recording); |
| 80 | } |
| 81 | |
| 82 | void QDeclarativePinchGenerator::mouseMoveEvent(QMouseEvent *event) |
| 83 | { |
| 84 | if (state_ != Recording || !enabled_) { |
| 85 | event->ignore(); |
| 86 | return; |
| 87 | } |
| 88 | Q_ASSERT(activeSwipe_); |
| 89 | Q_ASSERT(swipeTimer_.isValid()); |
| 90 | |
| 91 | activeSwipe_->touchPoints << event->pos(); |
| 92 | activeSwipe_->durations << swipeTimer_.elapsed(); |
| 93 | swipeTimer_.restart(); |
| 94 | } |
| 95 | |
| 96 | void QDeclarativePinchGenerator::mouseReleaseEvent(QMouseEvent *event) |
| 97 | { |
| 98 | if (state_ != Recording || !enabled_) { |
| 99 | event->ignore(); |
| 100 | return; |
| 101 | } |
| 102 | Q_ASSERT(activeSwipe_); |
| 103 | Q_ASSERT(swipeTimer_.isValid()); |
| 104 | activeSwipe_->touchPoints << event->pos(); |
| 105 | activeSwipe_->durations << swipeTimer_.elapsed(); |
| 106 | |
| 107 | if (swipes_.count() == SWIPES_REQUIRED) |
| 108 | delete swipes_.takeFirst(); |
| 109 | swipes_ << activeSwipe_; |
| 110 | activeSwipe_ = 0; |
| 111 | swipeTimer_.invalidate(); |
| 112 | if (window_ && target_) setState(Idle); else setState(Invalid); |
| 113 | } |
| 114 | |
| 115 | void QDeclarativePinchGenerator::mouseDoubleClickEvent(QMouseEvent *event) |
| 116 | { |
| 117 | Q_UNUSED(event); |
| 118 | if (!enabled_) { |
| 119 | event->ignore(); |
| 120 | return; |
| 121 | } |
| 122 | stop(); |
| 123 | clear(); |
| 124 | if (window_ && target_) setState(Idle); else setState(Invalid); |
| 125 | } |
| 126 | |
| 127 | void QDeclarativePinchGenerator::keyPressEvent(QKeyEvent *e) |
| 128 | { |
| 129 | if (!enabled_) { |
| 130 | e->ignore(); |
| 131 | } |
| 132 | |
| 133 | if (e->key() == Qt::Key_C) { |
| 134 | clear(); |
| 135 | } else if (e->key() == Qt::Key_R) { |
| 136 | replay(); |
| 137 | } else if (e->key() == Qt::Key_S) { |
| 138 | stop(); |
| 139 | } else if (e->key() == Qt::Key_Plus) { |
| 140 | setReplaySpeedFactor(replaySpeedFactor() + 0.1); |
| 141 | } else if (e->key() == Qt::Key_Minus) { |
| 142 | setReplaySpeedFactor(replaySpeedFactor() - 0.1); |
| 143 | } else { |
| 144 | qDebug() << metaObject()->className() << "Unsupported key event." ; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | bool QDeclarativePinchGenerator::enabled() const |
| 149 | { |
| 150 | return enabled_; |
| 151 | } |
| 152 | |
| 153 | |
| 154 | void QDeclarativePinchGenerator::setEnabled(bool enabled) |
| 155 | { |
| 156 | if (enabled == enabled_) |
| 157 | return; |
| 158 | enabled_ = enabled; |
| 159 | if (!enabled_) { |
| 160 | stop(); |
| 161 | clear(); |
| 162 | } |
| 163 | emit enabledChanged(); |
| 164 | } |
| 165 | |
| 166 | |
| 167 | qreal QDeclarativePinchGenerator::replaySpeedFactor() const |
| 168 | { |
| 169 | return replaySpeedFactor_; |
| 170 | } |
| 171 | |
| 172 | void QDeclarativePinchGenerator::setReplaySpeedFactor(qreal factor) |
| 173 | { |
| 174 | if (factor == replaySpeedFactor_ || factor < 0.001) |
| 175 | return; |
| 176 | replaySpeedFactor_ = factor; |
| 177 | emit replaySpeedFactorChanged(); |
| 178 | } |
| 179 | |
| 180 | |
| 181 | QString QDeclarativePinchGenerator::state() const |
| 182 | { |
| 183 | switch (state_) { |
| 184 | case Invalid: |
| 185 | return "Invalid" ; |
| 186 | case Idle: |
| 187 | return "Idle" ; |
| 188 | break; |
| 189 | case Recording: |
| 190 | return "Recording" ; |
| 191 | break; |
| 192 | case Replaying: |
| 193 | return "Replaying" ; |
| 194 | break; |
| 195 | default: |
| 196 | Q_ASSERT(false); |
| 197 | } |
| 198 | return "How emberassing" ; |
| 199 | } |
| 200 | |
| 201 | void QDeclarativePinchGenerator::setState(GeneratorState state) |
| 202 | { |
| 203 | if (state == state_) |
| 204 | return; |
| 205 | state_ = state; |
| 206 | emit stateChanged(); |
| 207 | } |
| 208 | |
| 209 | void QDeclarativePinchGenerator::itemChange(ItemChange change, const ItemChangeData & data) |
| 210 | { |
| 211 | if (change == ItemSceneChange) { |
| 212 | window_ = data.window; |
| 213 | if (target_) |
| 214 | setState(Idle); |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | void QDeclarativePinchGenerator::timerEvent(QTimerEvent *event) |
| 219 | { |
| 220 | Q_ASSERT(replayTimer_ == event->timerId()); |
| 221 | Q_UNUSED(event); |
| 222 | Q_ASSERT(state_ == Replaying); |
| 223 | |
| 224 | int slaveSwipe = masterSwipe_ ^ 1; |
| 225 | |
| 226 | int masterCount = swipes_.at(i: masterSwipe_)->touchPoints.count(); |
| 227 | int slaveCount = swipes_.at(i: slaveSwipe)->touchPoints.count(); |
| 228 | |
| 229 | if (replayBookmark_ == 0) { |
| 230 | QTest::touchEvent(window: window_, device: device_) |
| 231 | .press(touchId: 0, pt: swipes_.at(i: masterSwipe_)->touchPoints.at(i: replayBookmark_)) |
| 232 | .press(touchId: 1, pt: swipes_.at(i: slaveSwipe)->touchPoints.at(i: replayBookmark_)); |
| 233 | } else if (replayBookmark_ == (slaveCount - 1)) { |
| 234 | if (masterCount != slaveCount) { |
| 235 | QTest::touchEvent(window: window_, device: device_) |
| 236 | .move(touchId: 0, pt: swipes_.at(i: masterSwipe_)->touchPoints.at(i: replayBookmark_)) |
| 237 | .release(touchId: 1, pt: swipes_.at(i: slaveSwipe)->touchPoints.at(i: replayBookmark_)); |
| 238 | } else { |
| 239 | QTest::touchEvent(window: window_, device: device_) |
| 240 | .release(touchId: 0, pt: swipes_.at(i: masterSwipe_)->touchPoints.at(i: replayBookmark_)) |
| 241 | .release(touchId: 1, pt: swipes_.at(i: slaveSwipe)->touchPoints.at(i: replayBookmark_)); |
| 242 | } |
| 243 | } else if (replayBookmark_ == (masterCount - 1)) { |
| 244 | QTest::touchEvent(window: window_, device: device_) |
| 245 | .release(touchId: 0, pt: swipes_.at(i: masterSwipe_)->touchPoints.at(i: replayBookmark_)); |
| 246 | } |
| 247 | else { |
| 248 | QTest::touchEvent(window: window_, device: device_) |
| 249 | .move(touchId: 0, pt: swipes_.at(i: masterSwipe_)->touchPoints.at(i: replayBookmark_)) |
| 250 | .move(touchId: 1, pt: swipes_.at(i: slaveSwipe)->touchPoints.at(i: replayBookmark_)); |
| 251 | } |
| 252 | |
| 253 | replayBookmark_++; |
| 254 | if (replayBookmark_ >= swipes_.at(i: masterSwipe_)->touchPoints.count()) |
| 255 | stop(); |
| 256 | else { |
| 257 | killTimer(id: replayTimer_); |
| 258 | replayTimer_ = startTimer(interval: (swipes_.at(i: masterSwipe_)->durations.at(i: replayBookmark_) + 5) / replaySpeedFactor_ ); |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | QQuickItem* QDeclarativePinchGenerator::target() const |
| 263 | { |
| 264 | return target_; |
| 265 | } |
| 266 | |
| 267 | void QDeclarativePinchGenerator::setTarget(QQuickItem* target) |
| 268 | { |
| 269 | if (target == target_) |
| 270 | return; |
| 271 | target_ = target; |
| 272 | stop(); |
| 273 | clear(); |
| 274 | if (window_) |
| 275 | setState(Idle); |
| 276 | else |
| 277 | setState(Invalid); |
| 278 | emit targetChanged(); |
| 279 | } |
| 280 | |
| 281 | void QDeclarativePinchGenerator::pinch(QPoint point1From, |
| 282 | QPoint point1To, |
| 283 | QPoint point2From, |
| 284 | QPoint point2To, |
| 285 | int interval1, |
| 286 | int interval2, |
| 287 | int samples1, |
| 288 | int samples2) |
| 289 | { |
| 290 | Q_ASSERT(interval1 > 10); |
| 291 | Q_ASSERT(interval2 > 10); |
| 292 | Q_ASSERT(samples1 >= 2); // we need press and release events at minimum |
| 293 | Q_ASSERT(samples2 >= 2); |
| 294 | |
| 295 | clear(); |
| 296 | |
| 297 | Swipe* swipe1 = new Swipe; |
| 298 | Swipe* swipe2 = new Swipe; |
| 299 | for (int i = 0; i < samples1; ++i) { |
| 300 | swipe1->touchPoints << point1From + (point1To - point1From) / samples1 * i; |
| 301 | swipe1->durations << interval1; |
| 302 | } |
| 303 | for (int i = 0; i < samples2; ++i) { |
| 304 | swipe2->touchPoints << point2From + (point2To - point2From) / samples2 * i; |
| 305 | swipe2->durations << interval2; |
| 306 | } |
| 307 | swipes_ << swipe1 << swipe2; |
| 308 | Q_ASSERT(swipes_.at(0)); |
| 309 | Q_ASSERT(swipes_.at(1)); |
| 310 | |
| 311 | masterSwipe_ = (samples1 >= samples2) ? 0 : 1; |
| 312 | |
| 313 | replayTimer_ = startTimer(interval: swipes_.at(i: masterSwipe_)->durations.at(i: 0) / replaySpeedFactor_); |
| 314 | replayBookmark_ = 0; |
| 315 | setState(Replaying); |
| 316 | } |
| 317 | |
| 318 | void QDeclarativePinchGenerator::pinchPress(QPoint point1From, QPoint point2From) |
| 319 | { |
| 320 | QTest::touchEvent(window: window_, device: device_).press(touchId: 0, pt: point1From).press(touchId: 1, pt: point2From); |
| 321 | } |
| 322 | |
| 323 | void QDeclarativePinchGenerator::pinchMoveTo(QPoint point1To, QPoint point2To) |
| 324 | { |
| 325 | QTest::touchEvent(window: window_, device: device_).move(touchId: 0, pt: point1To).move(touchId: 1, pt: point2To); |
| 326 | } |
| 327 | |
| 328 | void QDeclarativePinchGenerator::pinchRelease(QPoint point1To, QPoint point2To) |
| 329 | { |
| 330 | QTest::touchEvent(window: window_, device: device_).release(touchId: 0, pt: point1To).release(touchId: 1, pt: point2To); |
| 331 | } |
| 332 | |
| 333 | void QDeclarativePinchGenerator::replay() |
| 334 | { |
| 335 | if (state_ != Idle) { |
| 336 | qDebug() << "Wrong state, will not replay pinch, state: " << state_; |
| 337 | return; |
| 338 | } |
| 339 | if (swipes_.count() < SWIPES_REQUIRED) { |
| 340 | qDebug() << "Too few swipes, cannot replay, amount: " << swipes_.count(); |
| 341 | return; |
| 342 | } |
| 343 | if ((swipes_.at(i: 0)->touchPoints.count() < 2) || (swipes_.at(i: 1)->touchPoints.count() < 2)) { |
| 344 | qDebug() << "Too few touchpoints, won't replay, amount: " << |
| 345 | swipes_.at(i: 0)->touchPoints.count() << (swipes_.at(i: 1)->touchPoints.count() < 2); |
| 346 | return; |
| 347 | } |
| 348 | |
| 349 | masterSwipe_ = (swipes_.at(i: 0)->touchPoints.count() >= swipes_.at(i: 1)->touchPoints.count()) ? 0 : 1; |
| 350 | |
| 351 | replayTimer_ = startTimer(interval: swipes_.at(i: masterSwipe_)->touchPoints.count() / replaySpeedFactor_); |
| 352 | replayBookmark_ = 0; |
| 353 | setState(Replaying); |
| 354 | } |
| 355 | |
| 356 | void QDeclarativePinchGenerator::clear() |
| 357 | { |
| 358 | stop(); |
| 359 | delete activeSwipe_; |
| 360 | activeSwipe_ = 0; |
| 361 | if (!swipes_.isEmpty()) { |
| 362 | qDeleteAll(c: swipes_); |
| 363 | swipes_.clear(); |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | void QDeclarativePinchGenerator::stop() |
| 368 | { |
| 369 | if (state_ != Replaying) |
| 370 | return; |
| 371 | // stop replay |
| 372 | Q_ASSERT(replayTimer_ != -1); |
| 373 | killTimer(id: replayTimer_); |
| 374 | replayTimer_ = -1; |
| 375 | setState(Idle); |
| 376 | } |
| 377 | |
| 378 | int QDeclarativePinchGenerator::startDragDistance() |
| 379 | { |
| 380 | return qApp->styleHints()->startDragDistance(); |
| 381 | } |
| 382 | |
| 383 | QT_END_NAMESPACE |
| 384 | |