1 /************************************************************************
2  **
3  **  @file   vtoolsplinepath.cpp
4  **  @author Roman Telezhynskyi <dismine(at)gmail.com>
5  **  @date   November 15, 2013
6  **
7  **  @brief
8  **  @copyright
9  **  This source code is part of the Valentina project, a pattern making
10  **  program, whose allow create and modeling patterns of clothing.
11  **  Copyright (C) 2013-2015 Valentina project
12  **  <https://gitlab.com/smart-pattern/valentina> All Rights Reserved.
13  **
14  **  Valentina is free software: you can redistribute it and/or modify
15  **  it under the terms of the GNU General Public License as published by
16  **  the Free Software Foundation, either version 3 of the License, or
17  **  (at your option) any later version.
18  **
19  **  Valentina is distributed in the hope that it will be useful,
20  **  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  **  GNU General Public License for more details.
23  **
24  **  You should have received a copy of the GNU General Public License
25  **  along with Valentina.  If not, see <http://www.gnu.org/licenses/>.
26  **
27  *************************************************************************/
28 
29 #include "vtoolsplinepath.h"
30 
31 #include <QDomElement>
32 #include <QEvent>
33 #include <QFlags>
34 #include <QGraphicsScene>
35 #include <QGraphicsSceneHoverEvent>
36 #include <QGraphicsSceneMouseEvent>
37 #include <QGraphicsView>
38 #include <QList>
39 #include <QPen>
40 #include <QPoint>
41 #include <QRectF>
42 #include <QSharedPointer>
43 #include <QStaticStringData>
44 #include <QStringData>
45 #include <QStringDataPtr>
46 #include <QUndoStack>
47 #include <Qt>
48 #include <new>
49 
50 #include "../../../dialogs/tools/dialogtool.h"
51 #include "../../../dialogs/tools/dialogsplinepath.h"
52 #include "../../../undocommands/movesplinepath.h"
53 #include "../../../undocommands/vundocommand.h"
54 #include "../../../visualization/visualization.h"
55 #include "../../../visualization/path/vistoolsplinepath.h"
56 #include "../ifc/exception/vexception.h"
57 #include "../ifc/xml/vdomdocument.h"
58 #include "../ifc/ifcdef.h"
59 #include "../qmuparser/qmutokenparser.h"
60 #include "../vgeometry/vabstractcubicbezierpath.h"
61 #include "../vgeometry/vabstractcurve.h"
62 #include "../vgeometry/vgobject.h"
63 #include "../vgeometry/vpointf.h"
64 #include "../vgeometry/vspline.h"
65 #include "../vgeometry/vsplinepoint.h"
66 #include "../vmisc/vabstractapplication.h"
67 #include "../vmisc/vmath.h"
68 #include "../vpatterndb/vcontainer.h"
69 #include "../vwidgets/../vgeometry/vsplinepath.h"
70 #include "../vwidgets/vcontrolpointspline.h"
71 #include "../vwidgets/vmaingraphicsscene.h"
72 #include "../../vabstracttool.h"
73 #include "../vdrawtool.h"
74 #include "vabstractspline.h"
75 
76 const QString VToolSplinePath::ToolType = QStringLiteral("pathInteractive");
77 const QString VToolSplinePath::OldToolType = QStringLiteral("path");
78 
79 //---------------------------------------------------------------------------------------------------------------------
80 /**
81  * @brief VToolSplinePath constructor.
82  * @param initData init data.
83  * @param parent parent object.
84  */
VToolSplinePath(const VToolSplinePathInitData & initData,QGraphicsItem * parent)85 VToolSplinePath::VToolSplinePath(const VToolSplinePathInitData &initData, QGraphicsItem *parent)
86     : VAbstractSpline(initData.doc, initData.data, initData.id, initData.notes, parent),
87       oldPosition(),
88       splIndex(-1),
89       moved(false),
90       oldMoveSplinePath(),
91       newMoveSplinePath()
92 {
93     sceneType = SceneObject::SplinePath;
94 
95     this->setFlag(QGraphicsItem::ItemIsMovable, true);
96     this->setFlag(QGraphicsItem::ItemIsFocusable, true);// For keyboard input focus
97 
98     auto InitControlPoint = [this](VControlPointSpline* cPoint)
99     {
100         connect(cPoint, &VControlPointSpline::ControlPointChangePosition, this,
101                 &VToolSplinePath::ControlPointChangePosition);
102         connect(this, &VToolSplinePath::setEnabledPoint, cPoint, &VControlPointSpline::setEnabledPoint);
103         connect(cPoint, &VControlPointSpline::ShowContextMenu, this, &VToolSplinePath::contextMenuEvent);
104         connect(cPoint, &VControlPointSpline::Released, this, &VToolSplinePath::CurveReleased);
105         connect(cPoint, &VControlPointSpline::Selected, this, &VToolSplinePath::CurveSelected);
106         controlPoints.append(cPoint);
107     };
108 
109     const QSharedPointer<VSplinePath> splPath = initData.data->GeometricObject<VSplinePath>(initData.id);
110     for (qint32 i = 1; i<=splPath->CountSubSpl(); ++i)
111     {
112         const VSpline spl = splPath->GetSpline(i);
113 
114         bool freeAngle1 = true;
115 
116         if (i > 1)
117         {
118             const VSpline prevSpl = splPath->GetSpline(i-1);
119             freeAngle1 = qmu::QmuTokenParser::IsSingle(spl.GetStartAngleFormula()) &&
120                          qmu::QmuTokenParser::IsSingle(prevSpl.GetEndAngleFormula());
121         }
122         else
123         {
124             freeAngle1 = qmu::QmuTokenParser::IsSingle(spl.GetStartAngleFormula());
125         }
126 
127         const bool freeLength1 = qmu::QmuTokenParser::IsSingle(spl.GetC1LengthFormula());
128 
129         auto *controlPoint = new VControlPointSpline(i, SplinePointPosition::FirstPoint,
130                                                      static_cast<QPointF>(spl.GetP2()), freeAngle1, freeLength1, this);
131         InitControlPoint(controlPoint);
132 
133         bool freeAngle2 = true;
134 
135         if (i < splPath->CountSubSpl())
136         {
137             const VSpline nextSpl = splPath->GetSpline(i+1);
138             freeAngle2 = qmu::QmuTokenParser::IsSingle(nextSpl.GetStartAngleFormula()) &&
139                          qmu::QmuTokenParser::IsSingle(spl.GetEndAngleFormula());
140         }
141         else
142         {
143             freeAngle2 = qmu::QmuTokenParser::IsSingle(spl.GetEndAngleFormula());
144         }
145 
146         const bool freeLength2 = qmu::QmuTokenParser::IsSingle(spl.GetC2LengthFormula());
147 
148         controlPoint = new VControlPointSpline(i, SplinePointPosition::LastPoint, static_cast<QPointF>(spl.GetP3()),
149                                                freeAngle2, freeLength2, this);
150         InitControlPoint(controlPoint);
151     }
152 
153     VToolSplinePath::RefreshCtrlPoints();
154 
155     ShowHandles(detailsMode);
156 
157     ToolCreation(initData.typeCreation);
158 }
159 
160 //---------------------------------------------------------------------------------------------------------------------
161 /**
162  * @brief setDialog set dialog when user want change tool option.
163  */
setDialog()164 void VToolSplinePath::setDialog()
165 {
166     SCASSERT(not m_dialog.isNull())
167     const QPointer<DialogSplinePath> dialogTool = qobject_cast<DialogSplinePath *>(m_dialog);
168     SCASSERT(not dialogTool.isNull())
169     const QSharedPointer<VSplinePath> splPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
170     dialogTool->SetPath(*splPath);
171     dialogTool->SetNotes(m_notes);
172 }
173 
174 //---------------------------------------------------------------------------------------------------------------------
175 /**
176  * @brief Create help create tool from GUI.
177  * @param dialog dialog.
178  * @param scene pointer to scene.
179  * @param doc dom document container.
180  * @param data container with variables.
181  */
Create(const QPointer<DialogTool> & dialog,VMainGraphicsScene * scene,VAbstractPattern * doc,VContainer * data)182 VToolSplinePath* VToolSplinePath::Create(const QPointer<DialogTool> &dialog, VMainGraphicsScene *scene,
183                                          VAbstractPattern *doc, VContainer *data)
184 {
185     SCASSERT(not dialog.isNull())
186     const QPointer<DialogSplinePath> dialogTool = qobject_cast<DialogSplinePath *>(dialog);
187     SCASSERT(not dialogTool.isNull())
188 
189     VToolSplinePathInitData initData;
190     initData.scene = scene;
191     initData.doc = doc;
192     initData.data = data;
193     initData.parse = Document::FullParse;
194     initData.typeCreation = Source::FromGui;
195     initData.notes = dialogTool->GetNotes();
196 
197     VSplinePath *path = new VSplinePath(dialogTool->GetPath());
198     for (qint32 i = 0; i < path->CountPoints(); ++i)
199     {
200         doc->IncrementReferens((*path)[i].P().getIdTool());
201     }
202 
203     VToolSplinePath* spl = Create(initData, path);
204     if (spl != nullptr)
205     {
206         spl->m_dialog = dialog;
207     }
208     return spl;
209 }
210 
211 //---------------------------------------------------------------------------------------------------------------------
212 /**
213  * @brief Create help create tool.
214  * @param initData init data.
215  * @param path spline path.
216  */
Create(VToolSplinePathInitData & initData,VSplinePath * path)217 VToolSplinePath* VToolSplinePath::Create(VToolSplinePathInitData &initData,  VSplinePath *path)
218 {
219     if (initData.typeCreation == Source::FromGui)
220     {
221         initData.id = initData.data->AddGObject(path);
222         initData.data->AddCurveWithSegments(initData.data->GeometricObject<VAbstractCubicBezierPath>(initData.id),
223                                             initData.id);
224     }
225     else
226     {
227         initData.data->UpdateGObject(initData.id, path);
228         initData.data->AddCurveWithSegments(initData.data->GeometricObject<VAbstractCubicBezierPath>(initData.id),
229                                             initData.id);
230         if (initData.parse != Document::FullParse)
231         {
232             initData.doc->UpdateToolData(initData.id, initData.data);
233         }
234     }
235 
236     if (initData.parse == Document::FullParse)
237     {
238         VAbstractTool::AddRecord(initData.id, Tool::SplinePath, initData.doc);
239         VToolSplinePath *spl = new VToolSplinePath(initData);
240         initData.scene->addItem(spl);
241         InitSplinePathToolConnections(initData.scene, spl);
242         VAbstractPattern::AddTool(initData.id, spl);
243         return spl;
244     }
245     return nullptr;
246 }
247 
248 //---------------------------------------------------------------------------------------------------------------------
Create(VToolSplinePathInitData & initData)249 VToolSplinePath *VToolSplinePath::Create(VToolSplinePathInitData &initData)
250 {
251     auto* path = new VSplinePath();
252 
253     if (initData.duplicate > 0)
254     {
255         path->SetDuplicate(initData.duplicate);
256     }
257 
258     for (int i = 0; i < initData.points.size(); ++i)
259     {
260         const qreal calcAngle1 = CheckFormula(initData.id, initData.a1[i], initData.data);
261         const qreal calcAngle2 = CheckFormula(initData.id, initData.a2[i], initData.data);
262 
263         const qreal calcLength1 =
264                 VAbstractValApplication::VApp()->toPixel(CheckFormula(initData.id, initData.l1[i], initData.data));
265         const qreal calcLength2 =
266                 VAbstractValApplication::VApp()->toPixel(CheckFormula(initData.id, initData.l2[i], initData.data));
267 
268         const auto p = *initData.data->GeometricObject<VPointF>(initData.points.at(i));
269 
270         path->append(VSplinePoint(p, calcAngle1, initData.a1.at(i), calcAngle2, initData.a2.at(i), calcLength1,
271                                   initData.l1.at(i), calcLength2, initData.l2.at(i)));
272     }
273 
274     path->SetColor(initData.color);
275     path->SetPenStyle(initData.penStyle);
276     path->SetApproximationScale(initData.approximationScale);
277     path->SetAliasSuffix(initData.aliasSuffix);
278 
279     return VToolSplinePath::Create(initData, path);
280 }
281 
282 //---------------------------------------------------------------------------------------------------------------------
283 /**
284  * @brief ControlPointChangePosition handle change position control point.
285  * @param indexSpline position spline in spline list.
286  * @param position position point in spline.
287  * @param pos new position.
288  */
ControlPointChangePosition(const qint32 & indexSpline,const SplinePointPosition & position,const QPointF & pos)289 void VToolSplinePath::ControlPointChangePosition(const qint32 &indexSpline, const SplinePointPosition &position,
290                                                  const QPointF &pos)
291 {
292     const QSharedPointer<VSplinePath> oldSplPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
293 
294     if (VAbstractApplication::VApp()->Settings()->IsFreeCurveMode() && not moved)
295     {
296         oldMoveSplinePath = QSharedPointer<VSplinePath>::create(*oldSplPath);
297         moved = true;
298     }
299 
300     QSharedPointer<VSplinePath> newSplPath = QSharedPointer<VSplinePath>::create(*oldSplPath);
301     const VSpline spl = CorrectedSpline(newSplPath->GetSpline(indexSpline), position, pos);
302 
303     UpdateControlPoints(spl, newSplPath, indexSpline);
304 
305     if (not VAbstractApplication::VApp()->Settings()->IsFreeCurveMode())
306     {
307         UndoCommandMove(*oldSplPath, *newSplPath);
308     }
309     else
310     {
311         newMoveSplinePath = newSplPath;
312         VAbstractTool::data.UpdateGObject(m_id, newMoveSplinePath);
313         RefreshGeometry();
314 
315         if (QGraphicsScene *sc = scene())
316         {
317             VMainGraphicsView::NewSceneRect(sc, VAbstractValApplication::VApp()->getSceneView(), this);
318         }
319     }
320 }
321 
322 //---------------------------------------------------------------------------------------------------------------------
EnableToolMove(bool move)323 void VToolSplinePath::EnableToolMove(bool move)
324 {
325     this->setFlag(QGraphicsItem::ItemIsMovable, move);
326 
327     for (auto point : qAsConst(controlPoints))
328     {
329         point->setFlag(QGraphicsItem::ItemIsMovable, move);
330     }
331 }
332 
333 //---------------------------------------------------------------------------------------------------------------------
ShowContextMenu(QGraphicsSceneContextMenuEvent * event,quint32 id)334 void VToolSplinePath::ShowContextMenu(QGraphicsSceneContextMenuEvent *event, quint32 id)
335 {
336     Q_UNUSED(id)
337     try
338     {
339         ContextMenu<DialogSplinePath>(event);
340     }
341     catch(const VExceptionToolWasDeleted &e)
342     {
343         Q_UNUSED(e)
344         return;//Leave this method immediately!!!
345     }
346 }
347 
348 //---------------------------------------------------------------------------------------------------------------------
349 /**
350  * @brief UpdateControlPoints update position control points in file.
351  * @param spl spline that was changed.
352  * @param splPath spline path.
353  * @param indexSpline index spline in spline path.
354  */
UpdateControlPoints(const VSpline & spl,QSharedPointer<VSplinePath> & splPath,qint32 indexSpline) const355 void VToolSplinePath::UpdateControlPoints(const VSpline &spl, QSharedPointer<VSplinePath> &splPath,
356                                           qint32 indexSpline) const
357 {
358     VSplinePoint p = splPath->GetSplinePoint(indexSpline, SplinePointPosition::FirstPoint);
359 
360     if (indexSpline >1)
361     {
362         VSpline prevSpline = splPath->GetSpline(indexSpline-1);
363         if (qmu::QmuTokenParser::IsSingle(prevSpline.GetEndAngleFormula()))
364         {
365             p.SetAngle2(spl.GetStartAngle(), spl.GetStartAngleFormula());
366         }
367     }
368     else
369     {
370         p.SetAngle2(spl.GetStartAngle(), spl.GetStartAngleFormula());
371     }
372 
373 
374     p.SetLength2(spl.GetC1Length(), spl.GetC1LengthFormula());
375     splPath->UpdatePoint(indexSpline, SplinePointPosition::FirstPoint, p);
376 
377     p = splPath->GetSplinePoint(indexSpline, SplinePointPosition::LastPoint);
378 
379     if (indexSpline < splPath->CountSubSpl())
380     {
381         VSpline nextSpline = splPath->GetSpline(indexSpline+1);
382         if (qmu::QmuTokenParser::IsSingle(nextSpline.GetStartAngleFormula()))
383         {
384             p.SetAngle1(spl.GetEndAngle(), spl.GetEndAngleFormula());
385         }
386     }
387     else
388     {
389         p.SetAngle1(spl.GetEndAngle(), spl.GetEndAngleFormula());
390     }
391 
392     p.SetLength1(spl.GetC2Length(), spl.GetC2LengthFormula());
393     splPath->UpdatePoint(indexSpline, SplinePointPosition::LastPoint, p);
394 }
395 
396 //---------------------------------------------------------------------------------------------------------------------
SetSplinePathAttributes(QDomElement & domElement,const VSplinePath & path)397 void VToolSplinePath::SetSplinePathAttributes(QDomElement &domElement, const VSplinePath &path)
398 {
399     doc->SetAttribute(domElement, AttrType, ToolType);
400     doc->SetAttributeOrRemoveIf(domElement, AttrDuplicate, path.GetDuplicate(), path.GetDuplicate() <= 0);
401 
402     if (domElement.hasAttribute(AttrKCurve))
403     {
404         domElement.removeAttribute(AttrKCurve);
405     }
406 
407     doc->SetAttribute(domElement, AttrColor, path.GetColor());
408     doc->SetAttribute(domElement, AttrPenStyle, path.GetPenStyle());
409     doc->SetAttribute(domElement, AttrAScale, path.GetApproximationScale());
410     doc->SetAttributeOrRemoveIf(domElement, AttrAlias, path.GetAliasSuffix(), path.GetAliasSuffix().isEmpty());
411 
412     UpdatePathPoints(doc, domElement, path);
413 }
414 
415 //---------------------------------------------------------------------------------------------------------------------
UndoCommandMove(const VSplinePath & oldPath,const VSplinePath & newPath)416 void VToolSplinePath::UndoCommandMove(const VSplinePath &oldPath, const VSplinePath &newPath)
417 {
418     MoveSplinePath *moveSplPath = new MoveSplinePath(doc, oldPath, newPath, m_id);
419     connect(moveSplPath, &VUndoCommand::NeedLiteParsing, doc, &VAbstractPattern::LiteParseTree);
420     VAbstractApplication::VApp()->getUndoStack()->push(moveSplPath);
421 }
422 
423 //---------------------------------------------------------------------------------------------------------------------
424 /**
425  * @brief UpdatePathPoints update spline path in pattern file.
426  * @param doc dom document container.
427  * @param element tag in file.
428  * @param path spline path.
429  */
UpdatePathPoints(VAbstractPattern * doc,QDomElement & element,const VSplinePath & path)430 void VToolSplinePath::UpdatePathPoints(VAbstractPattern *doc, QDomElement &element, const VSplinePath &path)
431 {
432     VDomDocument::RemoveAllChildren(element);
433     for (qint32 i = 0; i < path.CountPoints(); ++i)
434     {
435         AddPathPoint(doc, element, path.at(i));
436     }
437 }
438 
439 //---------------------------------------------------------------------------------------------------------------------
getSplinePath() const440 VSplinePath VToolSplinePath::getSplinePath() const
441 {
442     QSharedPointer<VSplinePath> splPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
443     return *splPath.data();
444 }
445 
446 //---------------------------------------------------------------------------------------------------------------------
setSplinePath(const VSplinePath & splPath)447 void VToolSplinePath::setSplinePath(const VSplinePath &splPath)
448 {
449     QSharedPointer<VGObject> obj = VAbstractTool::data.GetGObject(m_id);
450     QSharedPointer<VSplinePath> splinePath = qSharedPointerDynamicCast<VSplinePath>(obj);
451     *splinePath.data() = splPath;
452     SaveOption(obj);
453 }
454 
455 //---------------------------------------------------------------------------------------------------------------------
ShowVisualization(bool show)456 void VToolSplinePath::ShowVisualization(bool show)
457 {
458     ShowToolVisualization<VisToolSplinePath>(show);
459 }
460 
461 //---------------------------------------------------------------------------------------------------------------------
462 /**
463  * @brief AddPathPoint write path point to pattern file.
464  * @param domElement dom element.
465  * @param splPoint spline path point.
466  */
AddPathPoint(VAbstractPattern * doc,QDomElement & domElement,const VSplinePoint & splPoint)467 void VToolSplinePath::AddPathPoint(VAbstractPattern *doc, QDomElement &domElement, const VSplinePoint &splPoint)
468 {
469     SCASSERT(doc != nullptr)
470     QDomElement pathPoint = doc->createElement(AttrPathPoint);
471 
472     doc->SetAttribute(pathPoint, AttrPSpline, splPoint.P().id());
473     doc->SetAttribute(pathPoint, AttrLength1, splPoint.Length1Formula());
474     doc->SetAttribute(pathPoint, AttrLength2, splPoint.Length2Formula());
475     doc->SetAttribute(pathPoint, AttrAngle1, splPoint.Angle1Formula());
476     doc->SetAttribute(pathPoint, AttrAngle2, splPoint.Angle2Formula());
477 
478     if (domElement.hasAttribute(AttrKAsm1))
479     {
480         domElement.removeAttribute(AttrKAsm1);
481     }
482 
483     if (domElement.hasAttribute(AttrKAsm2))
484     {
485         domElement.removeAttribute(AttrKAsm2);
486     }
487 
488     if (domElement.hasAttribute(AttrAngle))
489     {
490         domElement.removeAttribute(AttrAngle);
491     }
492 
493     domElement.appendChild(pathPoint);
494 }
495 
496 //---------------------------------------------------------------------------------------------------------------------
497 /**
498  * @brief RemoveReferens decrement value of reference.
499  */
RemoveReferens()500 void VToolSplinePath::RemoveReferens()
501 {
502     const QSharedPointer<VSplinePath> splPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
503     for (qint32 i = 0; i < splPath->CountPoints(); ++i)
504     {
505         doc->DecrementReferens(splPath->at(i).P().getIdTool());
506     }
507 }
508 
509 //---------------------------------------------------------------------------------------------------------------------
510 /**
511  * @brief SaveDialog save options into file after change in dialog.
512  */
SaveDialog(QDomElement & domElement,QList<quint32> & oldDependencies,QList<quint32> & newDependencies)513 void VToolSplinePath::SaveDialog(QDomElement &domElement, QList<quint32> &oldDependencies,
514                                  QList<quint32> &newDependencies)
515 {
516     SCASSERT(not m_dialog.isNull())
517     const QPointer<DialogSplinePath> dialogTool = qobject_cast<DialogSplinePath *>(m_dialog);
518     SCASSERT(not dialogTool.isNull())
519 
520     const auto oldSplPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
521     for (qint32 i = 0; i < oldSplPath->CountPoints(); ++i)
522     {
523         AddDependence(oldDependencies, oldSplPath->at(i).P().id());
524     }
525 
526     const VSplinePath splPath = dialogTool->GetPath();
527     for (qint32 i = 0; i < splPath.CountPoints(); ++i)
528     {
529         AddDependence(newDependencies, splPath.at(i).P().id());
530     }
531 
532     for (qint32 i = 1; i <= splPath.CountSubSpl(); ++i)
533     {
534         VSpline spl = splPath.GetSpline(i);
535         qint32 j = i*2;
536 
537         controlPoints[j-2]->blockSignals(true);
538         controlPoints[j-1]->blockSignals(true);
539 
540         controlPoints[j-2]->setPos(static_cast<QPointF>(spl.GetP2()));
541         controlPoints[j-1]->setPos(static_cast<QPointF>(spl.GetP3()));
542 
543         controlPoints[j-2]->blockSignals(false);
544         controlPoints[j-1]->blockSignals(false);
545     }
546 
547     const QString notes = dialogTool->GetNotes();
548     doc->SetAttributeOrRemoveIf(domElement, AttrNotes, notes, notes.isEmpty());
549 
550     SetSplinePathAttributes(domElement, splPath);
551 }
552 
553 //---------------------------------------------------------------------------------------------------------------------
SaveOptions(QDomElement & tag,QSharedPointer<VGObject> & obj)554 void VToolSplinePath::SaveOptions(QDomElement &tag, QSharedPointer<VGObject> &obj)
555 {
556     VAbstractSpline::SaveOptions(tag, obj);
557 
558     QSharedPointer<VSplinePath> splPath = qSharedPointerDynamicCast<VSplinePath>(obj);
559     SCASSERT(splPath.isNull() == false)
560 
561     SetSplinePathAttributes(tag, *splPath);
562 }
563 
564 //---------------------------------------------------------------------------------------------------------------------
mousePressEvent(QGraphicsSceneMouseEvent * event)565 void VToolSplinePath::mousePressEvent(QGraphicsSceneMouseEvent *event)
566 {
567     if (flags() & QGraphicsItem::ItemIsMovable)
568     {
569         if (event->button() == Qt::LeftButton && event->type() != QEvent::GraphicsSceneMouseDoubleClick)
570         {
571             oldPosition = event->scenePos();
572             const auto splPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
573             splIndex = splPath->Segment(oldPosition);
574             if (IsMovable(splIndex))
575             {
576                 SetItemOverrideCursor(this, cursorArrowCloseHand, 1, 1);
577                 event->accept();
578             }
579         }
580     }
581     VAbstractSpline::mousePressEvent(event);
582 }
583 
584 //---------------------------------------------------------------------------------------------------------------------
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)585 void VToolSplinePath::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
586 {
587     if (flags() & QGraphicsItem::ItemIsMovable)
588     {
589         if (event->button() == Qt::LeftButton && event->type() != QEvent::GraphicsSceneMouseDoubleClick)
590         {
591             oldPosition = event->scenePos();
592             SetItemOverrideCursor(this, cursorArrowOpenHand, 1, 1);
593 
594             CurveReleased();
595         }
596     }
597     VAbstractSpline::mouseReleaseEvent(event);
598 }
599 
600 //---------------------------------------------------------------------------------------------------------------------
mouseMoveEvent(QGraphicsSceneMouseEvent * event)601 void VToolSplinePath::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
602 {
603     // Don't need to check if left mouse button was pressed. According to the Qt documentation "If you do receive this
604     // event, you can be certain that this item also received a mouse press event, and that this item is the current
605     // mouse grabber.".
606 
607     if (IsMovable(splIndex))
608     {
609         const auto oldSplPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
610 
611         if (VAbstractApplication::VApp()->Settings()->IsFreeCurveMode() && not moved)
612         {
613             oldMoveSplinePath = QSharedPointer<VSplinePath>::create(*oldSplPath);
614             moved = true;
615         }
616 
617         newMoveSplinePath = QSharedPointer<VSplinePath>::create(*oldSplPath);
618 
619         VSpline spline = newMoveSplinePath->GetSpline(splIndex);
620         const qreal t = spline.ParamT(oldPosition);
621 
622         if (qFloor(t) == -1)
623         {
624             return;
625         }
626 
627         // Magic Bezier Drag Equations follow!
628         // "weight" describes how the influence of the drag should be distributed
629         // among the handles; 0 = front handle only, 1 = back handle only.
630 
631         double weight;
632         if (t <= 1.0 / 6.0)
633         {
634             weight = 0;
635         }
636         else if (t <= 0.5)
637         {
638             weight = (pow((6 * t - 1) / 2.0, 3)) / 2;
639         }
640         else if (t <= 5.0 / 6.0)
641         {
642             weight = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
643         }
644         else
645         {
646             weight = 1;
647         }
648 
649         const QPointF delta = event->scenePos() - oldPosition;
650         const QPointF offset0 = ((1-weight)/(3*t*(1-t)*(1-t))) * delta;
651         const QPointF offset1 = (weight/(3*t*t*(1-t))) * delta;
652 
653         const QPointF p2 = static_cast<QPointF>(spline.GetP2()) + offset0;
654         const QPointF p3 = static_cast<QPointF>(spline.GetP3()) + offset1;
655 
656         oldPosition = event->scenePos(); // Now mouse here
657 
658         const VSpline spl = VSpline(spline.GetP1(), p2, p3, spline.GetP4());
659 
660         UpdateControlPoints(spl, newMoveSplinePath, splIndex);
661 
662         if (not VAbstractApplication::VApp()->Settings()->IsFreeCurveMode())
663         {
664             UndoCommandMove(*oldSplPath, *newMoveSplinePath);
665         }
666         else
667         {
668             VAbstractTool::data.UpdateGObject(m_id, newMoveSplinePath);
669             RefreshGeometry();
670 
671             if (QGraphicsScene *sc = scene())
672             {
673                 VMainGraphicsView::NewSceneRect(sc, VAbstractValApplication::VApp()->getSceneView(), this);
674             }
675         }
676 
677         // Each time we move something we call recalculation scene rect. In some cases this can cause moving
678         // objects positions. And this cause infinite redrawing. That's why we wait the finish of saving the last move.
679         static bool changeFinished = true;
680         if (changeFinished)
681         {
682            changeFinished = false;
683 
684            const QList<QGraphicsView *> viewList = scene()->views();
685            if (not viewList.isEmpty())
686            {
687                if (VMainGraphicsView *view = qobject_cast<VMainGraphicsView *>(viewList.at(0)))
688                {
689                    VMainGraphicsScene *currentScene = qobject_cast<VMainGraphicsScene *>(scene());
690                    SCASSERT(currentScene)
691                    const QPointF cursorPosition = currentScene->getScenePos();
692                    const qreal scale = SceneScale(scene());
693                    view->EnsureVisibleWithDelay(QRectF(cursorPosition.x()-5/scale, cursorPosition.y()-5/scale, 10/scale,
694                                                        10/scale),
695                                                 VMainGraphicsView::scrollDelay);
696                }
697            }
698            changeFinished = true;
699         }
700     }
701 }
702 
703 //---------------------------------------------------------------------------------------------------------------------
hoverEnterEvent(QGraphicsSceneHoverEvent * event)704 void VToolSplinePath::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
705 {
706     if (m_acceptHoverEvents)
707     {
708         if (flags() & QGraphicsItem::ItemIsMovable)
709         {
710             oldPosition = event->scenePos();
711             const auto splPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
712             splIndex = splPath->Segment(oldPosition);
713             if (IsMovable(splIndex))
714             {
715                 SetItemOverrideCursor(this, cursorArrowOpenHand, 1, 1);
716             }
717             else
718             {
719                 setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor());
720             }
721         }
722         else
723         {
724             setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor());
725         }
726 
727         VAbstractSpline::hoverEnterEvent(event);
728     }
729     else
730     {
731         setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor());
732     }
733 }
734 
735 //---------------------------------------------------------------------------------------------------------------------
hoverLeaveEvent(QGraphicsSceneHoverEvent * event)736 void VToolSplinePath::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
737 {
738     if (m_acceptHoverEvents)
739     {
740         VAbstractSpline::hoverLeaveEvent(event);
741     }
742 }
743 
744 //---------------------------------------------------------------------------------------------------------------------
SetVisualization()745 void VToolSplinePath::SetVisualization()
746 {
747     if (not vis.isNull())
748     {
749         VisToolSplinePath *visual = qobject_cast<VisToolSplinePath *>(vis);
750         SCASSERT(visual != nullptr)
751 
752         QSharedPointer<VSplinePath> splPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
753         visual->setPath(*splPath.data());
754         visual->setLineStyle(LineStyleToPenStyle(splPath->GetPenStyle()));
755         visual->SetMode(Mode::Show);
756         visual->RefreshGeometry();
757     }
758 }
759 
760 //---------------------------------------------------------------------------------------------------------------------
IsMovable(int index) const761 bool VToolSplinePath::IsMovable(int index) const
762 {
763     const auto splPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
764 
765     if (index < 1 || index > splPath->CountSubSpl())
766     {
767         return false;
768     }
769 
770     const VSplinePoint p1 = splPath->GetSplinePoint(index, SplinePointPosition::FirstPoint);
771     const VSplinePoint p2 = splPath->GetSplinePoint(index, SplinePointPosition::LastPoint);
772 
773     return p1.IsMovable() && p2.IsMovable();
774 }
775 
776 //---------------------------------------------------------------------------------------------------------------------
RefreshCtrlPoints()777 void VToolSplinePath::RefreshCtrlPoints()
778 {
779     // Very important to disable control points. Without it the pogram can't move the curve.
780     for (auto point : qAsConst(controlPoints))
781     {
782         point->setFlag(QGraphicsItem::ItemSendsGeometryChanges, false);
783     }
784 
785     const auto splPath = VAbstractTool::data.GeometricObject<VSplinePath>(m_id);
786 
787     for (qint32 i = 1; i<=splPath->CountSubSpl(); ++i)
788     {
789         const qint32 j = i*2;
790 
791         controlPoints[j-2]->blockSignals(true);
792         controlPoints[j-1]->blockSignals(true);
793 
794         const auto spl = splPath->GetSpline(i);
795 
796         {
797             bool freeAngle1 = true;
798 
799             if (i > 1)
800             {
801                 const VSpline prevSpl = splPath->GetSpline(i-1);
802                 freeAngle1 = qmu::QmuTokenParser::IsSingle(spl.GetStartAngleFormula()) &&
803                              qmu::QmuTokenParser::IsSingle(prevSpl.GetEndAngleFormula());
804             }
805             else
806             {
807                 freeAngle1 = qmu::QmuTokenParser::IsSingle(spl.GetStartAngleFormula());
808             }
809 
810             const bool freeLength1 = qmu::QmuTokenParser::IsSingle(spl.GetC1LengthFormula());
811 
812             const auto splinePoint = spl.GetP1();
813             controlPoints[j-2]->RefreshCtrlPoint(i, SplinePointPosition::FirstPoint, static_cast<QPointF>(spl.GetP2()),
814                                                  static_cast<QPointF>(splinePoint), freeAngle1, freeLength1);
815         }
816 
817         {
818             bool freeAngle2 = true;
819 
820             if (i < splPath->CountSubSpl())
821             {
822                 const VSpline nextSpl = splPath->GetSpline(i+1);
823                 freeAngle2 = qmu::QmuTokenParser::IsSingle(nextSpl.GetStartAngleFormula()) &&
824                              qmu::QmuTokenParser::IsSingle(spl.GetEndAngleFormula());
825             }
826             else
827             {
828                 freeAngle2 = qmu::QmuTokenParser::IsSingle(spl.GetEndAngleFormula());
829             }
830 
831             const bool freeLength2 = qmu::QmuTokenParser::IsSingle(spl.GetC2LengthFormula());
832 
833             const auto splinePoint = spl.GetP4();
834             controlPoints[j-1]->RefreshCtrlPoint(i, SplinePointPosition::LastPoint, static_cast<QPointF>(spl.GetP3()),
835                                                  static_cast<QPointF>(splinePoint), freeAngle2, freeLength2);
836         }
837 
838         controlPoints[j-2]->blockSignals(false);
839         controlPoints[j-1]->blockSignals(false);
840     }
841 
842     for (auto point : qAsConst(controlPoints))
843     {
844         point->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
845     }
846 }
847 
848 //---------------------------------------------------------------------------------------------------------------------
CurveReleased()849 void VToolSplinePath::CurveReleased()
850 {
851     if (VAbstractApplication::VApp()->Settings()->IsFreeCurveMode() && moved)
852     {
853         UndoCommandMove(*oldMoveSplinePath, *newMoveSplinePath);
854 
855         oldMoveSplinePath.clear();
856         newMoveSplinePath.clear();
857 
858         moved = false;
859     }
860 }
861