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 @@
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 @@
~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 @@
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 @@
( 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> 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 @@
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::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 @@
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 SwNodeRange& rRange )
const SwNodeRange& rRange, bool const bExpandNodes)
{
SwPaM aPam( rRange.aStart, rRange.aEnd );
@@ -1231,8 +1231,11 @@
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, 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 @@
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 @@
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: */