| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qtgradientstopsmodel.h" |
| #include <QtGui/QColor> |
| |
| QT_BEGIN_NAMESPACE |
| |
| class QtGradientStopPrivate |
| { |
| public: |
| qreal m_position; |
| QColor m_color; |
| QtGradientStopsModel *m_model; |
| }; |
| |
| qreal QtGradientStop::position() const |
| { |
| return d_ptr->m_position; |
| } |
| |
| QColor QtGradientStop::color() const |
| { |
| return d_ptr->m_color; |
| } |
| |
| QtGradientStopsModel *QtGradientStop::gradientModel() const |
| { |
| return d_ptr->m_model; |
| } |
| |
| void QtGradientStop::setColor(const QColor &color) |
| { |
| d_ptr->m_color = color; |
| } |
| |
| void QtGradientStop::setPosition(qreal position) |
| { |
| d_ptr->m_position = position; |
| } |
| |
| QtGradientStop::QtGradientStop(QtGradientStopsModel *model) |
| : d_ptr(new QtGradientStopPrivate()) |
| { |
| d_ptr->m_position = 0; |
| d_ptr->m_color = Qt::white; |
| d_ptr->m_model = model; |
| } |
| |
| QtGradientStop::~QtGradientStop() |
| { |
| } |
| |
| class QtGradientStopsModelPrivate |
| { |
| QtGradientStopsModel *q_ptr; |
| Q_DECLARE_PUBLIC(QtGradientStopsModel) |
| public: |
| QMap<qreal, QtGradientStop *> m_posToStop; |
| QMap<QtGradientStop *, qreal> m_stopToPos; |
| QMap<QtGradientStop *, bool> m_selection; |
| QtGradientStop *m_current; |
| }; |
| |
| |
| |
| QtGradientStopsModel::QtGradientStopsModel(QObject *parent) |
| : QObject(parent), d_ptr(new QtGradientStopsModelPrivate) |
| { |
| d_ptr->q_ptr = this; |
| d_ptr->m_current = 0; |
| } |
| |
| QtGradientStopsModel::~QtGradientStopsModel() |
| { |
| clear(); |
| } |
| |
| QtGradientStopsModel::PositionStopMap QtGradientStopsModel::stops() const |
| { |
| return d_ptr->m_posToStop; |
| } |
| |
| QtGradientStop *QtGradientStopsModel::at(qreal pos) const |
| { |
| if (d_ptr->m_posToStop.contains(pos)) |
| return d_ptr->m_posToStop[pos]; |
| return 0; |
| } |
| |
| QColor QtGradientStopsModel::color(qreal pos) const |
| { |
| PositionStopMap gradStops = stops(); |
| if (gradStops.isEmpty()) |
| return QColor::fromRgbF(pos, pos, pos, 1.0); |
| if (gradStops.contains(pos)) |
| return gradStops[pos]->color(); |
| |
| gradStops[pos] = 0; |
| PositionStopMap::ConstIterator itStop = gradStops.constFind(pos); |
| if (itStop == gradStops.constBegin()) { |
| ++itStop; |
| return itStop.value()->color(); |
| } |
| if (itStop == --gradStops.constEnd()) { |
| --itStop; |
| return itStop.value()->color(); |
| } |
| PositionStopMap::ConstIterator itPrev = itStop; |
| PositionStopMap::ConstIterator itNext = itStop; |
| --itPrev; |
| ++itNext; |
| |
| double prevX = itPrev.key(); |
| double nextX = itNext.key(); |
| |
| double coefX = (pos - prevX) / (nextX - prevX); |
| QColor prevCol = itPrev.value()->color(); |
| QColor nextCol = itNext.value()->color(); |
| |
| QColor newColor; |
| newColor.setRgbF((nextCol.redF() - prevCol.redF() ) * coefX + prevCol.redF(), |
| (nextCol.greenF() - prevCol.greenF()) * coefX + prevCol.greenF(), |
| (nextCol.blueF() - prevCol.blueF() ) * coefX + prevCol.blueF(), |
| (nextCol.alphaF() - prevCol.alphaF()) * coefX + prevCol.alphaF()); |
| return newColor; |
| } |
| |
| QList<QtGradientStop *> QtGradientStopsModel::selectedStops() const |
| { |
| return d_ptr->m_selection.keys(); |
| } |
| |
| QtGradientStop *QtGradientStopsModel::currentStop() const |
| { |
| return d_ptr->m_current; |
| } |
| |
| bool QtGradientStopsModel::isSelected(QtGradientStop *stop) const |
| { |
| if (d_ptr->m_selection.contains(stop)) |
| return true; |
| return false; |
| } |
| |
| QtGradientStop *QtGradientStopsModel::addStop(qreal pos, const QColor &color) |
| { |
| qreal newPos = pos; |
| if (pos < 0.0) |
| newPos = 0.0; |
| if (pos > 1.0) |
| newPos = 1.0; |
| if (d_ptr->m_posToStop.contains(newPos)) |
| return 0; |
| QtGradientStop *stop = new QtGradientStop(); |
| stop->setPosition(newPos); |
| stop->setColor(color); |
| |
| d_ptr->m_posToStop[newPos] = stop; |
| d_ptr->m_stopToPos[stop] = newPos; |
| |
| emit stopAdded(stop); |
| |
| return stop; |
| } |
| |
| void QtGradientStopsModel::removeStop(QtGradientStop *stop) |
| { |
| if (!d_ptr->m_stopToPos.contains(stop)) |
| return; |
| if (currentStop() == stop) |
| setCurrentStop(0); |
| selectStop(stop, false); |
| |
| emit stopRemoved(stop); |
| |
| qreal pos = d_ptr->m_stopToPos[stop]; |
| d_ptr->m_stopToPos.remove(stop); |
| d_ptr->m_posToStop.remove(pos); |
| delete stop; |
| } |
| |
| void QtGradientStopsModel::moveStop(QtGradientStop *stop, qreal newPos) |
| { |
| if (!d_ptr->m_stopToPos.contains(stop)) |
| return; |
| if (d_ptr->m_posToStop.contains(newPos)) |
| return; |
| |
| if (newPos > 1.0) |
| newPos = 1.0; |
| else if (newPos < 0.0) |
| newPos = 0.0; |
| |
| emit stopMoved(stop, newPos); |
| |
| const qreal oldPos = stop->position(); |
| stop->setPosition(newPos); |
| d_ptr->m_stopToPos[stop] = newPos; |
| d_ptr->m_posToStop.remove(oldPos); |
| d_ptr->m_posToStop[newPos] = stop; |
| } |
| |
| void QtGradientStopsModel::swapStops(QtGradientStop *stop1, QtGradientStop *stop2) |
| { |
| if (stop1 == stop2) |
| return; |
| if (!d_ptr->m_stopToPos.contains(stop1)) |
| return; |
| if (!d_ptr->m_stopToPos.contains(stop2)) |
| return; |
| |
| emit stopsSwapped(stop1, stop2); |
| |
| const qreal pos1 = stop1->position(); |
| const qreal pos2 = stop2->position(); |
| stop1->setPosition(pos2); |
| stop2->setPosition(pos1); |
| d_ptr->m_stopToPos[stop1] = pos2; |
| d_ptr->m_stopToPos[stop2] = pos1; |
| d_ptr->m_posToStop[pos1] = stop2; |
| d_ptr->m_posToStop[pos2] = stop1; |
| } |
| |
| void QtGradientStopsModel::changeStop(QtGradientStop *stop, const QColor &newColor) |
| { |
| if (!d_ptr->m_stopToPos.contains(stop)) |
| return; |
| if (stop->color() == newColor) |
| return; |
| |
| emit stopChanged(stop, newColor); |
| |
| stop->setColor(newColor); |
| } |
| |
| void QtGradientStopsModel::selectStop(QtGradientStop *stop, bool select) |
| { |
| if (!d_ptr->m_stopToPos.contains(stop)) |
| return; |
| bool selected = d_ptr->m_selection.contains(stop); |
| if (select == selected) |
| return; |
| |
| emit stopSelected(stop, select); |
| |
| if (select) |
| d_ptr->m_selection[stop] = true; |
| else |
| d_ptr->m_selection.remove(stop); |
| } |
| |
| void QtGradientStopsModel::setCurrentStop(QtGradientStop *stop) |
| { |
| if (stop && !d_ptr->m_stopToPos.contains(stop)) |
| return; |
| if (stop == currentStop()) |
| return; |
| |
| emit currentStopChanged(stop); |
| |
| d_ptr->m_current = stop; |
| } |
| |
| QtGradientStop *QtGradientStopsModel::firstSelected() const |
| { |
| PositionStopMap stopList = stops(); |
| PositionStopMap::ConstIterator itStop = stopList.constBegin(); |
| while (itStop != stopList.constEnd()) { |
| QtGradientStop *stop = itStop.value(); |
| if (isSelected(stop)) |
| return stop; |
| ++itStop; |
| }; |
| return 0; |
| } |
| |
| QtGradientStop *QtGradientStopsModel::lastSelected() const |
| { |
| PositionStopMap stopList = stops(); |
| PositionStopMap::ConstIterator itStop = stopList.constEnd(); |
| while (itStop != stopList.constBegin()) { |
| --itStop; |
| |
| QtGradientStop *stop = itStop.value(); |
| if (isSelected(stop)) |
| return stop; |
| }; |
| return 0; |
| } |
| |
| QtGradientStopsModel *QtGradientStopsModel::clone() const |
| { |
| QtGradientStopsModel *model = new QtGradientStopsModel(); |
| |
| QMap<qreal, QtGradientStop *> stopsToClone = stops(); |
| for (auto it = stopsToClone.cbegin(), end = stopsToClone.cend(); it != end; ++it) |
| model->addStop(it.key(), it.value()->color()); |
| // clone selection and current also |
| return model; |
| } |
| |
| void QtGradientStopsModel::moveStops(double newPosition) |
| { |
| QtGradientStop *current = currentStop(); |
| if (!current) |
| return; |
| |
| double newPos = newPosition; |
| |
| if (newPos > 1) |
| newPos = 1; |
| else if (newPos < 0) |
| newPos = 0; |
| |
| if (newPos == current->position()) |
| return; |
| |
| double offset = newPos - current->position(); |
| |
| QtGradientStop *first = firstSelected(); |
| QtGradientStop *last = lastSelected(); |
| |
| if (first && last) { // multiselection |
| double maxOffset = 1.0 - last->position(); |
| double minOffset = -first->position(); |
| |
| if (offset > maxOffset) |
| offset = maxOffset; |
| else if (offset < minOffset) |
| offset = minOffset; |
| |
| } |
| |
| if (offset == 0) |
| return; |
| |
| bool forward = (offset > 0) ? false : true; |
| |
| PositionStopMap stopList; |
| |
| const auto selected = selectedStops(); |
| for (QtGradientStop *stop : selected) |
| stopList[stop->position()] = stop; |
| stopList[current->position()] = current; |
| |
| PositionStopMap::ConstIterator itStop = forward ? stopList.constBegin() : stopList.constEnd(); |
| while (itStop != (forward ? stopList.constEnd() : stopList.constBegin())) { |
| if (!forward) |
| --itStop; |
| QtGradientStop *stop = itStop.value(); |
| double pos = stop->position() + offset; |
| if (pos > 1) |
| pos = 1; |
| if (pos < 0) |
| pos = 0; |
| |
| if (current == stop) |
| pos = newPos; |
| |
| QtGradientStop *oldStop = at(pos); |
| if (oldStop && !stopList.values().contains(oldStop)) |
| removeStop(oldStop); |
| moveStop(stop, pos); |
| |
| if (forward) |
| ++itStop; |
| } |
| } |
| |
| void QtGradientStopsModel::clear() |
| { |
| const auto stopsList = stops().values(); |
| for (QtGradientStop *stop : stopsList) |
| removeStop(stop); |
| } |
| |
| void QtGradientStopsModel::clearSelection() |
| { |
| const auto stopsList = selectedStops(); |
| for (QtGradientStop *stop : stopsList) |
| selectStop(stop, false); |
| } |
| |
| namespace { |
| template <typename BidirectionalIterator> |
| std::reverse_iterator<BidirectionalIterator> rev(BidirectionalIterator it) |
| { return std::reverse_iterator<BidirectionalIterator>(it); } |
| } |
| |
| void QtGradientStopsModel::flipAll() |
| { |
| QMap<qreal, QtGradientStop *> stopsMap = stops(); |
| QMap<QtGradientStop *, bool> swappedList; |
| for (auto itStop = rev(stopsMap.keyValueEnd()), end = rev(stopsMap.keyValueBegin()); itStop != end; ++itStop) { |
| QtGradientStop *stop = (*itStop).second; |
| if (swappedList.contains(stop)) |
| continue; |
| const double newPos = 1.0 - (*itStop).first; |
| if (stopsMap.contains(newPos)) { |
| QtGradientStop *swapped = stopsMap.value(newPos); |
| swappedList[swapped] = true; |
| swapStops(stop, swapped); |
| } else { |
| moveStop(stop, newPos); |
| } |
| } |
| } |
| |
| void QtGradientStopsModel::selectAll() |
| { |
| const auto stopsMap = stops(); |
| for (auto it = stopsMap.cbegin(), end = stopsMap.cend(); it != end; ++it) |
| selectStop(it.value(), true); |
| } |
| |
| void QtGradientStopsModel::deleteStops() |
| { |
| const auto selected = selectedStops(); |
| for (QtGradientStop *stop : selected) |
| removeStop(stop); |
| QtGradientStop *current = currentStop(); |
| if (current) |
| removeStop(current); |
| } |
| |
| QT_END_NAMESPACE |