summaryrefslogtreecommitdiffstats
path: root/sw/source/core/txtnode/ndtxt.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /sw/source/core/txtnode/ndtxt.cxx
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
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.cxx5326
1 files changed, 5326 insertions, 0 deletions
diff --git a/sw/source/core/txtnode/ndtxt.cxx b/sw/source/core/txtnode/ndtxt.cxx
new file mode 100644
index 000000000..62b817138
--- /dev/null
+++ b/sw/source/core/txtnode/ndtxt.cxx
@@ -0,0 +1,5326 @@
+/* -*- 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 <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 <fmtmeta.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 <rootfrm.hxx>
+#include <expfld.hxx>
+#include <section.hxx>
+#include <mvsave.hxx>
+#include <swcache.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/intitem.hxx>
+#include <list.hxx>
+#include <sortedobjs.hxx>
+#include <calbck.hxx>
+#include <attrhint.hxx>
+#include <memory>
+#include <unoparagraph.hxx>
+#include <wrtsh.hxx>
+#include <frameformats.hxx>
+#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx>
+#include <svl/itemiter.hxx>
+
+using namespace ::com::sun::star;
+
+typedef std::vector<SwTextAttr*> SwpHts;
+
+
+// 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( const SwNodeIndex & rWhere,
+ SwTextFormatColl *pColl, bool const bNewFrames)
+{
+ OSL_ENSURE( pColl, "Collection pointer is 0." );
+
+ SwTextNode *pNode = new SwTextNode( rWhere, pColl, nullptr );
+
+ SwNodeIndex aIdx( *pNode );
+
+ // call method <UpdateOutlineNode(..)> only for the document nodes array
+ if ( IsDocNodes() )
+ UpdateOutlineNode(*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() )
+ {
+ SwNodeIndex aTmpIdx( *pNode );
+ pNd = FindPrvNxtFrameNode( aTmpIdx, 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 )
+ aTmp -= 2;
+ else
+ return pNode;
+ break;
+ }
+ } while( true );
+}
+
+SwTextNode::SwTextNode( const SwNodeIndex &rWhere, SwTextFormatColl *pTextColl, const SfxItemSet* pAutoAttr )
+: SwContentNode( rWhere, SwNodeType::Text, pTextColl ),
+ m_Text(),
+ m_pParaIdleData_Impl(nullptr),
+ m_bContainsHiddenChars(false),
+ m_bHiddenCharsHidePara(false),
+ m_bRecalcHiddenCharFlags(false),
+ m_bLastOutlineState( false ),
+ m_bNotifiable( false ),
+ mbEmptyListStyleSetDueToSetOutlineLevelAttr( false ),
+ mbInSetOrResetAttr( false ),
+ m_pNumStringCache(),
+ m_wXParagraph(),
+ maFillAttributes()
+{
+ InitSwParaStatistics( true );
+
+ 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();
+ }
+ GetNodes().UpdateOutlineNode(*this);
+
+ m_bNotifiable = true;
+
+ 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();
+
+ InitSwParaStatistics( false );
+ DelFrames(nullptr); // must be called here while it's still a SwTextNode
+ DelFrames_TextNodePart();
+}
+
+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() )
+ {
+ 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 (sal_uLong 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.GetContentAnchor()->nNode.GetIndex())
+ {
+ // move it to the new frame of "this"
+ rFormat.NotifyClients(&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)> 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.nContent.GetIndex();
+ const sal_Int32 nTextLen = m_Text.getLength();
+ SwTextNode* const pNode =
+ MakeNewTextNode( rPos.nNode, 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 );
+ }
+
+ if ( HasWriterListeners() && !m_Text.isEmpty() && (nTextLen / 2) < nSplitPos )
+ {
+ // 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.
+ SwIndex aIdx( this );
+ CutText( pNode, aIdx, nSplitPos );
+
+ if( GetWrong() )
+ {
+ pNode->SetWrong( GetWrong()->SplitList( nSplitPos ) );
+ }
+ SetWrongDirty(WrongState::TODO);
+
+ if( GetGrammarCheck() )
+ {
+ pNode->SetGrammarCheck( GetGrammarCheck()->SplitGrammarList( nSplitPos ) );
+ }
+ SetGrammarCheckDirty( true );
+
+ SetWordCountDirty( true );
+
+ if( GetSmartTags() )
+ {
+ pNode->SetSmartTags( GetSmartTags()->SplitList( nSplitPos ) );
+ }
+ SetSmartTagDirty( true );
+
+ 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);
+ }
+ 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()->IsHideRedlines())
+ {
+ isHide = true;
+ }
+ frames.push_back(pFrame);
+ }
+ for (SwTextFrame * pFrame : frames)
+ {
+ pFrame->RegisterToNode( *pNode );
+ if (!pFrame->IsFollow() && pFrame->GetOffset())
+ {
+ pFrame->SetOffset( TextFrameIndex(0) );
+ }
+ }
+
+ if ( IsInCache() )
+ {
+ SwFrame::GetCache().Delete( this );
+ SetInCache( false );
+ }
+
+ 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);
+ }
+ if (eOldMergeFlag != SwNode::Merge::None)
+ {
+ MoveMergedFlysAndFootnotes(frames, *pNode, *this, true);
+ }
+ }
+ else
+ {
+ SwWrongList *pList = GetWrong();
+ SetWrong( nullptr, false );
+ SetWrongDirty(WrongState::TODO);
+
+ SwGrammarMarkUp *pList3 = GetGrammarCheck();
+ SetGrammarCheck( nullptr, false );
+ SetGrammarCheckDirty( true );
+
+ SetWordCountDirty( true );
+
+ SwWrongList *pList2 = GetSmartTags();
+ SetSmartTags( nullptr, false );
+ SetSmartTagDirty( true );
+
+ SwIndex 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( pList, false );
+ }
+
+ if( pList3 )
+ {
+ pNode->SetGrammarCheck( pList3->SplitGrammarList( nSplitPos ) );
+ SetGrammarCheck( pList3, false );
+ }
+
+ if( pList2 )
+ {
+ pNode->SetSmartTags( pList2->SplitList( nSplitPos ) );
+ SetSmartTags( pList2, false );
+ }
+
+ if (pContentIndexRestore)
+ { // call before making frames and before RegisterToNode
+ (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::NonFlys);
+ }
+
+ 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()->IsHideRedlines())
+ {
+ 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 (pFrame->GetText().isEmpty())
+ {
+ // turns out it's empty - 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);
+ }
+
+ if (bRecreateThis)
+ {
+ MoveMergedFlysAndFootnotes(frames, *pNode, *this, true);
+ }
+ }
+
+#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 SfxPoolItem *pItem;
+ if( HasWriterListeners() && SfxItemState::SET == pNode->GetSwAttrSet().
+ GetItemState( RES_PAGEDESC, true, &pItem ) )
+ {
+ pNode->ModifyNotification( 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()->IsHideRedlines())
+ {
+ 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()->IsHideRedlines())
+ {
+ 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() != 0)
+ {
+ for (sal_uLong 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()->IsHideRedlines())
+ {
+ 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));
+ assert(pFrame->GetMergedPara());
+ assert(pFrame->GetMergedPara()->listener.IsListeningTo(&rNode));
+ assert(rNode.GetIndex() <= pFrame->GetMergedPara()->pLastNode->GetIndex());
+ 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()->IsHideRedlines())
+ {
+ rNode.SetRedlineMergeFlag(SwNode::Merge::None);
+ break; // checking once is enough
+ }
+ }
+ }
+}
+
+} // namespace
+
+SwContentNode *SwTextNode::JoinNext()
+{
+ SwNodes& rNds = GetNodes();
+ SwNodeIndex aIdx( *this );
+ if( SwContentNode::CanJoinNext( &aIdx ) )
+ {
+ SwDoc* pDoc = rNds.GetDoc();
+ const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
+ pContentStore->Save(pDoc, aIdx.GetIndex(), SAL_MAX_INT32);
+ SwTextNode *pTextNode = aIdx.GetNode().GetTextNode();
+ sal_Int32 nOldLen = m_Text.getLength();
+
+ // METADATA: merge
+ JoinMetadatable(*pTextNode, !Len(), !pTextNode->Len());
+
+ SwWrongList *pList = GetWrong();
+ if( pList )
+ {
+ pList->JoinList( pTextNode->GetWrong(), nOldLen );
+ SetWrongDirty(WrongState::TODO);
+ SetWrong( nullptr, false );
+ }
+ else
+ {
+ pList = pTextNode->GetWrong();
+ if( pList )
+ {
+ pList->Move( 0, nOldLen );
+ SetWrongDirty(WrongState::TODO);
+ pTextNode->SetWrong( nullptr, false );
+ }
+ }
+
+ SwGrammarMarkUp *pList3 = GetGrammarCheck();
+ if( pList3 )
+ {
+ pList3->JoinGrammarList( pTextNode->GetGrammarCheck(), nOldLen );
+ SetGrammarCheckDirty( true );
+ SetGrammarCheck( nullptr, false );
+ }
+ else
+ {
+ pList3 = pTextNode->GetGrammarCheck();
+ if( pList3 )
+ {
+ pList3->MoveGrammar( 0, nOldLen );
+ SetGrammarCheckDirty( true );
+ pTextNode->SetGrammarCheck( nullptr, false );
+ }
+ }
+
+ SwWrongList *pList2 = GetSmartTags();
+ if( pList2 )
+ {
+ pList2->JoinList( pTextNode->GetSmartTags(), nOldLen );
+ SetSmartTagDirty( true );
+ SetSmartTags( nullptr, false );
+ }
+ else
+ {
+ pList2 = pTextNode->GetSmartTags();
+ if( pList2 )
+ {
+ pList2->Move( 0, nOldLen );
+ SetSmartTagDirty( true );
+ pTextNode->SetSmartTags( nullptr, false );
+ }
+ }
+
+ { // scope for SwIndex
+ pTextNode->CutText( this, SwIndex(pTextNode), pTextNode->Len() );
+ }
+ // move all Bookmarks/TOXMarks
+ if( !pContentStore->Empty())
+ pContentStore->Restore( pDoc, GetIndex(), nOldLen );
+
+ if( pTextNode->HasAnyIndex() )
+ {
+ // move all ShellCursor/StackCursor/UnoCursor out of delete range
+ pDoc->CorrAbs( aIdx, SwPosition( *this ), nOldLen, true );
+ }
+ SwNode::Merge const eOldMergeFlag(pTextNode->GetRedlineMergeFlag());
+ rNds.Delete(aIdx);
+ SetWrong( pList, false );
+ SetGrammarCheck( pList3, false );
+ SetSmartTags( pList2, false );
+ InvalidateNumRule();
+ CheckResetRedlineMergeFlag(*this, eOldMergeFlag == SwNode::Merge::First
+ ? sw::Recreate::ThisNode
+ : sw::Recreate::No);
+ }
+ else {
+ OSL_FAIL( "No TextNode." );
+ }
+
+ return this;
+}
+
+void SwTextNode::JoinPrev()
+{
+ SwNodes& rNds = GetNodes();
+ SwNodeIndex aIdx( *this );
+ if( SwContentNode::CanJoinPrev( &aIdx ) )
+ {
+ SwDoc* pDoc = rNds.GetDoc();
+ const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
+ pContentStore->Save( pDoc, aIdx.GetIndex(), SAL_MAX_INT32);
+ SwTextNode *pTextNode = aIdx.GetNode().GetTextNode();
+ const sal_Int32 nLen = pTextNode->Len();
+
+ SwWrongList *pList = pTextNode->GetWrong();
+ if( pList )
+ {
+ pList->JoinList( GetWrong(), Len() );
+ SetWrongDirty(WrongState::TODO);
+ pTextNode->SetWrong( nullptr, false );
+ SetWrong( nullptr );
+ }
+ else
+ {
+ pList = GetWrong();
+ if( pList )
+ {
+ pList->Move( 0, nLen );
+ SetWrongDirty(WrongState::TODO);
+ SetWrong( nullptr, false );
+ }
+ }
+
+ SwGrammarMarkUp *pList3 = pTextNode->GetGrammarCheck();
+ if( pList3 )
+ {
+ pList3->JoinGrammarList( GetGrammarCheck(), Len() );
+ SetGrammarCheckDirty( true );
+ pTextNode->SetGrammarCheck( nullptr, false );
+ SetGrammarCheck( nullptr );
+ }
+ else
+ {
+ pList3 = GetGrammarCheck();
+ if( pList3 )
+ {
+ pList3->MoveGrammar( 0, nLen );
+ SetGrammarCheckDirty( true );
+ SetGrammarCheck( nullptr, false );
+ }
+ }
+
+ SwWrongList *pList2 = pTextNode->GetSmartTags();
+ if( pList2 )
+ {
+ pList2->JoinList( GetSmartTags(), Len() );
+ SetSmartTagDirty( true );
+ pTextNode->SetSmartTags( nullptr, false );
+ SetSmartTags( nullptr );
+ }
+ else
+ {
+ pList2 = GetSmartTags();
+ if( pList2 )
+ {
+ pList2->Move( 0, nLen );
+ SetSmartTagDirty( true );
+ SetSmartTags( nullptr, false );
+ }
+ }
+
+ { // scope for SwIndex
+ pTextNode->CutText( this, SwIndex(this), SwIndex(pTextNode), nLen );
+ }
+ // move all Bookmarks/TOXMarks
+ if( !pContentStore->Empty() )
+ pContentStore->Restore( pDoc, GetIndex() );
+
+ if( pTextNode->HasAnyIndex() )
+ {
+ // move all ShellCursor/StackCursor/UnoCursor out of delete range
+ pDoc->CorrAbs( aIdx, SwPosition( *this ), nLen, true );
+ }
+ SwNode::Merge const eOldMergeFlag(pTextNode->GetRedlineMergeFlag());
+ if (eOldMergeFlag == SwNode::Merge::First
+ && !IsCreateFrameWhenHidingRedlines())
+ {
+ sw::MoveDeletedPrevFrames(*pTextNode, *this);
+ }
+ rNds.Delete(aIdx);
+ SetWrong( pList, false );
+ SetGrammarCheck( pList3, false );
+ SetSmartTags( pList2, false );
+ 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 );
+}
+
+// override SwIndexReg::Update => text hints do not need SwIndex for start/end!
+void SwTextNode::Update(
+ SwIndex const & rPos,
+ const sal_Int32 nChangeLen,
+ const bool bNegative,
+ const bool bDelete )
+{
+ SetAutoCompleteWordDirty( true );
+
+ std::unique_ptr<SwpHts> pCollector;
+ const sal_Int32 nChangePos = rPos.GetIndex();
+
+ if ( HasHints() )
+ {
+ if ( bNegative )
+ {
+ 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;
+ SwIndexReg aTmpIdxReg;
+ if ( !bNegative && !bDelete )
+ {
+ const SwRedlineTable& rTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable();
+ for (SwRangeRedline* pRedl : rTable)
+ {
+ if ( pRedl->HasMark() )
+ {
+ SwPosition* const pEnd = pRedl->End();
+ if ( this == &pEnd->nNode.GetNode() &&
+ *pRedl->GetPoint() != *pRedl->GetMark() )
+ {
+ SwIndex & rIdx = pEnd->nContent;
+ if (nChangePos == rIdx.GetIndex())
+ {
+ rIdx.Assign( &aTmpIdxReg, rIdx.GetIndex() );
+ }
+ }
+ }
+ else if ( this == &pRedl->GetPoint()->nNode.GetNode() )
+ {
+ SwIndex & 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).nNode.GetNode().IsTextNode());
+ assert(!pRedl->GetBound(!isOneUsed).nContent.GetIdxReg()); (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)
+ {
+ bool bAtLeastOneBookmarkMoved = false;
+ bool bAtLeastOneExpandedBookmarkAtInsertionPosition = false;
+ // A text node already knows its marks via its SwIndexes.
+ o3tl::sorted_vector<const sw::mark::IMark*> aSeenMarks;
+ const SwIndex* next;
+ for (const SwIndex* 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();
+ SwIndex & rEndIdx = const_cast<SwIndex&>(pEnd->nContent);
+ if( this == &pEnd->nNode.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->nNode.GetNode()
+ && rPos.GetIndex() == pStart->nContent.GetIndex() )
+ {
+ bAtLeastOneExpandedBookmarkAtInsertionPosition = true;
+ }
+ }
+ }
+ }
+
+ bSortMarks = bAtLeastOneBookmarkMoved && bAtLeastOneExpandedBookmarkAtInsertionPosition;
+ }
+
+ // at-char anchored flys shouldn't be moved, either.
+#if OSL_DEBUG_LEVEL > 0
+ std::vector<SwFrameFormat*> checkFormats;
+ const SwFrameFormats& rFormats = *GetDoc()->GetSpzFrameFormats();
+ for (auto& rpFormat : rFormats)
+ {
+ const SwFormatAnchor& rAnchor = rpFormat->GetAnchor();
+ const SwPosition* pContentAnchor = rAnchor.GetContentAnchor();
+ if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR && pContentAnchor)
+ {
+ // The fly is at-char anchored and has an anchor position.
+ SwIndex& rEndIdx = const_cast<SwIndex&>(pContentAnchor->nContent);
+ if (&pContentAnchor->nNode.GetNode() == this && rEndIdx.GetIndex() == rPos.GetIndex())
+ {
+ // The anchor position is exactly our insert position.
+ #if 0
+ rEndIdx.Assign(&aTmpIdxReg, rEndIdx.GetIndex());
+ #endif
+ checkFormats.push_back( rpFormat );
+ }
+ }
+ }
+#endif
+ std::vector<SwFrameFormat*> const*const pFlys(GetAnchoredFlys());
+ for (size_t i = 0; pFlys && i != pFlys->size(); ++i)
+ {
+ SwFrameFormat const*const pFormat = (*pFlys)[i];
+ const SwFormatAnchor& rAnchor = pFormat->GetAnchor();
+ const SwPosition* pContentAnchor = rAnchor.GetContentAnchor();
+ if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR && pContentAnchor)
+ {
+ // The fly is at-char anchored and has an anchor position.
+ SwIndex& rEndIdx = const_cast<SwIndex&>(pContentAnchor->nContent);
+ if (&pContentAnchor->nNode.GetNode() == this && rEndIdx.GetIndex() == rPos.GetIndex())
+ {
+ // The anchor position is exactly our insert position.
+ rEndIdx.Assign(&aTmpIdxReg, rEndIdx.GetIndex());
+#if OSL_DEBUG_LEVEL > 0
+ auto checkPos = std::find( checkFormats.begin(), checkFormats.end(), pFormat );
+ assert( checkPos != checkFormats.end());
+ checkFormats.erase( checkPos );
+#endif
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 0
+ assert( checkFormats.empty());
+#endif
+
+ // 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;
+
+ SwIndex& rIndex = pCursor->Start()->nContent;
+ if (&pCursor->Start()->nNode.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
+ SwIndexReg::Update( rPos, nChangeLen, bNegative, bDelete );
+
+ 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
+ SwContentFrame* pContentFrame = getLayoutFrame(GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout());
+ SwSortedObjs* pSortedObjs = pContentFrame ? pContentFrame->GetDrawObjs() : nullptr;
+ if (pSortedObjs)
+ 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())
+ {
+ 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()->nNode.GetNode() && *pRedln->GetPoint() != *pRedln->GetMark())
+ {
+ // Redline is changed only when some change occurs before it
+ if (nChangePos <= pRedln->Start()->nContent.GetIndex())
+ {
+ SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedln);
+ }
+ }
+ }
+ else if (this == &pRedln->GetPoint()->nNode.GetNode())
+ SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedln);
+ }
+ }
+}
+
+void SwTextNode::ChgTextCollUpdateNum( const SwTextFormatColl *pOldColl,
+ const SwTextFormatColl *pNewColl)
+{
+ SwDoc* pDoc = GetDoc();
+ OSL_ENSURE( pDoc, "No Doc?" );
+ // 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);
+ }
+ if (pDoc)
+ {
+ pDoc->GetNodes().UpdateOutlineNode(*this);
+ }
+
+ SwNodes& rNds = GetNodes();
+ // If Level 0 (Chapter), update the footnotes!
+ if( ( !nNewLevel || !nOldLevel) && pDoc && !pDoc->GetFootnoteIdxs().empty() &&
+ FTNNUM_CHAPTER == pDoc->GetFootnoteInfo().m_eNum &&
+ rNds.IsDocNodes() )
+ {
+ SwNodeIndex aTmpIndex( rNds, GetIndex());
+
+ pDoc->GetFootnoteIdxs().UpdateFootnote( aTmpIndex);
+ }
+
+ 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( const SwIndex& rIdx, bool bFlag,
+ bool bFormatToTextAttributes )
+{
+ const sal_Int32 nIdx = rIdx.GetIndex();
+ 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,
+ enum SwTextNode::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 SwTextNode::DEFAULT: pMatchFunc = &lcl_GetTextAttrDefault; break;
+ case SwTextNode::EXPAND: pMatchFunc = &lcl_GetTextAttrExpand; break;
+ case SwTextNode::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, DEFAULT);
+ return ret;
+}
+
+SwTextAttr *
+SwTextNode::GetTextAttrAt(sal_Int32 const nIndex, sal_uInt16 const nWhich,
+ enum 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_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, PARENT ));
+
+ if ( pTextInputField == nullptr && rTextAttr.End() != nullptr )
+ {
+ pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt( *(rTextAttr.End()), RES_TXTATR_INPUTFIELD, PARENT ));
+ }
+
+ return pTextInputField;
+}
+
+void SwTextNode::DelFrames_TextNodePart()
+{
+ SetWrong( nullptr );
+ SetWrongDirty(WrongState::TODO);
+
+ SetGrammarCheck( nullptr );
+ SetGrammarCheckDirty( true );
+
+ SetSmartTags( nullptr );
+ SetSmartTagDirty( true );
+
+ SetWordCountDirty( true );
+ SetAutoCompleteWordDirty( true );
+}
+
+SwTextField* SwTextNode::GetFieldTextAttrAt(
+ const sal_Int32 nIndex,
+ const bool bIncludeInputFieldAtStart ) 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,
+ bIncludeInputFieldAtStart ? DEFAULT : EXPAND ));
+ }
+
+ return pTextField;
+}
+
+static SwCharFormat* lcl_FindCharFormat( const SwCharFormats* pCharFormats, const OUString& rName )
+{
+ if( !rName.isEmpty() )
+ {
+ 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 (SwModify).
+ 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* const pDoc = static_txtattr_cast<
+ const SwTextINetFormat*>(pHt)->GetTextNode().GetDoc();
+ if ( pDoc )
+ {
+ const SwCharFormats* pCharFormats = pDoc->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->ModifyNotification( nullptr, &aHint );
+ }
+}
+
+/// copy text and attributes to node pDest
+void SwTextNode::CopyText( SwTextNode *const pDest,
+ const SwIndex &rStart,
+ const sal_Int32 nLen,
+ const bool bForceCopyOfAllAttrs )
+{
+ SwIndex const aIdx( pDest, pDest->m_Text.getLength() );
+ CopyText( pDest, aIdx, rStart, nLen, bForceCopyOfAllAttrs );
+}
+
+void SwTextNode::CopyText( SwTextNode *const pDest,
+ const SwIndex &rDestStart,
+ const SwIndex &rStart,
+ sal_Int32 nLen,
+ const bool bForceCopyOfAllAttrs )
+{
+ CHECK_SWPHINTS_IF_FRM(this);
+ CHECK_SWPHINTS(pDest);
+ 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_pNumStringCache.reset( (nTextStartIdx != 0)
+ ? new OUString // fdo#49076: numbering only if copy from para start
+ : new OUString(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()))
+ {
+ SfxItemSet aCharSet(
+ pDest->GetDoc()->GetAttrPool(),
+ svl::Items<
+ RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
+ RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
+ RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>{} );
+ 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()))
+ {
+ SfxItemSet aCharSet(
+ pDest->GetDoc()->GetAttrPool(),
+ svl::Items<
+ RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
+ RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
+ RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>{});
+ 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 SwIndex 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 SwIndex aIdx( pDest, pNewHt->GetStart() );
+ pDest->EraseText( aIdx, 1 );
+ }
+ }
+ }
+
+ CHECK_SWPHINTS_IF_FRM(this);
+ CHECK_SWPHINTS(pDest);
+}
+
+OUString SwTextNode::InsertText( const OUString & rStr, const SwIndex & rIdx,
+ const SwInsertFlags nMode )
+{
+ 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;
+ }
+ 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 ); // text content changed!
+
+ if (nMode & SwInsertFlags::FORCEHINTEXPAND)
+ {
+ SetIgnoreDontExpand( bOldExpFlg );
+ }
+
+ if ( HasWriterListeners() )
+ { // send this before messing with hints, which will send RES_UPDATE_ATTR
+ SwInsText aHint( aPos, nLen );
+ NotifyClients( nullptr, &aHint );
+ }
+
+ 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 SwIndex & rStart, const sal_Int32 nLen )
+{
+ assert(pDest); // Cut requires a destination
+ SwIndex aDestStt(pDest, pDest->GetText().getLength());
+ CutImpl( pDest, aDestStt, rStart, nLen, false );
+}
+
+void SwTextNode::CutImpl( SwTextNode * const pDest, const SwIndex & rDestStart,
+ const SwIndex & 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
+
+ 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.copy(nTextStartIdx, nLen));
+ OUString const newText = m_Text.replaceAt(nTextStartIdx, nLen, "");
+ nLen = pDest->m_Text.getLength() - nInitSize; // update w/ current size!
+ if (!nLen) // String didn't grow?
+ return;
+
+ if (bUpdate)
+ {
+ // Update all SwIndex
+ pDest->Update( rDestStart, nLen, false, false/*??? why was it true*/);
+ }
+
+ 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
+ sal_uInt16 nWhich = IsInvalidItem( pItem )
+ ? pDest->GetpSwAttrSet()->GetWhichByPos( aIter.GetCurPos() )
+ : pItem->Which();
+ if( RES_FRMATR_STYLE_NAME != nWhich &&
+ RES_FRMATR_CONDITIONAL_STYLE_NAME != 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())
+ {
+ SfxItemSet aCharSet(
+ pDest->GetDoc()->GetAttrPool(),
+ svl::Items<
+ RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
+ RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
+ RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>{});
+ aCharSet.Put( *GetpSwAttrSet() );
+ if( aCharSet.Count() )
+ pDest->SetAttr( aCharSet, nDestStart, nDestStart + nLen );
+ }
+ else
+ {
+ GetpSwAttrSet()->CopyToModify( *pDest );
+ }
+ }
+
+ // notify frames - before moving hints, because footnotes
+ // want to find their anchor text frame in the follow chain
+ SwInsText aInsHint( nDestStart, nLen );
+ pDest->ModifyNotification( nullptr, &aInsHint );
+ sw::MoveText const moveHint(pDest, nDestStart, nTextStartIdx, nLen);
+ CallSwClientNotify(moveHint);
+ SwDelText aDelHint( nTextStartIdx, nLen );
+ ModifyNotification( nullptr, &aDelHint );
+
+ // 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, true, true );
+
+ for (SwTextAttr* pHt : aArr)
+ {
+ pHt->SetStart( rStart.GetIndex() );
+ pHt->SetEnd( rStart.GetIndex() );
+ InsertHint( pHt );
+ }
+ }
+ else
+ {
+ Update( rStart, nLen, true, true );
+ }
+
+ // set after moving hints
+ m_Text = newText;
+
+ if (bMergePortionsNeeded)
+ {
+ m_pSwpHints->MergePortions(*this);
+ }
+
+ CHECK_SWPHINTS(this);
+
+ TryDeleteSwpHints();
+}
+
+void SwTextNode::EraseText(const SwIndex &rIdx, const sal_Int32 nCount,
+ const SwInsertFlags nMode )
+{
+ 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, "");
+
+ // 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, true );
+
+ if( 1 == nCnt )
+ {
+ SwDelChr aHint( nStartIdx );
+ NotifyClients( nullptr, &aHint );
+ }
+ else
+ {
+ SwDelText aHint( nStartIdx, nCnt );
+ NotifyClients( nullptr, &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);
+
+ NotifyClients( nullptr, &aHint );
+ SwFormatChg aNew( GetTextColl() );
+ NotifyClients( 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);
+ }
+ }
+ }
+
+ if( IsInCache() )
+ {
+ SwFrame::GetCache().Delete( this );
+ SetInCache( false );
+ }
+ SetInSwFntCache( false );
+
+ // 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:
+ {
+ SvxLRSpaceItem& rLR = const_cast<SvxLRSpaceItem&>(GetSwAttrSet().GetLRSpace());
+ NotifyClients( &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( const SwNodeIndex& rPos, bool bNext,
+ bool bChgFollow )
+{
+ // ignore hard PageBreak/PageDesc/ColumnBreak from Auto-Set
+ std::unique_ptr<SwAttrSet> pNewAttrSet;
+ // #i75353#
+ bool bClearHardSetNumRuleWhenFormatCollChanges( false );
+ if( HasSwAttrSet() )
+ {
+ pNewAttrSet.reset(new SwAttrSet( *GetpSwAttrSet() ));
+ const SfxItemSet* pTmpSet = GetpSwAttrSet();
+
+ if (bNext) // successor doesn't inherit breaks!
+ pTmpSet = pNewAttrSet.get();
+
+ // !bNext: remove PageBreaks/PageDesc/ColBreak from this
+ bool bRemoveFromCache = false;
+ std::vector<sal_uInt16> aClearWhichIds;
+ if ( bNext )
+ bRemoveFromCache = ( 0 != pNewAttrSet->ClearItem( RES_PAGEDESC ) );
+ else
+ aClearWhichIds.push_back( RES_PAGEDESC );
+
+ if( SfxItemState::SET == pTmpSet->GetItemState( RES_BREAK, false ) )
+ {
+ if ( bNext )
+ pNewAttrSet->ClearItem( RES_BREAK );
+ else
+ aClearWhichIds.push_back( RES_BREAK );
+ bRemoveFromCache = true;
+ }
+ if( SfxItemState::SET == pTmpSet->GetItemState( RES_KEEP, false ) )
+ {
+ if ( bNext )
+ pNewAttrSet->ClearItem( RES_KEEP );
+ else
+ aClearWhichIds.push_back( RES_KEEP );
+ bRemoveFromCache = true;
+ }
+ if( SfxItemState::SET == pTmpSet->GetItemState( RES_PARATR_SPLIT, false ) )
+ {
+ if ( bNext )
+ pNewAttrSet->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 )
+ pNewAttrSet->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 && IsInCache() )
+ {
+ SwFrame::GetCache().Delete( this );
+ SetInCache( false );
+ }
+ }
+ SwNodes& rNds = GetNodes();
+
+ SwTextFormatColl* pColl = GetTextColl();
+
+ SwTextNode *pNode = new SwTextNode( rPos, pColl, pNewAttrSet.get() );
+
+ pNewAttrSet.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 )
+ {
+ std::vector<sal_uInt16> aClearWhichIds;
+ aClearWhichIds.push_back( RES_PARATR_NUMRULE );
+ if ( ClearItemsFromAttrSet( aClearWhichIds ) != 0 && IsInCache() )
+ {
+ SwFrame::GetCache().Delete( this );
+ SetInCache( false );
+ }
+ }
+ }
+ ChgFormatColl( pNextColl );
+
+ return pNode;
+}
+
+SwContentNode* SwTextNode::AppendNode( const SwPosition & rPos )
+{
+ // position behind which it will be inserted
+ SwNodeIndex aIdx( rPos.nNode, 1 );
+ SwTextNode* pNew = MakeNewTextNode( aIdx );
+
+ // 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;
+}
+
+namespace
+{
+
+sal_uInt16 lcl_BoundListLevel(const int nActualLevel)
+{
+ return static_cast<sal_uInt16>( std::min( std::max(nActualLevel, 0), MAXLEVEL-1 ) );
+}
+
+}
+
+// -> #i29560#
+bool SwTextNode::HasNumber() const
+{
+ bool bResult = false;
+
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pRule )
+ {
+ const SwNumFormat& aFormat(pRule->Get(lcl_BoundListLevel(GetActualListLevel())));
+
+ // #i40041#
+ bResult = aFormat.IsEnumeration() &&
+ SVX_NUM_NUMBER_NONE != aFormat.GetNumberingType();
+ }
+
+ 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) const
+{
+ if (GetDoc()->IsClipBoard() && m_pNumStringCache)
+ {
+ // #i111677# do not expand number strings in clipboard documents
+ return *m_pNumStringCache;
+ }
+ const SwNumRule* pRule = GetNum(pLayout) ? GetNum(pLayout)->GetNumRule() : nullptr;
+ if ( pRule &&
+ IsCountedInList() )
+ {
+ SvxNumberType const& rNumberType(
+ pRule->Get( lcl_BoundListLevel(GetActualListLevel()) ) );
+ if (rNumberType.IsTextFormat() ||
+
+ (style::NumberingType::NUMBER_NONE == rNumberType.GetNumberingType()))
+ {
+ return pRule->MakeNumString( GetNum(pLayout)->GetNumberVector(),
+ _bInclPrefixAndSuffixStrings,
+ false,
+ _nRestrictToThisLevel,
+ nullptr,
+ GetLang(0));
+ }
+ }
+
+ return OUString();
+}
+
+long SwTextNode::GetLeftMarginWithNum( bool bTextLeft ) const
+{
+ 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() )
+ nRet = nRet - GetSwAttrSet().GetLRSpace().GetLeft();
+ }
+ else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ if ( AreListLevelIndentsApplicable() )
+ {
+ nRet = rFormat.GetIndentAt();
+ // #i90401#
+ // Only negative first line indents have consider for the left margin
+ if ( !bTextLeft &&
+ rFormat.GetFirstLineIndent() < 0 )
+ {
+ nRet = nRet + rFormat.GetFirstLineIndent();
+ }
+ }
+ }
+ }
+
+ 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))
+ {
+ SvxLRSpaceItem aItem = GetSwAttrSet().GetLRSpace();
+ rFLOffset = rFLOffset + aItem.GetTextFirstLineOffset();
+ }
+ }
+ else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ if ( AreListLevelIndentsApplicable() )
+ {
+ rFLOffset = rFormat.GetFirstLineIndent();
+ }
+ else if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ SvxLRSpaceItem aItem = GetSwAttrSet().GetLRSpace();
+ rFLOffset = aItem.GetTextFirstLineOffset();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ rFLOffset = GetSwAttrSet().GetLRSpace().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 )
+ {
+ nAdditionalIndent = GetSwAttrSet().GetLRSpace().GetLeft();
+
+ if (getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ nAdditionalIndent = nAdditionalIndent -
+ GetSwAttrSet().GetLRSpace().GetTextFirstLineOffset();
+ }
+ }
+ else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ if ( AreListLevelIndentsApplicable() )
+ {
+ nAdditionalIndent = rFormat.GetIndentAt() + rFormat.GetFirstLineIndent();
+ }
+ else
+ {
+ nAdditionalIndent = GetSwAttrSet().GetLRSpace().GetLeft();
+ if (getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ nAdditionalIndent = nAdditionalIndent -
+ GetSwAttrSet().GetLRSpace().GetTextFirstLineOffset();
+ }
+ }
+ }
+ }
+ else
+ {
+ nAdditionalIndent = GetSwAttrSet().GetLRSpace().GetLeft();
+ }
+
+ return nAdditionalIndent;
+}
+
+// #i96772#
+void SwTextNode::ClearLRSpaceItemDueToListLevelIndents( std::shared_ptr<SvxLRSpaceItem>& o_rLRSpaceItem ) const
+{
+ if ( AreListLevelIndentsApplicable() )
+ {
+ const SwNumRule* pRule = GetNumRule();
+ if ( pRule && GetActualListLevel() >= 0 )
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ o_rLRSpaceItem = std::make_shared<SvxLRSpaceItem>(RES_LR_SPACE);
+ }
+ }
+ }
+}
+
+// #i91133#
+long SwTextNode::GetLeftMarginForTabCalculation() const
+{
+ 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() )
+ {
+ nLeftMarginForTabCalc = rFormat.GetIndentAt();
+ bLeftMarginForTabCalcSetToListLevelIndent = true;
+ }
+ }
+ }
+ if ( !bLeftMarginForTabCalcSetToListLevelIndent )
+ {
+ nLeftMarginForTabCalc = GetSwAttrSet().GetLRSpace().GetTextLeft();
+ }
+
+ return nLeftMarginForTabCalc;
+}
+
+static void Replace0xFF(
+ SwTextNode const& rNode,
+ OUStringBuffer & rText,
+ sal_Int32 & rTextStt,
+ sal_Int32 nEndPos )
+{
+ if (rNode.GetpSwpHints())
+ {
+ 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.copy(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);
+
+ 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 SwIndex* pDestIdx,
+ sal_Int32 nIdx, sal_Int32 nLen,
+ SwRootFrame const*const pLayout, bool bWithNum,
+ bool bWithFootnote, bool bReplaceTabsWithSpaces ) const
+{
+ if( &rDestNd == this )
+ return false;
+
+ SwIndex 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(
+ SwIndex(&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* pDoc = GetDoc();
+ SwRedlineTable::size_type nRedlPos = pDoc->getIDocumentRedlineAccess().GetRedlinePos( *this, RedlineType::Delete );
+ if( SwRedlineTable::npos != nRedlPos )
+ {
+ // some redline-delete object exists for the node
+ const sal_uLong nNdIdx = GetIndex();
+ for( ; nRedlPos < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() ; ++nRedlPos )
+ {
+ const SwRangeRedline* pTmp = pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
+ if( RedlineType::Delete == pTmp->GetType() )
+ {
+ const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End();
+ if( pRStt->nNode < nNdIdx )
+ {
+ if( pREnd->nNode > nNdIdx )
+ // paragraph is fully deleted
+ return OUString();
+ else if( pREnd->nNode == nNdIdx )
+ {
+ // deleted from 0 to nContent
+ aRedlArr.push_back( 0 );
+ aRedlArr.push_back( pREnd->nContent.GetIndex() );
+ }
+ }
+ else if( pRStt->nNode == nNdIdx )
+ {
+ //aRedlArr.Insert( pRStt->nContent.GetIndex(), aRedlArr.Count() );
+ aRedlArr.push_back( pRStt->nContent.GetIndex() );
+ if( pREnd->nNode == nNdIdx )
+ aRedlArr.push_back( pREnd->nContent.GetIndex() );
+ 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 SwIndex& rStart, const sal_Int32 nDelLen,
+ const OUString & rStr)
+{
+ 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 );
+
+ if (nLen && sInserted.getLength())
+ {
+ // Replace the 1st char, then delete the rest and insert.
+ // This way the attributes of the 1st char are expanded!
+ m_Text = m_Text.replaceAt(nStartPos, 1, sInserted.copy(0, 1));
+
+ ++const_cast<SwIndex&>(rStart);
+ m_Text = m_Text.replaceAt(rStart.GetIndex(), nLen - 1, "");
+ Update( rStart, nLen - 1, true );
+
+ OUString aTmpText( sInserted.copy(1) );
+ m_Text = m_Text.replaceAt(rStart.GetIndex(), 0, aTmpText);
+ Update( rStart, aTmpText.getLength() );
+ }
+ else
+ {
+ m_Text = m_Text.replaceAt(nStartPos, nLen, "");
+ Update( rStart, nLen, true );
+
+ m_Text = m_Text.replaceAt(nStartPos, 0, sInserted);
+ Update( rStart, sInserted.getLength() );
+ }
+
+ SetIgnoreDontExpand( bOldExpFlg );
+ SwDelText aDelHint( nStartPos, nDelLen );
+ NotifyClients( nullptr, &aDelHint );
+
+ if (sInserted.getLength())
+ {
+ SwInsText aHint( nStartPos, sInserted.getLength() );
+ NotifyClients( nullptr, &aHint );
+ }
+}
+
+namespace {
+ void lcl_ResetParAttrs( SwTextNode &rTextNode )
+ {
+ std::set<sal_uInt16> aAttrs;
+ aAttrs.insert( aAttrs.end(), RES_PARATR_LIST_ID );
+ aAttrs.insert( aAttrs.end(), RES_PARATR_LIST_LEVEL );
+ aAttrs.insert( aAttrs.end(), RES_PARATR_LIST_ISRESTART );
+ aAttrs.insert( aAttrs.end(), RES_PARATR_LIST_RESTARTVALUE );
+ aAttrs.insert( aAttrs.end(), 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 SfxPoolItem* pItem = nullptr;
+ 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, &pItem ) ==
+ 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 ( dynamic_cast<const SfxUInt16Item &>(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." );
+ OSL_ENSURE( dynamic_cast<const SwTextFormatColl *>(pNewColl) != nullptr,
+ "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) );
+ }
+
+ GetNodes().UpdateOutlineNode(*this);
+
+ return pOldColl;
+}
+
+const SwNodeNum* SwTextNode::GetNum(SwRootFrame const*const pLayout) 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() ? mpNodeNumRLHidden.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);
+ assert(mpNodeNum);
+ rFunc(*mpNodeNum);
+ if (pBackup)
+ {
+ mpNodeNumRLHidden = std::move(pBackup);
+ rFunc(*mpNodeNumRLHidden);
+ }
+}
+
+SwNumberTree::tNumberVector
+SwTextNode::GetNumberVector(SwRootFrame const*const pLayout) const
+{
+ if (SwNodeNum const*const pNum = GetNum(pLayout))
+ {
+ 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 static_cast<const SfxUInt16Item &>(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,
+ static_cast<sal_uInt16>(nLevel) ) );
+ }
+}
+
+// #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 =
+ dynamic_cast<const SfxInt16Item&>(GetAttr( RES_PARATR_LIST_LEVEL ));
+ nAttrListLevel = static_cast<int>(aListLevelItem.GetValue());
+
+ return nAttrListLevel;
+}
+
+int SwTextNode::GetActualListLevel() const
+{
+ assert(!GetNum() || !mpNodeNumRLHidden || // must be in sync
+ GetNum()->GetLevelInListTree() == mpNodeNumRLHidden->GetLevelInListTree());
+ return GetNum() ? GetNum()->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 =
+ dynamic_cast<const SfxBoolItem&>(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())
+ {
+ // #i87154#
+ // Correction of #newlistlevelattrs#:
+ // The numbering type has to be checked for bullet lists.
+ const SwNumFormat& rFormat = pRule->Get( lcl_BoundListLevel(GetActualListLevel()) );
+ 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() )
+ {
+ 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 =
+ dynamic_cast<const SfxInt16Item&>(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( static_cast<sal_uInt16>(GetAttrListLevel()) );
+ if ( pFormat )
+ {
+ nListRestartValue = pFormat->GetStart();
+ }
+ }
+ }
+
+ return nListRestartValue;
+}
+
+bool SwTextNode::IsNotifiable() const
+{
+ return m_bNotifiable && IsNotificationEnabled();
+}
+
+bool SwTextNode::IsNotificationEnabled() const
+{
+ bool bResult = false;
+ const SwDoc * pDoc = GetDoc();
+ if( pDoc )
+ {
+ bResult = !(pDoc->IsInReading() || pDoc->IsInDtor());
+ }
+ return bResult;
+}
+
+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 =
+ dynamic_cast<const SfxBoolItem&>(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
+ {
+ assert(!mpNodeNum);
+ mpNodeNum.reset(new SwNodeNum(this, false));
+ pList->InsertListItem(*mpNodeNum, false, GetAttrListLevel());
+ // iterate all frames & if there's one with hidden layout...
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> iter(*this);
+ for (SwTextFrame* pFrame = iter.First(); pFrame; 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)
+ {
+ assert(false);
+ OSL_FAIL( "<SwTextNode::AddToListRLHidden()> - the text node is already added to a list. Serious defect" );
+ return;
+ }
+
+ SwList *const pList(FindList(this));
+ if (pList)
+ {
+ assert(!mpNodeNumRLHidden);
+ mpNodeNumRLHidden.reset(new SwNodeNum(this, true));
+ pList->InsertListItem(*mpNodeNumRLHidden, true, GetAttrListLevel());
+ }
+}
+
+void SwTextNode::RemoveFromList()
+{
+ // sw_redlinehide: ensure it's removed from the other half too!
+ RemoveFromListRLHidden();
+ if ( IsInList() )
+ {
+ SwList::RemoveListItem( *mpNodeNum );
+ 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);
+ mpNodeNumRLHidden.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 =
+ dynamic_cast<const SfxStringItem&>(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 =
+ dynamic_cast<const SfxStringItem&>(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 boolean
+*/
+bool SwTextNode::AreListLevelIndentsApplicable() const
+{
+ bool bAreListLevelIndentsApplicable( true );
+
+ if ( !GetNum() || !GetNum()->GetNumRule() )
+ {
+ // no list style applied to paragraph
+ bAreListLevelIndentsApplicable = false;
+ }
+ else if ( HasSwAttrSet() &&
+ GetpSwAttrSet()->GetItemState( RES_LR_SPACE, 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( RES_LR_SPACE, 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( long& nListTabStopPosition ) const
+{
+ bool bListTabStopPositionProvided(false);
+
+ const SwNumRule* pNumRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pNumRule && HasVisibleNumberingOrBullet() && GetActualListLevel() >= 0 )
+ {
+ const SwNumFormat& rFormat = pNumRule->Get( static_cast<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() )
+ {
+ nListTabStopPosition -= rFormat.GetIndentAt();
+ }
+ else if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ SvxLRSpaceItem aItem = GetSwAttrSet().GetLRSpace();
+ 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( static_cast<sal_uInt16>(GetActualListLevel()) );
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ switch ( rFormat.GetLabelFollowedBy() )
+ {
+ case SvxNumberFormat::LISTTAB:
+ {
+ return "\t";
+ }
+ break;
+ case SvxNumberFormat::SPACE:
+ {
+ return " ";
+ }
+ break;
+ case SvxNumberFormat::NEWLINE:
+ {
+ return "\n";
+ }
+ break;
+ case SvxNumberFormat::NOTHING:
+ {
+ // intentionally left blank.
+ }
+ break;
+ default:
+ {
+ OSL_FAIL( "<SwTextNode::GetLabelFollowedBy()> - unknown SvxNumberFormat::GetLabelFollowedBy() return value" );
+ }
+ }
+ }
+ }
+
+ 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 )
+ {
+ const SfxPoolItem* pItem = nullptr;
+ // handle RES_PARATR_NUMRULE
+ if ( rItemSet.GetItemState( RES_PARATR_NUMRULE, false, &pItem ) == SfxItemState::SET )
+ {
+ mrTextNode.RemoveFromList();
+
+ const SwNumRuleItem* pNumRuleItem =
+ dynamic_cast<const SwNumRuleItem*>(pItem);
+ assert(pNumRuleItem);
+ if ( !pNumRuleItem->GetValue().isEmpty() )
+ {
+ mbAddTextNodeToList = true;
+ // #i70748#
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+ }
+
+ // handle RES_PARATR_LIST_ID
+ if ( rItemSet.GetItemState( RES_PARATR_LIST_ID, false, &pItem ) == SfxItemState::SET )
+ {
+ const SfxStringItem* pListIdItem =
+ dynamic_cast<const SfxStringItem*>(pItem);
+ const OUString sListIdOfTextNode = mrTextNode.GetListId();
+ if ( pListIdItem &&
+ pListIdItem->GetValue() != sListIdOfTextNode )
+ {
+ mbAddTextNodeToList = true;
+ if ( mrTextNode.IsInList() )
+ {
+ mrTextNode.RemoveFromList();
+ }
+ }
+ }
+
+ // handle RES_PARATR_LIST_LEVEL
+ if ( rItemSet.GetItemState( RES_PARATR_LIST_LEVEL, false, &pItem ) == SfxItemState::SET )
+ {
+ const SfxInt16Item* pListLevelItem =
+ dynamic_cast<const SfxInt16Item*>(pItem);
+ if (pListLevelItem && pListLevelItem->GetValue() != mrTextNode.GetAttrListLevel())
+ {
+ mbUpdateListLevel = true;
+ }
+ }
+
+ // handle RES_PARATR_LIST_ISRESTART
+ if ( rItemSet.GetItemState( RES_PARATR_LIST_ISRESTART, false, &pItem ) == SfxItemState::SET )
+ {
+ const SfxBoolItem* pListIsRestartItem =
+ dynamic_cast<const SfxBoolItem*>(pItem);
+ if (pListIsRestartItem && pListIsRestartItem->GetValue() != mrTextNode.IsListRestart())
+ {
+ mbUpdateListRestart = true;
+ }
+ }
+
+ // handle RES_PARATR_LIST_RESTARTVALUE
+ if ( rItemSet.GetItemState( RES_PARATR_LIST_RESTARTVALUE, false, &pItem ) == SfxItemState::SET )
+ {
+ const SfxInt16Item* pListRestartValueItem =
+ dynamic_cast<const SfxInt16Item*>(pItem);
+ if ( !mrTextNode.HasAttrListRestartValue() || (pListRestartValueItem &&
+ pListRestartValueItem->GetValue() != mrTextNode.GetAttrListRestartValue()) )
+ {
+ mbUpdateListRestart = true;
+ }
+ }
+
+ // handle RES_PARATR_LIST_ISCOUNTED
+ if ( rItemSet.GetItemState( RES_PARATR_LIST_ISCOUNTED, false, &pItem ) == SfxItemState::SET )
+ {
+ const SfxBoolItem* pIsCountedInListItem =
+ dynamic_cast<const SfxBoolItem*>(pItem);
+ if (pIsCountedInListItem && pIsCountedInListItem->GetValue() !=
+ mrTextNode.IsCountedInList())
+ {
+ mbUpdateListCount = true;
+ }
+ }
+
+ // #i70748#
+ // handle RES_PARATR_OUTLINELEVEL
+ if ( rItemSet.GetItemState( RES_PARATR_OUTLINELEVEL, false, &pItem ) == SfxItemState::SET )
+ {
+ const SfxUInt16Item* pOutlineLevelItem =
+ dynamic_cast<const SfxUInt16Item*>(pItem);
+ if (pOutlineLevelItem && 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());
+ mrTextNode.DoNum(
+ [nLevel](SwNodeNum & rNum) { rNum.SetLevelInListTree(nLevel); });
+ }
+
+ if ( mbUpdateListRestart && mrTextNode.IsInList() )
+ {
+ mrTextNode.DoNum(
+ [](SwNodeNum & rNum) {
+ rNum.InvalidateMe();
+ rNum.NotifyInvalidSiblings();
+ });
+ }
+
+ if ( mbUpdateListCount && mrTextNode.IsInList() )
+ {
+ mrTextNode.DoNum(
+ [](SwNodeNum & rNum) { rNum.InvalidateAndNotifyTree(); });
+ }
+ }
+
+ // #i70748#
+ if (mbOutlineLevelSet)
+ {
+ mrTextNode.GetNodes().UpdateOutlineNode(mrTextNode);
+ if (mrTextNode.GetAttrOutlineLevel() == 0)
+ {
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+ else
+ {
+ const SfxPoolItem* pItem = nullptr;
+ if ( mrTextNode.GetSwAttrSet().GetItemState( RES_PARATR_NUMRULE,
+ true, &pItem )
+ != 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;
+}
+
+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( const std::vector<sal_uInt16>& rWhichArr );
+ };
+
+ 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;
+ std::vector<sal_uInt16> rWhichArr;
+ for ( sal_uInt16 nWhich = nWhich1; nWhich <= nWhich2; ++nWhich )
+ rWhichArr.push_back( nWhich );
+
+ init( rWhichArr );
+ }
+
+ HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode,
+ const std::vector<sal_uInt16>& rWhichArr )
+ : mrTextNode( rTextNode ),
+ mbListStyleOrIdReset( false ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false )
+ {
+ init( rWhichArr );
+ }
+
+ 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( const std::vector<sal_uInt16>& rWhichArr )
+ {
+ bool bRemoveFromList( false );
+ {
+ for (const auto& rWhich : rWhichArr)
+ {
+ if ( rWhich == RES_PARATR_NUMRULE )
+ {
+ bRemoveFromList = bRemoveFromList ||
+ mrTextNode.GetNumRule() != nullptr;
+ mbListStyleOrIdReset = true;
+ }
+ else if ( rWhich == RES_PARATR_LIST_ID )
+ {
+ bRemoveFromList = bRemoveFromList ||
+ ( 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 ( !bRemoveFromList )
+ {
+ // 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() );
+ }
+ }
+ }
+
+ if ( bRemoveFromList && mrTextNode.IsInList() )
+ {
+ mrTextNode.RemoveFromList();
+ }
+ }
+
+ 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
+ {
+ assert(!mrTextNode.GetpSwAttrSet()
+ || dynamic_cast<const SfxUInt16Item*>(
+ &mrTextNode.GetAttr(RES_PARATR_OUTLINELEVEL, false)));
+ if (mrTextNode.GetpSwAttrSet()
+ && static_cast<const SfxUInt16Item&>(
+ mrTextNode.GetAttr(RES_PARATR_OUTLINELEVEL, false)).GetValue() > 0)
+ {
+ mrTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr();
+ }
+ }
+ }
+
+ if ( mrTextNode.IsInList() )
+ {
+ if ( mbUpdateListLevel )
+ {
+ auto const nLevel(mrTextNode.GetAttrListLevel());
+ mrTextNode.DoNum(
+ [nLevel](SwNodeNum & rNum) { rNum.SetLevelInListTree(nLevel); });
+ }
+
+ if ( mbUpdateListRestart )
+ {
+ mrTextNode.DoNum(
+ [](SwNodeNum & rNum) {
+ rNum.InvalidateMe();
+ rNum.NotifyInvalidSiblings();
+ });
+ }
+
+ if ( mbUpdateListCount )
+ {
+ mrTextNode.DoNum(
+ [](SwNodeNum & rNum) { rNum.InvalidateAndNotifyTree(); });
+ }
+ }
+ }
+ // 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
+{
+ xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextNode"));
+ xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(GetIndex()).getStr()));
+
+ OUString sText = GetText();
+ for (int i = 0; i < 32; ++i)
+ sText = sText.replace(i, '*');
+ xmlTextWriterStartElement(pWriter, BAD_CAST("m_Text"));
+ xmlTextWriterWriteString(pWriter, BAD_CAST(sText.toUtf8().getStr()));
+ xmlTextWriterEndElement(pWriter);
+
+ if (GetFormatColl())
+ {
+ xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColl"));
+ xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetFormatColl()->GetName().toUtf8().getStr()));
+ xmlTextWriterEndElement(pWriter);
+ }
+
+ if (HasSwAttrSet())
+ {
+ xmlTextWriterStartElement(pWriter, BAD_CAST("SwAttrSet"));
+ GetSwAttrSet().dumpAsXml(pWriter);
+ xmlTextWriterEndElement(pWriter);
+ }
+
+ if (HasHints())
+ {
+ xmlTextWriterStartElement(pWriter, BAD_CAST("SwpHints"));
+ const SwpHints& rHints = GetSwpHints();
+ for (size_t i = 0; i < rHints.Count(); ++i)
+ rHints.Get(i)->dumpAsXml(pWriter);
+ xmlTextWriterEndElement(pWriter);
+ }
+
+ if (GetNumRule())
+ GetNumRule()->dumpAsXml(pWriter);
+
+ xmlTextWriterEndElement(pWriter);
+}
+
+sal_uInt32 SwTextNode::GetRsid( sal_Int32 nStt, sal_Int32 nEnd ) const
+{
+ SfxItemSet aSet( const_cast<SfxItemPool&>(static_cast<SfxItemPool const &>(GetDoc()->GetAttrPool())), svl::Items<RES_CHRATR_RSID, RES_CHRATR_RSID>{} );
+ 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( SwNodeIndex(*this) );
+}
+
+void SwTextNode::SwClientNotify( const SwModify& rModify, const SfxHint& rHint )
+{
+ if (auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint))
+ {
+ bool bWasNotifiable = m_bNotifiable;
+ m_bNotifiable = false;
+
+ const auto pOldValue = pLegacyHint->m_pOld;
+ const auto pNewValue = pLegacyHint->m_pNew;
+ // 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() )
+ {
+ 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(rModify, rHint);
+
+ SwDoc* pDoc = GetDoc();
+ // #125329# - assure that text node is in document nodes array
+ if ( pDoc && !pDoc->IsInDtor() && &pDoc->GetNodes() == &GetNodes() )
+ {
+ pDoc->GetNodes().UpdateOutlineNode(*this);
+ }
+
+ m_bNotifiable = bWasNotifiable;
+
+ if (pOldValue && (RES_REMOVE_UNO_OBJECT == pOldValue->Which()))
+ { // invalidate cached uno object
+ SetXParagraph(css::uno::Reference<css::text::XTextContent>(nullptr));
+ }
+ }
+ 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), uno::UNO_QUERY);
+ 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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */