1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qtgradientstopswidget.h"
5#include "qtgradientstopsmodel.h"
6
7#include <QtCore/QMap>
8#include <QtCore/QHash>
9#include <QtCore/QMimeData>
10#include <QtGui/QImage>
11#include <QtGui/QPainter>
12#include <QtWidgets/QScrollBar>
13#include <QtGui/QMouseEvent>
14#include <QtWidgets/QRubberBand>
15#include <QtWidgets/QMenu>
16
17QT_BEGIN_NAMESPACE
18
19class QtGradientStopsWidgetPrivate : public QObject
20{
21 Q_OBJECT
22 QtGradientStopsWidget *q_ptr;
23 Q_DECLARE_PUBLIC(QtGradientStopsWidget)
24public:
25 void setGradientStopsModel(QtGradientStopsModel *model);
26
27 void slotStopAdded(QtGradientStop *stop);
28 void slotStopRemoved(QtGradientStop *stop);
29 void slotStopMoved(QtGradientStop *stop, qreal newPos);
30 void slotStopsSwapped(QtGradientStop *stop1, QtGradientStop *stop2);
31 void slotStopChanged(QtGradientStop *stop, const QColor &newColor);
32 void slotStopSelected(QtGradientStop *stop, bool selected);
33 void slotCurrentStopChanged(QtGradientStop *stop);
34 void slotNewStop();
35 void slotDelete();
36 void slotFlipAll();
37 void slotSelectAll();
38 void slotZoomIn();
39 void slotZoomOut();
40 void slotResetZoom();
41
42 double fromViewport(int x) const;
43 double toViewport(double x) const;
44 QtGradientStop *stopAt(const QPoint &viewportPos) const;
45 QList<QtGradientStop *> stopsAt(const QPoint &viewportPos) const;
46 void setupMove(QtGradientStop *stop, int x);
47 void ensureVisible(double x); // x = stop position
48 void ensureVisible(QtGradientStop *stop);
49 QtGradientStop *newStop(const QPoint &viewportPos);
50
51 bool m_backgroundCheckered;
52 QtGradientStopsModel *m_model;
53 double m_handleSize;
54 int m_scaleFactor;
55 double m_zoom;
56
57#ifndef QT_NO_DRAGANDDROP
58 QtGradientStop *m_dragStop;
59 QtGradientStop *m_changedStop;
60 QtGradientStop *m_clonedStop;
61 QtGradientStopsModel *m_dragModel;
62 QColor m_dragColor;
63 void clearDrag();
64 void removeClonedStop();
65 void restoreChangedStop();
66 void changeStop(qreal pos);
67 void cloneStop(qreal pos);
68#endif
69
70 QRubberBand *m_rubber;
71 QPoint m_clickPos;
72
73 QList<QtGradientStop *> m_stops;
74
75 bool m_moving;
76 int m_moveOffset;
77 QHash<QtGradientStop *, qreal> m_moveStops;
78
79 QMap<qreal, QColor> m_moveOriginal;
80};
81
82void QtGradientStopsWidgetPrivate::setGradientStopsModel(QtGradientStopsModel *model)
83{
84 if (m_model == model)
85 return;
86
87 if (m_model) {
88 disconnect(sender: m_model, signal: &QtGradientStopsModel::stopAdded,
89 receiver: this, slot: &QtGradientStopsWidgetPrivate::slotStopAdded);
90 disconnect(sender: m_model, signal: &QtGradientStopsModel::stopRemoved,
91 receiver: this, slot: &QtGradientStopsWidgetPrivate::slotStopRemoved);
92 disconnect(sender: m_model, signal: &QtGradientStopsModel::stopMoved,
93 receiver: this, slot: &QtGradientStopsWidgetPrivate::slotStopMoved);
94 disconnect(sender: m_model, signal: &QtGradientStopsModel::stopsSwapped,
95 receiver: this, slot: &QtGradientStopsWidgetPrivate::slotStopsSwapped);
96 disconnect(sender: m_model, signal: &QtGradientStopsModel::stopChanged,
97 receiver: this, slot: &QtGradientStopsWidgetPrivate::slotStopChanged);
98 disconnect(sender: m_model, signal: &QtGradientStopsModel::stopSelected,
99 receiver: this, slot: &QtGradientStopsWidgetPrivate::slotStopSelected);
100 disconnect(sender: m_model, signal: &QtGradientStopsModel::currentStopChanged,
101 receiver: this, slot: &QtGradientStopsWidgetPrivate::slotCurrentStopChanged);
102
103 m_stops.clear();
104 }
105
106 m_model = model;
107
108 if (m_model) {
109 connect(sender: m_model, signal: &QtGradientStopsModel::stopAdded,
110 context: this, slot: &QtGradientStopsWidgetPrivate::slotStopAdded);
111 connect(sender: m_model, signal: &QtGradientStopsModel::stopRemoved,
112 context: this, slot: &QtGradientStopsWidgetPrivate::slotStopRemoved);
113 connect(sender: m_model, signal: &QtGradientStopsModel::stopMoved,
114 context: this, slot: &QtGradientStopsWidgetPrivate::slotStopMoved);
115 connect(sender: m_model, signal: &QtGradientStopsModel::stopsSwapped,
116 context: this, slot: &QtGradientStopsWidgetPrivate::slotStopsSwapped);
117 connect(sender: m_model, signal: &QtGradientStopsModel::stopChanged,
118 context: this, slot: &QtGradientStopsWidgetPrivate::slotStopChanged);
119 connect(sender: m_model, signal: &QtGradientStopsModel::stopSelected,
120 context: this, slot: &QtGradientStopsWidgetPrivate::slotStopSelected);
121 connect(sender: m_model, signal: &QtGradientStopsModel::currentStopChanged,
122 context: this, slot: &QtGradientStopsWidgetPrivate::slotCurrentStopChanged);
123
124 const auto stopsMap = m_model->stops();
125 for (auto it = stopsMap.cbegin(), end = stopsMap.cend(); it != end; ++it)
126 slotStopAdded(stop: it.value());
127
128 const auto selected = m_model->selectedStops();
129 for (QtGradientStop *stop : selected)
130 slotStopSelected(stop, selected: true);
131
132 slotCurrentStopChanged(stop: m_model->currentStop());
133 }
134}
135
136double QtGradientStopsWidgetPrivate::fromViewport(int x) const
137{
138 QSize size = q_ptr->viewport()->size();
139 int w = size.width();
140 int max = q_ptr->horizontalScrollBar()->maximum();
141 int val = q_ptr->horizontalScrollBar()->value();
142 return (double(x) * m_scaleFactor + w * val) / (w * (m_scaleFactor + max));
143}
144
145double QtGradientStopsWidgetPrivate::toViewport(double x) const
146{
147 QSize size = q_ptr->viewport()->size();
148 int w = size.width();
149 int max = q_ptr->horizontalScrollBar()->maximum();
150 int val = q_ptr->horizontalScrollBar()->value();
151 return w * (x * (m_scaleFactor + max) - val) / m_scaleFactor;
152}
153
154QtGradientStop *QtGradientStopsWidgetPrivate::stopAt(const QPoint &viewportPos) const
155{
156 double posY = m_handleSize / 2;
157 for (QtGradientStop *stop : m_stops) {
158 double posX = toViewport(x: stop->position());
159
160 double x = viewportPos.x() - posX;
161 double y = viewportPos.y() - posY;
162
163 if ((m_handleSize * m_handleSize / 4) > (x * x + y * y))
164 return stop;
165 }
166 return 0;
167}
168
169QList<QtGradientStop *> QtGradientStopsWidgetPrivate::stopsAt(const QPoint &viewportPos) const
170{
171 QList<QtGradientStop *> stops;
172 double posY = m_handleSize / 2;
173 for (QtGradientStop *stop : m_stops) {
174 double posX = toViewport(x: stop->position());
175
176 double x = viewportPos.x() - posX;
177 double y = viewportPos.y() - posY;
178
179 if ((m_handleSize * m_handleSize / 4) > (x * x + y * y))
180 stops.append(t: stop);
181 }
182 return stops;
183}
184
185void QtGradientStopsWidgetPrivate::setupMove(QtGradientStop *stop, int x)
186{
187 m_model->setCurrentStop(stop);
188
189 int viewportX = qRound(d: toViewport(x: stop->position()));
190 m_moveOffset = x - viewportX;
191
192 const auto stops = m_stops;
193 m_stops.clear();
194 for (QtGradientStop *s : stops) {
195 if (m_model->isSelected(stop: s) || s == stop) {
196 m_moveStops[s] = s->position() - stop->position();
197 m_stops.append(t: s);
198 } else {
199 m_moveOriginal[s->position()] = s->color();
200 }
201 }
202 for (QtGradientStop *s : stops) {
203 if (!m_model->isSelected(stop: s))
204 m_stops.append(t: s);
205 }
206 m_stops.removeAll(t: stop);
207 m_stops.prepend(t: stop);
208}
209
210void QtGradientStopsWidgetPrivate::ensureVisible(double x)
211{
212 double viewX = toViewport(x);
213 if (viewX < 0 || viewX > q_ptr->viewport()->size().width()) {
214 int max = q_ptr->horizontalScrollBar()->maximum();
215 int newVal = qRound(d: x * (max + m_scaleFactor) - m_scaleFactor / 2);
216 q_ptr->horizontalScrollBar()->setValue(newVal);
217 }
218}
219
220void QtGradientStopsWidgetPrivate::ensureVisible(QtGradientStop *stop)
221{
222 if (!stop)
223 return;
224 ensureVisible(x: stop->position());
225}
226
227QtGradientStop *QtGradientStopsWidgetPrivate::newStop(const QPoint &viewportPos)
228{
229 QtGradientStop *copyStop = stopAt(viewportPos);
230 double posX = fromViewport(x: viewportPos.x());
231 QtGradientStop *stop = m_model->at(pos: posX);
232 if (!stop) {
233 QColor newColor;
234 if (copyStop)
235 newColor = copyStop->color();
236 else
237 newColor = m_model->color(pos: posX);
238 if (!newColor.isValid())
239 newColor = Qt::white;
240 stop = m_model->addStop(pos: posX, color: newColor);
241 }
242 return stop;
243}
244
245void QtGradientStopsWidgetPrivate::slotStopAdded(QtGradientStop *stop)
246{
247 m_stops.append(t: stop);
248 q_ptr->viewport()->update();
249}
250
251void QtGradientStopsWidgetPrivate::slotStopRemoved(QtGradientStop *stop)
252{
253 m_stops.removeAll(t: stop);
254 q_ptr->viewport()->update();
255}
256
257void QtGradientStopsWidgetPrivate::slotStopMoved(QtGradientStop *stop, qreal newPos)
258{
259 Q_UNUSED(stop);
260 Q_UNUSED(newPos);
261 q_ptr->viewport()->update();
262}
263
264void QtGradientStopsWidgetPrivate::slotStopsSwapped(QtGradientStop *stop1, QtGradientStop *stop2)
265{
266 Q_UNUSED(stop1);
267 Q_UNUSED(stop2);
268 q_ptr->viewport()->update();
269}
270
271void QtGradientStopsWidgetPrivate::slotStopChanged(QtGradientStop *stop, const QColor &newColor)
272{
273 Q_UNUSED(stop);
274 Q_UNUSED(newColor);
275 q_ptr->viewport()->update();
276}
277
278void QtGradientStopsWidgetPrivate::slotStopSelected(QtGradientStop *stop, bool selected)
279{
280 Q_UNUSED(stop);
281 Q_UNUSED(selected);
282 q_ptr->viewport()->update();
283}
284
285void QtGradientStopsWidgetPrivate::slotCurrentStopChanged(QtGradientStop *stop)
286{
287 Q_UNUSED(stop);
288
289 if (!m_model)
290 return;
291 q_ptr->viewport()->update();
292 if (stop) {
293 m_stops.removeAll(t: stop);
294 m_stops.prepend(t: stop);
295 }
296}
297
298void QtGradientStopsWidgetPrivate::slotNewStop()
299{
300 if (!m_model)
301 return;
302
303 QtGradientStop *stop = newStop(viewportPos: m_clickPos);
304
305 if (!stop)
306 return;
307
308 m_model->clearSelection();
309 m_model->selectStop(stop, select: true);
310 m_model->setCurrentStop(stop);
311}
312
313void QtGradientStopsWidgetPrivate::slotDelete()
314{
315 if (!m_model)
316 return;
317
318 m_model->deleteStops();
319}
320
321void QtGradientStopsWidgetPrivate::slotFlipAll()
322{
323 if (!m_model)
324 return;
325
326 m_model->flipAll();
327}
328
329void QtGradientStopsWidgetPrivate::slotSelectAll()
330{
331 if (!m_model)
332 return;
333
334 m_model->selectAll();
335}
336
337void QtGradientStopsWidgetPrivate::slotZoomIn()
338{
339 double newZoom = q_ptr->zoom() * 2;
340 if (newZoom > 100)
341 newZoom = 100;
342 if (newZoom == q_ptr->zoom())
343 return;
344
345 q_ptr->setZoom(newZoom);
346 emit q_ptr->zoomChanged(zoom: q_ptr->zoom());
347}
348
349void QtGradientStopsWidgetPrivate::slotZoomOut()
350{
351 double newZoom = q_ptr->zoom() / 2;
352 if (newZoom < 1)
353 newZoom = 1;
354 if (newZoom == q_ptr->zoom())
355 return;
356
357 q_ptr->setZoom(newZoom);
358 emit q_ptr->zoomChanged(zoom: q_ptr->zoom());
359}
360
361void QtGradientStopsWidgetPrivate::slotResetZoom()
362{
363 if (1 == q_ptr->zoom())
364 return;
365
366 q_ptr->setZoom(1);
367 emit q_ptr->zoomChanged(zoom: 1);
368}
369
370QtGradientStopsWidget::QtGradientStopsWidget(QWidget *parent)
371 : QAbstractScrollArea(parent), d_ptr(new QtGradientStopsWidgetPrivate)
372{
373 d_ptr->q_ptr = this;
374 d_ptr->m_backgroundCheckered = true;
375 d_ptr->m_model = 0;
376 d_ptr->m_handleSize = 25.0;
377 d_ptr->m_scaleFactor = 1000;
378 d_ptr->m_moving = false;
379 d_ptr->m_zoom = 1;
380 d_ptr->m_rubber = new QRubberBand(QRubberBand::Rectangle, this);
381#ifndef QT_NO_DRAGANDDROP
382 d_ptr->m_dragStop = 0;
383 d_ptr->m_changedStop = 0;
384 d_ptr->m_clonedStop = 0;
385 d_ptr->m_dragModel = 0;
386#endif
387 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
388 setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
389 horizontalScrollBar()->setRange(min: 0, max: (int)(d_ptr->m_scaleFactor * (d_ptr->m_zoom - 1) + 0.5));
390 horizontalScrollBar()->setPageStep(d_ptr->m_scaleFactor);
391 horizontalScrollBar()->setSingleStep(4);
392 viewport()->setAutoFillBackground(false);
393
394 setAcceptDrops(true);
395
396 setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred));
397}
398
399QtGradientStopsWidget::~QtGradientStopsWidget()
400{
401}
402
403QSize QtGradientStopsWidget::sizeHint() const
404{
405 return QSize(qRound(d: 2 * d_ptr->m_handleSize), qRound(d: 3 * d_ptr->m_handleSize) + horizontalScrollBar()->sizeHint().height());
406}
407
408QSize QtGradientStopsWidget::minimumSizeHint() const
409{
410 return QSize(qRound(d: 2 * d_ptr->m_handleSize), qRound(d: 3 * d_ptr->m_handleSize) + horizontalScrollBar()->minimumSizeHint().height());
411}
412
413void QtGradientStopsWidget::setBackgroundCheckered(bool checkered)
414{
415 if (d_ptr->m_backgroundCheckered == checkered)
416 return;
417 d_ptr->m_backgroundCheckered = checkered;
418 update();
419}
420
421bool QtGradientStopsWidget::isBackgroundCheckered() const
422{
423 return d_ptr->m_backgroundCheckered;
424}
425
426void QtGradientStopsWidget::setGradientStopsModel(QtGradientStopsModel *model)
427{
428 d_ptr->setGradientStopsModel(model);
429}
430
431void QtGradientStopsWidget::mousePressEvent(QMouseEvent *e)
432{
433 if (!d_ptr->m_model)
434 return;
435
436 if (e->button() != Qt::LeftButton)
437 return;
438
439 d_ptr->m_moving = true;
440
441 d_ptr->m_moveStops.clear();
442 d_ptr->m_moveOriginal.clear();
443 d_ptr->m_clickPos = e->position().toPoint();
444 QtGradientStop *stop = d_ptr->stopAt(viewportPos: e->position().toPoint());
445 if (stop) {
446 if (e->modifiers() & Qt::ControlModifier) {
447 d_ptr->m_model->selectStop(stop, select: !d_ptr->m_model->isSelected(stop));
448 } else if (e->modifiers() & Qt::ShiftModifier) {
449 QtGradientStop *oldCurrent = d_ptr->m_model->currentStop();
450 if (oldCurrent) {
451 const auto stops = d_ptr->m_model->stops();
452 auto itSt = stops.constFind(key: oldCurrent->position());
453 if (itSt != stops.constEnd()) {
454 while (itSt != stops.constFind(key: stop->position())) {
455 d_ptr->m_model->selectStop(stop: itSt.value(), select: true);
456 if (oldCurrent->position() < stop->position())
457 ++itSt;
458 else
459 --itSt;
460 }
461 }
462 }
463 d_ptr->m_model->selectStop(stop, select: true);
464 } else {
465 if (!d_ptr->m_model->isSelected(stop)) {
466 d_ptr->m_model->clearSelection();
467 d_ptr->m_model->selectStop(stop, select: true);
468 }
469 }
470 d_ptr->setupMove(stop, x: e->position().toPoint().x());
471 } else {
472 d_ptr->m_model->clearSelection();
473 d_ptr->m_rubber->setGeometry(QRect(d_ptr->m_clickPos, QSize()));
474 d_ptr->m_rubber->show();
475 }
476 viewport()->update();
477}
478
479void QtGradientStopsWidget::mouseReleaseEvent(QMouseEvent *e)
480{
481 if (!d_ptr->m_model)
482 return;
483
484 if (e->button() != Qt::LeftButton)
485 return;
486
487 d_ptr->m_moving = false;
488 d_ptr->m_rubber->hide();
489 d_ptr->m_moveStops.clear();
490 d_ptr->m_moveOriginal.clear();
491}
492
493void QtGradientStopsWidget::mouseMoveEvent(QMouseEvent *e)
494{
495 if (!d_ptr->m_model)
496 return;
497
498 if (!(e->buttons() & Qt::LeftButton))
499 return;
500
501 if (!d_ptr->m_moving)
502 return;
503
504 if (!d_ptr->m_moveStops.isEmpty()) {
505 double maxOffset = 0.0;
506 double minOffset = 0.0;
507 bool first = true;
508 auto itStop = d_ptr->m_moveStops.cbegin();
509 while (itStop != d_ptr->m_moveStops.constEnd()) {
510 double offset = itStop.value();
511
512 if (first) {
513 maxOffset = offset;
514 minOffset = offset;
515 first = false;
516 } else {
517 if (maxOffset < offset)
518 maxOffset = offset;
519 else if (minOffset > offset)
520 minOffset = offset;
521 }
522 ++itStop;
523 }
524
525 double viewportMin = d_ptr->toViewport(x: -minOffset);
526 double viewportMax = d_ptr->toViewport(x: 1.0 - maxOffset);
527
528 QtGradientStopsModel::PositionStopMap newPositions;
529
530 int viewportX = e->position().toPoint().x() - d_ptr->m_moveOffset;
531
532 if (viewportX > viewport()->size().width())
533 viewportX = viewport()->size().width();
534 else if (viewportX < 0)
535 viewportX = 0;
536
537 double posX = d_ptr->fromViewport(x: viewportX);
538
539 if (viewportX > viewportMax)
540 posX = 1.0 - maxOffset;
541 else if (viewportX < viewportMin)
542 posX = -minOffset;
543
544 itStop = d_ptr->m_moveStops.constBegin();
545 while (itStop != d_ptr->m_moveStops.constEnd()) {
546 QtGradientStop *stop = itStop.key();
547
548 newPositions[posX + itStop.value()] = stop;
549
550 ++itStop;
551 }
552
553 bool forward = true;
554 auto itNewPos = newPositions.cbegin();
555 if (itNewPos.value()->position() < itNewPos.key())
556 forward = false;
557
558 itNewPos = forward ? newPositions.constBegin() : newPositions.constEnd();
559 while (itNewPos != (forward ? newPositions.constEnd() : newPositions.constBegin())) {
560 if (!forward)
561 --itNewPos;
562 QtGradientStop *stop = itNewPos.value();
563 double newPos = itNewPos.key();
564 if (newPos > 1)
565 newPos = 1;
566 else if (newPos < 0)
567 newPos = 0;
568
569 QtGradientStop *existingStop = d_ptr->m_model->at(pos: newPos);
570 if (existingStop && !d_ptr->m_moveStops.contains(key: existingStop))
571 d_ptr->m_model->removeStop(stop: existingStop);
572 d_ptr->m_model->moveStop(stop, newPos);
573
574 if (forward)
575 ++itNewPos;
576 }
577
578 auto itOld = d_ptr->m_moveOriginal.cbegin();
579 while (itOld != d_ptr->m_moveOriginal.constEnd()) {
580 double position = itOld.key();
581 if (!d_ptr->m_model->at(pos: position))
582 d_ptr->m_model->addStop(pos: position, color: itOld.value());
583
584 ++itOld;
585 }
586
587 } else {
588 QRect r(QRect(d_ptr->m_clickPos, e->position().toPoint()).normalized());
589 r.translate(dx: 1, dy: 0);
590 d_ptr->m_rubber->setGeometry(r);
591 //d_ptr->m_model->clearSelection();
592
593 int xv1 = d_ptr->m_clickPos.x();
594 int xv2 = e->position().toPoint().x();
595 if (xv1 > xv2) {
596 int temp = xv1;
597 xv1 = xv2;
598 xv2 = temp;
599 }
600 int yv1 = d_ptr->m_clickPos.y();
601 int yv2 = e->position().toPoint().y();
602 if (yv1 > yv2) {
603 int temp = yv1;
604 yv1 = yv2;
605 yv2 = temp;
606 }
607
608 QPoint p1, p2;
609
610 if (yv2 < d_ptr->m_handleSize / 2) {
611 p1 = QPoint(xv1, yv2);
612 p2 = QPoint(xv2, yv2);
613 } else if (yv1 > d_ptr->m_handleSize / 2) {
614 p1 = QPoint(xv1, yv1);
615 p2 = QPoint(xv2, yv1);
616 } else {
617 p1 = QPoint(xv1, qRound(d: d_ptr->m_handleSize / 2));
618 p2 = QPoint(xv2, qRound(d: d_ptr->m_handleSize / 2));
619 }
620
621 const auto beginList = d_ptr->stopsAt(viewportPos: p1);
622 const auto endList = d_ptr->stopsAt(viewportPos: p2);
623
624 double x1 = d_ptr->fromViewport(x: xv1);
625 double x2 = d_ptr->fromViewport(x: xv2);
626
627 for (QtGradientStop *stop : std::as_const(t&: d_ptr->m_stops)) {
628 if ((stop->position() >= x1 && stop->position() <= x2) ||
629 beginList.contains(t: stop) || endList.contains(t: stop))
630 d_ptr->m_model->selectStop(stop, select: true);
631 else
632 d_ptr->m_model->selectStop(stop, select: false);
633 }
634 }
635}
636
637void QtGradientStopsWidget::mouseDoubleClickEvent(QMouseEvent *e)
638{
639 if (!d_ptr->m_model)
640 return;
641
642 if (e->button() != Qt::LeftButton)
643 return;
644
645 if (d_ptr->m_clickPos != e->position().toPoint()) {
646 mousePressEvent(e);
647 return;
648 }
649 d_ptr->m_moving = true;
650 d_ptr->m_moveStops.clear();
651 d_ptr->m_moveOriginal.clear();
652
653 QtGradientStop *stop = d_ptr->newStop(viewportPos: e->position().toPoint());
654
655 if (!stop)
656 return;
657
658 d_ptr->m_model->clearSelection();
659 d_ptr->m_model->selectStop(stop, select: true);
660
661 d_ptr->setupMove(stop, x: e->position().toPoint().x());
662
663 viewport()->update();
664}
665
666void QtGradientStopsWidget::keyPressEvent(QKeyEvent *e)
667{
668 if (!d_ptr->m_model)
669 return;
670
671 if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
672 d_ptr->m_model->deleteStops();
673 } else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right ||
674 e->key() == Qt::Key_Home || e->key() == Qt::Key_End) {
675 const auto stops = d_ptr->m_model->stops();
676 if (stops.isEmpty())
677 return;
678 QtGradientStop *newCurrent = nullptr;
679 QtGradientStop *current = d_ptr->m_model->currentStop();
680 if (!current || e->key() == Qt::Key_Home || e->key() == Qt::Key_End) {
681 if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Home)
682 newCurrent = stops.constBegin().value();
683 else if (e->key() == Qt::Key_Right || e->key() == Qt::Key_End)
684 newCurrent = (--stops.constEnd()).value();
685 } else {
686 auto itStop = stops.cbegin();
687 while (itStop.value() != current)
688 ++itStop;
689 if (e->key() == Qt::Key_Left && itStop != stops.constBegin())
690 --itStop;
691 else if (e->key() == Qt::Key_Right && itStop != --stops.constEnd())
692 ++itStop;
693 newCurrent = itStop.value();
694 }
695 d_ptr->m_model->clearSelection();
696 d_ptr->m_model->selectStop(stop: newCurrent, select: true);
697 d_ptr->m_model->setCurrentStop(newCurrent);
698 d_ptr->ensureVisible(stop: newCurrent);
699 } else if (e->key() == Qt::Key_A) {
700 if (e->modifiers() & Qt::ControlModifier)
701 d_ptr->m_model->selectAll();
702 }
703}
704
705void QtGradientStopsWidget::paintEvent(QPaintEvent *e)
706{
707 Q_UNUSED(e);
708 if (!d_ptr->m_model)
709 return;
710
711 QtGradientStopsModel *model = d_ptr->m_model;
712#ifndef QT_NO_DRAGANDDROP
713 if (d_ptr->m_dragModel)
714 model = d_ptr->m_dragModel;
715#endif
716
717 QSize size = viewport()->size();
718 int w = size.width();
719 double h = size.height() - d_ptr->m_handleSize;
720 if (w <= 0)
721 return;
722
723 QPixmap pix(size);
724 QPainter p;
725
726 if (d_ptr->m_backgroundCheckered) {
727 int pixSize = 20;
728 QPixmap pm(2 * pixSize, 2 * pixSize);
729 QPainter pmp(&pm);
730 pmp.fillRect(x: 0, y: 0, w: pixSize, h: pixSize, c: Qt::white);
731 pmp.fillRect(x: pixSize, y: pixSize, w: pixSize, h: pixSize, c: Qt::white);
732 pmp.fillRect(x: 0, y: pixSize, w: pixSize, h: pixSize, c: Qt::black);
733 pmp.fillRect(x: pixSize, y: 0, w: pixSize, h: pixSize, c: Qt::black);
734
735 p.begin(&pix);
736 p.setBrushOrigin(x: (size.width() % pixSize + pixSize) / 2, y: (size.height() % pixSize + pixSize) / 2);
737 p.fillRect(viewport()->rect(), pm);
738 p.setBrushOrigin(x: 0, y: 0);
739 } else {
740 p.begin(viewport());
741 }
742
743 const double viewBegin = double(w) * horizontalScrollBar()->value() / d_ptr->m_scaleFactor;
744
745 int val = horizontalScrollBar()->value();
746 int max = horizontalScrollBar()->maximum();
747
748 const double begin = double(val) / (d_ptr->m_scaleFactor + max);
749 const double end = double(val + d_ptr->m_scaleFactor) / (d_ptr->m_scaleFactor + max);
750 double width = end - begin;
751
752 if (h > 0) {
753 QLinearGradient lg(0, 0, w, 0);
754 QMap<qreal, QtGradientStop *> stops = model->stops();
755 for (auto itStop = stops.cbegin(), send = stops.cend(); itStop != send; ++itStop) {
756 QtGradientStop *stop = itStop.value();
757 double pos = stop->position();
758 if (pos >= begin && pos <= end) {
759 double gradPos = (pos - begin) / width;
760 QColor c = stop->color();
761 lg.setColorAt(pos: gradPos, color: c);
762 }
763 //lg.setColorAt(stop->position(), stop->color());
764 }
765 lg.setColorAt(pos: 0, color: model->color(pos: begin));
766 lg.setColorAt(pos: 1, color: model->color(pos: end));
767 QImage img(w, 1, QImage::Format_ARGB32_Premultiplied);
768 QPainter p1(&img);
769 p1.setCompositionMode(QPainter::CompositionMode_Source);
770
771 /*
772 if (viewBegin != 0)
773 p1.translate(-viewBegin, 0);
774 if (d_ptr->m_zoom != 1)
775 p1.scale(d_ptr->m_zoom, 1);
776 */
777 p1.fillRect(x: 0, y: 0, w, h: 1, b: lg);
778
779 p.fillRect(QRectF(0, d_ptr->m_handleSize, w, h), QPixmap::fromImage(image: img));
780 }
781
782
783 double handleWidth = d_ptr->m_handleSize * d_ptr->m_scaleFactor / (w * (d_ptr->m_scaleFactor + max));
784
785 QColor insideColor = QColor::fromRgb(r: 0x20, g: 0x20, b: 0x20, a: 0xFF);
786 QColor drawColor;
787 QColor back1 = QColor(Qt::lightGray);
788 QColor back2 = QColor(Qt::darkGray);
789 QColor back = QColor::fromRgb(r: (back1.red() + back2.red()) / 2,
790 g: (back1.green() + back2.green()) / 2,
791 b: (back1.blue() + back2.blue()) / 2);
792
793 QPen pen;
794 p.setRenderHint(hint: QPainter::Antialiasing);
795 for (auto rit = d_ptr->m_stops.crbegin(), rend = d_ptr->m_stops.crend(); rit != rend; ++rit) {
796 QtGradientStop *stop = *rit;
797 double x = stop->position();
798 if (x >= begin - handleWidth / 2 && x <= end + handleWidth / 2) {
799 double viewX = x * w * (d_ptr->m_scaleFactor + max) / d_ptr->m_scaleFactor - viewBegin;
800 p.save();
801 QColor c = stop->color();
802#ifndef QT_NO_DRAGANDDROP
803 if (stop == d_ptr->m_dragStop)
804 c = d_ptr->m_dragColor;
805#endif
806 if ((0.3 * c.redF() + 0.59 * c.greenF() + 0.11 * c.blueF()) * c.alphaF() +
807 (0.3 * back.redF() + 0.59 * back.greenF() + 0.11 * back.blueF()) * (1.0 - c.alphaF()) < 0.5) {
808 drawColor = QColor::fromRgb(r: 0xC0, g: 0xC0, b: 0xC0, a: 0xB0);
809 } else {
810 drawColor = QColor::fromRgb(r: 0x40, g: 0x40, b: 0x40, a: 0x80);
811 }
812 QRectF rect(viewX - d_ptr->m_handleSize / 2, 0, d_ptr->m_handleSize, d_ptr->m_handleSize);
813 rect.adjust(xp1: 0.5, yp1: 0.5, xp2: -0.5, yp2: -0.5);
814 if (h > 0) {
815 pen.setWidthF(1);
816 QLinearGradient lg(0, d_ptr->m_handleSize, 0, d_ptr->m_handleSize + h / 2);
817 lg.setColorAt(pos: 0, color: drawColor);
818 QColor alphaZero = drawColor;
819 alphaZero.setAlpha(0);
820 lg.setColorAt(pos: 1, color: alphaZero);
821 pen.setBrush(lg);
822 p.setPen(pen);
823 p.drawLine(p1: QPointF(viewX, d_ptr->m_handleSize), p2: QPointF(viewX, d_ptr->m_handleSize + h / 2));
824
825 pen.setWidthF(1);
826 pen.setBrush(drawColor);
827 p.setPen(pen);
828 QRectF r1 = rect.adjusted(xp1: 0.5, yp1: 0.5, xp2: -0.5, yp2: -0.5);
829 QRectF r2 = rect.adjusted(xp1: 1.5, yp1: 1.5, xp2: -1.5, yp2: -1.5);
830 QColor inColor = QColor::fromRgb(r: 0x80, g: 0x80, b: 0x80, a: 0x80);
831 if (!d_ptr->m_model->isSelected(stop)) {
832 p.setBrush(c);
833 p.drawEllipse(r: rect);
834 } else {
835 pen.setBrush(insideColor);
836 pen.setWidthF(2);
837 p.setPen(pen);
838 p.setBrush(Qt::NoBrush);
839 p.drawEllipse(r: r1);
840
841 pen.setBrush(inColor);
842 pen.setWidthF(1);
843 p.setPen(pen);
844 p.setBrush(c);
845 p.drawEllipse(r: r2);
846 }
847
848 if (d_ptr->m_model->currentStop() == stop) {
849 p.setBrush(Qt::NoBrush);
850 pen.setWidthF(5);
851 pen.setBrush(drawColor);
852 int corr = 4;
853 if (!d_ptr->m_model->isSelected(stop)) {
854 corr = 3;
855 pen.setWidthF(7);
856 }
857 p.setPen(pen);
858 p.drawEllipse(r: rect.adjusted(xp1: corr, yp1: corr, xp2: -corr, yp2: -corr));
859 }
860
861 }
862 p.restore();
863 }
864 }
865 if (d_ptr->m_backgroundCheckered) {
866 p.end();
867 p.begin(viewport());
868 p.drawPixmap(x: 0, y: 0, pm: pix);
869 }
870 p.end();
871}
872
873void QtGradientStopsWidget::focusInEvent(QFocusEvent *e)
874{
875 Q_UNUSED(e);
876 viewport()->update();
877}
878
879void QtGradientStopsWidget::focusOutEvent(QFocusEvent *e)
880{
881 Q_UNUSED(e);
882 viewport()->update();
883}
884
885void QtGradientStopsWidget::contextMenuEvent(QContextMenuEvent *e)
886{
887 if (!d_ptr->m_model)
888 return;
889
890 d_ptr->m_clickPos = e->pos();
891
892 QMenu menu(this);
893 QAction *newStopAction = new QAction(tr(s: "New Stop"), &menu);
894 QAction *deleteAction = new QAction(tr(s: "Delete"), &menu);
895 QAction *flipAllAction = new QAction(tr(s: "Flip All"), &menu);
896 QAction *selectAllAction = new QAction(tr(s: "Select All"), &menu);
897 QAction *zoomInAction = new QAction(tr(s: "Zoom In"), &menu);
898 QAction *zoomOutAction = new QAction(tr(s: "Zoom Out"), &menu);
899 QAction *zoomAllAction = new QAction(tr(s: "Reset Zoom"), &menu);
900 if (d_ptr->m_model->selectedStops().isEmpty() && !d_ptr->m_model->currentStop())
901 deleteAction->setEnabled(false);
902 if (zoom() <= 1) {
903 zoomOutAction->setEnabled(false);
904 zoomAllAction->setEnabled(false);
905 } else if (zoom() >= 100) {
906 zoomInAction->setEnabled(false);
907 }
908 connect(sender: newStopAction, signal: &QAction::triggered, context: d_ptr.data(), slot: &QtGradientStopsWidgetPrivate::slotNewStop);
909 connect(sender: deleteAction, signal: &QAction::triggered, context: d_ptr.data(), slot: &QtGradientStopsWidgetPrivate::slotDelete);
910 connect(sender: flipAllAction, signal: &QAction::triggered, context: d_ptr.data(), slot: &QtGradientStopsWidgetPrivate::slotFlipAll);
911 connect(sender: selectAllAction, signal: &QAction::triggered, context: d_ptr.data(), slot: &QtGradientStopsWidgetPrivate::slotSelectAll);
912 connect(sender: zoomInAction, signal: &QAction::triggered, context: d_ptr.data(), slot: &QtGradientStopsWidgetPrivate::slotZoomIn);
913 connect(sender: zoomOutAction, signal: &QAction::triggered, context: d_ptr.data(), slot: &QtGradientStopsWidgetPrivate::slotZoomOut);
914 connect(sender: zoomAllAction, signal: &QAction::triggered, context: d_ptr.data(), slot: &QtGradientStopsWidgetPrivate::slotResetZoom);
915 menu.addAction(action: newStopAction);
916 menu.addAction(action: deleteAction);
917 menu.addAction(action: flipAllAction);
918 menu.addAction(action: selectAllAction);
919 menu.addSeparator();
920 menu.addAction(action: zoomInAction);
921 menu.addAction(action: zoomOutAction);
922 menu.addAction(action: zoomAllAction);
923 menu.exec(pos: e->globalPos());
924}
925
926void QtGradientStopsWidget::wheelEvent(QWheelEvent *e)
927{
928 int numDegrees = e->angleDelta().y() / 8;
929 int numSteps = numDegrees / 15;
930
931 int shift = numSteps;
932 if (shift < 0)
933 shift = -shift;
934 int pow = 1 << shift;
935 //const double c = 0.7071067; // 2 steps per doubled value
936 const double c = 0.5946036; // 4 steps pre doubled value
937 // in general c = pow(2, 1 / n) / 2; where n is the step
938 double factor = pow * c;
939
940 double newZoom = zoom();
941 if (numSteps < 0)
942 newZoom /= factor;
943 else
944 newZoom *= factor;
945 if (newZoom > 100)
946 newZoom = 100;
947 if (newZoom < 1)
948 newZoom = 1;
949
950 if (newZoom == zoom())
951 return;
952
953 setZoom(newZoom);
954 emit zoomChanged(zoom: zoom());
955}
956
957#ifndef QT_NO_DRAGANDDROP
958void QtGradientStopsWidget::dragEnterEvent(QDragEnterEvent *event)
959{
960 const QMimeData *mime = event->mimeData();
961 if (!mime->hasColor())
962 return;
963 event->accept();
964 d_ptr->m_dragModel = d_ptr->m_model->clone();
965
966 d_ptr->m_dragColor = qvariant_cast<QColor>(v: mime->colorData());
967 update();
968}
969
970void QtGradientStopsWidget::dragMoveEvent(QDragMoveEvent *event)
971{
972 QRectF rect = viewport()->rect();
973 rect.adjust(xp1: 0, yp1: d_ptr->m_handleSize, xp2: 0, yp2: 0);
974 double x = d_ptr->fromViewport(x: event->position().toPoint().x());
975 QtGradientStop *dragStop = d_ptr->stopAt(viewportPos: event->position().toPoint());
976 if (dragStop) {
977 event->accept();
978 d_ptr->removeClonedStop();
979 d_ptr->changeStop(pos: dragStop->position());
980 } else if (rect.contains(p: event->position().toPoint())) {
981 event->accept();
982 if (d_ptr->m_model->at(pos: x)) {
983 d_ptr->removeClonedStop();
984 d_ptr->changeStop(pos: x);
985 } else {
986 d_ptr->restoreChangedStop();
987 d_ptr->cloneStop(pos: x);
988 }
989 } else {
990 event->ignore();
991 d_ptr->removeClonedStop();
992 d_ptr->restoreChangedStop();
993 }
994
995 update();
996}
997
998void QtGradientStopsWidget::dragLeaveEvent(QDragLeaveEvent *event)
999{
1000 event->accept();
1001 d_ptr->clearDrag();
1002 update();
1003}
1004
1005void QtGradientStopsWidget::dropEvent(QDropEvent *event)
1006{
1007 event->accept();
1008 if (!d_ptr->m_dragModel)
1009 return;
1010
1011 if (d_ptr->m_changedStop)
1012 d_ptr->m_model->changeStop(stop: d_ptr->m_model->at(pos: d_ptr->m_changedStop->position()), newColor: d_ptr->m_dragColor);
1013 else if (d_ptr->m_clonedStop)
1014 d_ptr->m_model->addStop(pos: d_ptr->m_clonedStop->position(), color: d_ptr->m_dragColor);
1015
1016 d_ptr->clearDrag();
1017 update();
1018}
1019
1020void QtGradientStopsWidgetPrivate::clearDrag()
1021{
1022 removeClonedStop();
1023 restoreChangedStop();
1024 delete m_dragModel;
1025 m_dragModel = 0;
1026}
1027
1028void QtGradientStopsWidgetPrivate::removeClonedStop()
1029{
1030 if (!m_clonedStop)
1031 return;
1032 m_dragModel->removeStop(stop: m_clonedStop);
1033 m_clonedStop = 0;
1034}
1035
1036void QtGradientStopsWidgetPrivate::restoreChangedStop()
1037{
1038 if (!m_changedStop)
1039 return;
1040 m_dragModel->changeStop(stop: m_changedStop, newColor: m_model->at(pos: m_changedStop->position())->color());
1041 m_changedStop = 0;
1042 m_dragStop = 0;
1043}
1044
1045void QtGradientStopsWidgetPrivate::changeStop(qreal pos)
1046{
1047 QtGradientStop *stop = m_dragModel->at(pos);
1048 if (!stop)
1049 return;
1050
1051 m_dragModel->changeStop(stop, newColor: m_dragColor);
1052 m_changedStop = stop;
1053 m_dragStop = m_model->at(pos: stop->position());
1054}
1055
1056void QtGradientStopsWidgetPrivate::cloneStop(qreal pos)
1057{
1058 if (m_clonedStop) {
1059 m_dragModel->moveStop(stop: m_clonedStop, newPos: pos);
1060 return;
1061 }
1062 QtGradientStop *stop = m_dragModel->at(pos);
1063 if (stop)
1064 return;
1065
1066 m_clonedStop = m_dragModel->addStop(pos, color: m_dragColor);
1067}
1068
1069#endif
1070
1071void QtGradientStopsWidget::setZoom(double zoom)
1072{
1073 double z = zoom;
1074 if (z < 1)
1075 z = 1;
1076 else if (z > 100)
1077 z = 100;
1078
1079 if (d_ptr->m_zoom == z)
1080 return;
1081
1082 d_ptr->m_zoom = z;
1083 int oldMax = horizontalScrollBar()->maximum();
1084 int oldVal = horizontalScrollBar()->value();
1085 horizontalScrollBar()->setRange(min: 0, max: qRound(d: d_ptr->m_scaleFactor * (d_ptr->m_zoom - 1)));
1086 int newMax = horizontalScrollBar()->maximum();
1087 const double newVal = (oldVal + double(d_ptr->m_scaleFactor) / 2) * (newMax + d_ptr->m_scaleFactor)
1088 / (oldMax + d_ptr->m_scaleFactor) - double(d_ptr->m_scaleFactor) / 2;
1089 horizontalScrollBar()->setValue(qRound(d: newVal));
1090 viewport()->update();
1091}
1092
1093double QtGradientStopsWidget::zoom() const
1094{
1095 return d_ptr->m_zoom;
1096}
1097
1098QT_END_NAMESPACE
1099
1100#include "qtgradientstopswidget.moc"
1101

source code of qttools/src/shared/qtgradienteditor/qtgradientstopswidget.cpp