desktop/qa/desktop_lib/test_desktop_lib.cxx                         |    4 
 desktop/source/lib/init.cxx                                         |   32 ++++
 include/LibreOfficeKit/LibreOfficeKit.h                             |    3 
 include/LibreOfficeKit/LibreOfficeKit.hxx                           |   19 ++
 include/LibreOfficeKit/LibreOfficeKitGtk.h                          |    7 
 include/vcl/ITiledRenderable.hxx                                    |    6 
 libreofficekit/qa/gtktiledviewer/gtv-application-window.cxx         |    2 
 libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx |   30 +++
 libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.hxx |    1 
 libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx               |    4 
 libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx               |    1 
 libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx            |   16 ++
 libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx            |    2 
 libreofficekit/qa/gtktiledviewer/gtv.ui                             |   18 ++
 libreofficekit/source/gtk/lokdocview.cxx                            |   27 ++-
 sw/inc/unotxdoc.hxx                                                 |    3 
 sw/qa/extras/tiledrendering/tiledrendering.cxx                      |   77 
++++++++++
 sw/source/core/crsr/viscrs.cxx                                      |   10 +
 sw/source/uibase/uno/unotxdoc.cxx                                   |   46 
+++++
 tools/qa/cppunit/test_json_writer.cxx                               |   17 ++
 20 files changed, 319 insertions(+), 6 deletions(-)

New commits:
commit c7d80d229a5660a0ee702477bfbd2ca137992a7d
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Fri May 13 08:26:32 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri May 13 09:10:25 2022 +0200

    sw content controls, dropdown: add LOK API
    
    - expose the available list items in a new "items" key of the
      LOK_CALLBACK_CONTENT_CONTROL callback
    
    - add a new lok::Document::sendContentControlEvent() function to be able
      to select a list item from the current drop-down
    
    - add a new listbox to the gtktiledviewer toolbar to select a content
      control list item when the cursor is inside a dropdown
    
    - add tests for the array API of tools::JsonWriter
    
    Change-Id: I47f1333a7815d67952f7c20a9cba1b248886f6dd
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/134256
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/desktop/qa/desktop_lib/test_desktop_lib.cxx 
b/desktop/qa/desktop_lib/test_desktop_lib.cxx
index 038ea2db6aca..f2fd0ab16173 100644
--- a/desktop/qa/desktop_lib/test_desktop_lib.cxx
+++ b/desktop/qa/desktop_lib/test_desktop_lib.cxx
@@ -3631,10 +3631,12 @@ void DesktopLOKTest::testABI()
     CPPUNIT_ASSERT_EQUAL(documentClassOffset(61), offsetof(struct 
_LibreOfficeKitDocumentClass, sendFormFieldEvent));
     CPPUNIT_ASSERT_EQUAL(documentClassOffset(62), offsetof(struct 
_LibreOfficeKitDocumentClass, setBlockedCommandList));
     CPPUNIT_ASSERT_EQUAL(documentClassOffset(63), offsetof(struct 
_LibreOfficeKitDocumentClass, renderSearchResult));
+    CPPUNIT_ASSERT_EQUAL(documentClassOffset(64),
+                         offsetof(struct _LibreOfficeKitDocumentClass, 
sendContentControlEvent));
 
     // Extending is fine, update this, and add new assert for the offsetof the
     // new method
-    CPPUNIT_ASSERT_EQUAL(documentClassOffset(64), sizeof(struct 
_LibreOfficeKitDocumentClass));
+    CPPUNIT_ASSERT_EQUAL(documentClassOffset(65), sizeof(struct 
_LibreOfficeKitDocumentClass));
 }
 
 CPPUNIT_TEST_SUITE_REGISTRATION(DesktopLOKTest);
diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx
index 50ae3a5da400..45f059a73d7d 100644
--- a/desktop/source/lib/init.cxx
+++ b/desktop/source/lib/init.cxx
@@ -1143,6 +1143,8 @@ static bool 
doc_renderSearchResult(LibreOfficeKitDocument* pThis,
                                  const char* pSearchResult, unsigned char** 
pBitmapBuffer,
                                  int* pWidth, int* pHeight, size_t* pByteSize);
 
+static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const 
char* pArguments);
+
 } // extern "C"
 
 namespace {
@@ -1286,6 +1288,8 @@ LibLODocument_Impl::LibLODocument_Impl(const 
uno::Reference <css::lang::XCompone
 
         m_pDocumentClass->setBlockedCommandList = doc_setBlockedCommandList;
 
+        m_pDocumentClass->sendContentControlEvent = 
doc_sendContentControlEvent;
+
         gDocumentClass = m_pDocumentClass;
     }
     pClass = m_pDocumentClass.get();
@@ -6070,6 +6074,34 @@ static bool 
doc_renderSearchResult(LibreOfficeKitDocument* pThis,
     return true;
 }
 
+static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const 
char* pArguments)
+{
+    SolarMutexGuard aGuard;
+
+    // Supported in Writer only
+    if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT)
+    {
+        return;
+    }
+
+    StringMap aMap(jsdialog::jsonToStringMap(pArguments));
+    ITiledRenderable* pDoc = getTiledRenderable(pThis);
+    if (!pDoc)
+    {
+        SetLastExceptionMsg("Document doesn't support tiled rendering");
+        return;
+    }
+
+    // Sanity check
+    if (aMap.find("type") == aMap.end() || aMap.find("selected") == aMap.end())
+    {
+        SetLastExceptionMsg("Wrong arguments for sendContentControlEvent");
+        return;
+    }
+
+    pDoc->executeContentControlEvent(aMap);
+}
+
 static char* lo_getError (LibreOfficeKit *pThis)
 {
     comphelper::ProfileZone aZone("lo_getError");
diff --git a/include/LibreOfficeKit/LibreOfficeKit.h 
b/include/LibreOfficeKit/LibreOfficeKit.h
index 75b10017b6f6..6ebf81aacaa4 100644
--- a/include/LibreOfficeKit/LibreOfficeKit.h
+++ b/include/LibreOfficeKit/LibreOfficeKit.h
@@ -469,6 +469,9 @@ struct _LibreOfficeKitDocumentClass
                                 unsigned char** pBitmapBuffer,
                                 int* pWidth, int* pHeight, size_t* pByteSize);
 
+    /// @see lok::Document::sendContentControlEvent().
+    void (*sendContentControlEvent)(LibreOfficeKitDocument* pThis, const char* 
pArguments);
+
 #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY
 };
 
diff --git a/include/LibreOfficeKit/LibreOfficeKit.hxx 
b/include/LibreOfficeKit/LibreOfficeKit.hxx
index 912b3e0d2203..3140e121151d 100644
--- a/include/LibreOfficeKit/LibreOfficeKit.hxx
+++ b/include/LibreOfficeKit/LibreOfficeKit.hxx
@@ -807,6 +807,25 @@ public:
         return mpDoc->pClass->renderSearchResult(mpDoc, pSearchResult, 
pBitmapBuffer, pWidth, pHeight, pByteSize);
     }
 
+    /**
+     * Posts an event for the content control at the cursor position.
+     *
+     * @param pArguments arguments of the event.
+     *
+     * Example argument string:
+     *
+     * {
+     *     "type": "drop-down",
+     *     "selected": "2"
+     * }
+     *
+     * selects the 3rd list item of the drop-down.
+     */
+    void sendContentControlEvent(const char* pArguments)
+    {
+        mpDoc->pClass->sendContentControlEvent(mpDoc, pArguments);
+    }
+
 #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY
 };
 
diff --git a/include/LibreOfficeKit/LibreOfficeKitGtk.h 
b/include/LibreOfficeKit/LibreOfficeKitGtk.h
index 15958e35644b..83a2a1f750f6 100644
--- a/include/LibreOfficeKit/LibreOfficeKitGtk.h
+++ b/include/LibreOfficeKit/LibreOfficeKitGtk.h
@@ -367,6 +367,13 @@ gfloat                         lok_doc_view_pixel_to_twip  
        (LOKDocView*
 gfloat                         lok_doc_view_twip_to_pixel          
(LOKDocView* pDocView,
                                                                     float 
fInput);
 
+/**
+ * lok_doc_view_send_content_control_event:
+ * @pDocView: The #LOKDocView instance
+ * @pArguments: (nullable) (allow-none): see 
lok::Document::sendContentControlEvent() for the details.
+ */
+void lok_doc_view_send_content_control_event(LOKDocView* pDocView, const 
gchar* pArguments);
+
 G_END_DECLS
 
 #endif // INCLUDED_LIBREOFFICEKIT_LIBREOFFICEKITGTK_H
diff --git a/include/vcl/ITiledRenderable.hxx b/include/vcl/ITiledRenderable.hxx
index 0156db0f17c5..1bddde538c71 100644
--- a/include/vcl/ITiledRenderable.hxx
+++ b/include/vcl/ITiledRenderable.hxx
@@ -336,6 +336,12 @@ public:
     {
         return std::vector<basegfx::B2DRange>();
     }
+
+    /**
+     * Execute a content control event in the document.
+     * E.g. select a list item from a drop down content control.
+     */
+    virtual void executeContentControlEvent(const StringMap&) {}
 };
 } // namespace vcl
 
diff --git a/libreofficekit/qa/gtktiledviewer/gtv-application-window.cxx 
b/libreofficekit/qa/gtktiledviewer/gtv-application-window.cxx
index 4d2dbd41f251..7e2f9f907ebf 100644
--- a/libreofficekit/qa/gtktiledviewer/gtv-application-window.cxx
+++ b/libreofficekit/qa/gtktiledviewer/gtv-application-window.cxx
@@ -331,6 +331,8 @@ static void setupDocView(GtvApplicationWindow* window)
     g_signal_connect(window->lokdocview, "search-result-count", 
G_CALLBACK(LOKDocViewSigHandlers::searchResultCount), nullptr);
     g_signal_connect(window->lokdocview, "part-changed", 
G_CALLBACK(LOKDocViewSigHandlers::partChanged), nullptr);
     g_signal_connect(window->lokdocview, "hyperlink-clicked", 
G_CALLBACK(LOKDocViewSigHandlers::hyperlinkClicked), nullptr);
+    g_signal_connect(window->lokdocview, "content-control",
+                     G_CALLBACK(LOKDocViewSigHandlers::contentControl), 
nullptr);
     g_signal_connect(window->lokdocview, "cursor-changed", 
G_CALLBACK(LOKDocViewSigHandlers::cursorChanged), nullptr);
     g_signal_connect(window->lokdocview, "address-changed", 
G_CALLBACK(LOKDocViewSigHandlers::addressChanged), nullptr);
     g_signal_connect(window->lokdocview, "formula-changed", 
G_CALLBACK(LOKDocViewSigHandlers::formulaChanged), nullptr);
diff --git 
a/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx 
b/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx
index a2cbd8021dea..4e702364a6a9 100644
--- a/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx
+++ b/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx
@@ -202,6 +202,36 @@ void LOKDocViewSigHandlers::formulaChanged(LOKDocView* 
pDocView, char* pPayload,
     gtk_entry_set_text(pFormulabar, pPayload);
 }
 
+void LOKDocViewSigHandlers::contentControl(LOKDocView* pDocView, gchar* pJson, 
gpointer)
+{
+    GtvApplicationWindow* window
+        = 
GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView)));
+    GtvMainToolbar* toolbar = gtv_application_window_get_main_toolbar(window);
+    gtv_application_window_set_part_broadcast(window, false);
+    gtk_list_store_clear(
+        
GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(toolbar->m_pContentControlSelector))));
+    if (!window->lokdocview)
+    {
+        return;
+    }
+
+    std::stringstream aStream(pJson);
+    boost::property_tree::ptree aTree;
+    boost::property_tree::read_json(aStream, aTree);
+    boost::optional<boost::property_tree::ptree&> oItems = 
aTree.get_child_optional("items");
+    if (oItems)
+    {
+        for (const auto& rItem : *oItems)
+        {
+            std::string aValue = rItem.second.get_value<std::string>();
+            
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(toolbar->m_pContentControlSelector),
+                                           aValue.c_str());
+        }
+    }
+
+    gtv_application_window_set_part_broadcast(window, true);
+}
+
 void LOKDocViewSigHandlers::passwordRequired(LOKDocView* pDocView, char* pUrl, 
gboolean bModify, gpointer)
 {
     GtvApplicationWindow* window = 
GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(pDocView)));
diff --git 
a/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.hxx 
b/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.hxx
index 41fc73ab1a07..0c5bb7113e26 100644
--- a/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.hxx
+++ b/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.hxx
@@ -27,6 +27,7 @@ namespace LOKDocViewSigHandlers {
     void passwordRequired(LOKDocView* pDocView, char* pUrl, gboolean bModify, 
gpointer);
     void comment(LOKDocView* pDocView, gchar* pComment, gpointer);
     void window(LOKDocView* pDocView, gchar* pPayload, gpointer);
+    void contentControl(LOKDocView* pDocView, gchar* pComment, gpointer);
 
     gboolean configureEvent(GtkWidget* pWidget, GdkEventConfigure* pEvent, 
gpointer pData);
 }
diff --git a/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx 
b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx
index 73ff6042e911..afe6162f76fc 100644
--- a/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx
+++ b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx
@@ -116,6 +116,8 @@ gtv_main_toolbar_init(GtvMainToolbar* toolbar)
 
     toolbar->m_pAddressbar = GTK_WIDGET(gtk_builder_get_object(builder.get(), 
"addressbar_entry"));
     toolbar->m_pFormulabar = GTK_WIDGET(gtk_builder_get_object(builder.get(), 
"formulabar_entry"));
+    toolbar->m_pContentControlSelector
+        = GTK_WIDGET(gtk_builder_get_object(builder.get(), 
"combo_contentcontrolselector"));
 
     // TODO: compile with -rdynamic and get rid of it
     gtk_builder_add_callback_symbol(builder.get(), "btn_clicked", 
G_CALLBACK(btn_clicked));
@@ -128,6 +130,8 @@ gtv_main_toolbar_init(GtvMainToolbar* toolbar)
     gtk_builder_add_callback_symbol(builder.get(), "toggleEditing", 
G_CALLBACK(toggleEditing));
     gtk_builder_add_callback_symbol(builder.get(), "changePartMode", 
G_CALLBACK(changePartMode));
     gtk_builder_add_callback_symbol(builder.get(), "changePart", 
G_CALLBACK(changePart));
+    gtk_builder_add_callback_symbol(builder.get(), "changeContentControl",
+                                    G_CALLBACK(changeContentControl));
     gtk_builder_add_callback_symbol(builder.get(), "changeZoom", 
G_CALLBACK(changeZoom));
     gtk_builder_add_callback_symbol(builder.get(), "toggleFindbar", 
G_CALLBACK(toggleFindbar));
     gtk_builder_add_callback_symbol(builder.get(), "documentRedline", 
G_CALLBACK(documentRedline));
diff --git a/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx 
b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx
index 0930228fda59..e385e6e855d1 100644
--- a/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx
+++ b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx
@@ -29,6 +29,7 @@ struct GtvMainToolbar
 
     GtkWidget* m_pAddressbar;
     GtkWidget* m_pFormulabar;
+    GtkWidget* m_pContentControlSelector;
 };
 
 struct GtvMainToolbarClass
diff --git a/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx 
b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx
index e7208e5860a3..0afce9b671db 100644
--- a/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx
+++ b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx
@@ -318,6 +318,22 @@ void changePartMode( GtkWidget* pSelector, gpointer /* 
pItem */ )
     }
 }
 
+void changeContentControl(GtkWidget* pSelector, gpointer /*pItem*/)
+{
+    GtvApplicationWindow* window = 
GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pSelector));
+    if (gtv_application_window_get_part_broadcast(window) && 
window->lokdocview)
+    {
+        int nItem = gtk_combo_box_get_active(GTK_COMBO_BOX(pSelector));
+        boost::property_tree::ptree aValues;
+        aValues.put("type", "drop-down");
+        aValues.put("selected", std::to_string(nItem));
+        std::stringstream aStream;
+        boost::property_tree::write_json(aStream, aValues);
+        std::string aJson = aStream.str();
+        
lok_doc_view_send_content_control_event(LOK_DOC_VIEW(window->lokdocview), 
aJson.c_str());
+    }
+}
+
 void changeZoom( GtkWidget* pButton, gpointer /* pItem */ )
 {
     static const float fZooms[] = { 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 5.0 };
diff --git a/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx 
b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx
index a441d4e13bf5..c5cf89c281fc 100644
--- a/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx
+++ b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx
@@ -67,6 +67,8 @@ gboolean signalAddressbar(GtkWidget* pWidget, GdkEventKey* 
pEvent, gpointer /*pD
 /// Handles the key-press-event of the formula entry widget.
 gboolean signalFormulabar(GtkWidget* /*pWidget*/, GdkEventKey* /*pEvent*/, 
gpointer /*pData*/);
 
+void changeContentControl(GtkWidget* pSelector, gpointer /*pItem*/);
+
 #endif
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/libreofficekit/qa/gtktiledviewer/gtv.ui 
b/libreofficekit/qa/gtktiledviewer/gtv.ui
index 2efb235cc2b0..26e15a0a9e3d 100644
--- a/libreofficekit/qa/gtktiledviewer/gtv.ui
+++ b/libreofficekit/qa/gtktiledviewer/gtv.ui
@@ -427,6 +427,24 @@
         <property name="homogeneous">True</property>
       </packing>
     </child>
+    <child>
+      <object class="GtkToolItem" id="contentcontrolselectortoolitem">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkComboBoxText" id="combo_contentcontrolselector">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="tooltip_text">Content control list items</property>
+            <signal name="changed" handler="changeContentControl" 
swapped="no"/>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="homogeneous">True</property>
+      </packing>
+    </child>
     <child>
       <object class="GtkToggleToolButton" id="btn_editmode">
         <property name="visible">True</property>
diff --git a/libreofficekit/source/gtk/lokdocview.cxx 
b/libreofficekit/source/gtk/lokdocview.cxx
index 1bf7e7301d7c..b20cc85af875 100644
--- a/libreofficekit/source/gtk/lokdocview.cxx
+++ b/libreofficekit/source/gtk/lokdocview.cxx
@@ -1410,8 +1410,8 @@ callback (gpointer pData)
         {
             priv->m_aContentControlRectangles.clear();
         }
-        bool bIsTextSelected = !priv->m_aContentControlRectangles.empty();
-        g_signal_emit(pDocView, doc_view_signals[CONTENT_CONTROL], 0, 
bIsTextSelected);
+        g_signal_emit(pCallback->m_pDocView, 
doc_view_signals[CONTENT_CONTROL], 0,
+                      pCallback->m_aPayload.c_str());
         gtk_widget_queue_draw(GTK_WIDGET(pDocView));
     }
     break;
@@ -3325,7 +3325,7 @@ static void lok_doc_view_class_init (LOKDocViewClass* 
pClass)
     /**
      * LOKDocView::content-control:
      * @pDocView: the #LOKDocView on which the signal is emitted
-     * @bIsTextSelected: whether current content control is non-null
+     * @pPayload: the JSON string containing the information about ruler 
properties
      */
     doc_view_signals[CONTENT_CONTROL] =
         g_signal_new("content-control",
@@ -3333,9 +3333,9 @@ static void lok_doc_view_class_init (LOKDocViewClass* 
pClass)
                      G_SIGNAL_RUN_FIRST,
                      0,
                      nullptr, nullptr,
-                     g_cclosure_marshal_VOID__BOOLEAN,
+                     g_cclosure_marshal_generic,
                      G_TYPE_NONE, 1,
-                     G_TYPE_BOOLEAN);
+                     G_TYPE_STRING);
 
     /**
      * LOKDocView::password-required:
@@ -3726,6 +3726,23 @@ lok_doc_view_set_part (LOKDocView* pDocView, int nPart)
     priv->m_nPartId = nPart;
 }
 
+SAL_DLLPUBLIC_EXPORT void lok_doc_view_send_content_control_event(LOKDocView* 
pDocView,
+                                                                  const gchar* 
pArguments)
+{
+    LOKDocViewPrivate& priv = getPrivate(pDocView);
+    if (!priv->m_pDocument)
+    {
+        return;
+    }
+
+    std::scoped_lock<std::mutex> aGuard(g_aLOKMutex);
+    std::stringstream ss;
+    ss << "lok::Document::sendContentControlEvent('" << pArguments << "')";
+    g_info("%s", ss.str().c_str());
+    priv->m_pDocument->pClass->setView(priv->m_pDocument, priv->m_nViewId);
+    return 
priv->m_pDocument->pClass->sendContentControlEvent(priv->m_pDocument, 
pArguments);
+}
+
 SAL_DLLPUBLIC_EXPORT gchar*
 lok_doc_view_get_part_name (LOKDocView* pDocView, int nPart)
 {
diff --git a/sw/inc/unotxdoc.hxx b/sw/inc/unotxdoc.hxx
index 02e2d8efb365..dc022d34ccc7 100644
--- a/sw/inc/unotxdoc.hxx
+++ b/sw/inc/unotxdoc.hxx
@@ -449,6 +449,9 @@ public:
     // css::tiledrendering::XTiledRenderable
     virtual void SAL_CALL paintTile( const ::css::uno::Any& Parent, 
::sal_Int32 nOutputWidth, ::sal_Int32 nOutputHeight, ::sal_Int32 nTilePosX, 
::sal_Int32 nTilePosY, ::sal_Int32 nTileWidth, ::sal_Int32 nTileHeight ) 
override;
 
+    /// @see vcl::ITiledRenderable::executeContentControlEvent().
+    void executeContentControlEvent(const StringMap& aArguments) override;
+
     void                        Invalidate();
     void                        Reactivate(SwDocShell* pNewDocShell);
     SwXDocumentPropertyHelper * GetPropertyHelper ();
diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx 
b/sw/qa/extras/tiledrendering/tiledrendering.cxx
index c99b38aabef0..1090662aa127 100644
--- a/sw/qa/extras/tiledrendering/tiledrendering.cxx
+++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx
@@ -168,6 +168,7 @@ public:
     void testMoveShapeHandle();
     void testRedlinePortions();
     void testContentControl();
+    void testDropDownContentControl();
 
     CPPUNIT_TEST_SUITE(SwTiledRenderingTest);
     CPPUNIT_TEST(testRegisterCallback);
@@ -256,6 +257,7 @@ public:
     CPPUNIT_TEST(testMoveShapeHandle);
     CPPUNIT_TEST(testRedlinePortions);
     CPPUNIT_TEST(testContentControl);
+    CPPUNIT_TEST(testDropDownContentControl);
     CPPUNIT_TEST_SUITE_END();
 
 private:
@@ -3637,6 +3639,81 @@ void SwTiledRenderingTest::testContentControl()
     CPPUNIT_ASSERT_EQUAL(OString("hide"), sAction);
 }
 
+void SwTiledRenderingTest::testDropDownContentControl()
+{
+    // Given a document with a dropdown content control:
+    SwXTextDocument* pXTextDocument = createDoc();
+    SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
+    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, "choose an item", /*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::Any(OUString("red"))),
+                comphelper::makePropertyValue("Value", 
uno::Any(OUString("R"))),
+            },
+            {
+                comphelper::makePropertyValue("DisplayText", 
uno::Any(OUString("green"))),
+                comphelper::makePropertyValue("Value", 
uno::Any(OUString("G"))),
+            },
+            {
+                comphelper::makePropertyValue("DisplayText", 
uno::Any(OUString("blue"))),
+                comphelper::makePropertyValue("Value", 
uno::Any(OUString("B"))),
+            },
+        };
+        xContentControlProps->setPropertyValue("ListItems", 
uno::Any(aListItems));
+    }
+    xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    m_aContentControl.clear();
+
+    // When entering that content control:
+    pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, /*nCount=*/1, 
/*bBasicCall=*/false);
+
+    // Then make sure that the callback is emitted:
+    CPPUNIT_ASSERT(!m_aContentControl.isEmpty());
+    {
+        std::stringstream aStream(m_aContentControl.getStr());
+        boost::property_tree::ptree aTree;
+        boost::property_tree::read_json(aStream, aTree);
+        OString sAction = 
aTree.get_child("action").get_value<std::string>().c_str();
+        CPPUNIT_ASSERT_EQUAL(OString("show"), sAction);
+        OString sRectangles = 
aTree.get_child("rectangles").get_value<std::string>().c_str();
+        CPPUNIT_ASSERT(!sRectangles.isEmpty());
+        boost::optional<boost::property_tree::ptree&> oItems = 
aTree.get_child_optional("items");
+        CPPUNIT_ASSERT(oItems);
+        static const std::vector<std::string> vExpected = { "red", "green", 
"blue" };
+        size_t i = 0;
+        for (const auto& rItem : *oItems)
+        {
+            CPPUNIT_ASSERT_EQUAL(vExpected[i++], 
rItem.second.get_value<std::string>());
+        }
+    }
+
+    // And when selecting the 2nd item (green):
+    std::map<OUString, OUString> aArguments;
+    aArguments.emplace("type", "drop-down");
+    aArguments.emplace("selected", "1");
+    pXTextDocument->executeContentControlEvent(aArguments);
+
+    // Then make sure that the document is updated accordingly:
+    SwTextNode* pTextNode = pWrtShell->GetCursor()->GetNode().GetTextNode();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: green
+    // - Actual  : choose an item
+    // i.e. the document text was not updated.
+    CPPUNIT_ASSERT_EQUAL(OUString("green"), 
pTextNode->GetExpandText(pWrtShell->GetLayout()));
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(SwTiledRenderingTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/core/crsr/viscrs.cxx b/sw/source/core/crsr/viscrs.cxx
index 2cf7052553ac..ff81dd74d8ca 100644
--- a/sw/source/core/crsr/viscrs.cxx
+++ b/sw/source/core/crsr/viscrs.cxx
@@ -697,6 +697,16 @@ void SwSelPaintRects::HighlightContentControl()
             tools::JsonWriter aJson;
             aJson.put("action", "show");
             aJson.put("rectangles", aPayload);
+
+            if (pContentControl && pContentControl->HasListItems())
+            {
+                tools::ScopedJsonWriterArray aItems = 
aJson.startArray("items");
+                for (const auto& rItem : pContentControl->GetListItems())
+                {
+                    aJson.putSimpleValue(rItem.ToString());
+                }
+            }
+
             std::unique_ptr<char, o3tl::free_delete> 
pJson(aJson.extractData());
             
GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CONTENT_CONTROL,
 pJson.get());
         }
diff --git a/sw/source/uibase/uno/unotxdoc.cxx 
b/sw/source/uibase/uno/unotxdoc.cxx
index a5ce16dc8642..3a2368015c38 100644
--- a/sw/source/uibase/uno/unotxdoc.cxx
+++ b/sw/source/uibase/uno/unotxdoc.cxx
@@ -163,6 +163,7 @@
 
 #include <IDocumentOutlineNodes.hxx>
 #include <SearchResultLocator.hxx>
+#include <textcontentcontrol.hxx>
 
 using namespace ::com::sun::star;
 using namespace ::com::sun::star::text;
@@ -3363,6 +3364,51 @@ SwXTextDocument::getSearchResultRectangles(const char* 
pPayload)
     return std::vector<basegfx::B2DRange>();
 }
 
+void SwXTextDocument::executeContentControlEvent(const StringMap& rArguments)
+{
+    SwWrtShell* pWrtShell = m_pDocShell->GetWrtShell();
+    const SwPosition* pStart = pWrtShell->GetCursor()->Start();
+    SwTextNode* pTextNode = pStart->nNode.GetNode().GetTextNode();
+    if (!pTextNode)
+    {
+        return;
+    }
+
+    SwTextAttr* pAttr = pTextNode->GetTextAttrAt(pStart->nContent.GetIndex(),
+                                                 RES_TXTATR_CONTENTCONTROL, 
SwTextNode::PARENT);
+    if (!pAttr)
+    {
+        return;
+    }
+
+    auto pTextContentControl = 
static_txtattr_cast<SwTextContentControl*>(pAttr);
+    const SwFormatContentControl& rFormatContentControl = 
pTextContentControl->GetContentControl();
+    auto pContentControl = 
const_cast<SwContentControl*>(rFormatContentControl.GetContentControl());
+    auto it = rArguments.find("type");
+    if (it == rArguments.end())
+    {
+        return;
+    }
+
+    if (it->second == "drop-down")
+    {
+        if (!pContentControl->HasListItems())
+        {
+            return;
+        }
+
+        it = rArguments.find("selected");
+        if (it == rArguments.end())
+        {
+            return;
+        }
+
+        sal_Int32 nSelection = it->second.toInt32();
+        pContentControl->SetSelectedListItem(nSelection);
+        pWrtShell->GotoContentControl(rFormatContentControl);
+    }
+}
+
 int SwXTextDocument::getPart()
 {
     SolarMutexGuard aGuard;
diff --git a/tools/qa/cppunit/test_json_writer.cxx 
b/tools/qa/cppunit/test_json_writer.cxx
index 2257fe6d834d..188b22f9c617 100644
--- a/tools/qa/cppunit/test_json_writer.cxx
+++ b/tools/qa/cppunit/test_json_writer.cxx
@@ -29,10 +29,12 @@ public:
 
     void test1();
     void test2();
+    void testArray();
 
     CPPUNIT_TEST_SUITE(JsonWriterTest);
     CPPUNIT_TEST(test1);
     CPPUNIT_TEST(test2);
+    CPPUNIT_TEST(testArray);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -81,6 +83,21 @@ void JsonWriterTest::test2()
                          std::string(result.get()));
 }
 
+void JsonWriterTest::testArray()
+{
+    tools::JsonWriter aJson;
+    {
+        tools::ScopedJsonWriterArray aArray = aJson.startArray("items");
+        aJson.putSimpleValue("foo");
+        aJson.putSimpleValue("bar");
+    }
+
+    std::unique_ptr<char, o3tl::free_delete> aResult(aJson.extractData());
+
+    CPPUNIT_ASSERT_EQUAL(std::string("{ \"items\": [ \"foo\", \"bar\"]}"),
+                         std::string(aResult.get()));
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(JsonWriterTest);
 }
 

Reply via email to