tdf#134253 sw: fix SwUndoInserts::UndoImpl() if inserting before section

Somehow the clipboard is pasted before the SwSectionNode, which is the
first node in the body text.

The OSL_ENSURE( !bStartIsTextNode, "Oops, undo may be instable now." )
is triggered; the Undo must remove 2 SwTextNode in this case.

Because of the extra SwTextNode the indexes are off by 1 and this starts
to assert since commit 24fd14b387dca458a1b6e9415e936d26562ddb1e when
setting a fieldmark on a non-text node.

Change-Id: Ic52b0f4c7665994cadfe8f074bc4607c55352aa1
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/97887
Tested-by: Jenkins
Reviewed-by: Michael Stahl <michael.stahl@cib.de>
(cherry picked from commit dc7e7b94a7211c576454267c09eb108e761e4487)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/98078
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
diff --git a/sw/inc/undobj.hxx b/sw/inc/undobj.hxx
index c76a1df..71d9c51 100644
--- a/sw/inc/undobj.hxx
+++ b/sw/inc/undobj.hxx
@@ -252,7 +252,8 @@ class SwUndoInserts : public SwUndo, public SwUndRng, private SwUndoSaveContent
    std::unique_ptr<std::vector<SwFrameFormat*>> m_pFrameFormats;
    std::vector< std::shared_ptr<SwUndoInsLayFormat> > m_FlyUndos;
    std::unique_ptr<SwRedlineData> m_pRedlineData;
    bool m_bStartWasTextNode;
    int m_nDeleteTextNodes;

protected:
    sal_uLong m_nNodeDiff;
    /// start of Content in UndoNodes for Redo
@@ -269,7 +270,7 @@ public:

    // Set destination range after reading.
    void SetInsertRange( const SwPaM&, bool bScanFlys = true,
                         bool bSttWasTextNd = true );
                         int nDeleteTextNodes = 1);

    static bool IsCreateUndoForNewFly(SwFormatAnchor const& rAnchor,
        sal_uLong const nStartNode, sal_uLong const nEndNode);
diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx
index f62d773..33b7e5f 100644
--- a/sw/source/core/doc/DocumentContentOperationsManager.cxx
+++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx
@@ -4732,7 +4732,7 @@ bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPo
                          ( !bOneNode && !rPos.nContent.GetIndex() ) );
    bool bCopyBookmarks = true;
    bool bCopyPageSource  = false;
    bool bStartIsTextNode = nullptr != pSttTextNd;
    int nDeleteTextNodes = 0;

    // #i104585# copy outline num rule to clipboard (for ASCII filter)
    if (pDoc->IsClipBoard() && m_rDoc.GetOutlineNumRule())
@@ -4769,6 +4769,7 @@ bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPo
    do {
        if( pSttTextNd )
        {
            ++nDeleteTextNodes; // must be joined in Undo
            // Don't copy the beginning completely?
            if( !bCopyCollFormat || bColumnSel || pStt->nContent.GetIndex() )
            {
@@ -4878,7 +4879,7 @@ bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPo
            else if( rPos.nContent.GetIndex() )
            {   // Insertion in the middle of a text node, it has to be split
                // (and joined from undo)
                bStartIsTextNode = true;
                ++nDeleteTextNodes;

                const sal_Int32 nContentEnd = pEnd->nContent.GetIndex();
                {
@@ -4931,11 +4932,9 @@ bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPo

                // if we have to insert an extra text node
                // at the destination, this node will be our new destination
                // (text) node, and thus we set bStartisTextNode to true. This
                // will ensure that this node will be deleted during Undo
                // using JoinNext.
                OSL_ENSURE( !bStartIsTextNode, "Oops, undo may be instable now." );
                bStartIsTextNode = true;
                // (text) node, and thus we increment nDeleteTextNodes. This
                // will ensure that this node will be deleted during Undo.
                ++nDeleteTextNodes; // must be deleted
            }

            const bool bEmptyDestNd = pDestTextNd->GetText().isEmpty();
@@ -5109,7 +5108,7 @@ bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPo
    // If Undo is enabled, store the inserted area
    if (pDoc->GetIDocumentUndoRedo().DoesUndo())
    {
        pUndo->SetInsertRange( *pCopyPam, true, bStartIsTextNode );
        pUndo->SetInsertRange(*pCopyPam, true, nDeleteTextNodes);
    }

    if( pCpyRange )
diff --git a/sw/source/core/undo/untblk.cxx b/sw/source/core/undo/untblk.cxx
index 134db46..28f41f8 100644
--- a/sw/source/core/undo/untblk.cxx
+++ b/sw/source/core/undo/untblk.cxx
@@ -62,9 +62,13 @@ GetFlysAnchoredAt(SwDoc & rDoc, sal_uLong const nSttNode)

//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 ),
    m_pTextFormatColl( nullptr ), m_pLastNodeColl(nullptr),
    m_bStartWasTextNode( true ), m_nNodeDiff( 0 ), m_nSetPos( 0 )
    : SwUndo( nUndoId, rPam.GetDoc() )
    , SwUndRng( rPam )
    , m_pTextFormatColl(nullptr)
    , m_pLastNodeColl(nullptr)
    , m_nDeleteTextNodes(1)
    , m_nNodeDiff(0)
    , m_nSetPos(0)
{
    m_pHistory.reset( new SwHistory );
    SwDoc* pDoc = rPam.GetDoc();
@@ -73,6 +77,7 @@ SwUndoInserts::SwUndoInserts( SwUndoId nUndoId, const SwPaM& rPam )
    if( pTextNd )
    {
        m_pTextFormatColl = pTextNd->GetTextColl();
        assert(m_pTextFormatColl);
        m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nSttNode,
                            0, pTextNd->GetText().getLength(), false );
        if( pTextNd->HasSwAttrSet() )
@@ -105,7 +110,7 @@ SwUndoInserts::SwUndoInserts( SwUndoId nUndoId, const SwPaM& rPam )
//  Flys, anchored to any paragraph, but not first and last, are handled by DelContentIndex (see SwUndoInserts::UndoImpl) and are not stored in m_FlyUndos.

void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys,
                                    bool bSttIsTextNd )
                                    int const nDeleteTextNodes)
{
    const SwPosition* pTmpPos = rPam.End();
    m_nEndNode = pTmpPos->nNode.GetIndex();
@@ -120,11 +125,11 @@ void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys,
        m_nSttNode = pTmpPos->nNode.GetIndex();
        m_nSttContent = pTmpPos->nContent.GetIndex();

        if( !bSttIsTextNd )      // if a table selection is added...
        if (m_nDeleteTextNodes == 0) // if a table selection is added...
        {
            ++m_nSttNode;         // ... then the CopyPam is not fully correct
            m_bStartWasTextNode = false;
        }
        m_nDeleteTextNodes = nDeleteTextNodes;
    }

    // Fill m_FlyUndos with flys anchored to first and last paragraphs
@@ -275,8 +280,10 @@ void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext)
                    new SwNodeIndex(rDoc.GetNodes().GetEndOfContent()));
            MoveToUndoNds(rPam, m_pUndoNodeIndex.get());

            if( !m_bStartWasTextNode )
            if (m_nDeleteTextNodes == 0)
            {
                rPam.Move( fnMoveBackward, GoInContent );
            }
        }
    }

@@ -287,15 +294,19 @@ void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext)
        if( !m_pTextFormatColl ) // if 0 than it's no TextNode -> delete
        {
            SwNodeIndex aDelIdx( rIdx );
            ++rIdx;
            SwContentNode* pCNd = rIdx.GetNode().GetContentNode();
            rPam.GetPoint()->nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 );
            rPam.SetMark();
            assert(0 < m_nDeleteTextNodes && m_nDeleteTextNodes < 3);
            for (int i = 0; i < m_nDeleteTextNodes; ++i)
            {
                rPam.Move(fnMoveForward, GoInNode);
            }
            rPam.DeleteMark();

            RemoveIdxRel(aDelIdx.GetIndex(), *rPam.GetPoint());
            for (int i = 0; i < m_nDeleteTextNodes; ++i)
            {
                RemoveIdxRel(aDelIdx.GetIndex() + i, *rPam.GetPoint());
            }

            rDoc.GetNodes().Delete( aDelIdx );
            rDoc.GetNodes().Delete( aDelIdx, m_nDeleteTextNodes );
        }
        else
        {
@@ -352,8 +363,10 @@ void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext)
        sal_uLong const nMvNd = m_pUndoNodeIndex->GetIndex();
        m_pUndoNodeIndex.reset();
        MoveFromUndoNds(*pDoc, nMvNd, *rPam.GetMark());
        if( m_bStartWasTextNode )
        if (m_nDeleteTextNodes != 0)
        {
            MovePtForward(rPam, bMvBkwrd);
        }
        rPam.Exchange();

        // at-char anchors post SplitNode are on index 0 of 2nd node and will