sw/inc/textboxhelper.hxx | 19 + sw/inc/unomid.h | 3 sw/inc/unoprnms.hxx | 1 sw/qa/extras/layout/data/TextBoxFrame.odt |binary sw/qa/extras/layout/layout2.cxx | 41 ++++ sw/qa/uitest/data/ComplexGroupShapeTest.odt |binary sw/qa/uitest/writer_tests2/ComplexGroupShapeTest.py | 127 ++++++++++++ sw/source/core/doc/docdraw.cxx | 56 ++++- sw/source/core/doc/textboxhelper.cxx | 203 +++++++++++++++++++- sw/source/core/draw/dcontact.cxx | 6 sw/source/core/draw/dview.cxx | 9 sw/source/core/unocore/unodraw.cxx | 77 +++++-- sw/source/core/unocore/unomap.cxx | 3 13 files changed, 495 insertions(+), 50 deletions(-)
New commits: commit d4b87c11b451cb08aa950e09efed3cc6fa1422f3 Author: Attila Bakos (NISZ) <bakos.attilakar...@nisz.hu> AuthorDate: Tue Nov 30 15:38:26 2021 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Tue Jan 17 09:24:00 2023 +0000 tdf#143574 sw: textboxes in group shapes -- part 4 A new UNO property has been added and implemented for the filters. This provides the possibility of assigning textboxes in the filter at import time via UNO. Follow-up to commit e5650de86072b9db586a4532b5239acda77598c4 "tdf#143574 sw: textboxes in group shapes - part 3 take 2". Change-Id: I58c445cb7f6d865c1d82dbe68f985e4c11ff832e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/126162 Tested-by: László Németh <nem...@numbertext.org> Reviewed-by: László Németh <nem...@numbertext.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143366 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/sw/inc/textboxhelper.hxx b/sw/inc/textboxhelper.hxx index 924b3e6b5c91..1a0cadabc0e9 100644 --- a/sw/inc/textboxhelper.hxx +++ b/sw/inc/textboxhelper.hxx @@ -60,6 +60,9 @@ public: /// the original text in the shape will be copied to the frame /// The textbox is created for the shape given by the pObject parameter. static void create(SwFrameFormat* pShape, SdrObject* pObject, bool bCopyText = false); + /// Sets the given textframe as textbox for the given (group member) shape. + static void set(SwFrameFormat* pShape, SdrObject* pObject, + css::uno::Reference<css::text::XTextFrame> xNew); /// 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. diff --git a/sw/inc/unomid.h b/sw/inc/unomid.h index d249b32fc25a..9f413509ae1c 100644 --- a/sw/inc/unomid.h +++ b/sw/inc/unomid.h @@ -151,6 +151,9 @@ // SwFormatFollowTextFlow #define MID_FOLLOW_TEXT_FLOW 0 +#define MID_TEXT_BOX 0 +#define MID_TEXT_BOX_CONTENT 1 + #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx index 3a8df7c69182..bb3ee1f47b3c 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -74,6 +74,7 @@ #define UNO_NAME_FOOTER_RIGHT_MARGIN "FooterRightMargin" #define UNO_NAME_TEXT_RANGE "TextRange" #define UNO_NAME_TEXT_BOX "TextBox" +#define UNO_NAME_TEXT_BOX_CONTENT "TextBoxContent" #define UNO_NAME_NAME "Name" #define UNO_NAME_CHAR_STYLE_NAME "CharStyleName" #define UNO_NAME_ANCHOR_CHAR_STYLE_NAME "AnchorCharStyleName" diff --git a/sw/qa/extras/layout/data/TextBoxFrame.odt b/sw/qa/extras/layout/data/TextBoxFrame.odt new file mode 100644 index 000000000000..a6155e34fdfb Binary files /dev/null and b/sw/qa/extras/layout/data/TextBoxFrame.odt differ diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx index 466f640beeee..9fd595eafefc 100644 --- a/sw/qa/extras/layout/layout2.cxx +++ b/sw/qa/extras/layout/layout2.cxx @@ -1647,6 +1647,47 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf141220) CPPUNIT_ASSERT_LESS(static_cast<sal_Int32>(15), nTextBoxTop - nShapeTop); } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, TestTextBoxChangeViaUNO) +{ + CPPUNIT_ASSERT(createSwDoc(DATA_DIRECTORY, "TextBoxFrame.odt")); + // this file has a shape and a frame inside. Try to set up + // the frame for the shape as textbox. Before this was not + // implemented. This will be necesary for proper WPG import. + + CPPUNIT_ASSERT_EQUAL_MESSAGE("There must be a shape and a frame!", 2, getShapes()); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("This must be a custom shape!", + OUString("com.sun.star.drawing.CustomShape"), + getShape(1)->getShapeType()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("This must be a frame shape!", OUString("FrameShape"), + getShape(2)->getShapeType()); + + CPPUNIT_ASSERT_MESSAGE("This is not supposed to be a textbox!", + !uno::Reference<beans::XPropertySet>(getShape(1), uno::UNO_QUERY_THROW) + ->getPropertyValue("TextBox") + .get<bool>()); + // Without the fix it will crash at this line: + CPPUNIT_ASSERT_MESSAGE("This is not supposed to be a textbox!", + !uno::Reference<beans::XPropertySet>(getShape(1), uno::UNO_QUERY_THROW) + ->getPropertyValue("TextBoxContent") + .hasValue()); + + // So now set the frame as textbox for the shape! + uno::Reference<beans::XPropertySet>(getShape(1), uno::UNO_QUERY_THROW) + ->setPropertyValue("TextBoxContent", uno::Any(uno::Reference<text::XTextFrame>( + getShape(2), uno::UNO_QUERY_THROW))); + + CPPUNIT_ASSERT_MESSAGE("This is supposed to be a textbox!", + uno::Reference<beans::XPropertySet>(getShape(1), uno::UNO_QUERY_THROW) + ->getPropertyValue("TextBox") + .get<bool>()); + + CPPUNIT_ASSERT_MESSAGE("This is supposed to be a textbox!", + uno::Reference<beans::XPropertySet>(getShape(1), uno::UNO_QUERY_THROW) + ->getPropertyValue("TextBoxContent") + .hasValue()); +} + CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf121509) { auto pDoc = createSwDoc(DATA_DIRECTORY, "Tdf121509.odt"); diff --git a/sw/source/core/doc/textboxhelper.cxx b/sw/source/core/doc/textboxhelper.cxx index 7889fd443b89..1b9155f9b6e8 100644 --- a/sw/source/core/doc/textboxhelper.cxx +++ b/sw/source/core/doc/textboxhelper.cxx @@ -199,6 +199,127 @@ void SwTextBoxHelper::create(SwFrameFormat* pShape, SdrObject* pObject, bool bCo } } +void SwTextBoxHelper::set(SwFrameFormat* pShapeFormat, SdrObject* pObj, + uno::Reference<text::XTextFrame> xNew) +{ + // Do not set invalid data + assert(pShapeFormat && pObj && xNew); + // Firstly find the format of the new textbox. + SwFrameFormat* pFormat = nullptr; + if (auto pTextFrame = dynamic_cast<SwXTextFrame*>(xNew.get())) + pFormat = pTextFrame->GetFrameFormat(); + if (!pFormat) + return; + std::vector<std::pair<beans::Property, uno::Any>> aOldProps; + // If there is a format, check if the shape already has a textbox assigned to. + if (auto pTextBoxNode = pShapeFormat->GetOtherTextBoxFormat()) + { + // If it has a texbox, destroy it. + if (pTextBoxNode->GetTextBox(pObj)) + { + auto xOldFrame + = pObj->getUnoShape()->queryInterface(cppu::UnoType<text::XTextRange>::get()); + if (xOldFrame.hasValue()) + { + uno::Reference<beans::XPropertySet> xOldprops(xOldFrame, uno::UNO_QUERY); + uno::Reference<beans::XPropertyState> xOldPropStates(xOldFrame, uno::UNO_QUERY); + for (auto& rProp : xOldprops->getPropertySetInfo()->getProperties()) + { + try + { + if (xOldPropStates->getPropertyState(rProp.Name) + == beans::PropertyState::PropertyState_DIRECT_VALUE) + aOldProps.push_back( + std::pair(rProp, xOldprops->getPropertyValue(rProp.Name))); + } + catch (...) + { + } + } + } + destroy(pShapeFormat, pObj); + } + // And set the new one. + pTextBoxNode->AddTextBox(pObj, pFormat); + pFormat->SetOtherTextBoxFormat(pTextBoxNode); + } + else + { + // If the shape do not have a texbox node and textbox, + // create that for the shape. + auto* pTextBox = new SwTextBoxNode(pShapeFormat); + pTextBox->AddTextBox(pObj, pFormat); + pShapeFormat->SetOtherTextBoxFormat(pTextBox); + pFormat->SetOtherTextBoxFormat(pTextBox); + } + // Initialize its properties + uno::Reference<beans::XPropertySet> xPropertySet(xNew, uno::UNO_QUERY); + uno::Any aEmptyBorder = uno::makeAny(table::BorderLine2()); + xPropertySet->setPropertyValue(UNO_NAME_TOP_BORDER, aEmptyBorder); + xPropertySet->setPropertyValue(UNO_NAME_BOTTOM_BORDER, aEmptyBorder); + xPropertySet->setPropertyValue(UNO_NAME_LEFT_BORDER, aEmptyBorder); + xPropertySet->setPropertyValue(UNO_NAME_RIGHT_BORDER, aEmptyBorder); + xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::makeAny(sal_Int32(100))); + xPropertySet->setPropertyValue(UNO_NAME_SIZE_TYPE, uno::makeAny(text::SizeType::FIX)); + xPropertySet->setPropertyValue(UNO_NAME_SURROUND, uno::makeAny(text::WrapTextMode_THROUGH)); + // Add a new name to it + uno::Reference<container::XNamed> xNamed(xNew, uno::UNO_QUERY); + xNamed->setName(pShapeFormat->GetDoc()->GetUniqueFrameName()); + // And sync. properties. + uno::Reference<drawing::XShape> xShape(pObj->getUnoShape(), uno::UNO_QUERY); + syncProperty(pShapeFormat, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::makeAny(xShape->getSize()), + pObj); + uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY); + syncProperty(pShapeFormat, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE, + xShapePropertySet->getPropertyValue(UNO_NAME_ANCHOR_TYPE), pObj); + syncProperty(pShapeFormat, RES_HORI_ORIENT, MID_HORIORIENT_ORIENT, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT), pObj); + syncProperty(pShapeFormat, RES_HORI_ORIENT, MID_HORIORIENT_RELATION, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_RELATION), pObj); + syncProperty(pShapeFormat, RES_VERT_ORIENT, MID_VERTORIENT_ORIENT, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT), pObj); + syncProperty(pShapeFormat, RES_VERT_ORIENT, MID_VERTORIENT_RELATION, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_RELATION), pObj); + syncProperty(pShapeFormat, RES_HORI_ORIENT, MID_HORIORIENT_POSITION, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_POSITION), pObj); + syncProperty(pShapeFormat, RES_VERT_ORIENT, MID_VERTORIENT_POSITION, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_POSITION), pObj); + syncProperty(pShapeFormat, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, + xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT), pObj); + drawing::TextVerticalAdjust aVertAdj = drawing::TextVerticalAdjust_CENTER; + if ((uno::Reference<beans::XPropertyState>(xShape, uno::UNO_QUERY_THROW)) + ->getPropertyState(UNO_NAME_TEXT_VERT_ADJUST) + != beans::PropertyState::PropertyState_DEFAULT_VALUE) + { + aVertAdj = xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_VERT_ADJUST) + .get<drawing::TextVerticalAdjust>(); + } + xPropertySet->setPropertyValue(UNO_NAME_TEXT_VERT_ADJUST, uno::makeAny(aVertAdj)); + text::WritingMode eMode; + if (xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_WRITINGMODE) >>= eMode) + syncProperty(pShapeFormat, RES_FRAMEDIR, 0, uno::makeAny(sal_Int16(eMode)), pObj); + if (aOldProps.size()) + { + for (auto& rProp : aOldProps) + { + try + { + xPropertySet->setPropertyValue(rProp.first.Name, rProp.second); + } + catch (...) + { + } + } + } + if (pFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE + && pFormat->GetAnchor().GetPageNum() == 0) + { + pFormat->SetFormatAttr(SwFormatAnchor(RndStdIds::FLY_AT_PAGE, 1)); + } + // Do sync for the new textframe. + synchronizeGroupTextBoxProperty(&changeAnchor, pShapeFormat, pObj); +} + void SwTextBoxHelper::destroy(const SwFrameFormat* pShape, const SdrObject* pObject) { // If a TextBox was enabled previously diff --git a/sw/source/core/unocore/unodraw.cxx b/sw/source/core/unocore/unodraw.cxx index 4d0b404e9a1c..1104df24085a 100644 --- a/sw/source/core/unocore/unodraw.cxx +++ b/sw/source/core/unocore/unodraw.cxx @@ -1164,18 +1164,29 @@ void SwXShape::setPropertyValue(const OUString& rPropertyName, const uno::Any& a } else if (pEntry->nWID == FN_TEXT_BOX) { - bool bValue(false); - aValue >>= bValue; - if (bValue) - SwTextBoxHelper::create(pFormat, GetSvxShape()->GetSdrObject()); - else - SwTextBoxHelper::destroy(pFormat, GetSvxShape()->GetSdrObject()); - + if (pEntry->nMemberId == MID_TEXT_BOX) + { + bool bValue(false); + aValue >>= bValue; + if (bValue) + SwTextBoxHelper::create(pFormat, GetSvxShape()->GetSdrObject()); + else + SwTextBoxHelper::destroy(pFormat, GetSvxShape()->GetSdrObject()); + } + else if (pEntry->nMemberId == MID_TEXT_BOX_CONTENT) + { + if (aValue.getValueType() == cppu::UnoType<uno::Reference<text::XTextFrame>>::get()) + SwTextBoxHelper::set(pFormat, GetSvxShape()->GetSdrObject(), + aValue.get<uno::Reference<text::XTextFrame>>()); + else + SAL_WARN( "sw.uno", "This is not a TextFrame!" ); + } } else if (pEntry->nWID == RES_CHAIN) { if (pEntry->nMemberId == MID_CHAIN_NEXTNAME || pEntry->nMemberId == MID_CHAIN_PREVNAME) - SwTextBoxHelper::syncProperty(pFormat, pEntry->nWID, pEntry->nMemberId, aValue); + SwTextBoxHelper::syncProperty(pFormat, pEntry->nWID, pEntry->nMemberId, aValue, + SdrObject::getSdrObjectFromXShape(mxShape)); } // #i28749# else if ( FN_SHAPE_POSITION_LAYOUT_DIR == pEntry->nWID ) @@ -1346,7 +1357,8 @@ void SwXShape::setPropertyValue(const OUString& rPropertyName, const uno::Any& a pFormat->SetFormatAttr(aSet); } // We have a pFormat and a pEntry as well: try to sync TextBox property. - SwTextBoxHelper::syncProperty(pFormat, pEntry->nWID, pEntry->nMemberId, aValue); + SwTextBoxHelper::syncProperty(pFormat, pEntry->nWID, pEntry->nMemberId, aValue, + SdrObject::getSdrObjectFromXShape(mxShape)); } else { @@ -1436,7 +1448,8 @@ void SwXShape::setPropertyValue(const OUString& rPropertyName, const uno::Any& a if (pFormat) { // We have a pFormat (but no pEntry): try to sync TextBox property. - SwTextBoxHelper::syncProperty(pFormat, rPropertyName, aValue); + SwTextBoxHelper::syncProperty(pFormat, rPropertyName, aValue, + SdrObject::getSdrObjectFromXShape(mxShape)); } // #i31698# - restore object position, if caption point is set. @@ -1517,12 +1530,25 @@ uno::Any SwXShape::getPropertyValue(const OUString& rPropertyName) } else if (pEntry->nWID == FN_TEXT_BOX) { - auto pSvxShape = GetSvxShape(); - bool bValue = SwTextBoxHelper::isTextBox( - pFormat, RES_DRAWFRMFMT, - ((pSvxShape && pSvxShape->GetSdrObject()) ? pSvxShape->GetSdrObject() - : pFormat->FindRealSdrObject())); - aRet <<= bValue; + if (pEntry->nMemberId == MID_TEXT_BOX) + { + auto pSvxShape = GetSvxShape(); + bool bValue = SwTextBoxHelper::isTextBox( + pFormat, RES_DRAWFRMFMT, + ((pSvxShape && pSvxShape->GetSdrObject()) ? pSvxShape->GetSdrObject() + : pFormat->FindRealSdrObject())); + aRet <<= bValue; + } + else if (pEntry->nMemberId == MID_TEXT_BOX_CONTENT) + { + auto pObj = SdrObject::getSdrObjectFromXShape(mxShape); + auto xRange = SwTextBoxHelper::queryInterface( + pFormat, cppu::UnoType<text::XText>::get(), + pObj ? pObj : pFormat->FindRealSdrObject()); + uno::Reference<text::XTextFrame> xFrame(xRange, uno::UNO_QUERY); + if (xFrame.is()) + aRet <<= xFrame; + } } else if (pEntry->nWID == RES_CHAIN) { @@ -1803,7 +1829,9 @@ uno::Sequence< beans::PropertyState > SwXShape::getPropertyStates( else if (pEntry->nWID == FN_TEXT_BOX) { // The TextBox property is set, if we can find a textbox for this shape. - if (pFormat && SwTextBoxHelper::isTextBox(pFormat, RES_DRAWFRMFMT)) + if (pFormat + && SwTextBoxHelper::isTextBox(pFormat, RES_DRAWFRMFMT, + SdrObject::getSdrObjectFromXShape(mxShape))) pRet[nProperty] = beans::PropertyState_DIRECT_VALUE; else pRet[nProperty] = beans::PropertyState_DEFAULT_VALUE; diff --git a/sw/source/core/unocore/unomap.cxx b/sw/source/core/unocore/unomap.cxx index abad86f1ce65..db62cc990d69 100644 --- a/sw/source/core/unocore/unomap.cxx +++ b/sw/source/core/unocore/unomap.cxx @@ -291,7 +291,8 @@ const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetPropertyMapEntries(s { u"" UNO_NAME_RELATIVE_HEIGHT_RELATION, RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_FRMSIZE_REL_HEIGHT_RELATION }, { u"" UNO_NAME_RELATIVE_WIDTH, RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_REL_WIDTH }, { u"" UNO_NAME_RELATIVE_WIDTH_RELATION, RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_FRMSIZE_REL_WIDTH_RELATION }, - { u"" UNO_NAME_TEXT_BOX, FN_TEXT_BOX, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { u"" UNO_NAME_TEXT_BOX, FN_TEXT_BOX, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TEXT_BOX}, + { u"" UNO_NAME_TEXT_BOX_CONTENT, FN_TEXT_BOX, cppu::UnoType<text::XTextFrame>::get(), PROPERTY_NONE, MID_TEXT_BOX_CONTENT}, { u"" UNO_NAME_CHAIN_NEXT_NAME, RES_CHAIN, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_CHAIN_NEXTNAME}, { u"" UNO_NAME_CHAIN_PREV_NAME, RES_CHAIN, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_CHAIN_PREVNAME}, { u"" UNO_NAME_CHAIN_NAME, RES_CHAIN, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_CHAIN_NAME }, commit d3daa7ed0361785d8667f726340538ada1607937 Author: Attila Bakos (NISZ) <bakos.attilakar...@nisz.hu> AuthorDate: Mon Nov 29 14:37:20 2021 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Tue Jan 17 09:23:51 2023 +0000 tdf#143574 sw: textboxes in group shapes - part 3 take 2 In this part, missing parameters have been fixed, and queryInterface method can handle textbox groups now. A new function, synchronizeGroupTextBoxProperty has been introduced to do the sync for all group members. Nested group textbox shape handling also has been introduced. Note: Copy still has issues. Change-Id: I3d2090fe6a4066edfd2cb417b30bca9dd9acb011 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/126052 Tested-by: László Németh <nem...@numbertext.org> Reviewed-by: László Németh <nem...@numbertext.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143365 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/sw/inc/textboxhelper.hxx b/sw/inc/textboxhelper.hxx index 2e5b27cfccb0..924b3e6b5c91 100644 --- a/sw/inc/textboxhelper.hxx +++ b/sw/inc/textboxhelper.hxx @@ -65,7 +65,8 @@ public: /// to the given pObject shape. static void destroy(const SwFrameFormat* pShape, const 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); + static css::uno::Any queryInterface(const SwFrameFormat* pShape, const css::uno::Type& rType, + SdrObject* pObj); /// Sync property of TextBox with the one of the shape. static void syncProperty(SwFrameFormat* pShape, sal_uInt16 nWID, sal_uInt8 nMemberID, @@ -107,6 +108,9 @@ public: /// this function, and returns true in that case too. static std::optional<bool> isAnchorTypeDifferent(const SwFrameFormat* pShape); + /// Sets the correct size of textframe depending on the given SdrObject. + static bool syncTextBoxSize(SwFrameFormat* pShape, SdrObject* pObj); + /// Returns true if the given shape has a valid textframe. static bool isTextBoxShapeHasValidTextFrame(const SwFrameFormat* pShape); @@ -177,6 +181,14 @@ public: /// Undo the effect of saveLinks() + individual resetLink() calls. static void restoreLinks(std::set<ZSortFly>& rOld, std::vector<SwFrameFormat*>& rNew, SavedLink& rSavedLinks); + + /// Calls the method given by pFunc with every textboxes of the group given by pFormat. + static void synchronizeGroupTextBoxProperty(bool pFunc(SwFrameFormat*, SdrObject*), + SwFrameFormat* pFormat, SdrObject* pObj); + /// Collect all textboxes of the group given by the pGoupObj Parameter. Returns with a + /// vector filled with the textboxes. + static std::vector<SwFrameFormat*> CollectTextBoxes(SdrObject* pGroupObject, + SwFrameFormat* pFormat); }; /// Textboxes are basically textframe + shape pairs. This means one shape has one frame. @@ -242,6 +254,8 @@ public: SwFrameFormat* GetOwnerShape() { return m_pOwnerShapeFormat; }; // This will give the current number of textboxes. 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; }; #endif // INCLUDED_SW_INC_TEXTBOXHELPER_HXX diff --git a/sw/qa/uitest/data/ComplexGroupShapeTest.odt b/sw/qa/uitest/data/ComplexGroupShapeTest.odt new file mode 100644 index 000000000000..8fe093203690 Binary files /dev/null and b/sw/qa/uitest/data/ComplexGroupShapeTest.odt differ diff --git a/sw/qa/uitest/writer_tests2/ComplexGroupShapeTest.py b/sw/qa/uitest/writer_tests2/ComplexGroupShapeTest.py new file mode 100644 index 000000000000..7e219d8d7976 --- /dev/null +++ b/sw/qa/uitest/writer_tests2/ComplexGroupShapeTest.py @@ -0,0 +1,127 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/. +# + +from uitest.framework import UITestCase +from uitest.uihelper.common import get_state_as_dict +from uitest.uihelper.common import select_pos +from uitest.uihelper.common import get_url_for_data_file +from libreoffice.uno.propertyvalue import mkPropertyValues +import time + +class ComplexGroupShapeTest(UITestCase): + def test_ComplexGroupShape(self): + with self.ui_test.load_file(get_url_for_data_file("ComplexGroupShapeTest.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(1).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"})) + + # add a textbox to this subshape + self.xUITest.executeCommand(".uno:AddTextBox") + + # select the next shape in the group + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "TAB"})) + + # add a textbox to this subshape + self.xUITest.executeCommand(".uno:AddTextBox") + + # leave the groupshape + self.xUITest.executeCommand(".uno:LeaveGroup") + + # select the other shape + self.xUITest.executeCommand(".uno:JumpToNextFrame") + self.ui_test.wait_until_child_is_available('metricfield') + + # get the current selection + ShapeCollection = document.getCurrentSelection() + + # extend the selection with the grouped shape + ShapeCollection.add(document.DrawPage.getByIndex(0)) + ShapeCollection.add(document.DrawPage.getByIndex(1)) + + # select these shapes + document.getCurrentController().select(ShapeCollection) + + # do ungroup + self.xUITest.executeCommand(".uno:FormatGroup") + + # deselect + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE":"ESC"})) + time.sleep(0.1) + + # select the group + self.xUITest.executeCommand(".uno:JumpToNextFrame") + self.ui_test.wait_until_child_is_available('metricfield') + + # move it down + for i in range(1, 30): + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE":"DOWN"})) + time.sleep(0.1) + + # select again + self.xUITest.executeCommand(".uno:JumpToNextFrame") + self.ui_test.wait_until_child_is_available('metricfield') + + # do ungroup + self.xUITest.executeCommand(".uno:FormatUngroup") + + # deselect everything + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE":"ESC"})) + time.sleep(0.1) + + # select the first ex-group member shape + self.xUITest.executeCommand(".uno:JumpToNextFrame") + self.ui_test.wait_until_child_is_available('metricfield') + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "TAB"})) + + # check if it is a textbox + self.assertEqual(True,document.getCurrentSelection().getByIndex(0).TextBox) + + # go to the other one + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "TAB"})) + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "TAB"})) + + # this is still a group, so it cannot be a textbox + self.assertEqual(False,document.getCurrentSelection().getByIndex(0).TextBox) + + # do ungroup + self.xUITest.executeCommand(".uno:FormatUngroup") + + # deselect + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE":"ESC"})) + time.sleep(0.1) + + # select one shape of the last group + self.xUITest.executeCommand(".uno:JumpToNextFrame") + self.ui_test.wait_until_child_is_available('metricfield') + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "TAB"})) + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "TAB"})) + xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "TAB"})) + + # check if it is a textbox + self.assertEqual(True,document.getCurrentSelection().getByIndex(0).TextBox) + + # Without the fix in place, the following problems occurred during this test: + # - After the grouping old textbox frames detached from their shape before + # - Moving caused messed layout + # - After ungroup, the shapes in the embed group lost their textbox + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/sw/source/core/doc/docdraw.cxx b/sw/source/core/doc/docdraw.cxx index 0aff4b8993ff..6445ab757a0e 100644 --- a/sw/source/core/doc/docdraw.cxx +++ b/sw/source/core/doc/docdraw.cxx @@ -209,7 +209,7 @@ SwDrawContact* SwDoc::GroupSelection( SdrView& rDrawView ) bGroupMembersNotPositioned = pAnchoredDrawObj->NotYetPositioned(); } - std::vector<std::pair<SwFrameFormat*, SdrObject*>> vSavedTextBoxes; + std::map<const SdrObject*, SwFrameFormat*> vSavedTextBoxes; // Destroy ContactObjects and formats. for( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) { @@ -224,8 +224,10 @@ SwDrawContact* SwDoc::GroupSelection( SdrView& rDrawView ) "<SwDoc::GroupSelection(..)> - group members have different positioning status!" ); #endif // Before the format will be killed, save its textbox for later use. - if (auto pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(pContact->GetFormat(), RES_DRAWFRMFMT, pObj)) - vSavedTextBoxes.push_back(std::pair<SwFrameFormat*, SdrObject*>(pTextBox, pObj)); + if (auto pShapeFormat = pContact->GetFormat()) + if (auto pTextBoxNode = pShapeFormat->GetOtherTextBoxFormat()) + for (const auto& rTextBoxElement : pTextBoxNode->GetAllTextBoxes()) + vSavedTextBoxes.emplace(rTextBoxElement); pFormat = static_cast<SwDrawFrameFormat*>(pContact->GetFormat()); // Deletes itself! @@ -254,10 +256,11 @@ SwDrawContact* SwDoc::GroupSelection( SdrView& rDrawView ) // Add the saved textboxes to the new format. auto pTextBoxNode = new SwTextBoxNode(pFormat); - for (auto& pTextBoxEntry : vSavedTextBoxes) + for (const auto& pTextBoxEntry : vSavedTextBoxes) { - pTextBoxNode->AddTextBox(pTextBoxEntry.second, pTextBoxEntry.first); - pTextBoxEntry.first->SetOtherTextBoxFormat(pTextBoxNode); + pTextBoxNode->AddTextBox(const_cast<SdrObject*>(pTextBoxEntry.first), + pTextBoxEntry.second); + pTextBoxEntry.second->SetOtherTextBoxFormat(pTextBoxNode); } pFormat->SetOtherTextBoxFormat(pTextBoxNode); vSavedTextBoxes.clear(); @@ -299,6 +302,27 @@ SwDrawContact* SwDoc::GroupSelection( SdrView& rDrawView ) return pNewContact; } +static void lcl_CollectTextBoxesForSubGroupObj(SwFrameFormat* pTargetFormat, SwTextBoxNode* pTextBoxNode, + SdrObject* pSourceObjs) +{ + if (auto pChildrenObjs = pSourceObjs->getChildrenOfSdrObject()) + for (size_t i = 0; i < pChildrenObjs->GetObjCount(); ++i) + lcl_CollectTextBoxesForSubGroupObj(pTargetFormat, pTextBoxNode, pChildrenObjs->GetObj(i)); + else + { + if (auto pTextBox = pTextBoxNode->GetTextBox(pSourceObjs)) + { + if (!pTargetFormat->GetOtherTextBoxFormat()) + { + pTargetFormat->SetOtherTextBoxFormat(new SwTextBoxNode(pTargetFormat)); + } + pTargetFormat->GetOtherTextBoxFormat()->AddTextBox(pSourceObjs, pTextBox); + pTextBox->SetOtherTextBoxFormat(pTargetFormat->GetOtherTextBoxFormat()); + } + } +} + + void SwDoc::UnGroupSelection( SdrView& rDrawView ) { bool const bUndo = GetIDocumentUndoRedo().DoesUndo(); @@ -349,14 +373,22 @@ void SwDoc::UnGroupSelection( SdrView& rDrawView ) pFormat->SetFormatAttr( aAnch ); if (pTextBoxNode) - if (auto pTextBoxFormat = pTextBoxNode->GetTextBox(pSubObj)) + { + if (!pObj->getChildrenOfSdrObject()) { - auto pNewTextBoxNode = new SwTextBoxNode(pFormat); - pNewTextBoxNode->AddTextBox(pSubObj, pTextBoxFormat); - pFormat->SetOtherTextBoxFormat(pNewTextBoxNode); - pTextBoxFormat->SetOtherTextBoxFormat(pNewTextBoxNode); + if (auto pTextBoxFormat = pTextBoxNode->GetTextBox(pSubObj)) + { + auto pNewTextBoxNode = new SwTextBoxNode(pFormat); + pNewTextBoxNode->AddTextBox(pSubObj, pTextBoxFormat); + pFormat->SetOtherTextBoxFormat(pNewTextBoxNode); + pTextBoxFormat->SetOtherTextBoxFormat(pNewTextBoxNode); + } } - + else + { + lcl_CollectTextBoxesForSubGroupObj(pFormat, pTextBoxNode, pSubObj); + } + } // #i36010# - set layout direction of the position pFormat->SetPositionLayoutDir( text::PositionLayoutDir::PositionInLayoutDirOfAnchor ); diff --git a/sw/source/core/doc/textboxhelper.cxx b/sw/source/core/doc/textboxhelper.cxx index 4cfebf732b1c..7889fd443b89 100644 --- a/sw/source/core/doc/textboxhelper.cxx +++ b/sw/source/core/doc/textboxhelper.cxx @@ -359,7 +359,8 @@ SwFrameFormat* SwTextBoxHelper::getOtherTextBoxFormat(uno::Reference<drawing::XS return nullptr; SwFrameFormat* pFormat = pShape->GetFrameFormat(); - return getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT); + return getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT, + SdrObject::getSdrObjectFromXShape(xShape)); } uno::Reference<text::XTextFrame> @@ -380,9 +381,11 @@ SwTextBoxHelper::getUnoTextFrame(uno::Reference<drawing::XShape> const& xShape) return {}; } -template <typename T> static void lcl_queryInterface(const SwFrameFormat* pShape, uno::Any& rAny) +template <typename T> +static void lcl_queryInterface(const SwFrameFormat* pShape, uno::Any& rAny, SdrObject* pObj) { - if (SwFrameFormat* pFormat = SwTextBoxHelper::getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)) + if (SwFrameFormat* pFormat + = SwTextBoxHelper::getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj)) { uno::Reference<T> const xInterface( SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat), uno::UNO_QUERY); @@ -390,21 +393,22 @@ template <typename T> static void lcl_queryInterface(const SwFrameFormat* pShape } } -uno::Any SwTextBoxHelper::queryInterface(const SwFrameFormat* pShape, const uno::Type& rType) +uno::Any SwTextBoxHelper::queryInterface(const SwFrameFormat* pShape, const uno::Type& rType, + SdrObject* pObj) { uno::Any aRet; if (rType == cppu::UnoType<css::text::XTextAppend>::get()) { - lcl_queryInterface<text::XTextAppend>(pShape, aRet); + lcl_queryInterface<text::XTextAppend>(pShape, aRet, pObj); } else if (rType == cppu::UnoType<css::text::XText>::get()) { - lcl_queryInterface<text::XText>(pShape, aRet); + lcl_queryInterface<text::XText>(pShape, aRet, pObj); } else if (rType == cppu::UnoType<css::text::XTextRange>::get()) { - lcl_queryInterface<text::XTextRange>(pShape, aRet); + lcl_queryInterface<text::XTextRange>(pShape, aRet, pObj); } return aRet; @@ -1300,6 +1304,25 @@ std::optional<bool> SwTextBoxHelper::isAnchorTypeDifferent(const SwFrameFormat* return bRet; } +bool SwTextBoxHelper::syncTextBoxSize(SwFrameFormat* pShape, SdrObject* pObj) +{ + if (!pShape || !pObj) + return false; + + if (auto pTextBox = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj)) + { + const auto& rSize = getTextRectangle(pObj, false).GetSize(); + if (!rSize.IsEmpty()) + { + SwFormatFrameSize aSize(pTextBox->GetFrameSize()); + aSize.SetSize(rSize); + return pTextBox->SetFormatAttr(aSize); + } + } + + return false; +} + bool SwTextBoxHelper::isTextBoxShapeHasValidTextFrame(const SwFrameFormat* pShape) { if (pShape && pShape->Which() == RES_DRAWFRMFMT) @@ -1374,6 +1397,41 @@ bool SwTextBoxHelper::DoTextBoxZOrderCorrection(SwFrameFormat* pShape, const Sdr return false; } +void SwTextBoxHelper::synchronizeGroupTextBoxProperty(bool pFunc(SwFrameFormat*, SdrObject*), + SwFrameFormat* pShape, SdrObject* pObj) +{ + if (auto pChildren = pObj->getChildrenOfSdrObject()) + { + for (size_t i = 0; i < pChildren->GetObjCount(); ++i) + synchronizeGroupTextBoxProperty(pFunc, pShape, pChildren->GetObj(i)); + } + else + { + (*pFunc)(pShape, pObj); + } +} + +std::vector<SwFrameFormat*> SwTextBoxHelper::CollectTextBoxes(SdrObject* pGroupObject, + SwFrameFormat* pFormat) +{ + std::vector<SwFrameFormat*> vRet; + if (auto pChildren = pGroupObject->getChildrenOfSdrObject()) + { + for (size_t i = 0; i < pChildren->GetObjCount(); ++i) + { + auto pChildTextBoxes = CollectTextBoxes(pChildren->GetObj(i), pFormat); + for (auto& rChildTextBox : pChildTextBoxes) + vRet.push_back(rChildTextBox); + } + } + else + { + if (isTextBox(pFormat, RES_DRAWFRMFMT, pGroupObject)) + vRet.push_back(getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT, pGroupObject)); + } + return vRet; +} + SwTextBoxNode::SwTextBoxNode(SwFrameFormat* pOwnerShape) { assert(pOwnerShape); @@ -1497,4 +1555,14 @@ void SwTextBoxNode::SetTextBoxInactive(const SdrObject* pDrawObject) bool SwTextBoxNode::IsGroupTextBox() const { return m_pTextBoxes.size() > 1; } +std::map<SdrObject*, SwFrameFormat*> SwTextBoxNode::GetAllTextBoxes() const +{ + std::map<SdrObject*, SwFrameFormat*> aRet; + for (auto& rElem : m_pTextBoxes) + { + aRet.emplace(rElem.m_pDrawObject, rElem.m_pTextBoxFormat); + } + return aRet; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/draw/dcontact.cxx b/sw/source/core/draw/dcontact.cxx index 9af2c7162111..13bc5bd3f338 100644 --- a/sw/source/core/draw/dcontact.cxx +++ b/sw/source/core/draw/dcontact.cxx @@ -1250,10 +1250,8 @@ void SwDrawContact::Changed_( const SdrObject& rObj, // use geometry of drawing object aObjRect = pGroupObj->GetSnapRect(); - for (size_t i = 0; i < pGroupObj->getChildrenOfSdrObject()->GetObjCount(); ++i ) - { - SwTextBoxHelper::doTextBoxPositioning(GetFormat(), pGroupObj->getChildrenOfSdrObject()->GetObj(i)); - } + SwTextBoxHelper::synchronizeGroupTextBoxProperty(&SwTextBoxHelper::changeAnchor, GetFormat(), &const_cast<SdrObject&>(rObj)); + SwTextBoxHelper::synchronizeGroupTextBoxProperty(&SwTextBoxHelper::syncTextBoxSize, GetFormat(), &const_cast<SdrObject&>(rObj)); } SwTwips nXPosDiff(0); diff --git a/sw/source/core/draw/dview.cxx b/sw/source/core/draw/dview.cxx index 510addf10a9c..12b955d04ee5 100644 --- a/sw/source/core/draw/dview.cxx +++ b/sw/source/core/draw/dview.cxx @@ -967,12 +967,11 @@ void SwDrawView::DeleteMarked() SdrObject *pObject = rMarkList.GetMark(i)->GetMarkedSdrObj(); SwContact* pContact = GetUserCall(pObject); SwFrameFormat* pFormat = pContact->GetFormat(); - if (auto pChildren = pObject->getChildrenOfSdrObject()) + if (pObject->getChildrenOfSdrObject()) { - for (size_t it = 0; it < pChildren->GetObjCount(); ++it) - if (SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat( - pFormat, RES_DRAWFRMFMT, pChildren->GetObj(it))) - aTextBoxesToDelete.push_back(pTextBox); + auto pChildTextBoxes = SwTextBoxHelper::CollectTextBoxes(pObject, pFormat); + for (auto& rChildTextBox : pChildTextBoxes) + aTextBoxesToDelete.push_back(rChildTextBox); } else if (SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT)) diff --git a/sw/source/core/unocore/unodraw.cxx b/sw/source/core/unocore/unodraw.cxx index 5c5811ee3934..4d0b404e9a1c 100644 --- a/sw/source/core/unocore/unodraw.cxx +++ b/sw/source/core/unocore/unodraw.cxx @@ -965,10 +965,19 @@ SwXShape::~SwXShape() uno::Any SwXShape::queryInterface( const uno::Type& aType ) { - uno::Any aRet = SwTextBoxHelper::queryInterface(GetFrameFormat(), aType); - if (aRet.hasValue()) - return aRet; + uno::Any aRet; + SdrObject* pObj = nullptr; + + if ((aType == cppu::UnoType<text::XText>::get()) + || (aType == cppu::UnoType<text::XTextRange>::get()) + || (aType == cppu::UnoType<text::XTextAppend>::get())) + { + pObj = SdrObject::getSdrObjectFromXShape(mxShape); + aRet = SwTextBoxHelper::queryInterface(GetFrameFormat(), aType, pObj); + if (aRet.hasValue()) + return aRet; + } aRet = SwXShapeBaseClass::queryInterface(aType); // #i53320# - follow-up of #i31698# // interface drawing::XShape is overloaded. Thus, provide