Krita Source Code Documentation
Loading...
Searching...
No Matches
KisStopGradientSlider.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2004 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2016 Sven Langkamp <sven.langkamp@gmail.com>
4 * SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include <QWindow>
10#include <QPainter>
11#include <QPixmap>
12#include <QMouseEvent>
13#include <QPaintEvent>
14#include <QKeyEvent>
15#include <QPolygon>
16#include <QFontMetrics>
17#include <QStyle>
18#include <QApplication>
19#include <QStyleOptionToolButton>
20#include <QPainterPath>
21#include <QColorDialog>
22
24#include "kis_global.h"
25#include "kis_debug.h"
26#include "krita_utils.h"
27#include <KoColor.h>
29#include <kconfiggroup.h>
30#include <ksharedconfig.h>
31
33
34KisStopGradientSlider::KisStopGradientSlider(QWidget *parent, Qt::WindowFlags f)
35 : QWidget(parent, f)
36 , m_selectedStop(0)
37 , m_hoveredStop(-1)
38 , m_drag(0)
39 , m_updateCompressor(40, KisSignalCompressor::FIRST_ACTIVE)
40{
41 QLinearGradient defaultGradient;
43
45 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
46 setFocusPolicy(Qt::WheelFocus);
47 setMouseTracking(true);
48
49 connect(this, SIGNAL(updateRequested()), &m_updateCompressor, SLOT(start()));
50 connect(&m_updateCompressor, SIGNAL(timeout()), this, SLOT(update()));
51
52 QWindow *window = this->window()->windowHandle();
53 if (window) {
54 connect(window, SIGNAL(screenChanged(QScreen*)), SLOT(updateHandleSize()));
55 }
57}
58
60{
61 QFontMetrics fm(font());
62 const int h = qMax(15, static_cast<int>(std::ceil(fm.height() * 0.75)));
63 m_handleSize = QSize(h * 0.75, h);
64}
65
67{
68 // the size of the default text!
69 return m_handleSize.width();
70}
71
73{
74 m_gradient = gradient ? gradient : m_defaultGradient;
75
76 if (m_gradient) {
78 } else {
79 m_selectedStop = -1;
80 }
82 Q_EMIT updateRequested();
83}
84
86{
87 QPainter painter(this);
88
89 const QRect previewRect = gradientStripeRect();
90
91 if (m_gradient) {
92 // Gradient
94 // Stops
95 painter.setRenderHint(QPainter::Antialiasing, true);
96 const QRect handlesRect = this->handlesStripeRect();
97 const bool hasFocus = this->hasFocus();
98 const QColor highlightColor = palette().color(QPalette::Highlight);
99 const QList<KoGradientStop> handlePositions = m_gradient->stops();
100 for (int i = 0; i < handlePositions.count(); ++i) {
101 if (i == m_selectedStop) {
102 continue;
103 }
105 if (handlePositions[i].type == FOREGROUNDSTOP) {
107 } else if (handlePositions[i].type == BACKGROUNDSTOP) {
109 } else {
111 }
113 painter,
114 QPointF(handlesRect.left() + handlePositions[i].position * handlesRect.width(), handlesRect.top()),
115 QSizeF(m_handleSize),
116 false, i == m_hoveredStop, hasFocus,
117 highlightColor,
118 { colorType, handlePositions[i].color.toQColor() }
119 );
120 }
121 if (handlePositions.count() > 0 && m_selectedStop >= 0 && m_selectedStop < handlePositions.count()) {
123 if (handlePositions[m_selectedStop].type == FOREGROUNDSTOP) {
125 } else if (handlePositions[m_selectedStop].type == BACKGROUNDSTOP) {
127 } else {
129 }
131 painter,
132 QPointF(handlesRect.left() + handlePositions[m_selectedStop].position * handlesRect.width(), handlesRect.top()),
133 QSizeF(m_handleSize),
134 true, false, hasFocus,
135 highlightColor,
136 { colorType, handlePositions[m_selectedStop].color.toQColor() }
137 );
138 }
139 } else {
140 painter.setPen(palette().color(QPalette::Mid));
141 painter.drawRect(previewRect);
142 }
143}
144
145int findNearestHandle(qreal t, const qreal tolerance, const QList<KoGradientStop> &stops)
146{
147 int result = -1;
148 qreal minDistance = tolerance;
149
150 for (int i = 0; i < stops.size(); i++) {
151 const KoGradientStop &stop = stops[i];
152
153 const qreal distance = qAbs(t - stop.position);
154 if (distance < minDistance) {
155 minDistance = distance;
156 result = i;
157 }
158 }
159
160 return result;
161}
162
163
165{
166 if (!allowedClickRegion(handleClickTolerance()).contains(e->pos())) {
167 QWidget::mousePressEvent(e);
168 return;
169 }
170
171 if (e->buttons() != Qt::LeftButton ) {
172 QWidget::mousePressEvent(e);
173 return;
174 }
175
176 const QRect handlesRect = this->handlesStripeRect();
177 const qreal t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width();
178 const QList<KoGradientStop> stops = m_gradient->stops();
179
180 const int clickedStop = findNearestHandle(t, qreal(handleClickTolerance()) / handlesRect.width(), stops);
181
182 if (clickedStop >= 0) {
183 if (m_selectedStop != clickedStop) {
184 m_selectedStop = clickedStop;
186 }
187 m_drag = true;
188 } else {
189 insertStop(qBound(0.0, t, 1.0));
190 m_drag = true;
191 }
192
193 updateHoveredStop(e->pos());
194 Q_EMIT updateRequested();
195}
196
198{
199 Q_UNUSED(e);
200 m_drag = false;
201 int previousHoveredStop = m_hoveredStop;
202 updateHoveredStop(e->pos());
203 if (previousHoveredStop != m_hoveredStop) {
204 Q_EMIT updateRequested();
205 }
206}
207
209{
210 int result = 0;
211
212 for (int i = 0; i < stops.size(); i++) {
213 if (stop.position <= stops[i].position) break;
214
215 result = i + 1;
216 }
217
218 return result;
219}
220
222{
223 int previousHoveredStop = m_hoveredStop;
224 updateHoveredStop(e->pos());
225
226 if (m_drag) {
227 QList<KoGradientStop> stops = m_gradient->stops();
228 const QRect augmentedRect = kisGrowRect(rect(), removeStopDistance);
229
230 if (stops.size() > 2 && !augmentedRect.contains(e->pos()))
231 {
232 if (m_selectedStop >= 0) {
234 stops.removeAt(m_selectedStop);
235 m_selectedStop = -1;
236 }
237 } else {
238 const QRect handlesRect = this->handlesStripeRect();
239 double t = qreal(e->pos().x() - handlesRect.left()) / handlesRect.width();
240 if (m_selectedStop < 0) {
241 if (augmentedRect.contains(e->pos())) {
242 m_removedStop.position = qBound(0.0, t, 1.0);
243 const int newPos = getNewInsertPosition(m_removedStop, stops);
244 stops.insert(newPos, m_removedStop);
245 m_selectedStop = newPos;
246 } else {
247 return;
248 }
249 } else {
250 KoGradientStop draggedStop = stops[m_selectedStop];
251 draggedStop.position = qBound(0.0, t, 1.0);
252
253 stops.removeAt(m_selectedStop);
254 const int newPos = getNewInsertPosition(draggedStop, stops);
255 stops.insert(newPos, draggedStop);
256 m_selectedStop = newPos;
257 }
258 }
259
260 m_gradient->setStops(stops);
262
263 Q_EMIT updateRequested();
264 } else {
265 if (previousHoveredStop != m_hoveredStop) {
266 Q_EMIT updateRequested();
267 }
268 QWidget::mouseMoveEvent(e);
269 }
270}
271
273{
274 if (e->button() != Qt::LeftButton) {
275 QWidget::mouseDoubleClickEvent(e);
276 return;
277 }
278
279 const QRect handlesRect = this->handlesStripeRect();
280 const qreal t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width();
281 const QList<KoGradientStop> stops = m_gradient->stops();
282
283 if (qAbs(t - stops[m_selectedStop].position) < qreal(handleClickTolerance()) / handlesRect.width()) {
285 }
286}
287
288void KisStopGradientSlider::handleIncrementInput(int direction, Qt::KeyboardModifiers modifiers)
289{
290 if (direction == 0) {
291 return;
292 }
293 QList<KoGradientStop> stops = m_gradient->stops();
294 if (modifiers & Qt::ControlModifier) {
295 m_selectedStop += direction < 0 ? -1 : 1;
296 m_selectedStop = qBound(0, m_selectedStop, stops.count() - 1);
297 } else if (m_selectedStop >= 0 && m_selectedStop < stops.count()) {
298 const qreal increment = modifiers & Qt::ShiftModifier ? 0.001 : 0.01;
299 KoGradientStop draggedStop = stops[m_selectedStop];
300 draggedStop.position += direction < 0 ? -increment : increment;
301 draggedStop.position = qBound(0.0, draggedStop.position, 1.0);
302
303 stops.removeAt(m_selectedStop);
304 const int newPos = getNewInsertPosition(draggedStop, stops);
305 stops.insert(newPos, draggedStop);
306 m_selectedStop = newPos;
307 m_gradient->setStops(stops);
308 }
310 Q_EMIT updateRequested();
311}
312
314{
315 if (e->angleDelta().y() != 0) {
316 handleIncrementInput(e->angleDelta().y(), e->modifiers());
317 e->accept();
318 } else {
319 QWidget::wheelEvent(e);
320 }
321}
322
324{
325 switch (e->key()) {
326 case Qt::Key_Left:
327 handleIncrementInput(-1, e->modifiers());
328 break;
329 case Qt::Key_Right:
330 handleIncrementInput(1, e->modifiers());
331 break;
332 case Qt::Key_Return:
333 case Qt::Key_Enter:
335 break;
336 case Qt::Key_Delete:
338 break;
339 default:
340 QWidget::keyPressEvent(e);
341 break;
342 }
343}
344
346{
347 m_hoveredStop = -1;
348 Q_EMIT updateRequested();
349 QWidget::leaveEvent(e);
350}
351
353{
354 const bool isInAllowedRegion =
356
357 if (isInAllowedRegion) {
358 const QRect handlesRect = this->handlesStripeRect();
359 const qreal t = (qreal(pos.x()) - handlesRect.x()) / handlesRect.width();
360 const QList<KoGradientStop> stops = m_gradient->stops();
361
362 m_hoveredStop = m_drag ? -1 : findNearestHandle(t, qreal(handleClickTolerance()) / handlesRect.width(), stops);
363
364 } else {
365 m_hoveredStop = -1;
366 }
367}
368
370{
371 KIS_ASSERT_RECOVER(t >= 0 && t <= 1.0 ) {
372 t = qBound(0.0, t, 1.0);
373 }
374
375 QList<KoGradientStop> stops = m_gradient->stops();
376
377 KoColor color;
378 m_gradient->colorAt(color, t);
379
380 const KoGradientStop stop(t, color, COLORSTOP);
381 const int newPos = getNewInsertPosition(stop, stops);
382
383 stops.insert(newPos, stop);
384 m_gradient->setStops(stops);
385
386 m_selectedStop = newPos;
388}
389
391{
392 const qreal handleWidthOverTwo = static_cast<qreal>(m_handleSize.width()) / 2.0;
393 const int hMargin = static_cast<int>(std::ceil(handleWidthOverTwo)) + 2;
394 return rect().adjusted(hMargin, 0, -hMargin, 0);
395}
396
398{
399 const QRect rc = sliderRect();
400 return rc.adjusted(0, 0, 0, -m_handleSize.height() - 4);
401}
402
404{
405 const QRect rc = sliderRect();
406 return rc.adjusted(0, rc.height() - (m_handleSize.height() + 2), 0, -2);
407}
408
410{
411 Q_UNUSED(tolerance);
412 QRegion result;
413 result += rect();
414 return result;
415}
416
421
423{
424 m_selectedStop = selected;
426
427 Q_EMIT updateRequested();
428}
429
431{
432 if (m_selectedStop < 0) {
434 } else if (m_selectedStop > 0) {
436 }
437}
438
440{
441 if (m_selectedStop < 0) {
443 } else if (m_selectedStop < m_gradient->stops().size() - 1) {
445 }
446}
447
448void KisStopGradientSlider::deleteSelectedStop(bool selectNeighborStop)
449{
450 if (m_drag || m_selectedStop < 0) {
451 return;
452 }
453
454 QList<KoGradientStop> stops = m_gradient->stops();
455
456 if (stops.size() <= 2) {
457 return;
458 }
459
460 const qreal pos = stops[m_selectedStop].position;
461 stops.removeAt(m_selectedStop);
462 if (selectNeighborStop) {
463 m_selectedStop = findNearestHandle(pos, 2.0, stops);
464 } else {
465 m_selectedStop = -1;
466 }
467 m_gradient->setStops(stops);
469}
470
472{
473 QFontMetrics fm(font());
474 const int h = fm.height();
475
476 QStyleOptionToolButton opt;
477 QSize sz = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(h, h), this);
478
479 return qMax(32, sz.height()) + m_handleSize.height();
480}
481
483{
484 const int h = minimalHeight();
485 return QSize(2 * h, h);
486}
487
489{
490 const int h = minimalHeight();
491 return QSize(h, h);
492}
493
495{
496 QList<KoGradientStop> stops = m_gradient->stops();
497 if (m_selectedStop < 0 || m_selectedStop >= stops.count()) {
498 return;
499 }
500
501 KConfigGroup cfg = KSharedConfig::openConfig()->group("colorselector");
502 bool usePlatformDialog = cfg.readEntry("UsePlatformColorDialog", false);
503 QDialog *colorDialog = nullptr;
504
505 if (!usePlatformDialog) {
507 KisDlgInternalColorSelector *dialog = new KisDlgInternalColorSelector(this, stops[m_selectedStop].color, cfg, i18n("Choose a color"));
508 dialog->setPreviousColor(stops[m_selectedStop].color);
509 auto setColorFn = [dialog, stops, this]() mutable
510 {
511 stops[m_selectedStop].type = COLORSTOP;
512 stops[m_selectedStop].color = dialog->getCurrentColor();
513 m_gradient->setStops(stops);
515 Q_EMIT updateRequested();
516 };
518 connect(dialog, &QDialog::accepted, setColorFn);
519 colorDialog = dialog;
520 } else {
521 QColorDialog *dialog = new QColorDialog(this);
522 dialog->setCurrentColor(stops[m_selectedStop].color.toQColor());
523 auto setColorFn = [dialog, stops, this]() mutable
524 {
525 stops[m_selectedStop].type = COLORSTOP;
526 stops[m_selectedStop].color.fromQColor(dialog->currentColor());
527 m_gradient->setStops(stops);
529 Q_EMIT updateRequested();
530 };
531 connect(dialog, &QColorDialog::currentColorChanged, setColorFn);
532 connect(dialog, &QDialog::accepted, setColorFn);
533 colorDialog = dialog;
534 }
535 // common functionality
536 connect(colorDialog, &QDialog::rejected, [stops, this]()
537 {
538 m_gradient->setStops(stops);
540 Q_EMIT updateRequested();
541 });
542 colorDialog->setAttribute(Qt::WA_DeleteOnClose);
543 colorDialog->show();
544 colorDialog->raise();
545 colorDialog->activateWindow();
546}
int findNearestHandle(qreal t, const qreal tolerance, const QList< KoGradientStop > &stops)
int getNewInsertPosition(const KoGradientStop &stop, const QList< KoGradientStop > &stops)
qreal distance(const QPointF &p1, const QPointF &p2)
@ FOREGROUNDSTOP
@ BACKGROUNDSTOP
@ COLORSTOP
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
The KisInternalColorSelector class.
void signalForegroundColorChosen(KoColor color)
signalForegroundColorChosen The most important signal. This will sent out when a color has been chose...
void wheelEvent(QWheelEvent *e) override
void paintEvent(QPaintEvent *) override
void sigSelectedStop(int stop)
void mouseMoveEvent(QMouseEvent *e) override
KisSignalCompressor m_updateCompressor
void setGradientResource(KoStopGradientSP gradient)
void updateHoveredStop(const QPoint &pos)
void leaveEvent(QEvent *e) override
QSize sizeHint() const override
KisStopGradientSlider(QWidget *parent=0, Qt::WindowFlags f=Qt::WindowFlags())
QRegion allowedClickRegion(int tolerance) const
void mouseReleaseEvent(QMouseEvent *e) override
void mouseDoubleClickEvent(QMouseEvent *e) override
KoStopGradientSP m_defaultGradient
static constexpr int removeStopDistance
void deleteSelectedStop(bool selectNeighborStop=true)
void setSelectedStop(int selected)
QSize minimumSizeHint() const override
void keyPressEvent(QKeyEvent *e) override
void mousePressEvent(QMouseEvent *e) override
void handleIncrementInput(int direction, Qt::KeyboardModifiers modifiers)
static QSharedPointer< KoStopGradient > fromQGradient(const QGradient *gradient)
Creates KoStopGradient from a QGradient.
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
void paintGradientBox(QPainter &painter, const KoAbstractGradientSP gradient, const QRectF &rect)
void paintStopHandle(QPainter &painter, const QPointF &position, const QSizeF &size, bool isSelected, bool isHovered, bool hasFocus, const QColor &highlightColor, const StopHandleColor &color1, const StopHandleColor &color2)
rgba palette[MAX_PALETTE]
Definition palette.c:35