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