summaryrefslogtreecommitdiffstats
path: root/sw/source/core/crsr/bookmrk.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/crsr/bookmrk.cxx')
-rw-r--r--sw/source/core/crsr/bookmrk.cxx1068
1 files changed, 1068 insertions, 0 deletions
diff --git a/sw/source/core/crsr/bookmrk.cxx b/sw/source/core/crsr/bookmrk.cxx
new file mode 100644
index 000000000..bd7e5d48f
--- /dev/null
+++ b/sw/source/core/crsr/bookmrk.cxx
@@ -0,0 +1,1068 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <bookmrk.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentLinksAdministration.hxx>
+#include <IDocumentState.hxx>
+#include <doc.hxx>
+#include <ndtxt.hxx>
+#include <pam.hxx>
+#include <swserv.hxx>
+#include <sfx2/linkmgr.hxx>
+#include <UndoBookmark.hxx>
+#include <unobookmark.hxx>
+#include <xmloff/odffields.hxx>
+#include <libxml/xmlwriter.h>
+#include <comphelper/random.hxx>
+#include <comphelper/anytostring.hxx>
+#include <sal/log.hxx>
+#include <svl/zforlist.hxx>
+#include <edtwin.hxx>
+#include <DateFormFieldButton.hxx>
+#include <DropDownFormFieldButton.hxx>
+#include <DocumentContentOperationsManager.hxx>
+#include <comphelper/lok.hxx>
+#include <view.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <wrtsh.hxx>
+#include <rtl/strbuf.hxx>
+#include <sfx2/lokhelper.hxx>
+#include <strings.hrc>
+
+using namespace ::sw::mark;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace sw::mark
+{
+
+ SwPosition FindFieldSep(IFieldmark const& rMark)
+ {
+ SwPosition const& rStartPos(rMark.GetMarkStart());
+ SwPosition const& rEndPos(rMark.GetMarkEnd());
+ SwNodes const& rNodes(rStartPos.nNode.GetNodes());
+ sal_uLong const nStartNode(rStartPos.nNode.GetIndex());
+ sal_uLong const nEndNode(rEndPos.nNode.GetIndex());
+ int nFields(0);
+ std::optional<SwPosition> ret;
+ for (sal_uLong n = nEndNode; nStartNode <= n; --n)
+ {
+ SwNode *const pNode(rNodes[n]);
+ if (pNode->IsTextNode())
+ {
+ SwTextNode & rTextNode(*pNode->GetTextNode());
+ sal_Int32 const nStart(n == nStartNode
+ ? rStartPos.nContent.GetIndex() + 1
+ : 0);
+ sal_Int32 const nEnd(n == nEndNode
+ // subtract 1 to ignore the end char
+ ? rEndPos.nContent.GetIndex() - 1
+ : rTextNode.Len());
+ for (sal_Int32 i = nEnd; nStart < i; --i)
+ {
+ const sal_Unicode c(rTextNode.GetText()[i - 1]);
+ switch (c)
+ {
+ case CH_TXT_ATR_FIELDSTART:
+ --nFields;
+ assert(0 <= nFields);
+ break;
+ case CH_TXT_ATR_FIELDEND:
+ ++nFields;
+ // fields in field result could happen by manual
+ // editing, although the field update deletes them
+ break;
+ case CH_TXT_ATR_FIELDSEP:
+ if (nFields == 0)
+ {
+ assert(!ret); // one per field
+ ret = SwPosition(rTextNode, i - 1);
+#ifndef DBG_UTIL
+ return *ret;
+#endif
+ }
+ break;
+ }
+ }
+ }
+ else if (pNode->IsEndNode())
+ {
+ assert(nStartNode <= pNode->StartOfSectionIndex());
+ // fieldmark cannot overlap node section
+ n = pNode->StartOfSectionIndex();
+ }
+ else
+ {
+ assert(pNode->IsNoTextNode());
+ }
+ }
+ assert(ret); // must have found it
+ return *ret;
+ }
+} // namespace sw::mark
+
+namespace
+{
+ void lcl_FixPosition(SwPosition& rPos)
+ {
+ // make sure the position has 1) the proper node, and 2) a proper index
+ SwTextNode* pTextNode = rPos.nNode.GetNode().GetTextNode();
+ if(pTextNode == nullptr && rPos.nContent.GetIndex() > 0)
+ {
+ SAL_INFO(
+ "sw.core",
+ "illegal position: " << rPos.nContent.GetIndex()
+ << " without proper TextNode");
+ rPos.nContent.Assign(nullptr, 0);
+ }
+ else if(pTextNode != nullptr && rPos.nContent.GetIndex() > pTextNode->Len())
+ {
+ SAL_INFO(
+ "sw.core",
+ "illegal position: " << rPos.nContent.GetIndex()
+ << " is beyond " << pTextNode->Len());
+ rPos.nContent.Assign(pTextNode, pTextNode->Len());
+ }
+ }
+
+ void lcl_AssertFieldMarksSet(Fieldmark const * const pField,
+ const sal_Unicode aStartMark,
+ const sal_Unicode aEndMark)
+ {
+ if (aEndMark != CH_TXT_ATR_FORMELEMENT)
+ {
+ SwPosition const& rStart(pField->GetMarkStart());
+ assert(rStart.nNode.GetNode().GetTextNode()->GetText()[rStart.nContent.GetIndex()] == aStartMark); (void) rStart; (void) aStartMark;
+ SwPosition const sepPos(sw::mark::FindFieldSep(*pField));
+ assert(sepPos.nNode.GetNode().GetTextNode()->GetText()[sepPos.nContent.GetIndex()] == CH_TXT_ATR_FIELDSEP); (void) sepPos;
+ }
+ SwPosition const& rEnd(pField->GetMarkEnd());
+ assert(rEnd.nNode.GetNode().GetTextNode()->GetText()[rEnd.nContent.GetIndex() - 1] == aEndMark); (void) rEnd;
+ }
+
+ void lcl_SetFieldMarks(Fieldmark* const pField,
+ SwDoc* const io_pDoc,
+ const sal_Unicode aStartMark,
+ const sal_Unicode aEndMark,
+ SwPosition const*const pSepPos)
+ {
+ io_pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr);
+ OUString startChar(aStartMark);
+ if (aEndMark != CH_TXT_ATR_FORMELEMENT
+ && pField->GetMarkStart() == pField->GetMarkEnd())
+ {
+ // do only 1 InsertString call - to expand existing bookmarks at the
+ // position over the whole field instead of just aStartMark
+ startChar += OUStringChar(CH_TXT_ATR_FIELDSEP) + OUStringChar(aEndMark);
+ }
+
+ SwPosition start = pField->GetMarkStart();
+ if (aEndMark != CH_TXT_ATR_FORMELEMENT)
+ {
+ SwPaM aStartPaM(start);
+ io_pDoc->getIDocumentContentOperations().InsertString(aStartPaM, startChar);
+ start.nContent -= startChar.getLength(); // restore, it was moved by InsertString
+ // do not manipulate via reference directly but call SetMarkStartPos
+ // which works even if start and end pos were the same
+ pField->SetMarkStartPos( start );
+ SwPosition& rEnd = pField->GetMarkEnd(); // note: retrieve after
+ // setting start, because if start==end it can go stale, see SetMarkPos()
+ assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd));
+ if (startChar.getLength() == 1)
+ {
+ *aStartPaM.GetPoint() = pSepPos ? *pSepPos : rEnd;
+ io_pDoc->getIDocumentContentOperations().InsertString(aStartPaM, OUString(CH_TXT_ATR_FIELDSEP));
+ if (!pSepPos || rEnd < *pSepPos)
+ { // rEnd is not moved automatically if it's same as insert pos
+ ++rEnd.nContent;
+ }
+ }
+ assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd));
+ }
+ else
+ {
+ assert(pSepPos == nullptr);
+ }
+
+ SwPosition& rEnd = pField->GetMarkEnd();
+ if (aEndMark && startChar.getLength() == 1)
+ {
+ SwPaM aEndPaM(rEnd);
+ io_pDoc->getIDocumentContentOperations().InsertString(aEndPaM, OUString(aEndMark));
+ ++rEnd.nContent;
+ }
+ lcl_AssertFieldMarksSet(pField, aStartMark, aEndMark);
+
+ io_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr);
+ };
+
+ void lcl_RemoveFieldMarks(Fieldmark const * const pField,
+ SwDoc* const io_pDoc,
+ const sal_Unicode aStartMark,
+ const sal_Unicode aEndMark)
+ {
+ io_pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr);
+
+ const SwPosition& rStart = pField->GetMarkStart();
+ SwTextNode const*const pStartTextNode = rStart.nNode.GetNode().GetTextNode();
+ assert(pStartTextNode);
+ if (aEndMark != CH_TXT_ATR_FORMELEMENT)
+ {
+ (void) pStartTextNode;
+ // check this before start / end because of the +1 / -1 ...
+ SwPosition const sepPos(sw::mark::FindFieldSep(*pField));
+ io_pDoc->GetDocumentContentOperationsManager().DeleteDummyChar(rStart, aStartMark);
+ io_pDoc->GetDocumentContentOperationsManager().DeleteDummyChar(sepPos, CH_TXT_ATR_FIELDSEP);
+ }
+
+ const SwPosition& rEnd = pField->GetMarkEnd();
+ SwTextNode *const pEndTextNode = rEnd.nNode.GetNode().GetTextNode();
+ assert(pEndTextNode);
+ const sal_Int32 nEndPos = (rEnd == rStart)
+ ? rEnd.nContent.GetIndex()
+ : rEnd.nContent.GetIndex() - 1;
+ assert(pEndTextNode->GetText()[nEndPos] == aEndMark);
+ SwPosition const aEnd(*pEndTextNode, nEndPos);
+ io_pDoc->GetDocumentContentOperationsManager().DeleteDummyChar(aEnd, aEndMark);
+
+ io_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr);
+ };
+
+ auto InvalidatePosition(SwPosition const& rPos) -> void
+ {
+ SwUpdateAttr const hint(rPos.nContent.GetIndex(), rPos.nContent.GetIndex(), 0);
+ rPos.nNode.GetNode().GetTextNode()->NotifyClients(nullptr, &hint);
+ }
+}
+
+namespace sw::mark
+{
+ MarkBase::MarkBase(const SwPaM& aPaM,
+ const OUString& rName)
+ : m_pPos1(new SwPosition(*(aPaM.GetPoint())))
+ , m_aName(rName)
+ {
+ m_pPos1->nContent.SetMark(this);
+ lcl_FixPosition(*m_pPos1);
+ if (aPaM.HasMark() && (*aPaM.GetMark() != *aPaM.GetPoint()))
+ {
+ MarkBase::SetOtherMarkPos(*(aPaM.GetMark()));
+ lcl_FixPosition(*m_pPos2);
+ }
+ }
+
+ // For fieldmarks, the CH_TXT_ATR_FIELDSTART and CH_TXT_ATR_FIELDEND
+ // themselves are part of the covered range. This is guaranteed by
+ // TextFieldmark::InitDoc/lcl_AssureFieldMarksSet.
+ bool MarkBase::IsCoveringPosition(const SwPosition& rPos) const
+ {
+ return GetMarkStart() <= rPos && rPos < GetMarkEnd();
+ }
+
+ void MarkBase::SetMarkPos(const SwPosition& rNewPos)
+ {
+ std::make_unique<SwPosition>(rNewPos).swap(m_pPos1);
+ m_pPos1->nContent.SetMark(this);
+ }
+
+ void MarkBase::SetOtherMarkPos(const SwPosition& rNewPos)
+ {
+ std::make_unique<SwPosition>(rNewPos).swap(m_pPos2);
+ m_pPos2->nContent.SetMark(this);
+ }
+
+ OUString MarkBase::ToString( ) const
+ {
+ return "Mark: ( Name, [ Node1, Index1 ] ): ( " + m_aName + ", [ "
+ + OUString::number( GetMarkPos().nNode.GetIndex( ) ) + ", "
+ + OUString::number( GetMarkPos().nContent.GetIndex( ) ) + " ] )";
+ }
+
+ void MarkBase::dumpAsXml(xmlTextWriterPtr pWriter) const
+ {
+ xmlTextWriterStartElement(pWriter, BAD_CAST("MarkBase"));
+ xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(m_aName.toUtf8().getStr()));
+ xmlTextWriterStartElement(pWriter, BAD_CAST("markPos"));
+ GetMarkPos().dumpAsXml(pWriter);
+ xmlTextWriterEndElement(pWriter);
+ if (IsExpanded())
+ {
+ xmlTextWriterStartElement(pWriter, BAD_CAST("otherMarkPos"));
+ GetOtherMarkPos().dumpAsXml(pWriter);
+ xmlTextWriterEndElement(pWriter);
+ }
+ xmlTextWriterEndElement(pWriter);
+ }
+
+ MarkBase::~MarkBase()
+ { }
+
+ OUString MarkBase::GenerateNewName(const OUString& rPrefix)
+ {
+ static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr);
+
+ if (bHack)
+ {
+ static sal_Int64 nIdCounter = SAL_CONST_INT64(6000000000);
+ return rPrefix + OUString::number(nIdCounter++);
+ }
+ else
+ {
+ static OUString sUniquePostfix;
+ static sal_Int32 nCount = SAL_MAX_INT32;
+ if(nCount == SAL_MAX_INT32)
+ {
+ unsigned int const n(comphelper::rng::uniform_uint_distribution(0,
+ std::numeric_limits<unsigned int>::max()));
+ sUniquePostfix = "_" + OUString::number(n);
+ nCount = 0;
+ }
+ // putting the counter in front of the random parts will speed up string comparisons
+ return rPrefix + OUString::number(nCount++) + sUniquePostfix;
+ }
+ }
+
+ void MarkBase::Modify( const SfxPoolItem *pOld, const SfxPoolItem *pNew )
+ {
+ NotifyClients(pOld, pNew);
+ if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which()))
+ { // invalidate cached uno object
+ SetXBookmark(uno::Reference<text::XTextContent>(nullptr));
+ }
+ }
+
+ auto MarkBase::InvalidateFrames() -> void
+ {
+ }
+
+ NavigatorReminder::NavigatorReminder(const SwPaM& rPaM)
+ : MarkBase(rPaM, MarkBase::GenerateNewName("__NavigatorReminder__"))
+ { }
+
+ UnoMark::UnoMark(const SwPaM& aPaM)
+ : MarkBase(aPaM, MarkBase::GenerateNewName("__UnoMark__"))
+ { }
+
+ DdeBookmark::DdeBookmark(const SwPaM& aPaM)
+ : MarkBase(aPaM, MarkBase::GenerateNewName("__DdeLink__"))
+ { }
+
+ void DdeBookmark::SetRefObject(SwServerObject* pObj)
+ {
+ m_aRefObj = pObj;
+ }
+
+ void DdeBookmark::DeregisterFromDoc(SwDoc* const pDoc)
+ {
+ if(m_aRefObj.is())
+ pDoc->getIDocumentLinksAdministration().GetLinkManager().RemoveServer(m_aRefObj.get());
+ }
+
+ DdeBookmark::~DdeBookmark()
+ {
+ if( m_aRefObj.is() )
+ {
+ if(m_aRefObj->HasDataLinks())
+ {
+ ::sfx2::SvLinkSource* p = m_aRefObj.get();
+ p->SendDataChanged();
+ }
+ m_aRefObj->SetNoServer();
+ }
+ }
+
+ Bookmark::Bookmark(const SwPaM& aPaM,
+ const vcl::KeyCode& rCode,
+ const OUString& rName)
+ : DdeBookmark(aPaM)
+ , ::sfx2::Metadatable()
+ , m_aCode(rCode)
+ , m_bHidden(false)
+ {
+ m_aName = rName;
+ }
+
+ void Bookmark::InitDoc(SwDoc* const io_pDoc,
+ sw::mark::InsertMode const, SwPosition const*const)
+ {
+ if (io_pDoc->GetIDocumentUndoRedo().DoesUndo())
+ {
+ io_pDoc->GetIDocumentUndoRedo().AppendUndo(
+ std::make_unique<SwUndoInsBookmark>(*this));
+ }
+ io_pDoc->getIDocumentState().SetModified();
+ InvalidateFrames();
+ }
+
+ void Bookmark::DeregisterFromDoc(SwDoc* const io_pDoc)
+ {
+ DdeBookmark::DeregisterFromDoc(io_pDoc);
+
+ if (io_pDoc->GetIDocumentUndoRedo().DoesUndo())
+ {
+ io_pDoc->GetIDocumentUndoRedo().AppendUndo(
+ std::make_unique<SwUndoDeleteBookmark>(*this));
+ }
+ io_pDoc->getIDocumentState().SetModified();
+ InvalidateFrames();
+ }
+
+ // invalidate text frames in case it's hidden or Formatting Marks enabled
+ auto Bookmark::InvalidateFrames() -> void
+ {
+ InvalidatePosition(GetMarkPos());
+ if (IsExpanded())
+ {
+ InvalidatePosition(GetOtherMarkPos());
+ }
+ }
+
+ void Bookmark::Hide(bool const isHide)
+ {
+ if (isHide != m_bHidden)
+ {
+ m_bHidden = isHide;
+ InvalidateFrames();
+ }
+ }
+
+ void Bookmark::SetHideCondition(OUString const& rHideCondition)
+ {
+ if (m_sHideCondition != rHideCondition)
+ {
+ m_sHideCondition = rHideCondition;
+ InvalidateFrames();
+ }
+ }
+
+ ::sfx2::IXmlIdRegistry& Bookmark::GetRegistry()
+ {
+ SwDoc *const pDoc( GetMarkPos().GetDoc() );
+ assert(pDoc);
+ return pDoc->GetXmlIdRegistry();
+ }
+
+ bool Bookmark::IsInClipboard() const
+ {
+ SwDoc *const pDoc( GetMarkPos().GetDoc() );
+ assert(pDoc);
+ return pDoc->IsClipBoard();
+ }
+
+ bool Bookmark::IsInUndo() const
+ {
+ return false;
+ }
+
+ bool Bookmark::IsInContent() const
+ {
+ SwDoc *const pDoc( GetMarkPos().GetDoc() );
+ assert(pDoc);
+ return !pDoc->IsInHeaderFooter( GetMarkPos().nNode );
+ }
+
+ uno::Reference< rdf::XMetadatable > Bookmark::MakeUnoObject()
+ {
+ SwDoc *const pDoc( GetMarkPos().GetDoc() );
+ assert(pDoc);
+ const uno::Reference< rdf::XMetadatable> xMeta(
+ SwXBookmark::CreateXBookmark(*pDoc, this), uno::UNO_QUERY);
+ return xMeta;
+ }
+
+ Fieldmark::Fieldmark(const SwPaM& rPaM)
+ : MarkBase(rPaM, MarkBase::GenerateNewName("__Fieldmark__"))
+ {
+ if(!IsExpanded())
+ SetOtherMarkPos(GetMarkPos());
+ }
+
+ void Fieldmark::SetMarkStartPos( const SwPosition& rNewStartPos )
+ {
+ if ( GetMarkPos( ) <= GetOtherMarkPos( ) )
+ return SetMarkPos( rNewStartPos );
+ else
+ return SetOtherMarkPos( rNewStartPos );
+ }
+
+ void Fieldmark::SetMarkEndPos( const SwPosition& rNewEndPos )
+ {
+ if ( GetMarkPos( ) <= GetOtherMarkPos( ) )
+ return SetOtherMarkPos( rNewEndPos );
+ else
+ return SetMarkPos( rNewEndPos );
+ }
+
+ OUString Fieldmark::ToString( ) const
+ {
+ return "Fieldmark: ( Name, Type, [ Nd1, Id1 ], [ Nd2, Id2 ] ): ( " + m_aName + ", "
+ + m_aFieldname + ", [ " + OUString::number( GetMarkPos().nNode.GetIndex( ) )
+ + ", " + OUString::number( GetMarkPos( ).nContent.GetIndex( ) ) + " ], ["
+ + OUString::number( GetOtherMarkPos().nNode.GetIndex( ) ) + ", "
+ + OUString::number( GetOtherMarkPos( ).nContent.GetIndex( ) ) + " ] ) ";
+ }
+
+ void Fieldmark::Invalidate( )
+ {
+ // TODO: Does exist a better solution to trigger a format of the
+ // fieldmark portion? If yes, please use it.
+ SwPaM aPaM( GetMarkPos(), GetOtherMarkPos() );
+ aPaM.InvalidatePaM();
+ }
+
+ void Fieldmark::dumpAsXml(xmlTextWriterPtr pWriter) const
+ {
+ xmlTextWriterStartElement(pWriter, BAD_CAST("Fieldmark"));
+ xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldname"), BAD_CAST(m_aFieldname.toUtf8().getStr()));
+ xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldHelptext"), BAD_CAST(m_aFieldHelptext.toUtf8().getStr()));
+ MarkBase::dumpAsXml(pWriter);
+ xmlTextWriterStartElement(pWriter, BAD_CAST("parameters"));
+ for (auto& rParam : m_vParams)
+ {
+ xmlTextWriterStartElement(pWriter, BAD_CAST("parameter"));
+ xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(rParam.first.toUtf8().getStr()));
+ xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(comphelper::anyToString(rParam.second).toUtf8().getStr()));
+ xmlTextWriterEndElement(pWriter);
+ }
+ xmlTextWriterEndElement(pWriter);
+ xmlTextWriterEndElement(pWriter);
+ }
+
+ TextFieldmark::TextFieldmark(const SwPaM& rPaM, const OUString& rName)
+ : Fieldmark(rPaM)
+ {
+ if ( !rName.isEmpty() )
+ m_aName = rName;
+ }
+
+ void TextFieldmark::InitDoc(SwDoc* const io_pDoc,
+ sw::mark::InsertMode const eMode, SwPosition const*const pSepPos)
+ {
+ if (eMode == sw::mark::InsertMode::New)
+ {
+ lcl_SetFieldMarks(this, io_pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos);
+ // no need to invalidate text frames here, the insertion of the
+ // CH_TXT_ATR already invalidates
+ }
+ else
+ {
+ lcl_AssertFieldMarksSet(this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
+ }
+ }
+
+ void TextFieldmark::ReleaseDoc(SwDoc* const pDoc)
+ {
+ IDocumentUndoRedo & rIDUR(pDoc->GetIDocumentUndoRedo());
+ if (rIDUR.DoesUndo())
+ {
+ rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this));
+ }
+ ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
+ lcl_RemoveFieldMarks(this, pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
+ }
+
+ NonTextFieldmark::NonTextFieldmark(const SwPaM& rPaM)
+ : Fieldmark(rPaM)
+ { }
+
+ void NonTextFieldmark::InitDoc(SwDoc* const io_pDoc,
+ sw::mark::InsertMode const eMode, SwPosition const*const pSepPos)
+ {
+ assert(pSepPos == nullptr);
+ if (eMode == sw::mark::InsertMode::New)
+ {
+ lcl_SetFieldMarks(this, io_pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT, pSepPos);
+
+ // For some reason the end mark is moved from 1 by the Insert:
+ // we don't want this for checkboxes
+ SwPosition aNewEndPos = GetMarkEnd();
+ aNewEndPos.nContent--;
+ SetMarkEndPos( aNewEndPos );
+ }
+ else
+ {
+ lcl_AssertFieldMarksSet(this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT);
+ }
+ }
+
+ void NonTextFieldmark::ReleaseDoc(SwDoc* const pDoc)
+ {
+ IDocumentUndoRedo & rIDUR(pDoc->GetIDocumentUndoRedo());
+ if (rIDUR.DoesUndo())
+ {
+ rIDUR.AppendUndo(std::make_unique<SwUndoDelNoTextFieldmark>(*this));
+ }
+ ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
+ lcl_RemoveFieldMarks(this, pDoc,
+ CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT);
+ }
+
+
+ CheckboxFieldmark::CheckboxFieldmark(const SwPaM& rPaM)
+ : NonTextFieldmark(rPaM)
+ { }
+
+ void CheckboxFieldmark::SetChecked(bool checked)
+ {
+ if ( IsChecked() != checked )
+ {
+ (*GetParameters())[OUString(ODF_FORMCHECKBOX_RESULT)] <<= checked;
+ // mark document as modified
+ SwDoc *const pDoc( GetMarkPos().GetDoc() );
+ if ( pDoc )
+ pDoc->getIDocumentState().SetModified();
+ }
+ }
+
+ bool CheckboxFieldmark::IsChecked() const
+ {
+ bool bResult = false;
+ parameter_map_t::const_iterator pResult = GetParameters()->find(OUString(ODF_FORMCHECKBOX_RESULT));
+ if(pResult != GetParameters()->end())
+ pResult->second >>= bResult;
+ return bResult;
+ }
+
+ FieldmarkWithDropDownButton::FieldmarkWithDropDownButton(const SwPaM& rPaM)
+ : NonTextFieldmark(rPaM)
+ , m_pButton(nullptr)
+ {
+ }
+
+ FieldmarkWithDropDownButton::~FieldmarkWithDropDownButton()
+ {
+ m_pButton.disposeAndClear();
+ }
+
+ void FieldmarkWithDropDownButton::HideButton()
+ {
+ if(m_pButton)
+ m_pButton->Show(false);
+ }
+
+ void FieldmarkWithDropDownButton::RemoveButton()
+ {
+ if(m_pButton)
+ m_pButton.disposeAndClear();
+ }
+
+ DropDownFieldmark::DropDownFieldmark(const SwPaM& rPaM)
+ : FieldmarkWithDropDownButton(rPaM)
+ {
+ }
+
+ DropDownFieldmark::~DropDownFieldmark()
+ {
+ SendLOKMessage("hide");
+ }
+
+ void DropDownFieldmark::ShowButton(SwEditWin* pEditWin)
+ {
+ if(pEditWin)
+ {
+ if(!m_pButton)
+ m_pButton = VclPtr<DropDownFormFieldButton>::Create(pEditWin, *this);
+ m_pButton->CalcPosAndSize(m_aPortionPaintArea);
+ m_pButton->Show();
+ SendLOKMessage("show");
+ }
+ }
+
+ void DropDownFieldmark::HideButton()
+ {
+ SendLOKMessage("hide");
+ FieldmarkWithDropDownButton::HideButton();
+ }
+
+ void DropDownFieldmark::RemoveButton()
+ {
+ SendLOKMessage("hide");
+ FieldmarkWithDropDownButton::RemoveButton();
+ }
+
+ void DropDownFieldmark::SetPortionPaintArea(const SwRect& rPortionPaintArea)
+ {
+ m_aPortionPaintArea = rPortionPaintArea;
+ if(m_pButton)
+ {
+ m_pButton->Show();
+ m_pButton->CalcPosAndSize(m_aPortionPaintArea);
+ SendLOKMessage("show");
+ }
+ }
+
+ void DropDownFieldmark::SendLOKMessage(const OString& sAction)
+ {
+ const SfxViewShell* pViewShell = SfxViewShell::Current();
+ if (pViewShell && pViewShell->isLOKMobilePhone())
+ {
+ return;
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if (!m_pButton)
+ return;
+
+ SwEditWin* pEditWin = dynamic_cast<SwEditWin*>(m_pButton->GetParent());
+ if (!pEditWin)
+ return;
+
+ OStringBuffer sPayload;
+ if (sAction == "show")
+ {
+ if(m_aPortionPaintArea.IsEmpty())
+ return;
+
+ sPayload = OStringLiteral("{\"action\": \"show\","
+ " \"type\": \"drop-down\", \"textArea\": \"") +
+ m_aPortionPaintArea.SVRect().toString() + "\",";
+ // Add field params to the message
+ sPayload.append(" \"params\": { \"items\": [");
+
+ // List items
+ auto pParameters = this->GetParameters();
+ auto pListEntriesIter = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
+ css::uno::Sequence<OUString> vListEntries;
+ if (pListEntriesIter != pParameters->end())
+ {
+ pListEntriesIter->second >>= vListEntries;
+ for (const OUString& sItem : std::as_const(vListEntries))
+ sPayload.append("\"" + OUStringToOString(sItem, RTL_TEXTENCODING_UTF8) + "\", ");
+ sPayload.setLength(sPayload.getLength() - 2);
+ }
+ sPayload.append("], ");
+
+ // Selected item
+ OUString sResultKey = ODF_FORMDROPDOWN_RESULT;
+ auto pSelectedItemIter = pParameters->find(sResultKey);
+ sal_Int32 nSelection = -1;
+ if (pSelectedItemIter != pParameters->end())
+ {
+ pSelectedItemIter->second >>= nSelection;
+ }
+ sPayload.append("\"selected\": \"" + OString::number(nSelection) + "\", ");
+
+ // Placeholder text
+ sPayload.append("\"placeholderText\": \"" + OUStringToOString(SwResId(STR_DROP_DOWN_EMPTY_LIST), RTL_TEXTENCODING_UTF8) + "\"}}");
+ }
+ else
+ {
+ sPayload = "{\"action\": \"hide\", \"type\": \"drop-down\"}";
+ }
+ if (sPayload.toString() != m_sLastSentLOKMsg) {
+ m_sLastSentLOKMsg = sPayload.toString();
+ pEditWin->GetView().GetWrtShell().GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, m_sLastSentLOKMsg.getStr());
+ }
+ }
+ }
+
+ DateFieldmark::DateFieldmark(const SwPaM& rPaM)
+ : FieldmarkWithDropDownButton(rPaM)
+ , m_pNumberFormatter(nullptr)
+ , m_pDocumentContentOperationsManager(nullptr)
+ {
+ }
+
+ DateFieldmark::~DateFieldmark()
+ {
+ }
+
+ void DateFieldmark::InitDoc(SwDoc* const io_pDoc,
+ sw::mark::InsertMode eMode, SwPosition const*const pSepPos)
+ {
+ m_pNumberFormatter = io_pDoc->GetNumberFormatter();
+ m_pDocumentContentOperationsManager = &io_pDoc->GetDocumentContentOperationsManager();
+ if (eMode == sw::mark::InsertMode::New)
+ {
+ lcl_SetFieldMarks(this, io_pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos);
+ }
+ else
+ {
+ lcl_AssertFieldMarksSet(this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
+ }
+ }
+
+ void DateFieldmark::ReleaseDoc(SwDoc* const pDoc)
+ {
+ IDocumentUndoRedo & rIDUR(pDoc->GetIDocumentUndoRedo());
+ if (rIDUR.DoesUndo())
+ {
+ // TODO does this need a 3rd Undo class?
+ rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this));
+ }
+ ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
+ lcl_RemoveFieldMarks(this, pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
+ }
+
+ void DateFieldmark::ShowButton(SwEditWin* pEditWin)
+ {
+ if(pEditWin)
+ {
+ if(!m_pButton)
+ m_pButton = VclPtr<DateFormFieldButton>::Create(pEditWin, *this, m_pNumberFormatter);
+ SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight());
+ m_pButton->CalcPosAndSize(aPaintArea);
+ m_pButton->Show();
+ }
+ }
+
+ void DateFieldmark::SetPortionPaintAreaStart(const SwRect& rPortionPaintArea)
+ {
+ if (rPortionPaintArea.IsEmpty())
+ return;
+
+ m_aPaintAreaStart = rPortionPaintArea;
+ InvalidateCurrentDateParam();
+ }
+
+ void DateFieldmark::SetPortionPaintAreaEnd(const SwRect& rPortionPaintArea)
+ {
+ if (rPortionPaintArea.IsEmpty())
+ return;
+
+ if(m_aPaintAreaEnd == rPortionPaintArea &&
+ m_pButton && m_pButton->IsVisible())
+ return;
+
+ m_aPaintAreaEnd = rPortionPaintArea;
+ if(m_pButton)
+ {
+ m_pButton->Show();
+ SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight());
+ m_pButton->CalcPosAndSize(aPaintArea);
+ m_pButton->Invalidate();
+ }
+ InvalidateCurrentDateParam();
+ }
+
+ OUString DateFieldmark::GetContent() const
+ {
+ const SwTextNode* const pTextNode = GetMarkEnd().nNode.GetNode().GetTextNode();
+ SwPosition const sepPos(sw::mark::FindFieldSep(*this));
+ const sal_Int32 nStart(sepPos.nContent.GetIndex());
+ const sal_Int32 nEnd (GetMarkEnd().nContent.GetIndex());
+
+ OUString sContent;
+ if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() &&
+ nEnd > nStart + 2)
+ sContent = pTextNode->GetText().copy(nStart + 1, nEnd - nStart - 2);
+ return sContent;
+ }
+
+ void DateFieldmark::ReplaceContent(const OUString& sNewContent)
+ {
+ if(!m_pDocumentContentOperationsManager)
+ return;
+
+ const SwTextNode* const pTextNode = GetMarkEnd().nNode.GetNode().GetTextNode();
+ SwPosition const sepPos(sw::mark::FindFieldSep(*this));
+ const sal_Int32 nStart(sepPos.nContent.GetIndex());
+ const sal_Int32 nEnd (GetMarkEnd().nContent.GetIndex());
+
+ if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() &&
+ nEnd > nStart + 2)
+ {
+ SwPaM aFieldPam(GetMarkStart().nNode, nStart + 1,
+ GetMarkStart().nNode, nEnd - 1);
+ m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false);
+ }
+ else
+ {
+ SwPaM aFieldStartPam(GetMarkStart().nNode, nStart + 1);
+ m_pDocumentContentOperationsManager->InsertString(aFieldStartPam, sNewContent);
+ }
+
+ }
+
+ std::pair<bool, double> DateFieldmark::GetCurrentDate() const
+ {
+ // Check current date param first
+ std::pair<bool, double> aResult = ParseCurrentDateParam();
+ if(aResult.first)
+ return aResult;
+
+ const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ bool bFoundValidDate = false;
+ double dCurrentDate = 0;
+ OUString sDateFormat;
+ auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sDateFormat;
+ }
+
+ OUString sLang;
+ pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sLang;
+ }
+
+ // Get current content of the field
+ OUString sContent = GetContent();
+
+ sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType());
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ m_pNumberFormatter->PutEntry(sDateFormat,
+ nCheckPos,
+ nType,
+ nFormat,
+ LanguageTag(sLang).getLanguageType());
+ }
+
+ if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sContent, nFormat, dCurrentDate);
+ }
+ return std::pair<bool, double>(bFoundValidDate, dCurrentDate);
+ }
+
+ void DateFieldmark::SetCurrentDate(double fDate)
+ {
+ // Replace current content with the selected date
+ ReplaceContent(GetDateInCurrentDateFormat(fDate));
+
+ // Also save the current date in a standard format
+ sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= GetDateInStandardDateFormat(fDate);
+ }
+
+ OUString DateFieldmark::GetDateInStandardDateFormat(double fDate) const
+ {
+ OUString sCurrentDate;
+ sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE);
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT;
+ m_pNumberFormatter->PutEntry(sFormat,
+ nCheckPos,
+ nType,
+ nFormat,
+ ODF_FORMDATE_CURRENTDATE_LANGUAGE);
+ }
+
+ if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ Color* pCol = nullptr;
+ m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentDate, &pCol, false);
+ }
+ return sCurrentDate;
+ }
+
+ std::pair<bool, double> DateFieldmark::ParseCurrentDateParam() const
+ {
+ bool bFoundValidDate = false;
+ double dCurrentDate = 0;
+
+ const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ auto pResult = pParameters->find(ODF_FORMDATE_CURRENTDATE);
+ OUString sCurrentDate;
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sCurrentDate;
+ }
+ if(!sCurrentDate.isEmpty())
+ {
+ sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE);
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT;
+ m_pNumberFormatter->PutEntry(sFormat,
+ nCheckPos,
+ nType,
+ nFormat,
+ ODF_FORMDATE_CURRENTDATE_LANGUAGE);
+ }
+
+ if(nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sCurrentDate, nFormat, dCurrentDate);
+ }
+ }
+ return std::pair<bool, double>(bFoundValidDate, dCurrentDate);
+ }
+
+
+ OUString DateFieldmark::GetDateInCurrentDateFormat(double fDate) const
+ {
+ // Get current date format and language
+ OUString sDateFormat;
+ const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sDateFormat;
+ }
+
+ OUString sLang;
+ pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sLang;
+ }
+
+ // Fill the content with the specified format
+ OUString sCurrentContent;
+ sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType());
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ OUString sFormat = sDateFormat;
+ m_pNumberFormatter->PutEntry(sFormat,
+ nCheckPos,
+ nType,
+ nFormat,
+ LanguageTag(sLang).getLanguageType());
+ }
+
+ if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ Color* pCol = nullptr;
+ m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentContent, &pCol, false);
+ }
+ return sCurrentContent;
+ }
+
+ void DateFieldmark::InvalidateCurrentDateParam()
+ {
+ std::pair<bool, double> aResult = ParseCurrentDateParam();
+ if(!aResult.first)
+ return;
+
+ // Current date became invalid
+ if(GetDateInCurrentDateFormat(aResult.second) != GetContent())
+ {
+ sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= OUString();
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */