Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / source / core / txtnode / ndtxt.cxx
blobb5cc61b13f2b57c7ce0669d60d9872991b4d71b4
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 .
20 #include <hintids.hxx>
21 #include <hints.hxx>
23 #include <comphelper/lok.hxx>
24 #include <comphelper/string.hxx>
25 #include <editeng/fontitem.hxx>
26 #include <editeng/escapementitem.hxx>
27 #include <editeng/lrspitem.hxx>
28 #include <editeng/rsiditem.hxx>
29 #include <sal/log.hxx>
30 #include <osl/diagnose.h>
31 #include <anchoredobject.hxx>
32 #include <txtfld.hxx>
33 #include <txtinet.hxx>
34 #include <fmtanchr.hxx>
35 #include <fmtinfmt.hxx>
36 #include <fmtrfmrk.hxx>
37 #include <txttxmrk.hxx>
38 #include <fchrfmt.hxx>
39 #include <txtftn.hxx>
40 #include <fmtflcnt.hxx>
41 #include <fmtfld.hxx>
42 #include <frmatr.hxx>
43 #include <ftnidx.hxx>
44 #include <ftninfo.hxx>
45 #include <fmtftn.hxx>
46 #include <charfmt.hxx>
47 #include <ndtxt.hxx>
48 #include <doc.hxx>
49 #include <IDocumentUndoRedo.hxx>
50 #include <IDocumentSettingAccess.hxx>
51 #include <IDocumentListsAccess.hxx>
52 #include <IDocumentRedlineAccess.hxx>
53 #include <IDocumentLayoutAccess.hxx>
54 #include <docary.hxx>
55 #include <pam.hxx>
56 #include <fldbas.hxx>
57 #include <paratr.hxx>
58 #include <txtfrm.hxx>
59 #include <ftnfrm.hxx>
60 #include <pagefrm.hxx>
61 #include <rootfrm.hxx>
62 #include <expfld.hxx>
63 #include <section.hxx>
64 #include <mvsave.hxx>
65 #include <SwGrammarMarkUp.hxx>
66 #include <redline.hxx>
67 #include <IMark.hxx>
68 #include <scriptinfo.hxx>
69 #include <istyleaccess.hxx>
70 #include <SwStyleNameMapper.hxx>
71 #include <numrule.hxx>
72 #include <docsh.hxx>
73 #include <SwNodeNum.hxx>
74 #include <svl/grabbagitem.hxx>
75 #include <svl/intitem.hxx>
76 #include <sortedobjs.hxx>
77 #include <calbck.hxx>
78 #include <attrhint.hxx>
79 #include <memory>
80 #include <unoparagraph.hxx>
81 #include <wrtsh.hxx>
82 #include <fmtpdsc.hxx>
83 #include <svx/sdr/attribute/sdrallfillattributeshelper.hxx>
84 #include <svl/itemiter.hxx>
86 using namespace ::com::sun::star;
88 typedef std::vector<SwTextAttr*> SwpHts;
90 namespace sw {
91 class TextNodeNotificationSuppressor {
92 SwTextNode& m_rNode;
93 bool m_bWasNotifiable;
94 public:
95 TextNodeNotificationSuppressor(SwTextNode& rNode)
96 : m_rNode(rNode)
97 , m_bWasNotifiable(rNode.m_bNotifiable)
99 m_rNode.m_bNotifiable = false;
101 ~TextNodeNotificationSuppressor()
103 m_rNode.m_bNotifiable = m_bWasNotifiable;
108 // unfortunately everyone can change Hints without ensuring order or the linking between them
109 #ifdef DBG_UTIL
110 #define CHECK_SWPHINTS(pNd) { if( pNd->GetpSwpHints() && \
111 !pNd->GetDoc().IsInReading() ) \
112 pNd->GetpSwpHints()->Check(true); }
113 #define CHECK_SWPHINTS_IF_FRM(pNd) { if( pNd->GetpSwpHints() && \
114 !pNd->GetDoc().IsInReading() ) \
115 pNd->GetpSwpHints()->Check(getLayoutFrame(nullptr, nullptr, nullptr) != nullptr); }
116 #else
117 #define CHECK_SWPHINTS(pNd)
118 #define CHECK_SWPHINTS_IF_FRM(pNd)
119 #endif
121 SwTextNode *SwNodes::MakeTextNode( SwNode& rWhere,
122 SwTextFormatColl *pColl, bool const bNewFrames)
124 OSL_ENSURE( pColl, "Collection pointer is 0." );
126 SwTextNode *pNode = new SwTextNode( rWhere, pColl, nullptr );
128 SwNodeIndex aIdx( *pNode );
130 // if there is no layout or it is in a hidden section, MakeFrames is not needed
131 const SwSectionNode* pSectNd;
132 if (!bNewFrames ||
133 !GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell() ||
134 ( nullptr != (pSectNd = pNode->FindSectionNode()) &&
135 pSectNd->GetSection().IsHiddenFlag() ))
136 return pNode;
138 SwNodeIndex aTmp( rWhere );
139 do {
140 // max. 2 loops:
141 // 1. take the successor
142 // 2. take the predecessor
144 SwNode * pNd = & aTmp.GetNode();
145 switch (pNd->GetNodeType())
147 case SwNodeType::Table:
148 static_cast<SwTableNode*>(pNd)->MakeFramesForAdjacentContentNode(aIdx);
149 return pNode;
151 case SwNodeType::Section:
152 if( static_cast<SwSectionNode*>(pNd)->GetSection().IsHidden() ||
153 static_cast<SwSectionNode*>(pNd)->IsContentHidden() )
155 pNd = FindPrvNxtFrameNode( *pNode, pNode );
156 if( !pNd )
157 return pNode;
158 aTmp = *pNd;
159 break;
161 static_cast<SwSectionNode*>(pNd)->MakeFramesForAdjacentContentNode(aIdx);
162 return pNode;
164 case SwNodeType::Text:
165 case SwNodeType::Grf:
166 case SwNodeType::Ole:
167 static_cast<SwContentNode*>(pNd)->MakeFramesForAdjacentContentNode(*pNode);
168 return pNode;
170 case SwNodeType::End:
171 if( pNd->StartOfSectionNode()->IsSectionNode() &&
172 aTmp.GetIndex() < rWhere.GetIndex() )
174 if( pNd->StartOfSectionNode()->GetSectionNode()->GetSection().IsHiddenFlag())
176 if( !GoPrevSection( &aTmp, true, false ) ||
177 aTmp.GetNode().FindTableNode() !=
178 pNode->FindTableNode() )
179 return pNode;
181 else
182 aTmp = *pNd->StartOfSectionNode();
183 break;
185 else if( pNd->StartOfSectionNode()->IsTableNode() &&
186 aTmp.GetIndex() < rWhere.GetIndex() )
188 // after a table node
189 aTmp = *pNd->StartOfSectionNode();
190 break;
192 [[fallthrough]];
193 default:
194 if( &rWhere == &aTmp.GetNode() )
195 aTmp -= SwNodeOffset(2);
196 else
197 return pNode;
198 break;
200 } while( true );
203 SwTextNode::SwTextNode( SwNode& rWhere, SwTextFormatColl *pTextColl, const SfxItemSet* pAutoAttr )
204 : SwContentNode( rWhere, SwNodeType::Text, pTextColl ),
205 m_bContainsHiddenChars(false),
206 m_bHiddenCharsHidePara(false),
207 m_bRecalcHiddenCharFlags(false),
208 m_bLastOutlineState( false ),
209 m_bNotifiable( true ),
210 mbEmptyListStyleSetDueToSetOutlineLevelAttr( false ),
211 mbInSetOrResetAttr( false ),
212 m_bInUndo(false)
215 sw::TextNodeNotificationSuppressor(*this);
217 if( pAutoAttr )
218 SetAttr( *pAutoAttr );
220 if (!IsInList() && GetNumRule() && !GetListId().isEmpty())
222 // #i101516#
223 // apply paragraph style's assigned outline style list level as
224 // list level of the paragraph, if it has none set already.
225 if ( !HasAttrListLevel() &&
226 pTextColl && pTextColl->IsAssignedToListLevelOfOutlineStyle() )
228 SetAttrListLevel( pTextColl->GetAssignedOutlineStyleLevel() );
230 AddToList();
233 // call method <UpdateOutlineNode(..)> only for the document nodes array
234 if (GetNodes().IsDocNodes())
235 GetNodes().UpdateOutlineNode(*this);
238 m_bContainsHiddenChars = m_bHiddenCharsHidePara = false;
239 m_bRecalcHiddenCharFlags = true;
242 SwTextNode::~SwTextNode()
244 // delete only removes the pointer not the array elements!
245 if ( m_pSwpHints )
247 // do not delete attributes twice when those delete their content
248 std::unique_ptr<SwpHints> pTmpHints(std::move(m_pSwpHints));
250 for( size_t j = pTmpHints->Count(); j; )
252 // first remove the attribute from the array otherwise
253 // if would delete itself
254 DestroyAttr( pTmpHints->Get( --j ) );
258 // must be removed from outline nodes by now
259 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
260 SwOutlineNodes::size_type foo;
261 assert(!GetNodes().GetOutLineNds().Seek_Entry(this, &foo));
262 #endif
264 RemoveFromList();
266 DelFrames(nullptr); // must be called here while it's still a SwTextNode
267 DelFrames_TextNodePart();
268 #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
269 if (!GetDoc().IsInDtor())
270 ResetAttr(RES_PAGEDESC);
271 #else
272 ResetAttr(RES_PAGEDESC);
273 #endif
274 InvalidateInSwCache(RES_OBJECTDYING);
277 void SwTextNode::FileLoadedInitHints()
279 if (m_pSwpHints)
281 m_pSwpHints->MergePortions(*this);
285 SwContentFrame *SwTextNode::MakeFrame( SwFrame* pSib )
287 SwContentFrame *pFrame = sw::MakeTextFrame(*this, pSib, sw::FrameMode::New);
288 return pFrame;
291 sal_Int32 SwTextNode::Len() const
293 return m_Text.getLength();
296 // After a split node, it's necessary to actualize the ref-pointer of the ftnfrms.
297 static void lcl_ChangeFootnoteRef( SwTextNode &rNode )
299 SwpHints *pSwpHints = rNode.GetpSwpHints();
300 if( !(pSwpHints && rNode.GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell()) )
301 return;
303 SwContentFrame* pFrame = nullptr;
304 // OD 07.11.2002 #104840# - local variable to remember first footnote
305 // of node <rNode> in order to invalidate position of its first content.
306 // Thus, in its <MakeAll()> it will checked its position relative to its reference.
307 SwFootnoteFrame* pFirstFootnoteOfNode = nullptr;
308 for( size_t j = pSwpHints->Count(); j; )
310 SwTextAttr* pHt = pSwpHints->Get(--j);
311 if (RES_TXTATR_FTN == pHt->Which())
313 if( !pFrame )
315 pFrame = SwIterator<SwContentFrame, SwTextNode, sw::IteratorMode::UnwrapMulti>(rNode).First();
316 if (!pFrame)
317 return;
319 SwTextFootnote *pAttr = static_cast<SwTextFootnote*>(pHt);
320 OSL_ENSURE( pAttr->GetStartNode(), "FootnoteAtr without StartNode." );
321 SwNodeIndex aIdx( *pAttr->GetStartNode(), 1 );
322 SwContentNode *pNd = aIdx.GetNode().GetContentNode();
323 if ( !pNd )
324 pNd = pFrame->GetAttrSet()->GetDoc()->
325 GetNodes().GoNextSection( &aIdx, true, false );
326 if ( !pNd )
327 continue;
329 SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pNd);
330 SwContentFrame* pContent = aIter.First();
331 if( pContent )
333 OSL_ENSURE( pContent->getRootFrame() == pFrame->getRootFrame(),
334 "lcl_ChangeFootnoteRef: Layout double?" );
335 SwFootnoteFrame *pFootnote = pContent->FindFootnoteFrame();
336 if( pFootnote && pFootnote->GetAttr() == pAttr )
338 while( pFootnote->GetMaster() )
339 pFootnote = pFootnote->GetMaster();
340 // #104840# - remember footnote frame
341 pFirstFootnoteOfNode = pFootnote;
342 while ( pFootnote )
344 pFootnote->SetRef( pFrame );
345 pFootnote = pFootnote->GetFollow();
346 static_cast<SwTextFrame*>(pFrame)->SetFootnote( true );
349 #if OSL_DEBUG_LEVEL > 0
350 while( nullptr != (pContent = aIter.Next()) )
352 SwFootnoteFrame *pDbgFootnote = pContent->FindFootnoteFrame();
353 OSL_ENSURE( !pDbgFootnote || pDbgFootnote->GetRef() == pFrame,
354 "lcl_ChangeFootnoteRef: Who's that guy?" );
356 #endif
359 } // end of for-loop on <SwpHints>
360 // #104840# - invalidate
361 if ( pFirstFootnoteOfNode )
363 SwContentFrame* pContent = pFirstFootnoteOfNode->ContainsContent();
364 if ( pContent )
366 pContent->InvalidatePos_();
371 namespace sw {
373 // check if there are flys on the existing frames (now on "pNode")
374 // that need to be moved to the new frames of "this"
375 void MoveMergedFlysAndFootnotes(std::vector<SwTextFrame*> const& rFrames,
376 SwTextNode const& rFirstNode, SwTextNode & rSecondNode,
377 bool isSplitNode)
379 if (!isSplitNode)
381 lcl_ChangeFootnoteRef(rSecondNode);
383 for (SwNodeOffset nIndex = rSecondNode.GetIndex() + 1; ; ++nIndex)
385 SwNode *const pTmp(rSecondNode.GetNodes()[nIndex]);
386 if (pTmp->IsCreateFrameWhenHidingRedlines() || pTmp->IsEndNode())
388 break;
390 else if (pTmp->IsStartNode())
392 nIndex = pTmp->EndOfSectionIndex();
394 else if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst
395 && pTmp->IsTextNode())
397 lcl_ChangeFootnoteRef(*pTmp->GetTextNode());
400 for (SwTextFrame *const pFrame : rFrames)
402 if (SwSortedObjs *const pObjs = pFrame->GetDrawObjs())
404 std::vector<SwAnchoredObject*> objs;
405 objs.reserve(pObjs->size());
406 for (SwAnchoredObject *const pObj : *pObjs)
408 objs.push_back(pObj);
410 for (SwAnchoredObject *const pObj : objs)
412 SwFrameFormat & rFormat(pObj->GetFrameFormat());
413 SwFormatAnchor const& rAnchor(rFormat.GetAnchor());
414 if (rFirstNode.GetIndex() < rAnchor.GetAnchorNode()->GetIndex())
416 // move it to the new frame of "this"
417 rFormat.CallSwClientNotify(sw::LegacyModifyHint(&rAnchor, &rAnchor));
418 // note pObjs will be deleted if it becomes empty
419 assert(!pFrame->GetDrawObjs() || !pObjs->Contains(*pObj));
426 } // namespace
428 SwTextNode *SwTextNode::SplitContentNode(const SwPosition & rPos,
429 std::function<void (SwTextNode *, sw::mark::RestoreMode, bool AtStart)> const*const pContentIndexRestore)
431 bool isHide(false);
432 SwNode::Merge const eOldMergeFlag(GetRedlineMergeFlag());
433 bool parentIsOutline = IsOutline();
435 // create a node "in front" of me
436 const sal_Int32 nSplitPos = rPos.GetContentIndex();
437 const sal_Int32 nTextLen = m_Text.getLength();
438 SwTextNode* const pNode =
439 MakeNewTextNode( rPos.GetNode(), false, nSplitPos==nTextLen );
441 // the first paragraph gets the XmlId,
442 // _except_ if it is empty and the second is not empty
443 if (nSplitPos != 0) {
444 pNode->RegisterAsCopyOf(*this, true);
445 if (nSplitPos == nTextLen)
447 RemoveMetadataReference();
448 // NB: SwUndoSplitNode will call pNode->JoinNext,
449 // which is sufficient even in this case!
453 ResetAttr( RES_PARATR_LIST_ISRESTART );
454 ResetAttr( RES_PARATR_LIST_RESTARTVALUE );
455 ResetAttr( RES_PARATR_LIST_ISCOUNTED );
456 if ( GetNumRule() == nullptr || (parentIsOutline && !IsOutline()) )
458 ResetAttr( RES_PARATR_LIST_ID );
459 ResetAttr( RES_PARATR_LIST_LEVEL );
462 if ( HasWriterListeners() && !m_Text.isEmpty() && (nTextLen / 2) < nSplitPos )
464 // optimization for SplitNode: If a split is at the end of a node then
465 // move the frames from the current to the new one and create new ones
466 // for the current one.
468 // If fly frames are moved, they don't need to destroy their layout
469 // frames. Set a flag that is checked in SwTextFlyCnt::SetAnchor.
470 if ( HasHints() )
472 pNode->GetOrCreateSwpHints().SetInSplitNode(true);
475 // Move the first part of the content to the new node and delete
476 // it in the old node.
477 SwContentIndex aIdx( this );
478 CutText( pNode, aIdx, nSplitPos );
480 if( GetWrong() )
482 pNode->SetWrong( GetWrong()->SplitList( nSplitPos ) );
484 SetWrongDirty(sw::WrongState::TODO);
486 if( GetGrammarCheck() )
488 pNode->SetGrammarCheck( GetGrammarCheck()->SplitGrammarList( nSplitPos ) );
490 SetGrammarCheckDirty( true );
492 SetWordCountDirty( true );
494 if( GetSmartTags() )
496 pNode->SetSmartTags( GetSmartTags()->SplitList( nSplitPos ) );
498 SetSmartTagDirty( true );
500 resetAndQueueAccessibilityCheck();
501 pNode->resetAndQueueAccessibilityCheck();
503 if ( pNode->HasHints() )
505 if ( pNode->m_pSwpHints->CanBeDeleted() )
507 pNode->m_pSwpHints.reset();
509 else
511 pNode->m_pSwpHints->SetInSplitNode(false);
514 // All fly frames anchored as char that are moved to the new
515 // node must have their layout frames deleted.
516 // This comment is sort of silly because we actually delete the
517 // layout frames of those which were not moved?
518 // JP 01.10.96: delete all empty and not-to-be-expanded attributes
519 if ( HasHints() )
521 for ( size_t j = m_pSwpHints->Count(); j; )
523 SwTextAttr* const pHt = m_pSwpHints->Get( --j );
524 if ( RES_TXTATR_FLYCNT == pHt ->Which() )
526 pHt->GetFlyCnt().GetFrameFormat()->DelFrames();
528 else if ( pHt->DontExpand() )
530 const sal_Int32* const pEnd = pHt->GetEnd();
531 if (pEnd && pHt->GetStart() == *pEnd )
533 // delete it!
534 m_pSwpHints->DeleteAtPos( j );
535 DestroyAttr( pHt );
543 if (pContentIndexRestore)
544 { // call before making frames and before RegisterToNode
545 (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::NonFlys, false);
547 if (eOldMergeFlag != SwNode::Merge::None)
548 { // clear before making frames and before RegisterToNode
549 SetRedlineMergeFlag(SwNode::Merge::None);
550 } // now RegisterToNode will set merge flags in both nodes properly!
552 std::vector<SwTextFrame*> frames;
553 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
554 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
556 if (pFrame->getRootFrame()->HasMergedParas())
558 isHide = true;
560 frames.push_back(pFrame);
562 for (SwTextFrame * pFrame : frames)
564 pFrame->RegisterToNode( *pNode );
565 if (!pFrame->IsFollow() && pFrame->GetOffset())
567 pFrame->SetOffset( TextFrameIndex(0) );
571 InvalidateInSwCache(RES_ATTRSET_CHG);
573 if ( HasHints() )
575 MoveTextAttr_To_AttrSet();
577 // in case there are frames, the RegisterToNode has set the merge flag
578 pNode->MakeFramesForAdjacentContentNode(*this);
579 lcl_ChangeFootnoteRef( *this );
580 if (pContentIndexRestore)
581 { // call after making frames; listeners will take care of adding to the right frame
582 (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::Flys, false);
584 if (eOldMergeFlag != SwNode::Merge::None)
586 MoveMergedFlysAndFootnotes(frames, *pNode, *this, true);
589 else
591 std::unique_ptr<SwWrongList> pList = ReleaseWrong();
592 SetWrongDirty(sw::WrongState::TODO);
594 std::unique_ptr<SwGrammarMarkUp> pList3 = ReleaseGrammarCheck();
595 SetGrammarCheckDirty( true );
597 SetWordCountDirty( true );
599 std::unique_ptr<SwWrongList> pList2 = ReleaseSmartTags();
600 SetSmartTagDirty( true );
602 SwContentIndex aIdx( this );
603 CutText( pNode, aIdx, nSplitPos );
605 // JP 01.10.96: delete all empty and not-to-be-expanded attributes
606 if ( HasHints() )
608 for ( size_t j = m_pSwpHints->Count(); j; )
610 SwTextAttr* const pHt = m_pSwpHints->Get( --j );
611 const sal_Int32* const pEnd = pHt->GetEnd();
612 if ( pHt->DontExpand() && pEnd && (pHt->GetStart() == *pEnd) )
614 // delete it!
615 m_pSwpHints->DeleteAtPos( j );
616 DestroyAttr( pHt );
619 MoveTextAttr_To_AttrSet();
622 if( pList )
624 pNode->SetWrong( pList->SplitList( nSplitPos ) );
625 SetWrong( std::move(pList) );
628 if( pList3 )
630 pNode->SetGrammarCheck( pList3->SplitGrammarList( nSplitPos ) );
631 SetGrammarCheck( std::move(pList3) );
634 if( pList2 )
636 pNode->SetSmartTags( pList2->SplitList( nSplitPos ) );
637 SetSmartTags( std::move(pList2) );
640 resetAndQueueAccessibilityCheck();
641 pNode->resetAndQueueAccessibilityCheck();
643 if (pContentIndexRestore)
644 { // call before making frames and before RegisterToNode
645 (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::NonFlys, false);
648 std::vector<SwTextFrame*> frames;
649 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
650 for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
652 frames.push_back(pFrame);
653 if (pFrame->getRootFrame()->HasMergedParas())
655 isHide = true;
658 bool bNonMerged(false);
659 bool bRecreateThis(false);
660 for (SwTextFrame * pFrame : frames)
662 // sw_redlinehide: for this to work properly with hidden nodes,
663 // the frame needs to listen on them too.
664 // also: have to check the frame; this->GetRedlineMergeFlag()
665 // is None in case there's a delete redline inside the paragraph,
666 // but that could still result in a merged frame after split...
667 if (pFrame->GetMergedPara())
669 // Can't special case this == First here - that could (if
670 // both nodes are still merged by redline) lead to
671 // duplicate frames on "this".
672 // Update the extents with new node; also inits merge flag,
673 // so the MakeFramesForAdjacentContentNode below respects it
674 pFrame->RegisterToNode(*pNode);
675 if (nSplitPos == 0)
677 // in this case, it was not
678 // invalidated because Cut didn't sent it any hints,
679 // so we have to invalidate it here!
680 pFrame->Prepare(PrepareHint::Clear, nullptr, false);
682 if (!pFrame->GetMergedPara() ||
683 !pFrame->GetMergedPara()->listener.IsListeningTo(this))
685 // it's no longer listening - need to recreate frame
686 // (note this is idempotent, can be done once per frame)
687 SetRedlineMergeFlag(SwNode::Merge::None);
688 bRecreateThis = true;
691 else
693 bNonMerged = true;
696 assert(!(bNonMerged && bRecreateThis)); // 2 layouts not handled yet - maybe best to simply use the other branch then?
697 if (!frames.empty() && bNonMerged)
699 // the existing frame on "this" should have been updated by Cut
700 MakeFramesForAdjacentContentNode(*pNode);
701 lcl_ChangeFootnoteRef(*pNode);
703 else if (bRecreateThis)
705 assert(pNode->HasWriterListeners()); // was just moved there
706 pNode->MakeFramesForAdjacentContentNode(*this);
707 lcl_ChangeFootnoteRef(*this);
710 if (pContentIndexRestore)
711 { // call after making frames; listeners will take care of adding to the right frame
712 (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::Flys, nSplitPos == 0);
715 if (bRecreateThis)
717 MoveMergedFlysAndFootnotes(frames, *pNode, *this, true);
721 // pNode is the previous node, 'this' is the next node from the split.
722 if (nSplitPos == nTextLen && m_pSwpHints)
724 // We just created an empty next node: avoid unwanted superscript in the new node if it's
725 // there.
726 for (size_t i = 0; i < m_pSwpHints->Count(); ++i)
728 SwTextAttr* pHt = m_pSwpHints->Get(i);
729 if (pHt->Which() != RES_TXTATR_AUTOFMT)
731 continue;
734 const sal_Int32* pEnd = pHt->GetEnd();
735 if (!pEnd || pHt->GetStart() != *pEnd)
737 continue;
740 const std::shared_ptr<SfxItemSet>& pSet = pHt->GetAutoFormat().GetStyleHandle();
741 if (!pSet || pSet->Count() != 1 || !pSet->HasItem(RES_CHRATR_ESCAPEMENT))
743 continue;
746 m_pSwpHints->DeleteAtPos(i);
747 SwTextAttr::Destroy(pHt, GetDoc().GetAttrPool());
748 --i;
752 #ifndef NDEBUG
753 if (isHide) // otherwise flags won't be set anyway
755 // First
756 // -> First,NonFirst
757 // -> First,Hidden
758 // -> None,First
759 // Hidden
760 // -> Hidden,Hidden (if still inside merge rl)
761 // -> NonFirst,First (if redline was split)
762 // NonFirst
763 // -> NonFirst,First (if split after end of "incoming" redline &
764 // before start of "outgoing" redline)
765 // -> NonFirst,None (if split after end of "incoming" redline)
766 // -> NonFirst,Hidden (if split after start of "outgoing" redline)
767 // -> Hidden, NonFirst (if split before end of "incoming" redline)
768 // None
769 // -> None,None
770 // -> First,NonFirst (if splitting inside a delete redline)
771 SwNode::Merge const eFirst(pNode->GetRedlineMergeFlag());
772 SwNode::Merge const eSecond(GetRedlineMergeFlag());
773 switch (eOldMergeFlag)
775 case Merge::First:
776 assert((eFirst == Merge::First && eSecond == Merge::NonFirst)
777 || (eFirst == Merge::First && eSecond == Merge::Hidden)
778 || (eFirst == Merge::None && eSecond == Merge::First));
779 break;
780 case Merge::Hidden:
781 assert((eFirst == Merge::Hidden && eSecond == Merge::Hidden)
782 || (eFirst == Merge::NonFirst && eSecond == Merge::First)
783 // next ones can happen temp. in UndoDelete :(
784 || (eFirst == Merge::Hidden && eSecond == Merge::NonFirst)
785 || (eFirst == Merge::NonFirst && eSecond == Merge::None));
786 break;
787 case Merge::NonFirst:
788 assert((eFirst == Merge::NonFirst && eSecond == Merge::First)
789 || (eFirst == Merge::NonFirst && eSecond == Merge::None)
790 || (eFirst == Merge::NonFirst && eSecond == Merge::Hidden)
791 || (eFirst == Merge::Hidden && eSecond == Merge::NonFirst));
792 break;
793 case Merge::None:
794 assert((eFirst == Merge::None && eSecond == Merge::None)
795 || (eFirst == Merge::First && eSecond == Merge::NonFirst));
796 break;
799 #else
800 (void) isHide;
801 #endif
804 // Send Hint for PageDesc. This should be done in the Layout when
805 // pasting the frames, but that causes other problems that look
806 // expensive to solve.
807 const SwFormatPageDesc *pItem;
808 if(HasWriterListeners() && (pItem = pNode->GetSwAttrSet().GetItemIfSet(RES_PAGEDESC)))
809 pNode->TriggerNodeUpdate(sw::LegacyModifyHint(pItem, pItem));
811 return pNode;
814 void SwTextNode::MoveTextAttr_To_AttrSet()
816 OSL_ENSURE( m_pSwpHints, "MoveTextAttr_To_AttrSet without SwpHints?" );
817 for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i )
819 SwTextAttr *pHt = m_pSwpHints->Get(i);
821 if( pHt->GetStart() )
822 break;
824 const sal_Int32* pHtEndIdx = pHt->GetEnd();
826 if( !pHtEndIdx )
827 continue;
829 if (*pHtEndIdx < m_Text.getLength() || pHt->IsCharFormatAttr())
830 break;
832 if( !pHt->IsDontMoveAttr() &&
833 SetAttr( pHt->GetAttr() ) )
835 m_pSwpHints->DeleteAtPos(i);
836 DestroyAttr( pHt );
837 --i;
843 namespace sw {
845 /// if first node is deleted & second survives, then the first node's frame
846 /// will be deleted too; prevent this by moving the frame to the second node
847 /// if necessary.
848 void MoveDeletedPrevFrames(const SwTextNode & rDeletedPrev, SwTextNode & rNode)
850 std::vector<SwTextFrame*> frames;
851 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rDeletedPrev);
852 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
854 if (pFrame->getRootFrame()->HasMergedParas())
856 frames.push_back(pFrame);
860 auto frames2(frames);
861 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIt(rNode);
862 for (SwTextFrame* pFrame = aIt.First(); pFrame; pFrame = aIt.Next())
864 if (pFrame->getRootFrame()->HasMergedParas())
866 auto const it(std::find(frames2.begin(), frames2.end(), pFrame));
867 assert(it != frames2.end());
868 frames2.erase(it);
871 assert(frames2.empty());
873 for (SwTextFrame *const pFrame : frames)
875 pFrame->RegisterToNode(rNode, true);
879 // typical Join:
880 // None,Node->None
881 // None,First->First
882 // First,NonFirst->First
883 // NonFirst,First->NonFirst
884 // NonFirst,None->NonFirst
886 /// if first node is First, its frames may need to be moved, never deleted.
887 /// if first node is NonFirst, second node's own frames (First/None) must be deleted
888 void CheckResetRedlineMergeFlag(SwTextNode & rNode, Recreate const eRecreateMerged)
890 if (eRecreateMerged != sw::Recreate::No)
892 SwTextNode * pMergeNode(&rNode);
893 if (eRecreateMerged == sw::Recreate::Predecessor
894 // tdf#135018 check that there is a predecessor node, i.e. rNode
895 // isn't the first node after the body start node
896 && rNode.GetNodes()[rNode.GetIndex() - 1]->StartOfSectionIndex() != SwNodeOffset(0))
898 for (SwNodeOffset i = rNode.GetIndex() - 1; ; --i)
900 SwNode *const pNode(rNode.GetNodes()[i]);
901 assert(!pNode->IsStartNode());
902 if (pNode->IsEndNode())
904 i = pNode->StartOfSectionIndex();
906 else if (pNode->IsTextNode())
908 pMergeNode = pNode->GetTextNode(); // use predecessor to merge
909 break;
913 std::vector<SwTextFrame*> frames;
914 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pMergeNode);
915 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
917 if (pFrame->getRootFrame()->HasMergedParas())
919 frames.push_back(pFrame);
922 auto eMode(sw::FrameMode::Existing);
923 for (SwTextFrame * pFrame : frames)
925 SwTextNode & rFirstNode(pFrame->GetMergedPara()
926 ? *pFrame->GetMergedPara()->pFirstNode
927 : *pMergeNode);
928 assert(rFirstNode.GetIndex() <= rNode.GetIndex());
929 pFrame->SetMergedPara(sw::CheckParaRedlineMerge(
930 *pFrame, rFirstNode, eMode));
931 // there is no merged para in case the deleted node had one but
932 // nothing was actually hidden
933 if (pFrame->GetMergedPara())
935 assert(pFrame->GetMergedPara()->listener.IsListeningTo(&rNode));
936 assert(rNode.GetIndex() <= pFrame->GetMergedPara()->pLastNode->GetIndex());
937 // tdf#135978 Join: recreate fly frames anchored to subsequent nodes
938 if (eRecreateMerged == sw::Recreate::ThisNode)
940 AddRemoveFlysAnchoredToFrameStartingAtNode(*pFrame, rNode, nullptr);
943 eMode = sw::FrameMode::New; // Existing is not idempotent!
946 else if (rNode.GetRedlineMergeFlag() != SwNode::Merge::None)
948 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rNode);
949 for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
951 if (auto const pMergedPara = pFrame->GetMergedPara())
953 if (pMergedPara->pFirstNode == pMergedPara->pLastNode)
955 assert(pMergedPara->pFirstNode == &rNode);
956 rNode.SetRedlineMergeFlag(SwNode::Merge::None);
958 break; // checking once is enough
960 else if (pFrame->getRootFrame()->HasMergedParas())
962 rNode.SetRedlineMergeFlag(SwNode::Merge::None);
963 break; // checking once is enough
969 bool HasNumberingWhichNeedsLayoutUpdate(const SwTextNode& rTextNode)
971 const SwNodeNum* pNodeNum = rTextNode.GetNum();
972 if (!pNodeNum)
974 return false;
977 const SwNumRule* pNumRule = pNodeNum->GetNumRule();
978 if (!pNumRule)
980 return false;
983 const SwNumFormat* pFormat
984 = pNumRule->GetNumFormat(o3tl::narrowing<sal_uInt16>(rTextNode.GetAttrListLevel()));
985 if (!pFormat)
987 return false;
990 switch (pFormat->GetNumberingType())
992 case SVX_NUM_NUMBER_NONE:
993 case SVX_NUM_CHAR_SPECIAL:
994 case SVX_NUM_BITMAP:
995 return false;
996 default:
997 return true;
1000 } // namespace
1002 SwContentNode *SwTextNode::JoinNext()
1004 SwNodes& rNds = GetNodes();
1005 SwNodeIndex aIdx( *this );
1006 if( SwContentNode::CanJoinNext( &aIdx ) )
1008 SwDoc& rDoc = rNds.GetDoc();
1009 const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
1010 pContentStore->Save(rDoc, aIdx.GetIndex(), SAL_MAX_INT32);
1011 SwTextNode *pTextNode = aIdx.GetNode().GetTextNode();
1012 sal_Int32 nOldLen = m_Text.getLength();
1014 // METADATA: merge
1015 JoinMetadatable(*pTextNode, !Len(), !pTextNode->Len());
1017 std::unique_ptr<SwWrongList> pList = ReleaseWrong();
1018 if( pList )
1020 pList->JoinList( pTextNode->GetWrong(), nOldLen );
1021 SetWrongDirty(sw::WrongState::TODO);
1023 else
1025 pList = pTextNode->ReleaseWrong();
1026 if( pList )
1028 pList->Move( 0, nOldLen );
1029 SetWrongDirty(sw::WrongState::TODO);
1033 std::unique_ptr<SwGrammarMarkUp> pList3 = ReleaseGrammarCheck();
1034 if( pList3 )
1036 pList3->JoinGrammarList( pTextNode->GetGrammarCheck(), nOldLen );
1037 SetGrammarCheckDirty( true );
1039 else
1041 pList3 = pTextNode->ReleaseGrammarCheck();
1042 if( pList3 )
1044 pList3->MoveGrammar( 0, nOldLen );
1045 SetGrammarCheckDirty( true );
1049 std::unique_ptr<SwWrongList> pList2 = ReleaseSmartTags();
1050 if( pList2 )
1052 pList2->JoinList( pTextNode->GetSmartTags(), nOldLen );
1053 SetSmartTagDirty( true );
1055 else
1057 pList2 = pTextNode->ReleaseSmartTags();
1058 if( pList2 )
1060 pList2->Move( 0, nOldLen );
1061 SetSmartTagDirty( true );
1065 { // scope for SwContentIndex
1066 pTextNode->CutText( this, SwContentIndex(pTextNode), pTextNode->Len() );
1068 // move all Bookmarks/TOXMarks
1069 if( !pContentStore->Empty())
1070 pContentStore->Restore( rDoc, GetIndex(), nOldLen );
1072 if( pTextNode->HasAnyIndex() )
1074 // move all ShellCursor/StackCursor/UnoCursor out of delete range
1075 rDoc.CorrAbs( aIdx.GetNode(), SwPosition( *this ), nOldLen, true );
1077 SwNode::Merge const eOldMergeFlag(pTextNode->GetRedlineMergeFlag());
1078 auto eRecreateMerged(eOldMergeFlag == SwNode::Merge::First
1079 ? sw::Recreate::ThisNode
1080 : sw::Recreate::No);
1081 if (eRecreateMerged == sw::Recreate::No)
1083 // tdf#137318 if a delete is inside one node, flag is still None!
1084 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pTextNode);
1085 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
1087 if (pFrame->GetMergedPara())
1089 eRecreateMerged = sw::Recreate::ThisNode;
1090 break;
1094 bool bOldHasNumberingWhichNeedsLayoutUpdate = HasNumberingWhichNeedsLayoutUpdate(*pTextNode);
1096 rNds.Delete(aIdx);
1097 SetWrong( std::move(pList) );
1098 SetGrammarCheck( std::move(pList3) );
1099 SetSmartTags( std::move(pList2) );
1101 resetAndQueueAccessibilityCheck();
1103 if (bOldHasNumberingWhichNeedsLayoutUpdate || HasNumberingWhichNeedsLayoutUpdate(*this))
1105 // Repaint all text frames that belong to this numbering to avoid outdated generated
1106 // numbers.
1107 InvalidateNumRule();
1110 CheckResetRedlineMergeFlag(*this, eRecreateMerged);
1112 else {
1113 OSL_FAIL( "No TextNode." );
1116 return this;
1119 void SwTextNode::JoinPrev()
1121 SwNodes& rNds = GetNodes();
1122 SwNodeIndex aIdx( *this );
1123 if( SwContentNode::CanJoinPrev( &aIdx ) )
1125 SwDoc& rDoc = rNds.GetDoc();
1126 const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
1127 pContentStore->Save( rDoc, aIdx.GetIndex(), SAL_MAX_INT32);
1128 SwTextNode *pTextNode = aIdx.GetNode().GetTextNode();
1129 const sal_Int32 nLen = pTextNode->Len();
1131 std::unique_ptr<SwWrongList> pList = pTextNode->ReleaseWrong();
1132 if( pList )
1134 pList->JoinList( GetWrong(), Len() );
1135 SetWrongDirty(sw::WrongState::TODO);
1136 ClearWrong();
1138 else
1140 pList = ReleaseWrong();
1141 if( pList )
1143 pList->Move( 0, nLen );
1144 SetWrongDirty(sw::WrongState::TODO);
1148 std::unique_ptr<SwGrammarMarkUp> pList3 = pTextNode->ReleaseGrammarCheck();
1149 if( pList3 )
1151 pList3->JoinGrammarList( GetGrammarCheck(), Len() );
1152 SetGrammarCheckDirty( true );
1153 ClearGrammarCheck();
1155 else
1157 pList3 = ReleaseGrammarCheck();
1158 if( pList3 )
1160 pList3->MoveGrammar( 0, nLen );
1161 SetGrammarCheckDirty( true );
1165 std::unique_ptr<SwWrongList> pList2 = pTextNode->ReleaseSmartTags();
1166 if( pList2 )
1168 pList2->JoinList( GetSmartTags(), Len() );
1169 SetSmartTagDirty( true );
1170 ClearSmartTags();
1172 else
1174 pList2 = ReleaseSmartTags();
1175 if( pList2 )
1177 pList2->Move( 0, nLen );
1178 SetSmartTagDirty( true );
1182 { // scope for SwContentIndex
1183 pTextNode->CutText( this, SwContentIndex(this), SwContentIndex(pTextNode), nLen );
1185 // move all Bookmarks/TOXMarks
1186 if( !pContentStore->Empty() )
1187 pContentStore->Restore( rDoc, GetIndex() );
1189 if( pTextNode->HasAnyIndex() )
1191 // move all ShellCursor/StackCursor/UnoCursor out of delete range
1192 rDoc.CorrAbs( aIdx.GetNode(), SwPosition( *this ), nLen, true );
1194 SwNode::Merge const eOldMergeFlag(pTextNode->GetRedlineMergeFlag());
1195 if (eOldMergeFlag == SwNode::Merge::First
1196 && !IsCreateFrameWhenHidingRedlines())
1198 sw::MoveDeletedPrevFrames(*pTextNode, *this);
1200 rNds.Delete(aIdx);
1201 SetWrong( std::move(pList) );
1202 SetGrammarCheck( std::move(pList3) );
1203 SetSmartTags( std::move(pList2) );
1204 resetAndQueueAccessibilityCheck();
1205 InvalidateNumRule();
1206 sw::CheckResetRedlineMergeFlag(*this,
1207 eOldMergeFlag == SwNode::Merge::NonFirst
1208 ? sw::Recreate::Predecessor
1209 : sw::Recreate::No);
1211 else {
1212 OSL_FAIL( "No TextNode." );
1216 // create an AttrSet with ranges for Frame-/Para/Char-attributes
1217 void SwTextNode::NewAttrSet( SwAttrPool& rPool )
1219 OSL_ENSURE( !mpAttrSet, "AttrSet is set after all" );
1220 SwAttrSet aNewAttrSet( rPool, aTextNodeSetRange );
1222 // put names of parent style and conditional style:
1223 const SwFormatColl* pAnyFormatColl = &GetAnyFormatColl();
1224 const SwFormatColl* pFormatColl = GetFormatColl();
1225 OUString sVal;
1226 SwStyleNameMapper::FillProgName( pAnyFormatColl->GetName(), sVal, SwGetPoolIdFromName::TxtColl );
1227 SfxStringItem aAnyFormatColl( RES_FRMATR_STYLE_NAME, sVal );
1228 if ( pFormatColl != pAnyFormatColl )
1229 SwStyleNameMapper::FillProgName( pFormatColl->GetName(), sVal, SwGetPoolIdFromName::TxtColl );
1230 SfxStringItem aFormatColl( RES_FRMATR_CONDITIONAL_STYLE_NAME, sVal );
1231 aNewAttrSet.Put( aAnyFormatColl );
1232 aNewAttrSet.Put( aFormatColl );
1234 aNewAttrSet.SetParent( &pAnyFormatColl->GetAttrSet() );
1235 mpAttrSet = GetDoc().GetIStyleAccess().getAutomaticStyle( aNewAttrSet, IStyleAccess::AUTO_STYLE_PARA, &sVal );
1238 namespace
1240 class SwContentNodeTmp : public SwContentNode
1242 public:
1243 SwContentNodeTmp() : SwContentNode() {}
1244 virtual void NewAttrSet(SwAttrPool&) override {}
1245 virtual SwContentFrame *MakeFrame(SwFrame*) override { return nullptr; }
1246 virtual SwContentNode* MakeCopy(SwDoc&, SwNode&, bool /*bNewFrames*/) const override { return nullptr; };
1250 // override SwContentIndexReg::Update => text hints do not need SwContentIndex for start/end!
1251 void SwTextNode::Update(
1252 SwContentIndex const & rPos,
1253 const sal_Int32 nChangeLen,
1254 UpdateMode const eMode)
1256 assert(rPos.GetContentNode() == this);
1257 SetAutoCompleteWordDirty( true );
1259 std::unique_ptr<SwpHts> pCollector;
1260 const sal_Int32 nChangePos = rPos.GetIndex();
1262 if ( HasHints() )
1264 if (eMode & UpdateMode::Negative)
1266 std::vector<SwTextInputField*> aTextInputFields;
1268 const sal_Int32 nChangeEnd = nChangePos + nChangeLen;
1269 for ( size_t n = 0; n < m_pSwpHints->Count(); ++n )
1271 bool bTextAttrChanged = false;
1272 bool bStartOfTextAttrChanged = false;
1273 SwTextAttr * const pHint = m_pSwpHints->GetWithoutResorting(n);
1274 if ( pHint->GetStart() > nChangePos )
1276 if ( pHint->GetStart() > nChangeEnd )
1278 pHint->SetStart( pHint->GetStart() - nChangeLen );
1280 else
1282 pHint->SetStart( nChangePos );
1284 bStartOfTextAttrChanged = true;
1287 const sal_Int32 * const pEnd = pHint->GetEnd();
1288 if (pEnd && *pEnd > nChangePos )
1290 if( *pEnd > nChangeEnd )
1292 pHint->SetEnd(*pEnd - nChangeLen);
1294 else
1296 pHint->SetEnd(nChangePos);
1298 bTextAttrChanged = !bStartOfTextAttrChanged;
1301 if ( bTextAttrChanged
1302 && pHint->Which() == RES_TXTATR_INPUTFIELD )
1304 SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pHint);
1305 if ( pTextInputField )
1306 aTextInputFields.push_back(pTextInputField);
1310 //wait until all the attribute positions are correct
1311 //before updating the field contents
1312 for (SwTextInputField* pTextInputField : aTextInputFields)
1314 pTextInputField->UpdateFieldContent();
1317 m_pSwpHints->MergePortions( *this );
1319 else
1321 bool bNoExp = false;
1322 bool bResort = false;
1323 bool bMergePortionsNeeded = false;
1324 const int coArrSz = RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN;
1325 std::vector<SwTextInputField*> aTextInputFields;
1327 bool aDontExp[ coArrSz ] = {};
1329 for ( size_t n = 0; n < m_pSwpHints->Count(); ++n )
1331 bool bTextAttrChanged = false;
1332 SwTextAttr * const pHint = m_pSwpHints->GetWithoutResorting(n);
1333 const sal_Int32 * const pEnd = pHint->GetEnd();
1334 if ( pHint->GetStart() >= nChangePos )
1336 pHint->SetStart( pHint->GetStart() + nChangeLen );
1337 if ( pEnd )
1339 pHint->SetEnd(*pEnd + nChangeLen);
1342 else if ( pEnd && (*pEnd >= nChangePos) )
1344 if ( (*pEnd > nChangePos) || IsIgnoreDontExpand() )
1346 pHint->SetEnd(*pEnd + nChangeLen);
1347 bTextAttrChanged = true;
1349 else // *pEnd == nChangePos
1351 const sal_uInt16 nWhich = pHint->Which();
1353 OSL_ENSURE(!isCHRATR(nWhich), "Update: char attr hint?");
1354 if (!(isCHRATR(nWhich) || isTXTATR_WITHEND(nWhich)))
1355 continue;
1357 const sal_uInt16 nWhPos = nWhich - RES_CHRATR_BEGIN;
1359 if( aDontExp[ nWhPos ] )
1360 continue;
1362 if ( pHint->DontExpand() )
1364 pHint->SetDontExpand( false );
1365 bResort = true;
1366 // could have a continuation with IgnoreStart()...
1367 if (pHint->IsFormatIgnoreEnd())
1369 bMergePortionsNeeded = true;
1371 if ( pHint->IsCharFormatAttr() )
1373 bNoExp = true;
1374 aDontExp[ RES_TXTATR_CHARFMT - RES_CHRATR_BEGIN ] = true;
1375 aDontExp[ RES_TXTATR_INETFMT - RES_CHRATR_BEGIN ] = true;
1377 else
1378 aDontExp[ nWhPos ] = true;
1380 else if( bNoExp )
1382 if (!pCollector)
1384 pCollector.reset( new SwpHts );
1386 auto it = std::find_if(pCollector->begin(), pCollector->end(),
1387 [nWhich](const SwTextAttr *pTmp) { return nWhich == pTmp->Which(); });
1388 if (it != pCollector->end())
1390 SwTextAttr *pTmp = *it;
1391 pCollector->erase( it );
1392 SwTextAttr::Destroy( pTmp, GetDoc().GetAttrPool() );
1394 SwTextAttr * const pTmp =
1395 MakeTextAttr( GetDoc(),
1396 pHint->GetAttr(), nChangePos, nChangePos + nChangeLen);
1397 pCollector->push_back( pTmp );
1399 else
1401 pHint->SetEnd(*pEnd + nChangeLen);
1402 bTextAttrChanged = true;
1407 if ( bTextAttrChanged
1408 && pHint->Which() == RES_TXTATR_INPUTFIELD )
1410 SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pHint);
1411 if ( pTextInputField )
1412 aTextInputFields.push_back(pTextInputField);
1416 //wait until all the attribute positions are correct
1417 //before updating the field contents
1418 for (SwTextInputField* pTextInputField : aTextInputFields)
1420 pTextInputField->UpdateFieldContent();
1423 if (bMergePortionsNeeded)
1425 m_pSwpHints->MergePortions(*this); // does Resort too
1427 else if (bResort)
1429 m_pSwpHints->Resort();
1434 bool bSortMarks = false;
1435 SwContentNodeTmp aTmpIdxReg;
1436 if (!(eMode & UpdateMode::Negative) && !(eMode & UpdateMode::Delete))
1438 std::vector<SwRangeRedline*> vMyRedlines;
1439 // walk the list of SwIndex attached to me and see if any of them are redlines
1440 const SwContentIndex* pContentNodeIndex = GetFirstIndex();
1441 while (pContentNodeIndex)
1443 SwRangeRedline* pRedl = pContentNodeIndex->GetRedline();
1444 if (pRedl)
1445 vMyRedlines.push_back(pRedl);
1446 pContentNodeIndex = pContentNodeIndex->GetNext();
1448 std::sort(vMyRedlines.begin(), vMyRedlines.end());
1449 vMyRedlines.erase( std::unique( vMyRedlines.begin(), vMyRedlines.end() ), vMyRedlines.end() );
1450 for (SwRangeRedline* pRedl : vMyRedlines)
1452 if ( pRedl->HasMark() )
1454 SwPosition* const pEnd = pRedl->End();
1455 if ( *this == pEnd->GetNode() &&
1456 *pRedl->GetPoint() != *pRedl->GetMark() )
1458 SwContentIndex & rIdx = pEnd->nContent;
1459 if (nChangePos == rIdx.GetIndex())
1461 rIdx.Assign( &aTmpIdxReg, rIdx.GetIndex() );
1465 else if ( this == &pRedl->GetPoint()->GetNode() )
1467 SwContentIndex & rIdx = pRedl->GetPoint()->nContent;
1468 if (nChangePos == rIdx.GetIndex())
1470 rIdx.Assign( &aTmpIdxReg, rIdx.GetIndex() );
1472 // the unused position must not be on a SwTextNode
1473 bool const isOneUsed(&pRedl->GetBound() == pRedl->GetPoint());
1474 assert(!pRedl->GetBound(!isOneUsed).GetNode().IsTextNode());
1475 assert(!pRedl->GetBound(!isOneUsed).GetContentNode()); (void)isOneUsed;
1479 // Bookmarks must never grow to either side, when editing (directly)
1480 // to the left or right (i#29942)! Exception: if the bookmark has
1481 // 2 positions and start == end, then expand it (tdf#96479)
1482 if (!(eMode & UpdateMode::Replace)) // Exception: Replace
1484 bool bAtLeastOneBookmarkMoved = false;
1485 bool bAtLeastOneExpandedBookmarkAtInsertionPosition = false;
1486 // A text node already knows its marks via its SwContentIndexes.
1487 o3tl::sorted_vector<const sw::mark::IMark*> aSeenMarks;
1488 const SwContentIndex* next;
1489 for (const SwContentIndex* pIndex = GetFirstIndex(); pIndex; pIndex = next )
1491 next = pIndex->GetNext();
1492 const sw::mark::IMark* pMark = pIndex->GetMark();
1493 if (!pMark)
1494 continue;
1495 // Only handle bookmarks once, if they start and end at this node as well.
1496 if (!aSeenMarks.insert(pMark).second)
1497 continue;
1498 const SwPosition* pEnd = &pMark->GetMarkEnd();
1499 SwContentIndex & rEndIdx = const_cast<SwContentIndex&>(pEnd->nContent);
1500 if( *this == pEnd->GetNode() &&
1501 rPos.GetIndex() == rEndIdx.GetIndex() )
1503 if (&rEndIdx == next) // nasty corner case:
1504 { // don't switch to iterating aTmpIdxReg!
1505 next = rEndIdx.GetNext();
1507 // tdf#96479: if start == end, ignore the other position
1508 // so it is moved!
1509 rEndIdx.Assign( &aTmpIdxReg, rEndIdx.GetIndex() );
1510 bAtLeastOneBookmarkMoved = true;
1512 else if ( !bAtLeastOneExpandedBookmarkAtInsertionPosition )
1514 if ( pMark->IsExpanded() )
1516 const SwPosition* pStart = &pMark->GetMarkStart();
1517 if ( this == &pStart->GetNode()
1518 && rPos.GetIndex() == pStart->GetContentIndex() )
1520 bAtLeastOneExpandedBookmarkAtInsertionPosition = true;
1526 bSortMarks = bAtLeastOneBookmarkMoved && bAtLeastOneExpandedBookmarkAtInsertionPosition;
1529 // at-char anchored flys shouldn't be moved, either.
1530 if (!m_bInUndo)
1532 std::vector<SwFrameFormat*> const& rFlys(GetAnchoredFlys());
1533 for (size_t i = 0; i != rFlys.size(); ++i)
1535 SwFrameFormat const*const pFormat = rFlys[i];
1536 const SwFormatAnchor& rAnchor = pFormat->GetAnchor();
1537 const SwNode* pAnchorNode = rAnchor.GetAnchorNode();
1538 if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR && pAnchorNode)
1540 // The fly is at-char anchored and has an anchor position.
1541 SwContentIndex& rEndIdx = const_cast<SwContentIndex&>(rAnchor.GetContentAnchor()->nContent);
1542 if (*pAnchorNode == *this && rEndIdx.GetIndex() == rPos.GetIndex())
1544 // The anchor position is exactly our insert position.
1545 rEndIdx.Assign(&aTmpIdxReg, rEndIdx.GetIndex());
1551 // The cursors of other shells shouldn't be moved, either.
1552 if (SwDocShell* pDocShell = GetDoc().GetDocShell())
1554 if (pDocShell->GetWrtShell())
1556 for (SwViewShell& rShell : pDocShell->GetWrtShell()->GetRingContainer())
1558 auto pWrtShell = dynamic_cast<SwWrtShell*>(&rShell);
1559 if (!pWrtShell || pWrtShell == pDocShell->GetWrtShell())
1560 continue;
1562 SwShellCursor* pCursor = pWrtShell->GetCursor_();
1563 if (!pCursor)
1564 continue;
1566 SwContentIndex& rIndex = pCursor->Start()->nContent;
1567 if (pCursor->Start()->GetNode() == *this && rIndex.GetIndex() == rPos.GetIndex())
1569 // The cursor position of this other shell is exactly our insert position.
1570 rIndex.Assign(&aTmpIdxReg, rIndex.GetIndex());
1577 // base class
1578 SwContentIndexReg::Update(rPos, nChangeLen, eMode);
1580 if (pCollector)
1582 const size_t nCount = pCollector->size();
1583 for ( size_t i = 0; i < nCount; ++i )
1585 m_pSwpHints->TryInsertHint( (*pCollector)[ i ], *this );
1589 aTmpIdxReg.MoveTo( *this );
1590 if ( bSortMarks )
1592 getIDocumentMarkAccess()->assureSortedMarkContainers();
1595 //Any drawing objects anchored into this text node may be sorted by their
1596 //anchor position which may have changed here, so resort them
1597 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> iter(*this);
1598 for (SwTextFrame* pFrame = iter.First(); pFrame; pFrame = iter.Next())
1600 SwSortedObjs * pSortedObjs(pFrame->GetDrawObjs());
1601 if (pSortedObjs)
1603 pSortedObjs->UpdateAll();
1605 // also sort the objs on the page frame
1606 if (SwPageFrame *pPage = pFrame->FindPageFrame())
1607 pSortedObjs = pPage->GetSortedObjs();
1609 if (pSortedObjs) // doesn't exist yet if called for inserting as-char fly
1611 pSortedObjs->UpdateAll();
1615 // Update the paragraph signatures.
1616 if (SwEditShell* pEditShell = GetDoc().GetEditShell())
1618 pEditShell->ValidateParagraphSignatures(this, true);
1621 // Inform LOK clients about change in position of redlines (if any)
1622 // Don't emit notifications during save: redline flags are temporarily changed during save, but
1623 // it's not useful to let clients know about such changes.
1624 if (!comphelper::LibreOfficeKit::isActive() || GetDoc().IsInWriting())
1625 return;
1627 const SwRedlineTable& rTable = GetDoc().getIDocumentRedlineAccess().GetRedlineTable();
1628 for (SwRedlineTable::size_type nRedlnPos = 0; nRedlnPos < rTable.size(); ++nRedlnPos)
1630 SwRangeRedline* pRedln = rTable[nRedlnPos];
1631 if (pRedln->HasMark())
1633 if (*this == pRedln->End()->GetNode() && *pRedln->GetPoint() != *pRedln->GetMark())
1635 // Redline is changed only when some change occurs before it
1636 if (nChangePos <= pRedln->Start()->GetContentIndex())
1638 SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedln);
1642 else if (this == &pRedln->GetPoint()->GetNode())
1643 SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedln);
1647 void SwTextNode::ChgTextCollUpdateNum( const SwTextFormatColl *pOldColl,
1648 const SwTextFormatColl *pNewColl)
1650 SwDoc& rDoc = GetDoc();
1651 // query the OutlineLevel and if it changed, notify the Nodes-Array!
1652 const int nOldLevel = pOldColl && pOldColl->IsAssignedToListLevelOfOutlineStyle() ?
1653 pOldColl->GetAssignedOutlineStyleLevel() : MAXLEVEL;
1654 const int nNewLevel = pNewColl && pNewColl->IsAssignedToListLevelOfOutlineStyle() ?
1655 pNewColl->GetAssignedOutlineStyleLevel() : MAXLEVEL;
1657 if ( MAXLEVEL != nNewLevel && -1 != nNewLevel )
1659 SetAttrListLevel(nNewLevel);
1661 rDoc.GetNodes().UpdateOutlineNode(*this);
1663 SwNodes& rNds = GetNodes();
1664 // If Level 0 (Chapter), update the footnotes!
1665 if( ( !nNewLevel || !nOldLevel) && !rDoc.GetFootnoteIdxs().empty() &&
1666 FTNNUM_CHAPTER == rDoc.GetFootnoteInfo().m_eNum &&
1667 rNds.IsDocNodes() )
1669 rDoc.GetFootnoteIdxs().UpdateFootnote( *rNds[GetIndex()] );
1672 if( pNewColl && RES_CONDTXTFMTCOLL == pNewColl->Which() )
1674 // check the condition of the text node again
1675 ChkCondColl();
1679 // If positioned exactly at the end of a CharStyle or Hyperlink,
1680 // set its DontExpand flag.
1681 bool SwTextNode::DontExpandFormat( sal_Int32 nIdx, bool bFlag,
1682 bool bFormatToTextAttributes )
1684 if (bFormatToTextAttributes && nIdx == m_Text.getLength())
1686 FormatToTextAttr( this );
1689 bool bRet = false;
1690 if ( HasHints() )
1692 m_pSwpHints->SortIfNeedBe();
1693 int nPos = m_pSwpHints->GetLastPosSortedByEnd(nIdx);
1694 for ( ; nPos >= 0; --nPos)
1696 SwTextAttr *pTmp = m_pSwpHints->GetSortedByEnd( nPos );
1697 const sal_Int32 *pEnd = pTmp->GetEnd();
1698 if( !pEnd )
1699 continue;
1700 assert( *pEnd <= nIdx );
1701 if( nIdx != *pEnd )
1702 break;
1703 if( bFlag != pTmp->DontExpand() && !pTmp->IsLockExpandFlag()
1704 && *pEnd > pTmp->GetStart())
1706 bRet = true;
1707 m_pSwpHints->NoteInHistory( pTmp );
1708 pTmp->SetDontExpand( bFlag );
1712 return bRet;
1715 static bool lcl_GetTextAttrDefault(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd)
1717 return ((nHintStart <= nIndex) && (nIndex < nHintEnd));
1719 static bool lcl_GetTextAttrExpand(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd)
1721 return ((nHintStart < nIndex) && (nIndex <= nHintEnd));
1723 static bool lcl_GetTextAttrParent(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd)
1725 return ((nHintStart < nIndex) && (nIndex < nHintEnd));
1728 static void
1729 lcl_GetTextAttrs(
1730 std::vector<SwTextAttr *> *const pVector,
1731 SwTextAttr **const ppTextAttr,
1732 SwpHints const *const pSwpHints,
1733 sal_Int32 const nIndex, sal_uInt16 const nWhich,
1734 ::sw::GetTextAttrMode const eMode)
1736 assert(nWhich >= RES_TXTATR_BEGIN && nWhich < RES_TXTATR_END);
1737 if (!pSwpHints)
1738 return;
1739 size_t const nSize = pSwpHints->Count();
1740 sal_Int32 nPreviousIndex(0); // index of last hint with nWhich
1741 bool (*pMatchFunc)(sal_Int32, sal_Int32, sal_Int32)=nullptr;
1742 switch (eMode)
1744 case ::sw::GetTextAttrMode::Default: pMatchFunc = &lcl_GetTextAttrDefault;
1745 break;
1746 case ::sw::GetTextAttrMode::Expand: pMatchFunc = &lcl_GetTextAttrExpand;
1747 break;
1748 case ::sw::GetTextAttrMode::Parent: pMatchFunc = &lcl_GetTextAttrParent;
1749 break;
1750 default: assert(false);
1753 for( size_t i = pSwpHints->GetFirstPosSortedByWhichAndStart(nWhich); i < nSize; ++i )
1755 SwTextAttr *const pHint = pSwpHints->GetSortedByWhichAndStart(i);
1756 if (pHint->Which() != nWhich)
1757 break; // hints are sorted by which&start, so we are done...
1759 sal_Int32 const nHintStart = pHint->GetStart();
1760 if (nIndex < nHintStart)
1761 break; // hints are sorted by which&start, so we are done...
1763 sal_Int32 const*const pEndIdx = pHint->GetEnd();
1764 // cannot have hint with no end and no dummy char
1765 assert(pEndIdx || pHint->HasDummyChar());
1766 // If EXPAND is set, simulate the text input behavior, i.e.
1767 // move the start, and expand the end.
1768 bool const bContained( pEndIdx
1769 ? (*pMatchFunc)(nIndex, nHintStart, *pEndIdx)
1770 : (nHintStart == nIndex) );
1771 if (bContained)
1773 if (pVector)
1775 if (nPreviousIndex < nHintStart)
1777 pVector->clear(); // clear hints that are outside pHint
1778 nPreviousIndex = nHintStart;
1780 pVector->push_back(pHint);
1782 else
1784 *ppTextAttr = pHint; // and possibly overwrite outer hint
1786 if (!pEndIdx)
1788 break;
1794 std::vector<SwTextAttr *>
1795 SwTextNode::GetTextAttrsAt(sal_Int32 const nIndex, sal_uInt16 const nWhich) const
1797 assert(nWhich >= RES_TXTATR_BEGIN && nWhich < RES_TXTATR_END);
1798 std::vector<SwTextAttr *> ret;
1799 lcl_GetTextAttrs(&ret, nullptr, m_pSwpHints.get(), nIndex, nWhich, ::sw::GetTextAttrMode::Default);
1800 return ret;
1803 SwTextAttr *
1804 SwTextNode::GetTextAttrAt(sal_Int32 const nIndex, sal_uInt16 const nWhich,
1805 ::sw::GetTextAttrMode const eMode) const
1807 assert( (nWhich == RES_TXTATR_META)
1808 || (nWhich == RES_TXTATR_METAFIELD)
1809 || (nWhich == RES_TXTATR_AUTOFMT)
1810 || (nWhich == RES_TXTATR_INETFMT)
1811 || (nWhich == RES_TXTATR_CJK_RUBY)
1812 || (nWhich == RES_TXTATR_UNKNOWN_CONTAINER)
1813 || (nWhich == RES_TXTATR_CONTENTCONTROL)
1814 || (nWhich == RES_TXTATR_INPUTFIELD ) );
1815 // "GetTextAttrAt() will give wrong result for this hint!")
1817 SwTextAttr * pRet(nullptr);
1818 lcl_GetTextAttrs(nullptr, & pRet, m_pSwpHints.get(), nIndex, nWhich, eMode);
1819 return pRet;
1822 const SwTextInputField* SwTextNode::GetOverlappingInputField( const SwTextAttr& rTextAttr ) const
1824 const SwTextInputField* pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(rTextAttr.GetStart(), RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
1826 if ( pTextInputField == nullptr && rTextAttr.End() != nullptr )
1828 pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(*(rTextAttr.End()), RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
1831 return pTextInputField;
1834 void SwTextNode::DelFrames_TextNodePart()
1836 SetWrong( nullptr );
1837 SetWrongDirty(sw::WrongState::TODO);
1839 SetGrammarCheck( nullptr );
1840 SetGrammarCheckDirty( true );
1842 SetSmartTags( nullptr );
1843 SetSmartTagDirty( true );
1845 SetWordCountDirty( true );
1846 SetAutoCompleteWordDirty( true );
1849 SwTextField* SwTextNode::GetFieldTextAttrAt(
1850 const sal_Int32 nIndex,
1851 ::sw::GetTextAttrMode const eMode) const
1853 SwTextField* pTextField = dynamic_cast<SwTextField*>(GetTextAttrForCharAt( nIndex, RES_TXTATR_FIELD ));
1854 if ( pTextField == nullptr )
1856 pTextField = dynamic_cast<SwTextField*>(GetTextAttrForCharAt( nIndex, RES_TXTATR_ANNOTATION ));
1858 if ( pTextField == nullptr )
1860 pTextField =
1861 dynamic_cast<SwTextField*>( GetTextAttrAt(
1862 nIndex,
1863 RES_TXTATR_INPUTFIELD,
1864 eMode));
1867 return pTextField;
1870 static SwCharFormat* lcl_FindCharFormat( const SwCharFormats* pCharFormats, std::u16string_view rName )
1872 if( !rName.empty() )
1874 const size_t nArrLen = pCharFormats->size();
1875 for( size_t i = 1; i < nArrLen; i++ )
1877 SwCharFormat* pFormat = (*pCharFormats)[ i ];
1878 if( pFormat->GetName()==rName )
1879 return pFormat;
1882 return nullptr;
1885 static void lcl_CopyHint(
1886 const sal_uInt16 nWhich,
1887 const SwTextAttr * const pHt,
1888 SwTextAttr *const pNewHt,
1889 SwDoc *const pOtherDoc,
1890 SwTextNode *const pDest )
1892 assert(nWhich == pHt->Which()); // wrong hint-id
1893 switch( nWhich )
1895 // copy nodesarray section with footnote content
1896 case RES_TXTATR_FTN :
1897 assert(pDest); // "lcl_CopyHint: no destination text node?"
1898 static_cast<const SwTextFootnote*>(pHt)->CopyFootnote( *static_cast<SwTextFootnote*>(pNewHt), *pDest);
1899 break;
1901 // Fields that are copied into different SwDocs must be registered
1902 // at their new FieldTypes.
1904 case RES_TXTATR_FIELD :
1906 if( pOtherDoc != nullptr )
1908 static_txtattr_cast<const SwTextField*>(pHt)->CopyTextField(
1909 static_txtattr_cast<SwTextField*>(pNewHt));
1912 // Table Formula must be copied relative.
1913 const SwFormatField& rField = pHt->GetFormatField();
1914 if( SwFieldIds::Table == rField.GetField()->GetTyp()->Which()
1915 && static_cast<const SwTableField*>(rField.GetField())->IsIntrnlName())
1917 // convert internal formula to external
1918 const SwTableNode* const pDstTableNd =
1919 static_txtattr_cast<const SwTextField*>(pHt)->GetTextNode().FindTableNode();
1920 if( pDstTableNd )
1922 SwTableField* const pTableField =
1923 const_cast<SwTableField*>(static_cast<const SwTableField*>(
1924 pNewHt->GetFormatField().GetField()));
1925 pTableField->PtrToBoxNm( &pDstTableNd->GetTable() );
1929 break;
1931 case RES_TXTATR_INPUTFIELD :
1932 case RES_TXTATR_ANNOTATION :
1933 if( pOtherDoc != nullptr )
1935 static_txtattr_cast<const SwTextField*>(pHt)->CopyTextField(
1936 static_txtattr_cast<SwTextField*>(pNewHt));
1938 break;
1940 case RES_TXTATR_TOXMARK :
1941 if( pOtherDoc && pDest && pDest->GetpSwpHints()
1942 && pDest->GetpSwpHints()->Contains( pNewHt ) )
1944 // ToXMarks that are copied to different SwDocs must register
1945 // at their new ToX (sw::BroadcastingModify).
1946 static_txtattr_cast<SwTextTOXMark*>(pNewHt)->CopyTOXMark(*pOtherDoc);
1948 break;
1950 case RES_TXTATR_CHARFMT :
1951 // For CharacterStyles, the format must be copied too.
1952 if( pDest && pDest->GetpSwpHints()
1953 && pDest->GetpSwpHints()->Contains( pNewHt ) )
1955 SwCharFormat* pFormat = pHt->GetCharFormat().GetCharFormat();
1957 if (pOtherDoc)
1959 pFormat = pOtherDoc->CopyCharFormat( *pFormat );
1961 const_cast<SwFormatCharFormat&>(
1962 pNewHt->GetCharFormat() ).SetCharFormat( pFormat );
1964 break;
1965 case RES_TXTATR_INETFMT :
1967 // For Hyperlinks, the format must be copied too.
1968 if( pOtherDoc && pDest && pDest->GetpSwpHints()
1969 && pDest->GetpSwpHints()->Contains( pNewHt ) )
1971 const SwDoc& rDoc = static_txtattr_cast<
1972 const SwTextINetFormat*>(pHt)->GetTextNode().GetDoc();
1973 const SwCharFormats* pCharFormats = rDoc.GetCharFormats();
1974 const SwFormatINetFormat& rFormat = pHt->GetINetFormat();
1975 SwCharFormat* pFormat;
1976 pFormat = lcl_FindCharFormat( pCharFormats, rFormat.GetINetFormat() );
1977 if( pFormat )
1978 pOtherDoc->CopyCharFormat( *pFormat );
1979 pFormat = lcl_FindCharFormat( pCharFormats, rFormat.GetVisitedFormat() );
1980 if( pFormat )
1981 pOtherDoc->CopyCharFormat( *pFormat );
1983 //JP 24.04.98: The attribute must point to a text node, so that
1984 // the styles can be created.
1985 SwTextINetFormat *const pINetHt = static_txtattr_cast<SwTextINetFormat*>(pNewHt);
1986 if ( !pINetHt->GetpTextNode() )
1988 pINetHt->ChgTextNode( pDest );
1991 //JP 22.10.97: set up link to char style
1992 pINetHt->GetCharFormat();
1993 break;
1995 case RES_TXTATR_META:
1996 case RES_TXTATR_METAFIELD:
1997 OSL_ENSURE( pNewHt, "copying Meta should not fail!" );
1998 OSL_ENSURE( pDest
1999 && (CH_TXTATR_INWORD == pDest->GetText()[pNewHt->GetStart()]),
2000 "missing CH_TXTATR?");
2001 break;
2005 /// copy attributes at position nTextStartIdx to node pDest
2006 // BP 7.6.93: Intentionally copy only attributes _with_ EndIdx!
2007 // CopyAttr is usually called when attributes are set on a
2008 // node with no text.
2009 void SwTextNode::CopyAttr( SwTextNode *pDest, const sal_Int32 nTextStartIdx,
2010 const sal_Int32 nOldPos )
2012 if ( HasHints() )
2014 SwDoc* const pOtherDoc = (&pDest->GetDoc() != &GetDoc()) ?
2015 &pDest->GetDoc() : nullptr;
2017 for ( size_t i = 0; i < m_pSwpHints->Count(); ++i )
2019 SwTextAttr *const pHt = m_pSwpHints->Get(i);
2020 sal_Int32 const nAttrStartIdx = pHt->GetStart();
2021 if ( nTextStartIdx < nAttrStartIdx )
2022 break; // beyond end of text, because nLen == 0
2024 const sal_Int32 *const pEndIdx = pHt->GetEnd();
2025 if ( pEndIdx && !pHt->HasDummyChar() )
2027 sal_uInt16 const nWhich = pHt->Which();
2028 if (RES_TXTATR_INPUTFIELD != nWhich // fdo#74981 skip fields
2029 && ( *pEndIdx > nTextStartIdx
2030 || (*pEndIdx == nTextStartIdx
2031 && nAttrStartIdx == nTextStartIdx)))
2033 if ( RES_TXTATR_REFMARK != nWhich )
2035 // attribute in the area => copy
2036 SwTextAttr *const pNewHt =
2037 pDest->InsertItem( pHt->GetAttr(), nOldPos, nOldPos, SetAttrMode::IS_COPY);
2038 if ( pNewHt )
2040 lcl_CopyHint( nWhich, pHt, pNewHt,
2041 pOtherDoc, pDest );
2044 else if( !pOtherDoc
2045 ? GetDoc().IsCopyIsMove()
2046 : nullptr == pOtherDoc->GetRefMark( pHt->GetRefMark().GetRefName() ) )
2048 pDest->InsertItem(
2049 pHt->GetAttr(), nOldPos, nOldPos, SetAttrMode::IS_COPY);
2056 if( this != pDest )
2058 // notify layout frames, to prevent disappearance of footnote numbers
2059 SwUpdateAttr aHint(
2060 nOldPos,
2061 nOldPos,
2064 pDest->TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
2068 /// copy text and attributes to node pDest
2069 void SwTextNode::CopyText( SwTextNode *const pDest,
2070 const SwContentIndex &rStart,
2071 const sal_Int32 nLen,
2072 const bool bForceCopyOfAllAttrs )
2074 SwContentIndex const aIdx( pDest, pDest->m_Text.getLength() );
2075 CopyText( pDest, aIdx, rStart, nLen, bForceCopyOfAllAttrs );
2078 void SwTextNode::CopyText( SwTextNode *const pDest,
2079 const SwContentIndex &rDestStart,
2080 const SwPosition &rStart,
2081 sal_Int32 nLen,
2082 const bool bForceCopyOfAllAttrs )
2084 CopyText( pDest, rDestStart, rStart.nContent, nLen, bForceCopyOfAllAttrs );
2087 void SwTextNode::CopyText( SwTextNode *const pDest,
2088 const SwContentIndex &rDestStart,
2089 const SwContentIndex &rStart,
2090 sal_Int32 nLen,
2091 const bool bForceCopyOfAllAttrs )
2093 CHECK_SWPHINTS_IF_FRM(this);
2094 CHECK_SWPHINTS(pDest);
2095 assert(rDestStart.GetContentNode() == pDest);
2096 assert(rStart.GetContentNode() == this);
2097 sal_Int32 nTextStartIdx = rStart.GetIndex();
2098 sal_Int32 nDestStart = rDestStart.GetIndex(); // remember old Pos
2100 if (pDest->GetDoc().IsClipBoard() && GetNum())
2102 // #i111677# cache expansion of source (for clipboard)
2103 pDest->m_oNumStringCache = (nTextStartIdx != 0)
2104 ? OUString() // fdo#49076: numbering only if copy from para start
2105 : GetNumString();
2108 if( !nLen )
2110 // if no length is given, copy attributes at position rStart
2111 CopyAttr( pDest, nTextStartIdx, nDestStart );
2113 // copy hard attributes on whole paragraph
2114 if( HasSwAttrSet() )
2116 // i#96213 all or just the Char attributes?
2117 if ( !bForceCopyOfAllAttrs &&
2118 ( nDestStart ||
2119 pDest->HasSwAttrSet() ||
2120 nLen != pDest->GetText().getLength()))
2122 SfxItemSetFixed<
2123 RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
2124 RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
2125 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>
2126 aCharSet( pDest->GetDoc().GetAttrPool() );
2127 aCharSet.Put( *GetpSwAttrSet() );
2128 if( aCharSet.Count() )
2130 pDest->SetAttr( aCharSet, nDestStart, nDestStart );
2133 else
2135 GetpSwAttrSet()->CopyToModify( *pDest );
2138 return;
2141 // 1. copy text
2142 const sal_Int32 oldLen = pDest->m_Text.getLength();
2143 // JP 15.02.96: missing attribute handling at the end!
2144 // hence call InsertText and don't modify m_Text directly
2145 pDest->InsertText( m_Text.copy(nTextStartIdx, nLen), rDestStart,
2146 SwInsertFlags::EMPTYEXPAND );
2148 // update with actual new size
2149 nLen = pDest->m_Text.getLength() - oldLen;
2150 if ( !nLen ) // string not longer?
2151 return;
2153 SwDoc* const pOtherDoc = (&pDest->GetDoc() != &GetDoc()) ? &pDest->GetDoc() : nullptr;
2155 // copy hard attributes on whole paragraph
2156 if( HasSwAttrSet() )
2158 // i#96213 all or just the Char attributes?
2159 if ( !bForceCopyOfAllAttrs &&
2160 ( nDestStart ||
2161 pDest->HasSwAttrSet() ||
2162 nLen != pDest->GetText().getLength()))
2164 SfxItemSetFixed<
2165 RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
2166 RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
2167 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>
2168 aCharSet( pDest->GetDoc().GetAttrPool() );
2169 aCharSet.Put( *GetpSwAttrSet() );
2170 if( aCharSet.Count() )
2172 pDest->SetAttr( aCharSet, nDestStart, nDestStart + nLen );
2175 else
2177 GetpSwAttrSet()->CopyToModify( *pDest );
2181 bool const bUndoNodes = !pOtherDoc
2182 && GetDoc().GetIDocumentUndoRedo().IsUndoNodes(GetNodes());
2184 // Fetch end only now, because copying into self updates the start index
2185 // and all attributes
2186 nTextStartIdx = rStart.GetIndex();
2187 const sal_Int32 nEnd = nTextStartIdx + nLen;
2189 // 2. copy attributes
2190 // Iterate over attribute array until the start of the attribute
2191 // is behind the copied range
2192 const size_t nSize = m_pSwpHints ? m_pSwpHints->Count() : 0;
2194 // If copying into self, inserting can delete attributes!
2195 // Hence first copy into temp-array, and then move that into hints array.
2196 SwpHts aArr;
2198 // Del-Array for all RefMarks without extent
2199 SwpHts aRefMrkArr;
2201 std::vector<std::pair<sal_Int32, sal_Int32>> metaFieldRanges;
2202 sal_Int32 nDeletedDummyChars(0);
2203 for (size_t n = 0; n < nSize; ++n)
2205 SwTextAttr * const pHt = m_pSwpHints->Get(n);
2207 const sal_Int32 nAttrStartIdx = pHt->GetStart();
2208 if ( nAttrStartIdx >= nEnd )
2209 break;
2211 const sal_Int32 * const pEndIdx = pHt->GetEnd();
2212 const sal_uInt16 nWhich = pHt->Which();
2214 // JP 26.04.94: RefMarks are never copied. If the refmark doesn't have
2215 // an extent, there is a dummy char in the text, which
2216 // must be removed. So we first copy the attribute,
2217 // but remember it, and when we're done delete it,
2218 // which also deletes the dummy character!
2219 // JP 14.08.95: May RefMarks be moved?
2220 const bool bCopyRefMark = RES_TXTATR_REFMARK == nWhich
2221 && ( bUndoNodes
2222 || ( !pOtherDoc
2223 ? GetDoc().IsCopyIsMove()
2224 : nullptr == pOtherDoc->GetRefMark( pHt->GetRefMark().GetRefName() ) ) );
2226 if ( pEndIdx
2227 && RES_TXTATR_REFMARK == nWhich
2228 && !bCopyRefMark )
2230 continue;
2233 // Input Fields are only copied, if completely covered by copied text
2234 if ( nWhich == RES_TXTATR_INPUTFIELD )
2236 assert(pEndIdx != nullptr &&
2237 "<SwTextNode::CopyText(..)> - RES_TXTATR_INPUTFIELD without EndIndex!" );
2238 if ( nAttrStartIdx < nTextStartIdx
2239 || ( pEndIdx != nullptr
2240 && *pEndIdx > nEnd ) )
2242 continue;
2246 if (nWhich == RES_TXTATR_METAFIELD)
2248 // Skip metadata fields. Also remember the range to strip the text later.
2249 metaFieldRanges.emplace_back(nAttrStartIdx, pEndIdx ? *pEndIdx : nEnd);
2250 continue;
2253 sal_Int32 nAttrStt = 0;
2254 sal_Int32 nAttrEnd = 0;
2256 if( nAttrStartIdx < nTextStartIdx )
2258 // start is before selection
2259 // copy hints with end and CH_TXTATR only if dummy char is copied
2260 if ( pEndIdx && (*pEndIdx > nTextStartIdx) && !pHt->HasDummyChar() )
2262 // attribute with extent and the end is in the selection
2263 nAttrStt = nDestStart;
2264 nAttrEnd = (*pEndIdx > nEnd)
2265 ? rDestStart.GetIndex()
2266 : nDestStart + (*pEndIdx) - nTextStartIdx;
2268 else
2270 continue;
2273 else
2275 // start is in the selection
2276 nAttrStt = nDestStart + ( nAttrStartIdx - nTextStartIdx );
2277 if( pEndIdx )
2279 nAttrEnd = *pEndIdx > nEnd
2280 ? rDestStart.GetIndex()
2281 : nDestStart + ( *pEndIdx - nTextStartIdx );
2283 else
2285 nAttrEnd = nAttrStt;
2289 SwTextAttr * pNewHt = nullptr;
2291 if( pDest == this )
2293 // copy the hint here, but insert it later
2294 pNewHt = MakeTextAttr( GetDoc(), pHt->GetAttr(),
2295 nAttrStt, nAttrEnd, CopyOrNewType::Copy, pDest );
2297 lcl_CopyHint(nWhich, pHt, pNewHt, nullptr, pDest);
2298 aArr.push_back( pNewHt );
2300 else
2302 pNewHt = pDest->InsertItem(
2303 pHt->GetAttr(),
2304 nAttrStt - nDeletedDummyChars,
2305 nAttrEnd - nDeletedDummyChars,
2306 SetAttrMode::NOTXTATRCHR | SetAttrMode::IS_COPY);
2307 if (pNewHt)
2309 lcl_CopyHint( nWhich, pHt, pNewHt, pOtherDoc, pDest );
2311 else if (pHt->HasDummyChar())
2313 // The attribute that has failed to be copied would insert
2314 // dummy char, so positions of the following attributes have
2315 // to be shifted by one to compensate for that missing char.
2316 ++nDeletedDummyChars;
2320 if( RES_TXTATR_REFMARK == nWhich && !pEndIdx && !bCopyRefMark )
2322 aRefMrkArr.push_back( pNewHt );
2326 // Strip the metadata fields, since we don't copy the RDF entries
2327 // yet and so they are inconsistent upon copy/pasting.
2328 if (!metaFieldRanges.empty())
2330 // Reverse to remove without messing the offsets.
2331 std::reverse(metaFieldRanges.begin(), metaFieldRanges.end());
2332 for (const auto& pair : metaFieldRanges)
2334 const SwContentIndex aIdx(pDest, pair.first);
2335 pDest->EraseText(aIdx, pair.second - pair.first);
2339 // this can only happen when copying into self
2340 for (SwTextAttr* i : aArr)
2342 InsertHint( i, SetAttrMode::NOTXTATRCHR );
2345 if( pDest->GetpSwpHints() )
2347 for (SwTextAttr* pNewHt : aRefMrkArr)
2349 if( pNewHt->GetEnd() )
2351 pDest->GetpSwpHints()->Delete( pNewHt );
2352 pDest->DestroyAttr( pNewHt );
2354 else
2356 const SwContentIndex aIdx( pDest, pNewHt->GetStart() );
2357 pDest->EraseText( aIdx, 1 );
2362 CHECK_SWPHINTS_IF_FRM(this);
2363 CHECK_SWPHINTS(pDest);
2366 OUString SwTextNode::InsertText( const OUString & rStr, const SwPosition & rIdx,
2367 const SwInsertFlags nMode )
2369 return InsertText(rStr, rIdx.nContent, nMode);
2372 OUString SwTextNode::InsertText( const OUString & rStr, const SwContentIndex & rIdx,
2373 const SwInsertFlags nMode )
2375 assert(rIdx.GetContentNode() == this);
2376 assert(rIdx <= m_Text.getLength()); // invalid index
2378 const sal_Int32 aPos = rIdx.GetIndex();
2379 sal_Int32 nLen = m_Text.getLength() - aPos;
2380 sal_Int32 const nOverflow(rStr.getLength() - GetSpaceLeft());
2381 SAL_WARN_IF(nOverflow > 0, "sw.core",
2382 "SwTextNode::InsertText: node text with insertion > capacity.");
2383 OUString const sInserted(
2384 (nOverflow > 0) ? rStr.copy(0, rStr.getLength() - nOverflow) : rStr);
2385 if (sInserted.isEmpty())
2387 return sInserted;
2389 if (aPos == 0 && m_Text.isEmpty())
2390 m_Text = sInserted;
2391 else
2392 m_Text = m_Text.replaceAt(aPos, 0, sInserted);
2393 assert(GetSpaceLeft()>=0);
2394 nLen = m_Text.getLength() - aPos - nLen;
2395 assert(nLen != 0);
2397 bool bOldExpFlg = IsIgnoreDontExpand();
2398 if (nMode & SwInsertFlags::FORCEHINTEXPAND)
2400 SetIgnoreDontExpand( true );
2403 Update(rIdx, nLen, UpdateMode::Default); // text content changed!
2405 if (nMode & SwInsertFlags::FORCEHINTEXPAND)
2407 SetIgnoreDontExpand( bOldExpFlg );
2410 if ( HasWriterListeners() )
2411 { // send this before messing with hints, which will send RES_UPDATE_ATTR
2412 auto aInsHint = sw::MakeInsertText(*this, aPos, nLen);
2413 CallSwClientNotify(aInsHint);
2416 if ( HasHints() )
2418 m_pSwpHints->SortIfNeedBe();
2419 bool const bHadHints(!m_pSwpHints->CanBeDeleted());
2420 bool bMergePortionsNeeded(false);
2421 for ( size_t i = 0; i < m_pSwpHints->Count() &&
2422 rIdx >= m_pSwpHints->GetWithoutResorting(i)->GetStart(); ++i )
2424 SwTextAttr * const pHt = m_pSwpHints->GetWithoutResorting( i );
2425 const sal_Int32 * const pEndIdx = pHt->GetEnd();
2426 if( !pEndIdx )
2427 continue;
2429 if( rIdx == *pEndIdx )
2431 if ( (nMode & SwInsertFlags::NOHINTEXPAND) ||
2432 (!(nMode & SwInsertFlags::FORCEHINTEXPAND)
2433 && pHt->DontExpand()) )
2435 m_pSwpHints->DeleteAtPos(i);
2436 // on empty attributes also adjust Start
2437 if( rIdx == pHt->GetStart() )
2438 pHt->SetStart( pHt->GetStart() - nLen );
2439 pHt->SetEnd(*pEndIdx - nLen);
2440 // could be that pHt has IsFormatIgnoreEnd set, and it's
2441 // not a RSID-only hint - now we have the inserted text
2442 // between pHt and its continuation... which we don't know.
2443 // punt the job to MergePortions below.
2444 if (pHt->IsFormatIgnoreEnd())
2446 bMergePortionsNeeded = true;
2448 InsertHint( pHt, SetAttrMode::NOHINTADJUST );
2450 // empty hints at insert position?
2451 else if ( (nMode & SwInsertFlags::EMPTYEXPAND)
2452 && (*pEndIdx == pHt->GetStart()) )
2454 m_pSwpHints->DeleteAtPos(i);
2455 pHt->SetStart( pHt->GetStart() - nLen );
2456 const size_t nCurrentLen = m_pSwpHints->Count();
2457 InsertHint( pHt/* AUTOSTYLES:, SetAttrMode::NOHINTADJUST*/ );
2458 if ( nCurrentLen > m_pSwpHints->Count() && i )
2460 --i;
2462 continue;
2464 else
2466 continue;
2469 if ( !(nMode & SwInsertFlags::NOHINTEXPAND) &&
2470 rIdx == nLen && pHt->GetStart() == rIdx.GetIndex() &&
2471 !pHt->IsDontExpandStartAttr() )
2473 // no field, at paragraph start, HintExpand
2474 m_pSwpHints->DeleteAtPos(i);
2475 pHt->SetStart( pHt->GetStart() - nLen );
2476 // no effect on format ignore flags here (para start)
2477 InsertHint( pHt, SetAttrMode::NOHINTADJUST );
2480 if (bMergePortionsNeeded)
2482 m_pSwpHints->MergePortions(*this);
2484 SAL_WARN_IF(bHadHints && m_pSwpHints->CanBeDeleted(), "sw.core",
2485 "SwTextNode::InsertText: unexpected loss of hints");
2488 // By inserting a character, the hidden flags
2489 // at the TextNode can become invalid:
2490 SetCalcHiddenCharFlags();
2492 CHECK_SWPHINTS(this);
2493 return sInserted;
2496 void SwTextNode::CutText( SwTextNode * const pDest,
2497 const SwContentIndex & rStart, const sal_Int32 nLen )
2499 assert(pDest); // Cut requires a destination
2500 SwContentIndex aDestStt(pDest, pDest->GetText().getLength());
2501 CutImpl( pDest, aDestStt, rStart, nLen, false );
2504 void SwTextNode::CutImpl( SwTextNode * const pDest, const SwContentIndex & rDestStart,
2505 const SwContentIndex & rStart, sal_Int32 nLen, const bool bUpdate )
2507 assert(pDest); // Cut requires a destination
2509 assert(&GetDoc() == &pDest->GetDoc()); // must be same document
2511 assert(pDest != this); // destination must be different node
2513 assert(rDestStart.GetContentNode() == pDest);
2514 assert(rStart.GetContentNode() == this);
2516 if( !nLen )
2518 // if no length is given, copy attributes at position rStart
2519 CopyAttr( pDest, rStart.GetIndex(), rDestStart.GetIndex() );
2520 return;
2523 sal_Int32 nTextStartIdx = rStart.GetIndex();
2524 sal_Int32 nDestStart = rDestStart.GetIndex(); // remember old Pos
2525 const sal_Int32 nInitSize = pDest->m_Text.getLength();
2527 if (pDest->GetSpaceLeft() < nLen)
2528 { // FIXME: could only happen when called from SwRangeRedline::Show.
2529 // unfortunately can't really do anything here to handle that...
2530 abort();
2532 pDest->m_Text = pDest->m_Text.replaceAt(nDestStart, 0,
2533 m_Text.subView(nTextStartIdx, nLen));
2534 OUString const newText = m_Text.replaceAt(nTextStartIdx, nLen, u"");
2535 nLen = pDest->m_Text.getLength() - nInitSize; // update w/ current size!
2536 if (!nLen) // String didn't grow?
2537 return;
2539 if (bUpdate)
2541 // Update all SwContentIndex
2542 pDest->Update(rDestStart, nLen, UpdateMode::Default);
2545 CHECK_SWPHINTS(pDest);
2547 const sal_Int32 nEnd = rStart.GetIndex() + nLen;
2548 bool const bUndoNodes =
2549 GetDoc().GetIDocumentUndoRedo().IsUndoNodes(GetNodes());
2551 // copy hard attributes on whole paragraph
2552 if (HasSwAttrSet())
2554 bool hasSwAttrSet = pDest->HasSwAttrSet();
2555 if (hasSwAttrSet)
2557 // if we have our own property set it doesn't mean
2558 // that this set defines any style different to Standard one.
2559 hasSwAttrSet = false;
2561 // so, let's check deeper if property set has defined any property
2562 if (pDest->GetpSwAttrSet())
2564 // check all items in the property set
2565 SfxItemIter aIter( *pDest->GetpSwAttrSet() );
2566 const SfxPoolItem* pItem = aIter.GetCurItem();
2569 // check current item
2570 const sal_uInt16 nWhich = IsInvalidItem( pItem )
2571 ? pDest->GetpSwAttrSet()->GetWhichByPos( aIter.GetCurPos() )
2572 : pItem->Which();
2573 if( RES_FRMATR_STYLE_NAME != nWhich &&
2574 RES_FRMATR_CONDITIONAL_STYLE_NAME != nWhich &&
2575 RES_PAGEDESC != nWhich &&
2576 RES_BREAK != nWhich &&
2577 SfxItemState::SET == pDest->GetpSwAttrSet()->GetItemState( nWhich, false ) )
2579 // check if parent value (original value in style) has the same value as in [pItem]
2580 const SfxPoolItem& rParentItem = pDest->GetpSwAttrSet()->GetParent()->Get( nWhich, true );
2582 hasSwAttrSet = (rParentItem != *pItem);
2584 // property set is not empty => no need to make anymore checks
2585 if (hasSwAttrSet)
2586 break;
2589 // let's check next item
2590 pItem = aIter.NextItem();
2591 } while (pItem);
2595 // all or just the Char attributes?
2596 if( nInitSize || hasSwAttrSet ||
2597 nLen != pDest->GetText().getLength())
2599 SfxItemSetFixed<
2600 RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
2601 RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
2602 RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>
2603 aCharSet( pDest->GetDoc().GetAttrPool() );
2604 aCharSet.Put( *GetpSwAttrSet() );
2605 if( aCharSet.Count() )
2606 pDest->SetAttr( aCharSet, nDestStart, nDestStart + nLen );
2608 else
2610 // Copy all attrs except RES_PARATR_LIST_LEVEL: it was initialized before
2611 // and current SwTextNode can contain not suitable for pDest value
2612 SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_LEVEL - 1,
2613 RES_PARATR_LIST_LEVEL + 1, HINT_END>
2614 aCharSet(pDest->GetDoc().GetAttrPool());
2615 aCharSet.Put(*GetpSwAttrSet());
2616 if (aCharSet.Count())
2617 pDest->SetAttr(aCharSet, nDestStart, nDestStart + nLen);
2621 // notify frames - before moving hints, because footnotes
2622 // want to find their anchor text frame in the follow chain
2623 // (also ignore fieldmarks, the caller will recreate frames)
2624 const sw::InsertText aInsHint(nDestStart, nLen, false, false);
2625 pDest->HandleNonLegacyHint(aInsHint);
2626 const sw::MoveText aMoveHint(pDest, nDestStart, nTextStartIdx, nLen);
2627 CallSwClientNotify(aMoveHint);
2628 const sw::DeleteText aDelText(nTextStartIdx, nLen);
2629 HandleNonLegacyHint(aDelText);
2631 // 2. move attributes
2632 // Iterate over attribute array until the start of the attribute
2633 // is behind the moved range
2634 bool bMergePortionsNeeded(false);
2635 size_t nAttrCnt = 0;
2636 while (m_pSwpHints && (nAttrCnt < m_pSwpHints->Count()))
2638 SwTextAttr * const pHt = m_pSwpHints->Get(nAttrCnt);
2639 const sal_Int32 nAttrStartIdx = pHt->GetStart();
2640 if ( nAttrStartIdx >= nEnd )
2641 break;
2642 const sal_Int32 * const pEndIdx = pHt->GetEnd();
2643 const sal_uInt16 nWhich = pHt->Which();
2644 SwTextAttr *pNewHt = nullptr;
2646 // if the hint has a dummy character, then it must not be split!
2647 if(nAttrStartIdx < nTextStartIdx)
2649 // start is before the range
2650 if (!pHt->HasDummyChar() && ( RES_TXTATR_REFMARK != nWhich
2651 || bUndoNodes ) && pEndIdx && *pEndIdx > nTextStartIdx)
2653 // attribute with extent and end of attribute is in the range
2654 pNewHt = MakeTextAttr( pDest->GetDoc(), pHt->GetAttr(),
2655 nDestStart,
2656 nDestStart + (
2657 *pEndIdx > nEnd
2658 ? nLen
2659 : *pEndIdx - nTextStartIdx ) );
2662 else
2664 // start is inside the range
2665 if (!pEndIdx || *pEndIdx < nEnd ||
2666 (!bUndoNodes && RES_TXTATR_REFMARK == nWhich)
2667 || pHt->HasDummyChar() )
2669 // do not delete note and later add it -> sidebar flickering
2670 if (GetDoc().GetDocShell())
2672 GetDoc().GetDocShell()->Broadcast( SfxHint(SfxHintId::SwSplitNodeOperation));
2674 // move attribute
2675 m_pSwpHints->Delete( pHt );
2676 // reset start/end indexes
2677 if (pHt->IsFormatIgnoreStart() || pHt->IsFormatIgnoreEnd())
2679 bMergePortionsNeeded = true;
2681 pHt->SetStart(nDestStart + (nAttrStartIdx - nTextStartIdx));
2682 if (pEndIdx)
2684 pHt->SetEnd( nDestStart + (
2685 *pEndIdx > nEnd
2686 ? nLen
2687 : *pEndIdx - nTextStartIdx ) );
2689 pDest->InsertHint( pHt,
2690 SetAttrMode::NOTXTATRCHR
2691 | SetAttrMode::DONTREPLACE );
2692 if (GetDoc().GetDocShell())
2694 GetDoc().GetDocShell()->Broadcast( SfxHint(SfxHintId::SwSplitNodeOperation));
2696 continue; // iterate while loop, no ++ !
2698 // the end is behind the range
2699 else if (RES_TXTATR_REFMARK != nWhich || bUndoNodes)
2701 pNewHt = MakeTextAttr( GetDoc(), pHt->GetAttr(),
2702 nDestStart + (nAttrStartIdx - nTextStartIdx),
2703 nDestStart + (*pEndIdx > nEnd
2704 ? nLen
2705 : *pEndIdx - nTextStartIdx));
2708 if (pNewHt)
2710 const bool bSuccess( pDest->InsertHint( pNewHt,
2711 SetAttrMode::NOTXTATRCHR
2712 | SetAttrMode::DONTREPLACE
2713 | SetAttrMode::IS_COPY) );
2714 if (bSuccess)
2716 lcl_CopyHint( nWhich, pHt, pNewHt, nullptr, pDest );
2719 ++nAttrCnt;
2721 // If there are still empty attributes around, they have a higher priority
2722 // than any attributes that become empty due to the move.
2723 // So temporarily remove them and Update the array, then re-insert the
2724 // removed ones so they overwrite the others.
2725 if (m_pSwpHints && nAttrCnt < m_pSwpHints->Count())
2727 SwpHts aArr;
2728 while (nAttrCnt < m_pSwpHints->Count())
2730 SwTextAttr * const pHt = m_pSwpHints->Get(nAttrCnt);
2731 if (nEnd != pHt->GetStart())
2732 break;
2733 const sal_Int32 * const pEndIdx = pHt->GetEnd();
2734 if (pEndIdx && *pEndIdx == nEnd)
2736 aArr.push_back( pHt );
2737 m_pSwpHints->Delete( pHt );
2739 else
2741 ++nAttrCnt;
2744 Update(rStart, nLen, UpdateMode::Negative|UpdateMode::Delete);
2746 for (SwTextAttr* pHt : aArr)
2748 pHt->SetStart( rStart.GetIndex() );
2749 pHt->SetEnd( rStart.GetIndex() );
2750 InsertHint( pHt );
2753 else
2755 Update(rStart, nLen, UpdateMode::Negative|UpdateMode::Delete);
2758 // set after moving hints
2759 m_Text = newText;
2761 if (bMergePortionsNeeded)
2763 m_pSwpHints->MergePortions(*this);
2766 CHECK_SWPHINTS(this);
2768 TryDeleteSwpHints();
2771 void SwTextNode::EraseText(const SwPosition &rIdx, const sal_Int32 nCount,
2772 const SwInsertFlags nMode )
2774 EraseText(rIdx.nContent, nCount, nMode);
2777 void SwTextNode::EraseText(const SwContentIndex &rIdx, const sal_Int32 nCount,
2778 const SwInsertFlags nMode )
2780 assert(rIdx.GetContentNode() == this);
2781 assert(rIdx <= m_Text.getLength()); // invalid index
2783 const sal_Int32 nStartIdx = rIdx.GetIndex();
2784 const sal_Int32 nCnt = (nCount==SAL_MAX_INT32)
2785 ? m_Text.getLength() - nStartIdx : nCount;
2786 const sal_Int32 nEndIdx = nStartIdx + nCnt;
2787 if (nEndIdx <= m_Text.getLength())
2788 m_Text = m_Text.replaceAt(nStartIdx, nCnt, u"");
2790 // GCAttr(); don't remove all empty ones, just the ones that are in the
2791 // range but not at the end of the range.
2793 for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i )
2795 SwTextAttr *pHt = m_pSwpHints->Get(i);
2797 const sal_Int32 nHintStart = pHt->GetStart();
2799 if ( nHintStart < nStartIdx )
2800 continue;
2802 if ( nHintStart > nEndIdx )
2803 break; // hints are sorted by end, so break here
2805 const sal_Int32* pHtEndIdx = pHt->GetEnd();
2806 const sal_uInt16 nWhich = pHt->Which();
2808 if( !pHtEndIdx )
2810 // attribute with neither end nor CH_TXTATR?
2811 assert(pHt->HasDummyChar());
2812 if (isTXTATR(nWhich) && (nHintStart < nEndIdx))
2814 m_pSwpHints->DeleteAtPos(i);
2815 DestroyAttr( pHt );
2816 --i;
2818 continue;
2821 assert(!( (nHintStart < nEndIdx) && (*pHtEndIdx > nEndIdx)
2822 && pHt->HasDummyChar() )
2823 // next line: deleting exactly dummy char: DeleteAttributes
2824 || ((nHintStart == nStartIdx) && (nHintStart + 1 == nEndIdx)));
2825 // "ERROR: deleting left-overlapped attribute with CH_TXTATR");
2827 // Delete the hint if:
2828 // 1. The hint ends before the deletion end position or
2829 // 2. The hint ends at the deletion end position and
2830 // we are not in empty expand mode and
2831 // the hint is a [toxmark|refmark|ruby|inputfield] text attribute
2832 // 3. deleting exactly the dummy char of an hint with end and dummy
2833 // char deletes the hint
2834 if ( (*pHtEndIdx < nEndIdx)
2835 || ( (*pHtEndIdx == nEndIdx) &&
2836 !(SwInsertFlags::EMPTYEXPAND & nMode) &&
2837 ( (RES_TXTATR_TOXMARK == nWhich) ||
2838 (RES_TXTATR_REFMARK == nWhich) ||
2839 (RES_TXTATR_CJK_RUBY == nWhich) ||
2840 (RES_TXTATR_INPUTFIELD == nWhich) ) )
2841 || ( (nHintStart < nEndIdx) &&
2842 pHt->HasDummyChar() )
2845 m_pSwpHints->DeleteAtPos(i);
2846 DestroyAttr( pHt );
2847 --i;
2851 OSL_ENSURE(rIdx.GetIndex() == nStartIdx, "huh? start index has changed?");
2853 TryDeleteSwpHints();
2855 Update(rIdx, nCnt, UpdateMode::Negative);
2857 if(1 == nCnt)
2859 const auto aHint = sw::DeleteChar(nStartIdx);
2860 CallSwClientNotify(aHint);
2861 } else {
2862 const auto aHint = sw::DeleteText(nStartIdx, nCnt);
2863 CallSwClientNotify(aHint);
2866 OSL_ENSURE(rIdx.GetIndex() == nStartIdx, "huh? start index has changed?");
2868 // By deleting a character, the hidden flags
2869 // at the TextNode can become invalid:
2870 SetCalcHiddenCharFlags();
2872 CHECK_SWPHINTS(this);
2875 void SwTextNode::GCAttr()
2877 if ( !HasHints() )
2878 return;
2880 bool bChanged = false;
2881 sal_Int32 nMin = m_Text.getLength();
2882 sal_Int32 nMax = 0;
2883 const bool bAll = nMin != 0; // on empty paragraphs only remove INetFormats
2885 for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i )
2887 SwTextAttr * const pHt = m_pSwpHints->Get(i);
2889 // if end and start are equal, delete it
2890 const sal_Int32 * const pEndIdx = pHt->GetEnd();
2891 if (pEndIdx && !pHt->HasDummyChar() && (*pEndIdx == pHt->GetStart())
2892 && ( bAll || pHt->Which() == RES_TXTATR_INETFMT ) )
2894 bChanged = true;
2895 nMin = std::min( nMin, pHt->GetStart() );
2896 nMax = std::max( nMax, *pHt->GetEnd() );
2897 DestroyAttr( m_pSwpHints->Cut(i) );
2898 --i;
2900 else
2902 pHt->SetDontExpand( false );
2905 TryDeleteSwpHints();
2907 if(bChanged)
2909 // textframes react to aHint, others to aNew
2910 SwUpdateAttr aHint(
2911 nMin,
2912 nMax,
2915 CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aHint));
2916 SwFormatChg aNew( GetTextColl() );
2917 CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aNew));
2921 SwNumRule* SwTextNode::GetNumRule(bool bInParent) const
2923 SwNumRule* pRet = nullptr;
2925 const SfxPoolItem* pItem = GetNoCondAttr( RES_PARATR_NUMRULE, bInParent );
2926 bool bNoNumRule = false;
2927 if ( pItem )
2929 OUString sNumRuleName =
2930 static_cast<const SwNumRuleItem *>(pItem)->GetValue();
2931 if (!sNumRuleName.isEmpty())
2933 pRet = GetDoc().FindNumRulePtr( sNumRuleName );
2935 else // numbering is turned off
2936 bNoNumRule = true;
2939 if ( !bNoNumRule )
2941 if ( pRet && pRet == GetDoc().GetOutlineNumRule() &&
2942 ( !HasSwAttrSet() ||
2943 SfxItemState::SET !=
2944 GetpSwAttrSet()->GetItemState( RES_PARATR_NUMRULE, false ) ) )
2946 SwTextFormatColl* pColl = GetTextColl();
2947 if ( pColl )
2949 const SwNumRuleItem& rDirectItem = pColl->GetNumRule( false );
2950 if ( rDirectItem.GetValue().isEmpty() )
2952 pRet = nullptr;
2958 return pRet;
2961 void SwTextNode::NumRuleChgd()
2963 if ( IsInList() )
2965 SwNumRule* pNumRule = GetNumRule();
2966 if ( pNumRule && pNumRule != GetNum()->GetNumRule() )
2968 mpNodeNum->ChangeNumRule( *pNumRule );
2969 if (mpNodeNumRLHidden)
2971 mpNodeNumRLHidden->ChangeNumRule(*pNumRule);
2976 // Sending "noop" modify in order to cause invalidations of registered
2977 // <SwTextFrame> instances to get the list style change respectively the change
2978 // in the list tree reflected in the layout.
2979 // Important note:
2981 SvxTextLeftMarginItem & rLR = const_cast<SvxTextLeftMarginItem&>(GetSwAttrSet().GetTextLeftMargin());
2982 CallSwClientNotify(sw::LegacyModifyHint(&rLR, &rLR));
2985 SetWordCountDirty( true );
2988 // -> #i27615#
2989 bool SwTextNode::IsNumbered(SwRootFrame const*const pLayout) const
2991 SwNumRule* pRule = GetNum(pLayout) ? GetNum(pLayout)->GetNumRule() : nullptr;
2992 return pRule && IsCountedInList();
2995 bool SwTextNode::HasMarkedLabel() const
2997 bool bResult = false;
2999 if ( IsInList() )
3001 bResult =
3002 GetDoc().getIDocumentListsAccess().getListByName( GetListId() )->IsListLevelMarked( GetActualListLevel() );
3005 return bResult;
3007 // <- #i27615#
3009 SwTextNode* SwTextNode::MakeNewTextNode( SwNode& rPosNd, bool bNext,
3010 bool bChgFollow )
3012 // ignore hard PageBreak/PageDesc/ColumnBreak from Auto-Set
3013 std::optional<SwAttrSet> oNewAttrSet;
3014 // #i75353#
3015 bool bClearHardSetNumRuleWhenFormatCollChanges( false );
3016 if( HasSwAttrSet() )
3018 oNewAttrSet.emplace( *GetpSwAttrSet() );
3019 const SfxItemSet* pTmpSet = GetpSwAttrSet();
3021 if (bNext) // successor doesn't inherit breaks!
3022 pTmpSet = &*oNewAttrSet;
3024 // !bNext: remove PageBreaks/PageDesc/ColBreak from this
3025 bool bRemoveFromCache = false;
3026 std::vector<sal_uInt16> aClearWhichIds;
3027 if ( bNext )
3028 bRemoveFromCache = ( 0 != oNewAttrSet->ClearItem( RES_PAGEDESC ) );
3029 else
3030 aClearWhichIds.push_back( RES_PAGEDESC );
3032 if( SfxItemState::SET == pTmpSet->GetItemState( RES_BREAK, false ) )
3034 if ( bNext )
3035 oNewAttrSet->ClearItem( RES_BREAK );
3036 else
3037 aClearWhichIds.push_back( RES_BREAK );
3038 bRemoveFromCache = true;
3040 if( SfxItemState::SET == pTmpSet->GetItemState( RES_KEEP, false ) )
3042 if ( bNext )
3043 oNewAttrSet->ClearItem( RES_KEEP );
3044 else
3045 aClearWhichIds.push_back( RES_KEEP );
3046 bRemoveFromCache = true;
3048 if( SfxItemState::SET == pTmpSet->GetItemState( RES_PARATR_SPLIT, false ) )
3050 if ( bNext )
3051 oNewAttrSet->ClearItem( RES_PARATR_SPLIT );
3052 else
3053 aClearWhichIds.push_back( RES_PARATR_SPLIT );
3054 bRemoveFromCache = true;
3056 if(SfxItemState::SET == pTmpSet->GetItemState(RES_PARATR_NUMRULE, false))
3058 SwNumRule * pRule = GetNumRule();
3060 if (pRule && IsOutline())
3062 if ( bNext )
3063 oNewAttrSet->ClearItem(RES_PARATR_NUMRULE);
3064 else
3066 // #i75353#
3067 // No clear of hard set numbering rule at an outline paragraph at this point.
3068 // Only if the paragraph style changes - see below.
3069 bClearHardSetNumRuleWhenFormatCollChanges = true;
3071 bRemoveFromCache = true;
3075 if ( !aClearWhichIds.empty() )
3076 bRemoveFromCache = 0 != ClearItemsFromAttrSet( aClearWhichIds );
3078 if( !bNext && bRemoveFromCache )
3080 InvalidateInSwCache(RES_OBJECTDYING);
3083 SwNodes& rNds = GetNodes();
3085 SwTextFormatColl* pColl = GetTextColl();
3087 SwTextNode *pNode = new SwTextNode( rPosNd, pColl, oNewAttrSet ? &*oNewAttrSet : nullptr );
3089 oNewAttrSet.reset();
3091 const SwNumRule* pRule = GetNumRule();
3092 if( pRule && pRule == pNode->GetNumRule() && rNds.IsDocNodes() )
3094 // #i55459#
3095 // - correction: parameter <bNext> has to be checked, as it was in the
3096 // previous implementation.
3097 if ( !bNext && !IsCountedInList() )
3098 SetCountedInList(true);
3101 // In case the numbering caused a style from the pool to be assigned to
3102 // the new node, don't overwrite that here!
3103 if( pColl != pNode->GetTextColl() ||
3104 ( bChgFollow && pColl != GetTextColl() ))
3105 return pNode; // that ought to be enough?
3107 pNode->ChgTextCollUpdateNum( nullptr, pColl ); // for numbering/outline
3108 if( bNext || !bChgFollow )
3109 return pNode;
3111 SwTextFormatColl *pNextColl = &pColl->GetNextTextFormatColl();
3112 // i#101870 perform action on different paragraph styles before applying
3113 // the new paragraph style
3114 if (pNextColl != pColl)
3116 // #i75353#
3117 if ( bClearHardSetNumRuleWhenFormatCollChanges )
3119 if ( ClearItemsFromAttrSet( { RES_PARATR_NUMRULE } ) != 0 )
3121 InvalidateInSwCache(RES_ATTRSET_CHG);
3125 ChgFormatColl( pNextColl );
3127 return pNode;
3130 SwContentNode* SwTextNode::AppendNode( const SwPosition & rPos )
3132 // position behind which it will be inserted
3133 SwTextNode* pNew = MakeNewTextNode( *rPos.GetNodes()[rPos.GetNodeIndex() + 1] );
3135 // reset list attributes at appended text node
3136 pNew->ResetAttr( RES_PARATR_LIST_ISRESTART );
3137 pNew->ResetAttr( RES_PARATR_LIST_RESTARTVALUE );
3138 pNew->ResetAttr( RES_PARATR_LIST_ISCOUNTED );
3139 if ( pNew->GetNumRule() == nullptr )
3141 pNew->ResetAttr( RES_PARATR_LIST_ID );
3142 pNew->ResetAttr( RES_PARATR_LIST_LEVEL );
3145 if (!IsInList() && GetNumRule() && !GetListId().isEmpty())
3147 AddToList();
3150 if( HasWriterListeners() )
3151 MakeFramesForAdjacentContentNode(*pNew);
3152 return pNew;
3155 SwTextAttr * SwTextNode::GetTextAttrForCharAt(
3156 const sal_Int32 nIndex,
3157 const sal_uInt16 nWhich ) const
3159 assert(nWhich >= RES_TXTATR_BEGIN && nWhich <= RES_TXTATR_END);
3160 if ( HasHints() )
3162 for ( size_t i = 0; i < m_pSwpHints->Count(); ++i )
3164 SwTextAttr * const pHint = m_pSwpHints->Get(i);
3165 const sal_Int32 nStartPos = pHint->GetStart();
3166 if ( nIndex < nStartPos )
3168 return nullptr;
3170 if ( (nIndex == nStartPos) && pHint->HasDummyChar() )
3172 return ( RES_TXTATR_END == nWhich || nWhich == pHint->Which() )
3173 ? pHint : nullptr;
3177 return nullptr;
3180 SwTextAttr* SwTextNode::GetTextAttrForEndCharAt(sal_Int32 nIndex, sal_uInt16 nWhich) const
3182 SwTextAttr* pAttr = GetTextAttrAt(nIndex, nWhich, ::sw::GetTextAttrMode::Expand);
3183 if (!pAttr)
3185 return nullptr;
3188 if (!pAttr->End())
3190 return nullptr;
3193 // The start-end range covers the end dummy character.
3194 if (*pAttr->End() - 1 != nIndex)
3196 return nullptr;
3199 return pAttr;
3202 namespace
3205 sal_uInt16 lcl_BoundListLevel(const int nActualLevel)
3207 return o3tl::narrowing<sal_uInt16>( std::clamp( nActualLevel, 0, MAXLEVEL-1 ) );
3212 // -> #i29560#
3213 bool SwTextNode::HasNumber(SwRootFrame const*const pLayout) const
3215 bool bResult = false;
3217 const SwNumRule *const pRule = GetNum(pLayout) ? GetNum(pLayout)->GetNumRule() : nullptr;
3218 if ( pRule )
3220 const SwNumFormat& aFormat(pRule->Get(lcl_BoundListLevel(GetActualListLevel())));
3222 // #i40041#
3223 bResult = aFormat.IsEnumeration();
3226 return bResult;
3229 bool SwTextNode::HasBullet() const
3231 bool bResult = false;
3233 const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
3234 if ( pRule )
3236 const SwNumFormat& aFormat(pRule->Get(lcl_BoundListLevel(GetActualListLevel())));
3238 bResult = aFormat.IsItemize();
3241 return bResult;
3243 // <- #i29560#
3245 // #128041# - introduce parameter <_bInclPrefixAndSuffixStrings>
3246 //i53420 added max outline parameter
3247 OUString SwTextNode::GetNumString( const bool _bInclPrefixAndSuffixStrings,
3248 const unsigned int _nRestrictToThisLevel,
3249 SwRootFrame const*const pLayout, SwListRedlineType eRedline) const
3251 if (GetDoc().IsClipBoard() && m_oNumStringCache)
3253 // #i111677# do not expand number strings in clipboard documents
3254 return *m_oNumStringCache;
3256 const SwNumRule* pRule = GetNum(pLayout, eRedline) ? GetNum(pLayout, eRedline)->GetNumRule() : nullptr;
3257 if ( pRule &&
3258 IsCountedInList() )
3260 SvxNumberType const& rNumberType(
3261 pRule->Get( lcl_BoundListLevel(GetActualListLevel(eRedline)) ) );
3262 if (rNumberType.IsTextFormat() ||
3264 (style::NumberingType::NUMBER_NONE == rNumberType.GetNumberingType()))
3266 return pRule->MakeNumString( GetNum(pLayout, eRedline)->GetNumberVector(),
3267 _bInclPrefixAndSuffixStrings,
3268 _nRestrictToThisLevel,
3269 nullptr,
3270 GetLang(0));
3274 return OUString();
3277 tools::Long SwTextNode::GetLeftMarginWithNum( bool bTextLeft ) const
3279 tools::Long nRet = 0;
3280 const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
3281 if( pRule )
3283 const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
3285 if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
3287 nRet = rFormat.GetAbsLSpace();
3289 if( !bTextLeft )
3291 if( 0 > rFormat.GetFirstLineOffset() &&
3292 nRet > -rFormat.GetFirstLineOffset() )
3293 nRet = nRet + rFormat.GetFirstLineOffset();
3294 else
3295 nRet = 0;
3298 if( pRule->IsAbsSpaces() )
3300 SvxFirstLineIndentItem const& rFirst(GetSwAttrSet().GetFirstLineIndent());
3301 nRet = nRet - GetSwAttrSet().GetTextLeftMargin().GetLeft(rFirst);
3304 else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
3306 ::sw::ListLevelIndents const indents(AreListLevelIndentsApplicable());
3307 // note: the result is *always* added to either the left-margin
3308 // or the text-left-margin of the node itself by the caller.
3309 // so first, subtract what the caller has computed anyway,
3310 // and then add the value according to combination of
3311 // list/paragraph items. (this is rather inelegant)
3312 SvxFirstLineIndentItem firstLine(GetSwAttrSet().GetFirstLineIndent());
3313 SvxTextLeftMarginItem leftMargin(GetSwAttrSet().GetTextLeftMargin());
3314 nRet = bTextLeft
3315 ? - leftMargin.GetTextLeft()
3316 : - leftMargin.GetLeft(firstLine);
3317 if (indents & ::sw::ListLevelIndents::LeftMargin)
3319 leftMargin.SetTextLeft(rFormat.GetIndentAt());
3321 if (indents & ::sw::ListLevelIndents::FirstLine)
3323 firstLine.SetTextFirstLineOffset(rFormat.GetFirstLineIndent());
3325 nRet += bTextLeft
3326 ? leftMargin.GetTextLeft()
3327 : leftMargin.GetLeft(firstLine);
3331 return nRet;
3334 bool SwTextNode::GetFirstLineOfsWithNum( short& rFLOffset ) const
3336 // #i95907#
3337 rFLOffset = 0;
3339 // #i51089#
3340 const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
3341 if ( pRule )
3343 if ( IsCountedInList() )
3345 const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
3346 if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
3348 rFLOffset = rFormat.GetFirstLineOffset(); //TODO: overflow
3350 if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
3352 SvxFirstLineIndentItem const aItem(GetSwAttrSet().GetFirstLineIndent());
3353 rFLOffset = rFLOffset + aItem.GetTextFirstLineOffset();
3356 else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
3358 if (AreListLevelIndentsApplicable() & ::sw::ListLevelIndents::FirstLine)
3360 rFLOffset = rFormat.GetFirstLineIndent();
3362 else if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
3364 SvxFirstLineIndentItem const aItem(GetSwAttrSet().GetFirstLineIndent());
3365 rFLOffset = aItem.GetTextFirstLineOffset();
3370 return true;
3373 rFLOffset = GetSwAttrSet().GetFirstLineIndent().GetTextFirstLineOffset();
3374 return false;
3377 SwTwips SwTextNode::GetAdditionalIndentForStartingNewList() const
3379 SwTwips nAdditionalIndent = 0;
3381 const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
3382 if ( pRule )
3384 const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
3385 if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
3387 SvxFirstLineIndentItem const& rFirst(GetSwAttrSet().GetFirstLineIndent());
3388 nAdditionalIndent = GetSwAttrSet().GetTextLeftMargin().GetLeft(rFirst);
3390 if (getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
3392 nAdditionalIndent = nAdditionalIndent -
3393 GetSwAttrSet().GetFirstLineIndent().GetTextFirstLineOffset();
3396 else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
3398 // note: there was an apparent bug here, list GetIndentAt()
3399 // was interpreted as left-margin not text-left-margin unlike every
3400 // other use of it.
3401 ::sw::ListLevelIndents const indents(AreListLevelIndentsApplicable());
3402 SvxFirstLineIndentItem const& rFirst(
3403 indents & ::sw::ListLevelIndents::FirstLine
3404 ? SvxFirstLineIndentItem(rFormat.GetFirstLineIndent(), RES_MARGIN_FIRSTLINE)
3405 : GetSwAttrSet().GetFirstLineIndent());
3406 SvxTextLeftMarginItem const& rLeft(
3407 indents & ::sw::ListLevelIndents::LeftMargin
3408 ? SvxTextLeftMarginItem(rFormat.GetIndentAt(), RES_MARGIN_TEXTLEFT)
3409 : GetSwAttrSet().GetTextLeftMargin());
3410 nAdditionalIndent = rLeft.GetLeft(rFirst);
3411 if (!(indents & ::sw::ListLevelIndents::FirstLine))
3413 if (getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
3415 nAdditionalIndent = nAdditionalIndent - rFirst.GetTextFirstLineOffset();
3420 else
3422 SvxFirstLineIndentItem const& rFirst(GetSwAttrSet().GetFirstLineIndent());
3423 nAdditionalIndent = GetSwAttrSet().GetTextLeftMargin().GetLeft(rFirst);
3426 return nAdditionalIndent;
3429 // #i96772#
3430 void SwTextNode::ClearLRSpaceItemDueToListLevelIndents(
3431 std::unique_ptr<SvxFirstLineIndentItem>& o_rFirstLineItem,
3432 std::unique_ptr<SvxTextLeftMarginItem>& o_rTextLeftMarginItem) const
3434 ::sw::ListLevelIndents const result(AreListLevelIndentsApplicable());
3435 if (result != ::sw::ListLevelIndents::No)
3437 const SwNumRule* pRule = GetNumRule();
3438 if ( pRule && GetActualListLevel() >= 0 )
3440 const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
3441 if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
3443 if (result & ::sw::ListLevelIndents::FirstLine)
3445 o_rFirstLineItem = std::make_unique<SvxFirstLineIndentItem>(RES_MARGIN_FIRSTLINE);
3447 if (result & ::sw::ListLevelIndents::LeftMargin)
3449 o_rTextLeftMarginItem = std::make_unique<SvxTextLeftMarginItem>(RES_MARGIN_TEXTLEFT);
3456 // #i91133#
3457 tools::Long SwTextNode::GetLeftMarginForTabCalculation() const
3459 tools::Long nLeftMarginForTabCalc = 0;
3461 bool bLeftMarginForTabCalcSetToListLevelIndent( false );
3462 const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
3463 if( pRule )
3465 const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
3466 if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
3468 if (AreListLevelIndentsApplicable() & ::sw::ListLevelIndents::LeftMargin)
3470 nLeftMarginForTabCalc = rFormat.GetIndentAt();
3471 bLeftMarginForTabCalcSetToListLevelIndent = true;
3475 if ( !bLeftMarginForTabCalcSetToListLevelIndent )
3477 nLeftMarginForTabCalc = GetSwAttrSet().GetTextLeftMargin().GetTextLeft();
3480 return nLeftMarginForTabCalc;
3483 static void Replace0xFF(
3484 SwTextNode const& rNode,
3485 OUStringBuffer & rText,
3486 sal_Int32 & rTextStt,
3487 sal_Int32 nEndPos )
3489 if (!rNode.GetpSwpHints())
3490 return;
3492 sal_Unicode cSrchChr = CH_TXTATR_BREAKWORD;
3493 for( int nSrchIter = 0; 2 > nSrchIter; ++nSrchIter, cSrchChr = CH_TXTATR_INWORD )
3495 sal_Int32 nPos = rText.indexOf(cSrchChr);
3496 while (-1 != nPos && nPos < nEndPos)
3498 const SwTextAttr* const pAttr =
3499 rNode.GetTextAttrForCharAt(rTextStt + nPos);
3500 if( pAttr )
3502 switch( pAttr->Which() )
3504 case RES_TXTATR_FIELD:
3505 case RES_TXTATR_ANNOTATION:
3506 rText.remove(nPos, 1);
3507 ++rTextStt;
3508 break;
3510 case RES_TXTATR_FTN:
3511 rText.remove(nPos, 1);
3512 ++rTextStt;
3513 break;
3515 default:
3516 rText.remove(nPos, 1);
3517 ++rTextStt;
3520 else
3522 ++nPos;
3523 ++nEndPos;
3525 nPos = rText.indexOf(cSrchChr, nPos);
3530 // Expand fields
3531 // #i83479# - handling of new parameters
3532 OUString SwTextNode::GetExpandText(SwRootFrame const*const pLayout,
3533 const sal_Int32 nIdx,
3534 const sal_Int32 nLen,
3535 const bool bWithNum,
3536 const bool bAddSpaceAfterListLabelStr,
3537 const bool bWithSpacesForLevel,
3538 const ExpandMode eAdditionalMode) const
3541 ExpandMode eMode = ExpandMode::ExpandFields | eAdditionalMode;
3542 if (pLayout && pLayout->IsHideRedlines())
3544 eMode |= ExpandMode::HideDeletions;
3547 ModelToViewHelper aConversionMap(*this, pLayout, eMode);
3548 const OUString aExpandText = aConversionMap.getViewText();
3549 const sal_Int32 nExpandBegin = aConversionMap.ConvertToViewPosition( nIdx );
3550 sal_Int32 nEnd = nLen == -1 ? GetText().getLength() : nIdx + nLen;
3551 const sal_Int32 nExpandEnd = aConversionMap.ConvertToViewPosition( nEnd );
3552 OUStringBuffer aText(aExpandText.subView(nExpandBegin, nExpandEnd-nExpandBegin));
3554 // remove dummy characters of Input Fields
3555 comphelper::string::remove(aText, CH_TXT_ATR_INPUTFIELDSTART);
3556 comphelper::string::remove(aText, CH_TXT_ATR_INPUTFIELDEND);
3557 comphelper::string::remove(aText, CH_TXTATR_BREAKWORD);
3559 if( bWithNum )
3561 if (!GetNumString(true, MAXLEVEL, pLayout).isEmpty())
3563 if ( bAddSpaceAfterListLabelStr )
3565 const sal_Unicode aSpace = ' ';
3566 aText.insert(0, aSpace);
3568 aText.insert(0, GetNumString(true, MAXLEVEL, pLayout));
3572 if (bWithSpacesForLevel)
3574 const sal_Unicode aSpace = ' ';
3575 for (int nLevel = GetActualListLevel(); nLevel > 0; --nLevel)
3577 aText.insert(0, aSpace);
3578 aText.insert(0, aSpace);
3582 return aText.makeStringAndClear();
3585 bool SwTextNode::CopyExpandText(SwTextNode& rDestNd, const SwContentIndex* pDestIdx,
3586 sal_Int32 nIdx, sal_Int32 nLen,
3587 SwRootFrame const*const pLayout, bool bWithNum,
3588 bool bWithFootnote, bool bReplaceTabsWithSpaces ) const
3590 if( &rDestNd == this )
3591 return false;
3592 assert(!pDestIdx || pDestIdx->GetContentNode() == &rDestNd);
3594 SwContentIndex aDestIdx(&rDestNd, rDestNd.GetText().getLength());
3595 if( pDestIdx )
3596 aDestIdx = *pDestIdx;
3597 const sal_Int32 nDestStt = aDestIdx.GetIndex();
3599 // first, start with the text
3600 OUStringBuffer buf(GetText());
3601 if( bReplaceTabsWithSpaces )
3602 buf.replace('\t', ' ');
3604 // mask hidden characters
3605 const sal_Unicode cChar = CH_TXTATR_BREAKWORD;
3606 SwScriptInfo::MaskHiddenRanges(*this, buf, 0, buf.getLength(), cChar);
3608 buf.remove(0, nIdx);
3609 if (nLen != -1)
3611 buf.truncate(nLen);
3613 // remove dummy characters of Input Fields
3615 comphelper::string::remove(buf, CH_TXT_ATR_INPUTFIELDSTART);
3616 comphelper::string::remove(buf, CH_TXT_ATR_INPUTFIELDEND);
3618 rDestNd.InsertText(buf.makeStringAndClear(), aDestIdx);
3619 nLen = aDestIdx.GetIndex() - nDestStt;
3621 // set all char attributes with Symbol font
3622 if ( HasHints() )
3624 sal_Int32 nInsPos = nDestStt - nIdx;
3625 for ( size_t i = 0; i < m_pSwpHints->Count(); ++i )
3627 const SwTextAttr* pHt = m_pSwpHints->Get(i);
3628 const sal_Int32 nAttrStartIdx = pHt->GetStart();
3629 const sal_uInt16 nWhich = pHt->Which();
3630 if (nIdx + nLen <= nAttrStartIdx)
3631 break; // behind end of text
3633 const sal_Int32 *pEndIdx = pHt->End();
3634 if( pEndIdx && *pEndIdx > nIdx &&
3635 ( RES_CHRATR_FONT == nWhich ||
3636 RES_TXTATR_CHARFMT == nWhich ||
3637 RES_TXTATR_AUTOFMT == nWhich ))
3639 const SvxFontItem* const pFont =
3640 CharFormat::GetItem( *pHt, RES_CHRATR_FONT );
3641 if ( pFont && RTL_TEXTENCODING_SYMBOL == pFont->GetCharSet() )
3643 // attribute in area => copy
3644 rDestNd.InsertItem( *const_cast<SvxFontItem*>(pFont),
3645 nInsPos + nAttrStartIdx, nInsPos + *pEndIdx );
3648 else if ( pHt->HasDummyChar() && (nAttrStartIdx >= nIdx) )
3650 aDestIdx = nInsPos + nAttrStartIdx;
3651 switch( nWhich )
3653 case RES_TXTATR_FIELD:
3654 case RES_TXTATR_ANNOTATION:
3656 OUString const aExpand(
3657 static_txtattr_cast<SwTextField const*>(pHt)->GetFormatField().GetField()->ExpandField(true, pLayout));
3658 if (!aExpand.isEmpty())
3660 ++aDestIdx; // insert behind
3661 OUString const ins(
3662 rDestNd.InsertText( aExpand, aDestIdx));
3663 SAL_INFO_IF(ins.getLength() != aExpand.getLength(),
3664 "sw.core", "GetExpandText lossage");
3665 aDestIdx = nInsPos + nAttrStartIdx;
3666 nInsPos += ins.getLength();
3668 rDestNd.EraseText( aDestIdx, 1 );
3669 --nInsPos;
3671 break;
3673 case RES_TXTATR_FTN:
3675 if ( bWithFootnote )
3677 const SwFormatFootnote& rFootnote = pHt->GetFootnote();
3678 OUString sExpand;
3679 auto const number(pLayout && pLayout->IsHideRedlines()
3680 ? rFootnote.GetNumberRLHidden()
3681 : rFootnote.GetNumber());
3682 if( !rFootnote.GetNumStr().isEmpty() )
3683 sExpand = rFootnote.GetNumStr();
3684 else if( rFootnote.IsEndNote() )
3685 sExpand = GetDoc().GetEndNoteInfo().m_aFormat.
3686 GetNumStr(number);
3687 else
3688 sExpand = GetDoc().GetFootnoteInfo().m_aFormat.
3689 GetNumStr(number);
3690 if( !sExpand.isEmpty() )
3692 ++aDestIdx; // insert behind
3693 SvxEscapementItem aItem( SvxEscapement::Superscript, RES_CHRATR_ESCAPEMENT );
3694 rDestNd.InsertItem(
3695 aItem,
3696 aDestIdx.GetIndex(),
3697 aDestIdx.GetIndex() );
3698 OUString const ins( rDestNd.InsertText(sExpand, aDestIdx, SwInsertFlags::EMPTYEXPAND));
3699 SAL_INFO_IF(ins.getLength() != sExpand.getLength(),
3700 "sw.core", "GetExpandText lossage");
3701 aDestIdx = nInsPos + nAttrStartIdx;
3702 nInsPos += ins.getLength();
3705 rDestNd.EraseText( aDestIdx, 1 );
3706 --nInsPos;
3708 break;
3710 default:
3711 rDestNd.EraseText( aDestIdx, 1 );
3712 --nInsPos;
3718 if( bWithNum )
3720 aDestIdx = nDestStt;
3721 rDestNd.InsertText( GetNumString(true, MAXLEVEL, pLayout), aDestIdx );
3724 aDestIdx = 0;
3725 sal_Int32 nStartDelete(-1);
3726 while (aDestIdx < rDestNd.GetText().getLength())
3728 sal_Unicode const cur(rDestNd.GetText()[aDestIdx.GetIndex()]);
3729 if ( (cChar == cur) // filter substituted hidden text
3730 || (CH_TXT_ATR_FIELDSTART == cur) // filter all fieldmarks
3731 || (CH_TXT_ATR_FIELDSEP == cur)
3732 || (CH_TXT_ATR_FIELDEND == cur)
3733 || (CH_TXT_ATR_FORMELEMENT == cur))
3735 if (-1 == nStartDelete)
3737 nStartDelete = aDestIdx.GetIndex(); // start deletion range
3739 ++aDestIdx;
3740 if (aDestIdx < rDestNd.GetText().getLength())
3742 continue;
3743 } // else: end of paragraph => delete, see below
3745 else
3747 if (-1 == nStartDelete)
3749 ++aDestIdx;
3750 continue;
3751 } // else: delete, see below
3753 assert(-1 != nStartDelete); // without delete range, would have continued
3754 rDestNd.EraseText(
3755 SwContentIndex(&rDestNd, nStartDelete),
3756 aDestIdx.GetIndex() - nStartDelete);
3757 assert(aDestIdx.GetIndex() == nStartDelete);
3758 nStartDelete = -1; // reset
3761 return true;
3764 OUString SwTextNode::GetRedlineText() const
3766 std::vector<sal_Int32> aRedlArr;
3767 const SwDoc& rDoc = GetDoc();
3768 SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos( *this, RedlineType::Delete );
3769 if( SwRedlineTable::npos != nRedlPos )
3771 // some redline-delete object exists for the node
3772 const SwNodeOffset nNdIdx = GetIndex();
3773 for( ; nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() ; ++nRedlPos )
3775 const SwRangeRedline* pTmp = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
3776 if( RedlineType::Delete == pTmp->GetType() )
3778 const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End();
3779 if( pRStt->GetNodeIndex() < nNdIdx )
3781 if( pREnd->GetNodeIndex() > nNdIdx )
3782 // paragraph is fully deleted
3783 return OUString();
3784 else if( pREnd->GetNodeIndex() == nNdIdx )
3786 // deleted from 0 to nContent
3787 aRedlArr.push_back( 0 );
3788 aRedlArr.push_back( pREnd->GetContentIndex() );
3791 else if( pRStt->GetNodeIndex() == nNdIdx )
3793 //aRedlArr.Insert( pRStt->GetContentIndex(), aRedlArr.Count() );
3794 aRedlArr.push_back( pRStt->GetContentIndex() );
3795 if( pREnd->GetNodeIndex() == nNdIdx )
3796 aRedlArr.push_back( pREnd->GetContentIndex() );
3797 else
3799 aRedlArr.push_back(GetText().getLength());
3800 break; // that was all
3803 else
3804 break; // that was all
3809 OUStringBuffer aText(GetText());
3811 sal_Int32 nTextStt = 0;
3812 sal_Int32 nIdxEnd = aText.getLength();
3813 for( size_t n = 0; n < aRedlArr.size(); n += 2 )
3815 sal_Int32 nStt = aRedlArr[ n ];
3816 sal_Int32 nEnd = aRedlArr[ n+1 ];
3817 if( ( 0 <= nStt && nStt <= nIdxEnd ) ||
3818 ( 0 <= nEnd && nEnd <= nIdxEnd ))
3820 if( nStt < 0 ) nStt = 0;
3821 if( nIdxEnd < nEnd ) nEnd = nIdxEnd;
3822 const sal_Int32 nDelCnt = nEnd - nStt;
3823 aText.remove(nStt - nTextStt, nDelCnt);
3824 Replace0xFF(*this, aText, nTextStt, nStt - nTextStt);
3825 nTextStt += nDelCnt;
3827 else if( nStt >= nIdxEnd )
3828 break;
3830 Replace0xFF(*this, aText, nTextStt, aText.getLength());
3832 return aText.makeStringAndClear();
3835 void SwTextNode::ReplaceText( const SwContentIndex& rStart, const sal_Int32 nDelLen,
3836 const OUString & rStr)
3838 assert(rStart.GetContentNode() == this);
3839 assert( rStart.GetIndex() < m_Text.getLength() // index out of bounds
3840 && rStart.GetIndex() + nDelLen <= m_Text.getLength());
3842 sal_Int32 const nOverflow(rStr.getLength() - nDelLen - GetSpaceLeft());
3843 SAL_WARN_IF(nOverflow > 0, "sw.core",
3844 "SwTextNode::ReplaceText: node text with insertion > node capacity.");
3845 OUString const sInserted(
3846 (nOverflow > 0) ? rStr.copy(0, rStr.getLength() - nOverflow) : rStr);
3847 if (sInserted.isEmpty() && 0 == nDelLen)
3849 return; // nothing to do
3852 const sal_Int32 nStartPos = rStart.GetIndex();
3853 sal_Int32 nEndPos = nStartPos + nDelLen;
3854 sal_Int32 nLen = nDelLen;
3855 for( sal_Int32 nPos = nStartPos; nPos < nEndPos; ++nPos )
3857 if ((CH_TXTATR_BREAKWORD == m_Text[nPos]) ||
3858 (CH_TXTATR_INWORD == m_Text[nPos]))
3860 SwTextAttr *const pHint = GetTextAttrForCharAt( nPos );
3861 if (pHint)
3863 assert(!( pHint->GetEnd() && pHint->HasDummyChar()
3864 && (pHint->GetStart() < nEndPos)
3865 && (*pHint->GetEnd() > nEndPos) ));
3866 // "deleting left-overlapped attribute with CH_TXTATR"
3867 DeleteAttribute( pHint );
3868 --nEndPos;
3869 --nLen;
3874 bool bOldExpFlg = IsIgnoreDontExpand();
3875 SetIgnoreDontExpand( true );
3877 if (nLen && sInserted.getLength())
3879 // Replace the 1st char, then delete the rest and insert.
3880 // This way the attributes of the 1st char are expanded!
3881 m_Text = m_Text.replaceAt(nStartPos, 1, sInserted.subView(0, 1));
3883 ++const_cast<SwContentIndex&>(rStart);
3884 m_Text = m_Text.replaceAt(rStart.GetIndex(), nLen - 1, u"");
3885 Update(rStart, nLen - 1, UpdateMode::Negative);
3887 std::u16string_view aTmpText( sInserted.subView(1) );
3888 m_Text = m_Text.replaceAt(rStart.GetIndex(), 0, aTmpText);
3889 Update(rStart, aTmpText.size(), UpdateMode::Replace);
3891 else
3893 m_Text = m_Text.replaceAt(nStartPos, nLen, u"");
3894 Update(rStart, nLen, UpdateMode::Negative);
3896 m_Text = m_Text.replaceAt(nStartPos, 0, sInserted);
3897 Update(rStart, sInserted.getLength(), UpdateMode::Replace);
3900 SetIgnoreDontExpand( bOldExpFlg );
3901 auto aDelHint = sw::DeleteText(nStartPos, nDelLen);
3902 CallSwClientNotify(aDelHint);
3904 if (sInserted.getLength())
3906 auto aInsHint = sw::MakeInsertText(*this, nStartPos, sInserted.getLength());
3907 CallSwClientNotify(aInsHint);
3911 void SwTextNode::ReplaceText( SwPosition& rStart, const sal_Int32 nDelLen,
3912 const OUString & rStr)
3914 ReplaceText(rStart.nContent, nDelLen, rStr);
3917 namespace {
3918 void lcl_ResetParAttrs( SwTextNode &rTextNode )
3920 const o3tl::sorted_vector<sal_uInt16> aAttrs{ RES_PARATR_LIST_ID, RES_PARATR_LIST_LEVEL,
3921 RES_PARATR_LIST_ISRESTART,
3922 RES_PARATR_LIST_RESTARTVALUE,
3923 RES_PARATR_LIST_ISCOUNTED };
3924 SwPaM aPam( rTextNode );
3925 // #i96644#
3926 // suppress side effect "send data changed events"
3927 rTextNode.GetDoc().ResetAttrs( aPam, false, aAttrs, false );
3930 // Helper method for special handling of modified attributes at text node.
3931 // The following is handled:
3932 // (1) on changing the paragraph style - RES_FMT_CHG:
3933 // Check, if list style of the text node is changed. If yes, add respectively
3934 // remove the text node to the corresponding list.
3935 // (2) on changing the attributes - RES_ATTRSET_CHG:
3936 // Same as (1).
3937 // (3) on changing the list style - RES_PARATR_NUMRULE:
3938 // Same as (1).
3939 void HandleModifyAtTextNode( SwTextNode& rTextNode,
3940 const SfxPoolItem* pOldValue,
3941 const SfxPoolItem* pNewValue )
3943 const sal_uInt16 nWhich = pOldValue ? pOldValue->Which() :
3944 pNewValue ? pNewValue->Which() : 0 ;
3945 bool bNumRuleSet = false;
3946 bool bParagraphStyleChanged = false;
3947 OUString sNumRule;
3948 OUString sOldNumRule;
3949 switch ( nWhich )
3951 case RES_FMT_CHG:
3953 bParagraphStyleChanged = true;
3954 if( rTextNode.GetNodes().IsDocNodes() )
3956 const SwNumRule* pFormerNumRuleAtTextNode =
3957 rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr;
3958 if ( pFormerNumRuleAtTextNode )
3960 sOldNumRule = pFormerNumRuleAtTextNode->GetName();
3962 if ( rTextNode.IsEmptyListStyleDueToSetOutlineLevelAttr() )
3964 const SwNumRuleItem& rNumRuleItem = rTextNode.GetTextColl()->GetNumRule();
3965 if ( !rNumRuleItem.GetValue().isEmpty() )
3967 rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
3970 const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule();
3971 if ( pNumRuleAtTextNode )
3973 bNumRuleSet = true;
3974 sNumRule = pNumRuleAtTextNode->GetName();
3977 break;
3979 case RES_ATTRSET_CHG:
3981 const SwNumRule* pFormerNumRuleAtTextNode =
3982 rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr;
3983 if ( pFormerNumRuleAtTextNode )
3985 sOldNumRule = pFormerNumRuleAtTextNode->GetName();
3988 const SwAttrSetChg* pSet = dynamic_cast<const SwAttrSetChg*>(pNewValue);
3989 if ( pSet && pSet->GetChgSet()->GetItemState( RES_PARATR_NUMRULE, false ) ==
3990 SfxItemState::SET )
3992 // #i70748#
3993 rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
3994 bNumRuleSet = true;
3996 // #i70748#
3997 // The new list style set at the paragraph.
3998 const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule();
3999 if ( pNumRuleAtTextNode )
4001 sNumRule = pNumRuleAtTextNode->GetName();
4003 break;
4005 case RES_PARATR_NUMRULE:
4007 if ( rTextNode.GetNodes().IsDocNodes() )
4009 const SwNumRule* pFormerNumRuleAtTextNode =
4010 rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr;
4011 if ( pFormerNumRuleAtTextNode )
4013 sOldNumRule = pFormerNumRuleAtTextNode->GetName();
4016 if ( pNewValue )
4018 // #i70748#
4019 rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
4020 bNumRuleSet = true;
4022 // #i70748#
4023 // The new list style set at the paragraph.
4024 const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule();
4025 if ( pNumRuleAtTextNode )
4027 sNumRule = pNumRuleAtTextNode->GetName();
4030 break;
4033 if ( sNumRule != sOldNumRule )
4035 if ( bNumRuleSet )
4037 if (sNumRule.isEmpty())
4039 rTextNode.RemoveFromList();
4040 if ( bParagraphStyleChanged )
4042 lcl_ResetParAttrs(rTextNode);
4045 else
4047 rTextNode.RemoveFromList();
4048 // If new list style is the outline style, apply outline
4049 // level as the list level.
4050 if (sNumRule==SwNumRule::GetOutlineRuleName())
4052 // #i70748#
4053 OSL_ENSURE( rTextNode.GetTextColl()->IsAssignedToListLevelOfOutlineStyle(),
4054 "<HandleModifyAtTextNode()> - text node with outline style, but its paragraph style is not assigned to outline style." );
4055 const int nNewListLevel =
4056 rTextNode.GetTextColl()->GetAssignedOutlineStyleLevel();
4057 if ( 0 <= nNewListLevel && nNewListLevel < MAXLEVEL )
4059 rTextNode.SetAttrListLevel( nNewListLevel );
4062 rTextNode.AddToList();
4065 else // <sNumRule.Len() == 0 && sOldNumRule.Len() != 0>
4067 rTextNode.RemoveFromList();
4068 if ( bParagraphStyleChanged )
4070 lcl_ResetParAttrs(rTextNode);
4071 // #i70748#
4072 if ( rTextNode.GetAttr( RES_PARATR_OUTLINELEVEL, false ).GetValue() > 0 )
4074 rTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr();
4079 else if (!sNumRule.isEmpty() && !rTextNode.IsInList())
4081 rTextNode.AddToList();
4084 // End of method <HandleModifyAtTextNode>
4087 SwFormatColl* SwTextNode::ChgFormatColl( SwFormatColl *pNewColl )
4089 OSL_ENSURE( pNewColl,"ChgFormatColl: Collectionpointer has value 0." );
4090 assert( dynamic_cast<const SwTextFormatColl *>(pNewColl) && "ChgFormatColl: is not a Text Collection pointer." );
4092 SwTextFormatColl *pOldColl = GetTextColl();
4093 if( pNewColl != pOldColl )
4095 SetCalcHiddenCharFlags();
4096 SwContentNode::ChgFormatColl( pNewColl );
4097 OSL_ENSURE( !mbInSetOrResetAttr,
4098 "DEBUG OSL_ENSURE(ON - <SwTextNode::ChgFormatColl(..)> called during <Set/ResetAttr(..)>" );
4099 if ( !mbInSetOrResetAttr )
4101 SwFormatChg aTmp1( pOldColl );
4102 SwFormatChg aTmp2( pNewColl );
4103 HandleModifyAtTextNode( *this, &aTmp1, &aTmp2 );
4106 // reset fill information on parent style change
4107 if(maFillAttributes)
4109 maFillAttributes.reset();
4113 // only for real nodes-array
4114 if( GetNodes().IsDocNodes() )
4116 ChgTextCollUpdateNum( pOldColl, static_cast<SwTextFormatColl *>(pNewColl) );
4119 return pOldColl;
4122 const SwNodeNum* SwTextNode::GetNum(SwRootFrame const*const pLayout, SwListRedlineType eRedline) const
4124 // invariant: it's only in list in Hide mode if it's in list in normal mode
4125 assert(mpNodeNum || !mpNodeNumRLHidden);
4126 return (pLayout && pLayout->IsHideRedlines()) || SwListRedlineType::HIDDEN == eRedline
4127 ? mpNodeNumRLHidden.get()
4128 : ( SwListRedlineType::ORIGTEXT == eRedline ? mpNodeNumOrig.get() : mpNodeNum.get() );
4131 void SwTextNode::DoNum(std::function<void (SwNodeNum &)> const& rFunc)
4133 // temp. clear because GetActualListLevel() may be called and the assert
4134 // there triggered during update, which is unhelpful
4135 std::unique_ptr<SwNodeNum> pBackup = std::move(mpNodeNumRLHidden);
4136 std::unique_ptr<SwNodeNum> pBackup2 = std::move(mpNodeNumOrig);
4137 assert(mpNodeNum);
4138 rFunc(*mpNodeNum);
4139 if (pBackup)
4141 mpNodeNumRLHidden = std::move(pBackup);
4142 rFunc(*mpNodeNumRLHidden);
4144 if (pBackup2)
4146 mpNodeNumOrig = std::move(pBackup2);
4147 rFunc(*mpNodeNumOrig);
4151 SwNumberTree::tNumberVector
4152 SwTextNode::GetNumberVector(SwRootFrame const*const pLayout, SwListRedlineType eRedline) const
4154 if (SwNodeNum const*const pNum = GetNum(pLayout, eRedline))
4156 return pNum->GetNumberVector();
4158 else
4160 SwNumberTree::tNumberVector aResult;
4161 return aResult;
4165 bool SwTextNode::IsOutline() const
4167 bool bResult = false;
4169 if ( GetAttrOutlineLevel() > 0 )
4171 bResult = !IsInRedlines();
4173 else
4175 const SwNumRule* pRule( GetNum() ? GetNum()->GetNumRule() : nullptr );
4176 if ( pRule && pRule->IsOutlineRule() )
4178 bResult = !IsInRedlines();
4182 return bResult;
4185 bool SwTextNode::IsOutlineStateChanged() const
4187 return IsOutline() != m_bLastOutlineState;
4190 void SwTextNode::UpdateOutlineState()
4192 m_bLastOutlineState = IsOutline();
4195 int SwTextNode::GetAttrOutlineLevel() const
4197 return GetAttr(RES_PARATR_OUTLINELEVEL).GetValue();
4200 void SwTextNode::SetAttrOutlineLevel(int nLevel)
4202 assert(0 <= nLevel && nLevel <= MAXLEVEL); // Level Out Of Range
4203 if ( 0 <= nLevel && nLevel <= MAXLEVEL )
4205 SetAttr( SfxUInt16Item( RES_PARATR_OUTLINELEVEL,
4206 o3tl::narrowing<sal_uInt16>(nLevel) ) );
4210 void SwTextNode::GetAttrOutlineContentVisible(bool& bOutlineContentVisibleAttr)
4212 const SfxGrabBagItem & rGrabBagItem = GetAttr(RES_PARATR_GRABBAG);
4213 auto it = rGrabBagItem.GetGrabBag().find("OutlineContentVisibleAttr");
4214 if (it != rGrabBagItem.GetGrabBag().end())
4215 it->second >>= bOutlineContentVisibleAttr;
4218 void SwTextNode::SetAttrOutlineContentVisible(bool bVisible)
4220 SfxGrabBagItem aGrabBagItem(RES_PARATR_GRABBAG);
4221 aGrabBagItem.GetGrabBag()["OutlineContentVisibleAttr"] <<= bVisible;
4222 SetAttr(aGrabBagItem);
4225 // #i70748#
4227 void SwTextNode::SetEmptyListStyleDueToSetOutlineLevelAttr()
4229 if ( !mbEmptyListStyleSetDueToSetOutlineLevelAttr )
4231 SetAttr( SwNumRuleItem() );
4232 mbEmptyListStyleSetDueToSetOutlineLevelAttr = true;
4236 void SwTextNode::ResetEmptyListStyleDueToResetOutlineLevelAttr()
4238 if ( mbEmptyListStyleSetDueToSetOutlineLevelAttr )
4240 ResetAttr( RES_PARATR_NUMRULE );
4241 mbEmptyListStyleSetDueToSetOutlineLevelAttr = false;
4245 void SwTextNode::SetAttrListLevel( int nLevel )
4247 if ( nLevel < 0 || nLevel >= MAXLEVEL )
4249 assert(false); // invalid level
4250 return;
4253 SfxInt16Item aNewListLevelItem( RES_PARATR_LIST_LEVEL,
4254 static_cast<sal_Int16>(nLevel) );
4255 SetAttr( aNewListLevelItem );
4258 bool SwTextNode::HasAttrListLevel() const
4260 return GetpSwAttrSet() &&
4261 GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_LEVEL, false ) == SfxItemState::SET;
4264 int SwTextNode::GetAttrListLevel() const
4266 int nAttrListLevel = 0;
4268 const SfxInt16Item& aListLevelItem =
4269 GetAttr( RES_PARATR_LIST_LEVEL );
4270 nAttrListLevel = static_cast<int>(aListLevelItem.GetValue());
4272 return nAttrListLevel;
4275 int SwTextNode::GetActualListLevel(SwListRedlineType eRedline) const
4277 assert(SwListRedlineType::SHOW != eRedline ||
4278 !GetNum(nullptr, SwListRedlineType::SHOW) || !mpNodeNumRLHidden || // must be in sync
4279 GetNum(nullptr, SwListRedlineType::SHOW)->GetLevelInListTree() ==
4280 mpNodeNumRLHidden->GetLevelInListTree());
4281 return GetNum(nullptr, eRedline) ? GetNum(nullptr, eRedline)->GetLevelInListTree() : -1;
4284 void SwTextNode::SetListRestart( bool bRestart )
4286 if ( !bRestart )
4288 // attribute not contained in paragraph style's attribute set. Thus,
4289 // it can be reset to the attribute pool default by resetting the attribute.
4290 ResetAttr( RES_PARATR_LIST_ISRESTART );
4292 else
4294 SfxBoolItem aNewIsRestartItem( RES_PARATR_LIST_ISRESTART,
4295 true );
4296 SetAttr( aNewIsRestartItem );
4300 bool SwTextNode::IsListRestart() const
4302 const SfxBoolItem& aIsRestartItem = GetAttr( RES_PARATR_LIST_ISRESTART );
4304 return aIsRestartItem.GetValue();
4307 /** Returns if the paragraph has a visible numbering or bullet.
4308 This includes all kinds of numbering/bullet/outlines.
4309 The concrete list label string has to be checked, too.
4311 bool SwTextNode::HasVisibleNumberingOrBullet() const
4313 const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
4314 if ( pRule && IsCountedInList())
4316 const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
4317 if (getIDocumentSettingAccess()->get(DocumentSettingId::NO_NUMBERING_SHOW_FOLLOWBY))
4318 // True if we have something in label text or there is a non-empty
4319 // FollowedBy separator (space, tab or whatsoever)
4320 return rFormat.GetLabelFollowedBy() != SvxNumberFormat::LabelFollowedBy::NOTHING ||
4321 !pRule->MakeNumString(*GetNum()).isEmpty();
4322 else
4323 // #i87154#
4324 // Correction of #newlistlevelattrs#:
4325 // The numbering type has to be checked for bullet lists.
4326 return SVX_NUM_NUMBER_NONE != rFormat.GetNumberingType() ||
4327 !pRule->MakeNumString(*(GetNum())).isEmpty();
4330 return false;
4333 void SwTextNode::SetAttrListRestartValue( SwNumberTree::tSwNumTreeNumber nNumber )
4335 const bool bChanged( HasAttrListRestartValue()
4336 ? GetAttrListRestartValue() != nNumber
4337 : nNumber != USHRT_MAX );
4339 if ( !bChanged && HasAttrListRestartValue() )
4340 return;
4342 if ( nNumber == USHRT_MAX )
4344 ResetAttr( RES_PARATR_LIST_RESTARTVALUE );
4346 else
4348 SfxInt16Item aNewListRestartValueItem( RES_PARATR_LIST_RESTARTVALUE,
4349 static_cast<sal_Int16>(nNumber) );
4350 SetAttr( aNewListRestartValueItem );
4354 bool SwTextNode::HasAttrListRestartValue() const
4356 return GetpSwAttrSet() &&
4357 GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_RESTARTVALUE, false ) == SfxItemState::SET;
4359 SwNumberTree::tSwNumTreeNumber SwTextNode::GetAttrListRestartValue() const
4361 OSL_ENSURE( HasAttrListRestartValue(),
4362 "<SwTextNode::GetAttrListRestartValue()> - only ask for list restart value, if attribute is set at text node." );
4364 const SfxInt16Item& aListRestartValueItem =
4365 GetAttr( RES_PARATR_LIST_RESTARTVALUE );
4366 return static_cast<SwNumberTree::tSwNumTreeNumber>(aListRestartValueItem.GetValue());
4369 SwNumberTree::tSwNumTreeNumber SwTextNode::GetActualListStartValue() const
4371 SwNumberTree::tSwNumTreeNumber nListRestartValue = 1;
4373 if ( IsListRestart() && HasAttrListRestartValue() )
4375 nListRestartValue = GetAttrListRestartValue();
4377 else
4379 SwNumRule* pRule = GetNumRule();
4380 if ( pRule )
4382 const SwNumFormat* pFormat =
4383 pRule->GetNumFormat( o3tl::narrowing<sal_uInt16>(GetAttrListLevel()) );
4384 if ( pFormat )
4386 nListRestartValue = pFormat->GetStart();
4391 return nListRestartValue;
4394 bool SwTextNode::IsNotifiable() const
4396 return m_bNotifiable && IsNotificationEnabled();
4399 bool SwTextNode::IsNotificationEnabled() const
4401 const SwDoc& rDoc = GetDoc();
4402 return !rDoc.IsInReading() && !rDoc.IsInDtor();
4405 void SwTextNode::SetCountedInList( bool bCounted )
4407 if ( bCounted )
4409 // attribute not contained in paragraph style's attribute set. Thus,
4410 // it can be reset to the attribute pool default by resetting the attribute.
4411 ResetAttr( RES_PARATR_LIST_ISCOUNTED );
4413 else
4415 SfxBoolItem aIsCountedInListItem( RES_PARATR_LIST_ISCOUNTED, false );
4416 SetAttr( aIsCountedInListItem );
4420 bool SwTextNode::IsCountedInList() const
4422 const SfxBoolItem& aIsCountedInListItem = GetAttr( RES_PARATR_LIST_ISCOUNTED );
4424 return aIsCountedInListItem.GetValue();
4427 static SwList * FindList(SwTextNode *const pNode)
4429 const OUString sListId = pNode->GetListId();
4430 if (!sListId.isEmpty())
4432 auto & rIDLA(pNode->GetDoc().getIDocumentListsAccess());
4433 SwList* pList = rIDLA.getListByName( sListId );
4434 if ( pList == nullptr )
4436 // Create corresponding list.
4437 SwNumRule* pNumRule = pNode->GetNumRule();
4438 if ( pNumRule )
4440 pList = rIDLA.createList(sListId, pNode->GetNumRule()->GetName());
4443 OSL_ENSURE( pList != nullptr,
4444 "<SwTextNode::AddToList()> - no list for given list id. Serious defect" );
4445 return pList;
4447 return nullptr;
4450 void SwTextNode::AddToList()
4452 if ( IsInList() )
4454 OSL_FAIL( "<SwTextNode::AddToList()> - the text node is already added to a list. Serious defect" );
4455 return;
4458 SwList *const pList(FindList(this));
4459 if (!(pList && GetNodes().IsDocNodes())) // not for undo nodes
4460 return;
4462 assert(!mpNodeNum);
4463 mpNodeNum.reset(new SwNodeNum(this, false));
4464 pList->InsertListItem(*mpNodeNum, SwListRedlineType::SHOW, GetAttrListLevel(), GetDoc());
4466 // set redline lists
4467 // "default" list: visible items in Show Changes mode (tracked insertions and deletions)
4468 // "hidden" list: visible items in Hide Changes mode (tracked insertions, but not deletions)
4469 // "orig" list: visible items rejecting all changes (no tracked insertions and deletions)
4470 bool bRecordChanges = GetDoc().GetDocShell() && GetDoc().GetDocShell()->IsChangeRecording();
4471 if (!bRecordChanges || GetDoc().IsInXMLImport() || GetDoc().IsInWriterfilterImport() )
4473 const SwRedlineTable& rRedTable = GetDoc().getIDocumentRedlineAccess().GetRedlineTable();
4474 SwRedlineTable::size_type nRedlPos = GetDoc().getIDocumentRedlineAccess().GetRedlinePos(*this, RedlineType::Insert);
4475 // paragraph start is not in a tracked insertion
4476 if ( SwRedlineTable::npos == nRedlPos || GetIndex() <= rRedTable[nRedlPos]->Start()->GetNode().GetIndex() )
4478 AddToListOrig();
4480 // if the paragraph is not deleted, add to the "hidden" list, too
4481 SwRedlineTable::size_type nRedlPosDel = GetDoc().getIDocumentRedlineAccess().GetRedlinePos(*this, RedlineType::Delete);
4482 if ( SwRedlineTable::npos == nRedlPosDel )
4483 AddToListRLHidden();
4485 // inserted paragraph, e.g. during file load, add to the "hidden" list
4486 else if ( SwRedlineTable::npos != nRedlPos )
4487 AddToListRLHidden();
4489 else if ( bRecordChanges )
4490 AddToListRLHidden();
4492 // iterate all frames & if there's one with hidden layout...
4493 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> iter(*this);
4494 for (SwTextFrame* pFrame = iter.First(); pFrame && !mpNodeNumRLHidden; pFrame = iter.Next())
4496 if (pFrame->getRootFrame()->IsHideRedlines())
4498 if (pFrame->GetTextNodeForParaProps() == this)
4500 AddToListRLHidden();
4502 break; // assume it's consistent, need to check only once
4507 void SwTextNode::AddToListRLHidden()
4509 if (mpNodeNumRLHidden)
4510 return;
4512 SwList *const pList(FindList(this));
4513 if (pList)
4515 assert(!mpNodeNumRLHidden);
4516 mpNodeNumRLHidden.reset(new SwNodeNum(this, true));
4517 pList->InsertListItem(*mpNodeNumRLHidden, SwListRedlineType::HIDDEN, GetAttrListLevel(), GetDoc());
4521 void SwTextNode::AddToListOrig()
4523 if (mpNodeNumOrig)
4524 return;
4526 SwList *const pList(FindList(this));
4527 if (pList)
4529 assert(!mpNodeNumOrig);
4530 mpNodeNumOrig.reset(new SwNodeNum(this, true));
4531 pList->InsertListItem(*mpNodeNumOrig, SwListRedlineType::ORIGTEXT, GetAttrListLevel(), GetDoc());
4535 void SwTextNode::RemoveFromList()
4537 // sw_redlinehide: ensure it's removed from the other half too!
4538 RemoveFromListRLHidden();
4539 RemoveFromListOrig();
4540 if ( IsInList() )
4542 SwList::RemoveListItem(*mpNodeNum, GetDoc());
4543 mpNodeNum.reset();
4545 SetWordCountDirty( true );
4549 void SwTextNode::RemoveFromListRLHidden()
4551 if (mpNodeNumRLHidden) // direct access because RemoveFromList doesn't have layout
4553 assert(mpNodeNumRLHidden->GetParent() || !GetNodes().IsDocNodes());
4554 SwList::RemoveListItem(*mpNodeNumRLHidden, GetDoc());
4555 mpNodeNumRLHidden.reset();
4557 SetWordCountDirty( true );
4561 void SwTextNode::RemoveFromListOrig()
4563 if (mpNodeNumOrig) // direct access because RemoveFromList doesn't have layout
4565 assert(mpNodeNumOrig->GetParent() || !GetNodes().IsDocNodes());
4566 SwList::RemoveListItem(*mpNodeNumOrig, GetDoc());
4567 mpNodeNumOrig.reset();
4569 SetWordCountDirty( true );
4573 bool SwTextNode::IsInList() const
4575 return GetNum() != nullptr && GetNum()->GetParent() != nullptr;
4578 bool SwTextNode::IsFirstOfNumRule(SwRootFrame const& rLayout) const
4580 bool bResult = false;
4582 SwNodeNum const*const pNum(GetNum(&rLayout));
4583 if (pNum && pNum->GetNumRule())
4584 bResult = pNum->IsFirst();
4586 return bResult;
4589 void SwTextNode::SetListId(OUString const& rListId)
4591 const SfxStringItem& rListIdItem =
4592 GetAttr( RES_PARATR_LIST_ID );
4593 if (rListIdItem.GetValue() != rListId)
4595 if (rListId.isEmpty())
4597 ResetAttr( RES_PARATR_LIST_ID );
4599 else
4601 SfxStringItem aNewListIdItem(RES_PARATR_LIST_ID, rListId);
4602 SetAttr( aNewListIdItem );
4607 OUString SwTextNode::GetListId() const
4609 const SfxStringItem& rListIdItem =
4610 GetAttr( RES_PARATR_LIST_ID );
4611 const OUString& sListId {rListIdItem.GetValue()};
4613 // As long as no explicit list id attribute is set, use the list id of
4614 // the list, which has been created for the applied list style.
4615 if (sListId.isEmpty())
4617 SwNumRule* pRule = GetNumRule();
4618 if ( pRule )
4620 return pRule->GetDefaultListId();
4624 return sListId;
4627 /** Determines, if the list level indent attributes can be applied to the
4628 paragraph.
4630 The list level indents can be applied to the paragraph under the one
4631 of following conditions:
4632 - the list style is directly applied to the paragraph and the paragraph
4633 has no own indent attributes.
4634 - the list style is applied to the paragraph through one of its paragraph
4635 styles, the paragraph has no own indent attributes and on the paragraph
4636 style hierarchy from the paragraph to the paragraph style with the
4637 list style no indent attributes are found.
4639 @return bitmask
4641 ::sw::ListLevelIndents SwTextNode::AreListLevelIndentsApplicable() const
4643 ::sw::ListLevelIndents ret(::sw::ListLevelIndents::No);
4644 if (AreListLevelIndentsApplicableImpl(RES_MARGIN_FIRSTLINE))
4646 ret |= ::sw::ListLevelIndents::FirstLine;
4648 if (AreListLevelIndentsApplicableImpl(RES_MARGIN_TEXTLEFT))
4650 ret |= ::sw::ListLevelIndents::LeftMargin;
4652 return ret;
4655 bool SwTextNode::AreListLevelIndentsApplicableImpl(sal_uInt16 const nWhich) const
4657 bool bAreListLevelIndentsApplicable( true );
4659 if ( !GetNum() || !GetNum()->GetNumRule() )
4661 // no list style applied to paragraph
4662 bAreListLevelIndentsApplicable = false;
4664 else if ( HasSwAttrSet() &&
4665 GetpSwAttrSet()->GetItemState(nWhich, false) == SfxItemState::SET)
4667 // paragraph has hard-set indent attributes
4668 bAreListLevelIndentsApplicable = false;
4670 else if ( HasSwAttrSet() &&
4671 GetpSwAttrSet()->GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET )
4673 // list style is directly applied to paragraph and paragraph has no
4674 // hard-set indent attributes
4675 bAreListLevelIndentsApplicable = true;
4677 else
4679 // list style is applied through one of the paragraph styles and
4680 // paragraph has no hard-set indent attributes
4682 // check, paragraph's
4683 const SwTextFormatColl* pColl = GetTextColl();
4684 while ( pColl )
4686 if (pColl->GetAttrSet().GetItemState(nWhich, false) == SfxItemState::SET)
4688 // indent attributes found in the paragraph style hierarchy.
4689 bAreListLevelIndentsApplicable = false;
4690 break;
4693 if ( pColl->GetAttrSet().GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET )
4695 // paragraph style with the list style found and until now no
4696 // indent attributes are found in the paragraph style hierarchy.
4697 bAreListLevelIndentsApplicable = true;
4698 break;
4701 pColl = dynamic_cast<const SwTextFormatColl*>(pColl->DerivedFrom());
4702 OSL_ENSURE( pColl,
4703 "<SwTextNode::AreListLevelIndentsApplicable()> - something wrong in paragraph's style hierarchy. The applied list style is not found." );
4707 return bAreListLevelIndentsApplicable;
4710 /** Retrieves the list tab stop position, if the paragraph's list level defines
4711 one and this list tab stop has to merged into the tap stops of the paragraph
4713 @param nListTabStopPosition
4714 output parameter - containing the list tab stop position
4716 @return boolean - indicating, if a list tab stop position is provided
4718 bool SwTextNode::GetListTabStopPosition( tools::Long& nListTabStopPosition ) const
4720 bool bListTabStopPositionProvided(false);
4722 const SwNumRule* pNumRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
4723 if ( pNumRule && HasVisibleNumberingOrBullet() && GetActualListLevel() >= 0 )
4725 const SwNumFormat& rFormat = pNumRule->Get( o3tl::narrowing<sal_uInt16>(GetActualListLevel()) );
4726 if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT &&
4727 rFormat.GetLabelFollowedBy() == SvxNumberFormat::LISTTAB )
4729 bListTabStopPositionProvided = true;
4730 nListTabStopPosition = rFormat.GetListtabPos();
4732 if ( getIDocumentSettingAccess()->get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) )
4734 // tab stop position are treated to be relative to the "before text"
4735 // indent value of the paragraph. Thus, adjust <nListTabStopPos>.
4736 if (AreListLevelIndentsApplicable() & ::sw::ListLevelIndents::LeftMargin)
4738 nListTabStopPosition -= rFormat.GetIndentAt();
4740 else if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
4742 SvxTextLeftMarginItem const aItem(GetSwAttrSet().GetTextLeftMargin());
4743 nListTabStopPosition -= aItem.GetTextLeft();
4749 return bListTabStopPositionProvided;
4752 OUString SwTextNode::GetLabelFollowedBy() const
4754 const SwNumRule* pNumRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
4755 if ( pNumRule && HasVisibleNumberingOrBullet() && GetActualListLevel() >= 0 )
4757 const SwNumFormat& rFormat = pNumRule->Get( o3tl::narrowing<sal_uInt16>(GetActualListLevel()) );
4758 if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
4760 return rFormat.GetLabelFollowedByAsString();
4764 return OUString();
4767 void SwTextNode::CalcHiddenCharFlags() const
4769 sal_Int32 nStartPos;
4770 sal_Int32 nEndPos;
4771 // Update of the flags is done inside GetBoundsOfHiddenRange()
4772 SwScriptInfo::GetBoundsOfHiddenRange( *this, 0, nStartPos, nEndPos );
4775 // #i12836# enhanced pdf export
4776 bool SwTextNode::IsHidden() const
4778 if ( IsHiddenByParaField() || HasHiddenCharAttribute( true ) )
4779 return true;
4781 const SwSectionNode* pSectNd = FindSectionNode();
4782 return pSectNd && pSectNd->GetSection().IsHiddenFlag();
4785 namespace {
4786 // Helper class for special handling of setting attributes at text node:
4787 // In constructor an instance of the helper class recognize whose attributes
4788 // are set and perform corresponding actions before the intrinsic set of
4789 // attributes has been taken place.
4790 // In the destructor - after the attributes have been set at the text
4791 // node - corresponding actions are performed.
4792 // The following is handled:
4793 // (1) When the list style attribute - RES_PARATR_NUMRULE - is set,
4794 // (A) list style attribute is empty -> the text node is removed from
4795 // its list.
4796 // (B) list style attribute is not empty
4797 // (a) text node has no list style -> add text node to its list after
4798 // the attributes have been set.
4799 // (b) text node has list style -> change of list style is notified
4800 // after the attributes have been set.
4801 // (2) When the list id attribute - RES_PARATR_LIST_ID - is set and changed,
4802 // the text node is removed from its current list before the attributes
4803 // are set and added to its new list after the attributes have been set.
4804 // (3) Notify list tree, if list level - RES_PARATR_LIST_LEVEL - is set
4805 // and changed after the attributes have been set
4806 // (4) Notify list tree, if list restart - RES_PARATR_LIST_ISRESTART - is set
4807 // and changed after the attributes have been set
4808 // (5) Notify list tree, if list restart value - RES_PARATR_LIST_RESTARTVALUE -
4809 // is set and changed after the attributes have been set
4810 // (6) Notify list tree, if count in list - RES_PARATR_LIST_ISCOUNTED - is set
4811 // and changed after the attributes have been set
4812 // (7) Set or Reset empty list style due to changed outline level - RES_PARATR_OUTLINELEVEL.
4813 class HandleSetAttrAtTextNode
4815 public:
4816 HandleSetAttrAtTextNode( SwTextNode& rTextNode,
4817 const SfxPoolItem& pItem );
4818 HandleSetAttrAtTextNode( SwTextNode& rTextNode,
4819 const SfxItemSet& rItemSet );
4820 ~HandleSetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE;
4822 private:
4823 SwTextNode& mrTextNode;
4824 bool mbAddTextNodeToList;
4825 bool mbUpdateListLevel;
4826 bool mbUpdateListRestart;
4827 bool mbUpdateListCount;
4828 // #i70748#
4829 bool mbOutlineLevelSet;
4832 HandleSetAttrAtTextNode::HandleSetAttrAtTextNode( SwTextNode& rTextNode,
4833 const SfxPoolItem& pItem )
4834 : mrTextNode( rTextNode ),
4835 mbAddTextNodeToList( false ),
4836 mbUpdateListLevel( false ),
4837 mbUpdateListRestart( false ),
4838 mbUpdateListCount( false ),
4839 // #i70748#
4840 mbOutlineLevelSet( false )
4842 switch ( pItem.Which() )
4844 // handle RES_PARATR_NUMRULE
4845 case RES_PARATR_NUMRULE:
4847 mrTextNode.RemoveFromList();
4849 const SwNumRuleItem& rNumRuleItem =
4850 dynamic_cast<const SwNumRuleItem&>(pItem);
4851 if ( !rNumRuleItem.GetValue().isEmpty() )
4853 mbAddTextNodeToList = true;
4854 // #i105562#
4856 mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
4859 break;
4861 // handle RES_PARATR_LIST_ID
4862 case RES_PARATR_LIST_ID:
4864 const SfxStringItem& rListIdItem =
4865 dynamic_cast<const SfxStringItem&>(pItem);
4866 OSL_ENSURE( rListIdItem.GetValue().getLength() > 0,
4867 "<HandleSetAttrAtTextNode(..)> - empty list id attribute not expected. Serious defect." );
4868 const OUString sListIdOfTextNode = rTextNode.GetListId();
4869 if ( rListIdItem.GetValue() != sListIdOfTextNode )
4871 mbAddTextNodeToList = true;
4872 if ( mrTextNode.IsInList() )
4874 mrTextNode.RemoveFromList();
4878 break;
4880 // handle RES_PARATR_LIST_LEVEL
4881 case RES_PARATR_LIST_LEVEL:
4883 const SfxInt16Item& aListLevelItem =
4884 dynamic_cast<const SfxInt16Item&>(pItem);
4885 if ( aListLevelItem.GetValue() != mrTextNode.GetAttrListLevel() )
4887 mbUpdateListLevel = true;
4890 break;
4892 // handle RES_PARATR_LIST_ISRESTART
4893 case RES_PARATR_LIST_ISRESTART:
4895 const SfxBoolItem& aListIsRestartItem =
4896 dynamic_cast<const SfxBoolItem&>(pItem);
4897 if ( aListIsRestartItem.GetValue() !=
4898 mrTextNode.IsListRestart() )
4900 mbUpdateListRestart = true;
4903 break;
4905 // handle RES_PARATR_LIST_RESTARTVALUE
4906 case RES_PARATR_LIST_RESTARTVALUE:
4908 const SfxInt16Item& aListRestartValueItem =
4909 dynamic_cast<const SfxInt16Item&>(pItem);
4910 if ( !mrTextNode.HasAttrListRestartValue() ||
4911 aListRestartValueItem.GetValue() != mrTextNode.GetAttrListRestartValue() )
4913 mbUpdateListRestart = true;
4916 break;
4918 // handle RES_PARATR_LIST_ISCOUNTED
4919 case RES_PARATR_LIST_ISCOUNTED:
4921 const SfxBoolItem& aIsCountedInListItem =
4922 dynamic_cast<const SfxBoolItem&>(pItem);
4923 if ( aIsCountedInListItem.GetValue() !=
4924 mrTextNode.IsCountedInList() )
4926 mbUpdateListCount = true;
4929 break;
4931 // #i70748#
4932 // handle RES_PARATR_OUTLINELEVEL
4933 case RES_PARATR_OUTLINELEVEL:
4935 const SfxUInt16Item& aOutlineLevelItem =
4936 dynamic_cast<const SfxUInt16Item&>(pItem);
4937 if ( aOutlineLevelItem.GetValue() != mrTextNode.GetAttrOutlineLevel() )
4939 mbOutlineLevelSet = true;
4942 break;
4947 HandleSetAttrAtTextNode::HandleSetAttrAtTextNode( SwTextNode& rTextNode,
4948 const SfxItemSet& rItemSet )
4949 : mrTextNode( rTextNode ),
4950 mbAddTextNodeToList( false ),
4951 mbUpdateListLevel( false ),
4952 mbUpdateListRestart( false ),
4953 mbUpdateListCount( false ),
4954 // #i70748#
4955 mbOutlineLevelSet( false )
4957 // handle RES_PARATR_NUMRULE
4958 if ( const SwNumRuleItem* pNumRuleItem = rItemSet.GetItemIfSet( RES_PARATR_NUMRULE, false ) )
4960 mrTextNode.RemoveFromList();
4962 if ( !pNumRuleItem->GetValue().isEmpty() )
4964 mbAddTextNodeToList = true;
4965 // #i70748#
4966 mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
4970 // handle RES_PARATR_LIST_ID
4971 if ( const SfxStringItem* pListIdItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_ID, false ) )
4973 const OUString sListIdOfTextNode = mrTextNode.GetListId();
4974 if ( pListIdItem->GetValue() != sListIdOfTextNode )
4976 mbAddTextNodeToList = true;
4977 if ( mrTextNode.IsInList() )
4979 mrTextNode.RemoveFromList();
4984 // handle RES_PARATR_LIST_LEVEL
4985 if ( const SfxInt16Item* pListLevelItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_LEVEL, false ) )
4987 if (pListLevelItem->GetValue() != mrTextNode.GetAttrListLevel())
4989 mbUpdateListLevel = true;
4993 // handle RES_PARATR_LIST_ISRESTART
4994 if ( const SfxBoolItem* pListIsRestartItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_ISRESTART, false ) )
4996 if (pListIsRestartItem->GetValue() != mrTextNode.IsListRestart())
4998 mbUpdateListRestart = true;
5002 // handle RES_PARATR_LIST_RESTARTVALUE
5003 if ( const SfxInt16Item* pListRestartValueItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_RESTARTVALUE, false ) )
5005 if ( !mrTextNode.HasAttrListRestartValue() ||
5006 pListRestartValueItem->GetValue() != mrTextNode.GetAttrListRestartValue() )
5008 mbUpdateListRestart = true;
5012 // handle RES_PARATR_LIST_ISCOUNTED
5013 if ( const SfxBoolItem* pIsCountedInListItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_ISCOUNTED, false ) )
5015 if (pIsCountedInListItem->GetValue() != mrTextNode.IsCountedInList())
5017 mbUpdateListCount = true;
5021 // #i70748#
5022 // handle RES_PARATR_OUTLINELEVEL
5023 if ( const SfxUInt16Item* pOutlineLevelItem = rItemSet.GetItemIfSet( RES_PARATR_OUTLINELEVEL, false ) )
5025 if (pOutlineLevelItem->GetValue() != mrTextNode.GetAttrOutlineLevel())
5027 mbOutlineLevelSet = true;
5032 HandleSetAttrAtTextNode::~HandleSetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE
5034 if ( mbAddTextNodeToList )
5036 SwNumRule* pNumRuleAtTextNode = mrTextNode.GetNumRule();
5037 if ( pNumRuleAtTextNode )
5039 mrTextNode.AddToList();
5042 else
5044 if ( mbUpdateListLevel && mrTextNode.IsInList() )
5046 auto const nLevel(mrTextNode.GetAttrListLevel());
5047 const SwDoc& rDoc(mrTextNode.GetDoc());
5048 mrTextNode.DoNum(
5049 [nLevel, &rDoc](SwNodeNum & rNum) { rNum.SetLevelInListTree(nLevel, rDoc); });
5052 if ( mbUpdateListRestart && mrTextNode.IsInList() )
5054 const SwDoc& rDoc(mrTextNode.GetDoc());
5055 mrTextNode.DoNum(
5056 [&rDoc](SwNodeNum & rNum) {
5057 rNum.InvalidateMe();
5058 rNum.NotifyInvalidSiblings(rDoc);
5062 if (mbUpdateListCount && mrTextNode.IsInList() && HasNumberingWhichNeedsLayoutUpdate(mrTextNode))
5064 // Repaint all text frames that belong to this numbering to avoid outdated generated
5065 // numbers.
5066 const SwDoc& rDoc(mrTextNode.GetDoc());
5067 mrTextNode.DoNum(
5068 [&rDoc](SwNodeNum & rNum) { rNum.InvalidateAndNotifyTree(rDoc); });
5072 // #i70748#
5073 if (!mbOutlineLevelSet)
5074 return;
5076 mrTextNode.GetNodes().UpdateOutlineNode(mrTextNode);
5077 if (mrTextNode.GetAttrOutlineLevel() == 0)
5079 mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
5081 else
5083 if ( mrTextNode.GetSwAttrSet().GetItemState( RES_PARATR_NUMRULE )
5084 != SfxItemState::SET )
5086 mrTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr();
5090 // End of class <HandleSetAttrAtTextNode>
5093 bool SwTextNode::SetAttr( const SfxPoolItem& pItem )
5095 const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
5096 mbInSetOrResetAttr = true;
5098 HandleSetAttrAtTextNode aHandleSetAttr( *this, pItem );
5100 bool bRet = SwContentNode::SetAttr( pItem );
5102 mbInSetOrResetAttr = bOldIsSetOrResetAttr;
5104 return bRet;
5107 bool SwTextNode::SetAttr( const SfxItemSet& rSet )
5109 const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
5110 mbInSetOrResetAttr = true;
5112 HandleSetAttrAtTextNode aHandleSetAttr( *this, rSet );
5114 bool bRet = SwContentNode::SetAttr( rSet );
5116 mbInSetOrResetAttr = bOldIsSetOrResetAttr;
5118 return bRet;
5121 void SwTextNode::SetInSwUndo(bool bInUndo)
5123 m_bInUndo = bInUndo;
5126 namespace {
5127 // Helper class for special handling of resetting attributes at text node:
5128 // In constructor an instance of the helper class recognize whose attributes
5129 // are reset and perform corresponding actions before the intrinsic reset of
5130 // attributes has been taken place.
5131 // In the destructor - after the attributes have been reset at the text
5132 // node - corresponding actions are performed.
5133 // The following is handled:
5134 // (1) When the list style attribute - RES_PARATR_NUMRULE - is reset,
5135 // the text is removed from its list before the attributes have been reset.
5136 // (2) When the list id attribute - RES_PARATR_LIST_ID - is reset,
5137 // the text is removed from its list before the attributes have been reset.
5138 // (3) Notify list tree, if list level - RES_PARATR_LIST_LEVEL - is reset.
5139 // (4) Notify list tree, if list restart - RES_PARATR_LIST_ISRESTART - is reset.
5140 // (5) Notify list tree, if list restart value - RES_PARATR_LIST_RESTARTVALUE - is reset.
5141 // (6) Notify list tree, if count in list - RES_PARATR_LIST_ISCOUNTED - is reset.
5142 // (7) Reset empty list style, if outline level attribute - RES_PARATR_OUTLINELEVEL - is reset.
5143 class HandleResetAttrAtTextNode
5145 public:
5146 HandleResetAttrAtTextNode( SwTextNode& rTextNode,
5147 const sal_uInt16 nWhich1,
5148 sal_uInt16 nWhich2 );
5149 HandleResetAttrAtTextNode( SwTextNode& rTextNode,
5150 const std::vector<sal_uInt16>& rWhichArr );
5151 explicit HandleResetAttrAtTextNode( SwTextNode& rTextNode );
5153 ~HandleResetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE;
5155 private:
5156 SwTextNode& mrTextNode;
5157 bool mbListStyleOrIdReset;
5158 bool mbUpdateListLevel;
5159 bool mbUpdateListRestart;
5160 bool mbUpdateListCount;
5162 void init( sal_uInt16 nWhich, bool& rbRemoveFromList );
5165 HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode,
5166 const sal_uInt16 nWhich1,
5167 sal_uInt16 nWhich2 )
5168 : mrTextNode( rTextNode ),
5169 mbListStyleOrIdReset( false ),
5170 mbUpdateListLevel( false ),
5171 mbUpdateListRestart( false ),
5172 mbUpdateListCount( false )
5174 if ( nWhich2 < nWhich1 )
5175 nWhich2 = nWhich1;
5176 bool bRemoveFromList( false );
5177 for ( sal_uInt16 nWhich = nWhich1; nWhich <= nWhich2; ++nWhich )
5178 init( nWhich, bRemoveFromList );
5179 if ( bRemoveFromList && mrTextNode.IsInList() )
5180 mrTextNode.RemoveFromList();
5183 HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode,
5184 const std::vector<sal_uInt16>& rWhichArr )
5185 : mrTextNode( rTextNode ),
5186 mbListStyleOrIdReset( false ),
5187 mbUpdateListLevel( false ),
5188 mbUpdateListRestart( false ),
5189 mbUpdateListCount( false )
5191 bool bRemoveFromList( false );
5192 for ( sal_uInt16 nWhich : rWhichArr )
5193 init( nWhich, bRemoveFromList );
5194 if ( bRemoveFromList && mrTextNode.IsInList() )
5195 mrTextNode.RemoveFromList();
5198 HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode )
5199 : mrTextNode( rTextNode ),
5200 mbListStyleOrIdReset( true ),
5201 mbUpdateListLevel( false ),
5202 mbUpdateListRestart( false ),
5203 mbUpdateListCount( false )
5205 if ( rTextNode.IsInList() )
5207 rTextNode.RemoveFromList();
5209 // #i70748#
5210 mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
5213 void HandleResetAttrAtTextNode::init( sal_uInt16 rWhich, bool& rbRemoveFromList )
5215 if ( rWhich == RES_PARATR_NUMRULE )
5217 rbRemoveFromList = rbRemoveFromList ||
5218 mrTextNode.GetNumRule() != nullptr;
5219 mbListStyleOrIdReset = true;
5221 else if ( rWhich == RES_PARATR_LIST_ID )
5223 rbRemoveFromList = rbRemoveFromList ||
5224 ( mrTextNode.GetpSwAttrSet() &&
5225 mrTextNode.GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_ID, false ) == SfxItemState::SET );
5226 mbListStyleOrIdReset = true;
5228 else if ( rWhich == RES_PARATR_OUTLINELEVEL )
5229 mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
5230 else if ( rWhich == RES_BACKGROUND )
5231 mrTextNode.ResetAttr( XATTR_FILL_FIRST, XATTR_FILL_LAST );
5233 if ( !rbRemoveFromList )
5235 // RES_PARATR_LIST_LEVEL
5236 mbUpdateListLevel = mbUpdateListLevel ||
5237 ( rWhich == RES_PARATR_LIST_LEVEL &&
5238 mrTextNode.HasAttrListLevel() );
5240 // RES_PARATR_LIST_ISRESTART and RES_PARATR_LIST_RESTARTVALUE
5241 mbUpdateListRestart = mbUpdateListRestart ||
5242 ( rWhich == RES_PARATR_LIST_ISRESTART &&
5243 mrTextNode.IsListRestart() ) ||
5244 ( rWhich == RES_PARATR_LIST_RESTARTVALUE &&
5245 mrTextNode.HasAttrListRestartValue() );
5247 // RES_PARATR_LIST_ISCOUNTED
5248 mbUpdateListCount = mbUpdateListCount ||
5249 ( rWhich == RES_PARATR_LIST_ISCOUNTED &&
5250 !mrTextNode.IsCountedInList() );
5254 HandleResetAttrAtTextNode::~HandleResetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE
5256 if ( mbListStyleOrIdReset && !mrTextNode.IsInList() )
5258 // check, if in spite of the reset of the list style or the list id
5259 // the paragraph still has to be added to a list.
5260 if (mrTextNode.GetNumRule() && !mrTextNode.GetListId().isEmpty())
5262 // #i96062#
5263 // If paragraph has no list level attribute set and list style
5264 // is the outline style, apply outline level as the list level.
5265 if ( !mrTextNode.HasAttrListLevel() &&
5266 mrTextNode.GetNumRule()->GetName()==SwNumRule::GetOutlineRuleName() &&
5267 mrTextNode.GetTextColl()->IsAssignedToListLevelOfOutlineStyle() )
5269 int nNewListLevel = mrTextNode.GetTextColl()->GetAssignedOutlineStyleLevel();
5270 if ( 0 <= nNewListLevel && nNewListLevel < MAXLEVEL )
5272 mrTextNode.SetAttrListLevel( nNewListLevel );
5275 mrTextNode.AddToList();
5277 // #i70748#
5278 // #i105562#
5279 else
5281 if (mrTextNode.GetpSwAttrSet()
5282 && mrTextNode.GetAttr(RES_PARATR_OUTLINELEVEL, false).GetValue() > 0)
5284 mrTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr();
5289 if ( !mrTextNode.IsInList() )
5290 return;
5292 if ( mbUpdateListLevel )
5294 auto const nLevel(mrTextNode.GetAttrListLevel());
5295 const SwDoc& rDoc(mrTextNode.GetDoc());
5296 mrTextNode.DoNum(
5297 [nLevel, &rDoc](SwNodeNum & rNum) { rNum.SetLevelInListTree(nLevel, rDoc); });
5300 if ( mbUpdateListRestart )
5302 const SwDoc& rDoc(mrTextNode.GetDoc());
5303 mrTextNode.DoNum(
5304 [&rDoc](SwNodeNum & rNum) {
5305 rNum.InvalidateMe();
5306 rNum.NotifyInvalidSiblings(rDoc);
5310 if ( mbUpdateListCount )
5312 const SwDoc& rDoc(mrTextNode.GetDoc());
5313 mrTextNode.DoNum(
5314 [&rDoc](SwNodeNum & rNum) { rNum.InvalidateAndNotifyTree(rDoc); });
5317 // End of class <HandleResetAttrAtTextNode>
5320 bool SwTextNode::ResetAttr( sal_uInt16 nWhich1, sal_uInt16 nWhich2 )
5322 const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
5323 mbInSetOrResetAttr = true;
5325 HandleResetAttrAtTextNode aHandleResetAttr( *this, nWhich1, nWhich2 );
5327 bool bRet = SwContentNode::ResetAttr( nWhich1, nWhich2 );
5329 mbInSetOrResetAttr = bOldIsSetOrResetAttr;
5331 return bRet;
5334 bool SwTextNode::ResetAttr( const std::vector<sal_uInt16>& rWhichArr )
5336 const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
5337 mbInSetOrResetAttr = true;
5339 HandleResetAttrAtTextNode aHandleResetAttr( *this, rWhichArr );
5341 bool bRet = SwContentNode::ResetAttr( rWhichArr );
5343 mbInSetOrResetAttr = bOldIsSetOrResetAttr;
5345 return bRet;
5348 sal_uInt16 SwTextNode::ResetAllAttr()
5350 const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
5351 mbInSetOrResetAttr = true;
5353 HandleResetAttrAtTextNode aHandleResetAttr( *this );
5355 const sal_uInt16 nRet = SwContentNode::ResetAllAttr();
5357 mbInSetOrResetAttr = bOldIsSetOrResetAttr;
5359 return nRet;
5362 void SwTextNode::dumpAsXml(xmlTextWriterPtr pWriter) const
5364 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextNode"));
5365 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
5366 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(sal_Int32(GetIndex())).getStr()));
5368 OUString sText = GetText();
5369 for (int i = 0; i < 32; ++i)
5370 sText = sText.replace(i, '*');
5371 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_Text"));
5372 (void)xmlTextWriterWriteString(pWriter, BAD_CAST(sText.toUtf8().getStr()));
5373 (void)xmlTextWriterEndElement(pWriter);
5375 if (GetFormatColl())
5377 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColl"));
5378 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetFormatColl()->GetName().toUtf8().getStr()));
5379 (void)xmlTextWriterEndElement(pWriter);
5382 if (HasSwAttrSet())
5384 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwAttrSet"));
5385 GetSwAttrSet().dumpAsXml(pWriter);
5386 (void)xmlTextWriterEndElement(pWriter);
5389 if (HasHints())
5391 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwpHints"));
5392 const SwpHints& rHints = GetSwpHints();
5393 for (size_t i = 0; i < rHints.Count(); ++i)
5394 rHints.Get(i)->dumpAsXml(pWriter);
5395 (void)xmlTextWriterEndElement(pWriter);
5398 (void)xmlTextWriterEndElement(pWriter);
5401 sal_uInt32 SwTextNode::GetRsid( sal_Int32 nStt, sal_Int32 nEnd ) const
5403 SfxItemSetFixed<RES_CHRATR_RSID, RES_CHRATR_RSID> aSet( const_cast<SwAttrPool&>((GetDoc().GetAttrPool())) );
5404 if (GetParaAttr(aSet, nStt, nEnd))
5406 const SvxRsidItem* pRsid = aSet.GetItem<SvxRsidItem>(RES_CHRATR_RSID);
5407 if( pRsid )
5408 return pRsid->GetValue();
5411 return 0;
5414 sal_uInt32 SwTextNode::GetParRsid() const
5416 return reinterpret_cast<const SvxRsidItem&>(GetAttr( RES_PARATR_RSID )).GetValue();
5419 bool SwTextNode::CompareParRsid( const SwTextNode &rTextNode ) const
5421 sal_uInt32 nThisRsid = GetParRsid();
5422 sal_uInt32 nRsid = rTextNode.GetParRsid();
5424 return nThisRsid == nRsid;
5427 bool SwTextNode::CompareRsid( const SwTextNode &rTextNode, sal_Int32 nStt1, sal_Int32 nStt2 ) const
5429 sal_uInt32 nThisRsid = GetRsid( nStt1, nStt1 );
5430 sal_uInt32 nRsid = rTextNode.GetRsid( nStt2, nStt2 );
5432 return nThisRsid == nRsid;
5435 // sw::Metadatable
5436 ::sfx2::IXmlIdRegistry& SwTextNode::GetRegistry()
5438 return GetDoc().GetXmlIdRegistry();
5441 bool SwTextNode::IsInClipboard() const
5443 return GetDoc().IsClipBoard();
5446 bool SwTextNode::IsInUndo() const
5448 return GetDoc().GetIDocumentUndoRedo().IsUndoNodes(GetNodes());
5451 bool SwTextNode::IsInContent() const
5453 return !GetDoc().IsInHeaderFooter( *this );
5456 void SwTextNode::HandleNonLegacyHint(const SfxHint& rHint)
5458 assert(!dynamic_cast<const sw::LegacyModifyHint*>(&rHint));
5459 sw::TextNodeNotificationSuppressor(*this);
5460 CallSwClientNotify(rHint);
5462 SwDoc& rDoc = GetDoc();
5463 // #125329# - assure that text node is in document nodes array
5464 if ( !rDoc.IsInDtor() && &rDoc.GetNodes() == &GetNodes() )
5466 rDoc.GetNodes().UpdateOutlineNode(*this);
5470 void SwTextNode::UpdateDocPos(const SwTwips nDocPos, const sal_uInt32 nIndex)
5472 const sw::DocPosUpdateAtIndex aHint(nDocPos, *this, nIndex);
5473 CallSwClientNotify(aHint);
5476 void SwTextNode::TriggerNodeUpdate(const sw::LegacyModifyHint& rHint)
5478 const auto pOldValue = rHint.m_pOld;
5479 const auto pNewValue = rHint.m_pNew;
5481 sw::TextNodeNotificationSuppressor(*this);
5483 // Override Modify so that deleting styles works properly (outline
5484 // numbering!).
5485 // Never call ChgTextCollUpdateNum for Nodes in Undo.
5486 if( pOldValue
5487 && pNewValue
5488 && RES_FMT_CHG == pOldValue->Which()
5489 && GetRegisteredIn() == static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat
5490 && GetNodes().IsDocNodes() )
5492 assert(dynamic_cast<SwTextFormatColl const*>(static_cast<const SwFormatChg*>(pOldValue)->pChangedFormat));
5493 assert(dynamic_cast<SwTextFormatColl const*>(static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat));
5494 ChgTextCollUpdateNum(
5495 static_cast<const SwTextFormatColl*>(static_cast<const SwFormatChg*>(pOldValue)->pChangedFormat),
5496 static_cast<const SwTextFormatColl*>(static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat) );
5499 // reset fill information
5500 if (maFillAttributes && pNewValue)
5502 const sal_uInt16 nWhich = pNewValue->Which();
5503 bool bReset(RES_FMT_CHG == nWhich); // ..on format change (e.g. style changed)
5505 if(!bReset && RES_ATTRSET_CHG == nWhich) // ..on ItemChange from DrawingLayer FillAttributes
5507 SfxItemIter aIter(*static_cast<const SwAttrSetChg*>(pNewValue)->GetChgSet());
5509 for(const SfxPoolItem* pItem = aIter.GetCurItem(); pItem && !bReset; pItem = aIter.NextItem())
5511 bReset = !IsInvalidItem(pItem) && pItem->Which() >= XATTR_FILL_FIRST && pItem->Which() <= XATTR_FILL_LAST;
5515 if(bReset)
5517 maFillAttributes.reset();
5521 if ( !mbInSetOrResetAttr )
5523 HandleModifyAtTextNode( *this, pOldValue, pNewValue );
5526 SwContentNode::SwClientNotify(*this, rHint);
5528 SwDoc& rDoc = GetDoc();
5529 // #125329# - assure that text node is in document nodes array
5530 if ( !rDoc.IsInDtor() && &rDoc.GetNodes() == &GetNodes() )
5532 rDoc.GetNodes().UpdateOutlineNode(*this);
5536 if (pOldValue && (RES_REMOVE_UNO_OBJECT == pOldValue->Which()))
5537 { // invalidate cached uno object
5538 SetXParagraph(nullptr);
5542 void SwTextNode::SwClientNotify( const SwModify& rModify, const SfxHint& rHint )
5544 if (rHint.GetId() == SfxHintId::SwLegacyModify)
5546 auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint);
5547 TriggerNodeUpdate(*pLegacyHint);
5549 else if (dynamic_cast<const SwAttrHint*>(&rHint))
5551 if (&rModify == GetRegisteredIn())
5552 ChkCondColl();
5556 uno::Reference< rdf::XMetadatable >
5557 SwTextNode::MakeUnoObject()
5559 const uno::Reference<rdf::XMetadatable> xMeta(
5560 SwXParagraph::CreateXParagraph(GetDoc(), this));
5561 return xMeta;
5564 drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwTextNode::getSdrAllFillAttributesHelper() const
5566 // create SdrAllFillAttributesHelper on demand
5567 if(!maFillAttributes)
5569 const_cast< SwTextNode* >(this)->maFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(GetSwAttrSet());
5572 return maFillAttributes;
5575 void SwTextNode::SetXParagraph(rtl::Reference<SwXParagraph> const & xParagraph)
5577 m_wXParagraph = xParagraph.get();
5580 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */