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 "qquickrangeslider_p.h" |
5 | #include "qquickcontrol_p_p.h" |
6 | #include "qquickdeferredexecute_p_p.h" |
7 | |
8 | #include <QtCore/qscopedpointer.h> |
9 | #include <QtQuick/private/qquickwindow_p.h> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | /*! |
14 | \qmltype RangeSlider |
15 | \inherits Control |
16 | //! \nativetype QQuickRangeSlider |
17 | \inqmlmodule QtQuick.Controls |
18 | \since 5.7 |
19 | \ingroup qtquickcontrols-input |
20 | \ingroup qtquickcontrols-focusscopes |
21 | \brief Used to select a range of values by sliding two handles along a track. |
22 | |
23 | \image qtquickcontrols-rangeslider.gif |
24 | |
25 | RangeSlider is used to select a range specified by two values, by sliding |
26 | each handle along a track. |
27 | |
28 | In the example below, custom \l from and \l to values are set, and the |
29 | initial positions of the \l first and \l second handles are set: |
30 | |
31 | \code |
32 | RangeSlider { |
33 | from: 1 |
34 | to: 100 |
35 | first.value: 25 |
36 | second.value: 75 |
37 | } |
38 | \endcode |
39 | |
40 | In order to perform an action when the value for a particular handle changes, |
41 | use the following syntax: |
42 | |
43 | \code |
44 | first.onMoved: console.log("first.value changed to " + first.value) |
45 | \endcode |
46 | |
47 | The \l {first.position} and \l {second.position} properties are expressed as |
48 | fractions of the control's size, in the range \c {0.0 - 1.0}. |
49 | The \l {first.visualPosition} and \l {second.visualPosition} properties are |
50 | the same, except that they are reversed in a |
51 | \l {Right-to-left User Interfaces}{right-to-left} application. |
52 | The \c visualPosition is useful for positioning the handles when styling |
53 | RangeSlider. In the example above, \l {first.visualPosition} will be \c 0.24 |
54 | in a left-to-right application, and \c 0.76 in a right-to-left application. |
55 | |
56 | For a slider that allows the user to select a single value, see \l Slider. |
57 | |
58 | \sa {Customizing RangeSlider}, {Input Controls}, |
59 | {Focus Management in Qt Quick Controls} |
60 | */ |
61 | |
62 | class QQuickRangeSliderNodePrivate : public QObjectPrivate |
63 | { |
64 | Q_DECLARE_PUBLIC(QQuickRangeSliderNode) |
65 | public: |
66 | QQuickRangeSliderNodePrivate(qreal value, QQuickRangeSlider *slider) |
67 | : value(value), |
68 | slider(slider) |
69 | { |
70 | } |
71 | |
72 | bool isFirst() const; |
73 | |
74 | void setPosition(qreal position, bool ignoreOtherPosition = false); |
75 | void updatePosition(bool ignoreOtherPosition = false); |
76 | |
77 | void cancelHandle(); |
78 | void executeHandle(bool complete = false); |
79 | |
80 | static QQuickRangeSliderNodePrivate *get(QQuickRangeSliderNode *node); |
81 | |
82 | qreal value = 0; |
83 | bool isPendingValue = false; |
84 | qreal pendingValue = 0; |
85 | qreal position = 0; |
86 | QQuickDeferredPointer<QQuickItem> handle; |
87 | QQuickRangeSlider *slider = nullptr; |
88 | bool pressed = false; |
89 | bool hovered = false; |
90 | int touchId = -1; |
91 | }; |
92 | |
93 | bool QQuickRangeSliderNodePrivate::isFirst() const |
94 | { |
95 | return this == get(node: slider->first()); |
96 | } |
97 | |
98 | void QQuickRangeSliderNodePrivate::setPosition(qreal position, bool ignoreOtherPosition) |
99 | { |
100 | Q_Q(QQuickRangeSliderNode); |
101 | |
102 | const qreal min = isFirst() || ignoreOtherPosition ? 0.0 : qMax<qreal>(a: 0.0, b: slider->first()->position()); |
103 | const qreal max = !isFirst() || ignoreOtherPosition ? 1.0 : qMin<qreal>(a: 1.0, b: slider->second()->position()); |
104 | position = qBound(min, val: position, max); |
105 | if (!qFuzzyCompare(p1: this->position, p2: position)) { |
106 | this->position = position; |
107 | emit q->positionChanged(); |
108 | emit q->visualPositionChanged(); |
109 | } |
110 | } |
111 | |
112 | void QQuickRangeSliderNodePrivate::updatePosition(bool ignoreOtherPosition) |
113 | { |
114 | qreal pos = 0; |
115 | if (!qFuzzyCompare(p1: slider->from(), p2: slider->to())) |
116 | pos = (value - slider->from()) / (slider->to() - slider->from()); |
117 | setPosition(position: pos, ignoreOtherPosition); |
118 | } |
119 | |
120 | void QQuickRangeSliderNodePrivate::cancelHandle() |
121 | { |
122 | Q_Q(QQuickRangeSliderNode); |
123 | quickCancelDeferred(object: q, property: handleName()); |
124 | } |
125 | |
126 | void QQuickRangeSliderNodePrivate::executeHandle(bool complete) |
127 | { |
128 | Q_Q(QQuickRangeSliderNode); |
129 | if (handle.wasExecuted()) |
130 | return; |
131 | |
132 | if (!handle || complete) |
133 | quickBeginDeferred(object: q, property: handleName(), delegate&: handle); |
134 | if (complete) |
135 | quickCompleteDeferred(object: q, property: handleName(), delegate&: handle); |
136 | } |
137 | |
138 | QQuickRangeSliderNodePrivate *QQuickRangeSliderNodePrivate::get(QQuickRangeSliderNode *node) |
139 | { |
140 | return node->d_func(); |
141 | } |
142 | |
143 | QQuickRangeSliderNode::QQuickRangeSliderNode(qreal value, QQuickRangeSlider *slider) |
144 | : QObject(*(new QQuickRangeSliderNodePrivate(value, slider)), slider) |
145 | { |
146 | } |
147 | |
148 | QQuickRangeSliderNode::~QQuickRangeSliderNode() |
149 | { |
150 | } |
151 | |
152 | qreal QQuickRangeSliderNode::value() const |
153 | { |
154 | Q_D(const QQuickRangeSliderNode); |
155 | return d->value; |
156 | } |
157 | |
158 | void QQuickRangeSliderNode::setValue(qreal value) |
159 | { |
160 | Q_D(QQuickRangeSliderNode); |
161 | if (!d->slider->isComponentComplete()) { |
162 | d->pendingValue = value; |
163 | d->isPendingValue = true; |
164 | return; |
165 | } |
166 | |
167 | // First, restrict the first value to be within to and from. |
168 | const qreal smaller = qMin(a: d->slider->to(), b: d->slider->from()); |
169 | const qreal larger = qMax(a: d->slider->to(), b: d->slider->from()); |
170 | value = qBound(min: smaller, val: value, max: larger); |
171 | |
172 | // Then, ensure that it doesn't go past the other value, |
173 | // a check that depends on whether or not the range is inverted. |
174 | const bool invertedRange = d->slider->from() > d->slider->to(); |
175 | if (d->isFirst()) { |
176 | if (invertedRange) { |
177 | if (value < d->slider->second()->value()) |
178 | value = d->slider->second()->value(); |
179 | } else { |
180 | if (value > d->slider->second()->value()) |
181 | value = d->slider->second()->value(); |
182 | } |
183 | } else { |
184 | if (invertedRange) { |
185 | if (value > d->slider->first()->value()) |
186 | value = d->slider->first()->value(); |
187 | } else { |
188 | if (value < d->slider->first()->value()) |
189 | value = d->slider->first()->value(); |
190 | } |
191 | } |
192 | |
193 | if (!qFuzzyCompare(p1: d->value, p2: value)) { |
194 | d->value = value; |
195 | d->updatePosition(); |
196 | emit valueChanged(); |
197 | } |
198 | } |
199 | |
200 | qreal QQuickRangeSliderNode::position() const |
201 | { |
202 | Q_D(const QQuickRangeSliderNode); |
203 | return d->position; |
204 | } |
205 | |
206 | qreal QQuickRangeSliderNode::visualPosition() const |
207 | { |
208 | Q_D(const QQuickRangeSliderNode); |
209 | if (d->slider->orientation() == Qt::Vertical || d->slider->isMirrored()) |
210 | return 1.0 - d->position; |
211 | return d->position; |
212 | } |
213 | |
214 | QQuickItem *QQuickRangeSliderNode::handle() const |
215 | { |
216 | QQuickRangeSliderNodePrivate *d = const_cast<QQuickRangeSliderNodePrivate *>(d_func()); |
217 | if (!d->handle) |
218 | d->executeHandle(); |
219 | return d->handle; |
220 | } |
221 | |
222 | void QQuickRangeSliderNode::setHandle(QQuickItem *handle) |
223 | { |
224 | Q_D(QQuickRangeSliderNode); |
225 | if (d->handle == handle) |
226 | return; |
227 | |
228 | QQuickControlPrivate::warnIfCustomizationNotSupported(control: d->slider, item: handle, QStringLiteral("handle")); |
229 | |
230 | if (!d->handle.isExecuting()) |
231 | d->cancelHandle(); |
232 | |
233 | const qreal oldImplicitHandleWidth = implicitHandleWidth(); |
234 | const qreal oldImplicitHandleHeight = implicitHandleHeight(); |
235 | |
236 | QQuickControlPrivate::get(control: d->slider)->removeImplicitSizeListener(item: d->handle); |
237 | QQuickControlPrivate::hideOldItem(item: d->handle); |
238 | d->handle = handle; |
239 | |
240 | if (handle) { |
241 | if (!handle->parentItem()) |
242 | handle->setParentItem(d->slider); |
243 | |
244 | QQuickItem *firstHandle = QQuickRangeSliderNodePrivate::get(node: d->slider->first())->handle; |
245 | QQuickItem *secondHandle = QQuickRangeSliderNodePrivate::get(node: d->slider->second())->handle; |
246 | if (firstHandle && secondHandle) { |
247 | // The order of property assignments in QML is undefined, |
248 | // but we need the first handle to be before the second due |
249 | // to focus order constraints, so check for that here. |
250 | const QList<QQuickItem *> childItems = d->slider->childItems(); |
251 | const int firstIndex = childItems.indexOf(t: firstHandle); |
252 | const int secondIndex = childItems.indexOf(t: secondHandle); |
253 | if (firstIndex != -1 && secondIndex != -1 && firstIndex > secondIndex) { |
254 | firstHandle->stackBefore(secondHandle); |
255 | // Ensure we have some way of knowing which handle is above |
256 | // the other when it comes to mouse presses, and also that |
257 | // they are rendered in the correct order. |
258 | secondHandle->setZ(secondHandle->z() + 1); |
259 | } |
260 | } |
261 | |
262 | handle->setActiveFocusOnTab(true); |
263 | QQuickControlPrivate::get(control: d->slider)->addImplicitSizeListener(item: handle); |
264 | } |
265 | |
266 | if (!qFuzzyCompare(p1: oldImplicitHandleWidth, p2: implicitHandleWidth())) |
267 | emit implicitHandleWidthChanged(); |
268 | if (!qFuzzyCompare(p1: oldImplicitHandleHeight, p2: implicitHandleHeight())) |
269 | emit implicitHandleHeightChanged(); |
270 | if (!d->handle.isExecuting()) |
271 | emit handleChanged(); |
272 | } |
273 | |
274 | bool QQuickRangeSliderNode::isPressed() const |
275 | { |
276 | Q_D(const QQuickRangeSliderNode); |
277 | return d->pressed; |
278 | } |
279 | |
280 | void QQuickRangeSliderNode::setPressed(bool pressed) |
281 | { |
282 | Q_D(QQuickRangeSliderNode); |
283 | if (d->pressed == pressed) |
284 | return; |
285 | |
286 | d->pressed = pressed; |
287 | d->slider->setAccessibleProperty(propertyName: "pressed", value: pressed || d->slider->second()->isPressed()); |
288 | emit pressedChanged(); |
289 | } |
290 | |
291 | bool QQuickRangeSliderNode::isHovered() const |
292 | { |
293 | Q_D(const QQuickRangeSliderNode); |
294 | return d->hovered; |
295 | } |
296 | |
297 | void QQuickRangeSliderNode::setHovered(bool hovered) |
298 | { |
299 | Q_D(QQuickRangeSliderNode); |
300 | if (d->hovered == hovered) |
301 | return; |
302 | |
303 | d->hovered = hovered; |
304 | emit hoveredChanged(); |
305 | } |
306 | |
307 | qreal QQuickRangeSliderNode::implicitHandleWidth() const |
308 | { |
309 | Q_D(const QQuickRangeSliderNode); |
310 | if (!d->handle) |
311 | return 0; |
312 | return d->handle->implicitWidth(); |
313 | } |
314 | |
315 | qreal QQuickRangeSliderNode::implicitHandleHeight() const |
316 | { |
317 | Q_D(const QQuickRangeSliderNode); |
318 | if (!d->handle) |
319 | return 0; |
320 | return d->handle->implicitHeight(); |
321 | } |
322 | |
323 | void QQuickRangeSliderNode::increase() |
324 | { |
325 | Q_D(QQuickRangeSliderNode); |
326 | qreal step = qFuzzyIsNull(d: d->slider->stepSize()) ? 0.1 : d->slider->stepSize(); |
327 | setValue(d->value + step); |
328 | } |
329 | |
330 | void QQuickRangeSliderNode::decrease() |
331 | { |
332 | Q_D(QQuickRangeSliderNode); |
333 | qreal step = qFuzzyIsNull(d: d->slider->stepSize()) ? 0.1 : d->slider->stepSize(); |
334 | setValue(d->value - step); |
335 | } |
336 | |
337 | static const qreal defaultFrom = 0.0; |
338 | static const qreal defaultTo = 1.0; |
339 | |
340 | class QQuickRangeSliderPrivate : public QQuickControlPrivate |
341 | { |
342 | Q_DECLARE_PUBLIC(QQuickRangeSlider) |
343 | |
344 | public: |
345 | QQuickRangeSliderNode *pressedNode(int touchId = -1) const; |
346 | |
347 | #if QT_CONFIG(quicktemplates2_multitouch) |
348 | bool acceptTouch(const QTouchEvent::TouchPoint &point) override; |
349 | #endif |
350 | bool handlePress(const QPointF &point, ulong timestamp) override; |
351 | bool handleMove(const QPointF &point, ulong timestamp) override; |
352 | bool handleRelease(const QPointF &point, ulong timestamp) override; |
353 | void handleUngrab() override; |
354 | |
355 | void updateHover(const QPointF &pos); |
356 | |
357 | void itemImplicitWidthChanged(QQuickItem *item) override; |
358 | void itemImplicitHeightChanged(QQuickItem *item) override; |
359 | void itemDestroyed(QQuickItem *item) override; |
360 | |
361 | bool live = true; |
362 | qreal from = defaultFrom; |
363 | qreal to = defaultTo; |
364 | qreal stepSize = 0; |
365 | qreal touchDragThreshold = -1; |
366 | QQuickRangeSliderNode *first = nullptr; |
367 | QQuickRangeSliderNode *second = nullptr; |
368 | QPointF pressPoint; |
369 | Qt::Orientation orientation = Qt::Horizontal; |
370 | QQuickRangeSlider::SnapMode snapMode = QQuickRangeSlider::NoSnap; |
371 | }; |
372 | |
373 | static qreal valueAt(const QQuickRangeSlider *slider, qreal position) |
374 | { |
375 | return slider->from() + (slider->to() - slider->from()) * position; |
376 | } |
377 | |
378 | static qreal snapPosition(const QQuickRangeSlider *slider, qreal position) |
379 | { |
380 | const qreal range = slider->to() - slider->from(); |
381 | if (qFuzzyIsNull(d: range)) |
382 | return position; |
383 | |
384 | const qreal effectiveStep = slider->stepSize() / range; |
385 | if (qFuzzyIsNull(d: effectiveStep)) |
386 | return position; |
387 | |
388 | return qRound(d: position / effectiveStep) * effectiveStep; |
389 | } |
390 | |
391 | static qreal positionAt(const QQuickRangeSlider *slider, QQuickItem *handle, const QPointF &point) |
392 | { |
393 | if (slider->orientation() == Qt::Horizontal) { |
394 | const qreal hw = handle ? handle->width() : 0; |
395 | const qreal offset = hw / 2; |
396 | const qreal extent = slider->availableWidth() - hw; |
397 | if (!qFuzzyIsNull(d: extent)) { |
398 | if (slider->isMirrored()) |
399 | return (slider->width() - point.x() - slider->rightPadding() - offset) / extent; |
400 | return (point.x() - slider->leftPadding() - offset) / extent; |
401 | } |
402 | } else { |
403 | const qreal hh = handle ? handle->height() : 0; |
404 | const qreal offset = hh / 2; |
405 | const qreal extent = slider->availableHeight() - hh; |
406 | if (!qFuzzyIsNull(d: extent)) |
407 | return (slider->height() - point.y() - slider->bottomPadding() - offset) / extent; |
408 | } |
409 | return 0; |
410 | } |
411 | |
412 | QQuickRangeSliderNode *QQuickRangeSliderPrivate::pressedNode(int touchId) const |
413 | { |
414 | if (touchId == -1) |
415 | return first->isPressed() ? first : (second->isPressed() ? second : nullptr); |
416 | if (QQuickRangeSliderNodePrivate::get(node: first)->touchId == touchId) |
417 | return first; |
418 | if (QQuickRangeSliderNodePrivate::get(node: second)->touchId == touchId) |
419 | return second; |
420 | return nullptr; |
421 | } |
422 | |
423 | #if QT_CONFIG(quicktemplates2_multitouch) |
424 | bool QQuickRangeSliderPrivate::acceptTouch(const QTouchEvent::TouchPoint &point) |
425 | { |
426 | int firstId = QQuickRangeSliderNodePrivate::get(node: first)->touchId; |
427 | int secondId = QQuickRangeSliderNodePrivate::get(node: second)->touchId; |
428 | |
429 | if (((firstId == -1 || secondId == -1) && point.state() == QEventPoint::Pressed) || point.id() == firstId || point.id() == secondId) { |
430 | touchId = point.id(); |
431 | return true; |
432 | } |
433 | |
434 | return false; |
435 | } |
436 | #endif |
437 | |
438 | bool QQuickRangeSliderPrivate::handlePress(const QPointF &point, ulong timestamp) |
439 | { |
440 | Q_Q(QQuickRangeSlider); |
441 | QQuickControlPrivate::handlePress(point, timestamp); |
442 | pressPoint = point; |
443 | |
444 | QQuickItem *firstHandle = first->handle(); |
445 | QQuickItem *secondHandle = second->handle(); |
446 | const bool firstHit = firstHandle && !first->isPressed() && firstHandle->contains(point: q->mapToItem(item: firstHandle, point)); |
447 | const bool secondHit = secondHandle && !second->isPressed() && secondHandle->contains(point: q->mapToItem(item: secondHandle, point)); |
448 | QQuickRangeSliderNode *hitNode = nullptr; |
449 | QQuickRangeSliderNode *otherNode = nullptr; |
450 | |
451 | if (firstHit && secondHit) { |
452 | // choose highest |
453 | hitNode = firstHandle->z() > secondHandle->z() ? first : second; |
454 | otherNode = firstHandle->z() > secondHandle->z() ? second : first; |
455 | } else if (firstHit) { |
456 | hitNode = first; |
457 | otherNode = second; |
458 | } else if (secondHit) { |
459 | hitNode = second; |
460 | otherNode = first; |
461 | } else { |
462 | // find the nearest |
463 | const qreal firstPos = positionAt(slider: q, handle: firstHandle, point); |
464 | const qreal secondPos = positionAt(slider: q, handle: secondHandle, point); |
465 | const qreal firstDistance = qAbs(t: firstPos - first->position()); |
466 | const qreal secondDistance = qAbs(t: secondPos - second->position()); |
467 | |
468 | if (qFuzzyCompare(p1: firstDistance, p2: secondDistance)) { |
469 | // same distance => choose the one that can be moved towards the press position |
470 | const bool inverted = from > to; |
471 | if ((!inverted && firstPos < first->position()) || (inverted && firstPos > first->position())) { |
472 | hitNode = first; |
473 | otherNode = second; |
474 | } else { |
475 | hitNode = second; |
476 | otherNode = first; |
477 | } |
478 | } else if (firstDistance < secondDistance) { |
479 | hitNode = first; |
480 | otherNode = second; |
481 | } else { |
482 | hitNode = second; |
483 | otherNode = first; |
484 | } |
485 | } |
486 | |
487 | if (hitNode) { |
488 | hitNode->setPressed(true); |
489 | if (QQuickItem *handle = hitNode->handle()) { |
490 | handle->setZ(1); |
491 | |
492 | // A specific handle was hit, so it should get focus, rather than the default |
493 | // (first handle) that gets focus whenever the RangeSlider itself does - see focusInEvent(). |
494 | if (focusPolicy & Qt::ClickFocus) |
495 | handle->forceActiveFocus(reason: Qt::MouseFocusReason); |
496 | } |
497 | QQuickRangeSliderNodePrivate::get(node: hitNode)->touchId = touchId; |
498 | } |
499 | if (otherNode) { |
500 | if (QQuickItem *handle = otherNode->handle()) |
501 | handle->setZ(0); |
502 | } |
503 | return true; |
504 | } |
505 | |
506 | bool QQuickRangeSliderPrivate::handleMove(const QPointF &point, ulong timestamp) |
507 | { |
508 | Q_Q(QQuickRangeSlider); |
509 | QQuickControlPrivate::handleMove(point, timestamp); |
510 | QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId); |
511 | if (pressedNode) { |
512 | const qreal oldPos = pressedNode->position(); |
513 | qreal pos = positionAt(slider: q, handle: pressedNode->handle(), point); |
514 | if (snapMode == QQuickRangeSlider::SnapAlways) |
515 | pos = snapPosition(slider: q, position: pos); |
516 | if (live) |
517 | pressedNode->setValue(valueAt(slider: q, position: pos)); |
518 | else |
519 | QQuickRangeSliderNodePrivate::get(node: pressedNode)->setPosition(position: pos); |
520 | |
521 | if (!qFuzzyCompare(p1: pressedNode->position(), p2: oldPos)) |
522 | emit pressedNode->moved(); |
523 | } |
524 | return true; |
525 | } |
526 | |
527 | bool QQuickRangeSliderPrivate::handleRelease(const QPointF &point, ulong timestamp) |
528 | { |
529 | Q_Q(QQuickRangeSlider); |
530 | QQuickControlPrivate::handleRelease(point, timestamp); |
531 | pressPoint = QPointF(); |
532 | |
533 | QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId); |
534 | if (!pressedNode) |
535 | return true; |
536 | QQuickRangeSliderNodePrivate *pressedNodePrivate = QQuickRangeSliderNodePrivate::get(node: pressedNode); |
537 | |
538 | const qreal oldPos = pressedNode->position(); |
539 | qreal pos = positionAt(slider: q, handle: pressedNode->handle(), point); |
540 | if (snapMode != QQuickRangeSlider::NoSnap) |
541 | pos = snapPosition(slider: q, position: pos); |
542 | qreal val = valueAt(slider: q, position: pos); |
543 | if (!qFuzzyCompare(p1: val, p2: pressedNode->value())) |
544 | pressedNode->setValue(val); |
545 | else if (snapMode != QQuickRangeSlider::NoSnap) |
546 | pressedNodePrivate->setPosition(position: pos); |
547 | q->setKeepMouseGrab(false); |
548 | q->setKeepTouchGrab(false); |
549 | |
550 | if (!qFuzzyCompare(p1: pressedNode->position(), p2: oldPos)) |
551 | emit pressedNode->moved(); |
552 | |
553 | pressedNode->setPressed(false); |
554 | pressedNodePrivate->touchId = -1; |
555 | return true; |
556 | } |
557 | |
558 | void QQuickRangeSliderPrivate::handleUngrab() |
559 | { |
560 | QQuickControlPrivate::handleUngrab(); |
561 | pressPoint = QPointF(); |
562 | first->setPressed(false); |
563 | second->setPressed(false); |
564 | QQuickRangeSliderNodePrivate::get(node: first)->touchId = -1; |
565 | QQuickRangeSliderNodePrivate::get(node: second)->touchId = -1; |
566 | } |
567 | |
568 | void QQuickRangeSliderPrivate::updateHover(const QPointF &pos) |
569 | { |
570 | Q_Q(QQuickRangeSlider); |
571 | QQuickItem *firstHandle = first->handle(); |
572 | QQuickItem *secondHandle = second->handle(); |
573 | bool firstHandleHovered = firstHandle && firstHandle->isEnabled() |
574 | && firstHandle->contains(point: q->mapToItem(item: firstHandle, point: pos)); |
575 | bool secondHandleHovered = secondHandle && secondHandle->isEnabled() |
576 | && secondHandle->contains(point: q->mapToItem(item: secondHandle, point: pos)); |
577 | |
578 | if (firstHandleHovered && secondHandleHovered) { |
579 | // Only hover the handle with the higher Z value. |
580 | const bool firstHandleHasHigherZ = firstHandle->z() > secondHandle->z(); |
581 | firstHandleHovered = firstHandleHasHigherZ; |
582 | secondHandleHovered = !firstHandleHasHigherZ; |
583 | } |
584 | first->setHovered(firstHandleHovered); |
585 | second->setHovered(secondHandleHovered); |
586 | } |
587 | |
588 | void QQuickRangeSliderPrivate::itemImplicitWidthChanged(QQuickItem *item) |
589 | { |
590 | QQuickControlPrivate::itemImplicitWidthChanged(item); |
591 | if (item == first->handle()) |
592 | emit first->implicitHandleWidthChanged(); |
593 | else if (item == second->handle()) |
594 | emit second->implicitHandleWidthChanged(); |
595 | } |
596 | |
597 | void QQuickRangeSliderPrivate::itemImplicitHeightChanged(QQuickItem *item) |
598 | { |
599 | QQuickControlPrivate::itemImplicitHeightChanged(item); |
600 | if (item == first->handle()) |
601 | emit first->implicitHandleHeightChanged(); |
602 | else if (item == second->handle()) |
603 | emit second->implicitHandleHeightChanged(); |
604 | } |
605 | |
606 | void QQuickRangeSliderPrivate::itemDestroyed(QQuickItem *item) |
607 | { |
608 | QQuickControlPrivate::itemDestroyed(item); |
609 | if (item == first->handle()) |
610 | first->setHandle(nullptr); |
611 | else if (item == second->handle()) |
612 | second->setHandle(nullptr); |
613 | } |
614 | |
615 | QQuickRangeSlider::QQuickRangeSlider(QQuickItem *parent) |
616 | : QQuickControl(*(new QQuickRangeSliderPrivate), parent) |
617 | { |
618 | Q_D(QQuickRangeSlider); |
619 | d->first = new QQuickRangeSliderNode(0.0, this); |
620 | d->second = new QQuickRangeSliderNode(1.0, this); |
621 | d->setSizePolicy(horizontalPolicy: QLayoutPolicy::Expanding, verticalPolicy: QLayoutPolicy::Fixed); |
622 | |
623 | setFlag(flag: QQuickItem::ItemIsFocusScope); |
624 | #ifdef Q_OS_MACOS |
625 | setFocusPolicy(Qt::TabFocus); |
626 | #else |
627 | setFocusPolicy(Qt::StrongFocus); |
628 | #endif |
629 | setAcceptedMouseButtons(Qt::LeftButton); |
630 | #if QT_CONFIG(quicktemplates2_multitouch) |
631 | setAcceptTouchEvents(true); |
632 | #endif |
633 | #if QT_CONFIG(cursor) |
634 | setCursor(Qt::ArrowCursor); |
635 | #endif |
636 | } |
637 | |
638 | QQuickRangeSlider::~QQuickRangeSlider() |
639 | { |
640 | Q_D(QQuickRangeSlider); |
641 | d->removeImplicitSizeListener(item: d->first->handle()); |
642 | d->removeImplicitSizeListener(item: d->second->handle()); |
643 | } |
644 | |
645 | /*! |
646 | \qmlproperty real QtQuick.Controls::RangeSlider::from |
647 | |
648 | This property holds the starting value for the range. The default value is \c 0.0. |
649 | |
650 | \sa to, first.value, second.value |
651 | */ |
652 | qreal QQuickRangeSlider::from() const |
653 | { |
654 | Q_D(const QQuickRangeSlider); |
655 | return d->from; |
656 | } |
657 | |
658 | void QQuickRangeSlider::setFrom(qreal from) |
659 | { |
660 | Q_D(QQuickRangeSlider); |
661 | if (qFuzzyCompare(p1: d->from, p2: from)) |
662 | return; |
663 | |
664 | d->from = from; |
665 | emit fromChanged(); |
666 | |
667 | if (isComponentComplete()) { |
668 | d->first->setValue(d->first->value()); |
669 | d->second->setValue(d->second->value()); |
670 | auto *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first); |
671 | auto *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second); |
672 | firstPrivate->updatePosition(ignoreOtherPosition: true); |
673 | secondPrivate->updatePosition(); |
674 | } |
675 | } |
676 | |
677 | /*! |
678 | \qmlproperty real QtQuick.Controls::RangeSlider::to |
679 | |
680 | This property holds the end value for the range. The default value is \c 1.0. |
681 | |
682 | \sa from, first.value, second.value |
683 | */ |
684 | qreal QQuickRangeSlider::to() const |
685 | { |
686 | Q_D(const QQuickRangeSlider); |
687 | return d->to; |
688 | } |
689 | |
690 | void QQuickRangeSlider::setTo(qreal to) |
691 | { |
692 | Q_D(QQuickRangeSlider); |
693 | if (qFuzzyCompare(p1: d->to, p2: to)) |
694 | return; |
695 | |
696 | d->to = to; |
697 | emit toChanged(); |
698 | |
699 | if (isComponentComplete()) { |
700 | d->first->setValue(d->first->value()); |
701 | d->second->setValue(d->second->value()); |
702 | auto *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first); |
703 | auto *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second); |
704 | firstPrivate->updatePosition(ignoreOtherPosition: true); |
705 | secondPrivate->updatePosition(); |
706 | } |
707 | } |
708 | |
709 | /*! |
710 | \since QtQuick.Controls 2.5 (Qt 5.12) |
711 | \qmlproperty qreal QtQuick.Controls::RangeSlider::touchDragThreshold |
712 | |
713 | This property holds the threshold (in logical pixels) at which a touch drag event will be initiated. |
714 | The mouse drag threshold won't be affected. |
715 | The default value is \c Application.styleHints.startDragDistance. |
716 | |
717 | \sa QStyleHints |
718 | |
719 | */ |
720 | qreal QQuickRangeSlider::touchDragThreshold() const |
721 | { |
722 | Q_D(const QQuickRangeSlider); |
723 | return d->touchDragThreshold; |
724 | } |
725 | |
726 | void QQuickRangeSlider::setTouchDragThreshold(qreal touchDragThreshold) |
727 | { |
728 | Q_D(QQuickRangeSlider); |
729 | if (d->touchDragThreshold == touchDragThreshold) |
730 | return; |
731 | |
732 | d->touchDragThreshold = touchDragThreshold; |
733 | emit touchDragThresholdChanged(); |
734 | } |
735 | |
736 | void QQuickRangeSlider::resetTouchDragThreshold() |
737 | { |
738 | setTouchDragThreshold(-1); |
739 | } |
740 | |
741 | /*! |
742 | \since QtQuick.Controls 2.5 (Qt 5.12) |
743 | \qmlmethod real QtQuick.Controls::RangeSlider::valueAt(real position) |
744 | |
745 | Returns the value for the given \a position. |
746 | |
747 | \sa first.value, second.value, first.position, second.position, live |
748 | */ |
749 | qreal QQuickRangeSlider::valueAt(qreal position) const |
750 | { |
751 | Q_D(const QQuickRangeSlider); |
752 | const qreal value = (d->to - d->from) * position; |
753 | if (qFuzzyIsNull(d: d->stepSize)) |
754 | return d->from + value; |
755 | return d->from + qRound(d: value / d->stepSize) * d->stepSize; |
756 | } |
757 | |
758 | /*! |
759 | \qmlproperty real QtQuick.Controls::RangeSlider::first.value |
760 | \qmlproperty real QtQuick.Controls::RangeSlider::first.position |
761 | \qmlproperty real QtQuick.Controls::RangeSlider::first.visualPosition |
762 | \qmlproperty Item QtQuick.Controls::RangeSlider::first.handle |
763 | \qmlproperty bool QtQuick.Controls::RangeSlider::first.pressed |
764 | \qmlproperty bool QtQuick.Controls::RangeSlider::first.hovered |
765 | \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleWidth |
766 | \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleHeight |
767 | |
768 | \table |
769 | \header |
770 | \li Property |
771 | \li Description |
772 | \row |
773 | \li value |
774 | \li This property holds the value of the first handle in the range |
775 | \c from - \c to. |
776 | |
777 | If \l from is greater than \l to, the value of the first handle |
778 | must be greater than the second, and vice versa. |
779 | |
780 | The default value is \c 0.0. |
781 | \row |
782 | \li handle |
783 | \li This property holds the first handle item. |
784 | \row |
785 | \li visualPosition |
786 | \li This property holds the visual position of the first handle. |
787 | |
788 | The position is expressed as a fraction of the control's size, in the range |
789 | \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the |
790 | value is equal to \c {1.0 - position}. This makes the value suitable for |
791 | visualizing the slider, taking right-to-left support into account. |
792 | \row |
793 | \li position |
794 | \li This property holds the logical position of the first handle. |
795 | |
796 | The position is expressed as a fraction of the control's size, in the range |
797 | \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware |
798 | \l {first.visualPosition}{visualPosition} should be used instead. |
799 | \row |
800 | \li pressed |
801 | \li This property holds whether the first handle is pressed by either touch, |
802 | mouse, or keys. |
803 | \row |
804 | \li hovered |
805 | \li This property holds whether the first handle is hovered. |
806 | This property was introduced in QtQuick.Controls 2.1. |
807 | \row |
808 | \li implicitHandleWidth |
809 | \li This property holds the implicit width of the first handle. |
810 | This property was introduced in QtQuick.Controls 2.5. |
811 | \row |
812 | \li implicitHandleHeight |
813 | \li This property holds the implicit height of the first handle. |
814 | This property was introduced in QtQuick.Controls 2.5. |
815 | \endtable |
816 | |
817 | \sa first.moved(), first.increase(), first.decrease() |
818 | */ |
819 | QQuickRangeSliderNode *QQuickRangeSlider::first() const |
820 | { |
821 | Q_D(const QQuickRangeSlider); |
822 | return d->first; |
823 | } |
824 | |
825 | /*! |
826 | \qmlsignal void QtQuick.Controls::RangeSlider::first.moved() |
827 | \qmlsignal void QtQuick.Controls::RangeSlider::second.moved() |
828 | \since QtQuick.Controls 2.5 |
829 | |
830 | This signal is emitted when either the first or second handle has been |
831 | interactively moved by the user by either touch, mouse, or keys. |
832 | |
833 | \sa first, second |
834 | */ |
835 | |
836 | /*! |
837 | \qmlproperty real QtQuick.Controls::RangeSlider::second.value |
838 | \qmlproperty real QtQuick.Controls::RangeSlider::second.position |
839 | \qmlproperty real QtQuick.Controls::RangeSlider::second.visualPosition |
840 | \qmlproperty Item QtQuick.Controls::RangeSlider::second.handle |
841 | \qmlproperty bool QtQuick.Controls::RangeSlider::second.pressed |
842 | \qmlproperty bool QtQuick.Controls::RangeSlider::second.hovered |
843 | \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleWidth |
844 | \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleHeight |
845 | |
846 | \table |
847 | \header |
848 | \li Property |
849 | \li Description |
850 | \row |
851 | \li value |
852 | \li This property holds the value of the second handle in the range |
853 | \c from - \c to. |
854 | |
855 | If \l from is greater than \l to, the value of the first handle |
856 | must be greater than the second, and vice versa. |
857 | |
858 | The default value is \c 0.0. |
859 | \row |
860 | \li handle |
861 | \li This property holds the second handle item. |
862 | \row |
863 | \li visualPosition |
864 | \li This property holds the visual position of the second handle. |
865 | |
866 | The position is expressed as a fraction of the control's size, in the range |
867 | \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the |
868 | value is equal to \c {1.0 - position}. This makes the value suitable for |
869 | visualizing the slider, taking right-to-left support into account. |
870 | \row |
871 | \li position |
872 | \li This property holds the logical position of the second handle. |
873 | |
874 | The position is expressed as a fraction of the control's size, in the range |
875 | \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware |
876 | \l {second.visualPosition}{visualPosition} should be used instead. |
877 | \row |
878 | \li pressed |
879 | \li This property holds whether the second handle is pressed by either touch, |
880 | mouse, or keys. |
881 | \row |
882 | \li hovered |
883 | \li This property holds whether the second handle is hovered. |
884 | This property was introduced in QtQuick.Controls 2.1. |
885 | \row |
886 | \li implicitHandleWidth |
887 | \li This property holds the implicit width of the second handle. |
888 | This property was introduced in QtQuick.Controls 2.5. |
889 | \row |
890 | \li implicitHandleHeight |
891 | \li This property holds the implicit height of the second handle. |
892 | This property was introduced in QtQuick.Controls 2.5. |
893 | \endtable |
894 | |
895 | \sa second.moved(), second.increase(), second.decrease() |
896 | */ |
897 | QQuickRangeSliderNode *QQuickRangeSlider::second() const |
898 | { |
899 | Q_D(const QQuickRangeSlider); |
900 | return d->second; |
901 | } |
902 | |
903 | /*! |
904 | \qmlproperty real QtQuick.Controls::RangeSlider::stepSize |
905 | |
906 | This property holds the step size. The default value is \c 0.0. |
907 | |
908 | \sa snapMode, first.increase(), first.decrease() |
909 | */ |
910 | qreal QQuickRangeSlider::stepSize() const |
911 | { |
912 | Q_D(const QQuickRangeSlider); |
913 | return d->stepSize; |
914 | } |
915 | |
916 | void QQuickRangeSlider::setStepSize(qreal step) |
917 | { |
918 | Q_D(QQuickRangeSlider); |
919 | if (qFuzzyCompare(p1: d->stepSize, p2: step)) |
920 | return; |
921 | |
922 | d->stepSize = step; |
923 | emit stepSizeChanged(); |
924 | } |
925 | |
926 | /*! |
927 | \qmlproperty enumeration QtQuick.Controls::RangeSlider::snapMode |
928 | |
929 | This property holds the snap mode. |
930 | |
931 | The snap mode determines how the slider handles behave with |
932 | regards to the \l stepSize. |
933 | |
934 | Possible values: |
935 | \value RangeSlider.NoSnap The slider does not snap (default). |
936 | \value RangeSlider.SnapAlways The slider snaps while the handle is dragged. |
937 | \value RangeSlider.SnapOnRelease The slider does not snap while being dragged, but only after the handle is released. |
938 | |
939 | For visual explanations of the various modes, see the |
940 | \l {Slider::}{snapMode} documentation of \l Slider. |
941 | |
942 | \sa stepSize |
943 | */ |
944 | QQuickRangeSlider::SnapMode QQuickRangeSlider::snapMode() const |
945 | { |
946 | Q_D(const QQuickRangeSlider); |
947 | return d->snapMode; |
948 | } |
949 | |
950 | void QQuickRangeSlider::setSnapMode(SnapMode mode) |
951 | { |
952 | Q_D(QQuickRangeSlider); |
953 | if (d->snapMode == mode) |
954 | return; |
955 | |
956 | d->snapMode = mode; |
957 | emit snapModeChanged(); |
958 | } |
959 | |
960 | /*! |
961 | \qmlproperty enumeration QtQuick.Controls::RangeSlider::orientation |
962 | |
963 | This property holds the orientation. |
964 | |
965 | Possible values: |
966 | \value Qt.Horizontal Horizontal (default) |
967 | \value Qt.Vertical Vertical |
968 | |
969 | \sa horizontal, vertical |
970 | */ |
971 | Qt::Orientation QQuickRangeSlider::orientation() const |
972 | { |
973 | Q_D(const QQuickRangeSlider); |
974 | return d->orientation; |
975 | } |
976 | |
977 | void QQuickRangeSlider::setOrientation(Qt::Orientation orientation) |
978 | { |
979 | Q_D(QQuickRangeSlider); |
980 | if (d->orientation == orientation) |
981 | return; |
982 | |
983 | if (orientation == Qt::Horizontal) |
984 | d->setSizePolicy(horizontalPolicy: QLayoutPolicy::Expanding, verticalPolicy: QLayoutPolicy::Fixed); |
985 | else |
986 | d->setSizePolicy(horizontalPolicy: QLayoutPolicy::Fixed, verticalPolicy: QLayoutPolicy::Expanding); |
987 | |
988 | d->orientation = orientation; |
989 | emit orientationChanged(); |
990 | } |
991 | |
992 | /*! |
993 | \qmlmethod void QtQuick.Controls::RangeSlider::setValues(real firstValue, real secondValue) |
994 | |
995 | Sets \l first.value and \l second.value with the given arguments. |
996 | |
997 | If \l to is larger than \l from and \a firstValue is larger than |
998 | \a secondValue, firstValue will be clamped to secondValue. |
999 | |
1000 | If \l from is larger than \l to and secondValue is larger than |
1001 | firstValue, secondValue will be clamped to firstValue. |
1002 | |
1003 | This function may be necessary to set the first and second values |
1004 | after the control has been completed, as there is a circular |
1005 | dependency between firstValue and secondValue which can cause |
1006 | assigned values to be clamped to each other. |
1007 | |
1008 | \sa stepSize |
1009 | */ |
1010 | void QQuickRangeSlider::setValues(qreal firstValue, qreal secondValue) |
1011 | { |
1012 | Q_D(QQuickRangeSlider); |
1013 | // Restrict the values to be within to and from. |
1014 | const qreal smaller = qMin(a: d->to, b: d->from); |
1015 | const qreal larger = qMax(a: d->to, b: d->from); |
1016 | firstValue = qBound(min: smaller, val: firstValue, max: larger); |
1017 | secondValue = qBound(min: smaller, val: secondValue, max: larger); |
1018 | |
1019 | if (d->from > d->to) { |
1020 | // If the from and to values are reversed, the secondValue |
1021 | // might be less than the first value, which is not allowed. |
1022 | if (secondValue > firstValue) |
1023 | secondValue = firstValue; |
1024 | } else { |
1025 | // Otherwise, clamp first to second if it's too large. |
1026 | if (firstValue > secondValue) |
1027 | firstValue = secondValue; |
1028 | } |
1029 | |
1030 | // Then set both values. If they didn't change, no change signal will be emitted. |
1031 | QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first); |
1032 | if (firstValue != firstPrivate->value) { |
1033 | firstPrivate->value = firstValue; |
1034 | emit d->first->valueChanged(); |
1035 | } |
1036 | |
1037 | QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second); |
1038 | if (secondValue != secondPrivate->value) { |
1039 | secondPrivate->value = secondValue; |
1040 | emit d->second->valueChanged(); |
1041 | } |
1042 | |
1043 | // After we've set both values, then we can update the positions. |
1044 | // If we don't do this last, the positions may be incorrect. |
1045 | firstPrivate->updatePosition(ignoreOtherPosition: true); |
1046 | secondPrivate->updatePosition(); |
1047 | } |
1048 | |
1049 | /*! |
1050 | \since QtQuick.Controls 2.2 (Qt 5.9) |
1051 | \qmlproperty bool QtQuick.Controls::RangeSlider::live |
1052 | |
1053 | This property holds whether the slider provides live updates for the \l first.value |
1054 | and \l second.value properties while the respective handles are dragged. |
1055 | |
1056 | The default value is \c true. |
1057 | |
1058 | \sa first.value, second.value |
1059 | */ |
1060 | bool QQuickRangeSlider::live() const |
1061 | { |
1062 | Q_D(const QQuickRangeSlider); |
1063 | return d->live; |
1064 | } |
1065 | |
1066 | void QQuickRangeSlider::setLive(bool live) |
1067 | { |
1068 | Q_D(QQuickRangeSlider); |
1069 | if (d->live == live) |
1070 | return; |
1071 | |
1072 | d->live = live; |
1073 | emit liveChanged(); |
1074 | } |
1075 | |
1076 | /*! |
1077 | \since QtQuick.Controls 2.3 (Qt 5.10) |
1078 | \qmlproperty bool QtQuick.Controls::RangeSlider::horizontal |
1079 | \readonly |
1080 | |
1081 | This property holds whether the slider is horizontal. |
1082 | |
1083 | \sa orientation |
1084 | */ |
1085 | bool QQuickRangeSlider::isHorizontal() const |
1086 | { |
1087 | Q_D(const QQuickRangeSlider); |
1088 | return d->orientation == Qt::Horizontal; |
1089 | } |
1090 | |
1091 | /*! |
1092 | \since QtQuick.Controls 2.3 (Qt 5.10) |
1093 | \qmlproperty bool QtQuick.Controls::RangeSlider::vertical |
1094 | \readonly |
1095 | |
1096 | This property holds whether the slider is vertical. |
1097 | |
1098 | \sa orientation |
1099 | */ |
1100 | bool QQuickRangeSlider::isVertical() const |
1101 | { |
1102 | Q_D(const QQuickRangeSlider); |
1103 | return d->orientation == Qt::Vertical; |
1104 | } |
1105 | |
1106 | void QQuickRangeSlider::focusInEvent(QFocusEvent *event) |
1107 | { |
1108 | Q_D(QQuickRangeSlider); |
1109 | QQuickControl::focusInEvent(event); |
1110 | |
1111 | // The active focus ends up to RangeSlider when using forceActiveFocus() |
1112 | // or QML KeyNavigation. We must forward the focus to one of the handles, |
1113 | // because RangeSlider handles key events for the focused handle. If |
1114 | // neither handle has active focus, RangeSlider doesn't do anything. |
1115 | QQuickItem *handle = nextItemInFocusChain(); |
1116 | // QQuickItem::nextItemInFocusChain() only works as desired with |
1117 | // Qt::TabFocusAllControls. otherwise pick the first handle |
1118 | if (!handle || handle == this) |
1119 | handle = d->first->handle(); |
1120 | if (handle) |
1121 | handle->forceActiveFocus(reason: event->reason()); |
1122 | } |
1123 | |
1124 | void QQuickRangeSlider::keyPressEvent(QKeyEvent *event) |
1125 | { |
1126 | Q_D(QQuickRangeSlider); |
1127 | QQuickControl::keyPressEvent(event); |
1128 | |
1129 | QQuickRangeSliderNode *focusNode = d->first->handle()->hasActiveFocus() |
1130 | ? d->first : (d->second->handle()->hasActiveFocus() ? d->second : nullptr); |
1131 | if (!focusNode) |
1132 | return; |
1133 | |
1134 | const qreal oldValue = focusNode->value(); |
1135 | if (d->orientation == Qt::Horizontal) { |
1136 | if (event->key() == Qt::Key_Left) { |
1137 | focusNode->setPressed(true); |
1138 | if (isMirrored()) |
1139 | focusNode->increase(); |
1140 | else |
1141 | focusNode->decrease(); |
1142 | event->accept(); |
1143 | } else if (event->key() == Qt::Key_Right) { |
1144 | focusNode->setPressed(true); |
1145 | if (isMirrored()) |
1146 | focusNode->decrease(); |
1147 | else |
1148 | focusNode->increase(); |
1149 | event->accept(); |
1150 | } |
1151 | } else { |
1152 | if (event->key() == Qt::Key_Up) { |
1153 | focusNode->setPressed(true); |
1154 | focusNode->increase(); |
1155 | event->accept(); |
1156 | } else if (event->key() == Qt::Key_Down) { |
1157 | focusNode->setPressed(true); |
1158 | focusNode->decrease(); |
1159 | event->accept(); |
1160 | } |
1161 | } |
1162 | if (!qFuzzyCompare(p1: focusNode->value(), p2: oldValue)) |
1163 | emit focusNode->moved(); |
1164 | } |
1165 | |
1166 | void QQuickRangeSlider::hoverEnterEvent(QHoverEvent *event) |
1167 | { |
1168 | Q_D(QQuickRangeSlider); |
1169 | QQuickControl::hoverEnterEvent(event); |
1170 | d->updateHover(pos: event->position()); |
1171 | event->ignore(); |
1172 | } |
1173 | |
1174 | void QQuickRangeSlider::hoverMoveEvent(QHoverEvent *event) |
1175 | { |
1176 | Q_D(QQuickRangeSlider); |
1177 | QQuickControl::hoverMoveEvent(event); |
1178 | d->updateHover(pos: event->position()); |
1179 | event->ignore(); |
1180 | } |
1181 | |
1182 | void QQuickRangeSlider::hoverLeaveEvent(QHoverEvent *event) |
1183 | { |
1184 | Q_D(QQuickRangeSlider); |
1185 | QQuickControl::hoverLeaveEvent(event); |
1186 | d->first->setHovered(false); |
1187 | d->second->setHovered(false); |
1188 | event->ignore(); |
1189 | } |
1190 | |
1191 | void QQuickRangeSlider::keyReleaseEvent(QKeyEvent *event) |
1192 | { |
1193 | Q_D(QQuickRangeSlider); |
1194 | QQuickControl::keyReleaseEvent(event); |
1195 | d->first->setPressed(false); |
1196 | d->second->setPressed(false); |
1197 | } |
1198 | |
1199 | void QQuickRangeSlider::mousePressEvent(QMouseEvent *event) |
1200 | { |
1201 | Q_D(QQuickRangeSlider); |
1202 | QQuickControl::mousePressEvent(event); |
1203 | d->handleMove(point: event->position(), timestamp: event->timestamp()); |
1204 | setKeepMouseGrab(true); |
1205 | } |
1206 | |
1207 | #if QT_CONFIG(quicktemplates2_multitouch) |
1208 | void QQuickRangeSlider::touchEvent(QTouchEvent *event) |
1209 | { |
1210 | Q_D(QQuickRangeSlider); |
1211 | switch (event->type()) { |
1212 | case QEvent::TouchUpdate: |
1213 | for (const QTouchEvent::TouchPoint &point : event->points()) { |
1214 | if (!d->acceptTouch(point)) |
1215 | continue; |
1216 | |
1217 | switch (point.state()) { |
1218 | case QEventPoint::Pressed: |
1219 | d->handlePress(point: point.position(), timestamp: event->timestamp()); |
1220 | break; |
1221 | case QEventPoint::Updated: |
1222 | if (!keepTouchGrab()) { |
1223 | if (d->orientation == Qt::Horizontal) |
1224 | setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(d: point.position().x() - point.pressPosition().x(), axis: Qt::XAxis, tp: &point, startDragThreshold: qRound(d: d->touchDragThreshold))); |
1225 | else |
1226 | setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(d: point.position().y() - point.pressPosition().y(), axis: Qt::YAxis, tp: &point, startDragThreshold: qRound(d: d->touchDragThreshold))); |
1227 | } |
1228 | if (keepTouchGrab()) |
1229 | d->handleMove(point: point.position(), timestamp: event->timestamp()); |
1230 | break; |
1231 | case QEventPoint::Released: |
1232 | d->handleRelease(point: point.position(), timestamp: event->timestamp()); |
1233 | break; |
1234 | default: |
1235 | break; |
1236 | } |
1237 | } |
1238 | break; |
1239 | |
1240 | default: |
1241 | QQuickControl::touchEvent(event); |
1242 | break; |
1243 | } |
1244 | } |
1245 | #endif |
1246 | |
1247 | void QQuickRangeSlider::mirrorChange() |
1248 | { |
1249 | Q_D(QQuickRangeSlider); |
1250 | QQuickControl::mirrorChange(); |
1251 | emit d->first->visualPositionChanged(); |
1252 | emit d->second->visualPositionChanged(); |
1253 | } |
1254 | |
1255 | void QQuickRangeSlider::classBegin() |
1256 | { |
1257 | Q_D(QQuickRangeSlider); |
1258 | QQuickControl::classBegin(); |
1259 | |
1260 | QQmlContext *context = qmlContext(this); |
1261 | if (context) { |
1262 | QQmlEngine::setContextForObject(d->first, context); |
1263 | QQmlEngine::setContextForObject(d->second, context); |
1264 | } |
1265 | } |
1266 | |
1267 | void QQuickRangeSlider::componentComplete() |
1268 | { |
1269 | Q_D(QQuickRangeSlider); |
1270 | QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(node: d->first); |
1271 | QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(node: d->second); |
1272 | firstPrivate->executeHandle(complete: true); |
1273 | secondPrivate->executeHandle(complete: true); |
1274 | |
1275 | QQuickControl::componentComplete(); |
1276 | |
1277 | if (firstPrivate->isPendingValue || secondPrivate->isPendingValue |
1278 | || !qFuzzyCompare(p1: d->from, p2: defaultFrom) || !qFuzzyCompare(p1: d->to, p2: defaultTo)) { |
1279 | // Properties were set while we were loading. To avoid clamping issues that occur when setting the |
1280 | // values of first and second overriding values set by the user, set them all at once at the end. |
1281 | // Another reason that we must set these values here is that the from and to values might have made the old range invalid. |
1282 | setValues(firstValue: firstPrivate->isPendingValue ? firstPrivate->pendingValue : firstPrivate->value, |
1283 | secondValue: secondPrivate->isPendingValue ? secondPrivate->pendingValue : secondPrivate->value); |
1284 | |
1285 | firstPrivate->pendingValue = 0; |
1286 | firstPrivate->isPendingValue = false; |
1287 | secondPrivate->pendingValue = 0; |
1288 | secondPrivate->isPendingValue = false; |
1289 | } else { |
1290 | // If there was no pending data, we must still update the positions, |
1291 | // as first.setValue()/second.setValue() won't be called as part of default construction. |
1292 | // Don't need to ignore the second position when updating the first position here, |
1293 | // as our default values are guaranteed to be valid. |
1294 | firstPrivate->updatePosition(); |
1295 | secondPrivate->updatePosition(); |
1296 | } |
1297 | } |
1298 | |
1299 | /*! |
1300 | \qmlmethod void QtQuick.Controls::RangeSlider::first.increase() |
1301 | |
1302 | Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined. |
1303 | |
1304 | \sa first |
1305 | */ |
1306 | |
1307 | /*! |
1308 | \qmlmethod void QtQuick.Controls::RangeSlider::first.decrease() |
1309 | |
1310 | Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined. |
1311 | |
1312 | \sa first |
1313 | */ |
1314 | |
1315 | /*! |
1316 | \qmlmethod void QtQuick.Controls::RangeSlider::second.increase() |
1317 | |
1318 | Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined. |
1319 | |
1320 | \sa second |
1321 | */ |
1322 | |
1323 | /*! |
1324 | \qmlmethod void QtQuick.Controls::RangeSlider::second.decrease() |
1325 | |
1326 | Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined. |
1327 | |
1328 | \sa second |
1329 | */ |
1330 | |
1331 | #if QT_CONFIG(accessibility) |
1332 | QAccessible::Role QQuickRangeSlider::accessibleRole() const |
1333 | { |
1334 | return QAccessible::Slider; |
1335 | } |
1336 | #endif |
1337 | |
1338 | QT_END_NAMESPACE |
1339 | |
1340 | #include "moc_qquickrangeslider_p.cpp" |
1341 |
Definitions
- QQuickRangeSliderNodePrivate
- QQuickRangeSliderNodePrivate
- isFirst
- setPosition
- updatePosition
- cancelHandle
- executeHandle
- get
- QQuickRangeSliderNode
- ~QQuickRangeSliderNode
- value
- setValue
- position
- visualPosition
- handle
- setHandle
- isPressed
- setPressed
- isHovered
- setHovered
- implicitHandleWidth
- implicitHandleHeight
- increase
- decrease
- defaultFrom
- defaultTo
- QQuickRangeSliderPrivate
- valueAt
- snapPosition
- positionAt
- pressedNode
- acceptTouch
- handlePress
- handleMove
- handleRelease
- handleUngrab
- updateHover
- itemImplicitWidthChanged
- itemImplicitHeightChanged
- itemDestroyed
- QQuickRangeSlider
- ~QQuickRangeSlider
- from
- setFrom
- to
- setTo
- touchDragThreshold
- setTouchDragThreshold
- resetTouchDragThreshold
- valueAt
- first
- second
- stepSize
- setStepSize
- snapMode
- setSnapMode
- orientation
- setOrientation
- setValues
- live
- setLive
- isHorizontal
- isVertical
- focusInEvent
- keyPressEvent
- hoverEnterEvent
- hoverMoveEvent
- hoverLeaveEvent
- keyReleaseEvent
- mousePressEvent
- touchEvent
- mirrorChange
- classBegin
- componentComplete
Learn to use CMake with our Intro Training
Find out more