sw/inc/undobj.hxx                                       |   15 +
 sw/qa/uitest/writer_tests6/tdf107975.py                 |   39 ++
 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 -
 22 files changed, 549 insertions(+), 209 deletions(-)

New commits:
commit 3345feb67f2c49a1b76639965b56968e1c5f03ee
Author:     Michael Stahl <michael.st...@cib.de>
AuthorDate: Thu Jul 11 18:37:28 2019 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Mon Jul 22 14:14:06 2019 +0200

    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.st...@cib.de>
    (cherry picked from commit 28b77c89dfcafae82cf2a6d85731b643ff9290e5)
    Reviewed-on: https://gerrit.libreoffice.org/76086
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/sw/inc/undobj.hxx b/sw/inc/undobj.hxx
index 38cab61b0b58..3128ffc788d5 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 202e7426bbaa..333c0f77b820 100644
--- a/sw/qa/uitest/writer_tests6/tdf107975.py
+++ b/sw/qa/uitest/writer_tests6/tdf107975.py
@@ -27,6 +27,34 @@ 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")
+        #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(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")
@@ -34,9 +62,20 @@ 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(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 a49fcd7f3a63..ca85201f0fdf 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
-        {
-            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 )
@@ -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
         {
@@ -3939,7 +3968,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,
@@ -4379,8 +4409,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 ) ||
@@ -4400,11 +4430,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();
@@ -4426,7 +4464,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 );
@@ -4552,6 +4593,8 @@ bool DocumentContentOperationsManager::CopyImpl( SwPaM& 
rPam, SwPosition& rPos,
                         pEnd->nContent -= nCpyLen;
                 }
 
+                aRg.aStart++;
+
                 if( bOneNode )
                 {
                     if (bCopyCollFormat)
@@ -4560,10 +4603,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 )
@@ -4661,9 +4706,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() );
@@ -4672,7 +4717,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)
@@ -4686,16 +4733,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 68e08bad1b80..11ed4010f7ff 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 3054d17940f3..0df9cdf55068 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 ca14991405c5..c7a59f30e955 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 baf32805d516..1388bdc1b53a 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 5bd36426a600..10bbb0d097c8 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 10521aa6ae54..4bc71627c5c0 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 36734eaa0c61..847bca754c94 100644
--- a/sw/source/core/doc/docredln.cxx
+++ b/sw/source/core/doc/docredln.cxx
@@ -1486,7 +1486,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 43e06fce73e2..4975217f16e1 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 1084e4c77475..b7b28213e2ca 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 8ea531804045..be3d20e48869 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 f02bf2fb46e7..d6f8f8eb2a09 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 4340e40babc3..a127579c59ad 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 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 484e5d2a8e93..624b2701e8d1 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)
+            {
+                // 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;
@@ -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 cfd0e4ff5024..95af7d95dda2 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 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 ac36268b40d7..612ca3500bc3 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 47465620b882..8b206e064873 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);
+    }
+
+    // ... 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 6fc17d4f9858..66c9d1d0ea7d 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()) 
&&
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to