diff options
Diffstat (limited to 'sw/source/core/txtnode/attrcontentcontrol.cxx')
-rw-r--r-- | sw/source/core/txtnode/attrcontentcontrol.cxx | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx b/sw/source/core/txtnode/attrcontentcontrol.cxx new file mode 100644 index 000000000..fb2176024 --- /dev/null +++ b/sw/source/core/txtnode/attrcontentcontrol.cxx @@ -0,0 +1,506 @@ +/* -*- 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 <formatcontentcontrol.hxx> + +#include <libxml/xmlwriter.h> + +#include <sal/log.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <svl/numformat.hxx> +#include <vcl/keycod.hxx> + +#include <ndtxt.hxx> +#include <textcontentcontrol.hxx> +#include <doc.hxx> + +using namespace com::sun::star; + +namespace +{ +inline constexpr OUStringLiteral CURRENT_DATE_FORMAT = u"YYYY-MM-DD"; +} + +SwFormatContentControl* SwFormatContentControl::CreatePoolDefault(sal_uInt16 nWhich) +{ + return new SwFormatContentControl(nWhich); +} + +SwFormatContentControl::SwFormatContentControl(sal_uInt16 nWhich) + : SfxPoolItem(nWhich) + , m_pTextAttr(nullptr) +{ +} + +SwFormatContentControl::SwFormatContentControl( + const std::shared_ptr<SwContentControl>& pContentControl, sal_uInt16 nWhich) + : SfxPoolItem(nWhich) + , m_pContentControl(pContentControl) + , m_pTextAttr(nullptr) +{ + if (!pContentControl) + { + SAL_WARN("sw.core", "SwFormatContentControl ctor: no pContentControl?"); + } + // Not calling m_pContentControl->SetFormatContentControl(this) here; only from SetTextAttr. +} + +SwFormatContentControl::~SwFormatContentControl() +{ + if (m_pContentControl && (m_pContentControl->GetFormatContentControl() == this)) + { + NotifyChangeTextNode(nullptr); + m_pContentControl->SetFormatContentControl(nullptr); + } +} + +bool SwFormatContentControl::operator==(const SfxPoolItem& rOther) const +{ + return SfxPoolItem::operator==(rOther) + && m_pContentControl + == static_cast<const SwFormatContentControl&>(rOther).m_pContentControl; +} + +SwFormatContentControl* SwFormatContentControl::Clone(SfxItemPool* /*pPool*/) const +{ + // If this is indeed a copy, then DoCopy will be called later. + if (m_pContentControl) + { + return new SwFormatContentControl(m_pContentControl, Which()); + } + else + { + return new SwFormatContentControl(Which()); + } +} + +void SwFormatContentControl::SetTextAttr(SwTextContentControl* pTextAttr) +{ + if (m_pTextAttr && pTextAttr) + { + SAL_WARN("sw.core", "SwFormatContentControl::SetTextAttr: already has a text attribute"); + } + if (!m_pTextAttr && !pTextAttr) + { + SAL_WARN("sw.core", "SwFormatContentControl::SetTextAttr: no attribute to remove"); + } + m_pTextAttr = pTextAttr; + if (!m_pContentControl) + { + SAL_WARN("sw.core", "inserted SwFormatContentControl has no SwContentControl"); + } + // The SwContentControl should be able to find the current text attribute. + if (m_pContentControl) + { + if (pTextAttr) + { + m_pContentControl->SetFormatContentControl(this); + } + else if (m_pContentControl->GetFormatContentControl() == this) + { + // The text attribute is gone, so de-register from text node. + NotifyChangeTextNode(nullptr); + m_pContentControl->SetFormatContentControl(nullptr); + } + } +} + +void SwFormatContentControl::NotifyChangeTextNode(SwTextNode* pTextNode) +{ + // Not deleting m_pTextAttr here, SwNodes::ChgNode() doesn't do that, either. + if (!m_pContentControl) + { + SAL_WARN("sw.core", "SwFormatContentControl::NotifyChangeTextNode: no content control?"); + } + if (m_pContentControl && (m_pContentControl->GetFormatContentControl() == this)) + { + // Not calling Modify, that would call SwXContentControl::SwClientNotify. + m_pContentControl->NotifyChangeTextNode(pTextNode); + } +} + +// This SwFormatContentControl has been cloned and points at the same SwContentControl as the +// source: this function copies the SwContentControl. +void SwFormatContentControl::DoCopy(SwTextNode& rTargetTextNode) +{ + if (!m_pContentControl) + { + SAL_WARN("sw.core", "SwFormatContentControl::DoCopy: called for SwFormatContentControl " + "with no SwContentControl."); + return; + } + + m_pContentControl = std::make_shared<SwContentControl>(this); + m_pContentControl->NotifyChangeTextNode(&rTargetTextNode); +} + +void SwFormatContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatContentControl")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_pTextAttr"), "%p", m_pTextAttr); + SfxPoolItem::dumpAsXml(pWriter); + + if (m_pContentControl) + { + m_pContentControl->dumpAsXml(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +SwContentControl::SwContentControl(SwFormatContentControl* pFormat) + : sw::BroadcastingModify() + , m_pFormat(pFormat) + , m_pTextNode(nullptr) +{ +} + +SwContentControl::~SwContentControl() {} + +SwTextContentControl* SwContentControl::GetTextAttr() const +{ + return m_pFormat ? m_pFormat->GetTextAttr() : nullptr; +} + +void SwContentControl::NotifyChangeTextNode(SwTextNode* pTextNode) +{ + m_pTextNode = pTextNode; + if (m_pTextNode && (GetRegisteredIn() != m_pTextNode)) + { + m_pTextNode->Add(this); + } + else if (!m_pTextNode) + { + EndListeningAll(); + } + if (!pTextNode) + { + // If the text node is gone, then invalidate clients (e.g. UNO object). + GetNotifier().Broadcast(SfxHint(SfxHintId::Deinitializing)); + } +} + +void SwContentControl::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + CallSwClientNotify(rHint); + GetNotifier().Broadcast(SfxHint(SfxHintId::DataChanged)); + + if (pLegacy->GetWhich() == RES_REMOVE_UNO_OBJECT) + { + // Invalidate cached uno object. + SetXContentControl(uno::Reference<text::XTextContent>()); + GetNotifier().Broadcast(SfxHint(SfxHintId::Deinitializing)); + } +} + +OUString SwContentControl::GetDateString() const +{ + SwDoc& rDoc = m_pTextNode->GetDoc(); + SvNumberFormatter* pNumberFormatter = rDoc.GetNumberFormatter(); + sal_uInt32 nFormat = pNumberFormatter->GetEntryKey( + m_aDateFormat, LanguageTag(m_aDateLanguage).getLanguageType()); + + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + // If not found, then create it. + sal_Int32 nCheckPos = 0; + SvNumFormatType nType; + OUString aFormat = m_aDateFormat; + pNumberFormatter->PutEntry(aFormat, nCheckPos, nType, nFormat, + LanguageTag(m_aDateLanguage).getLanguageType()); + } + + const Color* pColor = nullptr; + OUString aFormatted; + if (!m_oSelectedDate) + { + return OUString(); + } + + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + return OUString(); + } + + pNumberFormatter->GetOutputString(*m_oSelectedDate, nFormat, aFormatted, &pColor, false); + return aFormatted; +} + +void SwContentControl::SetCurrentDateValue(double fCurrentDate) +{ + SwDoc& rDoc = m_pTextNode->GetDoc(); + SvNumberFormatter* pNumberFormatter = rDoc.GetNumberFormatter(); + OUString aFormatted; + sal_uInt32 nFormat = pNumberFormatter->GetEntryKey(CURRENT_DATE_FORMAT, LANGUAGE_ENGLISH_US); + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + // If not found, then create it. + sal_Int32 nCheckPos = 0; + SvNumFormatType nType; + OUString sFormat = CURRENT_DATE_FORMAT; + pNumberFormatter->PutEntry(sFormat, nCheckPos, nType, nFormat, LANGUAGE_ENGLISH_US); + } + + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + return; + } + + const Color* pColor = nullptr; + pNumberFormatter->GetOutputString(fCurrentDate, nFormat, aFormatted, &pColor, false); + m_aCurrentDate = aFormatted + "T00:00:00Z"; +} + +double SwContentControl::GetCurrentDateValue() const +{ + if (m_aCurrentDate.isEmpty()) + { + return 0; + } + + SwDoc& rDoc = m_pTextNode->GetDoc(); + SvNumberFormatter* pNumberFormatter = rDoc.GetNumberFormatter(); + sal_uInt32 nFormat = pNumberFormatter->GetEntryKey(CURRENT_DATE_FORMAT, LANGUAGE_ENGLISH_US); + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + sal_Int32 nCheckPos = 0; + SvNumFormatType nType; + OUString sFormat = CURRENT_DATE_FORMAT; + pNumberFormatter->PutEntry(sFormat, nCheckPos, nType, nFormat, LANGUAGE_ENGLISH_US); + } + + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + return 0; + } + + double dCurrentDate = 0; + OUString aCurrentDate = m_aCurrentDate.replaceAll("T00:00:00Z", ""); + (void)pNumberFormatter->IsNumberFormat(aCurrentDate, nFormat, dCurrentDate); + return dCurrentDate; +} + +bool SwContentControl::IsInteractingCharacter(sal_Unicode cCh) +{ + if (GetCheckbox()) + { + return cCh == ' '; + } + + if (GetPicture()) + { + return cCh == '\r'; + } + + return false; +} + +bool SwContentControl::ShouldOpenPopup(const vcl::KeyCode& rKeyCode) +{ + if (HasListItems() || GetDate()) + { + // Alt-down opens the popup. + return rKeyCode.IsMod2() && rKeyCode.GetCode() == KEY_DOWN; + } + + return false; +} + +void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwContentControl")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteFormatAttribute( + pWriter, BAD_CAST("showing-place-holder"), "%s", + BAD_CAST(OString::boolean(m_bShowingPlaceHolder).getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("checkbox"), "%s", + BAD_CAST(OString::boolean(m_bCheckbox).getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("checked"), "%s", + BAD_CAST(OString::boolean(m_bChecked).getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("checked-state"), "%s", + BAD_CAST(m_aCheckedState.toUtf8().getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("unchecked-state"), "%s", + BAD_CAST(m_aUncheckedState.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("picture"), + BAD_CAST(OString::boolean(m_bPicture).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date"), + BAD_CAST(OString::boolean(m_bDate).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date-format"), + BAD_CAST(m_aDateFormat.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date-language"), + BAD_CAST(m_aDateLanguage.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("current-date"), + BAD_CAST(m_aCurrentDate.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("placeholder-doc-part"), + BAD_CAST(m_aPlaceholderDocPart.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("data-binding-prefix-mappings"), + BAD_CAST(m_aDataBindingPrefixMappings.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("data-binding-xpath"), + BAD_CAST(m_aDataBindingXpath.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("data-binding-store-item-id"), + BAD_CAST(m_aDataBindingStoreItemID.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("color"), + BAD_CAST(m_aColor.toUtf8().getStr())); + + if (!m_aListItems.empty()) + { + for (const auto& rListItem : m_aListItems) + { + rListItem.dumpAsXml(pWriter); + } + } + + (void)xmlTextWriterEndElement(pWriter); +} + +void SwContentControlListItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwContentControlListItem")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("display-text"), + BAD_CAST(m_aDisplayText.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(m_aValue.toUtf8().getStr())); + + (void)xmlTextWriterEndElement(pWriter); +} + +OUString SwContentControlListItem::ToString() const +{ + if (!m_aDisplayText.isEmpty()) + { + return m_aDisplayText; + } + + return m_aValue; +} + +bool SwContentControlListItem::operator==(const SwContentControlListItem& rOther) const +{ + return m_aDisplayText == rOther.m_aDisplayText && m_aValue == rOther.m_aValue; +} + +void SwContentControlListItem::ItemsToAny(const std::vector<SwContentControlListItem>& rItems, + uno::Any& rVal) +{ + uno::Sequence<uno::Sequence<beans::PropertyValue>> aRet(rItems.size()); + + uno::Sequence<beans::PropertyValue>* pRet = aRet.getArray(); + for (size_t i = 0; i < rItems.size(); ++i) + { + const SwContentControlListItem& rItem = rItems[i]; + uno::Sequence<beans::PropertyValue> aItem = { + comphelper::makePropertyValue("DisplayText", rItem.m_aDisplayText), + comphelper::makePropertyValue("Value", rItem.m_aValue), + }; + pRet[i] = aItem; + } + + rVal <<= aRet; +} + +std::vector<SwContentControlListItem> +SwContentControlListItem::ItemsFromAny(const css::uno::Any& rVal) +{ + std::vector<SwContentControlListItem> aRet; + + uno::Sequence<uno::Sequence<beans::PropertyValue>> aSequence; + rVal >>= aSequence; + for (const auto& rItem : aSequence) + { + comphelper::SequenceAsHashMap aMap(rItem); + SwContentControlListItem aItem; + auto it = aMap.find("DisplayText"); + if (it != aMap.end()) + { + it->second >>= aItem.m_aDisplayText; + } + it = aMap.find("Value"); + if (it != aMap.end()) + { + it->second >>= aItem.m_aValue; + } + aRet.push_back(aItem); + } + + return aRet; +} + +SwTextContentControl* SwTextContentControl::CreateTextContentControl(SwTextNode* pTargetTextNode, + SwFormatContentControl& rAttr, + sal_Int32 nStart, + sal_Int32 nEnd, bool bIsCopy) +{ + if (bIsCopy) + { + // rAttr is already cloned, now call DoCopy to copy the SwContentControl + if (!pTargetTextNode) + { + SAL_WARN("sw.core", + "SwTextContentControl ctor: cannot copy content control without target node"); + } + rAttr.DoCopy(*pTargetTextNode); + } + auto pTextContentControl(new SwTextContentControl(rAttr, nStart, nEnd)); + return pTextContentControl; +} + +SwTextContentControl::SwTextContentControl(SwFormatContentControl& rAttr, sal_Int32 nStart, + sal_Int32 nEnd) + : SwTextAttr(rAttr, nStart) + , SwTextAttrNesting(rAttr, nStart, nEnd) +{ + rAttr.SetTextAttr(this); + SetHasDummyChar(true); +} + +SwTextContentControl::~SwTextContentControl() +{ + auto& rFormatContentControl = static_cast<SwFormatContentControl&>(GetAttr()); + if (rFormatContentControl.GetTextAttr() == this) + { + rFormatContentControl.SetTextAttr(nullptr); + } +} + +void SwTextContentControl::ChgTextNode(SwTextNode* pNode) +{ + auto& rFormatContentControl = static_cast<SwFormatContentControl&>(GetAttr()); + if (rFormatContentControl.GetTextAttr() == this) + { + rFormatContentControl.NotifyChangeTextNode(pNode); + } +} + +void SwTextContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextContentControl")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + SwTextAttr::dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |