/* -*- 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 <config_wasm_strip.h>

#include <hintids.hxx>
#include <svtools/optionsdrawinglayer.hxx>
#include <officecfg/Office/Common.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdpagv.hxx>
#include <svx/fmmodel.hxx>
#include <sot/exchange.hxx>
#include <svx/sdrundomanager.hxx>
#include <tools/globname.hxx>
#include <editeng/outliner.hxx>
#include <osl/diagnose.h>
#include <com/sun/star/embed/EmbedMisc.hpp>
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <unotools/configmgr.hxx>

#include <pagefrm.hxx>
#include <rootfrm.hxx>
#include <cntfrm.hxx>
#include <notxtfrm.hxx>
#include <flyfrm.hxx>
#include <frmfmt.hxx>
#include <dflyobj.hxx>
#include <dcontact.hxx>
#include <textboxhelper.hxx>
#include <viewsh.hxx>
#include <viewimp.hxx>
#include <dview.hxx>
#include <doc.hxx>
#include <mdiexp.hxx>
#include <ndole.hxx>
#include <ndgrf.hxx>
#include <fmtanchr.hxx>
#include <IDocumentUndoRedo.hxx>
#include <DocumentSettingManager.hxx>
#include <IDocumentLayoutAccess.hxx>

#include <com/sun/star/embed/Aspects.hpp>

#include <vector>

#include <sortedobjs.hxx>

using namespace com::sun::star;

namespace {

class SwSdrHdl : public SdrHdl
{
public:
    SwSdrHdl(const Point& rPnt, bool bTopRight ) :
        SdrHdl( rPnt, bTopRight ? SdrHdlKind::Anchor_TR : SdrHdlKind::Anchor ) {}
    virtual bool IsFocusHdl() const override;
};

}

bool SwSdrHdl::IsFocusHdl() const
{
    if( SdrHdlKind::Anchor == m_eKind || SdrHdlKind::Anchor_TR == m_eKind )
        return true;
    return SdrHdl::IsFocusHdl();
}

static const SwFrame *lcl_FindAnchor( const SdrObject *pObj, bool bAll )
{
    const SwVirtFlyDrawObj *pVirt = dynamic_cast< const SwVirtFlyDrawObj *>( pObj );
    if ( pVirt )
    {
        if ( bAll || !pVirt->GetFlyFrame()->IsFlyInContentFrame() )
            return pVirt->GetFlyFrame()->GetAnchorFrame();
    }
    else
    {
        const SwDrawContact *pCont = static_cast<const SwDrawContact*>(GetUserCall(pObj));
        if ( pCont )
            return pCont->GetAnchorFrame( pObj );
    }
    return nullptr;
}

SwDrawView::SwDrawView(
    SwViewShellImp& rI,
    FmFormModel& rFmFormModel,
    OutputDevice* pOutDev)
:   FmFormView(rFmFormModel, pOutDev),
    m_rImp( rI )
{
    SetPageVisible( false );
    SetBordVisible( false );
    SetGridVisible( false );
    SetHlplVisible( false );
    SetGlueVisible( false );
    SetFrameDragSingles();
    SetSwapAsynchron();

    EnableExtendedKeyInputDispatcher( false );
    EnableExtendedMouseEventDispatcher( false );

    SetHitTolerancePixel( GetMarkHdlSizePixel()/2 );

    SetPrintPreview( rI.GetShell().IsPreview() );

    // #i73602# Use default from the configuration
    SetBufferedOverlayAllowed(!comphelper::IsFuzzing() && officecfg::Office::Common::Drawinglayer::OverlayBuffer_Writer::get());

    // #i74769#, #i75172# Use default from the configuration
    SetBufferedOutputAllowed(!comphelper::IsFuzzing() && officecfg::Office::Common::Drawinglayer::OverlayBuffer_DrawImpress::get());
}

// #i99665#
bool SwDrawView::IsAntiAliasing()
{
    return SvtOptionsDrawinglayer::IsAntiAliasing();
}

static SdrObject* impLocalHitCorrection(SdrObject* pRetval, const Point& rPnt, sal_uInt16 nTol, const SdrMarkList &rMrkList)
{
    if(!nTol)
    {
        // the old method forced back to outer bounds test when nTol == 0, so
        // do not try to correct when nTol is not set (used from HelpContent)
    }
    else
    {
        // rebuild logic from former SwVirtFlyDrawObj::CheckSdrObjectHit. This is needed since
        // the SdrObject-specific CheckHit implementations are now replaced with primitives and
        // 'tricks' like in the old implementation (e.g. using a view from a model-data class to
        // detect if object is selected) are no longer valid.
        // The standard primitive hit-test for SwVirtFlyDrawObj now is the outer bound. The old
        // implementation reduced this excluding the inner bound when the object was not selected.
        SwVirtFlyDrawObj* pSwVirtFlyDrawObj = dynamic_cast< SwVirtFlyDrawObj* >(pRetval);

        if(pSwVirtFlyDrawObj)
        {
            SwFrame* pLower = pSwVirtFlyDrawObj->GetFlyFrame()->Lower();
            if(pLower && pLower->IsNoTextFrame())
            {
                // the old method used IsNoTextFrame (should be for SW's own OLE and
                // graphic's) to accept hit only based on outer bounds; nothing to do
            }
            else
            {
                // check if the object is selected in this view
                const size_t nMarkCount(rMrkList.GetMarkCount());
                bool bSelected(false);

                for(size_t a = 0; !bSelected && a < nMarkCount; ++a)
                {
                    if(pSwVirtFlyDrawObj == rMrkList.GetMark(a)->GetMarkedSdrObj())
                    {
                        bSelected = true;
                    }
                }

                if(!bSelected)
                {
                    // when not selected, the object is not hit when hit position is inside
                    // inner range. Get and shrink inner range
                    basegfx::B2DRange aInnerBound(pSwVirtFlyDrawObj->getInnerBound());

                    aInnerBound.grow(-1.0 * nTol);

                    if(aInnerBound.isInside(basegfx::B2DPoint(rPnt.X(), rPnt.Y())))
                    {
                        // exclude this hit
                        pRetval = nullptr;
                    }
                }
            }
        }
    }

    return pRetval;
}

SdrObject* SwDrawView::CheckSingleSdrObjectHit(const Point& rPnt, sal_uInt16 nTol, SdrObject* pObj, SdrPageView* pPV, SdrSearchOptions nOptions, const SdrLayerIDSet* pMVisLay) const
{
    // call parent
    SdrObject* pRetval = FmFormView::CheckSingleSdrObjectHit(rPnt, nTol, pObj, pPV, nOptions, pMVisLay);

    if(pRetval)
    {
        // override to allow extra handling when picking SwVirtFlyDrawObj's
        pRetval = impLocalHitCorrection(pRetval, rPnt, nTol, GetMarkedObjectList());
    }

    return pRetval;
}

/// Gets called every time the handles need to be build
void SwDrawView::AddCustomHdl()
{
    const SdrMarkList &rMrkList = GetMarkedObjectList();

    if(rMrkList.GetMarkCount() != 1)
        return;

    SdrObject *pObj = rMrkList.GetMark(0)->GetMarkedSdrObj();
    SwContact* pContact = ::GetUserCall( pObj );
    if (!pContact)
        return;

    // make code robust
    SwFrameFormat* pFrameFormat( ::FindFrameFormat( pObj ) );
    if ( !pFrameFormat )
    {
        OSL_FAIL( "<SwDrawView::AddCustomHdl()> - missing frame format!" );
        return;
    }
    const SwFormatAnchor &rAnchor = pFrameFormat->GetAnchor();

    if (RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId())
        return;

    const SwFrame* pAnch = CalcAnchor();
    if(nullptr == pAnch)
        return;

    Point aPos(m_aAnchorPoint);

    if ( RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId() )
    {
        // #i28701# - use last character rectangle saved at object
        // in order to avoid a format of the anchor frame
        SwAnchoredObject* pAnchoredObj = pContact->GetAnchoredObj( pObj );

        // Invalidate/recalc LastCharRect which can contain invalid frame offset because
        // of later frame changes
        pAnchoredObj->CheckCharRectAndTopOfLine(false);

        SwRect aAutoPos = pAnchoredObj->GetLastCharRect();
        if ( aAutoPos.Height() )
        {
            aPos = aAutoPos.Pos();
        }
    }

    // add anchor handle:
    std::unique_ptr<SdrHdl> hdl = std::make_unique<SwSdrHdl>( aPos, ( pAnch->IsVertical() && !pAnch->IsVertLR() ) ||
                                     pAnch->IsRightToLeft() );
    hdl->SetObjHdlNum(maHdlList.GetHdlCount());
    maHdlList.AddHdl(std::move(hdl));
}

SdrObject* SwDrawView::GetMaxToTopObj( SdrObject* pObj ) const
{
    if ( GetUserCall(pObj) )
    {
        const SwFrame *pAnch = ::lcl_FindAnchor( pObj, false );
        if ( pAnch )
        {
            //The topmost Obj within the anchor must not be overtaken.
            const SwFlyFrame *pFly = pAnch->FindFlyFrame();
            if ( pFly )
            {
                const SwPageFrame *pPage = pFly->FindPageFrame();
                if ( pPage->GetSortedObjs() )
                {
                    size_t nOrdNum = 0;
                    for (SwAnchoredObject* i : *pPage->GetSortedObjs())
                    {
                        const SdrObject *pO = i->GetDrawObj();

                        if ( pO->GetOrdNumDirect() > nOrdNum )
                        {
                            const SwFrame *pTmpAnch = ::lcl_FindAnchor( pO, false );
                            if ( pFly->IsAnLower( pTmpAnch ) )
                            {
                                nOrdNum = pO->GetOrdNumDirect();
                            }
                        }
                    }
                    if ( nOrdNum )
                    {
                        SdrPage *pTmpPage = GetModel().GetPage( 0 );
                        ++nOrdNum;
                        if ( nOrdNum < pTmpPage->GetObjCount() )
                        {
                            return pTmpPage->GetObj( nOrdNum );
                        }
                    }
                }
            }
        }
    }
    return nullptr;
}

SdrObject* SwDrawView::GetMaxToBtmObj(SdrObject* pObj) const
{
    if ( GetUserCall(pObj) )
    {
        const SwFrame *pAnch = ::lcl_FindAnchor( pObj, false );
        if ( pAnch )
        {
            //The Fly of the anchor must not be "flying under".
            const SwFlyFrame *pFly = pAnch->FindFlyFrame();
            if ( pFly )
            {
                SdrObject *pRet = const_cast<SdrObject*>(static_cast<SdrObject const *>(pFly->GetVirtDrawObj()));
                return pRet != pObj ? pRet : nullptr;
            }
        }
    }
    return nullptr;
}

/// determine maximal order number for a 'child' object of given 'parent' object
sal_uInt32 SwDrawView::GetMaxChildOrdNum( const SwFlyFrame& _rParentObj,
                                           const SdrObject* _pExclChildObj )
{
    sal_uInt32 nMaxChildOrdNum = _rParentObj.GetDrawObj()->GetOrdNum();

    const SdrPage* pDrawPage = _rParentObj.GetDrawObj()->getSdrPageFromSdrObject();
    assert(pDrawPage && "<SwDrawView::GetMaxChildOrdNum(..) - missing drawing page at parent object - crash!");

    const size_t nObjCount = pDrawPage->GetObjCount();
    for ( size_t i = nObjCount-1; i > _rParentObj.GetDrawObj()->GetOrdNum() ; --i )
    {
        const SdrObject* pObj = pDrawPage->GetObj( i );

        // Don't consider 'child' object <_pExclChildObj>
        if ( pObj == _pExclChildObj )
        {
            continue;
        }

        if ( pObj->GetOrdNum() > nMaxChildOrdNum &&
             _rParentObj.IsAnLower( lcl_FindAnchor( pObj, true ) ) )
        {
            nMaxChildOrdNum = pObj->GetOrdNum();
            break;
        }
    }

    return nMaxChildOrdNum;
}

/// method to move 'repeated' objects of the given moved object to the according level
void SwDrawView::MoveRepeatedObjs( const SwAnchoredObject& _rMovedAnchoredObj,
                                    const std::vector<SdrObject*>& _rMovedChildObjs ) const
{
    // determine 'repeated' objects of already moved object <_rMovedAnchoredObj>
    std::vector<SwAnchoredObject*> aAnchoredObjs;
    {
        const SwContact* pContact = ::GetUserCall( _rMovedAnchoredObj.GetDrawObj() );
        assert(pContact && "SwDrawView::MoveRepeatedObjs(..) - missing contact object -> crash.");
        if (pContact)
            pContact->GetAnchoredObjs( aAnchoredObjs );
    }

    // check, if 'repeated' objects exists.
    if ( aAnchoredObjs.size() <= 1 )
        return;

    SdrPage* pDrawPage = GetModel().GetPage( 0 );

    // move 'repeated' ones to the same order number as the already moved one.
    const size_t nNewPos = _rMovedAnchoredObj.GetDrawObj()->GetOrdNum();
    while ( !aAnchoredObjs.empty() )
    {
        SwAnchoredObject* pAnchoredObj = aAnchoredObjs.back();
        if ( pAnchoredObj != &_rMovedAnchoredObj )
        {
            pDrawPage->SetObjectOrdNum( pAnchoredObj->GetDrawObj()->GetOrdNum(),
                                        nNewPos );
            pDrawPage->RecalcObjOrdNums();
            // adjustments for accessibility API
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
            if ( auto pTmpFlyFrame = pAnchoredObj->DynCastFlyFrame() )
            {
                m_rImp.DisposeAccessibleFrame( pTmpFlyFrame );
                m_rImp.AddAccessibleFrame( pTmpFlyFrame );
            }
            else
            {
                m_rImp.DisposeAccessibleObj(pAnchoredObj->GetDrawObj(), true);
                m_rImp.AddAccessibleObj( pAnchoredObj->GetDrawObj() );
            }
#endif
        }
        aAnchoredObjs.pop_back();
    }

    // move 'repeated' ones of 'child' objects
    for ( SdrObject* pChildObj : _rMovedChildObjs )
    {
        {
            const SwContact* pContact = ::GetUserCall( pChildObj );
            assert(pContact && "SwDrawView::MoveRepeatedObjs(..) - missing contact object -> crash.");
            if (pContact)
                pContact->GetAnchoredObjs( aAnchoredObjs );
        }
        // move 'repeated' ones to the same order number as the already moved one.
        const size_t nTmpNewPos = pChildObj->GetOrdNum();
        while ( !aAnchoredObjs.empty() )
        {
            SwAnchoredObject* pAnchoredObj = aAnchoredObjs.back();
            if ( pAnchoredObj->GetDrawObj() != pChildObj )
            {
                pDrawPage->SetObjectOrdNum( pAnchoredObj->GetDrawObj()->GetOrdNum(),
                                            nTmpNewPos );
                pDrawPage->RecalcObjOrdNums();
                // adjustments for accessibility API
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
                if ( auto pTmpFlyFrame = pAnchoredObj->DynCastFlyFrame() )
                {
                    m_rImp.DisposeAccessibleFrame( pTmpFlyFrame );
                    m_rImp.AddAccessibleFrame( pTmpFlyFrame );
                }
                else
                {
                    m_rImp.DisposeAccessibleObj(pAnchoredObj->GetDrawObj(), true);
                    m_rImp.AddAccessibleObj( pAnchoredObj->GetDrawObj() );
                }
#endif
            }
            aAnchoredObjs.pop_back();
        }
    }
}

// --> adjustment and re-factoring of method
void SwDrawView::ObjOrderChanged( SdrObject* pObj, size_t nOldPos,
                                          size_t nNewPos )
{
    // nothing to do for group members
    if ( pObj->getParentSdrObjectFromSdrObject() )
    {
        return;
    }

    // determine drawing page and assure that the order numbers are correct.
    SdrPage* pDrawPage = GetModel().GetPage( 0 );
    if ( pDrawPage->IsObjOrdNumsDirty() )
        pDrawPage->RecalcObjOrdNums();
    const size_t nObjCount = pDrawPage->GetObjCount();

    SwContact* pContact = ::GetUserCall( pObj );
    if (!pContact)
        return;

    SwAnchoredObject* pMovedAnchoredObj = pContact->GetAnchoredObj( pObj );
    const SwFlyFrame* pParentAnchoredObj =
                                pMovedAnchoredObj->GetAnchorFrame()->FindFlyFrame();

    const bool bMovedForward = nOldPos < nNewPos;

    // assure for a 'child' object, that it doesn't exceed the limits of its 'parent'
    if ( pParentAnchoredObj )
    {
        if ( bMovedForward )
        {
            const size_t nMaxChildOrdNumWithoutMoved =
                    GetMaxChildOrdNum( *pParentAnchoredObj, pMovedAnchoredObj->GetDrawObj() );
            if ( nNewPos > nMaxChildOrdNumWithoutMoved+1 )
            {
                // set position to the top of the 'child' object group
                pDrawPage->SetObjectOrdNum( nNewPos, nMaxChildOrdNumWithoutMoved+1 );
                nNewPos = nMaxChildOrdNumWithoutMoved+1;
            }
        }
        else
        {
            const size_t nParentOrdNum = pParentAnchoredObj->GetDrawObj()->GetOrdNum();
            if ( nNewPos < nParentOrdNum )
            {
                // set position to the bottom of the 'child' object group
                pDrawPage->SetObjectOrdNum( nNewPos, nParentOrdNum );
                nNewPos = nParentOrdNum;
            }
        }
        if ( pDrawPage->IsObjOrdNumsDirty() )
            pDrawPage->RecalcObjOrdNums();
    }

    // Assure, that object isn't positioned between 'repeated' ones
    if ( ( bMovedForward && nNewPos < nObjCount - 1 ) ||
         ( !bMovedForward && nNewPos > 0 ) )
    {
        const SdrObject* pTmpObj =
                pDrawPage->GetObj( bMovedForward ? nNewPos - 1 : nNewPos + 1 );
        if ( pTmpObj )
        {
            size_t nTmpNewPos( nNewPos );
            if (const SwContact* pContact2 = ::GetUserCall( pTmpObj ))
            {
                if ( bMovedForward )
                {
                    // move before the top 'repeated' object
                    const sal_uInt32 nTmpMaxOrdNum = pContact2->GetMaxOrdNum();
                    if ( nTmpMaxOrdNum > nNewPos )
                        nTmpNewPos = nTmpMaxOrdNum;
                }
                else
                {
                    // move behind the bottom 'repeated' object
                    const sal_uInt32 nTmpMinOrdNum = pContact2->GetMinOrdNum();
                    if ( nTmpMinOrdNum < nNewPos )
                        nTmpNewPos = nTmpMinOrdNum;
                }
            }
            if ( nTmpNewPos != nNewPos )
            {
                pDrawPage->SetObjectOrdNum( nNewPos, nTmpNewPos );
                nNewPos = nTmpNewPos;
                pDrawPage->RecalcObjOrdNums();
            }
        }
    }

    // On move forward, assure that object is moved before its own children.
    // Only Writer fly frames can have children.
    if ( pMovedAnchoredObj->DynCastFlyFrame() &&
         bMovedForward && nNewPos < nObjCount - 1 )
    {
        sal_uInt32 nMaxChildOrdNum =
                    GetMaxChildOrdNum( *static_cast<const SwFlyFrame*>(pMovedAnchoredObj) );
        if ( nNewPos < nMaxChildOrdNum )
        {
            // determine position before the object before its top 'child' object
            const SdrObject* pTmpObj = pDrawPage->GetObj( nMaxChildOrdNum );
            if (SwContact* pContact2 = ::GetUserCall( pTmpObj ))
            {
                size_t nTmpNewPos = pContact2->GetMaxOrdNum() + 1;
                if ( nTmpNewPos >= nObjCount )
                {
                    --nTmpNewPos;
                }
                // assure, that determined position isn't between 'repeated' objects
                pTmpObj = pDrawPage->GetObj( nTmpNewPos );
                pContact2 = ::GetUserCall( pTmpObj );
                if (pContact2)
                {
                    nTmpNewPos = pContact2->GetMaxOrdNum();
                    // apply new position
                    pDrawPage->SetObjectOrdNum( nNewPos, nTmpNewPos );
                    nNewPos = nTmpNewPos;
                    pDrawPage->RecalcObjOrdNums();
                }
            }
        }
    }

    // Assure, that object isn't positioned between nested objects
    if ( ( bMovedForward && nNewPos < nObjCount - 1 ) ||
         ( !bMovedForward && nNewPos > 0 ) )
    {
        size_t nTmpNewPos( nNewPos );
        const SwFrameFormat* pParentFrameFormat =
                pParentAnchoredObj ? pParentAnchoredObj->GetFrameFormat() : nullptr;
        const SdrObject* pTmpObj = pDrawPage->GetObj( nNewPos + 1 );
        while ( pTmpObj )
        {
            // #i38563# - assure, that anchor frame exists.
            // If object is anchored inside an invisible part of the document
            // (e.g. page header, whose page style isn't applied, or hidden
            // section), no anchor frame exists.
            const SwFrame* pTmpAnchorFrame = lcl_FindAnchor( pTmpObj, true );
            const SwFlyFrame* pTmpParentObj = pTmpAnchorFrame
                                            ? pTmpAnchorFrame->FindFlyFrame() : nullptr;
            if ( pTmpParentObj &&
                 pTmpParentObj->GetFrameFormat() != pParentFrameFormat )
            {
                if (const SwContact* pContact2 = ::GetUserCall( pTmpObj ))
                {
                    if ( bMovedForward )
                    {
                        nTmpNewPos = pContact2->GetMaxOrdNum();
                        pTmpObj = pDrawPage->GetObj( nTmpNewPos + 1 );
                    }
                    else
                    {
                        nTmpNewPos = pContact2->GetMinOrdNum();
                        pTmpObj = pTmpParentObj->GetDrawObj();
                    }
                }
            }
            else
                break;
        }
        if ( nTmpNewPos != nNewPos )
        {
            pDrawPage->SetObjectOrdNum( nNewPos, nTmpNewPos );
            nNewPos = nTmpNewPos;
            pDrawPage->RecalcObjOrdNums();
        }
    }

    // setup collection of moved 'child' objects to move its 'repeated' objects.
    std::vector< SdrObject* > aMovedChildObjs;

    // move 'children' accordingly
    if ( auto pFlyFrame = pMovedAnchoredObj->DynCastFlyFrame() )
    {
        // adjustments for accessibility API
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
        m_rImp.DisposeAccessibleFrame( pFlyFrame );
        m_rImp.AddAccessibleFrame( pFlyFrame );
#endif

        const sal_uInt32 nChildNewPos = bMovedForward ? nNewPos : nNewPos+1;
        size_t i = bMovedForward ? nOldPos : nObjCount-1;
        do
        {
            SdrObject* pTmpObj = pDrawPage->GetObj( i );
            if ( pTmpObj == pObj )
                break;

            // #i38563# - assure, that anchor frame exists.
            // If object is anchored inside an invisible part of the document
            // (e.g. page header, whose page style isn't applied, or hidden
            // section), no anchor frame exists.
            const SwFrame* pTmpAnchorFrame = lcl_FindAnchor( pTmpObj, true );
            const SwFlyFrame* pTmpParentObj = pTmpAnchorFrame
                                            ? pTmpAnchorFrame->FindFlyFrame() : nullptr;
            if ( pTmpParentObj &&
                 ( ( pTmpParentObj == pFlyFrame ) ||
                   ( pFlyFrame->IsUpperOf( *pTmpParentObj ) ) ) )
            {
                // move child object.,
                pDrawPage->SetObjectOrdNum( i, nChildNewPos );
                pDrawPage->RecalcObjOrdNums();
                // collect 'child' object
                aMovedChildObjs.push_back( pTmpObj );
                // adjustments for accessibility API
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
                if ( auto pFlyDrawObj = dynamic_cast<SwVirtFlyDrawObj *>( pTmpObj ) )
                {
                    const SwFlyFrame *pTmpFlyFrame = pFlyDrawObj->GetFlyFrame();
                    m_rImp.DisposeAccessibleFrame( pTmpFlyFrame );
                    m_rImp.AddAccessibleFrame( pTmpFlyFrame );
                }
                else
                {
                    m_rImp.DisposeAccessibleObj(pTmpObj, true);
                    m_rImp.AddAccessibleObj( pTmpObj );
                }
#endif
            }
            else
            {
                // adjust loop counter
                if ( bMovedForward )
                    ++i;
                else if (i > 0)
                    --i;
            }

        } while ( ( bMovedForward && i < ( nObjCount - aMovedChildObjs.size() ) ) ||
                  ( !bMovedForward && i > ( nNewPos + aMovedChildObjs.size() ) ) );
    }
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
    else
    {
        // adjustments for accessibility API
        m_rImp.DisposeAccessibleObj(pObj, true);
        m_rImp.AddAccessibleObj( pObj );
    }
#endif

    MoveRepeatedObjs( *pMovedAnchoredObj, aMovedChildObjs );
}

bool SwDrawView::TakeDragLimit( SdrDragMode eMode,
                                            tools::Rectangle& rRect ) const
{
    const SdrMarkList &rMrkList = GetMarkedObjectList();
    bool bRet = false;
    if( 1 == rMrkList.GetMarkCount() )
    {
        const SdrObject *pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj();
        SwRect aRect;
        if( ::CalcClipRect( pObj, aRect, eMode == SdrDragMode::Move ) )
        {
            rRect = aRect.SVRect();
            bRet = true;
        }
    }
    return bRet;
}

const SwFrame* SwDrawView::CalcAnchor()
{
    const SdrMarkList &rMrkList = GetMarkedObjectList();
    if ( rMrkList.GetMarkCount() != 1 )
        return nullptr;

    SdrObject* pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj();

    //Search for paragraph bound objects, otherwise only the
    //current anchor. Search only if we currently drag.
    const SwFrame* pAnch = nullptr;
    tools::Rectangle aMyRect;
    auto pFlyDrawObj = dynamic_cast<SwVirtFlyDrawObj *>( pObj );
    if ( pFlyDrawObj )
    {
        pAnch = pFlyDrawObj->GetFlyFrame()->GetAnchorFrame();
        aMyRect = pFlyDrawObj->GetFlyFrame()->getFrameArea().SVRect();
    }
    else
    {
        // determine correct anchor position for 'virtual' drawing objects.
        // #i26791#
        if (SwDrawContact* pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)))
        {
            pAnch = pContact->GetAnchorFrame( pObj );
            if( !pAnch )
            {
                pContact->ConnectToLayout();
                // determine correct anchor position for 'virtual' drawing objects.
                // #i26791#
                pAnch = pContact->GetAnchorFrame( pObj );
            }
        }
        aMyRect = pObj->GetSnapRect();
    }

    const bool bTopRight = pAnch && ( ( pAnch->IsVertical() &&
                                            !pAnch->IsVertLR() ) ||
                                             pAnch->IsRightToLeft() );
    const Point aMyPt = bTopRight ? aMyRect.TopRight() : aMyRect.TopLeft();

    Point aPt;
    if ( IsAction() )
    {
        if ( !TakeDragObjAnchorPos( aPt, bTopRight ) )
            return nullptr;
    }
    else
    {
        tools::Rectangle aRect = pObj->GetSnapRect();
        aPt = bTopRight ? aRect.TopRight() : aRect.TopLeft();
    }

    if ( aPt != aMyPt )
    {
        if ( pAnch && pAnch->IsContentFrame() )
        {
            // allow drawing objects in header/footer,
            // but exclude control objects.
            bool bBodyOnly = CheckControlLayer( pObj );
            pAnch = ::FindAnchor( static_cast<const SwContentFrame*>(pAnch), aPt, bBodyOnly );
        }
        else if ( !pFlyDrawObj )
        {
            const SwRect aRect( aPt.getX(), aPt.getY(), 1, 1 );

            if (SwDrawContact* pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)))
            {
                if ( pContact->GetAnchorFrame( pObj ) &&
                     pContact->GetAnchorFrame( pObj )->IsPageFrame() )
                    pAnch = pContact->GetPageFrame();
                else
                    pAnch = pContact->FindPage( aRect );
            }
        }
    }
    if( pAnch && !pAnch->IsProtected() )
        m_aAnchorPoint = pAnch->GetFrameAnchorPos( ::HasWrap( pObj ) );
    else
        pAnch = nullptr;
    return pAnch;
}

void SwDrawView::ShowDragAnchor()
{
    SdrHdl* pHdl = maHdlList.GetHdl(SdrHdlKind::Anchor);
    if ( ! pHdl )
        pHdl = maHdlList.GetHdl(SdrHdlKind::Anchor_TR);

    if(pHdl)
    {
        CalcAnchor();
        pHdl->SetPos(m_aAnchorPoint);
    }
}

void SwDrawView::MarkListHasChanged()
{
    Imp().GetShell().DrawSelChanged();
    FmFormView::MarkListHasChanged();
}

// #i7672#
void SwDrawView::ModelHasChanged()
{
    // The ModelHasChanged() call in DrawingLayer also updates
    // an eventually active text edit view (OutlinerView). This also leads
    // to newly setting the background color for that edit view. Thus,
    // this method rescues the current background color if an OutlinerView
    // exists and re-establishes it then. To be more safe, the OutlinerView
    // will be fetched again (maybe textedit has ended).
    OutlinerView* pView = GetTextEditOutlinerView();
    Color aBackColor;
    bool bColorWasSaved(false);

    if(pView)
    {
        aBackColor = pView->GetBackgroundColor();
        bColorWasSaved = true;
    }

    // call parent
    FmFormView::ModelHasChanged();

    if(bColorWasSaved)
    {
        pView = GetTextEditOutlinerView();

        if(pView)
        {
            pView->SetBackgroundColor(aBackColor);
        }
    }
}

void SwDrawView::MakeVisible( const tools::Rectangle &rRect, vcl::Window & )
{
    OSL_ENSURE( m_rImp.GetShell().GetWin(), "MakeVisible, unknown Window");
    m_rImp.GetShell().MakeVisible( SwRect( rRect ) );
}

void SwDrawView::CheckPossibilities()
{
    FmFormView::CheckPossibilities();

    //In addition to the existing flags of the objects themselves,
    //which are evaluated by the DrawingEngine, other circumstances
    //lead to a protection.
    //Objects that are anchored in frames need to be protected
    //if the content of the frame is protected.
    //OLE-Objects may themselves wish a resize protection (StarMath)

    const SdrMarkList &rMrkList = GetMarkedObjectList();
    bool bProtect = false;
    bool bSzProtect = false;
    bool bRotate(false);

    for ( size_t i = 0; !bProtect && i < rMrkList.GetMarkCount(); ++i )
    {
        const SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj();
        const SwFrame *pFrame = nullptr;
        if ( auto pVirtFlyDrawObj = dynamic_cast< const SwVirtFlyDrawObj *>( pObj ) )
        {
            const SwFlyFrame *pFly = pVirtFlyDrawObj->GetFlyFrame();
            if ( pFly  )
            {
                pFrame = pFly->GetAnchorFrame();
                const SwFrame* pLower = pFly->Lower();
                if ( pLower && pLower->IsNoTextFrame() )
                {
                    const SwNoTextFrame *const pNTF(static_cast<const SwNoTextFrame*>(pLower));
                    const SwOLENode *const pOLENd = pNTF->GetNode()->GetOLENode();
                    const SwGrfNode *const pGrfNd = pNTF->GetNode()->GetGrfNode();

                    if ( pOLENd )
                    {
                        const uno::Reference < embed::XEmbeddedObject > xObj = const_cast< SwOLEObj& >(pOLENd->GetOLEObj()).GetOleRef();

                        if ( xObj.is() )
                        {
                            // --> improvement for the future, when more
                            // than one Writer fly frame can be selected.

                            // TODO/LATER: retrieve Aspect - from where?!
                            bSzProtect |= ( embed::EmbedMisc::EMBED_NEVERRESIZE & xObj->getStatus( embed::Aspects::MSOLE_CONTENT ) ) != 0;

                            // #i972: protect position if it is a Math object anchored 'as char' and baseline alignment is activated
                            SwDoc* pDoc = Imp().GetShell().GetDoc();
                            const bool bProtectMathPos = SotExchange::IsMath( xObj->getClassID() )
                                    && RndStdIds::FLY_AS_CHAR == pFly->GetFormat()->GetAnchor().GetAnchorId()
                                    && pDoc->GetDocumentSettingManager().get( DocumentSettingId::MATH_BASELINE_ALIGNMENT );
                            if (bProtectMathPos)
                                m_bMoveProtect = true;
                        }
                    }
                    else if(pGrfNd)
                    {
                        // RotGrfFlyFrame: GraphicNode allows rotation(s). The loop ew are in stops
                        // as soon as bMoveProtect is set, but since rotation is valid only with
                        // a single object selected this makes no difference
                        bRotate = true;
                    }
                }
            }
        }
        else
        {
            SwDrawContact *pC = static_cast<SwDrawContact*>(GetUserCall(pObj));
            if ( pC )
                pFrame = pC->GetAnchorFrame( pObj );
        }
        if ( pFrame )
            bProtect = pFrame->IsProtected(); //Frames, areas etc.
        {
            SwFrameFormat* pFrameFormat( ::FindFrameFormat( const_cast<SdrObject*>(pObj) ) );
            if ( !pFrameFormat )
            {
                OSL_FAIL( "<SwDrawView::CheckPossibilities()> - missing frame format" );
                bProtect = true;
            }
            else if ((RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId()) &&
                      rMrkList.GetMarkCount() > 1 )
            {
                bProtect = true;
            }
        }
    }
    m_bMoveProtect    |= bProtect;
    m_bResizeProtect  |= bProtect || bSzProtect;

    // RotGrfFlyFrame: allow rotation when SwGrfNode is selected and not size protected
    m_bRotateFreeAllowed |= bRotate && !bProtect;
    m_bRotate90Allowed |= m_bRotateFreeAllowed;
}

/// replace marked <SwDrawVirtObj>-objects by its reference object for delete marked objects.
void SwDrawView::ReplaceMarkedDrawVirtObjs( SdrMarkView& _rMarkView )
{
    SdrPageView* pDrawPageView = _rMarkView.GetSdrPageView();
    const SdrMarkList& rMarkList = _rMarkView.GetMarkedObjectList();

    if( !rMarkList.GetMarkCount() )
        return;

    // collect marked objects in a local data structure
    std::vector<SdrObject*> aMarkedObjs;
    for( size_t i = 0; i < rMarkList.GetMarkCount(); ++i )
    {
        SdrObject* pMarkedObj = rMarkList.GetMark( i )->GetMarkedSdrObj();
        aMarkedObjs.push_back( pMarkedObj );
    }
    // unmark all objects
    _rMarkView.UnmarkAllObj();
    // re-mark objects, but for marked <SwDrawVirtObj>-objects marked its
    // reference object.
    while ( !aMarkedObjs.empty() )
    {
        SdrObject* pMarkObj = aMarkedObjs.back();
        if ( auto pVirtObj = dynamic_cast<SwDrawVirtObj *>( pMarkObj ) )
        {
            SdrObject* pRefObj = &(pVirtObj->ReferencedObj());
            if ( !_rMarkView.IsObjMarked( pRefObj )  )
            {
                _rMarkView.MarkObj( pRefObj, pDrawPageView );
            }
        }
        else
        {
            _rMarkView.MarkObj( pMarkObj, pDrawPageView );
        }

        aMarkedObjs.pop_back();
    }
    // sort marked list in order to assure consistent state in drawing layer
    _rMarkView.GetMarkedObjectList().ForceSort();
}

SfxViewShell* SwDrawView::GetSfxViewShell() const
{
    return m_rImp.GetShell().GetSfxViewShell();
}

void SwDrawView::DeleteMarked()
{
    SwDoc* pDoc = Imp().GetShell().GetDoc();
    SwRootFrame *pTmpRoot = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
    if ( pTmpRoot )
        pTmpRoot->StartAllAction();
    pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
    // replace marked <SwDrawVirtObj>-objects by its reference objects.
    if (SdrPageView* pDrawPageView = m_rImp.GetPageView())
    {
        ReplaceMarkedDrawVirtObjs(pDrawPageView->GetView());
    }

    // Check what textboxes have to be deleted afterwards.
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    std::vector<SwFrameFormat*> aTextBoxesToDelete;
    for (size_t i = 0; i < rMarkList.GetMarkCount(); ++i)
    {
        SdrObject *pObject = rMarkList.GetMark(i)->GetMarkedSdrObj();
        SwContact* pContact = GetUserCall(pObject);
        if (pContact)
        {
            SwFrameFormat* pFormat = pContact->GetFormat();
            if (pObject->getChildrenOfSdrObject())
            {
                auto pChildTextBoxes = SwTextBoxHelper::CollectTextBoxes(pObject, pFormat);
                for (auto& rChildTextBox : pChildTextBoxes)
                    aTextBoxesToDelete.push_back(rChildTextBox);
            }
            else if (SwFrameFormat* pTextBox
                     = SwTextBoxHelper::getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT))
                aTextBoxesToDelete.push_back(pTextBox);
        }
    }

    if ( pDoc->DeleteSelection( *this ) )
    {
        FmFormView::DeleteMarked();
        ::FrameNotify( &Imp().GetShell(), FLY_DRAG_END );
    }

    // Only delete these now: earlier deletion would clear the mark list as well.
    // Delete in reverse order, assuming that the container is sorted by anchor positions.
    for (int i = aTextBoxesToDelete.size() - 1; i >= 0; --i)
    {
        SwFrameFormat*& rpTextBox = aTextBoxesToDelete[i];
        pDoc->getIDocumentLayoutAccess().DelLayoutFormat(rpTextBox);
    }

    pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
    if( pTmpRoot )
        pTmpRoot->EndAllAction();
}

// Create a new view-local UndoManager manager for Writer
std::unique_ptr<SdrUndoManager> SwDrawView::createLocalTextUndoManager()
{
    std::unique_ptr<SdrUndoManager> pUndoManager(new SdrUndoManager);
    pUndoManager->SetDocShell(SfxObjectShell::Current());
    return pUndoManager;
}

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