1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <o3tl/unit_conversion.hxx>
21 #include <tools/bigint.hxx>
22 #include <tools/helpers.hxx>
23 #include <rtl/ustrbuf.hxx>
24 #include <svx/svdopath.hxx>
25 #include <math.h>
26 #include <svx/xpoly.hxx>
27 #include <svx/svdtrans.hxx>
28 #include <svx/svddrag.hxx>
29 #include <svx/svdmodel.hxx>
30 #include <svx/svdhdl.hxx>
31 #include <svx/svdview.hxx>
32 #include <svx/dialmgr.hxx>
33 #include <svx/strings.hrc>
34
35 #include <svx/polypolygoneditor.hxx>
36 #include <sdr/contact/viewcontactofsdrpathobj.hxx>
37 #include <basegfx/matrix/b2dhommatrix.hxx>
38 #include <basegfx/point/b2dpoint.hxx>
39 #include <basegfx/polygon/b2dpolypolygontools.hxx>
40 #include <basegfx/range/b2drange.hxx>
41 #include <basegfx/curve/b2dcubicbezier.hxx>
42 #include <basegfx/polygon/b2dpolygontools.hxx>
43 #include <sdr/attribute/sdrtextattribute.hxx>
44 #include <sdr/primitive2d/sdrattributecreator.hxx>
45 #include <basegfx/matrix/b2dhommatrixtools.hxx>
46 #include <sdr/attribute/sdrformtextattribute.hxx>
47 #include <vcl/ptrstyle.hxx>
48 #include <memory>
49 #include <sal/log.hxx>
50 #include <osl/diagnose.h>
51
52 using namespace sdr;
53
GetPrevPnt(sal_uInt16 nPnt,sal_uInt16 nPntMax,bool bClosed)54 static sal_uInt16 GetPrevPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed)
55 {
56 if (nPnt>0) {
57 nPnt--;
58 } else {
59 nPnt=nPntMax;
60 if (bClosed) nPnt--;
61 }
62 return nPnt;
63 }
64
GetNextPnt(sal_uInt16 nPnt,sal_uInt16 nPntMax,bool bClosed)65 static sal_uInt16 GetNextPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed)
66 {
67 nPnt++;
68 if (nPnt>nPntMax || (bClosed && nPnt>=nPntMax)) nPnt=0;
69 return nPnt;
70 }
71
72 namespace {
73
74 struct ImpSdrPathDragData : public SdrDragStatUserData
75 {
76 XPolygon aXP; // section of the original polygon
77 bool bValid; // FALSE = too few points
78 bool bClosed; // closed object?
79 sal_uInt16 nPoly; // number of the polygon in the PolyPolygon
80 sal_uInt16 nPnt; // number of point in the above polygon
81 sal_uInt16 nPointCount; // number of points of the polygon
82 bool bBegPnt; // dragged point is first point of a Polyline
83 bool bEndPnt; // dragged point is finishing point of a Polyline
84 sal_uInt16 nPrevPnt; // index of previous point
85 sal_uInt16 nNextPnt; // index of next point
86 bool bPrevIsBegPnt; // previous point is first point of a Polyline
87 bool bNextIsEndPnt; // next point is first point of a Polyline
88 sal_uInt16 nPrevPrevPnt; // index of point before previous point
89 sal_uInt16 nNextNextPnt; // index of point after next point
90 bool bControl; // point is a control point
91 bool bIsNextControl; // point is a control point after a support point
92 bool bPrevIsControl; // if nPnt is a support point: a control point comes before
93 bool bNextIsControl; // if nPnt is a support point: a control point comes after
94 sal_uInt16 nPrevPrevPnt0;
95 sal_uInt16 nPrevPnt0;
96 sal_uInt16 nPnt0;
97 sal_uInt16 nNextPnt0;
98 sal_uInt16 nNextNextPnt0;
99 bool bEliminate; // delete point? (is set by MovDrag)
100
101 bool mbMultiPointDrag;
102 const XPolyPolygon maOrig;
103 XPolyPolygon maMove;
104 std::vector<SdrHdl*> maHandles;
105
106 public:
107 ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag);
108 void ResetPoly(const SdrPathObj& rPO);
IsMultiPointDrag__anonea1551cd0111::ImpSdrPathDragData109 bool IsMultiPointDrag() const { return mbMultiPointDrag; }
110 };
111
112 }
113
ImpSdrPathDragData(const SdrPathObj & rPO,const SdrHdl & rHdl,bool bMuPoDr,const SdrDragStat & rDrag)114 ImpSdrPathDragData::ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag)
115 : aXP(5)
116 , bValid(false)
117 , bClosed(false)
118 , nPoly(0)
119 , nPnt(0)
120 , nPointCount(0)
121 , bBegPnt(false)
122 , bEndPnt(false)
123 , nPrevPnt(0)
124 , nNextPnt(0)
125 , bPrevIsBegPnt(false)
126 , bNextIsEndPnt(false)
127 , nPrevPrevPnt(0)
128 , nNextNextPnt(0)
129 , bControl(false)
130 , bIsNextControl(false)
131 , bPrevIsControl(false)
132 , bNextIsControl(false)
133 , nPrevPrevPnt0(0)
134 , nPrevPnt0(0)
135 , nPnt0(0)
136 , nNextPnt0(0)
137 , nNextNextPnt0(0)
138 , bEliminate(false)
139 , mbMultiPointDrag(bMuPoDr)
140 , maOrig(rPO.GetPathPoly())
141 , maHandles(0)
142 {
143 if(mbMultiPointDrag)
144 {
145 const SdrMarkView& rMarkView = *rDrag.GetView();
146 const SdrHdlList& rHdlList = rMarkView.GetHdlList();
147 const size_t nHdlCount = rHdlList.GetHdlCount();
148 const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr);
149
150 for(size_t a = 0; a < nHdlCount; ++a)
151 {
152 SdrHdl* pTestHdl = rHdlList.GetHdl(a);
153
154 if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject)
155 {
156 maHandles.push_back(pTestHdl);
157 }
158 }
159
160 maMove = maOrig;
161 bValid = true;
162 }
163 else
164 {
165 sal_uInt16 nPntMax = 0; // maximum index
166 bValid=false;
167 bClosed=rPO.IsClosed(); // closed object?
168 nPoly=static_cast<sal_uInt16>(rHdl.GetPolyNum()); // number of the polygon in the PolyPolygon
169 nPnt=static_cast<sal_uInt16>(rHdl.GetPointNum()); // number of points in the above polygon
170 const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly));
171 nPointCount=aTmpXP.GetPointCount(); // number of point of the polygon
172 if (nPointCount==0 || (bClosed && nPointCount==1)) return; // minimum of 1 points for Lines, minimum of 2 points for Polygon
173 nPntMax=nPointCount-1; // maximum index
174 bBegPnt=!bClosed && nPnt==0; // dragged point is first point of a Polyline
175 bEndPnt=!bClosed && nPnt==nPntMax; // dragged point is finishing point of a Polyline
176 if (bClosed && nPointCount<=3) { // if polygon is only a line
177 bBegPnt=(nPointCount<3) || nPnt==0;
178 bEndPnt=(nPointCount<3) || nPnt==nPntMax-1;
179 }
180 nPrevPnt=nPnt; // index of previous point
181 nNextPnt=nPnt; // index of next point
182 if (!bBegPnt) nPrevPnt=GetPrevPnt(nPnt,nPntMax,bClosed);
183 if (!bEndPnt) nNextPnt=GetNextPnt(nPnt,nPntMax,bClosed);
184 bPrevIsBegPnt=bBegPnt || (!bClosed && nPrevPnt==0);
185 bNextIsEndPnt=bEndPnt || (!bClosed && nNextPnt==nPntMax);
186 nPrevPrevPnt=nPnt; // index of point before previous point
187 nNextNextPnt=nPnt; // index of point after next point
188 if (!bPrevIsBegPnt) nPrevPrevPnt=GetPrevPnt(nPrevPnt,nPntMax,bClosed);
189 if (!bNextIsEndPnt) nNextNextPnt=GetNextPnt(nNextPnt,nPntMax,bClosed);
190 bControl=rHdl.IsPlusHdl(); // point is a control point
191 bIsNextControl=false; // point is a control point after a support point
192 bPrevIsControl=false; // if nPnt is a support point: a control point comes before
193 bNextIsControl=false; // if nPnt is a support point: a control point comes after
194 if (bControl) {
195 bIsNextControl=!aTmpXP.IsControl(nPrevPnt);
196 } else {
197 bPrevIsControl=!bBegPnt && !bPrevIsBegPnt && aTmpXP.GetFlags(nPrevPnt)==PolyFlags::Control;
198 bNextIsControl=!bEndPnt && !bNextIsEndPnt && aTmpXP.GetFlags(nNextPnt)==PolyFlags::Control;
199 }
200 nPrevPrevPnt0=nPrevPrevPnt;
201 nPrevPnt0 =nPrevPnt;
202 nPnt0 =nPnt;
203 nNextPnt0 =nNextPnt;
204 nNextNextPnt0=nNextNextPnt;
205 nPrevPrevPnt=0;
206 nPrevPnt=1;
207 nPnt=2;
208 nNextPnt=3;
209 nNextNextPnt=4;
210 bEliminate=false;
211 ResetPoly(rPO);
212 bValid=true;
213 }
214 }
215
ResetPoly(const SdrPathObj & rPO)216 void ImpSdrPathDragData::ResetPoly(const SdrPathObj& rPO)
217 {
218 const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly));
219 aXP[0]=aTmpXP[nPrevPrevPnt0]; aXP.SetFlags(0,aTmpXP.GetFlags(nPrevPrevPnt0));
220 aXP[1]=aTmpXP[nPrevPnt0]; aXP.SetFlags(1,aTmpXP.GetFlags(nPrevPnt0));
221 aXP[2]=aTmpXP[nPnt0]; aXP.SetFlags(2,aTmpXP.GetFlags(nPnt0));
222 aXP[3]=aTmpXP[nNextPnt0]; aXP.SetFlags(3,aTmpXP.GetFlags(nNextPnt0));
223 aXP[4]=aTmpXP[nNextNextPnt0]; aXP.SetFlags(4,aTmpXP.GetFlags(nNextNextPnt0));
224 }
225
226 namespace {
227
228 struct ImpPathCreateUser : public SdrDragStatUserData
229 {
230 Point aBezControl0;
231 Point aBezStart;
232 Point aBezCtrl1;
233 Point aBezCtrl2;
234 Point aBezEnd;
235 Point aCircStart;
236 Point aCircEnd;
237 Point aCircCenter;
238 Point aLineStart;
239 Point aLineEnd;
240 Point aRectP1;
241 Point aRectP2;
242 Point aRectP3;
243 tools::Long nCircRadius;
244 Degree100 nCircStAngle;
245 Degree100 nCircRelAngle;
246 bool bBezier;
247 bool bBezHasCtrl0;
248 bool bCircle;
249 bool bAngleSnap;
250 bool bLine;
251 bool bLine90;
252 bool bRect;
253 bool bMixedCreate;
254 sal_uInt16 nBezierStartPoint;
255 SdrObjKind eStartKind;
256 SdrObjKind eCurrentKind;
257
258 public:
ImpPathCreateUser__anonea1551cd0211::ImpPathCreateUser259 ImpPathCreateUser(): nCircRadius(0),nCircStAngle(0),nCircRelAngle(0),
260 bBezier(false),bBezHasCtrl0(false),bCircle(false),bAngleSnap(false),bLine(false),bLine90(false),bRect(false),
261 bMixedCreate(false),nBezierStartPoint(0),eStartKind(OBJ_NONE),eCurrentKind(OBJ_NONE) { }
262
ResetFormFlags__anonea1551cd0211::ImpPathCreateUser263 void ResetFormFlags() { bBezier=false; bCircle=false; bLine=false; bRect=false; }
IsFormFlag__anonea1551cd0211::ImpPathCreateUser264 bool IsFormFlag() const { return bBezier || bCircle || bLine || bRect; }
265 XPolygon GetFormPoly() const;
266 void CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown);
267 XPolygon GetBezierPoly() const;
268 void CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
269 XPolygon GetCirclePoly() const;
270 void CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
271 static Point CalcLine(const Point& rCsr, tools::Long nDirX, tools::Long nDirY, SdrView const * pView);
272 XPolygon GetLinePoly() const;
273 void CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
274 XPolygon GetRectPoly() const;
275 };
276
277 }
278
GetFormPoly() const279 XPolygon ImpPathCreateUser::GetFormPoly() const
280 {
281 if (bBezier) return GetBezierPoly();
282 if (bCircle) return GetCirclePoly();
283 if (bLine) return GetLinePoly();
284 if (bRect) return GetRectPoly();
285 return XPolygon();
286 }
287
CalcBezier(const Point & rP1,const Point & rP2,const Point & rDir,bool bMouseDown)288 void ImpPathCreateUser::CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown)
289 {
290 aBezStart=rP1;
291 aBezCtrl1=rP1+rDir;
292 aBezCtrl2=rP2;
293
294 // #i21479#
295 // Also copy the end point when no end point is set yet
296 if (!bMouseDown || (0 == aBezEnd.X() && 0 == aBezEnd.Y())) aBezEnd=rP2;
297
298 bBezier=true;
299 }
300
GetBezierPoly() const301 XPolygon ImpPathCreateUser::GetBezierPoly() const
302 {
303 XPolygon aXP(4);
304 aXP[0]=aBezStart; aXP.SetFlags(0,PolyFlags::Smooth);
305 aXP[1]=aBezCtrl1; aXP.SetFlags(1,PolyFlags::Control);
306 aXP[2]=aBezCtrl2; aXP.SetFlags(2,PolyFlags::Control);
307 aXP[3]=aBezEnd;
308 return aXP;
309 }
310
CalcCircle(const Point & rP1,const Point & rP2,const Point & rDir,SdrView const * pView)311 void ImpPathCreateUser::CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
312 {
313 Degree100 nTangAngle=GetAngle(rDir);
314 aCircStart=rP1;
315 aCircEnd=rP2;
316 aCircCenter=rP1;
317 tools::Long dx=rP2.X()-rP1.X();
318 tools::Long dy=rP2.Y()-rP1.Y();
319 Degree100 dAngle=GetAngle(Point(dx,dy))-nTangAngle;
320 dAngle=NormAngle36000(dAngle);
321 Degree100 nTmpAngle=NormAngle36000(9000_deg100-dAngle);
322 bool bRet=nTmpAngle!=9000_deg100 && nTmpAngle!=27000_deg100;
323 tools::Long nRad=0;
324 if (bRet) {
325 double cs = cos(nTmpAngle.get() * F_PI18000);
326 double nR=static_cast<double>(GetLen(Point(dx,dy)))/cs/2;
327 nRad=std::abs(FRound(nR));
328 }
329 if (dAngle<18000_deg100) {
330 nCircStAngle=NormAngle36000(nTangAngle-9000_deg100);
331 nCircRelAngle=NormAngle36000(2_deg100*dAngle);
332 aCircCenter.AdjustX(FRound(nRad * cos((nTangAngle.get() + 9000) * F_PI18000)));
333 aCircCenter.AdjustY(-(FRound(nRad * sin((nTangAngle.get() + 9000) * F_PI18000))));
334 } else {
335 nCircStAngle=NormAngle36000(nTangAngle+9000_deg100);
336 nCircRelAngle=-NormAngle36000(36000_deg100-2_deg100*dAngle);
337 aCircCenter.AdjustX(FRound(nRad * cos((nTangAngle.get() - 9000) * F_PI18000)));
338 aCircCenter.AdjustY(-(FRound(nRad * sin((nTangAngle.get() - 9000) * F_PI18000))));
339 }
340 bAngleSnap=pView!=nullptr && pView->IsAngleSnapEnabled();
341 if (bAngleSnap) {
342 Degree100 nSA=pView->GetSnapAngle();
343 if (nSA) { // angle snapping
344 bool bNeg=nCircRelAngle<0_deg100;
345 if (bNeg) nCircRelAngle=-nCircRelAngle;
346 nCircRelAngle+=nSA/2_deg100;
347 nCircRelAngle/=nSA;
348 nCircRelAngle*=nSA;
349 nCircRelAngle=NormAngle36000(nCircRelAngle);
350 if (bNeg) nCircRelAngle=-nCircRelAngle;
351 }
352 }
353 nCircRadius=nRad;
354 if (nRad==0 || abs(nCircRelAngle).get()<5) bRet=false;
355 bCircle=bRet;
356 }
357
GetCirclePoly() const358 XPolygon ImpPathCreateUser::GetCirclePoly() const
359 {
360 if (nCircRelAngle>=0_deg100) {
361 XPolygon aXP(aCircCenter,nCircRadius,nCircRadius,
362 nCircStAngle, nCircStAngle+nCircRelAngle,false);
363 aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth);
364 if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd;
365 return aXP;
366 } else {
367 XPolygon aXP(aCircCenter,nCircRadius,nCircRadius,
368 NormAngle36000(nCircStAngle+nCircRelAngle), nCircStAngle,false);
369 sal_uInt16 nCount=aXP.GetPointCount();
370 for (sal_uInt16 nNum=nCount/2; nNum>0;) {
371 nNum--; // reverse XPoly's order of points
372 sal_uInt16 n2=nCount-nNum-1;
373 Point aPt(aXP[nNum]);
374 aXP[nNum]=aXP[n2];
375 aXP[n2]=aPt;
376 }
377 aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth);
378 if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd;
379 return aXP;
380 }
381 }
382
CalcLine(const Point & aCsr,tools::Long nDirX,tools::Long nDirY,SdrView const * pView)383 Point ImpPathCreateUser::CalcLine(const Point& aCsr, tools::Long nDirX, tools::Long nDirY, SdrView const * pView)
384 {
385 tools::Long x=aCsr.X();
386 tools::Long y=aCsr.Y();
387 bool bHLin=nDirY==0;
388 bool bVLin=nDirX==0;
389 if (bHLin) y=0;
390 else if (bVLin) x=0;
391 else {
392 tools::Long x1=BigMulDiv(y,nDirX,nDirY);
393 tools::Long y1=y;
394 tools::Long x2=x;
395 tools::Long y2=BigMulDiv(x,nDirY,nDirX);
396 tools::Long l1=std::abs(x1)+std::abs(y1);
397 tools::Long l2=std::abs(x2)+std::abs(y2);
398 if ((l1<=l2) != (pView!=nullptr && pView->IsBigOrtho())) {
399 x=x1; y=y1;
400 } else {
401 x=x2; y=y2;
402 }
403 }
404 return Point(x,y);
405 }
406
CalcLine(const Point & rP1,const Point & rP2,const Point & rDir,SdrView const * pView)407 void ImpPathCreateUser::CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
408 {
409 aLineStart=rP1;
410 aLineEnd=rP2;
411 bLine90=false;
412 if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bLine=false; return; }
413 Point aTmpPt(rP2-rP1);
414 tools::Long nDirX=rDir.X();
415 tools::Long nDirY=rDir.Y();
416 Point aP1(CalcLine(aTmpPt, nDirX, nDirY,pView)); aP1-=aTmpPt; tools::Long nQ1=std::abs(aP1.X())+std::abs(aP1.Y());
417 Point aP2(CalcLine(aTmpPt, nDirY,-nDirX,pView)); aP2-=aTmpPt; tools::Long nQ2=std::abs(aP2.X())+std::abs(aP2.Y());
418 if (pView!=nullptr && pView->IsOrtho()) nQ1=0; // Ortho turns off at right angle
419 bLine90=nQ1>2*nQ2;
420 if (!bLine90) { // smooth transition
421 aLineEnd+=aP1;
422 } else { // rectangular transition
423 aLineEnd+=aP2;
424 }
425 bLine=true;
426 }
427
GetLinePoly() const428 XPolygon ImpPathCreateUser::GetLinePoly() const
429 {
430 XPolygon aXP(2);
431 aXP[0]=aLineStart; if (!bLine90) aXP.SetFlags(0,PolyFlags::Smooth);
432 aXP[1]=aLineEnd;
433 return aXP;
434 }
435
CalcRect(const Point & rP1,const Point & rP2,const Point & rDir,SdrView const * pView)436 void ImpPathCreateUser::CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
437 {
438 aRectP1=rP1;
439 aRectP2=rP1;
440 aRectP3=rP2;
441 if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bRect=false; return; }
442 Point aTmpPt(rP2-rP1);
443 tools::Long nDirX=rDir.X();
444 tools::Long nDirY=rDir.Y();
445 tools::Long x=aTmpPt.X();
446 tools::Long y=aTmpPt.Y();
447 bool bHLin=nDirY==0;
448 bool bVLin=nDirX==0;
449 if (bHLin) y=0;
450 else if (bVLin) x=0;
451 else {
452 y=BigMulDiv(x,nDirY,nDirX);
453 tools::Long nHypLen=aTmpPt.Y()-y;
454 Degree100 nTangAngle=-GetAngle(rDir);
455 // sin=g/h, g=h*sin
456 double a = nTangAngle.get() * F_PI18000;
457 double sn=sin(a);
458 double cs=cos(a);
459 double nGKathLen=nHypLen*sn;
460 y+=FRound(nGKathLen*sn);
461 x+=FRound(nGKathLen*cs);
462 }
463 aRectP2.AdjustX(x );
464 aRectP2.AdjustY(y );
465 if (pView!=nullptr && pView->IsOrtho()) {
466 tools::Long dx1=aRectP2.X()-aRectP1.X(); tools::Long dx1a=std::abs(dx1);
467 tools::Long dy1=aRectP2.Y()-aRectP1.Y(); tools::Long dy1a=std::abs(dy1);
468 tools::Long dx2=aRectP3.X()-aRectP2.X(); tools::Long dx2a=std::abs(dx2);
469 tools::Long dy2=aRectP3.Y()-aRectP2.Y(); tools::Long dy2a=std::abs(dy2);
470 bool b1MoreThan2=dx1a+dy1a>dx2a+dy2a;
471 if (b1MoreThan2 != pView->IsBigOrtho()) {
472 tools::Long xtemp=dy2a-dx1a; if (dx1<0) xtemp=-xtemp;
473 tools::Long ytemp=dx2a-dy1a; if (dy1<0) ytemp=-ytemp;
474 aRectP2.AdjustX(xtemp );
475 aRectP2.AdjustY(ytemp );
476 aRectP3.AdjustX(xtemp );
477 aRectP3.AdjustY(ytemp );
478 } else {
479 tools::Long xtemp=dy1a-dx2a; if (dx2<0) xtemp=-xtemp;
480 tools::Long ytemp=dx1a-dy2a; if (dy2<0) ytemp=-ytemp;
481 aRectP3.AdjustX(xtemp );
482 aRectP3.AdjustY(ytemp );
483 }
484 }
485 bRect=true;
486 }
487
GetRectPoly() const488 XPolygon ImpPathCreateUser::GetRectPoly() const
489 {
490 XPolygon aXP(3);
491 aXP[0]=aRectP1; aXP.SetFlags(0,PolyFlags::Smooth);
492 aXP[1]=aRectP2;
493 if (aRectP3!=aRectP2) aXP[2]=aRectP3;
494 return aXP;
495 }
496
497 class ImpPathForDragAndCreate
498 {
499 SdrPathObj& mrSdrPathObject;
500 XPolyPolygon aPathPolygon;
501 SdrObjKind meObjectKind;
502 std::unique_ptr<ImpSdrPathDragData>
503 mpSdrPathDragData;
504 bool mbCreating;
505
506 public:
507 explicit ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject);
508
509 // drag stuff
510 bool beginPathDrag( SdrDragStat const & rDrag ) const;
511 bool movePathDrag( SdrDragStat& rDrag ) const;
512 bool endPathDrag( SdrDragStat const & rDrag );
513 OUString getSpecialDragComment(const SdrDragStat& rDrag) const;
514 basegfx::B2DPolyPolygon getSpecialDragPoly(const SdrDragStat& rDrag) const;
515
516 // create stuff
517 void BegCreate(SdrDragStat& rStat);
518 bool MovCreate(SdrDragStat& rStat);
519 bool EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd);
520 bool BckCreate(SdrDragStat const & rStat);
521 void BrkCreate(SdrDragStat& rStat);
522 PointerStyle GetCreatePointer() const;
523
524 // helping stuff
IsClosed(SdrObjKind eKind)525 static bool IsClosed(SdrObjKind eKind) { return eKind==OBJ_POLY || eKind==OBJ_PATHPOLY || eKind==OBJ_PATHFILL || eKind==OBJ_FREEFILL || eKind==OBJ_SPLNFILL; }
IsFreeHand(SdrObjKind eKind)526 static bool IsFreeHand(SdrObjKind eKind) { return eKind==OBJ_FREELINE || eKind==OBJ_FREEFILL; }
IsBezier(SdrObjKind eKind)527 static bool IsBezier(SdrObjKind eKind) { return eKind==OBJ_PATHLINE || eKind==OBJ_PATHFILL; }
IsCreating() const528 bool IsCreating() const { return mbCreating; }
529
530 // get the polygon
531 basegfx::B2DPolyPolygon TakeObjectPolyPolygon(const SdrDragStat& rDrag) const;
532 static basegfx::B2DPolyPolygon TakeDragPolyPolygon(const SdrDragStat& rDrag);
getModifiedPolyPolygon() const533 basegfx::B2DPolyPolygon getModifiedPolyPolygon() const { return aPathPolygon.getB2DPolyPolygon(); }
534 };
535
ImpPathForDragAndCreate(SdrPathObj & rSdrPathObject)536 ImpPathForDragAndCreate::ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject)
537 : mrSdrPathObject(rSdrPathObject),
538 aPathPolygon(rSdrPathObject.GetPathPoly()),
539 meObjectKind(mrSdrPathObject.meKind),
540 mbCreating(false)
541 {
542 }
543
beginPathDrag(SdrDragStat const & rDrag) const544 bool ImpPathForDragAndCreate::beginPathDrag( SdrDragStat const & rDrag ) const
545 {
546 const SdrHdl* pHdl=rDrag.GetHdl();
547 if(!pHdl)
548 return false;
549
550 bool bMultiPointDrag(true);
551
552 if(aPathPolygon[static_cast<sal_uInt16>(pHdl->GetPolyNum())].IsControl(static_cast<sal_uInt16>(pHdl->GetPointNum())))
553 bMultiPointDrag = false;
554
555 if(bMultiPointDrag)
556 {
557 const SdrMarkView& rMarkView = *rDrag.GetView();
558 const SdrHdlList& rHdlList = rMarkView.GetHdlList();
559 const size_t nHdlCount = rHdlList.GetHdlCount();
560 const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr);
561 sal_uInt32 nSelectedPoints(0);
562
563 for(size_t a = 0; a < nHdlCount; ++a)
564 {
565 SdrHdl* pTestHdl = rHdlList.GetHdl(a);
566
567 if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject)
568 {
569 nSelectedPoints++;
570 }
571 }
572
573 if(nSelectedPoints <= 1)
574 bMultiPointDrag = false;
575 }
576
577 const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset( new ImpSdrPathDragData(mrSdrPathObject,*pHdl,bMultiPointDrag,rDrag) );
578
579 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
580 {
581 OSL_FAIL("ImpPathForDragAndCreate::BegDrag(): ImpSdrPathDragData is invalid.");
582 const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset();
583 return false;
584 }
585
586 return true;
587 }
588
movePathDrag(SdrDragStat & rDrag) const589 bool ImpPathForDragAndCreate::movePathDrag( SdrDragStat& rDrag ) const
590 {
591 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
592 {
593 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
594 return false;
595 }
596
597 if(mpSdrPathDragData->IsMultiPointDrag())
598 {
599 Point aDelta(rDrag.GetNow() - rDrag.GetStart());
600
601 if(aDelta.X() || aDelta.Y())
602 {
603 for(SdrHdl* pHandle : mpSdrPathDragData->maHandles)
604 {
605 const sal_uInt16 nPolyIndex(static_cast<sal_uInt16>(pHandle->GetPolyNum()));
606 const sal_uInt16 nPointIndex(static_cast<sal_uInt16>(pHandle->GetPointNum()));
607 const XPolygon& rOrig = mpSdrPathDragData->maOrig[nPolyIndex];
608 XPolygon& rMove = mpSdrPathDragData->maMove[nPolyIndex];
609 const sal_uInt16 nPointCount(rOrig.GetPointCount());
610 bool bClosed(rOrig[0] == rOrig[nPointCount-1]);
611
612 // move point itself
613 rMove[nPointIndex] = rOrig[nPointIndex] + aDelta;
614
615 // when point is first and poly closed, move close point, too.
616 if(nPointCount > 0 && !nPointIndex && bClosed)
617 {
618 rMove[nPointCount - 1] = rOrig[nPointCount - 1] + aDelta;
619
620 // when moving the last point it may be necessary to move the
621 // control point in front of this one, too.
622 if(nPointCount > 1 && rOrig.IsControl(nPointCount - 2))
623 rMove[nPointCount - 2] = rOrig[nPointCount - 2] + aDelta;
624 }
625
626 // is a control point before this?
627 if(nPointIndex > 0 && rOrig.IsControl(nPointIndex - 1))
628 {
629 // Yes, move it, too
630 rMove[nPointIndex - 1] = rOrig[nPointIndex - 1] + aDelta;
631 }
632
633 // is a control point after this?
634 if(nPointIndex + 1 < nPointCount && rOrig.IsControl(nPointIndex + 1))
635 {
636 // Yes, move it, too
637 rMove[nPointIndex + 1] = rOrig[nPointIndex + 1] + aDelta;
638 }
639 }
640 }
641 }
642 else
643 {
644 mpSdrPathDragData->ResetPoly(mrSdrPathObject);
645
646 // copy certain data locally to use less code and have faster access times
647 bool bClosed =mpSdrPathDragData->bClosed ; // closed object?
648 sal_uInt16 nPnt =mpSdrPathDragData->nPnt ; // number of point in the above polygon
649 bool bBegPnt =mpSdrPathDragData->bBegPnt ; // dragged point is first point of a Polyline
650 bool bEndPnt =mpSdrPathDragData->bEndPnt ; // dragged point is last point of a Polyline
651 sal_uInt16 nPrevPnt =mpSdrPathDragData->nPrevPnt ; // index of previous point
652 sal_uInt16 nNextPnt =mpSdrPathDragData->nNextPnt ; // index of next point
653 bool bPrevIsBegPnt =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline
654 bool bNextIsEndPnt =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline
655 sal_uInt16 nPrevPrevPnt =mpSdrPathDragData->nPrevPrevPnt ; // index of the point before the previous point
656 sal_uInt16 nNextNextPnt =mpSdrPathDragData->nNextNextPnt ; // index if the point after the next point
657 bool bControl =mpSdrPathDragData->bControl ; // point is a control point
658 bool bIsNextControl =mpSdrPathDragData->bIsNextControl; // point is a control point after a support point
659 bool bPrevIsControl =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before
660 bool bNextIsControl =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after
661
662 // Ortho for lines/polygons: keep angle
663 if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho()) {
664 bool bBigOrtho=rDrag.GetView()->IsBigOrtho();
665 Point aPos(rDrag.GetNow()); // current position
666 Point aPnt(mpSdrPathDragData->aXP[nPnt]); // the dragged point
667 sal_uInt16 nPnt1=0xFFFF,nPnt2=0xFFFF; // its neighboring points
668 Point aNewPos1,aNewPos2; // new alternative for aPos
669 bool bPnt1 = false, bPnt2 = false; // are these valid alternatives?
670 if (!bClosed && mpSdrPathDragData->nPointCount>=2) { // minimum of 2 points for lines
671 if (!bBegPnt) nPnt1=nPrevPnt;
672 if (!bEndPnt) nPnt2=nNextPnt;
673 }
674 if (bClosed && mpSdrPathDragData->nPointCount>=3) { // minimum of 3 points for polygon
675 nPnt1=nPrevPnt;
676 nPnt2=nNextPnt;
677 }
678 if (nPnt1!=0xFFFF && !bPrevIsControl) {
679 Point aPnt1=mpSdrPathDragData->aXP[nPnt1];
680 tools::Long ndx0=aPnt.X()-aPnt1.X();
681 tools::Long ndy0=aPnt.Y()-aPnt1.Y();
682 bool bHLin=ndy0==0;
683 bool bVLin=ndx0==0;
684 if (!bHLin || !bVLin) {
685 tools::Long ndx=aPos.X()-aPnt1.X();
686 tools::Long ndy=aPos.Y()-aPnt1.Y();
687 bPnt1=true;
688 double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0);
689 double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0);
690 bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho);
691 bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho);
692 if (bHor) ndy=tools::Long(ndy0*nXFact);
693 if (bVer) ndx=tools::Long(ndx0*nYFact);
694 aNewPos1=aPnt1;
695 aNewPos1.AdjustX(ndx );
696 aNewPos1.AdjustY(ndy );
697 }
698 }
699 if (nPnt2!=0xFFFF && !bNextIsControl) {
700 Point aPnt2=mpSdrPathDragData->aXP[nPnt2];
701 tools::Long ndx0=aPnt.X()-aPnt2.X();
702 tools::Long ndy0=aPnt.Y()-aPnt2.Y();
703 bool bHLin=ndy0==0;
704 bool bVLin=ndx0==0;
705 if (!bHLin || !bVLin) {
706 tools::Long ndx=aPos.X()-aPnt2.X();
707 tools::Long ndy=aPos.Y()-aPnt2.Y();
708 bPnt2=true;
709 double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0);
710 double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0);
711 bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho);
712 bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho);
713 if (bHor) ndy=tools::Long(ndy0*nXFact);
714 if (bVer) ndx=tools::Long(ndx0*nYFact);
715 aNewPos2=aPnt2;
716 aNewPos2.AdjustX(ndx );
717 aNewPos2.AdjustY(ndy );
718 }
719 }
720 if (bPnt1 && bPnt2) { // both alternatives exist (and compete)
721 BigInt nX1(aNewPos1.X()-aPos.X()); nX1*=nX1;
722 BigInt nY1(aNewPos1.Y()-aPos.Y()); nY1*=nY1;
723 BigInt nX2(aNewPos2.X()-aPos.X()); nX2*=nX2;
724 BigInt nY2(aNewPos2.Y()-aPos.Y()); nY2*=nY2;
725 nX1+=nY1; // correction distance to square
726 nX2+=nY2; // correction distance to square
727 // let the alternative that allows fewer correction win
728 if (nX1<nX2) bPnt2=false; else bPnt1=false;
729 }
730 if (bPnt1) rDrag.SetNow(aNewPos1);
731 if (bPnt2) rDrag.SetNow(aNewPos2);
732 }
733 rDrag.SetActionRect(tools::Rectangle(rDrag.GetNow(),rDrag.GetNow()));
734
735 // specially for IBM: Eliminate points if both adjoining lines form near 180 degrees angle anyway
736 if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsEliminatePolyPoints() &&
737 !bBegPnt && !bEndPnt && !bPrevIsControl && !bNextIsControl)
738 {
739 Point aPt(mpSdrPathDragData->aXP[nNextPnt]);
740 aPt-=rDrag.GetNow();
741 Degree100 nAngle1=GetAngle(aPt);
742 aPt=rDrag.GetNow();
743 aPt-=mpSdrPathDragData->aXP[nPrevPnt];
744 Degree100 nAngle2=GetAngle(aPt);
745 Degree100 nDiff=nAngle1-nAngle2;
746 nDiff=abs(nDiff);
747 mpSdrPathDragData->bEliminate=nDiff<=rDrag.GetView()->GetEliminatePolyPointLimitAngle();
748 if (mpSdrPathDragData->bEliminate) { // adapt position, Smooth is true for the ends
749 aPt=mpSdrPathDragData->aXP[nNextPnt];
750 aPt+=mpSdrPathDragData->aXP[nPrevPnt];
751 aPt/=2;
752 rDrag.SetNow(aPt);
753 }
754 }
755
756 // we dragged by this distance
757 Point aDiff(rDrag.GetNow()); aDiff-=mpSdrPathDragData->aXP[nPnt];
758
759 /* There are 8 possible cases:
760 X 1. A control point neither on the left nor on the right.
761 o--X--o 2. There are control points on the left and the right, we are dragging a support point.
762 o--X 3. There is a control point on the left, we are dragging a support point.
763 X--o 4. There is a control point on the right, we are dragging a support point.
764 x--O--o 5. There are control points on the left and the right, we are dragging the left one.
765 x--O 6. There is a control point on the left, we are dragging it.
766 o--O--x 7. There are control points on the left and the right, we are dragging the right one.
767 O--x 8. There is a control point on the right, we are dragging it.
768 Note: modifying a line (not a curve!) might create a curve on the other end of the line
769 if Smooth is set there (with control points aligned to line).
770 */
771
772 mpSdrPathDragData->aXP[nPnt]+=aDiff;
773
774 // now check symmetric plus handles
775 if (bControl) { // cases 5,6,7,8
776 sal_uInt16 nSt; // the associated support point
777 sal_uInt16 nFix; // the opposing control point
778 if (bIsNextControl) { // if the next one is a control point, the on before has to be a support point
779 nSt=nPrevPnt;
780 nFix=nPrevPrevPnt;
781 } else {
782 nSt=nNextPnt;
783 nFix=nNextNextPnt;
784 }
785 if (mpSdrPathDragData->aXP.IsSmooth(nSt)) {
786 mpSdrPathDragData->aXP.CalcSmoothJoin(nSt,nPnt,nFix);
787 }
788 }
789
790 if (!bControl) { // Cases 1,2,3,4. In case 1, nothing happens; in cases 3 and 4, there is more following below.
791 // move both control points
792 if (bPrevIsControl) mpSdrPathDragData->aXP[nPrevPnt]+=aDiff;
793 if (bNextIsControl) mpSdrPathDragData->aXP[nNextPnt]+=aDiff;
794 // align control point to line, if appropriate
795 if (mpSdrPathDragData->aXP.IsSmooth(nPnt)) {
796 if (bPrevIsControl && !bNextIsControl && !bEndPnt) { // case 3
797 mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nNextPnt,nPrevPnt);
798 }
799 if (bNextIsControl && !bPrevIsControl && !bBegPnt) { // case 4
800 mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nPrevPnt,nNextPnt);
801 }
802 }
803 // Now check the other ends of the line (nPnt+-1). If there is a
804 // curve (IsControl(nPnt+-2)) with SmoothJoin (nPnt+-1), the
805 // associated control point (nPnt+-2) has to be adapted.
806 if (!bBegPnt && !bPrevIsControl && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsSmooth(nPrevPnt)) {
807 if (mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
808 mpSdrPathDragData->aXP.CalcSmoothJoin(nPrevPnt,nPnt,nPrevPrevPnt);
809 }
810 }
811 if (!bEndPnt && !bNextIsControl && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsSmooth(nNextPnt)) {
812 if (mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
813 mpSdrPathDragData->aXP.CalcSmoothJoin(nNextPnt,nPnt,nNextNextPnt);
814 }
815 }
816 }
817 }
818
819 return true;
820 }
821
endPathDrag(SdrDragStat const & rDrag)822 bool ImpPathForDragAndCreate::endPathDrag(SdrDragStat const & rDrag)
823 {
824 Point aLinePt1;
825 Point aLinePt2;
826 bool bLineGlueMirror(OBJ_LINE == meObjectKind);
827 if (bLineGlueMirror) {
828 XPolygon& rXP=aPathPolygon[0];
829 aLinePt1=rXP[0];
830 aLinePt2=rXP[1];
831 }
832
833 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
834 {
835 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
836 return false;
837 }
838
839 if(mpSdrPathDragData->IsMultiPointDrag())
840 {
841 aPathPolygon = mpSdrPathDragData->maMove;
842 }
843 else
844 {
845 const SdrHdl* pHdl=rDrag.GetHdl();
846
847 // reference the polygon
848 XPolygon& rXP=aPathPolygon[static_cast<sal_uInt16>(pHdl->GetPolyNum())];
849
850 // the 5 points that might have changed
851 if (!mpSdrPathDragData->bPrevIsBegPnt) rXP[mpSdrPathDragData->nPrevPrevPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPrevPnt];
852 if (!mpSdrPathDragData->bNextIsEndPnt) rXP[mpSdrPathDragData->nNextNextPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nNextNextPnt];
853 if (!mpSdrPathDragData->bBegPnt) rXP[mpSdrPathDragData->nPrevPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPnt];
854 if (!mpSdrPathDragData->bEndPnt) rXP[mpSdrPathDragData->nNextPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nNextPnt];
855 rXP[mpSdrPathDragData->nPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nPnt];
856
857 // for closed objects: last point has to be equal to first point
858 if (mpSdrPathDragData->bClosed) rXP[rXP.GetPointCount()-1]=rXP[0];
859
860 if (mpSdrPathDragData->bEliminate)
861 {
862 basegfx::B2DPolyPolygon aTempPolyPolygon(aPathPolygon.getB2DPolyPolygon());
863 sal_uInt32 nPoly,nPnt;
864
865 if(PolyPolygonEditor::GetRelativePolyPoint(aTempPolyPolygon, rDrag.GetHdl()->GetSourceHdlNum(), nPoly, nPnt))
866 {
867 basegfx::B2DPolygon aCandidate(aTempPolyPolygon.getB2DPolygon(nPoly));
868 aCandidate.remove(nPnt);
869
870 if(aCandidate.count() < 2)
871 {
872 aTempPolyPolygon.remove(nPoly);
873 }
874 else
875 {
876 aTempPolyPolygon.setB2DPolygon(nPoly, aCandidate);
877 }
878 }
879
880 aPathPolygon = XPolyPolygon(aTempPolyPolygon);
881 }
882
883 // adapt angle for text beneath a simple line
884 if (bLineGlueMirror)
885 {
886 Point aLinePt1_(aPathPolygon[0][0]);
887 Point aLinePt2_(aPathPolygon[0][1]);
888 bool bXMirr=(aLinePt1_.X()>aLinePt2_.X())!=(aLinePt1.X()>aLinePt2.X());
889 bool bYMirr=(aLinePt1_.Y()>aLinePt2_.Y())!=(aLinePt1.Y()>aLinePt2.Y());
890 if (bXMirr || bYMirr) {
891 Point aRef1(mrSdrPathObject.GetSnapRect().Center());
892 if (bXMirr) {
893 Point aRef2(aRef1);
894 aRef2.AdjustY( 1 );
895 mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2);
896 }
897 if (bYMirr) {
898 Point aRef2(aRef1);
899 aRef2.AdjustX( 1 );
900 mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2);
901 }
902 }
903 }
904 }
905
906 mpSdrPathDragData.reset();
907
908 return true;
909 }
910
getSpecialDragComment(const SdrDragStat & rDrag) const911 OUString ImpPathForDragAndCreate::getSpecialDragComment(const SdrDragStat& rDrag) const
912 {
913 OUString aStr;
914 const SdrHdl* pHdl = rDrag.GetHdl();
915 const bool bCreateComment(rDrag.GetView() && &mrSdrPathObject == rDrag.GetView()->GetCreateObj());
916
917 if(bCreateComment && rDrag.GetUser())
918 {
919 // #i103058# re-add old creation comment mode
920 const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser());
921 const SdrObjKind eOriginalKind(meObjectKind);
922 mrSdrPathObject.meKind = pU->eCurrentKind;
923 aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewCreateObj);
924 mrSdrPathObject.meKind = eOriginalKind;
925
926 Point aPrev(rDrag.GetPrev());
927 Point aNow(rDrag.GetNow());
928
929 if(pU->bLine)
930 aNow = pU->aLineEnd;
931
932 aNow -= aPrev;
933 aStr += " (";
934
935 if(pU->bCircle)
936 {
937 aStr += SdrModel::GetAngleString(abs(pU->nCircRelAngle))
938 + " r="
939 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(pU->nCircRadius, true);
940 }
941
942 aStr += "dx="
943 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X(), true)
944 + " dy="
945 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y(), true);
946
947 if(!IsFreeHand(meObjectKind))
948 {
949 sal_Int32 nLen(GetLen(aNow));
950 Degree100 nAngle(GetAngle(aNow));
951 aStr += " l="
952 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
953 + " "
954 + SdrModel::GetAngleString(nAngle);
955 }
956
957 aStr += ")";
958 }
959 else if(!pHdl)
960 {
961 // #i103058# fallback when no model and/or Handle, both needed
962 // for else-path
963 aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_DragPathObj);
964 }
965 else
966 {
967 // #i103058# standard for modification; model and handle needed
968 ImpSdrPathDragData* pDragData = mpSdrPathDragData.get();
969
970 if(!pDragData)
971 {
972 // getSpecialDragComment is also used from create, so fallback to GetUser()
973 // when mpSdrPathDragData is not set
974 pDragData = static_cast<ImpSdrPathDragData*>(rDrag.GetUser());
975 }
976
977 if(!pDragData)
978 {
979 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
980 return OUString();
981 }
982
983 if(!pDragData->IsMultiPointDrag() && pDragData->bEliminate)
984 {
985 // point of ...
986 aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewMarkedPoint);
987
988 // delete %O
989 OUString aStr2(SvxResId(STR_EditDelete));
990
991 // UNICODE: delete point of ...
992 aStr2 = aStr2.replaceFirst("%1", aStr);
993
994 return aStr2;
995 }
996
997 // dx=0.00 dy=0.00 -- both sides bezier
998 // dx=0.00 dy=0.00 l=0.00 0.00\302\260 -- one bezier/lever on one side, a start, or an ending
999 // dx=0.00 dy=0.00 l=0.00 0.00\302\260 / l=0.00 0.00\302\260 -- in between
1000 Point aBeg(rDrag.GetStart());
1001 Point aNow(rDrag.GetNow());
1002
1003 aStr.clear();
1004 aStr += "dx="
1005 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X() - aBeg.X(), true)
1006 + " dy="
1007 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y() - aBeg.Y(), true);
1008
1009 if(!pDragData->IsMultiPointDrag())
1010 {
1011 sal_uInt16 nPntNum(static_cast<sal_uInt16>(pHdl->GetPointNum()));
1012 const XPolygon& rXPoly = aPathPolygon[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPolyNum())];
1013 sal_uInt16 nPointCount(rXPoly.GetPointCount());
1014 bool bClose(IsClosed(meObjectKind));
1015
1016 if(bClose)
1017 nPointCount--;
1018
1019 if(pHdl->IsPlusHdl())
1020 {
1021 // lever
1022 sal_uInt16 nRef(nPntNum);
1023
1024 if(rXPoly.IsControl(nPntNum + 1))
1025 nRef--;
1026 else
1027 nRef++;
1028
1029 aNow -= rXPoly[nRef];
1030
1031 sal_Int32 nLen(GetLen(aNow));
1032 Degree100 nAngle(GetAngle(aNow));
1033 aStr += " l="
1034 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
1035 + " "
1036 + SdrModel::GetAngleString(nAngle);
1037 }
1038 else if(nPointCount > 1)
1039 {
1040 sal_uInt16 nPntMax(nPointCount - 1);
1041 bool bIsClosed(IsClosed(meObjectKind));
1042 bool bPt1(nPntNum > 0);
1043 bool bPt2(nPntNum < nPntMax);
1044
1045 if(bIsClosed && nPointCount > 2)
1046 {
1047 bPt1 = true;
1048 bPt2 = true;
1049 }
1050
1051 sal_uInt16 nPt1,nPt2;
1052
1053 if(nPntNum > 0)
1054 nPt1 = nPntNum - 1;
1055 else
1056 nPt1 = nPntMax;
1057
1058 if(nPntNum < nPntMax)
1059 nPt2 = nPntNum + 1;
1060 else
1061 nPt2 = 0;
1062
1063 if(bPt1 && rXPoly.IsControl(nPt1))
1064 bPt1 = false; // don't display
1065
1066 if(bPt2 && rXPoly.IsControl(nPt2))
1067 bPt2 = false; // of bezier data
1068
1069 if(bPt1)
1070 {
1071 Point aPt(aNow);
1072 aPt -= rXPoly[nPt1];
1073
1074 sal_Int32 nLen(GetLen(aPt));
1075 Degree100 nAngle(GetAngle(aPt));
1076 aStr += " l="
1077 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
1078 + " "
1079 + SdrModel::GetAngleString(nAngle);
1080 }
1081
1082 if(bPt2)
1083 {
1084 if(bPt1)
1085 aStr += " / ";
1086 else
1087 aStr += " ";
1088
1089 Point aPt(aNow);
1090 aPt -= rXPoly[nPt2];
1091
1092 sal_Int32 nLen(GetLen(aPt));
1093 Degree100 nAngle(GetAngle(aPt));
1094 aStr += "l="
1095 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
1096 + " "
1097 + SdrModel::GetAngleString(nAngle);
1098 }
1099 }
1100 }
1101 }
1102
1103 return aStr;
1104 }
1105
getSpecialDragPoly(const SdrDragStat & rDrag) const1106 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::getSpecialDragPoly(const SdrDragStat& rDrag) const
1107 {
1108 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
1109 {
1110 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
1111 return basegfx::B2DPolyPolygon();
1112 }
1113
1114 XPolyPolygon aRetval;
1115
1116 if(mpSdrPathDragData->IsMultiPointDrag())
1117 {
1118 aRetval.Insert(mpSdrPathDragData->maMove);
1119 }
1120 else
1121 {
1122 const XPolygon& rXP=aPathPolygon[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPolyNum())];
1123 if (rXP.GetPointCount()<=2) {
1124 XPolygon aXPoly(rXP);
1125 aXPoly[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPointNum())]=rDrag.GetNow();
1126 aRetval.Insert(std::move(aXPoly));
1127 return aRetval.getB2DPolyPolygon();
1128 }
1129 // copy certain data locally to use less code and have faster access times
1130 bool bClosed =mpSdrPathDragData->bClosed ; // closed object?
1131 sal_uInt16 nPointCount = mpSdrPathDragData->nPointCount; // number of points
1132 sal_uInt16 nPnt =mpSdrPathDragData->nPnt ; // number of points in the polygon
1133 bool bBegPnt =mpSdrPathDragData->bBegPnt ; // dragged point is the first point of a Polyline
1134 bool bEndPnt =mpSdrPathDragData->bEndPnt ; // dragged point is the last point of a Polyline
1135 sal_uInt16 nPrevPnt =mpSdrPathDragData->nPrevPnt ; // index of the previous point
1136 sal_uInt16 nNextPnt =mpSdrPathDragData->nNextPnt ; // index of the next point
1137 bool bPrevIsBegPnt =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline
1138 bool bNextIsEndPnt =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline
1139 sal_uInt16 nPrevPrevPnt =mpSdrPathDragData->nPrevPrevPnt ; // index of the point before the previous point
1140 sal_uInt16 nNextNextPnt =mpSdrPathDragData->nNextNextPnt ; // index of the point after the last point
1141 bool bControl =mpSdrPathDragData->bControl ; // point is a control point
1142 bool bIsNextControl =mpSdrPathDragData->bIsNextControl; //point is a control point after a support point
1143 bool bPrevIsControl =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before
1144 bool bNextIsControl =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after
1145 XPolygon aXPoly(mpSdrPathDragData->aXP);
1146 XPolygon aLine1(2);
1147 XPolygon aLine2(2);
1148 XPolygon aLine3(2);
1149 XPolygon aLine4(2);
1150 if (bControl) {
1151 aLine1[1]=mpSdrPathDragData->aXP[nPnt];
1152 if (bIsNextControl) { // is this a control point after the support point?
1153 aLine1[0]=mpSdrPathDragData->aXP[nPrevPnt];
1154 aLine2[0]=mpSdrPathDragData->aXP[nNextNextPnt];
1155 aLine2[1]=mpSdrPathDragData->aXP[nNextPnt];
1156 if (mpSdrPathDragData->aXP.IsSmooth(nPrevPnt) && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
1157 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control);
1158 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal);
1159 // leverage lines for the opposing curve segment
1160 aLine3[0]=mpSdrPathDragData->aXP[nPrevPnt];
1161 aLine3[1]=mpSdrPathDragData->aXP[nPrevPrevPnt];
1162 aLine4[0]=rXP[mpSdrPathDragData->nPrevPrevPnt0-2];
1163 aLine4[1]=rXP[mpSdrPathDragData->nPrevPrevPnt0-1];
1164 } else {
1165 aXPoly.Remove(0,1);
1166 }
1167 } else { // else this is a control point before a support point
1168 aLine1[0]=mpSdrPathDragData->aXP[nNextPnt];
1169 aLine2[0]=mpSdrPathDragData->aXP[nPrevPrevPnt];
1170 aLine2[1]=mpSdrPathDragData->aXP[nPrevPnt];
1171 if (mpSdrPathDragData->aXP.IsSmooth(nNextPnt) && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
1172 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control);
1173 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal);
1174 // leverage lines for the opposing curve segment
1175 aLine3[0]=mpSdrPathDragData->aXP[nNextPnt];
1176 aLine3[1]=mpSdrPathDragData->aXP[nNextNextPnt];
1177 aLine4[0]=rXP[mpSdrPathDragData->nNextNextPnt0+2];
1178 aLine4[1]=rXP[mpSdrPathDragData->nNextNextPnt0+1];
1179 } else {
1180 aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1181 }
1182 }
1183 } else { // else is not a control point
1184 if (mpSdrPathDragData->bEliminate) {
1185 aXPoly.Remove(2,1);
1186 }
1187 if (bPrevIsControl) aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Normal);
1188 else if (!bBegPnt && !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 } else {
1192 aXPoly.Remove(0,1);
1193 if (bBegPnt) aXPoly.Remove(0,1);
1194 }
1195 if (bNextIsControl) aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Normal);
1196 else if (!bEndPnt && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
1197 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control);
1198 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal);
1199 } else {
1200 aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1201 if (bEndPnt) aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1202 }
1203 if (bClosed) { // "pear problem": 2 lines, 1 curve, everything smoothed, a point between both lines is dragged
1204 if (aXPoly.GetPointCount()>nPointCount && aXPoly.IsControl(1)) {
1205 sal_uInt16 a=aXPoly.GetPointCount();
1206 aXPoly[a-2]=aXPoly[2]; aXPoly.SetFlags(a-2,aXPoly.GetFlags(2));
1207 aXPoly[a-1]=aXPoly[3]; aXPoly.SetFlags(a-1,aXPoly.GetFlags(3));
1208 aXPoly.Remove(0,3);
1209 }
1210 }
1211 }
1212 aRetval.Insert(std::move(aXPoly));
1213 if (aLine1.GetPointCount()>1) aRetval.Insert(std::move(aLine1));
1214 if (aLine2.GetPointCount()>1) aRetval.Insert(std::move(aLine2));
1215 if (aLine3.GetPointCount()>1) aRetval.Insert(std::move(aLine3));
1216 if (aLine4.GetPointCount()>1) aRetval.Insert(std::move(aLine4));
1217 }
1218
1219 return aRetval.getB2DPolyPolygon();
1220 }
1221
BegCreate(SdrDragStat & rStat)1222 void ImpPathForDragAndCreate::BegCreate(SdrDragStat& rStat)
1223 {
1224 bool bFreeHand(IsFreeHand(meObjectKind));
1225 rStat.SetNoSnap(bFreeHand);
1226 rStat.SetOrtho8Possible();
1227 aPathPolygon.Clear();
1228 mbCreating=true;
1229 bool bMakeStartPoint = true;
1230 SdrView* pView=rStat.GetView();
1231 if (pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface() &&
1232 (meObjectKind==OBJ_POLY || meObjectKind==OBJ_PLIN || meObjectKind==OBJ_PATHLINE || meObjectKind==OBJ_PATHFILL)) {
1233 bMakeStartPoint = false;
1234 }
1235 aPathPolygon.Insert(XPolygon());
1236 aPathPolygon[0][0]=rStat.GetStart();
1237 if (bMakeStartPoint) {
1238 aPathPolygon[0][1]=rStat.GetNow();
1239 }
1240 std::unique_ptr<ImpPathCreateUser> pU(new ImpPathCreateUser);
1241 pU->eStartKind=meObjectKind;
1242 pU->eCurrentKind=meObjectKind;
1243 rStat.SetUser(std::move(pU));
1244 }
1245
MovCreate(SdrDragStat & rStat)1246 bool ImpPathForDragAndCreate::MovCreate(SdrDragStat& rStat)
1247 {
1248 ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1249 SdrView* pView=rStat.GetView();
1250 XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1251 if (pView!=nullptr && pView->IsCreateMode()) {
1252 // switch to different CreateTool, if appropriate
1253 SdrObjKind nIdent;
1254 SdrInventor nInvent;
1255 pView->TakeCurrentObj(nIdent,nInvent);
1256 if (nInvent==SdrInventor::Default && pU->eCurrentKind != nIdent) {
1257 SdrObjKind eNewKind = nIdent;
1258 switch (eNewKind) {
1259 case OBJ_CARC:
1260 case OBJ_CIRC:
1261 case OBJ_CCUT:
1262 case OBJ_SECT:
1263 eNewKind=OBJ_CARC;
1264 [[fallthrough]];
1265 case OBJ_RECT:
1266 case OBJ_LINE:
1267 case OBJ_PLIN:
1268 case OBJ_POLY:
1269 case OBJ_PATHLINE:
1270 case OBJ_PATHFILL:
1271 case OBJ_FREELINE:
1272 case OBJ_FREEFILL:
1273 case OBJ_SPLNLINE:
1274 case OBJ_SPLNFILL: {
1275 pU->eCurrentKind=eNewKind;
1276 pU->bMixedCreate=true;
1277 pU->nBezierStartPoint=rXPoly.GetPointCount();
1278 if (pU->nBezierStartPoint>0) pU->nBezierStartPoint--;
1279 } break;
1280 default: break;
1281 } // switch
1282 }
1283 }
1284 sal_uInt16 nCurrentPoint=rXPoly.GetPointCount();
1285 if (aPathPolygon.Count()>1 && rStat.IsMouseDown() && nCurrentPoint<2) {
1286 rXPoly[0]=rStat.GetPos0();
1287 rXPoly[1]=rStat.GetNow();
1288 nCurrentPoint=2;
1289 }
1290 if (nCurrentPoint==0) {
1291 rXPoly[0]=rStat.GetPos0();
1292 } else nCurrentPoint--;
1293 bool bFreeHand=IsFreeHand(pU->eCurrentKind);
1294 rStat.SetNoSnap(bFreeHand);
1295 rStat.SetOrtho8Possible(pU->eCurrentKind!=OBJ_CARC && pU->eCurrentKind!=OBJ_RECT && (!pU->bMixedCreate || pU->eCurrentKind!=OBJ_LINE));
1296 rXPoly[nCurrentPoint]=rStat.GetNow();
1297 if (!pU->bMixedCreate && pU->eStartKind==OBJ_LINE && rXPoly.GetPointCount()>=1) {
1298 Point aPt(rStat.GetStart());
1299 if (pView!=nullptr && pView->IsCreate1stPointAsCenter()) {
1300 aPt+=aPt;
1301 aPt-=rStat.GetNow();
1302 }
1303 rXPoly[0]=aPt;
1304 }
1305 OutputDevice* pOut=pView==nullptr ? nullptr : pView->GetFirstOutputDevice();
1306 if (bFreeHand) {
1307 if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint;
1308 if (rStat.IsMouseDown() && nCurrentPoint>0) {
1309 // don't allow two consecutive points to occupy too similar positions
1310 tools::Long nMinDist=1;
1311 if (pView!=nullptr) nMinDist=pView->GetFreeHandMinDistPix();
1312 if (pOut!=nullptr) nMinDist=pOut->PixelToLogic(Size(nMinDist,0)).Width();
1313 if (nMinDist<1) nMinDist=1;
1314
1315 Point aPt0(rXPoly[nCurrentPoint-1]);
1316 Point aPt1(rStat.GetNow());
1317 tools::Long dx=aPt0.X()-aPt1.X(); if (dx<0) dx=-dx;
1318 tools::Long dy=aPt0.Y()-aPt1.Y(); if (dy<0) dy=-dy;
1319 if (dx<nMinDist && dy<nMinDist) return false;
1320
1321 // TODO: the following is copied from EndCreate (with a few smaller modifications)
1322 // and should be combined into a method with the code there.
1323
1324 if (nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) {
1325 rXPoly.PointsToBezier(nCurrentPoint-3);
1326 rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control);
1327 rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control);
1328
1329 if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) {
1330 rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2);
1331 rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth);
1332 }
1333 }
1334 rXPoly[nCurrentPoint+1]=rStat.GetNow();
1335 rStat.NextPoint();
1336 } else {
1337 pU->nBezierStartPoint=nCurrentPoint;
1338 }
1339 }
1340
1341 pU->ResetFormFlags();
1342 if (IsBezier(pU->eCurrentKind)) {
1343 if (nCurrentPoint>=2) {
1344 pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],rStat.IsMouseDown());
1345 } else if (pU->bBezHasCtrl0) {
1346 pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],pU->aBezControl0-rXPoly[nCurrentPoint-1],rStat.IsMouseDown());
1347 }
1348 }
1349 if (pU->eCurrentKind==OBJ_CARC && nCurrentPoint>=2) {
1350 pU->CalcCircle(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
1351 }
1352 if (pU->eCurrentKind==OBJ_LINE && nCurrentPoint>=2) {
1353 pU->CalcLine(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
1354 }
1355 if (pU->eCurrentKind==OBJ_RECT && nCurrentPoint>=2) {
1356 pU->CalcRect(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
1357 }
1358
1359 return true;
1360 }
1361
EndCreate(SdrDragStat & rStat,SdrCreateCmd eCmd)1362 bool ImpPathForDragAndCreate::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
1363 {
1364 ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1365 bool bRet = false;
1366 SdrView* pView=rStat.GetView();
1367 bool bIncomp=pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface();
1368 XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1369 sal_uInt16 nCurrentPoint=rXPoly.GetPointCount()-1;
1370 rXPoly[nCurrentPoint]=rStat.GetNow();
1371 if (!pU->bMixedCreate && pU->eStartKind==OBJ_LINE) {
1372 if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd;
1373 bRet = eCmd==SdrCreateCmd::ForceEnd;
1374 if (bRet) {
1375 mbCreating = false;
1376 rStat.SetUser(nullptr);
1377 }
1378 return bRet;
1379 }
1380
1381 if (!pU->bMixedCreate && IsFreeHand(pU->eStartKind)) {
1382 if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd;
1383 bRet=eCmd==SdrCreateCmd::ForceEnd;
1384 if (bRet) {
1385 mbCreating=false;
1386 rStat.SetUser(nullptr);
1387 }
1388 return bRet;
1389 }
1390 if (eCmd==SdrCreateCmd::NextPoint || eCmd==SdrCreateCmd::NextObject) {
1391 // don't allow two consecutive points to occupy the same position
1392 if (nCurrentPoint==0 || rStat.GetNow()!=rXPoly[nCurrentPoint-1]) {
1393 if (bIncomp) {
1394 if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint;
1395 if (IsBezier(pU->eCurrentKind) && nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) {
1396 rXPoly.PointsToBezier(nCurrentPoint-3);
1397 rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control);
1398 rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control);
1399
1400 if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) {
1401 rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2);
1402 rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth);
1403 }
1404 }
1405 } else {
1406 if (nCurrentPoint==1 && IsBezier(pU->eCurrentKind) && !pU->bBezHasCtrl0) {
1407 pU->aBezControl0=rStat.GetNow();
1408 pU->bBezHasCtrl0=true;
1409 nCurrentPoint--;
1410 }
1411 if (pU->IsFormFlag()) {
1412 sal_uInt16 nPointCount0=rXPoly.GetPointCount();
1413 rXPoly.Remove(nCurrentPoint-1,2); // remove last two points and replace by form
1414 rXPoly.Insert(XPOLY_APPEND,pU->GetFormPoly());
1415 sal_uInt16 nPointCount1=rXPoly.GetPointCount();
1416 for (sal_uInt16 i=nPointCount0+1; i<nPointCount1-1; i++) { // to make BckAction work
1417 if (!rXPoly.IsControl(i)) rStat.NextPoint();
1418 }
1419 nCurrentPoint=rXPoly.GetPointCount()-1;
1420 }
1421 }
1422 nCurrentPoint++;
1423 rXPoly[nCurrentPoint]=rStat.GetNow();
1424 }
1425 if (eCmd==SdrCreateCmd::NextObject) {
1426 if (rXPoly.GetPointCount()>=2) {
1427 pU->bBezHasCtrl0=false;
1428 // only a singular polygon may be opened, so close this
1429 rXPoly[nCurrentPoint]=rXPoly[0];
1430 XPolygon aXP;
1431 aXP[0]=rStat.GetNow();
1432 aPathPolygon.Insert(std::move(aXP));
1433 }
1434 }
1435 }
1436
1437 sal_uInt16 nPolyCount=aPathPolygon.Count();
1438 if (nPolyCount!=0) {
1439 // delete last point, if necessary
1440 if (eCmd==SdrCreateCmd::ForceEnd) {
1441 XPolygon& rXP=aPathPolygon[nPolyCount-1];
1442 sal_uInt16 nPointCount=rXP.GetPointCount();
1443 if (nPointCount>=2) {
1444 if (!rXP.IsControl(nPointCount-2)) {
1445 if (rXP[nPointCount-1]==rXP[nPointCount-2]) {
1446 rXP.Remove(nPointCount-1,1);
1447 }
1448 } else {
1449 if (rXP[nPointCount-3]==rXP[nPointCount-2]) {
1450 rXP.Remove(nPointCount-3,3);
1451 }
1452 }
1453 }
1454 }
1455 for (sal_uInt16 nPolyNum=nPolyCount; nPolyNum>0;) {
1456 nPolyNum--;
1457 XPolygon& rXP=aPathPolygon[nPolyNum];
1458 sal_uInt16 nPointCount=rXP.GetPointCount();
1459 // delete polygons with too few points
1460 if (nPolyNum<nPolyCount-1 || eCmd==SdrCreateCmd::ForceEnd) {
1461 if (nPointCount<2) aPathPolygon.Remove(nPolyNum);
1462 }
1463 }
1464 }
1465 pU->ResetFormFlags();
1466 bRet=eCmd==SdrCreateCmd::ForceEnd;
1467 if (bRet) {
1468 mbCreating=false;
1469 rStat.SetUser(nullptr);
1470 }
1471 return bRet;
1472 }
1473
BckCreate(SdrDragStat const & rStat)1474 bool ImpPathForDragAndCreate::BckCreate(SdrDragStat const & rStat)
1475 {
1476 ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1477 if (aPathPolygon.Count()>0) {
1478 XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1479 sal_uInt16 nCurrentPoint=rXPoly.GetPointCount();
1480 if (nCurrentPoint>0) {
1481 nCurrentPoint--;
1482 // make the last part of a bezier curve a line
1483 rXPoly.Remove(nCurrentPoint,1);
1484 if (nCurrentPoint>=3 && rXPoly.IsControl(nCurrentPoint-1)) {
1485 // there should never be a bezier segment at the end, so this is just in case...
1486 rXPoly.Remove(nCurrentPoint-1,1);
1487 if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1);
1488 }
1489 }
1490 nCurrentPoint=rXPoly.GetPointCount();
1491 if (nCurrentPoint>=4) { // no bezier segment at the end
1492 nCurrentPoint--;
1493 if (rXPoly.IsControl(nCurrentPoint-1)) {
1494 rXPoly.Remove(nCurrentPoint-1,1);
1495 if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1);
1496 }
1497 }
1498 if (rXPoly.GetPointCount()<2) {
1499 aPathPolygon.Remove(aPathPolygon.Count()-1);
1500 }
1501 if (aPathPolygon.Count()>0) {
1502 XPolygon& rLocalXPoly=aPathPolygon[aPathPolygon.Count()-1];
1503 sal_uInt16 nLocalCurrentPoint=rLocalXPoly.GetPointCount();
1504 if (nLocalCurrentPoint>0) {
1505 nLocalCurrentPoint--;
1506 rLocalXPoly[nLocalCurrentPoint]=rStat.GetNow();
1507 }
1508 }
1509 }
1510 pU->ResetFormFlags();
1511 return aPathPolygon.Count()!=0;
1512 }
1513
BrkCreate(SdrDragStat & rStat)1514 void ImpPathForDragAndCreate::BrkCreate(SdrDragStat& rStat)
1515 {
1516 aPathPolygon.Clear();
1517 mbCreating=false;
1518 rStat.SetUser(nullptr);
1519 }
1520
TakeObjectPolyPolygon(const SdrDragStat & rDrag) const1521 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeObjectPolyPolygon(const SdrDragStat& rDrag) const
1522 {
1523 basegfx::B2DPolyPolygon aRetval(aPathPolygon.getB2DPolyPolygon());
1524 SdrView* pView = rDrag.GetView();
1525
1526 if(pView && pView->IsUseIncompatiblePathCreateInterface())
1527 return aRetval;
1528
1529 ImpPathCreateUser* pU = static_cast<ImpPathCreateUser*>(rDrag.GetUser());
1530 basegfx::B2DPolygon aNewPolygon(aRetval.count() ? aRetval.getB2DPolygon(aRetval.count() - 1) : basegfx::B2DPolygon());
1531
1532 if(pU->IsFormFlag() && aNewPolygon.count() > 1)
1533 {
1534 // remove last segment and replace with current
1535 // do not forget to rescue the previous control point which will be lost when
1536 // the point it's associated with is removed
1537 const sal_uInt32 nChangeIndex(aNewPolygon.count() - 2);
1538 const basegfx::B2DPoint aSavedPrevCtrlPoint(aNewPolygon.getPrevControlPoint(nChangeIndex));
1539
1540 aNewPolygon.remove(nChangeIndex, 2);
1541 aNewPolygon.append(pU->GetFormPoly().getB2DPolygon());
1542
1543 if(nChangeIndex < aNewPolygon.count())
1544 {
1545 // if really something was added, set the saved previous control point to the
1546 // point where it belongs
1547 aNewPolygon.setPrevControlPoint(nChangeIndex, aSavedPrevCtrlPoint);
1548 }
1549 }
1550
1551 if(aRetval.count())
1552 {
1553 aRetval.setB2DPolygon(aRetval.count() - 1, aNewPolygon);
1554 }
1555 else
1556 {
1557 aRetval.append(aNewPolygon);
1558 }
1559
1560 return aRetval;
1561 }
1562
TakeDragPolyPolygon(const SdrDragStat & rDrag)1563 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeDragPolyPolygon(const SdrDragStat& rDrag)
1564 {
1565 basegfx::B2DPolyPolygon aRetval;
1566 SdrView* pView = rDrag.GetView();
1567
1568 if(pView && pView->IsUseIncompatiblePathCreateInterface())
1569 return aRetval;
1570
1571 const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser());
1572
1573 if(pU && pU->bBezier && rDrag.IsMouseDown())
1574 {
1575 // no more XOR, no need for complicated helplines
1576 basegfx::B2DPolygon aHelpline;
1577 aHelpline.append(basegfx::B2DPoint(pU->aBezCtrl2.X(), pU->aBezCtrl2.Y()));
1578 aHelpline.append(basegfx::B2DPoint(pU->aBezEnd.X(), pU->aBezEnd.Y()));
1579 aRetval.append(aHelpline);
1580 }
1581
1582 return aRetval;
1583 }
1584
GetCreatePointer() const1585 PointerStyle ImpPathForDragAndCreate::GetCreatePointer() const
1586 {
1587 switch (meObjectKind) {
1588 case OBJ_LINE : return PointerStyle::DrawLine;
1589 case OBJ_POLY : return PointerStyle::DrawPolygon;
1590 case OBJ_PLIN : return PointerStyle::DrawPolygon;
1591 case OBJ_PATHLINE: return PointerStyle::DrawBezier;
1592 case OBJ_PATHFILL: return PointerStyle::DrawBezier;
1593 case OBJ_FREELINE: return PointerStyle::DrawFreehand;
1594 case OBJ_FREEFILL: return PointerStyle::DrawFreehand;
1595 case OBJ_SPLNLINE: return PointerStyle::DrawFreehand;
1596 case OBJ_SPLNFILL: return PointerStyle::DrawFreehand;
1597 case OBJ_PATHPOLY: return PointerStyle::DrawPolygon;
1598 case OBJ_PATHPLIN: return PointerStyle::DrawPolygon;
1599 default: break;
1600 } // switch
1601 return PointerStyle::Cross;
1602 }
1603
SdrPathObjGeoData()1604 SdrPathObjGeoData::SdrPathObjGeoData()
1605 : meKind(OBJ_NONE)
1606 {
1607 }
1608
~SdrPathObjGeoData()1609 SdrPathObjGeoData::~SdrPathObjGeoData()
1610 {
1611 }
1612
1613 // DrawContact section
1614
CreateObjectSpecificViewContact()1615 std::unique_ptr<sdr::contact::ViewContact> SdrPathObj::CreateObjectSpecificViewContact()
1616 {
1617 return std::make_unique<sdr::contact::ViewContactOfSdrPathObj>(*this);
1618 }
1619
1620
SdrPathObj(SdrModel & rSdrModel,SdrObjKind eNewKind)1621 SdrPathObj::SdrPathObj(
1622 SdrModel& rSdrModel,
1623 SdrObjKind eNewKind)
1624 : SdrTextObj(rSdrModel),
1625 meKind(eNewKind)
1626 {
1627 m_bClosedObj = IsClosed();
1628 }
1629
SdrPathObj(SdrModel & rSdrModel,SdrPathObj const & rSource)1630 SdrPathObj::SdrPathObj(SdrModel& rSdrModel, SdrPathObj const & rSource)
1631 : SdrTextObj(rSdrModel, rSource),
1632 meKind(rSource.meKind)
1633 {
1634 m_bClosedObj = IsClosed();
1635 maPathPolygon = rSource.GetPathPoly();
1636 }
1637
SdrPathObj(SdrModel & rSdrModel,SdrObjKind eNewKind,const basegfx::B2DPolyPolygon & rPathPoly)1638 SdrPathObj::SdrPathObj(
1639 SdrModel& rSdrModel,
1640 SdrObjKind eNewKind,
1641 const basegfx::B2DPolyPolygon& rPathPoly)
1642 : SdrTextObj(rSdrModel),
1643 maPathPolygon(rPathPoly),
1644 meKind(eNewKind)
1645 {
1646 m_bClosedObj = IsClosed();
1647 ImpForceKind();
1648 }
1649
1650 SdrPathObj::~SdrPathObj() = default;
1651
lcl_ImpIsLine(const basegfx::B2DPolyPolygon & rPolyPolygon)1652 static bool lcl_ImpIsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
1653 {
1654 return (1 == rPolyPolygon.count() && 2 == rPolyPolygon.getB2DPolygon(0).count());
1655 }
1656
lcl_ImpGetBoundRect(const basegfx::B2DPolyPolygon & rPolyPolygon)1657 static tools::Rectangle lcl_ImpGetBoundRect(const basegfx::B2DPolyPolygon& rPolyPolygon)
1658 {
1659 basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
1660
1661 if (aRange.isEmpty())
1662 return tools::Rectangle();
1663
1664 return tools::Rectangle(
1665 FRound(aRange.getMinX()), FRound(aRange.getMinY()),
1666 FRound(aRange.getMaxX()), FRound(aRange.getMaxY()));
1667 }
1668
ImpForceLineAngle()1669 void SdrPathObj::ImpForceLineAngle()
1670 {
1671 if(OBJ_LINE != meKind || !lcl_ImpIsLine(GetPathPoly()))
1672 return;
1673
1674 const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0));
1675 const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
1676 const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
1677 const Point aPoint0(FRound(aB2DPoint0.getX()), FRound(aB2DPoint0.getY()));
1678 const Point aPoint1(FRound(aB2DPoint1.getX()), FRound(aB2DPoint1.getY()));
1679 const basegfx::B2DPoint aB2DDelt(aB2DPoint1 - aB2DPoint0);
1680 const Point aDelt(FRound(aB2DDelt.getX()), FRound(aB2DDelt.getY()));
1681
1682 aGeo.nRotationAngle=GetAngle(aDelt);
1683 aGeo.nShearAngle=0_deg100;
1684 aGeo.RecalcSinCos();
1685 aGeo.RecalcTan();
1686
1687 // for SdrTextObj, keep aRect up to date
1688 maRect = tools::Rectangle::Justify(aPoint0, aPoint1);
1689 }
1690
ImpForceKind()1691 void SdrPathObj::ImpForceKind()
1692 {
1693 if (meKind==OBJ_PATHPLIN) meKind=OBJ_PLIN;
1694 if (meKind==OBJ_PATHPOLY) meKind=OBJ_POLY;
1695
1696 if(GetPathPoly().areControlPointsUsed())
1697 {
1698 switch (meKind)
1699 {
1700 case OBJ_LINE: meKind=OBJ_PATHLINE; break;
1701 case OBJ_PLIN: meKind=OBJ_PATHLINE; break;
1702 case OBJ_POLY: meKind=OBJ_PATHFILL; break;
1703 default: break;
1704 }
1705 }
1706 else
1707 {
1708 switch (meKind)
1709 {
1710 case OBJ_PATHLINE: meKind=OBJ_PLIN; break;
1711 case OBJ_FREELINE: meKind=OBJ_PLIN; break;
1712 case OBJ_PATHFILL: meKind=OBJ_POLY; break;
1713 case OBJ_FREEFILL: meKind=OBJ_POLY; break;
1714 default: break;
1715 }
1716 }
1717
1718 if (meKind==OBJ_LINE && !lcl_ImpIsLine(GetPathPoly())) meKind=OBJ_PLIN;
1719 if (meKind==OBJ_PLIN && lcl_ImpIsLine(GetPathPoly())) meKind=OBJ_LINE;
1720
1721 m_bClosedObj=IsClosed();
1722
1723 if (meKind==OBJ_LINE)
1724 {
1725 ImpForceLineAngle();
1726 }
1727 else
1728 {
1729 // #i10659#, for polys with more than 2 points.
1730
1731 // Here i again need to fix something, because when Path-Polys are Copy-Pasted
1732 // between Apps with different measurements (e.g. 100TH_MM and TWIPS) there is
1733 // a scaling loop started from SdrExchangeView::Paste. In itself, this is not
1734 // wrong, but aRect is wrong here and not even updated by RecalcSnapRect(). If
1735 // this is the case, some size needs to be set here in aRect to avoid that the cycle
1736 // through Rect2Poly - Poly2Rect does something badly wrong since that cycle is
1737 // BASED on aRect. That cycle is triggered in SdrTextObj::NbcResize() which is called
1738 // from the local Resize() implementation.
1739
1740 // Basic problem is that the member aRect in SdrTextObj basically is a unrotated
1741 // text rectangle for the text object itself and methods at SdrTextObj do handle it
1742 // in that way. Many draw objects derived from SdrTextObj 'abuse' aRect as SnapRect
1743 // which is basically wrong. To make the SdrText methods which deal with aRect directly
1744 // work it is necessary to always keep aRect updated. This e.g. not done after a Clone()
1745 // command for SdrPathObj. Since adding this update mechanism with #101412# to
1746 // ImpForceLineAngle() for lines was very successful, i add it to where ImpForceLineAngle()
1747 // was called, once here below and once on a 2nd place below.
1748
1749 // #i10659# for SdrTextObj, keep aRect up to date
1750 if(GetPathPoly().count())
1751 {
1752 maRect = lcl_ImpGetBoundRect(GetPathPoly());
1753 }
1754 }
1755
1756 // #i75974# adapt polygon state to object type. This may include a reinterpretation
1757 // of a closed geometry as open one, but with identical first and last point
1758 for(auto& rPolygon : maPathPolygon)
1759 {
1760 if(IsClosed() != rPolygon.isClosed())
1761 {
1762 // #i80213# really change polygon geometry; else e.g. the last point which
1763 // needs to be identical with the first one will be missing when opening
1764 // due to OBJ_PATH type
1765 if(rPolygon.isClosed())
1766 {
1767 basegfx::utils::openWithGeometryChange(rPolygon);
1768 }
1769 else
1770 {
1771 basegfx::utils::closeWithGeometryChange(rPolygon);
1772 }
1773 }
1774 }
1775 }
1776
ImpSetClosed(bool bClose)1777 void SdrPathObj::ImpSetClosed(bool bClose)
1778 {
1779 if(bClose)
1780 {
1781 switch (meKind)
1782 {
1783 case OBJ_LINE : meKind=OBJ_POLY; break;
1784 case OBJ_PLIN : meKind=OBJ_POLY; break;
1785 case OBJ_PATHLINE: meKind=OBJ_PATHFILL; break;
1786 case OBJ_FREELINE: meKind=OBJ_FREEFILL; break;
1787 case OBJ_SPLNLINE: meKind=OBJ_SPLNFILL; break;
1788 default: break;
1789 }
1790
1791 m_bClosedObj = true;
1792 }
1793 else
1794 {
1795 switch (meKind)
1796 {
1797 case OBJ_POLY : meKind=OBJ_PLIN; break;
1798 case OBJ_PATHFILL: meKind=OBJ_PATHLINE; break;
1799 case OBJ_FREEFILL: meKind=OBJ_FREELINE; break;
1800 case OBJ_SPLNFILL: meKind=OBJ_SPLNLINE; break;
1801 default: break;
1802 }
1803
1804 m_bClosedObj = false;
1805 }
1806
1807 ImpForceKind();
1808 }
1809
TakeObjInfo(SdrObjTransformInfoRec & rInfo) const1810 void SdrPathObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
1811 {
1812 rInfo.bNoContortion=false;
1813
1814 bool bCanConv = !HasText() || ImpCanConvTextToCurve();
1815 bool bIsPath = IsBezier() || IsSpline();
1816
1817 rInfo.bEdgeRadiusAllowed = false;
1818 rInfo.bCanConvToPath = bCanConv && !bIsPath;
1819 rInfo.bCanConvToPoly = bCanConv && bIsPath;
1820 rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary());
1821 }
1822
GetObjIdentifier() const1823 SdrObjKind SdrPathObj::GetObjIdentifier() const
1824 {
1825 return meKind;
1826 }
1827
CloneSdrObject(SdrModel & rTargetModel) const1828 SdrPathObj* SdrPathObj::CloneSdrObject(SdrModel& rTargetModel) const
1829 {
1830 return new SdrPathObj(rTargetModel, *this);
1831 }
1832
TakeObjNameSingul() const1833 OUString SdrPathObj::TakeObjNameSingul() const
1834 {
1835 OUString sName;
1836
1837 if(OBJ_LINE == meKind)
1838 {
1839 const char* pId(STR_ObjNameSingulLINE);
1840
1841 if(lcl_ImpIsLine(GetPathPoly()))
1842 {
1843 const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0));
1844 const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
1845 const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
1846
1847 if(aB2DPoint0 != aB2DPoint1)
1848 {
1849 if(aB2DPoint0.getY() == aB2DPoint1.getY())
1850 {
1851 pId = STR_ObjNameSingulLINE_Hori;
1852 }
1853 else if(aB2DPoint0.getX() == aB2DPoint1.getX())
1854 {
1855 pId = STR_ObjNameSingulLINE_Vert;
1856 }
1857 else
1858 {
1859 const double fDx(fabs(aB2DPoint0.getX() - aB2DPoint1.getX()));
1860 const double fDy(fabs(aB2DPoint0.getY() - aB2DPoint1.getY()));
1861
1862 if(fDx == fDy)
1863 {
1864 pId = STR_ObjNameSingulLINE_Diag;
1865 }
1866 }
1867 }
1868 }
1869
1870 sName = SvxResId(pId);
1871 }
1872 else if(OBJ_PLIN == meKind || OBJ_POLY == meKind)
1873 {
1874 const bool bClosed(OBJ_POLY == meKind);
1875 const char* pId(nullptr);
1876
1877 if(mpDAC && mpDAC->IsCreating())
1878 {
1879 if(bClosed)
1880 {
1881 pId = STR_ObjNameSingulPOLY;
1882 }
1883 else
1884 {
1885 pId = STR_ObjNameSingulPLIN;
1886 }
1887
1888 sName = SvxResId(pId);
1889 }
1890 else
1891 {
1892 // get point count
1893 sal_uInt32 nPointCount(0);
1894
1895 for(auto const& rPolygon : GetPathPoly())
1896 {
1897 nPointCount += rPolygon.count();
1898 }
1899
1900 if(bClosed)
1901 {
1902 pId = STR_ObjNameSingulPOLY_PointCount;
1903 }
1904 else
1905 {
1906 pId = STR_ObjNameSingulPLIN_PointCount;
1907 }
1908
1909 // #i96537#
1910 sName = SvxResId(pId).replaceFirst("%2", OUString::number(nPointCount));
1911 }
1912 }
1913 else
1914 {
1915 switch (meKind)
1916 {
1917 case OBJ_PATHLINE: sName = SvxResId(STR_ObjNameSingulPATHLINE); break;
1918 case OBJ_FREELINE: sName = SvxResId(STR_ObjNameSingulFREELINE); break;
1919 case OBJ_SPLNLINE: sName = SvxResId(STR_ObjNameSingulNATSPLN); break;
1920 case OBJ_PATHFILL: sName = SvxResId(STR_ObjNameSingulPATHFILL); break;
1921 case OBJ_FREEFILL: sName = SvxResId(STR_ObjNameSingulFREEFILL); break;
1922 case OBJ_SPLNFILL: sName = SvxResId(STR_ObjNameSingulPERSPLN); break;
1923 default: break;
1924 }
1925 }
1926
1927 OUString aName(GetName());
1928 if (!aName.isEmpty())
1929 sName += " '" + aName + "'";
1930
1931 return sName;
1932 }
1933
TakeObjNamePlural() const1934 OUString SdrPathObj::TakeObjNamePlural() const
1935 {
1936 OUString sName;
1937 switch(meKind)
1938 {
1939 case OBJ_LINE : sName=SvxResId(STR_ObjNamePluralLINE ); break;
1940 case OBJ_PLIN : sName=SvxResId(STR_ObjNamePluralPLIN ); break;
1941 case OBJ_POLY : sName=SvxResId(STR_ObjNamePluralPOLY ); break;
1942 case OBJ_PATHLINE: sName=SvxResId(STR_ObjNamePluralPATHLINE); break;
1943 case OBJ_FREELINE: sName=SvxResId(STR_ObjNamePluralFREELINE); break;
1944 case OBJ_SPLNLINE: sName=SvxResId(STR_ObjNamePluralNATSPLN); break;
1945 case OBJ_PATHFILL: sName=SvxResId(STR_ObjNamePluralPATHFILL); break;
1946 case OBJ_FREEFILL: sName=SvxResId(STR_ObjNamePluralFREEFILL); break;
1947 case OBJ_SPLNFILL: sName=SvxResId(STR_ObjNamePluralPERSPLN); break;
1948 default: break;
1949 }
1950 return sName;
1951 }
1952
TakeXorPoly() const1953 basegfx::B2DPolyPolygon SdrPathObj::TakeXorPoly() const
1954 {
1955 return GetPathPoly();
1956 }
1957
GetHdlCount() const1958 sal_uInt32 SdrPathObj::GetHdlCount() const
1959 {
1960 sal_uInt32 nRetval(0);
1961
1962 for(auto const& rPolygon : GetPathPoly())
1963 {
1964 nRetval += rPolygon.count();
1965 }
1966
1967 return nRetval;
1968 }
1969
AddToHdlList(SdrHdlList & rHdlList) const1970 void SdrPathObj::AddToHdlList(SdrHdlList& rHdlList) const
1971 {
1972 // keep old stuff to be able to keep old SdrHdl stuff, too
1973 const XPolyPolygon aOldPathPolygon(GetPathPoly());
1974 sal_uInt16 nPolyCnt=aOldPathPolygon.Count();
1975 bool bClosed=IsClosed();
1976 sal_uInt16 nIdx=0;
1977
1978 for (sal_uInt16 i=0; i<nPolyCnt; i++) {
1979 const XPolygon& rXPoly=aOldPathPolygon.GetObject(i);
1980 sal_uInt16 nPntCnt=rXPoly.GetPointCount();
1981 if (bClosed && nPntCnt>1) nPntCnt--;
1982
1983 for (sal_uInt16 j=0; j<nPntCnt; j++) {
1984 if (rXPoly.GetFlags(j)!=PolyFlags::Control) {
1985 const Point& rPnt=rXPoly[j];
1986 std::unique_ptr<SdrHdl> pHdl(new SdrHdl(rPnt,SdrHdlKind::Poly));
1987 pHdl->SetPolyNum(i);
1988 pHdl->SetPointNum(j);
1989 pHdl->Set1PixMore(j==0);
1990 pHdl->SetSourceHdlNum(nIdx);
1991 nIdx++;
1992 rHdlList.AddHdl(std::move(pHdl));
1993 }
1994 }
1995 }
1996 }
1997
AddToPlusHdlList(SdrHdlList & rHdlList,SdrHdl & rHdl) const1998 void SdrPathObj::AddToPlusHdlList(SdrHdlList& rHdlList, SdrHdl& rHdl) const
1999 {
2000 // keep old stuff to be able to keep old SdrHdl stuff, too
2001 const XPolyPolygon aOldPathPolygon(GetPathPoly());
2002 sal_uInt16 nPnt = static_cast<sal_uInt16>(rHdl.GetPointNum());
2003 sal_uInt16 nPolyNum = static_cast<sal_uInt16>(rHdl.GetPolyNum());
2004
2005 if (nPolyNum>=aOldPathPolygon.Count())
2006 return;
2007
2008 const XPolygon& rXPoly = aOldPathPolygon[nPolyNum];
2009 sal_uInt16 nPntMax = rXPoly.GetPointCount();
2010
2011 if (nPntMax<=0)
2012 return;
2013 nPntMax--;
2014 if (nPnt>nPntMax)
2015 return;
2016
2017 // calculate the number of plus points
2018 sal_uInt16 nCnt = 0;
2019 if (rXPoly.GetFlags(nPnt)!=PolyFlags::Control)
2020 {
2021 if (nPnt==0 && IsClosed())
2022 nPnt=nPntMax;
2023 if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control)
2024 nCnt++;
2025 if (nPnt==nPntMax && IsClosed())
2026 nPnt=0;
2027 if (nPnt<nPntMax && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control)
2028 nCnt++;
2029 }
2030
2031 // construct the plus points
2032 for (sal_uInt32 nPlusNum = 0; nPlusNum < nCnt; ++nPlusNum)
2033 {
2034 nPnt = static_cast<sal_uInt16>(rHdl.GetPointNum());
2035 std::unique_ptr<SdrHdl> pHdl(new SdrHdlBezWgt(&rHdl));
2036 pHdl->SetPolyNum(rHdl.GetPolyNum());
2037
2038 if (nPnt==0 && IsClosed())
2039 nPnt=nPntMax;
2040 if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control && nPlusNum==0)
2041 {
2042 pHdl->SetPos(rXPoly[nPnt-1]);
2043 pHdl->SetPointNum(nPnt-1);
2044 }
2045 else
2046 {
2047 if (nPnt==nPntMax && IsClosed())
2048 nPnt=0;
2049 if (nPnt<rXPoly.GetPointCount()-1 && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control)
2050 {
2051 pHdl->SetPos(rXPoly[nPnt+1]);
2052 pHdl->SetPointNum(nPnt+1);
2053 }
2054 }
2055
2056 pHdl->SetSourceHdlNum(rHdl.GetSourceHdlNum());
2057 pHdl->SetPlusHdl(true);
2058 rHdlList.AddHdl(std::move(pHdl));
2059 }
2060 }
2061
2062 // tdf#123321: Make sure that SdrPathObj (e.g. line) has big enough extent for
2063 // visibility. This is realised by ensuring GetLogicRect() is the same as
2064 // GetSnapRect() for the SdrPathObj. Other SdrTextObj objects like
2065 // SdrObjCustomShape will still use a different version of this method that
2066 // does not consider the rotation. Otherwise, the rotated SdrObjCustomShape
2067 // would become mistakenly larger after save and reload (tdf#91687).
2068 // The invokation of the GetLogicRect() method that caused tdf#123321 was in
2069 // PlcDrawObj::WritePlc().
GetLogicRect() const2070 const tools::Rectangle &SdrPathObj::GetLogicRect() const
2071 {
2072 return GetSnapRect();
2073 }
2074
2075 // dragging
2076
hasSpecialDrag() const2077 bool SdrPathObj::hasSpecialDrag() const
2078 {
2079 return true;
2080 }
2081
beginSpecialDrag(SdrDragStat & rDrag) const2082 bool SdrPathObj::beginSpecialDrag(SdrDragStat& rDrag) const
2083 {
2084 ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2085
2086 return aDragAndCreate.beginPathDrag(rDrag);
2087 }
2088
applySpecialDrag(SdrDragStat & rDrag)2089 bool SdrPathObj::applySpecialDrag(SdrDragStat& rDrag)
2090 {
2091 ImpPathForDragAndCreate aDragAndCreate(*this);
2092 bool bRetval(aDragAndCreate.beginPathDrag(rDrag));
2093
2094 if(bRetval)
2095 {
2096 bRetval = aDragAndCreate.movePathDrag(rDrag);
2097 }
2098
2099 if(bRetval)
2100 {
2101 bRetval = aDragAndCreate.endPathDrag(rDrag);
2102 }
2103
2104 if(bRetval)
2105 {
2106 NbcSetPathPoly(aDragAndCreate.getModifiedPolyPolygon());
2107 }
2108
2109 return bRetval;
2110 }
2111
getSpecialDragComment(const SdrDragStat & rDrag) const2112 OUString SdrPathObj::getSpecialDragComment(const SdrDragStat& rDrag) const
2113 {
2114 OUString aRetval;
2115
2116 if(mpDAC)
2117 {
2118 // #i103058# also get a comment when in creation
2119 const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj());
2120
2121 if(bCreateComment)
2122 {
2123 aRetval = mpDAC->getSpecialDragComment(rDrag);
2124 }
2125 }
2126 else
2127 {
2128 ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2129 bool bDidWork(aDragAndCreate.beginPathDrag(rDrag));
2130
2131 if(bDidWork)
2132 {
2133 aRetval = aDragAndCreate.getSpecialDragComment(rDrag);
2134 }
2135 }
2136
2137 return aRetval;
2138 }
2139
getSpecialDragPoly(const SdrDragStat & rDrag) const2140 basegfx::B2DPolyPolygon SdrPathObj::getSpecialDragPoly(const SdrDragStat& rDrag) const
2141 {
2142 basegfx::B2DPolyPolygon aRetval;
2143 ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2144 bool bDidWork(aDragAndCreate.beginPathDrag(rDrag));
2145
2146 if(bDidWork)
2147 {
2148 aRetval = aDragAndCreate.getSpecialDragPoly(rDrag);
2149 }
2150
2151 return aRetval;
2152 }
2153
2154 // creation
2155
BegCreate(SdrDragStat & rStat)2156 bool SdrPathObj::BegCreate(SdrDragStat& rStat)
2157 {
2158 mpDAC.reset();
2159 impGetDAC().BegCreate(rStat);
2160 return true;
2161 }
2162
MovCreate(SdrDragStat & rStat)2163 bool SdrPathObj::MovCreate(SdrDragStat& rStat)
2164 {
2165 return impGetDAC().MovCreate(rStat);
2166 }
2167
EndCreate(SdrDragStat & rStat,SdrCreateCmd eCmd)2168 bool SdrPathObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
2169 {
2170 bool bRetval(impGetDAC().EndCreate(rStat, eCmd));
2171
2172 if(bRetval && mpDAC)
2173 {
2174 SetPathPoly(mpDAC->getModifiedPolyPolygon());
2175
2176 // #i75974# Check for AutoClose feature. Moved here from ImpPathForDragAndCreate::EndCreate
2177 // to be able to use the type-changing ImpSetClosed method
2178 if(!IsClosedObj())
2179 {
2180 SdrView* pView = rStat.GetView();
2181
2182 if(pView && !pView->IsUseIncompatiblePathCreateInterface())
2183 {
2184 OutputDevice* pOut = pView->GetFirstOutputDevice();
2185
2186 if(pOut)
2187 {
2188 if(GetPathPoly().count())
2189 {
2190 const basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(0));
2191
2192 if(aCandidate.count() > 2)
2193 {
2194 // check distance of first and last point
2195 const sal_Int32 nCloseDist(pOut->PixelToLogic(Size(pView->GetAutoCloseDistPix(), 0)).Width());
2196 const basegfx::B2DVector aDistVector(aCandidate.getB2DPoint(aCandidate.count() - 1) - aCandidate.getB2DPoint(0));
2197
2198 if(aDistVector.getLength() <= static_cast<double>(nCloseDist))
2199 {
2200 // close it
2201 ImpSetClosed(true);
2202 }
2203 }
2204 }
2205 }
2206 }
2207 }
2208
2209 mpDAC.reset();
2210 }
2211
2212 return bRetval;
2213 }
2214
BckCreate(SdrDragStat & rStat)2215 bool SdrPathObj::BckCreate(SdrDragStat& rStat)
2216 {
2217 return impGetDAC().BckCreate(rStat);
2218 }
2219
BrkCreate(SdrDragStat & rStat)2220 void SdrPathObj::BrkCreate(SdrDragStat& rStat)
2221 {
2222 impGetDAC().BrkCreate(rStat);
2223 mpDAC.reset();
2224 }
2225
2226 // polygons
2227
TakeCreatePoly(const SdrDragStat & rDrag) const2228 basegfx::B2DPolyPolygon SdrPathObj::TakeCreatePoly(const SdrDragStat& rDrag) const
2229 {
2230 basegfx::B2DPolyPolygon aRetval;
2231
2232 if(mpDAC)
2233 {
2234 aRetval = mpDAC->TakeObjectPolyPolygon(rDrag);
2235 aRetval.append(ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag));
2236 }
2237
2238 return aRetval;
2239 }
2240
2241 // during drag or create, allow accessing the so-far created/modified polyPolygon
getObjectPolyPolygon(const SdrDragStat & rDrag) const2242 basegfx::B2DPolyPolygon SdrPathObj::getObjectPolyPolygon(const SdrDragStat& rDrag) const
2243 {
2244 basegfx::B2DPolyPolygon aRetval;
2245
2246 if(mpDAC)
2247 {
2248 aRetval = mpDAC->TakeObjectPolyPolygon(rDrag);
2249 }
2250
2251 return aRetval;
2252 }
2253
getDragPolyPolygon(const SdrDragStat & rDrag) const2254 basegfx::B2DPolyPolygon SdrPathObj::getDragPolyPolygon(const SdrDragStat& rDrag) const
2255 {
2256 basegfx::B2DPolyPolygon aRetval;
2257
2258 if(mpDAC)
2259 {
2260 aRetval = ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag);
2261 }
2262
2263 return aRetval;
2264 }
2265
GetCreatePointer() const2266 PointerStyle SdrPathObj::GetCreatePointer() const
2267 {
2268 return impGetDAC().GetCreatePointer();
2269 }
2270
NbcMove(const Size & rSiz)2271 void SdrPathObj::NbcMove(const Size& rSiz)
2272 {
2273 maPathPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(rSiz.Width(), rSiz.Height()));
2274
2275 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2276 SdrTextObj::NbcMove(rSiz);
2277 }
2278
NbcResize(const Point & rRef,const Fraction & xFact,const Fraction & yFact)2279 void SdrPathObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
2280 {
2281 const double fResizeX(xFact);
2282 const double fResizeY(yFact);
2283
2284 if(basegfx::fTools::equal(fResizeX, 1.0) && basegfx::fTools::equal(fResizeY, 1.0))
2285 {
2286 // tdf#106792 avoid numerical unprecisions: If both scale factors are 1.0, do not
2287 // manipulate at all - that may change aGeo rapidly (and wrongly) in
2288 // SdrTextObj::NbcResize. Combined with the UNO API trying to not 'apply'
2289 // a rotation but to manipulate the existing one, this is fatal. So just
2290 // avoid this error as long as we have to deal with imprecise geometry
2291 // manipulations
2292 return;
2293 }
2294
2295 basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRef.X(), -rRef.Y()));
2296 aTrans = basegfx::utils::createScaleTranslateB2DHomMatrix(
2297 double(xFact), double(yFact), rRef.X(), rRef.Y()) * aTrans;
2298 maPathPolygon.transform(aTrans);
2299
2300 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2301 SdrTextObj::NbcResize(rRef,xFact,yFact);
2302 }
2303
NbcRotate(const Point & rRef,Degree100 nAngle,double sn,double cs)2304 void SdrPathObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs)
2305 {
2306 // Thank JOE, the angles are defined mirrored to the mathematical meanings
2307 const basegfx::B2DHomMatrix aTrans(
2308 basegfx::utils::createRotateAroundPoint(rRef.X(), rRef.Y(), -nAngle.get() * F_PI18000));
2309 maPathPolygon.transform(aTrans);
2310
2311 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2312 SdrTextObj::NbcRotate(rRef,nAngle,sn,cs);
2313 }
2314
NbcShear(const Point & rRefPnt,Degree100 nAngle,double fTan,bool bVShear)2315 void SdrPathObj::NbcShear(const Point& rRefPnt, Degree100 nAngle, double fTan, bool bVShear)
2316 {
2317 basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt.X(), -rRefPnt.Y()));
2318
2319 if(bVShear)
2320 {
2321 // Thank JOE, the angles are defined mirrored to the mathematical meanings
2322 aTrans.shearY(-fTan);
2323 }
2324 else
2325 {
2326 aTrans.shearX(-fTan);
2327 }
2328
2329 aTrans.translate(rRefPnt.X(), rRefPnt.Y());
2330 maPathPolygon.transform(aTrans);
2331
2332 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2333 SdrTextObj::NbcShear(rRefPnt,nAngle,fTan,bVShear);
2334 }
2335
NbcMirror(const Point & rRefPnt1,const Point & rRefPnt2)2336 void SdrPathObj::NbcMirror(const Point& rRefPnt1, const Point& rRefPnt2)
2337 {
2338 const double fDiffX(rRefPnt2.X() - rRefPnt1.X());
2339 const double fDiffY(rRefPnt2.Y() - rRefPnt1.Y());
2340 const double fRot(atan2(fDiffY, fDiffX));
2341 basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt1.X(), -rRefPnt1.Y()));
2342 aTrans.rotate(-fRot);
2343 aTrans.scale(1.0, -1.0);
2344 aTrans.rotate(fRot);
2345 aTrans.translate(rRefPnt1.X(), rRefPnt1.Y());
2346 maPathPolygon.transform(aTrans);
2347
2348 // Do Joe's special handling for lines when mirroring, too
2349 ImpForceKind();
2350
2351 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2352 SdrTextObj::NbcMirror(rRefPnt1,rRefPnt2);
2353 }
2354
TakeUnrotatedSnapRect(tools::Rectangle & rRect) const2355 void SdrPathObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const
2356 {
2357 if(!aGeo.nRotationAngle)
2358 {
2359 rRect = GetSnapRect();
2360 }
2361 else
2362 {
2363 XPolyPolygon aXPP(GetPathPoly());
2364 RotateXPoly(aXPP,Point(),-aGeo.mfSinRotationAngle,aGeo.mfCosRotationAngle);
2365 rRect=aXPP.GetBoundRect();
2366 Point aTmp(rRect.TopLeft());
2367 RotatePoint(aTmp,Point(),aGeo.mfSinRotationAngle,aGeo.mfCosRotationAngle);
2368 aTmp-=rRect.TopLeft();
2369 rRect.Move(aTmp.X(),aTmp.Y());
2370 }
2371 }
2372
RecalcSnapRect()2373 void SdrPathObj::RecalcSnapRect()
2374 {
2375 if(GetPathPoly().count())
2376 {
2377 maSnapRect = lcl_ImpGetBoundRect(GetPathPoly());
2378 }
2379 }
2380
NbcSetSnapRect(const tools::Rectangle & rRect)2381 void SdrPathObj::NbcSetSnapRect(const tools::Rectangle& rRect)
2382 {
2383 tools::Rectangle aOld(GetSnapRect());
2384 if (aOld.IsEmpty())
2385 {
2386 Fraction aX(1,1);
2387 Fraction aY(1,1);
2388 NbcResize(aOld.TopLeft(), aX, aY);
2389 NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top()));
2390 return;
2391 }
2392
2393 // Take empty into account when calculating scale factors
2394 tools::Long nMulX = rRect.IsWidthEmpty() ? 0 : rRect.Right() - rRect.Left();
2395
2396 tools::Long nDivX = aOld.Right() - aOld.Left();
2397
2398 // Take empty into account when calculating scale factors
2399 tools::Long nMulY = rRect.IsHeightEmpty() ? 0 : rRect.Bottom() - rRect.Top();
2400
2401 tools::Long nDivY = aOld.Bottom() - aOld.Top();
2402 if ( nDivX == 0 ) { nMulX = 1; nDivX = 1; }
2403 if ( nDivY == 0 ) { nMulY = 1; nDivY = 1; }
2404 if ( nDivX == nMulX ) { nMulX = 1; nDivX = 1; }
2405 if ( nDivY == nMulY ) { nMulY = 1; nDivY = 1; }
2406 Fraction aX(nMulX,nDivX);
2407 Fraction aY(nMulY,nDivY);
2408 NbcResize(aOld.TopLeft(), aX, aY);
2409 NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top()));
2410 }
2411
GetSnapPointCount() const2412 sal_uInt32 SdrPathObj::GetSnapPointCount() const
2413 {
2414 return GetHdlCount();
2415 }
2416
GetSnapPoint(sal_uInt32 nSnapPnt) const2417 Point SdrPathObj::GetSnapPoint(sal_uInt32 nSnapPnt) const
2418 {
2419 sal_uInt32 nPoly,nPnt;
2420 if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nSnapPnt, nPoly, nPnt))
2421 {
2422 SAL_WARN("svx", "SdrPathObj::GetSnapPoint: Point nSnapPnt does not exist.");
2423 }
2424
2425 const basegfx::B2DPoint aB2DPoint(GetPathPoly().getB2DPolygon(nPoly).getB2DPoint(nPnt));
2426 return Point(FRound(aB2DPoint.getX()), FRound(aB2DPoint.getY()));
2427 }
2428
IsPolyObj() const2429 bool SdrPathObj::IsPolyObj() const
2430 {
2431 return true;
2432 }
2433
GetPointCount() const2434 sal_uInt32 SdrPathObj::GetPointCount() const
2435 {
2436 sal_uInt32 nRetval(0);
2437
2438 for(auto const& rPolygon : GetPathPoly())
2439 {
2440 nRetval += rPolygon.count();
2441 }
2442
2443 return nRetval;
2444 }
2445
GetPoint(sal_uInt32 nHdlNum) const2446 Point SdrPathObj::GetPoint(sal_uInt32 nHdlNum) const
2447 {
2448 Point aRetval;
2449 sal_uInt32 nPoly,nPnt;
2450
2451 if(PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt))
2452 {
2453 const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(nPoly));
2454 const basegfx::B2DPoint aPoint(aPoly.getB2DPoint(nPnt));
2455 aRetval = Point(FRound(aPoint.getX()), FRound(aPoint.getY()));
2456 }
2457
2458 return aRetval;
2459 }
2460
NbcSetPoint(const Point & rPnt,sal_uInt32 nHdlNum)2461 void SdrPathObj::NbcSetPoint(const Point& rPnt, sal_uInt32 nHdlNum)
2462 {
2463 sal_uInt32 nPoly,nPnt;
2464
2465 if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt))
2466 return;
2467
2468 basegfx::B2DPolygon aNewPolygon(GetPathPoly().getB2DPolygon(nPoly));
2469 aNewPolygon.setB2DPoint(nPnt, basegfx::B2DPoint(rPnt.X(), rPnt.Y()));
2470 maPathPolygon.setB2DPolygon(nPoly, aNewPolygon);
2471
2472 if(meKind==OBJ_LINE)
2473 {
2474 ImpForceLineAngle();
2475 }
2476 else
2477 {
2478 if(GetPathPoly().count())
2479 {
2480 // #i10659# for SdrTextObj, keep aRect up to date
2481 maRect = lcl_ImpGetBoundRect(GetPathPoly());
2482 }
2483 }
2484
2485 SetRectsDirty();
2486 }
2487
NbcInsPointOld(const Point & rPos,bool bNewObj)2488 sal_uInt32 SdrPathObj::NbcInsPointOld(const Point& rPos, bool bNewObj)
2489 {
2490 sal_uInt32 nNewHdl;
2491
2492 if(bNewObj)
2493 {
2494 nNewHdl = NbcInsPoint(rPos, true);
2495 }
2496 else
2497 {
2498 // look for smallest distance data
2499 const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y());
2500 sal_uInt32 nSmallestPolyIndex(0);
2501 sal_uInt32 nSmallestEdgeIndex(0);
2502 double fSmallestCut;
2503 basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut);
2504
2505 nNewHdl = NbcInsPoint(rPos, false);
2506 }
2507
2508 ImpForceKind();
2509 return nNewHdl;
2510 }
2511
NbcInsPoint(const Point & rPos,bool bNewObj)2512 sal_uInt32 SdrPathObj::NbcInsPoint(const Point& rPos, bool bNewObj)
2513 {
2514 sal_uInt32 nNewHdl;
2515
2516 if(bNewObj)
2517 {
2518 basegfx::B2DPolygon aNewPoly;
2519 const basegfx::B2DPoint aPoint(rPos.X(), rPos.Y());
2520 aNewPoly.append(aPoint);
2521 aNewPoly.setClosed(IsClosed());
2522 maPathPolygon.append(aNewPoly);
2523 SetRectsDirty();
2524 nNewHdl = GetHdlCount();
2525 }
2526 else
2527 {
2528 // look for smallest distance data
2529 const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y());
2530 sal_uInt32 nSmallestPolyIndex(0);
2531 sal_uInt32 nSmallestEdgeIndex(0);
2532 double fSmallestCut;
2533 basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut);
2534 basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(nSmallestPolyIndex));
2535 const bool bBefore(!aCandidate.isClosed() && 0 == nSmallestEdgeIndex && 0.0 == fSmallestCut);
2536 const bool bAfter(!aCandidate.isClosed() && aCandidate.count() == nSmallestEdgeIndex + 2 && 1.0 == fSmallestCut);
2537
2538 if(bBefore)
2539 {
2540 // before first point
2541 aCandidate.insert(0, aTestPoint);
2542
2543 if(aCandidate.areControlPointsUsed())
2544 {
2545 if(aCandidate.isNextControlPointUsed(1))
2546 {
2547 aCandidate.setNextControlPoint(0, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (1.0 / 3.0)));
2548 aCandidate.setPrevControlPoint(1, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (2.0 / 3.0)));
2549 }
2550 }
2551
2552 nNewHdl = 0;
2553 }
2554 else if(bAfter)
2555 {
2556 // after last point
2557 aCandidate.append(aTestPoint);
2558
2559 if(aCandidate.areControlPointsUsed())
2560 {
2561 if(aCandidate.isPrevControlPointUsed(aCandidate.count() - 2))
2562 {
2563 aCandidate.setNextControlPoint(aCandidate.count() - 2, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (1.0 / 3.0)));
2564 aCandidate.setPrevControlPoint(aCandidate.count() - 1, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (2.0 / 3.0)));
2565 }
2566 }
2567
2568 nNewHdl = aCandidate.count() - 1;
2569 }
2570 else
2571 {
2572 // in between
2573 bool bSegmentSplit(false);
2574 const sal_uInt32 nNextIndex((nSmallestEdgeIndex + 1) % aCandidate.count());
2575
2576 if(aCandidate.areControlPointsUsed())
2577 {
2578 if(aCandidate.isNextControlPointUsed(nSmallestEdgeIndex) || aCandidate.isPrevControlPointUsed(nNextIndex))
2579 {
2580 bSegmentSplit = true;
2581 }
2582 }
2583
2584 if(bSegmentSplit)
2585 {
2586 // rebuild original segment to get the split data
2587 basegfx::B2DCubicBezier aBezierA, aBezierB;
2588 const basegfx::B2DCubicBezier aBezier(
2589 aCandidate.getB2DPoint(nSmallestEdgeIndex),
2590 aCandidate.getNextControlPoint(nSmallestEdgeIndex),
2591 aCandidate.getPrevControlPoint(nNextIndex),
2592 aCandidate.getB2DPoint(nNextIndex));
2593
2594 // split and insert hit point
2595 aBezier.split(fSmallestCut, &aBezierA, &aBezierB);
2596 aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint);
2597
2598 // since we inserted hit point and not split point, we need to add an offset
2599 // to the control points to get the C1 continuity we want to achieve
2600 const basegfx::B2DVector aOffset(aTestPoint - aBezierA.getEndPoint());
2601 aCandidate.setNextControlPoint(nSmallestEdgeIndex, aBezierA.getControlPointA() + aOffset);
2602 aCandidate.setPrevControlPoint(nSmallestEdgeIndex + 1, aBezierA.getControlPointB() + aOffset);
2603 aCandidate.setNextControlPoint(nSmallestEdgeIndex + 1, aBezierB.getControlPointA() + aOffset);
2604 aCandidate.setPrevControlPoint((nSmallestEdgeIndex + 2) % aCandidate.count(), aBezierB.getControlPointB() + aOffset);
2605 }
2606 else
2607 {
2608 aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint);
2609 }
2610
2611 nNewHdl = nSmallestEdgeIndex + 1;
2612 }
2613
2614 maPathPolygon.setB2DPolygon(nSmallestPolyIndex, aCandidate);
2615
2616 // create old polygon index from it
2617 for(sal_uInt32 a(0); a < nSmallestPolyIndex; a++)
2618 {
2619 nNewHdl += GetPathPoly().getB2DPolygon(a).count();
2620 }
2621 }
2622
2623 ImpForceKind();
2624 return nNewHdl;
2625 }
2626
RipPoint(sal_uInt32 nHdlNum,sal_uInt32 & rNewPt0Index)2627 SdrObject* SdrPathObj::RipPoint(sal_uInt32 nHdlNum, sal_uInt32& rNewPt0Index)
2628 {
2629 SdrPathObj* pNewObj = nullptr;
2630 const basegfx::B2DPolyPolygon aLocalPolyPolygon(GetPathPoly());
2631 sal_uInt32 nPoly, nPnt;
2632
2633 if(PolyPolygonEditor::GetRelativePolyPoint(aLocalPolyPolygon, nHdlNum, nPoly, nPnt))
2634 {
2635 if(0 == nPoly)
2636 {
2637 const basegfx::B2DPolygon& aCandidate(aLocalPolyPolygon.getB2DPolygon(nPoly));
2638 const sal_uInt32 nPointCount(aCandidate.count());
2639
2640 if(nPointCount)
2641 {
2642 if(IsClosed())
2643 {
2644 // when closed, RipPoint means to open the polygon at the selected point. To
2645 // be able to do that, it is necessary to make the selected point the first one
2646 basegfx::B2DPolygon aNewPolygon(basegfx::utils::makeStartPoint(aCandidate, nPnt));
2647 SetPathPoly(basegfx::B2DPolyPolygon(aNewPolygon));
2648 ToggleClosed();
2649
2650 // give back new position of old start point (historical reasons)
2651 rNewPt0Index = (nPointCount - nPnt) % nPointCount;
2652 }
2653 else
2654 {
2655 if(nPointCount >= 3 && nPnt != 0 && nPnt + 1 < nPointCount)
2656 {
2657 // split in two objects at point nPnt
2658 basegfx::B2DPolygon aSplitPolyA(aCandidate, 0, nPnt + 1);
2659 SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyA));
2660
2661 pNewObj = CloneSdrObject(getSdrModelFromSdrObject());
2662 basegfx::B2DPolygon aSplitPolyB(aCandidate, nPnt, nPointCount - nPnt);
2663 pNewObj->SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyB));
2664 }
2665 }
2666 }
2667 }
2668 }
2669
2670 return pNewObj;
2671 }
2672
DoConvertToPolyObj(bool bBezier,bool bAddText) const2673 SdrObjectUniquePtr SdrPathObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const
2674 {
2675 // #i89784# check for FontWork with activated HideContour
2676 const drawinglayer::attribute::SdrTextAttribute aText(
2677 drawinglayer::primitive2d::createNewSdrTextAttribute(GetObjectItemSet(), *getText(0)));
2678 const bool bHideContour(
2679 !aText.isDefault() && !aText.getSdrFormTextAttribute().isDefault() && aText.isHideContour());
2680
2681 SdrObjectUniquePtr pRet;
2682
2683 if(!bHideContour)
2684 {
2685 SdrPathObjUniquePtr pPath = ImpConvertMakeObj(GetPathPoly(), IsClosed(), bBezier);
2686
2687 if(pPath->GetPathPoly().areControlPointsUsed())
2688 {
2689 if(!bBezier)
2690 {
2691 // reduce all bezier curves
2692 pPath->SetPathPoly(basegfx::utils::adaptiveSubdivideByAngle(pPath->GetPathPoly()));
2693 }
2694 }
2695 else
2696 {
2697 if(bBezier)
2698 {
2699 // create bezier curves
2700 pPath->SetPathPoly(basegfx::utils::expandToCurve(pPath->GetPathPoly()));
2701 }
2702 }
2703 pRet = std::move(pPath);
2704 }
2705
2706 if(bAddText)
2707 {
2708 pRet = ImpConvertAddText(std::move(pRet), bBezier);
2709 }
2710
2711 return pRet;
2712 }
2713
NewGeoData() const2714 std::unique_ptr<SdrObjGeoData> SdrPathObj::NewGeoData() const
2715 {
2716 return std::make_unique<SdrPathObjGeoData>();
2717 }
2718
SaveGeoData(SdrObjGeoData & rGeo) const2719 void SdrPathObj::SaveGeoData(SdrObjGeoData& rGeo) const
2720 {
2721 SdrTextObj::SaveGeoData(rGeo);
2722 SdrPathObjGeoData& rPGeo = static_cast<SdrPathObjGeoData&>( rGeo );
2723 rPGeo.maPathPolygon=GetPathPoly();
2724 rPGeo.meKind=meKind;
2725 }
2726
RestoreGeoData(const SdrObjGeoData & rGeo)2727 void SdrPathObj::RestoreGeoData(const SdrObjGeoData& rGeo)
2728 {
2729 SdrTextObj::RestoreGeoData(rGeo);
2730 const SdrPathObjGeoData& rPGeo=static_cast<const SdrPathObjGeoData&>(rGeo);
2731 maPathPolygon=rPGeo.maPathPolygon;
2732 meKind=rPGeo.meKind;
2733 ImpForceKind(); // to set bClosed (among other things)
2734 }
2735
NbcSetPathPoly(const basegfx::B2DPolyPolygon & rPathPoly)2736 void SdrPathObj::NbcSetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly)
2737 {
2738 if(GetPathPoly() != rPathPoly)
2739 {
2740 maPathPolygon=rPathPoly;
2741 ImpForceKind();
2742 SetRectsDirty();
2743 }
2744 }
2745
SetPathPoly(const basegfx::B2DPolyPolygon & rPathPoly)2746 void SdrPathObj::SetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly)
2747 {
2748 if(GetPathPoly() != rPathPoly)
2749 {
2750 tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect();
2751 NbcSetPathPoly(rPathPoly);
2752 SetChanged();
2753 BroadcastObjectChange();
2754 SendUserCall(SdrUserCallType::Resize,aBoundRect0);
2755 }
2756 }
2757
ToggleClosed()2758 void SdrPathObj::ToggleClosed()
2759 {
2760 tools::Rectangle aBoundRect0;
2761 if(m_pUserCall != nullptr)
2762 aBoundRect0 = GetLastBoundRect();
2763 ImpSetClosed(!IsClosed()); // set new ObjKind
2764 ImpForceKind(); // because we want Line -> Poly -> PolyLine instead of Line -> Poly -> Line
2765 SetRectsDirty();
2766 SetChanged();
2767 BroadcastObjectChange();
2768 SendUserCall(SdrUserCallType::Resize, aBoundRect0);
2769 }
2770
impGetDAC() const2771 ImpPathForDragAndCreate& SdrPathObj::impGetDAC() const
2772 {
2773 if(!mpDAC)
2774 {
2775 const_cast<SdrPathObj*>(this)->mpDAC.reset(new ImpPathForDragAndCreate(*const_cast<SdrPathObj*>(this)));
2776 }
2777
2778 return *mpDAC;
2779 }
2780
2781
2782 // transformation interface for StarOfficeAPI. This implements support for
2783 // homogeneous 3x3 matrices containing the transformation of the SdrObject. At the
2784 // moment it contains a shearX, rotation and translation, but for setting all linear
2785 // transforms like Scale, ShearX, ShearY, Rotate and Translate are supported.
2786
2787
2788 // gets base transformation and rectangle of object. If it's an SdrPathObj it fills the PolyPolygon
2789 // with the base geometry and returns TRUE. Otherwise it returns FALSE.
TRGetBaseGeometry(basegfx::B2DHomMatrix & rMatrix,basegfx::B2DPolyPolygon & rPolyPolygon) const2790 bool SdrPathObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& rPolyPolygon) const
2791 {
2792 double fRotate(0.0);
2793 double fShearX(0.0);
2794 basegfx::B2DTuple aScale(1.0, 1.0);
2795 basegfx::B2DTuple aTranslate(0.0, 0.0);
2796
2797 if(GetPathPoly().count())
2798 {
2799 // copy geometry
2800 basegfx::B2DHomMatrix aMoveToZeroMatrix;
2801 rPolyPolygon = GetPathPoly();
2802
2803 if(OBJ_LINE == meKind)
2804 {
2805 // ignore shear and rotate, just use scale and translate
2806 OSL_ENSURE(GetPathPoly().count() > 0 && GetPathPoly().getB2DPolygon(0).count() > 1, "OBJ_LINE with too few polygons (!)");
2807 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2808 // itself, else this method will no longer return the full polygon information (curve will
2809 // be lost)
2810 const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2811 aScale = aPolyRangeNoCurve.getRange();
2812 aTranslate = aPolyRangeNoCurve.getMinimum();
2813
2814 // define matrix for move polygon to zero point
2815 aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY());
2816 }
2817 else
2818 {
2819 if(aGeo.nShearAngle || aGeo.nRotationAngle)
2820 {
2821 // get rotate and shear in drawingLayer notation
2822 fRotate = aGeo.nRotationAngle.get() * F_PI18000;
2823 fShearX = aGeo.nShearAngle.get() * F_PI18000;
2824
2825 // build mathematically correct (negative shear and rotate) object transform
2826 // containing shear and rotate to extract unsheared, unrotated polygon
2827 basegfx::B2DHomMatrix aObjectMatrix;
2828 aObjectMatrix.shearX(-aGeo.mfTanShearAngle);
2829 aObjectMatrix.rotate((36000 - aGeo.nRotationAngle.get()) * F_PI18000);
2830
2831 // create inverse from it and back-transform polygon
2832 basegfx::B2DHomMatrix aInvObjectMatrix(aObjectMatrix);
2833 aInvObjectMatrix.invert();
2834 rPolyPolygon.transform(aInvObjectMatrix);
2835
2836 // get range from unsheared, unrotated polygon and extract scale and translate.
2837 // transform topLeft from it back to transformed state to get original
2838 // topLeft (rotation center)
2839 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2840 // itself, else this method will no longer return the full polygon information (curve will
2841 // be lost)
2842 const basegfx::B2DRange aCorrectedRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2843 aTranslate = aObjectMatrix * aCorrectedRangeNoCurve.getMinimum();
2844 aScale = aCorrectedRangeNoCurve.getRange();
2845
2846 // define matrix for move polygon to zero point
2847 // #i112280# Added missing minus for Y-Translation
2848 aMoveToZeroMatrix.translate(-aCorrectedRangeNoCurve.getMinX(), -aCorrectedRangeNoCurve.getMinY());
2849 }
2850 else
2851 {
2852 // get scale and translate from unsheared, unrotated polygon
2853 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2854 // itself, else this method will no longer return the full polygon information (curve will
2855 // be lost)
2856 const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2857 aScale = aPolyRangeNoCurve.getRange();
2858 aTranslate = aPolyRangeNoCurve.getMinimum();
2859
2860 // define matrix for move polygon to zero point
2861 aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY());
2862 }
2863 }
2864
2865 // move polygon to zero point with pre-defined matrix
2866 rPolyPolygon.transform(aMoveToZeroMatrix);
2867 }
2868
2869 // position maybe relative to anchorpos, convert
2870 if( getSdrModelFromSdrObject().IsWriter() )
2871 {
2872 if(GetAnchorPos().X() || GetAnchorPos().Y())
2873 {
2874 aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
2875 }
2876 }
2877
2878 // build return value matrix
2879 rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
2880 aScale,
2881 basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX),
2882 basegfx::fTools::equalZero(fRotate) ? 0.0 : -fRotate,
2883 aTranslate);
2884
2885 return true;
2886 }
2887
SetHandleScale(bool bHandleScale)2888 void SdrPathObj::SetHandleScale(bool bHandleScale)
2889 {
2890 mbHandleScale = bHandleScale;
2891 }
2892
2893 // Sets the base geometry of the object using infos contained in the homogeneous 3x3 matrix.
2894 // If it's an SdrPathObj it will use the provided geometry information. The Polygon has
2895 // to use (0,0) as upper left and will be scaled to the given size in the matrix.
TRSetBaseGeometry(const basegfx::B2DHomMatrix & rMatrix,const basegfx::B2DPolyPolygon & rPolyPolygon)2896 void SdrPathObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& rPolyPolygon)
2897 {
2898 // break up matrix
2899 basegfx::B2DTuple aScale;
2900 basegfx::B2DTuple aTranslate;
2901 double fRotate, fShearX;
2902 rMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
2903
2904 // #i75086# Old DrawingLayer (GeoStat and geometry) does not support holding negative scalings
2905 // in X and Y which equal a 180 degree rotation. Recognize it and react accordingly
2906 if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0))
2907 {
2908 aScale.setX(fabs(aScale.getX()));
2909 aScale.setY(fabs(aScale.getY()));
2910 fRotate = fmod(fRotate + F_PI, F_2PI);
2911 }
2912
2913 // copy poly
2914 basegfx::B2DPolyPolygon aNewPolyPolygon(rPolyPolygon);
2915
2916 // reset object shear and rotations
2917 aGeo.nRotationAngle = 0_deg100;
2918 aGeo.RecalcSinCos();
2919 aGeo.nShearAngle = 0_deg100;
2920 aGeo.RecalcTan();
2921
2922 if( getSdrModelFromSdrObject().IsWriter() )
2923 {
2924 // if anchor is used, make position relative to it
2925 if(GetAnchorPos().X() || GetAnchorPos().Y())
2926 {
2927 aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
2928 }
2929 }
2930
2931 // create transformation for polygon, set values at aGeo direct
2932 basegfx::B2DHomMatrix aTransform;
2933
2934 // #i75086#
2935 // Given polygon is already scaled (for historical reasons), but not mirrored yet.
2936 // Thus, when scale is negative in X or Y, apply the needed mirroring accordingly.
2937 double fScaleX(basegfx::fTools::less(aScale.getX(), 0.0) ? -1.0 : 1.0);
2938 double fScaleY(basegfx::fTools::less(aScale.getY(), 0.0) ? -1.0 : 1.0);
2939
2940 // tdf#98565, tdf#98584. While loading a shape, svg:width and svg:height is used to scale
2941 // the polygon. But draw:transform might introduce additional scaling factors, which need to
2942 // be applied to the polygon too, so aScale cannot be ignored while loading.
2943 // I use "maSnapRect.IsEmpty() && GetPathPoly().count()" to detect this case. Any better
2944 // idea? The behavior in other cases is the same as it was before this fix.
2945 if (maSnapRect.IsEmpty() && GetPathPoly().count() && mbHandleScale)
2946 {
2947 // In case of a Writer document, the scaling factors were converted to twips. That is not
2948 // correct here, because width and height are already in the points coordinates and aScale
2949 // is no length but only a factor here. Convert back.
2950 if (getSdrModelFromSdrObject().IsWriter())
2951 {
2952 aScale.setX(o3tl::convert(aScale.getX(), o3tl::Length::twip, o3tl::Length::mm100));
2953 aScale.setY(o3tl::convert(aScale.getY(), o3tl::Length::twip, o3tl::Length::mm100));
2954 }
2955 fScaleX *= fabs(aScale.getX());
2956 fScaleY *= fabs(aScale.getY());
2957 }
2958
2959 if (fScaleX != 1.0 || fScaleY != 1.0)
2960 aTransform.scale(fScaleX, fScaleY);
2961
2962 if(!basegfx::fTools::equalZero(fShearX))
2963 {
2964 aTransform.shearX(tan(-atan(fShearX)));
2965 aGeo.nShearAngle = Degree100(FRound(atan(fShearX) / F_PI18000));
2966 aGeo.RecalcTan();
2967 }
2968
2969 if(!basegfx::fTools::equalZero(fRotate))
2970 {
2971 // #i78696#
2972 // fRotate is mathematically correct for linear transformations, so it's
2973 // the one to use for the geometry change
2974 aTransform.rotate(fRotate);
2975
2976 // #i78696#
2977 // fRotate is mathematically correct, but aGeoStat.nRotationAngle is
2978 // mirrored -> mirror value here
2979 aGeo.nRotationAngle = NormAngle36000(Degree100(FRound(-fRotate / F_PI18000)));
2980 aGeo.RecalcSinCos();
2981 }
2982
2983 if(!aTranslate.equalZero())
2984 {
2985 // #i39529# absolute positioning, so get current position (without control points (!))
2986 const basegfx::B2DRange aCurrentRange(basegfx::utils::getRange(aNewPolyPolygon));
2987 aTransform.translate(aTranslate.getX() - aCurrentRange.getMinX(), aTranslate.getY() - aCurrentRange.getMinY());
2988 }
2989
2990 // transform polygon and trigger change
2991 aNewPolyPolygon.transform(aTransform);
2992 SetPathPoly(aNewPolyPolygon);
2993 }
2994
2995 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2996