1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <memory>
21 #include <bookmrk.hxx>
22 #include <IDocumentUndoRedo.hxx>
23 #include <IDocumentLinksAdministration.hxx>
24 #include <IDocumentState.hxx>
25 #include <doc.hxx>
26 #include <ndtxt.hxx>
27 #include <pam.hxx>
28 #include <swserv.hxx>
29 #include <sfx2/linkmgr.hxx>
30 #include <sfx2/viewsh.hxx>
31 #include <UndoBookmark.hxx>
32 #include <unobookmark.hxx>
33 #include <xmloff/odffields.hxx>
34 #include <libxml/xmlwriter.h>
35 #include <comphelper/random.hxx>
36 #include <comphelper/anytostring.hxx>
37 #include <sal/log.hxx>
38 #include <svl/zforlist.hxx>
39 #include <edtwin.hxx>
40 #include <DateFormFieldButton.hxx>
41 #include <DropDownFormFieldButton.hxx>
42 #include <DocumentContentOperationsManager.hxx>
43 #include <comphelper/lok.hxx>
44 #include <txtfrm.hxx>
45 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
46 #include <rtl/strbuf.hxx>
47 #include <strings.hrc>
48 
49 using namespace ::sw::mark;
50 using namespace ::com::sun::star;
51 using namespace ::com::sun::star::uno;
52 
53 namespace sw::mark
54 {
55 
FindFieldSep(IFieldmark const & rMark)56     SwPosition FindFieldSep(IFieldmark const& rMark)
57     {
58         SwPosition const& rStartPos(rMark.GetMarkStart());
59         SwPosition const& rEndPos(rMark.GetMarkEnd());
60         SwNodes const& rNodes(rStartPos.nNode.GetNodes());
61         sal_uLong const nStartNode(rStartPos.nNode.GetIndex());
62         sal_uLong const nEndNode(rEndPos.nNode.GetIndex());
63         int nFields(0);
64         std::optional<SwPosition> ret;
65         for (sal_uLong n = nEndNode; nStartNode <= n; --n)
66         {
67             SwNode *const pNode(rNodes[n]);
68             if (pNode->IsTextNode())
69             {
70                 SwTextNode & rTextNode(*pNode->GetTextNode());
71                 sal_Int32 const nStart(n == nStartNode
72                         ? rStartPos.nContent.GetIndex() + 1
73                         : 0);
74                 sal_Int32 const nEnd(n == nEndNode
75                         // subtract 1 to ignore the end char
76                         ? rEndPos.nContent.GetIndex() - 1
77                         : rTextNode.Len());
78                 for (sal_Int32 i = nEnd; nStart < i; --i)
79                 {
80                     const sal_Unicode c(rTextNode.GetText()[i - 1]);
81                     switch (c)
82                     {
83                         case CH_TXT_ATR_FIELDSTART:
84                             --nFields;
85                             assert(0 <= nFields);
86                             break;
87                         case CH_TXT_ATR_FIELDEND:
88                             ++nFields;
89                             // fields in field result could happen by manual
90                             // editing, although the field update deletes them
91                             break;
92                         case CH_TXT_ATR_FIELDSEP:
93                             if (nFields == 0)
94                             {
95                                 assert(!ret); // one per field
96                                 ret = SwPosition(rTextNode, i - 1);
97 #ifndef DBG_UTIL
98                                 return *ret;
99 #endif
100                             }
101                             break;
102                     }
103                 }
104             }
105             else if (pNode->IsEndNode() && !pNode->StartOfSectionNode()->IsSectionNode())
106             {
107                 assert(nStartNode <= pNode->StartOfSectionIndex());
108                 // fieldmark cannot overlap node section, unless it's a section
109                 n = pNode->StartOfSectionIndex();
110             }
111             else
112             {
113                 assert(pNode->IsNoTextNode() || pNode->IsSectionNode());
114             }
115         }
116         assert(ret); // must have found it
117         return *ret;
118     }
119 } // namespace sw::mark
120 
121 namespace
122 {
lcl_FixPosition(SwPosition & rPos)123     void lcl_FixPosition(SwPosition& rPos)
124     {
125         // make sure the position has 1) the proper node, and 2) a proper index
126         SwTextNode* pTextNode = rPos.nNode.GetNode().GetTextNode();
127         if(pTextNode == nullptr && rPos.nContent.GetIndex() > 0)
128         {
129             SAL_INFO(
130                 "sw.core",
131                 "illegal position: " << rPos.nContent.GetIndex()
132                     << " without proper TextNode");
133             rPos.nContent.Assign(nullptr, 0);
134         }
135         else if(pTextNode != nullptr && rPos.nContent.GetIndex() > pTextNode->Len())
136         {
137             SAL_INFO(
138                 "sw.core",
139                 "illegal position: " << rPos.nContent.GetIndex()
140                     << " is beyond " << pTextNode->Len());
141             rPos.nContent.Assign(pTextNode, pTextNode->Len());
142         }
143     }
144 
lcl_AssertFieldMarksSet(const Fieldmark & rField,const sal_Unicode aStartMark,const sal_Unicode aEndMark)145     void lcl_AssertFieldMarksSet(const Fieldmark& rField,
146         const sal_Unicode aStartMark,
147         const sal_Unicode aEndMark)
148     {
149         if (aEndMark != CH_TXT_ATR_FORMELEMENT)
150         {
151             SwPosition const& rStart(rField.GetMarkStart());
152             assert(rStart.nNode.GetNode().GetTextNode()->GetText()[rStart.nContent.GetIndex()] == aStartMark); (void) rStart; (void) aStartMark;
153             SwPosition const sepPos(sw::mark::FindFieldSep(rField));
154             assert(sepPos.nNode.GetNode().GetTextNode()->GetText()[sepPos.nContent.GetIndex()] == CH_TXT_ATR_FIELDSEP); (void) sepPos;
155         }
156         SwPosition const& rEnd(rField.GetMarkEnd());
157         assert(rEnd.nNode.GetNode().GetTextNode()->GetText()[rEnd.nContent.GetIndex() - 1] == aEndMark); (void) rEnd;
158     }
159 
lcl_SetFieldMarks(Fieldmark & rField,SwDoc & io_rDoc,const sal_Unicode aStartMark,const sal_Unicode aEndMark,SwPosition const * const pSepPos)160     void lcl_SetFieldMarks(Fieldmark& rField,
161         SwDoc& io_rDoc,
162         const sal_Unicode aStartMark,
163         const sal_Unicode aEndMark,
164         SwPosition const*const pSepPos)
165     {
166         io_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr);
167         OUString startChar(aStartMark);
168         if (aEndMark != CH_TXT_ATR_FORMELEMENT
169             && rField.GetMarkStart() == rField.GetMarkEnd())
170         {
171             // do only 1 InsertString call - to expand existing bookmarks at the
172             // position over the whole field instead of just aStartMark
173             startChar += OUStringChar(CH_TXT_ATR_FIELDSEP) + OUStringChar(aEndMark);
174         }
175 
176         SwPosition start = rField.GetMarkStart();
177         if (aEndMark != CH_TXT_ATR_FORMELEMENT)
178         {
179             SwPaM aStartPaM(start);
180             io_rDoc.getIDocumentContentOperations().InsertString(aStartPaM, startChar);
181             start.nContent -= startChar.getLength(); // restore, it was moved by InsertString
182             // do not manipulate via reference directly but call SetMarkStartPos
183             // which works even if start and end pos were the same
184             rField.SetMarkStartPos( start );
185             SwPosition& rEnd = rField.GetMarkEnd(); // note: retrieve after
186             // setting start, because if start==end it can go stale, see SetMarkPos()
187             assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd));
188             if (startChar.getLength() == 1)
189             {
190                 *aStartPaM.GetPoint() = pSepPos ? *pSepPos : rEnd;
191                 io_rDoc.getIDocumentContentOperations().InsertString(aStartPaM, OUString(CH_TXT_ATR_FIELDSEP));
192                 if (!pSepPos || rEnd < *pSepPos)
193                 {   // rEnd is not moved automatically if it's same as insert pos
194                     ++rEnd.nContent;
195                 }
196             }
197             assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd));
198         }
199         else
200         {
201             assert(pSepPos == nullptr);
202         }
203 
204         SwPosition& rEnd = rField.GetMarkEnd();
205         if (aEndMark && startChar.getLength() == 1)
206         {
207             SwPaM aEndPaM(rEnd);
208             io_rDoc.getIDocumentContentOperations().InsertString(aEndPaM, OUString(aEndMark));
209             ++rEnd.nContent;
210         }
211         lcl_AssertFieldMarksSet(rField, aStartMark, aEndMark);
212 
213         io_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr);
214     }
215 
lcl_RemoveFieldMarks(const Fieldmark & rField,SwDoc & io_rDoc,const sal_Unicode aStartMark,const sal_Unicode aEndMark)216     void lcl_RemoveFieldMarks(const Fieldmark& rField,
217         SwDoc& io_rDoc,
218         const sal_Unicode aStartMark,
219         const sal_Unicode aEndMark)
220     {
221         io_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr);
222 
223         const SwPosition& rStart = rField.GetMarkStart();
224         SwTextNode const*const pStartTextNode = rStart.nNode.GetNode().GetTextNode();
225         assert(pStartTextNode);
226         if (aEndMark != CH_TXT_ATR_FORMELEMENT)
227         {
228             (void) pStartTextNode;
229             // check this before start / end because of the +1 / -1 ...
230             SwPosition const sepPos(sw::mark::FindFieldSep(rField));
231             io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(rStart, aStartMark);
232             io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(sepPos, CH_TXT_ATR_FIELDSEP);
233         }
234 
235         const SwPosition& rEnd = rField.GetMarkEnd();
236         SwTextNode *const pEndTextNode = rEnd.nNode.GetNode().GetTextNode();
237         assert(pEndTextNode);
238         const sal_Int32 nEndPos = (rEnd == rStart)
239                                    ? rEnd.nContent.GetIndex()
240                                    : rEnd.nContent.GetIndex() - 1;
241         assert(pEndTextNode->GetText()[nEndPos] == aEndMark);
242         SwPosition const aEnd(*pEndTextNode, nEndPos);
243         io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(aEnd, aEndMark);
244 
245         io_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr);
246     }
247 
InvalidatePosition(SwPosition const & rPos)248     auto InvalidatePosition(SwPosition const& rPos) -> void
249     {
250         SwUpdateAttr const aHint(rPos.nContent.GetIndex(), rPos.nContent.GetIndex(), 0);
251         rPos.nNode.GetNode().GetTextNode()->CallSwClientNotify(sw::LegacyModifyHint(&aHint, &aHint));
252     }
253 }
254 
255 namespace sw::mark
256 {
MarkBase(const SwPaM & aPaM,const OUString & rName)257     MarkBase::MarkBase(const SwPaM& aPaM,
258         const OUString& rName)
259         : m_pPos1(new SwPosition(*(aPaM.GetPoint())))
260         , m_aName(rName)
261     {
262         m_pPos1->nContent.SetMark(this);
263         lcl_FixPosition(*m_pPos1);
264         if (aPaM.HasMark() && (*aPaM.GetMark() != *aPaM.GetPoint()))
265         {
266             MarkBase::SetOtherMarkPos(*(aPaM.GetMark()));
267             lcl_FixPosition(*m_pPos2);
268         }
269     }
270 
271     // For fieldmarks, the CH_TXT_ATR_FIELDSTART and CH_TXT_ATR_FIELDEND
272     // themselves are part of the covered range. This is guaranteed by
273     // TextFieldmark::InitDoc/lcl_AssureFieldMarksSet.
IsCoveringPosition(const SwPosition & rPos) const274     bool MarkBase::IsCoveringPosition(const SwPosition& rPos) const
275     {
276         return GetMarkStart() <= rPos && rPos < GetMarkEnd();
277     }
278 
SetMarkPos(const SwPosition & rNewPos)279     void MarkBase::SetMarkPos(const SwPosition& rNewPos)
280     {
281         std::make_unique<SwPosition>(rNewPos).swap(m_pPos1);
282         m_pPos1->nContent.SetMark(this);
283     }
284 
SetOtherMarkPos(const SwPosition & rNewPos)285     void MarkBase::SetOtherMarkPos(const SwPosition& rNewPos)
286     {
287         std::make_unique<SwPosition>(rNewPos).swap(m_pPos2);
288         m_pPos2->nContent.SetMark(this);
289     }
290 
ToString() const291     OUString MarkBase::ToString( ) const
292     {
293         return "Mark: ( Name, [ Node1, Index1 ] ): ( " + m_aName + ", [ "
294             + OUString::number( GetMarkPos().nNode.GetIndex( ) )  + ", "
295             + OUString::number( GetMarkPos().nContent.GetIndex( ) ) + " ] )";
296     }
297 
dumpAsXml(xmlTextWriterPtr pWriter) const298     void MarkBase::dumpAsXml(xmlTextWriterPtr pWriter) const
299     {
300         (void)xmlTextWriterStartElement(pWriter, BAD_CAST("MarkBase"));
301         (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(m_aName.toUtf8().getStr()));
302         (void)xmlTextWriterStartElement(pWriter, BAD_CAST("markPos"));
303         GetMarkPos().dumpAsXml(pWriter);
304         (void)xmlTextWriterEndElement(pWriter);
305         if (IsExpanded())
306         {
307             (void)xmlTextWriterStartElement(pWriter, BAD_CAST("otherMarkPos"));
308             GetOtherMarkPos().dumpAsXml(pWriter);
309             (void)xmlTextWriterEndElement(pWriter);
310         }
311         (void)xmlTextWriterEndElement(pWriter);
312     }
313 
~MarkBase()314     MarkBase::~MarkBase()
315     { }
316 
GenerateNewName(std::u16string_view rPrefix)317     OUString MarkBase::GenerateNewName(std::u16string_view rPrefix)
318     {
319         static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr);
320 
321         if (bHack)
322         {
323             static sal_Int64 nIdCounter = SAL_CONST_INT64(6000000000);
324             return rPrefix + OUString::number(nIdCounter++);
325         }
326         else
327         {
328             static OUString sUniquePostfix;
329             static sal_Int32 nCount = SAL_MAX_INT32;
330             if(nCount == SAL_MAX_INT32)
331             {
332                 unsigned int const n(comphelper::rng::uniform_uint_distribution(0,
333                                     std::numeric_limits<unsigned int>::max()));
334                 sUniquePostfix = "_" + OUString::number(n);
335                 nCount = 0;
336             }
337             // putting the counter in front of the random parts will speed up string comparisons
338             return rPrefix + OUString::number(nCount++) + sUniquePostfix;
339         }
340     }
341 
SwClientNotify(const SwModify &,const SfxHint & rHint)342     void MarkBase::SwClientNotify(const SwModify&, const SfxHint& rHint)
343     {
344         CallSwClientNotify(rHint);
345         if (rHint.GetId() != SfxHintId::SwLegacyModify)
346             return;
347         auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
348         if(RES_REMOVE_UNO_OBJECT == pLegacy->GetWhich())
349         {   // invalidate cached uno object
350             SetXBookmark(uno::Reference<text::XTextContent>(nullptr));
351         }
352     }
353 
InvalidateFrames()354     auto MarkBase::InvalidateFrames() -> void
355     {
356     }
357 
NavigatorReminder(const SwPaM & rPaM)358     NavigatorReminder::NavigatorReminder(const SwPaM& rPaM)
359         : MarkBase(rPaM, MarkBase::GenerateNewName(u"__NavigatorReminder__"))
360     { }
361 
UnoMark(const SwPaM & aPaM)362     UnoMark::UnoMark(const SwPaM& aPaM)
363         : MarkBase(aPaM, MarkBase::GenerateNewName(u"__UnoMark__"))
364     { }
365 
DdeBookmark(const SwPaM & aPaM)366     DdeBookmark::DdeBookmark(const SwPaM& aPaM)
367         : MarkBase(aPaM, MarkBase::GenerateNewName(u"__DdeLink__"))
368     { }
369 
SetRefObject(SwServerObject * pObj)370     void DdeBookmark::SetRefObject(SwServerObject* pObj)
371     {
372         m_aRefObj = pObj;
373     }
374 
DeregisterFromDoc(SwDoc & rDoc)375     void DdeBookmark::DeregisterFromDoc(SwDoc& rDoc)
376     {
377         if(m_aRefObj.is())
378             rDoc.getIDocumentLinksAdministration().GetLinkManager().RemoveServer(m_aRefObj.get());
379     }
380 
~DdeBookmark()381     DdeBookmark::~DdeBookmark()
382     {
383         if( m_aRefObj.is() )
384         {
385             if(m_aRefObj->HasDataLinks())
386             {
387                 ::sfx2::SvLinkSource* p = m_aRefObj.get();
388                 p->SendDataChanged();
389             }
390             m_aRefObj->SetNoServer();
391         }
392     }
393 
Bookmark(const SwPaM & aPaM,const vcl::KeyCode & rCode,const OUString & rName)394     Bookmark::Bookmark(const SwPaM& aPaM,
395         const vcl::KeyCode& rCode,
396         const OUString& rName)
397         : DdeBookmark(aPaM)
398         , ::sfx2::Metadatable()
399         , m_aCode(rCode)
400         , m_bHidden(false)
401     {
402         m_aName = rName;
403     }
404 
InitDoc(SwDoc & io_rDoc,sw::mark::InsertMode const,SwPosition const * const)405     void Bookmark::InitDoc(SwDoc& io_rDoc,
406             sw::mark::InsertMode const, SwPosition const*const)
407     {
408         if (io_rDoc.GetIDocumentUndoRedo().DoesUndo())
409         {
410             io_rDoc.GetIDocumentUndoRedo().AppendUndo(
411                     std::make_unique<SwUndoInsBookmark>(*this));
412         }
413         io_rDoc.getIDocumentState().SetModified();
414         InvalidateFrames();
415     }
416 
DeregisterFromDoc(SwDoc & io_rDoc)417     void Bookmark::DeregisterFromDoc(SwDoc& io_rDoc)
418     {
419         DdeBookmark::DeregisterFromDoc(io_rDoc);
420 
421         if (io_rDoc.GetIDocumentUndoRedo().DoesUndo())
422         {
423             io_rDoc.GetIDocumentUndoRedo().AppendUndo(
424                     std::make_unique<SwUndoDeleteBookmark>(*this));
425         }
426         io_rDoc.getIDocumentState().SetModified();
427         InvalidateFrames();
428     }
429 
430     // invalidate text frames in case it's hidden or Formatting Marks enabled
InvalidateFrames()431     auto Bookmark::InvalidateFrames() -> void
432     {
433         InvalidatePosition(GetMarkPos());
434         if (IsExpanded())
435         {
436             InvalidatePosition(GetOtherMarkPos());
437         }
438     }
439 
Hide(bool const isHide)440     void Bookmark::Hide(bool const isHide)
441     {
442         if (isHide != m_bHidden)
443         {
444             m_bHidden = isHide;
445             InvalidateFrames();
446         }
447     }
448 
SetHideCondition(OUString const & rHideCondition)449     void Bookmark::SetHideCondition(OUString const& rHideCondition)
450     {
451         if (m_sHideCondition != rHideCondition)
452         {
453             m_sHideCondition = rHideCondition;
454             InvalidateFrames();
455         }
456     }
457 
GetRegistry()458     ::sfx2::IXmlIdRegistry& Bookmark::GetRegistry()
459     {
460         SwDoc& rDoc( GetMarkPos().GetDoc() );
461         return rDoc.GetXmlIdRegistry();
462     }
463 
IsInClipboard() const464     bool Bookmark::IsInClipboard() const
465     {
466         SwDoc& rDoc( GetMarkPos().GetDoc() );
467         return rDoc.IsClipBoard();
468     }
469 
IsInUndo() const470     bool Bookmark::IsInUndo() const
471     {
472         return false;
473     }
474 
IsInContent() const475     bool Bookmark::IsInContent() const
476     {
477         SwDoc& rDoc( GetMarkPos().GetDoc() );
478         return !rDoc.IsInHeaderFooter( GetMarkPos().nNode );
479     }
480 
MakeUnoObject()481     uno::Reference< rdf::XMetadatable > Bookmark::MakeUnoObject()
482     {
483         SwDoc& rDoc( GetMarkPos().GetDoc() );
484         const uno::Reference< rdf::XMetadatable> xMeta(
485                 SwXBookmark::CreateXBookmark(rDoc, this), uno::UNO_QUERY);
486         return xMeta;
487     }
488 
Fieldmark(const SwPaM & rPaM)489     Fieldmark::Fieldmark(const SwPaM& rPaM)
490         : MarkBase(rPaM, MarkBase::GenerateNewName(u"__Fieldmark__"))
491     {
492         if(!IsExpanded())
493             SetOtherMarkPos(GetMarkPos());
494     }
495 
SetMarkStartPos(const SwPosition & rNewStartPos)496     void Fieldmark::SetMarkStartPos( const SwPosition& rNewStartPos )
497     {
498         if ( GetMarkPos( ) <= GetOtherMarkPos( ) )
499             return SetMarkPos( rNewStartPos );
500         else
501             return SetOtherMarkPos( rNewStartPos );
502     }
503 
SetMarkEndPos(const SwPosition & rNewEndPos)504     void Fieldmark::SetMarkEndPos( const SwPosition& rNewEndPos )
505     {
506         if ( GetMarkPos( ) <= GetOtherMarkPos( ) )
507             return SetOtherMarkPos( rNewEndPos );
508         else
509             return SetMarkPos( rNewEndPos );
510     }
511 
ToString() const512     OUString Fieldmark::ToString( ) const
513     {
514         return "Fieldmark: ( Name, Type, [ Nd1, Id1 ], [ Nd2, Id2 ] ): ( " + m_aName + ", "
515             + m_aFieldname + ", [ " + OUString::number( GetMarkPos().nNode.GetIndex( ) )
516             + ", " + OUString::number( GetMarkPos( ).nContent.GetIndex( ) ) + " ], ["
517             + OUString::number( GetOtherMarkPos().nNode.GetIndex( ) ) + ", "
518             + OUString::number( GetOtherMarkPos( ).nContent.GetIndex( ) ) + " ] ) ";
519     }
520 
Invalidate()521     void Fieldmark::Invalidate( )
522     {
523         // TODO: Does exist a better solution to trigger a format of the
524         //       fieldmark portion? If yes, please use it.
525         SwPaM aPaM( GetMarkPos(), GetOtherMarkPos() );
526         aPaM.InvalidatePaM();
527     }
528 
dumpAsXml(xmlTextWriterPtr pWriter) const529     void Fieldmark::dumpAsXml(xmlTextWriterPtr pWriter) const
530     {
531         (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Fieldmark"));
532         (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldname"), BAD_CAST(m_aFieldname.toUtf8().getStr()));
533         (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldHelptext"), BAD_CAST(m_aFieldHelptext.toUtf8().getStr()));
534         MarkBase::dumpAsXml(pWriter);
535         (void)xmlTextWriterStartElement(pWriter, BAD_CAST("parameters"));
536         for (auto& rParam : m_vParams)
537         {
538             (void)xmlTextWriterStartElement(pWriter, BAD_CAST("parameter"));
539             (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(rParam.first.toUtf8().getStr()));
540             (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(comphelper::anyToString(rParam.second).toUtf8().getStr()));
541             (void)xmlTextWriterEndElement(pWriter);
542         }
543         (void)xmlTextWriterEndElement(pWriter);
544         (void)xmlTextWriterEndElement(pWriter);
545     }
546 
TextFieldmark(const SwPaM & rPaM,const OUString & rName)547     TextFieldmark::TextFieldmark(const SwPaM& rPaM, const OUString& rName)
548         : Fieldmark(rPaM)
549     {
550         if ( !rName.isEmpty() )
551             m_aName = rName;
552     }
553 
InitDoc(SwDoc & io_rDoc,sw::mark::InsertMode const eMode,SwPosition const * const pSepPos)554     void TextFieldmark::InitDoc(SwDoc& io_rDoc,
555             sw::mark::InsertMode const eMode, SwPosition const*const pSepPos)
556     {
557         if (eMode == sw::mark::InsertMode::New)
558         {
559             lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos);
560         }
561         else
562         {
563             lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
564         }
565     }
566 
ReleaseDoc(SwDoc & rDoc)567     void TextFieldmark::ReleaseDoc(SwDoc& rDoc)
568     {
569         IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo());
570         if (rIDUR.DoesUndo())
571         {
572             rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this));
573         }
574         ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
575         lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
576         // notify layouts to unhide - for the entire fieldmark, as in InitDoc()
577         SwPaM const tmp(GetMarkPos(), GetOtherMarkPos());
578         sw::UpdateFramesForRemoveDeleteRedline(rDoc, tmp);
579     }
580 
NonTextFieldmark(const SwPaM & rPaM)581     NonTextFieldmark::NonTextFieldmark(const SwPaM& rPaM)
582         : Fieldmark(rPaM)
583     { }
584 
InitDoc(SwDoc & io_rDoc,sw::mark::InsertMode const eMode,SwPosition const * const pSepPos)585     void NonTextFieldmark::InitDoc(SwDoc& io_rDoc,
586             sw::mark::InsertMode const eMode, SwPosition const*const pSepPos)
587     {
588         assert(pSepPos == nullptr);
589         if (eMode == sw::mark::InsertMode::New)
590         {
591             lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT, pSepPos);
592 
593             // For some reason the end mark is moved from 1 by the Insert:
594             // we don't want this for checkboxes
595             SwPosition aNewEndPos = GetMarkEnd();
596             aNewEndPos.nContent--;
597             SetMarkEndPos( aNewEndPos );
598         }
599         else
600         {
601             lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT);
602         }
603     }
604 
ReleaseDoc(SwDoc & rDoc)605     void NonTextFieldmark::ReleaseDoc(SwDoc& rDoc)
606     {
607         IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo());
608         if (rIDUR.DoesUndo())
609         {
610             rIDUR.AppendUndo(std::make_unique<SwUndoDelNoTextFieldmark>(*this));
611         }
612         ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
613         lcl_RemoveFieldMarks(*this, rDoc,
614                 CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT);
615     }
616 
617 
CheckboxFieldmark(const SwPaM & rPaM)618     CheckboxFieldmark::CheckboxFieldmark(const SwPaM& rPaM)
619         : NonTextFieldmark(rPaM)
620     { }
621 
SetChecked(bool checked)622     void CheckboxFieldmark::SetChecked(bool checked)
623     {
624         if ( IsChecked() != checked )
625         {
626             (*GetParameters())[OUString(ODF_FORMCHECKBOX_RESULT)] <<= checked;
627             // mark document as modified
628             SwDoc& rDoc( GetMarkPos().GetDoc() );
629             rDoc.getIDocumentState().SetModified();
630         }
631     }
632 
IsChecked() const633     bool CheckboxFieldmark::IsChecked() const
634     {
635         bool bResult = false;
636         parameter_map_t::const_iterator pResult = GetParameters()->find(OUString(ODF_FORMCHECKBOX_RESULT));
637         if(pResult != GetParameters()->end())
638             pResult->second >>= bResult;
639         return bResult;
640     }
641 
FieldmarkWithDropDownButton(const SwPaM & rPaM)642     FieldmarkWithDropDownButton::FieldmarkWithDropDownButton(const SwPaM& rPaM)
643         : NonTextFieldmark(rPaM)
644         , m_pButton(nullptr)
645     {
646     }
647 
~FieldmarkWithDropDownButton()648     FieldmarkWithDropDownButton::~FieldmarkWithDropDownButton()
649     {
650         m_pButton.disposeAndClear();
651     }
652 
RemoveButton()653     void FieldmarkWithDropDownButton::RemoveButton()
654     {
655         if(m_pButton)
656             m_pButton.disposeAndClear();
657     }
658 
DropDownFieldmark(const SwPaM & rPaM)659     DropDownFieldmark::DropDownFieldmark(const SwPaM& rPaM)
660         : FieldmarkWithDropDownButton(rPaM)
661     {
662     }
663 
~DropDownFieldmark()664     DropDownFieldmark::~DropDownFieldmark()
665     {
666     }
667 
ShowButton(SwEditWin * pEditWin)668     void DropDownFieldmark::ShowButton(SwEditWin* pEditWin)
669     {
670         if(pEditWin)
671         {
672             if(!m_pButton)
673                 m_pButton = VclPtr<DropDownFormFieldButton>::Create(pEditWin, *this);
674             m_pButton->CalcPosAndSize(m_aPortionPaintArea);
675             m_pButton->Show();
676         }
677     }
678 
RemoveButton()679     void DropDownFieldmark::RemoveButton()
680     {
681         FieldmarkWithDropDownButton::RemoveButton();
682     }
683 
SetPortionPaintArea(const SwRect & rPortionPaintArea)684     void DropDownFieldmark::SetPortionPaintArea(const SwRect& rPortionPaintArea)
685     {
686         m_aPortionPaintArea = rPortionPaintArea;
687         if(m_pButton)
688         {
689             m_pButton->Show();
690             m_pButton->CalcPosAndSize(m_aPortionPaintArea);
691         }
692     }
693 
SendLOKShowMessage(SfxViewShell * pViewShell)694     void DropDownFieldmark::SendLOKShowMessage(SfxViewShell* pViewShell)
695     {
696         if (!comphelper::LibreOfficeKit::isActive())
697             return;
698 
699         if (!pViewShell || pViewShell->isLOKMobilePhone())
700             return;
701 
702         if (m_aPortionPaintArea.IsEmpty())
703             return;
704 
705         OStringBuffer sPayload;
706         sPayload = OString::Concat("{\"action\": \"show\","
707                    " \"type\": \"drop-down\", \"textArea\": \"") +
708                    m_aPortionPaintArea.SVRect().toString() + "\",";
709         // Add field params to the message
710         sPayload.append(" \"params\": { \"items\": [");
711 
712         // List items
713         auto pParameters = this->GetParameters();
714         auto pListEntriesIter = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
715         css::uno::Sequence<OUString> vListEntries;
716         if (pListEntriesIter != pParameters->end())
717         {
718             pListEntriesIter->second >>= vListEntries;
719             for (const OUString& sItem : std::as_const(vListEntries))
720                 sPayload.append("\"" + OUStringToOString(sItem, RTL_TEXTENCODING_UTF8) + "\", ");
721             sPayload.setLength(sPayload.getLength() - 2);
722         }
723         sPayload.append("], ");
724 
725         // Selected item
726         auto pSelectedItemIter = pParameters->find(ODF_FORMDROPDOWN_RESULT);
727         sal_Int32 nSelection = -1;
728         if (pSelectedItemIter != pParameters->end())
729         {
730             pSelectedItemIter->second >>= nSelection;
731         }
732         sPayload.append("\"selected\": \"" + OString::number(nSelection) + "\", ");
733 
734         // Placeholder text
735         sPayload.append("\"placeholderText\": \"" + OUStringToOString(SwResId(STR_DROP_DOWN_EMPTY_LIST), RTL_TEXTENCODING_UTF8) + "\"}}");
736         pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, sPayload.toString().getStr());
737     }
738 
SendLOKHideMessage(SfxViewShell * pViewShell)739     void DropDownFieldmark::SendLOKHideMessage(SfxViewShell* pViewShell)
740     {
741         OString sPayload = "{\"action\": \"hide\", \"type\": \"drop-down\"}";
742         pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, sPayload.getStr());
743     }
744 
DateFieldmark(const SwPaM & rPaM)745     DateFieldmark::DateFieldmark(const SwPaM& rPaM)
746         : FieldmarkWithDropDownButton(rPaM)
747         , m_pNumberFormatter(nullptr)
748         , m_pDocumentContentOperationsManager(nullptr)
749     {
750     }
751 
~DateFieldmark()752     DateFieldmark::~DateFieldmark()
753     {
754     }
755 
InitDoc(SwDoc & io_rDoc,sw::mark::InsertMode eMode,SwPosition const * const pSepPos)756     void DateFieldmark::InitDoc(SwDoc& io_rDoc,
757             sw::mark::InsertMode eMode, SwPosition const*const pSepPos)
758     {
759         m_pNumberFormatter = io_rDoc.GetNumberFormatter();
760         m_pDocumentContentOperationsManager = &io_rDoc.GetDocumentContentOperationsManager();
761         if (eMode == sw::mark::InsertMode::New)
762         {
763             lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos);
764         }
765         else
766         {
767             lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
768         }
769     }
770 
ReleaseDoc(SwDoc & rDoc)771     void DateFieldmark::ReleaseDoc(SwDoc& rDoc)
772     {
773         IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo());
774         if (rIDUR.DoesUndo())
775         {
776             // TODO does this need a 3rd Undo class?
777             rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this));
778         }
779         ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
780         lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
781         // notify layouts to unhide - for the entire fieldmark, as in InitDoc()
782         SwPaM const tmp(GetMarkPos(), GetOtherMarkPos());
783         sw::UpdateFramesForRemoveDeleteRedline(rDoc, tmp);
784     }
785 
ShowButton(SwEditWin * pEditWin)786     void DateFieldmark::ShowButton(SwEditWin* pEditWin)
787     {
788         if(pEditWin)
789         {
790             if(!m_pButton)
791                 m_pButton = VclPtr<DateFormFieldButton>::Create(pEditWin, *this, m_pNumberFormatter);
792             SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight());
793             m_pButton->CalcPosAndSize(aPaintArea);
794             m_pButton->Show();
795         }
796     }
797 
SetPortionPaintAreaStart(const SwRect & rPortionPaintArea)798     void DateFieldmark::SetPortionPaintAreaStart(const SwRect& rPortionPaintArea)
799     {
800         if (rPortionPaintArea.IsEmpty())
801             return;
802 
803         m_aPaintAreaStart = rPortionPaintArea;
804         InvalidateCurrentDateParam();
805     }
806 
SetPortionPaintAreaEnd(const SwRect & rPortionPaintArea)807     void DateFieldmark::SetPortionPaintAreaEnd(const SwRect& rPortionPaintArea)
808     {
809         if (rPortionPaintArea.IsEmpty())
810             return;
811 
812         if(m_aPaintAreaEnd == rPortionPaintArea &&
813            m_pButton && m_pButton->IsVisible())
814             return;
815 
816         m_aPaintAreaEnd = rPortionPaintArea;
817         if(m_pButton)
818         {
819             m_pButton->Show();
820             SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight());
821             m_pButton->CalcPosAndSize(aPaintArea);
822             m_pButton->Invalidate();
823         }
824         InvalidateCurrentDateParam();
825     }
826 
GetContent() const827     OUString DateFieldmark::GetContent() const
828     {
829         const SwTextNode* const pTextNode = GetMarkEnd().nNode.GetNode().GetTextNode();
830         SwPosition const sepPos(sw::mark::FindFieldSep(*this));
831         const sal_Int32 nStart(sepPos.nContent.GetIndex());
832         const sal_Int32 nEnd  (GetMarkEnd().nContent.GetIndex());
833 
834         OUString sContent;
835         if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() &&
836            nEnd > nStart + 2)
837             sContent = pTextNode->GetText().copy(nStart + 1, nEnd - nStart - 2);
838         return sContent;
839     }
840 
ReplaceContent(const OUString & sNewContent)841     void DateFieldmark::ReplaceContent(const OUString& sNewContent)
842     {
843         if(!m_pDocumentContentOperationsManager)
844             return;
845 
846         const SwTextNode* const pTextNode = GetMarkEnd().nNode.GetNode().GetTextNode();
847         SwPosition const sepPos(sw::mark::FindFieldSep(*this));
848         const sal_Int32 nStart(sepPos.nContent.GetIndex());
849         const sal_Int32 nEnd  (GetMarkEnd().nContent.GetIndex());
850 
851         if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() &&
852            nEnd > nStart + 2)
853         {
854             SwPaM aFieldPam(GetMarkStart().nNode, nStart + 1,
855                             GetMarkStart().nNode, nEnd - 1);
856             m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false);
857         }
858         else
859         {
860             SwPaM aFieldStartPam(GetMarkStart().nNode, nStart + 1);
861             m_pDocumentContentOperationsManager->InsertString(aFieldStartPam, sNewContent);
862         }
863 
864     }
865 
GetCurrentDate() const866     std::pair<bool, double> DateFieldmark::GetCurrentDate() const
867     {
868         // Check current date param first
869         std::pair<bool, double> aResult = ParseCurrentDateParam();
870         if(aResult.first)
871             return aResult;
872 
873         const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
874         bool bFoundValidDate = false;
875         double dCurrentDate = 0;
876         OUString sDateFormat;
877         auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
878         if (pResult != pParameters->end())
879         {
880             pResult->second >>= sDateFormat;
881         }
882 
883         OUString sLang;
884         pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
885         if (pResult != pParameters->end())
886         {
887             pResult->second >>= sLang;
888         }
889 
890         // Get current content of the field
891         OUString sContent = GetContent();
892 
893         sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType());
894         if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
895         {
896             sal_Int32 nCheckPos = 0;
897             SvNumFormatType nType;
898             m_pNumberFormatter->PutEntry(sDateFormat,
899                                          nCheckPos,
900                                          nType,
901                                          nFormat,
902                                          LanguageTag(sLang).getLanguageType());
903         }
904 
905         if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
906         {
907             bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sContent, nFormat, dCurrentDate);
908         }
909         return std::pair<bool, double>(bFoundValidDate, dCurrentDate);
910     }
911 
SetCurrentDate(double fDate)912     void DateFieldmark::SetCurrentDate(double fDate)
913     {
914         // Replace current content with the selected date
915         ReplaceContent(GetDateInCurrentDateFormat(fDate));
916 
917         // Also save the current date in a standard format
918         sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
919         (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= GetDateInStandardDateFormat(fDate);
920     }
921 
GetDateInStandardDateFormat(double fDate) const922     OUString DateFieldmark::GetDateInStandardDateFormat(double fDate) const
923     {
924         OUString sCurrentDate;
925         sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE);
926         if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
927         {
928             sal_Int32 nCheckPos = 0;
929             SvNumFormatType nType;
930             OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT;
931             m_pNumberFormatter->PutEntry(sFormat,
932                                          nCheckPos,
933                                          nType,
934                                          nFormat,
935                                          ODF_FORMDATE_CURRENTDATE_LANGUAGE);
936         }
937 
938         if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
939         {
940             const Color* pCol = nullptr;
941             m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentDate, &pCol, false);
942         }
943         return sCurrentDate;
944     }
945 
ParseCurrentDateParam() const946     std::pair<bool, double> DateFieldmark::ParseCurrentDateParam() const
947     {
948         bool bFoundValidDate = false;
949         double dCurrentDate = 0;
950 
951         const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
952         auto pResult = pParameters->find(ODF_FORMDATE_CURRENTDATE);
953         OUString sCurrentDate;
954         if (pResult != pParameters->end())
955         {
956             pResult->second >>= sCurrentDate;
957         }
958         if(!sCurrentDate.isEmpty())
959         {
960             sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE);
961             if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
962             {
963                 sal_Int32 nCheckPos = 0;
964                 SvNumFormatType nType;
965                 OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT;
966                 m_pNumberFormatter->PutEntry(sFormat,
967                                              nCheckPos,
968                                              nType,
969                                              nFormat,
970                                              ODF_FORMDATE_CURRENTDATE_LANGUAGE);
971             }
972 
973             if(nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
974             {
975                 bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sCurrentDate, nFormat, dCurrentDate);
976             }
977         }
978         return std::pair<bool, double>(bFoundValidDate, dCurrentDate);
979     }
980 
981 
GetDateInCurrentDateFormat(double fDate) const982     OUString DateFieldmark::GetDateInCurrentDateFormat(double fDate) const
983     {
984         // Get current date format and language
985         OUString sDateFormat;
986         const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
987         auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
988         if (pResult != pParameters->end())
989         {
990             pResult->second >>= sDateFormat;
991         }
992 
993         OUString sLang;
994         pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
995         if (pResult != pParameters->end())
996         {
997             pResult->second >>= sLang;
998         }
999 
1000         // Fill the content with the specified format
1001         OUString sCurrentContent;
1002         sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType());
1003         if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
1004         {
1005             sal_Int32 nCheckPos = 0;
1006             SvNumFormatType nType;
1007             OUString sFormat = sDateFormat;
1008             m_pNumberFormatter->PutEntry(sFormat,
1009                                          nCheckPos,
1010                                          nType,
1011                                          nFormat,
1012                                          LanguageTag(sLang).getLanguageType());
1013         }
1014 
1015         if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
1016         {
1017             const Color* pCol = nullptr;
1018             m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentContent, &pCol, false);
1019         }
1020         return sCurrentContent;
1021     }
1022 
InvalidateCurrentDateParam()1023     void DateFieldmark::InvalidateCurrentDateParam()
1024     {
1025         std::pair<bool, double> aResult = ParseCurrentDateParam();
1026         if(!aResult.first)
1027             return;
1028 
1029         // Current date became invalid
1030         if(GetDateInCurrentDateFormat(aResult.second) != GetContent())
1031         {
1032             sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
1033             (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= OUString();
1034         }
1035     }
1036 }
1037 
1038 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1039