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 Qt Quick Controls module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
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 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.LGPL3 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-3.0.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 (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40/*!
41 With this class, the user sets a value range and a position range, which
42 represent the valid values/positions the model can assume. It is worth telling
43 that the value property always has priority over the position property. A nice use
44 case, would be a Slider implementation with the help of QQuickRangeModel. If the user sets
45 a value range to [0,100], a position range to [50,100] and sets the value
46 to 80, the equivalent position would be 90. After that, if the user decides to
47 resize the slider, the value would be the same, but the knob position would
48 be updated due to the new position range.
49*/
50
51#include "qquickrangemodel_p.h"
52#include "qquickrangemodel_p_p.h"
53
54QT_BEGIN_NAMESPACE
55
56QQuickRangeModel1Private::QQuickRangeModel1Private(QQuickRangeModel1 *qq)
57 : q_ptr(qq)
58{
59}
60
61QQuickRangeModel1Private::~QQuickRangeModel1Private()
62{
63}
64
65void QQuickRangeModel1Private::init()
66{
67 minimum = 0;
68 maximum = 99;
69 stepSize = 0;
70 value = 0;
71 pos = 0;
72 posatmin = 0;
73 posatmax = 0;
74 inverted = false;
75 isComplete = false;
76 positionChanged = false;
77 valueChanged = false;
78}
79
80/*!
81 Calculates the position that is going to be seen outside by the component
82 that is using QQuickRangeModel. It takes into account the \l stepSize,
83 \l positionAtMinimum, \l positionAtMaximum properties
84 and \a position that is passed as parameter.
85*/
86
87qreal QQuickRangeModel1Private::publicPosition(qreal position) const
88{
89 // Calculate the equivalent stepSize for the position property.
90 const qreal min = effectivePosAtMin();
91 const qreal max = effectivePosAtMax();
92 const qreal valueRange = maximum - minimum;
93 const qreal positionValueRatio = valueRange ? (max - min) / valueRange : 0;
94 const qreal positionStep = stepSize * positionValueRatio;
95
96 if (positionStep == 0)
97 return (min < max) ? qBound(min, val: position, max) : qBound(min: max, val: position, max: min);
98
99 const int stepSizeMultiplier = (position - min) / positionStep;
100
101 // Test whether value is below minimum range
102 if (stepSizeMultiplier < 0)
103 return min;
104
105 qreal leftEdge = (stepSizeMultiplier * positionStep) + min;
106 qreal rightEdge = ((stepSizeMultiplier + 1) * positionStep) + min;
107
108 if (min < max) {
109 leftEdge = qMin(a: leftEdge, b: max);
110 rightEdge = qMin(a: rightEdge, b: max);
111 } else {
112 leftEdge = qMax(a: leftEdge, b: max);
113 rightEdge = qMax(a: rightEdge, b: max);
114 }
115
116 if (qAbs(t: leftEdge - position) <= qAbs(t: rightEdge - position))
117 return leftEdge;
118 return rightEdge;
119}
120
121/*!
122 Calculates the value that is going to be seen outside by the component
123 that is using QQuickRangeModel. It takes into account the \l stepSize,
124 \l minimumValue, \l maximumValue properties
125 and \a value that is passed as parameter.
126*/
127
128qreal QQuickRangeModel1Private::publicValue(qreal value) const
129{
130 // It is important to do value-within-range check this
131 // late (as opposed to during setPosition()). The reason is
132 // QML bindings; a position that is initially invalid because it lays
133 // outside the range, might become valid later if the range changes.
134
135 if (stepSize == 0)
136 return qBound(min: minimum, val: value, max: maximum);
137
138 const int stepSizeMultiplier = (value - minimum) / stepSize;
139
140 // Test whether value is below minimum range
141 if (stepSizeMultiplier < 0)
142 return minimum;
143
144 const qreal leftEdge = qMin(a: maximum, b: (stepSizeMultiplier * stepSize) + minimum);
145 const qreal rightEdge = qMin(a: maximum, b: ((stepSizeMultiplier + 1) * stepSize) + minimum);
146 const qreal middle = (leftEdge + rightEdge) / 2;
147
148 return (value <= middle) ? leftEdge : rightEdge;
149}
150
151/*!
152 Checks if the \l value or \l position, that is seen by the user, has changed and emits the changed signal if it
153 has changed.
154*/
155
156void QQuickRangeModel1Private::emitValueAndPositionIfChanged(const qreal oldValue, const qreal oldPosition)
157{
158 Q_Q(QQuickRangeModel1);
159
160 // Effective value and position might have changed even in cases when e.g. d->value is
161 // unchanged. This will be the case when operating with values outside range:
162 const qreal newValue = q->value();
163 const qreal newPosition = q->position();
164
165 if (isComplete) {
166 if (!qFuzzyCompare(p1: newValue, p2: oldValue))
167 emit q->valueChanged(value: newValue);
168 if (!qFuzzyCompare(p1: newPosition, p2: oldPosition))
169 emit q->positionChanged(position: newPosition);
170 } else {
171 positionChanged |= qFuzzyCompare(p1: oldPosition, p2: newPosition);
172 valueChanged |= !qFuzzyCompare(p1: oldValue, p2: newValue);
173 }
174}
175
176/*!
177 Constructs a QQuickRangeModel with \a parent
178*/
179
180QQuickRangeModel1::QQuickRangeModel1(QObject *parent)
181 : QObject(parent), d_ptr(new QQuickRangeModel1Private(this))
182{
183 Q_D(QQuickRangeModel1);
184 d->init();
185}
186
187/*!
188 \internal
189 Constructs a QQuickRangeModel with private class pointer \a dd and \a parent
190*/
191
192QQuickRangeModel1::QQuickRangeModel1(QQuickRangeModel1Private &dd, QObject *parent)
193 : QObject(parent), d_ptr(&dd)
194{
195 Q_D(QQuickRangeModel1);
196 d->init();
197}
198
199/*!
200 Destroys the QQuickRangeModel
201*/
202
203QQuickRangeModel1::~QQuickRangeModel1()
204{
205 delete d_ptr;
206 d_ptr = 0;
207}
208
209/*!
210 Sets the range of valid positions, that \l position can assume externally, with
211 \a min and \a max.
212 Such range is represented by \l positionAtMinimum and \l positionAtMaximum
213*/
214
215void QQuickRangeModel1::setPositionRange(qreal min, qreal max)
216{
217 Q_D(QQuickRangeModel1);
218
219 bool emitPosAtMinChanged = !qFuzzyCompare(p1: min, p2: d->posatmin);
220 bool emitPosAtMaxChanged = !qFuzzyCompare(p1: max, p2: d->posatmax);
221
222 if (!(emitPosAtMinChanged || emitPosAtMaxChanged))
223 return;
224
225 const qreal oldPosition = position();
226 d->posatmin = min;
227 d->posatmax = max;
228
229 // When a new positionRange is defined, the position property must be updated based on the value property.
230 // For instance, imagine that you have a valueRange of [0,100] and a position range of [20,100],
231 // if a user set the value to 50, the position would be 60. If this positionRange is updated to [0,100], then
232 // the new position, based on the value (50), will be 50.
233 // If the newPosition is different than the old one, it must be updated, in order to emit
234 // the positionChanged signal.
235 d->pos = d->equivalentPosition(value: d->value);
236
237 if (emitPosAtMinChanged)
238 emit positionAtMinimumChanged(min: d->posatmin);
239 if (emitPosAtMaxChanged)
240 emit positionAtMaximumChanged(max: d->posatmax);
241
242 d->emitValueAndPositionIfChanged(oldValue: value(), oldPosition);
243}
244/*!
245 Sets the range of valid values, that \l value can assume externally, with
246 \a min and \a max. The range has the following constraint: \a min must be less or equal \a max
247 Such range is represented by \l minimumValue and \l maximumValue
248*/
249
250void QQuickRangeModel1::setRange(qreal min, qreal max)
251{
252 Q_D(QQuickRangeModel1);
253
254 bool emitMinimumChanged = !qFuzzyCompare(p1: min, p2: d->minimum);
255 bool emitMaximumChanged = !qFuzzyCompare(p1: max, p2: d->maximum);
256
257 if (!(emitMinimumChanged || emitMaximumChanged))
258 return;
259
260 const qreal oldValue = value();
261 const qreal oldPosition = position();
262
263 d->minimum = min;
264 d->maximum = qMax(a: min, b: max);
265
266 // Update internal position if it was changed. It can occur if internal value changes, due to range update
267 d->pos = d->equivalentPosition(value: d->value);
268
269 if (emitMinimumChanged)
270 emit minimumChanged(min: d->minimum);
271 if (emitMaximumChanged)
272 emit maximumChanged(max: d->maximum);
273
274 d->emitValueAndPositionIfChanged(oldValue, oldPosition);
275}
276
277/*!
278 \property QQuickRangeModel1::minimumValue
279 \brief the minimum value that \l value can assume
280
281 This property's default value is 0
282*/
283
284void QQuickRangeModel1::setMinimum(qreal min)
285{
286 Q_D(const QQuickRangeModel1);
287 setRange(min, max: d->maximum);
288}
289
290qreal QQuickRangeModel1::minimum() const
291{
292 Q_D(const QQuickRangeModel1);
293 return d->minimum;
294}
295
296/*!
297 \property QQuickRangeModel1::maximumValue
298 \brief the maximum value that \l value can assume
299
300 This property's default value is 99
301*/
302
303void QQuickRangeModel1::setMaximum(qreal max)
304{
305 Q_D(const QQuickRangeModel1);
306 // if the new maximum value is smaller than
307 // minimum, update minimum too
308 setRange(min: qMin(a: d->minimum, b: max), max);
309}
310
311qreal QQuickRangeModel1::maximum() const
312{
313 Q_D(const QQuickRangeModel1);
314 return d->maximum;
315}
316
317/*!
318 \property QQuickRangeModel1::stepSize
319 \brief the value that is added to the \l value and \l position property
320
321 Example: If a user sets a range of [0,100] and stepSize
322 to 30, the valid values that are going to be seen externally would be: 0, 30, 60, 90, 100.
323*/
324
325void QQuickRangeModel1::setStepSize(qreal stepSize)
326{
327 Q_D(QQuickRangeModel1);
328
329 stepSize = qMax(a: qreal(0.0), b: stepSize);
330 if (qFuzzyCompare(p1: stepSize, p2: d->stepSize))
331 return;
332
333 const qreal oldValue = value();
334 const qreal oldPosition = position();
335 d->stepSize = stepSize;
336
337 emit stepSizeChanged(stepSize: d->stepSize);
338 d->emitValueAndPositionIfChanged(oldValue, oldPosition);
339}
340
341qreal QQuickRangeModel1::stepSize() const
342{
343 Q_D(const QQuickRangeModel1);
344 return d->stepSize;
345}
346
347/*!
348 Returns a valid position, respecting the \l positionAtMinimum,
349 \l positionAtMaximum and the \l stepSize properties.
350 Such calculation is based on the parameter \a value (which is valid externally).
351*/
352
353qreal QQuickRangeModel1::positionForValue(qreal value) const
354{
355 Q_D(const QQuickRangeModel1);
356
357 const qreal unconstrainedPosition = d->equivalentPosition(value);
358 return d->publicPosition(position: unconstrainedPosition);
359}
360
361void QQuickRangeModel1::classBegin()
362{
363}
364
365void QQuickRangeModel1::componentComplete()
366{
367 Q_D(QQuickRangeModel1);
368 d->isComplete = true;
369 emit minimumChanged(min: minimum());
370 emit maximumChanged(max: maximum());
371 if (d->valueChanged)
372 emit valueChanged(value: value());
373 if (d->positionChanged)
374 emit positionChanged(position: position());
375}
376
377/*!
378 \property QQuickRangeModel1::position
379 \brief the current position of the model
380
381 Represents a valid external position, based on the \l positionAtMinimum,
382 \l positionAtMaximum and the \l stepSize properties.
383 The user can set it internally with a position, that is not within the current position range,
384 since it can become valid if the user changes the position range later.
385*/
386
387qreal QQuickRangeModel1::position() const
388{
389 Q_D(const QQuickRangeModel1);
390
391 // Return the internal position but observe boundaries and
392 // stepSize restrictions.
393 return d->publicPosition(position: d->pos);
394}
395
396void QQuickRangeModel1::setPosition(qreal newPosition)
397{
398 Q_D(QQuickRangeModel1);
399
400 if (qFuzzyCompare(p1: newPosition, p2: d->pos))
401 return;
402
403 const qreal oldPosition = position();
404 const qreal oldValue = value();
405
406 // Update position and calculate new value
407 d->pos = newPosition;
408 d->value = d->equivalentValue(pos: d->pos);
409 d->emitValueAndPositionIfChanged(oldValue, oldPosition);
410}
411
412/*!
413 \property QQuickRangeModel1::positionAtMinimum
414 \brief the minimum value that \l position can assume
415
416 This property's default value is 0
417*/
418
419void QQuickRangeModel1::setPositionAtMinimum(qreal min)
420{
421 Q_D(QQuickRangeModel1);
422 setPositionRange(min, max: d->posatmax);
423}
424
425qreal QQuickRangeModel1::positionAtMinimum() const
426{
427 Q_D(const QQuickRangeModel1);
428 return d->posatmin;
429}
430
431/*!
432 \property QQuickRangeModel1::positionAtMaximum
433 \brief the maximum value that \l position can assume
434
435 This property's default value is 0
436*/
437
438void QQuickRangeModel1::setPositionAtMaximum(qreal max)
439{
440 Q_D(QQuickRangeModel1);
441 setPositionRange(min: d->posatmin, max);
442}
443
444qreal QQuickRangeModel1::positionAtMaximum() const
445{
446 Q_D(const QQuickRangeModel1);
447 return d->posatmax;
448}
449
450/*!
451 Returns a valid value, respecting the \l minimumValue,
452 \l maximumValue and the \l stepSize properties.
453 Such calculation is based on the parameter \a position (which is valid externally).
454*/
455
456qreal QQuickRangeModel1::valueForPosition(qreal position) const
457{
458 Q_D(const QQuickRangeModel1);
459
460 const qreal unconstrainedValue = d->equivalentValue(pos: position);
461 return d->publicValue(value: unconstrainedValue);
462}
463
464/*!
465 \property QQuickRangeModel1::value
466 \brief the current value of the model
467
468 Represents a valid external value, based on the \l minimumValue,
469 \l maximumValue and the \l stepSize properties.
470 The user can set it internally with a value, that is not within the current range,
471 since it can become valid if the user changes the range later.
472*/
473
474qreal QQuickRangeModel1::value() const
475{
476 Q_D(const QQuickRangeModel1);
477
478 // Return internal value but observe boundaries and
479 // stepSize restrictions
480 return d->publicValue(value: d->value);
481}
482
483void QQuickRangeModel1::setValue(qreal newValue)
484{
485 Q_D(QQuickRangeModel1);
486
487 if (qFuzzyCompare(p1: newValue, p2: d->value))
488 return;
489
490 const qreal oldValue = value();
491 const qreal oldPosition = position();
492
493 // Update relative value and position
494 d->value = newValue;
495 d->pos = d->equivalentPosition(value: d->value);
496 d->emitValueAndPositionIfChanged(oldValue, oldPosition);
497}
498
499/*!
500 \property QQuickRangeModel1::inverted
501 \brief the model is inverted or not
502
503 The model can be represented with an inverted behavior, e.g. when \l value assumes
504 the maximum value (represented by \l maximumValue) the \l position will be at its
505 minimum (represented by \l positionAtMinimum).
506*/
507
508void QQuickRangeModel1::setInverted(bool inverted)
509{
510 Q_D(QQuickRangeModel1);
511 if (inverted == d->inverted)
512 return;
513
514 d->inverted = inverted;
515 emit invertedChanged(inverted: d->inverted);
516
517 // After updating the internal value, the position property can change.
518 setPosition(d->equivalentPosition(value: d->value));
519}
520
521bool QQuickRangeModel1::inverted() const
522{
523 Q_D(const QQuickRangeModel1);
524 return d->inverted;
525}
526
527/*!
528 Sets the \l value to \l minimumValue.
529*/
530
531void QQuickRangeModel1::toMinimum()
532{
533 Q_D(const QQuickRangeModel1);
534 setValue(d->minimum);
535}
536
537/*!
538 Sets the \l value to \l maximumValue.
539*/
540
541void QQuickRangeModel1::toMaximum()
542{
543 Q_D(const QQuickRangeModel1);
544 setValue(d->maximum);
545}
546
547void QQuickRangeModel1::increaseSingleStep()
548{
549 Q_D(const QQuickRangeModel1);
550 if (qFuzzyIsNull(d: d->stepSize))
551 setValue(value() + (d->maximum - d->minimum)/10.0);
552 else
553 setValue(value() + d->stepSize);
554}
555
556void QQuickRangeModel1::decreaseSingleStep()
557{
558 Q_D(const QQuickRangeModel1);
559 if (qFuzzyIsNull(d: d->stepSize))
560 setValue(value() - (d->maximum - d->minimum)/10.0);
561 else
562 setValue(value() - d->stepSize);
563}
564
565QT_END_NAMESPACE
566

source code of qtquickcontrols/src/controls/Private/qquickrangemodel.cpp