diff options
Diffstat (limited to 'sw/source/core/doc/DocumentContentOperationsManager.cxx')
-rw-r--r-- | sw/source/core/doc/DocumentContentOperationsManager.cxx | 5138 |
1 files changed, 5138 insertions, 0 deletions
diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx new file mode 100644 index 000000000..17bbd2b56 --- /dev/null +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -0,0 +1,5138 @@ +/* -*- 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 <DocumentContentOperationsManager.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentMarkAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <UndoManager.hxx> +#include <docary.hxx> +#include <textboxhelper.hxx> +#include <dcontact.hxx> +#include <grfatr.hxx> +#include <numrule.hxx> +#include <charfmt.hxx> +#include <ndgrf.hxx> +#include <ndnotxt.hxx> +#include <ndole.hxx> +#include <breakit.hxx> +#include <frmfmt.hxx> +#include <fmtanchr.hxx> +#include <fmtcntnt.hxx> +#include <fmtinfmt.hxx> +#include <fmtpdsc.hxx> +#include <fmtcnct.hxx> +#include <SwStyleNameMapper.hxx> +#include <redline.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <frmtool.hxx> +#include <unocrsr.hxx> +#include <mvsave.hxx> +#include <ndtxt.hxx> +#include <poolfmt.hxx> +#include <paratr.hxx> +#include <txatbase.hxx> +#include <UndoRedline.hxx> +#include <undobj.hxx> +#include <UndoBookmark.hxx> +#include <UndoDelete.hxx> +#include <UndoSplitMove.hxx> +#include <UndoOverwrite.hxx> +#include <UndoInsert.hxx> +#include <UndoAttribute.hxx> +#include <rolbck.hxx> +#include <acorrect.hxx> +#include <bookmrk.hxx> +#include <ftnidx.hxx> +#include <txtftn.hxx> +#include <hints.hxx> +#include <fmtflcnt.hxx> +#include <docedt.hxx> +#include <frameformats.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> +#include <unotools/charclass.hxx> +#include <unotools/configmgr.hxx> +#include <sfx2/Metadatable.hxx> +#include <sot/exchange.hxx> +#include <svl/stritem.hxx> +#include <svl/itemiter.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdouno.hxx> +#include <tools/globname.hxx> +#include <editeng/formatbreakitem.hxx> +#include <com/sun/star/i18n/Boundary.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + +#include <tuple> +#include <memory> + + +using namespace ::com::sun::star::i18n; + +namespace +{ + // Copy method from SwDoc + // Prevent copying into Flys that are anchored in the range + bool lcl_ChkFlyFly( SwDoc* pDoc, sal_uLong nSttNd, sal_uLong nEndNd, + sal_uLong nInsNd ) + { + const SwFrameFormats& rFrameFormatTable = *pDoc->GetSpzFrameFormats(); + + for( size_t n = 0; n < rFrameFormatTable.size(); ++n ) + { + SwFrameFormat const*const pFormat = rFrameFormatTable[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId())) && + nSttNd <= pAPos->nNode.GetIndex() && + pAPos->nNode.GetIndex() < nEndNd ) + { + const SwFormatContent& rContent = pFormat->GetContent(); + SwStartNode* pSNd; + if( !rContent.GetContentIdx() || + nullptr == ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() )) + continue; + + if( pSNd->GetIndex() < nInsNd && + nInsNd < pSNd->EndOfSectionIndex() ) + // Do not copy ! + return true; + + if( lcl_ChkFlyFly( pDoc, pSNd->GetIndex(), + pSNd->EndOfSectionIndex(), nInsNd ) ) + // Do not copy ! + return true; + } + } + + return false; + } + + SwNodeIndex InitDelCount(SwPaM const& rSourcePaM, sal_uLong & rDelCount) + { + SwNodeIndex const& rStart(rSourcePaM.Start()->nNode); + // Special handling for SwDoc::AppendDoc + if (rSourcePaM.GetDoc()->GetNodes().GetEndOfExtras().GetIndex() + 1 + == rStart.GetIndex()) + { + rDelCount = 1; + return SwNodeIndex(rStart, +1); + } + else + { + rDelCount = 0; + return rStart; + } + } + + /* + The CopyBookmarks function has to copy bookmarks from the source to the destination nodes + array. It is called after a call of the CopyNodes(..) function. But this function does not copy + every node (at least at the moment: 2/08/2006 ), section start and end nodes will not be copied + if the corresponding end/start node is outside the copied pam. + The lcl_NonCopyCount function counts the number of these nodes, given the copied pam and a node + index inside the pam. + rPam is the original source pam, rLastIdx is the last calculated position, rDelCount the number + of "non-copy" nodes between rPam.Start() and rLastIdx. + nNewIdx is the new position of interest. + */ + void lcl_NonCopyCount( const SwPaM& rPam, SwNodeIndex& rLastIdx, const sal_uLong nNewIdx, sal_uLong& rDelCount ) + { + sal_uLong nStart = rPam.Start()->nNode.GetIndex(); + sal_uLong nEnd = rPam.End()->nNode.GetIndex(); + if( rLastIdx.GetIndex() < nNewIdx ) // Moving forward? + { + // We never copy the StartOfContent node + do // count "non-copy" nodes + { + SwNode& rNode = rLastIdx.GetNode(); + if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd ) + || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) ) + { + ++rDelCount; + } + ++rLastIdx; + } + while( rLastIdx.GetIndex() < nNewIdx ); + } + else if( rDelCount ) // optimization: if there are no "non-copy" nodes until now, + // no move backward needed + { + while( rLastIdx.GetIndex() > nNewIdx ) + { + SwNode& rNode = rLastIdx.GetNode(); + if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd ) + || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) ) + { + --rDelCount; + } + rLastIdx--; + } + } + } + + void lcl_SetCpyPos( const SwPosition& rOrigPos, + const SwPosition& rOrigStt, + const SwPosition& rCpyStt, + SwPosition& rChgPos, + sal_uLong nDelCount ) + { + sal_uLong nNdOff = rOrigPos.nNode.GetIndex(); + nNdOff -= rOrigStt.nNode.GetIndex(); + nNdOff -= nDelCount; + sal_Int32 nContentPos = rOrigPos.nContent.GetIndex(); + + // Always adjust <nNode> at to be changed <SwPosition> instance <rChgPos> + rChgPos.nNode = nNdOff + rCpyStt.nNode.GetIndex(); + if( !nNdOff ) + { + // just adapt the content index + if( nContentPos > rOrigStt.nContent.GetIndex() ) + nContentPos -= rOrigStt.nContent.GetIndex(); + else + nContentPos = 0; + nContentPos += rCpyStt.nContent.GetIndex(); + } + rChgPos.nContent.Assign( rChgPos.nNode.GetNode().GetContentNode(), nContentPos ); + } + +} + +namespace sw +{ + // TODO: use SaveBookmark (from DelBookmarks) + void CopyBookmarks(const SwPaM& rPam, SwPosition& rCpyPam) + { + const SwDoc* pSrcDoc = rPam.GetDoc(); + SwDoc* pDestDoc = rCpyPam.GetDoc(); + const IDocumentMarkAccess* const pSrcMarkAccess = pSrcDoc->getIDocumentMarkAccess(); + ::sw::UndoGuard const undoGuard(pDestDoc->GetIDocumentUndoRedo()); + + const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End(); + SwPosition const*const pCpyStt = &rCpyPam; + + std::vector< const ::sw::mark::IMark* > vMarksToCopy; + for ( IDocumentMarkAccess::const_iterator_t ppMark = pSrcMarkAccess->getAllMarksBegin(); + ppMark != pSrcMarkAccess->getAllMarksEnd(); + ++ppMark ) + { + const ::sw::mark::IMark* const pMark = *ppMark; + + const SwPosition& rMarkStart = pMark->GetMarkStart(); + const SwPosition& rMarkEnd = pMark->GetMarkEnd(); + // only include marks that are in the range and not touching both start and end + // - not for annotation or checkbox marks. + const bool bIsNotOnBoundary = + pMark->IsExpanded() + ? (rMarkStart != rStt || rMarkEnd != rEnd) // rMarkStart != rMarkEnd + : (rMarkStart != rStt && rMarkEnd != rEnd); // rMarkStart == rMarkEnd + const IDocumentMarkAccess::MarkType aMarkType = IDocumentMarkAccess::GetType(*pMark); + if ( rMarkStart >= rStt && rMarkEnd <= rEnd + && ( bIsNotOnBoundary + || aMarkType == IDocumentMarkAccess::MarkType::ANNOTATIONMARK + || aMarkType == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK + || aMarkType == IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK + || aMarkType == IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK + || aMarkType == IDocumentMarkAccess::MarkType::DATE_FIELDMARK)) + { + vMarksToCopy.push_back(pMark); + } + } + // We have to count the "non-copied" nodes... + sal_uLong nDelCount; + SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount)); + for(const sw::mark::IMark* const pMark : vMarksToCopy) + { + SwPaM aTmpPam(*pCpyStt); + lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetMarkPos().nNode.GetIndex(), nDelCount); + lcl_SetCpyPos( pMark->GetMarkPos(), rStt, *pCpyStt, *aTmpPam.GetPoint(), nDelCount); + if(pMark->IsExpanded()) + { + aTmpPam.SetMark(); + lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetOtherMarkPos().nNode.GetIndex(), nDelCount); + lcl_SetCpyPos(pMark->GetOtherMarkPos(), rStt, *pCpyStt, *aTmpPam.GetMark(), nDelCount); + } + + ::sw::mark::IMark* const pNewMark = pDestDoc->getIDocumentMarkAccess()->makeMark( + aTmpPam, + pMark->GetName(), + IDocumentMarkAccess::GetType(*pMark), + ::sw::mark::InsertMode::CopyText); + // Explicitly try to get exactly the same name as in the source + // because NavigatorReminders, DdeBookmarks etc. ignore the proposed name + pDestDoc->getIDocumentMarkAccess()->renameMark(pNewMark, pMark->GetName()); + + // copying additional attributes for bookmarks or fieldmarks + ::sw::mark::IBookmark* const pNewBookmark = + dynamic_cast< ::sw::mark::IBookmark* const >(pNewMark); + const ::sw::mark::IBookmark* const pOldBookmark = + dynamic_cast< const ::sw::mark::IBookmark* >(pMark); + if (pNewBookmark && pOldBookmark) + { + pNewBookmark->SetKeyCode(pOldBookmark->GetKeyCode()); + pNewBookmark->SetShortName(pOldBookmark->GetShortName()); + } + ::sw::mark::IFieldmark* const pNewFieldmark = + dynamic_cast< ::sw::mark::IFieldmark* const >(pNewMark); + const ::sw::mark::IFieldmark* const pOldFieldmark = + dynamic_cast< const ::sw::mark::IFieldmark* >(pMark); + if (pNewFieldmark && pOldFieldmark) + { + pNewFieldmark->SetFieldname(pOldFieldmark->GetFieldname()); + pNewFieldmark->SetFieldHelptext(pOldFieldmark->GetFieldHelptext()); + ::sw::mark::IFieldmark::parameter_map_t* pNewParams = pNewFieldmark->GetParameters(); + const ::sw::mark::IFieldmark::parameter_map_t* pOldParams = pOldFieldmark->GetParameters(); + for (const auto& rEntry : *pOldParams ) + { + pNewParams->insert( rEntry ); + } + } + + ::sfx2::Metadatable const*const pMetadatable( + dynamic_cast< ::sfx2::Metadatable const* >(pMark)); + ::sfx2::Metadatable *const pNewMetadatable( + dynamic_cast< ::sfx2::Metadatable * >(pNewMark)); + if (pMetadatable && pNewMetadatable) + { + pNewMetadatable->RegisterAsCopyOf(*pMetadatable); + } + } + } +} // namespace sw + +namespace +{ + void lcl_DeleteRedlines( const SwPaM& rPam, SwPaM& rCpyPam ) + { + const SwDoc* pSrcDoc = rPam.GetDoc(); + const SwRedlineTable& rTable = pSrcDoc->getIDocumentRedlineAccess().GetRedlineTable(); + if( !rTable.empty() ) + { + SwDoc* pDestDoc = rCpyPam.GetDoc(); + SwPosition* pCpyStt = rCpyPam.Start(), *pCpyEnd = rCpyPam.End(); + std::unique_ptr<SwPaM> pDelPam; + const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + // We have to count the "non-copied" nodes + sal_uLong nDelCount; + SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount)); + + SwRedlineTable::size_type n = 0; + pSrcDoc->getIDocumentRedlineAccess().GetRedline( *pStt, &n ); + for( ; n < rTable.size(); ++n ) + { + const SwRangeRedline* pRedl = rTable[ n ]; + if( RedlineType::Delete == pRedl->GetType() && pRedl->IsVisible() ) + { + const SwPosition *pRStt = pRedl->Start(), *pREnd = pRedl->End(); + + SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ); + switch( eCmpPos ) + { + case SwComparePosition::CollideEnd: + case SwComparePosition::Before: + // Pos1 is before Pos2 + break; + + case SwComparePosition::CollideStart: + case SwComparePosition::Behind: + // Pos1 is after Pos2 + n = rTable.size(); + break; + + default: + { + pDelPam.reset(new SwPaM( *pCpyStt, pDelPam.release() )); + if( *pStt < *pRStt ) + { + lcl_NonCopyCount( rPam, aCorrIdx, pRStt->nNode.GetIndex(), nDelCount ); + lcl_SetCpyPos( *pRStt, *pStt, *pCpyStt, + *pDelPam->GetPoint(), nDelCount ); + } + pDelPam->SetMark(); + + if( *pEnd < *pREnd ) + *pDelPam->GetPoint() = *pCpyEnd; + else + { + lcl_NonCopyCount( rPam, aCorrIdx, pREnd->nNode.GetIndex(), nDelCount ); + lcl_SetCpyPos( *pREnd, *pStt, *pCpyStt, + *pDelPam->GetPoint(), nDelCount ); + } + + if (pDelPam->GetNext() && *pDelPam->GetNext()->End() == *pDelPam->Start()) + { + *pDelPam->GetNext()->End() = *pDelPam->End(); + pDelPam.reset(pDelPam->GetNext()); + } + } + } + } + } + + if( pDelPam ) + { + RedlineFlags eOld = pDestDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDestDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::Ignore ); + + ::sw::UndoGuard const undoGuard(pDestDoc->GetIDocumentUndoRedo()); + + do { + pDestDoc->getIDocumentContentOperations().DeleteAndJoin( *pDelPam->GetNext() ); + if( !pDelPam->IsMultiSelection() ) + break; + delete pDelPam->GetNext(); + } while( true ); + + pDestDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + } + } + + void lcl_DeleteRedlines( const SwNodeRange& rRg, SwNodeRange const & rCpyRg ) + { + SwDoc* pSrcDoc = rRg.aStart.GetNode().GetDoc(); + if( !pSrcDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aRgTmp( rRg.aStart, rRg.aEnd ); + SwPaM aCpyTmp( rCpyRg.aStart, rCpyRg.aEnd ); + lcl_DeleteRedlines( aRgTmp, aCpyTmp ); + } + } + + void lcl_ChainFormats( SwFlyFrameFormat *pSrc, SwFlyFrameFormat *pDest ) + { + SwFormatChain aSrc( pSrc->GetChain() ); + if ( !aSrc.GetNext() ) + { + aSrc.SetNext( pDest ); + pSrc->SetFormatAttr( aSrc ); + } + SwFormatChain aDest( pDest->GetChain() ); + if ( !aDest.GetPrev() ) + { + aDest.SetPrev( pSrc ); + pDest->SetFormatAttr( aDest ); + } + } + + // #i86492# + bool lcl_ContainsOnlyParagraphsInList( const SwPaM& rPam ) + { + bool bRet = false; + + const SwTextNode* pTextNd = rPam.Start()->nNode.GetNode().GetTextNode(); + const SwTextNode* pEndTextNd = rPam.End()->nNode.GetNode().GetTextNode(); + if ( pTextNd && pTextNd->IsInList() && + pEndTextNd && pEndTextNd->IsInList() ) + { + bRet = true; + SwNodeIndex aIdx(rPam.Start()->nNode); + + do + { + ++aIdx; + pTextNd = aIdx.GetNode().GetTextNode(); + + if ( !pTextNd || !pTextNd->IsInList() ) + { + bRet = false; + break; + } + } while (pTextNd != pEndTextNd); + } + + return bRet; + } + + bool lcl_MarksWholeNode(const SwPaM & rPam) + { + bool bResult = false; + const SwPosition* pStt = rPam.Start(); + const SwPosition* pEnd = rPam.End(); + + if (nullptr != pStt && nullptr != pEnd) + { + const SwTextNode* pSttNd = pStt->nNode.GetNode().GetTextNode(); + const SwTextNode* pEndNd = pEnd->nNode.GetNode().GetTextNode(); + + if (nullptr != pSttNd && nullptr != pEndNd && + pStt->nContent.GetIndex() == 0 && + pEnd->nContent.GetIndex() == pEndNd->Len()) + { + bResult = true; + } + } + + return bResult; + } +} + +//local functions originally from sw/source/core/doc/docedt.cxx +namespace sw +{ + void CalcBreaks(std::vector<std::pair<sal_uLong, sal_Int32>> & rBreaks, + SwPaM const & rPam, bool const isOnlyFieldmarks) + { + sal_uLong const nStartNode(rPam.Start()->nNode.GetIndex()); + sal_uLong const nEndNode(rPam.End()->nNode.GetIndex()); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + IDocumentMarkAccess const& rIDMA(*rPam.GetDoc()->getIDocumentMarkAccess()); + + std::stack<std::tuple<sw::mark::IFieldmark const*, bool, sal_uLong, sal_Int32>> startedFields; + + for (sal_uLong n = nStartNode; n <= nEndNode; ++n) + { + SwNode *const pNode(rNodes[n]); + if (pNode->IsTextNode()) + { + SwTextNode & rTextNode(*pNode->GetTextNode()); + sal_Int32 const nStart(n == nStartNode + ? rPam.Start()->nContent.GetIndex() + : 0); + sal_Int32 const nEnd(n == nEndNode + ? rPam.End()->nContent.GetIndex() + : rTextNode.Len()); + for (sal_Int32 i = nStart; i < nEnd; ++i) + { + const sal_Unicode c(rTextNode.GetText()[i]); + switch (c) + { + // note: CH_TXT_ATR_FORMELEMENT does not need handling + // not sure how CH_TXT_ATR_INPUTFIELDSTART/END are currently handled + case CH_TXTATR_INWORD: + case CH_TXTATR_BREAKWORD: + { + // META hints only have dummy char at the start, not + // at the end, so no need to check in nStartNode + if (n == nEndNode && !isOnlyFieldmarks) + { + SwTextAttr const*const pAttr(rTextNode.GetTextAttrForCharAt(i)); + if (pAttr && pAttr->End() && (nEnd < *pAttr->End())) + { + assert(pAttr->HasDummyChar()); + rBreaks.emplace_back(n, i); + } + } + break; + } + case CH_TXT_ATR_FIELDSTART: + { + auto const pFieldMark(rIDMA.getFieldmarkAt(SwPosition(rTextNode, i))); + startedFields.emplace(pFieldMark, false, 0, 0); + break; + } + case CH_TXT_ATR_FIELDSEP: + { + if (startedFields.empty()) + { + rBreaks.emplace_back(n, i); + } + else + { // no way to find the field via MarkManager... + assert(std::get<0>(startedFields.top())->IsCoveringPosition(SwPosition(rTextNode, i))); + std::get<1>(startedFields.top()) = true; + std::get<2>(startedFields.top()) = n; + std::get<3>(startedFields.top()) = i; + } + break; + } + case CH_TXT_ATR_FIELDEND: + { + if (startedFields.empty()) + { + rBreaks.emplace_back(n, i); + } + else + { // fieldmarks must not overlap => stack + assert(std::get<0>(startedFields.top()) == rIDMA.getFieldmarkAt(SwPosition(rTextNode, i))); + startedFields.pop(); + } + break; + } + } + } + } + else if (pNode->IsStartNode()) + { + if (pNode->EndOfSectionIndex() <= nEndNode) + { // fieldmark cannot overlap node section + n = pNode->EndOfSectionIndex(); + } + } + else + { // EndNode can actually happen with sections :( + assert(pNode->IsEndNode() || pNode->IsNoTextNode()); + } + } + while (!startedFields.empty()) + { + SwPosition const& rStart(std::get<0>(startedFields.top())->GetMarkStart()); + std::pair<sal_uLong, sal_Int32> const pos( + rStart.nNode.GetIndex(), rStart.nContent.GetIndex()); + auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), pos); + assert(it == rBreaks.end() || *it != pos); + rBreaks.insert(it, pos); + if (std::get<1>(startedFields.top())) + { + std::pair<sal_uLong, sal_Int32> const posSep( + std::get<2>(startedFields.top()), + std::get<3>(startedFields.top())); + it = std::lower_bound(rBreaks.begin(), rBreaks.end(), posSep); + assert(it == rBreaks.end() || *it != posSep); + rBreaks.insert(it, posSep); + } + startedFields.pop(); + } + } +} + +namespace +{ + + bool lcl_DoWithBreaks(::sw::DocumentContentOperationsManager & rDocumentContentOperations, SwPaM & rPam, + bool (::sw::DocumentContentOperationsManager::*pFunc)(SwPaM&, bool), const bool bForceJoinNext = false) + { + std::vector<std::pair<sal_uLong, sal_Int32>> Breaks; + + sw::CalcBreaks(Breaks, rPam); + + if (Breaks.empty()) + { + return (rDocumentContentOperations.*pFunc)(rPam, bForceJoinNext); + } + + // Deletion must be split into several parts if the text node + // contains a text attribute with end and with dummy character + // and the selection does not contain the text attribute completely, + // but overlaps its start (left), where the dummy character is. + + SwPosition const & rSelectionEnd( *rPam.End() ); + + bool bRet( true ); + // iterate from end to start, to avoid invalidating the offsets! + auto iter( Breaks.rbegin() ); + sal_uLong nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node! + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + + while (iter != Breaks.rend()) + { + rStart = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + bRet &= (rDocumentContentOperations.*pFunc)(aPam, bForceJoinNext); + nOffset = iter->first - rStart.nNode.GetIndex(); // deleted fly nodes... + } + rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); + ++iter; + } + + rStart = *rPam.Start(); // set to original start + if (rStart < rEnd) // check if part is empty + { + bRet &= (rDocumentContentOperations.*pFunc)(aPam, bForceJoinNext); + } + + return bRet; + } + + bool lcl_StrLenOverflow( const SwPaM& rPam ) + { + // If we try to merge two paragraphs we have to test if afterwards + // the string doesn't exceed the allowed string length + if( rPam.GetPoint()->nNode != rPam.GetMark()->nNode ) + { + const SwPosition* pStt = rPam.Start(), *pEnd = rPam.End(); + const SwTextNode* pEndNd = pEnd->nNode.GetNode().GetTextNode(); + if( (nullptr != pEndNd) && pStt->nNode.GetNode().IsTextNode() ) + { + const sal_uInt64 nSum = pStt->nContent.GetIndex() + + pEndNd->GetText().getLength() - pEnd->nContent.GetIndex(); + return nSum > o3tl::make_unsigned(SAL_MAX_INT32); + } + } + return false; + } + + struct SaveRedline + { + SwRangeRedline* pRedl; + sal_uInt32 nStt, nEnd; + sal_Int32 nSttCnt; + sal_Int32 nEndCnt; + + SaveRedline( SwRangeRedline* pR, const SwNodeIndex& rSttIdx ) + : pRedl(pR) + , nEnd(0) + , nEndCnt(0) + { + const SwPosition* pStt = pR->Start(), + * pEnd = pR->GetMark() == pStt ? pR->GetPoint() : pR->GetMark(); + sal_uInt32 nSttIdx = rSttIdx.GetIndex(); + nStt = pStt->nNode.GetIndex() - nSttIdx; + nSttCnt = pStt->nContent.GetIndex(); + if( pR->HasMark() ) + { + nEnd = pEnd->nNode.GetIndex() - nSttIdx; + nEndCnt = pEnd->nContent.GetIndex(); + } + + pRedl->GetPoint()->nNode = 0; + pRedl->GetPoint()->nContent.Assign( nullptr, 0 ); + pRedl->GetMark()->nNode = 0; + pRedl->GetMark()->nContent.Assign( nullptr, 0 ); + } + + SaveRedline( SwRangeRedline* pR, const SwPosition& rPos ) + : pRedl(pR) + , nEnd(0) + , nEndCnt(0) + { + const SwPosition* pStt = pR->Start(), + * pEnd = pR->GetMark() == pStt ? pR->GetPoint() : pR->GetMark(); + sal_uInt32 nSttIdx = rPos.nNode.GetIndex(); + nStt = pStt->nNode.GetIndex() - nSttIdx; + nSttCnt = pStt->nContent.GetIndex(); + if( nStt == 0 ) + nSttCnt = nSttCnt - rPos.nContent.GetIndex(); + if( pR->HasMark() ) + { + nEnd = pEnd->nNode.GetIndex() - nSttIdx; + nEndCnt = pEnd->nContent.GetIndex(); + if( nEnd == 0 ) + nEndCnt = nEndCnt - rPos.nContent.GetIndex(); + } + + pRedl->GetPoint()->nNode = 0; + pRedl->GetPoint()->nContent.Assign( nullptr, 0 ); + pRedl->GetMark()->nNode = 0; + pRedl->GetMark()->nContent.Assign( nullptr, 0 ); + } + + void SetPos( sal_uInt32 nInsPos ) + { + pRedl->GetPoint()->nNode = nInsPos + nStt; + pRedl->GetPoint()->nContent.Assign( pRedl->GetContentNode(), nSttCnt ); + if( pRedl->HasMark() ) + { + pRedl->GetMark()->nNode = nInsPos + nEnd; + pRedl->GetMark()->nContent.Assign( pRedl->GetContentNode(false), nEndCnt ); + } + } + + void SetPos( const SwPosition& aPos ) + { + pRedl->GetPoint()->nNode = aPos.nNode.GetIndex() + nStt; + pRedl->GetPoint()->nContent.Assign( pRedl->GetContentNode(), nSttCnt + ( nStt == 0 ? aPos.nContent.GetIndex() : 0 ) ); + if( pRedl->HasMark() ) + { + pRedl->GetMark()->nNode = aPos.nNode.GetIndex() + nEnd; + pRedl->GetMark()->nContent.Assign( pRedl->GetContentNode(false), nEndCnt + ( nEnd == 0 ? aPos.nContent.GetIndex() : 0 ) ); + } + } + }; + + typedef std::vector< SaveRedline > SaveRedlines_t; + + void lcl_SaveRedlines(const SwPaM& aPam, SaveRedlines_t& rArr) + { + SwDoc* pDoc = aPam.GetNode().GetDoc(); + + const SwPosition* pStart = aPam.Start(); + const SwPosition* pEnd = aPam.End(); + + // get first relevant redline + SwRedlineTable::size_type nCurrentRedline; + pDoc->getIDocumentRedlineAccess().GetRedline( *pStart, &nCurrentRedline ); + if( nCurrentRedline > 0) + nCurrentRedline--; + + // redline mode RedlineFlags::Ignore|RedlineFlags::On; save old mode + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + // iterate over relevant redlines and decide for each whether it should + // be saved, or split + saved + SwRedlineTable& rRedlineTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for( ; nCurrentRedline < rRedlineTable.size(); nCurrentRedline++ ) + { + SwRangeRedline* pCurrent = rRedlineTable[ nCurrentRedline ]; + SwComparePosition eCompare = + ComparePosition( *pCurrent->Start(), *pCurrent->End(), + *pStart, *pEnd); + + // we must save this redline if it overlaps aPam + // (we may have to split it, too) + if( eCompare == SwComparePosition::OverlapBehind || + eCompare == SwComparePosition::OverlapBefore || + eCompare == SwComparePosition::Outside || + eCompare == SwComparePosition::Inside || + eCompare == SwComparePosition::Equal ) + { + rRedlineTable.Remove( nCurrentRedline-- ); + + // split beginning, if necessary + if( eCompare == SwComparePosition::OverlapBefore || + eCompare == SwComparePosition::Outside ) + { + SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent ); + *pNewRedline->End() = *pStart; + *pCurrent->Start() = *pStart; + pDoc->getIDocumentRedlineAccess().AppendRedline( pNewRedline, true ); + } + + // split end, if necessary + if( eCompare == SwComparePosition::OverlapBehind || + eCompare == SwComparePosition::Outside ) + { + SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent ); + *pNewRedline->Start() = *pEnd; + *pCurrent->End() = *pEnd; + pDoc->getIDocumentRedlineAccess().AppendRedline( pNewRedline, true ); + } + + // save the current redline + rArr.emplace_back( pCurrent, *pStart ); + } + } + + // restore old redline mode + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_RestoreRedlines(SwDoc* pDoc, const SwPosition& rPos, SaveRedlines_t& rArr) + { + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + for(SaveRedline & rSvRedLine : rArr) + { + rSvRedLine.SetPos( rPos ); + pDoc->getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_SaveRedlines(const SwNodeRange& rRg, SaveRedlines_t& rArr) + { + SwDoc* pDoc = rRg.aStart.GetNode().GetDoc(); + SwRedlineTable::size_type nRedlPos; + SwPosition aSrchPos( rRg.aStart ); aSrchPos.nNode--; + aSrchPos.nContent.Assign( aSrchPos.nNode.GetNode().GetContentNode(), 0 ); + if( pDoc->getIDocumentRedlineAccess().GetRedline( aSrchPos, &nRedlPos ) && nRedlPos ) + --nRedlPos; + else if( nRedlPos >= pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() ) + return ; + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + SwRedlineTable& rRedlTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + + do { + SwRangeRedline* pTmp = rRedlTable[ nRedlPos ]; + + const SwPosition* pRStt = pTmp->Start(), + * pREnd = pTmp->GetMark() == pRStt + ? pTmp->GetPoint() : pTmp->GetMark(); + + if( pRStt->nNode < rRg.aStart ) + { + if( pREnd->nNode > rRg.aStart && pREnd->nNode < rRg.aEnd ) + { + // Create a copy and set the end of the original to the end of the MoveArea. + // The copy is moved too. + SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp ); + SwPosition* pTmpPos = pNewRedl->Start(); + pTmpPos->nNode = rRg.aStart; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + + rArr.emplace_back(pNewRedl, rRg.aStart); + + pTmpPos = pTmp->End(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + } + else if( pREnd->nNode == rRg.aStart ) + { + SwPosition* pTmpPos = pTmp->End(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + } + } + else if( pRStt->nNode < rRg.aEnd ) + { + rRedlTable.Remove( nRedlPos-- ); + if( pREnd->nNode < rRg.aEnd || + ( pREnd->nNode == rRg.aEnd && !pREnd->nContent.GetIndex()) ) + { + // move everything + rArr.emplace_back( pTmp, rRg.aStart ); + } + else + { + // split + SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp ); + SwPosition* pTmpPos = pNewRedl->End(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + + rArr.emplace_back( pNewRedl, rRg.aStart ); + + pTmpPos = pTmp->Start(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + pDoc->getIDocumentRedlineAccess().AppendRedline( pTmp, true ); + } + } + else + break; + + } while( ++nRedlPos < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() ); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_RestoreRedlines(SwDoc *const pDoc, sal_uInt32 const nInsPos, SaveRedlines_t& rArr) + { + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + for(SaveRedline & rSvRedLine : rArr) + { + rSvRedLine.SetPos( nInsPos ); + pDoc->getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ); + if (rSvRedLine.pRedl->GetType() == RedlineType::Delete) + { + UpdateFramesForAddDeleteRedline(*pDoc, *rSvRedLine.pRedl); + } + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + bool lcl_SaveFootnote( const SwNodeIndex& rSttNd, const SwNodeIndex& rEndNd, + const SwNodeIndex& rInsPos, + SwFootnoteIdxs& rFootnoteArr, SwFootnoteIdxs& rSaveArr, + const SwIndex* pSttCnt = nullptr, const SwIndex* pEndCnt = nullptr ) + { + bool bUpdateFootnote = false; + const SwNodes& rNds = rInsPos.GetNodes(); + const bool bDelFootnote = rInsPos.GetIndex() < rNds.GetEndOfAutotext().GetIndex() && + rSttNd.GetIndex() >= rNds.GetEndOfAutotext().GetIndex(); + const bool bSaveFootnote = !bDelFootnote && + rInsPos.GetIndex() >= rNds.GetEndOfExtras().GetIndex(); + if( !rFootnoteArr.empty() ) + { + + size_t nPos = 0; + rFootnoteArr.SeekEntry( rSttNd, &nPos ); + SwTextFootnote* pSrch; + const SwNode* pFootnoteNd; + + // Delete/save all that come after it + while( nPos < rFootnoteArr.size() && ( pFootnoteNd = + &( pSrch = rFootnoteArr[ nPos ] )->GetTextNode())->GetIndex() + <= rEndNd.GetIndex() ) + { + const sal_Int32 nFootnoteSttIdx = pSrch->GetStart(); + if( ( pEndCnt && pSttCnt ) + ? (( &rSttNd.GetNode() == pFootnoteNd && + pSttCnt->GetIndex() > nFootnoteSttIdx) || + ( &rEndNd.GetNode() == pFootnoteNd && + nFootnoteSttIdx >= pEndCnt->GetIndex() )) + : ( &rEndNd.GetNode() == pFootnoteNd )) + { + ++nPos; // continue searching + } + else + { + // delete it + if( bDelFootnote ) + { + SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode()); + SwIndex aIdx( &rTextNd, nFootnoteSttIdx ); + rTextNd.EraseText( aIdx, 1 ); + } + else + { + pSrch->DelFrames(nullptr); + rFootnoteArr.erase( rFootnoteArr.begin() + nPos ); + if( bSaveFootnote ) + rSaveArr.insert( pSrch ); + } + bUpdateFootnote = true; + } + } + + while( nPos-- && ( pFootnoteNd = &( pSrch = rFootnoteArr[ nPos ] )-> + GetTextNode())->GetIndex() >= rSttNd.GetIndex() ) + { + const sal_Int32 nFootnoteSttIdx = pSrch->GetStart(); + if( !pEndCnt || !pSttCnt || + ! (( &rSttNd.GetNode() == pFootnoteNd && + pSttCnt->GetIndex() > nFootnoteSttIdx ) || + ( &rEndNd.GetNode() == pFootnoteNd && + nFootnoteSttIdx >= pEndCnt->GetIndex() )) ) + { + if( bDelFootnote ) + { + // delete it + SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode()); + SwIndex aIdx( &rTextNd, nFootnoteSttIdx ); + rTextNd.EraseText( aIdx, 1 ); + } + else + { + pSrch->DelFrames(nullptr); + rFootnoteArr.erase( rFootnoteArr.begin() + nPos ); + if( bSaveFootnote ) + rSaveArr.insert( pSrch ); + } + bUpdateFootnote = true; + } + } + } + // When moving from redline section into document content section, e.g. + // after loading a document with (delete-)redlines, the footnote array + // has to be adjusted... (#i70572) + if( bSaveFootnote ) + { + SwNodeIndex aIdx( rSttNd ); + while( aIdx < rEndNd ) // Check the moved section + { + SwNode* pNode = &aIdx.GetNode(); + if( pNode->IsTextNode() ) // Looking for text nodes... + { + SwpHints *pHints = pNode->GetTextNode()->GetpSwpHints(); + if( pHints && pHints->HasFootnote() ) //...with footnotes + { + bUpdateFootnote = true; // Heureka + const size_t nCount = pHints->Count(); + for( size_t i = 0; i < nCount; ++i ) + { + SwTextAttr *pAttr = pHints->Get( i ); + if ( pAttr->Which() == RES_TXTATR_FTN ) + { + rSaveArr.insert( static_cast<SwTextFootnote*>(pAttr) ); + } + } + } + } + ++aIdx; + } + } + return bUpdateFootnote; + } + + bool lcl_MayOverwrite( const SwTextNode *pNode, const sal_Int32 nPos ) + { + sal_Unicode const cChr = pNode->GetText()[nPos]; + switch (cChr) + { + case CH_TXTATR_BREAKWORD: + case CH_TXTATR_INWORD: + return !pNode->GetTextAttrForCharAt(nPos);// how could there be none? + case CH_TXT_ATR_INPUTFIELDSTART: + case CH_TXT_ATR_INPUTFIELDEND: + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDSEP: + case CH_TXT_ATR_FIELDEND: + case CH_TXT_ATR_FORMELEMENT: + return false; + default: + return true; + } + } + + void lcl_SkipAttr( const SwTextNode *pNode, SwIndex &rIdx, sal_Int32 &rStart ) + { + if( !lcl_MayOverwrite( pNode, rStart ) ) + { + // skip all special attributes + do { + ++rIdx; + rStart = rIdx.GetIndex(); + } while (rStart < pNode->GetText().getLength() + && !lcl_MayOverwrite(pNode, rStart) ); + } + } + + bool lcl_GetTokenToParaBreak( OUString& rStr, OUString& rRet, bool bRegExpRplc ) + { + if( bRegExpRplc ) + { + sal_Int32 nPos = 0; + const OUString sPara("\\n"); + for (;;) + { + nPos = rStr.indexOf( sPara, nPos ); + if (nPos<0) + { + break; + } + // Has this been escaped? + if( nPos && '\\' == rStr[nPos-1]) + { + ++nPos; + if( nPos >= rStr.getLength() ) + { + break; + } + } + else + { + rRet = rStr.copy( 0, nPos ); + rStr = rStr.copy( nPos + sPara.getLength() ); + return true; + } + } + } + rRet = rStr; + rStr.clear(); + return false; + } +} + +namespace //local functions originally from docfmt.cxx +{ + + bool lcl_ApplyOtherSet( + SwContentNode & rNode, + SwHistory *const pHistory, + SfxItemSet const& rOtherSet, + SfxItemSet const& rFirstSet, + SfxItemSet const& rPropsSet, + SwRootFrame const*const pLayout, + SwNodeIndex *const o_pIndex = nullptr) + { + assert(rOtherSet.Count()); + + bool ret(false); + SwTextNode *const pTNd = rNode.GetTextNode(); + sw::MergedPara const* pMerged(nullptr); + if (pLayout && pLayout->IsHideRedlines() && pTNd) + { + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>( + pTNd->getLayoutFrame(pLayout))); + if (pTextFrame) + { + pMerged = pTextFrame->GetMergedPara(); + } + if (pMerged) + { + if (rFirstSet.Count()) + { + if (pHistory) + { + SwRegHistory aRegH(pMerged->pFirstNode, *pMerged->pFirstNode, pHistory); + ret = pMerged->pFirstNode->SetAttr(rFirstSet); + } + else + { + ret = pMerged->pFirstNode->SetAttr(rFirstSet); + } + } + if (rPropsSet.Count()) + { + if (pHistory) + { + SwRegHistory aRegH(pMerged->pParaPropsNode, *pMerged->pParaPropsNode, pHistory); + ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret; + } + else + { + ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret; + } + } + if (o_pIndex) + { + *o_pIndex = *pMerged->pLastNode; // skip hidden + } + } + } + + // input cursor can't be on hidden node, and iteration skips them + assert(!pLayout || !pLayout->IsHideRedlines() + || rNode.GetRedlineMergeFlag() != SwNode::Merge::Hidden); + + if (!pMerged) + { + if (pHistory) + { + SwRegHistory aRegH(&rNode, rNode, pHistory); + ret = rNode.SetAttr( rOtherSet ); + } + else + { + ret = rNode.SetAttr( rOtherSet ); + } + } + return ret; + } + + #define DELETECHARSETS if ( bDelete ) { delete pCharSet; delete pOtherSet; } + + /// Insert Hints according to content types; + // Is used in SwDoc::Insert(..., SwFormatHint &rHt) + + bool lcl_InsAttr( + SwDoc *const pDoc, + const SwPaM &rRg, + const SfxItemSet& rChgSet, + const SetAttrMode nFlags, + SwUndoAttr *const pUndo, + SwRootFrame const*const pLayout, + const bool bExpandCharToPara, + SwTextAttr **ppNewTextAttr) + { + // Divide the Sets (for selections in Nodes) + const SfxItemSet* pCharSet = nullptr; + const SfxItemSet* pOtherSet = nullptr; + bool bDelete = false; + bool bCharAttr = false; + bool bOtherAttr = false; + + // Check, if we can work with rChgSet or if we have to create additional SfxItemSets + if ( 1 == rChgSet.Count() ) + { + SfxItemIter aIter( rChgSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + if (pItem && !IsInvalidItem(pItem)) + { + const sal_uInt16 nWhich = pItem->Which(); + + if ( isCHRATR(nWhich) || + (RES_TXTATR_CHARFMT == nWhich) || + (RES_TXTATR_INETFMT == nWhich) || + (RES_TXTATR_AUTOFMT == nWhich) || + (RES_TXTATR_UNKNOWN_CONTAINER == nWhich) ) + { + pCharSet = &rChgSet; + bCharAttr = true; + } + + if ( isPARATR(nWhich) + || isPARATR_LIST(nWhich) + || isFRMATR(nWhich) + || isGRFATR(nWhich) + || isUNKNOWNATR(nWhich) + || isDrawingLayerAttribute(nWhich) ) + { + pOtherSet = &rChgSet; + bOtherAttr = true; + } + } + } + + // Build new itemset if either + // - rChgSet.Count() > 1 or + // - The attribute in rChgSet does not belong to one of the above categories + if ( !bCharAttr && !bOtherAttr ) + { + SfxItemSet* pTmpCharItemSet = new SfxItemSet( + pDoc->GetAttrPool(), + svl::Items< + RES_CHRATR_BEGIN, RES_CHRATR_END - 1, + RES_TXTATR_AUTOFMT, RES_TXTATR_CHARFMT, + RES_TXTATR_UNKNOWN_CONTAINER, + RES_TXTATR_UNKNOWN_CONTAINER>{}); + + SfxItemSet* pTmpOtherItemSet = new SfxItemSet( + pDoc->GetAttrPool(), + svl::Items< + RES_PARATR_BEGIN, RES_GRFATR_END - 1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1, + // FillAttribute support: + XATTR_FILL_FIRST, XATTR_FILL_LAST>{}); + + pTmpCharItemSet->Put( rChgSet ); + pTmpOtherItemSet->Put( rChgSet ); + + pCharSet = pTmpCharItemSet; + pOtherSet = pTmpOtherItemSet; + + bDelete = true; + } + + SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr; + bool bRet = false; + const SwPosition *pStt = rRg.Start(), *pEnd = rRg.End(); + SwContentNode* pNode = pStt->nNode.GetNode().GetContentNode(); + + if( pNode && pNode->IsTextNode() ) + { + // tdf#127606 at editing, remove different formatting of DOCX-like numbering symbol + if (pLayout && pNode->GetTextNode()->getIDocumentSettingAccess()-> + get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING )) + { + SwContentNode* pEndNode = pEnd->nNode.GetNode().GetContentNode(); + SwContentNode* pCurrentNode = pEndNode; + auto nStartIndex = pNode->GetIndex(); + auto nEndIndex = pEndNode->GetIndex(); + SwNodeIndex aIdx( pEnd->nNode.GetNode() ); + while ( pCurrentNode != nullptr && nStartIndex <= pCurrentNode->GetIndex() ) + { + if (pCurrentNode->GetSwAttrSet().HasItem(RES_PARATR_LIST_AUTOFMT) && + // remove character formatting only on wholly selected paragraphs + (nStartIndex < pCurrentNode->GetIndex() || pStt->nContent.GetIndex() == 0) && + (pCurrentNode->GetIndex() < nEndIndex || pEnd->nContent.GetIndex() == pEndNode->Len())) + { + pCurrentNode->ResetAttr(RES_PARATR_LIST_AUTOFMT); + // reset also paragraph marker + SwIndex nIdx( pCurrentNode, pCurrentNode->Len() ); + pCurrentNode->GetTextNode()->RstTextAttr(nIdx, 1); + } + pCurrentNode = SwNodes::GoPrevious( &aIdx ); + } + } + // #i27615# + if (rRg.IsInFrontOfLabel()) + { + SwTextNode * pTextNd = pNode->GetTextNode(); + if (pLayout) + { + pTextNd = sw::GetParaPropsNode(*pLayout, *pTextNd); + } + SwNumRule * pNumRule = pTextNd->GetNumRule(); + + if ( !pNumRule ) + { + OSL_FAIL( "<InsAttr(..)> - PaM in front of label, but text node has no numbering rule set. This is a serious defect." ); + DELETECHARSETS + return false; + } + + int nLevel = pTextNd->GetActualListLevel(); + + if (nLevel < 0) + nLevel = 0; + + if (nLevel >= MAXLEVEL) + nLevel = MAXLEVEL - 1; + + SwNumFormat aNumFormat = pNumRule->Get(static_cast<sal_uInt16>(nLevel)); + SwCharFormat * pCharFormat = + pDoc->FindCharFormatByName(aNumFormat.GetCharFormatName()); + + if (pCharFormat) + { + if (pHistory) + pHistory->Add(pCharFormat->GetAttrSet(), *pCharFormat); + + if ( pCharSet ) + pCharFormat->SetFormatAttr(*pCharSet); + } + + DELETECHARSETS + return true; + } + + const SwIndex& rSt = pStt->nContent; + + // Attributes without an end do not have a range + if ( !bCharAttr && !bOtherAttr ) + { + SfxItemSet aTextSet( pDoc->GetAttrPool(), + svl::Items<RES_TXTATR_NOEND_BEGIN, RES_TXTATR_NOEND_END-1>{} ); + aTextSet.Put( rChgSet ); + if( aTextSet.Count() ) + { + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( + aTextSet, rSt.GetIndex(), rSt.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr ) || bRet; + + if (bRet && (pDoc->getIDocumentRedlineAccess().IsRedlineOn() || (!pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() + && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()))) + { + SwPaM aPam( pStt->nNode, pStt->nContent.GetIndex()-1, + pStt->nNode, pStt->nContent.GetIndex() ); + + if( pUndo ) + pUndo->SaveRedlineData( aPam, true ); + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + else + pDoc->getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + } + + // TextAttributes with an end never expand their range + if ( !bCharAttr && !bOtherAttr ) + { + // CharFormat and URL attributes are treated separately! + // TEST_TEMP ToDo: AutoFormat! + SfxItemSet aTextSet( + pDoc->GetAttrPool(), + svl::Items< + RES_TXTATR_REFMARK, RES_TXTATR_METAFIELD, + RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY, + RES_TXTATR_INPUTFIELD, RES_TXTATR_INPUTFIELD>{}); + + aTextSet.Put( rChgSet ); + if( aTextSet.Count() ) + { + const sal_Int32 nInsCnt = rSt.GetIndex(); + const sal_Int32 nEnd = pStt->nNode == pEnd->nNode + ? pEnd->nContent.GetIndex() + : pNode->Len(); + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( aTextSet, nInsCnt, nEnd, nFlags, ppNewTextAttr ) + || bRet; + + if (bRet && (pDoc->getIDocumentRedlineAccess().IsRedlineOn() || (!pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() + && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()))) + { + // Was text content inserted? (RefMark/TOXMarks without an end) + bool bTextIns = nInsCnt != rSt.GetIndex(); + // Was content inserted or set over the selection? + SwPaM aPam( pStt->nNode, bTextIns ? nInsCnt + 1 : nEnd, + pStt->nNode, nInsCnt ); + if( pUndo ) + pUndo->SaveRedlineData( aPam, bTextIns ); + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pDoc->getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline( + bTextIns ? RedlineType::Insert : RedlineType::Format, aPam ), + true); + else if( bTextIns ) + pDoc->getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + } + } + + // We always have to set the auto flag for PageDescs that are set at the Node! + if( pOtherSet && pOtherSet->Count() ) + { + SwTableNode* pTableNd; + const SwFormatPageDesc* pDesc; + if( SfxItemState::SET == pOtherSet->GetItemState( RES_PAGEDESC, + false, reinterpret_cast<const SfxPoolItem**>(&pDesc) )) + { + if( pNode ) + { + // Set auto flag. Only in the template it's without auto! + SwFormatPageDesc aNew( *pDesc ); + + // Tables now also know line breaks + if( !(nFlags & SetAttrMode::APICALL) && + nullptr != ( pTableNd = pNode->FindTableNode() ) ) + { + SwTableNode* pCurTableNd = pTableNd; + while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) ) + pTableNd = pCurTableNd; + + // set the table format + SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat(); + SwRegHistory aRegH( pFormat, *pTableNd, pHistory ); + pFormat->SetFormatAttr( aNew ); + bRet = true; + } + else + { + SwContentNode * pFirstNode(pNode); + if (pLayout && pLayout->IsHideRedlines()) + { + pFirstNode = sw::GetFirstAndLastNode(*pLayout, pStt->nNode).first; + } + SwRegHistory aRegH( pFirstNode, *pFirstNode, pHistory ); + bRet = pFirstNode->SetAttr( aNew ) || bRet; + } + } + + // bOtherAttr = true means that pOtherSet == rChgSet. In this case + // we know, that there is only one attribute in pOtherSet. We cannot + // perform the following operations, instead we return: + if ( bOtherAttr ) + return bRet; + + const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_PAGEDESC ); + if( !pOtherSet->Count() ) + { + DELETECHARSETS + return bRet; + } + } + + // Tables now also know line breaks + const SvxFormatBreakItem* pBreak; + if( pNode && !(nFlags & SetAttrMode::APICALL) && + nullptr != (pTableNd = pNode->FindTableNode() ) && + SfxItemState::SET == pOtherSet->GetItemState( RES_BREAK, + false, reinterpret_cast<const SfxPoolItem**>(&pBreak) ) ) + { + SwTableNode* pCurTableNd = pTableNd; + while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) ) + pTableNd = pCurTableNd; + + // set the table format + SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat(); + SwRegHistory aRegH( pFormat, *pTableNd, pHistory ); + pFormat->SetFormatAttr( *pBreak ); + bRet = true; + + // bOtherAttr = true means that pOtherSet == rChgSet. In this case + // we know, that there is only one attribute in pOtherSet. We cannot + // perform the following operations, instead we return: + if ( bOtherAttr ) + return bRet; + + const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_BREAK ); + if( !pOtherSet->Count() ) + { + DELETECHARSETS + return bRet; + } + } + + { + // If we have a PoolNumRule, create it if needed + const SwNumRuleItem* pRule; + sal_uInt16 nPoolId=0; + if( SfxItemState::SET == pOtherSet->GetItemState( RES_PARATR_NUMRULE, + false, reinterpret_cast<const SfxPoolItem**>(&pRule) ) && + !pDoc->FindNumRulePtr( pRule->GetValue() ) && + USHRT_MAX != (nPoolId = SwStyleNameMapper::GetPoolIdFromUIName ( pRule->GetValue(), + SwGetPoolIdFromName::NumRule )) ) + pDoc->getIDocumentStylePoolAccess().GetNumRuleFromPool( nPoolId ); + } + } + + SfxItemSet firstSet(pDoc->GetAttrPool(), + svl::Items<RES_PAGEDESC, RES_BREAK>{}); + if (pOtherSet && pOtherSet->Count()) + { // actually only RES_BREAK is possible here... + firstSet.Put(*pOtherSet); + } + SfxItemSet propsSet(pDoc->GetAttrPool(), + svl::Items<RES_PARATR_BEGIN, RES_PAGEDESC, + RES_BREAK+1, RES_FRMATR_END, + XATTR_FILL_FIRST, XATTR_FILL_LAST+1>{}); + if (pOtherSet && pOtherSet->Count()) + { + propsSet.Put(*pOtherSet); + } + + if( !rRg.HasMark() ) // no range + { + if( !pNode ) + { + DELETECHARSETS + return bRet; + } + + if( pNode->IsTextNode() && pCharSet && pCharSet->Count() ) + { + SwTextNode* pTextNd = pNode->GetTextNode(); + const SwIndex& rSt = pStt->nContent; + sal_Int32 nMkPos, nPtPos = rSt.GetIndex(); + const OUString& rStr = pTextNd->GetText(); + + // Special case: if the Cursor is located within a URL attribute, we take over it's area + SwTextAttr const*const pURLAttr( + pTextNd->GetTextAttrAt(rSt.GetIndex(), RES_TXTATR_INETFMT)); + if (pURLAttr && !pURLAttr->GetINetFormat().GetValue().isEmpty()) + { + nMkPos = pURLAttr->GetStart(); + nPtPos = *pURLAttr->End(); + } + else + { + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + pTextNd->GetText(), nPtPos, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/, + true); + + if( aBndry.startPos < nPtPos && nPtPos < aBndry.endPos ) + { + nMkPos = aBndry.startPos; + nPtPos = aBndry.endPos; + } + else + nPtPos = nMkPos = rSt.GetIndex(); + } + + // Remove the overriding attributes from the SwpHintsArray, + // if the selection spans across the whole paragraph. + // These attributes are inserted as FormatAttributes and + // never override the TextAttributes! + if( !(nFlags & SetAttrMode::DONTREPLACE ) && + pTextNd->HasHints() && !nMkPos && nPtPos == rStr.getLength()) + { + SwIndex aSt( pTextNd ); + if( pHistory ) + { + // Save all attributes for the Undo. + SwRegHistory aRHst( *pTextNd, pHistory ); + pTextNd->GetpSwpHints()->Register( &aRHst ); + pTextNd->RstTextAttr( aSt, nPtPos, 0, pCharSet ); + if( pTextNd->GetpSwpHints() ) + pTextNd->GetpSwpHints()->DeRegister(); + } + else + pTextNd->RstTextAttr( aSt, nPtPos, 0, pCharSet ); + } + + // the SwRegHistory inserts the attribute into the TextNode! + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( *pCharSet, nMkPos, nPtPos, nFlags, /*ppNewTextAttr*/nullptr ) + || bRet; + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + SwPaM aPam( *pNode, nMkPos, *pNode, nPtPos ); + + if( pUndo ) + pUndo->SaveRedlineData( aPam, false ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Format, aPam ), true); + } + } + if( pOtherSet && pOtherSet->Count() ) + { + // Need to check for unique item for DrawingLayer items of type NameOrIndex + // and evtl. correct that item to ensure unique names for that type. This call may + // modify/correct entries inside of the given SfxItemSet + SfxItemSet aTempLocalCopy(*pOtherSet); + + pDoc->CheckForUniqueItemForLineFillNameOrIndex(aTempLocalCopy); + bRet = lcl_ApplyOtherSet(*pNode, pHistory, aTempLocalCopy, firstSet, propsSet, pLayout) || bRet; + } + + DELETECHARSETS + return bRet; + } + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() && pCharSet && pCharSet->Count() ) + { + if( pUndo ) + pUndo->SaveRedlineData( rRg, false ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Format, rRg ), true); + } + + /* now if range */ + sal_uLong nNodes = 0; + + SwNodeIndex aSt( pDoc->GetNodes() ); + SwNodeIndex aEnd( pDoc->GetNodes() ); + SwIndex aCntEnd( pEnd->nContent ); + + if( pNode ) + { + const sal_Int32 nLen = pNode->Len(); + if( pStt->nNode != pEnd->nNode ) + aCntEnd.Assign( pNode, nLen ); + + if( pStt->nContent.GetIndex() != 0 || aCntEnd.GetIndex() != nLen ) + { + // the SwRegHistory inserts the attribute into the TextNode! + if( pNode->IsTextNode() && pCharSet && pCharSet->Count() ) + { + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems(*pCharSet, + pStt->nContent.GetIndex(), aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr) + || bRet; + } + + if( pOtherSet && pOtherSet->Count() ) + { + bRet = lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout) || bRet; + } + + // Only selection in a Node. + if( pStt->nNode == pEnd->nNode ) + { + //The data parameter flag: bExpandCharToPara, comes from the data member of SwDoc, + //which is set in SW MS Word Binary filter WW8ImplRreader. With this flag on, means that + //current setting attribute set is a character range properties set and comes from a MS Word + //binary file, and the setting range include a paragraph end position (0X0D); + //more specifications, as such property inside the character range properties set recorded in + //MS Word binary file are dealt and inserted into data model (SwDoc) one by one, so we + //only dealing the scenario that the char properties set with 1 item inside; + + if (bExpandCharToPara && pCharSet && pCharSet->Count() ==1 ) + { + SwTextNode* pCurrentNd = pStt->nNode.GetNode().GetTextNode(); + + if (pCurrentNd) + { + pCurrentNd->TryCharSetExpandToNum(*pCharSet); + + } + } + DELETECHARSETS + return bRet; + } + ++nNodes; + aSt.Assign( pStt->nNode.GetNode(), +1 ); + } + else + aSt = pStt->nNode; + aCntEnd = pEnd->nContent; // aEnd was changed! + } + else + aSt.Assign( pStt->nNode.GetNode(), +1 ); + + // aSt points to the first full Node now + + /* + * The selection spans more than one Node. + */ + if( pStt->nNode < pEnd->nNode ) + { + pNode = pEnd->nNode.GetNode().GetContentNode(); + if(pNode) + { + if( aCntEnd.GetIndex() != pNode->Len() ) + { + // the SwRegHistory inserts the attribute into the TextNode! + if( pNode->IsTextNode() && pCharSet && pCharSet->Count() ) + { + SwRegHistory history( pNode, *pNode, pHistory ); + (void)history.InsertItems(*pCharSet, + 0, aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr); + } + + if( pOtherSet && pOtherSet->Count() ) + { + lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout); + } + + ++nNodes; + aEnd = pEnd->nNode; + } + else + aEnd.Assign( pEnd->nNode.GetNode(), +1 ); + } + else + aEnd = pEnd->nNode; + } + else + aEnd.Assign( pEnd->nNode.GetNode(), +1 ); + + // aEnd points BEHIND the last full node now + + /* Edit the fully selected Nodes. */ + // Reset all attributes from the set! + if( pCharSet && pCharSet->Count() && !( SetAttrMode::DONTREPLACE & nFlags ) ) + { + ::sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHistory, pCharSet, pLayout); + pDoc->GetNodes().ForEach( aSt, aEnd, ::sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara ); + } + + bool bCreateSwpHints = pCharSet && ( + SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_CHARFMT, false ) || + SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_INETFMT, false ) ); + + for (SwNodeIndex current = aSt; current < aEnd; ++current) + { + SwTextNode *const pTNd = current.GetNode().GetTextNode(); + if (!pTNd) + continue; + + if (pLayout && pLayout->IsHideRedlines() + && pTNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { // not really sure what to do here, but applying to hidden + continue; // nodes doesn't make sense... + } + + if( pHistory ) + { + SwRegHistory aRegH( pTNd, *pTNd, pHistory ); + + if (pCharSet && pCharSet->Count()) + { + SwpHints *pSwpHints = bCreateSwpHints ? &pTNd->GetOrCreateSwpHints() + : pTNd->GetpSwpHints(); + if( pSwpHints ) + pSwpHints->Register( &aRegH ); + + pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags); + if( pSwpHints ) + pSwpHints->DeRegister(); + } + } + else + { + if (pCharSet && pCharSet->Count()) + pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags); + } + ++nNodes; + } + + if (pOtherSet && pOtherSet->Count()) + { + for (; aSt < aEnd; ++aSt) + { + pNode = aSt.GetNode().GetContentNode(); + if (!pNode) + continue; + + lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout, &aSt); + ++nNodes; + } + } + + //The data parameter flag: bExpandCharToPara, comes from the data member of SwDoc, + //which is set in SW MS Word Binary filter WW8ImplRreader. With this flag on, means that + //current setting attribute set is a character range properties set and comes from a MS Word + //binary file, and the setting range include a paragraph end position (0X0D); + //more specifications, as such property inside the character range properties set recorded in + //MS Word binary file are dealt and inserted into data model (SwDoc) one by one, so we + //only dealing the scenario that the char properties set with 1 item inside; + if (bExpandCharToPara && pCharSet && pCharSet->Count() ==1) + { + SwPosition aStartPos (*rRg.Start()); + SwPosition aEndPos (*rRg.End()); + + if (aEndPos.nNode.GetNode().GetTextNode() && aEndPos.nContent != aEndPos.nNode.GetNode().GetTextNode()->Len()) + aEndPos.nNode--; + + sal_uLong nStart = aStartPos.nNode.GetIndex(); + sal_uLong nEnd = aEndPos.nNode.GetIndex(); + for(; nStart <= nEnd; ++nStart) + { + SwNode* pNd = pDoc->GetNodes()[ nStart ]; + if (!pNd || !pNd->IsTextNode()) + continue; + SwTextNode *pCurrentNd = pNd->GetTextNode(); + pCurrentNd->TryCharSetExpandToNum(*pCharSet); + } + } + + DELETECHARSETS + return (nNodes != 0) || bRet; + } +} + +namespace sw +{ + +namespace mark +{ + bool IsFieldmarkOverlap(SwPaM const& rPaM) + { + std::vector<std::pair<sal_uLong, sal_Int32>> Breaks; + sw::CalcBreaks(Breaks, rPaM); + return !Breaks.empty(); + } +} + +DocumentContentOperationsManager::DocumentContentOperationsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ) +{ +} + +/** + * Checks if rStart..rEnd mark a range that makes sense to copy. + * + * IsMoveToFly means the copy is a move to create a fly + * and so existing flys at the edge must not be copied. + */ +static bool IsEmptyRange(const SwPosition& rStart, const SwPosition& rEnd, + SwCopyFlags const flags) +{ + if (rStart == rEnd) + { // check if a fly anchored there would be copied - then copy... + return !IsDestroyFrameAnchoredAtChar(rStart, rStart, rEnd, + (flags & SwCopyFlags::IsMoveToFly) + ? DelContentType::WriterfilterHack|DelContentType::AllMask + : DelContentType::AllMask); + } + else + { + return rEnd < rStart; + } +} + +// Copy an area into this document or into another document +bool +DocumentContentOperationsManager::CopyRange( SwPaM& rPam, SwPosition& rPos, + SwCopyFlags const flags) const +{ + const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + + SwDoc* pDoc = rPos.nNode.GetNode().GetDoc(); + bool bColumnSel = pDoc->IsClipBoard() && pDoc->IsColumnSelection(); + + // Catch if there's no copy to do + if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel)) + return false; + + // Prevent copying into Flys that are anchored in the source range + if (pDoc == &m_rDoc && (flags & SwCopyFlags::CheckPosInFly)) + { + // Correct the Start-/EndNode + sal_uLong nStt = pStt->nNode.GetIndex(), + nEnd = pEnd->nNode.GetIndex(), + nDiff = nEnd - nStt +1; + SwNode* pNd = m_rDoc.GetNodes()[ nStt ]; + if( pNd->IsContentNode() && pStt->nContent.GetIndex() ) + { + ++nStt; + --nDiff; + } + if( (pNd = m_rDoc.GetNodes()[ nEnd ])->IsContentNode() && + static_cast<SwContentNode*>(pNd)->Len() != pEnd->nContent.GetIndex() ) + { + --nEnd; + --nDiff; + } + if( nDiff && + lcl_ChkFlyFly( pDoc, nStt, nEnd, rPos.nNode.GetIndex() ) ) + { + return false; + } + } + + SwPaM* pRedlineRange = nullptr; + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() || + (!pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) ) + pRedlineRange = new SwPaM( rPos ); + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + + bool bRet = false; + + if( pDoc != &m_rDoc ) + { // ordinary copy + bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange); + } + else if( ! ( *pStt <= rPos && rPos < *pEnd && + ( pStt->nNode != pEnd->nNode || + !pStt->nNode.GetNode().IsTextNode() )) ) + { + // Copy to a position outside of the area, or copy a single TextNode + // Do an ordinary copy + bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange); + } + else + { + // Copy the range in itself + assert(!"mst: this is assumed to be dead code"); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + if( pRedlineRange ) + { + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, *pRedlineRange ), true); + else + pDoc->getIDocumentRedlineAccess().SplitRedline( *pRedlineRange ); + delete pRedlineRange; + } + + return bRet; +} + +/// Delete a full Section of the NodeArray. +/// The passed Node is located somewhere in the designated Section. +void DocumentContentOperationsManager::DeleteSection( SwNode *pNode ) +{ + assert(pNode && "Didn't pass a Node."); + + SwStartNode* pSttNd = pNode->IsStartNode() ? static_cast<SwStartNode*>(pNode) + : pNode->StartOfSectionNode(); + SwNodeIndex aSttIdx( *pSttNd ), aEndIdx( *pNode->EndOfSectionNode() ); + + // delete all Flys, Bookmarks, ... + DelFlyInRange( aSttIdx, aEndIdx ); + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( *pSttNd, true, RedlineType::Any ); + DelBookmarks(aSttIdx, aEndIdx); + + { + // move all Cursor/StackCursor/UnoCursor out of the to-be-deleted area + SwNodeIndex aMvStt( aSttIdx, 1 ); + SwDoc::CorrAbs( aMvStt, aEndIdx, SwPosition( aSttIdx ), true ); + } + + m_rDoc.GetNodes().DelNodes( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() + 1 ); +} + +void DocumentContentOperationsManager::DeleteDummyChar( + SwPosition const& rPos, sal_Unicode const cDummy) +{ + SwPaM aPam(rPos, rPos); + ++aPam.GetPoint()->nContent; + assert(aPam.GetText().getLength() == 1 && aPam.GetText()[0] == cDummy); + (void) cDummy; + + DeleteRangeImpl(aPam); + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } +} + +void DocumentContentOperationsManager::DeleteRange( SwPaM & rPam ) +{ + lcl_DoWithBreaks( *this, rPam, &DocumentContentOperationsManager::DeleteRangeImpl ); + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } +} + +bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam ) +{ + const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End(); + const SwNode* pNd = &rStt.nNode.GetNode(); + sal_uInt32 nSectDiff = pNd->StartOfSectionNode()->EndOfSectionIndex() - + pNd->StartOfSectionIndex(); + sal_uInt32 nNodeDiff = rEnd.nNode.GetIndex() - rStt.nNode.GetIndex(); + + if ( nSectDiff-2 <= nNodeDiff || m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || + /* #i9185# Prevent getting the node after the end node (see below) */ + rEnd.nNode.GetIndex() + 1 == m_rDoc.GetNodes().Count() ) + { + return false; + } + + { + SwPaM temp(rPam, nullptr); + if (!temp.HasMark()) + { + temp.SetMark(); + } + if (SwTextNode *const pNode = temp.Start()->nNode.GetNode().GetTextNode()) + { // rPam may not have nContent set but IsFieldmarkOverlap requires it + pNode->MakeStartIndex(&temp.Start()->nContent); + } + if (SwTextNode *const pNode = temp.End()->nNode.GetNode().GetTextNode()) + { + pNode->MakeEndIndex(&temp.End()->nContent); + } + if (sw::mark::IsFieldmarkOverlap(temp)) + { // a bit of a problem: we want to completely remove the nodes + // but then how can the CH_TXT_ATR survive? + return false; + } + } + + // Move hard page brakes to the following Node. + bool bSavePageBreak = false, bSavePageDesc = false; + + /* #i9185# This would lead to a segmentation fault if not caught above. */ + sal_uLong nNextNd = rEnd.nNode.GetIndex() + 1; + SwTableNode *const pTableNd = m_rDoc.GetNodes()[ nNextNd ]->GetTableNode(); + + if( pTableNd && pNd->IsContentNode() ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + + { + const SfxPoolItem *pItem; + const SfxItemSet* pSet = static_cast<const SwContentNode*>(pNd)->GetpSwAttrSet(); + if( pSet && SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + bSavePageDesc = true; + } + + if( pSet && SfxItemState::SET == pSet->GetItemState( RES_BREAK, + false, &pItem ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + bSavePageBreak = true; + } + } + } + + bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + if( bDoesUndo ) + { + if( !rPam.HasMark() ) + rPam.SetMark(); + else if( rPam.GetPoint() == &rStt ) + rPam.Exchange(); + rPam.GetPoint()->nNode++; + + SwContentNode *pTmpNode = rPam.GetPoint()->nNode.GetNode().GetContentNode(); + rPam.GetPoint()->nContent.Assign( pTmpNode, 0 ); + bool bGoNext = (nullptr == pTmpNode); + pTmpNode = rPam.GetMark()->nNode.GetNode().GetContentNode(); + rPam.GetMark()->nContent.Assign( pTmpNode, 0 ); + + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + + SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); + { + SwPosition aTmpPos( *aDelPam.GetPoint() ); + if( bGoNext ) + { + pTmpNode = m_rDoc.GetNodes().GoNext( &aTmpPos.nNode ); + aTmpPos.nContent.Assign( pTmpNode, 0 ); + } + ::PaMCorrAbs( aDelPam, aTmpPos ); + } + + std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete( aDelPam, true )); + + *rPam.GetPoint() = *aDelPam.GetPoint(); + pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + rPam.DeleteMark(); + } + else + { + SwNodeRange aRg( rStt.nNode, rEnd.nNode ); + rPam.Normalize(false); + + // Try to move past the End + if( !rPam.Move( fnMoveForward, GoInNode ) ) + { + // Fair enough, at the Beginning then + rPam.Exchange(); + if( !rPam.Move( fnMoveBackward, GoInNode )) + { + SAL_WARN("sw.core", "DelFullPara: no more Nodes"); + return false; + } + } + // move bookmarks, redlines etc. + if (aRg.aStart == aRg.aEnd) // only first CorrAbs variant handles this + { + m_rDoc.CorrAbs( aRg.aStart, *rPam.GetPoint(), 0, true ); + } + else + { + SwDoc::CorrAbs( aRg.aStart, aRg.aEnd, *rPam.GetPoint(), true ); + } + + // What's with Flys? + { + // If there are FlyFrames left, delete these too + for( size_t n = 0; n < m_rDoc.GetSpzFrameFormats()->size(); ++n ) + { + SwFrameFormat* pFly = (*m_rDoc.GetSpzFrameFormats())[n]; + const SwFormatAnchor* pAnchor = &pFly->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + // note: here use <= not < like in + // IsDestroyFrameAnchoredAtChar() because of the increment + // of rPam in the bDoesUndo path above! + aRg.aStart <= pAPos->nNode && pAPos->nNode <= aRg.aEnd ) + { + m_rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFly ); + --n; + } + } + } + + rPam.DeleteMark(); + m_rDoc.GetNodes().Delete( aRg.aStart, nNodeDiff+1 ); + } + m_rDoc.getIDocumentState().SetModified(); + + return true; +} + +// #i100466# Add handling of new optional parameter <bForceJoinNext> +bool DocumentContentOperationsManager::DeleteAndJoin( SwPaM & rPam, + const bool bForceJoinNext ) +{ + if ( lcl_StrLenOverflow( rPam ) ) + return false; + + bool const ret = lcl_DoWithBreaks( *this, rPam, (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + ? &DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl + : &DocumentContentOperationsManager::DeleteAndJoinImpl, + bForceJoinNext ); + + return ret; +} + +// It seems that this is mostly used by SwDoc internals; the only +// way to call this from the outside seems to be the special case in +// SwDoc::CopyRange (but I have not managed to actually hit that case). +bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos, SwMoveFlags eMvFlags ) +{ + // nothing moved: return + const SwPosition *pStt = rPaM.Start(), *pEnd = rPaM.End(); + if( !rPaM.HasMark() || *pStt >= *pEnd || (*pStt <= rPos && rPos < *pEnd)) + return false; + + assert(!sw::mark::IsFieldmarkOverlap(rPaM)); // probably an invalid redline was created? + + // Save the paragraph anchored Flys, so that they can be moved. + SaveFlyArr aSaveFlyArr; + SaveFlyInRange( rPaM, rPos, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) ); + + // save redlines (if DOC_MOVEREDLINES is used) + SaveRedlines_t aSaveRedl; + if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + lcl_SaveRedlines( rPaM, aSaveRedl ); + + // #i17764# unfortunately, code below relies on undos being + // in a particular order, and presence of bookmarks + // will change this order. Hence, we delete bookmarks + // here without undo. + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + DelBookmarks( + pStt->nNode, + pEnd->nNode, + nullptr, + &pStt->nContent, + &pEnd->nContent); + } + + bool bUpdateFootnote = false; + SwFootnoteIdxs aTmpFntIdx; + + std::unique_ptr<SwUndoMove> pUndoMove; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + pUndoMove.reset(new SwUndoMove( rPaM, rPos )); + pUndoMove->SetMoveRedlines( eMvFlags == SwMoveFlags::REDLINES ); + } + else + { + bUpdateFootnote = lcl_SaveFootnote( pStt->nNode, pEnd->nNode, rPos.nNode, + m_rDoc.GetFootnoteIdxs(), aTmpFntIdx, + &pStt->nContent, &pEnd->nContent ); + } + + bool bSplit = false; + SwPaM aSavePam( rPos, rPos ); + + // Move the SPoint to the beginning of the range + if( rPaM.GetPoint() == pEnd ) + rPaM.Exchange(); + + // If there is a TextNode before and after the Move, create a JoinNext in the EditShell. + SwTextNode* pSrcNd = rPaM.GetPoint()->nNode.GetNode().GetTextNode(); + bool bCorrSavePam = pSrcNd && pStt->nNode != pEnd->nNode; + + // If one ore more TextNodes are moved, SwNodes::Move will do a SplitNode. + // However, this does not update the cursor. So we create a TextNode to keep + // updating the indices. After the Move the Node is optionally deleted. + SwTextNode * pTNd = rPos.nNode.GetNode().GetTextNode(); + if( pTNd && rPaM.GetPoint()->nNode != rPaM.GetMark()->nNode && + ( rPos.nContent.GetIndex() || ( pTNd->Len() && bCorrSavePam )) ) + { + bSplit = true; + const sal_Int32 nMkContent = rPaM.GetMark()->nContent.GetIndex(); + + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save( &m_rDoc, rPos.nNode.GetIndex(), rPos.nContent.GetIndex(), true ); + + SwTextNode * pOrigNode = pTNd; + assert(*aSavePam.GetPoint() == *aSavePam.GetMark() && + *aSavePam.GetPoint() == rPos); + assert(aSavePam.GetPoint()->nContent.GetIdxReg() == pOrigNode); + assert(aSavePam.GetPoint()->nNode == rPos.nNode.GetIndex()); + assert(rPos.nNode.GetIndex() == pOrigNode->GetIndex()); + + std::function<void (SwTextNode *, sw::mark::RestoreMode)> restoreFunc( + [&](SwTextNode *const, sw::mark::RestoreMode const eMode) + { + if (!pContentStore->Empty()) + { + pContentStore->Restore(&m_rDoc, pOrigNode->GetIndex()-1, 0, true, eMode); + } + }); + pTNd = pTNd->SplitContentNode(rPos, &restoreFunc)->GetTextNode(); + + //A new node was inserted before the orig pTNd and the content up to + //rPos moved into it. The old node is returned with the remainder + //of the content in it. + // + //aSavePam was created with rPos, it continues to point to the + //old node, but with the *original* content index into the node. + //Seeing as all the orignode content before that index has + //been removed, the new index into the original node should now be set + //to 0 and the content index of rPos should also be adapted to the + //truncated node + assert(*aSavePam.GetPoint() == *aSavePam.GetMark() && + *aSavePam.GetPoint() == rPos); + assert(aSavePam.GetPoint()->nContent.GetIdxReg() == pOrigNode); + assert(aSavePam.GetPoint()->nNode == rPos.nNode.GetIndex()); + assert(rPos.nNode.GetIndex() == pOrigNode->GetIndex()); + aSavePam.GetPoint()->nContent.Assign(pOrigNode, 0); + rPos = *aSavePam.GetMark() = *aSavePam.GetPoint(); + + // correct the PaM! + if( rPos.nNode == rPaM.GetMark()->nNode ) + { + rPaM.GetMark()->nNode = rPos.nNode.GetIndex()-1; + rPaM.GetMark()->nContent.Assign( pTNd, nMkContent ); + } + } + + // Put back the Pam by one "content"; so that it's always outside of + // the manipulated range. + // tdf#99692 don't Move() back if that would end up in another node + // because moving backward is not necessarily the inverse of forward then. + // (but do Move() back if we have split the node) + const bool bNullContent = !bSplit && aSavePam.GetPoint()->nContent == 0; + if( bNullContent ) + { + aSavePam.GetPoint()->nNode--; + aSavePam.GetPoint()->nContent.Assign(aSavePam.GetContentNode(), 0); + } + else + { + bool const success(aSavePam.Move(fnMoveBackward, GoInContent)); + assert(success); + (void) success; + } + + // Copy all Bookmarks that are within the Move range into an array, + // that saves the position as an offset. + std::vector< ::sw::mark::SaveBookmark> aSaveBkmks; + DelBookmarks( + pStt->nNode, + pEnd->nNode, + &aSaveBkmks, + &pStt->nContent, + &pEnd->nContent); + + // If there is no range anymore due to the above deletions (e.g. the + // footnotes got deleted), it's still a valid Move! + if( *rPaM.GetPoint() != *rPaM.GetMark() ) + { + // now do the actual move + m_rDoc.GetNodes().MoveRange( rPaM, rPos, m_rDoc.GetNodes() ); + + // after a MoveRange() the Mark is deleted + if ( rPaM.HasMark() ) // => no Move occurred! + { + return false; + } + } + else + rPaM.DeleteMark(); + + OSL_ENSURE( *aSavePam.GetMark() == rPos || + ( aSavePam.GetMark()->nNode.GetNode().GetContentNode() == nullptr ), + "PaM was not moved. Aren't there ContentNodes at the beginning/end?" ); + *aSavePam.GetMark() = rPos; + + rPaM.SetMark(); // create a Sel. around the new range + pTNd = aSavePam.GetNode().GetTextNode(); + assert(!m_rDoc.GetIDocumentUndoRedo().DoesUndo()); + bool bRemove = true; + // Do two Nodes have to be joined at the SavePam? + if (bSplit && pTNd) + { + if (pTNd->CanJoinNext()) + { + // Always join next, because <pTNd> has to stay as it is. + // A join previous from its next would more or less delete <pTNd> + pTNd->JoinNext(); + bRemove = false; + } + } + if (bNullContent) + { + aSavePam.GetPoint()->nNode++; + aSavePam.GetPoint()->nContent.Assign( aSavePam.GetContentNode(), 0 ); + } + else if (bRemove) // No move forward after joining with next paragraph + { + aSavePam.Move( fnMoveForward, GoInContent ); + } + + // Insert the Bookmarks back into the Document. + *rPaM.GetMark() = *aSavePam.Start(); + for(auto& rBkmk : aSaveBkmks) + rBkmk.SetInDoc( + &m_rDoc, + rPaM.GetMark()->nNode, + &rPaM.GetMark()->nContent); + *rPaM.GetPoint() = *aSavePam.End(); + + // Move the Flys to the new position. + // note: rPos is at the end here; can't really tell flys that used to be + // at the start of rPam from flys that used to be at the end of rPam + // unfortunately, so some of them are going to end up with wrong anchor... + RestFlyInRange( aSaveFlyArr, *rPaM.Start(), &(rPos.nNode) ); + + // restore redlines (if DOC_MOVEREDLINES is used) + if( !aSaveRedl.empty() ) + { + lcl_RestoreRedlines( &m_rDoc, *aSavePam.Start(), aSaveRedl ); + } + + if( bUpdateFootnote ) + { + if( !aTmpFntIdx.empty() ) + { + m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx ); + aTmpFntIdx.clear(); + } + + m_rDoc.GetFootnoteIdxs().UpdateAllFootnote(); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::MoveNodeRange( SwNodeRange& rRange, SwNodeIndex& rPos, + SwMoveFlags eMvFlags ) +{ + // Moves all Nodes to the new position. + // Bookmarks are moved too (currently without Undo support). + + // If footnotes are being moved to the special section, remove them now. + + // Or else delete the Frames for all footnotes that are being moved + // and have it rebuild after the Move (footnotes can change pages). + // Additionally we have to correct the FootnoteIdx array's sorting. + bool bUpdateFootnote = false; + SwFootnoteIdxs aTmpFntIdx; + + std::unique_ptr<SwUndoMove> pUndo; + if ((SwMoveFlags::CREATEUNDOOBJ & eMvFlags ) && m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoMove( &m_rDoc, rRange, rPos )); + } + else + { + bUpdateFootnote = lcl_SaveFootnote( rRange.aStart, rRange.aEnd, rPos, + m_rDoc.GetFootnoteIdxs(), aTmpFntIdx ); + } + + SaveRedlines_t aSaveRedl; + std::vector<SwRangeRedline*> aSavRedlInsPosArr; + if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + lcl_SaveRedlines( rRange, aSaveRedl ); + + // Find all RedLines that end at the InsPos. + // These have to be moved back to the "old" position after the Move. + SwRedlineTable::size_type nRedlPos = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos( rPos.GetNode(), RedlineType::Any ); + if( SwRedlineTable::npos != nRedlPos ) + { + const SwPosition *pRStt, *pREnd; + do { + SwRangeRedline* pTmp = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + pRStt = pTmp->Start(); + pREnd = pTmp->End(); + if( pREnd->nNode == rPos && pRStt->nNode < rPos ) + { + aSavRedlInsPosArr.push_back( pTmp ); + } + } while( pRStt->nNode < rPos && ++nRedlPos < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size()); + } + } + + // Copy all Bookmarks that are within the Move range into an array + // that stores all references to positions as an offset. + // The final mapping happens after the Move. + std::vector< ::sw::mark::SaveBookmark> aSaveBkmks; + DelBookmarks(rRange.aStart, rRange.aEnd, &aSaveBkmks); + + // Save the paragraph-bound Flys, so that they can be moved. + SaveFlyArr aSaveFlyArr; + if( !m_rDoc.GetSpzFrameFormats()->empty() ) + SaveFlyInRange( rRange, aSaveFlyArr ); + + // Set it to before the Position, so that it cannot be moved further. + SwNodeIndex aIdx( rPos, -1 ); + + std::unique_ptr<SwNodeIndex> pSaveInsPos; + if( pUndo ) + pSaveInsPos.reset(new SwNodeIndex( rRange.aStart, -1 )); + + // move the Nodes + bool bNoDelFrames = bool(SwMoveFlags::NO_DELFRMS & eMvFlags); + if( m_rDoc.GetNodes().MoveNodes( rRange, m_rDoc.GetNodes(), rPos, !bNoDelFrames ) ) + { + ++aIdx; // again back to old position + if( pSaveInsPos ) + ++(*pSaveInsPos); + } + else + { + aIdx = rRange.aStart; + pUndo.reset(); + } + + // move the Flys to the new position + if( !aSaveFlyArr.empty() ) + { + SwPosition const tmp(aIdx); + RestFlyInRange(aSaveFlyArr, tmp, nullptr); + } + + // Add the Bookmarks back to the Document + for(auto& rBkmk : aSaveBkmks) + rBkmk.SetInDoc(&m_rDoc, aIdx); + + if( !aSavRedlInsPosArr.empty() ) + { + SwNode* pNewNd = &aIdx.GetNode(); + for(SwRangeRedline* pTmp : aSavRedlInsPosArr) + { + if( m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().Contains( pTmp ) ) + { + SwPosition* pEnd = pTmp->End(); + pEnd->nNode = aIdx; + pEnd->nContent.Assign( pNewNd->GetContentNode(), 0 ); + } + } + } + + if( !aSaveRedl.empty() ) + lcl_RestoreRedlines( &m_rDoc, aIdx.GetIndex(), aSaveRedl ); + + if( pUndo ) + { + pUndo->SetDestRange( aIdx, rPos, *pSaveInsPos ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + pSaveInsPos.reset(); + + if( bUpdateFootnote ) + { + if( !aTmpFntIdx.empty() ) + { + m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx ); + aTmpFntIdx.clear(); + } + + m_rDoc.GetFootnoteIdxs().UpdateAllFootnote(); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::MoveAndJoin( SwPaM& rPaM, SwPosition& rPos ) +{ + SwNodeIndex aIdx( rPaM.Start()->nNode ); + bool bJoinText = aIdx.GetNode().IsTextNode(); + bool bOneNode = rPaM.GetPoint()->nNode == rPaM.GetMark()->nNode; + aIdx--; // in front of the move area! + + bool bRet = MoveRange( rPaM, rPos, SwMoveFlags::DEFAULT ); + if( bRet && !bOneNode ) + { + if( bJoinText ) + ++aIdx; + SwTextNode * pTextNd = aIdx.GetNode().GetTextNode(); + SwNodeIndex aNxtIdx( aIdx ); + if( pTextNd && pTextNd->CanJoinNext( &aNxtIdx ) ) + { + { // Block so SwIndex into node is deleted before Join + m_rDoc.CorrRel( aNxtIdx, SwPosition( aIdx, SwIndex(pTextNd, + pTextNd->GetText().getLength()) ), 0, true ); + } + pTextNd->JoinNext(); + } + } + return bRet; +} + +// Overwrite only uses the point of the PaM, the mark is ignored; characters +// are replaced from point until the end of the node; at the end of the node, +// characters are inserted. +bool DocumentContentOperationsManager::Overwrite( const SwPaM &rRg, const OUString &rStr ) +{ + assert(rStr.getLength()); + SwPosition& rPt = *const_cast<SwPosition*>(rRg.GetPoint()); + if( m_rDoc.GetAutoCorrExceptWord() ) // Add to AutoCorrect + { + if( 1 == rStr.getLength() ) + m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPt, rStr[ 0 ] ); + m_rDoc.DeleteAutoCorrExceptWord(); + } + + SwTextNode *pNode = rPt.nNode.GetNode().GetTextNode(); + if (!pNode || rStr.getLength() > pNode->GetSpaceLeft()) // worst case: no erase + { + return false; + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called + } + + const size_t nOldAttrCnt = pNode->GetpSwpHints() + ? pNode->GetpSwpHints()->Count() : 0; + SwDataChanged aTmp( rRg ); + SwIndex& rIdx = rPt.nContent; + sal_Int32 const nActualStart(rIdx.GetIndex()); + sal_Int32 nStart = 0; + + bool bOldExpFlg = pNode->IsIgnoreDontExpand(); + pNode->SetIgnoreDontExpand( true ); + + for( sal_Int32 nCnt = 0; nCnt < rStr.getLength(); ++nCnt ) + { + // start behind the characters (to fix the attributes!) + nStart = rIdx.GetIndex(); + if (nStart < pNode->GetText().getLength()) + { + lcl_SkipAttr( pNode, rIdx, nStart ); + } + sal_Unicode c = rStr[ nCnt ]; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + bool bMerged(false); + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + SwUndo *const pUndo = m_rDoc.GetUndoManager().GetLastUndo(); + SwUndoOverwrite *const pUndoOW( + dynamic_cast<SwUndoOverwrite *>(pUndo) ); + if (pUndoOW) + { + // if CanGrouping() returns true it's already merged + bMerged = pUndoOW->CanGrouping( &m_rDoc, rPt, c ); + } + } + if (!bMerged) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoOverwrite>(&m_rDoc, rPt, c) ); + } + } + else + { + // start behind the characters (to fix the attributes!) + if (nStart < pNode->GetText().getLength()) + ++rIdx; + pNode->InsertText( OUString(c), rIdx, SwInsertFlags::EMPTYEXPAND ); + if( nStart+1 < rIdx.GetIndex() ) + { + rIdx = nStart; + pNode->EraseText( rIdx, 1 ); + ++rIdx; + } + } + } + pNode->SetIgnoreDontExpand( bOldExpFlg ); + + const size_t nNewAttrCnt = pNode->GetpSwpHints() + ? pNode->GetpSwpHints()->Count() : 0; + if( nOldAttrCnt != nNewAttrCnt ) + { + SwUpdateAttr aHint(0,0,0); + pNode->ModifyBroadcast(nullptr, &aHint); + } + + if (!m_rDoc.GetIDocumentUndoRedo().DoesUndo() && + !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + SwPaM aPam(rPt.nNode, nActualStart, rPt.nNode, rPt.nContent.GetIndex()); + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, true, RedlineType::Any ); + } + else if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + // FIXME: this redline is WRONG: there is no DELETE, and the skipped + // characters are also included in aPam + SwPaM aPam(rPt.nNode, nActualStart, rPt.nNode, rPt.nContent.GetIndex()); + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::InsertString( const SwPaM &rRg, const OUString &rStr, + const SwInsertFlags nInsertMode ) +{ + // tdf#119019 accept tracked paragraph formatting to do not hide new insertions + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting( rRg ); + if (eOld != m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags()) + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + } + + // fetching DoesUndo is surprisingly expensive + bool bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + if (bDoesUndo) + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called! + + const SwPosition& rPos = *rRg.GetPoint(); + + if( m_rDoc.GetAutoCorrExceptWord() ) // add to auto correction + { + if( 1 == rStr.getLength() && m_rDoc.GetAutoCorrExceptWord()->IsDeleted() ) + { + m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPos, rStr[ 0 ] ); + } + m_rDoc.DeleteAutoCorrExceptWord(); + } + + SwTextNode *const pNode = rPos.nNode.GetNode().GetTextNode(); + if(!pNode) + return false; + + SwDataChanged aTmp( rRg ); + + if (!bDoesUndo || !m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + OUString const ins(pNode->InsertText(rStr, rPos.nContent, nInsertMode)); + if (bDoesUndo) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoInsert>(rPos.nNode, + rPos.nContent.GetIndex(), ins.getLength(), nInsertMode)); + } + } + else + { // if Undo and grouping is enabled, everything changes! + SwUndoInsert * pUndo = nullptr; + + // don't group the start if hints at the start should be expanded + if (!(nInsertMode & SwInsertFlags::FORCEHINTEXPAND)) + { + SwUndo *const pLastUndo = m_rDoc.GetUndoManager().GetLastUndo(); + SwUndoInsert *const pUndoInsert( + dynamic_cast<SwUndoInsert *>(pLastUndo) ); + if (pUndoInsert && pUndoInsert->CanGrouping(rPos)) + { + pUndo = pUndoInsert; + } + } + + CharClass const& rCC = GetAppCharClass(); + sal_Int32 nInsPos = rPos.nContent.GetIndex(); + + if (!pUndo) + { + pUndo = new SwUndoInsert( rPos.nNode, nInsPos, 0, nInsertMode, + !rCC.isLetterNumeric( rStr, 0 ) ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + } + + OUString const ins(pNode->InsertText(rStr, rPos.nContent, nInsertMode)); + + for (sal_Int32 i = 0; i < ins.getLength(); ++i) + { + nInsPos++; + // if CanGrouping() returns true, everything has already been done + if (!pUndo->CanGrouping(ins[i])) + { + pUndo = new SwUndoInsert(rPos.nNode, nInsPos, 1, nInsertMode, + !rCC.isLetterNumeric(ins, i)); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + } + } + } + + // To-Do - add 'SwExtraRedlineTable' also ? + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( rPos.nNode, aTmp.GetContent(), + rPos.nNode, rPos.nContent.GetIndex()); + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline( RedlineType::Insert, aPam ), true); + } + else + { + m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +void DocumentContentOperationsManager::TransliterateText( + const SwPaM& rPaM, + utl::TransliterationWrapper& rTrans ) +{ + std::unique_ptr<SwUndoTransliterate> pUndo; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoTransliterate( rPaM, rTrans )); + + const SwPosition* pStt = rPaM.Start(), + * pEnd = rPaM.End(); + sal_uLong nSttNd = pStt->nNode.GetIndex(), + nEndNd = pEnd->nNode.GetIndex(); + sal_Int32 nSttCnt = pStt->nContent.GetIndex(); + sal_Int32 nEndCnt = pEnd->nContent.GetIndex(); + + SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode(); + if( pStt == pEnd && pTNd ) // no selection? + { + // set current word as 'area of effect' + + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + pTNd->GetText(), nSttCnt, + g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ), + WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/, + true); + + if( aBndry.startPos < nSttCnt && nSttCnt < aBndry.endPos ) + { + nSttCnt = aBndry.startPos; + nEndCnt = aBndry.endPos; + } + } + + if( nSttNd != nEndNd ) // is more than one text node involved? + { + // iterate over all effected text nodes, the first and the last one + // may be incomplete because the selection starts and/or ends there + + SwNodeIndex aIdx( pStt->nNode ); + if( nSttCnt ) + { + ++aIdx; + if( pTNd ) + pTNd->TransliterateText( + rTrans, nSttCnt, pTNd->GetText().getLength(), pUndo.get()); + } + + for( ; aIdx.GetIndex() < nEndNd; ++aIdx ) + { + pTNd = aIdx.GetNode().GetTextNode(); + if (pTNd) + { + pTNd->TransliterateText( + rTrans, 0, pTNd->GetText().getLength(), pUndo.get()); + } + } + + if( nEndCnt && nullptr != ( pTNd = pEnd->nNode.GetNode().GetTextNode() )) + pTNd->TransliterateText( rTrans, 0, nEndCnt, pUndo.get() ); + } + else if( pTNd && nSttCnt < nEndCnt ) + pTNd->TransliterateText( rTrans, nSttCnt, nEndCnt, pUndo.get() ); + + if( pUndo && pUndo->HasData() ) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + m_rDoc.getIDocumentState().SetModified(); +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertGraphic( + const SwPaM &rRg, + const OUString& rGrfName, + const OUString& rFltName, + const Graphic* pGraphic, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet, + SwFrameFormat* pFrameFormat ) +{ + if( !pFrameFormat ) + pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC ); + SwGrfNode* pSwGrfNode = SwNodes::MakeGrfNode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + rGrfName, rFltName, pGraphic, + m_rDoc.GetDfltGrfFormatColl() ); + SwFlyFrameFormat* pSwFlyFrameFormat = InsNoTextNode( *rRg.GetPoint(), pSwGrfNode, + pFlyAttrSet, pGrfAttrSet, pFrameFormat ); + return pSwFlyFrameFormat; +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertGraphicObject( + const SwPaM &rRg, const GraphicObject& rGrfObj, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet ) +{ + SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC ); + SwGrfNode* pSwGrfNode = SwNodes::MakeGrfNode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + rGrfObj, m_rDoc.GetDfltGrfFormatColl() ); + SwFlyFrameFormat* pSwFlyFrameFormat = InsNoTextNode( *rRg.GetPoint(), pSwGrfNode, + pFlyAttrSet, pGrfAttrSet, pFrameFormat ); + return pSwFlyFrameFormat; +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertEmbObject( + const SwPaM &rRg, const svt::EmbeddedObjectRef& xObj, + SfxItemSet* pFlyAttrSet) +{ + sal_uInt16 nId = RES_POOLFRM_OLE; + if (xObj.is()) + { + SvGlobalName aClassName( xObj->getClassID() ); + if (SotExchange::IsMath(aClassName)) + { + nId = RES_POOLFRM_FORMEL; + } + } + + SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( nId ); + + return InsNoTextNode( *rRg.GetPoint(), m_rDoc.GetNodes().MakeOLENode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + xObj, + m_rDoc.GetDfltGrfFormatColl() ), + pFlyAttrSet, nullptr, + pFrameFormat ); +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertOLE(const SwPaM &rRg, const OUString& rObjName, + sal_Int64 nAspect, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet) +{ + SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_OLE ); + + return InsNoTextNode( *rRg.GetPoint(), + m_rDoc.GetNodes().MakeOLENode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + rObjName, + nAspect, + m_rDoc.GetDfltGrfFormatColl(), + nullptr ), + pFlyAttrSet, pGrfAttrSet, + pFrameFormat ); +} + +void DocumentContentOperationsManager::ReRead( SwPaM& rPam, const OUString& rGrfName, + const OUString& rFltName, const Graphic* pGraphic ) +{ + SwGrfNode *pGrfNd; + if( ( !rPam.HasMark() + || rPam.GetPoint()->nNode.GetIndex() == rPam.GetMark()->nNode.GetIndex() ) + && nullptr != ( pGrfNd = rPam.GetPoint()->nNode.GetNode().GetGrfNode() ) ) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoReRead>(rPam, *pGrfNd)); + } + + // Because we don't know if we can mirror the graphic, the mirror attribute is always reset + if( MirrorGraph::Dont != pGrfNd->GetSwAttrSet(). + GetMirrorGrf().GetValue() ) + pGrfNd->SetAttr( SwMirrorGrf() ); + + pGrfNd->ReRead( rGrfName, rFltName, pGraphic ); + m_rDoc.getIDocumentState().SetModified(); + } +} + +// Insert drawing object, which has to be already inserted in the DrawModel +SwDrawFrameFormat* DocumentContentOperationsManager::InsertDrawObj( + const SwPaM &rRg, + SdrObject& rDrawObj, + const SfxItemSet& rFlyAttrSet ) +{ + SwDrawFrameFormat* pFormat = m_rDoc.MakeDrawFrameFormat( OUString(), m_rDoc.GetDfltFrameFormat() ); + + const SwFormatAnchor* pAnchor = nullptr; + rFlyAttrSet.GetItemState( RES_ANCHOR, false, reinterpret_cast<const SfxPoolItem**>(&pAnchor) ); + pFormat->SetFormatAttr( rFlyAttrSet ); + + // Didn't set the Anchor yet? + // DrawObjecte must never end up in the Header/Footer! + RndStdIds eAnchorId = pAnchor != nullptr ? pAnchor->GetAnchorId() : pFormat->GetAnchor().GetAnchorId(); + const bool bIsAtContent = (RndStdIds::FLY_AT_PAGE != eAnchorId); + + const SwNodeIndex* pChkIdx = nullptr; + if ( pAnchor == nullptr ) + { + pChkIdx = &rRg.GetPoint()->nNode; + } + else if ( bIsAtContent ) + { + pChkIdx = + pAnchor->GetContentAnchor() ? &pAnchor->GetContentAnchor()->nNode : &rRg.GetPoint()->nNode; + } + + // allow drawing objects in header/footer, but control objects aren't allowed in header/footer. + if( pChkIdx != nullptr + && ::CheckControlLayer( &rDrawObj ) + && m_rDoc.IsInHeaderFooter( *pChkIdx ) ) + { + // apply at-page anchor format + eAnchorId = RndStdIds::FLY_AT_PAGE; + pFormat->SetFormatAttr( SwFormatAnchor( eAnchorId ) ); + } + else if( pAnchor == nullptr + || ( bIsAtContent + && pAnchor->GetContentAnchor() == nullptr ) ) + { + // apply anchor format + SwFormatAnchor aAnch( pAnchor != nullptr ? *pAnchor : pFormat->GetAnchor() ); + eAnchorId = aAnch.GetAnchorId(); + if ( eAnchorId == RndStdIds::FLY_AT_FLY ) + { + SwPosition aPos( *rRg.GetNode().FindFlyStartNode() ); + aAnch.SetAnchor( &aPos ); + } + else + { + aAnch.SetAnchor( rRg.GetPoint() ); + if ( eAnchorId == RndStdIds::FLY_AT_PAGE ) + { + eAnchorId = dynamic_cast<const SdrUnoObj*>( &rDrawObj) != nullptr ? RndStdIds::FLY_AS_CHAR : RndStdIds::FLY_AT_PARA; + aAnch.SetType( eAnchorId ); + } + } + pFormat->SetFormatAttr( aAnch ); + } + + // insert text attribute for as-character anchored drawing object + if ( eAnchorId == RndStdIds::FLY_AS_CHAR ) + { + bool bAnchorAtPageAsFallback = true; + const SwFormatAnchor& rDrawObjAnchorFormat = pFormat->GetAnchor(); + if ( rDrawObjAnchorFormat.GetContentAnchor() != nullptr ) + { + SwTextNode* pAnchorTextNode = + rDrawObjAnchorFormat.GetContentAnchor()->nNode.GetNode().GetTextNode(); + if ( pAnchorTextNode != nullptr ) + { + const sal_Int32 nStt = rDrawObjAnchorFormat.GetContentAnchor()->nContent.GetIndex(); + SwFormatFlyCnt aFormat( pFormat ); + pAnchorTextNode->InsertItem( aFormat, nStt, nStt ); + bAnchorAtPageAsFallback = false; + } + } + + if ( bAnchorAtPageAsFallback ) + { + OSL_ENSURE( false, "DocumentContentOperationsManager::InsertDrawObj(..) - missing content anchor for as-character anchored drawing object --> anchor at-page" ); + pFormat->SetFormatAttr( SwFormatAnchor( RndStdIds::FLY_AT_PAGE ) ); + } + } + + SwDrawContact* pContact = new SwDrawContact( pFormat, &rDrawObj ); + + // Create Frames if necessary + if( m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + // create layout representation + pFormat->MakeFrames(); + // #i42319# - follow-up of #i35635# + // move object to visible layer + // #i79391# + if ( pContact->GetAnchorFrame() ) + { + pContact->MoveObjToVisibleLayer( &rDrawObj ); + } + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsLayFormat>(pFormat, 0, 0) ); + } + + m_rDoc.getIDocumentState().SetModified(); + return pFormat; +} + +bool DocumentContentOperationsManager::SplitNode( const SwPosition &rPos, bool bChkTableStart ) +{ + SwContentNode *pNode = rPos.nNode.GetNode().GetContentNode(); + if(nullptr == pNode) + return false; + + { + // BUG 26675: Send DataChanged before deleting, so that we notice which objects are in scope. + // After that they can be before/after the position. + SwDataChanged aTmp( &m_rDoc, rPos ); + } + + SwUndoSplitNode* pUndo = nullptr; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + // insert the Undo object (currently only for TextNode) + if( pNode->IsTextNode() ) + { + pUndo = new SwUndoSplitNode( &m_rDoc, rPos, bChkTableStart ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + } + + // Update the rsid of the old and the new node unless + // the old node is split at the beginning or at the end + SwTextNode *pTextNode = rPos.nNode.GetNode().GetTextNode(); + const sal_Int32 nPos = rPos.nContent.GetIndex(); + if( pTextNode && nPos && nPos != pTextNode->Len() ) + { + m_rDoc.UpdateParRsid( pTextNode ); + } + + //JP 28.01.97: Special case for SplitNode at table start: + // If it is at the beginning of a Doc/Fly/Footer/... or right at after a table + // then insert a paragraph before it. + if( bChkTableStart && !rPos.nContent.GetIndex() && pNode->IsTextNode() ) + { + sal_uLong nPrevPos = rPos.nNode.GetIndex() - 1; + const SwTableNode* pTableNd; + const SwNode* pNd = m_rDoc.GetNodes()[ nPrevPos ]; + if( pNd->IsStartNode() && + SwTableBoxStartNode == static_cast<const SwStartNode*>(pNd)->GetStartNodeType() && + nullptr != ( pTableNd = m_rDoc.GetNodes()[ --nPrevPos ]->GetTableNode() ) && + ((( pNd = m_rDoc.GetNodes()[ --nPrevPos ])->IsStartNode() && + SwTableBoxStartNode != static_cast<const SwStartNode*>(pNd)->GetStartNodeType() ) + || ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsTableNode() ) + || pNd->IsContentNode() )) + { + if( pNd->IsContentNode() ) + { + //JP 30.04.99 Bug 65660: + // There are no page breaks outside of the normal body area, + // so this is not a valid condition to insert a paragraph. + if( nPrevPos < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + pNd = nullptr; + else + { + // Only if the table has page breaks! + const SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat(); + if( SfxItemState::SET != pFrameFormat->GetItemState(RES_PAGEDESC, false) && + SfxItemState::SET != pFrameFormat->GetItemState( RES_BREAK, false ) ) + pNd = nullptr; + } + } + + if( pNd ) + { + SwTextNode* pTextNd = m_rDoc.GetNodes().MakeTextNode( + SwNodeIndex( *pTableNd ), + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT )); + if( pTextNd ) + { + const_cast<SwPosition&>(rPos).nNode = pTableNd->GetIndex()-1; + const_cast<SwPosition&>(rPos).nContent.Assign( pTextNd, 0 ); + + // only add page breaks/styles to the body area + if( nPrevPos > m_rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + { + SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pFrameFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + { + pTextNd->SetAttr( *pItem ); + pFrameFormat->ResetFormatAttr( RES_PAGEDESC ); + } + if( SfxItemState::SET == pFrameFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + { + pTextNd->SetAttr( *pItem ); + pFrameFormat->ResetFormatAttr( RES_BREAK ); + } + } + + if( pUndo ) + pUndo->SetTableFlag(); + m_rDoc.getIDocumentState().SetModified(); + return true; + } + } + } + } + + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save( &m_rDoc, rPos.nNode.GetIndex(), rPos.nContent.GetIndex(), true ); + assert(pNode->IsTextNode()); + std::function<void (SwTextNode *, sw::mark::RestoreMode)> restoreFunc( + [&](SwTextNode *const, sw::mark::RestoreMode const eMode) + { + if (!pContentStore->Empty()) + { // move all bookmarks, TOXMarks, FlyAtCnt + pContentStore->Restore(&m_rDoc, rPos.nNode.GetIndex()-1, 0, true, eMode); + } + if (eMode & sw::mark::RestoreMode::NonFlys) + { + // To-Do - add 'SwExtraRedlineTable' also ? + if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || + (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && + !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())) + { + SwPaM aPam( rPos ); + aPam.SetMark(); + aPam.Move( fnMoveBackward ); + if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + { + m_rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline(RedlineType::Insert, aPam), true); + } + else + { + m_rDoc.getIDocumentRedlineAccess().SplitRedline(aPam); + } + } + } + }); + pNode->GetTextNode()->SplitContentNode(rPos, &restoreFunc); + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::AppendTextNode( SwPosition& rPos ) +{ + // create new node before EndOfContent + SwTextNode * pCurNode = rPos.nNode.GetNode().GetTextNode(); + if( !pCurNode ) + { + // so then one can be created! + SwNodeIndex aIdx( rPos.nNode, 1 ); + pCurNode = m_rDoc.GetNodes().MakeTextNode( aIdx, + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + } + else + pCurNode = pCurNode->AppendNode( rPos )->GetTextNode(); + + rPos.nNode++; + rPos.nContent.Assign( pCurNode, 0 ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsert>( rPos.nNode ) ); + } + + // To-Do - add 'SwExtraRedlineTable' also ? + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( rPos ); + aPam.SetMark(); + aPam.Move( fnMoveBackward ); + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + else + m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam ); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::ReplaceRange( SwPaM& rPam, const OUString& rStr, + const bool bRegExReplace ) +{ + // unfortunately replace works slightly differently from delete, + // so we cannot use lcl_DoWithBreaks here... + + std::vector<std::pair<sal_uLong, sal_Int32>> Breaks; + + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + aPam.Normalize(false); + if (aPam.GetPoint()->nNode != aPam.GetMark()->nNode) + { + aPam.Move(fnMoveBackward); + } + OSL_ENSURE((aPam.GetPoint()->nNode == aPam.GetMark()->nNode), "invalid pam?"); + + sw::CalcBreaks(Breaks, aPam); + + while (!Breaks.empty() // skip over prefix of dummy chars + && (aPam.GetMark()->nNode.GetIndex() == Breaks.begin()->first) + && (aPam.GetMark()->nContent.GetIndex() == Breaks.begin()->second)) + { + // skip! + ++aPam.GetMark()->nContent; // always in bounds if Breaks valid + Breaks.erase(Breaks.begin()); + } + *rPam.Start() = *aPam.GetMark(); // update start of original pam w/ prefix + + if (Breaks.empty()) + { + // park aPam somewhere so it does not point to node that is deleted + aPam.DeleteMark(); + *aPam.GetPoint() = SwPosition(m_rDoc.GetNodes().GetEndOfContent()); + return ReplaceRangeImpl(rPam, rStr, bRegExReplace); // original pam! + } + + // Deletion must be split into several parts if the text node + // contains a text attribute with end and with dummy character + // and the selection does not contain the text attribute completely, + // but overlaps its start (left), where the dummy character is. + + bool bRet( true ); + // iterate from end to start, to avoid invalidating the offsets! + auto iter( Breaks.rbegin() ); + sal_uLong nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + OSL_ENSURE(aPam.GetPoint() == aPam.End(), "wrong!"); + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + + // set end of temp pam to original end (undo Move backward above) + rEnd = *rPam.End(); + // after first deletion, rEnd will point into the original text node again! + + while (iter != Breaks.rend()) + { + rStart = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + bRet &= (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + ? DeleteAndJoinWithRedlineImpl(aPam) + : DeleteAndJoinImpl(aPam, false); + nOffset = iter->first - rStart.nNode.GetIndex(); // deleted fly nodes... + } + rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); + ++iter; + } + + rStart = *rPam.Start(); // set to original start + assert(rStart < rEnd && "replace part empty!"); + if (rStart < rEnd) // check if part is empty + { + bRet &= ReplaceRangeImpl(aPam, rStr, bRegExReplace); + } + + rPam = aPam; // update original pam (is this required?) + + return bRet; +} + +///Add a para for the char attribute exp... +bool DocumentContentOperationsManager::InsertPoolItem( + const SwPaM &rRg, + const SfxPoolItem &rHt, + const SetAttrMode nFlags, + SwRootFrame const*const pLayout, + const bool bExpandCharToPara, + SwTextAttr **ppNewTextAttr) +{ + if (utl::ConfigManager::IsFuzzing()) + return false; + + SwDataChanged aTmp( rRg ); + std::unique_ptr<SwUndoAttr> pUndoAttr; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + pUndoAttr.reset(new SwUndoAttr( rRg, rHt, nFlags )); + } + + SfxItemSet aSet( m_rDoc.GetAttrPool(), {{rHt.Which(), rHt.Which()}} ); + aSet.Put( rHt ); + const bool bRet = lcl_InsAttr(&m_rDoc, rRg, aSet, nFlags, pUndoAttr.get(), pLayout, bExpandCharToPara, ppNewTextAttr); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) ); + } + + if( bRet ) + { + m_rDoc.getIDocumentState().SetModified(); + } + return bRet; +} + +void DocumentContentOperationsManager::InsertItemSet ( const SwPaM &rRg, const SfxItemSet &rSet, + const SetAttrMode nFlags, SwRootFrame const*const pLayout) +{ + SwDataChanged aTmp( rRg ); + std::unique_ptr<SwUndoAttr> pUndoAttr; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + pUndoAttr.reset(new SwUndoAttr( rRg, rSet, nFlags )); + } + + bool bRet = lcl_InsAttr(&m_rDoc, rRg, rSet, nFlags, pUndoAttr.get(), pLayout, /*bExpandCharToPara*/false, /*ppNewTextAttr*/nullptr ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) ); + } + + if( bRet ) + m_rDoc.getIDocumentState().SetModified(); +} + +void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(const SwPosition & rPos ) +{ + const SwTextNode* pTNd = rPos.nNode.GetNode().GetTextNode(); + if ( pTNd ) + { + const OUString& rText = pTNd->GetText(); + sal_Int32 nIdx = 0; + while (nIdx < rText.getLength()) + { + sal_Unicode const cCh = rText[nIdx]; + if (('\t' != cCh) && (' ' != cCh)) + { + break; + } + ++nIdx; + } + + if ( nIdx > 0 ) + { + SwPaM aPam(rPos); + aPam.GetPoint()->nContent = 0; + aPam.SetMark(); + aPam.GetMark()->nContent = nIdx; + DeleteRange( aPam ); + } + } +} + +// Copy method from SwDoc - "copy Flys in Flys" +/// note: rRg/rInsPos *exclude* a partially selected start text node; +/// pCopiedPaM *includes* a partially selected start text node +void DocumentContentOperationsManager::CopyWithFlyInFly( + const SwNodeRange& rRg, + const SwNodeIndex& rInsPos, + const std::pair<const SwPaM&, const SwPosition&>* pCopiedPaM /*and real insert pos*/, + const bool bMakeNewFrames, + const bool bDelRedlines, + const bool bCopyFlyAtFly, + SwCopyFlags const flags) const +{ + assert(!pCopiedPaM || pCopiedPaM->first.End()->nNode == rRg.aEnd); + assert(!pCopiedPaM || pCopiedPaM->second.nNode <= rInsPos); + + SwDoc* pDest = rInsPos.GetNode().GetDoc(); + SwNodeIndex aSavePos( rInsPos ); + + if (rRg.aStart != rRg.aEnd) + { + bool bEndIsEqualEndPos = rInsPos == rRg.aEnd; + bool isRecreateEndNode(false); + --aSavePos; + SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 ); + + // insert behind the already copied start node + m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, false, true ); + aRedlRest.Restore(); + if (bMakeNewFrames) // tdf#130685 only after aRedlRest + { // recreate from previous node (could be merged now) + if (SwTextNode *const pNode = aSavePos.GetNode().GetTextNode()) + { + std::unordered_set<SwTextFrame*> frames; + SwTextNode *const pEndNode = rInsPos.GetNode().GetTextNode(); + if (pEndNode) + { + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.insert(pFrame); + } + } + } + sw::RecreateStartTextFrames(*pNode); + if (!frames.empty()) + { // tdf#132187 check if the end node needs new frames + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + auto const it = frames.find(pFrame); + if (it != frames.end()) + { + frames.erase(it); + } + } + } + if (!frames.empty()) // existing frame was deleted + { // all layouts because MakeFrames recreates all layouts + pEndNode->DelFrames(nullptr); + isRecreateEndNode = true; + } + } + } + } + bool const isAtStartOfSection(aSavePos.GetNode().IsStartNode()); + ++aSavePos; + if (bMakeNewFrames) + { + // it's possible that CheckParaRedlineMerge() deleted frames + // on rInsPos so have to include it, but it must not be included + // if it was the first node in the document so that MakeFrames() + // will find the existing (wasn't deleted) frame on it + SwNodeIndex const end(rInsPos, + (!isRecreateEndNode || isAtStartOfSection) + ? 0 : +1); + ::MakeFrames(pDest, aSavePos, end); + } + if (bEndIsEqualEndPos) + { + const_cast<SwNodeIndex&>(rRg.aEnd) = aSavePos; + } + } + +#if OSL_DEBUG_LEVEL > 0 + { + //JP 17.06.99: Bug 66973 - check count only if the selection is in + // the same section or there's no section, because sections that are + // not fully selected are not copied. + const SwSectionNode* pSSectNd = rRg.aStart.GetNode().FindSectionNode(); + SwNodeIndex aTmpI( rRg.aEnd, -1 ); + const SwSectionNode* pESectNd = aTmpI.GetNode().FindSectionNode(); + if( pSSectNd == pESectNd && + !rRg.aStart.GetNode().IsSectionNode() && + !aTmpI.GetNode().IsEndNode() ) + { + // If the range starts with a SwStartNode, it isn't copied + sal_uInt16 offset = (rRg.aStart.GetNode().GetNodeType() != SwNodeType::Start) ? 1 : 0; + OSL_ENSURE( rInsPos.GetIndex() - aSavePos.GetIndex() == + rRg.aEnd.GetIndex() - rRg.aStart.GetIndex() - 1 + offset, + "An insufficient number of nodes were copied!" ); + } + } +#endif + + { + ::sw::UndoGuard const undoGuard(pDest->GetIDocumentUndoRedo()); + CopyFlyInFlyImpl(rRg, pCopiedPaM ? &pCopiedPaM->first : nullptr, + // see comment below regarding use of pCopiedPaM->second + (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->nNode) + ? pCopiedPaM->second.nNode + : aSavePos, + bCopyFlyAtFly, + flags); + } + + SwNodeRange aCpyRange( aSavePos, rInsPos ); + + // Also copy all bookmarks + // guess this must be done before the DelDummyNodes below as that + // deletes nodes so would mess up the index arithmetic + if( m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() ) + { + SwPaM aRgTmp( rRg.aStart, rRg.aEnd ); + SwPaM aCpyPaM(aCpyRange.aStart, aCpyRange.aEnd); + if (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->nNode) + { + // there is 1 (partially selected, maybe) paragraph before + assert(SwNodeIndex(rRg.aStart, -1) == pCopiedPaM->first.Start()->nNode); + // only use the passed in target SwPosition if the source PaM point + // is on a different node; if it was the same node then the target + // position was likely moved along by the copy operation and now + // points to the end of the range! + *aCpyPaM.GetPoint() = pCopiedPaM->second; + } + + sw::CopyBookmarks(pCopiedPaM ? pCopiedPaM->first : aRgTmp, *aCpyPaM.Start()); + } + + if( bDelRedlines && ( RedlineFlags::DeleteRedlines & pDest->getIDocumentRedlineAccess().GetRedlineFlags() )) + lcl_DeleteRedlines( rRg, aCpyRange ); + + pDest->GetNodes().DelDummyNodes( aCpyRange ); +} + +// note: for the redline Show/Hide this must be in sync with +// SwRangeRedline::CopyToSection()/DelCopyOfSection()/MoveFromSection() +void DocumentContentOperationsManager::CopyFlyInFlyImpl( + const SwNodeRange& rRg, + SwPaM const*const pCopiedPaM, + const SwNodeIndex& rStartIdx, + const bool bCopyFlyAtFly, + SwCopyFlags const flags) const +{ + assert(!pCopiedPaM || pCopiedPaM->End()->nNode == rRg.aEnd); + + // First collect all Flys, sort them according to their ordering number, + // and then only copy them. This maintains the ordering numbers (which are only + // managed in the DrawModel). + SwDoc *const pDest = rStartIdx.GetNode().GetDoc(); + std::set< ZSortFly > aSet; + const size_t nArrLen = m_rDoc.GetSpzFrameFormats()->size(); + + SwTextBoxHelper::SavedLink aOldTextBoxes; + SwTextBoxHelper::saveLinks(*m_rDoc.GetSpzFrameFormats(), aOldTextBoxes); + + for ( size_t n = 0; n < nArrLen; ++n ) + { + SwFrameFormat* pFormat = (*m_rDoc.GetSpzFrameFormats())[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if ( !pAPos ) + continue; + bool bAdd = false; + sal_uLong nSkipAfter = pAPos->nNode.GetIndex(); + sal_uLong nStart = rRg.aStart.GetIndex(); + switch ( pAnchor->GetAnchorId() ) + { + case RndStdIds::FLY_AT_FLY: + if(bCopyFlyAtFly) + ++nSkipAfter; + else if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) + ++nStart; + break; + case RndStdIds::FLY_AT_PARA: + { + bAdd = IsSelectFrameAnchoredAtPara(*pAPos, + pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart), + pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd), + (flags & SwCopyFlags::IsMoveToFly) + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask); + } + break; + case RndStdIds::FLY_AT_CHAR: + { + bAdd = IsDestroyFrameAnchoredAtChar(*pAPos, + pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart), + pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd), + (flags & SwCopyFlags::IsMoveToFly) + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask); + } + break; + default: + continue; + } + if (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId()) + { + if (nStart > nSkipAfter) + continue; + if (pAPos->nNode > rRg.aEnd) + continue; + //frames at the last source node are not always copied: + //- if the node is empty and is the last node of the document or a table cell + // or a text frame then they have to be copied + //- if the content index in this node is > 0 then paragraph and frame bound objects are copied + //- to-character bound objects are copied if their index is <= nEndContentIndex + if (pAPos->nNode < rRg.aEnd) + bAdd = true; + if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move + { + if (!bAdd) + { + // technically old code checked nContent of AT_FLY which is pointless + bAdd = pCopiedPaM && 0 < pCopiedPaM->End()->nContent.GetIndex(); + } + } + } + if( bAdd ) + { + aSet.insert( ZSortFly( pFormat, pAnchor, nArrLen + aSet.size() )); + } + } + + // Store all copied (and also the newly created) frames in another array. + // They are stored as matching the originals, so that we will be later + // able to build the chains accordingly. + std::vector< SwFrameFormat* > aVecSwFrameFormat; + std::set< ZSortFly >::const_iterator it=aSet.begin(); + + while (it != aSet.end()) + { + // #i59964# + // correct determination of new anchor position + SwFormatAnchor aAnchor( *(*it).GetAnchor() ); + assert( aAnchor.GetContentAnchor() != nullptr ); + SwPosition newPos = *aAnchor.GetContentAnchor(); + // for at-paragraph and at-character anchored objects the new anchor + // position can *not* be determined by the difference of the current + // anchor position to the start of the copied range, because not + // complete selected sections in the copied range aren't copied - see + // method <SwNodes::CopyNodes(..)>. + // Thus, the new anchor position in the destination document is found + // by counting the text nodes. + if ((aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) || + (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) ) + { + // First, determine number of anchor text node in the copied range. + // Note: The anchor text node *have* to be inside the copied range. + sal_uLong nAnchorTextNdNumInRange( 0 ); + bool bAnchorTextNdFound( false ); + // start at the first node for which flys are copied + SwNodeIndex aIdx(pCopiedPaM ? pCopiedPaM->Start()->nNode : rRg.aStart); + while ( !bAnchorTextNdFound && aIdx <= rRg.aEnd ) + { + if ( aIdx.GetNode().IsTextNode() ) + { + ++nAnchorTextNdNumInRange; + bAnchorTextNdFound = aAnchor.GetContentAnchor()->nNode == aIdx; + } + + ++aIdx; + } + + if ( !bAnchorTextNdFound ) + { + // This case can *not* happen, but to be robust take the first + // text node in the destination document. + OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - anchor text node in copied range not found" ); + nAnchorTextNdNumInRange = 1; + } + // Second, search corresponding text node in destination document + // by counting forward from start insert position <rStartIdx> the + // determined number of text nodes. + aIdx = rStartIdx; + SwNodeIndex aAnchorNdIdx( rStartIdx ); + const SwNode& aEndOfContentNd = + aIdx.GetNode().GetNodes().GetEndOfContent(); + while ( nAnchorTextNdNumInRange > 0 && + &(aIdx.GetNode()) != &aEndOfContentNd ) + { + if ( aIdx.GetNode().IsTextNode() ) + { + --nAnchorTextNdNumInRange; + aAnchorNdIdx = aIdx; + } + + ++aIdx; + } + if ( !aAnchorNdIdx.GetNode().IsTextNode() ) + { + // This case can *not* happen, but to be robust take the first + // text node in the destination document. + OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - found anchor node index isn't a text node" ); + aAnchorNdIdx = rStartIdx; + while ( !aAnchorNdIdx.GetNode().IsTextNode() ) + { + ++aAnchorNdIdx; + } + } + // apply found anchor text node as new anchor position + newPos.nNode = aAnchorNdIdx; + } + else + { + long nOffset = newPos.nNode.GetIndex() - rRg.aStart.GetIndex(); + SwNodeIndex aIdx( rStartIdx, nOffset ); + newPos.nNode = aIdx; + } + // Set the character bound Flys back at the original character + if ((RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) && + newPos.nNode.GetNode().IsTextNode() ) + { + // only if pCopiedPaM: care about partially selected start node + sal_Int32 const nContent = pCopiedPaM && pCopiedPaM->Start()->nNode == aAnchor.GetContentAnchor()->nNode + ? newPos.nContent.GetIndex() - pCopiedPaM->Start()->nContent.GetIndex() + : newPos.nContent.GetIndex(); + newPos.nContent.Assign(newPos.nNode.GetNode().GetTextNode(), nContent); + } + else + { + newPos.nContent.Assign( nullptr, 0 ); + } + aAnchor.SetAnchor( &newPos ); + + // Check recursion: if copying content inside the same frame, then don't copy the format. + if( pDest == &m_rDoc ) + { + const SwFormatContent& rContent = (*it).GetFormat()->GetContent(); + const SwStartNode* pSNd; + if( rContent.GetContentIdx() && + nullptr != ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ) && + pSNd->GetIndex() < rStartIdx.GetIndex() && + rStartIdx.GetIndex() < pSNd->EndOfSectionIndex() ) + { + it = aSet.erase(it); + continue; + } + } + + // Ignore TextBoxes, they are already handled in + // sw::DocumentLayoutManager::CopyLayoutFormat(). + if (SwTextBoxHelper::isTextBox(it->GetFormat(), RES_FLYFRMFMT)) + { + it = aSet.erase(it); + continue; + } + + // Copy the format and set the new anchor + aVecSwFrameFormat.push_back( pDest->getIDocumentLayoutAccess().CopyLayoutFormat( *(*it).GetFormat(), + aAnchor, false, true ) ); + ++it; + } + + // Rebuild as much as possible of all chains that are available in the original, + OSL_ENSURE( aSet.size() == aVecSwFrameFormat.size(), "Missing new Flys" ); + if ( aSet.size() == aVecSwFrameFormat.size() ) + { + size_t n = 0; + for (const auto& rFlyN : aSet) + { + const SwFrameFormat *pFormatN = rFlyN.GetFormat(); + const SwFormatChain &rChain = pFormatN->GetChain(); + int nCnt = int(nullptr != rChain.GetPrev()); + nCnt += rChain.GetNext() ? 1: 0; + size_t k = 0; + for (const auto& rFlyK : aSet) + { + const SwFrameFormat *pFormatK = rFlyK.GetFormat(); + if ( rChain.GetPrev() == pFormatK ) + { + ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]), + static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]) ); + --nCnt; + } + else if ( rChain.GetNext() == pFormatK ) + { + ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]), + static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]) ); + --nCnt; + } + ++k; + } + ++n; + } + + // Re-create content property of draw formats, knowing how old shapes + // were paired with old fly formats (aOldTextBoxes) and that aSet is + // parallel with aVecSwFrameFormat. + SwTextBoxHelper::restoreLinks(aSet, aVecSwFrameFormat, aOldTextBoxes); + } +} + +/* + * Reset the text's hard formatting + */ +/** @params pArgs contains the document's ChrFormatTable + * Is need for selections at the beginning/end and with no SSelection. + */ +bool DocumentContentOperationsManager::lcl_RstTextAttr( const SwNodePtr& rpNd, void* pArgs ) +{ + ParaRstFormat* pPara = static_cast<ParaRstFormat*>(pArgs); + if (pPara->pLayout && pPara->pLayout->IsHideRedlines() + && rpNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return true; // skip hidden, since new items aren't applied + } + SwTextNode * pTextNode = rpNd->GetTextNode(); + if( pTextNode && pTextNode->GetpSwpHints() ) + { + SwIndex aSt( pTextNode, 0 ); + sal_Int32 nEnd = pTextNode->Len(); + + if( &pPara->pSttNd->nNode.GetNode() == pTextNode && + pPara->pSttNd->nContent.GetIndex() ) + aSt = pPara->pSttNd->nContent.GetIndex(); + + if( &pPara->pEndNd->nNode.GetNode() == rpNd ) + nEnd = pPara->pEndNd->nContent.GetIndex(); + + if( pPara->pHistory ) + { + // Save all attributes for the Undo. + SwRegHistory aRHst( *pTextNode, pPara->pHistory ); + pTextNode->GetpSwpHints()->Register( &aRHst ); + pTextNode->RstTextAttr( aSt, nEnd - aSt.GetIndex(), pPara->nWhich, + pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange ); + if( pTextNode->GetpSwpHints() ) + pTextNode->GetpSwpHints()->DeRegister(); + } + else + pTextNode->RstTextAttr( aSt, nEnd - aSt.GetIndex(), pPara->nWhich, + pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange ); + } + return true; +} + +DocumentContentOperationsManager::~DocumentContentOperationsManager() +{ +} +//Private methods + +bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl( SwPaM & rPam, const bool ) +{ + assert(m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()); + + RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + + if (*rPam.GetPoint() == *rPam.GetMark()) + { + return false; // do not add empty redlines + } + + std::vector<SwRangeRedline*> redlines; + { + auto pRedline(std::make_unique<SwRangeRedline>(RedlineType::Delete, rPam)); + if (pRedline->HasValidRange()) + { + redlines.push_back(pRedline.release()); + } + else // sigh ... why is such a selection even possible... + { // split it up so we get one SwUndoRedlineDelete per inserted RL + redlines = GetAllValidRanges(std::move(pRedline)); + } + } + + if (redlines.empty()) + { + return false; + } + + // tdf#54819 current redlining needs also modification of paragraph style and + // attributes added to the same grouped Undo + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + auto & rDMA(*m_rDoc.getIDocumentMarkAccess()); + std::vector<std::unique_ptr<SwUndo>> MarkUndos; + for (auto iter = rDMA.getAnnotationMarksBegin(); + iter != rDMA.getAnnotationMarksEnd(); ) + { + // tdf#111524 remove annotation marks that have their field + // characters deleted + SwPosition const& rEndPos((**iter).GetMarkEnd()); + if (*rPam.Start() < rEndPos && rEndPos <= *rPam.End()) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + MarkUndos.emplace_back(std::make_unique<SwUndoDeleteBookmark>(**iter)); + } + // iter is into annotation mark vector so must be dereferenced! + rDMA.deleteMark(&**iter); + // this invalidates iter, have to start over... + iter = rDMA.getAnnotationMarksBegin(); + } + else + { // marks are sorted by start + if (*rPam.End() < (**iter).GetMarkStart()) + { + break; + } + ++iter; + } + } + + // tdf#119019 accept tracked paragraph formatting to do not hide new deletions + if (*rPam.GetPoint() != *rPam.GetMark()) + m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting(rPam); + + std::vector<std::unique_ptr<SwUndoRedlineDelete>> undos; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + // this should no longer happen in calls from the UI but maybe via API + // (randomTest and testTdf54819 triggers it) + SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask, + "sw.core", "redlines will be moved in DeleteAndJoin"); + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( + RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + for (SwRangeRedline * pRedline : redlines) + { + assert(pRedline->HasValidRange()); + undos.emplace_back(std::make_unique<SwUndoRedlineDelete>( + *pRedline, SwUndoId::DELETE)); + } + const SwRewriter aRewriter = undos.front()->GetRewriter(); + // can only group a single undo action + if (MarkUndos.empty() && undos.size() == 1 + && m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + SwUndo * const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() ); + SwUndoRedlineDelete *const pUndoRedlineDel(dynamic_cast<SwUndoRedlineDelete*>(pLastUndo)); + bool const bMerged = pUndoRedlineDel + && pUndoRedlineDel->CanGrouping(*undos.front()); + if (!bMerged) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(undos.front())); + } + undos.clear(); // prevent unmatched EndUndo + } + else + { + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter); + for (auto& it : MarkUndos) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it)); + } + for (auto & it : undos) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it)); + } + } + } + + for (SwRangeRedline *const pRedline : redlines) + { + // note: 1. the pRedline can still be merged & deleted + // 2. the impl. can even DeleteAndJoin the range => no plain PaM + std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*pRedline->GetMark())); + pCursor->SetMark(); + *pCursor->GetPoint() = *pRedline->GetPoint(); + m_rDoc.getIDocumentRedlineAccess().AppendRedline(pRedline, true); + // sw_redlinehide: 2 reasons why this is needed: + // 1. it's the first redline in node => RedlineDelText was sent but ignored + // 2. redline spans multiple nodes => must merge text frames + sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor); + } + m_rDoc.getIDocumentState().SetModified(); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + if (!undos.empty()) + { + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + } + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + return true; +} + +bool DocumentContentOperationsManager::DeleteAndJoinImpl( SwPaM & rPam, + const bool bForceJoinNext ) +{ + bool bJoinText, bJoinPrev; + ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev ); + // #i100466# + if ( bForceJoinNext ) + { + bJoinPrev = false; + } + + { + bool const bSuccess( DeleteRangeImpl( rPam ) ); + if (!bSuccess) + return false; + } + + if( bJoinText ) + { + ::sw_JoinText( rPam, bJoinPrev ); + } + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } + + return true; +} + +bool DocumentContentOperationsManager::DeleteRangeImpl(SwPaM & rPam, const bool) +{ + // Move all cursors out of the deleted range, but first copy the + // passed PaM, because it could be a cursor that would be moved! + SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); + ::PaMCorrAbs( aDelPam, *aDelPam.GetPoint() ); + + bool const bSuccess( DeleteRangeImplImpl( aDelPam ) ); + if (bSuccess) + { // now copy position from temp copy to given PaM + *rPam.GetPoint() = *aDelPam.GetPoint(); + } + + return bSuccess; +} + +bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam) +{ + SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + + if (!rPam.HasMark() + || (*pStt == *pEnd && !IsFlySelectedByCursor(m_rDoc, *pStt, *pEnd))) + { + return false; + } + + if( m_rDoc.GetAutoCorrExceptWord() ) + { + // if necessary the saved Word for the exception + if( m_rDoc.GetAutoCorrExceptWord()->IsDeleted() || pStt->nNode != pEnd->nNode || + pStt->nContent.GetIndex() + 1 != pEnd->nContent.GetIndex() || + !m_rDoc.GetAutoCorrExceptWord()->CheckDelChar( *pStt )) + { m_rDoc.DeleteAutoCorrExceptWord(); } + } + + { + // Delete all empty TextHints at the Mark's position + SwTextNode* pTextNd = rPam.GetMark()->nNode.GetNode().GetTextNode(); + SwpHints* pHts; + if( pTextNd && nullptr != ( pHts = pTextNd->GetpSwpHints()) && pHts->Count() ) + { + const sal_Int32 nMkCntPos = rPam.GetMark()->nContent.GetIndex(); + for( size_t n = pHts->Count(); n; ) + { + const SwTextAttr* pAttr = pHts->Get( --n ); + if( nMkCntPos > pAttr->GetStart() ) + break; + + const sal_Int32 *pEndIdx; + if( nMkCntPos == pAttr->GetStart() && + nullptr != (pEndIdx = pAttr->End()) && + *pEndIdx == pAttr->GetStart() ) + pTextNd->DestroyAttr( pHts->Cut( n ) ); + } + } + } + + { + // Send DataChanged before deletion, so that we still know + // which objects are in the range. + // Afterwards they could be before/after the Position. + SwDataChanged aTmp( rPam ); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + bool bMerged(false); + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + SwUndo *const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() ); + SwUndoDelete *const pUndoDelete( + dynamic_cast<SwUndoDelete *>(pLastUndo) ); + if (pUndoDelete) + { + bMerged = pUndoDelete->CanGrouping( &m_rDoc, rPam ); + // if CanGrouping() returns true it's already merged + } + } + if (!bMerged) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoDelete>( rPam ) ); + } + + m_rDoc.getIDocumentState().SetModified(); + + return true; + } + + if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any ); + + // Delete and move all "Flys at the paragraph", which are within the Selection + DelFlyInRange(rPam.GetMark()->nNode, rPam.GetPoint()->nNode, + &rPam.GetMark()->nContent, &rPam.GetPoint()->nContent); + DelBookmarks( + pStt->nNode, + pEnd->nNode, + nullptr, + &pStt->nContent, + &pEnd->nContent); + + SwNodeIndex aSttIdx( pStt->nNode ); + SwContentNode * pCNd = aSttIdx.GetNode().GetContentNode(); + + do { // middle checked loop! + if( pCNd ) + { + SwTextNode * pStartTextNode( pCNd->GetTextNode() ); + if ( pStartTextNode ) + { + // now move the Content to the new Node + bool bOneNd = pStt->nNode == pEnd->nNode; + const sal_Int32 nLen = ( bOneNd ? pEnd->nContent.GetIndex() + : pCNd->Len() ) + - pStt->nContent.GetIndex(); + + // Don't call again, if already empty + if( nLen ) + { + pStartTextNode->EraseText( pStt->nContent, nLen ); + + if( !pStartTextNode->Len() ) + { + // METADATA: remove reference if empty (consider node deleted) + pStartTextNode->RemoveMetadataReference(); + } + } + + if( bOneNd ) // that's it + break; + + ++aSttIdx; + } + else + { + // So that there are no indices left registered when deleted, + // we remove a SwPaM from the Content here. + pStt->nContent.Assign( nullptr, 0 ); + } + } + + pCNd = pEnd->nNode.GetNode().GetContentNode(); + if( pCNd ) + { + SwTextNode * pEndTextNode( pCNd->GetTextNode() ); + if( pEndTextNode ) + { + // if already empty, don't call again + if( pEnd->nContent.GetIndex() ) + { + SwIndex aIdx( pCNd, 0 ); + pEndTextNode->EraseText( aIdx, pEnd->nContent.GetIndex() ); + + if( !pEndTextNode->Len() ) + { + // METADATA: remove reference if empty (consider node deleted) + pEndTextNode->RemoveMetadataReference(); + } + } + } + else + { + // So that there are no indices left registered when deleted, + // we remove a SwPaM from the Content here. + pEnd->nContent.Assign( nullptr, 0 ); + } + } + + // if the end is not a content node, delete it as well + sal_uInt32 nEnd = pEnd->nNode.GetIndex(); + if( pCNd == nullptr ) + nEnd++; + + if( aSttIdx != nEnd ) + { + // tdf#134436 delete section nodes like SwUndoDelete::SwUndoDelete + SwNode *pTmpNd; + while (pEnd == rPam.GetPoint() + && nEnd + 2 < m_rDoc.GetNodes().Count() + && (pTmpNd = m_rDoc.GetNodes()[nEnd + 1])->IsEndNode() + && pTmpNd->StartOfSectionNode()->IsSectionNode() + && aSttIdx <= pTmpNd->StartOfSectionNode()->GetIndex()) + { + SwNodeRange range(*pTmpNd->StartOfSectionNode(), *pTmpNd); + m_rDoc.GetNodes().SectionUp(&range); + --nEnd; // account for deleted start node + } + + // delete the Nodes from the NodesArary + m_rDoc.GetNodes().Delete( aSttIdx, nEnd - aSttIdx.GetIndex() ); + } + + // If the Node that contained the Cursor has been deleted, + // the Content has to be assigned to the current Content. + pStt->nContent.Assign( pStt->nNode.GetNode().GetContentNode(), + pStt->nContent.GetIndex() ); + + // If we deleted across Node boundaries we have to correct the PaM, + // because they are in different Nodes now. + // Also, the Selection is revoked. + *pEnd = *pStt; + rPam.DeleteMark(); + + } while( false ); + + m_rDoc.getIDocumentState().SetModified(); + + return true; +} + +// It's possible to call Replace with a PaM that spans 2 paragraphs: +// search with regex for "$", then replace _all_ +bool DocumentContentOperationsManager::ReplaceRangeImpl( SwPaM& rPam, const OUString& rStr, + const bool bRegExReplace ) +{ + if (!rPam.HasMark()) + return false; + + bool bJoinText, bJoinPrev; + ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev ); + + { + // Create a copy of the Cursor in order to move all Pams from + // the other views out of the deletion range. + // Except for itself! + SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); + ::PaMCorrAbs( aDelPam, *aDelPam.GetPoint() ); + + SwPosition *pStt = aDelPam.Start(), + *pEnd = aDelPam.End(); + bool bOneNode = pStt->nNode == pEnd->nNode; + + // Own Undo? + OUString sRepl( rStr ); + SwTextNode* pTextNd = pStt->nNode.GetNode().GetTextNode(); + sal_Int32 nStt = pStt->nContent.GetIndex(); + sal_Int32 nEnd; + + SwDataChanged aTmp( aDelPam ); + + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + // this should no longer happen in calls from the UI but maybe via API + SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask, + "sw.core", "redlines will be moved in ReplaceRange"); + + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + // If any Redline will change (split!) the node + const ::sw::mark::IMark* pBkmk = + m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam, + OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK, + ::sw::mark::InsertMode::New); + + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( + RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete ); + + *aDelPam.GetPoint() = pBkmk->GetMarkPos(); + if(pBkmk->IsExpanded()) + *aDelPam.GetMark() = pBkmk->GetOtherMarkPos(); + m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk); + pStt = aDelPam.Start(); + pTextNd = pStt->nNode.GetNode().GetTextNode(); + nStt = pStt->nContent.GetIndex(); + } + + if( !sRepl.isEmpty() ) + { + // Apply the first character's attributes to the ReplaceText + SfxItemSet aSet( m_rDoc.GetAttrPool(), + svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_WITHEND_END - 1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1>{} ); + pTextNd->GetParaAttr( aSet, nStt+1, nStt+1 ); + + aSet.ClearItem( RES_TXTATR_REFMARK ); + aSet.ClearItem( RES_TXTATR_TOXMARK ); + aSet.ClearItem( RES_TXTATR_CJK_RUBY ); + aSet.ClearItem( RES_TXTATR_INETFMT ); + aSet.ClearItem( RES_TXTATR_META ); + aSet.ClearItem( RES_TXTATR_METAFIELD ); + + if( aDelPam.GetPoint() != aDelPam.End() ) + aDelPam.Exchange(); + + // Remember the End + SwNodeIndex aPtNd( aDelPam.GetPoint()->nNode, -1 ); + const sal_Int32 nPtCnt = aDelPam.GetPoint()->nContent.GetIndex(); + + bool bFirst = true; + OUString sIns; + while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) ) + { + InsertString( aDelPam, sIns ); + if( bFirst ) + { + SwNodeIndex aMkNd( aDelPam.GetMark()->nNode, -1 ); + const sal_Int32 nMkCnt = aDelPam.GetMark()->nContent.GetIndex(); + + SplitNode( *aDelPam.GetPoint(), false ); + + ++aMkNd; + aDelPam.GetMark()->nNode = aMkNd; + aDelPam.GetMark()->nContent.Assign( + aMkNd.GetNode().GetContentNode(), nMkCnt ); + bFirst = false; + } + else + SplitNode( *aDelPam.GetPoint(), false ); + } + if( !sIns.isEmpty() ) + { + InsertString( aDelPam, sIns ); + } + + SwPaM aTmpRange( *aDelPam.GetPoint() ); + aTmpRange.SetMark(); + + ++aPtNd; + aDelPam.GetPoint()->nNode = aPtNd; + aDelPam.GetPoint()->nContent.Assign( aPtNd.GetNode().GetContentNode(), + nPtCnt); + *aTmpRange.GetMark() = *aDelPam.GetPoint(); + + m_rDoc.RstTextAttrs( aTmpRange ); + InsertItemSet( aTmpRange, aSet ); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoRedlineDelete>( aDelPam, SwUndoId::REPLACE )); + } + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDelPam ), true); + + *rPam.GetMark() = *aDelPam.GetMark(); + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + *aDelPam.GetPoint() = *rPam.GetPoint(); + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + // If any Redline will change (split!) the node + const ::sw::mark::IMark* pBkmk = + m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam, + OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK, + ::sw::mark::InsertMode::New); + + SwIndex& rIdx = aDelPam.GetPoint()->nContent; + rIdx.Assign( nullptr, 0 ); + aDelPam.GetMark()->nContent = rIdx; + rPam.GetPoint()->nNode = 0; + rPam.GetPoint()->nContent = rIdx; + *rPam.GetMark() = *rPam.GetPoint(); + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + + *rPam.GetPoint() = pBkmk->GetMarkPos(); + if(pBkmk->IsExpanded()) + *rPam.GetMark() = pBkmk->GetOtherMarkPos(); + m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk); + } + bJoinText = false; + } + else + { + assert((pStt->nNode == pEnd->nNode || + ( pStt->nNode.GetIndex() + 1 == pEnd->nNode.GetIndex() && + !pEnd->nContent.GetIndex() )) && + "invalid range: Point and Mark on different nodes" ); + + if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aDelPam, true, RedlineType::Any ); + + SwUndoReplace* pUndoRpl = nullptr; + bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + if (bDoesUndo) + { + pUndoRpl = new SwUndoReplace(aDelPam, sRepl, bRegExReplace); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoRpl)); + } + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + + if( aDelPam.GetPoint() != pStt ) + aDelPam.Exchange(); + + SwNodeIndex aPtNd( pStt->nNode, -1 ); + const sal_Int32 nPtCnt = pStt->nContent.GetIndex(); + + // Set the values again, if Frames or footnotes on the Text have been removed. + nStt = nPtCnt; + nEnd = bOneNode ? pEnd->nContent.GetIndex() + : pTextNd->GetText().getLength(); + + bool bFirst = true; + OUString sIns; + while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) ) + { + if (!bFirst || nStt == pTextNd->GetText().getLength()) + { + InsertString( aDelPam, sIns ); + } + else if( nStt < nEnd || !sIns.isEmpty() ) + { + pTextNd->ReplaceText( pStt->nContent, nEnd - nStt, sIns ); + } + SplitNode( *pStt, false); + bFirst = false; + } + + if( bFirst || !sIns.isEmpty() ) + { + if (!bFirst || nStt == pTextNd->GetText().getLength()) + { + InsertString( aDelPam, sIns ); + } + else if( nStt < nEnd || !sIns.isEmpty() ) + { + pTextNd->ReplaceText( pStt->nContent, nEnd - nStt, sIns ); + } + } + + *rPam.GetPoint() = *aDelPam.GetMark(); + ++aPtNd; + rPam.GetMark()->nNode = aPtNd; + rPam.GetMark()->nContent.Assign( aPtNd.GetNode().GetContentNode(), + nPtCnt ); + + if (bJoinText) + { + assert(rPam.GetPoint() == rPam.End()); + // move so that SetEnd remembers position after sw_JoinText + rPam.Move(fnMoveBackward); + } + else if (aDelPam.GetPoint() == pStt) // backward selection? + { + assert(*rPam.GetMark() <= *rPam.GetPoint()); + rPam.Exchange(); // swap so that rPam is backwards + } + + if( pUndoRpl ) + { + pUndoRpl->SetEnd(rPam); + } + } + } + + bool bRet(true); + if (bJoinText) + { + bRet = ::sw_JoinText(rPam, bJoinPrev); + } + + m_rDoc.getIDocumentState().SetModified(); + return bRet; +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsNoTextNode( const SwPosition& rPos, SwNoTextNode* pNode, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet, + SwFrameFormat* pFrameFormat) +{ + SwFlyFrameFormat *pFormat = nullptr; + if( pNode ) + { + pFormat = m_rDoc.MakeFlySection_( rPos, *pNode, RndStdIds::FLY_AT_PARA, + pFlyAttrSet, pFrameFormat ); + if( pGrfAttrSet ) + pNode->SetAttr( *pGrfAttrSet ); + } + return pFormat; +} + +#define NUMRULE_STATE \ + SfxItemState aNumRuleState = SfxItemState::UNKNOWN; \ + std::shared_ptr<SwNumRuleItem> aNumRuleItem; \ + SfxItemState aListIdState = SfxItemState::UNKNOWN; \ + std::shared_ptr<SfxStringItem> aListIdItem; \ + +#define PUSH_NUMRULE_STATE \ + lcl_PushNumruleState( aNumRuleState, aNumRuleItem, aListIdState, aListIdItem, pDestTextNd ); + +#define POP_NUMRULE_STATE \ + lcl_PopNumruleState( aNumRuleState, aNumRuleItem, aListIdState, aListIdItem, pDestTextNd, rPam ); + +static void lcl_PushNumruleState( + SfxItemState &aNumRuleState, std::shared_ptr<SwNumRuleItem>& aNumRuleItem, + SfxItemState &aListIdState, std::shared_ptr<SfxStringItem>& aListIdItem, + const SwTextNode *pDestTextNd ) +{ + // Safe numrule item at destination. + // #i86492# - Safe also <ListId> item of destination. + const SfxItemSet * pAttrSet = pDestTextNd->GetpSwAttrSet(); + if (pAttrSet != nullptr) + { + const SfxPoolItem * pItem = nullptr; + aNumRuleState = pAttrSet->GetItemState(RES_PARATR_NUMRULE, false, &pItem); + if (SfxItemState::SET == aNumRuleState) + { + aNumRuleItem.reset(static_cast<SwNumRuleItem*>(pItem->Clone())); + } + + aListIdState = pAttrSet->GetItemState(RES_PARATR_LIST_ID, false, &pItem); + if (SfxItemState::SET == aListIdState) + { + aListIdItem.reset(static_cast<SfxStringItem*>(pItem->Clone())); + } + } +} + +static void lcl_PopNumruleState( + SfxItemState aNumRuleState, const std::shared_ptr<SwNumRuleItem>& aNumRuleItem, + SfxItemState aListIdState, const std::shared_ptr<SfxStringItem>& aListIdItem, + SwTextNode *pDestTextNd, const SwPaM& rPam ) +{ + /* If only a part of one paragraph is copied + restore the numrule at the destination. */ + // #i86492# - restore also <ListId> item + if ( !lcl_MarksWholeNode(rPam) ) + { + if (SfxItemState::SET == aNumRuleState) + { + pDestTextNd->SetAttr(*aNumRuleItem); + } + else + { + pDestTextNd->ResetAttr(RES_PARATR_NUMRULE); + } + if (SfxItemState::SET == aListIdState) + { + pDestTextNd->SetAttr(*aListIdItem); + } + else + { + pDestTextNd->ResetAttr(RES_PARATR_LIST_ID); + } + } +} + +bool DocumentContentOperationsManager::CopyImpl(SwPaM& rPam, SwPosition& rPos, + SwCopyFlags const flags, + SwPaM *const pCopyRange) const +{ + std::vector<std::pair<sal_uLong, sal_Int32>> Breaks; + + sw::CalcBreaks(Breaks, rPam, true); + + if (Breaks.empty()) + { + return CopyImplImpl(rPam, rPos, flags, pCopyRange); + } + + SwPosition const & rSelectionEnd( *rPam.End() ); + + bool bRet(true); + bool bFirst(true); + // iterate from end to start, ... don't think it's necessary here? + auto iter( Breaks.rbegin() ); + sal_uLong nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node! + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + SwPaM copyRange(rPos, rPos); + + while (iter != Breaks.rend()) + { + rStart = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + // pass in copyRange member as rPos; should work ... + bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, ©Range); + nOffset = iter->first - rStart.nNode.GetIndex(); // fly nodes... + if (pCopyRange) + { + if (bFirst) + { + pCopyRange->SetMark(); + *pCopyRange->GetMark() = *copyRange.End(); + } + *pCopyRange->GetPoint() = *copyRange.Start(); + } + bFirst = false; + } + rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); + ++iter; + } + + rStart = *rPam.Start(); // set to original start + if (rStart < rEnd) // check if part is empty + { + bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, ©Range); + if (pCopyRange) + { + if (bFirst) + { + pCopyRange->SetMark(); + *pCopyRange->GetMark() = *copyRange.End(); + } + *pCopyRange->GetPoint() = *copyRange.Start(); + } + } + + return bRet; +} + +bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPos, + SwCopyFlags const flags, + SwPaM *const pCpyRange) const +{ + SwDoc* pDoc = rPos.nNode.GetNode().GetDoc(); + const bool bColumnSel = pDoc->IsClipBoard() && pDoc->IsColumnSelection(); + + SwPosition const*const pStt = rPam.Start(); + SwPosition *const pEnd = rPam.End(); + + // Catch when there's no copy to do. + if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel) || + //JP 29.6.2001: 88963 - don't copy if inspos is in region of start to end + //JP 15.11.2001: don't test inclusive the end, ever exclusive + ( pDoc == &m_rDoc && *pStt <= rPos && rPos < *pEnd )) + { + return false; + } + + const bool bEndEqualIns = pDoc == &m_rDoc && rPos == *pEnd; + + // If Undo is enabled, create the UndoCopy object + SwUndoCpyDoc* pUndo = nullptr; + // lcl_DeleteRedlines may delete the start or end node of the cursor when + // removing the redlines so use cursor that is corrected by PaMCorrAbs + std::shared_ptr<SwUnoCursor> const pCopyPam(pDoc->CreateUnoCursor(rPos)); + + SwTableNumFormatMerge aTNFM( m_rDoc, *pDoc ); + std::unique_ptr<std::vector<SwFrameFormat*>> pFlys; + std::vector<SwFrameFormat*> const* pFlysAtInsPos; + + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoCpyDoc(*pCopyPam); + pDoc->GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + pFlysAtInsPos = pUndo->GetFlysAnchoredAt(); + } + else + { + pFlys = sw::GetFlysAnchoredAt(*pDoc, rPos.nNode.GetIndex()); + pFlysAtInsPos = pFlys.get(); + } + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + + // Move the PaM one node back from the insert position, so that + // the position doesn't get moved + pCopyPam->SetMark(); + bool bCanMoveBack = pCopyPam->Move(fnMoveBackward, GoInContent); + // If the position was shifted from more than one node, an end node has been skipped + bool bAfterTable = false; + if ((rPos.nNode.GetIndex() - pCopyPam->GetPoint()->nNode.GetIndex()) > 1) + { + // First go back to the original place + pCopyPam->GetPoint()->nNode = rPos.nNode; + pCopyPam->GetPoint()->nContent = rPos.nContent; + + bCanMoveBack = false; + bAfterTable = true; + } + if( !bCanMoveBack ) + { + pCopyPam->GetPoint()->nNode--; + assert(pCopyPam->GetPoint()->nContent.GetIndex() == 0); + } + + SwNodeRange aRg( pStt->nNode, pEnd->nNode ); + SwNodeIndex aInsPos( rPos.nNode ); + const bool bOneNode = pStt->nNode == pEnd->nNode; + SwTextNode* pSttTextNd = pStt->nNode.GetNode().GetTextNode(); + SwTextNode* pEndTextNd = pEnd->nNode.GetNode().GetTextNode(); + SwTextNode* pDestTextNd = aInsPos.GetNode().GetTextNode(); + bool bCopyCollFormat = !pDoc->IsInsOnlyTextGlossary() && + ( (pDestTextNd && !pDestTextNd->GetText().getLength()) || + ( !bOneNode && !rPos.nContent.GetIndex() ) ); + bool bCopyBookmarks = true; + bool bCopyPageSource = false; + int nDeleteTextNodes = 0; + + // #i104585# copy outline num rule to clipboard (for ASCII filter) + if (pDoc->IsClipBoard() && m_rDoc.GetOutlineNumRule()) + { + pDoc->SetOutlineNumRule(*m_rDoc.GetOutlineNumRule()); + } + + // #i86492# + // Correct the search for a previous list: + // First search for non-outline numbering list. Then search for non-outline + // bullet list. + // Keep also the <ListId> value for possible propagation. + OUString aListIdToPropagate; + const SwNumRule* pNumRuleToPropagate = + pDoc->SearchNumRule( rPos, false, true, false, 0, aListIdToPropagate, nullptr, true ); + if ( !pNumRuleToPropagate ) + { + pNumRuleToPropagate = + pDoc->SearchNumRule( rPos, false, false, false, 0, aListIdToPropagate, nullptr, true ); + } + // #i86492# + // Do not propagate previous found list, if + // - destination is an empty paragraph which is not in a list and + // - source contains at least one paragraph which is not in a list + if ( pNumRuleToPropagate && + pDestTextNd && !pDestTextNd->GetText().getLength() && + !pDestTextNd->IsInList() && + !lcl_ContainsOnlyParagraphsInList( rPam ) ) + { + pNumRuleToPropagate = nullptr; + } + + // This do/while block is only there so that we can break out of it! + do { + if( pSttTextNd ) + { + ++nDeleteTextNodes; // must be joined in Undo + // Don't copy the beginning completely? + if( !bCopyCollFormat || bColumnSel || pStt->nContent.GetIndex() ) + { + SwIndex aDestIdx( rPos.nContent ); + bool bCopyOk = false; + if( !pDestTextNd ) + { + if( pStt->nContent.GetIndex() || bOneNode ) + pDestTextNd = pDoc->GetNodes().MakeTextNode( aInsPos, + pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD)); + else + { + pDestTextNd = pSttTextNd->MakeCopy(pDoc, aInsPos, true)->GetTextNode(); + bCopyOk = true; + } + aDestIdx.Assign( pDestTextNd, 0 ); + bCopyCollFormat = true; + } + else if( !bOneNode || bColumnSel ) + { + const sal_Int32 nContentEnd = pEnd->nContent.GetIndex(); + { + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + pDoc->getIDocumentContentOperations().SplitNode( rPos, false ); + } + + if (bCanMoveBack && rPos == *pCopyPam->GetPoint()) + { + // after the SplitNode, span the CpyPam correctly again + pCopyPam->Move( fnMoveBackward, GoInContent ); + pCopyPam->Move( fnMoveBackward, GoInContent ); + } + + pDestTextNd = pDoc->GetNodes()[ aInsPos.GetIndex()-1 ]->GetTextNode(); + aDestIdx.Assign( + pDestTextNd, pDestTextNd->GetText().getLength()); + + // Correct the area again + if( bEndEqualIns ) + { + bool bChg = pEnd != rPam.GetPoint(); + if( bChg ) + rPam.Exchange(); + rPam.Move( fnMoveBackward, GoInContent ); + if( bChg ) + rPam.Exchange(); + } + else if( rPos == *pEnd ) + { + // The end was also moved + pEnd->nNode--; + pEnd->nContent.Assign( pDestTextNd, nContentEnd ); + } + // tdf#63022 always reset pEndTextNd after SplitNode + aRg.aEnd = pEnd->nNode; + pEndTextNd = pEnd->nNode.GetNode().GetTextNode(); + } + + NUMRULE_STATE + if( bCopyCollFormat && bOneNode ) + { + PUSH_NUMRULE_STATE + } + + if( !bCopyOk ) + { + const sal_Int32 nCpyLen = ( bOneNode + ? pEnd->nContent.GetIndex() + : pSttTextNd->GetText().getLength()) + - pStt->nContent.GetIndex(); + pSttTextNd->CopyText( pDestTextNd, aDestIdx, + pStt->nContent, nCpyLen ); + if( bEndEqualIns ) + pEnd->nContent -= nCpyLen; + } + + aRg.aStart++; + + if( bOneNode ) + { + if (bCopyCollFormat) + { + pSttTextNd->CopyCollFormat( *pDestTextNd ); + POP_NUMRULE_STATE + } + + // copy at-char flys in rPam + SwNodeIndex temp(*pDestTextNd); // update to new (start) node for flys + // tdf#126626 prevent duplicate Undos + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + CopyFlyInFlyImpl(aRg, &rPam, temp, false); + + break; + } + } + } + else if( pDestTextNd ) + { + // Problems with insertion of table selections into "normal" text solved. + // We have to set the correct PaM for Undo, if this PaM starts in a textnode, + // the undo operation will try to merge this node after removing the table. + // If we didn't split a textnode, the PaM should start at the inserted table node + if( rPos.nContent.GetIndex() == pDestTextNd->Len() ) + { // Insertion at the last position of a textnode (empty or not) + ++aInsPos; // The table will be inserted behind the text node + } + else if( rPos.nContent.GetIndex() ) + { // Insertion in the middle of a text node, it has to be split + // (and joined from undo) + ++nDeleteTextNodes; + + const sal_Int32 nContentEnd = pEnd->nContent.GetIndex(); + { + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + pDoc->getIDocumentContentOperations().SplitNode( rPos, false ); + } + + if (bCanMoveBack && rPos == *pCopyPam->GetPoint()) + { + // after the SplitNode, span the CpyPam correctly again + pCopyPam->Move( fnMoveBackward, GoInContent ); + pCopyPam->Move( fnMoveBackward, GoInContent ); + } + + // Correct the area again + if( bEndEqualIns ) + aRg.aEnd--; + // The end would also be moved + else if( rPos == *pEnd ) + { + rPos.nNode-=2; + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), + nContentEnd ); + rPos.nNode++; + aRg.aEnd--; + } + } + else if( bCanMoveBack ) + { // Insertion at the first position of a text node. It will not be split, the table + // will be inserted before the text node. + // See below, before the SetInsertRange function of the undo object will be called, + // the CpyPam would be moved to the next content position. This has to be avoided + // We want to be moved to the table node itself thus we have to set bCanMoveBack + // and to manipulate pCopyPam. + bCanMoveBack = false; + pCopyPam->GetPoint()->nNode--; + } + } + + pDestTextNd = aInsPos.GetNode().GetTextNode(); + if (pEndTextNd) + { + SwIndex aDestIdx( rPos.nContent ); + if( !pDestTextNd ) + { + pDestTextNd = pDoc->GetNodes().MakeTextNode( aInsPos, + pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD)); + aDestIdx.Assign( pDestTextNd, 0 ); + aInsPos--; + + // if we have to insert an extra text node + // at the destination, this node will be our new destination + // (text) node, and thus we increment nDeleteTextNodes. This + // will ensure that this node will be deleted during Undo. + ++nDeleteTextNodes; // must be deleted + } + + const bool bEmptyDestNd = pDestTextNd->GetText().isEmpty(); + + NUMRULE_STATE + if( bCopyCollFormat && ( bOneNode || bEmptyDestNd )) + { + PUSH_NUMRULE_STATE + } + + pEndTextNd->CopyText( pDestTextNd, aDestIdx, SwIndex( pEndTextNd ), + pEnd->nContent.GetIndex() ); + + // Also copy all format templates + if( bCopyCollFormat && ( bOneNode || bEmptyDestNd )) + { + pEndTextNd->CopyCollFormat( *pDestTextNd ); + if ( bOneNode ) + { + POP_NUMRULE_STATE + } + } + } + + SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange ); + if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd) + { + if (pSttTextNd && bCopyCollFormat && pDestTextNd->HasSwAttrSet()) + { + aBrkSet.Put( *pDestTextNd->GetpSwAttrSet() ); + if( SfxItemState::SET == aBrkSet.GetItemState( RES_BREAK, false ) ) + pDestTextNd->ResetAttr( RES_BREAK ); + if( SfxItemState::SET == aBrkSet.GetItemState( RES_PAGEDESC, false ) ) + pDestTextNd->ResetAttr( RES_PAGEDESC ); + } + } + + { + SwPosition startPos(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1), + SwIndex(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1).GetNode().GetContentNode())); + if (bCanMoveBack) + { // pCopyPam is actually 1 before the copy range so move it fwd + SwPaM temp(*pCopyPam->GetPoint()); + temp.Move(fnMoveForward, GoInContent); + startPos = *temp.GetPoint(); + } + assert(startPos.nNode.GetNode().IsContentNode()); + std::pair<SwPaM const&, SwPosition const&> tmp(rPam, startPos); + if( aInsPos == pEnd->nNode ) + { + SwNodeIndex aSaveIdx( aInsPos, -1 ); + assert(pStt->nNode != pEnd->nNode); + pEnd->nContent = 0; // TODO why this? + CopyWithFlyInFly(aRg, aInsPos, &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags); + ++aSaveIdx; + pEnd->nNode = aSaveIdx; + pEnd->nContent.Assign( aSaveIdx.GetNode().GetTextNode(), 0 ); + } + else + CopyWithFlyInFly(aRg, aInsPos, &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags); + + bCopyBookmarks = false; + } + + // at-char anchors post SplitNode are on index 0 of 2nd node and will + // remain there - move them back to the start (end would also work?) + // ... also for at-para anchors; here start is preferable because + // it's consistent with SplitNode from SwUndoInserts::RedoImpl() + if (pFlysAtInsPos) + { + // init *again* - because CopyWithFlyInFly moved startPos + SwPosition startPos(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1), + SwIndex(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1).GetNode().GetContentNode())); + if (bCanMoveBack) + { // pCopyPam is actually 1 before the copy range so move it fwd + SwPaM temp(*pCopyPam->GetPoint()); + temp.Move(fnMoveForward, GoInContent); + startPos = *temp.GetPoint(); + } + assert(startPos.nNode.GetNode().IsContentNode()); + SwPosition startPosAtPara(startPos); + startPosAtPara.nContent.Assign(nullptr, 0); + + for (SwFrameFormat * pFly : *pFlysAtInsPos) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + SwFormatAnchor anchor(*pAnchor); + anchor.SetAnchor( &startPos ); + pFly->SetFormatAttr(anchor); + } + else if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + SwFormatAnchor anchor(*pAnchor); + anchor.SetAnchor( &startPosAtPara ); + pFly->SetFormatAttr(anchor); + } + } + } + + if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd) + { + // Put the breaks back into the first node + if( aBrkSet.Count() && nullptr != ( pDestTextNd = pDoc->GetNodes()[ + pCopyPam->GetPoint()->nNode.GetIndex()+1 ]->GetTextNode())) + { + pDestTextNd->SetAttr( aBrkSet ); + bCopyPageSource = true; + } + } + } while( false ); + + + // it is not possible to make this test when copy from the clipBoard to document + // in this case the PageNum not exist anymore + // tdf#39400 and tdf#97526 + // when copy from document to ClipBoard, and it is from the first page + // and not the source has the page break + if (pDoc->IsClipBoard() && (rPam.GetPageNum(pStt == rPam.GetPoint()) == 1) && !bCopyPageSource) + { + pDestTextNd->ResetAttr(RES_BREAK); // remove the page-break + pDestTextNd->ResetAttr(RES_PAGEDESC); + } + + + // Adjust position (in case it was moved / in another node) + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), + rPos.nContent.GetIndex() ); + + if( rPos.nNode != aInsPos ) + { + pCopyPam->GetMark()->nNode = aInsPos; + if (aInsPos < rPos.nNode) + { // tdf#134250 decremented in (pEndTextNd && !pDestTextNd) above + pCopyPam->GetContentNode(false)->MakeEndIndex(&pCopyPam->GetMark()->nContent); + } + else // incremented in (!pSttTextNd && pDestTextNd) above + { + pCopyPam->GetMark()->nContent.Assign(pCopyPam->GetContentNode(false), 0); + } + rPos = *pCopyPam->GetMark(); + } + else + *pCopyPam->GetMark() = rPos; + + if ( !bAfterTable ) + pCopyPam->Move( fnMoveForward, bCanMoveBack ? GoInContent : GoInNode ); + else + { + pCopyPam->GetPoint()->nNode++; + + // Reset the offset to 0 as it was before the insertion + pCopyPam->GetPoint()->nContent.Assign(pCopyPam->GetPoint()->nNode.GetNode().GetContentNode(), 0); + // If the next node is a start node, then step back: the start node + // has been copied and needs to be in the selection for the undo + if (pCopyPam->GetPoint()->nNode.GetNode().IsStartNode()) + pCopyPam->GetPoint()->nNode--; + + } + pCopyPam->Exchange(); + + // Also copy all bookmarks + if( bCopyBookmarks && m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() ) + { + sw::CopyBookmarks(rPam, *pCopyPam->Start()); + } + + if( RedlineFlags::DeleteRedlines & eOld ) + { + assert(*pCopyPam->GetPoint() == rPos); + // the Node rPos points to may be deleted so unregister ... + rPos.nContent.Assign(nullptr, 0); + lcl_DeleteRedlines(rPam, *pCopyPam); + rPos = *pCopyPam->GetPoint(); // ... and restore. + } + + // If Undo is enabled, store the inserted area + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + pUndo->SetInsertRange(*pCopyPam, true, nDeleteTextNodes); + } + + if( pCpyRange ) + { + pCpyRange->SetMark(); + *pCpyRange->GetPoint() = *pCopyPam->GetPoint(); + *pCpyRange->GetMark() = *pCopyPam->GetMark(); + } + + if ( pNumRuleToPropagate != nullptr ) + { + // #i86492# - use <SwDoc::SetNumRule(..)>, because it also handles the <ListId> + // Don't reset indent attributes, that would mean loss of direct + // formatting. + pDoc->SetNumRule( *pCopyPam, *pNumRuleToPropagate, false, nullptr, + aListIdToPropagate, true, /*bResetIndentAttrs=*/false ); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + pDoc->getIDocumentState().SetModified(); + + return true; +} + + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |