summaryrefslogtreecommitdiffstats
path: root/svx/source/svdraw/svdocirc.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'svx/source/svdraw/svdocirc.cxx')
-rw-r--r--svx/source/svdraw/svdocirc.cxx1154
1 files changed, 1154 insertions, 0 deletions
diff --git a/svx/source/svdraw/svdocirc.cxx b/svx/source/svdraw/svdocirc.cxx
new file mode 100644
index 000000000..0204b2c2e
--- /dev/null
+++ b/svx/source/svdraw/svdocirc.cxx
@@ -0,0 +1,1154 @@
+/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <math.h>
+#include <rtl/ustrbuf.hxx>
+
+#include <svx/dialmgr.hxx>
+#include <svx/strings.hrc>
+
+#include <sdr/contact/viewcontactofsdrcircobj.hxx>
+#include <sdr/properties/circleproperties.hxx>
+#include <svx/svddrag.hxx>
+#include <svx/svdmodel.hxx>
+#include <svx/svdocirc.hxx>
+#include <svx/svdopath.hxx>
+#include <svx/svdtrans.hxx>
+#include <svx/svdview.hxx>
+#include <svx/sxciaitm.hxx>
+#include <sxcikitm.hxx>
+#include <svx/xfillit0.hxx>
+#include <svx/xlineit0.hxx>
+#include <svx/xlnedit.hxx>
+#include <svx/xlnedwit.hxx>
+#include <svx/xlnstit.hxx>
+#include <svx/xlnstwit.hxx>
+#include <svx/xlnwtit.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/ptrstyle.hxx>
+
+using namespace com::sun::star;
+
+static Point GetAnglePnt(const tools::Rectangle& rR, Degree100 nAngle)
+{
+ Point aCenter(rR.Center());
+ tools::Long nWdt=rR.Right()-rR.Left();
+ tools::Long nHgt=rR.Bottom()-rR.Top();
+ tools::Long nMaxRad=(std::max(nWdt,nHgt)+1) /2;
+ double a = toRadians(nAngle);
+ Point aRetval(FRound(cos(a)*nMaxRad),-FRound(sin(a)*nMaxRad));
+ if (nWdt==0) aRetval.setX(0 );
+ if (nHgt==0) aRetval.setY(0 );
+ if (nWdt!=nHgt) {
+ if (nWdt>nHgt) {
+ if (nWdt!=0) {
+ // stop possible overruns for very large objects
+ if (std::abs(nHgt)>32767 || std::abs(aRetval.Y())>32767) {
+ aRetval.setY(BigMulDiv(aRetval.Y(),nHgt,nWdt) );
+ } else {
+ aRetval.setY(aRetval.Y()*nHgt/nWdt );
+ }
+ }
+ } else {
+ if (nHgt!=0) {
+ // stop possible overruns for very large objects
+ if (std::abs(nWdt)>32767 || std::abs(aRetval.X())>32767) {
+ aRetval.setX(BigMulDiv(aRetval.X(),nWdt,nHgt) );
+ } else {
+ aRetval.setX(aRetval.X()*nWdt/nHgt );
+ }
+ }
+ }
+ }
+ aRetval+=aCenter;
+ return aRetval;
+}
+
+
+// BaseProperties section
+
+std::unique_ptr<sdr::properties::BaseProperties> SdrCircObj::CreateObjectSpecificProperties()
+{
+ return std::make_unique<sdr::properties::CircleProperties>(*this);
+}
+
+
+// DrawContact section
+
+std::unique_ptr<sdr::contact::ViewContact> SdrCircObj::CreateObjectSpecificViewContact()
+{
+ return std::make_unique<sdr::contact::ViewContactOfSdrCircObj>(*this);
+}
+
+SdrCircKind ToSdrCircKind(SdrObjKind eKind)
+{
+ switch (eKind)
+ {
+ case SdrObjKind::CircleOrEllipse: return SdrCircKind::Full;
+ case SdrObjKind::CircleSection: return SdrCircKind::Section;
+ case SdrObjKind::CircleArc: return SdrCircKind::Arc;
+ case SdrObjKind::CircleCut: return SdrCircKind::Cut;
+ default: assert(false);
+ }
+ return SdrCircKind::Full;
+}
+
+SdrCircObj::SdrCircObj(
+ SdrModel& rSdrModel,
+ SdrCircKind eNewKind)
+: SdrRectObj(rSdrModel)
+{
+ nStartAngle=0_deg100;
+ nEndAngle=36000_deg100;
+ meCircleKind=eNewKind;
+ m_bClosedObj=eNewKind!=SdrCircKind::Arc;
+}
+
+SdrCircObj::SdrCircObj(SdrModel& rSdrModel, SdrCircObj const & rSource)
+: SdrRectObj(rSdrModel, rSource)
+{
+ meCircleKind = rSource.meCircleKind;
+ nStartAngle = rSource.nStartAngle;
+ nEndAngle = rSource.nEndAngle;
+ m_bClosedObj = rSource.m_bClosedObj;
+}
+
+SdrCircObj::SdrCircObj(
+ SdrModel& rSdrModel,
+ SdrCircKind eNewKind,
+ const tools::Rectangle& rRect)
+: SdrRectObj(rSdrModel, rRect)
+{
+ nStartAngle=0_deg100;
+ nEndAngle=36000_deg100;
+ meCircleKind=eNewKind;
+ m_bClosedObj=eNewKind!=SdrCircKind::Arc;
+}
+
+SdrCircObj::SdrCircObj(
+ SdrModel& rSdrModel,
+ SdrCircKind eNewKind,
+ const tools::Rectangle& rRect,
+ Degree100 nNewStartAngle,
+ Degree100 nNewEndAngle)
+: SdrRectObj(rSdrModel, rRect)
+{
+ Degree100 nAngleDif=nNewEndAngle-nNewStartAngle;
+ nStartAngle=NormAngle36000(nNewStartAngle);
+ nEndAngle=NormAngle36000(nNewEndAngle);
+ if (nAngleDif==36000_deg100) nEndAngle+=nAngleDif; // full circle
+ meCircleKind=eNewKind;
+ m_bClosedObj=eNewKind!=SdrCircKind::Arc;
+}
+
+SdrCircObj::~SdrCircObj()
+{
+}
+
+void SdrCircObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
+{
+ bool bCanConv=!HasText() || ImpCanConvTextToCurve();
+ rInfo.bEdgeRadiusAllowed = false;
+ rInfo.bCanConvToPath=bCanConv;
+ rInfo.bCanConvToPoly=bCanConv;
+ rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary());
+}
+
+SdrObjKind SdrCircObj::GetObjIdentifier() const
+{
+ switch (meCircleKind)
+ {
+ case SdrCircKind::Full: return SdrObjKind::CircleOrEllipse;
+ case SdrCircKind::Section: return SdrObjKind::CircleSection;
+ case SdrCircKind::Cut: return SdrObjKind::CircleCut;
+ case SdrCircKind::Arc: return SdrObjKind::CircleArc;
+ default: assert(false);
+ }
+ return SdrObjKind::CircleOrEllipse;
+}
+
+bool SdrCircObj::PaintNeedsXPolyCirc() const
+{
+ // XPoly is necessary for all rotated ellipse objects, circle and
+ // ellipse segments.
+ // If not WIN, then (for now) also for circle/ellipse segments and circle/
+ // ellipse arcs (for precision)
+ bool bNeed = maGeo.nRotationAngle || maGeo.nShearAngle || meCircleKind == SdrCircKind::Cut;
+ // If not WIN, then for everything except full circle (for now!)
+ if (meCircleKind!=SdrCircKind::Full) bNeed = true;
+
+ const SfxItemSet& rSet = GetObjectItemSet();
+ if(!bNeed)
+ {
+ // XPoly is necessary for everything that isn't LineSolid or LineNone
+ drawing::LineStyle eLine = rSet.Get(XATTR_LINESTYLE).GetValue();
+ bNeed = eLine != drawing::LineStyle_NONE && eLine != drawing::LineStyle_SOLID;
+
+ // XPoly is necessary for thick lines
+ if(!bNeed && eLine != drawing::LineStyle_NONE)
+ bNeed = rSet.Get(XATTR_LINEWIDTH).GetValue() != 0;
+
+ // XPoly is necessary for circle arcs with line ends
+ if(!bNeed && meCircleKind == SdrCircKind::Arc)
+ {
+ // start of the line is here if StartPolygon, StartWidth!=0
+ bNeed=rSet.Get(XATTR_LINESTART).GetLineStartValue().count() != 0 &&
+ rSet.Get(XATTR_LINESTARTWIDTH).GetValue() != 0;
+
+ if(!bNeed)
+ {
+ // end of the line is here if EndPolygon, EndWidth!=0
+ bNeed = rSet.Get(XATTR_LINEEND).GetLineEndValue().count() != 0 &&
+ rSet.Get(XATTR_LINEENDWIDTH).GetValue() != 0;
+ }
+ }
+ }
+
+ // XPoly is necessary if Fill !=None and !=Solid
+ if(!bNeed && meCircleKind != SdrCircKind::Arc)
+ {
+ drawing::FillStyle eFill=rSet.Get(XATTR_FILLSTYLE).GetValue();
+ bNeed = eFill != drawing::FillStyle_NONE && eFill != drawing::FillStyle_SOLID;
+ }
+
+ if(!bNeed && meCircleKind != SdrCircKind::Full && nStartAngle == nEndAngle)
+ bNeed = true; // otherwise we're drawing a full circle
+
+ return bNeed;
+}
+
+basegfx::B2DPolygon SdrCircObj::ImpCalcXPolyCirc(const SdrCircKind eCircleKind, const tools::Rectangle& rRect1, Degree100 nStart, Degree100 nEnd) const
+{
+ const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRect1);
+ basegfx::B2DPolygon aCircPolygon;
+
+ if(SdrCircKind::Full == eCircleKind)
+ {
+ // create full circle. Do not use createPolygonFromEllipse; it's necessary
+ // to get the start point to the bottom of the circle to keep compatible to
+ // old geometry creation
+ aCircPolygon = basegfx::utils::createPolygonFromUnitCircle(1);
+
+ // needs own scaling and translation from unit circle to target size (same as
+ // would be in createPolygonFromEllipse)
+ const basegfx::B2DPoint aCenter(aRange.getCenter());
+ const basegfx::B2DHomMatrix aMatrix(basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aRange.getWidth() / 2.0, aRange.getHeight() / 2.0,
+ aCenter.getX(), aCenter.getY()));
+ aCircPolygon.transform(aMatrix);
+ }
+ else
+ {
+ // mirror start, end for geometry creation since model coordinate system is mirrored in Y
+ const double fStart(toRadians((36000_deg100 - nEnd) % 36000_deg100));
+ const double fEnd(toRadians((36000_deg100 - nStart) % 36000_deg100));
+
+ // create circle segment. This is not closed by default
+ aCircPolygon = basegfx::utils::createPolygonFromEllipseSegment(
+ aRange.getCenter(), aRange.getWidth() / 2.0, aRange.getHeight() / 2.0,
+ fStart, fEnd);
+
+ // check closing states
+ const bool bCloseSegment(SdrCircKind::Arc != eCircleKind);
+ const bool bCloseUsingCenter(SdrCircKind::Section == eCircleKind);
+
+ if(bCloseSegment)
+ {
+ if(bCloseUsingCenter)
+ {
+ // add center point at start (for historical reasons)
+ basegfx::B2DPolygon aSector;
+ aSector.append(aRange.getCenter());
+ aSector.append(aCircPolygon);
+ aCircPolygon = aSector;
+ }
+
+ // close
+ aCircPolygon.setClosed(true);
+ }
+ }
+
+ // #i76950#
+ if (maGeo.nShearAngle || maGeo.nRotationAngle)
+ {
+ // translate top left to (0,0)
+ const basegfx::B2DPoint aTopLeft(aRange.getMinimum());
+ basegfx::B2DHomMatrix aMatrix(basegfx::utils::createTranslateB2DHomMatrix(
+ -aTopLeft.getX(), -aTopLeft.getY()));
+
+ // shear, rotate and back to top left (if needed)
+ aMatrix = basegfx::utils::createShearXRotateTranslateB2DHomMatrix(
+ -maGeo.mfTanShearAngle,
+ maGeo.nRotationAngle ? toRadians(36000_deg100 - maGeo.nRotationAngle) : 0.0,
+ aTopLeft) * aMatrix;
+
+ // apply transformation
+ aCircPolygon.transform(aMatrix);
+ }
+
+ return aCircPolygon;
+}
+
+void SdrCircObj::RecalcXPoly()
+{
+ basegfx::B2DPolygon aPolyCirc(ImpCalcXPolyCirc(meCircleKind, maRect, nStartAngle, nEndAngle));
+ mpXPoly = XPolygon(aPolyCirc);
+}
+
+OUString SdrCircObj::TakeObjNameSingul() const
+{
+ TranslateId pID=STR_ObjNameSingulCIRC;
+ if (maRect.GetWidth() == maRect.GetHeight() && maGeo.nShearAngle==0_deg100)
+ {
+ switch (meCircleKind) {
+ case SdrCircKind::Full: pID=STR_ObjNameSingulCIRC; break;
+ case SdrCircKind::Section: pID=STR_ObjNameSingulSECT; break;
+ case SdrCircKind::Arc: pID=STR_ObjNameSingulCARC; break;
+ case SdrCircKind::Cut: pID=STR_ObjNameSingulCCUT; break;
+ default: break;
+ }
+ } else {
+ switch (meCircleKind) {
+ case SdrCircKind::Full: pID=STR_ObjNameSingulCIRCE; break;
+ case SdrCircKind::Section: pID=STR_ObjNameSingulSECTE; break;
+ case SdrCircKind::Arc: pID=STR_ObjNameSingulCARCE; break;
+ case SdrCircKind::Cut: pID=STR_ObjNameSingulCCUTE; break;
+ default: break;
+ }
+ }
+ OUString sName(SvxResId(pID));
+
+ OUString aName(GetName());
+ if (!aName.isEmpty())
+ sName += " '" + aName + "'";
+ return sName;
+}
+
+OUString SdrCircObj::TakeObjNamePlural() const
+{
+ TranslateId pID=STR_ObjNamePluralCIRC;
+ if (maRect.GetWidth() == maRect.GetHeight() && maGeo.nShearAngle==0_deg100)
+ {
+ switch (meCircleKind) {
+ case SdrCircKind::Full: pID=STR_ObjNamePluralCIRC; break;
+ case SdrCircKind::Section: pID=STR_ObjNamePluralSECT; break;
+ case SdrCircKind::Arc: pID=STR_ObjNamePluralCARC; break;
+ case SdrCircKind::Cut: pID=STR_ObjNamePluralCCUT; break;
+ default: break;
+ }
+ } else {
+ switch (meCircleKind) {
+ case SdrCircKind::Full: pID=STR_ObjNamePluralCIRCE; break;
+ case SdrCircKind::Section: pID=STR_ObjNamePluralSECTE; break;
+ case SdrCircKind::Arc: pID=STR_ObjNamePluralCARCE; break;
+ case SdrCircKind::Cut: pID=STR_ObjNamePluralCCUTE; break;
+ default: break;
+ }
+ }
+ return SvxResId(pID);
+}
+
+SdrCircObj* SdrCircObj::CloneSdrObject(SdrModel& rTargetModel) const
+{
+ return new SdrCircObj(rTargetModel, *this);
+}
+
+basegfx::B2DPolyPolygon SdrCircObj::TakeXorPoly() const
+{
+ const basegfx::B2DPolygon aCircPolygon(ImpCalcXPolyCirc(meCircleKind, maRect, nStartAngle, nEndAngle));
+ return basegfx::B2DPolyPolygon(aCircPolygon);
+}
+
+namespace {
+
+struct ImpCircUser : public SdrDragStatUserData
+{
+ tools::Rectangle aR;
+ Point aCenter;
+ Point aP1;
+ tools::Long nHgt;
+ tools::Long nWdt;
+ Degree100 nStart;
+ Degree100 nEnd;
+
+public:
+ ImpCircUser()
+ : nHgt(0),
+ nWdt(0),
+ nStart(0),
+ nEnd(0)
+ {}
+ void SetCreateParams(SdrDragStat const & rStat);
+};
+
+}
+
+sal_uInt32 SdrCircObj::GetHdlCount() const
+{
+ if(SdrCircKind::Full != meCircleKind)
+ {
+ return 10;
+ }
+ else
+ {
+ return 8;
+ }
+}
+
+void SdrCircObj::AddToHdlList(SdrHdlList& rHdlList) const
+{
+ for (sal_uInt32 nHdlNum=(SdrCircKind::Full==meCircleKind)?2:0; nHdlNum<=9; ++nHdlNum)
+ {
+ Point aPnt;
+ SdrHdlKind eLocalKind(SdrHdlKind::Move);
+ sal_uInt32 nPNum(0);
+
+ switch (nHdlNum)
+ {
+ case 0:
+ aPnt = GetAnglePnt(maRect,nStartAngle);
+ eLocalKind = SdrHdlKind::Circle;
+ nPNum = 1;
+ break;
+ case 1:
+ aPnt = GetAnglePnt(maRect,nEndAngle);
+ eLocalKind = SdrHdlKind::Circle;
+ nPNum = 2;
+ break;
+ case 2:
+ aPnt = maRect.TopLeft();
+ eLocalKind = SdrHdlKind::UpperLeft;
+ break;
+ case 3:
+ aPnt = maRect.TopCenter();
+ eLocalKind = SdrHdlKind::Upper;
+ break;
+ case 4:
+ aPnt = maRect.TopRight();
+ eLocalKind = SdrHdlKind::UpperRight;
+ break;
+ case 5:
+ aPnt = maRect.LeftCenter();
+ eLocalKind = SdrHdlKind::Left;
+ break;
+ case 6:
+ aPnt = maRect.RightCenter();
+ eLocalKind = SdrHdlKind::Right;
+ break;
+ case 7:
+ aPnt = maRect.BottomLeft();
+ eLocalKind = SdrHdlKind::LowerLeft;
+ break;
+ case 8:
+ aPnt = maRect.BottomCenter();
+ eLocalKind = SdrHdlKind::Lower;
+ break;
+ case 9:
+ aPnt = maRect.BottomRight();
+ eLocalKind = SdrHdlKind::LowerRight;
+ break;
+ }
+
+ if (maGeo.nShearAngle)
+ {
+ ShearPoint(aPnt, maRect.TopLeft(), maGeo.mfTanShearAngle);
+ }
+
+ if (maGeo.nRotationAngle)
+ {
+ RotatePoint(aPnt, maRect.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
+ }
+
+ std::unique_ptr<SdrHdl> pH(new SdrHdl(aPnt,eLocalKind));
+ pH->SetPointNum(nPNum);
+ pH->SetObj(const_cast<SdrCircObj*>(this));
+ pH->SetRotationAngle(maGeo.nRotationAngle);
+ rHdlList.AddHdl(std::move(pH));
+ }
+}
+
+
+bool SdrCircObj::hasSpecialDrag() const
+{
+ return true;
+}
+
+bool SdrCircObj::beginSpecialDrag(SdrDragStat& rDrag) const
+{
+ const bool bAngle(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind());
+
+ if(bAngle)
+ {
+ if(1 == rDrag.GetHdl()->GetPointNum() || 2 == rDrag.GetHdl()->GetPointNum())
+ {
+ rDrag.SetNoSnap();
+ }
+
+ return true;
+ }
+
+ return SdrTextObj::beginSpecialDrag(rDrag);
+}
+
+bool SdrCircObj::applySpecialDrag(SdrDragStat& rDrag)
+{
+ const bool bAngle(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind());
+
+ if(bAngle)
+ {
+ Point aPt(rDrag.GetNow());
+
+ if (maGeo.nRotationAngle)
+ RotatePoint(aPt,maRect.TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
+
+ if (maGeo.nShearAngle)
+ ShearPoint(aPt,maRect.TopLeft(), -maGeo.mfTanShearAngle);
+
+ aPt -= maRect.Center();
+
+ tools::Long nWdt = maRect.Right() - maRect.Left();
+ tools::Long nHgt = maRect.Bottom() - maRect.Top();
+
+ if(nWdt>=nHgt)
+ {
+ aPt.setY(BigMulDiv(aPt.Y(),nWdt,nHgt) );
+ }
+ else
+ {
+ aPt.setX(BigMulDiv(aPt.X(),nHgt,nWdt) );
+ }
+
+ Degree100 nAngle=NormAngle36000(GetAngle(aPt));
+
+ if (rDrag.GetView() && rDrag.GetView()->IsAngleSnapEnabled())
+ {
+ Degree100 nSA=rDrag.GetView()->GetSnapAngle();
+
+ if (nSA)
+ {
+ nAngle+=nSA/2_deg100;
+ nAngle/=nSA;
+ nAngle*=nSA;
+ nAngle=NormAngle36000(nAngle);
+ }
+ }
+
+ if(1 == rDrag.GetHdl()->GetPointNum())
+ {
+ nStartAngle = nAngle;
+ }
+ else if(2 == rDrag.GetHdl()->GetPointNum())
+ {
+ nEndAngle = nAngle;
+ }
+
+ SetBoundAndSnapRectsDirty();
+ SetXPolyDirty();
+ ImpSetCircInfoToAttr();
+ SetChanged();
+
+ return true;
+ }
+ else
+ {
+ return SdrTextObj::applySpecialDrag(rDrag);
+ }
+}
+
+OUString SdrCircObj::getSpecialDragComment(const SdrDragStat& rDrag) const
+{
+ const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj());
+
+ if(bCreateComment)
+ {
+ OUStringBuffer aBuf(ImpGetDescriptionStr(STR_ViewCreateObj));
+ const sal_uInt32 nPointCount(rDrag.GetPointCount());
+
+ if(SdrCircKind::Full != meCircleKind && nPointCount > 2)
+ {
+ const ImpCircUser* pU = static_cast<const ImpCircUser*>(rDrag.GetUser());
+ Degree100 nAngle;
+
+ aBuf.append(" (");
+
+ if(3 == nPointCount)
+ {
+ nAngle = pU->nStart;
+ }
+ else
+ {
+ nAngle = pU->nEnd;
+ }
+
+ aBuf.append(SdrModel::GetAngleString(nAngle));
+ aBuf.append(')');
+ }
+
+ return aBuf.makeStringAndClear();
+ }
+ else
+ {
+ const bool bAngle(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind());
+
+ if(bAngle)
+ {
+ const Degree100 nAngle(1 == rDrag.GetHdl()->GetPointNum() ? nStartAngle : nEndAngle);
+
+ return ImpGetDescriptionStr(STR_DragCircAngle) +
+ " (" +
+ SdrModel::GetAngleString(nAngle) +
+ ")";
+ }
+ else
+ {
+ return SdrTextObj::getSpecialDragComment(rDrag);
+ }
+ }
+}
+
+
+void ImpCircUser::SetCreateParams(SdrDragStat const & rStat)
+{
+ rStat.TakeCreateRect(aR);
+ aR.Justify();
+ aCenter=aR.Center();
+ nWdt=aR.Right()-aR.Left();
+ nHgt=aR.Bottom()-aR.Top();
+ nStart=0_deg100;
+ nEnd=36000_deg100;
+ if (rStat.GetPointCount()>2) {
+ Point aP(rStat.GetPoint(2)-aCenter);
+ if (nWdt==0) aP.setX(0 );
+ if (nHgt==0) aP.setY(0 );
+ if (nWdt>=nHgt) {
+ if (nHgt!=0) aP.setY(aP.Y()*nWdt/nHgt );
+ } else {
+ if (nWdt!=0) aP.setX(aP.X()*nHgt/nWdt );
+ }
+ nStart=NormAngle36000(GetAngle(aP));
+ if (rStat.GetView()!=nullptr && rStat.GetView()->IsAngleSnapEnabled()) {
+ Degree100 nSA=rStat.GetView()->GetSnapAngle();
+ if (nSA) { // angle snapping
+ nStart+=nSA/2_deg100;
+ nStart/=nSA;
+ nStart*=nSA;
+ nStart=NormAngle36000(nStart);
+ }
+ }
+ aP1 = GetAnglePnt(aR,nStart);
+ nEnd=nStart;
+ } else aP1=aCenter;
+ if (rStat.GetPointCount()<=3)
+ return;
+
+ Point aP(rStat.GetPoint(3)-aCenter);
+ if (nWdt>=nHgt) {
+ aP.setY(BigMulDiv(aP.Y(),nWdt,nHgt) );
+ } else {
+ aP.setX(BigMulDiv(aP.X(),nHgt,nWdt) );
+ }
+ nEnd=NormAngle36000(GetAngle(aP));
+ if (rStat.GetView()!=nullptr && rStat.GetView()->IsAngleSnapEnabled()) {
+ Degree100 nSA=rStat.GetView()->GetSnapAngle();
+ if (nSA) { // angle snapping
+ nEnd+=nSA/2_deg100;
+ nEnd/=nSA;
+ nEnd*=nSA;
+ nEnd=NormAngle36000(nEnd);
+ }
+ }
+}
+
+void SdrCircObj::ImpSetCreateParams(SdrDragStat& rStat)
+{
+ ImpCircUser* pU=static_cast<ImpCircUser*>(rStat.GetUser());
+ if (pU==nullptr) {
+ pU=new ImpCircUser;
+ rStat.SetUser(std::unique_ptr<ImpCircUser>(pU));
+ }
+ pU->SetCreateParams(rStat);
+}
+
+bool SdrCircObj::BegCreate(SdrDragStat& rStat)
+{
+ rStat.SetOrtho4Possible();
+ tools::Rectangle aRect1(rStat.GetStart(), rStat.GetNow());
+ aRect1.Justify();
+ rStat.SetActionRect(aRect1);
+ maRect = aRect1;
+ ImpSetCreateParams(rStat);
+ return true;
+}
+
+bool SdrCircObj::MovCreate(SdrDragStat& rStat)
+{
+ ImpSetCreateParams(rStat);
+ ImpCircUser* pU=static_cast<ImpCircUser*>(rStat.GetUser());
+ rStat.SetActionRect(pU->aR);
+ maRect = pU->aR; // for ObjName
+ ImpJustifyRect(maRect);
+ nStartAngle=pU->nStart;
+ nEndAngle=pU->nEnd;
+ SetBoundRectDirty();
+ m_bSnapRectDirty=true;
+ SetXPolyDirty();
+
+ // #i103058# push current angle settings to ItemSet to
+ // allow FullDrag visualisation
+ if(rStat.GetPointCount() >= 4)
+ {
+ ImpSetCircInfoToAttr();
+ }
+
+ return true;
+}
+
+bool SdrCircObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
+{
+ ImpSetCreateParams(rStat);
+ ImpCircUser* pU=static_cast<ImpCircUser*>(rStat.GetUser());
+ bool bRet = false;
+ if (eCmd==SdrCreateCmd::ForceEnd && rStat.GetPointCount()<4) meCircleKind=SdrCircKind::Full;
+ if (meCircleKind==SdrCircKind::Full) {
+ bRet=rStat.GetPointCount()>=2;
+ if (bRet) {
+ maRect = pU->aR;
+ ImpJustifyRect(maRect);
+ }
+ } else {
+ rStat.SetNoSnap(rStat.GetPointCount()>=2);
+ rStat.SetOrtho4Possible(rStat.GetPointCount()<2);
+ bRet=rStat.GetPointCount()>=4;
+ if (bRet) {
+ maRect = pU->aR;
+ ImpJustifyRect(maRect);
+ nStartAngle=pU->nStart;
+ nEndAngle=pU->nEnd;
+ }
+ }
+ m_bClosedObj=meCircleKind!=SdrCircKind::Arc;
+ SetBoundAndSnapRectsDirty();
+ SetXPolyDirty();
+ ImpSetCircInfoToAttr();
+ if (bRet)
+ rStat.SetUser(nullptr);
+ return bRet;
+}
+
+void SdrCircObj::BrkCreate(SdrDragStat& rStat)
+{
+ rStat.SetUser(nullptr);
+}
+
+bool SdrCircObj::BckCreate(SdrDragStat& rStat)
+{
+ rStat.SetNoSnap(rStat.GetPointCount()>=3);
+ rStat.SetOrtho4Possible(rStat.GetPointCount()<3);
+ return meCircleKind!=SdrCircKind::Full;
+}
+
+basegfx::B2DPolyPolygon SdrCircObj::TakeCreatePoly(const SdrDragStat& rDrag) const
+{
+ const ImpCircUser* pU = static_cast<const ImpCircUser*>(rDrag.GetUser());
+
+ if(rDrag.GetPointCount() < 4)
+ {
+ // force to OBJ_CIRC to get full visualisation
+ basegfx::B2DPolyPolygon aRetval(ImpCalcXPolyCirc(SdrCircKind::Full, pU->aR, pU->nStart, pU->nEnd));
+
+ if(3 == rDrag.GetPointCount())
+ {
+ // add edge to first point on ellipse
+ basegfx::B2DPolygon aNew;
+
+ aNew.append(basegfx::B2DPoint(pU->aCenter.X(), pU->aCenter.Y()));
+ aNew.append(basegfx::B2DPoint(pU->aP1.X(), pU->aP1.Y()));
+ aRetval.append(aNew);
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return basegfx::B2DPolyPolygon(ImpCalcXPolyCirc(meCircleKind, pU->aR, pU->nStart, pU->nEnd));
+ }
+}
+
+PointerStyle SdrCircObj::GetCreatePointer() const
+{
+ switch (meCircleKind) {
+ case SdrCircKind::Full: return PointerStyle::DrawEllipse;
+ case SdrCircKind::Section: return PointerStyle::DrawPie;
+ case SdrCircKind::Arc: return PointerStyle::DrawArc;
+ case SdrCircKind::Cut: return PointerStyle::DrawCircleCut;
+ default: break;
+ } // switch
+ return PointerStyle::Cross;
+}
+
+void SdrCircObj::NbcMove(const Size& aSiz)
+{
+ maRect.Move(aSiz);
+ m_aOutRect.Move(aSiz);
+ maSnapRect.Move(aSiz);
+ SetXPolyDirty();
+ SetBoundAndSnapRectsDirty(true);
+}
+
+void SdrCircObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
+{
+ Degree100 nAngle0 = maGeo.nRotationAngle;
+ bool bNoShearRota = (maGeo.nRotationAngle == 0_deg100 && maGeo.nShearAngle == 0_deg100);
+ SdrTextObj::NbcResize(rRef,xFact,yFact);
+ bNoShearRota |= (maGeo.nRotationAngle == 0_deg100 && maGeo.nShearAngle == 0_deg100);
+ if (meCircleKind!=SdrCircKind::Full) {
+ bool bXMirr=(xFact.GetNumerator()<0) != (xFact.GetDenominator()<0);
+ bool bYMirr=(yFact.GetNumerator()<0) != (yFact.GetDenominator()<0);
+ if (bXMirr || bYMirr) {
+ // At bXMirr!=bYMirr we should actually swap both line ends.
+ // That, however, is pretty bad (because of forced "hard" formatting).
+ // Alternatively, we could implement a bMirrored flag (maybe even
+ // a more general one, e. g. for mirrored text, ...).
+ Degree100 nS0=nStartAngle;
+ Degree100 nE0=nEndAngle;
+ if (bNoShearRota) {
+ // the RectObj already mirrors at VMirror because of a 180deg rotation
+ if (! (bXMirr && bYMirr)) {
+ Degree100 nTmp=nS0;
+ nS0=18000_deg100-nE0;
+ nE0=18000_deg100-nTmp;
+ }
+ } else { // mirror contorted ellipses
+ if (bXMirr!=bYMirr) {
+ nS0+=nAngle0;
+ nE0+=nAngle0;
+ if (bXMirr) {
+ Degree100 nTmp=nS0;
+ nS0=18000_deg100-nE0;
+ nE0=18000_deg100-nTmp;
+ }
+ if (bYMirr) {
+ Degree100 nTmp=nS0;
+ nS0=-nE0;
+ nE0=-nTmp;
+ }
+ nS0 -= maGeo.nRotationAngle;
+ nE0 -= maGeo.nRotationAngle;
+ }
+ }
+ Degree100 nAngleDif=nE0-nS0;
+ nStartAngle=NormAngle36000(nS0);
+ nEndAngle =NormAngle36000(nE0);
+ if (nAngleDif==36000_deg100) nEndAngle+=nAngleDif; // full circle
+ }
+ }
+ SetXPolyDirty();
+ ImpSetCircInfoToAttr();
+}
+
+void SdrCircObj::NbcShear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear)
+{
+ SdrTextObj::NbcShear(rRef,nAngle,tn,bVShear);
+ SetXPolyDirty();
+ ImpSetCircInfoToAttr();
+}
+
+void SdrCircObj::NbcMirror(const Point& rRef1, const Point& rRef2)
+{
+ bool bFreeMirr=meCircleKind!=SdrCircKind::Full;
+ Point aTmpPt1;
+ Point aTmpPt2;
+ if (bFreeMirr) { // some preparations for using an arbitrary axis of reflection
+ Point aCenter(maRect.Center());
+ tools::Long nWdt=maRect.GetWidth()-1;
+ tools::Long nHgt=maRect.GetHeight()-1;
+ tools::Long nMaxRad=(std::max(nWdt,nHgt)+1) /2;
+ // starting point
+ double a = toRadians(nStartAngle);
+ aTmpPt1=Point(FRound(cos(a)*nMaxRad),-FRound(sin(a)*nMaxRad));
+ if (nWdt==0) aTmpPt1.setX(0 );
+ if (nHgt==0) aTmpPt1.setY(0 );
+ aTmpPt1+=aCenter;
+ // finishing point
+ a = toRadians(nEndAngle);
+ aTmpPt2=Point(FRound(cos(a)*nMaxRad),-FRound(sin(a)*nMaxRad));
+ if (nWdt==0) aTmpPt2.setX(0 );
+ if (nHgt==0) aTmpPt2.setY(0 );
+ aTmpPt2+=aCenter;
+ if (maGeo.nRotationAngle)
+ {
+ RotatePoint(aTmpPt1, maRect.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
+ RotatePoint(aTmpPt2, maRect.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
+ }
+ if (maGeo.nShearAngle)
+ {
+ ShearPoint(aTmpPt1, maRect.TopLeft(), maGeo.mfTanShearAngle);
+ ShearPoint(aTmpPt2, maRect.TopLeft(), maGeo.mfTanShearAngle);
+ }
+ }
+ SdrTextObj::NbcMirror(rRef1,rRef2);
+ if (meCircleKind!=SdrCircKind::Full) { // adapt starting and finishing angle
+ MirrorPoint(aTmpPt1,rRef1,rRef2);
+ MirrorPoint(aTmpPt2,rRef1,rRef2);
+ // unrotate:
+ if (maGeo.nRotationAngle)
+ {
+ RotatePoint(aTmpPt1, maRect.TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); // -sin for reversion
+ RotatePoint(aTmpPt2, maRect.TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); // -sin for reversion
+ }
+ // unshear:
+ if (maGeo.nShearAngle)
+ {
+ ShearPoint(aTmpPt1, maRect.TopLeft(), -maGeo.mfTanShearAngle); // -tan for reversion
+ ShearPoint(aTmpPt2, maRect.TopLeft(), -maGeo.mfTanShearAngle); // -tan for reversion
+ }
+ Point aCenter(maRect.Center());
+ aTmpPt1-=aCenter;
+ aTmpPt2-=aCenter;
+ // because it's mirrored, the angles are swapped, too
+ nStartAngle=GetAngle(aTmpPt2);
+ nEndAngle =GetAngle(aTmpPt1);
+ Degree100 nAngleDif=nEndAngle-nStartAngle;
+ nStartAngle=NormAngle36000(nStartAngle);
+ nEndAngle =NormAngle36000(nEndAngle);
+ if (nAngleDif==36000_deg100) nEndAngle+=nAngleDif; // full circle
+ }
+ SetXPolyDirty();
+ ImpSetCircInfoToAttr();
+}
+
+std::unique_ptr<SdrObjGeoData> SdrCircObj::NewGeoData() const
+{
+ return std::make_unique<SdrCircObjGeoData>();
+}
+
+void SdrCircObj::SaveGeoData(SdrObjGeoData& rGeo) const
+{
+ SdrRectObj::SaveGeoData(rGeo);
+ SdrCircObjGeoData& rCGeo=static_cast<SdrCircObjGeoData&>(rGeo);
+ rCGeo.nStartAngle=nStartAngle;
+ rCGeo.nEndAngle =nEndAngle;
+}
+
+void SdrCircObj::RestoreGeoData(const SdrObjGeoData& rGeo)
+{
+ SdrRectObj::RestoreGeoData(rGeo);
+ const SdrCircObjGeoData& rCGeo=static_cast<const SdrCircObjGeoData&>(rGeo);
+ nStartAngle=rCGeo.nStartAngle;
+ nEndAngle =rCGeo.nEndAngle;
+ SetXPolyDirty();
+ ImpSetCircInfoToAttr();
+}
+
+static void Union(tools::Rectangle& rR, const Point& rP)
+{
+ if (rP.X()<rR.Left ()) rR.SetLeft(rP.X() );
+ if (rP.X()>rR.Right ()) rR.SetRight(rP.X() );
+ if (rP.Y()<rR.Top ()) rR.SetTop(rP.Y() );
+ if (rP.Y()>rR.Bottom()) rR.SetBottom(rP.Y() );
+}
+
+void SdrCircObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const
+{
+ rRect = maRect;
+ if (meCircleKind!=SdrCircKind::Full) {
+ const Point aPntStart(GetAnglePnt(maRect,nStartAngle));
+ const Point aPntEnd(GetAnglePnt(maRect,nEndAngle));
+ Degree100 a=nStartAngle;
+ Degree100 e=nEndAngle;
+ rRect.SetLeft(maRect.Right() );
+ rRect.SetRight(maRect.Left() );
+ rRect.SetTop(maRect.Bottom() );
+ rRect.SetBottom(maRect.Top() );
+ Union(rRect,aPntStart);
+ Union(rRect,aPntEnd);
+ if ((a<=18000_deg100 && e>=18000_deg100) || (a>e && (a<=18000_deg100 || e>=18000_deg100))) {
+ Union(rRect,maRect.LeftCenter());
+ }
+ if ((a<=27000_deg100 && e>=27000_deg100) || (a>e && (a<=27000_deg100 || e>=27000_deg100))) {
+ Union(rRect,maRect.BottomCenter());
+ }
+ if (a>e) {
+ Union(rRect,maRect.RightCenter());
+ }
+ if ((a<=9000_deg100 && e>=9000_deg100) || (a>e && (a<=9000_deg100 || e>=9000_deg100))) {
+ Union(rRect,maRect.TopCenter());
+ }
+ if (meCircleKind==SdrCircKind::Section) {
+ Union(rRect,maRect.Center());
+ }
+ if (maGeo.nRotationAngle)
+ {
+ Point aDst(rRect.TopLeft());
+ aDst-=maRect.TopLeft();
+ Point aDst0(aDst);
+ RotatePoint(aDst,Point(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
+ aDst-=aDst0;
+ rRect.Move(aDst.X(),aDst.Y());
+ }
+ }
+ if (maGeo.nShearAngle==0_deg100)
+ return;
+
+ tools::Long nDst = FRound((rRect.Bottom() - rRect.Top()) * maGeo.mfTanShearAngle);
+ if (maGeo.nShearAngle > 0_deg100)
+ {
+ Point aRef(rRect.TopLeft());
+ rRect.AdjustLeft( -nDst );
+ Point aTmpPt(rRect.TopLeft());
+ RotatePoint(aTmpPt, aRef, maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
+ aTmpPt-=rRect.TopLeft();
+ rRect.Move(aTmpPt.X(),aTmpPt.Y());
+ } else {
+ rRect.AdjustRight( -nDst );
+ }
+}
+
+void SdrCircObj::RecalcSnapRect()
+{
+ if (PaintNeedsXPolyCirc()) {
+ maSnapRect=GetXPoly().GetBoundRect();
+ } else {
+ TakeUnrotatedSnapRect(maSnapRect);
+ }
+}
+
+void SdrCircObj::NbcSetSnapRect(const tools::Rectangle& rRect)
+{
+ if (maGeo.nRotationAngle || maGeo.nShearAngle || meCircleKind != SdrCircKind::Full)
+ {
+ tools::Rectangle aSR0(GetSnapRect());
+ tools::Long nWdt0=aSR0.Right()-aSR0.Left();
+ tools::Long nHgt0=aSR0.Bottom()-aSR0.Top();
+ tools::Long nWdt1=rRect.Right()-rRect.Left();
+ tools::Long nHgt1=rRect.Bottom()-rRect.Top();
+ NbcResize(maSnapRect.TopLeft(),Fraction(nWdt1,nWdt0),Fraction(nHgt1,nHgt0));
+ NbcMove(Size(rRect.Left()-aSR0.Left(),rRect.Top()-aSR0.Top()));
+ } else {
+ maRect=rRect;
+ ImpJustifyRect(maRect);
+ }
+ SetBoundAndSnapRectsDirty();
+ SetXPolyDirty();
+ ImpSetCircInfoToAttr();
+}
+
+sal_uInt32 SdrCircObj::GetSnapPointCount() const
+{
+ if (meCircleKind==SdrCircKind::Full) {
+ return 1;
+ } else {
+ return 3;
+ }
+}
+
+Point SdrCircObj::GetSnapPoint(sal_uInt32 i) const
+{
+ switch (i) {
+ case 1 : return GetAnglePnt(maRect,nStartAngle);
+ case 2 : return GetAnglePnt(maRect,nEndAngle);
+ default: return maRect.Center();
+ }
+}
+
+void SdrCircObj::Notify(SfxBroadcaster& rBC, const SfxHint& rHint)
+{
+ SetXPolyDirty();
+ SdrRectObj::Notify(rBC,rHint);
+ ImpSetAttrToCircInfo();
+}
+
+
+void SdrCircObj::ImpSetAttrToCircInfo()
+{
+ const SfxItemSet& rSet = GetObjectItemSet();
+ SdrCircKind eNewKind = rSet.Get(SDRATTR_CIRCKIND).GetValue();
+
+ Degree100 nNewStart = rSet.Get(SDRATTR_CIRCSTARTANGLE).GetValue();
+ Degree100 nNewEnd = rSet.Get(SDRATTR_CIRCENDANGLE).GetValue();
+
+ bool bKindChg = meCircleKind != eNewKind;
+ bool bAngleChg = nNewStart != nStartAngle || nNewEnd != nEndAngle;
+
+ if(bKindChg || bAngleChg)
+ {
+ meCircleKind = eNewKind;
+ nStartAngle = nNewStart;
+ nEndAngle = nNewEnd;
+
+ if(bKindChg || (meCircleKind != SdrCircKind::Full && bAngleChg))
+ {
+ SetXPolyDirty();
+ SetBoundAndSnapRectsDirty();
+ }
+ }
+}
+
+void SdrCircObj::ImpSetCircInfoToAttr()
+{
+ const SfxItemSet& rSet = GetObjectItemSet();
+
+ SdrCircKind eOldKindA = rSet.Get(SDRATTR_CIRCKIND).GetValue();
+ Degree100 nOldStartAngle = rSet.Get(SDRATTR_CIRCSTARTANGLE).GetValue();
+ Degree100 nOldEndAngle = rSet.Get(SDRATTR_CIRCENDANGLE).GetValue();
+
+ if(meCircleKind == eOldKindA && nStartAngle == nOldStartAngle && nEndAngle == nOldEndAngle)
+ return;
+
+ // since SetItem() implicitly calls ImpSetAttrToCircInfo()
+ // setting the item directly is necessary here.
+ if(meCircleKind != eOldKindA)
+ {
+ GetProperties().SetObjectItemDirect(SdrCircKindItem(meCircleKind));
+ }
+
+ if(nStartAngle != nOldStartAngle)
+ {
+ GetProperties().SetObjectItemDirect(makeSdrCircStartAngleItem(nStartAngle));
+ }
+
+ if(nEndAngle != nOldEndAngle)
+ {
+ GetProperties().SetObjectItemDirect(makeSdrCircEndAngleItem(nEndAngle));
+ }
+
+ SetXPolyDirty();
+ ImpSetAttrToCircInfo();
+}
+
+SdrObjectUniquePtr SdrCircObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const
+{
+ const bool bFill(meCircleKind != SdrCircKind::Arc);
+ const basegfx::B2DPolygon aCircPolygon(ImpCalcXPolyCirc(meCircleKind, maRect, nStartAngle, nEndAngle));
+ SdrObjectUniquePtr pRet = ImpConvertMakeObj(basegfx::B2DPolyPolygon(aCircPolygon), bFill, bBezier);
+
+ if(bAddText)
+ {
+ pRet = ImpConvertAddText(std::move(pRet), bBezier);
+ }
+
+ return pRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */