1 /* This file is part of the KDE project
2    Copyright (C) 2012 Oleg Kukharchuk <oleg.kuh@gmail.com>
3 
4    This program is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this program; see the file COPYING.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18 */
19 
20 #include "kexislider.h"
21 
22 #include <QBoxLayout>
23 #include <QSpinBox>
24 #include <QPainter>
25 #include <QStyle>
26 #include <QSlider>
27 #include <QStyleOptionSlider>
28 
29 
30 class Q_DECL_HIDDEN KexiSlider::Private
31 {
32 public:
Private()33     Private() {}
34 
35     Slider *slider;
36     QSpinBox *spinBox;
37     QBoxLayout *layout;
38 };
39 
40 
41 class Slider : public QSlider
42 {
43     Q_OBJECT
44 public:
Slider(QWidget * parent)45     explicit Slider(QWidget *parent) :
46         QSlider(parent)
47     {
48 
49     }
50 
sizeHint() const51     QSize sizeHint() const
52     {
53         if (tickPosition() == QSlider::NoTicks)
54             return QSlider::sizeHint();
55 
56         // preserve space for labels
57         QSize extra(0,0);
58 
59         QFontMetrics fm(font());
60 
61         int h = fm.height() + 3;
62         int w = fm.width(QString::number(maximum())) + 3;
63 
64         if (orientation() == Qt::Horizontal) {
65             extra.setHeight(tickPosition() == QSlider::TicksBothSides ? h * 2 : h);
66         } else {
67             extra.setWidth(tickPosition() == QSlider::TicksBothSides ? w * 2 : w);
68         }
69 
70         return QSlider::sizeHint() + extra;
71     }
72 
73 protected:
paintEvent(QPaintEvent * ev)74     virtual void paintEvent(QPaintEvent *ev)
75     {
76         if (tickPosition() == QSlider::NoTicks)
77             return QSlider::paintEvent(ev);
78 
79         QPainter p(this);
80         QStyleOptionSlider option;
81         initStyleOption(&option);
82 
83         const QSlider::TickPosition ticks( option.tickPosition );
84         const int available(style()->proxy()->pixelMetric(QStyle::PM_SliderSpaceAvailable, &option, this));
85         int interval = option.tickInterval;
86         if( interval < 1 ) interval = option.pageStep;
87         if( interval < 1 ) return;
88 
89         const QRect r(option.rect);
90         const QPalette palette(option.palette);
91         const int fudge(style()->proxy()->pixelMetric(QStyle::PM_SliderLength, &option, this) / 2);
92         int current(option.minimum);
93         int nextLabel = current;
94 
95         const QFontMetrics fm(fontMetrics());
96         int h = fm.height() + 3;
97         int w = fm.width(QString::number(option.maximum)) + 3;
98 
99         if(available<w)
100             nextLabel = -1;
101 
102         qreal i = qreal(available) / qreal(orientation() == Qt::Horizontal ? w : h);
103         qreal t = qreal(option.maximum)/qreal(interval);
104         int valStep = t/ i + 1;
105 
106         // Since there is no subrect for tickmarks do a translation here.
107         p.save();
108         p.translate(r.x(), r.y());
109 
110         p.setPen(palette.color(QPalette::WindowText));
111         int extra = (option.tickPosition == QSlider::TicksBothSides ? 2 : 1);
112         int tickSize(option.orientation == Qt::Horizontal ? (r.height() - h*extra)/3:(r.width() - w*extra)/3);
113 
114         while(current <= option.maximum)
115         {
116 
117             const int position(QStyle::sliderPositionFromValue(option.minimum, option.maximum,
118                                                                  current, available, option.upsideDown) + fudge);
119 
120             // calculate positions
121             if(option.orientation == Qt::Horizontal)
122             {
123 
124                 if (ticks & QSlider::TicksAbove) {
125                     p.drawLine(position, h, position, tickSize + h);
126                     if(current == nextLabel)
127                         p.drawText(QRect(position - w/2, 0, w, h), Qt::AlignHCenter, QString::number(current));
128                 }
129                 if (ticks & QSlider::TicksBelow) {
130                     p.drawLine( position, r.height() - h - tickSize, position, r.height() - h );
131                     if(current == nextLabel)
132                         p.drawText(QRect(position - w/2, r.height() - h + 3, w, h), Qt::AlignHCenter, QString::number(current));
133                 }
134             } else {
135                 if (ticks & QSlider::TicksAbove) {
136                     p.drawLine(w, position, tickSize + w, position);
137                     if(current == nextLabel)
138                         p.drawText(QRect(0, position - h/2, w - 3, h), Qt::AlignRight | Qt::AlignVCenter, QString::number(current));
139                 }
140                 if (ticks & QSlider::TicksBelow) {
141                     p.drawLine(r.width() - w - tickSize, position, r.width() - w, position );
142                     if(current == nextLabel)
143                         p.drawText(QRect(r.width() - w + 3, position - h/2, w, h), Qt::AlignVCenter, QString::number(current));
144                 }
145             }
146 
147             // go to next position
148             if (current == nextLabel)
149                 nextLabel += interval*valStep;
150             current += interval;
151 
152         }
153         p.restore();
154         option.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle;
155 
156         style()->proxy()->drawComplexControl(QStyle::CC_Slider, &option, &p, this);
157     }
158 };
159 
KexiSlider(QWidget * parent)160 KexiSlider::KexiSlider(QWidget *parent)
161     : QWidget(parent)
162     , d(new Private)
163 {
164     init(Qt::Horizontal);
165 }
166 
KexiSlider(Qt::Orientation orientation,QWidget * parent)167 KexiSlider::KexiSlider(Qt::Orientation orientation, QWidget *parent)
168     : QWidget(parent)
169     , d(new Private)
170 {
171     init(orientation);
172 }
173 
init(Qt::Orientation orientation)174 void KexiSlider::init(Qt::Orientation orientation)
175 {
176     d->layout=new QBoxLayout(QBoxLayout::LeftToRight, this);
177     d->layout->setSpacing(2);
178     d->layout->setMargin(0);
179     d->slider = new Slider(this);
180     d->spinBox = new QSpinBox(this);
181     d->spinBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
182 
183     d->layout->addWidget(d->spinBox,0, Qt::AlignVCenter);
184     d->layout->addWidget(d->slider,0, Qt::AlignVCenter);
185 
186     connect(d->slider, SIGNAL(valueChanged(int)), this, SIGNAL(valueChanged(int)));
187     connect(d->slider, SIGNAL(sliderPressed()), this, SIGNAL(sliderPressed()));
188     connect(d->slider, SIGNAL(sliderReleased()), this, SIGNAL(sliderReleased()));
189     connect(d->slider, SIGNAL(valueChanged(int)), d->spinBox, SLOT(setValue(int)));
190     connect(d->spinBox, SIGNAL(valueChanged(int)), d->slider, SLOT(setValue(int)));
191 
192     setMaximum(100);
193     setOrientation(orientation);
194     setTickPosition(QSlider::TicksAbove);
195 }
196 
~KexiSlider()197 KexiSlider::~KexiSlider()
198 {
199     delete d;
200 }
201 
setMinimum(int min)202 void KexiSlider::setMinimum(int min)
203 {
204     d->spinBox->setMinimum(min);
205     d->slider->setMinimum(min);
206 }
207 
setMaximum(int max)208 void KexiSlider::setMaximum(int max)
209 {
210     d->spinBox->setMaximum(max);
211     d->slider->setMaximum(max);
212 }
213 
setValue(int val)214 void KexiSlider::setValue(int val)
215 {
216     d->slider->setValue(val);
217 }
218 
minimum() const219 int KexiSlider::minimum() const
220 {
221     return d->slider->minimum();
222 }
223 
maximum() const224 int KexiSlider::maximum() const
225 {
226     return d->slider->maximum();
227 }
228 
value() const229 int KexiSlider::value() const
230 {
231     return d->slider->value();
232 }
233 
setPageStep(int step)234 void KexiSlider::setPageStep(int step)
235 {
236     d->slider->setPageStep(step);
237 }
238 
pageStep() const239 int KexiSlider::pageStep() const
240 {
241     return d->slider->pageStep();
242 }
243 
setSingleStep(int step)244 void KexiSlider::setSingleStep(int step)
245 {
246     d->spinBox->setSingleStep(step);
247     d->slider->setSingleStep(step);
248 }
249 
singleStep() const250 int KexiSlider::singleStep() const
251 {
252     return d->slider->singleStep();
253 }
254 
setOrientation(Qt::Orientation o)255 void KexiSlider::setOrientation(Qt::Orientation o)
256 {
257     d->layout->removeWidget(d->spinBox);
258     d->slider->setOrientation(o);
259     if(o == Qt::Horizontal)
260         d->layout->insertWidget(0, d->spinBox);
261     else
262         d->layout->addWidget(d->spinBox);
263     updateLayout();
264 }
265 
orientation() const266 Qt::Orientation KexiSlider::orientation() const
267 {
268     return d->slider->orientation();
269 }
270 
setTickInterval(int ti)271 void KexiSlider::setTickInterval(int ti)
272 {
273     d->slider->setTickInterval(ti);
274 }
275 
tickInterval() const276 int KexiSlider::tickInterval() const
277 {
278     return d->slider->tickInterval();
279 }
280 
setTickPosition(QSlider::TickPosition pos)281 void KexiSlider::setTickPosition(QSlider::TickPosition pos)
282 {
283     d->slider->setTickPosition(pos);
284     updateLayout();
285 }
286 
tickPosition() const287 QSlider::TickPosition KexiSlider::tickPosition() const
288 {
289     return d->slider->tickPosition();
290 }
291 
setShowEditor(bool show)292 void KexiSlider::setShowEditor(bool show)
293 {
294     d->spinBox->setVisible(show);
295 
296 }
297 
showEditor() const298 bool KexiSlider::showEditor() const
299 {
300     return d->spinBox->isVisible();
301 }
302 
updateLayout()303 void KexiSlider::updateLayout()
304 {
305     d->layout->setDirection(orientation() == Qt::Horizontal ?
306                         QBoxLayout::LeftToRight : QBoxLayout::TopToBottom);
307 
308     if (tickPosition() == QSlider::TicksBothSides
309             || tickPosition() == QSlider::NoTicks) {
310         d->layout->setAlignment(d->slider, orientation() == Qt::Horizontal ?
311                                    Qt::AlignVCenter :Qt::AlignHCenter);
312         d->layout->setAlignment(d->spinBox, orientation() == Qt::Horizontal ?
313                                    Qt::AlignVCenter :Qt::AlignHCenter);
314     } else {
315         if (orientation() == Qt::Horizontal) {
316             d->layout->setAlignment(d->slider,
317                                    tickPosition() == QSlider::TicksAbove ? Qt::AlignBottom : Qt::AlignTop);
318             d->layout->setAlignment(d->spinBox,
319                                    tickPosition() == QSlider::TicksAbove ? Qt::AlignBottom : Qt::AlignTop);
320         } else {
321             d->layout->setAlignment(d->slider,
322                                    tickPosition() == QSlider::TicksLeft ? Qt::AlignRight : Qt::AlignLeft);
323             d->layout->setAlignment(d->spinBox,
324                                    tickPosition() == QSlider::TicksLeft ? Qt::AlignRight : Qt::AlignLeft);
325         }
326     }
327 }
328 
329 #include "kexislider.moc"
330