sc: factor out some more code
[LibreOffice.git] / sw / source / core / undo / untblk.cxx
blob55ae66b86fd95fe61e73522020abb1b97cf27ebf
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <libxml/xmlwriter.h>
22 #include <fmtanchr.hxx>
23 #include <frmfmt.hxx>
24 #include <doc.hxx>
25 #include <IDocumentRedlineAccess.hxx>
26 #include <IShellCursorSupplier.hxx>
27 #include <docary.hxx>
28 #include <swcrsr.hxx>
29 #include <swundo.hxx>
30 #include <pam.hxx>
31 #include <mvsave.hxx>
32 #include <ndtxt.hxx>
33 #include <UndoCore.hxx>
34 #include <rolbck.hxx>
35 #include <redline.hxx>
36 #include <frameformats.hxx>
38 namespace sw {
40 std::optional<std::vector<SwFrameFormat*>>
41 GetFlysAnchoredAt(SwDoc & rDoc, SwNodeOffset const nSttNode, bool const isAtPageIncluded)
43 std::optional<std::vector<SwFrameFormat*>> pFrameFormats;
44 const size_t nArrLen = rDoc.GetSpzFrameFormats()->size();
45 for (size_t n = 0; n < nArrLen; ++n)
47 SwFrameFormat *const pFormat = (*rDoc.GetSpzFrameFormats())[n];
48 SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
49 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
50 if ((pAnchorNode
51 && nSttNode == pAnchorNode->GetIndex()
52 && ((pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA)
53 || (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)))
54 || (isAtPageIncluded && pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PAGE))
56 if (!pFrameFormats)
57 pFrameFormats.emplace();
58 pFrameFormats->push_back( pFormat );
61 return pFrameFormats;
64 } // namespace sw
66 //note: parameter is SwPam just so we can init SwUndRng, the End is ignored!
67 SwUndoInserts::SwUndoInserts( SwUndoId nUndoId, const SwPaM& rPam )
68 : SwUndo( nUndoId, &rPam.GetDoc() )
69 , SwUndRng( rPam )
70 , m_pTextFormatColl(nullptr)
71 , m_pLastNodeColl(nullptr)
72 , m_nDeleteTextNodes(1)
73 , m_nNodeDiff(0)
74 , m_nSetPos(0)
76 m_pHistory.reset( new SwHistory );
77 SwDoc& rDoc = rPam.GetDoc();
79 SwTextNode* pTextNd = rPam.GetPoint()->GetNode().GetTextNode();
80 if( pTextNd )
82 m_pTextFormatColl = pTextNd->GetTextColl();
83 assert(m_pTextFormatColl);
84 m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nSttNode,
85 0, pTextNd->GetText().getLength(), false );
86 if( pTextNd->HasSwAttrSet() )
87 m_pHistory->CopyFormatAttr( *pTextNd->GetpSwAttrSet(), m_nSttNode );
89 // We may have some flys anchored to paragraph where we inserting.
90 // These flys will be saved in pFrameFormats array (only flys which exist BEFORE insertion!)
91 // Then in SwUndoInserts::SetInsertRange the flys saved in pFrameFormats will NOT create Undos.
92 // m_FlyUndos will only be filled with newly inserted flys.
93 m_pFrameFormats = sw::GetFlysAnchoredAt(rDoc, m_nSttNode, true);
95 // consider Redline
96 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
98 m_pRedlineData.reset( new SwRedlineData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) );
99 SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() );
103 // This method does two things:
104 // 1. Adjusts SwUndoRng members, required for Undo.
105 // Members are:
106 // SwUndoRng::nSttNode - all nodes starting from this node will be deleted during Undo (in SwUndoInserts::UndoImpl)
107 // SwUndoRng::nSttContent - corresponding content index in SwUndoRng::nSttNode
108 // SwUndoRng::nEndNode - end node for deletion
109 // SwUndoRng::nEndContent - end content index
110 // All these members are filled in during construction of SwUndoInserts instance, and can be adjusted using this method
112 // 2. Fills in m_FlyUndos array with flys anchored ONLY to first and last paragraphs (first == rPam.Start(), last == rPam.End())
113 // Flys, anchored to any paragraph, but not first and last, are handled by DelContentIndex (see SwUndoInserts::UndoImpl) and are not stored in m_FlyUndos.
115 void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys,
116 SwNodeOffset const nDeleteTextNodes)
118 const SwPosition* pTmpPos = rPam.End();
119 m_nEndNode = pTmpPos->GetNodeIndex();
120 m_nEndContent = pTmpPos->GetContentIndex();
121 if( rPam.HasMark() )
123 if( pTmpPos == rPam.GetPoint() )
124 pTmpPos = rPam.GetMark();
125 else
126 pTmpPos = rPam.GetPoint();
128 m_nSttNode = pTmpPos->GetNodeIndex();
129 m_nSttContent = pTmpPos->GetContentIndex();
131 m_nDeleteTextNodes = nDeleteTextNodes;
132 if (m_nDeleteTextNodes == SwNodeOffset(0)) // if a table selection is added...
134 ++m_nSttNode; // ... then the CopyPam is not fully correct
138 // Fill m_FlyUndos with flys anchored to first and last paragraphs
140 if( !bScanFlys)
141 return;
143 // than collect all new Flys
144 SwDoc& rDoc = rPam.GetDoc();
145 const size_t nArrLen = rDoc.GetSpzFrameFormats()->size();
146 for( size_t n = 0; n < nArrLen; ++n )
148 SwFrameFormat* pFormat = (*rDoc.GetSpzFrameFormats())[n];
149 SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
150 if (IsCreateUndoForNewFly(*pAnchor, m_nSttNode, m_nEndNode))
152 std::vector<SwFrameFormat*>::iterator it;
153 if( !m_pFrameFormats ||
154 m_pFrameFormats->end() == ( it = std::find( m_pFrameFormats->begin(), m_pFrameFormats->end(), pFormat ) ) )
156 std::shared_ptr<SwUndoInsLayFormat> const pFlyUndo =
157 std::make_shared<SwUndoInsLayFormat>(pFormat, SwNodeOffset(0), 0);
158 m_FlyUndos.push_back(pFlyUndo);
160 else
161 m_pFrameFormats->erase( it );
164 m_pFrameFormats.reset();
167 /** This is not the same as IsDestroyFrameAnchoredAtChar()
168 and intentionally so: because the SwUndoInserts::UndoImpl() must remove
169 the flys at the start/end position that were inserted but not the ones
170 at the start/insert position that were already there;
171 handle all at-char flys at start/end node like this, even if they're
172 not *on* the start/end position, because it makes it easier to ensure
173 that the Undo/Redo run in inverse order.
175 bool SwUndoInserts::IsCreateUndoForNewFly(SwFormatAnchor const& rAnchor,
176 SwNodeOffset const nStartNode, SwNodeOffset const nEndNode)
178 assert(nStartNode <= nEndNode);
180 if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE)
182 return true; // needed for SwUndoInserts/SwReader::Read()
185 // check all at-char flys at the start/end nodes:
186 // ExcludeFlyAtStartEnd will exclude them!
187 SwNode const*const pAnchorNode = rAnchor.GetAnchorNode();
188 return pAnchorNode != nullptr
189 && ( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA
190 || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR)
191 && ( nStartNode == pAnchorNode->GetIndex()
192 || nEndNode == pAnchorNode->GetIndex());
195 void SwUndoInserts::dumpAsXml(xmlTextWriterPtr pWriter) const
197 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoInserts"));
198 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
199 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s",
200 BAD_CAST(typeid(*this).name()));
202 SwUndo::dumpAsXml(pWriter);
203 SwUndoSaveContent::dumpAsXml(pWriter);
205 if (m_pFrameFormats)
207 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pFrameFormats"));
208 for (const auto& pFormat : *m_pFrameFormats)
210 pFormat->dumpAsXml(pWriter);
212 (void)xmlTextWriterEndElement(pWriter);
215 if (!m_FlyUndos.empty())
217 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_FlyUndos"));
218 for (const auto& pFly : m_FlyUndos)
220 pFly->dumpAsXml(pWriter);
222 (void)xmlTextWriterEndElement(pWriter);
225 (void)xmlTextWriterEndElement(pWriter);
228 SwUndoInserts::~SwUndoInserts()
230 if (m_oUndoNodeIndex) // delete also the section from UndoNodes array
232 // Insert saves content in IconSection
233 SwNodes& rUNds = m_oUndoNodeIndex->GetNodes();
234 rUNds.Delete(*m_oUndoNodeIndex,
235 rUNds.GetEndOfExtras().GetIndex() - m_oUndoNodeIndex->GetIndex());
236 m_oUndoNodeIndex.reset();
238 m_pFrameFormats.reset();
239 m_pRedlineData.reset();
242 // Undo Insert operation
243 // It's important to note that Undo stores absolute node indexes. I.e. if during insertion, you insert nodes 31 to 33,
244 // 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.
245 // It just deletes them.
246 // 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.
247 // During Undo most recent actions will be executed first. So during execution of particular Undo action indices will be correct.
248 // But storing absolute indices leads to crashes if some action in Undo fails to roll back some modifications.
250 // Has following main steps:
251 // 1. m_FlyUndos removes flys anchored to first and last paragraph in Undo range.
252 // This array may be empty.
253 // 2. DelContentIndex to delete footnotes, flys, bookmarks (see comment for this function)
254 // Deleted flys are stored in pHistory array.
255 // First and last paragraphs flys are not deleted by DelContentIndex!
256 // For flys anchored to last paragraph, DelContentIndex re-anchors them to
257 // the last paragraph that will remain after Undo.
258 // 3. MoveToUndoNds moves nodes to Undo nodes array and removes them from document.
259 // 4. Lastly (starting from if(pTextNode)), text from last paragraph is joined to last remaining paragraph and FormatColl for last paragraph is restored.
260 // Format coll for last paragraph is removed during execution of UndoImpl
262 void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext)
264 SwDoc& rDoc = rContext.GetDoc();
265 SwPaM& rPam = AddUndoRedoPaM(rContext);
267 m_nNodeDiff = SwNodeOffset(0);
269 if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
270 rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any);
272 // if Point and Mark are different text nodes so a JoinNext has to be done
273 bool bJoinNext = m_nSttNode != m_nEndNode &&
274 rPam.GetMark()->GetNode().GetTextNode() &&
275 rPam.GetPoint()->GetNode().GetTextNode();
277 // Is there any content? (loading from template does not have content)
278 if( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent )
280 if( m_nSttNode != m_nEndNode )
282 SwTextNode* pTextNd = rDoc.GetNodes()[ m_nEndNode ]->GetTextNode();
283 if (pTextNd && pTextNd->GetText().getLength() == m_nEndContent)
284 m_pLastNodeColl = pTextNd->GetTextColl();
287 // tdf#128739 correct cursors but do not delete bookmarks yet
288 ::PaMCorrAbs(rPam, *rPam.End());
290 SetPaM(rPam);
293 // ... for consistency with the Insert File code in shellio.cxx, which
294 // creates separate SwUndoInsLayFormat for mysterious reasons, do this
295 // *before* anything else:
296 // after SetPaM but before MoveToUndoNds and DelContentIndex.
297 // note: there isn't an order dep wrt. initial Copy action because Undo
298 // overwrites the indexes but there is wrt. Redo because that uses the
299 // indexes
300 if (!m_FlyUndos.empty())
302 SwNodeOffset nTmp = rPam.GetPoint()->GetNodeIndex();
303 for (size_t n = m_FlyUndos.size(); 0 < n; --n)
305 m_FlyUndos[ n-1 ]->UndoImpl(rContext);
307 m_nNodeDiff += nTmp - rPam.GetPoint()->GetNodeIndex();
310 if (m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent)
312 // are there Footnotes or ContentFlyFrames in text?
313 m_nSetPos = m_pHistory->Count();
314 SwNodeOffset nTmp = rPam.GetMark()->GetNodeIndex();
315 DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(),
316 DelContentType::AllMask|DelContentType::ExcludeFlyAtStartEnd);
317 m_nNodeDiff += nTmp - rPam.GetMark()->GetNodeIndex();
318 if( *rPam.GetPoint() != *rPam.GetMark() )
320 m_oUndoNodeIndex.emplace(rDoc.GetNodes().GetEndOfContent());
321 MoveToUndoNds(rPam, &*m_oUndoNodeIndex);
323 if (m_nDeleteTextNodes == SwNodeOffset(0))
325 rPam.Move( fnMoveBackward, GoInContent );
330 SwTextNode* pTextNode = rPam.GetPoint()->GetNode().GetTextNode();
331 if( !pTextNode )
332 return;
334 if( !m_pTextFormatColl ) // if 0 than it's no TextNode -> delete
336 SwNodeIndex aDelIdx( *pTextNode );
337 assert(SwNodeOffset(0) < m_nDeleteTextNodes && m_nDeleteTextNodes < SwNodeOffset(3));
338 for (SwNodeOffset i(0); i < m_nDeleteTextNodes; ++i)
340 rPam.Move(fnMoveForward, GoInNode);
342 rPam.DeleteMark();
344 for (SwNodeOffset i(0); i < m_nDeleteTextNodes; ++i)
346 RemoveIdxRel(aDelIdx.GetIndex() + i, *rPam.GetPoint());
349 rDoc.GetNodes().Delete( aDelIdx, m_nDeleteTextNodes );
351 else
353 if( bJoinNext && pTextNode->CanJoinNext())
356 RemoveIdxRel( pTextNode->GetIndex()+1,
357 SwPosition( *pTextNode, pTextNode, pTextNode->GetText().getLength() ) );
359 pTextNode->JoinNext();
361 // reset all text attributes in the paragraph!
362 pTextNode->RstTextAttr( 0, pTextNode->Len(), 0, nullptr, true );
364 pTextNode->ResetAllAttr();
366 if (rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl))
367 m_pTextFormatColl = static_cast<SwTextFormatColl*>(pTextNode->ChgFormatColl( m_pTextFormatColl )) ;
369 m_pHistory->SetTmpEnd( m_nSetPos );
370 m_pHistory->TmpRollback(&rDoc, 0, false);
374 // See SwUndoInserts::UndoImpl comments
375 // All actions here should be done in reverse order to what is done in SwUndoInserts::UndoImpl!
377 void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext)
379 // position cursor onto REDO section
380 SwCursor& rPam(rContext.GetCursorSupplier().CreateNewShellCursor());
381 SwDoc& rDoc = rPam.GetDoc();
382 rPam.DeleteMark();
383 rPam.GetPoint()->Assign( m_nSttNode - m_nNodeDiff, m_nSttContent );
384 SwContentNode* pCNd = rPam.GetPointContentNode();
386 SwTextFormatColl* pSavTextFormatColl = m_pTextFormatColl;
387 if( m_pTextFormatColl && pCNd && pCNd->IsTextNode() )
388 pSavTextFormatColl = static_cast<SwTextNode*>(pCNd)->GetTextColl();
390 m_pHistory->SetTmpEnd( m_nSetPos );
392 // retrieve start position for rollback
393 if( ( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent ) && m_oUndoNodeIndex)
395 auto const pFlysAtInsPos(sw::GetFlysAnchoredAt(rDoc,
396 rPam.GetPoint()->GetNodeIndex(), false));
398 ::std::optional<SwNodeIndex> oMvBkwrd = MovePtBackward(rPam);
399 bool const isMoveFlyAnchors(!oMvBkwrd // equivalent to bCanMoveBack
400 || m_oUndoNodeIndex->GetNode().IsTextNode()
401 || (oMvBkwrd->GetNode().IsStartNode()
402 && m_oUndoNodeIndex->GetNode().IsSectionNode()));
404 // re-insert content again (first detach m_oUndoNodeIndex!)
405 SwNodeOffset const nMvNd = m_oUndoNodeIndex->GetIndex();
406 m_oUndoNodeIndex.reset();
407 MoveFromUndoNds(rDoc, nMvNd, *rPam.GetMark());
408 if (m_nDeleteTextNodes != SwNodeOffset(0) || oMvBkwrd)
410 MovePtForward(rPam, ::std::move(oMvBkwrd));
412 rPam.Exchange();
414 // at-char anchors post SplitNode are on index 0 of 2nd node and will
415 // remain there - move them back to the start (end would also work?)
416 if (pFlysAtInsPos && isMoveFlyAnchors)
418 for (SwFrameFormat * pFly : *pFlysAtInsPos)
420 SwFormatAnchor const*const pAnchor = &pFly->GetAnchor();
421 if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
423 SwFormatAnchor anchor(*pAnchor);
424 anchor.SetAnchor( rPam.GetMark() );
425 pFly->SetFormatAttr(anchor);
431 if (m_pTextFormatColl && rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl))
433 SwTextNode* pTextNd = rPam.GetMark()->GetNode().GetTextNode();
434 if( pTextNd )
435 pTextNd->ChgFormatColl( m_pTextFormatColl );
437 m_pTextFormatColl = pSavTextFormatColl;
439 if (m_pLastNodeColl && rDoc.GetTextFormatColls()->IsAlive(m_pLastNodeColl)
440 && rPam.GetPoint()->GetNode() != rPam.GetMark()->GetNode())
442 SwTextNode* pTextNd = rPam.GetPoint()->GetNode().GetTextNode();
443 if( pTextNd )
444 pTextNd->ChgFormatColl( m_pLastNodeColl );
447 // tdf#108124 the SwHistoryChangeFlyAnchor/SwHistoryFlyCnt must run before
448 // m_FlyUndos as they were created by DelContentIndex()
449 m_pHistory->Rollback( &rDoc, m_nSetPos );
451 // tdf#108124 (10/25/2017)
452 // During UNDO we call SwUndoInsLayFormat::UndoImpl in reverse order,
453 // firstly for m_FlyUndos[ m_FlyUndos.size()-1 ], etc.
454 // As absolute node index of fly stored in SwUndoFlyBase::nNdPgPos we
455 // should recover from Undo in direct order (last should be recovered first)
456 // During REDO we should recover Flys (Images) in direct order,
457 // firstly m_FlyUndos[0], then with m_FlyUndos[1] index, etc.
459 for (size_t n = 0; m_FlyUndos.size() > n; ++n)
461 m_FlyUndos[n]->RedoImpl(rContext);
464 if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
466 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
467 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags::Ignore );
468 rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, rPam ), true);
469 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
471 else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) &&
472 !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
473 rDoc.getIDocumentRedlineAccess().SplitRedline(rPam);
476 void SwUndoInserts::RepeatImpl(::sw::RepeatContext & rContext)
478 SwPaM aPam( rContext.GetDoc().GetNodes().GetEndOfContent() );
479 SetPaM( aPam );
480 SwPaM & rRepeatPaM( rContext.GetRepeatPaM() );
481 aPam.GetDoc().getIDocumentContentOperations().CopyRange( aPam, *rRepeatPaM.GetPoint(), SwCopyFlags::CheckPosInFly);
484 SwUndoInsDoc::SwUndoInsDoc( const SwPaM& rPam )
485 : SwUndoInserts( SwUndoId::INSDOKUMENT, rPam )
489 SwUndoCpyDoc::SwUndoCpyDoc( const SwPaM& rPam )
490 : SwUndoInserts( SwUndoId::COPY, rPam )
494 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */