/* -*- 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 <svx/dialmgr.hxx>
#include <svx/strings.hrc>

#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <editeng/editdata.hxx>
#include <editeng/editobj.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/flditem.hxx>
#include <editeng/measfld.hxx>
#include <editeng/outlobj.hxx>
#include <math.h>
#include <svl/style.hxx>

#include <sdr/contact/viewcontactofsdrmeasureobj.hxx>
#include <sdr/properties/measureproperties.hxx>
#include <svx/svddrag.hxx>
#include <svx/svdhdl.hxx>
#include <svx/svdmodel.hxx>
#include <svx/svdogrp.hxx>
#include <svx/svdomeas.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdoutl.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdtrans.hxx>
#include <svx/svdview.hxx>
#include <svx/sxmbritm.hxx>
#include <svx/sxmlhitm.hxx>
#include <sxmsitm.hxx>
#include <sxmtaitm.hxx>
#include <svx/sxmtfitm.hxx>
#include <svx/sxmtpitm.hxx>
#include <svx/sxmtritm.hxx>
#include <svx/sxmuitm.hxx>
#include <svx/xlnedcit.hxx>
#include <svx/xlnedit.hxx>
#include <svx/xlnedwit.hxx>
#include <svx/xlnstcit.hxx>
#include <svx/xlnstit.hxx>
#include <svx/xlnstwit.hxx>
#include <svx/xlnwtit.hxx>
#include <svx/xpoly.hxx>
#include <unotools/syslocale.hxx>
#include <unotools/localedatawrapper.hxx>
#include <vcl/ptrstyle.hxx>


SdrMeasureObjGeoData::SdrMeasureObjGeoData() {}
SdrMeasureObjGeoData::~SdrMeasureObjGeoData() {}

OUString SdrMeasureObj::TakeRepresentation(SdrMeasureFieldKind eMeasureFieldKind) const
{
    OUString aStr;
    Fraction aMeasureScale(1, 1);
    bool bTextRota90(false);
    bool bShowUnit(false);
    FieldUnit eMeasureUnit(FieldUnit::NONE);
    FieldUnit eModUIUnit(FieldUnit::NONE);

    const SfxItemSet& rSet = GetMergedItemSet();
    bTextRota90 = rSet.Get(SDRATTR_MEASURETEXTROTA90).GetValue();
    eMeasureUnit = rSet.Get(SDRATTR_MEASUREUNIT).GetValue();
    aMeasureScale = rSet.Get(SDRATTR_MEASURESCALE).GetValue();
    bShowUnit = rSet.Get(SDRATTR_MEASURESHOWUNIT).GetValue();
    sal_Int16 nNumDigits = rSet.Get(SDRATTR_MEASUREDECIMALPLACES).GetValue();

    switch(eMeasureFieldKind)
    {
        case SdrMeasureFieldKind::Value:
        {
            eModUIUnit = getSdrModelFromSdrObject().GetUIUnit();

            if(eMeasureUnit == FieldUnit::NONE)
                eMeasureUnit = eModUIUnit;

            sal_Int32 nLen(GetLen(m_aPt2 - m_aPt1));
            Fraction aFact(1,1);

            if(eMeasureUnit != eModUIUnit)
            {
                // for the unit conversion
                aFact *= GetMapFactor(eModUIUnit, eMeasureUnit).X();
            }

            if(aMeasureScale.GetNumerator() != aMeasureScale.GetDenominator())
            {
                aFact *= aMeasureScale;
            }

            if(aFact.GetNumerator() != aFact.GetDenominator())
            {
                // scale via BigInt, to avoid overruns
                nLen = BigMulDiv(nLen, aFact.GetNumerator(), aFact.GetDenominator());
            }

            if(!aFact.IsValid())
            {
                aStr = "?";
            }
            else
            {
                aStr = getSdrModelFromSdrObject().GetMetricString(nLen, true, nNumDigits);
            }

            SvtSysLocale aSysLocale;
            const LocaleDataWrapper& rLocaleDataWrapper = aSysLocale.GetLocaleData();
            sal_Unicode cDec(rLocaleDataWrapper.getNumDecimalSep()[0]);
            sal_Unicode cDecAlt(rLocaleDataWrapper.getNumDecimalSepAlt().toChar());

            if(aStr.indexOf(cDec) != -1 || (cDecAlt && aStr.indexOf(cDecAlt) != -1))
            {
                sal_Int32 nLen2(aStr.getLength() - 1);

                while(aStr[nLen2] == '0')
                {
                    aStr = aStr.copy(0, nLen2);
                    nLen2--;
                }

                if(aStr[nLen2] == cDec || (cDecAlt && aStr[nLen2] == cDecAlt))
                {
                    aStr = aStr.copy(0, nLen2);
                    nLen2--;
                }

                if(aStr.isEmpty())
                    aStr += "0";
            }

            break;
        }
        case SdrMeasureFieldKind::Unit:
        {
            if(bShowUnit)
            {
                eModUIUnit = getSdrModelFromSdrObject().GetUIUnit();

                if(eMeasureUnit == FieldUnit::NONE)
                    eMeasureUnit = eModUIUnit;

                aStr = SdrModel::GetUnitString(eMeasureUnit);
            }

            break;
        }
        case SdrMeasureFieldKind::Rotate90Blanks:
        {
            if(bTextRota90)
            {
                aStr = " ";
            }

            break;
        }
    }
    return aStr;
}


// BaseProperties section

std::unique_ptr<sdr::properties::BaseProperties> SdrMeasureObj::CreateObjectSpecificProperties()
{
    return std::make_unique<sdr::properties::MeasureProperties>(*this);
}


// DrawContact section

std::unique_ptr<sdr::contact::ViewContact> SdrMeasureObj::CreateObjectSpecificViewContact()
{
    return std::make_unique<sdr::contact::ViewContactOfSdrMeasureObj>(*this);
}


SdrMeasureObj::SdrMeasureObj(SdrModel& rSdrModel)
:   SdrTextObj(rSdrModel),
    m_bTextDirty(false)
{
    // #i25616#
    mbSupportTextIndentingOnLineWidthChange = false;
}

SdrMeasureObj::SdrMeasureObj(SdrModel& rSdrModel, SdrMeasureObj const & rSource)
:   SdrTextObj(rSdrModel, rSource),
    m_bTextDirty(false)
{
    // #i25616#
    mbSupportTextIndentingOnLineWidthChange = false;

    m_aPt1 = rSource.m_aPt1;
    m_aPt2 = rSource.m_aPt2;
    m_bTextDirty = rSource.m_bTextDirty;
}

SdrMeasureObj::SdrMeasureObj(
    SdrModel& rSdrModel,
    const Point& rPt1,
    const Point& rPt2)
:   SdrTextObj(rSdrModel),
    m_aPt1(rPt1),
    m_aPt2(rPt2),
    m_bTextDirty(false)
{
    // #i25616#
    mbSupportTextIndentingOnLineWidthChange = false;
}

SdrMeasureObj::~SdrMeasureObj()
{
}

void SdrMeasureObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
{
    rInfo.bMoveAllowed      =true;
    rInfo.bResizeFreeAllowed=true;
    rInfo.bResizePropAllowed=true;
    rInfo.bRotateFreeAllowed=true;
    rInfo.bRotate90Allowed  =true;
    rInfo.bMirrorFreeAllowed=true;
    rInfo.bMirror45Allowed  =true;
    rInfo.bMirror90Allowed  =true;
    rInfo.bTransparenceAllowed = false;
    rInfo.bShearAllowed     =true;
    rInfo.bEdgeRadiusAllowed=false;
    rInfo.bNoOrthoDesired   =true;
    rInfo.bNoContortion     =false;
    rInfo.bCanConvToPath    =false;
    rInfo.bCanConvToPoly    =true;
    rInfo.bCanConvToPathLineToArea=false;
    rInfo.bCanConvToPolyLineToArea=false;
    rInfo.bCanConvToContour = LineGeometryUsageIsNecessary();
}

SdrObjKind SdrMeasureObj::GetObjIdentifier() const
{
    return SdrObjKind::Measure;
}

struct ImpMeasureRec : public SdrDragStatUserData
{
    Point                       aPt1;
    Point                       aPt2;
    css::drawing::MeasureTextHorzPos eWantTextHPos;
    css::drawing::MeasureTextVertPos eWantTextVPos;
    tools::Long                        nLineDist;
    tools::Long                        nHelplineOverhang;
    tools::Long                        nHelplineDist;
    tools::Long                        nHelpline1Len;
    tools::Long                        nHelpline2Len;
    bool                        bBelowRefEdge;
    bool                        bTextRota90;
    bool                        bTextUpsideDown;
    bool                        bTextAutoAngle;
    Degree100                   nTextAutoAngleView;
};

namespace {

struct ImpLineRec
{
    Point                       aP1;
    Point                       aP2;
};

}

struct ImpMeasurePoly
{
    ImpLineRec                  aMainline1; // those with the 1st arrowhead
    ImpLineRec                  aMainline2; // those with the 2nd arrowhead
    ImpLineRec                  aMainline3; // those in between
    ImpLineRec                  aHelpline1;
    ImpLineRec                  aHelpline2;
    Size                        aTextSize;
    tools::Long                        nLineLen;
    Degree100                   nLineAngle;
    Degree100                   nTextAngle;
    Degree100                   nHlpAngle;
    double                      nLineSin;
    double                      nLineCos;
    sal_uInt16                      nMainlineCnt;
    css::drawing::MeasureTextHorzPos eUsedTextHPos;
    css::drawing::MeasureTextVertPos eUsedTextVPos;
    tools::Long                        nLineWdt2;  // half the line width
    tools::Long                        nArrow1Len; // length of 1st arrowhead; for Center, use only half
    tools::Long                        nArrow2Len; // length of 2nd arrowhead; for Center, use only half
    tools::Long                        nArrow1Wdt; // width of 1st arrow
    tools::Long                        nArrow2Wdt; // width of 2nd arrow
    tools::Long                        nShortLineLen; // line length, if PfeileAussen (arrowheads on the outside)
    bool                        bAutoUpsideDown; // UpsideDown via automation
    bool                        bBreakedLine;
};

void SdrMeasureObj::ImpTakeAttr(ImpMeasureRec& rRec) const
{
    rRec.aPt1 = m_aPt1;
    rRec.aPt2 = m_aPt2;

    const SfxItemSet& rSet = GetObjectItemSet();
    rRec.eWantTextHPos     =rSet.Get(SDRATTR_MEASURETEXTHPOS        ).GetValue();
    rRec.eWantTextVPos     =rSet.Get(SDRATTR_MEASURETEXTVPOS        ).GetValue();
    rRec.nLineDist         =rSet.Get(SDRATTR_MEASURELINEDIST        ).GetValue();
    rRec.nHelplineOverhang =rSet.Get(SDRATTR_MEASUREHELPLINEOVERHANG).GetValue();
    rRec.nHelplineDist     =rSet.Get(SDRATTR_MEASUREHELPLINEDIST    ).GetValue();
    rRec.nHelpline1Len     =rSet.Get(SDRATTR_MEASUREHELPLINE1LEN    ).GetValue();
    rRec.nHelpline2Len     =rSet.Get(SDRATTR_MEASUREHELPLINE2LEN    ).GetValue();
    rRec.bBelowRefEdge     =rSet.Get(SDRATTR_MEASUREBELOWREFEDGE    ).GetValue();
    rRec.bTextRota90       =rSet.Get(SDRATTR_MEASURETEXTROTA90      ).GetValue();
    rRec.bTextUpsideDown   =static_cast<const SdrMeasureTextUpsideDownItem&   >(rSet.Get(SDRATTR_MEASURETEXTUPSIDEDOWN  )).GetValue();
    rRec.bTextAutoAngle    =rSet.Get(SDRATTR_MEASURETEXTAUTOANGLE    ).GetValue();
    rRec.nTextAutoAngleView=static_cast<const SdrMeasureTextAutoAngleViewItem&>(rSet.Get(SDRATTR_MEASURETEXTAUTOANGLEVIEW)).GetValue();
}

static tools::Long impGetLineStartEndDistance(const basegfx::B2DPolyPolygon& rPolyPolygon, tools::Long nNewWidth, bool bCenter)
{
    const basegfx::B2DRange aPolygonRange(rPolyPolygon.getB2DRange());
    const double fOldWidth(std::max(aPolygonRange.getWidth(), 1.0));
    const double fScale(static_cast<double>(nNewWidth) / fOldWidth);
    tools::Long nHeight(basegfx::fround<tools::Long>(aPolygonRange.getHeight() * fScale));

    if(bCenter)
    {
        nHeight /= 2;
    }

    return nHeight;
}

void SdrMeasureObj::ImpCalcGeometrics(const ImpMeasureRec& rRec, ImpMeasurePoly& rPol) const
{
    Point aP1(rRec.aPt1);
    Point aP2(rRec.aPt2);
    Point aDelt(aP2); aDelt-=aP1;

    rPol.aTextSize=GetTextSize();
    rPol.nLineLen=GetLen(aDelt);

    rPol.nLineWdt2=0;
    tools::Long nArrow1Len=0; bool bArrow1Center=false;
    tools::Long nArrow2Len=0; bool bArrow2Center=false;
    tools::Long nArrow1Wdt=0;
    tools::Long nArrow2Wdt=0;
    rPol.nArrow1Wdt=0;
    rPol.nArrow2Wdt=0;
    tools::Long nArrowNeed=0;
    tools::Long nShortLen=0;
    bool bPfeileAussen = false;

    const SfxItemSet& rSet = GetObjectItemSet();
    sal_Int32 nLineWdt = rSet.Get(XATTR_LINEWIDTH).GetValue(); // line width
    rPol.nLineWdt2 = (nLineWdt + 1) / 2;

    nArrow1Wdt = rSet.Get(XATTR_LINESTARTWIDTH).GetValue();
    if(nArrow1Wdt < 0)
        nArrow1Wdt = -nLineWdt * nArrow1Wdt / 100; // <0 = relative

    nArrow2Wdt = rSet.Get(XATTR_LINEENDWIDTH).GetValue();
    if(nArrow2Wdt < 0)
        nArrow2Wdt = -nLineWdt * nArrow2Wdt / 100; // <0 = relative

    basegfx::B2DPolyPolygon aPol1(rSet.Get(XATTR_LINESTART).GetLineStartValue());
    basegfx::B2DPolyPolygon aPol2(rSet.Get(XATTR_LINEEND).GetLineEndValue());
    bArrow1Center = rSet.Get(XATTR_LINESTARTCENTER).GetValue();
    bArrow2Center = rSet.Get(XATTR_LINEENDCENTER).GetValue();
    nArrow1Len = impGetLineStartEndDistance(aPol1, nArrow1Wdt, bArrow1Center) - 1;
    nArrow2Len = impGetLineStartEndDistance(aPol2, nArrow2Wdt, bArrow2Center) - 1;

    // nArrowLen is already halved at bCenter.
    // In the case of 2 arrowheads each 4mm long, we can't go below 10mm.
    nArrowNeed=nArrow1Len+nArrow2Len+(nArrow1Wdt+nArrow2Wdt)/2;
    if (rPol.nLineLen<nArrowNeed) bPfeileAussen = true;
    nShortLen=(nArrow1Len+nArrow1Wdt + nArrow2Len+nArrow2Wdt) /2;

    rPol.eUsedTextHPos=rRec.eWantTextHPos;
    rPol.eUsedTextVPos=rRec.eWantTextVPos;
    if (rPol.eUsedTextVPos == css::drawing::MeasureTextVertPos_AUTO)
        rPol.eUsedTextVPos = css::drawing::MeasureTextVertPos_EAST;
    bool bBrkLine=false;
    if (rPol.eUsedTextVPos == css::drawing::MeasureTextVertPos_CENTERED)
    {
        OutlinerParaObject* pOutlinerParaObject = SdrTextObj::GetOutlinerParaObject();
        if (pOutlinerParaObject!=nullptr && pOutlinerParaObject->GetTextObject().GetParagraphCount()==1)
        {
            bBrkLine=true; // dashed line if there's only on paragraph.
        }
    }
    rPol.bBreakedLine=bBrkLine;
    if (rPol.eUsedTextHPos==css::drawing::MeasureTextHorzPos_AUTO) { // if text is too wide, push it outside
        bool bOutside = false;
        tools::Long nNeedSiz=!rRec.bTextRota90 ? rPol.aTextSize.Width() : rPol.aTextSize.Height();
        if (nNeedSiz>rPol.nLineLen) bOutside = true; // text doesn't fit in between
        if (bBrkLine) {
            if (nNeedSiz+nArrowNeed>rPol.nLineLen) bPfeileAussen = true; // text fits in between, if arrowheads are on the outside
        } else {
            tools::Long nSmallNeed=nArrow1Len+nArrow2Len+(nArrow1Wdt+nArrow2Wdt)/2/4;
            if (nNeedSiz+nSmallNeed>rPol.nLineLen) bPfeileAussen = true; // text fits in between, if arrowheads are on the outside
        }
        rPol.eUsedTextHPos=bOutside ? css::drawing::MeasureTextHorzPos_LEFTOUTSIDE : css::drawing::MeasureTextHorzPos_INSIDE;
    }
    if (rPol.eUsedTextHPos != css::drawing::MeasureTextHorzPos_INSIDE) bPfeileAussen = true;
    rPol.nArrow1Wdt=nArrow1Wdt;
    rPol.nArrow2Wdt=nArrow2Wdt;
    rPol.nShortLineLen=nShortLen;
    rPol.nArrow1Len=nArrow1Len;
    rPol.nArrow2Len=nArrow2Len;

    rPol.nLineAngle=GetAngle(aDelt);
    double a = toRadians(rPol.nLineAngle);
    double nLineSin=sin(a);
    double nLineCos=cos(a);
    rPol.nLineSin=nLineSin;
    rPol.nLineCos=nLineCos;

    rPol.nTextAngle=rPol.nLineAngle;
    if (rRec.bTextRota90) rPol.nTextAngle+=9000_deg100;

    rPol.bAutoUpsideDown=false;
    if (rRec.bTextAutoAngle) {
        Degree100 nTmpAngle=NormAngle36000(rPol.nTextAngle-rRec.nTextAutoAngleView);
        if (nTmpAngle>=18000_deg100) {
            rPol.nTextAngle+=18000_deg100;
            rPol.bAutoUpsideDown=true;
        }
    }

    if (rRec.bTextUpsideDown) rPol.nTextAngle+=18000_deg100;
    rPol.nTextAngle=NormAngle36000(rPol.nTextAngle);
    rPol.nHlpAngle=rPol.nLineAngle+9000_deg100;
    if (rRec.bBelowRefEdge) rPol.nHlpAngle+=18000_deg100;
    rPol.nHlpAngle=NormAngle36000(rPol.nHlpAngle);
    double nHlpSin=nLineCos;
    double nHlpCos=-nLineSin;
    if (rRec.bBelowRefEdge) {
        nHlpSin=-nHlpSin;
        nHlpCos=-nHlpCos;
    }

    tools::Long nLineDist=rRec.nLineDist;
    tools::Long nOverhang=rRec.nHelplineOverhang;
    tools::Long nHelplineDist=rRec.nHelplineDist;

    tools::Long dx = basegfx::fround<tools::Long>(nLineDist * nHlpCos);
    tools::Long dy = basegfx::fround<tools::Long>(nLineDist * -nHlpSin);
    tools::Long dxh1a = basegfx::fround<tools::Long>((nHelplineDist - rRec.nHelpline1Len) * nHlpCos);
    tools::Long dyh1a = basegfx::fround<tools::Long>((nHelplineDist - rRec.nHelpline1Len) * -nHlpSin);
    tools::Long dxh1b = basegfx::fround<tools::Long>((nHelplineDist - rRec.nHelpline2Len) * nHlpCos);
    tools::Long dyh1b = basegfx::fround<tools::Long>((nHelplineDist - rRec.nHelpline2Len) * -nHlpSin);
    tools::Long dxh2 = basegfx::fround<tools::Long>((nLineDist + nOverhang) * nHlpCos);
    tools::Long dyh2 = basegfx::fround<tools::Long>((nLineDist + nOverhang) * -nHlpSin);

    // extension line 1
    rPol.aHelpline1.aP1=Point(aP1.X()+dxh1a,aP1.Y()+dyh1a);
    rPol.aHelpline1.aP2=Point(aP1.X()+dxh2,aP1.Y()+dyh2);

    // extension line 2
    rPol.aHelpline2.aP1=Point(aP2.X()+dxh1b,aP2.Y()+dyh1b);
    rPol.aHelpline2.aP2=Point(aP2.X()+dxh2,aP2.Y()+dyh2);

    // dimension line
    Point aMainlinePt1(aP1.X()+dx,aP1.Y()+dy);
    Point aMainlinePt2(aP2.X()+dx,aP2.Y()+dy);
    if (!bPfeileAussen) {
        rPol.aMainline1.aP1=aMainlinePt1;
        rPol.aMainline1.aP2=aMainlinePt2;
        rPol.aMainline2=rPol.aMainline1;
        rPol.aMainline3=rPol.aMainline1;
        rPol.nMainlineCnt=1;
        if (bBrkLine) {
            tools::Long nNeedSiz=!rRec.bTextRota90 ? rPol.aTextSize.Width() : rPol.aTextSize.Height();
            tools::Long nHalfLen=(rPol.nLineLen-nNeedSiz-nArrow1Wdt/4-nArrow2Wdt/4) /2;
            rPol.nMainlineCnt=2;
            rPol.aMainline1.aP2=aMainlinePt1;
            rPol.aMainline1.aP2.AdjustX(nHalfLen );
            RotatePoint(rPol.aMainline1.aP2,rPol.aMainline1.aP1,nLineSin,nLineCos);
            rPol.aMainline2.aP1=aMainlinePt2;
            rPol.aMainline2.aP1.AdjustX( -nHalfLen );
            RotatePoint(rPol.aMainline2.aP1,rPol.aMainline2.aP2,nLineSin,nLineCos);
        }
    } else {
        tools::Long nLen1=nShortLen; // arrowhead's width as line length outside of the arrowhead
        tools::Long nLen2=nShortLen;
        tools::Long nTextWdt=rRec.bTextRota90 ? rPol.aTextSize.Height() : rPol.aTextSize.Width();
        if (!bBrkLine) {
            if (rPol.eUsedTextHPos==css::drawing::MeasureTextHorzPos_LEFTOUTSIDE) nLen1=nArrow1Len+nTextWdt;
            if (rPol.eUsedTextHPos==css::drawing::MeasureTextHorzPos_RIGHTOUTSIDE) nLen2=nArrow2Len+nTextWdt;
        }
        rPol.aMainline1.aP1=aMainlinePt1;
        rPol.aMainline1.aP2=aMainlinePt1; rPol.aMainline1.aP2.AdjustX( -nLen1 ); RotatePoint(rPol.aMainline1.aP2,aMainlinePt1,nLineSin,nLineCos);
        rPol.aMainline2.aP1=aMainlinePt2; rPol.aMainline2.aP1.AdjustX(nLen2 ); RotatePoint(rPol.aMainline2.aP1,aMainlinePt2,nLineSin,nLineCos);
        rPol.aMainline2.aP2=aMainlinePt2;
        rPol.aMainline3.aP1=aMainlinePt1;
        rPol.aMainline3.aP2=aMainlinePt2;
        rPol.nMainlineCnt=3;
        if (bBrkLine && rPol.eUsedTextHPos==css::drawing::MeasureTextHorzPos_INSIDE) rPol.nMainlineCnt=2;
    }
}

basegfx::B2DPolyPolygon SdrMeasureObj::ImpCalcXPoly(const ImpMeasurePoly& rPol)
{
    basegfx::B2DPolyPolygon aRetval;
    basegfx::B2DPolygon aPartPolyA;
    aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline1.aP1.X(), rPol.aMainline1.aP1.Y()));
    aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline1.aP2.X(), rPol.aMainline1.aP2.Y()));
    aRetval.append(aPartPolyA);

    if(rPol.nMainlineCnt > 1)
    {
        aPartPolyA.clear();
        aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline2.aP1.X(), rPol.aMainline2.aP1.Y()));
        aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline2.aP2.X(), rPol.aMainline2.aP2.Y()));
        aRetval.append(aPartPolyA);
    }

    if(rPol.nMainlineCnt > 2)
    {
        aPartPolyA.clear();
        aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline3.aP1.X(), rPol.aMainline3.aP1.Y()));
        aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline3.aP2.X(), rPol.aMainline3.aP2.Y()));
        aRetval.append(aPartPolyA);
    }

    aPartPolyA.clear();
    aPartPolyA.append(basegfx::B2DPoint(rPol.aHelpline1.aP1.X(), rPol.aHelpline1.aP1.Y()));
    aPartPolyA.append(basegfx::B2DPoint(rPol.aHelpline1.aP2.X(), rPol.aHelpline1.aP2.Y()));
    aRetval.append(aPartPolyA);

    aPartPolyA.clear();
    aPartPolyA.append(basegfx::B2DPoint(rPol.aHelpline2.aP1.X(), rPol.aHelpline2.aP1.Y()));
    aPartPolyA.append(basegfx::B2DPoint(rPol.aHelpline2.aP2.X(), rPol.aHelpline2.aP2.Y()));
    aRetval.append(aPartPolyA);

    return aRetval;
}

bool SdrMeasureObj::CalcFieldValue(const SvxFieldItem& rField, sal_Int32 nPara, sal_uInt16 nPos,
    bool bEdit,
    std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor, std::optional<FontLineStyle>& rpFldLineStyle, OUString& rRet) const
{
    const SvxFieldData* pField=rField.GetField();
    const SdrMeasureField* pMeasureField=dynamic_cast<const SdrMeasureField*>( pField );
    if (pMeasureField!=nullptr) {
        rRet = TakeRepresentation(pMeasureField->GetMeasureFieldKind());
        if (rpFldColor && !bEdit)
        {
            rpFldColor.reset();
        }
        return true;
    } else {
        return SdrTextObj::CalcFieldValue(rField,nPara,nPos,bEdit,rpTxtColor,rpFldColor,rpFldLineStyle,rRet);
    }
}

void SdrMeasureObj::UndirtyText() const
{
    if (!m_bTextDirty)
        return;

    SdrOutliner& rOutliner=ImpGetDrawOutliner();
    OutlinerParaObject* pOutlinerParaObject = SdrTextObj::GetOutlinerParaObject();
    if(pOutlinerParaObject==nullptr)
    {
        rOutliner.QuickInsertField(SvxFieldItem(SdrMeasureField(SdrMeasureFieldKind::Rotate90Blanks), EE_FEATURE_FIELD), ESelection(0,0));
        rOutliner.QuickInsertField(SvxFieldItem(SdrMeasureField(SdrMeasureFieldKind::Value), EE_FEATURE_FIELD),ESelection(0,1));
        rOutliner.QuickInsertText(u" "_ustr, ESelection(0,2));
        rOutliner.QuickInsertField(SvxFieldItem(SdrMeasureField(SdrMeasureFieldKind::Unit), EE_FEATURE_FIELD),ESelection(0,3));
        rOutliner.QuickInsertField(SvxFieldItem(SdrMeasureField(SdrMeasureFieldKind::Rotate90Blanks), EE_FEATURE_FIELD),ESelection(0,4));

        if(GetStyleSheet())
            rOutliner.SetStyleSheet(0, GetStyleSheet());

        rOutliner.SetParaAttribs(0, GetObjectItemSet());

        // cast to nonconst
        const_cast<SdrMeasureObj*>(this)->NbcSetOutlinerParaObject( rOutliner.CreateParaObject() );
    }
    else
    {
        rOutliner.SetText(*pOutlinerParaObject);
    }

    rOutliner.SetUpdateLayout(true);
    rOutliner.UpdateFields();
    Size aSiz(rOutliner.CalcTextSize());
    rOutliner.Clear();
    // cast to nonconst three times
    const_cast<SdrMeasureObj*>(this)->maTextSize = aSiz;
    const_cast<SdrMeasureObj*>(this)->mbTextSizeDirty = false;
    const_cast<SdrMeasureObj*>(this)->m_bTextDirty = false;
}

void SdrMeasureObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const
{
    if (m_bTextDirty) UndirtyText();
    ImpMeasureRec aRec;
    ImpMeasurePoly aMPol;
    ImpTakeAttr(aRec);
    ImpCalcGeometrics(aRec,aMPol);

    // determine TextSize including text frame margins
    Size aTextSize2(aMPol.aTextSize);
    if (aTextSize2.Width()<1) aTextSize2.setWidth(1 );
    if (aTextSize2.Height()<1) aTextSize2.setHeight(1 );
    aTextSize2.AdjustWidth(GetTextLeftDistance()+GetTextRightDistance() );
    aTextSize2.AdjustHeight(GetTextUpperDistance()+GetTextLowerDistance() );

    Point aPt1b(aMPol.aMainline1.aP1);
    tools::Long nLen=aMPol.nLineLen;
    tools::Long nLWdt=aMPol.nLineWdt2;
    tools::Long nArr1Len=aMPol.nArrow1Len;
    tools::Long nArr2Len=aMPol.nArrow2Len;
    if (aMPol.bBreakedLine) {
        // In the case of a dashed line and Outside, the text should be
        // placed next to the line at the arrowhead instead of directly
        // at the arrowhead.
        nArr1Len=aMPol.nShortLineLen+aMPol.nArrow1Wdt/4;
        nArr2Len=aMPol.nShortLineLen+aMPol.nArrow2Wdt/4;
    }

    Point aTextPos;
    bool bRota90=aRec.bTextRota90;
    bool bUpsideDown=aRec.bTextUpsideDown!=aMPol.bAutoUpsideDown;
    bool bBelowRefEdge=aRec.bBelowRefEdge;
    css::drawing::MeasureTextHorzPos eMH=aMPol.eUsedTextHPos;
    css::drawing::MeasureTextVertPos eMV=aMPol.eUsedTextVPos;
    if (!bRota90) {
        switch (eMH) {
            case css::drawing::MeasureTextHorzPos_LEFTOUTSIDE: aTextPos.setX(aPt1b.X()-aTextSize2.Width()-nArr1Len-nLWdt ); break;
            case css::drawing::MeasureTextHorzPos_RIGHTOUTSIDE: aTextPos.setX(aPt1b.X()+nLen+nArr2Len+nLWdt ); break;
            default: aTextPos.setX(aPt1b.X() ); aTextSize2.setWidth(nLen );
        }
        switch (eMV) {
            case css::drawing::MeasureTextVertPos_CENTERED:
                aTextPos.setY(aPt1b.Y()-aTextSize2.Height()/2 ); break;
            case css::drawing::MeasureTextVertPos_WEST: {
                if (!bUpsideDown) aTextPos.setY(aPt1b.Y()+nLWdt );
                else aTextPos.setY(aPt1b.Y()-aTextSize2.Height()-nLWdt );
            } break;
            default: {
                if (!bUpsideDown) aTextPos.setY(aPt1b.Y()-aTextSize2.Height()-nLWdt );
                else aTextPos.setY(aPt1b.Y()+nLWdt );
            }
        }
        if (bUpsideDown) {
            aTextPos.AdjustX(aTextSize2.Width() );
            aTextPos.AdjustY(aTextSize2.Height() );
        }
    } else { // also if bTextRota90==TRUE
        switch (eMH) {
            case css::drawing::MeasureTextHorzPos_LEFTOUTSIDE: aTextPos.setX(aPt1b.X()-aTextSize2.Height()-nArr1Len ); break;
            case css::drawing::MeasureTextHorzPos_RIGHTOUTSIDE: aTextPos.setX(aPt1b.X()+nLen+nArr2Len ); break;
            default: aTextPos.setX(aPt1b.X() ); aTextSize2.setHeight(nLen );
        }
        switch (eMV) {
            case css::drawing::MeasureTextVertPos_CENTERED:
                aTextPos.setY(aPt1b.Y()+aTextSize2.Width()/2 ); break;
            case css::drawing::MeasureTextVertPos_WEST: {
                if (!bBelowRefEdge) aTextPos.setY(aPt1b.Y()+aTextSize2.Width()+nLWdt );
                else aTextPos.setY(aPt1b.Y()-nLWdt );
            } break;
            default: {
                if (!bBelowRefEdge) aTextPos.setY(aPt1b.Y()-nLWdt );
                else aTextPos.setY(aPt1b.Y()+aTextSize2.Width()+nLWdt );
            }
        }
        if (bUpsideDown) {
            aTextPos.AdjustX(aTextSize2.Height() );
            aTextPos.AdjustY( -(aTextSize2.Width()) );
        }
    }
    if (aMPol.nTextAngle != maGeo.m_nRotationAngle) {
        const_cast<SdrMeasureObj*>(this)->maGeo.m_nRotationAngle=aMPol.nTextAngle;
        const_cast<SdrMeasureObj*>(this)->maGeo.RecalcSinCos();
    }
    RotatePoint(aTextPos,aPt1b,aMPol.nLineSin,aMPol.nLineCos);
    aTextSize2.AdjustWidth( 1 ); aTextSize2.AdjustHeight( 1 ); // because of the Rect-Ctor's odd behavior
    rRect=tools::Rectangle(aTextPos,aTextSize2);
    rRect.Normalize();
    const_cast<SdrMeasureObj*>(this)->setRectangle(rRect);

    if (aMPol.nTextAngle != maGeo.m_nRotationAngle) {
        const_cast<SdrMeasureObj*>(this)->maGeo.m_nRotationAngle=aMPol.nTextAngle;
        const_cast<SdrMeasureObj*>(this)->maGeo.RecalcSinCos();
    }
}

rtl::Reference<SdrObject> SdrMeasureObj::CloneSdrObject(SdrModel& rTargetModel) const
{
    return new SdrMeasureObj(rTargetModel, *this);
}

OUString SdrMeasureObj::TakeObjNameSingul() const
{
    OUString sName(SvxResId(STR_ObjNameSingulMEASURE));

    OUString aName( GetName() );
    if (!aName.isEmpty())
        sName += " '" + aName + "'";

    return sName;
}

OUString SdrMeasureObj::TakeObjNamePlural() const
{
    return SvxResId(STR_ObjNamePluralMEASURE);
}

basegfx::B2DPolyPolygon SdrMeasureObj::TakeXorPoly() const
{
    ImpMeasureRec aRec;
    ImpMeasurePoly aMPol;
    ImpTakeAttr(aRec);
    ImpCalcGeometrics(aRec,aMPol);
    return ImpCalcXPoly(aMPol);
}

sal_uInt32 SdrMeasureObj::GetHdlCount() const
{
    return 6;
}

void SdrMeasureObj::AddToHdlList(SdrHdlList& rHdlList) const
{
    ImpMeasureRec aRec;
    ImpMeasurePoly aMPol;
    ImpTakeAttr(aRec);
    aRec.nHelplineDist=0;
    ImpCalcGeometrics(aRec,aMPol);

    for (sal_uInt32 nHdlNum=0; nHdlNum<6; ++nHdlNum)
    {
        Point aPt;
        switch (nHdlNum) {
            case 0: aPt=aMPol.aHelpline1.aP1; break;
            case 1: aPt=aMPol.aHelpline2.aP1; break;
            case 2: aPt=m_aPt1;       break;
            case 3: aPt=m_aPt2;       break;
            case 4: aPt=aMPol.aHelpline1.aP2; break;
            case 5: aPt=aMPol.aHelpline2.aP2; break;
        } // switch
        std::unique_ptr<SdrHdl> pHdl(new ImpMeasureHdl(aPt,SdrHdlKind::User));
        pHdl->SetObjHdlNum(nHdlNum);
        pHdl->SetRotationAngle(aMPol.nLineAngle);
        rHdlList.AddHdl(std::move(pHdl));
    }
}


bool SdrMeasureObj::hasSpecialDrag() const
{
    return true;
}

bool SdrMeasureObj::beginSpecialDrag(SdrDragStat& rDrag) const
{
    const SdrHdl* pHdl = rDrag.GetHdl();

    if(pHdl)
    {
        const sal_uInt32 nHdlNum(pHdl->GetObjHdlNum());

        if(nHdlNum != 2 && nHdlNum != 3)
        {
            rDrag.SetEndDragChangesAttributes(true);
        }

        return true;
    }

    return false;
}

bool SdrMeasureObj::applySpecialDrag(SdrDragStat& rDrag)
{
    ImpMeasureRec aMeasureRec;
    const SdrHdl* pHdl = rDrag.GetHdl();
    const sal_uInt32 nHdlNum(pHdl->GetObjHdlNum());

    ImpTakeAttr(aMeasureRec);
    ImpEvalDrag(aMeasureRec, rDrag);

    switch (nHdlNum)
    {
        case 2:
        {
            m_aPt1 = aMeasureRec.aPt1;
            SetTextDirty();
            break;
        }
        case 3:
        {
            m_aPt2 = aMeasureRec.aPt2;
            SetTextDirty();
            break;
        }
        default:
        {
            switch(nHdlNum)
            {
                case 0:
                case 1:
                {
                    ImpMeasureRec aOrigMeasureRec;
                    ImpTakeAttr(aOrigMeasureRec);

                    if(aMeasureRec.nHelpline1Len != aOrigMeasureRec.nHelpline1Len)
                    {
                        SetObjectItem(makeSdrMeasureHelpline1LenItem(aMeasureRec.nHelpline1Len));
                    }

                    if(aMeasureRec.nHelpline2Len != aOrigMeasureRec.nHelpline2Len)
                    {
                        SetObjectItem(makeSdrMeasureHelpline2LenItem(aMeasureRec.nHelpline2Len));
                    }

                    break;
                }

                case 4:
                case 5:
                {
                    ImpMeasureRec aOrigMeasureRec;
                    ImpTakeAttr(aOrigMeasureRec);

                    if(aMeasureRec.nLineDist != aOrigMeasureRec.nLineDist)
                    {
                        SetObjectItem(makeSdrMeasureLineDistItem(aMeasureRec.nLineDist));
                    }

                    if(aMeasureRec.bBelowRefEdge != aOrigMeasureRec.bBelowRefEdge)
                    {
                        SetObjectItem(SdrMeasureBelowRefEdgeItem(aMeasureRec.bBelowRefEdge));
                    }
                }
            }
        }
    } // switch

    SetBoundAndSnapRectsDirty();
    SetChanged();

    return true;
}

OUString SdrMeasureObj::getSpecialDragComment(const SdrDragStat& /*rDrag*/) const
{
    return OUString();
}

void SdrMeasureObj::ImpEvalDrag(ImpMeasureRec& rRec, const SdrDragStat& rDrag) const
{
    Degree100 nLineAngle=GetAngle(rRec.aPt2-rRec.aPt1);
    double a = toRadians(nLineAngle);
    double nSin=sin(a);
    double nCos=cos(a);

    const SdrHdl* pHdl=rDrag.GetHdl();
    sal_uInt32 nHdlNum(pHdl->GetObjHdlNum());
    bool bOrtho=rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho();
    bool bBigOrtho=bOrtho && rDrag.GetView()->IsBigOrtho();
    bool bBelow=rRec.bBelowRefEdge;
    Point aPt(rDrag.GetNow());

    switch (nHdlNum) {
        case 0: {
            RotatePoint(aPt,m_aPt1,nSin,-nCos);
            rRec.nHelpline1Len=m_aPt1.Y()-aPt.Y();
            if (bBelow) rRec.nHelpline1Len=-rRec.nHelpline1Len;
            if (bOrtho) rRec.nHelpline2Len=rRec.nHelpline1Len;
        } break;
        case 1: {
            RotatePoint(aPt,m_aPt2,nSin,-nCos);
            rRec.nHelpline2Len=m_aPt2.Y()-aPt.Y();
            if (bBelow) rRec.nHelpline2Len=-rRec.nHelpline2Len;
            if (bOrtho) rRec.nHelpline1Len=rRec.nHelpline2Len;
        } break;
        case 2: case 3: {
            bool bAnf=nHdlNum==2;
            Point& rMov=bAnf ? rRec.aPt1 : rRec.aPt2;
            Point aMov(rMov);
            Point aFix(bAnf ? rRec.aPt2 : rRec.aPt1);
            if (bOrtho) {
                tools::Long ndx0=aMov.X()-aFix.X();
                tools::Long ndy0=aMov.Y()-aFix.Y();
                bool bHLin=ndy0==0;
                bool bVLin=ndx0==0;
                if (!bHLin || !bVLin) { // else aPt1==aPt2
                    tools::Long ndx=aPt.X()-aFix.X();
                    tools::Long ndy=aPt.Y()-aFix.Y();
                    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);
                    aPt=aFix;
                    aPt.AdjustX(ndx );
                    aPt.AdjustY(ndy );
                } // else Ortho8
            }
            rMov=aPt;
        } break;
        case 4: case 5: {
            tools::Long nVal0=rRec.nLineDist;
            RotatePoint(aPt,(nHdlNum==4 ? m_aPt1 : m_aPt2),nSin,-nCos);
            rRec.nLineDist=aPt.Y()- (nHdlNum==4 ? m_aPt1.Y() : m_aPt2.Y());
            if (bBelow) rRec.nLineDist=-rRec.nLineDist;
            if (rRec.nLineDist<0) {
                rRec.nLineDist=-rRec.nLineDist;
                rRec.bBelowRefEdge=!bBelow;
            }
            rRec.nLineDist-=rRec.nHelplineOverhang;
            if (bOrtho) rRec.nLineDist=nVal0;
        } break;
    } // switch
}


bool SdrMeasureObj::BegCreate(SdrDragStat& rStat)
{
    rStat.SetOrtho8Possible();
    m_aPt1=rStat.GetStart();
    m_aPt2=rStat.GetNow();
    SetTextDirty();
    return true;
}

bool SdrMeasureObj::MovCreate(SdrDragStat& rStat)
{
    SdrView* pView=rStat.GetView();
    m_aPt1=rStat.GetStart();
    m_aPt2=rStat.GetNow();
    if (pView!=nullptr && pView->IsCreate1stPointAsCenter()) {
        m_aPt1+=m_aPt1;
        m_aPt1-=rStat.GetNow();
    }
    SetTextDirty();
    SetBoundRectDirty();
    m_bSnapRectDirty=true;
    return true;
}

bool SdrMeasureObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
{
    SetTextDirty();
    SetBoundAndSnapRectsDirty();
    return (eCmd==SdrCreateCmd::ForceEnd || rStat.GetPointCount()>=2);
}

bool SdrMeasureObj::BckCreate(SdrDragStat& /*rStat*/)
{
    return false;
}

void SdrMeasureObj::BrkCreate(SdrDragStat& /*rStat*/)
{
}

basegfx::B2DPolyPolygon SdrMeasureObj::TakeCreatePoly(const SdrDragStat& /*rDrag*/) const
{
    ImpMeasureRec aRec;
    ImpMeasurePoly aMPol;

    ImpTakeAttr(aRec);
    ImpCalcGeometrics(aRec, aMPol);

    return ImpCalcXPoly(aMPol);
}

PointerStyle SdrMeasureObj::GetCreatePointer() const
{
    return PointerStyle::Cross;
}

void SdrMeasureObj::NbcMove(const Size& rSiz)
{
    SdrTextObj::NbcMove(rSiz);
    m_aPt1.Move(rSiz);
    m_aPt2.Move(rSiz);
}

void SdrMeasureObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
{
    SdrTextObj::NbcResize(rRef,xFact,yFact);
    ResizePoint(m_aPt1,rRef,xFact,yFact);
    ResizePoint(m_aPt2,rRef,xFact,yFact);
    SetTextDirty();
}

void SdrMeasureObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs)
{
    SdrTextObj::NbcRotate(rRef,nAngle,sn,cs);
    tools::Long nLen0=GetLen(m_aPt2-m_aPt1);
    RotatePoint(m_aPt1,rRef,sn,cs);
    RotatePoint(m_aPt2,rRef,sn,cs);
    tools::Long nLen1=GetLen(m_aPt2-m_aPt1);
    if (nLen1!=nLen0) { // rounding error!
        tools::Long dx=m_aPt2.X()-m_aPt1.X();
        tools::Long dy=m_aPt2.Y()-m_aPt1.Y();
        dx=BigMulDiv(dx,nLen0,nLen1);
        dy=BigMulDiv(dy,nLen0,nLen1);
        if (rRef==m_aPt2) {
            m_aPt1.setX(m_aPt2.X()-dx );
            m_aPt1.setY(m_aPt2.Y()-dy );
        } else {
            m_aPt2.setX(m_aPt1.X()+dx );
            m_aPt2.setY(m_aPt1.Y()+dy );
        }
    }
    SetBoundAndSnapRectsDirty();
}

void SdrMeasureObj::NbcMirror(const Point& rRef1, const Point& rRef2)
{
    SdrTextObj::NbcMirror(rRef1,rRef2);
    MirrorPoint(m_aPt1,rRef1,rRef2);
    MirrorPoint(m_aPt2,rRef1,rRef2);
    SetBoundAndSnapRectsDirty();
}

void SdrMeasureObj::NbcShear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear)
{
    SdrTextObj::NbcShear(rRef,nAngle,tn,bVShear);
    ShearPoint(m_aPt1,rRef,tn,bVShear);
    ShearPoint(m_aPt2,rRef,tn,bVShear);
    SetBoundAndSnapRectsDirty();
    SetTextDirty();
}

Degree100 SdrMeasureObj::GetRotateAngle() const
{
    return GetAngle(m_aPt2-m_aPt1);
}

void SdrMeasureObj::RecalcSnapRect()
{
    ImpMeasureRec aRec;
    ImpMeasurePoly aMPol;
    XPolyPolygon aXPP;

    ImpTakeAttr(aRec);
    ImpCalcGeometrics(aRec, aMPol);
    aXPP = XPolyPolygon(ImpCalcXPoly(aMPol));
    maSnapRect = aXPP.GetBoundRect();
}

sal_uInt32 SdrMeasureObj::GetSnapPointCount() const
{
    return 2;
}

Point SdrMeasureObj::GetSnapPoint(sal_uInt32 i) const
{
    if (i==0) return m_aPt1;
    else return m_aPt2;
}

bool SdrMeasureObj::IsPolyObj() const
{
    return true;
}

sal_uInt32 SdrMeasureObj::GetPointCount() const
{
    return 2;
}

Point SdrMeasureObj::GetPoint(sal_uInt32 i) const
{
     return (0 == i) ? m_aPt1 : m_aPt2;
}

void SdrMeasureObj::NbcSetPoint(const Point& rPnt, sal_uInt32 i)
{
    if (0 == i)
        m_aPt1=rPnt;
    if (1 == i)
        m_aPt2=rPnt;
    SetBoundAndSnapRectsDirty();
    SetTextDirty();
}

std::unique_ptr<SdrObjGeoData> SdrMeasureObj::NewGeoData() const
{
    return std::make_unique<SdrMeasureObjGeoData>();
}

void SdrMeasureObj::SaveGeoData(SdrObjGeoData& rGeo) const
{
    SdrTextObj::SaveGeoData(rGeo);
    SdrMeasureObjGeoData& rMGeo=static_cast<SdrMeasureObjGeoData&>(rGeo);
    rMGeo.aPt1=m_aPt1;
    rMGeo.aPt2=m_aPt2;
}

void SdrMeasureObj::RestoreGeoData(const SdrObjGeoData& rGeo)
{
    SdrTextObj::RestoreGeoData(rGeo);
    const SdrMeasureObjGeoData& rMGeo=static_cast<const SdrMeasureObjGeoData&>(rGeo);
    m_aPt1=rMGeo.aPt1;
    m_aPt2=rMGeo.aPt2;
    SetTextDirty();
}

rtl::Reference<SdrObject> SdrMeasureObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const
{
    // get XOR Poly as base
    XPolyPolygon aTmpPolyPolygon(TakeXorPoly());

    // get local ItemSet and StyleSheet
    SfxItemSet aSet(GetObjectItemSet());
    SfxStyleSheet* pStyleSheet = GetStyleSheet();

    // prepare group
    rtl::Reference<SdrObjGroup> pGroup(new SdrObjGroup(getSdrModelFromSdrObject()));

    // prepare parameters
    basegfx::B2DPolyPolygon aPolyPoly;
    rtl::Reference<SdrPathObj> pPath;
    sal_uInt16 nCount(aTmpPolyPolygon.Count());
    sal_uInt16 nLoopStart(0);

    if(nCount == 3)
    {
        // three lines, first one is the middle one
        aPolyPoly.clear();
        aPolyPoly.append(aTmpPolyPolygon[0].getB2DPolygon());

        pPath = new SdrPathObj(
            getSdrModelFromSdrObject(),
            SdrObjKind::PathLine,
            aPolyPoly);

        pPath->SetMergedItemSet(aSet);
        pPath->SetStyleSheet(pStyleSheet, true);
        pGroup->GetSubList()->NbcInsertObject(pPath.get());
        aSet.Put(XLineStartWidthItem(0));
        aSet.Put(XLineEndWidthItem(0));
        nLoopStart = 1;
    }
    else if(nCount == 4)
    {
        // four lines, middle line with gap, so there are two lines used
        // which have one arrow each
        sal_Int32 nEndWidth = aSet.Get(XATTR_LINEENDWIDTH).GetValue();
        aSet.Put(XLineEndWidthItem(0));

        aPolyPoly.clear();
        aPolyPoly.append(aTmpPolyPolygon[0].getB2DPolygon());
        pPath = new SdrPathObj(
            getSdrModelFromSdrObject(),
            SdrObjKind::PathLine,
            aPolyPoly);

        pPath->SetMergedItemSet(aSet);
        pPath->SetStyleSheet(pStyleSheet, true);

        pGroup->GetSubList()->NbcInsertObject(pPath.get());

        aSet.Put(XLineEndWidthItem(nEndWidth));
        aSet.Put(XLineStartWidthItem(0));

        aPolyPoly.clear();
        aPolyPoly.append(aTmpPolyPolygon[1].getB2DPolygon());
        pPath = new SdrPathObj(
            getSdrModelFromSdrObject(),
            SdrObjKind::PathLine,
            aPolyPoly);

        pPath->SetMergedItemSet(aSet);
        pPath->SetStyleSheet(pStyleSheet, true);

        pGroup->GetSubList()->NbcInsertObject(pPath.get());

        aSet.Put(XLineEndWidthItem(0));
        nLoopStart = 2;
    }
    else if(nCount == 5)
    {
        // five lines, first two are the outer ones
        sal_Int32 nEndWidth = aSet.Get(XATTR_LINEENDWIDTH).GetValue();

        aSet.Put(XLineEndWidthItem(0));

        aPolyPoly.clear();
        aPolyPoly.append(aTmpPolyPolygon[0].getB2DPolygon());
        pPath = new SdrPathObj(
            getSdrModelFromSdrObject(),
            SdrObjKind::PathLine,
            aPolyPoly);

        pPath->SetMergedItemSet(aSet);
        pPath->SetStyleSheet(pStyleSheet, true);

        pGroup->GetSubList()->NbcInsertObject(pPath.get());

        aSet.Put(XLineEndWidthItem(nEndWidth));
        aSet.Put(XLineStartWidthItem(0));

        aPolyPoly.clear();
        aPolyPoly.append(aTmpPolyPolygon[1].getB2DPolygon());
        pPath = new SdrPathObj(
            getSdrModelFromSdrObject(),
            SdrObjKind::PathLine,
            aPolyPoly);

        pPath->SetMergedItemSet(aSet);
        pPath->SetStyleSheet(pStyleSheet, true);

        pGroup->GetSubList()->NbcInsertObject(pPath.get());

        aSet.Put(XLineEndWidthItem(0));
        nLoopStart = 2;
    }

    for(;nLoopStart<nCount;nLoopStart++)
    {
        aPolyPoly.clear();
        aPolyPoly.append(aTmpPolyPolygon[nLoopStart].getB2DPolygon());
        pPath = new SdrPathObj(
            getSdrModelFromSdrObject(),
            SdrObjKind::PathLine,
            aPolyPoly);

        pPath->SetMergedItemSet(aSet);
        pPath->SetStyleSheet(pStyleSheet, true);

        pGroup->GetSubList()->NbcInsertObject(pPath.get());
    }

    if(bAddText)
    {
        return ImpConvertAddText(std::move(pGroup), bBezier);
    }
    else
    {
        return pGroup;
    }
}

bool SdrMeasureObj::BegTextEdit(SdrOutliner& rOutl)
{
    UndirtyText();
    return SdrTextObj::BegTextEdit(rOutl);
}

const Size& SdrMeasureObj::GetTextSize() const
{
    if (m_bTextDirty) UndirtyText();
    return SdrTextObj::GetTextSize();
}

OutlinerParaObject* SdrMeasureObj::GetOutlinerParaObject() const
{
    if(m_bTextDirty)
        UndirtyText();
    return SdrTextObj::GetOutlinerParaObject();
}

void SdrMeasureObj::NbcSetOutlinerParaObject(std::optional<OutlinerParaObject> pTextObject, bool bAdjustTextFrameWidthAndHeight)
{
    SdrTextObj::NbcSetOutlinerParaObject(std::move(pTextObject), bAdjustTextFrameWidthAndHeight);
    if(SdrTextObj::GetOutlinerParaObject())
        SetTextDirty(); // recalculate text
}

void SdrMeasureObj::TakeTextRect( SdrOutliner& rOutliner, tools::Rectangle& rTextRect, bool bNoEditText,
    tools::Rectangle* pAnchorRect, bool bLineWidth ) const
{
    if (m_bTextDirty) UndirtyText();
    SdrTextObj::TakeTextRect( rOutliner, rTextRect, bNoEditText, pAnchorRect, bLineWidth );
}

void SdrMeasureObj::TakeTextAnchorRect(tools::Rectangle& rAnchorRect) const
{
    if (m_bTextDirty) UndirtyText();
    SdrTextObj::TakeTextAnchorRect(rAnchorRect);
}

void SdrMeasureObj::TakeTextEditArea(Size* pPaperMin, Size* pPaperMax, tools::Rectangle* pViewInit, tools::Rectangle* pViewMin) const
{
    if (m_bTextDirty) UndirtyText();
    SdrTextObj::TakeTextEditArea(pPaperMin,pPaperMax,pViewInit,pViewMin);
}

EEAnchorMode SdrMeasureObj::GetOutlinerViewAnchorMode() const
{
    if (m_bTextDirty) UndirtyText();
    ImpMeasureRec aRec;
    ImpMeasurePoly aMPol;
    ImpTakeAttr(aRec);
    ImpCalcGeometrics(aRec,aMPol);

    SdrTextHorzAdjust eTH=GetTextHorizontalAdjust();
    SdrTextVertAdjust eTV=GetTextVerticalAdjust();
    css::drawing::MeasureTextHorzPos eMH = aMPol.eUsedTextHPos;
    css::drawing::MeasureTextVertPos eMV = aMPol.eUsedTextVPos;
    bool bTextRota90=aRec.bTextRota90;
    bool bBelowRefEdge=aRec.bBelowRefEdge;

    // TODO: bTextUpsideDown should be interpreted here!
    if (!bTextRota90) {
        if (eMH==css::drawing::MeasureTextHorzPos_LEFTOUTSIDE) eTH=SDRTEXTHORZADJUST_RIGHT;
        if (eMH==css::drawing::MeasureTextHorzPos_RIGHTOUTSIDE) eTH=SDRTEXTHORZADJUST_LEFT;
        // at eMH==css::drawing::MeasureTextHorzPos_INSIDE we can anchor horizontally
        if (eMV==css::drawing::MeasureTextVertPos_EAST) eTV=SDRTEXTVERTADJUST_BOTTOM;
        if (eMV==css::drawing::MeasureTextVertPos_WEST) eTV=SDRTEXTVERTADJUST_TOP;
        if (eMV==css::drawing::MeasureTextVertPos_CENTERED) eTV=SDRTEXTVERTADJUST_CENTER;
    } else {
        if (eMH==css::drawing::MeasureTextHorzPos_LEFTOUTSIDE) eTV=SDRTEXTVERTADJUST_BOTTOM;
        if (eMH==css::drawing::MeasureTextHorzPos_RIGHTOUTSIDE) eTV=SDRTEXTVERTADJUST_TOP;
        // at eMH==css::drawing::MeasureTextHorzPos_INSIDE we can anchor vertically
        if (!bBelowRefEdge) {
            if (eMV==css::drawing::MeasureTextVertPos_EAST) eTH=SDRTEXTHORZADJUST_LEFT;
            if (eMV==css::drawing::MeasureTextVertPos_WEST) eTH=SDRTEXTHORZADJUST_RIGHT;
        } else {
            if (eMV==css::drawing::MeasureTextVertPos_EAST) eTH=SDRTEXTHORZADJUST_RIGHT;
            if (eMV==css::drawing::MeasureTextVertPos_WEST) eTH=SDRTEXTHORZADJUST_LEFT;
        }
        if (eMV==css::drawing::MeasureTextVertPos_CENTERED) eTH=SDRTEXTHORZADJUST_CENTER;
    }

    EEAnchorMode eRet=EEAnchorMode::BottomHCenter;
    if (eTH==SDRTEXTHORZADJUST_LEFT) {
        if (eTV==SDRTEXTVERTADJUST_TOP) eRet=EEAnchorMode::TopLeft;
        else if (eTV==SDRTEXTVERTADJUST_BOTTOM) eRet=EEAnchorMode::BottomLeft;
        else eRet=EEAnchorMode::VCenterLeft;
    } else if (eTH==SDRTEXTHORZADJUST_RIGHT) {
        if (eTV==SDRTEXTVERTADJUST_TOP) eRet=EEAnchorMode::TopRight;
        else if (eTV==SDRTEXTVERTADJUST_BOTTOM) eRet=EEAnchorMode::BottomRight;
        else eRet=EEAnchorMode::VCenterRight;
    } else {
        if (eTV==SDRTEXTVERTADJUST_TOP) eRet=EEAnchorMode::TopHCenter;
        else if (eTV==SDRTEXTVERTADJUST_BOTTOM) eRet=EEAnchorMode::BottomHCenter;
        else eRet=EEAnchorMode::VCenterHCenter;
    }
    return eRet;
}


// #i97878#
// TRGetBaseGeometry/TRSetBaseGeometry needs to be based on two positions,
// same as line geometry in SdrPathObj. Thus needs to be overridden and
// implemented since currently it is derived from SdrTextObj which uses
// a functionality based on SnapRect which is not useful here

bool SdrMeasureObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& /*rPolyPolygon*/) const
{
    // handle the same as a simple line since the definition is based on two points
    const basegfx::B2DRange aRange(m_aPt1.X(), m_aPt1.Y(), m_aPt2.X(), m_aPt2.Y());
    basegfx::B2DTuple aScale(aRange.getRange());
    basegfx::B2DTuple aTranslate(aRange.getMinimum());

    // position maybe relative to anchor position, convert
    if( getSdrModelFromSdrObject().IsWriter() )
    {
        if(GetAnchorPos().X() || GetAnchorPos().Y())
        {
            aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
        }
    }

    // build return value matrix
    rMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix(aScale, aTranslate);

    return true;
}

void SdrMeasureObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& /*rPolyPolygon*/)
{
    // use given transformation to derive the two defining points from unit line
    basegfx::B2DPoint aPosA(rMatrix * basegfx::B2DPoint(0.0, 0.0));
    basegfx::B2DPoint aPosB(rMatrix * basegfx::B2DPoint(1.0, 0.0));

    if( getSdrModelFromSdrObject().IsWriter() )
    {
        // if anchor is used, make position relative to it
        if(GetAnchorPos().X() || GetAnchorPos().Y())
        {
            const basegfx::B2DVector aAnchorOffset(GetAnchorPos().X(), GetAnchorPos().Y());

            aPosA += aAnchorOffset;
            aPosB += aAnchorOffset;
        }
    }

    // derive new model data
    const Point aNewPt1(basegfx::fround<tools::Long>(aPosA.getX()), basegfx::fround<tools::Long>(aPosA.getY()));
    const Point aNewPt2(basegfx::fround<tools::Long>(aPosB.getX()), basegfx::fround<tools::Long>(aPosB.getY()));

    if(aNewPt1 == m_aPt1 && aNewPt2 == m_aPt2)
        return;

    // set model values and broadcast
    tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect();

    m_aPt1 = aNewPt1;
    m_aPt2 = aNewPt2;

    SetTextDirty();
    ActionChanged();
    SetChanged();
    BroadcastObjectChange();
    SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
