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