android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / core / doc / DocumentContentOperationsManager.cxx
blobbac73a325a5fb28fc70f4dc841474ea37fc101a7
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 <textboxhelper.hxx>
33 #include <dcontact.hxx>
34 #include <grfatr.hxx>
35 #include <numrule.hxx>
36 #include <charfmt.hxx>
37 #include <ndgrf.hxx>
38 #include <ndnotxt.hxx>
39 #include <ndole.hxx>
40 #include <breakit.hxx>
41 #include <frmfmt.hxx>
42 #include <fmtanchr.hxx>
43 #include <fmtcntnt.hxx>
44 #include <fmtinfmt.hxx>
45 #include <fmtpdsc.hxx>
46 #include <fmtcnct.hxx>
47 #include <SwStyleNameMapper.hxx>
48 #include <redline.hxx>
49 #include <txtfrm.hxx>
50 #include <rootfrm.hxx>
51 #include <frmtool.hxx>
52 #include <unocrsr.hxx>
53 #include <mvsave.hxx>
54 #include <ndtxt.hxx>
55 #include <poolfmt.hxx>
56 #include <paratr.hxx>
57 #include <txatbase.hxx>
58 #include <UndoRedline.hxx>
59 #include <undobj.hxx>
60 #include <UndoBookmark.hxx>
61 #include <UndoDelete.hxx>
62 #include <UndoSplitMove.hxx>
63 #include <UndoOverwrite.hxx>
64 #include <UndoInsert.hxx>
65 #include <UndoAttribute.hxx>
66 #include <rolbck.hxx>
67 #include <acorrect.hxx>
68 #include <bookmark.hxx>
69 #include <ftnidx.hxx>
70 #include <txtftn.hxx>
71 #include <hints.hxx>
72 #include <fmtflcnt.hxx>
73 #include <docedt.hxx>
74 #include <frameformats.hxx>
75 #include <formatflysplit.hxx>
76 #include <o3tl/safeint.hxx>
77 #include <sal/log.hxx>
78 #include <unotools/charclass.hxx>
79 #include <unotools/configmgr.hxx>
80 #include <unotools/transliterationwrapper.hxx>
81 #include <i18nutil/transliteration.hxx>
82 #include <sfx2/Metadatable.hxx>
83 #include <sot/exchange.hxx>
84 #include <svl/stritem.hxx>
85 #include <svl/itemiter.hxx>
86 #include <svx/svdobj.hxx>
87 #include <svx/svdouno.hxx>
88 #include <tools/globname.hxx>
89 #include <editeng/formatbreakitem.hxx>
90 #include <com/sun/star/i18n/Boundary.hpp>
91 #include <com/sun/star/i18n/WordType.hpp>
92 #include <com/sun/star/i18n/XBreakIterator.hpp>
93 #include <com/sun/star/embed/XEmbeddedObject.hpp>
95 #include <tuple>
96 #include <memory>
98 using namespace ::com::sun::star::i18n;
100 namespace
102 // Copy method from SwDoc
103 // Prevent copying into Flys that are anchored in the range
104 bool lcl_ChkFlyFly( SwDoc& rDoc, SwNodeOffset nSttNd, SwNodeOffset nEndNd,
105 SwNodeOffset nInsNd )
108 for(sw::SpzFrameFormat* pFormat: *rDoc.GetSpzFrameFormats())
110 SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
111 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
112 if (pAnchorNode &&
113 ((RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) ||
114 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) ||
115 (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId()) ||
116 (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId())) &&
117 nSttNd <= pAnchorNode->GetIndex() &&
118 pAnchorNode->GetIndex() < nEndNd )
120 const SwFormatContent& rContent = pFormat->GetContent();
121 SwStartNode* pSNd;
122 if( !rContent.GetContentIdx() ||
123 nullptr == ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ))
124 continue;
126 if( pSNd->GetIndex() < nInsNd &&
127 nInsNd < pSNd->EndOfSectionIndex() )
128 // Do not copy !
129 return true;
131 if( lcl_ChkFlyFly( rDoc, pSNd->GetIndex(),
132 pSNd->EndOfSectionIndex(), nInsNd ) )
133 // Do not copy !
134 return true;
138 return false;
141 SwNodeIndex InitDelCount(SwPaM const& rSourcePaM, SwNodeOffset & rDelCount)
143 SwPosition const& rStart(*rSourcePaM.Start());
144 // Special handling for SwDoc::AppendDoc
145 if (rSourcePaM.GetDoc().GetNodes().GetEndOfExtras().GetIndex() + 1
146 == rStart.GetNodeIndex())
148 rDelCount = SwNodeOffset(1);
149 return SwNodeIndex(rStart.GetNode(), +1);
151 else
153 rDelCount = SwNodeOffset(0);
154 return SwNodeIndex(rStart.GetNode());
159 The CopyBookmarks function has to copy bookmarks from the source to the destination nodes
160 array. It is called after a call of the CopyNodes(..) function. But this function does not copy
161 every node (at least at the moment: 2/08/2006 ), section start and end nodes will not be copied
162 if the corresponding end/start node is outside the copied pam.
163 The lcl_NonCopyCount function counts the number of these nodes, given the copied pam and a node
164 index inside the pam.
165 rPam is the original source pam, rLastIdx is the last calculated position, rDelCount the number
166 of "non-copy" nodes between rPam.Start() and rLastIdx.
167 nNewIdx is the new position of interest.
169 void lcl_NonCopyCount( const SwPaM& rPam, SwNodeIndex& rLastIdx, const SwNodeOffset nNewIdx, SwNodeOffset& rDelCount )
171 SwNodeOffset nStart = rPam.Start()->GetNodeIndex();
172 SwNodeOffset nEnd = rPam.End()->GetNodeIndex();
173 if( rLastIdx.GetIndex() < nNewIdx ) // Moving forward?
175 // We never copy the StartOfContent node
176 do // count "non-copy" nodes
178 SwNode& rNode = rLastIdx.GetNode();
179 if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd )
180 || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) )
182 ++rDelCount;
184 ++rLastIdx;
186 while( rLastIdx.GetIndex() < nNewIdx );
188 else if( rDelCount ) // optimization: if there are no "non-copy" nodes until now,
189 // no move backward needed
191 while( rLastIdx.GetIndex() > nNewIdx )
193 SwNode& rNode = rLastIdx.GetNode();
194 if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd )
195 || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) )
197 --rDelCount;
199 --rLastIdx;
204 void lcl_SetCpyPos( const SwPosition& rOrigPos,
205 const SwPosition& rOrigStt,
206 const SwPosition& rCpyStt,
207 SwPosition& rChgPos,
208 SwNodeOffset nDelCount )
210 SwNodeOffset nNdOff = rOrigPos.GetNodeIndex();
211 nNdOff -= rOrigStt.GetNodeIndex();
212 nNdOff -= nDelCount;
213 sal_Int32 nContentPos = rOrigPos.GetContentIndex();
215 // Always adjust <nNode> at to be changed <SwPosition> instance <rChgPos>
216 rChgPos.Assign( nNdOff + rCpyStt.GetNodeIndex() );
217 if (!rChgPos.GetNode().GetContentNode())
218 return;
219 if( !nNdOff )
221 // just adapt the content index
222 if( nContentPos > rOrigStt.GetContentIndex() )
223 nContentPos -= rOrigStt.GetContentIndex();
224 else
225 nContentPos = 0;
226 nContentPos += rCpyStt.GetContentIndex();
228 rChgPos.SetContent( nContentPos );
233 namespace sw
235 // TODO: use SaveBookmark (from DelBookmarks)
236 void CopyBookmarks(const SwPaM& rPam, const SwPosition& rCpyPam, SwCopyFlags flags)
238 const SwDoc& rSrcDoc = rPam.GetDoc();
239 SwDoc& rDestDoc = rCpyPam.GetDoc();
240 const IDocumentMarkAccess* const pSrcMarkAccess = rSrcDoc.getIDocumentMarkAccess();
241 ::sw::UndoGuard const undoGuard(rDestDoc.GetIDocumentUndoRedo());
243 const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End();
244 SwPosition const*const pCpyStt = &rCpyPam;
246 std::vector< const ::sw::mark::IMark* > vMarksToCopy;
247 for ( IDocumentMarkAccess::const_iterator_t ppMark = pSrcMarkAccess->getAllMarksBegin();
248 ppMark != pSrcMarkAccess->getAllMarksEnd();
249 ++ppMark )
251 const ::sw::mark::IMark* const pMark = *ppMark;
253 const SwPosition& rMarkStart = pMark->GetMarkStart();
254 const SwPosition& rMarkEnd = pMark->GetMarkEnd();
255 // only include marks that are in the range and not touching both start and end
256 // - not for annotation or checkbox marks.
257 bool const isIncludeStart(
258 (rStt.GetContentIndex() == 0 // paragraph start selected?
259 // also: only if inserting at the start - cross reference
260 // marks require index to be 0, and there could be one
261 // on the target node already
262 && rCpyPam.GetContentIndex() == 0)
263 || rMarkStart != rStt);
264 bool const isIncludeEnd(
265 (rEnd.GetNode().IsTextNode() // paragraph end selected?
266 && rEnd.GetContentIndex() == rEnd.GetNode().GetTextNode()->Len())
267 || rMarkEnd != rEnd);
268 const bool bIsNotOnBoundary =
269 pMark->IsExpanded()
270 ? (isIncludeStart || isIncludeEnd) // rMarkStart != rMarkEnd
271 : (isIncludeStart && isIncludeEnd); // rMarkStart == rMarkEnd
272 const IDocumentMarkAccess::MarkType aMarkType = IDocumentMarkAccess::GetType(*pMark);
273 if ( rMarkStart >= rStt && rMarkEnd <= rEnd
274 && ( bIsNotOnBoundary
275 || aMarkType == IDocumentMarkAccess::MarkType::ANNOTATIONMARK
276 || aMarkType == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK
277 || aMarkType == IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK
278 || aMarkType == IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK
279 || aMarkType == IDocumentMarkAccess::MarkType::DATE_FIELDMARK))
281 vMarksToCopy.push_back(pMark);
284 // We have to count the "non-copied" nodes...
285 SwNodeOffset nDelCount;
286 SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount));
287 for(const sw::mark::IMark* const pMark : vMarksToCopy)
289 SwPaM aTmpPam(*pCpyStt);
290 lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetMarkPos().GetNodeIndex(), nDelCount);
291 lcl_SetCpyPos( pMark->GetMarkPos(), rStt, *pCpyStt, *aTmpPam.GetPoint(), nDelCount);
292 if(pMark->IsExpanded())
294 aTmpPam.SetMark();
295 lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetOtherMarkPos().GetNodeIndex(), nDelCount);
296 lcl_SetCpyPos(pMark->GetOtherMarkPos(), rStt, *pCpyStt, *aTmpPam.GetMark(), nDelCount);
299 OUString sRequestedName = pMark->GetName();
300 if (flags & SwCopyFlags::IsMoveToFly)
302 assert(&rSrcDoc == &rDestDoc);
303 // Ensure the name can be given to NewMark, since this is ultimately a move
304 auto pSoonToBeDeletedMark = const_cast<sw::mark::IMark*>(pMark);
305 rDestDoc.getIDocumentMarkAccess()->renameMark(pSoonToBeDeletedMark,
306 sRequestedName + "COPY_IS_MOVE");
309 ::sw::mark::IMark* const pNewMark = rDestDoc.getIDocumentMarkAccess()->makeMark(
310 aTmpPam,
311 sRequestedName,
312 IDocumentMarkAccess::GetType(*pMark),
313 ::sw::mark::InsertMode::CopyText);
314 // Explicitly try to get exactly the same name as in the source
315 // because NavigatorReminders, DdeBookmarks etc. ignore the proposed name
316 if (pNewMark == nullptr)
318 assert(IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK
319 || IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK);
320 continue; // can't insert duplicate cross reference mark
322 rDestDoc.getIDocumentMarkAccess()->renameMark(pNewMark, sRequestedName);
324 // copying additional attributes for bookmarks or fieldmarks
325 ::sw::mark::IBookmark* const pNewBookmark =
326 dynamic_cast< ::sw::mark::IBookmark* const >(pNewMark);
327 const ::sw::mark::IBookmark* const pOldBookmark =
328 dynamic_cast< const ::sw::mark::IBookmark* >(pMark);
329 if (pNewBookmark && pOldBookmark)
331 pNewBookmark->SetKeyCode(pOldBookmark->GetKeyCode());
332 pNewBookmark->SetShortName(pOldBookmark->GetShortName());
333 pNewBookmark->Hide(pOldBookmark->IsHidden());
334 pNewBookmark->SetHideCondition(pOldBookmark->GetHideCondition());
336 ::sw::mark::IFieldmark* const pNewFieldmark =
337 dynamic_cast< ::sw::mark::IFieldmark* const >(pNewMark);
338 const ::sw::mark::IFieldmark* const pOldFieldmark =
339 dynamic_cast< const ::sw::mark::IFieldmark* >(pMark);
340 if (pNewFieldmark && pOldFieldmark)
342 pNewFieldmark->SetFieldname(pOldFieldmark->GetFieldname());
343 pNewFieldmark->SetFieldHelptext(pOldFieldmark->GetFieldHelptext());
344 ::sw::mark::IFieldmark::parameter_map_t* pNewParams = pNewFieldmark->GetParameters();
345 const ::sw::mark::IFieldmark::parameter_map_t* pOldParams = pOldFieldmark->GetParameters();
346 for (const auto& rEntry : *pOldParams )
348 pNewParams->insert( rEntry );
352 ::sfx2::Metadatable const*const pMetadatable(
353 dynamic_cast< ::sfx2::Metadatable const* >(pMark));
354 ::sfx2::Metadatable *const pNewMetadatable(
355 dynamic_cast< ::sfx2::Metadatable * >(pNewMark));
356 if (pMetadatable && pNewMetadatable)
358 pNewMetadatable->RegisterAsCopyOf(*pMetadatable);
362 } // namespace sw
364 namespace
366 void lcl_DeleteRedlines( const SwPaM& rPam, SwPaM& rCpyPam )
368 const SwDoc& rSrcDoc = rPam.GetDoc();
369 const SwRedlineTable& rTable = rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable();
370 if( rTable.empty() )
371 return;
373 SwDoc& rDestDoc = rCpyPam.GetDoc();
374 SwPosition* pCpyStt = rCpyPam.Start(), *pCpyEnd = rCpyPam.End();
375 std::unique_ptr<SwPaM> pDelPam;
376 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
377 // We have to count the "non-copied" nodes
378 SwNodeOffset nDelCount;
379 SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount));
381 SwRedlineTable::size_type n = 0;
382 rSrcDoc.getIDocumentRedlineAccess().GetRedline( *pStt, &n );
383 for( ; n < rTable.size(); ++n )
385 const SwRangeRedline* pRedl = rTable[ n ];
386 if( RedlineType::Delete == pRedl->GetType() && pRedl->IsVisible() )
388 auto [pRStt, pREnd] = pRedl->StartEnd(); // SwPosition*
390 SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd );
391 switch( eCmpPos )
393 case SwComparePosition::CollideEnd:
394 case SwComparePosition::Before:
395 // Pos1 is before Pos2
396 break;
398 case SwComparePosition::CollideStart:
399 case SwComparePosition::Behind:
400 // Pos1 is after Pos2
401 n = rTable.size();
402 break;
404 default:
406 pDelPam.reset(new SwPaM( *pCpyStt, pDelPam.release() ));
407 if( *pStt < *pRStt )
409 lcl_NonCopyCount( rPam, aCorrIdx, pRStt->GetNodeIndex(), nDelCount );
410 lcl_SetCpyPos( *pRStt, *pStt, *pCpyStt,
411 *pDelPam->GetPoint(), nDelCount );
413 pDelPam->SetMark();
415 if( *pEnd < *pREnd )
416 *pDelPam->GetPoint() = *pCpyEnd;
417 else
419 lcl_NonCopyCount( rPam, aCorrIdx, pREnd->GetNodeIndex(), nDelCount );
420 lcl_SetCpyPos( *pREnd, *pStt, *pCpyStt,
421 *pDelPam->GetPoint(), nDelCount );
424 if (pDelPam->GetNext() != pDelPam.get()
425 && *pDelPam->GetNext()->End() == *pDelPam->Start())
427 *pDelPam->GetNext()->End() = *pDelPam->End();
428 pDelPam.reset(pDelPam->GetNext());
435 if( !pDelPam )
436 return;
438 RedlineFlags eOld = rDestDoc.getIDocumentRedlineAccess().GetRedlineFlags();
439 rDestDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::Ignore );
441 ::sw::UndoGuard const undoGuard(rDestDoc.GetIDocumentUndoRedo());
443 do {
444 rDestDoc.getIDocumentContentOperations().DeleteAndJoin( *pDelPam->GetNext() );
445 if( !pDelPam->IsMultiSelection() )
446 break;
447 delete pDelPam->GetNext();
448 } while( true );
450 rDestDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
453 void lcl_DeleteRedlines( const SwNodeRange& rRg, SwNodeRange const & rCpyRg )
455 SwDoc& rSrcDoc = rRg.aStart.GetNode().GetDoc();
456 if( !rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
458 SwPaM aRgTmp( rRg.aStart, rRg.aEnd );
459 SwPaM aCpyTmp( rCpyRg.aStart, rCpyRg.aEnd );
460 lcl_DeleteRedlines( aRgTmp, aCpyTmp );
464 void lcl_ChainFormats( SwFlyFrameFormat *pSrc, SwFlyFrameFormat *pDest )
466 SwFormatChain aSrc( pSrc->GetChain() );
467 if ( !aSrc.GetNext() )
469 aSrc.SetNext( pDest );
470 pSrc->SetFormatAttr( aSrc );
472 SwFormatChain aDest( pDest->GetChain() );
473 if ( !aDest.GetPrev() )
475 aDest.SetPrev( pSrc );
476 pDest->SetFormatAttr( aDest );
480 // #i86492#
481 bool lcl_ContainsOnlyParagraphsInList( const SwPaM& rPam )
483 bool bRet = false;
485 const SwTextNode* pTextNd = rPam.Start()->GetNode().GetTextNode();
486 const SwTextNode* pEndTextNd = rPam.End()->GetNode().GetTextNode();
487 if ( pTextNd && pTextNd->IsInList() &&
488 pEndTextNd && pEndTextNd->IsInList() )
490 bRet = true;
491 SwNodeIndex aIdx(rPam.Start()->GetNode());
495 ++aIdx;
496 pTextNd = aIdx.GetNode().GetTextNode();
498 if ( !pTextNd || !pTextNd->IsInList() )
500 bRet = false;
501 break;
503 } while (pTextNd != pEndTextNd);
506 return bRet;
509 bool lcl_MarksWholeNode(const SwPaM & rPam)
511 bool bResult = false;
512 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
514 if (nullptr != pStt && nullptr != pEnd)
516 const SwTextNode* pSttNd = pStt->GetNode().GetTextNode();
517 const SwTextNode* pEndNd = pEnd->GetNode().GetTextNode();
519 if (nullptr != pSttNd && nullptr != pEndNd &&
520 pStt->GetContentIndex() == 0 &&
521 pEnd->GetContentIndex() == pEndNd->Len())
523 bResult = true;
527 return bResult;
531 //local functions originally from sw/source/core/doc/docedt.cxx
532 namespace sw
534 void CalcBreaks(std::vector<std::pair<SwNodeOffset, sal_Int32>> & rBreaks,
535 SwPaM const & rPam, bool const isOnlyFieldmarks)
537 SwNodeOffset const nStartNode(rPam.Start()->GetNodeIndex());
538 SwNodeOffset const nEndNode(rPam.End()->GetNodeIndex());
539 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
540 IDocumentMarkAccess const& rIDMA(*rPam.GetDoc().getIDocumentMarkAccess());
542 std::stack<std::tuple<sw::mark::IFieldmark const*, bool, SwNodeOffset, sal_Int32>> startedFields;
544 for (SwNodeOffset n = nStartNode; n <= nEndNode; ++n)
546 SwNode *const pNode(rNodes[n]);
547 if (pNode->IsTextNode())
549 SwTextNode & rTextNode(*pNode->GetTextNode());
550 sal_Int32 const nStart(n == nStartNode
551 ? rPam.Start()->GetContentIndex()
552 : 0);
553 sal_Int32 const nEnd(n == nEndNode
554 ? rPam.End()->GetContentIndex()
555 : rTextNode.Len());
556 for (sal_Int32 i = nStart; i < nEnd; ++i)
558 const sal_Unicode c(rTextNode.GetText()[i]);
559 switch (c)
561 // note: CH_TXT_ATR_FORMELEMENT does not need handling
562 // not sure how CH_TXT_ATR_INPUTFIELDSTART/END are currently handled
563 case CH_TXTATR_INWORD:
564 case CH_TXTATR_BREAKWORD:
566 // META hints only have dummy char at the start, not
567 // at the end, so no need to check in nStartNode
568 if (n == nEndNode && !isOnlyFieldmarks)
570 SwTextAttr const* pAttr(rTextNode.GetTextAttrForCharAt(i));
571 if (pAttr && pAttr->End() && (nEnd < *pAttr->End()))
573 assert(pAttr->HasDummyChar());
574 rBreaks.emplace_back(n, i);
577 if (!pAttr)
579 // See if this is an end dummy character for a content control.
580 pAttr = rTextNode.GetTextAttrForEndCharAt(i, RES_TXTATR_CONTENTCONTROL);
581 if (pAttr && (nStart > pAttr->GetStart()))
583 rBreaks.emplace_back(n, i);
587 break;
589 case CH_TXT_ATR_FIELDSTART:
591 auto const pFieldMark(rIDMA.getFieldmarkAt(SwPosition(rTextNode, i)));
592 startedFields.emplace(pFieldMark, false, 0, 0);
593 break;
595 case CH_TXT_ATR_FIELDSEP:
597 if (startedFields.empty())
599 rBreaks.emplace_back(n, i);
601 else
602 { // no way to find the field via MarkManager...
603 assert(std::get<0>(startedFields.top())->IsCoveringPosition(SwPosition(rTextNode, i)));
604 std::get<1>(startedFields.top()) = true;
605 std::get<2>(startedFields.top()) = n;
606 std::get<3>(startedFields.top()) = i;
608 break;
610 case CH_TXT_ATR_FIELDEND:
612 if (startedFields.empty())
614 rBreaks.emplace_back(n, i);
616 else
617 { // fieldmarks must not overlap => stack
618 assert(std::get<0>(startedFields.top()) == rIDMA.getFieldmarkAt(SwPosition(rTextNode, i)));
619 startedFields.pop();
621 break;
626 else if (pNode->IsStartNode())
628 if (pNode->EndOfSectionIndex() <= nEndNode)
629 { // fieldmark cannot overlap node section
630 n = pNode->EndOfSectionIndex();
633 else
634 { // EndNode can actually happen with sections :(
635 assert(pNode->IsEndNode() || pNode->IsNoTextNode());
638 while (!startedFields.empty())
640 if (const sw::mark::IFieldmark* pMark = std::get<0>(startedFields.top()))
642 SwPosition const& rStart(pMark->GetMarkStart());
643 std::pair<SwNodeOffset, sal_Int32> const pos(
644 rStart.GetNodeIndex(), rStart.GetContentIndex());
645 auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), pos);
646 assert(it == rBreaks.end() || *it != pos);
647 rBreaks.insert(it, pos);
649 if (std::get<1>(startedFields.top()))
651 std::pair<SwNodeOffset, sal_Int32> const posSep(
652 std::get<2>(startedFields.top()),
653 std::get<3>(startedFields.top()));
654 auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), posSep);
655 assert(it == rBreaks.end() || *it != posSep);
656 rBreaks.insert(it, posSep);
658 startedFields.pop();
663 namespace
666 bool lcl_DoWithBreaks(::sw::DocumentContentOperationsManager & rDocumentContentOperations,
667 SwPaM & rPam, SwDeleteFlags const flags,
668 bool (::sw::DocumentContentOperationsManager::*pFunc)(SwPaM&, SwDeleteFlags))
670 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
672 sw::CalcBreaks(Breaks, rPam);
674 if (Breaks.empty())
676 return (rDocumentContentOperations.*pFunc)(rPam, flags);
679 // Deletion must be split into several parts if the text node
680 // contains a text attribute with end and with dummy character
681 // and the selection does not contain the text attribute completely,
682 // but overlaps its start (left), where the dummy character is.
684 SwPosition const & rSelectionEnd( *rPam.End() );
686 bool bRet( true );
687 // iterate from end to start, to avoid invalidating the offsets!
688 auto iter( Breaks.rbegin() );
689 SwNodeOffset nOffset(0);
690 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
691 SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node!
692 SwPosition & rEnd( *aPam.End() );
693 SwPosition & rStart( *aPam.Start() );
695 while (iter != Breaks.rend())
697 rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
698 if (rStart < rEnd) // check if part is empty
700 bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags);
701 nOffset = iter->first - rStart.GetNodeIndex(); // deleted fly nodes...
703 rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
704 ++iter;
707 rStart = *rPam.Start(); // set to original start
708 if (rStart < rEnd) // check if part is empty
710 bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags);
713 return bRet;
716 bool lcl_StrLenOverflow( const SwPaM& rPam )
718 // If we try to merge two paragraphs we have to test if afterwards
719 // the string doesn't exceed the allowed string length
720 if( rPam.GetPoint()->GetNode() != rPam.GetMark()->GetNode() )
722 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
723 const SwTextNode* pEndNd = pEnd->GetNode().GetTextNode();
724 if( (nullptr != pEndNd) && pStt->GetNode().IsTextNode() )
726 const sal_uInt64 nSum = pStt->GetContentIndex() +
727 pEndNd->GetText().getLength() - pEnd->GetContentIndex();
728 return nSum > o3tl::make_unsigned(SAL_MAX_INT32);
731 return false;
734 struct SaveRedline
736 SwRangeRedline* pRedl;
737 SwNodeOffset nStt, nEnd;
738 sal_Int32 nSttCnt;
739 sal_Int32 nEndCnt;
741 SaveRedline( SwRangeRedline* pR, const SwNodeIndex& rSttIdx )
742 : pRedl(pR)
743 , nEnd(0)
744 , nEndCnt(0)
746 auto [pStt, pEnd] = pR->StartEnd(); // SwPosition*
747 SwNodeOffset nSttIdx = rSttIdx.GetIndex();
748 nStt = pStt->GetNodeIndex() - nSttIdx;
749 nSttCnt = pStt->GetContentIndex();
750 if( pR->HasMark() )
752 nEnd = pEnd->GetNodeIndex() - nSttIdx;
753 nEndCnt = pEnd->GetContentIndex();
756 pRedl->GetPoint()->Assign( SwNodeOffset(0) );
757 pRedl->GetMark()->Assign( SwNodeOffset(0) );
760 SaveRedline( SwRangeRedline* pR, const SwPosition& rPos )
761 : pRedl(pR)
762 , nEnd(0)
763 , nEndCnt(0)
765 auto [pStt, pEnd] = pR->StartEnd(); // SwPosition*
766 SwNodeOffset nSttIdx = rPos.GetNodeIndex();
767 nStt = pStt->GetNodeIndex() - nSttIdx;
768 nSttCnt = pStt->GetContentIndex();
769 if( nStt == SwNodeOffset(0) )
770 nSttCnt = nSttCnt - rPos.GetContentIndex();
771 if( pR->HasMark() )
773 nEnd = pEnd->GetNodeIndex() - nSttIdx;
774 nEndCnt = pEnd->GetContentIndex();
775 if( nEnd == SwNodeOffset(0) )
776 nEndCnt = nEndCnt - rPos.GetContentIndex();
779 pRedl->GetPoint()->Assign( SwNodeOffset(0) );
780 pRedl->GetMark()->Assign( SwNodeOffset(0) );
783 void SetPos( SwNodeOffset nInsPos )
785 pRedl->GetPoint()->Assign( nInsPos + nStt, nSttCnt );
786 if( pRedl->HasMark() )
788 pRedl->GetMark()->Assign( nInsPos + nEnd, nEndCnt );
792 void SetPos( const SwPosition& aPos )
794 pRedl->GetPoint()->Assign( aPos.GetNodeIndex() + nStt,
795 nSttCnt + ( nStt == SwNodeOffset(0) ? aPos.GetContentIndex() : 0 ) );
796 if( pRedl->HasMark() )
798 pRedl->GetMark()->Assign( aPos.GetNodeIndex() + nEnd,
799 nEndCnt + ( nEnd == SwNodeOffset(0) ? aPos.GetContentIndex() : 0 ) );
804 typedef std::vector< SaveRedline > SaveRedlines_t;
806 void lcl_SaveRedlines(const SwPaM& aPam, SaveRedlines_t& rArr)
808 SwDoc& rDoc = aPam.GetPointNode().GetDoc();
810 auto [pStart, pEnd] = aPam.StartEnd(); // SwPosition*
812 // get first relevant redline
813 SwRedlineTable::size_type nCurrentRedline;
814 rDoc.getIDocumentRedlineAccess().GetRedline( *pStart, &nCurrentRedline );
815 if( nCurrentRedline > 0)
816 nCurrentRedline--;
818 // redline mode RedlineFlags::Ignore|RedlineFlags::On; save old mode
819 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
820 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
822 // iterate over relevant redlines and decide for each whether it should
823 // be saved, or split + saved
824 SwRedlineTable& rRedlineTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
825 for( ; nCurrentRedline < rRedlineTable.size(); nCurrentRedline++ )
827 SwRangeRedline* pCurrent = rRedlineTable[ nCurrentRedline ];
828 SwComparePosition eCompare =
829 ComparePosition( *pCurrent->Start(), *pCurrent->End(),
830 *pStart, *pEnd);
832 // we must save this redline if it overlaps aPam
833 // (we may have to split it, too)
834 if( eCompare == SwComparePosition::OverlapBehind ||
835 eCompare == SwComparePosition::OverlapBefore ||
836 eCompare == SwComparePosition::Outside ||
837 eCompare == SwComparePosition::Inside ||
838 eCompare == SwComparePosition::Equal )
840 rRedlineTable.Remove( nCurrentRedline-- );
842 // split beginning, if necessary
843 if( eCompare == SwComparePosition::OverlapBefore ||
844 eCompare == SwComparePosition::Outside )
846 SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent );
847 *pNewRedline->End() = *pStart;
848 *pCurrent->Start() = *pStart;
849 rDoc.getIDocumentRedlineAccess().AppendRedline( pNewRedline, true );
852 // split end, if necessary
853 if( eCompare == SwComparePosition::OverlapBehind ||
854 eCompare == SwComparePosition::Outside )
856 SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent );
857 *pNewRedline->Start() = *pEnd;
858 *pCurrent->End() = *pEnd;
859 rDoc.getIDocumentRedlineAccess().AppendRedline( pNewRedline, true );
862 // save the current redline
863 rArr.emplace_back( pCurrent, *pStart );
867 // restore old redline mode
868 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
871 void lcl_RestoreRedlines(SwDoc& rDoc, const SwPosition& rPos, SaveRedlines_t& rArr)
873 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
874 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
876 for(SaveRedline & rSvRedLine : rArr)
878 rSvRedLine.SetPos( rPos );
879 rDoc.getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true );
882 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
885 void lcl_SaveRedlines(const SwNodeRange& rRg, SaveRedlines_t& rArr)
887 SwDoc& rDoc = rRg.aStart.GetNode().GetDoc();
888 SwRedlineTable::size_type nRedlPos;
889 SwPosition aSrchPos( rRg.aStart );
890 aSrchPos.Adjust(SwNodeOffset(-1));
891 if( rDoc.getIDocumentRedlineAccess().GetRedline( aSrchPos, &nRedlPos ) && nRedlPos )
892 --nRedlPos;
893 else if( nRedlPos >= rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() )
894 return ;
896 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
897 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
898 SwRedlineTable& rRedlTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
900 do {
901 SwRangeRedline* pTmp = rRedlTable[ nRedlPos ];
903 auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition*
905 if( pRStt->GetNode() < rRg.aStart.GetNode() )
907 if( pREnd->GetNode() > rRg.aStart.GetNode() && pREnd->GetNode() < rRg.aEnd.GetNode() )
909 // Create a copy and set the end of the original to the end of the MoveArea.
910 // The copy is moved too.
911 SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp );
912 SwPosition* pTmpPos = pNewRedl->Start();
913 pTmpPos->Assign(rRg.aStart);
915 rArr.emplace_back(pNewRedl, rRg.aStart);
917 pTmpPos = pTmp->End();
918 pTmpPos->Assign(rRg.aEnd);
920 else if( pREnd->GetNode() == rRg.aStart.GetNode() )
922 SwPosition* pTmpPos = pTmp->End();
923 pTmpPos->Assign(rRg.aEnd);
926 else if( pRStt->GetNode() < rRg.aEnd.GetNode() )
928 rRedlTable.Remove( nRedlPos-- );
929 if( pREnd->GetNode() < rRg.aEnd.GetNode() ||
930 ( pREnd->GetNode() == rRg.aEnd.GetNode() && !pREnd->GetContentIndex()) )
932 // move everything
933 rArr.emplace_back( pTmp, rRg.aStart );
935 else
937 // split
938 SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp );
939 SwPosition* pTmpPos = pNewRedl->End();
940 pTmpPos->Assign(rRg.aEnd);
942 rArr.emplace_back( pNewRedl, rRg.aStart );
944 pTmpPos = pTmp->Start();
945 pTmpPos->Assign(rRg.aEnd);
946 rDoc.getIDocumentRedlineAccess().AppendRedline( pTmp, true );
949 else
950 break;
952 } while( ++nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() );
953 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
956 void lcl_RestoreRedlines(SwDoc& rDoc, SwNodeOffset const nInsPos, SaveRedlines_t& rArr)
958 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
959 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
961 for(SaveRedline & rSvRedLine : rArr)
963 rSvRedLine.SetPos( nInsPos );
964 IDocumentRedlineAccess::AppendResult const result(
965 rDoc.getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ));
966 if ( IDocumentRedlineAccess::AppendResult::APPENDED == result &&
967 rSvRedLine.pRedl->GetType() == RedlineType::Delete )
969 UpdateFramesForAddDeleteRedline(rDoc, *rSvRedLine.pRedl);
973 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
976 bool lcl_SaveFootnote( const SwNode& rSttNd, const SwNode& rEndNd,
977 const SwNode& rInsPos,
978 SwFootnoteIdxs& rFootnoteArr, SwFootnoteIdxs& rSaveArr,
979 std::optional<sal_Int32> oSttCnt = std::nullopt, std::optional<sal_Int32> oEndCnt = std::nullopt )
981 bool bUpdateFootnote = false;
982 const SwNodes& rNds = rInsPos.GetNodes();
983 const bool bDelFootnote = rInsPos.GetIndex() < rNds.GetEndOfAutotext().GetIndex() &&
984 rSttNd.GetIndex() >= rNds.GetEndOfAutotext().GetIndex();
985 const bool bSaveFootnote = !bDelFootnote &&
986 rInsPos.GetIndex() >= rNds.GetEndOfExtras().GetIndex();
987 if( !rFootnoteArr.empty() )
990 size_t nPos = 0;
991 rFootnoteArr.SeekEntry( rSttNd, &nPos );
992 SwTextFootnote* pSrch;
993 const SwNode* pFootnoteNd;
995 // Delete/save all that come after it
996 while( nPos < rFootnoteArr.size() && ( pFootnoteNd =
997 &( pSrch = rFootnoteArr[ nPos ] )->GetTextNode())->GetIndex()
998 <= rEndNd.GetIndex() )
1000 const sal_Int32 nFootnoteSttIdx = pSrch->GetStart();
1001 if( ( oEndCnt && oSttCnt )
1002 ? (( &rSttNd == pFootnoteNd &&
1003 *oSttCnt > nFootnoteSttIdx) ||
1004 ( &rEndNd == pFootnoteNd &&
1005 nFootnoteSttIdx >= *oEndCnt ))
1006 : ( &rEndNd == pFootnoteNd ))
1008 ++nPos; // continue searching
1010 else
1012 // delete it
1013 if( bDelFootnote )
1015 SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode());
1016 SwContentIndex aIdx( &rTextNd, nFootnoteSttIdx );
1017 rTextNd.EraseText( aIdx, 1 );
1019 else
1021 pSrch->DelFrames(nullptr);
1022 rFootnoteArr.erase( rFootnoteArr.begin() + nPos );
1023 if( bSaveFootnote )
1024 rSaveArr.insert( pSrch );
1026 bUpdateFootnote = true;
1030 while( nPos-- && ( pFootnoteNd = &( pSrch = rFootnoteArr[ nPos ] )->
1031 GetTextNode())->GetIndex() >= rSttNd.GetIndex() )
1033 const sal_Int32 nFootnoteSttIdx = pSrch->GetStart();
1034 if( !oEndCnt || !oSttCnt ||
1035 ! (( &rSttNd == pFootnoteNd &&
1036 *oSttCnt > nFootnoteSttIdx ) ||
1037 ( &rEndNd == pFootnoteNd &&
1038 nFootnoteSttIdx >= *oEndCnt )) )
1040 if( bDelFootnote )
1042 // delete it
1043 SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode());
1044 SwContentIndex aIdx( &rTextNd, nFootnoteSttIdx );
1045 rTextNd.EraseText( aIdx, 1 );
1047 else
1049 pSrch->DelFrames(nullptr);
1050 rFootnoteArr.erase( rFootnoteArr.begin() + nPos );
1051 if( bSaveFootnote )
1052 rSaveArr.insert( pSrch );
1054 bUpdateFootnote = true;
1058 // When moving from redline section into document content section, e.g.
1059 // after loading a document with (delete-)redlines, the footnote array
1060 // has to be adjusted... (#i70572)
1061 if( bSaveFootnote )
1063 SwNodeIndex aIdx( rSttNd );
1064 while( aIdx < rEndNd ) // Check the moved section
1066 SwNode* pNode = &aIdx.GetNode();
1067 if( pNode->IsTextNode() ) // Looking for text nodes...
1069 SwpHints *pHints = pNode->GetTextNode()->GetpSwpHints();
1070 if( pHints && pHints->HasFootnote() ) //...with footnotes
1072 bUpdateFootnote = true; // Heureka
1073 const size_t nCount = pHints->Count();
1074 for( size_t i = 0; i < nCount; ++i )
1076 SwTextAttr *pAttr = pHints->Get( i );
1077 if ( pAttr->Which() == RES_TXTATR_FTN )
1079 rSaveArr.insert( static_cast<SwTextFootnote*>(pAttr) );
1084 ++aIdx;
1087 return bUpdateFootnote;
1090 bool lcl_MayOverwrite( const SwTextNode *pNode, const sal_Int32 nPos )
1092 sal_Unicode const cChr = pNode->GetText()[nPos];
1093 switch (cChr)
1095 case CH_TXTATR_BREAKWORD:
1096 case CH_TXTATR_INWORD:
1097 return !pNode->GetTextAttrForCharAt(nPos);// how could there be none?
1098 case CH_TXT_ATR_INPUTFIELDSTART:
1099 case CH_TXT_ATR_INPUTFIELDEND:
1100 case CH_TXT_ATR_FIELDSTART:
1101 case CH_TXT_ATR_FIELDSEP:
1102 case CH_TXT_ATR_FIELDEND:
1103 case CH_TXT_ATR_FORMELEMENT:
1104 return false;
1105 default:
1106 return true;
1110 void lcl_SkipAttr( const SwTextNode *pNode, SwPosition &rIdx, sal_Int32 &rStart )
1112 if( !lcl_MayOverwrite( pNode, rStart ) )
1114 // skip all special attributes
1115 do {
1116 rIdx.AdjustContent(+1);
1117 rStart = rIdx.GetContentIndex();
1118 } while (rStart < pNode->GetText().getLength()
1119 && !lcl_MayOverwrite(pNode, rStart) );
1123 bool lcl_GetTokenToParaBreak( OUString& rStr, OUString& rRet, bool bRegExpRplc )
1125 if( bRegExpRplc )
1127 sal_Int32 nPos = 0;
1128 static const OUStringLiteral sPara(u"\\n");
1129 for (;;)
1131 nPos = rStr.indexOf( sPara, nPos );
1132 if (nPos<0)
1134 break;
1136 // Has this been escaped?
1137 if( nPos && '\\' == rStr[nPos-1])
1139 ++nPos;
1140 if( nPos >= rStr.getLength() )
1142 break;
1145 else
1147 rRet = rStr.copy( 0, nPos );
1148 rStr = rStr.copy( nPos + sPara.getLength() );
1149 return true;
1153 rRet = rStr;
1154 rStr.clear();
1155 return false;
1159 namespace //local functions originally from docfmt.cxx
1162 bool lcl_ApplyOtherSet(
1163 SwContentNode & rNode,
1164 SwHistory *const pHistory,
1165 SfxItemSet const& rOtherSet,
1166 SfxItemSet const& rFirstSet,
1167 SfxItemSet const& rPropsSet,
1168 SwRootFrame const*const pLayout,
1169 SwNodeIndex *const o_pIndex = nullptr)
1171 assert(rOtherSet.Count());
1173 bool ret(false);
1174 SwTextNode *const pTNd = rNode.GetTextNode();
1175 sw::MergedPara const* pMerged(nullptr);
1176 if (pLayout && pLayout->HasMergedParas() && pTNd)
1178 SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(
1179 pTNd->getLayoutFrame(pLayout)));
1180 if (pTextFrame)
1182 pMerged = pTextFrame->GetMergedPara();
1184 if (pMerged)
1186 if (rFirstSet.Count())
1188 if (pHistory)
1190 SwRegHistory aRegH(pMerged->pFirstNode, *pMerged->pFirstNode, pHistory);
1191 ret = pMerged->pFirstNode->SetAttr(rFirstSet);
1193 else
1195 ret = pMerged->pFirstNode->SetAttr(rFirstSet);
1198 if (rPropsSet.Count())
1200 if (pHistory)
1202 SwRegHistory aRegH(pMerged->pParaPropsNode, *pMerged->pParaPropsNode, pHistory);
1203 ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret;
1205 else
1207 ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret;
1210 if (o_pIndex)
1212 *o_pIndex = *pMerged->pLastNode; // skip hidden
1217 // input cursor can't be on hidden node, and iteration skips them
1218 assert(!pLayout || !pLayout->HasMergedParas()
1219 || rNode.GetRedlineMergeFlag() != SwNode::Merge::Hidden);
1221 if (!pMerged)
1223 if (pHistory)
1225 SwRegHistory aRegH(&rNode, rNode, pHistory);
1226 ret = rNode.SetAttr( rOtherSet );
1228 else
1230 ret = rNode.SetAttr( rOtherSet );
1233 return ret;
1236 #define DELETECHARSETS if ( bDelete ) { delete pCharSet; delete pOtherSet; }
1238 // set format redline with extra data for lcl_InsAttr()
1239 void lcl_SetRedline(
1240 SwDoc& rDoc,
1241 const SwPaM &rRg)
1243 std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra;
1245 // check existing redline on the same range, and use its extra data, if it exists
1246 SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos(
1247 rRg.Start()->GetNode(), RedlineType::Format );
1248 if( SwRedlineTable::npos != nRedlPos )
1250 const SwPosition *pRStt, *pREnd;
1251 do {
1252 SwRangeRedline* pTmp = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
1253 pRStt = pTmp->Start();
1254 pREnd = pTmp->End();
1255 SwComparePosition eCompare = ComparePosition( *rRg.Start(), *rRg.End(), *pRStt, *pREnd );
1256 if ( eCompare == SwComparePosition::Inside || eCompare == SwComparePosition::Equal )
1258 if (pTmp->GetExtraData())
1260 const SwRedlineExtraData* pExtraData = pTmp->GetExtraData();
1261 const SwRedlineExtraData_FormatColl* pFormattingChanges =
1262 dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
1263 // Check if the extra data is of type 'formatting changes'
1264 if (pFormattingChanges)
1266 // Get the item set that holds all the changes properties
1267 const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
1268 xExtra.reset(new SwRedlineExtraData_FormatColl("", USHRT_MAX, pChangesSet));
1269 break;
1273 } while( pRStt <= rRg.Start() && ++nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size());
1276 SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::Format, rRg );
1277 auto const result(rDoc.getIDocumentRedlineAccess().AppendRedline( pRedline, true));
1278 // store original text attributes to reject formatting change
1279 if (IDocumentRedlineAccess::AppendResult::IGNORED == result)
1280 return;
1282 // no existing format redline in the range
1283 if (!xExtra)
1285 // Apply the first character's attributes to the ReplaceText
1286 SfxItemSetFixed<RES_CHRATR_BEGIN, RES_TXTATR_WITHEND_END - 1,
1287 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1> aSet( rDoc.GetAttrPool() );
1288 SwTextNode * pNode = rRg.Start()->GetNode().GetTextNode();
1289 pNode->GetParaAttr( aSet, rRg.Start()->GetContentIndex() + 1, rRg.End()->GetContentIndex() );
1291 aSet.ClearItem( RES_TXTATR_REFMARK );
1292 aSet.ClearItem( RES_TXTATR_TOXMARK );
1293 aSet.ClearItem( RES_TXTATR_CJK_RUBY );
1294 aSet.ClearItem( RES_TXTATR_INETFMT );
1295 aSet.ClearItem( RES_TXTATR_META );
1296 aSet.ClearItem( RES_TXTATR_METAFIELD );
1298 // After GetParaAttr aSet can contain INVALID_POOL_ITEM items, e.g. RES_TXTATR_CHARFMT
1299 // and (a copy of) this SfxItemSet can be passed to MSWordExportBase::OutputItemSet
1300 // which doesn't handle INVALID_POOL_ITEM items so clear InvalidItems here
1301 aSet.ClearInvalidItems();
1303 xExtra.reset(new SwRedlineExtraData_FormatColl("", USHRT_MAX, &aSet));
1306 if (xExtra)
1308 pRedline->SetExtraData(xExtra.get() );
1312 // create format redline(s) for the given range:
1313 // to track the original formatting stored in the
1314 // hints, create redlines for all parts of the
1315 // range partitioned by boundaries of the hints.
1316 void lcl_SetRedlines(
1317 SwDoc& rDoc,
1318 const SwPaM &rRg)
1320 SwNodeIndex aIdx( rRg.Start()->GetNode() );
1321 const SwNodeIndex aEndNd( rRg.End()->GetNode() );
1322 while( aIdx <= aEndNd )
1324 SwTextNode *pNode = aIdx.GetNode().GetTextNode();
1325 if( pNode )
1327 const sal_Int32 nStart = aIdx == rRg.Start()->GetNode()
1328 ? rRg.Start()->GetContentIndex()
1329 : 0;
1330 const sal_Int32 nEnd = aIdx < aEndNd
1331 ? pNode->GetText().getLength()
1332 : rRg.End()->GetContentIndex();
1334 if( SwpHints *pHints = pNode->GetpSwpHints() )
1336 const size_t nCount = pHints->Count();
1337 sal_Int32 nRedEnd = nStart;
1338 for( size_t i = 0; i < nCount; ++i )
1340 SwTextAttr *pAttr = pHints->Get( i );
1342 if ( pAttr->GetStart() > nEnd )
1344 break; // after the range
1347 if ( !pAttr->GetEnd() || *pAttr->GetEnd() < nStart )
1349 continue; // before the range
1352 // range part before the hint
1353 if ( nRedEnd < pAttr->GetStart() )
1355 SwPaM aPam( *pNode, nRedEnd, *pNode, pAttr->GetStart() );
1356 lcl_SetRedline(rDoc, aPam);
1359 // range part at the hint
1360 sal_Int32 nRedStart = std::max(pAttr->GetStart(), nStart);
1361 nRedEnd = std::min(*pAttr->GetEnd(), nEnd);
1362 SwPaM aPam2( *pNode, nRedStart, *pNode, nRedEnd );
1363 lcl_SetRedline(rDoc, aPam2);
1366 // range part after the last hint
1367 if ( nRedEnd < nEnd )
1369 SwPaM aPam( *pNode, nRedEnd, *pNode, nEnd );
1370 lcl_SetRedline(rDoc, aPam);
1373 else
1375 SwPaM aPam( *pNode, nStart, *pNode, nEnd );
1376 lcl_SetRedline(rDoc, aPam);
1379 ++aIdx;
1383 /// Insert Hints according to content types;
1384 // Is used in SwDoc::Insert(..., SwFormatHint &rHt)
1386 bool lcl_InsAttr(
1387 SwDoc& rDoc,
1388 const SwPaM &rRg,
1389 const SfxItemSet& rChgSet,
1390 const SetAttrMode nFlags,
1391 SwUndoAttr *const pUndo,
1392 SwRootFrame const*const pLayout,
1393 SwTextAttr **ppNewTextAttr)
1395 // Divide the Sets (for selections in Nodes)
1396 const SfxItemSet* pCharSet = nullptr;
1397 const SfxItemSet* pOtherSet = nullptr;
1398 bool bDelete = false;
1399 bool bCharAttr = false;
1400 bool bOtherAttr = false;
1402 // Check, if we can work with rChgSet or if we have to create additional SfxItemSets
1403 if ( 1 == rChgSet.Count() )
1405 SfxItemIter aIter( rChgSet );
1406 const SfxPoolItem* pItem = aIter.GetCurItem();
1407 if (pItem && !IsInvalidItem(pItem))
1409 const sal_uInt16 nWhich = pItem->Which();
1411 if ( isCHRATR(nWhich) ||
1412 (RES_TXTATR_CHARFMT == nWhich) ||
1413 (RES_TXTATR_INETFMT == nWhich) ||
1414 (RES_TXTATR_AUTOFMT == nWhich) ||
1415 (RES_TXTATR_UNKNOWN_CONTAINER == nWhich) )
1417 pCharSet = &rChgSet;
1418 bCharAttr = true;
1421 if ( isPARATR(nWhich)
1422 || isPARATR_LIST(nWhich)
1423 || isFRMATR(nWhich)
1424 || isGRFATR(nWhich)
1425 || isUNKNOWNATR(nWhich)
1426 || isDrawingLayerAttribute(nWhich) )
1428 pOtherSet = &rChgSet;
1429 bOtherAttr = true;
1434 // Build new itemset if either
1435 // - rChgSet.Count() > 1 or
1436 // - The attribute in rChgSet does not belong to one of the above categories
1437 if ( !bCharAttr && !bOtherAttr )
1439 SfxItemSet* pTmpCharItemSet = new SfxItemSetFixed<
1440 RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
1441 RES_TXTATR_AUTOFMT, RES_TXTATR_CHARFMT,
1442 RES_TXTATR_UNKNOWN_CONTAINER,
1443 RES_TXTATR_UNKNOWN_CONTAINER>( rDoc.GetAttrPool() );
1445 SfxItemSet* pTmpOtherItemSet = new SfxItemSetFixed<
1446 RES_PARATR_BEGIN, RES_GRFATR_END - 1,
1447 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1,
1448 // FillAttribute support:
1449 XATTR_FILL_FIRST, XATTR_FILL_LAST>( rDoc.GetAttrPool() );
1451 pTmpCharItemSet->Put( rChgSet );
1452 pTmpOtherItemSet->Put( rChgSet );
1454 pCharSet = pTmpCharItemSet;
1455 pOtherSet = pTmpOtherItemSet;
1457 bDelete = true;
1460 SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr;
1461 bool bRet = false;
1462 const SwPosition *pStt = rRg.Start(), *pEnd = rRg.End();
1463 SwContentNode* pNode = pStt->GetNode().GetContentNode();
1465 if( pNode && pNode->IsTextNode() )
1467 // tdf#127606 at editing, remove different formatting of DOCX-like numbering symbol
1468 if (pLayout && pNode->GetTextNode()->getIDocumentSettingAccess()->
1469 get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING ))
1471 SwContentNode* pEndNode = pEnd->GetNode().GetContentNode();
1472 SwContentNode* pCurrentNode = pEndNode;
1473 auto nStartIndex = pNode->GetIndex();
1474 auto nEndIndex = pEndNode->GetIndex();
1475 SwNodeIndex aIdx( pEnd->GetNode() );
1476 while ( pCurrentNode != nullptr && nStartIndex <= pCurrentNode->GetIndex() )
1478 if (pCurrentNode->GetSwAttrSet().HasItem(RES_PARATR_LIST_AUTOFMT) &&
1479 // remove character formatting only on wholly selected paragraphs
1480 (nStartIndex < pCurrentNode->GetIndex() || pStt->GetContentIndex() == 0) &&
1481 (pCurrentNode->GetIndex() < nEndIndex || pEnd->GetContentIndex() == pEndNode->Len()))
1483 pCurrentNode->ResetAttr(RES_PARATR_LIST_AUTOFMT);
1484 // reset also paragraph marker
1485 pCurrentNode->GetTextNode()->RstTextAttr(pCurrentNode->Len(), 1);
1487 pCurrentNode = SwNodes::GoPrevious( &aIdx );
1490 // #i27615#
1491 if (rRg.IsInFrontOfLabel())
1493 SwTextNode * pTextNd = pNode->GetTextNode();
1494 if (pLayout)
1496 pTextNd = sw::GetParaPropsNode(*pLayout, *pTextNd);
1498 SwNumRule * pNumRule = pTextNd->GetNumRule();
1500 if ( !pNumRule )
1502 OSL_FAIL( "<InsAttr(..)> - PaM in front of label, but text node has no numbering rule set. This is a serious defect." );
1503 DELETECHARSETS
1504 return false;
1507 int nLevel = pTextNd->GetActualListLevel();
1509 if (nLevel < 0)
1510 nLevel = 0;
1512 if (nLevel >= MAXLEVEL)
1513 nLevel = MAXLEVEL - 1;
1515 SwNumFormat aNumFormat = pNumRule->Get(o3tl::narrowing<sal_uInt16>(nLevel));
1516 SwCharFormat * pCharFormat =
1517 rDoc.FindCharFormatByName(aNumFormat.GetCharFormatName());
1519 if (pCharFormat)
1521 if (pHistory)
1522 pHistory->Add(pCharFormat->GetAttrSet(), *pCharFormat);
1524 if ( pCharSet )
1525 pCharFormat->SetFormatAttr(*pCharSet);
1528 DELETECHARSETS
1529 return true;
1532 // Attributes without an end do not have a range
1533 if ( !bCharAttr && !bOtherAttr )
1535 SfxItemSetFixed<RES_TXTATR_NOEND_BEGIN, RES_TXTATR_NOEND_END-1>
1536 aTextSet( rDoc.GetAttrPool() );
1537 aTextSet.Put( rChgSet );
1538 if( aTextSet.Count() )
1540 SwRegHistory history( pNode, *pNode, pHistory );
1541 bRet = history.InsertItems(
1542 aTextSet, pStt->GetContentIndex(), pStt->GetContentIndex(), nFlags, /*ppNewTextAttr*/nullptr ) || bRet;
1544 if (bRet && (rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
1545 && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())))
1547 SwPaM aPam( pStt->GetNode(), pStt->GetContentIndex()-1,
1548 pStt->GetNode(), pStt->GetContentIndex() );
1550 if( pUndo )
1551 pUndo->SaveRedlineData( aPam, true );
1553 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1554 rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
1555 else
1556 rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
1561 // TextAttributes with an end never expand their range
1562 if ( !bCharAttr && !bOtherAttr )
1564 // CharFormat and URL attributes are treated separately!
1565 // TEST_TEMP ToDo: AutoFormat!
1566 SfxItemSetFixed<
1567 RES_TXTATR_REFMARK, RES_TXTATR_METAFIELD,
1568 RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY,
1569 RES_TXTATR_INPUTFIELD, RES_TXTATR_CONTENTCONTROL>
1570 aTextSet(rDoc.GetAttrPool());
1572 aTextSet.Put( rChgSet );
1573 if( aTextSet.Count() )
1575 const sal_Int32 nInsCnt = pStt->GetContentIndex();
1576 const sal_Int32 nEnd = pStt->GetNode() == pEnd->GetNode()
1577 ? pEnd->GetContentIndex()
1578 : pNode->Len();
1579 SwRegHistory history( pNode, *pNode, pHistory );
1580 bRet = history.InsertItems( aTextSet, nInsCnt, nEnd, nFlags, ppNewTextAttr )
1581 || bRet;
1583 if (bRet && (rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
1584 && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())))
1586 // Was text content inserted? (RefMark/TOXMarks without an end)
1587 bool bTextIns = nInsCnt != pStt->GetContentIndex();
1588 // Was content inserted or set over the selection?
1589 SwPaM aPam( pStt->GetNode(), bTextIns ? nInsCnt + 1 : nEnd,
1590 pStt->GetNode(), nInsCnt );
1591 if( pUndo )
1592 pUndo->SaveRedlineData( aPam, bTextIns );
1594 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1595 rDoc.getIDocumentRedlineAccess().AppendRedline(
1596 new SwRangeRedline(
1597 bTextIns ? RedlineType::Insert : RedlineType::Format, aPam ),
1598 true);
1599 else if( bTextIns )
1600 rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
1606 // We always have to set the auto flag for PageDescs that are set at the Node!
1607 if( pOtherSet && pOtherSet->Count() )
1609 SwTableNode* pTableNd;
1610 const SwFormatPageDesc* pDesc = pOtherSet->GetItemIfSet( RES_PAGEDESC, false );
1611 if( pDesc )
1613 if( pNode )
1615 // Set auto flag. Only in the template it's without auto!
1616 SwFormatPageDesc aNew( *pDesc );
1618 // Tables now also know line breaks
1619 if( !(nFlags & SetAttrMode::APICALL) &&
1620 nullptr != ( pTableNd = pNode->FindTableNode() ) )
1622 SwTableNode* pCurTableNd = pTableNd;
1623 while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) )
1624 pTableNd = pCurTableNd;
1626 // set the table format
1627 SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat();
1628 SwRegHistory aRegH( pFormat, *pTableNd, pHistory );
1629 pFormat->SetFormatAttr( aNew );
1630 bRet = true;
1632 else
1634 SwContentNode * pFirstNode(pNode);
1635 if (pLayout && pLayout->HasMergedParas())
1637 pFirstNode = sw::GetFirstAndLastNode(*pLayout, pStt->GetNode()).first;
1639 SwRegHistory aRegH( pFirstNode, *pFirstNode, pHistory );
1640 bRet = pFirstNode->SetAttr( aNew ) || bRet;
1644 // bOtherAttr = true means that pOtherSet == rChgSet. In this case
1645 // we know, that there is only one attribute in pOtherSet. We cannot
1646 // perform the following operations, instead we return:
1647 if ( bOtherAttr )
1648 return bRet;
1650 const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_PAGEDESC );
1651 if( !pOtherSet->Count() )
1653 DELETECHARSETS
1654 return bRet;
1658 // Tables now also know line breaks
1659 const SvxFormatBreakItem* pBreak;
1660 if( pNode && !(nFlags & SetAttrMode::APICALL) &&
1661 nullptr != (pTableNd = pNode->FindTableNode() ) &&
1662 (pBreak = pOtherSet->GetItemIfSet( RES_BREAK, false )) )
1664 SwTableNode* pCurTableNd = pTableNd;
1665 while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) )
1666 pTableNd = pCurTableNd;
1668 // set the table format
1669 SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat();
1670 SwRegHistory aRegH( pFormat, *pTableNd, pHistory );
1671 pFormat->SetFormatAttr( *pBreak );
1672 bRet = true;
1674 // bOtherAttr = true means that pOtherSet == rChgSet. In this case
1675 // we know, that there is only one attribute in pOtherSet. We cannot
1676 // perform the following operations, instead we return:
1677 if ( bOtherAttr )
1678 return bRet;
1680 const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_BREAK );
1681 if( !pOtherSet->Count() )
1683 DELETECHARSETS
1684 return bRet;
1689 // If we have a PoolNumRule, create it if needed
1690 sal_uInt16 nPoolId=0;
1691 const SwNumRuleItem* pRule = pOtherSet->GetItemIfSet( RES_PARATR_NUMRULE, false );
1692 if( pRule &&
1693 !rDoc.FindNumRulePtr( pRule->GetValue() ) &&
1694 USHRT_MAX != (nPoolId = SwStyleNameMapper::GetPoolIdFromUIName ( pRule->GetValue(),
1695 SwGetPoolIdFromName::NumRule )) )
1696 rDoc.getIDocumentStylePoolAccess().GetNumRuleFromPool( nPoolId );
1700 SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> firstSet(rDoc.GetAttrPool());
1701 if (pOtherSet && pOtherSet->Count())
1702 { // actually only RES_BREAK is possible here...
1703 firstSet.Put(*pOtherSet);
1705 SfxItemSetFixed
1706 <RES_PARATR_BEGIN, RES_PAGEDESC,
1707 RES_BREAK+1, RES_FRMATR_END,
1708 XATTR_FILL_FIRST, XATTR_FILL_LAST+1> propsSet(rDoc.GetAttrPool());
1709 if (pOtherSet && pOtherSet->Count())
1711 propsSet.Put(*pOtherSet);
1714 if( !rRg.HasMark() ) // no range
1716 if( !pNode )
1718 DELETECHARSETS
1719 return bRet;
1722 if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1724 SwTextNode* pTextNd = pNode->GetTextNode();
1725 sal_Int32 nMkPos, nPtPos = pStt->GetContentIndex();
1726 const OUString& rStr = pTextNd->GetText();
1728 // Special case: if the Cursor is located within a URL attribute, we take over it's area
1729 SwTextAttr const*const pURLAttr(
1730 pTextNd->GetTextAttrAt(pStt->GetContentIndex(), RES_TXTATR_INETFMT));
1731 if (pURLAttr && !pURLAttr->GetINetFormat().GetValue().isEmpty())
1733 nMkPos = pURLAttr->GetStart();
1734 nPtPos = *pURLAttr->End();
1736 else
1738 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
1739 Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
1740 pTextNd->GetText(), nPtPos,
1741 g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
1742 WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/,
1743 true);
1745 if( aBndry.startPos < nPtPos && nPtPos < aBndry.endPos )
1747 nMkPos = aBndry.startPos;
1748 nPtPos = aBndry.endPos;
1750 else
1751 nPtPos = nMkPos = pStt->GetContentIndex();
1754 // Remove the overriding attributes from the SwpHintsArray,
1755 // if the selection spans across the whole paragraph.
1756 // These attributes are inserted as FormatAttributes and
1757 // never override the TextAttributes!
1758 if( !(nFlags & SetAttrMode::DONTREPLACE ) &&
1759 pTextNd->HasHints() && !nMkPos && nPtPos == rStr.getLength())
1761 if( pHistory )
1763 // Save all attributes for the Undo.
1764 SwRegHistory aRHst( *pTextNd, pHistory );
1765 pTextNd->GetpSwpHints()->Register( &aRHst );
1766 pTextNd->RstTextAttr( 0, nPtPos, 0, pCharSet );
1767 if( pTextNd->GetpSwpHints() )
1768 pTextNd->GetpSwpHints()->DeRegister();
1770 else
1771 pTextNd->RstTextAttr( 0, nPtPos, 0, pCharSet );
1774 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1776 SwPaM aPam( *pNode, nMkPos, *pNode, nPtPos );
1778 if( pUndo )
1779 pUndo->SaveRedlineData( aPam, false );
1781 lcl_SetRedlines(rDoc, aPam);
1784 // the SwRegHistory inserts the attribute into the TextNode!
1785 SwRegHistory history( pNode, *pNode, pHistory );
1787 bRet = history.InsertItems( *pCharSet, nMkPos, nPtPos, nFlags, /*ppNewTextAttr*/nullptr )
1788 || bRet;
1791 if( pOtherSet && pOtherSet->Count() )
1793 // Need to check for unique item for DrawingLayer items of type NameOrIndex
1794 // and evtl. correct that item to ensure unique names for that type. This call may
1795 // modify/correct entries inside of the given SfxItemSet
1796 SfxItemSet aTempLocalCopy(*pOtherSet);
1798 rDoc.CheckForUniqueItemForLineFillNameOrIndex(aTempLocalCopy);
1799 bRet = lcl_ApplyOtherSet(*pNode, pHistory, aTempLocalCopy, firstSet, propsSet, pLayout) || bRet;
1802 DELETECHARSETS
1803 return bRet;
1806 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() && pCharSet && pCharSet->Count() )
1808 if( pUndo )
1809 pUndo->SaveRedlineData( rRg, false );
1811 lcl_SetRedlines(rDoc, rRg);
1814 /* now if range */
1815 sal_uLong nNodes = 0;
1817 SwNodeIndex aSt( rDoc.GetNodes() );
1818 SwNodeIndex aEnd( rDoc.GetNodes() );
1819 SwContentIndex aCntEnd( pEnd->GetContentNode(), pEnd->GetContentIndex() );
1821 if( pNode )
1823 const sal_Int32 nLen = pNode->Len();
1824 if( pStt->GetNode() != pEnd->GetNode() )
1825 aCntEnd.Assign( pNode, nLen );
1827 if( pStt->GetContentIndex() != 0 || aCntEnd.GetIndex() != nLen )
1829 // the SwRegHistory inserts the attribute into the TextNode!
1830 if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1832 SwRegHistory history( pNode, *pNode, pHistory );
1833 bRet = history.InsertItems(*pCharSet,
1834 pStt->GetContentIndex(), aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr)
1835 || bRet;
1838 if( pOtherSet && pOtherSet->Count() )
1840 bRet = lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout) || bRet;
1843 // Only selection in a Node.
1844 if( pStt->GetNode() == pEnd->GetNode() )
1846 DELETECHARSETS
1847 return bRet;
1849 ++nNodes;
1850 aSt.Assign( pStt->GetNode(), +1 );
1852 else
1853 aSt = pStt->GetNode();
1854 aCntEnd.Assign(pEnd->GetContentNode(), pEnd->GetContentIndex()); // aEnd was changed!
1856 else
1857 aSt.Assign( pStt->GetNode(), +1 );
1859 // aSt points to the first full Node now
1862 * The selection spans more than one Node.
1864 if( pStt->GetNode() < pEnd->GetNode() )
1866 pNode = pEnd->GetNode().GetContentNode();
1867 if(pNode)
1869 if( aCntEnd.GetIndex() != pNode->Len() )
1871 // the SwRegHistory inserts the attribute into the TextNode!
1872 if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1874 SwRegHistory history( pNode, *pNode, pHistory );
1875 (void)history.InsertItems(*pCharSet,
1876 0, aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr);
1879 if( pOtherSet && pOtherSet->Count() )
1881 lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout);
1884 ++nNodes;
1885 aEnd = pEnd->GetNode();
1887 else
1888 aEnd.Assign( pEnd->GetNode(), +1 );
1890 else
1891 aEnd = pEnd->GetNode();
1893 else
1894 aEnd.Assign( pEnd->GetNode(), +1 );
1896 // aEnd points BEHIND the last full node now
1898 /* Edit the fully selected Nodes. */
1899 // Reset all attributes from the set!
1900 if( pCharSet && pCharSet->Count() && !( SetAttrMode::DONTREPLACE & nFlags ) )
1902 ::sw::DocumentContentOperationsManager::ParaRstFormat aPara(
1903 pStt, pEnd, pHistory, pCharSet, pLayout);
1904 rDoc.GetNodes().ForEach( aSt, aEnd, ::sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara );
1907 bool bCreateSwpHints = pCharSet && (
1908 SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_CHARFMT, false ) ||
1909 SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_INETFMT, false ) );
1911 for (SwNodeIndex current = aSt; current < aEnd; ++current)
1913 SwTextNode *const pTNd = current.GetNode().GetTextNode();
1914 if (!pTNd)
1915 continue;
1917 if (pLayout && pLayout->HasMergedParas()
1918 && pTNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)
1919 { // not really sure what to do here, but applying to hidden
1920 continue; // nodes doesn't make sense...
1923 if( pHistory )
1925 SwRegHistory aRegH( pTNd, *pTNd, pHistory );
1927 if (pCharSet && pCharSet->Count())
1929 if (SwpHints *pSwpHints = bCreateSwpHints ? &pTNd->GetOrCreateSwpHints()
1930 : pTNd->GetpSwpHints())
1932 pSwpHints->Register( &aRegH );
1935 pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags);
1937 // re-fetch as it may be deleted by SetAttr
1938 if (SwpHints *pSwpHints = pTNd->GetpSwpHints())
1939 pSwpHints->DeRegister();
1942 else
1944 if (pCharSet && pCharSet->Count())
1945 pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags);
1947 ++nNodes;
1950 if (pOtherSet && pOtherSet->Count())
1952 for (; aSt < aEnd; ++aSt)
1954 pNode = aSt.GetNode().GetContentNode();
1955 if (!pNode)
1956 continue;
1958 lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout, &aSt);
1959 ++nNodes;
1963 DELETECHARSETS
1964 return (nNodes != 0) || bRet;
1968 namespace sw
1971 namespace mark
1973 bool IsFieldmarkOverlap(SwPaM const& rPaM)
1975 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
1976 sw::CalcBreaks(Breaks, rPaM);
1977 return !Breaks.empty();
1981 DocumentContentOperationsManager::DocumentContentOperationsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc )
1986 * Checks if rStart..rEnd mark a range that makes sense to copy.
1988 * IsMoveToFly means the copy is a move to create a fly
1989 * and so existing flys at the edge must not be copied.
1991 static bool IsEmptyRange(const SwPosition& rStart, const SwPosition& rEnd,
1992 SwCopyFlags const flags)
1994 if (rStart == rEnd)
1995 { // check if a fly anchored there would be copied - then copy...
1996 return !IsDestroyFrameAnchoredAtChar(rStart, rStart, rEnd,
1997 (flags & SwCopyFlags::IsMoveToFly)
1998 ? DelContentType::WriterfilterHack|DelContentType::AllMask
1999 : DelContentType::AllMask);
2001 else
2003 return rEnd < rStart;
2007 // Copy an area into this document or into another document
2008 bool
2009 DocumentContentOperationsManager::CopyRange( SwPaM& rPam, SwPosition& rPos,
2010 SwCopyFlags const flags) const
2012 const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End();
2014 SwDoc& rDoc = rPos.GetNode().GetDoc();
2015 bool bColumnSel = rDoc.IsClipBoard() && rDoc.IsColumnSelection();
2017 // Catch if there's no copy to do
2018 if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel))
2019 return false;
2021 // Prevent copying into Flys that are anchored in the source range
2022 if (&rDoc == &m_rDoc && (flags & SwCopyFlags::CheckPosInFly))
2024 // Correct the Start-/EndNode
2025 SwNodeOffset nStt = pStt->GetNodeIndex(),
2026 nEnd = pEnd->GetNodeIndex(),
2027 nDiff = nEnd - nStt +1;
2028 SwNode* pNd = m_rDoc.GetNodes()[ nStt ];
2029 if( pNd->IsContentNode() && pStt->GetContentIndex() )
2031 ++nStt;
2032 --nDiff;
2034 if( (pNd = m_rDoc.GetNodes()[ nEnd ])->IsContentNode() &&
2035 static_cast<SwContentNode*>(pNd)->Len() != pEnd->GetContentIndex() )
2037 --nEnd;
2038 --nDiff;
2040 if( nDiff &&
2041 lcl_ChkFlyFly( rDoc, nStt, nEnd, rPos.GetNodeIndex() ) )
2043 return false;
2047 SwPaM* pRedlineRange = nullptr;
2048 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
2049 (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) )
2050 pRedlineRange = new SwPaM( rPos );
2052 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
2054 bool bRet = false;
2056 if( &rDoc != &m_rDoc )
2057 { // ordinary copy
2058 bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange);
2060 else if( ! ( *pStt <= rPos && rPos < *pEnd &&
2061 ( pStt->GetNode() != pEnd->GetNode() ||
2062 !pStt->GetNode().IsTextNode() )) )
2064 // Copy to a position outside of the area, or copy a single TextNode
2065 // Do an ordinary copy
2066 bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange);
2068 else
2070 // Copy the range in itself
2071 assert(!"mst: this is assumed to be dead code");
2074 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
2075 if( pRedlineRange )
2077 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2078 rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, *pRedlineRange ), true);
2079 else
2080 rDoc.getIDocumentRedlineAccess().SplitRedline( *pRedlineRange );
2081 delete pRedlineRange;
2084 return bRet;
2087 static auto GetCorrPosition(SwPaM const& rPam) -> SwPosition
2089 // tdf#152710 target position must be on node that survives deletion
2090 // so that PaMCorrAbs can invalidate SwUnoCursors properly
2091 return rPam.GetPoint()->GetNode().IsContentNode()
2092 ? *rPam.GetPoint()
2093 : rPam.GetMark()->GetNode().IsContentNode()
2094 ? *rPam.GetMark()
2095 // this would be the result in SwNodes::RemoveNode()
2096 : SwPosition(rPam.End()->GetNode(), SwNodeOffset(+1));
2099 /// Delete a full Section of the NodeArray.
2100 /// The passed Node is located somewhere in the designated Section.
2101 void DocumentContentOperationsManager::DeleteSection( SwNode *pNode )
2103 assert(pNode && "Didn't pass a Node.");
2105 SwStartNode* pSttNd = pNode->IsStartNode() ? static_cast<SwStartNode*>(pNode)
2106 : pNode->StartOfSectionNode();
2107 SwNodeIndex aSttIdx( *pSttNd ), aEndIdx( *pNode->EndOfSectionNode() );
2109 // delete all Flys, Bookmarks, ...
2110 DelFlyInRange( aSttIdx.GetNode(), aEndIdx.GetNode() );
2111 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( *pSttNd, true, RedlineType::Any );
2112 DelBookmarks(aSttIdx.GetNode(), aEndIdx.GetNode());
2115 // move all Cursor/StackCursor/UnoCursor out of the to-be-deleted area
2116 SwPaM const range(aSttIdx, aEndIdx);
2117 SwPosition const pos(GetCorrPosition(range));
2118 ::PaMCorrAbs(range, pos);
2121 m_rDoc.GetNodes().DelNodes( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() + 1 );
2124 void DocumentContentOperationsManager::DeleteDummyChar(
2125 SwPosition const& rPos, sal_Unicode const cDummy)
2127 SwPaM aPam(rPos, rPos);
2128 aPam.GetPoint()->AdjustContent(+1);
2129 assert(aPam.GetText().getLength() == 1 && aPam.GetText()[0] == cDummy);
2130 (void) cDummy;
2132 DeleteRangeImpl(aPam, SwDeleteFlags::Default);
2134 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2135 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2137 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2141 void DocumentContentOperationsManager::DeleteRange( SwPaM & rPam )
2143 lcl_DoWithBreaks(*this, rPam, SwDeleteFlags::Default, &DocumentContentOperationsManager::DeleteRangeImpl);
2145 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2146 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2148 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2152 bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam )
2154 const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End();
2155 const SwNode* pNd = &rStt.GetNode();
2156 SwNodeOffset nSectDiff = pNd->StartOfSectionNode()->EndOfSectionIndex() -
2157 pNd->StartOfSectionIndex();
2158 SwNodeOffset nNodeDiff = rEnd.GetNodeIndex() - rStt.GetNodeIndex();
2160 if ( nSectDiff-SwNodeOffset(2) <= nNodeDiff || m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
2161 /* #i9185# Prevent getting the node after the end node (see below) */
2162 rEnd.GetNodeIndex() + 1 == m_rDoc.GetNodes().Count() )
2164 return false;
2168 SwPaM temp(rPam, nullptr);
2169 if (!temp.HasMark())
2171 temp.SetMark();
2173 if (SwTextNode *const pNode = temp.Start()->GetNode().GetTextNode())
2174 { // rPam may not have nContent set but IsFieldmarkOverlap requires it
2175 temp.Start()->AssignStartIndex(*pNode);
2177 if (SwTextNode *const pNode = temp.End()->GetNode().GetTextNode())
2179 temp.End()->AssignEndIndex(*pNode);
2181 if (sw::mark::IsFieldmarkOverlap(temp))
2182 { // a bit of a problem: we want to completely remove the nodes
2183 // but then how can the CH_TXT_ATR survive?
2184 return false;
2188 // Move hard page breaks to the following Node.
2189 bool bSavePageBreak = false, bSavePageDesc = false;
2191 /* #i9185# This would lead to a segmentation fault if not caught above. */
2192 SwNodeOffset nNextNd = rEnd.GetNodeIndex() + 1;
2193 SwTableNode *const pTableNd = m_rDoc.GetNodes()[ nNextNd ]->GetTableNode();
2195 if( pTableNd && pNd->IsContentNode() )
2197 SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat();
2200 const SfxPoolItem *pItem;
2201 const SfxItemSet* pSet = static_cast<const SwContentNode*>(pNd)->GetpSwAttrSet();
2202 if( pSet && SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC,
2203 false, &pItem ) )
2205 pTableFormat->SetFormatAttr( *pItem );
2206 bSavePageDesc = true;
2209 if( pSet && SfxItemState::SET == pSet->GetItemState( RES_BREAK,
2210 false, &pItem ) )
2212 pTableFormat->SetFormatAttr( *pItem );
2213 bSavePageBreak = true;
2218 bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
2219 if( bDoesUndo )
2221 if( !rPam.HasMark() )
2222 rPam.SetMark();
2223 else if( rPam.GetPoint() == &rStt )
2224 rPam.Exchange();
2225 rPam.GetPoint()->Adjust(SwNodeOffset(1));
2227 SwContentNode *pTmpNode = rPam.GetPoint()->GetNode().GetContentNode();
2228 bool bGoNext = (nullptr == pTmpNode);
2230 if (rPam.GetMark()->GetContentNode())
2231 rPam.GetMark()->SetContent( 0 );
2233 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
2235 SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
2237 SwPosition aTmpPos( *aDelPam.GetPoint() );
2238 if( bGoNext )
2240 m_rDoc.GetNodes().GoNext( &aTmpPos );
2242 ::PaMCorrAbs( aDelPam, aTmpPos );
2245 std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aDelPam, SwDeleteFlags::Default, true));
2247 *rPam.GetPoint() = *aDelPam.GetPoint();
2248 pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc );
2249 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
2250 rPam.DeleteMark();
2252 else
2254 SwNodeRange aRg( rStt.GetNode(), rEnd.GetNode() );
2255 rPam.Normalize(false);
2257 // Try to move past the End
2258 if( !rPam.Move( fnMoveForward, GoInNode ) )
2260 // Fair enough, at the Beginning then
2261 rPam.Exchange();
2262 if( !rPam.Move( fnMoveBackward, GoInNode ))
2264 SAL_WARN("sw.core", "DelFullPara: no more Nodes");
2265 return false;
2269 // must delete all fieldmarks before CorrAbs(), or they'll remain
2270 // moved to wrong node without their CH_TXT_ATR_FIELD*
2271 // (note: deleteMarks() doesn't help here, in case of partially
2272 // selected fieldmarks; let's delete these as re-inserting their chars
2273 // elsewhere looks difficult)
2274 ::std::set<::sw::mark::IFieldmark*> fieldmarks;
2275 for (SwNodeIndex i = aRg.aStart; i <= aRg.aEnd; ++i)
2277 if (SwTextNode *const pTextNode = i.GetNode().GetTextNode())
2279 for (sal_Int32 j = 0; j < pTextNode->GetText().getLength(); ++j)
2281 switch (pTextNode->GetText()[j])
2283 case CH_TXT_ATR_FIELDSTART:
2284 case CH_TXT_ATR_FIELDEND:
2285 fieldmarks.insert(m_rDoc.getIDocumentMarkAccess()->getFieldmarkAt(SwPosition(*pTextNode, j)));
2286 break;
2287 case CH_TXT_ATR_FIELDSEP:
2288 fieldmarks.insert(m_rDoc.getIDocumentMarkAccess()->getInnerFieldmarkFor(SwPosition(*pTextNode, j)));
2289 break;
2294 for (auto const pFieldMark : fieldmarks)
2296 m_rDoc.getIDocumentMarkAccess()->deleteMark(pFieldMark);
2299 // move bookmarks, redlines etc.
2300 if (aRg.aStart == aRg.aEnd) // only first CorrAbs variant handles this
2302 m_rDoc.CorrAbs( aRg.aStart.GetNode(), *rPam.GetPoint(), 0, true );
2304 else
2306 SwDoc::CorrAbs( aRg.aStart, aRg.aEnd, *rPam.GetPoint(), true );
2309 // What's with Flys?
2311 // If there are FlyFrames left, delete these too
2312 for( size_t n = 0; n < m_rDoc.GetSpzFrameFormats()->size(); ++n )
2314 sw::SpzFrameFormat* pFly = (*m_rDoc.GetSpzFrameFormats())[n];
2315 const SwFormatAnchor* pAnchor = &pFly->GetAnchor();
2316 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
2317 if (pAnchorNode &&
2318 ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
2319 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
2320 // note: here use <= not < like in
2321 // IsDestroyFrameAnchoredAtChar() because of the increment
2322 // of rPam in the bDoesUndo path above!
2323 aRg.aStart <= *pAnchorNode && *pAnchorNode <= aRg.aEnd.GetNode() )
2325 m_rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFly );
2326 --n;
2331 rPam.DeleteMark();
2332 m_rDoc.GetNodes().Delete( aRg.aStart, nNodeDiff+1 );
2335 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2336 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2338 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2341 m_rDoc.getIDocumentState().SetModified();
2343 return true;
2346 bool DocumentContentOperationsManager::DeleteAndJoin(SwPaM & rPam, SwDeleteFlags const flags)
2348 if ( lcl_StrLenOverflow( rPam ) )
2349 return false;
2351 bool const ret = lcl_DoWithBreaks( *this, rPam, flags, (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
2352 ? &DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl
2353 : &DocumentContentOperationsManager::DeleteAndJoinImpl );
2355 return ret;
2358 // It seems that this is mostly used by SwDoc internals; the only
2359 // way to call this from the outside seems to be the special case in
2360 // SwDoc::CopyRange (but I have not managed to actually hit that case).
2361 bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos, SwMoveFlags eMvFlags )
2363 // nothing moved: return
2364 const SwPosition *pStt = rPaM.Start(), *pEnd = rPaM.End();
2365 if( !rPaM.HasMark() || *pStt >= *pEnd || (*pStt <= rPos && rPos < *pEnd))
2366 return false;
2368 assert(!sw::mark::IsFieldmarkOverlap(rPaM)); // probably an invalid redline was created?
2370 // Save the paragraph anchored Flys, so that they can be moved.
2371 SaveFlyArr aSaveFlyArr;
2372 SaveFlyInRange( rPaM, rPos, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) );
2374 // save redlines (if DOC_MOVEREDLINES is used)
2375 SaveRedlines_t aSaveRedl;
2376 if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
2378 lcl_SaveRedlines( rPaM, aSaveRedl );
2380 // #i17764# unfortunately, code below relies on undos being
2381 // in a particular order, and presence of bookmarks
2382 // will change this order. Hence, we delete bookmarks
2383 // here without undo.
2384 ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo());
2385 DelBookmarks(
2386 pStt->GetNode(),
2387 pEnd->GetNode(),
2388 nullptr,
2389 pStt->GetContentIndex(),
2390 pEnd->GetContentIndex());
2393 bool bUpdateFootnote = false;
2394 SwFootnoteIdxs aTmpFntIdx;
2396 std::unique_ptr<SwUndoMove> pUndoMove;
2397 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2399 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
2400 pUndoMove.reset(new SwUndoMove( rPaM, rPos ));
2401 pUndoMove->SetMoveRedlines( eMvFlags == SwMoveFlags::REDLINES );
2403 else
2405 bUpdateFootnote = lcl_SaveFootnote( pStt->GetNode(), pEnd->GetNode(), rPos.GetNode(),
2406 m_rDoc.GetFootnoteIdxs(), aTmpFntIdx,
2407 pStt->GetContentIndex(), pEnd->GetContentIndex() );
2410 bool bSplit = false;
2411 SwPaM aSavePam( rPos, rPos );
2413 // Move the SPoint to the beginning of the range
2414 if( rPaM.GetPoint() == pEnd )
2415 rPaM.Exchange();
2417 // If there is a TextNode before and after the Move, create a JoinNext in the EditShell.
2418 SwTextNode* pSrcNd = rPaM.GetPoint()->GetNode().GetTextNode();
2419 bool bCorrSavePam = pSrcNd && pStt->GetNode() != pEnd->GetNode();
2421 // If one or more TextNodes are moved, SwNodes::Move will do a SplitNode.
2422 // However, this does not update the cursor. So we create a TextNode to keep
2423 // updating the indices. After the Move the Node is optionally deleted.
2424 SwTextNode * pTNd = rPos.GetNode().GetTextNode();
2425 if( pTNd && rPaM.GetPoint()->GetNode() != rPaM.GetMark()->GetNode() &&
2426 ( rPos.GetContentIndex() || ( pTNd->Len() && bCorrSavePam )) )
2428 bSplit = true;
2429 const sal_Int32 nMkContent = rPaM.GetMark()->GetContentIndex();
2431 const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
2432 pContentStore->Save( m_rDoc, rPos.GetNodeIndex(), rPos.GetContentIndex(), true );
2434 SwTextNode * pOrigNode = pTNd;
2435 assert(*aSavePam.GetPoint() == *aSavePam.GetMark() &&
2436 *aSavePam.GetPoint() == rPos);
2437 assert(aSavePam.GetPoint()->GetContentNode() == pOrigNode);
2438 assert(aSavePam.GetPoint()->GetNode() == rPos.GetNode());
2439 assert(rPos.GetNodeIndex() == pOrigNode->GetIndex());
2441 std::function<void (SwTextNode *, sw::mark::RestoreMode, bool)> restoreFunc(
2442 [&](SwTextNode *const, sw::mark::RestoreMode const eMode, bool)
2444 if (!pContentStore->Empty())
2446 pContentStore->Restore(m_rDoc, pOrigNode->GetIndex()-SwNodeOffset(1), 0, true, false, eMode);
2449 pTNd->SplitContentNode(rPos, &restoreFunc);
2451 //A new node was inserted before the orig pTNd and the content up to
2452 //rPos moved into it. The old node is returned with the remainder
2453 //of the content in it.
2455 //aSavePam was created with rPos, it continues to point to the
2456 //old node, but with the *original* content index into the node.
2457 //Seeing as all the orignode content before that index has
2458 //been removed, the new index into the original node should now be set
2459 //to 0 and the content index of rPos should also be adapted to the
2460 //truncated node
2461 assert(*aSavePam.GetPoint() == *aSavePam.GetMark() &&
2462 *aSavePam.GetPoint() == rPos);
2463 assert(aSavePam.GetPoint()->GetContentNode() == pOrigNode);
2464 assert(aSavePam.GetPoint()->GetNode() == rPos.GetNode());
2465 assert(rPos.GetNodeIndex() == pOrigNode->GetIndex());
2466 aSavePam.GetPoint()->SetContent(0);
2467 rPos = *aSavePam.GetMark() = *aSavePam.GetPoint();
2469 // correct the PaM!
2470 if( rPos.GetNode() == rPaM.GetMark()->GetNode() )
2472 rPaM.GetMark()->Assign( rPos.GetNodeIndex() - SwNodeOffset(1) );
2473 rPaM.GetMark()->SetContent( nMkContent );
2477 // Put back the Pam by one "content"; so that it's always outside of
2478 // the manipulated range.
2479 // tdf#99692 don't Move() back if that would end up in another node
2480 // because moving backward is not necessarily the inverse of forward then.
2481 // (but do Move() back if we have split the node)
2482 const bool bNullContent = !bSplit && aSavePam.GetPoint()->GetContentIndex() == 0;
2483 if( bNullContent )
2485 aSavePam.GetPoint()->Adjust(SwNodeOffset(-1));
2487 else
2489 bool const success(aSavePam.Move(fnMoveBackward, GoInContent));
2490 assert(success);
2491 (void) success;
2494 // Copy all Bookmarks that are within the Move range into an array,
2495 // that saves the position as an offset.
2496 std::vector< ::sw::mark::SaveBookmark> aSaveBkmks;
2497 DelBookmarks(
2498 pStt->GetNode(),
2499 pEnd->GetNode(),
2500 &aSaveBkmks,
2501 pStt->GetContentIndex(),
2502 pEnd->GetContentIndex());
2504 // If there is no range anymore due to the above deletions (e.g. the
2505 // footnotes got deleted), it's still a valid Move!
2506 if( *rPaM.GetPoint() != *rPaM.GetMark() )
2508 // now do the actual move
2509 m_rDoc.GetNodes().MoveRange( rPaM, rPos, m_rDoc.GetNodes() );
2511 // after a MoveRange() the Mark is deleted
2512 if ( rPaM.HasMark() ) // => no Move occurred!
2514 return false;
2517 else
2518 rPaM.DeleteMark();
2520 OSL_ENSURE( *aSavePam.GetMark() == rPos ||
2521 ( aSavePam.GetMark()->GetNode().GetContentNode() == nullptr ),
2522 "PaM was not moved. Aren't there ContentNodes at the beginning/end?" );
2523 *aSavePam.GetMark() = rPos;
2525 rPaM.SetMark(); // create a Sel. around the new range
2526 pTNd = aSavePam.GetPointNode().GetTextNode();
2527 assert(!m_rDoc.GetIDocumentUndoRedo().DoesUndo());
2528 bool bRemove = true;
2529 // Do two Nodes have to be joined at the SavePam?
2530 if (bSplit && pTNd)
2532 if (pTNd->CanJoinNext())
2534 // Always join next, because <pTNd> has to stay as it is.
2535 // A join previous from its next would more or less delete <pTNd>
2536 pTNd->JoinNext();
2537 bRemove = false;
2540 if (bNullContent)
2542 aSavePam.GetPoint()->Adjust(SwNodeOffset(1));
2544 else if (bRemove) // No move forward after joining with next paragraph
2546 aSavePam.Move( fnMoveForward, GoInContent );
2549 // Insert the Bookmarks back into the Document.
2550 *rPaM.GetMark() = *aSavePam.Start();
2551 for(auto& rBkmk : aSaveBkmks)
2552 rBkmk.SetInDoc(
2553 &m_rDoc,
2554 rPaM.GetMark()->GetNode(),
2555 rPaM.GetMark()->GetContentIndex());
2556 *rPaM.GetPoint() = *aSavePam.End();
2558 // Move the Flys to the new position.
2559 // note: rPos is at the end here; can't really tell flys that used to be
2560 // at the start of rPam from flys that used to be at the end of rPam
2561 // unfortunately, so some of them are going to end up with wrong anchor...
2562 RestFlyInRange( aSaveFlyArr, *rPaM.Start(), &rPos.GetNode() );
2564 // restore redlines (if DOC_MOVEREDLINES is used)
2565 if( !aSaveRedl.empty() )
2567 lcl_RestoreRedlines( m_rDoc, *aSavePam.Start(), aSaveRedl );
2570 if( bUpdateFootnote )
2572 if( !aTmpFntIdx.empty() )
2574 m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx );
2575 aTmpFntIdx.clear();
2578 m_rDoc.GetFootnoteIdxs().UpdateAllFootnote();
2581 m_rDoc.getIDocumentState().SetModified();
2582 return true;
2585 bool DocumentContentOperationsManager::MoveNodeRange( SwNodeRange& rRange, SwNode& rDestNd,
2586 SwMoveFlags eMvFlags )
2588 // Moves all Nodes to the new position.
2589 // Bookmarks are moved too (currently without Undo support).
2591 // If footnotes are being moved to the special section, remove them now.
2593 // Or else delete the Frames for all footnotes that are being moved
2594 // and have it rebuild after the Move (footnotes can change pages).
2595 // Additionally we have to correct the FootnoteIdx array's sorting.
2596 bool bUpdateFootnote = false;
2597 SwFootnoteIdxs aTmpFntIdx;
2599 std::unique_ptr<SwUndoMove> pUndo;
2600 if ((SwMoveFlags::CREATEUNDOOBJ & eMvFlags ) && m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2602 pUndo.reset(new SwUndoMove( m_rDoc, rRange, rDestNd ));
2604 else
2606 bUpdateFootnote = lcl_SaveFootnote( rRange.aStart.GetNode(), rRange.aEnd.GetNode(), rDestNd,
2607 m_rDoc.GetFootnoteIdxs(), aTmpFntIdx );
2610 SaveRedlines_t aSaveRedl;
2611 std::vector<SwRangeRedline*> aSavRedlInsPosArr;
2612 if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
2614 lcl_SaveRedlines( rRange, aSaveRedl );
2616 // Find all RedLines that end at the InsPos.
2617 // These have to be moved back to the "old" position after the Move.
2618 SwRedlineTable::size_type nRedlPos = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos( rDestNd, RedlineType::Any );
2619 if( SwRedlineTable::npos != nRedlPos )
2621 const SwPosition *pRStt, *pREnd;
2622 do {
2623 SwRangeRedline* pTmp = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
2624 pRStt = pTmp->Start();
2625 pREnd = pTmp->End();
2626 if( pREnd->GetNode() == rDestNd && pRStt->GetNode() < rDestNd )
2628 aSavRedlInsPosArr.push_back( pTmp );
2630 } while( pRStt->GetNode() < rDestNd && ++nRedlPos < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size());
2634 // Copy all Bookmarks that are within the Move range into an array
2635 // that stores all references to positions as an offset.
2636 // The final mapping happens after the Move.
2637 std::vector< ::sw::mark::SaveBookmark> aSaveBkmks;
2638 DelBookmarks(rRange.aStart.GetNode(), rRange.aEnd.GetNode(), &aSaveBkmks);
2640 // Save the paragraph-bound Flys, so that they can be moved.
2641 SaveFlyArr aSaveFlyArr;
2642 if( !m_rDoc.GetSpzFrameFormats()->empty() )
2643 SaveFlyInRange( rRange, aSaveFlyArr );
2645 // Set it to before the Position, so that it cannot be moved further.
2646 SwNodeIndex aIdx( rDestNd, -1 );
2648 std::optional<SwNodeIndex> oSaveInsPos;
2649 if( pUndo )
2650 oSaveInsPos.emplace(rRange.aStart, -1 );
2652 // move the Nodes
2653 bool bNoDelFrames = bool(SwMoveFlags::NO_DELFRMS & eMvFlags);
2654 if( m_rDoc.GetNodes().MoveNodes( rRange, m_rDoc.GetNodes(), rDestNd, !bNoDelFrames ) )
2656 ++aIdx; // again back to old position
2657 if( oSaveInsPos )
2658 ++(*oSaveInsPos);
2660 else
2662 aIdx = rRange.aStart;
2663 pUndo.reset();
2666 // move the Flys to the new position
2667 if( !aSaveFlyArr.empty() )
2669 SwPosition const tmp(aIdx);
2670 RestFlyInRange(aSaveFlyArr, tmp, nullptr);
2673 // Add the Bookmarks back to the Document
2674 for(auto& rBkmk : aSaveBkmks)
2675 rBkmk.SetInDoc(&m_rDoc, aIdx.GetNode());
2677 if( !aSavRedlInsPosArr.empty() )
2679 for(SwRangeRedline* pTmp : aSavRedlInsPosArr)
2681 if( m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().Contains( pTmp ) )
2683 SwPosition* pEnd = pTmp->End();
2684 pEnd->Assign(aIdx);
2689 if( !aSaveRedl.empty() )
2690 lcl_RestoreRedlines( m_rDoc, aIdx.GetIndex(), aSaveRedl );
2692 if( pUndo )
2694 pUndo->SetDestRange( aIdx.GetNode(), rDestNd, *oSaveInsPos );
2695 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
2698 oSaveInsPos.reset();
2700 if( bUpdateFootnote )
2702 if( !aTmpFntIdx.empty() )
2704 m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx );
2705 aTmpFntIdx.clear();
2708 m_rDoc.GetFootnoteIdxs().UpdateAllFootnote();
2711 m_rDoc.getIDocumentState().SetModified();
2712 return true;
2715 void DocumentContentOperationsManager::MoveAndJoin( SwPaM& rPaM, SwPosition& rPos )
2717 SwNodeIndex aIdx( rPaM.Start()->GetNode() );
2718 bool bJoinText = aIdx.GetNode().IsTextNode();
2719 bool bOneNode = rPaM.GetPoint()->GetNode() == rPaM.GetMark()->GetNode();
2720 --aIdx; // in front of the move area!
2722 bool bRet = MoveRange( rPaM, rPos, SwMoveFlags::DEFAULT );
2723 if( !bRet || bOneNode )
2724 return;
2726 if( bJoinText )
2727 ++aIdx;
2728 SwTextNode * pTextNd = aIdx.GetNode().GetTextNode();
2729 SwNodeIndex aNxtIdx( aIdx );
2730 if( pTextNd && pTextNd->CanJoinNext( &aNxtIdx ) )
2732 { // Block so SwContentIndex into node is deleted before Join
2733 m_rDoc.CorrRel( aNxtIdx.GetNode(),
2734 SwPosition( *pTextNd, pTextNd->GetText().getLength() ),
2735 0, true );
2737 pTextNd->JoinNext();
2741 // Overwrite only uses the point of the PaM, the mark is ignored; characters
2742 // are replaced from point until the end of the node; at the end of the node,
2743 // characters are inserted.
2744 bool DocumentContentOperationsManager::Overwrite( const SwPaM &rRg, const OUString &rStr )
2746 assert(rStr.getLength());
2747 SwPosition& rPt = *const_cast<SwPosition*>(rRg.GetPoint());
2748 if( m_rDoc.GetAutoCorrExceptWord() ) // Add to AutoCorrect
2750 if( 1 == rStr.getLength() )
2751 m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPt, rStr[ 0 ] );
2752 m_rDoc.DeleteAutoCorrExceptWord();
2755 SwTextNode *pNode = rPt.GetNode().GetTextNode();
2756 if (!pNode || rStr.getLength() > pNode->GetSpaceLeft()) // worst case: no erase
2758 return false;
2761 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2763 m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called
2766 const size_t nOldAttrCnt = pNode->GetpSwpHints()
2767 ? pNode->GetpSwpHints()->Count() : 0;
2768 SwDataChanged aTmp( rRg );
2769 sal_Int32 const nActualStart(rPt.GetContentIndex());
2770 sal_Int32 nStart = 0;
2772 bool bOldExpFlg = pNode->IsIgnoreDontExpand();
2773 pNode->SetIgnoreDontExpand( true );
2775 for( sal_Int32 nCnt = 0; nCnt < rStr.getLength(); ++nCnt )
2777 // start behind the characters (to fix the attributes!)
2778 nStart = rPt.GetContentIndex();
2779 if (nStart < pNode->GetText().getLength())
2781 lcl_SkipAttr( pNode, rPt, nStart );
2783 sal_Unicode c = rStr[ nCnt ];
2784 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2786 bool bMerged(false);
2787 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
2789 SwUndo *const pUndo = m_rDoc.GetUndoManager().GetLastUndo();
2790 SwUndoOverwrite *const pUndoOW(
2791 dynamic_cast<SwUndoOverwrite *>(pUndo) );
2792 if (pUndoOW)
2794 // if CanGrouping() returns true it's already merged
2795 bMerged = pUndoOW->CanGrouping(m_rDoc, rPt, c);
2798 if (!bMerged)
2800 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
2801 std::make_unique<SwUndoOverwrite>(m_rDoc, rPt, c) );
2804 else
2806 // start behind the characters (to fix the attributes!)
2807 if (nStart < pNode->GetText().getLength())
2808 rPt.AdjustContent(+1);
2809 pNode->InsertText( OUString(c), rPt, SwInsertFlags::EMPTYEXPAND );
2810 if( nStart+1 < rPt.GetContentIndex() )
2812 rPt.SetContent(nStart);
2813 pNode->EraseText( rPt, 1 );
2814 rPt.AdjustContent(+1);
2818 pNode->SetIgnoreDontExpand( bOldExpFlg );
2820 const size_t nNewAttrCnt = pNode->GetpSwpHints()
2821 ? pNode->GetpSwpHints()->Count() : 0;
2822 if( nOldAttrCnt != nNewAttrCnt )
2824 const SwUpdateAttr aHint(0,0,0);
2825 pNode->TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
2828 if (!m_rDoc.GetIDocumentUndoRedo().DoesUndo() &&
2829 !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2831 SwPaM aPam(rPt.GetNode(), nActualStart, rPt.GetNode(), rPt.GetContentIndex());
2832 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, true, RedlineType::Any );
2834 else if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2836 // FIXME: this redline is WRONG: there is no DELETE, and the skipped
2837 // characters are also included in aPam
2838 SwPaM aPam(rPt.GetNode(), nActualStart, rPt.GetNode(), rPt.GetContentIndex());
2839 m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
2842 m_rDoc.getIDocumentState().SetModified();
2843 return true;
2846 bool DocumentContentOperationsManager::InsertString( const SwPaM &rRg, const OUString &rStr,
2847 const SwInsertFlags nInsertMode )
2849 // tdf#119019 accept tracked paragraph formatting to do not hide new insertions
2850 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2852 RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
2853 m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting( rRg );
2854 if (eOld != m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags())
2855 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
2858 // fetching DoesUndo is surprisingly expensive
2859 bool bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
2860 if (bDoesUndo)
2861 m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called!
2863 const SwPosition& rPos = *rRg.GetPoint();
2865 if( m_rDoc.GetAutoCorrExceptWord() ) // add to auto correction
2867 if( 1 == rStr.getLength() && m_rDoc.GetAutoCorrExceptWord()->IsDeleted() )
2869 m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPos, rStr[ 0 ] );
2871 m_rDoc.DeleteAutoCorrExceptWord();
2874 SwTextNode *const pNode = rPos.GetNode().GetTextNode();
2875 if(!pNode)
2876 return false;
2878 SwDataChanged aTmp( rRg );
2880 if (!bDoesUndo || !m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
2882 OUString const ins(pNode->InsertText(rStr, rPos, nInsertMode));
2883 if (bDoesUndo)
2885 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
2886 std::make_unique<SwUndoInsert>(rPos.GetNode(),
2887 rPos.GetContentIndex(), ins.getLength(), nInsertMode));
2890 else
2891 { // if Undo and grouping is enabled, everything changes!
2892 SwUndoInsert * pUndo = nullptr;
2894 // don't group the start if hints at the start should be expanded
2895 if (!(nInsertMode & SwInsertFlags::FORCEHINTEXPAND))
2897 SwUndo *const pLastUndo = m_rDoc.GetUndoManager().GetLastUndo();
2898 SwUndoInsert *const pUndoInsert(
2899 dynamic_cast<SwUndoInsert *>(pLastUndo) );
2900 if (pUndoInsert && pUndoInsert->CanGrouping(rPos))
2902 pUndo = pUndoInsert;
2906 CharClass const& rCC = GetAppCharClass();
2907 sal_Int32 nInsPos = rPos.GetContentIndex();
2909 if (!pUndo)
2911 pUndo = new SwUndoInsert( rPos.GetNode(), nInsPos, 0, nInsertMode,
2912 !rCC.isLetterNumeric( rStr, 0 ) );
2913 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
2916 OUString const ins(pNode->InsertText(rStr, rPos, nInsertMode));
2918 for (sal_Int32 i = 0; i < ins.getLength(); ++i)
2920 nInsPos++;
2921 // if CanGrouping() returns true, everything has already been done
2922 if (!pUndo->CanGrouping(ins[i]))
2924 pUndo = new SwUndoInsert(rPos.GetNode(), nInsPos, 1, nInsertMode,
2925 !rCC.isLetterNumeric(ins, i));
2926 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
2931 // To-Do - add 'SwExtraRedlineTable' also ?
2932 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ))
2934 SwPaM aPam( rPos.GetNode(), aTmp.GetContent(),
2935 rPos.GetNode(), rPos.GetContentIndex());
2936 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2938 m_rDoc.getIDocumentRedlineAccess().AppendRedline(
2939 new SwRangeRedline( RedlineType::Insert, aPam ), true);
2941 else
2943 m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
2947 m_rDoc.getIDocumentState().SetModified();
2948 return true;
2951 void DocumentContentOperationsManager::SetIME(bool bIME)
2953 m_bIME = bIME;
2956 bool DocumentContentOperationsManager::GetIME() const
2958 return m_bIME;
2961 void DocumentContentOperationsManager::TransliterateText(
2962 const SwPaM& rPaM,
2963 utl::TransliterationWrapper& rTrans )
2965 std::unique_ptr<SwUndoTransliterate> pUndo;
2966 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2967 pUndo.reset(new SwUndoTransliterate( rPaM, rTrans ));
2969 auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition*
2970 SwNodeOffset nSttNd = pStt->GetNodeIndex(),
2971 nEndNd = pEnd->GetNodeIndex();
2972 sal_Int32 nSttCnt = pStt->GetContentIndex();
2973 sal_Int32 nEndCnt = pEnd->GetContentIndex();
2975 SwTextNode* pTNd = pStt->GetNode().GetTextNode();
2976 if( (pStt == pEnd) && pTNd ) // no selection?
2978 /* Check if cursor is inside of a word */
2979 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
2980 Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
2981 pTNd->GetText(), nSttCnt,
2982 g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ),
2983 WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/,
2984 true);
2986 if( aBndry.startPos < nSttCnt && nSttCnt < aBndry.endPos )
2988 /* Cursor is inside of a word */
2989 if (rTrans.getType() == TransliterationFlags::SENTENCE_CASE) {
2990 /* set current sentence as 'area of effect' */
2991 nSttCnt = g_pBreakIt->GetBreakIter()->beginOfSentence(
2992 pTNd->GetText(), nSttCnt,
2993 g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ) );
2994 nEndCnt = g_pBreakIt->GetBreakIter()->endOfSentence(
2995 pTNd->GetText(), nEndCnt,
2996 g_pBreakIt->GetLocale( pTNd->GetLang( nEndCnt ) ) );
2997 } else {
2998 /* Set current word as 'area of effect' */
2999 nSttCnt = aBndry.startPos;
3000 nEndCnt = aBndry.endPos;
3002 } else {
3003 /* Cursor is not inside of a word. Nothing should happen. */
3004 /* Except in the case of change tracking, when the cursor is at the end of the change */
3005 /* Recognize and reject the previous deleted and inserted words to allow to cycle */
3006 IDocumentRedlineAccess& rIDRA = m_rDoc.getIDocumentRedlineAccess();
3007 if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) &&
3008 pStt->GetContentIndex() > 0 )
3010 SwPosition aPos(*pStt->GetContentNode(), pStt->GetContentIndex() - 1);
3011 SwRedlineTable::size_type n = 0;
3013 const SwRangeRedline* pFnd =
3014 rIDRA.GetRedlineTable().FindAtPosition( aPos, n );
3015 if ( pFnd && RedlineType::Insert == pFnd->GetType() && n > 0 )
3017 const SwRangeRedline* pFnd2 = rIDRA.GetRedlineTable()[n-1];
3018 if ( RedlineType::Delete == pFnd2->GetType() &&
3019 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() &&
3020 *pFnd2->End() == *pFnd->Start() &&
3021 pFnd->GetAuthor() == pFnd2->GetAuthor() )
3023 SwPosition aPos2(*pFnd2->End());
3024 rIDRA.RejectRedline(*pFnd, true);
3025 rIDRA.RejectRedline(*pFnd2, true);
3026 // positionate the text cursor inside the changed word to allow to cycle
3027 if ( SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3028 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) )
3030 pWrtShell->GetCursor()->GetPoint()->
3031 Assign(*aPos2.GetContentNode(), aPos2.GetContentIndex() - 1);
3036 return;
3039 else
3041 bool bHasTrackedChange = false;
3042 IDocumentRedlineAccess& rIDRA = m_rDoc.getIDocumentRedlineAccess();
3043 if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) &&
3044 pEnd->GetContentIndex() > 0 )
3046 SwPosition aPos(*pEnd->GetContentNode(), pEnd->GetContentIndex() - 1);
3047 SwRedlineTable::size_type n = 0;
3049 const SwRangeRedline* pFnd =
3050 rIDRA.GetRedlineTable().FindAtPosition( aPos, n );
3051 if ( pFnd && RedlineType::Insert == pFnd->GetType() && n > 0 )
3053 const SwRangeRedline* pFnd2 = rIDRA.GetRedlineTable()[n-1];
3054 if ( RedlineType::Delete == pFnd2->GetType() &&
3055 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() &&
3056 *pFnd2->End() == *pFnd->Start() &&
3057 pFnd->GetAuthor() == pFnd2->GetAuthor() )
3059 bHasTrackedChange = true;
3060 SwPosition aPos2(*pFnd2->Start());
3061 rIDRA.RejectRedline(*pFnd, true);
3063 rIDRA.RejectRedline(*pFnd2, true);
3064 // positionate the text cursor before the changed word to select it
3065 if ( SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3066 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) )
3068 pWrtShell->GetCursor()->GetPoint()->
3069 Assign(*aPos2.GetContentNode(), aPos2.GetContentIndex());
3074 if ( bHasTrackedChange )
3075 return;
3078 bool bUseRedlining = m_rDoc.getIDocumentRedlineAccess().IsRedlineOn();
3079 // as a workaround for a known performance problem, switch off redlining
3080 // to avoid freezing, if transliteration could result too many redlines
3081 if ( bUseRedlining )
3083 const sal_uLong nMaxRedlines = 500;
3084 const bool bIsTitleCase = rTrans.getType() == TransliterationFlags::TITLE_CASE;
3085 sal_uLong nAffectedNodes = 0;
3086 sal_uLong nAffectedChars = nEndCnt;
3087 SwNodeIndex aIdx( pStt->GetNode() );
3088 for( ; aIdx.GetIndex() <= nEndNd; ++aIdx )
3090 SwTextNode* pAffectedNode = aIdx.GetNode().GetTextNode();
3092 // don't count not text nodes or empty text nodes
3093 if( !pAffectedNode || pAffectedNode->GetText().isEmpty() )
3094 continue;
3096 nAffectedNodes++;
3098 // count characters of the node (the last - maybe partially
3099 // selected - node was counted at initialization of nAffectedChars)
3100 if( aIdx.GetIndex() < nEndNd )
3101 nAffectedChars += pAffectedNode->GetText().getLength();
3103 // transliteration creates n redlines for n nodes, except in the
3104 // case of title case, where it creates n redlines for n words
3105 if( nAffectedNodes > nMaxRedlines ||
3106 // estimate word count based on the character count, where
3107 // 6 = average English word length is ~5 letters + space
3108 ( bIsTitleCase && (nAffectedChars - nSttCnt)/6 > nMaxRedlines ) )
3110 bUseRedlining = false;
3111 break;
3116 if( nSttNd != nEndNd ) // is more than one text node involved?
3118 // iterate over all affected text nodes, the first and the last one
3119 // may be incomplete because the selection starts and/or ends there
3121 SwNodeIndex aIdx( pStt->GetNode() );
3122 if( nSttCnt )
3124 ++aIdx;
3125 if( pTNd )
3127 pTNd->TransliterateText(
3128 rTrans, nSttCnt, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining);
3132 for( ; aIdx.GetIndex() < nEndNd; ++aIdx )
3134 pTNd = aIdx.GetNode().GetTextNode();
3135 if (pTNd)
3137 pTNd->TransliterateText(
3138 rTrans, 0, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining);
3142 if( nEndCnt && nullptr != ( pTNd = pEnd->GetNode().GetTextNode() ))
3144 pTNd->TransliterateText( rTrans, 0, nEndCnt, pUndo.get(), bUseRedlining );
3147 else if( pTNd && nSttCnt < nEndCnt )
3149 pTNd->TransliterateText( rTrans, nSttCnt, nEndCnt, pUndo.get(), bUseRedlining );
3151 if( pUndo && pUndo->HasData() )
3153 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
3155 m_rDoc.getIDocumentState().SetModified();
3158 SwFlyFrameFormat* DocumentContentOperationsManager::InsertGraphic(
3159 const SwPaM &rRg,
3160 const OUString& rGrfName,
3161 const OUString& rFltName,
3162 const Graphic* pGraphic,
3163 const SfxItemSet* pFlyAttrSet,
3164 const SfxItemSet* pGrfAttrSet,
3165 SwFrameFormat* pFrameFormat )
3167 if( !pFrameFormat )
3168 pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC );
3169 SwGrfNode* pSwGrfNode = SwNodes::MakeGrfNode(
3170 m_rDoc.GetNodes().GetEndOfAutotext(),
3171 rGrfName, rFltName, pGraphic,
3172 m_rDoc.GetDfltGrfFormatColl() );
3173 SwFlyFrameFormat* pSwFlyFrameFormat = InsNoTextNode( *rRg.GetPoint(), pSwGrfNode,
3174 pFlyAttrSet, pGrfAttrSet, pFrameFormat );
3175 return pSwFlyFrameFormat;
3178 SwFlyFrameFormat* DocumentContentOperationsManager::InsertEmbObject(
3179 const SwPaM &rRg, const svt::EmbeddedObjectRef& xObj,
3180 SfxItemSet* pFlyAttrSet)
3182 sal_uInt16 nId = RES_POOLFRM_OLE;
3183 if (xObj.is())
3185 SvGlobalName aClassName( xObj->getClassID() );
3186 if (SotExchange::IsMath(aClassName))
3188 nId = RES_POOLFRM_FORMEL;
3192 SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( nId );
3194 return InsNoTextNode( *rRg.GetPoint(), m_rDoc.GetNodes().MakeOLENode(
3195 m_rDoc.GetNodes().GetEndOfAutotext(),
3196 xObj,
3197 m_rDoc.GetDfltGrfFormatColl() ),
3198 pFlyAttrSet, nullptr,
3199 pFrameFormat );
3202 SwFlyFrameFormat* DocumentContentOperationsManager::InsertOLE(const SwPaM &rRg, const OUString& rObjName,
3203 sal_Int64 nAspect,
3204 const SfxItemSet* pFlyAttrSet,
3205 const SfxItemSet* pGrfAttrSet)
3207 SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_OLE );
3209 return InsNoTextNode( *rRg.GetPoint(),
3210 m_rDoc.GetNodes().MakeOLENode(
3211 m_rDoc.GetNodes().GetEndOfAutotext(),
3212 rObjName,
3213 nAspect,
3214 m_rDoc.GetDfltGrfFormatColl(),
3215 nullptr ),
3216 pFlyAttrSet, pGrfAttrSet,
3217 pFrameFormat );
3220 void DocumentContentOperationsManager::ReRead( SwPaM& rPam, const OUString& rGrfName,
3221 const OUString& rFltName, const Graphic* pGraphic )
3223 SwGrfNode *pGrfNd;
3224 if( !(( !rPam.HasMark()
3225 || rPam.GetPoint()->GetNodeIndex() == rPam.GetMark()->GetNodeIndex() )
3226 && nullptr != ( pGrfNd = rPam.GetPoint()->GetNode().GetGrfNode() )) )
3227 return;
3229 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3231 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoReRead>(rPam, *pGrfNd));
3234 // Because we don't know if we can mirror the graphic, the mirror attribute is always reset
3235 if( MirrorGraph::Dont != pGrfNd->GetSwAttrSet().
3236 GetMirrorGrf().GetValue() )
3237 pGrfNd->SetAttr( SwMirrorGrf() );
3239 pGrfNd->ReRead( rGrfName, rFltName, pGraphic );
3240 m_rDoc.getIDocumentState().SetModified();
3243 // Insert drawing object, which has to be already inserted in the DrawModel
3244 SwDrawFrameFormat* DocumentContentOperationsManager::InsertDrawObj(
3245 const SwPaM &rRg,
3246 SdrObject& rDrawObj,
3247 const SfxItemSet& rFlyAttrSet )
3249 SwDrawFrameFormat* pFormat = m_rDoc.MakeDrawFrameFormat( OUString(), m_rDoc.GetDfltFrameFormat() );
3251 const SwFormatAnchor* pAnchor = rFlyAttrSet.GetItemIfSet( RES_ANCHOR, false );
3252 pFormat->SetFormatAttr( rFlyAttrSet );
3254 // Didn't set the Anchor yet?
3255 // DrawObjecte must never end up in the Header/Footer!
3256 RndStdIds eAnchorId = pAnchor != nullptr ? pAnchor->GetAnchorId() : pFormat->GetAnchor().GetAnchorId();
3257 const bool bIsAtContent = (RndStdIds::FLY_AT_PAGE != eAnchorId);
3259 const SwPosition* pChkPos = nullptr;
3260 if ( pAnchor == nullptr )
3262 pChkPos = rRg.GetPoint();
3264 else if ( bIsAtContent )
3266 pChkPos =
3267 pAnchor->GetContentAnchor() ? pAnchor->GetContentAnchor() : rRg.GetPoint();
3270 // allow drawing objects in header/footer, but control objects aren't allowed in header/footer.
3271 if( pChkPos != nullptr
3272 && ::CheckControlLayer( &rDrawObj )
3273 && m_rDoc.IsInHeaderFooter( pChkPos->GetNode() ) )
3275 // apply at-page anchor format
3276 eAnchorId = RndStdIds::FLY_AT_PAGE;
3277 pFormat->SetFormatAttr( SwFormatAnchor( eAnchorId ) );
3279 else if( pAnchor == nullptr
3280 || ( bIsAtContent
3281 && pAnchor->GetAnchorNode() == nullptr ) )
3283 // apply anchor format
3284 SwFormatAnchor aAnch( pAnchor != nullptr ? *pAnchor : pFormat->GetAnchor() );
3285 eAnchorId = aAnch.GetAnchorId();
3286 if ( eAnchorId == RndStdIds::FLY_AT_FLY )
3288 const SwStartNode* pStartNode = rRg.GetPointNode().FindFlyStartNode();
3289 assert(pStartNode);
3290 SwPosition aPos(*pStartNode);
3291 aAnch.SetAnchor( &aPos );
3293 else
3295 aAnch.SetAnchor( rRg.GetPoint() );
3296 if ( eAnchorId == RndStdIds::FLY_AT_PAGE )
3298 eAnchorId = dynamic_cast<const SdrUnoObj*>( &rDrawObj) != nullptr ? RndStdIds::FLY_AS_CHAR : RndStdIds::FLY_AT_PARA;
3299 aAnch.SetType( eAnchorId );
3302 pFormat->SetFormatAttr( aAnch );
3305 // insert text attribute for as-character anchored drawing object
3306 if ( eAnchorId == RndStdIds::FLY_AS_CHAR )
3308 bool bAnchorAtPageAsFallback = true;
3309 const SwFormatAnchor& rDrawObjAnchorFormat = pFormat->GetAnchor();
3310 if ( rDrawObjAnchorFormat.GetAnchorNode() != nullptr )
3312 SwTextNode* pAnchorTextNode =
3313 rDrawObjAnchorFormat.GetAnchorNode()->GetTextNode();
3314 if ( pAnchorTextNode != nullptr )
3316 const sal_Int32 nStt = rDrawObjAnchorFormat.GetContentAnchor()->GetContentIndex();
3317 SwFormatFlyCnt aFormat( pFormat );
3318 pAnchorTextNode->InsertItem( aFormat, nStt, nStt );
3319 bAnchorAtPageAsFallback = false;
3323 if ( bAnchorAtPageAsFallback )
3325 OSL_ENSURE( false, "DocumentContentOperationsManager::InsertDrawObj(..) - missing content anchor for as-character anchored drawing object --> anchor at-page" );
3326 pFormat->SetFormatAttr( SwFormatAnchor( RndStdIds::FLY_AT_PAGE ) );
3330 SwDrawContact* pContact = new SwDrawContact( pFormat, &rDrawObj );
3332 // Create Frames if necessary
3333 if( m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() )
3335 // create layout representation
3336 pFormat->MakeFrames();
3337 // #i42319# - follow-up of #i35635#
3338 // move object to visible layer
3339 // #i79391#
3340 if ( pContact->GetAnchorFrame() )
3342 pContact->MoveObjToVisibleLayer( &rDrawObj );
3346 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3348 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsLayFormat>(pFormat, SwNodeOffset(0), 0) );
3351 m_rDoc.getIDocumentState().SetModified();
3352 return pFormat;
3355 bool DocumentContentOperationsManager::SplitNode( const SwPosition &rPos, bool bChkTableStart )
3357 SwContentNode *pNode = rPos.GetNode().GetContentNode();
3358 if(nullptr == pNode)
3359 return false;
3362 // BUG 26675: Send DataChanged before deleting, so that we notice which objects are in scope.
3363 // After that they can be before/after the position.
3364 SwDataChanged aTmp( m_rDoc, rPos );
3367 SwUndoSplitNode* pUndo = nullptr;
3368 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3370 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3371 // insert the Undo object (currently only for TextNode)
3372 if( pNode->IsTextNode() )
3374 pUndo = new SwUndoSplitNode( m_rDoc, rPos, bChkTableStart );
3375 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo));
3379 // Update the rsid of the old and the new node unless
3380 // the old node is split at the beginning or at the end
3381 SwTextNode *pTextNode = rPos.GetNode().GetTextNode();
3382 const sal_Int32 nPos = rPos.GetContentIndex();
3383 if( pTextNode && nPos && nPos != pTextNode->Len() )
3385 m_rDoc.UpdateParRsid( pTextNode );
3388 //JP 28.01.97: Special case for SplitNode at table start:
3389 // If it is at the beginning of a Doc/Fly/Footer/... or right at after a table
3390 // then insert a paragraph before it.
3391 if( bChkTableStart && !rPos.GetContentIndex() && pNode->IsTextNode() )
3393 SwNodeOffset nPrevPos = rPos.GetNodeIndex() - 1;
3394 SwTableNode* pTableNd;
3395 const SwNode* pNd = m_rDoc.GetNodes()[ nPrevPos ];
3396 if( pNd->IsStartNode() &&
3397 SwTableBoxStartNode == static_cast<const SwStartNode*>(pNd)->GetStartNodeType() &&
3398 nullptr != ( pTableNd = m_rDoc.GetNodes()[ --nPrevPos ]->GetTableNode() ) &&
3399 ((( pNd = m_rDoc.GetNodes()[ --nPrevPos ])->IsStartNode() &&
3400 SwTableBoxStartNode != static_cast<const SwStartNode*>(pNd)->GetStartNodeType() )
3401 || ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsTableNode() )
3402 || pNd->IsContentNode() ))
3404 if( pNd->IsContentNode() )
3406 //JP 30.04.99 Bug 65660:
3407 // There are no page breaks outside of the normal body area,
3408 // so this is not a valid condition to insert a paragraph.
3409 if( nPrevPos < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() )
3410 pNd = nullptr;
3411 else
3413 // Only if the table has page breaks!
3414 const SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat();
3415 if( SfxItemState::SET != pFrameFormat->GetItemState(RES_PAGEDESC, false) &&
3416 SfxItemState::SET != pFrameFormat->GetItemState( RES_BREAK, false ) )
3417 pNd = nullptr;
3421 if( pNd )
3423 SwTextNode* pTextNd = m_rDoc.GetNodes().MakeTextNode(
3424 *pTableNd,
3425 m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ));
3426 if( pTextNd )
3428 const_cast<SwPosition&>(rPos).Assign( pTableNd->GetIndex() - SwNodeOffset(1) );
3430 // only add page breaks/styles to the body area
3431 if( nPrevPos > m_rDoc.GetNodes().GetEndOfExtras().GetIndex() )
3433 SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat();
3434 const SfxPoolItem *pItem;
3435 if( SfxItemState::SET == pFrameFormat->GetItemState( RES_PAGEDESC,
3436 false, &pItem ) )
3438 pTextNd->SetAttr( *pItem );
3439 pFrameFormat->ResetFormatAttr( RES_PAGEDESC );
3441 if( SfxItemState::SET == pFrameFormat->GetItemState( RES_BREAK,
3442 false, &pItem ) )
3444 pTextNd->SetAttr( *pItem );
3445 pFrameFormat->ResetFormatAttr( RES_BREAK );
3449 if( pUndo )
3450 pUndo->SetTableFlag();
3451 m_rDoc.getIDocumentState().SetModified();
3452 return true;
3458 const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
3459 pContentStore->Save( m_rDoc, rPos.GetNodeIndex(), rPos.GetContentIndex(), true );
3460 assert(pNode->IsTextNode());
3461 std::function<void (SwTextNode *, sw::mark::RestoreMode, bool bAtStart)> restoreFunc(
3462 [&](SwTextNode *const, sw::mark::RestoreMode const eMode, bool const bAtStart)
3464 if (!pContentStore->Empty())
3465 { // move all bookmarks, TOXMarks, FlyAtCnt
3466 pContentStore->Restore(m_rDoc, rPos.GetNodeIndex()-SwNodeOffset(1), 0, true, bAtStart && (eMode & sw::mark::RestoreMode::Flys), eMode);
3468 if (eMode & sw::mark::RestoreMode::NonFlys)
3470 // To-Do - add 'SwExtraRedlineTable' also ?
3471 if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
3472 (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() &&
3473 !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()))
3475 SwPaM aPam( rPos );
3476 aPam.SetMark();
3477 aPam.Move( fnMoveBackward );
3478 if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
3480 m_rDoc.getIDocumentRedlineAccess().AppendRedline(
3481 new SwRangeRedline(RedlineType::Insert, aPam), true);
3483 else
3485 m_rDoc.getIDocumentRedlineAccess().SplitRedline(aPam);
3490 pNode->GetTextNode()->SplitContentNode(rPos, &restoreFunc);
3492 m_rDoc.getIDocumentState().SetModified();
3493 return true;
3496 bool DocumentContentOperationsManager::AppendTextNode( SwPosition& rPos )
3498 // create new node before EndOfContent
3499 SwTextNode * pCurNode = rPos.GetNode().GetTextNode();
3500 if( !pCurNode )
3502 // so then one can be created!
3503 SwNodeIndex aIdx( rPos.GetNode(), 1 );
3504 pCurNode = m_rDoc.GetNodes().MakeTextNode( aIdx.GetNode(),
3505 m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ));
3507 else
3508 pCurNode = pCurNode->AppendNode( rPos )->GetTextNode();
3510 rPos.Adjust(SwNodeOffset(1));
3512 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3514 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsert>( rPos.GetNode() ) );
3517 // To-Do - add 'SwExtraRedlineTable' also ?
3518 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ))
3520 SwPaM aPam( rPos );
3521 aPam.SetMark();
3522 aPam.Move( fnMoveBackward );
3523 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
3524 m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
3525 else
3526 m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
3529 m_rDoc.getIDocumentState().SetModified();
3530 return true;
3533 bool DocumentContentOperationsManager::ReplaceRange( SwPaM& rPam, const OUString& rStr,
3534 const bool bRegExReplace )
3536 // unfortunately replace works slightly differently from delete,
3537 // so we cannot use lcl_DoWithBreaks here...
3539 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
3541 SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() );
3542 aPam.Normalize(false);
3543 if (aPam.GetPoint()->GetNode() != aPam.GetMark()->GetNode())
3545 aPam.Move(fnMoveBackward);
3547 OSL_ENSURE((aPam.GetPoint()->GetNode() == aPam.GetMark()->GetNode()), "invalid pam?");
3549 sw::CalcBreaks(Breaks, aPam);
3551 while (!Breaks.empty() // skip over prefix of dummy chars
3552 && (aPam.GetMark()->GetNodeIndex() == Breaks.begin()->first)
3553 && (aPam.GetMark()->GetContentIndex() == Breaks.begin()->second))
3555 // skip!
3556 aPam.GetMark()->AdjustContent(+1); // always in bounds if Breaks valid
3557 Breaks.erase(Breaks.begin());
3559 *rPam.Start() = *aPam.GetMark(); // update start of original pam w/ prefix
3561 if (Breaks.empty())
3563 // park aPam somewhere so it does not point to node that is deleted
3564 aPam.DeleteMark();
3565 aPam.GetPoint()->Assign(m_rDoc.GetNodes().GetEndOfContent());
3566 return ReplaceRangeImpl(rPam, rStr, bRegExReplace); // original pam!
3569 // Deletion must be split into several parts if the text node
3570 // contains a text attribute with end and with dummy character
3571 // and the selection does not contain the text attribute completely,
3572 // but overlaps its start (left), where the dummy character is.
3574 bool bRet( true );
3575 // iterate from end to start, to avoid invalidating the offsets!
3576 auto iter( Breaks.rbegin() );
3577 SwNodeOffset nOffset(0);
3578 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
3579 OSL_ENSURE(aPam.GetPoint() == aPam.End(), "wrong!");
3580 SwPosition & rEnd( *aPam.End() );
3581 SwPosition & rStart( *aPam.Start() );
3583 // set end of temp pam to original end (undo Move backward above)
3584 rEnd = *rPam.End();
3585 // after first deletion, rEnd will point into the original text node again!
3587 while (iter != Breaks.rend())
3589 rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
3590 if (rStart < rEnd) // check if part is empty
3592 bRet &= (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
3593 ? DeleteAndJoinWithRedlineImpl(aPam, SwDeleteFlags::Default)
3594 : DeleteAndJoinImpl(aPam, SwDeleteFlags::Default);
3595 nOffset = iter->first - rStart.GetNodeIndex(); // deleted fly nodes...
3597 rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
3598 ++iter;
3601 rStart = *rPam.Start(); // set to original start
3602 assert(rStart < rEnd && "replace part empty!");
3603 if (rStart < rEnd) // check if part is empty
3605 bRet &= ReplaceRangeImpl(aPam, rStr, bRegExReplace);
3608 rPam = aPam; // update original pam (is this required?)
3610 return bRet;
3613 bool DocumentContentOperationsManager::InsertPoolItem(
3614 const SwPaM &rRg,
3615 const SfxPoolItem &rHt,
3616 const SetAttrMode nFlags,
3617 SwRootFrame const*const pLayout,
3618 SwTextAttr **ppNewTextAttr)
3620 SwDataChanged aTmp( rRg );
3621 std::unique_ptr<SwUndoAttr> pUndoAttr;
3622 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3624 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3625 pUndoAttr.reset(new SwUndoAttr( rRg, rHt, nFlags ));
3628 SfxItemSet aSet( m_rDoc.GetAttrPool(), rHt.Which(), rHt.Which() );
3629 aSet.Put( rHt );
3630 const bool bRet = lcl_InsAttr(m_rDoc, rRg, aSet, nFlags, pUndoAttr.get(), pLayout, ppNewTextAttr);
3632 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3634 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) );
3637 if( bRet )
3639 m_rDoc.getIDocumentState().SetModified();
3641 return bRet;
3644 void DocumentContentOperationsManager::InsertItemSet ( const SwPaM &rRg, const SfxItemSet &rSet,
3645 const SetAttrMode nFlags, SwRootFrame const*const pLayout)
3647 SwDataChanged aTmp( rRg );
3648 std::unique_ptr<SwUndoAttr> pUndoAttr;
3649 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3651 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3652 pUndoAttr.reset(new SwUndoAttr( rRg, rSet, nFlags ));
3655 bool bRet = lcl_InsAttr(m_rDoc, rRg, rSet, nFlags, pUndoAttr.get(), pLayout, /*ppNewTextAttr*/nullptr );
3657 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3659 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) );
3662 if( bRet )
3663 m_rDoc.getIDocumentState().SetModified();
3666 void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(const SwPosition & rPos )
3668 const SwTextNode* pTNd = rPos.GetNode().GetTextNode();
3669 if ( !pTNd )
3670 return;
3672 const OUString& rText = pTNd->GetText();
3673 sal_Int32 nIdx = 0;
3674 while (nIdx < rText.getLength())
3676 sal_Unicode const cCh = rText[nIdx];
3677 if (('\t' != cCh) && (' ' != cCh))
3679 break;
3681 ++nIdx;
3684 if ( nIdx > 0 )
3686 SwPaM aPam(rPos);
3687 aPam.GetPoint()->SetContent(0);
3688 aPam.SetMark();
3689 aPam.GetMark()->SetContent(nIdx);
3690 DeleteRange( aPam );
3694 void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(SwPaM& rPaM )
3696 for (SwPaM& rSel :rPaM.GetRingContainer())
3698 SwNodeOffset nStt = rSel.Start()->GetNodeIndex();
3699 SwNodeOffset nEnd = rSel.End()->GetNodeIndex();
3700 for (SwNodeOffset nPos = nStt; nPos<=nEnd; nPos++)
3701 RemoveLeadingWhiteSpace(SwPosition(rSel.GetBound().GetNodes(), nPos));
3705 // Copy method from SwDoc - "copy Flys in Flys"
3706 /// note: rRg/rInsPos *exclude* a partially selected start text node;
3707 /// pCopiedPaM *includes* a partially selected start text node
3708 void DocumentContentOperationsManager::CopyWithFlyInFly(
3709 const SwNodeRange& rRg,
3710 SwNode& rInsPos,
3711 const std::pair<const SwPaM&, const SwPosition&>* pCopiedPaM /*and real insert pos*/,
3712 const bool bMakeNewFrames,
3713 const bool bDelRedlines,
3714 const bool bCopyFlyAtFly,
3715 SwCopyFlags const flags) const
3717 assert(!pCopiedPaM || pCopiedPaM->first.End()->GetNode() == rRg.aEnd.GetNode());
3718 assert(!pCopiedPaM || pCopiedPaM->second.GetNode() <= rInsPos);
3720 SwDoc& rDest = rInsPos.GetDoc();
3721 SwNodeIndex aSavePos( rInsPos );
3723 if (rRg.aStart != rRg.aEnd)
3725 bool bEndIsEqualEndPos = rInsPos == rRg.aEnd.GetNode();
3726 --aSavePos;
3727 SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 );
3729 // insert behind the already copied start node
3730 m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, false, true );
3731 aRedlRest.Restore();
3733 if (bEndIsEqualEndPos)
3735 const_cast<SwNodeIndex&>(rRg.aEnd).Assign(aSavePos.GetNode(), +1);
3739 // Also copy all bookmarks
3740 // guess this must be done before the DelDummyNodes below as that
3741 // deletes nodes so would mess up the index arithmetic
3742 // sw_fieldmarkhide: also needs to be done before making frames
3743 if (m_rDoc.getIDocumentMarkAccess()->getAllMarksCount())
3745 SwPaM aRgTmp( rRg.aStart, rRg.aEnd );
3746 SwPosition targetPos(aSavePos, SwNodeOffset(rRg.aStart != rRg.aEnd ? +1 : 0));
3747 if (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->GetNode())
3749 // there is 1 (partially selected, maybe) paragraph before
3750 assert(SwNodeIndex(rRg.aStart, -1) == pCopiedPaM->first.Start()->GetNode());
3751 // only use the passed in target SwPosition if the source PaM point
3752 // is on a different node; if it was the same node then the target
3753 // position was likely moved along by the copy operation and now
3754 // points to the end of the range!
3755 targetPos = pCopiedPaM->second;
3758 sw::CopyBookmarks(pCopiedPaM ? pCopiedPaM->first : aRgTmp, targetPos, flags);
3761 if (rRg.aStart != rRg.aEnd)
3763 bool isRecreateEndNode(false);
3764 if (bMakeNewFrames) // tdf#130685 only after aRedlRest
3765 { // recreate from previous node (could be merged now)
3766 o3tl::sorted_vector<SwTextFrame*> frames;
3767 SwTextNode * pNode = aSavePos.GetNode().GetTextNode();
3768 SwTextNode *const pEndNode = rInsPos.GetTextNode();
3769 if (pEndNode)
3771 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode);
3772 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
3774 if (pFrame->getRootFrame()->HasMergedParas())
3776 frames.insert(pFrame);
3777 // tdf#135061 check if end node is merged to a preceding node
3778 if (pNode == nullptr && pFrame->GetMergedPara()
3779 && pFrame->GetMergedPara()->pFirstNode->GetIndex() < aSavePos.GetIndex())
3781 pNode = pFrame->GetMergedPara()->pFirstNode;
3786 if (pNode != nullptr)
3788 sw::RecreateStartTextFrames(*pNode);
3789 if (!frames.empty())
3790 { // tdf#132187 check if the end node needs new frames
3791 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode);
3792 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
3794 if (pFrame->getRootFrame()->HasMergedParas())
3796 auto const it = frames.find(pFrame);
3797 if (it != frames.end())
3799 frames.erase(it);
3803 if (!frames.empty()) // existing frame was deleted
3804 { // all layouts because MakeFrames recreates all layouts
3805 pEndNode->DelFrames(nullptr);
3806 isRecreateEndNode = true;
3811 bool const isAtStartOfSection(aSavePos.GetNode().IsStartNode());
3812 ++aSavePos;
3813 if (bMakeNewFrames)
3815 // it's possible that CheckParaRedlineMerge() deleted frames
3816 // on rInsPos so have to include it, but it must not be included
3817 // if it was the first node in the document so that MakeFrames()
3818 // will find the existing (wasn't deleted) frame on it
3819 SwNodeIndex const end(rInsPos,
3820 SwNodeOffset((!isRecreateEndNode || isAtStartOfSection)
3821 ? 0 : +1));
3822 ::MakeFrames(&rDest, aSavePos.GetNode(), end.GetNode());
3826 #if OSL_DEBUG_LEVEL > 0
3828 //JP 17.06.99: Bug 66973 - check count only if the selection is in
3829 // the same section or there's no section, because sections that are
3830 // not fully selected are not copied.
3831 const SwSectionNode* pSSectNd = rRg.aStart.GetNode().FindSectionNode();
3832 SwNodeIndex aTmpI( rRg.aEnd, -1 );
3833 const SwSectionNode* pESectNd = aTmpI.GetNode().FindSectionNode();
3834 if( pSSectNd == pESectNd &&
3835 !rRg.aStart.GetNode().IsSectionNode() &&
3836 !aTmpI.GetNode().IsEndNode() )
3838 // If the range starts with a SwStartNode, it isn't copied
3839 SwNodeOffset offset( (rRg.aStart.GetNode().GetNodeType() != SwNodeType::Start) ? 1 : 0 );
3840 OSL_ENSURE( rInsPos.GetIndex() - aSavePos.GetIndex() ==
3841 rRg.aEnd.GetIndex() - rRg.aStart.GetIndex() - 1 + offset,
3842 "An insufficient number of nodes were copied!" );
3845 #endif
3848 ::sw::UndoGuard const undoGuard(rDest.GetIDocumentUndoRedo());
3849 CopyFlyInFlyImpl(rRg, pCopiedPaM ? &pCopiedPaM->first : nullptr,
3850 // see comment below regarding use of pCopiedPaM->second
3851 (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->GetNode())
3852 ? pCopiedPaM->second.GetNode()
3853 : aSavePos.GetNode(),
3854 bCopyFlyAtFly,
3855 flags);
3858 SwNodeRange aCpyRange( aSavePos.GetNode(), rInsPos );
3860 if( bDelRedlines && ( RedlineFlags::DeleteRedlines & rDest.getIDocumentRedlineAccess().GetRedlineFlags() ))
3861 lcl_DeleteRedlines( rRg, aCpyRange );
3863 rDest.GetNodes().DelDummyNodes( aCpyRange );
3866 // note: for the redline Show/Hide this must be in sync with
3867 // SwRangeRedline::CopyToSection()/DelCopyOfSection()/MoveFromSection()
3868 void DocumentContentOperationsManager::CopyFlyInFlyImpl(
3869 const SwNodeRange& rRg,
3870 SwPaM const*const pCopiedPaM,
3871 SwNode& rStartIdx,
3872 const bool bCopyFlyAtFly,
3873 SwCopyFlags const flags) const
3875 assert(!pCopiedPaM || pCopiedPaM->End()->GetNode() == rRg.aEnd.GetNode());
3877 // First collect all Flys, sort them according to their ordering number,
3878 // and then only copy them. This maintains the ordering numbers (which are only
3879 // managed in the DrawModel).
3880 SwDoc& rDest = rStartIdx.GetDoc();
3881 std::set< ZSortFly > aSet;
3882 const size_t nArrLen = m_rDoc.GetSpzFrameFormats()->size();
3884 SwTextBoxHelper::SavedLink aOldTextBoxes;
3885 SwTextBoxHelper::saveLinks(*m_rDoc.GetSpzFrameFormats(), aOldTextBoxes);
3887 for ( size_t n = 0; n < nArrLen; ++n )
3889 SwFrameFormat* pFormat = (*m_rDoc.GetSpzFrameFormats())[n];
3890 SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
3891 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
3892 if ( !pAnchorNode )
3893 continue;
3894 bool bAdd = false;
3895 SwNodeOffset nSkipAfter = pAnchorNode->GetIndex();
3896 SwNodeOffset nStart = rRg.aStart.GetIndex();
3897 switch ( pAnchor->GetAnchorId() )
3899 case RndStdIds::FLY_AT_FLY:
3900 if(bCopyFlyAtFly)
3901 ++nSkipAfter;
3902 else if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove())
3903 ++nStart;
3904 break;
3905 case RndStdIds::FLY_AT_PARA:
3907 bAdd = IsSelectFrameAnchoredAtPara(*pAnchor->GetContentAnchor(),
3908 pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart),
3909 pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd),
3910 (flags & SwCopyFlags::IsMoveToFly)
3911 ? DelContentType::AllMask|DelContentType::WriterfilterHack
3912 : DelContentType::AllMask);
3914 break;
3915 case RndStdIds::FLY_AT_CHAR:
3917 bAdd = IsDestroyFrameAnchoredAtChar(*pAnchor->GetContentAnchor(),
3918 pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart),
3919 pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd),
3920 (flags & SwCopyFlags::IsMoveToFly)
3921 ? DelContentType::AllMask|DelContentType::WriterfilterHack
3922 : DelContentType::AllMask);
3924 break;
3925 default:
3926 continue;
3928 if (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId())
3930 if (nStart > nSkipAfter)
3931 continue;
3932 if (*pAnchorNode > rRg.aEnd.GetNode())
3933 continue;
3934 //frames at the last source node are not always copied:
3935 //- if the node is empty and is the last node of the document or a table cell
3936 // or a text frame then they have to be copied
3937 //- if the content index in this node is > 0 then paragraph and frame bound objects are copied
3938 //- to-character bound objects are copied if their index is <= nEndContentIndex
3939 if (*pAnchorNode < rRg.aEnd.GetNode())
3940 bAdd = true;
3941 if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move
3943 if (!bAdd)
3945 // technically old code checked nContent of AT_FLY which is pointless
3946 bAdd = pCopiedPaM && 0 < pCopiedPaM->End()->GetContentIndex();
3950 if( bAdd )
3952 aSet.insert( ZSortFly( pFormat, pAnchor, nArrLen + aSet.size() ));
3956 // Store all copied (and also the newly created) frames in another array.
3957 // They are stored as matching the originals, so that we will be later
3958 // able to build the chains accordingly.
3959 std::vector< SwFrameFormat* > aVecSwFrameFormat;
3960 std::set< ZSortFly >::const_iterator it=aSet.begin();
3962 while (it != aSet.end())
3964 // #i59964#
3965 // correct determination of new anchor position
3966 SwFormatAnchor aAnchor( *(*it).GetAnchor() );
3967 assert( aAnchor.GetContentAnchor() != nullptr );
3968 SwPosition newPos = *aAnchor.GetContentAnchor();
3969 // for at-paragraph and at-character anchored objects the new anchor
3970 // position can *not* be determined by the difference of the current
3971 // anchor position to the start of the copied range, because not
3972 // complete selected sections in the copied range aren't copied - see
3973 // method <SwNodes::CopyNodes(..)>.
3974 // Thus, the new anchor position in the destination document is found
3975 // by counting the text nodes.
3976 if ((aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) ||
3977 (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) )
3979 // First, determine number of anchor text node in the copied range.
3980 // Note: The anchor text node *have* to be inside the copied range.
3981 sal_uLong nAnchorTextNdNumInRange( 0 );
3982 bool bAnchorTextNdFound( false );
3983 // start at the first node for which flys are copied
3984 SwNodeIndex aIdx(pCopiedPaM ? pCopiedPaM->Start()->GetNode() : rRg.aStart.GetNode());
3985 while ( !bAnchorTextNdFound && aIdx <= rRg.aEnd )
3987 if ( aIdx.GetNode().IsTextNode() )
3989 ++nAnchorTextNdNumInRange;
3990 bAnchorTextNdFound = *aAnchor.GetAnchorNode() == aIdx.GetNode();
3993 ++aIdx;
3996 if ( !bAnchorTextNdFound )
3998 // This case can *not* happen, but to be robust take the first
3999 // text node in the destination document.
4000 OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - anchor text node in copied range not found" );
4001 nAnchorTextNdNumInRange = 1;
4003 // Second, search corresponding text node in destination document
4004 // by counting forward from start insert position <rStartIdx> the
4005 // determined number of text nodes.
4006 aIdx = rStartIdx;
4007 SwNodeIndex aAnchorNdIdx( rStartIdx );
4008 const SwNode& aEndOfContentNd =
4009 aIdx.GetNode().GetNodes().GetEndOfContent();
4010 while ( nAnchorTextNdNumInRange > 0 &&
4011 aIdx.GetNode() != aEndOfContentNd )
4013 if ( aIdx.GetNode().IsTextNode() )
4015 --nAnchorTextNdNumInRange;
4016 aAnchorNdIdx = aIdx;
4019 ++aIdx;
4021 if ( !aAnchorNdIdx.GetNode().IsTextNode() )
4023 // This case can *not* happen, but to be robust take the first
4024 // text node in the destination document.
4025 OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - found anchor node index isn't a text node" );
4026 aAnchorNdIdx = rStartIdx;
4027 while ( !aAnchorNdIdx.GetNode().IsTextNode() )
4029 ++aAnchorNdIdx;
4032 // apply found anchor text node as new anchor position
4033 newPos.Assign( aAnchorNdIdx );
4035 else
4037 SwNodeOffset nOffset = newPos.GetNodeIndex() - rRg.aStart.GetIndex();
4038 newPos.Assign( rStartIdx, nOffset );
4040 // Set the character bound Flys back at the original character
4041 if ((RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) &&
4042 newPos.GetNode().IsTextNode() )
4044 // only if pCopiedPaM: care about partially selected start node
4045 sal_Int32 const nContent = pCopiedPaM && pCopiedPaM->Start()->GetNode() == *aAnchor.GetAnchorNode()
4046 ? newPos.GetContentIndex() - pCopiedPaM->Start()->GetContentIndex()
4047 : newPos.GetContentIndex();
4048 newPos.SetContent(nContent);
4050 aAnchor.SetAnchor( &newPos );
4052 // Check recursion: if copying content inside the same frame, then don't copy the format.
4053 if( &rDest == &m_rDoc )
4055 const SwFormatContent& rContent = (*it).GetFormat()->GetContent();
4056 const SwStartNode* pSNd;
4057 if( rContent.GetContentIdx() &&
4058 nullptr != ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ) &&
4059 pSNd->GetIndex() < rStartIdx.GetIndex() &&
4060 rStartIdx.GetIndex() < pSNd->EndOfSectionIndex() )
4062 it = aSet.erase(it);
4063 continue;
4067 // Ignore TextBoxes, they are already handled in
4068 // sw::DocumentLayoutManager::CopyLayoutFormat().
4069 if (SwTextBoxHelper::isTextBox(it->GetFormat(), RES_FLYFRMFMT))
4071 it = aSet.erase(it);
4072 continue;
4075 // Copy the format and set the new anchor
4076 aVecSwFrameFormat.push_back( rDest.getIDocumentLayoutAccess().CopyLayoutFormat( *(*it).GetFormat(),
4077 aAnchor, false, true ) );
4078 ++it;
4081 // Rebuild as much as possible of all chains that are available in the original,
4082 OSL_ENSURE( aSet.size() == aVecSwFrameFormat.size(), "Missing new Flys" );
4083 if ( aSet.size() != aVecSwFrameFormat.size() )
4084 return;
4086 size_t n = 0;
4087 for (const auto& rFlyN : aSet)
4089 const SwFrameFormat *pFormatN = rFlyN.GetFormat();
4090 const SwFormatChain &rChain = pFormatN->GetChain();
4091 size_t k = 0;
4092 for (const auto& rFlyK : aSet)
4094 const SwFrameFormat *pFormatK = rFlyK.GetFormat();
4095 if ( rChain.GetPrev() == pFormatK )
4097 ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]),
4098 static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]) );
4100 else if ( rChain.GetNext() == pFormatK )
4102 ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]),
4103 static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]) );
4105 ++k;
4107 ++n;
4110 // Re-create content property of draw formats, knowing how old shapes
4111 // were paired with old fly formats (aOldTextBoxes) and that aSet is
4112 // parallel with aVecSwFrameFormat.
4113 SwTextBoxHelper::restoreLinks(aSet, aVecSwFrameFormat, aOldTextBoxes);
4117 * Reset the text's hard formatting
4119 /** @params pArgs contains the document's ChrFormatTable
4120 * Is need for selections at the beginning/end and with no SSelection.
4122 bool DocumentContentOperationsManager::lcl_RstTextAttr( SwNode* pNd, void* pArgs )
4124 ParaRstFormat* pPara = static_cast<ParaRstFormat*>(pArgs);
4125 if (pPara->pLayout && pPara->pLayout->HasMergedParas()
4126 && pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)
4128 return true; // skip hidden, since new items aren't applied
4130 SwTextNode * pTextNode = pNd->GetTextNode();
4131 if( pTextNode && pTextNode->GetpSwpHints() )
4133 SwContentIndex aSt( pTextNode, 0 );
4134 sal_Int32 nEnd = pTextNode->Len();
4136 if( &pPara->pSttNd->GetNode() == pTextNode &&
4137 pPara->pSttNd->GetContentIndex() )
4138 aSt = pPara->pSttNd->GetContentIndex();
4140 if( &pPara->pEndNd->GetNode() == pNd )
4141 nEnd = pPara->pEndNd->GetContentIndex();
4143 if( pPara->pHistory )
4145 // Save all attributes for the Undo.
4146 SwRegHistory aRHst( *pTextNode, pPara->pHistory );
4147 pTextNode->GetpSwpHints()->Register( &aRHst );
4148 pTextNode->RstTextAttr( aSt.GetIndex(), nEnd - aSt.GetIndex(), pPara->nWhich,
4149 pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange );
4150 if( pTextNode->GetpSwpHints() )
4151 pTextNode->GetpSwpHints()->DeRegister();
4153 else
4154 pTextNode->RstTextAttr( aSt.GetIndex(), nEnd - aSt.GetIndex(), pPara->nWhich,
4155 pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange );
4157 return true;
4160 DocumentContentOperationsManager::~DocumentContentOperationsManager()
4163 //Private methods
4165 bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl(SwPaM & rPam, SwDeleteFlags const flags)
4167 assert(m_rDoc.getIDocumentRedlineAccess().IsRedlineOn());
4169 RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
4171 if (*rPam.GetPoint() == *rPam.GetMark())
4173 return false; // do not add empty redlines
4176 std::vector<std::unique_ptr<SwRangeRedline>> redlines;
4178 auto pRedline(std::make_unique<SwRangeRedline>(RedlineType::Delete, rPam));
4179 if (pRedline->HasValidRange())
4181 redlines.push_back(std::move(pRedline));
4183 else // sigh ... why is such a selection even possible...
4184 { // split it up so we get one SwUndoRedlineDelete per inserted RL
4185 redlines = GetAllValidRanges(std::move(pRedline));
4189 if (redlines.empty())
4191 return false;
4194 // tdf#54819 current redlining needs also modification of paragraph style and
4195 // attributes added to the same grouped Undo
4196 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4197 m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
4199 auto & rDMA(*m_rDoc.getIDocumentMarkAccess());
4200 std::vector<std::unique_ptr<SwUndo>> MarkUndos;
4201 for (auto iter = rDMA.getAnnotationMarksBegin();
4202 iter != rDMA.getAnnotationMarksEnd(); )
4204 // tdf#111524 remove annotation marks that have their field
4205 // characters deleted
4206 SwPosition const& rEndPos((**iter).GetMarkEnd());
4207 if (*rPam.Start() < rEndPos && rEndPos <= *rPam.End())
4209 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4211 MarkUndos.emplace_back(std::make_unique<SwUndoDeleteBookmark>(**iter));
4213 // iter is into annotation mark vector so must be dereferenced!
4214 rDMA.deleteMark(&**iter);
4215 // this invalidates iter, have to start over...
4216 iter = rDMA.getAnnotationMarksBegin();
4218 else
4219 { // marks are sorted by start
4220 if (*rPam.End() < (**iter).GetMarkStart())
4222 break;
4224 ++iter;
4228 // tdf#119019 accept tracked paragraph formatting to do not hide new deletions
4229 if (*rPam.GetPoint() != *rPam.GetMark())
4230 m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting(rPam);
4232 std::vector<std::unique_ptr<SwUndoRedlineDelete>> undos;
4233 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4235 // this should no longer happen in calls from the UI but maybe via API
4236 // (randomTest and testTdf54819 triggers it)
4237 SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask,
4238 "sw.core", "redlines will be moved in DeleteAndJoin");
4239 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags(
4240 RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete);
4242 for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
4244 assert(pRedline->HasValidRange());
4245 undos.emplace_back(std::make_unique<SwUndoRedlineDelete>(
4246 *pRedline, SwUndoId::DELETE, flags));
4248 const SwRewriter aRewriter = undos.front()->GetRewriter();
4249 // can only group a single undo action
4250 if (MarkUndos.empty() && undos.size() == 1
4251 && m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4253 SwUndo * const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() );
4254 SwUndoRedlineDelete *const pUndoRedlineDel(dynamic_cast<SwUndoRedlineDelete*>(pLastUndo));
4255 bool const bMerged = pUndoRedlineDel
4256 && pUndoRedlineDel->CanGrouping(*undos.front());
4257 if (!bMerged)
4259 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(undos.front()));
4261 undos.clear(); // prevent unmatched EndUndo
4263 else
4265 m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter);
4266 for (auto& it : MarkUndos)
4268 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it));
4270 for (auto & it : undos)
4272 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it));
4277 for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
4279 // note: 1. the pRedline can still be merged & deleted
4280 // 2. the impl. can even DeleteAndJoin the range => no plain PaM
4281 std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*pRedline->GetMark()));
4282 pCursor->SetMark();
4283 *pCursor->GetPoint() = *pRedline->GetPoint();
4284 m_rDoc.getIDocumentRedlineAccess().AppendRedline(pRedline.release(), true);
4285 // sw_redlinehide: 2 reasons why this is needed:
4286 // 1. it's the first redline in node => RedlineDelText was sent but ignored
4287 // 2. redline spans multiple nodes => must merge text frames
4288 sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor);
4290 m_rDoc.getIDocumentState().SetModified();
4292 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4294 if (!undos.empty())
4296 m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4298 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
4301 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4302 m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4304 return true;
4307 bool DocumentContentOperationsManager::DeleteAndJoinImpl(SwPaM & rPam, SwDeleteFlags const flags)
4309 bool bJoinText, bJoinPrev;
4310 ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev );
4312 bool const bSuccess( DeleteRangeImpl(rPam, flags) );
4313 if (!bSuccess)
4314 return false;
4316 if( bJoinText )
4318 ::sw_JoinText( rPam, bJoinPrev );
4321 if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
4322 && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
4324 m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
4327 return true;
4330 bool DocumentContentOperationsManager::DeleteRangeImpl(SwPaM & rPam, SwDeleteFlags const flags)
4332 // Move all cursors out of the deleted range, but first copy the
4333 // passed PaM, because it could be a cursor that would be moved!
4334 SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
4336 SwPosition const pos(GetCorrPosition(aDelPam));
4337 ::PaMCorrAbs(aDelPam, pos);
4340 bool const bSuccess( DeleteRangeImplImpl(aDelPam, flags) );
4341 if (bSuccess)
4342 { // now copy position from temp copy to given PaM
4343 *rPam.GetPoint() = *aDelPam.GetPoint();
4346 return bSuccess;
4349 bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam, SwDeleteFlags const flags)
4351 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
4353 if (!rPam.HasMark()
4354 || (*pStt == *pEnd && !IsFlySelectedByCursor(m_rDoc, *pStt, *pEnd)))
4356 return false;
4359 if( m_rDoc.GetAutoCorrExceptWord() )
4361 // if necessary the saved Word for the exception
4362 if( m_rDoc.GetAutoCorrExceptWord()->IsDeleted() || pStt->GetNode() != pEnd->GetNode() ||
4363 pStt->GetContentIndex() + 1 != pEnd->GetContentIndex() ||
4364 !m_rDoc.GetAutoCorrExceptWord()->CheckDelChar( *pStt ))
4365 { m_rDoc.DeleteAutoCorrExceptWord(); }
4369 // Delete all empty TextHints at the Mark's position
4370 SwTextNode* pTextNd = rPam.GetMark()->GetNode().GetTextNode();
4371 SwpHints* pHts;
4372 if( pTextNd && nullptr != ( pHts = pTextNd->GetpSwpHints()) && pHts->Count() )
4374 const sal_Int32 nMkCntPos = rPam.GetMark()->GetContentIndex();
4375 for( size_t n = pHts->Count(); n; )
4377 const SwTextAttr* pAttr = pHts->Get( --n );
4378 if( nMkCntPos > pAttr->GetStart() )
4379 break;
4381 const sal_Int32 *pEndIdx;
4382 if( nMkCntPos == pAttr->GetStart() &&
4383 nullptr != (pEndIdx = pAttr->End()) &&
4384 *pEndIdx == pAttr->GetStart() )
4385 pTextNd->DestroyAttr( pHts->Cut( n ) );
4391 // Send DataChanged before deletion, so that we still know
4392 // which objects are in the range.
4393 // Afterwards they could be before/after the Position.
4394 SwDataChanged aTmp( rPam );
4397 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4399 m_rDoc.GetIDocumentUndoRedo().ClearRedo();
4400 bool bMerged(false);
4401 if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4403 SwUndo *const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() );
4404 SwUndoDelete *const pUndoDelete(
4405 dynamic_cast<SwUndoDelete *>(pLastUndo) );
4406 if (pUndoDelete)
4408 bMerged = pUndoDelete->CanGrouping(m_rDoc, rPam);
4409 // if CanGrouping() returns true it's already merged
4412 if (!bMerged)
4414 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoDelete>(rPam, flags));
4417 m_rDoc.getIDocumentState().SetModified();
4419 return true;
4422 if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
4423 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any );
4425 // Delete and move all "Flys at the paragraph", which are within the Selection
4426 if (!(flags & SwDeleteFlags::ArtificialSelection))
4428 DelFlyInRange(rPam.GetMark()->GetNode(), rPam.GetPoint()->GetNode(),
4429 rPam.GetMark()->GetContentIndex(), rPam.GetPoint()->GetContentIndex());
4431 DelBookmarks(
4432 pStt->GetNode(),
4433 pEnd->GetNode(),
4434 nullptr,
4435 pStt->GetContentIndex(),
4436 pEnd->GetContentIndex());
4438 SwNodeIndex aSttIdx( pStt->GetNode() );
4439 SwContentNode * pCNd = aSttIdx.GetNode().GetContentNode();
4441 do { // middle checked loop!
4442 if( pCNd )
4444 SwTextNode * pStartTextNode( pCNd->GetTextNode() );
4445 if ( pStartTextNode )
4447 // now move the Content to the new Node
4448 bool bOneNd = pStt->GetNode() == pEnd->GetNode();
4449 const sal_Int32 nLen = ( bOneNd ? pEnd->GetContentIndex()
4450 : pCNd->Len() )
4451 - pStt->GetContentIndex();
4453 // Don't call again, if already empty
4454 if( nLen )
4456 pStartTextNode->EraseText( *pStt, nLen );
4458 if( !pStartTextNode->Len() )
4460 // METADATA: remove reference if empty (consider node deleted)
4461 pStartTextNode->RemoveMetadataReference();
4465 if( bOneNd ) // that's it
4466 break;
4468 ++aSttIdx;
4470 else
4472 // So that there are no indices left registered when deleted,
4473 // we remove a SwPaM from the Content here.
4474 pStt->nContent.Assign( nullptr, 0 );
4478 pCNd = pEnd->GetNode().GetContentNode();
4479 if( pCNd )
4481 SwTextNode * pEndTextNode( pCNd->GetTextNode() );
4482 if( pEndTextNode )
4484 // if already empty, don't call again
4485 if( pEnd->GetContentIndex() )
4487 SwContentIndex aIdx( pCNd, 0 );
4488 pEndTextNode->EraseText( aIdx, pEnd->GetContentIndex() );
4490 if( !pEndTextNode->Len() )
4492 // METADATA: remove reference if empty (consider node deleted)
4493 pEndTextNode->RemoveMetadataReference();
4497 else
4499 // So that there are no indices left registered when deleted,
4500 // we remove a SwPaM from the Content here.
4501 pEnd->nContent.Assign( nullptr, 0 );
4505 // if the end is not a content node, delete it as well
4506 SwNodeOffset nEnd = pEnd->GetNodeIndex();
4507 if( pCNd == nullptr )
4508 nEnd++;
4510 if( aSttIdx != nEnd )
4512 // tdf#134436 delete section nodes like SwUndoDelete::SwUndoDelete
4513 SwNode *pTmpNd;
4514 while (pEnd == rPam.GetPoint()
4515 && nEnd + SwNodeOffset(2) < m_rDoc.GetNodes().Count()
4516 && (pTmpNd = m_rDoc.GetNodes()[nEnd + 1])->IsEndNode()
4517 && pTmpNd->StartOfSectionNode()->IsSectionNode()
4518 && aSttIdx <= pTmpNd->StartOfSectionNode()->GetIndex())
4520 SwNodeRange range(*pTmpNd->StartOfSectionNode(), *pTmpNd);
4521 m_rDoc.GetNodes().SectionUp(&range);
4522 --nEnd; // account for deleted start node
4525 // delete the Nodes from the NodesArray
4526 m_rDoc.GetNodes().Delete( aSttIdx, nEnd - aSttIdx.GetIndex() );
4529 // If the Node that contained the Cursor has been deleted,
4530 // the Content has to be assigned to the current Content.
4531 if (pStt->GetNode().GetContentNode())
4532 pStt->SetContent( pStt->GetContentIndex() );
4534 // If we deleted across Node boundaries we have to correct the PaM,
4535 // because they are in different Nodes now.
4536 // Also, the Selection is revoked.
4537 *pEnd = *pStt;
4538 rPam.DeleteMark();
4540 } while( false );
4542 m_rDoc.getIDocumentState().SetModified();
4544 return true;
4547 // It's possible to call Replace with a PaM that spans 2 paragraphs:
4548 // search with regex for "$", then replace _all_
4549 bool DocumentContentOperationsManager::ReplaceRangeImpl( SwPaM& rPam, const OUString& rStr,
4550 const bool bRegExReplace )
4552 if (!rPam.HasMark())
4553 return false;
4555 bool bJoinText, bJoinPrev;
4556 ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev );
4559 // Create a copy of the Cursor in order to move all Pams from
4560 // the other views out of the deletion range.
4561 // Except for itself!
4562 SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
4563 ::PaMCorrAbs( aDelPam, *aDelPam.End() );
4565 auto [pStt, pEnd] = aDelPam.StartEnd(); // SwPosition*
4566 bool bOneNode = pStt->GetNode() == pEnd->GetNode();
4568 // Own Undo?
4569 OUString sRepl( rStr );
4570 SwTextNode* pTextNd = pStt->GetNode().GetTextNode();
4571 sal_Int32 nStt = pStt->GetContentIndex();
4572 sal_Int32 nEnd;
4574 SwDataChanged aTmp( aDelPam );
4576 if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
4578 RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
4579 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4581 // this should no longer happen in calls from the UI but maybe via API
4582 SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask,
4583 "sw.core", "redlines will be moved in ReplaceRange");
4585 m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
4587 // If any Redline will change (split!) the node
4588 const ::sw::mark::IMark* pBkmk =
4589 m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam,
4590 OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
4591 ::sw::mark::InsertMode::New);
4593 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags(
4594 RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete );
4596 *aDelPam.GetPoint() = pBkmk->GetMarkPos();
4597 if(pBkmk->IsExpanded())
4598 *aDelPam.GetMark() = pBkmk->GetOtherMarkPos();
4599 m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk);
4600 pStt = aDelPam.Start();
4601 pTextNd = pStt->GetNode().GetTextNode();
4602 nStt = pStt->GetContentIndex();
4605 if( !sRepl.isEmpty() )
4607 // Apply the first character's attributes to the ReplaceText
4608 SfxItemSetFixed
4609 <RES_CHRATR_BEGIN, RES_TXTATR_WITHEND_END - 1,
4610 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1> aSet( m_rDoc.GetAttrPool() );
4611 pTextNd->GetParaAttr( aSet, nStt+1, nStt+1 );
4613 aSet.ClearItem( RES_TXTATR_REFMARK );
4614 aSet.ClearItem( RES_TXTATR_TOXMARK );
4615 aSet.ClearItem( RES_TXTATR_CJK_RUBY );
4616 aSet.ClearItem( RES_TXTATR_INETFMT );
4617 aSet.ClearItem( RES_TXTATR_META );
4618 aSet.ClearItem( RES_TXTATR_METAFIELD );
4620 if( aDelPam.GetPoint() != aDelPam.End() )
4621 aDelPam.Exchange();
4623 // Remember the End
4624 SwNodeIndex aPtNd( aDelPam.GetPoint()->GetNode(), -1 );
4625 const sal_Int32 nPtCnt = aDelPam.GetPoint()->GetContentIndex();
4627 bool bFirst = true;
4628 OUString sIns;
4629 while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) )
4631 InsertString( aDelPam, sIns );
4632 if( bFirst )
4634 SwNodeIndex aMkNd( aDelPam.GetMark()->GetNode(), -1 );
4635 const sal_Int32 nMkCnt = aDelPam.GetMark()->GetContentIndex();
4637 SplitNode( *aDelPam.GetPoint(), false );
4639 ++aMkNd;
4640 aDelPam.GetMark()->Assign( aMkNd, nMkCnt );
4641 bFirst = false;
4643 else
4644 SplitNode( *aDelPam.GetPoint(), false );
4646 if( !sIns.isEmpty() )
4648 InsertString( aDelPam, sIns );
4651 SwPaM aTmpRange( *aDelPam.GetPoint() );
4652 aTmpRange.SetMark();
4654 ++aPtNd;
4655 aDelPam.GetPoint()->Assign(aPtNd, nPtCnt);
4656 *aTmpRange.GetMark() = *aDelPam.GetPoint();
4658 m_rDoc.RstTextAttrs( aTmpRange );
4659 InsertItemSet( aTmpRange, aSet );
4662 // tdf#139982: Appending the redline may immediately delete flys
4663 // anchored in the previous text if it's inside an insert redline.
4664 // Also flys will be deleted if the redline is accepted. Move them
4665 // to the position between the previous text and the new text,
4666 // there the chance of surviving both accept and reject is best.
4667 SaveFlyArr flys;
4668 SaveFlyInRange(aDelPam, *aDelPam.End(), flys, false);
4670 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4672 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
4673 std::make_unique<SwUndoRedlineDelete>( aDelPam, SwUndoId::REPLACE ));
4675 // add redline similar to DeleteAndJoinWithRedlineImpl()
4676 std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*aDelPam.GetMark()));
4677 pCursor->SetMark();
4678 *pCursor->GetPoint() = *aDelPam.GetPoint();
4679 m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDelPam ), true);
4680 RestFlyInRange(flys, *aDelPam.End(), &aDelPam.End()->GetNode(), true);
4681 sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor);
4683 *rPam.GetMark() = *aDelPam.GetMark();
4684 if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4686 *aDelPam.GetPoint() = *rPam.GetPoint();
4687 m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4689 // If any Redline will change (split!) the node
4690 const ::sw::mark::IMark* pBkmk =
4691 m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam,
4692 OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
4693 ::sw::mark::InsertMode::New);
4695 aDelPam.GetPoint()->Assign( SwNodeOffset(0) );
4696 aDelPam.GetMark()->Assign( SwNodeOffset(0) );
4697 rPam.GetPoint()->Assign( SwNodeOffset(0) );
4698 *rPam.GetMark() = *rPam.GetPoint();
4699 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
4701 *rPam.GetPoint() = pBkmk->GetMarkPos();
4702 *rPam.GetMark() = pBkmk->IsExpanded() ? pBkmk->GetOtherMarkPos() : pBkmk->GetMarkPos();
4704 m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk);
4706 bJoinText = false;
4708 else
4710 assert((pStt->GetNode() == pEnd->GetNode() ||
4711 ( pStt->GetNodeIndex() + 1 == pEnd->GetNodeIndex() &&
4712 !pEnd->GetContentIndex() )) &&
4713 "invalid range: Point and Mark on different nodes" );
4715 if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
4716 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aDelPam, true, RedlineType::Any );
4718 SwUndoReplace* pUndoRpl = nullptr;
4719 bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
4720 if (bDoesUndo)
4722 pUndoRpl = new SwUndoReplace(aDelPam, sRepl, bRegExReplace);
4723 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoRpl));
4725 ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo());
4727 if( aDelPam.GetPoint() != pStt )
4728 aDelPam.Exchange();
4730 SwNodeIndex aPtNd( pStt->GetNode(), -1 );
4731 const sal_Int32 nPtCnt = pStt->GetContentIndex();
4733 // Set the values again, if Frames or footnotes on the Text have been removed.
4734 nStt = nPtCnt;
4735 nEnd = bOneNode ? pEnd->GetContentIndex()
4736 : pTextNd->GetText().getLength();
4738 bool bFirst = true;
4739 OUString sIns;
4740 while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) )
4742 if (!bFirst || nStt == pTextNd->GetText().getLength())
4744 InsertString( aDelPam, sIns );
4746 else if( nStt < nEnd || !sIns.isEmpty() )
4748 pTextNd->ReplaceText( *pStt, nEnd - nStt, sIns );
4750 SplitNode( *pStt, false);
4751 bFirst = false;
4754 if( bFirst || !sIns.isEmpty() )
4756 if (!bFirst || nStt == pTextNd->GetText().getLength())
4758 InsertString( aDelPam, sIns );
4760 else if( nStt < nEnd || !sIns.isEmpty() )
4762 pTextNd->ReplaceText( *pStt, nEnd - nStt, sIns );
4766 *rPam.GetPoint() = *aDelPam.GetMark();
4767 ++aPtNd;
4768 rPam.GetMark()->Assign( aPtNd, nPtCnt );
4770 if (bJoinText)
4772 assert(rPam.GetPoint() == rPam.End());
4773 // move so that SetEnd remembers position after sw_JoinText
4774 rPam.Move(fnMoveBackward);
4776 else if (aDelPam.GetPoint() == pStt) // backward selection?
4778 assert(*rPam.GetMark() <= *rPam.GetPoint());
4779 rPam.Exchange(); // swap so that rPam is backwards
4782 if( pUndoRpl )
4784 pUndoRpl->SetEnd(rPam);
4789 bool bRet(true);
4790 if (bJoinText)
4792 bRet = ::sw_JoinText(rPam, bJoinPrev);
4795 m_rDoc.getIDocumentState().SetModified();
4796 return bRet;
4799 SwFlyFrameFormat* DocumentContentOperationsManager::InsNoTextNode( const SwPosition& rPos, SwNoTextNode* pNode,
4800 const SfxItemSet* pFlyAttrSet,
4801 const SfxItemSet* pGrfAttrSet,
4802 SwFrameFormat* pFrameFormat)
4804 SwFlyFrameFormat *pFormat = nullptr;
4805 if( pNode )
4807 pFormat = m_rDoc.MakeFlySection_( rPos, *pNode, RndStdIds::FLY_AT_PARA,
4808 pFlyAttrSet, pFrameFormat );
4809 if( pGrfAttrSet )
4810 pNode->SetAttr( *pGrfAttrSet );
4812 return pFormat;
4815 #define NUMRULE_STATE \
4816 std::shared_ptr<SwNumRuleItem> aNumRuleItemHolderIfSet; \
4817 std::shared_ptr<SfxStringItem> aListIdItemHolderIfSet; \
4819 #define PUSH_NUMRULE_STATE \
4820 lcl_PushNumruleState( aNumRuleItemHolderIfSet, aListIdItemHolderIfSet, pDestTextNd );
4822 #define POP_NUMRULE_STATE \
4823 lcl_PopNumruleState( aNumRuleItemHolderIfSet, aListIdItemHolderIfSet, pDestTextNd, rPam );
4825 static void lcl_PushNumruleState(
4826 std::shared_ptr<SwNumRuleItem>& aNumRuleItemHolderIfSet,
4827 std::shared_ptr<SfxStringItem>& aListIdItemHolderIfSet,
4828 const SwTextNode *pDestTextNd )
4830 // Safe numrule item at destination.
4831 // #i86492# - Safe also <ListId> item of destination.
4832 const SfxItemSet * pAttrSet = pDestTextNd->GetpSwAttrSet();
4833 if (pAttrSet == nullptr)
4834 return;
4836 if (const SwNumRuleItem* pItem = pAttrSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
4838 aNumRuleItemHolderIfSet.reset(pItem->Clone());
4841 if (const SfxStringItem* pItem = pAttrSet->GetItemIfSet(RES_PARATR_LIST_ID, false))
4843 aListIdItemHolderIfSet.reset(pItem->Clone());
4847 static void lcl_PopNumruleState(
4848 const std::shared_ptr<SwNumRuleItem>& aNumRuleItemHolderIfSet,
4849 const std::shared_ptr<SfxStringItem>& aListIdItemHolderIfSet,
4850 SwTextNode *pDestTextNd, const SwPaM& rPam )
4852 /* If only a part of one paragraph is copied
4853 restore the numrule at the destination. */
4854 // #i86492# - restore also <ListId> item
4855 if ( lcl_MarksWholeNode(rPam) )
4856 return;
4858 if (aNumRuleItemHolderIfSet)
4860 pDestTextNd->SetAttr(*aNumRuleItemHolderIfSet);
4862 else
4864 pDestTextNd->ResetAttr(RES_PARATR_NUMRULE);
4867 if (aListIdItemHolderIfSet)
4869 pDestTextNd->SetAttr(*aListIdItemHolderIfSet);
4871 else
4873 pDestTextNd->ResetAttr(RES_PARATR_LIST_ID);
4877 bool DocumentContentOperationsManager::CopyImpl(SwPaM& rPam, SwPosition& rPos,
4878 SwCopyFlags const flags,
4879 SwPaM *const pCopyRange) const
4881 std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
4883 sw::CalcBreaks(Breaks, rPam, true);
4885 if (Breaks.empty())
4887 return CopyImplImpl(rPam, rPos, flags, pCopyRange);
4890 SwPosition const & rSelectionEnd( *rPam.End() );
4892 bool bRet(true);
4893 bool bFirst(true);
4894 // iterate from end to start, ... don't think it's necessary here?
4895 auto iter( Breaks.rbegin() );
4896 SwNodeOffset nOffset(0);
4897 SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
4898 SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node!
4899 SwPosition & rEnd( *aPam.End() );
4900 SwPosition & rStart( *aPam.Start() );
4901 SwPaM copyRange(rPos, rPos);
4903 while (iter != Breaks.rend())
4905 rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
4906 if (rStart < rEnd) // check if part is empty
4908 // pass in copyRange member as rPos; should work ...
4909 bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, &copyRange);
4910 nOffset = iter->first - rStart.GetNodeIndex(); // fly nodes...
4911 if (pCopyRange)
4913 if (bFirst)
4915 pCopyRange->SetMark();
4916 *pCopyRange->GetMark() = *copyRange.End();
4918 *pCopyRange->GetPoint() = *copyRange.Start();
4920 bFirst = false;
4922 rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
4923 ++iter;
4926 rStart = *rPam.Start(); // set to original start
4927 if (rStart < rEnd) // check if part is empty
4929 bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, &copyRange);
4930 if (pCopyRange)
4932 if (bFirst)
4934 pCopyRange->SetMark();
4935 *pCopyRange->GetMark() = *copyRange.End();
4937 *pCopyRange->GetPoint() = *copyRange.Start();
4941 return bRet;
4944 bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPos,
4945 SwCopyFlags const flags,
4946 SwPaM *const pCpyRange) const
4948 SwDoc& rDoc = rPos.GetNode().GetDoc();
4949 const bool bColumnSel = rDoc.IsClipBoard() && rDoc.IsColumnSelection();
4951 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
4953 // Catch when there's no copy to do.
4954 if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel) ||
4955 //JP 29.6.2001: 88963 - don't copy if inspos is in region of start to end
4956 //JP 15.11.2001: don't test inclusive the end, ever exclusive
4957 ( &rDoc == &m_rDoc && *pStt <= rPos && rPos < *pEnd ))
4959 return false;
4962 const bool bEndEqualIns = &rDoc == &m_rDoc && rPos == *pEnd;
4964 // If Undo is enabled, create the UndoCopy object
4965 SwUndoCpyDoc* pUndo = nullptr;
4966 // lcl_DeleteRedlines may delete the start or end node of the cursor when
4967 // removing the redlines so use cursor that is corrected by PaMCorrAbs
4968 std::shared_ptr<SwUnoCursor> const pCopyPam(rDoc.CreateUnoCursor(rPos));
4970 SwTableNumFormatMerge aTNFM( m_rDoc, rDoc );
4971 std::optional<std::vector<SwFrameFormat*>> pFlys;
4972 std::vector<SwFrameFormat*> const* pFlysAtInsPos;
4974 if (rDoc.GetIDocumentUndoRedo().DoesUndo())
4976 pUndo = new SwUndoCpyDoc(*pCopyPam);
4977 pFlysAtInsPos = pUndo->GetFlysAnchoredAt();
4979 else
4981 pFlys = sw::GetFlysAnchoredAt(rDoc, rPos.GetNodeIndex());
4982 pFlysAtInsPos = pFlys ? &*pFlys : nullptr;
4985 RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
4986 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
4988 // Move the PaM one node back from the insert position, so that
4989 // the position doesn't get moved
4990 pCopyPam->SetMark();
4991 bool bCanMoveBack = pCopyPam->Move(fnMoveBackward, GoInContent);
4992 // If the position was shifted from more than one node, an end node has been skipped
4993 bool bAfterTable = false;
4994 if ((rPos.GetNodeIndex() - pCopyPam->GetPoint()->GetNodeIndex()) > SwNodeOffset(1))
4996 // First go back to the original place
4997 *(pCopyPam->GetPoint()) = rPos;
4999 bCanMoveBack = false;
5000 bAfterTable = true;
5002 if( !bCanMoveBack )
5004 pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1));
5005 assert(pCopyPam->GetPoint()->GetContentIndex() == 0);
5008 SwNodeRange aRg( pStt->GetNode(), pEnd->GetNode() );
5009 SwNodeIndex aInsPos( rPos.GetNode() );
5010 const bool bOneNode = pStt->GetNode() == pEnd->GetNode();
5011 SwTextNode* pSttTextNd = pStt->GetNode().GetTextNode();
5012 SwTextNode* pEndTextNd = pEnd->GetNode().GetTextNode();
5013 SwTextNode* pDestTextNd = aInsPos.GetNode().GetTextNode();
5014 bool bCopyCollFormat = !rDoc.IsInsOnlyTextGlossary() &&
5015 ( (pDestTextNd && !pDestTextNd->GetText().getLength()) ||
5016 ( !bOneNode && !rPos.GetContentIndex() ) );
5017 bool bCopyBookmarks = true;
5018 bool bCopyPageSource = false;
5019 SwNodeOffset nDeleteTextNodes(0);
5021 // #i104585# copy outline num rule to clipboard (for ASCII filter)
5022 if (rDoc.IsClipBoard() && m_rDoc.GetOutlineNumRule())
5024 rDoc.SetOutlineNumRule(*m_rDoc.GetOutlineNumRule());
5027 // #i86492#
5028 // Correct the search for a previous list:
5029 // First search for non-outline numbering list. Then search for non-outline
5030 // bullet list.
5031 // Keep also the <ListId> value for possible propagation.
5032 OUString aListIdToPropagate;
5033 const SwNumRule* pNumRuleToPropagate =
5034 rDoc.SearchNumRule( rPos, false, true, false, 0, aListIdToPropagate, nullptr, true );
5035 if ( !pNumRuleToPropagate )
5037 pNumRuleToPropagate =
5038 rDoc.SearchNumRule( rPos, false, false, false, 0, aListIdToPropagate, nullptr, true );
5040 // #i86492#
5041 // Do not propagate previous found list, if
5042 // - destination is an empty paragraph which is not in a list and
5043 // - source contains at least one paragraph which is not in a list
5044 if ( pNumRuleToPropagate &&
5045 pDestTextNd && !pDestTextNd->GetText().getLength() &&
5046 !pDestTextNd->IsInList() &&
5047 !lcl_ContainsOnlyParagraphsInList( rPam ) )
5049 pNumRuleToPropagate = nullptr;
5052 // This do/while block is only there so that we can break out of it!
5053 do {
5054 if( pSttTextNd )
5056 ++nDeleteTextNodes; // must be joined in Undo
5057 // Don't copy the beginning completely?
5058 if( !bCopyCollFormat || bColumnSel || pStt->GetContentIndex() )
5060 SwContentIndex aDestIdx( rPos.GetContentNode(), rPos.GetContentIndex() );
5061 bool bCopyOk = false;
5062 if( !pDestTextNd )
5064 if( pStt->GetContentIndex() || bOneNode )
5065 pDestTextNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode(),
5066 rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD));
5067 else
5069 pDestTextNd = pSttTextNd->MakeCopy(rDoc, aInsPos.GetNode(), true)->GetTextNode();
5070 bCopyOk = true;
5072 aDestIdx.Assign( pDestTextNd, 0 );
5073 bCopyCollFormat = true;
5075 else if( !bOneNode || bColumnSel )
5077 const sal_Int32 nContentEnd = pEnd->GetContentIndex();
5079 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5080 rDoc.getIDocumentContentOperations().SplitNode( rPos, false );
5083 if (bCanMoveBack && rPos == *pCopyPam->GetPoint())
5085 // after the SplitNode, span the CpyPam correctly again
5086 pCopyPam->Move( fnMoveBackward, GoInContent );
5087 pCopyPam->Move( fnMoveBackward, GoInContent );
5090 pDestTextNd = rDoc.GetNodes()[ aInsPos.GetIndex()-SwNodeOffset(1) ]->GetTextNode();
5091 aDestIdx.Assign(
5092 pDestTextNd, pDestTextNd->GetText().getLength());
5094 // Correct the area again
5095 if( bEndEqualIns )
5097 bool bChg = pEnd != rPam.GetPoint();
5098 if( bChg )
5099 rPam.Exchange();
5100 rPam.Move( fnMoveBackward, GoInContent );
5101 if( bChg )
5102 rPam.Exchange();
5104 else if( rPos == *pEnd )
5106 // The end was also moved
5107 pEnd->Adjust(SwNodeOffset(-1));
5108 pEnd->SetContent( nContentEnd );
5110 // tdf#63022 always reset pEndTextNd after SplitNode
5111 aRg.aEnd = pEnd->GetNode();
5112 pEndTextNd = pEnd->GetNode().GetTextNode();
5115 NUMRULE_STATE
5116 if( bCopyCollFormat && bOneNode )
5118 PUSH_NUMRULE_STATE
5121 if( !bCopyOk )
5123 const sal_Int32 nCpyLen = ( bOneNode
5124 ? pEnd->GetContentIndex()
5125 : pSttTextNd->GetText().getLength())
5126 - pStt->GetContentIndex();
5127 pSttTextNd->CopyText( pDestTextNd, aDestIdx, *pStt, nCpyLen );
5128 if( bEndEqualIns )
5129 pEnd->AdjustContent( -nCpyLen );
5132 ++aRg.aStart;
5134 if( bOneNode )
5136 if (bCopyCollFormat)
5138 // tdf#138897 no Undo for applying style, SwUndoInserts does it
5139 pSttTextNd->CopyCollFormat(*pDestTextNd, false);
5140 POP_NUMRULE_STATE
5143 // Copy at-char flys in rPam.
5144 // Update to new (start) node for flys.
5145 // tdf#126626 prevent duplicate Undos.
5146 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5147 CopyFlyInFlyImpl(aRg, &rPam, *pDestTextNd, false);
5149 break;
5153 else if( pDestTextNd )
5155 // Problems with insertion of table selections into "normal" text solved.
5156 // We have to set the correct PaM for Undo, if this PaM starts in a textnode,
5157 // the undo operation will try to merge this node after removing the table.
5158 // If we didn't split a textnode, the PaM should start at the inserted table node
5159 if( rPos.GetContentIndex() == pDestTextNd->Len() )
5160 { // Insertion at the last position of a textnode (empty or not)
5161 ++aInsPos; // The table will be inserted behind the text node
5163 else if( rPos.GetContentIndex() )
5164 { // Insertion in the middle of a text node, it has to be split
5165 // (and joined from undo)
5166 ++nDeleteTextNodes;
5168 const sal_Int32 nContentEnd = pEnd->GetContentIndex();
5170 ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5171 rDoc.getIDocumentContentOperations().SplitNode( rPos, false );
5174 if (bCanMoveBack && rPos == *pCopyPam->GetPoint())
5176 // after the SplitNode, span the CpyPam correctly again
5177 pCopyPam->Move( fnMoveBackward, GoInContent );
5178 pCopyPam->Move( fnMoveBackward, GoInContent );
5181 // Correct the area again
5182 if( bEndEqualIns )
5183 --aRg.aEnd;
5184 // The end would also be moved
5185 else if( rPos == *pEnd )
5187 rPos.Adjust(SwNodeOffset(-1));
5188 rPos.SetContent( nContentEnd );
5189 --aRg.aEnd;
5192 else if( bCanMoveBack )
5193 { // Insertion at the first position of a text node. It will not be split, the table
5194 // will be inserted before the text node.
5195 // See below, before the SetInsertRange function of the undo object will be called,
5196 // the CpyPam would be moved to the next content position. This has to be avoided
5197 // We want to be moved to the table node itself thus we have to set bCanMoveBack
5198 // and to manipulate pCopyPam.
5199 bCanMoveBack = false;
5200 pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1));
5204 pDestTextNd = aInsPos.GetNode().GetTextNode();
5205 if (pEndTextNd)
5207 SwContentIndex aDestIdx( aInsPos.GetNode().GetContentNode(), rPos.GetContentIndex() );
5208 if( !pDestTextNd )
5210 pDestTextNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode(),
5211 rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD));
5212 aDestIdx.Assign( pDestTextNd, 0 );
5213 --aInsPos;
5215 // if we have to insert an extra text node
5216 // at the destination, this node will be our new destination
5217 // (text) node, and thus we increment nDeleteTextNodes. This
5218 // will ensure that this node will be deleted during Undo.
5219 ++nDeleteTextNodes; // must be deleted
5222 const bool bEmptyDestNd = pDestTextNd->GetText().isEmpty();
5224 NUMRULE_STATE
5225 if( bCopyCollFormat && ( bOneNode || bEmptyDestNd ))
5227 PUSH_NUMRULE_STATE
5230 pEndTextNd->CopyText( pDestTextNd, aDestIdx, SwContentIndex( pEndTextNd ),
5231 pEnd->GetContentIndex() );
5233 // Also copy all format templates
5234 if( bCopyCollFormat && ( bOneNode || bEmptyDestNd ))
5236 // tdf#138897 no Undo for applying style, SwUndoInserts does it
5237 pEndTextNd->CopyCollFormat(*pDestTextNd, false);
5238 if ( bOneNode )
5240 POP_NUMRULE_STATE
5245 SfxItemSet aBrkSet( rDoc.GetAttrPool(), aBreakSetRange );
5246 if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd)
5248 if (pSttTextNd && bCopyCollFormat && pDestTextNd->HasSwAttrSet())
5250 aBrkSet.Put( *pDestTextNd->GetpSwAttrSet() );
5251 if( SfxItemState::SET == aBrkSet.GetItemState( RES_BREAK, false ) )
5252 pDestTextNd->ResetAttr( RES_BREAK );
5253 if( SfxItemState::SET == aBrkSet.GetItemState( RES_PAGEDESC, false ) )
5254 pDestTextNd->ResetAttr( RES_PAGEDESC );
5259 SwPosition startPos(pCopyPam->GetPoint()->GetNode(), SwNodeOffset(+1));
5260 if (bCanMoveBack)
5261 { // pCopyPam is actually 1 before the copy range so move it fwd
5262 SwPaM temp(*pCopyPam->GetPoint());
5263 temp.Move(fnMoveForward, GoInContent);
5264 startPos = *temp.GetPoint();
5266 assert(startPos.GetNode().IsContentNode());
5267 std::pair<SwPaM const&, SwPosition const&> tmp(rPam, startPos);
5268 if( aInsPos == pEnd->GetNode() )
5270 SwNodeIndex aSaveIdx( aInsPos, -1 );
5271 assert(pStt->GetNode() != pEnd->GetNode());
5272 pEnd->SetContent(0); // TODO why this?
5273 CopyWithFlyInFly(aRg, aInsPos.GetNode(), &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags);
5274 ++aSaveIdx;
5275 pEnd->Assign(aSaveIdx);
5277 else
5278 CopyWithFlyInFly(aRg, aInsPos.GetNode(), &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags);
5280 bCopyBookmarks = false;
5283 // at-char anchors post SplitNode are on index 0 of 2nd node and will
5284 // remain there - move them back to the start (end would also work?)
5285 // ... also for at-para anchors; here start is preferable because
5286 // it's consistent with SplitNode from SwUndoInserts::RedoImpl()
5287 if (pFlysAtInsPos)
5289 // init *again* - because CopyWithFlyInFly moved startPos
5290 SwPosition startPos(pCopyPam->GetPoint()->GetNode(), SwNodeOffset(+1));
5291 if (bCanMoveBack)
5292 { // pCopyPam is actually 1 before the copy range so move it fwd
5293 SwPaM temp(*pCopyPam->GetPoint());
5294 temp.Move(fnMoveForward, GoInContent);
5295 startPos = *temp.GetPoint();
5297 assert(startPos.GetNode().IsContentNode());
5298 SwPosition startPosAtPara(startPos);
5299 startPosAtPara.nContent.Assign(nullptr, 0);
5301 for (SwFrameFormat * pFly : *pFlysAtInsPos)
5303 SwFormatAnchor const*const pAnchor = &pFly->GetAnchor();
5304 if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
5306 SwFormatAnchor anchor(*pAnchor);
5307 anchor.SetAnchor( &startPos );
5308 pFly->SetFormatAttr(anchor);
5310 else if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA)
5312 SwFormatAnchor anchor(*pAnchor);
5313 anchor.SetAnchor( &startPosAtPara );
5315 bool bSplitFly = false;
5316 if (pFly->GetFlySplit().GetValue())
5318 SwIterator<SwFrame, SwModify> aIter(*pFly);
5319 bSplitFly = aIter.First() && aIter.Next();
5321 if (bSplitFly)
5323 // This fly format has multiple frames, and we change the anchor. Remove the
5324 // old frames, which were based on the old anchor position.
5325 pFly->DelFrames();
5328 pFly->SetFormatAttr(anchor);
5330 if (bSplitFly)
5332 // Re-create the frames now that the new anchor is set.
5333 pFly->MakeFrames();
5339 if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd)
5341 // Put the breaks back into the first node
5342 if( aBrkSet.Count() && nullptr != ( pDestTextNd = rDoc.GetNodes()[
5343 pCopyPam->GetPoint()->GetNodeIndex()+1 ]->GetTextNode()))
5345 pDestTextNd->SetAttr( aBrkSet );
5346 bCopyPageSource = true;
5349 } while( false );
5352 // it is not possible to make this test when copy from the clipBoard to document
5353 // in this case the PageNum not exist anymore
5354 // tdf#39400 and tdf#97526
5355 // when copy from document to ClipBoard, and it is from the first page
5356 // and not the source has the page break
5357 if (rDoc.IsClipBoard() && (rPam.GetPageNum(pStt == rPam.GetPoint()) == 1) && !bCopyPageSource)
5359 if (pDestTextNd)
5361 pDestTextNd->ResetAttr(RES_BREAK); // remove the page-break
5362 pDestTextNd->ResetAttr(RES_PAGEDESC);
5367 // Adjust position (in case it was moved / in another node)
5368 rPos.nContent.Assign( rPos.GetNode().GetContentNode(),
5369 rPos.GetContentIndex() );
5371 if( rPos.GetNode() != aInsPos.GetNode() )
5373 if (aInsPos < rPos.GetNode())
5374 { // tdf#134250 decremented in (pEndTextNd && !pDestTextNd) above
5375 pCopyPam->GetMark()->AssignEndIndex(*aInsPos.GetNode().GetContentNode());
5377 else // incremented in (!pSttTextNd && pDestTextNd) above
5379 pCopyPam->GetMark()->Assign(aInsPos);
5381 rPos = *pCopyPam->GetMark();
5383 else
5384 *pCopyPam->GetMark() = rPos;
5386 if ( !bAfterTable )
5387 pCopyPam->Move( fnMoveForward, bCanMoveBack ? GoInContent : GoInNode );
5388 else
5390 // Reset the offset to 0 as it was before the insertion
5391 pCopyPam->GetPoint()->Adjust(SwNodeOffset(+1));
5393 // If the next node is a start node, then step back: the start node
5394 // has been copied and needs to be in the selection for the undo
5395 if (pCopyPam->GetPoint()->GetNode().IsStartNode())
5396 pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1));
5399 pCopyPam->Exchange();
5401 // Also copy all bookmarks
5402 if( bCopyBookmarks && m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() )
5404 sw::CopyBookmarks(rPam, *pCopyPam->Start());
5407 if( RedlineFlags::DeleteRedlines & eOld )
5409 assert(*pCopyPam->GetPoint() == rPos);
5410 // the Node rPos points to may be deleted so unregister ...
5411 rPos.nContent.Assign(nullptr, 0);
5412 lcl_DeleteRedlines(rPam, *pCopyPam);
5413 rPos = *pCopyPam->GetPoint(); // ... and restore.
5416 // If Undo is enabled, store the inserted area
5417 if (rDoc.GetIDocumentUndoRedo().DoesUndo())
5419 // append it after styles have been copied when copying nodes
5420 rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
5421 pUndo->SetInsertRange(*pCopyPam, true, nDeleteTextNodes);
5424 if( pCpyRange )
5426 pCpyRange->SetMark();
5427 *pCpyRange->GetPoint() = *pCopyPam->GetPoint();
5428 *pCpyRange->GetMark() = *pCopyPam->GetMark();
5431 if ( pNumRuleToPropagate != nullptr )
5433 // #i86492# - use <SwDoc::SetNumRule(..)>, because it also handles the <ListId>
5434 // Don't reset indent attributes, that would mean loss of direct
5435 // formatting.
5436 rDoc.SetNumRule( *pCopyPam, *pNumRuleToPropagate, false, nullptr,
5437 aListIdToPropagate, true, /*bResetIndentAttrs=*/false );
5440 rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
5441 rDoc.getIDocumentState().SetModified();
5443 return true;
5448 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */