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 "qtgradientstopsmodel.h"
41#include <QtGui/QColor>
42
43QT_BEGIN_NAMESPACE
44
45class QtGradientStopPrivate
46{
47public:
48 qreal m_position;
49 QColor m_color;
50 QtGradientStopsModel *m_model;
51};
52
53qreal QtGradientStop::position() const
54{
55 return d_ptr->m_position;
56}
57
58QColor QtGradientStop::color() const
59{
60 return d_ptr->m_color;
61}
62
63QtGradientStopsModel *QtGradientStop::gradientModel() const
64{
65 return d_ptr->m_model;
66}
67
68void QtGradientStop::setColor(const QColor &color)
69{
70 d_ptr->m_color = color;
71}
72
73void QtGradientStop::setPosition(qreal position)
74{
75 d_ptr->m_position = position;
76}
77
78QtGradientStop::QtGradientStop(QtGradientStopsModel *model)
79 : d_ptr(new QtGradientStopPrivate())
80{
81 d_ptr->m_position = 0;
82 d_ptr->m_color = Qt::white;
83 d_ptr->m_model = model;
84}
85
86QtGradientStop::~QtGradientStop()
87{
88}
89
90class QtGradientStopsModelPrivate
91{
92 QtGradientStopsModel *q_ptr;
93 Q_DECLARE_PUBLIC(QtGradientStopsModel)
94public:
95 QMap<qreal, QtGradientStop *> m_posToStop;
96 QMap<QtGradientStop *, qreal> m_stopToPos;
97 QMap<QtGradientStop *, bool> m_selection;
98 QtGradientStop *m_current;
99};
100
101
102
103QtGradientStopsModel::QtGradientStopsModel(QObject *parent)
104 : QObject(parent), d_ptr(new QtGradientStopsModelPrivate)
105{
106 d_ptr->q_ptr = this;
107 d_ptr->m_current = 0;
108}
109
110QtGradientStopsModel::~QtGradientStopsModel()
111{
112 clear();
113}
114
115QtGradientStopsModel::PositionStopMap QtGradientStopsModel::stops() const
116{
117 return d_ptr->m_posToStop;
118}
119
120QtGradientStop *QtGradientStopsModel::at(qreal pos) const
121{
122 if (d_ptr->m_posToStop.contains(pos))
123 return d_ptr->m_posToStop[pos];
124 return 0;
125}
126
127QColor QtGradientStopsModel::color(qreal pos) const
128{
129 PositionStopMap gradStops = stops();
130 if (gradStops.isEmpty())
131 return QColor::fromRgbF(pos, pos, pos, 1.0);
132 if (gradStops.contains(pos))
133 return gradStops[pos]->color();
134
135 gradStops[pos] = 0;
136 PositionStopMap::ConstIterator itStop = gradStops.constFind(pos);
137 if (itStop == gradStops.constBegin()) {
138 ++itStop;
139 return itStop.value()->color();
140 }
141 if (itStop == --gradStops.constEnd()) {
142 --itStop;
143 return itStop.value()->color();
144 }
145 PositionStopMap::ConstIterator itPrev = itStop;
146 PositionStopMap::ConstIterator itNext = itStop;
147 --itPrev;
148 ++itNext;
149
150 double prevX = itPrev.key();
151 double nextX = itNext.key();
152
153 double coefX = (pos - prevX) / (nextX - prevX);
154 QColor prevCol = itPrev.value()->color();
155 QColor nextCol = itNext.value()->color();
156
157 QColor newColor;
158 newColor.setRgbF((nextCol.redF() - prevCol.redF() ) * coefX + prevCol.redF(),
159 (nextCol.greenF() - prevCol.greenF()) * coefX + prevCol.greenF(),
160 (nextCol.blueF() - prevCol.blueF() ) * coefX + prevCol.blueF(),
161 (nextCol.alphaF() - prevCol.alphaF()) * coefX + prevCol.alphaF());
162 return newColor;
163}
164
165QList<QtGradientStop *> QtGradientStopsModel::selectedStops() const
166{
167 return d_ptr->m_selection.keys();
168}
169
170QtGradientStop *QtGradientStopsModel::currentStop() const
171{
172 return d_ptr->m_current;
173}
174
175bool QtGradientStopsModel::isSelected(QtGradientStop *stop) const
176{
177 if (d_ptr->m_selection.contains(stop))
178 return true;
179 return false;
180}
181
182QtGradientStop *QtGradientStopsModel::addStop(qreal pos, const QColor &color)
183{
184 qreal newPos = pos;
185 if (pos < 0.0)
186 newPos = 0.0;
187 if (pos > 1.0)
188 newPos = 1.0;
189 if (d_ptr->m_posToStop.contains(newPos))
190 return 0;
191 QtGradientStop *stop = new QtGradientStop();
192 stop->setPosition(newPos);
193 stop->setColor(color);
194
195 d_ptr->m_posToStop[newPos] = stop;
196 d_ptr->m_stopToPos[stop] = newPos;
197
198 emit stopAdded(stop);
199
200 return stop;
201}
202
203void QtGradientStopsModel::removeStop(QtGradientStop *stop)
204{
205 if (!d_ptr->m_stopToPos.contains(stop))
206 return;
207 if (currentStop() == stop)
208 setCurrentStop(0);
209 selectStop(stop, false);
210
211 emit stopRemoved(stop);
212
213 qreal pos = d_ptr->m_stopToPos[stop];
214 d_ptr->m_stopToPos.remove(stop);
215 d_ptr->m_posToStop.remove(pos);
216 delete stop;
217}
218
219void QtGradientStopsModel::moveStop(QtGradientStop *stop, qreal newPos)
220{
221 if (!d_ptr->m_stopToPos.contains(stop))
222 return;
223 if (d_ptr->m_posToStop.contains(newPos))
224 return;
225
226 if (newPos > 1.0)
227 newPos = 1.0;
228 else if (newPos < 0.0)
229 newPos = 0.0;
230
231 emit stopMoved(stop, newPos);
232
233 const qreal oldPos = stop->position();
234 stop->setPosition(newPos);
235 d_ptr->m_stopToPos[stop] = newPos;
236 d_ptr->m_posToStop.remove(oldPos);
237 d_ptr->m_posToStop[newPos] = stop;
238}
239
240void QtGradientStopsModel::swapStops(QtGradientStop *stop1, QtGradientStop *stop2)
241{
242 if (stop1 == stop2)
243 return;
244 if (!d_ptr->m_stopToPos.contains(stop1))
245 return;
246 if (!d_ptr->m_stopToPos.contains(stop2))
247 return;
248
249 emit stopsSwapped(stop1, stop2);
250
251 const qreal pos1 = stop1->position();
252 const qreal pos2 = stop2->position();
253 stop1->setPosition(pos2);
254 stop2->setPosition(pos1);
255 d_ptr->m_stopToPos[stop1] = pos2;
256 d_ptr->m_stopToPos[stop2] = pos1;
257 d_ptr->m_posToStop[pos1] = stop2;
258 d_ptr->m_posToStop[pos2] = stop1;
259}
260
261void QtGradientStopsModel::changeStop(QtGradientStop *stop, const QColor &newColor)
262{
263 if (!d_ptr->m_stopToPos.contains(stop))
264 return;
265 if (stop->color() == newColor)
266 return;
267
268 emit stopChanged(stop, newColor);
269
270 stop->setColor(newColor);
271}
272
273void QtGradientStopsModel::selectStop(QtGradientStop *stop, bool select)
274{
275 if (!d_ptr->m_stopToPos.contains(stop))
276 return;
277 bool selected = d_ptr->m_selection.contains(stop);
278 if (select == selected)
279 return;
280
281 emit stopSelected(stop, select);
282
283 if (select)
284 d_ptr->m_selection[stop] = true;
285 else
286 d_ptr->m_selection.remove(stop);
287}
288
289void QtGradientStopsModel::setCurrentStop(QtGradientStop *stop)
290{
291 if (stop && !d_ptr->m_stopToPos.contains(stop))
292 return;
293 if (stop == currentStop())
294 return;
295
296 emit currentStopChanged(stop);
297
298 d_ptr->m_current = stop;
299}
300
301QtGradientStop *QtGradientStopsModel::firstSelected() const
302{
303 PositionStopMap stopList = stops();
304 PositionStopMap::ConstIterator itStop = stopList.constBegin();
305 while (itStop != stopList.constEnd()) {
306 QtGradientStop *stop = itStop.value();
307 if (isSelected(stop))
308 return stop;
309 ++itStop;
310 };
311 return 0;
312}
313
314QtGradientStop *QtGradientStopsModel::lastSelected() const
315{
316 PositionStopMap stopList = stops();
317 PositionStopMap::ConstIterator itStop = stopList.constEnd();
318 while (itStop != stopList.constBegin()) {
319 --itStop;
320
321 QtGradientStop *stop = itStop.value();
322 if (isSelected(stop))
323 return stop;
324 };
325 return 0;
326}
327
328QtGradientStopsModel *QtGradientStopsModel::clone() const
329{
330 QtGradientStopsModel *model = new QtGradientStopsModel();
331
332 QMap<qreal, QtGradientStop *> stopsToClone = stops();
333 for (auto it = stopsToClone.cbegin(), end = stopsToClone.cend(); it != end; ++it)
334 model->addStop(it.key(), it.value()->color());
335 // clone selection and current also
336 return model;
337}
338
339void QtGradientStopsModel::moveStops(double newPosition)
340{
341 QtGradientStop *current = currentStop();
342 if (!current)
343 return;
344
345 double newPos = newPosition;
346
347 if (newPos > 1)
348 newPos = 1;
349 else if (newPos < 0)
350 newPos = 0;
351
352 if (newPos == current->position())
353 return;
354
355 double offset = newPos - current->position();
356
357 QtGradientStop *first = firstSelected();
358 QtGradientStop *last = lastSelected();
359
360 if (first && last) { // multiselection
361 double maxOffset = 1.0 - last->position();
362 double minOffset = -first->position();
363
364 if (offset > maxOffset)
365 offset = maxOffset;
366 else if (offset < minOffset)
367 offset = minOffset;
368
369 }
370
371 if (offset == 0)
372 return;
373
374 bool forward = (offset > 0) ? false : true;
375
376 PositionStopMap stopList;
377
378 const QList<QtGradientStop *> selected = selectedStops();
379 for (QtGradientStop *stop : selected)
380 stopList[stop->position()] = stop;
381 stopList[current->position()] = current;
382
383 PositionStopMap::ConstIterator itStop = forward ? stopList.constBegin() : stopList.constEnd();
384 while (itStop != (forward ? stopList.constEnd() : stopList.constBegin())) {
385 if (!forward)
386 --itStop;
387 QtGradientStop *stop = itStop.value();
388 double pos = stop->position() + offset;
389 if (pos > 1)
390 pos = 1;
391 if (pos < 0)
392 pos = 0;
393
394 if (current == stop)
395 pos = newPos;
396
397 QtGradientStop *oldStop = at(pos);
398 if (oldStop && !stopList.values().contains(oldStop))
399 removeStop(oldStop);
400 moveStop(stop, pos);
401
402 if (forward)
403 ++itStop;
404 }
405}
406
407void QtGradientStopsModel::clear()
408{
409 const QList<QtGradientStop *> stopsList = stops().values();
410 for (QtGradientStop *stop : stopsList)
411 removeStop(stop);
412}
413
414void QtGradientStopsModel::clearSelection()
415{
416 const QList<QtGradientStop *> stopsList = selectedStops();
417 for (QtGradientStop *stop : stopsList)
418 selectStop(stop, false);
419}
420
421namespace {
422 template <typename BidirectionalIterator>
423 std::reverse_iterator<BidirectionalIterator> rev(BidirectionalIterator it)
424 { return std::reverse_iterator<BidirectionalIterator>(it); }
425}
426
427void QtGradientStopsModel::flipAll()
428{
429 QMap<qreal, QtGradientStop *> stopsMap = stops();
430 QMap<QtGradientStop *, bool> swappedList;
431 for (auto itStop = rev(stopsMap.keyValueEnd()), end = rev(stopsMap.keyValueBegin()); itStop != end; ++itStop) {
432 QtGradientStop *stop = (*itStop).second;
433 if (swappedList.contains(stop))
434 continue;
435 const double newPos = 1.0 - (*itStop).first;
436 if (stopsMap.contains(newPos)) {
437 QtGradientStop *swapped = stopsMap.value(newPos);
438 swappedList[swapped] = true;
439 swapStops(stop, swapped);
440 } else {
441 moveStop(stop, newPos);
442 }
443 }
444}
445
446void QtGradientStopsModel::selectAll()
447{
448 const auto stopsMap = stops();
449 for (auto it = stopsMap.cbegin(), end = stopsMap.cend(); it != end; ++it)
450 selectStop(it.value(), true);
451}
452
453void QtGradientStopsModel::deleteStops()
454{
455 const QList<QtGradientStop *> selected = selectedStops();
456 for (QtGradientStop *stop : selected)
457 removeStop(stop);
458 QtGradientStop *current = currentStop();
459 if (current)
460 removeStop(current);
461}
462
463QT_END_NAMESPACE
464