diff options
Diffstat (limited to 'src/plugins/qmldesigner/components/pathtool')
13 files changed, 2831 insertions, 0 deletions
diff --git a/src/plugins/qmldesigner/components/pathtool/controlpoint.cpp b/src/plugins/qmldesigner/components/pathtool/controlpoint.cpp new file mode 100644 index 0000000000..d2a8bf75c3 --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/controlpoint.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#include "controlpoint.h" + +#include <QtDebug> + +#include <variantproperty.h> + +#include <rewritertransaction.h> + +namespace QmlDesigner { + +ControlPoint::ControlPoint() = default; + +ControlPoint::ControlPoint(const ControlPoint &other) = default; + +ControlPoint::ControlPoint(const QPointF &coordinate) + : d(new ControlPointData) +{ + d->coordinate = coordinate; +} + +ControlPoint::ControlPoint(double x, double y) + : d(new ControlPointData) +{ + d->coordinate = QPointF(x, y); +} + +ControlPoint::~ControlPoint() = default; + +ControlPoint &ControlPoint::operator =(const ControlPoint &other) +{ + if (d != other.d) + d = other.d; + + return *this; +} + +void ControlPoint::setX(double x) +{ + d->coordinate.setX(x); +} + +void ControlPoint::setY(double y) +{ + d->coordinate.setY(y); +} + +void ControlPoint::setCoordinate(const QPointF &coordinate) +{ + d->coordinate = coordinate; +} + +void ControlPoint::setPathElementModelNode(const ModelNode &modelNode) +{ + d->pathElementModelNode = modelNode; +} + +ModelNode ControlPoint::pathElementModelNode() const +{ + return d->pathElementModelNode; +} + +void ControlPoint::setPathModelNode(const ModelNode &pathModelNode) +{ + d->pathModelNode = pathModelNode; +} + +ModelNode ControlPoint::pathModelNode() const +{ + return d->pathModelNode; +} + +void ControlPoint::setPointType(PointType pointType) +{ + d->pointType = pointType; +} + +PointType ControlPoint::pointType() const +{ + return d->pointType; +} + +QPointF ControlPoint::coordinate() const +{ + return d->coordinate; +} + +bool ControlPoint::isValid() const +{ + return d.data(); +} + +bool ControlPoint::isEditPoint() const +{ + return isValid() && (pointType() == StartPoint || pointType() == EndPoint); +} + +bool ControlPoint::isControlVertex() const +{ + return isValid() && (pointType() == FirstControlPoint || pointType() == SecondControlPoint); +} + +void ControlPoint::updateModelNode() +{ + switch (pointType()) { + case StartPoint: + d->pathModelNode.variantProperty("startX").setValue(coordinate().x()); + d->pathModelNode.variantProperty("startY").setValue(coordinate().y()); + break; + case FirstControlPoint: + d->pathElementModelNode.variantProperty("control1X").setValue(coordinate().x()); + d->pathElementModelNode.variantProperty("control1Y").setValue(coordinate().y()); + break; + case SecondControlPoint: + d->pathElementModelNode.variantProperty("control2X").setValue(coordinate().x()); + d->pathElementModelNode.variantProperty("control2Y").setValue(coordinate().y()); + break; + case EndPoint: + d->pathElementModelNode.variantProperty("x").setValue(coordinate().x()); + d->pathElementModelNode.variantProperty("y").setValue(coordinate().y()); + break; + case StartAndEndPoint: + d->pathElementModelNode.variantProperty("x").setValue(coordinate().x()); + d->pathElementModelNode.variantProperty("y").setValue(coordinate().y()); + d->pathModelNode.variantProperty("startX").setValue(coordinate().x()); + d->pathModelNode.variantProperty("startY").setValue(coordinate().y()); + break; + } +} + +bool operator ==(const ControlPoint& firstControlPoint, const ControlPoint& secondControlPoint) +{ + return firstControlPoint.d.data() == secondControlPoint.d.data() && firstControlPoint.d.data(); +} + +QDebug operator<<(QDebug debug, const ControlPoint &controlPoint) +{ + if (controlPoint.isValid()) { + debug.nospace() << "ControlPoint(" + << controlPoint.coordinate().x() << ", " + << controlPoint.coordinate().y() << ", " + << controlPoint.pointType() << ')'; + } else { + debug.nospace() << "ControlPoint(invalid)"; + } + + return debug.space(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/pathtool/controlpoint.h b/src/plugins/qmldesigner/components/pathtool/controlpoint.h new file mode 100644 index 0000000000..39dc184978 --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/controlpoint.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <modelnode.h> + +#include <QPointF> +#include <QExplicitlySharedDataPointer> + +namespace QmlDesigner { + +enum PointType { + StartPoint, + FirstControlPoint, + SecondControlPoint, + EndPoint, + StartAndEndPoint +}; + +class ControlPointData : public QSharedData +{ +public: + ModelNode pathElementModelNode; + ModelNode pathModelNode; + QPointF coordinate; + PointType pointType; +}; + +class ControlPoint +{ + friend bool operator ==(const ControlPoint& firstControlPoint, const ControlPoint& secondControlPoint); + +public: + ControlPoint(); + ControlPoint(const ControlPoint &other); + ControlPoint(const QPointF &coordinate); + ControlPoint(double x, double y); + + ~ControlPoint(); + + ControlPoint &operator =(const ControlPoint &other); + + void setX(double x); + void setY(double y); + void setCoordinate(const QPointF &coordinate); + QPointF coordinate() const; + + void setPathElementModelNode(const ModelNode &pathElementModelNode); + ModelNode pathElementModelNode() const; + + void setPathModelNode(const ModelNode &pathModelNode); + ModelNode pathModelNode() const; + + void setPointType(PointType pointType); + PointType pointType() const; + + bool isValid() const; + bool isEditPoint() const; + bool isControlVertex() const; + + void updateModelNode(); + +private: + QExplicitlySharedDataPointer<ControlPointData> d; +}; + +bool operator ==(const ControlPoint& firstControlPoint, const ControlPoint& secondControlPoint); +QDebug operator<<(QDebug debug, const ControlPoint &controlPoint); + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/pathtool/cubicsegment.cpp b/src/plugins/qmldesigner/components/pathtool/cubicsegment.cpp new file mode 100644 index 0000000000..0005514339 --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/cubicsegment.cpp @@ -0,0 +1,367 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#include "cubicsegment.h" + +#include <qmath.h> +#include <QtDebug> + + +namespace QmlDesigner { + +CubicSegment::CubicSegment() = default; + +CubicSegment CubicSegment::create() +{ + CubicSegment cubicSegment; + cubicSegment.d = new CubicSegmentData; + + return cubicSegment; +} + +void CubicSegment::setModelNode(const ModelNode &modelNode) +{ + d->modelNode = modelNode; +} + +ModelNode CubicSegment::modelNode() const +{ + return d->modelNode; +} + +void CubicSegment::setFirstControlPoint(const ControlPoint &firstControlPoint) +{ + d->firstControllPoint = firstControlPoint; +} + +void CubicSegment::setFirstControlPoint(double x, double y) +{ + d->firstControllPoint.setX(x); + d->firstControllPoint.setY(y); +} + +void CubicSegment::setFirstControlPoint(const QPointF &coordiante) +{ + d->firstControllPoint.setCoordinate(coordiante); +} + +void CubicSegment::setSecondControlPoint(const ControlPoint &secondControlPoint) +{ + d->secondControllPoint = secondControlPoint; + d->secondControllPoint.setPathElementModelNode(d->modelNode); + d->secondControllPoint.setPointType(FirstControlPoint); +} + +void CubicSegment::setSecondControlPoint(double x, double y) +{ + d->secondControllPoint.setX(x); + d->secondControllPoint.setY(y); + d->secondControllPoint.setPathElementModelNode(d->modelNode); + d->secondControllPoint.setPointType(FirstControlPoint); +} + +void CubicSegment::setSecondControlPoint(const QPointF &coordiante) +{ + d->secondControllPoint.setCoordinate(coordiante); + d->secondControllPoint.setPathElementModelNode(d->modelNode); + d->secondControllPoint.setPointType(FirstControlPoint); +} + +void CubicSegment::setThirdControlPoint(const ControlPoint &thirdControlPoint) +{ + d->thirdControllPoint = thirdControlPoint; + d->thirdControllPoint.setPathElementModelNode(d->modelNode); + d->thirdControllPoint.setPointType(SecondControlPoint); +} + +void CubicSegment::setThirdControlPoint(double x, double y) +{ + d->thirdControllPoint.setX(x); + d->thirdControllPoint.setY(y); + d->thirdControllPoint.setPathElementModelNode(d->modelNode); + d->thirdControllPoint.setPointType(SecondControlPoint); +} + +void CubicSegment::setThirdControlPoint(const QPointF &coordiante) +{ + d->thirdControllPoint.setCoordinate(coordiante); + d->thirdControllPoint.setPathElementModelNode(d->modelNode); + d->thirdControllPoint.setPointType(SecondControlPoint); +} + +void CubicSegment::setFourthControlPoint(const ControlPoint &fourthControlPoint) +{ + d->fourthControllPoint = fourthControlPoint; + d->fourthControllPoint.setPathElementModelNode(d->modelNode); + d->fourthControllPoint.setPointType(EndPoint); +} + +void CubicSegment::setFourthControlPoint(double x, double y) +{ + d->fourthControllPoint.setX(x); + d->fourthControllPoint.setY(y); + d->fourthControllPoint.setPathElementModelNode(d->modelNode); + d->fourthControllPoint.setPointType(EndPoint); +} + +void CubicSegment::setFourthControlPoint(const QPointF &coordiante) +{ + d->fourthControllPoint.setCoordinate(coordiante); + d->fourthControllPoint.setPathElementModelNode(d->modelNode); + d->fourthControllPoint.setPointType(EndPoint); +} + +void CubicSegment::setAttributes(const QMap<QString, QVariant> &attributes) +{ + d->attributes = attributes; +} + +void CubicSegment::setPercent(double percent) +{ + d->percent = percent; +} + +ControlPoint CubicSegment::firstControlPoint() const +{ + return d->firstControllPoint; +} + +ControlPoint CubicSegment::secondControlPoint() const +{ + return d->secondControllPoint; +} + +ControlPoint CubicSegment::thirdControlPoint() const +{ + return d->thirdControllPoint; +} + +ControlPoint CubicSegment::fourthControlPoint() const +{ + return d->fourthControllPoint; +} + +const QMap<QString, QVariant> CubicSegment::attributes() const +{ + return d->attributes; +} + +double CubicSegment::percent() const +{ + return d->percent; +} + +QList<ControlPoint> CubicSegment::controlPoints() const +{ + QList<ControlPoint> controlPointList; + + controlPointList.reserve(4); + + controlPointList.append(firstControlPoint()); + controlPointList.append(secondControlPoint()); + controlPointList.append(thirdControlPoint()); + controlPointList.append(fourthControlPoint()); + + return controlPointList; +} + +double CubicSegment::firstControlX() const +{ + return firstControlPoint().coordinate().x(); +} + +double CubicSegment::firstControlY() const +{ + return firstControlPoint().coordinate().y(); +} + +double CubicSegment::secondControlX() const +{ + return secondControlPoint().coordinate().x(); +} + +double CubicSegment::secondControlY() const +{ + return secondControlPoint().coordinate().y(); +} + +double CubicSegment::thirdControlX() const +{ + return thirdControlPoint().coordinate().x(); +} + +double CubicSegment::thirdControlY() const +{ + return thirdControlPoint().coordinate().y(); +} + +double CubicSegment::fourthControlX() const +{ + return fourthControlPoint().coordinate().x(); +} + +double CubicSegment::fourthControlY() const +{ + return fourthControlPoint().coordinate().y(); +} + +double CubicSegment::quadraticControlX() const +{ + return -0.25 * firstControlX() + 0.75 * secondControlX() + 0.75 * thirdControlX() - 0.25 * fourthControlX(); +} + +double CubicSegment::quadraticControlY() const +{ + return -0.25 * firstControlY() + 0.75 * secondControlY() + 0.75 * thirdControlY() - 0.25 * fourthControlY(); +} + +bool CubicSegment::isValid() const +{ + return d.data(); +} + +bool CubicSegment::canBeConvertedToLine() const +{ + return canBeConvertedToQuad() + && qFuzzyIsNull(((3. * d->firstControllPoint.coordinate()) + - (6. * d->secondControllPoint.coordinate()) + + (3. * d->thirdControllPoint.coordinate())).manhattanLength());; +} + +bool CubicSegment::canBeConvertedToQuad() const +{ + return qFuzzyIsNull(((3. * d->secondControllPoint.coordinate()) + - (3 * d->thirdControllPoint.coordinate()) + + d->fourthControllPoint.coordinate() + - d->firstControllPoint.coordinate()).manhattanLength()); +} + +QPointF CubicSegment::sample(double t) const +{ + return qPow(1.-t, 3.) * firstControlPoint().coordinate() + + 3 * qPow(1.-t, 2.) * t * secondControlPoint().coordinate() + + 3 * qPow(t, 2.) * (1. - t) * thirdControlPoint().coordinate() + + qPow(t, 3.) * fourthControlPoint().coordinate(); +} + +double CubicSegment::minimumDistance(const QPointF &pickPoint, double &tReturnValue) const +{ + double actualMinimumDistance = 10000000.; + for (double t = 0.0; t <= 1.0; t += 0.1) { + QPointF samplePoint = sample(t); + QPointF distanceVector = pickPoint - samplePoint; + if (distanceVector.manhattanLength() < actualMinimumDistance) { + actualMinimumDistance = distanceVector.manhattanLength(); + tReturnValue = t; + } + } + + return actualMinimumDistance; +} + +static QPointF interpolatedPoint(double t, const QPointF &firstPoint, const QPointF &secondPoint) +{ + return (secondPoint - firstPoint) * t + firstPoint; +} + +QPair<CubicSegment, CubicSegment> CubicSegment::split(double t) +{ + // first pass + QPointF secondPointFirstSegment = interpolatedPoint(t, firstControlPoint().coordinate(), secondControlPoint().coordinate()); + QPointF firstIntermediatPoint = interpolatedPoint(t, secondControlPoint().coordinate(), thirdControlPoint().coordinate()); + QPointF thirdPointSecondSegment = interpolatedPoint(t, thirdControlPoint().coordinate(), fourthControlPoint().coordinate()); + + // second pass + QPointF thirdPointFirstSegment = interpolatedPoint(t, secondPointFirstSegment, firstIntermediatPoint); + QPointF secondPointSecondSegment = interpolatedPoint(t, firstIntermediatPoint, thirdPointSecondSegment); + + // third pass + QPointF midPoint = interpolatedPoint(t, thirdPointFirstSegment, secondPointSecondSegment); + ControlPoint midControlPoint(midPoint); + + + CubicSegment firstCubicSegment = CubicSegment::create(); + firstCubicSegment.setFirstControlPoint(firstControlPoint().coordinate()); + firstCubicSegment.setSecondControlPoint(secondPointFirstSegment); + firstCubicSegment.setThirdControlPoint(thirdPointFirstSegment); + firstCubicSegment.setFourthControlPoint(midControlPoint); + + CubicSegment secondCubicSegment = CubicSegment::create(); + secondCubicSegment.setFirstControlPoint(midControlPoint); + secondCubicSegment.setSecondControlPoint(secondPointSecondSegment); + secondCubicSegment.setThirdControlPoint(thirdPointSecondSegment); + secondCubicSegment.setFourthControlPoint(fourthControlPoint().coordinate()); + + qDebug() << firstCubicSegment << secondCubicSegment; + + return {firstCubicSegment, secondCubicSegment}; +} + +void CubicSegment::makeStraightLine() +{ + QPointF lineVector = fourthControlPoint().coordinate() - firstControlPoint().coordinate(); + QPointF newSecondControlPoint = firstControlPoint().coordinate() + (lineVector * 0.3); + QPointF newThirdControlPoint = fourthControlPoint().coordinate() - (lineVector * 0.3); + setSecondControlPoint(newSecondControlPoint); + setThirdControlPoint(newThirdControlPoint); +} + +void CubicSegment::updateModelNode() +{ + firstControlPoint().updateModelNode(); + secondControlPoint().updateModelNode(); + thirdControlPoint().updateModelNode(); + fourthControlPoint().updateModelNode(); +} + +CubicSegmentData::CubicSegmentData() + : firstControllPoint(0., 0.), + secondControllPoint(0., 0.), + thirdControllPoint(0., 0.), + fourthControllPoint(0., 0.), + percent(-1.0) +{ +} + +bool operator ==(const CubicSegment& firstCubicSegment, const CubicSegment& secondCubicSegment) +{ + return firstCubicSegment.d.data() == secondCubicSegment.d.data(); +} + +QDebug operator<<(QDebug debug, const CubicSegment &cubicSegment) +{ + if (cubicSegment.isValid()) { + debug.nospace() << "CubicSegment(" + << cubicSegment.firstControlPoint() << ", " + << cubicSegment.secondControlPoint() << ", " + << cubicSegment.thirdControlPoint() << ", " + << cubicSegment.fourthControlPoint() << ')'; + } else { + debug.nospace() << "CubicSegment(invalid)"; + } + + return debug.space(); +} +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/pathtool/cubicsegment.h b/src/plugins/qmldesigner/components/pathtool/cubicsegment.h new file mode 100644 index 0000000000..e22b4e1aa3 --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/cubicsegment.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "controlpoint.h" + +#include <modelnode.h> + +#include <QMap> + +#include <QPointF> +#include <QExplicitlySharedDataPointer> + +namespace QmlDesigner { + +class CubicSegmentData : public QSharedData +{ +public: + CubicSegmentData(); + ModelNode modelNode; + ControlPoint firstControllPoint; + ControlPoint secondControllPoint; + ControlPoint thirdControllPoint; + ControlPoint fourthControllPoint; + QMap<QString, QVariant> attributes; + double percent; +}; + +class CubicSegment +{ + friend bool operator ==(const CubicSegment& firstCubicSegment, const CubicSegment& secondCubicSegment); + +public: + CubicSegment(); + + static CubicSegment create(); + + void setModelNode(const ModelNode &modelNode); + ModelNode modelNode() const; + + void setFirstControlPoint(const ControlPoint &firstControlPoint); + void setFirstControlPoint(double x, double y); + void setFirstControlPoint(const QPointF &coordiante); + + void setSecondControlPoint(const ControlPoint &secondControlPoint); + void setSecondControlPoint(double x, double y); + void setSecondControlPoint(const QPointF &coordiante); + + void setThirdControlPoint(const ControlPoint &thirdControlPoint); + void setThirdControlPoint(double x, double y); + void setThirdControlPoint(const QPointF &coordiante); + + void setFourthControlPoint(const ControlPoint &fourthControlPoint); + void setFourthControlPoint(double x, double y); + void setFourthControlPoint(const QPointF &coordiante); + + void setAttributes(const QMap<QString, QVariant> &attributes); + + void setPercent(double percent); + + ControlPoint firstControlPoint() const; + ControlPoint secondControlPoint() const; + ControlPoint thirdControlPoint() const; + ControlPoint fourthControlPoint() const; + + const QMap<QString, QVariant> attributes() const; + + double percent() const; + + QList<ControlPoint> controlPoints() const; + + double firstControlX() const; + double firstControlY() const; + double secondControlX() const; + double secondControlY() const; + double thirdControlX() const; + double thirdControlY() const; + double fourthControlX() const; + double fourthControlY() const; + double quadraticControlX() const; + double quadraticControlY() const; + + bool isValid() const; + bool canBeConvertedToLine() const; + bool canBeConvertedToQuad() const; + + QPointF sample(double t) const; + double minimumDistance(const QPointF &pickPoint, double &t) const; + + QPair<CubicSegment, CubicSegment> split(double t); + + void makeStraightLine(); + + void updateModelNode(); + +private: + QExplicitlySharedDataPointer<CubicSegmentData> d; +}; + +bool operator ==(const CubicSegment& firstCubicSegment, const CubicSegment& secondCubicSegment); +QDebug operator<<(QDebug debug, const CubicSegment &cubicSegment); + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/pathtool/pathitem.cpp b/src/plugins/qmldesigner/components/pathtool/pathitem.cpp new file mode 100644 index 0000000000..76fe6f0b90 --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/pathitem.cpp @@ -0,0 +1,971 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#include "pathitem.h" + +#include <exception.h> +#include <nodeproperty.h> +#include <variantproperty.h> +#include <nodelistproperty.h> +#include <rewritingexception.h> +#include <rewritertransaction.h> +#include <formeditorscene.h> +#include <formeditorview.h> +#include <theme.h> + +#include <QPainter> +#include <QMenu> +#include <QtDebug> +#include <QGraphicsSceneMouseEvent> + +namespace QmlDesigner { + +PathItem::PathItem(FormEditorScene* scene) + : m_selectionManipulator(this), + m_lastPercent(-1.), + m_formEditorItem(nullptr), + m_dontUpdatePath(false) +{ + scene->addItem(this); + setFlag(QGraphicsItem::ItemIsMovable, false); +} + +PathItem::~PathItem() +{ + m_formEditorItem = nullptr; +} + +static ModelNode pathModelNode(FormEditorItem *formEditorItem) +{ + ModelNode modelNode = formEditorItem->qmlItemNode().modelNode(); + + return modelNode.nodeProperty("path").modelNode(); +} + +using PropertyPair = QPair<PropertyName, QVariant>; + +void PathItem::writeLinePath(const ModelNode &pathNode, const CubicSegment &cubicSegment) +{ + QList<PropertyPair> propertyList; + propertyList.append(PropertyPair("x", cubicSegment.fourthControlX())); + propertyList.append(PropertyPair("y", cubicSegment.fourthControlY())); + + ModelNode lineNode = pathNode.view()->createModelNode("QtQuick.PathLine", pathNode.majorVersion(), pathNode.minorVersion(), propertyList); + pathNode.nodeListProperty("pathElements").reparentHere(lineNode); +} + +void PathItem::writeQuadPath(const ModelNode &pathNode, const CubicSegment &cubicSegment) +{ + QList<QPair<PropertyName, QVariant> > propertyList; + propertyList.append(PropertyPair("controlX", cubicSegment.quadraticControlX())); + propertyList.append(PropertyPair("controlY", cubicSegment.quadraticControlY())); + propertyList.append(PropertyPair("x", cubicSegment.fourthControlX())); + propertyList.append(PropertyPair("y", cubicSegment.fourthControlY())); + + ModelNode lineNode = pathNode.view()->createModelNode("QtQuick.PathQuad", pathNode.majorVersion(), pathNode.minorVersion(), propertyList); + pathNode.nodeListProperty("pathElements").reparentHere(lineNode); +} + +void PathItem::writeCubicPath(const ModelNode &pathNode, const CubicSegment &cubicSegment) +{ + QList<QPair<PropertyName, QVariant> > propertyList; + propertyList.append(PropertyPair("control1X", cubicSegment.secondControlX())); + propertyList.append(PropertyPair("control1Y", cubicSegment.secondControlY())); + propertyList.append(PropertyPair("control2X", cubicSegment.thirdControlX())); + propertyList.append(PropertyPair("control2Y", cubicSegment.thirdControlY())); + propertyList.append(PropertyPair("x", cubicSegment.fourthControlX())); + propertyList.append(PropertyPair("y", cubicSegment.fourthControlY())); + + ModelNode lineNode = pathNode.view()->createModelNode("QtQuick.PathCubic", pathNode.majorVersion(), pathNode.minorVersion(), propertyList); + pathNode.nodeListProperty("pathElements").reparentHere(lineNode); +} + +void PathItem::writePathAttributes(const ModelNode &pathNode, const QMap<QString, QVariant> &attributes) +{ + QMapIterator<QString, QVariant> attributesIterator(attributes); + while (attributesIterator.hasNext()) { + attributesIterator.next(); + QList<QPair<PropertyName, QVariant> > propertyList; + propertyList.append(PropertyPair("name", attributesIterator.key())); + propertyList.append(PropertyPair("value", attributesIterator.value())); + + ModelNode lineNode = pathNode.view()->createModelNode("QtQuick.PathAttribute", pathNode.majorVersion(), pathNode.minorVersion(), propertyList); + pathNode.nodeListProperty("pathElements").reparentHere(lineNode); + } +} + +void PathItem::writePathPercent(const ModelNode& pathNode, double percent) +{ + if (percent >= 0.0) { + QList<QPair<PropertyName, QVariant> > propertyList; + propertyList.append(PropertyPair("value", percent)); + + ModelNode lineNode = pathNode.view()->createModelNode("QtQuick.PathPercent", pathNode.majorVersion(), pathNode.minorVersion(), propertyList); + pathNode.nodeListProperty("pathElements").reparentHere(lineNode); + } +} + +void PathItem::writePathToProperty() +{ + PathUpdateDisabler pathUpdateDisable(this); + + ModelNode pathNode = pathModelNode(formEditorItem()); + + pathNode.view()->executeInTransaction("PathItem::writePathToProperty", [this, &pathNode](){ + QList<ModelNode> pathSegmentNodes = pathNode.nodeListProperty("pathElements").toModelNodeList(); + + foreach (ModelNode pathSegment, pathSegmentNodes) + pathSegment.destroy(); + + if (!m_cubicSegments.isEmpty()) { + pathNode.variantProperty("startX").setValue(m_cubicSegments.constFirst().firstControlPoint().coordinate().x()); + pathNode.variantProperty("startY").setValue(m_cubicSegments.constFirst().firstControlPoint().coordinate().y()); + + foreach (const CubicSegment &cubicSegment, m_cubicSegments) { + writePathAttributes(pathNode, cubicSegment.attributes()); + writePathPercent(pathNode, cubicSegment.percent()); + + if (cubicSegment.canBeConvertedToLine()) + writeLinePath(pathNode, cubicSegment); + else if (cubicSegment.canBeConvertedToQuad()) + writeQuadPath(pathNode, cubicSegment); + else + writeCubicPath(pathNode, cubicSegment); + } + + writePathAttributes(pathNode, m_lastAttributes); + writePathPercent(pathNode, m_lastPercent); + } + }); +} + +void PathItem::writePathAsCubicSegmentsOnly() +{ + PathUpdateDisabler pathUpdateDisabler(this); + + ModelNode pathNode = pathModelNode(formEditorItem()); + pathNode.view()->executeInTransaction("PathItem::writePathAsCubicSegmentsOnly", [this, &pathNode](){ + + QList<ModelNode> pathSegmentNodes = pathNode.nodeListProperty("pathElements").toModelNodeList(); + + foreach (ModelNode pathSegment, pathSegmentNodes) + pathSegment.destroy(); + + if (!m_cubicSegments.isEmpty()) { + pathNode.variantProperty("startX").setValue(m_cubicSegments.constFirst().firstControlPoint().coordinate().x()); + pathNode.variantProperty("startY").setValue(m_cubicSegments.constFirst().firstControlPoint().coordinate().y()); + + + foreach (const CubicSegment &cubicSegment, m_cubicSegments) { + writePathAttributes(pathNode, cubicSegment.attributes()); + writePathPercent(pathNode, cubicSegment.percent()); + writeCubicPath(pathNode, cubicSegment); + } + + writePathAttributes(pathNode, m_lastAttributes); + writePathPercent(pathNode, m_lastPercent); + } + }); +} + +void PathItem::setFormEditorItem(FormEditorItem *formEditorItem) +{ + m_formEditorItem = formEditorItem; + setTransform(formEditorItem->sceneTransform()); + updatePath(); + +// m_textEdit->setPlainText(m_formEditorItem->qmlItemNode().modelValue("path").toString()); +} + +static bool hasPath(FormEditorItem *formEditorItem) +{ + ModelNode modelNode = formEditorItem->qmlItemNode().modelNode(); + + return modelNode.hasProperty("path") && modelNode.property("path").isNodeProperty(); +} + +QPointF startPoint(const ModelNode &modelNode) +{ + QPointF point; + + if (modelNode.hasProperty("startX")) + point.setX(modelNode.variantProperty("startX").value().toDouble()); + + if (modelNode.hasProperty("startY")) + point.setY(modelNode.variantProperty("startY").value().toDouble()); + + return point; +} + +static void addCubicSegmentToPainterPath(const CubicSegment &cubicSegment, QPainterPath &painterPath) +{ + painterPath.cubicTo(cubicSegment.secondControlPoint().coordinate(), + cubicSegment.thirdControlPoint().coordinate(), + cubicSegment.fourthControlPoint().coordinate()); + +} + +static void drawCubicSegments(const QList<CubicSegment> &cubicSegments, QPainter *painter) +{ + painter->save(); + + QPainterPath curvePainterPath(cubicSegments.constFirst().firstControlPoint().coordinate()); + + foreach (const CubicSegment &cubicSegment, cubicSegments) + addCubicSegmentToPainterPath(cubicSegment, curvePainterPath); + + painter->setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); + painter->drawPath(curvePainterPath); + + painter->restore(); +} + +static void drawControlLine(const CubicSegment &cubicSegment, QPainter *painter) +{ + static const QPen solidPen(QColor(104, 183, 214), 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + painter->setPen(solidPen); + painter->drawLine(cubicSegment.firstControlPoint().coordinate(), + cubicSegment.secondControlPoint().coordinate()); + + QVector<double> dashVector; + dashVector.append(4); + dashVector.append(4); + QPen dashedPen(QColor(104, 183, 214), 1, Qt::CustomDashLine, Qt::FlatCap, Qt::MiterJoin); + dashedPen.setDashPattern(dashVector); + painter->setPen(dashedPen); + painter->drawLine(cubicSegment.secondControlPoint().coordinate(), + cubicSegment.thirdControlPoint().coordinate()); + + painter->setPen(QPen(QColor(104, 183, 214), 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); + painter->drawLine(cubicSegment.thirdControlPoint().coordinate(), + cubicSegment.fourthControlPoint().coordinate()); +} + +static void drawControlLines(const QList<CubicSegment> &cubicSegments, QPainter *painter) +{ + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, false); + + foreach (const CubicSegment &cubicSegment, cubicSegments) + drawControlLine(cubicSegment, painter); + + painter->restore(); +} + +static QRectF controlPointShape(-2, -2, 5, 5); + +static void drawControlPoint(const ControlPoint &controlPoint, const QList<ControlPoint> &selectionPoints, QPainter *painter) +{ + static const QColor editPointColor(0, 110, 255); + static const QColor controlVertexColor(0, 110, 255); + static const QColor selectionPointColor(0, 255, 0); + + double originX = controlPoint.coordinate().x(); + double originY = controlPoint.coordinate().y(); + + if (controlPoint.isEditPoint()) { + if (selectionPoints.contains(controlPoint)) { + painter->setBrush(selectionPointColor); + painter->setPen(selectionPointColor); + } else { + painter->setBrush(editPointColor); + painter->setPen(editPointColor); + } + painter->setRenderHint(QPainter::Antialiasing, false); + painter->drawRect(controlPointShape.adjusted(originX -1, originY - 1, originX - 1, originY - 1)); + painter->setRenderHint(QPainter::Antialiasing, true); + } else { + if (selectionPoints.contains(controlPoint)) { + painter->setBrush(selectionPointColor); + painter->setPen(selectionPointColor); + } else { + painter->setBrush(controlVertexColor); + painter->setPen(controlVertexColor); + } + painter->drawEllipse(controlPointShape.adjusted(originX, originY, originX, originY)); + } +} + +static void drawControlPoints(const QList<ControlPoint> &controlPoints, const QList<ControlPoint> &selectionPoints, QPainter *painter) +{ + painter->save(); + + foreach (const ControlPoint &controlPoint, controlPoints) + drawControlPoint(controlPoint, selectionPoints, painter); + + painter->restore(); +} + +static void drawPositionOverlay(const ControlPoint &controlPoint, QPainter *painter) +{ + QPoint position = controlPoint.coordinate().toPoint(); + position.rx() += 3; + position.ry() -= 3; + + QString postionText(QString(QLatin1String("x: %1 y: %2")).arg(controlPoint.coordinate().x()).arg(controlPoint.coordinate().y())); + painter->drawText(position, postionText); +} + +static void drawPostionOverlays(const QList<SelectionPoint> &selectedPoints, QPainter *painter) +{ + painter->save(); + QFont font = painter->font(); + font.setPixelSize(Theme::instance()->smallFontPixelSize()); + painter->setFont(font); + painter->setPen(QColor(0, 0, 0)); + + foreach (const SelectionPoint &selectedPoint, selectedPoints) + drawPositionOverlay(selectedPoint.controlPoint, painter); + + painter->restore(); +} + +static void drawMultiSelectionRectangle(const QRectF &selectionRectangle, QPainter *painter) +{ + painter->save(); + static QColor selectionBrush(painter->pen().color()); + selectionBrush.setAlpha(50); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setBrush(selectionBrush); + painter->drawRect(selectionRectangle); + painter->restore(); +} + +void PathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) +{ + painter->save(); + + painter->setRenderHint(QPainter::Antialiasing, true); + + if (!m_cubicSegments.isEmpty()) { + drawCubicSegments(m_cubicSegments, painter); + drawControlLines(m_cubicSegments, painter); + drawControlPoints(controlPoints(), m_selectionManipulator.allControlPoints(), painter); + drawPostionOverlays(m_selectionManipulator.singleSelectedPoints(), painter); + if (m_selectionManipulator.isMultiSelecting()) + drawMultiSelectionRectangle(m_selectionManipulator.multiSelectionRectangle(), painter); + } + + painter->restore(); +} + +FormEditorItem *PathItem::formEditorItem() const +{ + return m_formEditorItem; +} + +static CubicSegment createCubicSegmentForLine(const ModelNode &lineNode, const ControlPoint &startControlPoint) +{ + CubicSegment cubicSegment = CubicSegment::create(); + cubicSegment.setModelNode(lineNode); + + if (lineNode.hasProperty("x") + && lineNode.hasProperty("y")) { + + QPointF controlPoint0Line = startControlPoint.coordinate(); + QPointF controlPoint1Line(lineNode.variantProperty("x").value().toDouble(), + lineNode.variantProperty("y").value().toDouble()); + + QPointF controlPoint1Cubic = controlPoint0Line + (1./3.) * (controlPoint1Line - controlPoint0Line); + QPointF controlPoint2Cubic = controlPoint0Line + (2./3.) * (controlPoint1Line - controlPoint0Line); + + cubicSegment.setFirstControlPoint(startControlPoint); + cubicSegment.setSecondControlPoint(controlPoint1Cubic); + cubicSegment.setThirdControlPoint(controlPoint2Cubic); + cubicSegment.setFourthControlPoint(controlPoint1Line); + } else { + qWarning() << "PathLine has not all entries!"; + } + + return cubicSegment; +} + +static CubicSegment createCubicSegmentForQuad(const ModelNode &quadNode, const ControlPoint &startControlPoint) +{ + CubicSegment cubicSegment = CubicSegment::create(); + cubicSegment.setModelNode(quadNode); + + if (quadNode.hasProperty("controlX") + && quadNode.hasProperty("controlY") + && quadNode.hasProperty("x") + && quadNode.hasProperty("y")) { + QPointF controlPoint0Quad = startControlPoint.coordinate(); + QPointF controlPoint1Quad(quadNode.variantProperty("controlX").value().toDouble(), + quadNode.variantProperty("controlY").value().toDouble()); + QPointF controlPoint2Quad(quadNode.variantProperty("x").value().toDouble(), + quadNode.variantProperty("y").value().toDouble()); + + QPointF controlPoint1Cubic = controlPoint0Quad + (2./3.) * (controlPoint1Quad - controlPoint0Quad); + QPointF controlPoint2Cubic = controlPoint2Quad + (2./3.) * (controlPoint1Quad - controlPoint2Quad); + + cubicSegment.setFirstControlPoint(startControlPoint); + cubicSegment.setSecondControlPoint(controlPoint1Cubic); + cubicSegment.setThirdControlPoint(controlPoint2Cubic); + cubicSegment.setFourthControlPoint(controlPoint2Quad); + } else { + qWarning() << "PathQuad has not all entries!"; + } + + return cubicSegment; +} + +static CubicSegment createCubicSegmentForCubic(const ModelNode &cubicNode, const ControlPoint &startControlPoint) +{ + CubicSegment cubicSegment = CubicSegment::create(); + cubicSegment.setModelNode(cubicNode); + + if (cubicNode.hasProperty("control1X") + && cubicNode.hasProperty("control1Y") + && cubicNode.hasProperty("control2X") + && cubicNode.hasProperty("control2Y") + && cubicNode.hasProperty("x") + && cubicNode.hasProperty("y")) { + + cubicSegment.setFirstControlPoint(startControlPoint); + cubicSegment.setSecondControlPoint(cubicNode.variantProperty("control1X").value().toDouble(), + cubicNode.variantProperty("control1Y").value().toDouble()); + cubicSegment.setThirdControlPoint(cubicNode.variantProperty("control2X").value().toDouble(), + cubicNode.variantProperty("control2Y").value().toDouble()); + cubicSegment.setFourthControlPoint(cubicNode.variantProperty("x").value().toDouble(), + cubicNode.variantProperty("y").value().toDouble()); + } else { + qWarning() << "PathCubic has not all entries!"; + } + + return cubicSegment; +} + +static QRectF boundingRectForPath(const QList<ControlPoint> &controlPoints) +{ + double xMinimum = 0.; + double xMaximum = 0.; + double yMinimum = 0.; + double yMaximum = 0.; + + foreach (const ControlPoint & controlPoint, controlPoints) { + xMinimum = qMin(xMinimum, controlPoint.coordinate().x()); + xMaximum = qMax(xMaximum, controlPoint.coordinate().x()); + yMinimum = qMin(yMinimum, controlPoint.coordinate().y()); + yMaximum = qMax(yMaximum, controlPoint.coordinate().y()); + } + + return QRect(xMinimum, yMinimum, xMaximum - xMinimum, yMaximum - yMinimum); +} + +void PathItem::updateBoundingRect() +{ + QRectF controlBoundingRect = boundingRectForPath(controlPoints()).adjusted(-100, -100, 200, 100); + + if (m_selectionManipulator.isMultiSelecting()) + controlBoundingRect = controlBoundingRect.united(m_selectionManipulator.multiSelectionRectangle()); + + setBoundingRect(instanceBoundingRect().united(controlBoundingRect)); +} + +QRectF PathItem::instanceBoundingRect() const +{ + if (formEditorItem()) + return formEditorItem()->qmlItemNode().instanceBoundingRect(); + + return {}; +} + +void PathItem::readControlPoints() +{ + ModelNode pathNode = pathModelNode(formEditorItem()); + + m_cubicSegments.clear(); + + if (pathNode.hasNodeListProperty("pathElements")) { + ControlPoint firstControlPoint(startPoint(pathNode)); + firstControlPoint.setPathModelNode(pathNode); + firstControlPoint.setPointType(StartPoint); + + QMap<QString, QVariant> actualAttributes; + double percent = -1.0; + + foreach (const ModelNode &childNode, pathNode.nodeListProperty("pathElements").toModelNodeList()) { + + if (childNode.type() == "QtQuick.PathAttribute") { + actualAttributes.insert(childNode.variantProperty("name").value().toString(), childNode.variantProperty("value").value()); + } else if (childNode.type() == "QtQuick.PathPercent") { + percent = childNode.variantProperty("value").value().toDouble(); + } else { + CubicSegment newCubicSegement; + + if (childNode.type() == "QtQuick.PathLine") + newCubicSegement = createCubicSegmentForLine(childNode, firstControlPoint); + else if (childNode.type() == "QtQuick.PathQuad") + newCubicSegement = createCubicSegmentForQuad(childNode, firstControlPoint); + else if (childNode.type() == "QtQuick.PathCubic") + newCubicSegement = createCubicSegmentForCubic(childNode, firstControlPoint); + else + continue; + + newCubicSegement.setPercent(percent); + newCubicSegement.setAttributes(actualAttributes); + + firstControlPoint = newCubicSegement.fourthControlPoint(); + qDebug() << "Can be converted to quad" << newCubicSegement.canBeConvertedToQuad(); + qDebug() << "Can be converted to line" << newCubicSegement.canBeConvertedToLine(); + m_cubicSegments.append(newCubicSegement); + actualAttributes.clear(); + percent = -1.0; + } + } + + m_lastAttributes = actualAttributes; + m_lastPercent = percent; + + if (m_cubicSegments.constFirst().firstControlPoint().coordinate() == m_cubicSegments.constLast().fourthControlPoint().coordinate()) { + CubicSegment lastCubicSegment = m_cubicSegments.constLast(); + lastCubicSegment.setFourthControlPoint(m_cubicSegments.constFirst().firstControlPoint()); + lastCubicSegment.fourthControlPoint().setPathModelNode(pathNode); + lastCubicSegment.fourthControlPoint().setPointType(StartAndEndPoint); + } + } +} + +static CubicSegment getMinimumDistanceSegment(const QPointF &pickPoint, const QList<CubicSegment> &cubicSegments, double maximumDistance, double *t = nullptr) +{ + CubicSegment minimumDistanceSegment; + double actualMinimumDistance = maximumDistance; + + foreach (const CubicSegment &cubicSegment, cubicSegments) { + double tSegment = 0.; + double cubicSegmentMinimumDistance = cubicSegment.minimumDistance(pickPoint, tSegment); + if (cubicSegmentMinimumDistance < actualMinimumDistance) { + minimumDistanceSegment = cubicSegment; + actualMinimumDistance = cubicSegmentMinimumDistance; + if (t) + *t = tSegment; + } + } + + return minimumDistanceSegment; +} + +void PathItem::splitCubicSegment(CubicSegment &cubicSegment, double t) +{ + QPair<CubicSegment, CubicSegment> newCubicSegmentPair = cubicSegment.split(t); + int indexOfOldCubicSegment = m_cubicSegments.indexOf(cubicSegment); + + m_cubicSegments.removeAt(indexOfOldCubicSegment); + m_cubicSegments.insert(indexOfOldCubicSegment, newCubicSegmentPair.first); + m_cubicSegments.insert(indexOfOldCubicSegment + 1, newCubicSegmentPair.second); +} + +void PathItem::closePath() +{ + if (!m_cubicSegments.isEmpty()) { + const CubicSegment &firstCubicSegment = m_cubicSegments.constFirst(); + CubicSegment lastCubicSegment = m_cubicSegments.constLast(); + lastCubicSegment.setFourthControlPoint(firstCubicSegment.firstControlPoint()); + writePathAsCubicSegmentsOnly(); + } +} + +void PathItem::openPath() +{ + if (!m_cubicSegments.isEmpty()) { + const CubicSegment &firstCubicSegment = m_cubicSegments.constFirst(); + CubicSegment lastCubicSegment = m_cubicSegments.constLast(); + QPointF newEndPoint = firstCubicSegment.firstControlPoint().coordinate(); + newEndPoint.setX(newEndPoint.x() + 10.); + lastCubicSegment.setFourthControlPoint(ControlPoint(newEndPoint)); + writePathAsCubicSegmentsOnly(); + } +} + +QAction *PathItem::createClosedPathAction(QMenu *contextMenu) const +{ + auto closedPathAction = new QAction(contextMenu); + closedPathAction->setCheckable(true); + closedPathAction->setChecked(isClosedPath()); + closedPathAction->setText(tr("Closed Path")); + contextMenu->addAction(closedPathAction); + + if (m_cubicSegments.count() == 1) + closedPathAction->setDisabled(true); + + return closedPathAction; +} + +void PathItem::createGlobalContextMenu(const QPoint &menuPosition) +{ + QMenu contextMenu; + + QAction *closedPathAction = createClosedPathAction(&contextMenu); + + QAction *activatedAction = contextMenu.exec(menuPosition); + + if (activatedAction == closedPathAction) + makePathClosed(closedPathAction->isChecked()); +} + +void PathItem::createCubicSegmentContextMenu(CubicSegment &cubicSegment, const QPoint &menuPosition, double t) +{ + QMenu contextMenu; + + auto splitSegmentAction = new QAction(&contextMenu); + splitSegmentAction->setText(tr("Split Segment")); + contextMenu.addAction(splitSegmentAction); + + auto straightLinePointAction = new QAction(&contextMenu); + straightLinePointAction->setText(tr("Make Curve Segment Straight")); + contextMenu.addAction(straightLinePointAction); + + if (m_cubicSegments.count() == 1 && isClosedPath()) + straightLinePointAction->setDisabled(true); + + QAction *closedPathAction = createClosedPathAction(&contextMenu); + + QAction *activatedAction = contextMenu.exec(menuPosition); + + if (activatedAction == straightLinePointAction) { + cubicSegment.makeStraightLine(); + PathUpdateDisabler pathItemDisabler(this, PathUpdateDisabler::DontUpdatePath); + RewriterTransaction rewriterTransaction = + cubicSegment.modelNode().view()->beginRewriterTransaction(QByteArrayLiteral("PathItem::createCubicSegmentContextMenu")); + cubicSegment.updateModelNode(); + rewriterTransaction.commit(); + } else if (activatedAction == splitSegmentAction) { + splitCubicSegment(cubicSegment, t); + writePathAsCubicSegmentsOnly(); + } else if (activatedAction == closedPathAction) { + makePathClosed(closedPathAction->isChecked()); + } +} + + +void PathItem::createEditPointContextMenu(const ControlPoint &controlPoint, const QPoint &menuPosition) +{ + QMenu contextMenu; + auto removeEditPointAction = new QAction(&contextMenu); + removeEditPointAction->setText(tr("Remove Edit Point")); + contextMenu.addAction(removeEditPointAction); + + QAction *closedPathAction = createClosedPathAction(&contextMenu); + + if (m_cubicSegments.count() <= 1 || (m_cubicSegments.count() == 2 && isClosedPath())) + removeEditPointAction->setDisabled(true); + + QAction *activatedAction = contextMenu.exec(menuPosition); + + if (activatedAction == removeEditPointAction) + removeEditPoint(controlPoint); + else if (activatedAction == closedPathAction) + makePathClosed(closedPathAction->isChecked()); +} + +const QList<ControlPoint> PathItem::controlPoints() const +{ + QList<ControlPoint> controlPointList; + controlPointList.reserve((m_cubicSegments.count() * 4)); + + if (!m_cubicSegments.isEmpty()) + controlPointList.append(m_cubicSegments.constFirst().firstControlPoint()); + + foreach (const CubicSegment &cubicSegment, m_cubicSegments) { + controlPointList.append(cubicSegment.secondControlPoint()); + controlPointList.append(cubicSegment.thirdControlPoint()); + controlPointList.append(cubicSegment.fourthControlPoint()); + } + + if (isClosedPath()) + controlPointList.pop_back(); + + return controlPointList; +} + +bool hasLineOrQuadPathElements(const QList<ModelNode> &modelNodes) +{ + foreach (const ModelNode &modelNode, modelNodes) { + if (modelNode.type() == "QtQuick.PathLine" + || modelNode.type() == "QtQuick.PathQuad") + return true; + } + + return false; +} + +void PathItem::updatePath() +{ + if (m_dontUpdatePath) + return; + + if (hasPath(formEditorItem())) { + readControlPoints(); + + ModelNode pathNode = pathModelNode(formEditorItem()); + + if (hasLineOrQuadPathElements(pathNode.nodeListProperty("pathElements").toModelNodeList())) + writePathAsCubicSegmentsOnly(); + } + + updateBoundingRect(); + update(); +} + +QRectF PathItem::boundingRect() const +{ + return m_boundingRect; +} + +void PathItem::setBoundingRect(const QRectF &boundingRect) +{ + m_boundingRect = boundingRect; +} + +static bool controlPointIsNearMousePosition(const ControlPoint &controlPoint, const QPointF &mousePosition) +{ + QPointF distanceVector = controlPoint.coordinate() - mousePosition; + + if (distanceVector.manhattanLength() < 10) + return true; + + return false; +} + +static bool controlPointsAreNearMousePosition(const QList<ControlPoint> &controlPoints, const QPointF &mousePosition) +{ + foreach (const ControlPoint &controlPoint, controlPoints) { + if (controlPointIsNearMousePosition(controlPoint, mousePosition)) + return true; + } + + return false; +} + +static ControlPoint pickControlPoint(const QList<ControlPoint> &controlPoints, const QPointF &mousePosition) +{ + foreach (const ControlPoint &controlPoint, controlPoints) { + if (controlPointIsNearMousePosition(controlPoint, mousePosition)) + return controlPoint; + } + + return ControlPoint(); +} + +void PathItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + if (m_selectionManipulator.hasMultiSelection()) { + m_selectionManipulator.setStartPoint(event->pos()); + } else { + ControlPoint pickedControlPoint = pickControlPoint(controlPoints(), event->pos()); + + if (pickedControlPoint.isValid()) { + m_selectionManipulator.addSingleControlPointSmartly(pickedControlPoint); + m_selectionManipulator.startMoving(event->pos()); + } else { + m_selectionManipulator.startMultiSelection(event->pos()); + } + } + } +} + +bool hasMoveStartDistance(const QPointF &startPoint, const QPointF &updatePoint) +{ + return (startPoint - updatePoint).manhattanLength() > 10; +} + +void PathItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (controlPointsAreNearMousePosition(controlPoints(), event->pos())) + setCursor(Qt::SizeAllCursor); + else + setCursor(Qt::ArrowCursor); + + PathUpdateDisabler pathUpdateDisabler(this, PathUpdateDisabler::DontUpdatePath); + if (event->buttons().testFlag(Qt::LeftButton)) { + if (m_selectionManipulator.isMultiSelecting()) { + m_selectionManipulator.updateMultiSelection(event->pos()); + update(); + } else if (m_selectionManipulator.hasSingleSelection()) { + setCursor(Qt::SizeAllCursor); + m_selectionManipulator.updateMoving(event->pos(), event->modifiers()); + updatePathModelNodes(m_selectionManipulator.allSelectionSinglePoints()); + updateBoundingRect(); + update(); + } else if (m_selectionManipulator.hasMultiSelection()) { + setCursor(Qt::SizeAllCursor); + if (m_selectionManipulator.isMoving()) { + m_selectionManipulator.updateMoving(event->pos(), event->modifiers()); + updatePathModelNodes(m_selectionManipulator.allSelectionSinglePoints()); + updateBoundingRect(); + update(); + } else if (hasMoveStartDistance(m_selectionManipulator.startPoint(), event->pos())) { + m_selectionManipulator.startMoving(m_selectionManipulator.startPoint()); + m_selectionManipulator.updateMoving(event->pos(), event->modifiers()); + updatePathModelNodes(m_selectionManipulator.allSelectionSinglePoints()); + updateBoundingRect(); + update(); + } + } + } +} + +void PathItem::updatePathModelNodes(const QList<SelectionPoint> &changedPoints) +{ + PathUpdateDisabler pathUpdateDisabler(this, PathUpdateDisabler::DontUpdatePath); + + try { + RewriterTransaction rewriterTransaction = + formEditorItem()->qmlItemNode().view()->beginRewriterTransaction(QByteArrayLiteral("PathItem::createCubicSegmentContextMenu")); + + foreach (SelectionPoint changedPoint, changedPoints) + changedPoint.controlPoint.updateModelNode(); + + rewriterTransaction.commit(); + } catch (const Exception &e) { + e.showException(); + } +} + +void PathItem::disablePathUpdates() +{ + m_dontUpdatePath = true; +} + +void PathItem::enablePathUpdates() +{ + m_dontUpdatePath = false; +} + +bool PathItem::pathUpdatesDisabled() const +{ + return m_dontUpdatePath; +} + +void PathItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + if (m_selectionManipulator.isMultiSelecting()) { + m_selectionManipulator.updateMultiSelection(event->pos()); + m_selectionManipulator.endMultiSelection(); + } else if (m_selectionManipulator.hasSingleSelection()) { + m_selectionManipulator.updateMoving(event->pos(), event->modifiers()); + updatePathModelNodes(m_selectionManipulator.allSelectionSinglePoints()); + updateBoundingRect(); + m_selectionManipulator.clearSingleSelection(); + } else if (m_selectionManipulator.hasMultiSelection()) { + if (m_selectionManipulator.isMoving()) { + m_selectionManipulator.updateMoving(event->pos(), event->modifiers()); + m_selectionManipulator.endMoving(); + updatePathModelNodes(m_selectionManipulator.multiSelectedPoints()); + updateBoundingRect(); + } else { + m_selectionManipulator.clearMultiSelection(); + } + } + } else if (event->button() == Qt::RightButton) { + ControlPoint pickedControlPoint = pickControlPoint(controlPoints(), event->pos()); + if (pickedControlPoint.isEditPoint()) { + createEditPointContextMenu(pickedControlPoint, event->screenPos()); + } else { + double t = 0.0; + CubicSegment minimumDistanceSegment = getMinimumDistanceSegment(event->pos(), m_cubicSegments, 20., &t); + if (minimumDistanceSegment.isValid()) + createCubicSegmentContextMenu(minimumDistanceSegment, event->screenPos(), t); + else + createGlobalContextMenu(event->screenPos()); + } + } + + update(); + +} + +bool PathItem::isClosedPath() const +{ + if (m_cubicSegments.isEmpty()) + return false; + + ControlPoint firstControlPoint = m_cubicSegments.constFirst().firstControlPoint(); + ControlPoint lastControlPoint = m_cubicSegments.constLast().fourthControlPoint(); + + return firstControlPoint == lastControlPoint; +} + +void PathItem::makePathClosed(bool pathShoudlBeClosed) +{ + if (pathShoudlBeClosed && !isClosedPath()) + closePath(); + else if (!pathShoudlBeClosed && isClosedPath()) + openPath(); +} + +QList<CubicSegment> cubicSegmentsContainingControlPoint(const ControlPoint &controlPoint, const QList<CubicSegment> &allCubicSegments) +{ + QList<CubicSegment> cubicSegmentsHasControlPoint; + + foreach (const CubicSegment &cubicSegment, allCubicSegments) { + if (cubicSegment.controlPoints().contains(controlPoint)) + cubicSegmentsHasControlPoint.append(cubicSegment); + } + + return cubicSegmentsHasControlPoint; +} + +void PathItem::removeEditPoint(const ControlPoint &controlPoint) +{ + QList<CubicSegment> cubicSegments = cubicSegmentsContainingControlPoint(controlPoint, m_cubicSegments); + + if (cubicSegments.count() == 1) { + m_cubicSegments.removeOne(cubicSegments.constFirst()); + } else if (cubicSegments.count() == 2){ + CubicSegment mergedCubicSegment = CubicSegment::create(); + const CubicSegment &firstCubicSegment = cubicSegments.at(0); + const CubicSegment &secondCubicSegment = cubicSegments.at(1); + mergedCubicSegment.setFirstControlPoint(firstCubicSegment.firstControlPoint()); + mergedCubicSegment.setSecondControlPoint(firstCubicSegment.secondControlPoint()); + mergedCubicSegment.setThirdControlPoint(secondCubicSegment.thirdControlPoint()); + mergedCubicSegment.setFourthControlPoint(secondCubicSegment.fourthControlPoint()); + + int indexOfFirstCubicSegment = m_cubicSegments.indexOf(firstCubicSegment); + m_cubicSegments.removeAt(indexOfFirstCubicSegment); + m_cubicSegments.removeAt(indexOfFirstCubicSegment); + m_cubicSegments.insert(indexOfFirstCubicSegment, mergedCubicSegment); + } + + writePathAsCubicSegmentsOnly(); +} + +PathUpdateDisabler::PathUpdateDisabler(PathItem *pathItem, PathUpdate updatePath) + : m_pathItem(pathItem), + m_updatePath(updatePath) +{ + pathItem->disablePathUpdates(); +} + +PathUpdateDisabler::~PathUpdateDisabler() +{ + m_pathItem->enablePathUpdates(); + if (m_updatePath == UpdatePath) + m_pathItem->updatePath(); +} + +} diff --git a/src/plugins/qmldesigner/components/pathtool/pathitem.h b/src/plugins/qmldesigner/components/pathtool/pathitem.h new file mode 100644 index 0000000000..17981283a6 --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/pathitem.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QGraphicsObject> +#include <QWeakPointer> +#include <QMap> +#include <QVariant> +#include <qmldesignercorelib_global.h> + +#include "cubicsegment.h" +#include "pathselectionmanipulator.h" + +QT_BEGIN_NAMESPACE +class QTextEdit; +class QAction; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class FormEditorScene; +class FormEditorItem; +class PathItem; + +class PathUpdateDisabler +{ +public: + enum PathUpdate + { + UpdatePath, + DontUpdatePath + }; + + PathUpdateDisabler(PathItem *pathItem, PathUpdate updatePath = UpdatePath); + ~PathUpdateDisabler(); + +private: + PathItem *m_pathItem; + PathUpdate m_updatePath; +}; + +class PathItem : public QGraphicsObject +{ + Q_OBJECT + friend class PathUpdateDisabler; +public: + enum + { + Type = 0xEAAC + }; + PathItem(FormEditorScene* scene); + ~PathItem() override; + int type() const override; + + void setFormEditorItem(FormEditorItem *formEditorItem); + FormEditorItem *formEditorItem() const; + + QList<QGraphicsItem*> findAllChildItems() const; + + void updatePath(); + void writePathToProperty(); + void writePathAsCubicSegmentsOnly(); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + + QRectF boundingRect() const override; + void setBoundingRect(const QRectF &boundingRect); + + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + + bool isClosedPath() const; + const QList<ControlPoint> controlPoints() const; + +protected: + void updateBoundingRect(); + QRectF instanceBoundingRect() const; + void writeLinePath(const ModelNode &pathNode, const CubicSegment &cubicSegment); + void writeQuadPath(const ModelNode &pathNode, const CubicSegment &cubicSegment); + void writeCubicPath(const ModelNode &pathNode, const CubicSegment &cubicSegment); + void writePathAttributes(const ModelNode &pathNode, const QMap<QString, QVariant> &attributes); + void writePathPercent(const ModelNode &pathNode, double percent); + void readControlPoints(); + void splitCubicSegment(CubicSegment &cubicSegment, double t); + void closePath(); + void openPath(); + void createGlobalContextMenu(const QPoint &menuPosition); + void createCubicSegmentContextMenu(CubicSegment &cubicSegment, const QPoint &menuPosition, double t); + void createEditPointContextMenu(const ControlPoint &controlPoint, const QPoint &menuPosition); + void makePathClosed(bool pathShoudlBeClosed); + void removeEditPoint(const ControlPoint &controlPoint); + void updatePathModelNodes(const QList<SelectionPoint> &changedPoints); + void disablePathUpdates(); + void enablePathUpdates(); + bool pathUpdatesDisabled() const; + QAction *createClosedPathAction(QMenu *contextMenu) const; + +signals: + void textChanged(const QString &text); + +private: + PathSelectionManipulator m_selectionManipulator; + QList<CubicSegment> m_cubicSegments; + QPointF m_startPoint; + QRectF m_boundingRect; + QMap<QString, QVariant> m_lastAttributes; + double m_lastPercent; + FormEditorItem *m_formEditorItem; + bool m_dontUpdatePath; +}; + +inline int PathItem::type() const +{ + return Type; +} +} diff --git a/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.cpp b/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.cpp new file mode 100644 index 0000000000..6df6976233 --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.cpp @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#include "pathselectionmanipulator.h" + +#include "pathitem.h" + +#include <QtDebug> + +namespace QmlDesigner { + +PathSelectionManipulator::PathSelectionManipulator(PathItem *pathItem) + : m_pathItem(pathItem), + m_isMultiSelecting(false), + m_isMoving(false) +{ +} + +void PathSelectionManipulator::clear() +{ + clearSingleSelection(); + clearMultiSelection(); + m_isMultiSelecting = false; + m_isMoving = false; +} + +void PathSelectionManipulator::clearSingleSelection() +{ + m_singleSelectedPoints.clear(); + m_automaticallyAddedSinglePoints.clear(); +} + +void PathSelectionManipulator::clearMultiSelection() +{ + m_multiSelectedPoints.clear(); +} + +static SelectionPoint createSelectionPoint(const ControlPoint &controlPoint) +{ + SelectionPoint selectionPoint; + selectionPoint.controlPoint = controlPoint; + selectionPoint.startPosition = controlPoint.coordinate(); + + return selectionPoint; +} + +void PathSelectionManipulator::addMultiSelectionControlPoint(const ControlPoint &controlPoint) +{ + m_multiSelectedPoints.append(createSelectionPoint(controlPoint)); +} + +static ControlPoint getControlPoint(const QList<ControlPoint> &selectedPoints, const ControlPoint &controlPoint, int indexOffset, bool isClosedPath) +{ + int controlPointIndex = selectedPoints.indexOf(controlPoint); + if (controlPointIndex >= 0) { + int offsetIndex = controlPointIndex + indexOffset; + if (offsetIndex >= 0 && offsetIndex < selectedPoints.count()) + return selectedPoints.at(offsetIndex); + else if (isClosedPath) { + if (offsetIndex == -1) + return selectedPoints.constLast(); + else if (offsetIndex < selectedPoints.count()) + return selectedPoints.at(1); + } + } + + return ControlPoint(); +} + +QList<SelectionPoint> PathSelectionManipulator::singleSelectedPoints() +{ + return m_singleSelectedPoints; +} + +QList<SelectionPoint> PathSelectionManipulator::automaticallyAddedSinglePoints() +{ + return m_automaticallyAddedSinglePoints; +} + +QList<SelectionPoint> PathSelectionManipulator::allSelectionSinglePoints() +{ + + return m_singleSelectedPoints + m_automaticallyAddedSinglePoints; +} + +QList<SelectionPoint> PathSelectionManipulator::multiSelectedPoints() +{ + return m_multiSelectedPoints; +} + +QList<SelectionPoint> PathSelectionManipulator::allSelectionPoints() +{ + return m_singleSelectedPoints + m_multiSelectedPoints + m_automaticallyAddedSinglePoints; +} + +QList<ControlPoint> PathSelectionManipulator::allControlPoints() +{ + QList<ControlPoint> controlPoints; + + foreach (const SelectionPoint &selectionPoint, m_singleSelectedPoints) + controlPoints.append(selectionPoint.controlPoint); + + foreach (const SelectionPoint &selectionPoint, m_automaticallyAddedSinglePoints) + controlPoints.append(selectionPoint.controlPoint); + + foreach (const SelectionPoint &selectionPoint, m_multiSelectedPoints) + controlPoints.append(selectionPoint.controlPoint); + + return controlPoints; +} + +bool PathSelectionManipulator::hasSingleSelection() const +{ + return !m_singleSelectedPoints.isEmpty(); +} + +bool PathSelectionManipulator::hasMultiSelection() const +{ + return !m_multiSelectedPoints.isEmpty(); +} + +void PathSelectionManipulator::startMultiSelection(const QPointF &startPoint) +{ + m_startPoint = startPoint; + m_isMultiSelecting = true; +} + +void PathSelectionManipulator::updateMultiSelection(const QPointF &updatePoint) +{ + clearMultiSelection(); + + m_updatePoint = updatePoint; + + QRectF selectionRect(m_startPoint, updatePoint); + + foreach (const ControlPoint &controlPoint, m_pathItem->controlPoints()) { + if (selectionRect.contains(controlPoint.coordinate())) + addMultiSelectionControlPoint(controlPoint); + } +} + +void PathSelectionManipulator::endMultiSelection() +{ + m_isMultiSelecting = false; +} + +SelectionPoint::SelectionPoint() = default; + +SelectionPoint::SelectionPoint(const ControlPoint &controlPoint) + : controlPoint(controlPoint) +{ +} + +bool operator ==(const SelectionPoint &firstSelectionPoint, const SelectionPoint &secondSelectionPoint) +{ + return firstSelectionPoint.controlPoint == secondSelectionPoint.controlPoint; +} + +QPointF PathSelectionManipulator::multiSelectionStartPoint() const +{ + return m_startPoint; +} + +bool PathSelectionManipulator::isMultiSelecting() const +{ + return m_isMultiSelecting; +} + +QRectF PathSelectionManipulator::multiSelectionRectangle() const +{ + return QRectF(m_startPoint, m_updatePoint); +} + +void PathSelectionManipulator::setStartPoint(const QPointF &startPoint) +{ + m_startPoint = startPoint; +} + +QPointF PathSelectionManipulator::startPoint() const +{ + return m_startPoint; +} + +double snapFactor(Qt::KeyboardModifiers keyboardModifier) +{ + if (keyboardModifier.testFlag(Qt::ControlModifier)) + return 10.; + + return 1.; +} + +QPointF roundedVector(const QPointF &vector, double factor = 1.) +{ + QPointF roundedPosition; + + roundedPosition.setX(qRound(vector.x() / factor) * factor); + roundedPosition.setY(qRound(vector.y() / factor) * factor); + + return roundedPosition; +} + +QPointF manipulatedVector(const QPointF &vector, Qt::KeyboardModifiers keyboardModifier) +{ + QPointF manipulatedVector = roundedVector(vector, snapFactor(keyboardModifier)); + + if (keyboardModifier.testFlag(Qt::ShiftModifier)) + manipulatedVector.setX(0.); + + if (keyboardModifier.testFlag(Qt::AltModifier)) + manipulatedVector.setY(0.); + + return manipulatedVector; +} + +static void moveControlPoints(const QList<SelectionPoint> &movePoints, const QPointF &offsetVector) +{ + foreach (SelectionPoint movePoint, movePoints) + movePoint.controlPoint.setCoordinate(movePoint.startPosition + offsetVector); +} + +void PathSelectionManipulator::startMoving(const QPointF &startPoint) +{ + m_isMoving = true; + m_startPoint = startPoint; +} + +void PathSelectionManipulator::updateMoving(const QPointF &updatePoint, Qt::KeyboardModifiers keyboardModifier) +{ + m_updatePoint = updatePoint; + QPointF offsetVector = manipulatedVector(updatePoint - m_startPoint, keyboardModifier) ; + moveControlPoints(allSelectionPoints(), offsetVector); +} + +void PathSelectionManipulator::endMoving() +{ + updateMultiSelectedStartPoint(); + m_isMoving = false; +} + +bool PathSelectionManipulator::isMoving() const +{ + return m_isMoving; +} + +void PathSelectionManipulator::updateMultiSelectedStartPoint() +{ + QList<SelectionPoint> oldSelectionPoints = m_multiSelectedPoints; + + m_multiSelectedPoints.clear(); + + foreach (SelectionPoint selectionPoint, oldSelectionPoints) { + selectionPoint.startPosition = selectionPoint.controlPoint.coordinate(); + m_multiSelectedPoints.append(selectionPoint); + } +} + +void PathSelectionManipulator::addSingleControlPoint(const ControlPoint &controlPoint) +{ + m_singleSelectedPoints.append(createSelectionPoint(controlPoint)); +} + +void PathSelectionManipulator::addSingleControlPointSmartly(const ControlPoint &editPoint) +{ + m_singleSelectedPoints.append(createSelectionPoint(editPoint)); + + if (editPoint.isEditPoint()) { + ControlPoint previousControlPoint = getControlPoint(m_pathItem->controlPoints(), editPoint, -1, m_pathItem->isClosedPath()); + if (previousControlPoint.isValid()) + m_automaticallyAddedSinglePoints.append(createSelectionPoint(previousControlPoint)); + + ControlPoint nextControlPoint= getControlPoint(m_pathItem->controlPoints(), editPoint, 1, m_pathItem->isClosedPath()); + if (nextControlPoint.isValid()) + m_automaticallyAddedSinglePoints.append(createSelectionPoint(nextControlPoint)); + } +} + +} // namespace QMlDesigner diff --git a/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.h b/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.h new file mode 100644 index 0000000000..def0148367 --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QList> +#include <QPair> +#include <QPoint> + +#include "controlpoint.h" + +namespace QmlDesigner { + +class PathItem; + +struct SelectionPoint +{ + SelectionPoint(); + SelectionPoint(const ControlPoint &controlPoint); + ControlPoint controlPoint; + QPointF startPosition; +}; + +class PathSelectionManipulator +{ +public: + PathSelectionManipulator(PathItem *pathItem); + + void clear(); + void clearSingleSelection(); + void clearMultiSelection(); + + void addMultiSelectionControlPoint(const ControlPoint &controlPoint); + void addSingleControlPoint(const ControlPoint &controlPoint); + void addSingleControlPointSmartly(const ControlPoint &editPoint); + + QList<SelectionPoint> singleSelectedPoints(); + QList<SelectionPoint> automaticallyAddedSinglePoints(); + QList<SelectionPoint> allSelectionSinglePoints(); + QList<SelectionPoint> multiSelectedPoints(); + QList<SelectionPoint> allSelectionPoints(); + + QList<ControlPoint> allControlPoints(); + + bool hasSingleSelection() const; + bool hasMultiSelection() const; + + void startMultiSelection(const QPointF &startPoint); + void updateMultiSelection(const QPointF &updatePoint); + void endMultiSelection(); + QPointF multiSelectionStartPoint() const; + bool isMultiSelecting() const; + + QRectF multiSelectionRectangle() const; + + void setStartPoint(const QPointF &startPoint); + QPointF startPoint() const; + void startMoving(const QPointF &startPoint); + void updateMoving(const QPointF &updatePoint, Qt::KeyboardModifiers keyboardModifier); + void endMoving(); + bool isMoving() const; + + void updateMultiSelectedStartPoint(); + +private: + QList<SelectionPoint> m_singleSelectedPoints; + QList<SelectionPoint> m_automaticallyAddedSinglePoints; + QList<SelectionPoint> m_multiSelectedPoints; + QPointF m_startPoint; + QPointF m_updatePoint; + PathItem *m_pathItem; + bool m_isMultiSelecting; + bool m_isMoving; +}; + +bool operator ==(const SelectionPoint& firstSelectionPoint, const SelectionPoint& secondSelectionPoint); + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/pathtool/pathtool.cpp b/src/plugins/qmldesigner/components/pathtool/pathtool.cpp new file mode 100644 index 0000000000..a01ae050ac --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/pathtool.cpp @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#include "pathtool.h" + +#include <formeditorscene.h> +#include <formeditorview.h> +#include <formeditorwidget.h> +#include <itemutilfunctions.h> +#include <formeditoritem.h> + +#include "pathitem.h" + +#include <nodemetainfo.h> +#include <qmlitemnode.h> +#include <nodeproperty.h> +#include <nodelistproperty.h> +#include <qmldesignerplugin.h> + +#include <abstractaction.h> +#include <designeractionmanager.h> + +#include <QApplication> +#include <QGraphicsSceneMouseEvent> +#include <QAction> +#include <QDebug> +#include <QPair> + +namespace QmlDesigner { + +static bool isNonSupportedPathElement(const ModelNode &pathElement) +{ + if (pathElement.type() == "QtQuick.PathCubic") + return false; + + if (pathElement.type() == "QtQuick.PathAttribute") + return false; + + if (pathElement.type() == "QtQuick.PathPercent") + return false; + + if (pathElement.type() == "QtQuick.PathAttribute") + return false; + + if (pathElement.type() == "QtQuick.PathQuad") + return false; + + if (pathElement.type() == "QtQuick.PathLine") + return false; + + return true; +} + + +static int pathRankForModelNode(const ModelNode &modelNode) { + if (modelNode.metaInfo().hasProperty("path")) { + if (modelNode.hasNodeProperty("path")) { + ModelNode pathNode = modelNode.nodeProperty("path").modelNode(); + if (pathNode.metaInfo().isSubclassOf("QtQuick.Path") && pathNode.hasNodeListProperty("pathElements")) { + QList<ModelNode> pathElements = pathNode.nodeListProperty("pathElements").toModelNodeList(); + if (pathElements.isEmpty()) + return 0; + + foreach (const ModelNode &pathElement, pathElements) { + if (isNonSupportedPathElement(pathElement)) + return 0; + } + } + } + + return 20; + } + + return 0; +} + +class PathToolAction : public AbstractAction +{ +public: + PathToolAction() : AbstractAction(QCoreApplication::translate("PathToolAction","Edit Path")) {} + + QByteArray category() const override + { + return QByteArray(); + } + + QByteArray menuId() const override + { + return "PathTool"; + } + + int priority() const override + { + return CustomActionsPriority; + } + + Type type() const override + { + return ContextMenuAction; + } + +protected: + bool isVisible(const SelectionContext &selectionContext) const override + { + if (selectionContext.scenePosition().isNull()) + return false; + + if (selectionContext.singleNodeIsSelected()) + return pathRankForModelNode(selectionContext.currentSingleSelectedNode()) > 0; + + return false; + } + + bool isEnabled(const SelectionContext &selectionContext) const override + { + return isVisible(selectionContext); + } +}; + +PathTool::PathTool() + : m_pathToolView(this) +{ + auto textToolAction = new PathToolAction; + QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(textToolAction); + connect(textToolAction->action(), &QAction::triggered, [=]() { + if (m_pathToolView.model()) + m_pathToolView.model()->detachView(&m_pathToolView); + view()->changeCurrentToolTo(this); + }); + + +} + +PathTool::~PathTool() = default; + +void PathTool::clear() +{ + if (m_pathItem) + m_pathItem->deleteLater(); + + AbstractFormEditorTool::clear(); +} + +void PathTool::mousePressEvent(const QList<QGraphicsItem*> & /*itemList*/, + QGraphicsSceneMouseEvent * event) +{ + event->setPos(m_pathItem->mapFromScene(event->scenePos())); + event->setLastPos(m_pathItem->mapFromScene(event->lastScenePos())); + scene()->sendEvent(m_pathItem.data(), event); +} + +void PathTool::mouseMoveEvent(const QList<QGraphicsItem*> & /*itemList*/, + QGraphicsSceneMouseEvent *event) +{ + event->setPos(m_pathItem->mapFromScene(event->scenePos())); + event->setLastPos(m_pathItem->mapFromScene(event->lastScenePos())); + scene()->sendEvent(m_pathItem.data(), event); +} + +void PathTool::hoverMoveEvent(const QList<QGraphicsItem*> & /*itemList*/, + QGraphicsSceneMouseEvent * event) +{ + event->setPos(m_pathItem->mapFromScene(event->scenePos())); + event->setLastPos(m_pathItem->mapFromScene(event->lastScenePos())); + scene()->sendEvent(m_pathItem.data(), event); +} + +void PathTool::keyPressEvent(QKeyEvent *keyEvent) +{ + if (keyEvent->key() == Qt::Key_Escape) { + m_pathItem->writePathToProperty(); + keyEvent->accept(); + } +} + +void PathTool::keyReleaseEvent(QKeyEvent * keyEvent) +{ + if (keyEvent->key() == Qt::Key_Escape) { + keyEvent->accept(); + if (m_pathToolView.model()) + m_pathToolView.model()->detachView(&m_pathToolView); + view()->changeToSelectionTool(); + } +} + +void PathTool::dragLeaveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/) +{ + +} + +void PathTool::dragMoveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/) +{ + +} + +void PathTool::mouseReleaseEvent(const QList<QGraphicsItem*> & /*itemList*/, + QGraphicsSceneMouseEvent *event) +{ + event->setPos(m_pathItem->mapFromScene(event->scenePos())); + event->setLastPos(m_pathItem->mapFromScene(event->lastScenePos())); + scene()->sendEvent(m_pathItem.data(), event); +} + + +void PathTool::mouseDoubleClickEvent(const QList<QGraphicsItem*> & /*itemList*/, QGraphicsSceneMouseEvent *event) +{ + if (m_pathItem.data() && !m_pathItem->boundingRect().contains(m_pathItem->mapFromScene(event->scenePos()))) { + m_pathItem->writePathToProperty(); + view()->changeToSelectionTool(); + event->accept(); + } +} + +void PathTool::itemsAboutToRemoved(const QList<FormEditorItem*> &removedItemList) +{ + if (m_pathItem == nullptr) + return; + + if (removedItemList.contains(m_pathItem->formEditorItem())) + view()->changeToSelectionTool(); +} + +static bool hasPathProperty(FormEditorItem *formEditorItem) +{ + return formEditorItem->qmlItemNode().modelNode().metaInfo().hasProperty("path"); +} + +void PathTool::selectedItemsChanged(const QList<FormEditorItem*> &itemList) +{ + if (m_pathItem.data() && itemList.contains(m_pathItem->formEditorItem())) + m_pathItem->writePathToProperty(); + + delete m_pathItem.data(); + if (!itemList.isEmpty() && hasPathProperty(itemList.constFirst())) { + FormEditorItem *formEditorItem = itemList.constFirst(); + m_pathItem = new PathItem(scene()); + m_pathItem->setParentItem(scene()->manipulatorLayerItem()); + m_pathItem->setFormEditorItem(formEditorItem); + formEditorItem->qmlItemNode().modelNode().model()->attachView(&m_pathToolView); + } else { + if (m_pathToolView.model()) + m_pathToolView.model()->detachView(&m_pathToolView); + view()->changeToSelectionTool(); + } +} + +void PathTool::instancesCompleted(const QList<FormEditorItem*> & /*itemList*/) +{ +} + +void PathTool::instancesParentChanged(const QList<FormEditorItem *> & /*itemList*/) +{ +} + +void PathTool::instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList) +{ + using ModelNodePropertyNamePair = QPair<ModelNode, PropertyName>; + foreach (const ModelNodePropertyNamePair &propertyPair, propertyList) { + if (propertyPair.first == m_pathItem->formEditorItem()->qmlItemNode().modelNode() + && propertyPair.second == "path") + m_pathItem->updatePath(); + } +} + +void PathTool::formEditorItemsChanged(const QList<FormEditorItem*> & /*itemList*/) +{ +} + +int PathTool::wantHandleItem(const ModelNode &modelNode) const +{ + return pathRankForModelNode(modelNode); +} + +QString PathTool::name() const +{ + return QCoreApplication::translate("PathTool", "Path Tool"); +} + +ModelNode PathTool::editingPathViewModelNode() const +{ + if (m_pathItem) + return m_pathItem->formEditorItem()->qmlItemNode().modelNode(); + + return ModelNode(); +} + +void PathTool::pathChanged() +{ + if (m_pathItem) + m_pathItem->updatePath(); +} + +} diff --git a/src/plugins/qmldesigner/components/pathtool/pathtool.h b/src/plugins/qmldesigner/components/pathtool/pathtool.h new file mode 100644 index 0000000000..a2a65db27d --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/pathtool.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "abstractcustomtool.h" +#include "selectionindicator.h" + +#include <QHash> +#include <QPointer> +#include <QColorDialog> + +#include "pathtoolview.h" + +namespace QmlDesigner { + +class PathItem; + +class PathTool : public QObject, public AbstractCustomTool +{ + Q_OBJECT +public: + PathTool(); + ~PathTool() override; + + void mousePressEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) override; + void hoverMoveEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *keyEvent) override; + + void dragLeaveEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneDragDropEvent * event) override; + void dragMoveEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneDragDropEvent * event) override; + + void itemsAboutToRemoved(const QList<FormEditorItem*> &itemList) override; + + void selectedItemsChanged(const QList<FormEditorItem*> &itemList) override; + + void instancesCompleted(const QList<FormEditorItem*> &itemList) override; + void instancesParentChanged(const QList<FormEditorItem *> &itemList) override; + void instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList) override; + + void clear() override; + + void formEditorItemsChanged(const QList<FormEditorItem*> &itemList) override; + + int wantHandleItem(const ModelNode &modelNode) const override; + + QString name() const override; + + ModelNode editingPathViewModelNode() const; + + void pathChanged(); + +private: + PathToolView m_pathToolView; + QPointer<PathItem> m_pathItem; +}; + +} diff --git a/src/plugins/qmldesigner/components/pathtool/pathtool.pri b/src/plugins/qmldesigner/components/pathtool/pathtool.pri new file mode 100644 index 0000000000..eba35315fe --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/pathtool.pri @@ -0,0 +1,13 @@ +HEADERS += $$PWD/pathtool.h +HEADERS += $$PWD/pathselectionmanipulator.h +HEADERS += $$PWD/pathtoolview.h +HEADERS += $$PWD/controlpoint.h +HEADERS += $$PWD/cubicsegment.h +HEADERS += $$PWD/pathitem.h + +SOURCES += $$PWD/pathtool.cpp +SOURCES += $$PWD/pathselectionmanipulator.cpp +SOURCES += $$PWD/pathtoolview.cpp +SOURCES += $$PWD/controlpoint.cpp +SOURCES += $$PWD/cubicsegment.cpp +SOURCES += $$PWD/pathitem.cpp diff --git a/src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp b/src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp new file mode 100644 index 0000000000..ea1e28832a --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#include "pathtoolview.h" + +#include <nodeproperty.h> +#include <variantproperty.h> +#include <modelnode.h> +#include <metainfo.h> + +#include "pathtool.h" + +#include <QtDebug> + +namespace QmlDesigner { + +PathToolView::PathToolView(PathTool *pathTool) + : m_pathTool(pathTool) +{ +} + +static bool isInEditedPath(const NodeAbstractProperty &propertyParent, const ModelNode &editingPathViewModelNode) +{ + if (editingPathViewModelNode.isValid()) { + if (editingPathViewModelNode.hasNodeProperty("path")) { + ModelNode pathModelNode = editingPathViewModelNode.nodeProperty("path").modelNode(); + if (pathModelNode.metaInfo().isSubclassOf("QtQuick.Path")) { + if (propertyParent.name() == "pathElements" && propertyParent.parentModelNode() == pathModelNode) + return true; + } + } + } + + return false; +} + +void PathToolView::nodeReparented(const ModelNode & /*node*/, + const NodeAbstractProperty & newPropertyParent, + const NodeAbstractProperty & /*oldPropertyParent*/, + AbstractView::PropertyChangeFlags /*propertyChange*/) +{ + if (isInEditedPath(newPropertyParent, m_pathTool->editingPathViewModelNode())) + m_pathTool->pathChanged(); +} + +bool variantPropertyInEditedPath(const VariantProperty &variantProperty, const ModelNode &editingPathViewModelNode) +{ + ModelNode pathElementModelNode = variantProperty.parentModelNode(); + if (pathElementModelNode.hasParentProperty()) { + if (isInEditedPath(pathElementModelNode.parentProperty(), editingPathViewModelNode)) + return true; + } + + return false; +} + +bool changesEditedPath(const QList<VariantProperty> &propertyList, const ModelNode &editingPathViewModelNode) +{ + foreach (const VariantProperty variantProperty, propertyList) { + if (variantPropertyInEditedPath(variantProperty, editingPathViewModelNode)) + return true; + } + + return false; +} + +void PathToolView::variantPropertiesChanged(const QList<VariantProperty> &propertyList, AbstractView::PropertyChangeFlags /*propertyChange*/) +{ + if (changesEditedPath(propertyList, m_pathTool->editingPathViewModelNode())) + m_pathTool->pathChanged(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/pathtool/pathtoolview.h b/src/plugins/qmldesigner/components/pathtool/pathtoolview.h new file mode 100644 index 0000000000..46b688923e --- /dev/null +++ b/src/plugins/qmldesigner/components/pathtool/pathtoolview.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <abstractview.h> + +namespace QmlDesigner { + +class PathTool; + +class PathToolView : public AbstractView +{ + Q_OBJECT +public: + PathToolView(PathTool *pathTool); + + void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override; + void variantPropertiesChanged(const QList<VariantProperty>& propertyList, PropertyChangeFlags propertyChange) override; + +private: + PathTool *m_pathTool; +}; + +} // namespace QmlDesigner |