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 <tools/multisel.hxx>
26 #include <modeltoviewhelper.hxx>
29 #include <txatbase.hxx>
32 #include <scriptinfo.hxx>
33 #include <IDocumentMarkAccess.hxx>
34 #include <bookmark.hxx>
35 #include <o3tl/sorted_vector.hxx>
43 sal_Int32 m_nFieldPos
;
45 enum { NONE
, FIELD
, FOOTNOTE
} m_eType
;
46 explicit FieldResult(sal_Int32
const nPos
)
47 : m_nFieldPos(nPos
), m_eType(NONE
)
51 class sortfieldresults
54 bool operator()(const FieldResult
&rOne
, const FieldResult
&rTwo
) const
56 return rOne
.m_nFieldPos
< rTwo
.m_nFieldPos
;
69 o3tl::sorted_vector
<FieldResult
, sortfieldresults
> m_aAttrs
;
70 block(sal_Int32 nStart
, sal_Int32 nLen
, bool bVisible
)
71 : m_nStart(nStart
), m_nLen(nLen
), m_bVisible(bVisible
)
78 const sal_Int32 m_nPos
;
79 explicit containsPos(const sal_Int32 nPos
)
83 bool operator() (const block
& rIn
) const
85 return m_nPos
>= rIn
.m_nStart
&& m_nPos
< rIn
.m_nStart
+ rIn
.m_nLen
;
91 ModelToViewHelper::ModelToViewHelper(const SwTextNode
&rNode
,
92 SwRootFrame
const*const pLayout
, ExpandMode eMode
)
94 const OUString
& rNodeText
= rNode
.GetText();
95 m_aRetText
= rNodeText
;
97 if (eMode
== ExpandMode::PassThrough
)
100 Range
aRange( 0, rNodeText
.isEmpty() ? 0 : rNodeText
.getLength() - 1);
101 MultiSelection
aHiddenMulti(aRange
);
103 if (eMode
& ExpandMode::HideInvisible
)
104 SwScriptInfo::selectHiddenTextProperty(rNode
, aHiddenMulti
, nullptr);
106 if (eMode
& ExpandMode::HideDeletions
)
107 SwScriptInfo::selectRedLineDeleted(rNode
, aHiddenMulti
);
109 if (eMode
& ExpandMode::HideFieldmarkCommands
)
111 // hide fieldmark commands
112 IDocumentMarkAccess
const& rIDMA(*rNode
.GetDoc().getIDocumentMarkAccess());
113 ::std::deque
<::std::pair
<sw::mark::IFieldmark
const*, bool>> startedFields
;
114 SwPaM
cursor(rNode
, 0);
117 sw::mark::IFieldmark
const* pFieldMark(nullptr);
118 while (true) // loop to skip NonTextFieldmarks, those are handled later
120 pFieldMark
= rIDMA
.getInnerFieldmarkFor(*cursor
.GetPoint());
121 if (pFieldMark
== nullptr
122 || pFieldMark
->GetMarkStart().GetNode().GetTextNode()->GetText()[
123 pFieldMark
->GetMarkStart().GetContentIndex()]
124 != CH_TXT_ATR_FORMELEMENT
)
128 pFieldMark
= nullptr;
129 if (!cursor
.Move(fnMoveBackward
, GoInContent
))
138 assert(pFieldMark
->GetMarkStart().GetNode().GetTextNode()->GetText()[pFieldMark
->GetMarkStart().GetContentIndex()] != CH_TXT_ATR_FORMELEMENT
);
139 // getInnerFieldmarkFor may also return one that starts at rNode,0 -
140 // skip it, must be handled in loop below
141 if (pFieldMark
->GetMarkStart().GetNode() < rNode
)
143 // this can be a nested field's end - skip over those!
144 if (pFieldMark
->GetMarkEnd().GetNode() < rNode
)
146 assert(cursor
.GetPoint()->GetNode().GetTextNode()->GetText()[cursor
.GetPoint()->GetContentIndex()] == CH_TXT_ATR_FIELDEND
);
150 SwPosition
const sepPos(::sw::mark::FindFieldSep(*pFieldMark
));
151 startedFields
.emplace_front(pFieldMark
, sepPos
.GetNode() < rNode
);
153 *cursor
.GetPoint() = pFieldMark
->GetMarkStart();
155 if (!cursor
.Move(fnMoveBackward
, GoInContent
))
160 ::std::optional
<sal_Int32
> oStartHidden
;
161 if (!::std::all_of(startedFields
.begin(), startedFields
.end(),
162 [](auto const& it
) { return it
.second
; }))
164 oStartHidden
.emplace(0); // node starts out hidden as field command
166 for (sal_Int32 i
= 0; i
< rNode
.GetText().getLength(); ++i
)
168 switch (rNode
.GetText()[i
])
170 case CH_TXT_ATR_FIELDSTART
:
172 auto const pFieldMark(rIDMA
.getFieldmarkAt(SwPosition(rNode
, i
)));
174 startedFields
.emplace_back(pFieldMark
, false);
177 oStartHidden
.emplace(i
);
181 case CH_TXT_ATR_FIELDSEP
:
183 assert(startedFields
.back().first
->IsCoveringPosition(SwPosition(rNode
, i
)));
184 startedFields
.back().second
= true;
185 assert(oStartHidden
);
186 if (::std::all_of(startedFields
.begin(), startedFields
.end(),
187 [](auto const& it
) { return it
.second
; }))
189 // prevent -Werror=maybe-uninitialized under gcc 11.2.0
190 #if defined __GNUC__ && !defined __clang__ && __GNUC__ >= 11 && __GNUC__ <= 12
191 #pragma GCC diagnostic push
192 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
194 // i is still hidden but the Range end is oddly "-1"
195 aHiddenMulti
.Select({*oStartHidden
, i
}, true);
196 oStartHidden
.reset();
197 #if defined __GNUC__ && !defined __clang__ && __GNUC__ >= 11 && __GNUC__ <= 12
198 #pragma GCC diagnostic pop
203 case CH_TXT_ATR_FIELDEND
:
205 assert(startedFields
.back().first
== rIDMA
.getFieldmarkAt(SwPosition(rNode
, i
)));
206 startedFields
.pop_back();
207 aHiddenMulti
.Select({i
, i
}, true);
212 if (oStartHidden
&& rNode
.Len() != 0)
214 aHiddenMulti
.Select({*oStartHidden
, rNode
.Len() - 1}, true);
217 else if (eMode
& ExpandMode::ExpandFields
) // subset: only hide dummy chars
219 for (sal_Int32 i
= 0; i
< rNode
.GetText().getLength(); ++i
)
221 switch (rNode
.GetText()[i
])
223 case CH_TXT_ATR_FIELDSTART
:
224 case CH_TXT_ATR_FIELDSEP
:
225 case CH_TXT_ATR_FIELDEND
:
227 aHiddenMulti
.Select({i
, i
}, true);
234 std::vector
<block
> aBlocks
;
236 sal_Int32 nShownStart
= 0;
237 for (sal_Int32 i
= 0; i
< aHiddenMulti
.GetRangeCount(); ++i
)
239 const Range
& rRange
= aHiddenMulti
.GetRange(i
);
240 const sal_Int32 nHiddenStart
= rRange
.Min();
241 const sal_Int32 nHiddenEnd
= rRange
.Max() + 1;
242 const sal_Int32 nHiddenLen
= nHiddenEnd
- nHiddenStart
;
244 const sal_Int32 nShownEnd
= nHiddenStart
;
245 const sal_Int32 nShownLen
= nShownEnd
- nShownStart
;
248 aBlocks
.emplace_back(nShownStart
, nShownLen
, true);
251 aBlocks
.emplace_back(nHiddenStart
, nHiddenLen
, false);
253 nShownStart
= nHiddenEnd
;
256 sal_Int32 nTrailingShownLen
= rNodeText
.getLength() - nShownStart
;
257 if (nTrailingShownLen
)
258 aBlocks
.emplace_back(nShownStart
, nTrailingShownLen
, true);
260 if (eMode
& ExpandMode::ExpandFields
|| eMode
& ExpandMode::ExpandFootnote
)
262 //first the normal fields, get their position in the node and what the text they expand
264 const SwpHints
* pSwpHints2
= rNode
.GetpSwpHints();
265 for ( size_t i
= 0; pSwpHints2
&& i
< pSwpHints2
->Count(); ++i
)
267 const SwTextAttr
* pAttr
= pSwpHints2
->Get(i
);
268 if (pAttr
->HasDummyChar())
270 const sal_Int32 nDummyCharPos
= pAttr
->GetStart();
271 if (aHiddenMulti
.IsSelected(nDummyCharPos
))
273 std::vector
<block
>::iterator aFind
= std::find_if(aBlocks
.begin(),
274 aBlocks
.end(), containsPos(nDummyCharPos
));
275 if (aFind
!= aBlocks
.end())
277 FieldResult
aFieldResult(nDummyCharPos
);
278 switch (pAttr
->Which())
280 case RES_TXTATR_ANNOTATION
:
281 if (eMode
& ExpandMode::ExpandFields
)
283 // this uses CH_TXTATR_INWORD so replace with nothing
284 aFieldResult
.m_eType
= FieldResult::FIELD
;
287 case RES_TXTATR_FIELD
:
288 if (eMode
& ExpandMode::ExpandFields
)
290 // add a ZWSP before the expanded field in replace mode
291 aFieldResult
.m_sExpand
= ((eMode
& ExpandMode::ReplaceMode
)
292 ? OUString(CHAR_ZWSP
) : OUString("")) +
293 static_txtattr_cast
<SwTextField
const*>(pAttr
)->
294 GetFormatField().GetField()->ExpandField(true, pLayout
);
295 aFieldResult
.m_eType
= FieldResult::FIELD
;
299 if (eMode
& ExpandMode::ExpandFootnote
)
301 const SwFormatFootnote
& rFootnote
= static_cast<SwTextFootnote
const*>(pAttr
)->GetFootnote();
302 const SwDoc
& rDoc
= rNode
.GetDoc();
303 aFieldResult
.m_sExpand
= (eMode
& ExpandMode::ReplaceMode
)
304 ? OUString(CHAR_ZWSP
)
305 : rFootnote
.GetViewNumStr(rDoc
, pLayout
);
306 aFieldResult
.m_eType
= FieldResult::FOOTNOTE
;
312 aFind
->m_aAttrs
.insert(aFieldResult
);
317 if (eMode
& ExpandMode::ExpandFields
)
319 //now get the dropdown formfields, get their position in the node and what the text they expand
321 SwPaM
aPaM(rNode
, 0, rNode
, rNode
.Len());
322 std::vector
<sw::mark::IFieldmark
*> aNoTextFieldmarks
=
323 rNode
.GetDoc().getIDocumentMarkAccess()->getNoTextFieldmarksIn(aPaM
);
325 for (sw::mark::IFieldmark
*const pMark
: aNoTextFieldmarks
)
327 const sal_Int32 nDummyCharPos
= pMark
->GetMarkStart().GetContentIndex();
328 if (aHiddenMulti
.IsSelected(nDummyCharPos
))
330 std::vector
<block
>::iterator aFind
= std::find_if(aBlocks
.begin(), aBlocks
.end(),
331 containsPos(nDummyCharPos
));
332 if (aFind
!= aBlocks
.end())
334 FieldResult
aFieldResult(nDummyCharPos
);
335 aFieldResult
.m_sExpand
= (eMode
& ExpandMode::ReplaceMode
)
336 ? OUString(CHAR_ZWSP
)
337 : sw::mark::ExpandFieldmark(pMark
);
338 aFieldResult
.m_eType
= FieldResult::FIELD
;
339 aFind
->m_aAttrs
.insert(aFieldResult
);
345 //store the end of each range in the model and where that end of range
346 //maps to in the view
347 sal_Int32 nOffset
= 0;
348 for (const auto& rBlock
: aBlocks
)
350 const sal_Int32 nBlockLen
= rBlock
.m_nLen
;
353 const sal_Int32 nBlockStart
= rBlock
.m_nStart
;
354 const sal_Int32 nBlockEnd
= nBlockStart
+ nBlockLen
;
356 if (!rBlock
.m_bVisible
)
358 sal_Int32
const modelBlockPos(nBlockEnd
);
359 sal_Int32
const viewBlockPos(nBlockStart
+ nOffset
);
360 m_aMap
.emplace_back(modelBlockPos
, viewBlockPos
, false);
362 m_aRetText
= m_aRetText
.replaceAt(nOffset
+ nBlockStart
, nBlockLen
, u
"");
363 nOffset
-= nBlockLen
;
367 for (const auto& rAttr
: rBlock
.m_aAttrs
)
369 sal_Int32
const modelFieldPos(rAttr
.m_nFieldPos
);
370 sal_Int32
const viewFieldPos(rAttr
.m_nFieldPos
+ nOffset
);
371 m_aMap
.emplace_back(modelFieldPos
, viewFieldPos
, true );
373 m_aRetText
= m_aRetText
.replaceAt(viewFieldPos
, 1, rAttr
.m_sExpand
);
374 nOffset
+= rAttr
.m_sExpand
.getLength() - 1;
376 switch (rAttr
.m_eType
)
378 case FieldResult::FIELD
:
379 m_FieldPositions
.push_back(viewFieldPos
);
381 case FieldResult::FOOTNOTE
:
382 m_FootnotePositions
.push_back(viewFieldPos
);
384 case FieldResult::NONE
: /*ignore*/
389 sal_Int32
const modelEndBlock(nBlockEnd
);
390 sal_Int32
const viewFieldPos(nBlockEnd
+ nOffset
);
391 m_aMap
.emplace_back(modelEndBlock
, viewFieldPos
, true);
396 /** Converts a model position into a view position
398 sal_Int32
ModelToViewHelper::ConvertToViewPosition( sal_Int32 nModelPos
) const
400 // Search for entry after nPos:
401 auto aIter
= std::find_if(m_aMap
.begin(), m_aMap
.end(),
402 [nModelPos
](const ConversionMapEntry
& rEntry
) { return rEntry
.m_nModelPos
>= nModelPos
; });
403 if (aIter
!= m_aMap
.end())
405 //if it's an invisible portion, map all contained positions
406 //to the anchor viewpos
407 if (!aIter
->m_bVisible
)
408 return aIter
->m_nViewPos
;
410 //if it's a visible portion, then the view position is the anchor
411 //viewpos - the offset of the input modelpos from the anchor
413 const sal_Int32 nOffsetFromEnd
= aIter
->m_nModelPos
- nModelPos
;
414 return aIter
->m_nViewPos
- nOffsetFromEnd
;
420 /** Converts a view position into a model position
422 ModelToViewHelper::ModelPosition
ModelToViewHelper::ConvertToModelPosition( sal_Int32 nViewPos
) const
425 aRet
.mnPos
= nViewPos
;
427 // Search for entry after nPos:
428 auto aIter
= std::find_if(m_aMap
.begin(), m_aMap
.end(),
429 [nViewPos
](const ConversionMapEntry
& rEntry
) { return rEntry
.m_nViewPos
> nViewPos
; });
431 // If nViewPos is in front of first field, we are finished.
432 if (aIter
!= m_aMap
.end() && aIter
!= m_aMap
.begin())
434 const sal_Int32 nPosModel
= aIter
->m_nModelPos
;
435 const sal_Int32 nPosExpand
= aIter
->m_nViewPos
;
439 // nPrevPosModel is the field position
440 const sal_Int32 nPrevPosModel
= aIter
->m_nModelPos
;
441 const sal_Int32 nPrevPosExpand
= aIter
->m_nViewPos
;
443 const sal_Int32 nLengthModel
= nPosModel
- nPrevPosModel
;
444 const sal_Int32 nLengthExpand
= nPosExpand
- nPrevPosExpand
;
446 const sal_Int32 nFieldLengthExpand
= nLengthExpand
- nLengthModel
+ 1;
447 const sal_Int32 nFieldEndExpand
= nPrevPosExpand
+ nFieldLengthExpand
;
449 // Check if nPos is outside of field:
450 if ( nFieldEndExpand
<= nViewPos
)
452 // nPos is outside of field:
453 const sal_Int32 nDistToField
= nViewPos
- nFieldEndExpand
+ 1;
454 aRet
.mnPos
= nPrevPosModel
+ nDistToField
;
458 // nViewPos is inside a field:
459 aRet
.mnPos
= nPrevPosModel
;
460 aRet
.mnSubPos
= nViewPos
- nPrevPosExpand
;
461 aRet
.mbIsField
= true;
468 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */