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 <vmiklos@collabora.com>
Tested-by: Jenkins
diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx
index 8419446..0cd65b6 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 bfc3a28..1874919 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 0f1b6a5..f941c60 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 e469bab..c28e686 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 81ccc9f..19e5e60 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 3da6c80..b580ed2 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 a0cd400..32d515d 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())