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 | |
54 | QT_BEGIN_NAMESPACE |
55 | |
56 | QQuickRangeModel1Private::QQuickRangeModel1Private(QQuickRangeModel1 *qq) |
57 | : q_ptr(qq) |
58 | { |
59 | } |
60 | |
61 | QQuickRangeModel1Private::~QQuickRangeModel1Private() |
62 | { |
63 | } |
64 | |
65 | void 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 | |
87 | qreal 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 | |
128 | qreal 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 | |
156 | void 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 | |
180 | QQuickRangeModel1::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 | |
192 | QQuickRangeModel1::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 | |
203 | QQuickRangeModel1::~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 | |
215 | void 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 | |
250 | void 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 | |
284 | void QQuickRangeModel1::setMinimum(qreal min) |
285 | { |
286 | Q_D(const QQuickRangeModel1); |
287 | setRange(min, max: d->maximum); |
288 | } |
289 | |
290 | qreal 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 | |
303 | void 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 | |
311 | qreal 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 | |
325 | void 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 | |
341 | qreal 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 | |
353 | qreal 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 | |
361 | void QQuickRangeModel1::classBegin() |
362 | { |
363 | } |
364 | |
365 | void 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 | |
387 | qreal 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 | |
396 | void 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 | |
419 | void QQuickRangeModel1::setPositionAtMinimum(qreal min) |
420 | { |
421 | Q_D(QQuickRangeModel1); |
422 | setPositionRange(min, max: d->posatmax); |
423 | } |
424 | |
425 | qreal 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 | |
438 | void QQuickRangeModel1::setPositionAtMaximum(qreal max) |
439 | { |
440 | Q_D(QQuickRangeModel1); |
441 | setPositionRange(min: d->posatmin, max); |
442 | } |
443 | |
444 | qreal 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 | |
456 | qreal 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 | |
474 | qreal 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 | |
483 | void 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 | |
508 | void 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 | |
521 | bool 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 | |
531 | void 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 | |
541 | void QQuickRangeModel1::toMaximum() |
542 | { |
543 | Q_D(const QQuickRangeModel1); |
544 | setValue(d->maximum); |
545 | } |
546 | |
547 | void 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 | |
556 | void 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 | |
565 | QT_END_NAMESPACE |
566 | |