aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/qmldesigner/components/pathtool
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/qmldesigner/components/pathtool')
-rw-r--r--src/plugins/qmldesigner/components/pathtool/controlpoint.cpp174
-rw-r--r--src/plugins/qmldesigner/components/pathtool/controlpoint.h93
-rw-r--r--src/plugins/qmldesigner/components/pathtool/cubicsegment.cpp367
-rw-r--r--src/plugins/qmldesigner/components/pathtool/cubicsegment.h126
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathitem.cpp971
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathitem.h140
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.cpp299
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathselectionmanipulator.h100
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathtool.cpp315
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathtool.h91
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathtool.pri13
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathtoolview.cpp95
-rw-r--r--src/plugins/qmldesigner/components/pathtool/pathtoolview.h47
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