sw/Library_sw.mk                                        |    1 
 sw/inc/formatcontentcontrol.hxx                         |  104 +++++++
 sw/inc/hintids.hxx                                      |   17 -
 sw/inc/textcontentcontrol.hxx                           |   41 ++
 sw/source/core/bastyp/init.cxx                          |    5 
 sw/source/core/doc/DocumentContentOperationsManager.cxx |    2 
 sw/source/core/doc/dbgoutsw.cxx                         |    2 
 sw/source/core/txtnode/attrcontentcontrol.cxx           |  237 ++++++++++++++++
 sw/source/core/txtnode/thints.cxx                       |   32 +-
 sw/source/core/txtnode/txatbase.cxx                     |    2 
 sw/source/filter/html/css1atr.cxx                       |    2 
 sw/source/filter/html/htmlatr.cxx                       |    2 
 12 files changed, 429 insertions(+), 18 deletions(-)

New commits:
commit 3dd4f3691458ea537bc1867386269694775cfbcb
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Wed Mar 30 08:16:21 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Wed Mar 30 09:49:03 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.
    
    Change-Id: I1e57f7756cf87041975371483ec4fc8abf875dfe
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132291
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/sw/Library_sw.mk b/sw/Library_sw.mk
index 30c53b6131b1..c63efce27993 100644
--- a/sw/Library_sw.mk
+++ b/sw/Library_sw.mk
@@ -427,6 +427,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 76068d21ec47..7c6a18723af2 100644
--- a/sw/inc/hintids.hxx
+++ b/sw/inc/hintids.hxx
@@ -33,6 +33,7 @@ class SwUpdateAttr;
 class SwAttrSetChg;
 class SwDocPosUpdate;
 class SwFormatMeta;
+class SwFormatContentControl;
 class SvXMLAttrContainerItem;
 class SwMsgPoolItem;
 class SwPtrMsgPoolItem;
@@ -276,17 +277,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 f7ad215602bf..461d7bc2da6e 100644
--- a/sw/source/core/bastyp/init.cxx
+++ b/sw/source/core/bastyp/init.cxx
@@ -100,6 +100,7 @@
 #include <fmtline.hxx>
 #include <fmtlsplt.hxx>
 #include <fmtmeta.hxx>
+#include <formatcontentcontrol.hxx>
 #include <fmtornt.hxx>
 #include <fmtpdsc.hxx>
 #include <fmtrfmrk.hxx>
@@ -315,6 +316,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
@@ -322,7 +324,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
@@ -513,6 +514,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 );
@@ -522,7 +524,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 ca26163d3d90..8eb9f3fc4864 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 ced0488228e4..20d6828f81a0 100644
--- a/sw/source/core/doc/dbgoutsw.cxx
+++ b/sw/source/core/doc/dbgoutsw.cxx
@@ -138,6 +138,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" },
@@ -148,7 +149,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 b6ca240fcffd..b15667ec6fcc 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>
@@ -104,7 +106,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
     }
@@ -160,7 +163,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;
 }
 
@@ -172,7 +176,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;
 }
 
@@ -299,7 +304,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.
@@ -323,6 +329,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:
@@ -376,6 +384,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
@@ -1143,6 +1152,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 );
@@ -1270,6 +1284,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;
@@ -3164,6 +3183,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;
@@ -3469,6 +3492,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 ef3274ac6faf..9e4de8c52527 100644
--- a/sw/source/filter/html/css1atr.cxx
+++ b/sw/source/filter/html/css1atr.cxx
@@ -3457,6 +3457,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,
@@ -3464,7 +3465,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 7239bd1a8caa..874437ede6ec 100644
--- a/sw/source/filter/html/htmlatr.cxx
+++ b/sw/source/filter/html/htmlatr.cxx
@@ -3281,6 +3281,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,
@@ -3288,7 +3289,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,

Reply via email to