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 <vmiklos@collabora.com>
Tested-by: Jenkins
This commit is contained in:
Miklos Vajna
2022-05-27 12:11:46 +02:00
parent bd6cb0d821
commit 79baafccf3
16 changed files with 163 additions and 5 deletions
@@ -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">
+13
View File
@@ -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;
+1
View File
@@ -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: */
+3
View File
@@ -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();
+2
View File
@@ -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());
}
}
@@ -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();
+62 -1
View File
@@ -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())
{
@@ -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();
+1 -1
View File
@@ -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 }
};
+9 -1
View File
@@ -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())
{
+4 -1
View File
@@ -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);
@@ -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>
+6
View File
@@ -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();
+6
View File
@@ -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,
@@ -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>
@@ -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,