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

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