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