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 63bb6cc38f14fec79a241d7c313b1a7aea63637b
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue May 3 09:44:01 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Mon May 9 08:25:49 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.
    
    (cherry picked from commit 0a415b92d3c1ea2c5befd30b4ac29442f422a41d)
    
    Change-Id: I22b9f554e2e1a9e84cc7eb7e17772ea1a5775316
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133944
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx
index 43309f49f55c..f646e28b16f5 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 <svl/poolitem.hxx>
@@ -35,6 +36,7 @@ enum class SwContentControlType
 {
     RICH_TEXT,
     CHECKBOX,
+    DROP_DOWN_LIST,
 };
 
 /// SfxPoolItem subclass that wraps an SwContentControl.
@@ -72,6 +74,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 SwContentControl : public sw::BroadcastingModify
 {
@@ -97,6 +114,8 @@ class 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;
 
@@ -147,6 +166,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 239ab6a56a97..7a3b592a4858 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -875,6 +875,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 0e3fcc9ef7c0..40fe6c31329b 100644
--- a/sw/qa/core/unocore/unocore.cxx
+++ b/sw/qa/core/unocore/unocore.cxx
@@ -403,6 +403,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 90e09664f67f..ae3973fc8c4f 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,
@@ -514,6 +515,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
@@ -522,7 +524,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)
@@ -740,6 +742,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();
@@ -806,6 +821,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 306f3c2b42a6..c83b6b1d1fdb 100644
--- a/sw/source/core/unocore/unomap1.cxx
+++ b/sw/source/core/unocore/unomap1.cxx
@@ -1031,6 +1031,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 78e18197f39b..a009416f1d24 100644
--- a/sw/source/uibase/wrtsh/wrtsh1.cxx
+++ b/sw/source/uibase/wrtsh/wrtsh1.cxx
@@ -1025,6 +1025,7 @@ void 
SwWrtShell::InsertContentControl(SwContentControlType eType)
     switch (eType)
     {
         case SwContentControlType::RICH_TEXT:
+        case SwContentControlType::DROP_DOWN_LIST:
         {
             pContentControl->SetShowingPlaceHolder(true);
             if (!HasSelection())

Reply via email to