/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::sw::mark; using namespace ::com::sun::star; using namespace ::com::sun::star::uno; namespace sw::mark { SwPosition FindFieldSep(Fieldmark const& rMark) { auto [/*const SwPosition&*/ rStartPos, rEndPos] = rMark.GetMarkStartEnd(); SwNodes const& rNodes(rStartPos.GetNodes()); SwNodeOffset const nStartNode(rStartPos.GetNodeIndex()); SwNodeOffset const nEndNode(rEndPos.GetNodeIndex()); int nFields(0); std::optional ret; for (SwNodeOffset n = nEndNode; nStartNode <= n; --n) { SwNode *const pNode(rNodes[n]); if (pNode->IsTextNode()) { SwTextNode & rTextNode(*pNode->GetTextNode()); sal_Int32 const nStart(n == nStartNode ? rStartPos.GetContentIndex() + 1 : 0); sal_Int32 const nEnd(n == nEndNode // subtract 1 to ignore the end char ? rEndPos.GetContentIndex() - 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.emplace(rTextNode, i - 1); #ifndef DBG_UTIL return *ret; #endif } break; } } } else if (pNode->IsEndNode() && !pNode->StartOfSectionNode()->IsSectionNode()) { assert(nStartNode <= pNode->StartOfSectionIndex()); // fieldmark cannot overlap node section, unless it's a section n = pNode->StartOfSectionIndex(); } else { assert(pNode->IsNoTextNode() || pNode->IsSectionNode() || (pNode->IsEndNode() && pNode->StartOfSectionNode()->IsSectionNode())); } } 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.GetNode().GetTextNode(); if(pTextNode == nullptr && rPos.GetContentIndex() > 0) { SAL_INFO( "sw.core", "illegal position: " << rPos.GetContentIndex() << " without proper TextNode"); rPos.nContent.Assign(nullptr, 0); } else if(pTextNode != nullptr && rPos.GetContentIndex() > pTextNode->Len()) { SAL_INFO( "sw.core", "illegal position: " << rPos.GetContentIndex() << " is beyond " << pTextNode->Len()); rPos.nContent.Assign(pTextNode, pTextNode->Len()); } } void lcl_AssertFieldMarksSet(const Fieldmark& rField, const sal_Unicode aStartMark, const sal_Unicode aEndMark) { if (aEndMark != CH_TXT_ATR_FORMELEMENT) { SwPosition const& rStart(rField.GetMarkStart()); assert(rStart.GetNode().GetTextNode()->GetText()[rStart.GetContentIndex()] == aStartMark); (void) rStart; (void) aStartMark; SwPosition const sepPos(sw::mark::FindFieldSep(rField)); assert(sepPos.GetNode().GetTextNode()->GetText()[sepPos.GetContentIndex()] == CH_TXT_ATR_FIELDSEP); (void) sepPos; } else { // must be m_pPos1 < m_pPos2 because of asymmetric SplitNode update assert(rField.GetMarkPos().GetContentIndex() + 1 == rField.GetOtherMarkPos().GetContentIndex()); } SwPosition const& rEnd(rField.GetMarkEnd()); assert(rEnd.GetNode().GetTextNode()->GetText()[rEnd.GetContentIndex() - 1] == aEndMark); (void) rEnd; } void lcl_SetFieldMarks(Fieldmark& rField, SwDoc& io_rDoc, const sal_Unicode aStartMark, const sal_Unicode aEndMark, SwPosition const*const pSepPos) { io_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr); OUString startChar(aStartMark); if (aEndMark != CH_TXT_ATR_FORMELEMENT && rField.GetMarkStart() == rField.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 = rField.GetMarkStart(); if (aEndMark != CH_TXT_ATR_FORMELEMENT) { SwPaM aStartPaM(start); io_rDoc.getIDocumentContentOperations().InsertString(aStartPaM, startChar); start.AdjustContent( -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 rField.SetMarkStartPos( start ); SwPosition& rEnd = rField.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_rDoc.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.AdjustContent(1); } } assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd)); } else { assert(pSepPos == nullptr); } SwPosition& rEnd = rField.GetMarkEnd(); if (aEndMark && startChar.getLength() == 1) { SwPaM aEndPaM(rEnd); io_rDoc.getIDocumentContentOperations().InsertString(aEndPaM, OUString(aEndMark)); if (aEndMark != CH_TXT_ATR_FORMELEMENT) { rEnd.AdjustContent(1); // InsertString didn't move non-empty mark } else { // InsertString moved the mark's end, not its start assert(rField.GetMarkPos().GetContentIndex() + 1 == rField.GetOtherMarkPos().GetContentIndex()); } } lcl_AssertFieldMarksSet(rField, aStartMark, aEndMark); io_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr); } void lcl_RemoveFieldMarks(const Fieldmark& rField, SwDoc& io_rDoc, const sal_Unicode aStartMark, const sal_Unicode aEndMark) { io_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr); const SwPosition& rStart = rField.GetMarkStart(); SwTextNode const*const pStartTextNode = rStart.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(rField)); io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(rStart, aStartMark); io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(sepPos, CH_TXT_ATR_FIELDSEP); } const SwPosition& rEnd = rField.GetMarkEnd(); SwTextNode *const pEndTextNode = rEnd.GetNode().GetTextNode(); assert(pEndTextNode); const sal_Int32 nEndPos = (rEnd == rStart) ? rEnd.GetContentIndex() : rEnd.GetContentIndex() - 1; assert(pEndTextNode->GetText()[nEndPos] == aEndMark); SwPosition const aEnd(*pEndTextNode, nEndPos); io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(aEnd, aEndMark); io_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr); } auto InvalidatePosition(SwPosition const& rPos) -> void { SwUpdateAttr const aHint(rPos.GetContentIndex(), rPos.GetContentIndex(), 0); rPos.GetNode().GetTextNode()->CallSwClientNotify(sw::LegacyModifyHint(&aHint, &aHint)); } } namespace sw::mark { MarkBase::MarkBase(const SwPaM& aPaM, OUString aName) : m_oPos1(*aPaM.GetPoint()) , m_aName(std::move(aName)) { m_oPos1->SetOwner(this); lcl_FixPosition(*m_oPos1); if (aPaM.HasMark() && (*aPaM.GetMark() != *aPaM.GetPoint())) { MarkBase::SetOtherMarkPos(*(aPaM.GetMark())); lcl_FixPosition(*m_oPos2); } } void MarkBase::SetXBookmark(rtl::Reference const& xBkmk) { m_wXBookmark = xBkmk.get(); } // 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 { auto [/*const SwPosition&*/ rStartPos, rEndPos] = GetMarkStartEnd(); return rStartPos <= rPos && rPos < rEndPos; } void MarkBase::SetMarkPos(const SwPosition& rNewPos) { m_oPos1.emplace(rNewPos); m_oPos1->SetOwner(this); } void MarkBase::SetOtherMarkPos(const SwPosition& rNewPos) { m_oPos2.emplace(rNewPos); m_oPos2->SetOwner(this); } OUString MarkBase::ToString( ) const { return "Mark: ( Name, [ Node1, Index1 ] ): ( " + m_aName + ", [ " + OUString::number( sal_Int32(GetMarkPos().GetNodeIndex()) ) + ", " + OUString::number( GetMarkPos().GetContentIndex( ) ) + " ] )"; } void MarkBase::dumpAsXml(xmlTextWriterPtr pWriter) const { (void)xmlTextWriterStartElement(pWriter, BAD_CAST("MarkBase")); (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(m_aName.toUtf8().getStr())); (void)xmlTextWriterStartElement(pWriter, BAD_CAST("markPos")); GetMarkPos().dumpAsXml(pWriter); (void)xmlTextWriterEndElement(pWriter); if (IsExpanded()) { (void)xmlTextWriterStartElement(pWriter, BAD_CAST("otherMarkPos")); GetOtherMarkPos().dumpAsXml(pWriter); (void)xmlTextWriterEndElement(pWriter); } (void)xmlTextWriterEndElement(pWriter); } MarkBase::~MarkBase() { } OUString MarkBase::GenerateNewName(std::u16string_view 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::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::SwClientNotify(const SwModify&, const SfxHint& rHint) { CallSwClientNotify(rHint); if (rHint.GetId() != SfxHintId::SwLegacyModify) return; auto pLegacy = static_cast(&rHint); if(RES_REMOVE_UNO_OBJECT == pLegacy->GetWhich()) { // invalidate cached uno object SetXBookmark(nullptr); } } auto MarkBase::InvalidateFrames() -> void { } NavigatorReminder::NavigatorReminder(const SwPaM& rPaM) : MarkBase(rPaM, MarkBase::GenerateNewName(u"__NavigatorReminder__")) { } UnoMark::UnoMark(const SwPaM& aPaM) : MarkBase(aPaM, MarkBase::GenerateNewName(u"__UnoMark__")) { } DdeBookmark::DdeBookmark(const SwPaM& aPaM) : MarkBase(aPaM, MarkBase::GenerateNewName(u"__DdeLink__")) { } void DdeBookmark::SetRefObject(SwServerObject* pObj) { m_aRefObj = pObj; } void DdeBookmark::DeregisterFromDoc(SwDoc& rDoc) { if(m_aRefObj.is()) rDoc.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) , m_aCode(rCode) , m_bHidden(false) { m_aName = rName; } void Bookmark::sendLOKDeleteCallback() { if (!comphelper::LibreOfficeKit::isActive() || GetMarkPos().GetDoc().IsClipBoard()) return; SfxViewShell* pViewShell = SfxViewShell::Current(); if (!pViewShell) return; OUString fieldCommand = GetName(); tools::JsonWriter aJson; aJson.put("commandName", ".uno:DeleteBookmark"); aJson.put("success", true); { auto result = aJson.startNode("result"); aJson.put("DeleteBookmark", fieldCommand); } pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString()); } void Bookmark::InitDoc(SwDoc& io_rDoc, sw::mark::InsertMode const, SwPosition const*const) { if (io_rDoc.GetIDocumentUndoRedo().DoesUndo()) { io_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique(*this)); } io_rDoc.getIDocumentState().SetModified(); InvalidateFrames(); } void Bookmark::DeregisterFromDoc(SwDoc& io_rDoc) { DdeBookmark::DeregisterFromDoc(io_rDoc); if (io_rDoc.GetIDocumentUndoRedo().DoesUndo()) { io_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique(*this)); } io_rDoc.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; // don't eval condition here yet - probably only needed for // UI editing condition and that doesn't exist yet } } ::sfx2::IXmlIdRegistry& Bookmark::GetRegistry() { SwDoc& rDoc( GetMarkPos().GetDoc() ); return rDoc.GetXmlIdRegistry(); } bool Bookmark::IsInClipboard() const { SwDoc& rDoc( GetMarkPos().GetDoc() ); return rDoc.IsClipBoard(); } bool Bookmark::IsInUndo() const { return false; } bool Bookmark::IsInContent() const { SwDoc& rDoc( GetMarkPos().GetDoc() ); return !rDoc.IsInHeaderFooter( GetMarkPos().GetNode() ); } uno::Reference< rdf::XMetadatable > Bookmark::MakeUnoObject() { SwDoc& rDoc( GetMarkPos().GetDoc() ); const uno::Reference< rdf::XMetadatable> xMeta( SwXBookmark::CreateXBookmark(rDoc, this) ); return xMeta; } Fieldmark::Fieldmark(const SwPaM& rPaM) : MarkBase(rPaM, MarkBase::GenerateNewName(u"__Fieldmark__")) { if(!IsExpanded()) SetOtherMarkPos(GetMarkPos()); } void Fieldmark::SetMarkStartPos( const SwPosition& rNewStartPos ) { if ( GetMarkPos( ) <= GetOtherMarkPos( ) ) return SetMarkPos( rNewStartPos ); else return SetOtherMarkPos( rNewStartPos ); } OUString Fieldmark::ToString( ) const { return "Fieldmark: ( Name, Type, [ Nd1, Id1 ], [ Nd2, Id2 ] ): ( " + m_aName + ", " + m_aFieldname + ", [ " + OUString::number( sal_Int32(GetMarkPos().GetNodeIndex( )) ) + ", " + OUString::number( GetMarkPos( ).GetContentIndex( ) ) + " ], [" + OUString::number( sal_Int32(GetOtherMarkPos().GetNodeIndex( )) ) + ", " + OUString::number( GetOtherMarkPos( ).GetContentIndex( ) ) + " ] ) "; } 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 { (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Fieldmark")); (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldname"), BAD_CAST(m_aFieldname.toUtf8().getStr())); (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldHelptext"), BAD_CAST(m_aFieldHelptext.toUtf8().getStr())); MarkBase::dumpAsXml(pWriter); (void)xmlTextWriterStartElement(pWriter, BAD_CAST("parameters")); for (auto& rParam : m_vParams) { (void)xmlTextWriterStartElement(pWriter, BAD_CAST("parameter")); (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(rParam.first.toUtf8().getStr())); (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(comphelper::anyToString(rParam.second).toUtf8().getStr())); (void)xmlTextWriterEndElement(pWriter); } (void)xmlTextWriterEndElement(pWriter); (void)xmlTextWriterEndElement(pWriter); } TextFieldmark::TextFieldmark(const SwPaM& rPaM, const OUString& rName) : Fieldmark(rPaM) , m_pDocumentContentOperationsManager(nullptr) { if ( !rName.isEmpty() ) m_aName = rName; } TextFieldmark::~TextFieldmark() { if (!comphelper::LibreOfficeKit::isActive() || GetMarkPos().GetDoc().IsClipBoard()) return; SfxViewShell* pViewShell = SfxViewShell::Current(); if (!pViewShell) return; OUString fieldCommand; (*GetParameters())[ODF_CODE_PARAM] >>= fieldCommand; tools::JsonWriter aJson; aJson.put("commandName", ".uno:DeleteTextFormField"); aJson.put("success", true); { auto result = aJson.startNode("result"); aJson.put("DeleteTextFormField", fieldCommand); } pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString()); } void TextFieldmark::InitDoc(SwDoc& io_rDoc, sw::mark::InsertMode const eMode, SwPosition const*const pSepPos) { m_pDocumentContentOperationsManager = &io_rDoc.GetDocumentContentOperationsManager(); if (eMode == sw::mark::InsertMode::New) { lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos); } else { lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); } } void TextFieldmark::ReleaseDoc(SwDoc& rDoc) { IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo()); if (rIDUR.DoesUndo()) { rIDUR.AppendUndo(std::make_unique(*this)); } ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); // notify layouts to unhide - for the entire fieldmark, as in InitDoc() SwPaM const tmp(GetMarkPos(), GetOtherMarkPos()); sw::UpdateFramesForRemoveDeleteRedline(rDoc, tmp); } OUString TextFieldmark::GetContent() const { const SwTextNode& rTextNode = *GetMarkEnd().GetNode().GetTextNode(); SwPosition const sepPos(sw::mark::FindFieldSep(*this)); const sal_Int32 nStart(sepPos.GetContentIndex()); const sal_Int32 nEnd(GetMarkEnd().GetContentIndex()); OUString sContent; const sal_Int32 nLen = rTextNode.GetText().getLength(); if (nStart + 1 < nLen && nEnd <= nLen && nEnd > nStart + 2) sContent = rTextNode.GetText().copy(nStart + 1, nEnd - nStart - 2); return sContent; } void TextFieldmark::ReplaceContent(const OUString& sNewContent) { if (!m_pDocumentContentOperationsManager) return; SwPosition const sepPos(sw::mark::FindFieldSep(*this)); const sal_Int32 nStart(sepPos.GetContentIndex()); const sal_Int32 nEnd(GetMarkEnd().GetContentIndex()); const sal_Int32 nLen = GetMarkEnd().GetNode().GetTextNode()->GetText().getLength(); if (nStart + 1 < nLen && nEnd <= nLen && nEnd > nStart + 2) { SwPaM aFieldPam(GetMarkStart().GetNode(), nStart + 1, GetMarkStart().GetNode(), nEnd - 1); m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false); } else { SwPaM aFieldStartPam(GetMarkStart().GetNode(), nStart + 1); m_pDocumentContentOperationsManager->InsertString(aFieldStartPam, sNewContent); } Invalidate(); } NonTextFieldmark::NonTextFieldmark(const SwPaM& rPaM) : Fieldmark(rPaM) { } void NonTextFieldmark::InitDoc(SwDoc& io_rDoc, sw::mark::InsertMode const eMode, SwPosition const*const pSepPos) { assert(pSepPos == nullptr); if (eMode == sw::mark::InsertMode::New) { lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT, pSepPos); } else { lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT); } } void NonTextFieldmark::ReleaseDoc(SwDoc& rDoc) { IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo()); if (rIDUR.DoesUndo()) { rIDUR.AppendUndo(std::make_unique(*this)); } ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT); } CheckboxFieldmark::CheckboxFieldmark(const SwPaM& rPaM, const OUString& rName) : NonTextFieldmark(rPaM) { if (!rName.isEmpty()) m_aName = rName; } void CheckboxFieldmark::SetChecked(bool checked) { if ( IsChecked() != checked ) { (*GetParameters())[ODF_FORMCHECKBOX_RESULT] <<= checked; // mark document as modified SwDoc& rDoc( GetMarkPos().GetDoc() ); rDoc.getIDocumentState().SetModified(); } } bool CheckboxFieldmark::IsChecked() const { bool bResult = false; parameter_map_t::const_iterator pResult = GetParameters()->find(ODF_FORMCHECKBOX_RESULT); if(pResult != GetParameters()->end()) pResult->second >>= bResult; return bResult; } OUString CheckboxFieldmark::GetContent() const { return IsChecked() ? "1" : "0"; } void CheckboxFieldmark::ReplaceContent(const OUString& sNewContent) { SetChecked(sNewContent.toBoolean()); Invalidate(); } FieldmarkWithDropDownButton::FieldmarkWithDropDownButton(const SwPaM& rPaM) : NonTextFieldmark(rPaM) , m_pButton(nullptr) { } FieldmarkWithDropDownButton::~FieldmarkWithDropDownButton() { m_pButton.disposeAndClear(); } void FieldmarkWithDropDownButton::RemoveButton() { if(m_pButton) m_pButton.disposeAndClear(); } void FieldmarkWithDropDownButton::LaunchPopup() { if (!m_pButton) return; m_pButton->Invalidate(); m_pButton->LaunchPopup(); } DropDownFieldmark::DropDownFieldmark(const SwPaM& rPaM, const OUString& rName) : FieldmarkWithDropDownButton(rPaM) { if (!rName.isEmpty()) m_aName = rName; } DropDownFieldmark::~DropDownFieldmark() { } void DropDownFieldmark::ShowButton(SwEditWin* pEditWin) { if(pEditWin) { if(!m_pButton) m_pButton = VclPtr::Create(pEditWin, *this); m_pButton->CalcPosAndSize(m_aPortionPaintArea); m_pButton->Show(); } } void DropDownFieldmark::RemoveButton() { FieldmarkWithDropDownButton::RemoveButton(); } /** GetContent * @param pIndex The zero-based index to retrieve * [in] if pIndex is null or negative, return the listbox's chosen result, * else return the indicated entry (or last entry for invalid choice). * [out] the index of the returned result or -1 if error */ OUString DropDownFieldmark::GetContent(sal_Int32* pIndex) const { sal_Int32 nIndex = pIndex ? *pIndex : -1; auto rParameters = *GetParameters(); if (nIndex < 0) rParameters[ODF_FORMDROPDOWN_RESULT] >>= nIndex; uno::Sequence aSeq; rParameters[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq; nIndex = std::min(nIndex, aSeq.getLength() - 1); if (nIndex < 0) { if (pIndex) *pIndex = -1; return OUString(); } if (pIndex) *pIndex = nIndex; return aSeq[nIndex]; } OUString DropDownFieldmark::GetContent() const { return GetContent(nullptr); } /** AddContent : INSERTS a new choice * @param rText: The choice to add to the list choices. * * @param pIndex [optional] * [in] If pIndex is null or invalid, append to the end of the list. * [out] Modified to point to the position of the choice if it already exists. */ void DropDownFieldmark::AddContent(const OUString& rText, sal_Int32* pIndex) { uno::Sequence aSeq; sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters(); (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq; // no duplicates: if it already exists, modify the given index to point to it const sal_Int32 nCurrentTextPos = comphelper::findValue(aSeq, rText); if (nCurrentTextPos != -1) { if (pIndex) *pIndex = nCurrentTextPos; return; } const sal_Int32 nLen = aSeq.getLength(); const sal_Int32 nNewPos = pIndex && *pIndex > -1 ? std::min(*pIndex, nLen) : nLen; // need to shift list result index up if adding new entry before it sal_Int32 nResultIndex = -1; (*pParameters)[ODF_FORMDROPDOWN_RESULT] >>= nResultIndex; if (nNewPos <= nResultIndex) (*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= nResultIndex + 1; auto aList = comphelper::sequenceToContainer>(aSeq); if (nNewPos < nLen) aList.insert(aList.begin() + nNewPos, rText); else { if (pIndex) *pIndex = nLen; aList.push_back(rText); } (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= comphelper::containerToSequence(aList); Invalidate(); } /** * ReplaceContent : changes the list result index or renames the existing choices * @param pText * [in] If pIndex is null, change the list result index to this provided choice * (but do nothing if pText is an invalid choice) * else rename that entry. * * @param pIndex * [in] If pText is null, change the list result index to this provided Index * (or the last position if it is an invalid choice) * else rename this entry (doing nothing for invalid indexes). * [out] If pIndex is invalid, it is modified to use the last position. * * This function allows duplicate entries - which is also allowed in MS Word. */ void DropDownFieldmark::ReplaceContent(const OUString* pText, sal_Int32* pIndex) { if (!pIndex && !pText) return; uno::Sequence aSeq; sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters(); (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq; const sal_Int32 nLen = aSeq.getLength(); if (!pText) { if (*pIndex < 0 || *pIndex >= nLen) *pIndex = nLen - 1; // select pIndex as the new value for the list box (*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= *pIndex; Invalidate(); return; } if (!pIndex) { const sal_Int32 nNewPos = comphelper::findValue(aSeq, *pText); if (nNewPos != -1) { (*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= nNewPos; Invalidate(); } return; } if (*pIndex > -1 && *pIndex < nLen) { auto aList = comphelper::sequenceToContainer>(aSeq); aList[*pIndex] = *pText; (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= comphelper::containerToSequence(aList); Invalidate(); } } void DropDownFieldmark::ReplaceContent(const OUString& rNewContent) { ReplaceContent(&rNewContent, nullptr); } /** * Remove everything if the given index is negative, else remove the given index (if valid). * If deleting the currently selected choice, reset the selection to the first choice. */ void DropDownFieldmark::DelContent(sal_Int32 nDelIndex) { sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters(); uno::Sequence aSeq; if (nDelIndex < 0) { pParameters->erase(ODF_FORMDROPDOWN_RESULT); (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= aSeq; Invalidate(); return; } (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq; if (nDelIndex >= aSeq.getLength()) return; // If deleting the current choice, select the first entry instead // else need to shift list result index down if deleting an entry before it sal_Int32 nResultIndex = -1; (*pParameters)[ODF_FORMDROPDOWN_RESULT] >>= nResultIndex; if (nDelIndex == nResultIndex) nResultIndex = 0; else if (nDelIndex < nResultIndex) --nResultIndex; comphelper::removeElementAt(aSeq, nDelIndex); if (nResultIndex != -1) (*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= nResultIndex; (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= aSeq; Invalidate(); } void DropDownFieldmark::SetPortionPaintArea(const SwRect& rPortionPaintArea) { m_aPortionPaintArea = rPortionPaintArea; if(m_pButton) { m_pButton->Show(); m_pButton->CalcPosAndSize(m_aPortionPaintArea); } } void DropDownFieldmark::SendLOKShowMessage(const SfxViewShell* pViewShell) { if (!comphelper::LibreOfficeKit::isActive()) return; if (!pViewShell || pViewShell->isLOKMobilePhone()) return; if (m_aPortionPaintArea.IsEmpty()) return; OStringBuffer sPayload; sPayload = OString::Concat("{\"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 vListEntries; if (pListEntriesIter != pParameters->end()) { pListEntriesIter->second >>= vListEntries; for (const OUString& sItem : vListEntries) sPayload.append("\"" + OUStringToOString(sItem, RTL_TEXTENCODING_UTF8) + "\", "); sPayload.setLength(sPayload.getLength() - 2); } sPayload.append("], "); // Selected item auto pSelectedItemIter = pParameters->find(ODF_FORMDROPDOWN_RESULT); 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) + "\"}}"); pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, sPayload.toString()); } void DropDownFieldmark::SendLOKHideMessage(const SfxViewShell* pViewShell) { pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, "{\"action\": \"hide\", \"type\": \"drop-down\"}"_ostr); } DateFieldmark::DateFieldmark(const SwPaM& rPaM) : FieldmarkWithDropDownButton(rPaM) , m_pNumberFormatter(nullptr) , m_pDocumentContentOperationsManager(nullptr) { } DateFieldmark::~DateFieldmark() { } void DateFieldmark::InitDoc(SwDoc& io_rDoc, sw::mark::InsertMode eMode, SwPosition const*const pSepPos) { m_pNumberFormatter = io_rDoc.GetNumberFormatter(); m_pDocumentContentOperationsManager = &io_rDoc.GetDocumentContentOperationsManager(); if (eMode == sw::mark::InsertMode::New) { lcl_SetFieldMarks(*this, io_rDoc, 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& rDoc) { IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo()); if (rIDUR.DoesUndo()) { // TODO does this need a 3rd Undo class? rIDUR.AppendUndo(std::make_unique(*this)); } ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); // notify layouts to unhide - for the entire fieldmark, as in InitDoc() SwPaM const tmp(GetMarkPos(), GetOtherMarkPos()); sw::UpdateFramesForRemoveDeleteRedline(rDoc, tmp); } void DateFieldmark::ShowButton(SwEditWin* pEditWin) { if(pEditWin) { if(!m_pButton) m_pButton = VclPtr::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().GetNode().GetTextNode(); SwPosition const sepPos(sw::mark::FindFieldSep(*this)); const sal_Int32 nStart(sepPos.GetContentIndex()); const sal_Int32 nEnd (GetMarkEnd().GetContentIndex()); 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().GetNode().GetTextNode(); SwPosition const sepPos(sw::mark::FindFieldSep(*this)); const sal_Int32 nStart(sepPos.GetContentIndex()); const sal_Int32 nEnd (GetMarkEnd().GetContentIndex()); if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() && nEnd > nStart + 2) { SwPaM aFieldPam(GetMarkStart().GetNode(), nStart + 1, GetMarkStart().GetNode(), nEnd - 1); m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false); } else { SwPaM aFieldStartPam(GetMarkStart().GetNode(), nStart + 1); m_pDocumentContentOperationsManager->InsertString(aFieldStartPam, sNewContent); } } std::pair DateFieldmark::GetCurrentDate() const { // Check current date param first std::pair aResult = ParseCurrentDateParam(); if(aResult.first) return aResult; const sw::mark::Fieldmark::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(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::Fieldmark::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) { const Color* pCol = nullptr; m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentDate, &pCol, false); } return sCurrentDate; } std::pair DateFieldmark::ParseCurrentDateParam() const { bool bFoundValidDate = false; double dCurrentDate = 0; const sw::mark::Fieldmark::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(bFoundValidDate, dCurrentDate); } OUString DateFieldmark::GetDateInCurrentDateFormat(double fDate) const { // Get current date format and language OUString sDateFormat; const sw::mark::Fieldmark::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) { const Color* pCol = nullptr; m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentContent, &pCol, false); } return sCurrentContent; } void DateFieldmark::InvalidateCurrentDateParam() { std::pair aResult = ParseCurrentDateParam(); if(!aResult.first) return; // Current date became invalid if(GetDateInCurrentDateFormat(aResult.second) != GetContent()) { sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters(); (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= OUString(); } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */