1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
25 #include <IDocumentRedlineAccess.hxx>
26 #include <IShellCursorSupplier.hxx>
33 #include <UndoCore.hxx>
35 #include <redline.hxx>
36 #include <frameformats.hxx>
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();
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
))
57 pFrameFormats
.emplace();
58 pFrameFormats
->push_back( pFormat
);
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() )
70 , m_pTextFormatColl(nullptr)
71 , m_pLastNodeColl(nullptr)
72 , m_nDeleteTextNodes(1)
76 m_pHistory
.reset( new SwHistory
);
77 SwDoc
& rDoc
= rPam
.GetDoc();
79 SwTextNode
* pTextNd
= rPam
.GetPoint()->GetNode().GetTextNode();
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);
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.
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();
123 if( pTmpPos
== rPam
.GetPoint() )
124 pTmpPos
= rPam
.GetMark();
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
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
);
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
);
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());
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
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();
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
);
344 for (SwNodeOffset
i(0); i
< m_nDeleteTextNodes
; ++i
)
346 RemoveIdxRel(aDelIdx
.GetIndex() + i
, *rPam
.GetPoint());
349 rDoc
.GetNodes().Delete( aDelIdx
, m_nDeleteTextNodes
);
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();
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
));
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();
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();
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() );
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: */