diff options
author | Miklos Vajna <vmiklos@collabora.com> | 2022-04-29 08:12:25 +0200 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2022-04-29 09:03:01 +0200 |
commit | c53d3a1f4b8430507d54f5fac336870df7a700af (patch) | |
tree | 4f09f1b412ac6d3e3e0ed57429242279922d12d8 | |
parent | d3849255b76e92a42f653c266b88945708984c4f (diff) |
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 <vmiklos@collabora.com>
Tested-by: Jenkins
-rw-r--r-- | writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx | 46 | ||||
-rw-r--r-- | writerfilter/qa/cppunittests/dmapper/data/sdt-run-checkbox.docx | bin | 0 -> 4244 bytes | |||
-rw-r--r-- | writerfilter/source/dmapper/DomainMapper.cxx | 57 | ||||
-rw-r--r-- | writerfilter/source/dmapper/DomainMapper_Impl.cxx | 21 | ||||
-rw-r--r-- | writerfilter/source/dmapper/DomainMapper_Impl.hxx | 4 | ||||
-rw-r--r-- | writerfilter/source/dmapper/SdtHelper.cxx | 18 | ||||
-rw-r--r-- | writerfilter/source/dmapper/SdtHelper.hxx | 23 | ||||
-rw-r--r-- | writerfilter/source/dmapper/TextEffectsHandler.cxx | 1 | ||||
-rw-r--r-- | writerfilter/source/dmapper/TextEffectsHandler.hxx | 8 | ||||
-rw-r--r-- | writerfilter/source/ooxml/model.xml | 7 |
10 files changed, 172 insertions, 13 deletions
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 Binary files differnew file mode 100644 index 000000000000..c6718b97c2a0 --- /dev/null +++ b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-checkbox.docx 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 --> |