/* -*- 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>
usingnamespace 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());
// 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 ];
staticvoid UpdateFieldsForRedline(IDocumentFieldsAccess & rIDFA)
{ autoconst 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())
{ autoconst 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())
{ autoconst 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 (autoconst 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 (autoconst& 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
// 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);
// 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);
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;
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);
} elseif ( 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;
/// 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());
/// 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;
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)
{ returnfalse;
}
switch (rOuterRedline.GetType())
{ case RedlineType::Delete: case RedlineType::Format: break; default: returnfalse;
}
if (rOuterRedline.GetStackCount() <= 1)
{ returnfalse;
}
if (rOuterRedline.GetType(1) != RedlineType::Insert)
{ returnfalse;
}
returntrue;
}
/// 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: returnfalse;
}
if (rInnerData.GetType() != RedlineType::Format)
{ returnfalse;
}
const SwRedlineData* pInnerDataNext = rInnerData.Next(); if (!pInnerDataNext)
{ returnfalse;
}
switch (pInnerDataNext->GetType())
{ case RedlineType::Insert: case RedlineType::Delete: return pInnerDataNext->GetType() == rOuterRedline.GetType(); default: returnfalse;
}
}
}
if (pViewShell)
{ // Recording can be per-view, the rest is per-document.
eRedlineFlags = eRedlineFlags & ~RedlineFlags::On; if (pViewShell->GetViewOptions()->IsRedlineRecordingOn())
{
eRedlineFlags |= RedlineFlags::On;
}
}
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();
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);
}
} elseif (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;
} elseif (&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);
}
}
}
/// Data shared between DocumentRedlineManager::AppendRedline() and PreAppendInsertRedline(). class AppendRedlineContext
{ public:
SwRangeRedline*& pNewRedl;
SwPosition*& pStart;
SwPosition*& pEnd;
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;
// 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;
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;
}
}
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;
} elseif( 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;
}
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 );
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 )
{
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;
}
}
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;
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);
} elseif (*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);
} elseif( *rCtx.pREnd == *rCtx.pEnd ) // or else only shorten the current one
rCtx.pRedl->SetEnd( *rCtx.pStart, rCtx.pREnd ); elseif( *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;
} elseif( 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, boolconst 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;
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;
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;
}
// 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;
}
}
} boolconst 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);
}
}
}
}
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 )
*/
bool DocumentRedlineManager::SplitRedline( const SwPaM& rRange )
{ if (maRedlineTable.empty()) returnfalse; auto [pStart, pEnd] = rRange.StartEnd(); // SwPosition* // tdf#144208 this happens a lot during load of some DOCX files. if (*pEnd > maRedlineTable.GetMaxEndPos()) returnfalse; 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);
}
} elseif (*pEnd < *pRedlineStart) break; if (pRedline)
++n;
} return bChg;
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;
}
}
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();
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;
if (pTmp->End()->GetNodeIndex() > nPamEndNI
|| (pTmp->End()->GetNodeIndex() == nPamEndNI
&& pTmp->End()->GetContentIndex() > nPamEndCI))
{
} elseif (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);
} elseif (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Insert)
{ // This combination of 2 redline types prefers accepting the inner one first.
bRet |= lcl_DeleteInnerRedline(maRedlineTable, nRdlIdx, 1);
} elseif (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.
} elseif (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.
} elseif (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;
}
// Accept redlines between pPamStart-pPamEnd. // but only those that can be combined with the selected.
bRet |= AcceptRedlineRange(nPos, nPosStart, nPosEnd, bCallDelete, bDirect);
}
} elsedo {
if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
m_rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoAcceptRedline>(*pTmp) );
}
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);
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;
if (pTmp->End()->GetNodeIndex() > nPamEndNI
|| (pTmp->End()->GetNodeIndex() == nPamEndNI
&& pTmp->End()->GetContentIndex() > nPamEndCI))
{
} elseif (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);
} elseif (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);
} elseif (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.
} elseif (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.
} elseif (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++;
}
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 );
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;
}
}
// 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);
}
/// 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();
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.114Bemerkung:
(vorverarbeitet am 2026-05-06)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.