From ce5d609da9d20b3c91f6f8eb4ee88451cbd55a9d Mon Sep 17 00:00:00 2001
From: Miklos Vajna <vmiklos@collabora.com>
Date: Wed, 25 May 2022 09:58:28 +0200
Subject: sw content controls, date: show a date picker on click

- add a new SwContentControl::GetDateString() that knows how to produce
  a formatted date, taking the language and the date format into account

- add a new SwDateContentControlButton that knows how to open popup a
  calendar on click and that puts the selected date into
  SwContentControl::m_oSelectedDate

- extend SwWrtShell::GotoContentControl() to consume that selected date
  & update the document text accordingly

- in case SwSelPaintRects::HighlightContentControl() notices a date
  content control, create an instance of this newly introduced
  SwDateContentControlButton

Change-Id: Ia2cb0fa3aefbf543b8dc2e96bcebb41408eb12c3
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/134926
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins
---
 sw/Library_sw.mk                                 |  1 +
 sw/UIConfig_swriter.mk                           |  1 +
 sw/inc/formatcontentcontrol.hxx                  | 10 ++++
 sw/qa/core/unocore/unocore.cxx                   |  7 +--
 sw/qa/uibase/wrtsh/wrtsh.cxx                     | 37 +++++++++++++++
 sw/source/core/crsr/crstrvl.cxx                  |  2 +-
 sw/source/core/crsr/datecontentcontrolbutton.cxx | 59 ++++++++++++++++++++++++
 sw/source/core/crsr/viscrs.cxx                   | 21 +++++++++
 sw/source/core/inc/datecontentcontrolbutton.hxx  | 41 ++++++++++++++++
 sw/source/core/txtnode/attrcontentcontrol.cxx    | 35 ++++++++++++++
 sw/source/uibase/wrtsh/wrtsh3.cxx                | 21 +++++++++
 sw/uiconfig/swriter/ui/contentcontrolcalendar.ui | 28 +++++++++++
 12 files changed, 256 insertions(+), 7 deletions(-)
 create mode 100644 sw/source/core/crsr/datecontentcontrolbutton.cxx
 create mode 100644 sw/source/core/inc/datecontentcontrolbutton.hxx
 create mode 100644 sw/uiconfig/swriter/ui/contentcontrolcalendar.ui

diff --git a/sw/Library_sw.mk b/sw/Library_sw.mk
index eec16fd5612c..22694869fb54 100644
--- a/sw/Library_sw.mk
+++ b/sw/Library_sw.mk
@@ -164,6 +164,7 @@ $(eval $(call gb_Library_add_exception_objects,sw,\
     sw/source/core/crsr/crstrvl1 \
     sw/source/core/crsr/DateFormFieldButton \
     sw/source/core/crsr/DropDownFormFieldButton \
+    sw/source/core/crsr/datecontentcontrolbutton \
     sw/source/core/crsr/dropdowncontentcontrolbutton \
     sw/source/core/crsr/findattr \
     sw/source/core/crsr/findcoll \
diff --git a/sw/UIConfig_swriter.mk b/sw/UIConfig_swriter.mk
index 012d86008b39..5dafec57ee88 100644
--- a/sw/UIConfig_swriter.mk
+++ b/sw/UIConfig_swriter.mk
@@ -121,6 +121,7 @@ $(eval $(call gb_UIConfig_add_uifiles,modules/swriter,\
 	sw/uiconfig/swriter/ui/combobox \
 	sw/uiconfig/swriter/ui/comboboxfragment \
 	sw/uiconfig/swriter/ui/conditionpage \
+	sw/uiconfig/swriter/ui/contentcontrolcalendar \
 	sw/uiconfig/swriter/ui/contentcontroldlg \
 	sw/uiconfig/swriter/ui/contentcontroldropdown \
 	sw/uiconfig/swriter/ui/contentcontrollistitemdlg \
diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx
index 6e3da73412ba..5137037df62a 100644
--- a/sw/inc/formatcontentcontrol.hxx
+++ b/sw/inc/formatcontentcontrol.hxx
@@ -137,6 +137,9 @@ class SW_DLLPUBLIC SwContentControl : public sw::BroadcastingModify
     /// Stores a list item index, in case the doc model is not yet updated.
     std::optional<size_t> m_oSelectedListItem;
 
+    /// Stores a date timestamp, in case the doc model is not yet updated.
+    std::optional<double> m_oSelectedDate;
+
 public:
     SwTextContentControl* GetTextAttr() const;
 
@@ -212,6 +215,9 @@ public:
 
     OUString GetDateLanguage() const { return m_aDateLanguage; }
 
+    /// Formats m_oSelectedDate, taking m_aDateFormat and m_aDateLanguage into account.
+    OUString GetDateString() const;
+
     void SetSelectedListItem(std::optional<size_t> oSelectedListItem)
     {
         m_oSelectedListItem = oSelectedListItem;
@@ -219,6 +225,10 @@ public:
 
     std::optional<size_t> GetSelectedListItem() const { return m_oSelectedListItem; }
 
+    void SetSelectedDate(std::optional<double> oSelectedDate) { m_oSelectedDate = oSelectedDate; }
+
+    std::optional<double> GetSelectedDate() const { return m_oSelectedDate; }
+
     virtual void dumpAsXml(xmlTextWriterPtr pWriter) const;
 };
 
diff --git a/sw/qa/core/unocore/unocore.cxx b/sw/qa/core/unocore/unocore.cxx
index 16959a06f377..a998810e6029 100644
--- a/sw/qa/core/unocore/unocore.cxx
+++ b/sw/qa/core/unocore/unocore.cxx
@@ -590,12 +590,7 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testContentControlDate)
     uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
     uno::Reference<text::XText> xText = xTextDocument->getText();
     uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
-    uno::Reference<beans::XPropertySet> xTextGraphic(
-        xMSF->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY);
-    xTextGraphic->setPropertyValue("AnchorType",
-                                   uno::Any(text::TextContentAnchorType_AS_CHARACTER));
-    uno::Reference<text::XTextContent> xTextContent(xTextGraphic, uno::UNO_QUERY);
-    xText->insertTextContent(xCursor, xTextContent, false);
+    xText->insertString(xCursor, "test", /*bAbsorb=*/false);
     xCursor->gotoStart(/*bExpand=*/false);
     xCursor->gotoEnd(/*bExpand=*/true);
     uno::Reference<text::XTextContent> xContentControl(
diff --git a/sw/qa/uibase/wrtsh/wrtsh.cxx b/sw/qa/uibase/wrtsh/wrtsh.cxx
index 65a41086dce4..06bb8a45edc1 100644
--- a/sw/qa/uibase/wrtsh/wrtsh.cxx
+++ b/sw/qa/uibase/wrtsh/wrtsh.cxx
@@ -307,6 +307,43 @@ CPPUNIT_TEST_FIXTURE(Test, testInsertPictureContentControl)
     CPPUNIT_ASSERT(pContentControl->GetPicture());
     CPPUNIT_ASSERT(pTextNode->GetTextAttrForCharAt(1, RES_TXTATR_FLYCNT));
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testSelectDateContentControl)
+{
+    // Given a document with a date content control:
+    SwDoc* pDoc = createSwDoc();
+    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, "test", /*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);
+
+    // When clicking on that content control:
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    SwTextNode* pTextNode = pWrtShell->GetCursor()->GetNode().GetTextNode();
+    SwTextAttr* pAttr = pTextNode->GetTextAttrForCharAt(0, RES_TXTATR_CONTENTCONTROL);
+    auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr);
+    auto& rFormatContentControl
+        = static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr());
+    rFormatContentControl.GetContentControl()->SetSelectedDate(44705);
+    pWrtShell->GotoContentControl(rFormatContentControl);
+
+    // Then make sure that the document text is updated:
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 2022-05-24
+    // - Actual  : test
+    // i.e. the content control was not updated.
+    CPPUNIT_ASSERT_EQUAL(OUString("2022-05-24"), pTextNode->GetExpandText(pWrtShell->GetLayout()));
+}
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/core/crsr/crstrvl.cxx b/sw/source/core/crsr/crstrvl.cxx
index e36cf1671007..ee12399323ac 100644
--- a/sw/source/core/crsr/crstrvl.cxx
+++ b/sw/source/core/crsr/crstrvl.cxx
@@ -858,7 +858,7 @@ bool SwCursorShell::GotoFormatContentControl(const SwFormatContentControl& rCont
     bool bRet = false;
     std::shared_ptr<SwContentControl> pContentControl = rContentControl.GetContentControl();
     if (!pContentControl->GetShowingPlaceHolder() && !pContentControl->GetCheckbox()
-        && !pContentControl->GetSelectedListItem())
+        && !pContentControl->GetSelectedListItem() && !pContentControl->GetSelectedDate())
     {
         return bRet;
     }
diff --git a/sw/source/core/crsr/datecontentcontrolbutton.cxx b/sw/source/core/crsr/datecontentcontrolbutton.cxx
new file mode 100644
index 000000000000..c52f546e9c6f
--- /dev/null
+++ b/sw/source/core/crsr/datecontentcontrolbutton.cxx
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <datecontentcontrolbutton.hxx>
+
+#include <svl/numformat.hxx>
+#include <tools/date.hxx>
+#include <vcl/svapp.hxx>
+
+#include <edtwin.hxx>
+#include <formatcontentcontrol.hxx>
+#include <view.hxx>
+#include <wrtsh.hxx>
+
+IMPL_LINK(SwDateContentControlButton, SelectHandler, weld::Calendar&, rCalendar, void)
+{
+    const Date& rNullDate = m_pNumberFormatter->GetNullDate();
+    double fDate = rCalendar.get_date() - rNullDate;
+    m_xPopup->popdown();
+    m_pContentControl->SetSelectedDate(fDate);
+    SwView& rView = static_cast<SwEditWin*>(GetParent())->GetView();
+    SwWrtShell& rWrtShell = rView.GetWrtShell();
+    rWrtShell.GotoContentControl(*m_pContentControl->GetFormatContentControl());
+}
+
+SwDateContentControlButton::SwDateContentControlButton(
+    SwEditWin* pEditWin, const std::shared_ptr<SwContentControl>& pContentControl,
+    SvNumberFormatter* pNumberFormatter)
+    : SwContentControlButton(pEditWin, pContentControl)
+    , m_pNumberFormatter(pNumberFormatter)
+{
+}
+
+SwDateContentControlButton::~SwDateContentControlButton() { disposeOnce(); }
+
+void SwDateContentControlButton::LaunchPopup()
+{
+    m_xPopupBuilder = Application::CreateBuilder(GetFrameWeld(),
+                                                 "modules/swriter/ui/contentcontrolcalendar.ui");
+    m_xPopup = m_xPopupBuilder->weld_popover("Calendar");
+    m_xCalendar = m_xPopupBuilder->weld_calendar("date");
+    m_xCalendar->connect_activated(LINK(this, SwDateContentControlButton, SelectHandler));
+    SwContentControlButton::LaunchPopup();
+    m_xCalendar->grab_focus();
+}
+
+void SwDateContentControlButton::DestroyPopup()
+{
+    m_xCalendar.reset();
+    SwContentControlButton::DestroyPopup();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/viscrs.cxx b/sw/source/core/crsr/viscrs.cxx
index 6991ee8a7a95..9863e1a75324 100644
--- a/sw/source/core/crsr/viscrs.cxx
+++ b/sw/source/core/crsr/viscrs.cxx
@@ -63,6 +63,7 @@
 #include <wrtsh.hxx>
 #include <textcontentcontrol.hxx>
 #include <dropdowncontentcontrolbutton.hxx>
+#include <datecontentcontrolbutton.hxx>
 
 // Here static members are defined. They will get changed on alteration of the
 // MapMode. This is done so that on ShowCursor the same size does not have to be
@@ -753,6 +754,26 @@ void SwSelPaintRects::HighlightContentControl()
                 m_pContentControlButton->Show();
             }
         }
+        if (pContentControl && pContentControl->GetDate())
+        {
+            auto pWrtShell = dynamic_cast<const SwWrtShell*>(GetShell());
+            if (pWrtShell)
+            {
+                auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
+                if (m_pContentControlButton
+                    && m_pContentControlButton->GetContentControl() != pContentControl)
+                {
+                    m_pContentControlButton.disposeAndClear();
+                }
+                if (!m_pContentControlButton)
+                {
+                    m_pContentControlButton = VclPtr<SwDateContentControlButton>::Create(
+                        &rEditWin, pContentControl, pWrtShell->GetDoc()->GetNumberFormatter());
+                }
+                m_pContentControlButton->CalcPosAndSize(aLastPortionPaintArea);
+                m_pContentControlButton->Show();
+            }
+        }
     }
     else
     {
diff --git a/sw/source/core/inc/datecontentcontrolbutton.hxx b/sw/source/core/inc/datecontentcontrolbutton.hxx
new file mode 100644
index 000000000000..48b08f2db981
--- /dev/null
+++ b/sw/source/core/inc/datecontentcontrolbutton.hxx
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include "contentcontrolbutton.hxx"
+
+class SwEditWin;
+class SvNumberFormatter;
+class SwContentControl;
+
+/**
+ * This button is shown when the cursor is on a date content control.  The user can select a date
+ * from a date picker.
+ */
+class SwDateContentControlButton final : public SwContentControlButton
+{
+private:
+    SvNumberFormatter* m_pNumberFormatter;
+
+    std::unique_ptr<weld::Calendar> m_xCalendar;
+
+    DECL_LINK(SelectHandler, weld::Calendar&, void);
+
+public:
+    SwDateContentControlButton(SwEditWin* pEditWin,
+                               const std::shared_ptr<SwContentControl>& pContentControl,
+                               SvNumberFormatter* pNumberFormatter);
+    ~SwDateContentControlButton() override;
+
+    void LaunchPopup() override;
+    void DestroyPopup() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx b/sw/source/core/txtnode/attrcontentcontrol.cxx
index 7eb39907ce0c..06dc388ee1ef 100644
--- a/sw/source/core/txtnode/attrcontentcontrol.cxx
+++ b/sw/source/core/txtnode/attrcontentcontrol.cxx
@@ -24,9 +24,11 @@
 #include <sal/log.hxx>
 #include <comphelper/propertyvalue.hxx>
 #include <comphelper/sequenceashashmap.hxx>
+#include <svl/numformat.hxx>
 
 #include <ndtxt.hxx>
 #include <textcontentcontrol.hxx>
+#include <doc.hxx>
 
 using namespace com::sun::star;
 
@@ -207,6 +209,39 @@ void SwContentControl::SwClientNotify(const SwModify&, const SfxHint& rHint)
     }
 }
 
+OUString SwContentControl::GetDateString() const
+{
+    SwDoc& rDoc = m_pTextNode->GetDoc();
+    SvNumberFormatter* pNumberFormatter = rDoc.GetNumberFormatter();
+    sal_uInt32 nFormat = pNumberFormatter->GetEntryKey(
+        m_aDateFormat, LanguageTag(m_aDateLanguage).getLanguageType());
+
+    if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+    {
+        // Try to find a format based on just the language.
+        sal_Int32 nCheckPos = 0;
+        SvNumFormatType nType;
+        OUString aFormat = m_aDateFormat;
+        pNumberFormatter->PutEntry(aFormat, nCheckPos, nType, nFormat,
+                                   LanguageTag(m_aDateLanguage).getLanguageType());
+    }
+
+    const Color* pColor = nullptr;
+    OUString aFormatted;
+    if (!m_oSelectedDate)
+    {
+        return OUString();
+    }
+
+    if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+    {
+        return OUString();
+    }
+
+    pNumberFormatter->GetOutputString(*m_oSelectedDate, nFormat, aFormatted, &pColor, false);
+    return aFormatted;
+}
+
 void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const
 {
     (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwContentControl"));
diff --git a/sw/source/uibase/wrtsh/wrtsh3.cxx b/sw/source/uibase/wrtsh/wrtsh3.cxx
index fcb123662bcf..f01946e45d17 100644
--- a/sw/source/uibase/wrtsh/wrtsh3.cxx
+++ b/sw/source/uibase/wrtsh/wrtsh3.cxx
@@ -178,6 +178,27 @@ bool SwWrtShell::GotoContentControl(const SwFormatContentControl& rContentContro
         LockView(/*bViewLocked=*/false);
         ShowCursor();
     }
+    else if (bRet && pContentControl && pContentControl->GetSelectedDate())
+    {
+        // Date: GotoFormatContentControl() selected the old content.
+        LockView(/*bViewLocked=*/true);
+        OUString aOldState = GetCursorDescr();
+        OUString aNewState = pContentControl->GetDateString();
+        SwRewriter aRewriter;
+        aRewriter.AddRule(UndoArg1, aOldState);
+        aRewriter.AddRule(UndoArg2, SwResId(STR_YIELDS));
+        aRewriter.AddRule(UndoArg3, SwResId(STR_START_QUOTE) + aNewState + SwResId(STR_END_QUOTE));
+        GetIDocumentUndoRedo().StartUndo(SwUndoId::REPLACE, &aRewriter);
+
+        // Update the content.
+        DelLeft();
+        pContentControl->SetSelectedDate(std::nullopt);
+        Insert(aNewState);
+
+        GetIDocumentUndoRedo().EndUndo(SwUndoId::REPLACE, &aRewriter);
+        LockView(/*bViewLocked=*/false);
+        ShowCursor();
+    }
 
     if (bRet && IsSelFrameMode())
     {
diff --git a/sw/uiconfig/swriter/ui/contentcontrolcalendar.ui b/sw/uiconfig/swriter/ui/contentcontrolcalendar.ui
new file mode 100644
index 000000000000..e5355f723621
--- /dev/null
+++ b/sw/uiconfig/swriter/ui/contentcontrolcalendar.ui
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface domain="sw">
+  <requires lib="gtk+" version="3.20"/>
+  <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>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
-- 
cgit