/* -*- 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 <DocumentRedlineManager.hxx>
#include <frmfmt.hxx>
#include <rootfrm.hxx>
#include <txtfrm.hxx>
#include <txtfld.hxx>
#include <doc.hxx>
#include <docsh.hxx>
#include <wrtsh.hxx>
#include <fmtfld.hxx>
#include <frmtool.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentState.hxx>
#include <redline.hxx>
#include <UndoRedline.hxx>
#include <docary.hxx>
#include <ndtxt.hxx>
#include <unocrsr.hxx>
#include <ftnidx.hxx>
#include <authfld.hxx>
#include <strings.hrc>
#include <swmodule.hxx>
#include <osl/diagnose.h>
#include <editeng/prntitem.hxx>
#include <comphelper/lok.hxx>
#include <svl/itemiter.hxx>
#include <istyleaccess.hxx>
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<SwContentIndexReg*>(&pPos->GetNode())
== pPos->GetContentNode());
SwTextNode* pTextNode = pPos->GetNode().GetTextNode();
if( pTextNode == nullptr )
{
assert(pPos->GetContentIndex() == 0);
}
else
{
assert(pPos->GetContentIndex() >= 0 && pPos->GetContentIndex() <= 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<SwAuthorityFieldType*>(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()->GetNode());
SwPosition currentStart(*rPam.Start());
SwTextNode * pStartNode(rPam.Start()->GetNode().GetTextNode());
while (!pStartNode)
{
// note: branch only taken for redlines, not fieldmarks
SwStartNode *const pTableOrSectionNode(
currentStart.GetNode().IsTableNode()
? static_cast<SwStartNode*>(currentStart.GetNode().GetTableNode())
: static_cast<SwStartNode*>(currentStart.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<SwTableNode*>(pTableOrSectionNode)->DelFrames(pLayout);
}
else
{
static_cast<SwSectionNode*>(pTableOrSectionNode)->DelFrames(pLayout);
}
}
}
currentStart.Assign( pTableOrSectionNode->EndOfSectionIndex() + 1 );
pStartNode = currentStart.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<SwTextFrame*> frames;
SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> 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<SwTextNode*>(pFrame->GetMergedPara()->pLastNode);
}
}
SwNodeIndex tmp(*pLast);
// skip over hidden sections!
pNode = static_cast<SwTextNode*>(SwNodes::GoNextSection(&tmp, /*bSkipHidden=*/true, /*bSkipProtect=*/false));
}
while (pNode && pNode->GetIndex() <= rPam.End()->GetNodeIndex());
}
// 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()->GetNode());
SwPosition currentStart(*rPam.Start());
SwTextNode * pStartNode(rPam.Start()->GetNode().GetTextNode());
while (!pStartNode)
{
// note: branch only taken for redlines, not fieldmarks
SwStartNode *const pTableOrSectionNode(
currentStart.GetNode().IsTableNode()
? static_cast<SwStartNode*>(currentStart.GetNode().GetTableNode())
: static_cast<SwStartNode*>(currentStart.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
::MakeFrames(rDoc, currentStart.GetNode(), *pTableOrSectionNode->EndOfSectionNode());
isAppendObjsCalled = true;
}
currentStart.Assign( pTableOrSectionNode->EndOfSectionIndex() + 1 );
pStartNode = currentStart.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<SwTextFrame*> frames;
SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> 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()->GetNode() == rPam.GetMark()->GetNode())
{
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<SwTextNode*>(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.GetNode(), end.GetNode());
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<SwTextNode*>(SwNodes::GoNextSection(&tmp, /*bSkipHidden=*/true, /*bSkipProtect=*/false));
}
while (pNode && pNode->GetIndex() <= rPam.End()->GetNodeIndex());
}
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
const SwTextNode *pTextNode = rPam.GetPointNode().GetTextNode();
SwTextAttr* pTextAttr = pTextNode ? pTextNode->GetFieldTextAttrAt(rPam.GetPoint()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default) : nullptr;
SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pTextAttr));
if (pTextField && comphelper::LibreOfficeKit::isActive() )
rDoc.GetDocShell()->Broadcast(
SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::INSERTED));
else
rDoc.GetDocShell()->Broadcast(
SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::INSERTED) );
}
} // namespace sw
namespace
{
bool IsPrevPos( const SwPosition & rPos1, const SwPosition & rPos2 )
{
const SwContentNode* pCNd;
if( 0 != rPos2.GetContentIndex() )
return false;
if( rPos2.GetNodeIndex() - 1 != rPos1.GetNodeIndex() )
return false;
pCNd = rPos1.GetNode().GetContentNode();
return pCNd && rPos1.GetContentIndex() == pCNd->Len();
}
// copy style or return with SwRedlineExtra_FormatColl with reject data of the upcoming copy
std::unique_ptr<SwRedlineExtraData_FormatColl> lcl_CopyStyle( const SwPosition & rFrom, const SwPosition & rTo, bool bCopy = true )
{
SwTextNode* pToNode = rTo.GetNode().GetTextNode();
SwTextNode* pFromNode = rFrom.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
SfxItemSet aTmp(SfxItemSet::makeFixedSfxItemSet<
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>(rDoc.GetAttrPool()));
SfxItemSet aTmp2(aTmp);
pToNode->GetParaAttr(aTmp, 0, 0);
pFromNode->GetParaAttr(aTmp2, 0, 0);
bool bSameSet = aTmp == aTmp2;
if (!bSameSet)
{
for (SfxItemIter aIter(aTmp); !aIter.IsAtEnd(); aIter.NextItem())
{
const sal_uInt16 nWhich(aIter.GetCurWhich());
if( SfxItemState::SET == aTmp.GetItemState( nWhich, false ) &&
SfxItemState::SET != aTmp2.GetItemState( nWhich, false ) )
aTmp2.Put( aTmp.GetPool()->GetUserOrPoolDefaultItem(nWhich) );
}
}
if (bCopy && !bSameSet)
rDoc.getIDocumentContentOperations().InsertItemSet(aPam, aTmp2);
else if (!bCopy && (!bSameSet || pFromColl != pToColl))
{
IStyleAccess& rStyleAccess = rDoc.GetIStyleAccess();
std::shared_ptr<SfxItemSet> pAutoStyle = rStyleAccess.getAutomaticStyle(aTmp2, IStyleAccess::AUTO_STYLE_CHAR);
return std::make_unique<SwRedlineExtraData_FormatColl>( pFromColl->GetName(), USHRT_MAX, pAutoStyle );
}
}
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->GetNode().GetTableBox();
if ( !pBox )
return;
// tracked column deletion
const SvxPrintItem *pHasBoxTextChangesOnlyProp =
pBox->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
// empty table cell with property "HasTextChangesOnly" = false
if ( pHasBoxTextChangesOnlyProp && !pHasBoxTextChangesOnlyProp->GetValue() )
{
SwCursor aCursor( *pPos, nullptr );
if ( pBox->IsEmpty() )
{
// tdf#155747 remove table cursor
pPos->GetDoc().GetDocShell()->GetWrtShell()->EnterStdMode();
// TODO check the other cells of the column
// before removing the column
pPos->GetDoc().DeleteCol( aCursor );
return;
}
else
{
SvxPrintItem aHasTextChangesOnly(RES_PRINT, false);
pPos->GetDoc().SetBoxAttr( aCursor, aHasTextChangesOnly );
}
}
// tracked row deletion
const SwTableLine* pLine = pBox->GetUpper();
const SvxPrintItem *pHasTextChangesOnlyProp =
pLine->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(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->GetNode().GetTableBox();
if ( !pBox )
return;
// tracked column deletion
const SvxPrintItem *pHasBoxTextChangesOnlyProp =
pBox->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
// table cell property "HasTextChangesOnly" is set and its value is false
if ( pHasBoxTextChangesOnlyProp && !pHasBoxTextChangesOnlyProp->GetValue() )
{
SvxPrintItem aUnsetTracking(RES_PRINT, true);
SwCursor aCursor( *pPos, nullptr );
pPos->GetDoc().SetBoxAttr( aCursor, aUnsetTracking );
}
// tracked row deletion
const SwTableLine* pLine = pBox->GetUpper();
const SvxPrintItem *pHasTextChangesOnlyProp =
pLine->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(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()->GetNode());
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->GetNode().GetContentNode();
SwContentNode* pCEndNd = pDelEnd->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->GetContentIndex() == 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:
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->GetNode().GetContentNode();
SwContentNode* pCEndNd = pDelEnd->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()->GetNode().IsStartNode())
{ // end node will be deleted too! see nNodeDiff+1
aPam.End()->Adjust(SwNodeOffset(-1));
}
assert(!aPam.End()->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* pStart = pRedl->Start();
SwTextNode* pTNd = pStart->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<sal_uInt16> aResetAttrsArray;
static constexpr std::pair<sal_uInt16, sal_uInt16> 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;
}
/// Given a redline that has another underlying redline, drop that underlying redline.
/// Used to accept an insert or rejecting a delete, i.e. no changes to the text node strings.
bool lcl_DeleteInnerRedline(const SwRedlineTable& rArr, const SwRedlineTable::size_type& rPos,
int nDepth)
{
SwRangeRedline* pRedl = rArr[rPos];
SwDoc& rDoc = pRedl->GetDoc();
SwPaM const updatePaM(*pRedl->Start(), *pRedl->End());
pRedl->PopAllDataAfter(nDepth);
sw::UpdateFramesForRemoveDeleteRedline(rDoc, updatePaM);
return true;
}
/// Given a redline that has an other underlying redline, drop the redline on top.
/// Used to accept a format on top of insert/delete, no changes to the text node string.
bool lcl_AcceptOuterFormat(SwRedlineTable& rArr, SwRedlineTable::size_type& rPos)
{
SwRangeRedline* pRedl = rArr[rPos];
return pRedl->PopData();
}
/// Given a redline that has an other underlying redline, drop the redline on top & restore the
/// old doc model. Used to reject a format on top of insert/delete.
bool lcl_RejectOuterFormat(SwRedlineTable& rArr, SwRedlineTable::size_type& rPos)
{
SwRangeRedline* pRedl = rArr[rPos];
SwDoc& rDoc = pRedl->GetDoc();
SwPaM aPam(*(pRedl->Start()), *(pRedl->End()));
rDoc.ResetAttrs(aPam);
if (pRedl->GetExtraData())
pRedl->GetExtraData()->Reject(*pRedl);
return pRedl->PopData();
}
/// Given a redline that has two types and the underlying type is
/// delete, reject the redline based on that underlying type. Used
/// to accept a delete-then-format, i.e. this does change the text
/// node string.
bool lcl_AcceptInnerDelete(SwRangeRedline& rRedline, SwRedlineTable& rRedlines,
SwRedlineTable::size_type& rRedlineIndex, bool bCallDelete)
{
bool bRet = false;
SwDoc& rDoc = rRedline.GetDoc();
SwPaM aPam(*rRedline.Start(), *rRedline.End());
bRet |= lcl_RejectRedline(rRedlines, rRedlineIndex, bCallDelete);
// Handles undo/redo itself.
rDoc.getIDocumentContentOperations().DeleteRange(aPam);
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* pStart = rPam.Start(),
* pEnd = rPam.End();
const SwRangeRedline* pFnd = rArr.FindAtPosition( *pStart, n );
if( pFnd && // Is new a part of it?
( *pFnd->Start() != *pStart || *pFnd->End() > *pEnd ))
{
// Only revoke the partial selection
if( (*fn_AcceptReject)( rArr, n, bCallDelete, pStart, 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, pStart, 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.
auto [pStart, pEnd] = rPam.StartEnd(); // SwPosition*
SwDoc& rDoc = rPam.GetDoc();
if( !pStart->GetContentIndex() &&
!rDoc.GetNodes()[ pStart->GetNodeIndex() - 1 ]->IsContentNode() )
{
const SwRangeRedline* pRedl = rDoc.getIDocumentRedlineAccess().GetRedline( *pStart, nullptr );
if( pRedl )
{
const SwPosition* pRStt = pRedl->Start();
if( !pRStt->GetContentIndex() && pRStt->GetNodeIndex() ==
pStart->GetNodeIndex() - 1 )
*pStart = *pRStt;
}
}
if( pEnd->GetNode().IsContentNode() &&
!rDoc.GetNodes()[ pEnd->GetNodeIndex() + 1 ]->IsContentNode() &&
pEnd->GetContentIndex() == pEnd->GetNode().GetContentNode()->Len() )
{
const SwRangeRedline* pRedl = rDoc.getIDocumentRedlineAccess().GetRedline( *pEnd, nullptr );
if( pRedl )
{
const SwPosition* pREnd = pRedl->End();
if( !pREnd->GetContentIndex() && pREnd->GetNodeIndex() ==
pEnd->GetNodeIndex() + 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<SwUnoCursor> 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()->Assign(rDoc.GetNodes().GetEndOfContent());
}
m_rRedline.GetPoint()->Assign(rDoc.GetNodes().GetEndOfContent());
}
~TemporaryRedlineUpdater()
{
static_cast<SwPaM&>(m_rRedline) = *m_pCursor;
}
};
/// Decides if it's OK to combine two types of redlines next to each other, e.g. insert and
/// delete-on-insert can be combined if accepting an insert.
bool CanCombineTypesForAcceptReject(SwRedlineData& rInnerData, SwRangeRedline& rOuterRedline)
{
if (rInnerData.GetType() == RedlineType::Delete)
{
// Delete is OK to have 'format' on it, but 'insert' will be next to the 'delete'.
return rOuterRedline.GetType() == RedlineType::Format
&& rOuterRedline.GetStackCount() > 1
&& rOuterRedline.GetType(1) == RedlineType::Delete;
}
if (rInnerData.GetType() != RedlineType::Insert)
{
return false;
}
switch (rOuterRedline.GetType())
{
case RedlineType::Delete:
case RedlineType::Format:
break;
default:
return false;
}
if (rOuterRedline.GetStackCount() <= 1)
{
return false;
}
if (rOuterRedline.GetType(1) != RedlineType::Insert)
{
return false;
}
return true;
}
/// Decides if it's OK to combine this rInnerData having 2 types with an outer rOuterRedline for
/// accept or reject purposes. E.g. format-on-delete and delete can be combined if accepting a
/// delete.
bool CanReverseCombineTypesForAcceptReject(SwRangeRedline& rOuterRedline, SwRedlineData& rInnerData)
{
switch (rOuterRedline.GetType())
{
case RedlineType::Insert:
case RedlineType::Delete:
break;
default:
return false;
}
if (rInnerData.GetType() != RedlineType::Format)
{
return false;
}
const SwRedlineData* pInnerDataNext = rInnerData.Next();
if (!pInnerDataNext)
{
return false;
}
switch (pInnerDataNext->GetType())
{
case RedlineType::Insert:
case RedlineType::Delete:
return pInnerDataNext->GetType() == rOuterRedline.GetType();
default:
return false;
}
}
}
namespace sw
{
DocumentRedlineManager::DocumentRedlineManager(SwDoc& i_rSwdoc)
: m_rDoc(i_rSwdoc)
, meRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)
, mbIsRedlineMove(false)
, mnAutoFormatRedlnCommentNo(0)
{
}
RedlineFlags DocumentRedlineManager::GetRedlineFlags(const SwViewShell* pViewShell) const
{
if (!pViewShell)
{
SwDocShell* pDocShell = m_rDoc.GetDocShell();
if (pDocShell)
{
pViewShell = pDocShell->GetWrtShell();
}
}
RedlineFlags eRedlineFlags = meRedlineFlags;
if (pViewShell)
{
// Recording can be per-view, the rest is per-document.
eRedlineFlags = eRedlineFlags & ~RedlineFlags::On;
if (pViewShell->GetViewOptions()->IsRedlineRecordingOn())
{
eRedlineFlags |= RedlineFlags::On;
}
}
return eRedlineFlags;
}
void DocumentRedlineManager::SetRedlineFlags( RedlineFlags eMode, SfxRedlineRecordingMode eRedlineRecordingMode, bool bRecordModeChange )
{
if( GetRedlineFlags() == eMode && !bRecordModeChange )
return;
if( (RedlineFlags::ShowMask & GetRedlineFlags()) != (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<SwRootFrame *> 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<SwRootFrame *> 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(); )
{
SwRangeRedline *const pRedline = maRedlineTable[i];
(pRedline->*pFnc)(nLoop, i, false);
// a previous redline may have been deleted
if (i < maRedlineTable.size() && maRedlineTable[i] == pRedline)
++i;
}
//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 );
}
SetRedlineFlags_intern(eMode, eRedlineRecordingMode, bRecordModeChange);
m_rDoc.getIDocumentState().SetModified();
// #TODO - add 'SwExtraRedlineTable' also ?
}
bool DocumentRedlineManager::IsRedlineOn() const
{
return IDocumentRedlineAccess::IsRedlineOn(GetRedlineFlags());
}
bool DocumentRedlineManager::IsIgnoreRedline() const
{
return bool(RedlineFlags::Ignore & GetRedlineFlags());
}
void DocumentRedlineManager::SetRedlineFlags_intern(RedlineFlags eMode, SfxRedlineRecordingMode eRedlineRecordingMode, bool bRecordModeChange)
{
SwDocShell* pDocShell = m_rDoc.GetDocShell();
SwViewShell* pViewShell = pDocShell ? pDocShell->GetWrtShell() : nullptr;
if (pViewShell && eRedlineRecordingMode == SfxRedlineRecordingMode::ViewAgnostic)
{
// Just set the requested flags on the model and on the current view, so setting flags &
// restoring them result in the same state (no matter if that was this-view or all-views).
auto bRedlineRecordingOn = bool(eMode & RedlineFlags::On);
SwViewOption aOpt(*pViewShell->GetViewOptions());
if (aOpt.IsRedlineRecordingOn() != bRedlineRecordingOn)
{
aOpt.SetRedlineRecordingOn(bRedlineRecordingOn);
pViewShell->ApplyViewOptions(aOpt);
}
}
else if (pViewShell)
{
bool bRecordAllViews = eRedlineRecordingMode == SfxRedlineRecordingMode::AllViews;
// Recording may be per-view, the rest is per-document.
for(SwViewShell& rSh : pViewShell->GetRingContainer())
{
auto bRedlineRecordingOn = bool(eMode & RedlineFlags::On);
SwViewOption aOpt(*rSh.GetViewOptions());
bool bOn = aOpt.IsRedlineRecordingOn();
if (bRedlineRecordingOn)
{
// We'll want some kind of recording enabled.
if (bRecordAllViews)
{
// Enable for all views: turn it on everywhere.
bOn = true;
}
else
{
// Enable it for this view was requested.
if (bRecordModeChange)
{
// Transitioning from "all views" to "this view", turn it off everywhere
// except in this view.
bOn = &rSh == pViewShell;
}
else if (&rSh == pViewShell)
{
// Transitioning from "no record": just touch the current view, leave
// others unchanged.
bOn = true;
}
}
}
else
{
// Disable everywhere.
bOn = false;
}
if (aOpt.IsRedlineRecordingOn() != bOn)
{
aOpt.SetRedlineRecordingOn(bOn);
rSh.ApplyViewOptions(aOpt);
}
}
}
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
{
if (&rNode.GetNodes() != &m_rDoc.GetNodes())
return false;
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;
}
/// Data shared between DocumentRedlineManager::AppendRedline() and PreAppendInsertRedline().
class AppendRedlineContext
{
public:
SwRangeRedline*& pNewRedl;
SwPosition*& pStart;
SwPosition*& pEnd;
SwRangeRedline*& pRedl;
SwPosition*& pRStt;
SwPosition*& pREnd;
const SwComparePosition eCmpPos;
SwRedlineTable::size_type& n;
bool& bMerged;
bool& bDec;
bool& bCompress;
const bool bCallDelete;
const sal_uInt32 nMoveIDToDelete;
std::set<sal_uInt32>& deletedMoveIDs;
};
void DocumentRedlineManager::PreAppendForeignRedline(AppendRedlineContext& rCtx)
{
// 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( rCtx.eCmpPos )
{
case SwComparePosition::Equal:
{
rCtx.pRedl->PushData( *rCtx.pNewRedl );
delete rCtx.pNewRedl;
rCtx.pNewRedl = nullptr;
if( IsHideChanges( GetRedlineFlags() ))
{
rCtx.pRedl->Hide(0, maRedlineTable.GetPos(rCtx.pRedl));
}
rCtx.bCompress = true;
if (rCtx.pNewRedl && rCtx.pNewRedl->GetType() == RedlineType::Delete)
{
// set IsMoved checking nearby redlines
SwRedlineTable::size_type nRIdx = maRedlineTable.GetPos(rCtx.pRedl);
if (nRIdx < maRedlineTable.size()) // in case above 're-insert' failed
maRedlineTable.isMoved(nRIdx);
}
}
break;
case SwComparePosition::Inside:
{
if( *rCtx.pRStt == *rCtx.pStart )
{
// #i97421#
// redline w/out extent loops
if (*rCtx.pStart != *rCtx.pEnd)
{
rCtx.pNewRedl->PushData( *rCtx.pRedl, false );
rCtx.pRedl->SetStart( *rCtx.pEnd, rCtx.pRStt );
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
rCtx.bDec = true;
}
}
else
{
rCtx.pNewRedl->PushData( *rCtx.pRedl, false );
if( *rCtx.pREnd != *rCtx.pEnd )
{
pNew = new SwRangeRedline( *rCtx.pRedl );
pNew->SetStart( *rCtx.pEnd );
}
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
if( !rCtx.pRedl->HasValidRange() )
{
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
}
}
}
break;
case SwComparePosition::Outside:
{
rCtx.pRedl->PushData( *rCtx.pNewRedl );
if( *rCtx.pEnd == *rCtx.pREnd )
{
rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
}
else if (*rCtx.pStart == *rCtx.pRStt)
{
rCtx.pNewRedl->SetStart(*rCtx.pREnd, rCtx.pStart);
}
else
{
pNew = new SwRangeRedline( *rCtx.pNewRedl );
pNew->SetEnd( *rCtx.pRStt );
rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
}
rCtx.bCompress = true;
}
break;
case SwComparePosition::OverlapBefore:
{
if( *rCtx.pEnd == *rCtx.pREnd )
{
rCtx.pRedl->PushData( *rCtx.pNewRedl );
rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
if( IsHideChanges( GetRedlineFlags() ))
{
maRedlineTable.Insert(rCtx.pNewRedl);
rCtx.pRedl->Hide(0, maRedlineTable.GetPos(rCtx.pRedl));
maRedlineTable.Remove( rCtx.pNewRedl );
}
}
else
{
pNew = new SwRangeRedline( *rCtx.pRedl );
pNew->PushData( *rCtx.pNewRedl );
pNew->SetEnd( *rCtx.pEnd );
rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
rCtx.pRedl->SetStart( *pNew->End(), rCtx.pRStt ) ;
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl );
rCtx.bDec = true;
}
}
break;
case SwComparePosition::OverlapBehind:
{
if( *rCtx.pStart == *rCtx.pRStt )
{
rCtx.pRedl->PushData( *rCtx.pNewRedl );
rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
if( IsHideChanges( GetRedlineFlags() ))
{
maRedlineTable.Insert( rCtx.pNewRedl );
rCtx.pRedl->Hide(0, maRedlineTable.GetPos(rCtx.pRedl));
maRedlineTable.Remove( rCtx.pNewRedl );
}
}
else
{
pNew = new SwRangeRedline( *rCtx.pRedl );
pNew->PushData( *rCtx.pNewRedl );
pNew->SetStart( *rCtx.pStart );
rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
rCtx.pRedl->SetEnd( *pNew->Start(), rCtx.pREnd );
if( !rCtx.pRedl->HasValidRange() )
{
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.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)
rCtx.n = 0;
rCtx.bDec = true;
}
}
void DocumentRedlineManager::PreAppendInsertRedline(AppendRedlineContext& rCtx)
{
switch( rCtx.pRedl->GetType() )
{
case RedlineType::Insert:
if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
// don't join inserted characters with moved text
!rCtx.pRedl->IsMoved() )
{
bool bDelete = false;
bool bMaybeNotify = false;
// Merge if applicable?
if( (( SwComparePosition::Behind == rCtx.eCmpPos &&
IsPrevPos( *rCtx.pREnd, *rCtx.pStart ) ) ||
( SwComparePosition::CollideStart == rCtx.eCmpPos ) ||
( SwComparePosition::OverlapBehind == rCtx.eCmpPos ) ) &&
rCtx.pRedl->CanCombine( *rCtx.pNewRedl ) &&
( rCtx.n+1 >= maRedlineTable.size() ||
( *maRedlineTable[ rCtx.n+1 ]->Start() >= *rCtx.pEnd &&
*maRedlineTable[ rCtx.n+1 ]->Start() != *rCtx.pREnd ) ) )
{
rCtx.pRedl->SetEnd( *rCtx.pEnd, rCtx.pREnd );
if( !rCtx.pRedl->HasValidRange() )
{
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl );
}
rCtx.bMerged = true;
bDelete = true;
}
else if( (( SwComparePosition::Before == rCtx.eCmpPos &&
IsPrevPos( *rCtx.pEnd, *rCtx.pRStt ) ) ||
( SwComparePosition::CollideEnd == rCtx.eCmpPos ) ||
( SwComparePosition::OverlapBefore == rCtx.eCmpPos ) ) &&
rCtx.pRedl->CanCombine( *rCtx.pNewRedl ) &&
( !rCtx.n ||
*maRedlineTable[ rCtx.n-1 ]->End() != *rCtx.pRStt ))
{
rCtx.pRedl->SetStart( *rCtx.pStart, rCtx.pRStt );
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl );
rCtx.bMerged = true;
bDelete = true;
}
else if ( SwComparePosition::Outside == rCtx.eCmpPos )
{
// own insert-over-insert redlines:
// just scrap the inside ones
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
}
else if( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
{
*rCtx.pStart = *rCtx.pREnd;
if( ( *rCtx.pStart == *rCtx.pEnd ) &&
( rCtx.pNewRedl->GetContentIdx() == nullptr ) )
bDelete = bMaybeNotify = true;
}
else if( SwComparePosition::OverlapBefore == rCtx.eCmpPos )
{
*rCtx.pEnd = *rCtx.pRStt;
if( ( *rCtx.pStart == *rCtx.pEnd ) &&
( rCtx.pNewRedl->GetContentIdx() == nullptr ) )
bDelete = bMaybeNotify = true;
}
else if( SwComparePosition::Inside == rCtx.eCmpPos )
{
bDelete = bMaybeNotify = true;
rCtx.bMerged = true;
}
else if( SwComparePosition::Equal == rCtx.eCmpPos )
bDelete = bMaybeNotify = true;
if( bDelete )
{
delete rCtx.pNewRedl;
rCtx.pNewRedl = nullptr;
rCtx.bCompress = true;
if (bMaybeNotify)
MaybeNotifyRedlineModification(*rCtx.pRedl, m_rDoc);
// set IsMoved checking nearby redlines
if (rCtx.n < maRedlineTable.size()) // in case above 're-insert' failed
maRedlineTable.isMoved(rCtx.n);
}
}
else if( SwComparePosition::Inside == rCtx.eCmpPos )
{
// split up
if( *rCtx.pEnd != *rCtx.pREnd )
{
SwRangeRedline* pCpy = new SwRangeRedline( *rCtx.pRedl );
pCpy->SetStart( *rCtx.pEnd );
maRedlineTable.Insert( pCpy );
}
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
if( ( *rCtx.pStart == *rCtx.pRStt ) &&
( rCtx.pRedl->GetContentIdx() == nullptr ) )
{
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
}
else if( !rCtx.pRedl->HasValidRange() )
{
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl );
}
}
else if ( SwComparePosition::Outside == rCtx.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( *rCtx.pNewRedl );
pSplit->SetEnd( *rCtx.pRStt );
rCtx.pNewRedl->SetStart( *rCtx.pREnd );
maRedlineTable.Insert( pSplit );
if( *rCtx.pStart == *rCtx.pEnd && rCtx.pNewRedl->GetContentIdx() == nullptr )
{
delete rCtx.pNewRedl;
rCtx.pNewRedl = nullptr;
rCtx.bCompress = true;
}
}
else if ( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
{
// handle overlapping redlines in broken documents
rCtx.pNewRedl->SetStart( *rCtx.pREnd );
}
else if ( SwComparePosition::OverlapBefore == rCtx.eCmpPos )
{
// handle overlapping redlines in broken documents
*rCtx.pEnd = *rCtx.pRStt;
if( ( *rCtx.pStart == *rCtx.pEnd ) &&
( rCtx.pNewRedl->GetContentIdx() == nullptr ) )
{
delete rCtx.pNewRedl;
rCtx.pNewRedl = nullptr;
rCtx.bCompress = true;
MaybeNotifyRedlineModification(*rCtx.pRedl, m_rDoc);
}
}
break;
case RedlineType::Delete:
if( SwComparePosition::Inside == rCtx.eCmpPos )
{
// split up
if( *rCtx.pEnd != *rCtx.pREnd )
{
SwRangeRedline* pCpy = new SwRangeRedline( *rCtx.pRedl );
pCpy->SetStart( *rCtx.pEnd );
maRedlineTable.Insert( pCpy );
}
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
if( ( *rCtx.pStart == *rCtx.pRStt ) &&
( rCtx.pRedl->GetContentIdx() == nullptr ) )
{
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
}
else if( !rCtx.pRedl->HasValidRange() )
{
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
}
}
else if ( SwComparePosition::Outside == rCtx.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( *rCtx.pNewRedl );
pSplit->SetEnd( *rCtx.pRStt );
rCtx.pNewRedl->SetStart( *rCtx.pREnd );
maRedlineTable.Insert( pSplit );
if( *rCtx.pStart == *rCtx.pEnd && rCtx.pNewRedl->GetContentIdx() == nullptr )
{
delete rCtx.pNewRedl;
rCtx.pNewRedl = nullptr;
rCtx.bCompress = true;
}
}
else if ( SwComparePosition::Equal == rCtx.eCmpPos )
{
// handle identical redlines in broken documents
// delete old (delete) redline
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
}
else if ( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
{ // Another workaround for broken redlines
rCtx.pNewRedl->SetStart( *rCtx.pREnd );
}
break;
case RedlineType::Format:
switch( rCtx.eCmpPos )
{
case SwComparePosition::OverlapBefore:
rCtx.pRedl->SetStart( *rCtx.pEnd, rCtx.pRStt );
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
rCtx.bDec = true;
break;
case SwComparePosition::OverlapBehind:
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
if( *rCtx.pStart == *rCtx.pRStt && rCtx.pRedl->GetContentIdx() == nullptr )
{
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.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( rCtx.n );
rCtx.bDec = true;
break;
case SwComparePosition::Inside:
// Overlaps the current one completely,
// split or shorten the new one
if( *rCtx.pEnd != *rCtx.pREnd )
{
if( *rCtx.pEnd != *rCtx.pRStt )
{
SwRangeRedline* pNew = new SwRangeRedline( *rCtx.pRedl );
pNew->SetStart( *rCtx.pEnd );
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
if( *rCtx.pStart == *rCtx.pRStt && rCtx.pRedl->GetContentIdx() == nullptr )
maRedlineTable.DeleteAndDestroy( rCtx.n );
AppendRedline( pNew, rCtx.bCallDelete );
rCtx.n = 0; // re-initialize
rCtx.bDec = true;
}
}
else
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
break;
default:
break;
}
break;
default:
break;
}
}
void DocumentRedlineManager::PreAppendDeleteRedline(AppendRedlineContext& rCtx)
{
RedlineType eRedlType = rCtx.pRedl->GetType();
bool bHierarchicalFormat = rCtx.pRedl->GetType() == RedlineType::Format && rCtx.pRedl->GetStackCount() > 1;
if (bHierarchicalFormat && rCtx.pRedl->GetType(1) == RedlineType::Insert)
{
eRedlType = rCtx.pRedl->GetType(1);
}
switch (eRedlType)
{
case RedlineType::Delete:
switch( rCtx.eCmpPos )
{
case SwComparePosition::Outside:
{
// Overlaps the current one completely,
// split the new one
if (*rCtx.pEnd == *rCtx.pREnd)
{
rCtx.pNewRedl->SetEnd(*rCtx.pRStt, rCtx.pEnd);
}
else if (*rCtx.pStart == *rCtx.pRStt)
{
rCtx.pNewRedl->SetStart(*rCtx.pREnd, rCtx.pStart);
}
else
{
SwRangeRedline* pNew = new SwRangeRedline( *rCtx.pNewRedl );
pNew->SetStart( *rCtx.pREnd );
rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
AppendRedline( pNew, rCtx.bCallDelete );
rCtx.n = 0; // re-initialize
rCtx.bDec = true;
}
}
break;
case SwComparePosition::Inside:
case SwComparePosition::Equal:
delete rCtx.pNewRedl;
rCtx.pNewRedl = nullptr;
rCtx.bCompress = true;
MaybeNotifyRedlineModification(*rCtx.pRedl, m_rDoc);
break;
case SwComparePosition::OverlapBefore:
case SwComparePosition::OverlapBehind:
if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
rCtx.pRedl->CanCombine( *rCtx.pNewRedl ))
{
// If that's the case we can merge it, meaning
// the new one covers this well
if( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
rCtx.pNewRedl->SetStart( *rCtx.pRStt, rCtx.pStart );
else
rCtx.pNewRedl->SetEnd( *rCtx.pREnd, rCtx.pEnd );
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
}
else if( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
else
rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
break;
case SwComparePosition::CollideEnd:
if (rCtx.pRStt->GetContentIndex() != 0
&& rCtx.pRStt->GetNode() != rCtx.pREnd->GetNode())
{ // 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( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
rCtx.pRedl->CanCombine( *rCtx.pNewRedl ) )
{
if( IsHideChanges( GetRedlineFlags() ))
{
// Before we can merge, we make it visible!
// We insert temporarily so that pNew is
// also dealt with when moving the indices.
maRedlineTable.Insert(rCtx.pNewRedl);
rCtx.pRedl->Show(0, maRedlineTable.GetPos(rCtx.pRedl));
maRedlineTable.Remove( rCtx.pNewRedl );
rCtx.pRStt = rCtx.pRedl->Start();
rCtx.pREnd = rCtx.pRedl->End();
}
// If that's the case we can merge it, meaning
// the new one covers this well
if( SwComparePosition::CollideStart == rCtx.eCmpPos )
rCtx.pNewRedl->SetStart( *rCtx.pRStt, rCtx.pStart );
else
rCtx.pNewRedl->SetEnd( *rCtx.pREnd, rCtx.pEnd );
// delete current (below), and restart process with
// previous
SwRedlineTable::size_type nToBeDeleted = rCtx.n;
rCtx.bDec = true;
if( *(rCtx.pNewRedl->Start()) <= *rCtx.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.
rCtx.n = 0;
}
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 = GetRedlineFlags();
if( !( eOld & RedlineFlags::DontCombineRedlines ) &&
rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
// tdf#116084 tdf#121176 don't combine anonymized deletion
// and anonymized insertion, i.e. with the same dummy timestamp
!rCtx.pRedl->GetRedlineData(0).IsAnonymized() )
{
// Collect MoveID's of the redlines we delete.
if (rCtx.nMoveIDToDelete > 1 && maRedlineTable[rCtx.n]->GetMoved() > 0
&& (rCtx.eCmpPos == SwComparePosition::Equal
|| rCtx.eCmpPos == SwComparePosition::Inside
|| rCtx.eCmpPos == SwComparePosition::Outside
|| rCtx.eCmpPos == SwComparePosition::OverlapBefore
|| rCtx.eCmpPos == SwComparePosition::OverlapBehind))
{
rCtx.deletedMoveIDs.insert(maRedlineTable[rCtx.n]->GetMoved());
}
// Set to NONE, so that the Delete::Redo merges the Redline data correctly!
// The ShowMode needs to be retained!
SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));
switch( rCtx.eCmpPos )
{
case SwComparePosition::Equal:
rCtx.bCompress = true;
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
[[fallthrough]];
case SwComparePosition::Inside:
if( rCtx.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( (rCtx.pStart->GetContentIndex() == 0) &&
rCtx.pEnd->GetNode().IsEndNode() )
{
rCtx.pEnd->Adjust(SwNodeOffset(-1));
m_rDoc.getIDocumentContentOperations().DelFullPara( *rCtx.pNewRedl );
}
else
m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *rCtx.pNewRedl );
rCtx.bCompress = true;
}
delete rCtx.pNewRedl;
rCtx.pNewRedl = nullptr;
// No need to call MaybeNotifyRedlineModification, because a notification
// was already sent in DocumentRedlineManager::DeleteRedline
break;
case SwComparePosition::Outside:
{
maRedlineTable.Remove( rCtx.n );
rCtx.bDec = true;
if( rCtx.bCallDelete )
{
TemporaryRedlineUpdater const u(m_rDoc, *rCtx.pNewRedl);
m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *rCtx.pRedl );
rCtx.n = 0; // re-initialize
}
delete rCtx.pRedl;
}
break;
case SwComparePosition::OverlapBefore:
{
SwPaM aPam( *rCtx.pRStt, *rCtx.pEnd );
if( *rCtx.pEnd == *rCtx.pREnd )
maRedlineTable.DeleteAndDestroy( rCtx.n );
else
{
rCtx.pRedl->SetStart( *rCtx.pEnd, rCtx.pRStt );
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
}
if( rCtx.bCallDelete )
{
TemporaryRedlineUpdater const u(m_rDoc, *rCtx.pNewRedl);
m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam );
rCtx.n = 0; // re-initialize
}
rCtx.bDec = true;
}
break;
case SwComparePosition::OverlapBehind:
{
SwPaM aPam( *rCtx.pStart, *rCtx.pREnd );
if( *rCtx.pStart == *rCtx.pRStt )
{
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
}
else
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
if( rCtx.bCallDelete )
{
TemporaryRedlineUpdater const u(m_rDoc, *rCtx.pNewRedl);
m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam );
rCtx.n = 0; // re-initialize
rCtx.bDec = true;
}
}
break;
default:
break;
}
SetRedlineFlags_intern(eOld);
}
else
{
PreAppendForeignRedline(rCtx);
}
}
break;
case RedlineType::Format:
switch( rCtx.eCmpPos )
{
case SwComparePosition::OverlapBefore:
rCtx.pRedl->SetStart( *rCtx.pEnd, rCtx.pRStt );
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
rCtx.bDec = true;
break;
case SwComparePosition::OverlapBehind:
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
break;
case SwComparePosition::Equal:
case SwComparePosition::Outside:
// Overlaps the current one completely or has the
// same dimension, delete the old one
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
break;
case SwComparePosition::Inside:
// Overlaps the current one completely,
// split or shorten the new one
if( *rCtx.pEnd != *rCtx.pREnd )
{
if( *rCtx.pEnd != *rCtx.pRStt )
{
// At this point, rCtx.pRedl is the old format redline and rCtx.pNewRedl is the
// new delete redline. Make sure that when this pNewRedl gets appended, it still
// has the formatting redline data from rCtx.pRedl / pNew, and format is on top
// of delete.
rCtx.pNewRedl->PushData(*rCtx.pRedl);
SwRangeRedline* pNew = new SwRangeRedline( *rCtx.pRedl );
pNew->SetStart( *rCtx.pEnd );
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
if( ( *rCtx.pStart == *rCtx.pRStt ) &&
( rCtx.pRedl->GetContentIdx() == nullptr ) )
maRedlineTable.DeleteAndDestroy( rCtx.n );
AppendRedline( pNew, rCtx.bCallDelete );
rCtx.n = 0; // re-initialize
rCtx.bDec = true;
}
}
else
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
break;
default:
break;
}
break;
default:
break;
}
}
void DocumentRedlineManager::PreAppendFormatRedline(AppendRedlineContext& rCtx)
{
switch( rCtx.pRedl->GetType() )
{
case RedlineType::Insert:
case RedlineType::Delete:
{
RedlineFlags eOld = GetRedlineFlags();
bool bCombineRedlines = !(eOld & RedlineFlags::DontCombineRedlines)
&& rCtx.pRedl->IsOwnRedline(*rCtx.pNewRedl)
&& !rCtx.pRedl->GetRedlineData(0).IsAnonymized();
if (bCombineRedlines && rCtx.pRedl->GetType() == RedlineType::Delete
&& rCtx.pNewRedl->GetType() == RedlineType::Format)
{
// Don't fold format on top of delete into the delete, that would give an incorrect
// result when rejecting this redline, the format would remain in the doc model.
bCombineRedlines = false;
}
if (bCombineRedlines || rCtx.pRedl->IsMoved())
{
switch( rCtx.eCmpPos )
{
case SwComparePosition::OverlapBefore:
rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
break;
case SwComparePosition::OverlapBehind:
rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
break;
case SwComparePosition::Inside:
if (*rCtx.pRStt < *rCtx.pStart && *rCtx.pREnd == *rCtx.pEnd)
{
// pRedl start is before pNewRedl start, the ends match: then create the
// format on top of insert/delete & reduce the end of the original
// insert/delete to avoid an overlap.
rCtx.pNewRedl->PushData(*rCtx.pRedl, false);
rCtx.pRedl->SetEnd(*rCtx.pStart);
rCtx.n = 0;
rCtx.bDec = true;
break;
}
[[fallthrough]];
case SwComparePosition::Equal:
delete rCtx.pNewRedl;
rCtx.pNewRedl = nullptr;
MaybeNotifyRedlineModification(*rCtx.pRedl, m_rDoc);
break;
case SwComparePosition::Outside:
// Overlaps the current one completely,
// split or shorten the new one
if (*rCtx.pEnd == *rCtx.pREnd)
{
rCtx.pNewRedl->SetEnd(*rCtx.pRStt, rCtx.pEnd);
}
else if (*rCtx.pStart == *rCtx.pRStt)
{
rCtx.pNewRedl->SetStart(*rCtx.pREnd, rCtx.pStart);
}
else
{
SwRangeRedline* pNew = new SwRangeRedline( *rCtx.pNewRedl );
pNew->SetStart( *rCtx.pREnd );
rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
AppendRedline( pNew, rCtx.bCallDelete );
rCtx.n = 0; // re-initialize
rCtx.bDec = true;
}
break;
default:
break;
}
}
else
{
PreAppendForeignRedline(rCtx);
}
break;
}
case RedlineType::Format:
switch( rCtx.eCmpPos )
{
case SwComparePosition::Outside:
case SwComparePosition::Equal:
{
// Overlaps the current one completely or has the
// same dimension, delete the old one
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
}
break;
case SwComparePosition::Inside:
if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
rCtx.pRedl->CanCombine( *rCtx.pNewRedl ))
{
// own one can be ignored completely
delete rCtx.pNewRedl;
rCtx.pNewRedl = nullptr;
MaybeNotifyRedlineModification(*rCtx.pRedl, m_rDoc);
}
else if( *rCtx.pREnd == *rCtx.pEnd )
// or else only shorten the current one
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
else if( *rCtx.pRStt == *rCtx.pStart )
{
// or else only shorten the current one
rCtx.pRedl->SetStart( *rCtx.pEnd, rCtx.pRStt );
// re-insert
maRedlineTable.Remove( rCtx.n );
maRedlineTable.Insert( rCtx.pRedl, rCtx.n );
rCtx.bDec = true;
}
else
{
// If it lies completely within the current one
// we need to split it
SwRangeRedline* pNew = new SwRangeRedline( *rCtx.pRedl );
pNew->SetStart( *rCtx.pEnd );
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd );
AppendRedline( pNew, rCtx.bCallDelete );
rCtx.n = 0; // re-initialize
rCtx.bDec = true;
}
break;
case SwComparePosition::OverlapBefore:
case SwComparePosition::OverlapBehind:
if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
rCtx.pRedl->CanCombine( *rCtx.pNewRedl ))
{
// If that's the case we can merge it, meaning
// the new one covers this well
if( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
rCtx.pNewRedl->SetStart( *rCtx.pRStt, rCtx.pStart );
else
rCtx.pNewRedl->SetEnd( *rCtx.pREnd, rCtx.pEnd );
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = false;
}
else if( SwComparePosition::OverlapBehind == rCtx.eCmpPos )
rCtx.pNewRedl->SetStart( *rCtx.pREnd, rCtx.pStart );
else
rCtx.pNewRedl->SetEnd( *rCtx.pRStt, rCtx.pEnd );
break;
case SwComparePosition::CollideEnd:
if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
rCtx.pRedl->CanCombine( *rCtx.pNewRedl ) &&
(rCtx.n == 0 || *maRedlineTable[ rCtx.n-1 ]->End() < *rCtx.pStart))
{
// If that's the case we can merge it, meaning
// the new one covers this well
rCtx.pNewRedl->SetEnd( *rCtx.pREnd, rCtx.pEnd );
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
}
break;
case SwComparePosition::CollideStart:
if( rCtx.pRedl->IsOwnRedline( *rCtx.pNewRedl ) &&
rCtx.pRedl->CanCombine( *rCtx.pNewRedl ) &&
(rCtx.n+1 >= maRedlineTable.size() ||
(*maRedlineTable[ rCtx.n+1 ]->Start() >= *rCtx.pEnd &&
*maRedlineTable[ rCtx.n+1 ]->Start() != *rCtx.pREnd)))
{
// If that's the case we can merge it, meaning
// the new one covers this well
rCtx.pNewRedl->SetStart( *rCtx.pRStt, rCtx.pStart );
maRedlineTable.DeleteAndDestroy( rCtx.n );
rCtx.bDec = true;
}
break;
default:
break;
}
break;
default:
break;
}
}
/*
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,
sal_uInt32 nMoveIDToDelete)
{
CHECK_REDLINE( *this )
if (!IsRedlineOn() || IsShowOriginal(GetRedlineFlags()))
{
if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() )
{
RedlineFlags eOld = GetRedlineFlags();
// Set to NONE, so that the Delete::Redo merges the Redline data correctly!
// The ShowMode needs to be retained!
SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore));
m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl );
SetRedlineFlags_intern(eOld);
}
delete pNewRedl;
pNewRedl = nullptr;
CHECK_REDLINE( *this )
return AppendResult::IGNORED;
}
// Collect MoveID's of the redlines we delete.
// If there is only 1, then we should use its ID. (continuing the move)
std::set<sal_uInt32> deletedMoveIDs;
bool bMerged = false;
pNewRedl->InvalidateRange(SwRangeRedline::Invalidation::Add);
if( m_rDoc.IsAutoFormatRedline() )
{
pNewRedl->SetAutoFormat();
if( moAutoFormatRedlnComment && !moAutoFormatRedlnComment->isEmpty() )
{
pNewRedl->SetComment( *moAutoFormatRedlnComment );
pNewRedl->SetSeqNo( mnAutoFormatRedlnCommentNo );
}
}
auto [pStart, pEnd] = pNewRedl->StartEnd(); // SwPosition*
{
SwTextNode* pTextNode = pStart->GetNode().GetTextNode();
if( pTextNode == nullptr )
{
if( pStart->GetContentIndex() > 0 )
{
OSL_ENSURE( false, "Redline start: non-text-node with content" );
pStart->SetContent( 0 );
}
}
else
{
if( pStart->GetContentIndex() > pTextNode->Len() )
{
OSL_ENSURE( false, "Redline start: index after text" );
pStart->SetContent( pTextNode->Len() );
}
}
pTextNode = pEnd->GetNode().GetTextNode();
if( pTextNode == nullptr )
{
if( pEnd->GetContentIndex() > 0 )
{
OSL_ENSURE( false, "Redline end: non-text-node with content" );
pEnd->SetContent(0);
}
}
else
{
if( pEnd->GetContentIndex() > pTextNode->Len() )
{
OSL_ENSURE( false, "Redline end: index after text" );
pEnd->SetContent( pTextNode->Len() );
}
}
}
if( ( *pStart == *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( *pStart, &n ) && n > 0 )
--n;
const SwRedlineTable::size_type nStartPos = n;
bool bDec = false;
for( ; pNewRedl && n < maRedlineTable.size(); bDec ? n : ++n )
{
bDec = false;
SwRangeRedline* pRedl = maRedlineTable[ n ];
auto [pRStt, pREnd] = pRedl->StartEnd();
// #i8518# remove empty redlines while we're at it
if( ( *pRStt == *pREnd ) &&
( pRedl->GetContentIdx() == nullptr ) )
{
maRedlineTable.DeleteAndDestroy(n);
continue;
}
SwComparePosition eCmpPos = ComparePosition( *pStart, *pEnd, *pRStt, *pREnd );
if ( SwComparePosition::Before == eCmpPos && !IsPrevPos( *pEnd, *pRStt ))
break;
AppendRedlineContext aContext{ pNewRedl,
pStart,
pEnd,
pRedl,
pRStt,
pREnd,
eCmpPos,
n,
bMerged,
bDec,
bCompress,
bCallDelete,
nMoveIDToDelete,
deletedMoveIDs };
switch (pNewRedl->GetType())
{
case RedlineType::Insert:
PreAppendInsertRedline(aContext);
break;
case RedlineType::Delete:
PreAppendDeleteRedline(aContext);
break;
case RedlineType::Format:
PreAppendFormatRedline(aContext);
break;
case RedlineType::FmtColl:
// How should we behave here?
// insert as is
break;
default:
break;
}
}
if( pNewRedl )
{
if( ( *pStart == *pEnd ) &&
( pNewRedl->GetContentIdx() == nullptr ) )
{ // Do not insert empty redlines
delete pNewRedl;
pNewRedl = nullptr;
}
else
{
if ( bCallDelete && RedlineType::Delete == pNewRedl->GetType() )
{
if ( pStart->GetContentIndex() != 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 = pStart->GetNode().GetContentNode();
// start copying the style of the first paragraph from the end of the range
SwContentNode* pTextNd = pEnd->GetNode().GetContentNode();
SwNodeIndex aIdx( pEnd->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
std::unique_ptr<SwRedlineExtraData_FormatColl> pExtraData = lcl_CopyStyle(aPos, *pStart, false);
if (pExtraData)
{
if (!bFirst)
pExtraData->SetFormatAll(false);
pPar->SetExtraData( pExtraData.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(*pStart, 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 && !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( pStart->GetNode() );
SwNodeIndex aEndIdx( pEnd->GetNode() );
while ( aSttIdx < aEndIdx )
{
if ( aSttIdx.GetNode().IsTableNode() )
{
SvxPrintItem aHasTextChangesOnly(RES_PRINT, false);
SwCursor aCursor( SwPosition(aSttIdx), nullptr );
m_rDoc.SetRowNotTracked( aCursor, aHasTextChangesOnly, /*bAll=*/true );
}
++aSttIdx;
}
}
}
bool const ret = maRedlineTable.Insert( pNewRedl );
assert(ret || !pNewRedl);
if (ret && !pNewRedl)
{
bMerged = true; // treat InsertWithValidRanges as "merge"
}
}
}
// If we deleted moved redlines, and there was only 1 MoveID, then we should use that
// We overwrite those that was given right now, so it cannot be deeper under other redline
if (nMoveIDToDelete > 1 && deletedMoveIDs.size() == 1)
{
sal_uInt32 nNewMoveID = *(deletedMoveIDs.begin());
if (nNewMoveID > 1) // MoveID==1 is for old, unrecognised moves, leave them alone
{
for (n = 0; n < maRedlineTable.size(); ++n)
{
if (maRedlineTable[n]->GetMoved() == nMoveIDToDelete)
{
maRedlineTable[n]->SetMoved(nNewMoveID);
}
}
}
}
if( bCompress )
CompressRedlines(nStartPos);
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(GetRedlineFlags()))
{
// #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 = GetRedlineFlags();
// Set to NONE, so that the Delete::Redo merges the Redline data correctly!
// The ShowMode needs to be retained!
SetRedlineFlags_intern(eOld & ~(RedlineFlags::On | RedlineFlags::Ignore));
DeleteAndJoin( *pNewRedl );
SetRedlineFlags_intern(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(GetRedlineFlags()))
{
// #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 = GetRedlineFlags();
// Set to NONE, so that the Delete::Redo merges the Redline data correctly!
// The ShowMode needs to be retained!
SetRedlineFlags_intern(eOld & ~(RedlineFlags::On | RedlineFlags::Ignore));
DeleteAndJoin( *pNewRedl );
SetRedlineFlags_intern(eOld);
}
delete pNewRedl, pNewRedl = 0;
*/
}
// #TODO - equivalent for 'SwTableCellRedline'
/*
CHECK_REDLINE( this )
*/
return nullptr != pNewRedl;
}
void DocumentRedlineManager::CompressRedlines(size_t nStartIndex)
{
CHECK_REDLINE( *this )
void (SwRangeRedline::*pFnc)(sal_uInt16, size_t, bool) = nullptr;
RedlineFlags eShow = RedlineFlags::ShowMask & GetRedlineFlags();
if( eShow == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete))
pFnc = &SwRangeRedline::Show;
else if (eShow == RedlineFlags::ShowInsert)
pFnc = &SwRangeRedline::Hide;
// Try to merge identical ones
if (nStartIndex == 0)
nStartIndex = 1;
for( SwRedlineTable::size_type n = nStartIndex; n < maRedlineTable.size(); ++n )
{
SwRangeRedline* pPrev = maRedlineTable[ n-1 ],
* pCur = maRedlineTable[ n ];
auto [pPrevStt,pPrevEnd] = pPrev->StartEnd();
auto [pCurStt, pCurEnd] = pCur->StartEnd();
if( *pPrevEnd == *pCurStt && pPrev->CanCombine( *pCur ) &&
pPrevStt->GetNode().StartOfSectionNode() ==
pCurEnd->GetNode().StartOfSectionNode() &&
!pCurEnd->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 )
{
if (maRedlineTable.empty())
return false;
auto [pStart, pEnd] = rRange.StartEnd(); // SwPosition*
// tdf#144208 this happens a lot during load of some DOCX files.
if (*pEnd > maRedlineTable.GetMaxEndPos())
return false;
bool bChg = false;
SwRedlineTable::size_type n = 0;
//FIXME overlapping problem GetRedline( *pStart, &n );
while (n < maRedlineTable.size())
{
SwRangeRedline * pRedline = maRedlineTable[ n ];
auto [pRedlineStart, pRedlineEnd] = pRedline->StartEnd();
if (*pRedlineStart <= *pStart && *pEnd <= *pRedlineEnd)
{
bChg = true;
int nn = 0;
if (*pStart == *pRedlineStart)
nn += 1;
if (*pEnd == *pRedlineEnd)
nn += 2;
SwRangeRedline* pNew = nullptr;
switch( nn )
{
case 0:
pNew = new SwRangeRedline( *pRedline );
pRedline->SetEnd( *pStart, pRedlineEnd );
pNew->SetStart( *pEnd );
break;
case 1:
*pRedlineStart = *pEnd;
break;
case 2:
*pRedlineEnd = *pStart;
break;
case 3:
pRedline->InvalidateRange(SwRangeRedline::Invalidation::Remove);
maRedlineTable.DeleteAndDestroy( n );
// loop again with the same n to iterate to the next entry
pRedline = nullptr;
break;
}
if (pRedline)
{
if (!pRedline->HasValidRange())
{
// re-insert
maRedlineTable.Remove( n );
maRedlineTable.Insert( pRedline, n );
}
if (pNew)
maRedlineTable.Insert(pNew, n);
}
}
else if (*pEnd < *pRedlineStart)
break;
if (pRedline)
++n;
}
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<SwUndoRedline> pUndo(new SwUndoRedline( SwUndoId::REDLINE, rRange ));
if( pUndo->GetRedlSaveCount() )
{
m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
}
}
auto [pStart, pEnd] = rRange.StartEnd(); // SwPosition*
SwRedlineTable::size_type n = 0;
GetRedline( *pStart, &n );
while (n < maRedlineTable.size())
{
SwRangeRedline* pRedl = maRedlineTable[ n ];
if( RedlineType::Any != nDelType && nDelType != pRedl->GetType() )
{
++n;
continue;
}
auto [pRStt, pREnd] = pRedl->StartEnd(); // SwPosition*
switch( ComparePosition( *pStart, *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 );
break;
case SwComparePosition::OverlapBehind:
pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove);
pRedl->SetEnd( *pStart, pREnd );
pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add);
if( !pRedl->HasValidRange() )
{
// re-insert
maRedlineTable.Remove( n );
maRedlineTable.Insert( pRedl );
}
else
++n;
break;
case SwComparePosition::Inside:
{
// this one needs to be split
pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove);
if( *pRStt == *pStart )
{
pRedl->SetStart( *pEnd, pRStt );
pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add);
// re-insert
maRedlineTable.Remove( n );
maRedlineTable.Insert( pRedl );
}
else
{
SwRangeRedline* pCpy;
if( *pREnd != *pEnd )
{
pCpy = new SwRangeRedline( *pRedl );
pCpy->SetStart( *pEnd );
pCpy->InvalidateRange(SwRangeRedline::Invalidation::Add);
}
else
pCpy = nullptr;
pRedl->SetEnd( *pStart, pREnd );
pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add);
if( !pRedl->HasValidRange() )
{
// re-insert
maRedlineTable.Remove( n );
maRedlineTable.Insert( pRedl );
}
else
++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() + 1;
break;
default:
++n;
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()->GetNodeIndex() < nNdIdx;
});
for( ; it != maRedlineTable.end(); ++it)
{
const SwRangeRedline* pTmp = *it;
auto [pStart, pEnd] = pTmp->StartEnd(); // SwPosition*
SwNodeOffset nStart = pStart->GetNodeIndex(),
nEnd = pEnd->GetNodeIndex();
if( ( RedlineType::Any == nType || nType == pTmp->GetType()) &&
nStart <= nNdIdx && nNdIdx <= nEnd )
return std::distance(maRedlineTable.begin(), it);
if( nStart > nNdIdx )
break;
}
}
else
{
for( auto it = maRedlineTable.begin(), itEnd = maRedlineTable.end(); it != itEnd; ++it )
{
const SwRangeRedline* pTmp = *it;
SwNodeOffset nPt = pTmp->GetPoint()->GetNodeIndex(),
nMk = pTmp->GetMark()->GetNodeIndex();
if( nPt < nMk )
std::swap( nMk, nPt );
if( ( RedlineType::Any == nType || nType == pTmp->GetType()) &&
nMk <= nNdIdx && nNdIdx <= nPt )
return std::distance(maRedlineTable.begin(), it);
if( nMk > nNdIdx )
break;
}
}
return SwRedlineTable::npos;
// #TODO - add 'SwExtraRedlineTable' also ?
}
SwRedlineTable::size_type
DocumentRedlineManager::GetRedlineEndPos(SwRedlineTable::size_type nStartPos, const SwNode& rNd,
RedlineType nType) const
{
//if the start is already invalid
if (nStartPos >= maRedlineTable.size())
return nStartPos;
const SwNodeOffset nNdIdx = rNd.GetIndex();
SwRedlineTable::size_type nEndPos = nStartPos;
SwRedlineTable::size_type nEndPosTry = nEndPos + 1;
while (nEndPosTry < maRedlineTable.size()
&& maRedlineTable[nEndPosTry]->Start()->GetNodeIndex() <= nNdIdx)
{
if (RedlineType::Any == nType || nType == maRedlineTable[nEndPosTry]->GetType())
{
nEndPos = nEndPosTry;
}
nEndPosTry++;
}
return nEndPos;
}
void DocumentRedlineManager::UpdateRedlineContentNode(SwRedlineTable::size_type nStartPos,
SwRedlineTable::size_type nEndPos) const
{
for (SwRedlineTable::size_type n = nStartPos; n <= nEndPos; ++n)
{
//just in case we got wrong input
if (n >= maRedlineTable.size())
return;
SwPosition* pStart = maRedlineTable[n]->Start();
SwPosition* pEnd = maRedlineTable[n]->End();
SwContentNode* pCont = pStart->GetNode().GetContentNode();
if (pCont)
{
pStart->nContent.Assign(pCont, pStart->nContent.GetIndex());
}
pCont = pEnd->GetNode().GetContentNode();
if (pCont)
{
pEnd->nContent.Assign(pCont, pEnd->nContent.GetIndex());
}
}
}
void DocumentRedlineManager::dumpAsXml(xmlTextWriterPtr pWriter) const
{
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("DocumentRedlineManager"));
(void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
if (meRedlineFlags != RedlineFlags::NONE)
{
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("meRedlineFlags"));
if (meRedlineFlags & RedlineFlags::On)
{
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("On"), BAD_CAST("true"));
}
if (meRedlineFlags & RedlineFlags::Ignore)
{
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("Ignore"), BAD_CAST("true"));
}
if (meRedlineFlags & RedlineFlags::ShowInsert)
{
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("ShowInsert"), BAD_CAST("true"));
}
if (meRedlineFlags & RedlineFlags::ShowDelete)
{
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("ShowDelete"), BAD_CAST("true"));
}
if (meRedlineFlags & RedlineFlags::DeleteRedlines)
{
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("DeleteRedlines"), BAD_CAST("true"));
}
if (meRedlineFlags & RedlineFlags::DontCombineRedlines)
{
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("DontCombineRedlines"), BAD_CAST("true"));
}
(void)xmlTextWriterEndElement(pWriter);
}
maRedlineTable.dumpAsXml(pWriter);
maExtraRedlineTable.dumpAsXml(pWriter);
(void)xmlTextWriterEndElement(pWriter);
}
bool DocumentRedlineManager::HasRedline( const SwPaM& rPam, RedlineType nType, bool bStartOrEndInRange ) const
{
SwPosition currentStart(*rPam.Start());
SwPosition currentEnd(*rPam.End());
const SwNode& rEndNode(currentEnd.GetNode());
for( SwRedlineTable::size_type n = GetRedlinePos( rPam.Start()->GetNode(), nType );
n < maRedlineTable.size(); ++n )
{
const SwRangeRedline* pTmp = maRedlineTable[ n ];
if ( pTmp->Start()->GetNode() > rEndNode )
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
{
if (maRedlineTable.HasOverlappingElements())
{
for (auto it = maRedlineTable.begin(), itEnd = maRedlineTable.end(); it != itEnd; ++it)
{
auto [pStart, pEnd] = (**it).StartEnd();
if (rPos < *pStart)
{
if (pFndPos)
{
*pFndPos = std::distance(maRedlineTable.begin(), it);
}
return nullptr;
}
if (pEnd == pStart
? *pStart == rPos
: (*pStart <= rPos && rPos < *pEnd))
{
if (pFndPos)
{
*pFndPos = std::distance(maRedlineTable.begin(), it);
}
return *it;
}
}
if (pFndPos)
{
*pFndPos = maRedlineTable.size();
}
return nullptr;
}
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 ];
auto [pStart, pEnd] = pRedl->StartEnd();
if( pEnd == pStart
? *pStart == rPos
: ( *pStart <= 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::AcceptRedlineRange(SwRedlineTable::size_type nPosOrigin,
const SwRedlineTable::size_type& nPosStart,
const SwRedlineTable::size_type& nPosEnd,
bool bCallDelete, bool bDirect)
{
bool bRet = false;
SwRangeRedline* pTmp = maRedlineTable[nPosOrigin];
SwRedlineTable::size_type nRdlIdx = nPosEnd + 1;
SwRedlineData aOrigData = pTmp->GetRedlineData(0);
SwNodeOffset nPamStartNI = maRedlineTable[nPosStart]->Start()->GetNodeIndex();
sal_Int32 nPamStartCI = maRedlineTable[nPosStart]->Start()->GetContentIndex();
SwNodeOffset nPamEndNI = maRedlineTable[nPosEnd]->End()->GetNodeIndex();
sal_Int32 nPamEndCI = maRedlineTable[nPosEnd]->End()->GetContentIndex();
do
{
nRdlIdx--;
pTmp = maRedlineTable[nRdlIdx];
if (pTmp->Start()->GetNodeIndex() < nPamStartNI
|| (pTmp->Start()->GetNodeIndex() == nPamStartNI
&& pTmp->Start()->GetContentIndex() < nPamStartCI))
break;
if (pTmp->End()->GetNodeIndex() > nPamEndNI
|| (pTmp->End()->GetNodeIndex() == nPamEndNI
&& pTmp->End()->GetContentIndex() > nPamEndCI))
{
}
else if (pTmp->GetRedlineData(0).CanCombineForAcceptReject(aOrigData))
{
bool bHierarchicalFormat = pTmp->GetType() == RedlineType::Format && pTmp->GetStackCount() > 1;
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
// Set up the undo action with the correct depth & directness.
sal_Int8 nDepth = 0;
if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Insert && !bDirect)
{
// Only work with the underlying insert, so the undo action matches the UI
// action below.
nDepth = 1;
}
m_rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoAcceptRedline>(*pTmp, nDepth, bDirect));
}
nPamEndNI = pTmp->Start()->GetNodeIndex();
nPamEndCI = pTmp->Start()->GetContentIndex();
if (bHierarchicalFormat && bDirect
&& (pTmp->GetType(1) == RedlineType::Insert
|| pTmp->GetType(1) == RedlineType::Delete))
{
bRet |= lcl_AcceptOuterFormat(maRedlineTable, nRdlIdx);
}
else if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Insert)
{
// This combination of 2 redline types prefers accepting the inner one first.
bRet |= lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1);
}
else if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Delete)
{
// Get rid of the format itself and then accept the delete by deleting the range.
bRet |= lcl_AcceptInnerDelete(*pTmp, maRedlineTable, nRdlIdx, bCallDelete);
}
else
{
bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
}
nRdlIdx++; //we will decrease it in the loop anyway.
}
else if (CanCombineTypesForAcceptReject(aOrigData, *pTmp)
&& pTmp->GetRedlineData(1).CanCombineForAcceptReject(aOrigData))
{
// The Insert/Delete redline we want to accept has another type of redline too
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
m_rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoAcceptRedline>(*pTmp, 1));
}
nPamEndNI = pTmp->Start()->GetNodeIndex();
nPamEndCI = pTmp->Start()->GetContentIndex();
if (aOrigData.GetType() == RedlineType::Delete)
{
// We should delete the other type of redline when accepting the inner delete.
bRet |= lcl_AcceptInnerDelete(*pTmp, maRedlineTable, nRdlIdx, bCallDelete);
}
else
{
// we should leave the other type of redline, and only accept the inner insert.
bRet |= lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1);
}
nRdlIdx++; //we will decrease it in the loop anyway.
}
else if (CanReverseCombineTypesForAcceptReject(*pTmp, aOrigData) && !bDirect)
{
// The aOrigData has 2 types and for these types we want the underlying type to be
// combined with the type of the surrounding redlines, so accept pTmp, too.
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
m_rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoAcceptRedline>(*pTmp));
}
nPamEndNI = pTmp->Start()->GetNodeIndex();
nPamEndCI = pTmp->Start()->GetContentIndex();
bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
nRdlIdx++;
}
} while (nRdlIdx > 0);
return bRet;
}
bool DocumentRedlineManager::AcceptMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete)
{
assert(nMovedID > 1); // 0, and 1 is reserved
bool bRet = false;
SwRedlineTable::size_type nRdlIdx = maRedlineTable.size();
while (nRdlIdx > 0)
{
nRdlIdx--;
SwRangeRedline* pTmp = maRedlineTable[nRdlIdx];
if (pTmp->GetMoved(0) == nMovedID
|| (pTmp->GetStackCount() > 1 && pTmp->GetMoved(1) == nMovedID))
{
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
m_rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoAcceptRedline>(*pTmp));
}
if (pTmp->GetMoved(0) == nMovedID)
bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
else
bRet |= lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1);
nRdlIdx++; //we will decrease it in the loop anyway.
}
}
return bRet;
}
bool DocumentRedlineManager::AcceptRedline(SwRedlineTable::size_type nPos, bool bCallDelete,
bool bRange, bool bDirect)
{
bool bRet = false;
// Switch to visible in any case
if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
(RedlineFlags::ShowMask & GetRedlineFlags()) )
SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | GetRedlineFlags() );
SwRangeRedline* pTmp = maRedlineTable[ nPos ];
bool bAnonym = pTmp->GetRedlineData(0).IsAnonymized();
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();
if (bRange && !nSeqNo && !bAnonym
&& !pTmp->Start()->GetNode().StartOfSectionNode()->IsTableNode())
{
sal_uInt32 nMovedID = pTmp->GetMoved(0);
if (nMovedID > 1)
{
// Accept all redlineData with this unique move id
bRet |= AcceptMovedRedlines(nMovedID, bCallDelete);
}
else
{
SwRedlineTable::size_type nPosStart = nPos;
SwRedlineTable::size_type nPosEnd = nPos;
maRedlineTable.getConnectedArea(nPos, nPosStart, nPosEnd, true);
// Accept redlines between pPamStart-pPamEnd.
// but only those that can be combined with the selected.
bRet |= AcceptRedlineRange(nPos, nPosStart, nPosEnd, bCallDelete, bDirect);
}
}
else do {
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
m_rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoAcceptRedline>(*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, sal_Int8 nDepth, bool bDirect )
{
// Switch to visible in any case
if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
(RedlineFlags::ShowMask & GetRedlineFlags()) )
SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | GetRedlineFlags() );
// The Selection is only in the ContentSection. If there are Redlines
// to Non-ContentNodes before or after that, then the Selections
// expand to them.
std::shared_ptr<SwUnoCursor> const pPam(m_rDoc.CreateUnoCursor(*rPam.GetPoint(), false));
if (rPam.HasMark())
{
pPam->SetMark();
*pPam->GetMark() = *rPam.GetMark();
}
lcl_AdjustRedlineRange(*pPam);
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
m_rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::ACCEPT_REDLINE, nullptr );
m_rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoAcceptRedline>(*pPam, nDepth));
}
int nRet = 0;
if (nDepth == 0)
{
SwRedlineTable::size_type nRdlIdx = 0;
const SwRangeRedline* pRedline = maRedlineTable.FindAtPosition(*rPam.Start(), nRdlIdx);
bool bHierarchicalFormat = pRedline && pRedline->GetType() == RedlineType::Format
&& pRedline->GetStackCount() > 1;
if (bHierarchicalFormat && bDirect
&& (pRedline->GetType(1) == RedlineType::Insert
|| pRedline->GetType(1) == RedlineType::Delete))
{
// Direct accept: work with the format redline on top.
if (lcl_AcceptOuterFormat(maRedlineTable, nRdlIdx))
{
nRet = 1;
}
}
else
{
// Non-direct accept: work with all redlines under pPam.
nRet = lcl_AcceptRejectRedl(lcl_AcceptRedline, maRedlineTable, bCallDelete, *pPam);
}
}
else
{
// For now it is called only if it is an Insert redline in a delete redline.
SwRedlineTable::size_type nRdlIdx = 0;
maRedlineTable.FindAtPosition(*rPam.Start(), nRdlIdx);
if (lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1))
nRet = 1;
}
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 )
{
auto [pStart, pEnd] = rPam.StartEnd(); // SwPosition*
const SwNodeOffset nSttIdx = pStart->GetNodeIndex();
const SwNodeOffset nEndIdx = pEnd->GetNodeIndex();
for( SwRedlineTable::size_type n = 0; n < maRedlineTable.size() ; ++n )
{
const SwRangeRedline* pTmp = maRedlineTable[ n ];
SwNodeOffset nPt = pTmp->GetPoint()->GetNodeIndex(),
nMk = pTmp->GetMark()->GetNodeIndex();
if( nPt < nMk )
std::swap( nMk, nPt );
if( RedlineType::ParagraphFormat == pTmp->GetType() &&
( (nSttIdx <= nMk && nMk <= nEndIdx) || (nSttIdx <= nPt && nPt <= nEndIdx) ) )
AcceptRedline( n, false );
if( nMk > nEndIdx )
break;
}
}
bool DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type nPosOrigin,
const SwRedlineTable::size_type& nPosStart,
const SwRedlineTable::size_type& nPosEnd,
bool bCallDelete, bool bDirect)
{
bool bRet = false;
SwRangeRedline* pTmp = maRedlineTable[nPosOrigin];
SwRedlineTable::size_type nRdlIdx = nPosEnd + 1;
SwRedlineData aOrigData = pTmp->GetRedlineData(0);
SwNodeOffset nPamStartNI = maRedlineTable[nPosStart]->Start()->GetNodeIndex();
sal_Int32 nPamStartCI = maRedlineTable[nPosStart]->Start()->GetContentIndex();
SwNodeOffset nPamEndNI = maRedlineTable[nPosEnd]->End()->GetNodeIndex();
sal_Int32 nPamEndCI = maRedlineTable[nPosEnd]->End()->GetContentIndex();
do
{
nRdlIdx--;
pTmp = maRedlineTable[nRdlIdx];
if (pTmp->Start()->GetNodeIndex() < nPamStartNI
|| (pTmp->Start()->GetNodeIndex() == nPamStartNI
&& pTmp->Start()->GetContentIndex() < nPamStartCI))
break;
if (pTmp->End()->GetNodeIndex() > nPamEndNI
|| (pTmp->End()->GetNodeIndex() == nPamEndNI
&& pTmp->End()->GetContentIndex() > nPamEndCI))
{
}
else if (pTmp->GetRedlineData(0).CanCombineForAcceptReject(aOrigData))
{
bool bHierarchical = pTmp->GetStackCount() > 1;
bool bHierarchicalFormat = bHierarchical && pTmp->GetType() == RedlineType::Format;
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
sal_Int8 nDepth = 0;
if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Delete && !bDirect)
{
// Only work with the underlying delete, so the undo action matches the UI
// action below.
nDepth = 1;
}
auto pUndoRdl = std::make_unique<SwUndoRejectRedline>(*pTmp, nDepth, bHierarchical, bDirect);
#if OSL_DEBUG_LEVEL > 0
pUndoRdl->SetRedlineCountDontCheck(true);
#endif
m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
}
nPamEndNI = pTmp->Start()->GetNodeIndex();
nPamEndCI = pTmp->Start()->GetContentIndex();
if (bHierarchicalFormat && bDirect
&& (pTmp->GetType(1) == RedlineType::Insert
|| pTmp->GetType(1) == RedlineType::Delete))
{
bRet |= lcl_RejectOuterFormat(maRedlineTable, nRdlIdx);
}
else if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Insert)
{
// Accept the format itself and then reject the insert by deleting the range.
SwPaM aPam(*pTmp->Start(), *pTmp->End());
bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
// Handles undo/redo itself.
m_rDoc.getIDocumentContentOperations().DeleteRange(aPam);
}
else if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Delete)
{
// Keep the format redline on top, just get rid of the delete at the bottom.
bRet |= lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1);
}
else
{
bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete);
}
nRdlIdx++; //we will decrease it in the loop anyway.
}
else if (CanCombineTypesForAcceptReject(aOrigData, *pTmp)
&& pTmp->GetRedlineData(1).CanCombineForAcceptReject(aOrigData))
{
RedlineType eInnerType = aOrigData.GetType();
RedlineType eOuterType = pTmp->GetType();
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
std::unique_ptr<SwUndoRedline> pUndoRdl;
if (eInnerType == RedlineType::Delete && eOuterType == RedlineType::Format)
{
// Format on delete: record rejection of the underlying delete.
pUndoRdl = std::make_unique<SwUndoRejectRedline>(*pTmp, /*nDepth=*/1, /*bHierarchical=*/true);
}
else
{
// The Insert/Delete redline we want to reject has another type of redline too
pUndoRdl = std::make_unique<SwUndoRejectRedline>(*pTmp, /*nDepth=*/0, /*bHierarchical=*/true);
}
#if OSL_DEBUG_LEVEL > 0
pUndoRdl->SetRedlineCountDontCheck(true);
#endif
m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
}
nPamEndNI = pTmp->Start()->GetNodeIndex();
nPamEndCI = pTmp->Start()->GetContentIndex();
std::optional<SwPaM> oPam;
if (eInnerType == RedlineType::Insert && eOuterType == RedlineType::Format)
{
// The accept won't implicitly delete the range, so track its boundaries.
oPam.emplace(*pTmp->Start(), *pTmp->End());
}
if (eInnerType == RedlineType::Delete && eOuterType == RedlineType::Format)
{
// Keep the outer redline, just get rid of the underlying delete.
bRet |= lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1);
}
else
{
// without the insert, the other type is meaningless
// so we rather just accept the other type of redline
bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
}
if (oPam)
{
// Handles undo/redo itself.
m_rDoc.getIDocumentContentOperations().DeleteRange(*oPam);
}
nRdlIdx++; //we will decrease it in the loop anyway.
}
else if (CanReverseCombineTypesForAcceptReject(*pTmp, aOrigData) && !bDirect)
{
// The aOrigData has 2 types and for these types we want the underlying type to be
// combined with the type of the surrounding redlines, so reject pTmp, too.
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
std::unique_ptr<SwUndoRedline> pUndoRdl
= std::make_unique<SwUndoRejectRedline>(*pTmp);
m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
}
nPamEndNI = pTmp->Start()->GetNodeIndex();
nPamEndCI = pTmp->Start()->GetContentIndex();
bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete);
nRdlIdx++;
}
} while (nRdlIdx > 0);
return bRet;
}
bool DocumentRedlineManager::RejectMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete)
{
assert(nMovedID > 1); // 0, and 1 is reserved
bool bRet = false;
SwRedlineTable::size_type nRdlIdx = maRedlineTable.size();
while (nRdlIdx > 0)
{
nRdlIdx--;
SwRangeRedline* pTmp = maRedlineTable[nRdlIdx];
if (pTmp->GetMoved(0) == nMovedID
|| (pTmp->GetStackCount() > 1 && pTmp->GetMoved(1) == nMovedID))
{
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
std::unique_ptr<SwUndoRejectRedline> pUndoRdl
= std::make_unique<SwUndoRejectRedline>(*pTmp);
#if OSL_DEBUG_LEVEL > 0
pUndoRdl->SetRedlineCountDontCheck(true);
#endif
m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl));
}
if (pTmp->GetMoved(0) == nMovedID)
bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete);
else
bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete);
nRdlIdx++; //we will decrease it in the loop anyway.
}
}
return bRet;
}
bool DocumentRedlineManager::RejectRedline(SwRedlineTable::size_type nPos,
bool bCallDelete, bool bRange,
bool bDirect)
{
bool bRet = false;
// Switch to visible in any case
if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
(RedlineFlags::ShowMask & GetRedlineFlags()) )
SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | GetRedlineFlags() );
SwRangeRedline* pTmp = maRedlineTable[ nPos ];
bool bAnonym = pTmp->GetRedlineData(0).IsAnonymized();
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();
if (bRange && !nSeqNo && !bAnonym
&& !pTmp->Start()->GetNode().StartOfSectionNode()->IsTableNode())
{
sal_uInt32 nMovedID = pTmp->GetMoved(0);
if (nMovedID > 1)
{
// Reject all redlineData with this unique move id
bRet |= RejectMovedRedlines(nMovedID, bCallDelete);
}
else
{
SwRedlineTable::size_type nPosStart = nPos;
SwRedlineTable::size_type nPosEnd = nPos;
maRedlineTable.getConnectedArea(nPos, nPosStart, nPosEnd, true);
// Reject items between pPamStart-pPamEnd
// but only those that can be combined with the selected.
bRet |= RejectRedlineRange(nPos, nPosStart, nPosEnd, bCallDelete, bDirect);
}
}
else do {
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
m_rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoRejectRedline>( *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, sal_Int8 nDepth, bool bDirect )
{
// Switch to visible in any case
if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) !=
(RedlineFlags::ShowMask & GetRedlineFlags()) )
SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | GetRedlineFlags() );
// 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<SwUndoRejectRedline>(aPam, nDepth) );
}
int nRet = 0;
SwRedlineTable::size_type nRdlIdx = 0;
const SwRangeRedline* pRedline = maRedlineTable.FindAtPosition(*rPam.Start(), nRdlIdx);
if (nDepth == 0)
{
bool bHierarchicalFormat = pRedline && pRedline->GetType() == RedlineType::Format && pRedline->GetStackCount() > 1;
if (bHierarchicalFormat && bDirect && (pRedline->GetType(1) == RedlineType::Insert || pRedline->GetType(1) == RedlineType::Delete))
{
// Direct reject: work with the format redline on top.
if (lcl_RejectOuterFormat(maRedlineTable, nRdlIdx))
{
nRet = 1;
}
}
else
{
// Non-direct reject: work with all redlines under aPam.
nRet = lcl_AcceptRejectRedl(lcl_RejectRedline, maRedlineTable, bCallDelete, aPam);
}
}
else
{
if (nDepth == 1 && pRedline && pRedline->GetType(0) == RedlineType::Format
&& pRedline->GetType(1) == RedlineType::Delete)
{
// Reject a format-on-delete by getting rid of the underlying delete.
if (lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, nDepth))
{
nRet = 1;
}
}
else
{
// For now it is called only if it is an Insert redline in a delete redline.
if (lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete))
nRet = 1;
}
}
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->GetNode().IsContentNode() )
{
SwNodeIndex aTmp( pEnd->GetNode() );
SwContentNode* pCNd = SwNodes::GoPrevSection( &aTmp );
if( !pCNd || ( aTmp == rSttPos.GetNode() &&
pCNd->Len() == rSttPos.GetContentIndex() ))
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;
SwPosition* pPos = rPam.GetMark();
if( !pPos->GetNode().IsContentNode() )
{
pCNd = SwNodes::GoNextSection(pPos);
if( pCNd )
{
if( pPos->GetNode() <= rPam.GetPoint()->GetNode() )
pPos->Assign( *pCNd, 0 );
else
pFnd = nullptr;
}
}
if( pFnd )
{
pPos = rPam.GetPoint();
if( !pPos->GetNode().IsContentNode() )
{
pCNd = SwNodes::GoPrevSection( pPos );
if( pCNd )
{
if( pPos->GetNode() >= rPam.GetMark()->GetNode() )
pPos->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* pStart = pFnd->Start();
if( !pStart->GetNode().IsContentNode() )
{
SwNodeIndex aTmp( pStart->GetNode() );
SwContentNode* pCNd = SwNodes::GoNextSection(&aTmp);
if( !pCNd || ( aTmp == rSttPos.GetNode() &&
!rSttPos.GetContentIndex() ))
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;
SwPosition* pPos = rPam.GetMark();
if( !pPos->GetNode().IsContentNode() )
{
pCNd = SwNodes::GoPrevSection( pPos );
if( pCNd )
{
if( pPos->GetNode() >= rPam.GetPoint()->GetNode() )
pPos->Assign( *pCNd, pCNd->Len() );
else
pFnd = nullptr;
}
}
if( pFnd )
{
pPos = rPam.GetPoint();
if( !pPos->GetNode().IsContentNode() )
{
pCNd = SwNodes::GoNextSection(pPos);
if( pCNd )
{
if( pPos->GetNode() <= rPam.GetMark()->GetNode() )
pPos->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;
auto [pStart, pEnd] = rPaM.StartEnd(); // SwPosition*
SwRedlineTable::size_type n = 0;
if( GetRedlineTable().FindAtPosition( *pStart, n ) )
{
for( ; n < maRedlineTable.size(); ++n )
{
bRet = true;
SwRangeRedline* pTmp = maRedlineTable[ n ];
if( pStart != 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 SwModule::get()->GetRedlineAuthor();
}
/// Insert new author into the Table for the Readers etc.
std::size_t DocumentRedlineManager::InsertRedlineAuthor( const OUString& rNew )
{
return SwModule::get()->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 <sal_Int8>& DocumentRedlineManager::GetRedlinePassword() const
{
return maRedlinePasswd;
}
void DocumentRedlineManager::SetRedlinePassword(
/*[in]*/const uno::Sequence <sal_Int8>& 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: */
↑ V530 The return value of function 'Assign' is required to be utilized.
↑ V530 The return value of function 'Assign' is required to be utilized.
↑ V530 The return value of function 'Assign' is required to be utilized.
↑ V530 The return value of function 'Assign' is required to be utilized.
↑ V530 The return value of function 'Assign' is required to be utilized.
↑ V530 The return value of function 'Assign' is required to be utilized.
↑ V560 A part of conditional expression is always false: rCtx.pNewRedl.