summaryrefslogtreecommitdiffstats
path: root/sw/source/core/undo/untblk.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/undo/untblk.cxx')
-rw-r--r--sw/source/core/undo/untblk.cxx487
1 files changed, 487 insertions, 0 deletions
diff --git a/sw/source/core/undo/untblk.cxx b/sw/source/core/undo/untblk.cxx
new file mode 100644
index 000000000..2e53ff2d9
--- /dev/null
+++ b/sw/source/core/undo/untblk.cxx
@@ -0,0 +1,487 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <libxml/xmlwriter.h>
+
+#include <fmtanchr.hxx>
+#include <frmfmt.hxx>
+#include <doc.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IShellCursorSupplier.hxx>
+#include <docary.hxx>
+#include <swcrsr.hxx>
+#include <swundo.hxx>
+#include <pam.hxx>
+#include <mvsave.hxx>
+#include <ndtxt.hxx>
+#include <UndoCore.hxx>
+#include <rolbck.hxx>
+#include <redline.hxx>
+#include <frameformats.hxx>
+
+namespace sw {
+
+std::optional<std::vector<SwFrameFormat*>>
+GetFlysAnchoredAt(SwDoc & rDoc, SwNodeOffset const nSttNode)
+{
+ std::optional<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.emplace();
+ 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 )
+ , m_pTextFormatColl(nullptr)
+ , m_pLastNodeColl(nullptr)
+ , m_nDeleteTextNodes(1)
+ , m_nNodeDiff(0)
+ , m_nSetPos(0)
+{
+ m_pHistory.reset( new SwHistory );
+ SwDoc& rDoc = rPam.GetDoc();
+
+ SwTextNode* pTextNd = rPam.GetPoint()->nNode.GetNode().GetTextNode();
+ if( pTextNd )
+ {
+ m_pTextFormatColl = pTextNd->GetTextColl();
+ assert(m_pTextFormatColl);
+ m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nSttNode,
+ 0, pTextNd->GetText().getLength(), false );
+ if( pTextNd->HasSwAttrSet() )
+ m_pHistory->CopyFormatAttr( *pTextNd->GetpSwAttrSet(), m_nSttNode );
+
+ // We may have some flys anchored to paragraph where we inserting.
+ // 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.
+ m_pFrameFormats = sw::GetFlysAnchoredAt(rDoc, m_nSttNode);
+ }
+ // consider Redline
+ if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
+ {
+ m_pRedlineData.reset( new SwRedlineData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) );
+ SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() );
+ }
+}
+
+// This method does two things:
+// 1. Adjusts SwUndoRng members, required for Undo.
+// Members are:
+// SwUndoRng::nSttNode - all nodes starting from this node will be deleted during Undo (in SwUndoInserts::UndoImpl)
+// SwUndoRng::nSttContent - corresponding content index in SwUndoRng::nSttNode
+// SwUndoRng::nEndNode - end node for deletion
+// SwUndoRng::nEndContent - end content index
+// All these members are filled in during construction of SwUndoInserts instance, and can be adjusted using this method
+//
+// 2. Fills in m_FlyUndos array with flys anchored ONLY to first and last paragraphs (first == rPam.Start(), last == rPam.End())
+// 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,
+ SwNodeOffset const nDeleteTextNodes)
+{
+ const SwPosition* pTmpPos = rPam.End();
+ m_nEndNode = pTmpPos->nNode.GetIndex();
+ m_nEndContent = pTmpPos->nContent.GetIndex();
+ if( rPam.HasMark() )
+ {
+ if( pTmpPos == rPam.GetPoint() )
+ pTmpPos = rPam.GetMark();
+ else
+ pTmpPos = rPam.GetPoint();
+
+ m_nSttNode = pTmpPos->nNode.GetIndex();
+ m_nSttContent = pTmpPos->nContent.GetIndex();
+
+ m_nDeleteTextNodes = nDeleteTextNodes;
+ if (m_nDeleteTextNodes == SwNodeOffset(0)) // if a table selection is added...
+ {
+ ++m_nSttNode; // ... then the CopyPam is not fully correct
+ }
+ }
+
+ // Fill m_FlyUndos with flys anchored to first and last paragraphs
+
+ if( !bScanFlys)
+ return;
+
+ // than collect all new Flys
+ SwDoc& rDoc = rPam.GetDoc();
+ const size_t nArrLen = rDoc.GetSpzFrameFormats()->size();
+ for( size_t n = 0; n < nArrLen; ++n )
+ {
+ SwFrameFormat* pFormat = (*rDoc.GetSpzFrameFormats())[n];
+ SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
+ if (IsCreateUndoForNewFly(*pAnchor, m_nSttNode, m_nEndNode))
+ {
+ std::vector<SwFrameFormat*>::iterator it;
+ if( !m_pFrameFormats ||
+ m_pFrameFormats->end() == ( it = std::find( m_pFrameFormats->begin(), m_pFrameFormats->end(), pFormat ) ) )
+ {
+ std::shared_ptr<SwUndoInsLayFormat> const pFlyUndo =
+ std::make_shared<SwUndoInsLayFormat>(pFormat, SwNodeOffset(0), 0);
+ m_FlyUndos.push_back(pFlyUndo);
+ }
+ else
+ m_pFrameFormats->erase( it );
+ }
+ }
+ m_pFrameFormats.reset();
+}
+
+/** 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,
+ SwNodeOffset const nStartNode, SwNodeOffset const nEndNode)
+{
+ assert(nStartNode <= nEndNode);
+
+ // check all at-char flys at the start/end nodes:
+ // ExcludeFlyAtStartEnd 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());
+}
+
+void SwUndoInserts::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoInserts"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s",
+ BAD_CAST(typeid(*this).name()));
+
+ SwUndo::dumpAsXml(pWriter);
+ SwUndoSaveContent::dumpAsXml(pWriter);
+
+ if (m_pFrameFormats)
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pFrameFormats"));
+ for (const auto& pFormat : *m_pFrameFormats)
+ {
+ pFormat->dumpAsXml(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ if (!m_FlyUndos.empty())
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_FlyUndos"));
+ for (const auto& pFly : m_FlyUndos)
+ {
+ pFly->dumpAsXml(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwUndoInserts::~SwUndoInserts()
+{
+ if (m_pUndoNodeIndex) // delete also the section from UndoNodes array
+ {
+ // Insert saves content in IconSection
+ SwNodes& rUNds = m_pUndoNodeIndex->GetNodes();
+ rUNds.Delete(*m_pUndoNodeIndex,
+ rUNds.GetEndOfExtras().GetIndex() - m_pUndoNodeIndex->GetIndex());
+ m_pUndoNodeIndex.reset();
+ }
+ m_pFrameFormats.reset();
+ m_pRedlineData.reset();
+}
+
+// Undo Insert operation
+// It's important to note that Undo stores absolute node indexes. I.e. if during insertion, you insert nodes 31 to 33,
+// during Undo nodes with indices from 31 to 33 will be deleted. Undo doesn't check that nodes 31 to 33 are the same nodes which were inserted.
+// It just deletes them.
+// This may seem as bad programming practice, but Undo actions are strongly ordered. If you change your document in some way, a new Undo action is added.
+// During Undo most recent actions will be executed first. So during execution of particular Undo action indices will be correct.
+// But storing absolute indices leads to crashes if some action in Undo fails to roll back some modifications.
+
+// Has following main steps:
+// 1. m_FlyUndos removes flys anchored to first and last paragraph in Undo range.
+// This array may be empty.
+// 2. DelContentIndex to delete footnotes, flys, bookmarks (see comment for this function)
+// Deleted flys are stored in pHistory array.
+// First and last paragraphs flys are not deleted by DelContentIndex!
+// For flys anchored to last paragraph, DelContentIndex re-anchors them to
+// the last paragraph that will remain after Undo.
+// 3. MoveToUndoNds moves nodes to Undo nodes array and removes them from document.
+// 4. Lastly (starting from if(pTextNode)), text from last paragraph is joined to last remaining paragraph and FormatColl for last paragraph is restored.
+// Format coll for last paragraph is removed during execution of UndoImpl
+
+void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext)
+{
+ SwDoc& rDoc = rContext.GetDoc();
+ SwPaM& rPam = AddUndoRedoPaM(rContext);
+
+ m_nNodeDiff = SwNodeOffset(0);
+
+ if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
+ rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any);
+
+ // if Point and Mark are different text nodes so a JoinNext has to be done
+ bool bJoinNext = m_nSttNode != m_nEndNode &&
+ rPam.GetMark()->nNode.GetNode().GetTextNode() &&
+ rPam.GetPoint()->nNode.GetNode().GetTextNode();
+
+ // Is there any content? (loading from template does not have content)
+ if( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent )
+ {
+ if( m_nSttNode != m_nEndNode )
+ {
+ SwTextNode* pTextNd = rDoc.GetNodes()[ m_nEndNode ]->GetTextNode();
+ if (pTextNd && pTextNd->GetText().getLength() == m_nEndContent)
+ m_pLastNodeColl = pTextNd->GetTextColl();
+ }
+
+ // tdf#128739 correct cursors but do not delete bookmarks yet
+ ::PaMCorrAbs(rPam, *rPam.End());
+
+ 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())
+ {
+ SwNodeOffset nTmp = rPam.GetPoint()->nNode.GetIndex();
+ for (size_t n = m_FlyUndos.size(); 0 < n; --n)
+ {
+ m_FlyUndos[ n-1 ]->UndoImpl(rContext);
+ }
+ m_nNodeDiff += nTmp - rPam.GetPoint()->nNode.GetIndex();
+ }
+
+ if (m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent)
+ {
+ // are there Footnotes or ContentFlyFrames in text?
+ m_nSetPos = m_pHistory->Count();
+ SwNodeOffset nTmp = rPam.GetMark()->nNode.GetIndex();
+ DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(),
+ DelContentType::AllMask|DelContentType::ExcludeFlyAtStartEnd);
+ m_nNodeDiff += nTmp - rPam.GetMark()->nNode.GetIndex();
+ if( *rPam.GetPoint() != *rPam.GetMark() )
+ {
+ m_pUndoNodeIndex.reset(
+ new SwNodeIndex(rDoc.GetNodes().GetEndOfContent()));
+ MoveToUndoNds(rPam, m_pUndoNodeIndex.get());
+
+ if (m_nDeleteTextNodes == SwNodeOffset(0))
+ {
+ rPam.Move( fnMoveBackward, GoInContent );
+ }
+ }
+ }
+
+ SwNodeIndex& rIdx = rPam.GetPoint()->nNode;
+ SwTextNode* pTextNode = rIdx.GetNode().GetTextNode();
+ if( !pTextNode )
+ return;
+
+ if( !m_pTextFormatColl ) // if 0 than it's no TextNode -> delete
+ {
+ SwNodeIndex aDelIdx( rIdx );
+ assert(SwNodeOffset(0) < m_nDeleteTextNodes && m_nDeleteTextNodes < SwNodeOffset(3));
+ for (SwNodeOffset i(0); i < m_nDeleteTextNodes; ++i)
+ {
+ rPam.Move(fnMoveForward, GoInNode);
+ }
+ rPam.DeleteMark();
+
+ for (SwNodeOffset i(0); i < m_nDeleteTextNodes; ++i)
+ {
+ RemoveIdxRel(aDelIdx.GetIndex() + i, *rPam.GetPoint());
+ }
+
+ rDoc.GetNodes().Delete( aDelIdx, m_nDeleteTextNodes );
+ }
+ else
+ {
+ if( bJoinNext && pTextNode->CanJoinNext())
+ {
+ {
+ RemoveIdxRel( rIdx.GetIndex()+1, SwPosition( rIdx,
+ SwIndex( pTextNode, pTextNode->GetText().getLength() )));
+ }
+ pTextNode->JoinNext();
+ }
+ // reset all text attributes in the paragraph!
+ pTextNode->RstTextAttr( SwIndex(pTextNode, 0), pTextNode->Len(), 0, nullptr, true );
+
+ pTextNode->ResetAllAttr();
+
+ if (rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl))
+ m_pTextFormatColl = static_cast<SwTextFormatColl*>(pTextNode->ChgFormatColl( m_pTextFormatColl )) ;
+
+ m_pHistory->SetTmpEnd( m_nSetPos );
+ m_pHistory->TmpRollback(&rDoc, 0, false);
+ }
+}
+
+// See SwUndoInserts::UndoImpl comments
+// All actions here should be done in reverse order to what is done in SwUndoInserts::UndoImpl!
+
+void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext)
+{
+ // position cursor onto REDO section
+ SwCursor& rPam(rContext.GetCursorSupplier().CreateNewShellCursor());
+ SwDoc& rDoc = rPam.GetDoc();
+ rPam.DeleteMark();
+ rPam.GetPoint()->nNode = m_nSttNode - m_nNodeDiff;
+ SwContentNode* pCNd = rPam.GetContentNode();
+ rPam.GetPoint()->nContent.Assign( pCNd, m_nSttContent );
+
+ SwTextFormatColl* pSavTextFormatColl = m_pTextFormatColl;
+ if( m_pTextFormatColl && pCNd && pCNd->IsTextNode() )
+ pSavTextFormatColl = static_cast<SwTextNode*>(pCNd)->GetTextColl();
+
+ m_pHistory->SetTmpEnd( m_nSetPos );
+
+ // retrieve start position for rollback
+ if( ( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent ) && m_pUndoNodeIndex)
+ {
+ auto const pFlysAtInsPos(sw::GetFlysAnchoredAt(rDoc,
+ rPam.GetPoint()->nNode.GetIndex()));
+
+ const bool bMvBkwrd = MovePtBackward(rPam);
+
+ // re-insert content again (first detach m_pUndoNodeIndex!)
+ SwNodeOffset const nMvNd = m_pUndoNodeIndex->GetIndex();
+ m_pUndoNodeIndex.reset();
+ MoveFromUndoNds(rDoc, nMvNd, *rPam.GetMark());
+ if (m_nDeleteTextNodes != SwNodeOffset(0))
+ {
+ 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 (m_pTextFormatColl && rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl))
+ {
+ SwTextNode* pTextNd = rPam.GetMark()->nNode.GetNode().GetTextNode();
+ if( pTextNd )
+ pTextNd->ChgFormatColl( m_pTextFormatColl );
+ }
+ m_pTextFormatColl = pSavTextFormatColl;
+
+ if (m_pLastNodeColl && rDoc.GetTextFormatColls()->IsAlive(m_pLastNodeColl)
+ && rPam.GetPoint()->nNode != rPam.GetMark()->nNode)
+ {
+ SwTextNode* pTextNd = rPam.GetPoint()->nNode.GetNode().GetTextNode();
+ if( pTextNd )
+ pTextNd->ChgFormatColl( m_pLastNodeColl );
+ }
+
+ // tdf#108124 the SwHistoryChangeFlyAnchor/SwHistoryFlyCnt must run before
+ // m_FlyUndos as they were created by DelContentIndex()
+ m_pHistory->Rollback( &rDoc, m_nSetPos );
+
+ // tdf#108124 (10/25/2017)
+ // During UNDO we call SwUndoInsLayFormat::UndoImpl in reverse order,
+ // firstly for m_FlyUndos[ m_FlyUndos.size()-1 ], etc.
+ // As absolute node index of fly stored in SwUndoFlyBase::nNdPgPos we
+ // should recover from Undo in direct order (last should be recovered first)
+ // During REDO we should recover Flys (Images) in direct order,
+ // firstly m_FlyUndos[0], then with m_FlyUndos[1] index, etc.
+
+ for (size_t n = 0; m_FlyUndos.size() > n; ++n)
+ {
+ m_FlyUndos[n]->RedoImpl(rContext);
+ }
+
+ if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
+ {
+ RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
+ rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags::Ignore );
+ rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, rPam ), true);
+ rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
+ }
+ else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) &&
+ !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
+ rDoc.getIDocumentRedlineAccess().SplitRedline(rPam);
+}
+
+void SwUndoInserts::RepeatImpl(::sw::RepeatContext & rContext)
+{
+ SwPaM aPam( rContext.GetDoc().GetNodes().GetEndOfContent() );
+ SetPaM( aPam );
+ SwPaM & rRepeatPaM( rContext.GetRepeatPaM() );
+ aPam.GetDoc().getIDocumentContentOperations().CopyRange( aPam, *rRepeatPaM.GetPoint(), SwCopyFlags::CheckPosInFly);
+}
+
+SwUndoInsDoc::SwUndoInsDoc( const SwPaM& rPam )
+ : SwUndoInserts( SwUndoId::INSDOKUMENT, rPam )
+{
+}
+
+SwUndoCpyDoc::SwUndoCpyDoc( const SwPaM& rPam )
+ : SwUndoInserts( SwUndoId::COPY, rPam )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */