| 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: |
| 34 | KGradientSelectorPrivate(KGradientSelector *qq) |
| 35 | : q(qq) |
| 36 | { |
| 37 | } |
| 38 | |
| 39 | KGradientSelector *q; |
| 40 | QLinearGradient gradient; |
| 41 | QString text1; |
| 42 | QString text2; |
| 43 | }; |
| 44 | |
| 45 | KSelector::KSelector(QWidget *parent) |
| 46 | : QAbstractSlider(parent) |
| 47 | , d(new KSelectorPrivate) |
| 48 | { |
| 49 | setOrientation(Qt::Horizontal); |
| 50 | } |
| 51 | |
| 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 | |
| 64 | void KSelector::setIndent(bool i) |
| 65 | { |
| 66 | d->m_indent = i; |
| 67 | } |
| 68 | |
| 69 | bool KSelector::indent() const |
| 70 | { |
| 71 | return d->m_indent; |
| 72 | } |
| 73 | |
| 74 | QRect KSelector::contentsRect() const |
| 75 | { |
| 76 | int w = indent() ? style()->pixelMetric(metric: 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 | |
| 107 | void KSelector::paintEvent(QPaintEvent *) |
| 108 | { |
| 109 | QPainter painter; |
| 110 | int w = style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth); |
| 111 | int iw = (w < ARROWSIZE) ? ARROWSIZE : w; |
| 112 | |
| 113 | painter.begin(this); |
| 114 | |
| 115 | if (indent()) { |
| 116 | QStyleOptionFrame opt; |
| 117 | opt.initFrom(w: this); |
| 118 | opt.state = QStyle::State_Sunken; |
| 119 | if (orientation() == Qt::Vertical) { |
| 120 | opt.rect.adjust(dx1: 0, dy1: iw - w, dx2: -5, dy2: w - iw); |
| 121 | } else { |
| 122 | opt.rect.adjust(dx1: iw - w, dy1: 0, dx2: w - iw, dy2: -5); |
| 123 | } |
| 124 | QBrush oldBrush = painter.brush(); |
| 125 | painter.setBrush(Qt::NoBrush); |
| 126 | style()->drawPrimitive(pe: QStyle::PE_Frame, opt: &opt, p: &painter, w: this); |
| 127 | painter.setBrush(oldBrush); |
| 128 | } |
| 129 | |
| 130 | drawContents(&painter); |
| 131 | |
| 132 | QPoint pos = calcArrowPos(val: value()); |
| 133 | drawArrow(painter: &painter, pos); |
| 134 | |
| 135 | painter.end(); |
| 136 | } |
| 137 | |
| 138 | void KSelector::mousePressEvent(QMouseEvent *e) |
| 139 | { |
| 140 | setSliderDown(true); |
| 141 | moveArrow(pos: e->pos()); |
| 142 | } |
| 143 | |
| 144 | void KSelector::mouseMoveEvent(QMouseEvent *e) |
| 145 | { |
| 146 | moveArrow(pos: e->pos()); |
| 147 | } |
| 148 | |
| 149 | void KSelector::mouseReleaseEvent(QMouseEvent *e) |
| 150 | { |
| 151 | moveArrow(pos: e->pos()); |
| 152 | setSliderDown(false); |
| 153 | } |
| 154 | |
| 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 | |
| 163 | void KSelector::moveArrow(const QPoint &pos) |
| 164 | { |
| 165 | int val; |
| 166 | int w = style()->pixelMetric(metric: 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 | |
| 179 | QPoint KSelector::calcArrowPos(int val) |
| 180 | { |
| 181 | QPoint p; |
| 182 | int w = style()->pixelMetric(metric: 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 | |
| 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 | |
| 243 | 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 | |
| 258 | void KSelector::drawContents(QPainter *) |
| 259 | { |
| 260 | } |
| 261 | |
| 262 | void KSelector::drawArrow(QPainter *painter, const QPoint &pos) |
| 263 | { |
| 264 | painter->setPen(QPen()); |
| 265 | painter->setBrush(QBrush(palette().color(cr: 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(pe: d->arrowPE, opt: &o, p: painter, w: this); |
| 275 | } |
| 276 | |
| 277 | //---------------------------------------------------------------------------- |
| 278 | |
| 279 | KGradientSelector::KGradientSelector(QWidget *parent) |
| 280 | : KSelector(parent) |
| 281 | , d(new KGradientSelectorPrivate(this)) |
| 282 | { |
| 283 | } |
| 284 | |
| 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 | |
| 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(x: 0, y: 0, w: 8, h: 8, c: Qt::black); |
| 307 | patternPainter.fillRect(x: 8, y: 8, w: 8, h: 8, c: Qt::black); |
| 308 | patternPainter.fillRect(x: 0, y: 8, w: 8, h: 8, c: Qt::white); |
| 309 | patternPainter.fillRect(x: 8, y: 0, w: 8, h: 8, c: 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(rgb: firstColor().rgb()) > 180 ? Qt::black : Qt::white); |
| 319 | painter->setPen(pen); |
| 320 | painter->drawText(x: xPos, y: yPos, s: 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(rgb: secondColor().rgb()) > 180 ? Qt::black : Qt::white); |
| 325 | painter->setPen(pen); |
| 326 | painter->drawText(x: xPos, y: yPos, s: d->text1); |
| 327 | } else { |
| 328 | int yPos = contentsRect().bottom() - painter->fontMetrics().descent() - 2; |
| 329 | |
| 330 | QPen pen(qGray(rgb: firstColor().rgb()) > 180 ? Qt::black : Qt::white); |
| 331 | painter->setPen(pen); |
| 332 | painter->drawText(x: contentsRect().left() + 2, y: yPos, s: d->text1); |
| 333 | |
| 334 | pen.setColor(qGray(rgb: secondColor().rgb()) > 180 ? Qt::black : Qt::white); |
| 335 | painter->setPen(pen); |
| 336 | painter->drawText(x: contentsRect().right() - painter->fontMetrics().horizontalAdvance(d->text2) - 2, y: yPos, s: d->text2); |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | QSize KGradientSelector::minimumSize() const |
| 341 | { |
| 342 | return sizeHint(); |
| 343 | } |
| 344 | |
| 345 | void KGradientSelector::setStops(const QGradientStops &stops) |
| 346 | { |
| 347 | d->gradient.setStops(stops); |
| 348 | update(); |
| 349 | } |
| 350 | |
| 351 | QGradientStops KGradientSelector::stops() const |
| 352 | { |
| 353 | return d->gradient.stops(); |
| 354 | } |
| 355 | |
| 356 | void KGradientSelector::setColors(const QColor &col1, const QColor &col2) |
| 357 | { |
| 358 | d->gradient.setColorAt(pos: 0.0, color: col1); |
| 359 | d->gradient.setColorAt(pos: 1.0, color: col2); |
| 360 | update(); |
| 361 | } |
| 362 | |
| 363 | void KGradientSelector::setText(const QString &t1, const QString &t2) |
| 364 | { |
| 365 | d->text1 = t1; |
| 366 | d->text2 = t2; |
| 367 | update(); |
| 368 | } |
| 369 | |
| 370 | void KGradientSelector::setFirstColor(const QColor &col) |
| 371 | { |
| 372 | d->gradient.setColorAt(pos: 0.0, color: col); |
| 373 | update(); |
| 374 | } |
| 375 | |
| 376 | void KGradientSelector::setSecondColor(const QColor &col) |
| 377 | { |
| 378 | d->gradient.setColorAt(pos: 1.0, color: col); |
| 379 | update(); |
| 380 | } |
| 381 | |
| 382 | void KGradientSelector::setFirstText(const QString &t) |
| 383 | { |
| 384 | d->text1 = t; |
| 385 | update(); |
| 386 | } |
| 387 | |
| 388 | void KGradientSelector::setSecondText(const QString &t) |
| 389 | { |
| 390 | d->text2 = t; |
| 391 | update(); |
| 392 | } |
| 393 | |
| 394 | QColor KGradientSelector::firstColor() const |
| 395 | { |
| 396 | return d->gradient.stops().first().second; |
| 397 | } |
| 398 | |
| 399 | QColor KGradientSelector::secondColor() const |
| 400 | { |
| 401 | return d->gradient.stops().last().second; |
| 402 | } |
| 403 | |
| 404 | QString KGradientSelector::firstText() const |
| 405 | { |
| 406 | return d->text1; |
| 407 | } |
| 408 | |
| 409 | QString KGradientSelector::secondText() const |
| 410 | { |
| 411 | return d->text2; |
| 412 | } |
| 413 | |
| 414 | #include "moc_kselector.cpp" |
| 415 | |