// -------------------------------------------------------------------- // Overlay for creating IpePath objects // -------------------------------------------------------------------- /* This file is part of the extensible drawing editor Ipe. Copyright (C) 1993-2004 Otfried Cheong Ipe is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. As a special exception, you have permission to link Ipe with the CGAL library and distribute executables, as long as you follow the requirements of the Gnu General Public License in regard to all of the software in the executable aside from CGAL. Ipe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Ipe; if not, you can find it at "http://www.gnu.org/copyleft/gpl.html", or write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ipepath.h" #include "ipecreatepath.h" #include "ipecanvas.h" #include #include // -------------------------------------------------------------------- /*! \class CreateRectangle \brief Overlay for creating rectangles and squares. */ //! Constructor starts selection. IpeCreateRectangle::IpeCreateRectangle(QMouseEvent *ev, IpeCanvas *canvas, IpeOverlayServices *services) : IpeOverlay(canvas), iServices(services) { // need to work out coordinates in user space iV[0] = iCanvas->Pos(); iV[1] = iV[0]; iSquare = (ev->state() & QMouseEvent::ShiftButton); } void IpeCreateRectangle::Draw(QPaintEvent *, QPainter *qPainter) const { qPainter->setPen(CreateColor()); IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter); painter.Transform(iCanvas->CanvasTfm()); painter.NewPath(); painter.Rect(IpeRect(iV[0], iV[1])); painter.DrawPath(); } void IpeCreateRectangle::Compute() { iV[1] = iCanvas->Pos(); if (iSquare) { IpeVector d = iV[1] - iV[0]; IpeVector sign(d.iX > 0 ? 1 : -1, d.iY > 0 ? 1 : -1); double dd = IpeMax(IpeAbs(d.iX), IpeAbs(d.iY)); iV[1] = iV[0] + dd * sign; } } void IpeCreateRectangle::MouseMove(QMouseEvent *) { Compute(); iCanvas->UpdateOverlay(); } void IpeCreateRectangle::MousePress(QMouseEvent *) { Compute(); IpePath *obj = new IpePath(iServices->OvSvcAttributes(), IpeRect(iV[0], iV[1])); iServices->OvSvcAddObject(obj); iCanvas->FinishOverlay(); } // -------------------------------------------------------------------- /*! \class IpeCreateSplinegon \brief Overlay for creating IpePath splinegons. */ //! Constructor starts selection. IpeCreateSplinegon::IpeCreateSplinegon(QMouseEvent *, IpeCanvas *canvas, IpeOverlayServices *services) : IpeOverlay(canvas), iServices(services) { iV.push_back(iCanvas->Pos()); iV.push_back(iV[0]); iCanvas->SetDoubleBuffer(true); Explain(); } void IpeCreateSplinegon::Explain() const { iCanvas->Message(QObject::tr("Left: Add vertex | Right: Add final vertex | Del: Delete vertex")); } void IpeCreateSplinegon::KeyPress(QKeyEvent *ev) { if (ev->key() == Qt::Key_Delete) { if (iV.size() == 2) { iCanvas->FinishOverlay(); ev->accept(); return; } iV.pop_back(); iCanvas->UpdateOverlay(); } else ev->ignore(); Explain(); } void IpeCreateSplinegon::Draw(QPaintEvent *, QPainter *qPainter) const { qPainter->setPen(CreateColor()); IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter); painter.Transform(iCanvas->CanvasTfm()); painter.Push(); painter.NewPath(); if (iV.size() < 3) { painter.MoveTo(iV[0]); painter.LineTo(iV[1]); } else { std::vector bez; IpeBezier::ClosedSpline(iV.size(), &(iV[0]), bez); painter.MoveTo(bez.front().iV[0]); for (uint i = 0; i < bez.size(); ++i) painter.IpePainter::CurveTo(bez[i]); painter.ClosePath(); } painter.DrawPath(); painter.Pop(); } void IpeCreateSplinegon::MouseMove(QMouseEvent *) { iV.back() = iCanvas->Pos(); iCanvas->UpdateOverlay(); } void IpeCreateSplinegon::MousePress(QMouseEvent *ev) { if (ev->button() == QMouseEvent::LeftButton) { // freeze moving vertex iV.back() = iCanvas->Pos(); // add new moving vertex iV.push_back(iCanvas->Pos()); iCanvas->UpdateOverlay(); Explain(); } else if (iV.size() > 2){ // freeze moving vertex iV.back() = iCanvas->Pos(); // create IpePath object IpePath *obj = new IpePath(iServices->OvSvcAttributes()); obj->AddSubPath(new IpeClosedSpline(iV)); iServices->OvSvcAddObject(obj); iCanvas->FinishOverlay(); } } // -------------------------------------------------------------------- static void AppendSpline(IpeSegmentSubPath *sp, const IpeVector &v) { std::vector vv; vv.push_back(v); vv.push_back(v); sp->AppendSpline(vv); } /*! \class IpeCreatePath \brief Overlay for creating IpePath objects. */ //! Constructor starts selection. IpeCreatePath::IpeCreatePath(QMouseEvent *, IpeCanvas *canvas, IpeOverlayServices *services, bool closed, bool spline) : IpeOverlay(canvas), iServices(services) { iSP = new IpeSegmentSubPath; iSP->SetClosed(closed); IpeVector v = iCanvas->Pos(); if (spline) AppendSpline(iSP, v); else iSP->AppendSegment(v, v); iCanvas->SetDoubleBuffer(true); Update(); } IpeCreatePath::~IpeCreatePath() { delete iSP; } void IpeCreatePath::Update() { IpePathSegment seg = iSP->Segment(-1); iCanvas->SetAutoOrigin(seg.CP(seg.NumCP() - 1)); iCanvas->UpdateOverlay(); QString s = QObject::tr("Left: Add vtx"); if (seg.Type() == IpePathSegment::ESpline) s += QObject::tr(" | Left with Shift: Switch to polyline"); else if (seg.Type() == IpePathSegment::ESegment) s += QObject::tr(" | Left with Shift: Switch to spline"); s += QObject::tr(" | Right: final vtx | Del: Del vtx"); if (seg.Type() == IpePathSegment::ESegment && iSP->NumSegments() >= 2 && iSP->Segment(-2).Type() == IpePathSegment::ESegment) { s += QObject::tr(" | q : quadratic Bezier | a : circle arc"); if (iSP->NumSegments() >= 3 && iSP->Segment(-3).Type() == IpePathSegment::ESegment) s += QObject::tr(" | c : cubic Bezier"); } iCanvas->Message(s); } // Check whether path ends with k straight segments. // If so, remove them, and return the k + 1 control points. bool IpeCreatePath::Decrease(int k, std::vector &v) { for (int i = 1; i <= k; ++i) { if (iSP->Segment(-i).Type() != IpePathSegment::ESegment) return false; } v.resize(k + 1); v[k] = iSP->Segment(-1).Last(); while (k > 0) { v[k-1] = iSP->Segment(-1).CP(0); iSP->DeleteSegment(-1); --k; } return true; } IpeMatrix IpeCreatePath::ComputeMatrix(const IpeVector &v0, const IpeVector &v1, const IpeVector &v2) { IpeVector dir = (v1 - v0).Normalized(); IpeLine l1(v1, dir); IpeLine n1(v1, l1.Normal()); if (v1 == v2) return IpeMatrix(v1 + n1.Dir()); IpeLine l2(v1, (v2 - v1).Normalized()); IpeLine n2(0.5 * (v1 + v2), l2.Normal()); IpeVector center; n1.Intersects(n2, center); // double alpha = (v1 - center).Angle(); // double beta = (v2 - center).Angle().Normalize(alpha); double radius = (v2 - center).Len(); IpeMatrix m(radius, 0, 0, radius, center.iX, center.iY); if (l1.Side(v2) < 0) m = m * IpeLinear(1, 0, 0, -1); return m; } void IpeCreatePath::RecomputeMatrix() { IpeVector v1 = iSP->Segment(-1).CP(0); IpeVector v2 = iSP->Segment(-1).CP(1); iSP->DeleteSegment(-1); IpeVector v0 = iSP->Segment(-1).CP(0); IpeMatrix m = ComputeMatrix(v0, v1, v2); iSP->AppendArc(m, v1, v2); } void IpeCreatePath::KeyPress(QKeyEvent *ev) { std::vector v; if (ev->key() == Qt::Key_Delete) { if (iSP->NumSegments() == 1 && (iSP->Segment(0).Type() != IpePathSegment::ESpline || iSP->Segment(0).NumCP() < 3)) { iCanvas->FinishOverlay(); ev->accept(); return; } switch (iSP->Segment(-1).Type()) { case IpePathSegment::ESegment: case IpePathSegment::EArc: iSP->DeleteSegment(-1); break; case IpePathSegment::EBezier: case IpePathSegment::EQuad: break; case IpePathSegment::ESpline: if (iSP->Segment(-1).NumCP() > 2) iSP->DeleteCP(-1, -1); else iSP->DeleteSegment(-1); break; } ev->accept(); } else if (ev->key() == Qt::Key_Q && Decrease(2, v)) { // Quadratic Bezier iSP->AppendQuad(v[0], v[1], v[2]); ev->accept(); } else if (ev->key() == Qt::Key_C && Decrease(3, v)) { // Cubic Bezier iSP->AppendBezier(v[0], v[1], v[2], v[3]); ev->accept(); } else if (ev->key() == Qt::Key_A && Decrease(2, v)) { // arc mode: not quite clear why we need a preceding line segment, // but somehow the tangent needs to be fixed. iSP->AppendSegment(v[0], v[1]); IpeMatrix m = ComputeMatrix(v[0], v[1], v[2]); iSP->AppendArc(m, v[1], v[2]); } else ev->ignore(); Update(); } void IpeCreatePath::Draw(QPaintEvent *, QPainter *qPainter) const { if (iSP) { qPainter->setPen(CreateColor()); IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter); painter.Transform(iCanvas->CanvasTfm()); painter.Push(); painter.NewPath(); iSP->Draw(painter); painter.DrawPath(); painter.Pop(); } } void IpeCreatePath::MouseMove(QMouseEvent *) { iSP->MoveCP(-1, -1, iCanvas->Pos()); if (iSP->Segment(-1).Type() == IpePathSegment::EArc) RecomputeMatrix(); iCanvas->UpdateOverlay(); } void IpeCreatePath::MousePress(QMouseEvent *ev) { if (ev->button() == QMouseEvent::LeftButton) { bool swit = (ev->state() & QMouseEvent::ShiftButton) != 0; IpeVector v = iCanvas->Pos(); // freeze moving vertex iSP->MoveCP(-1, -1, v); // add new moving vertex IpePathSegment seg = iSP->Segment(-1); if (swit) { if (seg.Type() == IpePathSegment::ESegment) AppendSpline(iSP, v); else iSP->AppendSegment(v, v); } else { if (seg.Type() == IpePathSegment::ESpline) iSP->InsertCP(-1, seg.NumCP(), v); else iSP->AppendSegment(v, v); } Update(); } else { // freeze moving vertex iSP->MoveCP(-1, -1, iCanvas->Pos()); if (iSP->Segment(-1).Type() == IpePathSegment::EArc) RecomputeMatrix(); // many users seem to end their paths with zero-length segments // because they first click left and then right. // We now correct this behaviour: IpePathSegment seg = iSP->Segment(-1); if (seg.Type() == IpePathSegment::ESegment && seg.CP(0) == seg.CP(1) && iSP->NumSegments() > 1) iSP->DeleteSegment(-1); // create IpePath object const IpeAllAttributes &attr = iServices->OvSvcAttributes(); IpePath *obj = new IpePath(attr); if (!iSP->Closed() && !attr.iStroke.IsVoid()) obj->SetFill(IpeAttribute()); obj->AddSubPath(iSP); iSP = 0; iServices->OvSvcAddObject(obj); iCanvas->FinishOverlay(); } } // --------------------------------------------------------------------