tdf#38703 i#42807 tdf#123313 sw: add Undo of ToX Update

* SwTOXBaseSection::Update(): refactor this to *first* collect the info
  from the document (creating CrossRefHeadingBookmark as a side effect)
  and *then* do the node deletion/insertion of the ToX itself

* add new SwUndoUpdateIndex class for the update itself; it does 3 of
  the 4 required steps, the last one is done by a SwUndoDelSection that
  is already created when the original title section is deleted

* SwUndoInsSection::RedoImpl() for a ToX should not Update the ToX
  because that's the job of SwUndoUpdateIndex

* SwUndoSaveSection::RestoreSection()/MoveFromUndoNds() need to create
  frames for the moved nodes; not sure why other users of
  RestoreSection() don't do this currently so add a flag

* SwUndoSaveContent::MoveToUndoNds() should *always* delete frames;
  seriously, why would leaving frames alive ever be a good idea?

* SwUndoSaveSection::SaveSection() should not delete the surrounding
  section node of the ToX, so add a flag to prevent it

This fixes tdf#123313, the SwUndoInsBookmark are now appended at the
right time.

This should fix the crash in i#42807 properly.

In order to fix these bugs, implementing the feature requested in
tdf#38703 was necessary.

Change-Id: Ia976a118eb81ca37b0c48678825a1f5cd4031789
Reviewed-on: https://gerrit.libreoffice.org/71323
Tested-by: Jenkins
Reviewed-by: Michael Stahl <Michael.Stahl@cib.de>
diff --git a/sw/inc/undobj.hxx b/sw/inc/undobj.hxx
index 8610ab3..38cab61 100644
--- a/sw/inc/undobj.hxx
+++ b/sw/inc/undobj.hxx
@@ -163,7 +163,8 @@ protected:
                        sal_uLong* pEndNdIdx = nullptr );
    static void MoveFromUndoNds( SwDoc& rDoc, sal_uLong nNodeIdx,
                          SwPosition& rInsPos,
                          const sal_uLong* pEndNdIdx = nullptr );
                          const sal_uLong* pEndNdIdx = nullptr,
                          bool bForceCreateFrames = false);

    // These two methods move the SPoint back/forth from PaM. With it
    // a range can be spanned for Undo/Redo. (In this case the SPoint
@@ -199,9 +200,9 @@ public:
    ~SwUndoSaveSection();

    void SaveSection( const SwNodeIndex& rSttIdx );
    void SaveSection( const SwNodeRange& rRange );
    void SaveSection(const SwNodeRange& rRange, bool bExpandNodes = true);
    void RestoreSection( SwDoc* pDoc, SwNodeIndex* pIdx, sal_uInt16 nSectType );
    void RestoreSection( SwDoc* pDoc, const SwNodeIndex& rInsPos );
    void RestoreSection(SwDoc* pDoc, const SwNodeIndex& rInsPos, bool bForceCreateFrames = false);

    const SwHistory* GetHistory() const { return pHistory.get(); }
          SwHistory* GetHistory()       { return pHistory.get(); }
diff --git a/sw/source/core/doc/doctxm.cxx b/sw/source/core/doc/doctxm.cxx
index d1dd7e5..13ca079 100644
--- a/sw/source/core/doc/doctxm.cxx
+++ b/sw/source/core/doc/doctxm.cxx
@@ -51,6 +51,7 @@
#include <txtfrm.hxx>
#include <rootfrm.hxx>
#include <UndoAttribute.hxx>
#include <UndoSection.hxx>
#include <swundo.hxx>
#include <mdiexp.hxx>
#include <docary.hxx>
@@ -870,60 +871,6 @@ void SwTOXBaseSection::Update(const SfxItemSet* pAttr,
    SwNode2LayoutSaveUpperFrames aN2L(*pSectNd);
    const_cast<SwSectionNode*>(pSectNd)->DelFrames();

    // remove old content an insert one empty textnode (to hold the layout!)
    SwTextNode* pFirstEmptyNd;
    {
        pDoc->getIDocumentRedlineAccess().DeleteRedline( *pSectNd, true, USHRT_MAX );

        SwNodeIndex aSttIdx( *pSectNd, +1 );
        SwNodeIndex aEndIdx( *pSectNd->EndOfSectionNode() );
        pFirstEmptyNd = pDoc->GetNodes().MakeTextNode( aEndIdx,
                        pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ) );

        {
            // Task 70995 - save and restore PageDesc and Break Attributes
            SwNodeIndex aNxtIdx( aSttIdx );
            const SwContentNode* pCNd = aNxtIdx.GetNode().GetContentNode();
            if( !pCNd )
                pCNd = pDoc->GetNodes().GoNext( &aNxtIdx );
            if( pCNd->HasSwAttrSet() )
            {
                SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange );
                aBrkSet.Put( *pCNd->GetpSwAttrSet() );
                if( aBrkSet.Count() )
                    pFirstEmptyNd->SetAttr( aBrkSet );
            }
        }
        --aEndIdx;
        SwPosition aPos( aEndIdx, SwIndex( pFirstEmptyNd, 0 ));
        SwDoc::CorrAbs( aSttIdx, aEndIdx, aPos, true );

        // delete flys in whole range including start node which requires
        // giving the node before start node as Mark parameter, hence -1.
        // (flys must be deleted because the anchor nodes are removed)
        DelFlyInRange( SwNodeIndex(aSttIdx, -1), aEndIdx );

        pDoc->GetNodes().Delete( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() );
    }

    // insert title of TOX
    if ( !GetTitle().isEmpty() )
    {
        // then insert the headline section
        SwNodeIndex aIdx( *pSectNd, +1 );

        SwTextNode* pHeadNd = pDoc->GetNodes().MakeTextNode( aIdx,
                                GetTextFormatColl( FORM_TITLE ) );
        pHeadNd->InsertText( GetTitle(), SwIndex( pHeadNd ) );

        SwSectionData headerData( TOX_HEADER_SECTION, GetTOXName()+"_Head" );

        SwNodeIndex aStt( *pHeadNd ); --aIdx;
        SwSectionFormat* pSectFormat = pDoc->MakeSectionFormat();
        pDoc->GetNodes().InsertTextSection(
                aStt, *pSectFormat, headerData, nullptr, &aIdx, true, false);
    }

    // This would be a good time to update the Numbering
    pDoc->UpdateNumRule();

@@ -964,6 +911,81 @@ void SwTOXBaseSection::Update(const SfxItemSet* pAttr,
        ( GetOptions() & SwTOIOptions::AlphaDelimiter ) )
        InsertAlphaDelimitter( aIntl );

    // remove old content an insert one empty textnode (to hold the layout!)
    SwTextNode* pFirstEmptyNd;

    SwUndoUpdateIndex * pUndo(nullptr);
    {
        pDoc->getIDocumentRedlineAccess().DeleteRedline( *pSectNd, true, USHRT_MAX );

        SwNodeIndex aSttIdx( *pSectNd, +1 );
        SwNodeIndex aEndIdx( *pSectNd->EndOfSectionNode() );
        pFirstEmptyNd = pDoc->GetNodes().MakeTextNode( aEndIdx,
                        pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ) );

        {
            // Task 70995 - save and restore PageDesc and Break Attributes
            SwNodeIndex aNxtIdx( aSttIdx );
            const SwContentNode* pCNd = aNxtIdx.GetNode().GetContentNode();
            if( !pCNd )
                pCNd = pDoc->GetNodes().GoNext( &aNxtIdx );
            assert(pCNd != pFirstEmptyNd);
            assert(pCNd->GetIndex() < pFirstEmptyNd->GetIndex());
            if( pCNd->HasSwAttrSet() )
            {
                SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange );
                aBrkSet.Put( *pCNd->GetpSwAttrSet() );
                if( aBrkSet.Count() )
                    pFirstEmptyNd->SetAttr( aBrkSet );
            }
        }

        if (pDoc->GetIDocumentUndoRedo().DoesUndo())
        {
            // note: this will first append a SwUndoDelSection from the ctor...
            pUndo = new SwUndoUpdateIndex(*this);
            // tdf#123313 insert Undo *after* all CrossRefBookmark Undos have
            // been inserted by the Update*() functions
            pDoc->GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndoUpdateIndex>(pUndo));
        }
        else
        {
            --aEndIdx;
            SwPosition aPos( aEndIdx, SwIndex( pFirstEmptyNd, 0 ));
            SwDoc::CorrAbs( aSttIdx, aEndIdx, aPos, true );

            // delete flys in whole range including start node which requires
            // giving the node before start node as Mark parameter, hence -1.
            // (flys must be deleted because the anchor nodes are removed)
            DelFlyInRange( SwNodeIndex(aSttIdx, -1), aEndIdx );

            pDoc->GetNodes().Delete( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() );
        }
    }

    // insert title of TOX
    if ( !GetTitle().isEmpty() )
    {
        // then insert the headline section
        SwNodeIndex aIdx( *pSectNd, +1 );

        SwTextNode* pHeadNd = pDoc->GetNodes().MakeTextNode( aIdx,
                                GetTextFormatColl( FORM_TITLE ) );
        pHeadNd->InsertText( GetTitle(), SwIndex( pHeadNd ) );

        SwSectionData headerData( TOX_HEADER_SECTION, GetTOXName()+"_Head" );

        SwNodeIndex aStt( *pHeadNd ); --aIdx;
        SwSectionFormat* pSectFormat = pDoc->MakeSectionFormat();
        pDoc->GetNodes().InsertTextSection(
                aStt, *pSectFormat, headerData, nullptr, &aIdx, true, false);

        if (pUndo)
        {
            pUndo->TitleSectionInserted(*pSectFormat);
        }
    }

    // Sort the List of all TOC Marks and TOC Sections
    std::vector<SwTextFormatColl*> aCollArr( GetTOXForm().GetFormMax(), nullptr );
    SwNodeIndex aInsPos( *pFirstEmptyNd, 1 );
diff --git a/sw/source/core/inc/UndoSection.hxx b/sw/source/core/inc/UndoSection.hxx
index ef8847d..9852597 100644
--- a/sw/source/core/inc/UndoSection.hxx
+++ b/sw/source/core/inc/UndoSection.hxx
@@ -70,6 +70,28 @@ std::unique_ptr<SwUndo> MakeUndoDelSection(SwSectionFormat const&);

std::unique_ptr<SwUndo> MakeUndoUpdateSection(SwSectionFormat const&, bool const);


class SwTOXBaseSection;
class SwUndoDelSection;

class SwUndoUpdateIndex : public SwUndo
{
private:
    std::unique_ptr<SwUndoDelSection> m_pTitleSectionUpdated;
    std::unique_ptr<SwUndoSaveSection> const m_pSaveSectionOriginal;
    std::unique_ptr<SwUndoSaveSection> const m_pSaveSectionUpdated;
    sal_uLong const m_nStartIndex;

public:
    SwUndoUpdateIndex(SwTOXBaseSection &);
    virtual ~SwUndoUpdateIndex() override;

    void TitleSectionInserted(SwSectionFormat & rSectionFormat);

    virtual void UndoImpl(::sw::UndoRedoContext &) override;
    virtual void RedoImpl(::sw::UndoRedoContext &) override;
};

#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOSECTION_HXX

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx
index 0d17568..a294c7e 100644
--- a/sw/source/core/undo/undobj.cxx
+++ b/sw/source/core/undo/undobj.cxx
@@ -726,7 +726,7 @@ void SwUndoSaveContent::MoveToUndoNds( SwPaM& rPaM, SwNodeIndex* pNodeIdx,
    if( pCpyNd || pEndNdIdx )
    {
        SwNodeRange aRg( pStt->nNode, 0, pEnd->nNode, 1 );
        rDoc.GetNodes().MoveNodes( aRg, rNds, aPos.nNode, false );
        rDoc.GetNodes().MoveNodes( aRg, rNds, aPos.nNode, true );
        aPos.nContent = 0;
        --aPos.nNode;
    }
@@ -745,7 +745,7 @@ void SwUndoSaveContent::MoveToUndoNds( SwPaM& rPaM, SwNodeIndex* pNodeIdx,

void SwUndoSaveContent::MoveFromUndoNds( SwDoc& rDoc, sal_uLong nNodeIdx,
                            SwPosition& rInsPos,
                            const sal_uLong* pEndNdIdx )
            const sal_uLong* pEndNdIdx, bool const bForceCreateFrames)
{
    // here comes the recovery
    SwNodes & rNds = rDoc.GetUndoManager().GetUndoNodes();
@@ -793,7 +793,7 @@ void SwUndoSaveContent::MoveFromUndoNds( SwDoc& rDoc, sal_uLong nNodeIdx,
        SwNodeRange aRg( rNds, nNodeIdx, rNds, (pEndNdIdx
                        ? ((*pEndNdIdx) + 1)
                        : rNds.GetEndOfExtras().GetIndex() ) );
        rNds.MoveNodes( aRg, rDoc.GetNodes(), rInsPos.nNode, nullptr == pEndNdIdx );
        rNds.MoveNodes(aRg, rDoc.GetNodes(), rInsPos.nNode, nullptr == pEndNdIdx || bForceCreateFrames);

    }
    else {
@@ -1205,7 +1205,7 @@ void SwUndoSaveSection::SaveSection( const SwNodeIndex& rSttIdx )
}

void SwUndoSaveSection::SaveSection(
    const SwNodeRange& rRange )
    const SwNodeRange& rRange, bool const bExpandNodes)
{
    SwPaM aPam( rRange.aStart, rRange.aEnd );

@@ -1231,8 +1231,11 @@ void SwUndoSaveSection::SaveSection(

    nStartPos = rRange.aStart.GetIndex();

    --aPam.GetPoint()->nNode;
    ++aPam.GetMark()->nNode;
    if (bExpandNodes)
    {
        --aPam.GetPoint()->nNode;
        ++aPam.GetMark()->nNode;
    }

    SwContentNode* pCNd = aPam.GetContentNode( false );
    if( pCNd )
@@ -1266,13 +1269,14 @@ void SwUndoSaveSection::RestoreSection( SwDoc* pDoc, SwNodeIndex* pIdx,
    }
}

void SwUndoSaveSection::RestoreSection( SwDoc* pDoc, const SwNodeIndex& rInsPos )
void SwUndoSaveSection::RestoreSection(
        SwDoc *const pDoc, const SwNodeIndex& rInsPos, bool bForceCreateFrames)
{
    if( ULONG_MAX != nStartPos )        // was there any content?
    {
        SwPosition aInsPos( rInsPos );
        sal_uLong nEnd = m_pMovedStart->GetIndex() + nMvLen - 1;
        MoveFromUndoNds(*pDoc, m_pMovedStart->GetIndex(), aInsPos, &nEnd);
        MoveFromUndoNds(*pDoc, m_pMovedStart->GetIndex(), aInsPos, &nEnd, bForceCreateFrames);

        // destroy indices again, content was deleted from UndoNodes array
        m_pMovedStart.reset();
diff --git a/sw/source/core/undo/unsect.cxx b/sw/source/core/undo/unsect.cxx
index f449639..ef84c67 100644
--- a/sw/source/core/undo/unsect.cxx
+++ b/sw/source/core/undo/unsect.cxx
@@ -29,6 +29,8 @@
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <poolfmt.hxx>
#include <docary.hxx>
#include <swundo.hxx>
#include <pam.hxx>
@@ -204,7 +206,8 @@ void SwUndoInsSection::RedoImpl(::sw::UndoRedoContext & rContext)
            pLayout = pLayoutToReset;
        }
        pUpdateTOX = rDoc.InsertTableOf( *rPam.GetPoint(),
            *m_pTOXBase->first, m_pAttrSet.get(), true, pLayout);
            // don't expand: will be done by SwUndoUpdateIndex::RedoImpl()
            *m_pTOXBase->first, m_pAttrSet.get(), false, pLayout);
    }
    else
    {
@@ -507,4 +510,88 @@ void SwUndoUpdateSection::RedoImpl(::sw::UndoRedoContext & rContext)
    UndoImpl(rContext);
}


SwUndoUpdateIndex::SwUndoUpdateIndex(SwTOXBaseSection & rTOX)
    : SwUndo(SwUndoId::INSSECTION, rTOX.GetFormat()->GetDoc())
    , m_pSaveSectionOriginal(new SwUndoSaveSection)
    , m_pSaveSectionUpdated(new SwUndoSaveSection)
    , m_nStartIndex(rTOX.GetFormat()->GetSectionNode()->GetIndex() + 1)
{
    SwDoc & rDoc(*rTOX.GetFormat()->GetDoc());
    assert(rDoc.GetNodes()[m_nStartIndex-1]->IsSectionNode());
    assert(rDoc.GetNodes()[rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex()-1]->IsTextNode()); // -1 for extra empty node
    // note: title is optional
    assert(rDoc.GetNodes()[m_nStartIndex]->IsTextNode()
        || rDoc.GetNodes()[m_nStartIndex]->IsSectionNode());
    SwNodeIndex const first(rDoc.GetNodes(), m_nStartIndex);
    if (first.GetNode().IsSectionNode())
    {
        SwSectionFormat & rSectionFormat(*first.GetNode().GetSectionNode()->GetSection().GetFormat());
        // note: DelSectionFormat will create & append SwUndoDelSection!
        rDoc.DelSectionFormat(& rSectionFormat); // remove inner section nodes
    }
    assert(first.GetNode().IsTextNode()); // invariant: ToX section is *never* empty
    SwNodeIndex const last(rDoc.GetNodes(), rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex() - 2); // skip empty node
    assert(last.GetNode().IsTextNode());
    m_pSaveSectionOriginal->SaveSection(SwNodeRange(first, last), false);
}

SwUndoUpdateIndex::~SwUndoUpdateIndex() = default;

void SwUndoUpdateIndex::TitleSectionInserted(SwSectionFormat & rFormat)
{
    SwNodeIndex const tmp(rFormat.GetDoc()->GetNodes(), m_nStartIndex); // title inserted before empty node
    assert(tmp.GetNode().IsSectionNode());
    assert(tmp.GetNode().GetSectionNode()->GetSection().GetFormat() == &rFormat);
    m_pTitleSectionUpdated.reset(static_cast<SwUndoDelSection*>(MakeUndoDelSection(rFormat).release()));
}

void SwUndoUpdateIndex::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc(rContext.GetDoc());
    if (m_pTitleSectionUpdated)
    {
        m_pTitleSectionUpdated->RedoImpl(rContext);
    }
    SwNodeIndex const first(rDoc.GetNodes(), m_nStartIndex);
    assert(first.GetNode().IsTextNode()); // invariant: ToX section is *never* empty
    SwNodeIndex const last(rDoc.GetNodes(), rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex() - 1);
    assert(last.GetNode().IsTextNode());
    // dummy node so that SaveSection doesn't remove ToX section...
    SwTextNode *const pDeletionPrevention = rDoc.GetNodes().MakeTextNode(
        SwNodeIndex(*rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode()),
        rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT));
    m_pSaveSectionUpdated->SaveSection(SwNodeRange(first, last), false);
    m_pSaveSectionOriginal->RestoreSection(&rDoc, first, true);
    // delete before restoring nested undo, so its node indexes match
    SwNodeIndex const del(*pDeletionPrevention);
    SwDoc::CorrAbs(del, del, SwPosition(SwNodeIndex(*rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode())), true);
    rDoc.GetNodes().Delete(del);
    // original title section will be restored by next Undo, see ctor!
}

void SwUndoUpdateIndex::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc(rContext.GetDoc());
    // original title section was deleted by previous Undo, see ctor!
    SwNodeIndex const first(rDoc.GetNodes(), m_nStartIndex);
    assert(first.GetNode().IsTextNode()); // invariant: ToX section is *never* empty
    SwNodeIndex const last(rDoc.GetNodes(), rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex() - 1);
    assert(last.GetNode().IsTextNode());
    // dummy node so that SaveSection doesn't remove ToX section...
    SwTextNode *const pDeletionPrevention = rDoc.GetNodes().MakeTextNode(
        SwNodeIndex(*rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode()),
        rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT));
    m_pSaveSectionOriginal->SaveSection(SwNodeRange(first, last), false);
    m_pSaveSectionUpdated->RestoreSection(&rDoc, first, true);
    // delete before restoring nested undo, so its node indexes match
    SwNodeIndex const del(*pDeletionPrevention);
    SwDoc::CorrAbs(del, del, SwPosition(SwNodeIndex(*rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode())), true);
    rDoc.GetNodes().Delete(del);
    if (m_pTitleSectionUpdated)
    {
        m_pTitleSectionUpdated->UndoImpl(rContext);
    }
}

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