sw/Library_sw.mk | 2 sw/inc/formatcontentcontrol.hxx | 104 ++ sw/inc/hintids.hxx | 17 sw/inc/textcontentcontrol.hxx | 41 sw/inc/unobaseclass.hxx | 1 sw/inc/unocoll.hxx | 1 sw/inc/unotextcursor.hxx | 1 sw/qa/core/unocore/unocore.cxx | 28 sw/source/core/bastyp/init.cxx | 5 sw/source/core/doc/DocumentContentOperationsManager.cxx | 2 sw/source/core/doc/dbgoutsw.cxx | 2 sw/source/core/inc/unocontentcontrol.hxx | 139 +++ sw/source/core/txtnode/attrcontentcontrol.cxx | 237 +++++ sw/source/core/txtnode/thints.cxx | 32 sw/source/core/txtnode/txatbase.cxx | 2 sw/source/core/unocore/unocoll.cxx | 7 sw/source/core/unocore/unocontentcontrol.cxx | 686 ++++++++++++++++ sw/source/core/unocore/unoobj.cxx | 37 sw/source/core/unocore/unotext.cxx | 4 sw/source/filter/html/css1atr.cxx | 2 sw/source/filter/html/htmlatr.cxx | 2 21 files changed, 1332 insertions(+), 20 deletions(-)
New commits: commit 67da73d0cc0dfb2822b6bcffb05f71b452f72605 Author: Miklos Vajna <[email protected]> AuthorDate: Thu Mar 31 09:43:24 2022 +0200 Commit: Miklos Vajna <[email protected]> CommitDate: Thu Apr 21 08:07:30 2022 +0200 sw content controls: add UNO API to insert this This only allows the default richText type, the others are not yet handled. (cherry picked from commit c4268efef25129b162884312b15527f1e5c3bcb0) Conflicts: sw/qa/core/unocore/unocore.cxx Change-Id: I39f73ccd9e2c1f0db5735cf97301b01482063d1d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132961 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/sw/Library_sw.mk b/sw/Library_sw.mk index be6fae7446a1..28db1cc7a94b 100644 --- a/sw/Library_sw.mk +++ b/sw/Library_sw.mk @@ -474,6 +474,7 @@ $(eval $(call gb_Library_add_exception_objects,sw,\ sw/source/core/unocore/unobkm \ sw/source/core/unocore/unochart \ sw/source/core/unocore/unocoll \ + sw/source/core/unocore/unocontentcontrol \ sw/source/core/unocore/unocrsr \ sw/source/core/unocore/unocrsrhelper \ sw/source/core/unocore/unodraw \ diff --git a/sw/inc/unobaseclass.hxx b/sw/inc/unobaseclass.hxx index 0dc56aad2886..6d76bf5dbbac 100644 --- a/sw/inc/unobaseclass.hxx +++ b/sw/inc/unobaseclass.hxx @@ -52,6 +52,7 @@ enum class CursorType // a text range or cursor SelectionInTable, Meta, // meta/meta-field + ContentControl, }; /* diff --git a/sw/inc/unocoll.hxx b/sw/inc/unocoll.hxx index 821d50ab4a2c..8d883a8223cb 100644 --- a/sw/inc/unocoll.hxx +++ b/sw/inc/unocoll.hxx @@ -177,6 +177,7 @@ enum class SwServiceType { StyleTable = 114, StyleCell = 115, LineBreak = 116, + ContentControl = 117, Invalid = USHRT_MAX }; diff --git a/sw/inc/unotextcursor.hxx b/sw/inc/unotextcursor.hxx index e6c33b2aad8f..b055f2d64504 100644 --- a/sw/inc/unotextcursor.hxx +++ b/sw/inc/unotextcursor.hxx @@ -100,6 +100,7 @@ public: SwUnoCursor& GetCursor(); bool IsAtEndOfMeta() const; + bool IsAtEndOfContentControl() const; void DeleteAndInsert(OUString const& rText, const bool bForceExpandHints); diff --git a/sw/qa/core/unocore/unocore.cxx b/sw/qa/core/unocore/unocore.cxx index d971e57d77e3..e98b8d2838b1 100644 --- a/sw/qa/core/unocore/unocore.cxx +++ b/sw/qa/core/unocore/unocore.cxx @@ -25,6 +25,7 @@ #include <view.hxx> #include <ndtxt.hxx> #include <textlinebreak.hxx> +#include <textcontentcontrol.hxx> using namespace ::com::sun::star; @@ -281,6 +282,33 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testLineBreakTextPortionEnum) CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(SwLineBreakClear::ALL), eClear); } +CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testContentControlInsert) +{ + // Given an empty document: + SwDoc* pDoc = createSwDoc(); + + // When inserting a content control around one or more text portions: + 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); + xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); + + // Then make sure that the text attribute is inserted: + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + SwNodeOffset nIndex = pWrtShell->GetCursor()->GetNode().GetIndex(); + SwTextNode* pTextNode = pDoc->GetNodes()[nIndex]->GetTextNode(); + SwTextAttr* pAttr = pTextNode->GetTextAttrForCharAt(0, RES_TXTATR_CONTENTCONTROL); + // Without the accompanying fix in place, this test would have failed, as the + // SwXContentControl::attach() implementation was missing. + CPPUNIT_ASSERT(pAttr); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unocontentcontrol.hxx b/sw/source/core/inc/unocontentcontrol.hxx new file mode 100644 index 000000000000..7f90f2ec18e7 --- /dev/null +++ b/sw/source/core/inc/unocontentcontrol.hxx @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <memory> +#include <deque> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XTextField.hpp> + +#include <cppuhelper/implbase.hxx> + +#include <unobaseclass.hxx> + +typedef std::deque<css::uno::Reference<css::text::XTextRange>> TextRangeList_t; + +class SwPaM; +class SwTextNode; +class SwContentControl; + +/** + * UNO API wrapper around an SwContentControl, exposed as the com.sun.star.text.ContentControl + * service. + */ +class SwXContentControl + : public cppu::WeakImplHelper<css::lang::XUnoTunnel, css::lang::XServiceInfo, + css::container::XEnumerationAccess, css::text::XTextContent, + css::text::XText> +{ +public: + class Impl; + +protected: + sw::UnoImplPtr<Impl> m_pImpl; + + void AttachImpl(const css::uno::Reference<css::text::XTextRange>& xTextRange, + sal_uInt16 nWhich); + + ~SwXContentControl() override; + + SwXContentControl(const SwXContentControl&) = delete; + SwXContentControl& operator=(const SwXContentControl&) = delete; + + SwXContentControl(SwDoc* pDoc, SwContentControl* pContentControl, + const css::uno::Reference<css::text::XText>& xParentText, + std::unique_ptr<const TextRangeList_t> pPortions); + + SwXContentControl(SwDoc* pDoc); + +public: + static css::uno::Reference<css::text::XTextContent> + CreateXContentControl(SwContentControl& rContentControl, + const css::uno::Reference<css::text::XText>& xParentText = nullptr, + std::unique_ptr<const TextRangeList_t>&& pPortions + = std::unique_ptr<const TextRangeList_t>()); + + static css::uno::Reference<css::text::XTextContent> CreateXContentControl(SwDoc& rDoc); + + /// Initializes params with position of the attribute content (without CH_TXTATR). + bool SetContentRange(SwTextNode*& rpNode, sal_Int32& rStart, sal_Int32& rEnd) const; + css::uno::Reference<css::text::XText> GetParentText() const; + + bool CheckForOwnMemberContentControl(const SwPaM& rPam, const bool bAbsorb); + + static const css::uno::Sequence<sal_Int8>& getUnoTunnelId(); + + // XUnoTunnel + sal_Int64 SAL_CALL getSomething(const css::uno::Sequence<sal_Int8>& Identifier) override; + + // XServiceInfo + OUString SAL_CALL getImplementationName() override; + sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override; + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XComponent + void SAL_CALL dispose() override; + void SAL_CALL + addEventListener(const css::uno::Reference<css::lang::XEventListener>& xListener) override; + void SAL_CALL + removeEventListener(const css::uno::Reference<css::lang::XEventListener>& xListener) override; + + // XElementAccess + css::uno::Type SAL_CALL getElementType() override; + sal_Bool SAL_CALL hasElements() override; + + // XEnumerationAccess + css::uno::Reference<css::container::XEnumeration> SAL_CALL createEnumeration() override; + + // XTextContent + void SAL_CALL attach(const css::uno::Reference<css::text::XTextRange>& xTextRange) override; + css::uno::Reference<css::text::XTextRange> SAL_CALL getAnchor() override; + + // XTextRange + css::uno::Reference<css::text::XText> SAL_CALL getText() override; + css::uno::Reference<css::text::XTextRange> SAL_CALL getStart() override; + css::uno::Reference<css::text::XTextRange> SAL_CALL getEnd() override; + OUString SAL_CALL getString() override; + void SAL_CALL setString(const OUString& rString) override; + + // XSimpleText + css::uno::Reference<css::text::XTextCursor> SAL_CALL createTextCursor() override; + css::uno::Reference<css::text::XTextCursor> SAL_CALL createTextCursorByRange( + const css::uno::Reference<css::text::XTextRange>& xTextPosition) override; + void SAL_CALL insertString(const css::uno::Reference<css::text::XTextRange>& xRange, + const OUString& aString, sal_Bool bAbsorb) override; + void SAL_CALL insertControlCharacter(const css::uno::Reference<css::text::XTextRange>& xRange, + sal_Int16 nControlCharacter, sal_Bool bAbsorb) override; + + // XText + void SAL_CALL insertTextContent(const css::uno::Reference<css::text::XTextRange>& xRange, + const css::uno::Reference<css::text::XTextContent>& xContent, + sal_Bool bAbsorb) override; + void SAL_CALL + removeTextContent(const css::uno::Reference<css::text::XTextContent>& xContent) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unocoll.cxx b/sw/source/core/unocore/unocoll.cxx index fd10e33424f2..044e5acc99cb 100644 --- a/sw/source/core/unocore/unocoll.cxx +++ b/sw/source/core/unocore/unocoll.cxx @@ -56,6 +56,7 @@ #include <unobookmark.hxx> #include <unorefmark.hxx> #include <unometa.hxx> +#include <unocontentcontrol.hxx> #include <docsh.hxx> #include <hints.hxx> #include <frameformats.hxx> @@ -455,7 +456,8 @@ const ProvNamesId_Type aProvNamesId[] = { CSS_TEXT_FIELDMASTER_BIBLIOGRAPHY, SwServiceType::FieldMasterBibliography }, { "com.sun.star.style.TableStyle", SwServiceType::StyleTable }, { "com.sun.star.style.CellStyle", SwServiceType::StyleCell }, - { "com.sun.star.text.LineBreak", SwServiceType::LineBreak } + { "com.sun.star.text.LineBreak", SwServiceType::LineBreak }, + { "com.sun.star.text.ContentControl", SwServiceType::ContentControl } }; const SvEventDescription* sw_GetSupportedMacroItems() @@ -828,6 +830,9 @@ SwXServiceProvider::MakeInstance(SwServiceType nObjectType, SwDoc & rDoc) case SwServiceType::LineBreak: xRet = SwXLineBreak::CreateXLineBreak(nullptr); break; + case SwServiceType::ContentControl: + xRet = SwXContentControl::CreateXContentControl(rDoc); + break; default: throw uno::RuntimeException(); } diff --git a/sw/source/core/unocore/unocontentcontrol.cxx b/sw/source/core/unocore/unocontentcontrol.cxx new file mode 100644 index 000000000000..535762feae58 --- /dev/null +++ b/sw/source/core/unocore/unocontentcontrol.cxx @@ -0,0 +1,686 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unocontentcontrol.hxx> + +#include <mutex> + +#include <com/sun/star/text/XWordCursor.hpp> + +#include <comphelper/interfacecontainer4.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <formatcontentcontrol.hxx> +#include <ndtxt.hxx> +#include <textcontentcontrol.hxx> +#include <unotext.hxx> +#include <unotextcursor.hxx> +#include <unotextrange.hxx> +#include <doc.hxx> +#include <unoport.hxx> + +using namespace com::sun::star; + +namespace +{ +/// UNO API wrapper around the text inside an SwXContentControl. +class SwXContentControlText : public cppu::OWeakObject, public SwXText +{ +private: + SwXContentControl& m_rContentControl; + + void PrepareForAttach(uno::Reference<text::XTextRange>& xRange, const SwPaM& rPam) override; + +protected: + const SwStartNode* GetStartNode() const override; + uno::Reference<text::XTextCursor> CreateCursor() override; + +public: + SwXContentControlText(SwDoc& rDoc, SwXContentControl& rContentControl); + + /// SwXText::Invalidate() is protected. + using SwXText::Invalidate; + + // XInterface + void SAL_CALL acquire() noexcept override { cppu::OWeakObject::acquire(); } + void SAL_CALL release() noexcept override { cppu::OWeakObject::release(); } + + // XTypeProvider + uno::Sequence<sal_Int8> SAL_CALL getImplementationId() override; + + // XText + uno::Reference<text::XTextCursor> SAL_CALL createTextCursor() override; + uno::Reference<text::XTextCursor> SAL_CALL + createTextCursorByRange(const uno::Reference<text::XTextRange>& xTextPosition) override; +}; +} + +SwXContentControlText::SwXContentControlText(SwDoc& rDoc, SwXContentControl& rContentControl) + : SwXText(&rDoc, CursorType::ContentControl) + , m_rContentControl(rContentControl) +{ +} + +const SwStartNode* SwXContentControlText::GetStartNode() const +{ + auto pParent = dynamic_cast<SwXText*>(m_rContentControl.GetParentText().get()); + return pParent ? pParent->GetStartNode() : nullptr; +} + +void SwXContentControlText::PrepareForAttach(uno::Reference<text::XTextRange>& xRange, + const SwPaM& rPam) +{ + // Create a new cursor to prevent modifying SwXTextRange. + xRange = static_cast<text::XWordCursor*>( + new SwXTextCursor(*GetDoc(), &m_rContentControl, CursorType::ContentControl, + *rPam.GetPoint(), (rPam.HasMark()) ? rPam.GetMark() : nullptr)); +} + +uno::Reference<text::XTextCursor> SwXContentControlText::CreateCursor() +{ + uno::Reference<text::XTextCursor> xRet; + if (IsValid()) + { + SwTextNode* pTextNode; + sal_Int32 nContentControlStart; + sal_Int32 nContentControlEnd; + bool bSuccess = m_rContentControl.SetContentRange(pTextNode, nContentControlStart, + nContentControlEnd); + if (bSuccess) + { + SwPosition aPos(*pTextNode, nContentControlStart); + xRet = static_cast<text::XWordCursor*>( + new SwXTextCursor(*GetDoc(), &m_rContentControl, CursorType::ContentControl, aPos)); + } + } + return xRet; +} + +uno::Sequence<sal_Int8> SAL_CALL SwXContentControlText::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XText +uno::Reference<text::XTextCursor> SAL_CALL SwXContentControlText::createTextCursor() +{ + return CreateCursor(); +} + +uno::Reference<text::XTextCursor> SAL_CALL SwXContentControlText::createTextCursorByRange( + const uno::Reference<text::XTextRange>& xTextPosition) +{ + const uno::Reference<text::XTextCursor> xCursor(CreateCursor()); + xCursor->gotoRange(xTextPosition, false); + return xCursor; +} + +/** + * The inner part SwXContentControl, which is deleted with a locked SolarMutex. + * + * The content control has a cached list of text portions for its contents. This list is created by + * SwXTextPortionEnumeration. The content control listens at the SwTextNode and throws away the + * cache when the text node changes. + */ +class SwXContentControl::Impl : public SvtListener +{ +public: + uno::WeakReference<uno::XInterface> m_wThis; + // Just for OInterfaceContainerHelper4. + std::mutex m_Mutex; + ::comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_EventListeners; + std::unique_ptr<const TextRangeList_t> m_pTextPortions; + // 3 possible states: not attached, attached, disposed + bool m_bIsDisposed; + bool m_bIsDescriptor; + uno::Reference<text::XText> m_xParentText; + rtl::Reference<SwXContentControlText> m_xText; + SwContentControl* m_pContentControl; + + Impl(SwXContentControl& rThis, SwDoc& rDoc, SwContentControl* pContentControl, + const uno::Reference<text::XText>& xParentText, + std::unique_ptr<const TextRangeList_t> pPortions) + : m_pTextPortions(std::move(pPortions)) + , m_bIsDisposed(false) + , m_bIsDescriptor(pContentControl == nullptr) + , m_xParentText(xParentText) + , m_xText(new SwXContentControlText(rDoc, rThis)) + , m_pContentControl(pContentControl) + { + if (m_pContentControl) + { + StartListening(m_pContentControl->GetNotifier()); + } + } + + const SwContentControl* GetContentControl() const; + +protected: + void Notify(const SfxHint& rHint) override; +}; + +const SwContentControl* SwXContentControl::Impl::GetContentControl() const +{ + return m_pContentControl; +} + +// sw::BroadcastingModify +void SwXContentControl::Impl::Notify(const SfxHint& rHint) +{ + // throw away cache (SwTextNode changed) + m_pTextPortions.reset(); + + if (rHint.GetId() != SfxHintId::Dying && rHint.GetId() != SfxHintId::Deinitializing) + return; + + m_bIsDisposed = true; + m_pContentControl = nullptr; + m_xText->Invalidate(); + uno::Reference<uno::XInterface> xThis(m_wThis); + if (!xThis.is()) + { + // If UNO object is already dead, don't refer to it in an event. + return; + } + lang::EventObject aEvent(xThis); + std::unique_lock aGuard(m_Mutex); + m_EventListeners.disposeAndClear(aGuard, aEvent); +} + +uno::Reference<text::XText> SwXContentControl::GetParentText() const +{ + return m_pImpl->m_xParentText; +} + +SwXContentControl::SwXContentControl(SwDoc* pDoc, SwContentControl* pContentControl, + const uno::Reference<text::XText>& xParentText, + std::unique_ptr<const TextRangeList_t> pPortions) + : m_pImpl(new SwXContentControl::Impl(*this, *pDoc, pContentControl, xParentText, + std::move(pPortions))) +{ +} + +SwXContentControl::SwXContentControl(SwDoc* pDoc) + : m_pImpl(new SwXContentControl::Impl(*this, *pDoc, nullptr, nullptr, nullptr)) +{ +} + +SwXContentControl::~SwXContentControl() {} + +uno::Reference<text::XTextContent> SwXContentControl::CreateXContentControl(SwDoc& rDoc) +{ + rtl::Reference<SwXContentControl> xContentControl(new SwXContentControl(&rDoc)); + uno::Reference<text::XTextContent> xTextContent(xContentControl); + xContentControl->m_pImpl->m_wThis = xTextContent; + return xContentControl; +} + +uno::Reference<text::XTextContent> +SwXContentControl::CreateXContentControl(SwContentControl& rContentControl, + const uno::Reference<text::XText>& xParent, + std::unique_ptr<const TextRangeList_t>&& pPortions) +{ + // re-use existing SwXContentControl + uno::Reference<text::XTextContent> xContentControl(rContentControl.GetXContentControl()); + if (xContentControl.is()) + { + if (pPortions) + { + // Set the cache in the XContentControl to the given portions. + auto pXContentControl + = comphelper::getFromUnoTunnel<SwXContentControl>(xContentControl); + assert(pXContentControl); + // The content control must always be created with the complete content. If + // SwXTextPortionEnumeration is created for a selection, it must be checked that the + // content control is contained in the selection. + pXContentControl->m_pImpl->m_pTextPortions = std::move(pPortions); + if (pXContentControl->m_pImpl->m_xParentText.get() != xParent.get()) + { + SAL_WARN("sw.uno", "SwXContentControl with different parent"); + pXContentControl->m_pImpl->m_xParentText.set(xParent); + } + } + return xContentControl; + } + + // Create new SwXContentControl. + SwTextNode* pTextNode = rContentControl.GetTextNode(); + if (!pTextNode) + { + SAL_WARN("sw.uno", "CreateXContentControl: no text node"); + return nullptr; + } + uno::Reference<text::XText> xParentText(xParent); + if (!xParentText.is()) + { + SwTextContentControl* pTextAttr = rContentControl.GetTextAttr(); + if (!pTextAttr) + { + SAL_WARN("sw.uno", "CreateXContentControl: no text attr"); + return nullptr; + } + SwPosition aPos(*pTextNode, pTextAttr->GetStart()); + xParentText.set(sw::CreateParentXText(pTextNode->GetDoc(), aPos)); + } + if (!xParentText.is()) + { + return nullptr; + } + rtl::Reference<SwXContentControl> pXContentControl = new SwXContentControl( + &pTextNode->GetDoc(), &rContentControl, xParentText, std::move(pPortions)); + xContentControl.set(pXContentControl); + rContentControl.SetXContentControl(xContentControl); + pXContentControl->m_pImpl->m_wThis = xContentControl; + return xContentControl; +} + +bool SwXContentControl::SetContentRange(SwTextNode*& rpNode, sal_Int32& rStart, + sal_Int32& rEnd) const +{ + const SwContentControl* pContentControl = m_pImpl->GetContentControl(); + if (pContentControl) + { + const SwTextContentControl* pTextAttr = pContentControl->GetTextAttr(); + if (pTextAttr) + { + rpNode = pContentControl->GetTextNode(); + if (rpNode) + { + // rStart points at the first position within the content control. + rStart = pTextAttr->GetStart() + 1; + rEnd = *pTextAttr->End(); + return true; + } + } + } + return false; +} + +bool SwXContentControl::CheckForOwnMemberContentControl(const SwPaM& rPam, bool bAbsorb) +{ + SwTextNode* pTextNode; + sal_Int32 nContentControlStart; + sal_Int32 nContentControlEnd; + bool bSuccess = SetContentRange(pTextNode, nContentControlStart, nContentControlEnd); + if (!bSuccess) + { + SAL_WARN("sw.core", "SwXContentControl::CheckForOwnMemberContentControl: no pam"); + throw lang::DisposedException(); + } + + const SwPosition* pStartPos(rPam.Start()); + if (&pStartPos->nNode.GetNode() != pTextNode) + { + throw lang::IllegalArgumentException( + "trying to insert into a nesting text content, but start " + "of text range not in same paragraph as text content", + nullptr, 0); + } + bool bForceExpandHints(false); + sal_Int32 nStartPos = pStartPos->nContent.GetIndex(); + if ((nStartPos < nContentControlStart) || (nStartPos > nContentControlEnd)) + { + throw lang::IllegalArgumentException( + "trying to insert into a nesting text content, but start " + "of text range not inside text content", + nullptr, 0); + } + else if (nStartPos == nContentControlEnd) + { + bForceExpandHints = true; + } + if (rPam.HasMark() && bAbsorb) + { + const SwPosition* pEndPos = rPam.End(); + if (&pEndPos->nNode.GetNode() != pTextNode) + { + throw lang::IllegalArgumentException( + "trying to insert into a nesting text content, but end " + "of text range not in same paragraph as text content", + nullptr, 0); + } + sal_Int32 nEndPos = pEndPos->nContent.GetIndex(); + if ((nEndPos < nContentControlStart) || (nEndPos > nContentControlEnd)) + { + throw lang::IllegalArgumentException( + "trying to insert into a nesting text content, but end " + "of text range not inside text content", + nullptr, 0); + } + else if (nEndPos == nContentControlEnd) + { + bForceExpandHints = true; + } + } + return bForceExpandHints; +} + +const uno::Sequence<sal_Int8>& SwXContentControl::getUnoTunnelId() +{ + static const comphelper::UnoIdInit theSwXContentControlUnoTunnelId; + return theSwXContentControlUnoTunnelId.getSeq(); +} + +// XUnoTunnel +sal_Int64 SAL_CALL SwXContentControl::getSomething(const uno::Sequence<sal_Int8>& rId) +{ + return comphelper::getSomethingImpl<SwXContentControl>(rId, this); +} + +// XServiceInfo +OUString SAL_CALL SwXContentControl::getImplementationName() { return "SwXContentControl"; } + +sal_Bool SAL_CALL SwXContentControl::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence<OUString> SAL_CALL SwXContentControl::getSupportedServiceNames() +{ + return { "com.sun.star.text.TextContent", "com.sun.star.text.ContentControl" }; +} + +// XComponent +void SAL_CALL +SwXContentControl::addEventListener(const uno::Reference<lang::XEventListener>& xListener) +{ + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL +SwXContentControl::removeEventListener(const uno::Reference<lang::XEventListener>& xListener) +{ + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +void SAL_CALL SwXContentControl::dispose() +{ + SolarMutexGuard g; + + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_pTextPortions.reset(); + lang::EventObject aEvent(static_cast<::cppu::OWeakObject&>(*this)); + std::unique_lock aGuard(m_pImpl->m_Mutex); + m_pImpl->m_EventListeners.disposeAndClear(aGuard, aEvent); + m_pImpl->m_bIsDisposed = true; + m_pImpl->m_xText->Invalidate(); + } + else if (!m_pImpl->m_bIsDisposed) + { + SwTextNode* pTextNode; + sal_Int32 nContentControlStart; + sal_Int32 nContentControlEnd; + bool bSuccess = SetContentRange(pTextNode, nContentControlStart, nContentControlEnd); + if (!bSuccess) + { + SAL_WARN("sw.core", "SwXContentControl::dispose: no pam"); + } + else + { + // -1 because of CH_TXTATR + SwPaM aPam(*pTextNode, nContentControlStart - 1, *pTextNode, nContentControlEnd); + SwDoc& rDoc(pTextNode->GetDoc()); + rDoc.getIDocumentContentOperations().DeleteAndJoin(aPam); + + // removal should call Modify and do the dispose + assert(m_pImpl->m_bIsDisposed); + } + } +} + +void SwXContentControl::AttachImpl(const uno::Reference<text::XTextRange>& xTextRange, + sal_uInt16 nWhich) +{ + SolarMutexGuard aGuard; + + if (m_pImpl->m_bIsDisposed) + { + throw lang::DisposedException(); + } + if (!m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException("SwXContentControl::AttachImpl(): already attached", + static_cast<::cppu::OWeakObject*>(this)); + } + + uno::Reference<lang::XUnoTunnel> xRangeTunnel(xTextRange, uno::UNO_QUERY); + if (!xRangeTunnel.is()) + { + throw lang::IllegalArgumentException( + "SwXContentControl::AttachImpl(): argument is no XUnoTunnel", + static_cast<::cppu::OWeakObject*>(this), 0); + } + SwXTextRange* pRange = comphelper::getFromUnoTunnel<SwXTextRange>(xRangeTunnel); + OTextCursorHelper* pCursor + = pRange ? nullptr : comphelper::getFromUnoTunnel<OTextCursorHelper>(xRangeTunnel); + if (!pRange && !pCursor) + { + throw lang::IllegalArgumentException( + "SwXContentControl::AttachImpl(): argument not supported type", + static_cast<::cppu::OWeakObject*>(this), 0); + } + + SwDoc* pDoc = pRange ? &pRange->GetDoc() : pCursor->GetDoc(); + if (!pDoc) + { + throw lang::IllegalArgumentException( + "SwXContentControl::AttachImpl(): argument has no SwDoc", + static_cast<::cppu::OWeakObject*>(this), 0); + } + + SwUnoInternalPaM aPam(*pDoc); + ::sw::XTextRangeToSwPaM(aPam, xTextRange); + + UnoActionContext aContext(pDoc); + + auto pTextCursor = dynamic_cast<SwXTextCursor*>(pCursor); + bool bForceExpandHints = pTextCursor && pTextCursor->IsAtEndOfContentControl(); + SetAttrMode nInsertFlags = bForceExpandHints + ? (SetAttrMode::FORCEHINTEXPAND | SetAttrMode::DONTEXPAND) + : SetAttrMode::DONTEXPAND; + + auto pContentControl = std::make_shared<SwContentControl>(nullptr); + SwFormatContentControl aContentControl(pContentControl, nWhich); + bool bSuccess + = pDoc->getIDocumentContentOperations().InsertPoolItem(aPam, aContentControl, nInsertFlags); + SwTextAttr* pTextAttr = pContentControl->GetTextAttr(); + if (!bSuccess) + { + throw lang::IllegalArgumentException( + "SwXContentControl::AttachImpl(): cannot create meta: range invalid?", + static_cast<::cppu::OWeakObject*>(this), 1); + } + if (!pTextAttr) + { + SAL_WARN("sw.core", "content control inserted, but has no text attribute?"); + throw uno::RuntimeException( + "SwXContentControl::AttachImpl(): cannot create content control", + static_cast<::cppu::OWeakObject*>(this)); + } + + m_pImpl->EndListeningAll(); + m_pImpl->m_pContentControl = pContentControl.get(); + m_pImpl->StartListening(pContentControl->GetNotifier()); + pContentControl->SetXContentControl(uno::Reference<text::XTextContent>(this)); + + m_pImpl->m_xParentText = sw::CreateParentXText(*pDoc, *aPam.GetPoint()); + + m_pImpl->m_bIsDescriptor = false; +} + +// XTextContent +void SAL_CALL SwXContentControl::attach(const uno::Reference<text::XTextRange>& xTextRange) +{ + return SwXContentControl::AttachImpl(xTextRange, RES_TXTATR_CONTENTCONTROL); +} + +uno::Reference<text::XTextRange> SAL_CALL SwXContentControl::getAnchor() +{ + SolarMutexGuard g; + + if (m_pImpl->m_bIsDisposed) + { + throw lang::DisposedException(); + } + if (m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException("SwXContentControl::getAnchor(): not inserted", + static_cast<::cppu::OWeakObject*>(this)); + } + + SwTextNode* pTextNode; + sal_Int32 nContentControlStart; + sal_Int32 nContentControlEnd; + bool bSuccess = SetContentRange(pTextNode, nContentControlStart, nContentControlEnd); + if (!bSuccess) + { + SAL_WARN("sw.core", "no pam"); + throw lang::DisposedException("SwXContentControl::getAnchor(): not attached", + static_cast<::cppu::OWeakObject*>(this)); + } + + SwPosition aStart(*pTextNode, nContentControlStart - 1); // -1 due to CH_TXTATR + SwPosition aEnd(*pTextNode, nContentControlEnd); + return SwXTextRange::CreateXTextRange(pTextNode->GetDoc(), aStart, &aEnd); +} + +// XTextRange +uno::Reference<text::XText> SAL_CALL SwXContentControl::getText() { return this; } + +uno::Reference<text::XTextRange> SAL_CALL SwXContentControl::getStart() +{ + SolarMutexGuard g; + return m_pImpl->m_xText->getStart(); +} + +uno::Reference<text::XTextRange> SAL_CALL SwXContentControl::getEnd() +{ + SolarMutexGuard g; + return m_pImpl->m_xText->getEnd(); +} + +OUString SAL_CALL SwXContentControl::getString() +{ + SolarMutexGuard g; + return m_pImpl->m_xText->getString(); +} + +void SAL_CALL SwXContentControl::setString(const OUString& rString) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->setString(rString); +} + +// XSimpleText +uno::Reference<text::XTextCursor> SAL_CALL SwXContentControl::createTextCursor() +{ + SolarMutexGuard g; + return m_pImpl->m_xText->createTextCursor(); +} + +uno::Reference<text::XTextCursor> SAL_CALL +SwXContentControl::createTextCursorByRange(const uno::Reference<text::XTextRange>& xTextPosition) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->createTextCursorByRange(xTextPosition); +} + +void SAL_CALL SwXContentControl::insertString(const uno::Reference<text::XTextRange>& xRange, + const OUString& rString, sal_Bool bAbsorb) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->insertString(xRange, rString, bAbsorb); +} + +void SAL_CALL SwXContentControl::insertControlCharacter( + const uno::Reference<text::XTextRange>& xRange, sal_Int16 nControlCharacter, sal_Bool bAbsorb) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->insertControlCharacter(xRange, nControlCharacter, bAbsorb); +} + +// XText +void SAL_CALL SwXContentControl::insertTextContent( + const uno::Reference<text::XTextRange>& xRange, + const uno::Reference<text::XTextContent>& xContent, sal_Bool bAbsorb) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->insertTextContent(xRange, xContent, bAbsorb); +} + +void SAL_CALL +SwXContentControl::removeTextContent(const uno::Reference<text::XTextContent>& xContent) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->removeTextContent(xContent); +} + +// XElementAccess +uno::Type SAL_CALL SwXContentControl::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL SwXContentControl::hasElements() +{ + SolarMutexGuard g; + return m_pImpl->m_pContentControl != nullptr; +} + +// XEnumerationAccess +uno::Reference<container::XEnumeration> SAL_CALL SwXContentControl::createEnumeration() +{ + SolarMutexGuard g; + + if (m_pImpl->m_bIsDisposed) + { + throw lang::DisposedException(); + } + if (m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException("createEnumeration(): not inserted", + static_cast<::cppu::OWeakObject*>(this)); + } + + SwTextNode* pTextNode; + sal_Int32 nContentControlStart; + sal_Int32 nContentControlEnd; + bool bSuccess = SetContentRange(pTextNode, nContentControlStart, nContentControlEnd); + if (!bSuccess) + { + SAL_WARN("sw.core", "no pam"); + throw lang::DisposedException(); + } + + SwPaM aPam(*pTextNode, nContentControlStart); + + if (!m_pImpl->m_pTextPortions) + { + return new SwXTextPortionEnumeration(aPam, GetParentText(), nContentControlStart, + nContentControlEnd); + } + else + { + return new SwXTextPortionEnumeration(aPam, std::deque(*m_pImpl->m_pTextPortions)); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoobj.cxx b/sw/source/core/unocore/unoobj.cxx index 09136cf44c8c..90072ed52b78 100644 --- a/sw/source/core/unocore/unoobj.cxx +++ b/sw/source/core/unocore/unoobj.cxx @@ -54,6 +54,7 @@ #include <unomap.hxx> #include <unoprnms.hxx> #include <unometa.hxx> +#include <unocontentcontrol.hxx> #include <unotext.hxx> #include <com/sun/star/text/TextMarkupType.hpp> #include <vcl/svapp.hxx> @@ -823,6 +824,42 @@ bool SwXTextCursor::IsAtEndOfMeta() const return false; } +bool SwXTextCursor::IsAtEndOfContentControl() const +{ + if (CursorType::ContentControl == m_eType) + { + auto pCursor( m_pUnoCursor ); + auto pXContentControl( + dynamic_cast<SwXContentControl*>(m_xParentText.get()) ); + if (!pXContentControl) + { + SAL_WARN("sw.core", "SwXTextCursor::IsAtEndOfContentControl: no content control"); + } + if (pCursor && pXContentControl) + { + SwTextNode * pTextNode; + sal_Int32 nStart; + sal_Int32 nEnd; + const bool bSuccess( + pXContentControl->SetContentRange(pTextNode, nStart, nEnd) ); + if (!bSuccess) + { + SAL_WARN("sw.core", "SwXTextCursor::IsAtEndOfContentControl: no pam"); + } + else + { + const SwPosition end(*pTextNode, nEnd); + if ( (*pCursor->GetPoint() == end) + || (*pCursor->GetMark() == end)) + { + return true; + } + } + } + } + return false; +} + OUString SwXTextCursor::getImplementationName() { return "SwXTextCursor"; diff --git a/sw/source/core/unocore/unotext.cxx b/sw/source/core/unocore/unotext.cxx index 2ad7fce679d8..8b0f605640e6 100644 --- a/sw/source/core/unocore/unotext.cxx +++ b/sw/source/core/unocore/unotext.cxx @@ -70,6 +70,7 @@ #include <SwRewriter.hxx> #include <strings.hrc> #include <frameformats.hxx> +#include <unocontentcontrol.hxx> using namespace ::com::sun::star; @@ -593,13 +594,14 @@ SwXText::insertTextContent( comphelper::getFromUnoTunnel<SwXReferenceMark>(xContentTunnel); SwXMeta *const pMeta = comphelper::getFromUnoTunnel<SwXMeta>(xContentTunnel); + auto* pContentControl = comphelper::getFromUnoTunnel<SwXContentControl>(xContentTunnel); SwXTextField* pTextField = comphelper::getFromUnoTunnel<SwXTextField>(xContentTunnel); if (pTextField && pTextField->GetServiceId() != SwServiceType::FieldTypeAnnotation) pTextField = nullptr; const bool bAttribute = pBookmark || pDocumentIndexMark - || pSection || pReferenceMark || pMeta || pTextField; + || pSection || pReferenceMark || pMeta || pContentControl || pTextField; if (bAbsorb && !bAttribute) { commit 0ef9ce463f6fe6bd68f01d6b075da3b5e4dbdd32 Author: Miklos Vajna <[email protected]> AuthorDate: Wed Mar 30 08:16:21 2022 +0200 Commit: Miklos Vajna <[email protected]> CommitDate: Thu Apr 21 08:07:14 2022 +0200 sw content controls: add document model This is meant to represent inline structured document tags (<w:sdt> elements in DOCX) or content controls (as Word users know this). Don't confuse this with block-level, cell-level or table-level content controls, which are not covered here. You may wonder why fields or fieldmarks can't be used to represent this. The problems are: - a fieldmark can contain a paragraph break, inline content controls can't - content controls must be a well-formed XML element, while fieldmarks can start/end at random document model positions - fieldmarks are supposed to have a field command and a result result (with a separator between the two), but content controls don't have a field command - mapping to a field has the problem that Writer fields can only have a single text portion / run, but content controls can have multiple ones So model content controls with a meta-like text attribute which covers all these cases. SwContentControl is mostly empty as a start, it can track more data in follow-up commits. SwTextContentControl and SwFormatContentControl are the matching text attr and pool item subclasses. (cherry picked from commit 3dd4f3691458ea537bc1867386269694775cfbcb) Change-Id: I1e57f7756cf87041975371483ec4fc8abf875dfe Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132960 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/sw/Library_sw.mk b/sw/Library_sw.mk index ef3a27b74458..be6fae7446a1 100644 --- a/sw/Library_sw.mk +++ b/sw/Library_sw.mk @@ -420,6 +420,7 @@ $(eval $(call gb_Library_add_exception_objects,sw,\ sw/source/core/tox/ToxTextGenerator \ sw/source/core/tox/ToxWhitespaceStripper \ sw/source/core/txtnode/SwGrammarContact \ + sw/source/core/txtnode/attrcontentcontrol \ sw/source/core/txtnode/atrfld \ sw/source/core/txtnode/atrflyin \ sw/source/core/txtnode/atrftn \ diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx new file mode 100644 index 000000000000..0fa075bf7036 --- /dev/null +++ b/sw/inc/formatcontentcontrol.hxx @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/text/XTextContent.hpp> + +#include <cppuhelper/weakref.hxx> +#include <svl/poolitem.hxx> + +#include "calbck.hxx" + +class SwContentControl; +class SwTextContentControl; +class SwTextNode; +class SwXContentControl; + +/// SfxPoolItem subclass that wraps an SwContentControl. +class SwFormatContentControl final : public SfxPoolItem +{ + std::shared_ptr<SwContentControl> m_pContentControl; + SwTextContentControl* m_pTextAttr; + +public: + SwTextContentControl* GetTextAttr() { return m_pTextAttr; } + void SetTextAttr(SwTextContentControl* pTextAttr); + + /// This method must be called when the hint is actually copied. + void DoCopy(SwTextNode& rTargetTextNode); + + explicit SwFormatContentControl(sal_uInt16 nWhich); + + explicit SwFormatContentControl(const std::shared_ptr<SwContentControl>& pContentControl, + sal_uInt16 nWhich); + ~SwFormatContentControl() override; + + /// SfxPoolItem + bool operator==(const SfxPoolItem&) const override; + SwFormatContentControl* Clone(SfxItemPool* pPool = nullptr) const override; + + /** + * Notify clients registered at m_pContentControl that this content control is being + * (re-)moved. + */ + void NotifyChangeTextNode(SwTextNode* pTextNode); + static SwFormatContentControl* CreatePoolDefault(sal_uInt16 nWhich); + SwContentControl* GetContentControl() { return m_pContentControl.get(); } +}; + +/// Stores the properties of a content control. +class SwContentControl : public sw::BroadcastingModify +{ + css::uno::WeakReference<css::text::XTextContent> m_wXContentControl; + + SwFormatContentControl* m_pFormat; + + /// Can be nullptr if not in a document for undo purposes. + SwTextNode* m_pTextNode; + +public: + SwTextContentControl* GetTextAttr() const; + + SwTextNode* GetTextNode() const { return m_pTextNode; } + + SwFormatContentControl* GetFormatContentControl() const { return m_pFormat; } + + void SetFormatContentControl(SwFormatContentControl* pFormat) { m_pFormat = pFormat; }; + + void NotifyChangeTextNode(SwTextNode* pTextNode); + + css::uno::WeakReference<css::text::XTextContent> GetXContentControl() const + { + return m_wXContentControl; + } + + void SetXContentControl(const css::uno::Reference<css::text::XTextContent>& xContentCnotrol) + { + m_wXContentControl = xContentCnotrol; + } + + virtual void SwClientNotify(const SwModify&, const SfxHint&) override; + + explicit SwContentControl(SwFormatContentControl* pFormat); + + virtual ~SwContentControl() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/inc/hintids.hxx b/sw/inc/hintids.hxx index f446d6ab306b..6f681de8998e 100644 --- a/sw/inc/hintids.hxx +++ b/sw/inc/hintids.hxx @@ -32,6 +32,7 @@ class SwUpdateAttr; class SwAttrSetChg; class SwDocPosUpdate; class SwFormatMeta; +class SwFormatContentControl; class SvXMLAttrContainerItem; class SwMsgPoolItem; class SwPtrMsgPoolItem; @@ -275,17 +276,17 @@ constexpr TypedWhichId<SwFormatCharFormat> RES_TXTATR_CHARFMT(52); constexpr TypedWhichId<SwFormatRuby> RES_TXTATR_CJK_RUBY(53); constexpr TypedWhichId<SvXMLAttrContainerItem> RES_TXTATR_UNKNOWN_CONTAINER(54); constexpr TypedWhichId<SwFormatField> RES_TXTATR_INPUTFIELD(55); -constexpr sal_uInt16 RES_TXTATR_WITHEND_END(56); +constexpr TypedWhichId<SwFormatContentControl> RES_TXTATR_CONTENTCONTROL(56); +constexpr sal_uInt16 RES_TXTATR_WITHEND_END(57); // all TextAttributes without an end constexpr sal_uInt16 RES_TXTATR_NOEND_BEGIN(RES_TXTATR_WITHEND_END); -constexpr TypedWhichId<SwFormatField> RES_TXTATR_FIELD(RES_TXTATR_NOEND_BEGIN); // 56 -constexpr TypedWhichId<SwFormatFlyCnt> RES_TXTATR_FLYCNT(57); -constexpr TypedWhichId<SwFormatFootnote> RES_TXTATR_FTN(58); -constexpr TypedWhichId<SwFormatField> RES_TXTATR_ANNOTATION(59); -constexpr TypedWhichId<SwFormatLineBreak> RES_TXTATR_LINEBREAK(60); -constexpr TypedWhichId<SfxBoolItem> RES_TXTATR_DUMMY1(61); -constexpr TypedWhichId<SfxBoolItem> RES_TXTATR_DUMMY2(62); +constexpr TypedWhichId<SwFormatField> RES_TXTATR_FIELD(RES_TXTATR_NOEND_BEGIN); // 57 +constexpr TypedWhichId<SwFormatFlyCnt> RES_TXTATR_FLYCNT(58); +constexpr TypedWhichId<SwFormatFootnote> RES_TXTATR_FTN(59); +constexpr TypedWhichId<SwFormatField> RES_TXTATR_ANNOTATION(60); +constexpr TypedWhichId<SwFormatLineBreak> RES_TXTATR_LINEBREAK(61); +constexpr TypedWhichId<SfxBoolItem> RES_TXTATR_DUMMY1(62); constexpr sal_uInt16 RES_TXTATR_NOEND_END(63); constexpr sal_uInt16 RES_TXTATR_END(RES_TXTATR_NOEND_END); diff --git a/sw/inc/textcontentcontrol.hxx b/sw/inc/textcontentcontrol.hxx new file mode 100644 index 000000000000..3410a6a35506 --- /dev/null +++ b/sw/inc/textcontentcontrol.hxx @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#pragma once + +#include "txatbase.hxx" + +class SwFormatContentControl; + +/// SwTextAttr subclass that tracks the location of the wrapped SwFormatContentControl. +class SW_DLLPUBLIC SwTextContentControl final : public SwTextAttrNesting +{ + SwTextContentControl(SwFormatContentControl& rAttr, sal_Int32 nStart, sal_Int32 nEnd); + +public: + static SwTextContentControl* CreateTextContentControl(SwTextNode* pTargetTextNode, + SwFormatContentControl& rAttr, + sal_Int32 nStart, sal_Int32 nEnd, + bool bIsCopy); + + ~SwTextContentControl() override; + + void ChgTextNode(SwTextNode* pNode); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/init.cxx b/sw/source/core/bastyp/init.cxx index 87cb7dfa3127..dcbe9856602b 100644 --- a/sw/source/core/bastyp/init.cxx +++ b/sw/source/core/bastyp/init.cxx @@ -99,6 +99,7 @@ #include <fmtline.hxx> #include <fmtlsplt.hxx> #include <fmtmeta.hxx> +#include <formatcontentcontrol.hxx> #include <fmtornt.hxx> #include <fmtpdsc.hxx> #include <fmtrfmrk.hxx> @@ -314,6 +315,7 @@ SfxItemInfo aSlotTab[] = { SID_ATTR_CHAR_CJK_RUBY, false }, // RES_TXTATR_CJK_RUBY { 0, true }, // RES_TXTATR_UNKNOWN_CONTAINER { 0, false }, // RES_TXTATR_INPUTFIELD + { 0, false }, // RES_TXTATR_CONTENTCONTROL { 0, false }, // RES_TXTATR_FIELD { 0, false }, // RES_TXTATR_FLYCNT @@ -321,7 +323,6 @@ SfxItemInfo aSlotTab[] = { 0, false }, // RES_TXTATR_ANNOTATION { 0, false }, // RES_TXTATR_LINEBREAK { 0, true }, // RES_TXTATR_DUMMY1 - { 0, true }, // RES_TXTATR_DUMMY2 { SID_ATTR_PARA_LINESPACE, true }, // RES_PARATR_LINESPACING { SID_ATTR_PARA_ADJUST, true }, // RES_PARATR_ADJUST @@ -512,6 +513,7 @@ void InitCore() aAttrTab[ RES_TXTATR_CJK_RUBY - POOLATTR_BEGIN ] = new SwFormatRuby( OUString() ); aAttrTab[ RES_TXTATR_UNKNOWN_CONTAINER - POOLATTR_BEGIN ] = new SvXMLAttrContainerItem( RES_TXTATR_UNKNOWN_CONTAINER ); aAttrTab[ RES_TXTATR_INPUTFIELD - POOLATTR_BEGIN ] = new SwFormatField( RES_TXTATR_INPUTFIELD ); + aAttrTab[ RES_TXTATR_CONTENTCONTROL - POOLATTR_BEGIN ] = new SwFormatContentControl( RES_TXTATR_CONTENTCONTROL ); aAttrTab[ RES_TXTATR_FIELD- POOLATTR_BEGIN ] = new SwFormatField( RES_TXTATR_FIELD ); aAttrTab[ RES_TXTATR_FLYCNT - POOLATTR_BEGIN ] = new SwFormatFlyCnt( nullptr ); @@ -521,7 +523,6 @@ void InitCore() // TextAttr - Dummies aAttrTab[ RES_TXTATR_DUMMY1 - POOLATTR_BEGIN ] = new SfxBoolItem( RES_TXTATR_DUMMY1 ); - aAttrTab[ RES_TXTATR_DUMMY2 - POOLATTR_BEGIN ] = new SfxBoolItem( RES_TXTATR_DUMMY2 ); aAttrTab[ RES_PARATR_LINESPACING- POOLATTR_BEGIN ] = new SvxLineSpacingItem( LINE_SPACE_DEFAULT_HEIGHT, RES_PARATR_LINESPACING ); aAttrTab[ RES_PARATR_ADJUST- POOLATTR_BEGIN ] = new SvxAdjustItem( SvxAdjust::Left, RES_PARATR_ADJUST ); diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx index 11f91cfe38b5..b3f76c03a4c3 100644 --- a/sw/source/core/doc/DocumentContentOperationsManager.cxx +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -1563,7 +1563,7 @@ namespace //local functions originally from docfmt.cxx SfxItemSetFixed< RES_TXTATR_REFMARK, RES_TXTATR_METAFIELD, RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY, - RES_TXTATR_INPUTFIELD, RES_TXTATR_INPUTFIELD> + RES_TXTATR_INPUTFIELD, RES_TXTATR_CONTENTCONTROL> aTextSet(rDoc.GetAttrPool()); aTextSet.Put( rChgSet ); diff --git a/sw/source/core/doc/dbgoutsw.cxx b/sw/source/core/doc/dbgoutsw.cxx index 0d294863dbed..73b0c47462eb 100644 --- a/sw/source/core/doc/dbgoutsw.cxx +++ b/sw/source/core/doc/dbgoutsw.cxx @@ -139,6 +139,7 @@ static std::map<sal_uInt16,OUString> & GetItemWhichMap() { RES_TXTATR_TOXMARK , "TXTATR_TOXMARK" }, { RES_TXTATR_CHARFMT , "TXTATR_CHARFMT" }, { RES_TXTATR_INPUTFIELD , "RES_TXTATR_INPUTFIELD" }, + { RES_TXTATR_CONTENTCONTROL , "RES_TXTATR_CONTENTCONTROL" }, { RES_TXTATR_CJK_RUBY , "TXTATR_CJK_RUBY" }, { RES_TXTATR_UNKNOWN_CONTAINER , "TXTATR_UNKNOWN_CONTAINER" }, { RES_TXTATR_META , "TXTATR_META" }, @@ -149,7 +150,6 @@ static std::map<sal_uInt16,OUString> & GetItemWhichMap() { RES_TXTATR_ANNOTATION , "TXTATR_ANNOTATION" }, { RES_TXTATR_LINEBREAK , "RES_TXTATR_LINEBREAK" }, { RES_TXTATR_DUMMY1 , "TXTATR_DUMMY1" }, - { RES_TXTATR_DUMMY2 , "TXTATR_DUMMY2" }, { RES_PARATR_LINESPACING , "PARATR_LINESPACING" }, { RES_PARATR_ADJUST , "PARATR_ADJUST" }, { RES_PARATR_SPLIT , "PARATR_SPLIT" }, diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx b/sw/source/core/txtnode/attrcontentcontrol.cxx new file mode 100644 index 000000000000..eb127f1a1ba6 --- /dev/null +++ b/sw/source/core/txtnode/attrcontentcontrol.cxx @@ -0,0 +1,237 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <formatcontentcontrol.hxx> + +#include <sal/log.hxx> + +#include <ndtxt.hxx> +#include <textcontentcontrol.hxx> + +using namespace com::sun::star; + +SwFormatContentControl* SwFormatContentControl::CreatePoolDefault(sal_uInt16 nWhich) +{ + return new SwFormatContentControl(nWhich); +} + +SwFormatContentControl::SwFormatContentControl(sal_uInt16 nWhich) + : SfxPoolItem(nWhich) + , m_pTextAttr(nullptr) +{ +} + +SwFormatContentControl::SwFormatContentControl( + const std::shared_ptr<SwContentControl>& pContentControl, sal_uInt16 nWhich) + : SfxPoolItem(nWhich) + , m_pContentControl(pContentControl) + , m_pTextAttr(nullptr) +{ + if (!pContentControl) + { + SAL_WARN("sw.core", "SwFormatContentControl ctor: no pContentControl?"); + } + // Not calling m_pContentControl->SetFormatContentControl(this) here; only from SetTextAttr. +} + +SwFormatContentControl::~SwFormatContentControl() +{ + if (m_pContentControl && (m_pContentControl->GetFormatContentControl() == this)) + { + NotifyChangeTextNode(nullptr); + m_pContentControl->SetFormatContentControl(nullptr); + } +} + +bool SwFormatContentControl::operator==(const SfxPoolItem& rOther) const +{ + return SfxPoolItem::operator==(rOther) + && m_pContentControl + == static_cast<const SwFormatContentControl&>(rOther).m_pContentControl; +} + +SwFormatContentControl* SwFormatContentControl::Clone(SfxItemPool* /*pPool*/) const +{ + // If this is indeed a copy, then DoCopy will be called later. + if (m_pContentControl) + { + return new SwFormatContentControl(m_pContentControl, Which()); + } + else + { + return new SwFormatContentControl(Which()); + } +} + +void SwFormatContentControl::SetTextAttr(SwTextContentControl* pTextAttr) +{ + if (m_pTextAttr && pTextAttr) + { + SAL_WARN("sw.core", "SwFormatContentControl::SetTextAttr: already has a text attribute"); + } + if (!m_pTextAttr && !pTextAttr) + { + SAL_WARN("sw.core", "SwFormatContentControl::SetTextAttr: no attribute to remove"); + } + m_pTextAttr = pTextAttr; + if (!m_pContentControl) + { + SAL_WARN("sw.core", "inserted SwFormatContentControl has no SwContentControl"); + } + // The SwContentControl should be able to find the current text attribute. + if (m_pContentControl) + { + if (pTextAttr) + { + m_pContentControl->SetFormatContentControl(this); + } + else if (m_pContentControl->GetFormatContentControl() == this) + { + // The text attribute is gone, so de-register from text node. + NotifyChangeTextNode(nullptr); + m_pContentControl->SetFormatContentControl(nullptr); + } + } +} + +void SwFormatContentControl::NotifyChangeTextNode(SwTextNode* pTextNode) +{ + // Not deleting m_pTextAttr here, SwNodes::ChgNode() doesn't do that, either. + if (!m_pContentControl) + { + SAL_WARN("sw.core", "SwFormatContentControl::NotifyChangeTextNode: no content control?"); + } + if (m_pContentControl && (m_pContentControl->GetFormatContentControl() == this)) + { + // Not calling Modify, that would call SwXContentControl::SwClientNotify. + m_pContentControl->NotifyChangeTextNode(pTextNode); + } +} + +// This SwFormatContentControl has been cloned and points at the same SwContentControl as the +// source: this function copies the SwContentControl. +void SwFormatContentControl::DoCopy(SwTextNode& rTargetTextNode) +{ + if (!m_pContentControl) + { + SAL_WARN("sw.core", "SwFormatContentControl::DoCopy: called for SwFormatContentControl " + "with no SwContentControl."); + return; + } + + m_pContentControl = std::make_shared<SwContentControl>(this); + m_pContentControl->NotifyChangeTextNode(&rTargetTextNode); +} + +SwContentControl::SwContentControl(SwFormatContentControl* pFormat) + : sw::BroadcastingModify() + , m_pFormat(pFormat) + , m_pTextNode(nullptr) +{ +} + +SwContentControl::~SwContentControl() {} + +SwTextContentControl* SwContentControl::GetTextAttr() const +{ + return m_pFormat ? m_pFormat->GetTextAttr() : nullptr; +} + +void SwContentControl::NotifyChangeTextNode(SwTextNode* pTextNode) +{ + m_pTextNode = pTextNode; + if (m_pTextNode && (GetRegisteredIn() != m_pTextNode)) + { + m_pTextNode->Add(this); + } + else if (!m_pTextNode) + { + EndListeningAll(); + } + if (!pTextNode) + { + // If the text node is gone, then invalidate clients (e.g. UNO object). + GetNotifier().Broadcast(SfxHint(SfxHintId::Deinitializing)); + } +} + +void SwContentControl::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + CallSwClientNotify(rHint); + GetNotifier().Broadcast(SfxHint(SfxHintId::DataChanged)); + + if (pLegacy->GetWhich() == RES_REMOVE_UNO_OBJECT) + { + // Invalidate cached uno object. + SetXContentControl(uno::Reference<text::XTextContent>()); + GetNotifier().Broadcast(SfxHint(SfxHintId::Deinitializing)); + } +} + +SwTextContentControl* SwTextContentControl::CreateTextContentControl(SwTextNode* pTargetTextNode, + SwFormatContentControl& rAttr, + sal_Int32 nStart, + sal_Int32 nEnd, bool bIsCopy) +{ + if (bIsCopy) + { + // rAttr is already cloned, now call DoCopy to copy the SwContentControl + if (!pTargetTextNode) + { + SAL_WARN("sw.core", + "SwTextContentControl ctor: cannot copy content control without target node"); + } + rAttr.DoCopy(*pTargetTextNode); + } + auto pTextContentControl(new SwTextContentControl(rAttr, nStart, nEnd)); + return pTextContentControl; +} + +SwTextContentControl::SwTextContentControl(SwFormatContentControl& rAttr, sal_Int32 nStart, + sal_Int32 nEnd) + : SwTextAttr(rAttr, nStart) + , SwTextAttrNesting(rAttr, nStart, nEnd) +{ + rAttr.SetTextAttr(this); + SetHasDummyChar(true); +} + +SwTextContentControl::~SwTextContentControl() +{ + auto& rFormatContentControl = static_cast<SwFormatContentControl&>(GetAttr()); + if (rFormatContentControl.GetTextAttr() == this) + { + rFormatContentControl.SetTextAttr(nullptr); + } +} + +void SwTextContentControl::ChgTextNode(SwTextNode* pNode) +{ + auto& rFormatContentControl = static_cast<SwFormatContentControl&>(GetAttr()); + if (rFormatContentControl.GetTextAttr() == this) + { + rFormatContentControl.NotifyChangeTextNode(pNode); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/thints.cxx b/sw/source/core/txtnode/thints.cxx index ec5ef9aa7679..f465b65a2e39 100644 --- a/sw/source/core/txtnode/thints.cxx +++ b/sw/source/core/txtnode/thints.cxx @@ -52,6 +52,8 @@ #include <ftnidx.hxx> #include <fmtruby.hxx> #include <fmtmeta.hxx> +#include <formatcontentcontrol.hxx> +#include <textcontentcontrol.hxx> #include <breakit.hxx> #include <doc.hxx> #include <IDocumentUndoRedo.hxx> @@ -105,7 +107,8 @@ SwpHints::SwpHints(const SwTextNode& rParent) static void TextAttrDelete( SwDoc & rDoc, SwTextAttr * const pAttr ) { if (RES_TXTATR_META == pAttr->Which() || - RES_TXTATR_METAFIELD == pAttr->Which()) + RES_TXTATR_METAFIELD == pAttr->Which() || + pAttr->Which() == RES_TXTATR_CONTENTCONTROL) { static_txtattr_cast<SwTextMeta *>(pAttr)->ChgTextNode(nullptr); // prevents ASSERT } @@ -161,7 +164,8 @@ bool isSelfNestable(const sal_uInt16 nWhich) (RES_TXTATR_INPUTFIELD == nWhich)) return false; assert((RES_TXTATR_META == nWhich) || - (RES_TXTATR_METAFIELD == nWhich)); + (RES_TXTATR_METAFIELD == nWhich) || + (RES_TXTATR_CONTENTCONTROL == nWhich)); return true; } @@ -173,7 +177,8 @@ bool isSplittable(const sal_uInt16 nWhich) return true; assert((RES_TXTATR_META == nWhich) || (RES_TXTATR_METAFIELD == nWhich) || - (RES_TXTATR_INPUTFIELD == nWhich)); + (RES_TXTATR_INPUTFIELD == nWhich) || + (RES_TXTATR_CONTENTCONTROL == nWhich)); return false; } @@ -300,7 +305,8 @@ void SwpHints::InsertNesting(SwTextAttrNesting & rNewHint) /** The following hints correspond to well-formed XML elements in ODF: -RES_TXTATR_INETFMT, RES_TXTATR_CJK_RUBY, RES_TXTATR_META, RES_TXTATR_METAFIELD +RES_TXTATR_INETFMT, RES_TXTATR_CJK_RUBY, RES_TXTATR_META, RES_TXTATR_METAFIELD, +RES_TXTATR_CONTENTCONTROL The writer core must ensure that these do not overlap; if they did, the document would not be storable as ODF. @@ -324,6 +330,8 @@ the result is properly nested. meta and meta-field must not be split, because they have xml:id. +content controls should not split, either. + These constraints result in the following design: RES_TXTATR_INETFMT: @@ -377,6 +385,7 @@ SwpHints::TryInsertNesting( SwTextNode & rNode, SwTextAttrNesting & rNewHint ) (RES_TXTATR_CJK_RUBY == nNewWhich) || (RES_TXTATR_META == nNewWhich) || (RES_TXTATR_METAFIELD == nNewWhich) || + (RES_TXTATR_CONTENTCONTROL == nNewWhich) || (RES_TXTATR_INPUTFIELD == nNewWhich)); NestList_t OverlappingExisting; // existing hints to be split @@ -1144,6 +1153,11 @@ SwTextAttr* MakeTextAttr( case RES_TXTATR_LINEBREAK: pNew = new SwTextLineBreak(static_cast<SwFormatLineBreak&>(rNew), nStt); break; + case RES_TXTATR_CONTENTCONTROL: + pNew = SwTextContentControl::CreateTextContentControl( + pTextNode, static_cast<SwFormatContentControl&>(rNew), nStt, nEnd, + bIsCopy == CopyOrNewType::Copy); + break; default: assert(RES_TXTATR_AUTOFMT == rNew.Which()); pNew = new SwTextAttrEnd( rNew, nStt, nEnd ); @@ -1271,6 +1285,11 @@ void SwTextNode::DestroyAttr( SwTextAttr* pAttr ) static_txtattr_cast<SwTextMeta*>(pAttr)->ChgTextNode(nullptr); } break; + case RES_TXTATR_CONTENTCONTROL: + { + static_txtattr_cast<SwTextContentControl*>(pAttr)->ChgTextNode(nullptr); + break; + } default: break; @@ -3172,6 +3191,10 @@ bool SwpHints::TryInsertHint( static_txtattr_cast<SwTextMeta *>(pHint)->ChgTextNode( &rNode ); break; + case RES_TXTATR_CONTENTCONTROL: + static_txtattr_cast<SwTextContentControl*>(pHint)->ChgTextNode( &rNode ); + break; + case RES_CHRATR_HIDDEN: rNode.SetCalcHiddenCharFlags(); break; @@ -3477,6 +3500,7 @@ sal_Unicode GetCharOfTextAttr( const SwTextAttr& rAttr ) case RES_TXTATR_FTN: case RES_TXTATR_META: case RES_TXTATR_METAFIELD: + case RES_TXTATR_CONTENTCONTROL: { cRet = CH_TXTATR_BREAKWORD; } diff --git a/sw/source/core/txtnode/txatbase.cxx b/sw/source/core/txtnode/txatbase.cxx index d4235a830358..cd18549c58d8 100644 --- a/sw/source/core/txtnode/txatbase.cxx +++ b/sw/source/core/txtnode/txatbase.cxx @@ -165,6 +165,8 @@ void SwTextAttr::dumpAsXml(xmlTextWriterPtr pWriter) const break; case RES_TXTATR_META: break; + case RES_TXTATR_CONTENTCONTROL: + break; default: SAL_WARN("sw.core", "Unhandled TXTATR"); break; diff --git a/sw/source/filter/html/css1atr.cxx b/sw/source/filter/html/css1atr.cxx index 882d7678ef55..150bba91ed8b 100644 --- a/sw/source/filter/html/css1atr.cxx +++ b/sw/source/filter/html/css1atr.cxx @@ -3473,6 +3473,7 @@ SwAttrFnTab const aCSS1AttrFnTab = { /* RES_TXTATR_CJK_RUBY */ nullptr, /* RES_TXTATR_UNKNOWN_CONTAINER */ nullptr, /* RES_TXTATR_INPUTFIELD */ nullptr, +/* RES_TXTATR_CONTENTCONTROL */ nullptr, /* RES_TXTATR_FIELD */ nullptr, /* RES_TXTATR_FLYCNT */ nullptr, @@ -3480,7 +3481,6 @@ SwAttrFnTab const aCSS1AttrFnTab = { /* RES_TXTATR_ANNOTATION */ nullptr, /* RES_TXTATR_LINEBREAK */ nullptr, /* RES_TXTATR_DUMMY1 */ nullptr, // Dummy: -/* RES_TXTATR_DUMMY2 */ nullptr, // Dummy: /* RES_PARATR_LINESPACING */ OutCSS1_SvxLineSpacing, /* RES_PARATR_ADJUST */ OutCSS1_SvxAdjust, diff --git a/sw/source/filter/html/htmlatr.cxx b/sw/source/filter/html/htmlatr.cxx index b34eed5bd41b..e50a5f06d6c1 100644 --- a/sw/source/filter/html/htmlatr.cxx +++ b/sw/source/filter/html/htmlatr.cxx @@ -3264,6 +3264,7 @@ SwAttrFnTab aHTMLAttrFnTab = { /* RES_TXTATR_CJK_RUBY */ nullptr, /* RES_TXTATR_UNKNOWN_CONTAINER */ nullptr, /* RES_TXTATR_INPUTFIELD */ OutHTML_SwFormatField, +/* RES_TXTATR_CONTENTCONTROL */ nullptr, /* RES_TXTATR_FIELD */ OutHTML_SwFormatField, /* RES_TXTATR_FLYCNT */ OutHTML_SwFlyCnt, @@ -3271,7 +3272,6 @@ SwAttrFnTab aHTMLAttrFnTab = { /* RES_TXTATR_ANNOTATION */ OutHTML_SwFormatField, /* RES_TXTATR_LINEBREAK */ OutHTML_SwFormatLineBreak, /* RES_TXTATR_DUMMY1 */ nullptr, // Dummy: -/* RES_TXTATR_DUMMY2 */ nullptr, // Dummy: /* RES_PARATR_LINESPACING */ nullptr, /* RES_PARATR_ADJUST */ OutHTML_SvxAdjust,
