summaryrefslogtreecommitdiffstats
path: root/sw/source/core/txtnode/ndtxt.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/core/txtnode/ndtxt.cxx
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/txtnode/ndtxt.cxx')
-rw-r--r--sw/source/core/txtnode/ndtxt.cxx5587
1 files changed, 5587 insertions, 0 deletions
diff --git a/sw/source/core/txtnode/ndtxt.cxx b/sw/source/core/txtnode/ndtxt.cxx
new file mode 100644
index 0000000000..47bff5e08b
--- /dev/null
+++ b/sw/source/core/txtnode/ndtxt.cxx
@@ -0,0 +1,5587 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+#include <hints.hxx>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/string.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/rsiditem.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <anchoredobject.hxx>
+#include <txtfld.hxx>
+#include <txtinet.hxx>
+#include <fmtanchr.hxx>
+#include <fmtinfmt.hxx>
+#include <fmtrfmrk.hxx>
+#include <txttxmrk.hxx>
+#include <fchrfmt.hxx>
+#include <txtftn.hxx>
+#include <fmtflcnt.hxx>
+#include <fmtfld.hxx>
+#include <frmatr.hxx>
+#include <ftnidx.hxx>
+#include <ftninfo.hxx>
+#include <fmtftn.hxx>
+#include <charfmt.hxx>
+#include <ndtxt.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentListsAccess.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <docary.hxx>
+#include <pam.hxx>
+#include <fldbas.hxx>
+#include <paratr.hxx>
+#include <txtfrm.hxx>
+#include <ftnfrm.hxx>
+#include <pagefrm.hxx>
+#include <rootfrm.hxx>
+#include <expfld.hxx>
+#include <section.hxx>
+#include <mvsave.hxx>
+#include <SwGrammarMarkUp.hxx>
+#include <redline.hxx>
+#include <IMark.hxx>
+#include <scriptinfo.hxx>
+#include <istyleaccess.hxx>
+#include <SwStyleNameMapper.hxx>
+#include <numrule.hxx>
+#include <docsh.hxx>
+#include <SwNodeNum.hxx>
+#include <svl/grabbagitem.hxx>
+#include <svl/intitem.hxx>
+#include <sortedobjs.hxx>
+#include <calbck.hxx>
+#include <attrhint.hxx>
+#include <memory>
+#include <unoparagraph.hxx>
+#include <unotext.hxx>
+#include <wrtsh.hxx>
+#include <fmtpdsc.hxx>
+#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx>
+#include <svl/itemiter.hxx>
+#include <undobj.hxx>
+#include <formatflysplit.hxx>
+
+using namespace ::com::sun::star;
+
+typedef std::vector<SwTextAttr*> SwpHts;
+
+namespace sw {
+ class TextNodeNotificationSuppressor {
+ SwTextNode& m_rNode;
+ bool m_bWasNotifiable;
+ public:
+ TextNodeNotificationSuppressor(SwTextNode& rNode)
+ : m_rNode(rNode)
+ , m_bWasNotifiable(rNode.m_bNotifiable)
+ {
+ m_rNode.m_bNotifiable = false;
+ }
+ ~TextNodeNotificationSuppressor()
+ {
+ m_rNode.m_bNotifiable = m_bWasNotifiable;
+ }
+ };
+}
+
+// unfortunately everyone can change Hints without ensuring order or the linking between them
+#ifdef DBG_UTIL
+#define CHECK_SWPHINTS(pNd) { if( pNd->GetpSwpHints() && \
+ !pNd->GetDoc().IsInReading() ) \
+ pNd->GetpSwpHints()->Check(true); }
+#define CHECK_SWPHINTS_IF_FRM(pNd) { if( pNd->GetpSwpHints() && \
+ !pNd->GetDoc().IsInReading() ) \
+ pNd->GetpSwpHints()->Check(getLayoutFrame(nullptr, nullptr, nullptr) != nullptr); }
+#else
+#define CHECK_SWPHINTS(pNd)
+#define CHECK_SWPHINTS_IF_FRM(pNd)
+#endif
+
+SwTextNode *SwNodes::MakeTextNode( SwNode& rWhere,
+ SwTextFormatColl *pColl, bool const bNewFrames)
+{
+ OSL_ENSURE( pColl, "Collection pointer is 0." );
+
+ SwTextNode *pNode = new SwTextNode( rWhere, pColl, nullptr );
+
+ SwNodeIndex aIdx( *pNode );
+
+ // if there is no layout or it is in a hidden section, MakeFrames is not needed
+ const SwSectionNode* pSectNd;
+ if (!bNewFrames ||
+ !GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell() ||
+ ( nullptr != (pSectNd = pNode->FindSectionNode()) &&
+ pSectNd->GetSection().IsHiddenFlag() ))
+ return pNode;
+
+ SwNodeIndex aTmp( rWhere );
+ do {
+ // max. 2 loops:
+ // 1. take the successor
+ // 2. take the predecessor
+
+ SwNode * pNd = & aTmp.GetNode();
+ switch (pNd->GetNodeType())
+ {
+ case SwNodeType::Table:
+ static_cast<SwTableNode*>(pNd)->MakeFramesForAdjacentContentNode(aIdx);
+ return pNode;
+
+ case SwNodeType::Section:
+ if( static_cast<SwSectionNode*>(pNd)->GetSection().IsHidden() ||
+ static_cast<SwSectionNode*>(pNd)->IsContentHidden() )
+ {
+ pNd = FindPrvNxtFrameNode( *pNode, pNode );
+ if( !pNd )
+ return pNode;
+ aTmp = *pNd;
+ break;
+ }
+ static_cast<SwSectionNode*>(pNd)->MakeFramesForAdjacentContentNode(aIdx);
+ return pNode;
+
+ case SwNodeType::Text:
+ case SwNodeType::Grf:
+ case SwNodeType::Ole:
+ static_cast<SwContentNode*>(pNd)->MakeFramesForAdjacentContentNode(*pNode);
+ return pNode;
+
+ case SwNodeType::End:
+ if( pNd->StartOfSectionNode()->IsSectionNode() &&
+ aTmp.GetIndex() < rWhere.GetIndex() )
+ {
+ if( pNd->StartOfSectionNode()->GetSectionNode()->GetSection().IsHiddenFlag())
+ {
+ if( !GoPrevSection( &aTmp, true, false ) ||
+ aTmp.GetNode().FindTableNode() !=
+ pNode->FindTableNode() )
+ return pNode;
+ }
+ else
+ aTmp = *pNd->StartOfSectionNode();
+ break;
+ }
+ else if( pNd->StartOfSectionNode()->IsTableNode() &&
+ aTmp.GetIndex() < rWhere.GetIndex() )
+ {
+ // after a table node
+ aTmp = *pNd->StartOfSectionNode();
+ break;
+ }
+ [[fallthrough]];
+ default:
+ if( &rWhere == &aTmp.GetNode() )
+ aTmp -= SwNodeOffset(2);
+ else
+ return pNode;
+ break;
+ }
+ } while( true );
+}
+
+SwTextNode::SwTextNode( SwNode& rWhere, SwTextFormatColl *pTextColl, const SfxItemSet* pAutoAttr )
+: SwContentNode( rWhere, SwNodeType::Text, pTextColl ),
+ m_bContainsHiddenChars(false),
+ m_bHiddenCharsHidePara(false),
+ m_bRecalcHiddenCharFlags(false),
+ m_bLastOutlineState( false ),
+ m_bNotifiable( true ),
+ mbEmptyListStyleSetDueToSetOutlineLevelAttr( false ),
+ mbInSetOrResetAttr( false ),
+ m_bInUndo(false)
+{
+ {
+ sw::TextNodeNotificationSuppressor(*this);
+
+ if( pAutoAttr )
+ SetAttr( *pAutoAttr );
+
+ if (!IsInList() && GetNumRule() && !GetListId().isEmpty())
+ {
+ // #i101516#
+ // apply paragraph style's assigned outline style list level as
+ // list level of the paragraph, if it has none set already.
+ if ( !HasAttrListLevel() &&
+ pTextColl && pTextColl->IsAssignedToListLevelOfOutlineStyle() )
+ {
+ SetAttrListLevel( pTextColl->GetAssignedOutlineStyleLevel() );
+ }
+ AddToList();
+ }
+
+ // call method <UpdateOutlineNode(..)> only for the document nodes array
+ if (GetNodes().IsDocNodes())
+ GetNodes().UpdateOutlineNode(*this);
+ }
+
+ m_bContainsHiddenChars = m_bHiddenCharsHidePara = false;
+ m_bRecalcHiddenCharFlags = true;
+}
+
+SwTextNode::~SwTextNode()
+{
+ // delete only removes the pointer not the array elements!
+ if ( m_pSwpHints )
+ {
+ // do not delete attributes twice when those delete their content
+ std::unique_ptr<SwpHints> pTmpHints(std::move(m_pSwpHints));
+
+ for( size_t j = pTmpHints->Count(); j; )
+ {
+ // first remove the attribute from the array otherwise
+ // if would delete itself
+ DestroyAttr( pTmpHints->Get( --j ) );
+ }
+ }
+
+ // must be removed from outline nodes by now
+#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
+ SwOutlineNodes::size_type foo;
+ assert(!GetNodes().GetOutLineNds().Seek_Entry(this, &foo));
+#endif
+
+ RemoveFromList();
+
+ DelFrames(nullptr); // must be called here while it's still a SwTextNode
+ DelFrames_TextNodePart();
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ if (!GetDoc().IsInDtor())
+ ResetAttr(RES_PAGEDESC);
+#else
+ ResetAttr(RES_PAGEDESC);
+#endif
+ InvalidateInSwCache(RES_OBJECTDYING);
+}
+
+void SwTextNode::FileLoadedInitHints()
+{
+ if (m_pSwpHints)
+ {
+ m_pSwpHints->MergePortions(*this);
+ }
+}
+
+SwContentFrame *SwTextNode::MakeFrame( SwFrame* pSib )
+{
+ SwContentFrame *pFrame = sw::MakeTextFrame(*this, pSib, sw::FrameMode::New);
+ return pFrame;
+}
+
+sal_Int32 SwTextNode::Len() const
+{
+ return m_Text.getLength();
+}
+
+// After a split node, it's necessary to actualize the ref-pointer of the ftnfrms.
+static void lcl_ChangeFootnoteRef( SwTextNode &rNode )
+{
+ SwpHints *pSwpHints = rNode.GetpSwpHints();
+ if( !(pSwpHints && rNode.GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell()) )
+ return;
+
+ SwContentFrame* pFrame = nullptr;
+ // OD 07.11.2002 #104840# - local variable to remember first footnote
+ // of node <rNode> in order to invalidate position of its first content.
+ // Thus, in its <MakeAll()> it will checked its position relative to its reference.
+ SwFootnoteFrame* pFirstFootnoteOfNode = nullptr;
+ for( size_t j = pSwpHints->Count(); j; )
+ {
+ SwTextAttr* pHt = pSwpHints->Get(--j);
+ if (RES_TXTATR_FTN == pHt->Which())
+ {
+ if( !pFrame )
+ {
+ pFrame = SwIterator<SwContentFrame, SwTextNode, sw::IteratorMode::UnwrapMulti>(rNode).First();
+ if (!pFrame)
+ return;
+ }
+ SwTextFootnote *pAttr = static_cast<SwTextFootnote*>(pHt);
+ OSL_ENSURE( pAttr->GetStartNode(), "FootnoteAtr without StartNode." );
+ SwNodeIndex aIdx( *pAttr->GetStartNode(), 1 );
+ SwContentNode *pNd = aIdx.GetNode().GetContentNode();
+ if ( !pNd )
+ pNd = pFrame->GetAttrSet()->GetDoc()->
+ GetNodes().GoNextSection( &aIdx, true, false );
+ if ( !pNd )
+ continue;
+
+ SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pNd);
+ SwContentFrame* pContent = aIter.First();
+ if( pContent )
+ {
+ OSL_ENSURE( pContent->getRootFrame() == pFrame->getRootFrame(),
+ "lcl_ChangeFootnoteRef: Layout double?" );
+ SwFootnoteFrame *pFootnote = pContent->FindFootnoteFrame();
+ if( pFootnote && pFootnote->GetAttr() == pAttr )
+ {
+ while( pFootnote->GetMaster() )
+ pFootnote = pFootnote->GetMaster();
+ // #104840# - remember footnote frame
+ pFirstFootnoteOfNode = pFootnote;
+ while ( pFootnote )
+ {
+ pFootnote->SetRef( pFrame );
+ pFootnote = pFootnote->GetFollow();
+ static_cast<SwTextFrame*>(pFrame)->SetFootnote( true );
+ }
+ }
+#if OSL_DEBUG_LEVEL > 0
+ while( nullptr != (pContent = aIter.Next()) )
+ {
+ SwFootnoteFrame *pDbgFootnote = pContent->FindFootnoteFrame();
+ OSL_ENSURE( !pDbgFootnote || pDbgFootnote->GetRef() == pFrame,
+ "lcl_ChangeFootnoteRef: Who's that guy?" );
+ }
+#endif
+ }
+ }
+ } // end of for-loop on <SwpHints>
+ // #104840# - invalidate
+ if ( pFirstFootnoteOfNode )
+ {
+ SwContentFrame* pContent = pFirstFootnoteOfNode->ContainsContent();
+ if ( pContent )
+ {
+ pContent->InvalidatePos_();
+ }
+ }
+}
+
+namespace sw {
+
+// check if there are flys on the existing frames (now on "pNode")
+// that need to be moved to the new frames of "this"
+void MoveMergedFlysAndFootnotes(std::vector<SwTextFrame*> const& rFrames,
+ SwTextNode const& rFirstNode, SwTextNode & rSecondNode,
+ bool isSplitNode)
+{
+ if (!isSplitNode)
+ {
+ lcl_ChangeFootnoteRef(rSecondNode);
+ }
+ for (SwNodeOffset nIndex = rSecondNode.GetIndex() + 1; ; ++nIndex)
+ {
+ SwNode *const pTmp(rSecondNode.GetNodes()[nIndex]);
+ if (pTmp->IsCreateFrameWhenHidingRedlines() || pTmp->IsEndNode())
+ {
+ break;
+ }
+ else if (pTmp->IsStartNode())
+ {
+ nIndex = pTmp->EndOfSectionIndex();
+ }
+ else if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst
+ && pTmp->IsTextNode())
+ {
+ lcl_ChangeFootnoteRef(*pTmp->GetTextNode());
+ }
+ }
+ for (SwTextFrame *const pFrame : rFrames)
+ {
+ if (SwSortedObjs *const pObjs = pFrame->GetDrawObjs())
+ {
+ std::vector<SwAnchoredObject*> objs;
+ objs.reserve(pObjs->size());
+ for (SwAnchoredObject *const pObj : *pObjs)
+ {
+ objs.push_back(pObj);
+ }
+ for (SwAnchoredObject *const pObj : objs)
+ {
+ SwFrameFormat & rFormat(pObj->GetFrameFormat());
+ SwFormatAnchor const& rAnchor(rFormat.GetAnchor());
+ if (rFirstNode.GetIndex() < rAnchor.GetAnchorNode()->GetIndex())
+ {
+ // move it to the new frame of "this"
+ rFormat.CallSwClientNotify(sw::LegacyModifyHint(&rAnchor, &rAnchor));
+ // note pObjs will be deleted if it becomes empty
+ assert(!pFrame->GetDrawObjs() || !pObjs->Contains(*pObj));
+ }
+ }
+ }
+ }
+}
+
+} // namespace
+
+SwTextNode *SwTextNode::SplitContentNode(const SwPosition & rPos,
+ std::function<void (SwTextNode *, sw::mark::RestoreMode, bool AtStart)> const*const pContentIndexRestore)
+{
+ bool isHide(false);
+ SwNode::Merge const eOldMergeFlag(GetRedlineMergeFlag());
+ bool parentIsOutline = IsOutline();
+
+ // create a node "in front" of me
+ const sal_Int32 nSplitPos = rPos.GetContentIndex();
+ const sal_Int32 nTextLen = m_Text.getLength();
+ SwTextNode* const pNode =
+ MakeNewTextNode( rPos.GetNode(), false, nSplitPos==nTextLen );
+
+ // the first paragraph gets the XmlId,
+ // _except_ if it is empty and the second is not empty
+ if (nSplitPos != 0) {
+ pNode->RegisterAsCopyOf(*this, true);
+ if (nSplitPos == nTextLen)
+ {
+ RemoveMetadataReference();
+ // NB: SwUndoSplitNode will call pNode->JoinNext,
+ // which is sufficient even in this case!
+ }
+ }
+
+ ResetAttr( RES_PARATR_LIST_ISRESTART );
+ ResetAttr( RES_PARATR_LIST_RESTARTVALUE );
+ ResetAttr( RES_PARATR_LIST_ISCOUNTED );
+ if ( GetNumRule() == nullptr || (parentIsOutline && !IsOutline()) )
+ {
+ ResetAttr( RES_PARATR_LIST_ID );
+ ResetAttr( RES_PARATR_LIST_LEVEL );
+ }
+
+ bool bSplitFly = false;
+ std::optional<std::vector<SwFrameFormat*>> oFlys = sw::GetFlysAnchoredAt(GetDoc(), GetIndex());
+ if (oFlys.has_value())
+ {
+ // See if one of the flys is a split fly. If so, we need to keep
+ // the potentially split text frames unchanged and create a new
+ // text frame at the end.
+ for (const auto& rFly : *oFlys)
+ {
+ if (rFly->GetFlySplit().GetValue())
+ {
+ bSplitFly = true;
+ break;
+ }
+ }
+ }
+
+ if ( HasWriterListeners() && !m_Text.isEmpty() && ((nTextLen / 2) < nSplitPos || bSplitFly) )
+ {
+ // optimization for SplitNode: If a split is at the end of a node then
+ // move the frames from the current to the new one and create new ones
+ // for the current one.
+
+ // If fly frames are moved, they don't need to destroy their layout
+ // frames. Set a flag that is checked in SwTextFlyCnt::SetAnchor.
+ if ( HasHints() )
+ {
+ pNode->GetOrCreateSwpHints().SetInSplitNode(true);
+ }
+
+ // Move the first part of the content to the new node and delete
+ // it in the old node.
+ SwContentIndex aIdx( this );
+ CutText( pNode, aIdx, nSplitPos );
+
+ if( GetWrong() )
+ {
+ pNode->SetWrong( GetWrong()->SplitList( nSplitPos ) );
+ }
+ SetWrongDirty(sw::WrongState::TODO);
+
+ if( GetGrammarCheck() )
+ {
+ pNode->SetGrammarCheck( GetGrammarCheck()->SplitGrammarList( nSplitPos ) );
+ }
+ SetGrammarCheckDirty( true );
+
+ SetWordCountDirty( true );
+
+ if( GetSmartTags() )
+ {
+ pNode->SetSmartTags( GetSmartTags()->SplitList( nSplitPos ) );
+ }
+ SetSmartTagDirty( true );
+
+ resetAndQueueAccessibilityCheck();
+ pNode->resetAndQueueAccessibilityCheck();
+
+ if ( pNode->HasHints() )
+ {
+ if ( pNode->m_pSwpHints->CanBeDeleted() )
+ {
+ pNode->m_pSwpHints.reset();
+ }
+ else
+ {
+ pNode->m_pSwpHints->SetInSplitNode(false);
+ }
+
+ // All fly frames anchored as char that are moved to the new
+ // node must have their layout frames deleted.
+ // This comment is sort of silly because we actually delete the
+ // layout frames of those which were not moved?
+ // JP 01.10.96: delete all empty and not-to-be-expanded attributes
+ if ( HasHints() )
+ {
+ for ( size_t j = m_pSwpHints->Count(); j; )
+ {
+ SwTextAttr* const pHt = m_pSwpHints->Get( --j );
+ if ( RES_TXTATR_FLYCNT == pHt ->Which() )
+ {
+ pHt->GetFlyCnt().GetFrameFormat()->DelFrames();
+ }
+ else if ( pHt->DontExpand() )
+ {
+ const sal_Int32* const pEnd = pHt->GetEnd();
+ if (pEnd && pHt->GetStart() == *pEnd )
+ {
+ // delete it!
+ m_pSwpHints->DeleteAtPos( j );
+ DestroyAttr( pHt );
+ }
+ }
+ }
+ }
+
+ }
+
+ if (pContentIndexRestore)
+ { // call before making frames and before RegisterToNode
+ (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::NonFlys, false);
+ }
+ if (eOldMergeFlag != SwNode::Merge::None)
+ { // clear before making frames and before RegisterToNode
+ SetRedlineMergeFlag(SwNode::Merge::None);
+ } // now RegisterToNode will set merge flags in both nodes properly!
+
+ std::vector<SwTextFrame*> frames;
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
+ for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ isHide = true;
+ }
+ frames.push_back(pFrame);
+ }
+ for (SwTextFrame * pFrame : frames)
+ {
+ pFrame->RegisterToNode( *pNode );
+ if (!pFrame->IsFollow() && pFrame->GetOffset())
+ {
+ pFrame->SetOffset( TextFrameIndex(0) );
+ }
+ }
+
+ InvalidateInSwCache(RES_ATTRSET_CHG);
+
+ if ( HasHints() )
+ {
+ MoveTextAttr_To_AttrSet();
+ }
+ // in case there are frames, the RegisterToNode has set the merge flag
+ pNode->MakeFramesForAdjacentContentNode(*this);
+ lcl_ChangeFootnoteRef( *this );
+ if (pContentIndexRestore)
+ { // call after making frames; listeners will take care of adding to the right frame
+ (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::Flys, false);
+ }
+ if (eOldMergeFlag != SwNode::Merge::None)
+ {
+ MoveMergedFlysAndFootnotes(frames, *pNode, *this, true);
+ }
+ }
+ else
+ {
+ std::unique_ptr<SwWrongList> pList = ReleaseWrong();
+ SetWrongDirty(sw::WrongState::TODO);
+
+ std::unique_ptr<SwGrammarMarkUp> pList3 = ReleaseGrammarCheck();
+ SetGrammarCheckDirty( true );
+
+ SetWordCountDirty( true );
+
+ std::unique_ptr<SwWrongList> pList2 = ReleaseSmartTags();
+ SetSmartTagDirty( true );
+
+ SwContentIndex aIdx( this );
+ CutText( pNode, aIdx, nSplitPos );
+
+ // JP 01.10.96: delete all empty and not-to-be-expanded attributes
+ if ( HasHints() )
+ {
+ for ( size_t j = m_pSwpHints->Count(); j; )
+ {
+ SwTextAttr* const pHt = m_pSwpHints->Get( --j );
+ const sal_Int32* const pEnd = pHt->GetEnd();
+ if ( pHt->DontExpand() && pEnd && (pHt->GetStart() == *pEnd) )
+ {
+ // delete it!
+ m_pSwpHints->DeleteAtPos( j );
+ DestroyAttr( pHt );
+ }
+ }
+ MoveTextAttr_To_AttrSet();
+ }
+
+ if( pList )
+ {
+ pNode->SetWrong( pList->SplitList( nSplitPos ) );
+ SetWrong( std::move(pList) );
+ }
+
+ if( pList3 )
+ {
+ pNode->SetGrammarCheck( pList3->SplitGrammarList( nSplitPos ) );
+ SetGrammarCheck( std::move(pList3) );
+ }
+
+ if( pList2 )
+ {
+ pNode->SetSmartTags( pList2->SplitList( nSplitPos ) );
+ SetSmartTags( std::move(pList2) );
+ }
+
+ resetAndQueueAccessibilityCheck();
+ pNode->resetAndQueueAccessibilityCheck();
+
+ if (pContentIndexRestore)
+ { // call before making frames and before RegisterToNode
+ (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::NonFlys, false);
+ }
+
+ std::vector<SwTextFrame*> frames;
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
+ for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ frames.push_back(pFrame);
+ if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ isHide = true;
+ }
+ }
+ bool bNonMerged(false);
+ bool bRecreateThis(false);
+ for (SwTextFrame * pFrame : frames)
+ {
+ // sw_redlinehide: for this to work properly with hidden nodes,
+ // the frame needs to listen on them too.
+ // also: have to check the frame; this->GetRedlineMergeFlag()
+ // is None in case there's a delete redline inside the paragraph,
+ // but that could still result in a merged frame after split...
+ if (pFrame->GetMergedPara())
+ {
+ // Can't special case this == First here - that could (if
+ // both nodes are still merged by redline) lead to
+ // duplicate frames on "this".
+ // Update the extents with new node; also inits merge flag,
+ // so the MakeFramesForAdjacentContentNode below respects it
+ pFrame->RegisterToNode(*pNode);
+ if (nSplitPos == 0)
+ {
+ // in this case, it was not
+ // invalidated because Cut didn't sent it any hints,
+ // so we have to invalidate it here!
+ pFrame->Prepare(PrepareHint::Clear, nullptr, false);
+ }
+ if (!pFrame->GetMergedPara() ||
+ !pFrame->GetMergedPara()->listener.IsListeningTo(this))
+ {
+ // it's no longer listening - need to recreate frame
+ // (note this is idempotent, can be done once per frame)
+ SetRedlineMergeFlag(SwNode::Merge::None);
+ bRecreateThis = true;
+ }
+ }
+ else
+ {
+ bNonMerged = true;
+ }
+ }
+ assert(!(bNonMerged && bRecreateThis)); // 2 layouts not handled yet - maybe best to simply use the other branch then?
+ if (!frames.empty() && bNonMerged)
+ {
+ // the existing frame on "this" should have been updated by Cut
+ MakeFramesForAdjacentContentNode(*pNode);
+ lcl_ChangeFootnoteRef(*pNode);
+ }
+ else if (bRecreateThis)
+ {
+ assert(pNode->HasWriterListeners()); // was just moved there
+ pNode->MakeFramesForAdjacentContentNode(*this);
+ lcl_ChangeFootnoteRef(*this);
+ }
+
+ if (pContentIndexRestore)
+ { // call after making frames; listeners will take care of adding to the right frame
+ (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::Flys, nSplitPos == 0);
+ }
+
+ if (bRecreateThis)
+ {
+ MoveMergedFlysAndFootnotes(frames, *pNode, *this, true);
+ }
+ }
+
+ // pNode is the previous node, 'this' is the next node from the split.
+ if (nSplitPos == nTextLen && m_pSwpHints)
+ {
+ // We just created an empty next node: avoid unwanted superscript in the new node if it's
+ // there.
+ for (size_t i = 0; i < m_pSwpHints->Count(); ++i)
+ {
+ SwTextAttr* pHt = m_pSwpHints->Get(i);
+ if (pHt->Which() != RES_TXTATR_AUTOFMT)
+ {
+ continue;
+ }
+
+ const sal_Int32* pEnd = pHt->GetEnd();
+ if (!pEnd || pHt->GetStart() != *pEnd)
+ {
+ continue;
+ }
+
+ const std::shared_ptr<SfxItemSet>& pSet = pHt->GetAutoFormat().GetStyleHandle();
+ if (!pSet || pSet->Count() != 1 || !pSet->HasItem(RES_CHRATR_ESCAPEMENT))
+ {
+ continue;
+ }
+
+ m_pSwpHints->DeleteAtPos(i);
+ SwTextAttr::Destroy(pHt, GetDoc().GetAttrPool());
+ --i;
+ }
+ }
+
+#ifndef NDEBUG
+ if (isHide) // otherwise flags won't be set anyway
+ {
+ // First
+ // -> First,NonFirst
+ // -> First,Hidden
+ // -> None,First
+ // Hidden
+ // -> Hidden,Hidden (if still inside merge rl)
+ // -> NonFirst,First (if redline was split)
+ // NonFirst
+ // -> NonFirst,First (if split after end of "incoming" redline &
+ // before start of "outgoing" redline)
+ // -> NonFirst,None (if split after end of "incoming" redline)
+ // -> NonFirst,Hidden (if split after start of "outgoing" redline)
+ // -> Hidden, NonFirst (if split before end of "incoming" redline)
+ // None
+ // -> None,None
+ // -> First,NonFirst (if splitting inside a delete redline)
+ SwNode::Merge const eFirst(pNode->GetRedlineMergeFlag());
+ SwNode::Merge const eSecond(GetRedlineMergeFlag());
+ switch (eOldMergeFlag)
+ {
+ case Merge::First:
+ assert((eFirst == Merge::First && eSecond == Merge::NonFirst)
+ || (eFirst == Merge::First && eSecond == Merge::Hidden)
+ || (eFirst == Merge::None && eSecond == Merge::First));
+ break;
+ case Merge::Hidden:
+ assert((eFirst == Merge::Hidden && eSecond == Merge::Hidden)
+ || (eFirst == Merge::NonFirst && eSecond == Merge::First)
+ // next ones can happen temp. in UndoDelete :(
+ || (eFirst == Merge::Hidden && eSecond == Merge::NonFirst)
+ || (eFirst == Merge::NonFirst && eSecond == Merge::None));
+ break;
+ case Merge::NonFirst:
+ assert((eFirst == Merge::NonFirst && eSecond == Merge::First)
+ || (eFirst == Merge::NonFirst && eSecond == Merge::None)
+ || (eFirst == Merge::NonFirst && eSecond == Merge::Hidden)
+ || (eFirst == Merge::Hidden && eSecond == Merge::NonFirst));
+ break;
+ case Merge::None:
+ assert((eFirst == Merge::None && eSecond == Merge::None)
+ || (eFirst == Merge::First && eSecond == Merge::NonFirst));
+ break;
+ }
+ }
+#else
+ (void) isHide;
+#endif
+
+ {
+ // Send Hint for PageDesc. This should be done in the Layout when
+ // pasting the frames, but that causes other problems that look
+ // expensive to solve.
+ const SwFormatPageDesc *pItem;
+ if(HasWriterListeners() && (pItem = pNode->GetSwAttrSet().GetItemIfSet(RES_PAGEDESC)))
+ pNode->TriggerNodeUpdate(sw::LegacyModifyHint(pItem, pItem));
+ }
+ return pNode;
+}
+
+void SwTextNode::MoveTextAttr_To_AttrSet()
+{
+ OSL_ENSURE( m_pSwpHints, "MoveTextAttr_To_AttrSet without SwpHints?" );
+ for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i )
+ {
+ SwTextAttr *pHt = m_pSwpHints->Get(i);
+
+ if( pHt->GetStart() )
+ break;
+
+ const sal_Int32* pHtEndIdx = pHt->GetEnd();
+
+ if( !pHtEndIdx )
+ continue;
+
+ if (*pHtEndIdx < m_Text.getLength() || pHt->IsCharFormatAttr())
+ break;
+
+ if( !pHt->IsDontMoveAttr() &&
+ SetAttr( pHt->GetAttr() ) )
+ {
+ m_pSwpHints->DeleteAtPos(i);
+ DestroyAttr( pHt );
+ --i;
+ }
+ }
+
+}
+
+namespace sw {
+
+/// if first node is deleted & second survives, then the first node's frame
+/// will be deleted too; prevent this by moving the frame to the second node
+/// if necessary.
+void MoveDeletedPrevFrames(const SwTextNode & rDeletedPrev, SwTextNode & rNode)
+{
+ std::vector<SwTextFrame*> frames;
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rDeletedPrev);
+ for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ frames.push_back(pFrame);
+ }
+ }
+ {
+ auto frames2(frames);
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIt(rNode);
+ for (SwTextFrame* pFrame = aIt.First(); pFrame; pFrame = aIt.Next())
+ {
+ if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ auto const it(std::find(frames2.begin(), frames2.end(), pFrame));
+ assert(it != frames2.end());
+ frames2.erase(it);
+ }
+ }
+ assert(frames2.empty());
+ }
+ for (SwTextFrame *const pFrame : frames)
+ {
+ pFrame->RegisterToNode(rNode, true);
+ }
+}
+
+// typical Join:
+// None,Node->None
+// None,First->First
+// First,NonFirst->First
+// NonFirst,First->NonFirst
+// NonFirst,None->NonFirst
+
+/// if first node is First, its frames may need to be moved, never deleted.
+/// if first node is NonFirst, second node's own frames (First/None) must be deleted
+void CheckResetRedlineMergeFlag(SwTextNode & rNode, Recreate const eRecreateMerged)
+{
+ if (eRecreateMerged != sw::Recreate::No)
+ {
+ SwTextNode * pMergeNode(&rNode);
+ if (eRecreateMerged == sw::Recreate::Predecessor
+ // tdf#135018 check that there is a predecessor node, i.e. rNode
+ // isn't the first node after the body start node
+ && rNode.GetNodes()[rNode.GetIndex() - 1]->StartOfSectionIndex() != SwNodeOffset(0))
+ {
+ for (SwNodeOffset i = rNode.GetIndex() - 1; ; --i)
+ {
+ SwNode *const pNode(rNode.GetNodes()[i]);
+ assert(!pNode->IsStartNode());
+ if (pNode->IsEndNode())
+ {
+ i = pNode->StartOfSectionIndex();
+ }
+ else if (pNode->IsTextNode())
+ {
+ pMergeNode = pNode->GetTextNode(); // use predecessor to merge
+ break;
+ }
+ }
+ }
+ std::vector<SwTextFrame*> frames;
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pMergeNode);
+ for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ frames.push_back(pFrame);
+ }
+ }
+ auto eMode(sw::FrameMode::Existing);
+ for (SwTextFrame * pFrame : frames)
+ {
+ SwTextNode & rFirstNode(pFrame->GetMergedPara()
+ ? *pFrame->GetMergedPara()->pFirstNode
+ : *pMergeNode);
+ assert(rFirstNode.GetIndex() <= rNode.GetIndex());
+ pFrame->SetMergedPara(sw::CheckParaRedlineMerge(
+ *pFrame, rFirstNode, eMode));
+ // there is no merged para in case the deleted node had one but
+ // nothing was actually hidden
+ if (pFrame->GetMergedPara())
+ {
+ assert(pFrame->GetMergedPara()->listener.IsListeningTo(&rNode));
+ assert(rNode.GetIndex() <= pFrame->GetMergedPara()->pLastNode->GetIndex());
+ // tdf#135978 Join: recreate fly frames anchored to subsequent nodes
+ if (eRecreateMerged == sw::Recreate::ThisNode)
+ {
+ AddRemoveFlysAnchoredToFrameStartingAtNode(*pFrame, rNode, nullptr);
+ }
+ }
+ eMode = sw::FrameMode::New; // Existing is not idempotent!
+ }
+ }
+ else if (rNode.GetRedlineMergeFlag() != SwNode::Merge::None)
+ {
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rNode);
+ for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ if (auto const pMergedPara = pFrame->GetMergedPara())
+ {
+ if (pMergedPara->pFirstNode == pMergedPara->pLastNode)
+ {
+ assert(pMergedPara->pFirstNode == &rNode);
+ rNode.SetRedlineMergeFlag(SwNode::Merge::None);
+ }
+ break; // checking once is enough
+ }
+ else if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ rNode.SetRedlineMergeFlag(SwNode::Merge::None);
+ break; // checking once is enough
+ }
+ }
+ }
+}
+
+bool HasNumberingWhichNeedsLayoutUpdate(const SwTextNode& rTextNode)
+{
+ const SwNodeNum* pNodeNum = rTextNode.GetNum();
+ if (!pNodeNum)
+ {
+ return false;
+ }
+
+ const SwNumRule* pNumRule = pNodeNum->GetNumRule();
+ if (!pNumRule)
+ {
+ return false;
+ }
+
+ const SwNumFormat* pFormat
+ = pNumRule->GetNumFormat(o3tl::narrowing<sal_uInt16>(rTextNode.GetAttrListLevel()));
+ if (!pFormat)
+ {
+ return false;
+ }
+
+ switch (pFormat->GetNumberingType())
+ {
+ case SVX_NUM_NUMBER_NONE:
+ case SVX_NUM_CHAR_SPECIAL:
+ case SVX_NUM_BITMAP:
+ return false;
+ default:
+ return true;
+ }
+}
+} // namespace
+
+SwContentNode *SwTextNode::JoinNext()
+{
+ SwNodes& rNds = GetNodes();
+ SwNodeIndex aIdx( *this );
+ if( SwContentNode::CanJoinNext( &aIdx ) )
+ {
+ SwDoc& rDoc = rNds.GetDoc();
+ const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
+ pContentStore->Save(rDoc, aIdx.GetIndex(), SAL_MAX_INT32);
+ SwTextNode *pTextNode = aIdx.GetNode().GetTextNode();
+ sal_Int32 nOldLen = m_Text.getLength();
+
+ // METADATA: merge
+ JoinMetadatable(*pTextNode, !Len(), !pTextNode->Len());
+
+ std::unique_ptr<SwWrongList> pList = ReleaseWrong();
+ if( pList )
+ {
+ pList->JoinList( pTextNode->GetWrong(), nOldLen );
+ SetWrongDirty(sw::WrongState::TODO);
+ }
+ else
+ {
+ pList = pTextNode->ReleaseWrong();
+ if( pList )
+ {
+ pList->Move( 0, nOldLen );
+ SetWrongDirty(sw::WrongState::TODO);
+ }
+ }
+
+ std::unique_ptr<SwGrammarMarkUp> pList3 = ReleaseGrammarCheck();
+ if( pList3 )
+ {
+ pList3->JoinGrammarList( pTextNode->GetGrammarCheck(), nOldLen );
+ SetGrammarCheckDirty( true );
+ }
+ else
+ {
+ pList3 = pTextNode->ReleaseGrammarCheck();
+ if( pList3 )
+ {
+ pList3->MoveGrammar( 0, nOldLen );
+ SetGrammarCheckDirty( true );
+ }
+ }
+
+ std::unique_ptr<SwWrongList> pList2 = ReleaseSmartTags();
+ if( pList2 )
+ {
+ pList2->JoinList( pTextNode->GetSmartTags(), nOldLen );
+ SetSmartTagDirty( true );
+ }
+ else
+ {
+ pList2 = pTextNode->ReleaseSmartTags();
+ if( pList2 )
+ {
+ pList2->Move( 0, nOldLen );
+ SetSmartTagDirty( true );
+ }
+ }
+
+ { // scope for SwContentIndex
+ pTextNode->CutText( this, SwContentIndex(pTextNode), pTextNode->Len() );
+ }
+ // move all Bookmarks/TOXMarks
+ if( !pContentStore->Empty())
+ pContentStore->Restore( rDoc, GetIndex(), nOldLen );
+
+ if( pTextNode->HasAnyIndex() )
+ {
+ // move all ShellCursor/StackCursor/UnoCursor out of delete range
+ rDoc.CorrAbs( aIdx.GetNode(), SwPosition( *this ), nOldLen, true );
+ }
+ SwNode::Merge const eOldMergeFlag(pTextNode->GetRedlineMergeFlag());
+ auto eRecreateMerged(eOldMergeFlag == SwNode::Merge::First
+ ? sw::Recreate::ThisNode
+ : sw::Recreate::No);
+ if (eRecreateMerged == sw::Recreate::No)
+ {
+ // tdf#137318 if a delete is inside one node, flag is still None!
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pTextNode);
+ for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ if (pFrame->GetMergedPara())
+ {
+ eRecreateMerged = sw::Recreate::ThisNode;
+ break;
+ }
+ }
+ }
+ bool bOldHasNumberingWhichNeedsLayoutUpdate = HasNumberingWhichNeedsLayoutUpdate(*pTextNode);
+
+ rNds.Delete(aIdx);
+ SetWrong( std::move(pList) );
+ SetGrammarCheck( std::move(pList3) );
+ SetSmartTags( std::move(pList2) );
+
+ resetAndQueueAccessibilityCheck();
+
+ if (bOldHasNumberingWhichNeedsLayoutUpdate || HasNumberingWhichNeedsLayoutUpdate(*this))
+ {
+ // Repaint all text frames that belong to this numbering to avoid outdated generated
+ // numbers.
+ InvalidateNumRule();
+ }
+
+ CheckResetRedlineMergeFlag(*this, eRecreateMerged);
+ }
+ else {
+ OSL_FAIL( "No TextNode." );
+ }
+
+ return this;
+}
+
+void SwTextNode::JoinPrev()
+{
+ SwNodes& rNds = GetNodes();
+ SwNodeIndex aIdx( *this );
+ if( SwContentNode::CanJoinPrev( &aIdx ) )
+ {
+ SwDoc& rDoc = rNds.GetDoc();
+ const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
+ pContentStore->Save( rDoc, aIdx.GetIndex(), SAL_MAX_INT32);
+ SwTextNode *pTextNode = aIdx.GetNode().GetTextNode();
+ const sal_Int32 nLen = pTextNode->Len();
+
+ std::unique_ptr<SwWrongList> pList = pTextNode->ReleaseWrong();
+ if( pList )
+ {
+ pList->JoinList( GetWrong(), Len() );
+ SetWrongDirty(sw::WrongState::TODO);
+ ClearWrong();
+ }
+ else
+ {
+ pList = ReleaseWrong();
+ if( pList )
+ {
+ pList->Move( 0, nLen );
+ SetWrongDirty(sw::WrongState::TODO);
+ }
+ }
+
+ std::unique_ptr<SwGrammarMarkUp> pList3 = pTextNode->ReleaseGrammarCheck();
+ if( pList3 )
+ {
+ pList3->JoinGrammarList( GetGrammarCheck(), Len() );
+ SetGrammarCheckDirty( true );
+ ClearGrammarCheck();
+ }
+ else
+ {
+ pList3 = ReleaseGrammarCheck();
+ if( pList3 )
+ {
+ pList3->MoveGrammar( 0, nLen );
+ SetGrammarCheckDirty( true );
+ }
+ }
+
+ std::unique_ptr<SwWrongList> pList2 = pTextNode->ReleaseSmartTags();
+ if( pList2 )
+ {
+ pList2->JoinList( GetSmartTags(), Len() );
+ SetSmartTagDirty( true );
+ ClearSmartTags();
+ }
+ else
+ {
+ pList2 = ReleaseSmartTags();
+ if( pList2 )
+ {
+ pList2->Move( 0, nLen );
+ SetSmartTagDirty( true );
+ }
+ }
+
+ { // scope for SwContentIndex
+ pTextNode->CutText( this, SwContentIndex(this), SwContentIndex(pTextNode), nLen );
+ }
+ // move all Bookmarks/TOXMarks
+ if( !pContentStore->Empty() )
+ pContentStore->Restore( rDoc, GetIndex() );
+
+ if( pTextNode->HasAnyIndex() )
+ {
+ // move all ShellCursor/StackCursor/UnoCursor out of delete range
+ rDoc.CorrAbs( aIdx.GetNode(), SwPosition( *this ), nLen, true );
+ }
+ SwNode::Merge const eOldMergeFlag(pTextNode->GetRedlineMergeFlag());
+ if (eOldMergeFlag == SwNode::Merge::First
+ && !IsCreateFrameWhenHidingRedlines())
+ {
+ sw::MoveDeletedPrevFrames(*pTextNode, *this);
+ }
+ rNds.Delete(aIdx);
+ SetWrong( std::move(pList) );
+ SetGrammarCheck( std::move(pList3) );
+ SetSmartTags( std::move(pList2) );
+ resetAndQueueAccessibilityCheck();
+ InvalidateNumRule();
+ sw::CheckResetRedlineMergeFlag(*this,
+ eOldMergeFlag == SwNode::Merge::NonFirst
+ ? sw::Recreate::Predecessor
+ : sw::Recreate::No);
+ }
+ else {
+ OSL_FAIL( "No TextNode." );
+ }
+}
+
+// create an AttrSet with ranges for Frame-/Para/Char-attributes
+void SwTextNode::NewAttrSet( SwAttrPool& rPool )
+{
+ OSL_ENSURE( !mpAttrSet, "AttrSet is set after all" );
+ SwAttrSet aNewAttrSet( rPool, aTextNodeSetRange );
+
+ // put names of parent style and conditional style:
+ const SwFormatColl* pAnyFormatColl = &GetAnyFormatColl();
+ const SwFormatColl* pFormatColl = GetFormatColl();
+ OUString sVal;
+ SwStyleNameMapper::FillProgName( pAnyFormatColl->GetName(), sVal, SwGetPoolIdFromName::TxtColl );
+ SfxStringItem aAnyFormatColl( RES_FRMATR_STYLE_NAME, sVal );
+ if ( pFormatColl != pAnyFormatColl )
+ SwStyleNameMapper::FillProgName( pFormatColl->GetName(), sVal, SwGetPoolIdFromName::TxtColl );
+ SfxStringItem aFormatColl( RES_FRMATR_CONDITIONAL_STYLE_NAME, sVal );
+ aNewAttrSet.Put( aAnyFormatColl );
+ aNewAttrSet.Put( aFormatColl );
+
+ aNewAttrSet.SetParent( &pAnyFormatColl->GetAttrSet() );
+ mpAttrSet = GetDoc().GetIStyleAccess().getAutomaticStyle( aNewAttrSet, IStyleAccess::AUTO_STYLE_PARA, &sVal );
+}
+
+namespace
+{
+class SwContentNodeTmp : public SwContentNode
+{
+public:
+ SwContentNodeTmp() : SwContentNode() {}
+ virtual void NewAttrSet(SwAttrPool&) override {}
+ virtual SwContentFrame *MakeFrame(SwFrame*) override { return nullptr; }
+ virtual SwContentNode* MakeCopy(SwDoc&, SwNode&, bool /*bNewFrames*/) const override { return nullptr; };
+};
+};
+
+// override SwContentIndexReg::Update => text hints do not need SwContentIndex for start/end!
+void SwTextNode::Update(
+ SwContentIndex const & rPos,
+ const sal_Int32 nChangeLen,
+ UpdateMode const eMode)
+{
+ assert(rPos.GetContentNode() == this);
+ SetAutoCompleteWordDirty( true );
+
+ std::unique_ptr<SwpHts> pCollector;
+ const sal_Int32 nChangePos = rPos.GetIndex();
+
+ if ( HasHints() )
+ {
+ if (eMode & UpdateMode::Negative)
+ {
+ std::vector<SwTextInputField*> aTextInputFields;
+
+ const sal_Int32 nChangeEnd = nChangePos + nChangeLen;
+ for ( size_t n = 0; n < m_pSwpHints->Count(); ++n )
+ {
+ bool bTextAttrChanged = false;
+ bool bStartOfTextAttrChanged = false;
+ SwTextAttr * const pHint = m_pSwpHints->GetWithoutResorting(n);
+ if ( pHint->GetStart() > nChangePos )
+ {
+ if ( pHint->GetStart() > nChangeEnd )
+ {
+ pHint->SetStart( pHint->GetStart() - nChangeLen );
+ }
+ else
+ {
+ pHint->SetStart( nChangePos );
+ }
+ bStartOfTextAttrChanged = true;
+ }
+
+ const sal_Int32 * const pEnd = pHint->GetEnd();
+ if (pEnd && *pEnd > nChangePos )
+ {
+ if( *pEnd > nChangeEnd )
+ {
+ pHint->SetEnd(*pEnd - nChangeLen);
+ }
+ else
+ {
+ pHint->SetEnd(nChangePos);
+ }
+ bTextAttrChanged = !bStartOfTextAttrChanged;
+ }
+
+ if ( bTextAttrChanged
+ && pHint->Which() == RES_TXTATR_INPUTFIELD )
+ {
+ SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pHint);
+ if ( pTextInputField )
+ aTextInputFields.push_back(pTextInputField);
+ }
+ }
+
+ //wait until all the attribute positions are correct
+ //before updating the field contents
+ for (SwTextInputField* pTextInputField : aTextInputFields)
+ {
+ pTextInputField->UpdateFieldContent();
+ }
+
+ m_pSwpHints->MergePortions( *this );
+ }
+ else
+ {
+ bool bNoExp = false;
+ bool bResort = false;
+ bool bMergePortionsNeeded = false;
+ const int coArrSz = RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN;
+ std::vector<SwTextInputField*> aTextInputFields;
+
+ bool aDontExp[ coArrSz ] = {};
+
+ for ( size_t n = 0; n < m_pSwpHints->Count(); ++n )
+ {
+ bool bTextAttrChanged = false;
+ SwTextAttr * const pHint = m_pSwpHints->GetWithoutResorting(n);
+ const sal_Int32 * const pEnd = pHint->GetEnd();
+ if ( pHint->GetStart() >= nChangePos )
+ {
+ pHint->SetStart( pHint->GetStart() + nChangeLen );
+ if ( pEnd )
+ {
+ pHint->SetEnd(*pEnd + nChangeLen);
+ }
+ }
+ else if ( pEnd && (*pEnd >= nChangePos) )
+ {
+ if ( (*pEnd > nChangePos) || IsIgnoreDontExpand() )
+ {
+ pHint->SetEnd(*pEnd + nChangeLen);
+ bTextAttrChanged = true;
+ }
+ else // *pEnd == nChangePos
+ {
+ const sal_uInt16 nWhich = pHint->Which();
+
+ OSL_ENSURE(!isCHRATR(nWhich), "Update: char attr hint?");
+ if (!(isCHRATR(nWhich) || isTXTATR_WITHEND(nWhich)))
+ continue;
+
+ const sal_uInt16 nWhPos = nWhich - RES_CHRATR_BEGIN;
+
+ if( aDontExp[ nWhPos ] )
+ continue;
+
+ if ( pHint->DontExpand() )
+ {
+ pHint->SetDontExpand( false );
+ bResort = true;
+ // could have a continuation with IgnoreStart()...
+ if (pHint->IsFormatIgnoreEnd())
+ {
+ bMergePortionsNeeded = true;
+ }
+ if ( pHint->IsCharFormatAttr() )
+ {
+ bNoExp = true;
+ aDontExp[ RES_TXTATR_CHARFMT - RES_CHRATR_BEGIN ] = true;
+ aDontExp[ RES_TXTATR_INETFMT - RES_CHRATR_BEGIN ] = true;
+ }
+ else
+ aDontExp[ nWhPos ] = true;
+ }
+ else if( bNoExp )
+ {
+ if (!pCollector)
+ {
+ pCollector.reset( new SwpHts );
+ }
+ auto it = std::find_if(pCollector->begin(), pCollector->end(),
+ [nWhich](const SwTextAttr *pTmp) { return nWhich == pTmp->Which(); });
+ if (it != pCollector->end())
+ {
+ SwTextAttr *pTmp = *it;
+ pCollector->erase( it );
+ SwTextAttr::Destroy( pTmp, GetDoc().GetAttrPool() );
+ }
+ SwTextAttr * const pTmp =
+ MakeTextAttr( GetDoc(),
+ pHint->GetAttr(), nChangePos, nChangePos + nChangeLen);
+ pCollector->push_back( pTmp );
+ }
+ else
+ {
+ pHint->SetEnd(*pEnd + nChangeLen);
+ bTextAttrChanged = true;
+ }
+ }
+ }
+
+ if ( bTextAttrChanged
+ && pHint->Which() == RES_TXTATR_INPUTFIELD )
+ {
+ SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pHint);
+ if ( pTextInputField )
+ aTextInputFields.push_back(pTextInputField);
+ }
+ }
+
+ //wait until all the attribute positions are correct
+ //before updating the field contents
+ for (SwTextInputField* pTextInputField : aTextInputFields)
+ {
+ pTextInputField->UpdateFieldContent();
+ }
+
+ if (bMergePortionsNeeded)
+ {
+ m_pSwpHints->MergePortions(*this); // does Resort too
+ }
+ else if (bResort)
+ {
+ m_pSwpHints->Resort();
+ }
+ }
+ }
+
+ bool bSortMarks = false;
+ SwContentNodeTmp aTmpIdxReg;
+ if (!(eMode & UpdateMode::Negative) && !(eMode & UpdateMode::Delete))
+ {
+ std::vector<SwRangeRedline*> vMyRedlines;
+ // walk the list of SwIndex attached to me and see if any of them are redlines
+ const SwContentIndex* pContentNodeIndex = GetFirstIndex();
+ while (pContentNodeIndex)
+ {
+ SwRangeRedline* pRedl = pContentNodeIndex->GetRedline();
+ if (pRedl)
+ vMyRedlines.push_back(pRedl);
+ pContentNodeIndex = pContentNodeIndex->GetNext();
+ }
+ std::sort(vMyRedlines.begin(), vMyRedlines.end());
+ vMyRedlines.erase( std::unique( vMyRedlines.begin(), vMyRedlines.end() ), vMyRedlines.end() );
+ for (SwRangeRedline* pRedl : vMyRedlines)
+ {
+ if ( pRedl->HasMark() )
+ {
+ SwPosition* const pEnd = pRedl->End();
+ if ( *this == pEnd->GetNode() &&
+ *pRedl->GetPoint() != *pRedl->GetMark() )
+ {
+ SwContentIndex & rIdx = pEnd->nContent;
+ if (nChangePos == rIdx.GetIndex())
+ {
+ rIdx.Assign( &aTmpIdxReg, rIdx.GetIndex() );
+ }
+ }
+ }
+ else if ( this == &pRedl->GetPoint()->GetNode() )
+ {
+ SwContentIndex & rIdx = pRedl->GetPoint()->nContent;
+ if (nChangePos == rIdx.GetIndex())
+ {
+ rIdx.Assign( &aTmpIdxReg, rIdx.GetIndex() );
+ }
+ // the unused position must not be on a SwTextNode
+ bool const isOneUsed(&pRedl->GetBound() == pRedl->GetPoint());
+ assert(!pRedl->GetBound(!isOneUsed).GetNode().IsTextNode());
+ assert(!pRedl->GetBound(!isOneUsed).GetContentNode()); (void)isOneUsed;
+ }
+ }
+
+ // Bookmarks must never grow to either side, when editing (directly)
+ // to the left or right (i#29942)! Exception: if the bookmark has
+ // 2 positions and start == end, then expand it (tdf#96479)
+ if (!(eMode & UpdateMode::Replace)) // Exception: Replace
+ {
+ bool bAtLeastOneBookmarkMoved = false;
+ bool bAtLeastOneExpandedBookmarkAtInsertionPosition = false;
+ // A text node already knows its marks via its SwContentIndexes.
+ o3tl::sorted_vector<const sw::mark::IMark*> aSeenMarks;
+ const SwContentIndex* next;
+ for (const SwContentIndex* pIndex = GetFirstIndex(); pIndex; pIndex = next )
+ {
+ next = pIndex->GetNext();
+ const sw::mark::IMark* pMark = pIndex->GetMark();
+ if (!pMark)
+ continue;
+ // Only handle bookmarks once, if they start and end at this node as well.
+ if (!aSeenMarks.insert(pMark).second)
+ continue;
+ const SwPosition* pEnd = &pMark->GetMarkEnd();
+ SwContentIndex & rEndIdx = const_cast<SwContentIndex&>(pEnd->nContent);
+ if( *this == pEnd->GetNode() &&
+ rPos.GetIndex() == rEndIdx.GetIndex() )
+ {
+ if (&rEndIdx == next) // nasty corner case:
+ { // don't switch to iterating aTmpIdxReg!
+ next = rEndIdx.GetNext();
+ }
+ // tdf#96479: if start == end, ignore the other position
+ // so it is moved!
+ rEndIdx.Assign( &aTmpIdxReg, rEndIdx.GetIndex() );
+ bAtLeastOneBookmarkMoved = true;
+ }
+ else if ( !bAtLeastOneExpandedBookmarkAtInsertionPosition )
+ {
+ if ( pMark->IsExpanded() )
+ {
+ const SwPosition* pStart = &pMark->GetMarkStart();
+ if ( this == &pStart->GetNode()
+ && rPos.GetIndex() == pStart->GetContentIndex() )
+ {
+ bAtLeastOneExpandedBookmarkAtInsertionPosition = true;
+ }
+ }
+ }
+ }
+
+ bSortMarks = bAtLeastOneBookmarkMoved && bAtLeastOneExpandedBookmarkAtInsertionPosition;
+ }
+
+ // at-char anchored flys shouldn't be moved, either.
+ if (!m_bInUndo)
+ {
+ std::vector<SwFrameFormat*> const& rFlys(GetAnchoredFlys());
+ for (size_t i = 0; i != rFlys.size(); ++i)
+ {
+ SwFrameFormat const*const pFormat = rFlys[i];
+ const SwFormatAnchor& rAnchor = pFormat->GetAnchor();
+ const SwNode* pAnchorNode = rAnchor.GetAnchorNode();
+ if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR && pAnchorNode)
+ {
+ // The fly is at-char anchored and has an anchor position.
+ SwContentIndex& rEndIdx = const_cast<SwContentIndex&>(rAnchor.GetContentAnchor()->nContent);
+ if (*pAnchorNode == *this && rEndIdx.GetIndex() == rPos.GetIndex())
+ {
+ // The anchor position is exactly our insert position.
+ rEndIdx.Assign(&aTmpIdxReg, rEndIdx.GetIndex());
+ }
+ }
+ }
+ }
+
+ // The cursors of other shells shouldn't be moved, either.
+ if (SwDocShell* pDocShell = GetDoc().GetDocShell())
+ {
+ if (pDocShell->GetWrtShell())
+ {
+ for (SwViewShell& rShell : pDocShell->GetWrtShell()->GetRingContainer())
+ {
+ auto pWrtShell = dynamic_cast<SwWrtShell*>(&rShell);
+ if (!pWrtShell || pWrtShell == pDocShell->GetWrtShell())
+ continue;
+
+ SwShellCursor* pCursor = pWrtShell->GetCursor_();
+ if (!pCursor)
+ continue;
+
+ SwContentIndex& rIndex = pCursor->Start()->nContent;
+ if (pCursor->Start()->GetNode() == *this && rIndex.GetIndex() == rPos.GetIndex())
+ {
+ // The cursor position of this other shell is exactly our insert position.
+ rIndex.Assign(&aTmpIdxReg, rIndex.GetIndex());
+ }
+ }
+ }
+ }
+ }
+
+ // base class
+ SwContentIndexReg::Update(rPos, nChangeLen, eMode);
+
+ if (pCollector)
+ {
+ const size_t nCount = pCollector->size();
+ for ( size_t i = 0; i < nCount; ++i )
+ {
+ m_pSwpHints->TryInsertHint( (*pCollector)[ i ], *this );
+ }
+ }
+
+ aTmpIdxReg.MoveTo( *this );
+ if ( bSortMarks )
+ {
+ getIDocumentMarkAccess()->assureSortedMarkContainers();
+ }
+
+ //Any drawing objects anchored into this text node may be sorted by their
+ //anchor position which may have changed here, so resort them
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> iter(*this);
+ for (SwTextFrame* pFrame = iter.First(); pFrame; pFrame = iter.Next())
+ {
+ SwSortedObjs * pSortedObjs(pFrame->GetDrawObjs());
+ if (pSortedObjs)
+ {
+ pSortedObjs->UpdateAll();
+ }
+ // also sort the objs on the page frame
+ if (SwPageFrame *pPage = pFrame->FindPageFrame())
+ pSortedObjs = pPage->GetSortedObjs();
+
+ if (pSortedObjs) // doesn't exist yet if called for inserting as-char fly
+ {
+ pSortedObjs->UpdateAll();
+ }
+ }
+
+ // Update the paragraph signatures.
+ if (SwEditShell* pEditShell = GetDoc().GetEditShell())
+ {
+ pEditShell->ValidateParagraphSignatures(this, true);
+ }
+
+ // Inform LOK clients about change in position of redlines (if any)
+ // Don't emit notifications during save: redline flags are temporarily changed during save, but
+ // it's not useful to let clients know about such changes.
+ if (!comphelper::LibreOfficeKit::isActive() || GetDoc().IsInWriting())
+ return;
+
+ const SwRedlineTable& rTable = GetDoc().getIDocumentRedlineAccess().GetRedlineTable();
+ for (SwRedlineTable::size_type nRedlnPos = 0; nRedlnPos < rTable.size(); ++nRedlnPos)
+ {
+ SwRangeRedline* pRedln = rTable[nRedlnPos];
+ if (pRedln->HasMark())
+ {
+ if (*this == pRedln->End()->GetNode() && *pRedln->GetPoint() != *pRedln->GetMark())
+ {
+ // Redline is changed only when some change occurs before it
+ if (nChangePos <= pRedln->Start()->GetContentIndex())
+ {
+ SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedln);
+ }
+ }
+ }
+ else if (this == &pRedln->GetPoint()->GetNode())
+ SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedln);
+ }
+}
+
+void SwTextNode::ChgTextCollUpdateNum( const SwTextFormatColl *pOldColl,
+ const SwTextFormatColl *pNewColl)
+{
+ SwDoc& rDoc = GetDoc();
+ // query the OutlineLevel and if it changed, notify the Nodes-Array!
+ const int nOldLevel = pOldColl && pOldColl->IsAssignedToListLevelOfOutlineStyle() ?
+ pOldColl->GetAssignedOutlineStyleLevel() : MAXLEVEL;
+ const int nNewLevel = pNewColl && pNewColl->IsAssignedToListLevelOfOutlineStyle() ?
+ pNewColl->GetAssignedOutlineStyleLevel() : MAXLEVEL;
+
+ if ( MAXLEVEL != nNewLevel && -1 != nNewLevel )
+ {
+ SetAttrListLevel(nNewLevel);
+ }
+ rDoc.GetNodes().UpdateOutlineNode(*this);
+
+ SwNodes& rNds = GetNodes();
+ // If Level 0 (Chapter), update the footnotes!
+ if( ( !nNewLevel || !nOldLevel) && !rDoc.GetFootnoteIdxs().empty() &&
+ FTNNUM_CHAPTER == rDoc.GetFootnoteInfo().m_eNum &&
+ rNds.IsDocNodes() )
+ {
+ rDoc.GetFootnoteIdxs().UpdateFootnote( *rNds[GetIndex()] );
+ }
+
+ if( pNewColl && RES_CONDTXTFMTCOLL == pNewColl->Which() )
+ {
+ // check the condition of the text node again
+ ChkCondColl();
+ }
+}
+
+// If positioned exactly at the end of a CharStyle or Hyperlink,
+// set its DontExpand flag.
+bool SwTextNode::DontExpandFormat( sal_Int32 nIdx, bool bFlag,
+ bool bFormatToTextAttributes )
+{
+ if (bFormatToTextAttributes && nIdx == m_Text.getLength())
+ {
+ FormatToTextAttr( this );
+ }
+
+ bool bRet = false;
+ if ( HasHints() )
+ {
+ m_pSwpHints->SortIfNeedBe();
+ int nPos = m_pSwpHints->GetLastPosSortedByEnd(nIdx);
+ for ( ; nPos >= 0; --nPos)
+ {
+ SwTextAttr *pTmp = m_pSwpHints->GetSortedByEnd( nPos );
+ const sal_Int32 *pEnd = pTmp->GetEnd();
+ if( !pEnd )
+ continue;
+ assert( *pEnd <= nIdx );
+ if( nIdx != *pEnd )
+ break;
+ if( bFlag != pTmp->DontExpand() && !pTmp->IsLockExpandFlag()
+ && *pEnd > pTmp->GetStart())
+ {
+ bRet = true;
+ m_pSwpHints->NoteInHistory( pTmp );
+ pTmp->SetDontExpand( bFlag );
+ }
+ }
+ }
+ return bRet;
+}
+
+static bool lcl_GetTextAttrDefault(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd)
+{
+ return ((nHintStart <= nIndex) && (nIndex < nHintEnd));
+}
+static bool lcl_GetTextAttrExpand(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd)
+{
+ return ((nHintStart < nIndex) && (nIndex <= nHintEnd));
+}
+static bool lcl_GetTextAttrParent(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd)
+{
+ return ((nHintStart < nIndex) && (nIndex < nHintEnd));
+}
+
+static void
+lcl_GetTextAttrs(
+ std::vector<SwTextAttr *> *const pVector,
+ SwTextAttr **const ppTextAttr,
+ SwpHints const *const pSwpHints,
+ sal_Int32 const nIndex, sal_uInt16 const nWhich,
+ ::sw::GetTextAttrMode const eMode)
+{
+ assert(nWhich >= RES_TXTATR_BEGIN && nWhich < RES_TXTATR_END);
+ if (!pSwpHints)
+ return;
+ size_t const nSize = pSwpHints->Count();
+ sal_Int32 nPreviousIndex(0); // index of last hint with nWhich
+ bool (*pMatchFunc)(sal_Int32, sal_Int32, sal_Int32)=nullptr;
+ switch (eMode)
+ {
+ case ::sw::GetTextAttrMode::Default: pMatchFunc = &lcl_GetTextAttrDefault;
+ break;
+ case ::sw::GetTextAttrMode::Expand: pMatchFunc = &lcl_GetTextAttrExpand;
+ break;
+ case ::sw::GetTextAttrMode::Parent: pMatchFunc = &lcl_GetTextAttrParent;
+ break;
+ default: assert(false);
+ }
+
+ for( size_t i = pSwpHints->GetFirstPosSortedByWhichAndStart(nWhich); i < nSize; ++i )
+ {
+ SwTextAttr *const pHint = pSwpHints->GetSortedByWhichAndStart(i);
+ if (pHint->Which() != nWhich)
+ break; // hints are sorted by which&start, so we are done...
+
+ sal_Int32 const nHintStart = pHint->GetStart();
+ if (nIndex < nHintStart)
+ break; // hints are sorted by which&start, so we are done...
+
+ sal_Int32 const*const pEndIdx = pHint->GetEnd();
+ // cannot have hint with no end and no dummy char
+ assert(pEndIdx || pHint->HasDummyChar());
+ // If EXPAND is set, simulate the text input behavior, i.e.
+ // move the start, and expand the end.
+ bool const bContained( pEndIdx
+ ? (*pMatchFunc)(nIndex, nHintStart, *pEndIdx)
+ : (nHintStart == nIndex) );
+ if (bContained)
+ {
+ if (pVector)
+ {
+ if (nPreviousIndex < nHintStart)
+ {
+ pVector->clear(); // clear hints that are outside pHint
+ nPreviousIndex = nHintStart;
+ }
+ pVector->push_back(pHint);
+ }
+ else
+ {
+ *ppTextAttr = pHint; // and possibly overwrite outer hint
+ }
+ if (!pEndIdx)
+ {
+ break;
+ }
+ }
+ }
+}
+
+std::vector<SwTextAttr *>
+SwTextNode::GetTextAttrsAt(sal_Int32 const nIndex, sal_uInt16 const nWhich) const
+{
+ assert(nWhich >= RES_TXTATR_BEGIN && nWhich < RES_TXTATR_END);
+ std::vector<SwTextAttr *> ret;
+ lcl_GetTextAttrs(&ret, nullptr, m_pSwpHints.get(), nIndex, nWhich, ::sw::GetTextAttrMode::Default);
+ return ret;
+}
+
+SwTextAttr *
+SwTextNode::GetTextAttrAt(sal_Int32 const nIndex, sal_uInt16 const nWhich,
+ ::sw::GetTextAttrMode const eMode) const
+{
+ assert( (nWhich == RES_TXTATR_META)
+ || (nWhich == RES_TXTATR_METAFIELD)
+ || (nWhich == RES_TXTATR_AUTOFMT)
+ || (nWhich == RES_TXTATR_INETFMT)
+ || (nWhich == RES_TXTATR_CJK_RUBY)
+ || (nWhich == RES_TXTATR_UNKNOWN_CONTAINER)
+ || (nWhich == RES_TXTATR_CONTENTCONTROL)
+ || (nWhich == RES_TXTATR_INPUTFIELD ) );
+ // "GetTextAttrAt() will give wrong result for this hint!")
+
+ SwTextAttr * pRet(nullptr);
+ lcl_GetTextAttrs(nullptr, & pRet, m_pSwpHints.get(), nIndex, nWhich, eMode);
+ return pRet;
+}
+
+const SwTextInputField* SwTextNode::GetOverlappingInputField( const SwTextAttr& rTextAttr ) const
+{
+ const SwTextInputField* pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(rTextAttr.GetStart(), RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
+
+ if ( pTextInputField == nullptr && rTextAttr.End() != nullptr )
+ {
+ pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(*(rTextAttr.End()), RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
+ }
+
+ return pTextInputField;
+}
+
+void SwTextNode::DelFrames_TextNodePart()
+{
+ SetWrong( nullptr );
+ SetWrongDirty(sw::WrongState::TODO);
+
+ SetGrammarCheck( nullptr );
+ SetGrammarCheckDirty( true );
+
+ SetSmartTags( nullptr );
+ SetSmartTagDirty( true );
+
+ SetWordCountDirty( true );
+ SetAutoCompleteWordDirty( true );
+}
+
+SwTextField* SwTextNode::GetFieldTextAttrAt(
+ const sal_Int32 nIndex,
+ ::sw::GetTextAttrMode const eMode) const
+{
+ SwTextField* pTextField = dynamic_cast<SwTextField*>(GetTextAttrForCharAt( nIndex, RES_TXTATR_FIELD ));
+ if ( pTextField == nullptr )
+ {
+ pTextField = dynamic_cast<SwTextField*>(GetTextAttrForCharAt( nIndex, RES_TXTATR_ANNOTATION ));
+ }
+ if ( pTextField == nullptr )
+ {
+ pTextField =
+ dynamic_cast<SwTextField*>( GetTextAttrAt(
+ nIndex,
+ RES_TXTATR_INPUTFIELD,
+ eMode));
+ }
+
+ return pTextField;
+}
+
+static SwCharFormat* lcl_FindCharFormat( const SwCharFormats* pCharFormats, std::u16string_view rName )
+{
+ if( !rName.empty() )
+ {
+ const size_t nArrLen = pCharFormats->size();
+ for( size_t i = 1; i < nArrLen; i++ )
+ {
+ SwCharFormat* pFormat = (*pCharFormats)[ i ];
+ if( pFormat->GetName()==rName )
+ return pFormat;
+ }
+ }
+ return nullptr;
+}
+
+static void lcl_CopyHint(
+ const sal_uInt16 nWhich,
+ const SwTextAttr * const pHt,
+ SwTextAttr *const pNewHt,
+ SwDoc *const pOtherDoc,
+ SwTextNode *const pDest )
+{
+ assert(nWhich == pHt->Which()); // wrong hint-id
+ switch( nWhich )
+ {
+ // copy nodesarray section with footnote content
+ case RES_TXTATR_FTN :
+ assert(pDest); // "lcl_CopyHint: no destination text node?"
+ static_cast<const SwTextFootnote*>(pHt)->CopyFootnote( *static_cast<SwTextFootnote*>(pNewHt), *pDest);
+ break;
+
+ // Fields that are copied into different SwDocs must be registered
+ // at their new FieldTypes.
+
+ case RES_TXTATR_FIELD :
+ {
+ if( pOtherDoc != nullptr )
+ {
+ static_txtattr_cast<const SwTextField*>(pHt)->CopyTextField(
+ static_txtattr_cast<SwTextField*>(pNewHt));
+ }
+
+ // Table Formula must be copied relative.
+ const SwFormatField& rField = pHt->GetFormatField();
+ if( SwFieldIds::Table == rField.GetField()->GetTyp()->Which()
+ && static_cast<const SwTableField*>(rField.GetField())->IsIntrnlName())
+ {
+ // convert internal formula to external
+ const SwTableNode* const pDstTableNd =
+ static_txtattr_cast<const SwTextField*>(pHt)->GetTextNode().FindTableNode();
+ if( pDstTableNd )
+ {
+ SwTableField* const pTableField =
+ const_cast<SwTableField*>(static_cast<const SwTableField*>(
+ pNewHt->GetFormatField().GetField()));
+ pTableField->PtrToBoxNm( &pDstTableNd->GetTable() );
+ }
+ }
+ }
+ break;
+
+ case RES_TXTATR_INPUTFIELD :
+ case RES_TXTATR_ANNOTATION :
+ if( pOtherDoc != nullptr )
+ {
+ static_txtattr_cast<const SwTextField*>(pHt)->CopyTextField(
+ static_txtattr_cast<SwTextField*>(pNewHt));
+ }
+ break;
+
+ case RES_TXTATR_TOXMARK :
+ if( pOtherDoc && pDest && pDest->GetpSwpHints()
+ && pDest->GetpSwpHints()->Contains( pNewHt ) )
+ {
+ // ToXMarks that are copied to different SwDocs must register
+ // at their new ToX (sw::BroadcastingModify).
+ static_txtattr_cast<SwTextTOXMark*>(pNewHt)->CopyTOXMark(*pOtherDoc);
+ }
+ break;
+
+ case RES_TXTATR_CHARFMT :
+ // For CharacterStyles, the format must be copied too.
+ if( pDest && pDest->GetpSwpHints()
+ && pDest->GetpSwpHints()->Contains( pNewHt ) )
+ {
+ SwCharFormat* pFormat = pHt->GetCharFormat().GetCharFormat();
+
+ if (pOtherDoc)
+ {
+ pFormat = pOtherDoc->CopyCharFormat( *pFormat );
+ }
+ const_cast<SwFormatCharFormat&>(
+ pNewHt->GetCharFormat() ).SetCharFormat( pFormat );
+ }
+ break;
+ case RES_TXTATR_INETFMT :
+ {
+ // For Hyperlinks, the format must be copied too.
+ if( pOtherDoc && pDest && pDest->GetpSwpHints()
+ && pDest->GetpSwpHints()->Contains( pNewHt ) )
+ {
+ const SwDoc& rDoc = static_txtattr_cast<
+ const SwTextINetFormat*>(pHt)->GetTextNode().GetDoc();
+ const SwCharFormats* pCharFormats = rDoc.GetCharFormats();
+ const SwFormatINetFormat& rFormat = pHt->GetINetFormat();
+ SwCharFormat* pFormat;
+ pFormat = lcl_FindCharFormat( pCharFormats, rFormat.GetINetFormat() );
+ if( pFormat )
+ pOtherDoc->CopyCharFormat( *pFormat );
+ pFormat = lcl_FindCharFormat( pCharFormats, rFormat.GetVisitedFormat() );
+ if( pFormat )
+ pOtherDoc->CopyCharFormat( *pFormat );
+ }
+ //JP 24.04.98: The attribute must point to a text node, so that
+ // the styles can be created.
+ SwTextINetFormat *const pINetHt = static_txtattr_cast<SwTextINetFormat*>(pNewHt);
+ if ( !pINetHt->GetpTextNode() )
+ {
+ pINetHt->ChgTextNode( pDest );
+ }
+
+ //JP 22.10.97: set up link to char style
+ pINetHt->GetCharFormat();
+ break;
+ }
+ case RES_TXTATR_META:
+ case RES_TXTATR_METAFIELD:
+ OSL_ENSURE( pNewHt, "copying Meta should not fail!" );
+ OSL_ENSURE( pDest
+ && (CH_TXTATR_INWORD == pDest->GetText()[pNewHt->GetStart()]),
+ "missing CH_TXTATR?");
+ break;
+ }
+}
+
+/// copy attributes at position nTextStartIdx to node pDest
+// BP 7.6.93: Intentionally copy only attributes _with_ EndIdx!
+// CopyAttr is usually called when attributes are set on a
+// node with no text.
+void SwTextNode::CopyAttr( SwTextNode *pDest, const sal_Int32 nTextStartIdx,
+ const sal_Int32 nOldPos )
+{
+ if ( HasHints() )
+ {
+ SwDoc* const pOtherDoc = (&pDest->GetDoc() != &GetDoc()) ?
+ &pDest->GetDoc() : nullptr;
+
+ for ( size_t i = 0; i < m_pSwpHints->Count(); ++i )
+ {
+ SwTextAttr *const pHt = m_pSwpHints->Get(i);
+ sal_Int32 const nAttrStartIdx = pHt->GetStart();
+ if ( nTextStartIdx < nAttrStartIdx )
+ break; // beyond end of text, because nLen == 0
+
+ const sal_Int32 *const pEndIdx = pHt->GetEnd();
+ if ( pEndIdx && !pHt->HasDummyChar() )
+ {
+ sal_uInt16 const nWhich = pHt->Which();
+ if (RES_TXTATR_INPUTFIELD != nWhich // fdo#74981 skip fields
+ && ( *pEndIdx > nTextStartIdx
+ || (*pEndIdx == nTextStartIdx
+ && nAttrStartIdx == nTextStartIdx)))
+ {
+ if ( RES_TXTATR_REFMARK != nWhich )
+ {
+ // attribute in the area => copy
+ SwTextAttr *const pNewHt =
+ pDest->InsertItem( pHt->GetAttr(), nOldPos, nOldPos, SetAttrMode::IS_COPY);
+ if ( pNewHt )
+ {
+ lcl_CopyHint( nWhich, pHt, pNewHt,
+ pOtherDoc, pDest );
+ }
+ }
+ else if( !pOtherDoc
+ ? GetDoc().IsCopyIsMove()
+ : nullptr == pOtherDoc->GetRefMark( pHt->GetRefMark().GetRefName() ) )
+ {
+ pDest->InsertItem(
+ pHt->GetAttr(), nOldPos, nOldPos, SetAttrMode::IS_COPY);
+ }
+ }
+ }
+ }
+ }
+
+ if( this != pDest )
+ {
+ // notify layout frames, to prevent disappearance of footnote numbers
+ SwUpdateAttr aHint(
+ nOldPos,
+ nOldPos,
+ 0);
+
+ pDest->TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
+ }
+}
+
+/// copy text and attributes to node pDest
+void SwTextNode::CopyText( SwTextNode *const pDest,
+ const SwContentIndex &rStart,
+ const sal_Int32 nLen,
+ const bool bForceCopyOfAllAttrs )
+{
+ SwContentIndex const aIdx( pDest, pDest->m_Text.getLength() );
+ CopyText( pDest, aIdx, rStart, nLen, bForceCopyOfAllAttrs );
+}
+
+void SwTextNode::CopyText( SwTextNode *const pDest,
+ const SwContentIndex &rDestStart,
+ const SwPosition &rStart,
+ sal_Int32 nLen,
+ const bool bForceCopyOfAllAttrs )
+{
+ CopyText( pDest, rDestStart, rStart.nContent, nLen, bForceCopyOfAllAttrs );
+}
+
+void SwTextNode::CopyText( SwTextNode *const pDest,
+ const SwContentIndex &rDestStart,
+ const SwContentIndex &rStart,
+ sal_Int32 nLen,
+ const bool bForceCopyOfAllAttrs )
+{
+ CHECK_SWPHINTS_IF_FRM(this);
+ CHECK_SWPHINTS(pDest);
+ assert(rDestStart.GetContentNode() == pDest);
+ assert(rStart.GetContentNode() == this);
+ sal_Int32 nTextStartIdx = rStart.GetIndex();
+ sal_Int32 nDestStart = rDestStart.GetIndex(); // remember old Pos
+
+ if (pDest->GetDoc().IsClipBoard() && GetNum())
+ {
+ // #i111677# cache expansion of source (for clipboard)
+ pDest->m_oNumStringCache = (nTextStartIdx != 0)
+ ? OUString() // fdo#49076: numbering only if copy from para start
+ : GetNumString();
+ }
+
+ if( !nLen )
+ {
+ // if no length is given, copy attributes at position rStart
+ CopyAttr( pDest, nTextStartIdx, nDestStart );
+
+ // copy hard attributes on whole paragraph
+ if( HasSwAttrSet() )
+ {
+ // i#96213 all or just the Char attributes?
+ if ( !bForceCopyOfAllAttrs &&
+ ( nDestStart ||
+ pDest->HasSwAttrSet() ||
+ nLen != pDest->GetText().getLength()))
+ {
+ SfxItemSetFixed<
+ RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
+ RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
+ RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>
+ aCharSet( pDest->GetDoc().GetAttrPool() );
+ aCharSet.Put( *GetpSwAttrSet() );
+ if( aCharSet.Count() )
+ {
+ pDest->SetAttr( aCharSet, nDestStart, nDestStart );
+ }
+ }
+ else
+ {
+ GetpSwAttrSet()->CopyToModify( *pDest );
+ }
+ }
+ return;
+ }
+
+ // 1. copy text
+ const sal_Int32 oldLen = pDest->m_Text.getLength();
+ // JP 15.02.96: missing attribute handling at the end!
+ // hence call InsertText and don't modify m_Text directly
+ pDest->InsertText( m_Text.copy(nTextStartIdx, nLen), rDestStart,
+ SwInsertFlags::EMPTYEXPAND );
+
+ // update with actual new size
+ nLen = pDest->m_Text.getLength() - oldLen;
+ if ( !nLen ) // string not longer?
+ return;
+
+ SwDoc* const pOtherDoc = (&pDest->GetDoc() != &GetDoc()) ? &pDest->GetDoc() : nullptr;
+
+ // copy hard attributes on whole paragraph
+ if( HasSwAttrSet() )
+ {
+ // i#96213 all or just the Char attributes?
+ if ( !bForceCopyOfAllAttrs &&
+ ( nDestStart ||
+ pDest->HasSwAttrSet() ||
+ nLen != pDest->GetText().getLength()))
+ {
+ SfxItemSetFixed<
+ RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
+ RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
+ RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>
+ aCharSet( pDest->GetDoc().GetAttrPool() );
+ aCharSet.Put( *GetpSwAttrSet() );
+ if( aCharSet.Count() )
+ {
+ pDest->SetAttr( aCharSet, nDestStart, nDestStart + nLen );
+ }
+ }
+ else
+ {
+ GetpSwAttrSet()->CopyToModify( *pDest );
+ }
+ }
+
+ bool const bUndoNodes = !pOtherDoc
+ && GetDoc().GetIDocumentUndoRedo().IsUndoNodes(GetNodes());
+
+ // Fetch end only now, because copying into self updates the start index
+ // and all attributes
+ nTextStartIdx = rStart.GetIndex();
+ const sal_Int32 nEnd = nTextStartIdx + nLen;
+
+ // 2. copy attributes
+ // Iterate over attribute array until the start of the attribute
+ // is behind the copied range
+ const size_t nSize = m_pSwpHints ? m_pSwpHints->Count() : 0;
+
+ // If copying into self, inserting can delete attributes!
+ // Hence first copy into temp-array, and then move that into hints array.
+ SwpHts aArr;
+
+ // Del-Array for all RefMarks without extent
+ SwpHts aRefMrkArr;
+
+ std::vector<std::pair<sal_Int32, sal_Int32>> metaFieldRanges;
+ sal_Int32 nDeletedDummyChars(0);
+ for (size_t n = 0; n < nSize; ++n)
+ {
+ SwTextAttr * const pHt = m_pSwpHints->Get(n);
+
+ const sal_Int32 nAttrStartIdx = pHt->GetStart();
+ if ( nAttrStartIdx >= nEnd )
+ break;
+
+ const sal_Int32 * const pEndIdx = pHt->GetEnd();
+ const sal_uInt16 nWhich = pHt->Which();
+
+ // JP 26.04.94: RefMarks are never copied. If the refmark doesn't have
+ // an extent, there is a dummy char in the text, which
+ // must be removed. So we first copy the attribute,
+ // but remember it, and when we're done delete it,
+ // which also deletes the dummy character!
+ // JP 14.08.95: May RefMarks be moved?
+ const bool bCopyRefMark = RES_TXTATR_REFMARK == nWhich
+ && ( bUndoNodes
+ || ( !pOtherDoc
+ ? GetDoc().IsCopyIsMove()
+ : nullptr == pOtherDoc->GetRefMark( pHt->GetRefMark().GetRefName() ) ) );
+
+ if ( pEndIdx
+ && RES_TXTATR_REFMARK == nWhich
+ && !bCopyRefMark )
+ {
+ continue;
+ }
+
+ // Input Fields are only copied, if completely covered by copied text
+ if ( nWhich == RES_TXTATR_INPUTFIELD )
+ {
+ assert(pEndIdx != nullptr &&
+ "<SwTextNode::CopyText(..)> - RES_TXTATR_INPUTFIELD without EndIndex!" );
+ if ( nAttrStartIdx < nTextStartIdx
+ || ( pEndIdx != nullptr
+ && *pEndIdx > nEnd ) )
+ {
+ continue;
+ }
+ }
+
+ if (nWhich == RES_TXTATR_METAFIELD)
+ {
+ // Skip metadata fields. Also remember the range to strip the text later.
+ metaFieldRanges.emplace_back(nAttrStartIdx, pEndIdx ? *pEndIdx : nEnd);
+ continue;
+ }
+
+ sal_Int32 nAttrStt = 0;
+ sal_Int32 nAttrEnd = 0;
+
+ if( nAttrStartIdx < nTextStartIdx )
+ {
+ // start is before selection
+ // copy hints with end and CH_TXTATR only if dummy char is copied
+ if ( pEndIdx && (*pEndIdx > nTextStartIdx) && !pHt->HasDummyChar() )
+ {
+ // attribute with extent and the end is in the selection
+ nAttrStt = nDestStart;
+ nAttrEnd = (*pEndIdx > nEnd)
+ ? rDestStart.GetIndex()
+ : nDestStart + (*pEndIdx) - nTextStartIdx;
+ }
+ else
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // start is in the selection
+ nAttrStt = nDestStart + ( nAttrStartIdx - nTextStartIdx );
+ if( pEndIdx )
+ {
+ nAttrEnd = *pEndIdx > nEnd
+ ? rDestStart.GetIndex()
+ : nDestStart + ( *pEndIdx - nTextStartIdx );
+ }
+ else
+ {
+ nAttrEnd = nAttrStt;
+ }
+ }
+
+ SwTextAttr * pNewHt = nullptr;
+
+ if( pDest == this )
+ {
+ // copy the hint here, but insert it later
+ pNewHt = MakeTextAttr( GetDoc(), pHt->GetAttr(),
+ nAttrStt, nAttrEnd, CopyOrNewType::Copy, pDest );
+
+ lcl_CopyHint(nWhich, pHt, pNewHt, nullptr, pDest);
+ aArr.push_back( pNewHt );
+ }
+ else
+ {
+ pNewHt = pDest->InsertItem(
+ pHt->GetAttr(),
+ nAttrStt - nDeletedDummyChars,
+ nAttrEnd - nDeletedDummyChars,
+ SetAttrMode::NOTXTATRCHR | SetAttrMode::IS_COPY);
+ if (pNewHt)
+ {
+ lcl_CopyHint( nWhich, pHt, pNewHt, pOtherDoc, pDest );
+ }
+ else if (pHt->HasDummyChar())
+ {
+ // The attribute that has failed to be copied would insert
+ // dummy char, so positions of the following attributes have
+ // to be shifted by one to compensate for that missing char.
+ ++nDeletedDummyChars;
+ }
+ }
+
+ if( RES_TXTATR_REFMARK == nWhich && !pEndIdx && !bCopyRefMark )
+ {
+ aRefMrkArr.push_back( pNewHt );
+ }
+ }
+
+ // Strip the metadata fields, since we don't copy the RDF entries
+ // yet and so they are inconsistent upon copy/pasting.
+ if (!metaFieldRanges.empty())
+ {
+ // Reverse to remove without messing the offsets.
+ std::reverse(metaFieldRanges.begin(), metaFieldRanges.end());
+ for (const auto& pair : metaFieldRanges)
+ {
+ const SwContentIndex aIdx(pDest, pair.first);
+ pDest->EraseText(aIdx, pair.second - pair.first);
+ }
+ }
+
+ // this can only happen when copying into self
+ for (SwTextAttr* i : aArr)
+ {
+ InsertHint( i, SetAttrMode::NOTXTATRCHR );
+ }
+
+ if( pDest->GetpSwpHints() )
+ {
+ for (SwTextAttr* pNewHt : aRefMrkArr)
+ {
+ if( pNewHt->GetEnd() )
+ {
+ pDest->GetpSwpHints()->Delete( pNewHt );
+ pDest->DestroyAttr( pNewHt );
+ }
+ else
+ {
+ const SwContentIndex aIdx( pDest, pNewHt->GetStart() );
+ pDest->EraseText( aIdx, 1 );
+ }
+ }
+ }
+
+ CHECK_SWPHINTS_IF_FRM(this);
+ CHECK_SWPHINTS(pDest);
+}
+
+OUString SwTextNode::InsertText( const OUString & rStr, const SwPosition & rIdx,
+ const SwInsertFlags nMode )
+{
+ return InsertText(rStr, rIdx.nContent, nMode);
+}
+
+OUString SwTextNode::InsertText( const OUString & rStr, const SwContentIndex & rIdx,
+ const SwInsertFlags nMode )
+{
+ assert(rIdx.GetContentNode() == this);
+ assert(rIdx <= m_Text.getLength()); // invalid index
+
+ const sal_Int32 aPos = rIdx.GetIndex();
+ sal_Int32 nLen = m_Text.getLength() - aPos;
+ sal_Int32 const nOverflow(rStr.getLength() - GetSpaceLeft());
+ SAL_WARN_IF(nOverflow > 0, "sw.core",
+ "SwTextNode::InsertText: node text with insertion > capacity.");
+ OUString const sInserted(
+ (nOverflow > 0) ? rStr.copy(0, rStr.getLength() - nOverflow) : rStr);
+ if (sInserted.isEmpty())
+ {
+ return sInserted;
+ }
+ if (aPos == 0 && m_Text.isEmpty())
+ m_Text = sInserted;
+ else
+ m_Text = m_Text.replaceAt(aPos, 0, sInserted);
+ assert(GetSpaceLeft()>=0);
+ nLen = m_Text.getLength() - aPos - nLen;
+ assert(nLen != 0);
+
+ bool bOldExpFlg = IsIgnoreDontExpand();
+ if (nMode & SwInsertFlags::FORCEHINTEXPAND)
+ {
+ SetIgnoreDontExpand( true );
+ }
+
+ Update(rIdx, nLen, UpdateMode::Default); // text content changed!
+
+ if (nMode & SwInsertFlags::FORCEHINTEXPAND)
+ {
+ SetIgnoreDontExpand( bOldExpFlg );
+ }
+
+ if ( HasWriterListeners() )
+ { // send this before messing with hints, which will send RES_UPDATE_ATTR
+ auto aInsHint = sw::MakeInsertText(*this, aPos, nLen);
+ CallSwClientNotify(aInsHint);
+ }
+
+ if ( HasHints() )
+ {
+ m_pSwpHints->SortIfNeedBe();
+ bool const bHadHints(!m_pSwpHints->CanBeDeleted());
+ bool bMergePortionsNeeded(false);
+ for ( size_t i = 0; i < m_pSwpHints->Count() &&
+ rIdx >= m_pSwpHints->GetWithoutResorting(i)->GetStart(); ++i )
+ {
+ SwTextAttr * const pHt = m_pSwpHints->GetWithoutResorting( i );
+ const sal_Int32 * const pEndIdx = pHt->GetEnd();
+ if( !pEndIdx )
+ continue;
+
+ if( rIdx == *pEndIdx )
+ {
+ if ( (nMode & SwInsertFlags::NOHINTEXPAND) ||
+ (!(nMode & SwInsertFlags::FORCEHINTEXPAND)
+ && pHt->DontExpand()) )
+ {
+ m_pSwpHints->DeleteAtPos(i);
+ // on empty attributes also adjust Start
+ if( rIdx == pHt->GetStart() )
+ pHt->SetStart( pHt->GetStart() - nLen );
+ pHt->SetEnd(*pEndIdx - nLen);
+ // could be that pHt has IsFormatIgnoreEnd set, and it's
+ // not a RSID-only hint - now we have the inserted text
+ // between pHt and its continuation... which we don't know.
+ // punt the job to MergePortions below.
+ if (pHt->IsFormatIgnoreEnd())
+ {
+ bMergePortionsNeeded = true;
+ }
+ InsertHint( pHt, SetAttrMode::NOHINTADJUST );
+ }
+ // empty hints at insert position?
+ else if ( (nMode & SwInsertFlags::EMPTYEXPAND)
+ && (*pEndIdx == pHt->GetStart()) )
+ {
+ m_pSwpHints->DeleteAtPos(i);
+ pHt->SetStart( pHt->GetStart() - nLen );
+ const size_t nCurrentLen = m_pSwpHints->Count();
+ InsertHint( pHt/* AUTOSTYLES:, SetAttrMode::NOHINTADJUST*/ );
+ if ( nCurrentLen > m_pSwpHints->Count() && i )
+ {
+ --i;
+ }
+ continue;
+ }
+ else
+ {
+ continue;
+ }
+ }
+ if ( !(nMode & SwInsertFlags::NOHINTEXPAND) &&
+ rIdx == nLen && pHt->GetStart() == rIdx.GetIndex() &&
+ !pHt->IsDontExpandStartAttr() )
+ {
+ // no field, at paragraph start, HintExpand
+ m_pSwpHints->DeleteAtPos(i);
+ pHt->SetStart( pHt->GetStart() - nLen );
+ // no effect on format ignore flags here (para start)
+ InsertHint( pHt, SetAttrMode::NOHINTADJUST );
+ }
+ }
+ if (bMergePortionsNeeded)
+ {
+ m_pSwpHints->MergePortions(*this);
+ }
+ SAL_WARN_IF(bHadHints && m_pSwpHints->CanBeDeleted(), "sw.core",
+ "SwTextNode::InsertText: unexpected loss of hints");
+ }
+
+ // By inserting a character, the hidden flags
+ // at the TextNode can become invalid:
+ SetCalcHiddenCharFlags();
+
+ CHECK_SWPHINTS(this);
+ return sInserted;
+}
+
+void SwTextNode::CutText( SwTextNode * const pDest,
+ const SwContentIndex & rStart, const sal_Int32 nLen )
+{
+ assert(pDest); // Cut requires a destination
+ SwContentIndex aDestStt(pDest, pDest->GetText().getLength());
+ CutImpl( pDest, aDestStt, rStart, nLen, false );
+}
+
+void SwTextNode::CutImpl( SwTextNode * const pDest, const SwContentIndex & rDestStart,
+ const SwContentIndex & rStart, sal_Int32 nLen, const bool bUpdate )
+{
+ assert(pDest); // Cut requires a destination
+
+ assert(&GetDoc() == &pDest->GetDoc()); // must be same document
+
+ assert(pDest != this); // destination must be different node
+
+ assert(rDestStart.GetContentNode() == pDest);
+ assert(rStart.GetContentNode() == this);
+
+ if( !nLen )
+ {
+ // if no length is given, copy attributes at position rStart
+ CopyAttr( pDest, rStart.GetIndex(), rDestStart.GetIndex() );
+ return;
+ }
+
+ sal_Int32 nTextStartIdx = rStart.GetIndex();
+ sal_Int32 nDestStart = rDestStart.GetIndex(); // remember old Pos
+ const sal_Int32 nInitSize = pDest->m_Text.getLength();
+
+ if (pDest->GetSpaceLeft() < nLen)
+ { // FIXME: could only happen when called from SwRangeRedline::Show.
+ // unfortunately can't really do anything here to handle that...
+ abort();
+ }
+ pDest->m_Text = pDest->m_Text.replaceAt(nDestStart, 0,
+ m_Text.subView(nTextStartIdx, nLen));
+ OUString const newText = m_Text.replaceAt(nTextStartIdx, nLen, u"");
+ nLen = pDest->m_Text.getLength() - nInitSize; // update w/ current size!
+ if (!nLen) // String didn't grow?
+ return;
+
+ if (bUpdate)
+ {
+ // Update all SwContentIndex
+ pDest->Update(rDestStart, nLen, UpdateMode::Default);
+ }
+
+ CHECK_SWPHINTS(pDest);
+
+ const sal_Int32 nEnd = rStart.GetIndex() + nLen;
+ bool const bUndoNodes =
+ GetDoc().GetIDocumentUndoRedo().IsUndoNodes(GetNodes());
+
+ // copy hard attributes on whole paragraph
+ if (HasSwAttrSet())
+ {
+ bool hasSwAttrSet = pDest->HasSwAttrSet();
+ if (hasSwAttrSet)
+ {
+ // if we have our own property set it doesn't mean
+ // that this set defines any style different to Standard one.
+ hasSwAttrSet = false;
+
+ // so, let's check deeper if property set has defined any property
+ if (pDest->GetpSwAttrSet())
+ {
+ // check all items in the property set
+ SfxItemIter aIter( *pDest->GetpSwAttrSet() );
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ do
+ {
+ // check current item
+ const sal_uInt16 nWhich = IsInvalidItem( pItem )
+ ? pDest->GetpSwAttrSet()->GetWhichByOffset( aIter.GetCurPos() )
+ : pItem->Which();
+ if( RES_FRMATR_STYLE_NAME != nWhich &&
+ RES_FRMATR_CONDITIONAL_STYLE_NAME != nWhich &&
+ RES_PAGEDESC != nWhich &&
+ RES_BREAK != nWhich &&
+ SfxItemState::SET == pDest->GetpSwAttrSet()->GetItemState( nWhich, false ) )
+ {
+ // check if parent value (original value in style) has the same value as in [pItem]
+ const SfxPoolItem& rParentItem = pDest->GetpSwAttrSet()->GetParent()->Get( nWhich, true );
+
+ hasSwAttrSet = (rParentItem != *pItem);
+
+ // property set is not empty => no need to make anymore checks
+ if (hasSwAttrSet)
+ break;
+ }
+
+ // let's check next item
+ pItem = aIter.NextItem();
+ } while (pItem);
+ }
+ }
+
+ // all or just the Char attributes?
+ if( nInitSize || hasSwAttrSet ||
+ nLen != pDest->GetText().getLength())
+ {
+ SfxItemSetFixed<
+ RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
+ RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
+ RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>
+ aCharSet( pDest->GetDoc().GetAttrPool() );
+ aCharSet.Put( *GetpSwAttrSet() );
+ if( aCharSet.Count() )
+ pDest->SetAttr( aCharSet, nDestStart, nDestStart + nLen );
+ }
+ else
+ {
+ // Copy all attrs except RES_PARATR_LIST_LEVEL: it was initialized before
+ // and current SwTextNode can contain not suitable for pDest value
+ SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_LEVEL - 1,
+ RES_PARATR_LIST_LEVEL + 1, HINT_END>
+ aCharSet(pDest->GetDoc().GetAttrPool());
+ aCharSet.Put(*GetpSwAttrSet());
+ if (aCharSet.Count())
+ pDest->SetAttr(aCharSet, nDestStart, nDestStart + nLen);
+ }
+ }
+
+ // notify frames - before moving hints, because footnotes
+ // want to find their anchor text frame in the follow chain
+ // (also ignore fieldmarks, the caller will recreate frames)
+ const sw::InsertText aInsHint(nDestStart, nLen, false, false);
+ pDest->HandleNonLegacyHint(aInsHint);
+ const sw::MoveText aMoveHint(pDest, nDestStart, nTextStartIdx, nLen);
+ CallSwClientNotify(aMoveHint);
+ const sw::DeleteText aDelText(nTextStartIdx, nLen);
+ HandleNonLegacyHint(aDelText);
+
+ // 2. move attributes
+ // Iterate over attribute array until the start of the attribute
+ // is behind the moved range
+ bool bMergePortionsNeeded(false);
+ size_t nAttrCnt = 0;
+ while (m_pSwpHints && (nAttrCnt < m_pSwpHints->Count()))
+ {
+ SwTextAttr * const pHt = m_pSwpHints->Get(nAttrCnt);
+ const sal_Int32 nAttrStartIdx = pHt->GetStart();
+ if ( nAttrStartIdx >= nEnd )
+ break;
+ const sal_Int32 * const pEndIdx = pHt->GetEnd();
+ const sal_uInt16 nWhich = pHt->Which();
+ SwTextAttr *pNewHt = nullptr;
+
+ // if the hint has a dummy character, then it must not be split!
+ if(nAttrStartIdx < nTextStartIdx)
+ {
+ // start is before the range
+ if (!pHt->HasDummyChar() && ( RES_TXTATR_REFMARK != nWhich
+ || bUndoNodes ) && pEndIdx && *pEndIdx > nTextStartIdx)
+ {
+ // attribute with extent and end of attribute is in the range
+ pNewHt = MakeTextAttr( pDest->GetDoc(), pHt->GetAttr(),
+ nDestStart,
+ nDestStart + (
+ *pEndIdx > nEnd
+ ? nLen
+ : *pEndIdx - nTextStartIdx ) );
+ }
+ }
+ else
+ {
+ // start is inside the range
+ if (!pEndIdx || *pEndIdx < nEnd ||
+ (!bUndoNodes && RES_TXTATR_REFMARK == nWhich)
+ || pHt->HasDummyChar() )
+ {
+ // do not delete note and later add it -> sidebar flickering
+ if (GetDoc().GetDocShell())
+ {
+ GetDoc().GetDocShell()->Broadcast( SfxHint(SfxHintId::SwSplitNodeOperation));
+ }
+ // move attribute
+ m_pSwpHints->Delete( pHt );
+ // reset start/end indexes
+ if (pHt->IsFormatIgnoreStart() || pHt->IsFormatIgnoreEnd())
+ {
+ bMergePortionsNeeded = true;
+ }
+ pHt->SetStart(nDestStart + (nAttrStartIdx - nTextStartIdx));
+ if (pEndIdx)
+ {
+ pHt->SetEnd( nDestStart + (
+ *pEndIdx > nEnd
+ ? nLen
+ : *pEndIdx - nTextStartIdx ) );
+ }
+ pDest->InsertHint( pHt,
+ SetAttrMode::NOTXTATRCHR
+ | SetAttrMode::DONTREPLACE );
+ if (GetDoc().GetDocShell())
+ {
+ GetDoc().GetDocShell()->Broadcast( SfxHint(SfxHintId::SwSplitNodeOperation));
+ }
+ continue; // iterate while loop, no ++ !
+ }
+ // the end is behind the range
+ else if (RES_TXTATR_REFMARK != nWhich || bUndoNodes)
+ {
+ pNewHt = MakeTextAttr( GetDoc(), pHt->GetAttr(),
+ nDestStart + (nAttrStartIdx - nTextStartIdx),
+ nDestStart + (*pEndIdx > nEnd
+ ? nLen
+ : *pEndIdx - nTextStartIdx));
+ }
+ }
+ if (pNewHt)
+ {
+ const bool bSuccess( pDest->InsertHint( pNewHt,
+ SetAttrMode::NOTXTATRCHR
+ | SetAttrMode::DONTREPLACE
+ | SetAttrMode::IS_COPY) );
+ if (bSuccess)
+ {
+ lcl_CopyHint( nWhich, pHt, pNewHt, nullptr, pDest );
+ }
+ }
+ ++nAttrCnt;
+ }
+ // If there are still empty attributes around, they have a higher priority
+ // than any attributes that become empty due to the move.
+ // So temporarily remove them and Update the array, then re-insert the
+ // removed ones so they overwrite the others.
+ if (m_pSwpHints && nAttrCnt < m_pSwpHints->Count())
+ {
+ SwpHts aArr;
+ while (nAttrCnt < m_pSwpHints->Count())
+ {
+ SwTextAttr * const pHt = m_pSwpHints->Get(nAttrCnt);
+ if (nEnd != pHt->GetStart())
+ break;
+ const sal_Int32 * const pEndIdx = pHt->GetEnd();
+ if (pEndIdx && *pEndIdx == nEnd)
+ {
+ aArr.push_back( pHt );
+ m_pSwpHints->Delete( pHt );
+ }
+ else
+ {
+ ++nAttrCnt;
+ }
+ }
+ Update(rStart, nLen, UpdateMode::Negative|UpdateMode::Delete);
+
+ for (SwTextAttr* pHt : aArr)
+ {
+ pHt->SetStart( rStart.GetIndex() );
+ pHt->SetEnd( rStart.GetIndex() );
+ InsertHint( pHt );
+ }
+ }
+ else
+ {
+ Update(rStart, nLen, UpdateMode::Negative|UpdateMode::Delete);
+ }
+
+ // set after moving hints
+ m_Text = newText;
+
+ if (bMergePortionsNeeded)
+ {
+ m_pSwpHints->MergePortions(*this);
+ }
+
+ CHECK_SWPHINTS(this);
+
+ TryDeleteSwpHints();
+}
+
+void SwTextNode::EraseText(const SwPosition &rIdx, const sal_Int32 nCount,
+ const SwInsertFlags nMode )
+{
+ EraseText(rIdx.nContent, nCount, nMode);
+}
+
+void SwTextNode::EraseText(const SwContentIndex &rIdx, const sal_Int32 nCount,
+ const SwInsertFlags nMode )
+{
+ assert(rIdx.GetContentNode() == this);
+ assert(rIdx <= m_Text.getLength()); // invalid index
+
+ const sal_Int32 nStartIdx = rIdx.GetIndex();
+ const sal_Int32 nCnt = (nCount==SAL_MAX_INT32)
+ ? m_Text.getLength() - nStartIdx : nCount;
+ const sal_Int32 nEndIdx = nStartIdx + nCnt;
+ if (nEndIdx <= m_Text.getLength())
+ m_Text = m_Text.replaceAt(nStartIdx, nCnt, u"");
+
+ // GCAttr(); don't remove all empty ones, just the ones that are in the
+ // range but not at the end of the range.
+
+ for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i )
+ {
+ SwTextAttr *pHt = m_pSwpHints->Get(i);
+
+ const sal_Int32 nHintStart = pHt->GetStart();
+
+ if ( nHintStart < nStartIdx )
+ continue;
+
+ if ( nHintStart > nEndIdx )
+ break; // hints are sorted by end, so break here
+
+ const sal_Int32* pHtEndIdx = pHt->GetEnd();
+ const sal_uInt16 nWhich = pHt->Which();
+
+ if( !pHtEndIdx )
+ {
+ // attribute with neither end nor CH_TXTATR?
+ assert(pHt->HasDummyChar());
+ if (isTXTATR(nWhich) && (nHintStart < nEndIdx))
+ {
+ m_pSwpHints->DeleteAtPos(i);
+ DestroyAttr( pHt );
+ --i;
+ }
+ continue;
+ }
+
+ assert(!( (nHintStart < nEndIdx) && (*pHtEndIdx > nEndIdx)
+ && pHt->HasDummyChar() )
+ // next line: deleting exactly dummy char: DeleteAttributes
+ || ((nHintStart == nStartIdx) && (nHintStart + 1 == nEndIdx)));
+ // "ERROR: deleting left-overlapped attribute with CH_TXTATR");
+
+ // Delete the hint if:
+ // 1. The hint ends before the deletion end position or
+ // 2. The hint ends at the deletion end position and
+ // we are not in empty expand mode and
+ // the hint is a [toxmark|refmark|ruby|inputfield] text attribute
+ // 3. deleting exactly the dummy char of an hint with end and dummy
+ // char deletes the hint
+ if ( (*pHtEndIdx < nEndIdx)
+ || ( (*pHtEndIdx == nEndIdx) &&
+ !(SwInsertFlags::EMPTYEXPAND & nMode) &&
+ ( (RES_TXTATR_TOXMARK == nWhich) ||
+ (RES_TXTATR_REFMARK == nWhich) ||
+ (RES_TXTATR_CJK_RUBY == nWhich) ||
+ (RES_TXTATR_INPUTFIELD == nWhich) ) )
+ || ( (nHintStart < nEndIdx) &&
+ pHt->HasDummyChar() )
+ )
+ {
+ m_pSwpHints->DeleteAtPos(i);
+ DestroyAttr( pHt );
+ --i;
+ }
+ }
+
+ OSL_ENSURE(rIdx.GetIndex() == nStartIdx, "huh? start index has changed?");
+
+ TryDeleteSwpHints();
+
+ Update(rIdx, nCnt, UpdateMode::Negative);
+
+ if(1 == nCnt)
+ {
+ const auto aHint = sw::DeleteChar(nStartIdx);
+ CallSwClientNotify(aHint);
+ } else {
+ const auto aHint = sw::DeleteText(nStartIdx, nCnt);
+ CallSwClientNotify(aHint);
+ }
+
+ OSL_ENSURE(rIdx.GetIndex() == nStartIdx, "huh? start index has changed?");
+
+ // By deleting a character, the hidden flags
+ // at the TextNode can become invalid:
+ SetCalcHiddenCharFlags();
+
+ CHECK_SWPHINTS(this);
+}
+
+void SwTextNode::GCAttr()
+{
+ if ( !HasHints() )
+ return;
+
+ bool bChanged = false;
+ sal_Int32 nMin = m_Text.getLength();
+ sal_Int32 nMax = 0;
+ const bool bAll = nMin != 0; // on empty paragraphs only remove INetFormats
+
+ for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i )
+ {
+ SwTextAttr * const pHt = m_pSwpHints->Get(i);
+
+ // if end and start are equal, delete it
+ const sal_Int32 * const pEndIdx = pHt->GetEnd();
+ if (pEndIdx && !pHt->HasDummyChar() && (*pEndIdx == pHt->GetStart())
+ && ( bAll || pHt->Which() == RES_TXTATR_INETFMT ) )
+ {
+ bChanged = true;
+ nMin = std::min( nMin, pHt->GetStart() );
+ nMax = std::max( nMax, *pHt->GetEnd() );
+ DestroyAttr( m_pSwpHints->Cut(i) );
+ --i;
+ }
+ else
+ {
+ pHt->SetDontExpand( false );
+ }
+ }
+ TryDeleteSwpHints();
+
+ if(bChanged)
+ {
+ // textframes react to aHint, others to aNew
+ SwUpdateAttr aHint(
+ nMin,
+ nMax,
+ 0);
+
+ CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aHint));
+ SwFormatChg aNew( GetTextColl() );
+ CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aNew));
+ }
+}
+
+SwNumRule* SwTextNode::GetNumRule(bool bInParent) const
+{
+ SwNumRule* pRet = nullptr;
+
+ const SfxPoolItem* pItem = GetNoCondAttr( RES_PARATR_NUMRULE, bInParent );
+ bool bNoNumRule = false;
+ if ( pItem )
+ {
+ OUString sNumRuleName =
+ static_cast<const SwNumRuleItem *>(pItem)->GetValue();
+ if (!sNumRuleName.isEmpty())
+ {
+ pRet = GetDoc().FindNumRulePtr( sNumRuleName );
+ }
+ else // numbering is turned off
+ bNoNumRule = true;
+ }
+
+ if ( !bNoNumRule )
+ {
+ if ( pRet && pRet == GetDoc().GetOutlineNumRule() &&
+ ( !HasSwAttrSet() ||
+ SfxItemState::SET !=
+ GetpSwAttrSet()->GetItemState( RES_PARATR_NUMRULE, false ) ) )
+ {
+ SwTextFormatColl* pColl = GetTextColl();
+ if ( pColl )
+ {
+ const SwNumRuleItem& rDirectItem = pColl->GetNumRule( false );
+ if ( rDirectItem.GetValue().isEmpty() )
+ {
+ pRet = nullptr;
+ }
+ }
+ }
+ }
+
+ return pRet;
+}
+
+void SwTextNode::NumRuleChgd()
+{
+ if ( IsInList() )
+ {
+ SwNumRule* pNumRule = GetNumRule();
+ if ( pNumRule && pNumRule != GetNum()->GetNumRule() )
+ {
+ mpNodeNum->ChangeNumRule( *pNumRule );
+ if (mpNodeNumRLHidden)
+ {
+ mpNodeNumRLHidden->ChangeNumRule(*pNumRule);
+ }
+ }
+ }
+
+ // Sending "noop" modify in order to cause invalidations of registered
+ // <SwTextFrame> instances to get the list style change respectively the change
+ // in the list tree reflected in the layout.
+ // Important note:
+ {
+ SvxTextLeftMarginItem & rLR = const_cast<SvxTextLeftMarginItem&>(GetSwAttrSet().GetTextLeftMargin());
+ CallSwClientNotify(sw::LegacyModifyHint(&rLR, &rLR));
+ }
+
+ SetWordCountDirty( true );
+}
+
+// -> #i27615#
+bool SwTextNode::IsNumbered(SwRootFrame const*const pLayout) const
+{
+ SwNumRule* pRule = GetNum(pLayout) ? GetNum(pLayout)->GetNumRule() : nullptr;
+ return pRule && IsCountedInList();
+}
+
+bool SwTextNode::HasMarkedLabel() const
+{
+ bool bResult = false;
+
+ if ( IsInList() )
+ {
+ bResult =
+ GetDoc().getIDocumentListsAccess().getListByName( GetListId() )->IsListLevelMarked( GetActualListLevel() );
+ }
+
+ return bResult;
+}
+// <- #i27615#
+
+SwTextNode* SwTextNode::MakeNewTextNode( SwNode& rPosNd, bool bNext,
+ bool bChgFollow )
+{
+ // ignore hard PageBreak/PageDesc/ColumnBreak from Auto-Set
+ std::optional<SwAttrSet> oNewAttrSet;
+ // #i75353#
+ bool bClearHardSetNumRuleWhenFormatCollChanges( false );
+ if( HasSwAttrSet() )
+ {
+ oNewAttrSet.emplace( *GetpSwAttrSet() );
+ const SfxItemSet* pTmpSet = GetpSwAttrSet();
+
+ if (bNext) // successor doesn't inherit breaks!
+ pTmpSet = &*oNewAttrSet;
+
+ // !bNext: remove PageBreaks/PageDesc/ColBreak from this
+ bool bRemoveFromCache = false;
+ std::vector<sal_uInt16> aClearWhichIds;
+ if ( bNext )
+ bRemoveFromCache = ( 0 != oNewAttrSet->ClearItem( RES_PAGEDESC ) );
+ else
+ aClearWhichIds.push_back( RES_PAGEDESC );
+
+ if( SfxItemState::SET == pTmpSet->GetItemState( RES_BREAK, false ) )
+ {
+ if ( bNext )
+ oNewAttrSet->ClearItem( RES_BREAK );
+ else
+ aClearWhichIds.push_back( RES_BREAK );
+ bRemoveFromCache = true;
+ }
+ if( SfxItemState::SET == pTmpSet->GetItemState( RES_KEEP, false ) )
+ {
+ if ( bNext )
+ oNewAttrSet->ClearItem( RES_KEEP );
+ else
+ aClearWhichIds.push_back( RES_KEEP );
+ bRemoveFromCache = true;
+ }
+ if( SfxItemState::SET == pTmpSet->GetItemState( RES_PARATR_SPLIT, false ) )
+ {
+ if ( bNext )
+ oNewAttrSet->ClearItem( RES_PARATR_SPLIT );
+ else
+ aClearWhichIds.push_back( RES_PARATR_SPLIT );
+ bRemoveFromCache = true;
+ }
+ if(SfxItemState::SET == pTmpSet->GetItemState(RES_PARATR_NUMRULE, false))
+ {
+ SwNumRule * pRule = GetNumRule();
+
+ if (pRule && IsOutline())
+ {
+ if ( bNext )
+ oNewAttrSet->ClearItem(RES_PARATR_NUMRULE);
+ else
+ {
+ // #i75353#
+ // No clear of hard set numbering rule at an outline paragraph at this point.
+ // Only if the paragraph style changes - see below.
+ bClearHardSetNumRuleWhenFormatCollChanges = true;
+ }
+ bRemoveFromCache = true;
+ }
+ }
+
+ if ( !aClearWhichIds.empty() )
+ bRemoveFromCache = 0 != ClearItemsFromAttrSet( aClearWhichIds );
+
+ if( !bNext && bRemoveFromCache )
+ {
+ InvalidateInSwCache(RES_OBJECTDYING);
+ }
+ }
+ SwNodes& rNds = GetNodes();
+
+ SwTextFormatColl* pColl = GetTextColl();
+
+ SwTextNode *pNode = new SwTextNode( rPosNd, pColl, oNewAttrSet ? &*oNewAttrSet : nullptr );
+
+ oNewAttrSet.reset();
+
+ const SwNumRule* pRule = GetNumRule();
+ if( pRule && pRule == pNode->GetNumRule() && rNds.IsDocNodes() )
+ {
+ // #i55459#
+ // - correction: parameter <bNext> has to be checked, as it was in the
+ // previous implementation.
+ if ( !bNext && !IsCountedInList() )
+ SetCountedInList(true);
+ }
+
+ // In case the numbering caused a style from the pool to be assigned to
+ // the new node, don't overwrite that here!
+ if( pColl != pNode->GetTextColl() ||
+ ( bChgFollow && pColl != GetTextColl() ))
+ return pNode; // that ought to be enough?
+
+ pNode->ChgTextCollUpdateNum( nullptr, pColl ); // for numbering/outline
+ if( bNext || !bChgFollow )
+ return pNode;
+
+ SwTextFormatColl *pNextColl = &pColl->GetNextTextFormatColl();
+ // i#101870 perform action on different paragraph styles before applying
+ // the new paragraph style
+ if (pNextColl != pColl)
+ {
+ // #i75353#
+ if ( bClearHardSetNumRuleWhenFormatCollChanges )
+ {
+ if ( ClearItemsFromAttrSet( { RES_PARATR_NUMRULE } ) != 0 )
+ {
+ InvalidateInSwCache(RES_ATTRSET_CHG);
+ }
+ }
+ }
+ ChgFormatColl( pNextColl );
+
+ return pNode;
+}
+
+SwContentNode* SwTextNode::AppendNode( const SwPosition & rPos )
+{
+ // position behind which it will be inserted
+ SwTextNode* pNew = MakeNewTextNode( *rPos.GetNodes()[rPos.GetNodeIndex() + 1] );
+
+ // reset list attributes at appended text node
+ pNew->ResetAttr( RES_PARATR_LIST_ISRESTART );
+ pNew->ResetAttr( RES_PARATR_LIST_RESTARTVALUE );
+ pNew->ResetAttr( RES_PARATR_LIST_ISCOUNTED );
+ if ( pNew->GetNumRule() == nullptr )
+ {
+ pNew->ResetAttr( RES_PARATR_LIST_ID );
+ pNew->ResetAttr( RES_PARATR_LIST_LEVEL );
+ }
+
+ if (!IsInList() && GetNumRule() && !GetListId().isEmpty())
+ {
+ AddToList();
+ }
+
+ if( HasWriterListeners() )
+ MakeFramesForAdjacentContentNode(*pNew);
+ return pNew;
+}
+
+SwTextAttr * SwTextNode::GetTextAttrForCharAt(
+ const sal_Int32 nIndex,
+ const sal_uInt16 nWhich ) const
+{
+ assert(nWhich >= RES_TXTATR_BEGIN && nWhich <= RES_TXTATR_END);
+ if ( HasHints() )
+ {
+ for ( size_t i = 0; i < m_pSwpHints->Count(); ++i )
+ {
+ SwTextAttr * const pHint = m_pSwpHints->Get(i);
+ const sal_Int32 nStartPos = pHint->GetStart();
+ if ( nIndex < nStartPos )
+ {
+ return nullptr;
+ }
+ if ( (nIndex == nStartPos) && pHint->HasDummyChar() )
+ {
+ return ( RES_TXTATR_END == nWhich || nWhich == pHint->Which() )
+ ? pHint : nullptr;
+ }
+ }
+ }
+ return nullptr;
+}
+
+SwTextAttr* SwTextNode::GetTextAttrForEndCharAt(sal_Int32 nIndex, sal_uInt16 nWhich) const
+{
+ SwTextAttr* pAttr = GetTextAttrAt(nIndex, nWhich, ::sw::GetTextAttrMode::Expand);
+ if (!pAttr)
+ {
+ return nullptr;
+ }
+
+ if (!pAttr->End())
+ {
+ return nullptr;
+ }
+
+ // The start-end range covers the end dummy character.
+ if (*pAttr->End() - 1 != nIndex)
+ {
+ return nullptr;
+ }
+
+ return pAttr;
+}
+
+namespace
+{
+
+sal_uInt16 lcl_BoundListLevel(const int nActualLevel)
+{
+ return o3tl::narrowing<sal_uInt16>( std::clamp( nActualLevel, 0, MAXLEVEL-1 ) );
+}
+
+}
+
+// -> #i29560#
+bool SwTextNode::HasNumber(SwRootFrame const*const pLayout) const
+{
+ bool bResult = false;
+
+ const SwNumRule *const pRule = GetNum(pLayout) ? GetNum(pLayout)->GetNumRule() : nullptr;
+ if ( pRule )
+ {
+ const SwNumFormat& aFormat(pRule->Get(lcl_BoundListLevel(GetActualListLevel())));
+
+ // #i40041#
+ bResult = aFormat.IsEnumeration();
+ }
+
+ return bResult;
+}
+
+bool SwTextNode::HasBullet() const
+{
+ bool bResult = false;
+
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pRule )
+ {
+ const SwNumFormat& aFormat(pRule->Get(lcl_BoundListLevel(GetActualListLevel())));
+
+ bResult = aFormat.IsItemize();
+ }
+
+ return bResult;
+}
+// <- #i29560#
+
+// #128041# - introduce parameter <_bInclPrefixAndSuffixStrings>
+//i53420 added max outline parameter
+OUString SwTextNode::GetNumString( const bool _bInclPrefixAndSuffixStrings,
+ const unsigned int _nRestrictToThisLevel,
+ SwRootFrame const*const pLayout, SwListRedlineType eRedline) const
+{
+ if (GetDoc().IsClipBoard() && m_oNumStringCache)
+ {
+ // #i111677# do not expand number strings in clipboard documents
+ return *m_oNumStringCache;
+ }
+ const SwNumRule* pRule = GetNum(pLayout, eRedline) ? GetNum(pLayout, eRedline)->GetNumRule() : nullptr;
+ if ( pRule &&
+ IsCountedInList() )
+ {
+ SvxNumberType const& rNumberType(
+ pRule->Get( lcl_BoundListLevel(GetActualListLevel(eRedline)) ) );
+ if (rNumberType.IsTextFormat() ||
+
+ (style::NumberingType::NUMBER_NONE == rNumberType.GetNumberingType()))
+ {
+ return pRule->MakeNumString( GetNum(pLayout, eRedline)->GetNumberVector(),
+ _bInclPrefixAndSuffixStrings,
+ _nRestrictToThisLevel,
+ false,
+ nullptr,
+ GetLang(0));
+ }
+ }
+
+ return OUString();
+}
+
+tools::Long SwTextNode::GetLeftMarginWithNum( bool bTextLeft ) const
+{
+ tools::Long nRet = 0;
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if( pRule )
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
+ {
+ nRet = rFormat.GetAbsLSpace();
+
+ if( !bTextLeft )
+ {
+ if( 0 > rFormat.GetFirstLineOffset() &&
+ nRet > -rFormat.GetFirstLineOffset() )
+ nRet = nRet + rFormat.GetFirstLineOffset();
+ else
+ nRet = 0;
+ }
+
+ if( pRule->IsAbsSpaces() )
+ {
+ SvxFirstLineIndentItem const& rFirst(GetSwAttrSet().GetFirstLineIndent());
+ nRet = nRet - GetSwAttrSet().GetTextLeftMargin().GetLeft(rFirst);
+ }
+ }
+ else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ ::sw::ListLevelIndents const indents(AreListLevelIndentsApplicable());
+ // note: the result is *always* added to either the left-margin
+ // or the text-left-margin of the node itself by the caller.
+ // so first, subtract what the caller has computed anyway,
+ // and then add the value according to combination of
+ // list/paragraph items. (this is rather inelegant)
+ SvxFirstLineIndentItem firstLine(GetSwAttrSet().GetFirstLineIndent());
+ SvxTextLeftMarginItem leftMargin(GetSwAttrSet().GetTextLeftMargin());
+ nRet = bTextLeft
+ ? - leftMargin.GetTextLeft()
+ : - leftMargin.GetLeft(firstLine);
+ if (indents & ::sw::ListLevelIndents::LeftMargin)
+ {
+ leftMargin.SetTextLeft(rFormat.GetIndentAt());
+ }
+ if (indents & ::sw::ListLevelIndents::FirstLine)
+ {
+ firstLine.SetTextFirstLineOffset(rFormat.GetFirstLineIndent());
+ }
+ nRet += bTextLeft
+ ? leftMargin.GetTextLeft()
+ : leftMargin.GetLeft(firstLine);
+ }
+ }
+
+ return nRet;
+}
+
+bool SwTextNode::GetFirstLineOfsWithNum( short& rFLOffset ) const
+{
+ // #i95907#
+ rFLOffset = 0;
+
+ // #i51089#
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pRule )
+ {
+ if ( IsCountedInList() )
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
+ {
+ rFLOffset = rFormat.GetFirstLineOffset(); //TODO: overflow
+
+ if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ SvxFirstLineIndentItem const aItem(GetSwAttrSet().GetFirstLineIndent());
+ rFLOffset = rFLOffset + aItem.GetTextFirstLineOffset();
+ }
+ }
+ else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ if (AreListLevelIndentsApplicable() & ::sw::ListLevelIndents::FirstLine)
+ {
+ rFLOffset = rFormat.GetFirstLineIndent();
+ }
+ else if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ SvxFirstLineIndentItem const aItem(GetSwAttrSet().GetFirstLineIndent());
+ rFLOffset = aItem.GetTextFirstLineOffset();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ rFLOffset = GetSwAttrSet().GetFirstLineIndent().GetTextFirstLineOffset();
+ return false;
+}
+
+SwTwips SwTextNode::GetAdditionalIndentForStartingNewList() const
+{
+ SwTwips nAdditionalIndent = 0;
+
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pRule )
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
+ {
+ SvxFirstLineIndentItem const& rFirst(GetSwAttrSet().GetFirstLineIndent());
+ nAdditionalIndent = GetSwAttrSet().GetTextLeftMargin().GetLeft(rFirst);
+
+ if (getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ nAdditionalIndent = nAdditionalIndent -
+ GetSwAttrSet().GetFirstLineIndent().GetTextFirstLineOffset();
+ }
+ }
+ else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ // note: there was an apparent bug here, list GetIndentAt()
+ // was interpreted as left-margin not text-left-margin unlike every
+ // other use of it.
+ ::sw::ListLevelIndents const indents(AreListLevelIndentsApplicable());
+ SvxFirstLineIndentItem const& rFirst(
+ indents & ::sw::ListLevelIndents::FirstLine
+ ? SvxFirstLineIndentItem(rFormat.GetFirstLineIndent(), RES_MARGIN_FIRSTLINE)
+ : GetSwAttrSet().GetFirstLineIndent());
+ SvxTextLeftMarginItem const& rLeft(
+ indents & ::sw::ListLevelIndents::LeftMargin
+ ? SvxTextLeftMarginItem(rFormat.GetIndentAt(), RES_MARGIN_TEXTLEFT)
+ : GetSwAttrSet().GetTextLeftMargin());
+ nAdditionalIndent = rLeft.GetLeft(rFirst);
+ if (!(indents & ::sw::ListLevelIndents::FirstLine))
+ {
+ if (getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ nAdditionalIndent = nAdditionalIndent - rFirst.GetTextFirstLineOffset();
+ }
+ }
+ }
+ }
+ else
+ {
+ SvxFirstLineIndentItem const& rFirst(GetSwAttrSet().GetFirstLineIndent());
+ nAdditionalIndent = GetSwAttrSet().GetTextLeftMargin().GetLeft(rFirst);
+ }
+
+ return nAdditionalIndent;
+}
+
+// #i91133#
+tools::Long SwTextNode::GetLeftMarginForTabCalculation() const
+{
+ tools::Long nLeftMarginForTabCalc = 0;
+
+ bool bLeftMarginForTabCalcSetToListLevelIndent( false );
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if( pRule )
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ if (AreListLevelIndentsApplicable() & ::sw::ListLevelIndents::LeftMargin)
+ {
+ nLeftMarginForTabCalc = rFormat.GetIndentAt();
+ bLeftMarginForTabCalcSetToListLevelIndent = true;
+ }
+ }
+ }
+ if ( !bLeftMarginForTabCalcSetToListLevelIndent )
+ {
+ nLeftMarginForTabCalc = GetSwAttrSet().GetTextLeftMargin().GetTextLeft();
+ }
+
+ return nLeftMarginForTabCalc;
+}
+
+static void Replace0xFF(
+ SwTextNode const& rNode,
+ OUStringBuffer & rText,
+ sal_Int32 & rTextStt,
+ sal_Int32 nEndPos )
+{
+ if (!rNode.GetpSwpHints())
+ return;
+
+ sal_Unicode cSrchChr = CH_TXTATR_BREAKWORD;
+ for( int nSrchIter = 0; 2 > nSrchIter; ++nSrchIter, cSrchChr = CH_TXTATR_INWORD )
+ {
+ sal_Int32 nPos = rText.indexOf(cSrchChr);
+ while (-1 != nPos && nPos < nEndPos)
+ {
+ const SwTextAttr* const pAttr =
+ rNode.GetTextAttrForCharAt(rTextStt + nPos);
+ if( pAttr )
+ {
+ switch( pAttr->Which() )
+ {
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_ANNOTATION:
+ rText.remove(nPos, 1);
+ ++rTextStt;
+ break;
+
+ case RES_TXTATR_FTN:
+ rText.remove(nPos, 1);
+ ++rTextStt;
+ break;
+
+ default:
+ rText.remove(nPos, 1);
+ ++rTextStt;
+ }
+ }
+ else
+ {
+ ++nPos;
+ ++nEndPos;
+ }
+ nPos = rText.indexOf(cSrchChr, nPos);
+ }
+ }
+}
+
+// Expand fields
+// #i83479# - handling of new parameters
+OUString SwTextNode::GetExpandText(SwRootFrame const*const pLayout,
+ const sal_Int32 nIdx,
+ const sal_Int32 nLen,
+ const bool bWithNum,
+ const bool bAddSpaceAfterListLabelStr,
+ const bool bWithSpacesForLevel,
+ const ExpandMode eAdditionalMode) const
+
+{
+ ExpandMode eMode = ExpandMode::ExpandFields | eAdditionalMode;
+ if (pLayout && pLayout->IsHideRedlines())
+ {
+ eMode |= ExpandMode::HideDeletions;
+ }
+
+ ModelToViewHelper aConversionMap(*this, pLayout, eMode);
+ const OUString aExpandText = aConversionMap.getViewText();
+ const sal_Int32 nExpandBegin = aConversionMap.ConvertToViewPosition( nIdx );
+ sal_Int32 nEnd = nLen == -1 ? GetText().getLength() : nIdx + nLen;
+ const sal_Int32 nExpandEnd = aConversionMap.ConvertToViewPosition( nEnd );
+ OUStringBuffer aText(aExpandText.subView(nExpandBegin, nExpandEnd-nExpandBegin));
+
+ // remove dummy characters of Input Fields
+ comphelper::string::remove(aText, CH_TXT_ATR_INPUTFIELDSTART);
+ comphelper::string::remove(aText, CH_TXT_ATR_INPUTFIELDEND);
+ comphelper::string::remove(aText, CH_TXTATR_BREAKWORD);
+
+ if( bWithNum )
+ {
+ if (!GetNumString(true, MAXLEVEL, pLayout).isEmpty())
+ {
+ if ( bAddSpaceAfterListLabelStr )
+ {
+ const sal_Unicode aSpace = ' ';
+ aText.insert(0, aSpace);
+ }
+ aText.insert(0, GetNumString(true, MAXLEVEL, pLayout));
+ }
+ }
+
+ if (bWithSpacesForLevel)
+ {
+ const sal_Unicode aSpace = ' ';
+ for (int nLevel = GetActualListLevel(); nLevel > 0; --nLevel)
+ {
+ aText.insert(0, aSpace);
+ aText.insert(0, aSpace);
+ }
+ }
+
+ return aText.makeStringAndClear();
+}
+
+bool SwTextNode::CopyExpandText(SwTextNode& rDestNd, const SwContentIndex* pDestIdx,
+ sal_Int32 nIdx, sal_Int32 nLen,
+ SwRootFrame const*const pLayout, bool bWithNum,
+ bool bWithFootnote, bool bReplaceTabsWithSpaces ) const
+{
+ if( &rDestNd == this )
+ return false;
+ assert(!pDestIdx || pDestIdx->GetContentNode() == &rDestNd);
+
+ SwContentIndex aDestIdx(&rDestNd, rDestNd.GetText().getLength());
+ if( pDestIdx )
+ aDestIdx = *pDestIdx;
+ const sal_Int32 nDestStt = aDestIdx.GetIndex();
+
+ // first, start with the text
+ OUStringBuffer buf(GetText());
+ if( bReplaceTabsWithSpaces )
+ buf.replace('\t', ' ');
+
+ // mask hidden characters
+ const sal_Unicode cChar = CH_TXTATR_BREAKWORD;
+ SwScriptInfo::MaskHiddenRanges(*this, buf, 0, buf.getLength(), cChar);
+
+ buf.remove(0, nIdx);
+ if (nLen != -1)
+ {
+ buf.truncate(nLen);
+ }
+ // remove dummy characters of Input Fields
+ {
+ comphelper::string::remove(buf, CH_TXT_ATR_INPUTFIELDSTART);
+ comphelper::string::remove(buf, CH_TXT_ATR_INPUTFIELDEND);
+ }
+ rDestNd.InsertText(buf.makeStringAndClear(), aDestIdx);
+ nLen = aDestIdx.GetIndex() - nDestStt;
+
+ // set all char attributes with Symbol font
+ if ( HasHints() )
+ {
+ sal_Int32 nInsPos = nDestStt - nIdx;
+ for ( size_t i = 0; i < m_pSwpHints->Count(); ++i )
+ {
+ const SwTextAttr* pHt = m_pSwpHints->Get(i);
+ const sal_Int32 nAttrStartIdx = pHt->GetStart();
+ const sal_uInt16 nWhich = pHt->Which();
+ if (nIdx + nLen <= nAttrStartIdx)
+ break; // behind end of text
+
+ const sal_Int32 *pEndIdx = pHt->End();
+ if( pEndIdx && *pEndIdx > nIdx &&
+ ( RES_CHRATR_FONT == nWhich ||
+ RES_TXTATR_CHARFMT == nWhich ||
+ RES_TXTATR_AUTOFMT == nWhich ))
+ {
+ const SvxFontItem* const pFont =
+ CharFormat::GetItem( *pHt, RES_CHRATR_FONT );
+ if ( pFont && RTL_TEXTENCODING_SYMBOL == pFont->GetCharSet() )
+ {
+ // attribute in area => copy
+ rDestNd.InsertItem( *const_cast<SvxFontItem*>(pFont),
+ nInsPos + nAttrStartIdx, nInsPos + *pEndIdx );
+ }
+ }
+ else if ( pHt->HasDummyChar() && (nAttrStartIdx >= nIdx) )
+ {
+ aDestIdx = nInsPos + nAttrStartIdx;
+ switch( nWhich )
+ {
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_ANNOTATION:
+ {
+ OUString const aExpand(
+ static_txtattr_cast<SwTextField const*>(pHt)->GetFormatField().GetField()->ExpandField(true, pLayout));
+ if (!aExpand.isEmpty())
+ {
+ ++aDestIdx; // insert behind
+ OUString const ins(
+ rDestNd.InsertText( aExpand, aDestIdx));
+ SAL_INFO_IF(ins.getLength() != aExpand.getLength(),
+ "sw.core", "GetExpandText lossage");
+ aDestIdx = nInsPos + nAttrStartIdx;
+ nInsPos += ins.getLength();
+ }
+ rDestNd.EraseText( aDestIdx, 1 );
+ --nInsPos;
+ }
+ break;
+
+ case RES_TXTATR_FTN:
+ {
+ if ( bWithFootnote )
+ {
+ const SwFormatFootnote& rFootnote = pHt->GetFootnote();
+ OUString sExpand;
+ auto const number(pLayout && pLayout->IsHideRedlines()
+ ? rFootnote.GetNumberRLHidden()
+ : rFootnote.GetNumber());
+ if( !rFootnote.GetNumStr().isEmpty() )
+ sExpand = rFootnote.GetNumStr();
+ else if( rFootnote.IsEndNote() )
+ sExpand = GetDoc().GetEndNoteInfo().m_aFormat.
+ GetNumStr(number);
+ else
+ sExpand = GetDoc().GetFootnoteInfo().m_aFormat.
+ GetNumStr(number);
+ if( !sExpand.isEmpty() )
+ {
+ ++aDestIdx; // insert behind
+ SvxEscapementItem aItem( SvxEscapement::Superscript, RES_CHRATR_ESCAPEMENT );
+ rDestNd.InsertItem(
+ aItem,
+ aDestIdx.GetIndex(),
+ aDestIdx.GetIndex() );
+ OUString const ins( rDestNd.InsertText(sExpand, aDestIdx, SwInsertFlags::EMPTYEXPAND));
+ SAL_INFO_IF(ins.getLength() != sExpand.getLength(),
+ "sw.core", "GetExpandText lossage");
+ aDestIdx = nInsPos + nAttrStartIdx;
+ nInsPos += ins.getLength();
+ }
+ }
+ rDestNd.EraseText( aDestIdx, 1 );
+ --nInsPos;
+ }
+ break;
+
+ default:
+ rDestNd.EraseText( aDestIdx, 1 );
+ --nInsPos;
+ }
+ }
+ }
+ }
+
+ if( bWithNum )
+ {
+ aDestIdx = nDestStt;
+ rDestNd.InsertText( GetNumString(true, MAXLEVEL, pLayout), aDestIdx );
+ }
+
+ aDestIdx = 0;
+ sal_Int32 nStartDelete(-1);
+ while (aDestIdx < rDestNd.GetText().getLength())
+ {
+ sal_Unicode const cur(rDestNd.GetText()[aDestIdx.GetIndex()]);
+ if ( (cChar == cur) // filter substituted hidden text
+ || (CH_TXT_ATR_FIELDSTART == cur) // filter all fieldmarks
+ || (CH_TXT_ATR_FIELDSEP == cur)
+ || (CH_TXT_ATR_FIELDEND == cur)
+ || (CH_TXT_ATR_FORMELEMENT == cur))
+ {
+ if (-1 == nStartDelete)
+ {
+ nStartDelete = aDestIdx.GetIndex(); // start deletion range
+ }
+ ++aDestIdx;
+ if (aDestIdx < rDestNd.GetText().getLength())
+ {
+ continue;
+ } // else: end of paragraph => delete, see below
+ }
+ else
+ {
+ if (-1 == nStartDelete)
+ {
+ ++aDestIdx;
+ continue;
+ } // else: delete, see below
+ }
+ assert(-1 != nStartDelete); // without delete range, would have continued
+ rDestNd.EraseText(
+ SwContentIndex(&rDestNd, nStartDelete),
+ aDestIdx.GetIndex() - nStartDelete);
+ assert(aDestIdx.GetIndex() == nStartDelete);
+ nStartDelete = -1; // reset
+ }
+
+ return true;
+}
+
+OUString SwTextNode::GetRedlineText() const
+{
+ std::vector<sal_Int32> aRedlArr;
+ const SwDoc& rDoc = GetDoc();
+ SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos( *this, RedlineType::Delete );
+ if( SwRedlineTable::npos != nRedlPos )
+ {
+ // some redline-delete object exists for the node
+ const SwNodeOffset nNdIdx = GetIndex();
+ for( ; nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() ; ++nRedlPos )
+ {
+ const SwRangeRedline* pTmp = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
+ if( RedlineType::Delete == pTmp->GetType() )
+ {
+ const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End();
+ if( pRStt->GetNodeIndex() < nNdIdx )
+ {
+ if( pREnd->GetNodeIndex() > nNdIdx )
+ // paragraph is fully deleted
+ return OUString();
+ else if( pREnd->GetNodeIndex() == nNdIdx )
+ {
+ // deleted from 0 to nContent
+ aRedlArr.push_back( 0 );
+ aRedlArr.push_back( pREnd->GetContentIndex() );
+ }
+ }
+ else if( pRStt->GetNodeIndex() == nNdIdx )
+ {
+ //aRedlArr.Insert( pRStt->GetContentIndex(), aRedlArr.Count() );
+ aRedlArr.push_back( pRStt->GetContentIndex() );
+ if( pREnd->GetNodeIndex() == nNdIdx )
+ aRedlArr.push_back( pREnd->GetContentIndex() );
+ else
+ {
+ aRedlArr.push_back(GetText().getLength());
+ break; // that was all
+ }
+ }
+ else
+ break; // that was all
+ }
+ }
+ }
+
+ OUStringBuffer aText(GetText());
+
+ sal_Int32 nTextStt = 0;
+ sal_Int32 nIdxEnd = aText.getLength();
+ for( size_t n = 0; n < aRedlArr.size(); n += 2 )
+ {
+ sal_Int32 nStt = aRedlArr[ n ];
+ sal_Int32 nEnd = aRedlArr[ n+1 ];
+ if( ( 0 <= nStt && nStt <= nIdxEnd ) ||
+ ( 0 <= nEnd && nEnd <= nIdxEnd ))
+ {
+ if( nStt < 0 ) nStt = 0;
+ if( nIdxEnd < nEnd ) nEnd = nIdxEnd;
+ const sal_Int32 nDelCnt = nEnd - nStt;
+ aText.remove(nStt - nTextStt, nDelCnt);
+ Replace0xFF(*this, aText, nTextStt, nStt - nTextStt);
+ nTextStt += nDelCnt;
+ }
+ else if( nStt >= nIdxEnd )
+ break;
+ }
+ Replace0xFF(*this, aText, nTextStt, aText.getLength());
+
+ return aText.makeStringAndClear();
+}
+
+void SwTextNode::ReplaceText( const SwContentIndex& rStart, const sal_Int32 nDelLen,
+ const OUString & rStr)
+{
+ assert(rStart.GetContentNode() == this);
+ assert( rStart.GetIndex() < m_Text.getLength() // index out of bounds
+ && rStart.GetIndex() + nDelLen <= m_Text.getLength());
+
+ sal_Int32 const nOverflow(rStr.getLength() - nDelLen - GetSpaceLeft());
+ SAL_WARN_IF(nOverflow > 0, "sw.core",
+ "SwTextNode::ReplaceText: node text with insertion > node capacity.");
+ OUString const sInserted(
+ (nOverflow > 0) ? rStr.copy(0, rStr.getLength() - nOverflow) : rStr);
+ if (sInserted.isEmpty() && 0 == nDelLen)
+ {
+ return; // nothing to do
+ }
+
+ const sal_Int32 nStartPos = rStart.GetIndex();
+ sal_Int32 nEndPos = nStartPos + nDelLen;
+ sal_Int32 nLen = nDelLen;
+ for( sal_Int32 nPos = nStartPos; nPos < nEndPos; ++nPos )
+ {
+ if ((CH_TXTATR_BREAKWORD == m_Text[nPos]) ||
+ (CH_TXTATR_INWORD == m_Text[nPos]))
+ {
+ SwTextAttr *const pHint = GetTextAttrForCharAt( nPos );
+ if (pHint)
+ {
+ assert(!( pHint->GetEnd() && pHint->HasDummyChar()
+ && (pHint->GetStart() < nEndPos)
+ && (*pHint->GetEnd() > nEndPos) ));
+ // "deleting left-overlapped attribute with CH_TXTATR"
+ DeleteAttribute( pHint );
+ --nEndPos;
+ --nLen;
+ }
+ }
+ }
+
+ bool bOldExpFlg = IsIgnoreDontExpand();
+ SetIgnoreDontExpand( true );
+
+ const sal_Int32 nInsLen = sInserted.getLength();
+ if (nLen && nInsLen)
+ {
+ m_Text = m_Text.replaceAt(nStartPos, nLen, sInserted);
+
+ if (nLen > nInsLen) // short insert
+ {
+ // delete up to the point that the user specified
+ const SwContentIndex aNegIdx(rStart, nInsLen);
+ Update(aNegIdx, nLen - nInsLen, UpdateMode::Negative);
+ }
+ else if (nLen < nInsLen) // long insert
+ {
+ const SwContentIndex aIdx(rStart, nLen);
+ Update(aIdx, nInsLen - nLen, UpdateMode::Replace);
+ }
+
+ for (sal_Int32 i = 0; i < nInsLen; i++)
+ {
+ ++const_cast<SwContentIndex&>(rStart);
+ }
+ }
+ else
+ {
+ m_Text = m_Text.replaceAt(nStartPos, nLen, u"");
+ Update(rStart, nLen, UpdateMode::Negative);
+
+ m_Text = m_Text.replaceAt(nStartPos, 0, sInserted);
+ Update(rStart, sInserted.getLength(), UpdateMode::Replace);
+ }
+
+ SetIgnoreDontExpand( bOldExpFlg );
+ auto aDelHint = sw::DeleteText(nStartPos, nDelLen);
+ CallSwClientNotify(aDelHint);
+
+ if (sInserted.getLength())
+ {
+ auto aInsHint = sw::MakeInsertText(*this, nStartPos, sInserted.getLength());
+ CallSwClientNotify(aInsHint);
+ }
+}
+
+void SwTextNode::ReplaceText( SwPosition& rStart, const sal_Int32 nDelLen,
+ const OUString & rStr)
+{
+ ReplaceText(rStart.nContent, nDelLen, rStr);
+}
+
+namespace {
+ void lcl_ResetParAttrs( SwTextNode &rTextNode )
+ {
+ const o3tl::sorted_vector<sal_uInt16> aAttrs{ RES_PARATR_LIST_ID, RES_PARATR_LIST_LEVEL,
+ RES_PARATR_LIST_ISRESTART,
+ RES_PARATR_LIST_RESTARTVALUE,
+ RES_PARATR_LIST_ISCOUNTED };
+ SwPaM aPam( rTextNode );
+ // #i96644#
+ // suppress side effect "send data changed events"
+ rTextNode.GetDoc().ResetAttrs( aPam, false, aAttrs, false );
+ }
+
+ // Helper method for special handling of modified attributes at text node.
+ // The following is handled:
+ // (1) on changing the paragraph style - RES_FMT_CHG:
+ // Check, if list style of the text node is changed. If yes, add respectively
+ // remove the text node to the corresponding list.
+ // (2) on changing the attributes - RES_ATTRSET_CHG:
+ // Same as (1).
+ // (3) on changing the list style - RES_PARATR_NUMRULE:
+ // Same as (1).
+ void HandleModifyAtTextNode( SwTextNode& rTextNode,
+ const SfxPoolItem* pOldValue,
+ const SfxPoolItem* pNewValue )
+ {
+ const sal_uInt16 nWhich = pOldValue ? pOldValue->Which() :
+ pNewValue ? pNewValue->Which() : 0 ;
+ bool bNumRuleSet = false;
+ bool bParagraphStyleChanged = false;
+ OUString sNumRule;
+ OUString sOldNumRule;
+ switch ( nWhich )
+ {
+ case RES_FMT_CHG:
+ {
+ bParagraphStyleChanged = true;
+ if( rTextNode.GetNodes().IsDocNodes() )
+ {
+ const SwNumRule* pFormerNumRuleAtTextNode =
+ rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr;
+ if ( pFormerNumRuleAtTextNode )
+ {
+ sOldNumRule = pFormerNumRuleAtTextNode->GetName();
+ }
+ if ( rTextNode.IsEmptyListStyleDueToSetOutlineLevelAttr() )
+ {
+ const SwNumRuleItem& rNumRuleItem = rTextNode.GetTextColl()->GetNumRule();
+ if ( !rNumRuleItem.GetValue().isEmpty() )
+ {
+ rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+ }
+ const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule();
+ if ( pNumRuleAtTextNode )
+ {
+ bNumRuleSet = true;
+ sNumRule = pNumRuleAtTextNode->GetName();
+ }
+ }
+ break;
+ }
+ case RES_ATTRSET_CHG:
+ {
+ const SwNumRule* pFormerNumRuleAtTextNode =
+ rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr;
+ if ( pFormerNumRuleAtTextNode )
+ {
+ sOldNumRule = pFormerNumRuleAtTextNode->GetName();
+ }
+
+ const SwAttrSetChg* pSet = dynamic_cast<const SwAttrSetChg*>(pNewValue);
+ if ( pSet && pSet->GetChgSet()->GetItemState( RES_PARATR_NUMRULE, false ) ==
+ SfxItemState::SET )
+ {
+ // #i70748#
+ rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ bNumRuleSet = true;
+ }
+ // #i70748#
+ // The new list style set at the paragraph.
+ const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule();
+ if ( pNumRuleAtTextNode )
+ {
+ sNumRule = pNumRuleAtTextNode->GetName();
+ }
+ break;
+ }
+ case RES_PARATR_NUMRULE:
+ {
+ if ( rTextNode.GetNodes().IsDocNodes() )
+ {
+ const SwNumRule* pFormerNumRuleAtTextNode =
+ rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr;
+ if ( pFormerNumRuleAtTextNode )
+ {
+ sOldNumRule = pFormerNumRuleAtTextNode->GetName();
+ }
+
+ if ( pNewValue )
+ {
+ // #i70748#
+ rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ bNumRuleSet = true;
+ }
+ // #i70748#
+ // The new list style set at the paragraph.
+ const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule();
+ if ( pNumRuleAtTextNode )
+ {
+ sNumRule = pNumRuleAtTextNode->GetName();
+ }
+ }
+ break;
+ }
+ }
+ if ( sNumRule != sOldNumRule )
+ {
+ if ( bNumRuleSet )
+ {
+ if (sNumRule.isEmpty())
+ {
+ rTextNode.RemoveFromList();
+ if ( bParagraphStyleChanged )
+ {
+ lcl_ResetParAttrs(rTextNode);
+ }
+ }
+ else
+ {
+ rTextNode.RemoveFromList();
+ // If new list style is the outline style, apply outline
+ // level as the list level.
+ if (sNumRule==SwNumRule::GetOutlineRuleName())
+ {
+ // #i70748#
+ OSL_ENSURE( rTextNode.GetTextColl()->IsAssignedToListLevelOfOutlineStyle(),
+ "<HandleModifyAtTextNode()> - text node with outline style, but its paragraph style is not assigned to outline style." );
+ const int nNewListLevel =
+ rTextNode.GetTextColl()->GetAssignedOutlineStyleLevel();
+ if ( 0 <= nNewListLevel && nNewListLevel < MAXLEVEL )
+ {
+ rTextNode.SetAttrListLevel( nNewListLevel );
+ }
+ }
+ rTextNode.AddToList();
+ }
+ }
+ else // <sNumRule.Len() == 0 && sOldNumRule.Len() != 0>
+ {
+ rTextNode.RemoveFromList();
+ if ( bParagraphStyleChanged )
+ {
+ lcl_ResetParAttrs(rTextNode);
+ // #i70748#
+ if ( rTextNode.GetAttr( RES_PARATR_OUTLINELEVEL, false ).GetValue() > 0 )
+ {
+ rTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr();
+ }
+ }
+ }
+ }
+ else if (!sNumRule.isEmpty() && !rTextNode.IsInList())
+ {
+ rTextNode.AddToList();
+ }
+ }
+ // End of method <HandleModifyAtTextNode>
+}
+
+SwFormatColl* SwTextNode::ChgFormatColl( SwFormatColl *pNewColl )
+{
+ OSL_ENSURE( pNewColl,"ChgFormatColl: Collectionpointer has value 0." );
+ assert( dynamic_cast<const SwTextFormatColl *>(pNewColl) && "ChgFormatColl: is not a Text Collection pointer." );
+
+ SwTextFormatColl *pOldColl = GetTextColl();
+ if( pNewColl != pOldColl )
+ {
+ SetCalcHiddenCharFlags();
+ SwContentNode::ChgFormatColl( pNewColl );
+ OSL_ENSURE( !mbInSetOrResetAttr,
+ "DEBUG OSL_ENSURE(ON - <SwTextNode::ChgFormatColl(..)> called during <Set/ResetAttr(..)>" );
+ if ( !mbInSetOrResetAttr )
+ {
+ SwFormatChg aTmp1( pOldColl );
+ SwFormatChg aTmp2( pNewColl );
+ HandleModifyAtTextNode( *this, &aTmp1, &aTmp2 );
+ }
+
+ // reset fill information on parent style change
+ if(maFillAttributes)
+ {
+ maFillAttributes.reset();
+ }
+ }
+
+ // only for real nodes-array
+ if( GetNodes().IsDocNodes() )
+ {
+ ChgTextCollUpdateNum( pOldColl, static_cast<SwTextFormatColl *>(pNewColl) );
+ }
+
+ return pOldColl;
+}
+
+const SwNodeNum* SwTextNode::GetNum(SwRootFrame const*const pLayout, SwListRedlineType eRedline) const
+{
+ // invariant: it's only in list in Hide mode if it's in list in normal mode
+ assert(mpNodeNum || !mpNodeNumRLHidden);
+ return (pLayout && pLayout->IsHideRedlines()) || SwListRedlineType::HIDDEN == eRedline
+ ? mpNodeNumRLHidden.get()
+ : ( SwListRedlineType::ORIGTEXT == eRedline ? mpNodeNumOrig.get() : mpNodeNum.get() );
+}
+
+void SwTextNode::DoNum(std::function<void (SwNodeNum &)> const& rFunc)
+{
+ // temp. clear because GetActualListLevel() may be called and the assert
+ // there triggered during update, which is unhelpful
+ std::unique_ptr<SwNodeNum> pBackup = std::move(mpNodeNumRLHidden);
+ std::unique_ptr<SwNodeNum> pBackup2 = std::move(mpNodeNumOrig);
+ assert(mpNodeNum);
+ rFunc(*mpNodeNum);
+ if (pBackup)
+ {
+ mpNodeNumRLHidden = std::move(pBackup);
+ rFunc(*mpNodeNumRLHidden);
+ }
+ if (pBackup2)
+ {
+ mpNodeNumOrig = std::move(pBackup2);
+ rFunc(*mpNodeNumOrig);
+ }
+}
+
+SwNumberTree::tNumberVector
+SwTextNode::GetNumberVector(SwRootFrame const*const pLayout, SwListRedlineType eRedline) const
+{
+ if (SwNodeNum const*const pNum = GetNum(pLayout, eRedline))
+ {
+ return pNum->GetNumberVector();
+ }
+ else
+ {
+ SwNumberTree::tNumberVector aResult;
+ return aResult;
+ }
+}
+
+bool SwTextNode::IsOutline() const
+{
+ bool bResult = false;
+
+ if ( GetAttrOutlineLevel() > 0 )
+ {
+ bResult = !IsInRedlines();
+ }
+ else
+ {
+ const SwNumRule* pRule( GetNum() ? GetNum()->GetNumRule() : nullptr );
+ if ( pRule && pRule->IsOutlineRule() )
+ {
+ bResult = !IsInRedlines();
+ }
+ }
+
+ return bResult;
+}
+
+bool SwTextNode::IsOutlineStateChanged() const
+{
+ return IsOutline() != m_bLastOutlineState;
+}
+
+void SwTextNode::UpdateOutlineState()
+{
+ m_bLastOutlineState = IsOutline();
+}
+
+int SwTextNode::GetAttrOutlineLevel() const
+{
+ return GetAttr(RES_PARATR_OUTLINELEVEL).GetValue();
+}
+
+void SwTextNode::SetAttrOutlineLevel(int nLevel)
+{
+ assert(0 <= nLevel && nLevel <= MAXLEVEL); // Level Out Of Range
+ if ( 0 <= nLevel && nLevel <= MAXLEVEL )
+ {
+ SetAttr( SfxUInt16Item( RES_PARATR_OUTLINELEVEL,
+ o3tl::narrowing<sal_uInt16>(nLevel) ) );
+ }
+}
+
+void SwTextNode::GetAttrOutlineContentVisible(bool& bOutlineContentVisibleAttr)
+{
+ const SfxGrabBagItem & rGrabBagItem = GetAttr(RES_PARATR_GRABBAG);
+ auto it = rGrabBagItem.GetGrabBag().find("OutlineContentVisibleAttr");
+ if (it != rGrabBagItem.GetGrabBag().end())
+ it->second >>= bOutlineContentVisibleAttr;
+}
+
+void SwTextNode::SetAttrOutlineContentVisible(bool bVisible)
+{
+ SfxGrabBagItem aGrabBagItem(RES_PARATR_GRABBAG);
+ aGrabBagItem.GetGrabBag()["OutlineContentVisibleAttr"] <<= bVisible;
+ SetAttr(aGrabBagItem);
+}
+
+// #i70748#
+
+void SwTextNode::SetEmptyListStyleDueToSetOutlineLevelAttr()
+{
+ if ( !mbEmptyListStyleSetDueToSetOutlineLevelAttr )
+ {
+ SetAttr( SwNumRuleItem() );
+ mbEmptyListStyleSetDueToSetOutlineLevelAttr = true;
+ }
+}
+
+void SwTextNode::ResetEmptyListStyleDueToResetOutlineLevelAttr()
+{
+ if ( mbEmptyListStyleSetDueToSetOutlineLevelAttr )
+ {
+ ResetAttr( RES_PARATR_NUMRULE );
+ mbEmptyListStyleSetDueToSetOutlineLevelAttr = false;
+ }
+}
+
+void SwTextNode::SetAttrListLevel( int nLevel )
+{
+ if ( nLevel < 0 || nLevel >= MAXLEVEL )
+ {
+ assert(false); // invalid level
+ return;
+ }
+
+ SfxInt16Item aNewListLevelItem( RES_PARATR_LIST_LEVEL,
+ static_cast<sal_Int16>(nLevel) );
+ SetAttr( aNewListLevelItem );
+}
+
+bool SwTextNode::HasAttrListLevel() const
+{
+ return GetpSwAttrSet() &&
+ GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_LEVEL, false ) == SfxItemState::SET;
+}
+
+int SwTextNode::GetAttrListLevel() const
+{
+ int nAttrListLevel = 0;
+
+ const SfxInt16Item& aListLevelItem =
+ GetAttr( RES_PARATR_LIST_LEVEL );
+ nAttrListLevel = static_cast<int>(aListLevelItem.GetValue());
+
+ return nAttrListLevel;
+}
+
+int SwTextNode::GetActualListLevel(SwListRedlineType eRedline) const
+{
+ assert(SwListRedlineType::SHOW != eRedline ||
+ !GetNum(nullptr, SwListRedlineType::SHOW) || !mpNodeNumRLHidden || // must be in sync
+ GetNum(nullptr, SwListRedlineType::SHOW)->GetLevelInListTree() ==
+ mpNodeNumRLHidden->GetLevelInListTree());
+ return GetNum(nullptr, eRedline) ? GetNum(nullptr, eRedline)->GetLevelInListTree() : -1;
+}
+
+void SwTextNode::SetListRestart( bool bRestart )
+{
+ if ( !bRestart )
+ {
+ // attribute not contained in paragraph style's attribute set. Thus,
+ // it can be reset to the attribute pool default by resetting the attribute.
+ ResetAttr( RES_PARATR_LIST_ISRESTART );
+ }
+ else
+ {
+ SfxBoolItem aNewIsRestartItem( RES_PARATR_LIST_ISRESTART,
+ true );
+ SetAttr( aNewIsRestartItem );
+ }
+}
+
+bool SwTextNode::IsListRestart() const
+{
+ const SfxBoolItem& aIsRestartItem = GetAttr( RES_PARATR_LIST_ISRESTART );
+
+ return aIsRestartItem.GetValue();
+}
+
+/** Returns if the paragraph has a visible numbering or bullet.
+ This includes all kinds of numbering/bullet/outlines.
+ The concrete list label string has to be checked, too.
+ */
+bool SwTextNode::HasVisibleNumberingOrBullet() const
+{
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pRule && IsCountedInList())
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+ if (getIDocumentSettingAccess()->get(DocumentSettingId::NO_NUMBERING_SHOW_FOLLOWBY))
+ // True if we have something in label text or there is a non-empty
+ // FollowedBy separator (space, tab or whatsoever)
+ return rFormat.GetLabelFollowedBy() != SvxNumberFormat::LabelFollowedBy::NOTHING ||
+ !pRule->MakeNumString(*GetNum()).isEmpty();
+ else
+ // #i87154#
+ // Correction of #newlistlevelattrs#:
+ // The numbering type has to be checked for bullet lists.
+ return SVX_NUM_NUMBER_NONE != rFormat.GetNumberingType() ||
+ !pRule->MakeNumString(*(GetNum())).isEmpty();
+ }
+
+ return false;
+}
+
+void SwTextNode::SetAttrListRestartValue( SwNumberTree::tSwNumTreeNumber nNumber )
+{
+ const bool bChanged( HasAttrListRestartValue()
+ ? GetAttrListRestartValue() != nNumber
+ : nNumber != USHRT_MAX );
+
+ if ( !bChanged && HasAttrListRestartValue() )
+ return;
+
+ if ( nNumber == USHRT_MAX )
+ {
+ ResetAttr( RES_PARATR_LIST_RESTARTVALUE );
+ }
+ else
+ {
+ SfxInt16Item aNewListRestartValueItem( RES_PARATR_LIST_RESTARTVALUE,
+ static_cast<sal_Int16>(nNumber) );
+ SetAttr( aNewListRestartValueItem );
+ }
+}
+
+bool SwTextNode::HasAttrListRestartValue() const
+{
+ return GetpSwAttrSet() &&
+ GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_RESTARTVALUE, false ) == SfxItemState::SET;
+}
+SwNumberTree::tSwNumTreeNumber SwTextNode::GetAttrListRestartValue() const
+{
+ OSL_ENSURE( HasAttrListRestartValue(),
+ "<SwTextNode::GetAttrListRestartValue()> - only ask for list restart value, if attribute is set at text node." );
+
+ const SfxInt16Item& aListRestartValueItem =
+ GetAttr( RES_PARATR_LIST_RESTARTVALUE );
+ return static_cast<SwNumberTree::tSwNumTreeNumber>(aListRestartValueItem.GetValue());
+}
+
+SwNumberTree::tSwNumTreeNumber SwTextNode::GetActualListStartValue() const
+{
+ SwNumberTree::tSwNumTreeNumber nListRestartValue = 1;
+
+ if ( IsListRestart() && HasAttrListRestartValue() )
+ {
+ nListRestartValue = GetAttrListRestartValue();
+ }
+ else
+ {
+ SwNumRule* pRule = GetNumRule();
+ if ( pRule )
+ {
+ const SwNumFormat* pFormat =
+ pRule->GetNumFormat( o3tl::narrowing<sal_uInt16>(GetAttrListLevel()) );
+ if ( pFormat )
+ {
+ nListRestartValue = pFormat->GetStart();
+ }
+ }
+ }
+
+ return nListRestartValue;
+}
+
+bool SwTextNode::IsNotifiable() const
+{
+ return m_bNotifiable && IsNotificationEnabled();
+}
+
+bool SwTextNode::IsNotificationEnabled() const
+{
+ const SwDoc& rDoc = GetDoc();
+ return !rDoc.IsInReading() && !rDoc.IsInDtor();
+}
+
+void SwTextNode::SetCountedInList( bool bCounted )
+{
+ if ( bCounted )
+ {
+ // attribute not contained in paragraph style's attribute set. Thus,
+ // it can be reset to the attribute pool default by resetting the attribute.
+ ResetAttr( RES_PARATR_LIST_ISCOUNTED );
+ }
+ else
+ {
+ SfxBoolItem aIsCountedInListItem( RES_PARATR_LIST_ISCOUNTED, false );
+ SetAttr( aIsCountedInListItem );
+ }
+}
+
+bool SwTextNode::IsCountedInList() const
+{
+ const SfxBoolItem& aIsCountedInListItem = GetAttr( RES_PARATR_LIST_ISCOUNTED );
+
+ return aIsCountedInListItem.GetValue();
+}
+
+static SwList * FindList(SwTextNode *const pNode)
+{
+ const OUString sListId = pNode->GetListId();
+ if (!sListId.isEmpty())
+ {
+ auto & rIDLA(pNode->GetDoc().getIDocumentListsAccess());
+ SwList* pList = rIDLA.getListByName( sListId );
+ if ( pList == nullptr )
+ {
+ // Create corresponding list.
+ SwNumRule* pNumRule = pNode->GetNumRule();
+ if ( pNumRule )
+ {
+ pList = rIDLA.createList(sListId, pNode->GetNumRule()->GetName());
+ }
+ }
+ OSL_ENSURE( pList != nullptr,
+ "<SwTextNode::AddToList()> - no list for given list id. Serious defect" );
+ return pList;
+ }
+ return nullptr;
+}
+
+void SwTextNode::AddToList()
+{
+ if ( IsInList() )
+ {
+ OSL_FAIL( "<SwTextNode::AddToList()> - the text node is already added to a list. Serious defect" );
+ return;
+ }
+
+ SwList *const pList(FindList(this));
+ if (!(pList && GetNodes().IsDocNodes())) // not for undo nodes
+ return;
+
+ assert(!mpNodeNum);
+ mpNodeNum.reset(new SwNodeNum(this, false));
+ pList->InsertListItem(*mpNodeNum, SwListRedlineType::SHOW, GetAttrListLevel(), GetDoc());
+
+ // set redline lists
+ // "default" list: visible items in Show Changes mode (tracked insertions and deletions)
+ // "hidden" list: visible items in Hide Changes mode (tracked insertions, but not deletions)
+ // "orig" list: visible items rejecting all changes (no tracked insertions and deletions)
+ bool bRecordChanges = GetDoc().GetDocShell() && GetDoc().GetDocShell()->IsChangeRecording();
+ if (!bRecordChanges || GetDoc().IsInXMLImport() || GetDoc().IsInWriterfilterImport() )
+ {
+ const SwRedlineTable& rRedTable = GetDoc().getIDocumentRedlineAccess().GetRedlineTable();
+ SwRedlineTable::size_type nRedlPos = GetDoc().getIDocumentRedlineAccess().GetRedlinePos(*this, RedlineType::Insert);
+ // paragraph start is not in a tracked insertion
+ if ( SwRedlineTable::npos == nRedlPos || GetIndex() <= rRedTable[nRedlPos]->Start()->GetNode().GetIndex() )
+ {
+ AddToListOrig();
+
+ // if the paragraph is not deleted, add to the "hidden" list, too
+ SwRedlineTable::size_type nRedlPosDel = GetDoc().getIDocumentRedlineAccess().GetRedlinePos(*this, RedlineType::Delete);
+ if ( SwRedlineTable::npos == nRedlPosDel )
+ AddToListRLHidden();
+ }
+ // inserted paragraph, e.g. during file load, add to the "hidden" list
+ else if ( SwRedlineTable::npos != nRedlPos )
+ AddToListRLHidden();
+ }
+ else if ( bRecordChanges )
+ AddToListRLHidden();
+
+ // iterate all frames & if there's one with hidden layout...
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> iter(*this);
+ for (SwTextFrame* pFrame = iter.First(); pFrame && !mpNodeNumRLHidden; pFrame = iter.Next())
+ {
+ if (pFrame->getRootFrame()->IsHideRedlines())
+ {
+ if (pFrame->GetTextNodeForParaProps() == this)
+ {
+ AddToListRLHidden();
+ }
+ break; // assume it's consistent, need to check only once
+ }
+ }
+}
+
+void SwTextNode::AddToListRLHidden()
+{
+ if (mpNodeNumRLHidden)
+ return;
+
+ SwList *const pList(FindList(this));
+ if (pList)
+ {
+ assert(!mpNodeNumRLHidden);
+ mpNodeNumRLHidden.reset(new SwNodeNum(this, true));
+ pList->InsertListItem(*mpNodeNumRLHidden, SwListRedlineType::HIDDEN, GetAttrListLevel(), GetDoc());
+ }
+}
+
+void SwTextNode::AddToListOrig()
+{
+ if (mpNodeNumOrig)
+ return;
+
+ SwList *const pList(FindList(this));
+ if (pList)
+ {
+ assert(!mpNodeNumOrig);
+ mpNodeNumOrig.reset(new SwNodeNum(this, true));
+ pList->InsertListItem(*mpNodeNumOrig, SwListRedlineType::ORIGTEXT, GetAttrListLevel(), GetDoc());
+ }
+}
+
+void SwTextNode::RemoveFromList()
+{
+ // sw_redlinehide: ensure it's removed from the other half too!
+ RemoveFromListRLHidden();
+ RemoveFromListOrig();
+ if ( IsInList() )
+ {
+ SwList::RemoveListItem(*mpNodeNum, GetDoc());
+ mpNodeNum.reset();
+
+ SetWordCountDirty( true );
+ }
+}
+
+void SwTextNode::RemoveFromListRLHidden()
+{
+ if (mpNodeNumRLHidden) // direct access because RemoveFromList doesn't have layout
+ {
+ assert(mpNodeNumRLHidden->GetParent() || !GetNodes().IsDocNodes());
+ SwList::RemoveListItem(*mpNodeNumRLHidden, GetDoc());
+ mpNodeNumRLHidden.reset();
+
+ SetWordCountDirty( true );
+ }
+}
+
+void SwTextNode::RemoveFromListOrig()
+{
+ if (mpNodeNumOrig) // direct access because RemoveFromList doesn't have layout
+ {
+ assert(mpNodeNumOrig->GetParent() || !GetNodes().IsDocNodes());
+ SwList::RemoveListItem(*mpNodeNumOrig, GetDoc());
+ mpNodeNumOrig.reset();
+
+ SetWordCountDirty( true );
+ }
+}
+
+bool SwTextNode::IsInList() const
+{
+ return GetNum() != nullptr && GetNum()->GetParent() != nullptr;
+}
+
+bool SwTextNode::IsFirstOfNumRule(SwRootFrame const& rLayout) const
+{
+ bool bResult = false;
+
+ SwNodeNum const*const pNum(GetNum(&rLayout));
+ if (pNum && pNum->GetNumRule())
+ bResult = pNum->IsFirst();
+
+ return bResult;
+}
+
+void SwTextNode::SetListId(OUString const& rListId)
+{
+ const SfxStringItem& rListIdItem =
+ GetAttr( RES_PARATR_LIST_ID );
+ if (rListIdItem.GetValue() != rListId)
+ {
+ if (rListId.isEmpty())
+ {
+ ResetAttr( RES_PARATR_LIST_ID );
+ }
+ else
+ {
+ SfxStringItem aNewListIdItem(RES_PARATR_LIST_ID, rListId);
+ SetAttr( aNewListIdItem );
+ }
+ }
+}
+
+OUString SwTextNode::GetListId() const
+{
+ const SfxStringItem& rListIdItem =
+ GetAttr( RES_PARATR_LIST_ID );
+ const OUString& sListId {rListIdItem.GetValue()};
+
+ // As long as no explicit list id attribute is set, use the list id of
+ // the list, which has been created for the applied list style.
+ if (sListId.isEmpty())
+ {
+ SwNumRule* pRule = GetNumRule();
+ if ( pRule )
+ {
+ return pRule->GetDefaultListId();
+ }
+ }
+
+ return sListId;
+}
+
+/** Determines, if the list level indent attributes can be applied to the
+ paragraph.
+
+ The list level indents can be applied to the paragraph under the one
+ of following conditions:
+ - the list style is directly applied to the paragraph and the paragraph
+ has no own indent attributes.
+ - the list style is applied to the paragraph through one of its paragraph
+ styles, the paragraph has no own indent attributes and on the paragraph
+ style hierarchy from the paragraph to the paragraph style with the
+ list style no indent attributes are found.
+
+ @return bitmask
+*/
+::sw::ListLevelIndents SwTextNode::AreListLevelIndentsApplicable() const
+{
+ ::sw::ListLevelIndents ret(::sw::ListLevelIndents::No);
+ if (AreListLevelIndentsApplicableImpl(RES_MARGIN_FIRSTLINE))
+ {
+ ret |= ::sw::ListLevelIndents::FirstLine;
+ }
+ if (AreListLevelIndentsApplicableImpl(RES_MARGIN_TEXTLEFT))
+ {
+ ret |= ::sw::ListLevelIndents::LeftMargin;
+ }
+ return ret;
+}
+
+bool SwTextNode::AreListLevelIndentsApplicableImpl(sal_uInt16 const nWhich) const
+{
+ bool bAreListLevelIndentsApplicable( true );
+
+ if ( !GetNum() || !GetNum()->GetNumRule() )
+ {
+ // no list style applied to paragraph
+ bAreListLevelIndentsApplicable = false;
+ }
+ else if ( HasSwAttrSet() &&
+ GetpSwAttrSet()->GetItemState(nWhich, false) == SfxItemState::SET)
+ {
+ // paragraph has hard-set indent attributes
+ bAreListLevelIndentsApplicable = false;
+ }
+ else if ( HasSwAttrSet() &&
+ GetpSwAttrSet()->GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET )
+ {
+ // list style is directly applied to paragraph and paragraph has no
+ // hard-set indent attributes
+ bAreListLevelIndentsApplicable = true;
+ }
+ else
+ {
+ // list style is applied through one of the paragraph styles and
+ // paragraph has no hard-set indent attributes
+
+ // check, paragraph's
+ const SwTextFormatColl* pColl = GetTextColl();
+ while ( pColl )
+ {
+ if (pColl->GetAttrSet().GetItemState(nWhich, false) == SfxItemState::SET)
+ {
+ // indent attributes found in the paragraph style hierarchy.
+ bAreListLevelIndentsApplicable = false;
+ break;
+ }
+
+ if ( pColl->GetAttrSet().GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET )
+ {
+ // paragraph style with the list style found and until now no
+ // indent attributes are found in the paragraph style hierarchy.
+ bAreListLevelIndentsApplicable = true;
+ break;
+ }
+
+ pColl = dynamic_cast<const SwTextFormatColl*>(pColl->DerivedFrom());
+ OSL_ENSURE( pColl,
+ "<SwTextNode::AreListLevelIndentsApplicable()> - something wrong in paragraph's style hierarchy. The applied list style is not found." );
+ }
+ }
+
+ return bAreListLevelIndentsApplicable;
+}
+
+/** Retrieves the list tab stop position, if the paragraph's list level defines
+ one and this list tab stop has to merged into the tap stops of the paragraph
+
+ @param nListTabStopPosition
+ output parameter - containing the list tab stop position
+
+ @return boolean - indicating, if a list tab stop position is provided
+*/
+bool SwTextNode::GetListTabStopPosition( tools::Long& nListTabStopPosition ) const
+{
+ bool bListTabStopPositionProvided(false);
+
+ const SwNumRule* pNumRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pNumRule && HasVisibleNumberingOrBullet() && GetActualListLevel() >= 0 )
+ {
+ const SwNumFormat& rFormat = pNumRule->Get( o3tl::narrowing<sal_uInt16>(GetActualListLevel()) );
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT &&
+ rFormat.GetLabelFollowedBy() == SvxNumberFormat::LISTTAB )
+ {
+ bListTabStopPositionProvided = true;
+ nListTabStopPosition = rFormat.GetListtabPos();
+
+ if ( getIDocumentSettingAccess()->get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) )
+ {
+ // tab stop position are treated to be relative to the "before text"
+ // indent value of the paragraph. Thus, adjust <nListTabStopPos>.
+ if (AreListLevelIndentsApplicable() & ::sw::ListLevelIndents::LeftMargin)
+ {
+ nListTabStopPosition -= rFormat.GetIndentAt();
+ }
+ else if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ SvxTextLeftMarginItem const aItem(GetSwAttrSet().GetTextLeftMargin());
+ nListTabStopPosition -= aItem.GetTextLeft();
+ }
+ }
+ }
+ }
+
+ return bListTabStopPositionProvided;
+}
+
+OUString SwTextNode::GetLabelFollowedBy() const
+{
+ const SwNumRule* pNumRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pNumRule && HasVisibleNumberingOrBullet() && GetActualListLevel() >= 0 )
+ {
+ const SwNumFormat& rFormat = pNumRule->Get( o3tl::narrowing<sal_uInt16>(GetActualListLevel()) );
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ return rFormat.GetLabelFollowedByAsString();
+ }
+ }
+
+ return OUString();
+}
+
+void SwTextNode::CalcHiddenCharFlags() const
+{
+ sal_Int32 nStartPos;
+ sal_Int32 nEndPos;
+ // Update of the flags is done inside GetBoundsOfHiddenRange()
+ SwScriptInfo::GetBoundsOfHiddenRange( *this, 0, nStartPos, nEndPos );
+}
+
+// #i12836# enhanced pdf export
+bool SwTextNode::IsHidden() const
+{
+ if ( IsHiddenByParaField() || HasHiddenCharAttribute( true ) )
+ return true;
+
+ const SwSectionNode* pSectNd = FindSectionNode();
+ return pSectNd && pSectNd->GetSection().IsHiddenFlag();
+}
+
+namespace {
+ // Helper class for special handling of setting attributes at text node:
+ // In constructor an instance of the helper class recognize whose attributes
+ // are set and perform corresponding actions before the intrinsic set of
+ // attributes has been taken place.
+ // In the destructor - after the attributes have been set at the text
+ // node - corresponding actions are performed.
+ // The following is handled:
+ // (1) When the list style attribute - RES_PARATR_NUMRULE - is set,
+ // (A) list style attribute is empty -> the text node is removed from
+ // its list.
+ // (B) list style attribute is not empty
+ // (a) text node has no list style -> add text node to its list after
+ // the attributes have been set.
+ // (b) text node has list style -> change of list style is notified
+ // after the attributes have been set.
+ // (2) When the list id attribute - RES_PARATR_LIST_ID - is set and changed,
+ // the text node is removed from its current list before the attributes
+ // are set and added to its new list after the attributes have been set.
+ // (3) Notify list tree, if list level - RES_PARATR_LIST_LEVEL - is set
+ // and changed after the attributes have been set
+ // (4) Notify list tree, if list restart - RES_PARATR_LIST_ISRESTART - is set
+ // and changed after the attributes have been set
+ // (5) Notify list tree, if list restart value - RES_PARATR_LIST_RESTARTVALUE -
+ // is set and changed after the attributes have been set
+ // (6) Notify list tree, if count in list - RES_PARATR_LIST_ISCOUNTED - is set
+ // and changed after the attributes have been set
+ // (7) Set or Reset empty list style due to changed outline level - RES_PARATR_OUTLINELEVEL.
+ class HandleSetAttrAtTextNode
+ {
+ public:
+ HandleSetAttrAtTextNode( SwTextNode& rTextNode,
+ const SfxPoolItem& pItem );
+ HandleSetAttrAtTextNode( SwTextNode& rTextNode,
+ const SfxItemSet& rItemSet );
+ ~HandleSetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE;
+
+ private:
+ SwTextNode& mrTextNode;
+ bool mbAddTextNodeToList;
+ bool mbUpdateListLevel;
+ bool mbUpdateListRestart;
+ bool mbUpdateListCount;
+ // #i70748#
+ bool mbOutlineLevelSet;
+ };
+
+ HandleSetAttrAtTextNode::HandleSetAttrAtTextNode( SwTextNode& rTextNode,
+ const SfxPoolItem& pItem )
+ : mrTextNode( rTextNode ),
+ mbAddTextNodeToList( false ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false ),
+ // #i70748#
+ mbOutlineLevelSet( false )
+ {
+ switch ( pItem.Which() )
+ {
+ // handle RES_PARATR_NUMRULE
+ case RES_PARATR_NUMRULE:
+ {
+ mrTextNode.RemoveFromList();
+
+ const SwNumRuleItem& rNumRuleItem =
+ dynamic_cast<const SwNumRuleItem&>(pItem);
+ if ( !rNumRuleItem.GetValue().isEmpty() )
+ {
+ mbAddTextNodeToList = true;
+ // #i105562#
+
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+ }
+ break;
+
+ // handle RES_PARATR_LIST_ID
+ case RES_PARATR_LIST_ID:
+ {
+ const SfxStringItem& rListIdItem =
+ dynamic_cast<const SfxStringItem&>(pItem);
+ OSL_ENSURE( rListIdItem.GetValue().getLength() > 0,
+ "<HandleSetAttrAtTextNode(..)> - empty list id attribute not expected. Serious defect." );
+ const OUString sListIdOfTextNode = rTextNode.GetListId();
+ if ( rListIdItem.GetValue() != sListIdOfTextNode )
+ {
+ mbAddTextNodeToList = true;
+ if ( mrTextNode.IsInList() )
+ {
+ mrTextNode.RemoveFromList();
+ }
+ }
+ }
+ break;
+
+ // handle RES_PARATR_LIST_LEVEL
+ case RES_PARATR_LIST_LEVEL:
+ {
+ const SfxInt16Item& aListLevelItem =
+ dynamic_cast<const SfxInt16Item&>(pItem);
+ if ( aListLevelItem.GetValue() != mrTextNode.GetAttrListLevel() )
+ {
+ mbUpdateListLevel = true;
+ }
+ }
+ break;
+
+ // handle RES_PARATR_LIST_ISRESTART
+ case RES_PARATR_LIST_ISRESTART:
+ {
+ const SfxBoolItem& aListIsRestartItem =
+ dynamic_cast<const SfxBoolItem&>(pItem);
+ if ( aListIsRestartItem.GetValue() !=
+ mrTextNode.IsListRestart() )
+ {
+ mbUpdateListRestart = true;
+ }
+ }
+ break;
+
+ // handle RES_PARATR_LIST_RESTARTVALUE
+ case RES_PARATR_LIST_RESTARTVALUE:
+ {
+ const SfxInt16Item& aListRestartValueItem =
+ dynamic_cast<const SfxInt16Item&>(pItem);
+ if ( !mrTextNode.HasAttrListRestartValue() ||
+ aListRestartValueItem.GetValue() != mrTextNode.GetAttrListRestartValue() )
+ {
+ mbUpdateListRestart = true;
+ }
+ }
+ break;
+
+ // handle RES_PARATR_LIST_ISCOUNTED
+ case RES_PARATR_LIST_ISCOUNTED:
+ {
+ const SfxBoolItem& aIsCountedInListItem =
+ dynamic_cast<const SfxBoolItem&>(pItem);
+ if ( aIsCountedInListItem.GetValue() !=
+ mrTextNode.IsCountedInList() )
+ {
+ mbUpdateListCount = true;
+ }
+ }
+ break;
+
+ // #i70748#
+ // handle RES_PARATR_OUTLINELEVEL
+ case RES_PARATR_OUTLINELEVEL:
+ {
+ const SfxUInt16Item& aOutlineLevelItem =
+ dynamic_cast<const SfxUInt16Item&>(pItem);
+ if ( aOutlineLevelItem.GetValue() != mrTextNode.GetAttrOutlineLevel() )
+ {
+ mbOutlineLevelSet = true;
+ }
+ }
+ break;
+ }
+
+ }
+
+ HandleSetAttrAtTextNode::HandleSetAttrAtTextNode( SwTextNode& rTextNode,
+ const SfxItemSet& rItemSet )
+ : mrTextNode( rTextNode ),
+ mbAddTextNodeToList( false ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false ),
+ // #i70748#
+ mbOutlineLevelSet( false )
+ {
+ // handle RES_PARATR_NUMRULE
+ if ( const SwNumRuleItem* pNumRuleItem = rItemSet.GetItemIfSet( RES_PARATR_NUMRULE, false ) )
+ {
+ mrTextNode.RemoveFromList();
+
+ if ( !pNumRuleItem->GetValue().isEmpty() )
+ {
+ mbAddTextNodeToList = true;
+ // #i70748#
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+ }
+
+ // handle RES_PARATR_LIST_ID
+ if ( const SfxStringItem* pListIdItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_ID, false ) )
+ {
+ const OUString sListIdOfTextNode = mrTextNode.GetListId();
+ if ( pListIdItem->GetValue() != sListIdOfTextNode )
+ {
+ mbAddTextNodeToList = true;
+ if ( mrTextNode.IsInList() )
+ {
+ mrTextNode.RemoveFromList();
+ }
+ }
+ }
+
+ // handle RES_PARATR_LIST_LEVEL
+ if ( const SfxInt16Item* pListLevelItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_LEVEL, false ) )
+ {
+ if (pListLevelItem->GetValue() != mrTextNode.GetAttrListLevel())
+ {
+ mbUpdateListLevel = true;
+ }
+ }
+
+ // handle RES_PARATR_LIST_ISRESTART
+ if ( const SfxBoolItem* pListIsRestartItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_ISRESTART, false ) )
+ {
+ if (pListIsRestartItem->GetValue() != mrTextNode.IsListRestart())
+ {
+ mbUpdateListRestart = true;
+ }
+ }
+
+ // handle RES_PARATR_LIST_RESTARTVALUE
+ if ( const SfxInt16Item* pListRestartValueItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_RESTARTVALUE, false ) )
+ {
+ if ( !mrTextNode.HasAttrListRestartValue() ||
+ pListRestartValueItem->GetValue() != mrTextNode.GetAttrListRestartValue() )
+ {
+ mbUpdateListRestart = true;
+ }
+ }
+
+ // handle RES_PARATR_LIST_ISCOUNTED
+ if ( const SfxBoolItem* pIsCountedInListItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_ISCOUNTED, false ) )
+ {
+ if (pIsCountedInListItem->GetValue() != mrTextNode.IsCountedInList())
+ {
+ mbUpdateListCount = true;
+ }
+ }
+
+ // #i70748#
+ // handle RES_PARATR_OUTLINELEVEL
+ if ( const SfxUInt16Item* pOutlineLevelItem = rItemSet.GetItemIfSet( RES_PARATR_OUTLINELEVEL, false ) )
+ {
+ if (pOutlineLevelItem->GetValue() != mrTextNode.GetAttrOutlineLevel())
+ {
+ mbOutlineLevelSet = true;
+ }
+ }
+ }
+
+ HandleSetAttrAtTextNode::~HandleSetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE
+ {
+ if ( mbAddTextNodeToList )
+ {
+ SwNumRule* pNumRuleAtTextNode = mrTextNode.GetNumRule();
+ if ( pNumRuleAtTextNode )
+ {
+ mrTextNode.AddToList();
+ }
+ }
+ else
+ {
+ if ( mbUpdateListLevel && mrTextNode.IsInList() )
+ {
+ auto const nLevel(mrTextNode.GetAttrListLevel());
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [nLevel, &rDoc](SwNodeNum & rNum) { rNum.SetLevelInListTree(nLevel, rDoc); });
+ }
+
+ if ( mbUpdateListRestart && mrTextNode.IsInList() )
+ {
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [&rDoc](SwNodeNum & rNum) {
+ rNum.InvalidateMe();
+ rNum.NotifyInvalidSiblings(rDoc);
+ });
+ }
+
+ if (mbUpdateListCount && mrTextNode.IsInList() && HasNumberingWhichNeedsLayoutUpdate(mrTextNode))
+ {
+ // Repaint all text frames that belong to this numbering to avoid outdated generated
+ // numbers.
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [&rDoc](SwNodeNum & rNum) { rNum.InvalidateAndNotifyTree(rDoc); });
+ }
+ }
+
+ // #i70748#
+ if (!mbOutlineLevelSet)
+ return;
+
+ mrTextNode.GetNodes().UpdateOutlineNode(mrTextNode);
+ if (mrTextNode.GetAttrOutlineLevel() == 0)
+ {
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+ else
+ {
+ if ( mrTextNode.GetSwAttrSet().GetItemState( RES_PARATR_NUMRULE )
+ != SfxItemState::SET )
+ {
+ mrTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr();
+ }
+ }
+ }
+ // End of class <HandleSetAttrAtTextNode>
+}
+
+bool SwTextNode::SetAttr( const SfxPoolItem& pItem )
+{
+ const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
+ mbInSetOrResetAttr = true;
+
+ HandleSetAttrAtTextNode aHandleSetAttr( *this, pItem );
+
+ bool bRet = SwContentNode::SetAttr( pItem );
+
+ mbInSetOrResetAttr = bOldIsSetOrResetAttr;
+
+ return bRet;
+}
+
+bool SwTextNode::SetAttr( const SfxItemSet& rSet )
+{
+ const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
+ mbInSetOrResetAttr = true;
+
+ HandleSetAttrAtTextNode aHandleSetAttr( *this, rSet );
+
+ bool bRet = SwContentNode::SetAttr( rSet );
+
+ mbInSetOrResetAttr = bOldIsSetOrResetAttr;
+
+ return bRet;
+}
+
+void SwTextNode::SetInSwUndo(bool bInUndo)
+{
+ m_bInUndo = bInUndo;
+}
+
+namespace {
+ // Helper class for special handling of resetting attributes at text node:
+ // In constructor an instance of the helper class recognize whose attributes
+ // are reset and perform corresponding actions before the intrinsic reset of
+ // attributes has been taken place.
+ // In the destructor - after the attributes have been reset at the text
+ // node - corresponding actions are performed.
+ // The following is handled:
+ // (1) When the list style attribute - RES_PARATR_NUMRULE - is reset,
+ // the text is removed from its list before the attributes have been reset.
+ // (2) When the list id attribute - RES_PARATR_LIST_ID - is reset,
+ // the text is removed from its list before the attributes have been reset.
+ // (3) Notify list tree, if list level - RES_PARATR_LIST_LEVEL - is reset.
+ // (4) Notify list tree, if list restart - RES_PARATR_LIST_ISRESTART - is reset.
+ // (5) Notify list tree, if list restart value - RES_PARATR_LIST_RESTARTVALUE - is reset.
+ // (6) Notify list tree, if count in list - RES_PARATR_LIST_ISCOUNTED - is reset.
+ // (7) Reset empty list style, if outline level attribute - RES_PARATR_OUTLINELEVEL - is reset.
+ class HandleResetAttrAtTextNode
+ {
+ public:
+ HandleResetAttrAtTextNode( SwTextNode& rTextNode,
+ const sal_uInt16 nWhich1,
+ sal_uInt16 nWhich2 );
+ HandleResetAttrAtTextNode( SwTextNode& rTextNode,
+ const std::vector<sal_uInt16>& rWhichArr );
+ explicit HandleResetAttrAtTextNode( SwTextNode& rTextNode );
+
+ ~HandleResetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE;
+
+ private:
+ SwTextNode& mrTextNode;
+ bool mbListStyleOrIdReset;
+ bool mbUpdateListLevel;
+ bool mbUpdateListRestart;
+ bool mbUpdateListCount;
+
+ void init( sal_uInt16 nWhich, bool& rbRemoveFromList );
+ };
+
+ HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode,
+ const sal_uInt16 nWhich1,
+ sal_uInt16 nWhich2 )
+ : mrTextNode( rTextNode ),
+ mbListStyleOrIdReset( false ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false )
+ {
+ if ( nWhich2 < nWhich1 )
+ nWhich2 = nWhich1;
+ bool bRemoveFromList( false );
+ for ( sal_uInt16 nWhich = nWhich1; nWhich <= nWhich2; ++nWhich )
+ init( nWhich, bRemoveFromList );
+ if ( bRemoveFromList && mrTextNode.IsInList() )
+ mrTextNode.RemoveFromList();
+ }
+
+ HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode,
+ const std::vector<sal_uInt16>& rWhichArr )
+ : mrTextNode( rTextNode ),
+ mbListStyleOrIdReset( false ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false )
+ {
+ bool bRemoveFromList( false );
+ for ( sal_uInt16 nWhich : rWhichArr )
+ init( nWhich, bRemoveFromList );
+ if ( bRemoveFromList && mrTextNode.IsInList() )
+ mrTextNode.RemoveFromList();
+ }
+
+ HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode )
+ : mrTextNode( rTextNode ),
+ mbListStyleOrIdReset( true ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false )
+ {
+ if ( rTextNode.IsInList() )
+ {
+ rTextNode.RemoveFromList();
+ }
+ // #i70748#
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+
+ void HandleResetAttrAtTextNode::init( sal_uInt16 rWhich, bool& rbRemoveFromList )
+ {
+ if ( rWhich == RES_PARATR_NUMRULE )
+ {
+ rbRemoveFromList = rbRemoveFromList ||
+ mrTextNode.GetNumRule() != nullptr;
+ mbListStyleOrIdReset = true;
+ }
+ else if ( rWhich == RES_PARATR_LIST_ID )
+ {
+ rbRemoveFromList = rbRemoveFromList ||
+ ( mrTextNode.GetpSwAttrSet() &&
+ mrTextNode.GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_ID, false ) == SfxItemState::SET );
+ mbListStyleOrIdReset = true;
+ }
+ else if ( rWhich == RES_PARATR_OUTLINELEVEL )
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ else if ( rWhich == RES_BACKGROUND )
+ mrTextNode.ResetAttr( XATTR_FILL_FIRST, XATTR_FILL_LAST );
+
+ if ( !rbRemoveFromList )
+ {
+ // RES_PARATR_LIST_LEVEL
+ mbUpdateListLevel = mbUpdateListLevel ||
+ ( rWhich == RES_PARATR_LIST_LEVEL &&
+ mrTextNode.HasAttrListLevel() );
+
+ // RES_PARATR_LIST_ISRESTART and RES_PARATR_LIST_RESTARTVALUE
+ mbUpdateListRestart = mbUpdateListRestart ||
+ ( rWhich == RES_PARATR_LIST_ISRESTART &&
+ mrTextNode.IsListRestart() ) ||
+ ( rWhich == RES_PARATR_LIST_RESTARTVALUE &&
+ mrTextNode.HasAttrListRestartValue() );
+
+ // RES_PARATR_LIST_ISCOUNTED
+ mbUpdateListCount = mbUpdateListCount ||
+ ( rWhich == RES_PARATR_LIST_ISCOUNTED &&
+ !mrTextNode.IsCountedInList() );
+ }
+ }
+
+ HandleResetAttrAtTextNode::~HandleResetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE
+ {
+ if ( mbListStyleOrIdReset && !mrTextNode.IsInList() )
+ {
+ // check, if in spite of the reset of the list style or the list id
+ // the paragraph still has to be added to a list.
+ if (mrTextNode.GetNumRule() && !mrTextNode.GetListId().isEmpty())
+ {
+ // #i96062#
+ // If paragraph has no list level attribute set and list style
+ // is the outline style, apply outline level as the list level.
+ if ( !mrTextNode.HasAttrListLevel() &&
+ mrTextNode.GetNumRule()->GetName()==SwNumRule::GetOutlineRuleName() &&
+ mrTextNode.GetTextColl()->IsAssignedToListLevelOfOutlineStyle() )
+ {
+ int nNewListLevel = mrTextNode.GetTextColl()->GetAssignedOutlineStyleLevel();
+ if ( 0 <= nNewListLevel && nNewListLevel < MAXLEVEL )
+ {
+ mrTextNode.SetAttrListLevel( nNewListLevel );
+ }
+ }
+ mrTextNode.AddToList();
+ }
+ // #i70748#
+ // #i105562#
+ else
+ {
+ if (mrTextNode.GetpSwAttrSet()
+ && mrTextNode.GetAttr(RES_PARATR_OUTLINELEVEL, false).GetValue() > 0)
+ {
+ mrTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr();
+ }
+ }
+ }
+
+ if ( !mrTextNode.IsInList() )
+ return;
+
+ if ( mbUpdateListLevel )
+ {
+ auto const nLevel(mrTextNode.GetAttrListLevel());
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [nLevel, &rDoc](SwNodeNum & rNum) { rNum.SetLevelInListTree(nLevel, rDoc); });
+ }
+
+ if ( mbUpdateListRestart )
+ {
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [&rDoc](SwNodeNum & rNum) {
+ rNum.InvalidateMe();
+ rNum.NotifyInvalidSiblings(rDoc);
+ });
+ }
+
+ if ( mbUpdateListCount )
+ {
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [&rDoc](SwNodeNum & rNum) { rNum.InvalidateAndNotifyTree(rDoc); });
+ }
+ }
+ // End of class <HandleResetAttrAtTextNode>
+}
+
+bool SwTextNode::ResetAttr( sal_uInt16 nWhich1, sal_uInt16 nWhich2 )
+{
+ const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
+ mbInSetOrResetAttr = true;
+
+ HandleResetAttrAtTextNode aHandleResetAttr( *this, nWhich1, nWhich2 );
+
+ bool bRet = SwContentNode::ResetAttr( nWhich1, nWhich2 );
+
+ mbInSetOrResetAttr = bOldIsSetOrResetAttr;
+
+ return bRet;
+}
+
+bool SwTextNode::ResetAttr( const std::vector<sal_uInt16>& rWhichArr )
+{
+ const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
+ mbInSetOrResetAttr = true;
+
+ HandleResetAttrAtTextNode aHandleResetAttr( *this, rWhichArr );
+
+ bool bRet = SwContentNode::ResetAttr( rWhichArr );
+
+ mbInSetOrResetAttr = bOldIsSetOrResetAttr;
+
+ return bRet;
+}
+
+sal_uInt16 SwTextNode::ResetAllAttr()
+{
+ const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
+ mbInSetOrResetAttr = true;
+
+ HandleResetAttrAtTextNode aHandleResetAttr( *this );
+
+ const sal_uInt16 nRet = SwContentNode::ResetAllAttr();
+
+ mbInSetOrResetAttr = bOldIsSetOrResetAttr;
+
+ return nRet;
+}
+
+void SwTextNode::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextNode"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(sal_Int32(GetIndex())).getStr()));
+
+ OUString sText = GetText();
+ for (int i = 0; i < 32; ++i)
+ sText = sText.replace(i, '*');
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_Text"));
+ (void)xmlTextWriterWriteString(pWriter, BAD_CAST(sText.toUtf8().getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+
+ if (GetFormatColl())
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColl"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetFormatColl()->GetName().toUtf8().getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ if (HasSwAttrSet())
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwAttrSet"));
+ GetSwAttrSet().dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ if (HasHints())
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwpHints"));
+ const SwpHints& rHints = GetSwpHints();
+ for (size_t i = 0; i < rHints.Count(); ++i)
+ rHints.Get(i)->dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+sal_uInt32 SwTextNode::GetRsid( sal_Int32 nStt, sal_Int32 nEnd ) const
+{
+ SfxItemSetFixed<RES_CHRATR_RSID, RES_CHRATR_RSID> aSet( const_cast<SwAttrPool&>((GetDoc().GetAttrPool())) );
+ if (GetParaAttr(aSet, nStt, nEnd))
+ {
+ const SvxRsidItem* pRsid = aSet.GetItem<SvxRsidItem>(RES_CHRATR_RSID);
+ if( pRsid )
+ return pRsid->GetValue();
+ }
+
+ return 0;
+}
+
+sal_uInt32 SwTextNode::GetParRsid() const
+{
+ return reinterpret_cast<const SvxRsidItem&>(GetAttr( RES_PARATR_RSID )).GetValue();
+}
+
+bool SwTextNode::CompareParRsid( const SwTextNode &rTextNode ) const
+{
+ sal_uInt32 nThisRsid = GetParRsid();
+ sal_uInt32 nRsid = rTextNode.GetParRsid();
+
+ return nThisRsid == nRsid;
+}
+
+bool SwTextNode::CompareRsid( const SwTextNode &rTextNode, sal_Int32 nStt1, sal_Int32 nStt2 ) const
+{
+ sal_uInt32 nThisRsid = GetRsid( nStt1, nStt1 );
+ sal_uInt32 nRsid = rTextNode.GetRsid( nStt2, nStt2 );
+
+ return nThisRsid == nRsid;
+}
+
+// sw::Metadatable
+::sfx2::IXmlIdRegistry& SwTextNode::GetRegistry()
+{
+ return GetDoc().GetXmlIdRegistry();
+}
+
+bool SwTextNode::IsInClipboard() const
+{
+ return GetDoc().IsClipBoard();
+}
+
+bool SwTextNode::IsInUndo() const
+{
+ return GetDoc().GetIDocumentUndoRedo().IsUndoNodes(GetNodes());
+}
+
+bool SwTextNode::IsInContent() const
+{
+ return !GetDoc().IsInHeaderFooter( *this );
+}
+
+void SwTextNode::HandleNonLegacyHint(const SfxHint& rHint)
+{
+ assert(!dynamic_cast<const sw::LegacyModifyHint*>(&rHint));
+ sw::TextNodeNotificationSuppressor(*this);
+ CallSwClientNotify(rHint);
+
+ SwDoc& rDoc = GetDoc();
+ // #125329# - assure that text node is in document nodes array
+ if ( !rDoc.IsInDtor() && &rDoc.GetNodes() == &GetNodes() )
+ {
+ rDoc.GetNodes().UpdateOutlineNode(*this);
+ }
+}
+
+void SwTextNode::UpdateDocPos(const SwTwips nDocPos, const sal_uInt32 nIndex)
+{
+ const sw::DocPosUpdateAtIndex aHint(nDocPos, *this, nIndex);
+ CallSwClientNotify(aHint);
+}
+
+void SwTextNode::TriggerNodeUpdate(const sw::LegacyModifyHint& rHint)
+{
+ const auto pOldValue = rHint.m_pOld;
+ const auto pNewValue = rHint.m_pNew;
+ {
+ sw::TextNodeNotificationSuppressor(*this);
+
+ // Override Modify so that deleting styles works properly (outline
+ // numbering!).
+ // Never call ChgTextCollUpdateNum for Nodes in Undo.
+ if( pOldValue
+ && pNewValue
+ && RES_FMT_CHG == pOldValue->Which()
+ && GetRegisteredIn() == static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat
+ && GetNodes().IsDocNodes() )
+ {
+ assert(dynamic_cast<SwTextFormatColl const*>(static_cast<const SwFormatChg*>(pOldValue)->pChangedFormat));
+ assert(dynamic_cast<SwTextFormatColl const*>(static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat));
+ ChgTextCollUpdateNum(
+ static_cast<const SwTextFormatColl*>(static_cast<const SwFormatChg*>(pOldValue)->pChangedFormat),
+ static_cast<const SwTextFormatColl*>(static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat) );
+ }
+
+ // reset fill information
+ if (maFillAttributes && pNewValue)
+ {
+ const sal_uInt16 nWhich = pNewValue->Which();
+ bool bReset(RES_FMT_CHG == nWhich); // ..on format change (e.g. style changed)
+
+ if(!bReset && RES_ATTRSET_CHG == nWhich) // ..on ItemChange from DrawingLayer FillAttributes
+ {
+ SfxItemIter aIter(*static_cast<const SwAttrSetChg*>(pNewValue)->GetChgSet());
+
+ for(const SfxPoolItem* pItem = aIter.GetCurItem(); pItem && !bReset; pItem = aIter.NextItem())
+ {
+ bReset = !IsInvalidItem(pItem) && pItem->Which() >= XATTR_FILL_FIRST && pItem->Which() <= XATTR_FILL_LAST;
+ }
+ }
+
+ if(bReset)
+ {
+ maFillAttributes.reset();
+ }
+ }
+
+ if ( !mbInSetOrResetAttr )
+ {
+ HandleModifyAtTextNode( *this, pOldValue, pNewValue );
+ }
+
+ SwContentNode::SwClientNotify(*this, rHint);
+
+ SwDoc& rDoc = GetDoc();
+ // #125329# - assure that text node is in document nodes array
+ if ( !rDoc.IsInDtor() && &rDoc.GetNodes() == &GetNodes() )
+ {
+ rDoc.GetNodes().UpdateOutlineNode(*this);
+ }
+ }
+
+ if (pOldValue && (RES_REMOVE_UNO_OBJECT == pOldValue->Which()))
+ { // invalidate cached uno object
+ SetXParagraph(nullptr);
+ }
+}
+
+void SwTextNode::SwClientNotify( const SwModify& rModify, const SfxHint& rHint )
+{
+ if(rHint.GetId() == SfxHintId::SwAutoFormatUsedHint)
+ {
+ static_cast<const sw::AutoFormatUsedHint&>(rHint).CheckNode(this);
+ return;
+ }
+ else if (rHint.GetId() == SfxHintId::SwLegacyModify)
+ {
+ auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ TriggerNodeUpdate(*pLegacyHint);
+ }
+ else if (dynamic_cast<const SwAttrHint*>(&rHint))
+ {
+ if (&rModify == GetRegisteredIn())
+ ChkCondColl();
+ }
+}
+
+uno::Reference< rdf::XMetadatable >
+SwTextNode::MakeUnoObject()
+{
+ const uno::Reference<rdf::XMetadatable> xMeta(
+ SwXParagraph::CreateXParagraph(GetDoc(), this, nullptr));
+ return xMeta;
+}
+
+drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwTextNode::getSdrAllFillAttributesHelper() const
+{
+ // create SdrAllFillAttributesHelper on demand
+ if(!maFillAttributes)
+ {
+ const_cast< SwTextNode* >(this)->maFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(GetSwAttrSet());
+ }
+
+ return maFillAttributes;
+}
+
+void SwTextNode::SetXParagraph(rtl::Reference<SwXParagraph> const & xParagraph)
+{
+ m_wXParagraph = xParagraph.get();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */