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