File indexing completed on 2024-04-28 03:42:25

0001 /*
0002     SPDX-FileCopyrightText: Kitware Inc.
0003     SPDX-License-Identifier: Apache-2.0
0004 
0005     Modified from the original code to support 3 sliders, May 2023.
0006 */
0007 
0008 // Qt includes
0009 #include <QDebug>
0010 #include <QMouseEvent>
0011 #include <QKeyEvent>
0012 #include <QStyleOptionSlider>
0013 #include <QApplication>
0014 #include <QStylePainter>
0015 #include <QStyle>
0016 #include <QToolTip>
0017 
0018 #include "ctk3slider.h"
0019 
0020 class ctk3SliderPrivate
0021 {
0022         Q_DECLARE_PUBLIC(ctk3Slider)
0023     protected:
0024         ctk3Slider* const q_ptr;
0025     public:
0026         /// Boolean indicates the selected handle
0027         ///   True for the minimum range handle, false for the maximum range handle
0028         enum Handle
0029         {
0030             NoHandle = 0x0,
0031             MinimumHandle = 0x1,
0032             MidHandle = 0x2,
0033             MaximumHandle = 0x4
0034         };
0035         Q_DECLARE_FLAGS(Handles, Handle)
0036 
0037         ctk3SliderPrivate(ctk3Slider &object);
0038         void init();
0039 
0040         /// Return the handle at the given pos, or none if no handle is at the pos.
0041         /// If a handle is selected, handleRect is set to the handle rect.
0042         /// otherwise return NoHandle and handleRect is set to the combined rect of
0043         /// the min and max handles
0044         Handle handleAtPos(const QPoint &pos, QRect &handleRect)const;
0045 
0046         /// Copied verbatim from QSliderPrivate class (see QSlider.cpp)
0047         int pixelPosToRangeValue(int pos) const;
0048         int pixelPosFromRangeValue(int val) const;
0049 
0050         /// Draw the bottom and top sliders.
0051         void drawMinimumSlider( QStylePainter* painter ) const;
0052         void drawMaximumSlider( QStylePainter* painter ) const;
0053         void drawMidSlider( QStylePainter* painter ) const;
0054 
0055         /// End points of the range on the Model
0056         int m_MaximumValue;
0057         int m_MinimumValue;
0058         int m_MidValue;
0059 
0060         /// End points of the range on the GUI. This is synced with the model.
0061         int m_MaximumPosition;
0062         int m_MinimumPosition;
0063         int m_MidPosition;
0064 
0065         /// Controls selected ?
0066         QStyle::SubControl m_MinimumSliderSelected;
0067         QStyle::SubControl m_MaximumSliderSelected;
0068         QStyle::SubControl m_MidSliderSelected;
0069 
0070         /// See QSliderPrivate::clickOffset.
0071         /// Overrides this ivar
0072         int m_SubclassClickOffset;
0073 
0074         /// See QSliderPrivate::position
0075         /// Overrides this ivar.
0076         int m_SubclassPosition;
0077 
0078         /// Original width between the 2 bounds before any moves
0079         int m_SubclassWidth;
0080 
0081         ctk3SliderPrivate::Handles m_SelectedHandles;
0082 
0083         QString m_HandleToolTip;
0084 
0085     private:
0086         Q_DISABLE_COPY(ctk3SliderPrivate)
0087 };
0088 
0089 // --------------------------------------------------------------------------
0090 ctk3SliderPrivate::ctk3SliderPrivate(ctk3Slider &object)
0091     : q_ptr(&object)
0092 {
0093     this->m_MinimumValue = 0;
0094     this->m_MaximumValue = 100;
0095     this->m_MidValue = 50;
0096     this->m_MinimumPosition = 0;
0097     this->m_MaximumPosition = 100;
0098     this->m_MidPosition = 50;
0099     this->m_MinimumSliderSelected = QStyle::SC_None;
0100     this->m_MaximumSliderSelected = QStyle::SC_None;
0101     this->m_MidSliderSelected = QStyle::SC_None;
0102     this->m_SubclassClickOffset = 0;
0103     this->m_SubclassPosition = 0;
0104     this->m_SubclassWidth = 0;
0105     this->m_SelectedHandles = ctk3SliderPrivate::NoHandle;
0106 }
0107 
0108 // --------------------------------------------------------------------------
0109 void ctk3SliderPrivate::init()
0110 {
0111     Q_Q(ctk3Slider);
0112     this->m_MinimumValue = q->minimum();
0113     this->m_MaximumValue = q->maximum();
0114     this->m_MaximumValue = (q->minimum() + q->maximum()) / 2.0;
0115     this->m_MinimumPosition = q->minimum();
0116     this->m_MaximumPosition = q->maximum();
0117     this->m_MidPosition = (q->minimum() + q->maximum()) / 2.0;
0118     q->connect(q, SIGNAL(rangeChanged(int, int)), q, SLOT(onRangeChanged(int, int)));
0119 }
0120 
0121 // --------------------------------------------------------------------------
0122 ctk3SliderPrivate::Handle ctk3SliderPrivate::handleAtPos(const QPoint &pos, QRect &handleRect)const
0123 {
0124     Q_Q(const ctk3Slider);
0125 
0126     QStyleOptionSlider option;
0127     q->initStyleOption( &option );
0128 
0129     // The functions hitTestComplexControl only know about 1 handle. As we have
0130     // 3, we change the position of the handle and test if the pos correspond to
0131     // any of the 3 positions.
0132 
0133     // Test the MinimumHandle
0134     option.sliderPosition = this->m_MinimumPosition;
0135     option.sliderValue    = this->m_MinimumValue;
0136 
0137     QStyle::SubControl minimumControl = q->style()->hitTestComplexControl(
0138                                             QStyle::CC_Slider, &option, pos, q);
0139     QRect minimumHandleRect = q->style()->subControlRect(
0140                                   QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q);
0141 
0142     // Test if the pos is under the Maximum handle
0143     option.sliderPosition = this->m_MaximumPosition;
0144     option.sliderValue    = this->m_MaximumValue;
0145 
0146     QStyle::SubControl maximumControl = q->style()->hitTestComplexControl(
0147                                             QStyle::CC_Slider, &option, pos, q);
0148     QRect maximumHandleRect = q->style()->subControlRect(
0149                                   QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q);
0150 
0151     // Test if the pos is under the Mid handle
0152     option.sliderPosition = this->m_MidPosition;
0153     option.sliderValue    = this->m_MidValue;
0154 
0155     QStyle::SubControl midControl = q->style()->hitTestComplexControl(
0156                                         QStyle::CC_Slider, &option, pos, q);
0157     QRect midHandleRect = q->style()->subControlRect(
0158                               QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q);
0159 
0160     if (minimumControl == QStyle::SC_SliderHandle)
0161     {
0162         handleRect = minimumHandleRect;
0163         return MinimumHandle;
0164     }
0165     else if (midControl == QStyle::SC_SliderHandle)
0166     {
0167         handleRect = midHandleRect;
0168         return MidHandle;
0169     }
0170     else if (maximumControl == QStyle::SC_SliderHandle)
0171     {
0172         handleRect = maximumHandleRect;
0173         return MaximumHandle;
0174     }
0175     handleRect = minimumHandleRect.united(maximumHandleRect);
0176     return NoHandle;
0177 }
0178 
0179 // --------------------------------------------------------------------------
0180 // Copied verbatim from QSliderPrivate::pixelPosToRangeValue. See QSlider.cpp
0181 //
0182 int ctk3SliderPrivate::pixelPosToRangeValue( int pos ) const
0183 {
0184     Q_Q(const ctk3Slider);
0185     QStyleOptionSlider option;
0186     q->initStyleOption( &option );
0187 
0188     QRect gr = q->style()->subControlRect( QStyle::CC_Slider,
0189                                            &option,
0190                                            QStyle::SC_SliderGroove,
0191                                            q );
0192     QRect sr = q->style()->subControlRect( QStyle::CC_Slider,
0193                                            &option,
0194                                            QStyle::SC_SliderHandle,
0195                                            q );
0196     int sliderMin, sliderMax, sliderLength;
0197     if (option.orientation == Qt::Horizontal)
0198     {
0199         sliderLength = sr.width();
0200         sliderMin = gr.x();
0201         sliderMax = gr.right() - sliderLength + 1;
0202     }
0203     else
0204     {
0205         sliderLength = sr.height();
0206         sliderMin = gr.y();
0207         sliderMax = gr.bottom() - sliderLength + 1;
0208     }
0209 
0210     return QStyle::sliderValueFromPosition( q->minimum(),
0211                                             q->maximum(),
0212                                             pos - sliderMin,
0213                                             sliderMax - sliderMin,
0214                                             option.upsideDown );
0215 }
0216 
0217 //---------------------------------------------------------------------------
0218 int ctk3SliderPrivate::pixelPosFromRangeValue( int val ) const
0219 {
0220     Q_Q(const ctk3Slider);
0221     QStyleOptionSlider option;
0222     q->initStyleOption( &option );
0223 
0224     QRect gr = q->style()->subControlRect( QStyle::CC_Slider,
0225                                            &option,
0226                                            QStyle::SC_SliderGroove,
0227                                            q );
0228     QRect sr = q->style()->subControlRect( QStyle::CC_Slider,
0229                                            &option,
0230                                            QStyle::SC_SliderHandle,
0231                                            q );
0232     int sliderMin, sliderMax, sliderLength;
0233     if (option.orientation == Qt::Horizontal)
0234     {
0235         sliderLength = sr.width();
0236         sliderMin = gr.x();
0237         sliderMax = gr.right() - sliderLength + 1;
0238     }
0239     else
0240     {
0241         sliderLength = sr.height();
0242         sliderMin = gr.y();
0243         sliderMax = gr.bottom() - sliderLength + 1;
0244     }
0245 
0246     return QStyle::sliderPositionFromValue( q->minimum(),
0247                                             q->maximum(),
0248                                             val,
0249                                             sliderMax - sliderMin,
0250                                             option.upsideDown ) + sliderMin;
0251 }
0252 
0253 //---------------------------------------------------------------------------
0254 // Draw slider at the bottom end of the range
0255 void ctk3SliderPrivate::drawMinimumSlider( QStylePainter* painter ) const
0256 {
0257     Q_Q(const ctk3Slider);
0258     QStyleOptionSlider option;
0259     q->initMinimumSliderStyleOption( &option );
0260 
0261     option.subControls = QStyle::SC_SliderHandle;
0262     option.sliderValue = m_MinimumValue;
0263     option.sliderPosition = m_MinimumPosition;
0264     if (q->isMinimumSliderDown())
0265     {
0266         option.activeSubControls = QStyle::SC_SliderHandle;
0267         option.state |= QStyle::State_Sunken;
0268     }
0269 #ifdef Q_OS_MAC
0270     // On mac style, drawing just the handle actually draws also the groove.
0271     QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option,
0272                                             QStyle::SC_SliderHandle, q);
0273     painter->setClipRect(clip);
0274 #endif
0275     painter->drawComplexControl(QStyle::CC_Slider, option);
0276 }
0277 
0278 //---------------------------------------------------------------------------
0279 // Draw slider at the top end of the range
0280 void ctk3SliderPrivate::drawMaximumSlider( QStylePainter* painter ) const
0281 {
0282     Q_Q(const ctk3Slider);
0283     QStyleOptionSlider option;
0284     q->initMaximumSliderStyleOption( &option );
0285 
0286     option.subControls = QStyle::SC_SliderHandle;
0287     option.sliderValue = m_MaximumValue;
0288     option.sliderPosition = m_MaximumPosition;
0289     if (q->isMaximumSliderDown())
0290     {
0291         option.activeSubControls = QStyle::SC_SliderHandle;
0292         option.state |= QStyle::State_Sunken;
0293     }
0294 #ifdef Q_OS_MAC
0295     // On mac style, drawing just the handle actually draws also the groove.
0296     QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option,
0297                                             QStyle::SC_SliderHandle, q);
0298     painter->setClipRect(clip);
0299 #endif
0300     painter->drawComplexControl(QStyle::CC_Slider, option);
0301 }
0302 
0303 // Draw the mid slider
0304 void ctk3SliderPrivate::drawMidSlider( QStylePainter* painter ) const
0305 {
0306     Q_Q(const ctk3Slider);
0307     QStyleOptionSlider option;
0308     q->initMidSliderStyleOption( &option );
0309 
0310     option.subControls = QStyle::SC_SliderHandle;
0311     option.sliderValue = m_MidValue;
0312     option.sliderPosition = m_MidPosition;
0313     if (q->isMidSliderDown())
0314     {
0315         option.activeSubControls = QStyle::SC_SliderHandle;
0316         option.state |= QStyle::State_Sunken;
0317     }
0318 #ifdef Q_OS_MAC
0319     // On mac style, drawing just the handle actually draws also the groove.
0320     QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option,
0321                                             QStyle::SC_SliderHandle, q);
0322     painter->setClipRect(clip);
0323 #endif
0324     painter->drawComplexControl(QStyle::CC_Slider, option);
0325 }
0326 
0327 // --------------------------------------------------------------------------
0328 ctk3Slider::ctk3Slider(QWidget* _parent)
0329     : QSlider(_parent)
0330     , d_ptr(new ctk3SliderPrivate(*this))
0331 {
0332     Q_D(ctk3Slider);
0333     d->init();
0334 }
0335 
0336 // --------------------------------------------------------------------------
0337 ctk3Slider::ctk3Slider( Qt::Orientation o,
0338                         QWidget* parentObject )
0339     : QSlider(o, parentObject)
0340     , d_ptr(new ctk3SliderPrivate(*this))
0341 {
0342     Q_D(ctk3Slider);
0343     d->init();
0344 }
0345 
0346 // --------------------------------------------------------------------------
0347 ctk3Slider::ctk3Slider(ctk3SliderPrivate* impl, QWidget* _parent)
0348     : QSlider(_parent)
0349     , d_ptr(impl)
0350 {
0351     Q_D(ctk3Slider);
0352     d->init();
0353 }
0354 
0355 // --------------------------------------------------------------------------
0356 ctk3Slider::ctk3Slider( ctk3SliderPrivate* impl, Qt::Orientation o,
0357                         QWidget* parentObject )
0358     : QSlider(o, parentObject)
0359     , d_ptr(impl)
0360 {
0361     Q_D(ctk3Slider);
0362     d->init();
0363 }
0364 
0365 // --------------------------------------------------------------------------
0366 ctk3Slider::~ctk3Slider()
0367 {
0368 }
0369 
0370 // --------------------------------------------------------------------------
0371 int ctk3Slider::minimumValue() const
0372 {
0373     Q_D(const ctk3Slider);
0374     return d->m_MinimumValue;
0375 }
0376 
0377 // --------------------------------------------------------------------------
0378 void ctk3Slider::setMinimumValue( int min )
0379 {
0380     Q_D(ctk3Slider);
0381     // Min cannot be set higher than mid or max
0382     int newMin = qMin( qMin(min, d->m_MidValue), d->m_MaximumValue);
0383     this->setValues( newMin, d->m_MidValue, d->m_MaximumValue);
0384 }
0385 
0386 // --------------------------------------------------------------------------
0387 int ctk3Slider::maximumValue() const
0388 {
0389     Q_D(const ctk3Slider);
0390     return d->m_MaximumValue;
0391 }
0392 
0393 // --------------------------------------------------------------------------
0394 void ctk3Slider::setMaximumValue( int max )
0395 {
0396     Q_D(ctk3Slider);
0397     // Max cannot be set lower than min or mid
0398     int newMax = qMax( qMax(max, d->m_MidValue), d->m_MinimumValue);
0399     this->setValues(d->m_MinimumValue, d->m_MidValue, newMax);
0400 }
0401 
0402 // --------------------------------------------------------------------------
0403 int ctk3Slider::midValue() const
0404 {
0405     Q_D(const ctk3Slider);
0406     return d->m_MidValue;
0407 }
0408 
0409 // --------------------------------------------------------------------------
0410 void ctk3Slider::setMidValue( int mid )
0411 {
0412     Q_D(ctk3Slider);
0413     // Mid cannot be set lower than min or higher than max;
0414     int newMid = qMax(d->m_MinimumValue, mid);
0415     newMid = qMin(newMid, d->m_MaximumValue);
0416     this->setValues(d->m_MinimumValue, newMid, d->m_MaximumValue);
0417 }
0418 
0419 // --------------------------------------------------------------------------
0420 namespace
0421 {
0422 // Sorts the 3 numbers so l becomes the least, and u becomes the largest.
0423 void sortLMU(int &l, int &m, int &u)
0424 {
0425     int kL = l, kM = m, kU = u;
0426     if (l <= m && l <= u) // l is the smallest
0427     {
0428         if (m <= u) // order is l,m,u
0429             return;
0430         else // order is l, u, m
0431         {
0432             l = kL;
0433             m = kU;
0434             u = kM;
0435         }
0436     }
0437     else if (m <= l && m <= u) // m is the smallest
0438     {
0439         if (l <= u) // order is m, l, u
0440         {
0441             l = kM;
0442             m = kL;
0443             u = kU;
0444         }
0445         else  // order is m, u, l
0446         {
0447             l = kM;
0448             m = kU;
0449             u = kL;
0450         }
0451     }
0452     else // u is the smallest
0453     {
0454         if (l <= m)  // order is u, l, m
0455         {
0456             l = kL;
0457             m = kU;
0458             u = kM;
0459             l = m;
0460         }
0461         else  // order is u, m, l
0462         {
0463             l = kU;
0464             m = kM;
0465             u = kL;
0466         }
0467     }
0468 }
0469 }
0470 void ctk3Slider::setValues(int l, int m, int u)
0471 {
0472     Q_D(ctk3Slider);
0473     sortLMU(l, m, u);
0474     const int minValue =
0475         qBound(this->minimum(), l, this->maximum());
0476     const int maxValue =
0477         qBound(this->minimum(), u, this->maximum());
0478     const int midValue =
0479         qBound(this->minimum(), m, this->maximum());
0480 
0481     bool emitMinValChanged = (minValue != d->m_MinimumValue);
0482     bool emitMaxValChanged = (maxValue != d->m_MaximumValue);
0483     bool emitMidValChanged = (midValue != d->m_MidValue);
0484 
0485     d->m_MinimumValue = minValue;
0486     d->m_MaximumValue = maxValue;
0487     d->m_MidValue = midValue;
0488 
0489     bool emitMinPosChanged =
0490         (minValue != d->m_MinimumPosition);
0491     bool emitMaxPosChanged =
0492         (maxValue != d->m_MaximumPosition);
0493     bool emitMidPosChanged =
0494         (midValue != d->m_MidPosition);
0495     d->m_MinimumPosition = minValue;
0496     d->m_MaximumPosition = maxValue;
0497     d->m_MidPosition = midValue;
0498 
0499     if (isSliderDown())
0500     {
0501         if (emitMinPosChanged || emitMaxPosChanged || emitMidPosChanged)
0502         {
0503             emit positionsChanged(d->m_MinimumPosition, d->m_MidPosition, d->m_MaximumPosition);
0504         }
0505         if (emitMinPosChanged)
0506         {
0507             emit minimumPositionChanged(d->m_MinimumPosition);
0508         }
0509         if (emitMaxPosChanged)
0510         {
0511             emit maximumPositionChanged(d->m_MaximumPosition);
0512         }
0513         if (emitMidPosChanged)
0514         {
0515             emit midPositionChanged(d->m_MidPosition);
0516         }
0517     }
0518     if (emitMinValChanged || emitMaxValChanged || emitMidValChanged)
0519     {
0520         emit valuesChanged(d->m_MinimumValue, d->m_MidValue, d->m_MaximumValue);
0521     }
0522     if (emitMinValChanged)
0523     {
0524         emit minimumValueChanged(d->m_MinimumValue);
0525     }
0526     if (emitMaxValChanged)
0527     {
0528         emit maximumValueChanged(d->m_MaximumValue);
0529     }
0530     if (emitMidValChanged)
0531     {
0532         emit midValueChanged(d->m_MidValue);
0533     }
0534     if (emitMinPosChanged || emitMaxPosChanged || emitMidPosChanged ||
0535             emitMinValChanged || emitMaxValChanged || emitMidValChanged)
0536     {
0537         this->update();
0538     }
0539 }
0540 
0541 // --------------------------------------------------------------------------
0542 int ctk3Slider::minimumPosition() const
0543 {
0544     Q_D(const ctk3Slider);
0545     return d->m_MinimumPosition;
0546 }
0547 
0548 // --------------------------------------------------------------------------
0549 int ctk3Slider::maximumPosition() const
0550 {
0551     Q_D(const ctk3Slider);
0552     return d->m_MaximumPosition;
0553 }
0554 // --------------------------------------------------------------------------
0555 int ctk3Slider::midPosition() const
0556 {
0557     Q_D(const ctk3Slider);
0558     return d->m_MidPosition;
0559 }
0560 
0561 // --------------------------------------------------------------------------
0562 void ctk3Slider::setMinimumPosition(int l)
0563 {
0564     Q_D(const ctk3Slider);
0565     // Min cannot be set higher than mid or max
0566     int newMin = qMin( qMin(l, d->m_MidPosition), d->m_MaximumPosition);
0567     this->setPositions(newMin, d->m_MidPosition, d->m_MaximumPosition);
0568 }
0569 
0570 // --------------------------------------------------------------------------
0571 void ctk3Slider::setMaximumPosition(int u)
0572 {
0573     Q_D(const ctk3Slider);
0574     // Max cannot be set lower than min or mid
0575     int newMax = qMax( qMax(u, d->m_MidPosition), d->m_MinimumPosition);
0576     this->setPositions(d->m_MinimumPosition, d->m_MidPosition, newMax);
0577 }
0578 // --------------------------------------------------------------------------
0579 void ctk3Slider::setMidPosition(int m)
0580 {
0581     Q_D(const ctk3Slider);
0582     // Mid cannot be set lower than min or higher than max;
0583     int newMid = qMax(d->m_MinimumPosition, m);
0584     newMid = qMin(newMid, d->m_MaximumPosition);
0585     this->setPositions(d->m_MinimumPosition, newMid, d->m_MaximumPosition);
0586 }
0587 
0588 // --------------------------------------------------------------------------
0589 void ctk3Slider::setPositions(int min, int mid, int max)
0590 {
0591     Q_D(ctk3Slider);
0592     sortLMU(min, mid, max);
0593     const int minPosition =
0594         qBound(this->minimum(), min, this->maximum());
0595     const int maxPosition =
0596         qBound(this->minimum(), max, this->maximum());
0597     const int midPosition =
0598         qBound(this->minimum(), mid, this->maximum());
0599 
0600     bool emitMinPosChanged = (minPosition != d->m_MinimumPosition);
0601     bool emitMaxPosChanged = (maxPosition != d->m_MaximumPosition);
0602     bool emitMidPosChanged = (midPosition != d->m_MidPosition);
0603 
0604     if (!emitMinPosChanged && !emitMaxPosChanged && !emitMidPosChanged)
0605     {
0606         return;
0607     }
0608 
0609     d->m_MinimumPosition = minPosition;
0610     d->m_MaximumPosition = maxPosition;
0611     d->m_MidPosition = midPosition;
0612 
0613     if (!this->hasTracking())
0614     {
0615         this->update();
0616     }
0617     if (isSliderDown())
0618     {
0619         if (emitMinPosChanged)
0620         {
0621             emit minimumPositionChanged(d->m_MinimumPosition);
0622         }
0623         if (emitMaxPosChanged)
0624         {
0625             emit maximumPositionChanged(d->m_MaximumPosition);
0626         }
0627         if (emitMidPosChanged)
0628         {
0629             emit midPositionChanged(d->m_MidPosition);
0630         }
0631         if (emitMinPosChanged || emitMaxPosChanged || emitMidPosChanged)
0632         {
0633             emit positionsChanged(d->m_MinimumPosition, d->m_MidPosition, d->m_MaximumPosition);
0634         }
0635     }
0636     if (this->hasTracking())
0637     {
0638         this->triggerAction(SliderMove);
0639         this->setValues(d->m_MinimumPosition, d->m_MidPosition, d->m_MaximumPosition);
0640     }
0641 }
0642 
0643 // --------------------------------------------------------------------------
0644 void ctk3Slider::onRangeChanged(int _minimum, int _maximum)
0645 {
0646     Q_UNUSED(_minimum);
0647     Q_UNUSED(_maximum);
0648     Q_D(ctk3Slider);
0649     this->setValues(d->m_MinimumValue, d->m_MidValue, d->m_MaximumValue);
0650 }
0651 
0652 // --------------------------------------------------------------------------
0653 // Render
0654 void ctk3Slider::paintEvent( QPaintEvent* )
0655 {
0656     Q_D(ctk3Slider);
0657     QStyleOptionSlider option;
0658     this->initStyleOption(&option);
0659 
0660     QStylePainter painter(this);
0661     option.subControls = QStyle::SC_SliderGroove;
0662     // Move to minimum to not highlight the SliderGroove.
0663     // On mac style, drawing just the slider groove also draws the handles,
0664     // therefore we give a negative (outside of view) position.
0665     option.sliderValue = this->minimum() - this->maximum();
0666     option.sliderPosition = this->minimum() - this->maximum();
0667     painter.drawComplexControl(QStyle::CC_Slider, option);
0668 
0669     option.sliderPosition = d->m_MinimumPosition;
0670     const QRect lr = style()->subControlRect( QStyle::CC_Slider,
0671                      &option,
0672                      QStyle::SC_SliderHandle,
0673                      this);
0674 
0675     option.sliderPosition = d->m_MidPosition;
0676 
0677     const QRect mr = style()->subControlRect( QStyle::CC_Slider,
0678                      &option,
0679                      QStyle::SC_SliderHandle,
0680                      this);
0681 
0682     option.sliderPosition = d->m_MaximumPosition;
0683 
0684     const QRect ur = style()->subControlRect( QStyle::CC_Slider,
0685                      &option,
0686                      QStyle::SC_SliderHandle,
0687                      this);
0688 
0689     QRect sr = style()->subControlRect( QStyle::CC_Slider,
0690                                         &option,
0691                                         QStyle::SC_SliderGroove,
0692                                         this);
0693     QRect rangeBox;
0694     if (option.orientation == Qt::Horizontal)
0695     {
0696         rangeBox = QRect(
0697                        QPoint(qMin( lr.center().x(), ur.center().x() ), sr.center().y() - 2),
0698                        QPoint(qMax( lr.center().x(), ur.center().x() ), sr.center().y() + 1));
0699     }
0700     else
0701     {
0702         rangeBox = QRect(
0703                        QPoint(sr.center().x() - 2, qMin( lr.center().y(), ur.center().y() )),
0704                        QPoint(sr.center().x() + 1, qMax( lr.center().y(), ur.center().y() )));
0705     }
0706 
0707     // -----------------------------
0708     // Render the range
0709     //
0710     QRect groove = this->style()->subControlRect( QStyle::CC_Slider,
0711                    &option,
0712                    QStyle::SC_SliderGroove,
0713                    this );
0714     groove.adjust(0, 0, -1, 0);
0715 
0716     // Create default colors based on the transfer function.
0717     //
0718     QColor highlight = this->palette().color(QPalette::Normal, QPalette::Highlight);
0719     QLinearGradient gradient;
0720     if (option.orientation == Qt::Horizontal)
0721     {
0722         gradient = QLinearGradient( groove.center().x(), groove.top(),
0723                                     groove.center().x(), groove.bottom());
0724     }
0725     else
0726     {
0727         gradient = QLinearGradient( groove.left(), groove.center().y(),
0728                                     groove.right(), groove.center().y());
0729     }
0730 
0731     gradient.setColorAt(0, highlight.darker(120));
0732     gradient.setColorAt(1, highlight.lighter(160));
0733 
0734     painter.setPen(QPen(highlight.darker(150), 0));
0735     painter.setBrush(gradient);
0736     painter.drawRect( rangeBox.intersected(groove) );
0737 
0738     //  -----------------------------------
0739     // Render the sliders
0740     //
0741     if (this->isMinimumSliderDown())
0742     {
0743         d->drawMaximumSlider( &painter );
0744         d->drawMidSlider( &painter );
0745         d->drawMinimumSlider( &painter );
0746     }
0747     else if (this->isMidSliderDown())
0748     {
0749         d->drawMaximumSlider( &painter );
0750         d->drawMinimumSlider( &painter );
0751         d->drawMidSlider( &painter );
0752     }
0753     else
0754     {
0755         d->drawMinimumSlider( &painter );
0756         d->drawMidSlider( &painter );
0757         d->drawMaximumSlider( &painter );
0758     }
0759 }
0760 
0761 // --------------------------------------------------------------------------
0762 // Standard Qt UI events
0763 void ctk3Slider::mousePressEvent(QMouseEvent* mouseEvent)
0764 {
0765     Q_D(ctk3Slider);
0766     if (minimum() == maximum() || (mouseEvent->buttons() ^ mouseEvent->button()))
0767     {
0768         mouseEvent->ignore();
0769         return;
0770     }
0771     int mepos = this->orientation() == Qt::Horizontal ?
0772                 mouseEvent->pos().x() : mouseEvent->pos().y();
0773 
0774     QStyleOptionSlider option;
0775     this->initStyleOption( &option );
0776 
0777     QRect handleRect;
0778     ctk3SliderPrivate::Handle handle_ = d->handleAtPos(mouseEvent->pos(), handleRect);
0779 
0780     if (handle_ != ctk3SliderPrivate::NoHandle)
0781     {
0782         if (handle_ == ctk3SliderPrivate::MinimumHandle)
0783             d->m_SubclassPosition = d->m_MinimumPosition;
0784         else if (handle_ == ctk3SliderPrivate::MaximumHandle)
0785             d->m_SubclassPosition = d->m_MaximumPosition;
0786         else if (handle_ == ctk3SliderPrivate::MidHandle)
0787             d->m_SubclassPosition = d->m_MidPosition;
0788 
0789         // save the position of the mouse inside the handle for later
0790         d->m_SubclassClickOffset = mepos - (this->orientation() == Qt::Horizontal ?
0791                                             handleRect.left() : handleRect.top());
0792 
0793         this->setSliderDown(true);
0794 
0795         if (d->m_SelectedHandles != handle_)
0796         {
0797             d->m_SelectedHandles = handle_;
0798             this->update(handleRect);
0799         }
0800         // Accept the mouseEvent
0801         mouseEvent->accept();
0802         return;
0803     }
0804 
0805     // if we are here, no handles have been pressed
0806     // Check if we pressed on the groove between the 2 handles
0807 
0808     QStyle::SubControl control = this->style()->hitTestComplexControl(
0809                                      QStyle::CC_Slider, &option, mouseEvent->pos(), this);
0810     QRect sr = style()->subControlRect(
0811                    QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this);
0812     int minCenter = (this->orientation() == Qt::Horizontal ?
0813                      handleRect.left() : handleRect.top());
0814     int maxCenter = (this->orientation() == Qt::Horizontal ?
0815                      handleRect.right() : handleRect.bottom());
0816     if (control == QStyle::SC_SliderGroove &&
0817             mepos > minCenter && mepos < maxCenter)
0818     {
0819         // warning lost of precision it might be fatal
0820         d->m_SubclassPosition = (d->m_MinimumPosition + d->m_MaximumPosition) / 2.;
0821         d->m_SubclassClickOffset = mepos - d->pixelPosFromRangeValue(d->m_SubclassPosition);
0822         d->m_SubclassWidth = (d->m_MaximumPosition - d->m_MinimumPosition) / 2;
0823         qMax(d->m_SubclassPosition - d->m_MinimumPosition, d->m_MaximumPosition - d->m_SubclassPosition);
0824         this->setSliderDown(true);
0825         if (!this->isMinimumSliderDown() || !this->isMaximumSliderDown())
0826         {
0827             d->m_SelectedHandles =
0828                 QFlags<ctk3SliderPrivate::Handle>(ctk3SliderPrivate::MinimumHandle) |
0829                 QFlags<ctk3SliderPrivate::Handle>(ctk3SliderPrivate::MaximumHandle);
0830             this->update(handleRect.united(sr));
0831         }
0832         mouseEvent->accept();
0833         return;
0834     }
0835     mouseEvent->ignore();
0836 }
0837 
0838 // --------------------------------------------------------------------------
0839 // Standard Qt UI events
0840 void ctk3Slider::mouseMoveEvent(QMouseEvent* mouseEvent)
0841 {
0842     Q_D(ctk3Slider);
0843     if (!d->m_SelectedHandles)
0844     {
0845         mouseEvent->ignore();
0846         return;
0847     }
0848     int mepos = this->orientation() == Qt::Horizontal ?
0849                 mouseEvent->pos().x() : mouseEvent->pos().y();
0850 
0851     QStyleOptionSlider option;
0852     this->initStyleOption(&option);
0853 
0854     const int m = style()->pixelMetric( QStyle::PM_MaximumDragDistance, &option, this );
0855 
0856     int newPosition = d->pixelPosToRangeValue(mepos - d->m_SubclassClickOffset);
0857 
0858     if (m >= 0)
0859     {
0860         const QRect r = rect().adjusted(-m, -m, m, m);
0861         if (!r.contains(mouseEvent->pos()))
0862         {
0863             newPosition = d->m_SubclassPosition;
0864         }
0865     }
0866 
0867     // If two sliders down, pick the one that makes sense.
0868     // TODO: this won't trigger because right not the code doesn't recognize two sliders down.
0869     if (this->isMinimumSliderDown() && this->isMidSliderDown())
0870     {
0871         // To break the tie, first try min, but only if the new position
0872         // isn't > the mid position
0873         if (newPosition < d->m_MidPosition)
0874             this->setPositions(newPosition, d->m_MidPosition, d->m_MaximumPosition);
0875         else
0876             this->setPositions(d->m_MinimumPosition, newPosition, d->m_MaximumPosition);
0877     }
0878     else if (this->isMidSliderDown() && this->isMaximumSliderDown())
0879     {
0880         // To break the tie, first try min, but only if the new position
0881         // isn't > the mid position
0882         if (newPosition < d->m_MaximumPosition)
0883             this->setPositions(d->m_MinimumPosition, newPosition, d->m_MaximumPosition);
0884         else
0885             this->setPositions(d->m_MinimumPosition, d->m_MidPosition, newPosition);
0886     }
0887     else if (this->isMinimumSliderDown())
0888     {
0889         double newMinPos = qMin(newPosition, d->m_MidPosition);
0890         this->setPositions(newMinPos, d->m_MidPosition, d->m_MaximumPosition);
0891     }
0892     // The upper/right slider is down
0893     else if (this->isMaximumSliderDown())
0894     {
0895         double newMaxPos = qMax(d->m_MidPosition, newPosition);
0896         this->setPositions(d->m_MinimumPosition, d->m_MidPosition, newMaxPos);
0897     }
0898     // The mid slider is down
0899     else if (this->isMidSliderDown())
0900     {
0901         double newMidPos = qMin(qMax(d->m_MinimumPosition, newPosition), d->m_MaximumPosition);
0902         this->setPositions(d->m_MinimumPosition, newMidPos, d->m_MaximumPosition);
0903     }
0904     mouseEvent->accept();
0905 }
0906 
0907 // --------------------------------------------------------------------------
0908 // Standard Qt UI mouseEvents
0909 void ctk3Slider::mouseReleaseEvent(QMouseEvent* mouseEvent)
0910 {
0911     Q_D(ctk3Slider);
0912     this->QSlider::mouseReleaseEvent(mouseEvent);
0913 
0914     setSliderDown(false);
0915     d->m_SelectedHandles = ctk3SliderPrivate::NoHandle;
0916 
0917     emit released(d->m_MinimumValue, d->m_MidValue, d->m_MaximumValue);
0918     this->update();
0919 }
0920 
0921 // --------------------------------------------------------------------------
0922 bool ctk3Slider::isMinimumSliderDown()const
0923 {
0924     Q_D(const ctk3Slider);
0925     return d->m_SelectedHandles & ctk3SliderPrivate::MinimumHandle;
0926 }
0927 
0928 // --------------------------------------------------------------------------
0929 bool ctk3Slider::isMaximumSliderDown()const
0930 {
0931     Q_D(const ctk3Slider);
0932     return d->m_SelectedHandles & ctk3SliderPrivate::MaximumHandle;
0933 }
0934 // --------------------------------------------------------------------------
0935 bool ctk3Slider::isMidSliderDown()const
0936 {
0937     Q_D(const ctk3Slider);
0938     return d->m_SelectedHandles & ctk3SliderPrivate::MidHandle;
0939 }
0940 
0941 // --------------------------------------------------------------------------
0942 void ctk3Slider::initMinimumSliderStyleOption(QStyleOptionSlider* option) const
0943 {
0944     this->initStyleOption(option);
0945 }
0946 
0947 // --------------------------------------------------------------------------
0948 void ctk3Slider::initMaximumSliderStyleOption(QStyleOptionSlider* option) const
0949 {
0950     this->initStyleOption(option);
0951 }
0952 // --------------------------------------------------------------------------
0953 void ctk3Slider::initMidSliderStyleOption(QStyleOptionSlider* option) const
0954 {
0955     this->initStyleOption(option);
0956 }
0957 
0958 // --------------------------------------------------------------------------
0959 QString ctk3Slider::handleToolTip()const
0960 {
0961     Q_D(const ctk3Slider);
0962     return d->m_HandleToolTip;
0963 }
0964 
0965 // --------------------------------------------------------------------------
0966 void ctk3Slider::setHandleToolTip(const QString &_toolTip)
0967 {
0968     Q_D(ctk3Slider);
0969     d->m_HandleToolTip = _toolTip;
0970 }
0971 
0972 // --------------------------------------------------------------------------
0973 bool ctk3Slider::event(QEvent* _event)
0974 {
0975     Q_D(ctk3Slider);
0976     switch(_event->type())
0977     {
0978         case QEvent::ToolTip:
0979         {
0980             QHelpEvent* helpEvent = static_cast<QHelpEvent*>(_event);
0981             QStyleOptionSlider opt;
0982             // Test the MinimumHandle
0983             opt.sliderPosition = d->m_MinimumPosition;
0984             opt.sliderValue = d->m_MinimumValue;
0985             this->initStyleOption(&opt);
0986             QStyle::SubControl hoveredControl =
0987                 this->style()->hitTestComplexControl(
0988                     QStyle::CC_Slider, &opt, helpEvent->pos(), this);
0989             if (!d->m_HandleToolTip.isEmpty() &&
0990                     hoveredControl == QStyle::SC_SliderHandle)
0991             {
0992                 QToolTip::showText(helpEvent->globalPos(), d->m_HandleToolTip.arg(this->minimumValue()));
0993                 _event->accept();
0994                 return true;
0995             }
0996             // Test the MaximumHandle
0997             opt.sliderPosition = d->m_MaximumPosition;
0998             opt.sliderValue = d->m_MaximumValue;
0999             this->initStyleOption(&opt);
1000             hoveredControl = this->style()->hitTestComplexControl(
1001                                  QStyle::CC_Slider, &opt, helpEvent->pos(), this);
1002             if (!d->m_HandleToolTip.isEmpty() &&
1003                     hoveredControl == QStyle::SC_SliderHandle)
1004             {
1005                 QToolTip::showText(helpEvent->globalPos(), d->m_HandleToolTip.arg(this->maximumValue()));
1006                 _event->accept();
1007                 return true;
1008             }
1009             // Test the MidHandle
1010             opt.sliderPosition = d->m_MidPosition;
1011             opt.sliderValue = d->m_MidValue;
1012             this->initStyleOption(&opt);
1013             hoveredControl = this->style()->hitTestComplexControl(
1014                                  QStyle::CC_Slider, &opt, helpEvent->pos(), this);
1015             if (!d->m_HandleToolTip.isEmpty() &&
1016                     hoveredControl == QStyle::SC_SliderHandle)
1017             {
1018                 QToolTip::showText(helpEvent->globalPos(), d->m_HandleToolTip.arg(this->midValue()));
1019                 _event->accept();
1020                 return true;
1021             }
1022         }
1023         default:
1024             break;
1025     }
1026     return this->Superclass::event(_event);
1027 }