/* -*- 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 <editeng/eeitem.hxx>
#include <math.h>
#include <svl/itemiter.hxx>
#include <sal/log.hxx>
#include <svl/whiter.hxx>
#include <tools/bigint.hxx>
#include <tools/debug.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>

#include <getallcharpropids.hxx>
#include <svx/dialmgr.hxx>
#include <svx/svditer.hxx>
#include <svx/strings.hrc>

#include <AffineMatrixItem.hxx>
#include <svx/e3dsceneupdater.hxx>
#include <svx/rectenum.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/sdooitm.hxx>
#include <svx/sderitm.hxx>
#include <svx/sdtagitm.hxx>
#include <svx/svdedtv.hxx>
#include <svx/svdetc.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdpagv.hxx>
#include <svx/svdtrans.hxx>
#include <svx/svdundo.hxx>
#include <svx/svxids.hrc>
#include <sxallitm.hxx>
#include <sxmovitm.hxx>
#include <sxreaitm.hxx>
#include <sxreoitm.hxx>
#include <sxroaitm.hxx>
#include <sxrooitm.hxx>
#include <sxsalitm.hxx>
#include <sxsoitm.hxx>
#include <sxtraitm.hxx>
#include <svx/xlnedwit.hxx>
#include <svx/xlnstwit.hxx>
#include <svx/xlnwtit.hxx>
#include <svx/xlnclit.hxx>
#include <svx/xflclit.hxx>
#include <svx/xlntrit.hxx>
#include <svx/xfltrit.hxx>
#include <svx/sdprcitm.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <rtl/ustring.hxx>
#include <sfx2/viewsh.hxx>
#include <comphelper/lok.hxx>
#include <osl/diagnose.h>
#include <sfx2/objsh.hxx>
#include <svl/cryptosign.hxx>

// EditView


void SdrEditView::SetMarkedObjRect(const tools::Rectangle& rRect)
{
    DBG_ASSERT(!rRect.IsEmpty(),"SetMarkedObjRect() with an empty Rect does not make sense.");
    if (rRect.IsEmpty())
        return;
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nCount=rMarkList.GetMarkCount();
    if (nCount==0) return;
    tools::Rectangle aR0(GetMarkedObjRect());
    DBG_ASSERT(!aR0.IsEmpty(),"SetMarkedObjRect(): GetMarkedObjRect() is empty.");
    if (aR0.IsEmpty()) return;
    tools::Long x0=aR0.Left();
    tools::Long y0=aR0.Top();
    tools::Long w0=aR0.Right()-x0;
    tools::Long h0=aR0.Bottom()-y0;
    tools::Long x1=rRect.Left();
    tools::Long y1=rRect.Top();
    tools::Long w1=rRect.Right()-x1;
    tools::Long h1=rRect.Bottom()-y1;

    const bool bUndo = IsUndoEnabled();
    if( bUndo )
    {
        EndTextEditCurrentView();
        BegUndo(ImpGetDescriptionString(STR_EditPosSize));
    }

    for (size_t nm=0; nm<nCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pO=pM->GetMarkedSdrObj();
        if (bUndo)
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO));

        tools::Rectangle aR1(pO->GetSnapRect());
        if (!aR1.IsEmpty())
        {
            if (aR1==aR0)
            {
                aR1=rRect;
            }
            else
            { // transform aR1 to aR0 after rRect
                aR1.Move(-x0,-y0);
                BigInt l(aR1.Left());
                BigInt r(aR1.Right());
                BigInt t(aR1.Top());
                BigInt b(aR1.Bottom());
                if (w0!=0) {
                    l*=w1; l/=w0;
                    r*=w1; r/=w0;
                } else {
                    l=0; r=w1;
                }
                if (h0!=0) {
                    t*=h1; t/=h0;
                    b*=h1; b/=h0;
                } else {
                    t=0; b=h1;
                }
                aR1.SetLeft(tools::Long(l) );
                aR1.SetRight(tools::Long(r) );
                aR1.SetTop(tools::Long(t) );
                aR1.SetBottom(tools::Long(b) );
                aR1.Move(x1,y1);
            }
            pO->SetSnapRect(aR1);
        } else {
            OSL_FAIL("SetMarkedObjRect(): pObj->GetSnapRect() returns empty Rect");
        }
    }
    if( bUndo )
        EndUndo();
}

std::vector< std::unique_ptr<SdrUndoAction> > SdrEditView::CreateConnectorUndo( const SdrObject& rO )
{
    std::vector< std::unique_ptr<SdrUndoAction> > vUndoActions;

    if ( rO.GetBroadcaster() )
    {
        const SdrPage* pPage = rO.getSdrPageFromSdrObject();
        if ( pPage )
        {
            SdrObjListIter aIter(pPage, SdrIterMode::DeepWithGroups);
            while( aIter.IsMore() )
            {
                SdrObject* pPartObj = aIter.Next();
                if ( dynamic_cast<const SdrEdgeObj*>( pPartObj) !=  nullptr )
                {
                    if ( ( pPartObj->GetConnectedNode( false ) == &rO ) ||
                         ( pPartObj->GetConnectedNode( true  ) == &rO ) )
                    {
                        vUndoActions.push_back(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pPartObj));
                    }
                }
            }
        }
    }
    return vUndoActions;
}

void SdrEditView::AddUndoActions( std::vector< std::unique_ptr<SdrUndoAction> > aUndoActions )
{
    for (auto & rAction : aUndoActions)
        AddUndo( std::move(rAction) );
}

void SdrEditView::MoveMarkedObj(const Size& rSiz, bool bCopy)
{
    const bool bUndo = IsUndoEnabled();
    const SdrMarkList& rMarkList = GetMarkedObjectList();

    if( bUndo )
    {
        EndTextEditCurrentView();
        OUString aStr(SvxResId(STR_EditMove));
        if (bCopy)
            aStr += SvxResId(STR_EditWithCopy);
        // needs its own UndoGroup because of its parameters
        BegUndo(aStr,rMarkList.GetMarkDescription(),SdrRepeatFunc::Move);
    }

    if (bCopy)
        CopyMarkedObj();

    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pO=pM->GetMarkedSdrObj();
        if( bUndo )
        {
            AddUndoActions( CreateConnectorUndo( *pO ) );
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoMoveObject(*pO,rSiz));
        }
        pO->Move(rSiz);
    }

    if( bUndo )
        EndUndo();
}

bool SdrEditView::IsMarkedObjSizeValid(const Size& aTargetSize)
{
    if (SdrMark* pM=GetMarkedObjectList().GetMark(0))
    {
        if (SdrObject* pO=pM->GetMarkedSdrObj())
        {
            if (pO->IsSizeValid(aTargetSize))
                return true;
        }
        else
            SAL_WARN("svx", "no MarkedSdrObj found");
    }
    else
        SAL_WARN("svx", "no SdrMark found");

    return false;
}

void SdrEditView::ResizeMarkedObj(const Point& rRef, const Fraction& xFact, const Fraction& yFact, bool bCopy)
{
    const bool bUndo = IsUndoEnabled();
    if( bUndo )
    {
        EndTextEditCurrentView();
        OUString aStr {ImpGetDescriptionString(STR_EditResize)};
        if (bCopy)
            aStr+=SvxResId(STR_EditWithCopy);
        BegUndo(aStr);
    }

    if (bCopy)
        CopyMarkedObj();

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pO=pM->GetMarkedSdrObj();
        if( bUndo )
        {
            AddUndoActions( CreateConnectorUndo( *pO ) );
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO));
        }
        pO->Resize(rRef,xFact,yFact);
    }

    if( bUndo )
        EndUndo();
}
void SdrEditView::ResizeMultMarkedObj(const Point& rRef,
    const Fraction& xFact,
    const Fraction& yFact,
    const bool bWdh,
    const bool bHgt)
{
    const bool bUndo = IsUndoEnabled();
    if( bUndo )
    {
        EndTextEditCurrentView();
        BegUndo(ImpGetDescriptionString(STR_EditResize));
    }

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pO=pM->GetMarkedSdrObj();
        if( bUndo )
        {
            AddUndoActions( CreateConnectorUndo( *pO ) );
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO));
        }

        Fraction aFrac(1,1);
        if (bWdh && xFact.IsValid() && bHgt && yFact.IsValid())
            pO->Resize(rRef, xFact, yFact);
        else if (bWdh && xFact.IsValid())
            pO->Resize(rRef, xFact, aFrac);
        else if (bHgt && yFact.IsValid())
            pO->Resize(rRef, aFrac, yFact);
    }
    if( bUndo )
        EndUndo();
}

Degree100 SdrEditView::GetMarkedObjRotate() const
{
    Degree100 nRetval(0);

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if(rMarkList.GetMarkCount())
    {
        SdrMark* pM = rMarkList.GetMark(0);
        SdrObject* pO = pM->GetMarkedSdrObj();

        nRetval = pO->GetRotateAngle();
    }

    return nRetval;
}

void SdrEditView::RotateMarkedObj(const Point& rRef, Degree100 nAngle, bool bCopy)
{
    const bool bUndo = IsUndoEnabled();
    if( bUndo )
    {
        EndTextEditCurrentView();
        OUString aStr {ImpGetDescriptionString(STR_EditRotate)};
        if (bCopy) aStr+=SvxResId(STR_EditWithCopy);
        BegUndo(aStr);
    }

    if (bCopy)
        CopyMarkedObj();

    double nSin = sin(toRadians(nAngle));
    double nCos = cos(toRadians(nAngle));
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount(rMarkList.GetMarkCount());

    if(nMarkCount)
    {
        std::vector< E3DModifySceneSnapRectUpdater* > aUpdaters;

        for(size_t nm = 0; nm < nMarkCount; ++nm)
        {
            SdrMark* pM = rMarkList.GetMark(nm);
            SdrObject* pO = pM->GetMarkedSdrObj();

            if( bUndo )
            {
                // extra undo actions for changed connector which now may hold its laid out path (SJ)
                AddUndoActions( CreateConnectorUndo( *pO ) );

                AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO));
            }

            // set up a scene updater if object is a 3d object
            if(DynCastE3dObject(pO))
            {
                aUpdaters.push_back(new E3DModifySceneSnapRectUpdater(pO));
            }

            pO->Rotate(rRef,nAngle,nSin,nCos);
        }

        // fire scene updaters
        while(!aUpdaters.empty())
        {
            delete aUpdaters.back();
            aUpdaters.pop_back();
        }
    }

    if( bUndo )
        EndUndo();
}

void SdrEditView::MirrorMarkedObj(const Point& rRef1, const Point& rRef2, bool bCopy)
{
    const bool bUndo = IsUndoEnabled();

    if( bUndo )
    {
        EndTextEditCurrentView();
        OUString aStr;
        Point aDif(rRef2-rRef1);
        if (aDif.X()==0)
            aStr = ImpGetDescriptionString(STR_EditMirrorHori);
        else if (aDif.Y()==0)
            aStr = ImpGetDescriptionString(STR_EditMirrorVert);
        else if (std::abs(aDif.X()) == std::abs(aDif.Y()))
            aStr = ImpGetDescriptionString(STR_EditMirrorDiag);
        else
            aStr = ImpGetDescriptionString(STR_EditMirrorFree);
        if (bCopy) aStr+=SvxResId(STR_EditWithCopy);
        BegUndo(aStr);
    }

    if (bCopy)
        CopyMarkedObj();

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount(rMarkList.GetMarkCount());

    if(nMarkCount)
    {
        std::vector< E3DModifySceneSnapRectUpdater* > aUpdaters;

        for(size_t nm = 0; nm < nMarkCount; ++nm)
        {
            SdrMark* pM = rMarkList.GetMark(nm);
            SdrObject* pO = pM->GetMarkedSdrObj();

            if( bUndo )
            {
                // extra undo actions for changed connector which now may hold its laid out path (SJ)
                AddUndoActions( CreateConnectorUndo( *pO ) );

                AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO));
            }

            // set up a scene updater if object is a 3d object
            if(DynCastE3dObject(pO))
            {
                aUpdaters.push_back(new E3DModifySceneSnapRectUpdater(pO));
            }

            pO->Mirror(rRef1,rRef2);
        }

        // fire scene updaters
        while(!aUpdaters.empty())
        {
            delete aUpdaters.back();
            aUpdaters.pop_back();
        }
    }

    if( bUndo )
        EndUndo();
}

void SdrEditView::MirrorMarkedObjHorizontal()
{
    Point aCenter(GetMarkedObjRect().Center());
    Point aPt2(aCenter);
    aPt2.AdjustY( 1 );
    MirrorMarkedObj(aCenter,aPt2);
}

void SdrEditView::MirrorMarkedObjVertical()
{
    Point aCenter(GetMarkedObjRect().Center());
    Point aPt2(aCenter);
    aPt2.AdjustX( 1 );
    MirrorMarkedObj(aCenter,aPt2);
}

Degree100 SdrEditView::GetMarkedObjShear() const
{
    bool b1st=true;
    bool bOk=true;
    Degree100 nAngle(0);
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount && bOk; ++nm) {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pO=pM->GetMarkedSdrObj();
        Degree100 nAngle2=pO->GetShearAngle();
        if (b1st) nAngle=nAngle2;
        else if (nAngle2!=nAngle) bOk=false;
        b1st=false;
    }
    if (nAngle>SDRMAXSHEAR) nAngle=SDRMAXSHEAR;
    if (nAngle<-SDRMAXSHEAR) nAngle=-SDRMAXSHEAR;
    if (!bOk) nAngle=0_deg100;
    return nAngle;
}

void SdrEditView::ShearMarkedObj(const Point& rRef, Degree100 nAngle, bool bVShear, bool bCopy)
{
    const bool bUndo = IsUndoEnabled();

    if( bUndo )
    {
        EndTextEditCurrentView();
        OUString aStr {ImpGetDescriptionString(STR_EditShear)};
        if (bCopy)
            aStr+=SvxResId(STR_EditWithCopy);
        BegUndo(aStr);
    }

    if (bCopy)
        CopyMarkedObj();

    double nTan = tan(toRadians(nAngle));
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pO=pM->GetMarkedSdrObj();
        if( bUndo )
        {
            AddUndoActions( CreateConnectorUndo( *pO ) );
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO));
        }
        pO->Shear(rRef,nAngle,nTan,bVShear);
    }

    if( bUndo )
        EndUndo();
}

void SdrEditView::ImpCrookObj(SdrObject* pO, const Point& rRef, const Point& rRad,
    SdrCrookMode eMode, bool bVertical, bool bNoContortion, bool bRotate, const tools::Rectangle& rMarkRect)
{
    SdrPathObj* pPath=dynamic_cast<SdrPathObj*>( pO );
    bool bDone = false;

    if(pPath!=nullptr && !bNoContortion)
    {
        XPolyPolygon aXPP(pPath->GetPathPoly());
        switch (eMode) {
            case SdrCrookMode::Rotate : CrookRotatePoly (aXPP,rRef,rRad,bVertical);           break;
            case SdrCrookMode::Slant  : CrookSlantPoly  (aXPP,rRef,rRad,bVertical);           break;
            case SdrCrookMode::Stretch: CrookStretchPoly(aXPP,rRef,rRad,bVertical,rMarkRect); break;
        } // switch
        pPath->SetPathPoly(aXPP.getB2DPolyPolygon());
        bDone = true;
    }

    if(!bDone && !pPath && pO->IsPolyObj() && 0 != pO->GetPointCount())
    {
        // for PolyObj's, but NOT for SdrPathObj's, e.g. the measurement object
        sal_uInt32 nPointCount(pO->GetPointCount());
        XPolygon aXP(static_cast<sal_uInt16>(nPointCount));
        sal_uInt32 nPtNum;

        for(nPtNum = 0; nPtNum < nPointCount; nPtNum++)
        {
            Point aPt(pO->GetPoint(nPtNum));
            aXP[static_cast<sal_uInt16>(nPtNum)]=aPt;
        }

        switch (eMode)
        {
            case SdrCrookMode::Rotate : CrookRotatePoly (aXP,rRef,rRad,bVertical);           break;
            case SdrCrookMode::Slant  : CrookSlantPoly  (aXP,rRef,rRad,bVertical);           break;
            case SdrCrookMode::Stretch: CrookStretchPoly(aXP,rRef,rRad,bVertical,rMarkRect); break;
        }

        for(nPtNum = 0; nPtNum < nPointCount; nPtNum++)
        {
            // broadcasting could be optimized here, but for the
            // current two points of the measurement object, it's fine
            pO->SetPoint(aXP[static_cast<sal_uInt16>(nPtNum)],nPtNum);
        }

        bDone = true;
    }

    if(bDone)
        return;

    // for all others or if bNoContortion
    Point aCtr0(pO->GetSnapRect().Center());
    Point aCtr1(aCtr0);
    bool bRotOk(false);
    double nSin(0.0), nCos(1.0);
    double nAngle(0.0);

    if(0 != rRad.X() && 0 != rRad.Y())
    {
        bRotOk = bRotate;

        switch (eMode)
        {
            case SdrCrookMode::Rotate : nAngle=CrookRotateXPoint (aCtr1,nullptr,nullptr,rRef,rRad,nSin,nCos,bVertical); bRotOk=bRotate; break;
            case SdrCrookMode::Slant  : nAngle=CrookSlantXPoint  (aCtr1,nullptr,nullptr,rRef,rRad,nSin,nCos,bVertical);           break;
            case SdrCrookMode::Stretch: nAngle=CrookStretchXPoint(aCtr1,nullptr,nullptr,rRef,rRad,nSin,nCos,bVertical,rMarkRect); break;
        }
    }

    aCtr1 -= aCtr0;

    if(bRotOk)
        pO->Rotate(aCtr0, Degree100(basegfx::fround(basegfx::rad2deg<100>(nAngle))), nSin, nCos);

    pO->Move(Size(aCtr1.X(),aCtr1.Y()));
}

void SdrEditView::CrookMarkedObj(const Point& rRef, const Point& rRad, SdrCrookMode eMode,
    bool bVertical, bool bNoContortion, bool bCopy)
{
    tools::Rectangle aMarkRect(GetMarkedObjRect());
    const bool bUndo = IsUndoEnabled();

    bool bRotate=bNoContortion && eMode==SdrCrookMode::Rotate && IsRotateAllowed();

    if( bUndo )
    {
        EndTextEditCurrentView();
        OUString aStr {ImpGetDescriptionString(bNoContortion ? STR_EditCrook : STR_EditCrookContortion)};
        if (bCopy)
            aStr+=SvxResId(STR_EditWithCopy);
        BegUndo(aStr);
    }

    if (bCopy)
        CopyMarkedObj();

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pO=pM->GetMarkedSdrObj();
        if (bUndo)
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO));

        const SdrObjList* pOL=pO->GetSubList();
        if (bNoContortion || pOL==nullptr) {
            ImpCrookObj(pO,rRef,rRad,eMode,bVertical,bNoContortion,bRotate,aMarkRect);
        } else {
            SdrObjListIter aIter(pOL,SdrIterMode::DeepNoGroups);
            while (aIter.IsMore()) {
                SdrObject* pO1=aIter.Next();
                ImpCrookObj(pO1,rRef,rRad,eMode,bVertical,bNoContortion,bRotate,aMarkRect);
            }
        }
    }

    if( bUndo )
        EndUndo();
}

void SdrEditView::ImpDistortObj(SdrObject* pO, const tools::Rectangle& rRef, const XPolygon& rDistortedRect, bool bNoContortion)
{
    SdrPathObj* pPath = dynamic_cast<SdrPathObj*>( pO );

    if(!bNoContortion && pPath)
    {
        XPolyPolygon aXPP(pPath->GetPathPoly());
        aXPP.Distort(rRef, rDistortedRect);
        pPath->SetPathPoly(aXPP.getB2DPolyPolygon());
    }
    else if(pO->IsPolyObj())
    {
        // e. g. for the measurement object
        sal_uInt32 nPointCount(pO->GetPointCount());
        XPolygon aXP(static_cast<sal_uInt16>(nPointCount));
        sal_uInt32 nPtNum;

        for(nPtNum = 0; nPtNum < nPointCount; nPtNum++)
        {
            Point aPt(pO->GetPoint(nPtNum));
            aXP[static_cast<sal_uInt16>(nPtNum)]=aPt;
        }

        aXP.Distort(rRef, rDistortedRect);

        for(nPtNum = 0; nPtNum < nPointCount; nPtNum++)
        {
            // broadcasting could be optimized here, but for the
            // current two points of the measurement object it's fine
            pO->SetPoint(aXP[static_cast<sal_uInt16>(nPtNum)],nPtNum);
        }
    }
}

void SdrEditView::DistortMarkedObj(const tools::Rectangle& rRef, const XPolygon& rDistortedRect, bool bNoContortion, bool bCopy)
{
    const bool bUndo = IsUndoEnabled();

    if( bUndo )
    {
        EndTextEditCurrentView();
        OUString aStr {ImpGetDescriptionString(STR_EditDistort)};
        if (bCopy)
            aStr+=SvxResId(STR_EditWithCopy);
        BegUndo(aStr);
    }

    if (bCopy)
        CopyMarkedObj();

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pO=pM->GetMarkedSdrObj();
        if (bUndo)
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO));

        tools::Rectangle aRefRect(rRef);
        const SdrObjList* pOL=pO->GetSubList();
        if (bNoContortion || pOL==nullptr) {
            ImpDistortObj(pO,aRefRect,rDistortedRect,bNoContortion);
        } else {
            SdrObjListIter aIter(pOL,SdrIterMode::DeepNoGroups);
            while (aIter.IsMore()) {
                SdrObject* pO1=aIter.Next();
                ImpDistortObj(pO1,aRefRect,rDistortedRect,bNoContortion);
            }
        }
    }
    if( bUndo )
        EndUndo();
}


void SdrEditView::SetNotPersistAttrToMarked(const SfxItemSet& rAttr)
{
    // bReplaceAll has no effect here
    tools::Rectangle aAllSnapRect(GetMarkedObjRect());
    if (const SdrTransformRef1XItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_TRANSFORMREF1X))
    {
        tools::Long n = pPoolItem->GetValue();
        SetRef1(Point(n,GetRef1().Y()));
    }
    if (const SdrTransformRef1YItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_TRANSFORMREF1Y))
    {
        tools::Long n = pPoolItem->GetValue();
        SetRef1(Point(GetRef1().X(),n));
    }
    if (const SdrTransformRef2XItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_TRANSFORMREF2X))
    {
        tools::Long n = pPoolItem->GetValue();
        SetRef2(Point(n,GetRef2().Y()));
    }
    if (const SdrTransformRef2YItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_TRANSFORMREF2Y))
    {
        tools::Long n = pPoolItem->GetValue();
        SetRef2(Point(GetRef2().X(),n));
    }
    tools::Long nAllPosX=0; bool bAllPosX=false;
    tools::Long nAllPosY=0; bool bAllPosY=false;
    tools::Long nAllWdt=0;  bool bAllWdt=false;
    tools::Long nAllHgt=0;  bool bAllHgt=false;
    bool bDoIt=false;
    if (const SdrAllPositionXItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ALLPOSITIONX))
    {
        nAllPosX = pPoolItem->GetValue();
        bAllPosX=true; bDoIt=true;
    }
    if (const SdrAllPositionYItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ALLPOSITIONY))
    {
        nAllPosY = pPoolItem->GetValue();
        bAllPosY=true; bDoIt=true;
    }
    if (const SdrAllSizeWidthItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ALLSIZEWIDTH))
    {
        nAllWdt = pPoolItem->GetValue();
        bAllWdt=true; bDoIt=true;
    }
    if (const SdrAllSizeHeightItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ALLSIZEHEIGHT))
    {
        nAllHgt = pPoolItem->GetValue();
        bAllHgt=true; bDoIt=true;
    }
    if (bDoIt) {
        tools::Rectangle aRect(aAllSnapRect); // TODO: change this for PolyPt's and GluePt's!!!
        if (bAllPosX) aRect.Move(nAllPosX-aRect.Left(),0);
        if (bAllPosY) aRect.Move(0,nAllPosY-aRect.Top());
        if (bAllWdt)  aRect.SetRight(aAllSnapRect.Left()+nAllWdt );
        if (bAllHgt)  aRect.SetBottom(aAllSnapRect.Top()+nAllHgt );
        SetMarkedObjRect(aRect);
    }
    if (const SdrResizeXAllItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_RESIZEXALL))
    {
        Fraction aXFact = pPoolItem->GetValue();
        ResizeMarkedObj(aAllSnapRect.TopLeft(),aXFact,Fraction(1,1));
    }
    if (const SdrResizeYAllItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_RESIZEYALL))
    {
        Fraction aYFact = pPoolItem->GetValue();
        ResizeMarkedObj(aAllSnapRect.TopLeft(),Fraction(1,1),aYFact);
    }
    if (const SdrRotateAllItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ROTATEALL))
    {
        Degree100 nAngle = pPoolItem->GetValue();
        RotateMarkedObj(aAllSnapRect.Center(),nAngle);
    }
    if (const SdrHorzShearAllItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_HORZSHEARALL))
    {
        Degree100 nAngle = pPoolItem->GetValue();
        ShearMarkedObj(aAllSnapRect.Center(),nAngle);
    }
    if (const SdrVertShearAllItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_VERTSHEARALL))
    {
        Degree100 nAngle = pPoolItem->GetValue();
        ShearMarkedObj(aAllSnapRect.Center(),nAngle,true);
    }

    const bool bUndo = IsUndoEnabled();

    // TODO: check if WhichRange is necessary.
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        const SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pObj=pM->GetMarkedSdrObj();
        if (bUndo)
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj));

        pObj->ApplyNotPersistAttr(rAttr);
    }
}

void SdrEditView::MergeNotPersistAttrFromMarked(SfxItemSet& rAttr) const
{
    // TODO: Take into account the origin and PvPos.
    tools::Rectangle aAllSnapRect(GetMarkedObjRect()); // TODO: change this for PolyPt's and GluePt's!!!
    tools::Long nAllSnapPosX=aAllSnapRect.Left();
    tools::Long nAllSnapPosY=aAllSnapRect.Top();
    tools::Long nAllSnapWdt=aAllSnapRect.GetWidth()-1;
    tools::Long nAllSnapHgt=aAllSnapRect.GetHeight()-1;
    // TODO: could go into CheckPossibilities
    bool bMovProtect = false, bMovProtectDC = false;
    bool bSizProtect = false, bSizProtectDC = false;
    bool bPrintable = true, bPrintableDC = false;
    bool bVisible = true, bVisibleDC = false;
    SdrLayerID nLayerId(0);
    bool bLayerDC=false;
    tools::Long nSnapPosX=0;      bool bSnapPosXDC=false;
    tools::Long nSnapPosY=0;      bool bSnapPosYDC=false;
    tools::Long nSnapWdt=0;       bool bSnapWdtDC=false;
    tools::Long nSnapHgt=0;       bool bSnapHgtDC=false;
    tools::Long nLogicWdt=0;      bool bLogicWdtDC=false,bLogicWdtDiff=false;
    tools::Long nLogicHgt=0;      bool bLogicHgtDC=false,bLogicHgtDiff=false;
    Degree100 nRotAngle(0);      bool bRotAngleDC=false;
    Degree100 nShrAngle(0);      bool bShrAngleDC=false;
    tools::Rectangle aSnapRect;
    tools::Rectangle aLogicRect;
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount; ++nm) {
        const SdrMark* pM=rMarkList.GetMark(nm);
        const SdrObject* pObj=pM->GetMarkedSdrObj();
        if (nm==0) {
            nLayerId=pObj->GetLayer();
            bMovProtect=pObj->IsMoveProtect();
            bSizProtect=pObj->IsResizeProtect();
            bPrintable =pObj->IsPrintable();
            bVisible = pObj->IsVisible();
            tools::Rectangle aSnapRect2(pObj->GetSnapRect());
            tools::Rectangle aLogicRect2(pObj->GetLogicRect());
            nSnapPosX=aSnapRect2.Left();
            nSnapPosY=aSnapRect2.Top();
            nSnapWdt=aSnapRect2.GetWidth()-1;
            nSnapHgt=aSnapRect2.GetHeight()-1;
            nLogicWdt=aLogicRect2.GetWidth()-1;
            nLogicHgt=aLogicRect2.GetHeight()-1;
            bLogicWdtDiff=nLogicWdt!=nSnapWdt;
            bLogicHgtDiff=nLogicHgt!=nSnapHgt;
            nRotAngle=pObj->GetRotateAngle();
            nShrAngle=pObj->GetShearAngle();
        } else {
            if (!bLayerDC      && nLayerId   !=pObj->GetLayer())        bLayerDC = true;
            if (!bMovProtectDC && bMovProtect!=pObj->IsMoveProtect())   bMovProtectDC = true;
            if (!bSizProtectDC && bSizProtect!=pObj->IsResizeProtect()) bSizProtectDC = true;
            if (!bPrintableDC  && bPrintable !=pObj->IsPrintable())     bPrintableDC = true;
            if (!bVisibleDC    && bVisible !=pObj->IsVisible())         bVisibleDC=true;
            if (!bRotAngleDC   && nRotAngle  !=pObj->GetRotateAngle())  bRotAngleDC=true;
            if (!bShrAngleDC   && nShrAngle  !=pObj->GetShearAngle())   bShrAngleDC=true;
            if (!bSnapWdtDC || !bSnapHgtDC || !bSnapPosXDC || !bSnapPosYDC || !bLogicWdtDiff || !bLogicHgtDiff) {
                aSnapRect=pObj->GetSnapRect();
                if (nSnapPosX!=aSnapRect.Left()) bSnapPosXDC=true;
                if (nSnapPosY!=aSnapRect.Top()) bSnapPosYDC=true;
                if (nSnapWdt!=aSnapRect.GetWidth()-1) bSnapWdtDC=true;
                if (nSnapHgt!=aSnapRect.GetHeight()-1) bSnapHgtDC=true;
            }
            if (!bLogicWdtDC || !bLogicHgtDC || !bLogicWdtDiff || !bLogicHgtDiff) {
                aLogicRect=pObj->GetLogicRect();
                if (nLogicWdt!=aLogicRect.GetWidth()-1) bLogicWdtDC=true;
                if (nLogicHgt!=aLogicRect.GetHeight()-1) bLogicHgtDC=true;
                if (!bLogicWdtDiff && aSnapRect.GetWidth()!=aLogicRect.GetWidth()) bLogicWdtDiff=true;
                if (!bLogicHgtDiff && aSnapRect.GetHeight()!=aLogicRect.GetHeight()) bLogicHgtDiff=true;
            }
        }
    }

    if (bSnapPosXDC || nAllSnapPosX!=nSnapPosX) rAttr.Put(SdrAllPositionXItem(nAllSnapPosX));
    if (bSnapPosYDC || nAllSnapPosY!=nSnapPosY) rAttr.Put(SdrAllPositionYItem(nAllSnapPosY));
    if (bSnapWdtDC  || nAllSnapWdt !=nSnapWdt ) rAttr.Put(SdrAllSizeWidthItem(nAllSnapWdt));
    if (bSnapHgtDC  || nAllSnapHgt !=nSnapHgt ) rAttr.Put(SdrAllSizeHeightItem(nAllSnapHgt));

    // items for pure transformations
    rAttr.Put(SdrMoveXItem());
    rAttr.Put(SdrMoveYItem());
    rAttr.Put(SdrResizeXOneItem());
    rAttr.Put(SdrResizeYOneItem());
    rAttr.Put(SdrRotateOneItem());
    rAttr.Put(SdrHorzShearOneItem());
    rAttr.Put(SdrVertShearOneItem());

    if (nMarkCount>1) {
        rAttr.Put(SdrResizeXAllItem());
        rAttr.Put(SdrResizeYAllItem());
        rAttr.Put(SdrRotateAllItem());
        rAttr.Put(SdrHorzShearAllItem());
        rAttr.Put(SdrVertShearAllItem());
    }

    if(meDragMode == SdrDragMode::Rotate || meDragMode == SdrDragMode::Mirror)
    {
        rAttr.Put(SdrTransformRef1XItem(GetRef1().X()));
        rAttr.Put(SdrTransformRef1YItem(GetRef1().Y()));
    }

    if(meDragMode == SdrDragMode::Mirror)
    {
        rAttr.Put(SdrTransformRef2XItem(GetRef2().X()));
        rAttr.Put(SdrTransformRef2YItem(GetRef2().Y()));
    }
}

SfxItemSet SdrEditView::GetAttrFromMarked(bool bOnlyHardAttr) const
{
    SfxItemSet aSet(GetModel().GetItemPool());
    MergeAttrFromMarked(aSet,bOnlyHardAttr);
    //the EE_FEATURE items should not be set with SetAttrToMarked (see error message there)
    //so we do not set them here
    // #i32448#
    // Do not disable, but clear the items.
    aSet.ClearItem(EE_FEATURE_TAB);
    aSet.ClearItem(EE_FEATURE_LINEBR);
    aSet.ClearItem(EE_FEATURE_NOTCONV);
    aSet.ClearItem(EE_FEATURE_FIELD);

    return aSet;
}

void SdrEditView::MergeAttrFromMarked(SfxItemSet& rAttr, bool bOnlyHardAttr) const
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount(rMarkList.GetMarkCount());

    for(size_t a = 0; a < nMarkCount; ++a)
    {
        // #80277# merging was done wrong in the prev version
        SdrObject *pObj = rMarkList.GetMark(a)->GetMarkedSdrObj();
        if (!pObj)
        {
            continue;
        }

        const SfxItemSet& rSet = pObj->GetMergedItemSet();
        SfxWhichIter aIter(rSet);
        sal_uInt16 nWhich(aIter.FirstWhich());

        while(nWhich)
        {
            if(!bOnlyHardAttr)
            {
                if(SfxItemState::INVALID == aIter.GetItemState(false))
                    rAttr.InvalidateItem(nWhich);
                else
                    rAttr.MergeValue(rSet.Get(nWhich));
            }
            else if(SfxItemState::SET == aIter.GetItemState(false))
            {
                const SfxPoolItem& rItem = rSet.Get(nWhich);
                rAttr.MergeValue(rItem);
            }

            if (comphelper::LibreOfficeKit::isActive())
            {
                OUString sPayload;
                switch(nWhich)
                {
                    case XATTR_LINECOLOR:
                    {
                        const SfxPoolItem* pItem = rSet.GetItem(XATTR_LINECOLOR);
                        if (pItem)
                        {
                            Color aColor = static_cast<const XLineColorItem*>(pItem)->GetColorValue();
                            sPayload = OUString::number(static_cast<sal_uInt32>(aColor));

                            sPayload = ".uno:XLineColor=" + sPayload;
                        }
                        break;
                    }

                    case XATTR_FILLCOLOR:
                    {
                        const SfxPoolItem* pItem = rSet.GetItem(XATTR_FILLCOLOR);
                        if (pItem)
                        {
                            Color aColor = static_cast<const XFillColorItem*>(pItem)->GetColorValue();
                            sPayload = OUString::number(static_cast<sal_uInt32>(aColor));

                            sPayload = ".uno:FillColor=" + sPayload;
                        }
                        break;
                    }

                    case XATTR_FILLTRANSPARENCE:
                    {
                        const SfxPoolItem* pItem = rSet.GetItem(XATTR_FILLTRANSPARENCE);
                        if (pItem)
                        {
                            sal_uInt16 nTransparency = static_cast<const XFillTransparenceItem*>(pItem)->GetValue();
                            sPayload = OUString::number(nTransparency);

                            sPayload = ".uno:FillTransparence=" + sPayload;
                        }
                        break;
                    }

                    case XATTR_LINETRANSPARENCE:
                    {
                        const SfxPoolItem* pItem = rSet.GetItem(XATTR_LINETRANSPARENCE);
                        if (pItem)
                        {
                            sal_uInt16 nTransparency = static_cast<const SfxUInt16Item*>(pItem)->GetValue();
                            sPayload = OUString::number(nTransparency);

                            sPayload = ".uno:LineTransparence=" + sPayload;
                        }
                        break;
                    }

                    case XATTR_LINEWIDTH:
                    {
                        const SfxPoolItem* pItem = rSet.GetItem(XATTR_LINEWIDTH);
                        if (pItem)
                        {
                            sal_uInt32 nWidth = static_cast<const XLineWidthItem*>(pItem)->GetValue();
                            sPayload = OUString::number(nWidth);

                            sPayload = ".uno:LineWidth=" + sPayload;
                        }
                        break;
                    }

                    case SDRATTR_SHADOWTRANSPARENCE:
                    {
                        const SfxPoolItem* pItem = rSet.GetItem(SDRATTR_SHADOWTRANSPARENCE);
                        if (pItem)
                        {
                            sal_uInt16 nWidth = static_cast<const SfxUInt16Item*>(pItem)->GetValue();
                            sPayload = OUString::number(nWidth);

                            sPayload = ".uno:FillShadowTransparency=" + sPayload;
                        }
                        break;
                    }
                }

                if (!sPayload.isEmpty())
                {
                    if (SfxViewShell* pViewShell = GetSfxViewShell())
                    {
                        pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
                            OUStringToOString(sPayload, RTL_TEXTENCODING_ASCII_US));
                    }
                }
            }

            nWhich = aIter.NextWhich();
        }
    }
}

std::vector<sal_uInt16> GetAllCharPropIds(const SfxItemSet& rSet)
{
    std::vector<sal_uInt16> aCharWhichIds;
    {
        SfxItemIter aIter(rSet);
        for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
        {
            if (!IsInvalidItem(pItem))
            {
                sal_uInt16 nWhich = pItem->Which();
                if (nWhich>=EE_CHAR_START && nWhich<=EE_CHAR_END)
                    aCharWhichIds.push_back( nWhich );
            }
        }
    }
    return aCharWhichIds;
}

std::vector<sal_uInt16> GetAllCharPropIds(std::span< const SfxPoolItem* const > aChangedItems)
{
    std::vector<sal_uInt16> aCharWhichIds;
    for (const SfxPoolItem* pItem : aChangedItems)
    {
        sal_uInt16 nWhich = pItem->Which();
        if (nWhich>=EE_CHAR_START && nWhich<=EE_CHAR_END)
            aCharWhichIds.push_back( nWhich );
    }
    return aCharWhichIds;
}

void SdrEditView::SetAttrToMarked(const SfxItemSet& rAttr, bool bReplaceAll)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount() == 0)
        return;

#ifdef DBG_UTIL
    {
        bool bHasEEFeatureItems=false;
        SfxItemIter aIter(rAttr);
        for (const SfxPoolItem* pItem = aIter.GetCurItem(); !bHasEEFeatureItems && pItem;
             pItem = aIter.NextItem())
        {
            if (!IsInvalidItem(pItem)) {
                sal_uInt16 nW=pItem->Which();
                if (nW>=EE_FEATURE_START && nW<=EE_FEATURE_END) bHasEEFeatureItems=true;
            }
        }
        if(bHasEEFeatureItems)
        {
            std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr,
                                                          VclMessageType::Info, VclButtonsType::Ok,
                                                          u"SdrEditView::SetAttrToMarked(): Setting EE_FEATURE items at the SdrView does not make sense! It only leads to overhead and unreadable documents."_ustr));
            xInfoBox->run();
        }
    }
#endif

    // #103836# if the user sets character attributes to the complete shape,
    //          we want to remove all hard set character attributes with same
    //          which ids from the text. We do that later but here we remember
    //          all character attribute which id's that are set.
    std::vector<sal_uInt16> aCharWhichIds(GetAllCharPropIds(rAttr));

    // To make Undo reconstruct text attributes correctly after Format.Standard
    bool bHasEEItems=SearchOutlinerItems(rAttr,bReplaceAll);

    // save additional geometry information when paragraph or character attributes
    // are changed and the geometrical shape of the text object might be changed
    bool bPossibleGeomChange(false);
    SfxWhichIter aIter(rAttr);
    sal_uInt16 nWhich = aIter.FirstWhich();
    while(!bPossibleGeomChange && nWhich)
    {
        SfxItemState eState = aIter.GetItemState();
        if(eState == SfxItemState::SET)
        {
            if((nWhich >= SDRATTR_TEXT_MINFRAMEHEIGHT && nWhich <= SDRATTR_TEXT_CONTOURFRAME)
                || nWhich == SDRATTR_3DOBJ_PERCENT_DIAGONAL
                || nWhich == SDRATTR_3DOBJ_BACKSCALE
                || nWhich == SDRATTR_3DOBJ_DEPTH
                || nWhich == SDRATTR_3DOBJ_END_ANGLE
                || nWhich == SDRATTR_3DSCENE_DISTANCE)
            {
                bPossibleGeomChange = true;
            }
        }
        nWhich = aIter.NextWhich();
    }

    const bool bUndo = IsUndoEnabled();
    if( bUndo )
    {
        EndTextEditCurrentView();
        BegUndo(ImpGetDescriptionString(STR_EditSetAttributes));
    }

    const size_t nMarkCount(rMarkList.GetMarkCount());
    std::vector< E3DModifySceneSnapRectUpdater* > aUpdaters;

    // create ItemSet without SfxItemState::INVALID. Put()
    // uses its second parameter (bInvalidAsDefault) to
    // remove all such items to set them to default.
    SfxItemSet aAttr(*rAttr.GetPool(), rAttr.GetRanges());
    aAttr.Put(rAttr);

    // #i38135#
    bool bResetAnimationTimer(false);

    const bool bLineStartWidthExplicitChange(SfxItemState::SET
                                             == aAttr.GetItemState(XATTR_LINESTARTWIDTH));
    const bool bLineEndWidthExplicitChange(SfxItemState::SET
                                           == aAttr.GetItemState(XATTR_LINEENDWIDTH));
    // check if LineWidth is part of the change
    const bool bAdaptStartEndWidths(!(bLineStartWidthExplicitChange && bLineEndWidthExplicitChange)
                                     && SfxItemState::SET == aAttr.GetItemState(XATTR_LINEWIDTH));
    sal_Int32 nNewLineWidth(0);

    if(bAdaptStartEndWidths)
    {
        nNewLineWidth = aAttr.Get(XATTR_LINEWIDTH).GetValue();
    }

    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pObj = pM->GetMarkedSdrObj();

        if( bUndo )
        {
            SdrEdgeObj* pEdgeObj = dynamic_cast< SdrEdgeObj* >( pObj );
            if ( pEdgeObj )
                bPossibleGeomChange = true;
            else
                AddUndoActions( CreateConnectorUndo( *pObj ) );
        }

        // new geometry undo
        if(bPossibleGeomChange && bUndo)
        {
            // save position and size of object, too
            AddUndo( GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj));
        }

        if( bUndo )
        {
            // #i8508#
            // If this is a text object also rescue the OutlinerParaObject since
            // applying attributes to the object may change text layout when
            // multiple portions exist with multiple formats. If an OutlinerParaObject
            // really exists and needs to be rescued is evaluated in the undo
            // implementation itself.
            const bool bRescueText = DynCastSdrTextObj(pObj) != nullptr;

            // add attribute undo
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoAttrObject(*pObj,false,bHasEEItems || bPossibleGeomChange || bRescueText));
        }

        // set up a scene updater if object is a 3d object
        if(DynCastE3dObject(pObj))
        {
            aUpdaters.push_back(new E3DModifySceneSnapRectUpdater(pObj));
        }

        sal_Int32 nOldLineWidth(0);
        if (bAdaptStartEndWidths)
        {
            nOldLineWidth = pObj->GetMergedItem(XATTR_LINEWIDTH).GetValue();
        }

        // set attributes at object
        pObj->SetMergedItemSetAndBroadcast(aAttr, bReplaceAll);

        if(bAdaptStartEndWidths)
        {
            const SfxItemSet& rSet = pObj->GetMergedItemSet();

            if(nOldLineWidth != nNewLineWidth)
            {
                if(SfxItemState::INVALID != rSet.GetItemState(XATTR_LINESTARTWIDTH))
                {
                    const sal_Int32 nValAct(rSet.Get(XATTR_LINESTARTWIDTH).GetValue());
                    const sal_Int32 nValNewStart(std::max(sal_Int32(0), nValAct + (((nNewLineWidth - nOldLineWidth) * 15) / 10)));

                    pObj->SetMergedItem(XLineStartWidthItem(nValNewStart));
                }

                if(SfxItemState::INVALID != rSet.GetItemState(XATTR_LINEENDWIDTH))
                {
                    const sal_Int32 nValAct(rSet.Get(XATTR_LINEENDWIDTH).GetValue());
                    const sal_Int32 nValNewEnd(std::max(sal_Int32(0), nValAct + (((nNewLineWidth - nOldLineWidth) * 15) / 10)));

                    pObj->SetMergedItem(XLineEndWidthItem(nValNewEnd));
                }
            }
        }

        if(auto pTextObj = DynCastSdrTextObj( pObj))
        {
            if(!aCharWhichIds.empty())
            {
                tools::Rectangle aOldBoundRect = pTextObj->GetLastBoundRect();

                // #110094#-14 pTextObj->SendRepaintBroadcast(pTextObj->GetBoundRect());
                pTextObj->RemoveOutlinerCharacterAttribs( aCharWhichIds );

                // object has changed, should be called from
                // RemoveOutlinerCharacterAttribs. This will change when the text
                // object implementation changes.
                pTextObj->SetChanged();

                pTextObj->BroadcastObjectChange();
                pTextObj->SendUserCall(SdrUserCallType::ChangeAttr, aOldBoundRect);
            }
        }

        // #i38495#
        if(!bResetAnimationTimer)
        {
            if(pObj->GetViewContact().isAnimatedInAnyViewObjectContact())
            {
                bResetAnimationTimer = true;
            }
        }
    }

    // fire scene updaters
    while(!aUpdaters.empty())
    {
        delete aUpdaters.back();
        aUpdaters.pop_back();
    }

    // #i38135#
    if(bResetAnimationTimer)
    {
        SetAnimationTimer(0);
    }

    // better check before what to do:
    // pObj->SetAttr() or SetNotPersistAttr()
    // TODO: missing implementation!
    SetNotPersistAttrToMarked(rAttr);

    if( bUndo )
        EndUndo();
}

SfxStyleSheet* SdrEditView::GetStyleSheetFromMarked() const
{
    SfxStyleSheet* pRet=nullptr;
    bool b1st=true;
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount; ++nm) {
        SdrMark* pM=rMarkList.GetMark(nm);
        SfxStyleSheet* pSS=pM->GetMarkedSdrObj()->GetStyleSheet();
        if (b1st) pRet=pSS;
        else if (pRet!=pSS) return nullptr; // different stylesheets
        b1st=false;
    }
    return pRet;
}

void SdrEditView::SetStyleSheetToMarked(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount() == 0)
        return;

    const bool bUndo = IsUndoEnabled();

    if( bUndo )
    {
        EndTextEditCurrentView();
        OUString aStr;
        if (pStyleSheet!=nullptr)
            aStr = ImpGetDescriptionString(STR_EditSetStylesheet);
        else
            aStr = ImpGetDescriptionString(STR_EditDelStylesheet);
        BegUndo(aStr);
    }

    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        if( bUndo )
        {
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pM->GetMarkedSdrObj()));
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoAttrObject(*pM->GetMarkedSdrObj(),true,true));
        }
        pM->GetMarkedSdrObj()->SetStyleSheet(pStyleSheet,bDontRemoveHardAttr);
    }

    if( bUndo )
        EndUndo();
}


void SdrEditView::GetAttributes(SfxItemSet& rTargetSet, bool bOnlyHardAttr) const
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if(rMarkList.GetMarkCount())
    {
        rTargetSet.Put(GetAttrFromMarked(bOnlyHardAttr), false);
    }
    else
    {
        SdrMarkView::GetAttributes(rTargetSet, bOnlyHardAttr);
    }
}

void SdrEditView::SetAttributes(const SfxItemSet& rSet, bool bReplaceAll)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount()!=0) {
        SetAttrToMarked(rSet,bReplaceAll);
    } else {
        SdrMarkView::SetAttributes(rSet,bReplaceAll);
    }
}

SfxStyleSheet* SdrEditView::GetStyleSheet() const
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount()!=0) {
        return GetStyleSheetFromMarked();
    } else {
        return SdrMarkView::GetStyleSheet();
    }
}

void SdrEditView::SetStyleSheet(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount()!=0) {
        SetStyleSheetToMarked(pStyleSheet,bDontRemoveHardAttr);
    } else {
        SdrMarkView::SetStyleSheet(pStyleSheet,bDontRemoveHardAttr);
    }
}


SfxItemSet SdrEditView::GetGeoAttrFromMarked() const
{
    SfxItemSet aRetSet(
        GetModel().GetItemPool(),
        svl::Items< // SID_ATTR_TRANSFORM_... from s:svxids.hrc
            SDRATTR_CORNER_RADIUS, SDRATTR_CORNER_RADIUS,
            SID_ATTR_TRANSFORM_POS_X, SID_ATTR_TRANSFORM_ANGLE,
            SID_ATTR_TRANSFORM_PROTECT_POS, SID_ATTR_TRANSFORM_AUTOHEIGHT>);

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount() != 0)
    {
        SfxItemSet aMarkAttr(GetAttrFromMarked(false)); // because of AutoGrowHeight and corner radius
        tools::Rectangle aRect(GetMarkedObjRect());

        if(GetSdrPageView())
        {
            GetSdrPageView()->LogicToPagePos(aRect);
        }

        // position
        aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_POS_X,aRect.Left()));
        aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_POS_Y,aRect.Top()));

        // size
        tools::Long nResizeRefX=aRect.Left();
        tools::Long nResizeRefY=aRect.Top();
        if (meDragMode==SdrDragMode::Rotate) { // use rotation axis as a reference for resizing, too
            nResizeRefX=maRef1.X();
            nResizeRefY=maRef1.Y();
        }
        aRetSet.Put(SfxUInt32Item(SID_ATTR_TRANSFORM_WIDTH,aRect.Right()-aRect.Left()));
        aRetSet.Put(SfxUInt32Item(SID_ATTR_TRANSFORM_HEIGHT,aRect.Bottom()-aRect.Top()));
        aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_RESIZE_REF_X,nResizeRefX));
        aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_RESIZE_REF_Y,nResizeRefY));

        Point aRotateAxe(maRef1);

        if(GetSdrPageView())
        {
            GetSdrPageView()->LogicToPagePos(aRotateAxe);
        }

        // rotation
        tools::Long nRotateRefX=aRect.Center().X();
        tools::Long nRotateRefY=aRect.Center().Y();
        if (meDragMode==SdrDragMode::Rotate) {
            nRotateRefX=aRotateAxe.X();
            nRotateRefY=aRotateAxe.Y();
        }
        aRetSet.Put(SdrAngleItem(SID_ATTR_TRANSFORM_ANGLE,GetMarkedObjRotate()));
        aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_ROT_X,nRotateRefX));
        aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_ROT_Y,nRotateRefY));

        // shearing
        tools::Long nShearRefX=aRect.Left();
        tools::Long nShearRefY=aRect.Bottom();
        if (meDragMode==SdrDragMode::Rotate) { // use rotation axis as a reference for shearing, too
            nShearRefX=aRotateAxe.X();
            nShearRefY=aRotateAxe.Y();
        }
        aRetSet.Put(SdrAngleItem(SID_ATTR_TRANSFORM_SHEAR,GetMarkedObjShear()));
        aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_SHEAR_X,nShearRefX));
        aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_SHEAR_Y,nShearRefY));

        // check every object whether it is protected
        const size_t nMarkCount=rMarkList.GetMarkCount();
        SdrObject* pObj=rMarkList.GetMark(0)->GetMarkedSdrObj();
        bool bPosProt=pObj->IsMoveProtect();
        bool bSizProt=pObj->IsResizeProtect();
        bool bPosProtDontCare=false;
        bool bSizProtDontCare=false;
        for (size_t i=1; i<nMarkCount && (!bPosProtDontCare || !bSizProtDontCare); ++i)
        {
            pObj=rMarkList.GetMark(i)->GetMarkedSdrObj();
            if (bPosProt!=pObj->IsMoveProtect()) bPosProtDontCare=true;
            if (bSizProt!=pObj->IsResizeProtect()) bSizProtDontCare=true;
        }

        // InvalidateItem sets item to DONT_CARE
        if (bPosProtDontCare) {
            aRetSet.InvalidateItem(SID_ATTR_TRANSFORM_PROTECT_POS);
        } else {
            aRetSet.Put(SfxBoolItem(SID_ATTR_TRANSFORM_PROTECT_POS,bPosProt));
        }
        if (bSizProtDontCare) {
            aRetSet.InvalidateItem(SID_ATTR_TRANSFORM_PROTECT_SIZE);
        } else {
            aRetSet.Put(SfxBoolItem(SID_ATTR_TRANSFORM_PROTECT_SIZE,bSizProt));
        }

        SfxItemState eState=aMarkAttr.GetItemState(SDRATTR_TEXT_AUTOGROWWIDTH);
        bool bAutoGrow=aMarkAttr.Get(SDRATTR_TEXT_AUTOGROWWIDTH).GetValue();
        if (eState==SfxItemState::INVALID) {
            aRetSet.InvalidateItem(SID_ATTR_TRANSFORM_AUTOWIDTH);
        } else if (eState==SfxItemState::SET) {
            aRetSet.Put(SfxBoolItem(SID_ATTR_TRANSFORM_AUTOWIDTH,bAutoGrow));
        }

        eState=aMarkAttr.GetItemState(SDRATTR_TEXT_AUTOGROWHEIGHT);
        bAutoGrow=aMarkAttr.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue();
        if (eState==SfxItemState::INVALID) {
            aRetSet.InvalidateItem(SID_ATTR_TRANSFORM_AUTOHEIGHT);
        } else if (eState==SfxItemState::SET) {
            aRetSet.Put(SfxBoolItem(SID_ATTR_TRANSFORM_AUTOHEIGHT,bAutoGrow));
        }

        eState=aMarkAttr.GetItemState(SDRATTR_CORNER_RADIUS);
        tools::Long nRadius=aMarkAttr.Get(SDRATTR_CORNER_RADIUS).GetValue();
        if (eState==SfxItemState::INVALID) {
            aRetSet.InvalidateItem(SDRATTR_CORNER_RADIUS);
        } else if (eState==SfxItemState::SET) {
            aRetSet.Put(makeSdrEckenradiusItem(nRadius));
        }

        basegfx::B2DHomMatrix aTransformation;

        if(nMarkCount > 1)
        {
            // multiple objects, range is collected in aRect
            aTransformation = basegfx::utils::createScaleTranslateB2DHomMatrix(
                aRect.Left(), aRect.Top(),
                aRect.getOpenWidth(), aRect.getOpenHeight());
        }
        else
        {
            // single object, get homogen transformation
            basegfx::B2DPolyPolygon aPolyPolygon;

            pObj->TRGetBaseGeometry(aTransformation, aPolyPolygon);
        }

        if(aTransformation.isIdentity())
        {
            aRetSet.InvalidateItem(SID_ATTR_TRANSFORM_MATRIX);
        }
        else
        {
            css::geometry::AffineMatrix2D aAffineMatrix2D;
            Point aPageOffset(0, 0);

            if(GetSdrPageView())
            {
                aPageOffset = GetSdrPageView()->GetPageOrigin();
            }

            aAffineMatrix2D.m00 = aTransformation.get(0, 0);
            aAffineMatrix2D.m01 = aTransformation.get(0, 1);
            aAffineMatrix2D.m02 = aTransformation.get(0, 2) - aPageOffset.X();
            aAffineMatrix2D.m10 = aTransformation.get(1, 0);
            aAffineMatrix2D.m11 = aTransformation.get(1, 1);
            aAffineMatrix2D.m12 = aTransformation.get(1, 2) - aPageOffset.Y();

            aRetSet.Put(AffineMatrixItem(&aAffineMatrix2D));
        }
    }

    return aRetSet;
}

static Point ImpGetPoint(const tools::Rectangle& rRect, RectPoint eRP)
{
    switch(eRP) {
        case RectPoint::LT: return rRect.TopLeft();
        case RectPoint::MT: return rRect.TopCenter();
        case RectPoint::RT: return rRect.TopRight();
        case RectPoint::LM: return rRect.LeftCenter();
        case RectPoint::MM: return rRect.Center();
        case RectPoint::RM: return rRect.RightCenter();
        case RectPoint::LB: return rRect.BottomLeft();
        case RectPoint::MB: return rRect.BottomCenter();
        case RectPoint::RB: return rRect.BottomRight();
    }
    return Point(); // Should not happen!
}

void SdrEditView::SetGeoAttrToMarked(const SfxItemSet& rAttr, bool addPageMargin)
{
    tools::Rectangle aRect(GetMarkedObjRect());

    if(GetSdrPageView())
    {
        if (addPageMargin)
        {
            SdrPage * pPage = GetSdrPageView()->GetPage();
            Point upperLeft(pPage->GetLeftBorder(), pPage->GetUpperBorder());
            aRect.Move(upperLeft.getX(), upperLeft.getY());
        }
        GetSdrPageView()->LogicToPagePos(aRect);
    }

    Degree100 nOldRotateAngle=GetMarkedObjRotate();
    Degree100 nOldShearAngle=GetMarkedObjShear();
    const SdrMarkList& rMarkList=GetMarkedObjectList();
    SdrObject* pObj=nullptr;

    RectPoint eSizePoint=RectPoint::MM;
    tools::Long nPosDX=0;
    tools::Long nPosDY=0;
    tools::Long nSizX=0;
    tools::Long nSizY=0;
    Degree100 nRotateAngle(0);

    bool bModeIsRotate(meDragMode == SdrDragMode::Rotate);
    tools::Long nRotateX(0);
    tools::Long nRotateY(0);
    tools::Long nOldRotateX(0);
    tools::Long nOldRotateY(0);
    if(bModeIsRotate)
    {
        Point aRotateAxe(maRef1);

        if(GetSdrPageView())
        {
            GetSdrPageView()->LogicToPagePos(aRotateAxe);
        }

        nRotateX = nOldRotateX = aRotateAxe.X();
        nRotateY = nOldRotateY = aRotateAxe.Y();
    }

    Degree100 nShearAngle(0);
    tools::Long nShearX=0;
    tools::Long nShearY=0;
    bool bShearVert=false;

    bool bChgPos=false;
    bool bChgSiz=false;
    bool bChgWdh=false;
    bool bChgHgt=false;
    bool bRotate=false;
    bool bShear =false;

    bool bSetAttr=false;
    SfxItemSet aSetAttr(GetModel().GetItemPool());

    // position
    if (const SfxInt32Item *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_POS_X))
    {
        nPosDX = pPoolItem->GetValue() - aRect.Left();
        bChgPos=true;
    }
    if (const SfxInt32Item *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_POS_Y))
    {
        nPosDY = pPoolItem->GetValue() - aRect.Top();
        bChgPos=true;
    }
    // size
    if (const SfxUInt32Item *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_WIDTH))
    {
        nSizX = pPoolItem->GetValue();
        bChgSiz=true;
        bChgWdh=true;
    }
    if (const SfxUInt32Item *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_HEIGHT))
    {
        nSizY = pPoolItem->GetValue();
        bChgSiz=true;
        bChgHgt=true;
    }

    if (bChgSiz)
    {
        // tdf#164285 the item was accessed even when not being set if
        // false == comphelper::LibreOfficeKit::isActive(). For SlotIDs
        // like SID_ATTR_TRANSFORM_SIZE_POINT this will crash by asserting
        // due to SlotIDs having no defaults (see
        // SfxItemPool::GetUserOrPoolDefaultItem line 725)
        if (const SfxUInt16Item* pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_SIZE_POINT))
            eSizePoint = static_cast<RectPoint>(pPoolItem->GetValue());
        else if (comphelper::LibreOfficeKit::isActive())
            // for TiledRendering the default which is set at the start of
            // this method to RectPoint::MM seems purposely being changed to
            // RectPoint::LT here - from a change 6 years ago. Note that this
            // will make choosing another definition point in the UI for the
            // PosSizeDialog except TopLeft not working when
            // comphelper::LibreOfficeKit *is* active.
            eSizePoint = RectPoint::LT;
    }

    // rotation
    if (const SdrAngleItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_DELTA_ANGLE))
    {
        nRotateAngle = pPoolItem->GetValue();
        bRotate = (nRotateAngle != 0_deg100);
    }

    // rotation
    if (const SdrAngleItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_ANGLE))
    {
        nRotateAngle = pPoolItem->GetValue() - nOldRotateAngle;
        bRotate = (nRotateAngle != 0_deg100);
    }

    // position rotation point x
    if(bRotate || rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_ROT_X))
        nRotateX = rAttr.Get(SID_ATTR_TRANSFORM_ROT_X).GetValue();

    // position rotation point y
    if(bRotate || rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_ROT_Y))
        nRotateY = rAttr.Get(SID_ATTR_TRANSFORM_ROT_Y).GetValue();

    // shearing
    if (const SdrAngleItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_SHEAR))
    {
        Degree100 nNewShearAngle=pPoolItem->GetValue();
        if (nNewShearAngle>SDRMAXSHEAR) nNewShearAngle=SDRMAXSHEAR;
        if (nNewShearAngle<-SDRMAXSHEAR) nNewShearAngle=-SDRMAXSHEAR;
        if (nNewShearAngle!=nOldShearAngle) {
            bShearVert = rAttr.Get(SID_ATTR_TRANSFORM_SHEAR_VERTICAL).GetValue();
            if (bShearVert) {
                nShearAngle=nNewShearAngle;
            } else {
                if (nNewShearAngle!=0_deg100 && nOldShearAngle!=0_deg100) {
                    // bug fix
                    double nOld = tan(toRadians(nOldShearAngle));
                    double nNew = tan(toRadians(nNewShearAngle));
                    nNew-=nOld;
                    nNew = basegfx::rad2deg<100>(atan(nNew));
                    nShearAngle = Degree100(basegfx::fround(nNew));
                } else {
                    nShearAngle=nNewShearAngle-nOldShearAngle;
                }
            }
            bShear=nShearAngle!=0_deg100;
            if (bShear) {
                nShearX = rAttr.Get(SID_ATTR_TRANSFORM_SHEAR_X).GetValue();
                nShearY = rAttr.Get(SID_ATTR_TRANSFORM_SHEAR_Y).GetValue();
            }
        }
    }

    // AutoGrow
    if (const SfxBoolItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_AUTOWIDTH))
    {
        bool bAutoGrow = pPoolItem->GetValue();
        aSetAttr.Put(makeSdrTextAutoGrowWidthItem(bAutoGrow));
        bSetAttr=true;
    }

    if (const SfxBoolItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_AUTOHEIGHT))
    {
        bool bAutoGrow = pPoolItem->GetValue();
        aSetAttr.Put(makeSdrTextAutoGrowHeightItem(bAutoGrow));
        bSetAttr=true;
    }

    // corner radius
    if (m_bEdgeRadiusAllowed)
        if (const SdrMetricItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_CORNER_RADIUS))
        {
            tools::Long nRadius = pPoolItem->GetValue();
            aSetAttr.Put(makeSdrEckenradiusItem(nRadius));
            bSetAttr=true;
        }

    ForcePossibilities();

    BegUndo(SvxResId(STR_EditTransform),rMarkList.GetMarkDescription());

    if (bSetAttr) {
        SetAttrToMarked(aSetAttr,false);
    }

    // change size and height
    if (bChgSiz && (m_bResizeFreeAllowed || m_bResizePropAllowed)) {
        Fraction aWdt(nSizX,aRect.Right()-aRect.Left());
        Fraction aHgt(nSizY,aRect.Bottom()-aRect.Top());
        Point aRef(ImpGetPoint(aRect,eSizePoint));

        if(GetSdrPageView())
        {
            GetSdrPageView()->PagePosToLogic(aRef);
        }

        ResizeMultMarkedObj(aRef, aWdt, aHgt, bChgWdh, bChgHgt);
    }

    // rotate
    if (bRotate && (m_bRotateFreeAllowed || m_bRotate90Allowed)) {
        Point aRef(nRotateX,nRotateY);

        if(GetSdrPageView())
        {
            GetSdrPageView()->PagePosToLogic(aRef);
        }

        RotateMarkedObj(aRef,nRotateAngle);
    }

    // set rotation point position
    if(bModeIsRotate && (nRotateX != nOldRotateX || nRotateY != nOldRotateY))
    {
        Point aNewRef1(nRotateX, nRotateY);

        if(GetSdrPageView())
        {
            GetSdrPageView()->PagePosToLogic(aNewRef1);
        }

        SetRef1(aNewRef1);
    }

    // shear
    if (bShear && m_bShearAllowed) {
        Point aRef(nShearX,nShearY);

        if(GetSdrPageView())
        {
            GetSdrPageView()->PagePosToLogic(aRef);
        }

        ShearMarkedObj(aRef,nShearAngle,bShearVert);

        // #i74358#
        // ShearMarkedObj creates a linear combination of the existing transformation and
        // the new shear to apply. If the object is already transformed (e.g. rotated) the
        // linear combination will not decompose to the same start values again, but to a
        // new combination. Thus it makes no sense to check if the wanted shear is reached
        // or not. Taking out.
    }

    // change position
    bool bMoveAllowed = m_bMoveAllowed;
    SfxViewShell* pViewShell = GetSfxViewShell();
    if (!bMoveAllowed && pViewShell && pViewShell->GetSignPDFCertificate().Is())
    {
        // If the just added signature line shape is selected, allow moving it.
        bMoveAllowed = true;
    }
    if (bChgPos && bMoveAllowed) {
        MoveMarkedObj(Size(nPosDX,nPosDY));
    }

    const size_t nMarkCount=rMarkList.GetMarkCount();
    // protect position
    if(const SfxBoolItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_PROTECT_POS))
    {
        const bool bProtPos(pPoolItem->GetValue());
        bool bChanged(false);

        for(size_t i = 0; i < nMarkCount; ++i)
        {
            pObj = rMarkList.GetMark(i)->GetMarkedSdrObj();

            if(pObj->IsMoveProtect() != bProtPos)
            {
                bChanged = true;
                pObj->SetMoveProtect(bProtPos);

                if(bProtPos)
                {
                    pObj->SetResizeProtect(true);
                }
            }
        }

        if(bChanged)
        {
            m_bMoveProtect = bProtPos;

            if(bProtPos)
            {
                m_bResizeProtect = true;
            }

            // #i77187# there is no simple method to get the toolbars updated
            // in the application. The App is listening to selection change and i
            // will use it here (even if not true). It's acceptable since changing
            // this model data is pretty rare and only possible using the F4 dialog
            MarkListHasChanged();
        }
    }

    if(!m_bMoveProtect)
    {
        // protect size
        if(const SfxBoolItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_PROTECT_SIZE))
        {
            const bool bProtSize(pPoolItem->GetValue());
            bool bChanged(false);

            for(size_t i = 0; i < nMarkCount; ++i)
            {
                pObj = rMarkList.GetMark(i)->GetMarkedSdrObj();

                if(pObj->IsResizeProtect() != bProtSize)
                {
                    bChanged = true;
                    pObj->SetResizeProtect(bProtSize);
                }
            }

            if(bChanged)
            {
                m_bResizeProtect = bProtSize;

                // #i77187# see above
                MarkListHasChanged();
            }
        }
    }

    EndUndo();
}


bool SdrEditView::IsAlignPossible() const
{  // at least two selected objects, at least one of them movable
    ForcePossibilities();
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nCount=rMarkList.GetMarkCount();
    if (nCount==0) return false;         // nothing selected!
    if (nCount==1) return m_bMoveAllowed;  // align single object to page
    return m_bOneOrMoreMovable;          // otherwise: MarkCount>=2
}

void SdrEditView::AlignMarkedObjects(SdrHorAlign eHor, SdrVertAlign eVert)
{
    if (eHor==SdrHorAlign::NONE && eVert==SdrVertAlign::NONE)
        return;

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    rMarkList.ForceSort();
    if (!rMarkList.GetMarkCount())
        return;

    const bool bUndo = IsUndoEnabled();
    if( bUndo )
    {
        EndTextEditCurrentView();
        OUString aStr(rMarkList.GetMarkDescription());
        if (eHor==SdrHorAlign::NONE)
        {
            switch (eVert)
            {
                case SdrVertAlign::Top:
                    aStr = ImpGetDescriptionString(STR_EditAlignVTop);
                    break;
                case SdrVertAlign::Bottom:
                    aStr = ImpGetDescriptionString(STR_EditAlignVBottom);
                    break;
                case SdrVertAlign::Center:
                    aStr = ImpGetDescriptionString(STR_EditAlignVCenter);
                    break;
                default: break;
            }
        }
        else if (eVert==SdrVertAlign::NONE)
        {
            switch (eHor)
            {
                case SdrHorAlign::Left:
                    aStr = ImpGetDescriptionString(STR_EditAlignHLeft);
                    break;
                case SdrHorAlign::Right:
                    aStr = ImpGetDescriptionString(STR_EditAlignHRight);
                    break;
                case SdrHorAlign::Center:
                    aStr = ImpGetDescriptionString(STR_EditAlignHCenter);
                    break;
                default: break;
            }
        }
        else if (eHor==SdrHorAlign::Center && eVert==SdrVertAlign::Center)
        {
            aStr = ImpGetDescriptionString(STR_EditAlignCenter);
        }
        else
        {
            aStr = ImpGetDescriptionString(STR_EditAlign);
        }
        BegUndo(aStr);
    }

    tools::Rectangle aBound;
    const size_t nMarkCount=rMarkList.GetMarkCount();
    bool bHasFixed=false;
    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pObj=pM->GetMarkedSdrObj();
        SdrObjTransformInfoRec aInfo;
        pObj->TakeObjInfo(aInfo);
        if (!aInfo.bMoveAllowed || pObj->IsMoveProtect())
        {
            tools::Rectangle aObjRect(pObj->GetSnapRect());
            aBound.Union(aObjRect);
            bHasFixed=true;
        }
    }
    if (!bHasFixed)
    {
        if (nMarkCount==1)
        {   // align single object to page
            const SdrObject* pObj=rMarkList.GetMark(0)->GetMarkedSdrObj();
            const SdrPage* pPage=pObj->getSdrPageFromSdrObject();
            const SdrPageGridFrameList* pGFL=pPage->GetGridFrameList(rMarkList.GetMark(0)->GetPageView(),&(pObj->GetSnapRect()));
            const SdrPageGridFrame* pFrame=nullptr;
            if (pGFL!=nullptr && pGFL->GetCount()!=0)
            { // Writer
                pFrame=&((*pGFL)[0]);
            }

            if (pFrame!=nullptr)
            { // Writer
                aBound=pFrame->GetUserArea();
            }
            else
            {
                aBound=tools::Rectangle(pPage->GetLeftBorder(),pPage->GetUpperBorder(),
                                 pPage->GetWidth()-pPage->GetRightBorder(),
                                 pPage->GetHeight()-pPage->GetLowerBorder());
            }
        }
        else
        {
            aBound=GetMarkedObjRect();
        }
    }
    Point aCenter(aBound.Center());
    for (size_t nm=0; nm<nMarkCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pObj=pM->GetMarkedSdrObj();
        SdrObjTransformInfoRec aInfo;
        pObj->TakeObjInfo(aInfo);
        if (aInfo.bMoveAllowed && !pObj->IsMoveProtect())
        {
            tools::Long nXMov=0;
            tools::Long nYMov=0;
            tools::Rectangle aObjRect(pObj->GetSnapRect());
            switch (eVert)
            {
                case SdrVertAlign::Top   : nYMov=aBound.Top()   -aObjRect.Top()       ; break;
                case SdrVertAlign::Bottom: nYMov=aBound.Bottom()-aObjRect.Bottom()    ; break;
                case SdrVertAlign::Center: nYMov=aCenter.Y()    -aObjRect.Center().Y(); break;
                default: break;
            }
            switch (eHor)
            {
                case SdrHorAlign::Left  : nXMov=aBound.Left()  -aObjRect.Left()      ; break;
                case SdrHorAlign::Right : nXMov=aBound.Right() -aObjRect.Right()     ; break;
                case SdrHorAlign::Center: nXMov=aCenter.X()    -aObjRect.Center().X(); break;
                default: break;
            }
            if (nXMov!=0 || nYMov!=0)
            {
                // SdrEdgeObj needs an extra SdrUndoGeoObj since the
                // connections may need to be saved
                if( bUndo )
                {
                    if( dynamic_cast<SdrEdgeObj*>(pObj) )
                    {
                        AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj));
                    }

                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoMoveObject(*pObj,Size(nXMov,nYMov)));
                }

                pObj->Move(Size(nXMov,nYMov));
            }
        }
    }

    if( bUndo )
        EndUndo();
}

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