android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / core / undo / untblk.cxx
blobfd5c3239c0426be7b187db6af8c701817d1363d6
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)
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)))
55 if (!pFrameFormats)
56 pFrameFormats.emplace();
57 pFrameFormats->push_back( pFormat );
60 return pFrameFormats;
63 } // namespace sw
65 //note: parameter is SwPam just so we can init SwUndRng, the End is ignored!
66 SwUndoInserts::SwUndoInserts( SwUndoId nUndoId, const SwPaM& rPam )
67 : SwUndo( nUndoId, &rPam.GetDoc() )
68 , SwUndRng( rPam )
69 , m_pTextFormatColl(nullptr)
70 , m_pLastNodeColl(nullptr)
71 , m_nDeleteTextNodes(1)
72 , m_nNodeDiff(0)
73 , m_nSetPos(0)
75 m_pHistory.reset( new SwHistory );
76 SwDoc& rDoc = rPam.GetDoc();
78 SwTextNode* pTextNd = rPam.GetPoint()->GetNode().GetTextNode();
79 if( pTextNd )
81 m_pTextFormatColl = pTextNd->GetTextColl();
82 assert(m_pTextFormatColl);
83 m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nSttNode,
84 0, pTextNd->GetText().getLength(), false );
85 if( pTextNd->HasSwAttrSet() )
86 m_pHistory->CopyFormatAttr( *pTextNd->GetpSwAttrSet(), m_nSttNode );
88 // We may have some flys anchored to paragraph where we inserting.
89 // These flys will be saved in pFrameFormats array (only flys which exist BEFORE insertion!)
90 // Then in SwUndoInserts::SetInsertRange the flys saved in pFrameFormats will NOT create Undos.
91 // m_FlyUndos will only be filled with newly inserted flys.
92 m_pFrameFormats = sw::GetFlysAnchoredAt(rDoc, m_nSttNode);
94 // consider Redline
95 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
97 m_pRedlineData.reset( new SwRedlineData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) );
98 SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() );
102 // This method does two things:
103 // 1. Adjusts SwUndoRng members, required for Undo.
104 // Members are:
105 // SwUndoRng::nSttNode - all nodes starting from this node will be deleted during Undo (in SwUndoInserts::UndoImpl)
106 // SwUndoRng::nSttContent - corresponding content index in SwUndoRng::nSttNode
107 // SwUndoRng::nEndNode - end node for deletion
108 // SwUndoRng::nEndContent - end content index
109 // All these members are filled in during construction of SwUndoInserts instance, and can be adjusted using this method
111 // 2. Fills in m_FlyUndos array with flys anchored ONLY to first and last paragraphs (first == rPam.Start(), last == rPam.End())
112 // Flys, anchored to any paragraph, but not first and last, are handled by DelContentIndex (see SwUndoInserts::UndoImpl) and are not stored in m_FlyUndos.
114 void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys,
115 SwNodeOffset const nDeleteTextNodes)
117 const SwPosition* pTmpPos = rPam.End();
118 m_nEndNode = pTmpPos->GetNodeIndex();
119 m_nEndContent = pTmpPos->GetContentIndex();
120 if( rPam.HasMark() )
122 if( pTmpPos == rPam.GetPoint() )
123 pTmpPos = rPam.GetMark();
124 else
125 pTmpPos = rPam.GetPoint();
127 m_nSttNode = pTmpPos->GetNodeIndex();
128 m_nSttContent = pTmpPos->GetContentIndex();
130 m_nDeleteTextNodes = nDeleteTextNodes;
131 if (m_nDeleteTextNodes == SwNodeOffset(0)) // if a table selection is added...
133 ++m_nSttNode; // ... then the CopyPam is not fully correct
137 // Fill m_FlyUndos with flys anchored to first and last paragraphs
139 if( !bScanFlys)
140 return;
142 // than collect all new Flys
143 SwDoc& rDoc = rPam.GetDoc();
144 const size_t nArrLen = rDoc.GetSpzFrameFormats()->size();
145 for( size_t n = 0; n < nArrLen; ++n )
147 SwFrameFormat* pFormat = (*rDoc.GetSpzFrameFormats())[n];
148 SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
149 if (IsCreateUndoForNewFly(*pAnchor, m_nSttNode, m_nEndNode))
151 std::vector<SwFrameFormat*>::iterator it;
152 if( !m_pFrameFormats ||
153 m_pFrameFormats->end() == ( it = std::find( m_pFrameFormats->begin(), m_pFrameFormats->end(), pFormat ) ) )
155 std::shared_ptr<SwUndoInsLayFormat> const pFlyUndo =
156 std::make_shared<SwUndoInsLayFormat>(pFormat, SwNodeOffset(0), 0);
157 m_FlyUndos.push_back(pFlyUndo);
159 else
160 m_pFrameFormats->erase( it );
163 m_pFrameFormats.reset();
166 /** This is not the same as IsDestroyFrameAnchoredAtChar()
167 and intentionally so: because the SwUndoInserts::UndoImpl() must remove
168 the flys at the start/end position that were inserted but not the ones
169 at the start/insert position that were already there;
170 handle all at-char flys at start/end node like this, even if they're
171 not *on* the start/end position, because it makes it easier to ensure
172 that the Undo/Redo run in inverse order.
174 bool SwUndoInserts::IsCreateUndoForNewFly(SwFormatAnchor const& rAnchor,
175 SwNodeOffset const nStartNode, SwNodeOffset const nEndNode)
177 assert(nStartNode <= nEndNode);
179 // check all at-char flys at the start/end nodes:
180 // ExcludeFlyAtStartEnd will exclude them!
181 SwNode const*const pAnchorNode = rAnchor.GetAnchorNode();
182 return pAnchorNode != nullptr
183 && ( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA
184 || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR)
185 && ( nStartNode == pAnchorNode->GetIndex()
186 || nEndNode == pAnchorNode->GetIndex());
189 void SwUndoInserts::dumpAsXml(xmlTextWriterPtr pWriter) const
191 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoInserts"));
192 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
193 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s",
194 BAD_CAST(typeid(*this).name()));
196 SwUndo::dumpAsXml(pWriter);
197 SwUndoSaveContent::dumpAsXml(pWriter);
199 if (m_pFrameFormats)
201 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pFrameFormats"));
202 for (const auto& pFormat : *m_pFrameFormats)
204 pFormat->dumpAsXml(pWriter);
206 (void)xmlTextWriterEndElement(pWriter);
209 if (!m_FlyUndos.empty())
211 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_FlyUndos"));
212 for (const auto& pFly : m_FlyUndos)
214 pFly->dumpAsXml(pWriter);
216 (void)xmlTextWriterEndElement(pWriter);
219 (void)xmlTextWriterEndElement(pWriter);
222 SwUndoInserts::~SwUndoInserts()
224 if (m_oUndoNodeIndex) // delete also the section from UndoNodes array
226 // Insert saves content in IconSection
227 SwNodes& rUNds = m_oUndoNodeIndex->GetNodes();
228 rUNds.Delete(*m_oUndoNodeIndex,
229 rUNds.GetEndOfExtras().GetIndex() - m_oUndoNodeIndex->GetIndex());
230 m_oUndoNodeIndex.reset();
232 m_pFrameFormats.reset();
233 m_pRedlineData.reset();
236 // Undo Insert operation
237 // It's important to note that Undo stores absolute node indexes. I.e. if during insertion, you insert nodes 31 to 33,
238 // 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.
239 // It just deletes them.
240 // 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.
241 // During Undo most recent actions will be executed first. So during execution of particular Undo action indices will be correct.
242 // But storing absolute indices leads to crashes if some action in Undo fails to roll back some modifications.
244 // Has following main steps:
245 // 1. m_FlyUndos removes flys anchored to first and last paragraph in Undo range.
246 // This array may be empty.
247 // 2. DelContentIndex to delete footnotes, flys, bookmarks (see comment for this function)
248 // Deleted flys are stored in pHistory array.
249 // First and last paragraphs flys are not deleted by DelContentIndex!
250 // For flys anchored to last paragraph, DelContentIndex re-anchors them to
251 // the last paragraph that will remain after Undo.
252 // 3. MoveToUndoNds moves nodes to Undo nodes array and removes them from document.
253 // 4. Lastly (starting from if(pTextNode)), text from last paragraph is joined to last remaining paragraph and FormatColl for last paragraph is restored.
254 // Format coll for last paragraph is removed during execution of UndoImpl
256 void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext)
258 SwDoc& rDoc = rContext.GetDoc();
259 SwPaM& rPam = AddUndoRedoPaM(rContext);
261 m_nNodeDiff = SwNodeOffset(0);
263 if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
264 rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any);
266 // if Point and Mark are different text nodes so a JoinNext has to be done
267 bool bJoinNext = m_nSttNode != m_nEndNode &&
268 rPam.GetMark()->GetNode().GetTextNode() &&
269 rPam.GetPoint()->GetNode().GetTextNode();
271 // Is there any content? (loading from template does not have content)
272 if( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent )
274 if( m_nSttNode != m_nEndNode )
276 SwTextNode* pTextNd = rDoc.GetNodes()[ m_nEndNode ]->GetTextNode();
277 if (pTextNd && pTextNd->GetText().getLength() == m_nEndContent)
278 m_pLastNodeColl = pTextNd->GetTextColl();
281 // tdf#128739 correct cursors but do not delete bookmarks yet
282 ::PaMCorrAbs(rPam, *rPam.End());
284 SetPaM(rPam);
287 // ... for consistency with the Insert File code in shellio.cxx, which
288 // creates separate SwUndoInsLayFormat for mysterious reasons, do this
289 // *before* anything else:
290 // after SetPaM but before MoveToUndoNds and DelContentIndex.
291 // note: there isn't an order dep wrt. initial Copy action because Undo
292 // overwrites the indexes but there is wrt. Redo because that uses the
293 // indexes
294 if (!m_FlyUndos.empty())
296 SwNodeOffset nTmp = rPam.GetPoint()->GetNodeIndex();
297 for (size_t n = m_FlyUndos.size(); 0 < n; --n)
299 m_FlyUndos[ n-1 ]->UndoImpl(rContext);
301 m_nNodeDiff += nTmp - rPam.GetPoint()->GetNodeIndex();
304 if (m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent)
306 // are there Footnotes or ContentFlyFrames in text?
307 m_nSetPos = m_pHistory->Count();
308 SwNodeOffset nTmp = rPam.GetMark()->GetNodeIndex();
309 DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(),
310 DelContentType::AllMask|DelContentType::ExcludeFlyAtStartEnd);
311 m_nNodeDiff += nTmp - rPam.GetMark()->GetNodeIndex();
312 if( *rPam.GetPoint() != *rPam.GetMark() )
314 m_oUndoNodeIndex.emplace(rDoc.GetNodes().GetEndOfContent());
315 MoveToUndoNds(rPam, &*m_oUndoNodeIndex);
317 if (m_nDeleteTextNodes == SwNodeOffset(0))
319 rPam.Move( fnMoveBackward, GoInContent );
324 SwTextNode* pTextNode = rPam.GetPoint()->GetNode().GetTextNode();
325 if( !pTextNode )
326 return;
328 if( !m_pTextFormatColl ) // if 0 than it's no TextNode -> delete
330 SwNodeIndex aDelIdx( *pTextNode );
331 assert(SwNodeOffset(0) < m_nDeleteTextNodes && m_nDeleteTextNodes < SwNodeOffset(3));
332 for (SwNodeOffset i(0); i < m_nDeleteTextNodes; ++i)
334 rPam.Move(fnMoveForward, GoInNode);
336 rPam.DeleteMark();
338 for (SwNodeOffset i(0); i < m_nDeleteTextNodes; ++i)
340 RemoveIdxRel(aDelIdx.GetIndex() + i, *rPam.GetPoint());
343 rDoc.GetNodes().Delete( aDelIdx, m_nDeleteTextNodes );
345 else
347 if( bJoinNext && pTextNode->CanJoinNext())
350 RemoveIdxRel( pTextNode->GetIndex()+1,
351 SwPosition( *pTextNode, pTextNode, pTextNode->GetText().getLength() ) );
353 pTextNode->JoinNext();
355 // reset all text attributes in the paragraph!
356 pTextNode->RstTextAttr( 0, pTextNode->Len(), 0, nullptr, true );
358 pTextNode->ResetAllAttr();
360 if (rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl))
361 m_pTextFormatColl = static_cast<SwTextFormatColl*>(pTextNode->ChgFormatColl( m_pTextFormatColl )) ;
363 m_pHistory->SetTmpEnd( m_nSetPos );
364 m_pHistory->TmpRollback(&rDoc, 0, false);
368 // See SwUndoInserts::UndoImpl comments
369 // All actions here should be done in reverse order to what is done in SwUndoInserts::UndoImpl!
371 void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext)
373 // position cursor onto REDO section
374 SwCursor& rPam(rContext.GetCursorSupplier().CreateNewShellCursor());
375 SwDoc& rDoc = rPam.GetDoc();
376 rPam.DeleteMark();
377 rPam.GetPoint()->Assign( m_nSttNode - m_nNodeDiff, m_nSttContent );
378 SwContentNode* pCNd = rPam.GetPointContentNode();
380 SwTextFormatColl* pSavTextFormatColl = m_pTextFormatColl;
381 if( m_pTextFormatColl && pCNd && pCNd->IsTextNode() )
382 pSavTextFormatColl = static_cast<SwTextNode*>(pCNd)->GetTextColl();
384 m_pHistory->SetTmpEnd( m_nSetPos );
386 // retrieve start position for rollback
387 if( ( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent ) && m_oUndoNodeIndex)
389 auto const pFlysAtInsPos(sw::GetFlysAnchoredAt(rDoc,
390 rPam.GetPoint()->GetNodeIndex()));
392 ::std::optional<SwNodeIndex> oMvBkwrd = MovePtBackward(rPam);
394 // re-insert content again (first detach m_oUndoNodeIndex!)
395 SwNodeOffset const nMvNd = m_oUndoNodeIndex->GetIndex();
396 m_oUndoNodeIndex.reset();
397 MoveFromUndoNds(rDoc, nMvNd, *rPam.GetMark());
398 if (m_nDeleteTextNodes != SwNodeOffset(0) || oMvBkwrd)
400 MovePtForward(rPam, ::std::move(oMvBkwrd));
402 rPam.Exchange();
404 // at-char anchors post SplitNode are on index 0 of 2nd node and will
405 // remain there - move them back to the start (end would also work?)
406 if (pFlysAtInsPos)
408 for (SwFrameFormat * pFly : *pFlysAtInsPos)
410 SwFormatAnchor const*const pAnchor = &pFly->GetAnchor();
411 if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
413 SwFormatAnchor anchor(*pAnchor);
414 anchor.SetAnchor( rPam.GetMark() );
415 pFly->SetFormatAttr(anchor);
421 if (m_pTextFormatColl && rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl))
423 SwTextNode* pTextNd = rPam.GetMark()->GetNode().GetTextNode();
424 if( pTextNd )
425 pTextNd->ChgFormatColl( m_pTextFormatColl );
427 m_pTextFormatColl = pSavTextFormatColl;
429 if (m_pLastNodeColl && rDoc.GetTextFormatColls()->IsAlive(m_pLastNodeColl)
430 && rPam.GetPoint()->GetNode() != rPam.GetMark()->GetNode())
432 SwTextNode* pTextNd = rPam.GetPoint()->GetNode().GetTextNode();
433 if( pTextNd )
434 pTextNd->ChgFormatColl( m_pLastNodeColl );
437 // tdf#108124 the SwHistoryChangeFlyAnchor/SwHistoryFlyCnt must run before
438 // m_FlyUndos as they were created by DelContentIndex()
439 m_pHistory->Rollback( &rDoc, m_nSetPos );
441 // tdf#108124 (10/25/2017)
442 // During UNDO we call SwUndoInsLayFormat::UndoImpl in reverse order,
443 // firstly for m_FlyUndos[ m_FlyUndos.size()-1 ], etc.
444 // As absolute node index of fly stored in SwUndoFlyBase::nNdPgPos we
445 // should recover from Undo in direct order (last should be recovered first)
446 // During REDO we should recover Flys (Images) in direct order,
447 // firstly m_FlyUndos[0], then with m_FlyUndos[1] index, etc.
449 for (size_t n = 0; m_FlyUndos.size() > n; ++n)
451 m_FlyUndos[n]->RedoImpl(rContext);
454 if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
456 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
457 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags::Ignore );
458 rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, rPam ), true);
459 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
461 else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) &&
462 !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
463 rDoc.getIDocumentRedlineAccess().SplitRedline(rPam);
466 void SwUndoInserts::RepeatImpl(::sw::RepeatContext & rContext)
468 SwPaM aPam( rContext.GetDoc().GetNodes().GetEndOfContent() );
469 SetPaM( aPam );
470 SwPaM & rRepeatPaM( rContext.GetRepeatPaM() );
471 aPam.GetDoc().getIDocumentContentOperations().CopyRange( aPam, *rRepeatPaM.GetPoint(), SwCopyFlags::CheckPosInFly);
474 SwUndoInsDoc::SwUndoInsDoc( const SwPaM& rPam )
475 : SwUndoInserts( SwUndoId::INSDOKUMENT, rPam )
479 SwUndoCpyDoc::SwUndoCpyDoc( const SwPaM& rPam )
480 : SwUndoInserts( SwUndoId::COPY, rPam )
484 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */