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 #include <DocumentContentOperationsManager.hxx>
20 #include <DocumentRedlineManager.hxx>
21 #include <wrtsh.hxx>
22 #include <doc.hxx>
23 #include <IDocumentUndoRedo.hxx>
24 #include <IDocumentMarkAccess.hxx>
25 #include <IDocumentState.hxx>
26 #include <IDocumentLayoutAccess.hxx>
27 #include <IDocumentRedlineAccess.hxx>
28 #include <IDocumentStylePoolAccess.hxx>
29 #include <IDocumentSettingAccess.hxx>
30 #include <UndoManager.hxx>
31 #include <docary.hxx>
32 #include <pamtyp.hxx>
33 #include <textboxhelper.hxx>
34 #include <dcontact.hxx>
35 #include <grfatr.hxx>
36 #include <numrule.hxx>
37 #include <charfmt.hxx>
38 #include <ndgrf.hxx>
39 #include <ndnotxt.hxx>
40 #include <ndole.hxx>
41 #include <breakit.hxx>
42 #include <frmfmt.hxx>
43 #include <fmtanchr.hxx>
44 #include <fmtcntnt.hxx>
45 #include <fmtinfmt.hxx>
46 #include <fmtpdsc.hxx>
47 #include <fmtcnct.hxx>
48 #include <SwStyleNameMapper.hxx>
49 #include <redline.hxx>
50 #include <txtfrm.hxx>
51 #include <rootfrm.hxx>
52 #include <frmtool.hxx>
53 #include <unocrsr.hxx>
54 #include <mvsave.hxx>
55 #include <ndtxt.hxx>
56 #include <poolfmt.hxx>
57 #include <paratr.hxx>
58 #include <txatbase.hxx>
59 #include <UndoRedline.hxx>
60 #include <undobj.hxx>
61 #include <UndoBookmark.hxx>
62 #include <UndoDelete.hxx>
63 #include <UndoSplitMove.hxx>
64 #include <UndoOverwrite.hxx>
65 #include <UndoInsert.hxx>
66 #include <UndoAttribute.hxx>
67 #include <rolbck.hxx>
68 #include <acorrect.hxx>
69 #include <bookmark.hxx>
70 #include <ftnidx.hxx>
71 #include <txtftn.hxx>
72 #include <hints.hxx>
73 #include <fmtflcnt.hxx>
74 #include <docedt.hxx>
75 #include <frameformats.hxx>
76 #include <annotationmark.hxx>
77 #include <formatflysplit.hxx>
78 #include <istyleaccess.hxx>
79 #include <o3tl/safeint.hxx>
80 #include <sal/log.hxx>
81 #include <unotools/charclass.hxx>
82 #include <unotools/configmgr.hxx>
83 #include <unotools/transliterationwrapper.hxx>
84 #include <i18nutil/transliteration.hxx>
85 #include <sfx2/Metadatable.hxx>
86 #include <sot/exchange.hxx>
87 #include <svl/stritem.hxx>
88 #include <svl/itemiter.hxx>
89 #include <svx/svdobj.hxx>
90 #include <svx/svdouno.hxx>
91 #include <tools/globname.hxx>
92 #include <editeng/formatbreakitem.hxx>
93 #include <com/sun/star/i18n/Boundary.hpp>
94 #include <com/sun/star/i18n/WordType.hpp>
95 #include <com/sun/star/i18n/XBreakIterator.hpp>
96 #include <com/sun/star/embed/XEmbeddedObject.hpp>
97
98 #include <tuple>
99 #include <memory>
100 #include <optional>
101
102 using namespace ::com::sun::star::i18n;
103
104 namespace
105 {
106 // Copy method from SwDoc
107 // Prevent copying into Flys that are anchored in the range
lcl_ChkFlyFly(SwDoc & rDoc,SwNodeOffset nSttNd,SwNodeOffset nEndNd,SwNodeOffset nInsNd)108 bool lcl_ChkFlyFly( SwDoc& rDoc, SwNodeOffset nSttNd, SwNodeOffset nEndNd,
109 SwNodeOffset nInsNd )
110 {
111
112 for(sw::SpzFrameFormat* pFormat: *rDoc.GetSpzFrameFormats())
113 {
114 SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
115 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
116 if (pAnchorNode &&
117 ((RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) ||
118 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) ||
119 (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId()) ||
120 (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId())) &&
121 nSttNd <= pAnchorNode->GetIndex() &&
122 pAnchorNode->GetIndex() < nEndNd )
123 {
124 const SwFormatContent& rContent = pFormat->GetContent();
125 SwStartNode* pSNd;
126 if( !rContent.GetContentIdx() ||
127 nullptr == ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ))
128 continue;
129
130 if( pSNd->GetIndex() < nInsNd &&
131 nInsNd < pSNd->EndOfSectionIndex() )
132 // Do not copy !
133 return true;
134
135 if( lcl_ChkFlyFly( rDoc, pSNd->GetIndex(),
136 pSNd->EndOfSectionIndex(), nInsNd ) )
137 // Do not copy !
138 return true;
139 }
140 }
141
142 return false;
143 }
144
InitDelCount(SwPaM const & rSourcePaM,SwNodeOffset & rDelCount)145 SwNodeIndex InitDelCount(SwPaM const& rSourcePaM, SwNodeOffset & rDelCount)
146 {
147 SwPosition const& rStart(*rSourcePaM.Start());
148 // Special handling for SwDoc::AppendDoc
149 if (rSourcePaM.GetDoc().GetNodes().GetEndOfExtras().GetIndex() + 1
150 == rStart.GetNodeIndex())
151 {
152 rDelCount = SwNodeOffset(1);
153 return SwNodeIndex(rStart.GetNode(), +1);
154 }
155 else
156 {
157 rDelCount = SwNodeOffset(0);
158 return SwNodeIndex(rStart.GetNode());
159 }
160 }
161
162 /*
163 The CopyBookmarks function has to copy bookmarks from the source to the destination nodes
164 array. It is called after a call of the CopyNodes(..) function. But this function does not copy
165 every node (at least at the moment: 2/08/2006 ), section start and end nodes will not be copied
166 if the corresponding end/start node is outside the copied pam.
167 The lcl_NonCopyCount function counts the number of these nodes, given the copied pam and a node
168 index inside the pam.
169 rPam is the original source pam, rLastIdx is the last calculated position, rDelCount the number
170 of "non-copy" nodes between rPam.Start() and rLastIdx.
171 nNewIdx is the new position of interest.
172 */
lcl_NonCopyCount(const SwPaM & rPam,SwNodeIndex & rLastIdx,const SwNodeOffset nNewIdx,SwNodeOffset & rDelCount)173 void lcl_NonCopyCount( const SwPaM& rPam, SwNodeIndex& rLastIdx, const SwNodeOffset nNewIdx, SwNodeOffset& rDelCount )
174 {
175 SwNodeOffset nStart = rPam.Start()->GetNodeIndex();
176 SwNodeOffset nEnd = rPam.End()->GetNodeIndex();
177 if( rLastIdx.GetIndex() < nNewIdx ) // Moving forward?
178 {
179 // We never copy the StartOfContent node
180 do // count "non-copy" nodes
181 {
182 SwNode& rNode = rLastIdx.GetNode();
183 if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd )
184 || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) )
185 {
186 ++rDelCount;
187 }
188 ++rLastIdx;
189 }
190 while( rLastIdx.GetIndex() < nNewIdx );
191 }
192 else if( rDelCount ) // optimization: if there are no "non-copy" nodes until now,
193 // no move backward needed
194 {
195 while( rLastIdx.GetIndex() > nNewIdx )
196 {
197 SwNode& rNode = rLastIdx.GetNode();
198 if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd )
199 || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) )
200 {
201 --rDelCount;
202 }
203 --rLastIdx;
204 }
205 }
206 }
207
lcl_SetCpyPos(const SwPosition & rOrigPos,const SwPosition & rOrigStt,const SwPosition & rCpyStt,SwPosition & rChgPos,SwNodeOffset nDelCount)208 void lcl_SetCpyPos( const SwPosition& rOrigPos,
209 const SwPosition& rOrigStt,
210 const SwPosition& rCpyStt,
211 SwPosition& rChgPos,
212 SwNodeOffset nDelCount )
213 {
214 SwNodeOffset nNdOff = rOrigPos.GetNodeIndex();
215 nNdOff -= rOrigStt.GetNodeIndex();
216 nNdOff -= nDelCount;
217 sal_Int32 nContentPos = rOrigPos.GetContentIndex();
218
219 // Always adjust <nNode> at to be changed <SwPosition> instance <rChgPos>
220 rChgPos.Assign( nNdOff + rCpyStt.GetNodeIndex() );
221 if (!rChgPos.GetNode().GetContentNode())
222 return;
223 if( !nNdOff )
224 {
225 // just adapt the content index
226 if( nContentPos > rOrigStt.GetContentIndex() )
227 nContentPos -= rOrigStt.GetContentIndex();
228 else
229 nContentPos = 0;
230 nContentPos += rCpyStt.GetContentIndex();
231 }
232 rChgPos.SetContent( nContentPos );
233 }
234 }
235
236 namespace sw
237 {
238 // TODO: use SaveBookmark (from DelBookmarks)
CopyBookmarks(const SwPaM & rPam,const SwPosition & rCpyPam,SwCopyFlags flags)239 void CopyBookmarks(const SwPaM& rPam, const SwPosition& rCpyPam, SwCopyFlags flags)
240 {
241 const SwDoc& rSrcDoc = rPam.GetDoc();
242 SwDoc& rDestDoc = rCpyPam.GetDoc();
243 const IDocumentMarkAccess* const pSrcMarkAccess = rSrcDoc.getIDocumentMarkAccess();
244 ::sw::UndoGuard const undoGuard(rDestDoc.GetIDocumentUndoRedo());
245
246 const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End();
247 SwPosition const*const pCpyStt = &rCpyPam;
248
249 std::vector< const ::sw::mark::MarkBase* > vMarksToCopy;
250 for ( auto ppMark = pSrcMarkAccess->getAllMarksBegin();
251 ppMark != pSrcMarkAccess->getAllMarksEnd();
252 ++ppMark )
253 {
254 const ::sw::mark::MarkBase* const pMark = *ppMark;
255
256 auto [/*const SwPosition&*/ rMarkStart, rMarkEnd] = pMark->GetMarkStartEnd();
257 // only include marks that are in the range and not touching both start and end
258 // - not for annotation or checkbox marks.
259 bool const isIncludeStart(
260 (rStt.GetContentIndex() == 0 // paragraph start selected?
261 // also: only if inserting at the start - cross reference
262 // marks require index to be 0, and there could be one
263 // on the target node already
264 && rCpyPam.GetContentIndex() == 0)
265 || rMarkStart != rStt);
266 bool const isIncludeEnd(
267 (rEnd.GetNode().IsTextNode() // paragraph end selected?
268 && rEnd.GetContentIndex() == rEnd.GetNode().GetTextNode()->Len())
269 || rMarkEnd != rEnd);
270 const bool bIsNotOnBoundary =
271 pMark->IsExpanded()
272 ? (isIncludeStart || isIncludeEnd) // rMarkStart != rMarkEnd
273 : (isIncludeStart && isIncludeEnd); // rMarkStart == rMarkEnd
274 const IDocumentMarkAccess::MarkType aMarkType = IDocumentMarkAccess::GetType(*pMark);
275 if ( rMarkStart >= rStt && rMarkEnd <= rEnd
276 && ( bIsNotOnBoundary
277 || aMarkType == IDocumentMarkAccess::MarkType::ANNOTATIONMARK
278 || aMarkType == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK
279 || aMarkType == IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK
280 || aMarkType == IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK
281 || aMarkType == IDocumentMarkAccess::MarkType::DATE_FIELDMARK))
282 {
283 vMarksToCopy.push_back(pMark);
284 }
285 }
286 // We have to count the "non-copied" nodes...
287 SwNodeOffset nDelCount;
288 SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount));
289 for(const sw::mark::MarkBase* const pMark : vMarksToCopy)
290 {
291 SwPaM aTmpPam(*pCpyStt);
292 lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetMarkPos().GetNodeIndex(), nDelCount);
293 lcl_SetCpyPos( pMark->GetMarkPos(), rStt, *pCpyStt, *aTmpPam.GetPoint(), nDelCount);
294 if(pMark->IsExpanded())
295 {
296 aTmpPam.SetMark();
297 lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetOtherMarkPos().GetNodeIndex(), nDelCount);
298 lcl_SetCpyPos(pMark->GetOtherMarkPos(), rStt, *pCpyStt, *aTmpPam.GetMark(), nDelCount);
299 }
300
301 SwMarkName sRequestedName = pMark->GetName();
302 if (flags & SwCopyFlags::IsMoveToFly)
303 {
304 assert(&rSrcDoc == &rDestDoc);
305 // Ensure the name can be given to NewMark, since this is ultimately a move
306 auto pSoonToBeDeletedMark = const_cast<sw::mark::MarkBase*>(pMark);
307 rDestDoc.getIDocumentMarkAccess()->renameMark(pSoonToBeDeletedMark,
308 SwMarkName(sRequestedName.toString() + "COPY_IS_MOVE"));
309 }
310
311 ::sw::mark::MarkBase* const pNewMark = rDestDoc.getIDocumentMarkAccess()->makeMark(
312 aTmpPam,
313 sRequestedName,
314 IDocumentMarkAccess::GetType(*pMark),
315 ::sw::mark::InsertMode::CopyText);
316 // Explicitly try to get exactly the same name as in the source
317 // because NavigatorReminders, DdeBookmarks etc. ignore the proposed name
318 if (pNewMark == nullptr)
319 {
320 assert(IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK
321 || IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK);
322 continue; // can't insert duplicate cross reference mark
323 }
324 rDestDoc.getIDocumentMarkAccess()->renameMark(pNewMark, sRequestedName);
325
326 // copying additional attributes for bookmarks or fieldmarks
327 ::sw::mark::Bookmark* const pNewBookmark =
328 dynamic_cast< ::sw::mark::Bookmark* const >(pNewMark);
329 const ::sw::mark::Bookmark* const pOldBookmark =
330 dynamic_cast< const ::sw::mark::Bookmark* >(pMark);
331 if (pNewBookmark && pOldBookmark)
332 {
333 pNewBookmark->SetKeyCode(pOldBookmark->GetKeyCode());
334 pNewBookmark->SetShortName(pOldBookmark->GetShortName());
335 pNewBookmark->Hide(pOldBookmark->IsHidden());
336 pNewBookmark->SetHideCondition(pOldBookmark->GetHideCondition());
337 }
338 ::sw::mark::Fieldmark* const pNewFieldmark =
339 dynamic_cast< ::sw::mark::Fieldmark* const >(pNewMark);
340 const ::sw::mark::Fieldmark* const pOldFieldmark =
341 dynamic_cast< const ::sw::mark::Fieldmark* >(pMark);
342 if (pNewFieldmark && pOldFieldmark)
343 {
344 pNewFieldmark->SetFieldname(pOldFieldmark->GetFieldname());
345 pNewFieldmark->SetFieldHelptext(pOldFieldmark->GetFieldHelptext());
346 ::sw::mark::Fieldmark::parameter_map_t* pNewParams = pNewFieldmark->GetParameters();
347 const ::sw::mark::Fieldmark::parameter_map_t* pOldParams = pOldFieldmark->GetParameters();
348 for (const auto& rEntry : *pOldParams )
349 {
350 pNewParams->insert( rEntry );
351 }
352 }
353
354 ::sfx2::Metadatable const*const pMetadatable(
355 dynamic_cast< ::sfx2::Metadatable const* >(pMark));
356 ::sfx2::Metadatable *const pNewMetadatable(
357 dynamic_cast< ::sfx2::Metadatable * >(pNewMark));
358 if (pMetadatable && pNewMetadatable)
359 {
360 pNewMetadatable->RegisterAsCopyOf(*pMetadatable);
361 }
362 }
363 }
364 } // namespace sw
365
366 namespace
367 {
lcl_DeleteRedlines(const SwPaM & rPam,SwPaM & rCpyPam)368 void lcl_DeleteRedlines( const SwPaM& rPam, SwPaM& rCpyPam )
369 {
370 const SwDoc& rSrcDoc = rPam.GetDoc();
371 const SwRedlineTable& rTable = rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable();
372 if( rTable.empty() )
373 return;
374
375 SwDoc& rDestDoc = rCpyPam.GetDoc();
376 SwPosition* pCpyStt = rCpyPam.Start(), *pCpyEnd = rCpyPam.End();
377 std::unique_ptr<SwPaM> pDelPam;
378 auto [pStart, pEnd] = rPam.StartEnd(); // SwPosition*
379 // We have to count the "non-copied" nodes
380 SwNodeOffset nDelCount;
381 SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount));
382
383 SwRedlineTable::size_type n = 0;
384 rSrcDoc.getIDocumentRedlineAccess().GetRedline( *pStart, &n );
385 for( ; n < rTable.size(); ++n )
386 {
387 const SwRangeRedline* pRedl = rTable[ n ];
388 if( RedlineType::Delete == pRedl->GetType() && pRedl->IsVisible() )
389 {
390 auto [pRStt, pREnd] = pRedl->StartEnd(); // SwPosition*
391
392 SwComparePosition eCmpPos = ComparePosition( *pStart, *pEnd, *pRStt, *pREnd );
393 switch( eCmpPos )
394 {
395 case SwComparePosition::CollideEnd:
396 case SwComparePosition::Before:
397 // Pos1 is before Pos2
398 break;
399
400 case SwComparePosition::CollideStart:
401 case SwComparePosition::Behind:
402 // Pos1 is after Pos2
403 n = rTable.size();
404 break;
405
406 default:
407 {
408 pDelPam.reset(new SwPaM( *pCpyStt, pDelPam.release() ));
409 if( *pStart < *pRStt )
410 {
411 lcl_NonCopyCount( rPam, aCorrIdx, pRStt->GetNodeIndex(), nDelCount );
412 lcl_SetCpyPos( *pRStt, *pStart, *pCpyStt,
413 *pDelPam->GetPoint(), nDelCount );
414 }
415 pDelPam->SetMark();
416
417 if( *pEnd < *pREnd )
418 *pDelPam->GetPoint() = *pCpyEnd;
419 else
420 {
421 lcl_NonCopyCount( rPam, aCorrIdx, pREnd->GetNodeIndex(), nDelCount );
422 lcl_SetCpyPos( *pREnd, *pStart, *pCpyStt,
423 *pDelPam->GetPoint(), nDelCount );
424 }
425
426 if (pDelPam->GetNext() != pDelPam.get()
427 && *pDelPam->GetNext()->End() == *pDelPam->Start())
428 {
429 *pDelPam->GetNext()->End() = *pDelPam->End();
430 pDelPam.reset(pDelPam->GetNext());
431 }
432 }
433 }
434 }
435 }
436
437 if( !pDelPam )
438 return;
439
440 RedlineFlags eOld = rDestDoc.getIDocumentRedlineAccess().GetRedlineFlags();
441 rDestDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::Ignore );
442
443 ::sw::UndoGuard const undoGuard(rDestDoc.GetIDocumentUndoRedo());
444
445 // At this point, pDelPam points to the last of maybe several disjoint selections, organized
446 // in reverse order in document (so every GetNext() returns a PaM closer to document start,
447 // until wrap to pDelPam). Removal of the selections must be from last in document to first,
448 // to avoid situations when another PaM in chain points into the node that will be destroyed
449 // (joined to previous) by removal of the currently processed PaM.
450 do {
451 rDestDoc.getIDocumentContentOperations().DeleteAndJoin(*pDelPam);
452 if( !pDelPam->IsMultiSelection() )
453 break;
454 pDelPam.reset(pDelPam->GetNext());
455 } while( true );
456
457 rDestDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
458 }
459
lcl_DeleteRedlines(const SwNodeRange & rRg,SwNodeRange const & rCpyRg)460 void lcl_DeleteRedlines( const SwNodeRange& rRg, SwNodeRange const & rCpyRg )
461 {
462 SwDoc& rSrcDoc = rRg.aStart.GetNode().GetDoc();
463 if( !rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
464 {
465 SwPaM aRgTmp( rRg.aStart, rRg.aEnd );
466 SwPaM aCpyTmp( rCpyRg.aStart, rCpyRg.aEnd );
467 lcl_DeleteRedlines( aRgTmp, aCpyTmp );
468 }
469 }
470
lcl_ChainFormats(SwFlyFrameFormat * pSrc,SwFlyFrameFormat * pDest)471 void lcl_ChainFormats( SwFlyFrameFormat *pSrc, SwFlyFrameFormat *pDest )
472 {
473 SwFormatChain aSrc( pSrc->GetChain() );
474 if ( !aSrc.GetNext() )
475 {
476 aSrc.SetNext( pDest );
477 pSrc->SetFormatAttr( aSrc );
478 }
479 SwFormatChain aDest( pDest->GetChain() );
480 if ( !aDest.GetPrev() )
481 {
482 aDest.SetPrev( pSrc );
483 pDest->SetFormatAttr( aDest );
484 }
485 }
486
487 // #i86492#
lcl_ShouldKeepSourceList(const SwPaM & rPam)488 bool lcl_ShouldKeepSourceList( const SwPaM& rPam )
489 {
490 const SwTextNode* pTextNd = rPam.Start()->GetNode().GetTextNode();
491 const SwTextNode* pEndTextNd = rPam.End()->GetNode().GetTextNode();
492 if (pTextNd == nullptr || pEndTextNd == nullptr)
493 return false;
494 bool bRet = pTextNd->IsInListFromStyle();
495 //single paragraphs are preferred only if it's a has a list from style
496 if (pTextNd == pEndTextNd)
497 return bRet;
498
499 if (pTextNd && pTextNd->IsInList() && !pTextNd->IsInListFromStyle() &&
500 pEndTextNd && pEndTextNd->IsInList() && !pEndTextNd->IsInListFromStyle())
501 {
502 bRet = false;
503 SwNodeIndex aIdx(rPam.Start()->GetNode());
504 do
505 {
506 ++aIdx;
507 pTextNd = aIdx.GetNode().GetTextNode();
508
509 if (!pTextNd || !pTextNd->IsInList() || pTextNd->IsInListFromStyle())
510 {
511 bRet = true;
512 break;
513 }
514 } while (pTextNd != pEndTextNd);
515 }
516
517 return bRet;
518 }
519
lcl_MarksWholeNode(const SwPaM & rPam)520 bool lcl_MarksWholeNode(const SwPaM & rPam)
521 {
522 bool bResult = false;
523 auto [pStart, pEnd] = rPam.StartEnd(); // SwPosition*
524
525 if (nullptr != pStart && nullptr != pEnd)
526 {
527 const SwTextNode* pSttNd = pStart->GetNode().GetTextNode();
528 const SwTextNode* pEndNd = pEnd->GetNode().GetTextNode();
529
530 if (nullptr != pSttNd && nullptr != pEndNd &&
531 pStart->GetContentIndex() == 0 &&
532 pEnd->GetContentIndex() == pEndNd->Len())
533 {
534 bResult = true;
535 }
536 }
537
538 return bResult;
539 }
540 }
541
542 //local functions originally from sw/source/core/doc/docedt.cxx
543 namespace sw
544 {
CalcBreaks(std::vector<std::pair<SwNodeOffset,sal_Int32>> & rBreaks,SwPaM const & rPam,bool const isOnlyFieldmarks)545 void CalcBreaks(std::vector<std::pair<SwNodeOffset, sal_Int32>> & rBreaks,
546 SwPaM const & rPam, bool const isOnlyFieldmarks)
547 {
548 SwNodeOffset const nStartNode(rPam.Start()->GetNodeIndex());
549 SwNodeOffset const nEndNode(rPam.End()->GetNodeIndex());
550 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
551 IDocumentMarkAccess const& rIDMA(*rPam.GetDoc().getIDocumentMarkAccess());
552
553 std::stack<std::tuple<sw::mark::Fieldmark const*, bool, SwNodeOffset, sal_Int32>> startedFields;
554
555 for (SwNodeOffset n = nStartNode; n <= nEndNode; ++n)
556 {
557 SwNode *const pNode(rNodes[n]);
558 if (pNode->IsTextNode())
559 {
560 SwTextNode & rTextNode(*pNode->GetTextNode());
561 sal_Int32 const nStart(n == nStartNode
562 ? rPam.Start()->GetContentIndex()
563 : 0);
564 sal_Int32 const nEnd(n == nEndNode
565 ? rPam.End()->GetContentIndex()
566 : rTextNode.Len());
567 for (sal_Int32 i = nStart; i < nEnd; ++i)
568 {
569 const sal_Unicode c(rTextNode.GetText()[i]);
570 switch (c)
571 {
572 // note: CH_TXT_ATR_FORMELEMENT does not need handling
573 // not sure how CH_TXT_ATR_INPUTFIELDSTART/END are currently handled
574 case CH_TXTATR_INWORD:
575 case CH_TXTATR_BREAKWORD:
576 {
577 // META hints only have dummy char at the start, not
578 // at the end, so no need to check in nStartNode
579 if (n == nEndNode && !isOnlyFieldmarks)
580 {
581 SwTextAttr const* pAttr(rTextNode.GetTextAttrForCharAt(i));
582 if (pAttr && pAttr->End() && (nEnd < *pAttr->End()))
583 {
584 assert(pAttr->HasDummyChar());
585 rBreaks.emplace_back(n, i);
586 }
587
588 if (!pAttr)
589 {
590 // See if this is an end dummy character for a content control.
591 pAttr = rTextNode.GetTextAttrForEndCharAt(i, RES_TXTATR_CONTENTCONTROL);
592 if (pAttr && (nStart > pAttr->GetStart()))
593 {
594 rBreaks.emplace_back(n, i);
595 }
596 }
597 }
598 break;
599 }
600 case CH_TXT_ATR_FIELDSTART:
601 {
602 auto const pFieldMark(rIDMA.getFieldmarkAt(SwPosition(rTextNode, i)));
603 startedFields.emplace(pFieldMark, false, 0, 0);
604 break;
605 }
606 case CH_TXT_ATR_FIELDSEP:
607 {
608 if (startedFields.empty())
609 {
610 rBreaks.emplace_back(n, i);
611 }
612 else
613 { // no way to find the field via MarkManager...
614 assert(std::get<0>(startedFields.top())->IsCoveringPosition(SwPosition(rTextNode, i)));
615 std::get<1>(startedFields.top()) = true;
616 std::get<2>(startedFields.top()) = n;
617 std::get<3>(startedFields.top()) = i;
618 }
619 break;
620 }
621 case CH_TXT_ATR_FIELDEND:
622 {
623 if (startedFields.empty())
624 {
625 rBreaks.emplace_back(n, i);
626 }
627 else
628 { // fieldmarks must not overlap => stack
629 assert(std::get<0>(startedFields.top()) == rIDMA.getFieldmarkAt(SwPosition(rTextNode, i)));
630 startedFields.pop();
631 }
632 break;
633 }
634 }
635 }
636 }
637 else if (pNode->IsStartNode())
638 {
639 if (pNode->EndOfSectionIndex() <= nEndNode)
640 { // fieldmark cannot overlap node section
641 n = pNode->EndOfSectionIndex();
642 }
643 }
644 else
645 { // EndNode can actually happen with sections :(
646 assert(pNode->IsEndNode() || pNode->IsNoTextNode());
647 }
648 }
649 while (!startedFields.empty())
650 {
651 if (const sw::mark::Fieldmark* pMark = std::get<0>(startedFields.top()))
652 {
653 SwPosition const& rStart(pMark->GetMarkStart());
654 std::pair<SwNodeOffset, sal_Int32> const pos(
655 rStart.GetNodeIndex(), rStart.GetContentIndex());
656 auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), pos);
657 assert(it == rBreaks.end() || *it != pos);
658 rBreaks.insert(it, pos);
659 }
660 if (std::get<1>(startedFields.top()))
661 {
662 std::pair<SwNodeOffset, sal_Int32> const posSep(
663 std::get<2>(startedFields.top()),
664 std::get<3>(startedFields.top()));
665 auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), posSep);
666 assert(it == rBreaks.end() || *it != posSep);
667 rBreaks.insert(it, posSep);
668 }
669 startedFields.pop();
670 }
671 }
672 }
673
674 namespace
675 {
676
lcl_DoWithBreaks(::sw::DocumentContentOperationsManager & rDocumentContentOperations,SwPaM & rPam,SwDeleteFlags const flags,bool (::sw::DocumentContentOperationsManager::* pFunc)(SwPaM &,SwDeleteFlags))677 bool lcl_DoWithBreaks(::sw::DocumentContentOperationsManager & rDocumentContentOperations,
678 SwPaM & rPam, SwDeleteFlags const flags,
679 bool (::sw::DocumentContentOperationsManager::*pFunc)(SwPaM&, SwDeleteFlags))
680 {
681 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
682
683 sw::CalcBreaks(Breaks, rPam);
684
685 if (Breaks.empty())
686 {
687 return (rDocumentContentOperations.*pFunc)(rPam, flags);
688 }
689
690 // Deletion must be split into several parts if the text node
691 // contains a text attribute with end and with dummy character
692 // and the selection does not contain the text attribute completely,
693 // but overlaps its start (left), where the dummy character is.
694
695 SwPosition const & rSelectionEnd( *rPam.End() );
696
697 bool bRet( true );
698 // iterate from end to start, to avoid invalidating the offsets!
699 auto iter( Breaks.rbegin() );
700 SwNodeOffset nOffset(0);
701 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
702 SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node!
703 SwPosition & rEnd( *aPam.End() );
704 SwPosition & rStart( *aPam.Start() );
705
706 while (iter != Breaks.rend())
707 {
708 rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
709 if (rStart < rEnd) // check if part is empty
710 {
711 bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags);
712 nOffset = iter->first - rStart.GetNodeIndex(); // deleted fly nodes...
713 }
714 rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
715 ++iter;
716 }
717
718 rStart = *rPam.Start(); // set to original start
719 if (rStart < rEnd) // check if part is empty
720 {
721 bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags);
722 }
723
724 return bRet;
725 }
726
lcl_StrLenOverflow(const SwPaM & rPam)727 bool lcl_StrLenOverflow( const SwPaM& rPam )
728 {
729 // If we try to merge two paragraphs we have to test if afterwards
730 // the string doesn't exceed the allowed string length
731 if( rPam.GetPoint()->GetNode() != rPam.GetMark()->GetNode() )
732 {
733 auto [pStart, pEnd] = rPam.StartEnd(); // SwPosition*
734 const SwTextNode* pEndNd = pEnd->GetNode().GetTextNode();
735 if( (nullptr != pEndNd) && pStart->GetNode().IsTextNode() )
736 {
737 const sal_uInt64 nSum = pStart->GetContentIndex() +
738 pEndNd->GetText().getLength() - pEnd->GetContentIndex();
739 return nSum > o3tl::make_unsigned(SAL_MAX_INT32);
740 }
741 }
742 return false;
743 }
744
745 struct SaveRedline
746 {
747 SwRangeRedline* pRedl;
748 SwNodeOffset nStt, nEnd;
749 sal_Int32 nSttCnt;
750 sal_Int32 nEndCnt;
751
SaveRedline__anon343aa8650311::SaveRedline752 SaveRedline( SwRangeRedline* pR, const SwNodeIndex& rSttIdx )
753 : pRedl(pR)
754 , nEnd(0)
755 , nEndCnt(0)
756 {
757 auto [pStart, pEnd] = pR->StartEnd(); // SwPosition*
758 SwNodeOffset nSttIdx = rSttIdx.GetIndex();
759 nStt = pStart->GetNodeIndex() - nSttIdx;
760 nSttCnt = pStart->GetContentIndex();
761 if( pR->HasMark() )
762 {
763 nEnd = pEnd->GetNodeIndex() - nSttIdx;
764 nEndCnt = pEnd->GetContentIndex();
765 }
766
767 pRedl->GetPoint()->Assign( SwNodeOffset(0) );
768 pRedl->GetMark()->Assign( SwNodeOffset(0) );
769 }
770
SaveRedline__anon343aa8650311::SaveRedline771 SaveRedline( SwRangeRedline* pR, const SwPosition& rPos )
772 : pRedl(pR)
773 , nEnd(0)
774 , nEndCnt(0)
775 {
776 auto [pStart, pEnd] = pR->StartEnd(); // SwPosition*
777 SwNodeOffset nSttIdx = rPos.GetNodeIndex();
778 nStt = pStart->GetNodeIndex() - nSttIdx;
779 nSttCnt = pStart->GetContentIndex();
780 if( nStt == SwNodeOffset(0) )
781 nSttCnt = nSttCnt - rPos.GetContentIndex();
782 if( pR->HasMark() )
783 {
784 nEnd = pEnd->GetNodeIndex() - nSttIdx;
785 nEndCnt = pEnd->GetContentIndex();
786 if( nEnd == SwNodeOffset(0) )
787 nEndCnt = nEndCnt - rPos.GetContentIndex();
788 }
789
790 pRedl->GetPoint()->Assign( SwNodeOffset(0) );
791 pRedl->GetMark()->Assign( SwNodeOffset(0) );
792 }
793
SetPos__anon343aa8650311::SaveRedline794 void SetPos( SwNodeOffset nInsPos )
795 {
796 pRedl->GetPoint()->Assign( nInsPos + nStt, nSttCnt );
797 if( pRedl->HasMark() )
798 {
799 pRedl->GetMark()->Assign( nInsPos + nEnd, nEndCnt );
800 }
801 }
802
SetPos__anon343aa8650311::SaveRedline803 void SetPos( const SwPosition& aPos )
804 {
805 pRedl->GetPoint()->Assign( aPos.GetNodeIndex() + nStt,
806 nSttCnt + ( nStt == SwNodeOffset(0) ? aPos.GetContentIndex() : 0 ) );
807 if( pRedl->HasMark() )
808 {
809 pRedl->GetMark()->Assign( aPos.GetNodeIndex() + nEnd,
810 nEndCnt + ( nEnd == SwNodeOffset(0) ? aPos.GetContentIndex() : 0 ) );
811 }
812 }
813 };
814
815 typedef std::vector< SaveRedline > SaveRedlines_t;
816
lcl_SaveRedlines(const SwPaM & aPam,SaveRedlines_t & rArr)817 void lcl_SaveRedlines(const SwPaM& aPam, SaveRedlines_t& rArr)
818 {
819 SwDoc& rDoc = aPam.GetPointNode().GetDoc();
820
821 auto [pStart, pEnd] = aPam.StartEnd(); // SwPosition*
822
823 // get first relevant redline
824 SwRedlineTable::size_type nCurrentRedline;
825 rDoc.getIDocumentRedlineAccess().GetRedline( *pStart, &nCurrentRedline );
826 if( nCurrentRedline > 0)
827 nCurrentRedline--;
828
829 // redline mode RedlineFlags::Ignore|RedlineFlags::On; save old mode
830 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
831 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
832
833 // iterate over relevant redlines and decide for each whether it should
834 // be saved, or split + saved
835 SwRedlineTable& rRedlineTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
836 for( ; nCurrentRedline < rRedlineTable.size(); )
837 {
838 SwRangeRedline* pCurrent = rRedlineTable[ nCurrentRedline ];
839 SwComparePosition eCompare =
840 ComparePosition( *pCurrent->Start(), *pCurrent->End(),
841 *pStart, *pEnd);
842
843 // we must save this redline if it overlaps aPam
844 // (we may have to split it, too)
845 if( eCompare == SwComparePosition::OverlapBehind ||
846 eCompare == SwComparePosition::OverlapBefore ||
847 eCompare == SwComparePosition::Outside ||
848 eCompare == SwComparePosition::Inside ||
849 eCompare == SwComparePosition::Equal )
850 {
851 rRedlineTable.Remove( nCurrentRedline );
852
853 // split beginning, if necessary
854 if( eCompare == SwComparePosition::OverlapBefore ||
855 eCompare == SwComparePosition::Outside )
856 {
857 SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent );
858 *pNewRedline->End() = *pStart;
859 *pCurrent->Start() = *pStart;
860 rDoc.getIDocumentRedlineAccess().AppendRedline( pNewRedline, true );
861 }
862
863 // split end, if necessary
864 if( eCompare == SwComparePosition::OverlapBehind ||
865 eCompare == SwComparePosition::Outside )
866 {
867 SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent );
868 *pNewRedline->Start() = *pEnd;
869 *pCurrent->End() = *pEnd;
870 rDoc.getIDocumentRedlineAccess().AppendRedline( pNewRedline, true );
871 }
872
873 // save the current redline
874 rArr.emplace_back( pCurrent, *pStart );
875 }
876 else
877 nCurrentRedline++;
878 }
879
880 // restore old redline mode
881 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
882 }
883
lcl_RestoreRedlines(SwDoc & rDoc,const SwPosition & rPos,SaveRedlines_t & rArr)884 void lcl_RestoreRedlines(SwDoc& rDoc, const SwPosition& rPos, SaveRedlines_t& rArr)
885 {
886 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
887 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
888
889 for(SaveRedline & rSvRedLine : rArr)
890 {
891 rSvRedLine.SetPos( rPos );
892 rDoc.getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true );
893 }
894
895 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
896 }
897
lcl_SaveRedlines(const SwNodeRange & rRg,SaveRedlines_t & rArr)898 void lcl_SaveRedlines(const SwNodeRange& rRg, SaveRedlines_t& rArr)
899 {
900 SwDoc& rDoc = rRg.aStart.GetNode().GetDoc();
901 SwRedlineTable& rRedlTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
902 SwRedlineTable::size_type nRedlPos;
903 SwPosition aSrchPos( rRg.aStart );
904 aSrchPos.Adjust(SwNodeOffset(-1));
905 if( rDoc.getIDocumentRedlineAccess().GetRedline( aSrchPos, &nRedlPos ) && nRedlPos )
906 --nRedlPos;
907 else if( nRedlPos >= rRedlTable.size() )
908 return ;
909
910 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
911 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
912
913 do {
914 SwRangeRedline* pTmp = rRedlTable[ nRedlPos ];
915
916 auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition*
917
918 if( pRStt->GetNode() < rRg.aStart.GetNode() )
919 {
920 if( pREnd->GetNode() > rRg.aStart.GetNode() && pREnd->GetNode() < rRg.aEnd.GetNode() )
921 {
922 // Create a copy and set the end of the original to the end of the MoveArea.
923 // The copy is moved too.
924 SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp );
925 SwPosition* pTmpPos = pNewRedl->Start();
926 pTmpPos->Assign(rRg.aStart);
927
928 rArr.emplace_back(pNewRedl, rRg.aStart);
929
930 pTmpPos = pTmp->End();
931 pTmpPos->Assign(rRg.aEnd);
932 }
933 else if( pREnd->GetNode() == rRg.aStart.GetNode() )
934 {
935 SwPosition* pTmpPos = pTmp->End();
936 pTmpPos->Assign(rRg.aEnd);
937 }
938 ++nRedlPos;
939 }
940 else if( pRStt->GetNode() < rRg.aEnd.GetNode() )
941 {
942 rRedlTable.Remove( nRedlPos );
943 if( pREnd->GetNode() < rRg.aEnd.GetNode() ||
944 ( pREnd->GetNode() == rRg.aEnd.GetNode() && !pREnd->GetContentIndex()) )
945 {
946 // move everything
947 rArr.emplace_back( pTmp, rRg.aStart );
948 }
949 else
950 {
951 // split
952 SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp );
953 SwPosition* pTmpPos = pNewRedl->End();
954 pTmpPos->Assign(rRg.aEnd);
955
956 rArr.emplace_back( pNewRedl, rRg.aStart );
957
958 pTmpPos = pTmp->Start();
959 pTmpPos->Assign(rRg.aEnd);
960 rDoc.getIDocumentRedlineAccess().AppendRedline( pTmp, true );
961 }
962 }
963 else
964 break;
965 } while( nRedlPos < rRedlTable.size() );
966 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
967 }
968
lcl_RestoreRedlines(SwDoc & rDoc,SwNodeOffset const nInsPos,SaveRedlines_t & rArr)969 void lcl_RestoreRedlines(SwDoc& rDoc, SwNodeOffset const nInsPos, SaveRedlines_t& rArr)
970 {
971 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
972 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
973
974 for(SaveRedline & rSvRedLine : rArr)
975 {
976 rSvRedLine.SetPos( nInsPos );
977 IDocumentRedlineAccess::AppendResult const result(
978 rDoc.getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ));
979 if ( IDocumentRedlineAccess::AppendResult::APPENDED == result &&
980 rSvRedLine.pRedl->GetType() == RedlineType::Delete )
981 {
982 UpdateFramesForAddDeleteRedline(rDoc, *rSvRedLine.pRedl);
983 }
984 }
985
986 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
987 }
988
lcl_SaveFootnote(const SwNode & rSttNd,const SwNode & rEndNd,const SwNode & rInsPos,SwFootnoteIdxs & rFootnoteArr,SwFootnoteIdxs & rSaveArr,std::optional<sal_Int32> oSttCnt=std::nullopt,std::optional<sal_Int32> oEndCnt=std::nullopt)989 bool lcl_SaveFootnote( const SwNode& rSttNd, const SwNode& rEndNd,
990 const SwNode& rInsPos,
991 SwFootnoteIdxs& rFootnoteArr, SwFootnoteIdxs& rSaveArr,
992 std::optional<sal_Int32> oSttCnt = std::nullopt, std::optional<sal_Int32> oEndCnt = std::nullopt )
993 {
994 bool bUpdateFootnote = false;
995 const SwNodes& rNds = rInsPos.GetNodes();
996 const bool bDelFootnote = rInsPos.GetIndex() < rNds.GetEndOfAutotext().GetIndex() &&
997 rSttNd.GetIndex() >= rNds.GetEndOfAutotext().GetIndex();
998 const bool bSaveFootnote = !bDelFootnote &&
999 rInsPos.GetIndex() >= rNds.GetEndOfExtras().GetIndex();
1000 if( !rFootnoteArr.empty() )
1001 {
1002
1003 size_t nPos = 0;
1004 rFootnoteArr.SeekEntry( rSttNd, &nPos );
1005 SwTextFootnote* pSrch;
1006 const SwNode* pFootnoteNd;
1007
1008 // Delete/save all that come after it
1009 while( nPos < rFootnoteArr.size() && ( pFootnoteNd =
1010 &( pSrch = rFootnoteArr[ nPos ] )->GetTextNode())->GetIndex()
1011 <= rEndNd.GetIndex() )
1012 {
1013 const sal_Int32 nFootnoteSttIdx = pSrch->GetStart();
1014 if( ( oEndCnt && oSttCnt )
1015 ? (( &rSttNd == pFootnoteNd &&
1016 *oSttCnt > nFootnoteSttIdx) ||
1017 ( &rEndNd == pFootnoteNd &&
1018 nFootnoteSttIdx >= *oEndCnt ))
1019 : ( &rEndNd == pFootnoteNd ))
1020 {
1021 ++nPos; // continue searching
1022 }
1023 else
1024 {
1025 // delete it
1026 if( bDelFootnote )
1027 {
1028 SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode());
1029 SwContentIndex aIdx( &rTextNd, nFootnoteSttIdx );
1030 rTextNd.EraseText( aIdx, 1 );
1031 }
1032 else
1033 {
1034 pSrch->DelFrames(nullptr);
1035 rFootnoteArr.erase( rFootnoteArr.begin() + nPos );
1036 if( bSaveFootnote )
1037 rSaveArr.insert( pSrch );
1038 }
1039 bUpdateFootnote = true;
1040 }
1041 }
1042
1043 while (nPos > 0)
1044 {
1045 nPos--;
1046 pSrch = rFootnoteArr[ nPos ];
1047 pFootnoteNd = &pSrch->GetTextNode();
1048 if (pFootnoteNd->GetIndex() < rSttNd.GetIndex())
1049 break;
1050 const sal_Int32 nFootnoteSttIdx = pSrch->GetStart();
1051 if( !oEndCnt || !oSttCnt ||
1052 ! (( &rSttNd == pFootnoteNd &&
1053 *oSttCnt > nFootnoteSttIdx ) ||
1054 ( &rEndNd == pFootnoteNd &&
1055 nFootnoteSttIdx >= *oEndCnt )) )
1056 {
1057 if( bDelFootnote )
1058 {
1059 // delete it
1060 SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode());
1061 SwContentIndex aIdx( &rTextNd, nFootnoteSttIdx );
1062 rTextNd.EraseText( aIdx, 1 );
1063 }
1064 else
1065 {
1066 pSrch->DelFrames(nullptr);
1067 rFootnoteArr.erase( rFootnoteArr.begin() + nPos );
1068 if( bSaveFootnote )
1069 rSaveArr.insert( pSrch );
1070 }
1071 bUpdateFootnote = true;
1072 }
1073 }
1074 }
1075 // When moving from redline section into document content section, e.g.
1076 // after loading a document with (delete-)redlines, the footnote array
1077 // has to be adjusted... (#i70572)
1078 if( bSaveFootnote )
1079 {
1080 SwNodeIndex aIdx( rSttNd );
1081 while( aIdx < rEndNd ) // Check the moved section
1082 {
1083 SwNode* pNode = &aIdx.GetNode();
1084 if( pNode->IsTextNode() ) // Looking for text nodes...
1085 {
1086 SwpHints *pHints = pNode->GetTextNode()->GetpSwpHints();
1087 if( pHints && pHints->HasFootnote() ) //...with footnotes
1088 {
1089 bUpdateFootnote = true; // Heureka
1090 const size_t nCount = pHints->Count();
1091 for( size_t i = 0; i < nCount; ++i )
1092 {
1093 SwTextAttr *pAttr = pHints->Get( i );
1094 if ( pAttr->Which() == RES_TXTATR_FTN )
1095 {
1096 rSaveArr.insert( static_cast<SwTextFootnote*>(pAttr) );
1097 }
1098 }
1099 }
1100 }
1101 ++aIdx;
1102 }
1103 }
1104 return bUpdateFootnote;
1105 }
1106
lcl_MayOverwrite(const SwTextNode * pNode,const sal_Int32 nPos)1107 bool lcl_MayOverwrite( const SwTextNode *pNode, const sal_Int32 nPos )
1108 {
1109 sal_Unicode const cChr = pNode->GetText()[nPos];
1110 switch (cChr)
1111 {
1112 case CH_TXTATR_BREAKWORD:
1113 case CH_TXTATR_INWORD:
1114 return !pNode->GetTextAttrForCharAt(nPos);// how could there be none?
1115 case CH_TXT_ATR_INPUTFIELDSTART:
1116 case CH_TXT_ATR_INPUTFIELDEND:
1117 case CH_TXT_ATR_FIELDSTART:
1118 case CH_TXT_ATR_FIELDSEP:
1119 case CH_TXT_ATR_FIELDEND:
1120 case CH_TXT_ATR_FORMELEMENT:
1121 return false;
1122 default:
1123 return true;
1124 }
1125 }
1126
lcl_SkipAttr(const SwTextNode * pNode,SwPosition & rIdx,sal_Int32 & rStart)1127 void lcl_SkipAttr( const SwTextNode *pNode, SwPosition &rIdx, sal_Int32 &rStart )
1128 {
1129 if( !lcl_MayOverwrite( pNode, rStart ) )
1130 {
1131 // skip all special attributes
1132 do {
1133 rIdx.AdjustContent(+1);
1134 rStart = rIdx.GetContentIndex();
1135 } while (rStart < pNode->GetText().getLength()
1136 && !lcl_MayOverwrite(pNode, rStart) );
1137 }
1138 }
1139
lcl_GetTokenToParaBreak(OUString & rStr,OUString & rRet,bool bRegExpRplc)1140 bool lcl_GetTokenToParaBreak( OUString& rStr, OUString& rRet, bool bRegExpRplc )
1141 {
1142 if( bRegExpRplc )
1143 {
1144 sal_Int32 nPos = 0;
1145 static constexpr OUString sPara(u"\\n"_ustr);
1146 for (;;)
1147 {
1148 nPos = rStr.indexOf( sPara, nPos );
1149 if (nPos<0)
1150 {
1151 break;
1152 }
1153 // Has this been escaped?
1154 if( nPos && '\\' == rStr[nPos-1])
1155 {
1156 ++nPos;
1157 if( nPos >= rStr.getLength() )
1158 {
1159 break;
1160 }
1161 }
1162 else
1163 {
1164 rRet = rStr.copy( 0, nPos );
1165 rStr = rStr.copy( nPos + sPara.getLength() );
1166 return true;
1167 }
1168 }
1169 }
1170 rRet = rStr;
1171 rStr.clear();
1172 return false;
1173 }
1174 }
1175
1176 namespace //local functions originally from docfmt.cxx
1177 {
1178
lcl_ApplyOtherSet(SwContentNode & rNode,SwHistory * const pHistory,SfxItemSet const & rOtherSet,SfxItemSet const & rFirstSet,SfxItemSet const & rPropsSet,SwRootFrame const * const pLayout,SwNodeIndex * const o_pIndex=nullptr)1179 bool lcl_ApplyOtherSet(
1180 SwContentNode & rNode,
1181 SwHistory *const pHistory,
1182 SfxItemSet const& rOtherSet,
1183 SfxItemSet const& rFirstSet,
1184 SfxItemSet const& rPropsSet,
1185 SwRootFrame const*const pLayout,
1186 SwNodeIndex *const o_pIndex = nullptr)
1187 {
1188 assert(rOtherSet.Count());
1189
1190 bool ret(false);
1191 SwTextNode *const pTNd = rNode.GetTextNode();
1192 sw::MergedPara const* pMerged(nullptr);
1193 if (pLayout && pLayout->HasMergedParas() && pTNd)
1194 {
1195 SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(
1196 pTNd->getLayoutFrame(pLayout)));
1197 if (pTextFrame)
1198 {
1199 pMerged = pTextFrame->GetMergedPara();
1200 }
1201 if (pMerged)
1202 {
1203 if (rFirstSet.Count())
1204 {
1205 if (pHistory)
1206 {
1207 SwRegHistory aRegH(pMerged->pFirstNode, *pMerged->pFirstNode, pHistory);
1208 ret = pMerged->pFirstNode->SetAttr(rFirstSet);
1209 }
1210 else
1211 {
1212 ret = pMerged->pFirstNode->SetAttr(rFirstSet);
1213 }
1214 }
1215 if (rPropsSet.Count())
1216 {
1217 if (pHistory)
1218 {
1219 SwRegHistory aRegH(pMerged->pParaPropsNode, *pMerged->pParaPropsNode, pHistory);
1220 ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret;
1221 }
1222 else
1223 {
1224 ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret;
1225 }
1226 }
1227 if (o_pIndex)
1228 {
1229 *o_pIndex = *pMerged->pLastNode; // skip hidden
1230 }
1231 }
1232 }
1233
1234 // input cursor can't be on hidden node, and iteration skips them
1235 assert(!pLayout || !pLayout->HasMergedParas()
1236 || rNode.GetRedlineMergeFlag() != SwNode::Merge::Hidden);
1237
1238 if (!pMerged)
1239 {
1240 if (pHistory)
1241 {
1242 SwRegHistory aRegH(&rNode, rNode, pHistory);
1243 ret = rNode.SetAttr( rOtherSet );
1244 }
1245 else
1246 {
1247 ret = rNode.SetAttr( rOtherSet );
1248 }
1249 }
1250 return ret;
1251 }
1252
1253 #define DELETECHARSETS if ( bDelete ) { delete pCharSet; delete pOtherSet; }
1254
1255 // set format redline with extra data for lcl_InsAttr()
lcl_SetRedline(SwDoc & rDoc,const SwPaM & rRg)1256 void lcl_SetRedline(
1257 SwDoc& rDoc,
1258 const SwPaM &rRg)
1259 {
1260 std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra;
1261
1262 // check existing redline on the same range, and use its extra data, if it exists
1263 SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos(
1264 rRg.Start()->GetNode(), RedlineType::Format );
1265 if( SwRedlineTable::npos != nRedlPos )
1266 {
1267 const SwPosition *pRStt, *pREnd;
1268 do {
1269 SwRangeRedline* pTmp = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
1270 pRStt = pTmp->Start();
1271 pREnd = pTmp->End();
1272 SwComparePosition eCompare = ComparePosition( *rRg.Start(), *rRg.End(), *pRStt, *pREnd );
1273 if ( eCompare == SwComparePosition::Inside || eCompare == SwComparePosition::Equal )
1274 {
1275 if (pTmp->GetExtraData())
1276 {
1277 const SwRedlineExtraData* pExtraData = pTmp->GetExtraData();
1278 const SwRedlineExtraData_FormatColl* pFormattingChanges =
1279 dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
1280 // Check if the extra data is of type 'formatting changes'
1281 if (pFormattingChanges)
1282 {
1283 // Get the item set that holds all the changes properties
1284 std::shared_ptr<SfxItemSet> pChangesSet = pFormattingChanges->GetItemSet();
1285 xExtra.reset(new SwRedlineExtraData_FormatColl(UIName(u""_ustr), USHRT_MAX, pChangesSet));
1286 break;
1287 }
1288 }
1289 }
1290 } while( pRStt <= rRg.Start() && ++nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size());
1291 }
1292
1293 SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::Format, rRg );
1294 auto const result(rDoc.getIDocumentRedlineAccess().AppendRedline( pRedline, true));
1295 // store original text attributes to reject formatting change
1296 if (IDocumentRedlineAccess::AppendResult::IGNORED == result)
1297 return;
1298
1299 // no existing format redline in the range
1300 if (!xExtra)
1301 {
1302 // Apply the first character's attributes to the ReplaceText
1303 SfxItemSet aSet(SfxItemSet::makeFixedSfxItemSet<RES_CHRATR_BEGIN, RES_TXTATR_WITHEND_END - 1,
1304 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1>(rDoc.GetAttrPool()));
1305 SwTextNode * pNode = rRg.Start()->GetNode().GetTextNode();
1306 pNode->GetParaAttr( aSet, rRg.Start()->GetContentIndex() + 1, rRg.End()->GetContentIndex() );
1307
1308 aSet.ClearItem( RES_TXTATR_REFMARK );
1309 aSet.ClearItem( RES_TXTATR_TOXMARK );
1310 aSet.ClearItem( RES_TXTATR_CJK_RUBY );
1311 aSet.ClearItem( RES_TXTATR_INETFMT );
1312 aSet.ClearItem( RES_TXTATR_META );
1313 aSet.ClearItem( RES_TXTATR_METAFIELD );
1314
1315 // After GetParaAttr aSet can contain invalid/dontcare items (true == IsInvalidItem,
1316 // DONTCARE == SfxItemState), e.g. RES_TXTATR_CHARFMT and (a copy of) this
1317 // SfxItemSet can be passed to MSWordExportBase::OutputItemSet
1318 // which doesn't handle invalid/dontcare items so clear them here
1319 aSet.ClearInvalidItems();
1320
1321 IStyleAccess& rStyleAccess = rDoc.GetIStyleAccess();
1322 std::shared_ptr<SfxItemSet> pAutoStyle = rStyleAccess.getAutomaticStyle(aSet, IStyleAccess::AUTO_STYLE_CHAR);
1323 xExtra.reset(new SwRedlineExtraData_FormatColl(UIName(u""_ustr), USHRT_MAX, pAutoStyle));
1324 }
1325
1326 pRedline->SetExtraData(xExtra.get() );
1327 }
1328
1329 // create format redline(s) for the given range:
1330 // to track the original formatting stored in the
1331 // hints, create redlines for all parts of the
1332 // range partitioned by boundaries of the hints.
lcl_SetRedlines(SwDoc & rDoc,const SwPaM & rRg)1333 void lcl_SetRedlines(
1334 SwDoc& rDoc,
1335 const SwPaM &rRg)
1336 {
1337 SwNodeIndex aIdx( rRg.Start()->GetNode() );
1338 const SwNodeIndex aEndNd( rRg.End()->GetNode() );
1339 while( aIdx <= aEndNd )
1340 {
1341 SwTextNode *pNode = aIdx.GetNode().GetTextNode();
1342 if( pNode )
1343 {
1344 const sal_Int32 nStart = aIdx == rRg.Start()->GetNode()
1345 ? rRg.Start()->GetContentIndex()
1346 : 0;
1347 const sal_Int32 nEnd = aIdx < aEndNd
1348 ? pNode->GetText().getLength()
1349 : rRg.End()->GetContentIndex();
1350
1351 if( SwpHints *pHints = pNode->GetpSwpHints() )
1352 {
1353 const size_t nCount = pHints->Count();
1354 sal_Int32 nRedEnd = nStart;
1355 for( size_t i = 0; i < nCount; ++i )
1356 {
1357 SwTextAttr *pAttr = pHints->Get( i );
1358
1359 if ( pAttr->GetStart() > nEnd )
1360 {
1361 break; // after the range
1362 }
1363
1364 if ( !pAttr->GetEnd() || *pAttr->GetEnd() < nStart )
1365 {
1366 continue; // before the range
1367 }
1368
1369 // range part before the hint
1370 if ( nRedEnd < pAttr->GetStart() )
1371 {
1372 SwPaM aPam( *pNode, nRedEnd, *pNode, pAttr->GetStart() );
1373 lcl_SetRedline(rDoc, aPam);
1374 }
1375
1376 // range part at the hint
1377 sal_Int32 nRedStart = std::max(pAttr->GetStart(), nStart);
1378 nRedEnd = std::min(*pAttr->GetEnd(), nEnd);
1379 SwPaM aPam2( *pNode, nRedStart, *pNode, nRedEnd );
1380 lcl_SetRedline(rDoc, aPam2);
1381 }
1382
1383 // range part after the last hint
1384 if ( nRedEnd < nEnd )
1385 {
1386 SwPaM aPam( *pNode, nRedEnd, *pNode, nEnd );
1387 lcl_SetRedline(rDoc, aPam);
1388 }
1389 }
1390 else
1391 {
1392 SwPaM aPam( *pNode, nStart, *pNode, nEnd );
1393 lcl_SetRedline(rDoc, aPam);
1394 }
1395 }
1396 ++aIdx;
1397 }
1398 }
1399
1400 /// Insert Hints according to content types;
1401 // Is used in SwDoc::Insert(..., SwFormatHint &rHt)
1402
lcl_InsAttr(SwDoc & rDoc,const SwPaM & rRg,const SfxItemSet & rChgSet,const SetAttrMode nFlags,SwUndoAttr * const pUndo,SwRootFrame const * const pLayout,SwTextAttr ** ppNewTextAttr)1403 bool lcl_InsAttr(
1404 SwDoc& rDoc,
1405 const SwPaM &rRg,
1406 const SfxItemSet& rChgSet,
1407 const SetAttrMode nFlags,
1408 SwUndoAttr *const pUndo,
1409 SwRootFrame const*const pLayout,
1410 SwTextAttr **ppNewTextAttr)
1411 {
1412 // Divide the Sets (for selections in Nodes)
1413 const SfxItemSet* pCharSet = nullptr;
1414 const SfxItemSet* pOtherSet = nullptr;
1415 bool bDelete = false;
1416 bool bCharAttr = false;
1417 bool bOtherAttr = false;
1418
1419 // Check, if we can work with rChgSet or if we have to create additional SfxItemSets
1420 if ( 1 == rChgSet.Count() )
1421 {
1422 SfxItemIter aIter( rChgSet );
1423 const SfxPoolItem* pItem = aIter.GetCurItem();
1424 if (pItem && !IsInvalidItem(pItem))
1425 {
1426 const sal_uInt16 nWhich = pItem->Which();
1427
1428 if ( isCHRATR(nWhich) ||
1429 (RES_TXTATR_CHARFMT == nWhich) ||
1430 (RES_TXTATR_INETFMT == nWhich) ||
1431 (RES_TXTATR_AUTOFMT == nWhich) ||
1432 (RES_TXTATR_UNKNOWN_CONTAINER == nWhich) )
1433 {
1434 pCharSet = &rChgSet;
1435 bCharAttr = true;
1436 }
1437
1438 if ( isPARATR(nWhich)
1439 || isPARATR_LIST(nWhich)
1440 || isFRMATR(nWhich)
1441 || isGRFATR(nWhich)
1442 || isUNKNOWNATR(nWhich)
1443 || isDrawingLayerAttribute(nWhich) )
1444 {
1445 pOtherSet = &rChgSet;
1446 bOtherAttr = true;
1447 }
1448 }
1449 }
1450
1451 // Build new itemset if either
1452 // - rChgSet.Count() > 1 or
1453 // - The attribute in rChgSet does not belong to one of the above categories
1454 if ( !bCharAttr && !bOtherAttr )
1455 {
1456 SfxItemSet* pTmpCharItemSet = new SfxItemSet(
1457 rDoc.GetAttrPool(), WhichRangesContainer(svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
1458 RES_TXTATR_AUTOFMT, RES_TXTATR_CHARFMT,
1459 RES_TXTATR_UNKNOWN_CONTAINER, RES_TXTATR_UNKNOWN_CONTAINER>));
1460 SfxItemSet* pTmpOtherItemSet = new SfxItemSet(
1461 rDoc.GetAttrPool(), WhichRangesContainer(svl::Items<RES_PARATR_BEGIN, RES_GRFATR_END - 1,
1462 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1,
1463 // FillAttribute support:
1464 XATTR_FILL_FIRST, XATTR_FILL_LAST>));
1465 pTmpCharItemSet->Put( rChgSet );
1466 pTmpOtherItemSet->Put( rChgSet );
1467
1468 pCharSet = pTmpCharItemSet;
1469 pOtherSet = pTmpOtherItemSet;
1470
1471 bDelete = true;
1472 }
1473
1474 SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr;
1475 bool bRet = false;
1476 const SwPosition *pStart = rRg.Start(), *pEnd = rRg.End();
1477 SwContentNode* pNode = pStart->GetNode().GetContentNode();
1478
1479 if( pNode && pNode->IsTextNode() )
1480 {
1481 // tdf#127606 at editing, remove different formatting of DOCX-like numbering symbol
1482 if (pLayout && pNode->GetTextNode()->getIDocumentSettingAccess()->
1483 get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING ))
1484 {
1485 SwContentNode* pEndNode = pEnd->GetNode().GetContentNode();
1486 SwContentNode* pCurrentNode = pEndNode;
1487 auto nStartIndex = pNode->GetIndex();
1488 auto nEndIndex = pEndNode->GetIndex();
1489 SwNodeIndex aIdx( pEnd->GetNode() );
1490 while ( pCurrentNode != nullptr && nStartIndex <= pCurrentNode->GetIndex() )
1491 {
1492 if (pCurrentNode->GetSwAttrSet().HasItem(RES_PARATR_LIST_AUTOFMT) &&
1493 // remove character formatting only on wholly selected paragraphs
1494 (nStartIndex < pCurrentNode->GetIndex() || pStart->GetContentIndex() == 0) &&
1495 (pCurrentNode->GetIndex() < nEndIndex || pEnd->GetContentIndex() == pEndNode->Len()))
1496 {
1497 pCurrentNode->ResetAttr(RES_PARATR_LIST_AUTOFMT);
1498 // reset also paragraph marker
1499 pCurrentNode->GetTextNode()->RstTextAttr(pCurrentNode->Len(), 1);
1500 }
1501 pCurrentNode = SwNodes::GoPrevious( &aIdx );
1502 }
1503 }
1504 // #i27615#
1505 if (rRg.IsInFrontOfLabel())
1506 {
1507 SwTextNode * pTextNd = pNode->GetTextNode();
1508 if (pLayout)
1509 {
1510 pTextNd = sw::GetParaPropsNode(*pLayout, *pTextNd);
1511 }
1512 SwNumRule * pNumRule = pTextNd->GetNumRule();
1513
1514 if ( !pNumRule )
1515 {
1516 OSL_FAIL( "<InsAttr(..)> - PaM in front of label, but text node has no numbering rule set. This is a serious defect." );
1517 DELETECHARSETS
1518 return false;
1519 }
1520
1521 int nLevel = pTextNd->GetActualListLevel();
1522
1523 if (nLevel < 0)
1524 nLevel = 0;
1525
1526 if (nLevel >= MAXLEVEL)
1527 nLevel = MAXLEVEL - 1;
1528
1529 SwNumFormat aNumFormat = pNumRule->Get(o3tl::narrowing<sal_uInt16>(nLevel));
1530 SwCharFormat * pCharFormat =
1531 rDoc.FindCharFormatByName(UIName(aNumFormat.GetCharFormatName()));
1532
1533 if (pCharFormat)
1534 {
1535 if (pHistory)
1536 pHistory->AddCharFormat(pCharFormat->GetAttrSet(), *pCharFormat);
1537
1538 if ( pCharSet )
1539 pCharFormat->SetFormatAttr(*pCharSet);
1540 }
1541
1542 DELETECHARSETS
1543 return true;
1544 }
1545
1546 // Attributes without an end do not have a range
1547 if ( !bCharAttr && !bOtherAttr )
1548 {
1549 SfxItemSet aTextSet(SfxItemSet::makeFixedSfxItemSet<RES_TXTATR_NOEND_BEGIN, RES_TXTATR_NOEND_END-1>(rDoc.GetAttrPool()));
1550 aTextSet.Put( rChgSet );
1551 if( aTextSet.Count() )
1552 {
1553 SwRegHistory history( pNode, *pNode, pHistory );
1554 bRet = history.InsertItems(
1555 aTextSet, pStart->GetContentIndex(), pStart->GetContentIndex(), nFlags, /*ppNewTextAttr*/nullptr ) || bRet;
1556
1557 if (bRet && (rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
1558 && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())))
1559 {
1560 SwPaM aPam( pStart->GetNode(), pStart->GetContentIndex()-1,
1561 pStart->GetNode(), pStart->GetContentIndex() );
1562
1563 if( pUndo )
1564 pUndo->SaveRedlineData( aPam, true );
1565
1566 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1567 rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
1568 else
1569 rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
1570 }
1571 }
1572 }
1573
1574 // TextAttributes with an end never expand their range
1575 if ( !bCharAttr && !bOtherAttr )
1576 {
1577 // CharFormat and URL attributes are treated separately!
1578 // TEST_TEMP ToDo: AutoFormat!
1579 SfxItemSet aTextSet(SfxItemSet::makeFixedSfxItemSet<RES_TXTATR_REFMARK, RES_TXTATR_METAFIELD,
1580 RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY,
1581 RES_TXTATR_INPUTFIELD, RES_TXTATR_CONTENTCONTROL>(rDoc.GetAttrPool()));
1582
1583 aTextSet.Put( rChgSet );
1584 if( aTextSet.Count() )
1585 {
1586 const sal_Int32 nInsCnt = pStart->GetContentIndex();
1587 const sal_Int32 nEnd = pStart->GetNode() == pEnd->GetNode()
1588 ? pEnd->GetContentIndex()
1589 : pNode->Len();
1590 SwRegHistory history( pNode, *pNode, pHistory );
1591 bRet = history.InsertItems( aTextSet, nInsCnt, nEnd, nFlags, ppNewTextAttr )
1592 || bRet;
1593
1594 if (bRet && (rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
1595 && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())))
1596 {
1597 // Was text content inserted? (RefMark/TOXMarks without an end)
1598 bool bTextIns = nInsCnt != pStart->GetContentIndex();
1599 // Was content inserted or set over the selection?
1600 SwPaM aPam( pStart->GetNode(), bTextIns ? nInsCnt + 1 : nEnd,
1601 pStart->GetNode(), nInsCnt );
1602 if( pUndo )
1603 pUndo->SaveRedlineData( aPam, bTextIns );
1604
1605 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1606 rDoc.getIDocumentRedlineAccess().AppendRedline(
1607 new SwRangeRedline(
1608 bTextIns ? RedlineType::Insert : RedlineType::Format, aPam ),
1609 true);
1610 else if( bTextIns )
1611 rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
1612 }
1613 }
1614 }
1615 }
1616
1617 // We always have to set the auto flag for PageDescs that are set at the Node!
1618 if( pOtherSet && pOtherSet->Count() )
1619 {
1620 SwTableNode* pTableNd;
1621 const SwFormatPageDesc* pDesc = pOtherSet->GetItemIfSet( RES_PAGEDESC, false );
1622 if( pDesc )
1623 {
1624 if( pNode )
1625 {
1626 // Set auto flag. Only in the template it's without auto!
1627 SwFormatPageDesc aNew( *pDesc );
1628
1629 // Tables now also know line breaks
1630 if( !(nFlags & SetAttrMode::APICALL) &&
1631 nullptr != ( pTableNd = pNode->FindTableNode() ) )
1632 {
1633 SwTableNode* pCurTableNd = pTableNd;
1634 while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) )
1635 pTableNd = pCurTableNd;
1636
1637 // set the table format
1638 SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat();
1639 SwRegHistory aRegH( pFormat, *pTableNd, pHistory );
1640 pFormat->SetFormatAttr( aNew );
1641 bRet = true;
1642 }
1643 else
1644 {
1645 SwContentNode * pFirstNode(pNode);
1646 if (pLayout && pLayout->HasMergedParas())
1647 {
1648 pFirstNode = sw::GetFirstAndLastNode(*pLayout, pStart->GetNode()).first;
1649 }
1650 SwRegHistory aRegH( pFirstNode, *pFirstNode, pHistory );
1651 bRet = pFirstNode->SetAttr( aNew ) || bRet;
1652 }
1653 }
1654
1655 // bOtherAttr = true means that pOtherSet == rChgSet. In this case
1656 // we know, that there is only one attribute in pOtherSet. We cannot
1657 // perform the following operations, instead we return:
1658 if ( bOtherAttr )
1659 return bRet;
1660
1661 const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_PAGEDESC );
1662 if( !pOtherSet->Count() )
1663 {
1664 DELETECHARSETS
1665 return bRet;
1666 }
1667 }
1668
1669 // Tables now also know line breaks
1670 const SvxFormatBreakItem* pBreak;
1671 if( pNode && !(nFlags & SetAttrMode::APICALL) &&
1672 nullptr != (pTableNd = pNode->FindTableNode() ) &&
1673 (pBreak = pOtherSet->GetItemIfSet( RES_BREAK, false )) )
1674 {
1675 SwTableNode* pCurTableNd = pTableNd;
1676 while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) )
1677 pTableNd = pCurTableNd;
1678
1679 // set the table format
1680 SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat();
1681 SwRegHistory aRegH( pFormat, *pTableNd, pHistory );
1682 pFormat->SetFormatAttr( *pBreak );
1683 bRet = true;
1684
1685 // bOtherAttr = true means that pOtherSet == rChgSet. In this case
1686 // we know, that there is only one attribute in pOtherSet. We cannot
1687 // perform the following operations, instead we return:
1688 if ( bOtherAttr )
1689 return bRet;
1690
1691 const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_BREAK );
1692 if( !pOtherSet->Count() )
1693 {
1694 DELETECHARSETS
1695 return bRet;
1696 }
1697 }
1698
1699 {
1700 // If we have a PoolNumRule, create it if needed
1701 sal_uInt16 nPoolId=0;
1702 const SwNumRuleItem* pRule = pOtherSet->GetItemIfSet( RES_PARATR_NUMRULE, false );
1703 if( pRule &&
1704 !rDoc.FindNumRulePtr( pRule->GetValue() ) &&
1705 USHRT_MAX != (nPoolId = SwStyleNameMapper::GetPoolIdFromUIName ( pRule->GetValue(),
1706 SwGetPoolIdFromName::NumRule )) )
1707 rDoc.getIDocumentStylePoolAccess().GetNumRuleFromPool( nPoolId );
1708 }
1709 }
1710
1711 SfxItemSet firstSet(SfxItemSet::makeFixedSfxItemSet<RES_PAGEDESC, RES_BREAK>(rDoc.GetAttrPool()));
1712 if (pOtherSet && pOtherSet->Count())
1713 { // actually only RES_BREAK is possible here...
1714 firstSet.Put(*pOtherSet);
1715 }
1716 SfxItemSet propsSet(SfxItemSet::makeFixedSfxItemSet<RES_PARATR_BEGIN, RES_PAGEDESC,
1717 RES_BREAK+1, RES_FRMATR_END,
1718 XATTR_FILL_FIRST, XATTR_FILL_LAST+1> (rDoc.GetAttrPool()));
1719 if (pOtherSet && pOtherSet->Count())
1720 {
1721 propsSet.Put(*pOtherSet);
1722 }
1723
1724 if( !rRg.HasMark() ) // no range
1725 {
1726 if( !pNode )
1727 {
1728 DELETECHARSETS
1729 return bRet;
1730 }
1731
1732 if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1733 {
1734 SwTextNode* pTextNd = pNode->GetTextNode();
1735 sal_Int32 nMkPos, nPtPos = pStart->GetContentIndex();
1736 const OUString& rStr = pTextNd->GetText();
1737
1738 // Special case: if the Cursor is located within a URL attribute, we take over it's area
1739 SwTextAttr const*const pURLAttr(
1740 pTextNd->GetTextAttrAt(pStart->GetContentIndex(), RES_TXTATR_INETFMT));
1741 if (pURLAttr && !pURLAttr->GetINetFormat().GetValue().isEmpty())
1742 {
1743 nMkPos = pURLAttr->GetStart();
1744 nPtPos = *pURLAttr->End();
1745 }
1746 else
1747 {
1748 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
1749 Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
1750 pTextNd->GetText(), nPtPos,
1751 g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
1752 WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/,
1753 true);
1754
1755 if( aBndry.startPos < nPtPos && nPtPos < aBndry.endPos )
1756 {
1757 nMkPos = aBndry.startPos;
1758 nPtPos = aBndry.endPos;
1759 }
1760 else
1761 nPtPos = nMkPos = pStart->GetContentIndex();
1762 }
1763
1764 // Remove the overriding attributes from the SwpHintsArray,
1765 // if the selection spans across the whole paragraph.
1766 // These attributes are inserted as FormatAttributes and
1767 // never override the TextAttributes!
1768 if( !(nFlags & SetAttrMode::DONTREPLACE ) &&
1769 pTextNd->HasHints() && !nMkPos && nPtPos == rStr.getLength())
1770 {
1771 if( pHistory )
1772 {
1773 // Save all attributes for the Undo.
1774 SwRegHistory aRHst( *pTextNd, pHistory );
1775 pTextNd->GetpSwpHints()->Register( &aRHst );
1776 pTextNd->RstTextAttr( 0, nPtPos, 0, pCharSet );
1777 if( pTextNd->GetpSwpHints() )
1778 pTextNd->GetpSwpHints()->DeRegister();
1779 }
1780 else
1781 pTextNd->RstTextAttr( 0, nPtPos, 0, pCharSet );
1782 }
1783
1784 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1785 {
1786 SwPaM aPam( *pNode, nMkPos, *pNode, nPtPos );
1787
1788 if( pUndo )
1789 pUndo->SaveRedlineData( aPam, false );
1790
1791 lcl_SetRedlines(rDoc, aPam);
1792 }
1793
1794 // the SwRegHistory inserts the attribute into the TextNode!
1795 SwRegHistory history( pNode, *pNode, pHistory );
1796
1797 bRet = history.InsertItems( *pCharSet, nMkPos, nPtPos, nFlags, /*ppNewTextAttr*/nullptr )
1798 || bRet;
1799
1800 if (bRet && pTextNd->GetSwAttrSet().HasItem(RES_PARATR_LIST_AUTOFMT)
1801 && nMkPos == nPtPos && nMkPos == pTextNd->Len())
1802 {
1803 // The hint is created exactly at the paragraph end and the paragraph has
1804 // paragraph marker character properties, update that autostyle, too.
1805 const SwFormatAutoFormat& rListAutoFormat
1806 = pTextNd->GetAttr(RES_PARATR_LIST_AUTOFMT);
1807 std::unique_ptr<SfxItemSet> pSet = rListAutoFormat.GetStyleHandle()->Clone();
1808 pSet->Put(*pCharSet);
1809 IStyleAccess& rStyleAccess = rDoc.GetIStyleAccess();
1810 std::shared_ptr<SfxItemSet> pAutoStyle
1811 = rStyleAccess.getAutomaticStyle(*pSet, IStyleAccess::AUTO_STYLE_CHAR);
1812 SwFormatAutoFormat aListAutofmt(RES_PARATR_LIST_AUTOFMT);
1813 aListAutofmt.SetStyleHandle(pAutoStyle);
1814 pTextNd->SetAttr(aListAutofmt);
1815 }
1816 }
1817 if( pOtherSet && pOtherSet->Count() )
1818 {
1819 // Need to check for unique item for DrawingLayer items of type NameOrIndex
1820 // and evtl. correct that item to ensure unique names for that type. This call may
1821 // modify/correct entries inside of the given SfxItemSet
1822 SfxItemSet aTempLocalCopy(*pOtherSet);
1823
1824 rDoc.CheckForUniqueItemForLineFillNameOrIndex(aTempLocalCopy);
1825 bRet = lcl_ApplyOtherSet(*pNode, pHistory, aTempLocalCopy, firstSet, propsSet, pLayout) || bRet;
1826 }
1827
1828 DELETECHARSETS
1829 return bRet;
1830 }
1831
1832 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() && pCharSet && pCharSet->Count() )
1833 {
1834 if( pUndo )
1835 pUndo->SaveRedlineData( rRg, false );
1836
1837 lcl_SetRedlines(rDoc, rRg);
1838 }
1839
1840 /* now if range */
1841 sal_uLong nNodes = 0;
1842
1843 SwNodeIndex aSt( rDoc.GetNodes() );
1844 SwNodeIndex aEnd( rDoc.GetNodes() );
1845 SwContentIndex aCntEnd( pEnd->GetContentNode(), pEnd->GetContentIndex() );
1846
1847 if( pNode )
1848 {
1849 const sal_Int32 nLen = pNode->Len();
1850 if( pStart->GetNode() != pEnd->GetNode() )
1851 aCntEnd.Assign( pNode, nLen );
1852
1853 if( pStart->GetContentIndex() != 0 || aCntEnd.GetIndex() != nLen )
1854 {
1855 // the SwRegHistory inserts the attribute into the TextNode!
1856 if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1857 {
1858 SwRegHistory history( pNode, *pNode, pHistory );
1859 bRet = history.InsertItems(*pCharSet,
1860 pStart->GetContentIndex(), aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr)
1861 || bRet;
1862 }
1863
1864 if( pOtherSet && pOtherSet->Count() )
1865 {
1866 bRet = lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout) || bRet;
1867 }
1868
1869 // Only selection in a Node.
1870 if( pStart->GetNode() == pEnd->GetNode() )
1871 {
1872 DELETECHARSETS
1873 return bRet;
1874 }
1875 ++nNodes;
1876 aSt.Assign( pStart->GetNode(), +1 );
1877 }
1878 else
1879 aSt = pStart->GetNode();
1880 aCntEnd.Assign(pEnd->GetContentNode(), pEnd->GetContentIndex()); // aEnd was changed!
1881 }
1882 else
1883 aSt.Assign( pStart->GetNode(), +1 );
1884
1885 // aSt points to the first full Node now
1886
1887 /*
1888 * The selection spans more than one Node.
1889 */
1890 if( pStart->GetNode() < pEnd->GetNode() )
1891 {
1892 pNode = pEnd->GetNode().GetContentNode();
1893 if(pNode)
1894 {
1895 if( aCntEnd.GetIndex() != pNode->Len() )
1896 {
1897 // the SwRegHistory inserts the attribute into the TextNode!
1898 if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1899 {
1900 SwRegHistory history( pNode, *pNode, pHistory );
1901 (void)history.InsertItems(*pCharSet,
1902 0, aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr);
1903 }
1904
1905 if( pOtherSet && pOtherSet->Count() )
1906 {
1907 lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout);
1908 }
1909
1910 ++nNodes;
1911 aEnd = pEnd->GetNode();
1912 }
1913 else
1914 aEnd.Assign( pEnd->GetNode(), +1 );
1915 }
1916 else
1917 aEnd = pEnd->GetNode();
1918 }
1919 else
1920 aEnd.Assign( pEnd->GetNode(), +1 );
1921
1922 // aEnd points BEHIND the last full node now
1923
1924 /* Edit the fully selected Nodes. */
1925 // Reset all attributes from the set!
1926 if( pCharSet && pCharSet->Count() && !( SetAttrMode::DONTREPLACE & nFlags ) )
1927 {
1928 ::sw::DocumentContentOperationsManager::ParaRstFormat aPara(
1929 pStart, pEnd, pHistory, pCharSet, pLayout);
1930 rDoc.GetNodes().ForEach( aSt, aEnd, ::sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara );
1931 }
1932
1933 bool bCreateSwpHints = pCharSet && (
1934 SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_CHARFMT, false ) ||
1935 SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_INETFMT, false ) );
1936
1937 for (SwNodeIndex current = aSt; current < aEnd; ++current)
1938 {
1939 SwTextNode *const pTNd = current.GetNode().GetTextNode();
1940 if (!pTNd)
1941 continue;
1942
1943 if (pLayout && pLayout->HasMergedParas()
1944 && pTNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)
1945 { // not really sure what to do here, but applying to hidden
1946 continue; // nodes doesn't make sense...
1947 }
1948
1949 if( pHistory )
1950 {
1951 SwRegHistory aRegH( pTNd, *pTNd, pHistory );
1952
1953 if (pCharSet && pCharSet->Count())
1954 {
1955 if (SwpHints *pSwpHints = bCreateSwpHints ? &pTNd->GetOrCreateSwpHints()
1956 : pTNd->GetpSwpHints())
1957 {
1958 pSwpHints->Register( &aRegH );
1959 }
1960
1961 pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags);
1962
1963 // re-fetch as it may be deleted by SetAttr
1964 if (SwpHints *pSwpHints = pTNd->GetpSwpHints())
1965 pSwpHints->DeRegister();
1966 }
1967 }
1968 else
1969 {
1970 if (pCharSet && pCharSet->Count())
1971 pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags);
1972 }
1973 ++nNodes;
1974 }
1975
1976 if (pOtherSet && pOtherSet->Count())
1977 {
1978 for (; aSt < aEnd; ++aSt)
1979 {
1980 pNode = aSt.GetNode().GetContentNode();
1981 if (!pNode)
1982 continue;
1983
1984 lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout, &aSt);
1985 ++nNodes;
1986 }
1987 }
1988
1989 DELETECHARSETS
1990 return (nNodes != 0) || bRet;
1991 }
1992 }
1993
1994 namespace sw
1995 {
1996
1997 namespace mark
1998 {
IsFieldmarkOverlap(SwPaM const & rPaM)1999 bool IsFieldmarkOverlap(SwPaM const& rPaM)
2000 {
2001 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
2002 sw::CalcBreaks(Breaks, rPaM);
2003 return !Breaks.empty();
2004 }
2005 }
2006
DocumentContentOperationsManager(SwDoc & i_rSwdoc)2007 DocumentContentOperationsManager::DocumentContentOperationsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc )
2008 {
2009 }
2010
2011 /**
2012 * Checks if rStart..rEnd mark a range that makes sense to copy.
2013 *
2014 * IsMoveToFly means the copy is a move to create a fly
2015 * and so existing flys at the edge must not be copied.
2016 */
IsEmptyRange(const SwPosition & rStart,const SwPosition & rEnd,SwCopyFlags const flags)2017 static bool IsEmptyRange(const SwPosition& rStart, const SwPosition& rEnd,
2018 SwCopyFlags const flags)
2019 {
2020 if (rStart == rEnd)
2021 { // check if a fly anchored there would be copied - then copy...
2022 return !IsDestroyFrameAnchoredAtChar(rStart, rStart, rEnd,
2023 (flags & SwCopyFlags::IsMoveToFly)
2024 ? DelContentType::WriterfilterHack|DelContentType::AllMask
2025 : DelContentType::AllMask);
2026 }
2027 else
2028 {
2029 return rEnd < rStart;
2030 }
2031 }
2032
2033 // Copy an area into this document or into another document
CopyRange(SwPaM & rPam,SwPosition & rPos,SwCopyFlags const flags,sal_uInt32 nMovedID) const2034 bool DocumentContentOperationsManager::CopyRange(SwPaM& rPam, SwPosition& rPos,
2035 SwCopyFlags const flags,
2036 sal_uInt32 nMovedID) const
2037 {
2038 const SwPosition *pStart = rPam.Start(), *pEnd = rPam.End();
2039
2040 SwDoc& rDoc = rPos.GetNode().GetDoc();
2041 bool bColumnSel = rDoc.IsClipBoard() && rDoc.IsColumnSelection();
2042
2043 // Catch if there's no copy to do
2044 if (!rPam.HasMark() || (IsEmptyRange(*pStart, *pEnd, flags) && !bColumnSel))
2045 return false;
2046
2047 // Prevent copying into Flys that are anchored in the source range
2048 if (&rDoc == &m_rDoc && (flags & SwCopyFlags::CheckPosInFly))
2049 {
2050 // Correct the Start-/EndNode
2051 SwNodeOffset nStt = pStart->GetNodeIndex(),
2052 nEnd = pEnd->GetNodeIndex(),
2053 nDiff = nEnd - nStt +1;
2054 SwNode* pNd = m_rDoc.GetNodes()[ nStt ];
2055 if( pNd->IsContentNode() && pStart->GetContentIndex() )
2056 {
2057 ++nStt;
2058 --nDiff;
2059 }
2060 if( (pNd = m_rDoc.GetNodes()[ nEnd ])->IsContentNode() &&
2061 static_cast<SwContentNode*>(pNd)->Len() != pEnd->GetContentIndex() )
2062 {
2063 --nEnd;
2064 --nDiff;
2065 }
2066 if( nDiff &&
2067 lcl_ChkFlyFly( rDoc, nStt, nEnd, rPos.GetNodeIndex() ) )
2068 {
2069 return false;
2070 }
2071 }
2072
2073 std::optional<SwPaM> pRedlineRange;
2074 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
2075 (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) )
2076 pRedlineRange.emplace( rPos );
2077
2078 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
2079
2080 bool bRet = false;
2081
2082 if( &rDoc != &m_rDoc )
2083 { // ordinary copy
2084 bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange ? &*pRedlineRange : nullptr);
2085 }
2086 else if( ! ( *pStart <= rPos && rPos < *pEnd &&
2087 ( pStart->GetNode() != pEnd->GetNode() ||
2088 !pStart->GetNode().IsTextNode() )) )
2089 {
2090 // Copy to a position outside of the area, or copy a single TextNode
2091 // Do an ordinary copy
2092 bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange ? &*pRedlineRange : nullptr);
2093 }
2094 else
2095 {
2096 // Copy the range in itself
2097 assert(!"mst: this is assumed to be dead code");
2098 }
2099
2100 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
2101 if( pRedlineRange )
2102 {
2103 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2104 rDoc.getIDocumentRedlineAccess().AppendRedline(
2105 new SwRangeRedline(RedlineType::Insert, *pRedlineRange, nMovedID), true);
2106 else
2107 rDoc.getIDocumentRedlineAccess().SplitRedline( *pRedlineRange );
2108 pRedlineRange.reset();
2109 }
2110
2111 return bRet;
2112 }
2113
GetCorrPosition(SwPaM const & rPam)2114 static auto GetCorrPosition(SwPaM const& rPam) -> SwPosition
2115 {
2116 // tdf#152710 target position must be on node that survives deletion
2117 // so that PaMCorrAbs can invalidate SwUnoCursors properly
2118 return rPam.GetPoint()->GetNode().IsContentNode()
2119 ? *rPam.GetPoint()
2120 : rPam.GetMark()->GetNode().IsContentNode()
2121 ? *rPam.GetMark()
2122 // this would be the result in SwNodes::RemoveNode()
2123 : SwPosition(rPam.End()->GetNode(), SwNodeOffset(+1));
2124 }
2125
2126 /// Delete a full Section of the NodeArray.
2127 /// The passed Node is located somewhere in the designated Section.
DeleteSection(SwNode * pNode)2128 void DocumentContentOperationsManager::DeleteSection( SwNode *pNode )
2129 {
2130 assert(pNode && "Didn't pass a Node.");
2131
2132 SwStartNode* pSttNd = pNode->IsStartNode() ? static_cast<SwStartNode*>(pNode)
2133 : pNode->StartOfSectionNode();
2134 SwNodeIndex aSttIdx( *pSttNd ), aEndIdx( *pNode->EndOfSectionNode() );
2135
2136 // delete all Flys, Bookmarks, ...
2137 DelFlyInRange( aSttIdx.GetNode(), aEndIdx.GetNode() );
2138 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( *pSttNd, true, RedlineType::Any );
2139 DelBookmarks(aSttIdx.GetNode(), aEndIdx.GetNode());
2140
2141 {
2142 // move all Cursor/StackCursor/UnoCursor out of the to-be-deleted area
2143 SwPaM const range(aSttIdx, aEndIdx);
2144 SwPosition const pos(GetCorrPosition(range));
2145 ::PaMCorrAbs(range, pos);
2146 }
2147
2148 m_rDoc.GetNodes().DelNodes( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() + 1 );
2149 }
2150
DeleteDummyChar(SwPosition const & rPos,sal_Unicode const cDummy)2151 void DocumentContentOperationsManager::DeleteDummyChar(
2152 SwPosition const& rPos, sal_Unicode const cDummy)
2153 {
2154 SwPaM aPam(rPos, rPos);
2155 aPam.GetPoint()->AdjustContent(+1);
2156 assert(aPam.GetText().getLength() == 1 && aPam.GetText()[0] == cDummy);
2157 (void) cDummy;
2158
2159 DeleteRangeImpl(aPam, SwDeleteFlags::Default);
2160
2161 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2162 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2163 {
2164 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2165 }
2166 }
2167
DeleteRange(SwPaM & rPam)2168 void DocumentContentOperationsManager::DeleteRange( SwPaM & rPam )
2169 {
2170 // Seek all redlines that are in that PaM to be deleted..
2171 SwRedlineTable::size_type nRedlStart = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos(
2172 rPam.Start()->GetNode(), RedlineType::Any);
2173 SwRedlineTable::size_type nRedlEnd = m_rDoc.getIDocumentRedlineAccess().GetRedlineEndPos(
2174 nRedlStart, rPam.End()->GetNode(), RedlineType::Any);
2175
2176 lcl_DoWithBreaks(*this, rPam, SwDeleteFlags::Default, &DocumentContentOperationsManager::DeleteRangeImpl);
2177
2178 // update all redlines was in the Pam that is
2179 m_rDoc.getIDocumentRedlineAccess().UpdateRedlineContentNode(nRedlStart, nRedlEnd);
2180
2181 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2182 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2183 {
2184 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2185 }
2186 }
2187
DelFullPara(SwPaM & rPam)2188 bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam )
2189 {
2190 const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End();
2191 const SwNode* pNd = &rStt.GetNode();
2192 SwNodeOffset nSectDiff = pNd->StartOfSectionNode()->EndOfSectionIndex() -
2193 pNd->StartOfSectionIndex();
2194 SwNodeOffset nNodeDiff = rEnd.GetNodeIndex() - rStt.GetNodeIndex();
2195
2196 if ( nSectDiff-SwNodeOffset(2) <= nNodeDiff || m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
2197 /* #i9185# Prevent getting the node after the end node (see below) */
2198 rEnd.GetNodeIndex() + 1 == m_rDoc.GetNodes().Count() )
2199 {
2200 return false;
2201 }
2202
2203 {
2204 SwPaM temp(rPam, nullptr);
2205 if (!temp.HasMark())
2206 {
2207 temp.SetMark();
2208 }
2209 if (SwTextNode *const pNode = temp.Start()->GetNode().GetTextNode())
2210 { // rPam may not have nContent set but IsFieldmarkOverlap requires it
2211 temp.Start()->AssignStartIndex(*pNode);
2212 }
2213 if (SwTextNode *const pNode = temp.End()->GetNode().GetTextNode())
2214 {
2215 temp.End()->AssignEndIndex(*pNode);
2216 }
2217 if (sw::mark::IsFieldmarkOverlap(temp))
2218 { // a bit of a problem: we want to completely remove the nodes
2219 // but then how can the CH_TXT_ATR survive?
2220 return false;
2221 }
2222 }
2223
2224 // Move hard page breaks to the following Node.
2225 bool bSavePageBreak = false, bSavePageDesc = false;
2226
2227 /* #i9185# This would lead to a segmentation fault if not caught above. */
2228 SwNodeOffset nNextNd = rEnd.GetNodeIndex() + 1;
2229 SwTableNode *const pTableNd = m_rDoc.GetNodes()[ nNextNd ]->GetTableNode();
2230
2231 if( pTableNd && pNd->IsContentNode() )
2232 {
2233 SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat();
2234
2235 {
2236 const SfxPoolItem *pItem;
2237 const SfxItemSet* pSet = static_cast<const SwContentNode*>(pNd)->GetpSwAttrSet();
2238 if( pSet && SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC,
2239 false, &pItem ) )
2240 {
2241 pTableFormat->SetFormatAttr( *pItem );
2242 bSavePageDesc = true;
2243 }
2244
2245 if( pSet && SfxItemState::SET == pSet->GetItemState( RES_BREAK,
2246 false, &pItem ) )
2247 {
2248 pTableFormat->SetFormatAttr( *pItem );
2249 bSavePageBreak = true;
2250 }
2251 }
2252 }
2253
2254 bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
2255 if( bDoesUndo )
2256 {
2257 if( !rPam.HasMark() )
2258 rPam.SetMark();
2259 else if( rPam.GetPoint() == &rStt )
2260 rPam.Exchange();
2261 rPam.GetPoint()->Adjust(SwNodeOffset(1));
2262
2263 SwContentNode *pTmpNode = rPam.GetPoint()->GetNode().GetContentNode();
2264 bool bGoNext = (nullptr == pTmpNode);
2265
2266 if (rPam.GetMark()->GetContentNode())
2267 rPam.GetMark()->SetContent( 0 );
2268
2269 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
2270
2271 SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
2272 {
2273 SwPosition aTmpPos( *aDelPam.GetPoint() );
2274 if( bGoNext )
2275 {
2276 SwNodes::GoNext(&aTmpPos);
2277 }
2278 ::PaMCorrAbs( aDelPam, aTmpPos );
2279 }
2280
2281 std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aDelPam, SwDeleteFlags::Default, true));
2282
2283 *rPam.GetPoint() = *aDelPam.GetPoint();
2284 pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc );
2285 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
2286 rPam.DeleteMark();
2287 }
2288 else
2289 {
2290 SwNodeRange aRg( rStt.GetNode(), rEnd.GetNode() );
2291 rPam.Normalize(false);
2292
2293 // Try to move past the End
2294 if( !rPam.Move( fnMoveForward, GoInNode ) )
2295 {
2296 // Fair enough, at the Beginning then
2297 rPam.Exchange();
2298 if( !rPam.Move( fnMoveBackward, GoInNode ))
2299 {
2300 SAL_WARN("sw.core", "DelFullPara: no more Nodes");
2301 return false;
2302 }
2303 }
2304
2305 // must delete all fieldmarks before CorrAbs(), or they'll remain
2306 // moved to wrong node without their CH_TXT_ATR_FIELD*
2307 // (note: deleteMarks() doesn't help here, in case of partially
2308 // selected fieldmarks; let's delete these as re-inserting their chars
2309 // elsewhere looks difficult)
2310 ::std::set<::sw::mark::Fieldmark*> fieldmarks;
2311 for (SwNodeIndex i = aRg.aStart; i <= aRg.aEnd; ++i)
2312 {
2313 if (SwTextNode *const pTextNode = i.GetNode().GetTextNode())
2314 {
2315 for (sal_Int32 j = 0; j < pTextNode->GetText().getLength(); ++j)
2316 {
2317 switch (pTextNode->GetText()[j])
2318 {
2319 case CH_TXT_ATR_FIELDSTART:
2320 case CH_TXT_ATR_FIELDEND:
2321 fieldmarks.insert(m_rDoc.getIDocumentMarkAccess()->getFieldmarkAt(SwPosition(*pTextNode, j)));
2322 break;
2323 case CH_TXT_ATR_FIELDSEP:
2324 fieldmarks.insert(m_rDoc.getIDocumentMarkAccess()->getInnerFieldmarkFor(SwPosition(*pTextNode, j)));
2325 break;
2326 }
2327 }
2328 }
2329 }
2330 for (auto const pFieldMark : fieldmarks)
2331 {
2332 m_rDoc.getIDocumentMarkAccess()->deleteMark(pFieldMark);
2333 }
2334
2335 // move bookmarks, redlines etc.
2336 if (aRg.aStart == aRg.aEnd) // only first CorrAbs variant handles this
2337 {
2338 m_rDoc.CorrAbs( aRg.aStart.GetNode(), *rPam.GetPoint(), 0, true );
2339 }
2340 else
2341 {
2342 SwDoc::CorrAbs( aRg.aStart, aRg.aEnd, *rPam.GetPoint(), true );
2343 }
2344
2345 // What's with Flys?
2346 {
2347 // If there are FlyFrames left, delete these too
2348 size_t n = 0;;
2349 while (n < m_rDoc.GetSpzFrameFormats()->size())
2350 {
2351 sw::SpzFrameFormat* pFly = (*m_rDoc.GetSpzFrameFormats())[n];
2352 const SwFormatAnchor* pAnchor = &pFly->GetAnchor();
2353 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
2354 if (pAnchorNode &&
2355 ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
2356 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
2357 // note: here use <= not < like in
2358 // IsDestroyFrameAnchoredAtChar() because of the increment
2359 // of rPam in the bDoesUndo path above!
2360 aRg.aStart <= *pAnchorNode && *pAnchorNode <= aRg.aEnd.GetNode() )
2361 {
2362 m_rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFly );
2363 continue;
2364 }
2365 ++n;
2366 }
2367 }
2368
2369 rPam.DeleteMark();
2370 m_rDoc.GetNodes().Delete( aRg.aStart, nNodeDiff+1 );
2371 }
2372
2373 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2374 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2375 {
2376 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2377 }
2378
2379 m_rDoc.getIDocumentState().SetModified();
2380
2381 return true;
2382 }
2383
DeleteAndJoin(SwPaM & rPam,SwDeleteFlags const flags)2384 bool DocumentContentOperationsManager::DeleteAndJoin(SwPaM & rPam, SwDeleteFlags const flags)
2385 {
2386 if ( lcl_StrLenOverflow( rPam ) )
2387 return false;
2388
2389 bool const ret = lcl_DoWithBreaks( *this, rPam, flags, (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
2390 ? &DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl
2391 : &DocumentContentOperationsManager::DeleteAndJoinImpl );
2392
2393 return ret;
2394 }
2395
2396 // It seems that this is mostly used by SwDoc internals; the only
2397 // way to call this from the outside seems to be the special case in
2398 // SwDoc::CopyRange (but I have not managed to actually hit that case).
MoveRange(SwPaM & rPaM,SwPosition & rPos,SwMoveFlags eMvFlags)2399 bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos, SwMoveFlags eMvFlags )
2400 {
2401 // nothing moved: return
2402 const SwPosition *pStart = rPaM.Start(), *pEnd = rPaM.End();
2403 if( !rPaM.HasMark() || *pStart >= *pEnd || (*pStart <= rPos && rPos < *pEnd))
2404 return false;
2405
2406 assert(!sw::mark::IsFieldmarkOverlap(rPaM)); // probably an invalid redline was created?
2407
2408 // Save the paragraph anchored Flys, so that they can be moved.
2409 SaveFlyArr aSaveFlyArr;
2410 SaveFlyInRange( rPaM, rPos, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) );
2411
2412 // save redlines (if DOC_MOVEREDLINES is used)
2413 SaveRedlines_t aSaveRedl;
2414 if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
2415 {
2416 lcl_SaveRedlines( rPaM, aSaveRedl );
2417
2418 // #i17764# unfortunately, code below relies on undos being
2419 // in a particular order, and presence of bookmarks
2420 // will change this order. Hence, we delete bookmarks
2421 // here without undo.
2422 ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo());
2423 DelBookmarks(
2424 pStart->GetNode(),
2425 pEnd->GetNode(),
2426 nullptr,
2427 pStart->GetContentIndex(),
2428 pEnd->GetContentIndex());
2429 }
2430
2431 bool bUpdateFootnote = false;
2432 SwFootnoteIdxs aTmpFntIdx;
2433
2434 std::unique_ptr<SwUndoMove> pUndoMove;
2435 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2436 {
2437 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
2438 pUndoMove.reset(new SwUndoMove( rPaM, rPos ));
2439 pUndoMove->SetMoveRedlines( eMvFlags == SwMoveFlags::REDLINES );
2440 }
2441 else
2442 {
2443 bUpdateFootnote = lcl_SaveFootnote( pStart->GetNode(), pEnd->GetNode(), rPos.GetNode(),
2444 m_rDoc.GetFootnoteIdxs(), aTmpFntIdx,
2445 pStart->GetContentIndex(), pEnd->GetContentIndex() );
2446 }
2447
2448 bool bSplit = false;
2449 SwPaM aSavePam( rPos, rPos );
2450
2451 // Move the SPoint to the beginning of the range
2452 if( rPaM.GetPoint() == pEnd )
2453 rPaM.Exchange();
2454
2455 // If there is a TextNode before and after the Move, create a JoinNext in the EditShell.
2456 SwTextNode* pSrcNd = rPaM.GetPoint()->GetNode().GetTextNode();
2457 bool bCorrSavePam = pSrcNd && pStart->GetNode() != pEnd->GetNode();
2458
2459 // If one or more TextNodes are moved, SwNodes::Move will do a SplitNode.
2460 // However, this does not update the cursor. So we create a TextNode to keep
2461 // updating the indices. After the Move the Node is optionally deleted.
2462 SwTextNode * pTNd = rPos.GetNode().GetTextNode();
2463 if( pTNd && rPaM.GetPoint()->GetNode() != rPaM.GetMark()->GetNode() &&
2464 ( rPos.GetContentIndex() || ( pTNd->Len() && bCorrSavePam )) )
2465 {
2466 bSplit = true;
2467 const sal_Int32 nMkContent = rPaM.GetMark()->GetContentIndex();
2468
2469 const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
2470 pContentStore->Save( m_rDoc, rPos.GetNodeIndex(), rPos.GetContentIndex(), true );
2471
2472 SwTextNode * pOrigNode = pTNd;
2473 assert(*aSavePam.GetPoint() == *aSavePam.GetMark() &&
2474 *aSavePam.GetPoint() == rPos);
2475 assert(aSavePam.GetPoint()->GetContentNode() == pOrigNode);
2476 assert(aSavePam.GetPoint()->GetNode() == rPos.GetNode());
2477 assert(rPos.GetNodeIndex() == pOrigNode->GetIndex());
2478
2479 std::function<void (SwTextNode *, sw::mark::RestoreMode, bool)> restoreFunc(
2480 [&](SwTextNode *const, sw::mark::RestoreMode const eMode, bool)
2481 {
2482 if (!pContentStore->Empty())
2483 {
2484 pContentStore->Restore(m_rDoc, pOrigNode->GetIndex()-SwNodeOffset(1), 0, true, false, eMode);
2485 }
2486 });
2487 pTNd->SplitContentNode(rPos, &restoreFunc);
2488
2489 //A new node was inserted before the orig pTNd and the content up to
2490 //rPos moved into it. The old node is returned with the remainder
2491 //of the content in it.
2492 //
2493 //aSavePam was created with rPos, it continues to point to the
2494 //old node, but with the *original* content index into the node.
2495 //Seeing as all the orignode content before that index has
2496 //been removed, the new index into the original node should now be set
2497 //to 0 and the content index of rPos should also be adapted to the
2498 //truncated node
2499 assert(*aSavePam.GetPoint() == *aSavePam.GetMark() &&
2500 *aSavePam.GetPoint() == rPos);
2501 assert(aSavePam.GetPoint()->GetContentNode() == pOrigNode);
2502 assert(aSavePam.GetPoint()->GetNode() == rPos.GetNode());
2503 assert(rPos.GetNodeIndex() == pOrigNode->GetIndex());
2504 aSavePam.GetPoint()->SetContent(0);
2505 rPos = *aSavePam.GetMark() = *aSavePam.GetPoint();
2506
2507 // correct the PaM!
2508 if( rPos.GetNode() == rPaM.GetMark()->GetNode() )
2509 {
2510 rPaM.GetMark()->Assign( rPos.GetNodeIndex() - SwNodeOffset(1) );
2511 rPaM.GetMark()->SetContent( nMkContent );
2512 }
2513 }
2514
2515 // Put back the Pam by one "content"; so that it's always outside of
2516 // the manipulated range.
2517 // tdf#99692 don't Move() back if that would end up in another node
2518 // because moving backward is not necessarily the inverse of forward then.
2519 // (but do Move() back if we have split the node)
2520 const bool bNullContent = !bSplit && aSavePam.GetPoint()->GetContentIndex() == 0;
2521 if( bNullContent )
2522 {
2523 aSavePam.GetPoint()->Adjust(SwNodeOffset(-1));
2524 }
2525 else
2526 {
2527 bool const success(aSavePam.Move(fnMoveBackward, GoInContent));
2528 assert(success);
2529 (void) success;
2530 }
2531
2532 // Copy all Bookmarks that are within the Move range into an array,
2533 // that saves the position as an offset.
2534 std::vector< ::sw::mark::SaveBookmark> aSaveBkmks;
2535 DelBookmarks(
2536 pStart->GetNode(),
2537 pEnd->GetNode(),
2538 &aSaveBkmks,
2539 pStart->GetContentIndex(),
2540 pEnd->GetContentIndex());
2541
2542 // If there is no range anymore due to the above deletions (e.g. the
2543 // footnotes got deleted), it's still a valid Move!
2544 if( *rPaM.GetPoint() != *rPaM.GetMark() )
2545 {
2546 // now do the actual move
2547 m_rDoc.GetNodes().MoveRange( rPaM, rPos, m_rDoc.GetNodes() );
2548
2549 // after a MoveRange() the Mark is deleted
2550 if ( rPaM.HasMark() ) // => no Move occurred!
2551 {
2552 return false;
2553 }
2554 }
2555 else
2556 rPaM.DeleteMark();
2557
2558 OSL_ENSURE( *aSavePam.GetMark() == rPos ||
2559 ( aSavePam.GetMark()->GetNode().GetContentNode() == nullptr ),
2560 "PaM was not moved. Aren't there ContentNodes at the beginning/end?" );
2561 *aSavePam.GetMark() = rPos;
2562
2563 rPaM.SetMark(); // create a Sel. around the new range
2564 pTNd = aSavePam.GetPointNode().GetTextNode();
2565 assert(!m_rDoc.GetIDocumentUndoRedo().DoesUndo());
2566 bool bRemove = true;
2567 // Do two Nodes have to be joined at the SavePam?
2568 if (bSplit && pTNd)
2569 {
2570 if (pTNd->CanJoinNext())
2571 {
2572 // Always join next, because <pTNd> has to stay as it is.
2573 // A join previous from its next would more or less delete <pTNd>
2574 pTNd->JoinNext();
2575 bRemove = false;
2576 }
2577 }
2578 if (bNullContent)
2579 {
2580 aSavePam.GetPoint()->Adjust(SwNodeOffset(1));
2581 }
2582 else if (bRemove) // No move forward after joining with next paragraph
2583 {
2584 aSavePam.Move( fnMoveForward, GoInContent );
2585 }
2586
2587 // Insert the Bookmarks back into the Document.
2588 *rPaM.GetMark() = *aSavePam.Start();
2589 for(auto& rBkmk : aSaveBkmks)
2590 rBkmk.SetInDoc(
2591 &m_rDoc,
2592 rPaM.GetMark()->GetNode(),
2593 rPaM.GetMark()->GetContentIndex());
2594 *rPaM.GetPoint() = *aSavePam.End();
2595
2596 // Move the Flys to the new position.
2597 // note: rPos is at the end here; can't really tell flys that used to be
2598 // at the start of rPam from flys that used to be at the end of rPam
2599 // unfortunately, so some of them are going to end up with wrong anchor...
2600 RestFlyInRange( aSaveFlyArr, *rPaM.Start(), &rPos.GetNode() );
2601
2602 // restore redlines (if DOC_MOVEREDLINES is used)
2603 if( !aSaveRedl.empty() )
2604 {
2605 lcl_RestoreRedlines( m_rDoc, *aSavePam.Start(), aSaveRedl );
2606 }
2607
2608 if( bUpdateFootnote )
2609 {
2610 if( !aTmpFntIdx.empty() )
2611 {
2612 m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx );
2613 aTmpFntIdx.clear();
2614 }
2615
2616 m_rDoc.GetFootnoteIdxs().UpdateAllFootnote();
2617 }
2618
2619 m_rDoc.getIDocumentState().SetModified();
2620 return true;
2621 }
2622
MoveNodeRange(SwNodeRange & rRange,SwNode & rDestNd,SwMoveFlags eMvFlags)2623 bool DocumentContentOperationsManager::MoveNodeRange( SwNodeRange& rRange, SwNode& rDestNd,
2624 SwMoveFlags eMvFlags )
2625 {
2626 // Moves all Nodes to the new position.
2627 // Bookmarks are moved too (currently without Undo support).
2628
2629 // If footnotes are being moved to the special section, remove them now.
2630
2631 // Or else delete the Frames for all footnotes that are being moved
2632 // and have it rebuild after the Move (footnotes can change pages).
2633 // Additionally we have to correct the FootnoteIdx array's sorting.
2634 bool bUpdateFootnote = false;
2635 SwFootnoteIdxs aTmpFntIdx;
2636
2637 std::unique_ptr<SwUndoMove> pUndo;
2638 if ((SwMoveFlags::CREATEUNDOOBJ & eMvFlags ) && m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2639 {
2640 pUndo.reset(new SwUndoMove( m_rDoc, rRange, rDestNd ));
2641 }
2642 else
2643 {
2644 bUpdateFootnote = lcl_SaveFootnote( rRange.aStart.GetNode(), rRange.aEnd.GetNode(), rDestNd,
2645 m_rDoc.GetFootnoteIdxs(), aTmpFntIdx );
2646 }
2647
2648 SaveRedlines_t aSaveRedl;
2649 std::vector<SwRangeRedline*> aSavRedlInsPosArr;
2650 if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
2651 {
2652 lcl_SaveRedlines( rRange, aSaveRedl );
2653
2654 // Find all RedLines that end at the InsPos.
2655 // These have to be moved back to the "old" position after the Move.
2656 SwRedlineTable::size_type nRedlPos = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos( rDestNd, RedlineType::Any );
2657 if( SwRedlineTable::npos != nRedlPos )
2658 {
2659 const SwPosition *pRStt, *pREnd;
2660 do {
2661 SwRangeRedline* pTmp = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
2662 pRStt = pTmp->Start();
2663 pREnd = pTmp->End();
2664 if( pREnd->GetNode() == rDestNd && pRStt->GetNode() < rDestNd )
2665 {
2666 aSavRedlInsPosArr.push_back( pTmp );
2667 }
2668 } while( pRStt->GetNode() < rDestNd && ++nRedlPos < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size());
2669 }
2670 }
2671
2672 // Copy all Bookmarks that are within the Move range into an array
2673 // that stores all references to positions as an offset.
2674 // The final mapping happens after the Move.
2675 std::vector< ::sw::mark::SaveBookmark> aSaveBkmks;
2676 DelBookmarks(rRange.aStart.GetNode(), rRange.aEnd.GetNode(), &aSaveBkmks);
2677
2678 // Save the paragraph-bound Flys, so that they can be moved.
2679 SaveFlyArr aSaveFlyArr;
2680 if( !m_rDoc.GetSpzFrameFormats()->empty() )
2681 SaveFlyInRange( rRange, aSaveFlyArr );
2682
2683 // Set it to before the Position, so that it cannot be moved further.
2684 SwNodeIndex aIdx( rDestNd, -1 );
2685
2686 std::optional<SwNodeIndex> oSaveInsPos;
2687 if( pUndo )
2688 oSaveInsPos.emplace(rRange.aStart, -1 );
2689
2690 // move the Nodes
2691 bool bNoDelFrames = bool(SwMoveFlags::NO_DELFRMS & eMvFlags);
2692 if( m_rDoc.GetNodes().MoveNodes( rRange, m_rDoc.GetNodes(), rDestNd, !bNoDelFrames ) )
2693 {
2694 ++aIdx; // again back to old position
2695 if( oSaveInsPos )
2696 ++(*oSaveInsPos);
2697 }
2698 else
2699 {
2700 aIdx = rRange.aStart;
2701 pUndo.reset();
2702 }
2703
2704 // move the Flys to the new position
2705 if( !aSaveFlyArr.empty() )
2706 {
2707 SwPosition const tmp(aIdx);
2708 RestFlyInRange(aSaveFlyArr, tmp, nullptr);
2709 }
2710
2711 // Add the Bookmarks back to the Document
2712 for(auto& rBkmk : aSaveBkmks)
2713 rBkmk.SetInDoc(&m_rDoc, aIdx.GetNode());
2714
2715 if( !aSavRedlInsPosArr.empty() )
2716 {
2717 for(SwRangeRedline* pTmp : aSavRedlInsPosArr)
2718 {
2719 if( m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().Contains( pTmp ) )
2720 {
2721 SwPosition* pEnd = pTmp->End();
2722 pEnd->Assign(aIdx);
2723 }
2724 }
2725 }
2726
2727 if( !aSaveRedl.empty() )
2728 lcl_RestoreRedlines( m_rDoc, aIdx.GetIndex(), aSaveRedl );
2729
2730 if( pUndo )
2731 {
2732 pUndo->SetDestRange( aIdx.GetNode(), rDestNd, *oSaveInsPos );
2733 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
2734 }
2735
2736 oSaveInsPos.reset();
2737
2738 if( bUpdateFootnote )
2739 {
2740 if( !aTmpFntIdx.empty() )
2741 {
2742 m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx );
2743 aTmpFntIdx.clear();
2744 }
2745
2746 m_rDoc.GetFootnoteIdxs().UpdateAllFootnote();
2747 }
2748
2749 m_rDoc.getIDocumentState().SetModified();
2750 return true;
2751 }
2752
MoveAndJoin(SwPaM & rPaM,SwPosition & rPos)2753 void DocumentContentOperationsManager::MoveAndJoin( SwPaM& rPaM, SwPosition& rPos )
2754 {
2755 SwNodeIndex aIdx( rPaM.Start()->GetNode() );
2756 bool bJoinText = aIdx.GetNode().IsTextNode();
2757 bool bOneNode = rPaM.GetPoint()->GetNode() == rPaM.GetMark()->GetNode();
2758 --aIdx; // in front of the move area!
2759
2760 bool bRet = MoveRange( rPaM, rPos, SwMoveFlags::DEFAULT );
2761 if( !bRet || bOneNode )
2762 return;
2763
2764 if( bJoinText )
2765 ++aIdx;
2766 SwTextNode * pTextNd = aIdx.GetNode().GetTextNode();
2767 SwNodeIndex aNxtIdx( aIdx );
2768 if( pTextNd && pTextNd->CanJoinNext( &aNxtIdx ) )
2769 {
2770 { // Block so SwContentIndex into node is deleted before Join
2771 m_rDoc.CorrRel( aNxtIdx.GetNode(),
2772 SwPosition( *pTextNd, pTextNd->GetText().getLength() ),
2773 0, true );
2774 }
2775 pTextNd->JoinNext();
2776 }
2777 }
2778
2779 // Overwrite only uses the point of the PaM, the mark is ignored; characters
2780 // are replaced from point until the end of the node; at the end of the node,
2781 // characters are inserted.
Overwrite(const SwPaM & rRg,const OUString & rStr)2782 bool DocumentContentOperationsManager::Overwrite( const SwPaM &rRg, const OUString &rStr )
2783 {
2784 assert(rStr.getLength());
2785 SwPosition& rPt = *const_cast<SwPosition*>(rRg.GetPoint());
2786 if( m_rDoc.GetAutoCorrExceptWord() ) // Add to AutoCorrect
2787 {
2788 if( 1 == rStr.getLength() )
2789 m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPt, rStr[ 0 ] );
2790 m_rDoc.DeleteAutoCorrExceptWord();
2791 }
2792
2793 SwTextNode *pNode = rPt.GetNode().GetTextNode();
2794 if (!pNode || rStr.getLength() > pNode->GetSpaceLeft()) // worst case: no erase
2795 {
2796 return false;
2797 }
2798
2799 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2800 {
2801 m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called
2802 }
2803
2804 const size_t nOldAttrCnt = pNode->GetpSwpHints()
2805 ? pNode->GetpSwpHints()->Count() : 0;
2806 SwDataChanged aTmp( rRg );
2807 sal_Int32 const nActualStart(rPt.GetContentIndex());
2808 sal_Int32 nStart = 0;
2809
2810 bool bOldExpFlg = pNode->IsIgnoreDontExpand();
2811 pNode->SetIgnoreDontExpand( true );
2812
2813 for( sal_Int32 nCnt = 0; nCnt < rStr.getLength(); ++nCnt )
2814 {
2815 // start behind the characters (to fix the attributes!)
2816 nStart = rPt.GetContentIndex();
2817 if (nStart < pNode->GetText().getLength())
2818 {
2819 lcl_SkipAttr( pNode, rPt, nStart );
2820 }
2821 sal_Unicode c = rStr[ nCnt ];
2822 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2823 {
2824 bool bMerged(false);
2825 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
2826 {
2827 SwUndo *const pUndo = m_rDoc.GetUndoManager().GetLastUndo();
2828 SwUndoOverwrite *const pUndoOW(
2829 dynamic_cast<SwUndoOverwrite *>(pUndo) );
2830 if (pUndoOW)
2831 {
2832 // if CanGrouping() returns true it's already merged
2833 bMerged = pUndoOW->CanGrouping(m_rDoc, rPt, c);
2834 }
2835 }
2836 if (!bMerged)
2837 {
2838 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
2839 std::make_unique<SwUndoOverwrite>(m_rDoc, rPt, c) );
2840 }
2841 }
2842 else
2843 {
2844 // start behind the characters (to fix the attributes!)
2845 if (nStart < pNode->GetText().getLength())
2846 rPt.AdjustContent(+1);
2847 pNode->InsertText( OUString(c), rPt, SwInsertFlags::EMPTYEXPAND );
2848 if( nStart+1 < rPt.GetContentIndex() )
2849 {
2850 rPt.SetContent(nStart);
2851 pNode->EraseText( rPt, 1 );
2852 rPt.AdjustContent(+1);
2853 }
2854 }
2855 }
2856 pNode->SetIgnoreDontExpand( bOldExpFlg );
2857
2858 const size_t nNewAttrCnt = pNode->GetpSwpHints()
2859 ? pNode->GetpSwpHints()->Count() : 0;
2860 if( nOldAttrCnt != nNewAttrCnt )
2861 {
2862 const SwUpdateAttr aHint(0,0,0);
2863 pNode->TriggerNodeUpdate(sw::UpdateAttrHint(&aHint, &aHint));
2864 }
2865
2866 if (!m_rDoc.GetIDocumentUndoRedo().DoesUndo() &&
2867 !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2868 {
2869 SwPaM aPam(rPt.GetNode(), nActualStart, rPt.GetNode(), rPt.GetContentIndex());
2870 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, true, RedlineType::Any );
2871 }
2872 else if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2873 {
2874 // FIXME: this redline is WRONG: there is no DELETE, and the skipped
2875 // characters are also included in aPam
2876 SwPaM aPam(rPt.GetNode(), nActualStart, rPt.GetNode(), rPt.GetContentIndex());
2877 m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
2878 }
2879
2880 m_rDoc.getIDocumentState().SetModified();
2881 return true;
2882 }
2883
InsertString(const SwPaM & rRg,const OUString & rStr,const SwInsertFlags nInsertMode)2884 bool DocumentContentOperationsManager::InsertString( const SwPaM &rRg, const OUString &rStr,
2885 const SwInsertFlags nInsertMode )
2886 {
2887 // tdf#119019 accept tracked paragraph formatting to do not hide new insertions
2888 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2889 {
2890 RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
2891 m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting( rRg );
2892 if (eOld != m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags())
2893 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
2894 }
2895
2896 // fetching DoesUndo is surprisingly expensive
2897 bool bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
2898 if (bDoesUndo)
2899 m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called!
2900
2901 const SwPosition& rPos = *rRg.GetPoint();
2902
2903 if( m_rDoc.GetAutoCorrExceptWord() ) // add to auto correction
2904 {
2905 if( 1 == rStr.getLength() && m_rDoc.GetAutoCorrExceptWord()->IsDeleted() )
2906 {
2907 m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPos, rStr[ 0 ] );
2908 }
2909 m_rDoc.DeleteAutoCorrExceptWord();
2910 }
2911
2912 SwTextNode *const pNode = rPos.GetNode().GetTextNode();
2913 if(!pNode)
2914 return false;
2915
2916 SwDataChanged aTmp( rRg );
2917
2918 if (!bDoesUndo || !m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
2919 {
2920 OUString const ins(pNode->InsertText(rStr, rPos, nInsertMode));
2921 if (bDoesUndo)
2922 {
2923 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
2924 std::make_unique<SwUndoInsert>(rPos.GetNode(),
2925 rPos.GetContentIndex(), ins.getLength(), nInsertMode));
2926 }
2927 }
2928 else
2929 { // if Undo and grouping is enabled, everything changes!
2930 SwUndoInsert * pUndo = nullptr;
2931
2932 // don't group the start if hints at the start should be expanded
2933 if (!(nInsertMode & SwInsertFlags::FORCEHINTEXPAND))
2934 {
2935 SwUndo *const pLastUndo = m_rDoc.GetUndoManager().GetLastUndo();
2936 SwUndoInsert *const pUndoInsert(
2937 dynamic_cast<SwUndoInsert *>(pLastUndo) );
2938 if (pUndoInsert && pUndoInsert->CanGrouping(rPos))
2939 {
2940 pUndo = pUndoInsert;
2941 }
2942 }
2943
2944 CharClass const& rCC = GetAppCharClass();
2945 sal_Int32 nInsPos = rPos.GetContentIndex();
2946
2947 if (!pUndo)
2948 {
2949 pUndo = new SwUndoInsert( rPos.GetNode(), nInsPos, 0, nInsertMode,
2950 !rCC.isLetterNumeric( rStr, 0 ) );
2951 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
2952 }
2953
2954 OUString const ins(pNode->InsertText(rStr, rPos, nInsertMode));
2955
2956 for (sal_Int32 i = 0; i < ins.getLength(); ++i)
2957 {
2958 nInsPos++;
2959 // if CanGrouping() returns true, everything has already been done
2960 if (!pUndo->CanGrouping(ins[i]))
2961 {
2962 pUndo = new SwUndoInsert(rPos.GetNode(), nInsPos, 1, nInsertMode,
2963 !rCC.isLetterNumeric(ins, i));
2964 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
2965 }
2966 }
2967 }
2968
2969 // To-Do - add 'SwExtraRedlineTable' also ?
2970 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ))
2971 {
2972 SwPaM aPam( rPos.GetNode(), aTmp.GetContent(),
2973 rPos.GetNode(), rPos.GetContentIndex());
2974 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2975 {
2976 m_rDoc.getIDocumentRedlineAccess().AppendRedline(
2977 new SwRangeRedline( RedlineType::Insert, aPam ), true);
2978 }
2979 else
2980 {
2981 m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
2982 }
2983 }
2984
2985 m_rDoc.getIDocumentState().SetModified();
2986 return true;
2987 }
2988
SetIME(bool bIME)2989 void DocumentContentOperationsManager::SetIME(bool bIME)
2990 {
2991 m_bIME = bIME;
2992 }
2993
GetIME() const2994 bool DocumentContentOperationsManager::GetIME() const
2995 {
2996 return m_bIME;
2997 }
2998
TransliterateText(const SwPaM & rPaM,utl::TransliterationWrapper & rTrans)2999 void DocumentContentOperationsManager::TransliterateText(
3000 const SwPaM& rPaM,
3001 utl::TransliterationWrapper& rTrans )
3002 {
3003 std::unique_ptr<SwUndoTransliterate> pUndo;
3004 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3005 pUndo.reset(new SwUndoTransliterate( rPaM, rTrans ));
3006
3007 auto [pStart, pEnd] = rPaM.StartEnd(); // SwPosition*
3008 SwNodeOffset nSttNd = pStart->GetNodeIndex(),
3009 nEndNd = pEnd->GetNodeIndex();
3010 sal_Int32 nSttCnt = pStart->GetContentIndex();
3011 sal_Int32 nEndCnt = pEnd->GetContentIndex();
3012
3013 SwTextNode* pTNd = pStart->GetNode().GetTextNode();
3014 bool bNoSelection = (pStart == pEnd) && pTNd; // no selection?
3015 if ( bNoSelection )
3016 {
3017 /* Check if cursor is inside of a word */
3018 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
3019 Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
3020 pTNd->GetText(), nSttCnt,
3021 g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ),
3022 WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/,
3023 true);
3024
3025 if( aBndry.startPos < nSttCnt && nSttCnt < aBndry.endPos )
3026 {
3027 /* Cursor is inside of a word */
3028 if (rTrans.getType() == TransliterationFlags::SENTENCE_CASE) {
3029 /* set current sentence as 'area of effect' */
3030 nSttCnt = g_pBreakIt->GetBreakIter()->beginOfSentence(
3031 pTNd->GetText(), nSttCnt,
3032 g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ) );
3033 nEndCnt = g_pBreakIt->GetBreakIter()->endOfSentence(
3034 pTNd->GetText(), nEndCnt,
3035 g_pBreakIt->GetLocale( pTNd->GetLang( nEndCnt ) ) );
3036 } else {
3037 /* Set current word as 'area of effect' */
3038 nSttCnt = aBndry.startPos;
3039 nEndCnt = aBndry.endPos;
3040 }
3041 } else {
3042 /* Cursor is not inside of a word. Nothing should happen. */
3043 /* Except in the case of change tracking, when the cursor is at the end of the change */
3044 /* Recognize and reject the previous deleted and inserted words to allow to cycle */
3045 IDocumentRedlineAccess& rIDRA = m_rDoc.getIDocumentRedlineAccess();
3046 if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) &&
3047 pStart->GetContentIndex() > 0 )
3048 {
3049 SwPosition aPos(*pStart->GetContentNode(), pStart->GetContentIndex() - 1);
3050 SwRedlineTable::size_type n = 0;
3051
3052 const SwRangeRedline* pFnd =
3053 rIDRA.GetRedlineTable().FindAtPosition( aPos, n );
3054 if ( pFnd && RedlineType::Insert == pFnd->GetType() && n > 0 )
3055 {
3056 const SwRangeRedline* pFnd2 = rIDRA.GetRedlineTable()[n-1];
3057 if ( RedlineType::Delete == pFnd2->GetType() &&
3058 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() &&
3059 *pFnd2->End() == *pFnd->Start() &&
3060 pFnd->GetAuthor() == pFnd2->GetAuthor() )
3061 {
3062 SwPosition aPos2(*pFnd2->End());
3063 rIDRA.RejectRedline(*pFnd, true);
3064 rIDRA.RejectRedline(*pFnd2, true);
3065 // positionate the text cursor inside the changed word to allow to cycle
3066 if ( SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3067 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) )
3068 {
3069 pWrtShell->GetCursor()->GetPoint()->
3070 Assign(*aPos2.GetContentNode(), aPos2.GetContentIndex() - 1);
3071 }
3072 }
3073 }
3074 }
3075 return;
3076 }
3077 }
3078 else
3079 {
3080 bool bHasTrackedChange = false;
3081 IDocumentRedlineAccess& rIDRA = m_rDoc.getIDocumentRedlineAccess();
3082 if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) &&
3083 pEnd->GetContentIndex() > 0 )
3084 {
3085 // search all own redlines within the selected area
3086 SwRedlineTable::size_type n = SwRedlineTable::npos;
3087 const SwRedlineTable& aRedlineTable = rIDRA.GetRedlineTable();
3088 for( SwRedlineTable::size_type m = 0; m < aRedlineTable.size(); ++m )
3089 {
3090 const SwRangeRedline* pRedline = aRedlineTable[ m ];
3091
3092 if ( *pRedline->Start() > *pEnd )
3093 break;
3094
3095 if ( *pRedline->Start() >= *pStart )
3096 n = m;
3097 }
3098
3099 if ( n != SwRedlineTable::npos && n > 0 )
3100 {
3101 SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3102 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell());
3103
3104 sal_Int32 nRejectedCharacters = 0;
3105 SwRangeRedline* pFnd = rIDRA.GetRedlineTable()[n];
3106 SwRangeRedline* pFnd2 = rIDRA.GetRedlineTable()[--n];
3107 // loop on all redlines of a case changing, and reject them
3108 while ( ( ( RedlineType::Insert == pFnd->GetType() &&
3109 RedlineType::Delete == pFnd2->GetType() ) ||
3110 ( RedlineType::Delete == pFnd->GetType() &&
3111 RedlineType::Insert == pFnd2->GetType() ) ) &&
3112 pWrtShell &&
3113 // use time stamp to recognize the multiple selections in the text,
3114 // not only the changes from the same author within the (sometimes
3115 // incomplete) selection
3116 ( pFnd2->GetTimeStamp() == pFnd->GetTimeStamp() ||
3117 ( pStart->GetContentNode() < pFnd2->Start()->GetContentNode() ||
3118 ( pStart->GetContentNode() == pFnd2->Start()->GetContentNode() &&
3119 nSttCnt <= pFnd2->Start()->GetContentIndex() ) ) ) &&
3120 pFnd->GetAuthor() == pFnd2->GetAuthor() )
3121 {
3122 bHasTrackedChange = true;
3123
3124 if ( RedlineType::Insert == pFnd->GetType() )
3125 nRejectedCharacters += pFnd->GetText().getLength();
3126
3127 rIDRA.RejectRedline(*pFnd, true);
3128
3129 pFnd = pFnd2;
3130 if ( n == 0 )
3131 break;
3132 assert(n > 0 && "coverity 2024.6.1");
3133 pFnd2 = rIDRA.GetRedlineTable()[--n];
3134 }
3135
3136 // remove the last item and restore the original selection within the node
3137 if ( bHasTrackedChange )
3138 {
3139 if ( nSttNd == nEndNd )
3140 {
3141 pWrtShell->GetCursor()->GetPoint()->
3142 Assign(*rPaM.Start()->GetContentNode(), nSttCnt);
3143 if ( nEndCnt >= nRejectedCharacters )
3144 pWrtShell->GetCursor()->GetMark()->
3145 Assign(*rPaM.End()->GetContentNode(), nEndCnt - nRejectedCharacters);
3146 }
3147 rIDRA.RejectRedline(*pFnd, true);
3148 }
3149 }
3150 }
3151
3152 // TODO handle title case to lowercase
3153 if ( bHasTrackedChange )
3154 return;
3155 }
3156
3157 bool bUseRedlining = m_rDoc.getIDocumentRedlineAccess().IsRedlineOn();
3158 // as a workaround for a known performance problem, switch off redlining
3159 // to avoid freezing, if transliteration could result too many redlines
3160 if ( bUseRedlining )
3161 {
3162 const sal_uLong nMaxRedlines = 500;
3163 const bool bIsTitleCase = rTrans.getType() == TransliterationFlags::TITLE_CASE;
3164 sal_uLong nAffectedNodes = 0;
3165 sal_uLong nAffectedChars = nEndCnt;
3166 SwNodeIndex aIdx( pStart->GetNode() );
3167 for( ; aIdx.GetIndex() <= nEndNd; ++aIdx )
3168 {
3169 SwTextNode* pAffectedNode = aIdx.GetNode().GetTextNode();
3170
3171 // don't count not text nodes or empty text nodes
3172 if( !pAffectedNode || pAffectedNode->GetText().isEmpty() )
3173 continue;
3174
3175 nAffectedNodes++;
3176
3177 // count characters of the node (the last - maybe partially
3178 // selected - node was counted at initialization of nAffectedChars)
3179 if( aIdx.GetIndex() < nEndNd )
3180 nAffectedChars += pAffectedNode->GetText().getLength();
3181
3182 // transliteration creates n redlines for n nodes, except in the
3183 // case of title case, where it creates n redlines for n words
3184 if( nAffectedNodes > nMaxRedlines ||
3185 // estimate word count based on the character count, where
3186 // 6 = average English word length is ~5 letters + space
3187 ( bIsTitleCase && (nAffectedChars - nSttCnt)/6 > nMaxRedlines ) )
3188 {
3189 bUseRedlining = false;
3190 break;
3191 }
3192 }
3193 }
3194
3195 if( nSttNd != nEndNd ) // is more than one text node involved?
3196 {
3197 // iterate over all affected text nodes, the first and the last one
3198 // may be incomplete because the selection starts and/or ends there
3199
3200 SwNodeIndex aIdx( pStart->GetNode() );
3201 if( nSttCnt )
3202 {
3203 ++aIdx;
3204 if( pTNd )
3205 {
3206 pTNd->TransliterateText(
3207 rTrans, nSttCnt, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining);
3208 }
3209 }
3210
3211 for( ; aIdx.GetIndex() < nEndNd; ++aIdx )
3212 {
3213 pTNd = aIdx.GetNode().GetTextNode();
3214 if (pTNd)
3215 {
3216 pTNd->TransliterateText(
3217 rTrans, 0, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining);
3218 }
3219 }
3220
3221 if( nEndCnt && nullptr != ( pTNd = pEnd->GetNode().GetTextNode() ))
3222 {
3223 pTNd->TransliterateText( rTrans, 0, nEndCnt, pUndo.get(), bUseRedlining );
3224 }
3225 }
3226 else if( pTNd && nSttCnt < nEndCnt )
3227 {
3228 pTNd->TransliterateText( rTrans, nSttCnt, nEndCnt, pUndo.get(), bUseRedlining );
3229 }
3230 if( pUndo && pUndo->HasData() )
3231 {
3232 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
3233 }
3234
3235 // restore selection after tracked changes
3236 if ( !bNoSelection && bUseRedlining && nSttNd == nEndNd )
3237 {
3238 if ( SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3239 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) )
3240 {
3241 *pWrtShell->GetCursor()->GetMark() = *pWrtShell->GetCursor()->End();
3242 pWrtShell->GetCursor()->GetPoint()->Assign(*pStart->GetContentNode(), nSttCnt);
3243 }
3244 }
3245
3246 m_rDoc.getIDocumentState().SetModified();
3247 }
3248
InsertGraphic(const SwPaM & rRg,const OUString & rGrfName,const OUString & rFltName,const Graphic * pGraphic,const SfxItemSet * pFlyAttrSet,const SfxItemSet * pGrfAttrSet,SwFrameFormat * pFrameFormat)3249 SwFlyFrameFormat* DocumentContentOperationsManager::InsertGraphic(
3250 const SwPaM &rRg,
3251 const OUString& rGrfName,
3252 const OUString& rFltName,
3253 const Graphic* pGraphic,
3254 const SfxItemSet* pFlyAttrSet,
3255 const SfxItemSet* pGrfAttrSet,
3256 SwFrameFormat* pFrameFormat )
3257 {
3258 if( !pFrameFormat )
3259 pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC );
3260 SwGrfNode* pSwGrfNode = SwNodes::MakeGrfNode(
3261 m_rDoc.GetNodes().GetEndOfAutotext(),
3262 rGrfName, rFltName, pGraphic,
3263 m_rDoc.GetDfltGrfFormatColl() );
3264 SwFlyFrameFormat* pSwFlyFrameFormat = InsNoTextNode( *rRg.GetPoint(), pSwGrfNode,
3265 pFlyAttrSet, pGrfAttrSet, pFrameFormat );
3266 return pSwFlyFrameFormat;
3267 }
3268
InsertEmbObject(const SwPaM & rRg,const svt::EmbeddedObjectRef & xObj,SfxItemSet * pFlyAttrSet)3269 SwFlyFrameFormat* DocumentContentOperationsManager::InsertEmbObject(
3270 const SwPaM &rRg, const svt::EmbeddedObjectRef& xObj,
3271 SfxItemSet* pFlyAttrSet)
3272 {
3273 sal_uInt16 nId = RES_POOLFRM_OLE;
3274 if (xObj.is())
3275 {
3276 SvGlobalName aClassName( xObj->getClassID() );
3277 if (SotExchange::IsMath(aClassName))
3278 {
3279 nId = RES_POOLFRM_FORMEL;
3280 }
3281 }
3282
3283 SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( nId );
3284
3285 return InsNoTextNode( *rRg.GetPoint(), m_rDoc.GetNodes().MakeOLENode(
3286 m_rDoc.GetNodes().GetEndOfAutotext(),
3287 xObj,
3288 m_rDoc.GetDfltGrfFormatColl() ),
3289 pFlyAttrSet, nullptr,
3290 pFrameFormat );
3291 }
3292
InsertOLE(const SwPaM & rRg,const OUString & rObjName,sal_Int64 nAspect,const SfxItemSet * pFlyAttrSet,const SfxItemSet * pGrfAttrSet)3293 SwFlyFrameFormat* DocumentContentOperationsManager::InsertOLE(const SwPaM &rRg, const OUString& rObjName,
3294 sal_Int64 nAspect,
3295 const SfxItemSet* pFlyAttrSet,
3296 const SfxItemSet* pGrfAttrSet)
3297 {
3298 SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_OLE );
3299
3300 return InsNoTextNode( *rRg.GetPoint(),
3301 m_rDoc.GetNodes().MakeOLENode(
3302 m_rDoc.GetNodes().GetEndOfAutotext(),
3303 rObjName,
3304 nAspect,
3305 m_rDoc.GetDfltGrfFormatColl(),
3306 nullptr ),
3307 pFlyAttrSet, pGrfAttrSet,
3308 pFrameFormat );
3309 }
3310
ReRead(SwPaM & rPam,const OUString & rGrfName,const OUString & rFltName,const Graphic * pGraphic)3311 void DocumentContentOperationsManager::ReRead( SwPaM& rPam, const OUString& rGrfName,
3312 const OUString& rFltName, const Graphic* pGraphic )
3313 {
3314 SwGrfNode *pGrfNd;
3315 if( !(( !rPam.HasMark()
3316 || rPam.GetPoint()->GetNodeIndex() == rPam.GetMark()->GetNodeIndex() )
3317 && nullptr != ( pGrfNd = rPam.GetPoint()->GetNode().GetGrfNode() )) )
3318 return;
3319
3320 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3321 {
3322 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoReRead>(rPam, *pGrfNd));
3323 }
3324
3325 // Because we don't know if we can mirror the graphic, the mirror attribute is always reset
3326 if( MirrorGraph::Dont != pGrfNd->GetSwAttrSet().
3327 GetMirrorGrf().GetValue() )
3328 pGrfNd->SetAttr( SwMirrorGrf() );
3329
3330 pGrfNd->ReRead( rGrfName, rFltName, pGraphic );
3331 m_rDoc.getIDocumentState().SetModified();
3332 }
3333
3334 // Insert drawing object, which has to be already inserted in the DrawModel
InsertDrawObj(const SwPaM & rRg,SdrObject & rDrawObj,const SfxItemSet & rFlyAttrSet)3335 SwDrawFrameFormat* DocumentContentOperationsManager::InsertDrawObj(
3336 const SwPaM &rRg,
3337 SdrObject& rDrawObj,
3338 const SfxItemSet& rFlyAttrSet )
3339 {
3340 SwDrawFrameFormat* pFormat = m_rDoc.MakeDrawFrameFormat( UIName(), m_rDoc.GetDfltFrameFormat() );
3341
3342 const SwFormatAnchor* pAnchor = rFlyAttrSet.GetItemIfSet( RES_ANCHOR, false );
3343 pFormat->SetFormatAttr( rFlyAttrSet );
3344
3345 // Didn't set the Anchor yet?
3346 // DrawObjecte must never end up in the Header/Footer!
3347 RndStdIds eAnchorId = pAnchor != nullptr ? pAnchor->GetAnchorId() : pFormat->GetAnchor().GetAnchorId();
3348 const bool bIsAtContent = (RndStdIds::FLY_AT_PAGE != eAnchorId);
3349
3350 const SwPosition* pChkPos = nullptr;
3351 if ( pAnchor == nullptr )
3352 {
3353 pChkPos = rRg.GetPoint();
3354 }
3355 else if ( bIsAtContent )
3356 {
3357 pChkPos =
3358 pAnchor->GetContentAnchor() ? pAnchor->GetContentAnchor() : rRg.GetPoint();
3359 }
3360
3361 // allow drawing objects in header/footer, but control objects aren't allowed in header/footer.
3362 if( pChkPos != nullptr
3363 && ::CheckControlLayer( &rDrawObj )
3364 && m_rDoc.IsInHeaderFooter( pChkPos->GetNode() ) )
3365 {
3366 // apply at-page anchor format
3367 eAnchorId = RndStdIds::FLY_AT_PAGE;
3368 pFormat->SetFormatAttr( SwFormatAnchor( eAnchorId ) );
3369 }
3370 else if( pAnchor == nullptr
3371 || ( bIsAtContent
3372 && pAnchor->GetAnchorNode() == nullptr ) )
3373 {
3374 // apply anchor format
3375 SwFormatAnchor aAnch( pAnchor != nullptr ? *pAnchor : pFormat->GetAnchor() );
3376 eAnchorId = aAnch.GetAnchorId();
3377 if ( eAnchorId == RndStdIds::FLY_AT_FLY )
3378 {
3379 const SwStartNode* pStartNode = rRg.GetPointNode().FindFlyStartNode();
3380 assert(pStartNode);
3381 SwPosition aPos(*pStartNode);
3382 aAnch.SetAnchor( &aPos );
3383 }
3384 else
3385 {
3386 aAnch.SetAnchor( rRg.GetPoint() );
3387 if ( eAnchorId == RndStdIds::FLY_AT_PAGE )
3388 {
3389 eAnchorId = dynamic_cast<const SdrUnoObj*>( &rDrawObj) != nullptr ? RndStdIds::FLY_AS_CHAR : RndStdIds::FLY_AT_PARA;
3390 aAnch.SetType( eAnchorId );
3391 }
3392 }
3393 pFormat->SetFormatAttr( aAnch );
3394 }
3395
3396 // insert text attribute for as-character anchored drawing object
3397 if ( eAnchorId == RndStdIds::FLY_AS_CHAR )
3398 {
3399 bool bAnchorAtPageAsFallback = true;
3400 const SwFormatAnchor& rDrawObjAnchorFormat = pFormat->GetAnchor();
3401 if ( rDrawObjAnchorFormat.GetAnchorNode() != nullptr )
3402 {
3403 SwTextNode* pAnchorTextNode =
3404 rDrawObjAnchorFormat.GetAnchorNode()->GetTextNode();
3405 if ( pAnchorTextNode != nullptr )
3406 {
3407 const sal_Int32 nStt = rDrawObjAnchorFormat.GetContentAnchor()->GetContentIndex();
3408 SwFormatFlyCnt aFormat( pFormat );
3409 pAnchorTextNode->InsertItem( aFormat, nStt, nStt );
3410 bAnchorAtPageAsFallback = false;
3411 }
3412 }
3413
3414 if ( bAnchorAtPageAsFallback )
3415 {
3416 OSL_ENSURE( false, "DocumentContentOperationsManager::InsertDrawObj(..) - missing content anchor for as-character anchored drawing object --> anchor at-page" );
3417 pFormat->SetFormatAttr( SwFormatAnchor( RndStdIds::FLY_AT_PAGE ) );
3418 }
3419 }
3420
3421 SwDrawContact* pContact = new SwDrawContact( pFormat, &rDrawObj );
3422
3423 // Create Frames if necessary
3424 if( m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() )
3425 {
3426 // create layout representation
3427 pFormat->MakeFrames();
3428 // #i42319# - follow-up of #i35635#
3429 // move object to visible layer
3430 // #i79391#
3431 if ( pContact->GetAnchorFrame() )
3432 {
3433 pContact->MoveObjToVisibleLayer( &rDrawObj );
3434 }
3435 }
3436
3437 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3438 {
3439 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsLayFormat>(pFormat, SwNodeOffset(0), 0) );
3440 }
3441
3442 m_rDoc.getIDocumentState().SetModified();
3443 return pFormat;
3444 }
3445
SplitNode(const SwPosition & rPos,bool bChkTableStart)3446 bool DocumentContentOperationsManager::SplitNode( const SwPosition &rPos, bool bChkTableStart )
3447 {
3448 SwContentNode *pNode = rPos.GetNode().GetContentNode();
3449 if(nullptr == pNode)
3450 return false;
3451
3452 {
3453 // BUG 26675: Send DataChanged before deleting, so that we notice which objects are in scope.
3454 // After that they can be before/after the position.
3455 SwDataChanged aTmp( m_rDoc, rPos );
3456 }
3457
3458 SwUndoSplitNode* pUndo = nullptr;
3459 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3460 {
3461 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3462 // insert the Undo object (currently only for TextNode)
3463 if( pNode->IsTextNode() )
3464 {
3465 pUndo = new SwUndoSplitNode( m_rDoc, rPos, bChkTableStart );
3466 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo));
3467 }
3468 }
3469
3470 // Update the rsid of the old and the new node unless
3471 // the old node is split at the beginning or at the end
3472 SwTextNode *pTextNode = rPos.GetNode().GetTextNode();
3473 const sal_Int32 nPos = rPos.GetContentIndex();
3474 if( pTextNode && nPos && nPos != pTextNode->Len() )
3475 {
3476 m_rDoc.UpdateParRsid( pTextNode );
3477 }
3478
3479 //JP 28.01.97: Special case for SplitNode at table start:
3480 // If it is at the beginning of a Doc/Fly/Footer/... or right at after a table
3481 // then insert a paragraph before it.
3482 if( bChkTableStart && !rPos.GetContentIndex() && pNode->IsTextNode() )
3483 {
3484 SwNodeOffset nPrevPos = rPos.GetNodeIndex() - 1;
3485 SwTableNode* pTableNd;
3486 const SwNode* pNd = m_rDoc.GetNodes()[ nPrevPos ];
3487 if( pNd->IsStartNode() &&
3488 SwTableBoxStartNode == static_cast<const SwStartNode*>(pNd)->GetStartNodeType() &&
3489 nullptr != ( pTableNd = m_rDoc.GetNodes()[ --nPrevPos ]->GetTableNode() ) &&
3490 ((( pNd = m_rDoc.GetNodes()[ --nPrevPos ])->IsStartNode() &&
3491 SwTableBoxStartNode != static_cast<const SwStartNode*>(pNd)->GetStartNodeType() )
3492 || ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsTableNode() )
3493 || pNd->IsContentNode() ))
3494 {
3495 if( pNd->IsContentNode() )
3496 {
3497 //JP 30.04.99 Bug 65660:
3498 // There are no page breaks outside of the normal body area,
3499 // so this is not a valid condition to insert a paragraph.
3500 if( nPrevPos < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() )
3501 pNd = nullptr;
3502 else
3503 {
3504 // Only if the table has page breaks!
3505 const SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat();
3506 if( SfxItemState::SET != pFrameFormat->GetItemState(RES_PAGEDESC, false) &&
3507 SfxItemState::SET != pFrameFormat->GetItemState( RES_BREAK, false ) )
3508 pNd = nullptr;
3509 }
3510 }
3511
3512 if( pNd )
3513 {
3514 SwTextNode* pTextNd = m_rDoc.GetNodes().MakeTextNode(
3515 *pTableNd,
3516 m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ));
3517 if( pTextNd )
3518 {
3519 const_cast<SwPosition&>(rPos).Assign( pTableNd->GetIndex() - SwNodeOffset(1) );
3520
3521 // only add page breaks/styles to the body area
3522 if( nPrevPos > m_rDoc.GetNodes().GetEndOfExtras().GetIndex() )
3523 {
3524 SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat();
3525 const SfxPoolItem *pItem;
3526 if( SfxItemState::SET == pFrameFormat->GetItemState( RES_PAGEDESC,
3527 false, &pItem ) )
3528 {
3529 pTextNd->SetAttr( *pItem );
3530 pFrameFormat->ResetFormatAttr( RES_PAGEDESC );
3531 }
3532 if( SfxItemState::SET == pFrameFormat->GetItemState( RES_BREAK,
3533 false, &pItem ) )
3534 {
3535 pTextNd->SetAttr( *pItem );
3536 pFrameFormat->ResetFormatAttr( RES_BREAK );
3537 }
3538 }
3539
3540 if( pUndo )
3541 pUndo->SetTableFlag();
3542 m_rDoc.getIDocumentState().SetModified();
3543 return true;
3544 }
3545 }
3546 }
3547 }
3548
3549 const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
3550 pContentStore->Save( m_rDoc, rPos.GetNodeIndex(), rPos.GetContentIndex(), true );
3551 assert(pNode->IsTextNode());
3552 std::function<void (SwTextNode *, sw::mark::RestoreMode, bool bAtStart)> restoreFunc(
3553 [&](SwTextNode *const, sw::mark::RestoreMode const eMode, bool const bAtStart)
3554 {
3555 if (!pContentStore->Empty())
3556 { // move all bookmarks, TOXMarks, FlyAtCnt
3557 pContentStore->Restore(m_rDoc, rPos.GetNodeIndex()-SwNodeOffset(1), 0, true, bAtStart && (eMode & sw::mark::RestoreMode::Flys), eMode);
3558 }
3559 if (eMode & sw::mark::RestoreMode::NonFlys)
3560 {
3561 // To-Do - add 'SwExtraRedlineTable' also ?
3562 if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
3563 (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() &&
3564 !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()))
3565 {
3566 SwPaM aPam( rPos );
3567 aPam.SetMark();
3568 aPam.Move( fnMoveBackward );
3569 if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
3570 {
3571 m_rDoc.getIDocumentRedlineAccess().AppendRedline(
3572 new SwRangeRedline(RedlineType::Insert, aPam), true);
3573 }
3574 else
3575 {
3576 m_rDoc.getIDocumentRedlineAccess().SplitRedline(aPam);
3577 }
3578 }
3579 }
3580 });
3581 pNode->GetTextNode()->SplitContentNode(rPos, &restoreFunc);
3582
3583 m_rDoc.getIDocumentState().SetModified();
3584 return true;
3585 }
3586
AppendTextNode(SwPosition & rPos)3587 bool DocumentContentOperationsManager::AppendTextNode( SwPosition& rPos )
3588 {
3589 // create new node before EndOfContent
3590 SwTextNode * pCurNode = rPos.GetNode().GetTextNode();
3591 if( !pCurNode )
3592 {
3593 // so then one can be created!
3594 SwNodeIndex aIdx( rPos.GetNode(), 1 );
3595 pCurNode = m_rDoc.GetNodes().MakeTextNode( aIdx.GetNode(),
3596 m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ));
3597 }
3598 else
3599 pCurNode = pCurNode->AppendNode( rPos )->GetTextNode();
3600
3601 rPos.Adjust(SwNodeOffset(1));
3602
3603 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3604 {
3605 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsert>( rPos.GetNode() ) );
3606 }
3607
3608 // To-Do - add 'SwExtraRedlineTable' also ?
3609 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ))
3610 {
3611 SwPaM aPam( rPos );
3612 aPam.SetMark();
3613 aPam.Move( fnMoveBackward );
3614 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
3615 m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
3616 else
3617 m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
3618 }
3619
3620 m_rDoc.getIDocumentState().SetModified();
3621 return true;
3622 }
3623
ReplaceRange(SwPaM & rPam,const OUString & rStr,const bool bRegExReplace)3624 bool DocumentContentOperationsManager::ReplaceRange( SwPaM& rPam, const OUString& rStr,
3625 const bool bRegExReplace )
3626 {
3627 // unfortunately replace works slightly differently from delete,
3628 // so we cannot use lcl_DoWithBreaks here...
3629
3630 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
3631
3632 SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() );
3633 aPam.Normalize(false);
3634 if (aPam.GetPoint()->GetNode() != aPam.GetMark()->GetNode())
3635 {
3636 aPam.Move(fnMoveBackward);
3637 }
3638 OSL_ENSURE((aPam.GetPoint()->GetNode() == aPam.GetMark()->GetNode()), "invalid pam?");
3639
3640 sw::CalcBreaks(Breaks, aPam);
3641
3642 while (!Breaks.empty() // skip over prefix of dummy chars
3643 && (aPam.GetMark()->GetNodeIndex() == Breaks.begin()->first)
3644 && (aPam.GetMark()->GetContentIndex() == Breaks.begin()->second))
3645 {
3646 // skip!
3647 aPam.GetMark()->AdjustContent(+1); // always in bounds if Breaks valid
3648 Breaks.erase(Breaks.begin());
3649 }
3650 *rPam.Start() = *aPam.GetMark(); // update start of original pam w/ prefix
3651
3652 if (Breaks.empty())
3653 {
3654 // park aPam somewhere so it does not point to node that is deleted
3655 aPam.DeleteMark();
3656 aPam.GetPoint()->Assign(m_rDoc.GetNodes().GetEndOfContent());
3657 return ReplaceRangeImpl(rPam, rStr, bRegExReplace); // original pam!
3658 }
3659
3660 // Deletion must be split into several parts if the text node
3661 // contains a text attribute with end and with dummy character
3662 // and the selection does not contain the text attribute completely,
3663 // but overlaps its start (left), where the dummy character is.
3664
3665 bool bRet( true );
3666 // iterate from end to start, to avoid invalidating the offsets!
3667 auto iter( Breaks.rbegin() );
3668 SwNodeOffset nOffset(0);
3669 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
3670 OSL_ENSURE(aPam.GetPoint() == aPam.End(), "wrong!");
3671 SwPosition & rEnd( *aPam.End() );
3672 SwPosition & rStart( *aPam.Start() );
3673
3674 // set end of temp pam to original end (undo Move backward above)
3675 rEnd = *rPam.End();
3676 // after first deletion, rEnd will point into the original text node again!
3677
3678 while (iter != Breaks.rend())
3679 {
3680 rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
3681 if (rStart < rEnd) // check if part is empty
3682 {
3683 bRet &= (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
3684 ? DeleteAndJoinWithRedlineImpl(aPam, SwDeleteFlags::Default)
3685 : DeleteAndJoinImpl(aPam, SwDeleteFlags::Default);
3686 nOffset = iter->first - rStart.GetNodeIndex(); // deleted fly nodes...
3687 }
3688 rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
3689 ++iter;
3690 }
3691
3692 rStart = *rPam.Start(); // set to original start
3693 assert(rStart < rEnd && "replace part empty!");
3694 if (rStart < rEnd) // check if part is empty
3695 {
3696 bRet &= ReplaceRangeImpl(aPam, rStr, bRegExReplace);
3697 }
3698
3699 rPam = aPam; // update original pam (is this required?)
3700
3701 return bRet;
3702 }
3703
InsertPoolItem(const SwPaM & rRg,const SfxPoolItem & rHt,const SetAttrMode nFlags,SwRootFrame const * const pLayout,SwTextAttr ** ppNewTextAttr)3704 bool DocumentContentOperationsManager::InsertPoolItem(
3705 const SwPaM &rRg,
3706 const SfxPoolItem &rHt,
3707 const SetAttrMode nFlags,
3708 SwRootFrame const*const pLayout,
3709 SwTextAttr **ppNewTextAttr)
3710 {
3711 SwHistory* pHistory = nullptr;
3712 SwDataChanged aTmp( rRg );
3713 std::unique_ptr<SwUndoAttr> pUndoAttr;
3714 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3715 {
3716 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3717 pUndoAttr.reset(new SwUndoAttr( rRg, rHt, nFlags ));
3718 pHistory = &pUndoAttr->GetHistory();
3719 }
3720
3721 if (nFlags & SetAttrMode::REMOVE_ALL_ATTR)
3722 {
3723 std::shared_ptr<const SfxItemSet> pDelSet = lcl_createDelSet(m_rDoc);
3724 SwPosition aStart(*rRg.GetMark()->GetNode().GetContentNode(), rRg.GetMark()->GetContentIndex());
3725 SwPosition aEnd(*rRg.GetPoint()->GetNode().GetContentNode(), rRg.GetPoint()->GetContentIndex());
3726 sw::DocumentContentOperationsManager::ParaRstFormat aPara(
3727 &aStart, &aEnd, pHistory, nullptr, nullptr /* //TODO: is layout required? m_rDoc.GetLayout()*/);
3728 // aPara.pFormatColl = pPara->pFormatColl;
3729 aPara.bReset = true;
3730 // #i62675#
3731 aPara.bResetListAttrs = true;
3732 aPara.bResetAllCharAttrs = true;
3733 aPara.pDelSet = pDelSet.get();
3734 m_rDoc.GetNodes().ForEach(
3735 aStart.GetNode().GetIndex(),
3736 aEnd.GetNode().GetIndex(),
3737 ::sw::DocumentContentOperationsManager::lcl_RstTextAttr,
3738 &aPara);
3739 }
3740
3741 SfxItemSet aSet( m_rDoc.GetAttrPool(), rHt.Which(), rHt.Which() );
3742 aSet.Put( rHt );
3743 const bool bRet = lcl_InsAttr(m_rDoc, rRg, aSet, nFlags, pUndoAttr.get(), pLayout, ppNewTextAttr);
3744
3745 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3746 {
3747 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) );
3748 }
3749
3750 if( bRet )
3751 {
3752 m_rDoc.getIDocumentState().SetModified();
3753 }
3754 return bRet;
3755 }
3756
InsertItemSet(const SwPaM & rRg,const SfxItemSet & rSet,const SetAttrMode nFlags,SwRootFrame const * const pLayout)3757 void DocumentContentOperationsManager::InsertItemSet ( const SwPaM &rRg, const SfxItemSet &rSet,
3758 const SetAttrMode nFlags, SwRootFrame const*const pLayout)
3759 {
3760 SwDataChanged aTmp( rRg );
3761 std::unique_ptr<SwUndoAttr> pUndoAttr;
3762 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3763 {
3764 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3765 pUndoAttr.reset(new SwUndoAttr( rRg, rSet, nFlags ));
3766 }
3767
3768 bool bRet = lcl_InsAttr(m_rDoc, rRg, rSet, nFlags, pUndoAttr.get(), pLayout, /*ppNewTextAttr*/nullptr );
3769
3770 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3771 {
3772 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) );
3773 }
3774
3775 if( bRet )
3776 m_rDoc.getIDocumentState().SetModified();
3777 }
3778
RemoveLeadingWhiteSpace(const SwPosition & rPos)3779 void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(const SwPosition & rPos )
3780 {
3781 const SwTextNode* pTNd = rPos.GetNode().GetTextNode();
3782 if ( !pTNd )
3783 return;
3784
3785 const OUString& rText = pTNd->GetText();
3786 sal_Int32 nIdx = 0;
3787 while (nIdx < rText.getLength())
3788 {
3789 sal_Unicode const cCh = rText[nIdx];
3790 if (('\t' != cCh) && (' ' != cCh))
3791 {
3792 break;
3793 }
3794 ++nIdx;
3795 }
3796
3797 if ( nIdx > 0 )
3798 {
3799 SwPaM aPam(rPos);
3800 aPam.GetPoint()->SetContent(0);
3801 aPam.SetMark();
3802 aPam.GetMark()->SetContent(nIdx);
3803 DeleteRange( aPam );
3804 }
3805 }
3806
RemoveLeadingWhiteSpace(SwPaM & rPaM)3807 void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(SwPaM& rPaM )
3808 {
3809 for (SwPaM& rSel :rPaM.GetRingContainer())
3810 {
3811 SwNodeOffset nStt = rSel.Start()->GetNodeIndex();
3812 SwNodeOffset nEnd = rSel.End()->GetNodeIndex();
3813 for (SwNodeOffset nPos = nStt; nPos<=nEnd; nPos++)
3814 RemoveLeadingWhiteSpace(SwPosition(rSel.GetBound().GetNodes(), nPos));
3815 }
3816 }
3817
3818 // Copy method from SwDoc - "copy Flys in Flys"
3819 /// note: rRg/rInsPos *exclude* a partially selected start text node;
3820 /// pCopiedPaM *includes* a partially selected start text node
CopyWithFlyInFly(const SwNodeRange & rRg,SwNode & rInsPos,const std::pair<const SwPaM &,const SwPosition &> * pCopiedPaM,const bool bMakeNewFrames,const bool bDelRedlines,const bool bCopyFlyAtFly,SwCopyFlags const flags) const3821 void DocumentContentOperationsManager::CopyWithFlyInFly(
3822 const SwNodeRange& rRg,
3823 SwNode& rInsPos,
3824 const std::pair<const SwPaM&, const SwPosition&>* pCopiedPaM /*and real insert pos*/,
3825 const bool bMakeNewFrames,
3826 const bool bDelRedlines,
3827 const bool bCopyFlyAtFly,
3828 SwCopyFlags const flags) const
3829 {
3830 assert(!pCopiedPaM || pCopiedPaM->first.End()->GetNode() == rRg.aEnd.GetNode());
3831 assert(!pCopiedPaM || pCopiedPaM->second.GetNode() <= rInsPos);
3832
3833 SwDoc& rDest = rInsPos.GetDoc();
3834 SwNodeIndex aSavePos( rInsPos );
3835
3836 SwPaM aCopiedPaM(rRg.aStart, rRg.aEnd);
3837 if (pCopiedPaM)
3838 aCopiedPaM = pCopiedPaM->first;
3839
3840 if (rRg.aStart != rRg.aEnd)
3841 {
3842 bool bEndIsEqualEndPos = rInsPos == rRg.aEnd.GetNode();
3843 --aSavePos;
3844 SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 );
3845 auto savedEndContentIndex = aCopiedPaM.End()->GetContentIndex();
3846
3847 // insert behind the already copied start node
3848 m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, false, true );
3849 aRedlRest.Restore();
3850
3851 if (bEndIsEqualEndPos)
3852 {
3853 const_cast<SwNodeIndex&>(rRg.aEnd).Assign(aSavePos.GetNode(), +1);
3854 // pCopiedPaM->first now spans a range from the start of the original selection
3855 // to the end of newly added text, and the insertion point is in the middle of
3856 // that range. Adjust the local copy to cover the original copied PaM.
3857 aCopiedPaM.End()->Assign(rRg.aEnd, savedEndContentIndex);
3858 }
3859 }
3860
3861 // Also copy all bookmarks
3862 // guess this must be done before the DelDummyNodes below as that
3863 // deletes nodes so would mess up the index arithmetic
3864 // sw_fieldmarkhide: also needs to be done before making frames
3865 if (m_rDoc.getIDocumentMarkAccess()->getAllMarksCount())
3866 {
3867 SwPosition targetPos(aSavePos, SwNodeOffset(rRg.aStart != rRg.aEnd ? +1 : 0));
3868 if (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->GetNode())
3869 {
3870 // there is 1 (partially selected, maybe) paragraph before
3871 assert(SwNodeIndex(rRg.aStart, -1) == pCopiedPaM->first.Start()->GetNode());
3872 // only use the passed in target SwPosition if the source PaM point
3873 // is on a different node; if it was the same node then the target
3874 // position was likely moved along by the copy operation and now
3875 // points to the end of the range!
3876 targetPos = pCopiedPaM->second;
3877 }
3878
3879 sw::CopyBookmarks(aCopiedPaM, targetPos, flags);
3880 }
3881
3882 if (rRg.aStart != rRg.aEnd)
3883 {
3884 ++aSavePos;
3885 }
3886
3887 #if OSL_DEBUG_LEVEL > 0
3888 {
3889 //JP 17.06.99: Bug 66973 - check count only if the selection is in
3890 // the same section or there's no section, because sections that are
3891 // not fully selected are not copied.
3892 const SwSectionNode* pSSectNd = rRg.aStart.GetNode().FindSectionNode();
3893 SwNodeIndex aTmpI( rRg.aEnd, -1 );
3894 const SwSectionNode* pESectNd = aTmpI.GetNode().FindSectionNode();
3895 if( pSSectNd == pESectNd &&
3896 !rRg.aStart.GetNode().IsSectionNode() &&
3897 !aTmpI.GetNode().IsEndNode() )
3898 {
3899 // If the range starts with a SwStartNode, it isn't copied
3900 SwNodeOffset offset( (rRg.aStart.GetNode().GetNodeType() != SwNodeType::Start) ? 1 : 0 );
3901 OSL_ENSURE( rInsPos.GetIndex() - aSavePos.GetIndex() ==
3902 rRg.aEnd.GetIndex() - rRg.aStart.GetIndex() - 1 + offset,
3903 "An insufficient number of nodes were copied!" );
3904 }
3905 }
3906 #endif
3907
3908 {
3909 ::sw::UndoGuard const undoGuard(rDest.GetIDocumentUndoRedo());
3910 // this must happen before lcl_DeleteRedlines() because it counts nodes
3911 CopyFlyInFlyImpl(rRg, pCopiedPaM ? &aCopiedPaM : nullptr,
3912 // see comment below regarding use of pCopiedPaM->second
3913 (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->GetNode())
3914 ? pCopiedPaM->second.GetNode()
3915 : aSavePos.GetNode(),
3916 bCopyFlyAtFly,
3917 flags,
3918 false);
3919 }
3920
3921 SwNodeRange aCpyRange( aSavePos.GetNode(), rInsPos );
3922
3923 if( bDelRedlines && ( RedlineFlags::DeleteRedlines & rDest.getIDocumentRedlineAccess().GetRedlineFlags() ))
3924 lcl_DeleteRedlines( rRg, aCpyRange );
3925
3926 rDest.GetNodes().DelDummyNodes( aCpyRange );
3927
3928 // tdf#159023 create layout frames after DelDummyNodes():
3929 // InsertCnt_() does early return on the first SwPlaceholderNode
3930 if (rRg.aStart != rRg.aEnd)
3931 {
3932 --aSavePos; // restore temporarily...
3933 bool isRecreateEndNode(false);
3934 if (bMakeNewFrames) // tdf#130685 only after aRedlRest
3935 { // recreate from previous node (could be merged now)
3936 o3tl::sorted_vector<SwTextFrame*> frames;
3937 SwTextNode * pNode(aSavePos.GetNode().GetTextNode());
3938 SwTextNode *const pEndNode(rInsPos.GetTextNode());
3939 if (pEndNode)
3940 {
3941 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode);
3942 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
3943 {
3944 if (pFrame->getRootFrame()->HasMergedParas())
3945 {
3946 frames.insert(pFrame);
3947 // tdf#135061 check if end node is merged to a preceding node
3948 if (pNode == nullptr && pFrame->GetMergedPara()
3949 && pFrame->GetMergedPara()->pFirstNode->GetIndex() < aSavePos.GetIndex())
3950 {
3951 pNode = pFrame->GetMergedPara()->pFirstNode;
3952 }
3953 }
3954 }
3955 }
3956 if (pNode != nullptr)
3957 {
3958 sw::RecreateStartTextFrames(*pNode);
3959 if (!frames.empty())
3960 { // tdf#132187 check if the end node needs new frames
3961 assert(pEndNode);
3962 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode);
3963 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
3964 {
3965 if (pFrame->getRootFrame()->HasMergedParas())
3966 {
3967 auto const it(frames.find(pFrame));
3968 if (it != frames.end())
3969 {
3970 frames.erase(it);
3971 }
3972 }
3973 }
3974 if (!frames.empty()) // existing frame was deleted
3975 { // all layouts because MakeFrames recreates all layouts
3976 pEndNode->DelFrames(nullptr);
3977 isRecreateEndNode = true;
3978 }
3979 }
3980 }
3981 }
3982 bool const isAtStartOfSection(aSavePos.GetNode().IsStartNode());
3983 ++aSavePos;
3984 if (bMakeNewFrames)
3985 {
3986 // it's possible that CheckParaRedlineMerge() deleted frames
3987 // on rInsPos so have to include it, but it must not be included
3988 // if it was the first node in the document so that MakeFrames()
3989 // will find the existing (wasn't deleted) frame on it
3990 SwNodeIndex const end(rInsPos,
3991 SwNodeOffset((!isRecreateEndNode || isAtStartOfSection)
3992 ? 0 : +1));
3993 ::MakeFrames(rDest, aSavePos.GetNode(), end.GetNode());
3994 }
3995 }
3996 }
3997
3998 // note: for the redline Show/Hide this must be in sync with
3999 // SwRangeRedline::CopyToSection()/DelCopyOfSection()/MoveFromSection()
CopyFlyInFlyImpl(const SwNodeRange & rRg,SwPaM const * const pCopiedPaM,SwNode & rStartIdx,const bool bCopyFlyAtFly,SwCopyFlags const flags,bool const bMakeNewFrames) const4000 void DocumentContentOperationsManager::CopyFlyInFlyImpl(
4001 const SwNodeRange& rRg,
4002 SwPaM const*const pCopiedPaM,
4003 SwNode& rStartIdx,
4004 const bool bCopyFlyAtFly,
4005 SwCopyFlags const flags,
4006 bool const bMakeNewFrames) const
4007 {
4008 assert(!pCopiedPaM || pCopiedPaM->End()->GetNode() == rRg.aEnd.GetNode());
4009
4010 // First collect all Flys, sort them according to their ordering number,
4011 // and then only copy them. This maintains the ordering numbers (which are only
4012 // managed in the DrawModel).
4013 SwDoc& rDest = rStartIdx.GetDoc();
4014 std::set< ZSortFly > aSet;
4015 const size_t nArrLen = m_rDoc.GetSpzFrameFormats()->size();
4016
4017 SwTextBoxHelper::SavedLink aOldTextBoxes;
4018 SwTextBoxHelper::saveLinks(*m_rDoc.GetSpzFrameFormats(), aOldTextBoxes);
4019
4020 for ( size_t n = 0; n < nArrLen; ++n )
4021 {
4022 SwFrameFormat* pFormat = (*m_rDoc.GetSpzFrameFormats())[n];
4023 SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
4024 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
4025 if ( !pAnchorNode )
4026 continue;
4027 bool bAdd = false;
4028 SwNodeOffset nSkipAfter = pAnchorNode->GetIndex();
4029 SwNodeOffset nStart = rRg.aStart.GetIndex();
4030 switch ( pAnchor->GetAnchorId() )
4031 {
4032 case RndStdIds::FLY_AT_FLY:
4033 if(bCopyFlyAtFly)
4034 ++nSkipAfter;
4035 else if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove())
4036 ++nStart;
4037 break;
4038 case RndStdIds::FLY_AT_PARA:
4039 {
4040 bAdd = IsSelectFrameAnchoredAtPara(*pAnchor->GetContentAnchor(),
4041 pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart),
4042 pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd),
4043 (flags & SwCopyFlags::IsMoveToFly)
4044 ? DelContentType::AllMask|DelContentType::WriterfilterHack
4045 : DelContentType::AllMask);
4046 }
4047 break;
4048 case RndStdIds::FLY_AT_CHAR:
4049 {
4050 bAdd = IsDestroyFrameAnchoredAtChar(*pAnchor->GetContentAnchor(),
4051 pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart),
4052 pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd),
4053 (flags & SwCopyFlags::IsMoveToFly)
4054 ? DelContentType::AllMask|DelContentType::WriterfilterHack
4055 : DelContentType::AllMask);
4056 }
4057 break;
4058 default:
4059 continue;
4060 }
4061 if (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId())
4062 {
4063 if (nStart > nSkipAfter)
4064 continue;
4065 if (*pAnchorNode > rRg.aEnd.GetNode())
4066 continue;
4067 //frames at the last source node are not always copied:
4068 //- if the node is empty and is the last node of the document or a table cell
4069 // or a text frame then they have to be copied
4070 //- if the content index in this node is > 0 then paragraph and frame bound objects are copied
4071 //- to-character bound objects are copied if their index is <= nEndContentIndex
4072 if (*pAnchorNode < rRg.aEnd.GetNode())
4073 bAdd = true;
4074 if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move
4075 {
4076 if (!bAdd)
4077 {
4078 // technically old code checked nContent of AT_FLY which is pointless
4079 bAdd = pCopiedPaM && 0 < pCopiedPaM->End()->GetContentIndex();
4080 }
4081 }
4082 }
4083 if( bAdd )
4084 {
4085 aSet.insert( ZSortFly( pFormat, pAnchor, nArrLen + aSet.size() ));
4086 }
4087 }
4088
4089 // Store all copied (and also the newly created) frames in another array.
4090 // They are stored as matching the originals, so that we will be later
4091 // able to build the chains accordingly.
4092 std::vector< SwFrameFormat* > aVecSwFrameFormat;
4093 std::set< ZSortFly >::const_iterator it=aSet.begin();
4094
4095 while (it != aSet.end())
4096 {
4097 // #i59964#
4098 // correct determination of new anchor position
4099 SwFormatAnchor aAnchor( *(*it).GetAnchor() );
4100 assert( aAnchor.GetContentAnchor() != nullptr );
4101 SwPosition newPos = *aAnchor.GetContentAnchor();
4102 // for at-paragraph and at-character anchored objects the new anchor
4103 // position can *not* be determined by the difference of the current
4104 // anchor position to the start of the copied range, because not
4105 // complete selected sections in the copied range aren't copied - see
4106 // method <SwNodes::CopyNodes(..)>.
4107 // Thus, the new anchor position in the destination document is found
4108 // by counting the text nodes.
4109 if ((aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) ||
4110 (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) )
4111 {
4112 // First, determine number of anchor text node in the copied range.
4113 // Note: The anchor text node *have* to be inside the copied range.
4114 sal_uLong nAnchorTextNdNumInRange( 0 );
4115 bool bAnchorTextNdFound( false );
4116 // start at the first node for which flys are copied
4117 SwNodeIndex aIdx(pCopiedPaM ? pCopiedPaM->Start()->GetNode() : rRg.aStart.GetNode());
4118 while ( !bAnchorTextNdFound && aIdx <= rRg.aEnd )
4119 {
4120 if ( aIdx.GetNode().IsTextNode() )
4121 {
4122 ++nAnchorTextNdNumInRange;
4123 bAnchorTextNdFound = *aAnchor.GetAnchorNode() == aIdx.GetNode();
4124 }
4125
4126 ++aIdx;
4127 }
4128
4129 if ( !bAnchorTextNdFound )
4130 {
4131 // This case can *not* happen, but to be robust take the first
4132 // text node in the destination document.
4133 OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - anchor text node in copied range not found" );
4134 nAnchorTextNdNumInRange = 1;
4135 }
4136 // Second, search corresponding text node in destination document
4137 // by counting forward from start insert position <rStartIdx> the
4138 // determined number of text nodes.
4139 aIdx = rStartIdx;
4140 SwNodeIndex aAnchorNdIdx( rStartIdx );
4141 const SwNode& aEndOfContentNd =
4142 aIdx.GetNode().GetNodes().GetEndOfContent();
4143 while ( nAnchorTextNdNumInRange > 0 &&
4144 aIdx.GetNode() != aEndOfContentNd )
4145 {
4146 if ( aIdx.GetNode().IsTextNode() )
4147 {
4148 --nAnchorTextNdNumInRange;
4149 aAnchorNdIdx = aIdx;
4150 }
4151
4152 ++aIdx;
4153 }
4154 if ( !aAnchorNdIdx.GetNode().IsTextNode() )
4155 {
4156 // This case can *not* happen, but to be robust take the first
4157 // text node in the destination document.
4158 OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - found anchor node index isn't a text node" );
4159 aAnchorNdIdx = rStartIdx;
4160 while ( !aAnchorNdIdx.GetNode().IsTextNode() )
4161 {
4162 ++aAnchorNdIdx;
4163 }
4164 }
4165 // apply found anchor text node as new anchor position, but keep the content index
4166 // unchanged
4167 newPos.Assign( aAnchorNdIdx, newPos.GetContentIndex() );
4168 }
4169 else
4170 {
4171 SwNodeOffset nOffset = newPos.GetNodeIndex() - rRg.aStart.GetIndex();
4172 newPos.Assign( rStartIdx, nOffset );
4173 }
4174 // Set the character bound Flys back at the original character
4175 if ((RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) &&
4176 newPos.GetNode().IsTextNode() )
4177 {
4178 // only if pCopiedPaM: care about partially selected start node
4179 sal_Int32 const nContent = pCopiedPaM && pCopiedPaM->Start()->GetNode() == *aAnchor.GetAnchorNode()
4180 ? newPos.GetContentIndex() - pCopiedPaM->Start()->GetContentIndex()
4181 : newPos.GetContentIndex();
4182 newPos.SetContent(nContent);
4183 }
4184 aAnchor.SetAnchor( &newPos );
4185
4186 // Check recursion: if copying content inside the same frame, then don't copy the format.
4187 if( &rDest == &m_rDoc )
4188 {
4189 const SwFormatContent& rContent = (*it).GetFormat()->GetContent();
4190 const SwStartNode* pSNd;
4191 if( rContent.GetContentIdx() &&
4192 nullptr != ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ) &&
4193 pSNd->GetIndex() < rStartIdx.GetIndex() &&
4194 rStartIdx.GetIndex() < pSNd->EndOfSectionIndex() )
4195 {
4196 it = aSet.erase(it);
4197 continue;
4198 }
4199 }
4200
4201 // Ignore TextBoxes, they are already handled in
4202 // sw::DocumentLayoutManager::CopyLayoutFormat().
4203 if (SwTextBoxHelper::isTextBox(it->GetFormat(), RES_FLYFRMFMT))
4204 {
4205 it = aSet.erase(it);
4206 continue;
4207 }
4208
4209 // Copy the format and set the new anchor
4210 aVecSwFrameFormat.push_back( rDest.getIDocumentLayoutAccess().CopyLayoutFormat( *(*it).GetFormat(),
4211 aAnchor, false, bMakeNewFrames) );
4212 ++it;
4213 }
4214
4215 // Rebuild as much as possible of all chains that are available in the original,
4216 OSL_ENSURE( aSet.size() == aVecSwFrameFormat.size(), "Missing new Flys" );
4217 if ( aSet.size() != aVecSwFrameFormat.size() )
4218 return;
4219
4220 size_t n = 0;
4221 for (const auto& rFlyN : aSet)
4222 {
4223 const SwFrameFormat *pFormatN = rFlyN.GetFormat();
4224 const SwFormatChain &rChain = pFormatN->GetChain();
4225 size_t k = 0;
4226 for (const auto& rFlyK : aSet)
4227 {
4228 const SwFrameFormat *pFormatK = rFlyK.GetFormat();
4229 if ( rChain.GetPrev() == pFormatK )
4230 {
4231 ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]),
4232 static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]) );
4233 }
4234 else if ( rChain.GetNext() == pFormatK )
4235 {
4236 ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]),
4237 static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]) );
4238 }
4239 ++k;
4240 }
4241 ++n;
4242 }
4243
4244 // Re-create content property of draw formats, knowing how old shapes
4245 // were paired with old fly formats (aOldTextBoxes) and that aSet is
4246 // parallel with aVecSwFrameFormat.
4247 SwTextBoxHelper::restoreLinks(aSet, aVecSwFrameFormat, aOldTextBoxes);
4248 }
4249
4250 /*
4251 create ItemSet to be used in ParaRstFormat
4252 */
lcl_createDelSet(SwDoc & rDoc)4253 std::shared_ptr<SfxItemSet> DocumentContentOperationsManager::lcl_createDelSet(SwDoc& rDoc)
4254 {
4255 std::shared_ptr<SfxItemSet> pDelSet(new SfxItemSet(
4256 rDoc.GetAttrPool(), WhichRangesContainer(svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
4257 RES_TXTATR_INETFMT, RES_TXTATR_UNKNOWN_CONTAINER,
4258 RES_PARATR_BEGIN, RES_FRMATR_END - 1,
4259 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>)));
4260 o3tl::sorted_vector<sal_uInt16> aAttribs;
4261
4262 static constexpr std::pair<sal_uInt16, sal_uInt16> aResetableSetRange[] = {
4263 // tdf#40496: we don't want to change writing direction, so exclude RES_FRAMEDIR:
4264 { RES_TXTATR_CHARFMT,RES_TXTATR_CHARFMT },
4265 { RES_FRMATR_BEGIN, RES_FRAMEDIR - 1 },
4266 { RES_FRAMEDIR + 1, RES_FRMATR_END - 1 },
4267 { RES_CHRATR_BEGIN, RES_CHRATR_LANGUAGE - 1 },
4268 { RES_CHRATR_LANGUAGE + 1, RES_CHRATR_CJK_LANGUAGE - 1 },
4269 { RES_CHRATR_CJK_LANGUAGE + 1, RES_CHRATR_CTL_LANGUAGE - 1 },
4270 { RES_CHRATR_CTL_LANGUAGE + 1, RES_CHRATR_END - 1 },
4271 { RES_PARATR_BEGIN, RES_PARATR_END - 1 },
4272 { RES_PARATR_LIST_AUTOFMT, RES_PARATR_LIST_AUTOFMT },
4273 { RES_TXTATR_UNKNOWN_CONTAINER, RES_TXTATR_UNKNOWN_CONTAINER },
4274 { RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1 },
4275 };
4276 for (const auto& [nBegin, nEnd] : aResetableSetRange)
4277 {
4278 for (sal_uInt16 i = nBegin; i <= nEnd; ++i)
4279 aAttribs.insert( i );
4280 }
4281 for( auto it = aAttribs.rbegin(); it != aAttribs.rend(); ++it )
4282 {
4283 if( POOLATTR_END > *it )
4284 pDelSet->Put( *GetDfltAttr( *it ));
4285 }
4286 return pDelSet;
4287 }
4288
4289 /*
4290 * Reset the text's hard formatting
4291 */
4292 /** @params pArgs contains the document's ChrFormatTable
4293 * Is need for selections at the beginning/end and with no SSelection.
4294 */
lcl_RstTextAttr(SwNode * pNd,void * pArgs)4295 bool DocumentContentOperationsManager::lcl_RstTextAttr( SwNode* pNd, void* pArgs )
4296 {
4297 ParaRstFormat* pPara = static_cast<ParaRstFormat*>(pArgs);
4298 if (pPara->pLayout && pPara->pLayout->HasMergedParas()
4299 && pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)
4300 {
4301 return true; // skip hidden, since new items aren't applied
4302 }
4303 SwTextNode * pTextNode = pNd->GetTextNode();
4304 if( pTextNode && pTextNode->GetpSwpHints() )
4305 {
4306 SwContentIndex aSt( pTextNode, 0 );
4307 sal_Int32 nEnd = pTextNode->Len();
4308
4309 if( &pPara->pSttNd->GetNode() == pTextNode &&
4310 pPara->pSttNd->GetContentIndex() )
4311 aSt = pPara->pSttNd->GetContentIndex();
4312
4313 if( &pPara->pEndNd->GetNode() == pNd )
4314 nEnd = pPara->pEndNd->GetContentIndex();
4315
4316 if( pPara->pHistory )
4317 {
4318 // Save all attributes for the Undo.
4319 SwRegHistory aRHst( *pTextNode, pPara->pHistory );
4320 pTextNode->GetpSwpHints()->Register( &aRHst );
4321 pTextNode->RstTextAttr( aSt.GetIndex(), nEnd - aSt.GetIndex(), pPara->nWhich,
4322 pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange );
4323 if( pTextNode->GetpSwpHints() )
4324 pTextNode->GetpSwpHints()->DeRegister();
4325 }
4326 else
4327 pTextNode->RstTextAttr( aSt.GetIndex(), nEnd - aSt.GetIndex(), pPara->nWhich,
4328 pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange );
4329 }
4330 return true;
4331 }
4332
~DocumentContentOperationsManager()4333 DocumentContentOperationsManager::~DocumentContentOperationsManager()
4334 {
4335 }
4336 //Private methods
4337
DeleteAndJoinWithRedlineImpl(SwPaM & rPam,SwDeleteFlags const flags)4338 bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl(SwPaM & rPam, SwDeleteFlags const flags)
4339 {
4340 assert(m_rDoc.getIDocumentRedlineAccess().IsRedlineOn());
4341
4342 RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
4343
4344 if (*rPam.GetPoint() == *rPam.GetMark())
4345 {
4346 return false; // do not add empty redlines
4347 }
4348
4349 std::vector<std::unique_ptr<SwRangeRedline>> redlines;
4350 {
4351 auto pRedline(std::make_unique<SwRangeRedline>(RedlineType::Delete, rPam));
4352 if (pRedline->HasValidRange())
4353 {
4354 redlines.push_back(std::move(pRedline));
4355 }
4356 else // sigh ... why is such a selection even possible...
4357 { // split it up so we get one SwUndoRedlineDelete per inserted RL
4358 redlines = GetAllValidRanges(std::move(pRedline));
4359 }
4360 }
4361
4362 if (redlines.empty())
4363 {
4364 return false;
4365 }
4366
4367 // tdf#54819 current redlining needs also modification of paragraph style and
4368 // attributes added to the same grouped Undo
4369 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4370 m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
4371
4372 auto & rDMA(*m_rDoc.getIDocumentMarkAccess());
4373 std::vector<std::unique_ptr<SwUndo>> MarkUndos;
4374 for (auto iter = rDMA.getAnnotationMarksBegin();
4375 iter != rDMA.getAnnotationMarksEnd(); )
4376 {
4377 // tdf#111524 remove annotation marks that have their field
4378 // characters deleted
4379 SwPosition const& rEndPos((**iter).GetMarkEnd());
4380 if (*rPam.Start() < rEndPos && rEndPos <= *rPam.End())
4381 {
4382 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4383 {
4384 MarkUndos.emplace_back(std::make_unique<SwUndoDeleteBookmark>(**iter));
4385 }
4386 // iter is into annotation mark vector so must be dereferenced!
4387 rDMA.deleteMark(&**iter);
4388 // this invalidates iter, have to start over...
4389 iter = rDMA.getAnnotationMarksBegin();
4390 }
4391 else
4392 { // marks are sorted by start
4393 if (*rPam.End() < (**iter).GetMarkStart())
4394 {
4395 break;
4396 }
4397 ++iter;
4398 }
4399 }
4400
4401 // tdf#119019 accept tracked paragraph formatting to do not hide new deletions
4402 if (*rPam.GetPoint() != *rPam.GetMark())
4403 m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting(rPam);
4404
4405 std::vector<std::unique_ptr<SwUndoRedlineDelete>> undos;
4406 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4407 {
4408 // this should no longer happen in calls from the UI but maybe via API
4409 // (randomTest and testTdf54819 triggers it)
4410 SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask,
4411 "sw.core", "redlines will be moved in DeleteAndJoin");
4412 RedlineFlags eFlags = RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete;
4413 if (eOld & RedlineFlags::DontCombineRedlines)
4414 {
4415 // Reinstate disables redline compressing, don't drop that here.
4416 eFlags |= RedlineFlags::DontCombineRedlines;
4417 }
4418 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags(eFlags);
4419
4420 for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
4421 {
4422 assert(pRedline->HasValidRange());
4423 undos.emplace_back(std::make_unique<SwUndoRedlineDelete>(
4424 *pRedline, SwUndoId::DELETE, flags));
4425 }
4426 const SwRewriter aRewriter = undos.front()->GetRewriter();
4427 // can only group a single undo action
4428 if (MarkUndos.empty() && undos.size() == 1
4429 && m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4430 {
4431 SwUndo * const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() );
4432 SwUndoRedlineDelete *const pUndoRedlineDel(dynamic_cast<SwUndoRedlineDelete*>(pLastUndo));
4433 bool const bMerged = pUndoRedlineDel
4434 && pUndoRedlineDel->CanGrouping(*undos.front());
4435 if (!bMerged)
4436 {
4437 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(undos.front()));
4438 }
4439 undos.clear(); // prevent unmatched EndUndo
4440 }
4441 else
4442 {
4443 m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter);
4444 for (auto& it : MarkUndos)
4445 {
4446 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it));
4447 }
4448 for (auto & it : undos)
4449 {
4450 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it));
4451 }
4452 }
4453 }
4454
4455 for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
4456 {
4457 // note: 1. the pRedline can still be merged & deleted
4458 // 2. the impl. can even DeleteAndJoin the range => no plain PaM
4459 std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*pRedline->GetMark()));
4460 pCursor->SetMark();
4461 *pCursor->GetPoint() = *pRedline->GetPoint();
4462 m_rDoc.getIDocumentRedlineAccess().AppendRedline(pRedline.release(), true);
4463 // sw_redlinehide: 2 reasons why this is needed:
4464 // 1. it's the first redline in node => RedlineDelText was sent but ignored
4465 // 2. redline spans multiple nodes => must merge text frames
4466 sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor);
4467 }
4468 m_rDoc.getIDocumentState().SetModified();
4469
4470 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4471 {
4472 if (!undos.empty())
4473 {
4474 m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4475 }
4476 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
4477 }
4478
4479 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4480 m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4481
4482 return true;
4483 }
4484
DeleteAndJoinImpl(SwPaM & rPam,SwDeleteFlags const flags)4485 bool DocumentContentOperationsManager::DeleteAndJoinImpl(SwPaM & rPam, SwDeleteFlags const flags)
4486 {
4487 bool bJoinText, bJoinPrev;
4488 ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev );
4489
4490 bool const bSuccess( DeleteRangeImpl(rPam, flags) );
4491 if (!bSuccess)
4492 return false;
4493
4494 if( bJoinText )
4495 {
4496 ::sw_JoinText( rPam, bJoinPrev );
4497 }
4498
4499 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
4500 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()
4501 && !(flags & SwDeleteFlags::DontCompressRedlines))
4502 {
4503 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
4504 }
4505
4506 return true;
4507 }
4508
DeleteRangeImpl(SwPaM & rPam,SwDeleteFlags const flags)4509 bool DocumentContentOperationsManager::DeleteRangeImpl(SwPaM & rPam, SwDeleteFlags const flags)
4510 {
4511 // Move all cursors out of the deleted range, but first copy the
4512 // passed PaM, because it could be a cursor that would be moved!
4513 SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
4514 {
4515 SwPosition const pos(GetCorrPosition(aDelPam));
4516 ::PaMCorrAbs(aDelPam, pos);
4517 }
4518
4519 bool const bSuccess( DeleteRangeImplImpl(aDelPam, flags) );
4520 if (bSuccess)
4521 { // now copy position from temp copy to given PaM
4522 *rPam.GetPoint() = *aDelPam.GetPoint();
4523 }
4524
4525 return bSuccess;
4526 }
4527
DeleteRangeImplImpl(SwPaM & rPam,SwDeleteFlags const flags)4528 bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam, SwDeleteFlags const flags)
4529 {
4530 auto [pStart, pEnd] = rPam.StartEnd(); // SwPosition*
4531
4532 if (!rPam.HasMark()
4533 || (*pStart == *pEnd && !IsFlySelectedByCursor(m_rDoc, *pStart, *pEnd)))
4534 {
4535 return false;
4536 }
4537
4538 if( m_rDoc.GetAutoCorrExceptWord() )
4539 {
4540 // if necessary the saved Word for the exception
4541 if( m_rDoc.GetAutoCorrExceptWord()->IsDeleted() || pStart->GetNode() != pEnd->GetNode() ||
4542 pStart->GetContentIndex() + 1 != pEnd->GetContentIndex() ||
4543 !m_rDoc.GetAutoCorrExceptWord()->CheckDelChar( *pStart ))
4544 { m_rDoc.DeleteAutoCorrExceptWord(); }
4545 }
4546
4547 {
4548 // Delete all empty TextHints at the Mark's position
4549 SwTextNode* pTextNd = rPam.GetMark()->GetNode().GetTextNode();
4550 SwpHints* pHts;
4551 if( pTextNd && nullptr != ( pHts = pTextNd->GetpSwpHints()) && pHts->Count() )
4552 {
4553 const sal_Int32 nMkCntPos = rPam.GetMark()->GetContentIndex();
4554 for( size_t n = pHts->Count(); n; )
4555 {
4556 const SwTextAttr* pAttr = pHts->Get( --n );
4557 if( nMkCntPos > pAttr->GetStart() )
4558 break;
4559
4560 const sal_Int32 *pEndIdx;
4561 if( nMkCntPos == pAttr->GetStart() &&
4562 nullptr != (pEndIdx = pAttr->End()) &&
4563 *pEndIdx == pAttr->GetStart() )
4564 pTextNd->DestroyAttr( pHts->Cut( n ) );
4565 }
4566 }
4567 }
4568
4569 {
4570 // Send DataChanged before deletion, so that we still know
4571 // which objects are in the range.
4572 // Afterwards they could be before/after the Position.
4573 SwDataChanged aTmp( rPam );
4574 }
4575
4576 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4577 {
4578 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
4579 bool bMerged(false);
4580 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4581 {
4582 SwUndo *const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() );
4583 SwUndoDelete *const pUndoDelete(
4584 dynamic_cast<SwUndoDelete *>(pLastUndo) );
4585 if (pUndoDelete)
4586 {
4587 bMerged = pUndoDelete->CanGrouping(m_rDoc, rPam);
4588 // if CanGrouping() returns true it's already merged
4589 }
4590 }
4591 if (!bMerged)
4592 {
4593 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoDelete>(rPam, flags));
4594 }
4595
4596 m_rDoc.getIDocumentState().SetModified();
4597
4598 return true;
4599 }
4600
4601 if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
4602 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any );
4603
4604 // Delete and move all "Flys at the paragraph", which are within the Selection
4605 if (!(flags & SwDeleteFlags::ArtificialSelection))
4606 {
4607 DelFlyInRange(rPam.GetMark()->GetNode(), rPam.GetPoint()->GetNode(),
4608 rPam.GetMark()->GetContentIndex(), rPam.GetPoint()->GetContentIndex());
4609 }
4610 DelBookmarks(
4611 pStart->GetNode(),
4612 pEnd->GetNode(),
4613 nullptr,
4614 pStart->GetContentIndex(),
4615 pEnd->GetContentIndex(),
4616 bool(flags & SwDeleteFlags::ArtificialSelection));
4617
4618 SwNodeIndex aSttIdx( pStart->GetNode() );
4619 SwContentNode * pCNd = aSttIdx.GetNode().GetContentNode();
4620
4621 do { // middle checked loop!
4622 if( pCNd )
4623 {
4624 SwTextNode * pStartTextNode( pCNd->GetTextNode() );
4625 if ( pStartTextNode )
4626 {
4627 // now move the Content to the new Node
4628 bool bOneNd = pStart->GetNode() == pEnd->GetNode();
4629 const sal_Int32 nLen = ( bOneNd ? pEnd->GetContentIndex()
4630 : pCNd->Len() )
4631 - pStart->GetContentIndex();
4632
4633 // Don't call again, if already empty
4634 if( nLen )
4635 {
4636 pStartTextNode->EraseText( *pStart, nLen );
4637
4638 if( !pStartTextNode->Len() )
4639 {
4640 // METADATA: remove reference if empty (consider node deleted)
4641 pStartTextNode->RemoveMetadataReference();
4642 }
4643 }
4644
4645 if( bOneNd ) // that's it
4646 break;
4647
4648 ++aSttIdx;
4649 }
4650 else
4651 {
4652 // So that there are no indices left registered when deleted,
4653 // we remove a SwPaM from the Content here.
4654 pStart->nContent.Assign( nullptr, 0 );
4655 }
4656 }
4657
4658 pCNd = pEnd->GetNode().GetContentNode();
4659 if( pCNd )
4660 {
4661 SwTextNode * pEndTextNode( pCNd->GetTextNode() );
4662 if( pEndTextNode )
4663 {
4664 // if already empty, don't call again
4665 if( pEnd->GetContentIndex() )
4666 {
4667 SwContentIndex aIdx( pCNd, 0 );
4668 pEndTextNode->EraseText( aIdx, pEnd->GetContentIndex() );
4669
4670 if( !pEndTextNode->Len() )
4671 {
4672 // METADATA: remove reference if empty (consider node deleted)
4673 pEndTextNode->RemoveMetadataReference();
4674 }
4675 }
4676 }
4677 else
4678 {
4679 // So that there are no indices left registered when deleted,
4680 // we remove a SwPaM from the Content here.
4681 pEnd->nContent.Assign( nullptr, 0 );
4682 }
4683 }
4684
4685 // if the end is not a content node, delete it as well
4686 SwNodeOffset nEnd = pEnd->GetNodeIndex();
4687 if( pCNd == nullptr )
4688 nEnd++;
4689
4690 if( aSttIdx != nEnd )
4691 {
4692 // tdf#134436 delete section nodes like SwUndoDelete::SwUndoDelete
4693 SwNode *pTmpNd;
4694 while (pEnd == rPam.GetPoint()
4695 && nEnd + SwNodeOffset(2) < m_rDoc.GetNodes().Count()
4696 && (pTmpNd = m_rDoc.GetNodes()[nEnd + 1])->IsEndNode()
4697 && pTmpNd->StartOfSectionNode()->IsSectionNode()
4698 && aSttIdx <= pTmpNd->StartOfSectionNode()->GetIndex())
4699 {
4700 SwNodeRange range(*pTmpNd->StartOfSectionNode(), *pTmpNd);
4701 m_rDoc.GetNodes().SectionUp(&range);
4702 --nEnd; // account for deleted start node
4703 }
4704
4705 // delete the Nodes from the NodesArray
4706 m_rDoc.GetNodes().Delete( aSttIdx, nEnd - aSttIdx.GetIndex() );
4707 }
4708
4709 // If the Node that contained the Cursor has been deleted,
4710 // the Content has to be assigned to the current Content.
4711 if (pStart->GetNode().GetContentNode())
4712 pStart->SetContent( pStart->GetContentIndex() );
4713
4714 // If we deleted across Node boundaries we have to correct the PaM,
4715 // because they are in different Nodes now.
4716 // Also, the Selection is revoked.
4717 *pEnd = *pStart;
4718 rPam.DeleteMark();
4719
4720 } while( false );
4721
4722 m_rDoc.getIDocumentState().SetModified();
4723
4724 return true;
4725 }
4726
4727 // It's possible to call Replace with a PaM that spans 2 paragraphs:
4728 // search with regex for "$", then replace _all_
ReplaceRangeImpl(SwPaM & rPam,const OUString & rStr,const bool bRegExReplace)4729 bool DocumentContentOperationsManager::ReplaceRangeImpl( SwPaM& rPam, const OUString& rStr,
4730 const bool bRegExReplace )
4731 {
4732 if (!rPam.HasMark())
4733 return false;
4734
4735 bool bJoinText, bJoinPrev;
4736 ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev );
4737
4738 {
4739 // Create a copy of the Cursor in order to move all Pams from
4740 // the other views out of the deletion range.
4741 // Except for itself!
4742 SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
4743 ::PaMCorrAbs( aDelPam, *aDelPam.End() );
4744
4745 auto [pStart, pEnd] = aDelPam.StartEnd(); // SwPosition*
4746 bool bOneNode = pStart->GetNode() == pEnd->GetNode();
4747
4748 // Own Undo?
4749 OUString sRepl( rStr );
4750 SwTextNode* pTextNd = pStart->GetNode().GetTextNode();
4751 sal_Int32 nStt = pStart->GetContentIndex();
4752 sal_Int32 nEnd;
4753
4754 SwDataChanged aTmp( aDelPam );
4755
4756 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
4757 {
4758 RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
4759 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4760 {
4761 // this should no longer happen in calls from the UI but maybe via API
4762 SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask,
4763 "sw.core", "redlines will be moved in ReplaceRange");
4764
4765 m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
4766
4767 // If any Redline will change (split!) the node
4768 const ::sw::mark::MarkBase* pBkmk =
4769 m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam,
4770 SwMarkName(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
4771 ::sw::mark::InsertMode::New);
4772
4773 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags(
4774 RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete );
4775
4776 *aDelPam.GetPoint() = pBkmk->GetMarkPos();
4777 if(pBkmk->IsExpanded())
4778 *aDelPam.GetMark() = pBkmk->GetOtherMarkPos();
4779 m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk);
4780 pStart = aDelPam.Start();
4781 pTextNd = pStart->GetNode().GetTextNode();
4782 nStt = pStart->GetContentIndex();
4783 }
4784
4785 if( !sRepl.isEmpty() )
4786 {
4787 // Apply the first character's attributes to the ReplaceText
4788 SfxItemSet aSet(SfxItemSet::makeFixedSfxItemSet<RES_CHRATR_BEGIN, RES_TXTATR_WITHEND_END - 1,
4789 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1>(m_rDoc.GetAttrPool()));
4790 pTextNd->GetParaAttr( aSet, nStt+1, nStt+1 );
4791
4792 aSet.ClearItem( RES_TXTATR_REFMARK );
4793 aSet.ClearItem( RES_TXTATR_TOXMARK );
4794 aSet.ClearItem( RES_TXTATR_CJK_RUBY );
4795 aSet.ClearItem( RES_TXTATR_INETFMT );
4796 aSet.ClearItem( RES_TXTATR_META );
4797 aSet.ClearItem( RES_TXTATR_METAFIELD );
4798
4799 if( aDelPam.GetPoint() != aDelPam.End() )
4800 aDelPam.Exchange();
4801
4802 // Remember the End
4803 SwNodeIndex aPtNd( aDelPam.GetPoint()->GetNode(), -1 );
4804 const sal_Int32 nPtCnt = aDelPam.GetPoint()->GetContentIndex();
4805
4806 bool bFirst = true;
4807 OUString sIns;
4808 while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) )
4809 {
4810 InsertString( aDelPam, sIns );
4811 if( bFirst )
4812 {
4813 SwNodeIndex aMkNd( aDelPam.GetMark()->GetNode(), -1 );
4814 const sal_Int32 nMkCnt = aDelPam.GetMark()->GetContentIndex();
4815
4816 SplitNode( *aDelPam.GetPoint(), false );
4817
4818 ++aMkNd;
4819 aDelPam.GetMark()->Assign( aMkNd, nMkCnt );
4820 bFirst = false;
4821 }
4822 else
4823 SplitNode( *aDelPam.GetPoint(), false );
4824 }
4825 if( !sIns.isEmpty() )
4826 {
4827 InsertString( aDelPam, sIns );
4828 }
4829
4830 SwPaM aTmpRange( *aDelPam.GetPoint() );
4831 aTmpRange.SetMark();
4832
4833 ++aPtNd;
4834 aDelPam.GetPoint()->Assign(aPtNd, nPtCnt);
4835 *aTmpRange.GetMark() = *aDelPam.GetPoint();
4836
4837 m_rDoc.RstTextAttrs( aTmpRange );
4838 InsertItemSet( aTmpRange, aSet );
4839 }
4840
4841 // tdf#139982: Appending the redline may immediately delete flys
4842 // anchored in the previous text if it's inside an insert redline.
4843 // Also flys will be deleted if the redline is accepted. Move them
4844 // to the position between the previous text and the new text,
4845 // there the chance of surviving both accept and reject is best.
4846 SaveFlyArr flys;
4847 SaveFlyInRange(aDelPam, *aDelPam.End(), flys, false);
4848
4849 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4850 {
4851 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
4852 std::make_unique<SwUndoRedlineDelete>( aDelPam, SwUndoId::REPLACE ));
4853 }
4854 // add redline similar to DeleteAndJoinWithRedlineImpl()
4855 std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*aDelPam.GetMark()));
4856 pCursor->SetMark();
4857 *pCursor->GetPoint() = *aDelPam.GetPoint();
4858 m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDelPam ), true);
4859 RestFlyInRange(flys, *aDelPam.End(), &aDelPam.End()->GetNode(), true);
4860 sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor);
4861
4862 *rPam.GetMark() = *aDelPam.GetMark();
4863 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4864 {
4865 *aDelPam.GetPoint() = *rPam.GetPoint();
4866 m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4867
4868 // If any Redline will change (split!) the node
4869 const ::sw::mark::MarkBase* pBkmk =
4870 m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam,
4871 SwMarkName(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
4872 ::sw::mark::InsertMode::New);
4873
4874 aDelPam.GetPoint()->Assign( SwNodeOffset(0) );
4875 aDelPam.GetMark()->Assign( SwNodeOffset(0) );
4876 rPam.GetPoint()->Assign( SwNodeOffset(0) );
4877 *rPam.GetMark() = *rPam.GetPoint();
4878 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
4879
4880 *rPam.GetPoint() = pBkmk->GetMarkPos();
4881 *rPam.GetMark() = pBkmk->IsExpanded() ? pBkmk->GetOtherMarkPos() : pBkmk->GetMarkPos();
4882
4883 m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk);
4884 }
4885 bJoinText = false;
4886 }
4887 else
4888 {
4889 assert((pStart->GetNode() == pEnd->GetNode() ||
4890 ( pStart->GetNodeIndex() + 1 == pEnd->GetNodeIndex() &&
4891 !pEnd->GetContentIndex() )) &&
4892 "invalid range: Point and Mark on different nodes" );
4893
4894 if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
4895 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aDelPam, true, RedlineType::Any );
4896
4897 SwUndoReplace* pUndoRpl = nullptr;
4898 bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
4899 if (bDoesUndo)
4900 {
4901 pUndoRpl = new SwUndoReplace(aDelPam, sRepl, bRegExReplace);
4902 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoRpl));
4903 }
4904 ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo());
4905
4906 if( aDelPam.GetPoint() != pStart )
4907 aDelPam.Exchange();
4908
4909 SwNodeIndex aPtNd( pStart->GetNode(), -1 );
4910 const sal_Int32 nPtCnt = pStart->GetContentIndex();
4911
4912 // Set the values again, if Frames or footnotes on the Text have been removed.
4913 nStt = nPtCnt;
4914 nEnd = bOneNode ? pEnd->GetContentIndex()
4915 : pTextNd->GetText().getLength();
4916
4917 bool bFirst = true;
4918 OUString sIns;
4919 while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) )
4920 {
4921 if (!bFirst || nStt == pTextNd->GetText().getLength())
4922 {
4923 InsertString( aDelPam, sIns );
4924 }
4925 else if( nStt < nEnd || !sIns.isEmpty() )
4926 {
4927 pTextNd->ReplaceText( *pStart, nEnd - nStt, sIns );
4928 }
4929 SplitNode( *pStart, false);
4930 bFirst = false;
4931 }
4932
4933 if( bFirst || !sIns.isEmpty() )
4934 {
4935 if (!bFirst || nStt == pTextNd->GetText().getLength())
4936 {
4937 InsertString( aDelPam, sIns );
4938 }
4939 else if( nStt < nEnd || !sIns.isEmpty() )
4940 {
4941 pTextNd->ReplaceText( *pStart, nEnd - nStt, sIns );
4942 }
4943 }
4944
4945 *rPam.GetPoint() = *aDelPam.GetMark();
4946 ++aPtNd;
4947 rPam.GetMark()->Assign( aPtNd, nPtCnt );
4948
4949 if (bJoinText)
4950 {
4951 assert(rPam.GetPoint() == rPam.End());
4952 // move so that SetEnd remembers position after sw_JoinText
4953 rPam.Move(fnMoveBackward);
4954 }
4955 else if (aDelPam.GetPoint() == pStart) // backward selection?
4956 {
4957 assert(*rPam.GetMark() <= *rPam.GetPoint());
4958 rPam.Exchange(); // swap so that rPam is backwards
4959 }
4960
4961 if( pUndoRpl )
4962 {
4963 pUndoRpl->SetEnd(rPam);
4964 }
4965 }
4966 }
4967
4968 bool bRet(true);
4969 if (bJoinText)
4970 {
4971 bRet = ::sw_JoinText(rPam, bJoinPrev);
4972 }
4973
4974 m_rDoc.getIDocumentState().SetModified();
4975 return bRet;
4976 }
4977
InsNoTextNode(const SwPosition & rPos,SwNoTextNode * pNode,const SfxItemSet * pFlyAttrSet,const SfxItemSet * pGrfAttrSet,SwFrameFormat * pFrameFormat)4978 SwFlyFrameFormat* DocumentContentOperationsManager::InsNoTextNode( const SwPosition& rPos, SwNoTextNode* pNode,
4979 const SfxItemSet* pFlyAttrSet,
4980 const SfxItemSet* pGrfAttrSet,
4981 SwFrameFormat* pFrameFormat)
4982 {
4983 SwFlyFrameFormat *pFormat = nullptr;
4984 if( pNode )
4985 {
4986 pFormat = m_rDoc.MakeFlySection_( rPos, *pNode, RndStdIds::FLY_AT_PARA,
4987 pFlyAttrSet, pFrameFormat );
4988 if( pGrfAttrSet )
4989 pNode->SetAttr( *pGrfAttrSet );
4990 }
4991 return pFormat;
4992 }
4993
4994 #define NUMRULE_STATE \
4995 std::shared_ptr<SwNumRuleItem> aNumRuleItemHolderIfSet; \
4996 std::shared_ptr<SfxStringItem> aListIdItemHolderIfSet; \
4997
4998 #define PUSH_NUMRULE_STATE \
4999 lcl_PushNumruleState( aNumRuleItemHolderIfSet, aListIdItemHolderIfSet, pDestTextNd );
5000
5001 #define POP_NUMRULE_STATE \
5002 lcl_PopNumruleState( aNumRuleItemHolderIfSet, aListIdItemHolderIfSet, pDestTextNd, rPam );
5003
lcl_PushNumruleState(std::shared_ptr<SwNumRuleItem> & aNumRuleItemHolderIfSet,std::shared_ptr<SfxStringItem> & aListIdItemHolderIfSet,const SwTextNode * pDestTextNd)5004 static void lcl_PushNumruleState(
5005 std::shared_ptr<SwNumRuleItem>& aNumRuleItemHolderIfSet,
5006 std::shared_ptr<SfxStringItem>& aListIdItemHolderIfSet,
5007 const SwTextNode *pDestTextNd )
5008 {
5009 // Safe numrule item at destination.
5010 // #i86492# - Safe also <ListId> item of destination.
5011 const SfxItemSet * pAttrSet = pDestTextNd->GetpSwAttrSet();
5012 if (pAttrSet == nullptr)
5013 return;
5014
5015 if (const SwNumRuleItem* pItem = pAttrSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
5016 {
5017 aNumRuleItemHolderIfSet.reset(pItem->Clone());
5018 }
5019
5020 if (const SfxStringItem* pItem = pAttrSet->GetItemIfSet(RES_PARATR_LIST_ID, false))
5021 {
5022 aListIdItemHolderIfSet.reset(pItem->Clone());
5023 }
5024 }
5025
lcl_PopNumruleState(const std::shared_ptr<SwNumRuleItem> & aNumRuleItemHolderIfSet,const std::shared_ptr<SfxStringItem> & aListIdItemHolderIfSet,SwTextNode * pDestTextNd,const SwPaM & rPam)5026 static void lcl_PopNumruleState(
5027 const std::shared_ptr<SwNumRuleItem>& aNumRuleItemHolderIfSet,
5028 const std::shared_ptr<SfxStringItem>& aListIdItemHolderIfSet,
5029 SwTextNode *pDestTextNd, const SwPaM& rPam )
5030 {
5031 /* If only a part of one paragraph is copied
5032 restore the numrule at the destination. */
5033 // #i86492# - restore also <ListId> item
5034 if ( lcl_MarksWholeNode(rPam) )
5035 return;
5036 if (aNumRuleItemHolderIfSet)
5037 {
5038 pDestTextNd->SetAttr(*aNumRuleItemHolderIfSet);
5039 }
5040 else
5041 {
5042 pDestTextNd->ResetAttr(RES_PARATR_NUMRULE);
5043 }
5044
5045 if (aListIdItemHolderIfSet)
5046 {
5047 pDestTextNd->SetAttr(*aListIdItemHolderIfSet);
5048 }
5049 else
5050 {
5051 pDestTextNd->ResetAttr(RES_PARATR_LIST_ID);
5052 }
5053 }
5054
CopyImpl(SwPaM & rPam,SwPosition & rPos,SwCopyFlags const flags,SwPaM * const pCopyRange) const5055 bool DocumentContentOperationsManager::CopyImpl(SwPaM& rPam, SwPosition& rPos,
5056 SwCopyFlags const flags,
5057 SwPaM *const pCopyRange) const
5058 {
5059 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
5060
5061 sw::CalcBreaks(Breaks, rPam, true);
5062
5063 if (Breaks.empty())
5064 {
5065 return CopyImplImpl(rPam, rPos, flags, pCopyRange);
5066 }
5067
5068 SwPosition const & rSelectionEnd( *rPam.End() );
5069
5070 bool bRet(true);
5071 bool bFirst(true);
5072 // iterate from end to start, ... don't think it's necessary here?
5073 auto iter( Breaks.rbegin() );
5074 SwNodeOffset nOffset(0);
5075 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
5076 SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node!
5077 SwPosition & rEnd( *aPam.End() );
5078 SwPosition & rStart( *aPam.Start() );
5079 SwPaM copyRange(rPos, rPos);
5080
5081 while (iter != Breaks.rend())
5082 {
5083 rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
5084 if (rStart < rEnd) // check if part is empty
5085 {
5086 // pass in copyRange member as rPos; should work ...
5087 bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, ©Range);
5088 nOffset = iter->first - rStart.GetNodeIndex(); // fly nodes...
5089 if (pCopyRange)
5090 {
5091 if (bFirst)
5092 {
5093 pCopyRange->SetMark();
5094 *pCopyRange->GetMark() = *copyRange.End();
5095 }
5096 *pCopyRange->GetPoint() = *copyRange.Start();
5097 }
5098 bFirst = false;
5099 }
5100 rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
5101 ++iter;
5102 }
5103
5104 rStart = *rPam.Start(); // set to original start
5105 if (rStart < rEnd) // check if part is empty
5106 {
5107 bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, ©Range);
5108 if (pCopyRange)
5109 {
5110 if (bFirst)
5111 {
5112 pCopyRange->SetMark();
5113 *pCopyRange->GetMark() = *copyRange.End();
5114 }
5115 *pCopyRange->GetPoint() = *copyRange.Start();
5116 }
5117 }
5118
5119 return bRet;
5120 }
5121
CopyImplImpl(SwPaM & rPam,SwPosition & rPos,SwCopyFlags const flags,SwPaM * const pCpyRange) const5122 bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPos,
5123 SwCopyFlags const flags,
5124 SwPaM *const pCpyRange) const
5125 {
5126 SwDoc& rDoc = rPos.GetNode().GetDoc();
5127 const bool bColumnSel = rDoc.IsClipBoard() && rDoc.IsColumnSelection();
5128
5129 auto [pStart, pEnd] = rPam.StartEnd(); // SwPosition*
5130
5131 // Catch when there's no copy to do.
5132 if (!rPam.HasMark() || (IsEmptyRange(*pStart, *pEnd, flags) && !bColumnSel) ||
5133 //JP 29.6.2001: 88963 - don't copy if inspos is in region of start to end
5134 //JP 15.11.2001: don't test inclusive the end, ever exclusive
5135 ( &rDoc == &m_rDoc && *pStart <= rPos && rPos < *pEnd ))
5136 {
5137 return false;
5138 }
5139
5140 const bool bEndEqualIns = &rDoc == &m_rDoc && rPos == *pEnd;
5141
5142 // If Undo is enabled, create the UndoCopy object
5143 SwUndoCpyDoc* pUndo = nullptr;
5144 // lcl_DeleteRedlines may delete the start or end node of the cursor when
5145 // removing the redlines so use cursor that is corrected by PaMCorrAbs
5146 std::shared_ptr<SwUnoCursor> const pCopyPam(rDoc.CreateUnoCursor(rPos));
5147
5148 SwTableNumFormatMerge aTNFM( m_rDoc, rDoc );
5149 std::optional<std::vector<SwFrameFormat*>> pFlys;
5150 std::vector<SwFrameFormat*> const* pFlysAtInsPos;
5151
5152 if (rDoc.GetIDocumentUndoRedo().DoesUndo())
5153 {
5154 pUndo = new SwUndoCpyDoc(*pCopyPam);
5155 pFlysAtInsPos = pUndo->GetFlysAnchoredAt();
5156 }
5157 else
5158 {
5159 pFlys = sw::GetFlysAnchoredAt(rDoc, rPos.GetNodeIndex(), false);
5160 pFlysAtInsPos = pFlys ? &*pFlys : nullptr;
5161 }
5162
5163 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
5164 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
5165
5166 // Move the PaM one node back from the insert position, so that
5167 // the position doesn't get moved
5168 pCopyPam->SetMark();
5169 bool bCanMoveBack = false;
5170 // First check if it will be able to move *to* first copied node.
5171 // Note this doesn't just check IsStartNode() because SwDoc::AppendDoc()
5172 // intentionally sets it to the body start node, perhaps it should just
5173 // call SplitNode instead?
5174 if ((!pStart->GetNode().IsSectionNode() && !pStart->GetNode().IsTableNode())
5175 || (pCopyPam->GetPoint()->GetContentIndex() != 0 // also if node will split
5176 && pCopyPam->GetPoint()->GetContentIndex() != pCopyPam->GetPoint()->GetNode().GetContentNode()->Len()))
5177 {
5178 bCanMoveBack = pCopyPam->Move(fnMoveBackward, GoInContent);
5179 }
5180 if( !bCanMoveBack )
5181 {
5182 pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1));
5183 assert(pCopyPam->GetPoint()->GetContentIndex() == 0);
5184 }
5185
5186 SwNodeRange aRg( pStart->GetNode(), pEnd->GetNode() );
5187 SwNodeIndex aInsPos( rPos.GetNode() );
5188 ::std::optional<SwContentIndex> oInsContentIndex;
5189 const bool bOneNode = pStart->GetNode() == pEnd->GetNode();
5190 SwTextNode* pSttTextNd = pStart->GetNode().GetTextNode();
5191 SwTextNode* pEndTextNd = pEnd->GetNode().GetTextNode();
5192 SwTextNode* pDestTextNd = aInsPos.GetNode().GetTextNode();
5193 bool bDestTextNdEmpty = pDestTextNd && (pDestTextNd->GetText().isEmpty() || pDestTextNd->GetText() == "\n");
5194 bool bCopyCollFormat = !rDoc.IsInsOnlyTextGlossary() &&
5195 ( bDestTextNdEmpty ||
5196 ( !bOneNode && !rPos.GetContentIndex() ) );
5197 bool bCopyBookmarks = true;
5198 bool bCopyPageSource = false;
5199 SwNodeOffset nDeleteTextNodes(0);
5200
5201 // #i104585# copy outline num rule to clipboard (for ASCII filter)
5202 if (rDoc.IsClipBoard() && m_rDoc.GetOutlineNumRule())
5203 {
5204 rDoc.SetOutlineNumRule(*m_rDoc.GetOutlineNumRule());
5205 }
5206
5207 // #i86492#
5208 // Correct the search for a previous list:
5209 // First search for non-outline numbering list. Then search for non-outline
5210 // bullet list.
5211 // Keep also the <ListId> value for possible propagation.
5212 OUString aListIdToPropagate;
5213 SvxTextLeftMarginItem const* pTextLeftMarginToPropagate{nullptr};
5214 SvxFirstLineIndentItem const* pFirstLineIndentToPropagate{nullptr};
5215 const SwNumRule* pNumRuleToPropagate =
5216 rDoc.SearchNumRule(rPos, false, true, false, 0, aListIdToPropagate, nullptr,
5217 true, &pTextLeftMarginToPropagate, &pFirstLineIndentToPropagate);
5218 if ( !pNumRuleToPropagate )
5219 {
5220 pNumRuleToPropagate =
5221 rDoc.SearchNumRule(rPos, false, false, false, 0, aListIdToPropagate, nullptr,
5222 true, &pTextLeftMarginToPropagate, &pFirstLineIndentToPropagate);
5223 }
5224 // #i86492#
5225 // Do not propagate previous found list, if
5226 // - destination is an empty paragraph which is not in a list and
5227 // - source contains at least one paragraph which is not in a list
5228 // or
5229 // - source is a table
5230 // - tdf#163340 overwrite list if source has a list
5231 // - overwrite also if all source paragraphs have a list from a style
5232
5233 if ( pNumRuleToPropagate &&
5234 ((pDestTextNd && !pDestTextNd->GetText().getLength() &&
5235 (!pDestTextNd->IsInList() || lcl_ShouldKeepSourceList(rPam) )) ||
5236 rPam.GetBound().nNode.GetNode().GetNodeType() == SwNodeType::Table) )
5237 {
5238 pNumRuleToPropagate = nullptr;
5239 }
5240
5241 // This do/while block is only there so that we can break out of it!
5242 do {
5243 if( pSttTextNd )
5244 {
5245 ++nDeleteTextNodes; // must be joined in Undo
5246 // Don't copy the beginning completely?
5247 if( !bCopyCollFormat || bColumnSel || pStart->GetContentIndex() )
5248 {
5249 SwContentIndex aDestIdx( rPos.GetContentNode(), rPos.GetContentIndex() );
5250 bool bCopyOk = false;
5251 if( !pDestTextNd )
5252 {
5253 if( pStart->GetContentIndex() || bOneNode )
5254 pDestTextNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode(),
5255 rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD));
5256 else
5257 {
5258 pDestTextNd = pSttTextNd->MakeCopy(rDoc, aInsPos.GetNode(), true)->GetTextNode();
5259 bCopyOk = true;
5260 }
5261 aDestIdx.Assign( pDestTextNd, 0 );
5262 bCopyCollFormat = true;
5263 }
5264 else if( !bOneNode || bColumnSel )
5265 {
5266 const sal_Int32 nContentEnd = pEnd->GetContentIndex();
5267 {
5268 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5269 rDoc.getIDocumentContentOperations().SplitNode( rPos, false );
5270 }
5271
5272 assert(rPos != *pCopyPam->GetPoint()); // code removed
5273
5274 pDestTextNd = rDoc.GetNodes()[ aInsPos.GetIndex()-SwNodeOffset(1) ]->GetTextNode();
5275 aDestIdx.Assign(
5276 pDestTextNd, pDestTextNd->GetText().getLength());
5277
5278 // Correct the area again
5279 if( bEndEqualIns )
5280 {
5281 bool bChg = pEnd != rPam.GetPoint();
5282 if( bChg )
5283 rPam.Exchange();
5284 rPam.Move( fnMoveBackward, GoInContent );
5285 if( bChg )
5286 rPam.Exchange();
5287 }
5288 else if( rPos == *pEnd )
5289 {
5290 // The end was also moved
5291 pEnd->Adjust(SwNodeOffset(-1));
5292 pEnd->SetContent( nContentEnd );
5293 }
5294 // tdf#63022 always reset pEndTextNd after SplitNode
5295 aRg.aEnd = pEnd->GetNode();
5296 pEndTextNd = pEnd->GetNode().GetTextNode();
5297 }
5298
5299 NUMRULE_STATE
5300 if( bCopyCollFormat && bOneNode )
5301 {
5302 PUSH_NUMRULE_STATE
5303 }
5304
5305 if( !bCopyOk )
5306 {
5307 const sal_Int32 nCpyLen = ( bOneNode
5308 ? pEnd->GetContentIndex()
5309 : pSttTextNd->GetText().getLength())
5310 - pStart->GetContentIndex();
5311 pSttTextNd->CopyText( pDestTextNd, aDestIdx, *pStart, nCpyLen );
5312 if( bEndEqualIns )
5313 pEnd->AdjustContent( -nCpyLen );
5314 }
5315
5316 ++aRg.aStart;
5317
5318 if( bOneNode )
5319 {
5320 if (bCopyCollFormat)
5321 {
5322 // tdf#138897 no Undo for applying style, SwUndoInserts does it
5323 pSttTextNd->CopyCollFormat(*pDestTextNd, false);
5324 POP_NUMRULE_STATE
5325 }
5326
5327 // Copy at-char flys in rPam.
5328 // Update to new (start) node for flys.
5329 // tdf#126626 prevent duplicate Undos.
5330 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5331 CopyFlyInFlyImpl(aRg, &rPam, *pDestTextNd, false);
5332
5333 break;
5334 }
5335 }
5336 }
5337 else if( pDestTextNd )
5338 {
5339 // Problems with insertion of table selections into "normal" text solved.
5340 // We have to set the correct PaM for Undo, if this PaM starts in a textnode,
5341 // the undo operation will try to merge this node after removing the table.
5342 // If we didn't split a textnode, the PaM should start at the inserted table node
5343 if (pDestTextNd->Len() && rPos.GetContentIndex() == pDestTextNd->Len())
5344 { // Insertion at the last position of a textnode
5345 ++aInsPos; // The table will be inserted behind the text node
5346 }
5347 else if( rPos.GetContentIndex() )
5348 { // Insertion in the middle of a text node, it has to be split
5349 // (and joined from undo)
5350 ++nDeleteTextNodes;
5351
5352 const sal_Int32 nContentEnd = pEnd->GetContentIndex();
5353 {
5354 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5355 rDoc.getIDocumentContentOperations().SplitNode( rPos, false );
5356 }
5357
5358 assert(rPos != *pCopyPam->GetPoint()); // code removed
5359
5360 // Correct the area again
5361 if( bEndEqualIns )
5362 --aRg.aEnd;
5363 // The end would also be moved
5364 else if( rPos == *pEnd )
5365 {
5366 rPos.Adjust(SwNodeOffset(-1));
5367 rPos.SetContent( nContentEnd );
5368 --aRg.aEnd;
5369 }
5370 }
5371 assert((nDeleteTextNodes.get() != 0) == bCanMoveBack);
5372 }
5373
5374 pDestTextNd = aInsPos.GetNode().GetTextNode();
5375 if (pEndTextNd)
5376 {
5377 oInsContentIndex.emplace(aInsPos.GetNode().GetContentNode(), rPos.GetContentIndex());
5378 if( !pDestTextNd )
5379 {
5380 pDestTextNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode(),
5381 rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD));
5382 oInsContentIndex->Assign(pDestTextNd, 0);
5383 --aInsPos;
5384
5385 // if we have to insert an extra text node
5386 // at the destination, this node will be our new destination
5387 // (text) node, and thus we increment nDeleteTextNodes. This
5388 // will ensure that this node will be deleted during Undo.
5389 ++nDeleteTextNodes; // must be deleted
5390 }
5391
5392 const bool bEmptyDestNd = pDestTextNd->GetText().isEmpty();
5393
5394 NUMRULE_STATE
5395 if( bCopyCollFormat && ( bOneNode || bEmptyDestNd ))
5396 {
5397 PUSH_NUMRULE_STATE
5398 }
5399
5400 pEndTextNd->CopyText(pDestTextNd, *oInsContentIndex,
5401 SwContentIndex(pEndTextNd), pEnd->GetContentIndex());
5402
5403 // Also copy all format templates
5404 if( bCopyCollFormat && ( bOneNode || bEmptyDestNd ))
5405 {
5406 // tdf#138897 no Undo for applying style, SwUndoInserts does it
5407 pEndTextNd->CopyCollFormat(*pDestTextNd, false);
5408 if ( bOneNode )
5409 {
5410 POP_NUMRULE_STATE
5411 }
5412 }
5413 }
5414
5415 SfxItemSet aBrkSet( rDoc.GetAttrPool(), aBreakSetRange );
5416 if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd)
5417 {
5418 if (pSttTextNd && bCopyCollFormat && pDestTextNd->HasSwAttrSet())
5419 {
5420 aBrkSet.Put( *pDestTextNd->GetpSwAttrSet() );
5421 if( SfxItemState::SET == aBrkSet.GetItemState( RES_BREAK, false ) )
5422 pDestTextNd->ResetAttr( RES_BREAK );
5423 if( SfxItemState::SET == aBrkSet.GetItemState( RES_PAGEDESC, false ) )
5424 pDestTextNd->ResetAttr( RES_PAGEDESC );
5425 }
5426 }
5427
5428 {
5429 SwPosition startPos(pCopyPam->GetPoint()->GetNode(), SwNodeOffset(+1));
5430 if (bCanMoveBack)
5431 { // pCopyPam is actually 1 before the copy range so move it fwd
5432 SwPaM temp(*pCopyPam->GetPoint());
5433 temp.Move(fnMoveForward, GoInContent);
5434 startPos = *temp.GetPoint();
5435 }
5436 assert(startPos.GetNode().IsContentNode());
5437 std::pair<SwPaM const&, SwPosition const&> tmp(rPam, startPos);
5438 if( aInsPos == pEnd->GetNode() )
5439 {
5440 SwNodeIndex aSaveIdx( aInsPos, -1 );
5441 assert(pStart->GetNode() != pEnd->GetNode());
5442 pEnd->SetContent(0); // TODO why this?
5443 CopyWithFlyInFly(aRg, aInsPos.GetNode(), &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags);
5444 ++aSaveIdx;
5445 pEnd->Assign(aSaveIdx);
5446 }
5447 else
5448 CopyWithFlyInFly(aRg, aInsPos.GetNode(), &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags);
5449
5450 bCopyBookmarks = false;
5451 }
5452
5453
5454 // init *again* - because CopyWithFlyInFly moved startPos
5455 SwPosition startPos(pCopyPam->GetPoint()->GetNode(), SwNodeOffset(+1));
5456 // at-char anchors post SplitNode are on index 0 of 2nd node and will
5457 // remain there - move them back to the start (end would also work?)
5458 // ... also for at-para anchors; here start is preferable because
5459 // it's consistent with SplitNode from SwUndoInserts::RedoImpl()
5460 if (pFlysAtInsPos
5461 && (bCanMoveBack
5462 || startPos.GetNode().IsTextNode()
5463 || (pCopyPam->GetPoint()->GetNode().IsStartNode()
5464 && startPos.GetNode().IsSectionNode()))) // not into table
5465 {
5466 if (bCanMoveBack)
5467 { // pCopyPam is actually 1 before the copy range so move it fwd
5468 SwPaM temp(*pCopyPam->GetPoint());
5469 temp.Move(fnMoveForward, GoInContent);
5470 startPos = *temp.GetPoint();
5471 }
5472 else if (startPos.GetNode().IsSectionNode())
5473 { // probably on top-level start node, so no CheckNodesRange here;
5474 GoNextPos(&startPos, false); // SwFEShell::Paste() deletes node
5475 }
5476 assert(startPos.GetNode().IsContentNode());
5477 SwPosition startPosAtPara(startPos);
5478 startPosAtPara.nContent.Assign(nullptr, 0);
5479
5480 for (SwFrameFormat * pFly : *pFlysAtInsPos)
5481 {
5482 SwFormatAnchor const*const pAnchor = &pFly->GetAnchor();
5483 if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
5484 {
5485 SwFormatAnchor anchor(*pAnchor);
5486 anchor.SetAnchor( &startPos );
5487 pFly->SetFormatAttr(anchor);
5488 }
5489 else if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA)
5490 {
5491 SwFormatAnchor anchor(*pAnchor);
5492 anchor.SetAnchor( &startPosAtPara );
5493
5494 bool bSplitFly = false;
5495 if (pFly->GetFlySplit().GetValue())
5496 {
5497 SwIterator<SwFrame, SwModify> aIter(*pFly);
5498 bSplitFly = aIter.First() && aIter.Next();
5499 }
5500 if (bSplitFly)
5501 {
5502 // This fly format has multiple frames, and we change the anchor. Remove the
5503 // old frames, which were based on the old anchor position.
5504 pFly->DelFrames();
5505 }
5506
5507 pFly->SetFormatAttr(anchor);
5508
5509 if (bSplitFly)
5510 {
5511 // Re-create the frames now that the new anchor is set.
5512 pFly->MakeFrames();
5513 }
5514 }
5515 }
5516 }
5517
5518 if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd)
5519 {
5520 // Put the breaks back into the first node
5521 if( aBrkSet.Count() && nullptr != ( pDestTextNd = rDoc.GetNodes()[
5522 pCopyPam->GetPoint()->GetNodeIndex()+1 ]->GetTextNode()))
5523 {
5524 pDestTextNd->SetAttr( aBrkSet );
5525 bCopyPageSource = true;
5526 }
5527 }
5528 } while( false );
5529
5530
5531 // it is not possible to make this test when copy from the clipBoard to document
5532 // in this case the PageNum not exist anymore
5533 // tdf#39400 and tdf#97526
5534 // when copy from document to ClipBoard, and it is from the first page
5535 // and not the source has the page break
5536 if (rDoc.IsClipBoard() && (rPam.GetPageNum(pStart == rPam.GetPoint()) == 1) && !bCopyPageSource)
5537 {
5538 if (pDestTextNd)
5539 {
5540 pDestTextNd->ResetAttr(RES_BREAK); // remove the page-break
5541 pDestTextNd->ResetAttr(RES_PAGEDESC);
5542 }
5543 }
5544
5545
5546 // Adjust position (in case it was moved / in another node)
5547 rPos.nContent.Assign( rPos.GetNode().GetContentNode(),
5548 rPos.GetContentIndex() );
5549
5550 if( rPos.GetNode() != aInsPos.GetNode() )
5551 {
5552 if (aInsPos < rPos.GetNode())
5553 { // tdf#134250 decremented in (pEndTextNd && !pDestTextNd) above
5554 pCopyPam->GetMark()->AssignEndIndex(*aInsPos.GetNode().GetContentNode());
5555 }
5556 else // incremented in (!pSttTextNd && pDestTextNd) above
5557 {
5558 // assign also content index in this case, see testSectionAnchorCopyTableAtStart
5559 assert(oInsContentIndex);
5560 assert(oInsContentIndex->GetContentNode() == &aInsPos.GetNode());
5561 pCopyPam->GetMark()->Assign(aInsPos, oInsContentIndex->GetIndex());
5562 }
5563 rPos = *pCopyPam->GetMark();
5564 }
5565 else
5566 *pCopyPam->GetMark() = rPos;
5567
5568 if (bCanMoveBack)
5569 {
5570 pCopyPam->Move(fnMoveForward, GoInContent);
5571 }
5572 else
5573 {
5574 // Reset the offset to 0 as it was before the insertion
5575 pCopyPam->GetPoint()->Adjust(SwNodeOffset(+1));
5576 }
5577 oInsContentIndex.reset();
5578 pCopyPam->Exchange();
5579
5580 // Also copy all bookmarks
5581 if( bCopyBookmarks && m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() )
5582 {
5583 sw::CopyBookmarks(rPam, *pCopyPam->Start());
5584 }
5585
5586 if( RedlineFlags::DeleteRedlines & eOld )
5587 {
5588 assert(*pCopyPam->GetPoint() == rPos);
5589 // the Node rPos points to may be deleted so unregister ...
5590 rPos.nContent.Assign(nullptr, 0);
5591 lcl_DeleteRedlines(rPam, *pCopyPam);
5592 rPos = *pCopyPam->GetPoint(); // ... and restore.
5593 }
5594
5595 // If Undo is enabled, store the inserted area
5596 if (rDoc.GetIDocumentUndoRedo().DoesUndo())
5597 {
5598 // append it after styles have been copied when copying nodes
5599 rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
5600 pUndo->SetInsertRange(*pCopyPam, true, nDeleteTextNodes);
5601 }
5602
5603 if( pCpyRange )
5604 {
5605 pCpyRange->SetMark();
5606 *pCpyRange->GetPoint() = *pCopyPam->GetPoint();
5607 *pCpyRange->GetMark() = *pCopyPam->GetMark();
5608 }
5609
5610 if ( pNumRuleToPropagate != nullptr )
5611 {
5612 // #i86492# - use <SwDoc::SetNumRule(..)>, because it also handles the <ListId>
5613 // Don't reset indent attributes, that would mean loss of direct
5614 // formatting.
5615 // It could be that pNumRuleToPropagate is already applied via
5616 // the paragraph style, in that case applying it again in mpAttrSet could
5617 // override indents, so avoid that.
5618 rDoc.SetNumRule(*pCopyPam, *pNumRuleToPropagate,
5619 SwDoc::SetNumRuleMode::DontSetIfAlreadyApplied, nullptr, aListIdToPropagate,
5620 pTextLeftMarginToPropagate, pFirstLineIndentToPropagate);
5621 }
5622
5623 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
5624 rDoc.getIDocumentState().SetModified();
5625
5626 return true;
5627 }
5628
5629
5630 }
5631 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
5632