crashtesting tdf135164-3.docx
[LibreOffice.git] / sw / source / core / doc / DocumentContentOperationsManager.cxx
blob73811f5d083eedf7fb5f36a73243a78d4af4e10e
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
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 <formatflysplit.hxx>
77 #include <o3tl/safeint.hxx>
78 #include <sal/log.hxx>
79 #include <unotools/charclass.hxx>
80 #include <unotools/configmgr.hxx>
81 #include <unotools/transliterationwrapper.hxx>
82 #include <i18nutil/transliteration.hxx>
83 #include <sfx2/Metadatable.hxx>
84 #include <sot/exchange.hxx>
85 #include <svl/stritem.hxx>
86 #include <svl/itemiter.hxx>
87 #include <svx/svdobj.hxx>
88 #include <svx/svdouno.hxx>
89 #include <tools/globname.hxx>
90 #include <editeng/formatbreakitem.hxx>
91 #include <com/sun/star/i18n/Boundary.hpp>
92 #include <com/sun/star/i18n/WordType.hpp>
93 #include <com/sun/star/i18n/XBreakIterator.hpp>
94 #include <com/sun/star/embed/XEmbeddedObject.hpp>
96 #include <tuple>
97 #include <memory>
98 #include <optional>
100 using namespace ::com::sun::star::i18n;
102 namespace
104 // Copy method from SwDoc
105 // Prevent copying into Flys that are anchored in the range
106 bool lcl_ChkFlyFly( SwDoc& rDoc, SwNodeOffset nSttNd, SwNodeOffset nEndNd,
107 SwNodeOffset nInsNd )
110 for(sw::SpzFrameFormat* pFormat: *rDoc.GetSpzFrameFormats())
112 SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
113 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
114 if (pAnchorNode &&
115 ((RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) ||
116 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) ||
117 (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId()) ||
118 (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId())) &&
119 nSttNd <= pAnchorNode->GetIndex() &&
120 pAnchorNode->GetIndex() < nEndNd )
122 const SwFormatContent& rContent = pFormat->GetContent();
123 SwStartNode* pSNd;
124 if( !rContent.GetContentIdx() ||
125 nullptr == ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ))
126 continue;
128 if( pSNd->GetIndex() < nInsNd &&
129 nInsNd < pSNd->EndOfSectionIndex() )
130 // Do not copy !
131 return true;
133 if( lcl_ChkFlyFly( rDoc, pSNd->GetIndex(),
134 pSNd->EndOfSectionIndex(), nInsNd ) )
135 // Do not copy !
136 return true;
140 return false;
143 SwNodeIndex InitDelCount(SwPaM const& rSourcePaM, SwNodeOffset & rDelCount)
145 SwPosition const& rStart(*rSourcePaM.Start());
146 // Special handling for SwDoc::AppendDoc
147 if (rSourcePaM.GetDoc().GetNodes().GetEndOfExtras().GetIndex() + 1
148 == rStart.GetNodeIndex())
150 rDelCount = SwNodeOffset(1);
151 return SwNodeIndex(rStart.GetNode(), +1);
153 else
155 rDelCount = SwNodeOffset(0);
156 return SwNodeIndex(rStart.GetNode());
161 The CopyBookmarks function has to copy bookmarks from the source to the destination nodes
162 array. It is called after a call of the CopyNodes(..) function. But this function does not copy
163 every node (at least at the moment: 2/08/2006 ), section start and end nodes will not be copied
164 if the corresponding end/start node is outside the copied pam.
165 The lcl_NonCopyCount function counts the number of these nodes, given the copied pam and a node
166 index inside the pam.
167 rPam is the original source pam, rLastIdx is the last calculated position, rDelCount the number
168 of "non-copy" nodes between rPam.Start() and rLastIdx.
169 nNewIdx is the new position of interest.
171 void lcl_NonCopyCount( const SwPaM& rPam, SwNodeIndex& rLastIdx, const SwNodeOffset nNewIdx, SwNodeOffset& rDelCount )
173 SwNodeOffset nStart = rPam.Start()->GetNodeIndex();
174 SwNodeOffset nEnd = rPam.End()->GetNodeIndex();
175 if( rLastIdx.GetIndex() < nNewIdx ) // Moving forward?
177 // We never copy the StartOfContent node
178 do // count "non-copy" nodes
180 SwNode& rNode = rLastIdx.GetNode();
181 if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd )
182 || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) )
184 ++rDelCount;
186 ++rLastIdx;
188 while( rLastIdx.GetIndex() < nNewIdx );
190 else if( rDelCount ) // optimization: if there are no "non-copy" nodes until now,
191 // no move backward needed
193 while( rLastIdx.GetIndex() > nNewIdx )
195 SwNode& rNode = rLastIdx.GetNode();
196 if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd )
197 || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) )
199 --rDelCount;
201 --rLastIdx;
206 void lcl_SetCpyPos( const SwPosition& rOrigPos,
207 const SwPosition& rOrigStt,
208 const SwPosition& rCpyStt,
209 SwPosition& rChgPos,
210 SwNodeOffset nDelCount )
212 SwNodeOffset nNdOff = rOrigPos.GetNodeIndex();
213 nNdOff -= rOrigStt.GetNodeIndex();
214 nNdOff -= nDelCount;
215 sal_Int32 nContentPos = rOrigPos.GetContentIndex();
217 // Always adjust <nNode> at to be changed <SwPosition> instance <rChgPos>
218 rChgPos.Assign( nNdOff + rCpyStt.GetNodeIndex() );
219 if (!rChgPos.GetNode().GetContentNode())
220 return;
221 if( !nNdOff )
223 // just adapt the content index
224 if( nContentPos > rOrigStt.GetContentIndex() )
225 nContentPos -= rOrigStt.GetContentIndex();
226 else
227 nContentPos = 0;
228 nContentPos += rCpyStt.GetContentIndex();
230 rChgPos.SetContent( nContentPos );
235 namespace sw
237 // TODO: use SaveBookmark (from DelBookmarks)
238 void CopyBookmarks(const SwPaM& rPam, const SwPosition& rCpyPam, SwCopyFlags flags)
240 const SwDoc& rSrcDoc = rPam.GetDoc();
241 SwDoc& rDestDoc = rCpyPam.GetDoc();
242 const IDocumentMarkAccess* const pSrcMarkAccess = rSrcDoc.getIDocumentMarkAccess();
243 ::sw::UndoGuard const undoGuard(rDestDoc.GetIDocumentUndoRedo());
245 const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End();
246 SwPosition const*const pCpyStt = &rCpyPam;
248 std::vector< const ::sw::mark::IMark* > vMarksToCopy;
249 for ( IDocumentMarkAccess::const_iterator_t ppMark = pSrcMarkAccess->getAllMarksBegin();
250 ppMark != pSrcMarkAccess->getAllMarksEnd();
251 ++ppMark )
253 const ::sw::mark::IMark* const pMark = *ppMark;
255 const SwPosition& rMarkStart = pMark->GetMarkStart();
256 const SwPosition& rMarkEnd = pMark->GetMarkEnd();
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))
283 vMarksToCopy.push_back(pMark);
286 // We have to count the "non-copied" nodes...
287 SwNodeOffset nDelCount;
288 SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount));
289 for(const sw::mark::IMark* const pMark : vMarksToCopy)
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())
296 aTmpPam.SetMark();
297 lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetOtherMarkPos().GetNodeIndex(), nDelCount);
298 lcl_SetCpyPos(pMark->GetOtherMarkPos(), rStt, *pCpyStt, *aTmpPam.GetMark(), nDelCount);
301 OUString sRequestedName = pMark->GetName();
302 if (flags & SwCopyFlags::IsMoveToFly)
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::IMark*>(pMark);
307 rDestDoc.getIDocumentMarkAccess()->renameMark(pSoonToBeDeletedMark,
308 sRequestedName + "COPY_IS_MOVE");
311 ::sw::mark::IMark* 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)
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
324 rDestDoc.getIDocumentMarkAccess()->renameMark(pNewMark, sRequestedName);
326 // copying additional attributes for bookmarks or fieldmarks
327 ::sw::mark::IBookmark* const pNewBookmark =
328 dynamic_cast< ::sw::mark::IBookmark* const >(pNewMark);
329 const ::sw::mark::IBookmark* const pOldBookmark =
330 dynamic_cast< const ::sw::mark::IBookmark* >(pMark);
331 if (pNewBookmark && pOldBookmark)
333 pNewBookmark->SetKeyCode(pOldBookmark->GetKeyCode());
334 pNewBookmark->SetShortName(pOldBookmark->GetShortName());
335 pNewBookmark->Hide(pOldBookmark->IsHidden());
336 pNewBookmark->SetHideCondition(pOldBookmark->GetHideCondition());
338 ::sw::mark::IFieldmark* const pNewFieldmark =
339 dynamic_cast< ::sw::mark::IFieldmark* const >(pNewMark);
340 const ::sw::mark::IFieldmark* const pOldFieldmark =
341 dynamic_cast< const ::sw::mark::IFieldmark* >(pMark);
342 if (pNewFieldmark && pOldFieldmark)
344 pNewFieldmark->SetFieldname(pOldFieldmark->GetFieldname());
345 pNewFieldmark->SetFieldHelptext(pOldFieldmark->GetFieldHelptext());
346 ::sw::mark::IFieldmark::parameter_map_t* pNewParams = pNewFieldmark->GetParameters();
347 const ::sw::mark::IFieldmark::parameter_map_t* pOldParams = pOldFieldmark->GetParameters();
348 for (const auto& rEntry : *pOldParams )
350 pNewParams->insert( rEntry );
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)
360 pNewMetadatable->RegisterAsCopyOf(*pMetadatable);
364 } // namespace sw
366 namespace
368 void lcl_DeleteRedlines( const SwPaM& rPam, SwPaM& rCpyPam )
370 const SwDoc& rSrcDoc = rPam.GetDoc();
371 const SwRedlineTable& rTable = rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable();
372 if( rTable.empty() )
373 return;
375 SwDoc& rDestDoc = rCpyPam.GetDoc();
376 SwPosition* pCpyStt = rCpyPam.Start(), *pCpyEnd = rCpyPam.End();
377 std::unique_ptr<SwPaM> pDelPam;
378 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
379 // We have to count the "non-copied" nodes
380 SwNodeOffset nDelCount;
381 SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount));
383 SwRedlineTable::size_type n = 0;
384 rSrcDoc.getIDocumentRedlineAccess().GetRedline( *pStt, &n );
385 for( ; n < rTable.size(); ++n )
387 const SwRangeRedline* pRedl = rTable[ n ];
388 if( RedlineType::Delete == pRedl->GetType() && pRedl->IsVisible() )
390 auto [pRStt, pREnd] = pRedl->StartEnd(); // SwPosition*
392 SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd );
393 switch( eCmpPos )
395 case SwComparePosition::CollideEnd:
396 case SwComparePosition::Before:
397 // Pos1 is before Pos2
398 break;
400 case SwComparePosition::CollideStart:
401 case SwComparePosition::Behind:
402 // Pos1 is after Pos2
403 n = rTable.size();
404 break;
406 default:
408 pDelPam.reset(new SwPaM( *pCpyStt, pDelPam.release() ));
409 if( *pStt < *pRStt )
411 lcl_NonCopyCount( rPam, aCorrIdx, pRStt->GetNodeIndex(), nDelCount );
412 lcl_SetCpyPos( *pRStt, *pStt, *pCpyStt,
413 *pDelPam->GetPoint(), nDelCount );
415 pDelPam->SetMark();
417 if( *pEnd < *pREnd )
418 *pDelPam->GetPoint() = *pCpyEnd;
419 else
421 lcl_NonCopyCount( rPam, aCorrIdx, pREnd->GetNodeIndex(), nDelCount );
422 lcl_SetCpyPos( *pREnd, *pStt, *pCpyStt,
423 *pDelPam->GetPoint(), nDelCount );
426 if (pDelPam->GetNext() != pDelPam.get()
427 && *pDelPam->GetNext()->End() == *pDelPam->Start())
429 *pDelPam->GetNext()->End() = *pDelPam->End();
430 pDelPam.reset(pDelPam->GetNext());
437 if( !pDelPam )
438 return;
440 RedlineFlags eOld = rDestDoc.getIDocumentRedlineAccess().GetRedlineFlags();
441 rDestDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::Ignore );
443 ::sw::UndoGuard const undoGuard(rDestDoc.GetIDocumentUndoRedo());
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 );
457 rDestDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
460 void lcl_DeleteRedlines( const SwNodeRange& rRg, SwNodeRange const & rCpyRg )
462 SwDoc& rSrcDoc = rRg.aStart.GetNode().GetDoc();
463 if( !rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
465 SwPaM aRgTmp( rRg.aStart, rRg.aEnd );
466 SwPaM aCpyTmp( rCpyRg.aStart, rCpyRg.aEnd );
467 lcl_DeleteRedlines( aRgTmp, aCpyTmp );
471 void lcl_ChainFormats( SwFlyFrameFormat *pSrc, SwFlyFrameFormat *pDest )
473 SwFormatChain aSrc( pSrc->GetChain() );
474 if ( !aSrc.GetNext() )
476 aSrc.SetNext( pDest );
477 pSrc->SetFormatAttr( aSrc );
479 SwFormatChain aDest( pDest->GetChain() );
480 if ( !aDest.GetPrev() )
482 aDest.SetPrev( pSrc );
483 pDest->SetFormatAttr( aDest );
487 // #i86492#
488 bool lcl_ContainsOnlyParagraphsInList( const SwPaM& rPam )
490 bool bRet = false;
492 const SwTextNode* pTextNd = rPam.Start()->GetNode().GetTextNode();
493 const SwTextNode* pEndTextNd = rPam.End()->GetNode().GetTextNode();
494 if ( pTextNd && pTextNd->IsInList() &&
495 pEndTextNd && pEndTextNd->IsInList() )
497 bRet = true;
498 SwNodeIndex aIdx(rPam.Start()->GetNode());
502 ++aIdx;
503 pTextNd = aIdx.GetNode().GetTextNode();
505 if ( !pTextNd || !pTextNd->IsInList() )
507 bRet = false;
508 break;
510 } while (pTextNd != pEndTextNd);
513 return bRet;
516 bool lcl_MarksWholeNode(const SwPaM & rPam)
518 bool bResult = false;
519 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
521 if (nullptr != pStt && nullptr != pEnd)
523 const SwTextNode* pSttNd = pStt->GetNode().GetTextNode();
524 const SwTextNode* pEndNd = pEnd->GetNode().GetTextNode();
526 if (nullptr != pSttNd && nullptr != pEndNd &&
527 pStt->GetContentIndex() == 0 &&
528 pEnd->GetContentIndex() == pEndNd->Len())
530 bResult = true;
534 return bResult;
538 //local functions originally from sw/source/core/doc/docedt.cxx
539 namespace sw
541 void CalcBreaks(std::vector<std::pair<SwNodeOffset, sal_Int32>> & rBreaks,
542 SwPaM const & rPam, bool const isOnlyFieldmarks)
544 SwNodeOffset const nStartNode(rPam.Start()->GetNodeIndex());
545 SwNodeOffset const nEndNode(rPam.End()->GetNodeIndex());
546 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
547 IDocumentMarkAccess const& rIDMA(*rPam.GetDoc().getIDocumentMarkAccess());
549 std::stack<std::tuple<sw::mark::IFieldmark const*, bool, SwNodeOffset, sal_Int32>> startedFields;
551 for (SwNodeOffset n = nStartNode; n <= nEndNode; ++n)
553 SwNode *const pNode(rNodes[n]);
554 if (pNode->IsTextNode())
556 SwTextNode & rTextNode(*pNode->GetTextNode());
557 sal_Int32 const nStart(n == nStartNode
558 ? rPam.Start()->GetContentIndex()
559 : 0);
560 sal_Int32 const nEnd(n == nEndNode
561 ? rPam.End()->GetContentIndex()
562 : rTextNode.Len());
563 for (sal_Int32 i = nStart; i < nEnd; ++i)
565 const sal_Unicode c(rTextNode.GetText()[i]);
566 switch (c)
568 // note: CH_TXT_ATR_FORMELEMENT does not need handling
569 // not sure how CH_TXT_ATR_INPUTFIELDSTART/END are currently handled
570 case CH_TXTATR_INWORD:
571 case CH_TXTATR_BREAKWORD:
573 // META hints only have dummy char at the start, not
574 // at the end, so no need to check in nStartNode
575 if (n == nEndNode && !isOnlyFieldmarks)
577 SwTextAttr const* pAttr(rTextNode.GetTextAttrForCharAt(i));
578 if (pAttr && pAttr->End() && (nEnd < *pAttr->End()))
580 assert(pAttr->HasDummyChar());
581 rBreaks.emplace_back(n, i);
584 if (!pAttr)
586 // See if this is an end dummy character for a content control.
587 pAttr = rTextNode.GetTextAttrForEndCharAt(i, RES_TXTATR_CONTENTCONTROL);
588 if (pAttr && (nStart > pAttr->GetStart()))
590 rBreaks.emplace_back(n, i);
594 break;
596 case CH_TXT_ATR_FIELDSTART:
598 auto const pFieldMark(rIDMA.getFieldmarkAt(SwPosition(rTextNode, i)));
599 startedFields.emplace(pFieldMark, false, 0, 0);
600 break;
602 case CH_TXT_ATR_FIELDSEP:
604 if (startedFields.empty())
606 rBreaks.emplace_back(n, i);
608 else
609 { // no way to find the field via MarkManager...
610 assert(std::get<0>(startedFields.top())->IsCoveringPosition(SwPosition(rTextNode, i)));
611 std::get<1>(startedFields.top()) = true;
612 std::get<2>(startedFields.top()) = n;
613 std::get<3>(startedFields.top()) = i;
615 break;
617 case CH_TXT_ATR_FIELDEND:
619 if (startedFields.empty())
621 rBreaks.emplace_back(n, i);
623 else
624 { // fieldmarks must not overlap => stack
625 assert(std::get<0>(startedFields.top()) == rIDMA.getFieldmarkAt(SwPosition(rTextNode, i)));
626 startedFields.pop();
628 break;
633 else if (pNode->IsStartNode())
635 if (pNode->EndOfSectionIndex() <= nEndNode)
636 { // fieldmark cannot overlap node section
637 n = pNode->EndOfSectionIndex();
640 else
641 { // EndNode can actually happen with sections :(
642 assert(pNode->IsEndNode() || pNode->IsNoTextNode());
645 while (!startedFields.empty())
647 if (const sw::mark::IFieldmark* pMark = std::get<0>(startedFields.top()))
649 SwPosition const& rStart(pMark->GetMarkStart());
650 std::pair<SwNodeOffset, sal_Int32> const pos(
651 rStart.GetNodeIndex(), rStart.GetContentIndex());
652 auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), pos);
653 assert(it == rBreaks.end() || *it != pos);
654 rBreaks.insert(it, pos);
656 if (std::get<1>(startedFields.top()))
658 std::pair<SwNodeOffset, sal_Int32> const posSep(
659 std::get<2>(startedFields.top()),
660 std::get<3>(startedFields.top()));
661 auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), posSep);
662 assert(it == rBreaks.end() || *it != posSep);
663 rBreaks.insert(it, posSep);
665 startedFields.pop();
670 namespace
673 bool lcl_DoWithBreaks(::sw::DocumentContentOperationsManager & rDocumentContentOperations,
674 SwPaM & rPam, SwDeleteFlags const flags,
675 bool (::sw::DocumentContentOperationsManager::*pFunc)(SwPaM&, SwDeleteFlags))
677 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
679 sw::CalcBreaks(Breaks, rPam);
681 if (Breaks.empty())
683 return (rDocumentContentOperations.*pFunc)(rPam, flags);
686 // Deletion must be split into several parts if the text node
687 // contains a text attribute with end and with dummy character
688 // and the selection does not contain the text attribute completely,
689 // but overlaps its start (left), where the dummy character is.
691 SwPosition const & rSelectionEnd( *rPam.End() );
693 bool bRet( true );
694 // iterate from end to start, to avoid invalidating the offsets!
695 auto iter( Breaks.rbegin() );
696 SwNodeOffset nOffset(0);
697 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
698 SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node!
699 SwPosition & rEnd( *aPam.End() );
700 SwPosition & rStart( *aPam.Start() );
702 while (iter != Breaks.rend())
704 rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
705 if (rStart < rEnd) // check if part is empty
707 bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags);
708 nOffset = iter->first - rStart.GetNodeIndex(); // deleted fly nodes...
710 rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
711 ++iter;
714 rStart = *rPam.Start(); // set to original start
715 if (rStart < rEnd) // check if part is empty
717 bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags);
720 return bRet;
723 bool lcl_StrLenOverflow( const SwPaM& rPam )
725 // If we try to merge two paragraphs we have to test if afterwards
726 // the string doesn't exceed the allowed string length
727 if( rPam.GetPoint()->GetNode() != rPam.GetMark()->GetNode() )
729 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
730 const SwTextNode* pEndNd = pEnd->GetNode().GetTextNode();
731 if( (nullptr != pEndNd) && pStt->GetNode().IsTextNode() )
733 const sal_uInt64 nSum = pStt->GetContentIndex() +
734 pEndNd->GetText().getLength() - pEnd->GetContentIndex();
735 return nSum > o3tl::make_unsigned(SAL_MAX_INT32);
738 return false;
741 struct SaveRedline
743 SwRangeRedline* pRedl;
744 SwNodeOffset nStt, nEnd;
745 sal_Int32 nSttCnt;
746 sal_Int32 nEndCnt;
748 SaveRedline( SwRangeRedline* pR, const SwNodeIndex& rSttIdx )
749 : pRedl(pR)
750 , nEnd(0)
751 , nEndCnt(0)
753 auto [pStt, pEnd] = pR->StartEnd(); // SwPosition*
754 SwNodeOffset nSttIdx = rSttIdx.GetIndex();
755 nStt = pStt->GetNodeIndex() - nSttIdx;
756 nSttCnt = pStt->GetContentIndex();
757 if( pR->HasMark() )
759 nEnd = pEnd->GetNodeIndex() - nSttIdx;
760 nEndCnt = pEnd->GetContentIndex();
763 pRedl->GetPoint()->Assign( SwNodeOffset(0) );
764 pRedl->GetMark()->Assign( SwNodeOffset(0) );
767 SaveRedline( SwRangeRedline* pR, const SwPosition& rPos )
768 : pRedl(pR)
769 , nEnd(0)
770 , nEndCnt(0)
772 auto [pStt, pEnd] = pR->StartEnd(); // SwPosition*
773 SwNodeOffset nSttIdx = rPos.GetNodeIndex();
774 nStt = pStt->GetNodeIndex() - nSttIdx;
775 nSttCnt = pStt->GetContentIndex();
776 if( nStt == SwNodeOffset(0) )
777 nSttCnt = nSttCnt - rPos.GetContentIndex();
778 if( pR->HasMark() )
780 nEnd = pEnd->GetNodeIndex() - nSttIdx;
781 nEndCnt = pEnd->GetContentIndex();
782 if( nEnd == SwNodeOffset(0) )
783 nEndCnt = nEndCnt - rPos.GetContentIndex();
786 pRedl->GetPoint()->Assign( SwNodeOffset(0) );
787 pRedl->GetMark()->Assign( SwNodeOffset(0) );
790 void SetPos( SwNodeOffset nInsPos )
792 pRedl->GetPoint()->Assign( nInsPos + nStt, nSttCnt );
793 if( pRedl->HasMark() )
795 pRedl->GetMark()->Assign( nInsPos + nEnd, nEndCnt );
799 void SetPos( const SwPosition& aPos )
801 pRedl->GetPoint()->Assign( aPos.GetNodeIndex() + nStt,
802 nSttCnt + ( nStt == SwNodeOffset(0) ? aPos.GetContentIndex() : 0 ) );
803 if( pRedl->HasMark() )
805 pRedl->GetMark()->Assign( aPos.GetNodeIndex() + nEnd,
806 nEndCnt + ( nEnd == SwNodeOffset(0) ? aPos.GetContentIndex() : 0 ) );
811 typedef std::vector< SaveRedline > SaveRedlines_t;
813 void lcl_SaveRedlines(const SwPaM& aPam, SaveRedlines_t& rArr)
815 SwDoc& rDoc = aPam.GetPointNode().GetDoc();
817 auto [pStart, pEnd] = aPam.StartEnd(); // SwPosition*
819 // get first relevant redline
820 SwRedlineTable::size_type nCurrentRedline;
821 rDoc.getIDocumentRedlineAccess().GetRedline( *pStart, &nCurrentRedline );
822 if( nCurrentRedline > 0)
823 nCurrentRedline--;
825 // redline mode RedlineFlags::Ignore|RedlineFlags::On; save old mode
826 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
827 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
829 // iterate over relevant redlines and decide for each whether it should
830 // be saved, or split + saved
831 SwRedlineTable& rRedlineTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
832 for( ; nCurrentRedline < rRedlineTable.size(); nCurrentRedline++ )
834 SwRangeRedline* pCurrent = rRedlineTable[ nCurrentRedline ];
835 SwComparePosition eCompare =
836 ComparePosition( *pCurrent->Start(), *pCurrent->End(),
837 *pStart, *pEnd);
839 // we must save this redline if it overlaps aPam
840 // (we may have to split it, too)
841 if( eCompare == SwComparePosition::OverlapBehind ||
842 eCompare == SwComparePosition::OverlapBefore ||
843 eCompare == SwComparePosition::Outside ||
844 eCompare == SwComparePosition::Inside ||
845 eCompare == SwComparePosition::Equal )
847 rRedlineTable.Remove( nCurrentRedline-- );
849 // split beginning, if necessary
850 if( eCompare == SwComparePosition::OverlapBefore ||
851 eCompare == SwComparePosition::Outside )
853 SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent );
854 *pNewRedline->End() = *pStart;
855 *pCurrent->Start() = *pStart;
856 rDoc.getIDocumentRedlineAccess().AppendRedline( pNewRedline, true );
859 // split end, if necessary
860 if( eCompare == SwComparePosition::OverlapBehind ||
861 eCompare == SwComparePosition::Outside )
863 SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent );
864 *pNewRedline->Start() = *pEnd;
865 *pCurrent->End() = *pEnd;
866 rDoc.getIDocumentRedlineAccess().AppendRedline( pNewRedline, true );
869 // save the current redline
870 rArr.emplace_back( pCurrent, *pStart );
874 // restore old redline mode
875 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
878 void lcl_RestoreRedlines(SwDoc& rDoc, const SwPosition& rPos, SaveRedlines_t& rArr)
880 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
881 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
883 for(SaveRedline & rSvRedLine : rArr)
885 rSvRedLine.SetPos( rPos );
886 rDoc.getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true );
889 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
892 void lcl_SaveRedlines(const SwNodeRange& rRg, SaveRedlines_t& rArr)
894 SwDoc& rDoc = rRg.aStart.GetNode().GetDoc();
895 SwRedlineTable::size_type nRedlPos;
896 SwPosition aSrchPos( rRg.aStart );
897 aSrchPos.Adjust(SwNodeOffset(-1));
898 if( rDoc.getIDocumentRedlineAccess().GetRedline( aSrchPos, &nRedlPos ) && nRedlPos )
899 --nRedlPos;
900 else if( nRedlPos >= rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() )
901 return ;
903 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
904 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
905 SwRedlineTable& rRedlTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
907 do {
908 SwRangeRedline* pTmp = rRedlTable[ nRedlPos ];
910 auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition*
912 if( pRStt->GetNode() < rRg.aStart.GetNode() )
914 if( pREnd->GetNode() > rRg.aStart.GetNode() && pREnd->GetNode() < rRg.aEnd.GetNode() )
916 // Create a copy and set the end of the original to the end of the MoveArea.
917 // The copy is moved too.
918 SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp );
919 SwPosition* pTmpPos = pNewRedl->Start();
920 pTmpPos->Assign(rRg.aStart);
922 rArr.emplace_back(pNewRedl, rRg.aStart);
924 pTmpPos = pTmp->End();
925 pTmpPos->Assign(rRg.aEnd);
927 else if( pREnd->GetNode() == rRg.aStart.GetNode() )
929 SwPosition* pTmpPos = pTmp->End();
930 pTmpPos->Assign(rRg.aEnd);
933 else if( pRStt->GetNode() < rRg.aEnd.GetNode() )
935 rRedlTable.Remove( nRedlPos-- );
936 if( pREnd->GetNode() < rRg.aEnd.GetNode() ||
937 ( pREnd->GetNode() == rRg.aEnd.GetNode() && !pREnd->GetContentIndex()) )
939 // move everything
940 rArr.emplace_back( pTmp, rRg.aStart );
942 else
944 // split
945 SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp );
946 SwPosition* pTmpPos = pNewRedl->End();
947 pTmpPos->Assign(rRg.aEnd);
949 rArr.emplace_back( pNewRedl, rRg.aStart );
951 pTmpPos = pTmp->Start();
952 pTmpPos->Assign(rRg.aEnd);
953 rDoc.getIDocumentRedlineAccess().AppendRedline( pTmp, true );
956 else
957 break;
959 } while( ++nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() );
960 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
963 void lcl_RestoreRedlines(SwDoc& rDoc, SwNodeOffset const nInsPos, SaveRedlines_t& rArr)
965 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
966 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
968 for(SaveRedline & rSvRedLine : rArr)
970 rSvRedLine.SetPos( nInsPos );
971 IDocumentRedlineAccess::AppendResult const result(
972 rDoc.getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ));
973 if ( IDocumentRedlineAccess::AppendResult::APPENDED == result &&
974 rSvRedLine.pRedl->GetType() == RedlineType::Delete )
976 UpdateFramesForAddDeleteRedline(rDoc, *rSvRedLine.pRedl);
980 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
983 bool lcl_SaveFootnote( const SwNode& rSttNd, const SwNode& rEndNd,
984 const SwNode& rInsPos,
985 SwFootnoteIdxs& rFootnoteArr, SwFootnoteIdxs& rSaveArr,
986 std::optional<sal_Int32> oSttCnt = std::nullopt, std::optional<sal_Int32> oEndCnt = std::nullopt )
988 bool bUpdateFootnote = false;
989 const SwNodes& rNds = rInsPos.GetNodes();
990 const bool bDelFootnote = rInsPos.GetIndex() < rNds.GetEndOfAutotext().GetIndex() &&
991 rSttNd.GetIndex() >= rNds.GetEndOfAutotext().GetIndex();
992 const bool bSaveFootnote = !bDelFootnote &&
993 rInsPos.GetIndex() >= rNds.GetEndOfExtras().GetIndex();
994 if( !rFootnoteArr.empty() )
997 size_t nPos = 0;
998 rFootnoteArr.SeekEntry( rSttNd, &nPos );
999 SwTextFootnote* pSrch;
1000 const SwNode* pFootnoteNd;
1002 // Delete/save all that come after it
1003 while( nPos < rFootnoteArr.size() && ( pFootnoteNd =
1004 &( pSrch = rFootnoteArr[ nPos ] )->GetTextNode())->GetIndex()
1005 <= rEndNd.GetIndex() )
1007 const sal_Int32 nFootnoteSttIdx = pSrch->GetStart();
1008 if( ( oEndCnt && oSttCnt )
1009 ? (( &rSttNd == pFootnoteNd &&
1010 *oSttCnt > nFootnoteSttIdx) ||
1011 ( &rEndNd == pFootnoteNd &&
1012 nFootnoteSttIdx >= *oEndCnt ))
1013 : ( &rEndNd == pFootnoteNd ))
1015 ++nPos; // continue searching
1017 else
1019 // delete it
1020 if( bDelFootnote )
1022 SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode());
1023 SwContentIndex aIdx( &rTextNd, nFootnoteSttIdx );
1024 rTextNd.EraseText( aIdx, 1 );
1026 else
1028 pSrch->DelFrames(nullptr);
1029 rFootnoteArr.erase( rFootnoteArr.begin() + nPos );
1030 if( bSaveFootnote )
1031 rSaveArr.insert( pSrch );
1033 bUpdateFootnote = true;
1037 while( nPos-- && ( pFootnoteNd = &( pSrch = rFootnoteArr[ nPos ] )->
1038 GetTextNode())->GetIndex() >= rSttNd.GetIndex() )
1040 const sal_Int32 nFootnoteSttIdx = pSrch->GetStart();
1041 if( !oEndCnt || !oSttCnt ||
1042 ! (( &rSttNd == pFootnoteNd &&
1043 *oSttCnt > nFootnoteSttIdx ) ||
1044 ( &rEndNd == pFootnoteNd &&
1045 nFootnoteSttIdx >= *oEndCnt )) )
1047 if( bDelFootnote )
1049 // delete it
1050 SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode());
1051 SwContentIndex aIdx( &rTextNd, nFootnoteSttIdx );
1052 rTextNd.EraseText( aIdx, 1 );
1054 else
1056 pSrch->DelFrames(nullptr);
1057 rFootnoteArr.erase( rFootnoteArr.begin() + nPos );
1058 if( bSaveFootnote )
1059 rSaveArr.insert( pSrch );
1061 bUpdateFootnote = true;
1065 // When moving from redline section into document content section, e.g.
1066 // after loading a document with (delete-)redlines, the footnote array
1067 // has to be adjusted... (#i70572)
1068 if( bSaveFootnote )
1070 SwNodeIndex aIdx( rSttNd );
1071 while( aIdx < rEndNd ) // Check the moved section
1073 SwNode* pNode = &aIdx.GetNode();
1074 if( pNode->IsTextNode() ) // Looking for text nodes...
1076 SwpHints *pHints = pNode->GetTextNode()->GetpSwpHints();
1077 if( pHints && pHints->HasFootnote() ) //...with footnotes
1079 bUpdateFootnote = true; // Heureka
1080 const size_t nCount = pHints->Count();
1081 for( size_t i = 0; i < nCount; ++i )
1083 SwTextAttr *pAttr = pHints->Get( i );
1084 if ( pAttr->Which() == RES_TXTATR_FTN )
1086 rSaveArr.insert( static_cast<SwTextFootnote*>(pAttr) );
1091 ++aIdx;
1094 return bUpdateFootnote;
1097 bool lcl_MayOverwrite( const SwTextNode *pNode, const sal_Int32 nPos )
1099 sal_Unicode const cChr = pNode->GetText()[nPos];
1100 switch (cChr)
1102 case CH_TXTATR_BREAKWORD:
1103 case CH_TXTATR_INWORD:
1104 return !pNode->GetTextAttrForCharAt(nPos);// how could there be none?
1105 case CH_TXT_ATR_INPUTFIELDSTART:
1106 case CH_TXT_ATR_INPUTFIELDEND:
1107 case CH_TXT_ATR_FIELDSTART:
1108 case CH_TXT_ATR_FIELDSEP:
1109 case CH_TXT_ATR_FIELDEND:
1110 case CH_TXT_ATR_FORMELEMENT:
1111 return false;
1112 default:
1113 return true;
1117 void lcl_SkipAttr( const SwTextNode *pNode, SwPosition &rIdx, sal_Int32 &rStart )
1119 if( !lcl_MayOverwrite( pNode, rStart ) )
1121 // skip all special attributes
1122 do {
1123 rIdx.AdjustContent(+1);
1124 rStart = rIdx.GetContentIndex();
1125 } while (rStart < pNode->GetText().getLength()
1126 && !lcl_MayOverwrite(pNode, rStart) );
1130 bool lcl_GetTokenToParaBreak( OUString& rStr, OUString& rRet, bool bRegExpRplc )
1132 if( bRegExpRplc )
1134 sal_Int32 nPos = 0;
1135 static constexpr OUString sPara(u"\\n"_ustr);
1136 for (;;)
1138 nPos = rStr.indexOf( sPara, nPos );
1139 if (nPos<0)
1141 break;
1143 // Has this been escaped?
1144 if( nPos && '\\' == rStr[nPos-1])
1146 ++nPos;
1147 if( nPos >= rStr.getLength() )
1149 break;
1152 else
1154 rRet = rStr.copy( 0, nPos );
1155 rStr = rStr.copy( nPos + sPara.getLength() );
1156 return true;
1160 rRet = rStr;
1161 rStr.clear();
1162 return false;
1166 namespace //local functions originally from docfmt.cxx
1169 bool lcl_ApplyOtherSet(
1170 SwContentNode & rNode,
1171 SwHistory *const pHistory,
1172 SfxItemSet const& rOtherSet,
1173 SfxItemSet const& rFirstSet,
1174 SfxItemSet const& rPropsSet,
1175 SwRootFrame const*const pLayout,
1176 SwNodeIndex *const o_pIndex = nullptr)
1178 assert(rOtherSet.Count());
1180 bool ret(false);
1181 SwTextNode *const pTNd = rNode.GetTextNode();
1182 sw::MergedPara const* pMerged(nullptr);
1183 if (pLayout && pLayout->HasMergedParas() && pTNd)
1185 SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(
1186 pTNd->getLayoutFrame(pLayout)));
1187 if (pTextFrame)
1189 pMerged = pTextFrame->GetMergedPara();
1191 if (pMerged)
1193 if (rFirstSet.Count())
1195 if (pHistory)
1197 SwRegHistory aRegH(pMerged->pFirstNode, *pMerged->pFirstNode, pHistory);
1198 ret = pMerged->pFirstNode->SetAttr(rFirstSet);
1200 else
1202 ret = pMerged->pFirstNode->SetAttr(rFirstSet);
1205 if (rPropsSet.Count())
1207 if (pHistory)
1209 SwRegHistory aRegH(pMerged->pParaPropsNode, *pMerged->pParaPropsNode, pHistory);
1210 ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret;
1212 else
1214 ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret;
1217 if (o_pIndex)
1219 *o_pIndex = *pMerged->pLastNode; // skip hidden
1224 // input cursor can't be on hidden node, and iteration skips them
1225 assert(!pLayout || !pLayout->HasMergedParas()
1226 || rNode.GetRedlineMergeFlag() != SwNode::Merge::Hidden);
1228 if (!pMerged)
1230 if (pHistory)
1232 SwRegHistory aRegH(&rNode, rNode, pHistory);
1233 ret = rNode.SetAttr( rOtherSet );
1235 else
1237 ret = rNode.SetAttr( rOtherSet );
1240 return ret;
1243 #define DELETECHARSETS if ( bDelete ) { delete pCharSet; delete pOtherSet; }
1245 // set format redline with extra data for lcl_InsAttr()
1246 void lcl_SetRedline(
1247 SwDoc& rDoc,
1248 const SwPaM &rRg)
1250 std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra;
1252 // check existing redline on the same range, and use its extra data, if it exists
1253 SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos(
1254 rRg.Start()->GetNode(), RedlineType::Format );
1255 if( SwRedlineTable::npos != nRedlPos )
1257 const SwPosition *pRStt, *pREnd;
1258 do {
1259 SwRangeRedline* pTmp = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
1260 pRStt = pTmp->Start();
1261 pREnd = pTmp->End();
1262 SwComparePosition eCompare = ComparePosition( *rRg.Start(), *rRg.End(), *pRStt, *pREnd );
1263 if ( eCompare == SwComparePosition::Inside || eCompare == SwComparePosition::Equal )
1265 if (pTmp->GetExtraData())
1267 const SwRedlineExtraData* pExtraData = pTmp->GetExtraData();
1268 const SwRedlineExtraData_FormatColl* pFormattingChanges =
1269 dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
1270 // Check if the extra data is of type 'formatting changes'
1271 if (pFormattingChanges)
1273 // Get the item set that holds all the changes properties
1274 const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
1275 xExtra.reset(new SwRedlineExtraData_FormatColl(u""_ustr, USHRT_MAX, pChangesSet));
1276 break;
1280 } while( pRStt <= rRg.Start() && ++nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size());
1283 SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::Format, rRg );
1284 auto const result(rDoc.getIDocumentRedlineAccess().AppendRedline( pRedline, true));
1285 // store original text attributes to reject formatting change
1286 if (IDocumentRedlineAccess::AppendResult::IGNORED == result)
1287 return;
1289 // no existing format redline in the range
1290 if (!xExtra)
1292 // Apply the first character's attributes to the ReplaceText
1293 SfxItemSetFixed<RES_CHRATR_BEGIN, RES_TXTATR_WITHEND_END - 1,
1294 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1> aSet( rDoc.GetAttrPool() );
1295 SwTextNode * pNode = rRg.Start()->GetNode().GetTextNode();
1296 pNode->GetParaAttr( aSet, rRg.Start()->GetContentIndex() + 1, rRg.End()->GetContentIndex() );
1298 aSet.ClearItem( RES_TXTATR_REFMARK );
1299 aSet.ClearItem( RES_TXTATR_TOXMARK );
1300 aSet.ClearItem( RES_TXTATR_CJK_RUBY );
1301 aSet.ClearItem( RES_TXTATR_INETFMT );
1302 aSet.ClearItem( RES_TXTATR_META );
1303 aSet.ClearItem( RES_TXTATR_METAFIELD );
1305 // After GetParaAttr aSet can contain invalid/dontcare items (true == IsInvalidItem,
1306 // DONTCARE == SfxItemState), e.g. RES_TXTATR_CHARFMT and (a copy of) this
1307 // SfxItemSet can be passed to MSWordExportBase::OutputItemSet
1308 // which doesn't handle invalid/dontcare items so clear them here
1309 aSet.ClearInvalidItems();
1311 xExtra.reset(new SwRedlineExtraData_FormatColl(u""_ustr, USHRT_MAX, &aSet));
1314 if (xExtra)
1316 pRedline->SetExtraData(xExtra.get() );
1320 // create format redline(s) for the given range:
1321 // to track the original formatting stored in the
1322 // hints, create redlines for all parts of the
1323 // range partitioned by boundaries of the hints.
1324 void lcl_SetRedlines(
1325 SwDoc& rDoc,
1326 const SwPaM &rRg)
1328 SwNodeIndex aIdx( rRg.Start()->GetNode() );
1329 const SwNodeIndex aEndNd( rRg.End()->GetNode() );
1330 while( aIdx <= aEndNd )
1332 SwTextNode *pNode = aIdx.GetNode().GetTextNode();
1333 if( pNode )
1335 const sal_Int32 nStart = aIdx == rRg.Start()->GetNode()
1336 ? rRg.Start()->GetContentIndex()
1337 : 0;
1338 const sal_Int32 nEnd = aIdx < aEndNd
1339 ? pNode->GetText().getLength()
1340 : rRg.End()->GetContentIndex();
1342 if( SwpHints *pHints = pNode->GetpSwpHints() )
1344 const size_t nCount = pHints->Count();
1345 sal_Int32 nRedEnd = nStart;
1346 for( size_t i = 0; i < nCount; ++i )
1348 SwTextAttr *pAttr = pHints->Get( i );
1350 if ( pAttr->GetStart() > nEnd )
1352 break; // after the range
1355 if ( !pAttr->GetEnd() || *pAttr->GetEnd() < nStart )
1357 continue; // before the range
1360 // range part before the hint
1361 if ( nRedEnd < pAttr->GetStart() )
1363 SwPaM aPam( *pNode, nRedEnd, *pNode, pAttr->GetStart() );
1364 lcl_SetRedline(rDoc, aPam);
1367 // range part at the hint
1368 sal_Int32 nRedStart = std::max(pAttr->GetStart(), nStart);
1369 nRedEnd = std::min(*pAttr->GetEnd(), nEnd);
1370 SwPaM aPam2( *pNode, nRedStart, *pNode, nRedEnd );
1371 lcl_SetRedline(rDoc, aPam2);
1374 // range part after the last hint
1375 if ( nRedEnd < nEnd )
1377 SwPaM aPam( *pNode, nRedEnd, *pNode, nEnd );
1378 lcl_SetRedline(rDoc, aPam);
1381 else
1383 SwPaM aPam( *pNode, nStart, *pNode, nEnd );
1384 lcl_SetRedline(rDoc, aPam);
1387 ++aIdx;
1391 /// Insert Hints according to content types;
1392 // Is used in SwDoc::Insert(..., SwFormatHint &rHt)
1394 bool lcl_InsAttr(
1395 SwDoc& rDoc,
1396 const SwPaM &rRg,
1397 const SfxItemSet& rChgSet,
1398 const SetAttrMode nFlags,
1399 SwUndoAttr *const pUndo,
1400 SwRootFrame const*const pLayout,
1401 SwTextAttr **ppNewTextAttr)
1403 // Divide the Sets (for selections in Nodes)
1404 const SfxItemSet* pCharSet = nullptr;
1405 const SfxItemSet* pOtherSet = nullptr;
1406 bool bDelete = false;
1407 bool bCharAttr = false;
1408 bool bOtherAttr = false;
1410 // Check, if we can work with rChgSet or if we have to create additional SfxItemSets
1411 if ( 1 == rChgSet.Count() )
1413 SfxItemIter aIter( rChgSet );
1414 const SfxPoolItem* pItem = aIter.GetCurItem();
1415 if (pItem && !IsInvalidItem(pItem))
1417 const sal_uInt16 nWhich = pItem->Which();
1419 if ( isCHRATR(nWhich) ||
1420 (RES_TXTATR_CHARFMT == nWhich) ||
1421 (RES_TXTATR_INETFMT == nWhich) ||
1422 (RES_TXTATR_AUTOFMT == nWhich) ||
1423 (RES_TXTATR_UNKNOWN_CONTAINER == nWhich) )
1425 pCharSet = &rChgSet;
1426 bCharAttr = true;
1429 if ( isPARATR(nWhich)
1430 || isPARATR_LIST(nWhich)
1431 || isFRMATR(nWhich)
1432 || isGRFATR(nWhich)
1433 || isUNKNOWNATR(nWhich)
1434 || isDrawingLayerAttribute(nWhich) )
1436 pOtherSet = &rChgSet;
1437 bOtherAttr = true;
1442 // Build new itemset if either
1443 // - rChgSet.Count() > 1 or
1444 // - The attribute in rChgSet does not belong to one of the above categories
1445 if ( !bCharAttr && !bOtherAttr )
1447 SfxItemSet* pTmpCharItemSet = new SfxItemSetFixed<
1448 RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
1449 RES_TXTATR_AUTOFMT, RES_TXTATR_CHARFMT,
1450 RES_TXTATR_UNKNOWN_CONTAINER,
1451 RES_TXTATR_UNKNOWN_CONTAINER>( rDoc.GetAttrPool() );
1453 SfxItemSet* pTmpOtherItemSet = new SfxItemSetFixed<
1454 RES_PARATR_BEGIN, RES_GRFATR_END - 1,
1455 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1,
1456 // FillAttribute support:
1457 XATTR_FILL_FIRST, XATTR_FILL_LAST>( rDoc.GetAttrPool() );
1459 pTmpCharItemSet->Put( rChgSet );
1460 pTmpOtherItemSet->Put( rChgSet );
1462 pCharSet = pTmpCharItemSet;
1463 pOtherSet = pTmpOtherItemSet;
1465 bDelete = true;
1468 SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr;
1469 bool bRet = false;
1470 const SwPosition *pStt = rRg.Start(), *pEnd = rRg.End();
1471 SwContentNode* pNode = pStt->GetNode().GetContentNode();
1473 if( pNode && pNode->IsTextNode() )
1475 // tdf#127606 at editing, remove different formatting of DOCX-like numbering symbol
1476 if (pLayout && pNode->GetTextNode()->getIDocumentSettingAccess()->
1477 get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING ))
1479 SwContentNode* pEndNode = pEnd->GetNode().GetContentNode();
1480 SwContentNode* pCurrentNode = pEndNode;
1481 auto nStartIndex = pNode->GetIndex();
1482 auto nEndIndex = pEndNode->GetIndex();
1483 SwNodeIndex aIdx( pEnd->GetNode() );
1484 while ( pCurrentNode != nullptr && nStartIndex <= pCurrentNode->GetIndex() )
1486 if (pCurrentNode->GetSwAttrSet().HasItem(RES_PARATR_LIST_AUTOFMT) &&
1487 // remove character formatting only on wholly selected paragraphs
1488 (nStartIndex < pCurrentNode->GetIndex() || pStt->GetContentIndex() == 0) &&
1489 (pCurrentNode->GetIndex() < nEndIndex || pEnd->GetContentIndex() == pEndNode->Len()))
1491 pCurrentNode->ResetAttr(RES_PARATR_LIST_AUTOFMT);
1492 // reset also paragraph marker
1493 pCurrentNode->GetTextNode()->RstTextAttr(pCurrentNode->Len(), 1);
1495 pCurrentNode = SwNodes::GoPrevious( &aIdx );
1498 // #i27615#
1499 if (rRg.IsInFrontOfLabel())
1501 SwTextNode * pTextNd = pNode->GetTextNode();
1502 if (pLayout)
1504 pTextNd = sw::GetParaPropsNode(*pLayout, *pTextNd);
1506 SwNumRule * pNumRule = pTextNd->GetNumRule();
1508 if ( !pNumRule )
1510 OSL_FAIL( "<InsAttr(..)> - PaM in front of label, but text node has no numbering rule set. This is a serious defect." );
1511 DELETECHARSETS
1512 return false;
1515 int nLevel = pTextNd->GetActualListLevel();
1517 if (nLevel < 0)
1518 nLevel = 0;
1520 if (nLevel >= MAXLEVEL)
1521 nLevel = MAXLEVEL - 1;
1523 SwNumFormat aNumFormat = pNumRule->Get(o3tl::narrowing<sal_uInt16>(nLevel));
1524 SwCharFormat * pCharFormat =
1525 rDoc.FindCharFormatByName(aNumFormat.GetCharFormatName());
1527 if (pCharFormat)
1529 if (pHistory)
1530 pHistory->AddCharFormat(pCharFormat->GetAttrSet(), *pCharFormat);
1532 if ( pCharSet )
1533 pCharFormat->SetFormatAttr(*pCharSet);
1536 DELETECHARSETS
1537 return true;
1540 // Attributes without an end do not have a range
1541 if ( !bCharAttr && !bOtherAttr )
1543 SfxItemSetFixed<RES_TXTATR_NOEND_BEGIN, RES_TXTATR_NOEND_END-1>
1544 aTextSet( rDoc.GetAttrPool() );
1545 aTextSet.Put( rChgSet );
1546 if( aTextSet.Count() )
1548 SwRegHistory history( pNode, *pNode, pHistory );
1549 bRet = history.InsertItems(
1550 aTextSet, pStt->GetContentIndex(), pStt->GetContentIndex(), nFlags, /*ppNewTextAttr*/nullptr ) || bRet;
1552 if (bRet && (rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
1553 && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())))
1555 SwPaM aPam( pStt->GetNode(), pStt->GetContentIndex()-1,
1556 pStt->GetNode(), pStt->GetContentIndex() );
1558 if( pUndo )
1559 pUndo->SaveRedlineData( aPam, true );
1561 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1562 rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
1563 else
1564 rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
1569 // TextAttributes with an end never expand their range
1570 if ( !bCharAttr && !bOtherAttr )
1572 // CharFormat and URL attributes are treated separately!
1573 // TEST_TEMP ToDo: AutoFormat!
1574 SfxItemSetFixed<
1575 RES_TXTATR_REFMARK, RES_TXTATR_METAFIELD,
1576 RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY,
1577 RES_TXTATR_INPUTFIELD, RES_TXTATR_CONTENTCONTROL>
1578 aTextSet(rDoc.GetAttrPool());
1580 aTextSet.Put( rChgSet );
1581 if( aTextSet.Count() )
1583 const sal_Int32 nInsCnt = pStt->GetContentIndex();
1584 const sal_Int32 nEnd = pStt->GetNode() == pEnd->GetNode()
1585 ? pEnd->GetContentIndex()
1586 : pNode->Len();
1587 SwRegHistory history( pNode, *pNode, pHistory );
1588 bRet = history.InsertItems( aTextSet, nInsCnt, nEnd, nFlags, ppNewTextAttr )
1589 || bRet;
1591 if (bRet && (rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
1592 && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())))
1594 // Was text content inserted? (RefMark/TOXMarks without an end)
1595 bool bTextIns = nInsCnt != pStt->GetContentIndex();
1596 // Was content inserted or set over the selection?
1597 SwPaM aPam( pStt->GetNode(), bTextIns ? nInsCnt + 1 : nEnd,
1598 pStt->GetNode(), nInsCnt );
1599 if( pUndo )
1600 pUndo->SaveRedlineData( aPam, bTextIns );
1602 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1603 rDoc.getIDocumentRedlineAccess().AppendRedline(
1604 new SwRangeRedline(
1605 bTextIns ? RedlineType::Insert : RedlineType::Format, aPam ),
1606 true);
1607 else if( bTextIns )
1608 rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
1614 // We always have to set the auto flag for PageDescs that are set at the Node!
1615 if( pOtherSet && pOtherSet->Count() )
1617 SwTableNode* pTableNd;
1618 const SwFormatPageDesc* pDesc = pOtherSet->GetItemIfSet( RES_PAGEDESC, false );
1619 if( pDesc )
1621 if( pNode )
1623 // Set auto flag. Only in the template it's without auto!
1624 SwFormatPageDesc aNew( *pDesc );
1626 // Tables now also know line breaks
1627 if( !(nFlags & SetAttrMode::APICALL) &&
1628 nullptr != ( pTableNd = pNode->FindTableNode() ) )
1630 SwTableNode* pCurTableNd = pTableNd;
1631 while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) )
1632 pTableNd = pCurTableNd;
1634 // set the table format
1635 SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat();
1636 SwRegHistory aRegH( pFormat, *pTableNd, pHistory );
1637 pFormat->SetFormatAttr( aNew );
1638 bRet = true;
1640 else
1642 SwContentNode * pFirstNode(pNode);
1643 if (pLayout && pLayout->HasMergedParas())
1645 pFirstNode = sw::GetFirstAndLastNode(*pLayout, pStt->GetNode()).first;
1647 SwRegHistory aRegH( pFirstNode, *pFirstNode, pHistory );
1648 bRet = pFirstNode->SetAttr( aNew ) || bRet;
1652 // bOtherAttr = true means that pOtherSet == rChgSet. In this case
1653 // we know, that there is only one attribute in pOtherSet. We cannot
1654 // perform the following operations, instead we return:
1655 if ( bOtherAttr )
1656 return bRet;
1658 const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_PAGEDESC );
1659 if( !pOtherSet->Count() )
1661 DELETECHARSETS
1662 return bRet;
1666 // Tables now also know line breaks
1667 const SvxFormatBreakItem* pBreak;
1668 if( pNode && !(nFlags & SetAttrMode::APICALL) &&
1669 nullptr != (pTableNd = pNode->FindTableNode() ) &&
1670 (pBreak = pOtherSet->GetItemIfSet( RES_BREAK, false )) )
1672 SwTableNode* pCurTableNd = pTableNd;
1673 while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) )
1674 pTableNd = pCurTableNd;
1676 // set the table format
1677 SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat();
1678 SwRegHistory aRegH( pFormat, *pTableNd, pHistory );
1679 pFormat->SetFormatAttr( *pBreak );
1680 bRet = true;
1682 // bOtherAttr = true means that pOtherSet == rChgSet. In this case
1683 // we know, that there is only one attribute in pOtherSet. We cannot
1684 // perform the following operations, instead we return:
1685 if ( bOtherAttr )
1686 return bRet;
1688 const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_BREAK );
1689 if( !pOtherSet->Count() )
1691 DELETECHARSETS
1692 return bRet;
1697 // If we have a PoolNumRule, create it if needed
1698 sal_uInt16 nPoolId=0;
1699 const SwNumRuleItem* pRule = pOtherSet->GetItemIfSet( RES_PARATR_NUMRULE, false );
1700 if( pRule &&
1701 !rDoc.FindNumRulePtr( pRule->GetValue() ) &&
1702 USHRT_MAX != (nPoolId = SwStyleNameMapper::GetPoolIdFromUIName ( pRule->GetValue(),
1703 SwGetPoolIdFromName::NumRule )) )
1704 rDoc.getIDocumentStylePoolAccess().GetNumRuleFromPool( nPoolId );
1708 SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> firstSet(rDoc.GetAttrPool());
1709 if (pOtherSet && pOtherSet->Count())
1710 { // actually only RES_BREAK is possible here...
1711 firstSet.Put(*pOtherSet);
1713 SfxItemSetFixed
1714 <RES_PARATR_BEGIN, RES_PAGEDESC,
1715 RES_BREAK+1, RES_FRMATR_END,
1716 XATTR_FILL_FIRST, XATTR_FILL_LAST+1> propsSet(rDoc.GetAttrPool());
1717 if (pOtherSet && pOtherSet->Count())
1719 propsSet.Put(*pOtherSet);
1722 if( !rRg.HasMark() ) // no range
1724 if( !pNode )
1726 DELETECHARSETS
1727 return bRet;
1730 if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1732 SwTextNode* pTextNd = pNode->GetTextNode();
1733 sal_Int32 nMkPos, nPtPos = pStt->GetContentIndex();
1734 const OUString& rStr = pTextNd->GetText();
1736 // Special case: if the Cursor is located within a URL attribute, we take over it's area
1737 SwTextAttr const*const pURLAttr(
1738 pTextNd->GetTextAttrAt(pStt->GetContentIndex(), RES_TXTATR_INETFMT));
1739 if (pURLAttr && !pURLAttr->GetINetFormat().GetValue().isEmpty())
1741 nMkPos = pURLAttr->GetStart();
1742 nPtPos = *pURLAttr->End();
1744 else
1746 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
1747 Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
1748 pTextNd->GetText(), nPtPos,
1749 g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
1750 WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/,
1751 true);
1753 if( aBndry.startPos < nPtPos && nPtPos < aBndry.endPos )
1755 nMkPos = aBndry.startPos;
1756 nPtPos = aBndry.endPos;
1758 else
1759 nPtPos = nMkPos = pStt->GetContentIndex();
1762 // Remove the overriding attributes from the SwpHintsArray,
1763 // if the selection spans across the whole paragraph.
1764 // These attributes are inserted as FormatAttributes and
1765 // never override the TextAttributes!
1766 if( !(nFlags & SetAttrMode::DONTREPLACE ) &&
1767 pTextNd->HasHints() && !nMkPos && nPtPos == rStr.getLength())
1769 if( pHistory )
1771 // Save all attributes for the Undo.
1772 SwRegHistory aRHst( *pTextNd, pHistory );
1773 pTextNd->GetpSwpHints()->Register( &aRHst );
1774 pTextNd->RstTextAttr( 0, nPtPos, 0, pCharSet );
1775 if( pTextNd->GetpSwpHints() )
1776 pTextNd->GetpSwpHints()->DeRegister();
1778 else
1779 pTextNd->RstTextAttr( 0, nPtPos, 0, pCharSet );
1782 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1784 SwPaM aPam( *pNode, nMkPos, *pNode, nPtPos );
1786 if( pUndo )
1787 pUndo->SaveRedlineData( aPam, false );
1789 lcl_SetRedlines(rDoc, aPam);
1792 // the SwRegHistory inserts the attribute into the TextNode!
1793 SwRegHistory history( pNode, *pNode, pHistory );
1795 bRet = history.InsertItems( *pCharSet, nMkPos, nPtPos, nFlags, /*ppNewTextAttr*/nullptr )
1796 || bRet;
1799 if( pOtherSet && pOtherSet->Count() )
1801 // Need to check for unique item for DrawingLayer items of type NameOrIndex
1802 // and evtl. correct that item to ensure unique names for that type. This call may
1803 // modify/correct entries inside of the given SfxItemSet
1804 SfxItemSet aTempLocalCopy(*pOtherSet);
1806 rDoc.CheckForUniqueItemForLineFillNameOrIndex(aTempLocalCopy);
1807 bRet = lcl_ApplyOtherSet(*pNode, pHistory, aTempLocalCopy, firstSet, propsSet, pLayout) || bRet;
1810 DELETECHARSETS
1811 return bRet;
1814 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() && pCharSet && pCharSet->Count() )
1816 if( pUndo )
1817 pUndo->SaveRedlineData( rRg, false );
1819 lcl_SetRedlines(rDoc, rRg);
1822 /* now if range */
1823 sal_uLong nNodes = 0;
1825 SwNodeIndex aSt( rDoc.GetNodes() );
1826 SwNodeIndex aEnd( rDoc.GetNodes() );
1827 SwContentIndex aCntEnd( pEnd->GetContentNode(), pEnd->GetContentIndex() );
1829 if( pNode )
1831 const sal_Int32 nLen = pNode->Len();
1832 if( pStt->GetNode() != pEnd->GetNode() )
1833 aCntEnd.Assign( pNode, nLen );
1835 if( pStt->GetContentIndex() != 0 || aCntEnd.GetIndex() != nLen )
1837 // the SwRegHistory inserts the attribute into the TextNode!
1838 if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1840 SwRegHistory history( pNode, *pNode, pHistory );
1841 bRet = history.InsertItems(*pCharSet,
1842 pStt->GetContentIndex(), aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr)
1843 || bRet;
1846 if( pOtherSet && pOtherSet->Count() )
1848 bRet = lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout) || bRet;
1851 // Only selection in a Node.
1852 if( pStt->GetNode() == pEnd->GetNode() )
1854 DELETECHARSETS
1855 return bRet;
1857 ++nNodes;
1858 aSt.Assign( pStt->GetNode(), +1 );
1860 else
1861 aSt = pStt->GetNode();
1862 aCntEnd.Assign(pEnd->GetContentNode(), pEnd->GetContentIndex()); // aEnd was changed!
1864 else
1865 aSt.Assign( pStt->GetNode(), +1 );
1867 // aSt points to the first full Node now
1870 * The selection spans more than one Node.
1872 if( pStt->GetNode() < pEnd->GetNode() )
1874 pNode = pEnd->GetNode().GetContentNode();
1875 if(pNode)
1877 if( aCntEnd.GetIndex() != pNode->Len() )
1879 // the SwRegHistory inserts the attribute into the TextNode!
1880 if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1882 SwRegHistory history( pNode, *pNode, pHistory );
1883 (void)history.InsertItems(*pCharSet,
1884 0, aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr);
1887 if( pOtherSet && pOtherSet->Count() )
1889 lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout);
1892 ++nNodes;
1893 aEnd = pEnd->GetNode();
1895 else
1896 aEnd.Assign( pEnd->GetNode(), +1 );
1898 else
1899 aEnd = pEnd->GetNode();
1901 else
1902 aEnd.Assign( pEnd->GetNode(), +1 );
1904 // aEnd points BEHIND the last full node now
1906 /* Edit the fully selected Nodes. */
1907 // Reset all attributes from the set!
1908 if( pCharSet && pCharSet->Count() && !( SetAttrMode::DONTREPLACE & nFlags ) )
1910 ::sw::DocumentContentOperationsManager::ParaRstFormat aPara(
1911 pStt, pEnd, pHistory, pCharSet, pLayout);
1912 rDoc.GetNodes().ForEach( aSt, aEnd, ::sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara );
1915 bool bCreateSwpHints = pCharSet && (
1916 SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_CHARFMT, false ) ||
1917 SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_INETFMT, false ) );
1919 for (SwNodeIndex current = aSt; current < aEnd; ++current)
1921 SwTextNode *const pTNd = current.GetNode().GetTextNode();
1922 if (!pTNd)
1923 continue;
1925 if (pLayout && pLayout->HasMergedParas()
1926 && pTNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)
1927 { // not really sure what to do here, but applying to hidden
1928 continue; // nodes doesn't make sense...
1931 if( pHistory )
1933 SwRegHistory aRegH( pTNd, *pTNd, pHistory );
1935 if (pCharSet && pCharSet->Count())
1937 if (SwpHints *pSwpHints = bCreateSwpHints ? &pTNd->GetOrCreateSwpHints()
1938 : pTNd->GetpSwpHints())
1940 pSwpHints->Register( &aRegH );
1943 pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags);
1945 // re-fetch as it may be deleted by SetAttr
1946 if (SwpHints *pSwpHints = pTNd->GetpSwpHints())
1947 pSwpHints->DeRegister();
1950 else
1952 if (pCharSet && pCharSet->Count())
1953 pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags);
1955 ++nNodes;
1958 if (pOtherSet && pOtherSet->Count())
1960 for (; aSt < aEnd; ++aSt)
1962 pNode = aSt.GetNode().GetContentNode();
1963 if (!pNode)
1964 continue;
1966 lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout, &aSt);
1967 ++nNodes;
1971 DELETECHARSETS
1972 return (nNodes != 0) || bRet;
1976 namespace sw
1979 namespace mark
1981 bool IsFieldmarkOverlap(SwPaM const& rPaM)
1983 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
1984 sw::CalcBreaks(Breaks, rPaM);
1985 return !Breaks.empty();
1989 DocumentContentOperationsManager::DocumentContentOperationsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc )
1994 * Checks if rStart..rEnd mark a range that makes sense to copy.
1996 * IsMoveToFly means the copy is a move to create a fly
1997 * and so existing flys at the edge must not be copied.
1999 static bool IsEmptyRange(const SwPosition& rStart, const SwPosition& rEnd,
2000 SwCopyFlags const flags)
2002 if (rStart == rEnd)
2003 { // check if a fly anchored there would be copied - then copy...
2004 return !IsDestroyFrameAnchoredAtChar(rStart, rStart, rEnd,
2005 (flags & SwCopyFlags::IsMoveToFly)
2006 ? DelContentType::WriterfilterHack|DelContentType::AllMask
2007 : DelContentType::AllMask);
2009 else
2011 return rEnd < rStart;
2015 // Copy an area into this document or into another document
2016 bool DocumentContentOperationsManager::CopyRange(SwPaM& rPam, SwPosition& rPos,
2017 SwCopyFlags const flags,
2018 sal_uInt32 nMovedID) const
2020 const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End();
2022 SwDoc& rDoc = rPos.GetNode().GetDoc();
2023 bool bColumnSel = rDoc.IsClipBoard() && rDoc.IsColumnSelection();
2025 // Catch if there's no copy to do
2026 if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel))
2027 return false;
2029 // Prevent copying into Flys that are anchored in the source range
2030 if (&rDoc == &m_rDoc && (flags & SwCopyFlags::CheckPosInFly))
2032 // Correct the Start-/EndNode
2033 SwNodeOffset nStt = pStt->GetNodeIndex(),
2034 nEnd = pEnd->GetNodeIndex(),
2035 nDiff = nEnd - nStt +1;
2036 SwNode* pNd = m_rDoc.GetNodes()[ nStt ];
2037 if( pNd->IsContentNode() && pStt->GetContentIndex() )
2039 ++nStt;
2040 --nDiff;
2042 if( (pNd = m_rDoc.GetNodes()[ nEnd ])->IsContentNode() &&
2043 static_cast<SwContentNode*>(pNd)->Len() != pEnd->GetContentIndex() )
2045 --nEnd;
2046 --nDiff;
2048 if( nDiff &&
2049 lcl_ChkFlyFly( rDoc, nStt, nEnd, rPos.GetNodeIndex() ) )
2051 return false;
2055 std::optional<SwPaM> pRedlineRange;
2056 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
2057 (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) )
2058 pRedlineRange.emplace( rPos );
2060 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
2062 bool bRet = false;
2064 if( &rDoc != &m_rDoc )
2065 { // ordinary copy
2066 bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange ? &*pRedlineRange : nullptr);
2068 else if( ! ( *pStt <= rPos && rPos < *pEnd &&
2069 ( pStt->GetNode() != pEnd->GetNode() ||
2070 !pStt->GetNode().IsTextNode() )) )
2072 // Copy to a position outside of the area, or copy a single TextNode
2073 // Do an ordinary copy
2074 bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange ? &*pRedlineRange : nullptr);
2076 else
2078 // Copy the range in itself
2079 assert(!"mst: this is assumed to be dead code");
2082 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
2083 if( pRedlineRange )
2085 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2086 rDoc.getIDocumentRedlineAccess().AppendRedline(
2087 new SwRangeRedline(RedlineType::Insert, *pRedlineRange, nMovedID), true);
2088 else
2089 rDoc.getIDocumentRedlineAccess().SplitRedline( *pRedlineRange );
2090 pRedlineRange.reset();
2093 return bRet;
2096 static auto GetCorrPosition(SwPaM const& rPam) -> SwPosition
2098 // tdf#152710 target position must be on node that survives deletion
2099 // so that PaMCorrAbs can invalidate SwUnoCursors properly
2100 return rPam.GetPoint()->GetNode().IsContentNode()
2101 ? *rPam.GetPoint()
2102 : rPam.GetMark()->GetNode().IsContentNode()
2103 ? *rPam.GetMark()
2104 // this would be the result in SwNodes::RemoveNode()
2105 : SwPosition(rPam.End()->GetNode(), SwNodeOffset(+1));
2108 /// Delete a full Section of the NodeArray.
2109 /// The passed Node is located somewhere in the designated Section.
2110 void DocumentContentOperationsManager::DeleteSection( SwNode *pNode )
2112 assert(pNode && "Didn't pass a Node.");
2114 SwStartNode* pSttNd = pNode->IsStartNode() ? static_cast<SwStartNode*>(pNode)
2115 : pNode->StartOfSectionNode();
2116 SwNodeIndex aSttIdx( *pSttNd ), aEndIdx( *pNode->EndOfSectionNode() );
2118 // delete all Flys, Bookmarks, ...
2119 DelFlyInRange( aSttIdx.GetNode(), aEndIdx.GetNode() );
2120 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( *pSttNd, true, RedlineType::Any );
2121 DelBookmarks(aSttIdx.GetNode(), aEndIdx.GetNode());
2124 // move all Cursor/StackCursor/UnoCursor out of the to-be-deleted area
2125 SwPaM const range(aSttIdx, aEndIdx);
2126 SwPosition const pos(GetCorrPosition(range));
2127 ::PaMCorrAbs(range, pos);
2130 m_rDoc.GetNodes().DelNodes( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() + 1 );
2133 void DocumentContentOperationsManager::DeleteDummyChar(
2134 SwPosition const& rPos, sal_Unicode const cDummy)
2136 SwPaM aPam(rPos, rPos);
2137 aPam.GetPoint()->AdjustContent(+1);
2138 assert(aPam.GetText().getLength() == 1 && aPam.GetText()[0] == cDummy);
2139 (void) cDummy;
2141 DeleteRangeImpl(aPam, SwDeleteFlags::Default);
2143 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2144 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2146 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2150 void DocumentContentOperationsManager::DeleteRange( SwPaM & rPam )
2152 // Seek all redlines that are in that PaM to be deleted..
2153 SwRedlineTable::size_type nRedlStart = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos(
2154 rPam.Start()->GetNode(), RedlineType::Any);
2155 SwRedlineTable::size_type nRedlEnd = m_rDoc.getIDocumentRedlineAccess().GetRedlineEndPos(
2156 nRedlStart, rPam.End()->GetNode(), RedlineType::Any);
2158 lcl_DoWithBreaks(*this, rPam, SwDeleteFlags::Default, &DocumentContentOperationsManager::DeleteRangeImpl);
2160 // update all redlines was in the Pam that is
2161 m_rDoc.getIDocumentRedlineAccess().UpdateRedlineContentNode(nRedlStart, nRedlEnd);
2163 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2164 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2166 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2170 bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam )
2172 const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End();
2173 const SwNode* pNd = &rStt.GetNode();
2174 SwNodeOffset nSectDiff = pNd->StartOfSectionNode()->EndOfSectionIndex() -
2175 pNd->StartOfSectionIndex();
2176 SwNodeOffset nNodeDiff = rEnd.GetNodeIndex() - rStt.GetNodeIndex();
2178 if ( nSectDiff-SwNodeOffset(2) <= nNodeDiff || m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
2179 /* #i9185# Prevent getting the node after the end node (see below) */
2180 rEnd.GetNodeIndex() + 1 == m_rDoc.GetNodes().Count() )
2182 return false;
2186 SwPaM temp(rPam, nullptr);
2187 if (!temp.HasMark())
2189 temp.SetMark();
2191 if (SwTextNode *const pNode = temp.Start()->GetNode().GetTextNode())
2192 { // rPam may not have nContent set but IsFieldmarkOverlap requires it
2193 temp.Start()->AssignStartIndex(*pNode);
2195 if (SwTextNode *const pNode = temp.End()->GetNode().GetTextNode())
2197 temp.End()->AssignEndIndex(*pNode);
2199 if (sw::mark::IsFieldmarkOverlap(temp))
2200 { // a bit of a problem: we want to completely remove the nodes
2201 // but then how can the CH_TXT_ATR survive?
2202 return false;
2206 // Move hard page breaks to the following Node.
2207 bool bSavePageBreak = false, bSavePageDesc = false;
2209 /* #i9185# This would lead to a segmentation fault if not caught above. */
2210 SwNodeOffset nNextNd = rEnd.GetNodeIndex() + 1;
2211 SwTableNode *const pTableNd = m_rDoc.GetNodes()[ nNextNd ]->GetTableNode();
2213 if( pTableNd && pNd->IsContentNode() )
2215 SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat();
2218 const SfxPoolItem *pItem;
2219 const SfxItemSet* pSet = static_cast<const SwContentNode*>(pNd)->GetpSwAttrSet();
2220 if( pSet && SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC,
2221 false, &pItem ) )
2223 pTableFormat->SetFormatAttr( *pItem );
2224 bSavePageDesc = true;
2227 if( pSet && SfxItemState::SET == pSet->GetItemState( RES_BREAK,
2228 false, &pItem ) )
2230 pTableFormat->SetFormatAttr( *pItem );
2231 bSavePageBreak = true;
2236 bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
2237 if( bDoesUndo )
2239 if( !rPam.HasMark() )
2240 rPam.SetMark();
2241 else if( rPam.GetPoint() == &rStt )
2242 rPam.Exchange();
2243 rPam.GetPoint()->Adjust(SwNodeOffset(1));
2245 SwContentNode *pTmpNode = rPam.GetPoint()->GetNode().GetContentNode();
2246 bool bGoNext = (nullptr == pTmpNode);
2248 if (rPam.GetMark()->GetContentNode())
2249 rPam.GetMark()->SetContent( 0 );
2251 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
2253 SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
2255 SwPosition aTmpPos( *aDelPam.GetPoint() );
2256 if( bGoNext )
2258 SwNodes::GoNext(&aTmpPos);
2260 ::PaMCorrAbs( aDelPam, aTmpPos );
2263 std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aDelPam, SwDeleteFlags::Default, true));
2265 *rPam.GetPoint() = *aDelPam.GetPoint();
2266 pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc );
2267 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
2268 rPam.DeleteMark();
2270 else
2272 SwNodeRange aRg( rStt.GetNode(), rEnd.GetNode() );
2273 rPam.Normalize(false);
2275 // Try to move past the End
2276 if( !rPam.Move( fnMoveForward, GoInNode ) )
2278 // Fair enough, at the Beginning then
2279 rPam.Exchange();
2280 if( !rPam.Move( fnMoveBackward, GoInNode ))
2282 SAL_WARN("sw.core", "DelFullPara: no more Nodes");
2283 return false;
2287 // must delete all fieldmarks before CorrAbs(), or they'll remain
2288 // moved to wrong node without their CH_TXT_ATR_FIELD*
2289 // (note: deleteMarks() doesn't help here, in case of partially
2290 // selected fieldmarks; let's delete these as re-inserting their chars
2291 // elsewhere looks difficult)
2292 ::std::set<::sw::mark::IFieldmark*> fieldmarks;
2293 for (SwNodeIndex i = aRg.aStart; i <= aRg.aEnd; ++i)
2295 if (SwTextNode *const pTextNode = i.GetNode().GetTextNode())
2297 for (sal_Int32 j = 0; j < pTextNode->GetText().getLength(); ++j)
2299 switch (pTextNode->GetText()[j])
2301 case CH_TXT_ATR_FIELDSTART:
2302 case CH_TXT_ATR_FIELDEND:
2303 fieldmarks.insert(m_rDoc.getIDocumentMarkAccess()->getFieldmarkAt(SwPosition(*pTextNode, j)));
2304 break;
2305 case CH_TXT_ATR_FIELDSEP:
2306 fieldmarks.insert(m_rDoc.getIDocumentMarkAccess()->getInnerFieldmarkFor(SwPosition(*pTextNode, j)));
2307 break;
2312 for (auto const pFieldMark : fieldmarks)
2314 m_rDoc.getIDocumentMarkAccess()->deleteMark(pFieldMark);
2317 // move bookmarks, redlines etc.
2318 if (aRg.aStart == aRg.aEnd) // only first CorrAbs variant handles this
2320 m_rDoc.CorrAbs( aRg.aStart.GetNode(), *rPam.GetPoint(), 0, true );
2322 else
2324 SwDoc::CorrAbs( aRg.aStart, aRg.aEnd, *rPam.GetPoint(), true );
2327 // What's with Flys?
2329 // If there are FlyFrames left, delete these too
2330 for( size_t n = 0; n < m_rDoc.GetSpzFrameFormats()->size(); ++n )
2332 sw::SpzFrameFormat* pFly = (*m_rDoc.GetSpzFrameFormats())[n];
2333 const SwFormatAnchor* pAnchor = &pFly->GetAnchor();
2334 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
2335 if (pAnchorNode &&
2336 ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
2337 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
2338 // note: here use <= not < like in
2339 // IsDestroyFrameAnchoredAtChar() because of the increment
2340 // of rPam in the bDoesUndo path above!
2341 aRg.aStart <= *pAnchorNode && *pAnchorNode <= aRg.aEnd.GetNode() )
2343 m_rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFly );
2344 --n;
2349 rPam.DeleteMark();
2350 m_rDoc.GetNodes().Delete( aRg.aStart, nNodeDiff+1 );
2353 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2354 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2356 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2359 m_rDoc.getIDocumentState().SetModified();
2361 return true;
2364 bool DocumentContentOperationsManager::DeleteAndJoin(SwPaM & rPam, SwDeleteFlags const flags)
2366 if ( lcl_StrLenOverflow( rPam ) )
2367 return false;
2369 bool const ret = lcl_DoWithBreaks( *this, rPam, flags, (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
2370 ? &DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl
2371 : &DocumentContentOperationsManager::DeleteAndJoinImpl );
2373 return ret;
2376 // It seems that this is mostly used by SwDoc internals; the only
2377 // way to call this from the outside seems to be the special case in
2378 // SwDoc::CopyRange (but I have not managed to actually hit that case).
2379 bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos, SwMoveFlags eMvFlags )
2381 // nothing moved: return
2382 const SwPosition *pStt = rPaM.Start(), *pEnd = rPaM.End();
2383 if( !rPaM.HasMark() || *pStt >= *pEnd || (*pStt <= rPos && rPos < *pEnd))
2384 return false;
2386 assert(!sw::mark::IsFieldmarkOverlap(rPaM)); // probably an invalid redline was created?
2388 // Save the paragraph anchored Flys, so that they can be moved.
2389 SaveFlyArr aSaveFlyArr;
2390 SaveFlyInRange( rPaM, rPos, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) );
2392 // save redlines (if DOC_MOVEREDLINES is used)
2393 SaveRedlines_t aSaveRedl;
2394 if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
2396 lcl_SaveRedlines( rPaM, aSaveRedl );
2398 // #i17764# unfortunately, code below relies on undos being
2399 // in a particular order, and presence of bookmarks
2400 // will change this order. Hence, we delete bookmarks
2401 // here without undo.
2402 ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo());
2403 DelBookmarks(
2404 pStt->GetNode(),
2405 pEnd->GetNode(),
2406 nullptr,
2407 pStt->GetContentIndex(),
2408 pEnd->GetContentIndex());
2411 bool bUpdateFootnote = false;
2412 SwFootnoteIdxs aTmpFntIdx;
2414 std::unique_ptr<SwUndoMove> pUndoMove;
2415 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2417 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
2418 pUndoMove.reset(new SwUndoMove( rPaM, rPos ));
2419 pUndoMove->SetMoveRedlines( eMvFlags == SwMoveFlags::REDLINES );
2421 else
2423 bUpdateFootnote = lcl_SaveFootnote( pStt->GetNode(), pEnd->GetNode(), rPos.GetNode(),
2424 m_rDoc.GetFootnoteIdxs(), aTmpFntIdx,
2425 pStt->GetContentIndex(), pEnd->GetContentIndex() );
2428 bool bSplit = false;
2429 SwPaM aSavePam( rPos, rPos );
2431 // Move the SPoint to the beginning of the range
2432 if( rPaM.GetPoint() == pEnd )
2433 rPaM.Exchange();
2435 // If there is a TextNode before and after the Move, create a JoinNext in the EditShell.
2436 SwTextNode* pSrcNd = rPaM.GetPoint()->GetNode().GetTextNode();
2437 bool bCorrSavePam = pSrcNd && pStt->GetNode() != pEnd->GetNode();
2439 // If one or more TextNodes are moved, SwNodes::Move will do a SplitNode.
2440 // However, this does not update the cursor. So we create a TextNode to keep
2441 // updating the indices. After the Move the Node is optionally deleted.
2442 SwTextNode * pTNd = rPos.GetNode().GetTextNode();
2443 if( pTNd && rPaM.GetPoint()->GetNode() != rPaM.GetMark()->GetNode() &&
2444 ( rPos.GetContentIndex() || ( pTNd->Len() && bCorrSavePam )) )
2446 bSplit = true;
2447 const sal_Int32 nMkContent = rPaM.GetMark()->GetContentIndex();
2449 const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
2450 pContentStore->Save( m_rDoc, rPos.GetNodeIndex(), rPos.GetContentIndex(), true );
2452 SwTextNode * pOrigNode = pTNd;
2453 assert(*aSavePam.GetPoint() == *aSavePam.GetMark() &&
2454 *aSavePam.GetPoint() == rPos);
2455 assert(aSavePam.GetPoint()->GetContentNode() == pOrigNode);
2456 assert(aSavePam.GetPoint()->GetNode() == rPos.GetNode());
2457 assert(rPos.GetNodeIndex() == pOrigNode->GetIndex());
2459 std::function<void (SwTextNode *, sw::mark::RestoreMode, bool)> restoreFunc(
2460 [&](SwTextNode *const, sw::mark::RestoreMode const eMode, bool)
2462 if (!pContentStore->Empty())
2464 pContentStore->Restore(m_rDoc, pOrigNode->GetIndex()-SwNodeOffset(1), 0, true, false, eMode);
2467 pTNd->SplitContentNode(rPos, &restoreFunc);
2469 //A new node was inserted before the orig pTNd and the content up to
2470 //rPos moved into it. The old node is returned with the remainder
2471 //of the content in it.
2473 //aSavePam was created with rPos, it continues to point to the
2474 //old node, but with the *original* content index into the node.
2475 //Seeing as all the orignode content before that index has
2476 //been removed, the new index into the original node should now be set
2477 //to 0 and the content index of rPos should also be adapted to the
2478 //truncated node
2479 assert(*aSavePam.GetPoint() == *aSavePam.GetMark() &&
2480 *aSavePam.GetPoint() == rPos);
2481 assert(aSavePam.GetPoint()->GetContentNode() == pOrigNode);
2482 assert(aSavePam.GetPoint()->GetNode() == rPos.GetNode());
2483 assert(rPos.GetNodeIndex() == pOrigNode->GetIndex());
2484 aSavePam.GetPoint()->SetContent(0);
2485 rPos = *aSavePam.GetMark() = *aSavePam.GetPoint();
2487 // correct the PaM!
2488 if( rPos.GetNode() == rPaM.GetMark()->GetNode() )
2490 rPaM.GetMark()->Assign( rPos.GetNodeIndex() - SwNodeOffset(1) );
2491 rPaM.GetMark()->SetContent( nMkContent );
2495 // Put back the Pam by one "content"; so that it's always outside of
2496 // the manipulated range.
2497 // tdf#99692 don't Move() back if that would end up in another node
2498 // because moving backward is not necessarily the inverse of forward then.
2499 // (but do Move() back if we have split the node)
2500 const bool bNullContent = !bSplit && aSavePam.GetPoint()->GetContentIndex() == 0;
2501 if( bNullContent )
2503 aSavePam.GetPoint()->Adjust(SwNodeOffset(-1));
2505 else
2507 bool const success(aSavePam.Move(fnMoveBackward, GoInContent));
2508 assert(success);
2509 (void) success;
2512 // Copy all Bookmarks that are within the Move range into an array,
2513 // that saves the position as an offset.
2514 std::vector< ::sw::mark::SaveBookmark> aSaveBkmks;
2515 DelBookmarks(
2516 pStt->GetNode(),
2517 pEnd->GetNode(),
2518 &aSaveBkmks,
2519 pStt->GetContentIndex(),
2520 pEnd->GetContentIndex());
2522 // If there is no range anymore due to the above deletions (e.g. the
2523 // footnotes got deleted), it's still a valid Move!
2524 if( *rPaM.GetPoint() != *rPaM.GetMark() )
2526 // now do the actual move
2527 m_rDoc.GetNodes().MoveRange( rPaM, rPos, m_rDoc.GetNodes() );
2529 // after a MoveRange() the Mark is deleted
2530 if ( rPaM.HasMark() ) // => no Move occurred!
2532 return false;
2535 else
2536 rPaM.DeleteMark();
2538 OSL_ENSURE( *aSavePam.GetMark() == rPos ||
2539 ( aSavePam.GetMark()->GetNode().GetContentNode() == nullptr ),
2540 "PaM was not moved. Aren't there ContentNodes at the beginning/end?" );
2541 *aSavePam.GetMark() = rPos;
2543 rPaM.SetMark(); // create a Sel. around the new range
2544 pTNd = aSavePam.GetPointNode().GetTextNode();
2545 assert(!m_rDoc.GetIDocumentUndoRedo().DoesUndo());
2546 bool bRemove = true;
2547 // Do two Nodes have to be joined at the SavePam?
2548 if (bSplit && pTNd)
2550 if (pTNd->CanJoinNext())
2552 // Always join next, because <pTNd> has to stay as it is.
2553 // A join previous from its next would more or less delete <pTNd>
2554 pTNd->JoinNext();
2555 bRemove = false;
2558 if (bNullContent)
2560 aSavePam.GetPoint()->Adjust(SwNodeOffset(1));
2562 else if (bRemove) // No move forward after joining with next paragraph
2564 aSavePam.Move( fnMoveForward, GoInContent );
2567 // Insert the Bookmarks back into the Document.
2568 *rPaM.GetMark() = *aSavePam.Start();
2569 for(auto& rBkmk : aSaveBkmks)
2570 rBkmk.SetInDoc(
2571 &m_rDoc,
2572 rPaM.GetMark()->GetNode(),
2573 rPaM.GetMark()->GetContentIndex());
2574 *rPaM.GetPoint() = *aSavePam.End();
2576 // Move the Flys to the new position.
2577 // note: rPos is at the end here; can't really tell flys that used to be
2578 // at the start of rPam from flys that used to be at the end of rPam
2579 // unfortunately, so some of them are going to end up with wrong anchor...
2580 RestFlyInRange( aSaveFlyArr, *rPaM.Start(), &rPos.GetNode() );
2582 // restore redlines (if DOC_MOVEREDLINES is used)
2583 if( !aSaveRedl.empty() )
2585 lcl_RestoreRedlines( m_rDoc, *aSavePam.Start(), aSaveRedl );
2588 if( bUpdateFootnote )
2590 if( !aTmpFntIdx.empty() )
2592 m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx );
2593 aTmpFntIdx.clear();
2596 m_rDoc.GetFootnoteIdxs().UpdateAllFootnote();
2599 m_rDoc.getIDocumentState().SetModified();
2600 return true;
2603 bool DocumentContentOperationsManager::MoveNodeRange( SwNodeRange& rRange, SwNode& rDestNd,
2604 SwMoveFlags eMvFlags )
2606 // Moves all Nodes to the new position.
2607 // Bookmarks are moved too (currently without Undo support).
2609 // If footnotes are being moved to the special section, remove them now.
2611 // Or else delete the Frames for all footnotes that are being moved
2612 // and have it rebuild after the Move (footnotes can change pages).
2613 // Additionally we have to correct the FootnoteIdx array's sorting.
2614 bool bUpdateFootnote = false;
2615 SwFootnoteIdxs aTmpFntIdx;
2617 std::unique_ptr<SwUndoMove> pUndo;
2618 if ((SwMoveFlags::CREATEUNDOOBJ & eMvFlags ) && m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2620 pUndo.reset(new SwUndoMove( m_rDoc, rRange, rDestNd ));
2622 else
2624 bUpdateFootnote = lcl_SaveFootnote( rRange.aStart.GetNode(), rRange.aEnd.GetNode(), rDestNd,
2625 m_rDoc.GetFootnoteIdxs(), aTmpFntIdx );
2628 SaveRedlines_t aSaveRedl;
2629 std::vector<SwRangeRedline*> aSavRedlInsPosArr;
2630 if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
2632 lcl_SaveRedlines( rRange, aSaveRedl );
2634 // Find all RedLines that end at the InsPos.
2635 // These have to be moved back to the "old" position after the Move.
2636 SwRedlineTable::size_type nRedlPos = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos( rDestNd, RedlineType::Any );
2637 if( SwRedlineTable::npos != nRedlPos )
2639 const SwPosition *pRStt, *pREnd;
2640 do {
2641 SwRangeRedline* pTmp = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
2642 pRStt = pTmp->Start();
2643 pREnd = pTmp->End();
2644 if( pREnd->GetNode() == rDestNd && pRStt->GetNode() < rDestNd )
2646 aSavRedlInsPosArr.push_back( pTmp );
2648 } while( pRStt->GetNode() < rDestNd && ++nRedlPos < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size());
2652 // Copy all Bookmarks that are within the Move range into an array
2653 // that stores all references to positions as an offset.
2654 // The final mapping happens after the Move.
2655 std::vector< ::sw::mark::SaveBookmark> aSaveBkmks;
2656 DelBookmarks(rRange.aStart.GetNode(), rRange.aEnd.GetNode(), &aSaveBkmks);
2658 // Save the paragraph-bound Flys, so that they can be moved.
2659 SaveFlyArr aSaveFlyArr;
2660 if( !m_rDoc.GetSpzFrameFormats()->empty() )
2661 SaveFlyInRange( rRange, aSaveFlyArr );
2663 // Set it to before the Position, so that it cannot be moved further.
2664 SwNodeIndex aIdx( rDestNd, -1 );
2666 std::optional<SwNodeIndex> oSaveInsPos;
2667 if( pUndo )
2668 oSaveInsPos.emplace(rRange.aStart, -1 );
2670 // move the Nodes
2671 bool bNoDelFrames = bool(SwMoveFlags::NO_DELFRMS & eMvFlags);
2672 if( m_rDoc.GetNodes().MoveNodes( rRange, m_rDoc.GetNodes(), rDestNd, !bNoDelFrames ) )
2674 ++aIdx; // again back to old position
2675 if( oSaveInsPos )
2676 ++(*oSaveInsPos);
2678 else
2680 aIdx = rRange.aStart;
2681 pUndo.reset();
2684 // move the Flys to the new position
2685 if( !aSaveFlyArr.empty() )
2687 SwPosition const tmp(aIdx);
2688 RestFlyInRange(aSaveFlyArr, tmp, nullptr);
2691 // Add the Bookmarks back to the Document
2692 for(auto& rBkmk : aSaveBkmks)
2693 rBkmk.SetInDoc(&m_rDoc, aIdx.GetNode());
2695 if( !aSavRedlInsPosArr.empty() )
2697 for(SwRangeRedline* pTmp : aSavRedlInsPosArr)
2699 if( m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().Contains( pTmp ) )
2701 SwPosition* pEnd = pTmp->End();
2702 pEnd->Assign(aIdx);
2707 if( !aSaveRedl.empty() )
2708 lcl_RestoreRedlines( m_rDoc, aIdx.GetIndex(), aSaveRedl );
2710 if( pUndo )
2712 pUndo->SetDestRange( aIdx.GetNode(), rDestNd, *oSaveInsPos );
2713 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
2716 oSaveInsPos.reset();
2718 if( bUpdateFootnote )
2720 if( !aTmpFntIdx.empty() )
2722 m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx );
2723 aTmpFntIdx.clear();
2726 m_rDoc.GetFootnoteIdxs().UpdateAllFootnote();
2729 m_rDoc.getIDocumentState().SetModified();
2730 return true;
2733 void DocumentContentOperationsManager::MoveAndJoin( SwPaM& rPaM, SwPosition& rPos )
2735 SwNodeIndex aIdx( rPaM.Start()->GetNode() );
2736 bool bJoinText = aIdx.GetNode().IsTextNode();
2737 bool bOneNode = rPaM.GetPoint()->GetNode() == rPaM.GetMark()->GetNode();
2738 --aIdx; // in front of the move area!
2740 bool bRet = MoveRange( rPaM, rPos, SwMoveFlags::DEFAULT );
2741 if( !bRet || bOneNode )
2742 return;
2744 if( bJoinText )
2745 ++aIdx;
2746 SwTextNode * pTextNd = aIdx.GetNode().GetTextNode();
2747 SwNodeIndex aNxtIdx( aIdx );
2748 if( pTextNd && pTextNd->CanJoinNext( &aNxtIdx ) )
2750 { // Block so SwContentIndex into node is deleted before Join
2751 m_rDoc.CorrRel( aNxtIdx.GetNode(),
2752 SwPosition( *pTextNd, pTextNd->GetText().getLength() ),
2753 0, true );
2755 pTextNd->JoinNext();
2759 // Overwrite only uses the point of the PaM, the mark is ignored; characters
2760 // are replaced from point until the end of the node; at the end of the node,
2761 // characters are inserted.
2762 bool DocumentContentOperationsManager::Overwrite( const SwPaM &rRg, const OUString &rStr )
2764 assert(rStr.getLength());
2765 SwPosition& rPt = *const_cast<SwPosition*>(rRg.GetPoint());
2766 if( m_rDoc.GetAutoCorrExceptWord() ) // Add to AutoCorrect
2768 if( 1 == rStr.getLength() )
2769 m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPt, rStr[ 0 ] );
2770 m_rDoc.DeleteAutoCorrExceptWord();
2773 SwTextNode *pNode = rPt.GetNode().GetTextNode();
2774 if (!pNode || rStr.getLength() > pNode->GetSpaceLeft()) // worst case: no erase
2776 return false;
2779 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2781 m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called
2784 const size_t nOldAttrCnt = pNode->GetpSwpHints()
2785 ? pNode->GetpSwpHints()->Count() : 0;
2786 SwDataChanged aTmp( rRg );
2787 sal_Int32 const nActualStart(rPt.GetContentIndex());
2788 sal_Int32 nStart = 0;
2790 bool bOldExpFlg = pNode->IsIgnoreDontExpand();
2791 pNode->SetIgnoreDontExpand( true );
2793 for( sal_Int32 nCnt = 0; nCnt < rStr.getLength(); ++nCnt )
2795 // start behind the characters (to fix the attributes!)
2796 nStart = rPt.GetContentIndex();
2797 if (nStart < pNode->GetText().getLength())
2799 lcl_SkipAttr( pNode, rPt, nStart );
2801 sal_Unicode c = rStr[ nCnt ];
2802 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2804 bool bMerged(false);
2805 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
2807 SwUndo *const pUndo = m_rDoc.GetUndoManager().GetLastUndo();
2808 SwUndoOverwrite *const pUndoOW(
2809 dynamic_cast<SwUndoOverwrite *>(pUndo) );
2810 if (pUndoOW)
2812 // if CanGrouping() returns true it's already merged
2813 bMerged = pUndoOW->CanGrouping(m_rDoc, rPt, c);
2816 if (!bMerged)
2818 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
2819 std::make_unique<SwUndoOverwrite>(m_rDoc, rPt, c) );
2822 else
2824 // start behind the characters (to fix the attributes!)
2825 if (nStart < pNode->GetText().getLength())
2826 rPt.AdjustContent(+1);
2827 pNode->InsertText( OUString(c), rPt, SwInsertFlags::EMPTYEXPAND );
2828 if( nStart+1 < rPt.GetContentIndex() )
2830 rPt.SetContent(nStart);
2831 pNode->EraseText( rPt, 1 );
2832 rPt.AdjustContent(+1);
2836 pNode->SetIgnoreDontExpand( bOldExpFlg );
2838 const size_t nNewAttrCnt = pNode->GetpSwpHints()
2839 ? pNode->GetpSwpHints()->Count() : 0;
2840 if( nOldAttrCnt != nNewAttrCnt )
2842 const SwUpdateAttr aHint(0,0,0);
2843 pNode->TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
2846 if (!m_rDoc.GetIDocumentUndoRedo().DoesUndo() &&
2847 !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2849 SwPaM aPam(rPt.GetNode(), nActualStart, rPt.GetNode(), rPt.GetContentIndex());
2850 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, true, RedlineType::Any );
2852 else if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2854 // FIXME: this redline is WRONG: there is no DELETE, and the skipped
2855 // characters are also included in aPam
2856 SwPaM aPam(rPt.GetNode(), nActualStart, rPt.GetNode(), rPt.GetContentIndex());
2857 m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
2860 m_rDoc.getIDocumentState().SetModified();
2861 return true;
2864 bool DocumentContentOperationsManager::InsertString( const SwPaM &rRg, const OUString &rStr,
2865 const SwInsertFlags nInsertMode )
2867 // tdf#119019 accept tracked paragraph formatting to do not hide new insertions
2868 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2870 RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
2871 m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting( rRg );
2872 if (eOld != m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags())
2873 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
2876 // fetching DoesUndo is surprisingly expensive
2877 bool bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
2878 if (bDoesUndo)
2879 m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called!
2881 const SwPosition& rPos = *rRg.GetPoint();
2883 if( m_rDoc.GetAutoCorrExceptWord() ) // add to auto correction
2885 if( 1 == rStr.getLength() && m_rDoc.GetAutoCorrExceptWord()->IsDeleted() )
2887 m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPos, rStr[ 0 ] );
2889 m_rDoc.DeleteAutoCorrExceptWord();
2892 SwTextNode *const pNode = rPos.GetNode().GetTextNode();
2893 if(!pNode)
2894 return false;
2896 SwDataChanged aTmp( rRg );
2898 if (!bDoesUndo || !m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
2900 OUString const ins(pNode->InsertText(rStr, rPos, nInsertMode));
2901 if (bDoesUndo)
2903 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
2904 std::make_unique<SwUndoInsert>(rPos.GetNode(),
2905 rPos.GetContentIndex(), ins.getLength(), nInsertMode));
2908 else
2909 { // if Undo and grouping is enabled, everything changes!
2910 SwUndoInsert * pUndo = nullptr;
2912 // don't group the start if hints at the start should be expanded
2913 if (!(nInsertMode & SwInsertFlags::FORCEHINTEXPAND))
2915 SwUndo *const pLastUndo = m_rDoc.GetUndoManager().GetLastUndo();
2916 SwUndoInsert *const pUndoInsert(
2917 dynamic_cast<SwUndoInsert *>(pLastUndo) );
2918 if (pUndoInsert && pUndoInsert->CanGrouping(rPos))
2920 pUndo = pUndoInsert;
2924 CharClass const& rCC = GetAppCharClass();
2925 sal_Int32 nInsPos = rPos.GetContentIndex();
2927 if (!pUndo)
2929 pUndo = new SwUndoInsert( rPos.GetNode(), nInsPos, 0, nInsertMode,
2930 !rCC.isLetterNumeric( rStr, 0 ) );
2931 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
2934 OUString const ins(pNode->InsertText(rStr, rPos, nInsertMode));
2936 for (sal_Int32 i = 0; i < ins.getLength(); ++i)
2938 nInsPos++;
2939 // if CanGrouping() returns true, everything has already been done
2940 if (!pUndo->CanGrouping(ins[i]))
2942 pUndo = new SwUndoInsert(rPos.GetNode(), nInsPos, 1, nInsertMode,
2943 !rCC.isLetterNumeric(ins, i));
2944 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
2949 // To-Do - add 'SwExtraRedlineTable' also ?
2950 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ))
2952 SwPaM aPam( rPos.GetNode(), aTmp.GetContent(),
2953 rPos.GetNode(), rPos.GetContentIndex());
2954 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2956 m_rDoc.getIDocumentRedlineAccess().AppendRedline(
2957 new SwRangeRedline( RedlineType::Insert, aPam ), true);
2959 else
2961 m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
2965 m_rDoc.getIDocumentState().SetModified();
2966 return true;
2969 void DocumentContentOperationsManager::SetIME(bool bIME)
2971 m_bIME = bIME;
2974 bool DocumentContentOperationsManager::GetIME() const
2976 return m_bIME;
2979 void DocumentContentOperationsManager::TransliterateText(
2980 const SwPaM& rPaM,
2981 utl::TransliterationWrapper& rTrans )
2983 std::unique_ptr<SwUndoTransliterate> pUndo;
2984 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2985 pUndo.reset(new SwUndoTransliterate( rPaM, rTrans ));
2987 auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition*
2988 SwNodeOffset nSttNd = pStt->GetNodeIndex(),
2989 nEndNd = pEnd->GetNodeIndex();
2990 sal_Int32 nSttCnt = pStt->GetContentIndex();
2991 sal_Int32 nEndCnt = pEnd->GetContentIndex();
2993 SwTextNode* pTNd = pStt->GetNode().GetTextNode();
2994 bool bNoSelection = (pStt == pEnd) && pTNd; // no selection?
2995 if ( bNoSelection )
2997 /* Check if cursor is inside of a word */
2998 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
2999 Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
3000 pTNd->GetText(), nSttCnt,
3001 g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ),
3002 WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/,
3003 true);
3005 if( aBndry.startPos < nSttCnt && nSttCnt < aBndry.endPos )
3007 /* Cursor is inside of a word */
3008 if (rTrans.getType() == TransliterationFlags::SENTENCE_CASE) {
3009 /* set current sentence as 'area of effect' */
3010 nSttCnt = g_pBreakIt->GetBreakIter()->beginOfSentence(
3011 pTNd->GetText(), nSttCnt,
3012 g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ) );
3013 nEndCnt = g_pBreakIt->GetBreakIter()->endOfSentence(
3014 pTNd->GetText(), nEndCnt,
3015 g_pBreakIt->GetLocale( pTNd->GetLang( nEndCnt ) ) );
3016 } else {
3017 /* Set current word as 'area of effect' */
3018 nSttCnt = aBndry.startPos;
3019 nEndCnt = aBndry.endPos;
3021 } else {
3022 /* Cursor is not inside of a word. Nothing should happen. */
3023 /* Except in the case of change tracking, when the cursor is at the end of the change */
3024 /* Recognize and reject the previous deleted and inserted words to allow to cycle */
3025 IDocumentRedlineAccess& rIDRA = m_rDoc.getIDocumentRedlineAccess();
3026 if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) &&
3027 pStt->GetContentIndex() > 0 )
3029 SwPosition aPos(*pStt->GetContentNode(), pStt->GetContentIndex() - 1);
3030 SwRedlineTable::size_type n = 0;
3032 const SwRangeRedline* pFnd =
3033 rIDRA.GetRedlineTable().FindAtPosition( aPos, n );
3034 if ( pFnd && RedlineType::Insert == pFnd->GetType() && n > 0 )
3036 const SwRangeRedline* pFnd2 = rIDRA.GetRedlineTable()[n-1];
3037 if ( RedlineType::Delete == pFnd2->GetType() &&
3038 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() &&
3039 *pFnd2->End() == *pFnd->Start() &&
3040 pFnd->GetAuthor() == pFnd2->GetAuthor() )
3042 SwPosition aPos2(*pFnd2->End());
3043 rIDRA.RejectRedline(*pFnd, true);
3044 rIDRA.RejectRedline(*pFnd2, true);
3045 // positionate the text cursor inside the changed word to allow to cycle
3046 if ( SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3047 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) )
3049 pWrtShell->GetCursor()->GetPoint()->
3050 Assign(*aPos2.GetContentNode(), aPos2.GetContentIndex() - 1);
3055 return;
3058 else
3060 bool bHasTrackedChange = false;
3061 IDocumentRedlineAccess& rIDRA = m_rDoc.getIDocumentRedlineAccess();
3062 if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) &&
3063 pEnd->GetContentIndex() > 0 )
3065 // search all own redlines within the selected area
3066 SwRedlineTable::size_type n = SwRedlineTable::npos;
3067 const SwRedlineTable& aRedlineTable = rIDRA.GetRedlineTable();
3068 for( SwRedlineTable::size_type m = 0; m < aRedlineTable.size(); ++m )
3070 const SwRangeRedline* pRedline = aRedlineTable[ m ];
3072 if ( *pRedline->Start() > *pEnd )
3073 break;
3075 if ( *pRedline->Start() >= *pStt )
3076 n = m;
3079 if ( n != SwRedlineTable::npos && n > 0 )
3081 SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3082 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell());
3084 sal_Int32 nRejectedCharacters = 0;
3085 SwRangeRedline* pFnd = rIDRA.GetRedlineTable()[n];
3086 SwRangeRedline* pFnd2 = rIDRA.GetRedlineTable()[--n];
3087 // loop on all redlines of a case changing, and reject them
3088 while ( ( ( RedlineType::Insert == pFnd->GetType() &&
3089 RedlineType::Delete == pFnd2->GetType() ) ||
3090 ( RedlineType::Delete == pFnd->GetType() &&
3091 RedlineType::Insert == pFnd2->GetType() ) ) &&
3092 pWrtShell &&
3093 // use time stamp to recognize the multiple selections in the text,
3094 // not only the changes from the same author within the (sometimes
3095 // incomplete) selection
3096 ( pFnd2->GetTimeStamp() == pFnd->GetTimeStamp() ||
3097 ( pStt->GetContentNode() < pFnd2->Start()->GetContentNode() ||
3098 ( pStt->GetContentNode() == pFnd2->Start()->GetContentNode() &&
3099 nSttCnt <= pFnd2->Start()->GetContentIndex() ) ) ) &&
3100 pFnd->GetAuthor() == pFnd2->GetAuthor() )
3102 bHasTrackedChange = true;
3104 if ( RedlineType::Insert == pFnd->GetType() )
3105 nRejectedCharacters += pFnd->GetText().getLength();
3107 rIDRA.RejectRedline(*pFnd, true);
3109 pFnd = pFnd2;
3110 if ( n == 0 )
3111 break;
3112 pFnd2 = rIDRA.GetRedlineTable()[--n];
3115 // remove the last item and restore the original selection within the node
3116 if ( bHasTrackedChange )
3118 if ( nSttNd == nEndNd )
3120 pWrtShell->GetCursor()->GetPoint()->
3121 Assign(*rPaM.Start()->GetContentNode(), nSttCnt);
3122 if ( nEndCnt >= nRejectedCharacters )
3123 pWrtShell->GetCursor()->GetMark()->
3124 Assign(*rPaM.End()->GetContentNode(), nEndCnt - nRejectedCharacters);
3126 rIDRA.RejectRedline(*pFnd, true);
3131 // TODO handle title case to lowercase
3132 if ( bHasTrackedChange )
3133 return;
3136 bool bUseRedlining = m_rDoc.getIDocumentRedlineAccess().IsRedlineOn();
3137 // as a workaround for a known performance problem, switch off redlining
3138 // to avoid freezing, if transliteration could result too many redlines
3139 if ( bUseRedlining )
3141 const sal_uLong nMaxRedlines = 500;
3142 const bool bIsTitleCase = rTrans.getType() == TransliterationFlags::TITLE_CASE;
3143 sal_uLong nAffectedNodes = 0;
3144 sal_uLong nAffectedChars = nEndCnt;
3145 SwNodeIndex aIdx( pStt->GetNode() );
3146 for( ; aIdx.GetIndex() <= nEndNd; ++aIdx )
3148 SwTextNode* pAffectedNode = aIdx.GetNode().GetTextNode();
3150 // don't count not text nodes or empty text nodes
3151 if( !pAffectedNode || pAffectedNode->GetText().isEmpty() )
3152 continue;
3154 nAffectedNodes++;
3156 // count characters of the node (the last - maybe partially
3157 // selected - node was counted at initialization of nAffectedChars)
3158 if( aIdx.GetIndex() < nEndNd )
3159 nAffectedChars += pAffectedNode->GetText().getLength();
3161 // transliteration creates n redlines for n nodes, except in the
3162 // case of title case, where it creates n redlines for n words
3163 if( nAffectedNodes > nMaxRedlines ||
3164 // estimate word count based on the character count, where
3165 // 6 = average English word length is ~5 letters + space
3166 ( bIsTitleCase && (nAffectedChars - nSttCnt)/6 > nMaxRedlines ) )
3168 bUseRedlining = false;
3169 break;
3174 if( nSttNd != nEndNd ) // is more than one text node involved?
3176 // iterate over all affected text nodes, the first and the last one
3177 // may be incomplete because the selection starts and/or ends there
3179 SwNodeIndex aIdx( pStt->GetNode() );
3180 if( nSttCnt )
3182 ++aIdx;
3183 if( pTNd )
3185 pTNd->TransliterateText(
3186 rTrans, nSttCnt, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining);
3190 for( ; aIdx.GetIndex() < nEndNd; ++aIdx )
3192 pTNd = aIdx.GetNode().GetTextNode();
3193 if (pTNd)
3195 pTNd->TransliterateText(
3196 rTrans, 0, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining);
3200 if( nEndCnt && nullptr != ( pTNd = pEnd->GetNode().GetTextNode() ))
3202 pTNd->TransliterateText( rTrans, 0, nEndCnt, pUndo.get(), bUseRedlining );
3205 else if( pTNd && nSttCnt < nEndCnt )
3207 pTNd->TransliterateText( rTrans, nSttCnt, nEndCnt, pUndo.get(), bUseRedlining );
3209 if( pUndo && pUndo->HasData() )
3211 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
3214 // restore selection after tracked changes
3215 if ( !bNoSelection && bUseRedlining && nSttNd == nEndNd )
3217 if ( SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3218 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) )
3220 *pWrtShell->GetCursor()->GetMark() = *pWrtShell->GetCursor()->End();
3221 pWrtShell->GetCursor()->GetPoint()->Assign(*pStt->GetContentNode(), nSttCnt);
3225 m_rDoc.getIDocumentState().SetModified();
3228 SwFlyFrameFormat* DocumentContentOperationsManager::InsertGraphic(
3229 const SwPaM &rRg,
3230 const OUString& rGrfName,
3231 const OUString& rFltName,
3232 const Graphic* pGraphic,
3233 const SfxItemSet* pFlyAttrSet,
3234 const SfxItemSet* pGrfAttrSet,
3235 SwFrameFormat* pFrameFormat )
3237 if( !pFrameFormat )
3238 pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC );
3239 SwGrfNode* pSwGrfNode = SwNodes::MakeGrfNode(
3240 m_rDoc.GetNodes().GetEndOfAutotext(),
3241 rGrfName, rFltName, pGraphic,
3242 m_rDoc.GetDfltGrfFormatColl() );
3243 SwFlyFrameFormat* pSwFlyFrameFormat = InsNoTextNode( *rRg.GetPoint(), pSwGrfNode,
3244 pFlyAttrSet, pGrfAttrSet, pFrameFormat );
3245 return pSwFlyFrameFormat;
3248 SwFlyFrameFormat* DocumentContentOperationsManager::InsertEmbObject(
3249 const SwPaM &rRg, const svt::EmbeddedObjectRef& xObj,
3250 SfxItemSet* pFlyAttrSet)
3252 sal_uInt16 nId = RES_POOLFRM_OLE;
3253 if (xObj.is())
3255 SvGlobalName aClassName( xObj->getClassID() );
3256 if (SotExchange::IsMath(aClassName))
3258 nId = RES_POOLFRM_FORMEL;
3262 SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( nId );
3264 return InsNoTextNode( *rRg.GetPoint(), m_rDoc.GetNodes().MakeOLENode(
3265 m_rDoc.GetNodes().GetEndOfAutotext(),
3266 xObj,
3267 m_rDoc.GetDfltGrfFormatColl() ),
3268 pFlyAttrSet, nullptr,
3269 pFrameFormat );
3272 SwFlyFrameFormat* DocumentContentOperationsManager::InsertOLE(const SwPaM &rRg, const OUString& rObjName,
3273 sal_Int64 nAspect,
3274 const SfxItemSet* pFlyAttrSet,
3275 const SfxItemSet* pGrfAttrSet)
3277 SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_OLE );
3279 return InsNoTextNode( *rRg.GetPoint(),
3280 m_rDoc.GetNodes().MakeOLENode(
3281 m_rDoc.GetNodes().GetEndOfAutotext(),
3282 rObjName,
3283 nAspect,
3284 m_rDoc.GetDfltGrfFormatColl(),
3285 nullptr ),
3286 pFlyAttrSet, pGrfAttrSet,
3287 pFrameFormat );
3290 void DocumentContentOperationsManager::ReRead( SwPaM& rPam, const OUString& rGrfName,
3291 const OUString& rFltName, const Graphic* pGraphic )
3293 SwGrfNode *pGrfNd;
3294 if( !(( !rPam.HasMark()
3295 || rPam.GetPoint()->GetNodeIndex() == rPam.GetMark()->GetNodeIndex() )
3296 && nullptr != ( pGrfNd = rPam.GetPoint()->GetNode().GetGrfNode() )) )
3297 return;
3299 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3301 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoReRead>(rPam, *pGrfNd));
3304 // Because we don't know if we can mirror the graphic, the mirror attribute is always reset
3305 if( MirrorGraph::Dont != pGrfNd->GetSwAttrSet().
3306 GetMirrorGrf().GetValue() )
3307 pGrfNd->SetAttr( SwMirrorGrf() );
3309 pGrfNd->ReRead( rGrfName, rFltName, pGraphic );
3310 m_rDoc.getIDocumentState().SetModified();
3313 // Insert drawing object, which has to be already inserted in the DrawModel
3314 SwDrawFrameFormat* DocumentContentOperationsManager::InsertDrawObj(
3315 const SwPaM &rRg,
3316 SdrObject& rDrawObj,
3317 const SfxItemSet& rFlyAttrSet )
3319 SwDrawFrameFormat* pFormat = m_rDoc.MakeDrawFrameFormat( OUString(), m_rDoc.GetDfltFrameFormat() );
3321 const SwFormatAnchor* pAnchor = rFlyAttrSet.GetItemIfSet( RES_ANCHOR, false );
3322 pFormat->SetFormatAttr( rFlyAttrSet );
3324 // Didn't set the Anchor yet?
3325 // DrawObjecte must never end up in the Header/Footer!
3326 RndStdIds eAnchorId = pAnchor != nullptr ? pAnchor->GetAnchorId() : pFormat->GetAnchor().GetAnchorId();
3327 const bool bIsAtContent = (RndStdIds::FLY_AT_PAGE != eAnchorId);
3329 const SwPosition* pChkPos = nullptr;
3330 if ( pAnchor == nullptr )
3332 pChkPos = rRg.GetPoint();
3334 else if ( bIsAtContent )
3336 pChkPos =
3337 pAnchor->GetContentAnchor() ? pAnchor->GetContentAnchor() : rRg.GetPoint();
3340 // allow drawing objects in header/footer, but control objects aren't allowed in header/footer.
3341 if( pChkPos != nullptr
3342 && ::CheckControlLayer( &rDrawObj )
3343 && m_rDoc.IsInHeaderFooter( pChkPos->GetNode() ) )
3345 // apply at-page anchor format
3346 eAnchorId = RndStdIds::FLY_AT_PAGE;
3347 pFormat->SetFormatAttr( SwFormatAnchor( eAnchorId ) );
3349 else if( pAnchor == nullptr
3350 || ( bIsAtContent
3351 && pAnchor->GetAnchorNode() == nullptr ) )
3353 // apply anchor format
3354 SwFormatAnchor aAnch( pAnchor != nullptr ? *pAnchor : pFormat->GetAnchor() );
3355 eAnchorId = aAnch.GetAnchorId();
3356 if ( eAnchorId == RndStdIds::FLY_AT_FLY )
3358 const SwStartNode* pStartNode = rRg.GetPointNode().FindFlyStartNode();
3359 assert(pStartNode);
3360 SwPosition aPos(*pStartNode);
3361 aAnch.SetAnchor( &aPos );
3363 else
3365 aAnch.SetAnchor( rRg.GetPoint() );
3366 if ( eAnchorId == RndStdIds::FLY_AT_PAGE )
3368 eAnchorId = dynamic_cast<const SdrUnoObj*>( &rDrawObj) != nullptr ? RndStdIds::FLY_AS_CHAR : RndStdIds::FLY_AT_PARA;
3369 aAnch.SetType( eAnchorId );
3372 pFormat->SetFormatAttr( aAnch );
3375 // insert text attribute for as-character anchored drawing object
3376 if ( eAnchorId == RndStdIds::FLY_AS_CHAR )
3378 bool bAnchorAtPageAsFallback = true;
3379 const SwFormatAnchor& rDrawObjAnchorFormat = pFormat->GetAnchor();
3380 if ( rDrawObjAnchorFormat.GetAnchorNode() != nullptr )
3382 SwTextNode* pAnchorTextNode =
3383 rDrawObjAnchorFormat.GetAnchorNode()->GetTextNode();
3384 if ( pAnchorTextNode != nullptr )
3386 const sal_Int32 nStt = rDrawObjAnchorFormat.GetContentAnchor()->GetContentIndex();
3387 SwFormatFlyCnt aFormat( pFormat );
3388 pAnchorTextNode->InsertItem( aFormat, nStt, nStt );
3389 bAnchorAtPageAsFallback = false;
3393 if ( bAnchorAtPageAsFallback )
3395 OSL_ENSURE( false, "DocumentContentOperationsManager::InsertDrawObj(..) - missing content anchor for as-character anchored drawing object --> anchor at-page" );
3396 pFormat->SetFormatAttr( SwFormatAnchor( RndStdIds::FLY_AT_PAGE ) );
3400 SwDrawContact* pContact = new SwDrawContact( pFormat, &rDrawObj );
3402 // Create Frames if necessary
3403 if( m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() )
3405 // create layout representation
3406 pFormat->MakeFrames();
3407 // #i42319# - follow-up of #i35635#
3408 // move object to visible layer
3409 // #i79391#
3410 if ( pContact->GetAnchorFrame() )
3412 pContact->MoveObjToVisibleLayer( &rDrawObj );
3416 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3418 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsLayFormat>(pFormat, SwNodeOffset(0), 0) );
3421 m_rDoc.getIDocumentState().SetModified();
3422 return pFormat;
3425 bool DocumentContentOperationsManager::SplitNode( const SwPosition &rPos, bool bChkTableStart )
3427 SwContentNode *pNode = rPos.GetNode().GetContentNode();
3428 if(nullptr == pNode)
3429 return false;
3432 // BUG 26675: Send DataChanged before deleting, so that we notice which objects are in scope.
3433 // After that they can be before/after the position.
3434 SwDataChanged aTmp( m_rDoc, rPos );
3437 SwUndoSplitNode* pUndo = nullptr;
3438 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3440 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3441 // insert the Undo object (currently only for TextNode)
3442 if( pNode->IsTextNode() )
3444 pUndo = new SwUndoSplitNode( m_rDoc, rPos, bChkTableStart );
3445 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo));
3449 // Update the rsid of the old and the new node unless
3450 // the old node is split at the beginning or at the end
3451 SwTextNode *pTextNode = rPos.GetNode().GetTextNode();
3452 const sal_Int32 nPos = rPos.GetContentIndex();
3453 if( pTextNode && nPos && nPos != pTextNode->Len() )
3455 m_rDoc.UpdateParRsid( pTextNode );
3458 //JP 28.01.97: Special case for SplitNode at table start:
3459 // If it is at the beginning of a Doc/Fly/Footer/... or right at after a table
3460 // then insert a paragraph before it.
3461 if( bChkTableStart && !rPos.GetContentIndex() && pNode->IsTextNode() )
3463 SwNodeOffset nPrevPos = rPos.GetNodeIndex() - 1;
3464 SwTableNode* pTableNd;
3465 const SwNode* pNd = m_rDoc.GetNodes()[ nPrevPos ];
3466 if( pNd->IsStartNode() &&
3467 SwTableBoxStartNode == static_cast<const SwStartNode*>(pNd)->GetStartNodeType() &&
3468 nullptr != ( pTableNd = m_rDoc.GetNodes()[ --nPrevPos ]->GetTableNode() ) &&
3469 ((( pNd = m_rDoc.GetNodes()[ --nPrevPos ])->IsStartNode() &&
3470 SwTableBoxStartNode != static_cast<const SwStartNode*>(pNd)->GetStartNodeType() )
3471 || ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsTableNode() )
3472 || pNd->IsContentNode() ))
3474 if( pNd->IsContentNode() )
3476 //JP 30.04.99 Bug 65660:
3477 // There are no page breaks outside of the normal body area,
3478 // so this is not a valid condition to insert a paragraph.
3479 if( nPrevPos < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() )
3480 pNd = nullptr;
3481 else
3483 // Only if the table has page breaks!
3484 const SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat();
3485 if( SfxItemState::SET != pFrameFormat->GetItemState(RES_PAGEDESC, false) &&
3486 SfxItemState::SET != pFrameFormat->GetItemState( RES_BREAK, false ) )
3487 pNd = nullptr;
3491 if( pNd )
3493 SwTextNode* pTextNd = m_rDoc.GetNodes().MakeTextNode(
3494 *pTableNd,
3495 m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ));
3496 if( pTextNd )
3498 const_cast<SwPosition&>(rPos).Assign( pTableNd->GetIndex() - SwNodeOffset(1) );
3500 // only add page breaks/styles to the body area
3501 if( nPrevPos > m_rDoc.GetNodes().GetEndOfExtras().GetIndex() )
3503 SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat();
3504 const SfxPoolItem *pItem;
3505 if( SfxItemState::SET == pFrameFormat->GetItemState( RES_PAGEDESC,
3506 false, &pItem ) )
3508 pTextNd->SetAttr( *pItem );
3509 pFrameFormat->ResetFormatAttr( RES_PAGEDESC );
3511 if( SfxItemState::SET == pFrameFormat->GetItemState( RES_BREAK,
3512 false, &pItem ) )
3514 pTextNd->SetAttr( *pItem );
3515 pFrameFormat->ResetFormatAttr( RES_BREAK );
3519 if( pUndo )
3520 pUndo->SetTableFlag();
3521 m_rDoc.getIDocumentState().SetModified();
3522 return true;
3528 const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
3529 pContentStore->Save( m_rDoc, rPos.GetNodeIndex(), rPos.GetContentIndex(), true );
3530 assert(pNode->IsTextNode());
3531 std::function<void (SwTextNode *, sw::mark::RestoreMode, bool bAtStart)> restoreFunc(
3532 [&](SwTextNode *const, sw::mark::RestoreMode const eMode, bool const bAtStart)
3534 if (!pContentStore->Empty())
3535 { // move all bookmarks, TOXMarks, FlyAtCnt
3536 pContentStore->Restore(m_rDoc, rPos.GetNodeIndex()-SwNodeOffset(1), 0, true, bAtStart && (eMode & sw::mark::RestoreMode::Flys), eMode);
3538 if (eMode & sw::mark::RestoreMode::NonFlys)
3540 // To-Do - add 'SwExtraRedlineTable' also ?
3541 if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
3542 (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() &&
3543 !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()))
3545 SwPaM aPam( rPos );
3546 aPam.SetMark();
3547 aPam.Move( fnMoveBackward );
3548 if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
3550 m_rDoc.getIDocumentRedlineAccess().AppendRedline(
3551 new SwRangeRedline(RedlineType::Insert, aPam), true);
3553 else
3555 m_rDoc.getIDocumentRedlineAccess().SplitRedline(aPam);
3560 pNode->GetTextNode()->SplitContentNode(rPos, &restoreFunc);
3562 m_rDoc.getIDocumentState().SetModified();
3563 return true;
3566 bool DocumentContentOperationsManager::AppendTextNode( SwPosition& rPos )
3568 // create new node before EndOfContent
3569 SwTextNode * pCurNode = rPos.GetNode().GetTextNode();
3570 if( !pCurNode )
3572 // so then one can be created!
3573 SwNodeIndex aIdx( rPos.GetNode(), 1 );
3574 pCurNode = m_rDoc.GetNodes().MakeTextNode( aIdx.GetNode(),
3575 m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ));
3577 else
3578 pCurNode = pCurNode->AppendNode( rPos )->GetTextNode();
3580 rPos.Adjust(SwNodeOffset(1));
3582 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3584 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsert>( rPos.GetNode() ) );
3587 // To-Do - add 'SwExtraRedlineTable' also ?
3588 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ))
3590 SwPaM aPam( rPos );
3591 aPam.SetMark();
3592 aPam.Move( fnMoveBackward );
3593 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
3594 m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
3595 else
3596 m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
3599 m_rDoc.getIDocumentState().SetModified();
3600 return true;
3603 bool DocumentContentOperationsManager::ReplaceRange( SwPaM& rPam, const OUString& rStr,
3604 const bool bRegExReplace )
3606 // unfortunately replace works slightly differently from delete,
3607 // so we cannot use lcl_DoWithBreaks here...
3609 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
3611 SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() );
3612 aPam.Normalize(false);
3613 if (aPam.GetPoint()->GetNode() != aPam.GetMark()->GetNode())
3615 aPam.Move(fnMoveBackward);
3617 OSL_ENSURE((aPam.GetPoint()->GetNode() == aPam.GetMark()->GetNode()), "invalid pam?");
3619 sw::CalcBreaks(Breaks, aPam);
3621 while (!Breaks.empty() // skip over prefix of dummy chars
3622 && (aPam.GetMark()->GetNodeIndex() == Breaks.begin()->first)
3623 && (aPam.GetMark()->GetContentIndex() == Breaks.begin()->second))
3625 // skip!
3626 aPam.GetMark()->AdjustContent(+1); // always in bounds if Breaks valid
3627 Breaks.erase(Breaks.begin());
3629 *rPam.Start() = *aPam.GetMark(); // update start of original pam w/ prefix
3631 if (Breaks.empty())
3633 // park aPam somewhere so it does not point to node that is deleted
3634 aPam.DeleteMark();
3635 aPam.GetPoint()->Assign(m_rDoc.GetNodes().GetEndOfContent());
3636 return ReplaceRangeImpl(rPam, rStr, bRegExReplace); // original pam!
3639 // Deletion must be split into several parts if the text node
3640 // contains a text attribute with end and with dummy character
3641 // and the selection does not contain the text attribute completely,
3642 // but overlaps its start (left), where the dummy character is.
3644 bool bRet( true );
3645 // iterate from end to start, to avoid invalidating the offsets!
3646 auto iter( Breaks.rbegin() );
3647 SwNodeOffset nOffset(0);
3648 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
3649 OSL_ENSURE(aPam.GetPoint() == aPam.End(), "wrong!");
3650 SwPosition & rEnd( *aPam.End() );
3651 SwPosition & rStart( *aPam.Start() );
3653 // set end of temp pam to original end (undo Move backward above)
3654 rEnd = *rPam.End();
3655 // after first deletion, rEnd will point into the original text node again!
3657 while (iter != Breaks.rend())
3659 rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
3660 if (rStart < rEnd) // check if part is empty
3662 bRet &= (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
3663 ? DeleteAndJoinWithRedlineImpl(aPam, SwDeleteFlags::Default)
3664 : DeleteAndJoinImpl(aPam, SwDeleteFlags::Default);
3665 nOffset = iter->first - rStart.GetNodeIndex(); // deleted fly nodes...
3667 rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
3668 ++iter;
3671 rStart = *rPam.Start(); // set to original start
3672 assert(rStart < rEnd && "replace part empty!");
3673 if (rStart < rEnd) // check if part is empty
3675 bRet &= ReplaceRangeImpl(aPam, rStr, bRegExReplace);
3678 rPam = aPam; // update original pam (is this required?)
3680 return bRet;
3683 bool DocumentContentOperationsManager::InsertPoolItem(
3684 const SwPaM &rRg,
3685 const SfxPoolItem &rHt,
3686 const SetAttrMode nFlags,
3687 SwRootFrame const*const pLayout,
3688 SwTextAttr **ppNewTextAttr)
3690 SwDataChanged aTmp( rRg );
3691 std::unique_ptr<SwUndoAttr> pUndoAttr;
3692 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3694 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3695 pUndoAttr.reset(new SwUndoAttr( rRg, rHt, nFlags ));
3698 SfxItemSet aSet( m_rDoc.GetAttrPool(), rHt.Which(), rHt.Which() );
3699 aSet.Put( rHt );
3700 const bool bRet = lcl_InsAttr(m_rDoc, rRg, aSet, nFlags, pUndoAttr.get(), pLayout, ppNewTextAttr);
3702 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3704 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) );
3707 if( bRet )
3709 m_rDoc.getIDocumentState().SetModified();
3711 return bRet;
3714 void DocumentContentOperationsManager::InsertItemSet ( const SwPaM &rRg, const SfxItemSet &rSet,
3715 const SetAttrMode nFlags, SwRootFrame const*const pLayout)
3717 SwDataChanged aTmp( rRg );
3718 std::unique_ptr<SwUndoAttr> pUndoAttr;
3719 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3721 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3722 pUndoAttr.reset(new SwUndoAttr( rRg, rSet, nFlags ));
3725 bool bRet = lcl_InsAttr(m_rDoc, rRg, rSet, nFlags, pUndoAttr.get(), pLayout, /*ppNewTextAttr*/nullptr );
3727 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3729 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) );
3732 if( bRet )
3733 m_rDoc.getIDocumentState().SetModified();
3736 void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(const SwPosition & rPos )
3738 const SwTextNode* pTNd = rPos.GetNode().GetTextNode();
3739 if ( !pTNd )
3740 return;
3742 const OUString& rText = pTNd->GetText();
3743 sal_Int32 nIdx = 0;
3744 while (nIdx < rText.getLength())
3746 sal_Unicode const cCh = rText[nIdx];
3747 if (('\t' != cCh) && (' ' != cCh))
3749 break;
3751 ++nIdx;
3754 if ( nIdx > 0 )
3756 SwPaM aPam(rPos);
3757 aPam.GetPoint()->SetContent(0);
3758 aPam.SetMark();
3759 aPam.GetMark()->SetContent(nIdx);
3760 DeleteRange( aPam );
3764 void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(SwPaM& rPaM )
3766 for (SwPaM& rSel :rPaM.GetRingContainer())
3768 SwNodeOffset nStt = rSel.Start()->GetNodeIndex();
3769 SwNodeOffset nEnd = rSel.End()->GetNodeIndex();
3770 for (SwNodeOffset nPos = nStt; nPos<=nEnd; nPos++)
3771 RemoveLeadingWhiteSpace(SwPosition(rSel.GetBound().GetNodes(), nPos));
3775 // Copy method from SwDoc - "copy Flys in Flys"
3776 /// note: rRg/rInsPos *exclude* a partially selected start text node;
3777 /// pCopiedPaM *includes* a partially selected start text node
3778 void DocumentContentOperationsManager::CopyWithFlyInFly(
3779 const SwNodeRange& rRg,
3780 SwNode& rInsPos,
3781 const std::pair<const SwPaM&, const SwPosition&>* pCopiedPaM /*and real insert pos*/,
3782 const bool bMakeNewFrames,
3783 const bool bDelRedlines,
3784 const bool bCopyFlyAtFly,
3785 SwCopyFlags const flags) const
3787 assert(!pCopiedPaM || pCopiedPaM->first.End()->GetNode() == rRg.aEnd.GetNode());
3788 assert(!pCopiedPaM || pCopiedPaM->second.GetNode() <= rInsPos);
3790 SwDoc& rDest = rInsPos.GetDoc();
3791 SwNodeIndex aSavePos( rInsPos );
3793 SwPaM aCopiedPaM(rRg.aStart, rRg.aEnd);
3794 if (pCopiedPaM)
3795 aCopiedPaM = pCopiedPaM->first;
3797 if (rRg.aStart != rRg.aEnd)
3799 bool bEndIsEqualEndPos = rInsPos == rRg.aEnd.GetNode();
3800 --aSavePos;
3801 SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 );
3802 auto savedEndContentIndex = aCopiedPaM.End()->GetContentIndex();
3804 // insert behind the already copied start node
3805 m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, false, true );
3806 aRedlRest.Restore();
3808 if (bEndIsEqualEndPos)
3810 const_cast<SwNodeIndex&>(rRg.aEnd).Assign(aSavePos.GetNode(), +1);
3811 // pCopiedPaM->first now spans a range from the start of the original selection
3812 // to the end of newly added text, and the insertion point is in the middle of
3813 // that range. Adjust the local copy to cover the original copied PaM.
3814 aCopiedPaM.End()->Assign(rRg.aEnd, savedEndContentIndex);
3818 // Also copy all bookmarks
3819 // guess this must be done before the DelDummyNodes below as that
3820 // deletes nodes so would mess up the index arithmetic
3821 // sw_fieldmarkhide: also needs to be done before making frames
3822 if (m_rDoc.getIDocumentMarkAccess()->getAllMarksCount())
3824 SwPosition targetPos(aSavePos, SwNodeOffset(rRg.aStart != rRg.aEnd ? +1 : 0));
3825 if (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->GetNode())
3827 // there is 1 (partially selected, maybe) paragraph before
3828 assert(SwNodeIndex(rRg.aStart, -1) == pCopiedPaM->first.Start()->GetNode());
3829 // only use the passed in target SwPosition if the source PaM point
3830 // is on a different node; if it was the same node then the target
3831 // position was likely moved along by the copy operation and now
3832 // points to the end of the range!
3833 targetPos = pCopiedPaM->second;
3836 sw::CopyBookmarks(aCopiedPaM, targetPos, flags);
3839 if (rRg.aStart != rRg.aEnd)
3841 ++aSavePos;
3844 #if OSL_DEBUG_LEVEL > 0
3846 //JP 17.06.99: Bug 66973 - check count only if the selection is in
3847 // the same section or there's no section, because sections that are
3848 // not fully selected are not copied.
3849 const SwSectionNode* pSSectNd = rRg.aStart.GetNode().FindSectionNode();
3850 SwNodeIndex aTmpI( rRg.aEnd, -1 );
3851 const SwSectionNode* pESectNd = aTmpI.GetNode().FindSectionNode();
3852 if( pSSectNd == pESectNd &&
3853 !rRg.aStart.GetNode().IsSectionNode() &&
3854 !aTmpI.GetNode().IsEndNode() )
3856 // If the range starts with a SwStartNode, it isn't copied
3857 SwNodeOffset offset( (rRg.aStart.GetNode().GetNodeType() != SwNodeType::Start) ? 1 : 0 );
3858 OSL_ENSURE( rInsPos.GetIndex() - aSavePos.GetIndex() ==
3859 rRg.aEnd.GetIndex() - rRg.aStart.GetIndex() - 1 + offset,
3860 "An insufficient number of nodes were copied!" );
3863 #endif
3866 ::sw::UndoGuard const undoGuard(rDest.GetIDocumentUndoRedo());
3867 // this must happen before lcl_DeleteRedlines() because it counts nodes
3868 CopyFlyInFlyImpl(rRg, pCopiedPaM ? &aCopiedPaM : nullptr,
3869 // see comment below regarding use of pCopiedPaM->second
3870 (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->GetNode())
3871 ? pCopiedPaM->second.GetNode()
3872 : aSavePos.GetNode(),
3873 bCopyFlyAtFly,
3874 flags,
3875 false);
3878 SwNodeRange aCpyRange( aSavePos.GetNode(), rInsPos );
3880 if( bDelRedlines && ( RedlineFlags::DeleteRedlines & rDest.getIDocumentRedlineAccess().GetRedlineFlags() ))
3881 lcl_DeleteRedlines( rRg, aCpyRange );
3883 rDest.GetNodes().DelDummyNodes( aCpyRange );
3885 // tdf#159023 create layout frames after DelDummyNodes():
3886 // InsertCnt_() does early return on the first SwPlaceholderNode
3887 if (rRg.aStart != rRg.aEnd)
3889 --aSavePos; // restore temporarily...
3890 bool isRecreateEndNode(false);
3891 if (bMakeNewFrames) // tdf#130685 only after aRedlRest
3892 { // recreate from previous node (could be merged now)
3893 o3tl::sorted_vector<SwTextFrame*> frames;
3894 SwTextNode * pNode(aSavePos.GetNode().GetTextNode());
3895 SwTextNode *const pEndNode(rInsPos.GetTextNode());
3896 if (pEndNode)
3898 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode);
3899 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
3901 if (pFrame->getRootFrame()->HasMergedParas())
3903 frames.insert(pFrame);
3904 // tdf#135061 check if end node is merged to a preceding node
3905 if (pNode == nullptr && pFrame->GetMergedPara()
3906 && pFrame->GetMergedPara()->pFirstNode->GetIndex() < aSavePos.GetIndex())
3908 pNode = pFrame->GetMergedPara()->pFirstNode;
3913 if (pNode != nullptr)
3915 sw::RecreateStartTextFrames(*pNode);
3916 if (!frames.empty())
3917 { // tdf#132187 check if the end node needs new frames
3918 assert(pEndNode);
3919 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode);
3920 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
3922 if (pFrame->getRootFrame()->HasMergedParas())
3924 auto const it(frames.find(pFrame));
3925 if (it != frames.end())
3927 frames.erase(it);
3931 if (!frames.empty()) // existing frame was deleted
3932 { // all layouts because MakeFrames recreates all layouts
3933 pEndNode->DelFrames(nullptr);
3934 isRecreateEndNode = true;
3939 bool const isAtStartOfSection(aSavePos.GetNode().IsStartNode());
3940 ++aSavePos;
3941 if (bMakeNewFrames)
3943 // it's possible that CheckParaRedlineMerge() deleted frames
3944 // on rInsPos so have to include it, but it must not be included
3945 // if it was the first node in the document so that MakeFrames()
3946 // will find the existing (wasn't deleted) frame on it
3947 SwNodeIndex const end(rInsPos,
3948 SwNodeOffset((!isRecreateEndNode || isAtStartOfSection)
3949 ? 0 : +1));
3950 ::MakeFrames(&rDest, aSavePos.GetNode(), end.GetNode());
3955 // note: for the redline Show/Hide this must be in sync with
3956 // SwRangeRedline::CopyToSection()/DelCopyOfSection()/MoveFromSection()
3957 void DocumentContentOperationsManager::CopyFlyInFlyImpl(
3958 const SwNodeRange& rRg,
3959 SwPaM const*const pCopiedPaM,
3960 SwNode& rStartIdx,
3961 const bool bCopyFlyAtFly,
3962 SwCopyFlags const flags,
3963 bool const bMakeNewFrames) const
3965 assert(!pCopiedPaM || pCopiedPaM->End()->GetNode() == rRg.aEnd.GetNode());
3967 // First collect all Flys, sort them according to their ordering number,
3968 // and then only copy them. This maintains the ordering numbers (which are only
3969 // managed in the DrawModel).
3970 SwDoc& rDest = rStartIdx.GetDoc();
3971 std::set< ZSortFly > aSet;
3972 const size_t nArrLen = m_rDoc.GetSpzFrameFormats()->size();
3974 SwTextBoxHelper::SavedLink aOldTextBoxes;
3975 SwTextBoxHelper::saveLinks(*m_rDoc.GetSpzFrameFormats(), aOldTextBoxes);
3977 for ( size_t n = 0; n < nArrLen; ++n )
3979 SwFrameFormat* pFormat = (*m_rDoc.GetSpzFrameFormats())[n];
3980 SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
3981 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
3982 if ( !pAnchorNode )
3983 continue;
3984 bool bAdd = false;
3985 SwNodeOffset nSkipAfter = pAnchorNode->GetIndex();
3986 SwNodeOffset nStart = rRg.aStart.GetIndex();
3987 switch ( pAnchor->GetAnchorId() )
3989 case RndStdIds::FLY_AT_FLY:
3990 if(bCopyFlyAtFly)
3991 ++nSkipAfter;
3992 else if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove())
3993 ++nStart;
3994 break;
3995 case RndStdIds::FLY_AT_PARA:
3997 bAdd = IsSelectFrameAnchoredAtPara(*pAnchor->GetContentAnchor(),
3998 pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart),
3999 pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd),
4000 (flags & SwCopyFlags::IsMoveToFly)
4001 ? DelContentType::AllMask|DelContentType::WriterfilterHack
4002 : DelContentType::AllMask);
4004 break;
4005 case RndStdIds::FLY_AT_CHAR:
4007 bAdd = IsDestroyFrameAnchoredAtChar(*pAnchor->GetContentAnchor(),
4008 pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart),
4009 pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd),
4010 (flags & SwCopyFlags::IsMoveToFly)
4011 ? DelContentType::AllMask|DelContentType::WriterfilterHack
4012 : DelContentType::AllMask);
4014 break;
4015 default:
4016 continue;
4018 if (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId())
4020 if (nStart > nSkipAfter)
4021 continue;
4022 if (*pAnchorNode > rRg.aEnd.GetNode())
4023 continue;
4024 //frames at the last source node are not always copied:
4025 //- if the node is empty and is the last node of the document or a table cell
4026 // or a text frame then they have to be copied
4027 //- if the content index in this node is > 0 then paragraph and frame bound objects are copied
4028 //- to-character bound objects are copied if their index is <= nEndContentIndex
4029 if (*pAnchorNode < rRg.aEnd.GetNode())
4030 bAdd = true;
4031 if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move
4033 if (!bAdd)
4035 // technically old code checked nContent of AT_FLY which is pointless
4036 bAdd = pCopiedPaM && 0 < pCopiedPaM->End()->GetContentIndex();
4040 if( bAdd )
4042 aSet.insert( ZSortFly( pFormat, pAnchor, nArrLen + aSet.size() ));
4046 // Store all copied (and also the newly created) frames in another array.
4047 // They are stored as matching the originals, so that we will be later
4048 // able to build the chains accordingly.
4049 std::vector< SwFrameFormat* > aVecSwFrameFormat;
4050 std::set< ZSortFly >::const_iterator it=aSet.begin();
4052 while (it != aSet.end())
4054 // #i59964#
4055 // correct determination of new anchor position
4056 SwFormatAnchor aAnchor( *(*it).GetAnchor() );
4057 assert( aAnchor.GetContentAnchor() != nullptr );
4058 SwPosition newPos = *aAnchor.GetContentAnchor();
4059 // for at-paragraph and at-character anchored objects the new anchor
4060 // position can *not* be determined by the difference of the current
4061 // anchor position to the start of the copied range, because not
4062 // complete selected sections in the copied range aren't copied - see
4063 // method <SwNodes::CopyNodes(..)>.
4064 // Thus, the new anchor position in the destination document is found
4065 // by counting the text nodes.
4066 if ((aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) ||
4067 (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) )
4069 // First, determine number of anchor text node in the copied range.
4070 // Note: The anchor text node *have* to be inside the copied range.
4071 sal_uLong nAnchorTextNdNumInRange( 0 );
4072 bool bAnchorTextNdFound( false );
4073 // start at the first node for which flys are copied
4074 SwNodeIndex aIdx(pCopiedPaM ? pCopiedPaM->Start()->GetNode() : rRg.aStart.GetNode());
4075 while ( !bAnchorTextNdFound && aIdx <= rRg.aEnd )
4077 if ( aIdx.GetNode().IsTextNode() )
4079 ++nAnchorTextNdNumInRange;
4080 bAnchorTextNdFound = *aAnchor.GetAnchorNode() == aIdx.GetNode();
4083 ++aIdx;
4086 if ( !bAnchorTextNdFound )
4088 // This case can *not* happen, but to be robust take the first
4089 // text node in the destination document.
4090 OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - anchor text node in copied range not found" );
4091 nAnchorTextNdNumInRange = 1;
4093 // Second, search corresponding text node in destination document
4094 // by counting forward from start insert position <rStartIdx> the
4095 // determined number of text nodes.
4096 aIdx = rStartIdx;
4097 SwNodeIndex aAnchorNdIdx( rStartIdx );
4098 const SwNode& aEndOfContentNd =
4099 aIdx.GetNode().GetNodes().GetEndOfContent();
4100 while ( nAnchorTextNdNumInRange > 0 &&
4101 aIdx.GetNode() != aEndOfContentNd )
4103 if ( aIdx.GetNode().IsTextNode() )
4105 --nAnchorTextNdNumInRange;
4106 aAnchorNdIdx = aIdx;
4109 ++aIdx;
4111 if ( !aAnchorNdIdx.GetNode().IsTextNode() )
4113 // This case can *not* happen, but to be robust take the first
4114 // text node in the destination document.
4115 OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - found anchor node index isn't a text node" );
4116 aAnchorNdIdx = rStartIdx;
4117 while ( !aAnchorNdIdx.GetNode().IsTextNode() )
4119 ++aAnchorNdIdx;
4122 // apply found anchor text node as new anchor position
4123 newPos.Assign( aAnchorNdIdx );
4125 else
4127 SwNodeOffset nOffset = newPos.GetNodeIndex() - rRg.aStart.GetIndex();
4128 newPos.Assign( rStartIdx, nOffset );
4130 // Set the character bound Flys back at the original character
4131 if ((RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) &&
4132 newPos.GetNode().IsTextNode() )
4134 // only if pCopiedPaM: care about partially selected start node
4135 sal_Int32 const nContent = pCopiedPaM && pCopiedPaM->Start()->GetNode() == *aAnchor.GetAnchorNode()
4136 ? newPos.GetContentIndex() - pCopiedPaM->Start()->GetContentIndex()
4137 : newPos.GetContentIndex();
4138 newPos.SetContent(nContent);
4140 aAnchor.SetAnchor( &newPos );
4142 // Check recursion: if copying content inside the same frame, then don't copy the format.
4143 if( &rDest == &m_rDoc )
4145 const SwFormatContent& rContent = (*it).GetFormat()->GetContent();
4146 const SwStartNode* pSNd;
4147 if( rContent.GetContentIdx() &&
4148 nullptr != ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ) &&
4149 pSNd->GetIndex() < rStartIdx.GetIndex() &&
4150 rStartIdx.GetIndex() < pSNd->EndOfSectionIndex() )
4152 it = aSet.erase(it);
4153 continue;
4157 // Ignore TextBoxes, they are already handled in
4158 // sw::DocumentLayoutManager::CopyLayoutFormat().
4159 if (SwTextBoxHelper::isTextBox(it->GetFormat(), RES_FLYFRMFMT))
4161 it = aSet.erase(it);
4162 continue;
4165 // Copy the format and set the new anchor
4166 aVecSwFrameFormat.push_back( rDest.getIDocumentLayoutAccess().CopyLayoutFormat( *(*it).GetFormat(),
4167 aAnchor, false, bMakeNewFrames) );
4168 ++it;
4171 // Rebuild as much as possible of all chains that are available in the original,
4172 OSL_ENSURE( aSet.size() == aVecSwFrameFormat.size(), "Missing new Flys" );
4173 if ( aSet.size() != aVecSwFrameFormat.size() )
4174 return;
4176 size_t n = 0;
4177 for (const auto& rFlyN : aSet)
4179 const SwFrameFormat *pFormatN = rFlyN.GetFormat();
4180 const SwFormatChain &rChain = pFormatN->GetChain();
4181 size_t k = 0;
4182 for (const auto& rFlyK : aSet)
4184 const SwFrameFormat *pFormatK = rFlyK.GetFormat();
4185 if ( rChain.GetPrev() == pFormatK )
4187 ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]),
4188 static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]) );
4190 else if ( rChain.GetNext() == pFormatK )
4192 ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]),
4193 static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]) );
4195 ++k;
4197 ++n;
4200 // Re-create content property of draw formats, knowing how old shapes
4201 // were paired with old fly formats (aOldTextBoxes) and that aSet is
4202 // parallel with aVecSwFrameFormat.
4203 SwTextBoxHelper::restoreLinks(aSet, aVecSwFrameFormat, aOldTextBoxes);
4207 * Reset the text's hard formatting
4209 /** @params pArgs contains the document's ChrFormatTable
4210 * Is need for selections at the beginning/end and with no SSelection.
4212 bool DocumentContentOperationsManager::lcl_RstTextAttr( SwNode* pNd, void* pArgs )
4214 ParaRstFormat* pPara = static_cast<ParaRstFormat*>(pArgs);
4215 if (pPara->pLayout && pPara->pLayout->HasMergedParas()
4216 && pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)
4218 return true; // skip hidden, since new items aren't applied
4220 SwTextNode * pTextNode = pNd->GetTextNode();
4221 if( pTextNode && pTextNode->GetpSwpHints() )
4223 SwContentIndex aSt( pTextNode, 0 );
4224 sal_Int32 nEnd = pTextNode->Len();
4226 if( &pPara->pSttNd->GetNode() == pTextNode &&
4227 pPara->pSttNd->GetContentIndex() )
4228 aSt = pPara->pSttNd->GetContentIndex();
4230 if( &pPara->pEndNd->GetNode() == pNd )
4231 nEnd = pPara->pEndNd->GetContentIndex();
4233 if( pPara->pHistory )
4235 // Save all attributes for the Undo.
4236 SwRegHistory aRHst( *pTextNode, pPara->pHistory );
4237 pTextNode->GetpSwpHints()->Register( &aRHst );
4238 pTextNode->RstTextAttr( aSt.GetIndex(), nEnd - aSt.GetIndex(), pPara->nWhich,
4239 pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange );
4240 if( pTextNode->GetpSwpHints() )
4241 pTextNode->GetpSwpHints()->DeRegister();
4243 else
4244 pTextNode->RstTextAttr( aSt.GetIndex(), nEnd - aSt.GetIndex(), pPara->nWhich,
4245 pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange );
4247 return true;
4250 DocumentContentOperationsManager::~DocumentContentOperationsManager()
4253 //Private methods
4255 bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl(SwPaM & rPam, SwDeleteFlags const flags)
4257 assert(m_rDoc.getIDocumentRedlineAccess().IsRedlineOn());
4259 RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
4261 if (*rPam.GetPoint() == *rPam.GetMark())
4263 return false; // do not add empty redlines
4266 std::vector<std::unique_ptr<SwRangeRedline>> redlines;
4268 auto pRedline(std::make_unique<SwRangeRedline>(RedlineType::Delete, rPam));
4269 if (pRedline->HasValidRange())
4271 redlines.push_back(std::move(pRedline));
4273 else // sigh ... why is such a selection even possible...
4274 { // split it up so we get one SwUndoRedlineDelete per inserted RL
4275 redlines = GetAllValidRanges(std::move(pRedline));
4279 if (redlines.empty())
4281 return false;
4284 // tdf#54819 current redlining needs also modification of paragraph style and
4285 // attributes added to the same grouped Undo
4286 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4287 m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
4289 auto & rDMA(*m_rDoc.getIDocumentMarkAccess());
4290 std::vector<std::unique_ptr<SwUndo>> MarkUndos;
4291 for (auto iter = rDMA.getAnnotationMarksBegin();
4292 iter != rDMA.getAnnotationMarksEnd(); )
4294 // tdf#111524 remove annotation marks that have their field
4295 // characters deleted
4296 SwPosition const& rEndPos((**iter).GetMarkEnd());
4297 if (*rPam.Start() < rEndPos && rEndPos <= *rPam.End())
4299 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4301 MarkUndos.emplace_back(std::make_unique<SwUndoDeleteBookmark>(**iter));
4303 // iter is into annotation mark vector so must be dereferenced!
4304 rDMA.deleteMark(&**iter);
4305 // this invalidates iter, have to start over...
4306 iter = rDMA.getAnnotationMarksBegin();
4308 else
4309 { // marks are sorted by start
4310 if (*rPam.End() < (**iter).GetMarkStart())
4312 break;
4314 ++iter;
4318 // tdf#119019 accept tracked paragraph formatting to do not hide new deletions
4319 if (*rPam.GetPoint() != *rPam.GetMark())
4320 m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting(rPam);
4322 std::vector<std::unique_ptr<SwUndoRedlineDelete>> undos;
4323 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4325 // this should no longer happen in calls from the UI but maybe via API
4326 // (randomTest and testTdf54819 triggers it)
4327 SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask,
4328 "sw.core", "redlines will be moved in DeleteAndJoin");
4329 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags(
4330 RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete);
4332 for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
4334 assert(pRedline->HasValidRange());
4335 undos.emplace_back(std::make_unique<SwUndoRedlineDelete>(
4336 *pRedline, SwUndoId::DELETE, flags));
4338 const SwRewriter aRewriter = undos.front()->GetRewriter();
4339 // can only group a single undo action
4340 if (MarkUndos.empty() && undos.size() == 1
4341 && m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4343 SwUndo * const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() );
4344 SwUndoRedlineDelete *const pUndoRedlineDel(dynamic_cast<SwUndoRedlineDelete*>(pLastUndo));
4345 bool const bMerged = pUndoRedlineDel
4346 && pUndoRedlineDel->CanGrouping(*undos.front());
4347 if (!bMerged)
4349 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(undos.front()));
4351 undos.clear(); // prevent unmatched EndUndo
4353 else
4355 m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter);
4356 for (auto& it : MarkUndos)
4358 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it));
4360 for (auto & it : undos)
4362 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it));
4367 for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
4369 // note: 1. the pRedline can still be merged & deleted
4370 // 2. the impl. can even DeleteAndJoin the range => no plain PaM
4371 std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*pRedline->GetMark()));
4372 pCursor->SetMark();
4373 *pCursor->GetPoint() = *pRedline->GetPoint();
4374 m_rDoc.getIDocumentRedlineAccess().AppendRedline(pRedline.release(), true);
4375 // sw_redlinehide: 2 reasons why this is needed:
4376 // 1. it's the first redline in node => RedlineDelText was sent but ignored
4377 // 2. redline spans multiple nodes => must merge text frames
4378 sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor);
4380 m_rDoc.getIDocumentState().SetModified();
4382 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4384 if (!undos.empty())
4386 m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4388 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
4391 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4392 m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4394 return true;
4397 bool DocumentContentOperationsManager::DeleteAndJoinImpl(SwPaM & rPam, SwDeleteFlags const flags)
4399 bool bJoinText, bJoinPrev;
4400 ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev );
4402 bool const bSuccess( DeleteRangeImpl(rPam, flags) );
4403 if (!bSuccess)
4404 return false;
4406 if( bJoinText )
4408 ::sw_JoinText( rPam, bJoinPrev );
4411 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
4412 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()
4413 && !(flags & SwDeleteFlags::DontCompressRedlines))
4415 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
4418 return true;
4421 bool DocumentContentOperationsManager::DeleteRangeImpl(SwPaM & rPam, SwDeleteFlags const flags)
4423 // Move all cursors out of the deleted range, but first copy the
4424 // passed PaM, because it could be a cursor that would be moved!
4425 SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
4427 SwPosition const pos(GetCorrPosition(aDelPam));
4428 ::PaMCorrAbs(aDelPam, pos);
4431 bool const bSuccess( DeleteRangeImplImpl(aDelPam, flags) );
4432 if (bSuccess)
4433 { // now copy position from temp copy to given PaM
4434 *rPam.GetPoint() = *aDelPam.GetPoint();
4437 return bSuccess;
4440 bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam, SwDeleteFlags const flags)
4442 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
4444 if (!rPam.HasMark()
4445 || (*pStt == *pEnd && !IsFlySelectedByCursor(m_rDoc, *pStt, *pEnd)))
4447 return false;
4450 if( m_rDoc.GetAutoCorrExceptWord() )
4452 // if necessary the saved Word for the exception
4453 if( m_rDoc.GetAutoCorrExceptWord()->IsDeleted() || pStt->GetNode() != pEnd->GetNode() ||
4454 pStt->GetContentIndex() + 1 != pEnd->GetContentIndex() ||
4455 !m_rDoc.GetAutoCorrExceptWord()->CheckDelChar( *pStt ))
4456 { m_rDoc.DeleteAutoCorrExceptWord(); }
4460 // Delete all empty TextHints at the Mark's position
4461 SwTextNode* pTextNd = rPam.GetMark()->GetNode().GetTextNode();
4462 SwpHints* pHts;
4463 if( pTextNd && nullptr != ( pHts = pTextNd->GetpSwpHints()) && pHts->Count() )
4465 const sal_Int32 nMkCntPos = rPam.GetMark()->GetContentIndex();
4466 for( size_t n = pHts->Count(); n; )
4468 const SwTextAttr* pAttr = pHts->Get( --n );
4469 if( nMkCntPos > pAttr->GetStart() )
4470 break;
4472 const sal_Int32 *pEndIdx;
4473 if( nMkCntPos == pAttr->GetStart() &&
4474 nullptr != (pEndIdx = pAttr->End()) &&
4475 *pEndIdx == pAttr->GetStart() )
4476 pTextNd->DestroyAttr( pHts->Cut( n ) );
4482 // Send DataChanged before deletion, so that we still know
4483 // which objects are in the range.
4484 // Afterwards they could be before/after the Position.
4485 SwDataChanged aTmp( rPam );
4488 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4490 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
4491 bool bMerged(false);
4492 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4494 SwUndo *const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() );
4495 SwUndoDelete *const pUndoDelete(
4496 dynamic_cast<SwUndoDelete *>(pLastUndo) );
4497 if (pUndoDelete)
4499 bMerged = pUndoDelete->CanGrouping(m_rDoc, rPam);
4500 // if CanGrouping() returns true it's already merged
4503 if (!bMerged)
4505 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoDelete>(rPam, flags));
4508 m_rDoc.getIDocumentState().SetModified();
4510 return true;
4513 if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
4514 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any );
4516 // Delete and move all "Flys at the paragraph", which are within the Selection
4517 if (!(flags & SwDeleteFlags::ArtificialSelection))
4519 DelFlyInRange(rPam.GetMark()->GetNode(), rPam.GetPoint()->GetNode(),
4520 rPam.GetMark()->GetContentIndex(), rPam.GetPoint()->GetContentIndex());
4522 DelBookmarks(
4523 pStt->GetNode(),
4524 pEnd->GetNode(),
4525 nullptr,
4526 pStt->GetContentIndex(),
4527 pEnd->GetContentIndex(),
4528 bool(flags & SwDeleteFlags::ArtificialSelection));
4530 SwNodeIndex aSttIdx( pStt->GetNode() );
4531 SwContentNode * pCNd = aSttIdx.GetNode().GetContentNode();
4533 do { // middle checked loop!
4534 if( pCNd )
4536 SwTextNode * pStartTextNode( pCNd->GetTextNode() );
4537 if ( pStartTextNode )
4539 // now move the Content to the new Node
4540 bool bOneNd = pStt->GetNode() == pEnd->GetNode();
4541 const sal_Int32 nLen = ( bOneNd ? pEnd->GetContentIndex()
4542 : pCNd->Len() )
4543 - pStt->GetContentIndex();
4545 // Don't call again, if already empty
4546 if( nLen )
4548 pStartTextNode->EraseText( *pStt, nLen );
4550 if( !pStartTextNode->Len() )
4552 // METADATA: remove reference if empty (consider node deleted)
4553 pStartTextNode->RemoveMetadataReference();
4557 if( bOneNd ) // that's it
4558 break;
4560 ++aSttIdx;
4562 else
4564 // So that there are no indices left registered when deleted,
4565 // we remove a SwPaM from the Content here.
4566 pStt->nContent.Assign( nullptr, 0 );
4570 pCNd = pEnd->GetNode().GetContentNode();
4571 if( pCNd )
4573 SwTextNode * pEndTextNode( pCNd->GetTextNode() );
4574 if( pEndTextNode )
4576 // if already empty, don't call again
4577 if( pEnd->GetContentIndex() )
4579 SwContentIndex aIdx( pCNd, 0 );
4580 pEndTextNode->EraseText( aIdx, pEnd->GetContentIndex() );
4582 if( !pEndTextNode->Len() )
4584 // METADATA: remove reference if empty (consider node deleted)
4585 pEndTextNode->RemoveMetadataReference();
4589 else
4591 // So that there are no indices left registered when deleted,
4592 // we remove a SwPaM from the Content here.
4593 pEnd->nContent.Assign( nullptr, 0 );
4597 // if the end is not a content node, delete it as well
4598 SwNodeOffset nEnd = pEnd->GetNodeIndex();
4599 if( pCNd == nullptr )
4600 nEnd++;
4602 if( aSttIdx != nEnd )
4604 // tdf#134436 delete section nodes like SwUndoDelete::SwUndoDelete
4605 SwNode *pTmpNd;
4606 while (pEnd == rPam.GetPoint()
4607 && nEnd + SwNodeOffset(2) < m_rDoc.GetNodes().Count()
4608 && (pTmpNd = m_rDoc.GetNodes()[nEnd + 1])->IsEndNode()
4609 && pTmpNd->StartOfSectionNode()->IsSectionNode()
4610 && aSttIdx <= pTmpNd->StartOfSectionNode()->GetIndex())
4612 SwNodeRange range(*pTmpNd->StartOfSectionNode(), *pTmpNd);
4613 m_rDoc.GetNodes().SectionUp(&range);
4614 --nEnd; // account for deleted start node
4617 // delete the Nodes from the NodesArray
4618 m_rDoc.GetNodes().Delete( aSttIdx, nEnd - aSttIdx.GetIndex() );
4621 // If the Node that contained the Cursor has been deleted,
4622 // the Content has to be assigned to the current Content.
4623 if (pStt->GetNode().GetContentNode())
4624 pStt->SetContent( pStt->GetContentIndex() );
4626 // If we deleted across Node boundaries we have to correct the PaM,
4627 // because they are in different Nodes now.
4628 // Also, the Selection is revoked.
4629 *pEnd = *pStt;
4630 rPam.DeleteMark();
4632 } while( false );
4634 m_rDoc.getIDocumentState().SetModified();
4636 return true;
4639 // It's possible to call Replace with a PaM that spans 2 paragraphs:
4640 // search with regex for "$", then replace _all_
4641 bool DocumentContentOperationsManager::ReplaceRangeImpl( SwPaM& rPam, const OUString& rStr,
4642 const bool bRegExReplace )
4644 if (!rPam.HasMark())
4645 return false;
4647 bool bJoinText, bJoinPrev;
4648 ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev );
4651 // Create a copy of the Cursor in order to move all Pams from
4652 // the other views out of the deletion range.
4653 // Except for itself!
4654 SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
4655 ::PaMCorrAbs( aDelPam, *aDelPam.End() );
4657 auto [pStt, pEnd] = aDelPam.StartEnd(); // SwPosition*
4658 bool bOneNode = pStt->GetNode() == pEnd->GetNode();
4660 // Own Undo?
4661 OUString sRepl( rStr );
4662 SwTextNode* pTextNd = pStt->GetNode().GetTextNode();
4663 sal_Int32 nStt = pStt->GetContentIndex();
4664 sal_Int32 nEnd;
4666 SwDataChanged aTmp( aDelPam );
4668 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
4670 RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
4671 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4673 // this should no longer happen in calls from the UI but maybe via API
4674 SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask,
4675 "sw.core", "redlines will be moved in ReplaceRange");
4677 m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
4679 // If any Redline will change (split!) the node
4680 const ::sw::mark::IMark* pBkmk =
4681 m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam,
4682 OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
4683 ::sw::mark::InsertMode::New);
4685 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags(
4686 RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete );
4688 *aDelPam.GetPoint() = pBkmk->GetMarkPos();
4689 if(pBkmk->IsExpanded())
4690 *aDelPam.GetMark() = pBkmk->GetOtherMarkPos();
4691 m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk);
4692 pStt = aDelPam.Start();
4693 pTextNd = pStt->GetNode().GetTextNode();
4694 nStt = pStt->GetContentIndex();
4697 if( !sRepl.isEmpty() )
4699 // Apply the first character's attributes to the ReplaceText
4700 SfxItemSetFixed
4701 <RES_CHRATR_BEGIN, RES_TXTATR_WITHEND_END - 1,
4702 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1> aSet( m_rDoc.GetAttrPool() );
4703 pTextNd->GetParaAttr( aSet, nStt+1, nStt+1 );
4705 aSet.ClearItem( RES_TXTATR_REFMARK );
4706 aSet.ClearItem( RES_TXTATR_TOXMARK );
4707 aSet.ClearItem( RES_TXTATR_CJK_RUBY );
4708 aSet.ClearItem( RES_TXTATR_INETFMT );
4709 aSet.ClearItem( RES_TXTATR_META );
4710 aSet.ClearItem( RES_TXTATR_METAFIELD );
4712 if( aDelPam.GetPoint() != aDelPam.End() )
4713 aDelPam.Exchange();
4715 // Remember the End
4716 SwNodeIndex aPtNd( aDelPam.GetPoint()->GetNode(), -1 );
4717 const sal_Int32 nPtCnt = aDelPam.GetPoint()->GetContentIndex();
4719 bool bFirst = true;
4720 OUString sIns;
4721 while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) )
4723 InsertString( aDelPam, sIns );
4724 if( bFirst )
4726 SwNodeIndex aMkNd( aDelPam.GetMark()->GetNode(), -1 );
4727 const sal_Int32 nMkCnt = aDelPam.GetMark()->GetContentIndex();
4729 SplitNode( *aDelPam.GetPoint(), false );
4731 ++aMkNd;
4732 aDelPam.GetMark()->Assign( aMkNd, nMkCnt );
4733 bFirst = false;
4735 else
4736 SplitNode( *aDelPam.GetPoint(), false );
4738 if( !sIns.isEmpty() )
4740 InsertString( aDelPam, sIns );
4743 SwPaM aTmpRange( *aDelPam.GetPoint() );
4744 aTmpRange.SetMark();
4746 ++aPtNd;
4747 aDelPam.GetPoint()->Assign(aPtNd, nPtCnt);
4748 *aTmpRange.GetMark() = *aDelPam.GetPoint();
4750 m_rDoc.RstTextAttrs( aTmpRange );
4751 InsertItemSet( aTmpRange, aSet );
4754 // tdf#139982: Appending the redline may immediately delete flys
4755 // anchored in the previous text if it's inside an insert redline.
4756 // Also flys will be deleted if the redline is accepted. Move them
4757 // to the position between the previous text and the new text,
4758 // there the chance of surviving both accept and reject is best.
4759 SaveFlyArr flys;
4760 SaveFlyInRange(aDelPam, *aDelPam.End(), flys, false);
4762 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4764 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
4765 std::make_unique<SwUndoRedlineDelete>( aDelPam, SwUndoId::REPLACE ));
4767 // add redline similar to DeleteAndJoinWithRedlineImpl()
4768 std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*aDelPam.GetMark()));
4769 pCursor->SetMark();
4770 *pCursor->GetPoint() = *aDelPam.GetPoint();
4771 m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDelPam ), true);
4772 RestFlyInRange(flys, *aDelPam.End(), &aDelPam.End()->GetNode(), true);
4773 sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor);
4775 *rPam.GetMark() = *aDelPam.GetMark();
4776 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4778 *aDelPam.GetPoint() = *rPam.GetPoint();
4779 m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4781 // If any Redline will change (split!) the node
4782 const ::sw::mark::IMark* pBkmk =
4783 m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam,
4784 OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
4785 ::sw::mark::InsertMode::New);
4787 aDelPam.GetPoint()->Assign( SwNodeOffset(0) );
4788 aDelPam.GetMark()->Assign( SwNodeOffset(0) );
4789 rPam.GetPoint()->Assign( SwNodeOffset(0) );
4790 *rPam.GetMark() = *rPam.GetPoint();
4791 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
4793 *rPam.GetPoint() = pBkmk->GetMarkPos();
4794 *rPam.GetMark() = pBkmk->IsExpanded() ? pBkmk->GetOtherMarkPos() : pBkmk->GetMarkPos();
4796 m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk);
4798 bJoinText = false;
4800 else
4802 assert((pStt->GetNode() == pEnd->GetNode() ||
4803 ( pStt->GetNodeIndex() + 1 == pEnd->GetNodeIndex() &&
4804 !pEnd->GetContentIndex() )) &&
4805 "invalid range: Point and Mark on different nodes" );
4807 if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
4808 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aDelPam, true, RedlineType::Any );
4810 SwUndoReplace* pUndoRpl = nullptr;
4811 bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
4812 if (bDoesUndo)
4814 pUndoRpl = new SwUndoReplace(aDelPam, sRepl, bRegExReplace);
4815 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoRpl));
4817 ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo());
4819 if( aDelPam.GetPoint() != pStt )
4820 aDelPam.Exchange();
4822 SwNodeIndex aPtNd( pStt->GetNode(), -1 );
4823 const sal_Int32 nPtCnt = pStt->GetContentIndex();
4825 // Set the values again, if Frames or footnotes on the Text have been removed.
4826 nStt = nPtCnt;
4827 nEnd = bOneNode ? pEnd->GetContentIndex()
4828 : pTextNd->GetText().getLength();
4830 bool bFirst = true;
4831 OUString sIns;
4832 while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) )
4834 if (!bFirst || nStt == pTextNd->GetText().getLength())
4836 InsertString( aDelPam, sIns );
4838 else if( nStt < nEnd || !sIns.isEmpty() )
4840 pTextNd->ReplaceText( *pStt, nEnd - nStt, sIns );
4842 SplitNode( *pStt, false);
4843 bFirst = false;
4846 if( bFirst || !sIns.isEmpty() )
4848 if (!bFirst || nStt == pTextNd->GetText().getLength())
4850 InsertString( aDelPam, sIns );
4852 else if( nStt < nEnd || !sIns.isEmpty() )
4854 pTextNd->ReplaceText( *pStt, nEnd - nStt, sIns );
4858 *rPam.GetPoint() = *aDelPam.GetMark();
4859 ++aPtNd;
4860 rPam.GetMark()->Assign( aPtNd, nPtCnt );
4862 if (bJoinText)
4864 assert(rPam.GetPoint() == rPam.End());
4865 // move so that SetEnd remembers position after sw_JoinText
4866 rPam.Move(fnMoveBackward);
4868 else if (aDelPam.GetPoint() == pStt) // backward selection?
4870 assert(*rPam.GetMark() <= *rPam.GetPoint());
4871 rPam.Exchange(); // swap so that rPam is backwards
4874 if( pUndoRpl )
4876 pUndoRpl->SetEnd(rPam);
4881 bool bRet(true);
4882 if (bJoinText)
4884 bRet = ::sw_JoinText(rPam, bJoinPrev);
4887 m_rDoc.getIDocumentState().SetModified();
4888 return bRet;
4891 SwFlyFrameFormat* DocumentContentOperationsManager::InsNoTextNode( const SwPosition& rPos, SwNoTextNode* pNode,
4892 const SfxItemSet* pFlyAttrSet,
4893 const SfxItemSet* pGrfAttrSet,
4894 SwFrameFormat* pFrameFormat)
4896 SwFlyFrameFormat *pFormat = nullptr;
4897 if( pNode )
4899 pFormat = m_rDoc.MakeFlySection_( rPos, *pNode, RndStdIds::FLY_AT_PARA,
4900 pFlyAttrSet, pFrameFormat );
4901 if( pGrfAttrSet )
4902 pNode->SetAttr( *pGrfAttrSet );
4904 return pFormat;
4907 #define NUMRULE_STATE \
4908 std::shared_ptr<SwNumRuleItem> aNumRuleItemHolderIfSet; \
4909 std::shared_ptr<SfxStringItem> aListIdItemHolderIfSet; \
4911 #define PUSH_NUMRULE_STATE \
4912 lcl_PushNumruleState( aNumRuleItemHolderIfSet, aListIdItemHolderIfSet, pDestTextNd );
4914 #define POP_NUMRULE_STATE \
4915 lcl_PopNumruleState( aNumRuleItemHolderIfSet, aListIdItemHolderIfSet, pDestTextNd, rPam );
4917 static void lcl_PushNumruleState(
4918 std::shared_ptr<SwNumRuleItem>& aNumRuleItemHolderIfSet,
4919 std::shared_ptr<SfxStringItem>& aListIdItemHolderIfSet,
4920 const SwTextNode *pDestTextNd )
4922 // Safe numrule item at destination.
4923 // #i86492# - Safe also <ListId> item of destination.
4924 const SfxItemSet * pAttrSet = pDestTextNd->GetpSwAttrSet();
4925 if (pAttrSet == nullptr)
4926 return;
4928 if (const SwNumRuleItem* pItem = pAttrSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
4930 aNumRuleItemHolderIfSet.reset(pItem->Clone());
4933 if (const SfxStringItem* pItem = pAttrSet->GetItemIfSet(RES_PARATR_LIST_ID, false))
4935 aListIdItemHolderIfSet.reset(pItem->Clone());
4939 static void lcl_PopNumruleState(
4940 const std::shared_ptr<SwNumRuleItem>& aNumRuleItemHolderIfSet,
4941 const std::shared_ptr<SfxStringItem>& aListIdItemHolderIfSet,
4942 SwTextNode *pDestTextNd, const SwPaM& rPam )
4944 /* If only a part of one paragraph is copied
4945 restore the numrule at the destination. */
4946 // #i86492# - restore also <ListId> item
4947 if ( lcl_MarksWholeNode(rPam) )
4948 return;
4950 if (aNumRuleItemHolderIfSet)
4952 pDestTextNd->SetAttr(*aNumRuleItemHolderIfSet);
4954 else
4956 pDestTextNd->ResetAttr(RES_PARATR_NUMRULE);
4959 if (aListIdItemHolderIfSet)
4961 pDestTextNd->SetAttr(*aListIdItemHolderIfSet);
4963 else
4965 pDestTextNd->ResetAttr(RES_PARATR_LIST_ID);
4969 bool DocumentContentOperationsManager::CopyImpl(SwPaM& rPam, SwPosition& rPos,
4970 SwCopyFlags const flags,
4971 SwPaM *const pCopyRange) const
4973 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
4975 sw::CalcBreaks(Breaks, rPam, true);
4977 if (Breaks.empty())
4979 return CopyImplImpl(rPam, rPos, flags, pCopyRange);
4982 SwPosition const & rSelectionEnd( *rPam.End() );
4984 bool bRet(true);
4985 bool bFirst(true);
4986 // iterate from end to start, ... don't think it's necessary here?
4987 auto iter( Breaks.rbegin() );
4988 SwNodeOffset nOffset(0);
4989 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
4990 SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node!
4991 SwPosition & rEnd( *aPam.End() );
4992 SwPosition & rStart( *aPam.Start() );
4993 SwPaM copyRange(rPos, rPos);
4995 while (iter != Breaks.rend())
4997 rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
4998 if (rStart < rEnd) // check if part is empty
5000 // pass in copyRange member as rPos; should work ...
5001 bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, &copyRange);
5002 nOffset = iter->first - rStart.GetNodeIndex(); // fly nodes...
5003 if (pCopyRange)
5005 if (bFirst)
5007 pCopyRange->SetMark();
5008 *pCopyRange->GetMark() = *copyRange.End();
5010 *pCopyRange->GetPoint() = *copyRange.Start();
5012 bFirst = false;
5014 rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
5015 ++iter;
5018 rStart = *rPam.Start(); // set to original start
5019 if (rStart < rEnd) // check if part is empty
5021 bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, &copyRange);
5022 if (pCopyRange)
5024 if (bFirst)
5026 pCopyRange->SetMark();
5027 *pCopyRange->GetMark() = *copyRange.End();
5029 *pCopyRange->GetPoint() = *copyRange.Start();
5033 return bRet;
5036 bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPos,
5037 SwCopyFlags const flags,
5038 SwPaM *const pCpyRange) const
5040 SwDoc& rDoc = rPos.GetNode().GetDoc();
5041 const bool bColumnSel = rDoc.IsClipBoard() && rDoc.IsColumnSelection();
5043 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
5045 // Catch when there's no copy to do.
5046 if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel) ||
5047 //JP 29.6.2001: 88963 - don't copy if inspos is in region of start to end
5048 //JP 15.11.2001: don't test inclusive the end, ever exclusive
5049 ( &rDoc == &m_rDoc && *pStt <= rPos && rPos < *pEnd ))
5051 return false;
5054 const bool bEndEqualIns = &rDoc == &m_rDoc && rPos == *pEnd;
5056 // If Undo is enabled, create the UndoCopy object
5057 SwUndoCpyDoc* pUndo = nullptr;
5058 // lcl_DeleteRedlines may delete the start or end node of the cursor when
5059 // removing the redlines so use cursor that is corrected by PaMCorrAbs
5060 std::shared_ptr<SwUnoCursor> const pCopyPam(rDoc.CreateUnoCursor(rPos));
5062 SwTableNumFormatMerge aTNFM( m_rDoc, rDoc );
5063 std::optional<std::vector<SwFrameFormat*>> pFlys;
5064 std::vector<SwFrameFormat*> const* pFlysAtInsPos;
5066 if (rDoc.GetIDocumentUndoRedo().DoesUndo())
5068 pUndo = new SwUndoCpyDoc(*pCopyPam);
5069 pFlysAtInsPos = pUndo->GetFlysAnchoredAt();
5071 else
5073 pFlys = sw::GetFlysAnchoredAt(rDoc, rPos.GetNodeIndex(), false);
5074 pFlysAtInsPos = pFlys ? &*pFlys : nullptr;
5077 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
5078 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
5080 // Move the PaM one node back from the insert position, so that
5081 // the position doesn't get moved
5082 pCopyPam->SetMark();
5083 bool bCanMoveBack = false;
5084 // First check if it will be able to move *to* first copied node.
5085 // Note this doesn't just check IsStartNode() because SwDoc::AppendDoc()
5086 // intentionally sets it to the body start node, perhaps it should just
5087 // call SplitNode instead?
5088 if (!pStt->GetNode().IsSectionNode() && !pStt->GetNode().IsTableNode())
5090 bCanMoveBack = pCopyPam->Move(fnMoveBackward, GoInContent);
5092 if( !bCanMoveBack )
5094 pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1));
5095 assert(pCopyPam->GetPoint()->GetContentIndex() == 0);
5098 SwNodeRange aRg( pStt->GetNode(), pEnd->GetNode() );
5099 SwNodeIndex aInsPos( rPos.GetNode() );
5100 ::std::optional<SwContentIndex> oInsContentIndex;
5101 const bool bOneNode = pStt->GetNode() == pEnd->GetNode();
5102 SwTextNode* pSttTextNd = pStt->GetNode().GetTextNode();
5103 SwTextNode* pEndTextNd = pEnd->GetNode().GetTextNode();
5104 SwTextNode* pDestTextNd = aInsPos.GetNode().GetTextNode();
5105 bool bCopyCollFormat = !rDoc.IsInsOnlyTextGlossary() &&
5106 ( (pDestTextNd && !pDestTextNd->GetText().getLength()) ||
5107 ( !bOneNode && !rPos.GetContentIndex() ) );
5108 bool bCopyBookmarks = true;
5109 bool bCopyPageSource = false;
5110 SwNodeOffset nDeleteTextNodes(0);
5112 // #i104585# copy outline num rule to clipboard (for ASCII filter)
5113 if (rDoc.IsClipBoard() && m_rDoc.GetOutlineNumRule())
5115 rDoc.SetOutlineNumRule(*m_rDoc.GetOutlineNumRule());
5118 // #i86492#
5119 // Correct the search for a previous list:
5120 // First search for non-outline numbering list. Then search for non-outline
5121 // bullet list.
5122 // Keep also the <ListId> value for possible propagation.
5123 OUString aListIdToPropagate;
5124 SvxTextLeftMarginItem const* pTextLeftMarginToPropagate{nullptr};
5125 SvxFirstLineIndentItem const* pFirstLineIndentToPropagate{nullptr};
5126 const SwNumRule* pNumRuleToPropagate =
5127 rDoc.SearchNumRule(rPos, false, true, false, 0, aListIdToPropagate, nullptr,
5128 true, &pTextLeftMarginToPropagate, &pFirstLineIndentToPropagate);
5129 if ( !pNumRuleToPropagate )
5131 pNumRuleToPropagate =
5132 rDoc.SearchNumRule(rPos, false, false, false, 0, aListIdToPropagate, nullptr,
5133 true, &pTextLeftMarginToPropagate, &pFirstLineIndentToPropagate);
5135 // #i86492#
5136 // Do not propagate previous found list, if
5137 // - destination is an empty paragraph which is not in a list and
5138 // - source contains at least one paragraph which is not in a list
5139 // or
5140 // - source is a table
5141 if ( pNumRuleToPropagate &&
5142 ((pDestTextNd && !pDestTextNd->GetText().getLength() &&
5143 !pDestTextNd->IsInList() &&
5144 !lcl_ContainsOnlyParagraphsInList(rPam)) ||
5145 rPam.GetBound().nNode.GetNode().GetNodeType() == SwNodeType::Table) )
5147 pNumRuleToPropagate = nullptr;
5150 // This do/while block is only there so that we can break out of it!
5151 do {
5152 if( pSttTextNd )
5154 ++nDeleteTextNodes; // must be joined in Undo
5155 // Don't copy the beginning completely?
5156 if( !bCopyCollFormat || bColumnSel || pStt->GetContentIndex() )
5158 SwContentIndex aDestIdx( rPos.GetContentNode(), rPos.GetContentIndex() );
5159 bool bCopyOk = false;
5160 if( !pDestTextNd )
5162 if( pStt->GetContentIndex() || bOneNode )
5163 pDestTextNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode(),
5164 rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD));
5165 else
5167 pDestTextNd = pSttTextNd->MakeCopy(rDoc, aInsPos.GetNode(), true)->GetTextNode();
5168 bCopyOk = true;
5170 aDestIdx.Assign( pDestTextNd, 0 );
5171 bCopyCollFormat = true;
5173 else if( !bOneNode || bColumnSel )
5175 const sal_Int32 nContentEnd = pEnd->GetContentIndex();
5177 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5178 rDoc.getIDocumentContentOperations().SplitNode( rPos, false );
5181 assert(rPos != *pCopyPam->GetPoint()); // code removed
5183 pDestTextNd = rDoc.GetNodes()[ aInsPos.GetIndex()-SwNodeOffset(1) ]->GetTextNode();
5184 aDestIdx.Assign(
5185 pDestTextNd, pDestTextNd->GetText().getLength());
5187 // Correct the area again
5188 if( bEndEqualIns )
5190 bool bChg = pEnd != rPam.GetPoint();
5191 if( bChg )
5192 rPam.Exchange();
5193 rPam.Move( fnMoveBackward, GoInContent );
5194 if( bChg )
5195 rPam.Exchange();
5197 else if( rPos == *pEnd )
5199 // The end was also moved
5200 pEnd->Adjust(SwNodeOffset(-1));
5201 pEnd->SetContent( nContentEnd );
5203 // tdf#63022 always reset pEndTextNd after SplitNode
5204 aRg.aEnd = pEnd->GetNode();
5205 pEndTextNd = pEnd->GetNode().GetTextNode();
5208 NUMRULE_STATE
5209 if( bCopyCollFormat && bOneNode )
5211 PUSH_NUMRULE_STATE
5214 if( !bCopyOk )
5216 const sal_Int32 nCpyLen = ( bOneNode
5217 ? pEnd->GetContentIndex()
5218 : pSttTextNd->GetText().getLength())
5219 - pStt->GetContentIndex();
5220 pSttTextNd->CopyText( pDestTextNd, aDestIdx, *pStt, nCpyLen );
5221 if( bEndEqualIns )
5222 pEnd->AdjustContent( -nCpyLen );
5225 ++aRg.aStart;
5227 if( bOneNode )
5229 if (bCopyCollFormat)
5231 // tdf#138897 no Undo for applying style, SwUndoInserts does it
5232 pSttTextNd->CopyCollFormat(*pDestTextNd, false);
5233 POP_NUMRULE_STATE
5236 // Copy at-char flys in rPam.
5237 // Update to new (start) node for flys.
5238 // tdf#126626 prevent duplicate Undos.
5239 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5240 CopyFlyInFlyImpl(aRg, &rPam, *pDestTextNd, false);
5242 break;
5246 else if( pDestTextNd )
5248 // Problems with insertion of table selections into "normal" text solved.
5249 // We have to set the correct PaM for Undo, if this PaM starts in a textnode,
5250 // the undo operation will try to merge this node after removing the table.
5251 // If we didn't split a textnode, the PaM should start at the inserted table node
5252 if (pDestTextNd->Len() && rPos.GetContentIndex() == pDestTextNd->Len())
5253 { // Insertion at the last position of a textnode
5254 ++aInsPos; // The table will be inserted behind the text node
5256 else if( rPos.GetContentIndex() )
5257 { // Insertion in the middle of a text node, it has to be split
5258 // (and joined from undo)
5259 ++nDeleteTextNodes;
5261 const sal_Int32 nContentEnd = pEnd->GetContentIndex();
5263 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5264 rDoc.getIDocumentContentOperations().SplitNode( rPos, false );
5267 assert(rPos != *pCopyPam->GetPoint()); // code removed
5269 // Correct the area again
5270 if( bEndEqualIns )
5271 --aRg.aEnd;
5272 // The end would also be moved
5273 else if( rPos == *pEnd )
5275 rPos.Adjust(SwNodeOffset(-1));
5276 rPos.SetContent( nContentEnd );
5277 --aRg.aEnd;
5280 assert(!bCanMoveBack);
5283 pDestTextNd = aInsPos.GetNode().GetTextNode();
5284 if (pEndTextNd)
5286 oInsContentIndex.emplace(aInsPos.GetNode().GetContentNode(), rPos.GetContentIndex());
5287 if( !pDestTextNd )
5289 pDestTextNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode(),
5290 rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD));
5291 oInsContentIndex->Assign(pDestTextNd, 0);
5292 --aInsPos;
5294 // if we have to insert an extra text node
5295 // at the destination, this node will be our new destination
5296 // (text) node, and thus we increment nDeleteTextNodes. This
5297 // will ensure that this node will be deleted during Undo.
5298 ++nDeleteTextNodes; // must be deleted
5301 const bool bEmptyDestNd = pDestTextNd->GetText().isEmpty();
5303 NUMRULE_STATE
5304 if( bCopyCollFormat && ( bOneNode || bEmptyDestNd ))
5306 PUSH_NUMRULE_STATE
5309 pEndTextNd->CopyText(pDestTextNd, *oInsContentIndex,
5310 SwContentIndex(pEndTextNd), pEnd->GetContentIndex());
5312 // Also copy all format templates
5313 if( bCopyCollFormat && ( bOneNode || bEmptyDestNd ))
5315 // tdf#138897 no Undo for applying style, SwUndoInserts does it
5316 pEndTextNd->CopyCollFormat(*pDestTextNd, false);
5317 if ( bOneNode )
5319 POP_NUMRULE_STATE
5324 SfxItemSet aBrkSet( rDoc.GetAttrPool(), aBreakSetRange );
5325 if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd)
5327 if (pSttTextNd && bCopyCollFormat && pDestTextNd->HasSwAttrSet())
5329 aBrkSet.Put( *pDestTextNd->GetpSwAttrSet() );
5330 if( SfxItemState::SET == aBrkSet.GetItemState( RES_BREAK, false ) )
5331 pDestTextNd->ResetAttr( RES_BREAK );
5332 if( SfxItemState::SET == aBrkSet.GetItemState( RES_PAGEDESC, false ) )
5333 pDestTextNd->ResetAttr( RES_PAGEDESC );
5338 SwPosition startPos(pCopyPam->GetPoint()->GetNode(), SwNodeOffset(+1));
5339 if (bCanMoveBack)
5340 { // pCopyPam is actually 1 before the copy range so move it fwd
5341 SwPaM temp(*pCopyPam->GetPoint());
5342 temp.Move(fnMoveForward, GoInContent);
5343 startPos = *temp.GetPoint();
5345 assert(startPos.GetNode().IsContentNode());
5346 std::pair<SwPaM const&, SwPosition const&> tmp(rPam, startPos);
5347 if( aInsPos == pEnd->GetNode() )
5349 SwNodeIndex aSaveIdx( aInsPos, -1 );
5350 assert(pStt->GetNode() != pEnd->GetNode());
5351 pEnd->SetContent(0); // TODO why this?
5352 CopyWithFlyInFly(aRg, aInsPos.GetNode(), &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags);
5353 ++aSaveIdx;
5354 pEnd->Assign(aSaveIdx);
5356 else
5357 CopyWithFlyInFly(aRg, aInsPos.GetNode(), &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags);
5359 bCopyBookmarks = false;
5363 // init *again* - because CopyWithFlyInFly moved startPos
5364 SwPosition startPos(pCopyPam->GetPoint()->GetNode(), SwNodeOffset(+1));
5365 // at-char anchors post SplitNode are on index 0 of 2nd node and will
5366 // remain there - move them back to the start (end would also work?)
5367 // ... also for at-para anchors; here start is preferable because
5368 // it's consistent with SplitNode from SwUndoInserts::RedoImpl()
5369 if (pFlysAtInsPos
5370 && (bCanMoveBack
5371 || startPos.GetNode().IsTextNode()
5372 || (pCopyPam->GetPoint()->GetNode().IsStartNode()
5373 && startPos.GetNode().IsSectionNode()))) // not into table
5375 if (bCanMoveBack)
5376 { // pCopyPam is actually 1 before the copy range so move it fwd
5377 SwPaM temp(*pCopyPam->GetPoint());
5378 temp.Move(fnMoveForward, GoInContent);
5379 startPos = *temp.GetPoint();
5381 else if (startPos.GetNode().IsSectionNode())
5382 { // probably on top-level start node, so no CheckNodesRange here;
5383 GoNextPos(&startPos, false); // SwFEShell::Paste() deletes node
5385 assert(startPos.GetNode().IsContentNode());
5386 SwPosition startPosAtPara(startPos);
5387 startPosAtPara.nContent.Assign(nullptr, 0);
5389 for (SwFrameFormat * pFly : *pFlysAtInsPos)
5391 SwFormatAnchor const*const pAnchor = &pFly->GetAnchor();
5392 if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
5394 SwFormatAnchor anchor(*pAnchor);
5395 anchor.SetAnchor( &startPos );
5396 pFly->SetFormatAttr(anchor);
5398 else if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA)
5400 SwFormatAnchor anchor(*pAnchor);
5401 anchor.SetAnchor( &startPosAtPara );
5403 bool bSplitFly = false;
5404 if (pFly->GetFlySplit().GetValue())
5406 SwIterator<SwFrame, SwModify> aIter(*pFly);
5407 bSplitFly = aIter.First() && aIter.Next();
5409 if (bSplitFly)
5411 // This fly format has multiple frames, and we change the anchor. Remove the
5412 // old frames, which were based on the old anchor position.
5413 pFly->DelFrames();
5416 pFly->SetFormatAttr(anchor);
5418 if (bSplitFly)
5420 // Re-create the frames now that the new anchor is set.
5421 pFly->MakeFrames();
5427 if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd)
5429 // Put the breaks back into the first node
5430 if( aBrkSet.Count() && nullptr != ( pDestTextNd = rDoc.GetNodes()[
5431 pCopyPam->GetPoint()->GetNodeIndex()+1 ]->GetTextNode()))
5433 pDestTextNd->SetAttr( aBrkSet );
5434 bCopyPageSource = true;
5437 } while( false );
5440 // it is not possible to make this test when copy from the clipBoard to document
5441 // in this case the PageNum not exist anymore
5442 // tdf#39400 and tdf#97526
5443 // when copy from document to ClipBoard, and it is from the first page
5444 // and not the source has the page break
5445 if (rDoc.IsClipBoard() && (rPam.GetPageNum(pStt == rPam.GetPoint()) == 1) && !bCopyPageSource)
5447 if (pDestTextNd)
5449 pDestTextNd->ResetAttr(RES_BREAK); // remove the page-break
5450 pDestTextNd->ResetAttr(RES_PAGEDESC);
5455 // Adjust position (in case it was moved / in another node)
5456 rPos.nContent.Assign( rPos.GetNode().GetContentNode(),
5457 rPos.GetContentIndex() );
5459 if( rPos.GetNode() != aInsPos.GetNode() )
5461 if (aInsPos < rPos.GetNode())
5462 { // tdf#134250 decremented in (pEndTextNd && !pDestTextNd) above
5463 pCopyPam->GetMark()->AssignEndIndex(*aInsPos.GetNode().GetContentNode());
5465 else // incremented in (!pSttTextNd && pDestTextNd) above
5467 // assign also content index in this case, see testSectionAnchorCopyTableAtStart
5468 assert(oInsContentIndex);
5469 assert(oInsContentIndex->GetContentNode() == &aInsPos.GetNode());
5470 pCopyPam->GetMark()->Assign(aInsPos, oInsContentIndex->GetIndex());
5472 rPos = *pCopyPam->GetMark();
5474 else
5475 *pCopyPam->GetMark() = rPos;
5477 if (bCanMoveBack)
5479 pCopyPam->Move(fnMoveForward, GoInContent);
5481 else
5483 // Reset the offset to 0 as it was before the insertion
5484 pCopyPam->GetPoint()->Adjust(SwNodeOffset(+1));
5486 // If the next node is a start node, then step back: SetInsertRange()
5487 // will add 1 in this case, but that is too much...
5488 if (pCopyPam->GetPoint()->GetNode().IsStartNode())
5489 pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1));
5491 oInsContentIndex.reset();
5492 pCopyPam->Exchange();
5494 // Also copy all bookmarks
5495 if( bCopyBookmarks && m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() )
5497 sw::CopyBookmarks(rPam, *pCopyPam->Start());
5500 if( RedlineFlags::DeleteRedlines & eOld )
5502 assert(*pCopyPam->GetPoint() == rPos);
5503 // the Node rPos points to may be deleted so unregister ...
5504 rPos.nContent.Assign(nullptr, 0);
5505 lcl_DeleteRedlines(rPam, *pCopyPam);
5506 rPos = *pCopyPam->GetPoint(); // ... and restore.
5509 // If Undo is enabled, store the inserted area
5510 if (rDoc.GetIDocumentUndoRedo().DoesUndo())
5512 // append it after styles have been copied when copying nodes
5513 rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
5514 pUndo->SetInsertRange(*pCopyPam, true, nDeleteTextNodes);
5517 if( pCpyRange )
5519 pCpyRange->SetMark();
5520 *pCpyRange->GetPoint() = *pCopyPam->GetPoint();
5521 *pCpyRange->GetMark() = *pCopyPam->GetMark();
5524 if ( pNumRuleToPropagate != nullptr )
5526 // #i86492# - use <SwDoc::SetNumRule(..)>, because it also handles the <ListId>
5527 // Don't reset indent attributes, that would mean loss of direct
5528 // formatting.
5529 // It could be that pNumRuleToPropagate is already applied via
5530 // the paragraph style, in that case applying it again in mpAttrSet could
5531 // override indents, so avoid that.
5532 rDoc.SetNumRule(*pCopyPam, *pNumRuleToPropagate,
5533 SwDoc::SetNumRuleMode::DontSetIfAlreadyApplied, nullptr, aListIdToPropagate,
5534 pTextLeftMarginToPropagate, pFirstLineIndentToPropagate);
5537 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
5538 rDoc.getIDocumentState().SetModified();
5540 return true;
5545 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */