sw/inc/textboxhelper.hxx                     |   11 +++
 sw/qa/extras/uiwriter/data/tdf149550.docx    |binary
 sw/qa/extras/uiwriter/uiwriter6.cxx          |   23 +++++++
 sw/source/core/doc/DocumentLayoutManager.cxx |   65 +--------------------
 sw/source/core/doc/textboxhelper.cxx         |   81 ++++++++++++++++++++++++++-
 5 files changed, 118 insertions(+), 62 deletions(-)

New commits:
commit 56b3fb0cc14af8dd2a1ddaee4566cbf53fe05c4c
Author:     Attila Bakos (NISZ) <bakos.attilakar...@nisz.hu>
AuthorDate: Tue Jun 14 10:38:46 2022 +0200
Commit:     László Németh <nem...@numbertext.org>
CommitDate: Wed Jun 29 13:06:06 2022 +0200

    tdf#149550 sw: fix crash by implementing nested textbox copy
    
    Grouped shapes with a nested textbox were copied without
    the textbox with frequent crashing.
    
    Regression from commit 2951cbdf3a6e2b62461665546b47e1d253fcb834
    "tdf#143574 OOXML export/import of textboxes in group shapes".
    
    Change-Id: Ie2cc24f10706d8999026dc92ebad21f2c5673003
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135815
    Tested-by: László Németh <nem...@numbertext.org>
    Reviewed-by: László Németh <nem...@numbertext.org>
    (cherry picked from commit a94edb7f9b8987518a1757a873d2c1fc94efa327)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136550
    Tested-by: Jenkins

diff --git a/sw/inc/textboxhelper.hxx b/sw/inc/textboxhelper.hxx
index d851a0fda7d2..112b312a0dab 100644
--- a/sw/inc/textboxhelper.hxx
+++ b/sw/inc/textboxhelper.hxx
@@ -26,6 +26,7 @@ class SdrObject;
 class SfxItemSet;
 class SwFrameFormat;
 class SwFrameFormats;
+class SwFormatAnchor;
 class SwFormatContent;
 class SwDoc;
 namespace tools
@@ -203,6 +204,8 @@ class SwTextBoxNode
     // (and the textboxes)
     SwFrameFormat* m_pOwnerShapeFormat;
 
+    mutable bool m_bIsCloningInProgress;
+
 public:
     // Not needed.
     SwTextBoxNode() = delete;
@@ -250,6 +253,14 @@ public:
     size_t GetTextBoxCount() const { return m_pTextBoxes.size(); };
     // Returns with a const collection of textboxes owned by this node.
     std::map<SdrObject*, SwFrameFormat*> GetAllTextBoxes() const;
+
+    void Clone(SwDoc* pDoc, const SwFormatAnchor& rNewAnc, SwFrameFormat* 
o_pTarget, bool bSetAttr,
+               bool bMakeFrame) const;
+
+private:
+    void Clone_Impl(SwDoc* pDoc, const SwFormatAnchor& rNewAnc, SwFrameFormat* 
o_pTarget,
+                    const SdrObject* pSrcObj, SdrObject* pDestObj, bool 
bSetAttr,
+                    bool bMakeFrame) const;
 };
 
 #endif // INCLUDED_SW_INC_TEXTBOXHELPER_HXX
diff --git a/sw/qa/extras/uiwriter/data/tdf149550.docx 
b/sw/qa/extras/uiwriter/data/tdf149550.docx
new file mode 100644
index 000000000000..3434fc1fff93
Binary files /dev/null and b/sw/qa/extras/uiwriter/data/tdf149550.docx differ
diff --git a/sw/qa/extras/uiwriter/uiwriter6.cxx 
b/sw/qa/extras/uiwriter/uiwriter6.cxx
index cf37fe5f28ee..b7187568cff4 100644
--- a/sw/qa/extras/uiwriter/uiwriter6.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter6.cxx
@@ -2014,6 +2014,29 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testHatchFill)
     CPPUNIT_ASSERT_EQUAL(sal_Int32(30), getProperty<sal_Int32>(getShape(1), 
"FillTransparence"));
 }
 
+CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testNestedGroupTextBoxCopyCrash)
+{
+    createSwDoc(DATA_DIRECTORY, "tdf149550.docx");
+
+    dispatchCommand(mxComponent, ".uno:SelectAll", {});
+    Scheduler::ProcessEventsToIdle();
+    dispatchCommand(mxComponent, ".uno:Copy", {});
+    Scheduler::ProcessEventsToIdle();
+    // This crashed here before the fix.
+    SwXTextDocument* pXTextDocument = 
dynamic_cast<SwXTextDocument*>(mxComponent.get());
+    CPPUNIT_ASSERT(pXTextDocument);
+    pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_ESCAPE);
+    Scheduler::ProcessEventsToIdle();
+    dispatchCommand(mxComponent, ".uno:Paste", {});
+    Scheduler::ProcessEventsToIdle();
+
+    CPPUNIT_ASSERT_MESSAGE("Where is the doc, it crashed, isn't it?!", 
mxComponent);
+
+    auto pLayout = parseLayoutDump();
+    // There must be 2 textboxes!
+    assertXPath(pLayout, "/root/page/body/txt/anchored/fly[2]");
+}
+
 CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testCaptionShape)
 {
     createSwDoc();
diff --git a/sw/source/core/doc/DocumentLayoutManager.cxx 
b/sw/source/core/doc/DocumentLayoutManager.cxx
index 006501b3aa36..a03d5dc1d60d 100644
--- a/sw/source/core/doc/DocumentLayoutManager.cxx
+++ b/sw/source/core/doc/DocumentLayoutManager.cxx
@@ -463,67 +463,6 @@ SwFrameFormat *DocumentLayoutManager::CopyLayoutFormat(
     if( bMakeFrames )
         pDest->MakeFrames();
 
-    // If the draw format has a TextBox, then copy its fly format as well.
-    if (rSource.Which() == RES_DRAWFRMFMT && rSource.GetOtherTextBoxFormats())
-    {
-        auto pObj = rSource.FindRealSdrObject();
-        auto pTextBoxNd = 
std::make_shared<SwTextBoxNode>(SwTextBoxNode(pDest));
-        pDest->SetOtherTextBoxFormats(pTextBoxNd);
-
-        if (pObj)
-        {
-            const bool bIsGroupObj = pObj->getChildrenOfSdrObject();
-            for (size_t it = 0;
-                 it < (bIsGroupObj ? 
pObj->getChildrenOfSdrObject()->GetObjCount() : 1); it++)
-            {
-                auto pChild = bIsGroupObj ? 
pObj->getChildrenOfSdrObject()->GetObj(it)
-                                          : const_cast<SdrObject*>(pObj);
-                if (auto pSourceTextBox
-                    = SwTextBoxHelper::getOtherTextBoxFormat(&rSource, 
RES_DRAWFRMFMT, pChild))
-                {
-                    SwFormatAnchor boxAnchor(rNewAnchor);
-                    if (RndStdIds::FLY_AS_CHAR == boxAnchor.GetAnchorId())
-                    {
-                        // AS_CHAR *must not* be set on textbox fly-frame
-                        boxAnchor.SetType(RndStdIds::FLY_AT_CHAR);
-                    }
-                    // presumably these anchors are supported though not sure
-                    assert(RndStdIds::FLY_AT_CHAR == boxAnchor.GetAnchorId()
-                           || RndStdIds::FLY_AT_PARA == boxAnchor.GetAnchorId()
-                           || boxAnchor.GetAnchorId() == 
RndStdIds::FLY_AT_PAGE);
-
-                    if (!bMakeFrames && rNewAnchor.GetAnchorId() == 
RndStdIds::FLY_AS_CHAR)
-                    {
-                        // If the draw format is as-char, then it will be 
copied with bMakeFrames=false, but
-                        // doing the same for the fly format would result in 
not making fly frames at all.
-                        bMakeFrames = true;
-                    }
-                    SwFrameFormat* pDestTextBox
-                        = CopyLayoutFormat(*pSourceTextBox, boxAnchor, 
bSetTextFlyAtt, bMakeFrames);
-
-                    SwAttrSet aSet(pDest->GetAttrSet());
-                    SwFormatContent aContent(
-                        
pDestTextBox->GetContent().GetContentIdx()->GetNode().GetStartNode());
-                    aSet.Put(aContent);
-                    pDest->SetFormatAttr(aSet);
-
-                    // Link FLY and DRAW formats, so it becomes a text box
-                    SdrObject* pNewObj = pDest->FindRealSdrObject();
-                    if (bIsGroupObj && pNewObj
-                        && pNewObj->getChildrenOfSdrObject()
-                        && (pNewObj->getChildrenOfSdrObject()->GetObjCount() > 
it)
-                        && pNewObj->getChildrenOfSdrObject()->GetObj(it))
-                        pNewObj = 
pNewObj->getChildrenOfSdrObject()->GetObj(it);
-                    pTextBoxNd->AddTextBox(pNewObj, pDestTextBox);
-                    pDestTextBox->SetOtherTextBoxFormats(pTextBoxNd);
-                }
-
-                if (!bIsGroupObj)
-                    break;
-            }
-        }
-    }
-
     if (pDest->GetName().isEmpty())
     {
         // Format name should have unique name. Let's use object name as a 
fallback
@@ -532,6 +471,10 @@ SwFrameFormat *DocumentLayoutManager::CopyLayoutFormat(
             pDest->SetName(pObj->GetName());
     }
 
+    // If the draw format has a TextBox, then copy its fly format as well.
+    if (const auto& pTextBoxes = rSource.GetOtherTextBoxFormats())
+        pTextBoxes->Clone(&m_rDoc, rNewAnchor, pDest, bSetTextFlyAtt, 
bMakeFrames);
+
     return pDest;
 }
 
diff --git a/sw/source/core/doc/textboxhelper.cxx 
b/sw/source/core/doc/textboxhelper.cxx
index 68d42b7e91f7..1ac5ccf71787 100644
--- a/sw/source/core/doc/textboxhelper.cxx
+++ b/sw/source/core/doc/textboxhelper.cxx
@@ -257,7 +257,7 @@ void SwTextBoxHelper::set(SwFrameFormat* pShapeFormat, 
SdrObject* pObj,
     {
         // If the shape do not have a texbox node and textbox,
         // create that for the shape.
-        auto pTextBox = std::shared_ptr<SwTextBoxNode>(new 
SwTextBoxNode(pShapeFormat));
+        auto pTextBox = 
std::make_shared<SwTextBoxNode>(SwTextBoxNode(pShapeFormat));
         pTextBox->AddTextBox(pObj, pFormat);
         pShapeFormat->SetOtherTextBoxFormats(pTextBox);
         pFormat->SetOtherTextBoxFormats(pTextBox);
@@ -1628,6 +1628,8 @@ SwTextBoxNode::SwTextBoxNode(SwFrameFormat* pOwnerShape)
     assert(pOwnerShape);
     assert(pOwnerShape->Which() == RES_DRAWFRMFMT);
 
+    m_bIsCloningInProgress = false;
+
     m_pOwnerShapeFormat = pOwnerShape;
     if (!m_pTextBoxes.empty())
         m_pTextBoxes.clear();
@@ -1786,4 +1788,81 @@ std::map<SdrObject*, SwFrameFormat*> 
SwTextBoxNode::GetAllTextBoxes() const
     return aRet;
 }
 
+void SwTextBoxNode::Clone(SwDoc* pDoc, const SwFormatAnchor& rNewAnc, 
SwFrameFormat* o_pTarget,
+                          bool bSetAttr, bool bMakeFrame) const
+{
+    if (!o_pTarget || !pDoc)
+        return;
+
+    if (o_pTarget->Which() != RES_DRAWFRMFMT)
+        return;
+
+    if (m_bIsCloningInProgress)
+        return;
+
+    m_bIsCloningInProgress = true;
+
+    Clone_Impl(pDoc, rNewAnc, o_pTarget, m_pOwnerShapeFormat->FindSdrObject(),
+               o_pTarget->FindSdrObject(), bSetAttr, bMakeFrame);
+
+    m_bIsCloningInProgress = false;
+}
+
+void SwTextBoxNode::Clone_Impl(SwDoc* pDoc, const SwFormatAnchor& rNewAnc, 
SwFrameFormat* o_pTarget,
+                               const SdrObject* pSrcObj, SdrObject* pDestObj, 
bool bSetAttr,
+                               bool bMakeFrame) const
+{
+    if (!pSrcObj || !pDestObj)
+        return;
+
+    auto pSrcList = pSrcObj->getChildrenOfSdrObject();
+    auto pDestList = pDestObj->getChildrenOfSdrObject();
+
+    if (pSrcList && pDestList)
+    {
+        if (pSrcList->GetObjCount() != pDestList->GetObjCount())
+            return;
+
+        for (size_t i = 0; i < pSrcList->GetObjCount(); ++i)
+        {
+            Clone_Impl(pDoc, rNewAnc, o_pTarget, pSrcList->GetObj(i), 
pDestList->GetObj(i),
+                       bSetAttr, bMakeFrame);
+        }
+        return;
+    }
+
+    if (!pSrcList && !pDestList)
+    {
+        if (auto pSrcFormat = GetTextBox(pSrcObj))
+        {
+            SwFormatAnchor aNewAnchor(rNewAnc);
+            if (aNewAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR)
+            {
+                aNewAnchor.SetType(RndStdIds::FLY_AT_CHAR);
+
+                if (!bMakeFrame)
+                    bMakeFrame = true;
+            }
+
+            if (auto pTargetFormat = 
pDoc->getIDocumentLayoutAccess().CopyLayoutFormat(
+                    *pSrcFormat, aNewAnchor, bSetAttr, bMakeFrame))
+            {
+                if (!o_pTarget->GetOtherTextBoxFormats())
+                {
+                    auto pNewTextBoxes = 
std::make_shared<SwTextBoxNode>(SwTextBoxNode(o_pTarget));
+                    o_pTarget->SetOtherTextBoxFormats(pNewTextBoxes);
+                    pNewTextBoxes->AddTextBox(pDestObj, pTargetFormat);
+                    pTargetFormat->SetOtherTextBoxFormats(pNewTextBoxes);
+                }
+                else
+                {
+                    o_pTarget->GetOtherTextBoxFormats()->AddTextBox(pDestObj, 
pTargetFormat);
+                    
pTargetFormat->SetOtherTextBoxFormats(o_pTarget->GetOtherTextBoxFormats());
+                }
+                o_pTarget->SetFormatAttr(pTargetFormat->GetContent());
+            }
+        }
+    }
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to