/* -*- 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 <sal/config.h>
#include <string_view>
#include <hintids.hxx>
#include <o3tl/safeint.hxx>
#include <svl/whiter.hxx>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <scriptinfo.hxx>
#include <swmodule.hxx>
#include <redline.hxx>
#include <txatbase.hxx>
#include <docary.hxx>
#include "itratr.hxx"
#include <ndtxt.hxx>
#include <doc.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentMarkAccess.hxx>
#include <IMark.hxx>
#include <bookmark.hxx>
#include <rootfrm.hxx>
#include <breakit.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/settings.hxx>
#include <txtfrm.hxx>
#include <ftnfrm.hxx>
#include <vcl/svapp.hxx>
#include "redlnitr.hxx"
#include <extinput.hxx>
#include <fmtpdsc.hxx>
#include <editeng/charhiddenitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/formatbreakitem.hxx>
#include <editeng/udlnitem.hxx>
#include <officecfg/Office/Writer.hxx>
using namespace ::com::sun::star;
namespace {
class HideIterator
{
private:
IDocumentRedlineAccess const& m_rIDRA;
IDocumentMarkAccess const& m_rIDMA;
bool const m_isHideRedlines;
sw::FieldmarkMode const m_eFieldmarkMode;
bool const m_isHideParagraphBreaks;
SwPosition const m_Start;
/// next redline
SwRedlineTable::size_type m_RedlineIndex;
/// next fieldmark
std::pair<sw::mark::Fieldmark const*, std::optional<SwPosition>> m_Fieldmark;
std::optional<SwPosition> m_oNextFieldmarkHide;
/// previous paragraph break - because m_pStartPos/EndPos are non-owning
std::optional<std::pair<SwPosition, SwPosition>> m_oParagraphBreak;
/// current start/end pair
SwPosition const* m_pStartPos;
SwPosition const* m_pEndPos;
public:
SwPosition const* GetStartPos() const { return m_pStartPos; }
SwPosition const* GetEndPos() const { return m_pEndPos; }
HideIterator(SwTextNode & rTextNode,
bool const isHideRedlines, sw::FieldmarkMode const eMode,
sw::ParagraphBreakMode const ePBMode)
: m_rIDRA(rTextNode.getIDocumentRedlineAccess())
, m_rIDMA(*rTextNode.getIDocumentMarkAccess())
, m_isHideRedlines(isHideRedlines)
, m_eFieldmarkMode(eMode)
, m_isHideParagraphBreaks(ePBMode == sw::ParagraphBreakMode::Hidden)
, m_Start(rTextNode, 0)
, m_RedlineIndex(isHideRedlines ? m_rIDRA.GetRedlinePos(rTextNode, RedlineType::Any) : SwRedlineTable::npos)
, m_pStartPos(nullptr)
, m_pEndPos(&m_Start)
{
}
// delete redlines and fieldmarks can't overlap, due to sw::CalcBreaks()
// and no combining of adjacent redlines
// -> dummy chars are delete-redlined *iff* entire fieldmark is
// Note: caller is responsible for checking for immediately adjacent hides
bool Next()
{
SwPosition const* pNextRedlineHide(nullptr);
assert(m_pEndPos);
if (m_isHideRedlines)
{
// position on current or next redline
for (; m_RedlineIndex < m_rIDRA.GetRedlineTable().size(); ++m_RedlineIndex)
{
SwRangeRedline const*const pRed = m_rIDRA.GetRedlineTable()[m_RedlineIndex];
if (m_pEndPos->GetNodeIndex() < pRed->Start()->GetNodeIndex())
break;
if (pRed->GetType() != RedlineType::Delete)
continue;
auto [pStart, pEnd] = pRed->StartEnd(); // SwPosition*
if (*pStart == *pEnd)
{ // only allowed while moving (either way?)
// assert(IDocumentRedlineAccess::IsHideChanges(rIDRA.GetRedlineFlags()));
continue;
}
if (pStart->GetNode().IsTableNode())
{
assert(pEnd->GetNode() == m_Start.GetNode() && pEnd->GetContentIndex() == 0);
continue; // known pathology, ignore it
}
if (*m_pEndPos <= *pStart)
{
pNextRedlineHide = pStart;
break; // the next one
}
}
}
// position on current or next fieldmark
m_oNextFieldmarkHide.reset();
if (m_eFieldmarkMode != sw::FieldmarkMode::ShowBoth)
{
sal_Unicode const magic(m_eFieldmarkMode == sw::FieldmarkMode::ShowResult
? CH_TXT_ATR_FIELDSTART
: CH_TXT_ATR_FIELDSEP);
SwTextNode* pTextNode = m_pEndPos->GetNode().GetTextNode();
sal_Int32 const nPos = pTextNode ? pTextNode->GetText().indexOf(
magic, m_pEndPos->GetContentIndex()) : -1;
if (nPos != -1)
{
m_oNextFieldmarkHide.emplace(*pTextNode, nPos);
sw::mark::Fieldmark const*const pFieldmark(
m_eFieldmarkMode == sw::FieldmarkMode::ShowResult
? m_rIDMA.getFieldmarkAt(*m_oNextFieldmarkHide)
: m_rIDMA.getInnerFieldmarkFor(*m_oNextFieldmarkHide));
assert(pFieldmark);
m_Fieldmark.first = pFieldmark;
// for cursor travelling, there should be 2 visible chars;
// whichever char is hidden, the cursor travelling needs to
// be adapted in any case to skip in some situation or other;
// always hide the CH_TXT_ATR_FIELDSEP for now
if (m_eFieldmarkMode == sw::FieldmarkMode::ShowResult)
{
m_Fieldmark.second.emplace(
sw::mark::FindFieldSep(*m_Fieldmark.first));
m_Fieldmark.second->AdjustContent(+1);
m_oNextFieldmarkHide->AdjustContent(+1); // skip start
}
else
{
m_Fieldmark.second.emplace(pFieldmark->GetMarkEnd());
m_Fieldmark.second->AdjustContent(-1);
}
}
}
// == can happen only if redline starts inside field command, and in
// that case redline will end before field separator
assert(!pNextRedlineHide || !m_oNextFieldmarkHide
|| *pNextRedlineHide != *m_oNextFieldmarkHide
|| *m_rIDRA.GetRedlineTable()[m_RedlineIndex]->End() < *m_Fieldmark.second);
if (pNextRedlineHide
&& (!m_oNextFieldmarkHide || *pNextRedlineHide < *m_oNextFieldmarkHide))
{
SwRangeRedline const*const pRed(m_rIDRA.GetRedlineTable()[m_RedlineIndex]);
m_pStartPos = pRed->Start();
m_pEndPos = pRed->End();
++m_RedlineIndex;
return true;
}
else if (m_oNextFieldmarkHide)
{
assert(!pNextRedlineHide || *m_oNextFieldmarkHide <= *pNextRedlineHide);
m_pStartPos = &*m_oNextFieldmarkHide;
m_pEndPos = &*m_Fieldmark.second;
return true;
}
else
{
assert(!pNextRedlineHide && !m_oNextFieldmarkHide);
auto const hasHiddenItem = [](auto const& rNode) {
auto const& rpSet(rNode.GetAttr(RES_PARATR_LIST_AUTOFMT).GetStyleHandle());
return rpSet ? rpSet->Get(RES_CHRATR_HIDDEN).GetValue() : false;
};
auto const hasBreakBefore = [](SwTextNode const& rNode) {
if (rNode.GetAttr(RES_PAGEDESC).GetPageDesc())
{
return true;
}
switch (rNode.GetAttr(RES_BREAK).GetBreak())
{
case SvxBreak::ColumnBefore:
case SvxBreak::ColumnBoth:
case SvxBreak::PageBefore:
case SvxBreak::PageBoth:
return true;
default:
break;
}
return false;
};
auto const hasBreakAfter = [](SwTextNode const& rNode) {
switch (rNode.GetAttr(RES_BREAK).GetBreak())
{
case SvxBreak::ColumnAfter:
case SvxBreak::ColumnBoth:
case SvxBreak::PageAfter:
case SvxBreak::PageBoth:
return true;
default:
break;
}
return false;
};
if (m_isHideParagraphBreaks
&& m_pEndPos->GetNode().IsTextNode() // ooo27109-1.sxw
// only merge if next node is also text node
&& m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->IsTextNode()
&& hasHiddenItem(*m_pEndPos->GetNode().GetTextNode())
// no merge if there's a page break on any node
&& !hasBreakBefore(*m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->GetTextNode())
// first node, see SwTextFrame::GetBreak()
&& !hasBreakAfter(*m_Start.GetNode().GetTextNode()))
{
m_oParagraphBreak.emplace(
SwPosition(*m_pEndPos->GetNode().GetTextNode(), m_pEndPos->GetNode().GetTextNode()->Len()),
SwPosition(*m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->GetTextNode(), 0));
m_pStartPos = &m_oParagraphBreak->first;
m_pEndPos = &m_oParagraphBreak->second;
return true;
}
else // nothing
{
m_pStartPos = nullptr;
m_pEndPos = nullptr;
return false;
}
}
}
};
}
namespace sw {
std::unique_ptr<sw::MergedPara>
CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode,
FrameMode const eMode)
{
if (!rFrame.getRootFrame()->HasMergedParas())
{
return nullptr;
}
bool bHaveRedlines(false);
std::vector<SwTextNode *> nodes{ &rTextNode };
std::vector<SwTableNode *> tables;
std::vector<SwSectionNode *> sections;
std::vector<sw::Extent> extents;
OUStringBuffer mergedText;
SwTextNode * pParaPropsNode(nullptr);
SwTextNode * pNode(&rTextNode);
sal_Int32 nLastEnd(0);
for (auto iter = HideIterator(rTextNode,
rFrame.getRootFrame()->IsHideRedlines(),
rFrame.getRootFrame()->GetFieldmarkMode(),
rFrame.getRootFrame()->GetParagraphBreakMode());
iter.Next(); )
{
SwPosition const*const pStart(iter.GetStartPos());
SwPosition const*const pEnd(iter.GetEndPos());
bHaveRedlines = true;
assert(pNode != &rTextNode || &pStart->GetNode() == &rTextNode); // detect calls with wrong start node
if (pStart->GetContentIndex() != nLastEnd) // not 0 so we eliminate adjacent deletes
{
extents.emplace_back(pNode, nLastEnd, pStart->GetContentIndex());
mergedText.append(pNode->GetText().subView(nLastEnd, pStart->GetContentIndex() - nLastEnd));
}
if (&pEnd->GetNode() != pNode)
{
if (pNode == &rTextNode)
{
pNode->SetRedlineMergeFlag(SwNode::Merge::First);
} // else: was already set before
int nLevel(0);
for (SwNodeOffset j = pNode->GetIndex() + 1; j < pEnd->GetNodeIndex(); ++j)
{
SwNode *const pTmp(pNode->GetNodes()[j]);
if (nLevel == 0)
{
if (pTmp->IsTextNode())
{
nodes.push_back(pTmp->GetTextNode());
}
else if (pTmp->IsTableNode())
{
tables.push_back(pTmp->GetTableNode());
}
else if (pTmp->IsSectionNode())
{
sections.push_back(pTmp->GetSectionNode());
}
}
if (pTmp->IsStartNode())
{
++nLevel;
}
else if (pTmp->IsEndNode())
{
--nLevel;
}
pTmp->SetRedlineMergeFlag(SwNode::Merge::Hidden);
}
// note: in DelLastPara() case, the end node is not actually merged
// and is likely a SwTableNode!
if (!pEnd->GetNode().IsTextNode())
{
assert(pEnd->GetNode() != pStart->GetNode());
// must set pNode too because it will mark the last node
pNode = nodes.back();
assert(pNode == pNode->GetNodes()[pEnd->GetNodeIndex() - 1]);
if (pNode != &rTextNode)
{ // something might depend on last merged one being NonFirst?
pNode->SetRedlineMergeFlag(SwNode::Merge::NonFirst);
}
nLastEnd = pNode->Len();
}
else
{
pNode = pEnd->GetNode().GetTextNode();
nodes.push_back(pNode);
pNode->SetRedlineMergeFlag(SwNode::Merge::NonFirst);
nLastEnd = pEnd->GetContentIndex();
}
}
else
{
nLastEnd = pEnd->GetContentIndex();
}
}
if (pNode == &rTextNode)
{
if (rTextNode.GetRedlineMergeFlag() != SwNode::Merge::None)
{
rTextNode.SetRedlineMergeFlag(SwNode::Merge::None);
}
}
// Reset flag of the following text node since we know it's not merged;
// also any table/sections in between.
// * the following SwTextNode is in same nodes section as pNode (nLevel=0)
// * the start nodes that don't have a SwTextNode before them
// on their level, and their corresponding end nodes
// * the first SwTextNode inside each start node of the previous point
// Other (non-first) SwTextNodes in nested sections shouldn't be reset!
int nLevel(0);
for (SwNodeOffset j = pNode->GetIndex() + 1; j < pNode->GetNodes().Count(); ++j)
{
SwNode *const pTmp(pNode->GetNodes()[j]);
if (!pTmp->IsCreateFrameWhenHidingRedlines())
{ // clear stale flag caused by editing with redlines shown
pTmp->SetRedlineMergeFlag(SwNode::Merge::None);
}
if (pTmp->IsStartNode())
{
++nLevel;
}
else if (pTmp->IsEndNode())
{
if (nLevel == 0)
{
break; // there is no following text node; avoid leaving section
}
--nLevel;
}
else if (pTmp->IsTextNode())
{
if (nLevel == 0)
{
break; // done
}
else
{ // skip everything other than 1st text node in section!
j = pTmp->EndOfSectionIndex() - 1; // will be incremented again
}
}
}
if (!bHaveRedlines)
{
if (rTextNode.IsInList() && !rTextNode.GetNum(rFrame.getRootFrame()))
{
rTextNode.AddToListRLHidden(); // try to add it...
}
return nullptr;
}
if (nLastEnd != pNode->Len())
{
extents.emplace_back(pNode, nLastEnd, pNode->Len());
mergedText.append(pNode->GetText().subView(nLastEnd, pNode->Len() - nLastEnd));
}
if (extents.empty()) // there was no text anywhere
{
assert(mergedText.isEmpty());
pParaPropsNode = pNode; // if every node is empty, the last one wins
}
else
{
assert(!mergedText.isEmpty());
pParaPropsNode = extents.begin()->pNode; // para props from first node that isn't empty
}
// pParaPropsNode = &rTextNode; // well, actually...
// keep lists up to date with visible nodes
if (pParaPropsNode->IsInList() && !pParaPropsNode->GetNum(rFrame.getRootFrame()))
{
pParaPropsNode->AddToListRLHidden(); // try to add it...
}
for (auto const pTextNode : nodes)
{
if (pTextNode != pParaPropsNode)
{
pTextNode->RemoveFromListRLHidden();
}
}
if (eMode == FrameMode::Existing)
{
// remove existing footnote frames for first node;
// for non-first nodes with own frames, DelFrames will remove all
// (could possibly call lcl_ChangeFootnoteRef, not sure if worth it)
// note: must be done *before* changing listeners!
// for non-first nodes that are already merged with this frame,
// need to remove here too, otherwise footnotes can be removed only
// by lucky accident, e.g. TruncLines().
auto itExtent(extents.begin());
for (auto const pTextNode : nodes)
{
sal_Int32 nLast(0);
std::vector<std::pair<sal_Int32, sal_Int32>> hidden;
for ( ; itExtent != extents.end(); ++itExtent)
{
if (itExtent->pNode != pTextNode)
{
break;
}
if (itExtent->nStart != 0)
{
assert(itExtent->nStart != nLast);
hidden.emplace_back(nLast, itExtent->nStart);
}
nLast = itExtent->nEnd;
}
if (nLast != pTextNode->Len())
{
hidden.emplace_back(nLast, pTextNode->Len());
}
sw::RemoveFootnotesForNode(*rFrame.getRootFrame(), *pTextNode, &hidden);
}
// unfortunately DelFrames() must be done before StartListening too,
// otherwise footnotes cannot be deleted by SwTextFootnote::DelFrames!
auto const end(--nodes.rend());
for (auto iter = nodes.rbegin(); iter != end; ++iter)
{
(**iter).DelFrames(rFrame.getRootFrame());
}
// also delete tables & sections here; not necessary, but convenient
for (auto const pTableNode : tables)
{
pTableNode->DelFrames(rFrame.getRootFrame());
}
for (auto const pSectionNode : sections)
{
pSectionNode->GetSection().GetFormat()->DelFrames(/*rFrame.getRootFrame()*/);
}
}
auto pRet(std::make_unique<sw::MergedPara>(rFrame, std::move(extents),
mergedText.makeStringAndClear(), pParaPropsNode, &rTextNode,
nodes.back()));
for (SwTextNode * pTmp : nodes)
{
pRet->listener.StartListening(pTmp);
}
rFrame.EndListeningAll();
return pRet;
}
} // namespace sw
void SwAttrIter::InitFontAndAttrHandler(
SwTextNode const& rPropsNode,
SwTextNode const& rTextNode,
std::u16string_view aText,
bool const*const pbVertLayout,
bool const*const pbVertLayoutLRBT)
{
// Build a font matching the default paragraph style:
SwFontAccess aFontAccess( &rPropsNode.GetAnyFormatColl(), m_pViewShell );
// It is possible that Init is called more than once, e.g., in a
// SwTextFrame::FormatOnceMore situation or (since sw_redlinehide)
// from SwAttrIter::Seek(); in the latter case SwTextSizeInfo::m_pFnt
// is an alias of m_pFont so it must not be deleted!
if (m_pFont)
{
*m_pFont = aFontAccess.Get()->GetFont();
}
else
{
m_pFont = new SwFont( aFontAccess.Get()->GetFont() );
}
// set font to vertical if frame layout is vertical
// if it's a re-init, the vert flag never changes
bool bVertLayoutLRBT = false;
if (pbVertLayoutLRBT)
bVertLayoutLRBT = *pbVertLayoutLRBT;
if (pbVertLayout ? *pbVertLayout : m_aAttrHandler.IsVertLayout())
{
m_pFont->SetVertical(m_pFont->GetOrientation(), true, bVertLayoutLRBT);
}
// Initialize the default attribute of the attribute handler
// based on the attribute array cached together with the font.
// If any further attributes for the paragraph are given in pAttrSet
// consider them during construction of the default array, and apply
// them to the font
m_aAttrHandler.Init(aFontAccess.Get()->GetDefault(), rTextNode.GetpSwAttrSet(),
*rTextNode.getIDocumentSettingAccess(), m_pViewShell, *m_pFont,
pbVertLayout ? *pbVertLayout : m_aAttrHandler.IsVertLayout(),
bVertLayoutLRBT );
m_aFontCacheIds[SwFontScript::Latin] = m_aFontCacheIds[SwFontScript::CJK] = m_aFontCacheIds[SwFontScript::CTL] = nullptr;
assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
m_pFont->SetActual( m_pScriptInfo->WhichFont(TextFrameIndex(0)) );
TextFrameIndex nChg(0);
size_t nCnt = 0;
do
{
if ( nCnt >= m_pScriptInfo->CountScriptChg() )
break;
nChg = m_pScriptInfo->GetScriptChg( nCnt );
SwFontScript nTmp = SW_SCRIPTS;
switch ( m_pScriptInfo->GetScriptType( nCnt++ ) ) {
case i18n::ScriptType::ASIAN :
if( !m_aFontCacheIds[SwFontScript::CJK] ) nTmp = SwFontScript::CJK;
break;
case i18n::ScriptType::COMPLEX :
if( !m_aFontCacheIds[SwFontScript::CTL] ) nTmp = SwFontScript::CTL;
break;
default:
if( !m_aFontCacheIds[SwFontScript::Latin ] ) nTmp = SwFontScript::Latin;
}
if( nTmp < SW_SCRIPTS )
{
m_pFont->CheckFontCacheId( m_pViewShell, nTmp );
m_pFont->GetFontCacheId( m_aFontCacheIds[ nTmp ], m_aFontIdx[ nTmp ], nTmp );
}
}
while (nChg < TextFrameIndex(aText.size()));
}
void SwAttrIter::CtorInitAttrIter(SwTextNode & rTextNode,
SwScriptInfo & rScriptInfo, SwTextFrame const*const pFrame)
{
// during HTML-Import it can happen, that no layout exists
SwRootFrame* pRootFrame = rTextNode.getIDocumentLayoutAccess().GetCurrentLayout();
m_pViewShell = pRootFrame ? pRootFrame->GetCurrShell() : nullptr;
m_pScriptInfo = &rScriptInfo;
// set font to vertical if frame layout is vertical
bool bVertLayout = false;
bool bVertLayoutLRBT = false;
bool bRTL = false;
if ( pFrame )
{
if ( pFrame->IsVertical() )
{
bVertLayout = true;
}
if (pFrame->IsVertLRBT())
{
bVertLayoutLRBT = true;
}
bRTL = pFrame->IsRightToLeft();
m_pMergedPara = pFrame->GetMergedPara();
}
// determine script changes if not already done for current paragraph
assert(m_pScriptInfo);
if (m_pScriptInfo->GetInvalidityA() != TextFrameIndex(COMPLETE_STRING))
m_pScriptInfo->InitScriptInfo(rTextNode, m_pMergedPara, bRTL);
InitFontAndAttrHandler(
m_pMergedPara ? *m_pMergedPara->pParaPropsNode : rTextNode,
rTextNode,
m_pMergedPara ? m_pMergedPara->mergedText : rTextNode.GetText(),
& bVertLayout,
& bVertLayoutLRBT);
m_nStartIndex = m_nEndIndex = m_nPosition = m_nChgCnt = 0;
m_nPropFont = 0;
SwDoc& rDoc = rTextNode.GetDoc();
const IDocumentRedlineAccess& rIDRA = rTextNode.getIDocumentRedlineAccess();
// sw_redlinehide: this is a Ring - pExtInp is the first PaM that's inside
// the node. It's not clear whether there can be more than 1 PaM in the
// Ring, and this code doesn't handle that case; neither did the old code.
const SwExtTextInput* pExtInp = rDoc.GetExtTextInput( rTextNode );
if (!pExtInp && m_pMergedPara)
{
SwTextNode const* pNode(&rTextNode);
for (auto const& rExtent : m_pMergedPara->extents)
{
if (rExtent.pNode != pNode)
{
pNode = rExtent.pNode;
pExtInp = rDoc.GetExtTextInput(*pNode);
if (pExtInp)
break;
}
}
}
const bool bShow = IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags())
&& pRootFrame && !pRootFrame->IsHideRedlines();
if (!(pExtInp || m_pMergedPara || bShow))
return;
SwRedlineTable::size_type nRedlPos = rIDRA.GetRedlinePos( rTextNode, RedlineType::Any );
if (SwRedlineTable::npos == nRedlPos && m_pMergedPara)
{
SwTextNode const* pNode(&rTextNode);
for (auto const& rExtent : m_pMergedPara->extents)
{ // note: have to search because extents based only on Delete
if (rExtent.pNode != pNode)
{
pNode = rExtent.pNode;
nRedlPos = rIDRA.GetRedlinePos(*pNode, RedlineType::Any);
if (SwRedlineTable::npos != nRedlPos)
break;
}
}
// TODO this is true initially but after delete ops it may be false... need to delete m_pMerged somewhere?
// assert(SwRedlineTable::npos != nRedlPos);
// false now with fieldmarks
assert(!pRootFrame
|| pRootFrame->GetFieldmarkMode() != sw::FieldmarkMode::ShowBoth
|| SwRedlineTable::npos != nRedlPos || m_pMergedPara->extents.size() <= 1);
}
if (!(pExtInp || m_pMergedPara || SwRedlineTable::npos != nRedlPos))
return;
const std::vector<ExtTextInputAttr> *pArr = nullptr;
if( pExtInp )
{
pArr = &pExtInp->GetAttrs();
Seek( TextFrameIndex(0) );
}
m_pRedline.reset(new SwRedlineItr( rTextNode, *m_pFont, m_aAttrHandler, nRedlPos,
(pRootFrame && pRootFrame->IsHideRedlines())
? SwRedlineItr::Mode::Hide
: bShow
? SwRedlineItr::Mode::Show
: SwRedlineItr::Mode::Ignore,
pArr, pExtInp ? pExtInp->Start() : nullptr));
if( m_pRedline->IsOn() )
++m_nChgCnt;
}
// The Redline-Iterator
// The following information/states exist in RedlineIterator:
//
// m_nFirst is the first index of RedlineTable, which overlaps with the paragraph.
//
// m_nAct is the currently active (if m_bOn is set) or the next possible index.
// m_nStart and m_nEnd give you the borders of the object within the paragraph.
//
// If m_bOn is set, the font has been manipulated according to it.
//
// If m_nAct is set to SwRedlineTable::npos (via Reset()), then currently no
// Redline is active, m_nStart and m_nEnd are invalid.
SwRedlineItr::SwRedlineItr( const SwTextNode& rTextNd, SwFont& rFnt,
SwAttrHandler& rAH, sal_Int32 nRed,
Mode const mode,
const std::vector<ExtTextInputAttr> *pArr,
SwPosition const*const pExtInputStart)
: m_rDoc( rTextNd.GetDoc() )
, m_rAttrHandler( rAH )
, m_nNdIdx( rTextNd.GetIndex() )
, m_nFirst( nRed )
, m_nAct( SwRedlineTable::npos )
, m_nStart( COMPLETE_STRING )
, m_nEnd( COMPLETE_STRING )
, m_bOn( false )
, m_eMode( mode )
{
if( pArr )
{
assert(pExtInputStart);
m_pExt.reset( new SwExtend(*pArr, pExtInputStart->GetNodeIndex(),
pExtInputStart->GetContentIndex()) );
}
else
m_pExt = nullptr;
assert(m_pExt || m_eMode != Mode::Ignore); // only create if necessary
Seek(rFnt, m_nNdIdx, 0, COMPLETE_STRING);
}
SwRedlineItr::~SwRedlineItr() COVERITY_NOEXCEPT_FALSE
{
Clear( nullptr );
m_pExt.reset();
}
// The return value of SwRedlineItr::Seek tells you if the current font
// has been manipulated by leaving (-1) or accessing (+1) of a section
short SwRedlineItr::Seek(SwFont& rFnt,
SwNodeOffset const nNode, sal_Int32 const nNew, sal_Int32 const nOld)
{
short nRet = 0;
if( ExtOn() )
return 0; // Abbreviation: if we're within an ExtendTextInputs
// there can't be other changes of attributes (not even by redlining)
if (m_eMode == Mode::Show)
{
if (m_bOn)
{
if (nNew >= m_nEnd)
{
--nRet;
Clear_( &rFnt ); // We go behind the current section
++m_nAct; // and check the next one
}
else if (nNew < m_nStart)
{
--nRet;
Clear_( &rFnt ); // We go in front of the current section
if (m_nAct > m_nFirst)
m_nAct = m_nFirst; // the test has to run from the beginning
else
return nRet + EnterExtend(rFnt, nNode, nNew); // There's none prior to us
}
else
return nRet + EnterExtend(rFnt, nNode, nNew); // We stayed in the same section
}
if (SwRedlineTable::npos == m_nAct || nOld > nNew)
m_nAct = m_nFirst;
m_nStart = COMPLETE_STRING;
m_nEnd = COMPLETE_STRING;
const SwRedlineTable& rTable = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable();
for ( ; m_nAct < rTable.size() ; ++m_nAct)
{
rTable[ m_nAct ]->CalcStartEnd(nNode, m_nStart, m_nEnd);
if (nNew < m_nEnd)
{
if (nNew >= m_nStart) // only possible candidate
{
m_bOn = true;
const SwRangeRedline *pRed = rTable[ m_nAct ];
if (m_pSet)
m_pSet->ClearItem();
else
{
SwAttrPool& rPool =
const_cast<SwDoc&>(m_rDoc).GetAttrPool();
m_pSet = std::make_unique<SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END-1>>(rPool);
}
if( 1 < pRed->GetStackCount() )
FillHints( pRed->GetAuthor( 1 ), pRed->GetType( 1 ) );
FillHints( pRed->GetAuthor(), pRed->GetType() );
SfxWhichIter aIter( *m_pSet );
// moved text: dark green with double underline or strikethrough
bool bDisplayMovedTextInGreen = officecfg::Office::Writer::Comparison::DisplayMovedTextInGreen::get();
if ( bDisplayMovedTextInGreen && pRed->IsMoved() )
{
m_pSet->Put(SvxColorItem( COL_GREEN, RES_CHRATR_COLOR ));
if (SfxItemState::SET == m_pSet->GetItemState(RES_CHRATR_CROSSEDOUT, true))
m_pSet->Put(SvxCrossedOutItem( STRIKEOUT_DOUBLE, RES_CHRATR_CROSSEDOUT ));
else
m_pSet->Put(SvxUnderlineItem( LINESTYLE_DOUBLE, RES_CHRATR_UNDERLINE ));
}
sal_uInt16 nWhich = aIter.FirstWhich();
while( nWhich )
{
const SfxPoolItem* pItem;
if( ( nWhich < RES_CHRATR_END ) &&
( SfxItemState::SET == aIter.GetItemState( true, &pItem ) ) )
{
SwTextAttr* pAttr = MakeRedlineTextAttr(
const_cast<SwDoc&>(m_rDoc),
*const_cast<SfxPoolItem*>(pItem) );
pAttr->SetPriorityAttr( true );
m_Hints.push_back(pAttr);
m_rAttrHandler.PushAndChg( *pAttr, rFnt );
}
nWhich = aIter.NextWhich();
}
++nRet;
}
break;
}
m_nStart = COMPLETE_STRING;
m_nEnd = COMPLETE_STRING;
}
}
else if (m_eMode == Mode::Hide)
{ // ... just iterate to update m_nAct for GetNextRedln();
// there is no need to care about formatting in this mode
if (m_nAct == SwRedlineTable::npos || nOld == COMPLETE_STRING)
{ // reset, or move backward
m_nAct = m_nFirst;
}
for ( ; m_nAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++m_nAct)
{ // only Start matters in this mode
// Seeks until it finds a RL that starts at or behind the seek pos.
// - then update m_nStart/m_nEnd to the intersection of it with the
// current node (if any).
// The only way to skip to a different node is if there is a Delete
// RL, so if there is no intersection we'll never skip again.
// Note: here, assume that delete can't nest inside delete!
SwRangeRedline const*const pRedline(
m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[m_nAct]);
SwPosition const*const pStart(pRedline->Start());
if (pRedline->GetType() == RedlineType::Delete
&& (nNode < pStart->GetNodeIndex()
|| (nNode == pStart->GetNodeIndex()
&& nNew <= pStart->GetContentIndex())))
{
pRedline->CalcStartEnd(nNode, m_nStart, m_nEnd);
break;
}
m_nStart = COMPLETE_STRING;
m_nEnd = COMPLETE_STRING;
}
}
return nRet + EnterExtend(rFnt, nNode, nNew);
}
void SwRedlineItr::FillHints( std::size_t nAuthor, RedlineType eType )
{
switch ( eType )
{
case RedlineType::Insert:
SW_MOD()->GetInsertAuthorAttr(nAuthor, *m_pSet);
break;
case RedlineType::Delete:
SW_MOD()->GetDeletedAuthorAttr(nAuthor, *m_pSet);
break;
case RedlineType::Format:
case RedlineType::FmtColl:
SW_MOD()->GetFormatAuthorAttr(nAuthor, *m_pSet);
break;
default:
break;
}
}
void SwRedlineItr::ChangeTextAttr( SwFont* pFnt, SwTextAttr const &rHt, bool bChg )
{
OSL_ENSURE( IsOn(), "SwRedlineItr::ChangeTextAttr: Off?" );
if (m_eMode != Mode::Show && !m_pExt)
return;
if( bChg )
{
if (m_pExt && m_pExt->IsOn())
m_rAttrHandler.PushAndChg( rHt, *m_pExt->GetFont() );
else
m_rAttrHandler.PushAndChg( rHt, *pFnt );
}
else
{
OSL_ENSURE( ! m_pExt || ! m_pExt->IsOn(), "Pop of attribute during opened extension" );
m_rAttrHandler.PopAndChg( rHt, *pFnt );
}
}
void SwRedlineItr::Clear_( SwFont* pFnt )
{
OSL_ENSURE( m_bOn, "SwRedlineItr::Clear: Off?" );
m_bOn = false;
for (auto const& hint : m_Hints)
{
if( pFnt )
m_rAttrHandler.PopAndChg( *hint, *pFnt );
else
m_rAttrHandler.Pop( *hint );
SwTextAttr::Destroy(hint);
}
m_Hints.clear();
}
/// Ignore mode: does nothing.
/// Show mode: returns end of redline if currently in one, or start of next
/// Hide mode: returns start of next redline in current node, plus (if it's a
/// Delete) its end position and number of consecutive RLs
std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>>
SwRedlineItr::GetNextRedln(sal_Int32 nNext, SwTextNode const*const pNode,
SwRedlineTable::size_type & rAct)
{
sal_Int32 nStart(m_nStart);
sal_Int32 nEnd(m_nEnd);
nNext = NextExtend(pNode->GetIndex(), nNext);
if (m_eMode == Mode::Ignore || SwRedlineTable::npos == m_nFirst)
return std::make_pair(nNext, std::make_pair(nullptr, 0));
if (SwRedlineTable::npos == rAct)
{
rAct = m_nFirst;
}
if (rAct != m_nAct)
{
while (rAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())
{
SwRangeRedline const*const pRedline(
m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct]);
pRedline->CalcStartEnd(pNode->GetIndex(), nStart, nEnd);
if (m_eMode != Mode::Hide
|| pRedline->GetType() == RedlineType::Delete)
{
break;
}
++rAct; // Hide mode: search a Delete RL
}
}
if (rAct == m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())
{
return std::make_pair(nNext, std::make_pair(nullptr, 0)); // no Delete here
}
if (m_bOn || (m_eMode == Mode::Show && nStart == 0))
{ // in Ignore mode, the end of redlines isn't relevant, except as returned in the second in the pair!
if (nEnd < nNext)
nNext = nEnd;
}
else if (nStart <= nNext)
{
if (m_eMode == Mode::Show)
{
nNext = nStart;
}
else
{
assert(m_eMode == Mode::Hide);
SwRangeRedline const* pRedline(
m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct]);
assert(pRedline->GetType() == RedlineType::Delete); //?
if (pRedline->GetType() == RedlineType::Delete)
{
nNext = nStart;
size_t nSkipped(1); // (consecutive) candidates to be skipped
while (rAct + nSkipped <
m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())
{
SwRangeRedline const*const pNext =
m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct + nSkipped];
if (*pRedline->End() < *pNext->Start())
{
break; // done for now
}
else if (*pNext->Start() == *pRedline->End() &&
pNext->GetType() == RedlineType::Delete)
{
// consecutive delete - continue
pRedline = pNext;
}
++nSkipped;
}
return std::make_pair(nNext, std::make_pair(pRedline, nSkipped));
}
}
}
return std::make_pair(nNext, std::make_pair(nullptr, 0));
}
bool SwRedlineItr::ChkSpecialUnderline_() const
{
// If the underlining or the escapement is caused by redlining,
// we always apply the SpecialUnderlining, i.e. the underlining
// below the base line
for (SwTextAttr* pHint : m_Hints)
{
const sal_uInt16 nWhich = pHint->Which();
if( RES_CHRATR_UNDERLINE == nWhich ||
RES_CHRATR_ESCAPEMENT == nWhich )
return true;
}
return false;
}
bool SwRedlineItr::CheckLine(
SwNodeOffset const nStartNode, sal_Int32 const nChkStart,
SwNodeOffset const nEndNode, sal_Int32 nChkEnd, OUString& rRedlineText,
bool& bRedlineEnd, RedlineType& eRedlineEnd, size_t* pAuthorAtPos)
{
// note: previously this would return true in the (!m_bShow && m_pExt)
// case, but surely that was a bug?
if (m_nFirst == SwRedlineTable::npos || m_eMode != Mode::Show)
return false;
if( nChkEnd == nChkStart && pAuthorAtPos == nullptr ) // empty lines look one char further
++nChkEnd;
sal_Int32 nOldStart = m_nStart;
sal_Int32 nOldEnd = m_nEnd;
SwRedlineTable::size_type const nOldAct = m_nAct;
bool bRet = bRedlineEnd = false;
eRedlineEnd = RedlineType::None;
SwPosition const start(*m_rDoc.GetNodes()[nStartNode]->GetContentNode(), nChkStart);
SwPosition const end(*m_rDoc.GetNodes()[nEndNode]->GetContentNode(), nChkEnd);
SwRangeRedline const* pPrevRedline = nullptr;
bool isBreak(false);
for (m_nAct = m_nFirst; m_nAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++m_nAct)
{
SwRangeRedline const*const pRedline(
m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ m_nAct ] );
// collect text of the hidden redlines at the end of the line
bool isExtendText(false);
switch (ComparePosition(*pRedline->Start(), *pRedline->End(), start, end))
{
case SwComparePosition::Behind:
isBreak = true;
break;
case SwComparePosition::OverlapBehind:
case SwComparePosition::CollideStart:
case SwComparePosition::Outside:
case SwComparePosition::Equal:
// store redlining at line end (for line break formatting)
eRedlineEnd = pRedline->GetType();
bRedlineEnd = true;
isBreak = true;
if (pAuthorAtPos)
*pAuthorAtPos = pRedline->GetAuthor();
[[fallthrough]];
case SwComparePosition::OverlapBefore:
case SwComparePosition::CollideEnd:
case SwComparePosition::Inside:
{
bRet = true;
// start to collect text of invisible redlines for ChangesInMargin layout
if (rRedlineText.isEmpty() && !pRedline->IsVisible())
{
rRedlineText = const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true);
pPrevRedline = pRedline;
isExtendText = true;
}
// join the text of the next invisible redlines in the same position
// i.e. characters deleted by pressing backspace or delete
else if (pPrevRedline && !pRedline->IsVisible() &&
*pRedline->Start() == *pPrevRedline->Start() && *pRedline->End() == *pPrevRedline->End() )
{
OUString sExtendText(const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true));
if (!sExtendText.isEmpty())
{
if (rRedlineText.getLength() < 12)
{
// TODO: remove extra space from GetDescr(true),
// but show deletion of paragraph or line break
rRedlineText = rRedlineText +
const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true).subView(1);
}
else
rRedlineText = OUString::Concat(rRedlineText.subView(0, rRedlineText.getLength() - 3)) + "...";
}
isExtendText = true;
}
break;
}
case SwComparePosition::Before:
break; // -Werror=switch
}
if (isBreak && !isExtendText)
{
break;
}
}
m_nStart = nOldStart;
m_nEnd = nOldEnd;
m_nAct = nOldAct;
return bRet;
}
void SwExtend::ActualizeFont( SwFont &rFnt, ExtTextInputAttr nAttr )
{
if ( nAttr & ExtTextInputAttr::Underline )
rFnt.SetUnderline( LINESTYLE_SINGLE );
else if ( nAttr & ExtTextInputAttr::DoubleUnderline )
rFnt.SetUnderline( LINESTYLE_DOUBLE );
else if ( nAttr & ExtTextInputAttr::BoldUnderline )
rFnt.SetUnderline( LINESTYLE_BOLD );
else if ( nAttr & ExtTextInputAttr::DottedUnderline )
rFnt.SetUnderline( LINESTYLE_DOTTED );
else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
rFnt.SetUnderline( LINESTYLE_DOTTED );
if ( nAttr & ExtTextInputAttr::RedText )
rFnt.SetColor( COL_RED );
if ( nAttr & ExtTextInputAttr::Highlight )
{
const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
rFnt.SetColor( rStyleSettings.GetHighlightTextColor() );
rFnt.SetBackColor( rStyleSettings.GetHighlightColor() );
}
if ( nAttr & ExtTextInputAttr::GrayWaveline )
rFnt.SetGreyWave( true );
}
short SwExtend::Enter(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew)
{
OSL_ENSURE( !m_pFont, "SwExtend: Enter with Font" );
if (nNode != m_nNode)
return 0;
OSL_ENSURE( !Inside(), "SwExtend: Enter without Leave" );
m_nPos = nNew;
if( Inside() )
{
m_pFont.reset( new SwFont(rFnt) );
ActualizeFont( rFnt, m_rArr[m_nPos - m_nStart] );
return 1;
}
return 0;
}
bool SwExtend::Leave_(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew)
{
OSL_ENSURE(nNode == m_nNode && Inside(), "SwExtend: Leave without Enter");
if (nNode != m_nNode)
return true;
const ExtTextInputAttr nOldAttr = m_rArr[m_nPos - m_nStart];
m_nPos = nNew;
if( Inside() )
{ // We stayed within the ExtendText-section
const ExtTextInputAttr nAttr = m_rArr[m_nPos - m_nStart];
if( nOldAttr != nAttr ) // Is there an (inner) change of attributes?
{
rFnt = *m_pFont;
ActualizeFont( rFnt, nAttr );
}
}
else
{
rFnt = *m_pFont;
m_pFont.reset();
return true;
}
return false;
}
sal_Int32 SwExtend::Next(SwNodeOffset const nNode, sal_Int32 nNext)
{
if (nNode != m_nNode)
return nNext;
if (m_nPos < m_nStart)
{
if (nNext > m_nStart)
nNext = m_nStart;
}
else if (m_nPos < m_nEnd)
{
sal_Int32 nIdx = m_nPos - m_nStart;
const ExtTextInputAttr nAttr = m_rArr[ nIdx ];
while (o3tl::make_unsigned(++nIdx) < m_rArr.size() && nAttr == m_rArr[nIdx])
; //nothing
nIdx = nIdx + m_nStart;
if( nNext > nIdx )
nNext = nIdx;
}
return nNext;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.