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: */