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 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | class QtGradientStopPrivate |
12 | { |
13 | public: |
14 | qreal m_position; |
15 | QColor m_color; |
16 | QtGradientStopsModel *m_model; |
17 | }; |
18 | |
19 | qreal QtGradientStop::position() const |
20 | { |
21 | return d_ptr->m_position; |
22 | } |
23 | |
24 | QColor QtGradientStop::color() const |
25 | { |
26 | return d_ptr->m_color; |
27 | } |
28 | |
29 | QtGradientStopsModel *QtGradientStop::gradientModel() const |
30 | { |
31 | return d_ptr->m_model; |
32 | } |
33 | |
34 | void QtGradientStop::setColor(const QColor &color) |
35 | { |
36 | d_ptr->m_color = color; |
37 | } |
38 | |
39 | void QtGradientStop::setPosition(qreal position) |
40 | { |
41 | d_ptr->m_position = position; |
42 | } |
43 | |
44 | QtGradientStop::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 | |
52 | QtGradientStop::~QtGradientStop() |
53 | { |
54 | } |
55 | |
56 | class QtGradientStopsModelPrivate |
57 | { |
58 | QtGradientStopsModel *q_ptr; |
59 | Q_DECLARE_PUBLIC(QtGradientStopsModel) |
60 | public: |
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 | |
69 | QtGradientStopsModel::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 | |
76 | QtGradientStopsModel::~QtGradientStopsModel() |
77 | { |
78 | clear(); |
79 | } |
80 | |
81 | QtGradientStopsModel::PositionStopMap QtGradientStopsModel::stops() const |
82 | { |
83 | return d_ptr->m_posToStop; |
84 | } |
85 | |
86 | QtGradientStop *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 | |
93 | QColor 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 | |
131 | QList<QtGradientStop *> QtGradientStopsModel::selectedStops() const |
132 | { |
133 | return d_ptr->m_selection.keys(); |
134 | } |
135 | |
136 | QtGradientStop *QtGradientStopsModel::currentStop() const |
137 | { |
138 | return d_ptr->m_current; |
139 | } |
140 | |
141 | bool QtGradientStopsModel::isSelected(QtGradientStop *stop) const |
142 | { |
143 | if (d_ptr->m_selection.contains(key: stop)) |
144 | return true; |
145 | return false; |
146 | } |
147 | |
148 | QtGradientStop *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 | |
169 | void 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 | |
185 | void 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 | |
206 | void 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 | |
227 | void 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 | |
239 | void 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 | |
255 | void 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 | |
267 | QtGradientStop *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 | |
280 | QtGradientStop *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 | |
294 | QtGradientStopsModel *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 | |
305 | void 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 | |
373 | void QtGradientStopsModel::clear() |
374 | { |
375 | const auto stopsList = stops().values(); |
376 | for (QtGradientStop *stop : stopsList) |
377 | removeStop(stop); |
378 | } |
379 | |
380 | void QtGradientStopsModel::clearSelection() |
381 | { |
382 | const auto stopsList = selectedStops(); |
383 | for (QtGradientStop *stop : stopsList) |
384 | selectStop(stop, select: false); |
385 | } |
386 | |
387 | void 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 | |
407 | void 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 | |
414 | void 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 | |
424 | QT_END_NAMESPACE |
425 | |