1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <libxml/xmlwriter.h>
21
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>
37
38 namespace sw {
39
40 std::optional<std::vector<SwFrameFormat*>>
GetFlysAnchoredAt(SwDoc & rDoc,SwNodeOffset const nSttNode,bool const isAtPageIncluded)41 GetFlysAnchoredAt(SwDoc & rDoc, SwNodeOffset const nSttNode, bool const isAtPageIncluded)
42 {
43 std::optional<std::vector<SwFrameFormat*>> pFrameFormats;
44 const size_t nArrLen = rDoc.GetSpzFrameFormats()->size();
45 for (size_t n = 0; n < nArrLen; ++n)
46 {
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))
55 {
56 if (!pFrameFormats)
57 pFrameFormats.emplace();
58 pFrameFormats->push_back( pFormat );
59 }
60 }
61 return pFrameFormats;
62 }
63
64 } // namespace sw
65
66 //note: parameter is SwPam just so we can init SwUndRng, the End is ignored!
SwUndoInserts(SwUndoId nUndoId,const SwPaM & rPam)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)
75 {
76 m_pHistory.reset( new SwHistory );
77 SwDoc& rDoc = rPam.GetDoc();
78
79 SwTextNode* pTextNd = rPam.GetPoint()->GetNode().GetTextNode();
80 if( pTextNd )
81 {
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 );
88
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);
94 }
95 // consider Redline
96 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
97 {
98 m_pRedlineData.reset( new SwRedlineData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) );
99 SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() );
100 }
101 }
102
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
111 //
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.
114
SetInsertRange(const SwPaM & rPam,bool bScanFlys,SwNodeOffset const nDeleteTextNodes)115 void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys,
116 SwNodeOffset const nDeleteTextNodes)
117 {
118 const SwPosition* pTmpPos = rPam.End();
119 m_nEndNode = pTmpPos->GetNodeIndex();
120 m_nEndContent = pTmpPos->GetContentIndex();
121 if( rPam.HasMark() )
122 {
123 if( pTmpPos == rPam.GetPoint() )
124 pTmpPos = rPam.GetMark();
125 else
126 pTmpPos = rPam.GetPoint();
127
128 m_nSttNode = pTmpPos->GetNodeIndex();
129 m_nSttContent = pTmpPos->GetContentIndex();
130
131 m_nDeleteTextNodes = nDeleteTextNodes;
132 if (m_nDeleteTextNodes == SwNodeOffset(0)) // if a table selection is added...
133 {
134 ++m_nSttNode; // ... then the CopyPam is not fully correct
135 }
136 }
137
138 // Fill m_FlyUndos with flys anchored to first and last paragraphs
139
140 if( !bScanFlys)
141 return;
142
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 )
147 {
148 SwFrameFormat* pFormat = (*rDoc.GetSpzFrameFormats())[n];
149 SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
150 if (IsCreateUndoForNewFly(*pAnchor, m_nSttNode, m_nEndNode))
151 {
152 std::vector<SwFrameFormat*>::iterator it;
153 if( !m_pFrameFormats ||
154 m_pFrameFormats->end() == ( it = std::find( m_pFrameFormats->begin(), m_pFrameFormats->end(), pFormat ) ) )
155 {
156 std::shared_ptr<SwUndoInsLayFormat> const pFlyUndo =
157 std::make_shared<SwUndoInsLayFormat>(pFormat, SwNodeOffset(0), 0);
158 m_FlyUndos.push_back(pFlyUndo);
159 }
160 else
161 m_pFrameFormats->erase( it );
162 }
163 }
164 m_pFrameFormats.reset();
165 }
166
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.
174 */
IsCreateUndoForNewFly(SwFormatAnchor const & rAnchor,SwNodeOffset const nStartNode,SwNodeOffset const nEndNode)175 bool SwUndoInserts::IsCreateUndoForNewFly(SwFormatAnchor const& rAnchor,
176 SwNodeOffset const nStartNode, SwNodeOffset const nEndNode)
177 {
178 assert(nStartNode <= nEndNode);
179
180 if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE)
181 {
182 return true; // needed for SwUndoInserts/SwReader::Read()
183 }
184
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());
193 }
194
dumpAsXml(xmlTextWriterPtr pWriter) const195 void SwUndoInserts::dumpAsXml(xmlTextWriterPtr pWriter) const
196 {
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()));
201
202 SwUndo::dumpAsXml(pWriter);
203 SwUndoSaveContent::dumpAsXml(pWriter);
204
205 if (m_pFrameFormats)
206 {
207 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pFrameFormats"));
208 for (const auto& pFormat : *m_pFrameFormats)
209 {
210 pFormat->dumpAsXml(pWriter);
211 }
212 (void)xmlTextWriterEndElement(pWriter);
213 }
214
215 if (!m_FlyUndos.empty())
216 {
217 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_FlyUndos"));
218 for (const auto& pFly : m_FlyUndos)
219 {
220 pFly->dumpAsXml(pWriter);
221 }
222 (void)xmlTextWriterEndElement(pWriter);
223 }
224
225 (void)xmlTextWriterEndElement(pWriter);
226 }
227
~SwUndoInserts()228 SwUndoInserts::~SwUndoInserts()
229 {
230 if (m_oUndoNodeIndex) // delete also the section from UndoNodes array
231 {
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();
237 }
238 m_pFrameFormats.reset();
239 m_pRedlineData.reset();
240 }
241
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.
249
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
261
UndoImpl(::sw::UndoRedoContext & rContext)262 void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext)
263 {
264 SwDoc& rDoc = rContext.GetDoc();
265 SwPaM& rPam = AddUndoRedoPaM(rContext);
266
267 m_nNodeDiff = SwNodeOffset(0);
268
269 if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
270 rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any);
271
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();
276
277 // Is there any content? (loading from template does not have content)
278 if( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent )
279 {
280 if( m_nSttNode != m_nEndNode )
281 {
282 SwTextNode* pTextNd = rDoc.GetNodes()[ m_nEndNode ]->GetTextNode();
283 if (pTextNd && pTextNd->GetText().getLength() == m_nEndContent)
284 m_pLastNodeColl = pTextNd->GetTextColl();
285 }
286
287 // tdf#128739 correct cursors but do not delete bookmarks yet
288 ::PaMCorrAbs(rPam, *rPam.End());
289
290 SetPaM(rPam);
291 }
292
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())
301 {
302 SwNodeOffset nTmp = rPam.GetPoint()->GetNodeIndex();
303 for (size_t n = m_FlyUndos.size(); 0 < n; --n)
304 {
305 m_FlyUndos[ n-1 ]->UndoImpl(rContext);
306 }
307 m_nNodeDiff += nTmp - rPam.GetPoint()->GetNodeIndex();
308 }
309
310 if (m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent)
311 {
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() )
319 {
320 m_oUndoNodeIndex.emplace(rDoc.GetNodes().GetEndOfContent());
321 MoveToUndoNds(rPam, &*m_oUndoNodeIndex);
322
323 if (m_nDeleteTextNodes == SwNodeOffset(0))
324 {
325 rPam.Move( fnMoveBackward, GoInContent );
326 }
327 }
328 }
329
330 SwTextNode* pTextNode = rPam.GetPoint()->GetNode().GetTextNode();
331 if( !pTextNode )
332 return;
333
334 if( !m_pTextFormatColl ) // if 0 than it's no TextNode -> delete
335 {
336 SwNodeIndex aDelIdx( *pTextNode );
337 assert(SwNodeOffset(0) < m_nDeleteTextNodes && m_nDeleteTextNodes < SwNodeOffset(3));
338 for (SwNodeOffset i(0); i < m_nDeleteTextNodes; ++i)
339 {
340 rPam.Move(fnMoveForward, GoInNode);
341 }
342 rPam.DeleteMark();
343
344 for (SwNodeOffset i(0); i < m_nDeleteTextNodes; ++i)
345 {
346 RemoveIdxRel(aDelIdx.GetIndex() + i, *rPam.GetPoint());
347 }
348
349 rDoc.GetNodes().Delete( aDelIdx, m_nDeleteTextNodes );
350 }
351 else
352 {
353 if( bJoinNext && pTextNode->CanJoinNext())
354 {
355 {
356 RemoveIdxRel( pTextNode->GetIndex()+1,
357 SwPosition( *pTextNode, pTextNode, pTextNode->GetText().getLength() ) );
358 }
359 pTextNode->JoinNext();
360 }
361 // reset all text attributes in the paragraph!
362 pTextNode->RstTextAttr( 0, pTextNode->Len(), 0, nullptr, true );
363
364 pTextNode->ResetAllAttr();
365
366 if (rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl))
367 m_pTextFormatColl = static_cast<SwTextFormatColl*>(pTextNode->ChgFormatColl( m_pTextFormatColl )) ;
368
369 m_pHistory->SetTmpEnd( m_nSetPos );
370 m_pHistory->TmpRollback(&rDoc, 0, false);
371 }
372 }
373
374 // See SwUndoInserts::UndoImpl comments
375 // All actions here should be done in reverse order to what is done in SwUndoInserts::UndoImpl!
376
RedoImpl(::sw::UndoRedoContext & rContext)377 void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext)
378 {
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();
385
386 SwTextFormatColl* pSavTextFormatColl = m_pTextFormatColl;
387 if( m_pTextFormatColl && pCNd && pCNd->IsTextNode() )
388 pSavTextFormatColl = static_cast<SwTextNode*>(pCNd)->GetTextColl();
389
390 m_pHistory->SetTmpEnd( m_nSetPos );
391
392 // retrieve start position for rollback
393 if( ( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent ) && m_oUndoNodeIndex)
394 {
395 auto const pFlysAtInsPos(sw::GetFlysAnchoredAt(rDoc,
396 rPam.GetPoint()->GetNodeIndex(), false));
397
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()));
403
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)
409 {
410 MovePtForward(rPam, ::std::move(oMvBkwrd));
411 }
412 rPam.Exchange();
413
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)
417 {
418 for (SwFrameFormat * pFly : *pFlysAtInsPos)
419 {
420 SwFormatAnchor const*const pAnchor = &pFly->GetAnchor();
421 if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
422 {
423 SwFormatAnchor anchor(*pAnchor);
424 anchor.SetAnchor( rPam.GetMark() );
425 pFly->SetFormatAttr(anchor);
426 }
427 }
428 }
429 }
430
431 if (m_pTextFormatColl && rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl))
432 {
433 SwTextNode* pTextNd = rPam.GetMark()->GetNode().GetTextNode();
434 if( pTextNd )
435 pTextNd->ChgFormatColl( m_pTextFormatColl );
436 }
437 m_pTextFormatColl = pSavTextFormatColl;
438
439 if (m_pLastNodeColl && rDoc.GetTextFormatColls()->IsAlive(m_pLastNodeColl)
440 && rPam.GetPoint()->GetNode() != rPam.GetMark()->GetNode())
441 {
442 SwTextNode* pTextNd = rPam.GetPoint()->GetNode().GetTextNode();
443 if( pTextNd )
444 pTextNd->ChgFormatColl( m_pLastNodeColl );
445 }
446
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 );
450
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.
458
459 for (size_t n = 0; m_FlyUndos.size() > n; ++n)
460 {
461 m_FlyUndos[n]->RedoImpl(rContext);
462 }
463
464 if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
465 {
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 );
470 }
471 else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) &&
472 !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
473 rDoc.getIDocumentRedlineAccess().SplitRedline(rPam);
474 }
475
RepeatImpl(::sw::RepeatContext & rContext)476 void SwUndoInserts::RepeatImpl(::sw::RepeatContext & rContext)
477 {
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);
482 }
483
SwUndoInsDoc(const SwPaM & rPam)484 SwUndoInsDoc::SwUndoInsDoc( const SwPaM& rPam )
485 : SwUndoInserts( SwUndoId::INSDOKUMENT, rPam )
486 {
487 }
488
SwUndoCpyDoc(const SwPaM & rPam)489 SwUndoCpyDoc::SwUndoCpyDoc( const SwPaM& rPam )
490 : SwUndoInserts( SwUndoId::COPY, rPam )
491 {
492 }
493
494 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
495