/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace sdr; static sal_uInt16 GetPrevPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed) { if (nPnt>0) { nPnt--; } else { nPnt=nPntMax; if (bClosed) nPnt--; } return nPnt; } static sal_uInt16 GetNextPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed) { nPnt++; if (nPnt>nPntMax || (bClosed && nPnt>=nPntMax)) nPnt=0; return nPnt; } namespace { struct ImpSdrPathDragData : public SdrDragStatUserData { XPolygon aXP; // section of the original polygon bool bValid; // FALSE = too few points bool bClosed; // closed object? sal_uInt16 nPoly; // number of the polygon in the PolyPolygon sal_uInt16 nPnt; // number of point in the above polygon sal_uInt16 nPointCount; // number of points of the polygon bool bBegPnt; // dragged point is first point of a Polyline bool bEndPnt; // dragged point is finishing point of a Polyline sal_uInt16 nPrevPnt; // index of previous point sal_uInt16 nNextPnt; // index of next point bool bPrevIsBegPnt; // previous point is first point of a Polyline bool bNextIsEndPnt; // next point is first point of a Polyline sal_uInt16 nPrevPrevPnt; // index of point before previous point sal_uInt16 nNextNextPnt; // index of point after next point bool bControl; // point is a control point bool bIsNextControl; // point is a control point after a support point bool bPrevIsControl; // if nPnt is a support point: a control point comes before bool bNextIsControl; // if nPnt is a support point: a control point comes after sal_uInt16 nPrevPrevPnt0; sal_uInt16 nPrevPnt0; sal_uInt16 nPnt0; sal_uInt16 nNextPnt0; sal_uInt16 nNextNextPnt0; bool bEliminate; // delete point? (is set by MovDrag) bool mbMultiPointDrag; const XPolyPolygon maOrig; XPolyPolygon maMove; std::vector maHandles; public: ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag); void ResetPoly(const SdrPathObj& rPO); bool IsMultiPointDrag() const { return mbMultiPointDrag; } }; } ImpSdrPathDragData::ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag) : aXP(5) , bValid(false) , bClosed(false) , nPoly(0) , nPnt(0) , nPointCount(0) , bBegPnt(false) , bEndPnt(false) , nPrevPnt(0) , nNextPnt(0) , bPrevIsBegPnt(false) , bNextIsEndPnt(false) , nPrevPrevPnt(0) , nNextNextPnt(0) , bControl(false) , bIsNextControl(false) , bPrevIsControl(false) , bNextIsControl(false) , nPrevPrevPnt0(0) , nPrevPnt0(0) , nPnt0(0) , nNextPnt0(0) , nNextNextPnt0(0) , bEliminate(false) , mbMultiPointDrag(bMuPoDr) , maOrig(rPO.GetPathPoly()) , maHandles(0) { if(mbMultiPointDrag) { const SdrMarkView& rMarkView = *rDrag.GetView(); const SdrHdlList& rHdlList = rMarkView.GetHdlList(); const size_t nHdlCount = rHdlList.GetHdlCount(); const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr); for(size_t a = 0; a < nHdlCount; ++a) { SdrHdl* pTestHdl = rHdlList.GetHdl(a); if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject) { maHandles.push_back(pTestHdl); } } maMove = maOrig; bValid = true; } else { sal_uInt16 nPntMax = 0; // maximum index bValid=false; bClosed=rPO.IsClosed(); // closed object? nPoly=static_cast(rHdl.GetPolyNum()); // number of the polygon in the PolyPolygon nPnt=static_cast(rHdl.GetPointNum()); // number of points in the above polygon const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly)); nPointCount=aTmpXP.GetPointCount(); // number of point of the polygon if (nPointCount==0 || (bClosed && nPointCount==1)) return; // minimum of 1 points for Lines, minimum of 2 points for Polygon nPntMax=nPointCount-1; // maximum index bBegPnt=!bClosed && nPnt==0; // dragged point is first point of a Polyline bEndPnt=!bClosed && nPnt==nPntMax; // dragged point is finishing point of a Polyline if (bClosed && nPointCount<=3) { // if polygon is only a line bBegPnt=(nPointCount<3) || nPnt==0; bEndPnt=(nPointCount<3) || nPnt==nPntMax-1; } nPrevPnt=nPnt; // index of previous point nNextPnt=nPnt; // index of next point if (!bBegPnt) nPrevPnt=GetPrevPnt(nPnt,nPntMax,bClosed); if (!bEndPnt) nNextPnt=GetNextPnt(nPnt,nPntMax,bClosed); bPrevIsBegPnt=bBegPnt || (!bClosed && nPrevPnt==0); bNextIsEndPnt=bEndPnt || (!bClosed && nNextPnt==nPntMax); nPrevPrevPnt=nPnt; // index of point before previous point nNextNextPnt=nPnt; // index of point after next point if (!bPrevIsBegPnt) nPrevPrevPnt=GetPrevPnt(nPrevPnt,nPntMax,bClosed); if (!bNextIsEndPnt) nNextNextPnt=GetNextPnt(nNextPnt,nPntMax,bClosed); bControl=rHdl.IsPlusHdl(); // point is a control point bIsNextControl=false; // point is a control point after a support point bPrevIsControl=false; // if nPnt is a support point: a control point comes before bNextIsControl=false; // if nPnt is a support point: a control point comes after if (bControl) { bIsNextControl=!aTmpXP.IsControl(nPrevPnt); } else { bPrevIsControl=!bBegPnt && !bPrevIsBegPnt && aTmpXP.GetFlags(nPrevPnt)==PolyFlags::Control; bNextIsControl=!bEndPnt && !bNextIsEndPnt && aTmpXP.GetFlags(nNextPnt)==PolyFlags::Control; } nPrevPrevPnt0=nPrevPrevPnt; nPrevPnt0 =nPrevPnt; nPnt0 =nPnt; nNextPnt0 =nNextPnt; nNextNextPnt0=nNextNextPnt; nPrevPrevPnt=0; nPrevPnt=1; nPnt=2; nNextPnt=3; nNextNextPnt=4; bEliminate=false; ResetPoly(rPO); bValid=true; } } void ImpSdrPathDragData::ResetPoly(const SdrPathObj& rPO) { const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly)); aXP[0]=aTmpXP[nPrevPrevPnt0]; aXP.SetFlags(0,aTmpXP.GetFlags(nPrevPrevPnt0)); aXP[1]=aTmpXP[nPrevPnt0]; aXP.SetFlags(1,aTmpXP.GetFlags(nPrevPnt0)); aXP[2]=aTmpXP[nPnt0]; aXP.SetFlags(2,aTmpXP.GetFlags(nPnt0)); aXP[3]=aTmpXP[nNextPnt0]; aXP.SetFlags(3,aTmpXP.GetFlags(nNextPnt0)); aXP[4]=aTmpXP[nNextNextPnt0]; aXP.SetFlags(4,aTmpXP.GetFlags(nNextNextPnt0)); } namespace { struct ImpPathCreateUser : public SdrDragStatUserData { Point aBezControl0; Point aBezStart; Point aBezCtrl1; Point aBezCtrl2; Point aBezEnd; Point aCircStart; Point aCircEnd; Point aCircCenter; Point aLineStart; Point aLineEnd; Point aRectP1; Point aRectP2; Point aRectP3; tools::Long nCircRadius; Degree100 nCircStAngle; Degree100 nCircRelAngle; bool bBezier; bool bBezHasCtrl0; bool bCircle; bool bAngleSnap; bool bLine; bool bLine90; bool bRect; bool bMixedCreate; sal_uInt16 nBezierStartPoint; SdrObjKind eStartKind; SdrObjKind eCurrentKind; public: ImpPathCreateUser(): nCircRadius(0),nCircStAngle(0),nCircRelAngle(0), bBezier(false),bBezHasCtrl0(false),bCircle(false),bAngleSnap(false),bLine(false),bLine90(false),bRect(false), bMixedCreate(false),nBezierStartPoint(0),eStartKind(SdrObjKind::NONE),eCurrentKind(SdrObjKind::NONE) { } void ResetFormFlags() { bBezier=false; bCircle=false; bLine=false; bRect=false; } bool IsFormFlag() const { return bBezier || bCircle || bLine || bRect; } XPolygon GetFormPoly() const; void CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown); XPolygon GetBezierPoly() const; void CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView); XPolygon GetCirclePoly() const; void CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView); static Point CalcLine(const Point& rCsr, tools::Long nDirX, tools::Long nDirY, SdrView const * pView); XPolygon GetLinePoly() const; void CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView); XPolygon GetRectPoly() const; }; } XPolygon ImpPathCreateUser::GetFormPoly() const { if (bBezier) return GetBezierPoly(); if (bCircle) return GetCirclePoly(); if (bLine) return GetLinePoly(); if (bRect) return GetRectPoly(); return XPolygon(); } void ImpPathCreateUser::CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown) { aBezStart=rP1; aBezCtrl1=rP1+rDir; aBezCtrl2=rP2; // #i21479# // Also copy the end point when no end point is set yet if (!bMouseDown || (0 == aBezEnd.X() && 0 == aBezEnd.Y())) aBezEnd=rP2; bBezier=true; } XPolygon ImpPathCreateUser::GetBezierPoly() const { XPolygon aXP(4); aXP[0]=aBezStart; aXP.SetFlags(0,PolyFlags::Smooth); aXP[1]=aBezCtrl1; aXP.SetFlags(1,PolyFlags::Control); aXP[2]=aBezCtrl2; aXP.SetFlags(2,PolyFlags::Control); aXP[3]=aBezEnd; return aXP; } void ImpPathCreateUser::CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView) { Degree100 nTangAngle=GetAngle(rDir); aCircStart=rP1; aCircEnd=rP2; aCircCenter=rP1; tools::Long dx=rP2.X()-rP1.X(); tools::Long dy=rP2.Y()-rP1.Y(); Degree100 dAngle=GetAngle(Point(dx,dy))-nTangAngle; dAngle=NormAngle36000(dAngle); Degree100 nTmpAngle=NormAngle36000(9000_deg100-dAngle); bool bRet=nTmpAngle!=9000_deg100 && nTmpAngle!=27000_deg100; tools::Long nRad=0; if (bRet) { double cs = cos(toRadians(nTmpAngle)); double nR=static_cast(GetLen(Point(dx,dy)))/cs/2; nRad = std::abs(basegfx::fround(nR)); } if (dAngle<18000_deg100) { nCircStAngle=NormAngle36000(nTangAngle-9000_deg100); nCircRelAngle=NormAngle36000(2_deg100*dAngle); aCircCenter.AdjustX(basegfx::fround(nRad * cos(toRadians(nTangAngle + 9000_deg100)))); aCircCenter.AdjustY(basegfx::fround(nRad * -sin(toRadians(nTangAngle + 9000_deg100)))); } else { nCircStAngle=NormAngle36000(nTangAngle+9000_deg100); nCircRelAngle=-NormAngle36000(36000_deg100-2_deg100*dAngle); aCircCenter.AdjustX(basegfx::fround(nRad * cos(toRadians(nTangAngle - 9000_deg100)))); aCircCenter.AdjustY(basegfx::fround(nRad * -sin(toRadians(nTangAngle - 9000_deg100)))); } bAngleSnap=pView!=nullptr && pView->IsAngleSnapEnabled(); if (bAngleSnap) { Degree100 nSA=pView->GetSnapAngle(); if (nSA) { // angle snapping bool bNeg=nCircRelAngle<0_deg100; if (bNeg) nCircRelAngle=-nCircRelAngle; nCircRelAngle+=nSA/2_deg100; nCircRelAngle/=nSA; nCircRelAngle*=nSA; nCircRelAngle=NormAngle36000(nCircRelAngle); if (bNeg) nCircRelAngle=-nCircRelAngle; } } nCircRadius=nRad; if (nRad==0 || abs(nCircRelAngle).get()<5) bRet=false; bCircle=bRet; } XPolygon ImpPathCreateUser::GetCirclePoly() const { if (nCircRelAngle>=0_deg100) { XPolygon aXP(aCircCenter,nCircRadius,nCircRadius, nCircStAngle, nCircStAngle+nCircRelAngle,false); aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth); if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd; return aXP; } else { XPolygon aXP(aCircCenter,nCircRadius,nCircRadius, NormAngle36000(nCircStAngle+nCircRelAngle), nCircStAngle,false); sal_uInt16 nCount=aXP.GetPointCount(); for (sal_uInt16 nNum=nCount/2; nNum>0;) { nNum--; // reverse XPoly's order of points sal_uInt16 n2=nCount-nNum-1; Point aPt(aXP[nNum]); aXP[nNum]=aXP[n2]; aXP[n2]=aPt; } aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth); if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd; return aXP; } } Point ImpPathCreateUser::CalcLine(const Point& aCsr, tools::Long nDirX, tools::Long nDirY, SdrView const * pView) { tools::Long x=aCsr.X(); tools::Long y=aCsr.Y(); bool bHLin=nDirY==0; bool bVLin=nDirX==0; if (bHLin) y=0; else if (bVLin) x=0; else { tools::Long x1=BigMulDiv(y,nDirX,nDirY); tools::Long y1=y; tools::Long x2=x; tools::Long y2=BigMulDiv(x,nDirY,nDirX); tools::Long l1=std::abs(x1)+std::abs(y1); tools::Long l2=std::abs(x2)+std::abs(y2); if ((l1<=l2) != (pView!=nullptr && pView->IsBigOrtho())) { x=x1; y=y1; } else { x=x2; y=y2; } } return Point(x,y); } void ImpPathCreateUser::CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView) { aLineStart=rP1; aLineEnd=rP2; bLine90=false; if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bLine=false; return; } Point aTmpPt(rP2-rP1); tools::Long nDirX=rDir.X(); tools::Long nDirY=rDir.Y(); Point aP1(CalcLine(aTmpPt, nDirX, nDirY,pView)); aP1-=aTmpPt; tools::Long nQ1=std::abs(aP1.X())+std::abs(aP1.Y()); Point aP2(CalcLine(aTmpPt, nDirY,-nDirX,pView)); aP2-=aTmpPt; tools::Long nQ2=std::abs(aP2.X())+std::abs(aP2.Y()); if (pView!=nullptr && pView->IsOrtho()) nQ1=0; // Ortho turns off at right angle bLine90=nQ1>2*nQ2; if (!bLine90) { // smooth transition aLineEnd+=aP1; } else { // rectangular transition aLineEnd+=aP2; } bLine=true; } XPolygon ImpPathCreateUser::GetLinePoly() const { XPolygon aXP(2); aXP[0]=aLineStart; if (!bLine90) aXP.SetFlags(0,PolyFlags::Smooth); aXP[1]=aLineEnd; return aXP; } void ImpPathCreateUser::CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView) { aRectP1=rP1; aRectP2=rP1; aRectP3=rP2; if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bRect=false; return; } Point aTmpPt(rP2-rP1); tools::Long nDirX=rDir.X(); tools::Long nDirY=rDir.Y(); tools::Long x=aTmpPt.X(); tools::Long y=aTmpPt.Y(); bool bHLin=nDirY==0; bool bVLin=nDirX==0; if (bHLin) y=0; else if (bVLin) x=0; else { y=BigMulDiv(x,nDirY,nDirX); tools::Long nHypLen=aTmpPt.Y()-y; Degree100 nTangAngle=-GetAngle(rDir); // sin=g/h, g=h*sin double a = toRadians(nTangAngle); double sn=sin(a); double cs=cos(a); double nGKathLen=nHypLen*sn; y += basegfx::fround(nGKathLen * sn); x += basegfx::fround(nGKathLen * cs); } aRectP2.AdjustX(x ); aRectP2.AdjustY(y ); if (pView!=nullptr && pView->IsOrtho()) { tools::Long dx1=aRectP2.X()-aRectP1.X(); tools::Long dx1a=std::abs(dx1); tools::Long dy1=aRectP2.Y()-aRectP1.Y(); tools::Long dy1a=std::abs(dy1); tools::Long dx2=aRectP3.X()-aRectP2.X(); tools::Long dx2a=std::abs(dx2); tools::Long dy2=aRectP3.Y()-aRectP2.Y(); tools::Long dy2a=std::abs(dy2); bool b1MoreThan2=dx1a+dy1a>dx2a+dy2a; if (b1MoreThan2 != pView->IsBigOrtho()) { tools::Long xtemp=dy2a-dx1a; if (dx1<0) xtemp=-xtemp; tools::Long ytemp=dx2a-dy1a; if (dy1<0) ytemp=-ytemp; aRectP2.AdjustX(xtemp ); aRectP2.AdjustY(ytemp ); aRectP3.AdjustX(xtemp ); aRectP3.AdjustY(ytemp ); } else { tools::Long xtemp=dy1a-dx2a; if (dx2<0) xtemp=-xtemp; tools::Long ytemp=dx1a-dy2a; if (dy2<0) ytemp=-ytemp; aRectP3.AdjustX(xtemp ); aRectP3.AdjustY(ytemp ); } } bRect=true; } XPolygon ImpPathCreateUser::GetRectPoly() const { XPolygon aXP(3); aXP[0]=aRectP1; aXP.SetFlags(0,PolyFlags::Smooth); aXP[1]=aRectP2; if (aRectP3!=aRectP2) aXP[2]=aRectP3; return aXP; } class ImpPathForDragAndCreate { SdrPathObj& mrSdrPathObject; XPolyPolygon aPathPolygon; SdrObjKind meObjectKind; std::unique_ptr mpSdrPathDragData; bool mbCreating; public: explicit ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject); // drag stuff bool beginPathDrag( SdrDragStat const & rDrag ) const; bool movePathDrag( SdrDragStat& rDrag ) const; bool endPathDrag( SdrDragStat const & rDrag ); OUString getSpecialDragComment(const SdrDragStat& rDrag) const; basegfx::B2DPolyPolygon getSpecialDragPoly(const SdrDragStat& rDrag) const; // create stuff void BegCreate(SdrDragStat& rStat); bool MovCreate(SdrDragStat& rStat); bool EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd); bool BckCreate(SdrDragStat const & rStat); void BrkCreate(SdrDragStat& rStat); PointerStyle GetCreatePointer() const; // helping stuff static bool IsClosed(SdrObjKind eKind) { return eKind==SdrObjKind::Polygon || eKind==SdrObjKind::PathPoly || eKind==SdrObjKind::PathFill || eKind==SdrObjKind::FreehandFill; } static bool IsFreeHand(SdrObjKind eKind) { return eKind==SdrObjKind::FreehandLine || eKind==SdrObjKind::FreehandFill; } static bool IsBezier(SdrObjKind eKind) { return eKind==SdrObjKind::PathLine || eKind==SdrObjKind::PathFill; } bool IsCreating() const { return mbCreating; } // get the polygon basegfx::B2DPolyPolygon TakeObjectPolyPolygon(const SdrDragStat& rDrag) const; static basegfx::B2DPolyPolygon TakeDragPolyPolygon(const SdrDragStat& rDrag); basegfx::B2DPolyPolygon getModifiedPolyPolygon() const { return aPathPolygon.getB2DPolyPolygon(); } }; ImpPathForDragAndCreate::ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject) : mrSdrPathObject(rSdrPathObject), aPathPolygon(rSdrPathObject.GetPathPoly()), meObjectKind(mrSdrPathObject.meKind), mbCreating(false) { } bool ImpPathForDragAndCreate::beginPathDrag( SdrDragStat const & rDrag ) const { const SdrHdl* pHdl=rDrag.GetHdl(); if(!pHdl) return false; bool bMultiPointDrag(true); if(aPathPolygon[static_cast(pHdl->GetPolyNum())].IsControl(static_cast(pHdl->GetPointNum()))) bMultiPointDrag = false; if(bMultiPointDrag) { const SdrMarkView& rMarkView = *rDrag.GetView(); const SdrHdlList& rHdlList = rMarkView.GetHdlList(); const size_t nHdlCount = rHdlList.GetHdlCount(); const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr); sal_uInt32 nSelectedPoints(0); for(size_t a = 0; a < nHdlCount; ++a) { SdrHdl* pTestHdl = rHdlList.GetHdl(a); if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject) { nSelectedPoints++; } } if(nSelectedPoints <= 1) bMultiPointDrag = false; } const_cast(this)->mpSdrPathDragData.reset( new ImpSdrPathDragData(mrSdrPathObject,*pHdl,bMultiPointDrag,rDrag) ); if(!mpSdrPathDragData || !mpSdrPathDragData->bValid) { OSL_FAIL("ImpPathForDragAndCreate::BegDrag(): ImpSdrPathDragData is invalid."); const_cast(this)->mpSdrPathDragData.reset(); return false; } return true; } bool ImpPathForDragAndCreate::movePathDrag( SdrDragStat& rDrag ) const { if(!mpSdrPathDragData || !mpSdrPathDragData->bValid) { OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid."); return false; } if(mpSdrPathDragData->IsMultiPointDrag()) { Point aDelta(rDrag.GetNow() - rDrag.GetStart()); if(aDelta.X() || aDelta.Y()) { for(SdrHdl* pHandle : mpSdrPathDragData->maHandles) { const sal_uInt16 nPolyIndex(static_cast(pHandle->GetPolyNum())); const sal_uInt16 nPointIndex(static_cast(pHandle->GetPointNum())); const XPolygon& rOrig = mpSdrPathDragData->maOrig[nPolyIndex]; XPolygon& rMove = mpSdrPathDragData->maMove[nPolyIndex]; const sal_uInt16 nPointCount(rOrig.GetPointCount()); bool bClosed(rOrig[0] == rOrig[nPointCount-1]); // move point itself rMove[nPointIndex] = rOrig[nPointIndex] + aDelta; // when point is first and poly closed, move close point, too. if(nPointCount > 0 && !nPointIndex && bClosed) { rMove[nPointCount - 1] = rOrig[nPointCount - 1] + aDelta; // when moving the last point it may be necessary to move the // control point in front of this one, too. if(nPointCount > 1 && rOrig.IsControl(nPointCount - 2)) rMove[nPointCount - 2] = rOrig[nPointCount - 2] + aDelta; } // is a control point before this? if(nPointIndex > 0 && rOrig.IsControl(nPointIndex - 1)) { // Yes, move it, too rMove[nPointIndex - 1] = rOrig[nPointIndex - 1] + aDelta; } // is a control point after this? if(nPointIndex + 1 < nPointCount && rOrig.IsControl(nPointIndex + 1)) { // Yes, move it, too rMove[nPointIndex + 1] = rOrig[nPointIndex + 1] + aDelta; } } } } else { mpSdrPathDragData->ResetPoly(mrSdrPathObject); // copy certain data locally to use less code and have faster access times bool bClosed =mpSdrPathDragData->bClosed ; // closed object? sal_uInt16 nPnt =mpSdrPathDragData->nPnt ; // number of point in the above polygon bool bBegPnt =mpSdrPathDragData->bBegPnt ; // dragged point is first point of a Polyline bool bEndPnt =mpSdrPathDragData->bEndPnt ; // dragged point is last point of a Polyline sal_uInt16 nPrevPnt =mpSdrPathDragData->nPrevPnt ; // index of previous point sal_uInt16 nNextPnt =mpSdrPathDragData->nNextPnt ; // index of next point bool bPrevIsBegPnt =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline bool bNextIsEndPnt =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline sal_uInt16 nPrevPrevPnt =mpSdrPathDragData->nPrevPrevPnt ; // index of the point before the previous point sal_uInt16 nNextNextPnt =mpSdrPathDragData->nNextNextPnt ; // index if the point after the next point bool bControl =mpSdrPathDragData->bControl ; // point is a control point bool bIsNextControl =mpSdrPathDragData->bIsNextControl; // point is a control point after a support point bool bPrevIsControl =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before bool bNextIsControl =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after // Ortho for lines/polygons: keep angle if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho()) { bool bBigOrtho=rDrag.GetView()->IsBigOrtho(); Point aPos(rDrag.GetNow()); // current position Point aPnt(mpSdrPathDragData->aXP[nPnt]); // the dragged point sal_uInt16 nPnt1=0xFFFF,nPnt2=0xFFFF; // its neighboring points Point aNewPos1,aNewPos2; // new alternative for aPos bool bPnt1 = false, bPnt2 = false; // are these valid alternatives? if (!bClosed && mpSdrPathDragData->nPointCount>=2) { // minimum of 2 points for lines if (!bBegPnt) nPnt1=nPrevPnt; if (!bEndPnt) nPnt2=nNextPnt; } if (bClosed && mpSdrPathDragData->nPointCount>=3) { // minimum of 3 points for polygon nPnt1=nPrevPnt; nPnt2=nNextPnt; } if (nPnt1!=0xFFFF && !bPrevIsControl) { Point aPnt1=mpSdrPathDragData->aXP[nPnt1]; tools::Long ndx0=aPnt.X()-aPnt1.X(); tools::Long ndy0=aPnt.Y()-aPnt1.Y(); bool bHLin=ndy0==0; bool bVLin=ndx0==0; if (!bHLin || !bVLin) { tools::Long ndx=aPos.X()-aPnt1.X(); tools::Long ndy=aPos.Y()-aPnt1.Y(); bPnt1=true; double nXFact=0; if (!bVLin) nXFact=static_cast(ndx)/static_cast(ndx0); double nYFact=0; if (!bHLin) nYFact=static_cast(ndy)/static_cast(ndy0); bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho); bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho); if (bHor) ndy=tools::Long(ndy0*nXFact); if (bVer) ndx=tools::Long(ndx0*nYFact); aNewPos1=aPnt1; aNewPos1.AdjustX(ndx ); aNewPos1.AdjustY(ndy ); } } if (nPnt2!=0xFFFF && !bNextIsControl) { Point aPnt2=mpSdrPathDragData->aXP[nPnt2]; tools::Long ndx0=aPnt.X()-aPnt2.X(); tools::Long ndy0=aPnt.Y()-aPnt2.Y(); bool bHLin=ndy0==0; bool bVLin=ndx0==0; if (!bHLin || !bVLin) { tools::Long ndx=aPos.X()-aPnt2.X(); tools::Long ndy=aPos.Y()-aPnt2.Y(); bPnt2=true; double nXFact=0; if (!bVLin) nXFact=static_cast(ndx)/static_cast(ndx0); double nYFact=0; if (!bHLin) nYFact=static_cast(ndy)/static_cast(ndy0); bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho); bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho); if (bHor) ndy=tools::Long(ndy0*nXFact); if (bVer) ndx=tools::Long(ndx0*nYFact); aNewPos2=aPnt2; aNewPos2.AdjustX(ndx ); aNewPos2.AdjustY(ndy ); } } if (bPnt1 && bPnt2) { // both alternatives exist (and compete) BigInt nX1(aNewPos1.X()-aPos.X()); nX1*=nX1; BigInt nY1(aNewPos1.Y()-aPos.Y()); nY1*=nY1; BigInt nX2(aNewPos2.X()-aPos.X()); nX2*=nX2; BigInt nY2(aNewPos2.Y()-aPos.Y()); nY2*=nY2; nX1+=nY1; // correction distance to square nX2+=nY2; // correction distance to square // let the alternative that allows fewer correction win if (nX1IsEliminatePolyPoints() && !bBegPnt && !bEndPnt && !bPrevIsControl && !bNextIsControl) { Point aPt(mpSdrPathDragData->aXP[nNextPnt]); aPt-=rDrag.GetNow(); Degree100 nAngle1=GetAngle(aPt); aPt=rDrag.GetNow(); aPt-=mpSdrPathDragData->aXP[nPrevPnt]; Degree100 nAngle2=GetAngle(aPt); Degree100 nDiff=nAngle1-nAngle2; nDiff=abs(nDiff); mpSdrPathDragData->bEliminate=nDiff<=rDrag.GetView()->GetEliminatePolyPointLimitAngle(); if (mpSdrPathDragData->bEliminate) { // adapt position, Smooth is true for the ends aPt=mpSdrPathDragData->aXP[nNextPnt]; aPt+=mpSdrPathDragData->aXP[nPrevPnt]; aPt/=2; rDrag.SetNow(aPt); } } // we dragged by this distance Point aDiff(rDrag.GetNow()); aDiff-=mpSdrPathDragData->aXP[nPnt]; /* There are 8 possible cases: X 1. A control point neither on the left nor on the right. o--X--o 2. There are control points on the left and the right, we are dragging a support point. o--X 3. There is a control point on the left, we are dragging a support point. X--o 4. There is a control point on the right, we are dragging a support point. x--O--o 5. There are control points on the left and the right, we are dragging the left one. x--O 6. There is a control point on the left, we are dragging it. o--O--x 7. There are control points on the left and the right, we are dragging the right one. O--x 8. There is a control point on the right, we are dragging it. Note: modifying a line (not a curve!) might create a curve on the other end of the line if Smooth is set there (with control points aligned to line). */ mpSdrPathDragData->aXP[nPnt]+=aDiff; // now check symmetric plus handles if (bControl) { // cases 5,6,7,8 sal_uInt16 nSt; // the associated support point sal_uInt16 nFix; // the opposing control point if (bIsNextControl) { // if the next one is a control point, the on before has to be a support point nSt=nPrevPnt; nFix=nPrevPrevPnt; } else { nSt=nNextPnt; nFix=nNextNextPnt; } if (mpSdrPathDragData->aXP.IsSmooth(nSt)) { mpSdrPathDragData->aXP.CalcSmoothJoin(nSt,nPnt,nFix); } } if (!bControl) { // Cases 1,2,3,4. In case 1, nothing happens; in cases 3 and 4, there is more following below. // move both control points if (bPrevIsControl) mpSdrPathDragData->aXP[nPrevPnt]+=aDiff; if (bNextIsControl) mpSdrPathDragData->aXP[nNextPnt]+=aDiff; // align control point to line, if appropriate if (mpSdrPathDragData->aXP.IsSmooth(nPnt)) { if (bPrevIsControl && !bNextIsControl && !bEndPnt) { // case 3 mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nNextPnt,nPrevPnt); } if (bNextIsControl && !bPrevIsControl && !bBegPnt) { // case 4 mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nPrevPnt,nNextPnt); } } // Now check the other ends of the line (nPnt+-1). If there is a // curve (IsControl(nPnt+-2)) with SmoothJoin (nPnt+-1), the // associated control point (nPnt+-2) has to be adapted. if (!bBegPnt && !bPrevIsControl && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsSmooth(nPrevPnt)) { if (mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) { mpSdrPathDragData->aXP.CalcSmoothJoin(nPrevPnt,nPnt,nPrevPrevPnt); } } if (!bEndPnt && !bNextIsControl && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsSmooth(nNextPnt)) { if (mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) { mpSdrPathDragData->aXP.CalcSmoothJoin(nNextPnt,nPnt,nNextNextPnt); } } } } return true; } bool ImpPathForDragAndCreate::endPathDrag(SdrDragStat const & rDrag) { Point aLinePt1; Point aLinePt2; bool bLineGlueMirror(SdrObjKind::Line == meObjectKind); if (bLineGlueMirror) { XPolygon& rXP=aPathPolygon[0]; aLinePt1=rXP[0]; aLinePt2=rXP[1]; } if(!mpSdrPathDragData || !mpSdrPathDragData->bValid) { OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid."); return false; } if(mpSdrPathDragData->IsMultiPointDrag()) { aPathPolygon = mpSdrPathDragData->maMove; } else { const SdrHdl* pHdl=rDrag.GetHdl(); // reference the polygon XPolygon& rXP=aPathPolygon[static_cast(pHdl->GetPolyNum())]; // the 5 points that might have changed if (!mpSdrPathDragData->bPrevIsBegPnt) rXP[mpSdrPathDragData->nPrevPrevPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPrevPnt]; if (!mpSdrPathDragData->bNextIsEndPnt) rXP[mpSdrPathDragData->nNextNextPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nNextNextPnt]; if (!mpSdrPathDragData->bBegPnt) rXP[mpSdrPathDragData->nPrevPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPnt]; if (!mpSdrPathDragData->bEndPnt) rXP[mpSdrPathDragData->nNextPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nNextPnt]; rXP[mpSdrPathDragData->nPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nPnt]; // for closed objects: last point has to be equal to first point if (mpSdrPathDragData->bClosed) rXP[rXP.GetPointCount()-1]=rXP[0]; if (mpSdrPathDragData->bEliminate) { basegfx::B2DPolyPolygon aTempPolyPolygon(aPathPolygon.getB2DPolyPolygon()); sal_uInt32 nPoly,nPnt; if(PolyPolygonEditor::GetRelativePolyPoint(aTempPolyPolygon, rDrag.GetHdl()->GetSourceHdlNum(), nPoly, nPnt)) { basegfx::B2DPolygon aCandidate(aTempPolyPolygon.getB2DPolygon(nPoly)); aCandidate.remove(nPnt); if(aCandidate.count() < 2) { aTempPolyPolygon.remove(nPoly); } else { aTempPolyPolygon.setB2DPolygon(nPoly, aCandidate); } } aPathPolygon = XPolyPolygon(aTempPolyPolygon); } // adapt angle for text beneath a simple line if (bLineGlueMirror) { Point aLinePt1_(aPathPolygon[0][0]); Point aLinePt2_(aPathPolygon[0][1]); bool bXMirr=(aLinePt1_.X()>aLinePt2_.X())!=(aLinePt1.X()>aLinePt2.X()); bool bYMirr=(aLinePt1_.Y()>aLinePt2_.Y())!=(aLinePt1.Y()>aLinePt2.Y()); if (bXMirr || bYMirr) { Point aRef1(mrSdrPathObject.GetSnapRect().Center()); if (bXMirr) { Point aRef2(aRef1); aRef2.AdjustY( 1 ); mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2); } if (bYMirr) { Point aRef2(aRef1); aRef2.AdjustX( 1 ); mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2); } } } } mpSdrPathDragData.reset(); return true; } OUString ImpPathForDragAndCreate::getSpecialDragComment(const SdrDragStat& rDrag) const { OUString aStr; const SdrHdl* pHdl = rDrag.GetHdl(); const bool bCreateComment(rDrag.GetView() && &mrSdrPathObject == rDrag.GetView()->GetCreateObj()); if(bCreateComment && rDrag.GetUser()) { // #i103058# re-add old creation comment mode const ImpPathCreateUser* pU = static_cast(rDrag.GetUser()); const SdrObjKind eOriginalKind(meObjectKind); mrSdrPathObject.meKind = pU->eCurrentKind; aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewCreateObj); mrSdrPathObject.meKind = eOriginalKind; Point aPrev(rDrag.GetPrev()); Point aNow(rDrag.GetNow()); if(pU->bLine) aNow = pU->aLineEnd; aNow -= aPrev; aStr += " ("; if(pU->bCircle) { aStr += SdrModel::GetAngleString(abs(pU->nCircRelAngle)) + " r=" + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(pU->nCircRadius, true); } aStr += "dx=" + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X(), true) + " dy=" + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y(), true); if(!IsFreeHand(meObjectKind)) { sal_Int32 nLen(GetLen(aNow)); Degree100 nAngle(GetAngle(aNow)); aStr += " l=" + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true) + " " + SdrModel::GetAngleString(nAngle); } aStr += ")"; } else if(!pHdl) { // #i103058# fallback when no model and/or Handle, both needed // for else-path aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_DragPathObj); } else { // #i103058# standard for modification; model and handle needed ImpSdrPathDragData* pDragData = mpSdrPathDragData.get(); if(!pDragData) { // getSpecialDragComment is also used from create, so fallback to GetUser() // when mpSdrPathDragData is not set pDragData = static_cast(rDrag.GetUser()); } if(!pDragData) { OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid."); return OUString(); } if(!pDragData->IsMultiPointDrag() && pDragData->bEliminate) { // point of ... aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewMarkedPoint); // delete %O OUString aStr2(SvxResId(STR_EditDelete)); // UNICODE: delete point of ... aStr2 = aStr2.replaceFirst("%1", aStr); return aStr2; } // dx=0.00 dy=0.00 -- both sides bezier // dx=0.00 dy=0.00 l=0.00 0.00\302\260 -- one bezier/lever on one side, a start, or an ending // dx=0.00 dy=0.00 l=0.00 0.00\302\260 / l=0.00 0.00\302\260 -- in between Point aBeg(rDrag.GetStart()); Point aNow(rDrag.GetNow()); aStr.clear(); aStr += "dx=" + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X() - aBeg.X(), true) + " dy=" + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y() - aBeg.Y(), true); if(!pDragData->IsMultiPointDrag()) { sal_uInt16 nPntNum(static_cast(pHdl->GetPointNum())); const XPolygon& rXPoly = aPathPolygon[static_cast(rDrag.GetHdl()->GetPolyNum())]; sal_uInt16 nPointCount(rXPoly.GetPointCount()); bool bClose(IsClosed(meObjectKind)); if(bClose) nPointCount--; if(pHdl->IsPlusHdl()) { // lever sal_uInt16 nRef(nPntNum); if(rXPoly.IsControl(nPntNum + 1)) nRef--; else nRef++; aNow -= rXPoly[nRef]; sal_Int32 nLen(GetLen(aNow)); Degree100 nAngle(GetAngle(aNow)); aStr += " l=" + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true) + " " + SdrModel::GetAngleString(nAngle); } else if(nPointCount > 1) { sal_uInt16 nPntMax(nPointCount - 1); bool bIsClosed(IsClosed(meObjectKind)); bool bPt1(nPntNum > 0); bool bPt2(nPntNum < nPntMax); if(bIsClosed && nPointCount > 2) { bPt1 = true; bPt2 = true; } sal_uInt16 nPt1,nPt2; if(nPntNum > 0) nPt1 = nPntNum - 1; else nPt1 = nPntMax; if(nPntNum < nPntMax) nPt2 = nPntNum + 1; else nPt2 = 0; if(bPt1 && rXPoly.IsControl(nPt1)) bPt1 = false; // don't display if(bPt2 && rXPoly.IsControl(nPt2)) bPt2 = false; // of bezier data if(bPt1) { Point aPt(aNow); aPt -= rXPoly[nPt1]; sal_Int32 nLen(GetLen(aPt)); Degree100 nAngle(GetAngle(aPt)); aStr += " l=" + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true) + " " + SdrModel::GetAngleString(nAngle); } if(bPt2) { if(bPt1) aStr += " / "; else aStr += " "; Point aPt(aNow); aPt -= rXPoly[nPt2]; sal_Int32 nLen(GetLen(aPt)); Degree100 nAngle(GetAngle(aPt)); aStr += "l=" + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true) + " " + SdrModel::GetAngleString(nAngle); } } } } return aStr; } basegfx::B2DPolyPolygon ImpPathForDragAndCreate::getSpecialDragPoly(const SdrDragStat& rDrag) const { if(!mpSdrPathDragData || !mpSdrPathDragData->bValid) { OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid."); return basegfx::B2DPolyPolygon(); } XPolyPolygon aRetval; if(mpSdrPathDragData->IsMultiPointDrag()) { aRetval.Insert(mpSdrPathDragData->maMove); } else { const XPolygon& rXP=aPathPolygon[static_cast(rDrag.GetHdl()->GetPolyNum())]; if (rXP.GetPointCount()<=2) { XPolygon aXPoly(rXP); aXPoly[static_cast(rDrag.GetHdl()->GetPointNum())]=rDrag.GetNow(); aRetval.Insert(std::move(aXPoly)); return aRetval.getB2DPolyPolygon(); } // copy certain data locally to use less code and have faster access times bool bClosed =mpSdrPathDragData->bClosed ; // closed object? sal_uInt16 nPointCount = mpSdrPathDragData->nPointCount; // number of points sal_uInt16 nPnt =mpSdrPathDragData->nPnt ; // number of points in the polygon bool bBegPnt =mpSdrPathDragData->bBegPnt ; // dragged point is the first point of a Polyline bool bEndPnt =mpSdrPathDragData->bEndPnt ; // dragged point is the last point of a Polyline sal_uInt16 nPrevPnt =mpSdrPathDragData->nPrevPnt ; // index of the previous point sal_uInt16 nNextPnt =mpSdrPathDragData->nNextPnt ; // index of the next point bool bPrevIsBegPnt =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline bool bNextIsEndPnt =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline sal_uInt16 nPrevPrevPnt =mpSdrPathDragData->nPrevPrevPnt ; // index of the point before the previous point sal_uInt16 nNextNextPnt =mpSdrPathDragData->nNextNextPnt ; // index of the point after the last point bool bControl =mpSdrPathDragData->bControl ; // point is a control point bool bIsNextControl =mpSdrPathDragData->bIsNextControl; //point is a control point after a support point bool bPrevIsControl =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before bool bNextIsControl =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after XPolygon aXPoly(mpSdrPathDragData->aXP); XPolygon aLine1(2); XPolygon aLine2(2); XPolygon aLine3(2); XPolygon aLine4(2); if (bControl) { aLine1[1]=mpSdrPathDragData->aXP[nPnt]; if (bIsNextControl) { // is this a control point after the support point? aLine1[0]=mpSdrPathDragData->aXP[nPrevPnt]; aLine2[0]=mpSdrPathDragData->aXP[nNextNextPnt]; aLine2[1]=mpSdrPathDragData->aXP[nNextPnt]; if (mpSdrPathDragData->aXP.IsSmooth(nPrevPnt) && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) { aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control); aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal); // leverage lines for the opposing curve segment aLine3[0]=mpSdrPathDragData->aXP[nPrevPnt]; aLine3[1]=mpSdrPathDragData->aXP[nPrevPrevPnt]; aLine4[0]=rXP[mpSdrPathDragData->nPrevPrevPnt0-2]; aLine4[1]=rXP[mpSdrPathDragData->nPrevPrevPnt0-1]; } else { aXPoly.Remove(0,1); } } else { // else this is a control point before a support point aLine1[0]=mpSdrPathDragData->aXP[nNextPnt]; aLine2[0]=mpSdrPathDragData->aXP[nPrevPrevPnt]; aLine2[1]=mpSdrPathDragData->aXP[nPrevPnt]; if (mpSdrPathDragData->aXP.IsSmooth(nNextPnt) && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) { aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control); aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal); // leverage lines for the opposing curve segment aLine3[0]=mpSdrPathDragData->aXP[nNextPnt]; aLine3[1]=mpSdrPathDragData->aXP[nNextNextPnt]; aLine4[0]=rXP[mpSdrPathDragData->nNextNextPnt0+2]; aLine4[1]=rXP[mpSdrPathDragData->nNextNextPnt0+1]; } else { aXPoly.Remove(aXPoly.GetPointCount()-1,1); } } } else { // else is not a control point if (mpSdrPathDragData->bEliminate) { aXPoly.Remove(2,1); } if (bPrevIsControl) aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Normal); else if (!bBegPnt && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) { aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control); aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal); } else { aXPoly.Remove(0,1); if (bBegPnt) aXPoly.Remove(0,1); } if (bNextIsControl) aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Normal); else if (!bEndPnt && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) { aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control); aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal); } else { aXPoly.Remove(aXPoly.GetPointCount()-1,1); if (bEndPnt) aXPoly.Remove(aXPoly.GetPointCount()-1,1); } if (bClosed) { // "pear problem": 2 lines, 1 curve, everything smoothed, a point between both lines is dragged if (aXPoly.GetPointCount()>nPointCount && aXPoly.IsControl(1)) { sal_uInt16 a=aXPoly.GetPointCount(); aXPoly[a-2]=aXPoly[2]; aXPoly.SetFlags(a-2,aXPoly.GetFlags(2)); aXPoly[a-1]=aXPoly[3]; aXPoly.SetFlags(a-1,aXPoly.GetFlags(3)); aXPoly.Remove(0,3); } } } aRetval.Insert(std::move(aXPoly)); if (aLine1.GetPointCount()>1) aRetval.Insert(std::move(aLine1)); if (aLine2.GetPointCount()>1) aRetval.Insert(std::move(aLine2)); if (aLine3.GetPointCount()>1) aRetval.Insert(std::move(aLine3)); if (aLine4.GetPointCount()>1) aRetval.Insert(std::move(aLine4)); } return aRetval.getB2DPolyPolygon(); } void ImpPathForDragAndCreate::BegCreate(SdrDragStat& rStat) { bool bFreeHand(IsFreeHand(meObjectKind)); rStat.SetNoSnap(bFreeHand); rStat.SetOrtho8Possible(); aPathPolygon.Clear(); mbCreating=true; bool bMakeStartPoint = true; SdrView* pView=rStat.GetView(); if (pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface() && (meObjectKind==SdrObjKind::Polygon || meObjectKind==SdrObjKind::PolyLine || meObjectKind==SdrObjKind::PathLine || meObjectKind==SdrObjKind::PathFill)) { bMakeStartPoint = false; } aPathPolygon.Insert(XPolygon()); aPathPolygon[0][0]=rStat.GetStart(); if (bMakeStartPoint) { aPathPolygon[0][1]=rStat.GetNow(); } std::unique_ptr pU(new ImpPathCreateUser); pU->eStartKind=meObjectKind; pU->eCurrentKind=meObjectKind; rStat.SetUser(std::move(pU)); } bool ImpPathForDragAndCreate::MovCreate(SdrDragStat& rStat) { ImpPathCreateUser* pU=static_cast(rStat.GetUser()); SdrView* pView=rStat.GetView(); XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1]; if (pView!=nullptr && pView->IsCreateMode()) { // switch to different CreateTool, if appropriate SdrObjKind nIdent; SdrInventor nInvent; pView->TakeCurrentObj(nIdent,nInvent); if (nInvent==SdrInventor::Default && pU->eCurrentKind != nIdent) { SdrObjKind eNewKind = nIdent; switch (eNewKind) { case SdrObjKind::CircleArc: case SdrObjKind::CircleOrEllipse: case SdrObjKind::CircleCut: case SdrObjKind::CircleSection: eNewKind=SdrObjKind::CircleArc; [[fallthrough]]; case SdrObjKind::Rectangle: case SdrObjKind::Line: case SdrObjKind::PolyLine: case SdrObjKind::Polygon: case SdrObjKind::PathLine: case SdrObjKind::PathFill: case SdrObjKind::FreehandLine: case SdrObjKind::FreehandFill: { pU->eCurrentKind=eNewKind; pU->bMixedCreate=true; pU->nBezierStartPoint=rXPoly.GetPointCount(); if (pU->nBezierStartPoint>0) pU->nBezierStartPoint--; } break; default: break; } // switch } } sal_uInt16 nCurrentPoint=rXPoly.GetPointCount(); if (aPathPolygon.Count()>1 && rStat.IsMouseDown() && nCurrentPoint<2) { rXPoly[0]=rStat.GetPos0(); rXPoly[1]=rStat.GetNow(); nCurrentPoint=2; } if (nCurrentPoint==0) { rXPoly[0]=rStat.GetPos0(); } else nCurrentPoint--; bool bFreeHand=IsFreeHand(pU->eCurrentKind); rStat.SetNoSnap(bFreeHand); rStat.SetOrtho8Possible(pU->eCurrentKind!=SdrObjKind::CircleArc && pU->eCurrentKind!=SdrObjKind::Rectangle && (!pU->bMixedCreate || pU->eCurrentKind!=SdrObjKind::Line)); rXPoly[nCurrentPoint]=rStat.GetNow(); if (!pU->bMixedCreate && pU->eStartKind==SdrObjKind::Line && rXPoly.GetPointCount()>=1) { Point aPt(rStat.GetStart()); if (pView!=nullptr && pView->IsCreate1stPointAsCenter()) { aPt+=aPt; aPt-=rStat.GetNow(); } rXPoly[0]=aPt; } OutputDevice* pOut=pView==nullptr ? nullptr : pView->GetFirstOutputDevice(); if (bFreeHand) { if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint; if (rStat.IsMouseDown() && nCurrentPoint>0) { // don't allow two consecutive points to occupy too similar positions tools::Long nMinDist=1; if (pView!=nullptr) nMinDist=pView->GetFreeHandMinDistPix(); if (pOut!=nullptr) nMinDist=pOut->PixelToLogic(Size(nMinDist,0)).Width(); if (nMinDist<1) nMinDist=1; Point aPt0(rXPoly[nCurrentPoint-1]); Point aPt1(rStat.GetNow()); tools::Long dx=aPt0.X()-aPt1.X(); if (dx<0) dx=-dx; tools::Long dy=aPt0.Y()-aPt1.Y(); if (dy<0) dy=-dy; if (dxnBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) { rXPoly.PointsToBezier(nCurrentPoint-3); rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control); rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control); if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) { rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2); rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth); } } rXPoly[nCurrentPoint+1]=rStat.GetNow(); rStat.NextPoint(); } else { pU->nBezierStartPoint=nCurrentPoint; } } pU->ResetFormFlags(); if (IsBezier(pU->eCurrentKind)) { if (nCurrentPoint>=2) { pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],rStat.IsMouseDown()); } else if (pU->bBezHasCtrl0) { pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],pU->aBezControl0-rXPoly[nCurrentPoint-1],rStat.IsMouseDown()); } } if (pU->eCurrentKind==SdrObjKind::CircleArc && nCurrentPoint>=2) { pU->CalcCircle(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView); } if (pU->eCurrentKind==SdrObjKind::Line && nCurrentPoint>=2) { pU->CalcLine(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView); } if (pU->eCurrentKind==SdrObjKind::Rectangle && nCurrentPoint>=2) { pU->CalcRect(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView); } return true; } bool ImpPathForDragAndCreate::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) { ImpPathCreateUser* pU=static_cast(rStat.GetUser()); bool bRet = false; SdrView* pView=rStat.GetView(); bool bIncomp=pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface(); XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1]; sal_uInt16 nCurrentPoint=rXPoly.GetPointCount()-1; rXPoly[nCurrentPoint]=rStat.GetNow(); if (!pU->bMixedCreate && pU->eStartKind==SdrObjKind::Line) { if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd; bRet = eCmd==SdrCreateCmd::ForceEnd; if (bRet) { mbCreating = false; rStat.SetUser(nullptr); } return bRet; } if (!pU->bMixedCreate && IsFreeHand(pU->eStartKind)) { if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd; bRet=eCmd==SdrCreateCmd::ForceEnd; if (bRet) { mbCreating=false; rStat.SetUser(nullptr); } return bRet; } if (eCmd==SdrCreateCmd::NextPoint || eCmd==SdrCreateCmd::NextObject) { // don't allow two consecutive points to occupy the same position if (nCurrentPoint==0 || rStat.GetNow()!=rXPoly[nCurrentPoint-1]) { if (bIncomp) { if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint; if (IsBezier(pU->eCurrentKind) && nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) { rXPoly.PointsToBezier(nCurrentPoint-3); rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control); rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control); if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) { rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2); rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth); } } } else { if (nCurrentPoint==1 && IsBezier(pU->eCurrentKind) && !pU->bBezHasCtrl0) { pU->aBezControl0=rStat.GetNow(); pU->bBezHasCtrl0=true; nCurrentPoint--; } if (pU->IsFormFlag()) { sal_uInt16 nPointCount0=rXPoly.GetPointCount(); rXPoly.Remove(nCurrentPoint-1,2); // remove last two points and replace by form rXPoly.Insert(XPOLY_APPEND,pU->GetFormPoly()); sal_uInt16 nPointCount1=rXPoly.GetPointCount(); for (sal_uInt16 i=nPointCount0+1; i=2) { pU->bBezHasCtrl0=false; // only a singular polygon may be opened, so close this rXPoly[nCurrentPoint]=rXPoly[0]; XPolygon aXP; aXP[0]=rStat.GetNow(); aPathPolygon.Insert(std::move(aXP)); } } } sal_uInt16 nPolyCount=aPathPolygon.Count(); if (nPolyCount!=0) { // delete last point, if necessary if (eCmd==SdrCreateCmd::ForceEnd) { XPolygon& rXP=aPathPolygon[nPolyCount-1]; sal_uInt16 nPointCount=rXP.GetPointCount(); if (nPointCount>=2) { if (!rXP.IsControl(nPointCount-2)) { if (rXP[nPointCount-1]==rXP[nPointCount-2]) { rXP.Remove(nPointCount-1,1); } } else { if (rXP[nPointCount-3]==rXP[nPointCount-2]) { rXP.Remove(nPointCount-3,3); } } } } for (sal_uInt16 nPolyNum=nPolyCount; nPolyNum>0;) { nPolyNum--; XPolygon& rXP=aPathPolygon[nPolyNum]; sal_uInt16 nPointCount=rXP.GetPointCount(); // delete polygons with too few points if (nPolyNumResetFormFlags(); bRet=eCmd==SdrCreateCmd::ForceEnd; if (bRet) { mbCreating=false; rStat.SetUser(nullptr); } return bRet; } bool ImpPathForDragAndCreate::BckCreate(SdrDragStat const & rStat) { ImpPathCreateUser* pU=static_cast(rStat.GetUser()); if (aPathPolygon.Count()>0) { XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1]; sal_uInt16 nCurrentPoint=rXPoly.GetPointCount(); if (nCurrentPoint>0) { nCurrentPoint--; // make the last part of a bezier curve a line rXPoly.Remove(nCurrentPoint,1); if (nCurrentPoint>=3 && rXPoly.IsControl(nCurrentPoint-1)) { // there should never be a bezier segment at the end, so this is just in case... rXPoly.Remove(nCurrentPoint-1,1); if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1); } } nCurrentPoint=rXPoly.GetPointCount(); if (nCurrentPoint>=4) { // no bezier segment at the end nCurrentPoint--; if (rXPoly.IsControl(nCurrentPoint-1)) { rXPoly.Remove(nCurrentPoint-1,1); if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1); } } if (rXPoly.GetPointCount()<2) { aPathPolygon.Remove(aPathPolygon.Count()-1); } if (aPathPolygon.Count()>0) { XPolygon& rLocalXPoly=aPathPolygon[aPathPolygon.Count()-1]; sal_uInt16 nLocalCurrentPoint=rLocalXPoly.GetPointCount(); if (nLocalCurrentPoint>0) { nLocalCurrentPoint--; rLocalXPoly[nLocalCurrentPoint]=rStat.GetNow(); } } } pU->ResetFormFlags(); return aPathPolygon.Count()!=0; } void ImpPathForDragAndCreate::BrkCreate(SdrDragStat& rStat) { aPathPolygon.Clear(); mbCreating=false; rStat.SetUser(nullptr); } basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeObjectPolyPolygon(const SdrDragStat& rDrag) const { basegfx::B2DPolyPolygon aRetval(aPathPolygon.getB2DPolyPolygon()); SdrView* pView = rDrag.GetView(); if(pView && pView->IsUseIncompatiblePathCreateInterface()) return aRetval; ImpPathCreateUser* pU = static_cast(rDrag.GetUser()); basegfx::B2DPolygon aNewPolygon(aRetval.count() ? aRetval.getB2DPolygon(aRetval.count() - 1) : basegfx::B2DPolygon()); if(pU->IsFormFlag() && aNewPolygon.count() > 1) { // remove last segment and replace with current // do not forget to rescue the previous control point which will be lost when // the point it's associated with is removed const sal_uInt32 nChangeIndex(aNewPolygon.count() - 2); const basegfx::B2DPoint aSavedPrevCtrlPoint(aNewPolygon.getPrevControlPoint(nChangeIndex)); aNewPolygon.remove(nChangeIndex, 2); aNewPolygon.append(pU->GetFormPoly().getB2DPolygon()); if(nChangeIndex < aNewPolygon.count()) { // if really something was added, set the saved previous control point to the // point where it belongs aNewPolygon.setPrevControlPoint(nChangeIndex, aSavedPrevCtrlPoint); } } if(aRetval.count()) { aRetval.setB2DPolygon(aRetval.count() - 1, aNewPolygon); } else { aRetval.append(aNewPolygon); } return aRetval; } basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeDragPolyPolygon(const SdrDragStat& rDrag) { basegfx::B2DPolyPolygon aRetval; SdrView* pView = rDrag.GetView(); if(pView && pView->IsUseIncompatiblePathCreateInterface()) return aRetval; const ImpPathCreateUser* pU = static_cast(rDrag.GetUser()); if(pU && pU->bBezier && rDrag.IsMouseDown()) { // no more XOR, no need for complicated helplines basegfx::B2DPolygon aHelpline; aHelpline.append(basegfx::B2DPoint(pU->aBezCtrl2.X(), pU->aBezCtrl2.Y())); aHelpline.append(basegfx::B2DPoint(pU->aBezEnd.X(), pU->aBezEnd.Y())); aRetval.append(aHelpline); } return aRetval; } PointerStyle ImpPathForDragAndCreate::GetCreatePointer() const { switch (meObjectKind) { case SdrObjKind::Line : return PointerStyle::DrawLine; case SdrObjKind::Polygon : return PointerStyle::DrawPolygon; case SdrObjKind::PolyLine : return PointerStyle::DrawPolygon; case SdrObjKind::PathLine: return PointerStyle::DrawBezier; case SdrObjKind::PathFill: return PointerStyle::DrawBezier; case SdrObjKind::FreehandLine: return PointerStyle::DrawFreehand; case SdrObjKind::FreehandFill: return PointerStyle::DrawFreehand; case SdrObjKind::PathPoly: return PointerStyle::DrawPolygon; case SdrObjKind::PathPolyLine: return PointerStyle::DrawPolygon; default: break; } // switch return PointerStyle::Cross; } SdrPathObjGeoData::SdrPathObjGeoData() : meKind(SdrObjKind::NONE) { } SdrPathObjGeoData::~SdrPathObjGeoData() { } // DrawContact section std::unique_ptr SdrPathObj::CreateObjectSpecificViewContact() { return std::make_unique(*this); } SdrPathObj::SdrPathObj( SdrModel& rSdrModel, SdrObjKind eNewKind) : SdrTextObj(rSdrModel), meKind(eNewKind) { m_bClosedObj = IsClosed(); } SdrPathObj::SdrPathObj(SdrModel& rSdrModel, SdrPathObj const & rSource) : SdrTextObj(rSdrModel, rSource), meKind(rSource.meKind) { m_bClosedObj = IsClosed(); maPathPolygon = rSource.GetPathPoly(); } SdrPathObj::SdrPathObj( SdrModel& rSdrModel, SdrObjKind eNewKind, basegfx::B2DPolyPolygon aPathPoly) : SdrTextObj(rSdrModel), maPathPolygon(std::move(aPathPoly)), meKind(eNewKind) { m_bClosedObj = IsClosed(); ImpForceKind(); } SdrPathObj::~SdrPathObj() = default; static bool lcl_ImpIsLine(const basegfx::B2DPolyPolygon& rPolyPolygon) { return (1 == rPolyPolygon.count() && 2 == rPolyPolygon.getB2DPolygon(0).count()); } static tools::Rectangle lcl_ImpGetBoundRect(const basegfx::B2DPolyPolygon& rPolyPolygon) { basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon)); if (aRange.isEmpty()) return tools::Rectangle(); return tools::Rectangle( basegfx::fround(aRange.getMinX()), basegfx::fround(aRange.getMinY()), basegfx::fround(aRange.getMaxX()), basegfx::fround(aRange.getMaxY())); } void SdrPathObj::ImpForceLineAngle() { if(SdrObjKind::Line != meKind || !lcl_ImpIsLine(GetPathPoly())) return; const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0)); const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0)); const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1)); const Point aPoint0(basegfx::fround(aB2DPoint0.getX()), basegfx::fround(aB2DPoint0.getY())); const Point aPoint1(basegfx::fround(aB2DPoint1.getX()), basegfx::fround(aB2DPoint1.getY())); const basegfx::B2DPoint aB2DDelt(aB2DPoint1 - aB2DPoint0); const Point aDelt(basegfx::fround(aB2DDelt.getX()), basegfx::fround(aB2DDelt.getY())); maGeo.m_nRotationAngle=GetAngle(aDelt); maGeo.m_nShearAngle=0_deg100; maGeo.RecalcSinCos(); maGeo.RecalcTan(); // for SdrTextObj, keep aRect up to date setRectangle(tools::Rectangle::Normalize(aPoint0, aPoint1)); } void SdrPathObj::ImpForceKind() { if (meKind==SdrObjKind::PathPolyLine) meKind=SdrObjKind::PolyLine; if (meKind==SdrObjKind::PathPoly) meKind=SdrObjKind::Polygon; if(GetPathPoly().areControlPointsUsed()) { switch (meKind) { case SdrObjKind::Line: meKind=SdrObjKind::PathLine; break; case SdrObjKind::PolyLine: meKind=SdrObjKind::PathLine; break; case SdrObjKind::Polygon: meKind=SdrObjKind::PathFill; break; default: break; } } else { switch (meKind) { case SdrObjKind::PathLine: meKind=SdrObjKind::PolyLine; break; case SdrObjKind::FreehandLine: meKind=SdrObjKind::PolyLine; break; case SdrObjKind::PathFill: meKind=SdrObjKind::Polygon; break; case SdrObjKind::FreehandFill: meKind=SdrObjKind::Polygon; break; default: break; } } if (meKind==SdrObjKind::Line && !lcl_ImpIsLine(GetPathPoly())) meKind=SdrObjKind::PolyLine; if (meKind==SdrObjKind::PolyLine && lcl_ImpIsLine(GetPathPoly())) meKind=SdrObjKind::Line; m_bClosedObj=IsClosed(); if (meKind==SdrObjKind::Line) { ImpForceLineAngle(); } else { // #i10659#, for polys with more than 2 points. // Here i again need to fix something, because when Path-Polys are Copy-Pasted // between Apps with different measurements (e.g. 100TH_MM and TWIPS) there is // a scaling loop started from SdrExchangeView::Paste. In itself, this is not // wrong, but aRect is wrong here and not even updated by RecalcSnapRect(). If // this is the case, some size needs to be set here in aRect to avoid that the cycle // through Rect2Poly - Poly2Rect does something badly wrong since that cycle is // BASED on aRect. That cycle is triggered in SdrTextObj::NbcResize() which is called // from the local Resize() implementation. // Basic problem is that the member aRect in SdrTextObj basically is a unrotated // text rectangle for the text object itself and methods at SdrTextObj do handle it // in that way. Many draw objects derived from SdrTextObj 'abuse' aRect as SnapRect // which is basically wrong. To make the SdrText methods which deal with aRect directly // work it is necessary to always keep aRect updated. This e.g. not done after a Clone() // command for SdrPathObj. Since adding this update mechanism with #101412# to // ImpForceLineAngle() for lines was very successful, i add it to where ImpForceLineAngle() // was called, once here below and once on a 2nd place below. // #i10659# for SdrTextObj, keep aRect up to date if(GetPathPoly().count()) { setRectangle(lcl_ImpGetBoundRect(GetPathPoly())); } } // #i75974# adapt polygon state to object type. This may include a reinterpretation // of a closed geometry as open one, but with identical first and last point for(auto& rPolygon : maPathPolygon) { if(IsClosed() != rPolygon.isClosed()) { // #i80213# really change polygon geometry; else e.g. the last point which // needs to be identical with the first one will be missing when opening // due to OBJ_PATH type if(rPolygon.isClosed()) { basegfx::utils::openWithGeometryChange(rPolygon); } else { basegfx::utils::closeWithGeometryChange(rPolygon); } } } } void SdrPathObj::ImpSetClosed(bool bClose) { if(bClose) { switch (meKind) { case SdrObjKind::Line : meKind=SdrObjKind::Polygon; break; case SdrObjKind::PolyLine : meKind=SdrObjKind::Polygon; break; case SdrObjKind::PathLine: meKind=SdrObjKind::PathFill; break; case SdrObjKind::FreehandLine: meKind=SdrObjKind::FreehandFill; break; default: break; } m_bClosedObj = true; } else { switch (meKind) { case SdrObjKind::Polygon : meKind=SdrObjKind::PolyLine; break; case SdrObjKind::PathFill: meKind=SdrObjKind::PathLine; break; case SdrObjKind::FreehandFill: meKind=SdrObjKind::FreehandLine; break; default: break; } m_bClosedObj = false; } ImpForceKind(); } void SdrPathObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const { rInfo.bNoContortion=false; bool bCanConv = !HasText() || ImpCanConvTextToCurve(); bool bIsPath = IsBezier(); rInfo.bEdgeRadiusAllowed = false; rInfo.bCanConvToPath = bCanConv && !bIsPath; rInfo.bCanConvToPoly = bCanConv && bIsPath; rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary()); } SdrObjKind SdrPathObj::GetObjIdentifier() const { return meKind; } rtl::Reference SdrPathObj::CloneSdrObject(SdrModel& rTargetModel) const { return new SdrPathObj(rTargetModel, *this); } OUString SdrPathObj::TakeObjNameSingul() const { OUString sName; if(SdrObjKind::Line == meKind) { TranslateId pId(STR_ObjNameSingulLINE); if(lcl_ImpIsLine(GetPathPoly())) { const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0)); const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0)); const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1)); if(aB2DPoint0 != aB2DPoint1) { if(aB2DPoint0.getY() == aB2DPoint1.getY()) { pId = STR_ObjNameSingulLINE_Hori; } else if(aB2DPoint0.getX() == aB2DPoint1.getX()) { pId = STR_ObjNameSingulLINE_Vert; } else { const double fDx(fabs(aB2DPoint0.getX() - aB2DPoint1.getX())); const double fDy(fabs(aB2DPoint0.getY() - aB2DPoint1.getY())); if(fDx == fDy) { pId = STR_ObjNameSingulLINE_Diag; } } } } sName = SvxResId(pId); } else if(SdrObjKind::PolyLine == meKind || SdrObjKind::Polygon == meKind) { const bool bClosed(SdrObjKind::Polygon == meKind); TranslateId pId; if(mpDAC && mpDAC->IsCreating()) { if(bClosed) { pId = STR_ObjNameSingulPOLY; } else { pId = STR_ObjNameSingulPLIN; } sName = SvxResId(pId); } else { // get point count sal_uInt32 nPointCount(0); for(auto const& rPolygon : GetPathPoly()) { nPointCount += rPolygon.count(); } if(bClosed) { pId = STR_ObjNameSingulPOLY_PointCount; } else { pId = STR_ObjNameSingulPLIN_PointCount; } // #i96537# sName = SvxResId(pId).replaceFirst("%2", OUString::number(nPointCount)); } } else { switch (meKind) { case SdrObjKind::PathLine: sName = SvxResId(STR_ObjNameSingulPATHLINE); break; case SdrObjKind::FreehandLine: sName = SvxResId(STR_ObjNameSingulFREELINE); break; case SdrObjKind::PathFill: sName = SvxResId(STR_ObjNameSingulPATHFILL); break; case SdrObjKind::FreehandFill: sName = SvxResId(STR_ObjNameSingulFREEFILL); break; default: break; } } OUString aName(GetName()); if (!aName.isEmpty()) sName += " '" + aName + "'"; return sName; } OUString SdrPathObj::TakeObjNamePlural() const { OUString sName; switch(meKind) { case SdrObjKind::Line : sName=SvxResId(STR_ObjNamePluralLINE ); break; case SdrObjKind::PolyLine : sName=SvxResId(STR_ObjNamePluralPLIN ); break; case SdrObjKind::Polygon : sName=SvxResId(STR_ObjNamePluralPOLY ); break; case SdrObjKind::PathLine: sName=SvxResId(STR_ObjNamePluralPATHLINE); break; case SdrObjKind::FreehandLine: sName=SvxResId(STR_ObjNamePluralFREELINE); break; case SdrObjKind::PathFill: sName=SvxResId(STR_ObjNamePluralPATHFILL); break; case SdrObjKind::FreehandFill: sName=SvxResId(STR_ObjNamePluralFREEFILL); break; default: break; } return sName; } basegfx::B2DPolyPolygon SdrPathObj::TakeXorPoly() const { return GetPathPoly(); } sal_uInt32 SdrPathObj::GetHdlCount() const { sal_uInt32 nRetval(0); for(auto const& rPolygon : GetPathPoly()) { nRetval += rPolygon.count(); } return nRetval; } void SdrPathObj::AddToHdlList(SdrHdlList& rHdlList) const { // keep old stuff to be able to keep old SdrHdl stuff, too const XPolyPolygon aOldPathPolygon(GetPathPoly()); sal_uInt16 nPolyCnt=aOldPathPolygon.Count(); bool bClosed=IsClosed(); sal_uInt16 nIdx=0; for (sal_uInt16 i=0; i1) nPntCnt--; for (sal_uInt16 j=0; j pHdl(new SdrHdl(rPnt,SdrHdlKind::Poly)); pHdl->SetPolyNum(i); pHdl->SetPointNum(j); pHdl->Set1PixMore(j==0); pHdl->SetSourceHdlNum(nIdx); nIdx++; rHdlList.AddHdl(std::move(pHdl)); } } } } void SdrPathObj::AddToPlusHdlList(SdrHdlList& rHdlList, SdrHdl& rHdl) const { // keep old stuff to be able to keep old SdrHdl stuff, too const XPolyPolygon aOldPathPolygon(GetPathPoly()); sal_uInt16 nPnt = static_cast(rHdl.GetPointNum()); sal_uInt16 nPolyNum = static_cast(rHdl.GetPolyNum()); if (nPolyNum>=aOldPathPolygon.Count()) return; const XPolygon& rXPoly = aOldPathPolygon[nPolyNum]; sal_uInt16 nPntMax = rXPoly.GetPointCount(); if (nPntMax<=0) return; nPntMax--; if (nPnt>nPntMax) return; // calculate the number of plus points sal_uInt16 nCnt = 0; if (rXPoly.GetFlags(nPnt)!=PolyFlags::Control) { if (nPnt==0 && IsClosed()) nPnt=nPntMax; if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control) nCnt++; if (nPnt==nPntMax && IsClosed()) nPnt=0; if (nPnt(rHdl.GetPointNum()); std::unique_ptr pHdl(new SdrHdlBezWgt(&rHdl)); pHdl->SetPolyNum(rHdl.GetPolyNum()); if (nPnt==0 && IsClosed()) nPnt=nPntMax; if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control && nPlusNum==0) { pHdl->SetPos(rXPoly[nPnt-1]); pHdl->SetPointNum(nPnt-1); } else { if (nPnt==nPntMax && IsClosed()) nPnt=0; if (nPntSetPos(rXPoly[nPnt+1]); pHdl->SetPointNum(nPnt+1); } } pHdl->SetSourceHdlNum(rHdl.GetSourceHdlNum()); pHdl->SetPlusHdl(true); rHdlList.AddHdl(std::move(pHdl)); } } // tdf#123321: Make sure that SdrPathObj (e.g. line) has big enough extent for // visibility. This is realised by ensuring GetLogicRect() is the same as // GetSnapRect() for the SdrPathObj. Other SdrTextObj objects like // SdrObjCustomShape will still use a different version of this method that // does not consider the rotation. Otherwise, the rotated SdrObjCustomShape // would become mistakenly larger after save and reload (tdf#91687). // The invocation of the GetLogicRect() method that caused tdf#123321 was in // PlcDrawObj::WritePlc(). const tools::Rectangle &SdrPathObj::GetLogicRect() const { return GetSnapRect(); } // dragging bool SdrPathObj::hasSpecialDrag() const { return true; } bool SdrPathObj::beginSpecialDrag(SdrDragStat& rDrag) const { ImpPathForDragAndCreate aDragAndCreate(*const_cast(this)); return aDragAndCreate.beginPathDrag(rDrag); } bool SdrPathObj::applySpecialDrag(SdrDragStat& rDrag) { ImpPathForDragAndCreate aDragAndCreate(*this); bool bRetval(aDragAndCreate.beginPathDrag(rDrag)); if(bRetval) { bRetval = aDragAndCreate.movePathDrag(rDrag); } if(bRetval) { bRetval = aDragAndCreate.endPathDrag(rDrag); } if(bRetval) { NbcSetPathPoly(aDragAndCreate.getModifiedPolyPolygon()); } return bRetval; } OUString SdrPathObj::getSpecialDragComment(const SdrDragStat& rDrag) const { OUString aRetval; if(mpDAC) { // #i103058# also get a comment when in creation const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj()); if(bCreateComment) { aRetval = mpDAC->getSpecialDragComment(rDrag); } } else { ImpPathForDragAndCreate aDragAndCreate(*const_cast(this)); bool bDidWork(aDragAndCreate.beginPathDrag(rDrag)); if(bDidWork) { aRetval = aDragAndCreate.getSpecialDragComment(rDrag); } } return aRetval; } basegfx::B2DPolyPolygon SdrPathObj::getSpecialDragPoly(const SdrDragStat& rDrag) const { basegfx::B2DPolyPolygon aRetval; ImpPathForDragAndCreate aDragAndCreate(*const_cast(this)); bool bDidWork(aDragAndCreate.beginPathDrag(rDrag)); if(bDidWork) { aRetval = aDragAndCreate.getSpecialDragPoly(rDrag); } return aRetval; } // creation bool SdrPathObj::BegCreate(SdrDragStat& rStat) { mpDAC.reset(); impGetDAC().BegCreate(rStat); return true; } bool SdrPathObj::MovCreate(SdrDragStat& rStat) { return impGetDAC().MovCreate(rStat); } bool SdrPathObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) { bool bRetval(impGetDAC().EndCreate(rStat, eCmd)); if(bRetval && mpDAC) { SetPathPoly(mpDAC->getModifiedPolyPolygon()); // #i75974# Check for AutoClose feature. Moved here from ImpPathForDragAndCreate::EndCreate // to be able to use the type-changing ImpSetClosed method if(!IsClosedObj()) { SdrView* pView = rStat.GetView(); if(pView && !pView->IsUseIncompatiblePathCreateInterface()) { OutputDevice* pOut = pView->GetFirstOutputDevice(); if(pOut) { if(GetPathPoly().count()) { const basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(0)); if(aCandidate.count() > 2) { // check distance of first and last point const sal_Int32 nCloseDist(pOut->PixelToLogic(Size(pView->GetAutoCloseDistPix(), 0)).Width()); const basegfx::B2DVector aDistVector(aCandidate.getB2DPoint(aCandidate.count() - 1) - aCandidate.getB2DPoint(0)); if(aDistVector.getLength() <= static_cast(nCloseDist)) { // close it ImpSetClosed(true); } } } } } } mpDAC.reset(); } return bRetval; } bool SdrPathObj::BckCreate(SdrDragStat& rStat) { return impGetDAC().BckCreate(rStat); } void SdrPathObj::BrkCreate(SdrDragStat& rStat) { impGetDAC().BrkCreate(rStat); mpDAC.reset(); } // polygons basegfx::B2DPolyPolygon SdrPathObj::TakeCreatePoly(const SdrDragStat& rDrag) const { basegfx::B2DPolyPolygon aRetval; if(mpDAC) { aRetval = mpDAC->TakeObjectPolyPolygon(rDrag); aRetval.append(ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag)); } return aRetval; } // during drag or create, allow accessing the so-far created/modified polyPolygon basegfx::B2DPolyPolygon SdrPathObj::getObjectPolyPolygon(const SdrDragStat& rDrag) const { basegfx::B2DPolyPolygon aRetval; if(mpDAC) { aRetval = mpDAC->TakeObjectPolyPolygon(rDrag); } return aRetval; } basegfx::B2DPolyPolygon SdrPathObj::getDragPolyPolygon(const SdrDragStat& rDrag) const { basegfx::B2DPolyPolygon aRetval; if(mpDAC) { aRetval = ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag); } return aRetval; } PointerStyle SdrPathObj::GetCreatePointer() const { return impGetDAC().GetCreatePointer(); } void SdrPathObj::NbcMove(const Size& rSiz) { maPathPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(rSiz.Width(), rSiz.Height())); // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints) SdrTextObj::NbcMove(rSiz); } void SdrPathObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) { const double fResizeX(xFact); const double fResizeY(yFact); if(basegfx::fTools::equal(fResizeX, 1.0) && basegfx::fTools::equal(fResizeY, 1.0)) { // tdf#106792 avoid numerical unprecisions: If both scale factors are 1.0, do not // manipulate at all - that may change maGeo rapidly (and wrongly) in // SdrTextObj::NbcResize. Combined with the UNO API trying to not 'apply' // a rotation but to manipulate the existing one, this is fatal. So just // avoid this error as long as we have to deal with imprecise geometry // manipulations return; } basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRef.X(), -rRef.Y())); aTrans = basegfx::utils::createScaleTranslateB2DHomMatrix( double(xFact), double(yFact), rRef.X(), rRef.Y()) * aTrans; maPathPolygon.transform(aTrans); // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints) SdrTextObj::NbcResize(rRef,xFact,yFact); } void SdrPathObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs) { // Thank JOE, the angles are defined mirrored to the mathematical meanings const basegfx::B2DHomMatrix aTrans( basegfx::utils::createRotateAroundPoint(rRef.X(), rRef.Y(), -toRadians(nAngle))); maPathPolygon.transform(aTrans); // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints) SdrTextObj::NbcRotate(rRef,nAngle,sn,cs); } void SdrPathObj::NbcShear(const Point& rRefPnt, Degree100 nAngle, double fTan, bool bVShear) { basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt.X(), -rRefPnt.Y())); if(bVShear) { // Thank JOE, the angles are defined mirrored to the mathematical meanings aTrans.shearY(-fTan); } else { aTrans.shearX(-fTan); } aTrans.translate(rRefPnt.X(), rRefPnt.Y()); maPathPolygon.transform(aTrans); // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints) SdrTextObj::NbcShear(rRefPnt,nAngle,fTan,bVShear); } void SdrPathObj::NbcMirror(const Point& rRefPnt1, const Point& rRefPnt2) { const double fDiffX(rRefPnt2.X() - rRefPnt1.X()); const double fDiffY(rRefPnt2.Y() - rRefPnt1.Y()); const double fRot(atan2(fDiffY, fDiffX)); basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt1.X(), -rRefPnt1.Y())); aTrans.rotate(-fRot); aTrans.scale(1.0, -1.0); aTrans.rotate(fRot); aTrans.translate(rRefPnt1.X(), rRefPnt1.Y()); maPathPolygon.transform(aTrans); // Do Joe's special handling for lines when mirroring, too ImpForceKind(); // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints) SdrTextObj::NbcMirror(rRefPnt1,rRefPnt2); } void SdrPathObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const { if(!maGeo.m_nRotationAngle) { rRect = GetSnapRect(); } else { XPolyPolygon aXPP(GetPathPoly()); RotateXPoly(aXPP,Point(),-maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); rRect=aXPP.GetBoundRect(); Point aTmp(rRect.TopLeft()); RotatePoint(aTmp,Point(),maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); aTmp-=rRect.TopLeft(); rRect.Move(aTmp.X(),aTmp.Y()); } } void SdrPathObj::RecalcSnapRect() { if(GetPathPoly().count()) { maSnapRect = lcl_ImpGetBoundRect(GetPathPoly()); } } void SdrPathObj::NbcSetSnapRect(const tools::Rectangle& rRect) { tools::Rectangle aOld(GetSnapRect()); if (aOld.IsEmpty()) { Fraction aX(1,1); Fraction aY(1,1); NbcResize(aOld.TopLeft(), aX, aY); NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top())); return; } // Take empty into account when calculating scale factors tools::Long nMulX = rRect.IsWidthEmpty() ? 0 : rRect.Right() - rRect.Left(); tools::Long nDivX = aOld.Right() - aOld.Left(); // Take empty into account when calculating scale factors tools::Long nMulY = rRect.IsHeightEmpty() ? 0 : rRect.Bottom() - rRect.Top(); tools::Long nDivY = aOld.Bottom() - aOld.Top(); if ( nDivX == 0 ) { nMulX = 1; nDivX = 1; } if ( nDivY == 0 ) { nMulY = 1; nDivY = 1; } if ( nDivX == nMulX ) { nMulX = 1; nDivX = 1; } if ( nDivY == nMulY ) { nMulY = 1; nDivY = 1; } Fraction aX(nMulX,nDivX); Fraction aY(nMulY,nDivY); NbcResize(aOld.TopLeft(), aX, aY); NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top())); } sal_uInt32 SdrPathObj::GetSnapPointCount() const { return GetHdlCount(); } Point SdrPathObj::GetSnapPoint(sal_uInt32 nSnapPnt) const { sal_uInt32 nPoly,nPnt; if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nSnapPnt, nPoly, nPnt)) { SAL_WARN("svx", "SdrPathObj::GetSnapPoint: Point nSnapPnt does not exist."); } const basegfx::B2DPoint aB2DPoint(GetPathPoly().getB2DPolygon(nPoly).getB2DPoint(nPnt)); return Point(basegfx::fround(aB2DPoint.getX()), basegfx::fround(aB2DPoint.getY())); } bool SdrPathObj::IsPolyObj() const { return true; } sal_uInt32 SdrPathObj::GetPointCount() const { sal_uInt32 nRetval(0); for(auto const& rPolygon : GetPathPoly()) { nRetval += rPolygon.count(); } return nRetval; } Point SdrPathObj::GetPoint(sal_uInt32 nHdlNum) const { Point aRetval; sal_uInt32 nPoly,nPnt; if(PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt)) { const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(nPoly)); const basegfx::B2DPoint aPoint(aPoly.getB2DPoint(nPnt)); aRetval = Point(basegfx::fround(aPoint.getX()), basegfx::fround(aPoint.getY())); } return aRetval; } void SdrPathObj::NbcSetPoint(const Point& rPnt, sal_uInt32 nHdlNum) { sal_uInt32 nPoly,nPnt; if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt)) return; basegfx::B2DPolygon aNewPolygon(GetPathPoly().getB2DPolygon(nPoly)); aNewPolygon.setB2DPoint(nPnt, basegfx::B2DPoint(rPnt.X(), rPnt.Y())); maPathPolygon.setB2DPolygon(nPoly, aNewPolygon); if(meKind==SdrObjKind::Line) { ImpForceLineAngle(); } else { if(GetPathPoly().count()) { // #i10659# for SdrTextObj, keep aRect up to date setRectangle(lcl_ImpGetBoundRect(GetPathPoly())); } } SetBoundAndSnapRectsDirty(); } sal_uInt32 SdrPathObj::NbcInsPointOld(const Point& rPos, bool bNewObj) { sal_uInt32 nNewHdl; if(bNewObj) { nNewHdl = NbcInsPoint(rPos, true); } else { // look for smallest distance data const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y()); sal_uInt32 nSmallestPolyIndex(0); sal_uInt32 nSmallestEdgeIndex(0); double fSmallestCut; basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut); nNewHdl = NbcInsPoint(rPos, false); } ImpForceKind(); return nNewHdl; } sal_uInt32 SdrPathObj::NbcInsPoint(const Point& rPos, bool bNewObj) { sal_uInt32 nNewHdl; if(bNewObj) { basegfx::B2DPolygon aNewPoly; const basegfx::B2DPoint aPoint(rPos.X(), rPos.Y()); aNewPoly.append(aPoint); aNewPoly.setClosed(IsClosed()); maPathPolygon.append(aNewPoly); SetBoundAndSnapRectsDirty(); nNewHdl = GetHdlCount(); } else { // look for smallest distance data const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y()); sal_uInt32 nSmallestPolyIndex(0); sal_uInt32 nSmallestEdgeIndex(0); double fSmallestCut; basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut); basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(nSmallestPolyIndex)); const bool bBefore(!aCandidate.isClosed() && 0 == nSmallestEdgeIndex && 0.0 == fSmallestCut); const bool bAfter(!aCandidate.isClosed() && aCandidate.count() == nSmallestEdgeIndex + 2 && 1.0 == fSmallestCut); if(bBefore) { // before first point aCandidate.insert(0, aTestPoint); if(aCandidate.areControlPointsUsed()) { if(aCandidate.isNextControlPointUsed(1)) { aCandidate.setNextControlPoint(0, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (1.0 / 3.0))); aCandidate.setPrevControlPoint(1, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (2.0 / 3.0))); } } nNewHdl = 0; } else if(bAfter) { // after last point aCandidate.append(aTestPoint); if(aCandidate.areControlPointsUsed()) { if(aCandidate.isPrevControlPointUsed(aCandidate.count() - 2)) { aCandidate.setNextControlPoint(aCandidate.count() - 2, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (1.0 / 3.0))); aCandidate.setPrevControlPoint(aCandidate.count() - 1, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (2.0 / 3.0))); } } nNewHdl = aCandidate.count() - 1; } else { // in between bool bSegmentSplit(false); const sal_uInt32 nNextIndex((nSmallestEdgeIndex + 1) % aCandidate.count()); if(aCandidate.areControlPointsUsed()) { if(aCandidate.isNextControlPointUsed(nSmallestEdgeIndex) || aCandidate.isPrevControlPointUsed(nNextIndex)) { bSegmentSplit = true; } } if(bSegmentSplit) { // rebuild original segment to get the split data basegfx::B2DCubicBezier aBezierA, aBezierB; const basegfx::B2DCubicBezier aBezier( aCandidate.getB2DPoint(nSmallestEdgeIndex), aCandidate.getNextControlPoint(nSmallestEdgeIndex), aCandidate.getPrevControlPoint(nNextIndex), aCandidate.getB2DPoint(nNextIndex)); // split and insert hit point aBezier.split(fSmallestCut, &aBezierA, &aBezierB); aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint); // since we inserted hit point and not split point, we need to add an offset // to the control points to get the C1 continuity we want to achieve const basegfx::B2DVector aOffset(aTestPoint - aBezierA.getEndPoint()); aCandidate.setNextControlPoint(nSmallestEdgeIndex, aBezierA.getControlPointA() + aOffset); aCandidate.setPrevControlPoint(nSmallestEdgeIndex + 1, aBezierA.getControlPointB() + aOffset); aCandidate.setNextControlPoint(nSmallestEdgeIndex + 1, aBezierB.getControlPointA() + aOffset); aCandidate.setPrevControlPoint((nSmallestEdgeIndex + 2) % aCandidate.count(), aBezierB.getControlPointB() + aOffset); } else { aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint); } nNewHdl = nSmallestEdgeIndex + 1; } maPathPolygon.setB2DPolygon(nSmallestPolyIndex, aCandidate); // create old polygon index from it for(sal_uInt32 a(0); a < nSmallestPolyIndex; a++) { nNewHdl += GetPathPoly().getB2DPolygon(a).count(); } } ImpForceKind(); return nNewHdl; } rtl::Reference SdrPathObj::RipPoint(sal_uInt32 nHdlNum, sal_uInt32& rNewPt0Index) { rtl::Reference pNewObj; const basegfx::B2DPolyPolygon aLocalPolyPolygon(GetPathPoly()); sal_uInt32 nPoly, nPnt; if(PolyPolygonEditor::GetRelativePolyPoint(aLocalPolyPolygon, nHdlNum, nPoly, nPnt)) { if(0 == nPoly) { const basegfx::B2DPolygon& aCandidate(aLocalPolyPolygon.getB2DPolygon(nPoly)); const sal_uInt32 nPointCount(aCandidate.count()); if(nPointCount) { if(IsClosed()) { // when closed, RipPoint means to open the polygon at the selected point. To // be able to do that, it is necessary to make the selected point the first one basegfx::B2DPolygon aNewPolygon(basegfx::utils::makeStartPoint(aCandidate, nPnt)); SetPathPoly(basegfx::B2DPolyPolygon(aNewPolygon)); ToggleClosed(); // give back new position of old start point (historical reasons) rNewPt0Index = (nPointCount - nPnt) % nPointCount; } else { if(nPointCount >= 3 && nPnt != 0 && nPnt + 1 < nPointCount) { // split in two objects at point nPnt basegfx::B2DPolygon aSplitPolyA(aCandidate, 0, nPnt + 1); SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyA)); pNewObj = SdrObject::Clone(*this, getSdrModelFromSdrObject()); basegfx::B2DPolygon aSplitPolyB(aCandidate, nPnt, nPointCount - nPnt); pNewObj->SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyB)); } } } } } return pNewObj; } rtl::Reference SdrPathObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const { // #i89784# check for FontWork with activated HideContour const drawinglayer::attribute::SdrTextAttribute aText( drawinglayer::primitive2d::createNewSdrTextAttribute(GetObjectItemSet(), *getText(0))); const bool bHideContour( !aText.isDefault() && !aText.getSdrFormTextAttribute().isDefault() && aText.isHideContour()); rtl::Reference pRet; if(!bHideContour) { rtl::Reference pPath = ImpConvertMakeObj(GetPathPoly(), IsClosed(), bBezier); if(pPath->GetPathPoly().areControlPointsUsed()) { if(!bBezier) { // reduce all bezier curves pPath->SetPathPoly(basegfx::utils::adaptiveSubdivideByAngle(pPath->GetPathPoly())); } } else { if(bBezier) { // create bezier curves pPath->SetPathPoly(basegfx::utils::expandToCurve(pPath->GetPathPoly())); } } pRet = std::move(pPath); } if(bAddText) { pRet = ImpConvertAddText(std::move(pRet), bBezier); } return pRet; } std::unique_ptr SdrPathObj::NewGeoData() const { return std::make_unique(); } void SdrPathObj::SaveGeoData(SdrObjGeoData& rGeo) const { SdrTextObj::SaveGeoData(rGeo); SdrPathObjGeoData& rPGeo = static_cast( rGeo ); rPGeo.maPathPolygon=GetPathPoly(); rPGeo.meKind=meKind; } void SdrPathObj::RestoreGeoData(const SdrObjGeoData& rGeo) { SdrTextObj::RestoreGeoData(rGeo); const SdrPathObjGeoData& rPGeo=static_cast(rGeo); maPathPolygon=rPGeo.maPathPolygon; meKind=rPGeo.meKind; ImpForceKind(); // to set bClosed (among other things) } void SdrPathObj::NbcSetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly) { if(GetPathPoly() != rPathPoly) { maPathPolygon=rPathPoly; ImpForceKind(); SetBoundAndSnapRectsDirty(); } } void SdrPathObj::SetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly) { if(GetPathPoly() != rPathPoly) { tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); NbcSetPathPoly(rPathPoly); SetChanged(); BroadcastObjectChange(); SendUserCall(SdrUserCallType::Resize,aBoundRect0); } } void SdrPathObj::ToggleClosed() { tools::Rectangle aBoundRect0; if(m_pUserCall != nullptr) aBoundRect0 = GetLastBoundRect(); ImpSetClosed(!IsClosed()); // set new ObjKind ImpForceKind(); // because we want Line -> Poly -> PolyLine instead of Line -> Poly -> Line SetBoundAndSnapRectsDirty(); SetChanged(); BroadcastObjectChange(); SendUserCall(SdrUserCallType::Resize, aBoundRect0); } ImpPathForDragAndCreate& SdrPathObj::impGetDAC() const { if(!mpDAC) { const_cast(this)->mpDAC.reset(new ImpPathForDragAndCreate(*const_cast(this))); } return *mpDAC; } // transformation interface for StarOfficeAPI. This implements support for // homogeneous 3x3 matrices containing the transformation of the SdrObject. At the // moment it contains a shearX, rotation and translation, but for setting all linear // transforms like Scale, ShearX, ShearY, Rotate and Translate are supported. // gets base transformation and rectangle of object. If it's an SdrPathObj it fills the PolyPolygon // with the base geometry and returns TRUE. Otherwise it returns FALSE. bool SdrPathObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& rPolyPolygon) const { double fRotate(0.0); double fShearX(0.0); basegfx::B2DTuple aScale(1.0, 1.0); basegfx::B2DTuple aTranslate(0.0, 0.0); if(GetPathPoly().count()) { // copy geometry basegfx::B2DHomMatrix aMoveToZeroMatrix; rPolyPolygon = GetPathPoly(); if(SdrObjKind::Line == meKind) { // ignore shear and rotate, just use scale and translate OSL_ENSURE(GetPathPoly().count() > 0 && GetPathPoly().getB2DPolygon(0).count() > 1, "OBJ_LINE with too few polygons (!)"); // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon // itself, else this method will no longer return the full polygon information (curve will // be lost) const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon)); aScale = aPolyRangeNoCurve.getRange(); aTranslate = aPolyRangeNoCurve.getMinimum(); // define matrix for move polygon to zero point aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY()); } else { if(maGeo.m_nShearAngle || maGeo.m_nRotationAngle) { // get rotate and shear in drawingLayer notation fRotate = toRadians(maGeo.m_nRotationAngle); fShearX = toRadians(maGeo.m_nShearAngle); // build mathematically correct (negative shear and rotate) object transform // containing shear and rotate to extract unsheared, unrotated polygon basegfx::B2DHomMatrix aObjectMatrix; aObjectMatrix.shearX(-maGeo.mfTanShearAngle); aObjectMatrix.rotate(toRadians(36000_deg100 - maGeo.m_nRotationAngle)); // create inverse from it and back-transform polygon basegfx::B2DHomMatrix aInvObjectMatrix(aObjectMatrix); aInvObjectMatrix.invert(); rPolyPolygon.transform(aInvObjectMatrix); // get range from unsheared, unrotated polygon and extract scale and translate. // transform topLeft from it back to transformed state to get original // topLeft (rotation center) // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon // itself, else this method will no longer return the full polygon information (curve will // be lost) const basegfx::B2DRange aCorrectedRangeNoCurve(basegfx::utils::getRange(rPolyPolygon)); aTranslate = aObjectMatrix * aCorrectedRangeNoCurve.getMinimum(); aScale = aCorrectedRangeNoCurve.getRange(); // define matrix for move polygon to zero point // #i112280# Added missing minus for Y-Translation aMoveToZeroMatrix.translate(-aCorrectedRangeNoCurve.getMinX(), -aCorrectedRangeNoCurve.getMinY()); } else { // get scale and translate from unsheared, unrotated polygon // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon // itself, else this method will no longer return the full polygon information (curve will // be lost) const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon)); if (!aPolyRangeNoCurve.isEmpty()) { aScale = aPolyRangeNoCurve.getRange(); aTranslate = aPolyRangeNoCurve.getMinimum(); // define matrix for move polygon to zero point aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY()); } } } // move polygon to zero point with pre-defined matrix rPolyPolygon.transform(aMoveToZeroMatrix); } // position maybe relative to anchorpos, convert if( getSdrModelFromSdrObject().IsWriter() ) { if(GetAnchorPos().X() || GetAnchorPos().Y()) { aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); } } // build return value matrix rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( aScale, basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX), basegfx::fTools::equalZero(fRotate) ? 0.0 : -fRotate, aTranslate); return true; } void SdrPathObj::SetHandleScale(bool bHandleScale) { mbHandleScale = bHandleScale; } // Sets the base geometry of the object using infos contained in the homogeneous 3x3 matrix. // If it's an SdrPathObj it will use the provided geometry information. The Polygon has // to use (0,0) as upper left and will be scaled to the given size in the matrix. void SdrPathObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& rPolyPolygon) { // break up matrix basegfx::B2DTuple aScale; basegfx::B2DTuple aTranslate; double fRotate, fShearX; rMatrix.decompose(aScale, aTranslate, fRotate, fShearX); // #i75086# Old DrawingLayer (GeoStat and geometry) does not support holding negative scalings // in X and Y which equal a 180 degree rotation. Recognize it and react accordingly if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0)) { aScale.setX(fabs(aScale.getX())); aScale.setY(fabs(aScale.getY())); fRotate = fmod(fRotate + M_PI, 2 * M_PI); } // copy poly basegfx::B2DPolyPolygon aNewPolyPolygon(rPolyPolygon); // reset object shear and rotations maGeo.m_nRotationAngle = 0_deg100; maGeo.RecalcSinCos(); maGeo.m_nShearAngle = 0_deg100; maGeo.RecalcTan(); if( getSdrModelFromSdrObject().IsWriter() ) { // if anchor is used, make position relative to it if(GetAnchorPos().X() || GetAnchorPos().Y()) { aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); } } // create transformation for polygon, set values at maGeo direct basegfx::B2DHomMatrix aTransform; // #i75086# // Given polygon is already scaled (for historical reasons), but not mirrored yet. // Thus, when scale is negative in X or Y, apply the needed mirroring accordingly. double fScaleX(basegfx::fTools::less(aScale.getX(), 0.0) ? -1.0 : 1.0); double fScaleY(basegfx::fTools::less(aScale.getY(), 0.0) ? -1.0 : 1.0); // tdf#98565, tdf#98584. While loading a shape, svg:width and svg:height is used to scale // the polygon. But draw:transform might introduce additional scaling factors, which need to // be applied to the polygon too, so aScale cannot be ignored while loading. // I use "maSnapRect.IsEmpty() && GetPathPoly().count()" to detect this case. Any better // idea? The behavior in other cases is the same as it was before this fix. if (maSnapRect.IsEmpty() && GetPathPoly().count() && mbHandleScale) { // In case of a Writer document, the scaling factors were converted to twips. That is not // correct here, because width and height are already in the points coordinates and aScale // is no length but only a factor here. Convert back. if (getSdrModelFromSdrObject().IsWriter()) { aScale.setX(o3tl::convert(aScale.getX(), o3tl::Length::twip, o3tl::Length::mm100)); aScale.setY(o3tl::convert(aScale.getY(), o3tl::Length::twip, o3tl::Length::mm100)); } fScaleX *= fabs(aScale.getX()); fScaleY *= fabs(aScale.getY()); } if (fScaleX != 1.0 || fScaleY != 1.0) aTransform.scale(fScaleX, fScaleY); if(!basegfx::fTools::equalZero(fShearX)) { aTransform.shearX(tan(-atan(fShearX))); maGeo.m_nShearAngle = Degree100(basegfx::fround(basegfx::rad2deg<100>(atan(fShearX)))); maGeo.RecalcTan(); } if(!basegfx::fTools::equalZero(fRotate)) { // #i78696# // fRotate is mathematically correct for linear transformations, so it's // the one to use for the geometry change aTransform.rotate(fRotate); // #i78696# // fRotate is mathematically correct, but aGeoStat.nRotationAngle is // mirrored -> mirror value here maGeo.m_nRotationAngle = NormAngle36000(Degree100(basegfx::fround(-basegfx::rad2deg<100>(fRotate)))); maGeo.RecalcSinCos(); } if(!aTranslate.equalZero()) { // #i39529# absolute positioning, so get current position (without control points (!)) const basegfx::B2DRange aCurrentRange(basegfx::utils::getRange(aNewPolyPolygon)); aTransform.translate(aTranslate.getX() - aCurrentRange.getMinX(), aTranslate.getY() - aCurrentRange.getMinY()); } // transform polygon and trigger change aNewPolyPolygon.transform(aTransform); SetPathPoly(aNewPolyPolygon); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */