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::Fieldmark
const*, bool>> startedFields
;
114 SwPaM
cursor(rNode
, 0);
117 sw::mark::Fieldmark
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 auto [/*const SwPosition&*/ rMarkStartPos
, rMarkEndPos
] = pFieldMark
->GetMarkStartEnd();
142 if (rMarkStartPos
.GetNode() < rNode
)
144 // this can be a nested field's end - skip over those!
145 if (rMarkEndPos
.GetNode() < rNode
)
147 assert(cursor
.GetPoint()->GetNode().GetTextNode()->GetText()[cursor
.GetPoint()->GetContentIndex()] == CH_TXT_ATR_FIELDEND
);
151 SwPosition
const sepPos(::sw::mark::FindFieldSep(*pFieldMark
));
152 startedFields
.emplace_front(pFieldMark
, sepPos
.GetNode() < rNode
);
154 *cursor
.GetPoint() = pFieldMark
->GetMarkStart();
156 if (!cursor
.Move(fnMoveBackward
, GoInContent
))
161 ::std::optional
<sal_Int32
> oStartHidden
;
162 if (!::std::all_of(startedFields
.begin(), startedFields
.end(),
163 [](auto const& it
) { return it
.second
; }))
165 oStartHidden
.emplace(0); // node starts out hidden as field command
167 for (sal_Int32 i
= 0; i
< rNode
.GetText().getLength(); ++i
)
169 switch (rNode
.GetText()[i
])
171 case CH_TXT_ATR_FIELDSTART
:
173 auto const pFieldMark(rIDMA
.getFieldmarkAt(SwPosition(rNode
, i
)));
175 startedFields
.emplace_back(pFieldMark
, false);
178 oStartHidden
.emplace(i
);
182 case CH_TXT_ATR_FIELDSEP
:
184 assert(startedFields
.back().first
->IsCoveringPosition(SwPosition(rNode
, i
)));
185 startedFields
.back().second
= true;
186 assert(oStartHidden
);
187 if (::std::all_of(startedFields
.begin(), startedFields
.end(),
188 [](auto const& it
) { return it
.second
; }))
190 // prevent -Werror=maybe-uninitialized under gcc 11.2.0
191 #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
192 #pragma GCC diagnostic push
193 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
195 // i is still hidden but the Range end is oddly "-1"
196 aHiddenMulti
.Select({*oStartHidden
, i
}, true);
197 oStartHidden
.reset();
198 #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
199 #pragma GCC diagnostic pop
204 case CH_TXT_ATR_FIELDEND
:
206 assert(startedFields
.back().first
== rIDMA
.getFieldmarkAt(SwPosition(rNode
, i
)));
207 startedFields
.pop_back();
208 aHiddenMulti
.Select({i
, i
}, true);
213 if (oStartHidden
&& rNode
.Len() != 0)
215 aHiddenMulti
.Select({*oStartHidden
, rNode
.Len() - 1}, true);
218 else if (eMode
& ExpandMode::ExpandFields
) // subset: only hide dummy chars
220 for (sal_Int32 i
= 0; i
< rNode
.GetText().getLength(); ++i
)
222 switch (rNode
.GetText()[i
])
224 case CH_TXT_ATR_FIELDSTART
:
225 case CH_TXT_ATR_FIELDSEP
:
226 case CH_TXT_ATR_FIELDEND
:
228 aHiddenMulti
.Select({i
, i
}, true);
235 std::vector
<block
> aBlocks
;
237 sal_Int32 nShownStart
= 0;
238 for (sal_Int32 i
= 0; i
< aHiddenMulti
.GetRangeCount(); ++i
)
240 const Range
& rRange
= aHiddenMulti
.GetRange(i
);
241 const sal_Int32 nHiddenStart
= rRange
.Min();
242 const sal_Int32 nHiddenEnd
= rRange
.Max() + 1;
243 const sal_Int32 nHiddenLen
= nHiddenEnd
- nHiddenStart
;
245 const sal_Int32 nShownEnd
= nHiddenStart
;
246 const sal_Int32 nShownLen
= nShownEnd
- nShownStart
;
249 aBlocks
.emplace_back(nShownStart
, nShownLen
, true);
252 aBlocks
.emplace_back(nHiddenStart
, nHiddenLen
, false);
254 nShownStart
= nHiddenEnd
;
257 sal_Int32 nTrailingShownLen
= rNodeText
.getLength() - nShownStart
;
258 if (nTrailingShownLen
)
259 aBlocks
.emplace_back(nShownStart
, nTrailingShownLen
, true);
261 if (eMode
& ExpandMode::ExpandFields
|| eMode
& ExpandMode::ExpandFootnote
)
263 //first the normal fields, get their position in the node and what the text they expand
265 const SwpHints
* pSwpHints2
= rNode
.GetpSwpHints();
266 for ( size_t i
= 0; pSwpHints2
&& i
< pSwpHints2
->Count(); ++i
)
268 const SwTextAttr
* pAttr
= pSwpHints2
->Get(i
);
269 if (pAttr
->HasDummyChar())
271 const sal_Int32 nDummyCharPos
= pAttr
->GetStart();
272 if (aHiddenMulti
.IsSelected(nDummyCharPos
))
274 std::vector
<block
>::iterator aFind
= std::find_if(aBlocks
.begin(),
275 aBlocks
.end(), containsPos(nDummyCharPos
));
276 if (aFind
!= aBlocks
.end())
278 FieldResult
aFieldResult(nDummyCharPos
);
279 switch (pAttr
->Which())
281 case RES_TXTATR_ANNOTATION
:
282 if (eMode
& ExpandMode::ExpandFields
)
284 // this uses CH_TXTATR_INWORD so replace with nothing
285 aFieldResult
.m_eType
= FieldResult::FIELD
;
288 case RES_TXTATR_FIELD
:
289 if (eMode
& ExpandMode::ExpandFields
)
291 // add a ZWSP before the expanded field in replace mode
292 aFieldResult
.m_sExpand
= ((eMode
& ExpandMode::ReplaceMode
)
293 ? OUString(CHAR_ZWSP
) : u
""_ustr
) +
294 static_txtattr_cast
<SwTextField
const*>(pAttr
)->
295 GetFormatField().GetField()->ExpandField(true, pLayout
);
296 aFieldResult
.m_eType
= FieldResult::FIELD
;
300 if (eMode
& ExpandMode::ExpandFootnote
)
302 const SwFormatFootnote
& rFootnote
= static_cast<SwTextFootnote
const*>(pAttr
)->GetFootnote();
303 const SwDoc
& rDoc
= rNode
.GetDoc();
304 aFieldResult
.m_sExpand
= (eMode
& ExpandMode::ReplaceMode
)
305 ? OUString(CHAR_ZWSP
)
306 : rFootnote
.GetViewNumStr(rDoc
, pLayout
);
307 aFieldResult
.m_eType
= FieldResult::FOOTNOTE
;
313 aFind
->m_aAttrs
.insert(aFieldResult
);
318 if (eMode
& ExpandMode::ExpandFields
)
320 //now get the dropdown formfields, get their position in the node and what the text they expand
322 SwPaM
aPaM(rNode
, 0, rNode
, rNode
.Len());
323 std::vector
<sw::mark::Fieldmark
*> aNoTextFieldmarks
=
324 rNode
.GetDoc().getIDocumentMarkAccess()->getNoTextFieldmarksIn(aPaM
);
326 for (sw::mark::Fieldmark
*const pMark
: aNoTextFieldmarks
)
328 const sal_Int32 nDummyCharPos
= pMark
->GetMarkStart().GetContentIndex();
329 if (aHiddenMulti
.IsSelected(nDummyCharPos
))
331 std::vector
<block
>::iterator aFind
= std::find_if(aBlocks
.begin(), aBlocks
.end(),
332 containsPos(nDummyCharPos
));
333 if (aFind
!= aBlocks
.end())
335 FieldResult
aFieldResult(nDummyCharPos
);
336 aFieldResult
.m_sExpand
= (eMode
& ExpandMode::ReplaceMode
)
337 ? OUString(CHAR_ZWSP
)
338 : sw::mark::ExpandFieldmark(pMark
);
339 aFieldResult
.m_eType
= FieldResult::FIELD
;
340 aFind
->m_aAttrs
.insert(aFieldResult
);
346 //store the end of each range in the model and where that end of range
347 //maps to in the view
348 sal_Int32 nOffset
= 0;
349 for (const auto& rBlock
: aBlocks
)
351 const sal_Int32 nBlockLen
= rBlock
.m_nLen
;
354 const sal_Int32 nBlockStart
= rBlock
.m_nStart
;
355 const sal_Int32 nBlockEnd
= nBlockStart
+ nBlockLen
;
357 if (!rBlock
.m_bVisible
)
359 sal_Int32
const modelBlockPos(nBlockEnd
);
360 sal_Int32
const viewBlockPos(nBlockStart
+ nOffset
);
361 m_aMap
.emplace_back(modelBlockPos
, viewBlockPos
, false);
363 m_aRetText
= m_aRetText
.replaceAt(nOffset
+ nBlockStart
, nBlockLen
, u
"");
364 nOffset
-= nBlockLen
;
368 for (const auto& rAttr
: rBlock
.m_aAttrs
)
370 sal_Int32
const modelFieldPos(rAttr
.m_nFieldPos
);
371 sal_Int32
const viewFieldPos(rAttr
.m_nFieldPos
+ nOffset
);
372 m_aMap
.emplace_back(modelFieldPos
, viewFieldPos
, true );
374 m_aRetText
= m_aRetText
.replaceAt(viewFieldPos
, 1, rAttr
.m_sExpand
);
375 nOffset
+= rAttr
.m_sExpand
.getLength() - 1;
377 switch (rAttr
.m_eType
)
379 case FieldResult::FIELD
:
380 m_FieldPositions
.push_back(viewFieldPos
);
382 case FieldResult::FOOTNOTE
:
383 m_FootnotePositions
.push_back(viewFieldPos
);
385 case FieldResult::NONE
: /*ignore*/
390 sal_Int32
const modelEndBlock(nBlockEnd
);
391 sal_Int32
const viewFieldPos(nBlockEnd
+ nOffset
);
392 m_aMap
.emplace_back(modelEndBlock
, viewFieldPos
, true);
397 /** Converts a model position into a view position
399 sal_Int32
ModelToViewHelper::ConvertToViewPosition( sal_Int32 nModelPos
) const
401 // Search for entry after nPos:
402 auto aIter
= std::find_if(m_aMap
.begin(), m_aMap
.end(),
403 [nModelPos
](const ConversionMapEntry
& rEntry
) { return rEntry
.m_nModelPos
>= nModelPos
; });
404 if (aIter
!= m_aMap
.end())
406 //if it's an invisible portion, map all contained positions
407 //to the anchor viewpos
408 if (!aIter
->m_bVisible
)
409 return aIter
->m_nViewPos
;
411 //if it's a visible portion, then the view position is the anchor
412 //viewpos - the offset of the input modelpos from the anchor
414 const sal_Int32 nOffsetFromEnd
= aIter
->m_nModelPos
- nModelPos
;
415 return aIter
->m_nViewPos
- nOffsetFromEnd
;
421 /** Converts a view position into a model position
423 ModelToViewHelper::ModelPosition
ModelToViewHelper::ConvertToModelPosition( sal_Int32 nViewPos
) const
426 aRet
.mnPos
= nViewPos
;
428 // Search for entry after nPos:
429 auto aIter
= std::find_if(m_aMap
.begin(), m_aMap
.end(),
430 [nViewPos
](const ConversionMapEntry
& rEntry
) { return rEntry
.m_nViewPos
> nViewPos
; });
432 // If nViewPos is in front of first field, we are finished.
433 if (aIter
!= m_aMap
.end() && aIter
!= m_aMap
.begin())
435 const sal_Int32 nPosModel
= aIter
->m_nModelPos
;
436 const sal_Int32 nPosExpand
= aIter
->m_nViewPos
;
440 // nPrevPosModel is the field position
441 const sal_Int32 nPrevPosModel
= aIter
->m_nModelPos
;
442 const sal_Int32 nPrevPosExpand
= aIter
->m_nViewPos
;
444 const sal_Int32 nLengthModel
= nPosModel
- nPrevPosModel
;
445 const sal_Int32 nLengthExpand
= nPosExpand
- nPrevPosExpand
;
447 const sal_Int32 nFieldLengthExpand
= nLengthExpand
- nLengthModel
+ 1;
448 const sal_Int32 nFieldEndExpand
= nPrevPosExpand
+ nFieldLengthExpand
;
450 // Check if nPos is outside of field:
451 if ( nFieldEndExpand
<= nViewPos
)
453 // nPos is outside of field:
454 const sal_Int32 nDistToField
= nViewPos
- nFieldEndExpand
+ 1;
455 aRet
.mnPos
= nPrevPosModel
+ nDistToField
;
459 // nViewPos is inside a field:
460 aRet
.mnPos
= nPrevPosModel
;
461 aRet
.mnSubPos
= nViewPos
- nPrevPosExpand
;
462 aRet
.mbIsField
= true;
469 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */