Add a comment to clarify what kind of inputs the class handles
[LibreOffice.git] / sw / source / core / txtnode / modeltoviewhelper.cxx
blob7b33bb32baa8fc432ae60a71128c0113e2d26483
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
21 #include <doc.hxx>
22 #include <IMark.hxx>
23 #include <fldbas.hxx>
24 #include <fmtfld.hxx>
25 #include <fmtftn.hxx>
26 #include <modeltoviewhelper.hxx>
27 #include <ndtxt.hxx>
28 #include <pam.hxx>
29 #include <txatbase.hxx>
30 #include <txtfld.hxx>
31 #include <txtftn.hxx>
32 #include <scriptinfo.hxx>
33 #include <IDocumentMarkAccess.hxx>
34 #include <bookmark.hxx>
35 #include <o3tl/sorted_vector.hxx>
36 #include <deque>
37 #include <vector>
39 namespace {
41 struct FieldResult
43 sal_Int32 m_nFieldPos;
44 OUString m_sExpand;
45 enum { NONE, FIELD, FOOTNOTE } m_eType;
46 explicit FieldResult(sal_Int32 const nPos)
47 : m_nFieldPos(nPos), m_eType(NONE)
48 { }
51 class sortfieldresults
53 public:
54 bool operator()(const FieldResult &rOne, const FieldResult &rTwo) const
56 return rOne.m_nFieldPos < rTwo.m_nFieldPos;
62 namespace {
64 struct block
66 sal_Int32 m_nStart;
67 sal_Int32 m_nLen;
68 bool m_bVisible;
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)
76 struct containsPos
78 const sal_Int32 m_nPos;
79 explicit containsPos(const sal_Int32 nPos)
80 : m_nPos(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)
98 return;
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);
115 while (true)
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)
126 break;
128 pFieldMark = nullptr;
129 if (!cursor.Move(fnMoveBackward, GoInContent))
131 break;
134 if (!pFieldMark)
136 break;
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);
149 else
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))
158 break;
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)));
174 assert(pFieldMark);
175 startedFields.emplace_back(pFieldMark, false);
176 if (!oStartHidden)
178 oStartHidden.emplace(i);
180 break;
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"
194 #endif
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
200 #endif
202 break;
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);
209 break;
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);
229 break;
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;
248 if (nShownLen)
249 aBlocks.emplace_back(nShownStart, nShownLen, true);
251 if (nHiddenLen)
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
264 //to is
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))
273 continue;
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;
287 break;
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;
298 break;
299 case RES_TXTATR_FTN:
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;
309 break;
310 default:
311 break;
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
321 //to is
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))
330 continue;
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;
352 if (!nBlockLen)
353 continue;
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;
366 else
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);
381 break;
382 case FieldResult::FOOTNOTE:
383 m_FootnotePositions.push_back(viewFieldPos);
384 break;
385 case FieldResult::NONE: /*ignore*/
386 break;
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
413 //modelpos
414 const sal_Int32 nOffsetFromEnd = aIter->m_nModelPos - nModelPos;
415 return aIter->m_nViewPos - nOffsetFromEnd;
418 return nModelPos;
421 /** Converts a view position into a model position
423 ModelToViewHelper::ModelPosition ModelToViewHelper::ConvertToModelPosition( sal_Int32 nViewPos ) const
425 ModelPosition aRet;
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;
438 --aIter;
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;
457 else
459 // nViewPos is inside a field:
460 aRet.mnPos = nPrevPosModel;
461 aRet.mnSubPos = nViewPos - nPrevPosExpand;
462 aRet.mbIsField = true;
466 return aRet;
469 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */