diff options
author | Miklos Vajna <vmiklos@collabora.com> | 2022-03-31 09:43:24 +0200 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2022-03-31 10:37:07 +0200 |
commit | c4268efef25129b162884312b15527f1e5c3bcb0 (patch) | |
tree | 69ce3e04167f2ef60540d79340a7210fa7e9c7be | |
parent | f0c7746e83e9c9f17f7c415ef3423c882a6de1ca (diff) |
sw content controls: add UNO API to insert this
This only allows the default richText type, the others are not yet
handled.
Change-Id: I39f73ccd9e2c1f0db5735cf97301b01482063d1d
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132350
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins
-rw-r--r-- | sw/Library_sw.mk | 1 | ||||
-rw-r--r-- | sw/inc/unobaseclass.hxx | 1 | ||||
-rw-r--r-- | sw/inc/unocoll.hxx | 1 | ||||
-rw-r--r-- | sw/inc/unotextcursor.hxx | 1 | ||||
-rw-r--r-- | sw/qa/core/unocore/unocore.cxx | 28 | ||||
-rw-r--r-- | sw/source/core/inc/unocontentcontrol.hxx | 139 | ||||
-rw-r--r-- | sw/source/core/unocore/unocoll.cxx | 7 | ||||
-rw-r--r-- | sw/source/core/unocore/unocontentcontrol.cxx | 688 | ||||
-rw-r--r-- | sw/source/core/unocore/unoobj.cxx | 37 | ||||
-rw-r--r-- | sw/source/core/unocore/unotext.cxx | 4 |
10 files changed, 905 insertions, 2 deletions
diff --git a/sw/Library_sw.mk b/sw/Library_sw.mk index c63efce27993..609be7281d53 100644 --- a/sw/Library_sw.mk +++ b/sw/Library_sw.mk @@ -481,6 +481,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 5982a5d699ec..049d28b9a748 100644 --- a/sw/inc/unobaseclass.hxx +++ b/sw/inc/unobaseclass.hxx @@ -50,6 +50,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 3a569d64feef..f7181a22b2b1 100644 --- a/sw/qa/core/unocore/unocore.cxx +++ b/sw/qa/core/unocore/unocore.cxx @@ -25,6 +25,7 @@ #include <docsh.hxx> #include <ndtxt.hxx> #include <textlinebreak.hxx> +#include <textcontentcontrol.hxx> using namespace ::com::sun::star; @@ -309,6 +310,33 @@ CPPUNIT_TEST_FIXTURE(SwModelTestBase, testUserFieldTooltip) CPPUNIT_ASSERT_EQUAL(aExpected, getProperty<OUString>(xFieldProps, "Title")); } +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..63d80cc831fc --- /dev/null +++ b/sw/source/core/unocore/unocontentcontrol.cxx @@ -0,0 +1,688 @@ +/* -*- 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) +{ + std::unique_lock aGuard(m_pImpl->m_Mutex); + m_pImpl->m_EventListeners.addInterface(aGuard, xListener); +} + +void SAL_CALL +SwXContentControl::removeEventListener(const uno::Reference<lang::XEventListener>& xListener) +{ + std::unique_lock aGuard(m_pImpl->m_Mutex); + m_pImpl->m_EventListeners.removeInterface(aGuard, 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 94e2e5d37e06..72376372fe2f 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> @@ -817,6 +818,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) { |