/* -*- 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;
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.