QGIS API Documentation 3.41.0-Master (45a0abf3bec)
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,
226 sliderMax - sliderMin );
227 if ( mFlipped )
228 value = mStyleOption.maximum + mStyleOption.minimum - value;
229 return value;
230}
231
232bool QgsRangeSlider::updateHoverControl( const QPoint &pos )
233{
234 const QRect lastHoverRect = mHoverRect;
235 const bool doesHover = testAttribute( Qt::WA_Hover );
236 if ( doesHover && newHoverControl( pos ) )
237 {
238 update( lastHoverRect );
239 update( mHoverRect );
240 return true;
241 }
242 return !doesHover;
243}
244
245bool QgsRangeSlider::newHoverControl( const QPoint &pos )
246{
247 const Control lastHoverControl = mHoverControl;
248 const QStyle::SubControl lastHoverSubControl = mHoverSubControl;
249
250 mStyleOption.subControls = QStyle::SC_All;
251
252 mStyleOption.sliderPosition = unFlippedSliderPosition( mLowerValue );
253 const QRect lowerHandleRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
254 mStyleOption.sliderPosition = unFlippedSliderPosition( mUpperValue );
255 const QRect upperHandleRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
256
257 const QRect grooveRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderGroove, this );
258 const QRect tickmarksRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderTickmarks, this );
259 if ( lowerHandleRect.contains( pos ) )
260 {
261 mHoverRect = lowerHandleRect;
262 mHoverControl = Lower;
263 mHoverSubControl = QStyle::SC_SliderHandle;
264 setCursor( Qt::OpenHandCursor );
265 }
266 else if ( upperHandleRect.contains( pos ) )
267 {
268 mHoverRect = upperHandleRect;
269 mHoverControl = Upper;
270 mHoverSubControl = QStyle::SC_SliderHandle;
271 setCursor( Qt::OpenHandCursor );
272 }
273 else if ( grooveRect.contains( pos ) )
274 {
275 mHoverRect = grooveRect;
276 mHoverControl = None;
277 mHoverSubControl = QStyle::SC_SliderGroove;
278
279 if ( selectedRangeRect().contains( pos ) )
280 setCursor( Qt::OpenHandCursor );
281 else
282 unsetCursor();
283 }
284 else if ( tickmarksRect.contains( pos ) )
285 {
286 mHoverRect = tickmarksRect;
287 mHoverControl = None;
288 mHoverSubControl = QStyle::SC_SliderTickmarks;
289 unsetCursor();
290 }
291 else
292 {
293 mHoverRect = QRect();
294 mHoverControl = None;
295 mHoverSubControl = QStyle::SC_None;
296 unsetCursor();
297 }
298 return mHoverSubControl != lastHoverSubControl || mHoverControl != lastHoverControl;
299}
300
301QRect QgsRangeSlider::selectedRangeRect()
302{
303 QRect selectionRect;
304
305 mStyleOption.activeSubControls = mHoverControl == Lower || mActiveControl == Lower ? QStyle::SC_SliderHandle : QStyle::SC_None;
306 mStyleOption.sliderPosition = unFlippedSliderPosition( mLowerValue );
307 const QRect lowerHandleRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, nullptr );
308
309 mStyleOption.activeSubControls = mHoverControl == Upper || mActiveControl == Lower ? QStyle::SC_SliderHandle : QStyle::SC_None;
310 mStyleOption.sliderPosition = unFlippedSliderPosition( mUpperValue );
311 const QRect upperHandleRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, nullptr );
312
313 const QRect grooveRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderGroove, nullptr );
314
315 switch ( mStyleOption.orientation )
316 {
317 case Qt::Horizontal:
318 selectionRect = mFlipped ? QRect( upperHandleRect.right(),
319 grooveRect.y(),
320 lowerHandleRect.left() - upperHandleRect.right(),
321 grooveRect.height()
322 )
323 : QRect( lowerHandleRect.right(),
324 grooveRect.y(),
325 upperHandleRect.left() - lowerHandleRect.right(),
326 grooveRect.height()
327 );
328 break;
329
330 case Qt::Vertical:
331 selectionRect = mFlipped ? QRect( grooveRect.x(),
332 lowerHandleRect.top(),
333 grooveRect.width(),
334 upperHandleRect.bottom() - lowerHandleRect.top()
335 )
336 : QRect( grooveRect.x(),
337 upperHandleRect.top(),
338 grooveRect.width(),
339 lowerHandleRect.bottom() - upperHandleRect.top()
340 );
341 break;
342 }
343
344 return selectionRect.adjusted( -1, 1, 1, -1 );
345}
346
348{
349 return mFixedRangeSize;
350}
351
353{
354 if ( size == mFixedRangeSize )
355 return;
356
357 mFixedRangeSize = size;
358
359 if ( mFixedRangeSize >= 0 )
360 setUpperValue( mLowerValue + mFixedRangeSize );
361
362 emit fixedRangeSizeChanged( mFixedRangeSize );
363}
364
365void QgsRangeSlider::applyStep( int step )
366{
367 switch ( mFocusControl )
368 {
369 case Lower:
370 {
371 const int newLowerValue = std::min( mUpperValue, std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, mLowerValue + step ) ) );
372 if ( newLowerValue != mLowerValue )
373 {
374 mLowerValue = newLowerValue;
375 if ( mFixedRangeSize >= 0 )
376 {
377 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
378 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
379 }
380 emit rangeChanged( mLowerValue, mUpperValue );
381 update();
382 }
383 break;
384 }
385
386 case Upper:
387 {
388 const int newUpperValue = std::max( mLowerValue, std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, mUpperValue + step ) ) );
389 if ( newUpperValue != mUpperValue )
390 {
391 mUpperValue = newUpperValue;
392 if ( mFixedRangeSize >= 0 )
393 {
394 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
395 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
396 }
397 emit rangeChanged( mLowerValue, mUpperValue );
398 update();
399 }
400 break;
401 }
402
403 case Range:
404 {
405 if ( step < 0 )
406 {
407 const int previousWidth = mUpperValue - mLowerValue;
408 const int newLowerValue = std::min( mUpperValue, std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, mLowerValue + step ) ) );
409 if ( newLowerValue != mLowerValue )
410 {
411 mLowerValue = newLowerValue;
412 if ( mFixedRangeSize >= 0 )
413 {
414 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
415 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
416 }
417 else
418 {
419 mUpperValue = std::min( mStyleOption.maximum, mLowerValue + previousWidth );
420 }
421 emit rangeChanged( mLowerValue, mUpperValue );
422 update();
423 }
424 }
425 else
426 {
427 const int previousWidth = mUpperValue - mLowerValue;
428 const int newUpperValue = std::max( mLowerValue, std::min( mStyleOption.maximum, std::max( mStyleOption.minimum, mUpperValue + step ) ) );
429 if ( newUpperValue != mUpperValue )
430 {
431 mUpperValue = newUpperValue;
432 if ( mFixedRangeSize >= 0 )
433 {
434 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
435 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
436 }
437 else
438 {
439 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - previousWidth );
440 }
441 emit rangeChanged( mLowerValue, mUpperValue );
442 update();
443 }
444 }
445 break;
446 }
447
448 case None:
449 case Both:
450 break;
451 }
452}
453
454int QgsRangeSlider::unFlippedSliderPosition( int value ) const
455{
456 return mFlipped ? mStyleOption.maximum + mStyleOption.minimum - value : value;
457}
458
460{
461 return mPageStep;
462}
463
465{
466 mPageStep = step;
467}
468
470{
471 return mSingleStep;
472}
473
475{
476 mSingleStep = step;
477}
478
479void QgsRangeSlider::setTickPosition( QSlider::TickPosition position )
480{
481 mStyleOption.tickPosition = position;
482 update();
483}
484
485QSlider::TickPosition QgsRangeSlider::tickPosition() const
486{
487 return mStyleOption.tickPosition;
488}
489
491{
492 mStyleOption.tickInterval = interval;
493 update();
494}
495
497{
498 return mStyleOption.tickInterval;
499}
500
501void QgsRangeSlider::setOrientation( Qt::Orientation orientation )
502{
503 mStyleOption.orientation = orientation;
504 if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) )
505 {
506 setSizePolicy( sizePolicy().transposed() );
507 setAttribute( Qt::WA_WState_OwnSizePolicy, false );
508 }
509 update();
510 updateGeometry();
511}
512
513Qt::Orientation QgsRangeSlider::orientation() const
514{
515 return mStyleOption.orientation;
516}
517
519{
520 return mFlipped;
521}
522
524{
525 mFlipped = flipped;
526 update();
527}
528
529void QgsRangeSlider::paintEvent( QPaintEvent * )
530{
531 QPainter painter( this );
532
533 mStyleOption.initFrom( this );
534 mStyleOption.rect = rect();
535 mStyleOption.sliderPosition = mStyleOption.minimum;
536 mStyleOption.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderTickmarks;
537
538 mStyleOption.activeSubControls = mHoverSubControl;
539 // draw groove
540 style()->drawComplexControl( QStyle::CC_Slider, &mStyleOption, &painter );
541
542 QColor color = palette().color( QPalette::Highlight );
543 color.setAlpha( 160 );
544 painter.setBrush( QBrush( color ) );
545 painter.setPen( Qt::NoPen );
546 painter.drawRect( selectedRangeRect() );
547
548 // draw first handle
549 mStyleOption.subControls = QStyle::SC_SliderHandle;
550 mStyleOption.activeSubControls = mHoverControl == Lower || mActiveControl == Lower ? QStyle::SC_SliderHandle : QStyle::SC_None;
551 mStyleOption.sliderPosition = unFlippedSliderPosition( mLowerValue );
552 if ( mActiveControl == Lower )
553 mStyleOption.state |= QStyle::State_Sunken;
554 else
555 mStyleOption.state &= ~QStyle::State_Sunken;
556 style()->drawComplexControl( QStyle::CC_Slider, &mStyleOption, &painter );
557
558 // draw second handle
559 mStyleOption.activeSubControls = mHoverControl == Upper || mActiveControl == Lower ? QStyle::SC_SliderHandle : QStyle::SC_None;
560 mStyleOption.sliderPosition = unFlippedSliderPosition( mUpperValue );
561 if ( mActiveControl == Upper )
562 mStyleOption.state |= QStyle::State_Sunken;
563 else
564 mStyleOption.state &= ~QStyle::State_Sunken;
565 style()->drawComplexControl( QStyle::CC_Slider, &mStyleOption, &painter );
566
567 if ( hasFocus() && mFocusControl != None )
568 {
569 //draw focus rect
570 QStyleOptionFocusRect option;
571 option.initFrom( this );
572 option.state = QStyle::State_KeyboardFocusChange;
573 if ( mFocusControl == Lower )
574 {
575 mStyleOption.sliderPosition = unFlippedSliderPosition( mLowerValue );
576 option.rect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
577 }
578 else if ( mFocusControl == Upper )
579 {
580 mStyleOption.sliderPosition = unFlippedSliderPosition( mUpperValue );
581 option.rect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
582 }
583 else if ( mFocusControl == Range )
584 {
585 option.rect = selectedRangeRect();
586 if ( mStyleOption.orientation == Qt::Horizontal )
587 option.rect = option.rect.adjusted( 0, -1, 0, 1 );
588 else
589 option.rect = option.rect.adjusted( -1, 0, 1, 0 );
590 }
591 style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &painter );
592 }
593}
594
595void QgsRangeSlider::mousePressEvent( QMouseEvent *event )
596{
597 if ( mStyleOption.maximum == mStyleOption.minimum || ( event->buttons() ^ event->button() ) )
598 {
599 event->ignore();
600 return;
601 }
602
603 event->accept();
604
605 mStyleOption.sliderPosition = unFlippedSliderPosition( mLowerValue );
606 const bool overLowerControl = style()->hitTestComplexControl( QStyle::CC_Slider, &mStyleOption, event->pos(), this ) == QStyle::SC_SliderHandle;
607 const QRect lowerSliderRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
608 mStyleOption.sliderPosition = unFlippedSliderPosition( mUpperValue );
609 const bool overUpperControl = style()->hitTestComplexControl( QStyle::CC_Slider, &mStyleOption, event->pos(), this ) == QStyle::SC_SliderHandle;
610 const QRect upperSliderRect = style()->subControlRect( QStyle::CC_Slider, &mStyleOption, QStyle::SC_SliderHandle, this );
611
612 const bool overSelectedRange = selectedRangeRect().contains( event->pos() );
613
614 mLowerClickOffset = pick( event->pos() - lowerSliderRect.topLeft() );
615 mUpperClickOffset = pick( event->pos() - upperSliderRect.topLeft() );
616
617 mPreDragLowerValue = mLowerValue;
618 mPreDragUpperValue = mUpperValue;
619 mRangeDragOffset = 0;
620
621 if ( ( overLowerControl || overUpperControl ) && event->modifiers() & Qt::ShiftModifier )
622 {
623 mActiveControl = Range; // shift + drag over handle moves the whole range
624 mRangeDragOffset = overUpperControl ? mUpperClickOffset : mLowerClickOffset;
625 mFocusControl = overUpperControl ? Upper : Lower;
626 }
627 else if ( overLowerControl && overUpperControl )
628 mActiveControl = Both;
629 else if ( overLowerControl )
630 {
631 mActiveControl = Lower;
632 mFocusControl = Lower;
633 }
634 else if ( overUpperControl )
635 {
636 mActiveControl = Upper;
637 mFocusControl = Upper;
638 }
639 else if ( overSelectedRange )
640 {
641 mActiveControl = Range;
642 mFocusControl = Range;
643 }
644 else
645 mActiveControl = None;
646
647 if ( mActiveControl != None )
648 {
649 mStartDragPos = pixelPosToRangeValue( pick( event->pos() ) - mRangeDragOffset );
650 }
651}
652
653void QgsRangeSlider::mouseMoveEvent( QMouseEvent *event )
654{
655 if ( mActiveControl == None )
656 {
657 event->ignore();
658 return;
659 }
660
661 event->accept();
662
663 int newPosition = pixelPosToRangeValue( pick( event->pos() ) );
664
665 bool changed = false;
666 Control destControl = mActiveControl;
667 if ( destControl == Both )
668 {
669 // if click was over both handles, then the direction of the drag changes which control is affected
670 if ( newPosition < mStartDragPos )
671 {
672 destControl = Lower;
673 mFocusControl = Lower;
674 if ( mUpperValue != mPreDragUpperValue )
675 {
676 changed = true;
677 mUpperValue = mPreDragUpperValue;
678 if ( mFixedRangeSize >= 0 )
679 {
680 // don't permit fixed width drags if it pushes the other value out of range
681 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
682 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
683 }
684 }
685 }
686 else if ( newPosition > mStartDragPos )
687 {
688 destControl = Upper;
689 mFocusControl = Upper;
690 if ( mLowerValue != mPreDragLowerValue )
691 {
692 changed = true;
693 mLowerValue = mPreDragLowerValue;
694 if ( mFixedRangeSize >= 0 )
695 {
696 // don't permit fixed width drags if it pushes the other value out of range
697 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
698 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
699 }
700 }
701 }
702 else
703 {
704 destControl = None;
705 if ( mUpperValue != mPreDragUpperValue )
706 {
707 changed = true;
708 mUpperValue = mPreDragUpperValue;
709 if ( mFixedRangeSize >= 0 )
710 {
711 // don't permit fixed width drags if it pushes the other value out of range
712 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
713 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
714 }
715 }
716 if ( mLowerValue != mPreDragLowerValue )
717 {
718 changed = true;
719 mLowerValue = mPreDragLowerValue;
720 if ( mFixedRangeSize >= 0 )
721 {
722 // don't permit fixed width drags if it pushes the other value out of range
723 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
724 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
725 }
726 }
727 }
728 }
729
730 switch ( destControl )
731 {
732 case None:
733 case Both:
734 break;
735
736 case Lower:
737 {
738 // adjust value to account for lower handle click offset
739 newPosition = std::min( mUpperValue, pixelPosToRangeValue( pick( event->pos() ) - mLowerClickOffset ) );
740 if ( mLowerValue != newPosition )
741 {
742 mLowerValue = newPosition;
743 if ( mFixedRangeSize >= 0 )
744 {
745 // don't permit fixed width drags if it pushes the other value out of range
746 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
747 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
748 }
749
750 changed = true;
751 }
752 break;
753 }
754
755 case Upper:
756 {
757 // adjust value to account for upper handle click offset
758 newPosition = std::max( mLowerValue, pixelPosToRangeValue( pick( event->pos() ) - mUpperClickOffset ) );
759 if ( mUpperValue != newPosition )
760 {
761 mUpperValue = newPosition;
762 if ( mFixedRangeSize >= 0 )
763 {
764 // don't permit fixed width drags if it pushes the other value out of range
765 mLowerValue = std::max( mStyleOption.minimum, mUpperValue - mFixedRangeSize );
766 mUpperValue = std::min( mLowerValue + mFixedRangeSize, mStyleOption.maximum );
767 }
768
769 changed = true;
770 }
771 break;
772 }
773
774 case Range:
775 {
776 newPosition = pixelPosToRangeValue( pick( event->pos() ) - mRangeDragOffset ) ;
777 int delta = newPosition - mStartDragPos;
778
779 if ( delta > 0 )
780 {
781 // move range up
782 const int maxDelta = mStyleOption.maximum - mPreDragUpperValue;
783 delta = std::min( maxDelta, delta );
784 mLowerValue = mPreDragLowerValue + delta;
785 mUpperValue = mPreDragUpperValue + delta;
786 changed = true;
787 }
788 else if ( delta < 0 )
789 {
790 // move range down
791 delta = -delta;
792 const int maxDelta = mPreDragLowerValue - mStyleOption.minimum ;
793 delta = std::min( maxDelta, delta );
794 mLowerValue = mPreDragLowerValue - delta;
795 mUpperValue = mPreDragUpperValue - delta;
796 changed = true;
797 }
798
799 break;
800 }
801 }
802
803 if ( changed )
804 {
805 update();
806 emit rangeChanged( mLowerValue, mUpperValue );
807 }
808}
809
810void QgsRangeSlider::mouseReleaseEvent( QMouseEvent *event )
811{
812 if ( mActiveControl == None || event->buttons() )
813 {
814 event->ignore();
815 return;
816 }
817
818 event->accept();
819 mActiveControl = None;
820 update();
821}
822
823void QgsRangeSlider::keyPressEvent( QKeyEvent *event )
824{
825 Control destControl = mFocusControl;
826 if ( ( destControl == Lower || destControl == Upper ) && mLowerValue == mUpperValue )
827 destControl = Both; //ambiguous destination, because both sliders are on top of each other
828
829 switch ( event->key() )
830 {
831 case Qt::Key_Left:
832 {
833 switch ( mStyleOption.orientation )
834 {
835 case Qt::Horizontal:
836 if ( destControl == Both )
837 mFocusControl = mFlipped ? Upper : Lower;
838
839 applyStep( mFlipped ? mSingleStep : -mSingleStep );
840 break;
841
842 case Qt::Vertical:
843 if ( mFlipped )
844 {
845 switch ( mFocusControl )
846 {
847 case Lower:
848 mFocusControl = Range;
849 break;
850 case Range:
851 mFocusControl = Upper;
852 break;
853 case Upper:
854 case None:
855 case Both:
856 mFocusControl = Lower;
857 break;
858 }
859 }
860 else
861 {
862 switch ( mFocusControl )
863 {
864 case Lower:
865 case None:
866 case Both:
867 mFocusControl = Upper;
868 break;
869 case Range:
870 mFocusControl = Lower;
871 break;
872 case Upper:
873 mFocusControl = Range;
874 break;
875 }
876 }
877 update();
878 break;
879 }
880 break;
881 }
882
883 case Qt::Key_Right:
884 {
885 switch ( mStyleOption.orientation )
886 {
887 case Qt::Horizontal:
888 if ( destControl == Both )
889 mFocusControl = mFlipped ? Lower : Upper;
890 applyStep( mFlipped ? -mSingleStep : mSingleStep );
891 break;
892
893 case Qt::Vertical:
894 if ( mFlipped )
895 {
896 switch ( mFocusControl )
897 {
898 case Lower:
899 case None:
900 case Both:
901 mFocusControl = Upper;
902 break;
903 case Range:
904 mFocusControl = Lower;
905 break;
906 case Upper:
907 mFocusControl = Range;
908 break;
909 }
910 }
911 else
912 {
913 switch ( mFocusControl )
914 {
915 case Lower:
916 mFocusControl = Range;
917 break;
918 case Range:
919 mFocusControl = Upper;
920 break;
921 case Upper:
922 case None:
923 case Both:
924 mFocusControl = Lower;
925 break;
926 }
927 }
928 update();
929 break;
930 }
931 break;
932 }
933
934 case Qt::Key_Up:
935 {
936 switch ( mStyleOption.orientation )
937 {
938 case Qt::Horizontal:
939 if ( mFlipped )
940 {
941 switch ( mFocusControl )
942 {
943 case Lower:
944 mFocusControl = Range;
945 break;
946 case Range:
947 mFocusControl = Upper;
948 break;
949 case Upper:
950 case None:
951 case Both:
952 mFocusControl = Lower;
953 break;
954 }
955 }
956 else
957 {
958 switch ( mFocusControl )
959 {
960 case Lower:
961 mFocusControl = Upper;
962 break;
963 case Range:
964 case None:
965 case Both:
966 mFocusControl = Lower;
967 break;
968 case Upper:
969 mFocusControl = Range;
970 break;
971 }
972 }
973 update();
974 break;
975
976 case Qt::Vertical:
977 if ( destControl == Both )
978 mFocusControl = mFlipped ? Upper : Lower;
979
980 applyStep( mFlipped ? mSingleStep : -mSingleStep );
981 break;
982 }
983 break;
984 }
985
986 case Qt::Key_Down:
987 {
988 switch ( mStyleOption.orientation )
989 {
990 case Qt::Horizontal:
991 if ( mFlipped )
992 {
993 switch ( mFocusControl )
994 {
995 case Lower:
996 mFocusControl = Upper;
997 break;
998 case Range:
999 case None:
1000 case Both:
1001 mFocusControl = Lower;
1002 break;
1003 case Upper:
1004 mFocusControl = Range;
1005 break;
1006 }
1007 }
1008 else
1009 {
1010 switch ( mFocusControl )
1011 {
1012 case Lower:
1013 mFocusControl = Range;
1014 break;
1015 case Range:
1016 mFocusControl = Upper;
1017 break;
1018 case Upper:
1019 case None:
1020 case Both:
1021 mFocusControl = Lower;
1022 break;
1023 }
1024 }
1025 update();
1026 break;
1027
1028 case Qt::Vertical:
1029 if ( destControl == Both )
1030 mFocusControl = mFlipped ? Lower : Upper;
1031
1032 applyStep( mFlipped ? -mSingleStep : mSingleStep );
1033 break;
1034 }
1035 break;
1036 }
1037
1038 case Qt::Key_PageUp:
1039 {
1040 switch ( mStyleOption.orientation )
1041 {
1042 case Qt::Horizontal:
1043 if ( destControl == Both )
1044 mFocusControl = mFlipped ? Lower : Upper;
1045
1046 applyStep( mFlipped ? -mPageStep : mPageStep );
1047 break;
1048
1049 case Qt::Vertical:
1050 if ( destControl == Both )
1051 mFocusControl = mFlipped ? Upper : Lower;
1052
1053 applyStep( mFlipped ? mPageStep : -mPageStep );
1054 break;
1055 }
1056 break;
1057 }
1058
1059 case Qt::Key_PageDown:
1060 {
1061 switch ( mStyleOption.orientation )
1062 {
1063 case Qt::Horizontal:
1064 if ( destControl == Both )
1065 mFocusControl = mFlipped ? Upper : Lower;
1066
1067 applyStep( mFlipped ? mPageStep : -mPageStep );
1068 break;
1069
1070 case Qt::Vertical:
1071 if ( destControl == Both )
1072 mFocusControl = mFlipped ? Lower : Upper;
1073
1074 applyStep( mFlipped ? -mPageStep : mPageStep );
1075 break;
1076 }
1077 break;
1078 }
1079
1080 case Qt::Key_Home:
1081 switch ( destControl )
1082 {
1083 case Lower:
1084 applyStep( mFlipped ? mUpperValue - mLowerValue : mStyleOption.minimum - mLowerValue );
1085 break;
1086
1087 case Upper:
1088 applyStep( mFlipped ? mStyleOption.maximum - mUpperValue : mLowerValue - mUpperValue );
1089 break;
1090
1091 case Range:
1092 applyStep( mFlipped ? mStyleOption.maximum - mUpperValue : mStyleOption.minimum - mLowerValue );
1093 break;
1094
1095 case Both:
1096 if ( destControl == Both )
1097 mFocusControl = mFlipped ? Upper : Lower;
1098
1099 applyStep( mFlipped ? mStyleOption.maximum - mUpperValue : mStyleOption.minimum - mLowerValue );
1100 break;
1101
1102 case None:
1103 break;
1104 }
1105
1106 break;
1107
1108 case Qt::Key_End:
1109 switch ( destControl )
1110 {
1111 case Lower:
1112 applyStep( mFlipped ? mStyleOption.minimum - mLowerValue : mUpperValue - mLowerValue );
1113 break;
1114
1115 case Upper:
1116 applyStep( mFlipped ? mLowerValue - mUpperValue : mStyleOption.maximum - mUpperValue );
1117 break;
1118
1119 case Range:
1120 applyStep( mFlipped ? mStyleOption.minimum - mLowerValue : mStyleOption.maximum - mUpperValue );
1121 break;
1122
1123 case Both:
1124 if ( destControl == Both )
1125 mFocusControl = mFlipped ? Lower : Upper;
1126
1127 applyStep( mFlipped ? mStyleOption.minimum - mLowerValue : mStyleOption.maximum - mUpperValue );
1128 break;
1129
1130 case None:
1131 break;
1132 }
1133 break;
1134
1135 default:
1136 event->ignore();
1137 break;
1138 }
1139}
1140
1142{
1143 ensurePolished();
1144
1145 // these hardcoded magic values look like a hack, but they are taken straight from the Qt QSlider widget code!
1146 static constexpr int SLIDER_LENGTH = 84;
1147 static constexpr int TICK_SPACE = 5;
1148
1149 int thick = style()->pixelMetric( QStyle::PM_SliderThickness, &mStyleOption, this );
1150 if ( mStyleOption.tickPosition & QSlider::TicksAbove )
1151 thick += TICK_SPACE;
1152 if ( mStyleOption.tickPosition & QSlider::TicksBelow )
1153 thick += TICK_SPACE;
1154 int w = thick, h = SLIDER_LENGTH;
1155 if ( mStyleOption.orientation == Qt::Horizontal )
1156 {
1157 std::swap( w, h );
1158 }
1159 return style()->sizeFromContents( QStyle::CT_Slider, &mStyleOption, QSize( w, h ), this );
1160}
1161
1163{
1164 QSize s = sizeHint();
1165 const int length = style()->pixelMetric( QStyle::PM_SliderLength, &mStyleOption, this );
1166 if ( mStyleOption.orientation == Qt::Horizontal )
1167 s.setWidth( length );
1168 else
1169 s.setHeight( length );
1170 return s;
1171}
1172
1173
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.