writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx              |   46 ++++++++
 writerfilter/qa/cppunittests/dmapper/data/sdt-run-checkbox.docx |binary
 writerfilter/source/dmapper/DomainMapper.cxx                    |   57 
+++++++++-
 writerfilter/source/dmapper/DomainMapper_Impl.cxx               |   21 +++
 writerfilter/source/dmapper/DomainMapper_Impl.hxx               |    4 
 writerfilter/source/dmapper/SdtHelper.cxx                       |   18 +++
 writerfilter/source/dmapper/SdtHelper.hxx                       |   23 +++-
 writerfilter/source/dmapper/TextEffectsHandler.cxx              |    1 
 writerfilter/source/dmapper/TextEffectsHandler.hxx              |    8 -
 writerfilter/source/ooxml/model.xml                             |    7 -
 10 files changed, 172 insertions(+), 13 deletions(-)

New commits:
commit c53d3a1f4b8430507d54f5fac336870df7a700af
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Fri Apr 29 08:12:25 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Apr 29 09:03:01 2022 +0200

    sw content controls, checkbox: add DOCX import
    
    My expectation was that <w14:checked w14:val="0"/> would be mapped to a
    single SPRM where the int value is 0 or 1 depending on if this is a true
    or false boolean. But the w14 tokenizer rules actually created a
    NS_ooxml::LN_CT_SdtCheckbox_checked token with a
    NS_ooxml::LN_CT_OnOff_val token in it, which itself again didn't contain
    just a bool but dedicated NS_ooxml::LN_ST_OnOff_true,
    NS_ooxml::LN_ST_OnOff_1, etc values.
    
    To make this more complicated, TextEffectsHandler even depends on this
    weird behavior.
    
    Bring the w14 rules closer to the "main" wml rules by folding the
    NS_ooxml::LN_CT_OnOff_val token into the parent token
    (NS_ooxml::LN_CT_SdtCheckbox_checked in this case), but leave the
    NS_ooxml::LN_ST_OnOff_* values unchanged for now.
    
    The rest of the changes are more straightforward: we now handle
    inline/run checkbox SDTs similar to rich text ones, i.e. map them to
    Writer content controls, rather than just doing a poor mapping to
    grab-bags.
    
    The main benefit here is that the checkbox type of Writer content
    controls actually change their value on mouse click, so it's possible to
    fill in such forms.
    
    Change-Id: Idbf49a8ff1843d5271f2836e5299c4387bb58e55
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133588
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx 
b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
index da2663b93409..c96cb604f91f 100644
--- a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
+++ b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
@@ -84,6 +84,52 @@ CPPUNIT_TEST_FIXTURE(Test, testSdtRunRichText)
     xContent->getPropertyValue("CharHeight") >>= fCharheight;
     CPPUNIT_ASSERT_EQUAL(24.f, fCharheight);
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testSdtRunCheckbox)
+{
+    // Given a document with a checkbox inline/run SDT:
+    OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + 
"sdt-run-checkbox.docx";
+
+    // When loading the document:
+    getComponent() = loadFromDesktop(aURL);
+
+    // Then make sure that the doc model has a clickable checkbox content 
control:
+    uno::Reference<text::XTextDocument> xTextDocument(getComponent(), 
uno::UNO_QUERY);
+    uno::Reference<container::XEnumerationAccess> 
xParaEnumAccess(xTextDocument->getText(),
+                                                                  
uno::UNO_QUERY);
+    uno::Reference<container::XEnumeration> xParaEnum = 
xParaEnumAccess->createEnumeration();
+    uno::Reference<container::XEnumerationAccess> 
xPara(xParaEnum->nextElement(), uno::UNO_QUERY);
+    uno::Reference<container::XEnumeration> xPortionEnum = 
xPara->createEnumeration();
+    uno::Reference<beans::XPropertySet> xPortion(xPortionEnum->nextElement(), 
uno::UNO_QUERY);
+    OUString aTextPortionType;
+    xPortion->getPropertyValue("TextPortionType") >>= aTextPortionType;
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: ContentControl
+    // - Actual  : Text
+    // i.e. the SDT was imported as plain text, making it hard to fill in 
checkboxes.
+    CPPUNIT_ASSERT_EQUAL(OUString("ContentControl"), aTextPortionType);
+    uno::Reference<text::XTextContent> xContentControl;
+    xPortion->getPropertyValue("ContentControl") >>= xContentControl;
+    uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, 
uno::UNO_QUERY);
+    bool bCheckbox{};
+    xContentControlProps->getPropertyValue("Checkbox") >>= bCheckbox;
+    CPPUNIT_ASSERT(bCheckbox);
+    bool bChecked{};
+    xContentControlProps->getPropertyValue("Checked") >>= bChecked;
+    CPPUNIT_ASSERT(bChecked);
+    OUString aCheckedState;
+    xContentControlProps->getPropertyValue("CheckedState") >>= aCheckedState;
+    CPPUNIT_ASSERT_EQUAL(OUString(u"☒"), aCheckedState);
+    OUString aUncheckedState;
+    xContentControlProps->getPropertyValue("UncheckedState") >>= 
aUncheckedState;
+    CPPUNIT_ASSERT_EQUAL(OUString(u"☐"), aUncheckedState);
+    uno::Reference<text::XTextRange> xContentControlRange(xContentControl, 
uno::UNO_QUERY);
+    uno::Reference<text::XText> xText = xContentControlRange->getText();
+    uno::Reference<container::XEnumerationAccess> xContentEnumAccess(xText, 
uno::UNO_QUERY);
+    uno::Reference<container::XEnumeration> xContentEnum = 
xContentEnumAccess->createEnumeration();
+    uno::Reference<text::XTextRange> xContent(xContentEnum->nextElement(), 
uno::UNO_QUERY);
+    CPPUNIT_ASSERT_EQUAL(OUString(u"☒"), xContent->getString());
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/writerfilter/qa/cppunittests/dmapper/data/sdt-run-checkbox.docx 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-checkbox.docx
new file mode 100644
index 000000000000..c6718b97c2a0
Binary files /dev/null and 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-checkbox.docx differ
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx 
b/writerfilter/source/dmapper/DomainMapper.cxx
index 61ff5a2c67fb..cd0031f107f6 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -1083,6 +1083,7 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
                 {
                     
m_pImpl->m_pSdtHelper->setControlType(SdtControlType::richText);
                     m_pImpl->PushSdt();
+                    break;
                 }
             }
             m_pImpl->SetSdt(true);
@@ -1094,6 +1095,7 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
                 switch (m_pImpl->m_pSdtHelper->getControlType())
                 {
                     case SdtControlType::richText:
+                    case SdtControlType::checkBox:
                         m_pImpl->PopSdt();
                         break;
                     default:
@@ -2771,6 +2773,20 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
     case NS_ooxml::LN_CT_SdtPlaceholder_docPart:
     case NS_ooxml::LN_CT_SdtPr_color:
     {
+        if (!m_pImpl->GetSdtStarts().empty())
+        {
+            if (nSprmId == NS_ooxml::LN_CT_SdtPr_checkbox)
+            {
+                
m_pImpl->m_pSdtHelper->setControlType(SdtControlType::checkBox);
+                writerfilter::Reference<Properties>::Pointer_t pProperties = 
rSprm.getProps();
+                if (pProperties)
+                {
+                    pProperties->resolve(*this);
+                }
+                break;
+            }
+        }
+
         // this is an unsupported SDT property, create a grab bag for it
         OUString sName;
         switch (nSprmId)
@@ -2819,13 +2835,42 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
     }
     break;
     case NS_ooxml::LN_CT_SdtCheckbox_checked:
-        m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, 
"ooxml:CT_SdtCheckbox_checked", sStringValue);
+        if (!m_pImpl->GetSdtStarts().empty())
+        {
+            // nIntValue is not just 0 or 1, because we're in the w14 
namespace's ST_OnOff.
+            if (nIntValue == NS_ooxml::LN_ST_OnOff_true || nIntValue == 
NS_ooxml::LN_ST_OnOff_1)
+            {
+                m_pImpl->m_pSdtHelper->SetChecked();
+            }
+        }
+        else
+        {
+            m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, 
"ooxml:CT_SdtCheckbox_checked",
+                                   
TextEffectsHandler::getOnOffString(nIntValue));
+        }
         break;
     case NS_ooxml::LN_CT_SdtCheckbox_checkedState:
-        m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, 
"ooxml:CT_SdtCheckbox_checkedState", sStringValue);
+        if (!m_pImpl->GetSdtStarts().empty())
+        {
+            
m_pImpl->m_pSdtHelper->SetCheckedState(OUString(sal_Unicode(sStringValue.toInt32(16))));
+        }
+        else
+        {
+            m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, 
"ooxml:CT_SdtCheckbox_checkedState",
+                                   sStringValue);
+        }
         break;
     case NS_ooxml::LN_CT_SdtCheckbox_uncheckedState:
-        m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, 
"ooxml:CT_SdtCheckbox_uncheckedState", sStringValue);
+        if (!m_pImpl->GetSdtStarts().empty())
+        {
+            m_pImpl->m_pSdtHelper->SetUncheckedState(
+                OUString(sal_Unicode(sStringValue.toInt32(16))));
+        }
+        else
+        {
+            m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag,
+                                   "ooxml:CT_SdtCheckbox_uncheckedState", 
sStringValue);
+        }
         break;
     case NS_ooxml::LN_CT_SdtDocPart_docPartGallery:
         m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, 
"ooxml:CT_SdtDocPart_docPartGallery", sStringValue);
@@ -2931,6 +2976,12 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
                     rContext->Insert(PROP_CHAR_TRANSPARENCE, 
uno::makeAny(nTransparency));
                 }
             }
+            else if (nSprmId == NS_ooxml::LN_cntxtAlts_cntxtAlts)
+            {
+                pTextEffectsHandlerPtr->lcl_sprm(rSprm);
+                beans::PropertyValue aGrabBag = 
pTextEffectsHandlerPtr->getInteropGrabBag();
+                rContext->Insert(*aPropertyId, uno::makeAny(aGrabBag), true, 
CHAR_GRAB_BAG);
+            }
         }
     }
     break;
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index c95bbf568678..2d956b5ab09c 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -849,6 +849,11 @@ void DomainMapper_Impl::PushSdt()
     m_xSdtStarts.push({bStart, OUString(), xCursor->getStart()});
 }
 
+const std::stack<BookmarkInsertPosition>& DomainMapper_Impl::GetSdtStarts() 
const
+{
+    return m_xSdtStarts;
+}
+
 void DomainMapper_Impl::PopSdt()
 {
     if (m_xSdtStarts.empty())
@@ -886,7 +891,23 @@ void DomainMapper_Impl::PopSdt()
         xContentControlProps->setPropertyValue("ShowingPlaceHolder",
                                                
uno::makeAny(m_pSdtHelper->GetShowingPlcHdr()));
     }
+
+    if (m_pSdtHelper->getControlType() == SdtControlType::checkBox)
+    {
+        xContentControlProps->setPropertyValue("Checkbox", uno::makeAny(true));
+
+        xContentControlProps->setPropertyValue("Checked", 
uno::makeAny(m_pSdtHelper->GetChecked()));
+
+        xContentControlProps->setPropertyValue("CheckedState",
+                                               
uno::makeAny(m_pSdtHelper->GetCheckedState()));
+
+        xContentControlProps->setPropertyValue("UncheckedState",
+                                               
uno::makeAny(m_pSdtHelper->GetUncheckedState()));
+    }
+
     xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
+
+    m_pSdtHelper->clear();
 }
 
 void    DomainMapper_Impl::PushProperties(ContextType eId)
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
index 32721e170c38..5a48e26fe73b 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
@@ -725,8 +725,12 @@ public:
 
     /// Setter method for m_bSdt.
     void SetSdt(bool bSdt);
+
     void PushSdt();
     void PopSdt();
+    /// Gives access to the currently open run/inline SDTs.
+    const std::stack<BookmarkInsertPosition>& GetSdtStarts() const;
+
     /// Getter method for m_bSdt.
     bool GetSdt() const { return m_bSdt;}
     bool GetParaChanged() const { return m_bParaChanged;}
diff --git a/writerfilter/source/dmapper/SdtHelper.cxx 
b/writerfilter/source/dmapper/SdtHelper.cxx
index 924d70272181..81e503a7f549 100644
--- a/writerfilter/source/dmapper/SdtHelper.cxx
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -423,6 +423,21 @@ void SdtHelper::SetShowingPlcHdr() { m_bShowingPlcHdr = 
true; }
 
 bool SdtHelper::GetShowingPlcHdr() const { return m_bShowingPlcHdr; }
 
+void SdtHelper::SetChecked() { m_bChecked = true; }
+
+bool SdtHelper::GetChecked() const { return m_bChecked; }
+
+void SdtHelper::SetCheckedState(const OUString& rCheckedState) { 
m_aCheckedState = rCheckedState; }
+
+OUString SdtHelper::GetCheckedState() const { return m_aCheckedState; }
+
+void SdtHelper::SetUncheckedState(const OUString& rUncheckedState)
+{
+    m_aUncheckedState = rUncheckedState;
+}
+
+OUString SdtHelper::GetUncheckedState() const { return m_aUncheckedState; }
+
 void SdtHelper::clear()
 {
     m_aDropDownItems.clear();
@@ -432,6 +447,9 @@ void SdtHelper::clear()
     m_sDataBindingStoreItemID.clear();
     m_aGrabBag.clear();
     m_bShowingPlcHdr = false;
+    m_bChecked = false;
+    m_aCheckedState.clear();
+    m_aUncheckedState.clear();
 }
 
 } // namespace writerfilter::dmapper
diff --git a/writerfilter/source/dmapper/SdtHelper.hxx 
b/writerfilter/source/dmapper/SdtHelper.hxx
index f0515f91c7a3..62d04d140128 100644
--- a/writerfilter/source/dmapper/SdtHelper.hxx
+++ b/writerfilter/source/dmapper/SdtHelper.hxx
@@ -43,6 +43,7 @@ enum class SdtControlType
     dropDown,
     plainText,
     richText,
+    checkBox,
     unsupported, // Sdt block is defined, but we still do not support such 
type of field
     unknown
 };
@@ -97,6 +98,15 @@ class SdtHelper final : public virtual SvRefBase
     /// Current contents are placeholder text.
     bool m_bShowingPlcHdr = false;
 
+    /// If this is a checkbox, is the checkbox checked?
+    bool m_bChecked = false;
+
+    /// If this is a checkbox, the value of a checked checkbox.
+    OUString m_aCheckedState;
+
+    /// If this is a checkbox, the value of an unchecked checkbox.
+    OUString m_aUncheckedState;
+
     /// Create and append the drawing::XControlShape, containing the various 
models.
     void createControlShape(css::awt::Size aSize,
                             css::uno::Reference<css::awt::XControlModel> 
const& xControlModel,
@@ -106,9 +116,6 @@ class SdtHelper final : public virtual SvRefBase
 
     void loadPropertiesXMLs();
 
-    /// Clear all collected attributes for further reuse
-    void clear();
-
 public:
     explicit SdtHelper(DomainMapper_Impl& rDM_Impl,
                        css::uno::Reference<css::uno::XComponentContext> const& 
xContext);
@@ -162,6 +169,16 @@ public:
 
     void SetShowingPlcHdr();
     bool GetShowingPlcHdr() const;
+
+    void SetChecked();
+    bool GetChecked() const;
+    void SetCheckedState(const OUString& rCheckedState);
+    OUString GetCheckedState() const;
+    void SetUncheckedState(const OUString& rUncheckedState);
+    OUString GetUncheckedState() const;
+
+    /// Clear all collected attributes for further reuse
+    void clear();
 };
 
 } // namespace writerfilter::dmapper
diff --git a/writerfilter/source/dmapper/TextEffectsHandler.cxx 
b/writerfilter/source/dmapper/TextEffectsHandler.cxx
index 3288556c8979..d145c854fc80 100644
--- a/writerfilter/source/dmapper/TextEffectsHandler.cxx
+++ b/writerfilter/source/dmapper/TextEffectsHandler.cxx
@@ -66,6 +66,7 @@ OUString lclGetNameForElementId(sal_uInt32 aId)
         aIdMap[NS_ooxml::LN_CT_Props3D_extrusionClr]            = 
"extrusionClr";
         aIdMap[NS_ooxml::LN_CT_Props3D_contourClr]              = "contourClr";
         aIdMap[NS_ooxml::LN_CT_StylisticSets_styleSet]          = "styleSet";
+        aIdMap[NS_ooxml::LN_cntxtAlts_cntxtAlts]                = "cntxtAlts";
     }
     return aIdMap[aId];
 }
diff --git a/writerfilter/source/dmapper/TextEffectsHandler.hxx 
b/writerfilter/source/dmapper/TextEffectsHandler.hxx
index 22127c6c81b2..30a8435b2829 100644
--- a/writerfilter/source/dmapper/TextEffectsHandler.hxx
+++ b/writerfilter/source/dmapper/TextEffectsHandler.hxx
@@ -33,10 +33,6 @@ private:
 
     void convertElementIdToPropertyId(sal_Int32 aElementId);
 
-    // LoggedProperties
-    virtual void lcl_attribute(Id aName, Value& aValue) override;
-    virtual void lcl_sprm(Sprm& sprm) override;
-
 public:
     explicit TextEffectsHandler(sal_uInt32 aElementId);
     virtual ~TextEffectsHandler() override;
@@ -63,6 +59,10 @@ public:
     static OUString getNumSpacingString(sal_Int32 nType);
 
     static sal_uInt8 GetTextFillSolidFillAlpha(const 
css::beans::PropertyValue& rValue);
+
+    // LoggedProperties
+    virtual void lcl_attribute(Id aName, Value& aValue) override;
+    virtual void lcl_sprm(Sprm& sprm) override;
 };
 }
 
diff --git a/writerfilter/source/ooxml/model.xml 
b/writerfilter/source/ooxml/model.xml
index 439f5e8d8a20..bff00b41a925 100644
--- a/writerfilter/source/ooxml/model.xml
+++ b/writerfilter/source/ooxml/model.xml
@@ -4886,7 +4886,7 @@
       </define>
       <define name="CT_SdtCheckbox">
         <element name="checked">
-          <ref name="CT_String"/>
+          <ref name="CT_OnOff"/>
         </element>
         <element name="checkedState">
           <ref name="CT_String"/>
@@ -5260,8 +5260,9 @@
       <attribute name="id" tokenid="ooxml:CT_StyleSet_id"/>
       <attribute name="val" tokenid="ooxml:CT_StyleSet_val"/>
     </resource>
-    <resource name="CT_OnOff" resource="Properties">
-      <attribute name="val" tokenid="ooxml:CT_OnOff_val"/>
+    <resource name="CT_OnOff" resource="Value">
+      <attribute name="val" tokenid="ooxml:CT_OnOff_val" action="setValue"/>
+      <action name="start" action="setDefaultBooleanValue"/>
     </resource>
 
     <!-- Main element content -->

Reply via email to