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
)
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
)))
56 pFrameFormats
.emplace();
57 pFrameFormats
->push_back( pFormat
);
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() )
69 , m_pTextFormatColl(nullptr)
70 , m_pLastNodeColl(nullptr)
71 , m_nDeleteTextNodes(1)
75 m_pHistory
.reset( new SwHistory
);
76 SwDoc
& rDoc
= rPam
.GetDoc();
78 SwTextNode
* pTextNd
= rPam
.GetPoint()->GetNode().GetTextNode();
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
);
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.
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();
122 if( pTmpPos
== rPam
.GetPoint() )
123 pTmpPos
= rPam
.GetMark();
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
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
);
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
);
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());
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
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();
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
);
338 for (SwNodeOffset
i(0); i
< m_nDeleteTextNodes
; ++i
)
340 RemoveIdxRel(aDelIdx
.GetIndex() + i
, *rPam
.GetPoint());
343 rDoc
.GetNodes().Delete( aDelIdx
, m_nDeleteTextNodes
);
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();
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
));
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?)
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();
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();
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() );
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: */