#include "lts_range_slider.h" #include #include #include #include #include #include #include namespace LtsWidgets { class LtsRangeSliderPrivate { Q_DECLARE_PUBLIC(LtsRangeSlider) protected: LtsRangeSlider *const q_ptr; public: enum Handle { NoHandle = 0x0, MinimumHandle = 0x1, MaximumHandle = 0x2 }; Q_DECLARE_FLAGS(Handles, Handle) explicit LtsRangeSliderPrivate(LtsRangeSlider &object); void init(); Handle handleAtPos(const QPoint &pos, QRect &handleRect) const; int pixelPosToRangeValue(int pos) const; int pixelPosFromRangeValue(int val) const; void drawMinimumSlider(QStylePainter *painter) const; void drawMaximumSlider(QStylePainter *painter) const; int m_minimumValue = 0; int m_maximumValue = 100; int m_minimumPosition = 0; int m_maximumPosition = 100; QStyle::SubControl m_minimumSliderSelected = QStyle::SC_None; QStyle::SubControl m_maximumSliderSelected = QStyle::SC_None; int m_subclassClickOffset = 0; int m_subclassPosition = 0; float m_subclassWidth = 0; LtsRangeSliderPrivate::Handles m_selectedHandles = {}; bool m_symmetricMoves = false; QString m_handleToolTip; private: Q_DISABLE_COPY(LtsRangeSliderPrivate) }; LtsRangeSliderPrivate::LtsRangeSliderPrivate(LtsRangeSlider &object) : q_ptr(&object) {} void LtsRangeSliderPrivate::init() { Q_Q(LtsRangeSlider); m_minimumValue = q->minimum(); m_maximumValue = q->maximum(); m_minimumPosition = q->minimum(); m_maximumPosition = q->maximum(); q->connect(q, &LtsRangeSlider::rangeChanged, q, &LtsRangeSlider::onRangeChanged); } LtsRangeSliderPrivate::Handle LtsRangeSliderPrivate::handleAtPos(const QPoint &pos, QRect &handleRect)const { Q_Q(const LtsRangeSlider); QStyleOptionSlider option; q->initStyleOption(&option); option.sliderPosition = m_minimumPosition; option.sliderValue = m_minimumValue; QStyle::SubControl minimumControl = q->style()->hitTestComplexControl( QStyle::CC_Slider, &option, pos, q); QRect minimumHandleRect = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); option.sliderPosition = m_maximumPosition; option.sliderValue = m_maximumValue; QStyle::SubControl maximumControl = q->style()->hitTestComplexControl(QStyle::CC_Slider, &option, pos, q); QRect maximumHandleRect = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); if (minimumControl == QStyle::SC_SliderHandle && maximumControl == QStyle::SC_SliderHandle) { int minDist = 0; int maxDist = 0; if (q->orientation() == Qt::Horizontal) { minDist = pos.x() - minimumHandleRect.left(); maxDist = maximumHandleRect.right() - pos.x(); } else { minDist = minimumHandleRect.bottom() - pos.y(); maxDist = pos.y() - maximumHandleRect.top(); } Q_ASSERT(minDist >= 0 && maxDist >= 0); minimumControl = minDist < maxDist ? minimumControl : QStyle::SC_None; } if (maximumControl == QStyle::SC_SliderHandle) { handleRect = maximumHandleRect; return MaximumHandle; } else if (minimumControl == QStyle::SC_SliderHandle) { handleRect = minimumHandleRect; return MinimumHandle; } handleRect = minimumHandleRect.united(maximumHandleRect); return NoHandle; } int LtsRangeSliderPrivate::pixelPosToRangeValue(int pos) const { Q_Q(const LtsRangeSlider); QStyleOptionSlider option; q->initStyleOption(&option); QRect gr = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, q); QRect sr = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); 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(q->minimum(), q->maximum(), pos - sliderMin, sliderMax - sliderMin, option.upsideDown); } int LtsRangeSliderPrivate::pixelPosFromRangeValue(int val) const { Q_Q(const LtsRangeSlider); QStyleOptionSlider option; q->initStyleOption(&option); QRect gr = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, q); QRect sr = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); 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(q->minimum(), q->maximum(), val, sliderMax - sliderMin, option.upsideDown) + sliderMin; } void LtsRangeSliderPrivate::drawMinimumSlider(QStylePainter *painter) const { Q_Q(const LtsRangeSlider); QStyleOptionSlider option; q->initMinimumSliderStyleOption(&option); option.subControls = QStyle::SC_SliderHandle; option.sliderValue = m_minimumValue; option.sliderPosition = m_minimumPosition; if (q->isMinimumSliderDown()) { option.activeSubControls = QStyle::SC_SliderHandle; option.state |= QStyle::State_Sunken; } #ifdef Q_OS_MAC QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); painter->setClipRect(clip); #endif painter->drawComplexControl(QStyle::CC_Slider, option); } void LtsRangeSliderPrivate::drawMaximumSlider(QStylePainter *painter) const { Q_Q(const LtsRangeSlider); QStyleOptionSlider option; q->initMaximumSliderStyleOption(&option); option.subControls = QStyle::SC_SliderHandle; option.sliderValue = m_maximumValue; option.sliderPosition = m_maximumPosition; if (q->isMaximumSliderDown()) { option.activeSubControls = QStyle::SC_SliderHandle; option.state |= QStyle::State_Sunken; } #ifdef Q_OS_MAC QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); painter->setClipRect(clip); #endif painter->drawComplexControl(QStyle::CC_Slider, option); } LtsRangeSlider::LtsRangeSlider(Qt::Orientation o, QWidget *parent) : QSlider(o, parent) , d_ptr(new LtsRangeSliderPrivate(*this)) { Q_D(LtsRangeSlider); d->init(); } LtsRangeSlider::LtsRangeSlider(LtsRangeSliderPrivate *impl, Qt::Orientation o, QWidget *parent) : QSlider(o, parent) , d_ptr(impl) { Q_D(LtsRangeSlider); d->init(); } LtsRangeSlider::~LtsRangeSlider() { } int LtsRangeSlider::minimumValue() const { Q_D(const LtsRangeSlider); return d->m_minimumValue; } void LtsRangeSlider::setMinimumValue(int min) { Q_D(LtsRangeSlider); setValues(min, qMax(d->m_maximumValue, min)); } int LtsRangeSlider::maximumValue() const { Q_D(const LtsRangeSlider); return d->m_maximumValue; } void LtsRangeSlider::setMaximumValue(int max) { Q_D(LtsRangeSlider); setValues(qMin(d->m_minimumValue, max), max); } void LtsRangeSlider::setValues(int min, int max) { Q_D(LtsRangeSlider); const int minValue = qBound(minimum(), qMin(min, max), maximum()); const int maxValue = qBound(minimum(), qMax(min, max), maximum()); bool emitMinValChanged = (minValue != d->m_minimumValue); bool emitMaxValChanged = (maxValue != d->m_maximumValue); d->m_minimumValue = minValue; d->m_maximumValue = maxValue; bool emitMinPosChanged = (minValue != d->m_minimumPosition); bool emitMaxPosChanged = (maxValue != d->m_maximumPosition); d->m_minimumPosition = minValue; d->m_maximumPosition = maxValue; if (isSliderDown()) { if (emitMinPosChanged || emitMaxPosChanged) emit positionsChanged(d->m_minimumPosition, d->m_maximumPosition); if (emitMinPosChanged) emit minimumPositionChanged(d->m_minimumPosition); if (emitMaxPosChanged) emit maximumPositionChanged(d->m_maximumPosition); } if (emitMinValChanged || emitMaxValChanged) emit valuesChanged(d->m_minimumValue, d->m_maximumValue); if (emitMinValChanged) emit minimumValueChanged(d->m_minimumValue); if (emitMaxValChanged) emit maximumValueChanged(d->m_maximumValue); if (emitMinPosChanged || emitMaxPosChanged || emitMinValChanged || emitMaxValChanged) update(); } int LtsRangeSlider::minimumPosition() const { Q_D(const LtsRangeSlider); return d->m_minimumPosition; } int LtsRangeSlider::maximumPosition() const { Q_D(const LtsRangeSlider); return d->m_maximumPosition; } void LtsRangeSlider::setMinimumPosition(int min) { Q_D(const LtsRangeSlider); setPositions(min, qMax(min, d->m_maximumPosition)); } void LtsRangeSlider::setMaximumPosition(int max) { Q_D(const LtsRangeSlider); setPositions(qMin(d->m_minimumPosition, max), max); } void LtsRangeSlider::setPositions(int min, int max) { Q_D(LtsRangeSlider); const int minPosition = qBound(minimum(), qMin(min, max), maximum()); const int maxPosition = qBound(minimum(), qMax(min, max), maximum()); bool emitMinPosChanged = (minPosition != d->m_minimumPosition); bool emitMaxPosChanged = (maxPosition != d->m_maximumPosition); if (!emitMinPosChanged && !emitMaxPosChanged) return; d->m_minimumPosition = minPosition; d->m_maximumPosition = maxPosition; if (!hasTracking()) update(); if (isSliderDown()) { if (emitMinPosChanged) emit minimumPositionChanged(d->m_minimumPosition); if (emitMaxPosChanged) emit maximumPositionChanged(d->m_maximumPosition); if (emitMinPosChanged || emitMaxPosChanged) emit positionsChanged(d->m_minimumPosition, d->m_maximumPosition); } if (hasTracking()) { triggerAction(SliderMove); setValues(d->m_minimumPosition, d->m_maximumPosition); } } void LtsRangeSlider::setSymmetricMoves(bool symmetry) { Q_D(LtsRangeSlider); d->m_symmetricMoves = symmetry; } bool LtsRangeSlider::symmetricMoves() const { Q_D(const LtsRangeSlider); return d->m_symmetricMoves; } void LtsRangeSlider::onRangeChanged(int minimum, int maximum) { Q_UNUSED(minimum); Q_UNUSED(maximum); Q_D(LtsRangeSlider); setValues(d->m_minimumValue, d->m_maximumValue); } void LtsRangeSlider::paintEvent(QPaintEvent *) { Q_D(LtsRangeSlider); QStyleOptionSlider option; initStyleOption(&option); QStylePainter painter(this); painter.setPen(Qt::lightGray); if (option.orientation == Qt::Horizontal) { painter.drawLine(option.rect.left(), option.rect.top(), option.rect.left(), option.rect.bottom()); painter.drawRect(option.rect.left(), option.rect.center().y() - 1, option.rect.right() - option.rect.left(), 3); painter.drawLine(option.rect.right(), option.rect.top(), option.rect.right(), option.rect.bottom()); } else { painter.drawLine(option.rect.left(), option.rect.top(), option.rect.right(), option.rect.top()); painter.drawRect(option.rect.center().x() - 1, option.rect.top(), 3, option.rect.top() - option.rect.bottom()); painter.drawLine(option.rect.center().x(), option.rect.top(), option.rect.center().x(), option.rect.bottom()); painter.drawLine(option.rect.left(), option.rect.bottom(), option.rect.right(), option.rect.bottom()); } option.subControls = QStyle::SC_SliderGroove; option.sliderValue = minimum() - maximum(); option.sliderPosition = minimum() - maximum(); option.subControls &= ~QStyle::SC_SliderGroove; painter.drawComplexControl(QStyle::CC_Slider, option); option.subControls |= QStyle::SC_SliderGroove; option.sliderPosition = d->m_minimumPosition; const QRect lr = style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, this); option.sliderPosition = d->m_maximumPosition; const QRect ur = style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, this); QRect sr = style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this); QRect rangeBox; if (option.orientation == Qt::Horizontal) { 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)); } else { rangeBox = QRect(QPoint(sr.center().x() - 2, qMin(lr.center().y(), ur.center().y())), QPoint(sr.center().x() + 1, qMax(lr.center().y(), ur.center().y()))); } QRect groove = style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this); groove.adjust(0, 0, -1, 0); QColor highlight = palette().color(QPalette::Normal, QPalette::Highlight); QLinearGradient gradient; if (option.orientation == Qt::Horizontal) gradient = QLinearGradient(groove.center().x(), groove.top(), groove.center().x(), groove.bottom()); else gradient = QLinearGradient(groove.left(), groove.center().y(), groove.right(), groove.center().y()); gradient.setColorAt(0, highlight.darker(120)); gradient.setColorAt(1, highlight.lighter(160)); painter.setPen(QPen(highlight.darker(150), 0)); painter.setBrush(gradient); painter.drawRect(rangeBox.intersected(groove)); if (isMinimumSliderDown()) { painter.setClipRect(ur); d->drawMaximumSlider(&painter); painter.setClipRect(lr); d->drawMinimumSlider(&painter); } else { painter.setClipRect(lr); d->drawMinimumSlider(&painter); painter.setClipRect(ur); d->drawMaximumSlider(&painter); } } void LtsRangeSlider::mousePressEvent(QMouseEvent *ev) { Q_D(LtsRangeSlider); if (minimum() == maximum() || (ev->buttons() ^ ev->button())) { ev->ignore(); return; } int mepos = orientation() == Qt::Horizontal ? ev->pos().x() : ev->pos().y(); QStyleOptionSlider option; initStyleOption(&option); QRect handleRect; LtsRangeSliderPrivate::Handle handle_ = d->handleAtPos(ev->pos(), handleRect); if (handle_ != LtsRangeSliderPrivate::NoHandle) { if (ev->buttons() == Qt::RightButton) { d->m_subclassPosition = (d->m_minimumPosition + d->m_maximumPosition) / 2.; d->m_subclassClickOffset = mepos - d->pixelPosFromRangeValue(d->m_subclassPosition); d->m_subclassWidth = (d->m_maximumPosition - d->m_minimumPosition) / 2.; setSliderDown(true); if (!isMinimumSliderDown() || !isMaximumSliderDown()) { d->m_selectedHandles = QFlags(LtsRangeSliderPrivate::MinimumHandle) | QFlags(LtsRangeSliderPrivate::MaximumHandle); QRect sr = style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this); update(handleRect.united(sr)); } ev->accept(); return; } d->m_subclassPosition = (handle_ == LtsRangeSliderPrivate::MinimumHandle) ? d->m_minimumPosition : d->m_maximumPosition; d->m_subclassClickOffset = mepos - (orientation() == Qt::Horizontal ? handleRect.left() : handleRect.top()); setSliderDown(true); if (d->m_selectedHandles != handle_) { d->m_selectedHandles = handle_; update(handleRect); } ev->accept(); return; } QStyle::SubControl control = style()->hitTestComplexControl(QStyle::CC_Slider, &option, ev->pos(), this); QRect sr = style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this); int minCenter = (orientation() == Qt::Horizontal ? handleRect.left() : handleRect.top()); int maxCenter = (orientation() == Qt::Horizontal ? handleRect.right() : handleRect.bottom()); if (control == QStyle::SC_SliderGroove && mepos > minCenter && mepos < maxCenter) { d->m_subclassPosition = (d->m_minimumPosition + d->m_maximumPosition) / 2.; d->m_subclassClickOffset = mepos - d->pixelPosFromRangeValue(d->m_subclassPosition); d->m_subclassWidth = (d->m_maximumPosition - d->m_minimumPosition) / 2.; setSliderDown(true); if (!isMinimumSliderDown() || !isMaximumSliderDown()) { d->m_selectedHandles = QFlags(LtsRangeSliderPrivate::MinimumHandle) | QFlags(LtsRangeSliderPrivate::MaximumHandle); update(handleRect.united(sr)); } ev->accept(); return; } ev->ignore(); } void LtsRangeSlider::mouseMoveEvent(QMouseEvent *ev) { Q_D(LtsRangeSlider); if (!d->m_selectedHandles) { ev->ignore(); return; } int mepos = orientation() == Qt::Horizontal ? ev->pos().x() : ev->pos().y(); QStyleOptionSlider option; initStyleOption(&option); if (isMinimumSliderDown() && isMaximumSliderDown()) { int newPosition = d->pixelPosToRangeValue(mepos); int newMinPosition = newPosition - static_cast(d->m_subclassWidth); int newMaxPosition = newPosition + static_cast(d->m_subclassWidth + .5); if (newMinPosition < minimum()) { newMinPosition = minimum(); newMaxPosition = newMinPosition + (d->m_maximumPosition - d->m_minimumPosition); } else if (newMaxPosition > maximum()) { newMaxPosition = maximum(); newMinPosition = newMaxPosition - (d->m_maximumPosition - d->m_minimumPosition); } setPositions(newMinPosition, newMaxPosition); ev->accept(); return; } const int m = style()->pixelMetric(QStyle::PM_MaximumDragDistance, &option, this); int newPosition = d->pixelPosToRangeValue(mepos - d->m_subclassClickOffset); if (m >= 0) { const QRect r = rect().adjusted(-m, -m, m, m); if (!r.contains(ev->pos())) newPosition = d->m_subclassPosition; } if (isMinimumSliderDown() && !isMaximumSliderDown()) { double newMinPos = qMin(newPosition, d->m_maximumPosition); setPositions(newMinPos, d->m_maximumPosition + (d->m_symmetricMoves ? d->m_minimumPosition - newMinPos : 0)); } else if (isMaximumSliderDown() && !isMinimumSliderDown()) { double newMaxPos = qMax(d->m_minimumPosition, newPosition); setPositions(d->m_minimumPosition - (d->m_symmetricMoves ? newMaxPos - d->m_maximumPosition : 0), newMaxPos); } ev->accept(); } void LtsRangeSlider::mouseReleaseEvent(QMouseEvent *ev) { Q_D(LtsRangeSlider); QSlider::mouseReleaseEvent(ev); setSliderDown(false); d->m_selectedHandles = {}; update(); } bool LtsRangeSlider::isMinimumSliderDown() const { Q_D(const LtsRangeSlider); return d->m_selectedHandles & LtsRangeSliderPrivate::MinimumHandle; } bool LtsRangeSlider::isMaximumSliderDown() const { Q_D(const LtsRangeSlider); return d->m_selectedHandles & LtsRangeSliderPrivate::MaximumHandle; } void LtsRangeSlider::initMinimumSliderStyleOption(QStyleOptionSlider *option) const { initStyleOption(option); } void LtsRangeSlider::initMaximumSliderStyleOption(QStyleOptionSlider *option) const { initStyleOption(option); } QString LtsRangeSlider::handleToolTip()const { Q_D(const LtsRangeSlider); return d->m_handleToolTip; } void LtsRangeSlider::setHandleToolTip(const QString &toolTip) { Q_D(LtsRangeSlider); d->m_handleToolTip = toolTip; } bool LtsRangeSlider::event(QEvent *event) { Q_D(LtsRangeSlider); switch (event->type()) { case QEvent::ToolTip: { QHelpEvent *helpEvent = static_cast(event); QStyleOptionSlider opt; opt.sliderPosition = d->m_minimumPosition; opt.sliderValue = d->m_minimumValue; initStyleOption(&opt); QStyle::SubControl hoveredControl = style()->hitTestComplexControl(QStyle::CC_Slider, &opt, helpEvent->pos(), this); if (!d->m_handleToolTip.isEmpty() && hoveredControl == QStyle::SC_SliderHandle) { QToolTip::showText(helpEvent->globalPos(), d->m_handleToolTip.arg(minimumValue())); event->accept(); return true; } opt.sliderPosition = d->m_maximumPosition; opt.sliderValue = d->m_maximumValue; initStyleOption(&opt); hoveredControl = style()->hitTestComplexControl(QStyle::CC_Slider, &opt, helpEvent->pos(), this); if (!d->m_handleToolTip.isEmpty() && hoveredControl == QStyle::SC_SliderHandle) { QToolTip::showText(helpEvent->globalPos(), d->m_handleToolTip.arg(maximumValue())); event->accept(); return true; } } default: break; } return QSlider::event(event); } }