Adjust includes
[LibreOffice.git] / svx / source / svdraw / svdopath.cxx
blobd9bc1287e904a28079aaa2b2fcf7a7816979713e
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <tools/bigint.hxx>
21 #include <tools/helpers.hxx>
22 #include <svx/svdopath.hxx>
23 #include <math.h>
24 #include <svx/xpool.hxx>
25 #include <svx/xpoly.hxx>
26 #include <svx/svdattr.hxx>
27 #include <svx/svdtrans.hxx>
28 #include <svx/svdetc.hxx>
29 #include <svx/svddrag.hxx>
30 #include <svx/svdmodel.hxx>
31 #include <svx/svdpage.hxx>
32 #include <svx/svdhdl.hxx>
33 #include <svx/svdview.hxx>
34 #include <svdglob.hxx>
35 #include <svx/strings.hrc>
37 #include <svx/xlnwtit.hxx>
38 #include <svx/xlnclit.hxx>
39 #include <svx/xflclit.hxx>
40 #include <svx/svdogrp.hxx>
41 #include <svx/polypolygoneditor.hxx>
42 #include <svx/xlntrit.hxx>
43 #include <sdr/contact/viewcontactofsdrpathobj.hxx>
44 #include <basegfx/matrix/b2dhommatrix.hxx>
45 #include "svdconv.hxx"
46 #include <basegfx/point/b2dpoint.hxx>
47 #include <basegfx/polygon/b2dpolypolygontools.hxx>
48 #include <basegfx/range/b2drange.hxx>
49 #include <basegfx/curve/b2dcubicbezier.hxx>
50 #include <basegfx/polygon/b2dpolygontools.hxx>
51 #include <svx/sdr/attribute/sdrtextattribute.hxx>
52 #include <svx/sdr/primitive2d/sdrattributecreator.hxx>
53 #include <basegfx/matrix/b2dhommatrixtools.hxx>
54 #include <svx/sdr/attribute/sdrformtextattribute.hxx>
55 #include <memory>
57 using namespace sdr;
59 inline sal_uInt16 GetPrevPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed)
61 if (nPnt>0) {
62 nPnt--;
63 } else {
64 nPnt=nPntMax;
65 if (bClosed) nPnt--;
67 return nPnt;
70 inline sal_uInt16 GetNextPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed)
72 nPnt++;
73 if (nPnt>nPntMax || (bClosed && nPnt>=nPntMax)) nPnt=0;
74 return nPnt;
77 struct ImpSdrPathDragData : public SdrDragStatUserData
79 XPolygon aXP; // section of the original polygon
80 bool bValid; // FALSE = too few points
81 bool bClosed; // closed object?
82 sal_uInt16 nPoly; // number of the polygon in the PolyPolygon
83 sal_uInt16 nPnt; // number of point in the above polygon
84 sal_uInt16 nPointCount; // number of points of the polygon
85 sal_uInt16 nPntMax; // maximum index
86 bool bBegPnt; // dragged point is first point of a Polyline
87 bool bEndPnt; // dragged point is finishing point of a Polyline
88 sal_uInt16 nPrevPnt; // index of previous point
89 sal_uInt16 nNextPnt; // index of next point
90 bool bPrevIsBegPnt; // previous point is first point of a Polyline
91 bool bNextIsEndPnt; // next point is first point of a Polyline
92 sal_uInt16 nPrevPrevPnt; // index of point before previous point
93 sal_uInt16 nNextNextPnt; // index of point after next point
94 bool bControl; // point is a control point
95 bool bIsPrevControl; // point is a control point before a support point
96 bool bIsNextControl; // point is a control point after a support point
97 bool bPrevIsControl; // if nPnt is a support point: a control point comes before
98 bool bNextIsControl; // if nPnt is a support point: a control point comes after
99 sal_uInt16 nPrevPrevPnt0;
100 sal_uInt16 nPrevPnt0;
101 sal_uInt16 nPnt0;
102 sal_uInt16 nNextPnt0;
103 sal_uInt16 nNextNextPnt0;
104 bool bEliminate; // delete point? (is set by MovDrag)
106 bool mbMultiPointDrag;
107 const XPolyPolygon maOrig;
108 XPolyPolygon maMove;
109 std::vector<SdrHdl*> maHandles;
111 public:
112 ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag);
113 void ResetPoly(const SdrPathObj& rPO);
114 bool IsMultiPointDrag() const { return mbMultiPointDrag; }
117 ImpSdrPathDragData::ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag)
118 : aXP(5)
119 , bValid(false)
120 , bClosed(false)
121 , nPoly(0)
122 , nPnt(0)
123 , nPointCount(0)
124 , nPntMax(0)
125 , bBegPnt(false)
126 , bEndPnt(false)
127 , nPrevPnt(0)
128 , nNextPnt(0)
129 , bPrevIsBegPnt(false)
130 , bNextIsEndPnt(false)
131 , nPrevPrevPnt(0)
132 , nNextNextPnt(0)
133 , bControl(false)
134 , bIsPrevControl(false)
135 , bIsNextControl(false)
136 , bPrevIsControl(false)
137 , bNextIsControl(false)
138 , nPrevPrevPnt0(0)
139 , nPrevPnt0(0)
140 , nPnt0(0)
141 , nNextPnt0(0)
142 , nNextNextPnt0(0)
143 , bEliminate(false)
144 , mbMultiPointDrag(bMuPoDr)
145 , maOrig(rPO.GetPathPoly())
146 , maHandles(0)
148 if(mbMultiPointDrag)
150 const SdrMarkView& rMarkView = *rDrag.GetView();
151 const SdrHdlList& rHdlList = rMarkView.GetHdlList();
152 const size_t nHdlCount = rHdlList.GetHdlCount();
153 const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr);
155 for(size_t a = 0; a < nHdlCount; ++a)
157 SdrHdl* pTestHdl = rHdlList.GetHdl(a);
159 if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject)
161 maHandles.push_back(pTestHdl);
165 maMove = maOrig;
166 bValid = true;
168 else
170 bValid=false;
171 bClosed=rPO.IsClosed(); // closed object?
172 nPoly=(sal_uInt16)rHdl.GetPolyNum(); // number of the polygon in the PolyPolygon
173 nPnt=(sal_uInt16)rHdl.GetPointNum(); // number of points in the above polygon
174 const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly));
175 nPointCount=aTmpXP.GetPointCount(); // number of point of the polygon
176 if (nPointCount==0 || (bClosed && nPointCount==1)) return; // minimum of 1 points for Lines, minimum of 2 points for Polygon
177 nPntMax=nPointCount-1; // maximum index
178 bBegPnt=!bClosed && nPnt==0; // dragged point is first point of a Polyline
179 bEndPnt=!bClosed && nPnt==nPntMax; // dragged point is finishing point of a Polyline
180 if (bClosed && nPointCount<=3) { // if polygon is only a line
181 bBegPnt=(nPointCount<3) || nPnt==0;
182 bEndPnt=(nPointCount<3) || nPnt==nPntMax-1;
184 nPrevPnt=nPnt; // index of previous point
185 nNextPnt=nPnt; // index of next point
186 if (!bBegPnt) nPrevPnt=GetPrevPnt(nPnt,nPntMax,bClosed);
187 if (!bEndPnt) nNextPnt=GetNextPnt(nPnt,nPntMax,bClosed);
188 bPrevIsBegPnt=bBegPnt || (!bClosed && nPrevPnt==0);
189 bNextIsEndPnt=bEndPnt || (!bClosed && nNextPnt==nPntMax);
190 nPrevPrevPnt=nPnt; // index of point before previous point
191 nNextNextPnt=nPnt; // index of point after next point
192 if (!bPrevIsBegPnt) nPrevPrevPnt=GetPrevPnt(nPrevPnt,nPntMax,bClosed);
193 if (!bNextIsEndPnt) nNextNextPnt=GetNextPnt(nNextPnt,nPntMax,bClosed);
194 bControl=rHdl.IsPlusHdl(); // point is a control point
195 bIsPrevControl=false; // point is a control point before a support point
196 bIsNextControl=false; // point is a control point after a support point
197 bPrevIsControl=false; // if nPnt is a support point: a control point comes before
198 bNextIsControl=false; // if nPnt is a support point: a control point comes after
199 if (bControl) {
200 bIsPrevControl=aTmpXP.IsControl(nPrevPnt);
201 bIsNextControl=!bIsPrevControl;
202 } else {
203 bPrevIsControl=!bBegPnt && !bPrevIsBegPnt && aTmpXP.GetFlags(nPrevPnt)==PolyFlags::Control;
204 bNextIsControl=!bEndPnt && !bNextIsEndPnt && aTmpXP.GetFlags(nNextPnt)==PolyFlags::Control;
206 nPrevPrevPnt0=nPrevPrevPnt;
207 nPrevPnt0 =nPrevPnt;
208 nPnt0 =nPnt;
209 nNextPnt0 =nNextPnt;
210 nNextNextPnt0=nNextNextPnt;
211 nPrevPrevPnt=0;
212 nPrevPnt=1;
213 nPnt=2;
214 nNextPnt=3;
215 nNextNextPnt=4;
216 bEliminate=false;
217 ResetPoly(rPO);
218 bValid=true;
222 void ImpSdrPathDragData::ResetPoly(const SdrPathObj& rPO)
224 const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly));
225 aXP[0]=aTmpXP[nPrevPrevPnt0]; aXP.SetFlags(0,aTmpXP.GetFlags(nPrevPrevPnt0));
226 aXP[1]=aTmpXP[nPrevPnt0]; aXP.SetFlags(1,aTmpXP.GetFlags(nPrevPnt0));
227 aXP[2]=aTmpXP[nPnt0]; aXP.SetFlags(2,aTmpXP.GetFlags(nPnt0));
228 aXP[3]=aTmpXP[nNextPnt0]; aXP.SetFlags(3,aTmpXP.GetFlags(nNextPnt0));
229 aXP[4]=aTmpXP[nNextNextPnt0]; aXP.SetFlags(4,aTmpXP.GetFlags(nNextNextPnt0));
232 struct ImpPathCreateUser : public SdrDragStatUserData
234 Point aBezControl0;
235 Point aBezStart;
236 Point aBezCtrl1;
237 Point aBezCtrl2;
238 Point aBezEnd;
239 Point aCircStart;
240 Point aCircEnd;
241 Point aCircCenter;
242 Point aLineStart;
243 Point aLineEnd;
244 Point aRectP1;
245 Point aRectP2;
246 Point aRectP3;
247 long nCircRadius;
248 long nCircStAngle;
249 long nCircRelAngle;
250 bool bBezier;
251 bool bBezHasCtrl0;
252 bool bCircle;
253 bool bAngleSnap;
254 bool bLine;
255 bool bLine90;
256 bool bRect;
257 bool bMixedCreate;
258 sal_uInt16 nBezierStartPoint;
259 SdrObjKind eStartKind;
260 SdrObjKind eAktKind;
262 public:
263 ImpPathCreateUser(): nCircRadius(0),nCircStAngle(0),nCircRelAngle(0),
264 bBezier(false),bBezHasCtrl0(false),bCircle(false),bAngleSnap(false),bLine(false),bLine90(false),bRect(false),
265 bMixedCreate(false),nBezierStartPoint(0),eStartKind(OBJ_NONE),eAktKind(OBJ_NONE) { }
267 void ResetFormFlags() { bBezier=false; bCircle=false; bLine=false; bRect=false; }
268 bool IsFormFlag() const { return bBezier || bCircle || bLine || bRect; }
269 XPolygon GetFormPoly() const;
270 void CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown);
271 XPolygon GetBezierPoly() const;
272 void CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
273 XPolygon GetCirclePoly() const;
274 void CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
275 static Point CalcLine(const Point& rCsr, long nDirX, long nDirY, SdrView const * pView);
276 XPolygon GetLinePoly() const;
277 void CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
278 XPolygon GetRectPoly() const;
281 XPolygon ImpPathCreateUser::GetFormPoly() const
283 if (bBezier) return GetBezierPoly();
284 if (bCircle) return GetCirclePoly();
285 if (bLine) return GetLinePoly();
286 if (bRect) return GetRectPoly();
287 return XPolygon();
290 void ImpPathCreateUser::CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown)
292 aBezStart=rP1;
293 aBezCtrl1=rP1+rDir;
294 aBezCtrl2=rP2;
296 // #i21479#
297 // Also copy the end point when no end point is set yet
298 if (!bMouseDown || (0 == aBezEnd.X() && 0 == aBezEnd.Y())) aBezEnd=rP2;
300 bBezier=true;
303 XPolygon ImpPathCreateUser::GetBezierPoly() const
305 XPolygon aXP(4);
306 aXP[0]=aBezStart; aXP.SetFlags(0,PolyFlags::Smooth);
307 aXP[1]=aBezCtrl1; aXP.SetFlags(1,PolyFlags::Control);
308 aXP[2]=aBezCtrl2; aXP.SetFlags(2,PolyFlags::Control);
309 aXP[3]=aBezEnd;
310 return aXP;
313 void ImpPathCreateUser::CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
315 long nTangAngle=GetAngle(rDir);
316 aCircStart=rP1;
317 aCircEnd=rP2;
318 aCircCenter=rP1;
319 long dx=rP2.X()-rP1.X();
320 long dy=rP2.Y()-rP1.Y();
321 long dAngle=GetAngle(Point(dx,dy))-nTangAngle;
322 dAngle=NormAngle360(dAngle);
323 long nTmpAngle=NormAngle360(9000-dAngle);
324 bool bRet=nTmpAngle!=9000 && nTmpAngle!=27000;
325 long nRad=0;
326 if (bRet) {
327 double cs=cos(nTmpAngle*nPi180);
328 double nR=(double)GetLen(Point(dx,dy))/cs/2;
329 nRad=std::abs(svx::Round(nR));
331 if (dAngle<18000) {
332 nCircStAngle=NormAngle360(nTangAngle-9000);
333 nCircRelAngle=NormAngle360(2*dAngle);
334 aCircCenter.X()+=svx::Round(nRad*cos((nTangAngle+9000)*nPi180));
335 aCircCenter.Y()-=svx::Round(nRad*sin((nTangAngle+9000)*nPi180));
336 } else {
337 nCircStAngle=NormAngle360(nTangAngle+9000);
338 nCircRelAngle=-NormAngle360(36000-2*dAngle);
339 aCircCenter.X()+=svx::Round(nRad*cos((nTangAngle-9000)*nPi180));
340 aCircCenter.Y()-=svx::Round(nRad*sin((nTangAngle-9000)*nPi180));
342 bAngleSnap=pView!=nullptr && pView->IsAngleSnapEnabled();
343 if (bAngleSnap) {
344 long nSA=pView->GetSnapAngle();
345 if (nSA!=0) { // angle snapping
346 bool bNeg=nCircRelAngle<0;
347 if (bNeg) nCircRelAngle=-nCircRelAngle;
348 nCircRelAngle+=nSA/2;
349 nCircRelAngle/=nSA;
350 nCircRelAngle*=nSA;
351 nCircRelAngle=NormAngle360(nCircRelAngle);
352 if (bNeg) nCircRelAngle=-nCircRelAngle;
355 nCircRadius=nRad;
356 if (nRad==0 || std::abs(nCircRelAngle)<5) bRet=false;
357 bCircle=bRet;
360 XPolygon ImpPathCreateUser::GetCirclePoly() const
362 if (nCircRelAngle>=0) {
363 XPolygon aXP(aCircCenter,nCircRadius,nCircRadius,
364 sal_uInt16((nCircStAngle+5)/10),sal_uInt16((nCircStAngle+nCircRelAngle+5)/10),false);
365 aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth);
366 if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd;
367 return aXP;
368 } else {
369 XPolygon aXP(aCircCenter,nCircRadius,nCircRadius,
370 sal_uInt16(NormAngle360(nCircStAngle+nCircRelAngle+5)/10),sal_uInt16((nCircStAngle+5)/10),false);
371 sal_uInt16 nCount=aXP.GetPointCount();
372 for (sal_uInt16 nNum=nCount/2; nNum>0;) {
373 nNum--; // reverse XPoly's order of points
374 sal_uInt16 n2=nCount-nNum-1;
375 Point aPt(aXP[nNum]);
376 aXP[nNum]=aXP[n2];
377 aXP[n2]=aPt;
379 aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth);
380 if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd;
381 return aXP;
385 Point ImpPathCreateUser::CalcLine(const Point& aCsr, long nDirX, long nDirY, SdrView const * pView)
387 long x=aCsr.X();
388 long y=aCsr.Y();
389 bool bHLin=nDirY==0;
390 bool bVLin=nDirX==0;
391 if (bHLin) y=0;
392 else if (bVLin) x=0;
393 else {
394 long x1=BigMulDiv(y,nDirX,nDirY);
395 long y1=y;
396 long x2=x;
397 long y2=BigMulDiv(x,nDirY,nDirX);
398 long l1=std::abs(x1)+std::abs(y1);
399 long l2=std::abs(x2)+std::abs(y2);
400 if ((l1<=l2) != (pView!=nullptr && pView->IsBigOrtho())) {
401 x=x1; y=y1;
402 } else {
403 x=x2; y=y2;
406 return Point(x,y);
409 void ImpPathCreateUser::CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
411 aLineStart=rP1;
412 aLineEnd=rP2;
413 bLine90=false;
414 if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bLine=false; return; }
415 Point aTmpPt(rP2-rP1);
416 long nDirX=rDir.X();
417 long nDirY=rDir.Y();
418 Point aP1(CalcLine(aTmpPt, nDirX, nDirY,pView)); aP1-=aTmpPt; long nQ1=std::abs(aP1.X())+std::abs(aP1.Y());
419 Point aP2(CalcLine(aTmpPt, nDirY,-nDirX,pView)); aP2-=aTmpPt; long nQ2=std::abs(aP2.X())+std::abs(aP2.Y());
420 if (pView!=nullptr && pView->IsOrtho()) nQ1=0; // Ortho turns off at right angle
421 bLine90=nQ1>2*nQ2;
422 if (!bLine90) { // smooth transition
423 aLineEnd+=aP1;
424 } else { // rectangular transition
425 aLineEnd+=aP2;
427 bLine=true;
430 XPolygon ImpPathCreateUser::GetLinePoly() const
432 XPolygon aXP(2);
433 aXP[0]=aLineStart; if (!bLine90) aXP.SetFlags(0,PolyFlags::Smooth);
434 aXP[1]=aLineEnd;
435 return aXP;
438 void ImpPathCreateUser::CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
440 aRectP1=rP1;
441 aRectP2=rP1;
442 aRectP3=rP2;
443 if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bRect=false; return; }
444 Point aTmpPt(rP2-rP1);
445 long nDirX=rDir.X();
446 long nDirY=rDir.Y();
447 long x=aTmpPt.X();
448 long y=aTmpPt.Y();
449 bool bHLin=nDirY==0;
450 bool bVLin=nDirX==0;
451 if (bHLin) y=0;
452 else if (bVLin) x=0;
453 else {
454 y=BigMulDiv(x,nDirY,nDirX);
455 long nHypLen=aTmpPt.Y()-y;
456 long nTangAngle=-GetAngle(rDir);
457 // sin=g/h, g=h*sin
458 double a=nTangAngle*nPi180;
459 double sn=sin(a);
460 double cs=cos(a);
461 double nGKathLen=nHypLen*sn;
462 y+=svx::Round(nGKathLen*sn);
463 x+=svx::Round(nGKathLen*cs);
465 aRectP2.X()+=x;
466 aRectP2.Y()+=y;
467 if (pView!=nullptr && pView->IsOrtho()) {
468 long dx1=aRectP2.X()-aRectP1.X(); long dx1a=std::abs(dx1);
469 long dy1=aRectP2.Y()-aRectP1.Y(); long dy1a=std::abs(dy1);
470 long dx2=aRectP3.X()-aRectP2.X(); long dx2a=std::abs(dx2);
471 long dy2=aRectP3.Y()-aRectP2.Y(); long dy2a=std::abs(dy2);
472 bool b1MoreThan2=dx1a+dy1a>dx2a+dy2a;
473 if (b1MoreThan2 != pView->IsBigOrtho()) {
474 long xtemp=dy2a-dx1a; if (dx1<0) xtemp=-xtemp;
475 long ytemp=dx2a-dy1a; if (dy1<0) ytemp=-ytemp;
476 aRectP2.X()+=xtemp;
477 aRectP2.Y()+=ytemp;
478 aRectP3.X()+=xtemp;
479 aRectP3.Y()+=ytemp;
480 } else {
481 long xtemp=dy1a-dx2a; if (dx2<0) xtemp=-xtemp;
482 long ytemp=dx1a-dy2a; if (dy2<0) ytemp=-ytemp;
483 aRectP3.X()+=xtemp;
484 aRectP3.Y()+=ytemp;
487 bRect=true;
490 XPolygon ImpPathCreateUser::GetRectPoly() const
492 XPolygon aXP(3);
493 aXP[0]=aRectP1; aXP.SetFlags(0,PolyFlags::Smooth);
494 aXP[1]=aRectP2;
495 if (aRectP3!=aRectP2) aXP[2]=aRectP3;
496 return aXP;
499 class ImpPathForDragAndCreate
501 SdrPathObj& mrSdrPathObject;
502 XPolyPolygon aPathPolygon;
503 SdrObjKind meObjectKind;
504 std::unique_ptr<ImpSdrPathDragData>
505 mpSdrPathDragData;
506 bool mbCreating;
508 public:
509 explicit ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject);
511 // drag stuff
512 bool beginPathDrag( SdrDragStat const & rDrag ) const;
513 bool movePathDrag( SdrDragStat& rDrag ) const;
514 bool endPathDrag( SdrDragStat const & rDrag );
515 OUString getSpecialDragComment(const SdrDragStat& rDrag) const;
516 basegfx::B2DPolyPolygon getSpecialDragPoly(const SdrDragStat& rDrag) const;
518 // create stuff
519 bool BegCreate(SdrDragStat& rStat);
520 bool MovCreate(SdrDragStat& rStat);
521 bool EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd);
522 bool BckCreate(SdrDragStat& rStat);
523 void BrkCreate(SdrDragStat& rStat);
524 Pointer GetCreatePointer() const;
526 // helping stuff
527 static bool IsClosed(SdrObjKind eKind) { return eKind==OBJ_POLY || eKind==OBJ_PATHPOLY || eKind==OBJ_PATHFILL || eKind==OBJ_FREEFILL || eKind==OBJ_SPLNFILL; }
528 static bool IsFreeHand(SdrObjKind eKind) { return eKind==OBJ_FREELINE || eKind==OBJ_FREEFILL; }
529 static bool IsBezier(SdrObjKind eKind) { return eKind==OBJ_PATHLINE || eKind==OBJ_PATHFILL; }
530 bool IsCreating() const { return mbCreating; }
532 // get the polygon
533 basegfx::B2DPolyPolygon TakeObjectPolyPolygon(const SdrDragStat& rDrag) const;
534 static basegfx::B2DPolyPolygon TakeDragPolyPolygon(const SdrDragStat& rDrag);
535 basegfx::B2DPolyPolygon getModifiedPolyPolygon() const { return aPathPolygon.getB2DPolyPolygon(); }
538 ImpPathForDragAndCreate::ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject)
539 : mrSdrPathObject(rSdrPathObject),
540 aPathPolygon(rSdrPathObject.GetPathPoly()),
541 meObjectKind(mrSdrPathObject.meKind),
542 mpSdrPathDragData(nullptr),
543 mbCreating(false)
547 bool ImpPathForDragAndCreate::beginPathDrag( SdrDragStat const & rDrag ) const
549 const SdrHdl* pHdl=rDrag.GetHdl();
550 if(!pHdl)
551 return false;
553 bool bMultiPointDrag(true);
555 if(aPathPolygon[(sal_uInt16)pHdl->GetPolyNum()].IsControl((sal_uInt16)pHdl->GetPointNum()))
556 bMultiPointDrag = false;
558 if(bMultiPointDrag)
560 const SdrMarkView& rMarkView = *rDrag.GetView();
561 const SdrHdlList& rHdlList = rMarkView.GetHdlList();
562 const size_t nHdlCount = rHdlList.GetHdlCount();
563 const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr);
564 sal_uInt32 nSelectedPoints(0);
566 for(size_t a = 0; a < nHdlCount; ++a)
568 SdrHdl* pTestHdl = rHdlList.GetHdl(a);
570 if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject)
572 nSelectedPoints++;
576 if(nSelectedPoints <= 1)
577 bMultiPointDrag = false;
580 const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset( new ImpSdrPathDragData(mrSdrPathObject,*pHdl,bMultiPointDrag,rDrag) );
582 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
584 OSL_FAIL("ImpPathForDragAndCreate::BegDrag(): ImpSdrPathDragData is invalid.");
585 const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset();
586 return false;
589 return true;
592 bool ImpPathForDragAndCreate::movePathDrag( SdrDragStat& rDrag ) const
594 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
596 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
597 return false;
600 if(mpSdrPathDragData->IsMultiPointDrag())
602 Point aDelta(rDrag.GetNow() - rDrag.GetStart());
604 if(aDelta.X() || aDelta.Y())
606 for(SdrHdl* pHandle : mpSdrPathDragData->maHandles)
608 const sal_uInt16 nPolyIndex((sal_uInt16)pHandle->GetPolyNum());
609 const sal_uInt16 nPointIndex((sal_uInt16)pHandle->GetPointNum());
610 const XPolygon& rOrig = mpSdrPathDragData->maOrig[nPolyIndex];
611 XPolygon& rMove = mpSdrPathDragData->maMove[nPolyIndex];
612 const sal_uInt16 nPointCount(rOrig.GetPointCount());
613 bool bClosed(rOrig[0] == rOrig[nPointCount-1]);
615 // move point itself
616 rMove[nPointIndex] = rOrig[nPointIndex] + aDelta;
618 // when point is first and poly closed, move close point, too.
619 if(nPointCount > 0 && !nPointIndex && bClosed)
621 rMove[nPointCount - 1] = rOrig[nPointCount - 1] + aDelta;
623 // when moving the last point it may be necessary to move the
624 // control point in front of this one, too.
625 if(nPointCount > 1 && rOrig.IsControl(nPointCount - 2))
626 rMove[nPointCount - 2] = rOrig[nPointCount - 2] + aDelta;
629 // is a control point before this?
630 if(nPointIndex > 0 && rOrig.IsControl(nPointIndex - 1))
632 // Yes, move it, too
633 rMove[nPointIndex - 1] = rOrig[nPointIndex - 1] + aDelta;
636 // is a control point after this?
637 if(nPointIndex + 1 < nPointCount && rOrig.IsControl(nPointIndex + 1))
639 // Yes, move it, too
640 rMove[nPointIndex + 1] = rOrig[nPointIndex + 1] + aDelta;
645 else
647 mpSdrPathDragData->ResetPoly(mrSdrPathObject);
649 // copy certain data locally to use less code and have faster access times
650 bool bClosed =mpSdrPathDragData->bClosed ; // closed object?
651 sal_uInt16 nPnt =mpSdrPathDragData->nPnt ; // number of point in the above polygon
652 bool bBegPnt =mpSdrPathDragData->bBegPnt ; // dragged point is first point of a Polyline
653 bool bEndPnt =mpSdrPathDragData->bEndPnt ; // dragged point is last point of a Polyline
654 sal_uInt16 nPrevPnt =mpSdrPathDragData->nPrevPnt ; // index of previous point
655 sal_uInt16 nNextPnt =mpSdrPathDragData->nNextPnt ; // index of next point
656 bool bPrevIsBegPnt =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline
657 bool bNextIsEndPnt =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline
658 sal_uInt16 nPrevPrevPnt =mpSdrPathDragData->nPrevPrevPnt ; // index of the point before the previous point
659 sal_uInt16 nNextNextPnt =mpSdrPathDragData->nNextNextPnt ; // index if the point after the next point
660 bool bControl =mpSdrPathDragData->bControl ; // point is a control point
661 bool bIsNextControl =mpSdrPathDragData->bIsNextControl; // point is a control point after a support point
662 bool bPrevIsControl =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before
663 bool bNextIsControl =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after
665 // Ortho for lines/polygons: keep angle
666 if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho()) {
667 bool bBigOrtho=rDrag.GetView()->IsBigOrtho();
668 Point aPos(rDrag.GetNow()); // current position
669 Point aPnt(mpSdrPathDragData->aXP[nPnt]); // the dragged point
670 sal_uInt16 nPnt1=0xFFFF,nPnt2=0xFFFF; // its neighboring points
671 Point aNeuPos1,aNeuPos2; // new alternative for aPos
672 bool bPnt1 = false, bPnt2 = false; // are these valid alternatives?
673 if (!bClosed && mpSdrPathDragData->nPointCount>=2) { // minimum of 2 points for lines
674 if (!bBegPnt) nPnt1=nPrevPnt;
675 if (!bEndPnt) nPnt2=nNextPnt;
677 if (bClosed && mpSdrPathDragData->nPointCount>=3) { // minimum of 3 points for polygon
678 nPnt1=nPrevPnt;
679 nPnt2=nNextPnt;
681 if (nPnt1!=0xFFFF && !bPrevIsControl) {
682 Point aPnt1=mpSdrPathDragData->aXP[nPnt1];
683 long ndx0=aPnt.X()-aPnt1.X();
684 long ndy0=aPnt.Y()-aPnt1.Y();
685 bool bHLin=ndy0==0;
686 bool bVLin=ndx0==0;
687 if (!bHLin || !bVLin) {
688 long ndx=aPos.X()-aPnt1.X();
689 long ndy=aPos.Y()-aPnt1.Y();
690 bPnt1=true;
691 double nXFact=0; if (!bVLin) nXFact=(double)ndx/(double)ndx0;
692 double nYFact=0; if (!bHLin) nYFact=(double)ndy/(double)ndy0;
693 bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho);
694 bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho);
695 if (bHor) ndy=long(ndy0*nXFact);
696 if (bVer) ndx=long(ndx0*nYFact);
697 aNeuPos1=aPnt1;
698 aNeuPos1.X()+=ndx;
699 aNeuPos1.Y()+=ndy;
702 if (nPnt2!=0xFFFF && !bNextIsControl) {
703 Point aPnt2=mpSdrPathDragData->aXP[nPnt2];
704 long ndx0=aPnt.X()-aPnt2.X();
705 long ndy0=aPnt.Y()-aPnt2.Y();
706 bool bHLin=ndy0==0;
707 bool bVLin=ndx0==0;
708 if (!bHLin || !bVLin) {
709 long ndx=aPos.X()-aPnt2.X();
710 long ndy=aPos.Y()-aPnt2.Y();
711 bPnt2=true;
712 double nXFact=0; if (!bVLin) nXFact=(double)ndx/(double)ndx0;
713 double nYFact=0; if (!bHLin) nYFact=(double)ndy/(double)ndy0;
714 bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho);
715 bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho);
716 if (bHor) ndy=long(ndy0*nXFact);
717 if (bVer) ndx=long(ndx0*nYFact);
718 aNeuPos2=aPnt2;
719 aNeuPos2.X()+=ndx;
720 aNeuPos2.Y()+=ndy;
723 if (bPnt1 && bPnt2) { // both alternatives exist (and compete)
724 BigInt nX1(aNeuPos1.X()-aPos.X()); nX1*=nX1;
725 BigInt nY1(aNeuPos1.Y()-aPos.Y()); nY1*=nY1;
726 BigInt nX2(aNeuPos2.X()-aPos.X()); nX2*=nX2;
727 BigInt nY2(aNeuPos2.Y()-aPos.Y()); nY2*=nY2;
728 nX1+=nY1; // correction distance to square
729 nX2+=nY2; // correction distance to square
730 // let the alternative that allows fewer correction win
731 if (nX1<nX2) bPnt2=false; else bPnt1=false;
733 if (bPnt1) rDrag.SetNow(aNeuPos1);
734 if (bPnt2) rDrag.SetNow(aNeuPos2);
736 rDrag.SetActionRect(tools::Rectangle(rDrag.GetNow(),rDrag.GetNow()));
738 // specially for IBM: Eliminate points if both adjoining lines form near 180 degrees angle anyway
739 if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsEliminatePolyPoints() &&
740 !bBegPnt && !bEndPnt && !bPrevIsControl && !bNextIsControl)
742 Point aPt(mpSdrPathDragData->aXP[nNextPnt]);
743 aPt-=rDrag.GetNow();
744 long nAngle1=GetAngle(aPt);
745 aPt=rDrag.GetNow();
746 aPt-=mpSdrPathDragData->aXP[nPrevPnt];
747 long nAngle2=GetAngle(aPt);
748 long nDiff=nAngle1-nAngle2;
749 nDiff=std::abs(nDiff);
750 mpSdrPathDragData->bEliminate=nDiff<=rDrag.GetView()->GetEliminatePolyPointLimitAngle();
751 if (mpSdrPathDragData->bEliminate) { // adapt position, Smooth is true for the ends
752 aPt=mpSdrPathDragData->aXP[nNextPnt];
753 aPt+=mpSdrPathDragData->aXP[nPrevPnt];
754 aPt/=2;
755 rDrag.SetNow(aPt);
759 // we dragged by this distance
760 Point aDiff(rDrag.GetNow()); aDiff-=mpSdrPathDragData->aXP[nPnt];
762 /* There are 8 possible cases:
763 X 1. A control point neither on the left nor on the right.
764 o--X--o 2. There are control points on the left and the right, we are dragging a support point.
765 o--X 3. There is a control point on the left, we are dragging a support point.
766 X--o 4. There is a control point on the right, we are dragging a support point.
767 x--O--o 5. There are control points on the left and the right, we are dragging the left one.
768 x--O 6. There is a control point on the left, we are dragging it.
769 o--O--x 7. There are control points on the left and the right, we are dragging the right one.
770 O--x 8. There is a control point on the right, we are dragging it.
771 Note: modifying a line (not a curve!) might create a curve on the other end of the line
772 if Smooth is set there (with control points aligned to line).
775 mpSdrPathDragData->aXP[nPnt]+=aDiff;
777 // now check symmetric plus handles
778 if (bControl) { // cases 5,6,7,8
779 sal_uInt16 nSt; // the associated support point
780 sal_uInt16 nFix; // the opposing control point
781 if (bIsNextControl) { // if the next one is a control point, the on before has to be a support point
782 nSt=nPrevPnt;
783 nFix=nPrevPrevPnt;
784 } else {
785 nSt=nNextPnt;
786 nFix=nNextNextPnt;
788 if (mpSdrPathDragData->aXP.IsSmooth(nSt)) {
789 mpSdrPathDragData->aXP.CalcSmoothJoin(nSt,nPnt,nFix);
793 if (!bControl) { // Cases 1,2,3,4. In case 1, nothing happens; in cases 3 and 4, there is more following below.
794 // move both control points
795 if (bPrevIsControl) mpSdrPathDragData->aXP[nPrevPnt]+=aDiff;
796 if (bNextIsControl) mpSdrPathDragData->aXP[nNextPnt]+=aDiff;
797 // align control point to line, if appropriate
798 if (mpSdrPathDragData->aXP.IsSmooth(nPnt)) {
799 if (bPrevIsControl && !bNextIsControl && !bEndPnt) { // case 3
800 mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nNextPnt,nPrevPnt);
802 if (bNextIsControl && !bPrevIsControl && !bBegPnt) { // case 4
803 mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nPrevPnt,nNextPnt);
806 // Now check the other ends of the line (nPnt+-1). If there is a
807 // curve (IsControl(nPnt+-2)) with SmoothJoin (nPnt+-1), the
808 // associated control point (nPnt+-2) has to be adapted.
809 if (!bBegPnt && !bPrevIsControl && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsSmooth(nPrevPnt)) {
810 if (mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
811 mpSdrPathDragData->aXP.CalcSmoothJoin(nPrevPnt,nPnt,nPrevPrevPnt);
814 if (!bEndPnt && !bNextIsControl && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsSmooth(nNextPnt)) {
815 if (mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
816 mpSdrPathDragData->aXP.CalcSmoothJoin(nNextPnt,nPnt,nNextNextPnt);
822 return true;
825 bool ImpPathForDragAndCreate::endPathDrag(SdrDragStat const & rDrag)
827 Point aLinePt1;
828 Point aLinePt2;
829 bool bLineGlueMirror(OBJ_LINE == meObjectKind);
830 if (bLineGlueMirror) {
831 XPolygon& rXP=aPathPolygon[0];
832 aLinePt1=rXP[0];
833 aLinePt2=rXP[1];
836 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
838 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
839 return false;
842 if(mpSdrPathDragData->IsMultiPointDrag())
844 aPathPolygon = mpSdrPathDragData->maMove;
846 else
848 const SdrHdl* pHdl=rDrag.GetHdl();
850 // reference the polygon
851 XPolygon& rXP=aPathPolygon[(sal_uInt16)pHdl->GetPolyNum()];
853 // the 5 points that might have changed
854 if (!mpSdrPathDragData->bPrevIsBegPnt) rXP[mpSdrPathDragData->nPrevPrevPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPrevPnt];
855 if (!mpSdrPathDragData->bNextIsEndPnt) rXP[mpSdrPathDragData->nNextNextPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nNextNextPnt];
856 if (!mpSdrPathDragData->bBegPnt) rXP[mpSdrPathDragData->nPrevPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPnt];
857 if (!mpSdrPathDragData->bEndPnt) rXP[mpSdrPathDragData->nNextPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nNextPnt];
858 rXP[mpSdrPathDragData->nPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nPnt];
860 // for closed objects: last point has to be equal to first point
861 if (mpSdrPathDragData->bClosed) rXP[rXP.GetPointCount()-1]=rXP[0];
863 if (mpSdrPathDragData->bEliminate)
865 basegfx::B2DPolyPolygon aTempPolyPolygon(aPathPolygon.getB2DPolyPolygon());
866 sal_uInt32 nPoly,nPnt;
868 if(PolyPolygonEditor::GetRelativePolyPoint(aTempPolyPolygon, rDrag.GetHdl()->GetSourceHdlNum(), nPoly, nPnt))
870 basegfx::B2DPolygon aCandidate(aTempPolyPolygon.getB2DPolygon(nPoly));
871 aCandidate.remove(nPnt);
873 if(aCandidate.count() < 2)
875 aTempPolyPolygon.remove(nPoly);
877 else
879 aTempPolyPolygon.setB2DPolygon(nPoly, aCandidate);
883 aPathPolygon = XPolyPolygon(aTempPolyPolygon);
886 // adapt angle for text beneath a simple line
887 if (bLineGlueMirror)
889 Point aLinePt1_(aPathPolygon[0][0]);
890 Point aLinePt2_(aPathPolygon[0][1]);
891 bool bXMirr=(aLinePt1_.X()>aLinePt2_.X())!=(aLinePt1.X()>aLinePt2.X());
892 bool bYMirr=(aLinePt1_.Y()>aLinePt2_.Y())!=(aLinePt1.Y()>aLinePt2.Y());
893 if (bXMirr || bYMirr) {
894 Point aRef1(mrSdrPathObject.GetSnapRect().Center());
895 if (bXMirr) {
896 Point aRef2(aRef1);
897 aRef2.Y()++;
898 mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2);
900 if (bYMirr) {
901 Point aRef2(aRef1);
902 aRef2.X()++;
903 mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2);
909 mpSdrPathDragData.reset();
911 return true;
914 OUString ImpPathForDragAndCreate::getSpecialDragComment(const SdrDragStat& rDrag) const
916 OUString aStr;
917 const SdrHdl* pHdl = rDrag.GetHdl();
918 const bool bCreateComment(rDrag.GetView() && &mrSdrPathObject == rDrag.GetView()->GetCreateObj());
920 if(bCreateComment && rDrag.GetUser())
922 // #i103058# re-add old creation comment mode
923 const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser());
924 const SdrObjKind eKindMerk(meObjectKind);
925 mrSdrPathObject.meKind = pU->eAktKind;
926 OUString aTmp;
927 mrSdrPathObject.ImpTakeDescriptionStr(STR_ViewCreateObj, aTmp);
928 aStr = aTmp;
929 mrSdrPathObject.meKind = eKindMerk;
931 Point aPrev(rDrag.GetPrev());
932 Point aNow(rDrag.GetNow());
934 if(pU->bLine)
935 aNow = pU->aLineEnd;
937 aNow -= aPrev;
938 aStr += " (";
940 OUString aMetr;
942 if(pU->bCircle)
944 SdrModel::TakeAngleStr(std::abs(pU->nCircRelAngle), aMetr);
945 aStr += aMetr;
946 aStr += " r=";
947 mrSdrPathObject.GetModel()->TakeMetricStr(pU->nCircRadius, aMetr, true);
948 aStr += aMetr;
951 aStr += "dx=";
952 mrSdrPathObject.GetModel()->TakeMetricStr(aNow.X(), aMetr, true);
953 aStr += aMetr;
955 aStr += " dy=";
956 mrSdrPathObject.GetModel()->TakeMetricStr(aNow.Y(), aMetr, true);
957 aStr += aMetr;
959 if(!IsFreeHand(meObjectKind))
961 sal_Int32 nLen(GetLen(aNow));
962 aStr += " l=";
963 mrSdrPathObject.GetModel()->TakeMetricStr(nLen, aMetr, true);
964 aStr += aMetr;
966 sal_Int32 nAngle(GetAngle(aNow));
967 aStr += " ";
968 SdrModel::TakeAngleStr(nAngle, aMetr);
969 aStr += aMetr;
972 aStr += ")";
974 else if(!mrSdrPathObject.GetModel() || !pHdl)
976 // #i103058# fallback when no model and/or Handle, both needed
977 // for else-path
978 OUString aTmp;
979 mrSdrPathObject.ImpTakeDescriptionStr(STR_DragPathObj, aTmp);
980 aStr = aTmp;
982 else
984 // #i103058# standard for modification; model and handle needed
985 ImpSdrPathDragData* pDragData = mpSdrPathDragData.get();
987 if(!pDragData)
989 // getSpecialDragComment is also used from create, so fallback to GetUser()
990 // when mpSdrPathDragData is not set
991 pDragData = static_cast<ImpSdrPathDragData*>(rDrag.GetUser());
994 if(!pDragData)
996 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
997 return OUString();
1000 if(!pDragData->IsMultiPointDrag() && pDragData->bEliminate)
1002 // point of ...
1003 OUString aTmp;
1004 mrSdrPathObject.ImpTakeDescriptionStr(STR_ViewMarkedPoint, aTmp);
1005 aStr = aTmp;
1007 // delete %O
1008 OUString aStr2(ImpGetResStr(STR_EditDelete));
1010 // UNICODE: delete point of ...
1011 aStr2 = aStr2.replaceFirst("%1", aStr);
1013 return aStr2;
1016 // dx=0.00 dy=0.00 -- both sides bezier
1017 // dx=0.00 dy=0.00 l=0.00 0.00\302\260 -- one bezier/lever on one side, a start, or an ending
1018 // dx=0.00 dy=0.00 l=0.00 0.00\302\260 / l=0.00 0.00\302\260 -- in between
1019 OUString aMetr;
1020 Point aBeg(rDrag.GetStart());
1021 Point aNow(rDrag.GetNow());
1023 aStr.clear();
1024 aStr += "dx=";
1025 mrSdrPathObject.GetModel()->TakeMetricStr(aNow.X() - aBeg.X(), aMetr, true);
1026 aStr += aMetr;
1028 aStr += " dy=";
1029 mrSdrPathObject.GetModel()->TakeMetricStr(aNow.Y() - aBeg.Y(), aMetr, true);
1030 aStr += aMetr;
1032 if(!pDragData->IsMultiPointDrag())
1034 sal_uInt16 nPntNum((sal_uInt16)pHdl->GetPointNum());
1035 const XPolygon& rXPoly = aPathPolygon[(sal_uInt16)rDrag.GetHdl()->GetPolyNum()];
1036 sal_uInt16 nPointCount(rXPoly.GetPointCount());
1037 bool bClose(IsClosed(meObjectKind));
1039 if(bClose)
1040 nPointCount--;
1042 if(pHdl->IsPlusHdl())
1044 // lever
1045 sal_uInt16 nRef(nPntNum);
1047 if(rXPoly.IsControl(nPntNum + 1))
1048 nRef--;
1049 else
1050 nRef++;
1052 aNow -= rXPoly[nRef];
1054 sal_Int32 nLen(GetLen(aNow));
1055 aStr += " l=";
1056 mrSdrPathObject.GetModel()->TakeMetricStr(nLen, aMetr, true);
1057 aStr += aMetr;
1059 sal_Int32 nAngle(GetAngle(aNow));
1060 aStr += " ";
1061 SdrModel::TakeAngleStr(nAngle, aMetr);
1062 aStr += aMetr;
1064 else if(nPointCount > 1)
1066 sal_uInt16 nPntMax(nPointCount - 1);
1067 bool bIsClosed(IsClosed(meObjectKind));
1068 bool bPt1(nPntNum > 0);
1069 bool bPt2(nPntNum < nPntMax);
1071 if(bIsClosed && nPointCount > 2)
1073 bPt1 = true;
1074 bPt2 = true;
1077 sal_uInt16 nPt1,nPt2;
1079 if(nPntNum > 0)
1080 nPt1 = nPntNum - 1;
1081 else
1082 nPt1 = nPntMax;
1084 if(nPntNum < nPntMax)
1085 nPt2 = nPntNum + 1;
1086 else
1087 nPt2 = 0;
1089 if(bPt1 && rXPoly.IsControl(nPt1))
1090 bPt1 = false; // don't display
1092 if(bPt2 && rXPoly.IsControl(nPt2))
1093 bPt2 = false; // of bezier data
1095 if(bPt1)
1097 Point aPt(aNow);
1098 aPt -= rXPoly[nPt1];
1100 sal_Int32 nLen(GetLen(aPt));
1101 aStr += " l=";
1102 mrSdrPathObject.GetModel()->TakeMetricStr(nLen, aMetr, true);
1103 aStr += aMetr;
1105 sal_Int32 nAngle(GetAngle(aPt));
1106 aStr += " ";
1107 SdrModel::TakeAngleStr(nAngle, aMetr);
1108 aStr += aMetr;
1111 if(bPt2)
1113 if(bPt1)
1114 aStr += " / ";
1115 else
1116 aStr += " ";
1118 Point aPt(aNow);
1119 aPt -= rXPoly[nPt2];
1121 sal_Int32 nLen(GetLen(aPt));
1122 aStr += "l=";
1123 mrSdrPathObject.GetModel()->TakeMetricStr(nLen, aMetr, true);
1124 aStr += aMetr;
1126 sal_Int32 nAngle(GetAngle(aPt));
1127 aStr += " ";
1128 SdrModel::TakeAngleStr(nAngle, aMetr);
1129 aStr += aMetr;
1135 return aStr;
1138 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::getSpecialDragPoly(const SdrDragStat& rDrag) const
1140 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
1142 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
1143 return basegfx::B2DPolyPolygon();
1146 XPolyPolygon aRetval;
1148 if(mpSdrPathDragData->IsMultiPointDrag())
1150 aRetval.Insert(mpSdrPathDragData->maMove);
1152 else
1154 const XPolygon& rXP=aPathPolygon[(sal_uInt16)rDrag.GetHdl()->GetPolyNum()];
1155 if (rXP.GetPointCount()<=2) {
1156 XPolygon aXPoly(rXP);
1157 aXPoly[(sal_uInt16)rDrag.GetHdl()->GetPointNum()]=rDrag.GetNow();
1158 aRetval.Insert(std::move(aXPoly));
1159 return aRetval.getB2DPolyPolygon();
1161 // copy certain data locally to use less code and have faster access times
1162 bool bClosed =mpSdrPathDragData->bClosed ; // closed object?
1163 sal_uInt16 nPointCount = mpSdrPathDragData->nPointCount; // number of points
1164 sal_uInt16 nPnt =mpSdrPathDragData->nPnt ; // number of points in the polygon
1165 bool bBegPnt =mpSdrPathDragData->bBegPnt ; // dragged point is the first point of a Polyline
1166 bool bEndPnt =mpSdrPathDragData->bEndPnt ; // dragged point is the last point of a Polyline
1167 sal_uInt16 nPrevPnt =mpSdrPathDragData->nPrevPnt ; // index of the previous point
1168 sal_uInt16 nNextPnt =mpSdrPathDragData->nNextPnt ; // index of the next point
1169 bool bPrevIsBegPnt =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline
1170 bool bNextIsEndPnt =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline
1171 sal_uInt16 nPrevPrevPnt =mpSdrPathDragData->nPrevPrevPnt ; // index of the point before the previous point
1172 sal_uInt16 nNextNextPnt =mpSdrPathDragData->nNextNextPnt ; // index of the point after the last point
1173 bool bControl =mpSdrPathDragData->bControl ; // point is a control point
1174 bool bIsNextControl =mpSdrPathDragData->bIsNextControl; //point is a control point after a support point
1175 bool bPrevIsControl =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before
1176 bool bNextIsControl =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after
1177 XPolygon aXPoly(mpSdrPathDragData->aXP);
1178 XPolygon aLine1(2);
1179 XPolygon aLine2(2);
1180 XPolygon aLine3(2);
1181 XPolygon aLine4(2);
1182 if (bControl) {
1183 aLine1[1]=mpSdrPathDragData->aXP[nPnt];
1184 if (bIsNextControl) { // is this a control point after the support point?
1185 aLine1[0]=mpSdrPathDragData->aXP[nPrevPnt];
1186 aLine2[0]=mpSdrPathDragData->aXP[nNextNextPnt];
1187 aLine2[1]=mpSdrPathDragData->aXP[nNextPnt];
1188 if (mpSdrPathDragData->aXP.IsSmooth(nPrevPnt) && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
1189 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control);
1190 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal);
1191 // leverage lines for the opposing curve segment
1192 aLine3[0]=mpSdrPathDragData->aXP[nPrevPnt];
1193 aLine3[1]=mpSdrPathDragData->aXP[nPrevPrevPnt];
1194 aLine4[0]=rXP[mpSdrPathDragData->nPrevPrevPnt0-2];
1195 aLine4[1]=rXP[mpSdrPathDragData->nPrevPrevPnt0-1];
1196 } else {
1197 aXPoly.Remove(0,1);
1199 } else { // else this is a control point before a support point
1200 aLine1[0]=mpSdrPathDragData->aXP[nNextPnt];
1201 aLine2[0]=mpSdrPathDragData->aXP[nPrevPrevPnt];
1202 aLine2[1]=mpSdrPathDragData->aXP[nPrevPnt];
1203 if (mpSdrPathDragData->aXP.IsSmooth(nNextPnt) && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
1204 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control);
1205 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal);
1206 // leverage lines for the opposing curve segment
1207 aLine3[0]=mpSdrPathDragData->aXP[nNextPnt];
1208 aLine3[1]=mpSdrPathDragData->aXP[nNextNextPnt];
1209 aLine4[0]=rXP[mpSdrPathDragData->nNextNextPnt0+2];
1210 aLine4[1]=rXP[mpSdrPathDragData->nNextNextPnt0+1];
1211 } else {
1212 aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1215 } else { // else is not a control point
1216 if (mpSdrPathDragData->bEliminate) {
1217 aXPoly.Remove(2,1);
1219 if (bPrevIsControl) aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Normal);
1220 else if (!bBegPnt && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
1221 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control);
1222 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal);
1223 } else {
1224 aXPoly.Remove(0,1);
1225 if (bBegPnt) aXPoly.Remove(0,1);
1227 if (bNextIsControl) aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Normal);
1228 else if (!bEndPnt && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
1229 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control);
1230 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal);
1231 } else {
1232 aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1233 if (bEndPnt) aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1235 if (bClosed) { // "pear problem": 2 lines, 1 curve, everything smoothed, a point between both lines is dragged
1236 if (aXPoly.GetPointCount()>nPointCount && aXPoly.IsControl(1)) {
1237 sal_uInt16 a=aXPoly.GetPointCount();
1238 aXPoly[a-2]=aXPoly[2]; aXPoly.SetFlags(a-2,aXPoly.GetFlags(2));
1239 aXPoly[a-1]=aXPoly[3]; aXPoly.SetFlags(a-1,aXPoly.GetFlags(3));
1240 aXPoly.Remove(0,3);
1244 aRetval.Insert(std::move(aXPoly));
1245 if (aLine1.GetPointCount()>1) aRetval.Insert(std::move(aLine1));
1246 if (aLine2.GetPointCount()>1) aRetval.Insert(std::move(aLine2));
1247 if (aLine3.GetPointCount()>1) aRetval.Insert(std::move(aLine3));
1248 if (aLine4.GetPointCount()>1) aRetval.Insert(std::move(aLine4));
1251 return aRetval.getB2DPolyPolygon();
1254 bool ImpPathForDragAndCreate::BegCreate(SdrDragStat& rStat)
1256 bool bFreeHand(IsFreeHand(meObjectKind));
1257 rStat.SetNoSnap(bFreeHand);
1258 rStat.SetOrtho8Possible();
1259 aPathPolygon.Clear();
1260 mbCreating=true;
1261 bool bMakeStartPoint = true;
1262 SdrView* pView=rStat.GetView();
1263 if (pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface() &&
1264 (meObjectKind==OBJ_POLY || meObjectKind==OBJ_PLIN || meObjectKind==OBJ_PATHLINE || meObjectKind==OBJ_PATHFILL)) {
1265 bMakeStartPoint = false;
1267 aPathPolygon.Insert(XPolygon());
1268 aPathPolygon[0][0]=rStat.GetStart();
1269 if (bMakeStartPoint) {
1270 aPathPolygon[0][1]=rStat.GetNow();
1272 std::unique_ptr<ImpPathCreateUser> pU(new ImpPathCreateUser);
1273 pU->eStartKind=meObjectKind;
1274 pU->eAktKind=meObjectKind;
1275 rStat.SetUser(std::move(pU));
1276 return true;
1279 bool ImpPathForDragAndCreate::MovCreate(SdrDragStat& rStat)
1281 ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1282 SdrView* pView=rStat.GetView();
1283 XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1284 if (pView!=nullptr && pView->IsCreateMode()) {
1285 // switch to different CreateTool, if appropriate
1286 sal_uInt16 nIdent;
1287 SdrInventor nInvent;
1288 pView->TakeCurrentObj(nIdent,nInvent);
1289 if (nInvent==SdrInventor::Default && pU->eAktKind!=(SdrObjKind)nIdent) {
1290 SdrObjKind eNewKind=(SdrObjKind)nIdent;
1291 switch (eNewKind) {
1292 case OBJ_CARC:
1293 case OBJ_CIRC:
1294 case OBJ_CCUT:
1295 case OBJ_SECT:
1296 eNewKind=OBJ_CARC;
1297 SAL_FALLTHROUGH;
1298 case OBJ_RECT:
1299 case OBJ_LINE:
1300 case OBJ_PLIN:
1301 case OBJ_POLY:
1302 case OBJ_PATHLINE:
1303 case OBJ_PATHFILL:
1304 case OBJ_FREELINE:
1305 case OBJ_FREEFILL:
1306 case OBJ_SPLNLINE:
1307 case OBJ_SPLNFILL: {
1308 pU->eAktKind=eNewKind;
1309 pU->bMixedCreate=true;
1310 pU->nBezierStartPoint=rXPoly.GetPointCount();
1311 if (pU->nBezierStartPoint>0) pU->nBezierStartPoint--;
1312 } break;
1313 default: break;
1314 } // switch
1317 sal_uInt16 nActPoint=rXPoly.GetPointCount();
1318 if (aPathPolygon.Count()>1 && rStat.IsMouseDown() && nActPoint<2) {
1319 rXPoly[0]=rStat.GetPos0();
1320 rXPoly[1]=rStat.GetNow();
1321 nActPoint=2;
1323 if (nActPoint==0) {
1324 rXPoly[0]=rStat.GetPos0();
1325 } else nActPoint--;
1326 bool bFreeHand=IsFreeHand(pU->eAktKind);
1327 rStat.SetNoSnap(bFreeHand);
1328 rStat.SetOrtho8Possible(pU->eAktKind!=OBJ_CARC && pU->eAktKind!=OBJ_RECT && (!pU->bMixedCreate || pU->eAktKind!=OBJ_LINE));
1329 rXPoly[nActPoint]=rStat.GetNow();
1330 if (!pU->bMixedCreate && pU->eStartKind==OBJ_LINE && rXPoly.GetPointCount()>=1) {
1331 Point aPt(rStat.GetStart());
1332 if (pView!=nullptr && pView->IsCreate1stPointAsCenter()) {
1333 aPt+=aPt;
1334 aPt-=rStat.GetNow();
1336 rXPoly[0]=aPt;
1338 OutputDevice* pOut=pView==nullptr ? nullptr : pView->GetFirstOutputDevice();
1339 if (bFreeHand) {
1340 if (pU->nBezierStartPoint>nActPoint) pU->nBezierStartPoint=nActPoint;
1341 if (rStat.IsMouseDown() && nActPoint>0) {
1342 // don't allow two consecutive points to occupy too similar positions
1343 long nMinDist=1;
1344 if (pView!=nullptr) nMinDist=pView->GetFreeHandMinDistPix();
1345 if (pOut!=nullptr) nMinDist=pOut->PixelToLogic(Size(nMinDist,0)).Width();
1346 if (nMinDist<1) nMinDist=1;
1348 Point aPt0(rXPoly[nActPoint-1]);
1349 Point aPt1(rStat.GetNow());
1350 long dx=aPt0.X()-aPt1.X(); if (dx<0) dx=-dx;
1351 long dy=aPt0.Y()-aPt1.Y(); if (dy<0) dy=-dy;
1352 if (dx<nMinDist && dy<nMinDist) return false;
1354 // TODO: the following is copied from EndCreate (with a few smaller modifications)
1355 // and should be combined into a method with the code there.
1357 if (nActPoint-pU->nBezierStartPoint>=3 && ((nActPoint-pU->nBezierStartPoint)%3)==0) {
1358 rXPoly.PointsToBezier(nActPoint-3);
1359 rXPoly.SetFlags(nActPoint-1,PolyFlags::Control);
1360 rXPoly.SetFlags(nActPoint-2,PolyFlags::Control);
1362 if (nActPoint>=6 && rXPoly.IsControl(nActPoint-4)) {
1363 rXPoly.CalcTangent(nActPoint-3,nActPoint-4,nActPoint-2);
1364 rXPoly.SetFlags(nActPoint-3,PolyFlags::Smooth);
1367 rXPoly[nActPoint+1]=rStat.GetNow();
1368 rStat.NextPoint();
1369 } else {
1370 pU->nBezierStartPoint=nActPoint;
1374 pU->ResetFormFlags();
1375 if (IsBezier(pU->eAktKind)) {
1376 if (nActPoint>=2) {
1377 pU->CalcBezier(rXPoly[nActPoint-1],rXPoly[nActPoint],rXPoly[nActPoint-1]-rXPoly[nActPoint-2],rStat.IsMouseDown());
1378 } else if (pU->bBezHasCtrl0) {
1379 pU->CalcBezier(rXPoly[nActPoint-1],rXPoly[nActPoint],pU->aBezControl0-rXPoly[nActPoint-1],rStat.IsMouseDown());
1382 if (pU->eAktKind==OBJ_CARC && nActPoint>=2) {
1383 pU->CalcCircle(rXPoly[nActPoint-1],rXPoly[nActPoint],rXPoly[nActPoint-1]-rXPoly[nActPoint-2],pView);
1385 if (pU->eAktKind==OBJ_LINE && nActPoint>=2) {
1386 pU->CalcLine(rXPoly[nActPoint-1],rXPoly[nActPoint],rXPoly[nActPoint-1]-rXPoly[nActPoint-2],pView);
1388 if (pU->eAktKind==OBJ_RECT && nActPoint>=2) {
1389 pU->CalcRect(rXPoly[nActPoint-1],rXPoly[nActPoint],rXPoly[nActPoint-1]-rXPoly[nActPoint-2],pView);
1392 return true;
1395 bool ImpPathForDragAndCreate::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
1397 ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1398 bool bRet = false;
1399 SdrView* pView=rStat.GetView();
1400 bool bIncomp=pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface();
1401 XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1402 sal_uInt16 nActPoint=rXPoly.GetPointCount()-1;
1403 rXPoly[nActPoint]=rStat.GetNow();
1404 if (!pU->bMixedCreate && pU->eStartKind==OBJ_LINE) {
1405 if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd;
1406 bRet = eCmd==SdrCreateCmd::ForceEnd;
1407 if (bRet) {
1408 mbCreating = false;
1409 rStat.SetUser(nullptr);
1411 return bRet;
1414 if (!pU->bMixedCreate && IsFreeHand(pU->eStartKind)) {
1415 if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd;
1416 bRet=eCmd==SdrCreateCmd::ForceEnd;
1417 if (bRet) {
1418 mbCreating=false;
1419 rStat.SetUser(nullptr);
1421 return bRet;
1423 if (eCmd==SdrCreateCmd::NextPoint || eCmd==SdrCreateCmd::NextObject) {
1424 // don't allow two consecutive points to occupy the same position
1425 if (nActPoint==0 || rStat.GetNow()!=rXPoly[nActPoint-1]) {
1426 if (bIncomp) {
1427 if (pU->nBezierStartPoint>nActPoint) pU->nBezierStartPoint=nActPoint;
1428 if (IsBezier(pU->eAktKind) && nActPoint-pU->nBezierStartPoint>=3 && ((nActPoint-pU->nBezierStartPoint)%3)==0) {
1429 rXPoly.PointsToBezier(nActPoint-3);
1430 rXPoly.SetFlags(nActPoint-1,PolyFlags::Control);
1431 rXPoly.SetFlags(nActPoint-2,PolyFlags::Control);
1433 if (nActPoint>=6 && rXPoly.IsControl(nActPoint-4)) {
1434 rXPoly.CalcTangent(nActPoint-3,nActPoint-4,nActPoint-2);
1435 rXPoly.SetFlags(nActPoint-3,PolyFlags::Smooth);
1438 } else {
1439 if (nActPoint==1 && IsBezier(pU->eAktKind) && !pU->bBezHasCtrl0) {
1440 pU->aBezControl0=rStat.GetNow();
1441 pU->bBezHasCtrl0=true;
1442 nActPoint--;
1444 if (pU->IsFormFlag()) {
1445 sal_uInt16 nPointCount0=rXPoly.GetPointCount();
1446 rXPoly.Remove(nActPoint-1,2); // remove last two points and replace by form
1447 rXPoly.Insert(XPOLY_APPEND,pU->GetFormPoly());
1448 sal_uInt16 nPointCount1=rXPoly.GetPointCount();
1449 for (sal_uInt16 i=nPointCount0+1; i<nPointCount1-1; i++) { // to make BckAction work
1450 if (!rXPoly.IsControl(i)) rStat.NextPoint();
1452 nActPoint=rXPoly.GetPointCount()-1;
1455 nActPoint++;
1456 rXPoly[nActPoint]=rStat.GetNow();
1458 if (eCmd==SdrCreateCmd::NextObject) {
1459 if (rXPoly.GetPointCount()>=2) {
1460 pU->bBezHasCtrl0=false;
1461 // only a singular polygon may be opened, so close this
1462 rXPoly[nActPoint]=rXPoly[0];
1463 XPolygon aXP;
1464 aXP[0]=rStat.GetNow();
1465 aPathPolygon.Insert(std::move(aXP));
1470 sal_uInt16 nPolyCount=aPathPolygon.Count();
1471 if (nPolyCount!=0) {
1472 // delete last point, if necessary
1473 if (eCmd==SdrCreateCmd::ForceEnd) {
1474 XPolygon& rXP=aPathPolygon[nPolyCount-1];
1475 sal_uInt16 nPointCount=rXP.GetPointCount();
1476 if (nPointCount>=2) {
1477 if (!rXP.IsControl(nPointCount-2)) {
1478 if (rXP[nPointCount-1]==rXP[nPointCount-2]) {
1479 rXP.Remove(nPointCount-1,1);
1481 } else {
1482 if (rXP[nPointCount-3]==rXP[nPointCount-2]) {
1483 rXP.Remove(nPointCount-3,3);
1488 for (sal_uInt16 nPolyNum=nPolyCount; nPolyNum>0;) {
1489 nPolyNum--;
1490 XPolygon& rXP=aPathPolygon[nPolyNum];
1491 sal_uInt16 nPointCount=rXP.GetPointCount();
1492 // delete polygons with too few points
1493 if (nPolyNum<nPolyCount-1 || eCmd==SdrCreateCmd::ForceEnd) {
1494 if (nPointCount<2) aPathPolygon.Remove(nPolyNum);
1498 pU->ResetFormFlags();
1499 bRet=eCmd==SdrCreateCmd::ForceEnd;
1500 if (bRet) {
1501 mbCreating=false;
1502 rStat.SetUser(nullptr);
1504 return bRet;
1507 bool ImpPathForDragAndCreate::BckCreate(SdrDragStat& rStat)
1509 ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1510 if (aPathPolygon.Count()>0) {
1511 XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1512 sal_uInt16 nActPoint=rXPoly.GetPointCount();
1513 if (nActPoint>0) {
1514 nActPoint--;
1515 // make the last part of a bezier curve a line
1516 rXPoly.Remove(nActPoint,1);
1517 if (nActPoint>=3 && rXPoly.IsControl(nActPoint-1)) {
1518 // there should never be a bezier segment at the end, so this is just in case...
1519 rXPoly.Remove(nActPoint-1,1);
1520 if (rXPoly.IsControl(nActPoint-2)) rXPoly.Remove(nActPoint-2,1);
1523 nActPoint=rXPoly.GetPointCount();
1524 if (nActPoint>=4) { // no bezier segment at the end
1525 nActPoint--;
1526 if (rXPoly.IsControl(nActPoint-1)) {
1527 rXPoly.Remove(nActPoint-1,1);
1528 if (rXPoly.IsControl(nActPoint-2)) rXPoly.Remove(nActPoint-2,1);
1531 if (rXPoly.GetPointCount()<2) {
1532 aPathPolygon.Remove(aPathPolygon.Count()-1);
1534 if (aPathPolygon.Count()>0) {
1535 XPolygon& rLocalXPoly=aPathPolygon[aPathPolygon.Count()-1];
1536 sal_uInt16 nLocalActPoint=rLocalXPoly.GetPointCount();
1537 if (nLocalActPoint>0) {
1538 nLocalActPoint--;
1539 rLocalXPoly[nLocalActPoint]=rStat.GetNow();
1543 pU->ResetFormFlags();
1544 return aPathPolygon.Count()!=0;
1547 void ImpPathForDragAndCreate::BrkCreate(SdrDragStat& rStat)
1549 aPathPolygon.Clear();
1550 mbCreating=false;
1551 rStat.SetUser(nullptr);
1554 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeObjectPolyPolygon(const SdrDragStat& rDrag) const
1556 basegfx::B2DPolyPolygon aRetval(aPathPolygon.getB2DPolyPolygon());
1557 SdrView* pView = rDrag.GetView();
1559 if(pView && pView->IsUseIncompatiblePathCreateInterface())
1560 return aRetval;
1562 ImpPathCreateUser* pU = static_cast<ImpPathCreateUser*>(rDrag.GetUser());
1563 basegfx::B2DPolygon aNewPolygon(aRetval.count() ? aRetval.getB2DPolygon(aRetval.count() - 1) : basegfx::B2DPolygon());
1565 if(pU->IsFormFlag() && aNewPolygon.count() > 1)
1567 // remove last segment and replace with current
1568 // do not forget to rescue the previous control point which will be lost when
1569 // the point it's associated with is removed
1570 const sal_uInt32 nChangeIndex(aNewPolygon.count() - 2);
1571 const basegfx::B2DPoint aSavedPrevCtrlPoint(aNewPolygon.getPrevControlPoint(nChangeIndex));
1573 aNewPolygon.remove(nChangeIndex, 2L);
1574 aNewPolygon.append(pU->GetFormPoly().getB2DPolygon());
1576 if(nChangeIndex < aNewPolygon.count())
1578 // if really something was added, set the saved previous control point to the
1579 // point where it belongs
1580 aNewPolygon.setPrevControlPoint(nChangeIndex, aSavedPrevCtrlPoint);
1584 if(aRetval.count())
1586 aRetval.setB2DPolygon(aRetval.count() - 1, aNewPolygon);
1588 else
1590 aRetval.append(aNewPolygon);
1593 return aRetval;
1596 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeDragPolyPolygon(const SdrDragStat& rDrag)
1598 basegfx::B2DPolyPolygon aRetval;
1599 SdrView* pView = rDrag.GetView();
1601 if(pView && pView->IsUseIncompatiblePathCreateInterface())
1602 return aRetval;
1604 const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser());
1606 if(pU && pU->bBezier && rDrag.IsMouseDown())
1608 // no more XOR, no need for complicated helplines
1609 basegfx::B2DPolygon aHelpline;
1610 aHelpline.append(basegfx::B2DPoint(pU->aBezCtrl2.X(), pU->aBezCtrl2.Y()));
1611 aHelpline.append(basegfx::B2DPoint(pU->aBezEnd.X(), pU->aBezEnd.Y()));
1612 aRetval.append(aHelpline);
1615 return aRetval;
1618 Pointer ImpPathForDragAndCreate::GetCreatePointer() const
1620 switch (meObjectKind) {
1621 case OBJ_LINE : return Pointer(PointerStyle::DrawLine);
1622 case OBJ_POLY : return Pointer(PointerStyle::DrawPolygon);
1623 case OBJ_PLIN : return Pointer(PointerStyle::DrawPolygon);
1624 case OBJ_PATHLINE: return Pointer(PointerStyle::DrawBezier);
1625 case OBJ_PATHFILL: return Pointer(PointerStyle::DrawBezier);
1626 case OBJ_FREELINE: return Pointer(PointerStyle::DrawFreehand);
1627 case OBJ_FREEFILL: return Pointer(PointerStyle::DrawFreehand);
1628 case OBJ_SPLNLINE: return Pointer(PointerStyle::DrawFreehand);
1629 case OBJ_SPLNFILL: return Pointer(PointerStyle::DrawFreehand);
1630 case OBJ_PATHPOLY: return Pointer(PointerStyle::DrawPolygon);
1631 case OBJ_PATHPLIN: return Pointer(PointerStyle::DrawPolygon);
1632 default: break;
1633 } // switch
1634 return Pointer(PointerStyle::Cross);
1637 SdrPathObjGeoData::SdrPathObjGeoData()
1638 : meKind(OBJ_NONE)
1642 SdrPathObjGeoData::~SdrPathObjGeoData()
1646 // DrawContact section
1648 sdr::contact::ViewContact* SdrPathObj::CreateObjectSpecificViewContact()
1650 return new sdr::contact::ViewContactOfSdrPathObj(*this);
1654 SdrPathObj::SdrPathObj(SdrObjKind eNewKind)
1655 : meKind(eNewKind),
1656 mdBrightness(0.0)
1658 bClosedObj = IsClosed();
1661 SdrPathObj::SdrPathObj(SdrObjKind eNewKind, const basegfx::B2DPolyPolygon& rPathPoly, double dBrightness)
1662 : maPathPolygon(rPathPoly),
1663 meKind(eNewKind),
1664 mdBrightness(dBrightness)
1666 bClosedObj = IsClosed();
1667 ImpForceKind();
1670 SdrPathObj::~SdrPathObj() = default;
1672 static bool lcl_ImpIsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
1674 return (1 == rPolyPolygon.count() && 2 == rPolyPolygon.getB2DPolygon(0).count());
1677 static tools::Rectangle lcl_ImpGetBoundRect(const basegfx::B2DPolyPolygon& rPolyPolygon)
1679 basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
1681 return tools::Rectangle(
1682 FRound(aRange.getMinX()), FRound(aRange.getMinY()),
1683 FRound(aRange.getMaxX()), FRound(aRange.getMaxY()));
1686 void SdrPathObj::ImpForceLineAngle()
1688 if(OBJ_LINE != meKind || !lcl_ImpIsLine(GetPathPoly()))
1689 return;
1691 const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0));
1692 const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
1693 const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
1694 const Point aPoint0(FRound(aB2DPoint0.getX()), FRound(aB2DPoint0.getY()));
1695 const Point aPoint1(FRound(aB2DPoint1.getX()), FRound(aB2DPoint1.getY()));
1696 const Point aDelt(aPoint1 - aPoint0);
1698 aGeo.nRotationAngle=GetAngle(aDelt);
1699 aGeo.nShearAngle=0;
1700 aGeo.RecalcSinCos();
1701 aGeo.RecalcTan();
1703 // for SdrTextObj, keep aRect up to date
1704 maRect = tools::Rectangle(aPoint0, aPoint1);
1705 maRect.Justify();
1708 void SdrPathObj::ImpForceKind()
1710 if (meKind==OBJ_PATHPLIN) meKind=OBJ_PLIN;
1711 if (meKind==OBJ_PATHPOLY) meKind=OBJ_POLY;
1713 if(GetPathPoly().areControlPointsUsed())
1715 switch (meKind)
1717 case OBJ_LINE: meKind=OBJ_PATHLINE; break;
1718 case OBJ_PLIN: meKind=OBJ_PATHLINE; break;
1719 case OBJ_POLY: meKind=OBJ_PATHFILL; break;
1720 default: break;
1723 else
1725 switch (meKind)
1727 case OBJ_PATHLINE: meKind=OBJ_PLIN; break;
1728 case OBJ_FREELINE: meKind=OBJ_PLIN; break;
1729 case OBJ_PATHFILL: meKind=OBJ_POLY; break;
1730 case OBJ_FREEFILL: meKind=OBJ_POLY; break;
1731 default: break;
1735 if (meKind==OBJ_LINE && !lcl_ImpIsLine(GetPathPoly())) meKind=OBJ_PLIN;
1736 if (meKind==OBJ_PLIN && lcl_ImpIsLine(GetPathPoly())) meKind=OBJ_LINE;
1738 bClosedObj=IsClosed();
1740 if (meKind==OBJ_LINE)
1742 ImpForceLineAngle();
1744 else
1746 // #i10659#, for polys with more than 2 points.
1748 // Here i again need to fix something, because when Path-Polys are Copy-Pasted
1749 // between Apps with different measurements (e.g. 100TH_MM and TWIPS) there is
1750 // a scaling loop started from SdrExchangeView::Paste. In itself, this is not
1751 // wrong, but aRect is wrong here and not even updated by RecalcSnapRect(). If
1752 // this is the case, some size needs to be set here in aRect to avoid that the cycle
1753 // through Rect2Poly - Poly2Rect does something badly wrong since that cycle is
1754 // BASED on aRect. That cycle is triggered in SdrTextObj::NbcResize() which is called
1755 // from the local Resize() implementation.
1757 // Basic problem is that the member aRect in SdrTextObj basically is a unrotated
1758 // text rectangle for the text object itself and methods at SdrTextObj do handle it
1759 // in that way. Many draw objects derived from SdrTextObj 'abuse' aRect as SnapRect
1760 // which is basically wrong. To make the SdrText methods which deal with aRect directly
1761 // work it is necessary to always keep aRect updated. This e.g. not done after a Clone()
1762 // command for SdrPathObj. Since adding this update mechanism with #101412# to
1763 // ImpForceLineAngle() for lines was very successful, i add it to where ImpForceLineAngle()
1764 // was called, once here below and once on a 2nd place below.
1766 // #i10659# for SdrTextObj, keep aRect up to date
1767 if(GetPathPoly().count())
1769 maRect = lcl_ImpGetBoundRect(GetPathPoly());
1773 // #i75974# adapt polygon state to object type. This may include a reinterpretation
1774 // of a closed geometry as open one, but with identical first and last point
1775 for(sal_uInt32 a(0); a < maPathPolygon.count(); a++)
1777 basegfx::B2DPolygon aCandidate(maPathPolygon.getB2DPolygon(a));
1779 if(IsClosed() != aCandidate.isClosed())
1781 // #i80213# really change polygon geometry; else e.g. the last point which
1782 // needs to be identical with the first one will be missing when opening
1783 // due to OBJ_PATH type
1784 if(aCandidate.isClosed())
1786 basegfx::utils::openWithGeometryChange(aCandidate);
1788 else
1790 basegfx::utils::closeWithGeometryChange(aCandidate);
1793 maPathPolygon.setB2DPolygon(a, aCandidate);
1798 void SdrPathObj::ImpSetClosed(bool bClose)
1800 if(bClose)
1802 switch (meKind)
1804 case OBJ_LINE : meKind=OBJ_POLY; break;
1805 case OBJ_PLIN : meKind=OBJ_POLY; break;
1806 case OBJ_PATHLINE: meKind=OBJ_PATHFILL; break;
1807 case OBJ_FREELINE: meKind=OBJ_FREEFILL; break;
1808 case OBJ_SPLNLINE: meKind=OBJ_SPLNFILL; break;
1809 default: break;
1812 bClosedObj = true;
1814 else
1816 switch (meKind)
1818 case OBJ_POLY : meKind=OBJ_PLIN; break;
1819 case OBJ_PATHFILL: meKind=OBJ_PATHLINE; break;
1820 case OBJ_FREEFILL: meKind=OBJ_FREELINE; break;
1821 case OBJ_SPLNFILL: meKind=OBJ_SPLNLINE; break;
1822 default: break;
1825 bClosedObj = false;
1828 ImpForceKind();
1831 void SdrPathObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
1833 rInfo.bNoContortion=false;
1835 bool bCanConv = !HasText() || ImpCanConvTextToCurve();
1836 bool bIsPath = IsBezier() || IsSpline();
1838 rInfo.bEdgeRadiusAllowed = false;
1839 rInfo.bCanConvToPath = bCanConv && !bIsPath;
1840 rInfo.bCanConvToPoly = bCanConv && bIsPath;
1841 rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary());
1844 sal_uInt16 SdrPathObj::GetObjIdentifier() const
1846 return sal_uInt16(meKind);
1849 SdrPathObj* SdrPathObj::Clone() const
1851 return CloneHelper< SdrPathObj >();
1854 SdrPathObj& SdrPathObj::operator=(const SdrPathObj& rObj)
1856 if( this == &rObj )
1857 return *this;
1858 SdrTextObj::operator=(rObj);
1859 maPathPolygon=rObj.GetPathPoly();
1860 return *this;
1863 OUString SdrPathObj::TakeObjNameSingul() const
1865 OUStringBuffer sName;
1867 if(OBJ_LINE == meKind)
1869 const char* pId(STR_ObjNameSingulLINE);
1871 if(lcl_ImpIsLine(GetPathPoly()))
1873 const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0));
1874 const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
1875 const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
1877 if(aB2DPoint0 != aB2DPoint1)
1879 if(aB2DPoint0.getY() == aB2DPoint1.getY())
1881 pId = STR_ObjNameSingulLINE_Hori;
1883 else if(aB2DPoint0.getX() == aB2DPoint1.getX())
1885 pId = STR_ObjNameSingulLINE_Vert;
1887 else
1889 const double fDx(fabs(aB2DPoint0.getX() - aB2DPoint1.getX()));
1890 const double fDy(fabs(aB2DPoint0.getY() - aB2DPoint1.getY()));
1892 if(fDx == fDy)
1894 pId = STR_ObjNameSingulLINE_Diag;
1900 sName.append(ImpGetResStr(pId));
1902 else if(OBJ_PLIN == meKind || OBJ_POLY == meKind)
1904 const bool bClosed(OBJ_POLY == meKind);
1905 const char* pId(nullptr);
1907 if(mpDAC && mpDAC->IsCreating())
1909 if(bClosed)
1911 pId = STR_ObjNameSingulPOLY;
1913 else
1915 pId = STR_ObjNameSingulPLIN;
1918 sName.append(ImpGetResStr(pId));
1920 else
1922 // get point count
1923 sal_uInt32 nPointCount(0);
1924 const sal_uInt32 nPolyCount(GetPathPoly().count());
1926 for(sal_uInt32 a(0); a < nPolyCount; a++)
1928 nPointCount += GetPathPoly().getB2DPolygon(a).count();
1931 if(bClosed)
1933 pId = STR_ObjNameSingulPOLY_PointCount;
1935 else
1937 pId = STR_ObjNameSingulPLIN_PointCount;
1940 OUString sTemp(ImpGetResStr(pId));
1941 // #i96537#
1942 sName.append(sTemp.replaceFirst("%2", OUString::number(nPointCount)));
1945 else
1947 switch (meKind)
1949 case OBJ_PATHLINE: sName.append(ImpGetResStr(STR_ObjNameSingulPATHLINE)); break;
1950 case OBJ_FREELINE: sName.append(ImpGetResStr(STR_ObjNameSingulFREELINE)); break;
1951 case OBJ_SPLNLINE: sName.append(ImpGetResStr(STR_ObjNameSingulNATSPLN)); break;
1952 case OBJ_PATHFILL: sName.append(ImpGetResStr(STR_ObjNameSingulPATHFILL)); break;
1953 case OBJ_FREEFILL: sName.append(ImpGetResStr(STR_ObjNameSingulFREEFILL)); break;
1954 case OBJ_SPLNFILL: sName.append(ImpGetResStr(STR_ObjNameSingulPERSPLN)); break;
1955 default: break;
1959 OUString aName(GetName());
1960 if (!aName.isEmpty())
1962 sName.append(' ');
1963 sName.append('\'');
1964 sName.append(aName);
1965 sName.append('\'');
1968 return sName.makeStringAndClear();
1971 OUString SdrPathObj::TakeObjNamePlural() const
1973 OUString sName;
1974 switch(meKind)
1976 case OBJ_LINE : sName=ImpGetResStr(STR_ObjNamePluralLINE ); break;
1977 case OBJ_PLIN : sName=ImpGetResStr(STR_ObjNamePluralPLIN ); break;
1978 case OBJ_POLY : sName=ImpGetResStr(STR_ObjNamePluralPOLY ); break;
1979 case OBJ_PATHLINE: sName=ImpGetResStr(STR_ObjNamePluralPATHLINE); break;
1980 case OBJ_FREELINE: sName=ImpGetResStr(STR_ObjNamePluralFREELINE); break;
1981 case OBJ_SPLNLINE: sName=ImpGetResStr(STR_ObjNamePluralNATSPLN); break;
1982 case OBJ_PATHFILL: sName=ImpGetResStr(STR_ObjNamePluralPATHFILL); break;
1983 case OBJ_FREEFILL: sName=ImpGetResStr(STR_ObjNamePluralFREEFILL); break;
1984 case OBJ_SPLNFILL: sName=ImpGetResStr(STR_ObjNamePluralPERSPLN); break;
1985 default: break;
1987 return sName;
1990 basegfx::B2DPolyPolygon SdrPathObj::TakeXorPoly() const
1992 return GetPathPoly();
1995 sal_uInt32 SdrPathObj::GetHdlCount() const
1997 sal_uInt32 nRetval(0);
1998 const sal_uInt32 nPolyCount(GetPathPoly().count());
2000 for(sal_uInt32 a(0); a < nPolyCount; a++)
2002 nRetval += GetPathPoly().getB2DPolygon(a).count();
2005 return nRetval;
2008 SdrHdl* SdrPathObj::GetHdl(sal_uInt32 nHdlNum) const
2010 // #i73248#
2011 // Warn the user that this is ineffective and show alternatives. Should not be used at all.
2012 OSL_FAIL("SdrPathObj::GetHdl(): ineffective, use AddToHdlList instead (!)");
2014 // to have an alternative, get single handle using the ineffective way
2015 SdrHdl* pRetval = nullptr;
2016 SdrHdlList aLocalList(nullptr);
2017 AddToHdlList(aLocalList);
2018 const sal_uInt32 nHdlCount(aLocalList.GetHdlCount());
2020 if(nHdlCount && nHdlNum < nHdlCount)
2022 // remove and remember. The other created handles will be deleted again with the
2023 // destruction of the local list
2024 pRetval = aLocalList.RemoveHdl(nHdlNum);
2027 return pRetval;
2030 void SdrPathObj::AddToHdlList(SdrHdlList& rHdlList) const
2032 // keep old stuff to be able to keep old SdrHdl stuff, too
2033 const XPolyPolygon aOldPathPolygon(GetPathPoly());
2034 sal_uInt16 nPolyCnt=aOldPathPolygon.Count();
2035 bool bClosed=IsClosed();
2036 sal_uInt16 nIdx=0;
2038 for (sal_uInt16 i=0; i<nPolyCnt; i++) {
2039 const XPolygon& rXPoly=aOldPathPolygon.GetObject(i);
2040 sal_uInt16 nPntCnt=rXPoly.GetPointCount();
2041 if (bClosed && nPntCnt>1) nPntCnt--;
2043 for (sal_uInt16 j=0; j<nPntCnt; j++) {
2044 if (rXPoly.GetFlags(j)!=PolyFlags::Control) {
2045 const Point& rPnt=rXPoly[j];
2046 SdrHdl* pHdl=new SdrHdl(rPnt,SdrHdlKind::Poly);
2047 pHdl->SetPolyNum(i);
2048 pHdl->SetPointNum(j);
2049 pHdl->Set1PixMore(j==0);
2050 pHdl->SetSourceHdlNum(nIdx);
2051 nIdx++;
2052 rHdlList.AddHdl(pHdl);
2058 sal_uInt32 SdrPathObj::GetPlusHdlCount(const SdrHdl& rHdl) const
2060 // keep old stuff to be able to keep old SdrHdl stuff, too
2061 const XPolyPolygon aOldPathPolygon(GetPathPoly());
2062 sal_uInt16 nCnt = 0;
2063 sal_uInt16 nPnt = (sal_uInt16)rHdl.GetPointNum();
2064 sal_uInt16 nPolyNum = (sal_uInt16)rHdl.GetPolyNum();
2066 if(nPolyNum < aOldPathPolygon.Count())
2068 const XPolygon& rXPoly = aOldPathPolygon[nPolyNum];
2069 sal_uInt16 nPntMax = rXPoly.GetPointCount();
2070 if (nPntMax>0)
2072 nPntMax--;
2073 if (nPnt<=nPntMax)
2075 if (rXPoly.GetFlags(nPnt)!=PolyFlags::Control)
2077 if (nPnt==0 && IsClosed()) nPnt=nPntMax;
2078 if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control) nCnt++;
2079 if (nPnt==nPntMax && IsClosed()) nPnt=0;
2080 if (nPnt<nPntMax && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control) nCnt++;
2086 return nCnt;
2089 SdrHdl* SdrPathObj::GetPlusHdl(const SdrHdl& rHdl, sal_uInt32 nPlusNum) const
2091 // keep old stuff to be able to keep old SdrHdl stuff, too
2092 const XPolyPolygon aOldPathPolygon(GetPathPoly());
2093 SdrHdl* pHdl = nullptr;
2094 sal_uInt16 nPnt = (sal_uInt16)rHdl.GetPointNum();
2095 sal_uInt16 nPolyNum = (sal_uInt16)rHdl.GetPolyNum();
2097 if (nPolyNum<aOldPathPolygon.Count())
2099 const XPolygon& rXPoly = aOldPathPolygon[nPolyNum];
2100 sal_uInt16 nPntMax = rXPoly.GetPointCount();
2102 if (nPntMax>0)
2104 nPntMax--;
2105 if (nPnt<=nPntMax)
2107 pHdl=new SdrHdlBezWgt(&rHdl);
2108 pHdl->SetPolyNum(rHdl.GetPolyNum());
2110 if (nPnt==0 && IsClosed()) nPnt=nPntMax;
2111 if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control && nPlusNum==0)
2113 pHdl->SetPos(rXPoly[nPnt-1]);
2114 pHdl->SetPointNum(nPnt-1);
2116 else
2118 if (nPnt==nPntMax && IsClosed()) nPnt=0;
2119 if (nPnt<rXPoly.GetPointCount()-1 && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control)
2121 pHdl->SetPos(rXPoly[nPnt+1]);
2122 pHdl->SetPointNum(nPnt+1);
2126 pHdl->SetSourceHdlNum(rHdl.GetSourceHdlNum());
2127 pHdl->SetPlusHdl(true);
2131 return pHdl;
2134 // dragging
2136 bool SdrPathObj::hasSpecialDrag() const
2138 return true;
2141 bool SdrPathObj::beginSpecialDrag(SdrDragStat& rDrag) const
2143 ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2145 return aDragAndCreate.beginPathDrag(rDrag);
2148 bool SdrPathObj::applySpecialDrag(SdrDragStat& rDrag)
2150 ImpPathForDragAndCreate aDragAndCreate(*this);
2151 bool bRetval(aDragAndCreate.beginPathDrag(rDrag));
2153 if(bRetval)
2155 bRetval = aDragAndCreate.movePathDrag(rDrag);
2158 if(bRetval)
2160 bRetval = aDragAndCreate.endPathDrag(rDrag);
2163 if(bRetval)
2165 NbcSetPathPoly(aDragAndCreate.getModifiedPolyPolygon());
2168 return bRetval;
2171 OUString SdrPathObj::getSpecialDragComment(const SdrDragStat& rDrag) const
2173 OUString aRetval;
2175 if(mpDAC)
2177 // #i103058# also get a comment when in creation
2178 const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj());
2180 if(bCreateComment)
2182 aRetval = mpDAC->getSpecialDragComment(rDrag);
2185 else
2187 ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2188 bool bDidWork(aDragAndCreate.beginPathDrag(rDrag));
2190 if(bDidWork)
2192 aRetval = aDragAndCreate.getSpecialDragComment(rDrag);
2196 return aRetval;
2199 basegfx::B2DPolyPolygon SdrPathObj::getSpecialDragPoly(const SdrDragStat& rDrag) const
2201 basegfx::B2DPolyPolygon aRetval;
2202 ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2203 bool bDidWork(aDragAndCreate.beginPathDrag(rDrag));
2205 if(bDidWork)
2207 aRetval = aDragAndCreate.getSpecialDragPoly(rDrag);
2210 return aRetval;
2213 // creation
2215 bool SdrPathObj::BegCreate(SdrDragStat& rStat)
2217 mpDAC.reset();
2218 return impGetDAC().BegCreate(rStat);
2221 bool SdrPathObj::MovCreate(SdrDragStat& rStat)
2223 return impGetDAC().MovCreate(rStat);
2226 bool SdrPathObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
2228 bool bRetval(impGetDAC().EndCreate(rStat, eCmd));
2230 if(bRetval && mpDAC)
2232 SetPathPoly(mpDAC->getModifiedPolyPolygon());
2234 // #i75974# Check for AutoClose feature. Moved here from ImpPathForDragAndCreate::EndCreate
2235 // to be able to use the type-changing ImpSetClosed method
2236 if(!IsClosedObj())
2238 SdrView* pView = rStat.GetView();
2240 if(pView && !pView->IsUseIncompatiblePathCreateInterface())
2242 OutputDevice* pOut = pView->GetFirstOutputDevice();
2244 if(pOut)
2246 if(GetPathPoly().count())
2248 const basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(0));
2250 if(aCandidate.count() > 2)
2252 // check distance of first and last point
2253 const sal_Int32 nCloseDist(pOut->PixelToLogic(Size(pView->GetAutoCloseDistPix(), 0)).Width());
2254 const basegfx::B2DVector aDistVector(aCandidate.getB2DPoint(aCandidate.count() - 1) - aCandidate.getB2DPoint(0));
2256 if(aDistVector.getLength() <= (double)nCloseDist)
2258 // close it
2259 ImpSetClosed(true);
2267 mpDAC.reset();
2270 return bRetval;
2273 bool SdrPathObj::BckCreate(SdrDragStat& rStat)
2275 return impGetDAC().BckCreate(rStat);
2278 void SdrPathObj::BrkCreate(SdrDragStat& rStat)
2280 impGetDAC().BrkCreate(rStat);
2281 mpDAC.reset();
2284 // polygons
2286 basegfx::B2DPolyPolygon SdrPathObj::TakeCreatePoly(const SdrDragStat& rDrag) const
2288 basegfx::B2DPolyPolygon aRetval;
2290 if(mpDAC)
2292 aRetval = mpDAC->TakeObjectPolyPolygon(rDrag);
2293 aRetval.append(ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag));
2296 return aRetval;
2299 // during drag or create, allow accessing the so-far created/modified polyPolygon
2300 basegfx::B2DPolyPolygon SdrPathObj::getObjectPolyPolygon(const SdrDragStat& rDrag) const
2302 basegfx::B2DPolyPolygon aRetval;
2304 if(mpDAC)
2306 aRetval = mpDAC->TakeObjectPolyPolygon(rDrag);
2309 return aRetval;
2312 basegfx::B2DPolyPolygon SdrPathObj::getDragPolyPolygon(const SdrDragStat& rDrag) const
2314 basegfx::B2DPolyPolygon aRetval;
2316 if(mpDAC)
2318 aRetval = ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag);
2321 return aRetval;
2324 Pointer SdrPathObj::GetCreatePointer() const
2326 return impGetDAC().GetCreatePointer();
2329 void SdrPathObj::NbcMove(const Size& rSiz)
2331 maPathPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(rSiz.Width(), rSiz.Height()));
2333 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2334 SdrTextObj::NbcMove(rSiz);
2337 void SdrPathObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
2339 basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRef.X(), -rRef.Y()));
2340 aTrans = basegfx::utils::createScaleTranslateB2DHomMatrix(
2341 double(xFact), double(yFact), rRef.X(), rRef.Y()) * aTrans;
2342 maPathPolygon.transform(aTrans);
2344 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2345 SdrTextObj::NbcResize(rRef,xFact,yFact);
2348 void SdrPathObj::NbcRotate(const Point& rRef, long nAngle, double sn, double cs)
2350 // Thank JOE, the angles are defined mirrored to the mathematical meanings
2351 const basegfx::B2DHomMatrix aTrans(basegfx::utils::createRotateAroundPoint(rRef.X(), rRef.Y(), -nAngle * nPi180));
2352 maPathPolygon.transform(aTrans);
2354 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2355 SdrTextObj::NbcRotate(rRef,nAngle,sn,cs);
2358 void SdrPathObj::NbcShear(const Point& rRefPnt, long nAngle, double fTan, bool bVShear)
2360 basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt.X(), -rRefPnt.Y()));
2362 if(bVShear)
2364 // Thank JOE, the angles are defined mirrored to the mathematical meanings
2365 aTrans.shearY(-fTan);
2367 else
2369 aTrans.shearX(-fTan);
2372 aTrans.translate(rRefPnt.X(), rRefPnt.Y());
2373 maPathPolygon.transform(aTrans);
2375 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2376 SdrTextObj::NbcShear(rRefPnt,nAngle,fTan,bVShear);
2379 void SdrPathObj::NbcMirror(const Point& rRefPnt1, const Point& rRefPnt2)
2381 const double fDiffX(rRefPnt2.X() - rRefPnt1.X());
2382 const double fDiffY(rRefPnt2.Y() - rRefPnt1.Y());
2383 const double fRot(atan2(fDiffY, fDiffX));
2384 basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt1.X(), -rRefPnt1.Y()));
2385 aTrans.rotate(-fRot);
2386 aTrans.scale(1.0, -1.0);
2387 aTrans.rotate(fRot);
2388 aTrans.translate(rRefPnt1.X(), rRefPnt1.Y());
2389 maPathPolygon.transform(aTrans);
2391 // Do Joe's special handling for lines when mirroring, too
2392 ImpForceKind();
2394 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2395 SdrTextObj::NbcMirror(rRefPnt1,rRefPnt2);
2398 void SdrPathObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const
2400 if(!aGeo.nRotationAngle)
2402 rRect = GetSnapRect();
2404 else
2406 XPolyPolygon aXPP(GetPathPoly());
2407 RotateXPoly(aXPP,Point(),-aGeo.nSin,aGeo.nCos);
2408 rRect=aXPP.GetBoundRect();
2409 Point aTmp(rRect.TopLeft());
2410 RotatePoint(aTmp,Point(),aGeo.nSin,aGeo.nCos);
2411 aTmp-=rRect.TopLeft();
2412 rRect.Move(aTmp.X(),aTmp.Y());
2416 void SdrPathObj::RecalcSnapRect()
2418 if(GetPathPoly().count())
2420 maSnapRect = lcl_ImpGetBoundRect(GetPathPoly());
2424 void SdrPathObj::NbcSetSnapRect(const tools::Rectangle& rRect)
2426 tools::Rectangle aOld(GetSnapRect());
2428 // Take RECT_EMPTY into account when calculating scale factors
2429 long nMulX = (RECT_EMPTY == rRect.Right()) ? 0 : rRect.Right() - rRect.Left();
2431 long nDivX = aOld.Right() - aOld.Left();
2433 // Take RECT_EMPTY into account when calculating scale factors
2434 long nMulY = (RECT_EMPTY == rRect.Bottom()) ? 0 : rRect.Bottom() - rRect.Top();
2436 long nDivY = aOld.Bottom() - aOld.Top();
2437 if ( nDivX == 0 ) { nMulX = 1; nDivX = 1; }
2438 if ( nDivY == 0 ) { nMulY = 1; nDivY = 1; }
2439 if ( nDivX == nMulX ) { nMulX = 1; nDivX = 1; }
2440 if ( nDivY == nMulY ) { nMulY = 1; nDivY = 1; }
2441 Fraction aX(nMulX,nDivX);
2442 Fraction aY(nMulY,nDivY);
2443 NbcResize(aOld.TopLeft(), aX, aY);
2444 NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top()));
2447 sal_uInt32 SdrPathObj::GetSnapPointCount() const
2449 return GetHdlCount();
2452 Point SdrPathObj::GetSnapPoint(sal_uInt32 nSnapPnt) const
2454 sal_uInt32 nPoly,nPnt;
2455 if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nSnapPnt, nPoly, nPnt))
2457 SAL_WARN("svx", "SdrPathObj::GetSnapPoint: Point nSnapPnt does not exist.");
2460 const basegfx::B2DPoint aB2DPoint(GetPathPoly().getB2DPolygon(nPoly).getB2DPoint(nPnt));
2461 return Point(FRound(aB2DPoint.getX()), FRound(aB2DPoint.getY()));
2464 bool SdrPathObj::IsPolyObj() const
2466 return true;
2469 sal_uInt32 SdrPathObj::GetPointCount() const
2471 const sal_uInt32 nPolyCount(GetPathPoly().count());
2472 sal_uInt32 nRetval(0);
2474 for(sal_uInt32 a(0); a < nPolyCount; a++)
2476 nRetval += GetPathPoly().getB2DPolygon(a).count();
2479 return nRetval;
2482 Point SdrPathObj::GetPoint(sal_uInt32 nHdlNum) const
2484 Point aRetval;
2485 sal_uInt32 nPoly,nPnt;
2487 if(PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt))
2489 const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(nPoly));
2490 const basegfx::B2DPoint aPoint(aPoly.getB2DPoint(nPnt));
2491 aRetval = Point(FRound(aPoint.getX()), FRound(aPoint.getY()));
2494 return aRetval;
2497 void SdrPathObj::NbcSetPoint(const Point& rPnt, sal_uInt32 nHdlNum)
2499 sal_uInt32 nPoly,nPnt;
2501 if(PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt))
2503 basegfx::B2DPolygon aNewPolygon(GetPathPoly().getB2DPolygon(nPoly));
2504 aNewPolygon.setB2DPoint(nPnt, basegfx::B2DPoint(rPnt.X(), rPnt.Y()));
2505 maPathPolygon.setB2DPolygon(nPoly, aNewPolygon);
2507 if(meKind==OBJ_LINE)
2509 ImpForceLineAngle();
2511 else
2513 if(GetPathPoly().count())
2515 // #i10659# for SdrTextObj, keep aRect up to date
2516 maRect = lcl_ImpGetBoundRect(GetPathPoly());
2520 SetRectsDirty();
2524 sal_uInt32 SdrPathObj::NbcInsPointOld(const Point& rPos, bool bNewObj)
2526 sal_uInt32 nNewHdl;
2528 if(bNewObj)
2530 nNewHdl = NbcInsPoint(rPos, true);
2532 else
2534 // look for smallest distance data
2535 const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y());
2536 sal_uInt32 nSmallestPolyIndex(0);
2537 sal_uInt32 nSmallestEdgeIndex(0);
2538 double fSmallestCut;
2539 basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut);
2541 nNewHdl = NbcInsPoint(rPos, false);
2544 ImpForceKind();
2545 return nNewHdl;
2548 sal_uInt32 SdrPathObj::NbcInsPoint(const Point& rPos, bool bNewObj)
2550 sal_uInt32 nNewHdl;
2552 if(bNewObj)
2554 basegfx::B2DPolygon aNewPoly;
2555 const basegfx::B2DPoint aPoint(rPos.X(), rPos.Y());
2556 aNewPoly.append(aPoint);
2557 aNewPoly.setClosed(IsClosed());
2558 maPathPolygon.append(aNewPoly);
2559 SetRectsDirty();
2560 nNewHdl = GetHdlCount();
2562 else
2564 // look for smallest distance data
2565 const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y());
2566 sal_uInt32 nSmallestPolyIndex(0);
2567 sal_uInt32 nSmallestEdgeIndex(0);
2568 double fSmallestCut;
2569 basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut);
2570 basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(nSmallestPolyIndex));
2571 const bool bBefore(!aCandidate.isClosed() && 0 == nSmallestEdgeIndex && 0.0 == fSmallestCut);
2572 const bool bAfter(!aCandidate.isClosed() && aCandidate.count() == nSmallestEdgeIndex + 2 && 1.0 == fSmallestCut);
2574 if(bBefore)
2576 // before first point
2577 aCandidate.insert(0L, aTestPoint);
2579 if(aCandidate.areControlPointsUsed())
2581 if(aCandidate.isNextControlPointUsed(1))
2583 aCandidate.setNextControlPoint(0, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (1.0 / 3.0)));
2584 aCandidate.setPrevControlPoint(1, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (2.0 / 3.0)));
2588 nNewHdl = 0;
2590 else if(bAfter)
2592 // after last point
2593 aCandidate.append(aTestPoint);
2595 if(aCandidate.areControlPointsUsed())
2597 if(aCandidate.isPrevControlPointUsed(aCandidate.count() - 2))
2599 aCandidate.setNextControlPoint(aCandidate.count() - 2, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (1.0 / 3.0)));
2600 aCandidate.setPrevControlPoint(aCandidate.count() - 1, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (2.0 / 3.0)));
2604 nNewHdl = aCandidate.count() - 1;
2606 else
2608 // in between
2609 bool bSegmentSplit(false);
2610 const sal_uInt32 nNextIndex((nSmallestEdgeIndex + 1) % aCandidate.count());
2612 if(aCandidate.areControlPointsUsed())
2614 if(aCandidate.isNextControlPointUsed(nSmallestEdgeIndex) || aCandidate.isPrevControlPointUsed(nNextIndex))
2616 bSegmentSplit = true;
2620 if(bSegmentSplit)
2622 // rebuild original segment to get the split data
2623 basegfx::B2DCubicBezier aBezierA, aBezierB;
2624 const basegfx::B2DCubicBezier aBezier(
2625 aCandidate.getB2DPoint(nSmallestEdgeIndex),
2626 aCandidate.getNextControlPoint(nSmallestEdgeIndex),
2627 aCandidate.getPrevControlPoint(nNextIndex),
2628 aCandidate.getB2DPoint(nNextIndex));
2630 // split and insert hit point
2631 aBezier.split(fSmallestCut, &aBezierA, &aBezierB);
2632 aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint);
2634 // since we inserted hit point and not split point, we need to add an offset
2635 // to the control points to get the C1 continuity we want to achieve
2636 const basegfx::B2DVector aOffset(aTestPoint - aBezierA.getEndPoint());
2637 aCandidate.setNextControlPoint(nSmallestEdgeIndex, aBezierA.getControlPointA() + aOffset);
2638 aCandidate.setPrevControlPoint(nSmallestEdgeIndex + 1, aBezierA.getControlPointB() + aOffset);
2639 aCandidate.setNextControlPoint(nSmallestEdgeIndex + 1, aBezierB.getControlPointA() + aOffset);
2640 aCandidate.setPrevControlPoint((nSmallestEdgeIndex + 2) % aCandidate.count(), aBezierB.getControlPointB() + aOffset);
2642 else
2644 aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint);
2647 nNewHdl = nSmallestEdgeIndex + 1;
2650 maPathPolygon.setB2DPolygon(nSmallestPolyIndex, aCandidate);
2652 // create old polygon index from it
2653 for(sal_uInt32 a(0); a < nSmallestPolyIndex; a++)
2655 nNewHdl += GetPathPoly().getB2DPolygon(a).count();
2659 ImpForceKind();
2660 return nNewHdl;
2663 SdrObject* SdrPathObj::RipPoint(sal_uInt32 nHdlNum, sal_uInt32& rNewPt0Index)
2665 SdrPathObj* pNewObj = nullptr;
2666 const basegfx::B2DPolyPolygon aLocalPolyPolygon(GetPathPoly());
2667 sal_uInt32 nPoly, nPnt;
2669 if(PolyPolygonEditor::GetRelativePolyPoint(aLocalPolyPolygon, nHdlNum, nPoly, nPnt))
2671 if(0 == nPoly)
2673 const basegfx::B2DPolygon aCandidate(aLocalPolyPolygon.getB2DPolygon(nPoly));
2674 const sal_uInt32 nPointCount(aCandidate.count());
2676 if(nPointCount)
2678 if(IsClosed())
2680 // when closed, RipPoint means to open the polygon at the selected point. To
2681 // be able to do that, it is necessary to make the selected point the first one
2682 basegfx::B2DPolygon aNewPolygon(basegfx::utils::makeStartPoint(aCandidate, nPnt));
2683 SetPathPoly(basegfx::B2DPolyPolygon(aNewPolygon));
2684 ToggleClosed();
2686 // give back new position of old start point (historical reasons)
2687 rNewPt0Index = (nPointCount - nPnt) % nPointCount;
2689 else
2691 if(nPointCount >= 3 && nPnt != 0 && nPnt + 1 < nPointCount)
2693 // split in two objects at point nPnt
2694 basegfx::B2DPolygon aSplitPolyA(aCandidate, 0L, nPnt + 1);
2695 SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyA));
2697 pNewObj = Clone();
2698 basegfx::B2DPolygon aSplitPolyB(aCandidate, nPnt, nPointCount - nPnt);
2699 pNewObj->SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyB));
2706 return pNewObj;
2709 SdrObject* SdrPathObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const
2711 // #i89784# check for FontWork with activated HideContour
2712 const drawinglayer::attribute::SdrTextAttribute aText(
2713 drawinglayer::primitive2d::createNewSdrTextAttribute(GetObjectItemSet(), *getText(0)));
2714 const bool bHideContour(
2715 !aText.isDefault() && !aText.getSdrFormTextAttribute().isDefault() && aText.isHideContour());
2717 SdrObject* pRet = bHideContour ?
2718 nullptr :
2719 ImpConvertMakeObj(GetPathPoly(), IsClosed(), bBezier);
2721 SdrPathObj* pPath = dynamic_cast<SdrPathObj*>( pRet );
2723 if(pPath)
2725 if(pPath->GetPathPoly().areControlPointsUsed())
2727 if(!bBezier)
2729 // reduce all bezier curves
2730 pPath->SetPathPoly(basegfx::utils::adaptiveSubdivideByAngle(pPath->GetPathPoly()));
2733 else
2735 if(bBezier)
2737 // create bezier curves
2738 pPath->SetPathPoly(basegfx::utils::expandToCurve(pPath->GetPathPoly()));
2743 if(bAddText)
2745 pRet = ImpConvertAddText(pRet, bBezier);
2748 return pRet;
2751 SdrObjGeoData* SdrPathObj::NewGeoData() const
2753 return new SdrPathObjGeoData;
2756 void SdrPathObj::SaveGeoData(SdrObjGeoData& rGeo) const
2758 SdrTextObj::SaveGeoData(rGeo);
2759 SdrPathObjGeoData& rPGeo = static_cast<SdrPathObjGeoData&>( rGeo );
2760 rPGeo.maPathPolygon=GetPathPoly();
2761 rPGeo.meKind=meKind;
2764 void SdrPathObj::RestGeoData(const SdrObjGeoData& rGeo)
2766 SdrTextObj::RestGeoData(rGeo);
2767 const SdrPathObjGeoData& rPGeo=static_cast<const SdrPathObjGeoData&>(rGeo);
2768 maPathPolygon=rPGeo.maPathPolygon;
2769 meKind=rPGeo.meKind;
2770 ImpForceKind(); // to set bClosed (among other things)
2773 void SdrPathObj::NbcSetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly)
2775 if(GetPathPoly() != rPathPoly)
2777 maPathPolygon=rPathPoly;
2778 ImpForceKind();
2779 SetRectsDirty();
2783 void SdrPathObj::SetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly)
2785 if(GetPathPoly() != rPathPoly)
2787 tools::Rectangle aBoundRect0; if (pUserCall!=nullptr) aBoundRect0=GetLastBoundRect();
2788 NbcSetPathPoly(rPathPoly);
2789 SetChanged();
2790 BroadcastObjectChange();
2791 SendUserCall(SdrUserCallType::Resize,aBoundRect0);
2795 void SdrPathObj::ToggleClosed()
2797 tools::Rectangle aBoundRect0;
2798 if(pUserCall != nullptr)
2799 aBoundRect0 = GetLastBoundRect();
2800 ImpSetClosed(!IsClosed()); // set new ObjKind
2801 ImpForceKind(); // because we want Line -> Poly -> PolyLine instead of Line -> Poly -> Line
2802 SetRectsDirty();
2803 SetChanged();
2804 BroadcastObjectChange();
2805 SendUserCall(SdrUserCallType::Resize, aBoundRect0);
2808 ImpPathForDragAndCreate& SdrPathObj::impGetDAC() const
2810 if(!mpDAC)
2812 const_cast<SdrPathObj*>(this)->mpDAC.reset(new ImpPathForDragAndCreate(*const_cast<SdrPathObj*>(this)));
2815 return *mpDAC;
2819 // transformation interface for StarOfficeAPI. This implements support for
2820 // homogeneous 3x3 matrices containing the transformation of the SdrObject. At the
2821 // moment it contains a shearX, rotation and translation, but for setting all linear
2822 // transforms like Scale, ShearX, ShearY, Rotate and Translate are supported.
2825 // gets base transformation and rectangle of object. If it's an SdrPathObj it fills the PolyPolygon
2826 // with the base geometry and returns TRUE. Otherwise it returns FALSE.
2827 bool SdrPathObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& rPolyPolygon) const
2829 double fRotate(0.0);
2830 double fShearX(0.0);
2831 basegfx::B2DTuple aScale(1.0, 1.0);
2832 basegfx::B2DTuple aTranslate(0.0, 0.0);
2834 if(GetPathPoly().count())
2836 // copy geometry
2837 basegfx::B2DHomMatrix aMoveToZeroMatrix;
2838 rPolyPolygon = GetPathPoly();
2840 if(OBJ_LINE == meKind)
2842 // ignore shear and rotate, just use scale and translate
2843 OSL_ENSURE(GetPathPoly().count() > 0 && GetPathPoly().getB2DPolygon(0).count() > 1, "OBJ_LINE with too few polygons (!)");
2844 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2845 // itself, else this method will no longer return the full polygon information (curve will
2846 // be lost)
2847 const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2848 aScale = aPolyRangeNoCurve.getRange();
2849 aTranslate = aPolyRangeNoCurve.getMinimum();
2851 // define matrix for move polygon to zero point
2852 aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY());
2854 else
2856 if(aGeo.nShearAngle || aGeo.nRotationAngle)
2858 // get rotate and shear in drawingLayer notation
2859 fRotate = aGeo.nRotationAngle * F_PI18000;
2860 fShearX = aGeo.nShearAngle * F_PI18000;
2862 // build mathematically correct (negative shear and rotate) object transform
2863 // containing shear and rotate to extract unsheared, unrotated polygon
2864 basegfx::B2DHomMatrix aObjectMatrix;
2865 aObjectMatrix.shearX(tan((36000 - aGeo.nShearAngle) * F_PI18000));
2866 aObjectMatrix.rotate((36000 - aGeo.nRotationAngle) * F_PI18000);
2868 // create inverse from it and back-transform polygon
2869 basegfx::B2DHomMatrix aInvObjectMatrix(aObjectMatrix);
2870 aInvObjectMatrix.invert();
2871 rPolyPolygon.transform(aInvObjectMatrix);
2873 // get range from unsheared, unrotated polygon and extract scale and translate.
2874 // transform topLeft from it back to transformed state to get original
2875 // topLeft (rotation center)
2876 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2877 // itself, else this method will no longer return the full polygon information (curve will
2878 // be lost)
2879 const basegfx::B2DRange aCorrectedRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2880 aTranslate = aObjectMatrix * aCorrectedRangeNoCurve.getMinimum();
2881 aScale = aCorrectedRangeNoCurve.getRange();
2883 // define matrix for move polygon to zero point
2884 // #i112280# Added missing minus for Y-Translation
2885 aMoveToZeroMatrix.translate(-aCorrectedRangeNoCurve.getMinX(), -aCorrectedRangeNoCurve.getMinY());
2887 else
2889 // get scale and translate from unsheared, unrotated polygon
2890 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2891 // itself, else this method will no longer return the full polygon information (curve will
2892 // be lost)
2893 const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2894 aScale = aPolyRangeNoCurve.getRange();
2895 aTranslate = aPolyRangeNoCurve.getMinimum();
2897 // define matrix for move polygon to zero point
2898 aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY());
2902 // move polygon to zero point with pre-defined matrix
2903 rPolyPolygon.transform(aMoveToZeroMatrix);
2906 // position maybe relative to anchorpos, convert
2907 if( pModel && pModel->IsWriter() )
2909 if(GetAnchorPos().X() || GetAnchorPos().Y())
2911 aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
2915 // force MapUnit to 100th mm
2916 const MapUnit eMapUnit(GetObjectMapUnit());
2917 if(eMapUnit != MapUnit::Map100thMM)
2919 switch(eMapUnit)
2921 case MapUnit::MapTwip :
2923 // position
2924 aTranslate.setX(ImplTwipsToMM(aTranslate.getX()));
2925 aTranslate.setY(ImplTwipsToMM(aTranslate.getY()));
2927 // size
2928 aScale.setX(ImplTwipsToMM(aScale.getX()));
2929 aScale.setY(ImplTwipsToMM(aScale.getY()));
2931 // polygon
2932 basegfx::B2DHomMatrix aTwipsToMM;
2933 const double fFactorTwipsToMM(127.0 / 72.0);
2934 aTwipsToMM.scale(fFactorTwipsToMM, fFactorTwipsToMM);
2935 rPolyPolygon.transform(aTwipsToMM);
2937 break;
2939 default:
2941 OSL_FAIL("TRGetBaseGeometry: Missing unit translation to 100th mm!");
2946 // build return value matrix
2947 rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
2948 aScale,
2949 basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX),
2950 basegfx::fTools::equalZero(fRotate) ? 0.0 : -fRotate,
2951 aTranslate);
2953 return true;
2956 // Sets the base geometry of the object using infos contained in the homogeneous 3x3 matrix.
2957 // If it's an SdrPathObj it will use the provided geometry information. The Polygon has
2958 // to use (0,0) as upper left and will be scaled to the given size in the matrix.
2959 void SdrPathObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& rPolyPolygon)
2961 // break up matrix
2962 basegfx::B2DTuple aScale;
2963 basegfx::B2DTuple aTranslate;
2964 double fRotate, fShearX;
2965 rMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
2967 // #i75086# Old DrawingLayer (GeoStat and geometry) does not support holding negative scalings
2968 // in X and Y which equal a 180 degree rotation. Recognize it and react accordingly
2969 if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0))
2971 aScale.setX(fabs(aScale.getX()));
2972 aScale.setY(fabs(aScale.getY()));
2973 fRotate = fmod(fRotate + F_PI, F_2PI);
2976 // copy poly
2977 basegfx::B2DPolyPolygon aNewPolyPolygon(rPolyPolygon);
2979 // reset object shear and rotations
2980 aGeo.nRotationAngle = 0;
2981 aGeo.RecalcSinCos();
2982 aGeo.nShearAngle = 0;
2983 aGeo.RecalcTan();
2985 // force metric to pool metric
2986 const MapUnit eMapUnit(GetObjectMapUnit());
2987 if(eMapUnit != MapUnit::Map100thMM)
2989 switch(eMapUnit)
2991 case MapUnit::MapTwip :
2993 // position
2994 aTranslate.setX(ImplMMToTwips(aTranslate.getX()));
2995 aTranslate.setY(ImplMMToTwips(aTranslate.getY()));
2997 // size
2998 aScale.setX(ImplMMToTwips(aScale.getX()));
2999 aScale.setY(ImplMMToTwips(aScale.getY()));
3001 // polygon
3002 basegfx::B2DHomMatrix aMMToTwips;
3003 const double fFactorMMToTwips(72.0 / 127.0);
3004 aMMToTwips.scale(fFactorMMToTwips, fFactorMMToTwips);
3005 aNewPolyPolygon.transform(aMMToTwips);
3007 break;
3009 default:
3011 OSL_FAIL("TRSetBaseGeometry: Missing unit translation to PoolMetric!");
3016 if( pModel && pModel->IsWriter() )
3018 // if anchor is used, make position relative to it
3019 if(GetAnchorPos().X() || GetAnchorPos().Y())
3021 aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
3025 // create transformation for polygon, set values at aGeo direct
3026 basegfx::B2DHomMatrix aTransform;
3028 // #i75086#
3029 // Given polygon is already scaled (for historical reasons), but not mirrored yet.
3030 // Thus, when scale is negative in X or Y, apply the needed mirroring accordingly.
3031 if(basegfx::fTools::less(aScale.getX(), 0.0) || basegfx::fTools::less(aScale.getY(), 0.0))
3033 aTransform.scale(
3034 basegfx::fTools::less(aScale.getX(), 0.0) ? -1.0 : 1.0,
3035 basegfx::fTools::less(aScale.getY(), 0.0) ? -1.0 : 1.0);
3038 if(!basegfx::fTools::equalZero(fShearX))
3040 aTransform.shearX(tan(-atan(fShearX)));
3041 aGeo.nShearAngle = FRound(atan(fShearX) / F_PI18000);
3042 aGeo.RecalcTan();
3045 if(!basegfx::fTools::equalZero(fRotate))
3047 // #i78696#
3048 // fRotate is mathematically correct for linear transformations, so it's
3049 // the one to use for the geometry change
3050 aTransform.rotate(fRotate);
3052 // #i78696#
3053 // fRotate is mathematically correct, but aGeoStat.nRotationAngle is
3054 // mirrored -> mirror value here
3055 aGeo.nRotationAngle = NormAngle360(FRound(-fRotate / F_PI18000));
3056 aGeo.RecalcSinCos();
3059 if(!aTranslate.equalZero())
3061 // #i39529# absolute positioning, so get current position (without control points (!))
3062 const basegfx::B2DRange aCurrentRange(basegfx::utils::getRange(aNewPolyPolygon));
3063 aTransform.translate(aTranslate.getX() - aCurrentRange.getMinX(), aTranslate.getY() - aCurrentRange.getMinY());
3066 // transform polygon and trigger change
3067 aNewPolyPolygon.transform(aTransform);
3068 SetPathPoly(aNewPolyPolygon);
3071 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */