[Libreoffice-commits] core.git: Branch 'distro/collabora/cp-6.2' - sw/inc sw/source

Michael Stahl (via logerrit) logerrit at kemper.freedesktop.org
Thu Feb 20 17:51:39 UTC 2020


 sw/inc/undobj.hxx                                       |   15 +
 sw/source/core/doc/DocumentContentOperationsManager.cxx |  217 ++++++++++------
 sw/source/core/doc/DocumentLayoutManager.cxx            |    2 
 sw/source/core/doc/doccomp.cxx                          |    2 
 sw/source/core/doc/docdesc.cxx                          |    4 
 sw/source/core/doc/docedt.cxx                           |   99 +++++--
 sw/source/core/doc/docfmt.cxx                           |    2 
 sw/source/core/doc/docglbl.cxx                          |    2 
 sw/source/core/doc/docredln.cxx                         |    2 
 sw/source/core/doc/tblcpy.cxx                           |    2 
 sw/source/core/doc/tblrwcl.cxx                          |    2 
 sw/source/core/docnode/section.cxx                      |    2 
 sw/source/core/inc/DocumentContentOperationsManager.hxx |    3 
 sw/source/core/inc/frmtool.hxx                          |    6 
 sw/source/core/inc/mvsave.hxx                           |   18 -
 sw/source/core/layout/frmtool.cxx                       |  105 ++++++-
 sw/source/core/layout/wsfrm.cxx                         |   13 
 sw/source/core/txtnode/atrftn.cxx                       |    2 
 sw/source/core/undo/undobj.cxx                          |   58 +++-
 sw/source/core/undo/untblk.cxx                          |  139 +++++++---
 sw/source/filter/basflt/shellio.cxx                     |   24 -
 21 files changed, 510 insertions(+), 209 deletions(-)

New commits:
commit 5e79482c28db7d8e479c603eb3e9eb276ad4ad7d
Author:     Michael Stahl <Michael.Stahl at cib.de>
AuthorDate: Thu Jul 11 18:37:28 2019 +0200
Commit:     Miklos Vajna <vmiklos at collabora.com>
CommitDate: Thu Feb 20 18:51:02 2020 +0100

    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
    
    (cherry picked from commit 28b77c89dfcafae82cf2a6d85731b643ff9290e5)
    
    Conflicts:
            sw/qa/uitest/writer_tests6/tdf107975.py
            sw/source/core/doc/docedt.cxx
            sw/source/core/inc/frmtool.hxx
            sw/source/core/layout/frmtool.cxx
    
    Change-Id: I4fec2a3c15ca0e64e5c4e99acfb04f59bb2bcf64
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/89093
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Miklos Vajna <vmiklos at collabora.com>

diff --git a/sw/inc/undobj.hxx b/sw/inc/undobj.hxx
index 8610ab3d38a6..2d8b02ce6e12 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?
@@ -226,6 +228,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
 {
@@ -251,6 +260,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/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx
index dcb298ac226b..5b07f7b48fb8 100644
--- a/sw/source/core/doc/DocumentContentOperationsManager.cxx
+++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx
@@ -2002,6 +2002,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 );
@@ -2043,7 +2046,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;
@@ -2284,7 +2287,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() )
@@ -2390,7 +2396,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)
@@ -3302,31 +3311,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
@@ -3350,7 +3364,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 );
@@ -3382,18 +3401,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).
@@ -3410,9 +3427,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() )
@@ -3423,59 +3440,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
-        {
-            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 (RndStdIds::FLY_AT_CHAR != pAnchor->GetAnchorId())
+        {
+            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 )
@@ -3514,7 +3538,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() )
@@ -3575,7 +3600,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
         {
@@ -3945,7 +3974,8 @@ bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam)
         m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, USHRT_MAX );
 
     // 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,
@@ -4382,8 +4412,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 ) ||
@@ -4403,11 +4433,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();
@@ -4429,7 +4467,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 );
@@ -4555,6 +4596,8 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& rPam, SwPosition& rPos,
                         pEnd->nContent -= nCpyLen;
                 }
 
+                aRg.aStart++;
+
                 if( bOneNode )
                 {
                     if (bCopyCollFormat)
@@ -4563,10 +4606,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 )
@@ -4664,9 +4709,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() );
@@ -4675,7 +4720,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)
@@ -4689,16 +4736,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 867bb5d62555..ff462831d87e 100644
--- a/sw/source/core/doc/DocumentLayoutManager.cxx
+++ b/sw/source/core/doc/DocumentLayoutManager.cxx
@@ -427,7 +427,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 4bc700531907..c29a688ecb38 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 ac172c00a94c..ffed651e45e1 100644
--- a/sw/source/core/doc/docdesc.cxx
+++ b/sw/source/core/doc/docdesc.cxx
@@ -297,7 +297,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 ) );
@@ -369,7 +369,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 2e4d132a838e..7b35c948c31f 100644
--- a/sw/source/core/doc/docedt.cxx
+++ b/sw/source/core/doc/docedt.cxx
@@ -44,6 +44,7 @@
 #include <SwGrammarMarkUp.hxx>
 #include <docedt.hxx>
 #include <frmfmt.hxx>
+#include <undobj.hxx>
 
 #include <vector>
 #include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
@@ -53,27 +54,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
@@ -82,7 +103,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 )
@@ -99,6 +120,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();
@@ -112,7 +136,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();
@@ -141,12 +165,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
@@ -159,12 +185,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();
@@ -182,8 +219,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();
@@ -194,14 +241,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 8783623f6ab6..40c78fc0c73a 100644
--- a/sw/source/core/doc/docfmt.cxx
+++ b/sw/source/core/doc/docfmt.cxx
@@ -1407,7 +1407,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 5b60d9056aaf..9a6d760e4af3 100644
--- a/sw/source/core/doc/docglbl.cxx
+++ b/sw/source/core/doc/docglbl.cxx
@@ -313,7 +313,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 6c8ef90bbe42..a09014d719ed 100644
--- a/sw/source/core/doc/docredln.cxx
+++ b/sw/source/core/doc/docredln.cxx
@@ -1494,7 +1494,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 672324930747..b8ac700706b7 100644
--- a/sw/source/core/doc/tblcpy.cxx
+++ b/sw/source/core/doc/tblcpy.cxx
@@ -519,7 +519,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 5b17b345666f..6cf6b2210e54 100644
--- a/sw/source/core/doc/tblrwcl.cxx
+++ b/sw/source/core/doc/tblrwcl.cxx
@@ -1908,7 +1908,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 a8919d10073e..28c730d858a0 100644
--- a/sw/source/core/docnode/section.cxx
+++ b/sw/source/core/docnode/section.cxx
@@ -1349,7 +1349,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 3ed457bd3f33..edbd3c4e8154 100644
--- a/sw/source/core/inc/DocumentContentOperationsManager.hxx
+++ b/sw/source/core/inc/DocumentContentOperationsManager.hxx
@@ -101,14 +101,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 eea90deeb271..5ffcd20083f7 100644
--- a/sw/source/core/inc/frmtool.hxx
+++ b/sw/source/core/inc/frmtool.hxx
@@ -61,11 +61,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 * 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 * 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 f070a11a5bd6..c8ff124af161 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 c532c291788c..7693c3892fdd 100644
--- a/sw/source/core/layout/frmtool.cxx
+++ b/sw/source/core/layout/frmtool.cxx
@@ -63,6 +63,7 @@
 #include <sortedobjs.hxx>
 #include <objectformatter.hxx>
 #include <calbck.hxx>
+#include <undobj.hxx>
 #include <DocumentSettingManager.hxx>
 #include <IDocumentTimerAccess.hxx>
 #include <IDocumentRedlineAccess.hxx>
@@ -1038,7 +1039,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 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());
@@ -1046,30 +1048,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)
+            {
+                // 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
             {
-                return true;
+                assert(rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR);
+                // for AS_CHAR obviously must be <
+                if (rAnchor.nContent.GetIndex() < iter->nEnd)
+                {
+                    return true;
+                }
             }
         }
         return false;
@@ -1082,7 +1137,8 @@ static bool IsShown(sal_uLong const nIndex,
 
 void RemoveHiddenObjsOfNode(SwTextNode const& rNode,
     std::vector<sw::Extent>::const_iterator *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)
@@ -1097,7 +1153,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();
             }
@@ -1108,7 +1164,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 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;
@@ -1117,7 +1174,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 );
         }
@@ -1133,7 +1190,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 );
@@ -1167,7 +1224,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();
@@ -1178,7 +1236,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())
@@ -1192,12 +1250,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);
     }
 }
 
@@ -1222,7 +1280,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 b4bd2ba55d6f..42edab15459d 100644
--- a/sw/source/core/layout/wsfrm.cxx
+++ b/sw/source/core/layout/wsfrm.cxx
@@ -4179,18 +4179,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
@@ -4238,7 +4239,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();
@@ -4250,7 +4252,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 81afed72030b..18bf9c5d05f4 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 844a29be2073..452091c548f8 100644
--- a/sw/source/core/undo/undobj.cxx
+++ b/sw/source/core/undo/undobj.cxx
@@ -1021,7 +1021,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 )
                             {
@@ -1518,19 +1520,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 5a347704333e..01d2970be4d1 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, USHRT_MAX);
 
@@ -204,14 +239,35 @@ void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext)
         }
 
         RemoveIdxFromRange(rPam, false);
+
         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 dddfdd4c4c8a..051c9e174e0a 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()) &&


More information about the Libreoffice-commits mailing list