1 /****************************************************************************
3 ** Copyright (C) Qxt Foundation. Some rights reserved.
5 ** This file is part of the QxtGui module of the Qt eXTension library
7 ** This library is free software; you can redistribute it and/or modify it
8 ** under the terms of th Common Public License, version 1.0, as published by
11 ** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
12 ** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
13 ** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
14 ** FITNESS FOR A PARTICULAR PURPOSE.
16 ** You should have received a copy of the CPL along with this file.
17 ** See the LICENSE file and the cpl1.0.txt file included with the source
18 ** distribution for more information. If you did not receive a copy of the
19 ** license, contact the Qxt Foundation.
21 ** <http://libqxt.sourceforge.net> <foundation@libqxt.org>
23 ****************************************************************************/
24 #include "qxtspanslider.h"
25 #include "qxtspanslider_p.h"
27 #include <QMouseEvent>
28 #include <QApplication>
29 #include <QStylePainter>
30 #include <QStyleOptionSlider>
32 QxtSpanSliderPrivate::QxtSpanSliderPrivate()
37 lastPressed(NoHandle),
38 mainControl(LowerHandle),
39 lowerPressed(QStyle::SC_None),
40 upperPressed(QStyle::SC_None)
43 // TODO: get rid of this in Qt 4.3
44 void QxtSpanSliderPrivate::initStyleOption(QStyleOptionSlider* option, SpanHandle handle) const
49 const QSlider* p = &qxt_p();
51 option->subControls = QStyle::SC_None;
52 option->activeSubControls = QStyle::SC_None;
53 option->orientation = p->orientation();
54 option->maximum = p->maximum();
55 option->minimum = p->minimum();
56 option->tickPosition = p->tickPosition();
57 option->tickInterval = p->tickInterval();
58 option->upsideDown = (p->orientation() == Qt::Horizontal) ?
59 (p->invertedAppearance() != (option->direction == Qt::RightToLeft)) : (!p->invertedAppearance());
60 option->direction = Qt::LeftToRight; // we use the upsideDown option instead
61 option->sliderPosition = (handle == LowerHandle ? lower : upper);
62 option->sliderValue = (handle == LowerHandle ? lower : upper);
63 option->singleStep = p->singleStep();
64 option->pageStep = p->pageStep();
65 if (p->orientation() == Qt::Horizontal)
66 option->state |= QStyle::State_Horizontal;
69 int QxtSpanSliderPrivate::pixelPosToRangeValue(int pos) const
71 QStyleOptionSlider opt;
72 initStyleOption(&opt);
77 const QSlider* p = &qxt_p();
78 const QRect gr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, p);
79 const QRect sr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, p);
80 if (p->orientation() == Qt::Horizontal)
82 sliderLength = sr.width();
84 sliderMax = gr.right() - sliderLength + 1;
88 sliderLength = sr.height();
90 sliderMax = gr.bottom() - sliderLength + 1;
92 return QStyle::sliderValueFromPosition(p->minimum(), p->maximum(), pos - sliderMin,
93 sliderMax - sliderMin, opt.upsideDown);
96 void QxtSpanSliderPrivate::handleMousePress(const QPoint& pos, QStyle::SubControl& control, int value, SpanHandle handle)
98 QStyleOptionSlider opt;
99 initStyleOption(&opt, handle);
100 QSlider* p = &qxt_p();
101 const QStyle::SubControl oldControl = control;
102 control = p->style()->hitTestComplexControl(QStyle::CC_Slider, &opt, pos, p);
103 const QRect sr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, p);
104 if (control == QStyle::SC_SliderHandle)
107 offset = pick(pos - sr.topLeft());
108 lastPressed = handle;
109 p->setSliderDown(true);
111 if (control != oldControl)
115 void QxtSpanSliderPrivate::setupPainter(QPainter* painter, Qt::Orientation orientation, qreal x1, qreal y1, qreal x2, qreal y2) const
117 QColor highlight = qxt_p().palette().color(QPalette::Highlight);
118 QLinearGradient gradient(x1, y1, x2, y2);
119 gradient.setColorAt(0, highlight.dark(120));
120 gradient.setColorAt(1, highlight.light(108));
121 painter->setBrush(gradient);
123 if (orientation == Qt::Horizontal)
124 painter->setPen(QPen(highlight.dark(130), 0));
126 painter->setPen(QPen(highlight.dark(150), 0));
129 void QxtSpanSliderPrivate::drawSpan(QStylePainter* painter, const QRect& rect) const
131 QStyleOptionSlider opt;
132 initStyleOption(&opt);
133 const QSlider* p = &qxt_p();
136 QRect groove = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, p);
137 if (opt.orientation == Qt::Horizontal)
138 groove.adjust(0, 0, -1, 0);
140 groove.adjust(0, 0, 0, -1);
143 painter->setPen(QPen(p->palette().color(QPalette::Dark).light(110), 0));
144 if (opt.orientation == Qt::Horizontal)
145 setupPainter(painter, opt.orientation, groove.center().x(), groove.top(), groove.center().x(), groove.bottom());
147 setupPainter(painter, opt.orientation, groove.left(), groove.center().y(), groove.right(), groove.center().y());
150 #if QT_VERSION >= 0x040200
151 painter->drawRect(rect.intersected(groove));
152 #else // QT_VERSION < 0x040200
153 painter->drawRect(rect.intersect(groove));
157 void QxtSpanSliderPrivate::drawHandle(QStylePainter* painter, SpanHandle handle) const
159 QStyleOptionSlider opt;
160 initStyleOption(&opt, handle);
161 opt.subControls = QStyle::SC_SliderHandle;
162 QStyle::SubControl pressed = (handle == LowerHandle ? lowerPressed : upperPressed);
163 if (pressed == QStyle::SC_SliderHandle)
165 opt.activeSubControls = pressed;
166 opt.state |= QStyle::State_Sunken;
168 painter->drawComplexControl(QStyle::CC_Slider, opt);
171 void QxtSpanSliderPrivate::triggerAction(QAbstractSlider::SliderAction action, bool main)
175 const int min = qxt_p().minimum();
176 const int max = qxt_p().maximum();
177 const SpanHandle altControl = (mainControl == LowerHandle ? UpperHandle : LowerHandle);
180 case QAbstractSlider::SliderSingleStepAdd:
181 if ((main && mainControl == UpperHandle) || (!main && altControl == UpperHandle))
183 value = qBound(min, upper + qxt_p().singleStep(), max);
187 value = qBound(min, lower + qxt_p().singleStep(), max);
189 case QAbstractSlider::SliderSingleStepSub:
190 if ((main && mainControl == UpperHandle) || (!main && altControl == UpperHandle))
192 value = qBound(min, upper - qxt_p().singleStep(), max);
196 value = qBound(min, lower - qxt_p().singleStep(), max);
198 case QAbstractSlider::SliderToMinimum:
200 if ((main && mainControl == UpperHandle) || (!main && altControl == UpperHandle))
203 case QAbstractSlider::SliderToMaximum:
205 if ((main && mainControl == UpperHandle) || (!main && altControl == UpperHandle))
209 qWarning("QxtSpanSliderPrivate::triggerAction: Unknown action");
218 qxt_p().setUpperValue(value);
222 qxt_p().setLowerValue(value);
230 qxt_p().setLowerValue(value);
234 qxt_p().setUpperValue(value);
239 void QxtSpanSliderPrivate::swapControls()
242 qSwap(lowerPressed, upperPressed);
243 lastPressed = (lastPressed == LowerHandle ? UpperHandle : LowerHandle);
244 mainControl = (mainControl == LowerHandle ? UpperHandle : LowerHandle);
247 void QxtSpanSliderPrivate::updateRange(int min, int max)
251 // setSpan() takes care of keeping span in range
252 qxt_p().setSpan(lower, upper);
256 \class QxtSpanSlider QxtSpanSlider
258 \brief A QSlider with two handles.
260 QxtSpanSlider is a slider with two handles. QxtSpanSlider is
261 handy for letting user to choose an span between min/max.
263 The span color is calculated based on \b QPalette::Highlight.
265 The keys are bound according to the following table:
267 <tr><td><b>Orientation</b></td><td><b>Key</b></td><td><b>Handle</b></td></tr>
268 <tr><td>Qt::Horizontal</td><td>Qt::Key_Left</td><td>lower</td></tr>
269 <tr><td>Qt::Horizontal</td><td>Qt::Key_Right</td><td>lower</td></tr>
270 <tr><td>Qt::Horizontal</td><td>Qt::Key_Up</td><td>upper</td></tr>
271 <tr><td>Qt::Horizontal</td><td>Qt::Key_Down</td><td>upper</td></tr>
272 <tr><td>Qt::Vertical</td><td>Qt::Key_Up</td><td>lower</td></tr>
273 <tr><td>Qt::Vertical</td><td>Qt::Key_Down</td><td>lower</td></tr>
274 <tr><td>Qt::Vertical</td><td>Qt::Key_Left</td><td>upper</td></tr>
275 <tr><td>Qt::Vertical</td><td>Qt::Key_Right</td><td>upper</td></tr>
278 Keys are bound by the time the slider is created. A key is bound
279 to same handle for the lifetime of the slider. So even if the handle
280 representation might change from lower to upper, the same key binding
283 \image html qxtspanslider.png "QxtSpanSlider in Plastique style."
285 \note QxtSpanSlider inherits \b QSlider for implementation specific
286 reasons. Adjusting any single handle specific properties like
288 <li>\b QAbstractSlider::sliderPosition</li>
289 <li>\b QAbstractSlider::value</li>
291 has no effect. However, all slider specific properties like
293 <li>\b QAbstractSlider::invertedAppearance</li>
294 <li>\b QAbstractSlider::invertedControls</li>
295 <li>\b QAbstractSlider::minimum</li>
296 <li>\b QAbstractSlider::maximum</li>
297 <li>\b QAbstractSlider::orientation</li>
298 <li>\b QAbstractSlider::pageStep</li>
299 <li>\b QAbstractSlider::singleStep</li>
300 <li>\b QSlider::tickInterval</li>
301 <li>\b QSlider::tickPosition</li>
303 are taken into consideration.
307 \fn QxtSpanSlider::lowerValueChanged(int lower)
309 This signal is emitted whenever the lower value has changed.
313 \fn QxtSpanSlider::upperValueChanged(int upper)
315 This signal is emitted whenever the upper value has changed.
319 \fn QxtSpanSlider::spanChanged(int lower, int upper)
321 This signal is emitted whenever the span has changed.
325 Constructs a new QxtSpanSlider with \a parent.
327 QxtSpanSlider::QxtSpanSlider(QWidget* parent) : QSlider(parent)
329 QXT_INIT_PRIVATE(QxtSpanSlider);
330 connect(this, SIGNAL(rangeChanged(int, int)), &qxt_d(), SLOT(updateRange(int, int)));
334 Constructs a new QxtSpanSlider with \a orientation and \a parent.
336 QxtSpanSlider::QxtSpanSlider(Qt::Orientation orientation, QWidget* parent) : QSlider(orientation, parent)
338 QXT_INIT_PRIVATE(QxtSpanSlider);
339 connect(this, SIGNAL(rangeChanged(int, int)), &qxt_d(), SLOT(updateRange(int, int)));
343 Destructs the slider.
345 QxtSpanSlider::~QxtSpanSlider()
349 \property QxtSpanSlider::lowerValue
350 \brief This property holds the lower value of the span
352 int QxtSpanSlider::lowerValue() const
354 return qMin(qxt_d().lower, qxt_d().upper);
357 void QxtSpanSlider::setLowerValue(int lower)
359 setSpan(lower, qxt_d().upper);
363 \property QxtSpanSlider::upperValue
364 \brief This property holds the upper value of the span
366 int QxtSpanSlider::upperValue() const
368 return qMax(qxt_d().lower, qxt_d().upper);
371 void QxtSpanSlider::setUpperValue(int upper)
373 setSpan(qxt_d().lower, upper);
377 Sets the span from \a lower to \a upper.
378 \sa upperValue, lowerValue
380 void QxtSpanSlider::setSpan(int lower, int upper)
382 const int low = qBound(minimum(), qMin(lower, upper), maximum());
383 const int upp = qBound(minimum(), qMax(lower, upper), maximum());
384 if (low != qxt_d().lower || upp != qxt_d().upper)
386 if (low != qxt_d().lower)
389 emit lowerValueChanged(low);
391 if (upp != qxt_d().upper)
394 emit upperValueChanged(upp);
396 emit spanChanged(qxt_d().lower, qxt_d().upper);
401 void QxtSpanSlider::keyPressEvent(QKeyEvent* event)
403 QSlider::keyPressEvent(event);
406 SliderAction action = SliderNoAction;
407 switch (event->key())
410 main = (orientation() == Qt::Horizontal);
411 action = !invertedAppearance() ? SliderSingleStepSub : SliderSingleStepAdd;
414 main = (orientation() == Qt::Horizontal);
415 action = !invertedAppearance() ? SliderSingleStepAdd : SliderSingleStepSub;
418 main = (orientation() == Qt::Vertical);
419 action = invertedControls() ? SliderSingleStepSub : SliderSingleStepAdd;
422 main = (orientation() == Qt::Vertical);
423 action = invertedControls() ? SliderSingleStepAdd : SliderSingleStepSub;
426 main = (qxt_d().mainControl == QxtSpanSliderPrivate::LowerHandle);
427 action = SliderToMinimum;
430 main = (qxt_d().mainControl == QxtSpanSliderPrivate::UpperHandle);
431 action = SliderToMaximum;
439 qxt_d().triggerAction(action, main);
442 void QxtSpanSlider::mousePressEvent(QMouseEvent* event)
444 if (minimum() == maximum() || (event->buttons() ^ event->button()))
450 qxt_d().handleMousePress(event->pos(), qxt_d().upperPressed, qxt_d().upper, QxtSpanSliderPrivate::UpperHandle);
451 if (qxt_d().upperPressed != QStyle::SC_SliderHandle)
452 qxt_d().handleMousePress(event->pos(), qxt_d().lowerPressed, qxt_d().lower, QxtSpanSliderPrivate::LowerHandle);
457 void QxtSpanSlider::mouseMoveEvent(QMouseEvent* event)
459 if (qxt_d().lowerPressed != QStyle::SC_SliderHandle && qxt_d().upperPressed != QStyle::SC_SliderHandle)
465 QStyleOptionSlider opt;
466 qxt_d().initStyleOption(&opt);
467 const int m = style()->pixelMetric(QStyle::PM_MaximumDragDistance, &opt, this);
468 int newPosition = qxt_d().pixelPosToRangeValue(qxt_d().pick(event->pos()) - qxt_d().offset);
471 const QRect r = rect().adjusted(-m, -m, m, m);
472 if (!r.contains(event->pos()))
474 newPosition = qxt_d().position;
478 if (qxt_d().lowerPressed == QStyle::SC_SliderHandle)
480 if (newPosition > qxt_d().upper)
482 qxt_d().swapControls();
483 setUpperValue(newPosition);
487 setLowerValue(newPosition);
490 else if (qxt_d().upperPressed == QStyle::SC_SliderHandle)
492 if (newPosition < qxt_d().lower)
494 qxt_d().swapControls();
495 setLowerValue(newPosition);
499 setUpperValue(newPosition);
505 void QxtSpanSlider::mouseReleaseEvent(QMouseEvent* event)
507 QSlider::mouseReleaseEvent(event);
508 qxt_d().lowerPressed = QStyle::SC_None;
509 qxt_d().upperPressed = QStyle::SC_None;
513 void QxtSpanSlider::paintEvent(QPaintEvent* event)
516 QStylePainter painter(this);
519 QStyleOptionSlider opt;
520 qxt_d().initStyleOption(&opt);
521 opt.subControls = QStyle::SC_SliderTickmarks;
522 painter.drawComplexControl(QStyle::CC_Slider, opt);
525 opt.sliderPosition = 0;
526 opt.subControls = QStyle::SC_SliderGroove;
527 painter.drawComplexControl(QStyle::CC_Slider, opt);
530 opt.sliderPosition = qxt_d().lower;
531 const QRect lr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
532 const int lrv = qxt_d().pick(lr.center());
533 opt.sliderPosition = qxt_d().upper;
534 const QRect ur = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
535 const int urv = qxt_d().pick(ur.center());
538 const int minv = qMin(lrv, urv);
539 const int maxv = qMax(lrv, urv);
540 const QPoint c = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this).center();
542 if (orientation() == Qt::Horizontal)
543 spanRect = QRect(QPoint(minv, c.y()-2), QPoint(maxv, c.y()+1));
545 spanRect = QRect(QPoint(c.x()-2, minv), QPoint(c.x()+1, maxv));
546 qxt_d().drawSpan(&painter, spanRect);
549 switch (qxt_d().lastPressed)
551 case QxtSpanSliderPrivate::LowerHandle:
552 qxt_d().drawHandle(&painter, QxtSpanSliderPrivate::UpperHandle);
553 qxt_d().drawHandle(&painter, QxtSpanSliderPrivate::LowerHandle);
555 case QxtSpanSliderPrivate::UpperHandle:
557 qxt_d().drawHandle(&painter, QxtSpanSliderPrivate::LowerHandle);
558 qxt_d().drawHandle(&painter, QxtSpanSliderPrivate::UpperHandle);