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