1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <string_view>
24 #include <hintids.hxx>
25 #include <o3tl/safeint.hxx>
26 #include <svl/whiter.hxx>
27 #include <com/sun/star/i18n/ScriptType.hpp>
28 #include <scriptinfo.hxx>
29 #include <swmodule.hxx>
30 #include <redline.hxx>
31 #include <txatbase.hxx>
36 #include <IDocumentRedlineAccess.hxx>
37 #include <IDocumentLayoutAccess.hxx>
38 #include <IDocumentMarkAccess.hxx>
40 #include <bookmark.hxx>
41 #include <rootfrm.hxx>
42 #include <breakit.hxx>
43 #include <vcl/commandevent.hxx>
44 #include <vcl/settings.hxx>
47 #include <vcl/svapp.hxx>
48 #include "redlnitr.hxx"
49 #include <extinput.hxx>
50 #include <fmtpdsc.hxx>
51 #include <editeng/charhiddenitem.hxx>
52 #include <editeng/colritem.hxx>
53 #include <editeng/crossedoutitem.hxx>
54 #include <editeng/formatbreakitem.hxx>
55 #include <editeng/udlnitem.hxx>
57 using namespace ::com::sun::star
;
64 IDocumentRedlineAccess
const& m_rIDRA
;
65 IDocumentMarkAccess
const& m_rIDMA
;
66 bool const m_isHideRedlines
;
67 sw::FieldmarkMode
const m_eFieldmarkMode
;
68 bool const m_isHideParagraphBreaks
;
69 SwPosition
const m_Start
;
71 SwRedlineTable::size_type m_RedlineIndex
;
73 std::pair
<sw::mark::IFieldmark
const*, std::optional
<SwPosition
>> m_Fieldmark
;
74 std::optional
<SwPosition
> m_oNextFieldmarkHide
;
75 /// previous paragraph break - because m_pStartPos/EndPos are non-owning
76 std::optional
<std::pair
<SwPosition
, SwPosition
>> m_oParagraphBreak
;
77 /// current start/end pair
78 SwPosition
const* m_pStartPos
;
79 SwPosition
const* m_pEndPos
;
82 SwPosition
const* GetStartPos() const { return m_pStartPos
; }
83 SwPosition
const* GetEndPos() const { return m_pEndPos
; }
85 HideIterator(SwTextNode
& rTextNode
,
86 bool const isHideRedlines
, sw::FieldmarkMode
const eMode
,
87 sw::ParagraphBreakMode
const ePBMode
)
88 : m_rIDRA(rTextNode
.getIDocumentRedlineAccess())
89 , m_rIDMA(*rTextNode
.getIDocumentMarkAccess())
90 , m_isHideRedlines(isHideRedlines
)
91 , m_eFieldmarkMode(eMode
)
92 , m_isHideParagraphBreaks(ePBMode
== sw::ParagraphBreakMode::Hidden
)
93 , m_Start(rTextNode
, 0)
94 , m_RedlineIndex(isHideRedlines
? m_rIDRA
.GetRedlinePos(rTextNode
, RedlineType::Any
) : SwRedlineTable::npos
)
95 , m_pStartPos(nullptr)
100 // delete redlines and fieldmarks can't overlap, due to sw::CalcBreaks()
101 // and no combining of adjacent redlines
102 // -> dummy chars are delete-redlined *iff* entire fieldmark is
103 // Note: caller is responsible for checking for immediately adjacent hides
106 SwPosition
const* pNextRedlineHide(nullptr);
108 if (m_isHideRedlines
)
110 // position on current or next redline
111 for (; m_RedlineIndex
< m_rIDRA
.GetRedlineTable().size(); ++m_RedlineIndex
)
113 SwRangeRedline
const*const pRed
= m_rIDRA
.GetRedlineTable()[m_RedlineIndex
];
115 if (m_pEndPos
->GetNodeIndex() < pRed
->Start()->GetNodeIndex())
118 if (pRed
->GetType() != RedlineType::Delete
)
121 auto [pStart
, pEnd
] = pRed
->StartEnd(); // SwPosition*
122 if (*pStart
== *pEnd
)
123 { // only allowed while moving (either way?)
124 // assert(IDocumentRedlineAccess::IsHideChanges(rIDRA.GetRedlineFlags()));
127 if (pStart
->GetNode().IsTableNode())
129 assert(pEnd
->GetNode() == m_Start
.GetNode() && pEnd
->GetContentIndex() == 0);
130 continue; // known pathology, ignore it
132 if (*m_pEndPos
<= *pStart
)
134 pNextRedlineHide
= pStart
;
135 break; // the next one
140 // position on current or next fieldmark
141 m_oNextFieldmarkHide
.reset();
142 if (m_eFieldmarkMode
!= sw::FieldmarkMode::ShowBoth
)
144 sal_Unicode
const magic(m_eFieldmarkMode
== sw::FieldmarkMode::ShowResult
145 ? CH_TXT_ATR_FIELDSTART
146 : CH_TXT_ATR_FIELDSEP
);
147 SwTextNode
* pTextNode
= m_pEndPos
->GetNode().GetTextNode();
148 sal_Int32
const nPos
= pTextNode
? pTextNode
->GetText().indexOf(
149 magic
, m_pEndPos
->GetContentIndex()) : -1;
152 m_oNextFieldmarkHide
.emplace(*pTextNode
, nPos
);
153 sw::mark::IFieldmark
const*const pFieldmark(
154 m_eFieldmarkMode
== sw::FieldmarkMode::ShowResult
155 ? m_rIDMA
.getFieldmarkAt(*m_oNextFieldmarkHide
)
156 : m_rIDMA
.getInnerFieldmarkFor(*m_oNextFieldmarkHide
));
158 m_Fieldmark
.first
= pFieldmark
;
159 // for cursor travelling, there should be 2 visible chars;
160 // whichever char is hidden, the cursor travelling needs to
161 // be adapted in any case to skip in some situation or other;
162 // always hide the CH_TXT_ATR_FIELDSEP for now
163 if (m_eFieldmarkMode
== sw::FieldmarkMode::ShowResult
)
165 m_Fieldmark
.second
.emplace(
166 sw::mark::FindFieldSep(*m_Fieldmark
.first
));
167 m_Fieldmark
.second
->AdjustContent(+1);
168 m_oNextFieldmarkHide
->AdjustContent(+1); // skip start
172 m_Fieldmark
.second
.emplace(pFieldmark
->GetMarkEnd());
173 m_Fieldmark
.second
->AdjustContent(-1);
178 // == can happen only if redline starts inside field command, and in
179 // that case redline will end before field separator
180 assert(!pNextRedlineHide
|| !m_oNextFieldmarkHide
181 || *pNextRedlineHide
!= *m_oNextFieldmarkHide
182 || *m_rIDRA
.GetRedlineTable()[m_RedlineIndex
]->End() < *m_Fieldmark
.second
);
184 && (!m_oNextFieldmarkHide
|| *pNextRedlineHide
< *m_oNextFieldmarkHide
))
186 SwRangeRedline
const*const pRed(m_rIDRA
.GetRedlineTable()[m_RedlineIndex
]);
187 m_pStartPos
= pRed
->Start();
188 m_pEndPos
= pRed
->End();
192 else if (m_oNextFieldmarkHide
)
194 assert(!pNextRedlineHide
|| *m_oNextFieldmarkHide
<= *pNextRedlineHide
);
195 m_pStartPos
= &*m_oNextFieldmarkHide
;
196 m_pEndPos
= &*m_Fieldmark
.second
;
201 assert(!pNextRedlineHide
&& !m_oNextFieldmarkHide
);
202 auto const hasHiddenItem
= [](auto const& rNode
) {
203 auto const& rpSet(rNode
.GetAttr(RES_PARATR_LIST_AUTOFMT
).GetStyleHandle());
204 return rpSet
? rpSet
->Get(RES_CHRATR_HIDDEN
).GetValue() : false;
206 auto const hasBreakBefore
= [](SwTextNode
const& rNode
) {
207 if (rNode
.GetAttr(RES_PAGEDESC
).GetPageDesc())
211 switch (rNode
.GetAttr(RES_BREAK
).GetBreak())
213 case SvxBreak::ColumnBefore
:
214 case SvxBreak::ColumnBoth
:
215 case SvxBreak::PageBefore
:
216 case SvxBreak::PageBoth
:
223 auto const hasBreakAfter
= [](SwTextNode
const& rNode
) {
224 switch (rNode
.GetAttr(RES_BREAK
).GetBreak())
226 case SvxBreak::ColumnAfter
:
227 case SvxBreak::ColumnBoth
:
228 case SvxBreak::PageAfter
:
229 case SvxBreak::PageBoth
:
236 if (m_isHideParagraphBreaks
237 && m_pEndPos
->GetNode().IsTextNode() // ooo27109-1.sxw
238 // only merge if next node is also text node
239 && m_pEndPos
->GetNodes()[m_pEndPos
->GetNodeIndex()+1]->IsTextNode()
240 && hasHiddenItem(*m_pEndPos
->GetNode().GetTextNode())
241 // no merge if there's a page break on any node
242 && !hasBreakBefore(*m_pEndPos
->GetNodes()[m_pEndPos
->GetNodeIndex()+1]->GetTextNode())
243 // first node, see SwTextFrame::GetBreak()
244 && !hasBreakAfter(*m_Start
.GetNode().GetTextNode()))
246 m_oParagraphBreak
.emplace(
247 SwPosition(*m_pEndPos
->GetNode().GetTextNode(), m_pEndPos
->GetNode().GetTextNode()->Len()),
248 SwPosition(*m_pEndPos
->GetNodes()[m_pEndPos
->GetNodeIndex()+1]->GetTextNode(), 0));
249 m_pStartPos
= &m_oParagraphBreak
->first
;
250 m_pEndPos
= &m_oParagraphBreak
->second
;
255 m_pStartPos
= nullptr;
267 std::unique_ptr
<sw::MergedPara
>
268 CheckParaRedlineMerge(SwTextFrame
& rFrame
, SwTextNode
& rTextNode
,
269 FrameMode
const eMode
)
271 if (!rFrame
.getRootFrame()->HasMergedParas())
275 bool bHaveRedlines(false);
276 std::vector
<SwTextNode
*> nodes
{ &rTextNode
};
277 std::vector
<SwTableNode
*> tables
;
278 std::vector
<SwSectionNode
*> sections
;
279 std::vector
<sw::Extent
> extents
;
280 OUStringBuffer mergedText
;
281 SwTextNode
* pParaPropsNode(nullptr);
282 SwTextNode
* pNode(&rTextNode
);
283 sal_Int32
nLastEnd(0);
284 for (auto iter
= HideIterator(rTextNode
,
285 rFrame
.getRootFrame()->IsHideRedlines(),
286 rFrame
.getRootFrame()->GetFieldmarkMode(),
287 rFrame
.getRootFrame()->GetParagraphBreakMode());
290 SwPosition
const*const pStart(iter
.GetStartPos());
291 SwPosition
const*const pEnd(iter
.GetEndPos());
292 bHaveRedlines
= true;
293 assert(pNode
!= &rTextNode
|| &pStart
->GetNode() == &rTextNode
); // detect calls with wrong start node
294 if (pStart
->GetContentIndex() != nLastEnd
) // not 0 so we eliminate adjacent deletes
296 extents
.emplace_back(pNode
, nLastEnd
, pStart
->GetContentIndex());
297 mergedText
.append(pNode
->GetText().subView(nLastEnd
, pStart
->GetContentIndex() - nLastEnd
));
299 if (&pEnd
->GetNode() != pNode
)
301 if (pNode
== &rTextNode
)
303 pNode
->SetRedlineMergeFlag(SwNode::Merge::First
);
304 } // else: was already set before
306 for (SwNodeOffset j
= pNode
->GetIndex() + 1; j
< pEnd
->GetNodeIndex(); ++j
)
308 SwNode
*const pTmp(pNode
->GetNodes()[j
]);
311 if (pTmp
->IsTextNode())
313 nodes
.push_back(pTmp
->GetTextNode());
315 else if (pTmp
->IsTableNode())
317 tables
.push_back(pTmp
->GetTableNode());
319 else if (pTmp
->IsSectionNode())
321 sections
.push_back(pTmp
->GetSectionNode());
324 if (pTmp
->IsStartNode())
328 else if (pTmp
->IsEndNode())
332 pTmp
->SetRedlineMergeFlag(SwNode::Merge::Hidden
);
334 // note: in DelLastPara() case, the end node is not actually merged
335 // and is likely a SwTableNode!
336 if (!pEnd
->GetNode().IsTextNode())
338 assert(pEnd
->GetNode() != pStart
->GetNode());
339 // must set pNode too because it will mark the last node
340 pNode
= nodes
.back();
341 assert(pNode
== pNode
->GetNodes()[pEnd
->GetNodeIndex() - 1]);
342 if (pNode
!= &rTextNode
)
343 { // something might depend on last merged one being NonFirst?
344 pNode
->SetRedlineMergeFlag(SwNode::Merge::NonFirst
);
346 nLastEnd
= pNode
->Len();
350 pNode
= pEnd
->GetNode().GetTextNode();
351 nodes
.push_back(pNode
);
352 pNode
->SetRedlineMergeFlag(SwNode::Merge::NonFirst
);
353 nLastEnd
= pEnd
->GetContentIndex();
358 nLastEnd
= pEnd
->GetContentIndex();
361 if (pNode
== &rTextNode
)
363 if (rTextNode
.GetRedlineMergeFlag() != SwNode::Merge::None
)
365 rTextNode
.SetRedlineMergeFlag(SwNode::Merge::None
);
368 // Reset flag of the following text node since we know it's not merged;
369 // also any table/sections in between.
370 // * the following SwTextNode is in same nodes section as pNode (nLevel=0)
371 // * the start nodes that don't have a SwTextNode before them
372 // on their level, and their corresponding end nodes
373 // * the first SwTextNode inside each start node of the previous point
374 // Other (non-first) SwTextNodes in nested sections shouldn't be reset!
376 for (SwNodeOffset j
= pNode
->GetIndex() + 1; j
< pNode
->GetNodes().Count(); ++j
)
378 SwNode
*const pTmp(pNode
->GetNodes()[j
]);
379 if (!pTmp
->IsCreateFrameWhenHidingRedlines())
380 { // clear stale flag caused by editing with redlines shown
381 pTmp
->SetRedlineMergeFlag(SwNode::Merge::None
);
383 if (pTmp
->IsStartNode())
387 else if (pTmp
->IsEndNode())
391 break; // there is no following text node; avoid leaving section
395 else if (pTmp
->IsTextNode())
402 { // skip everything other than 1st text node in section!
403 j
= pTmp
->EndOfSectionIndex() - 1; // will be incremented again
409 if (rTextNode
.IsInList() && !rTextNode
.GetNum(rFrame
.getRootFrame()))
411 rTextNode
.AddToListRLHidden(); // try to add it...
415 if (nLastEnd
!= pNode
->Len())
417 extents
.emplace_back(pNode
, nLastEnd
, pNode
->Len());
418 mergedText
.append(pNode
->GetText().subView(nLastEnd
, pNode
->Len() - nLastEnd
));
420 if (extents
.empty()) // there was no text anywhere
422 assert(mergedText
.isEmpty());
423 pParaPropsNode
= pNode
; // if every node is empty, the last one wins
427 assert(!mergedText
.isEmpty());
428 pParaPropsNode
= extents
.begin()->pNode
; // para props from first node that isn't empty
430 // pParaPropsNode = &rTextNode; // well, actually...
431 // keep lists up to date with visible nodes
432 if (pParaPropsNode
->IsInList() && !pParaPropsNode
->GetNum(rFrame
.getRootFrame()))
434 pParaPropsNode
->AddToListRLHidden(); // try to add it...
436 for (auto const pTextNode
: nodes
)
438 if (pTextNode
!= pParaPropsNode
)
440 pTextNode
->RemoveFromListRLHidden();
443 if (eMode
== FrameMode::Existing
)
445 // remove existing footnote frames for first node;
446 // for non-first nodes with own frames, DelFrames will remove all
447 // (could possibly call lcl_ChangeFootnoteRef, not sure if worth it)
448 // note: must be done *before* changing listeners!
449 // for non-first nodes that are already merged with this frame,
450 // need to remove here too, otherwise footnotes can be removed only
451 // by lucky accident, e.g. TruncLines().
452 auto itExtent(extents
.begin());
453 for (auto const pTextNode
: nodes
)
456 std::vector
<std::pair
<sal_Int32
, sal_Int32
>> hidden
;
457 for ( ; itExtent
!= extents
.end(); ++itExtent
)
459 if (itExtent
->pNode
!= pTextNode
)
463 if (itExtent
->nStart
!= 0)
465 assert(itExtent
->nStart
!= nLast
);
466 hidden
.emplace_back(nLast
, itExtent
->nStart
);
468 nLast
= itExtent
->nEnd
;
470 if (nLast
!= pTextNode
->Len())
472 hidden
.emplace_back(nLast
, pTextNode
->Len());
474 sw::RemoveFootnotesForNode(*rFrame
.getRootFrame(), *pTextNode
, &hidden
);
476 // unfortunately DelFrames() must be done before StartListening too,
477 // otherwise footnotes cannot be deleted by SwTextFootnote::DelFrames!
478 auto const end(--nodes
.rend());
479 for (auto iter
= nodes
.rbegin(); iter
!= end
; ++iter
)
481 (**iter
).DelFrames(rFrame
.getRootFrame());
483 // also delete tables & sections here; not necessary, but convenient
484 for (auto const pTableNode
: tables
)
486 pTableNode
->DelFrames(rFrame
.getRootFrame());
488 for (auto const pSectionNode
: sections
)
490 pSectionNode
->GetSection().GetFormat()->DelFrames(/*rFrame.getRootFrame()*/);
493 auto pRet(std::make_unique
<sw::MergedPara
>(rFrame
, std::move(extents
),
494 mergedText
.makeStringAndClear(), pParaPropsNode
, &rTextNode
,
496 for (SwTextNode
* pTmp
: nodes
)
498 pRet
->listener
.StartListening(pTmp
);
500 rFrame
.EndListeningAll();
506 void SwAttrIter::InitFontAndAttrHandler(
507 SwTextNode
const& rPropsNode
,
508 SwTextNode
const& rTextNode
,
509 std::u16string_view aText
,
510 bool const*const pbVertLayout
,
511 bool const*const pbVertLayoutLRBT
)
513 // Build a font matching the default paragraph style:
514 SwFontAccess
aFontAccess( &rPropsNode
.GetAnyFormatColl(), m_pViewShell
);
515 // It is possible that Init is called more than once, e.g., in a
516 // SwTextFrame::FormatOnceMore situation or (since sw_redlinehide)
517 // from SwAttrIter::Seek(); in the latter case SwTextSizeInfo::m_pFnt
518 // is an alias of m_pFont so it must not be deleted!
521 *m_pFont
= aFontAccess
.Get()->GetFont();
525 m_pFont
= new SwFont( aFontAccess
.Get()->GetFont() );
528 // set font to vertical if frame layout is vertical
529 // if it's a re-init, the vert flag never changes
530 bool bVertLayoutLRBT
= false;
531 if (pbVertLayoutLRBT
)
532 bVertLayoutLRBT
= *pbVertLayoutLRBT
;
533 if (pbVertLayout
? *pbVertLayout
: m_aAttrHandler
.IsVertLayout())
535 m_pFont
->SetVertical(m_pFont
->GetOrientation(), true, bVertLayoutLRBT
);
538 // Initialize the default attribute of the attribute handler
539 // based on the attribute array cached together with the font.
540 // If any further attributes for the paragraph are given in pAttrSet
541 // consider them during construction of the default array, and apply
543 m_aAttrHandler
.Init(aFontAccess
.Get()->GetDefault(), rTextNode
.GetpSwAttrSet(),
544 *rTextNode
.getIDocumentSettingAccess(), m_pViewShell
, *m_pFont
,
545 pbVertLayout
? *pbVertLayout
: m_aAttrHandler
.IsVertLayout(),
548 m_aFontCacheIds
[SwFontScript::Latin
] = m_aFontCacheIds
[SwFontScript::CJK
] = m_aFontCacheIds
[SwFontScript::CTL
] = nullptr;
550 assert(g_pBreakIt
&& g_pBreakIt
->GetBreakIter().is());
552 m_pFont
->SetActual( m_pScriptInfo
->WhichFont(TextFrameIndex(0)) );
554 TextFrameIndex
nChg(0);
559 if ( nCnt
>= m_pScriptInfo
->CountScriptChg() )
561 nChg
= m_pScriptInfo
->GetScriptChg( nCnt
);
562 SwFontScript nTmp
= SW_SCRIPTS
;
563 switch ( m_pScriptInfo
->GetScriptType( nCnt
++ ) ) {
564 case i18n::ScriptType::ASIAN
:
565 if( !m_aFontCacheIds
[SwFontScript::CJK
] ) nTmp
= SwFontScript::CJK
;
567 case i18n::ScriptType::COMPLEX
:
568 if( !m_aFontCacheIds
[SwFontScript::CTL
] ) nTmp
= SwFontScript::CTL
;
571 if( !m_aFontCacheIds
[SwFontScript::Latin
] ) nTmp
= SwFontScript::Latin
;
573 if( nTmp
< SW_SCRIPTS
)
575 m_pFont
->CheckFontCacheId( m_pViewShell
, nTmp
);
576 m_pFont
->GetFontCacheId( m_aFontCacheIds
[ nTmp
], m_aFontIdx
[ nTmp
], nTmp
);
579 while (nChg
< TextFrameIndex(aText
.size()));
582 void SwAttrIter::CtorInitAttrIter(SwTextNode
& rTextNode
,
583 SwScriptInfo
& rScriptInfo
, SwTextFrame
const*const pFrame
)
585 // during HTML-Import it can happen, that no layout exists
586 SwRootFrame
* pRootFrame
= rTextNode
.getIDocumentLayoutAccess().GetCurrentLayout();
587 m_pViewShell
= pRootFrame
? pRootFrame
->GetCurrShell() : nullptr;
589 m_pScriptInfo
= &rScriptInfo
;
591 // set font to vertical if frame layout is vertical
592 bool bVertLayout
= false;
593 bool bVertLayoutLRBT
= false;
597 if ( pFrame
->IsVertical() )
601 if (pFrame
->IsVertLRBT())
603 bVertLayoutLRBT
= true;
605 bRTL
= pFrame
->IsRightToLeft();
606 m_pMergedPara
= pFrame
->GetMergedPara();
609 // determine script changes if not already done for current paragraph
610 assert(m_pScriptInfo
);
611 if (m_pScriptInfo
->GetInvalidityA() != TextFrameIndex(COMPLETE_STRING
))
612 m_pScriptInfo
->InitScriptInfo(rTextNode
, m_pMergedPara
, bRTL
);
614 InitFontAndAttrHandler(
615 m_pMergedPara
? *m_pMergedPara
->pParaPropsNode
: rTextNode
,
617 m_pMergedPara
? m_pMergedPara
->mergedText
: rTextNode
.GetText(),
621 m_nStartIndex
= m_nEndIndex
= m_nPosition
= m_nChgCnt
= 0;
623 SwDoc
& rDoc
= rTextNode
.GetDoc();
624 const IDocumentRedlineAccess
& rIDRA
= rTextNode
.getIDocumentRedlineAccess();
626 // sw_redlinehide: this is a Ring - pExtInp is the first PaM that's inside
627 // the node. It's not clear whether there can be more than 1 PaM in the
628 // Ring, and this code doesn't handle that case; neither did the old code.
629 const SwExtTextInput
* pExtInp
= rDoc
.GetExtTextInput( rTextNode
);
630 if (!pExtInp
&& m_pMergedPara
)
632 SwTextNode
const* pNode(&rTextNode
);
633 for (auto const& rExtent
: m_pMergedPara
->extents
)
635 if (rExtent
.pNode
!= pNode
)
637 pNode
= rExtent
.pNode
;
638 pExtInp
= rDoc
.GetExtTextInput(*pNode
);
644 const bool bShow
= IDocumentRedlineAccess::IsShowChanges(rIDRA
.GetRedlineFlags())
645 && pRootFrame
&& !pRootFrame
->IsHideRedlines();
646 if (!(pExtInp
|| m_pMergedPara
|| bShow
))
649 SwRedlineTable::size_type nRedlPos
= rIDRA
.GetRedlinePos( rTextNode
, RedlineType::Any
);
650 if (SwRedlineTable::npos
== nRedlPos
&& m_pMergedPara
)
652 SwTextNode
const* pNode(&rTextNode
);
653 for (auto const& rExtent
: m_pMergedPara
->extents
)
654 { // note: have to search because extents based only on Delete
655 if (rExtent
.pNode
!= pNode
)
657 pNode
= rExtent
.pNode
;
658 nRedlPos
= rIDRA
.GetRedlinePos(*pNode
, RedlineType::Any
);
659 if (SwRedlineTable::npos
!= nRedlPos
)
663 // TODO this is true initially but after delete ops it may be false... need to delete m_pMerged somewhere?
664 // assert(SwRedlineTable::npos != nRedlPos);
665 // false now with fieldmarks
667 || pRootFrame
->GetFieldmarkMode() != sw::FieldmarkMode::ShowBoth
668 || SwRedlineTable::npos
!= nRedlPos
|| m_pMergedPara
->extents
.size() <= 1);
670 if (!(pExtInp
|| m_pMergedPara
|| SwRedlineTable::npos
!= nRedlPos
))
673 const std::vector
<ExtTextInputAttr
> *pArr
= nullptr;
676 pArr
= &pExtInp
->GetAttrs();
677 Seek( TextFrameIndex(0) );
680 m_pRedline
.reset(new SwRedlineItr( rTextNode
, *m_pFont
, m_aAttrHandler
, nRedlPos
,
681 (pRootFrame
&& pRootFrame
->IsHideRedlines())
682 ? SwRedlineItr::Mode::Hide
684 ? SwRedlineItr::Mode::Show
685 : SwRedlineItr::Mode::Ignore
,
686 pArr
, pExtInp
? pExtInp
->Start() : nullptr));
688 if( m_pRedline
->IsOn() )
692 // The Redline-Iterator
693 // The following information/states exist in RedlineIterator:
695 // m_nFirst is the first index of RedlineTable, which overlaps with the paragraph.
697 // m_nAct is the currently active (if m_bOn is set) or the next possible index.
698 // m_nStart and m_nEnd give you the borders of the object within the paragraph.
700 // If m_bOn is set, the font has been manipulated according to it.
702 // If m_nAct is set to SwRedlineTable::npos (via Reset()), then currently no
703 // Redline is active, m_nStart and m_nEnd are invalid.
704 SwRedlineItr::SwRedlineItr( const SwTextNode
& rTextNd
, SwFont
& rFnt
,
705 SwAttrHandler
& rAH
, sal_Int32 nRed
,
707 const std::vector
<ExtTextInputAttr
> *pArr
,
708 SwPosition
const*const pExtInputStart
)
709 : m_rDoc( rTextNd
.GetDoc() )
710 , m_rAttrHandler( rAH
)
711 , m_nNdIdx( rTextNd
.GetIndex() )
713 , m_nAct( SwRedlineTable::npos
)
714 , m_nStart( COMPLETE_STRING
)
715 , m_nEnd( COMPLETE_STRING
)
721 assert(pExtInputStart
);
722 m_pExt
.reset( new SwExtend(*pArr
, pExtInputStart
->GetNodeIndex(),
723 pExtInputStart
->GetContentIndex()) );
727 assert(m_pExt
|| m_eMode
!= Mode::Ignore
); // only create if necessary
728 Seek(rFnt
, m_nNdIdx
, 0, COMPLETE_STRING
);
731 SwRedlineItr::~SwRedlineItr() COVERITY_NOEXCEPT_FALSE
737 // The return value of SwRedlineItr::Seek tells you if the current font
738 // has been manipulated by leaving (-1) or accessing (+1) of a section
739 short SwRedlineItr::Seek(SwFont
& rFnt
,
740 SwNodeOffset
const nNode
, sal_Int32
const nNew
, sal_Int32
const nOld
)
744 return 0; // Abbreviation: if we're within an ExtendTextInputs
745 // there can't be other changes of attributes (not even by redlining)
746 if (m_eMode
== Mode::Show
)
753 Clear_( &rFnt
); // We go behind the current section
754 ++m_nAct
; // and check the next one
756 else if (nNew
< m_nStart
)
759 Clear_( &rFnt
); // We go in front of the current section
760 if (m_nAct
> m_nFirst
)
761 m_nAct
= m_nFirst
; // the test has to run from the beginning
763 return nRet
+ EnterExtend(rFnt
, nNode
, nNew
); // There's none prior to us
766 return nRet
+ EnterExtend(rFnt
, nNode
, nNew
); // We stayed in the same section
768 if (SwRedlineTable::npos
== m_nAct
|| nOld
> nNew
)
771 m_nStart
= COMPLETE_STRING
;
772 m_nEnd
= COMPLETE_STRING
;
773 const SwRedlineTable
& rTable
= m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable();
775 for ( ; m_nAct
< rTable
.size() ; ++m_nAct
)
777 rTable
[ m_nAct
]->CalcStartEnd(nNode
, m_nStart
, m_nEnd
);
781 if (nNew
>= m_nStart
) // only possible candidate
784 const SwRangeRedline
*pRed
= rTable
[ m_nAct
];
791 const_cast<SwDoc
&>(m_rDoc
).GetAttrPool();
792 m_pSet
= std::make_unique
<SfxItemSetFixed
<RES_CHRATR_BEGIN
, RES_CHRATR_END
-1>>(rPool
);
795 if( 1 < pRed
->GetStackCount() )
796 FillHints( pRed
->GetAuthor( 1 ), pRed
->GetType( 1 ) );
797 FillHints( pRed
->GetAuthor(), pRed
->GetType() );
799 SfxWhichIter
aIter( *m_pSet
);
801 // moved text: dark green with double underline or strikethrough
802 if ( pRed
->IsMoved() )
804 m_pSet
->Put(SvxColorItem( COL_GREEN
, RES_CHRATR_COLOR
));
805 if (SfxItemState::SET
== m_pSet
->GetItemState(RES_CHRATR_CROSSEDOUT
, true))
806 m_pSet
->Put(SvxCrossedOutItem( STRIKEOUT_DOUBLE
, RES_CHRATR_CROSSEDOUT
));
808 m_pSet
->Put(SvxUnderlineItem( LINESTYLE_DOUBLE
, RES_CHRATR_UNDERLINE
));
811 sal_uInt16 nWhich
= aIter
.FirstWhich();
814 const SfxPoolItem
* pItem
;
815 if( ( nWhich
< RES_CHRATR_END
) &&
816 ( SfxItemState::SET
== aIter
.GetItemState( true, &pItem
) ) )
818 SwTextAttr
* pAttr
= MakeRedlineTextAttr(
819 const_cast<SwDoc
&>(m_rDoc
),
820 *const_cast<SfxPoolItem
*>(pItem
) );
821 pAttr
->SetPriorityAttr( true );
822 m_Hints
.push_back(pAttr
);
823 m_rAttrHandler
.PushAndChg( *pAttr
, rFnt
);
825 nWhich
= aIter
.NextWhich();
832 m_nStart
= COMPLETE_STRING
;
833 m_nEnd
= COMPLETE_STRING
;
836 else if (m_eMode
== Mode::Hide
)
837 { // ... just iterate to update m_nAct for GetNextRedln();
838 // there is no need to care about formatting in this mode
839 if (m_nAct
== SwRedlineTable::npos
|| nOld
== COMPLETE_STRING
)
840 { // reset, or move backward
843 for ( ; m_nAct
< m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable().size(); ++m_nAct
)
844 { // only Start matters in this mode
845 // Seeks until it finds a RL that starts at or behind the seek pos.
846 // - then update m_nStart/m_nEnd to the intersection of it with the
847 // current node (if any).
848 // The only way to skip to a different node is if there is a Delete
849 // RL, so if there is no intersection we'll never skip again.
850 // Note: here, assume that delete can't nest inside delete!
851 SwRangeRedline
const*const pRedline(
852 m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable()[m_nAct
]);
853 SwPosition
const*const pStart(pRedline
->Start());
854 if (pRedline
->GetType() == RedlineType::Delete
855 && (nNode
< pStart
->GetNodeIndex()
856 || (nNode
== pStart
->GetNodeIndex()
857 && nNew
<= pStart
->GetContentIndex())))
859 pRedline
->CalcStartEnd(nNode
, m_nStart
, m_nEnd
);
862 m_nStart
= COMPLETE_STRING
;
863 m_nEnd
= COMPLETE_STRING
;
866 return nRet
+ EnterExtend(rFnt
, nNode
, nNew
);
869 void SwRedlineItr::FillHints( std::size_t nAuthor
, RedlineType eType
)
873 case RedlineType::Insert
:
874 SW_MOD()->GetInsertAuthorAttr(nAuthor
, *m_pSet
);
876 case RedlineType::Delete
:
877 SW_MOD()->GetDeletedAuthorAttr(nAuthor
, *m_pSet
);
879 case RedlineType::Format
:
880 case RedlineType::FmtColl
:
881 SW_MOD()->GetFormatAuthorAttr(nAuthor
, *m_pSet
);
888 void SwRedlineItr::ChangeTextAttr( SwFont
* pFnt
, SwTextAttr
const &rHt
, bool bChg
)
890 OSL_ENSURE( IsOn(), "SwRedlineItr::ChangeTextAttr: Off?" );
892 if (m_eMode
!= Mode::Show
&& !m_pExt
)
897 if (m_pExt
&& m_pExt
->IsOn())
898 m_rAttrHandler
.PushAndChg( rHt
, *m_pExt
->GetFont() );
900 m_rAttrHandler
.PushAndChg( rHt
, *pFnt
);
904 OSL_ENSURE( ! m_pExt
|| ! m_pExt
->IsOn(), "Pop of attribute during opened extension" );
905 m_rAttrHandler
.PopAndChg( rHt
, *pFnt
);
909 void SwRedlineItr::Clear_( SwFont
* pFnt
)
911 OSL_ENSURE( m_bOn
, "SwRedlineItr::Clear: Off?" );
913 for (auto const& hint
: m_Hints
)
916 m_rAttrHandler
.PopAndChg( *hint
, *pFnt
);
918 m_rAttrHandler
.Pop( *hint
);
919 SwTextAttr::Destroy(hint
, const_cast<SwDoc
&>(m_rDoc
).GetAttrPool() );
924 /// Ignore mode: does nothing.
925 /// Show mode: returns end of redline if currently in one, or start of next
926 /// Hide mode: returns start of next redline in current node, plus (if it's a
927 /// Delete) its end position and number of consecutive RLs
928 std::pair
<sal_Int32
, std::pair
<SwRangeRedline
const*, size_t>>
929 SwRedlineItr::GetNextRedln(sal_Int32 nNext
, SwTextNode
const*const pNode
,
930 SwRedlineTable::size_type
& rAct
)
932 sal_Int32
nStart(m_nStart
);
933 sal_Int32
nEnd(m_nEnd
);
934 nNext
= NextExtend(pNode
->GetIndex(), nNext
);
935 if (m_eMode
== Mode::Ignore
|| SwRedlineTable::npos
== m_nFirst
)
936 return std::make_pair(nNext
, std::make_pair(nullptr, 0));
937 if (SwRedlineTable::npos
== rAct
)
943 while (rAct
< m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable().size())
945 SwRangeRedline
const*const pRedline(
946 m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable()[rAct
]);
947 pRedline
->CalcStartEnd(pNode
->GetIndex(), nStart
, nEnd
);
948 if (m_eMode
!= Mode::Hide
949 || pRedline
->GetType() == RedlineType::Delete
)
953 ++rAct
; // Hide mode: search a Delete RL
956 if (rAct
== m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable().size())
958 return std::make_pair(nNext
, std::make_pair(nullptr, 0)); // no Delete here
960 if (m_bOn
|| (m_eMode
== Mode::Show
&& nStart
== 0))
961 { // in Ignore mode, the end of redlines isn't relevant, except as returned in the second in the pair!
965 else if (nStart
<= nNext
)
967 if (m_eMode
== Mode::Show
)
973 assert(m_eMode
== Mode::Hide
);
974 SwRangeRedline
const* pRedline(
975 m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable()[rAct
]);
976 assert(pRedline
->GetType() == RedlineType::Delete
); //?
977 if (pRedline
->GetType() == RedlineType::Delete
)
980 size_t nSkipped(1); // (consecutive) candidates to be skipped
981 while (rAct
+ nSkipped
<
982 m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable().size())
984 SwRangeRedline
const*const pNext
=
985 m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable()[rAct
+ nSkipped
];
986 if (*pRedline
->End() < *pNext
->Start())
988 break; // done for now
990 else if (*pNext
->Start() == *pRedline
->End() &&
991 pNext
->GetType() == RedlineType::Delete
)
993 // consecutive delete - continue
998 return std::make_pair(nNext
, std::make_pair(pRedline
, nSkipped
));
1002 return std::make_pair(nNext
, std::make_pair(nullptr, 0));
1005 bool SwRedlineItr::ChkSpecialUnderline_() const
1007 // If the underlining or the escapement is caused by redlining,
1008 // we always apply the SpecialUnderlining, i.e. the underlining
1009 // below the base line
1010 for (SwTextAttr
* pHint
: m_Hints
)
1012 const sal_uInt16 nWhich
= pHint
->Which();
1013 if( RES_CHRATR_UNDERLINE
== nWhich
||
1014 RES_CHRATR_ESCAPEMENT
== nWhich
)
1020 bool SwRedlineItr::CheckLine(
1021 SwNodeOffset
const nStartNode
, sal_Int32
const nChkStart
,
1022 SwNodeOffset
const nEndNode
, sal_Int32 nChkEnd
, OUString
& rRedlineText
,
1023 bool& bRedlineEnd
, RedlineType
& eRedlineEnd
, size_t* pAuthorAtPos
)
1025 // note: previously this would return true in the (!m_bShow && m_pExt)
1026 // case, but surely that was a bug?
1027 if (m_nFirst
== SwRedlineTable::npos
|| m_eMode
!= Mode::Show
)
1029 if( nChkEnd
== nChkStart
&& pAuthorAtPos
== nullptr ) // empty lines look one char further
1031 sal_Int32 nOldStart
= m_nStart
;
1032 sal_Int32 nOldEnd
= m_nEnd
;
1033 SwRedlineTable::size_type
const nOldAct
= m_nAct
;
1034 bool bRet
= bRedlineEnd
= false;
1035 eRedlineEnd
= RedlineType::None
;
1037 SwPosition
const start(*m_rDoc
.GetNodes()[nStartNode
]->GetContentNode(), nChkStart
);
1038 SwPosition
const end(*m_rDoc
.GetNodes()[nEndNode
]->GetContentNode(), nChkEnd
);
1039 SwRangeRedline
const* pPrevRedline
= nullptr;
1040 bool isBreak(false);
1041 for (m_nAct
= m_nFirst
; m_nAct
< m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable().size(); ++m_nAct
)
1043 SwRangeRedline
const*const pRedline(
1044 m_rDoc
.getIDocumentRedlineAccess().GetRedlineTable()[ m_nAct
] );
1045 // collect text of the hidden redlines at the end of the line
1046 bool isExtendText(false);
1047 switch (ComparePosition(*pRedline
->Start(), *pRedline
->End(), start
, end
))
1049 case SwComparePosition::Behind
:
1052 case SwComparePosition::OverlapBehind
:
1053 case SwComparePosition::CollideStart
:
1054 case SwComparePosition::Outside
:
1055 case SwComparePosition::Equal
:
1056 // store redlining at line end (for line break formatting)
1057 eRedlineEnd
= pRedline
->GetType();
1061 *pAuthorAtPos
= pRedline
->GetAuthor();
1063 case SwComparePosition::OverlapBefore
:
1064 case SwComparePosition::CollideEnd
:
1065 case SwComparePosition::Inside
:
1068 // start to collect text of invisible redlines for ChangesInMargin layout
1069 if (rRedlineText
.isEmpty() && !pRedline
->IsVisible())
1071 rRedlineText
= const_cast<SwRangeRedline
*>(pRedline
)->GetDescr(/*bSimplified=*/true);
1072 pPrevRedline
= pRedline
;
1073 isExtendText
= true;
1075 // join the text of the next invisible redlines in the same position
1076 // i.e. characters deleted by pressing backspace or delete
1077 else if (pPrevRedline
&& !pRedline
->IsVisible() &&
1078 *pRedline
->Start() == *pPrevRedline
->Start() && *pRedline
->End() == *pPrevRedline
->End() )
1080 OUString
sExtendText(const_cast<SwRangeRedline
*>(pRedline
)->GetDescr(/*bSimplified=*/true));
1081 if (!sExtendText
.isEmpty())
1083 if (rRedlineText
.getLength() < 12)
1085 // TODO: remove extra space from GetDescr(true),
1086 // but show deletion of paragraph or line break
1087 rRedlineText
= rRedlineText
+
1088 const_cast<SwRangeRedline
*>(pRedline
)->GetDescr(/*bSimplified=*/true).subView(1);
1091 rRedlineText
= OUString::Concat(rRedlineText
.subView(0, rRedlineText
.getLength() - 3)) + "...";
1093 isExtendText
= true;
1097 case SwComparePosition::Before
:
1098 break; // -Werror=switch
1100 if (isBreak
&& !isExtendText
)
1106 m_nStart
= nOldStart
;
1112 void SwExtend::ActualizeFont( SwFont
&rFnt
, ExtTextInputAttr nAttr
)
1114 if ( nAttr
& ExtTextInputAttr::Underline
)
1115 rFnt
.SetUnderline( LINESTYLE_SINGLE
);
1116 else if ( nAttr
& ExtTextInputAttr::DoubleUnderline
)
1117 rFnt
.SetUnderline( LINESTYLE_DOUBLE
);
1118 else if ( nAttr
& ExtTextInputAttr::BoldUnderline
)
1119 rFnt
.SetUnderline( LINESTYLE_BOLD
);
1120 else if ( nAttr
& ExtTextInputAttr::DottedUnderline
)
1121 rFnt
.SetUnderline( LINESTYLE_DOTTED
);
1122 else if ( nAttr
& ExtTextInputAttr::DashDotUnderline
)
1123 rFnt
.SetUnderline( LINESTYLE_DOTTED
);
1125 if ( nAttr
& ExtTextInputAttr::RedText
)
1126 rFnt
.SetColor( COL_RED
);
1128 if ( nAttr
& ExtTextInputAttr::Highlight
)
1130 const StyleSettings
& rStyleSettings
= Application::GetSettings().GetStyleSettings();
1131 rFnt
.SetColor( rStyleSettings
.GetHighlightTextColor() );
1132 rFnt
.SetBackColor( rStyleSettings
.GetHighlightColor() );
1134 if ( nAttr
& ExtTextInputAttr::GrayWaveline
)
1135 rFnt
.SetGreyWave( true );
1138 short SwExtend::Enter(SwFont
& rFnt
, SwNodeOffset
const nNode
, sal_Int32
const nNew
)
1140 OSL_ENSURE( !m_pFont
, "SwExtend: Enter with Font" );
1141 if (nNode
!= m_nNode
)
1143 OSL_ENSURE( !Inside(), "SwExtend: Enter without Leave" );
1147 m_pFont
.reset( new SwFont(rFnt
) );
1148 ActualizeFont( rFnt
, m_rArr
[m_nPos
- m_nStart
] );
1154 bool SwExtend::Leave_(SwFont
& rFnt
, SwNodeOffset
const nNode
, sal_Int32
const nNew
)
1156 OSL_ENSURE(nNode
== m_nNode
&& Inside(), "SwExtend: Leave without Enter");
1157 if (nNode
!= m_nNode
)
1159 const ExtTextInputAttr nOldAttr
= m_rArr
[m_nPos
- m_nStart
];
1162 { // We stayed within the ExtendText-section
1163 const ExtTextInputAttr nAttr
= m_rArr
[m_nPos
- m_nStart
];
1164 if( nOldAttr
!= nAttr
) // Is there an (inner) change of attributes?
1167 ActualizeFont( rFnt
, nAttr
);
1179 sal_Int32
SwExtend::Next(SwNodeOffset
const nNode
, sal_Int32 nNext
)
1181 if (nNode
!= m_nNode
)
1183 if (m_nPos
< m_nStart
)
1185 if (nNext
> m_nStart
)
1188 else if (m_nPos
< m_nEnd
)
1190 sal_Int32 nIdx
= m_nPos
- m_nStart
;
1191 const ExtTextInputAttr nAttr
= m_rArr
[ nIdx
];
1192 while (o3tl::make_unsigned(++nIdx
) < m_rArr
.size() && nAttr
== m_rArr
[nIdx
])
1194 nIdx
= nIdx
+ m_nStart
;
1201 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */