sw/Library_sw.mk                             |    1 
 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/inc/unocontentcontrol.hxx     |  139 +++++
 sw/source/core/unocore/unocoll.cxx           |    7 
 sw/source/core/unocore/unocontentcontrol.cxx |  688 +++++++++++++++++++++++++++
 sw/source/core/unocore/unoobj.cxx            |   37 +
 sw/source/core/unocore/unotext.cxx           |    4 
 10 files changed, 905 insertions(+), 2 deletions(-)

New commits:
commit c4268efef25129b162884312b15527f1e5c3bcb0
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Thu Mar 31 09:43:24 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Thu Mar 31 10:37:07 2022 +0200

    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 <vmik...@collabora.com>
    Tested-by: Jenkins

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)
     {

Reply via email to