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
52QT_BEGIN_NAMESPACE
53
54class QtGradientStopsWidgetPrivate
55{
56 QtGradientStopsWidget *q_ptr;
57 Q_DECLARE_PUBLIC(QtGradientStopsWidget)
58public:
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
117double 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
126double 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
135QtGradientStop *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
150QList<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
166void 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 QList<QtGradientStop *> 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
191void 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
201void QtGradientStopsWidgetPrivate::ensureVisible(QtGradientStop *stop)
202{
203 if (!stop)
204 return;
205 ensureVisible(stop->position());
206}
207
208QtGradientStop *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
226void QtGradientStopsWidgetPrivate::slotStopAdded(QtGradientStop *stop)
227{
228 m_stops.append(stop);
229 q_ptr->viewport()->update();
230}
231
232void QtGradientStopsWidgetPrivate::slotStopRemoved(QtGradientStop *stop)
233{
234 m_stops.removeAll(stop);
235 q_ptr->viewport()->update();
236}
237
238void QtGradientStopsWidgetPrivate::slotStopMoved(QtGradientStop *stop, qreal newPos)
239{
240 Q_UNUSED(stop);
241 Q_UNUSED(newPos);
242 q_ptr->viewport()->update();
243}
244
245void QtGradientStopsWidgetPrivate::slotStopsSwapped(QtGradientStop *stop1, QtGradientStop *stop2)
246{
247 Q_UNUSED(stop1);
248 Q_UNUSED(stop2);
249 q_ptr->viewport()->update();
250}
251
252void QtGradientStopsWidgetPrivate::slotStopChanged(QtGradientStop *stop, const QColor &newColor)
253{
254 Q_UNUSED(stop);
255 Q_UNUSED(newColor);
256 q_ptr->viewport()->update();
257}
258
259void QtGradientStopsWidgetPrivate::slotStopSelected(QtGradientStop *stop, bool selected)
260{
261 Q_UNUSED(stop);
262 Q_UNUSED(selected);
263 q_ptr->viewport()->update();
264}
265
266void 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
279void 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
294void QtGradientStopsWidgetPrivate::slotDelete()
295{
296 if (!m_model)
297 return;
298
299 m_model->deleteStops();
300}
301
302void QtGradientStopsWidgetPrivate::slotFlipAll()
303{
304 if (!m_model)
305 return;
306
307 m_model->flipAll();
308}
309
310void QtGradientStopsWidgetPrivate::slotSelectAll()
311{
312 if (!m_model)
313 return;
314
315 m_model->selectAll();
316}
317
318void 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
330void 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
342void 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
351QtGradientStopsWidget::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
380QtGradientStopsWidget::~QtGradientStopsWidget()
381{
382}
383
384QSize QtGradientStopsWidget::sizeHint() const
385{
386 return QSize(qRound(2 * d_ptr->m_handleSize), qRound(3 * d_ptr->m_handleSize) + horizontalScrollBar()->sizeHint().height());
387}
388
389QSize QtGradientStopsWidget::minimumSizeHint() const
390{
391 return QSize(qRound(2 * d_ptr->m_handleSize), qRound(3 * d_ptr->m_handleSize) + horizontalScrollBar()->minimumSizeHint().height());
392}
393
394void QtGradientStopsWidget::setBackgroundCheckered(bool checkered)
395{
396 if (d_ptr->m_backgroundCheckered == checkered)
397 return;
398 d_ptr->m_backgroundCheckered = checkered;
399 update();
400}
401
402bool QtGradientStopsWidget::isBackgroundCheckered() const
403{
404 return d_ptr->m_backgroundCheckered;
405}
406
407void 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 QList<QtGradientStop *> 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
461void 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
510void 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
524void 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 QList<QtGradientStop *> beginList = d_ptr->stopsAt(p1);
656 QList<QtGradientStop *> 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
671void 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
700void 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
740void 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
908void QtGradientStopsWidget::focusInEvent(QFocusEvent *e)
909{
910 Q_UNUSED(e);
911 viewport()->update();
912}
913
914void QtGradientStopsWidget::focusOutEvent(QFocusEvent *e)
915{
916 Q_UNUSED(e);
917 viewport()->update();
918}
919
920void 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
961void 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
993void 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
1005void 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
1033void QtGradientStopsWidget::dragLeaveEvent(QDragLeaveEvent *event)
1034{
1035 event->accept();
1036 d_ptr->clearDrag();
1037 update();
1038}
1039
1040void 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
1055void QtGradientStopsWidgetPrivate::clearDrag()
1056{
1057 removeClonedStop();
1058 restoreChangedStop();
1059 delete m_dragModel;
1060 m_dragModel = 0;
1061}
1062
1063void QtGradientStopsWidgetPrivate::removeClonedStop()
1064{
1065 if (!m_clonedStop)
1066 return;
1067 m_dragModel->removeStop(m_clonedStop);
1068 m_clonedStop = 0;
1069}
1070
1071void 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
1080void 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
1091void 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
1106void 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
1128double QtGradientStopsWidget::zoom() const
1129{
1130 return d_ptr->m_zoom;
1131}
1132
1133QT_END_NAMESPACE
1134
1135#include "moc_qtgradientstopswidget.cpp"
1136