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 <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 924b3e6..1a0cada 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 d249b32..9f41350 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 7ef5d0a..1e16bc4 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 0000000..a6155e3
--- /dev/null
+++ b/sw/qa/extras/layout/data/TextBoxFrame.odt
Binary files differ
diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx
index 10d01fa..47403ea 100644
--- a/sw/qa/extras/layout/layout2.cxx
+++ b/sw/qa/extras/layout/layout2.cxx
@@ -1557,6 +1557,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 707cc5d..3ad9f5e 100644
--- a/sw/source/core/doc/textboxhelper.cxx
+++ b/sw/source/core/doc/textboxhelper.cxx
@@ -207,6 +207,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 af5b74b..5c5bd75 100644
--- a/sw/source/core/unocore/unodraw.cxx
+++ b/sw/source/core/unocore/unodraw.cxx
@@ -1160,18 +1160,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 )
@@ -1342,7 +1353,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
        {
@@ -1432,7 +1444,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.
@@ -1513,12 +1526,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)
                {
@@ -1799,7 +1825,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 75e4db6..03946a0 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    },