We now have a current svn snapshot of libqxt in our contrib dir, and
[quassel.git] / src / contrib / libqxt-2007-10-24 / src / gui / qxtstars.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) Qxt Foundation. Some rights reserved.
4 **
5 ** This file is part of the QxtGui module of the Qt eXTension library
6 **
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
9 ** IBM.
10 **
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.
15 **
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.
20 **
21 ** <http://libqxt.sourceforge.net>  <foundation@libqxt.org>
22 **
23 ****************************************************************************/
24 #include "qxtstars.h"
25
26 #include <QStyleOptionSlider>
27 #include <QPainterPath>
28 #include <QMouseEvent>
29 #include <QPainter>
30
31 class QxtStarsPrivate : public QxtPrivate<QxtStars>
32 {
33 public:
34     QXT_DECLARE_PUBLIC(QxtStars);
35     QxtStarsPrivate();
36     int pixelPosToRangeValue(int pos) const;
37     inline int pick(const QPoint& pt) const;
38     QStyleOptionSlider getStyleOption() const;
39     QSize getStarSize() const;
40     int snapBackPosition;
41     bool readOnly;
42     QSize starSize;
43     QPainterPath star;
44 };
45
46 QxtStarsPrivate::QxtStarsPrivate()
47         : snapBackPosition(0), readOnly(false)
48 {
49     star.moveTo(14.285716,-43.352104);
50     star.lineTo(38.404536,9.1654726);
51     star.lineTo(95.804846,15.875014);
52     star.lineTo(53.310787,55.042197);
53     star.lineTo(64.667306,111.7065);
54     star.lineTo(14.285714,83.395573);
55     star.lineTo(-36.095881,111.7065);
56     star.lineTo(-24.739359,55.042198);
57     star.lineTo(-67.233416,15.875009);
58     star.lineTo(-9.8331075,9.1654728);
59     star.closeSubpath();
60 }
61
62 int QxtStarsPrivate::pixelPosToRangeValue(int pos) const
63 {
64     const QxtStars* p = &qxt_p();
65     QStyleOptionSlider opt = getStyleOption();
66     QRect gr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, p);
67     QRect sr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, p);
68     int sliderMin, sliderMax, sliderLength;
69
70     gr.setSize(qxt_p().sizeHint());
71     if (p->orientation() == Qt::Horizontal)
72     {
73         sliderLength = sr.width();
74         sliderMin = gr.x();
75         sliderMax = gr.right() - sliderLength + 1;
76     }
77     else
78     {
79         sliderLength = sr.height();
80         sliderMin = gr.y();
81         sliderMax = gr.bottom() - sliderLength + 1;
82     }
83     return QStyle::sliderValueFromPosition(p->minimum(), p->maximum(), pos - sliderMin,
84                                            sliderMax - sliderMin, opt.upsideDown);
85 }
86
87 inline int QxtStarsPrivate::pick(const QPoint& pt) const
88 {
89     return qxt_p().orientation() == Qt::Horizontal ? pt.x() : pt.y();
90 }
91
92 // TODO: get rid of this in Qt 4.3
93 QStyleOptionSlider QxtStarsPrivate::getStyleOption() const
94 {
95     const QxtStars* p = &qxt_p();
96     QStyleOptionSlider opt;
97     opt.initFrom(p);
98     opt.subControls = QStyle::SC_None;
99     opt.activeSubControls = QStyle::SC_None;
100     opt.orientation = p->orientation();
101     opt.maximum = p->maximum();
102     opt.minimum = p->minimum();
103     opt.upsideDown = (p->orientation() == Qt::Horizontal) ?
104                      (p->invertedAppearance() != (opt.direction == Qt::RightToLeft)) : (!p->invertedAppearance());
105     opt.direction = Qt::LeftToRight; // we use the upsideDown option instead
106     opt.sliderPosition = p->sliderPosition();
107     opt.sliderValue = p->value();
108     opt.singleStep = p->singleStep();
109     opt.pageStep = p->pageStep();
110     if (p->orientation() == Qt::Horizontal)
111         opt.state |= QStyle::State_Horizontal;
112     return opt;
113 }
114
115 QSize QxtStarsPrivate::getStarSize() const
116 {
117     QSize size = starSize;
118     if (!size.isValid() || size.isNull())
119     {
120         const int width = qxt_p().style()->pixelMetric(QStyle::PM_SmallIconSize);
121         size = QSize(width, width);
122     }
123     return size;
124 }
125
126 /*!
127     \class QxtStars QxtStars
128     \ingroup QxtGui
129     \brief A stars assessment widget.
130
131     QxtStars is an optionally interactive stars assessment widget.
132
133     By default, orientation is \b Qt::Horizonal and range is from \b 0 to \b 5.
134
135     The stars are rendered accoring to palette with the following color roles:
136     <table>
137     <tr><td><b>ColorRole</b></td><td><b>Element</b></td></tr>
138     <tr><td>QPalette::Text</td><td>outlines</td></tr>
139     <tr><td>QPalette::Base</td><td>unselected stars</td></tr>
140     <tr><td>QPalette::Highlight</td><td>selected stars</td></tr>
141     </table>
142
143     \image html qxtstars.png "QxtStars in action."
144  */
145
146 /*!
147     \fn QxtStars::valueChanged(int value)
148
149     This signal is emitted whenever the value has been changed.
150  */
151
152 /*!
153     Constructs a new QxtStars with \a parent.
154  */
155 QxtStars::QxtStars(QWidget* parent) : QAbstractSlider(parent)
156 {
157     QXT_INIT_PRIVATE(QxtStars);
158     setOrientation(Qt::Horizontal);
159     setFocusPolicy(Qt::FocusPolicy(style()->styleHint(QStyle::SH_Button_FocusPolicy)));
160     setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
161     setRange(0, 5);
162 }
163
164 /*!
165     Destructs the stars.
166  */
167 QxtStars::~QxtStars()
168 {}
169
170 /*!
171     \property QxtStars::readOnly
172     \brief This property holds whether stars are adjustable
173
174     In read-only mode, the user is not able to change the value.
175     The default value is \b false.
176  */
177 bool QxtStars::isReadOnly() const
178 {
179     return qxt_d().readOnly;
180 }
181
182 void QxtStars::setReadOnly(bool readOnly)
183 {
184     if (qxt_d().readOnly != readOnly)
185     {
186         qxt_d().readOnly = readOnly;
187         if (readOnly)
188             setFocusPolicy(Qt::NoFocus);
189         else
190             setFocusPolicy(Qt::FocusPolicy(style()->styleHint(QStyle::SH_Button_FocusPolicy)));
191     }
192 }
193
194 /*!
195     \property QxtStars::starSize
196     \brief This property holds the size of star icons
197
198     The default value is \b QStyle::PM_SmallIconSize.
199  */
200 QSize QxtStars::starSize() const
201 {
202     return qxt_d().starSize;
203 }
204
205 void QxtStars::setStarSize(const QSize& size)
206 {
207     if (qxt_d().starSize != size)
208     {
209         qxt_d().starSize = size;
210         updateGeometry();
211         update();
212     }
213 }
214
215 QSize QxtStars::sizeHint() const
216 {
217     return minimumSizeHint();
218 }
219
220 QSize QxtStars::minimumSizeHint() const
221 {
222     QSize size = qxt_d().getStarSize();
223     size.rwidth() *= maximum() - minimum();
224     if (orientation() == Qt::Vertical)
225         size.transpose();
226     return size;
227 }
228
229 void QxtStars::paintEvent(QPaintEvent* event)
230 {
231     QAbstractSlider::paintEvent(event);
232
233     QPainter painter(this);
234     painter.save();
235     painter.setPen(palette().color(QPalette::Text));
236     painter.setRenderHint(QPainter::Antialiasing);
237
238     const bool invert = invertedAppearance();
239     const QSize size = qxt_d().getStarSize();
240     const QRectF star = qxt_d().star.boundingRect();
241     painter.scale(size.width() / star.width(), size.height() / star.height());
242     const int count = maximum() - minimum();
243     if (orientation() == Qt::Horizontal)
244     {
245         painter.translate(-star.x(), -star.y());
246         if (invert != isRightToLeft())
247             painter.translate((count - 1) * star.width(), 0);
248     }
249     else
250     {
251         painter.translate(-star.x(), -star.y());
252         if (!invert)
253             painter.translate(0, (count - 1) * star.height());
254     }
255     for (int i = 0; i < count; ++i)
256     {
257         if (value() > minimum() + i)
258             painter.setBrush(palette().highlight());
259         else
260             painter.setBrush(palette().base());
261         painter.drawPath(qxt_d().star);
262
263         if (orientation() == Qt::Horizontal)
264             painter.translate(invert != isRightToLeft() ? -star.width() : star.width(), 0);
265         else
266             painter.translate(0, invert ? star.height() : -star.height());
267     }
268
269     painter.restore();
270     if (hasFocus())
271     {
272         QStyleOptionFocusRect opt;
273         opt.initFrom(this);
274         opt.rect.setSize(sizeHint());
275         style()->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, &painter, this);
276     }
277 }
278
279 void QxtStars::keyPressEvent(QKeyEvent* event)
280 {
281     if (qxt_d().readOnly)
282     {
283         event->ignore();
284         return;
285     }
286     QAbstractSlider::keyPressEvent(event);
287 }
288
289 void QxtStars::mousePressEvent(QMouseEvent* event)
290 {
291     if (qxt_d().readOnly)
292     {
293         event->ignore();
294         return;
295     }
296     QAbstractSlider::mousePressEvent(event);
297
298     if (maximum() == minimum() || (event->buttons() ^ event->button()))
299     {
300         event->ignore();
301         return;
302     }
303
304     event->accept();
305     QStyleOptionSlider opt = qxt_d().getStyleOption();
306     const QRect sliderRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
307     const QPoint center = sliderRect.center() - sliderRect.topLeft();
308     const int pos = qxt_d().pixelPosToRangeValue(qxt_d().pick(event->pos() - center));
309     setSliderPosition(pos);
310     triggerAction(SliderMove);
311     setRepeatAction(SliderNoAction);
312     qxt_d().snapBackPosition = pos;
313     update();
314 }
315
316 void QxtStars::mouseMoveEvent(QMouseEvent* event)
317 {
318     if (qxt_d().readOnly)
319     {
320         event->ignore();
321         return;
322     }
323     QAbstractSlider::mouseMoveEvent(event);
324
325     event->accept();
326     int newPosition = qxt_d().pixelPosToRangeValue(qxt_d().pick(event->pos()));
327     QStyleOptionSlider opt = qxt_d().getStyleOption();
328     int m = style()->pixelMetric(QStyle::PM_MaximumDragDistance, &opt, this);
329     if (m >= 0)
330     {
331         QRect r = rect();
332         r.adjust(-m, -m, m, m);
333         if (!r.contains(event->pos()))
334             newPosition = qxt_d().snapBackPosition;
335     }
336     setSliderPosition(newPosition);
337 }
338
339 void QxtStars::mouseReleaseEvent(QMouseEvent* event)
340 {
341     if (qxt_d().readOnly)
342     {
343         event->ignore();
344         return;
345     }
346     QAbstractSlider::mouseReleaseEvent(event);
347
348     if (event->buttons())
349     {
350         event->ignore();
351         return;
352     }
353
354     event->accept();
355     setRepeatAction(SliderNoAction);
356 }