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 <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/inc/textboxhelper.hxx b/sw/inc/textboxhelper.hxx
index 2e5b27c..924b3e6 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 0000000..8fe0932
--- /dev/null
+++ b/sw/qa/uitest/data/ComplexGroupShapeTest.odt
Binary files differ
diff --git a/sw/qa/uitest/writer_tests2/ComplexGroupShapeTest.py b/sw/qa/uitest/writer_tests2/ComplexGroupShapeTest.py
new file mode 100644
index 0000000..7e219d8
--- /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 0aff4b89..6445ab7 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 418c699..707cc5d 100644
--- a/sw/source/core/doc/textboxhelper.cxx
+++ b/sw/source/core/doc/textboxhelper.cxx
@@ -367,7 +367,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>
@@ -388,9 +389,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);
@@ -398,21 +401,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;
@@ -1308,6 +1312,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)
@@ -1382,6 +1405,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);
@@ -1505,4 +1563,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 984cacf..48c29c5 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 510addf..12b955d 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 4d04857..af5b74b 100644
--- a/sw/source/core/unocore/unodraw.cxx
+++ b/sw/source/core/unocore/unodraw.cxx
@@ -963,10 +963,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