schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng | 5 sw/inc/formatcontentcontrol.hxx | 13 ++ sw/inc/unoprnms.hxx | 1 sw/qa/core/unocore/unocore.cxx | 3 sw/qa/uibase/wrtsh/wrtsh.cxx | 2 sw/source/core/crsr/datecontentcontrolbutton.cxx | 12 ++ sw/source/core/txtnode/attrcontentcontrol.cxx | 63 +++++++++++- sw/source/core/unocore/unocontentcontrol.cxx | 28 +++++ sw/source/core/unocore/unomap1.cxx | 2 sw/source/filter/ww8/docxattributeoutput.cxx | 10 + sw/source/uibase/wrtsh/wrtsh3.cxx | 5 xmloff/qa/unit/data/content-control-date.fodt | 2 xmloff/qa/unit/text.cxx | 6 + xmloff/source/text/txtparae.cxx | 6 + xmloff/source/text/xmlcontentcontrolcontext.cxx | 9 + xmloff/source/text/xmlcontentcontrolcontext.hxx | 1 16 files changed, 163 insertions(+), 5 deletions(-)
New commits: commit 79baafccf3d390810f516b2cf9cb3ad2b4e9e63b Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Fri May 27 11:38:42 2022 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Fri May 27 12:11:46 2022 +0200 sw content controls, date: add current date handling While working on the DOCX import for dates, it turns out there is a need to store the selected date in machine-readable format as well. This is useful, because once the timestamp is formatted, the user is allowed to hand-edit the result, so otherwise the selected date would be lost. This commit adds: - doc model & UNO API - click handler (store the selected date, default to the current date in the date picker if possible) - ODT filter - DOCX export And tests for all these. Change-Id: I00f4e87ebfe0e8a19486367c32d472ccd2ff16a8 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135035 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng index 0f2ea32c300e..d5857c132e37 100644 --- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng +++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng @@ -2887,6 +2887,11 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1. <rng:ref name="language"/> </rng:attribute> </rng:optional> + <rng:optional> + <rng:attribute name="loext:current-date"> + <rng:ref name="string"/> + </rng:attribute> + </rng:optional> <rng:zeroOrMore> <rng:element name="loext:list-item"> <rng:attribute name="loext:display-text"> diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx index b7f3c02f9f04..36f0b01a527e 100644 --- a/sw/inc/formatcontentcontrol.hxx +++ b/sw/inc/formatcontentcontrol.hxx @@ -136,6 +136,9 @@ class SW_DLLPUBLIC SwContentControl : public sw::BroadcastingModify /// If m_bDate is true, the date's BCP 47 language tag. OUString m_aDateLanguage; + /// Date in YYYY-MM-DDT00:00:00Z format. + OUString m_aCurrentDate; + /// Stores a list item index, in case the doc model is not yet updated. std::optional<size_t> m_oSelectedListItem; @@ -217,6 +220,16 @@ public: OUString GetDateLanguage() const { return m_aDateLanguage; } + void SetCurrentDate(const OUString& rCurrentDate) { m_aCurrentDate = rCurrentDate; } + + OUString GetCurrentDate() const { return m_aCurrentDate; } + + /// Formats fCurrentDate and sets it. + void SetCurrentDateValue(double fCurrentDate); + + /// Parses m_aCurrentDate and returns it. + double GetCurrentDateValue() const; + /// Formats m_oSelectedDate, taking m_aDateFormat and m_aDateLanguage into account. OUString GetDateString() const; diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx index 6e3330e65126..90921cbc655d 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -882,6 +882,7 @@ #define UNO_NAME_PICTURE "Picture" #define UNO_NAME_DATE_FORMAT "DateFormat" #define UNO_NAME_DATE_LANGUAGE "DateLanguage" +#define UNO_NAME_CURRENT_DATE "CurrentDate" #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 a998810e6029..1ccd8b250ec3 100644 --- a/sw/qa/core/unocore/unocore.cxx +++ b/sw/qa/core/unocore/unocore.cxx @@ -601,6 +601,8 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testContentControlDate) xContentControlProps->setPropertyValue("Date", uno::Any(true)); xContentControlProps->setPropertyValue("DateFormat", uno::Any(OUString("M/d/yyyy"))); xContentControlProps->setPropertyValue("DateLanguage", uno::Any(OUString("en-US"))); + xContentControlProps->setPropertyValue("CurrentDate", + uno::Any(OUString("2022-05-25T00:00:00Z"))); xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); // Then make sure that the specified properties are set: @@ -614,6 +616,7 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testContentControlDate) CPPUNIT_ASSERT(pContentControl->GetDate()); CPPUNIT_ASSERT_EQUAL(OUString("M/d/yyyy"), pContentControl->GetDateFormat()); CPPUNIT_ASSERT_EQUAL(OUString("en-US"), pContentControl->GetDateLanguage()); + CPPUNIT_ASSERT_EQUAL(OUString("2022-05-25T00:00:00Z"), pContentControl->GetCurrentDate()); } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/uibase/wrtsh/wrtsh.cxx b/sw/qa/uibase/wrtsh/wrtsh.cxx index 06bb8a45edc1..706e1c6653a7 100644 --- a/sw/qa/uibase/wrtsh/wrtsh.cxx +++ b/sw/qa/uibase/wrtsh/wrtsh.cxx @@ -343,6 +343,8 @@ CPPUNIT_TEST_FIXTURE(Test, testSelectDateContentControl) // - Actual : test // i.e. the content control was not updated. CPPUNIT_ASSERT_EQUAL(OUString("2022-05-24"), pTextNode->GetExpandText(pWrtShell->GetLayout())); + CPPUNIT_ASSERT_EQUAL(OUString("2022-05-24T00:00:00Z"), + rFormatContentControl.GetContentControl()->GetCurrentDate()); } } diff --git a/sw/source/core/crsr/datecontentcontrolbutton.cxx b/sw/source/core/crsr/datecontentcontrolbutton.cxx index c52f546e9c6f..9de971b6f9f6 100644 --- a/sw/source/core/crsr/datecontentcontrolbutton.cxx +++ b/sw/source/core/crsr/datecontentcontrolbutton.cxx @@ -45,6 +45,18 @@ void SwDateContentControlButton::LaunchPopup() "modules/swriter/ui/contentcontrolcalendar.ui"); m_xPopup = m_xPopupBuilder->weld_popover("Calendar"); m_xCalendar = m_xPopupBuilder->weld_calendar("date"); + + // Read the doc model. + if (m_pContentControl) + { + const Date& rNullDate = m_pNumberFormatter->GetNullDate(); + double fCurrentDate = m_pContentControl->GetCurrentDateValue(); + if (fCurrentDate != 0) + { + m_xCalendar->set_date(rNullDate + sal_Int32(fCurrentDate)); + } + } + m_xCalendar->connect_activated(LINK(this, SwDateContentControlButton, SelectHandler)); SwContentControlButton::LaunchPopup(); m_xCalendar->grab_focus(); diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx b/sw/source/core/txtnode/attrcontentcontrol.cxx index 06dc388ee1ef..529a3ea9e331 100644 --- a/sw/source/core/txtnode/attrcontentcontrol.cxx +++ b/sw/source/core/txtnode/attrcontentcontrol.cxx @@ -32,6 +32,11 @@ using namespace com::sun::star; +namespace +{ +inline constexpr OUStringLiteral CURRENT_DATE_FORMAT = u"YYYY-MM-DD"; +} + SwFormatContentControl* SwFormatContentControl::CreatePoolDefault(sal_uInt16 nWhich) { return new SwFormatContentControl(nWhich); @@ -218,7 +223,7 @@ OUString SwContentControl::GetDateString() const if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) { - // Try to find a format based on just the language. + // If not found, then create it. sal_Int32 nCheckPos = 0; SvNumFormatType nType; OUString aFormat = m_aDateFormat; @@ -242,6 +247,60 @@ OUString SwContentControl::GetDateString() const return aFormatted; } +void SwContentControl::SetCurrentDateValue(double fCurrentDate) +{ + SwDoc& rDoc = m_pTextNode->GetDoc(); + SvNumberFormatter* pNumberFormatter = rDoc.GetNumberFormatter(); + OUString aFormatted; + sal_uInt32 nFormat = pNumberFormatter->GetEntryKey(CURRENT_DATE_FORMAT, LANGUAGE_ENGLISH_US); + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + // If not found, then create it. + sal_Int32 nCheckPos = 0; + SvNumFormatType nType; + OUString sFormat = CURRENT_DATE_FORMAT; + pNumberFormatter->PutEntry(sFormat, nCheckPos, nType, nFormat, LANGUAGE_ENGLISH_US); + } + + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + return; + } + + const Color* pColor = nullptr; + pNumberFormatter->GetOutputString(fCurrentDate, nFormat, aFormatted, &pColor, false); + m_aCurrentDate = aFormatted + "T00:00:00Z"; +} + +double SwContentControl::GetCurrentDateValue() const +{ + if (m_aCurrentDate.isEmpty()) + { + return 0; + } + + SwDoc& rDoc = m_pTextNode->GetDoc(); + SvNumberFormatter* pNumberFormatter = rDoc.GetNumberFormatter(); + sal_uInt32 nFormat = pNumberFormatter->GetEntryKey(CURRENT_DATE_FORMAT, LANGUAGE_ENGLISH_US); + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + sal_Int32 nCheckPos = 0; + SvNumFormatType nType; + OUString sFormat = CURRENT_DATE_FORMAT; + pNumberFormatter->PutEntry(sFormat, nCheckPos, nType, nFormat, LANGUAGE_ENGLISH_US); + } + + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + return 0; + } + + double dCurrentDate = 0; + OUString aCurrentDate = m_aCurrentDate.replaceAll("T00:00:00Z", ""); + pNumberFormatter->IsNumberFormat(aCurrentDate, nFormat, dCurrentDate); + return dCurrentDate; +} + void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const { (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwContentControl")); @@ -265,6 +324,8 @@ void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const BAD_CAST(m_aDateFormat.toUtf8().getStr())); (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date-language"), BAD_CAST(m_aDateLanguage.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("current-date"), + BAD_CAST(m_aCurrentDate.toUtf8().getStr())); if (!m_aListItems.empty()) { diff --git a/sw/source/core/unocore/unocontentcontrol.cxx b/sw/source/core/unocore/unocontentcontrol.cxx index e780a57e943c..493e287296e3 100644 --- a/sw/source/core/unocore/unocontentcontrol.cxx +++ b/sw/source/core/unocore/unocontentcontrol.cxx @@ -165,6 +165,7 @@ public: bool m_bDate; OUString m_aDateFormat; OUString m_aDateLanguage; + OUString m_aCurrentDate; Impl(SwXContentControl& rThis, SwDoc& rDoc, SwContentControl* pContentControl, const uno::Reference<text::XText>& xParentText, @@ -529,6 +530,7 @@ void SwXContentControl::AttachImpl(const uno::Reference<text::XTextRange>& xText pContentControl->SetDate(m_pImpl->m_bDate); pContentControl->SetDateFormat(m_pImpl->m_aDateFormat); pContentControl->SetDateLanguage(m_pImpl->m_aDateLanguage); + pContentControl->SetCurrentDate(m_pImpl->m_aCurrentDate); SwFormatContentControl aContentControl(pContentControl, nWhich); bool bSuccess @@ -828,6 +830,21 @@ void SAL_CALL SwXContentControl::setPropertyValue(const OUString& rPropertyName, } } } + else if (rPropertyName == UNO_NAME_CURRENT_DATE) + { + OUString aValue; + if (rValue >>= aValue) + { + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_aCurrentDate = aValue; + } + else + { + m_pImpl->m_pContentControl->SetCurrentDate(aValue); + } + } + } else { throw beans::UnknownPropertyException(); @@ -951,6 +968,17 @@ uno::Any SAL_CALL SwXContentControl::getPropertyValue(const OUString& rPropertyN aRet <<= m_pImpl->m_pContentControl->GetDateLanguage(); } } + else if (rPropertyName == UNO_NAME_CURRENT_DATE) + { + if (m_pImpl->m_bIsDescriptor) + { + aRet <<= m_pImpl->m_aCurrentDate; + } + else + { + aRet <<= m_pImpl->m_pContentControl->GetCurrentDate(); + } + } else { throw beans::UnknownPropertyException(); diff --git a/sw/source/core/unocore/unomap1.cxx b/sw/source/core/unocore/unomap1.cxx index 867faf11aeaf..d61e598590a4 100644 --- a/sw/source/core/unocore/unomap1.cxx +++ b/sw/source/core/unocore/unomap1.cxx @@ -1031,7 +1031,7 @@ const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetContentControlProper { u"" UNO_NAME_PICTURE, 0, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, { u"" UNO_NAME_DATE, 0, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, { u"" UNO_NAME_DATE_FORMAT, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, - { u"" UNO_NAME_DATE_LANGUAGE, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { u"" UNO_NAME_CURRENT_DATE, 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, { u"", 0, css::uno::Type(), 0, 0 } }; diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 3d9835ef410a..b46c6ad3354c 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -2374,7 +2374,15 @@ void DocxAttributeOutput::WriteContentControlStart() if (m_pContentControl->GetDate()) { - m_pSerializer->startElementNS(XML_w, XML_date); + OUString aCurrentDate = m_pContentControl->GetCurrentDate(); + if (aCurrentDate.isEmpty()) + { + m_pSerializer->startElementNS(XML_w, XML_date); + } + else + { + m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), aCurrentDate); + } OUString aDateFormat = m_pContentControl->GetDateFormat(); if (!aDateFormat.isEmpty()) { diff --git a/sw/source/uibase/wrtsh/wrtsh3.cxx b/sw/source/uibase/wrtsh/wrtsh3.cxx index f01946e45d17..e4b594770282 100644 --- a/sw/source/uibase/wrtsh/wrtsh3.cxx +++ b/sw/source/uibase/wrtsh/wrtsh3.cxx @@ -190,9 +190,12 @@ bool SwWrtShell::GotoContentControl(const SwFormatContentControl& rContentContro aRewriter.AddRule(UndoArg3, SwResId(STR_START_QUOTE) + aNewState + SwResId(STR_END_QUOTE)); GetIDocumentUndoRedo().StartUndo(SwUndoId::REPLACE, &aRewriter); + // Write the doc model. + pContentControl->SetCurrentDateValue(*pContentControl->GetSelectedDate()); + pContentControl->SetSelectedDate(std::nullopt); + // Update the content. DelLeft(); - pContentControl->SetSelectedDate(std::nullopt); Insert(aNewState); GetIDocumentUndoRedo().EndUndo(SwUndoId::REPLACE, &aRewriter); diff --git a/xmloff/qa/unit/data/content-control-date.fodt b/xmloff/qa/unit/data/content-control-date.fodt index dd3749a02e99..c49e51339c3b 100644 --- a/xmloff/qa/unit/data/content-control-date.fodt +++ b/xmloff/qa/unit/data/content-control-date.fodt @@ -2,7 +2,7 @@ <office:document xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> <office:body> <office:text> - <text:p><loext:content-control loext:date="true" loext:date-format="YYYY-MM-DD" loext:date-rfc-language-tag="en-US">choose a date</loext:content-control></text:p> + <text:p><loext:content-control loext:date="true" loext:date-format="YYYY-MM-DD" loext:date-rfc-language-tag="en-US" loext:current-date="2022-05-25T00:00:00Z">choose a date</loext:content-control></text:p> </office:text> </office:body> </office:document> diff --git a/xmloff/qa/unit/text.cxx b/xmloff/qa/unit/text.cxx index 303f31c4ca40..2aed79376fe0 100644 --- a/xmloff/qa/unit/text.cxx +++ b/xmloff/qa/unit/text.cxx @@ -732,6 +732,8 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testDateContentControlExport) xContentControlProps->setPropertyValue("Date", uno::Any(true)); xContentControlProps->setPropertyValue("DateFormat", uno::Any(OUString("YYYY-MM-DD"))); xContentControlProps->setPropertyValue("DateLanguage", uno::Any(OUString("en-US"))); + xContentControlProps->setPropertyValue("CurrentDate", + uno::Any(OUString("2022-05-25T00:00:00Z"))); xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); // When exporting to ODT: @@ -752,6 +754,7 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testDateContentControlExport) assertXPath(pXmlDoc, "//loext:content-control", "date", "true"); assertXPath(pXmlDoc, "//loext:content-control", "date-format", "YYYY-MM-DD"); assertXPath(pXmlDoc, "//loext:content-control", "date-rfc-language-tag", "en-US"); + assertXPath(pXmlDoc, "//loext:content-control", "current-date", "2022-05-25T00:00:00Z"); } CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testDateContentControlImport) @@ -788,6 +791,9 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testDateContentControlImport) OUString aDateLanguage; xContentControlProps->getPropertyValue("DateLanguage") >>= aDateLanguage; CPPUNIT_ASSERT_EQUAL(OUString("en-US"), aDateLanguage); + OUString aCurrentDate; + xContentControlProps->getPropertyValue("CurrentDate") >>= aCurrentDate; + CPPUNIT_ASSERT_EQUAL(OUString("2022-05-25T00:00:00Z"), aCurrentDate); } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/xmloff/source/text/txtparae.cxx b/xmloff/source/text/txtparae.cxx index 4c4537db064a..5d9bd3768d01 100644 --- a/xmloff/source/text/txtparae.cxx +++ b/xmloff/source/text/txtparae.cxx @@ -3966,6 +3966,12 @@ void XMLTextParagraphExport::ExportContentControl( { GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_DATE_RFC_LANGUAGE_TAG, aDateLanguage); } + OUString aCurrentDate; + xPropertySet->getPropertyValue("CurrentDate") >>= aCurrentDate; + if (!aCurrentDate.isEmpty()) + { + GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_CURRENT_DATE, aCurrentDate); + } } SvXMLElementExport aElem(GetExport(), bExport, XML_NAMESPACE_LO_EXT, XML_CONTENT_CONTROL, false, diff --git a/xmloff/source/text/xmlcontentcontrolcontext.cxx b/xmloff/source/text/xmlcontentcontrolcontext.cxx index dbe6b824504c..a597a28a686c 100644 --- a/xmloff/source/text/xmlcontentcontrolcontext.cxx +++ b/xmloff/source/text/xmlcontentcontrolcontext.cxx @@ -112,6 +112,11 @@ void XMLContentControlContext::startFastElement( m_aDateLanguage = rIter.toString(); break; } + case XML_ELEMENT(LO_EXT, XML_CURRENT_DATE): + { + m_aCurrentDate = rIter.toString(); + break; + } default: XMLOFF_WARN_UNKNOWN("xmloff", rIter); } @@ -193,6 +198,10 @@ void XMLContentControlContext::endFastElement(sal_Int32) { xPropertySet->setPropertyValue("DateLanguage", uno::Any(m_aDateLanguage)); } + if (!m_aCurrentDate.isEmpty()) + { + xPropertySet->setPropertyValue("CurrentDate", uno::Any(m_aCurrentDate)); + } } css::uno::Reference<css::xml::sax::XFastContextHandler> diff --git a/xmloff/source/text/xmlcontentcontrolcontext.hxx b/xmloff/source/text/xmlcontentcontrolcontext.hxx index 623ef97e8df3..2c3ecfb9cafb 100644 --- a/xmloff/source/text/xmlcontentcontrolcontext.hxx +++ b/xmloff/source/text/xmlcontentcontrolcontext.hxx @@ -47,6 +47,7 @@ class XMLContentControlContext : public SvXMLImportContext bool m_bDate = false; OUString m_aDateFormat; OUString m_aDateLanguage; + OUString m_aCurrentDate; public: XMLContentControlContext(SvXMLImport& rImport, sal_Int32 nElement, XMLHints_Impl& rHints,