/* -*- 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 <hintids.hxx>
#include <comphelper/lok.hxx>
#include <osl/diagnose.h>
#include <tools/mapunit.hxx>
#include <tools/UnitConversion.hxx>
#include <svx/svdhdl.hxx>
#include <svx/svdtrans.hxx>
#include <editeng/protitem.hxx>
#include <svx/svdpage.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/gdimtf.hxx>
#include <vcl/svapp.hxx>
#include <vcl/ptrstyle.hxx>

#include <fmtclds.hxx>
#include <fmtornt.hxx>
#include <fmtfsize.hxx>
#include <fmturl.hxx>
#include <viewsh.hxx>
#include <frmatr.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <dflyobj.hxx>
#include <flyfrm.hxx>
#include <frmfmt.hxx>
#include <viewopt.hxx>
#include <frmtool.hxx>
#include <flyfrms.hxx>
#include <ndnotxt.hxx>
#include <grfatr.hxx>
#include <pagefrm.hxx>
#include <rootfrm.hxx>
#include <textboxhelper.hxx>
#include <wrtsh.hxx>
#include <ndgrf.hxx>
#include <frmmgr.hxx>

#include <svx/sdr/properties/defaultproperties.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>

// AW: For VCOfDrawVirtObj and stuff
#include <svx/sdr/contact/viewcontactofvirtobj.hxx>
#include <drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <sw_primitivetypes2d.hxx>
#include <drawinglayer/primitive2d/sdrdecompositiontools2d.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <notxtfrm.hxx>

using namespace ::com::sun::star;

static bool bInResize = false;


namespace sdr::contact
{
        namespace {

        /**
         * @see #i95264#
         *
         * currently needed since createViewIndependentPrimitive2DSequence() is called when
         * RecalcBoundRect() is used. There should currently no VOCs being constructed since it
         * gets not visualized (instead the corresponding SwVirtFlyDrawObj's referencing this one
         * are visualized).
         */
        class VCOfSwFlyDrawObj : public ViewContactOfSdrObj
        {
        protected:
            /** This method is responsible for creating the graphical visualisation data
             *
             * @note ONLY based on model data
             */
            virtual void createViewIndependentPrimitive2DSequence(drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const override;

        public:
            /// basic constructor, used from SdrObject.
            explicit VCOfSwFlyDrawObj(SwFlyDrawObj& rObj)
            :   ViewContactOfSdrObj(rObj)
            {
            }
        };

        }

        void VCOfSwFlyDrawObj::createViewIndependentPrimitive2DSequence(drawinglayer::primitive2d::Primitive2DDecompositionVisitor&) const
        {
            // currently gets not visualized, return empty sequence
        }

} // end of namespace sdr::contact

std::unique_ptr<sdr::properties::BaseProperties> SwFlyDrawObj::CreateObjectSpecificProperties()
{
    // create default properties
    return std::make_unique<sdr::properties::DefaultProperties>(*this);
}

std::unique_ptr<sdr::contact::ViewContact> SwFlyDrawObj::CreateObjectSpecificViewContact()
{
    // needs an own VC since createViewIndependentPrimitive2DSequence()
    // is called when RecalcBoundRect() is used
    return std::make_unique<sdr::contact::VCOfSwFlyDrawObj>(*this);
}

SwFlyDrawObj::SwFlyDrawObj(SdrModel& rSdrModel)
:   SdrObject(rSdrModel),
    mbIsTextBox(false)
{
}

SwFlyDrawObj::~SwFlyDrawObj()
{
}

// SwFlyDrawObj - Factory-Methods
SdrInventor SwFlyDrawObj::GetObjInventor() const
{
    return SdrInventor::Swg;
}

SdrObjKind SwFlyDrawObj::GetObjIdentifier() const
{
    return SdrObjKind::SwFlyDrawObjIdentifier;
}

rtl::Reference<SdrObject> SwFlyDrawObj::CloneSdrObject(SdrModel& rTargetModel) const
{
    return new SwFlyDrawObj(rTargetModel);
}

void SwFlyDrawObj::NbcRotate(const Point& /*rRef*/, Degree100 /*nAngle*/, double /*sinAngle*/, double /*cosAngle*/)
{
    assert(false);
}

// TODO: Need own primitive to get the FlyFrame paint working
namespace drawinglayer::primitive2d
{
        namespace {

        class SwVirtFlyDrawObjPrimitive : public BufferedDecompositionPrimitive2D
        {
        private:
            const SwVirtFlyDrawObj&                 mrSwVirtFlyDrawObj;
            const basegfx::B2DRange                 maOuterRange;

        protected:
            /// method which is to be used to implement the local decomposition of a 2D primitive
            virtual Primitive2DReference create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const override;

        public:
            SwVirtFlyDrawObjPrimitive(
                const SwVirtFlyDrawObj& rSwVirtFlyDrawObj,
                const basegfx::B2DRange &rOuterRange)
            :   mrSwVirtFlyDrawObj(rSwVirtFlyDrawObj),
                maOuterRange(rOuterRange)
            {
            }

            virtual bool operator==(const BasePrimitive2D& rPrimitive) const override;

            virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override;

            // override to allow callbacks to wrap_DoPaintObject
            virtual void get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const override;

            // data read access
            const SwVirtFlyDrawObj& getSwVirtFlyDrawObj() const { return mrSwVirtFlyDrawObj; }
            const basegfx::B2DRange& getOuterRange() const { return maOuterRange; }

            /// provide unique ID
            virtual sal_uInt32 getPrimitive2DID() const override;
        };

        }
} // end of namespace drawinglayer::primitive2d

namespace drawinglayer::primitive2d
{
        Primitive2DReference SwVirtFlyDrawObjPrimitive::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            if(getOuterRange().isEmpty())
                return nullptr;

            // currently this SW object has no primitive representation. As long as this is the case,
            // create invisible geometry to allow correct HitTest and BoundRect calculations for the
            // object. Use a filled primitive to get 'inside' as default object hit. The special cases from
            // the old SwVirtFlyDrawObj::CheckHit implementation are handled now in SwDrawView::PickObj;
            // this removed the 'hack' to get a view from inside model data or to react on null-tolerance
            // as it was done in the old implementation
            return
                createHiddenGeometryPrimitives2D(
                    true,
                    getOuterRange());
        }

        bool SwVirtFlyDrawObjPrimitive::operator==(const BasePrimitive2D& rPrimitive) const
        {
            if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
            {
                const SwVirtFlyDrawObjPrimitive& rCompare = static_cast<const SwVirtFlyDrawObjPrimitive&>(rPrimitive);

                return (&getSwVirtFlyDrawObj() == &rCompare.getSwVirtFlyDrawObj()
                    && getOuterRange() == rCompare.getOuterRange());
            }

            return false;
        }

        basegfx::B2DRange SwVirtFlyDrawObjPrimitive::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            return getOuterRange();
        }

        void SwVirtFlyDrawObjPrimitive::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const
        {
            // This is the callback to keep the FlyFrame painting in SW alive as long as it
            // is not changed to primitives. This is the method which will be called by the processors
            // when they do not know this primitive (and they do not). Inside wrap_DoPaintObject
            // there needs to be a test that paint is only done during SW repaints (see there).
            // Using this mechanism guarantees the correct Z-Order of the VirtualObject-based FlyFrames.
            getSwVirtFlyDrawObj().wrap_DoPaintObject(rViewInformation);

            // call parent
            BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
        }

        // provide unique ID
        sal_uInt32 SwVirtFlyDrawObjPrimitive::getPrimitive2DID() const
        {
            return PRIMITIVE2D_ID_SWVIRTFLYDRAWOBJPRIMITIVE2D;
        }

} // end of namespace drawinglayer::primitive2d

// AW: own sdr::contact::ViewContact (VC) sdr::contact::ViewObjectContact (VOC) needed
// since offset is defined different from SdrVirtObj's sdr::contact::ViewContactOfVirtObj.
// For paint, that offset is used by setting at the OutputDevice; for primitives this is
// not possible since we have no OutputDevice, but define the geometry itself.

namespace sdr::contact
{
        namespace {

        class VCOfSwVirtFlyDrawObj : public ViewContactOfVirtObj
        {
        protected:
            /** This method is responsible for creating the graphical visualisation data
             *
             * @note ONLY based on model data
             */
            virtual void createViewIndependentPrimitive2DSequence(drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const override;

        public:
            /// basic constructor, used from SdrObject.
            explicit VCOfSwVirtFlyDrawObj(SwVirtFlyDrawObj& rObj)
            :   ViewContactOfVirtObj(rObj)
            {
            }

            /// access to SwVirtFlyDrawObj
            SwVirtFlyDrawObj& GetSwVirtFlyDrawObj() const
            {
                return static_cast<SwVirtFlyDrawObj&>(mrObject);
            }
        };

        }
} // end of namespace sdr::contact

namespace sdr::contact
{
        void VCOfSwVirtFlyDrawObj::createViewIndependentPrimitive2DSequence(drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const
        {
            const SdrObject& rReferencedObject = GetSwVirtFlyDrawObj().GetReferencedObj();

            // check if it is a SwFlyDrawObj*
            if (rReferencedObject.GetObjIdentifier() == SdrObjKind::SwFlyDrawObjIdentifier)
            {
                // create an own specialized primitive which is used as repaint callpoint and HitTest
                // for HitTest processor (see primitive implementation above)
                const basegfx::B2DRange aOuterRange(GetSwVirtFlyDrawObj().getOuterBound());

                if(!aOuterRange.isEmpty())
                {
                    const drawinglayer::primitive2d::Primitive2DReference xPrimitive(
                        new drawinglayer::primitive2d::SwVirtFlyDrawObjPrimitive(
                            GetSwVirtFlyDrawObj(),
                            aOuterRange));

                    rVisitor.visit(xPrimitive);
                }
            }
       }

} // end of namespace sdr::contact

basegfx::B2DRange SwVirtFlyDrawObj::getOuterBound() const
{
    basegfx::B2DRange aOuterRange;
    const SdrObject& rReferencedObject = GetReferencedObj();

    // check if it is a SwFlyDrawObj*
    if (rReferencedObject.GetObjIdentifier() == SdrObjKind::SwFlyDrawObjIdentifier)
    {
        const SwFlyFrame* pFlyFrame = GetFlyFrame();

        if(pFlyFrame)
        {
            const tools::Rectangle aOuterRectangle(pFlyFrame->getFrameArea().Pos(), pFlyFrame->getFrameArea().SSize());

            if(!aOuterRectangle.IsEmpty())
            {
                aOuterRange.expand(basegfx::B2DTuple(aOuterRectangle.Left(), aOuterRectangle.Top()));
                aOuterRange.expand(basegfx::B2DTuple(aOuterRectangle.Right(), aOuterRectangle.Bottom()));
            }
        }
    }

    return aOuterRange;
}

basegfx::B2DRange SwVirtFlyDrawObj::getInnerBound() const
{
    basegfx::B2DRange aInnerRange;
    const SdrObject& rReferencedObject = GetReferencedObj();

    if(dynamic_cast<const SwFlyDrawObj*>( &rReferencedObject) !=  nullptr)
    {
        const SwFlyFrame* pFlyFrame = GetFlyFrame();

        if(pFlyFrame)
        {
            const tools::Rectangle aInnerRectangle(pFlyFrame->getFrameArea().Pos() + pFlyFrame->getFramePrintArea().Pos(), pFlyFrame->getFramePrintArea().SSize());

            if(!aInnerRectangle.IsEmpty())
            {
                aInnerRange.expand(basegfx::B2DTuple(aInnerRectangle.Left(), aInnerRectangle.Top()));
                aInnerRange.expand(basegfx::B2DTuple(aInnerRectangle.Right(), aInnerRectangle.Bottom()));
            }
        }
    }

    return aInnerRange;
}

bool SwVirtFlyDrawObj::ContainsSwGrfNode() const
{
    // RotGrfFlyFrame: Check if this is a SwGrfNode
    const SwFlyFrame* pFlyFrame(GetFlyFrame());
    const SwFrame* pLower = pFlyFrame ? pFlyFrame->Lower() : nullptr;

    if(pLower && pLower->IsNoTextFrame())
    {
        const SwNoTextFrame *const pNTF(static_cast<const SwNoTextFrame*>(pLower));

        const SwGrfNode *const pGrfNd(pNTF->GetNode()->GetGrfNode());

        return nullptr != pGrfNd;
    }

    return false;
}

bool SwVirtFlyDrawObj::HasLimitedRotation() const
{
    // RotGrfFlyFrame: If true, this SdrObject supports only limited rotation.
    // This is the case for SwGrfNode instances
    return ContainsSwGrfNode();
}

void SwVirtFlyDrawObj::Rotate(const Point& rRef, Degree100 nAngle100, double sn, double cs)
{
    if(ContainsSwGrfNode())
    {
        // RotGrfFlyFrame: Here is where the positively completed rotate interaction is executed.
        // Rotation is in 1/100th degree and may be signed (!)
        Degree10 nAngle10 = to<Degree10>(nAngle100);

        while(nAngle10 < 0_deg10)
        {
            nAngle10 += 3600_deg10;
        }

        SwWrtShell *pShForAngle = nAngle10 ? dynamic_cast<SwWrtShell*>(GetFlyFrame()->getRootFrame()->GetCurrShell()) : nullptr;
        if (pShForAngle)
        {
            // RotGrfFlyFrame: Add transformation to placeholder object
            Size aSize;
            const Degree10 nOldRot(SwVirtFlyDrawObj::getPossibleRotationFromFraphicFrame(aSize));
            SwFlyFrameAttrMgr aMgr(false, pShForAngle, Frmmgr_Type::NONE, nullptr);

            aMgr.SetRotation(nOldRot, (nOldRot + nAngle10) % 3600_deg10, aSize);
        }
    }
    else
    {
        // call parent
        SdrVirtObj::Rotate(rRef, nAngle100, sn, cs);
    }
}

std::unique_ptr<sdr::contact::ViewContact> SwVirtFlyDrawObj::CreateObjectSpecificViewContact()
{
    // need an own ViewContact (VC) to allow creation of a specialized primitive
    // for being able to visualize the FlyFrames in primitive renderers
    return std::make_unique<sdr::contact::VCOfSwVirtFlyDrawObj>(*this);
}

SwVirtFlyDrawObj::SwVirtFlyDrawObj(
    SdrModel& rSdrModel,
    SdrObject& rNew,
    SwFlyFrame* pFly)
:   SdrVirtObj(rSdrModel, rNew),
    m_pFlyFrame(pFly)
{
    const SvxProtectItem &rP = m_pFlyFrame->GetFormat()->GetProtect();
    m_bMovProt = rP.IsPosProtected();
    m_bSizProt = rP.IsSizeProtected();
}

SwVirtFlyDrawObj::~SwVirtFlyDrawObj()
{
    assert (!getSdrPageFromSdrObject() && "should have already been removed");
}

const SwFrameFormat *SwVirtFlyDrawObj::GetFormat() const
{
    return GetFlyFrame()->GetFormat();
}
SwFrameFormat *SwVirtFlyDrawObj::GetFormat()
{
    return GetFlyFrame()->GetFormat();
}

// --> OD #i102707#
namespace
{
    class RestoreMapMode
    {
        public:
            explicit RestoreMapMode( SwViewShell const * pViewShell )
                : mbMapModeRestored( false )
                , mpOutDev( pViewShell->GetOut() )
            {
                if ( pViewShell->getPrePostMapMode() == mpOutDev->GetMapMode() )
                    return;

                mpOutDev->Push(vcl::PushFlags::MAPMODE);

                GDIMetaFile* pMetaFile = mpOutDev->GetConnectMetaFile();
                if ( pMetaFile &&
                     pMetaFile->IsRecord() && !pMetaFile->IsPause() )
                {
                    OSL_FAIL( "MapMode restoration during meta file creation is somehow suspect - using <SetRelativeMapMode(..)>, but not sure, if correct." );
                    mpOutDev->SetRelativeMapMode( pViewShell->getPrePostMapMode() );
                }
                else
                {
                    mpOutDev->SetMapMode( pViewShell->getPrePostMapMode() );
                }

                mbMapModeRestored = true;
            };

            ~RestoreMapMode()
            {
                if ( mbMapModeRestored )
                {
                    mpOutDev->Pop();
                }
            };

        private:
            bool mbMapModeRestored;
            VclPtr<OutputDevice> mpOutDev;
    };
}
// <--

void SwVirtFlyDrawObj::wrap_DoPaintObject(
    drawinglayer::geometry::ViewInformation2D const& rViewInformation) const
{
    SwViewShell* pShell = m_pFlyFrame->getRootFrame()->GetCurrShell();

    // Only paint when we have a current shell and a DrawingLayer paint is in progress.
    // This avoids evtl. problems with renderers which do processing stuff,
    // but no paints. IsPaintInProgress() depends on SW repaint, so, as long
    // as SW paints self and calls DrawLayer() for Heaven and Hell, this will
    // be correct
    if (!pShell || !pShell->IsDrawingLayerPaintInProgress())
        return;

    bool bDrawObject(true);

    if ( !SwFlyFrame::IsPaint( const_cast<SwVirtFlyDrawObj*>(this), *pShell ) )
    {
        bDrawObject = false;
    }

    if ( !bDrawObject )
        return;

    // if there's no viewport set, all fly-frames will be painted,
    // which is slow, wastes memory, and can cause other trouble.
    (void) rViewInformation; // suppress "unused parameter" warning
    assert(comphelper::LibreOfficeKit::isActive() || !rViewInformation.getViewport().isEmpty());
    if ( m_pFlyFrame->IsFlyInContentFrame() )
        return;

    // it is also necessary to restore the VCL MapMode from ViewInformation since e.g.
    // the VCL PixelRenderer resets it at the used OutputDevice. Unfortunately, this
    // excludes shears and rotates which are not expressible in MapMode.
    // OD #i102707#
    // new helper class to restore MapMode - restoration, only if
    // needed and consideration of paint for meta file creation .
    RestoreMapMode aRestoreMapModeIfNeeded( pShell );

    // paint the FlyFrame (use standard VCL-Paint)
    m_pFlyFrame->PaintSwFrame( *pShell->GetOut(), m_pFlyFrame->GetPageFrame()->getFrameArea());
}

void SwVirtFlyDrawObj::TakeObjInfo( SdrObjTransformInfoRec& rInfo ) const
{
    rInfo.bMoveAllowed =
    rInfo.bResizeFreeAllowed = rInfo.bResizePropAllowed = true;

    // RotGrfFlyFrame: Some rotation may be allowed
    rInfo.bRotateFreeAllowed = rInfo.bRotate90Allowed = HasLimitedRotation();

    rInfo.bMirrorFreeAllowed = rInfo.bMirror45Allowed =
    rInfo.bMirror90Allowed   = rInfo.bShearAllowed    =
    rInfo.bCanConvToPath     = rInfo.bCanConvToPoly   =
    rInfo.bCanConvToPathLineToArea = rInfo.bCanConvToPolyLineToArea = false;
}

// SwVirtFlyDrawObj - Size Determination

void SwVirtFlyDrawObj::SetRect() const
{
    auto* pWritableThis = const_cast<SwVirtFlyDrawObj*>(this);
    if ( GetFlyFrame()->getFrameArea().HasArea() )
        pWritableThis->setOutRectangle(GetFlyFrame()->getFrameArea().SVRect());
    else
        pWritableThis->resetOutRectangle();
}

const tools::Rectangle& SwVirtFlyDrawObj::GetCurrentBoundRect() const
{
    SetRect();
    return getOutRectangle();
}

const tools::Rectangle& SwVirtFlyDrawObj::GetLastBoundRect() const
{
    return GetCurrentBoundRect();
}

void SwVirtFlyDrawObj::RecalcBoundRect()
{
    SetRect();
}

void SwVirtFlyDrawObj::RecalcSnapRect()
{
    SetRect();
}

const tools::Rectangle& SwVirtFlyDrawObj::GetSnapRect()  const
{
    SetRect();
    return getOutRectangle();
}

void SwVirtFlyDrawObj::SetSnapRect(const tools::Rectangle& )
{
    tools::Rectangle aTmp( GetLastBoundRect() );
    SetRect();
    SetChanged();
    BroadcastObjectChange();
    if (m_pUserCall!=nullptr)
        m_pUserCall->Changed(*this, SdrUserCallType::Resize, aTmp);
}

void SwVirtFlyDrawObj::NbcSetSnapRect(const tools::Rectangle& )
{
    SetRect();
}

const tools::Rectangle& SwVirtFlyDrawObj::GetLogicRect() const
{
    SetRect();
    return getOutRectangle();
}

void SwVirtFlyDrawObj::SetLogicRect(const tools::Rectangle& )
{
    tools::Rectangle aTmp( GetLastBoundRect() );
    SetRect();
    SetChanged();
    BroadcastObjectChange();
    if (m_pUserCall!=nullptr)
        m_pUserCall->Changed(*this, SdrUserCallType::Resize, aTmp);
}

void SwVirtFlyDrawObj::NbcSetLogicRect(const tools::Rectangle&, bool /*bAdaptTextMinSize*/ )
{
    SetRect();
}

::basegfx::B2DPolyPolygon SwVirtFlyDrawObj::TakeXorPoly() const
{
    const tools::Rectangle aSourceRectangle(GetFlyFrame()->getFrameArea().SVRect());
    const ::basegfx::B2DRange aSourceRange = vcl::unotools::b2DRectangleFromRectangle(aSourceRectangle);
    ::basegfx::B2DPolyPolygon aRetval;

    aRetval.append(::basegfx::utils::createPolygonFromRect(aSourceRange));

    return aRetval;
}

//  SwVirtFlyDrawObj::Move() and Resize()
void SwVirtFlyDrawObj::NbcMove(const Size& rSiz)
{
    if(GetFlyFrame()->IsFlyFreeFrame() && static_cast< SwFlyFreeFrame* >(GetFlyFrame())->isTransformableSwFrame())
    {
        // RotateFlyFrame3: When we have a change and are in transformed state (e.g. rotation used),
        // we need to fall back to the un-transformed state to keep the old code below
        // working properly. Restore FrameArea and use aOutRect from old FrameArea.
        TransformableSwFrame* pTransformableSwFrame(static_cast<SwFlyFreeFrame*>(GetFlyFrame())->getTransformableSwFrame());
        pTransformableSwFrame->restoreFrameAreas();
        setOutRectangle(GetFlyFrame()->getFrameArea().SVRect());
    }

    moveOutRectangle(rSiz.Width(), rSiz.Height());

    const Point aOldPos( GetFlyFrame()->getFrameArea().Pos() );
    const Point aNewPos(getOutRectangle().TopLeft());
    const SwRect aFlyRect(getOutRectangle());

    //If the Fly has an automatic align (right or top),
    //so preserve the automatic.
    SwFrameFormat *pFormat = GetFlyFrame()->GetFormat();
    const sal_Int16 eHori = pFormat->GetHoriOrient().GetHoriOrient();
    const sal_Int16 eVert = pFormat->GetVertOrient().GetVertOrient();
    const sal_Int16 eRelHori = pFormat->GetHoriOrient().GetRelationOrient();
    const sal_Int16 eRelVert = pFormat->GetVertOrient().GetRelationOrient();
    //On paragraph bound Flys starting from the new position a new
    //anchor must be set. Anchor and the new RelPos is calculated and
    //placed by the Fly itself.
    if( GetFlyFrame()->IsFlyAtContentFrame() )
    {
        static_cast<SwFlyAtContentFrame*>(GetFlyFrame())->SetAbsPos( aNewPos );
    }
    else
    {
        const SwFrameFormat *pTmpFormat = GetFormat();
        const SwFormatVertOrient &rVert = pTmpFormat->GetVertOrient();
        const SwFormatHoriOrient &rHori = pTmpFormat->GetHoriOrient();
        tools::Long lXDiff = aNewPos.X() - aOldPos.X();
        if( rHori.IsPosToggle() && text::HoriOrientation::NONE == eHori &&
            !GetFlyFrame()->FindPageFrame()->OnRightPage() )
            lXDiff = -lXDiff;

        if( GetFlyFrame()->GetAnchorFrame()->IsRightToLeft() &&
            text::HoriOrientation::NONE == eHori )
            lXDiff = -lXDiff;

        tools::Long lYDiff = aNewPos.Y() - aOldPos.Y();
        if( GetFlyFrame()->GetAnchorFrame()->IsVertical() )
        {
            //lXDiff -= rVert.GetPos();
            //lYDiff += rHori.GetPos();

            if ( GetFlyFrame()->GetAnchorFrame()->IsVertLR() )
            {
                lXDiff += rVert.GetPos();
                lXDiff = -lXDiff;
            }
            else
            {
                lXDiff -= rVert.GetPos();
                lYDiff += rHori.GetPos();
            }
        }
        else
        {
            lXDiff += rHori.GetPos();
            lYDiff += rVert.GetPos();
        }

        if( GetFlyFrame()->GetAnchorFrame()->IsRightToLeft() &&
            text::HoriOrientation::NONE != eHori )
            lXDiff = GetFlyFrame()->GetAnchorFrame()->getFrameArea().Width() -
                     aFlyRect.Width() - lXDiff;

        const Point aTmp( lXDiff, lYDiff );
        GetFlyFrame()->ChgRelPos( aTmp );
    }

    SwAttrSet aSet( pFormat->GetDoc().GetAttrPool(),
                                            RES_VERT_ORIENT, RES_HORI_ORIENT );
    SwFormatHoriOrient aHori( pFormat->GetHoriOrient() );
    SwFormatVertOrient aVert( pFormat->GetVertOrient() );
    bool bPut = false;

    if( !GetFlyFrame()->IsFlyLayFrame() &&
        ::GetHtmlMode(pFormat->GetDoc().GetDocShell()) )
    {
        //In HTML-Mode only automatic aligns are allowed.
        //Only we can try a snap to left/right respectively left-/right border
        const SwFrame* pAnch = GetFlyFrame()->GetAnchorFrame();
        bool bNextLine = false;

        if( !GetFlyFrame()->IsAutoPos() || text::RelOrientation::PAGE_FRAME != aHori.GetRelationOrient() )
        {
            if( text::RelOrientation::CHAR == eRelHori )
            {
                aHori.SetHoriOrient( text::HoriOrientation::LEFT );
                aHori.SetRelationOrient( text::RelOrientation::CHAR );
            }
            else
            {
                bNextLine = true;
                //Horizontal Align:
                const bool bLeftFrame =
                    aFlyRect.Left() < pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Left(),
                    bLeftPrt = aFlyRect.Left() + aFlyRect.Width() <
                               pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Width()/2;
                if ( bLeftFrame || bLeftPrt )
                {
                    aHori.SetHoriOrient( text::HoriOrientation::LEFT );
                    aHori.SetRelationOrient( bLeftFrame ? text::RelOrientation::FRAME : text::RelOrientation::PRINT_AREA );
                }
                else
                {
                    const bool bRightFrame = aFlyRect.Left() >
                                       pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Width();
                    aHori.SetHoriOrient( text::HoriOrientation::RIGHT );
                    aHori.SetRelationOrient( bRightFrame ? text::RelOrientation::FRAME : text::RelOrientation::PRINT_AREA );
                }
            }
            aSet.Put( aHori );
        }
        //Vertical alignment simply is retained principally,
        //only on manual align will be switched over.
        bool bRelChar = text::RelOrientation::CHAR == eRelVert;
        aVert.SetVertOrient( eVert != text::VertOrientation::NONE ? eVert :
                GetFlyFrame()->IsFlyInContentFrame() ? text::VertOrientation::CHAR_CENTER :
                bRelChar && bNextLine ? text::VertOrientation::CHAR_TOP : text::VertOrientation::TOP );
        if( bRelChar )
            aVert.SetRelationOrient( text::RelOrientation::CHAR );
        else
            aVert.SetRelationOrient( text::RelOrientation::PRINT_AREA );
        aSet.Put( aVert );
        bPut = true;
    }

    //We want preferably not to lose the automatic alignments.
    if ( !bPut && bInResize )
    {
        if ( text::HoriOrientation::NONE != eHori )
        {
            aHori.SetHoriOrient( eHori );
            aHori.SetRelationOrient( eRelHori );
            aSet.Put( aHori );
            bPut = true;
        }
        if ( text::VertOrientation::NONE != eVert )
        {
            aVert.SetVertOrient( eVert );
            aVert.SetRelationOrient( eRelVert );
            aSet.Put( aVert );
            bPut = true;
        }
    }
    if ( bPut )
        pFormat->SetFormatAttr( aSet );
}


void SwVirtFlyDrawObj::NbcCrop(const basegfx::B2DPoint& rRef, double fxFact, double fyFact)
{
    // Get Wrt Shell
    SwWrtShell *pSh = dynamic_cast<SwWrtShell*>( GetFlyFrame()->getRootFrame()->GetCurrShell() );

    if (!pSh)
    {
        return;
    }

    GraphicObject const *pGraphicObject = pSh->GetGraphicObj();

    if (!pGraphicObject)
    {
        return;
    }

    // Get graphic object size in 100th of mm
    const MapMode aMapMode100thmm(MapUnit::Map100thMM);
    Size aGraphicSize(pGraphicObject->GetPrefSize());

    if( MapUnit::MapPixel == pGraphicObject->GetPrefMapMode().GetMapUnit() )
    {
        aGraphicSize = Application::GetDefaultDevice()->PixelToLogic( aGraphicSize, aMapMode100thmm );
    }
    else
    {
        aGraphicSize = OutputDevice::LogicToLogic( aGraphicSize, pGraphicObject->GetPrefMapMode(), aMapMode100thmm);
    }

    if( aGraphicSize.IsEmpty() )
    {
        return ;
    }

    const bool bIsTransformableSwFrame(
        GetFlyFrame()->IsFlyFreeFrame() &&
        static_cast< SwFlyFreeFrame* >(GetFlyFrame())->isTransformableSwFrame());

    if(bIsTransformableSwFrame)
    {
        // When we have a change and are in transformed state (e.g. rotation used),
        // we need to fall back to the un-transformed state to keep the old code below
        // working properly. Restore FrameArea and use aOutRect from old FrameArea.
        TransformableSwFrame* pTransformableSwFrame(static_cast<SwFlyFreeFrame*>(GetFlyFrame())->getTransformableSwFrame());
        pTransformableSwFrame->restoreFrameAreas();
        setOutRectangle(GetFlyFrame()->getFrameArea().SVRect());
    }

    // Compute old and new rect. This will give us the deformation to apply to
    // the object to crop. OldRect is the inner frame, see getFullDragClone()
    // below where getFramePrintAreaTransformation is used as object geometry for Crop
    const tools::Rectangle aOldRect(
        GetFlyFrame()->getFrameArea().TopLeft() + GetFlyFrame()->getFramePrintArea().TopLeft(),
        GetFlyFrame()->getFramePrintArea().SSize());
    const tools::Long nOldWidth(aOldRect.GetWidth());
    const tools::Long nOldHeight(aOldRect.GetHeight());

    if (!nOldWidth || !nOldHeight)
    {
        return;
    }

    // rRef is relative to the Crop-Action, si in X/Y-Ranges of [0.0 .. 1.0],
    // to get the correct absolute position, transform using the old Rect
    const Point aRef(
        aOldRect.Left() + basegfx::fround<tools::Long>(aOldRect.GetWidth() * rRef.getX()),
        aOldRect.Top() + basegfx::fround<tools::Long>(aOldRect.GetHeight() * rRef.getY()));

    // apply transformation, use old ResizeRect for now
    tools::Rectangle aNewRect( aOldRect );
    ResizeRect(
        aNewRect,
        aRef,
        Fraction(fxFact),
        Fraction(fyFact));

    // Get old values for crop in 10th of mm
    SfxItemSetFixed<RES_GRFATR_CROPGRF, RES_GRFATR_CROPGRF> aSet( pSh->GetAttrPool() );
    pSh->GetCurAttr( aSet );
    SwCropGrf aCrop( aSet.Get(RES_GRFATR_CROPGRF) );

    tools::Rectangle aCropRectangle(
        convertTwipToMm100(aCrop.GetLeft()),
        convertTwipToMm100(aCrop.GetTop()),
        convertTwipToMm100(aCrop.GetRight()),
        convertTwipToMm100(aCrop.GetBottom()) );

    // Compute delta to apply
    double fScaleX = ( aGraphicSize.Width() - aCropRectangle.Left() - aCropRectangle.Right() ) / static_cast<double>(nOldWidth);
    double fScaleY = ( aGraphicSize.Height() - aCropRectangle.Top() - aCropRectangle.Bottom() ) / static_cast<double>(nOldHeight);

    sal_Int32 nDiffLeft = aNewRect.Left() - aOldRect.Left();
    sal_Int32 nDiffTop = aNewRect.Top() - aOldRect.Top();
    sal_Int32 nDiffRight = aNewRect.Right() - aOldRect.Right();
    sal_Int32 nDiffBottom = aNewRect.Bottom() - aOldRect.Bottom();

    // Compute new values in 10th of mm
    sal_Int32 nLeftCrop = static_cast<sal_Int32>( aCropRectangle.Left() + nDiffLeft * fScaleX );
    sal_Int32 nTopCrop = static_cast<sal_Int32>( aCropRectangle.Top() + nDiffTop * fScaleY );
    sal_Int32 nRightCrop = static_cast<sal_Int32>( aCropRectangle.Right() - nDiffRight * fScaleX );
    sal_Int32 nBottomCrop = static_cast<sal_Int32>( aCropRectangle.Bottom() - nDiffBottom * fScaleY );

    // Apply values
    pSh->StartAllAction();
    // pSh->StartUndo(SwUndoId::START);

    // Set new crop values in twips
    aCrop.SetLeft  (o3tl::toTwips(nLeftCrop, o3tl::Length::mm100));
    aCrop.SetTop   (o3tl::toTwips(nTopCrop, o3tl::Length::mm100));
    aCrop.SetRight (o3tl::toTwips(nRightCrop, o3tl::Length::mm100));
    aCrop.SetBottom(o3tl::toTwips(nBottomCrop, o3tl::Length::mm100));
    pSh->SetAttrItem(aCrop);

    // Set new frame size
    SwFrameFormat *pFormat = GetFormat();
    SwFormatFrameSize aSz( pFormat->GetFrameSize() );
    const tools::Long aNewWidth(aNewRect.GetWidth() + (getOutRectangle().GetWidth() - aOldRect.GetWidth()));
    const tools::Long aNewHeight(aNewRect.GetHeight() + (getOutRectangle().GetHeight() - aOldRect.GetHeight()));
    aSz.SetWidth(aNewWidth);
    aSz.SetHeight(aNewHeight);
    pFormat->GetDoc().SetAttr( aSz, *pFormat );

    // add move - to make result look better. Fill with defaults
    // for the untransformed case
    Point aNewTopLeft(aNewRect.TopLeft());
    const Point aOldTopLeft(aOldRect.TopLeft());

    if(bIsTransformableSwFrame)
    {
        // Need to correct the NewTopLeft position in transformed state to make
        // the interaction look correct. First, extract rotation
        basegfx::B2DVector aScale, aTranslate;
        double fRotate, fShearX;
        GetFlyFrame()->getFrameAreaTransformation().decompose(aScale, aTranslate, fRotate, fShearX);

        // calc the center of the unchanged object
        const basegfx::B2DPoint aFormerCenter(
            GetFlyFrame()->getFrameAreaTransformation() * basegfx::B2DPoint(0.5, 0.5));

        // define the existing rotation around that former center
        const basegfx::B2DHomMatrix aRotFormerCenter(
            basegfx::utils::createRotateAroundPoint(
                aFormerCenter.getX(),
                aFormerCenter.getY(),
                fRotate));

        // use the new center of the unrotated object, rotate it around the
        // former center
        const Point aNewCenter(aNewRect.Center());
        const basegfx::B2DPoint aRotNewCenter(
            aRotFormerCenter * basegfx::B2DPoint(aNewCenter.X(), aNewCenter.Y()));

        // Create the new TopLeft of the unrotated, cropped object by creating
        // as if re-creating the unrotated geometry
        aNewTopLeft = Point(
            basegfx::fround<tools::Long>(aRotNewCenter.getX() - (0.5 * aNewRect.getOpenWidth())),
            basegfx::fround<tools::Long>(aRotNewCenter.getY() - (0.5 * aNewRect.getOpenHeight())));
    }

    // check if we have movement and execute if yes
    const Size aDeltaMove(
        aNewTopLeft.X() - aOldTopLeft.X(),
        aNewTopLeft.Y() - aOldTopLeft.Y());

    if(0 != aDeltaMove.Width() || 0 != aDeltaMove.Height())
    {
        NbcMove(aDeltaMove);
    }

    // pSh->EndUndo(SwUndoId::END);
    pSh->EndAllAction();
}

bool SwVirtFlyDrawObj::IsSizeValid(Size aTargetSize)
{
    SwBorderAttrAccess aAccess( SwFrame::GetCache(), GetFlyFrame() );
    const SwBorderAttrs &rAttrs = *aAccess.Get();
    return GetFlyFrame()->IsResizeValid(&rAttrs, aTargetSize);
}

void SwVirtFlyDrawObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
{
    const SwFrame* pTmpFrame = GetFlyFrame()->GetAnchorFrame();

    if( !pTmpFrame )
    {
        pTmpFrame = GetFlyFrame();
    }

    const bool bVertX(pTmpFrame->IsVertical());
    const bool bRTL(pTmpFrame->IsRightToLeft());
    const bool bVertL2RX(pTmpFrame->IsVertLR());
    const bool bUseRightEdge((bVertX && !bVertL2RX ) || bRTL);
    const bool bIsTransformableSwFrame(
        GetFlyFrame()->IsFlyFreeFrame() &&
        static_cast< SwFlyFreeFrame* >(GetFlyFrame())->isTransformableSwFrame());

    tools::Rectangle aRectangle;

    if(bIsTransformableSwFrame)
    {
        // When we have a change in transformed state, we need to fall back to the
        // state without possible transformations.
        // In the Resize case to correctly handle the changes, apply to the transformation
        // and extract the new, untransformed state from that modified transformation
        basegfx::B2DHomMatrix aNewMat(GetFlyFrame()->getFrameAreaTransformation());
        const basegfx::B2DPoint aRef(rRef.X(), rRef.Y());

        // apply state to already valid transformation
        aNewMat.translate(-aRef.getX(), -aRef.getY());
        aNewMat.scale(double(xFact), double(yFact));
        aNewMat.translate(aRef.getX(), aRef.getY());

        // get center of transformed state
        const basegfx::B2DPoint aCenter(aNewMat * basegfx::B2DPoint(0.5, 0.5));

        // decompose to extract scale
        basegfx::B2DVector aScale, aTranslate;
        double fRotate, fShearX;
        aNewMat.decompose(aScale, aTranslate, fRotate, fShearX);
        const basegfx::B2DVector aAbsScale(basegfx::absolute(aScale));

        // create new modified, but untransformed OutRect
        setOutRectangle(tools::Rectangle(
            basegfx::fround<tools::Long>(aCenter.getX() - (0.5 * aAbsScale.getX())),
            basegfx::fround<tools::Long>(aCenter.getY() - (0.5 * aAbsScale.getY())),
            basegfx::fround<tools::Long>(aCenter.getX() + (0.5 * aAbsScale.getX())),
            basegfx::fround<tools::Long>(aCenter.getY() + (0.5 * aAbsScale.getY()))));

        // restore FrameAreas so that actions below not adapted to new
        // full transformations take the correct actions
        TransformableSwFrame* pTransformableSwFrame(static_cast<SwFlyFreeFrame*>(GetFlyFrame())->getTransformableSwFrame());
        pTransformableSwFrame->restoreFrameAreas();
    }
    else
    {
        aRectangle = getOutRectangle();
        ResizeRect(aRectangle, rRef, xFact, yFact);
        setOutRectangle(aRectangle);
    }

    // Position may also change, remember old one. This is now already
    // the one in the unrotated, old coordinate system
    Point aOldPos(bUseRightEdge ? GetFlyFrame()->getFrameArea().TopRight() : GetFlyFrame()->getFrameArea().Pos());

    // get target size in old coordinate system
    aRectangle = getOutRectangle();
    Size aSz(aRectangle.Right() - aRectangle.Left() + 1, aRectangle.Bottom() - aRectangle.Top() + 1);

    // compare with restored FrameArea
    if( aSz != GetFlyFrame()->getFrameArea().SSize() )
    {
        //The width of the columns should not be too narrow
        SwFrame* pLower = GetFlyFrame()->Lower();
        if ( pLower && pLower->IsColumnFrame() )
        {
            SwBorderAttrAccess aAccess( SwFrame::GetCache(), GetFlyFrame() );
            const SwBorderAttrs &rAttrs = *aAccess.Get();
            tools::Long nMin = rAttrs.CalcLeftLine()+rAttrs.CalcRightLine();
            const SwFormatCol& rCol = rAttrs.GetAttrSet().GetCol();
            if ( rCol.GetColumns().size() > 1 )
            {
                for ( const auto &rC : rCol.GetColumns() )
                {
                    nMin += rC.GetLeft() + rC.GetRight() + MINFLY;
                }
                nMin -= MINFLY;
            }
            aSz.setWidth( std::max( aSz.Width(), nMin ) );
        }

        SwFrameFormat *pFormat = GetFormat();
        const SwFormatFrameSize aOldFrameSz( pFormat->GetFrameSize() );
        GetFlyFrame()->ChgSize( aSz );
        SwFormatFrameSize aFrameSz( pFormat->GetFrameSize() );

        if ( aFrameSz.GetWidthPercent() || aFrameSz.GetHeightPercent() )
        {
            tools::Long nRelWidth, nRelHeight;
            const SwFrame *pRel = GetFlyFrame()->IsFlyLayFrame() ?
                                GetFlyFrame()->GetAnchorFrame() :
                                GetFlyFrame()->GetAnchorFrame()->GetUpper();
            const SwViewShell *pSh = GetFlyFrame()->getRootFrame()->GetCurrShell();

            if ( pSh && pRel->IsBodyFrame() &&
                 pSh->GetViewOptions()->getBrowseMode() &&
                 pSh->VisArea().HasArea() )
            {
                nRelWidth  = pSh->GetBrowseWidth();
                nRelHeight = pSh->VisArea().Height();
                const Size aBorder = pSh->GetOut()->PixelToLogic( pSh->GetBrowseBorder() );
                nRelHeight -= 2*aBorder.Height();
            }
            else
            {
                nRelWidth  = pRel->getFramePrintArea().Width();
                nRelHeight = pRel->getFramePrintArea().Height();
            }

            if ( aFrameSz.GetWidthPercent() && aFrameSz.GetWidthPercent() != SwFormatFrameSize::SYNCED &&
                 aOldFrameSz.GetWidth() != aFrameSz.GetWidth() )
            {
                aFrameSz.SetWidthPercent( sal_uInt8(aSz.Width() * 100.0 / nRelWidth + 0.5) );
            }

            if ( aFrameSz.GetHeightPercent() && aFrameSz.GetHeightPercent() != SwFormatFrameSize::SYNCED &&
                 aOldFrameSz.GetHeight() != aFrameSz.GetHeight() )
            {
                aFrameSz.SetHeightPercent( sal_uInt8(aSz.Height() * 100.0 / nRelHeight + 0.5) );
            }

            pFormat->GetDoc().SetAttr( aFrameSz, *pFormat );
        }
    }

    //Position can also be changed, get new one
    aRectangle = getOutRectangle();
    const Point aNewPos(bUseRightEdge ? aRectangle.Right() + 1 : aRectangle.Left(), aRectangle.Top());

    if ( aNewPos == aOldPos )
        return;

    // Former late change in aOutRect by ChgSize
    // is now taken into account directly by calculating
    // aNewPos *after* calling ChgSize (see old code).
    // Still need to adapt aOutRect since the 'Move' is already applied
    // here (see ResizeRect) and it's the same SdrObject
    const Size aDeltaMove(
            aNewPos.X() - aOldPos.X(),
            aNewPos.Y() - aOldPos.Y());

    moveOutRectangle(-aDeltaMove.Width(), -aDeltaMove.Height());

    // Now, move as needed (no empty delta which was a hack anyways)
    if(bIsTransformableSwFrame)
    {
        // need to save aOutRect to FrameArea, will be restored to aOutRect in
        // SwVirtFlyDrawObj::NbcMove currently for TransformableSwFrames
        SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*GetFlyFrame());
        aFrm.setSwRect(SwRect(getOutRectangle()));
    }

    // keep old hack - not clear what happens here
    bInResize = true;
    NbcMove(aDeltaMove);
    bInResize = false;
}

void SwVirtFlyDrawObj::Move(const Size& rSiz)
{
    NbcMove( rSiz );
    SetChanged();
    GetFormat()->GetDoc().GetIDocumentUndoRedo().DoDrawUndo(false);
}

void SwVirtFlyDrawObj::Resize(const Point& rRef,
                    const Fraction& xFact, const Fraction& yFact, bool /*bUnsetRelative*/)
{
    NbcResize( rRef, xFact, yFact );
    SetChanged();
    GetFormat()->GetDoc().GetIDocumentUndoRedo().DoDrawUndo(false);
}

void SwVirtFlyDrawObj::Crop(const basegfx::B2DPoint& rRef, double fxFact, double fyFact)
{
    NbcCrop( rRef, fxFact, fyFact );
    SetChanged();
    GetFormat()->GetDoc().GetIDocumentUndoRedo().DoDrawUndo(false);
}

// RotGrfFlyFrame: Helper to access possible rotation of Graphic contained in FlyFrame
Degree10 SwVirtFlyDrawObj::getPossibleRotationFromFraphicFrame(Size& rSize) const
{
    Degree10 nRetval;
    const SwNoTextFrame* pNoTx = dynamic_cast< const SwNoTextFrame* >(GetFlyFrame()->Lower());

    if(pNoTx)
    {
        SwNoTextNode& rNoTNd = const_cast< SwNoTextNode& >(*static_cast<const SwNoTextNode*>(pNoTx->GetNode()));
        SwGrfNode* pGrfNd = rNoTNd.GetGrfNode();

        if(nullptr != pGrfNd)
        {
            const SwAttrSet& rSet = pGrfNd->GetSwAttrSet();
            const SwRotationGrf& rRotation = rSet.GetRotationGrf();

            rSize = rRotation.GetUnrotatedSize();
            nRetval = rRotation.GetValue();
        }
    }

    return nRetval;
}

Degree100 SwVirtFlyDrawObj::GetRotateAngle() const
{
    if(ContainsSwGrfNode())
    {
        Size aSize;
        return to<Degree100>(getPossibleRotationFromFraphicFrame(aSize));
    }
    else
    {
        return SdrVirtObj::GetRotateAngle();
    }
}

rtl::Reference<SdrObject> SwVirtFlyDrawObj::getFullDragClone() const
{
    // call parent
    rtl::Reference<SdrObject> pRetval = SdrVirtObj::getFullDragClone();

    if(pRetval && GetFlyFrame() && ContainsSwGrfNode())
    {
        // RotGrfFlyFrame3: get inner bounds/transformation
        const basegfx::B2DHomMatrix aTargetTransform(GetFlyFrame()->getFramePrintAreaTransformation());

        pRetval->TRSetBaseGeometry(aTargetTransform, basegfx::B2DPolyPolygon());
    }

    return pRetval;
}

void SwVirtFlyDrawObj::addCropHandles(SdrHdlList& rTarget) const
{
    // RotGrfFlyFrame: Adapt to possible rotated Graphic contained in FlyFrame
    if(!GetFlyFrame()->getFrameArea().HasArea())
        return;

    // Use InnerBound, OuterBound (same as GetFlyFrame()->getFrameArea().SVRect())
    // may have a distance to InnerBound which needs to be taken into account.
    // The Graphic is mapped to InnerBound, as is the rotated Graphic.
    const basegfx::B2DRange aTargetRange(getInnerBound());

    if(aTargetRange.isEmpty())
        return;

    // RotGrfFlyFrame3: get inner bounds/transformation
    const basegfx::B2DHomMatrix aTargetTransform(GetFlyFrame()->getFramePrintAreaTransformation());

    // break up matrix
    basegfx::B2DTuple aScale;
    basegfx::B2DTuple aTranslate;
    double fRotate(0.0);
    double fShearX(0.0);
    aTargetTransform.decompose(aScale, aTranslate, fRotate, fShearX);
    basegfx::B2DPoint aPos;

    aPos = aTargetTransform * basegfx::B2DPoint(0.0, 0.0);
    rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround<tools::Long>(aPos.getX()), basegfx::fround<tools::Long>(aPos.getY())), SdrHdlKind::UpperLeft, fShearX, fRotate));
    aPos = aTargetTransform * basegfx::B2DPoint(0.5, 0.0);
    rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround<tools::Long>(aPos.getX()), basegfx::fround<tools::Long>(aPos.getY())), SdrHdlKind::Upper, fShearX, fRotate));
    aPos = aTargetTransform * basegfx::B2DPoint(1.0, 0.0);
    rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround<tools::Long>(aPos.getX()), basegfx::fround<tools::Long>(aPos.getY())), SdrHdlKind::UpperRight, fShearX, fRotate));
    aPos = aTargetTransform * basegfx::B2DPoint(0.0, 0.5);
    rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround<tools::Long>(aPos.getX()), basegfx::fround<tools::Long>(aPos.getY())), SdrHdlKind::Left , fShearX, fRotate));
    aPos = aTargetTransform * basegfx::B2DPoint(1.0, 0.5);
    rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround<tools::Long>(aPos.getX()), basegfx::fround<tools::Long>(aPos.getY())), SdrHdlKind::Right, fShearX, fRotate));
    aPos = aTargetTransform * basegfx::B2DPoint(0.0, 1.0);
    rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround<tools::Long>(aPos.getX()), basegfx::fround<tools::Long>(aPos.getY())), SdrHdlKind::LowerLeft, fShearX, fRotate));
    aPos = aTargetTransform * basegfx::B2DPoint(0.5, 1.0);
    rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround<tools::Long>(aPos.getX()), basegfx::fround<tools::Long>(aPos.getY())), SdrHdlKind::Lower, fShearX, fRotate));
    aPos = aTargetTransform * basegfx::B2DPoint(1.0, 1.0);
    rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround<tools::Long>(aPos.getX()), basegfx::fround<tools::Long>(aPos.getY())), SdrHdlKind::LowerRight, fShearX, fRotate));
}

// Macro

PointerStyle  SwVirtFlyDrawObj::GetMacroPointer(
    const SdrObjMacroHitRec& ) const
{
    return PointerStyle::RefHand;
}

bool SwVirtFlyDrawObj::HasMacro() const
{
    const SwFormatURL &rURL = m_pFlyFrame->GetFormat()->GetURL();
    return rURL.GetMap() || !rURL.GetURL().isEmpty();
}

SdrObject* SwVirtFlyDrawObj::CheckMacroHit( const SdrObjMacroHitRec& rRec ) const
{
    const SwFormatURL &rURL = m_pFlyFrame->GetFormat()->GetURL();
    if( rURL.GetMap() || !rURL.GetURL().isEmpty() )
    {
        SwRect aRect;
        SwFrame* pLower = m_pFlyFrame->Lower();
        if ( pLower && pLower->IsNoTextFrame() )
        {
            aRect = m_pFlyFrame->getFramePrintArea();
            aRect += m_pFlyFrame->getFrameArea().Pos();
        }
        else
            aRect = m_pFlyFrame->getFrameArea();

        if( aRect.Contains( rRec.aPos ) )
        {
            aRect.Pos().setX(aRect.Pos().getX() + rRec.nTol);
            aRect.Pos().setY(aRect.Pos().getY() + rRec.nTol);
            aRect.AddHeight( -(2 * rRec.nTol) );
            aRect.AddWidth( -(2 * rRec.nTol) );

            if( aRect.Contains( rRec.aPos ) )
            {
                if( !rURL.GetMap() ||
                    m_pFlyFrame->GetFormat()->GetIMapObject( rRec.aPos, m_pFlyFrame ))
                    return const_cast<SwVirtFlyDrawObj*>(this);

                return nullptr;
            }
        }
    }
    return SdrObject::CheckMacroHit( rRec );
}

bool SwVirtFlyDrawObj::IsTextBox() const
{
    return SwTextBoxHelper::isTextBox(GetFormat(), RES_FLYFRMFMT, this);
}

void SwVirtFlyDrawObj::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwVirtFlyDrawObj"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
    (void)xmlTextWriterWriteAttribute(
        pWriter, BAD_CAST("fly-frame"),
        BAD_CAST(OString::number(m_pFlyFrame->GetFrameId()).getStr()));

    SdrVirtObj::dumpAsXml(pWriter);

    (void)xmlTextWriterEndElement(pWriter);
}

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