1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 1997 Martin Jones <mjones@kde.org>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "kselector.h"
9 
10 #include <QPaintEvent>
11 #include <QPainter>
12 #include <QPixmap>
13 #include <QStyle>
14 #include <QStyleOption>
15 
16 //-----------------------------------------------------------------------------
17 /*
18  * 1D value selector with contents drawn by derived class.
19  * See KColorDialog for example.
20  */
21 
22 #define ARROWSIZE 5
23 
24 class KSelectorPrivate
25 {
26 public:
27     bool m_indent = true;
28     QStyle::PrimitiveElement arrowPE = QStyle::PE_IndicatorArrowLeft;
29 };
30 
31 class KGradientSelectorPrivate
32 {
33 public:
KGradientSelectorPrivate(KGradientSelector * qq)34     KGradientSelectorPrivate(KGradientSelector *qq)
35         : q(qq)
36     {
37     }
38 
39     KGradientSelector *q;
40     QLinearGradient gradient;
41     QString text1;
42     QString text2;
43 };
44 
KSelector(QWidget * parent)45 KSelector::KSelector(QWidget *parent)
46     : QAbstractSlider(parent)
47     , d(new KSelectorPrivate)
48 {
49     setOrientation(Qt::Horizontal);
50 }
51 
KSelector(Qt::Orientation o,QWidget * parent)52 KSelector::KSelector(Qt::Orientation o, QWidget *parent)
53     : QAbstractSlider(parent)
54     , d(new KSelectorPrivate)
55 {
56     setOrientation(o);
57     if (o == Qt::Horizontal) {
58         setArrowDirection(Qt::UpArrow);
59     }
60 }
61 
62 KSelector::~KSelector() = default;
63 
setIndent(bool i)64 void KSelector::setIndent(bool i)
65 {
66     d->m_indent = i;
67 }
68 
indent() const69 bool KSelector::indent() const
70 {
71     return d->m_indent;
72 }
73 
contentsRect() const74 QRect KSelector::contentsRect() const
75 {
76     int w = indent() ? style()->pixelMetric(QStyle::PM_DefaultFrameWidth) : 0;
77     // TODO: is the height:width ratio of an indicator arrow always 2:1? hm.
78     int iw = (w < ARROWSIZE) ? ARROWSIZE : w;
79 
80     if (orientation() == Qt::Vertical) {
81         if (arrowDirection() == Qt::RightArrow) {
82             return QRect(w + ARROWSIZE, //
83                          iw,
84                          width() - w * 2 - ARROWSIZE,
85                          height() - iw * 2);
86         } else {
87             return QRect(w, //
88                          iw,
89                          width() - w * 2 - ARROWSIZE,
90                          height() - iw * 2);
91         }
92     } else { // Qt::Horizontal
93         if (arrowDirection() == Qt::UpArrow) {
94             return QRect(iw, //
95                          w,
96                          width() - 2 * iw,
97                          height() - w * 2 - ARROWSIZE);
98         } else {
99             return QRect(iw, //
100                          w + ARROWSIZE,
101                          width() - 2 * iw,
102                          height() - w * 2 - ARROWSIZE);
103         }
104     }
105 }
106 
paintEvent(QPaintEvent *)107 void KSelector::paintEvent(QPaintEvent *)
108 {
109     QPainter painter;
110     int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
111     int iw = (w < ARROWSIZE) ? ARROWSIZE : w;
112 
113     painter.begin(this);
114 
115     drawContents(&painter);
116 
117     QPoint pos = calcArrowPos(value());
118     drawArrow(&painter, pos);
119 
120     if (indent()) {
121         QStyleOptionFrame opt;
122         opt.initFrom(this);
123         opt.state = QStyle::State_Sunken;
124         if (orientation() == Qt::Vertical) {
125             opt.rect.adjust(0, iw - w, -5, w - iw);
126         } else {
127             opt.rect.adjust(iw - w, 0, w - iw, -5);
128         }
129         QBrush oldBrush = painter.brush();
130         painter.setBrush(Qt::NoBrush);
131         style()->drawPrimitive(QStyle::PE_Frame, &opt, &painter, this);
132         painter.setBrush(oldBrush);
133     }
134 
135     painter.end();
136 }
137 
mousePressEvent(QMouseEvent * e)138 void KSelector::mousePressEvent(QMouseEvent *e)
139 {
140     setSliderDown(true);
141     moveArrow(e->pos());
142 }
143 
mouseMoveEvent(QMouseEvent * e)144 void KSelector::mouseMoveEvent(QMouseEvent *e)
145 {
146     moveArrow(e->pos());
147 }
148 
mouseReleaseEvent(QMouseEvent * e)149 void KSelector::mouseReleaseEvent(QMouseEvent *e)
150 {
151     moveArrow(e->pos());
152     setSliderDown(false);
153 }
154 
wheelEvent(QWheelEvent * e)155 void KSelector::wheelEvent(QWheelEvent *e)
156 {
157     int val = value() + e->angleDelta().y() / 120;
158     setSliderDown(true);
159     setValue(val);
160     setSliderDown(false);
161 }
162 
moveArrow(const QPoint & pos)163 void KSelector::moveArrow(const QPoint &pos)
164 {
165     int val;
166     int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
167     int iw = (w < ARROWSIZE) ? ARROWSIZE : w;
168 
169     if (orientation() == Qt::Vertical) {
170         val = (maximum() - minimum()) * (height() - pos.y() - iw) / (height() - iw * 2) + minimum();
171     } else {
172         val = (maximum() - minimum()) * (pos.x() - iw) / (width() - iw * 2) + minimum();
173     }
174 
175     setValue(val);
176     update();
177 }
178 
calcArrowPos(int val)179 QPoint KSelector::calcArrowPos(int val)
180 {
181     QPoint p;
182     int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
183     int iw = (w < ARROWSIZE) ? ARROWSIZE : w;
184 
185     if (orientation() == Qt::Vertical) {
186         p.setY(height() - iw - 1 - (height() - 2 * iw - 1) * val / (maximum() - minimum()));
187 
188         if (d->arrowPE == QStyle::PE_IndicatorArrowRight) {
189             p.setX(0);
190         } else {
191             p.setX(width() - 5);
192         }
193     } else {
194         p.setX(iw + (width() - 2 * iw - 1) * val / (maximum() - minimum()));
195 
196         if (d->arrowPE == QStyle::PE_IndicatorArrowDown) {
197             p.setY(0);
198         } else {
199             p.setY(height() - 5);
200         }
201     }
202 
203     return p;
204 }
205 
setArrowDirection(Qt::ArrowType direction)206 void KSelector::setArrowDirection(Qt::ArrowType direction)
207 {
208     switch (direction) {
209     case Qt::UpArrow:
210         if (orientation() == Qt::Horizontal) {
211             d->arrowPE = QStyle::PE_IndicatorArrowUp;
212         } else {
213             d->arrowPE = QStyle::PE_IndicatorArrowLeft;
214         }
215         break;
216     case Qt::DownArrow:
217         if (orientation() == Qt::Horizontal) {
218             d->arrowPE = QStyle::PE_IndicatorArrowDown;
219         } else {
220             d->arrowPE = QStyle::PE_IndicatorArrowRight;
221         }
222         break;
223     case Qt::LeftArrow:
224         if (orientation() == Qt::Vertical) {
225             d->arrowPE = QStyle::PE_IndicatorArrowLeft;
226         } else {
227             d->arrowPE = QStyle::PE_IndicatorArrowDown;
228         }
229         break;
230     case Qt::RightArrow:
231         if (orientation() == Qt::Vertical) {
232             d->arrowPE = QStyle::PE_IndicatorArrowRight;
233         } else {
234             d->arrowPE = QStyle::PE_IndicatorArrowUp;
235         }
236         break;
237 
238     case Qt::NoArrow:
239         break;
240     }
241 }
242 
arrowDirection() const243 Qt::ArrowType KSelector::arrowDirection() const
244 {
245     switch (d->arrowPE) {
246     case QStyle::PE_IndicatorArrowUp:
247         return Qt::UpArrow;
248     case QStyle::PE_IndicatorArrowDown:
249         return Qt::DownArrow;
250     case QStyle::PE_IndicatorArrowRight:
251         return Qt::RightArrow;
252     case QStyle::PE_IndicatorArrowLeft:
253     default:
254         return Qt::LeftArrow;
255     }
256 }
257 
drawContents(QPainter *)258 void KSelector::drawContents(QPainter *)
259 {
260 }
261 
drawArrow(QPainter * painter,const QPoint & pos)262 void KSelector::drawArrow(QPainter *painter, const QPoint &pos)
263 {
264     painter->setPen(QPen());
265     painter->setBrush(QBrush(palette().color(QPalette::ButtonText)));
266 
267     QStyleOption o;
268 
269     if (orientation() == Qt::Vertical) {
270         o.rect = QRect(pos.x(), pos.y() - ARROWSIZE / 2, ARROWSIZE, ARROWSIZE);
271     } else {
272         o.rect = QRect(pos.x() - ARROWSIZE / 2, pos.y(), ARROWSIZE, ARROWSIZE);
273     }
274     style()->drawPrimitive(d->arrowPE, &o, painter, this);
275 }
276 
277 //----------------------------------------------------------------------------
278 
KGradientSelector(QWidget * parent)279 KGradientSelector::KGradientSelector(QWidget *parent)
280     : KSelector(parent)
281     , d(new KGradientSelectorPrivate(this))
282 {
283 }
284 
KGradientSelector(Qt::Orientation o,QWidget * parent)285 KGradientSelector::KGradientSelector(Qt::Orientation o, QWidget *parent)
286     : KSelector(o, parent)
287     , d(new KGradientSelectorPrivate(this))
288 {
289 }
290 
291 KGradientSelector::~KGradientSelector() = default;
292 
drawContents(QPainter * painter)293 void KGradientSelector::drawContents(QPainter *painter)
294 {
295     d->gradient.setStart(contentsRect().topLeft());
296     if (orientation() == Qt::Vertical) {
297         d->gradient.setFinalStop(contentsRect().bottomLeft());
298     } else {
299         d->gradient.setFinalStop(contentsRect().topRight());
300     }
301     QBrush gradientBrush(d->gradient);
302 
303     if (!gradientBrush.isOpaque()) {
304         QPixmap chessboardPattern(16, 16);
305         QPainter patternPainter(&chessboardPattern);
306         patternPainter.fillRect(0, 0, 8, 8, Qt::black);
307         patternPainter.fillRect(8, 8, 8, 8, Qt::black);
308         patternPainter.fillRect(0, 8, 8, 8, Qt::white);
309         patternPainter.fillRect(8, 0, 8, 8, Qt::white);
310         patternPainter.end();
311         painter->fillRect(contentsRect(), QBrush(chessboardPattern));
312     }
313     painter->fillRect(contentsRect(), gradientBrush);
314 
315     if (orientation() == Qt::Vertical) {
316         int yPos = contentsRect().top() + painter->fontMetrics().ascent() + 2;
317         int xPos = contentsRect().left() + (contentsRect().width() - painter->fontMetrics().horizontalAdvance(d->text2)) / 2;
318         QPen pen(qGray(firstColor().rgb()) > 180 ? Qt::black : Qt::white);
319         painter->setPen(pen);
320         painter->drawText(xPos, yPos, d->text2);
321 
322         yPos = contentsRect().bottom() - painter->fontMetrics().descent() - 2;
323         xPos = contentsRect().left() + (contentsRect().width() - painter->fontMetrics().horizontalAdvance(d->text1)) / 2;
324         pen.setColor(qGray(secondColor().rgb()) > 180 ? Qt::black : Qt::white);
325         painter->setPen(pen);
326         painter->drawText(xPos, yPos, d->text1);
327     } else {
328         int yPos = contentsRect().bottom() - painter->fontMetrics().descent() - 2;
329 
330         QPen pen(qGray(firstColor().rgb()) > 180 ? Qt::black : Qt::white);
331         painter->setPen(pen);
332         painter->drawText(contentsRect().left() + 2, yPos, d->text1);
333 
334         pen.setColor(qGray(secondColor().rgb()) > 180 ? Qt::black : Qt::white);
335         painter->setPen(pen);
336         painter->drawText(contentsRect().right() - painter->fontMetrics().horizontalAdvance(d->text2) - 2, yPos, d->text2);
337     }
338 }
339 
minimumSize() const340 QSize KGradientSelector::minimumSize() const
341 {
342     return sizeHint();
343 }
344 
setStops(const QGradientStops & stops)345 void KGradientSelector::setStops(const QGradientStops &stops)
346 {
347     d->gradient.setStops(stops);
348     update();
349 }
350 
stops() const351 QGradientStops KGradientSelector::stops() const
352 {
353     return d->gradient.stops();
354 }
355 
setColors(const QColor & col1,const QColor & col2)356 void KGradientSelector::setColors(const QColor &col1, const QColor &col2)
357 {
358     d->gradient.setColorAt(0.0, col1);
359     d->gradient.setColorAt(1.0, col2);
360     update();
361 }
362 
setText(const QString & t1,const QString & t2)363 void KGradientSelector::setText(const QString &t1, const QString &t2)
364 {
365     d->text1 = t1;
366     d->text2 = t2;
367     update();
368 }
369 
setFirstColor(const QColor & col)370 void KGradientSelector::setFirstColor(const QColor &col)
371 {
372     d->gradient.setColorAt(0.0, col);
373     update();
374 }
375 
setSecondColor(const QColor & col)376 void KGradientSelector::setSecondColor(const QColor &col)
377 {
378     d->gradient.setColorAt(1.0, col);
379     update();
380 }
381 
setFirstText(const QString & t)382 void KGradientSelector::setFirstText(const QString &t)
383 {
384     d->text1 = t;
385     update();
386 }
387 
setSecondText(const QString & t)388 void KGradientSelector::setSecondText(const QString &t)
389 {
390     d->text2 = t;
391     update();
392 }
393 
firstColor() const394 QColor KGradientSelector::firstColor() const
395 {
396     return d->gradient.stops().first().second;
397 }
398 
secondColor() const399 QColor KGradientSelector::secondColor() const
400 {
401     return d->gradient.stops().last().second;
402 }
403 
firstText() const404 QString KGradientSelector::firstText() const
405 {
406     return d->text1;
407 }
408 
secondText() const409 QString KGradientSelector::secondText() const
410 {
411     return d->text2;
412 }
413