sw/inc/formatcontentcontrol.hxx | 26 +++++++++ sw/inc/unoprnms.hxx | 1 sw/qa/core/unocore/unocore.cxx | 51 +++++++++++++++++++ sw/source/core/txtnode/attrcontentcontrol.cxx | 69 ++++++++++++++++++++++++++ sw/source/core/unocore/unocontentcontrol.cxx | 30 ++++++++++- sw/source/core/unocore/unomap1.cxx | 1 sw/source/uibase/wrtsh/wrtsh1.cxx | 1 7 files changed, 178 insertions(+), 1 deletion(-)
New commits: commit 0a415b92d3c1ea2c5befd30b4ac29442f422a41d Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Tue May 3 09:44:01 2022 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Tue May 3 14:04:51 2022 +0200 sw content controls, drop-down: add doc model & UNO API Add a new property, which is a list of display-text / value pairs. If the list is non-empty, that implies that the type is a dropdown. This should be enough for the UI to be able to provide a list of choices & update dropdown state on click. Note that in contrast to dropdown field-marks, here each entry has a user-readable string and a machine-readable value. Fieldmarks only had a single value. Change-Id: I22b9f554e2e1a9e84cc7eb7e17772ea1a5775316 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133742 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx index 8419446f59c0..0cd65b66d711 100644 --- a/sw/inc/formatcontentcontrol.hxx +++ b/sw/inc/formatcontentcontrol.hxx @@ -20,6 +20,7 @@ #pragma once #include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> #include <cppuhelper/weakref.hxx> #include <sal/types.h> @@ -36,6 +37,7 @@ enum class SwContentControlType { RICH_TEXT, CHECKBOX, + DROP_DOWN_LIST, }; /// SfxPoolItem subclass that wraps an SwContentControl. @@ -73,6 +75,21 @@ public: void dumpAsXml(xmlTextWriterPtr pWriter) const override; }; +/// Represents one list item in a content control dropdown list. +class SwContentControlListItem +{ +public: + OUString m_aDisplayText; + OUString m_aValue; + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + + static void ItemsToAny(const std::vector<SwContentControlListItem>& rItems, + css::uno::Any& rVal); + + static std::vector<SwContentControlListItem> ItemsFromAny(const css::uno::Any& rVal); +}; + /// Stores the properties of a content control. class SAL_DLLPUBLIC_RTTI SwContentControl : public sw::BroadcastingModify { @@ -98,6 +115,8 @@ class SAL_DLLPUBLIC_RTTI SwContentControl : public sw::BroadcastingModify /// If m_bCheckbox is true, the value of an unchecked checkbox. OUString m_aUncheckedState; + std::vector<SwContentControlListItem> m_aListItems; + public: SwTextContentControl* GetTextAttr() const; @@ -148,6 +167,13 @@ public: OUString GetUncheckedState() const { return m_aUncheckedState; } + std::vector<SwContentControlListItem> GetListItems() const { return m_aListItems; } + + void SetListItems(const std::vector<SwContentControlListItem>& rListItems) + { + m_aListItems = rListItems; + } + virtual void dumpAsXml(xmlTextWriterPtr pWriter) const; }; diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx index bfc3a28aeecc..187491905d77 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -876,6 +876,7 @@ #define UNO_NAME_CHECKED "Checked" #define UNO_NAME_CHECKED_STATE "CheckedState" #define UNO_NAME_UNCHECKED_STATE "UncheckedState" +#define UNO_NAME_LIST_ITEMS "ListItems" #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/core/unocore/unocore.cxx b/sw/qa/core/unocore/unocore.cxx index 0f1b6a5e7623..f941c60c4aaf 100644 --- a/sw/qa/core/unocore/unocore.cxx +++ b/sw/qa/core/unocore/unocore.cxx @@ -456,6 +456,57 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testContentControlCheckbox) CPPUNIT_ASSERT_EQUAL(OUString(u"☐"), pContentControl->GetUncheckedState()); } +CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testContentControlDropdown) +{ + // Given an empty document: + SwDoc* pDoc = createSwDoc(); + + // When inserting a dropdown content control: + uno::Reference<lang::XMultiServiceFactory> xMSF(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText = xTextDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + xText->insertString(xCursor, "test", /*bAbsorb=*/false); + xCursor->gotoStart(/*bExpand=*/false); + xCursor->gotoEnd(/*bExpand=*/true); + uno::Reference<text::XTextContent> xContentControl( + xMSF->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY); + { + uno::Sequence<beans::PropertyValues> aListItems = { + { + comphelper::makePropertyValue("DisplayText", uno::makeAny(OUString("red"))), + comphelper::makePropertyValue("Value", uno::makeAny(OUString("R"))), + }, + { + comphelper::makePropertyValue("DisplayText", uno::makeAny(OUString("green"))), + comphelper::makePropertyValue("Value", uno::makeAny(OUString("G"))), + }, + { + comphelper::makePropertyValue("DisplayText", uno::makeAny(OUString("blue"))), + comphelper::makePropertyValue("Value", uno::makeAny(OUString("B"))), + }, + }; + // Without the accompanying fix in place, this test would have failed with: + // An uncaught exception of type com.sun.star.beans.UnknownPropertyException + xContentControlProps->setPropertyValue("ListItems", uno::makeAny(aListItems)); + } + xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); + + // Then make sure that the specified properties are set: + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + SwTextNode* pTextNode = pWrtShell->GetCursor()->GetNode().GetTextNode(); + SwTextAttr* pAttr = pTextNode->GetTextAttrForCharAt(0, RES_TXTATR_CONTENTCONTROL); + auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr); + auto& rFormatContentControl + = static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr()); + SwContentControl* pContentControl = rFormatContentControl.GetContentControl(); + std::vector<SwContentControlListItem> aListItems = pContentControl->GetListItems(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aListItems.size()); + CPPUNIT_ASSERT_EQUAL(OUString("red"), aListItems[0].m_aDisplayText); + CPPUNIT_ASSERT_EQUAL(OUString("R"), aListItems[0].m_aValue); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx b/sw/source/core/txtnode/attrcontentcontrol.cxx index e469bab96e1d..c28e686ce6fb 100644 --- a/sw/source/core/txtnode/attrcontentcontrol.cxx +++ b/sw/source/core/txtnode/attrcontentcontrol.cxx @@ -22,6 +22,8 @@ #include <libxml/xmlwriter.h> #include <sal/log.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequenceashashmap.hxx> #include <ndtxt.hxx> #include <textcontentcontrol.hxx> @@ -220,9 +222,76 @@ void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const BAD_CAST(m_aCheckedState.toUtf8().getStr())); (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("unchecked-state"), "%s", BAD_CAST(m_aUncheckedState.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); +} + +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, diff --git a/sw/source/core/unocore/unocontentcontrol.cxx b/sw/source/core/unocore/unocontentcontrol.cxx index 81ccc9fd088c..19e5e60176d3 100644 --- a/sw/source/core/unocore/unocontentcontrol.cxx +++ b/sw/source/core/unocore/unocontentcontrol.cxx @@ -160,6 +160,7 @@ public: bool m_bChecked; OUString m_aCheckedState; OUString m_aUncheckedState; + std::vector<SwContentControlListItem> m_aListItems; Impl(SwXContentControl& rThis, SwDoc& rDoc, SwContentControl* pContentControl, const uno::Reference<text::XText>& xParentText, @@ -516,6 +517,7 @@ void SwXContentControl::AttachImpl(const uno::Reference<text::XTextRange>& xText pContentControl->SetChecked(m_pImpl->m_bChecked); pContentControl->SetCheckedState(m_pImpl->m_aCheckedState); pContentControl->SetUncheckedState(m_pImpl->m_aUncheckedState); + pContentControl->SetListItems(m_pImpl->m_aListItems); SwFormatContentControl aContentControl(pContentControl, nWhich); bool bSuccess @@ -524,7 +526,7 @@ void SwXContentControl::AttachImpl(const uno::Reference<text::XTextRange>& xText if (!bSuccess) { throw lang::IllegalArgumentException( - "SwXContentControl::AttachImpl(): cannot create meta: range invalid?", + "SwXContentControl::AttachImpl(): cannot create content control: invalid range", static_cast<::cppu::OWeakObject*>(this), 1); } if (!pTextAttr) @@ -742,6 +744,19 @@ void SAL_CALL SwXContentControl::setPropertyValue(const OUString& rPropertyName, } } } + else if (rPropertyName == UNO_NAME_LIST_ITEMS) + { + std::vector<SwContentControlListItem> aItems + = SwContentControlListItem::ItemsFromAny(rValue); + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_aListItems = aItems; + } + else + { + m_pImpl->m_pContentControl->SetListItems(aItems); + } + } else { throw beans::UnknownPropertyException(); @@ -808,6 +823,19 @@ uno::Any SAL_CALL SwXContentControl::getPropertyValue(const OUString& rPropertyN aRet <<= m_pImpl->m_pContentControl->GetUncheckedState(); } } + else if (rPropertyName == UNO_NAME_LIST_ITEMS) + { + std::vector<SwContentControlListItem> aItems; + if (m_pImpl->m_bIsDescriptor) + { + aItems = m_pImpl->m_aListItems; + } + else + { + aItems = m_pImpl->m_pContentControl->GetListItems(); + } + SwContentControlListItem::ItemsToAny(aItems, aRet); + } else { throw beans::UnknownPropertyException(); diff --git a/sw/source/core/unocore/unomap1.cxx b/sw/source/core/unocore/unomap1.cxx index 3da6c8097051..b580ed2cc982 100644 --- a/sw/source/core/unocore/unomap1.cxx +++ b/sw/source/core/unocore/unomap1.cxx @@ -1027,6 +1027,7 @@ const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetContentControlProper { u"" UNO_NAME_CHECKED, 0, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, { u"" UNO_NAME_CHECKED_STATE, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, { u"" UNO_NAME_UNCHECKED_STATE, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { u"" UNO_NAME_LIST_ITEMS, 0, cppu::UnoType<uno::Sequence<uno::Sequence<beans::PropertyValue>>>::get(), PROPERTY_NONE, 0 }, { u"", 0, css::uno::Type(), 0, 0 } }; diff --git a/sw/source/uibase/wrtsh/wrtsh1.cxx b/sw/source/uibase/wrtsh/wrtsh1.cxx index a0cd400b5145..32d515de2217 100644 --- a/sw/source/uibase/wrtsh/wrtsh1.cxx +++ b/sw/source/uibase/wrtsh/wrtsh1.cxx @@ -1030,6 +1030,7 @@ void SwWrtShell::InsertContentControl(SwContentControlType eType) switch (eType) { case SwContentControlType::RICH_TEXT: + case SwContentControlType::DROP_DOWN_LIST: { pContentControl->SetShowingPlaceHolder(true); if (!HasSelection())