| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2017 The Qt Company Ltd. |
| 4 | ** Contact: http://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:LGPL3$ |
| 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 http://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at http://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU Lesser General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
| 21 | ** packaging of this file. Please review the following information to |
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
| 24 | ** |
| 25 | ** GNU General Public License Usage |
| 26 | ** Alternatively, this file may be used under the terms of the GNU |
| 27 | ** General Public License version 2.0 or later as published by the Free |
| 28 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
| 29 | ** the packaging of this file. Please review the following information to |
| 30 | ** ensure the GNU General Public License version 2.0 requirements will be |
| 31 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
| 32 | ** |
| 33 | ** $QT_END_LICENSE$ |
| 34 | ** |
| 35 | ****************************************************************************/ |
| 36 | |
| 37 | #include "qquickdial_p.h" |
| 38 | #include "qquickdeferredexecute_p_p.h" |
| 39 | |
| 40 | #include <QtCore/qmath.h> |
| 41 | #include <QtQuick/private/qquickflickable_p.h> |
| 42 | #include <QtQuickTemplates2/private/qquickcontrol_p_p.h> |
| 43 | |
| 44 | #include <cmath> |
| 45 | |
| 46 | QT_BEGIN_NAMESPACE |
| 47 | |
| 48 | /*! |
| 49 | \qmltype Dial |
| 50 | \inherits Control |
| 51 | //! \instantiates QQuickDial |
| 52 | \inqmlmodule QtQuick.Controls |
| 53 | \since 5.7 |
| 54 | \ingroup qtquickcontrols2-input |
| 55 | \brief Circular dial that is rotated to set a value. |
| 56 | |
| 57 | The Dial is similar to a traditional dial knob that is found on devices |
| 58 | such as stereos or industrial equipment. It allows the user to specify a |
| 59 | value within a range. |
| 60 | |
| 61 | \image qtquickcontrols2-dial-no-wrap.gif |
| 62 | |
| 63 | The value of the dial is set with the \l value property. The range is |
| 64 | set with the \l from and \l to properties. To enable or disable wrapping, |
| 65 | use the \l wrap property. |
| 66 | |
| 67 | The dial can be manipulated with a keyboard. It supports the following |
| 68 | actions: |
| 69 | |
| 70 | \table |
| 71 | \header \li \b {Action} \li \b {Key} |
| 72 | \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Left |
| 73 | \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Down |
| 74 | \row \li Set \l value to \l from \li \c Qt.Key_Home |
| 75 | \row \li Increase \l value by \l stepSize \li \c Qt.Key_Right |
| 76 | \row \li Increase \l value by \l stepSize \li \c Qt.Key_Up |
| 77 | \row \li Set \l value to \l to \li \c Qt.Key_End |
| 78 | \endtable |
| 79 | |
| 80 | \include qquickdial.qdocinc inputMode |
| 81 | |
| 82 | \sa {Customizing Dial}, {Input Controls} |
| 83 | */ |
| 84 | |
| 85 | /*! |
| 86 | \since QtQuick.Controls 2.2 (Qt 5.9) |
| 87 | \qmlsignal QtQuick.Controls::Dial::moved() |
| 88 | |
| 89 | This signal is emitted when the dial has been interactively moved |
| 90 | by the user by either touch, mouse, or keys. |
| 91 | */ |
| 92 | |
| 93 | static const qreal startAngleRadians = (M_PI * 2.0) * (4.0 / 6.0); |
| 94 | static const qreal startAngle = -140; |
| 95 | static const qreal endAngleRadians = (M_PI * 2.0) * (5.0 / 6.0); |
| 96 | static const qreal endAngle = 140; |
| 97 | |
| 98 | class QQuickDialPrivate : public QQuickControlPrivate |
| 99 | { |
| 100 | Q_DECLARE_PUBLIC(QQuickDial) |
| 101 | |
| 102 | public: |
| 103 | qreal valueAt(qreal position) const; |
| 104 | qreal snapPosition(qreal position) const; |
| 105 | qreal positionAt(const QPointF &point) const; |
| 106 | qreal circularPositionAt(const QPointF &point) const; |
| 107 | qreal linearPositionAt(const QPointF &point) const; |
| 108 | void setPosition(qreal position); |
| 109 | void updatePosition(); |
| 110 | bool isLargeChange(const QPointF &eventPos, qreal proposedPosition) const; |
| 111 | bool isHorizontalOrVertical() const; |
| 112 | |
| 113 | void handlePress(const QPointF &point) override; |
| 114 | void handleMove(const QPointF &point) override; |
| 115 | void handleRelease(const QPointF &point) override; |
| 116 | void handleUngrab() override; |
| 117 | |
| 118 | void cancelHandle(); |
| 119 | void executeHandle(bool complete = false); |
| 120 | |
| 121 | void updateAllValuesAreInteger(); |
| 122 | |
| 123 | qreal from = 0; |
| 124 | qreal to = 1; |
| 125 | qreal value = 0; |
| 126 | qreal position = 0; |
| 127 | qreal angle = startAngle; |
| 128 | qreal stepSize = 0; |
| 129 | QPointF pressPoint; |
| 130 | qreal positionBeforePress = 0; |
| 131 | QQuickDial::SnapMode snapMode = QQuickDial::NoSnap; |
| 132 | QQuickDial::InputMode inputMode = QQuickDial::Circular; |
| 133 | QQuickDeferredPointer<QQuickItem> handle; |
| 134 | bool wrap = false; |
| 135 | bool live = true; |
| 136 | bool pressed = false; |
| 137 | bool allValuesAreInteger = false; |
| 138 | }; |
| 139 | |
| 140 | qreal QQuickDialPrivate::valueAt(qreal position) const |
| 141 | { |
| 142 | qreal value = from + (to - from) * position; |
| 143 | |
| 144 | /* play nice with users expecting that integer from, to and stepSize leads to |
| 145 | integer values - given that we are using floating point internally (and in |
| 146 | the API of value), this does not hold, but it is easy enough to handle |
| 147 | */ |
| 148 | if (allValuesAreInteger) |
| 149 | value = qRound(d: value); |
| 150 | |
| 151 | return value; |
| 152 | } |
| 153 | |
| 154 | qreal QQuickDialPrivate::snapPosition(qreal position) const |
| 155 | { |
| 156 | const qreal range = to - from; |
| 157 | if (qFuzzyIsNull(d: range)) |
| 158 | return position; |
| 159 | |
| 160 | const qreal effectiveStep = stepSize / range; |
| 161 | if (qFuzzyIsNull(d: effectiveStep)) |
| 162 | return position; |
| 163 | |
| 164 | return qRound(d: position / effectiveStep) * effectiveStep; |
| 165 | } |
| 166 | |
| 167 | qreal QQuickDialPrivate::positionAt(const QPointF &point) const |
| 168 | { |
| 169 | return inputMode == QQuickDial::Circular ? circularPositionAt(point) : linearPositionAt(point); |
| 170 | } |
| 171 | |
| 172 | qreal QQuickDialPrivate::circularPositionAt(const QPointF &point) const |
| 173 | { |
| 174 | qreal yy = height / 2.0 - point.y(); |
| 175 | qreal xx = point.x() - width / 2.0; |
| 176 | qreal angle = (xx || yy) ? std::atan2(y: yy, x: xx) : 0; |
| 177 | |
| 178 | if (angle < M_PI / -2) |
| 179 | angle = angle + M_PI * 2; |
| 180 | |
| 181 | qreal normalizedAngle = (startAngleRadians - angle) / endAngleRadians; |
| 182 | return normalizedAngle; |
| 183 | } |
| 184 | |
| 185 | qreal QQuickDialPrivate::linearPositionAt(const QPointF &point) const |
| 186 | { |
| 187 | // This value determines the range (either horizontal or vertical) |
| 188 | // within which the dial can be dragged. |
| 189 | // The larger this value is, the further the drag distance |
| 190 | // must be to go from a position of e.g. 0.0 to 1.0. |
| 191 | qreal dragArea = 0; |
| 192 | |
| 193 | // The linear input mode uses a "relative" input system, |
| 194 | // where the distance from the press point is used to calculate |
| 195 | // the change in position. Moving the mouse above the press |
| 196 | // point increases the position (when inputMode is Vertical), |
| 197 | // and vice versa. This prevents the dial from jumping when clicked. |
| 198 | qreal dragDistance = 0; |
| 199 | |
| 200 | if (inputMode == QQuickDial::Horizontal) { |
| 201 | dragArea = width * 2; |
| 202 | dragDistance = pressPoint.x() - point.x(); |
| 203 | } else { |
| 204 | dragArea = height * 2; |
| 205 | dragDistance = point.y() - pressPoint.y(); |
| 206 | } |
| 207 | const qreal normalisedDifference = dragDistance / dragArea; |
| 208 | return qBound(min: qreal(0), val: positionBeforePress - normalisedDifference, max: qreal(1)); |
| 209 | } |
| 210 | |
| 211 | void QQuickDialPrivate::setPosition(qreal pos) |
| 212 | { |
| 213 | Q_Q(QQuickDial); |
| 214 | pos = qBound<qreal>(min: qreal(0), val: pos, max: qreal(1)); |
| 215 | if (qFuzzyCompare(p1: position, p2: pos)) |
| 216 | return; |
| 217 | |
| 218 | position = pos; |
| 219 | |
| 220 | angle = startAngle + position * qAbs(t: endAngle - startAngle); |
| 221 | |
| 222 | emit q->positionChanged(); |
| 223 | emit q->angleChanged(); |
| 224 | } |
| 225 | |
| 226 | void QQuickDialPrivate::updatePosition() |
| 227 | { |
| 228 | qreal pos = 0; |
| 229 | if (!qFuzzyCompare(p1: from, p2: to)) |
| 230 | pos = (value - from) / (to - from); |
| 231 | setPosition(pos); |
| 232 | } |
| 233 | |
| 234 | bool QQuickDialPrivate::isLargeChange(const QPointF &eventPos, qreal proposedPosition) const |
| 235 | { |
| 236 | return qAbs(t: proposedPosition - position) >= qreal(0.5) && eventPos.y() >= height / 2; |
| 237 | } |
| 238 | |
| 239 | bool QQuickDialPrivate::isHorizontalOrVertical() const |
| 240 | { |
| 241 | return inputMode == QQuickDial::Horizontal || inputMode == QQuickDial::Vertical; |
| 242 | } |
| 243 | |
| 244 | void QQuickDialPrivate::handlePress(const QPointF &point) |
| 245 | { |
| 246 | Q_Q(QQuickDial); |
| 247 | QQuickControlPrivate::handlePress(point); |
| 248 | pressPoint = point; |
| 249 | positionBeforePress = position; |
| 250 | q->setPressed(true); |
| 251 | } |
| 252 | |
| 253 | void QQuickDialPrivate::handleMove(const QPointF &point) |
| 254 | { |
| 255 | Q_Q(QQuickDial); |
| 256 | QQuickControlPrivate::handleMove(point); |
| 257 | const qreal oldPos = position; |
| 258 | qreal pos = positionAt(point); |
| 259 | if (snapMode == QQuickDial::SnapAlways) |
| 260 | pos = snapPosition(position: pos); |
| 261 | |
| 262 | if (wrap || (!wrap && (isHorizontalOrVertical() || !isLargeChange(eventPos: point, proposedPosition: pos)))) { |
| 263 | if (live) |
| 264 | q->setValue(valueAt(position: pos)); |
| 265 | else |
| 266 | setPosition(pos); |
| 267 | if (!qFuzzyCompare(p1: pos, p2: oldPos)) |
| 268 | emit q->moved(); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | void QQuickDialPrivate::handleRelease(const QPointF &point) |
| 273 | { |
| 274 | Q_Q(QQuickDial); |
| 275 | QQuickControlPrivate::handleRelease(point); |
| 276 | if (q->keepMouseGrab() || q->keepTouchGrab()) { |
| 277 | const qreal oldPos = position; |
| 278 | qreal pos = positionAt(point); |
| 279 | if (snapMode != QQuickDial::NoSnap) |
| 280 | pos = snapPosition(position: pos); |
| 281 | |
| 282 | if (wrap || (!wrap && (isHorizontalOrVertical() || !isLargeChange(eventPos: point, proposedPosition: pos)))) |
| 283 | q->setValue(valueAt(position: pos)); |
| 284 | if (!qFuzzyCompare(p1: pos, p2: oldPos)) |
| 285 | emit q->moved(); |
| 286 | |
| 287 | q->setKeepMouseGrab(false); |
| 288 | q->setKeepTouchGrab(false); |
| 289 | } |
| 290 | |
| 291 | q->setPressed(false); |
| 292 | pressPoint = QPointF(); |
| 293 | positionBeforePress = 0; |
| 294 | } |
| 295 | |
| 296 | void QQuickDialPrivate::handleUngrab() |
| 297 | { |
| 298 | Q_Q(QQuickDial); |
| 299 | QQuickControlPrivate::handleUngrab(); |
| 300 | pressPoint = QPointF(); |
| 301 | positionBeforePress = 0; |
| 302 | q->setPressed(false); |
| 303 | } |
| 304 | |
| 305 | static inline QString handleName() { return QStringLiteral("handle" ); } |
| 306 | |
| 307 | void QQuickDialPrivate::cancelHandle() |
| 308 | { |
| 309 | Q_Q(QQuickDial); |
| 310 | quickCancelDeferred(object: q, property: handleName()); |
| 311 | } |
| 312 | |
| 313 | void QQuickDialPrivate::executeHandle(bool complete) |
| 314 | { |
| 315 | Q_Q(QQuickDial); |
| 316 | if (handle.wasExecuted()) |
| 317 | return; |
| 318 | |
| 319 | if (!handle || complete) |
| 320 | quickBeginDeferred(object: q, property: handleName(), delegate&: handle); |
| 321 | if (complete) |
| 322 | quickCompleteDeferred(object: q, property: handleName(), delegate&: handle); |
| 323 | } |
| 324 | |
| 325 | static bool areRepresentableAsInteger(qreal num1, qreal num2, qreal num3) { |
| 326 | auto check = [](qreal number) -> bool { return std::nearbyint(x: number) == number; }; |
| 327 | return check(num1) && check(num2) && check(num3); |
| 328 | } |
| 329 | |
| 330 | void QQuickDialPrivate::updateAllValuesAreInteger() |
| 331 | { |
| 332 | allValuesAreInteger = areRepresentableAsInteger(num1: to, num2: from, num3: stepSize) && stepSize != 0.0; |
| 333 | } |
| 334 | |
| 335 | QQuickDial::QQuickDial(QQuickItem *parent) |
| 336 | : QQuickControl(*(new QQuickDialPrivate), parent) |
| 337 | { |
| 338 | setActiveFocusOnTab(true); |
| 339 | setAcceptedMouseButtons(Qt::LeftButton); |
| 340 | #if QT_CONFIG(quicktemplates2_multitouch) |
| 341 | setAcceptTouchEvents(true); |
| 342 | #endif |
| 343 | #if QT_CONFIG(cursor) |
| 344 | setCursor(Qt::ArrowCursor); |
| 345 | #endif |
| 346 | } |
| 347 | |
| 348 | /*! |
| 349 | \qmlproperty real QtQuick.Controls::Dial::from |
| 350 | |
| 351 | This property holds the starting value for the range. The default value is \c 0.0. |
| 352 | |
| 353 | \sa to, value |
| 354 | */ |
| 355 | qreal QQuickDial::from() const |
| 356 | { |
| 357 | Q_D(const QQuickDial); |
| 358 | return d->from; |
| 359 | } |
| 360 | |
| 361 | void QQuickDial::setFrom(qreal from) |
| 362 | { |
| 363 | Q_D(QQuickDial); |
| 364 | if (qFuzzyCompare(p1: d->from, p2: from)) |
| 365 | return; |
| 366 | |
| 367 | d->from = from; |
| 368 | emit fromChanged(); |
| 369 | d->updateAllValuesAreInteger(); |
| 370 | if (isComponentComplete()) { |
| 371 | setValue(d->value); |
| 372 | d->updatePosition(); |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | /*! |
| 377 | \qmlproperty real QtQuick.Controls::Dial::to |
| 378 | |
| 379 | This property holds the end value for the range. The default value is |
| 380 | \c 1.0. |
| 381 | |
| 382 | \sa from, value |
| 383 | */ |
| 384 | qreal QQuickDial::to() const |
| 385 | { |
| 386 | Q_D(const QQuickDial); |
| 387 | return d->to; |
| 388 | } |
| 389 | |
| 390 | void QQuickDial::setTo(qreal to) |
| 391 | { |
| 392 | Q_D(QQuickDial); |
| 393 | if (qFuzzyCompare(p1: d->to, p2: to)) |
| 394 | return; |
| 395 | |
| 396 | d->to = to; |
| 397 | d->updateAllValuesAreInteger(); |
| 398 | emit toChanged(); |
| 399 | if (isComponentComplete()) { |
| 400 | setValue(d->value); |
| 401 | d->updatePosition(); |
| 402 | } |
| 403 | } |
| 404 | |
| 405 | /*! |
| 406 | \qmlproperty real QtQuick.Controls::Dial::value |
| 407 | |
| 408 | This property holds the value in the range \c from - \c to. The default |
| 409 | value is \c 0.0. |
| 410 | |
| 411 | \sa position, live |
| 412 | */ |
| 413 | qreal QQuickDial::value() const |
| 414 | { |
| 415 | Q_D(const QQuickDial); |
| 416 | return d->value; |
| 417 | } |
| 418 | |
| 419 | void QQuickDial::setValue(qreal value) |
| 420 | { |
| 421 | Q_D(QQuickDial); |
| 422 | if (isComponentComplete()) |
| 423 | value = d->from > d->to ? qBound(min: d->to, val: value, max: d->from) : qBound(min: d->from, val: value, max: d->to); |
| 424 | |
| 425 | if (qFuzzyCompare(p1: d->value, p2: value)) |
| 426 | return; |
| 427 | |
| 428 | d->value = value; |
| 429 | d->updatePosition(); |
| 430 | emit valueChanged(); |
| 431 | } |
| 432 | |
| 433 | /*! |
| 434 | \qmlproperty real QtQuick.Controls::Dial::position |
| 435 | \readonly |
| 436 | |
| 437 | This property holds the logical position of the handle. |
| 438 | |
| 439 | The position is expressed as a fraction of the control's angle range (the |
| 440 | range within which the handle can be moved) in the range \c {0.0 - 1.0}. |
| 441 | |
| 442 | \sa value, angle |
| 443 | */ |
| 444 | qreal QQuickDial::position() const |
| 445 | { |
| 446 | Q_D(const QQuickDial); |
| 447 | return d->position; |
| 448 | } |
| 449 | |
| 450 | /*! |
| 451 | \qmlproperty real QtQuick.Controls::Dial::angle |
| 452 | \readonly |
| 453 | |
| 454 | This property holds the angle of the handle. |
| 455 | |
| 456 | The range is from \c -140 degrees to \c 140 degrees. |
| 457 | |
| 458 | \sa position |
| 459 | */ |
| 460 | qreal QQuickDial::angle() const |
| 461 | { |
| 462 | Q_D(const QQuickDial); |
| 463 | return d->angle; |
| 464 | } |
| 465 | |
| 466 | /*! |
| 467 | \qmlproperty real QtQuick.Controls::Dial::stepSize |
| 468 | |
| 469 | This property holds the step size. |
| 470 | |
| 471 | The step size determines the amount by which the dial's value |
| 472 | is increased and decreased when interacted with via the keyboard. |
| 473 | For example, a step size of \c 0.2, will result in the dial's |
| 474 | value increasing and decreasing in increments of \c 0.2. |
| 475 | |
| 476 | The step size is only respected for touch and mouse interaction |
| 477 | when \l snapMode is set to a value other than \c Dial.NoSnap. |
| 478 | |
| 479 | The default value is \c 0.0, which results in an effective step |
| 480 | size of \c 0.1 for keyboard interaction. |
| 481 | |
| 482 | \sa snapMode, increase(), decrease() |
| 483 | */ |
| 484 | qreal QQuickDial::stepSize() const |
| 485 | { |
| 486 | Q_D(const QQuickDial); |
| 487 | return d->stepSize; |
| 488 | } |
| 489 | |
| 490 | void QQuickDial::setStepSize(qreal step) |
| 491 | { |
| 492 | Q_D(QQuickDial); |
| 493 | if (qFuzzyCompare(p1: d->stepSize, p2: step)) |
| 494 | return; |
| 495 | |
| 496 | d->stepSize = step; |
| 497 | d->updateAllValuesAreInteger(); |
| 498 | emit stepSizeChanged(); |
| 499 | } |
| 500 | |
| 501 | /*! |
| 502 | \qmlproperty enumeration QtQuick.Controls::Dial::snapMode |
| 503 | |
| 504 | This property holds the snap mode. |
| 505 | |
| 506 | The snap mode works with the \l stepSize to allow the handle to snap to |
| 507 | certain points along the dial. |
| 508 | |
| 509 | Possible values: |
| 510 | \value Dial.NoSnap The dial does not snap (default). |
| 511 | \value Dial.SnapAlways The dial snaps while the handle is dragged. |
| 512 | \value Dial.SnapOnRelease The dial does not snap while being dragged, but only after the handle is released. |
| 513 | |
| 514 | \sa stepSize |
| 515 | */ |
| 516 | QQuickDial::SnapMode QQuickDial::snapMode() const |
| 517 | { |
| 518 | Q_D(const QQuickDial); |
| 519 | return d->snapMode; |
| 520 | } |
| 521 | |
| 522 | void QQuickDial::setSnapMode(SnapMode mode) |
| 523 | { |
| 524 | Q_D(QQuickDial); |
| 525 | if (d->snapMode == mode) |
| 526 | return; |
| 527 | |
| 528 | d->snapMode = mode; |
| 529 | emit snapModeChanged(); |
| 530 | } |
| 531 | |
| 532 | /*! |
| 533 | \since QtQuick.Controls 2.5 (Qt 5.12) |
| 534 | \qmlproperty enumeration QtQuick.Controls::Dial::inputMode |
| 535 | |
| 536 | This property holds the input mode. |
| 537 | |
| 538 | \include qquickdial.qdocinc inputMode |
| 539 | |
| 540 | The default value is \c Dial.Circular. |
| 541 | */ |
| 542 | QQuickDial::InputMode QQuickDial::inputMode() const |
| 543 | { |
| 544 | Q_D(const QQuickDial); |
| 545 | return d->inputMode; |
| 546 | } |
| 547 | |
| 548 | void QQuickDial::setInputMode(QQuickDial::InputMode mode) |
| 549 | { |
| 550 | Q_D(QQuickDial); |
| 551 | if (d->inputMode == mode) |
| 552 | return; |
| 553 | |
| 554 | d->inputMode = mode; |
| 555 | emit inputModeChanged(); |
| 556 | } |
| 557 | |
| 558 | /*! |
| 559 | \qmlproperty bool QtQuick.Controls::Dial::wrap |
| 560 | |
| 561 | This property holds whether the dial wraps when dragged. |
| 562 | |
| 563 | For example, when this property is set to \c true, dragging the dial past |
| 564 | the \l to position will result in the handle being positioned at the |
| 565 | \l from position, and vice versa: |
| 566 | |
| 567 | \image qtquickcontrols2-dial-wrap.gif |
| 568 | |
| 569 | When this property is \c false, it's not possible to drag the dial across |
| 570 | the from and to values. |
| 571 | |
| 572 | \image qtquickcontrols2-dial-no-wrap.gif |
| 573 | |
| 574 | The default value is \c false. |
| 575 | */ |
| 576 | bool QQuickDial::wrap() const |
| 577 | { |
| 578 | Q_D(const QQuickDial); |
| 579 | return d->wrap; |
| 580 | } |
| 581 | |
| 582 | void QQuickDial::setWrap(bool wrap) |
| 583 | { |
| 584 | Q_D(QQuickDial); |
| 585 | if (d->wrap == wrap) |
| 586 | return; |
| 587 | |
| 588 | d->wrap = wrap; |
| 589 | emit wrapChanged(); |
| 590 | } |
| 591 | |
| 592 | /*! |
| 593 | \qmlproperty bool QtQuick.Controls::Dial::pressed |
| 594 | |
| 595 | This property holds whether the dial is pressed. |
| 596 | |
| 597 | The dial will be pressed when either the mouse is pressed over it, or a key |
| 598 | such as \c Qt.Key_Left is held down. If you'd prefer not to have the dial |
| 599 | be pressed upon key presses (due to styling reasons, for example), you can |
| 600 | use the \l {Keys}{Keys attached property}: |
| 601 | |
| 602 | \code |
| 603 | Dial { |
| 604 | Keys.onLeftPressed: {} |
| 605 | } |
| 606 | \endcode |
| 607 | |
| 608 | This will result in pressed only being \c true upon mouse presses. |
| 609 | */ |
| 610 | bool QQuickDial::isPressed() const |
| 611 | { |
| 612 | Q_D(const QQuickDial); |
| 613 | return d->pressed; |
| 614 | } |
| 615 | |
| 616 | void QQuickDial::setPressed(bool pressed) |
| 617 | { |
| 618 | Q_D(QQuickDial); |
| 619 | if (d->pressed == pressed) |
| 620 | return; |
| 621 | |
| 622 | d->pressed = pressed; |
| 623 | setAccessibleProperty(propertyName: "pressed" , value: pressed); |
| 624 | emit pressedChanged(); |
| 625 | } |
| 626 | |
| 627 | /*! |
| 628 | \qmlproperty Item QtQuick.Controls::Dial::handle |
| 629 | |
| 630 | This property holds the handle of the dial. |
| 631 | |
| 632 | The handle acts as a visual indicator of the position of the dial. |
| 633 | |
| 634 | \sa {Customizing Dial} |
| 635 | */ |
| 636 | QQuickItem *QQuickDial::handle() const |
| 637 | { |
| 638 | QQuickDialPrivate *d = const_cast<QQuickDialPrivate *>(d_func()); |
| 639 | if (!d->handle) |
| 640 | d->executeHandle(); |
| 641 | return d->handle; |
| 642 | } |
| 643 | |
| 644 | void QQuickDial::setHandle(QQuickItem *handle) |
| 645 | { |
| 646 | Q_D(QQuickDial); |
| 647 | if (handle == d->handle) |
| 648 | return; |
| 649 | |
| 650 | if (!d->handle.isExecuting()) |
| 651 | d->cancelHandle(); |
| 652 | |
| 653 | QQuickControlPrivate::hideOldItem(item: d->handle); |
| 654 | d->handle = handle; |
| 655 | if (d->handle && !d->handle->parentItem()) |
| 656 | d->handle->setParentItem(this); |
| 657 | if (!d->handle.isExecuting()) |
| 658 | emit handleChanged(); |
| 659 | } |
| 660 | |
| 661 | /*! |
| 662 | \since QtQuick.Controls 2.2 (Qt 5.9) |
| 663 | \qmlproperty bool QtQuick.Controls::Dial::live |
| 664 | |
| 665 | This property holds whether the dial provides live updates for the \l value |
| 666 | property while the handle is dragged. |
| 667 | |
| 668 | The default value is \c true. |
| 669 | |
| 670 | \sa value |
| 671 | */ |
| 672 | bool QQuickDial::live() const |
| 673 | { |
| 674 | Q_D(const QQuickDial); |
| 675 | return d->live; |
| 676 | } |
| 677 | |
| 678 | void QQuickDial::setLive(bool live) |
| 679 | { |
| 680 | Q_D(QQuickDial); |
| 681 | if (d->live == live) |
| 682 | return; |
| 683 | |
| 684 | d->live = live; |
| 685 | emit liveChanged(); |
| 686 | } |
| 687 | |
| 688 | /*! |
| 689 | \qmlmethod void QtQuick.Controls::Dial::increase() |
| 690 | |
| 691 | Increases the value by \l stepSize, or \c 0.1 if stepSize is not defined. |
| 692 | |
| 693 | \sa stepSize |
| 694 | */ |
| 695 | void QQuickDial::increase() |
| 696 | { |
| 697 | Q_D(QQuickDial); |
| 698 | qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
| 699 | setValue(d->value + step); |
| 700 | } |
| 701 | |
| 702 | /*! |
| 703 | \qmlmethod void QtQuick.Controls::Dial::decrease() |
| 704 | |
| 705 | Decreases the value by \l stepSize, or \c 0.1 if stepSize is not defined. |
| 706 | |
| 707 | \sa stepSize |
| 708 | */ |
| 709 | void QQuickDial::decrease() |
| 710 | { |
| 711 | Q_D(QQuickDial); |
| 712 | qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
| 713 | setValue(d->value - step); |
| 714 | } |
| 715 | |
| 716 | void QQuickDial::keyPressEvent(QKeyEvent *event) |
| 717 | { |
| 718 | Q_D(QQuickDial); |
| 719 | const qreal oldValue = d->value; |
| 720 | switch (event->key()) { |
| 721 | case Qt::Key_Left: |
| 722 | case Qt::Key_Down: |
| 723 | setPressed(true); |
| 724 | if (isMirrored()) |
| 725 | increase(); |
| 726 | else |
| 727 | decrease(); |
| 728 | break; |
| 729 | |
| 730 | case Qt::Key_Right: |
| 731 | case Qt::Key_Up: |
| 732 | setPressed(true); |
| 733 | if (isMirrored()) |
| 734 | decrease(); |
| 735 | else |
| 736 | increase(); |
| 737 | break; |
| 738 | |
| 739 | case Qt::Key_Home: |
| 740 | setPressed(true); |
| 741 | setValue(isMirrored() ? d->to : d->from); |
| 742 | break; |
| 743 | |
| 744 | case Qt::Key_End: |
| 745 | setPressed(true); |
| 746 | setValue(isMirrored() ? d->from : d->to); |
| 747 | break; |
| 748 | |
| 749 | default: |
| 750 | event->ignore(); |
| 751 | QQuickControl::keyPressEvent(event); |
| 752 | break; |
| 753 | } |
| 754 | if (!qFuzzyCompare(p1: d->value, p2: oldValue)) |
| 755 | emit moved(); |
| 756 | } |
| 757 | |
| 758 | void QQuickDial::keyReleaseEvent(QKeyEvent *event) |
| 759 | { |
| 760 | QQuickControl::keyReleaseEvent(event); |
| 761 | setPressed(false); |
| 762 | } |
| 763 | |
| 764 | void QQuickDial::mousePressEvent(QMouseEvent *event) |
| 765 | { |
| 766 | Q_D(QQuickDial); |
| 767 | QQuickControl::mousePressEvent(event); |
| 768 | d->handleMove(point: event->localPos()); |
| 769 | setKeepMouseGrab(true); |
| 770 | } |
| 771 | |
| 772 | #if QT_CONFIG(quicktemplates2_multitouch) |
| 773 | void QQuickDial::touchEvent(QTouchEvent *event) |
| 774 | { |
| 775 | Q_D(QQuickDial); |
| 776 | switch (event->type()) { |
| 777 | case QEvent::TouchUpdate: |
| 778 | for (const QTouchEvent::TouchPoint &point : event->touchPoints()) { |
| 779 | if (!d->acceptTouch(point)) |
| 780 | continue; |
| 781 | |
| 782 | switch (point.state()) { |
| 783 | case Qt::TouchPointMoved: |
| 784 | if (!keepTouchGrab()) { |
| 785 | bool overXDragThreshold = QQuickWindowPrivate::dragOverThreshold(d: point.pos().x() - d->pressPoint.x(), axis: Qt::XAxis, tp: &point); |
| 786 | setKeepTouchGrab(overXDragThreshold); |
| 787 | |
| 788 | if (!overXDragThreshold) { |
| 789 | bool overYDragThreshold = QQuickWindowPrivate::dragOverThreshold(d: point.pos().y() - d->pressPoint.y(), axis: Qt::YAxis, tp: &point); |
| 790 | setKeepTouchGrab(overYDragThreshold); |
| 791 | } |
| 792 | } |
| 793 | if (keepTouchGrab()) |
| 794 | d->handleMove(point: point.pos()); |
| 795 | break; |
| 796 | |
| 797 | default: |
| 798 | QQuickControl::touchEvent(event); |
| 799 | break; |
| 800 | } |
| 801 | } |
| 802 | break; |
| 803 | |
| 804 | default: |
| 805 | QQuickControl::touchEvent(event); |
| 806 | break; |
| 807 | } |
| 808 | } |
| 809 | #endif |
| 810 | |
| 811 | #if QT_CONFIG(wheelevent) |
| 812 | void QQuickDial::wheelEvent(QWheelEvent *event) |
| 813 | { |
| 814 | Q_D(QQuickDial); |
| 815 | QQuickControl::wheelEvent(event); |
| 816 | if (d->wheelEnabled) { |
| 817 | const qreal oldValue = d->value; |
| 818 | const QPointF angle = event->angleDelta(); |
| 819 | const qreal delta = (qFuzzyIsNull(d: angle.y()) ? angle.x() : (event->inverted() ? -angle.y() : angle.y())) / QWheelEvent::DefaultDeltasPerStep; |
| 820 | const qreal step = qFuzzyIsNull(d: d->stepSize) ? 0.1 : d->stepSize; |
| 821 | setValue(oldValue + step * delta); |
| 822 | event->setAccepted(!qFuzzyCompare(p1: d->value, p2: oldValue)); |
| 823 | } |
| 824 | } |
| 825 | #endif |
| 826 | |
| 827 | void QQuickDial::mirrorChange() |
| 828 | { |
| 829 | QQuickControl::mirrorChange(); |
| 830 | emit angleChanged(); |
| 831 | } |
| 832 | |
| 833 | void QQuickDial::componentComplete() |
| 834 | { |
| 835 | Q_D(QQuickDial); |
| 836 | d->executeHandle(complete: true); |
| 837 | QQuickControl::componentComplete(); |
| 838 | setValue(d->value); |
| 839 | d->updatePosition(); |
| 840 | } |
| 841 | |
| 842 | #if QT_CONFIG(accessibility) |
| 843 | void QQuickDial::accessibilityActiveChanged(bool active) |
| 844 | { |
| 845 | QQuickControl::accessibilityActiveChanged(active); |
| 846 | |
| 847 | Q_D(QQuickDial); |
| 848 | if (active) |
| 849 | setAccessibleProperty(propertyName: "pressed" , value: d->pressed); |
| 850 | } |
| 851 | |
| 852 | QAccessible::Role QQuickDial::accessibleRole() const |
| 853 | { |
| 854 | return QAccessible::Dial; |
| 855 | } |
| 856 | #endif |
| 857 | |
| 858 | QT_END_NAMESPACE |
| 859 | |