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