/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace com::sun::star; #ifdef DBG_UTIL #define ERROR_PREFIX "redline table corrupted: " namespace { // helper function for lcl_CheckRedline // 1. make sure that pPos->nContent points into pPos->nNode // 2. check that position is valid and doesn't point after text void lcl_CheckPosition( const SwPosition* pPos ) { assert(dynamic_cast(&pPos->nNode.GetNode()) == pPos->nContent.GetIdxReg()); SwTextNode* pTextNode = pPos->nNode.GetNode().GetTextNode(); if( pTextNode == nullptr ) { assert(pPos->nContent == 0); } else { assert(pPos->nContent >= 0 && pPos->nContent <= pTextNode->Len()); } } void lcl_CheckPam( const SwPaM* pPam ) { assert(pPam); lcl_CheckPosition( pPam->GetPoint() ); lcl_CheckPosition( pPam->GetMark() ); } // check validity of the redline table. Checks redline bounds, and make // sure the redlines are sorted and non-overlapping. void lcl_CheckRedline( const IDocumentRedlineAccess& redlineAccess ) { const SwRedlineTable& rTable = redlineAccess.GetRedlineTable(); // verify valid redline positions for(SwRangeRedline* i : rTable) lcl_CheckPam( i ); for(SwRangeRedline* j : rTable) { // check for empty redlines // note: these can destroy sorting in SwTextNode::Update() // if there's another one without mark on the same pos. OSL_ENSURE( ( *(j->GetPoint()) != *(j->GetMark()) ) || ( j->GetContentIdx() != nullptr ), ERROR_PREFIX "empty redline" ); } // verify proper redline sorting for( size_t n = 1; n < rTable.size(); ++n ) { const SwRangeRedline* pPrev = rTable[ n-1 ]; const SwRangeRedline* pCurrent = rTable[ n ]; // check redline sorting SAL_WARN_IF( *pPrev->Start() > *pCurrent->Start(), "sw", ERROR_PREFIX "not sorted correctly" ); // check for overlapping redlines SAL_WARN_IF( *pPrev->End() > *pCurrent->Start(), "sw", ERROR_PREFIX "overlapping redlines" ); } assert(std::is_sorted(rTable.begin(), rTable.end(), CompareSwRedlineTable())); } } #define CHECK_REDLINE( pDoc ) lcl_CheckRedline( pDoc ); #else #define CHECK_REDLINE( pDoc ) #endif namespace sw { static void UpdateFieldsForRedline(IDocumentFieldsAccess & rIDFA) { auto const pAuthType(static_cast(rIDFA.GetFieldType( SwFieldIds::TableOfAuthorities, OUString(), false))); if (pAuthType) // created on demand... { pAuthType->DelSequenceArray(); } rIDFA.GetFieldType(SwFieldIds::RefPageGet, OUString(), false)->UpdateFields(); rIDFA.GetSysFieldType(SwFieldIds::Chapter)->UpdateFields(); rIDFA.UpdateExpFields(nullptr, false); rIDFA.UpdateRefFields(); } void UpdateFramesForAddDeleteRedline(SwDoc & rDoc, SwPaM const& rPam) { if (rDoc.IsClipBoard()) { return; } // no need to call UpdateFootnoteNums for FTNNUM_PAGE: // the AppendFootnote/RemoveFootnote will do it by itself! rDoc.GetFootnoteIdxs().UpdateFootnote(rPam.Start()->nNode); SwPosition currentStart(*rPam.Start()); SwTextNode * pStartNode(rPam.Start()->nNode.GetNode().GetTextNode()); while (!pStartNode) { // note: branch only taken for redlines, not fieldmarks SwStartNode *const pTableOrSectionNode( currentStart.nNode.GetNode().IsTableNode() ? static_cast(currentStart.nNode.GetNode().GetTableNode()) : static_cast(currentStart.nNode.GetNode().GetSectionNode())); if ( !pTableOrSectionNode ) { SAL_WARN("sw.core", "UpdateFramesForAddDeleteRedline:: known pathology (or ChangesInRedline mode)"); return; } for (SwNodeOffset j = pTableOrSectionNode->GetIndex(); j <= pTableOrSectionNode->EndOfSectionIndex(); ++j) { pTableOrSectionNode->GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::Hidden); } for (SwRootFrame const*const pLayout : rDoc.GetAllLayouts()) { if (pLayout->HasMergedParas()) { if (pTableOrSectionNode->IsTableNode()) { static_cast(pTableOrSectionNode)->DelFrames(pLayout); } else { static_cast(pTableOrSectionNode)->DelFrames(pLayout); } } } currentStart.nNode = pTableOrSectionNode->EndOfSectionIndex() + 1; currentStart.nContent.Assign(currentStart.nNode.GetNode().GetContentNode(), 0); pStartNode = currentStart.nNode.GetNode().GetTextNode(); } if (currentStart < *rPam.End()) { SwTextNode * pNode(pStartNode); do { // deleted text node: remove it from "hidden" list // to update numbering in Show Changes mode SwPosition aPos( *pNode, pNode->Len() ); if ( pNode->GetNumRule() && aPos < *rPam.End() ) pNode->RemoveFromListRLHidden(); std::vector frames; SwIterator aIter(*pNode); for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) { if (pFrame->getRootFrame()->HasMergedParas()) { frames.push_back(pFrame); } // set anchored objects as deleted pFrame->SetDrawObjsAsDeleted(true); } if (frames.empty()) { auto const& layouts(rDoc.GetAllLayouts()); assert(std::none_of(layouts.begin(), layouts.end(), [](SwRootFrame const*const pLayout) { return pLayout->IsHideRedlines(); })); (void) layouts; break; } auto eMode(sw::FrameMode::Existing); SwTextNode * pLast(pNode); for (SwTextFrame * pFrame : frames) { SwTextNode & rFirstNode(pFrame->GetMergedPara() ? *pFrame->GetMergedPara()->pFirstNode : *pNode); assert(pNode == pStartNode ? rFirstNode.GetIndex() <= pNode->GetIndex() : &rFirstNode == pNode); // clear old one first to avoid DelFrames confusing updates & asserts... pFrame->SetMergedPara(nullptr); pFrame->SetMergedPara(sw::CheckParaRedlineMerge( *pFrame, rFirstNode, eMode)); eMode = sw::FrameMode::New; // Existing is not idempotent! // the first node of the new redline is not necessarily the first // node of the merged frame, there could be another redline nearby sw::AddRemoveFlysAnchoredToFrameStartingAtNode(*pFrame, *pNode, nullptr); // if redline is split across table and table cell is empty, there's no redline in the cell and so no merged para if (pFrame->GetMergedPara()) { pLast = const_cast(pFrame->GetMergedPara()->pLastNode); } } SwNodeIndex tmp(*pLast); // skip over hidden sections! pNode = static_cast(pLast->GetNodes().GoNextSection(&tmp, /*bSkipHidden=*/true, /*bSkipProtect=*/false)); } while (pNode && pNode->GetIndex() <= rPam.End()->nNode.GetIndex()); } // fields last - SwGetRefField::UpdateField requires up-to-date frames UpdateFieldsForRedline(rDoc.getIDocumentFieldsAccess()); // after footnotes // update SwPostItMgr / notes in the margin rDoc.GetDocShell()->Broadcast( SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::REMOVED) ); } void UpdateFramesForRemoveDeleteRedline(SwDoc & rDoc, SwPaM const& rPam) { // tdf#147006 fieldmark command may be empty => do not call AppendAllObjs() if (rDoc.IsClipBoard() || *rPam.GetPoint() == *rPam.GetMark()) { return; } bool isAppendObjsCalled(false); rDoc.GetFootnoteIdxs().UpdateFootnote(rPam.Start()->nNode); SwPosition currentStart(*rPam.Start()); SwTextNode * pStartNode(rPam.Start()->nNode.GetNode().GetTextNode()); while (!pStartNode) { // note: branch only taken for redlines, not fieldmarks SwStartNode const*const pTableOrSectionNode( currentStart.nNode.GetNode().IsTableNode() ? static_cast(currentStart.nNode.GetNode().GetTableNode()) : static_cast(currentStart.nNode.GetNode().GetSectionNode())); assert(pTableOrSectionNode); // known pathology for (SwNodeOffset j = pTableOrSectionNode->GetIndex(); j <= pTableOrSectionNode->EndOfSectionIndex(); ++j) { pTableOrSectionNode->GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::None); } if (rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->HasMergedParas()) { // note: this will also create frames for all currently hidden flys // because it calls AppendAllObjs SwNodeIndex const end(*pTableOrSectionNode->EndOfSectionNode()); ::MakeFrames(&rDoc, currentStart.nNode, end); isAppendObjsCalled = true; } currentStart.nNode = pTableOrSectionNode->EndOfSectionIndex() + 1; currentStart.nContent.Assign(currentStart.nNode.GetNode().GetContentNode(), 0); pStartNode = currentStart.nNode.GetNode().GetTextNode(); } if (currentStart < *rPam.End()) { SwTextNode * pNode(pStartNode); do { // undeleted text node: add it to the "hidden" list // to update numbering in Show Changes mode SwPosition aPos( *pNode, pNode->Len() ); if ( pNode->GetNumRule() && aPos < *rPam.End() ) pNode->AddToListRLHidden(); std::vector frames; SwIterator aIter(*pNode); for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) { if (pFrame->getRootFrame()->HasMergedParas()) { frames.push_back(pFrame); } // set anchored objects as not deleted pFrame->SetDrawObjsAsDeleted(false); } if (frames.empty()) { // in SwUndoSaveSection::SaveSection(), DelFrames() preceded this call if (!pNode->FindTableBoxStartNode() && !pNode->FindFlyStartNode()) { auto const& layouts(rDoc.GetAllLayouts()); assert(std::none_of(layouts.begin(), layouts.end(), [](SwRootFrame const*const pLayout) { return pLayout->IsHideRedlines(); })); (void) layouts; } isAppendObjsCalled = true; // skip that! break; } // no nodes can be unmerged by this - skip MakeFrames() etc. if (rPam.GetPoint()->nNode == rPam.GetMark()->nNode) { break; // continue with AppendAllObjs() } // first, call CheckParaRedlineMerge on the first paragraph, // to init flag on new merge range (if any) + 1st node post the merge auto eMode(sw::FrameMode::Existing); SwTextNode * pLast(pNode); for (SwTextFrame * pFrame : frames) { if (auto const pMergedPara = pFrame->GetMergedPara()) { pLast = const_cast(pMergedPara->pLastNode); assert(pNode == pStartNode ? pMergedPara->pFirstNode->GetIndex() <= pNode->GetIndex() : pMergedPara->pFirstNode == pNode); // clear old one first to avoid DelFrames confusing updates & asserts... SwTextNode & rFirstNode(*pMergedPara->pFirstNode); pFrame->SetMergedPara(nullptr); pFrame->SetMergedPara(sw::CheckParaRedlineMerge( *pFrame, rFirstNode, eMode)); eMode = sw::FrameMode::New; // Existing is not idempotent! // update pNode so MakeFrames starts on 2nd node pNode = &rFirstNode; } } if (pLast != pNode) { // now start node until end of merge + 1 has proper flags; MakeFrames // should pick up from the next node in need of frames by checking flags SwNodeIndex const start(*pNode, +1); SwNodeIndex const end(*pLast, +1); // end is exclusive // note: this will also create frames for all currently hidden flys // both on first and non-first nodes because it calls AppendAllObjs ::MakeFrames(&rDoc, start, end); isAppendObjsCalled = true; // re-use this to move flys that are now on the wrong frame, with end // of redline as "second" node; the nodes between start and end should // be complete with MakeFrames already sw::MoveMergedFlysAndFootnotes(frames, *pNode, *pLast, false); } SwNodeIndex tmp(*pLast); // skip over hidden sections! pNode = static_cast(pLast->GetNodes().GoNextSection(&tmp, /*bSkipHidden=*/true, /*bSkipProtect=*/false)); } while (pNode && pNode->GetIndex() <= rPam.End()->nNode.GetIndex()); } if (!isAppendObjsCalled) { // recreate flys in the one node the hard way... for (auto const& pLayout : rDoc.GetAllLayouts()) { if (pLayout->HasMergedParas()) { AppendAllObjs(rDoc.GetSpzFrameFormats(), pLayout); break; } } } // fields last - SwGetRefField::UpdateField requires up-to-date frames UpdateFieldsForRedline(rDoc.getIDocumentFieldsAccess()); // after footnotes // update SwPostItMgr / notes in the margin rDoc.GetDocShell()->Broadcast( SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::INSERTED) ); } } // namespace sw namespace { bool IsPrevPos( const SwPosition & rPos1, const SwPosition & rPos2 ) { const SwContentNode* pCNd; if( 0 != rPos2.nContent.GetIndex() ) return false; if( rPos2.nNode.GetIndex() - 1 != rPos1.nNode.GetIndex() ) return false; pCNd = rPos1.nNode.GetNode().GetContentNode(); return pCNd && rPos1.nContent.GetIndex() == pCNd->Len(); } // copy style or return with SwRedlineExtra_FormatColl with reject data of the upcoming copy SwRedlineExtraData_FormatColl* lcl_CopyStyle( const SwPosition & rFrom, const SwPosition & rTo, bool bCopy = true ) { SwTextNode* pToNode = rTo.nNode.GetNode().GetTextNode(); SwTextNode* pFromNode = rFrom.nNode.GetNode().GetTextNode(); if (pToNode != nullptr && pFromNode != nullptr && pToNode != pFromNode) { const SwPaM aPam(*pToNode); SwDoc& rDoc = aPam.GetDoc(); // using Undo, copy paragraph style SwTextFormatColl* pFromColl = pFromNode->GetTextColl(); SwTextFormatColl* pToColl = pToNode->GetTextColl(); if (bCopy && pFromColl != pToColl) rDoc.SetTextFormatColl(aPam, pFromColl); // using Undo, remove direct paragraph formatting of the "To" paragraph, // and apply here direct paragraph formatting of the "From" paragraph SfxItemSetFixed< RES_PARATR_BEGIN, RES_PARATR_END - 3, // skip RSID and GRABBAG RES_PARATR_LIST_BEGIN, RES_UL_SPACE, // skip PAGEDESC and BREAK RES_CNTNT, RES_FRMATR_END - 1> aTmp(rDoc.GetAttrPool()); SfxItemSet aTmp2(aTmp); pToNode->GetParaAttr(aTmp, 0, 0); pFromNode->GetParaAttr(aTmp2, 0, 0); bool bSameSet = aTmp == aTmp2; if (!bSameSet) { for( sal_uInt16 nItem = 0; nItem < aTmp.TotalCount(); ++nItem) { sal_uInt16 nWhich = aTmp.GetWhichByPos(nItem); if( SfxItemState::SET == aTmp.GetItemState( nWhich, false ) && SfxItemState::SET != aTmp2.GetItemState( nWhich, false ) ) aTmp2.Put( aTmp.GetPool()->GetDefaultItem(nWhich), nWhich ); } } if (bCopy && !bSameSet) rDoc.getIDocumentContentOperations().InsertItemSet(aPam, aTmp2); else if (!bCopy && (!bSameSet || pFromColl != pToColl)) return new SwRedlineExtraData_FormatColl( pFromColl->GetName(), USHRT_MAX, &aTmp2 ); } return nullptr; } // delete the empty tracked table row (i.e. if it's last tracked deletion was accepted) void lcl_DeleteTrackedTableRow ( const SwPosition* pPos ) { const SwTableBox* pBox = pPos->nNode.GetNode().GetTableBox(); if ( !pBox ) return; const SwTableLine* pLine = pBox->GetUpper(); const SvxPrintItem *pHasTextChangesOnlyProp = pLine->GetFrameFormat()->GetAttrSet().GetItem(RES_PRINT); // empty table row with property "HasTextChangesOnly" = false if ( pHasTextChangesOnlyProp && !pHasTextChangesOnlyProp->GetValue() ) { if ( pLine->IsEmpty() ) { SwCursor aCursor( *pPos, nullptr ); pPos->GetDoc().DeleteRow( aCursor ); } else { // update property "HasTextChangesOnly" SwRedlineTable::size_type nPos = 0; (void)pLine->UpdateTextChangesOnly(nPos); } } } // at rejection of a deletion in a table, remove the tracking of the table row // (also at accepting the last redline insertion of a tracked table row insertion) void lcl_RemoveTrackingOfTableRow( const SwPosition* pPos, bool bRejectDeletion ) { const SwTableBox* pBox = pPos->nNode.GetNode().GetTableBox(); if ( !pBox ) return; const SwTableLine* pLine = pBox->GetUpper(); const SvxPrintItem *pHasTextChangesOnlyProp = pLine->GetFrameFormat()->GetAttrSet().GetItem(RES_PRINT); // table row property "HasTextChangesOnly" is set and its value is false if ( pHasTextChangesOnlyProp && !pHasTextChangesOnlyProp->GetValue() ) { bool bNoMoreInsertion = false; if ( !bRejectDeletion ) { SwRedlineTable::size_type nPos = 0; SwRedlineTable::size_type nInsert = pLine->UpdateTextChangesOnly(nPos, /*bUpdateProperty=*/false); if ( SwRedlineTable::npos == nInsert ) bNoMoreInsertion = true; } if ( bRejectDeletion || bNoMoreInsertion ) { SvxPrintItem aUnsetTracking(RES_PRINT, true); SwCursor aCursor( *pPos, nullptr ); pPos->GetDoc().SetRowNotTracked( aCursor, aUnsetTracking ); } } } bool lcl_AcceptRedline( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos, bool bCallDelete, const SwPosition* pSttRng = nullptr, const SwPosition* pEndRng = nullptr ) { bool bRet = true; SwRangeRedline* pRedl = rArr[ rPos ]; SwPosition *pRStt = nullptr, *pREnd = nullptr; SwComparePosition eCmp = SwComparePosition::Outside; if( pSttRng && pEndRng ) { pRStt = pRedl->Start(); pREnd = pRedl->End(); eCmp = ComparePosition( *pSttRng, *pEndRng, *pRStt, *pREnd ); } pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); switch( pRedl->GetType() ) { case RedlineType::Insert: case RedlineType::Format: { bool bCheck = false, bReplace = false; switch( eCmp ) { case SwComparePosition::Inside: if( *pSttRng == *pRStt ) pRedl->SetStart( *pEndRng, pRStt ); else { if( *pEndRng != *pREnd ) { // split up SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); pNew->SetStart( *pEndRng ); rArr.Insert( pNew ); ++rPos; } pRedl->SetEnd( *pSttRng, pREnd ); bCheck = true; } break; case SwComparePosition::OverlapBefore: pRedl->SetStart( *pEndRng, pRStt ); bReplace = true; break; case SwComparePosition::OverlapBehind: pRedl->SetEnd( *pSttRng, pREnd ); bCheck = true; break; case SwComparePosition::Outside: case SwComparePosition::Equal: { bool bInsert = RedlineType::Insert == pRedl->GetType(); SwPosition aPos(pRedl->Start()->nNode); rArr.DeleteAndDestroy( rPos-- ); // remove tracking of the table row, if needed if ( bInsert ) lcl_RemoveTrackingOfTableRow( &aPos, /*bRejectDelete=*/false ); } break; default: bRet = false; } if( bReplace || ( bCheck && !pRedl->HasValidRange() )) { // re-insert rArr.Remove( pRedl ); rArr.Insert( pRedl ); } } break; case RedlineType::Delete: { SwDoc& rDoc = pRedl->GetDoc(); const SwPosition *pDelStt = nullptr, *pDelEnd = nullptr; bool bDelRedl = false; switch( eCmp ) { case SwComparePosition::Inside: if( bCallDelete ) { pDelStt = pSttRng; pDelEnd = pEndRng; } break; case SwComparePosition::OverlapBefore: if( bCallDelete ) { pDelStt = pRStt; pDelEnd = pEndRng; } break; case SwComparePosition::OverlapBehind: if( bCallDelete ) { pDelStt = pREnd; pDelEnd = pSttRng; } break; case SwComparePosition::Outside: case SwComparePosition::Equal: { rArr.Remove( rPos-- ); bDelRedl = true; if( bCallDelete ) { pDelStt = pRedl->Start(); pDelEnd = pRedl->End(); } } break; default: bRet = false; } if( pDelStt && pDelEnd ) { SwPaM aPam( *pDelStt, *pDelEnd ); SwContentNode* pCSttNd = pDelStt->nNode.GetNode().GetContentNode(); SwContentNode* pCEndNd = pDelEnd->nNode.GetNode().GetContentNode(); pRStt = pRedl->Start(); pREnd = pRedl->End(); // keep style of the empty paragraph after deletion of wholly paragraphs if( pCSttNd && pCEndNd && pRStt && pREnd && pRStt->nContent == 0 ) lcl_CopyStyle(*pREnd, *pRStt); if( bDelRedl ) delete pRedl; RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); if( pCSttNd && pCEndNd ) { rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); lcl_DeleteTrackedTableRow( aPam.End() ); } else if (pCSttNd && !pCEndNd) { aPam.GetBound().nContent.Assign( nullptr, 0 ); aPam.GetBound( false ).nContent.Assign( nullptr, 0 ); rDoc.getIDocumentContentOperations().DelFullPara( aPam ); } else { rDoc.getIDocumentContentOperations().DeleteRange(aPam); } rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); } else if( bDelRedl ) delete pRedl; } break; case RedlineType::FmtColl: rArr.DeleteAndDestroy( rPos-- ); break; case RedlineType::ParagraphFormat: rArr.DeleteAndDestroy( rPos-- ); break; default: bRet = false; } return bRet; } bool lcl_RejectRedline( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos, bool bCallDelete, const SwPosition* pSttRng = nullptr, const SwPosition* pEndRng = nullptr ) { bool bRet = true; SwRangeRedline* pRedl = rArr[ rPos ]; SwDoc& rDoc = pRedl->GetDoc(); SwPosition *pRStt = nullptr, *pREnd = nullptr; SwComparePosition eCmp = SwComparePosition::Outside; if( pSttRng && pEndRng ) { pRStt = pRedl->Start(); pREnd = pRedl->End(); eCmp = ComparePosition( *pSttRng, *pEndRng, *pRStt, *pREnd ); } pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); switch( pRedl->GetType() ) { case RedlineType::Insert: { const SwPosition *pDelStt = nullptr, *pDelEnd = nullptr; bool bDelRedl = false; switch( eCmp ) { case SwComparePosition::Inside: if( bCallDelete ) { pDelStt = pSttRng; pDelEnd = pEndRng; } break; case SwComparePosition::OverlapBefore: if( bCallDelete ) { pDelStt = pRStt; pDelEnd = pEndRng; } break; case SwComparePosition::OverlapBehind: if( bCallDelete ) { pDelStt = pREnd; pDelEnd = pSttRng; } break; case SwComparePosition::Outside: case SwComparePosition::Equal: { // delete the range again rArr.Remove( rPos-- ); bDelRedl = true; if( bCallDelete ) { pDelStt = pRedl->Start(); pDelEnd = pRedl->End(); } } break; default: bRet = false; } if( pDelStt && pDelEnd ) { SwPaM aPam( *pDelStt, *pDelEnd ); SwContentNode* pCSttNd = pDelStt->nNode.GetNode().GetContentNode(); SwContentNode* pCEndNd = pDelEnd->nNode.GetNode().GetContentNode(); if( bDelRedl ) delete pRedl; RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); if( pCSttNd && pCEndNd ) { rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); lcl_DeleteTrackedTableRow( aPam.End() ); } else if (pCSttNd && !pCEndNd) { aPam.GetBound().nContent.Assign( nullptr, 0 ); aPam.GetBound( false ).nContent.Assign( nullptr, 0 ); if (aPam.End()->nNode.GetNode().IsStartNode()) { // end node will be deleted too! see nNodeDiff+1 --aPam.End()->nNode; } assert(!aPam.End()->nNode.GetNode().IsStartNode()); rDoc.getIDocumentContentOperations().DelFullPara( aPam ); } else { rDoc.getIDocumentContentOperations().DeleteRange(aPam); } rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); } else if( bDelRedl ) delete pRedl; } break; case RedlineType::Delete: { SwRangeRedline* pNew = nullptr; bool bCheck = false, bReplace = false; SwPaM const updatePaM(pSttRng ? *pSttRng : *pRedl->Start(), pEndRng ? *pEndRng : *pRedl->End()); if( pRedl->GetExtraData() ) pRedl->GetExtraData()->Reject( *pRedl ); // remove tracking of the table row, if needed lcl_RemoveTrackingOfTableRow( updatePaM.End(), /*bRejectDelete=*/true ); switch( eCmp ) { case SwComparePosition::Inside: { if( 1 < pRedl->GetStackCount() ) { pNew = new SwRangeRedline( *pRedl ); pNew->PopData(); } if( *pSttRng == *pRStt ) { pRedl->SetStart( *pEndRng, pRStt ); bReplace = true; if( pNew ) pNew->SetEnd( *pEndRng ); } else { if( *pEndRng != *pREnd ) { // split up SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); pCpy->SetStart( *pEndRng ); rArr.Insert( pCpy ); ++rPos; if( pNew ) pNew->SetEnd( *pEndRng ); } pRedl->SetEnd( *pSttRng, pREnd ); bCheck = true; if( pNew ) pNew->SetStart( *pSttRng ); } } break; case SwComparePosition::OverlapBefore: if( 1 < pRedl->GetStackCount() ) { pNew = new SwRangeRedline( *pRedl ); pNew->PopData(); } pRedl->SetStart( *pEndRng, pRStt ); bReplace = true; if( pNew ) pNew->SetEnd( *pEndRng ); break; case SwComparePosition::OverlapBehind: if( 1 < pRedl->GetStackCount() ) { pNew = new SwRangeRedline( *pRedl ); pNew->PopData(); } pRedl->SetEnd( *pSttRng, pREnd ); bCheck = true; if( pNew ) pNew->SetStart( *pSttRng ); break; case SwComparePosition::Outside: case SwComparePosition::Equal: if( !pRedl->PopData() ) // deleting the RedlineObject is enough rArr.DeleteAndDestroy( rPos-- ); break; default: bRet = false; } if( pNew ) { rArr.Insert( pNew ); ++rPos; } if( bReplace || ( bCheck && !pRedl->HasValidRange() )) { // re-insert rArr.Remove( pRedl ); rArr.Insert( pRedl ); } sw::UpdateFramesForRemoveDeleteRedline(rDoc, updatePaM); } break; case RedlineType::Format: case RedlineType::FmtColl: case RedlineType::ParagraphFormat: { // tdf#52391 instead of hidden acception at the requested // rejection, remove direct text formatting to get the potential // original state of the text (FIXME if the original text // has already contained direct text formatting: unfortunately // ODF 1.2 doesn't support rejection of format-only changes) if ( pRedl->GetType() == RedlineType::Format ) { SwPaM aPam( *(pRedl->Start()), *(pRedl->End()) ); rDoc.ResetAttrs(aPam); } else if ( pRedl->GetType() == RedlineType::ParagraphFormat ) { // handle paragraph formatting changes // (range is only a full paragraph or a part of it) const SwPosition* pStt = pRedl->Start(); SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode(); if( pTNd ) { // expand range to the whole paragraph // and reset only the paragraph attributes SwPaM aPam( *pTNd, pTNd->GetText().getLength() ); o3tl::sorted_vector aResetAttrsArray; constexpr std::pair aResetableSetRange[] = { { RES_PARATR_BEGIN, RES_PARATR_END - 1 }, { RES_PARATR_LIST_BEGIN, RES_FRMATR_END - 1 }, }; for (const auto& [nBegin, nEnd] : aResetableSetRange) { for (sal_uInt16 i = nBegin; i <= nEnd; ++i) aResetAttrsArray.insert( i ); } rDoc.ResetAttrs(aPam, false, aResetAttrsArray); // remove numbering if ( pTNd->GetNumRule() ) rDoc.DelNumRules(aPam); } } if( pRedl->GetExtraData() ) pRedl->GetExtraData()->Reject( *pRedl ); rArr.DeleteAndDestroy( rPos-- ); } break; default: bRet = false; } return bRet; } typedef bool (*Fn_AcceptReject)( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos, bool bCallDelete, const SwPosition* pSttRng, const SwPosition* pEndRng); int lcl_AcceptRejectRedl( Fn_AcceptReject fn_AcceptReject, SwRedlineTable& rArr, bool bCallDelete, const SwPaM& rPam) { SwRedlineTable::size_type n = 0; int nCount = 0; const SwPosition* pStt = rPam.Start(), * pEnd = rPam.End(); const SwRangeRedline* pFnd = rArr.FindAtPosition( *pStt, n ); if( pFnd && // Is new a part of it? ( *pFnd->Start() != *pStt || *pFnd->End() > *pEnd )) { // Only revoke the partial selection if( (*fn_AcceptReject)( rArr, n, bCallDelete, pStt, pEnd )) nCount++; ++n; } // tdf#119824 first we will accept only overlapping paragraph format changes // in the first loop to avoid potential content changes during Redo bool bHasParagraphFormatChange = false; for( int m = 0 ; m < 2 && !bHasParagraphFormatChange; ++m ) { for(SwRedlineTable::size_type o = n ; o < rArr.size(); ++o ) { SwRangeRedline* pTmp = rArr[ o ]; if( pTmp->HasMark() && pTmp->IsVisible() ) { if( *pTmp->End() <= *pEnd ) { if( (m > 0 || RedlineType::ParagraphFormat == pTmp->GetType()) && (*fn_AcceptReject)( rArr, o, bCallDelete, nullptr, nullptr )) { bHasParagraphFormatChange = true; nCount++; } } else { if( *pTmp->Start() < *pEnd ) { // Only revoke the partial selection if( (m > 0 || RedlineType::ParagraphFormat == pTmp->GetType()) && (*fn_AcceptReject)( rArr, o, bCallDelete, pStt, pEnd )) { bHasParagraphFormatChange = true; nCount++; } } break; } } } } return nCount; } void lcl_AdjustRedlineRange( SwPaM& rPam ) { // The Selection is only in the ContentSection. If there are Redlines // to Non-ContentNodes before or after that, then the Selections // expand to them. SwPosition* pStt = rPam.Start(), * pEnd = rPam.End(); SwDoc& rDoc = rPam.GetDoc(); if( !pStt->nContent.GetIndex() && !rDoc.GetNodes()[ pStt->nNode.GetIndex() - 1 ]->IsContentNode() ) { const SwRangeRedline* pRedl = rDoc.getIDocumentRedlineAccess().GetRedline( *pStt, nullptr ); if( pRedl ) { const SwPosition* pRStt = pRedl->Start(); if( !pRStt->nContent.GetIndex() && pRStt->nNode.GetIndex() == pStt->nNode.GetIndex() - 1 ) *pStt = *pRStt; } } if( pEnd->nNode.GetNode().IsContentNode() && !rDoc.GetNodes()[ pEnd->nNode.GetIndex() + 1 ]->IsContentNode() && pEnd->nContent.GetIndex() == pEnd->nNode.GetNode().GetContentNode()->Len() ) { const SwRangeRedline* pRedl = rDoc.getIDocumentRedlineAccess().GetRedline( *pEnd, nullptr ); if( pRedl ) { const SwPosition* pREnd = pRedl->End(); if( !pREnd->nContent.GetIndex() && pREnd->nNode.GetIndex() == pEnd->nNode.GetIndex() + 1 ) *pEnd = *pREnd; } } } /// in case some text is deleted, ensure that the not-yet-inserted /// SwRangeRedline has its positions corrected not to point to deleted node class TemporaryRedlineUpdater { private: SwRangeRedline & m_rRedline; std::shared_ptr m_pCursor; public: TemporaryRedlineUpdater(SwDoc & rDoc, SwRangeRedline & rRedline) : m_rRedline(rRedline) , m_pCursor(rDoc.CreateUnoCursor(*rRedline.GetPoint(), false)) { if (m_rRedline.HasMark()) { m_pCursor->SetMark(); *m_pCursor->GetMark() = *m_rRedline.GetMark(); *m_rRedline.GetMark() = SwPosition(rDoc.GetNodes().GetEndOfContent()); } *m_rRedline.GetPoint() = SwPosition(rDoc.GetNodes().GetEndOfContent()); } ~TemporaryRedlineUpdater() { static_cast(m_rRedline) = *m_pCursor; } }; } namespace sw { DocumentRedlineManager::DocumentRedlineManager(SwDoc& i_rSwdoc) : m_rDoc(i_rSwdoc) , meRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) , mbIsRedlineMove(false) , mnAutoFormatRedlnCommentNo(0) { } RedlineFlags DocumentRedlineManager::GetRedlineFlags() const { return meRedlineFlags; } void DocumentRedlineManager::SetRedlineFlags( RedlineFlags eMode ) { if( meRedlineFlags == eMode ) return; if( (RedlineFlags::ShowMask & meRedlineFlags) != (RedlineFlags::ShowMask & eMode) || !(RedlineFlags::ShowMask & eMode) ) { bool bSaveInXMLImportFlag = m_rDoc.IsInXMLImport(); m_rDoc.SetInXMLImport( false ); // and then hide/display everything void (SwRangeRedline::*pFnc)(sal_uInt16, size_t, bool); // Allow compiler warn if use of // uninitialized ptr is possible RedlineFlags eShowMode = RedlineFlags::ShowMask & eMode; if (eShowMode == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) pFnc = &SwRangeRedline::Show; else if (eShowMode == RedlineFlags::ShowInsert) pFnc = &SwRangeRedline::Hide; else if (eShowMode == RedlineFlags::ShowDelete) pFnc = &SwRangeRedline::ShowOriginal; else { pFnc = &SwRangeRedline::Hide; eMode |= RedlineFlags::ShowInsert; } CheckAnchoredFlyConsistency(m_rDoc); CHECK_REDLINE( *this ) o3tl::sorted_vector hiddenLayouts; if (eShowMode == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) { // sw_redlinehide: the problem here is that MoveFromSection // creates the frames wrongly (non-merged), because its own // SwRangeRedline has wrong positions until after the nodes // are all moved, so fix things up by force by re-creating // all merged frames from scratch. o3tl::sorted_vector const layouts(m_rDoc.GetAllLayouts()); for (SwRootFrame *const pLayout : layouts) { if (pLayout->IsHideRedlines()) { pLayout->SetHideRedlines(false); hiddenLayouts.insert(pLayout); } } } for (sal_uInt16 nLoop = 1; nLoop <= 2; ++nLoop) for (size_t i = 0; i < maRedlineTable.size(); ++i) { SwRangeRedline *const pRedline = maRedlineTable[i]; (pRedline->*pFnc)(nLoop, i, false); while (maRedlineTable.size() <= i || maRedlineTable[i] != pRedline) { // ensure current position --i; // a previous redline may have been deleted } } //SwRangeRedline::MoveFromSection routinely changes //the keys that mpRedlineTable is sorted by maRedlineTable.Resort(); CheckAnchoredFlyConsistency(m_rDoc); CHECK_REDLINE( *this ) for (SwRootFrame *const pLayout : hiddenLayouts) { pLayout->SetHideRedlines(true); } m_rDoc.SetInXMLImport( bSaveInXMLImportFlag ); } meRedlineFlags = eMode; m_rDoc.getIDocumentState().SetModified(); // #TODO - add 'SwExtraRedlineTable' also ? } bool DocumentRedlineManager::IsRedlineOn() const { return IDocumentRedlineAccess::IsRedlineOn(meRedlineFlags); } bool DocumentRedlineManager::IsIgnoreRedline() const { return bool(RedlineFlags::Ignore & meRedlineFlags); } void DocumentRedlineManager::SetRedlineFlags_intern(RedlineFlags eMode) { meRedlineFlags = eMode; } const SwRedlineTable& DocumentRedlineManager::GetRedlineTable() const { return maRedlineTable; } SwRedlineTable& DocumentRedlineManager::GetRedlineTable() { return maRedlineTable; } const SwExtraRedlineTable& DocumentRedlineManager::GetExtraRedlineTable() const { return maExtraRedlineTable; } SwExtraRedlineTable& DocumentRedlineManager::GetExtraRedlineTable() { return maExtraRedlineTable; } bool DocumentRedlineManager::IsInRedlines(const SwNode & rNode) const { SwPosition aPos(rNode); SwNode & rEndOfRedlines = m_rDoc.GetNodes().GetEndOfRedlines(); SwPaM aPam(SwPosition(*rEndOfRedlines.StartOfSectionNode()), SwPosition(rEndOfRedlines)); return aPam.ContainsPosition(aPos); } bool DocumentRedlineManager::IsRedlineMove() const { return mbIsRedlineMove; } void DocumentRedlineManager::SetRedlineMove(bool bFlag) { mbIsRedlineMove = bFlag; } /* Text means Text not "polluted" by Redlines. Behaviour of Insert-Redline: - in the Text - insert Redline Object - in InsertRedline (own) - ignore, existing is extended - in InsertRedline (others) - split up InsertRedline and insert Redline Object - in DeleteRedline - split up DeleteRedline or move at the end/beginning Behaviour of Delete-Redline: - in the Text - insert Redline Object - in DeleteRedline (own/others) - ignore - in InsertRedline (own) - ignore, but delete character - in InsertRedline (others) - split up InsertRedline and insert Redline Object - Text and own Insert overlap - delete Text in the own Insert, extend in the other Text (up to the Insert!) - Text and other Insert overlap - insert Redline Object, the other Insert is overlapped by the Delete */ IDocumentRedlineAccess::AppendResult DocumentRedlineManager::AppendRedline(SwRangeRedline* pNewRedl, bool const bCallDelete) { CHECK_REDLINE( *this ) if (!IsRedlineOn() || IsShowOriginal(meRedlineFlags)) { if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) { RedlineFlags eOld = meRedlineFlags; // Set to NONE, so that the Delete::Redo merges the Redline data correctly! // The ShowMode needs to be retained! meRedlineFlags = eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore); m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl ); meRedlineFlags = eOld; } delete pNewRedl; pNewRedl = nullptr; CHECK_REDLINE( *this ) return AppendResult::IGNORED; } bool bMerged = false; pNewRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); if( m_rDoc.IsAutoFormatRedline() ) { pNewRedl->SetAutoFormat(); if( moAutoFormatRedlnComment && !moAutoFormatRedlnComment->isEmpty() ) { pNewRedl->SetComment( *moAutoFormatRedlnComment ); pNewRedl->SetSeqNo( mnAutoFormatRedlnCommentNo ); } } SwPosition* pStt = pNewRedl->Start(), * pEnd = pNewRedl->End(); { SwTextNode* pTextNode = pStt->nNode.GetNode().GetTextNode(); if( pTextNode == nullptr ) { if( pStt->nContent > 0 ) { OSL_ENSURE( false, "Redline start: non-text-node with content" ); pStt->nContent = 0; } } else { if( pStt->nContent > pTextNode->Len() ) { OSL_ENSURE( false, "Redline start: index after text" ); pStt->nContent = pTextNode->Len(); } } pTextNode = pEnd->nNode.GetNode().GetTextNode(); if( pTextNode == nullptr ) { if( pEnd->nContent > 0 ) { OSL_ENSURE( false, "Redline end: non-text-node with content" ); pEnd->nContent = 0; } } else { if( pEnd->nContent > pTextNode->Len() ) { OSL_ENSURE( false, "Redline end: index after text" ); pEnd->nContent = pTextNode->Len(); } } } if( ( *pStt == *pEnd ) && ( pNewRedl->GetContentIdx() == nullptr ) ) { // Do not insert empty redlines delete pNewRedl; return AppendResult::IGNORED; } bool bCompress = false; SwRedlineTable::size_type n = 0; // look up the first Redline for the starting position if( !GetRedline( *pStt, &n ) && n ) --n; bool bDec = false; for( ; pNewRedl && n < maRedlineTable.size(); bDec ? n : ++n ) { bDec = false; SwRangeRedline* pRedl = maRedlineTable[ n ]; SwPosition* pRStt = pRedl->Start(), * pREnd = pRedl->End(); // #i8518# remove empty redlines while we're at it if( ( *pRStt == *pREnd ) && ( pRedl->GetContentIdx() == nullptr ) ) { maRedlineTable.DeleteAndDestroy(n); continue; } SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ); switch( pNewRedl->GetType() ) { case RedlineType::Insert: switch( pRedl->GetType() ) { case RedlineType::Insert: if( pRedl->IsOwnRedline( *pNewRedl ) && // don't join inserted characters with moved text !pRedl->IsMoved() ) { bool bDelete = false; // Merge if applicable? if( (( SwComparePosition::Behind == eCmpPos && IsPrevPos( *pREnd, *pStt ) ) || ( SwComparePosition::CollideStart == eCmpPos ) || ( SwComparePosition::OverlapBehind == eCmpPos ) ) && pRedl->CanCombine( *pNewRedl ) && ( n+1 >= maRedlineTable.size() || ( *maRedlineTable[ n+1 ]->Start() >= *pEnd && *maRedlineTable[ n+1 ]->Start() != *pREnd ) ) ) { pRedl->SetEnd( *pEnd, pREnd ); if( !pRedl->HasValidRange() ) { // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl ); } bMerged = true; bDelete = true; } else if( (( SwComparePosition::Before == eCmpPos && IsPrevPos( *pEnd, *pRStt ) ) || ( SwComparePosition::CollideEnd == eCmpPos ) || ( SwComparePosition::OverlapBefore == eCmpPos ) ) && pRedl->CanCombine( *pNewRedl ) && ( !n || *maRedlineTable[ n-1 ]->End() != *pRStt )) { pRedl->SetStart( *pStt, pRStt ); // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl ); bMerged = true; bDelete = true; } else if ( SwComparePosition::Outside == eCmpPos ) { // own insert-over-insert redlines: // just scrap the inside ones maRedlineTable.DeleteAndDestroy( n ); bDec = true; } else if( SwComparePosition::OverlapBehind == eCmpPos ) { *pStt = *pREnd; if( ( *pStt == *pEnd ) && ( pNewRedl->GetContentIdx() == nullptr ) ) bDelete = true; } else if( SwComparePosition::OverlapBefore == eCmpPos ) { *pEnd = *pRStt; if( ( *pStt == *pEnd ) && ( pNewRedl->GetContentIdx() == nullptr ) ) bDelete = true; } else if( SwComparePosition::Inside == eCmpPos ) { bDelete = true; bMerged = true; } else if( SwComparePosition::Equal == eCmpPos ) bDelete = true; if( bDelete ) { delete pNewRedl; pNewRedl = nullptr; bCompress = true; // set IsMoved checking nearby redlines if (n < maRedlineTable.size()) // in case above 're-insert' failed maRedlineTable.isMoved(n); } } else if( SwComparePosition::Inside == eCmpPos ) { // split up if( *pEnd != *pREnd ) { SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); pCpy->SetStart( *pEnd ); maRedlineTable.Insert( pCpy ); } pRedl->SetEnd( *pStt, pREnd ); if( ( *pStt == *pRStt ) && ( pRedl->GetContentIdx() == nullptr ) ) { maRedlineTable.DeleteAndDestroy( n ); bDec = true; } else if( !pRedl->HasValidRange() ) { // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl ); } } else if ( SwComparePosition::Outside == eCmpPos ) { // handle overlapping redlines in broken documents // split up the new redline, since it covers the // existing redline. Insert the first part, and // progress with the remainder as usual SwRangeRedline* pSplit = new SwRangeRedline( *pNewRedl ); pSplit->SetEnd( *pRStt ); pNewRedl->SetStart( *pREnd ); maRedlineTable.Insert( pSplit ); if( *pStt == *pEnd && pNewRedl->GetContentIdx() == nullptr ) { delete pNewRedl; pNewRedl = nullptr; bCompress = true; } } else if ( SwComparePosition::OverlapBehind == eCmpPos ) { // handle overlapping redlines in broken documents pNewRedl->SetStart( *pREnd ); } else if ( SwComparePosition::OverlapBefore == eCmpPos ) { // handle overlapping redlines in broken documents *pEnd = *pRStt; if( ( *pStt == *pEnd ) && ( pNewRedl->GetContentIdx() == nullptr ) ) { delete pNewRedl; pNewRedl = nullptr; bCompress = true; } } break; case RedlineType::Delete: if( SwComparePosition::Inside == eCmpPos ) { // split up if( *pEnd != *pREnd ) { SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); pCpy->SetStart( *pEnd ); maRedlineTable.Insert( pCpy ); } pRedl->SetEnd( *pStt, pREnd ); if( ( *pStt == *pRStt ) && ( pRedl->GetContentIdx() == nullptr ) ) { maRedlineTable.DeleteAndDestroy( n ); bDec = true; } else if( !pRedl->HasValidRange() ) { // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl, n ); } } else if ( SwComparePosition::Outside == eCmpPos ) { // handle overlapping redlines in broken documents // split up the new redline, since it covers the // existing redline. Insert the first part, and // progress with the remainder as usual SwRangeRedline* pSplit = new SwRangeRedline( *pNewRedl ); pSplit->SetEnd( *pRStt ); pNewRedl->SetStart( *pREnd ); maRedlineTable.Insert( pSplit ); if( *pStt == *pEnd && pNewRedl->GetContentIdx() == nullptr ) { delete pNewRedl; pNewRedl = nullptr; bCompress = true; } } else if ( SwComparePosition::Equal == eCmpPos ) { // handle identical redlines in broken documents // delete old (delete) redline maRedlineTable.DeleteAndDestroy( n ); bDec = true; } else if ( SwComparePosition::OverlapBehind == eCmpPos ) { // Another workaround for broken redlines pNewRedl->SetStart( *pREnd ); } break; case RedlineType::Format: switch( eCmpPos ) { case SwComparePosition::OverlapBefore: pRedl->SetStart( *pEnd, pRStt ); // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl, n ); bDec = true; break; case SwComparePosition::OverlapBehind: pRedl->SetEnd( *pStt, pREnd ); if( *pStt == *pRStt && pRedl->GetContentIdx() == nullptr ) { maRedlineTable.DeleteAndDestroy( n ); bDec = true; } break; case SwComparePosition::Equal: case SwComparePosition::Outside: // Overlaps the current one completely or has the // same dimension, delete the old one maRedlineTable.DeleteAndDestroy( n ); bDec = true; break; case SwComparePosition::Inside: // Overlaps the current one completely, // split or shorten the new one if( *pEnd != *pREnd ) { if( *pEnd != *pRStt ) { SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); pNew->SetStart( *pEnd ); pRedl->SetEnd( *pStt, pREnd ); if( *pStt == *pRStt && pRedl->GetContentIdx() == nullptr ) maRedlineTable.DeleteAndDestroy( n ); AppendRedline( pNew, bCallDelete ); n = 0; // re-initialize bDec = true; } } else pRedl->SetEnd( *pStt, pREnd ); break; default: break; } break; default: break; } break; case RedlineType::Delete: switch( pRedl->GetType() ) { case RedlineType::Delete: switch( eCmpPos ) { case SwComparePosition::Outside: { // Overlaps the current one completely, // split the new one if (*pEnd == *pREnd) { pNewRedl->SetEnd(*pRStt, pEnd); } else if (*pStt == *pRStt) { pNewRedl->SetStart(*pREnd, pStt); } else { SwRangeRedline* pNew = new SwRangeRedline( *pNewRedl ); pNew->SetStart( *pREnd ); pNewRedl->SetEnd( *pRStt, pEnd ); AppendRedline( pNew, bCallDelete ); n = 0; // re-initialize bDec = true; } } break; case SwComparePosition::Inside: case SwComparePosition::Equal: delete pNewRedl; pNewRedl = nullptr; bCompress = true; break; case SwComparePosition::OverlapBefore: case SwComparePosition::OverlapBehind: if( pRedl->IsOwnRedline( *pNewRedl ) && pRedl->CanCombine( *pNewRedl )) { // If that's the case we can merge it, meaning // the new one covers this well if( SwComparePosition::OverlapBehind == eCmpPos ) pNewRedl->SetStart( *pRStt, pStt ); else pNewRedl->SetEnd( *pREnd, pEnd ); maRedlineTable.DeleteAndDestroy( n ); bDec = true; } else if( SwComparePosition::OverlapBehind == eCmpPos ) pNewRedl->SetStart( *pREnd, pStt ); else pNewRedl->SetEnd( *pRStt, pEnd ); break; case SwComparePosition::CollideEnd: if (pRStt->nContent != 0 && pRStt->nNode != pREnd->nNode) { // tdf#147466 HACK: don't combine in this case to avoid the tdf#119571 code from *undeleting* section nodes break; } [[fallthrough]]; case SwComparePosition::CollideStart: if( pRedl->IsOwnRedline( *pNewRedl ) && pRedl->CanCombine( *pNewRedl ) ) { if( IsHideChanges( meRedlineFlags )) { // Before we can merge, we make it visible! // We insert temporarily so that pNew is // also dealt with when moving the indices. maRedlineTable.Insert(pNewRedl); pRedl->Show(0, maRedlineTable.GetPos(pRedl)); maRedlineTable.Remove( pNewRedl ); pRStt = pRedl->Start(); pREnd = pRedl->End(); } // If that's the case we can merge it, meaning // the new one covers this well if( SwComparePosition::CollideStart == eCmpPos ) pNewRedl->SetStart( *pRStt, pStt ); else pNewRedl->SetEnd( *pREnd, pEnd ); // delete current (below), and restart process with // previous SwRedlineTable::size_type nToBeDeleted = n; bDec = true; if( *(pNewRedl->Start()) <= *pREnd ) { // Whoooah, we just extended the new 'redline' // beyond previous redlines, so better start // again. Of course this is not supposed to // happen, and in an ideal world it doesn't, // but unfortunately this code is buggy and // totally rotten so it does happen and we // better fix it. n = 0; bDec = true; } maRedlineTable.DeleteAndDestroy( nToBeDeleted ); } break; default: break; } break; case RedlineType::Insert: { // b62341295: Do not throw away redlines // even if they are not allowed to be combined RedlineFlags eOld = meRedlineFlags; if( !( eOld & RedlineFlags::DontCombineRedlines ) && pRedl->IsOwnRedline( *pNewRedl ) && // tdf#116084 tdf#121176 don't combine anonymized deletion // and anonymized insertion, i.e. with the same dummy timestamp !pRedl->GetRedlineData(0).IsAnonymized() ) { // Set to NONE, so that the Delete::Redo merges the Redline data correctly! // The ShowMode needs to be retained! meRedlineFlags = eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore); switch( eCmpPos ) { case SwComparePosition::Equal: bCompress = true; maRedlineTable.DeleteAndDestroy( n ); bDec = true; [[fallthrough]]; case SwComparePosition::Inside: if( bCallDelete ) { // DeleteAndJoin does not yield the // desired result if there is no paragraph to // join with, i.e. at the end of the document. // For this case, we completely delete the // paragraphs (if, of course, we also start on // a paragraph boundary). if( (pStt->nContent == 0) && pEnd->nNode.GetNode().IsEndNode() ) { pEnd->nNode--; pEnd->nContent.Assign( pEnd->nNode.GetNode().GetTextNode(), 0); m_rDoc.getIDocumentContentOperations().DelFullPara( *pNewRedl ); } else m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl ); bCompress = true; } delete pNewRedl; pNewRedl = nullptr; break; case SwComparePosition::Outside: { maRedlineTable.Remove( n ); bDec = true; if( bCallDelete ) { TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl); m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pRedl ); n = 0; // re-initialize } delete pRedl; } break; case SwComparePosition::OverlapBefore: { SwPaM aPam( *pRStt, *pEnd ); if( *pEnd == *pREnd ) maRedlineTable.DeleteAndDestroy( n ); else { pRedl->SetStart( *pEnd, pRStt ); // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl, n ); } if( bCallDelete ) { TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl); m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); n = 0; // re-initialize } bDec = true; } break; case SwComparePosition::OverlapBehind: { SwPaM aPam( *pStt, *pREnd ); if( *pStt == *pRStt ) { maRedlineTable.DeleteAndDestroy( n ); bDec = true; } else pRedl->SetEnd( *pStt, pREnd ); if( bCallDelete ) { TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl); m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); n = 0; // re-initialize bDec = true; } } break; default: break; } meRedlineFlags = eOld; } else { // it may be necessary to split the existing redline in // two. In this case, pRedl will be changed to cover // only part of its former range, and pNew will cover // the remainder. SwRangeRedline* pNew = nullptr; switch( eCmpPos ) { case SwComparePosition::Equal: { pRedl->PushData( *pNewRedl ); delete pNewRedl; pNewRedl = nullptr; if( IsHideChanges( meRedlineFlags )) { pRedl->Hide(0, maRedlineTable.GetPos(pRedl)); } bCompress = true; } break; case SwComparePosition::Inside: { if( *pRStt == *pStt ) { // #i97421# // redline w/out extent loops if (*pStt != *pEnd) { pNewRedl->PushData( *pRedl, false ); pRedl->SetStart( *pEnd, pRStt ); // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl, n ); bDec = true; } } else { pNewRedl->PushData( *pRedl, false ); if( *pREnd != *pEnd ) { pNew = new SwRangeRedline( *pRedl ); pNew->SetStart( *pEnd ); } pRedl->SetEnd( *pStt, pREnd ); if( !pRedl->HasValidRange() ) { // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl, n ); } } } break; case SwComparePosition::Outside: { pRedl->PushData( *pNewRedl ); if( *pEnd == *pREnd ) { pNewRedl->SetEnd( *pRStt, pEnd ); } else if (*pStt == *pRStt) { pNewRedl->SetStart(*pREnd, pStt); } else { pNew = new SwRangeRedline( *pNewRedl ); pNew->SetEnd( *pRStt ); pNewRedl->SetStart( *pREnd, pStt ); } bCompress = true; } break; case SwComparePosition::OverlapBefore: { if( *pEnd == *pREnd ) { pRedl->PushData( *pNewRedl ); pNewRedl->SetEnd( *pRStt, pEnd ); if( IsHideChanges( meRedlineFlags )) { maRedlineTable.Insert(pNewRedl); pRedl->Hide(0, maRedlineTable.GetPos(pRedl)); maRedlineTable.Remove( pNewRedl ); } } else { pNew = new SwRangeRedline( *pRedl ); pNew->PushData( *pNewRedl ); pNew->SetEnd( *pEnd ); pNewRedl->SetEnd( *pRStt, pEnd ); pRedl->SetStart( *pNew->End(), pRStt ) ; // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl ); bDec = true; } } break; case SwComparePosition::OverlapBehind: { if( *pStt == *pRStt ) { pRedl->PushData( *pNewRedl ); pNewRedl->SetStart( *pREnd, pStt ); if( IsHideChanges( meRedlineFlags )) { maRedlineTable.Insert( pNewRedl ); pRedl->Hide(0, maRedlineTable.GetPos(pRedl)); maRedlineTable.Remove( pNewRedl ); } } else { pNew = new SwRangeRedline( *pRedl ); pNew->PushData( *pNewRedl ); pNew->SetStart( *pStt ); pNewRedl->SetStart( *pREnd, pStt ); pRedl->SetEnd( *pNew->Start(), pREnd ); if( !pRedl->HasValidRange() ) { // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl ); } } } break; default: break; } // insert the pNew part (if it exists) if( pNew ) { maRedlineTable.Insert( pNew ); // pNew must be deleted if Insert() wasn't // successful. But that can't happen, since pNew is // part of the original pRedl redline. // OSL_ENSURE( bRet, "Can't insert existing redline?" ); // restart (now with pRedl being split up) n = 0; bDec = true; } } } break; case RedlineType::Format: switch( eCmpPos ) { case SwComparePosition::OverlapBefore: pRedl->SetStart( *pEnd, pRStt ); // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl, n ); bDec = true; break; case SwComparePosition::OverlapBehind: pRedl->SetEnd( *pStt, pREnd ); break; case SwComparePosition::Equal: case SwComparePosition::Outside: // Overlaps the current one completely or has the // same dimension, delete the old one maRedlineTable.DeleteAndDestroy( n ); bDec = true; break; case SwComparePosition::Inside: // Overlaps the current one completely, // split or shorten the new one if( *pEnd != *pREnd ) { if( *pEnd != *pRStt ) { SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); pNew->SetStart( *pEnd ); pRedl->SetEnd( *pStt, pREnd ); if( ( *pStt == *pRStt ) && ( pRedl->GetContentIdx() == nullptr ) ) maRedlineTable.DeleteAndDestroy( n ); AppendRedline( pNew, bCallDelete ); n = 0; // re-initialize bDec = true; } } else pRedl->SetEnd( *pStt, pREnd ); break; default: break; } break; default: break; } break; case RedlineType::Format: switch( pRedl->GetType() ) { case RedlineType::Insert: case RedlineType::Delete: switch( eCmpPos ) { case SwComparePosition::OverlapBefore: pNewRedl->SetEnd( *pRStt, pEnd ); break; case SwComparePosition::OverlapBehind: pNewRedl->SetStart( *pREnd, pStt ); break; case SwComparePosition::Equal: case SwComparePosition::Inside: delete pNewRedl; pNewRedl = nullptr; break; case SwComparePosition::Outside: // Overlaps the current one completely, // split or shorten the new one if (*pEnd == *pREnd) { pNewRedl->SetEnd(*pRStt, pEnd); } else if (*pStt == *pRStt) { pNewRedl->SetStart(*pREnd, pStt); } else { SwRangeRedline* pNew = new SwRangeRedline( *pNewRedl ); pNew->SetStart( *pREnd ); pNewRedl->SetEnd( *pRStt, pEnd ); AppendRedline( pNew, bCallDelete ); n = 0; // re-initialize bDec = true; } break; default: break; } break; case RedlineType::Format: switch( eCmpPos ) { case SwComparePosition::Outside: case SwComparePosition::Equal: { // Overlaps the current one completely or has the // same dimension, delete the old one maRedlineTable.DeleteAndDestroy( n ); bDec = true; } break; case SwComparePosition::Inside: if( pRedl->IsOwnRedline( *pNewRedl ) && pRedl->CanCombine( *pNewRedl )) { // own one can be ignored completely delete pNewRedl; pNewRedl = nullptr; } else if( *pREnd == *pEnd ) // or else only shorten the current one pRedl->SetEnd( *pStt, pREnd ); else if( *pRStt == *pStt ) { // or else only shorten the current one pRedl->SetStart( *pEnd, pRStt ); // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl, n ); bDec = true; } else { // If it lies completely within the current one // we need to split it SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); pNew->SetStart( *pEnd ); pRedl->SetEnd( *pStt, pREnd ); AppendRedline( pNew, bCallDelete ); n = 0; // re-initialize bDec = true; } break; case SwComparePosition::OverlapBefore: case SwComparePosition::OverlapBehind: if( pRedl->IsOwnRedline( *pNewRedl ) && pRedl->CanCombine( *pNewRedl )) { // If that's the case we can merge it, meaning // the new one covers this well if( SwComparePosition::OverlapBehind == eCmpPos ) pNewRedl->SetStart( *pRStt, pStt ); else pNewRedl->SetEnd( *pREnd, pEnd ); maRedlineTable.DeleteAndDestroy( n ); bDec = false; } else if( SwComparePosition::OverlapBehind == eCmpPos ) pNewRedl->SetStart( *pREnd, pStt ); else pNewRedl->SetEnd( *pRStt, pEnd ); break; case SwComparePosition::CollideEnd: if( pRedl->IsOwnRedline( *pNewRedl ) && pRedl->CanCombine( *pNewRedl ) && (n == 0 || *maRedlineTable[ n-1 ]->End() < *pStt)) { // If that's the case we can merge it, meaning // the new one covers this well pNewRedl->SetEnd( *pREnd, pEnd ); maRedlineTable.DeleteAndDestroy( n ); bDec = true; } break; case SwComparePosition::CollideStart: if( pRedl->IsOwnRedline( *pNewRedl ) && pRedl->CanCombine( *pNewRedl ) && (n+1 >= maRedlineTable.size() || (*maRedlineTable[ n+1 ]->Start() >= *pEnd && *maRedlineTable[ n+1 ]->Start() != *pREnd))) { // If that's the case we can merge it, meaning // the new one covers this well pNewRedl->SetStart( *pRStt, pStt ); maRedlineTable.DeleteAndDestroy( n ); bDec = true; } break; default: break; } break; default: break; } break; case RedlineType::FmtColl: // How should we behave here? // insert as is break; default: break; } } if( pNewRedl ) { if( ( *pStt == *pEnd ) && ( pNewRedl->GetContentIdx() == nullptr ) ) { // Do not insert empty redlines delete pNewRedl; pNewRedl = nullptr; } else { if ( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) { if ( pStt->nContent != 0 ) { // tdf#119571 update the style of the joined paragraph // after a partially deleted paragraph to show its correct style // in "Show changes" mode, too. All removed paragraphs // get the style of the first (partially deleted) paragraph // to avoid text insertion with bad style in the deleted // area later (except paragraphs of the removed tables). SwContentNode* pDelNd = pStt->nNode.GetNode().GetContentNode(); // start copying the style of the first paragraph from the end of the range SwContentNode* pTextNd = pEnd->nNode.GetNode().GetContentNode(); SwNodeIndex aIdx( pEnd->nNode.GetNode() ); bool bFirst = true; while (pTextNd != nullptr && pDelNd->GetIndex() < pTextNd->GetIndex()) { if( pTextNd->IsTextNode() ) { SwPosition aPos(aIdx); if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { bCompress = true; // split redline to store ExtraData per paragraphs SwRangeRedline* pPar = new SwRangeRedline( *pNewRedl ); pPar->SetStart( aPos ); pNewRedl->SetEnd( aPos ); // get extradata for reset formatting of the modified paragraph SwRedlineExtraData_FormatColl* pExtraData = lcl_CopyStyle(aPos, *pStt, false); if (pExtraData) { std::unique_ptr xRedlineExtraData; if (!bFirst) pExtraData->SetFormatAll(false); xRedlineExtraData.reset(pExtraData); pPar->SetExtraData( xRedlineExtraData.get() ); } // skip empty redlines without ExtraData // FIXME: maybe checking pExtraData is redundant here if ( pExtraData || *pPar->Start() != *pPar->End() ) maRedlineTable.Insert( pPar ); else delete pPar; } // modify paragraph formatting lcl_CopyStyle(*pStt, aPos); } if (bFirst) bFirst = false; // Jump to the previous paragraph and if needed, skip paragraphs of // the removed table(s) in the range to avoid leaving empty tables // because of the non-continuous redline range over the table. // FIXME: this is not enough for tables with inner redlines, where // tracked deletion of the text containing such a table leaves an // empty table at the place of the table (a problem inherited from OOo). pTextNd = nullptr; while( --aIdx && pDelNd->GetIndex() < aIdx.GetIndex() && !aIdx.GetNode().IsContentNode() ) { // possible table end if( aIdx.GetNode().IsEndNode() && aIdx.GetNode().FindTableNode() ) { SwNodeIndex aIdx2 = aIdx; // search table start and skip table paragraphs while ( pDelNd->GetIndex() < aIdx2.GetIndex() ) { SwTableNode* pTable = aIdx2.GetNode().GetTableNode(); if( pTable && pTable->EndOfSectionNode()->GetIndex() == aIdx.GetIndex() ) { aIdx = aIdx2; break; } --aIdx2; } } } if (aIdx.GetNode().IsContentNode()) pTextNd = aIdx.GetNode().GetContentNode(); } } // delete tables of the deletion explicitly, to avoid // remaining empty tables after accepting the rejection // and visible empty tables in Hide Changes mode // (this was the case, if tables have already contained // other tracked changes) // FIXME: because of recursive nature of AppendRedline, // this doesn't work for selections with multiple tables if ( m_rDoc.GetIDocumentUndoRedo().DoesUndo() ) { SwNodeIndex aSttIdx( pStt->nNode.GetNode() ); SwNodeIndex aEndIdx( pEnd->nNode.GetNode() ); while ( aSttIdx < aEndIdx ) { if ( aSttIdx.GetNode().IsTableNode() ) { SvxPrintItem aNotTracked(RES_PRINT, false); SwCursor aCursor( SwPosition(aSttIdx), nullptr ); m_rDoc.SetRowNotTracked( aCursor, aNotTracked, /*bAll=*/true ); } ++aSttIdx; } } } bool const ret = maRedlineTable.Insert( pNewRedl ); assert(ret || !pNewRedl); if (ret && !pNewRedl) { bMerged = true; // treat InsertWithValidRanges as "merge" } } } if( bCompress ) CompressRedlines(); CHECK_REDLINE( *this ) return (nullptr != pNewRedl) ? AppendResult::APPENDED : (bMerged ? AppendResult::MERGED : AppendResult::IGNORED); } bool DocumentRedlineManager::AppendTableRowRedline( SwTableRowRedline* pNewRedl ) { // #TODO - equivalent for 'SwTableRowRedline' /* CHECK_REDLINE( this ) */ if (IsRedlineOn() && !IsShowOriginal(meRedlineFlags)) { // #TODO - equivalent for 'SwTableRowRedline' /* pNewRedl->InvalidateRange(); */ // Make equivalent of 'AppendRedline' checks inside here too maExtraRedlineTable.Insert( pNewRedl ); } else { // TO DO - equivalent for 'SwTableRowRedline' /* if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) { RedlineFlags eOld = meRedlineFlags; // Set to NONE, so that the Delete::Redo merges the Redline data correctly! // The ShowMode needs to be retained! meRedlineFlags = eOld & ~(RedlineFlags::On | RedlineFlags::Ignore); DeleteAndJoin( *pNewRedl ); meRedlineFlags = eOld; } delete pNewRedl, pNewRedl = 0; */ } // #TODO - equivalent for 'SwTableRowRedline' /* CHECK_REDLINE( this ) */ return nullptr != pNewRedl; } bool DocumentRedlineManager::AppendTableCellRedline( SwTableCellRedline* pNewRedl ) { // #TODO - equivalent for 'SwTableCellRedline' /* CHECK_REDLINE( this ) */ if (IsRedlineOn() && !IsShowOriginal(meRedlineFlags)) { // #TODO - equivalent for 'SwTableCellRedline' /* pNewRedl->InvalidateRange(); */ // Make equivalent of 'AppendRedline' checks inside here too maExtraRedlineTable.Insert( pNewRedl ); } else { // TO DO - equivalent for 'SwTableCellRedline' /* if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) { RedlineFlags eOld = meRedlineFlags; // Set to NONE, so that the Delete::Redo merges the Redline data correctly! // The ShowMode needs to be retained! meRedlineFlags = eOld & ~(RedlineFlags::On | RedlineFlags::Ignore); DeleteAndJoin( *pNewRedl ); meRedlineFlags = eOld; } delete pNewRedl, pNewRedl = 0; */ } // #TODO - equivalent for 'SwTableCellRedline' /* CHECK_REDLINE( this ) */ return nullptr != pNewRedl; } void DocumentRedlineManager::CompressRedlines() { CHECK_REDLINE( *this ) void (SwRangeRedline::*pFnc)(sal_uInt16, size_t, bool) = nullptr; RedlineFlags eShow = RedlineFlags::ShowMask & meRedlineFlags; if( eShow == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) pFnc = &SwRangeRedline::Show; else if (eShow == RedlineFlags::ShowInsert) pFnc = &SwRangeRedline::Hide; // Try to merge identical ones for( SwRedlineTable::size_type n = 1; n < maRedlineTable.size(); ++n ) { SwRangeRedline* pPrev = maRedlineTable[ n-1 ], * pCur = maRedlineTable[ n ]; const SwPosition* pPrevStt = pPrev->Start(), * pPrevEnd = pPrev->End(); const SwPosition* pCurStt = pCur->Start(), * pCurEnd = pCur->End(); if( *pPrevEnd == *pCurStt && pPrev->CanCombine( *pCur ) && pPrevStt->nNode.GetNode().StartOfSectionNode() == pCurEnd->nNode.GetNode().StartOfSectionNode() && !pCurEnd->nNode.GetNode().StartOfSectionNode()->IsTableNode() ) { // we then can merge them SwRedlineTable::size_type nPrevIndex = n-1; pPrev->Show(0, nPrevIndex); pCur->Show(0, n); pPrev->SetEnd( *pCur->End() ); maRedlineTable.DeleteAndDestroy( n ); --n; if( pFnc ) (pPrev->*pFnc)(0, nPrevIndex, false); } } CHECK_REDLINE( *this ) // #TODO - add 'SwExtraRedlineTable' also ? } bool DocumentRedlineManager::SplitRedline( const SwPaM& rRange ) { bool bChg = false; SwRedlineTable::size_type n = 0; const SwPosition* pStt = rRange.Start(); const SwPosition* pEnd = rRange.End(); //FIXME overlapping problem GetRedline( *pStt, &n ); for ( ; n < maRedlineTable.size(); ++n) { SwRangeRedline * pRedline = maRedlineTable[ n ]; SwPosition *const pRedlineStart = pRedline->Start(); SwPosition *const pRedlineEnd = pRedline->End(); if (*pRedlineStart <= *pStt && *pStt <= *pRedlineEnd && *pRedlineStart <= *pEnd && *pEnd <= *pRedlineEnd) { bChg = true; int nn = 0; if (*pStt == *pRedlineStart) nn += 1; if (*pEnd == *pRedlineEnd) nn += 2; SwRangeRedline* pNew = nullptr; switch( nn ) { case 0: pNew = new SwRangeRedline( *pRedline ); pRedline->SetEnd( *pStt, pRedlineEnd ); pNew->SetStart( *pEnd ); break; case 1: *pRedlineStart = *pEnd; break; case 2: *pRedlineEnd = *pStt; break; case 3: pRedline->InvalidateRange(SwRangeRedline::Invalidation::Remove); maRedlineTable.DeleteAndDestroy( n-- ); pRedline = nullptr; break; } if (pRedline && !pRedline->HasValidRange()) { // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedline, n ); } if( pNew ) maRedlineTable.Insert( pNew, n ); } else if (*pEnd < *pRedlineStart) break; } return bChg; // #TODO - add 'SwExtraRedlineTable' also ? } bool DocumentRedlineManager::DeleteRedline( const SwPaM& rRange, bool bSaveInUndo, RedlineType nDelType ) { if( !rRange.HasMark() || *rRange.GetMark() == *rRange.GetPoint() ) return false; bool bChg = false; if (bSaveInUndo && m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { std::unique_ptr pUndo(new SwUndoRedline( SwUndoId::REDLINE, rRange )); if( pUndo->GetRedlSaveCount() ) { m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); } } const SwPosition* pStt = rRange.Start(), * pEnd = rRange.End(); SwRedlineTable::size_type n = 0; GetRedline( *pStt, &n ); for( ; n < maRedlineTable.size() ; ++n ) { SwRangeRedline* pRedl = maRedlineTable[ n ]; if( RedlineType::Any != nDelType && nDelType != pRedl->GetType() ) continue; SwPosition* pRStt = pRedl->Start(), * pREnd = pRedl->End(); switch( ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ) ) { case SwComparePosition::Equal: case SwComparePosition::Outside: pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); maRedlineTable.DeleteAndDestroy( n-- ); bChg = true; break; case SwComparePosition::OverlapBefore: pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); pRedl->SetStart( *pEnd, pRStt ); pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl ); --n; break; case SwComparePosition::OverlapBehind: pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); pRedl->SetEnd( *pStt, pREnd ); pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); if( !pRedl->HasValidRange() ) { // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl ); --n; } break; case SwComparePosition::Inside: { // this one needs to be split pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); if( *pRStt == *pStt ) { pRedl->SetStart( *pEnd, pRStt ); pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl ); --n; } else { SwRangeRedline* pCpy; if( *pREnd != *pEnd ) { pCpy = new SwRangeRedline( *pRedl ); pCpy->SetStart( *pEnd ); pCpy->InvalidateRange(SwRangeRedline::Invalidation::Add); } else pCpy = nullptr; pRedl->SetEnd( *pStt, pREnd ); pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); if( !pRedl->HasValidRange() ) { // re-insert maRedlineTable.Remove( n ); maRedlineTable.Insert( pRedl ); --n; } if( pCpy ) maRedlineTable.Insert( pCpy ); } } break; case SwComparePosition::CollideEnd: // remove (not hidden) empty redlines created for fixing tdf#119571 // (Note: hidden redlines are all empty, i.e. start and end are equal.) if ( pRedl->HasMark() && *pRedl->GetMark() == *pRedl->GetPoint() ) { pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); maRedlineTable.DeleteAndDestroy( n-- ); bChg = true; break; } [[fallthrough]]; case SwComparePosition::Before: n = maRedlineTable.size(); break; default: break; } } if( bChg ) m_rDoc.getIDocumentState().SetModified(); return bChg; // #TODO - add 'SwExtraRedlineTable' also ? } bool DocumentRedlineManager::DeleteRedline( const SwStartNode& rNode, bool bSaveInUndo, RedlineType nDelType ) { SwPaM aTemp(*rNode.EndOfSectionNode(), rNode); return DeleteRedline(aTemp, bSaveInUndo, nDelType); } SwRedlineTable::size_type DocumentRedlineManager::GetRedlinePos( const SwNode& rNd, RedlineType nType ) const { const SwNodeOffset nNdIdx = rNd.GetIndex(); // if the table only contains good (i.e. non-overlapping) data, we can do a binary search if (!maRedlineTable.HasOverlappingElements()) { // binary search to the first redline with end >= the needle auto it = std::lower_bound(maRedlineTable.begin(), maRedlineTable.end(), rNd, [&nNdIdx](const SwRangeRedline* lhs, const SwNode& /*rhs*/) { return lhs->End()->nNode.GetIndex() < nNdIdx; }); for( ; it != maRedlineTable.end(); ++it) { const SwRangeRedline* pTmp = *it; SwNodeOffset nStart = pTmp->Start()->nNode.GetIndex(), nEnd = pTmp->End()->nNode.GetIndex(); if( ( RedlineType::Any == nType || nType == pTmp->GetType()) && nStart <= nNdIdx && nNdIdx <= nEnd ) return std::distance(maRedlineTable.begin(), it); if( nStart > nNdIdx ) break; } } else { for( SwRedlineTable::size_type n = 0; n < maRedlineTable.size() ; ++n ) { const SwRangeRedline* pTmp = maRedlineTable[ n ]; SwNodeOffset nPt = pTmp->GetPoint()->nNode.GetIndex(), nMk = pTmp->GetMark()->nNode.GetIndex(); if( nPt < nMk ) { SwNodeOffset nTmp = nMk; nMk = nPt; nPt = nTmp; } if( ( RedlineType::Any == nType || nType == pTmp->GetType()) && nMk <= nNdIdx && nNdIdx <= nPt ) return n; if( nMk > nNdIdx ) break; } } return SwRedlineTable::npos; // #TODO - add 'SwExtraRedlineTable' also ? } bool DocumentRedlineManager::HasRedline( const SwPaM& rPam, RedlineType nType, bool bStartOrEndInRange ) const { SwPosition currentStart(*rPam.Start()); SwPosition currentEnd(*rPam.End()); SwNodeIndex pEndNodeIndex(currentEnd.nNode.GetNode()); for( SwRedlineTable::size_type n = GetRedlinePos( rPam.Start()->nNode.GetNode(), nType ); n < maRedlineTable.size(); ++n ) { const SwRangeRedline* pTmp = maRedlineTable[ n ]; if ( pTmp->Start()->nNode > pEndNodeIndex ) break; if( RedlineType::Any != nType && nType != pTmp->GetType() ) continue; // redline over the range if ( currentStart < *pTmp->End() && *pTmp->Start() <= currentEnd && // starting or ending within the range ( !bStartOrEndInRange || ( currentStart < *pTmp->Start() || *pTmp->End() < currentEnd ) ) ) { return true; } } return false; } const SwRangeRedline* DocumentRedlineManager::GetRedline( const SwPosition& rPos, SwRedlineTable::size_type* pFndPos ) const { SwRedlineTable::size_type nO = maRedlineTable.size(), nM, nU = 0; if( nO > 0 ) { nO--; while( nU <= nO ) { nM = nU + ( nO - nU ) / 2; const SwRangeRedline* pRedl = maRedlineTable[ nM ]; const SwPosition* pStt = pRedl->Start(); const SwPosition* pEnd = pRedl->End(); if( pEnd == pStt ? *pStt == rPos : ( *pStt <= rPos && rPos < *pEnd ) ) { while( nM && rPos == *maRedlineTable[ nM - 1 ]->End() && rPos == *maRedlineTable[ nM - 1 ]->Start() ) { --nM; pRedl = maRedlineTable[ nM ]; } // if there are format and insert changes in the same position // show insert change first. // since the redlines are sorted by position, only check the redline // before and after the current redline if( RedlineType::Format == pRedl->GetType() ) { if( nM && rPos >= *maRedlineTable[ nM - 1 ]->Start() && rPos <= *maRedlineTable[ nM - 1 ]->End() && ( RedlineType::Insert == maRedlineTable[ nM - 1 ]->GetType() ) ) { --nM; pRedl = maRedlineTable[ nM ]; } else if( ( nM + 1 ) <= nO && rPos >= *maRedlineTable[ nM + 1 ]->Start() && rPos <= *maRedlineTable[ nM + 1 ]->End() && ( RedlineType::Insert == maRedlineTable[ nM + 1 ]->GetType() ) ) { ++nM; pRedl = maRedlineTable[ nM ]; } } if( pFndPos ) *pFndPos = nM; return pRedl; } else if( *pEnd <= rPos ) nU = nM + 1; else if( nM == 0 ) { if( pFndPos ) *pFndPos = nU; return nullptr; } else nO = nM - 1; } } if( pFndPos ) *pFndPos = nU; return nullptr; // #TODO - add 'SwExtraRedlineTable' also ? } bool DocumentRedlineManager::AcceptRedline( SwRedlineTable::size_type nPos, bool bCallDelete ) { bool bRet = false; // Switch to visible in any case if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != (RedlineFlags::ShowMask & meRedlineFlags) ) SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); SwRangeRedline* pTmp = maRedlineTable[ nPos ]; pTmp->Show(0, maRedlineTable.GetPos(pTmp), /*bForced=*/true); pTmp->Show(1, maRedlineTable.GetPos(pTmp), /*bForced=*/true); if( pTmp->HasMark() && pTmp->IsVisible() ) { if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { SwRewriter aRewriter; aRewriter.AddRule(UndoArg1, pTmp->GetDescr()); m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::ACCEPT_REDLINE, &aRewriter); } int nLoopCnt = 2; sal_uInt16 nSeqNo = pTmp->GetSeqNo(); do { if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique(*pTmp) ); } bRet |= lcl_AcceptRedline( maRedlineTable, nPos, bCallDelete ); if( nSeqNo ) { if( SwRedlineTable::npos == nPos ) nPos = 0; SwRedlineTable::size_type nFndPos = 2 == nLoopCnt ? maRedlineTable.FindNextSeqNo( nSeqNo, nPos ) : maRedlineTable.FindPrevSeqNo( nSeqNo, nPos ); if( SwRedlineTable::npos != nFndPos || ( 0 != ( --nLoopCnt ) && SwRedlineTable::npos != ( nFndPos = maRedlineTable.FindPrevSeqNo( nSeqNo, nPos ))) ) { nPos = nFndPos; pTmp = maRedlineTable[ nPos ]; } else nLoopCnt = 0; } else nLoopCnt = 0; } while( nLoopCnt ); if( bRet ) { CompressRedlines(); m_rDoc.getIDocumentState().SetModified(); } if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); } } return bRet; // #TODO - add 'SwExtraRedlineTable' also ? } bool DocumentRedlineManager::AcceptRedline( const SwPaM& rPam, bool bCallDelete ) { // Switch to visible in any case if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != (RedlineFlags::ShowMask & meRedlineFlags) ) SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); // The Selection is only in the ContentSection. If there are Redlines // to Non-ContentNodes before or after that, then the Selections // expand to them. SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); lcl_AdjustRedlineRange( aPam ); if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { m_rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::ACCEPT_REDLINE, nullptr ); m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique( aPam )); } int nRet = lcl_AcceptRejectRedl( lcl_AcceptRedline, maRedlineTable, bCallDelete, aPam ); if( nRet > 0 ) { CompressRedlines(); m_rDoc.getIDocumentState().SetModified(); } if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { OUString aTmpStr; { SwRewriter aRewriter; aRewriter.AddRule(UndoArg1, OUString::number(nRet)); aTmpStr = aRewriter.Apply(SwResId(STR_N_REDLINES)); } SwRewriter aRewriter; aRewriter.AddRule(UndoArg1, aTmpStr); m_rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::ACCEPT_REDLINE, &aRewriter ); } return nRet != 0; // #TODO - add 'SwExtraRedlineTable' also ? } void DocumentRedlineManager::AcceptRedlineParagraphFormatting( const SwPaM &rPam ) { const SwPosition* pStt = rPam.Start(), * pEnd = rPam.End(); const SwNodeOffset nSttIdx = pStt->nNode.GetIndex(); const SwNodeOffset nEndIdx = pEnd->nNode.GetIndex(); for( SwRedlineTable::size_type n = 0; n < maRedlineTable.size() ; ++n ) { const SwRangeRedline* pTmp = maRedlineTable[ n ]; SwNodeOffset nPt = pTmp->GetPoint()->nNode.GetIndex(), nMk = pTmp->GetMark()->nNode.GetIndex(); if( nPt < nMk ) { SwNodeOffset nTmp = nMk; nMk = nPt; nPt = nTmp; } if( RedlineType::ParagraphFormat == pTmp->GetType() && ( (nSttIdx <= nMk && nMk <= nEndIdx) || (nSttIdx <= nPt && nPt <= nEndIdx) ) ) AcceptRedline( n, false ); if( nMk > nEndIdx ) break; } } bool DocumentRedlineManager::RejectRedline( SwRedlineTable::size_type nPos, bool bCallDelete ) { bool bRet = false; // Switch to visible in any case if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != (RedlineFlags::ShowMask & meRedlineFlags) ) SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); SwRangeRedline* pTmp = maRedlineTable[ nPos ]; pTmp->Show(0, maRedlineTable.GetPos(pTmp), /*bForced=*/true); pTmp->Show(1, maRedlineTable.GetPos(pTmp), /*bForced=*/true); if( pTmp->HasMark() && pTmp->IsVisible() ) { if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { SwRewriter aRewriter; aRewriter.AddRule(UndoArg1, pTmp->GetDescr()); m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::REJECT_REDLINE, &aRewriter); } int nLoopCnt = 2; sal_uInt16 nSeqNo = pTmp->GetSeqNo(); do { if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique( *pTmp ) ); } bRet |= lcl_RejectRedline( maRedlineTable, nPos, bCallDelete ); if( nSeqNo ) { if( SwRedlineTable::npos == nPos ) nPos = 0; SwRedlineTable::size_type nFndPos = 2 == nLoopCnt ? maRedlineTable.FindNextSeqNo( nSeqNo, nPos ) : maRedlineTable.FindPrevSeqNo( nSeqNo, nPos ); if( SwRedlineTable::npos != nFndPos || ( 0 != ( --nLoopCnt ) && SwRedlineTable::npos != ( nFndPos = maRedlineTable.FindPrevSeqNo( nSeqNo, nPos ))) ) { nPos = nFndPos; pTmp = maRedlineTable[ nPos ]; } else nLoopCnt = 0; } else nLoopCnt = 0; } while( nLoopCnt ); if( bRet ) { CompressRedlines(); m_rDoc.getIDocumentState().SetModified(); } if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); } } return bRet; // #TODO - add 'SwExtraRedlineTable' also ? } bool DocumentRedlineManager::RejectRedline( const SwPaM& rPam, bool bCallDelete ) { // Switch to visible in any case if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != (RedlineFlags::ShowMask & meRedlineFlags) ) SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); // The Selection is only in the ContentSection. If there are Redlines // to Non-ContentNodes before or after that, then the Selections // expand to them. SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); lcl_AdjustRedlineRange( aPam ); if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { m_rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REJECT_REDLINE, nullptr ); m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique(aPam) ); } int nRet = lcl_AcceptRejectRedl( lcl_RejectRedline, maRedlineTable, bCallDelete, aPam ); if( nRet > 0 ) { CompressRedlines(); m_rDoc.getIDocumentState().SetModified(); } if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { OUString aTmpStr; { SwRewriter aRewriter; aRewriter.AddRule(UndoArg1, OUString::number(nRet)); aTmpStr = aRewriter.Apply(SwResId(STR_N_REDLINES)); } SwRewriter aRewriter; aRewriter.AddRule(UndoArg1, aTmpStr); m_rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REJECT_REDLINE, &aRewriter ); } return nRet != 0; // #TODO - add 'SwExtraRedlineTable' also ? } void DocumentRedlineManager::AcceptAllRedline(bool bAccept) { bool bSuccess = true; OUString sUndoStr; IDocumentUndoRedo& rUndoMgr = m_rDoc.GetIDocumentUndoRedo(); if (maRedlineTable.size() > 1) { { SwRewriter aRewriter; aRewriter.AddRule(UndoArg1, OUString::number(maRedlineTable.size())); sUndoStr = aRewriter.Apply(SwResId(STR_N_REDLINES)); } SwRewriter aRewriter; aRewriter.AddRule(UndoArg1, sUndoStr); rUndoMgr.StartUndo(bAccept ? SwUndoId::ACCEPT_REDLINE : SwUndoId::REJECT_REDLINE, &aRewriter); } while (!maRedlineTable.empty() && bSuccess) { if (bAccept) bSuccess = AcceptRedline(maRedlineTable.size() - 1, true); else bSuccess = RejectRedline(maRedlineTable.size() - 1, true); } if (!sUndoStr.isEmpty()) { rUndoMgr.EndUndo(SwUndoId::EMPTY, nullptr); } } const SwRangeRedline* DocumentRedlineManager::SelNextRedline( SwPaM& rPam ) const { rPam.DeleteMark(); rPam.SetMark(); SwPosition& rSttPos = *rPam.GetPoint(); SwPosition aSavePos( rSttPos ); bool bRestart; // If the starting position points to the last valid ContentNode, // we take the next Redline in any case. SwRedlineTable::size_type n = 0; const SwRangeRedline* pFnd = GetRedlineTable().FindAtPosition( rSttPos, n ); if( pFnd ) { const SwPosition* pEnd = pFnd->End(); if( !pEnd->nNode.GetNode().IsContentNode() ) { SwNodeIndex aTmp( pEnd->nNode ); SwContentNode* pCNd = SwNodes::GoPrevSection( &aTmp ); if( !pCNd || ( aTmp == rSttPos.nNode && pCNd->Len() == rSttPos.nContent.GetIndex() )) pFnd = nullptr; } if( pFnd ) rSttPos = *pFnd->End(); } do { bRestart = false; for( ; !pFnd && n < maRedlineTable.size(); ++n ) { pFnd = maRedlineTable[ n ]; if( pFnd->HasMark() && pFnd->IsVisible() ) { *rPam.GetMark() = *pFnd->Start(); rSttPos = *pFnd->End(); break; } else pFnd = nullptr; } if( pFnd ) { // Merge all of the same type and author that are // consecutive into one Selection. const SwPosition* pPrevEnd = pFnd->End(); while( ++n < maRedlineTable.size() ) { const SwRangeRedline* pTmp = maRedlineTable[ n ]; if( pTmp->HasMark() && pTmp->IsVisible() ) { const SwPosition *pRStt; if( pFnd->GetType() != pTmp->GetType() || pFnd->GetAuthor() != pTmp->GetAuthor() ) break; pRStt = pTmp->Start(); if( *pPrevEnd == *pRStt || IsPrevPos( *pPrevEnd, *pRStt ) ) { pPrevEnd = pTmp->End(); rSttPos = *pPrevEnd; } else break; } } } if( pFnd ) { const SwRangeRedline* pSaveFnd = pFnd; SwContentNode* pCNd; SwNodeIndex* pIdx = &rPam.GetMark()->nNode; if( !pIdx->GetNode().IsContentNode() ) { pCNd = m_rDoc.GetNodes().GoNextSection( pIdx ); if( pCNd ) { if( *pIdx <= rPam.GetPoint()->nNode ) rPam.GetMark()->nContent.Assign( pCNd, 0 ); else pFnd = nullptr; } } if( pFnd ) { pIdx = &rPam.GetPoint()->nNode; if( !pIdx->GetNode().IsContentNode() ) { pCNd = SwNodes::GoPrevSection( pIdx ); if( pCNd ) { if( *pIdx >= rPam.GetMark()->nNode ) rPam.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); else pFnd = nullptr; } } } if( !pFnd || *rPam.GetMark() == *rPam.GetPoint() ) { if( n < maRedlineTable.size() ) { bRestart = true; *rPam.GetPoint() = *pSaveFnd->End(); } else { rPam.DeleteMark(); *rPam.GetPoint() = aSavePos; } pFnd = nullptr; } } } while( bRestart ); return pFnd; // #TODO - add 'SwExtraRedlineTable' also ? } const SwRangeRedline* DocumentRedlineManager::SelPrevRedline( SwPaM& rPam ) const { rPam.DeleteMark(); rPam.SetMark(); SwPosition& rSttPos = *rPam.GetPoint(); SwPosition aSavePos( rSttPos ); bool bRestart; // If the starting position points to the last valid ContentNode, // we take the previous Redline in any case. SwRedlineTable::size_type n = 0; const SwRangeRedline* pFnd = GetRedlineTable().FindAtPosition( rSttPos, n, false ); if( pFnd ) { const SwPosition* pStt = pFnd->Start(); if( !pStt->nNode.GetNode().IsContentNode() ) { SwNodeIndex aTmp( pStt->nNode ); SwContentNode* pCNd = m_rDoc.GetNodes().GoNextSection( &aTmp ); if( !pCNd || ( aTmp == rSttPos.nNode && !rSttPos.nContent.GetIndex() )) pFnd = nullptr; } if( pFnd ) rSttPos = *pFnd->Start(); } do { bRestart = false; while( !pFnd && 0 < n ) { pFnd = maRedlineTable[ --n ]; if( pFnd->HasMark() && pFnd->IsVisible() ) { *rPam.GetMark() = *pFnd->End(); rSttPos = *pFnd->Start(); } else pFnd = nullptr; } if( pFnd ) { // Merge all of the same type and author that are // consecutive into one Selection. const SwPosition* pNextStt = pFnd->Start(); while( 0 < n ) { const SwRangeRedline* pTmp = maRedlineTable[ --n ]; if( pTmp->HasMark() && pTmp->IsVisible() ) { const SwPosition *pREnd; if( pFnd->GetType() == pTmp->GetType() && pFnd->GetAuthor() == pTmp->GetAuthor() && ( *pNextStt == *( pREnd = pTmp->End() ) || IsPrevPos( *pREnd, *pNextStt )) ) { pNextStt = pTmp->Start(); rSttPos = *pNextStt; } else { ++n; break; } } } } if( pFnd ) { const SwRangeRedline* pSaveFnd = pFnd; SwContentNode* pCNd; SwNodeIndex* pIdx = &rPam.GetMark()->nNode; if( !pIdx->GetNode().IsContentNode() ) { pCNd = SwNodes::GoPrevSection( pIdx ); if( pCNd ) { if( *pIdx >= rPam.GetPoint()->nNode ) rPam.GetMark()->nContent.Assign( pCNd, pCNd->Len() ); else pFnd = nullptr; } } if( pFnd ) { pIdx = &rPam.GetPoint()->nNode; if( !pIdx->GetNode().IsContentNode() ) { pCNd = m_rDoc.GetNodes().GoNextSection( pIdx ); if( pCNd ) { if( *pIdx <= rPam.GetMark()->nNode ) rPam.GetPoint()->nContent.Assign( pCNd, 0 ); else pFnd = nullptr; } } } if( !pFnd || *rPam.GetMark() == *rPam.GetPoint() ) { if( n ) { bRestart = true; *rPam.GetPoint() = *pSaveFnd->Start(); } else { rPam.DeleteMark(); *rPam.GetPoint() = aSavePos; } pFnd = nullptr; } } } while( bRestart ); return pFnd; // #TODO - add 'SwExtraRedlineTable' also ? } // Set comment at the Redline bool DocumentRedlineManager::SetRedlineComment( const SwPaM& rPaM, const OUString& rS ) { bool bRet = false; const SwPosition* pStt = rPaM.Start(), * pEnd = rPaM.End(); SwRedlineTable::size_type n = 0; if( GetRedlineTable().FindAtPosition( *pStt, n ) ) { for( ; n < maRedlineTable.size(); ++n ) { bRet = true; SwRangeRedline* pTmp = maRedlineTable[ n ]; if( pStt != pEnd && *pTmp->Start() > *pEnd ) break; pTmp->SetComment( rS ); if( *pTmp->End() >= *pEnd ) break; } } if( bRet ) m_rDoc.getIDocumentState().SetModified(); return bRet; // #TODO - add 'SwExtraRedlineTable' also ? } // Create a new author if necessary std::size_t DocumentRedlineManager::GetRedlineAuthor() { return SW_MOD()->GetRedlineAuthor(); } /// Insert new author into the Table for the Readers etc. std::size_t DocumentRedlineManager::InsertRedlineAuthor( const OUString& rNew ) { return SW_MOD()->InsertRedlineAuthor(rNew); } void DocumentRedlineManager::UpdateRedlineAttr() { const SwRedlineTable& rTable = GetRedlineTable(); for(SwRangeRedline* pRedl : rTable) { if( pRedl->IsVisible() ) pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); } // #TODO - add 'SwExtraRedlineTable' also ? } const uno::Sequence & DocumentRedlineManager::GetRedlinePassword() const { return maRedlinePasswd; } void DocumentRedlineManager::SetRedlinePassword( /*[in]*/const uno::Sequence & rNewPassword) { maRedlinePasswd = rNewPassword; m_rDoc.getIDocumentState().SetModified(); } /// Set comment text for the Redline, which is inserted later on via /// AppendRedline. Is used by Autoformat. /// A null pointer resets the mode. The pointer is not copied, so it /// needs to stay valid! void DocumentRedlineManager::SetAutoFormatRedlineComment( const OUString* pText, sal_uInt16 nSeqNo ) { m_rDoc.SetAutoFormatRedline( nullptr != pText ); if( pText ) { moAutoFormatRedlnComment = *pText; } else { moAutoFormatRedlnComment.reset(); } mnAutoFormatRedlnCommentNo = nSeqNo; } void DocumentRedlineManager::HideAll( bool bDeletion ) { const SwRedlineTable& rTable = GetRedlineTable(); for (SwRedlineTable::size_type i = rTable.size(); i > 0; --i) { SwRangeRedline* pRedline = rTable[i-1]; if ( pRedline->GetType() == RedlineType::Delete ) { if ( bDeletion && pRedline->IsVisible() ) { pRedline->Hide(0, rTable.GetPos(pRedline), false); pRedline->Hide(1, rTable.GetPos(pRedline), false); } else if ( !bDeletion && !pRedline->IsVisible() ) { pRedline->Show(0, rTable.GetPos(pRedline), true); pRedline->Show(1, rTable.GetPos(pRedline), true); } } else if ( pRedline->GetType() == RedlineType::Insert ) { if ( !bDeletion && pRedline->IsVisible() ) { pRedline->ShowOriginal(0, rTable.GetPos(pRedline), false); pRedline->ShowOriginal(1, rTable.GetPos(pRedline), false); } else if ( bDeletion && !pRedline->IsVisible() ) { pRedline->Show(0, rTable.GetPos(pRedline), true); pRedline->Show(1, rTable.GetPos(pRedline), true); } } } } void DocumentRedlineManager::ShowAll() { const SwRedlineTable& rTable = GetRedlineTable(); for (SwRedlineTable::size_type i = rTable.size(); i > 0; --i) { SwRangeRedline* pRedline = rTable[i-1]; if ( !pRedline->IsVisible() ) { pRedline->Show(0, rTable.GetPos(pRedline), true); pRedline->Show(1, rTable.GetPos(pRedline), true); } } } DocumentRedlineManager::~DocumentRedlineManager() { } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */