Files
odfstudio/svx/source/svdraw/svdopath.cxx
T
Armin Le Grand (collabora) 5519a3d407 Correct StyleSheet handling when cloning to different Model II
Until now the 'local' StyleSheet got set, but that may
not be the same as the original. It was/gets identified just
by searching by name and type, not comparing content.

What to do? We could...

(a) the Style-Friendly way:
 - check if StyleSheet is identical
  - if not, create a new in-between StyleSheet hosting missing Items
  - if yes, use, done.

(b) the pragmatic/practical way:
 - Just hard-attribute the Items that are detected as different
   similar to what BurnInStyleSheetAttributes does/did, but only
   for a view differing Items

(b) is the most simple for now, but will lead to more hard
attributes. The other possibilities will be better in the
long term. For now, I will use (b).

Note that there are more cases in that VERY complex and grown
stuff emerging from SdDrawDocument::PasteBookmarkAsPage. More
things will need to be fixed, see this as a 1st step.

Needed to adapt postProcessAfterCloneToDifferentModel due to
UT testTdf96206 failing, see comment there for details.

That 1st implementation showed weaknesses with those Items
critical and only on MasterPages, so decided to give up on
that and implement ContentCompare to SdPage::Equals. That
was easier than thought: No parameter needed, only used by
InsertBookmarkAsPage_FindDuplicateLayouts::operator(). Thus,
SdPage::Equals needs no change, just SdrObject::Equals.

Due to still problems with Items corrected in Objects on
MasterPages I will for now exclude this.

Could now isolate remaining problems, these are defaults
and/or PresObj things on MasterPages. Important is that I can
completely correct all Items for all Objects in
postProcessAfterCloneToDifferentModel. I can react to these
false-positives in the ContentCompare in SdrObject::Equals.

Signed-off-by: Armin Le Grand (collabora) <Armin.LeGrand@collabora.com>
Change-Id: I10e17c8d6a893dbe92ab716182f07bafce1f3284
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/6595
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Armin Le Grand <armin.legrand@collabora.com>
(cherry picked from commit 92ffaeac04c5c8586006b026f29ce12fba3674f1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211729
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-10-05 08:52:54 +02:00

2993 lines
112 KiB
C++

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