include/LibreOfficeKit/LibreOfficeKit.hxx | 6 include/LibreOfficeKit/LibreOfficeKitEnums.h | 7 + libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx | 3 libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx | 4 libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx | 1 libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx | 27 ++++ libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx | 1 libreofficekit/qa/gtktiledviewer/gtv.ui | 45 +++++++ sw/qa/extras/tiledrendering/tiledrendering.cxx | 57 +++++++++ sw/source/core/crsr/viscrs.cxx | 5 sw/source/uibase/uno/unotxdoc.cxx | 61 ++++++++++ 11 files changed, 217 insertions(+)
New commits: commit 9cb1a07dc2760a30d7f321aa7fa4ce2a460dfa6c Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Wed Jun 1 08:30:03 2022 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Wed Jun 1 09:09:40 2022 +0200 sw content controls, date: add LOK API - send a LOK_CALLBACK_CONTENT_CONTROL with date=true when entering a date content control - extend lok::Document::sendContentControlEvent() to be able to set the date of a date content control (after the client's date picker is closed) - update gtktiledviewer to work with these Change-Id: I0abf21eb1d4ba233050f0aa2607b68740c048262 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135214 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/include/LibreOfficeKit/LibreOfficeKit.hxx b/include/LibreOfficeKit/LibreOfficeKit.hxx index 993654c88345..6dbfbf964b29 100644 --- a/include/LibreOfficeKit/LibreOfficeKit.hxx +++ b/include/LibreOfficeKit/LibreOfficeKit.hxx @@ -851,6 +851,12 @@ public: * "type": "picture", * "changed": "file:///path/to/test.png" * } + * + * To select a date of the current date content control: + * { + * "type": "date", + * "selected": "2022-05-29T00:00:00Z" + * } */ void sendContentControlEvent(const char* pArguments) { diff --git a/include/LibreOfficeKit/LibreOfficeKitEnums.h b/include/LibreOfficeKit/LibreOfficeKitEnums.h index 63ddec957be0..f1b2328510b3 100644 --- a/include/LibreOfficeKit/LibreOfficeKitEnums.h +++ b/include/LibreOfficeKit/LibreOfficeKitEnums.h @@ -822,6 +822,13 @@ typedef enum * { * "action": "change-picture" * } + * + * Entered a date content control: + * { + * "action": "show", + * "rectangles": "...", + * "date": "true" + * } */ LOK_CALLBACK_CONTENT_CONTROL = 55, diff --git a/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx b/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx index 4e702364a6a9..8d1b3eb67e10 100644 --- a/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx +++ b/libreofficekit/qa/gtktiledviewer/gtv-lokdocview-signal-handlers.cxx @@ -229,6 +229,9 @@ void LOKDocViewSigHandlers::contentControl(LOKDocView* pDocView, gchar* pJson, g } } + boost::optional<boost::property_tree::ptree&> oDate = aTree.get_child_optional("date"); + gtk_widget_set_sensitive(GTK_WIDGET(toolbar->m_pContentControlDateSelector), bool(oDate)); + gtv_application_window_set_part_broadcast(window, true); } diff --git a/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx index afe6162f76fc..84c5335b32c2 100644 --- a/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx +++ b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.cxx @@ -118,6 +118,8 @@ gtv_main_toolbar_init(GtvMainToolbar* toolbar) 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")); + toolbar->m_pContentControlDateSelector + = GTK_WIDGET(gtk_builder_get_object(builder.get(), "menu_contentcontroldateselector")); // TODO: compile with -rdynamic and get rid of it gtk_builder_add_callback_symbol(builder.get(), "btn_clicked", G_CALLBACK(btn_clicked)); @@ -132,6 +134,8 @@ gtv_main_toolbar_init(GtvMainToolbar* toolbar) 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(), "changeDateContentControl", + G_CALLBACK(changeDateContentControl)); 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 e385e6e855d1..91827ef92695 100644 --- a/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx +++ b/libreofficekit/qa/gtktiledviewer/gtv-main-toolbar.hxx @@ -30,6 +30,7 @@ struct GtvMainToolbar GtkWidget* m_pAddressbar; GtkWidget* m_pFormulabar; GtkWidget* m_pContentControlSelector; + GtkWidget* m_pContentControlDateSelector; }; struct GtvMainToolbarClass diff --git a/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx index 0afce9b671db..bb0e7edd390d 100644 --- a/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx +++ b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.cxx @@ -334,6 +334,33 @@ void changeContentControl(GtkWidget* pSelector, gpointer /*pItem*/) } } +void changeDateContentControl(GtkWidget* pSelector, gpointer /*pItem*/) +{ + GtvApplicationWindow* window = GTV_APPLICATION_WINDOW(gtk_widget_get_toplevel(pSelector)); + if (gtv_application_window_get_part_broadcast(window) && window->lokdocview) + { + GtkPopover* pPopover = GTK_POPOVER(gtk_widget_get_parent(gtk_widget_get_parent(pSelector))); + guint nYear, nMonth, nDay; + gtk_calendar_get_date(GTK_CALENDAR(pSelector), &nYear, &nMonth, &nDay); + gtk_popover_popdown(pPopover); + + std::stringstream aDate; + aDate << std::setfill('0') << std::setw(4) << nYear; + aDate << "-"; + aDate << std::setfill('0') << std::setw(2) << (nMonth + 1); + aDate << "-"; + aDate << std::setfill('0') << std::setw(2) << nDay; + aDate << "T00:00:00Z"; + boost::property_tree::ptree aValues; + aValues.put("type", "date"); + aValues.put("selected", aDate.str()); + 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 c5cf89c281fc..c06017d87414 100644 --- a/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx +++ b/libreofficekit/qa/gtktiledviewer/gtv-signal-handlers.hxx @@ -69,6 +69,7 @@ gboolean signalFormulabar(GtkWidget* /*pWidget*/, GdkEventKey* /*pEvent*/, gpoin void changeContentControl(GtkWidget* pSelector, gpointer /*pItem*/); +void changeDateContentControl(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 26e15a0a9e3d..79cb9a9ec409 100644 --- a/libreofficekit/qa/gtktiledviewer/gtv.ui +++ b/libreofficekit/qa/gtktiledviewer/gtv.ui @@ -445,6 +445,27 @@ <property name="homogeneous">True</property> </packing> </child> + <child> + <object class="GtkToolItem" id="contentcontroldateselectortoolitem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkMenuButton" id="menu_contentcontroldateselector"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text">Content control date</property> + <property name="popover">calendar</property> + <child> + <placeholder/> + </child> + </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> @@ -779,4 +800,28 @@ </packing> </child> </object> + <object class="GtkPopover" id="calendar"> + <property name="can-focus">False</property> + <property name="position">bottom</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkCalendar" id="date"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <signal name="day-selected-double-click" handler="changeDateContentControl" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + </object> </interface> diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx b/sw/qa/extras/tiledrendering/tiledrendering.cxx index 2a8c05fe19e6..a9205c07841a 100644 --- a/sw/qa/extras/tiledrendering/tiledrendering.cxx +++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx @@ -172,6 +172,7 @@ public: void testContentControl(); void testDropDownContentControl(); void testPictureContentControl(); + void testDateContentControl(); CPPUNIT_TEST_SUITE(SwTiledRenderingTest); CPPUNIT_TEST(testRegisterCallback); @@ -262,6 +263,7 @@ public: CPPUNIT_TEST(testContentControl); CPPUNIT_TEST(testDropDownContentControl); CPPUNIT_TEST(testPictureContentControl); + CPPUNIT_TEST(testDateContentControl); CPPUNIT_TEST_SUITE_END(); private: @@ -3784,6 +3786,61 @@ void SwTiledRenderingTest::testPictureContentControl() } +void SwTiledRenderingTest::testDateContentControl() +{ + // Given a document with a date 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 a date", /*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); + xContentControlProps->setPropertyValue("Date", uno::Any(true)); + xContentControlProps->setPropertyValue("DateFormat", uno::Any(OUString("YYYY-MM-DD"))); + xContentControlProps->setPropertyValue("DateLanguage", uno::Any(OUString("en-US"))); + 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&> oDate = aTree.get_child_optional("date"); + CPPUNIT_ASSERT(oDate); + } + + // And when selecting a date: + std::map<OUString, OUString> aArguments; + aArguments.emplace("type", "date"); + aArguments.emplace("selected", "2022-05-30T00:00:00Z"); + 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: 2022-05-30 + // - Actual : choose a date + // i.e. the document text was not updated. + CPPUNIT_ASSERT_EQUAL(OUString("2022-05-30"), 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 9863e1a75324..ba3d49893233 100644 --- a/sw/source/core/crsr/viscrs.cxx +++ b/sw/source/core/crsr/viscrs.cxx @@ -708,6 +708,11 @@ void SwSelPaintRects::HighlightContentControl() } } + if (pContentControl && pContentControl->GetDate()) + { + aJson.put("date", "true"); + } + 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 57998981f02e..b3f6e2e7487f 100644 --- a/sw/source/uibase/uno/unotxdoc.cxx +++ b/sw/source/uibase/uno/unotxdoc.cxx @@ -3359,6 +3359,11 @@ SwXTextDocument::getSearchResultRectangles(const char* pPayload) return std::vector<basegfx::B2DRange>(); } +namespace +{ +inline constexpr OUStringLiteral SELECTED_DATE_FORMAT = u"YYYY-MM-DD"; +} + void SwXTextDocument::executeContentControlEvent(const StringMap& rArguments) { auto it = rArguments.find("type"); @@ -3421,6 +3426,62 @@ void SwXTextDocument::executeContentControlEvent(const StringMap& rArguments) pView->GetViewFrame()->GetDispatcher()->ExecuteList(SID_INSERT_GRAPHIC, SfxCallMode::SYNCHRON, { &aItem }); } + else if (it->second == "date") + { + 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(); + std::shared_ptr<SwContentControl> pContentControl + = rFormatContentControl.GetContentControl(); + if (!pContentControl->GetDate()) + { + return; + } + + it = rArguments.find("selected"); + if (it == rArguments.end()) + { + return; + } + + OUString aSelectedDate = it->second.replaceAll("T00:00:00Z", ""); + SwDoc& rDoc = pTextNode->GetDoc(); + SvNumberFormatter* pNumberFormatter = rDoc.GetNumberFormatter(); + sal_uInt32 nFormat + = pNumberFormatter->GetEntryKey(SELECTED_DATE_FORMAT, LANGUAGE_ENGLISH_US); + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + sal_Int32 nCheckPos = 0; + SvNumFormatType nType; + OUString sFormat = SELECTED_DATE_FORMAT; + pNumberFormatter->PutEntry(sFormat, nCheckPos, nType, nFormat, LANGUAGE_ENGLISH_US); + } + + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + return; + } + + double dCurrentDate = 0; + pNumberFormatter->IsNumberFormat(aSelectedDate, nFormat, dCurrentDate); + pContentControl->SetSelectedDate(dCurrentDate); + pWrtShell->GotoContentControl(rFormatContentControl); + } } int SwXTextDocument::getPart()