QGIS API Documentation 3.41.0-Master (d5b93354e9c)
Loading...
Searching...
No Matches
qgsrangeslider.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrangeslider.cpp
3 ---------------------
4 begin : November 2020
5 copyright : (C) 2020 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsrangeslider.h"
17#include "moc_qgsrangeslider.cpp"
18#include <QPainter>
19#include <QMouseEvent>
20
22 : QgsRangeSlider( Qt::Horizontal, parent )
23{
24}
25
26QgsRangeSlider::QgsRangeSlider( Qt::Orientation orientation, QWidget *parent )
27 : QWidget( parent )
28{
29 mStyleOption.minimum = 0;
30 mStyleOption.maximum = 100;
31 mStyleOption.orientation = orientation;
32
33 setFocusPolicy( Qt::FocusPolicy( style()->styleHint( QStyle::SH_Button_FocusPolicy ) ) );
34 QSizePolicy sp( QSizePolicy::Expanding, QSizePolicy::Fixed, QSizePolicy::Slider );
35 if ( mStyleOption.orientation == Qt::Vertical )
36 sp.transpose();
37 setSizePolicy( sp );
38 setAttribute( Qt::WA_WState_OwnSizePolicy, false );
39
40 setAttribute( Qt::WA_Hover );
41 setMouseTracking( true );
42}
43
45{
46 return mStyleOption.maximum;
47}
48
49void QgsRangeSlider::setMaximum( int maximum )
50{
51 if ( mStyleOption.maximum == maximum )
52 return;
53
54 mStyleOption.maximum = maximum;
55 mStyleOption.minimum = std::min( maximum, mStyleOption.minimum );
56 emit rangeLimitsChanged( mStyleOption.minimum, mStyleOption.maximum );
57
58 if ( mUpperValue > maximum || mLowerValue > maximum )
59 {
60 mUpperValue = std::min( mUpperValue, maximum );
61 mLowerValue = std::min( mLowerValue, maximum );
62 emit rangeChanged( mLowerValue, mUpperValue );
63 }
64
65 update();
66}
67
69{
70 return mStyleOption.minimum;
71}
72
73void QgsRangeSlider::setMinimum( int minimum )
74{
75 if ( mStyleOption.minimum == minimum )
76 return;
77 mStyleOption.minimum = minimum;
78 mStyleOption.maximum = std::max( minimum, mStyleOption.maximum );
79 emit rangeLimitsChanged( mStyleOption.minimum, mStyleOption.maximum );
80
81 if ( mUpperValue < minimum || mLowerValue < minimum )
82 {
83 mUpperValue = std::max( mUpperValue, minimum );
84 mLowerValue = std::max( mLowerValue, minimum );
85 emit rangeChanged( mLowerValue, mUpperValue );
86 }
87
88 update();
89}
90
91void QgsRangeSlider::setRangeLimits( int minimum, int maximum )
92{
93 if ( maximum < minimum )
94 std::swap( minimum, maximum );
95
96 if ( mStyleOption.minimum == minimum && mStyleOption.maximum == maximum )
97 return;
98
99 mStyleOption.minimum = minimum;
100 mStyleOption.maximum = maximum;
101 emit rangeLimitsChanged( mStyleOption.minimum, mStyleOption.maximum );
102
103 if ( mUpperValue < minimum || mLowerValue < minimum || mUpperValue > maximum || mLowerValue > maximum )
104 {
105 mUpperValue = std::min( maximum, std::max( mUpperValue, minimum ) );
106 mLowerValue = std::min( maximum, std::max( mLowerValue, minimum ) );
107 emit rangeChanged( mLowerValue, mUpperValue );
108 }
109
110 update();
111}
112
114{
115 return mLowerValue;
116}
117
118void QgsRangeSlider::setLowerValue( int lowerValue )
119{
120 if ( lowerValue == mLowerValue )
121 return;
122
123 mLowerValue = std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, lowerValue ) );
124 if ( mFixedRangeSize >= 0 )
125 {
126 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
127 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
128 }
129 else
130 {
131 mUpperValue = std::max( mLowerValue, mUpperValue );
132 }
133 emit rangeChanged( mLowerValue, mUpperValue );
134 update();
135}
136
138{
139 return mUpperValue;
140}
141
142
143void QgsRangeSlider::setUpperValue( int upperValue )
144{
145 if ( upperValue == mUpperValue )
146 return;
147
148 mUpperValue = std::max( mStyleOption.minimum, std::min( mStyleOption.maximum, upperValue ) );
149 if ( mFixedRangeSize >= 0 )
150 {
151 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
152 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
153 }
154 else
155 {
156 mLowerValue = std::min( mLowerValue, mUpperValue );
157 }
158
159 emit rangeChanged( mLowerValue, mUpperValue );
160 update();
161}
162
163void QgsRangeSlider::setRange( int lower, int upper )
164{
165 if ( lower == mLowerValue && upper == mUpperValue )
166 return;
167
168 if ( upper < lower )
169 std::swap( lower, upper );
170
171 mLowerValue = std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, lower ) );
172 mUpperValue = std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, upper ) );
173 if ( mFixedRangeSize >= 0 )
174 {
175 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
176 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
177 }
178 else
179 {
180 mUpperValue = std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, upper ) );
181 }
182 emit rangeChanged( mLowerValue, mUpperValue );
183 update();
184}
185
186bool QgsRangeSlider::event( QEvent *event )
187{
188 switch ( event->type() )
189 {
190 case QEvent::HoverEnter:
191 case QEvent::HoverLeave:
192 case QEvent::HoverMove:
193 if ( const QHoverEvent *he = static_cast<const QHoverEvent *>( event ) )
194 updateHoverControl( he->pos() );
195 break;
196 default:
197 break;
198 }
199 return QWidget::event( event );
200}
201
202int QgsRangeSlider::pick( const QPoint &pt ) const
203{
204 return mStyleOption.orientation == Qt::Horizontal ? pt.x() : pt.y();
205}
206
207int QgsRangeSlider::pixelPosToRangeValue( int pos ) const
208{
209 const QRect gr = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderGroove, this );
210 const QRect sr = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
211 int sliderMin, sliderMax, sliderLength;
212 if ( mStyleOption.orientation == Qt::Horizontal )
213 {
214 sliderLength = sr.width();
215 sliderMin = gr.x();
216 sliderMax = gr.right() - sliderLength + 1;
217 }
218 else
219 {
220 sliderLength = sr.height();
221 sliderMin = gr.y();
222 sliderMax = gr.bottom() - sliderLength + 1;
223 }
224
225 int value = QStyle::sliderValueFromPosition( mStyleOption.minimum, mStyleOption.maximum, pos - sliderMin, sliderMax - sliderMin );
226 if ( mFlipped )
227 value = mStyleOption.maximum + mStyleOption.minimum - value;
228 return value;
229}
230
231bool QgsRangeSlider::updateHoverControl( const QPoint &pos )
232{
233 const QRect lastHoverRect = mHoverRect;
234 const bool doesHover = testAttribute( Qt::WA_Hover );
235 if ( doesHover && newHoverControl( pos ) )
236 {
237 update( lastHoverRect );
238 update( mHoverRect );
239 return true;
240 }
241 return !doesHover;
242}
243
244bool QgsRangeSlider::newHoverControl( const QPoint &pos )
245{
246 const Control lastHoverControl = mHoverControl;
247 const QStyle::SubControl lastHoverSubControl = mHoverSubControl;
248
249 mStyleOption.subControls = QStyle::SC_All;
250
251 mStyleOption.sliderPosition = unFlippedSliderPosition( mLowerValue );
252 const QRect lowerHandleRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
253 mStyleOption.sliderPosition = unFlippedSliderPosition( mUpperValue );
254 const QRect upperHandleRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
255
256 const QRect grooveRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderGroove, this );
257 const QRect tickmarksRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderTickmarks, this );
258 if ( lowerHandleRect.contains( pos ) )
259 {
260 mHoverRect = lowerHandleRect;
261 mHoverControl = Lower;
262 mHoverSubControl = QStyle::SC_SliderHandle;
263 setCursor( Qt::OpenHandCursor );
264 }
265 else if ( upperHandleRect.contains( pos ) )
266 {
267 mHoverRect = upperHandleRect;
268 mHoverControl = Upper;
269 mHoverSubControl = QStyle::SC_SliderHandle;
270 setCursor( Qt::OpenHandCursor );
271 }
272 else if ( grooveRect.contains( pos ) )
273 {
274 mHoverRect = grooveRect;
275 mHoverControl = None;
276 mHoverSubControl = QStyle::SC_SliderGroove;
277
278 if ( selectedRangeRect().contains( pos ) )
279 setCursor( Qt::OpenHandCursor );
280 else
281 unsetCursor();
282 }
283 else if ( tickmarksRect.contains( pos ) )
284 {
285 mHoverRect = tickmarksRect;
286 mHoverControl = None;
287 mHoverSubControl = QStyle::SC_SliderTickmarks;
288 unsetCursor();
289 }
290 else
291 {
292 mHoverRect = QRect();
293 mHoverControl = None;
294 mHoverSubControl = QStyle::SC_None;
295 unsetCursor();
296 }
297 return mHoverSubControl != lastHoverSubControl || mHoverControl != lastHoverControl;
298}
299
300QRect QgsRangeSlider::selectedRangeRect()
301{
302 QRect selectionRect;
303
304 mStyleOption.activeSubControls = mHoverControl == Lower || mActiveControl == Lower ? QStyle::SC_SliderHandle : QStyle::SC_None;
305 mStyleOption.sliderPosition = unFlippedSliderPosition( mLowerValue );
306 const QRect lowerHandleRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, nullptr );
307
308 mStyleOption.activeSubControls = mHoverControl == Upper || mActiveControl == Lower ? QStyle::SC_SliderHandle : QStyle::SC_None;
309 mStyleOption.sliderPosition = unFlippedSliderPosition( mUpperValue );
310 const QRect upperHandleRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, nullptr );
311
312 const QRect grooveRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderGroove, nullptr );
313
314 switch ( mStyleOption.orientation )
315 {
316 case Qt::Horizontal:
317 selectionRect = mFlipped ? QRect( upperHandleRect.right(), grooveRect.y(), lowerHandleRect.left() - upperHandleRect.right(), grooveRect.height() )
318 : QRect( lowerHandleRect.right(), grooveRect.y(), upperHandleRect.left() - lowerHandleRect.right(), grooveRect.height() );
319 break;
320
321 case Qt::Vertical:
322 selectionRect = mFlipped ? QRect( grooveRect.x(), lowerHandleRect.top(), grooveRect.width(), upperHandleRect.bottom() - lowerHandleRect.top() )
323 : QRect( grooveRect.x(), upperHandleRect.top(), grooveRect.width(), lowerHandleRect.bottom() - upperHandleRect.top() );
324 break;
325 }
326
327 return selectionRect.adjusted( -1, 1, 1, -1 );
328}
329
331{
332 return mFixedRangeSize;
333}
334
336{
337 if ( size == mFixedRangeSize )
338 return;
339
340 mFixedRangeSize = size;
341
342 if ( mFixedRangeSize >= 0 )
343 setUpperValue( mLowerValue + mFixedRangeSize );
344
345 emit fixedRangeSizeChanged( mFixedRangeSize );
346}
347
348void QgsRangeSlider::applyStep( int step )
349{
350 switch ( mFocusControl )
351 {
352 case Lower:
353 {
354 const int newLowerValue = std::min( mUpperValue, std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, mLowerValue + step ) ) );
355 if ( newLowerValue != mLowerValue )
356 {
357 mLowerValue = newLowerValue;
358 if ( mFixedRangeSize >= 0 )
359 {
360 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
361 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
362 }
363 emit rangeChanged( mLowerValue, mUpperValue );
364 update();
365 }
366 break;
367 }
368
369 case Upper:
370 {
371 const int newUpperValue = std::max( mLowerValue, std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, mUpperValue + step ) ) );
372 if ( newUpperValue != mUpperValue )
373 {
374 mUpperValue = newUpperValue;
375 if ( mFixedRangeSize >= 0 )
376 {
377 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
378 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
379 }
380 emit rangeChanged( mLowerValue, mUpperValue );
381 update();
382 }
383 break;
384 }
385
386 case Range:
387 {
388 if ( step < 0 )
389 {
390 const int previousWidth = mUpperValue - mLowerValue;
391 const int newLowerValue = std::min( mUpperValue, std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, mLowerValue + step ) ) );
392 if ( newLowerValue != mLowerValue )
393 {
394 mLowerValue = newLowerValue;
395 if ( mFixedRangeSize >= 0 )
396 {
397 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
398 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
399 }
400 else
401 {
402 mUpperValue = std::min( mStyleOption.maximum, mLowerValue + previousWidth );
403 }
404 emit rangeChanged( mLowerValue, mUpperValue );
405 update();
406 }
407 }
408 else
409 {
410 const int previousWidth = mUpperValue - mLowerValue;
411 const int newUpperValue = std::max( mLowerValue, std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, mUpperValue + step ) ) );
412 if ( newUpperValue != mUpperValue )
413 {
414 mUpperValue = newUpperValue;
415 if ( mFixedRangeSize >= 0 )
416 {
417 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
418 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
419 }
420 else
421 {
422 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - previousWidth );
423 }
424 emit rangeChanged( mLowerValue, mUpperValue );
425 update();
426 }
427 }
428 break;
429 }
430
431 case None:
432 case Both:
433 break;
434 }
435}
436
437int QgsRangeSlider::unFlippedSliderPosition( int value ) const
438{
439 return mFlipped ? mStyleOption.maximum + mStyleOption.minimum - value : value;
440}
441
443{
444 return mPageStep;
445}
446
448{
449 mPageStep = step;
450}
451
453{
454 return mSingleStep;
455}
456
458{
459 mSingleStep = step;
460}
461
462void QgsRangeSlider::setTickPosition( QSlider::TickPosition position )
463{
464 mStyleOption.tickPosition = position;
465 update();
466}
467
468QSlider::TickPosition QgsRangeSlider::tickPosition() const
469{
470 return mStyleOption.tickPosition;
471}
472
474{
475 mStyleOption.tickInterval = interval;
476 update();
477}
478
480{
481 return mStyleOption.tickInterval;
482}
483
484void QgsRangeSlider::setOrientation( Qt::Orientation orientation )
485{
486 mStyleOption.orientation = orientation;
487 if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) )
488 {
489 setSizePolicy( sizePolicy().transposed() );
490 setAttribute( Qt::WA_WState_OwnSizePolicy, false );
491 }
492 update();
493 updateGeometry();
494}
495
496Qt::Orientation QgsRangeSlider::orientation() const
497{
498 return mStyleOption.orientation;
499}
500
502{
503 return mFlipped;
504}
505
507{
508 mFlipped = flipped;
509 update();
510}
511
512void QgsRangeSlider::paintEvent( QPaintEvent * )
513{
514 QPainter painter( this );
515
516 mStyleOption.initFrom( this );
517 mStyleOption.rect = rect();
518 mStyleOption.sliderPosition = mStyleOption.minimum;
519 mStyleOption.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderTickmarks;
520
521 mStyleOption.activeSubControls = mHoverSubControl;
522 // draw groove
523 style()->drawComplexControl( QStyle::CC_Slider, &mStyleOption, &painter );
524
525 QColor color = palette().color( QPalette::Highlight );
526 color.setAlpha( 160 );
527 painter.setBrush( QBrush( color ) );
528 painter.setPen( Qt::NoPen );
529 painter.drawRect( selectedRangeRect() );
530
531 // draw first handle
532 mStyleOption.subControls = QStyle::SC_SliderHandle;
533 mStyleOption.activeSubControls = mHoverControl == Lower || mActiveControl == Lower ? QStyle::SC_SliderHandle : QStyle::SC_None;
534 mStyleOption.sliderPosition = unFlippedSliderPosition( mLowerValue );
535 if ( mActiveControl == Lower )
536 mStyleOption.state |= QStyle::State_Sunken;
537 else
538 mStyleOption.state &= ~QStyle::State_Sunken;
539 style()->drawComplexControl( QStyle::CC_Slider, &mStyleOption, &painter );
540
541 // draw second handle
542 mStyleOption.activeSubControls = mHoverControl == Upper || mActiveControl == Lower ? QStyle::SC_SliderHandle : QStyle::SC_None;
543 mStyleOption.sliderPosition = unFlippedSliderPosition( mUpperValue );
544 if ( mActiveControl == Upper )
545 mStyleOption.state |= QStyle::State_Sunken;
546 else
547 mStyleOption.state &= ~QStyle::State_Sunken;
548 style()->drawComplexControl( QStyle::CC_Slider, &mStyleOption, &painter );
549
550 if ( hasFocus() && mFocusControl != None )
551 {
552 //draw focus rect
553 QStyleOptionFocusRect option;
554 option.initFrom( this );
555 option.state = QStyle::State_KeyboardFocusChange;
556 if ( mFocusControl == Lower )
557 {
558 mStyleOption.sliderPosition = unFlippedSliderPosition( mLowerValue );
559 option.rect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
560 }
561 else if ( mFocusControl == Upper )
562 {
563 mStyleOption.sliderPosition = unFlippedSliderPosition( mUpperValue );
564 option.rect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
565 }
566 else if ( mFocusControl == Range )
567 {
568 option.rect = selectedRangeRect();
569 if ( mStyleOption.orientation == Qt::Horizontal )
570 option.rect = option.rect.adjusted( 0, -1, 0, 1 );
571 else
572 option.rect = option.rect.adjusted( -1, 0, 1, 0 );
573 }
574 style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &painter );
575 }
576}
577
578void QgsRangeSlider::mousePressEvent( QMouseEvent *event )
579{
580 if ( mStyleOption.maximum == mStyleOption.minimum || ( event->buttons() ^ event->button() ) )
581 {
582 event->ignore();
583 return;
584 }
585
586 event->accept();
587
588 mStyleOption.sliderPosition = unFlippedSliderPosition( mLowerValue );
589 const bool overLowerControl = style()->hitTestComplexControl( QStyle::CC_Slider, &mStyleOption, event->pos(), this ) == QStyle::SC_SliderHandle;
590 const QRect lowerSliderRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
591 mStyleOption.sliderPosition = unFlippedSliderPosition( mUpperValue );
592 const bool overUpperControl = style()->hitTestComplexControl( QStyle::CC_Slider, &mStyleOption, event->pos(), this ) == QStyle::SC_SliderHandle;
593 const QRect upperSliderRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
594
595 const bool overSelectedRange = selectedRangeRect().contains( event->pos() );
596
597 mLowerClickOffset = pick( event->pos() - lowerSliderRect.topLeft() );
598 mUpperClickOffset = pick( event->pos() - upperSliderRect.topLeft() );
599
600 mPreDragLowerValue = mLowerValue;
601 mPreDragUpperValue = mUpperValue;
602 mRangeDragOffset = 0;
603
604 if ( ( overLowerControl || overUpperControl ) && event->modifiers() & Qt::ShiftModifier )
605 {
606 mActiveControl = Range; // shift + drag over handle moves the whole range
607 mRangeDragOffset = overUpperControl ? mUpperClickOffset : mLowerClickOffset;
608 mFocusControl = overUpperControl ? Upper : Lower;
609 }
610 else if ( overLowerControl && overUpperControl )
611 mActiveControl = Both;
612 else if ( overLowerControl )
613 {
614 mActiveControl = Lower;
615 mFocusControl = Lower;
616 }
617 else if ( overUpperControl )
618 {
619 mActiveControl = Upper;
620 mFocusControl = Upper;
621 }
622 else if ( overSelectedRange )
623 {
624 mActiveControl = Range;
625 mFocusControl = Range;
626 }
627 else
628 mActiveControl = None;
629
630 if ( mActiveControl != None )
631 {
632 mStartDragPos = pixelPosToRangeValue( pick( event->pos() ) - mRangeDragOffset );
633 }
634}
635
636void QgsRangeSlider::mouseMoveEvent( QMouseEvent *event )
637{
638 if ( mActiveControl == None )
639 {
640 event->ignore();
641 return;
642 }
643
644 event->accept();
645
646 int newPosition = pixelPosToRangeValue( pick( event->pos() ) );
647
648 bool changed = false;
649 Control destControl = mActiveControl;
650 if ( destControl == Both )
651 {
652 // if click was over both handles, then the direction of the drag changes which control is affected
653 if ( newPosition < mStartDragPos )
654 {
655 destControl = Lower;
656 mFocusControl = Lower;
657 if ( mUpperValue != mPreDragUpperValue )
658 {
659 changed = true;
660 mUpperValue = mPreDragUpperValue;
661 if ( mFixedRangeSize >= 0 )
662 {
663 // don't permit fixed width drags if it pushes the other value out of range
664 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
665 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
666 }
667 }
668 }
669 else if ( newPosition > mStartDragPos )
670 {
671 destControl = Upper;
672 mFocusControl = Upper;
673 if ( mLowerValue != mPreDragLowerValue )
674 {
675 changed = true;
676 mLowerValue = mPreDragLowerValue;
677 if ( mFixedRangeSize >= 0 )
678 {
679 // don't permit fixed width drags if it pushes the other value out of range
680 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
681 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
682 }
683 }
684 }
685 else
686 {
687 destControl = None;
688 if ( mUpperValue != mPreDragUpperValue )
689 {
690 changed = true;
691 mUpperValue = mPreDragUpperValue;
692 if ( mFixedRangeSize >= 0 )
693 {
694 // don't permit fixed width drags if it pushes the other value out of range
695 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
696 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
697 }
698 }
699 if ( mLowerValue != mPreDragLowerValue )
700 {
701 changed = true;
702 mLowerValue = mPreDragLowerValue;
703 if ( mFixedRangeSize >= 0 )
704 {
705 // don't permit fixed width drags if it pushes the other value out of range
706 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
707 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
708 }
709 }
710 }
711 }
712
713 switch ( destControl )
714 {
715 case None:
716 case Both:
717 break;
718
719 case Lower:
720 {
721 // adjust value to account for lower handle click offset
722 newPosition = std::min( mUpperValue, pixelPosToRangeValue( pick( event->pos() ) - mLowerClickOffset ) );
723 if ( mLowerValue != newPosition )
724 {
725 mLowerValue = newPosition;
726 if ( mFixedRangeSize >= 0 )
727 {
728 // don't permit fixed width drags if it pushes the other value out of range
729 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
730 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
731 }
732
733 changed = true;
734 }
735 break;
736 }
737
738 case Upper:
739 {
740 // adjust value to account for upper handle click offset
741 newPosition = std::max( mLowerValue, pixelPosToRangeValue( pick( event->pos() ) - mUpperClickOffset ) );
742 if ( mUpperValue != newPosition )
743 {
744 mUpperValue = newPosition;
745 if ( mFixedRangeSize >= 0 )
746 {
747 // don't permit fixed width drags if it pushes the other value out of range
748 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
749 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
750 }
751
752 changed = true;
753 }
754 break;
755 }
756
757 case Range:
758 {
759 newPosition = pixelPosToRangeValue( pick( event->pos() ) - mRangeDragOffset );
760 int delta = newPosition - mStartDragPos;
761
762 if ( delta > 0 )
763 {
764 // move range up
765 const int maxDelta = mStyleOption.maximum - mPreDragUpperValue;
766 delta = std::min( maxDelta, delta );
767 mLowerValue = mPreDragLowerValue + delta;
768 mUpperValue = mPreDragUpperValue + delta;
769 changed = true;
770 }
771 else if ( delta < 0 )
772 {
773 // move range down
774 delta = -delta;
775 const int maxDelta = mPreDragLowerValue - mStyleOption.minimum;
776 delta = std::min( maxDelta, delta );
777 mLowerValue = mPreDragLowerValue - delta;
778 mUpperValue = mPreDragUpperValue - delta;
779 changed = true;
780 }
781
782 break;
783 }
784 }
785
786 if ( changed )
787 {
788 update();
789 emit rangeChanged( mLowerValue, mUpperValue );
790 }
791}
792
793void QgsRangeSlider::mouseReleaseEvent( QMouseEvent *event )
794{
795 if ( mActiveControl == None || event->buttons() )
796 {
797 event->ignore();
798 return;
799 }
800
801 event->accept();
802 mActiveControl = None;
803 update();
804}
805
806void QgsRangeSlider::keyPressEvent( QKeyEvent *event )
807{
808 Control destControl = mFocusControl;
809 if ( ( destControl == Lower || destControl == Upper ) && mLowerValue == mUpperValue )
810 destControl = Both; //ambiguous destination, because both sliders are on top of each other
811
812 switch ( event->key() )
813 {
814 case Qt::Key_Left:
815 {
816 switch ( mStyleOption.orientation )
817 {
818 case Qt::Horizontal:
819 if ( destControl == Both )
820 mFocusControl = mFlipped ? Upper : Lower;
821
822 applyStep( mFlipped ? mSingleStep : -mSingleStep );
823 break;
824
825 case Qt::Vertical:
826 if ( mFlipped )
827 {
828 switch ( mFocusControl )
829 {
830 case Lower:
831 mFocusControl = Range;
832 break;
833 case Range:
834 mFocusControl = Upper;
835 break;
836 case Upper:
837 case None:
838 case Both:
839 mFocusControl = Lower;
840 break;
841 }
842 }
843 else
844 {
845 switch ( mFocusControl )
846 {
847 case Lower:
848 case None:
849 case Both:
850 mFocusControl = Upper;
851 break;
852 case Range:
853 mFocusControl = Lower;
854 break;
855 case Upper:
856 mFocusControl = Range;
857 break;
858 }
859 }
860 update();
861 break;
862 }
863 break;
864 }
865
866 case Qt::Key_Right:
867 {
868 switch ( mStyleOption.orientation )
869 {
870 case Qt::Horizontal:
871 if ( destControl == Both )
872 mFocusControl = mFlipped ? Lower : Upper;
873 applyStep( mFlipped ? -mSingleStep : mSingleStep );
874 break;
875
876 case Qt::Vertical:
877 if ( mFlipped )
878 {
879 switch ( mFocusControl )
880 {
881 case Lower:
882 case None:
883 case Both:
884 mFocusControl = Upper;
885 break;
886 case Range:
887 mFocusControl = Lower;
888 break;
889 case Upper:
890 mFocusControl = Range;
891 break;
892 }
893 }
894 else
895 {
896 switch ( mFocusControl )
897 {
898 case Lower:
899 mFocusControl = Range;
900 break;
901 case Range:
902 mFocusControl = Upper;
903 break;
904 case Upper:
905 case None:
906 case Both:
907 mFocusControl = Lower;
908 break;
909 }
910 }
911 update();
912 break;
913 }
914 break;
915 }
916
917 case Qt::Key_Up:
918 {
919 switch ( mStyleOption.orientation )
920 {
921 case Qt::Horizontal:
922 if ( mFlipped )
923 {
924 switch ( mFocusControl )
925 {
926 case Lower:
927 mFocusControl = Range;
928 break;
929 case Range:
930 mFocusControl = Upper;
931 break;
932 case Upper:
933 case None:
934 case Both:
935 mFocusControl = Lower;
936 break;
937 }
938 }
939 else
940 {
941 switch ( mFocusControl )
942 {
943 case Lower:
944 mFocusControl = Upper;
945 break;
946 case Range:
947 case None:
948 case Both:
949 mFocusControl = Lower;
950 break;
951 case Upper:
952 mFocusControl = Range;
953 break;
954 }
955 }
956 update();
957 break;
958
959 case Qt::Vertical:
960 if ( destControl == Both )
961 mFocusControl = mFlipped ? Upper : Lower;
962
963 applyStep( mFlipped ? mSingleStep : -mSingleStep );
964 break;
965 }
966 break;
967 }
968
969 case Qt::Key_Down:
970 {
971 switch ( mStyleOption.orientation )
972 {
973 case Qt::Horizontal:
974 if ( mFlipped )
975 {
976 switch ( mFocusControl )
977 {
978 case Lower:
979 mFocusControl = Upper;
980 break;
981 case Range:
982 case None:
983 case Both:
984 mFocusControl = Lower;
985 break;
986 case Upper:
987 mFocusControl = Range;
988 break;
989 }
990 }
991 else
992 {
993 switch ( mFocusControl )
994 {
995 case Lower:
996 mFocusControl = Range;
997 break;
998 case Range:
999 mFocusControl = Upper;
1000 break;
1001 case Upper:
1002 case None:
1003 case Both:
1004 mFocusControl = Lower;
1005 break;
1006 }
1007 }
1008 update();
1009 break;
1010
1011 case Qt::Vertical:
1012 if ( destControl == Both )
1013 mFocusControl = mFlipped ? Lower : Upper;
1014
1015 applyStep( mFlipped ? -mSingleStep : mSingleStep );
1016 break;
1017 }
1018 break;
1019 }
1020
1021 case Qt::Key_PageUp:
1022 {
1023 switch ( mStyleOption.orientation )
1024 {
1025 case Qt::Horizontal:
1026 if ( destControl == Both )
1027 mFocusControl = mFlipped ? Lower : Upper;
1028
1029 applyStep( mFlipped ? -mPageStep : mPageStep );
1030 break;
1031
1032 case Qt::Vertical:
1033 if ( destControl == Both )
1034 mFocusControl = mFlipped ? Upper : Lower;
1035
1036 applyStep( mFlipped ? mPageStep : -mPageStep );
1037 break;
1038 }
1039 break;
1040 }
1041
1042 case Qt::Key_PageDown:
1043 {
1044 switch ( mStyleOption.orientation )
1045 {
1046 case Qt::Horizontal:
1047 if ( destControl == Both )
1048 mFocusControl = mFlipped ? Upper : Lower;
1049
1050 applyStep( mFlipped ? mPageStep : -mPageStep );
1051 break;
1052
1053 case Qt::Vertical:
1054 if ( destControl == Both )
1055 mFocusControl = mFlipped ? Lower : Upper;
1056
1057 applyStep( mFlipped ? -mPageStep : mPageStep );
1058 break;
1059 }
1060 break;
1061 }
1062
1063 case Qt::Key_Home:
1064 switch ( destControl )
1065 {
1066 case Lower:
1067 applyStep( mFlipped ? mUpperValue - mLowerValue : mStyleOption.minimum - mLowerValue );
1068 break;
1069
1070 case Upper:
1071 applyStep( mFlipped ? mStyleOption.maximum - mUpperValue : mLowerValue - mUpperValue );
1072 break;
1073
1074 case Range:
1075 applyStep( mFlipped ? mStyleOption.maximum - mUpperValue : mStyleOption.minimum - mLowerValue );
1076 break;
1077
1078 case Both:
1079 if ( destControl == Both )
1080 mFocusControl = mFlipped ? Upper : Lower;
1081
1082 applyStep( mFlipped ? mStyleOption.maximum - mUpperValue : mStyleOption.minimum - mLowerValue );
1083 break;
1084
1085 case None:
1086 break;
1087 }
1088
1089 break;
1090
1091 case Qt::Key_End:
1092 switch ( destControl )
1093 {
1094 case Lower:
1095 applyStep( mFlipped ? mStyleOption.minimum - mLowerValue : mUpperValue - mLowerValue );
1096 break;
1097
1098 case Upper:
1099 applyStep( mFlipped ? mLowerValue - mUpperValue : mStyleOption.maximum - mUpperValue );
1100 break;
1101
1102 case Range:
1103 applyStep( mFlipped ? mStyleOption.minimum - mLowerValue : mStyleOption.maximum - mUpperValue );
1104 break;
1105
1106 case Both:
1107 if ( destControl == Both )
1108 mFocusControl = mFlipped ? Lower : Upper;
1109
1110 applyStep( mFlipped ? mStyleOption.minimum - mLowerValue : mStyleOption.maximum - mUpperValue );
1111 break;
1112
1113 case None:
1114 break;
1115 }
1116 break;
1117
1118 default:
1119 event->ignore();
1120 break;
1121 }
1122}
1123
1125{
1126 ensurePolished();
1127
1128 // these hardcoded magic values look like a hack, but they are taken straight from the Qt QSlider widget code!
1129 static constexpr int SLIDER_LENGTH = 84;
1130 static constexpr int TICK_SPACE = 5;
1131
1132 int thick = style()->pixelMetric( QStyle::PM_SliderThickness, &mStyleOption, this );
1133 if ( mStyleOption.tickPosition & QSlider::TicksAbove )
1134 thick += TICK_SPACE;
1135 if ( mStyleOption.tickPosition & QSlider::TicksBelow )
1136 thick += TICK_SPACE;
1137 int w = thick, h = SLIDER_LENGTH;
1138 if ( mStyleOption.orientation == Qt::Horizontal )
1139 {
1140 std::swap( w, h );
1141 }
1142 return style()->sizeFromContents( QStyle::CT_Slider, &mStyleOption, QSize( w, h ), this );
1143}
1144
1146{
1147 QSize s = sizeHint();
1148 const int length = style()->pixelMetric( QStyle::PM_SliderLength, &mStyleOption, this );
1149 if ( mStyleOption.orientation == Qt::Horizontal )
1150 s.setWidth( length );
1151 else
1152 s.setHeight( length );
1153 return s;
1154}
A slider control with two interactive endpoints, for interactive selection of a range of values.
void setUpperValue(int value)
Sets the upper value for the range currently selected in the widget.
void setRangeLimits(int minimum, int maximum)
Sets the minimum and maximum range limits for values allowed in the widget.
QgsRangeSlider(QWidget *parent=nullptr)
Constructor for QgsRangeSlider, with the specified parent widget.
void setOrientation(Qt::Orientation orientation)
Sets the orientation of the slider.
void setTickInterval(int interval)
Sets the interval for tick marks shown in the widget.
int upperValue() const
Returns the upper value for the range selected in the widget.
int tickInterval() const
Returns the interval for tick marks shown in the widget.
void paintEvent(QPaintEvent *event) override
void fixedRangeSizeChanged(int size)
Emitted when the widget's fixed range size is changed.
void rangeLimitsChanged(int minimum, int maximum)
Emitted when the limits of values allowed in the widget is changed.
QSize sizeHint() const override
void keyPressEvent(QKeyEvent *event) override
void mouseMoveEvent(QMouseEvent *event) override
void rangeChanged(int minimum, int maximum)
Emitted when the range selected in the widget is changed.
int maximum() const
Returns the maximum value allowed by the widget.
void mousePressEvent(QMouseEvent *event) override
bool event(QEvent *event) override
void setSingleStep(int step)
Sets the single step value for the widget.
void setMinimum(int minimum)
Sets the minimum value allowed in the widget.
void setPageStep(int step)
Sets the page step value for the widget.
Qt::Orientation orientation() const
Returns the orientation of the slider.
void setMaximum(int maximum)
Sets the maximum value allowed in the widget.
int pageStep() const
Returns the page step value for the widget.
void setFlippedDirection(bool flipped)
Sets whether the slider has its values flipped.
int fixedRangeSize() const
Returns the slider's fixed range size, or -1 if not set.
int minimum() const
Returns the minimum value allowed by the widget.
QSize minimumSizeHint() const override
void setRange(int lower, int upper)
Sets the current range selected in the widget.
void setLowerValue(int value)
Sets the lower value for the range currently selected in the widget.
int lowerValue() const
Returns the lower value for the range selected in the widget.
void setTickPosition(QSlider::TickPosition position)
Sets the position of the tick marks shown in the widget.
void setFixedRangeSize(int size)
Sets the slider's fixed range size.
void mouseReleaseEvent(QMouseEvent *event) override
bool flippedDirection() const
Returns true if the slider has its values flipped.
QSlider::TickPosition tickPosition() const
Returns the position of the tick marks shown in the widget.
int singleStep() const
Returns the single step value for the widget.