tdf#143574 sw: textboxes in group shapes - part 1

Introduce SwTextBoxNode class to support grouped
textboxes, fixing the crash when entering a group
shape, trying to add a textbox to one of the shapes.

Test of crash fix: right click on a group shape.
Select the menu item "Enter group". Select one of
the shapes, and right click on it, and choose "Add
Text Box".

Note: textboxes in Writer are linked in SwFrameFormat
class. Each textbox consists of a text frame and a shape.
This object pair makes it possible to have complex
content inside any kind of shape (pictures, tables etc.
Group shapes have only one SwFrameFormat, but can have
many shapes so that means they can only have one textbox.
From now, each shape could have a textbox in a group.
Please note this is only a preparation for that. Filter
implementation and handlers are under development.

Change-Id: Iae35c118f0e67697b289c30d0fad4f5e16501c02
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/120452
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/inc/frmfmt.hxx b/sw/inc/frmfmt.hxx
index 24554c1..59aee54 100644
--- a/sw/inc/frmfmt.hxx
+++ b/sw/inc/frmfmt.hxx
@@ -27,6 +27,7 @@
#include "hintids.hxx"
#include "swdllapi.h"
#include <list>
#include "textboxhelper.hxx"

class SwFlyFrame;
class SwFlyDrawContact;
@@ -73,7 +74,7 @@
    // The assigned SwFrmFmt list.
    SwFrameFormats *m_ffList;

    SwFrameFormat *m_pOtherTextBoxFormat;
    SwTextBoxNode* m_pOtherTextBoxFormat;

    struct change_name
    {
@@ -99,10 +100,11 @@

    virtual void SwClientNotify(const SwModify&, const SfxHint&) override;

    SwFrameFormat* GetOtherTextBoxFormat() const { return m_pOtherTextBoxFormat; }
    void SetOtherTextBoxFormat( SwFrameFormat *pFormat );

public:

    SwTextBoxNode* GetOtherTextBoxFormat() const { return m_pOtherTextBoxFormat; };
    void SetOtherTextBoxFormat(SwTextBoxNode* pNew) { m_pOtherTextBoxFormat = pNew; };

    virtual ~SwFrameFormat() override;

    SwFrameFormat(SwFrameFormat const &) = default;
diff --git a/sw/inc/textboxhelper.hxx b/sw/inc/textboxhelper.hxx
index 3d1d4dc..3cd442e 100644
--- a/sw/inc/textboxhelper.hxx
+++ b/sw/inc/textboxhelper.hxx
@@ -56,11 +56,14 @@
    using SavedLink = std::map<const SwFrameFormat*, const SwFrameFormat*>;
    /// Maps a draw format to content.
    using SavedContent = std::map<const SwFrameFormat*, SwFormatContent>;
    /// Create a TextBox for a shape. If the second parameter is true,
    /// Create a TextBox for a shape. If the third parameter is true,
    /// the original text in the shape will be copied to the frame
    static void create(SwFrameFormat* pShape, bool bCopyText = false);
    /// Destroy a TextBox for a shape.
    static void destroy(SwFrameFormat* pShape);
    /// The textbox is created for the shape given by the pObject parameter.
    static void create(SwFrameFormat* pShape, SdrObject* pObject, bool bCopyText = false);
    /// Destroy a TextBox for a shape. If the format has more textboxes
    /// like group shapes, it will destroy only that textbox what belongs
    /// to the given pObject shape.
    static void destroy(SwFrameFormat* pShape, SdrObject* pObject);
    /// Get interface of a shape's TextBox, if there is any.
    static css::uno::Any queryInterface(const SwFrameFormat* pShape, const css::uno::Type& rType);

@@ -114,12 +117,16 @@
    /**
     * If we have an associated TextFrame, then return that.
     *
     * If we have more textboxes for this format (group shape), that one will be
     * returned, what belongs to the pObject.
     *
     * @param nType Expected frame format type.
     *              Valid types are RES_DRAWFRMFMT and RES_FLYFRMFMT.
     *
     * @see isTextBox
     */
    static SwFrameFormat* getOtherTextBoxFormat(const SwFrameFormat* pFormat, sal_uInt16 nType);
    static SwFrameFormat* getOtherTextBoxFormat(const SwFrameFormat* pFormat, sal_uInt16 nType,
                                                SdrObject* pObject = nullptr);
    /// If we have an associated TextFrame, then return that.
    static SwFrameFormat*
    getOtherTextBoxFormat(css::uno::Reference<css::drawing::XShape> const& xShape);
@@ -135,10 +142,18 @@
     * A text box consists of a coupled fly and draw format. Most times you
     * just want to check for a single type, otherwise you get duplicate results.
     *
     * @param nType Expected frame format input type.
     * @param pFormat: Is this format have a textbox?
     *
     * @param nType: Expected frame format input type.
     *              Valid types are RES_DRAWFRMFMT and RES_FLYFRMFMT.
     *
     * @param pObject: If the pFormat has more textboxes than one, like
     *                 groupshapes, the textbox what belongs to the given
     *                 pObject will be inspected. If this parameter nullptr,
     *                 the textbox what belongs to the pObject will only be inspected.
     */
    static bool isTextBox(const SwFrameFormat* pFormat, sal_uInt16 nType);
    static bool isTextBox(const SwFrameFormat* pFormat, sal_uInt16 nType,
                          SdrObject* pObject = nullptr);

    /// Returns true if the SdrObject has a SwTextFrame otherwise false
    static bool hasTextFrame(const SdrObject* pObj);
@@ -164,6 +179,71 @@
                             SavedLink& rSavedLinks);
};

/// Textboxes are basically textframe + shape pairs. This means one shape has one frame.
/// This is not enough for group shapes, because they have only one shape format and
/// can have many frame formats. This class provides if there is a group shape for example,
/// it can have multiple textboxes.
class SwTextBoxNode
{
    // One TextBox-entry
    struct SwTextBoxElement
    {
        // The textframe format
        SwFrameFormat* m_pTextBoxFormat;
        // The Draw object where the textbox belongs to
        SdrObject* m_pDrawObject;
        // This is for indicating if the textbox is in special case: for example during undo.
        bool m_bIsActive;
    };

    // This vector stores the textboxes what belongs to this node
    std::vector<SwTextBoxElement> m_pTextBoxes;
    // This is the pointer to the shape format, which has this node
    // (and the textboxes)
    SwFrameFormat* m_pOwnerShapeFormat;

public:
    // Not needed.
    SwTextBoxNode() = delete;

    // ctor
    SwTextBoxNode(SwFrameFormat* pOwnerShapeFormat);
    // dtor
    ~SwTextBoxNode();

    // default copy ctor is enough
    SwTextBoxNode(SwTextBoxNode&) = default;

    // This method adds a textbox entry to the shape
    // Parameters:
    //     pDrawObject: The shape what the textbox be added to.
    //     pNewTextBox: The newly created textbox format what will be added to the shape.
    void AddTextBox(SdrObject* pDrawObject, SwFrameFormat* pNewTextBox);

    // This will remove the textbox entry.
    // Parameters:
    //     pDrawObject: The shape which have the textbox to be deleted.
    void DelTextBox(SdrObject* pDrawObject);

    // This will return with the frame format of the textbox what belongs
    // to the given shape (pDrawObject)
    SwFrameFormat* GetTextBox(const SdrObject* pDrawObject) const;

    // Is this textbox has special state, undo for example?
    bool IsTextBoxActive(const SdrObject* pDrawObject) const;

    // Setters for the state flag.
    void SetTextBoxInactive(SdrObject* pDrawObject);
    void SetTextBoxActive(SdrObject* pDrawObject);

    // If this is a group shape, that returns true.
    bool IsGroupTextBox() const;
    // This returns with the shape what this class belongs to.
    SwFrameFormat* GetOwnerShape() { return m_pOwnerShapeFormat; };
    // This will give the current number of textboxes.
    size_t GetTextBoxCount() const { return m_pTextBoxes.size(); };
};

#endif // INCLUDED_SW_INC_TEXTBOXHELPER_HXX

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/qa/extras/layout/layout.cxx b/sw/qa/extras/layout/layout.cxx
index 51dd081..2f6f827 100644
--- a/sw/qa/extras/layout/layout.cxx
+++ b/sw/qa/extras/layout/layout.cxx
@@ -3564,7 +3564,7 @@
    CPPUNIT_ASSERT_EQUAL(OUString("Align me!"), xTxt->getText()->getString());

    // Add a textbox to the shape
    SwTextBoxHelper::create(pShape, true);
    SwTextBoxHelper::create(pShape, pShape->FindRealSdrObject(), true);

    // Check if the text moved from the shape to the frame
    auto pFormat = SwTextBoxHelper::getOtherTextBoxFormat(getShape(1));
diff --git a/sw/qa/extras/uiwriter/uiwriter3.cxx b/sw/qa/extras/uiwriter/uiwriter3.cxx
index 7ea98b9..590aa62 100644
--- a/sw/qa/extras/uiwriter/uiwriter3.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter3.cxx
@@ -1824,7 +1824,7 @@
    auto pShape = rFrmFormats.front();
    CPPUNIT_ASSERT(pShape);

    SwTextBoxHelper::create(pShape);
    SwTextBoxHelper::create(pShape, pShape->FindRealSdrObject());
    auto pTxBxFrm = SwTextBoxHelper::getOtherTextBoxFormat(getShape(1));
    CPPUNIT_ASSERT(pTxBxFrm);

@@ -1848,7 +1848,7 @@
    CPPUNIT_ASSERT(pShape);

    //Add a textbox
    SwTextBoxHelper::create(pShape);
    SwTextBoxHelper::create(pShape, pShape->FindRealSdrObject());
    SwFrameFormat* pTxBxFrm = SwTextBoxHelper::getOtherTextBoxFormat(getShape(1));
    CPPUNIT_ASSERT(pTxBxFrm);

@@ -1856,7 +1856,7 @@
    dispatchCommand(mxComponent, ".uno:Undo", {});

    //Add again
    SwTextBoxHelper::create(pShape);
    SwTextBoxHelper::create(pShape, pShape->FindRealSdrObject());
    pTxBxFrm = SwTextBoxHelper::getOtherTextBoxFormat(getShape(1));

    //This was nullptr because of unsuccessful re-adding
@@ -1903,7 +1903,7 @@
    CPPUNIT_ASSERT(pShape);

    // Add a textbox
    SwTextBoxHelper::create(pShape);
    SwTextBoxHelper::create(pShape, pShape->FindRealSdrObject());
    SwFrameFormat* pTxBxFrm = SwTextBoxHelper::getOtherTextBoxFormat(getShape(1));
    CPPUNIT_ASSERT(pTxBxFrm);

@@ -2454,7 +2454,7 @@
    CPPUNIT_ASSERT(pShape);

    // Add a textbox to the shape
    SwTextBoxHelper::create(pShape);
    SwTextBoxHelper::create(pShape, pShape->FindRealSdrObject());
    auto pTxBxFrm = SwTextBoxHelper::getOtherTextBoxFormat(getShape(1));
    CPPUNIT_ASSERT(pTxBxFrm);

diff --git a/sw/qa/uitest/data/tdf143574.odt b/sw/qa/uitest/data/tdf143574.odt
new file mode 100644
index 0000000..a2e961e
--- /dev/null
+++ b/sw/qa/uitest/data/tdf143574.odt
Binary files differ
diff --git a/sw/qa/uitest/writer_tests7/tdf143574.py b/sw/qa/uitest/writer_tests7/tdf143574.py
new file mode 100644
index 0000000..08e59b7
--- /dev/null
+++ b/sw/qa/uitest/writer_tests7/tdf143574.py
@@ -0,0 +1,39 @@
# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
#
# 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/.
#
from uitest.framework import UITestCase
import org.libreoffice.unotest
from uitest.uihelper.common import get_url_for_data_file
from libreoffice.uno.propertyvalue import mkPropertyValues

class tdf143574(UITestCase):
    def test_tdf143574(self):
        # load the sample file
        with self.ui_test.load_file(get_url_for_data_file("tdf143574.odt")):
            xWriterDoc = self.xUITest.getTopFocusWindow()
            xWriterEdit = xWriterDoc.getChild("writer_edit")
            document = self.ui_test.get_component()

            # check the shape type.
            self.assertEqual("com.sun.star.drawing.GroupShape", document.DrawPage.getByIndex(0).ShapeType)

            # select the shape.
            self.xUITest.executeCommand(".uno:JumpToNextFrame")
            self.ui_test.wait_until_child_is_available('metricfield')

            # go inside the group
            self.xUITest.executeCommand(".uno:EnterGroup");

            # select a shape in the group
            xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "TAB"}))

            # At this point the Writer crashed here before the fix.
            self.xUITest.executeCommand(".uno:AddTextBox");

            #follow up commit will introduce:
            #self.assertEqual(True, document.DrawPage.getByIndex(0).getByIndex(2).TextBox)

# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sw/source/core/doc/DocumentLayoutManager.cxx b/sw/source/core/doc/DocumentLayoutManager.cxx
index 1266e3e..1e071ef 100644
--- a/sw/source/core/doc/DocumentLayoutManager.cxx
+++ b/sw/source/core/doc/DocumentLayoutManager.cxx
@@ -490,8 +490,11 @@
        pDest->SetFormatAttr(aSet);

        // Link FLY and DRAW formats, so it becomes a text box
        pDest->SetOtherTextBoxFormat(pDestTextBox);
        pDestTextBox->SetOtherTextBoxFormat(pDest);
        auto pTextBox = new SwTextBoxNode(pDest);
        pTextBox->AddTextBox(pDest->FindRealSdrObject(), pDestTextBox);

        pDest->SetOtherTextBoxFormat(pTextBox);
        pDestTextBox->SetOtherTextBoxFormat(pTextBox);
    }

    if (pDest->GetName().isEmpty())
diff --git a/sw/source/core/doc/textboxhelper.cxx b/sw/source/core/doc/textboxhelper.cxx
index 410e232..2df4c62 100644
--- a/sw/source/core/doc/textboxhelper.cxx
+++ b/sw/source/core/doc/textboxhelper.cxx
@@ -28,11 +28,13 @@
#include <fmtsrnd.hxx>
#include <frmfmt.hxx>
#include <frameformats.hxx>
#include <dflyobj.hxx>

#include <editeng/unoprnms.hxx>
#include <editeng/memberids.h>
#include <svx/svdoashp.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdogrp.hxx>
#include <svl/itemiter.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <sal/log.hxx>
@@ -58,10 +60,14 @@

using namespace com::sun::star;

void SwTextBoxHelper::create(SwFrameFormat* pShape, bool bCopyText)
void SwTextBoxHelper::create(SwFrameFormat* pShape, SdrObject* pObject, bool bCopyText)
{
    if (dynamic_cast<SdrObjGroup*>(pObject->getParentSdrObjectFromSdrObject()))
        // The GroupShape Textbox creation method call comes here.
        return;

    // If TextBox wasn't enabled previously
    if (pShape->GetAttrSet().HasItem(RES_CNTNT) && pShape->GetOtherTextBoxFormat())
    if (pShape->GetOtherTextBoxFormat() && pShape->GetOtherTextBoxFormat()->GetTextBox(pObject))
        return;

    // Store the current text content of the shape
@@ -108,9 +114,20 @@
    assert(nullptr != dynamic_cast<SwDrawFrameFormat*>(pShape));
    assert(nullptr != dynamic_cast<SwFlyFrameFormat*>(pFormat));

    pShape->SetOtherTextBoxFormat(pFormat);
    pFormat->SetOtherTextBoxFormat(pShape);

    if (!pShape->GetOtherTextBoxFormat())
    {
        auto* pTextBox = new SwTextBoxNode(pShape);
        pTextBox->AddTextBox(pObject, pFormat);
        pShape->SetOtherTextBoxFormat(pTextBox);
        pFormat->SetOtherTextBoxFormat(pTextBox);
    }
    else
    {
        auto* pTextBox = pShape->GetOtherTextBoxFormat();
        pTextBox->AddTextBox(pObject, pFormat);
        pShape->SetOtherTextBoxFormat(pTextBox);
        pFormat->SetOtherTextBoxFormat(pTextBox);
    }
    // Initialize properties.
    uno::Reference<beans::XPropertySet> xPropertySet(xTextFrame, uno::UNO_QUERY);
    uno::Any aEmptyBorder = uno::makeAny(table::BorderLine2());
@@ -189,40 +206,45 @@
    }
}

void SwTextBoxHelper::destroy(SwFrameFormat* pShape)
void SwTextBoxHelper::destroy(SwFrameFormat* pShape, SdrObject* pObject)
{
    // If a TextBox was enabled previously
    if (pShape->GetAttrSet().HasItem(RES_CNTNT))
    auto pTextBox = pShape->GetOtherTextBoxFormat();
    if (pTextBox && pTextBox->IsTextBoxActive(pObject))
    {
        SwFrameFormat* pFormat = pShape->GetOtherTextBoxFormat();

        // Unlink the TextBox's text range from the original shape.
        pShape->ResetFormatAttr(RES_CNTNT);
        pTextBox->SetTextBoxInactive(pObject);

        // Delete the associated TextFrame.
        if (pFormat)
            pShape->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat(pFormat);
        pTextBox->DelTextBox(pObject);
    }
}

bool SwTextBoxHelper::isTextBox(const SwFrameFormat* pFormat, sal_uInt16 nType)
bool SwTextBoxHelper::isTextBox(const SwFrameFormat* pFormat, sal_uInt16 nType, SdrObject* pObject)
{
    SolarMutexGuard aGuard;
    assert(nType == RES_FLYFRMFMT || nType == RES_DRAWFRMFMT);
    if (!pFormat || pFormat->Which() != nType || !pFormat->GetAttrSet().HasItem(RES_CNTNT))
    if (!pFormat || pFormat->Which() != nType)
        return false;

    sal_uInt16 nOtherType = (pFormat->Which() == RES_FLYFRMFMT) ? sal_uInt16(RES_DRAWFRMFMT)
                                                                : sal_uInt16(RES_FLYFRMFMT);
    SwFrameFormat* pOtherFormat = pFormat->GetOtherTextBoxFormat();
    if (!pOtherFormat)
    auto pTextBox = pFormat->GetOtherTextBoxFormat();
    if (!pTextBox)
        return false;

    assert(pOtherFormat->Which() == nOtherType);
    if (pOtherFormat->Which() != nOtherType)
        return false;
    if (nType == RES_DRAWFRMFMT)
    {
        if (pObject)
            return pTextBox->GetTextBox(pObject);
        if (auto pObj = pFormat->FindRealSdrObject())
            return pTextBox->GetTextBox(pObj);
    }

    const SwFormatContent& rContent = pFormat->GetContent();
    return pOtherFormat->GetAttrSet().HasItem(RES_CNTNT) && pOtherFormat->GetContent() == rContent;
    if (nType == RES_FLYFRMFMT)
    {
        return pTextBox->GetOwnerShape();
    }

    return false;
}

bool SwTextBoxHelper::hasTextFrame(const SdrObject* pObj)
@@ -315,11 +337,25 @@
}

SwFrameFormat* SwTextBoxHelper::getOtherTextBoxFormat(const SwFrameFormat* pFormat,
                                                      sal_uInt16 nType)
                                                      sal_uInt16 nType, SdrObject* pObject)
{
    if (!isTextBox(pFormat, nType))
    SolarMutexGuard aGuard;
    if (!isTextBox(pFormat, nType, pObject))
        return nullptr;
    return pFormat->GetOtherTextBoxFormat();

    if (nType == RES_DRAWFRMFMT)
    {
        if (pObject)
            return pFormat->GetOtherTextBoxFormat()->GetTextBox(pObject);
        if (pFormat->FindRealSdrObject())
            return pFormat->GetOtherTextBoxFormat()->GetTextBox(pFormat->FindRealSdrObject());
        return nullptr;
    }
    if (nType == RES_FLYFRMFMT)
    {
        return pFormat->GetOtherTextBoxFormat()->GetOwnerShape();
    }
    return nullptr;
}

SwFrameFormat* SwTextBoxHelper::getOtherTextBoxFormat(uno::Reference<drawing::XShape> const& xShape)
@@ -341,7 +377,7 @@
        if (pFrameFormat)
        {
            auto pSdrObj = pFrameFormat->FindSdrObject();
            if (pSdrObj && pSdrObj->IsTextBox())
            if (pSdrObj)
            {
                return uno::Reference<css::text::XTextFrame>(pSdrObj->getUnoShape(),
                                                             uno::UNO_QUERY);
@@ -1323,4 +1359,134 @@
    return false;
}

SwTextBoxNode::SwTextBoxNode(SwFrameFormat* pOwnerShape)
{
    assert(pOwnerShape);
    assert(pOwnerShape->Which() == RES_DRAWFRMFMT);

    m_pOwnerShapeFormat = pOwnerShape;
    if (m_pTextBoxes.size())
        m_pTextBoxes.clear();
}

SwTextBoxNode::~SwTextBoxNode()
{
    // This only happens if the shape or the doc is in dtor.
    for (auto& rTextBoxEntry : m_pTextBoxes)
    {
        rTextBoxEntry.m_pDrawObject = nullptr;
        m_pOwnerShapeFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat(
            rTextBoxEntry.m_pTextBoxFormat);
    }
    m_pOwnerShapeFormat->SetOtherTextBoxFormat(nullptr);
}

void SwTextBoxNode::AddTextBox(SdrObject* pDrawObject, SwFrameFormat* pNewTextBox)
{
    assert(pNewTextBox);
    assert(pNewTextBox->Which() == RES_FLYFRMFMT);

    assert(pDrawObject);

    SwTextBoxElement aElem;
    aElem.m_bIsActive = true;
    aElem.m_pDrawObject = pDrawObject;
    aElem.m_pTextBoxFormat = pNewTextBox;
    SwFlyDrawObj* pSwFlyDraw = dynamic_cast<SwFlyDrawObj*>(pDrawObject);
    if (pSwFlyDraw)
    {
        pSwFlyDraw->SetTextBox(true);
    }
    m_pTextBoxes.push_back(aElem);
}

void SwTextBoxNode::DelTextBox(SdrObject* pDrawObject)
{
    assert(pDrawObject);
    if (m_pTextBoxes.size())
    {
        for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end(); it++)
        {
            if (it->m_pDrawObject == pDrawObject)
            {
                m_pOwnerShapeFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat(
                    it->m_pTextBoxFormat);
                m_pTextBoxes.erase(it);
                break;
            }
        }
    }
    if (!m_pTextBoxes.size())
    {
        m_pOwnerShapeFormat->SetOtherTextBoxFormat(nullptr);
    }
}

SwFrameFormat* SwTextBoxNode::GetTextBox(const SdrObject* pDrawObject) const
{
    assert(pDrawObject);
    if (m_pTextBoxes.size())
    {
        for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end(); it++)
        {
            if (it->m_pDrawObject == pDrawObject)
            {
                return it->m_pTextBoxFormat;
            }
        }
    }
    return nullptr;
}

bool SwTextBoxNode::IsTextBoxActive(const SdrObject* pDrawObject) const
{
    assert(pDrawObject);

    if (m_pTextBoxes.size())
    {
        for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end(); it++)
        {
            if (it->m_pDrawObject == pDrawObject)
            {
                return it->m_bIsActive;
            }
        }
    }
    return false;
}

void SwTextBoxNode::SetTextBoxActive(SdrObject* pDrawObject)
{
    assert(pDrawObject);

    if (m_pTextBoxes.size())
    {
        for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end(); it++)
        {
            if (it->m_pDrawObject == pDrawObject)
            {
                it->m_bIsActive = true;
            }
        }
    }
}

void SwTextBoxNode::SetTextBoxInactive(SdrObject* pDrawObject)
{
    assert(pDrawObject);

    if (m_pTextBoxes.size())
    {
        for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end(); it++)
        {
            if (it->m_pDrawObject == pDrawObject)
            {
                it->m_bIsActive = false;
            }
        }
    }
}

bool SwTextBoxNode::IsGroupTextBox() const { return m_pTextBoxes.size() > 1; }

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/draw/dflyobj.cxx b/sw/source/core/draw/dflyobj.cxx
index 1d51efc..a0a0b1e 100644
--- a/sw/source/core/draw/dflyobj.cxx
+++ b/sw/source/core/draw/dflyobj.cxx
@@ -1292,7 +1292,7 @@

bool SwVirtFlyDrawObj::IsTextBox() const
{
    return SwTextBoxHelper::isTextBox(GetFormat(), RES_FLYFRMFMT);
    return SwTextBoxHelper::isTextBox(GetFormat(), RES_FLYFRMFMT, const_cast<SwVirtFlyDrawObj*>(this));
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/layout/atrfrm.cxx b/sw/source/core/layout/atrfrm.cxx
index 932c361..df55753 100644
--- a/sw/source/core/layout/atrfrm.cxx
+++ b/sw/source/core/layout/atrfrm.cxx
@@ -2552,7 +2552,18 @@

    if( nullptr != m_pOtherTextBoxFormat )
    {
        m_pOtherTextBoxFormat->SetOtherTextBoxFormat( nullptr );
        auto pObj = FindRealSdrObject();
        if (Which() == RES_FLYFRMFMT && pObj)
        {
            // This is a fly-frame-format just del this
            // textbox entry from the draw-frame-format.
            m_pOtherTextBoxFormat->DelTextBox(pObj);

            // delete format after deleting the last textbox
            if (!m_pOtherTextBoxFormat->GetTextBoxCount())
                delete m_pOtherTextBoxFormat;
        }

        m_pOtherTextBoxFormat = nullptr;
    }
}
@@ -2580,45 +2591,6 @@
        SwFormat::SetName( rNewName, bBroadcast );
}

void SwFrameFormat::SetOtherTextBoxFormat( SwFrameFormat *pFormat )
{
    if( nullptr != pFormat )
    {
        assert( (Which() == RES_DRAWFRMFMT && pFormat->Which() == RES_FLYFRMFMT)
             || (Which() == RES_FLYFRMFMT && pFormat->Which() == RES_DRAWFRMFMT) );
        assert( nullptr == m_pOtherTextBoxFormat );
    }
    else
    {
        assert( nullptr != m_pOtherTextBoxFormat );
    }
    bool bChanged = m_pOtherTextBoxFormat != pFormat;
    m_pOtherTextBoxFormat = pFormat;

    SdrObject* pObj = FindSdrObject();

    if (pObj)
    {
        SwFlyDrawObj* pSwFlyDraw = dynamic_cast<SwFlyDrawObj*>(pObj);

        if (pSwFlyDraw)
            pSwFlyDraw->SetTextBox(true);
    }

    if (m_pOtherTextBoxFormat && bChanged && Which() == RES_DRAWFRMFMT)
    {
        // This is a shape of a shape+frame pair and my frame has changed. Make sure my content is
        // in sync with the frame's content.
        if (GetAttrSet().GetContent() != m_pOtherTextBoxFormat->GetAttrSet().GetContent())
        {
            SwAttrSet aSet(GetAttrSet());
            SwFormatContent aContent(m_pOtherTextBoxFormat->GetAttrSet().GetContent());
            aSet.Put(aContent);
            SetFormatAttr(aSet);
        }
    }
}

bool SwFrameFormat::supportsFullDrawingLayerFillAttributeSet() const
{
    return true;
diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx
index 02c3d54b..33b53994 100644
--- a/sw/source/core/layout/flycnt.cxx
+++ b/sw/source/core/layout/flycnt.cxx
@@ -529,6 +529,8 @@
    // wrong position will applied in that case. FollowTextFlow needs fix.
    if (pShapeFormat && !pShapeFormat->GetFollowTextFlow().GetValue() &&
        SwTextBoxHelper::getProperty(pShapeFormat,
            UNO_NAME_FRAME_ISAUTOMATIC_HEIGHT).hasValue() &&
        SwTextBoxHelper::getProperty(pShapeFormat,
            UNO_NAME_FRAME_ISAUTOMATIC_HEIGHT).get<bool>() )
    {
        // get the text area of the shape
diff --git a/sw/source/core/undo/undobj1.cxx b/sw/source/core/undo/undobj1.cxx
index bbbfaaa..54b031c 100644
--- a/sw/source/core/undo/undobj1.cxx
+++ b/sw/source/core/undo/undobj1.cxx
@@ -142,11 +142,17 @@
    {
        // recklessly assume that this thing will live longer than the
        // SwUndoFlyBase - not sure what could be done if that isn't the case...
        m_pFrameFormat->GetOtherTextBoxFormat()->SetOtherTextBoxFormat(m_pFrameFormat);
        m_pFrameFormat->GetOtherTextBoxFormat()->GetOwnerShape()->SetOtherTextBoxFormat(
            m_pFrameFormat->GetOtherTextBoxFormat());

        if (m_pFrameFormat->GetOtherTextBoxFormat()->Which() == RES_DRAWFRMFMT)
        SdrObject* pSdrObject
            = m_pFrameFormat->GetOtherTextBoxFormat()->GetOwnerShape()->FindSdrObject();
        if (pSdrObject && m_pFrameFormat->Which() == RES_FLYFRMFMT)
            m_pFrameFormat->GetOtherTextBoxFormat()->AddTextBox(pSdrObject, m_pFrameFormat);

        if (m_pFrameFormat->GetOtherTextBoxFormat()->GetOwnerShape()->Which() == RES_DRAWFRMFMT)
        {
            SdrObject* pSdrObject = m_pFrameFormat->GetOtherTextBoxFormat()->FindSdrObject();

            if (pSdrObject)
            {
                // Make sure the old UNO wrapper is no longer cached after changing the shape +
@@ -155,12 +161,10 @@
                pSdrObject->setUnoShape(nullptr);
            }
        }
        if (m_pFrameFormat->Which() == RES_DRAWFRMFMT)
        if (m_pFrameFormat->Which() == RES_FLYFRMFMT)
        {
            // This is a draw format and we just set the fly format's textbox pointer to this draw
            // format.  Sync the draw format's content with the fly format's content.
            SwFrameFormat* pFlyFormat = m_pFrameFormat->GetOtherTextBoxFormat();
            m_pFrameFormat->SetFormatAttr(pFlyFormat->GetContent());
            SwFrameFormat* pShapeFormat = m_pFrameFormat->GetOtherTextBoxFormat()->GetOwnerShape();
            pShapeFormat->SetFormatAttr(m_pFrameFormat->GetContent());
        }
    }

@@ -205,7 +209,7 @@

    if (m_pFrameFormat->GetOtherTextBoxFormat())
    {   // tdf#108867 clear that pointer
        m_pFrameFormat->GetOtherTextBoxFormat()->SetOtherTextBoxFormat(nullptr);
        m_pFrameFormat->GetOtherTextBoxFormat()->GetOwnerShape()->SetOtherTextBoxFormat(nullptr);
    }

    // all Uno objects should now log themselves off
diff --git a/sw/source/core/unocore/unodraw.cxx b/sw/source/core/unocore/unodraw.cxx
index 221f47f..2d25547a 100644
--- a/sw/source/core/unocore/unodraw.cxx
+++ b/sw/source/core/unocore/unodraw.cxx
@@ -1166,9 +1166,9 @@
                bool bValue(false);
                aValue >>= bValue;
                if (bValue)
                    SwTextBoxHelper::create(pFormat);
                    SwTextBoxHelper::create(pFormat, GetSvxShape()->GetSdrObject());
                else
                    SwTextBoxHelper::destroy(pFormat);
                    SwTextBoxHelper::destroy(pFormat, GetSvxShape()->GetSdrObject());

            }
            else if (pEntry->nWID == RES_CHAIN)
diff --git a/sw/source/uibase/shells/drawsh.cxx b/sw/source/uibase/shells/drawsh.cxx
index 89d8658..beb197c 100644
--- a/sw/source/uibase/shells/drawsh.cxx
+++ b/sw/source/uibase/shells/drawsh.cxx
@@ -372,7 +372,7 @@
            {
                SwFrameFormat* pFrameFormat = ::FindFrameFormat(pObj);
                if (pFrameFormat)
                    SwTextBoxHelper::create(pFrameFormat, pObj->HasText());
                    SwTextBoxHelper::create(pFrameFormat, pObj, pObj->HasText());
            }
            break;
        }
@@ -382,7 +382,7 @@
            {
                SwFrameFormat* pFrameFormat = ::FindFrameFormat(pObj);
                if (pFrameFormat)
                    SwTextBoxHelper::destroy(pFrameFormat);
                    SwTextBoxHelper::destroy(pFrameFormat, pObj);
            }
            break;
        }