tdf#117185 tdf#110442 sw: bring harmony & peace to fly at-char selection

Use IsDestroyFrameAnchoredAtChar() to harmonize the at-char fly
selection across all relevant operations:

* CopyImpl: this is the most tricky one:
- the code in CopyWithFlyInFly() and CopyFlyInFlyImpl() is quite con-
  voluted as it needs to do some things ignoring a partially selected
  start node, while including it in other cases
- it had pre-existing bugs too that would lose a fly anchored to the
  2nd (1st fully selected) node of a redline
- now it needs to copy the flys in the selection if it is inside a
  single node
- another complication is that flys that already existed at the
  insert position need to have their anchors corrected
- SwUndoInsLayFormat need to be created for the appropriate flys
- SwUndoInserts Undo/Redo needs to run the nested SwUndoInsLayFormat
  at the appropriate time
- SwUndoInserts::UndoImpl() needs a special case to *never* delete
  flys at the start/end of the selection because those are handled by
  nested SwUndoInsLayFormat
- Insert File (shellio.cxx) needs adapting to the SwUndoInserts change

* DeleteRange: this just needs to delete the flys via DelFlyInRange()

* MoveRange:
- this is used by the old SwRangeRedline Show/Hide, i.e. on ODF export
- the SaveFlyInRange()/RestFlyInRange() was rather inadequate and
  didn't even restore content indexes at all...

* IsShown: the sw_redlinehide code needs to check visibility against
  the (inverted) extents

The selection behavior is changed so that at-char flys in the start and
end node of the selection are also selected, instead of having their
anchor moved to a different content index by the operation. This appears
more obvious and user-friendly, fixes tdf#110442, and is also more like
what Word does.

Selections exclude the start and end position except if it's a fully
selected node or at the start or end of a section (i.e. Ctrl+A should
also select every at-char fly).

A special hack is needed to keep writerfilter happy for now; it likes to
anchor flys at nodes which it then deletes in RemoveLastParagraph(),
which likely could be improved there (disposing the SwXParagraph runs
into the same problem...).

Crashes fixed by this:
tdf#117185
tdf#117215 except comment#12
tdf#124720
tdf#124721
tdf#124739

Previously fixed bugs tested:
i#97570 plus the 2 bugs that already have UITests

Change-Id: I4fec2a3c15ca0e64e5c4e99acfb04f59bb2bcf64
Reviewed-on: https://gerrit.libreoffice.org/75516
Tested-by: Jenkins
Reviewed-by: Michael Stahl <Michael.Stahl@cib.de>
diff --git a/sw/inc/undobj.hxx b/sw/inc/undobj.hxx
index 38cab61..3128ffc 100644
--- a/sw/inc/undobj.hxx
+++ b/sw/inc/undobj.hxx
@@ -35,6 +35,7 @@ struct SwPosition;
class SwDoc;
class SwTextFormatColl;
class SwFrameFormat;
class SwFormatAnchor;
class SwNodeIndex;
class SwNodeRange;
class SwRedlineData;
@@ -134,10 +135,11 @@ enum class DelContentType : sal_uInt16
    Fly          = 0x02,
    Bkm          = 0x08,
    AllMask      = 0x0b,
    ExcludeAtCharFlyAtStartEnd = 0x40,
    CheckNoCntnt = 0x80,
};
namespace o3tl {
    template<> struct typed_flags<DelContentType> : is_typed_flags<DelContentType, 0x8b> {};
    template<> struct typed_flags<DelContentType> : is_typed_flags<DelContentType, 0xcb> {};
}

/// will DelContentIndex destroy a frame anchored at character at rAnchorPos?
@@ -227,6 +229,13 @@ public:

class SwUndoInsLayFormat;

namespace sw {

std::unique_ptr<std::vector<SwFrameFormat*>>
GetFlysAnchoredAt(SwDoc & rDoc, sal_uLong nSttNode);

}

// base class for insertion of Document, Glossaries and Copy
class SwUndoInserts : public SwUndo, public SwUndRng, private SwUndoSaveContent
{
@@ -252,6 +261,10 @@ public:
    // Set destination range after reading.
    void SetInsertRange( const SwPaM&, bool bScanFlys = true,
                         bool bSttWasTextNd = true );

    static bool IsCreateUndoForNewFly(SwFormatAnchor const& rAnchor,
        sal_uLong const nStartNode, sal_uLong const nEndNode);
    std::vector<SwFrameFormat*> * GetFlysAnchoredAt() { return pFrameFormats.get(); }
};

class SwUndoInsDoc : public SwUndoInserts
diff --git a/sw/qa/uitest/writer_tests6/tdf107975.py b/sw/qa/uitest/writer_tests6/tdf107975.py
index 202e742..333c0f7 100644
--- a/sw/qa/uitest/writer_tests6/tdf107975.py
+++ b/sw/qa/uitest/writer_tests6/tdf107975.py
@@ -27,6 +27,8 @@ class tdf107975(UITestCase):
        xWriterDoc = self.xUITest.getTopFocusWindow()
        xWriterEdit = xWriterDoc.getChild("writer_edit")

        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1)

        #Press CTRL+A and + CTRL+C
        self.xUITest.executeCommand(".uno:SelectAll")
        self.xUITest.executeCommand(".uno:Copy")
@@ -34,9 +36,46 @@ class tdf107975(UITestCase):
        xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "RIGHT"}))
        #Paste CTRL+V
        self.xUITest.executeCommand(".uno:Paste")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2)
        #Undo paste CTRL+Z -> Crash
        self.xUITest.executeCommand(".uno:Undo")
        self.assertEqual(document.Text.String[0:3], "ABC")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1)
        self.xUITest.executeCommand(".uno:Redo")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2)
        self.xUITest.executeCommand(".uno:Undo")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1)
        self.xUITest.executeCommand(".uno:Redo")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2)
        self.xUITest.executeCommand(".uno:Undo")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1)

        # try again with anchor at start of doc which is another special case
        xShape = writer_doc.getGraphicObjects().getByIndex(0)
        xStart = writer_doc.getText().getStart()
        xShape.attach(xStart)

        #Press CTRL+A and + CTRL+C
        self.xUITest.executeCommand(".uno:SelectAll")
        self.xUITest.executeCommand(".uno:Copy")
        #Position the mouse cursor (caret) after "ABC" below the blue image
        xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "RIGHT"}))
        #Paste CTRL+V
        self.xUITest.executeCommand(".uno:Paste")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2)
        #Undo paste CTRL+Z -> Crash
        self.xUITest.executeCommand(".uno:Undo")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1)
        self.assertEqual(document.Text.String[0:3], "ABC")
        self.xUITest.executeCommand(".uno:Redo")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2)
        self.xUITest.executeCommand(".uno:Undo")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1)
        self.xUITest.executeCommand(".uno:Redo")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 2)
        self.xUITest.executeCommand(".uno:Undo")
        self.assertEqual(writer_doc.getGraphicObjects().getCount(), 1)

        self.ui_test.close_doc()

# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx
index a4e0e67..bc632acf 100644
--- a/sw/source/core/doc/DocumentContentOperationsManager.cxx
+++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx
@@ -2003,6 +2003,9 @@ bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam )
                if (pAPos &&
                    ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
                     (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
                    // note: here use <= not < like in
                    // IsDestroyFrameAnchoredAtChar() because of the increment
                    // of rPam in the bDoesUndo path above!
                    aRg.aStart <= pAPos->nNode && pAPos->nNode <= aRg.aEnd )
                {
                    m_rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFly );
@@ -2044,7 +2047,7 @@ bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos,

    // Save the paragraph anchored Flys, so that they can be moved.
    SaveFlyArr aSaveFlyArr;
    SaveFlyInRange( rPaM, rPos.nNode, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) );
    SaveFlyInRange( rPaM, rPos, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) );

    // save redlines (if DOC_MOVEREDLINES is used)
    SaveRedlines_t aSaveRedl;
@@ -2285,7 +2288,10 @@ bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos,
    *rPaM.GetPoint() = *aSavePam.End();

    // Move the Flys to the new position.
    RestFlyInRange( aSaveFlyArr, rPaM.Start()->nNode, &(rPos.nNode) );
    // note: rPos is at the end here; can't really tell flys that used to be
    // at the start of rPam from flys that used to be at the end of rPam
    // unfortunately, so some of them are going to end up with wrong anchor...
    RestFlyInRange( aSaveFlyArr, *rPaM.Start(), &(rPos.nNode) );

    // restore redlines (if DOC_MOVEREDLINES is used)
    if( !aSaveRedl.empty() )
@@ -2391,7 +2397,10 @@ bool DocumentContentOperationsManager::MoveNodeRange( SwNodeRange& rRange, SwNod

    // move the Flys to the new position
    if( !aSaveFlyArr.empty() )
        RestFlyInRange( aSaveFlyArr, aIdx, nullptr );
    {
        SwPosition const tmp(aIdx);
        RestFlyInRange(aSaveFlyArr, tmp, nullptr);
    }

    // Add the Bookmarks back to the Document
    for(auto& rBkmk : aSaveBkmks)
@@ -3296,31 +3305,36 @@ void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(const SwPosition 
}

// Copy method from SwDoc - "copy Flys in Flys"
/// note: rRg/rInsPos *exclude* a partially selected start text node;
///       pCopiedPaM *includes* a partially selected start text node
void DocumentContentOperationsManager::CopyWithFlyInFly(
    const SwNodeRange& rRg,
    const sal_Int32 nEndContentIndex,
    const SwNodeIndex& rInsPos,
    const std::pair<const SwPaM&, const SwPosition&>* pCopiedPaM /*and real insert pos*/,
    const bool bMakeNewFrames,
    const bool bDelRedlines,
    const bool bCopyFlyAtFly ) const
{
    assert(!pCopiedPaM || pCopiedPaM->first.End()->nContent == nEndContentIndex);
    assert(!pCopiedPaM || pCopiedPaM->first.End()->nNode == rRg.aEnd);
    assert(!pCopiedPaM || pCopiedPaM->second.nNode <= rInsPos);

    SwDoc* pDest = rInsPos.GetNode().GetDoc();

    SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 );

    SwNodeIndex aSavePos( rInsPos, -1 );
    bool bEndIsEqualEndPos = rInsPos == rRg.aEnd;
    m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, bMakeNewFrames, true );

    if (rRg.aStart != rRg.aEnd)
    {
        SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 );

        // insert behind the already copied start node
        m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, bMakeNewFrames, true );
        aRedlRest.Restore();
    }

    ++aSavePos;
    if( bEndIsEqualEndPos )
        const_cast<SwNodeIndex&>(rRg.aEnd) = aSavePos;

    aRedlRest.Restore();

#if OSL_DEBUG_LEVEL > 0
    {
        //JP 17.06.99: Bug 66973 - check count only if the selection is in
@@ -3344,7 +3358,12 @@ void DocumentContentOperationsManager::CopyWithFlyInFly(

    {
        ::sw::UndoGuard const undoGuard(pDest->GetIDocumentUndoRedo());
        CopyFlyInFlyImpl( rRg, nEndContentIndex, aSavePos, bCopyFlyAtFly );
        CopyFlyInFlyImpl(rRg, pCopiedPaM ? &pCopiedPaM->first : nullptr,
            // see comment below regarding use of pCopiedPaM->second
            (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->nNode)
                ? pCopiedPaM->second.nNode
                : aSavePos,
            bCopyFlyAtFly);
    }

    SwNodeRange aCpyRange( aSavePos, rInsPos );
@@ -3376,18 +3395,16 @@ void DocumentContentOperationsManager::CopyWithFlyInFly(
    pDest->GetNodes().DelDummyNodes( aCpyRange );
}

// TODO: there is a limitation here in that it's not possible to pass a start
// content index - which means that at-character anchored frames inside
// partial 1st paragraph of redline is not copied.
// But the DelFlyInRange() that is called from DelCopyOfSection() does not
// delete it either, and it also does not delete those on partial last para of
// redline, so copying those is suppressed here too ...
// note: for the redline Show/Hide this must be in sync with
// SwRangeRedline::CopyToSection()/DelCopyOfSection()/MoveFromSection()
void DocumentContentOperationsManager::CopyFlyInFlyImpl(
    const SwNodeRange& rRg,
    const sal_Int32 nEndContentIndex,
    SwPaM const*const pCopiedPaM,
    const SwNodeIndex& rStartIdx,
    const bool bCopyFlyAtFly ) const
{
    assert(!pCopiedPaM || pCopiedPaM->End()->nNode == rRg.aEnd);

    // First collect all Flys, sort them according to their ordering number,
    // and then only copy them. This maintains the ordering numbers (which are only
    // managed in the DrawModel).
@@ -3404,9 +3421,9 @@ void DocumentContentOperationsManager::CopyFlyInFlyImpl(
        SwFrameFormat* pFormat = (*m_rDoc.GetSpzFrameFormats())[n];
        SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
        SwPosition const*const pAPos = pAnchor->GetContentAnchor();
        bool bAtContent = (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA);
        if ( !pAPos )
            continue;
        bool bAdd = false;
        sal_uLong nSkipAfter = pAPos->nNode.GetIndex();
        sal_uLong nStart = rRg.aStart.GetIndex();
        switch ( pAnchor->GetAnchorId() )
@@ -3417,59 +3434,66 @@ void DocumentContentOperationsManager::CopyFlyInFlyImpl(
                else if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove())
                    ++nStart;
            break;
            case RndStdIds::FLY_AT_CHAR:
            case RndStdIds::FLY_AT_PARA:
                // FIXME TODO why exclude start node, this seems very questionable and causes data loss on export
                if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove())
                    ++nStart;
            break;
            case RndStdIds::FLY_AT_CHAR:
                {
                    bAdd = IsDestroyFrameAnchoredAtChar(*pAPos,
                        pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart),
                        pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd));
                }
            break;
            default:
                continue;
        }
        if ( nStart > nSkipAfter )
            continue;
        if ( pAPos->nNode > rRg.aEnd )
            continue;
        //frames at the last source node are not always copied:
        //- if the node is empty and is the last node of the document or a table cell
        //  or a text frame then they have to be copied
        //- if the content index in this node is > 0 then paragraph and frame bound objects are copied
        //- to-character bound objects are copied if their index is <= nEndContentIndex
        bool bAdd = false;
        if( pAPos->nNode < rRg.aEnd )
            bAdd = true;
        if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move
        if (RndStdIds::FLY_AT_CHAR != pAnchor->GetAnchorId())
        {
            bool bEmptyNode = false;
            bool bLastNode = false;
            // is the node empty?
            const SwNodes& rNodes = pAPos->nNode.GetNodes();
            SwTextNode* pTextNode;
            if( nullptr != ( pTextNode = pAPos->nNode.GetNode().GetTextNode() ))
            if (nStart > nSkipAfter)
                continue;
            if (pAPos->nNode > rRg.aEnd)
                continue;
            //frames at the last source node are not always copied:
            //- if the node is empty and is the last node of the document or a table cell
            //  or a text frame then they have to be copied
            //- if the content index in this node is > 0 then paragraph and frame bound objects are copied
            //- to-character bound objects are copied if their index is <= nEndContentIndex
            if (pAPos->nNode < rRg.aEnd)
                bAdd = true;
            if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move
            {
                bEmptyNode = pTextNode->GetText().isEmpty();
                if( bEmptyNode )
                bool bEmptyNode = false;
                bool bLastNode = false;
                // is the node empty?
                const SwNodes& rNodes = pAPos->nNode.GetNodes();
                SwTextNode *const pTextNode = pAPos->nNode.GetNode().GetTextNode();
                if (nullptr != pTextNode)
                {
                    //last node information is only necessary to know for the last TextNode
                    SwNodeIndex aTmp( pAPos->nNode );
                    ++aTmp;//goto next node
                    while (aTmp.GetNode().IsEndNode())
                    bEmptyNode = pTextNode->GetText().isEmpty();
                    if (bEmptyNode)
                    {
                        if( aTmp == rNodes.GetEndOfContent().GetIndex() )
                        //last node information is only necessary to know for the last TextNode
                        SwNodeIndex aTmp( pAPos->nNode );
                        ++aTmp;//goto next node
                        while (aTmp.GetNode().IsEndNode())
                        {
                            bLastNode = true;
                            break;
                            if (aTmp == rNodes.GetEndOfContent().GetIndex())
                            {
                                bLastNode = true;
                                break;
                            }
                            ++aTmp;
                        }
                        ++aTmp;
                    }
                }
            }
            bAdd = bLastNode && bEmptyNode;
            if( !bAdd )
            {
                if( bAtContent )
                    bAdd = nEndContentIndex > 0;
                else
                    bAdd = pAPos->nContent <= nEndContentIndex;
                bAdd = bLastNode && bEmptyNode;
                if (!bAdd)
                {
                    // technically old code checked nContent of AT_FLY which is pointless
                    bAdd = pCopiedPaM && 0 < pCopiedPaM->End()->nContent.GetIndex();
                }
            }
        }
        if( bAdd )
@@ -3508,7 +3532,8 @@ void DocumentContentOperationsManager::CopyFlyInFlyImpl(
            // Note: The anchor text node *have* to be inside the copied range.
            sal_uLong nAnchorTextNdNumInRange( 0 );
            bool bAnchorTextNdFound( false );
            SwNodeIndex aIdx( rRg.aStart );
            // start at the first node for which flys are copied
            SwNodeIndex aIdx(pCopiedPaM ? pCopiedPaM->Start()->nNode : rRg.aStart);
            while ( !bAnchorTextNdFound && aIdx <= rRg.aEnd )
            {
                if ( aIdx.GetNode().IsTextNode() )
@@ -3569,7 +3594,11 @@ void DocumentContentOperationsManager::CopyFlyInFlyImpl(
        if ((RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) &&
             newPos.nNode.GetNode().IsTextNode() )
        {
            newPos.nContent.Assign( newPos.nNode.GetNode().GetTextNode(), newPos.nContent.GetIndex() );
            // only if pCopiedPaM: care about partially selected start node
            sal_Int32 const nContent = pCopiedPaM && pCopiedPaM->Start()->nNode == aAnchor.GetContentAnchor()->nNode
                ? newPos.nContent.GetIndex() - pCopiedPaM->Start()->nContent.GetIndex()
                : newPos.nContent.GetIndex();
            newPos.nContent.Assign(newPos.nNode.GetNode().GetTextNode(), nContent);
        }
        else
        {
@@ -3948,7 +3977,8 @@ bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam)
        m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any );

    // Delete and move all "Flys at the paragraph", which are within the Selection
    DelFlyInRange(rPam.GetMark()->nNode, rPam.GetPoint()->nNode);
    DelFlyInRange(rPam.GetMark()->nNode, rPam.GetPoint()->nNode,
        &rPam.GetMark()->nContent, &rPam.GetPoint()->nContent);
    DelBookmarks(
        pStt->nNode,
        pEnd->nNode,
@@ -4388,8 +4418,8 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos,
    SwDoc* pDoc = rPos.nNode.GetNode().GetDoc();
    const bool bColumnSel = pDoc->IsClipBoard() && pDoc->IsColumnSelection();

    SwPosition* pStt = rPam.Start();
    SwPosition* pEnd = rPam.End();
    SwPosition const*const pStt = rPam.Start();
    SwPosition *const pEnd = rPam.End();

    // Catch when there's no copy to do.
    if( !rPam.HasMark() || ( *pStt >= *pEnd && !bColumnSel ) ||
@@ -4409,11 +4439,19 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos,
    std::shared_ptr<SwUnoCursor> const pCopyPam(pDoc->CreateUnoCursor(rPos));

    SwTableNumFormatMerge aTNFM( m_rDoc, *pDoc );
    std::unique_ptr<std::vector<SwFrameFormat*>> pFlys;
    std::vector<SwFrameFormat*> const* pFlysAtInsPos;

    if (pDoc->GetIDocumentUndoRedo().DoesUndo())
    {
        pUndo = new SwUndoCpyDoc(*pCopyPam);
        pDoc->GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
        pFlysAtInsPos = pUndo->GetFlysAnchoredAt();
    }
    else
    {
        pFlys = sw::GetFlysAnchoredAt(*pDoc, rPos.nNode.GetIndex());
        pFlysAtInsPos = pFlys.get();
    }

    RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags();
@@ -4435,7 +4473,10 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos,
        bAfterTable = true;
    }
    if( !bCanMoveBack )
    {
        pCopyPam->GetPoint()->nNode--;
        assert(pCopyPam->GetPoint()->nContent.GetIndex() == 0);
    }

    SwNodeRange aRg( pStt->nNode, pEnd->nNode );
    SwNodeIndex aInsPos( rPos.nNode );
@@ -4561,6 +4602,8 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos,
                        pEnd->nContent -= nCpyLen;
                }

                aRg.aStart++;

                if( bOneNode )
                {
                    if (bCopyCollFormat)
@@ -4569,10 +4612,12 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos,
                        POP_NUMRULE_STATE
                    }

                    // copy at-char flys in rPam
                    aInsPos = *pDestTextNd; // update to new (start) node for flys
                    CopyFlyInFlyImpl(aRg, &rPam, aInsPos, false);

                    break;
                }

                aRg.aStart++;
            }
        }
        else if( pDestTextNd )
@@ -4670,9 +4715,9 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos,
            }
        }

        SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange );
        if( bCopyAll || aRg.aStart != aRg.aEnd )
        {
            SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange );
            if (pSttTextNd && bCopyCollFormat && pDestTextNd->HasSwAttrSet())
            {
                aBrkSet.Put( *pDestTextNd->GetpSwAttrSet() );
@@ -4681,7 +4726,9 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos,
                if( SfxItemState::SET == aBrkSet.GetItemState( RES_PAGEDESC, false ) )
                    pDestTextNd->ResetAttr( RES_PAGEDESC );
            }
        }

        {
            SwPosition startPos(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1),
                SwIndex(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1).GetNode().GetContentNode()));
            if (bCanMoveBack)
@@ -4695,16 +4742,48 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos,
            if( aInsPos == pEnd->nNode )
            {
                SwNodeIndex aSaveIdx( aInsPos, -1 );
                CopyWithFlyInFly( aRg, 0, aInsPos, &tmp, bMakeNewFrames, false );
                assert(pStt->nNode != pEnd->nNode);
                pEnd->nContent = 0; // TODO why this?
                CopyWithFlyInFly( aRg, aInsPos, &tmp, bMakeNewFrames, false );
                ++aSaveIdx;
                pEnd->nNode = aSaveIdx;
                pEnd->nContent.Assign( aSaveIdx.GetNode().GetTextNode(), 0 );
            }
            else
                CopyWithFlyInFly( aRg, pEnd->nContent.GetIndex(), aInsPos, &tmp, bMakeNewFrames, false );
                CopyWithFlyInFly( aRg, aInsPos, &tmp, bMakeNewFrames, false );

            bCopyBookmarks = false;
        }

        // at-char anchors post SplitNode are on index 0 of 2nd node and will
        // remain there - move them back to the start (end would also work?)
        if (pFlysAtInsPos)
        {
            // init *again* - because CopyWithFlyInFly moved startPos
            SwPosition startPos(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1),
                SwIndex(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1).GetNode().GetContentNode()));
            if (bCanMoveBack)
            {   // pCopyPam is actually 1 before the copy range so move it fwd
                SwPaM temp(*pCopyPam->GetPoint());
                temp.Move(fnMoveForward, GoInContent);
                startPos = *temp.GetPoint();
            }
            assert(startPos.nNode.GetNode().IsContentNode());

            for (SwFrameFormat * pFly : *pFlysAtInsPos)
            {
                SwFormatAnchor const*const pAnchor = &pFly->GetAnchor();
                if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
                {
                    SwFormatAnchor anchor(*pAnchor);
                    anchor.SetAnchor( &startPos );
                    pFly->SetFormatAttr(anchor);
                }
            }
        }

        if (bCopyAll || aRg.aStart != aRg.aEnd)
        {
            // Put the breaks back into the first node
            if( aBrkSet.Count() && nullptr != ( pDestTextNd = pDoc->GetNodes()[
                    pCopyPam->GetPoint()->nNode.GetIndex()+1 ]->GetTextNode()))
diff --git a/sw/source/core/doc/DocumentLayoutManager.cxx b/sw/source/core/doc/DocumentLayoutManager.cxx
index 68e08ba..11ed401 100644
--- a/sw/source/core/doc/DocumentLayoutManager.cxx
+++ b/sw/source/core/doc/DocumentLayoutManager.cxx
@@ -426,7 +426,7 @@ SwFrameFormat *DocumentLayoutManager::CopyLayoutFormat(
        //contact object itself. They should be managed by SwUndoInsLayFormat.
        const ::sw::DrawUndoGuard drawUndoGuard(m_rDoc.GetIDocumentUndoRedo());

        pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( aRg, 0, aIdx, nullptr, false, true, true );
        pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aIdx, nullptr, false, true, true);
    }
    else
    {
diff --git a/sw/source/core/doc/doccomp.cxx b/sw/source/core/doc/doccomp.cxx
index 3054d17..0df9cdf 100644
--- a/sw/source/core/doc/doccomp.cxx
+++ b/sw/source/core/doc/doccomp.cxx
@@ -1532,7 +1532,7 @@ void CompareData::ShowDelete(
    SwNodeIndex aInsPos( *pLineNd, nOffset );
    SwNodeIndex aSavePos( aInsPos, -1 );

    rData.rDoc.GetDocumentContentOperationsManager().CopyWithFlyInFly( aRg, 0, aInsPos );
    rData.rDoc.GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aInsPos);
    rDoc.getIDocumentState().SetModified();
    ++aSavePos;

diff --git a/sw/source/core/doc/docdesc.cxx b/sw/source/core/doc/docdesc.cxx
index ca14991..c7a59f3 100644
--- a/sw/source/core/doc/docdesc.cxx
+++ b/sw/source/core/doc/docdesc.cxx
@@ -299,7 +299,7 @@ void SwDoc::CopyMasterHeader(const SwPageDesc &rChged, const SwFormatHeader &rHe
                aTmp = *pSttNd->EndOfSectionNode();
                GetNodes().Copy_( aRange, aTmp, false );
                aTmp = *pSttNd;
                GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, 0, aTmp);
                GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, aTmp);

                pFormat->SetFormatAttr( SwFormatContent( pSttNd ) );
                rDescFrameFormat.SetFormatAttr( SwFormatHeader( pFormat ) );
@@ -371,7 +371,7 @@ void SwDoc::CopyMasterFooter(const SwPageDesc &rChged, const SwFormatFooter &rFo
                aTmp = *pSttNd->EndOfSectionNode();
                GetNodes().Copy_( aRange, aTmp, false );
                aTmp = *pSttNd;
                GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, 0, aTmp);
                GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, aTmp);

                pFormat->SetFormatAttr( SwFormatContent( pSttNd ) );
                rDescFrameFormat.SetFormatAttr( SwFormatFooter( pFormat ) );
diff --git a/sw/source/core/doc/docedt.cxx b/sw/source/core/doc/docedt.cxx
index baf3280..1388bdc 100644
--- a/sw/source/core/doc/docedt.cxx
+++ b/sw/source/core/doc/docedt.cxx
@@ -45,6 +45,7 @@
#include <docedt.hxx>
#include <frmfmt.hxx>
#include <ndtxt.hxx>
#include <undobj.hxx>

#include <vector>
#include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
@@ -54,27 +55,47 @@ using namespace ::com::sun::star::linguistic2;
using namespace ::com::sun::star::i18n;


void RestFlyInRange( SaveFlyArr & rArr, const SwNodeIndex& rSttIdx,
void RestFlyInRange( SaveFlyArr & rArr, const SwPosition& rStartPos,
                      const SwNodeIndex* pInsertPos )
{
    SwPosition aPos( rSttIdx );
    SwPosition aPos(rStartPos);
    for(SaveFly & rSave : rArr)
    {
        // create new anchor
        SwFrameFormat* pFormat = rSave.pFrameFormat;
        SwFormatAnchor aAnchor( pFormat->GetAnchor() );

        if( rSave.bInsertPosition )
        if (rSave.isAtInsertNode)
        {
            if( pInsertPos != nullptr )
                aPos.nNode = *pInsertPos;
            {
                if (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA)
                {
                    aPos.nNode = *pInsertPos;
                    aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()),
                        rSave.nContentIndex);
                }
                else
                {
                    assert(aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR);
                    aPos = rStartPos;
                }
            }
            else
                aPos.nNode = rSttIdx.GetIndex();
            {
                aPos.nNode = rStartPos.nNode;
                aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()), 0);
            }
        }
        else
            aPos.nNode = rSttIdx.GetIndex() + rSave.nNdDiff;
        {
            aPos.nNode = rStartPos.nNode.GetIndex() + rSave.nNdDiff;
            aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()),
                rSave.nNdDiff == 0
                    ? rStartPos.nContent.GetIndex() + rSave.nContentIndex
                    : rSave.nContentIndex);
        }

        aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()), 0);
        SwFormatAnchor aAnchor( pFormat->GetAnchor() );
        aAnchor.SetAnchor( &aPos );
        pFormat->GetDoc()->GetSpzFrameFormats()->push_back( pFormat );
        // SetFormatAttr should call Modify() and add it to the node
@@ -83,7 +104,7 @@ void RestFlyInRange( SaveFlyArr & rArr, const SwNodeIndex& rSttIdx,
        if (pCNd && pCNd->getLayoutFrame(pFormat->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr))
            pFormat->MakeFrames();
    }
    sw::CheckAnchoredFlyConsistency(*rSttIdx.GetNode().GetDoc());
    sw::CheckAnchoredFlyConsistency(*rStartPos.nNode.GetNode().GetDoc());
}

void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr )
@@ -100,6 +121,9 @@ void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr )
            rRg.aStart <= pAPos->nNode && pAPos->nNode < rRg.aEnd )
        {
            SaveFly aSave( pAPos->nNode.GetIndex() - rRg.aStart.GetIndex(),
                            (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())
                                ? pAPos->nContent.GetIndex()
                                : 0,
                            pFormat, false );
            rArr.push_back( aSave );
            pFormat->DelFrames();
@@ -113,7 +137,7 @@ void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr )
    sw::CheckAnchoredFlyConsistency(*rRg.aStart.GetNode().GetDoc());
}

void SaveFlyInRange( const SwPaM& rPam, const SwNodeIndex& rInsPos,
void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos,
                       SaveFlyArr& rArr, bool bMoveAllFlys )
{
    SwFrameFormats& rFormats = *rPam.GetPoint()->nNode.GetNode().GetDoc()->GetSpzFrameFormats();
@@ -142,12 +166,14 @@ void SaveFlyInRange( const SwPaM& rPam, const SwNodeIndex& rInsPos,
             (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
            // do not move if the InsPos is in the ContentArea of the Fly
            ( nullptr == ( pContentIdx = pFormat->GetContent().GetContentIdx() ) ||
              !( *pContentIdx < rInsPos &&
                rInsPos < pContentIdx->GetNode().EndOfSectionIndex() )) )
              !(*pContentIdx < rInsPos.nNode &&
                rInsPos.nNode < pContentIdx->GetNode().EndOfSectionIndex())))
        {
            bool bInsPos = false;

            if( !bMoveAllFlys && rEndNdIdx == pAPos->nNode )
            if (!bMoveAllFlys
                && RndStdIds::FLY_AT_CHAR != pAnchor->GetAnchorId()
                && rEndNdIdx == pAPos->nNode)
            {
                // Do not touch Anchor, if only a part of the EndNode
                // or the whole EndNode is identical with the SttNode
@@ -160,12 +186,23 @@ void SaveFlyInRange( const SwPaM& rPam, const SwNodeIndex& rInsPos,
                    pFormat->SetFormatAttr( aAnchor );
                }
            }
            else if( ( rSttNdIdx.GetIndex() + nSttOff <= pAPos->nNode.GetIndex()
                    && pAPos->nNode.GetIndex() <= rEndNdIdx.GetIndex() - nOff ) ||
                        ( bInsPos = (rInsPos == pAPos->nNode) ))

            else if (  (//bMoveAllFlys ... no do not check - all callers are actually from redline code, from the MoveToSection case; so check bMoveAllFlys only for AT_PARA!
                           (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())
                        && IsDestroyFrameAnchoredAtChar(*pAPos, *rPam.Start(), *rPam.End()))
                    || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()
                        && rSttNdIdx.GetIndex() + nSttOff <= pAPos->nNode.GetIndex()
                        && pAPos->nNode.GetIndex() <= rEndNdIdx.GetIndex() - nOff)
                    || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()
                            && (bInsPos = (rInsPos.nNode == pAPos->nNode)))
                    || (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()
                            && (bInsPos = (rInsPos == *pAPos))))
            {
                SaveFly aSave( pAPos->nNode.GetIndex() - rSttNdIdx.GetIndex(),
                    (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())
                        ? (pAPos->nNode == rSttNdIdx)
                            ? pAPos->nContent.GetIndex() - rPam.Start()->nContent.GetIndex()
                            : pAPos->nContent.GetIndex()
                        : 0,
                                pFormat, bInsPos );
                rArr.push_back( aSave );
                pFormat->DelFrames();
@@ -183,8 +220,18 @@ void SaveFlyInRange( const SwPaM& rPam, const SwNodeIndex& rInsPos,
/// Delete and move all Flys at the paragraph, that are within the selection.
/// If there is a Fly at the SPoint, it is moved onto the Mark.
void DelFlyInRange( const SwNodeIndex& rMkNdIdx,
                    const SwNodeIndex& rPtNdIdx )
                    const SwNodeIndex& rPtNdIdx,
                    SwIndex const*const pMkIdx, SwIndex const*const pPtIdx)
{
    assert((pMkIdx == nullptr) == (pPtIdx == nullptr));
    SwPosition const point(pPtIdx
                            ? SwPosition(rPtNdIdx, *pPtIdx)
                            : SwPosition(rPtNdIdx));
    SwPosition const mark(pPtIdx
                            ? SwPosition(rMkNdIdx, *pMkIdx)
                            : SwPosition(rMkNdIdx));
    SwPosition const& rStart = mark <= point ? mark : point;
    SwPosition const& rEnd   = mark <= point ? point : mark;
    const bool bDelFwrd = rMkNdIdx.GetIndex() <= rPtNdIdx.GetIndex();

    SwDoc* pDoc = rMkNdIdx.GetNode().GetDoc();
@@ -195,14 +242,18 @@ void DelFlyInRange( const SwNodeIndex& rMkNdIdx,
        const SwFormatAnchor &rAnch = pFormat->GetAnchor();
        SwPosition const*const pAPos = rAnch.GetContentAnchor();
        if (pAPos &&
            ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA) ||
             (rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR)) &&
            ( bDelFwrd
                ? rMkNdIdx < pAPos->nNode && pAPos->nNode <= rPtNdIdx
                : rPtNdIdx <= pAPos->nNode && pAPos->nNode < rMkNdIdx ))
            (((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA)
                && (bDelFwrd
                    ? rMkNdIdx < pAPos->nNode && pAPos->nNode <= rPtNdIdx
                    : rPtNdIdx <= pAPos->nNode && pAPos->nNode < rMkNdIdx))
            || ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR)
                && IsDestroyFrameAnchoredAtChar(*pAPos, rStart, rEnd, pPtIdx
                    ? DelContentType::AllMask
                    : DelContentType::AllMask|DelContentType::CheckNoCntnt))))
        {
            // Only move the Anchor??
            if( rPtNdIdx == pAPos->nNode )
            if ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA)
                && rPtNdIdx == pAPos->nNode )
            {
                SwFormatAnchor aAnch( pFormat->GetAnchor() );
                SwPosition aPos( rMkNdIdx );
diff --git a/sw/source/core/doc/docfmt.cxx b/sw/source/core/doc/docfmt.cxx
index 5bd3642..10bbb0d 100644
--- a/sw/source/core/doc/docfmt.cxx
+++ b/sw/source/core/doc/docfmt.cxx
@@ -1410,7 +1410,7 @@ void SwDoc::CopyPageDescHeaderFooterImpl( bool bCpyHeader,
                aTmpIdx = *pSttNd->EndOfSectionNode();
                rSrcNds.Copy_( aRg, aTmpIdx );
                aTmpIdx = *pSttNd;
                rSrcFormat.GetDoc()->GetDocumentContentOperationsManager().CopyFlyInFlyImpl( aRg, 0, aTmpIdx );
                rSrcFormat.GetDoc()->GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRg, nullptr, aTmpIdx);
                pNewFormat->SetFormatAttr( SwFormatContent( pSttNd ));
            }
            else
diff --git a/sw/source/core/doc/docglbl.cxx b/sw/source/core/doc/docglbl.cxx
index d4522f5..771512c 100644
--- a/sw/source/core/doc/docglbl.cxx
+++ b/sw/source/core/doc/docglbl.cxx
@@ -315,7 +315,7 @@ bool SwDoc::SplitDoc( sal_uInt16 eDocType, const OUString& rPath, bool bOutline,
                        pDoc->GetNodes().Delete( aIdx );

                    // All Flys in the section
                    GetDocumentContentOperationsManager().CopyFlyInFlyImpl( aRg, 0, aIdx );
                    GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRg, nullptr, aIdx);

                    // And what's with all the Bookmarks?
                    // ?????
diff --git a/sw/source/core/doc/docredln.cxx b/sw/source/core/doc/docredln.cxx
index 6b420cf..d18a3054 100644
--- a/sw/source/core/doc/docredln.cxx
+++ b/sw/source/core/doc/docredln.cxx
@@ -1419,7 +1419,7 @@ void SwRangeRedline::CopyToSection()
        {
            SwNodeIndex aInsPos( *pSttNd->EndOfSectionNode() );
            SwNodeRange aRg( pStt->nNode, 0, pEnd->nNode, 1 );
            pDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( aRg, 0, aInsPos );
            pDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aInsPos);
        }
    }
    m_pContentSect = new SwNodeIndex( *pSttNd );
diff --git a/sw/source/core/doc/tblcpy.cxx b/sw/source/core/doc/tblcpy.cxx
index 43e06fc..4975217 100644
--- a/sw/source/core/doc/tblcpy.cxx
+++ b/sw/source/core/doc/tblcpy.cxx
@@ -518,7 +518,7 @@ static void lcl_CpyBox( const SwTable& rCpyTable, const SwTableBox* pCpyBox,

    SwNodeIndex aSavePos( aInsIdx, -1 );
    if (pRg)
        pCpyDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( *pRg, 0, aInsIdx, nullptr, false );
        pCpyDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(*pRg, aInsIdx, nullptr, false);
    else
        pDoc->GetNodes().MakeTextNode( aInsIdx, pDoc->GetDfltTextFormatColl() );
    ++aSavePos;
diff --git a/sw/source/core/doc/tblrwcl.cxx b/sw/source/core/doc/tblrwcl.cxx
index 1084e4c..b7b2821 100644
--- a/sw/source/core/doc/tblrwcl.cxx
+++ b/sw/source/core/doc/tblrwcl.cxx
@@ -1896,7 +1896,7 @@ static void lcl_CopyBoxToDoc(FndBox_ const& rFndBox, CpyPara *const pCpyPara)
                        *rFndBox.GetBox()->GetSttNd()->EndOfSectionNode() );
                SwNodeIndex aInsIdx( *pBox->GetSttNd(), 1 );

                pFromDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( aCpyRg, 0, aInsIdx, nullptr, false );
                pFromDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aCpyRg, aInsIdx, nullptr, false);
                // Delete the initial TextNode
                pCpyPara->pDoc->GetNodes().Delete( aInsIdx );
            }
diff --git a/sw/source/core/docnode/section.cxx b/sw/source/core/docnode/section.cxx
index 8ea5318..be3d20e 100644
--- a/sw/source/core/docnode/section.cxx
+++ b/sw/source/core/docnode/section.cxx
@@ -1352,7 +1352,7 @@ static void lcl_UpdateLinksInSect( SwBaseLink& rUpdLnk, SwSectionNode& rSectNd )

                    SwTableNumFormatMerge aTNFM( *pSrcDoc, *pDoc );

                    pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( *pCpyRg, 0, rInsPos, nullptr, bCreateFrame );
                    pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(*pCpyRg, rInsPos, nullptr, bCreateFrame);
                    ++aSave;

                    if( !bCreateFrame )
diff --git a/sw/source/core/inc/DocumentContentOperationsManager.hxx b/sw/source/core/inc/DocumentContentOperationsManager.hxx
index f02bf2f..d6f8f8e 100644
--- a/sw/source/core/inc/DocumentContentOperationsManager.hxx
+++ b/sw/source/core/inc/DocumentContentOperationsManager.hxx
@@ -102,14 +102,13 @@ public:
    //Non-Interface methods

    void CopyWithFlyInFly( const SwNodeRange& rRg,
                            const sal_Int32 nEndContentIndex,
                            const SwNodeIndex& rInsPos,
                            const std::pair<const SwPaM&, const SwPosition&> * pCopiedPaM = nullptr,
                            bool bMakeNewFrames = true,
                            bool bDelRedlines = true,
                            bool bCopyFlyAtFly = false ) const;
    void CopyFlyInFlyImpl(  const SwNodeRange& rRg,
                            const sal_Int32 nEndContentIndex,
                            SwPaM const*const pCopiedPaM,
                            const SwNodeIndex& rStartIdx,
                            const bool bCopyFlyAtFly = false ) const;

diff --git a/sw/source/core/inc/frmtool.hxx b/sw/source/core/inc/frmtool.hxx
index 4340e40..a127579 100644
--- a/sw/source/core/inc/frmtool.hxx
+++ b/sw/source/core/inc/frmtool.hxx
@@ -60,11 +60,13 @@ void AppendObjs( const SwFrameFormats *pTable, sal_uLong nIndex,
void AppendObjsOfNode(SwFrameFormats const* pTable, sal_uLong nIndex,
        SwFrame * pFrame, SwPageFrame * pPage, SwDoc * pDoc,
        std::vector<sw::Extent>::const_iterator const* pIter,
        std::vector<sw::Extent>::const_iterator const* pEnd);
        std::vector<sw::Extent>::const_iterator const* pEnd,
        SwTextNode const* pFirstNode, SwTextNode const* pLastNode);

void RemoveHiddenObjsOfNode(SwTextNode const& rNode,
        std::vector<sw::Extent>::const_iterator const* pIter,
        std::vector<sw::Extent>::const_iterator const* pEnd);
        std::vector<sw::Extent>::const_iterator const* pEnd,
        SwTextNode const* pFirstNode, SwTextNode const* pLastNode);

bool IsAnchoredObjShown(SwTextFrame const& rFrame, SwFormatAnchor const& rAnchor);

diff --git a/sw/source/core/inc/mvsave.hxx b/sw/source/core/inc/mvsave.hxx
index f070a11..c8ff124 100644
--- a/sw/source/core/inc/mvsave.hxx
+++ b/sw/source/core/inc/mvsave.hxx
@@ -99,24 +99,30 @@ void DelBookmarks(const SwNodeIndex& rStt,
struct SaveFly
{
    sal_uLong const nNdDiff;      /// relative node difference
    sal_Int32 const nContentIndex; ///< index in node
    SwFrameFormat* const pFrameFormat;      /// the fly's frame format
    bool const bInsertPosition;   /// if true, anchor _at_ insert position
    bool const isAtInsertNode;   ///< if true, anchor _at_ insert node index

    SaveFly( sal_uLong nNodeDiff, SwFrameFormat* pFormat, bool bInsert )
        : nNdDiff( nNodeDiff ), pFrameFormat( pFormat ), bInsertPosition( bInsert )
    SaveFly( sal_uLong nNodeDiff, sal_Int32 const nCntntIdx, SwFrameFormat* pFormat, bool bInsert )
        : nNdDiff(nNodeDiff)
        , nContentIndex(nCntntIdx)
        , pFrameFormat(pFormat)
        , isAtInsertNode(bInsert)
    { }
};

typedef std::deque< SaveFly > SaveFlyArr;

void RestFlyInRange( SaveFlyArr& rArr, const SwNodeIndex& rSttIdx,
void RestFlyInRange( SaveFlyArr& rArr, const SwPosition& rSttIdx,
                      const SwNodeIndex* pInsPos );
void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr );
void SaveFlyInRange( const SwPaM& rPam, const SwNodeIndex& rInsPos,
void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos,
                       SaveFlyArr& rArr, bool bMoveAllFlys );

void DelFlyInRange( const SwNodeIndex& rMkNdIdx,
                    const SwNodeIndex& rPtNdIdx );
                    const SwNodeIndex& rPtNdIdx,
                    SwIndex const* pMkIdx = nullptr,
                    SwIndex const* pPtIdx = nullptr);

class SwDataChanged
{
diff --git a/sw/source/core/layout/frmtool.cxx b/sw/source/core/layout/frmtool.cxx
index 5564576..416037b 100644
--- a/sw/source/core/layout/frmtool.cxx
+++ b/sw/source/core/layout/frmtool.cxx
@@ -65,6 +65,7 @@
#include <objectformatter.hxx>
#include <calbck.hxx>
#include <ndtxt.hxx>
#include <undobj.hxx>
#include <DocumentSettingManager.hxx>
#include <IDocumentDrawModelAccess.hxx>
#include <IDocumentTimerAccess.hxx>
@@ -1041,7 +1042,8 @@ void AppendObj(SwFrame *const pFrame, SwPageFrame *const pPage, SwFrameFormat *c
static bool IsShown(sal_uLong const nIndex,
    const SwFormatAnchor & rAnch,
    std::vector<sw::Extent>::const_iterator const*const pIter,
    std::vector<sw::Extent>::const_iterator const*const pEnd)
    std::vector<sw::Extent>::const_iterator const*const pEnd,
    SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode)
{
    assert(!pIter || *pIter == *pEnd || (*pIter)->pNode->GetIndex() == nIndex);
    SwPosition const& rAnchor(*rAnch.GetContentAnchor());
@@ -1049,30 +1051,83 @@ static bool IsShown(sal_uLong const nIndex,
    {
        return false;
    }
    if (pIter && rAnch.GetAnchorId() != RndStdIds::FLY_AT_PARA
        // sw_redlinehide: we want to hide AT_CHAR, but currently can't
        // because Delete and Accept Redline don't delete them!
              && rAnch.GetAnchorId() != RndStdIds::FLY_AT_CHAR)
    if (pIter && rAnch.GetAnchorId() != RndStdIds::FLY_AT_PARA)
    {
        // note: frames are not sorted by anchor position.
        assert(pEnd);
        assert(pFirstNode);
        assert(pLastNode);
        assert(rAnch.GetAnchorId() != RndStdIds::FLY_AT_FLY);
        for (auto iter = *pIter; iter != *pEnd; ++iter)
        {
            assert(iter->nStart != iter->nEnd); // TODO possible?
            assert(iter->pNode->GetIndex() == nIndex);
            if (rAnchor.nContent.GetIndex() < iter->nStart)
            {
                return false;
            }
            // for AS_CHAR obviously must be <
            // for AT_CHAR it is questionable whether < or <= should be used
            // and there is the additional corner case of Len() to consider
            // prefer < for now for symmetry (and inverted usage with
            // "hidden") and handle special case explicitly
            if (rAnchor.nContent.GetIndex() < iter->nEnd
                || iter->nEnd == iter->pNode->Len())
            if (rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR)
            {
                return true;
                // if there is an extent then obviously the node was not
                // deleted fully...
                // show if start <= pos <= end
                // *or* if first-node/0  *and* not StartOfSection
                // *or* if last-node/Len *and* not EndOfSection

                // first determine the extent to compare to, then
                // construct start/end positions for the deletion *before* the
                // extent and compare once.
                // the interesting corner cases are on the edge of the extent!
                // no need to check for > the last extent because those
                // are never visible.
                if (rAnchor.nContent.GetIndex() <= iter->nEnd)
                {
                    if (iter->nStart == 0)
                    {
                        return true;
                    }
                    else
                    {
                        SwPosition const start(
                            const_cast<SwTextNode&>(
                                iter == *pIter
                                    ? *pFirstNode // simplification
                                    : *iter->pNode),
                            iter == *pIter // first extent?
                                ? iter->pNode == pFirstNode
                                    ? 0 // at start of 1st node
                                    : pFirstNode->Len() // previous node; simplification but should get right result
                                : (iter-1)->nEnd); // previous extent
                        SwPosition const end(*iter->pNode, iter->nStart);
                        return !IsDestroyFrameAnchoredAtChar(rAnchor, start, end);
                    }
                }
                else if (iter == *pEnd - 1) // special case: after last extent
                {
                    if (iter->nEnd == iter->pNode->Len())
                    {
                        return true; // special case: end of node
                    }
                    else
                    {
                        SwPosition const start(*iter->pNode, iter->nEnd);
                        SwPosition const end(
                            const_cast<SwTextNode&>(*pLastNode), // simplification
                            iter->pNode == pLastNode
                                ? iter->pNode->Len()
                                : 0);
                        return !IsDestroyFrameAnchoredAtChar(rAnchor, start, end);
                    }
                }
            }
            else
            {
                assert(rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR);
                // for AS_CHAR obviously must be <
                if (rAnchor.nContent.GetIndex() < iter->nEnd)
                {
                    return true;
                }
            }
        }
        return false;
@@ -1085,7 +1140,8 @@ static bool IsShown(sal_uLong const nIndex,

void RemoveHiddenObjsOfNode(SwTextNode const& rNode,
    std::vector<sw::Extent>::const_iterator const*const pIter,
    std::vector<sw::Extent>::const_iterator const*const pEnd)
    std::vector<sw::Extent>::const_iterator const*const pEnd,
    SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode)
{
    std::vector<SwFrameFormat*> const*const pFlys(rNode.GetAnchoredFlys());
    if (!pFlys)
@@ -1100,7 +1156,7 @@ void RemoveHiddenObjsOfNode(SwTextNode const& rNode,
                && RES_DRAWFRMFMT == pFrameFormat->Which()))
        {
            assert(rAnchor.GetContentAnchor()->nNode.GetIndex() == rNode.GetIndex());
            if (!IsShown(rNode.GetIndex(), rAnchor, pIter, pEnd))
            if (!IsShown(rNode.GetIndex(), rAnchor, pIter, pEnd, pFirstNode, pLastNode))
            {
                pFrameFormat->DelFrames();
            }
@@ -1111,7 +1167,8 @@ void RemoveHiddenObjsOfNode(SwTextNode const& rNode,
void AppendObjsOfNode(SwFrameFormats const*const pTable, sal_uLong const nIndex,
    SwFrame *const pFrame, SwPageFrame *const pPage, SwDoc *const pDoc,
    std::vector<sw::Extent>::const_iterator const*const pIter,
    std::vector<sw::Extent>::const_iterator const*const pEnd)
    std::vector<sw::Extent>::const_iterator const*const pEnd,
    SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode)
{
#if OSL_DEBUG_LEVEL > 0
    std::vector<SwFrameFormat*> checkFormats;
@@ -1120,7 +1177,7 @@ void AppendObjsOfNode(SwFrameFormats const*const pTable, sal_uLong const nIndex,
        SwFrameFormat *pFormat = (*pTable)[i];
        const SwFormatAnchor &rAnch = pFormat->GetAnchor();
        if ( rAnch.GetContentAnchor() &&
            IsShown(nIndex, rAnch, pIter, pEnd))
            IsShown(nIndex, rAnch, pIter, pEnd, pFirstNode, pLastNode))
        {
            checkFormats.push_back( pFormat );
        }
@@ -1136,7 +1193,7 @@ void AppendObjsOfNode(SwFrameFormats const*const pTable, sal_uLong const nIndex,
        SwFrameFormat *const pFormat = (*pFlys)[it];
        const SwFormatAnchor &rAnch = pFormat->GetAnchor();
        if ( rAnch.GetContentAnchor() &&
            IsShown(nIndex, rAnch, pIter, pEnd))
            IsShown(nIndex, rAnch, pIter, pEnd, pFirstNode, pLastNode))
        {
#if OSL_DEBUG_LEVEL > 0
            std::vector<SwFrameFormat*>::iterator checkPos = std::find( checkFormats.begin(), checkFormats.end(), pFormat );
@@ -1170,7 +1227,8 @@ void AppendObjs(const SwFrameFormats *const pTable, sal_uLong const nIndex,
                if (iter == pMerged->extents.end()
                    || iter->pNode != pNode)
                {
                    AppendObjsOfNode(pTable, pNode->GetIndex(), pFrame, pPage, pDoc, &iterFirst, &iter);
                    AppendObjsOfNode(pTable, pNode->GetIndex(), pFrame, pPage, pDoc,
                        &iterFirst, &iter, pMerged->pFirstNode, pMerged->pLastNode);
                    sal_uLong const until = iter == pMerged->extents.end()
                        ? pMerged->pLastNode->GetIndex() + 1
                        : iter->pNode->GetIndex();
@@ -1181,7 +1239,7 @@ void AppendObjs(const SwFrameFormats *const pTable, sal_uLong const nIndex,
                        SwNode const*const pTmp(pNode->GetNodes()[i]);
                        if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst)
                        {
                            AppendObjsOfNode(pTable, pTmp->GetIndex(), pFrame, pPage, pDoc, &iter, &iter);
                            AppendObjsOfNode(pTable, pTmp->GetIndex(), pFrame, pPage, pDoc, &iter, &iter, pMerged->pFirstNode, pMerged->pLastNode);
                        }
                    }
                    if (iter == pMerged->extents.end())
@@ -1195,12 +1253,12 @@ void AppendObjs(const SwFrameFormats *const pTable, sal_uLong const nIndex,
        }
        else
        {
            return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr);
            return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr, nullptr, nullptr);
        }
    }
    else
    {
        return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr);
        return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr, nullptr, nullptr);
    }
}

@@ -1225,7 +1283,8 @@ bool IsAnchoredObjShown(SwTextFrame const& rFrame, SwFormatAnchor const& rAnchor
                assert(pNode->GetRedlineMergeFlag() != SwNode::Merge::Hidden);
                if (pNode == &pAnchor->nNode.GetNode())
                {
                    ret = IsShown(pNode->GetIndex(), rAnchor, &iterFirst, &iter);
                    ret = IsShown(pNode->GetIndex(), rAnchor, &iterFirst, &iter,
                            pMergedPara->pFirstNode, pMergedPara->pLastNode);
                    break;
                }
                if (iter == pMergedPara->extents.end())
diff --git a/sw/source/core/layout/wsfrm.cxx b/sw/source/core/layout/wsfrm.cxx
index cfd0e4f..95af7d9 100644
--- a/sw/source/core/layout/wsfrm.cxx
+++ b/sw/source/core/layout/wsfrm.cxx
@@ -4180,18 +4180,19 @@ static void AddRemoveFlysForNode(
        SwPageFrame *const pPage,
        SwTextNode const*const pNode,
        std::vector<sw::Extent>::const_iterator & rIterFirst,
        std::vector<sw::Extent>::const_iterator const& rIterEnd)
        std::vector<sw::Extent>::const_iterator const& rIterEnd,
        SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode)
{
    if (pNode == &rTextNode)
    {   // remove existing hidden at-char anchored flys
        RemoveHiddenObjsOfNode(rTextNode, &rIterFirst, &rIterEnd);
        RemoveHiddenObjsOfNode(rTextNode, &rIterFirst, &rIterEnd, pFirstNode, pLastNode);
    }
    else if (rTextNode.GetIndex() < pNode->GetIndex())
    {
        // pNode's frame has been deleted by CheckParaRedlineMerge()
        AppendObjsOfNode(&rTable,
            pNode->GetIndex(), &rFrame, pPage, rTextNode.GetDoc(),
            &rIterFirst, &rIterEnd);
            &rIterFirst, &rIterEnd, pFirstNode, pLastNode);
        if (pSkipped)
        {
            // if a fly has been added by AppendObjsOfNode, it must be skipped; if not, then it doesn't matter if it's skipped or not because it has no frames and because of that it would be skipped anyway
@@ -4239,7 +4240,8 @@ void AddRemoveFlysAnchoredToFrameStartingAtNode(
                || iter->pNode != pNode)
            {
                AddRemoveFlysForNode(rFrame, rTextNode, pSkipped, rTable, pPage,
                        pNode, iterFirst, iter);
                        pNode, iterFirst, iter,
                        pMerged->pFirstNode, pMerged->pLastNode);
                sal_uLong const until = iter == pMerged->extents.end()
                    ? pMerged->pLastNode->GetIndex() + 1
                    : iter->pNode->GetIndex();
@@ -4251,7 +4253,8 @@ void AddRemoveFlysAnchoredToFrameStartingAtNode(
                    if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst)
                    {
                        AddRemoveFlysForNode(rFrame, rTextNode, pSkipped,
                            rTable, pPage, pTmp->GetTextNode(), iter, iter);
                            rTable, pPage, pTmp->GetTextNode(), iter, iter,
                            pMerged->pFirstNode, pMerged->pLastNode);
                    }
                }
                if (iter == pMerged->extents.end())
diff --git a/sw/source/core/txtnode/atrftn.cxx b/sw/source/core/txtnode/atrftn.cxx
index 81afed7..18bf9c5 100644
--- a/sw/source/core/txtnode/atrftn.cxx
+++ b/sw/source/core/txtnode/atrftn.cxx
@@ -398,7 +398,7 @@ void SwTextFootnote::CopyFootnote(
        SwNodeIndex aEnd( *aStart.GetNode().EndOfSectionNode() );
        sal_uLong  nDestLen = aEnd.GetIndex() - aStart.GetIndex() - 1;

        m_pTextNode->GetDoc()->GetDocumentContentOperationsManager().CopyWithFlyInFly( aRg, 0, aEnd );
        m_pTextNode->GetDoc()->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aEnd);

        // in case the destination section was not empty, delete the old nodes
        // before:   Src: SxxxE,  Dst: SnE
diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx
index ac36268..612ca35 100644
--- a/sw/source/core/undo/undobj.cxx
+++ b/sw/source/core/undo/undobj.cxx
@@ -1020,7 +1020,9 @@ void SwUndoSaveContent::DelContentIndex( const SwPosition& rMark,
                            pHistory->Add( *static_cast<SwFlyFrameFormat *>(pFormat), nChainInsPos );
                            n = n >= rSpzArr.size() ? rSpzArr.size() : n+1;
                        }
                        else if( !( DelContentType::CheckNoCntnt & nDelContentType ) )
                        else if (!((DelContentType::CheckNoCntnt |
                                    DelContentType::ExcludeAtCharFlyAtStartEnd)
                                    & nDelContentType))
                        {
                            if( *pStt <= *pAPos && *pAPos < *pEnd )
                            {
@@ -1521,19 +1523,55 @@ OUString ShortenString(const OUString & rStr, sal_Int32 nLength, const OUString 
           + rStr.copy(rStr.getLength() - nBackLen);
}

static bool IsAtEndOfSection(SwPosition const& rAnchorPos)
{
    SwNodeIndex node(*rAnchorPos.nNode.GetNode().EndOfSectionNode());
    SwContentNode *const pNode(SwNodes::GoPrevious(&node));
    assert(pNode);
    assert(rAnchorPos.nNode <= node); // last valid anchor pos is last content
    return node == rAnchorPos.nNode && rAnchorPos.nContent == pNode->Len();
}

static bool IsAtStartOfSection(SwPosition const& rAnchorPos)
{
    SwNodes const& rNodes(rAnchorPos.nNode.GetNodes());
    SwNodeIndex node(*rAnchorPos.nNode.GetNode().StartOfSectionNode());
    SwContentNode *const pNode(rNodes.GoNext(&node));
    assert(pNode);
    (void) pNode;
    assert(node <= rAnchorPos.nNode);
    return node == rAnchorPos.nNode && rAnchorPos.nContent == 0;
}

bool IsDestroyFrameAnchoredAtChar(SwPosition const & rAnchorPos,
        SwPosition const & rStart, SwPosition const & rEnd,
        DelContentType const nDelContentType)
{
    // Here we identified the objects to destroy:
    // - anchored between start and end of the selection
    // - anchored in start of the selection with "CheckNoContent"
    // - anchored in start of sel. and the selection start at pos 0
    return  (rAnchorPos.nNode < rEnd.nNode)
         && (   (DelContentType::CheckNoCntnt & nDelContentType)
            ||  (rStart.nNode < rAnchorPos.nNode)
            ||  !rStart.nContent.GetIndex()
            );
    // CheckNoCntnt means DelFullPara which is obvious to handle
    if (DelContentType::CheckNoCntnt & nDelContentType)
    {   // exclude selection end node because it won't be deleted
        return (rAnchorPos.nNode < rEnd.nNode)
            && (rStart.nNode <= rAnchorPos.nNode);
    }

    if (rAnchorPos.GetDoc()->IsInReading())
    {   // FIXME hack for writerfilter RemoveLastParagraph(); can't test file format more specific?
        return (rStart < rAnchorPos) && (rAnchorPos < rEnd);
    }

    // in general, exclude the start and end position
    return ((rStart < rAnchorPos)
            || (rStart == rAnchorPos
                && !(nDelContentType & DelContentType::ExcludeAtCharFlyAtStartEnd)
                // special case: fully deleted node
                && ((rStart.nNode != rEnd.nNode && rStart.nContent == 0)
                    || IsAtStartOfSection(rAnchorPos))))
        && ((rAnchorPos < rEnd)
            || (rAnchorPos == rEnd
                && !(nDelContentType & DelContentType::ExcludeAtCharFlyAtStartEnd)
                // special case: fully deleted node
                && ((rEnd.nNode != rStart.nNode && rEnd.nContent == rEnd.nNode.GetNode().GetTextNode()->Len())
                    || IsAtEndOfSection(rAnchorPos))));
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/undo/untblk.cxx b/sw/source/core/undo/untblk.cxx
index 4746562..8b206e0 100644
--- a/sw/source/core/undo/untblk.cxx
+++ b/sw/source/core/undo/untblk.cxx
@@ -32,6 +32,34 @@
#include <rolbck.hxx>
#include <redline.hxx>

namespace sw {

std::unique_ptr<std::vector<SwFrameFormat*>>
GetFlysAnchoredAt(SwDoc & rDoc, sal_uLong const nSttNode)
{
    std::unique_ptr<std::vector<SwFrameFormat*>> pFrameFormats;
    const size_t nArrLen = rDoc.GetSpzFrameFormats()->size();
    for (size_t n = 0; n < nArrLen; ++n)
    {
        SwFrameFormat *const pFormat = (*rDoc.GetSpzFrameFormats())[n];
        SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
        SwPosition const*const pAPos = pAnchor->GetContentAnchor();
        if (pAPos
             && nSttNode == pAPos->nNode.GetIndex()
             && ((pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA)
                 || (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)))
        {
            if (!pFrameFormats)
                pFrameFormats.reset( new std::vector<SwFrameFormat*> );
            pFrameFormats->push_back( pFormat );
        }
    }
    return pFrameFormats;
}

} // namespace sw

//note: parameter is SwPam just so we can init SwUndRng, the End is ignored!
SwUndoInserts::SwUndoInserts( SwUndoId nUndoId, const SwPaM& rPam )
    : SwUndo( nUndoId, rPam.GetDoc() ), SwUndRng( rPam ),
    pTextFormatColl( nullptr ), pLastNdColl(nullptr),
@@ -53,22 +81,7 @@ SwUndoInserts::SwUndoInserts( SwUndoId nUndoId, const SwPaM& rPam )
        // These flys will be saved in pFrameFormats array (only flys which exist BEFORE insertion!)
        // Then in SwUndoInserts::SetInsertRange the flys saved in pFrameFormats will NOT create Undos.
        // m_FlyUndos will only be filled with newly inserted flys.

        const size_t nArrLen = pDoc->GetSpzFrameFormats()->size();
        for( size_t n = 0; n < nArrLen; ++n )
        {
            SwFrameFormat* pFormat = (*pDoc->GetSpzFrameFormats())[n];
            SwFormatAnchor const*const  pAnchor = &pFormat->GetAnchor();
            const SwPosition* pAPos = pAnchor->GetContentAnchor();
            if (pAPos &&
                (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) &&
                 nSttNode == pAPos->nNode.GetIndex() )
            {
                if( !pFrameFormats )
                    pFrameFormats.reset( new std::vector<SwFrameFormat*> );
                pFrameFormats->push_back( pFormat );
            }
        }
        pFrameFormats = sw::GetFlysAnchoredAt(*pDoc, nSttNode);
    }
    // consider Redline
    if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() )
@@ -124,10 +137,7 @@ void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys,
        {
            SwFrameFormat* pFormat = (*pDoc->GetSpzFrameFormats())[n];
            SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
            SwPosition const*const pAPos = pAnchor->GetContentAnchor();
            if (pAPos &&
                (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) &&
                (nSttNode == pAPos->nNode.GetIndex() || nEndNode == pAPos->nNode.GetIndex()))
            if (IsCreateUndoForNewFly(*pAnchor, nSttNode, nEndNode))
            {
                std::vector<SwFrameFormat*>::iterator it;
                if( !pFrameFormats ||
@@ -145,6 +155,29 @@ void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys,
    }
}

/** This is not the same as IsDestroyFrameAnchoredAtChar()
    and intentionally so: because the SwUndoInserts::UndoImpl() must remove
    the flys at the start/end position that were inserted but not the ones
    at the start/insert position that were already there;
    handle all at-char flys at start/end node like this, even if they're
    not *on* the start/end position, because it makes it easier to ensure
    that the Undo/Redo run in inverse order.
 */
bool SwUndoInserts::IsCreateUndoForNewFly(SwFormatAnchor const& rAnchor,
    sal_uLong const nStartNode, sal_uLong const nEndNode)
{
    assert(nStartNode <= nEndNode);

    // check all at-char flys at the start/end nodes:
    // ExcludeAtCharFlyAtStartEnd will exclude them!
    SwPosition const*const pAnchorPos = rAnchor.GetContentAnchor();
    return pAnchorPos != nullptr
        && (   rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA
            || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR)
        && (   nStartNode == pAnchorPos->nNode.GetIndex()
            || nEndNode == pAnchorPos->nNode.GetIndex());
}

SwUndoInserts::~SwUndoInserts()
{
    if (m_pUndoNodeIndex) // delete also the section from UndoNodes array
@@ -185,6 +218,8 @@ void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext)
    SwDoc& rDoc = rContext.GetDoc();
    SwPaM& rPam = AddUndoRedoPaM(rContext);

    nNdDiff = 0;

    if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
        rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any);

@@ -204,14 +239,35 @@ void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext)
        }

        RemoveIdxFromRange(rPam, false);
        SetPaM(rPam);

        SetPaM(rPam);
    }

    // ... for consistency with the Insert File code in shellio.cxx, which
    // creates separate SwUndoInsLayFormat for mysterious reasons, do this
    // *before* anything else:
    // after SetPaM but before MoveToUndoNds and DelContentIndex.
    // note: there isn't an order dep wrt. initial Copy action because Undo
    // overwrites the indexes but there is wrt. Redo because that uses the
    // indexes
    if (!m_FlyUndos.empty())
    {
        sal_uLong nTmp = rPam.GetPoint()->nNode.GetIndex();
        for (size_t n = m_FlyUndos.size(); 0 < n; --n)
        {
            m_FlyUndos[ n-1 ]->UndoImpl(rContext);
        }
        nNdDiff += nTmp - rPam.GetPoint()->nNode.GetIndex();
    }

    if (nSttNode != nEndNode || nSttContent != nEndContent)
    {
        // are there Footnotes or ContentFlyFrames in text?
        nSetPos = pHistory->Count();
        nNdDiff = rPam.GetMark()->nNode.GetIndex();
        DelContentIndex(*rPam.GetMark(), *rPam.GetPoint());
        nNdDiff -= rPam.GetMark()->nNode.GetIndex();

        sal_uLong nTmp = rPam.GetMark()->nNode.GetIndex();
        DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(),
            DelContentType::AllMask|DelContentType::ExcludeAtCharFlyAtStartEnd);
        nNdDiff += nTmp - rPam.GetMark()->nNode.GetIndex();
        if( *rPam.GetPoint() != *rPam.GetMark() )
        {
            m_pUndoNodeIndex.reset(
@@ -223,16 +279,6 @@ void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext)
        }
    }

    if (!m_FlyUndos.empty())
    {
        sal_uLong nTmp = rPam.GetPoint()->nNode.GetIndex();
        for (size_t n = m_FlyUndos.size(); 0 < n; --n)
        {
            m_FlyUndos[ n-1 ]->UndoImpl(rContext);
        }
        nNdDiff += nTmp - rPam.GetPoint()->nNode.GetIndex();
    }

    SwNodeIndex& rIdx = rPam.GetPoint()->nNode;
    SwTextNode* pTextNode = rIdx.GetNode().GetTextNode();
    if( pTextNode )
@@ -296,6 +342,9 @@ void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext)
    // retrieve start position for rollback
    if( ( nSttNode != nEndNode || nSttContent != nEndContent ) && m_pUndoNodeIndex)
    {
        auto const pFlysAtInsPos(sw::GetFlysAnchoredAt(*pDoc,
            rPam.GetPoint()->nNode.GetIndex()));

        const bool bMvBkwrd = MovePtBackward(rPam);

        // re-insert content again (first detach m_pUndoNodeIndex!)
@@ -305,6 +354,22 @@ void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext)
        if( bSttWasTextNd )
            MovePtForward(rPam, bMvBkwrd);
        rPam.Exchange();

        // at-char anchors post SplitNode are on index 0 of 2nd node and will
        // remain there - move them back to the start (end would also work?)
        if (pFlysAtInsPos)
        {
            for (SwFrameFormat * pFly : *pFlysAtInsPos)
            {
                SwFormatAnchor const*const pAnchor = &pFly->GetAnchor();
                if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
                {
                    SwFormatAnchor anchor(*pAnchor);
                    anchor.SetAnchor( rPam.GetMark() );
                    pFly->SetFormatAttr(anchor);
                }
            }
        }
    }

    if (pDoc->GetTextFormatColls()->IsAlive(pTextFormatColl))
@@ -323,6 +388,10 @@ void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext)
            pTextNd->ChgFormatColl( pLastNdColl );
    }

    // tdf#108124 the SwHistoryChangeFlyAnchor/SwHistoryFlyCnt must run before
    // m_FlyUndos as they were created by DelContentIndex()
    pHistory->Rollback( pDoc, nSetPos );

    // tdf#108124 (10/25/2017)
    // During UNDO we call SwUndoInsLayFormat::UndoImpl in reverse order,
    //  firstly for m_FlyUndos[ m_FlyUndos.size()-1 ], etc.
@@ -336,8 +405,6 @@ void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext)
        m_FlyUndos[n]->RedoImpl(rContext);
    }

    pHistory->Rollback( pDoc, nSetPos );

    if( pRedlData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
    {
        RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags();
diff --git a/sw/source/filter/basflt/shellio.cxx b/sw/source/filter/basflt/shellio.cxx
index 6fc17d4..66c9d1d 100644
--- a/sw/source/filter/basflt/shellio.cxx
+++ b/sw/source/filter/basflt/shellio.cxx
@@ -240,27 +240,11 @@ ErrCode SwReader::Read( const Reader& rOptions )
                // ok, here IsAlive is a misnomer...
                if (!aFlyFrameArr.IsAlive(pFrameFormat))
                {
                    SwPosition const*const pFrameAnchor(
                            rAnchor.GetContentAnchor());
                    if  (   (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId())
                        ||  (   pFrameAnchor
                            &&  (   (   (RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId())
                                    &&  (   (pUndoPam->GetPoint()->nNode ==
                                             pFrameAnchor->nNode)
                                        ||  (pUndoPam->GetMark()->nNode ==
                                             pFrameAnchor->nNode)
                                        )
                                    )
                                // #i97570# also check frames anchored AT char
                                ||  (   (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())
                                    &&  !IsDestroyFrameAnchoredAtChar(
                                              *pFrameAnchor,
                                              *pUndoPam->GetPoint(),
                                              *pUndoPam->GetMark())
                                    )
                                )
                            )
                        )
                        // TODO: why is this not handled via SetInsertRange?
                        ||  SwUndoInserts::IsCreateUndoForNewFly(rAnchor,
                                pUndoPam->GetPoint()->nNode.GetIndex(),
                                pUndoPam->GetMark()->nNode.GetIndex()))
                    {
                        if( bChkHeaderFooter &&
                            (RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) &&