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