diff options
Diffstat (limited to 'sw/source/core/crsr/findattr.cxx')
-rw-r--r-- | sw/source/core/crsr/findattr.cxx | 1446 |
1 files changed, 1446 insertions, 0 deletions
diff --git a/sw/source/core/crsr/findattr.cxx b/sw/source/core/crsr/findattr.cxx new file mode 100644 index 0000000000..9492cfdd6c --- /dev/null +++ b/sw/source/core/crsr/findattr.cxx @@ -0,0 +1,1446 @@ +/* -*- 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 <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/util/SearchAlgorithms2.hpp> +#include <com/sun/star/util/SearchFlags.hpp> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/searchopt.hxx> +#include <osl/diagnose.h> +#include <unotools/syslocale.hxx> +#include <hintids.hxx> +#include <svl/itemiter.hxx> +#include <svl/srchitem.hxx> +#include <svl/whiter.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fontitem.hxx> +#include <fmtpdsc.hxx> +#include <txatbase.hxx> +#include <charfmt.hxx> +#include <crsrsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <swcrsr.hxx> +#include <ndtxt.hxx> +#include <pamtyp.hxx> +#include <txtfrm.hxx> +#include <swundo.hxx> +#include <optional> + +#include <algorithm> +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::util; + +// Special case for SvxFontItem: only compare the name +static bool CmpAttr( const SfxPoolItem& rItem1, const SfxPoolItem& rItem2 ) +{ + switch( rItem1.Which() ) + { + case RES_CHRATR_FONT: + return rItem1.StaticWhichCast(RES_CHRATR_FONT).GetFamilyName() == rItem2.StaticWhichCast(RES_CHRATR_FONT).GetFamilyName(); + + case RES_CHRATR_COLOR: + return rItem1.StaticWhichCast(RES_CHRATR_COLOR).GetValue().IsRGBEqual(rItem2.StaticWhichCast(RES_CHRATR_COLOR).GetValue()); + case RES_PAGEDESC: + ::std::optional<sal_uInt16> const oNumOffset1 = rItem1.StaticWhichCast(RES_PAGEDESC).GetNumOffset(); + ::std::optional<sal_uInt16> const oNumOffset2 = rItem2.StaticWhichCast(RES_PAGEDESC).GetNumOffset(); + + if (oNumOffset1 != oNumOffset2) + return false; + + return rItem1.StaticWhichCast(RES_PAGEDESC).GetPageDesc() == rItem2.StaticWhichCast(RES_PAGEDESC).GetPageDesc(); + } + return rItem1 == rItem2; +} + +const SwTextAttr* GetFrwrdTextHint( const SwpHints& rHtsArr, size_t& rPos, + sal_Int32 nContentPos ) +{ + while( rPos < rHtsArr.Count() ) + { + const SwTextAttr *pTextHt = rHtsArr.Get( rPos++ ); + // the start of an attribute has to be in the section + if( pTextHt->GetStart() >= nContentPos ) + return pTextHt; // valid text attribute + } + return nullptr; // invalid text attribute +} + +const SwTextAttr* GetBkwrdTextHint( const SwpHints& rHtsArr, size_t& rPos, + sal_Int32 nContentPos ) +{ + while( rPos > 0 ) + { + const SwTextAttr *pTextHt = rHtsArr.Get( --rPos ); + // the start of an attribute has to be in the section + if( pTextHt->GetStart() < nContentPos ) + return pTextHt; // valid text attribute + } + return nullptr; // invalid text attribute +} + +static void lcl_SetAttrPam( SwPaM& rPam, sal_Int32 nStart, const sal_Int32* pEnd, + const bool bSaveMark ) +{ + sal_Int32 nContentPos; + if( bSaveMark ) + nContentPos = rPam.GetMark()->GetContentIndex(); + else + nContentPos = rPam.GetPoint()->GetContentIndex(); + bool bTstEnd = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode(); + + rPam.GetPoint()->SetContent( nStart ); + rPam.SetMark(); // Point == GetMark + + // Point points to end of search area or end of attribute + if( pEnd ) + { + if( bTstEnd && *pEnd > nContentPos ) + rPam.GetPoint()->SetContent(nContentPos); + else + rPam.GetPoint()->SetContent(*pEnd); + } +} + +// TODO: provide documentation +/** search for a text attribute + + This function searches in a text node for a given attribute. + If that is found then the SwPaM contains the section that surrounds the + attribute (w.r.t. the search area). + + @param rTextNd Text node to search in. + @param rPam ??? + @param rCmpItem ??? + @param fnMove ??? + @return Returns <true> if found, <false> otherwise. +*/ +static bool lcl_SearchAttr( const SwTextNode& rTextNd, SwPaM& rPam, + const SfxPoolItem& rCmpItem, + SwMoveFnCollection const & fnMove) +{ + if ( !rTextNd.HasHints() ) + return false; + + const SwTextAttr *pTextHt = nullptr; + bool bForward = &fnMove == &fnMoveForward; + size_t nPos = bForward ? 0 : rTextNd.GetSwpHints().Count(); + sal_Int32 nContentPos = rPam.GetPoint()->GetContentIndex(); + + while( nullptr != ( pTextHt=(*fnMove.fnGetHint)(rTextNd.GetSwpHints(),nPos,nContentPos))) + if (pTextHt->Which() == rCmpItem.Which()) + { + lcl_SetAttrPam( rPam, pTextHt->GetStart(), pTextHt->End(), bForward ); + return true; + } + return false; +} + +namespace { + +/// search for multiple text attributes +struct SwSrchChrAttr +{ + sal_uInt16 nWhich; + sal_Int32 nStt; + sal_Int32 nEnd; + + SwSrchChrAttr(): nWhich(0), nStt(0), nEnd(0) {} + + SwSrchChrAttr( const SfxPoolItem& rItem, + sal_Int32 nStart, sal_Int32 nAnyEnd ) + : nWhich( rItem.Which() ), nStt( nStart ), nEnd( nAnyEnd ) + {} +}; + +class SwAttrCheckArr +{ + SwSrchChrAttr *m_pFindArr, *m_pStackArr; + sal_Int32 m_nNodeStart; + sal_Int32 m_nNodeEnd; + sal_uInt16 m_nArrStart, m_nArrLen; + sal_uInt16 m_nFound, m_nStackCount; + SfxItemSet m_aComapeSet; + bool m_bNoColls; + bool m_bForward; + +public: + SwAttrCheckArr( const SfxItemSet& rSet, bool bForward, bool bNoCollections ); + ~SwAttrCheckArr(); + + void SetNewSet( const SwTextNode& rTextNd, const SwPaM& rPam ); + + /// how many attributes are there in total? + sal_uInt16 Count() const { return m_aComapeSet.Count(); } + bool Found() const { return m_nFound == m_aComapeSet.Count(); } + bool CheckStack(); + + sal_Int32 Start() const; + sal_Int32 End() const; + + sal_Int32 GetNdStt() const { return m_nNodeStart; } + sal_Int32 GetNdEnd() const { return m_nNodeEnd; } + + bool SetAttrFwd( const SwTextAttr& rAttr ); + bool SetAttrBwd( const SwTextAttr& rAttr ); +}; + +} + +SwAttrCheckArr::SwAttrCheckArr( const SfxItemSet& rSet, bool bFwd, + bool bNoCollections ) + : m_nNodeStart(0) + , m_nNodeEnd(0) + , m_nFound(0) + , m_nStackCount(0) + , m_aComapeSet( *rSet.GetPool(), svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END-1> ) + , m_bNoColls(bNoCollections) + , m_bForward(bFwd) +{ + m_aComapeSet.Put( rSet, false ); + + // determine area of Fnd/Stack array (Min/Max) + SfxItemIter aIter( m_aComapeSet ); + m_nArrStart = m_aComapeSet.GetWhichByOffset( aIter.GetFirstPos() ); + m_nArrLen = m_aComapeSet.GetWhichByOffset( aIter.GetLastPos() ) - m_nArrStart+1; + + char* pFndChar = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ]; + char* pStackChar = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ]; + + m_pFindArr = reinterpret_cast<SwSrchChrAttr*>(pFndChar); + m_pStackArr = reinterpret_cast<SwSrchChrAttr*>(pStackChar); +} + +SwAttrCheckArr::~SwAttrCheckArr() +{ + delete[] reinterpret_cast<char*>(m_pFindArr); + delete[] reinterpret_cast<char*>(m_pStackArr); +} + +void SwAttrCheckArr::SetNewSet( const SwTextNode& rTextNd, const SwPaM& rPam ) +{ + std::fill(m_pFindArr, m_pFindArr + m_nArrLen, SwSrchChrAttr()); + std::fill(m_pStackArr, m_pStackArr + m_nArrLen, SwSrchChrAttr()); + m_nFound = 0; + m_nStackCount = 0; + + if( m_bForward ) + { + m_nNodeStart = rPam.GetPoint()->GetContentIndex(); + m_nNodeEnd = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode() + ? rPam.GetMark()->GetContentIndex() + : rTextNd.GetText().getLength(); + } + else + { + m_nNodeEnd = rPam.GetPoint()->GetContentIndex(); + m_nNodeStart = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode() + ? rPam.GetMark()->GetContentIndex() + : 0; + } + + if( m_bNoColls && !rTextNd.HasSwAttrSet() ) + return ; + + const SfxItemSet& rSet = rTextNd.GetSwAttrSet(); + + SfxItemIter aIter( m_aComapeSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + const SfxPoolItem* pFndItem; + sal_uInt16 nWhich; + + do + { + if( IsInvalidItem( pItem ) ) + { + nWhich = m_aComapeSet.GetWhichByOffset( aIter.GetCurPos() ); + if( RES_TXTATR_END <= nWhich ) + break; // end of text attributes + + if( SfxItemState::SET == rSet.GetItemState( nWhich, !m_bNoColls, &pFndItem ) + && !CmpAttr( *pFndItem, rSet.GetPool()->GetDefaultItem( nWhich ) )) + { + m_pFindArr[ nWhich - m_nArrStart ] = + SwSrchChrAttr( *pFndItem, m_nNodeStart, m_nNodeEnd ); + m_nFound++; + } + } + else + { + nWhich = pItem->Which(); + if( RES_TXTATR_END <= nWhich ) + break; // end of text attributes + + if( CmpAttr( rSet.Get( nWhich, !m_bNoColls ), *pItem ) ) + { + m_pFindArr[ nWhich - m_nArrStart ] = + SwSrchChrAttr( *pItem, m_nNodeStart, m_nNodeEnd ); + m_nFound++; + } + } + + pItem = aIter.NextItem(); + } while (pItem); +} + +static bool +lcl_IsAttributeIgnorable(sal_Int32 const nNdStart, sal_Int32 const nNdEnd, + SwSrchChrAttr const& rTmp) +{ + // #i115528#: if there is a paragraph attribute, it has been added by the + // SwAttrCheckArr ctor, and nFound is 1. + // if the paragraph is entirely covered by hints that override the paragraph + // attribute, then this function must find an attribute to decrement nFound! + // so check for an empty search range, let attributes that start/end there + // cover it, and hope for the best... + return ((nNdEnd == nNdStart) + ? ((rTmp.nEnd < nNdStart) || (nNdEnd < rTmp.nStt)) + : ((rTmp.nEnd <= nNdStart) || (nNdEnd <= rTmp.nStt))); +} + +bool SwAttrCheckArr::SetAttrFwd( const SwTextAttr& rAttr ) +{ + SwSrchChrAttr aTmp( rAttr.GetAttr(), rAttr.GetStart(), rAttr.GetAnyEnd() ); + + // ignore all attributes not in search range + if (lcl_IsAttributeIgnorable(m_nNodeStart, m_nNodeEnd, aTmp)) + { + return Found(); + } + + const SfxPoolItem* pItem; + // here we explicitly also search in character templates + sal_uInt16 nWhch = rAttr.Which(); + std::optional<SfxWhichIter> oIter; + const SfxPoolItem* pTmpItem = nullptr; + const SfxItemSet* pSet = nullptr; + if( RES_TXTATR_CHARFMT == nWhch || RES_TXTATR_AUTOFMT == nWhch ) + { + if( m_bNoColls && RES_TXTATR_CHARFMT == nWhch ) + return Found(); + pTmpItem = nullptr; + pSet = CharFormat::GetItemSet( rAttr.GetAttr() ); + if ( pSet ) + { + oIter.emplace( *pSet ); + nWhch = oIter->FirstWhich(); + while( nWhch && + SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) ) + nWhch = oIter->NextWhich(); + if( !nWhch ) + pTmpItem = nullptr; + } + } + else + pTmpItem = &rAttr.GetAttr(); + + while( pTmpItem ) + { + SfxItemState eState = m_aComapeSet.GetItemState( nWhch, false, &pItem ); + if( SfxItemState::DONTCARE == eState || SfxItemState::SET == eState ) + { + sal_uInt16 n; + SwSrchChrAttr* pCmp; + + // first delete all up to start position that are already invalid + SwSrchChrAttr* pArrPtr; + if( m_nFound ) + for( pArrPtr = m_pFindArr, n = 0; n < m_nArrLen; + ++n, ++pArrPtr ) + if( pArrPtr->nWhich && pArrPtr->nEnd <= aTmp.nStt ) + { + pArrPtr->nWhich = 0; // deleted + m_nFound--; + } + + // delete all up to start position that are already invalid and + // move all "open" ones (= stick out over start position) from stack + // into FndSet + if( m_nStackCount ) + for( pArrPtr = m_pStackArr, n=0; n < m_nArrLen; ++n, ++pArrPtr ) + { + if( !pArrPtr->nWhich ) + continue; + + if( pArrPtr->nEnd <= aTmp.nStt ) + { + pArrPtr->nWhich = 0; // deleted + if( !--m_nStackCount ) + break; + } + else if( pArrPtr->nStt <= aTmp.nStt ) + { + pCmp = &m_pFindArr[ n ]; + if( pCmp->nWhich ) + { + if( pCmp->nEnd < pArrPtr->nEnd ) // extend + pCmp->nEnd = pArrPtr->nEnd; + } + else + { + *pCmp = *pArrPtr; + m_nFound++; + } + pArrPtr->nWhich = 0; + if( !--m_nStackCount ) + break; + } + } + + bool bContinue = false; + + if( SfxItemState::DONTCARE == eState ) + { + // Will the attribute become valid? + if( !CmpAttr( m_aComapeSet.GetPool()->GetDefaultItem( nWhch ), + *pTmpItem )) + { + // search attribute and extend if needed + pCmp = &m_pFindArr[ nWhch - m_nArrStart ]; + if( !pCmp->nWhich ) + { + *pCmp = aTmp; // not found, insert + m_nFound++; + } + else if( pCmp->nEnd < aTmp.nEnd ) // extend? + pCmp->nEnd = aTmp.nEnd; + + bContinue = true; + } + } + // Will the attribute become valid? + else if( CmpAttr( *pItem, *pTmpItem ) ) + { + m_pFindArr[ nWhch - m_nArrStart ] = aTmp; + ++m_nFound; + bContinue = true; + } + + // then is has to go on the stack + if( !bContinue ) + { + pCmp = &m_pFindArr[ nWhch - m_nArrStart ]; + if (pCmp->nWhich ) + { + // exists on stack, only if it is even bigger + if( pCmp->nEnd > aTmp.nEnd ) + { + OSL_ENSURE( !m_pStackArr[ nWhch - m_nArrStart ].nWhich, + "slot on stack is still in use" ); + + if( aTmp.nStt <= pCmp->nStt ) + pCmp->nStt = aTmp.nEnd; + else + pCmp->nEnd = aTmp.nStt; + + m_pStackArr[ nWhch - m_nArrStart ] = *pCmp; + m_nStackCount++; + } + pCmp->nWhich = 0; + m_nFound--; + } + } + } + if( oIter ) + { + assert(pSet && "otherwise no oIter"); + nWhch = oIter->NextWhich(); + while( nWhch && + SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) ) + nWhch = oIter->NextWhich(); + if( !nWhch ) + break; + } + else + break; + } + oIter.reset(); + return Found(); +} + +bool SwAttrCheckArr::SetAttrBwd( const SwTextAttr& rAttr ) +{ + SwSrchChrAttr aTmp( rAttr.GetAttr(), rAttr.GetStart(), rAttr.GetAnyEnd() ); + + // ignore all attributes not in search range + if (lcl_IsAttributeIgnorable(m_nNodeStart, m_nNodeEnd, aTmp)) + { + return Found(); + } + + const SfxPoolItem* pItem; + // here we explicitly also search in character templates + sal_uInt16 nWhch = rAttr.Which(); + std::optional<SfxWhichIter> oIter; + const SfxPoolItem* pTmpItem = nullptr; + const SfxItemSet* pSet = nullptr; + if( RES_TXTATR_CHARFMT == nWhch || RES_TXTATR_AUTOFMT == nWhch ) + { + if( m_bNoColls && RES_TXTATR_CHARFMT == nWhch ) + return Found(); + + pSet = CharFormat::GetItemSet( rAttr.GetAttr() ); + if ( pSet ) + { + oIter.emplace( *pSet ); + nWhch = oIter->FirstWhich(); + while( nWhch && + SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) ) + nWhch = oIter->NextWhich(); + if( !nWhch ) + pTmpItem = nullptr; + } + } + else + pTmpItem = &rAttr.GetAttr(); + + while( pTmpItem ) + { + SfxItemState eState = m_aComapeSet.GetItemState( nWhch, false, &pItem ); + if( SfxItemState::DONTCARE == eState || SfxItemState::SET == eState ) + { + sal_uInt16 n; + SwSrchChrAttr* pCmp; + + // first delete all up to start position that are already invalid + SwSrchChrAttr* pArrPtr; + if( m_nFound ) + for( pArrPtr = m_pFindArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr ) + if( pArrPtr->nWhich && pArrPtr->nStt >= aTmp.nEnd ) + { + pArrPtr->nWhich = 0; // deleted + m_nFound--; + } + + // delete all up to start position that are already invalid and + // move all "open" ones (= stick out over start position) from stack + // into FndSet + if( m_nStackCount ) + for( pArrPtr = m_pStackArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr ) + { + if( !pArrPtr->nWhich ) + continue; + + if( pArrPtr->nStt >= aTmp.nEnd ) + { + pArrPtr->nWhich = 0; // deleted + if( !--m_nStackCount ) + break; + } + else if( pArrPtr->nEnd >= aTmp.nEnd ) + { + pCmp = &m_pFindArr[ n ]; + if( pCmp->nWhich ) + { + if( pCmp->nStt > pArrPtr->nStt ) // extend + pCmp->nStt = pArrPtr->nStt; + } + else + { + *pCmp = *pArrPtr; + m_nFound++; + } + pArrPtr->nWhich = 0; + if( !--m_nStackCount ) + break; + } + } + + bool bContinue = false; + if( SfxItemState::DONTCARE == eState ) + { + // Will the attribute become valid? + if( !CmpAttr( m_aComapeSet.GetPool()->GetDefaultItem( nWhch ), + *pTmpItem ) ) + { + // search attribute and extend if needed + pCmp = &m_pFindArr[ nWhch - m_nArrStart ]; + if( !pCmp->nWhich ) + { + *pCmp = aTmp; // not found, insert + m_nFound++; + } + else if( pCmp->nStt > aTmp.nStt ) // extend? + pCmp->nStt = aTmp.nStt; + + bContinue = true; + } + } + // Will the attribute become valid? + else if( CmpAttr( *pItem, *pTmpItem )) + { + m_pFindArr[ nWhch - m_nArrStart ] = aTmp; + ++m_nFound; + bContinue = true; + } + + // then is has to go on the stack + if( !bContinue ) + { + pCmp = &m_pFindArr[ nWhch - m_nArrStart ]; + if( pCmp->nWhich ) + { + // exists on stack, only if it is even bigger + if( pCmp->nStt < aTmp.nStt ) + { + OSL_ENSURE( !m_pStackArr[ nWhch - m_nArrStart ].nWhich, + "slot on stack is still in use" ); + + if( aTmp.nEnd <= pCmp->nEnd ) + pCmp->nEnd = aTmp.nStt; + else + pCmp->nStt = aTmp.nEnd; + + m_pStackArr[ nWhch - m_nArrStart ] = *pCmp; + m_nStackCount++; + } + pCmp->nWhich = 0; + m_nFound--; + } + } + } + if( oIter ) + { + assert(pSet && "otherwise no oIter"); + nWhch = oIter->NextWhich(); + while( nWhch && + SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) ) + nWhch = oIter->NextWhich(); + if( !nWhch ) + break; + } + else + break; + } + oIter.reset(); + return Found(); +} + +sal_Int32 SwAttrCheckArr::Start() const +{ + sal_Int32 nStart = m_nNodeStart; + SwSrchChrAttr* pArrPtr = m_pFindArr; + for( sal_uInt16 n = 0; n < m_nArrLen; ++n, ++pArrPtr ) + if( pArrPtr->nWhich && pArrPtr->nStt > nStart ) + nStart = pArrPtr->nStt; + + return nStart; +} + +sal_Int32 SwAttrCheckArr::End() const +{ + SwSrchChrAttr* pArrPtr = m_pFindArr; + sal_Int32 nEnd = m_nNodeEnd; + for( sal_uInt16 n = 0; n < m_nArrLen; ++n, ++pArrPtr ) + if( pArrPtr->nWhich && pArrPtr->nEnd < nEnd ) + nEnd = pArrPtr->nEnd; + + return nEnd; +} + +bool SwAttrCheckArr::CheckStack() +{ + if( !m_nStackCount ) + return false; + + sal_uInt16 n; + const sal_Int32 nSttPos = Start(); + const sal_Int32 nEndPos = End(); + SwSrchChrAttr* pArrPtr; + for( pArrPtr = m_pStackArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr ) + { + if( !pArrPtr->nWhich ) + continue; + + if( m_bForward ? pArrPtr->nEnd <= nSttPos : pArrPtr->nStt >= nEndPos ) + { + pArrPtr->nWhich = 0; // deleted + if( !--m_nStackCount ) + return m_nFound == m_aComapeSet.Count(); + } + else if( m_bForward ? pArrPtr->nStt < nEndPos : pArrPtr->nEnd > nSttPos ) + { + // move all "open" ones (= stick out over start position) into FndSet + OSL_ENSURE( !m_pFindArr[ n ].nWhich, "slot in array is already in use" ); + m_pFindArr[ n ] = *pArrPtr; + pArrPtr->nWhich = 0; + m_nFound++; + if( !--m_nStackCount ) + return m_nFound == m_aComapeSet.Count(); + } + } + return m_nFound == m_aComapeSet.Count(); +} + +static bool lcl_SearchForward( const SwTextNode& rTextNd, SwAttrCheckArr& rCmpArr, + SwPaM& rPam ) +{ + sal_Int32 nEndPos; + rCmpArr.SetNewSet( rTextNd, rPam ); + if( !rTextNd.HasHints() ) + { + if( !rCmpArr.Found() ) + return false; + nEndPos = rCmpArr.GetNdEnd(); + lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, true ); + return true; + } + + const SwpHints& rHtArr = rTextNd.GetSwpHints(); + const SwTextAttr* pAttr; + size_t nPos = 0; + + // if everything is already there then check with which it will be ended + if( rCmpArr.Found() ) + { + for( ; nPos < rHtArr.Count(); ++nPos ) + { + pAttr = rHtArr.Get( nPos ); + if( !rCmpArr.SetAttrFwd( *pAttr ) ) + { + if( rCmpArr.GetNdStt() < pAttr->GetStart() ) + { + // found end + auto nTmpStart = pAttr->GetStart(); + lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), + &nTmpStart, true ); + return true; + } + // continue search + break; + } + } + + if( nPos == rHtArr.Count() && rCmpArr.Found() ) + { + // found + nEndPos = rCmpArr.GetNdEnd(); + lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, true ); + return true; + } + } + + sal_Int32 nSttPos; + for( ; nPos < rHtArr.Count(); ++nPos ) + { + pAttr = rHtArr.Get( nPos ); + if( rCmpArr.SetAttrFwd( *pAttr ) ) + { + // Do multiple start at that position? Do also check those: + nSttPos = pAttr->GetStart(); + while( ++nPos < rHtArr.Count() ) + { + pAttr = rHtArr.Get( nPos ); + if( nSttPos != pAttr->GetStart() || !rCmpArr.SetAttrFwd( *pAttr ) ) + break; + } + + if( !rCmpArr.Found() ) + continue; + + // then we have our search area + nSttPos = rCmpArr.Start(); + nEndPos = rCmpArr.End(); + if( nSttPos > nEndPos ) + return false; + + lcl_SetAttrPam( rPam, nSttPos, &nEndPos, true ); + return true; + } + } + + if( !rCmpArr.CheckStack() ) + return false; + nSttPos = rCmpArr.Start(); + nEndPos = rCmpArr.End(); + if( nSttPos > nEndPos ) + return false; + + lcl_SetAttrPam( rPam, nSttPos, &nEndPos, true ); + return true; +} + +static bool lcl_SearchBackward( const SwTextNode& rTextNd, SwAttrCheckArr& rCmpArr, + SwPaM& rPam ) +{ + sal_Int32 nEndPos; + rCmpArr.SetNewSet( rTextNd, rPam ); + if( !rTextNd.HasHints() ) + { + if( !rCmpArr.Found() ) + return false; + nEndPos = rCmpArr.GetNdEnd(); + lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, false ); + return true; + } + + const SwpHints& rHtArr = rTextNd.GetSwpHints(); + const SwTextAttr* pAttr; + size_t nPos = rHtArr.Count(); + sal_Int32 nSttPos; + + // if everything is already there then check with which it will be ended + if( rCmpArr.Found() ) + { + while( nPos ) + { + pAttr = rHtArr.GetSortedByEnd( --nPos ); + if( !rCmpArr.SetAttrBwd( *pAttr ) ) + { + nSttPos = pAttr->GetAnyEnd(); + if( nSttPos < rCmpArr.GetNdEnd() ) + { + // found end + nEndPos = rCmpArr.GetNdEnd(); + lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false ); + return true; + } + + // continue search + break; + } + } + + if( !nPos && rCmpArr.Found() ) + { + // found + nEndPos = rCmpArr.GetNdEnd(); + lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, false ); + return true; + } + } + + while( nPos ) + { + pAttr = rHtArr.GetSortedByEnd( --nPos ); + if( rCmpArr.SetAttrBwd( *pAttr ) ) + { + // Do multiple start at that position? Do also check those: + if( nPos ) + { + nEndPos = pAttr->GetAnyEnd(); + while( --nPos ) + { + pAttr = rHtArr.GetSortedByEnd( nPos ); + if( nEndPos != pAttr->GetAnyEnd() || !rCmpArr.SetAttrBwd( *pAttr ) ) + break; + } + } + if( !rCmpArr.Found() ) + continue; + + // then we have our search area + nSttPos = rCmpArr.Start(); + nEndPos = rCmpArr.End(); + if( nSttPos > nEndPos ) + return false; + + lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false ); + return true; + } + } + + if( !rCmpArr.CheckStack() ) + return false; + nSttPos = rCmpArr.Start(); + nEndPos = rCmpArr.End(); + if( nSttPos > nEndPos ) + return false; + + lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false ); + return true; +} + +static bool lcl_Search( const SwContentNode& rCNd, const SfxItemSet& rCmpSet, bool bNoColls ) +{ + // search only hard attribution? + if( bNoColls && !rCNd.HasSwAttrSet() ) + return false; + + const SfxItemSet& rNdSet = rCNd.GetSwAttrSet(); + SfxItemIter aIter( rCmpSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + const SfxPoolItem* pNdItem; + sal_uInt16 nWhich; + + do + { + if( IsInvalidItem( pItem )) + { + nWhich = rCmpSet.GetWhichByOffset( aIter.GetCurPos() ); + if( SfxItemState::SET != rNdSet.GetItemState( nWhich, !bNoColls, &pNdItem ) + || CmpAttr( *pNdItem, rNdSet.GetPool()->GetDefaultItem( nWhich ) )) + return false; + } + else + { + nWhich = pItem->Which(); + + if( !CmpAttr( rNdSet.Get( nWhich, !bNoColls ), *pItem )) + return false; + } + + pItem = aIter.NextItem(); + } while (pItem); + return true; // found +} + +namespace sw { + +bool FindAttrImpl(SwPaM & rSearchPam, + const SfxPoolItem& rAttr, SwMoveFnCollection const & fnMove, + const SwPaM & rRegion, bool bInReadOnly, + SwRootFrame const*const pLayout) +{ + // determine which attribute is searched: + const sal_uInt16 nWhich = rAttr.Which(); + bool bCharAttr = isCHRATR(nWhich) || isTXTATR(nWhich); + assert(isTXTATR(nWhich)); // sw_redlinehide: only works for non-formatting hints such as needed in UpdateFields; use FindAttrsImpl for others + + std::optional<SwPaM> oPam; + sw::MakeRegion(fnMove, rRegion, oPam); + + bool bFound = false; + bool bFirst = true; + const bool bSrchForward = &fnMove == &fnMoveForward; + SwContentNode * pNode; + + // if at beginning/end then move it out of the node + if( bSrchForward + ? oPam->GetPoint()->GetContentIndex() == oPam->GetPointContentNode()->Len() + : !oPam->GetPoint()->GetContentIndex() ) + { + if( !(*fnMove.fnPos)( oPam->GetPoint(), false )) + { + return false; + } + SwContentNode *pNd = oPam->GetPointContentNode(); + oPam->GetPoint()->SetContent( bSrchForward ? 0 : pNd->Len() ); + } + + while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout))) + { + if( bCharAttr ) + { + if( !pNode->IsTextNode() ) // CharAttr are only in text nodes + continue; + + SwTextFrame const*const pFrame(pLayout + ? static_cast<SwTextFrame const*>(pNode->getLayoutFrame(pLayout)) + : nullptr); + if (pFrame) + { + SwTextNode const* pAttrNode(nullptr); + SwTextAttr const* pAttr(nullptr); + if (bSrchForward) + { + sw::MergedAttrIter iter(*pFrame); + do + { + pAttr = iter.NextAttr(&pAttrNode); + } + while (pAttr + && (pAttrNode->GetIndex() < oPam->GetPoint()->GetNodeIndex() + || (pAttrNode->GetIndex() == oPam->GetPoint()->GetNodeIndex() + && pAttr->GetStart() < oPam->GetPoint()->GetContentIndex()) + || pAttr->Which() != nWhich)); + } + else + { + sw::MergedAttrIterReverse iter(*pFrame); + do + { + pAttr = iter.PrevAttr(&pAttrNode); + } + while (pAttr + && (oPam->GetPoint()->GetNodeIndex() < pAttrNode->GetIndex() + || (oPam->GetPoint()->GetNodeIndex() == pAttrNode->GetIndex() + && oPam->GetPoint()->GetContentIndex() <= pAttr->GetStart()) + || pAttr->Which() != nWhich)); + } + if (pAttr) + { + assert(pAttrNode); + oPam->GetPoint()->Assign(*pAttrNode); + lcl_SetAttrPam(*oPam, pAttr->GetStart(), pAttr->End(), bSrchForward); + bFound = true; + break; + } + } + else if (!pLayout && pNode->GetTextNode()->HasHints() && + lcl_SearchAttr(*pNode->GetTextNode(), *oPam, rAttr, fnMove)) + { + bFound = true; + } + if (bFound) + { + // set to the values of the attribute + rSearchPam.SetMark(); + *rSearchPam.GetPoint() = *oPam->GetPoint(); + *rSearchPam.GetMark() = *oPam->GetMark(); + break; + } + else if (isTXTATR(nWhich)) + continue; + } + +#if 0 + // no hard attribution, so check if node was asked for this attr before + if( !pNode->HasSwAttrSet() ) + { + SwFormat* pTmpFormat = pNode->GetFormatColl(); + if( !aFormatArr.insert( pTmpFormat ).second ) + continue; // collection was requested earlier + } + + if( SfxItemState::SET == pNode->GetSwAttrSet().GetItemState( nWhich, + true, &pItem )) + { + // FORWARD: SPoint at the end, GetMark at the beginning of the node + // BACKWARD: SPoint at the beginning, GetMark at the end of the node + // always: incl. start and incl. end + *rSearchPam.GetPoint() = *pPam->GetPoint(); + rSearchPam.SetMark(); + rSearchPam.GetPoint()->SetContent(pNode->Len()); + bFound = true; + break; + } +#endif + } + + // if backward search, switch point and mark + if( bFound && !bSrchForward ) + rSearchPam.Exchange(); + + return bFound; +} + +} // namespace sw + +typedef bool (*FnSearchAttr)( const SwTextNode&, SwAttrCheckArr&, SwPaM& ); + +static bool FindAttrsImpl(SwPaM & rSearchPam, + const SfxItemSet& rSet, bool bNoColls, SwMoveFnCollection const & fnMove, + const SwPaM & rRegion, bool bInReadOnly, bool bMoveFirst, + SwRootFrame const*const pLayout) +{ + std::optional<SwPaM> oPam; + sw::MakeRegion(fnMove, rRegion, oPam); + + bool bFound = false; + bool bFirst = true; + const bool bSrchForward = &fnMove == &fnMoveForward; + SwContentNode * pNode; + o3tl::sorted_vector<SwFormat*> aFormatArr; + + // check which text/char attributes are searched + SwAttrCheckArr aCmpArr( rSet, bSrchForward, bNoColls ); + SfxItemSetFixed<RES_PARATR_BEGIN, RES_GRFATR_END-1> aOtherSet( rSearchPam.GetDoc().GetAttrPool() ); + aOtherSet.Put( rSet, false ); // got all invalid items + + FnSearchAttr fnSearch = bSrchForward + ? (&::lcl_SearchForward) + : (&::lcl_SearchBackward); + + // if at beginning/end then move it out of the node + if( bMoveFirst && + ( bSrchForward + ? oPam->GetPoint()->GetContentIndex() == oPam->GetPointContentNode()->Len() + : !oPam->GetPoint()->GetContentIndex() ) ) + { + if( !(*fnMove.fnPos)( oPam->GetPoint(), false )) + { + return false; + } + SwContentNode *pNd = oPam->GetPointContentNode(); + oPam->GetPoint()->SetContent( bSrchForward ? 0 : pNd->Len() ); + } + + while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout))) + { + SwTextFrame const*const pFrame(pLayout && pNode->IsTextNode() + ? static_cast<SwTextFrame const*>(pNode->getLayoutFrame(pLayout)) + : nullptr); + assert(!pLayout || !pNode->IsTextNode() || pFrame); + // sw_redlinehide: it's apparently not possible to find break items + // with the UI, so checking one node is enough + SwContentNode const& rPropsNode(*(pFrame + ? pFrame->GetTextNodeForParaProps() + : pNode)); + + if( aCmpArr.Count() ) + { + if( !pNode->IsTextNode() ) // CharAttr are only in text nodes + continue; + + if (aOtherSet.Count() && + !lcl_Search(rPropsNode, aOtherSet, bNoColls)) + { + continue; + } + sw::MergedPara const*const pMergedPara(pFrame ? pFrame->GetMergedPara() : nullptr); + if (pMergedPara) + { + SwPosition const& rStart(*oPam->Start()); + SwPosition const& rEnd(*oPam->End()); + // no extents? fall back to searching index 0 of propsnode + // to find its node items + if (pMergedPara->extents.empty()) + { + if (rStart.GetNodeIndex() <= rPropsNode.GetIndex() + && rPropsNode.GetIndex() <= rEnd.GetNodeIndex()) + { + SwPaM tmp(rPropsNode, 0, rPropsNode, 0); + bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, tmp); + if (bFound) + { + *oPam = tmp; + } + } + } + else + { + // iterate the extents, and intersect with input pPam: + // the found ranges should never include delete redlines + // so that subsequent Replace will not affect them + for (size_t i = 0; i < pMergedPara->extents.size(); ++i) + { + auto const rExtent(pMergedPara->extents[bSrchForward + ? i + : pMergedPara->extents.size() - i - 1]); + if (rExtent.pNode->GetIndex() < rStart.GetNodeIndex() + || rEnd.GetNodeIndex() < rExtent.pNode->GetIndex()) + { + continue; + } + sal_Int32 const nStart(rExtent.pNode == &rStart.GetNode() + ? rStart.GetContentIndex() + : 0); + if (rExtent.nEnd <= nStart) + { + continue; + } + sal_Int32 const nEnd(rExtent.pNode == &rEnd.GetNode() + ? rEnd.GetContentIndex() + : rExtent.pNode->Len()); + if (nEnd < rExtent.nStart + || (nStart != nEnd && nEnd == rExtent.nStart)) + { + continue; + } + SwPaM tmp(*rExtent.pNode, std::max(nStart, rExtent.nStart), + *rExtent.pNode, std::min(nEnd, rExtent.nEnd)); + tmp.Normalize(bSrchForward); + bFound = (*fnSearch)(*rExtent.pNode, aCmpArr, tmp); + if (bFound) + { + *oPam = tmp; + break; + } + } + } + } + else + { + bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, *oPam); + } + if (bFound) + { + // set to the values of the attribute + rSearchPam.SetMark(); + *rSearchPam.GetPoint() = *oPam->GetPoint(); + *rSearchPam.GetMark() = *oPam->GetMark(); + break; + } + continue; // text attribute + } + + if( !aOtherSet.Count() ) + continue; + + // no hard attribution, so check if node was asked for this attr before + // (as an optimisation) + if (!rPropsNode.HasSwAttrSet()) + { + SwFormat* pTmpFormat = rPropsNode.GetFormatColl(); + if( !aFormatArr.insert( pTmpFormat ).second ) + continue; // collection was requested earlier + } + + if (lcl_Search(rPropsNode, aOtherSet, bNoColls)) + { + // FORWARD: SPoint at the end, GetMark at the beginning of the node + // BACKWARD: SPoint at the beginning, GetMark at the end of the node + if (pFrame) + { + *rSearchPam.GetPoint() = *oPam->GetPoint(); + rSearchPam.SetMark(); + *rSearchPam.GetMark() = pFrame->MapViewToModelPos( + TextFrameIndex(bSrchForward ? pFrame->GetText().getLength() : 0)); + } + else + { + *rSearchPam.GetPoint() = *oPam->GetPoint(); + rSearchPam.SetMark(); + if (bSrchForward) + { + rSearchPam.GetPoint()->SetContent(pNode->Len()); + } + else + { + rSearchPam.GetPoint()->SetContent(0); + } + } + bFound = true; + break; + } + } + + // in search direction, mark precedes point, because the next iteration + // starts at point + if (bFound) + { + rSearchPam.Normalize(!bSrchForward); + } + + return bFound; +} + +namespace { + +/// parameters for search for attributes +struct SwFindParaAttr : public SwFindParas +{ + bool m_bNoCollection; + const SfxItemSet *pSet, *pReplSet; + const i18nutil::SearchOptions2 *pSearchOpt; + SwCursor& m_rCursor; + SwRootFrame const* m_pLayout; + std::unique_ptr<utl::TextSearch> pSText; + + SwFindParaAttr( const SfxItemSet& rSet, bool bNoCollection, + const i18nutil::SearchOptions2* pOpt, const SfxItemSet* pRSet, + SwCursor& rCursor, SwRootFrame const*const pLayout) + : m_bNoCollection(bNoCollection) + , pSet( &rSet ) + , pReplSet( pRSet ) + , pSearchOpt( pOpt ) + , m_rCursor(rCursor) + , m_pLayout(pLayout) + {} + + virtual ~SwFindParaAttr() {} + + virtual int DoFind(SwPaM &, SwMoveFnCollection const &, const SwPaM &, bool bInReadOnly, + std::unique_ptr<SvxSearchItem>& xSearchItem) override; + virtual bool IsReplaceMode() const override; +}; + +} + +int SwFindParaAttr::DoFind(SwPaM & rCursor, SwMoveFnCollection const & fnMove, + const SwPaM & rRegion, bool bInReadOnly, + std::unique_ptr<SvxSearchItem>& xSearchItem) +{ + // replace string (only if text given and search is not parameterized)? + bool bReplaceText = pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() || + !pSet->Count() ); + bool bReplaceAttr = pReplSet && pReplSet->Count(); + bool bMoveFirst = !bReplaceAttr; + if( bInReadOnly && (bReplaceAttr || bReplaceText )) + bInReadOnly = false; + + // We search for attributes, should we search for text as well? + { + SwPaM aRegion( *rRegion.GetMark(), *rRegion.GetPoint() ); + SwPaM* pTextRegion = &aRegion; + SwPaM aSrchPam( *rCursor.GetPoint() ); + + while( true ) + { + if( pSet->Count() ) // any attributes? + { + // first attributes + if (!FindAttrsImpl(aSrchPam, *pSet, m_bNoCollection, fnMove, aRegion, bInReadOnly, bMoveFirst, m_pLayout)) + return FIND_NOT_FOUND; + bMoveFirst = true; + + if( !pSearchOpt ) + break; // ok, only attributes, so found + + pTextRegion = &aSrchPam; + } + else if( !pSearchOpt ) + return FIND_NOT_FOUND; + + // then search in text of it + if( !pSText ) + { + i18nutil::SearchOptions2 aTmp( *pSearchOpt ); + + // search in selection + aTmp.searchFlag |= (SearchFlags::REG_NOT_BEGINOFLINE | + SearchFlags::REG_NOT_ENDOFLINE); + + aTmp.Locale = SvtSysLocale().GetLanguageTag().getLocale(); + + pSText.reset( new utl::TextSearch( aTmp ) ); + } + + // TODO: searching for attributes in Outliner text?! + + // continue search in correct section (pTextRegion) + if (sw::FindTextImpl(aSrchPam, *pSearchOpt, false/*bSearchInNotes*/, *pSText, fnMove, *pTextRegion, bInReadOnly, m_pLayout, xSearchItem) && + *aSrchPam.GetMark() != *aSrchPam.GetPoint() ) + break; // found + else if( !pSet->Count() ) + return FIND_NOT_FOUND; // only text and nothing found + + *aRegion.GetMark() = *aSrchPam.GetPoint(); + } + + *rCursor.GetPoint() = *aSrchPam.GetPoint(); + rCursor.SetMark(); + *rCursor.GetMark() = *aSrchPam.GetMark(); + } + + if( bReplaceText ) + { + const bool bRegExp( + SearchAlgorithms2::REGEXP == pSearchOpt->AlgorithmType2); + SwPosition& rSttCntPos = *rCursor.Start(); + const sal_Int32 nSttCnt = rSttCntPos.GetContentIndex(); + + // add to shell-cursor-ring so that the regions will be moved eventually + SwPaM* pPrevRing(nullptr); + if( bRegExp ) + { + pPrevRing = const_cast<SwPaM &>(rRegion).GetPrev(); + const_cast<SwPaM &>(rRegion).GetRingContainer().merge( m_rCursor.GetRingContainer() ); + } + + std::optional<OUString> xRepl; + if (bRegExp) + xRepl = sw::ReplaceBackReferences(*pSearchOpt, &rCursor, m_pLayout); + sw::ReplaceImpl(rCursor, + xRepl ? *xRepl : pSearchOpt->replaceString, bRegExp, + m_rCursor.GetDoc(), m_pLayout); + + m_rCursor.SaveTableBoxContent( rCursor.GetPoint() ); + + if( bRegExp ) + { + // and remove region again + SwPaM* p; + SwPaM* pNext = const_cast<SwPaM*>(&rRegion); + do { + p = pNext; + pNext = p->GetNext(); + p->MoveTo(const_cast<SwPaM*>(&rRegion)); + } while( p != pPrevRing ); + } + rSttCntPos.SetContent(nSttCnt); + } + + if( bReplaceAttr ) + { + // is the selection still existent? + // all searched attributes are reset to default if + // they are not in ReplaceSet + if( !pSet->Count() ) + { + rCursor.GetDoc().getIDocumentContentOperations().InsertItemSet( + rCursor, *pReplSet, SetAttrMode::DEFAULT, m_pLayout); + } + else + { + SfxItemPool* pPool = pReplSet->GetPool(); + SfxItemSet aSet( *pPool, pReplSet->GetRanges() ); + + SfxItemIter aIter( *pSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + // reset all that are not set with pool defaults + if( !IsInvalidItem( pItem ) && SfxItemState::SET != + pReplSet->GetItemState( pItem->Which(), false )) + aSet.Put( pPool->GetDefaultItem( pItem->Which() )); + + pItem = aIter.NextItem(); + } while (pItem); + aSet.Put( *pReplSet ); + rCursor.GetDoc().getIDocumentContentOperations().InsertItemSet( + rCursor, aSet, SetAttrMode::DEFAULT, m_pLayout); + } + + return FIND_NO_RING; + } + else + return FIND_FOUND; +} + +bool SwFindParaAttr::IsReplaceMode() const +{ + return ( pSearchOpt && !pSearchOpt->replaceString.isEmpty() ) || + ( pReplSet && pReplSet->Count() ); +} + +/// search for attributes +sal_Int32 SwCursor::FindAttrs( const SfxItemSet& rSet, bool bNoCollections, + SwDocPositions nStart, SwDocPositions nEnd, + bool& bCancel, FindRanges eFndRngs, + const i18nutil::SearchOptions2* pSearchOpt, + const SfxItemSet* pReplSet, + SwRootFrame const*const pLayout) +{ + // switch off OLE-notifications + SwDoc& rDoc = GetDoc(); + Link<bool,void> aLnk( rDoc.GetOle2Link() ); + rDoc.SetOle2Link( Link<bool,void>() ); + + bool bReplace = ( pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() || + !rSet.Count() ) ) || + (pReplSet && pReplSet->Count()); + bool const bStartUndo = rDoc.GetIDocumentUndoRedo().DoesUndo() && bReplace; + if (bStartUndo) + { + rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE, nullptr ); + } + + SwFindParaAttr aSwFindParaAttr( rSet, bNoCollections, pSearchOpt, + pReplSet, *this, pLayout ); + + sal_Int32 nRet = FindAll( aSwFindParaAttr, nStart, nEnd, eFndRngs, bCancel ); + rDoc.SetOle2Link( aLnk ); + if( nRet && bReplace ) + rDoc.getIDocumentState().SetModified(); + + if (bStartUndo) + { + rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE, nullptr ); + } + + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |