We now have a current svn snapshot of libqxt in our contrib dir, and
[quassel.git] / src / contrib / libqxt-2007-10-24 / src / gui / qxtspanslider.cpp
diff --git a/src/contrib/libqxt-2007-10-24/src/gui/qxtspanslider.cpp b/src/contrib/libqxt-2007-10-24/src/gui/qxtspanslider.cpp
new file mode 100644 (file)
index 0000000..9084475
--- /dev/null
@@ -0,0 +1,561 @@
+/****************************************************************************
+**
+** 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;
+    }
+}