Parcourir la source

ENH: ctkRangeSlider: Drag&drop in the area between the 2 handles moves the min and max positions at the same time

Julien Finet il y a 15 ans
Parent
commit
407b067a05
1 fichiers modifiés avec 182 ajouts et 104 suppressions
  1. 182 104
      Libs/Widgets/ctkRangeSlider.cpp

+ 182 - 104
Libs/Widgets/ctkRangeSlider.cpp

@@ -39,6 +39,7 @@ public:
   // Description:
   // Copied verbatim from QSliderPrivate class (see QSlider.cpp)
   int pixelPosToRangeValue(int pos) const;
+  int pixelPosFromRangeValue(int val) const;
 
   // Description:
   // Draw the bottom and top sliders.
@@ -70,10 +71,17 @@ public:
   // Overrides this ivar.
   int m_SubclassPosition;
 
+  int m_SubclassWidth;
   // Description:
   // Boolean indicates the selected handle
   //   True for the minimum range handle, false for the maximum range handle
-  bool m_SelectedSlider;
+  enum Handle {
+    NoHandle = 0x0,
+    MinimumHandle = 0x1,
+    MaximumHandle = 0x2
+  };
+  Q_DECLARE_FLAGS(Handles, Handle);
+  ctkRangeSliderPrivate::Handles m_SelectedHandles;
 };
 
 // --------------------------------------------------------------------------
@@ -87,6 +95,8 @@ ctkRangeSliderPrivate::ctkRangeSliderPrivate()
   this->m_MaximumSliderSelected = QStyle::SC_None;
   this->m_SubclassClickOffset = 0;
   this->m_SubclassPosition = 0;
+  this->m_SubclassWidth = 0;
+  this->m_SelectedHandles = 0;
 }
 
 // --------------------------------------------------------------------------
@@ -117,10 +127,19 @@ int ctkRangeSliderPrivate::pixelPosToRangeValue( int pos ) const
                                             &option, 
                                             QStyle::SC_SliderHandle, 
                                             p );
-
-  int sliderLength = sr.width();
-  int sliderMin = gr.x();
-  int sliderMax = gr.right() - sliderLength + 1;
+  int sliderMin, sliderMax, sliderLength;
+  if (option.orientation == Qt::Horizontal) 
+    {
+    sliderLength = sr.width();
+    sliderMin = gr.x();
+    sliderMax = gr.right() - sliderLength + 1;
+    }
+  else
+    {
+    sliderLength = sr.height();
+    sliderMin = gr.y();
+    sliderMax = gr.bottom() - sliderLength + 1;
+    }
 
   return QStyle::sliderValueFromPosition( p->minimum(), 
                                           p->maximum(), 
@@ -130,6 +149,42 @@ int ctkRangeSliderPrivate::pixelPosToRangeValue( int pos ) const
 }
 
 //---------------------------------------------------------------------------
+int ctkRangeSliderPrivate::pixelPosFromRangeValue( int val ) const
+{
+  CTK_P(const ctkRangeSlider);
+  QStyleOptionSlider option;
+  p->initStyleOption( &option );
+
+  QRect gr = p->style()->subControlRect( QStyle::CC_Slider, 
+                                            &option, 
+                                            QStyle::SC_SliderGroove, 
+                                            p );
+  QRect sr = p->style()->subControlRect( QStyle::CC_Slider, 
+                                            &option, 
+                                            QStyle::SC_SliderHandle, 
+                                            p );
+  int sliderMin, sliderMax, sliderLength;
+  if (option.orientation == Qt::Horizontal) 
+    {
+    sliderLength = sr.width();
+    sliderMin = gr.x();
+    sliderMax = gr.right() - sliderLength + 1;
+    }
+  else
+    {
+    sliderLength = sr.height();
+    sliderMin = gr.y();
+    sliderMax = gr.bottom() - sliderLength + 1;
+    }
+
+  return QStyle::sliderPositionFromValue( p->minimum(), 
+                                          p->maximum(), 
+                                          val,
+                                          sliderMax - sliderMin, 
+                                          option.upsideDown ) + sliderMin;
+}
+
+//---------------------------------------------------------------------------
 // Draw slider at the bottom end of the range
 void ctkRangeSliderPrivate::drawMinimumSlider( QStylePainter* painter ) const
 {
@@ -140,9 +195,9 @@ void ctkRangeSliderPrivate::drawMinimumSlider( QStylePainter* painter ) const
   option.subControls = QStyle::SC_SliderHandle;
   option.sliderValue = m_MinimumValue;
   option.sliderPosition = m_MinimumPosition;
-  if (m_MinimumSliderSelected == QStyle::SC_SliderHandle)
+  if (this->m_SelectedHandles & MinimumHandle)
     {
-    option.activeSubControls = m_MinimumSliderSelected;
+    option.activeSubControls = QStyle::SC_SliderHandle;
     option.state |= QStyle::State_Sunken;
     }
 
@@ -160,9 +215,9 @@ void ctkRangeSliderPrivate::drawMaximumSlider( QStylePainter* painter ) const
   option.subControls = QStyle::SC_SliderHandle;
   option.sliderValue = m_MaximumValue;
   option.sliderPosition = m_MaximumPosition;
-  if (m_MaximumSliderSelected == QStyle::SC_SliderHandle)
+  if (this->m_SelectedHandles & MaximumHandle)
     {
-    option.activeSubControls = m_MaximumSliderSelected;
+    option.activeSubControls = QStyle::SC_SliderHandle;
     option.state |= QStyle::State_Sunken;
     }
 
@@ -384,7 +439,6 @@ void ctkRangeSlider::paintEvent( QPaintEvent* )
                                       &option, 
                                       QStyle::SC_SliderGroove, 
                                       this);
-
   QRect rangeBox = QRect( 
       QPoint( qMin( lr.center().x(), ur.center().x() ), sr.center().y() - 2), 
       QPoint(qMax( lr.center().x(), ur.center().x() ), sr.center().y() + 1));
@@ -399,11 +453,9 @@ void ctkRangeSlider::paintEvent( QPaintEvent* )
                                                 this );
   groove.adjust(0, 0, -1, 0);
 
-  painter.setPen( QPen( this->palette().color(QPalette::Dark).light(90), 0));
-
   // Create default colors based on the transfer function.
   //
-  QColor highlight = this->palette().color(QPalette::Highlight);
+  QColor highlight = this->palette().color(QPalette::Normal, QPalette::Highlight);
   QLinearGradient gradient( groove.center().x(), groove.top(),
                             groove.center().x(), groove.bottom());
 
@@ -411,18 +463,17 @@ void ctkRangeSlider::paintEvent( QPaintEvent* )
   QColor l = Qt::darkGray;
   QColor u = Qt::black;
 
-  gradient.setColorAt(0, l);
-  gradient.setColorAt(1, u);
+  gradient.setColorAt(0, highlight.darker(120));
+  gradient.setColorAt(1, highlight.lighter(160));
 
+  painter.setPen(QPen(highlight.darker(150), 0));
   painter.setBrush(gradient);
-  painter.setPen(QPen(highlight.dark(140), 0));
-
   painter.drawRect( rangeBox.intersected(groove) );
 
   //  -----------------------------------
   // Render the sliders
   //
-  if (d->m_SelectedSlider)
+  if (d->m_SelectedHandles & ctkRangeSliderPrivate::MinimumHandle)
     {
     d->drawMaximumSlider( &painter );
     d->drawMinimumSlider( &painter );
@@ -448,80 +499,105 @@ void ctkRangeSlider::mousePressEvent(QMouseEvent* mouseEvent)
   QStyleOptionSlider option;
   this->initStyleOption( &option );
 
-  // Check if the first slider is pressed
+  // Check if the first handle is pressed
   
-  if (!this->isSliderDown())
+  option.sliderPosition = d->m_MinimumPosition;
+  option.sliderValue    = d->m_MinimumValue;
+
+  QStyle::SubControl control;
+  control = this->style()->hitTestComplexControl( QStyle::CC_Slider, 
+                                                  &option, 
+                                                  mouseEvent->pos(), 
+                                                  this);
+  const QRect lr = this->style()->subControlRect( QStyle::CC_Slider, 
+                                                  &option, 
+                                                  QStyle::SC_SliderHandle, 
+                                                  this);
+  if (control == QStyle::SC_SliderHandle)
     {
-    option.sliderPosition = d->m_MinimumPosition;
-    option.sliderValue    = d->m_MinimumValue;
+    d->m_SubclassPosition = d->m_MinimumPosition;
 
-    QStyle::SubControl& control = d->m_MinimumSliderSelected;
-    
-    control = this->style()->hitTestComplexControl( QStyle::CC_Slider, 
-                                                    &option, 
-                                                    mouseEvent->pos(), 
-                                                    this);
+    // save the position of the mouse inside the handle for later
+    d->m_SubclassClickOffset = mouseEvent->x() - lr.left();
+
+    this->setSliderDown(true);
 
-    if (control == QStyle::SC_SliderHandle)
+    if (d->m_SelectedHandles != ctkRangeSliderPrivate::MinimumHandle)
       {
-      d->m_SelectedSlider = true;
-      d->m_SubclassPosition = d->m_MinimumValue;
-
-      const QRect sr = this->style()->subControlRect( QStyle::CC_Slider, 
-                                                      &option, 
-                                                      QStyle::SC_SliderHandle, 
-                                                      this);
-      
-      d->m_SubclassClickOffset = mouseEvent->pos().x() - sr.topLeft().x();
-
-      this->setSliderDown(true);
-
-      if (control != d->m_MinimumSliderSelected)
-        {
-        this->update(sr);
-        }
+      d->m_SelectedHandles = ctkRangeSliderPrivate::MinimumHandle;
+      this->update(lr);
       }
+    // Accept the mouseEvent
+    mouseEvent->accept();
+    return;
     }
 
 
-  // Check if the other slider is pressed
+  // The user didn't press on the minimum handle, 
+  // Check if the other handle is pressed
 
-  if (!this->isSliderDown())
+  option.sliderPosition = d->m_MaximumPosition;
+  option.sliderValue    = d->m_MaximumValue;
+
+  control = this->style()->hitTestComplexControl( QStyle::CC_Slider, 
+                                                  &option, 
+                                                  mouseEvent->pos(), 
+                                                  this);
+  const QRect ur = this->style()->subControlRect( QStyle::CC_Slider, 
+                                                  &option, 
+                                                  QStyle::SC_SliderHandle, 
+                                                  this);
+  if (control == QStyle::SC_SliderHandle)
     {
-    option.sliderPosition = d->m_MaximumPosition;
-    option.sliderValue    = d->m_MaximumValue;
-
-    QStyle::SubControl& control = d->m_MaximumSliderSelected;
-    
-    control = this->style()->hitTestComplexControl( QStyle::CC_Slider, 
-                                                    &option, 
-                                                    mouseEvent->pos(), 
-                                                    this);
+    d->m_SubclassPosition = d->m_MaximumPosition;
 
-    if (control == QStyle::SC_SliderHandle)
-      {
-      d->m_SelectedSlider = false;
-      d->m_SubclassPosition = d->m_MaximumValue;
-
-      const QRect sr = this->style()->subControlRect( QStyle::CC_Slider, 
-                                                      &option, 
-                                                      QStyle::SC_SliderHandle, 
-                                                      this);
-
-      d->m_SubclassClickOffset = mouseEvent->pos().x() - sr.topLeft().x();
+    // save the position of the mouse inside the handle for later
+    d->m_SubclassClickOffset = mouseEvent->x() - ur.left();
 
-      this->setSliderDown(true);
+    this->setSliderDown(true);
 
-      if (d->m_MaximumSliderSelected != control)
-        {
-        this->update(sr);
-        }
+    if (d->m_SelectedHandles != ctkRangeSliderPrivate::MaximumHandle)
+      {
+      d->m_SelectedHandles = ctkRangeSliderPrivate::MaximumHandle;
+      this->update(ur);
       }
+    // Accept the mouseEvent
+    mouseEvent->accept();
+    return;
     }
 
-
-  // Accept the mouseEvent
-  mouseEvent->accept();
+  // if we are here, no handles have been pressed
+  // Check if we pressed on the groove between the 2 handles
+  
+  control = this->style()->hitTestComplexControl( QStyle::CC_Slider, 
+                                                  &option, 
+                                                  mouseEvent->pos(), 
+                                                  this);
+  QRect sr = style()->subControlRect( QStyle::CC_Slider, 
+                                      &option, 
+                                      QStyle::SC_SliderGroove, 
+                                      this);
+  if (control == QStyle::SC_SliderGroove &&
+      mouseEvent->x() > lr.center().x() &&
+      mouseEvent->x() < ur.center().x())
+    {
+    // warning lost of precision it might be fatal
+    d->m_SubclassPosition = (d->m_MinimumPosition + d->m_MaximumPosition) / 2.;
+    d->m_SubclassClickOffset = mouseEvent->x() - d->pixelPosFromRangeValue(d->m_SubclassPosition);
+    d->m_SubclassWidth = (d->m_MaximumPosition - d->m_MinimumPosition) / 2;
+    qMax(d->m_SubclassPosition - d->m_MinimumPosition, d->m_MaximumPosition - d->m_SubclassPosition);
+    this->setSliderDown(true);
+    if (!d->m_SelectedHandles & ctkRangeSliderPrivate::MinimumHandle || 
+        !d->m_SelectedHandles & ctkRangeSliderPrivate::MaximumHandle)
+      {
+      d->m_SelectedHandles = QFlags<ctkRangeSliderPrivate::Handle>(ctkRangeSliderPrivate::MinimumHandle) | 
+        QFlags<ctkRangeSliderPrivate::Handle>(ctkRangeSliderPrivate::MaximumHandle);
+      this->update(lr.united(ur).united(sr));
+      }
+    mouseEvent->accept();
+    return;
+    }
+  mouseEvent->ignore();
 }
 
 // --------------------------------------------------------------------------
@@ -529,39 +605,42 @@ void ctkRangeSlider::mousePressEvent(QMouseEvent* mouseEvent)
 void ctkRangeSlider::mouseMoveEvent(QMouseEvent* mouseEvent)
 {
   CTK_D(ctkRangeSlider);
-  if (d->m_MinimumSliderSelected == QStyle::SC_SliderHandle ||
-      d->m_MaximumSliderSelected == QStyle::SC_SliderHandle)
+  if (!d->m_SelectedHandles)
     {
+    mouseEvent->ignore();
+    return;
+    }
+  QStyleOptionSlider option;
+  this->initStyleOption(&option);
 
-    QStyleOptionSlider option;
-    this->initStyleOption(&option);
-
-    const int m = style()->pixelMetric( QStyle::PM_MaximumDragDistance, &option, this );
-
-    int newPosition = d->pixelPosToRangeValue(
-        mouseEvent->pos().x() - d->m_SubclassClickOffset);
+  const int m = style()->pixelMetric( QStyle::PM_MaximumDragDistance, &option, this );
 
-    if (m >= 0)
-      {
-      const QRect r = rect().adjusted(-m, -m, m, m);
-      if (!r.contains(mouseEvent->pos()))
-        {
-        newPosition = d->m_SubclassPosition;
-        }
-      }
+  int newPosition = d->pixelPosToRangeValue(
+    mouseEvent->x() - d->m_SubclassClickOffset);
 
-    if (d->m_MinimumSliderSelected == QStyle::SC_SliderHandle)
-      {
-      this->setMinimumPosition(qMin(newPosition,d->m_MaximumPosition));
-      }
-    else if (d->m_MaximumSliderSelected == QStyle::SC_SliderHandle)
+  if (m >= 0)
+    {
+    const QRect r = rect().adjusted(-m, -m, m, m);
+    if (!r.contains(mouseEvent->pos()))
       {
-      this->setMaximumPosition(qMax(d->m_MinimumPosition, newPosition));
+      newPosition = d->m_SubclassPosition;
       }
-    mouseEvent->accept();
     }
 
-  mouseEvent->ignore();  
+  if (d->m_SelectedHandles == ctkRangeSliderPrivate::MinimumHandle)
+    {
+    this->setMinimumPosition(qMin(newPosition,d->m_MaximumPosition));
+    }
+  else if (d->m_SelectedHandles == ctkRangeSliderPrivate::MaximumHandle)
+    {
+    this->setMaximumPosition(qMax(d->m_MinimumPosition, newPosition));
+    }
+  else if (d->m_SelectedHandles & ctkRangeSliderPrivate::MinimumHandle && 
+           d->m_SelectedHandles & ctkRangeSliderPrivate::MaximumHandle)
+    {
+    this->setPositions(newPosition - d->m_SubclassWidth, newPosition + d->m_SubclassWidth );
+    }
+  mouseEvent->accept();
 }
 
 // --------------------------------------------------------------------------
@@ -569,11 +648,10 @@ void ctkRangeSlider::mouseMoveEvent(QMouseEvent* mouseEvent)
 void ctkRangeSlider::mouseReleaseEvent(QMouseEvent* mouseEvent)
 {
   CTK_D(ctkRangeSlider);
-  QSlider::mouseReleaseEvent(mouseEvent);
+  this->QSlider::mouseReleaseEvent(mouseEvent);
+
   setSliderDown(false);
-  
-  d->m_MinimumSliderSelected = QStyle::SC_None;
-  d->m_MaximumSliderSelected = QStyle::SC_None;
+  d->m_SelectedHandles = 0;
 
   this->update();
 }