+/****************************************************************************
+**
+** Copyright (C) Qxt Foundation. Some rights reserved.
+**
+** This file is part of the QxtGui module of the Qt eXTension library
+**
+** This library is free software; you can redistribute it and/or modify it
+** under the terms of th Common Public License, version 1.0, as published by
+** IBM.
+**
+** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
+** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
+** FITNESS FOR A PARTICULAR PURPOSE.
+**
+** You should have received a copy of the CPL along with this file.
+** See the LICENSE file and the cpl1.0.txt file included with the source
+** distribution for more information. If you did not receive a copy of the
+** license, contact the Qxt Foundation.
+**
+** <http://libqxt.sourceforge.net> <foundation@libqxt.org>
+**
+****************************************************************************/
+#include "qxtspanslider.h"
+#include "qxtspanslider_p.h"
+#include <QKeyEvent>
+#include <QMouseEvent>
+#include <QApplication>
+#include <QStylePainter>
+#include <QStyleOptionSlider>
+
+QxtSpanSliderPrivate::QxtSpanSliderPrivate()
+ : lower(0),
+ upper(0),
+ offset(0),
+ position(0),
+ lastPressed(NoHandle),
+ mainControl(LowerHandle),
+ lowerPressed(QStyle::SC_None),
+ upperPressed(QStyle::SC_None)
+{}
+
+// TODO: get rid of this in Qt 4.3
+void QxtSpanSliderPrivate::initStyleOption(QStyleOptionSlider* option, SpanHandle handle) const
+{
+ if (!option)
+ return;
+
+ const QSlider* p = &qxt_p();
+ option->initFrom(p);
+ option->subControls = QStyle::SC_None;
+ option->activeSubControls = QStyle::SC_None;
+ option->orientation = p->orientation();
+ option->maximum = p->maximum();
+ option->minimum = p->minimum();
+ option->tickPosition = p->tickPosition();
+ option->tickInterval = p->tickInterval();
+ option->upsideDown = (p->orientation() == Qt::Horizontal) ?
+ (p->invertedAppearance() != (option->direction == Qt::RightToLeft)) : (!p->invertedAppearance());
+ option->direction = Qt::LeftToRight; // we use the upsideDown option instead
+ option->sliderPosition = (handle == LowerHandle ? lower : upper);
+ option->sliderValue = (handle == LowerHandle ? lower : upper);
+ option->singleStep = p->singleStep();
+ option->pageStep = p->pageStep();
+ if (p->orientation() == Qt::Horizontal)
+ option->state |= QStyle::State_Horizontal;
+}
+
+int QxtSpanSliderPrivate::pixelPosToRangeValue(int pos) const
+{
+ QStyleOptionSlider opt;
+ initStyleOption(&opt);
+
+ int sliderMin = 0;
+ int sliderMax = 0;
+ int sliderLength = 0;
+ const QSlider* p = &qxt_p();
+ const QRect gr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, p);
+ const QRect sr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, p);
+ if (p->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(), pos - sliderMin,
+ sliderMax - sliderMin, opt.upsideDown);
+}
+
+void QxtSpanSliderPrivate::handleMousePress(const QPoint& pos, QStyle::SubControl& control, int value, SpanHandle handle)
+{
+ QStyleOptionSlider opt;
+ initStyleOption(&opt, handle);
+ QSlider* p = &qxt_p();
+ const QStyle::SubControl oldControl = control;
+ control = p->style()->hitTestComplexControl(QStyle::CC_Slider, &opt, pos, p);
+ const QRect sr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, p);
+ if (control == QStyle::SC_SliderHandle)
+ {
+ position = value;
+ offset = pick(pos - sr.topLeft());
+ lastPressed = handle;
+ p->setSliderDown(true);
+ }
+ if (control != oldControl)
+ p->update(sr);
+}
+
+void QxtSpanSliderPrivate::setupPainter(QPainter* painter, Qt::Orientation orientation, qreal x1, qreal y1, qreal x2, qreal y2) const
+{
+ QColor highlight = qxt_p().palette().color(QPalette::Highlight);
+ QLinearGradient gradient(x1, y1, x2, y2);
+ gradient.setColorAt(0, highlight.dark(120));
+ gradient.setColorAt(1, highlight.light(108));
+ painter->setBrush(gradient);
+
+ if (orientation == Qt::Horizontal)
+ painter->setPen(QPen(highlight.dark(130), 0));
+ else
+ painter->setPen(QPen(highlight.dark(150), 0));
+}
+
+void QxtSpanSliderPrivate::drawSpan(QStylePainter* painter, const QRect& rect) const
+{
+ QStyleOptionSlider opt;
+ initStyleOption(&opt);
+ const QSlider* p = &qxt_p();
+
+ // area
+ QRect groove = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, p);
+ if (opt.orientation == Qt::Horizontal)
+ groove.adjust(0, 0, -1, 0);
+ else
+ groove.adjust(0, 0, 0, -1);
+
+ // pen & brush
+ painter->setPen(QPen(p->palette().color(QPalette::Dark).light(110), 0));
+ if (opt.orientation == Qt::Horizontal)
+ setupPainter(painter, opt.orientation, groove.center().x(), groove.top(), groove.center().x(), groove.bottom());
+ else
+ setupPainter(painter, opt.orientation, groove.left(), groove.center().y(), groove.right(), groove.center().y());
+
+ // draw groove
+#if QT_VERSION >= 0x040200
+ painter->drawRect(rect.intersected(groove));
+#else // QT_VERSION < 0x040200
+ painter->drawRect(rect.intersect(groove));
+#endif // QT_VERSION
+}
+
+void QxtSpanSliderPrivate::drawHandle(QStylePainter* painter, SpanHandle handle) const
+{
+ QStyleOptionSlider opt;
+ initStyleOption(&opt, handle);
+ opt.subControls = QStyle::SC_SliderHandle;
+ QStyle::SubControl pressed = (handle == LowerHandle ? lowerPressed : upperPressed);
+ if (pressed == QStyle::SC_SliderHandle)
+ {
+ opt.activeSubControls = pressed;
+ opt.state |= QStyle::State_Sunken;
+ }
+ painter->drawComplexControl(QStyle::CC_Slider, opt);
+}
+
+void QxtSpanSliderPrivate::triggerAction(QAbstractSlider::SliderAction action, bool main)
+{
+ int value = 0;
+ bool up = false;
+ const int min = qxt_p().minimum();
+ const int max = qxt_p().maximum();
+ const SpanHandle altControl = (mainControl == LowerHandle ? UpperHandle : LowerHandle);
+ switch (action)
+ {
+ case QAbstractSlider::SliderSingleStepAdd:
+ if ((main && mainControl == UpperHandle) || (!main && altControl == UpperHandle))
+ {
+ value = qBound(min, upper + qxt_p().singleStep(), max);
+ up = true;
+ break;
+ }
+ value = qBound(min, lower + qxt_p().singleStep(), max);
+ break;
+ case QAbstractSlider::SliderSingleStepSub:
+ if ((main && mainControl == UpperHandle) || (!main && altControl == UpperHandle))
+ {
+ value = qBound(min, upper - qxt_p().singleStep(), max);
+ up = true;
+ break;
+ }
+ value = qBound(min, lower - qxt_p().singleStep(), max);
+ break;
+ case QAbstractSlider::SliderToMinimum:
+ value = min;
+ if ((main && mainControl == UpperHandle) || (!main && altControl == UpperHandle))
+ up = true;
+ break;
+ case QAbstractSlider::SliderToMaximum:
+ value = max;
+ if ((main && mainControl == UpperHandle) || (!main && altControl == UpperHandle))
+ up = true;
+ break;
+ default:
+ qWarning("QxtSpanSliderPrivate::triggerAction: Unknown action");
+ break;
+ }
+
+ if (!up)
+ {
+ if (value > upper)
+ {
+ swapControls();
+ qxt_p().setUpperValue(value);
+ }
+ else
+ {
+ qxt_p().setLowerValue(value);
+ }
+ }
+ else
+ {
+ if (value < lower)
+ {
+ swapControls();
+ qxt_p().setLowerValue(value);
+ }
+ else
+ {
+ qxt_p().setUpperValue(value);
+ }
+ }
+}
+
+void QxtSpanSliderPrivate::swapControls()
+{
+ qSwap(lower, upper);
+ qSwap(lowerPressed, upperPressed);
+ lastPressed = (lastPressed == LowerHandle ? UpperHandle : LowerHandle);
+ mainControl = (mainControl == LowerHandle ? UpperHandle : LowerHandle);
+}
+
+void QxtSpanSliderPrivate::updateRange(int min, int max)
+{
+ Q_UNUSED(min);
+ Q_UNUSED(max);
+ // setSpan() takes care of keeping span in range
+ qxt_p().setSpan(lower, upper);
+}
+
+/*!
+ \class QxtSpanSlider QxtSpanSlider
+ \ingroup QxtGui
+ \brief A QSlider with two handles.
+
+ QxtSpanSlider is a slider with two handles. QxtSpanSlider is
+ handy for letting user to choose an span between min/max.
+
+ The span color is calculated based on \b QPalette::Highlight.
+
+ The keys are bound according to the following table:
+ <table>
+ <tr><td><b>Orientation</b></td><td><b>Key</b></td><td><b>Handle</b></td></tr>
+ <tr><td>Qt::Horizontal</td><td>Qt::Key_Left</td><td>lower</td></tr>
+ <tr><td>Qt::Horizontal</td><td>Qt::Key_Right</td><td>lower</td></tr>
+ <tr><td>Qt::Horizontal</td><td>Qt::Key_Up</td><td>upper</td></tr>
+ <tr><td>Qt::Horizontal</td><td>Qt::Key_Down</td><td>upper</td></tr>
+ <tr><td>Qt::Vertical</td><td>Qt::Key_Up</td><td>lower</td></tr>
+ <tr><td>Qt::Vertical</td><td>Qt::Key_Down</td><td>lower</td></tr>
+ <tr><td>Qt::Vertical</td><td>Qt::Key_Left</td><td>upper</td></tr>
+ <tr><td>Qt::Vertical</td><td>Qt::Key_Right</td><td>upper</td></tr>
+ </table>
+
+ Keys are bound by the time the slider is created. A key is bound
+ to same handle for the lifetime of the slider. So even if the handle
+ representation might change from lower to upper, the same key binding
+ remains.
+
+ \image html qxtspanslider.png "QxtSpanSlider in Plastique style."
+
+ \note QxtSpanSlider inherits \b QSlider for implementation specific
+ reasons. Adjusting any single handle specific properties like
+ <ul>
+ <li>\b QAbstractSlider::sliderPosition</li>
+ <li>\b QAbstractSlider::value</li>
+ </ul>
+ has no effect. However, all slider specific properties like
+ <ul>
+ <li>\b QAbstractSlider::invertedAppearance</li>
+ <li>\b QAbstractSlider::invertedControls</li>
+ <li>\b QAbstractSlider::minimum</li>
+ <li>\b QAbstractSlider::maximum</li>
+ <li>\b QAbstractSlider::orientation</li>
+ <li>\b QAbstractSlider::pageStep</li>
+ <li>\b QAbstractSlider::singleStep</li>
+ <li>\b QSlider::tickInterval</li>
+ <li>\b QSlider::tickPosition</li>
+ </ul>
+ are taken into consideration.
+ */
+
+/*!
+ \fn QxtSpanSlider::lowerValueChanged(int lower)
+
+ This signal is emitted whenever the lower value has changed.
+ */
+
+/*!
+ \fn QxtSpanSlider::upperValueChanged(int upper)
+
+ This signal is emitted whenever the upper value has changed.
+ */
+
+/*!
+ \fn QxtSpanSlider::spanChanged(int lower, int upper)
+
+ This signal is emitted whenever the span has changed.
+ */
+
+/*!
+ Constructs a new QxtSpanSlider with \a parent.
+ */
+QxtSpanSlider::QxtSpanSlider(QWidget* parent) : QSlider(parent)
+{
+ QXT_INIT_PRIVATE(QxtSpanSlider);
+ connect(this, SIGNAL(rangeChanged(int, int)), &qxt_d(), SLOT(updateRange(int, int)));
+}
+
+/*!
+ Constructs a new QxtSpanSlider with \a orientation and \a parent.
+ */
+QxtSpanSlider::QxtSpanSlider(Qt::Orientation orientation, QWidget* parent) : QSlider(orientation, parent)
+{
+ QXT_INIT_PRIVATE(QxtSpanSlider);
+ connect(this, SIGNAL(rangeChanged(int, int)), &qxt_d(), SLOT(updateRange(int, int)));
+}
+
+/*!
+ Destructs the slider.
+ */
+QxtSpanSlider::~QxtSpanSlider()
+{}
+
+/*!
+ \property QxtSpanSlider::lowerValue
+ \brief This property holds the lower value of the span
+ */
+int QxtSpanSlider::lowerValue() const
+{
+ return qMin(qxt_d().lower, qxt_d().upper);
+}
+
+void QxtSpanSlider::setLowerValue(int lower)
+{
+ setSpan(lower, qxt_d().upper);
+}
+
+/*!
+ \property QxtSpanSlider::upperValue
+ \brief This property holds the upper value of the span
+ */
+int QxtSpanSlider::upperValue() const
+{
+ return qMax(qxt_d().lower, qxt_d().upper);
+}
+
+void QxtSpanSlider::setUpperValue(int upper)
+{
+ setSpan(qxt_d().lower, upper);
+}
+
+/*!
+ Sets the span from \a lower to \a upper.
+ \sa upperValue, lowerValue
+ */
+void QxtSpanSlider::setSpan(int lower, int upper)
+{
+ const int low = qBound(minimum(), qMin(lower, upper), maximum());
+ const int upp = qBound(minimum(), qMax(lower, upper), maximum());
+ if (low != qxt_d().lower || upp != qxt_d().upper)
+ {
+ if (low != qxt_d().lower)
+ {
+ qxt_d().lower = low;
+ emit lowerValueChanged(low);
+ }
+ if (upp != qxt_d().upper)
+ {
+ qxt_d().upper = upp;
+ emit upperValueChanged(upp);
+ }
+ emit spanChanged(qxt_d().lower, qxt_d().upper);
+ update();
+ }
+}
+
+void QxtSpanSlider::keyPressEvent(QKeyEvent* event)
+{
+ QSlider::keyPressEvent(event);
+
+ bool main = true;
+ SliderAction action = SliderNoAction;
+ switch (event->key())
+ {
+ case Qt::Key_Left:
+ main = (orientation() == Qt::Horizontal);
+ action = !invertedAppearance() ? SliderSingleStepSub : SliderSingleStepAdd;
+ break;
+ case Qt::Key_Right:
+ main = (orientation() == Qt::Horizontal);
+ action = !invertedAppearance() ? SliderSingleStepAdd : SliderSingleStepSub;
+ break;
+ case Qt::Key_Up:
+ main = (orientation() == Qt::Vertical);
+ action = invertedControls() ? SliderSingleStepSub : SliderSingleStepAdd;
+ break;
+ case Qt::Key_Down:
+ main = (orientation() == Qt::Vertical);
+ action = invertedControls() ? SliderSingleStepAdd : SliderSingleStepSub;
+ break;
+ case Qt::Key_Home:
+ main = (qxt_d().mainControl == QxtSpanSliderPrivate::LowerHandle);
+ action = SliderToMinimum;
+ break;
+ case Qt::Key_End:
+ main = (qxt_d().mainControl == QxtSpanSliderPrivate::UpperHandle);
+ action = SliderToMaximum;
+ break;
+ default:
+ event->ignore();
+ break;
+ }
+
+ if (action)
+ qxt_d().triggerAction(action, main);
+}
+
+void QxtSpanSlider::mousePressEvent(QMouseEvent* event)
+{
+ if (minimum() == maximum() || (event->buttons() ^ event->button()))
+ {
+ event->ignore();
+ return;
+ }
+
+ qxt_d().handleMousePress(event->pos(), qxt_d().upperPressed, qxt_d().upper, QxtSpanSliderPrivate::UpperHandle);
+ if (qxt_d().upperPressed != QStyle::SC_SliderHandle)
+ qxt_d().handleMousePress(event->pos(), qxt_d().lowerPressed, qxt_d().lower, QxtSpanSliderPrivate::LowerHandle);
+
+ event->accept();
+}
+
+void QxtSpanSlider::mouseMoveEvent(QMouseEvent* event)
+{
+ if (qxt_d().lowerPressed != QStyle::SC_SliderHandle && qxt_d().upperPressed != QStyle::SC_SliderHandle)
+ {
+ event->ignore();
+ return;
+ }
+
+ QStyleOptionSlider opt;
+ qxt_d().initStyleOption(&opt);
+ const int m = style()->pixelMetric(QStyle::PM_MaximumDragDistance, &opt, this);
+ int newPosition = qxt_d().pixelPosToRangeValue(qxt_d().pick(event->pos()) - qxt_d().offset);
+ if (m >= 0)
+ {
+ const QRect r = rect().adjusted(-m, -m, m, m);
+ if (!r.contains(event->pos()))
+ {
+ newPosition = qxt_d().position;
+ }
+ }
+
+ if (qxt_d().lowerPressed == QStyle::SC_SliderHandle)
+ {
+ if (newPosition > qxt_d().upper)
+ {
+ qxt_d().swapControls();
+ setUpperValue(newPosition);
+ }
+ else
+ {
+ setLowerValue(newPosition);
+ }
+ }
+ else if (qxt_d().upperPressed == QStyle::SC_SliderHandle)
+ {
+ if (newPosition < qxt_d().lower)
+ {
+ qxt_d().swapControls();
+ setLowerValue(newPosition);
+ }
+ else
+ {
+ setUpperValue(newPosition);
+ }
+ }
+ event->accept();
+}
+
+void QxtSpanSlider::mouseReleaseEvent(QMouseEvent* event)
+{
+ QSlider::mouseReleaseEvent(event);
+ qxt_d().lowerPressed = QStyle::SC_None;
+ qxt_d().upperPressed = QStyle::SC_None;
+ update();
+}
+
+void QxtSpanSlider::paintEvent(QPaintEvent* event)
+{
+ Q_UNUSED(event);
+ QStylePainter painter(this);
+
+ // ticks
+ QStyleOptionSlider opt;
+ qxt_d().initStyleOption(&opt);
+ opt.subControls = QStyle::SC_SliderTickmarks;
+ painter.drawComplexControl(QStyle::CC_Slider, opt);
+
+ // groove
+ opt.sliderPosition = 0;
+ opt.subControls = QStyle::SC_SliderGroove;
+ painter.drawComplexControl(QStyle::CC_Slider, opt);
+
+ // handle rects
+ opt.sliderPosition = qxt_d().lower;
+ const QRect lr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
+ const int lrv = qxt_d().pick(lr.center());
+ opt.sliderPosition = qxt_d().upper;
+ const QRect ur = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
+ const int urv = qxt_d().pick(ur.center());
+
+ // span
+ const int minv = qMin(lrv, urv);
+ const int maxv = qMax(lrv, urv);
+ const QPoint c = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this).center();
+ QRect spanRect;
+ if (orientation() == Qt::Horizontal)
+ spanRect = QRect(QPoint(minv, c.y()-2), QPoint(maxv, c.y()+1));
+ else
+ spanRect = QRect(QPoint(c.x()-2, minv), QPoint(c.x()+1, maxv));
+ qxt_d().drawSpan(&painter, spanRect);
+
+ // handles
+ switch (qxt_d().lastPressed)
+ {
+ case QxtSpanSliderPrivate::LowerHandle:
+ qxt_d().drawHandle(&painter, QxtSpanSliderPrivate::UpperHandle);
+ qxt_d().drawHandle(&painter, QxtSpanSliderPrivate::LowerHandle);
+ break;
+ case QxtSpanSliderPrivate::UpperHandle:
+ default:
+ qxt_d().drawHandle(&painter, QxtSpanSliderPrivate::LowerHandle);
+ qxt_d().drawHandle(&painter, QxtSpanSliderPrivate::UpperHandle);
+ break;
+ }
+}