// -------------------------------------------------------------------- // Overlay for editing 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 "ipeeditpath.h" #include "ipecanvas.h" #include #include // -------------------------------------------------------------------- enum { ELineVtx, // between two straight segments EBezierVtx, // interior CP of EBezier or EQuad ESplineVtx, // interior CP of ESpline EFrontVtx, // front of path if it starts with line or spline ETailVtx, // tail of path if it ends with line or spline EFrontDivVtx, // front of path otherwise ETailDivVtx, // tail of path otherwise ESplineSplineVtx, // division between two spline segments ESplineLineVtx, // division between spline and line segments EDivVtx, // division between segments if one is not spline or line ECenterVtx, ECurrentVtx, EOtherVtx // styles for drawing only }; bool IsArcOrBezier(const IpePathSegment &seg) { return seg.Type() == IpePathSegment::EArc || seg.Type() == IpePathSegment::EBezier || seg.Type() == IpePathSegment::EQuad; } void DrawVertex(int type, QPainter *qPainter, const IpeVector &pos) { int siz = 4; int x = int(pos.iX); int y = int(pos.iY); qPainter->setPen(Qt::NoPen); switch (type) { case EFrontVtx: case ETailVtx: siz = 6; qPainter->setBrush(Qt::magenta); qPainter->drawEllipse(x - siz, y-siz, siz+siz+1, siz+siz+1); break; case ELineVtx: case ESplineVtx: case EBezierVtx: qPainter->setBrush(Qt::magenta); qPainter->drawRect(x - siz, y-siz, siz+siz+1, siz+siz+1); break; case ESplineLineVtx: case ESplineSplineVtx: case EDivVtx: case EFrontDivVtx: case ETailDivVtx: siz = 6; qPainter->setBrush(Qt::red); qPainter->drawEllipse(x - siz, y-siz, siz+siz+1, siz+siz+1); break; case ECenterVtx: siz = 6; qPainter->setBrush(Qt::green); qPainter->drawEllipse(x - siz, y-siz, siz+siz+1, siz+siz+1); break; case ECurrentVtx: siz = 8; qPainter->setPen(Qt::red); qPainter->setBrush(Qt::NoBrush); qPainter->drawEllipse(x - 8, y - 8, 17, 17); break; case EOtherVtx: siz = 8; qPainter->setPen(Qt::red); qPainter->setBrush(Qt::NoBrush); qPainter->drawRect(x - 8, y - 8, 17, 17); break; } } // -------------------------------------------------------------------- class EditSubPath { public: virtual ~EditSubPath() = 0; virtual void Draw(QPainter *qPainter, IpePainter &painter) const = 0; virtual double FindVertex(const IpeVector &pos, double bound) = 0; virtual IpeVector CurVertex() const = 0; virtual void Move(const IpeVector &v) = 0; virtual bool CanDelete() const = 0; //! Returns true if subpath is now gone. virtual bool Delete() = 0; virtual IpeSubPath *Take() = 0; virtual bool HasOther(int) const { return false; } virtual IpeVector Other(int) const { return IpeVector(); } virtual bool CanInsert(int) const { return false; } virtual void Insert(int, const IpeVector &) { /* nothing */ } }; EditSubPath::~EditSubPath() { // nothing } // -------------------------------------------------------------------- class EditEllipse : public EditSubPath { public: EditEllipse(IpeEllipse *ell) : iEll(ell) { /* nothing */ } ~EditEllipse() { delete iEll; } virtual void Draw(QPainter *qPainter, IpePainter &painter) const; virtual double FindVertex(const IpeVector &pos, double bound); virtual IpeVector CurVertex() const { return iEll->Matrix().Translation(); } virtual void Move(const IpeVector &v); virtual bool CanDelete() const { return true; } virtual bool Delete() { return true; } virtual IpeSubPath *Take() { IpeSubPath *sp = iEll; iEll = 0; return sp; } private: IpeEllipse *iEll; }; void EditEllipse::Draw(QPainter *qPainter, IpePainter &painter) const { qPainter->setPen(Qt::magenta); qPainter->setBrush(Qt::NoBrush); painter.NewPath(); iEll->Draw(painter); painter.DrawPath(); DrawVertex(ECenterVtx, qPainter, painter.Matrix() * iEll->Matrix().Translation()); } double EditEllipse::FindVertex(const IpeVector &pos, double /*bound*/) { return (pos - iEll->Matrix().Translation()).Len(); } void EditEllipse::Move(const IpeVector &v) { iEll->SetMatrix(IpeMatrix(iEll->Matrix().Linear(), v)); } // -------------------------------------------------------------------- class EditClosedSpline : public EditSubPath { public: EditClosedSpline(IpeClosedSpline *csp) : iCSP(csp), iCur(0) { /* nothing */ } ~EditClosedSpline() { delete iCSP; } virtual void Draw(QPainter *qPainter, IpePainter &painter) const; virtual double FindVertex(const IpeVector &pos, double bound); virtual IpeVector CurVertex() const { return iCSP->iCP[iCur]; } virtual void Move(const IpeVector &v); virtual bool CanDelete() const { return true; } virtual bool Delete(); virtual IpeSubPath *Take() { IpeSubPath *sp = iCSP; iCSP = 0; return sp; } virtual bool HasOther(int) const { return true; } virtual IpeVector Other(int other) const; virtual bool CanInsert(int) const { return true; } virtual void Insert(int other, const IpeVector &v); private: IpeClosedSpline *iCSP; int iCur; }; double EditClosedSpline::FindVertex(const IpeVector &pos, double bound) { double d = bound; double d1; for (uint j = 0; j < iCSP->iCP.size(); ++j) { if ((d1 = (pos - iCSP->iCP[j]).Len()) < d) { d = d1; iCur = j; } } return d; } bool EditClosedSpline::Delete() { if (iCSP->iCP.size() < 4) return true; iCSP->iCP.erase(iCSP->iCP.begin() + iCur); if (iCur > 0) iCur--; return false; } void EditClosedSpline::Draw(QPainter *qPainter, IpePainter &painter) const { qPainter->setPen(Qt::magenta); painter.NewPath(); iCSP->Draw(painter); QPen penSpline(Qt::green, 0, Qt::DashLine); qPainter->setPen(penSpline); painter.MoveTo(iCSP->iCP[0]); for (uint i = 1; i < iCSP->iCP.size(); ++i) painter.LineTo(iCSP->iCP[i]); painter.ClosePath(); painter.DrawPath(); for (uint i = 0; i < iCSP->iCP.size(); ++i) DrawVertex(ELineVtx, qPainter, painter.Matrix() * iCSP->iCP[i]); } void EditClosedSpline::Move(const IpeVector &v) { iCSP->iCP[iCur] = v; } IpeVector EditClosedSpline::Other(int other) const { int k = (iCur + other) % iCSP->iCP.size(); return iCSP->iCP[k]; } void EditClosedSpline::Insert(int other, const IpeVector &v) { if (other < 0) iCSP->iCP.insert(iCSP->iCP.begin() + iCur, v); else iCSP->iCP.insert(iCSP->iCP.begin() + iCur + 1, v); } // -------------------------------------------------------------------- class EditSegmentSubPath : public EditSubPath { public: EditSegmentSubPath(IpeSegmentSubPath *ssp); ~EditSegmentSubPath() { delete iSSP; } virtual void Draw(QPainter *qPainter, IpePainter &painter) const; virtual double FindVertex(const IpeVector &pos, double bound); int VertexType(int j, int k) const; virtual IpeVector CurVertex() const; virtual void Move(const IpeVector &v); virtual bool CanDelete() const; virtual bool Delete(); virtual IpeSubPath *Take() { IpeSubPath *sp = iSSP; iSSP = 0; return sp; } virtual bool HasOther(int other) const; virtual IpeVector Other(int other) const; virtual bool CanInsert(int other) const; virtual void Insert(int other, const IpeVector &v); private: IpeSegmentSubPath *iSSP; int iSeg; int iCur; }; EditSegmentSubPath::EditSegmentSubPath(IpeSegmentSubPath *ssp) : iSSP(ssp), iSeg(0), iCur(0) { // replace all B-spline segments with only 2 CPs by straight segments for (int i = 0; i < iSSP->NumSegments(); ++i) { IpePathSegment seg = iSSP->Segment(i); if (seg.Type() == IpePathSegment::ESpline && seg.NumCP() == 2) iSSP->Straighten(i); } } IpeVector EditSegmentSubPath::CurVertex() const { if (iCur < 0) return iSSP->Segment(iSeg).Matrix().Translation(); else return iSSP->Segment(iSeg).CP(iCur); } // Return type of CP(k) of segment j (both can be negative). int EditSegmentSubPath::VertexType(int j, int k) const { if (j < 0) j += iSSP->NumSegments(); IpePathSegment seg = iSSP->Segment(j); if (k < 0) k += seg.NumCP(); // Handle end cases if (j == 0 && k == 0) return IsArcOrBezier(seg) ? EFrontDivVtx : EFrontVtx; else if (j == iSSP->NumSegments() - 1 && k == iSSP->Segment(-1).NumCP() - 1) return IsArcOrBezier(seg) ? ETailDivVtx : ETailVtx; // Handle interior CPs if (0 < k && k < seg.NumCP() - 1) return IsArcOrBezier(seg) ? EBezierVtx : ESplineVtx; IpePathSegment::TType t1 = seg.Type(); IpePathSegment::TType t2 = (k > 0) ? iSSP->Segment(j + 1).Type() : iSSP->Segment(j - 1).Type(); if (t1 == IpePathSegment::ESegment) { if (t2 == IpePathSegment::ESegment) return ELineVtx; else if (t2 == IpePathSegment::ESpline) return ESplineLineVtx; } else if (t1 == IpePathSegment::ESpline) { if (t2 == IpePathSegment::ESegment) return ESplineLineVtx; else if (t2 == IpePathSegment::ESpline) return ESplineSplineVtx; } return EDivVtx; } bool EditSegmentSubPath::CanDelete() const { if (iCur < 0) return true; switch (VertexType(iSeg, iCur)) { case ELineVtx: case ESplineVtx: case EBezierVtx: case EFrontVtx: case ETailVtx: return true; default: return false; } } bool EditSegmentSubPath::Delete() { IpePathSegment seg = iSSP->Segment(iSeg); IpePathSegment::TType t = seg.Type(); // vertex in the middle if (iCur < 0 || (0 < iCur && iCur + 1 < seg.NumCP())) { if (IsArcOrBezier(seg)) { iSSP->Straighten(iSeg); iCur = 0; } else { assert(t == IpePathSegment::ESpline); if (seg.NumCP() == 3) iSSP->Straighten(iSeg); else iSSP->DeleteCP(iSeg, iCur); } return false; } else if (iSeg == iSSP->NumSegments() - 1 && iCur > 0) { // Last vertex of chain if (t == IpePathSegment::ESegment) iSSP->DeleteSegment(-1); else if (t == IpePathSegment::ESpline) iSSP->DeleteCP(-1, -1); // in all other cases, we cannot delete if (iSSP->NumSegments() > 0) { // make last vertex again current iSeg = iSSP->NumSegments() - 1; iCur = iSSP->Segment(-1).NumCP() - 1; return false; } else return true; } else if (iSeg == 0 && iCur == 0) { // First vertex of chain if (t == IpePathSegment::ESegment) iSSP->DeleteSegment(0); else if (t == IpePathSegment::ESpline) iSSP->DeleteCP(0, 0); // make first vertex again current iSeg = 0; iCur = 0; return (iSSP->NumSegments() == 0); } else { // deleting joining vertex in the middle if (VertexType(iSeg, iCur) == ELineVtx) { // make segment trivial, so it can be deleted. iSSP->MoveCP(iSeg, 0, seg.CP(1)); iSSP->DeleteSegment(iSeg); if (iSeg >= iSSP->NumSegments()) --iSeg; } } return false; } bool EditSegmentSubPath::HasOther(int other) const { if (iCur < 0) return false; switch (VertexType(iSeg, iCur)) { case ELineVtx: case ESplineVtx: case ESplineLineVtx: case ESplineSplineVtx: return true; case EFrontVtx: return iSSP->Closed() || (other > 0); case ETailVtx: return iSSP->Closed() || (other < 0); default: return false; } } IpeVector EditSegmentSubPath::Other(int other) const { IpePathSegment seg = iSSP->Segment(iSeg); int k = iCur + other; if (0 <= k && k < seg.NumCP()) return seg.CP(k); if (k < 0) { if (iSeg == 0) return iSSP->Segment(-1).Last(); IpePathSegment seg1 = iSSP->Segment(iSeg - 1); return seg1.CP(seg1.NumCP() - 2); } if (iSeg == iSSP->NumSegments() - 1) return iSSP->Segment(0).CP(0); IpePathSegment seg1 = iSSP->Segment(iSeg + 1); return seg1.CP(1); } bool EditSegmentSubPath::CanInsert(int other) const { if (iCur < 0) return false; switch (VertexType(iSeg, iCur)) { case ELineVtx: case ESplineVtx: case EFrontVtx: case ETailVtx: case ESplineSplineVtx: case ESplineLineVtx: return true; case EFrontDivVtx: return (other < 0); case ETailDivVtx: return (other > 0); case EDivVtx: if (other > 0) return !IsArcOrBezier(iSSP->Segment(iSeg)); else return !IsArcOrBezier(iSSP->Segment(iSeg - 1)); default: return false; } } void EditSegmentSubPath::Insert(int other, const IpeVector &v) { // appending at the front if (iSeg == 0 && iCur == 0 && other < 0) { iSSP->InsertSegment(0); iSSP->MoveCP(0, 0, v); // ensure current vertex remains front vertex iSeg = 0; iCur = 0; return; } // appending at the back if (iSeg == iSSP->NumSegments() - 1 && iCur == iSSP->Segment(-1).NumCP() - 1 && other > 0) { iSSP->AppendSegment(iSSP->Segment(-1).Last(), v); // ensure current vertex remains tail vertex iSeg = iSSP->NumSegments() - 1; iCur = iSSP->Segment(-1).NumCP() - 1; return; } // adjust iSeg to contain both points if (iCur == 0 && other < 0) { iSeg--; iCur = iSSP->Segment(iSeg).NumCP() - 1; } else if (iCur == iSSP->Segment(iSeg).NumCP() - 1 && other > 0) { iSeg++; iCur = 0; } IpePathSegment seg = iSSP->Segment(iSeg); if (seg.Type() == IpePathSegment::ESpline) { // insert into spline if (other > 0) iCur++; iSSP->InsertCP(iSeg, iCur, v); return; } // remaining case: seg is a segment assert(seg.Type() == IpePathSegment::ESegment); iSSP->InsertSegment(iSeg); iCur = 1; iSSP->MoveCP(iSeg, iCur, v); } /*! Never returns last vertex of a segment, unless it's the last vertex of the path. */ double EditSegmentSubPath::FindVertex(const IpeVector &pos, double bound) { double d = bound; double d1; for (int j = 0; j < iSSP->NumSegments(); ++j) { IpePathSegment seg = iSSP->Segment(j); for (int k = 0; k < seg.NumCP() - 1; ++k) { if ((d1 = (pos - seg.CP(k)).Len()) < d) { d = d1; iSeg = j; iCur = k; } } if (seg.Type() == IpePathSegment::EArc) { if ((d1 = (pos - seg.Matrix().Translation()).Len()) < d) { d = d1; iSeg = j; iCur = -1; } } } if ((d1 = (pos - iSSP->Segment(-1).Last()).Len()) < d) { d = d1; iSeg = iSSP->NumSegments() - 1; iCur = iSSP->Segment(-1).NumCP() - 1; } return d; } void EditSegmentSubPath::Draw(QPainter *qPainter, IpePainter &painter) const { qPainter->setPen(Qt::magenta); painter.NewPath(); iSSP->Draw(painter); QPen penBezier(Qt::green, 0, Qt::DotLine); QPen penSpline(Qt::green, 0, Qt::DashLine); for (int j = 0; j < iSSP->NumSegments(); ++j) { IpePathSegment segm = iSSP->Segment(j); if (segm.Type() != IpePathSegment::EArc && segm.Type() != IpePathSegment::ESegment) { if (segm.Type() == IpePathSegment::ESpline) qPainter->setPen(penSpline); else qPainter->setPen(penBezier); painter.MoveTo(segm.CP(0)); for (int i = 1; i < segm.NumCP(); ++i) painter.LineTo(segm.CP(i)); } } painter.DrawPath(); IpeMatrix pm = painter.Matrix(); for (int j = 0; j < iSSP->NumSegments(); ++j) { IpePathSegment segm = iSSP->Segment(j); for (int k = 0; k < segm.NumCP() - 1; ++k) DrawVertex(VertexType(j, k), qPainter, pm * segm.CP(k)); if (segm.Type() == IpePathSegment::EArc) DrawVertex(ECenterVtx, qPainter, pm * segm.Matrix().Translation()); } DrawVertex(VertexType(-1, -1), qPainter, pm * iSSP->Segment(-1).Last()); } void EditSegmentSubPath::Move(const IpeVector &v) { if (iCur < 0) { IpePathSegment seg = iSSP->Segment(iSeg); IpeMatrix m = seg.Matrix(); IpeMatrix inv = m.Inverse(); IpeVector v0 = inv * seg.CP(0); IpeVector v1 = inv * seg.CP(1); IpeLine l(0.5 * (v0 + v1), (v1 - v0).Normalized().Orthogonal()); IpeVector c = l.Project(inv * v); double r = (v0 - c).Len(); IpeMatrix m1 = m * IpeMatrix(IpeLinear(r, 0, 0, r), c); iSSP->SetMatrix(iSeg, m1); } else iSSP->MoveCP(iSeg, iCur, v); } // -------------------------------------------------------------------- /*! \class EditPath \brief Overlay for creating IpePath objects. */ //! Constructor starts selection. IpeEditPath::IpeEditPath(IpeCanvas *canvas, IpePath *path, IpePage *page, IpeOverlayServices* services, IpePage::iterator it) : IpeOverlay(canvas), iPath(path), iPage(page), iIt(it), iServices(services) { for (int i = 0; i < path->NumSubPaths(); ++i) { IpeSubPath *sp = path->SubPath(i)->Clone(); EditSubPath *sp1 = 0; switch (sp->Type()) { case IpeSubPath::EEllipse: sp1 = new EditEllipse(sp->AsEllipse()); break; case IpeSubPath::EClosedSpline: sp1 = new EditClosedSpline(sp->AsClosedSpline()); break; case IpeSubPath::ESegments: sp1 = new EditSegmentSubPath(sp->AsSegs()); break; } iSP.push_back(sp1); } iCur = 0; iOther = 1; iInv = iPath->Matrix().Inverse(); iMoving = false; iCanvas->SetDoubleBuffer(true); iCanvas->SetDimmed(true); Explain(); } //! Destructor. IpeEditPath::~IpeEditPath() { for (uint i = 0; i < iSP.size(); ++i) delete iSP[i]; } //! Replace object, finish overlay. void IpeEditPath::Finish() { IpeAllAttributes attr; attr.iStroke = iPath->Stroke(); attr.iFill = iPath->Fill(); attr.iLineWidth = iPath->LineWidth(); attr.iDashStyle = iPath->DashStyle(); IpePath *obj = new IpePath(attr); obj->SetForwardArrow(iPath->ForwardArrow()); obj->SetBackwardArrow(iPath->BackwardArrow()); obj->SetStrokeStyle(iPath->StrokeStyle()); obj->SetMatrix(iPath->Matrix()); for (uint i = 0; i < iSP.size(); ++i) obj->AddSubPath(iSP[i]->Take()); iServices->OvSvcAddUndoItem(iIt, iPage, *iIt, QObject::tr("editing path object")); iIt->ReplaceObject(obj); iPage->SetEdited(true); iCanvas->FinishOverlay(); } void IpeEditPath::Explain() const { QString s; if (iSP[iCur]->CanInsert(iOther)) s += QObject::tr("Left: Insert vtx | "); s += QObject::tr("Middle or Shift-Right: Move vtx | "); s += QObject::tr("Right: Select vtx | "); s += QObject::tr("O: Other insertion edge | "); if (iSP[iCur]->CanDelete()) s += QObject::tr("Del: Del vtx | "); s += QObject::tr("Space: accept edit"); iCanvas->Message(s); } //! Set current vertex to vertex closest to pos. void IpeEditPath::FindVertex(const IpeVector &pos) { IpeVector pos1 = iInv * pos; double d = (pos1 - iSP[iCur]->CurVertex()).Len(); for (uint i = 0; i < iSP.size(); ++i) { double d1 = iSP[i]->FindVertex(pos1, d); if (d1 < d) { d = d1; iCur = i; } } } //! Destroy current subpath. Return true if this object has been deleted. bool IpeEditPath::DestroySubPath() { // destroyed last subpath if (iSP.size() == 1) { iServices->OvSvcAddUndoItem(new IpePage(*iPage), QObject::tr("path object deletion")); iPage->erase(iIt); iPage->SetEdited(true); iCanvas->FinishOverlay(); return true; } else { // destroy this subpath delete iSP[iCur]; iSP.erase(iSP.begin() + iCur); iCur = 0; return false; } } void IpeEditPath::KeyPress(QKeyEvent *ev) { if (ev->key() == Qt::Key_Delete && iSP[iCur]->CanDelete()) { ev->accept(); if (iSP[iCur]->Delete() && DestroySubPath()) return; // this object doesn't exist anymore, must not call its methods iCanvas->UpdateOverlay(); Explain(); } else if (ev->key() == Qt::Key_O) { iOther = -iOther; iCanvas->UpdateOverlay(); Explain(); } else if (ev->key() == Qt::Key_Space) { Finish(); ev->accept(); } else ev->ignore(); } void IpeEditPath::Draw(QPaintEvent *, QPainter *qPainter) const { IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter); painter.Transform(iCanvas->CanvasTfm()); painter.Transform(iPath->Matrix()); for (uint i = 0; i < iSP.size(); ++i) { EditSubPath *sp = iSP[i]; sp->Draw(qPainter, painter); } DrawVertex(ECurrentVtx, qPainter, painter.Matrix() * iSP[iCur]->CurVertex()); if (iSP[iCur]->HasOther(iOther)) DrawVertex(EOtherVtx, qPainter, painter.Matrix() * iSP[iCur]->Other(iOther)); } void IpeEditPath::MouseMove(QMouseEvent *) { if (iMoving) { iSP[iCur]->Move(iInv * iCanvas->Pos()); iCanvas->UpdateOverlay(); } } void IpeEditPath::MouseRelease(QMouseEvent *ev) { if (iMoving) { MouseMove(ev); iMoving = false; Explain(); } } void IpeEditPath::MousePress(QMouseEvent *ev) { if (ev->button() == QMouseEvent::LeftButton) { if (iSP[iCur]->CanInsert(iOther)) iSP[iCur]->Insert(iOther, iInv * iCanvas->Pos()); } else if (ev->button() == QMouseEvent::MidButton || (ev->button() == QMouseEvent::RightButton && ev->state() & QMouseEvent::ShiftButton)) { FindVertex(iCanvas->Pos()); iMoving = true; } else if (ev->button() == QMouseEvent::RightButton) FindVertex(iCanvas->Pos()); iCanvas->UpdateOverlay(); Explain(); } // --------------------------------------------------------------------