diff options
author | Miklos Vajna <vmiklos@collabora.com> | 2022-05-13 08:26:32 +0200 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2022-05-13 09:10:25 +0200 |
commit | c7d80d229a5660a0ee702477bfbd2ca137992a7d (patch) | |
tree | 841439801c80dab822561b224b162f86c748f4e5 | |
parent | 09e0cfd9b3e391435c39d3bc933d26a87e2b082e (diff) |
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 <vmiklos@collabora.com>
Tested-by: Jenkins
20 files changed, 319 insertions, 6 deletions
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 @@ -428,6 +428,24 @@ </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> <property name="label" translatable="yes">Turn on/off edit mode</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); } |