tdf#160404 tdf#160535 tdf#160536 - sc improve sheet protection
[LibreOffice.git] / svx / source / svdraw / svdopath.cxx
blob84ef3909c7b50439024ac15c2eb63a081a1d52f4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <o3tl/unit_conversion.hxx>
21 #include <tools/bigint.hxx>
22 #include <tools/helpers.hxx>
23 #include <svx/svdopath.hxx>
24 #include <math.h>
25 #include <svx/xpoly.hxx>
26 #include <svx/svdtrans.hxx>
27 #include <svx/svddrag.hxx>
28 #include <svx/svdmodel.hxx>
29 #include <svx/svdhdl.hxx>
30 #include <svx/svdview.hxx>
31 #include <svx/dialmgr.hxx>
32 #include <svx/strings.hrc>
34 #include <svx/polypolygoneditor.hxx>
35 #include <sdr/contact/viewcontactofsdrpathobj.hxx>
36 #include <basegfx/matrix/b2dhommatrix.hxx>
37 #include <basegfx/point/b2dpoint.hxx>
38 #include <basegfx/polygon/b2dpolypolygontools.hxx>
39 #include <basegfx/range/b2drange.hxx>
40 #include <basegfx/curve/b2dcubicbezier.hxx>
41 #include <basegfx/polygon/b2dpolygontools.hxx>
42 #include <sdr/attribute/sdrtextattribute.hxx>
43 #include <sdr/primitive2d/sdrattributecreator.hxx>
44 #include <basegfx/matrix/b2dhommatrixtools.hxx>
45 #include <sdr/attribute/sdrformtextattribute.hxx>
46 #include <utility>
47 #include <vcl/ptrstyle.hxx>
48 #include <memory>
49 #include <sal/log.hxx>
50 #include <osl/diagnose.h>
52 using namespace sdr;
54 static sal_uInt16 GetPrevPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed)
56 if (nPnt>0) {
57 nPnt--;
58 } else {
59 nPnt=nPntMax;
60 if (bClosed) nPnt--;
62 return nPnt;
65 static sal_uInt16 GetNextPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed)
67 nPnt++;
68 if (nPnt>nPntMax || (bClosed && nPnt>=nPntMax)) nPnt=0;
69 return nPnt;
72 namespace {
74 struct ImpSdrPathDragData : public SdrDragStatUserData
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)
101 bool mbMultiPointDrag;
102 const XPolyPolygon maOrig;
103 XPolyPolygon maMove;
104 std::vector<SdrHdl*> maHandles;
106 public:
107 ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag);
108 void ResetPoly(const SdrPathObj& rPO);
109 bool IsMultiPointDrag() const { return mbMultiPointDrag; }
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)
143 if(mbMultiPointDrag)
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);
150 for(size_t a = 0; a < nHdlCount; ++a)
152 SdrHdl* pTestHdl = rHdlList.GetHdl(a);
154 if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject)
156 maHandles.push_back(pTestHdl);
160 maMove = maOrig;
161 bValid = true;
163 else
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;
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;
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;
216 void ImpSdrPathDragData::ResetPoly(const SdrPathObj& rPO)
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));
226 namespace {
228 struct ImpPathCreateUser : public SdrDragStatUserData
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;
258 public:
259 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(SdrObjKind::NONE),eCurrentKind(SdrObjKind::NONE) { }
263 void ResetFormFlags() { bBezier=false; bCircle=false; bLine=false; bRect=false; }
264 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;
279 XPolygon ImpPathCreateUser::GetFormPoly() const
281 if (bBezier) return GetBezierPoly();
282 if (bCircle) return GetCirclePoly();
283 if (bLine) return GetLinePoly();
284 if (bRect) return GetRectPoly();
285 return XPolygon();
288 void ImpPathCreateUser::CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown)
290 aBezStart=rP1;
291 aBezCtrl1=rP1+rDir;
292 aBezCtrl2=rP2;
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;
298 bBezier=true;
301 XPolygon ImpPathCreateUser::GetBezierPoly() const
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;
311 void ImpPathCreateUser::CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
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(toRadians(nTmpAngle));
326 double nR=static_cast<double>(GetLen(Point(dx,dy)))/cs/2;
327 nRad = std::abs(basegfx::fround<tools::Long>(nR));
329 if (dAngle<18000_deg100) {
330 nCircStAngle=NormAngle36000(nTangAngle-9000_deg100);
331 nCircRelAngle=NormAngle36000(2_deg100*dAngle);
332 aCircCenter.AdjustX(basegfx::fround<tools::Long>(nRad * cos(toRadians(nTangAngle + 9000_deg100))));
333 aCircCenter.AdjustY(basegfx::fround<tools::Long>(nRad * -sin(toRadians(nTangAngle + 9000_deg100))));
334 } else {
335 nCircStAngle=NormAngle36000(nTangAngle+9000_deg100);
336 nCircRelAngle=-NormAngle36000(36000_deg100-2_deg100*dAngle);
337 aCircCenter.AdjustX(basegfx::fround<tools::Long>(nRad * cos(toRadians(nTangAngle - 9000_deg100))));
338 aCircCenter.AdjustY(basegfx::fround<tools::Long>(nRad * -sin(toRadians(nTangAngle - 9000_deg100))));
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;
353 nCircRadius=nRad;
354 if (nRad==0 || abs(nCircRelAngle).get()<5) bRet=false;
355 bCircle=bRet;
358 XPolygon ImpPathCreateUser::GetCirclePoly() const
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;
377 aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth);
378 if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd;
379 return aXP;
383 Point ImpPathCreateUser::CalcLine(const Point& aCsr, tools::Long nDirX, tools::Long nDirY, SdrView const * pView)
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 x2=BigMulDiv(y,nDirX,nDirY);
393 tools::Long y2=BigMulDiv(x,nDirY,nDirX);
394 tools::Long l1=std::abs(x2)+std::abs(y);
395 tools::Long l2=std::abs(x)+std::abs(y2);
396 if ((l1<=l2) != (pView!=nullptr && pView->IsBigOrtho())) {
397 x = x2;
398 } else {
399 y = y2;
402 return Point(x,y);
405 void ImpPathCreateUser::CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
407 aLineStart=rP1;
408 aLineEnd=rP2;
409 bLine90=false;
410 if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bLine=false; return; }
411 Point aTmpPt(rP2-rP1);
412 tools::Long nDirX=rDir.X();
413 tools::Long nDirY=rDir.Y();
414 Point aP1(CalcLine(aTmpPt, nDirX, nDirY,pView)); aP1-=aTmpPt; tools::Long nQ1=std::abs(aP1.X())+std::abs(aP1.Y());
415 Point aP2(CalcLine(aTmpPt, nDirY,-nDirX,pView)); aP2-=aTmpPt; tools::Long nQ2=std::abs(aP2.X())+std::abs(aP2.Y());
416 if (pView!=nullptr && pView->IsOrtho()) nQ1=0; // Ortho turns off at right angle
417 bLine90=nQ1>2*nQ2;
418 if (!bLine90) { // smooth transition
419 aLineEnd+=aP1;
420 } else { // rectangular transition
421 aLineEnd+=aP2;
423 bLine=true;
426 XPolygon ImpPathCreateUser::GetLinePoly() const
428 XPolygon aXP(2);
429 aXP[0]=aLineStart; if (!bLine90) aXP.SetFlags(0,PolyFlags::Smooth);
430 aXP[1]=aLineEnd;
431 return aXP;
434 void ImpPathCreateUser::CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
436 aRectP1=rP1;
437 aRectP2=rP1;
438 aRectP3=rP2;
439 if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bRect=false; return; }
440 Point aTmpPt(rP2-rP1);
441 tools::Long nDirX=rDir.X();
442 tools::Long nDirY=rDir.Y();
443 tools::Long x=aTmpPt.X();
444 tools::Long y=aTmpPt.Y();
445 bool bHLin=nDirY==0;
446 bool bVLin=nDirX==0;
447 if (bHLin) y=0;
448 else if (bVLin) x=0;
449 else {
450 y=BigMulDiv(x,nDirY,nDirX);
451 tools::Long nHypLen=aTmpPt.Y()-y;
452 Degree100 nTangAngle=-GetAngle(rDir);
453 // sin=g/h, g=h*sin
454 double a = toRadians(nTangAngle);
455 double sn=sin(a);
456 double cs=cos(a);
457 double nGKathLen=nHypLen*sn;
458 y += basegfx::fround<tools::Long>(nGKathLen * sn);
459 x += basegfx::fround<tools::Long>(nGKathLen * cs);
461 aRectP2.AdjustX(x );
462 aRectP2.AdjustY(y );
463 if (pView!=nullptr && pView->IsOrtho()) {
464 tools::Long dx1=aRectP2.X()-aRectP1.X(); tools::Long dx1a=std::abs(dx1);
465 tools::Long dy1=aRectP2.Y()-aRectP1.Y(); tools::Long dy1a=std::abs(dy1);
466 tools::Long dx2=aRectP3.X()-aRectP2.X(); tools::Long dx2a=std::abs(dx2);
467 tools::Long dy2=aRectP3.Y()-aRectP2.Y(); tools::Long dy2a=std::abs(dy2);
468 bool b1MoreThan2=dx1a+dy1a>dx2a+dy2a;
469 if (b1MoreThan2 != pView->IsBigOrtho()) {
470 tools::Long xtemp=dy2a-dx1a; if (dx1<0) xtemp=-xtemp;
471 tools::Long ytemp=dx2a-dy1a; if (dy1<0) ytemp=-ytemp;
472 aRectP2.AdjustX(xtemp );
473 aRectP2.AdjustY(ytemp );
474 aRectP3.AdjustX(xtemp );
475 aRectP3.AdjustY(ytemp );
476 } else {
477 tools::Long xtemp=dy1a-dx2a; if (dx2<0) xtemp=-xtemp;
478 tools::Long ytemp=dx1a-dy2a; if (dy2<0) ytemp=-ytemp;
479 aRectP3.AdjustX(xtemp );
480 aRectP3.AdjustY(ytemp );
483 bRect=true;
486 XPolygon ImpPathCreateUser::GetRectPoly() const
488 XPolygon aXP(3);
489 aXP[0]=aRectP1; aXP.SetFlags(0,PolyFlags::Smooth);
490 aXP[1]=aRectP2;
491 if (aRectP3!=aRectP2) aXP[2]=aRectP3;
492 return aXP;
495 class ImpPathForDragAndCreate
497 SdrPathObj& mrSdrPathObject;
498 XPolyPolygon aPathPolygon;
499 SdrObjKind meObjectKind;
500 std::unique_ptr<ImpSdrPathDragData>
501 mpSdrPathDragData;
502 bool mbCreating;
504 public:
505 explicit ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject);
507 // drag stuff
508 bool beginPathDrag( SdrDragStat const & rDrag ) const;
509 bool movePathDrag( SdrDragStat& rDrag ) const;
510 bool endPathDrag( SdrDragStat const & rDrag );
511 OUString getSpecialDragComment(const SdrDragStat& rDrag) const;
512 basegfx::B2DPolyPolygon getSpecialDragPoly(const SdrDragStat& rDrag) const;
514 // create stuff
515 void BegCreate(SdrDragStat& rStat);
516 bool MovCreate(SdrDragStat& rStat);
517 bool EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd);
518 bool BckCreate(SdrDragStat const & rStat);
519 void BrkCreate(SdrDragStat& rStat);
520 PointerStyle GetCreatePointer() const;
522 // helping stuff
523 static bool IsClosed(SdrObjKind eKind) { return eKind==SdrObjKind::Polygon || eKind==SdrObjKind::PathPoly || eKind==SdrObjKind::PathFill || eKind==SdrObjKind::FreehandFill; }
524 static bool IsFreeHand(SdrObjKind eKind) { return eKind==SdrObjKind::FreehandLine || eKind==SdrObjKind::FreehandFill; }
525 static bool IsBezier(SdrObjKind eKind) { return eKind==SdrObjKind::PathLine || eKind==SdrObjKind::PathFill; }
526 bool IsCreating() const { return mbCreating; }
528 // get the polygon
529 basegfx::B2DPolyPolygon TakeObjectPolyPolygon(const SdrDragStat& rDrag) const;
530 static basegfx::B2DPolyPolygon TakeDragPolyPolygon(const SdrDragStat& rDrag);
531 basegfx::B2DPolyPolygon getModifiedPolyPolygon() const { return aPathPolygon.getB2DPolyPolygon(); }
534 ImpPathForDragAndCreate::ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject)
535 : mrSdrPathObject(rSdrPathObject),
536 aPathPolygon(rSdrPathObject.GetPathPoly()),
537 meObjectKind(mrSdrPathObject.meKind),
538 mbCreating(false)
542 bool ImpPathForDragAndCreate::beginPathDrag( SdrDragStat const & rDrag ) const
544 const SdrHdl* pHdl=rDrag.GetHdl();
545 if(!pHdl)
546 return false;
548 bool bMultiPointDrag(true);
550 if(aPathPolygon[static_cast<sal_uInt16>(pHdl->GetPolyNum())].IsControl(static_cast<sal_uInt16>(pHdl->GetPointNum())))
551 bMultiPointDrag = false;
553 if(bMultiPointDrag)
555 const SdrMarkView& rMarkView = *rDrag.GetView();
556 const SdrHdlList& rHdlList = rMarkView.GetHdlList();
557 const size_t nHdlCount = rHdlList.GetHdlCount();
558 const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr);
559 sal_uInt32 nSelectedPoints(0);
561 for(size_t a = 0; a < nHdlCount; ++a)
563 SdrHdl* pTestHdl = rHdlList.GetHdl(a);
565 if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject)
567 nSelectedPoints++;
571 if(nSelectedPoints <= 1)
572 bMultiPointDrag = false;
575 const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset( new ImpSdrPathDragData(mrSdrPathObject,*pHdl,bMultiPointDrag,rDrag) );
577 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
579 OSL_FAIL("ImpPathForDragAndCreate::BegDrag(): ImpSdrPathDragData is invalid.");
580 const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset();
581 return false;
584 return true;
587 bool ImpPathForDragAndCreate::movePathDrag( SdrDragStat& rDrag ) const
589 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
591 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
592 return false;
595 if(mpSdrPathDragData->IsMultiPointDrag())
597 Point aDelta(rDrag.GetNow() - rDrag.GetStart());
599 if(aDelta.X() || aDelta.Y())
601 for(SdrHdl* pHandle : mpSdrPathDragData->maHandles)
603 const sal_uInt16 nPolyIndex(static_cast<sal_uInt16>(pHandle->GetPolyNum()));
604 const sal_uInt16 nPointIndex(static_cast<sal_uInt16>(pHandle->GetPointNum()));
605 const XPolygon& rOrig = mpSdrPathDragData->maOrig[nPolyIndex];
606 XPolygon& rMove = mpSdrPathDragData->maMove[nPolyIndex];
607 const sal_uInt16 nPointCount(rOrig.GetPointCount());
608 bool bClosed(rOrig[0] == rOrig[nPointCount-1]);
610 // move point itself
611 rMove[nPointIndex] = rOrig[nPointIndex] + aDelta;
613 // when point is first and poly closed, move close point, too.
614 if(nPointCount > 0 && !nPointIndex && bClosed)
616 rMove[nPointCount - 1] = rOrig[nPointCount - 1] + aDelta;
618 // when moving the last point it may be necessary to move the
619 // control point in front of this one, too.
620 if(nPointCount > 1 && rOrig.IsControl(nPointCount - 2))
621 rMove[nPointCount - 2] = rOrig[nPointCount - 2] + aDelta;
624 // is a control point before this?
625 if(nPointIndex > 0 && rOrig.IsControl(nPointIndex - 1))
627 // Yes, move it, too
628 rMove[nPointIndex - 1] = rOrig[nPointIndex - 1] + aDelta;
631 // is a control point after this?
632 if(nPointIndex + 1 < nPointCount && rOrig.IsControl(nPointIndex + 1))
634 // Yes, move it, too
635 rMove[nPointIndex + 1] = rOrig[nPointIndex + 1] + aDelta;
640 else
642 mpSdrPathDragData->ResetPoly(mrSdrPathObject);
644 // copy certain data locally to use less code and have faster access times
645 bool bClosed =mpSdrPathDragData->bClosed ; // closed object?
646 sal_uInt16 nPnt =mpSdrPathDragData->nPnt ; // number of point in the above polygon
647 bool bBegPnt =mpSdrPathDragData->bBegPnt ; // dragged point is first point of a Polyline
648 bool bEndPnt =mpSdrPathDragData->bEndPnt ; // dragged point is last point of a Polyline
649 sal_uInt16 nPrevPnt =mpSdrPathDragData->nPrevPnt ; // index of previous point
650 sal_uInt16 nNextPnt =mpSdrPathDragData->nNextPnt ; // index of next point
651 bool bPrevIsBegPnt =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline
652 bool bNextIsEndPnt =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline
653 sal_uInt16 nPrevPrevPnt =mpSdrPathDragData->nPrevPrevPnt ; // index of the point before the previous point
654 sal_uInt16 nNextNextPnt =mpSdrPathDragData->nNextNextPnt ; // index if the point after the next point
655 bool bControl =mpSdrPathDragData->bControl ; // point is a control point
656 bool bIsNextControl =mpSdrPathDragData->bIsNextControl; // point is a control point after a support point
657 bool bPrevIsControl =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before
658 bool bNextIsControl =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after
660 // Ortho for lines/polygons: keep angle
661 if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho()) {
662 bool bBigOrtho=rDrag.GetView()->IsBigOrtho();
663 Point aPos(rDrag.GetNow()); // current position
664 Point aPnt(mpSdrPathDragData->aXP[nPnt]); // the dragged point
665 sal_uInt16 nPnt1=0xFFFF,nPnt2=0xFFFF; // its neighboring points
666 Point aNewPos1,aNewPos2; // new alternative for aPos
667 bool bPnt1 = false, bPnt2 = false; // are these valid alternatives?
668 if (!bClosed && mpSdrPathDragData->nPointCount>=2) { // minimum of 2 points for lines
669 if (!bBegPnt) nPnt1=nPrevPnt;
670 if (!bEndPnt) nPnt2=nNextPnt;
672 if (bClosed && mpSdrPathDragData->nPointCount>=3) { // minimum of 3 points for polygon
673 nPnt1=nPrevPnt;
674 nPnt2=nNextPnt;
676 if (nPnt1!=0xFFFF && !bPrevIsControl) {
677 Point aPnt1=mpSdrPathDragData->aXP[nPnt1];
678 tools::Long ndx0=aPnt.X()-aPnt1.X();
679 tools::Long ndy0=aPnt.Y()-aPnt1.Y();
680 bool bHLin=ndy0==0;
681 bool bVLin=ndx0==0;
682 if (!bHLin || !bVLin) {
683 tools::Long ndx=aPos.X()-aPnt1.X();
684 tools::Long ndy=aPos.Y()-aPnt1.Y();
685 bPnt1=true;
686 double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0);
687 double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0);
688 bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho);
689 bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho);
690 if (bHor) ndy=tools::Long(ndy0*nXFact);
691 if (bVer) ndx=tools::Long(ndx0*nYFact);
692 aNewPos1=aPnt1;
693 aNewPos1.AdjustX(ndx );
694 aNewPos1.AdjustY(ndy );
697 if (nPnt2!=0xFFFF && !bNextIsControl) {
698 Point aPnt2=mpSdrPathDragData->aXP[nPnt2];
699 tools::Long ndx0=aPnt.X()-aPnt2.X();
700 tools::Long ndy0=aPnt.Y()-aPnt2.Y();
701 bool bHLin=ndy0==0;
702 bool bVLin=ndx0==0;
703 if (!bHLin || !bVLin) {
704 tools::Long ndx=aPos.X()-aPnt2.X();
705 tools::Long ndy=aPos.Y()-aPnt2.Y();
706 bPnt2=true;
707 double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0);
708 double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0);
709 bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho);
710 bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho);
711 if (bHor) ndy=tools::Long(ndy0*nXFact);
712 if (bVer) ndx=tools::Long(ndx0*nYFact);
713 aNewPos2=aPnt2;
714 aNewPos2.AdjustX(ndx );
715 aNewPos2.AdjustY(ndy );
718 if (bPnt1 && bPnt2) { // both alternatives exist (and compete)
719 BigInt nX1(aNewPos1.X()-aPos.X()); nX1*=nX1;
720 BigInt nY1(aNewPos1.Y()-aPos.Y()); nY1*=nY1;
721 BigInt nX2(aNewPos2.X()-aPos.X()); nX2*=nX2;
722 BigInt nY2(aNewPos2.Y()-aPos.Y()); nY2*=nY2;
723 nX1+=nY1; // correction distance to square
724 nX2+=nY2; // correction distance to square
725 // let the alternative that allows fewer correction win
726 if (nX1<nX2) bPnt2=false; else bPnt1=false;
728 if (bPnt1) rDrag.SetNow(aNewPos1);
729 if (bPnt2) rDrag.SetNow(aNewPos2);
731 rDrag.SetActionRect(tools::Rectangle(rDrag.GetNow(),rDrag.GetNow()));
733 // specially for IBM: Eliminate points if both adjoining lines form near 180 degrees angle anyway
734 if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsEliminatePolyPoints() &&
735 !bBegPnt && !bEndPnt && !bPrevIsControl && !bNextIsControl)
737 Point aPt(mpSdrPathDragData->aXP[nNextPnt]);
738 aPt-=rDrag.GetNow();
739 Degree100 nAngle1=GetAngle(aPt);
740 aPt=rDrag.GetNow();
741 aPt-=mpSdrPathDragData->aXP[nPrevPnt];
742 Degree100 nAngle2=GetAngle(aPt);
743 Degree100 nDiff=nAngle1-nAngle2;
744 nDiff=abs(nDiff);
745 mpSdrPathDragData->bEliminate=nDiff<=rDrag.GetView()->GetEliminatePolyPointLimitAngle();
746 if (mpSdrPathDragData->bEliminate) { // adapt position, Smooth is true for the ends
747 aPt=mpSdrPathDragData->aXP[nNextPnt];
748 aPt+=mpSdrPathDragData->aXP[nPrevPnt];
749 aPt/=2;
750 rDrag.SetNow(aPt);
754 // we dragged by this distance
755 Point aDiff(rDrag.GetNow()); aDiff-=mpSdrPathDragData->aXP[nPnt];
757 /* There are 8 possible cases:
758 X 1. A control point neither on the left nor on the right.
759 o--X--o 2. There are control points on the left and the right, we are dragging a support point.
760 o--X 3. There is a control point on the left, we are dragging a support point.
761 X--o 4. There is a control point on the right, we are dragging a support point.
762 x--O--o 5. There are control points on the left and the right, we are dragging the left one.
763 x--O 6. There is a control point on the left, we are dragging it.
764 o--O--x 7. There are control points on the left and the right, we are dragging the right one.
765 O--x 8. There is a control point on the right, we are dragging it.
766 Note: modifying a line (not a curve!) might create a curve on the other end of the line
767 if Smooth is set there (with control points aligned to line).
770 mpSdrPathDragData->aXP[nPnt]+=aDiff;
772 // now check symmetric plus handles
773 if (bControl) { // cases 5,6,7,8
774 sal_uInt16 nSt; // the associated support point
775 sal_uInt16 nFix; // the opposing control point
776 if (bIsNextControl) { // if the next one is a control point, the on before has to be a support point
777 nSt=nPrevPnt;
778 nFix=nPrevPrevPnt;
779 } else {
780 nSt=nNextPnt;
781 nFix=nNextNextPnt;
783 if (mpSdrPathDragData->aXP.IsSmooth(nSt)) {
784 mpSdrPathDragData->aXP.CalcSmoothJoin(nSt,nPnt,nFix);
788 if (!bControl) { // Cases 1,2,3,4. In case 1, nothing happens; in cases 3 and 4, there is more following below.
789 // move both control points
790 if (bPrevIsControl) mpSdrPathDragData->aXP[nPrevPnt]+=aDiff;
791 if (bNextIsControl) mpSdrPathDragData->aXP[nNextPnt]+=aDiff;
792 // align control point to line, if appropriate
793 if (mpSdrPathDragData->aXP.IsSmooth(nPnt)) {
794 if (bPrevIsControl && !bNextIsControl && !bEndPnt) { // case 3
795 mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nNextPnt,nPrevPnt);
797 if (bNextIsControl && !bPrevIsControl && !bBegPnt) { // case 4
798 mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nPrevPnt,nNextPnt);
801 // Now check the other ends of the line (nPnt+-1). If there is a
802 // curve (IsControl(nPnt+-2)) with SmoothJoin (nPnt+-1), the
803 // associated control point (nPnt+-2) has to be adapted.
804 if (!bBegPnt && !bPrevIsControl && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsSmooth(nPrevPnt)) {
805 if (mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
806 mpSdrPathDragData->aXP.CalcSmoothJoin(nPrevPnt,nPnt,nPrevPrevPnt);
809 if (!bEndPnt && !bNextIsControl && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsSmooth(nNextPnt)) {
810 if (mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
811 mpSdrPathDragData->aXP.CalcSmoothJoin(nNextPnt,nPnt,nNextNextPnt);
817 return true;
820 bool ImpPathForDragAndCreate::endPathDrag(SdrDragStat const & rDrag)
822 Point aLinePt1;
823 Point aLinePt2;
824 bool bLineGlueMirror(SdrObjKind::Line == meObjectKind);
825 if (bLineGlueMirror) {
826 XPolygon& rXP=aPathPolygon[0];
827 aLinePt1=rXP[0];
828 aLinePt2=rXP[1];
831 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
833 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
834 return false;
837 if(mpSdrPathDragData->IsMultiPointDrag())
839 aPathPolygon = mpSdrPathDragData->maMove;
841 else
843 const SdrHdl* pHdl=rDrag.GetHdl();
845 // reference the polygon
846 XPolygon& rXP=aPathPolygon[static_cast<sal_uInt16>(pHdl->GetPolyNum())];
848 // the 5 points that might have changed
849 if (!mpSdrPathDragData->bPrevIsBegPnt) rXP[mpSdrPathDragData->nPrevPrevPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPrevPnt];
850 if (!mpSdrPathDragData->bNextIsEndPnt) rXP[mpSdrPathDragData->nNextNextPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nNextNextPnt];
851 if (!mpSdrPathDragData->bBegPnt) rXP[mpSdrPathDragData->nPrevPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPnt];
852 if (!mpSdrPathDragData->bEndPnt) rXP[mpSdrPathDragData->nNextPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nNextPnt];
853 rXP[mpSdrPathDragData->nPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nPnt];
855 // for closed objects: last point has to be equal to first point
856 if (mpSdrPathDragData->bClosed) rXP[rXP.GetPointCount()-1]=rXP[0];
858 if (mpSdrPathDragData->bEliminate)
860 basegfx::B2DPolyPolygon aTempPolyPolygon(aPathPolygon.getB2DPolyPolygon());
861 sal_uInt32 nPoly,nPnt;
863 if(PolyPolygonEditor::GetRelativePolyPoint(aTempPolyPolygon, rDrag.GetHdl()->GetSourceHdlNum(), nPoly, nPnt))
865 basegfx::B2DPolygon aCandidate(aTempPolyPolygon.getB2DPolygon(nPoly));
866 aCandidate.remove(nPnt);
868 if(aCandidate.count() < 2)
870 aTempPolyPolygon.remove(nPoly);
872 else
874 aTempPolyPolygon.setB2DPolygon(nPoly, aCandidate);
878 aPathPolygon = XPolyPolygon(aTempPolyPolygon);
881 // adapt angle for text beneath a simple line
882 if (bLineGlueMirror)
884 Point aLinePt1_(aPathPolygon[0][0]);
885 Point aLinePt2_(aPathPolygon[0][1]);
886 bool bXMirr=(aLinePt1_.X()>aLinePt2_.X())!=(aLinePt1.X()>aLinePt2.X());
887 bool bYMirr=(aLinePt1_.Y()>aLinePt2_.Y())!=(aLinePt1.Y()>aLinePt2.Y());
888 if (bXMirr || bYMirr) {
889 Point aRef1(mrSdrPathObject.GetSnapRect().Center());
890 if (bXMirr) {
891 Point aRef2(aRef1);
892 aRef2.AdjustY( 1 );
893 mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2);
895 if (bYMirr) {
896 Point aRef2(aRef1);
897 aRef2.AdjustX( 1 );
898 mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2);
904 mpSdrPathDragData.reset();
906 return true;
909 OUString ImpPathForDragAndCreate::getSpecialDragComment(const SdrDragStat& rDrag) const
911 OUString aStr;
912 const SdrHdl* pHdl = rDrag.GetHdl();
913 const bool bCreateComment(rDrag.GetView() && &mrSdrPathObject == rDrag.GetView()->GetCreateObj());
915 if(bCreateComment && rDrag.GetUser())
917 // #i103058# re-add old creation comment mode
918 const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser());
919 const SdrObjKind eOriginalKind(meObjectKind);
920 mrSdrPathObject.meKind = pU->eCurrentKind;
921 aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewCreateObj);
922 mrSdrPathObject.meKind = eOriginalKind;
924 Point aPrev(rDrag.GetPrev());
925 Point aNow(rDrag.GetNow());
927 if(pU->bLine)
928 aNow = pU->aLineEnd;
930 aNow -= aPrev;
931 aStr += " (";
933 if(pU->bCircle)
935 aStr += SdrModel::GetAngleString(abs(pU->nCircRelAngle))
936 + " r="
937 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(pU->nCircRadius, true);
940 aStr += "dx="
941 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X(), true)
942 + " dy="
943 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y(), true);
945 if(!IsFreeHand(meObjectKind))
947 sal_Int32 nLen(GetLen(aNow));
948 Degree100 nAngle(GetAngle(aNow));
949 aStr += " l="
950 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
951 + " "
952 + SdrModel::GetAngleString(nAngle);
955 aStr += ")";
957 else if(!pHdl)
959 // #i103058# fallback when no model and/or Handle, both needed
960 // for else-path
961 aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_DragPathObj);
963 else
965 // #i103058# standard for modification; model and handle needed
966 ImpSdrPathDragData* pDragData = mpSdrPathDragData.get();
968 if(!pDragData)
970 // getSpecialDragComment is also used from create, so fallback to GetUser()
971 // when mpSdrPathDragData is not set
972 pDragData = static_cast<ImpSdrPathDragData*>(rDrag.GetUser());
975 if(!pDragData)
977 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
978 return OUString();
981 if(!pDragData->IsMultiPointDrag() && pDragData->bEliminate)
983 // point of ...
984 aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewMarkedPoint);
986 // delete %O
987 OUString aStr2(SvxResId(STR_EditDelete));
989 // UNICODE: delete point of ...
990 aStr2 = aStr2.replaceFirst("%1", aStr);
992 return aStr2;
995 // dx=0.00 dy=0.00 -- both sides bezier
996 // dx=0.00 dy=0.00 l=0.00 0.00\302\260 -- one bezier/lever on one side, a start, or an ending
997 // dx=0.00 dy=0.00 l=0.00 0.00\302\260 / l=0.00 0.00\302\260 -- in between
998 Point aBeg(rDrag.GetStart());
999 Point aNow(rDrag.GetNow());
1001 aStr.clear();
1002 aStr += "dx="
1003 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X() - aBeg.X(), true)
1004 + " dy="
1005 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y() - aBeg.Y(), true);
1007 if(!pDragData->IsMultiPointDrag())
1009 sal_uInt16 nPntNum(static_cast<sal_uInt16>(pHdl->GetPointNum()));
1010 const XPolygon& rXPoly = aPathPolygon[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPolyNum())];
1011 sal_uInt16 nPointCount(rXPoly.GetPointCount());
1012 bool bClose(IsClosed(meObjectKind));
1014 if(bClose)
1015 nPointCount--;
1017 if(pHdl->IsPlusHdl())
1019 // lever
1020 sal_uInt16 nRef(nPntNum);
1022 if(rXPoly.IsControl(nPntNum + 1))
1023 nRef--;
1024 else
1025 nRef++;
1027 aNow -= rXPoly[nRef];
1029 sal_Int32 nLen(GetLen(aNow));
1030 Degree100 nAngle(GetAngle(aNow));
1031 aStr += " l="
1032 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
1033 + " "
1034 + SdrModel::GetAngleString(nAngle);
1036 else if(nPointCount > 1)
1038 sal_uInt16 nPntMax(nPointCount - 1);
1039 bool bIsClosed(IsClosed(meObjectKind));
1040 bool bPt1(nPntNum > 0);
1041 bool bPt2(nPntNum < nPntMax);
1043 if(bIsClosed && nPointCount > 2)
1045 bPt1 = true;
1046 bPt2 = true;
1049 sal_uInt16 nPt1,nPt2;
1051 if(nPntNum > 0)
1052 nPt1 = nPntNum - 1;
1053 else
1054 nPt1 = nPntMax;
1056 if(nPntNum < nPntMax)
1057 nPt2 = nPntNum + 1;
1058 else
1059 nPt2 = 0;
1061 if(bPt1 && rXPoly.IsControl(nPt1))
1062 bPt1 = false; // don't display
1064 if(bPt2 && rXPoly.IsControl(nPt2))
1065 bPt2 = false; // of bezier data
1067 if(bPt1)
1069 Point aPt(aNow);
1070 aPt -= rXPoly[nPt1];
1072 sal_Int32 nLen(GetLen(aPt));
1073 Degree100 nAngle(GetAngle(aPt));
1074 aStr += " l="
1075 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
1076 + " "
1077 + SdrModel::GetAngleString(nAngle);
1080 if(bPt2)
1082 if(bPt1)
1083 aStr += " / ";
1084 else
1085 aStr += " ";
1087 Point aPt(aNow);
1088 aPt -= rXPoly[nPt2];
1090 sal_Int32 nLen(GetLen(aPt));
1091 Degree100 nAngle(GetAngle(aPt));
1092 aStr += "l="
1093 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
1094 + " "
1095 + SdrModel::GetAngleString(nAngle);
1101 return aStr;
1104 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::getSpecialDragPoly(const SdrDragStat& rDrag) const
1106 if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
1108 OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
1109 return basegfx::B2DPolyPolygon();
1112 XPolyPolygon aRetval;
1114 if(mpSdrPathDragData->IsMultiPointDrag())
1116 aRetval.Insert(mpSdrPathDragData->maMove);
1118 else
1120 const XPolygon& rXP=aPathPolygon[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPolyNum())];
1121 if (rXP.GetPointCount()<=2) {
1122 XPolygon aXPoly(rXP);
1123 aXPoly[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPointNum())]=rDrag.GetNow();
1124 aRetval.Insert(std::move(aXPoly));
1125 return aRetval.getB2DPolyPolygon();
1127 // copy certain data locally to use less code and have faster access times
1128 bool bClosed =mpSdrPathDragData->bClosed ; // closed object?
1129 sal_uInt16 nPointCount = mpSdrPathDragData->nPointCount; // number of points
1130 sal_uInt16 nPnt =mpSdrPathDragData->nPnt ; // number of points in the polygon
1131 bool bBegPnt =mpSdrPathDragData->bBegPnt ; // dragged point is the first point of a Polyline
1132 bool bEndPnt =mpSdrPathDragData->bEndPnt ; // dragged point is the last point of a Polyline
1133 sal_uInt16 nPrevPnt =mpSdrPathDragData->nPrevPnt ; // index of the previous point
1134 sal_uInt16 nNextPnt =mpSdrPathDragData->nNextPnt ; // index of the next point
1135 bool bPrevIsBegPnt =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline
1136 bool bNextIsEndPnt =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline
1137 sal_uInt16 nPrevPrevPnt =mpSdrPathDragData->nPrevPrevPnt ; // index of the point before the previous point
1138 sal_uInt16 nNextNextPnt =mpSdrPathDragData->nNextNextPnt ; // index of the point after the last point
1139 bool bControl =mpSdrPathDragData->bControl ; // point is a control point
1140 bool bIsNextControl =mpSdrPathDragData->bIsNextControl; //point is a control point after a support point
1141 bool bPrevIsControl =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before
1142 bool bNextIsControl =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after
1143 XPolygon aXPoly(mpSdrPathDragData->aXP);
1144 XPolygon aLine1(2);
1145 XPolygon aLine2(2);
1146 XPolygon aLine3(2);
1147 XPolygon aLine4(2);
1148 if (bControl) {
1149 aLine1[1]=mpSdrPathDragData->aXP[nPnt];
1150 if (bIsNextControl) { // is this a control point after the support point?
1151 aLine1[0]=mpSdrPathDragData->aXP[nPrevPnt];
1152 aLine2[0]=mpSdrPathDragData->aXP[nNextNextPnt];
1153 aLine2[1]=mpSdrPathDragData->aXP[nNextPnt];
1154 if (mpSdrPathDragData->aXP.IsSmooth(nPrevPnt) && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
1155 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control);
1156 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal);
1157 // leverage lines for the opposing curve segment
1158 aLine3[0]=mpSdrPathDragData->aXP[nPrevPnt];
1159 aLine3[1]=mpSdrPathDragData->aXP[nPrevPrevPnt];
1160 aLine4[0]=rXP[mpSdrPathDragData->nPrevPrevPnt0-2];
1161 aLine4[1]=rXP[mpSdrPathDragData->nPrevPrevPnt0-1];
1162 } else {
1163 aXPoly.Remove(0,1);
1165 } else { // else this is a control point before a support point
1166 aLine1[0]=mpSdrPathDragData->aXP[nNextPnt];
1167 aLine2[0]=mpSdrPathDragData->aXP[nPrevPrevPnt];
1168 aLine2[1]=mpSdrPathDragData->aXP[nPrevPnt];
1169 if (mpSdrPathDragData->aXP.IsSmooth(nNextPnt) && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
1170 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control);
1171 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal);
1172 // leverage lines for the opposing curve segment
1173 aLine3[0]=mpSdrPathDragData->aXP[nNextPnt];
1174 aLine3[1]=mpSdrPathDragData->aXP[nNextNextPnt];
1175 aLine4[0]=rXP[mpSdrPathDragData->nNextNextPnt0+2];
1176 aLine4[1]=rXP[mpSdrPathDragData->nNextNextPnt0+1];
1177 } else {
1178 aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1181 } else { // else is not a control point
1182 if (mpSdrPathDragData->bEliminate) {
1183 aXPoly.Remove(2,1);
1185 if (bPrevIsControl) aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Normal);
1186 else if (!bBegPnt && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
1187 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control);
1188 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal);
1189 } else {
1190 aXPoly.Remove(0,1);
1191 if (bBegPnt) aXPoly.Remove(0,1);
1193 if (bNextIsControl) aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Normal);
1194 else if (!bEndPnt && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
1195 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control);
1196 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal);
1197 } else {
1198 aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1199 if (bEndPnt) aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1201 if (bClosed) { // "pear problem": 2 lines, 1 curve, everything smoothed, a point between both lines is dragged
1202 if (aXPoly.GetPointCount()>nPointCount && aXPoly.IsControl(1)) {
1203 sal_uInt16 a=aXPoly.GetPointCount();
1204 aXPoly[a-2]=aXPoly[2]; aXPoly.SetFlags(a-2,aXPoly.GetFlags(2));
1205 aXPoly[a-1]=aXPoly[3]; aXPoly.SetFlags(a-1,aXPoly.GetFlags(3));
1206 aXPoly.Remove(0,3);
1210 aRetval.Insert(std::move(aXPoly));
1211 if (aLine1.GetPointCount()>1) aRetval.Insert(std::move(aLine1));
1212 if (aLine2.GetPointCount()>1) aRetval.Insert(std::move(aLine2));
1213 if (aLine3.GetPointCount()>1) aRetval.Insert(std::move(aLine3));
1214 if (aLine4.GetPointCount()>1) aRetval.Insert(std::move(aLine4));
1217 return aRetval.getB2DPolyPolygon();
1220 void ImpPathForDragAndCreate::BegCreate(SdrDragStat& rStat)
1222 bool bFreeHand(IsFreeHand(meObjectKind));
1223 rStat.SetNoSnap(bFreeHand);
1224 rStat.SetOrtho8Possible();
1225 aPathPolygon.Clear();
1226 mbCreating=true;
1227 bool bMakeStartPoint = true;
1228 SdrView* pView=rStat.GetView();
1229 if (pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface() &&
1230 (meObjectKind==SdrObjKind::Polygon || meObjectKind==SdrObjKind::PolyLine || meObjectKind==SdrObjKind::PathLine || meObjectKind==SdrObjKind::PathFill)) {
1231 bMakeStartPoint = false;
1233 aPathPolygon.Insert(XPolygon());
1234 aPathPolygon[0][0]=rStat.GetStart();
1235 if (bMakeStartPoint) {
1236 aPathPolygon[0][1]=rStat.GetNow();
1238 std::unique_ptr<ImpPathCreateUser> pU(new ImpPathCreateUser);
1239 pU->eStartKind=meObjectKind;
1240 pU->eCurrentKind=meObjectKind;
1241 rStat.SetUser(std::move(pU));
1244 bool ImpPathForDragAndCreate::MovCreate(SdrDragStat& rStat)
1246 ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1247 SdrView* pView=rStat.GetView();
1248 XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1249 if (pView!=nullptr && pView->IsCreateMode()) {
1250 // switch to different CreateTool, if appropriate
1251 SdrObjKind nIdent;
1252 SdrInventor nInvent;
1253 pView->TakeCurrentObj(nIdent,nInvent);
1254 if (nInvent==SdrInventor::Default && pU->eCurrentKind != nIdent) {
1255 SdrObjKind eNewKind = nIdent;
1256 switch (eNewKind) {
1257 case SdrObjKind::CircleArc:
1258 case SdrObjKind::CircleOrEllipse:
1259 case SdrObjKind::CircleCut:
1260 case SdrObjKind::CircleSection:
1261 eNewKind=SdrObjKind::CircleArc;
1262 [[fallthrough]];
1263 case SdrObjKind::Rectangle:
1264 case SdrObjKind::Line:
1265 case SdrObjKind::PolyLine:
1266 case SdrObjKind::Polygon:
1267 case SdrObjKind::PathLine:
1268 case SdrObjKind::PathFill:
1269 case SdrObjKind::FreehandLine:
1270 case SdrObjKind::FreehandFill:
1272 pU->eCurrentKind=eNewKind;
1273 pU->bMixedCreate=true;
1274 pU->nBezierStartPoint=rXPoly.GetPointCount();
1275 if (pU->nBezierStartPoint>0) pU->nBezierStartPoint--;
1276 } break;
1277 default: break;
1278 } // switch
1281 sal_uInt16 nCurrentPoint=rXPoly.GetPointCount();
1282 if (aPathPolygon.Count()>1 && rStat.IsMouseDown() && nCurrentPoint<2) {
1283 rXPoly[0]=rStat.GetPos0();
1284 rXPoly[1]=rStat.GetNow();
1285 nCurrentPoint=2;
1287 if (nCurrentPoint==0) {
1288 rXPoly[0]=rStat.GetPos0();
1289 } else nCurrentPoint--;
1290 bool bFreeHand=IsFreeHand(pU->eCurrentKind);
1291 rStat.SetNoSnap(bFreeHand);
1292 rStat.SetOrtho8Possible(pU->eCurrentKind!=SdrObjKind::CircleArc && pU->eCurrentKind!=SdrObjKind::Rectangle && (!pU->bMixedCreate || pU->eCurrentKind!=SdrObjKind::Line));
1293 rXPoly[nCurrentPoint]=rStat.GetNow();
1294 if (!pU->bMixedCreate && pU->eStartKind==SdrObjKind::Line && rXPoly.GetPointCount()>=1) {
1295 Point aPt(rStat.GetStart());
1296 if (pView!=nullptr && pView->IsCreate1stPointAsCenter()) {
1297 aPt+=aPt;
1298 aPt-=rStat.GetNow();
1300 rXPoly[0]=aPt;
1302 OutputDevice* pOut=pView==nullptr ? nullptr : pView->GetFirstOutputDevice();
1303 if (bFreeHand) {
1304 if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint;
1305 if (rStat.IsMouseDown() && nCurrentPoint>0) {
1306 // don't allow two consecutive points to occupy too similar positions
1307 tools::Long nMinDist=1;
1308 if (pView!=nullptr) nMinDist=pView->GetFreeHandMinDistPix();
1309 if (pOut!=nullptr) nMinDist=pOut->PixelToLogic(Size(nMinDist,0)).Width();
1310 if (nMinDist<1) nMinDist=1;
1312 Point aPt0(rXPoly[nCurrentPoint-1]);
1313 Point aPt1(rStat.GetNow());
1314 tools::Long dx=aPt0.X()-aPt1.X(); if (dx<0) dx=-dx;
1315 tools::Long dy=aPt0.Y()-aPt1.Y(); if (dy<0) dy=-dy;
1316 if (dx<nMinDist && dy<nMinDist) return false;
1318 // TODO: the following is copied from EndCreate (with a few smaller modifications)
1319 // and should be combined into a method with the code there.
1321 if (nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) {
1322 rXPoly.PointsToBezier(nCurrentPoint-3);
1323 rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control);
1324 rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control);
1326 if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) {
1327 rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2);
1328 rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth);
1331 rXPoly[nCurrentPoint+1]=rStat.GetNow();
1332 rStat.NextPoint();
1333 } else {
1334 pU->nBezierStartPoint=nCurrentPoint;
1338 pU->ResetFormFlags();
1339 if (IsBezier(pU->eCurrentKind)) {
1340 if (nCurrentPoint>=2) {
1341 pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],rStat.IsMouseDown());
1342 } else if (pU->bBezHasCtrl0) {
1343 pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],pU->aBezControl0-rXPoly[nCurrentPoint-1],rStat.IsMouseDown());
1346 if (pU->eCurrentKind==SdrObjKind::CircleArc && nCurrentPoint>=2) {
1347 pU->CalcCircle(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
1349 if (pU->eCurrentKind==SdrObjKind::Line && nCurrentPoint>=2) {
1350 pU->CalcLine(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
1352 if (pU->eCurrentKind==SdrObjKind::Rectangle && nCurrentPoint>=2) {
1353 pU->CalcRect(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
1356 return true;
1359 bool ImpPathForDragAndCreate::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
1361 ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1362 bool bRet = false;
1363 SdrView* pView=rStat.GetView();
1364 bool bIncomp=pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface();
1365 XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1366 sal_uInt16 nCurrentPoint=rXPoly.GetPointCount()-1;
1367 rXPoly[nCurrentPoint]=rStat.GetNow();
1368 if (!pU->bMixedCreate && pU->eStartKind==SdrObjKind::Line) {
1369 if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd;
1370 bRet = eCmd==SdrCreateCmd::ForceEnd;
1371 if (bRet) {
1372 mbCreating = false;
1373 rStat.SetUser(nullptr);
1375 return bRet;
1378 if (!pU->bMixedCreate && IsFreeHand(pU->eStartKind)) {
1379 if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd;
1380 bRet=eCmd==SdrCreateCmd::ForceEnd;
1381 if (bRet) {
1382 mbCreating=false;
1383 rStat.SetUser(nullptr);
1385 return bRet;
1387 if (eCmd==SdrCreateCmd::NextPoint || eCmd==SdrCreateCmd::NextObject) {
1388 // don't allow two consecutive points to occupy the same position
1389 if (nCurrentPoint==0 || rStat.GetNow()!=rXPoly[nCurrentPoint-1]) {
1390 if (bIncomp) {
1391 if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint;
1392 if (IsBezier(pU->eCurrentKind) && nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) {
1393 rXPoly.PointsToBezier(nCurrentPoint-3);
1394 rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control);
1395 rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control);
1397 if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) {
1398 rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2);
1399 rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth);
1402 } else {
1403 if (nCurrentPoint==1 && IsBezier(pU->eCurrentKind) && !pU->bBezHasCtrl0) {
1404 pU->aBezControl0=rStat.GetNow();
1405 pU->bBezHasCtrl0=true;
1406 nCurrentPoint--;
1408 if (pU->IsFormFlag()) {
1409 sal_uInt16 nPointCount0=rXPoly.GetPointCount();
1410 rXPoly.Remove(nCurrentPoint-1,2); // remove last two points and replace by form
1411 rXPoly.Insert(XPOLY_APPEND,pU->GetFormPoly());
1412 sal_uInt16 nPointCount1=rXPoly.GetPointCount();
1413 for (sal_uInt16 i=nPointCount0+1; i<nPointCount1-1; i++) { // to make BckAction work
1414 if (!rXPoly.IsControl(i)) rStat.NextPoint();
1416 nCurrentPoint=rXPoly.GetPointCount()-1;
1419 nCurrentPoint++;
1420 rXPoly[nCurrentPoint]=rStat.GetNow();
1422 if (eCmd==SdrCreateCmd::NextObject) {
1423 if (rXPoly.GetPointCount()>=2) {
1424 pU->bBezHasCtrl0=false;
1425 // only a singular polygon may be opened, so close this
1426 rXPoly[nCurrentPoint]=rXPoly[0];
1427 XPolygon aXP;
1428 aXP[0]=rStat.GetNow();
1429 aPathPolygon.Insert(std::move(aXP));
1434 sal_uInt16 nPolyCount=aPathPolygon.Count();
1435 if (nPolyCount!=0) {
1436 // delete last point, if necessary
1437 if (eCmd==SdrCreateCmd::ForceEnd) {
1438 XPolygon& rXP=aPathPolygon[nPolyCount-1];
1439 sal_uInt16 nPointCount=rXP.GetPointCount();
1440 if (nPointCount>=2) {
1441 if (!rXP.IsControl(nPointCount-2)) {
1442 if (rXP[nPointCount-1]==rXP[nPointCount-2]) {
1443 rXP.Remove(nPointCount-1,1);
1445 } else {
1446 if (rXP[nPointCount-3]==rXP[nPointCount-2]) {
1447 rXP.Remove(nPointCount-3,3);
1452 for (sal_uInt16 nPolyNum=nPolyCount; nPolyNum>0;) {
1453 nPolyNum--;
1454 XPolygon& rXP=aPathPolygon[nPolyNum];
1455 sal_uInt16 nPointCount=rXP.GetPointCount();
1456 // delete polygons with too few points
1457 if (nPolyNum<nPolyCount-1 || eCmd==SdrCreateCmd::ForceEnd) {
1458 if (nPointCount<2) aPathPolygon.Remove(nPolyNum);
1462 pU->ResetFormFlags();
1463 bRet=eCmd==SdrCreateCmd::ForceEnd;
1464 if (bRet) {
1465 mbCreating=false;
1466 rStat.SetUser(nullptr);
1468 return bRet;
1471 bool ImpPathForDragAndCreate::BckCreate(SdrDragStat const & rStat)
1473 ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1474 if (aPathPolygon.Count()>0) {
1475 XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1476 sal_uInt16 nCurrentPoint=rXPoly.GetPointCount();
1477 if (nCurrentPoint>0) {
1478 nCurrentPoint--;
1479 // make the last part of a bezier curve a line
1480 rXPoly.Remove(nCurrentPoint,1);
1481 if (nCurrentPoint>=3 && rXPoly.IsControl(nCurrentPoint-1)) {
1482 // there should never be a bezier segment at the end, so this is just in case...
1483 rXPoly.Remove(nCurrentPoint-1,1);
1484 if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1);
1487 nCurrentPoint=rXPoly.GetPointCount();
1488 if (nCurrentPoint>=4) { // no bezier segment at the end
1489 nCurrentPoint--;
1490 if (rXPoly.IsControl(nCurrentPoint-1)) {
1491 rXPoly.Remove(nCurrentPoint-1,1);
1492 if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1);
1495 if (rXPoly.GetPointCount()<2) {
1496 aPathPolygon.Remove(aPathPolygon.Count()-1);
1498 if (aPathPolygon.Count()>0) {
1499 XPolygon& rLocalXPoly=aPathPolygon[aPathPolygon.Count()-1];
1500 sal_uInt16 nLocalCurrentPoint=rLocalXPoly.GetPointCount();
1501 if (nLocalCurrentPoint>0) {
1502 nLocalCurrentPoint--;
1503 rLocalXPoly[nLocalCurrentPoint]=rStat.GetNow();
1507 pU->ResetFormFlags();
1508 return aPathPolygon.Count()!=0;
1511 void ImpPathForDragAndCreate::BrkCreate(SdrDragStat& rStat)
1513 aPathPolygon.Clear();
1514 mbCreating=false;
1515 rStat.SetUser(nullptr);
1518 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeObjectPolyPolygon(const SdrDragStat& rDrag) const
1520 basegfx::B2DPolyPolygon aRetval(aPathPolygon.getB2DPolyPolygon());
1521 SdrView* pView = rDrag.GetView();
1523 if(pView && pView->IsUseIncompatiblePathCreateInterface())
1524 return aRetval;
1526 ImpPathCreateUser* pU = static_cast<ImpPathCreateUser*>(rDrag.GetUser());
1527 basegfx::B2DPolygon aNewPolygon(aRetval.count() ? aRetval.getB2DPolygon(aRetval.count() - 1) : basegfx::B2DPolygon());
1529 if(pU->IsFormFlag() && aNewPolygon.count() > 1)
1531 // remove last segment and replace with current
1532 // do not forget to rescue the previous control point which will be lost when
1533 // the point it's associated with is removed
1534 const sal_uInt32 nChangeIndex(aNewPolygon.count() - 2);
1535 const basegfx::B2DPoint aSavedPrevCtrlPoint(aNewPolygon.getPrevControlPoint(nChangeIndex));
1537 aNewPolygon.remove(nChangeIndex, 2);
1538 aNewPolygon.append(pU->GetFormPoly().getB2DPolygon());
1540 if(nChangeIndex < aNewPolygon.count())
1542 // if really something was added, set the saved previous control point to the
1543 // point where it belongs
1544 aNewPolygon.setPrevControlPoint(nChangeIndex, aSavedPrevCtrlPoint);
1548 if(aRetval.count())
1550 aRetval.setB2DPolygon(aRetval.count() - 1, aNewPolygon);
1552 else
1554 aRetval.append(aNewPolygon);
1557 return aRetval;
1560 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeDragPolyPolygon(const SdrDragStat& rDrag)
1562 basegfx::B2DPolyPolygon aRetval;
1563 SdrView* pView = rDrag.GetView();
1565 if(pView && pView->IsUseIncompatiblePathCreateInterface())
1566 return aRetval;
1568 const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser());
1570 if(pU && pU->bBezier && rDrag.IsMouseDown())
1572 // no more XOR, no need for complicated helplines
1573 basegfx::B2DPolygon aHelpline;
1574 aHelpline.append(basegfx::B2DPoint(pU->aBezCtrl2.X(), pU->aBezCtrl2.Y()));
1575 aHelpline.append(basegfx::B2DPoint(pU->aBezEnd.X(), pU->aBezEnd.Y()));
1576 aRetval.append(aHelpline);
1579 return aRetval;
1582 PointerStyle ImpPathForDragAndCreate::GetCreatePointer() const
1584 switch (meObjectKind) {
1585 case SdrObjKind::Line : return PointerStyle::DrawLine;
1586 case SdrObjKind::Polygon : return PointerStyle::DrawPolygon;
1587 case SdrObjKind::PolyLine : return PointerStyle::DrawPolygon;
1588 case SdrObjKind::PathLine: return PointerStyle::DrawBezier;
1589 case SdrObjKind::PathFill: return PointerStyle::DrawBezier;
1590 case SdrObjKind::FreehandLine: return PointerStyle::DrawFreehand;
1591 case SdrObjKind::FreehandFill: return PointerStyle::DrawFreehand;
1592 case SdrObjKind::PathPoly: return PointerStyle::DrawPolygon;
1593 case SdrObjKind::PathPolyLine: return PointerStyle::DrawPolygon;
1594 default: break;
1595 } // switch
1596 return PointerStyle::Cross;
1599 SdrPathObjGeoData::SdrPathObjGeoData()
1600 : meKind(SdrObjKind::NONE)
1604 SdrPathObjGeoData::~SdrPathObjGeoData()
1608 // DrawContact section
1610 std::unique_ptr<sdr::contact::ViewContact> SdrPathObj::CreateObjectSpecificViewContact()
1612 return std::make_unique<sdr::contact::ViewContactOfSdrPathObj>(*this);
1616 SdrPathObj::SdrPathObj(
1617 SdrModel& rSdrModel,
1618 SdrObjKind eNewKind)
1619 : SdrTextObj(rSdrModel),
1620 meKind(eNewKind)
1622 m_bClosedObj = IsClosed();
1625 SdrPathObj::SdrPathObj(SdrModel& rSdrModel, SdrPathObj const & rSource)
1626 : SdrTextObj(rSdrModel, rSource),
1627 meKind(rSource.meKind)
1629 m_bClosedObj = IsClosed();
1630 maPathPolygon = rSource.GetPathPoly();
1633 SdrPathObj::SdrPathObj(
1634 SdrModel& rSdrModel,
1635 SdrObjKind eNewKind,
1636 basegfx::B2DPolyPolygon aPathPoly)
1637 : SdrTextObj(rSdrModel),
1638 maPathPolygon(std::move(aPathPoly)),
1639 meKind(eNewKind)
1641 m_bClosedObj = IsClosed();
1642 ImpForceKind();
1645 SdrPathObj::~SdrPathObj() = default;
1647 static bool lcl_ImpIsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
1649 return (1 == rPolyPolygon.count() && 2 == rPolyPolygon.getB2DPolygon(0).count());
1652 static tools::Rectangle lcl_ImpGetBoundRect(const basegfx::B2DPolyPolygon& rPolyPolygon)
1654 basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
1656 if (aRange.isEmpty())
1657 return tools::Rectangle();
1659 return tools::Rectangle(
1660 basegfx::fround<tools::Long>(aRange.getMinX()), basegfx::fround<tools::Long>(aRange.getMinY()),
1661 basegfx::fround<tools::Long>(aRange.getMaxX()), basegfx::fround<tools::Long>(aRange.getMaxY()));
1664 void SdrPathObj::ImpForceLineAngle()
1666 if(SdrObjKind::Line != meKind || !lcl_ImpIsLine(GetPathPoly()))
1667 return;
1669 const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0));
1670 const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
1671 const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
1672 const Point aPoint0(basegfx::fround<tools::Long>(aB2DPoint0.getX()),
1673 basegfx::fround<tools::Long>(aB2DPoint0.getY()));
1674 const Point aPoint1(basegfx::fround<tools::Long>(aB2DPoint1.getX()),
1675 basegfx::fround<tools::Long>(aB2DPoint1.getY()));
1676 const basegfx::B2DPoint aB2DDelt(aB2DPoint1 - aB2DPoint0);
1677 const Point aDelt(basegfx::fround<tools::Long>(aB2DDelt.getX()),
1678 basegfx::fround<tools::Long>(aB2DDelt.getY()));
1680 maGeo.m_nRotationAngle=GetAngle(aDelt);
1681 maGeo.m_nShearAngle=0_deg100;
1682 maGeo.RecalcSinCos();
1683 maGeo.RecalcTan();
1685 // for SdrTextObj, keep aRect up to date
1686 setRectangle(tools::Rectangle::Normalize(aPoint0, aPoint1));
1689 void SdrPathObj::ImpForceKind()
1691 if (meKind==SdrObjKind::PathPolyLine) meKind=SdrObjKind::PolyLine;
1692 if (meKind==SdrObjKind::PathPoly) meKind=SdrObjKind::Polygon;
1694 if(GetPathPoly().areControlPointsUsed())
1696 switch (meKind)
1698 case SdrObjKind::Line: meKind=SdrObjKind::PathLine; break;
1699 case SdrObjKind::PolyLine: meKind=SdrObjKind::PathLine; break;
1700 case SdrObjKind::Polygon: meKind=SdrObjKind::PathFill; break;
1701 default: break;
1704 else
1706 switch (meKind)
1708 case SdrObjKind::PathLine: meKind=SdrObjKind::PolyLine; break;
1709 case SdrObjKind::FreehandLine: meKind=SdrObjKind::PolyLine; break;
1710 case SdrObjKind::PathFill: meKind=SdrObjKind::Polygon; break;
1711 case SdrObjKind::FreehandFill: meKind=SdrObjKind::Polygon; break;
1712 default: break;
1716 if (meKind==SdrObjKind::Line && !lcl_ImpIsLine(GetPathPoly())) meKind=SdrObjKind::PolyLine;
1717 if (meKind==SdrObjKind::PolyLine && lcl_ImpIsLine(GetPathPoly())) meKind=SdrObjKind::Line;
1719 m_bClosedObj=IsClosed();
1721 if (meKind==SdrObjKind::Line)
1723 ImpForceLineAngle();
1725 else
1727 // #i10659#, for polys with more than 2 points.
1729 // Here i again need to fix something, because when Path-Polys are Copy-Pasted
1730 // between Apps with different measurements (e.g. 100TH_MM and TWIPS) there is
1731 // a scaling loop started from SdrExchangeView::Paste. In itself, this is not
1732 // wrong, but aRect is wrong here and not even updated by RecalcSnapRect(). If
1733 // this is the case, some size needs to be set here in aRect to avoid that the cycle
1734 // through Rect2Poly - Poly2Rect does something badly wrong since that cycle is
1735 // BASED on aRect. That cycle is triggered in SdrTextObj::NbcResize() which is called
1736 // from the local Resize() implementation.
1738 // Basic problem is that the member aRect in SdrTextObj basically is a unrotated
1739 // text rectangle for the text object itself and methods at SdrTextObj do handle it
1740 // in that way. Many draw objects derived from SdrTextObj 'abuse' aRect as SnapRect
1741 // which is basically wrong. To make the SdrText methods which deal with aRect directly
1742 // work it is necessary to always keep aRect updated. This e.g. not done after a Clone()
1743 // command for SdrPathObj. Since adding this update mechanism with #101412# to
1744 // ImpForceLineAngle() for lines was very successful, i add it to where ImpForceLineAngle()
1745 // was called, once here below and once on a 2nd place below.
1747 // #i10659# for SdrTextObj, keep aRect up to date
1748 if(GetPathPoly().count())
1750 setRectangle(lcl_ImpGetBoundRect(GetPathPoly()));
1754 // #i75974# adapt polygon state to object type. This may include a reinterpretation
1755 // of a closed geometry as open one, but with identical first and last point
1756 for(auto& rPolygon : maPathPolygon)
1758 if(IsClosed() != rPolygon.isClosed())
1760 // #i80213# really change polygon geometry; else e.g. the last point which
1761 // needs to be identical with the first one will be missing when opening
1762 // due to OBJ_PATH type
1763 if(rPolygon.isClosed())
1765 basegfx::utils::openWithGeometryChange(rPolygon);
1767 else
1769 basegfx::utils::closeWithGeometryChange(rPolygon);
1775 void SdrPathObj::ImpSetClosed(bool bClose)
1777 if(bClose)
1779 switch (meKind)
1781 case SdrObjKind::Line : meKind=SdrObjKind::Polygon; break;
1782 case SdrObjKind::PolyLine : meKind=SdrObjKind::Polygon; break;
1783 case SdrObjKind::PathLine: meKind=SdrObjKind::PathFill; break;
1784 case SdrObjKind::FreehandLine: meKind=SdrObjKind::FreehandFill; break;
1785 default: break;
1788 m_bClosedObj = true;
1790 else
1792 switch (meKind)
1794 case SdrObjKind::Polygon : meKind=SdrObjKind::PolyLine; break;
1795 case SdrObjKind::PathFill: meKind=SdrObjKind::PathLine; break;
1796 case SdrObjKind::FreehandFill: meKind=SdrObjKind::FreehandLine; break;
1797 default: break;
1800 m_bClosedObj = false;
1803 ImpForceKind();
1806 void SdrPathObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
1808 rInfo.bNoContortion=false;
1810 bool bCanConv = !HasText() || ImpCanConvTextToCurve();
1811 bool bIsPath = IsBezier();
1813 rInfo.bEdgeRadiusAllowed = false;
1814 rInfo.bCanConvToPath = bCanConv && !bIsPath;
1815 rInfo.bCanConvToPoly = bCanConv && bIsPath;
1816 rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary());
1819 SdrObjKind SdrPathObj::GetObjIdentifier() const
1821 return meKind;
1824 rtl::Reference<SdrObject> SdrPathObj::CloneSdrObject(SdrModel& rTargetModel) const
1826 return new SdrPathObj(rTargetModel, *this);
1829 OUString SdrPathObj::TakeObjNameSingul() const
1831 OUString sName;
1833 if(SdrObjKind::Line == meKind)
1835 TranslateId pId(STR_ObjNameSingulLINE);
1837 if(lcl_ImpIsLine(GetPathPoly()))
1839 const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0));
1840 const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
1841 const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
1843 if(aB2DPoint0 != aB2DPoint1)
1845 if(aB2DPoint0.getY() == aB2DPoint1.getY())
1847 pId = STR_ObjNameSingulLINE_Hori;
1849 else if(aB2DPoint0.getX() == aB2DPoint1.getX())
1851 pId = STR_ObjNameSingulLINE_Vert;
1853 else
1855 const double fDx(fabs(aB2DPoint0.getX() - aB2DPoint1.getX()));
1856 const double fDy(fabs(aB2DPoint0.getY() - aB2DPoint1.getY()));
1858 if(fDx == fDy)
1860 pId = STR_ObjNameSingulLINE_Diag;
1866 sName = SvxResId(pId);
1868 else if(SdrObjKind::PolyLine == meKind || SdrObjKind::Polygon == meKind)
1870 const bool bClosed(SdrObjKind::Polygon == meKind);
1871 TranslateId pId;
1873 if(mpDAC && mpDAC->IsCreating())
1875 if(bClosed)
1877 pId = STR_ObjNameSingulPOLY;
1879 else
1881 pId = STR_ObjNameSingulPLIN;
1884 sName = SvxResId(pId);
1886 else
1888 // get point count
1889 sal_uInt32 nPointCount(0);
1891 for(auto const& rPolygon : GetPathPoly())
1893 nPointCount += rPolygon.count();
1896 if(bClosed)
1898 pId = STR_ObjNameSingulPOLY_PointCount;
1900 else
1902 pId = STR_ObjNameSingulPLIN_PointCount;
1905 // #i96537#
1906 sName = SvxResId(pId).replaceFirst("%2", OUString::number(nPointCount));
1909 else
1911 switch (meKind)
1913 case SdrObjKind::PathLine: sName = SvxResId(STR_ObjNameSingulPATHLINE); break;
1914 case SdrObjKind::FreehandLine: sName = SvxResId(STR_ObjNameSingulFREELINE); break;
1915 case SdrObjKind::PathFill: sName = SvxResId(STR_ObjNameSingulPATHFILL); break;
1916 case SdrObjKind::FreehandFill: sName = SvxResId(STR_ObjNameSingulFREEFILL); break;
1917 default: break;
1921 OUString aName(GetName());
1922 if (!aName.isEmpty())
1923 sName += " '" + aName + "'";
1925 return sName;
1928 OUString SdrPathObj::TakeObjNamePlural() const
1930 OUString sName;
1931 switch(meKind)
1933 case SdrObjKind::Line : sName=SvxResId(STR_ObjNamePluralLINE ); break;
1934 case SdrObjKind::PolyLine : sName=SvxResId(STR_ObjNamePluralPLIN ); break;
1935 case SdrObjKind::Polygon : sName=SvxResId(STR_ObjNamePluralPOLY ); break;
1936 case SdrObjKind::PathLine: sName=SvxResId(STR_ObjNamePluralPATHLINE); break;
1937 case SdrObjKind::FreehandLine: sName=SvxResId(STR_ObjNamePluralFREELINE); break;
1938 case SdrObjKind::PathFill: sName=SvxResId(STR_ObjNamePluralPATHFILL); break;
1939 case SdrObjKind::FreehandFill: sName=SvxResId(STR_ObjNamePluralFREEFILL); break;
1940 default: break;
1942 return sName;
1945 basegfx::B2DPolyPolygon SdrPathObj::TakeXorPoly() const
1947 return GetPathPoly();
1950 sal_uInt32 SdrPathObj::GetHdlCount() const
1952 sal_uInt32 nRetval(0);
1954 for(auto const& rPolygon : GetPathPoly())
1956 nRetval += rPolygon.count();
1959 return nRetval;
1962 void SdrPathObj::AddToHdlList(SdrHdlList& rHdlList) const
1964 // keep old stuff to be able to keep old SdrHdl stuff, too
1965 const XPolyPolygon aOldPathPolygon(GetPathPoly());
1966 sal_uInt16 nPolyCnt=aOldPathPolygon.Count();
1967 bool bClosed=IsClosed();
1968 sal_uInt16 nIdx=0;
1970 for (sal_uInt16 i=0; i<nPolyCnt; i++) {
1971 const XPolygon& rXPoly=aOldPathPolygon.GetObject(i);
1972 sal_uInt16 nPntCnt=rXPoly.GetPointCount();
1973 if (bClosed && nPntCnt>1) nPntCnt--;
1975 for (sal_uInt16 j=0; j<nPntCnt; j++) {
1976 if (rXPoly.GetFlags(j)!=PolyFlags::Control) {
1977 const Point& rPnt=rXPoly[j];
1978 std::unique_ptr<SdrHdl> pHdl(new SdrHdl(rPnt,SdrHdlKind::Poly));
1979 pHdl->SetPolyNum(i);
1980 pHdl->SetPointNum(j);
1981 pHdl->Set1PixMore(j==0);
1982 pHdl->SetSourceHdlNum(nIdx);
1983 nIdx++;
1984 rHdlList.AddHdl(std::move(pHdl));
1990 void SdrPathObj::AddToPlusHdlList(SdrHdlList& rHdlList, SdrHdl& rHdl) const
1992 // keep old stuff to be able to keep old SdrHdl stuff, too
1993 const XPolyPolygon aOldPathPolygon(GetPathPoly());
1994 sal_uInt16 nPnt = static_cast<sal_uInt16>(rHdl.GetPointNum());
1995 sal_uInt16 nPolyNum = static_cast<sal_uInt16>(rHdl.GetPolyNum());
1997 if (nPolyNum>=aOldPathPolygon.Count())
1998 return;
2000 const XPolygon& rXPoly = aOldPathPolygon[nPolyNum];
2001 sal_uInt16 nPntMax = rXPoly.GetPointCount();
2003 if (nPntMax<=0)
2004 return;
2005 nPntMax--;
2006 if (nPnt>nPntMax)
2007 return;
2009 // calculate the number of plus points
2010 sal_uInt16 nCnt = 0;
2011 if (rXPoly.GetFlags(nPnt)!=PolyFlags::Control)
2013 if (nPnt==0 && IsClosed())
2014 nPnt=nPntMax;
2015 if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control)
2016 nCnt++;
2017 if (nPnt==nPntMax && IsClosed())
2018 nPnt=0;
2019 if (nPnt<nPntMax && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control)
2020 nCnt++;
2023 // construct the plus points
2024 for (sal_uInt32 nPlusNum = 0; nPlusNum < nCnt; ++nPlusNum)
2026 nPnt = static_cast<sal_uInt16>(rHdl.GetPointNum());
2027 std::unique_ptr<SdrHdl> pHdl(new SdrHdlBezWgt(&rHdl));
2028 pHdl->SetPolyNum(rHdl.GetPolyNum());
2030 if (nPnt==0 && IsClosed())
2031 nPnt=nPntMax;
2032 if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control && nPlusNum==0)
2034 pHdl->SetPos(rXPoly[nPnt-1]);
2035 pHdl->SetPointNum(nPnt-1);
2037 else
2039 if (nPnt==nPntMax && IsClosed())
2040 nPnt=0;
2041 if (nPnt<rXPoly.GetPointCount()-1 && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control)
2043 pHdl->SetPos(rXPoly[nPnt+1]);
2044 pHdl->SetPointNum(nPnt+1);
2048 pHdl->SetSourceHdlNum(rHdl.GetSourceHdlNum());
2049 pHdl->SetPlusHdl(true);
2050 rHdlList.AddHdl(std::move(pHdl));
2054 // tdf#123321: Make sure that SdrPathObj (e.g. line) has big enough extent for
2055 // visibility. This is realised by ensuring GetLogicRect() is the same as
2056 // GetSnapRect() for the SdrPathObj. Other SdrTextObj objects like
2057 // SdrObjCustomShape will still use a different version of this method that
2058 // does not consider the rotation. Otherwise, the rotated SdrObjCustomShape
2059 // would become mistakenly larger after save and reload (tdf#91687).
2060 // The invocation of the GetLogicRect() method that caused tdf#123321 was in
2061 // PlcDrawObj::WritePlc().
2062 const tools::Rectangle &SdrPathObj::GetLogicRect() const
2064 return GetSnapRect();
2067 // dragging
2069 bool SdrPathObj::hasSpecialDrag() const
2071 return true;
2074 bool SdrPathObj::beginSpecialDrag(SdrDragStat& rDrag) const
2076 ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2078 return aDragAndCreate.beginPathDrag(rDrag);
2081 bool SdrPathObj::applySpecialDrag(SdrDragStat& rDrag)
2083 ImpPathForDragAndCreate aDragAndCreate(*this);
2084 bool bRetval(aDragAndCreate.beginPathDrag(rDrag));
2086 if(bRetval)
2088 bRetval = aDragAndCreate.movePathDrag(rDrag);
2091 if(bRetval)
2093 bRetval = aDragAndCreate.endPathDrag(rDrag);
2096 if(bRetval)
2098 NbcSetPathPoly(aDragAndCreate.getModifiedPolyPolygon());
2101 return bRetval;
2104 OUString SdrPathObj::getSpecialDragComment(const SdrDragStat& rDrag) const
2106 OUString aRetval;
2108 if(mpDAC)
2110 // #i103058# also get a comment when in creation
2111 const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj());
2113 if(bCreateComment)
2115 aRetval = mpDAC->getSpecialDragComment(rDrag);
2118 else
2120 ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2121 bool bDidWork(aDragAndCreate.beginPathDrag(rDrag));
2123 if(bDidWork)
2125 aRetval = aDragAndCreate.getSpecialDragComment(rDrag);
2129 return aRetval;
2132 basegfx::B2DPolyPolygon SdrPathObj::getSpecialDragPoly(const SdrDragStat& rDrag) const
2134 basegfx::B2DPolyPolygon aRetval;
2135 ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2136 bool bDidWork(aDragAndCreate.beginPathDrag(rDrag));
2138 if(bDidWork)
2140 aRetval = aDragAndCreate.getSpecialDragPoly(rDrag);
2143 return aRetval;
2146 // creation
2148 bool SdrPathObj::BegCreate(SdrDragStat& rStat)
2150 mpDAC.reset();
2151 impGetDAC().BegCreate(rStat);
2152 return true;
2155 bool SdrPathObj::MovCreate(SdrDragStat& rStat)
2157 return impGetDAC().MovCreate(rStat);
2160 bool SdrPathObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
2162 bool bRetval(impGetDAC().EndCreate(rStat, eCmd));
2164 if(bRetval && mpDAC)
2166 SetPathPoly(mpDAC->getModifiedPolyPolygon());
2168 // #i75974# Check for AutoClose feature. Moved here from ImpPathForDragAndCreate::EndCreate
2169 // to be able to use the type-changing ImpSetClosed method
2170 if(!IsClosedObj())
2172 SdrView* pView = rStat.GetView();
2174 if(pView && !pView->IsUseIncompatiblePathCreateInterface())
2176 OutputDevice* pOut = pView->GetFirstOutputDevice();
2178 if(pOut)
2180 if(GetPathPoly().count())
2182 const basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(0));
2184 if(aCandidate.count() > 2)
2186 // check distance of first and last point
2187 const sal_Int32 nCloseDist(pOut->PixelToLogic(Size(pView->GetAutoCloseDistPix(), 0)).Width());
2188 const basegfx::B2DVector aDistVector(aCandidate.getB2DPoint(aCandidate.count() - 1) - aCandidate.getB2DPoint(0));
2190 if(aDistVector.getLength() <= static_cast<double>(nCloseDist))
2192 // close it
2193 ImpSetClosed(true);
2201 mpDAC.reset();
2204 return bRetval;
2207 bool SdrPathObj::BckCreate(SdrDragStat& rStat)
2209 return impGetDAC().BckCreate(rStat);
2212 void SdrPathObj::BrkCreate(SdrDragStat& rStat)
2214 impGetDAC().BrkCreate(rStat);
2215 mpDAC.reset();
2218 // polygons
2220 basegfx::B2DPolyPolygon SdrPathObj::TakeCreatePoly(const SdrDragStat& rDrag) const
2222 basegfx::B2DPolyPolygon aRetval;
2224 if(mpDAC)
2226 aRetval = mpDAC->TakeObjectPolyPolygon(rDrag);
2227 aRetval.append(ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag));
2230 return aRetval;
2233 // during drag or create, allow accessing the so-far created/modified polyPolygon
2234 basegfx::B2DPolyPolygon SdrPathObj::getObjectPolyPolygon(const SdrDragStat& rDrag) const
2236 basegfx::B2DPolyPolygon aRetval;
2238 if(mpDAC)
2240 aRetval = mpDAC->TakeObjectPolyPolygon(rDrag);
2243 return aRetval;
2246 basegfx::B2DPolyPolygon SdrPathObj::getDragPolyPolygon(const SdrDragStat& rDrag) const
2248 basegfx::B2DPolyPolygon aRetval;
2250 if(mpDAC)
2252 aRetval = ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag);
2255 return aRetval;
2258 PointerStyle SdrPathObj::GetCreatePointer() const
2260 return impGetDAC().GetCreatePointer();
2263 void SdrPathObj::NbcMove(const Size& rSiz)
2265 maPathPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(rSiz.Width(), rSiz.Height()));
2267 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2268 SdrTextObj::NbcMove(rSiz);
2271 void SdrPathObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
2273 const double fResizeX(xFact);
2274 const double fResizeY(yFact);
2276 if(basegfx::fTools::equal(fResizeX, 1.0) && basegfx::fTools::equal(fResizeY, 1.0))
2278 // tdf#106792 avoid numerical unprecisions: If both scale factors are 1.0, do not
2279 // manipulate at all - that may change maGeo rapidly (and wrongly) in
2280 // SdrTextObj::NbcResize. Combined with the UNO API trying to not 'apply'
2281 // a rotation but to manipulate the existing one, this is fatal. So just
2282 // avoid this error as long as we have to deal with imprecise geometry
2283 // manipulations
2284 return;
2287 basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRef.X(), -rRef.Y()));
2288 aTrans = basegfx::utils::createScaleTranslateB2DHomMatrix(
2289 double(xFact), double(yFact), rRef.X(), rRef.Y()) * aTrans;
2290 maPathPolygon.transform(aTrans);
2292 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2293 SdrTextObj::NbcResize(rRef,xFact,yFact);
2296 void SdrPathObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs)
2298 // Thank JOE, the angles are defined mirrored to the mathematical meanings
2299 const basegfx::B2DHomMatrix aTrans(
2300 basegfx::utils::createRotateAroundPoint(rRef.X(), rRef.Y(), -toRadians(nAngle)));
2301 maPathPolygon.transform(aTrans);
2303 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2304 SdrTextObj::NbcRotate(rRef,nAngle,sn,cs);
2307 void SdrPathObj::NbcShear(const Point& rRefPnt, Degree100 nAngle, double fTan, bool bVShear)
2309 basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt.X(), -rRefPnt.Y()));
2311 if(bVShear)
2313 // Thank JOE, the angles are defined mirrored to the mathematical meanings
2314 aTrans.shearY(-fTan);
2316 else
2318 aTrans.shearX(-fTan);
2321 aTrans.translate(rRefPnt.X(), rRefPnt.Y());
2322 maPathPolygon.transform(aTrans);
2324 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2325 SdrTextObj::NbcShear(rRefPnt,nAngle,fTan,bVShear);
2328 void SdrPathObj::NbcMirror(const Point& rRefPnt1, const Point& rRefPnt2)
2330 const double fDiffX(rRefPnt2.X() - rRefPnt1.X());
2331 const double fDiffY(rRefPnt2.Y() - rRefPnt1.Y());
2332 const double fRot(atan2(fDiffY, fDiffX));
2333 basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt1.X(), -rRefPnt1.Y()));
2334 aTrans.rotate(-fRot);
2335 aTrans.scale(1.0, -1.0);
2336 aTrans.rotate(fRot);
2337 aTrans.translate(rRefPnt1.X(), rRefPnt1.Y());
2338 maPathPolygon.transform(aTrans);
2340 // Do Joe's special handling for lines when mirroring, too
2341 ImpForceKind();
2343 // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2344 SdrTextObj::NbcMirror(rRefPnt1,rRefPnt2);
2347 void SdrPathObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const
2349 if(!maGeo.m_nRotationAngle)
2351 rRect = GetSnapRect();
2353 else
2355 XPolyPolygon aXPP(GetPathPoly());
2356 RotateXPoly(aXPP,Point(),-maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle);
2357 rRect=aXPP.GetBoundRect();
2358 Point aTmp(rRect.TopLeft());
2359 RotatePoint(aTmp,Point(),maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle);
2360 aTmp-=rRect.TopLeft();
2361 rRect.Move(aTmp.X(),aTmp.Y());
2365 void SdrPathObj::RecalcSnapRect()
2367 if(GetPathPoly().count())
2369 maSnapRect = lcl_ImpGetBoundRect(GetPathPoly());
2373 void SdrPathObj::NbcSetSnapRect(const tools::Rectangle& rRect)
2375 tools::Rectangle aOld(GetSnapRect());
2376 if (aOld.IsEmpty())
2378 Fraction aX(1,1);
2379 Fraction aY(1,1);
2380 NbcResize(aOld.TopLeft(), aX, aY);
2381 NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top()));
2382 return;
2385 // Take empty into account when calculating scale factors
2386 tools::Long nMulX = rRect.IsWidthEmpty() ? 0 : rRect.Right() - rRect.Left();
2388 tools::Long nDivX = aOld.Right() - aOld.Left();
2390 // Take empty into account when calculating scale factors
2391 tools::Long nMulY = rRect.IsHeightEmpty() ? 0 : rRect.Bottom() - rRect.Top();
2393 tools::Long nDivY = aOld.Bottom() - aOld.Top();
2394 if ( nDivX == 0 ) { nMulX = 1; nDivX = 1; }
2395 if ( nDivY == 0 ) { nMulY = 1; nDivY = 1; }
2396 if ( nDivX == nMulX ) { nMulX = 1; nDivX = 1; }
2397 if ( nDivY == nMulY ) { nMulY = 1; nDivY = 1; }
2398 Fraction aX(nMulX,nDivX);
2399 Fraction aY(nMulY,nDivY);
2400 NbcResize(aOld.TopLeft(), aX, aY);
2401 NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top()));
2404 sal_uInt32 SdrPathObj::GetSnapPointCount() const
2406 return GetHdlCount();
2409 Point SdrPathObj::GetSnapPoint(sal_uInt32 nSnapPnt) const
2411 sal_uInt32 nPoly,nPnt;
2412 if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nSnapPnt, nPoly, nPnt))
2414 SAL_WARN("svx", "SdrPathObj::GetSnapPoint: Point nSnapPnt does not exist.");
2417 const basegfx::B2DPoint aB2DPoint(GetPathPoly().getB2DPolygon(nPoly).getB2DPoint(nPnt));
2418 return Point(basegfx::fround<tools::Long>(aB2DPoint.getX()),
2419 basegfx::fround<tools::Long>(aB2DPoint.getY()));
2422 bool SdrPathObj::IsPolyObj() const
2424 return true;
2427 sal_uInt32 SdrPathObj::GetPointCount() const
2429 sal_uInt32 nRetval(0);
2431 for(auto const& rPolygon : GetPathPoly())
2433 nRetval += rPolygon.count();
2436 return nRetval;
2439 Point SdrPathObj::GetPoint(sal_uInt32 nHdlNum) const
2441 Point aRetval;
2442 sal_uInt32 nPoly,nPnt;
2444 if(PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt))
2446 const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(nPoly));
2447 const basegfx::B2DPoint aPoint(aPoly.getB2DPoint(nPnt));
2448 aRetval = Point(basegfx::fround<tools::Long>(aPoint.getX()),
2449 basegfx::fround<tools::Long>(aPoint.getY()));
2452 return aRetval;
2455 void SdrPathObj::NbcSetPoint(const Point& rPnt, sal_uInt32 nHdlNum)
2457 sal_uInt32 nPoly,nPnt;
2459 if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt))
2460 return;
2462 basegfx::B2DPolygon aNewPolygon(GetPathPoly().getB2DPolygon(nPoly));
2463 aNewPolygon.setB2DPoint(nPnt, basegfx::B2DPoint(rPnt.X(), rPnt.Y()));
2464 maPathPolygon.setB2DPolygon(nPoly, aNewPolygon);
2466 if(meKind==SdrObjKind::Line)
2468 ImpForceLineAngle();
2470 else
2472 if(GetPathPoly().count())
2474 // #i10659# for SdrTextObj, keep aRect up to date
2475 setRectangle(lcl_ImpGetBoundRect(GetPathPoly()));
2479 SetBoundAndSnapRectsDirty();
2482 sal_uInt32 SdrPathObj::NbcInsPointOld(const Point& rPos, bool bNewObj)
2484 sal_uInt32 nNewHdl;
2486 if(bNewObj)
2488 nNewHdl = NbcInsPoint(rPos, true);
2490 else
2492 // look for smallest distance data
2493 const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y());
2494 sal_uInt32 nSmallestPolyIndex(0);
2495 sal_uInt32 nSmallestEdgeIndex(0);
2496 double fSmallestCut;
2497 basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut);
2499 nNewHdl = NbcInsPoint(rPos, false);
2502 ImpForceKind();
2503 return nNewHdl;
2506 sal_uInt32 SdrPathObj::NbcInsPoint(const Point& rPos, bool bNewObj)
2508 sal_uInt32 nNewHdl;
2510 if(bNewObj)
2512 basegfx::B2DPolygon aNewPoly;
2513 const basegfx::B2DPoint aPoint(rPos.X(), rPos.Y());
2514 aNewPoly.append(aPoint);
2515 aNewPoly.setClosed(IsClosed());
2516 maPathPolygon.append(aNewPoly);
2517 SetBoundAndSnapRectsDirty();
2518 nNewHdl = GetHdlCount();
2520 else
2522 // look for smallest distance data
2523 const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y());
2524 sal_uInt32 nSmallestPolyIndex(0);
2525 sal_uInt32 nSmallestEdgeIndex(0);
2526 double fSmallestCut;
2527 basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut);
2528 basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(nSmallestPolyIndex));
2529 const bool bBefore(!aCandidate.isClosed() && 0 == nSmallestEdgeIndex && 0.0 == fSmallestCut);
2530 const bool bAfter(!aCandidate.isClosed() && aCandidate.count() == nSmallestEdgeIndex + 2 && 1.0 == fSmallestCut);
2532 if(bBefore)
2534 // before first point
2535 aCandidate.insert(0, aTestPoint);
2537 if(aCandidate.areControlPointsUsed())
2539 if(aCandidate.isNextControlPointUsed(1))
2541 aCandidate.setNextControlPoint(0, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (1.0 / 3.0)));
2542 aCandidate.setPrevControlPoint(1, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (2.0 / 3.0)));
2546 nNewHdl = 0;
2548 else if(bAfter)
2550 // after last point
2551 aCandidate.append(aTestPoint);
2553 if(aCandidate.areControlPointsUsed())
2555 if(aCandidate.isPrevControlPointUsed(aCandidate.count() - 2))
2557 aCandidate.setNextControlPoint(aCandidate.count() - 2, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (1.0 / 3.0)));
2558 aCandidate.setPrevControlPoint(aCandidate.count() - 1, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (2.0 / 3.0)));
2562 nNewHdl = aCandidate.count() - 1;
2564 else
2566 // in between
2567 bool bSegmentSplit(false);
2568 const sal_uInt32 nNextIndex((nSmallestEdgeIndex + 1) % aCandidate.count());
2570 if(aCandidate.areControlPointsUsed())
2572 if(aCandidate.isNextControlPointUsed(nSmallestEdgeIndex) || aCandidate.isPrevControlPointUsed(nNextIndex))
2574 bSegmentSplit = true;
2578 if(bSegmentSplit)
2580 // rebuild original segment to get the split data
2581 basegfx::B2DCubicBezier aBezierA, aBezierB;
2582 const basegfx::B2DCubicBezier aBezier(
2583 aCandidate.getB2DPoint(nSmallestEdgeIndex),
2584 aCandidate.getNextControlPoint(nSmallestEdgeIndex),
2585 aCandidate.getPrevControlPoint(nNextIndex),
2586 aCandidate.getB2DPoint(nNextIndex));
2588 // split and insert hit point
2589 aBezier.split(fSmallestCut, &aBezierA, &aBezierB);
2590 aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint);
2592 // since we inserted hit point and not split point, we need to add an offset
2593 // to the control points to get the C1 continuity we want to achieve
2594 const basegfx::B2DVector aOffset(aTestPoint - aBezierA.getEndPoint());
2595 aCandidate.setNextControlPoint(nSmallestEdgeIndex, aBezierA.getControlPointA() + aOffset);
2596 aCandidate.setPrevControlPoint(nSmallestEdgeIndex + 1, aBezierA.getControlPointB() + aOffset);
2597 aCandidate.setNextControlPoint(nSmallestEdgeIndex + 1, aBezierB.getControlPointA() + aOffset);
2598 aCandidate.setPrevControlPoint((nSmallestEdgeIndex + 2) % aCandidate.count(), aBezierB.getControlPointB() + aOffset);
2600 else
2602 aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint);
2605 nNewHdl = nSmallestEdgeIndex + 1;
2608 maPathPolygon.setB2DPolygon(nSmallestPolyIndex, aCandidate);
2610 // create old polygon index from it
2611 for(sal_uInt32 a(0); a < nSmallestPolyIndex; a++)
2613 nNewHdl += GetPathPoly().getB2DPolygon(a).count();
2617 ImpForceKind();
2618 return nNewHdl;
2621 rtl::Reference<SdrPathObj> SdrPathObj::RipPoint(sal_uInt32 nHdlNum, sal_uInt32& rNewPt0Index)
2623 rtl::Reference<SdrPathObj> pNewObj;
2624 const basegfx::B2DPolyPolygon aLocalPolyPolygon(GetPathPoly());
2625 sal_uInt32 nPoly, nPnt;
2627 if(PolyPolygonEditor::GetRelativePolyPoint(aLocalPolyPolygon, nHdlNum, nPoly, nPnt))
2629 if(0 == nPoly)
2631 const basegfx::B2DPolygon& aCandidate(aLocalPolyPolygon.getB2DPolygon(nPoly));
2632 const sal_uInt32 nPointCount(aCandidate.count());
2634 if(nPointCount)
2636 if(IsClosed())
2638 // when closed, RipPoint means to open the polygon at the selected point. To
2639 // be able to do that, it is necessary to make the selected point the first one
2640 basegfx::B2DPolygon aNewPolygon(basegfx::utils::makeStartPoint(aCandidate, nPnt));
2641 SetPathPoly(basegfx::B2DPolyPolygon(aNewPolygon));
2642 ToggleClosed();
2644 // give back new position of old start point (historical reasons)
2645 rNewPt0Index = (nPointCount - nPnt) % nPointCount;
2647 else
2649 if(nPointCount >= 3 && nPnt != 0 && nPnt + 1 < nPointCount)
2651 // split in two objects at point nPnt
2652 basegfx::B2DPolygon aSplitPolyA(aCandidate, 0, nPnt + 1);
2653 SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyA));
2655 pNewObj = SdrObject::Clone(*this, getSdrModelFromSdrObject());
2656 basegfx::B2DPolygon aSplitPolyB(aCandidate, nPnt, nPointCount - nPnt);
2657 pNewObj->SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyB));
2664 return pNewObj;
2667 rtl::Reference<SdrObject> SdrPathObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const
2669 // #i89784# check for FontWork with activated HideContour
2670 const drawinglayer::attribute::SdrTextAttribute aText(
2671 drawinglayer::primitive2d::createNewSdrTextAttribute(GetObjectItemSet(), *getText(0)));
2672 const bool bHideContour(
2673 !aText.isDefault() && !aText.getSdrFormTextAttribute().isDefault() && aText.isHideContour());
2675 rtl::Reference<SdrObject> pRet;
2677 if(!bHideContour)
2679 rtl::Reference<SdrPathObj> pPath = ImpConvertMakeObj(GetPathPoly(), IsClosed(), bBezier);
2681 if(pPath->GetPathPoly().areControlPointsUsed())
2683 if(!bBezier)
2685 // reduce all bezier curves
2686 pPath->SetPathPoly(basegfx::utils::adaptiveSubdivideByAngle(pPath->GetPathPoly()));
2689 else
2691 if(bBezier)
2693 // create bezier curves
2694 pPath->SetPathPoly(basegfx::utils::expandToCurve(pPath->GetPathPoly()));
2697 pRet = std::move(pPath);
2700 if(bAddText)
2702 pRet = ImpConvertAddText(std::move(pRet), bBezier);
2705 return pRet;
2708 std::unique_ptr<SdrObjGeoData> SdrPathObj::NewGeoData() const
2710 return std::make_unique<SdrPathObjGeoData>();
2713 void SdrPathObj::SaveGeoData(SdrObjGeoData& rGeo) const
2715 SdrTextObj::SaveGeoData(rGeo);
2716 SdrPathObjGeoData& rPGeo = static_cast<SdrPathObjGeoData&>( rGeo );
2717 rPGeo.maPathPolygon=GetPathPoly();
2718 rPGeo.meKind=meKind;
2721 void SdrPathObj::RestoreGeoData(const SdrObjGeoData& rGeo)
2723 SdrTextObj::RestoreGeoData(rGeo);
2724 const SdrPathObjGeoData& rPGeo=static_cast<const SdrPathObjGeoData&>(rGeo);
2725 maPathPolygon=rPGeo.maPathPolygon;
2726 meKind=rPGeo.meKind;
2727 ImpForceKind(); // to set bClosed (among other things)
2730 void SdrPathObj::NbcSetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly)
2732 if(GetPathPoly() != rPathPoly)
2734 maPathPolygon=rPathPoly;
2735 ImpForceKind();
2736 SetBoundAndSnapRectsDirty();
2740 void SdrPathObj::SetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly)
2742 if(GetPathPoly() != rPathPoly)
2744 tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect();
2745 NbcSetPathPoly(rPathPoly);
2746 SetChanged();
2747 BroadcastObjectChange();
2748 SendUserCall(SdrUserCallType::Resize,aBoundRect0);
2752 void SdrPathObj::ToggleClosed()
2754 tools::Rectangle aBoundRect0;
2755 if(m_pUserCall != nullptr)
2756 aBoundRect0 = GetLastBoundRect();
2757 ImpSetClosed(!IsClosed()); // set new ObjKind
2758 ImpForceKind(); // because we want Line -> Poly -> PolyLine instead of Line -> Poly -> Line
2759 SetBoundAndSnapRectsDirty();
2760 SetChanged();
2761 BroadcastObjectChange();
2762 SendUserCall(SdrUserCallType::Resize, aBoundRect0);
2765 ImpPathForDragAndCreate& SdrPathObj::impGetDAC() const
2767 if(!mpDAC)
2769 const_cast<SdrPathObj*>(this)->mpDAC.reset(new ImpPathForDragAndCreate(*const_cast<SdrPathObj*>(this)));
2772 return *mpDAC;
2776 // transformation interface for StarOfficeAPI. This implements support for
2777 // homogeneous 3x3 matrices containing the transformation of the SdrObject. At the
2778 // moment it contains a shearX, rotation and translation, but for setting all linear
2779 // transforms like Scale, ShearX, ShearY, Rotate and Translate are supported.
2782 // gets base transformation and rectangle of object. If it's an SdrPathObj it fills the PolyPolygon
2783 // with the base geometry and returns TRUE. Otherwise it returns FALSE.
2784 bool SdrPathObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& rPolyPolygon) const
2786 double fRotate(0.0);
2787 double fShearX(0.0);
2788 basegfx::B2DTuple aScale(1.0, 1.0);
2789 basegfx::B2DTuple aTranslate(0.0, 0.0);
2791 if(GetPathPoly().count())
2793 // copy geometry
2794 basegfx::B2DHomMatrix aMoveToZeroMatrix;
2795 rPolyPolygon = GetPathPoly();
2797 if(SdrObjKind::Line == meKind)
2799 // ignore shear and rotate, just use scale and translate
2800 OSL_ENSURE(GetPathPoly().count() > 0 && GetPathPoly().getB2DPolygon(0).count() > 1, "OBJ_LINE with too few polygons (!)");
2801 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2802 // itself, else this method will no longer return the full polygon information (curve will
2803 // be lost)
2804 const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2805 aScale = aPolyRangeNoCurve.getRange();
2806 aTranslate = aPolyRangeNoCurve.getMinimum();
2808 // define matrix for move polygon to zero point
2809 aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY());
2811 else
2813 if(maGeo.m_nShearAngle || maGeo.m_nRotationAngle)
2815 // get rotate and shear in drawingLayer notation
2816 fRotate = toRadians(maGeo.m_nRotationAngle);
2817 fShearX = toRadians(maGeo.m_nShearAngle);
2819 // build mathematically correct (negative shear and rotate) object transform
2820 // containing shear and rotate to extract unsheared, unrotated polygon
2821 basegfx::B2DHomMatrix aObjectMatrix;
2822 aObjectMatrix.shearX(-maGeo.mfTanShearAngle);
2823 aObjectMatrix.rotate(toRadians(36000_deg100 - maGeo.m_nRotationAngle));
2825 // create inverse from it and back-transform polygon
2826 basegfx::B2DHomMatrix aInvObjectMatrix(aObjectMatrix);
2827 aInvObjectMatrix.invert();
2828 rPolyPolygon.transform(aInvObjectMatrix);
2830 // get range from unsheared, unrotated polygon and extract scale and translate.
2831 // transform topLeft from it back to transformed state to get original
2832 // topLeft (rotation center)
2833 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2834 // itself, else this method will no longer return the full polygon information (curve will
2835 // be lost)
2836 const basegfx::B2DRange aCorrectedRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2837 aTranslate = aObjectMatrix * aCorrectedRangeNoCurve.getMinimum();
2838 aScale = aCorrectedRangeNoCurve.getRange();
2840 // define matrix for move polygon to zero point
2841 // #i112280# Added missing minus for Y-Translation
2842 aMoveToZeroMatrix.translate(-aCorrectedRangeNoCurve.getMinX(), -aCorrectedRangeNoCurve.getMinY());
2844 else
2846 // get scale and translate from unsheared, unrotated polygon
2847 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2848 // itself, else this method will no longer return the full polygon information (curve will
2849 // be lost)
2850 const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2851 if (!aPolyRangeNoCurve.isEmpty())
2853 aScale = aPolyRangeNoCurve.getRange();
2854 aTranslate = aPolyRangeNoCurve.getMinimum();
2856 // define matrix for move polygon to zero point
2857 aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY());
2862 // move polygon to zero point with pre-defined matrix
2863 rPolyPolygon.transform(aMoveToZeroMatrix);
2866 // position maybe relative to anchorpos, convert
2867 if( getSdrModelFromSdrObject().IsWriter() )
2869 if(GetAnchorPos().X() || GetAnchorPos().Y())
2871 aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
2875 // build return value matrix
2876 rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
2877 aScale,
2878 basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX),
2879 basegfx::fTools::equalZero(fRotate) ? 0.0 : -fRotate,
2880 aTranslate);
2882 return true;
2885 void SdrPathObj::SetHandleScale(bool bHandleScale)
2887 mbHandleScale = bHandleScale;
2890 // Sets the base geometry of the object using infos contained in the homogeneous 3x3 matrix.
2891 // If it's an SdrPathObj it will use the provided geometry information. The Polygon has
2892 // to use (0,0) as upper left and will be scaled to the given size in the matrix.
2893 void SdrPathObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& rPolyPolygon)
2895 // break up matrix
2896 basegfx::B2DTuple aScale;
2897 basegfx::B2DTuple aTranslate;
2898 double fRotate, fShearX;
2899 rMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
2901 // #i75086# Old DrawingLayer (GeoStat and geometry) does not support holding negative scalings
2902 // in X and Y which equal a 180 degree rotation. Recognize it and react accordingly
2903 if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0))
2905 aScale.setX(fabs(aScale.getX()));
2906 aScale.setY(fabs(aScale.getY()));
2907 fRotate = fmod(fRotate + M_PI, 2 * M_PI);
2910 // copy poly
2911 basegfx::B2DPolyPolygon aNewPolyPolygon(rPolyPolygon);
2913 // reset object shear and rotations
2914 maGeo.m_nRotationAngle = 0_deg100;
2915 maGeo.RecalcSinCos();
2916 maGeo.m_nShearAngle = 0_deg100;
2917 maGeo.RecalcTan();
2919 if( getSdrModelFromSdrObject().IsWriter() )
2921 // if anchor is used, make position relative to it
2922 if(GetAnchorPos().X() || GetAnchorPos().Y())
2924 aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
2928 // create transformation for polygon, set values at maGeo direct
2929 basegfx::B2DHomMatrix aTransform;
2931 // #i75086#
2932 // Given polygon is already scaled (for historical reasons), but not mirrored yet.
2933 // Thus, when scale is negative in X or Y, apply the needed mirroring accordingly.
2934 double fScaleX(basegfx::fTools::less(aScale.getX(), 0.0) ? -1.0 : 1.0);
2935 double fScaleY(basegfx::fTools::less(aScale.getY(), 0.0) ? -1.0 : 1.0);
2937 // tdf#98565, tdf#98584. While loading a shape, svg:width and svg:height is used to scale
2938 // the polygon. But draw:transform might introduce additional scaling factors, which need to
2939 // be applied to the polygon too, so aScale cannot be ignored while loading.
2940 // I use "maSnapRect.IsEmpty() && GetPathPoly().count()" to detect this case. Any better
2941 // idea? The behavior in other cases is the same as it was before this fix.
2942 if (maSnapRect.IsEmpty() && GetPathPoly().count() && mbHandleScale)
2944 // In case of a Writer document, the scaling factors were converted to twips. That is not
2945 // correct here, because width and height are already in the points coordinates and aScale
2946 // is no length but only a factor here. Convert back.
2947 if (getSdrModelFromSdrObject().IsWriter())
2949 aScale.setX(o3tl::convert(aScale.getX(), o3tl::Length::twip, o3tl::Length::mm100));
2950 aScale.setY(o3tl::convert(aScale.getY(), o3tl::Length::twip, o3tl::Length::mm100));
2952 fScaleX *= fabs(aScale.getX());
2953 fScaleY *= fabs(aScale.getY());
2956 if (fScaleX != 1.0 || fScaleY != 1.0)
2957 aTransform.scale(fScaleX, fScaleY);
2959 if(!basegfx::fTools::equalZero(fShearX))
2961 aTransform.shearX(tan(-atan(fShearX)));
2962 maGeo.m_nShearAngle = Degree100(basegfx::fround(basegfx::rad2deg<100>(atan(fShearX))));
2963 maGeo.RecalcTan();
2966 if(!basegfx::fTools::equalZero(fRotate))
2968 // #i78696#
2969 // fRotate is mathematically correct for linear transformations, so it's
2970 // the one to use for the geometry change
2971 aTransform.rotate(fRotate);
2973 // #i78696#
2974 // fRotate is mathematically correct, but aGeoStat.nRotationAngle is
2975 // mirrored -> mirror value here
2976 maGeo.m_nRotationAngle = NormAngle36000(Degree100(basegfx::fround(-basegfx::rad2deg<100>(fRotate))));
2977 maGeo.RecalcSinCos();
2980 if(!aTranslate.equalZero())
2982 // #i39529# absolute positioning, so get current position (without control points (!))
2983 const basegfx::B2DRange aCurrentRange(basegfx::utils::getRange(aNewPolyPolygon));
2984 aTransform.translate(aTranslate.getX() - aCurrentRange.getMinX(), aTranslate.getY() - aCurrentRange.getMinY());
2987 // transform polygon and trigger change
2988 aNewPolyPolygon.transform(aTransform);
2989 SetPathPoly(aNewPolyPolygon);
2992 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */