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