[Libreoffice-commits] core.git: oox/source sd/qa

Grzegorz Araminowicz g.araminowicz at gmail.com
Thu Aug 10 14:52:47 UTC 2017


 oox/source/drawingml/diagram/diagram.cxx            |   10 
 oox/source/drawingml/diagram/diagram.hxx            |    1 
 oox/source/drawingml/diagram/diagramlayoutatoms.cxx |  226 +++++++++-----------
 oox/source/drawingml/diagram/diagramlayoutatoms.hxx |    2 
 oox/source/drawingml/diagram/layoutatomvisitors.cxx |   26 ++
 oox/source/drawingml/diagram/layoutatomvisitors.hxx |    4 
 sd/qa/unit/data/pptx/smartart-children.pptx         |binary
 sd/qa/unit/import-tests.cxx                         |   39 +++
 8 files changed, 182 insertions(+), 126 deletions(-)

New commits:
commit 93a3739bf47ada4549b3c139c55a9b039c1db1ee
Author: Grzegorz Araminowicz <g.araminowicz at gmail.com>
Date:   Tue Aug 8 10:57:24 2017 +0200

    SmartArt: add nested shapes only if they are node's children
    
    Change-Id: I6527fc4166001ffc1e1e170b179cd4eadf55305c
    Reviewed-on: https://gerrit.libreoffice.org/40870
    Reviewed-by: Jan Holesovsky <kendy at collabora.com>
    Tested-by: Jan Holesovsky <kendy at collabora.com>
    Tested-by: Jenkins <ci at libreoffice.org>

diff --git a/oox/source/drawingml/diagram/diagram.cxx b/oox/source/drawingml/diagram/diagram.cxx
index ec396ceed9d3..4185dd01f15b 100644
--- a/oox/source/drawingml/diagram/diagram.cxx
+++ b/oox/source/drawingml/diagram/diagram.cxx
@@ -72,6 +72,16 @@ DiagramData::DiagramData()
 {
 }
 
+const dgm::Point* DiagramData::getRootPoint() const
+{
+    for (const auto & aCurrPoint : maPoints)
+        if (aCurrPoint.mnType == XML_doc)
+            return &aCurrPoint;
+
+    SAL_WARN("oox.drawingml", "No root point");
+    return nullptr;
+}
+
 void DiagramData::dump()
 {
     SAL_INFO("oox.drawingml", "Dgm: DiagramData # of cnx: " << maConnections.size() );
diff --git a/oox/source/drawingml/diagram/diagram.hxx b/oox/source/drawingml/diagram/diagram.hxx
index 2e1429da37b0..914c421e1a6d 100644
--- a/oox/source/drawingml/diagram/diagram.hxx
+++ b/oox/source/drawingml/diagram/diagram.hxx
@@ -180,6 +180,7 @@ public:
         { return maPointsPresNameMap; }
     ::std::vector<OUString> &getExtDrawings()
         { return maExtDrawings; }
+    const dgm::Point* getRootPoint() const;
     void dump();
 private:
     FillPropertiesPtr mpFillProperties;
diff --git a/oox/source/drawingml/diagram/diagramlayoutatoms.cxx b/oox/source/drawingml/diagram/diagramlayoutatoms.cxx
index 2c3c8cf6749a..79390b1a3792 100644
--- a/oox/source/drawingml/diagram/diagramlayoutatoms.cxx
+++ b/oox/source/drawingml/diagram/diagramlayoutatoms.cxx
@@ -353,150 +353,132 @@ void LayoutNode::accept( LayoutAtomVisitor& rVisitor )
     rVisitor.visit(*this);
 }
 
-bool LayoutNode::setupShape( const ShapePtr& rShape, const Diagram& rDgm, sal_uInt32 nIdx ) const
+bool LayoutNode::setupShape( const ShapePtr& rShape, const Diagram& rDgm, const dgm::Point* pPresNode ) const
 {
-    // find the data node to grab text from
-    DiagramData::PointsNameMap::const_iterator aDataNode=rDgm.getData()->getPointsPresNameMap().find(msName);
-    if( aDataNode != rDgm.getData()->getPointsPresNameMap().end() &&
-        aDataNode->second.size() > nIdx )
+    SAL_INFO(
+        "oox.drawingml",
+        "Filling content from layout node named \"" << msName
+            << "\", modelId \"" << pPresNode->msModelId << "\"");
+
+    // have the presentation node - now, need the actual data node:
+    const DiagramData::StringMap::const_iterator aNodeName = rDgm.getData()->getPresOfNameMap().find(
+        pPresNode->msModelId);
+    if( aNodeName != rDgm.getData()->getPresOfNameMap().end() )
     {
-        const dgm::Point* aPresNode = aDataNode->second.at(nIdx);
-        SAL_INFO(
-            "oox.drawingml",
-            "Filling content from " << nIdx << "th layout node named \""
-                << msName << "\", modelId \""
-                << aPresNode->msModelId << "\"");
-
-        // got the presentation node - now, need the actual data node:
-        const DiagramData::StringMap::const_iterator aNodeName=rDgm.getData()->getPresOfNameMap().find(
-            aPresNode->msModelId);
-        if( aNodeName != rDgm.getData()->getPresOfNameMap().end() )
+        DiagramData::StringMap::value_type::second_type::const_iterator aVecIter=aNodeName->second.begin();
+        const DiagramData::StringMap::value_type::second_type::const_iterator aVecEnd=aNodeName->second.end();
+        while( aVecIter != aVecEnd )
         {
-            DiagramData::StringMap::value_type::second_type::const_iterator aVecIter=aNodeName->second.begin();
-            const DiagramData::StringMap::value_type::second_type::const_iterator aVecEnd=aNodeName->second.end();
-            while( aVecIter != aVecEnd )
+            DiagramData::PointNameMap& rMap = rDgm.getData()->getPointNameMap();
+            DiagramData::PointNameMap::const_iterator aDataNode2 = rMap.find(aVecIter->first);
+            if (aDataNode2 == rMap.end())
             {
-                DiagramData::PointNameMap& rMap = rDgm.getData()->getPointNameMap();
-                DiagramData::PointNameMap::const_iterator aDataNode2 = rMap.find(aVecIter->first);
-                if (aDataNode2 == rMap.end())
-                {
-                    //busted, skip it
-                    ++aVecIter;
-                    continue;
-                }
-
-                if( aVecIter->second == 0 )
-                {
-                    // grab shape attr from topmost element(s)
-                    rShape->getShapeProperties() = aDataNode2->second->mpShape->getShapeProperties();
-                    rShape->getLineProperties() = aDataNode2->second->mpShape->getLineProperties();
-                    rShape->getFillProperties() = aDataNode2->second->mpShape->getFillProperties();
-                    rShape->getCustomShapeProperties() = aDataNode2->second->mpShape->getCustomShapeProperties();
-                    rShape->setMasterTextListStyle( aDataNode2->second->mpShape->getMasterTextListStyle() );
-
-                    SAL_INFO(
-                        "oox.drawingml",
-                        "Custom shape with preset type "
-                            << (rShape->getCustomShapeProperties()
-                                ->getShapePresetType())
-                            << " added for layout node named \"" << msName
-                            << "\"");
-                }
-
-                // append text with right outline level
-                if( aDataNode2->second->mpShape->getTextBody() &&
-                    !aDataNode2->second->mpShape->getTextBody()->getParagraphs().empty() &&
-                    !aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getRuns().empty() )
-                {
-                    TextBodyPtr pTextBody=rShape->getTextBody();
-                    if( !pTextBody )
-                    {
-                        pTextBody.reset( new TextBody() );
-
-                        // also copy text attrs
-                        pTextBody->getTextListStyle() =
-                            aDataNode2->second->mpShape->getTextBody()->getTextListStyle();
-                        pTextBody->getTextProperties() =
-                            aDataNode2->second->mpShape->getTextBody()->getTextProperties();
-
-                        rShape->setTextBody(pTextBody);
-                    }
-
-                    TextParagraph& rPara=pTextBody->addParagraph();
-                    if( aVecIter->second != -1 )
-                        rPara.getProperties().setLevel(aVecIter->second);
-
-                    rPara.addRun(
-                        aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getRuns().front());
-                    rPara.getProperties().apply(
-                        aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getProperties());
-                }
-
+                //busted, skip it
                 ++aVecIter;
+                continue;
             }
-        }
-        else
-        {
-            SAL_INFO(
-                "oox.drawingml",
-                "ShapeCreationVisitor::visit: no data node name found while"
-                    " processing shape type "
-                    << rShape->getCustomShapeProperties()->getShapePresetType()
-                    << " for layout node named \"" << msName << "\"");
-        }
 
-        // TODO(Q1): apply styling & coloring - take presentation
-        // point's presStyleLbl for both style & color
-        // if not found use layout node's styleLbl
-        // however, docs are a bit unclear on this
-        OUString aStyleLabel = aPresNode->msPresentationLayoutStyleLabel;
-        if (aStyleLabel.isEmpty())
-            aStyleLabel = msStyleLabel;
-        if( !aStyleLabel.isEmpty() )
-        {
-            const DiagramQStyleMap::const_iterator aStyle = rDgm.getStyles().find(aStyleLabel);
-            if( aStyle != rDgm.getStyles().end() )
-            {
-                const DiagramStyle& rStyle = aStyle->second;
-                rShape->getShapeStyleRefs()[XML_fillRef] = rStyle.maFillStyle;
-                rShape->getShapeStyleRefs()[XML_lnRef] = rStyle.maLineStyle;
-                rShape->getShapeStyleRefs()[XML_effectRef] = rStyle.maEffectStyle;
-                rShape->getShapeStyleRefs()[XML_fontRef] = rStyle.maTextStyle;
-            }
-            else
+            if( aVecIter->second == 0 )
             {
-                SAL_WARN("oox.drawingml", "Style " << aStyleLabel << " not found");
+                // grab shape attr from topmost element(s)
+                rShape->getShapeProperties() = aDataNode2->second->mpShape->getShapeProperties();
+                rShape->getLineProperties() = aDataNode2->second->mpShape->getLineProperties();
+                rShape->getFillProperties() = aDataNode2->second->mpShape->getFillProperties();
+                rShape->getCustomShapeProperties() = aDataNode2->second->mpShape->getCustomShapeProperties();
+                rShape->setMasterTextListStyle( aDataNode2->second->mpShape->getMasterTextListStyle() );
+
+                SAL_INFO(
+                    "oox.drawingml",
+                    "Custom shape with preset type "
+                        << (rShape->getCustomShapeProperties()
+                            ->getShapePresetType())
+                        << " added for layout node named \"" << msName
+                        << "\"");
             }
 
-            const DiagramColorMap::const_iterator aColor = rDgm.getColors().find(aStyleLabel);
-            if( aColor != rDgm.getColors().end() )
+            // append text with right outline level
+            if( aDataNode2->second->mpShape->getTextBody() &&
+                !aDataNode2->second->mpShape->getTextBody()->getParagraphs().empty() &&
+                !aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getRuns().empty() )
             {
-                const DiagramColor& rColor=aColor->second;
-                if( rColor.maFillColor.isUsed() )
-                    rShape->getShapeStyleRefs()[XML_fillRef].maPhClr = rColor.maFillColor;
-                if( rColor.maLineColor.isUsed() )
-                    rShape->getShapeStyleRefs()[XML_lnRef].maPhClr = rColor.maLineColor;
-                if( rColor.maEffectColor.isUsed() )
-                    rShape->getShapeStyleRefs()[XML_effectRef].maPhClr = rColor.maEffectColor;
-                if( rColor.maTextFillColor.isUsed() )
-                    rShape->getShapeStyleRefs()[XML_fontRef].maPhClr = rColor.maTextFillColor;
+                TextBodyPtr pTextBody=rShape->getTextBody();
+                if( !pTextBody )
+                {
+                    pTextBody.reset( new TextBody() );
+
+                    // also copy text attrs
+                    pTextBody->getTextListStyle() =
+                        aDataNode2->second->mpShape->getTextBody()->getTextListStyle();
+                    pTextBody->getTextProperties() =
+                        aDataNode2->second->mpShape->getTextBody()->getTextProperties();
+
+                    rShape->setTextBody(pTextBody);
+                }
+
+                TextParagraph& rPara=pTextBody->addParagraph();
+                if( aVecIter->second != -1 )
+                    rPara.getProperties().setLevel(aVecIter->second);
+
+                rPara.addRun(
+                    aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getRuns().front());
+                rPara.getProperties().apply(
+                    aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getProperties());
             }
-        }
 
-        // even if no data node found, successful anyway. it's
-        // contained at the layoutnode
-        return true;
+            ++aVecIter;
+        }
     }
     else
     {
         SAL_INFO(
             "oox.drawingml",
-            "no text found while processing shape type "
+            "ShapeCreationVisitor::visit: no data node name found while"
+                " processing shape type "
                 << rShape->getCustomShapeProperties()->getShapePresetType()
                 << " for layout node named \"" << msName << "\"");
     }
 
-    return false;
+    // TODO(Q1): apply styling & coloring - take presentation
+    // point's presStyleLbl for both style & color
+    // if not found use layout node's styleLbl
+    // however, docs are a bit unclear on this
+    OUString aStyleLabel = pPresNode->msPresentationLayoutStyleLabel;
+    if (aStyleLabel.isEmpty())
+        aStyleLabel = msStyleLabel;
+    if( !aStyleLabel.isEmpty() )
+    {
+        const DiagramQStyleMap::const_iterator aStyle = rDgm.getStyles().find(aStyleLabel);
+        if( aStyle != rDgm.getStyles().end() )
+        {
+            const DiagramStyle& rStyle = aStyle->second;
+            rShape->getShapeStyleRefs()[XML_fillRef] = rStyle.maFillStyle;
+            rShape->getShapeStyleRefs()[XML_lnRef] = rStyle.maLineStyle;
+            rShape->getShapeStyleRefs()[XML_effectRef] = rStyle.maEffectStyle;
+            rShape->getShapeStyleRefs()[XML_fontRef] = rStyle.maTextStyle;
+        }
+        else
+        {
+            SAL_WARN("oox.drawingml", "Style " << aStyleLabel << " not found");
+        }
+
+        const DiagramColorMap::const_iterator aColor = rDgm.getColors().find(aStyleLabel);
+        if( aColor != rDgm.getColors().end() )
+        {
+            const DiagramColor& rColor=aColor->second;
+            if( rColor.maFillColor.isUsed() )
+                rShape->getShapeStyleRefs()[XML_fillRef].maPhClr = rColor.maFillColor;
+            if( rColor.maLineColor.isUsed() )
+                rShape->getShapeStyleRefs()[XML_lnRef].maPhClr = rColor.maLineColor;
+            if( rColor.maEffectColor.isUsed() )
+                rShape->getShapeStyleRefs()[XML_effectRef].maPhClr = rColor.maEffectColor;
+            if( rColor.maTextFillColor.isUsed() )
+                rShape->getShapeStyleRefs()[XML_fontRef].maPhClr = rColor.maTextFillColor;
+        }
+    }
+
+    // even if no data node found, successful anyway. it's
+    // contained at the layoutnode
+    return true;
 }
 
 void ShapeAtom::accept( LayoutAtomVisitor& rVisitor )
diff --git a/oox/source/drawingml/diagram/diagramlayoutatoms.hxx b/oox/source/drawingml/diagram/diagramlayoutatoms.hxx
index a6123ccbd62c..7414303bbdb1 100644
--- a/oox/source/drawingml/diagram/diagramlayoutatoms.hxx
+++ b/oox/source/drawingml/diagram/diagramlayoutatoms.hxx
@@ -247,7 +247,7 @@ public:
 
     bool setupShape( const ShapePtr& rShape,
                      const Diagram& rDgm,
-                     sal_uInt32 nIdx ) const;
+                     const dgm::Point* pPresNode ) const;
 
 private:
     VarMap                       mVariables;
diff --git a/oox/source/drawingml/diagram/layoutatomvisitors.cxx b/oox/source/drawingml/diagram/layoutatomvisitors.cxx
index f661d48389d2..c0b019a9019b 100755
--- a/oox/source/drawingml/diagram/layoutatomvisitors.cxx
+++ b/oox/source/drawingml/diagram/layoutatomvisitors.cxx
@@ -93,12 +93,30 @@ void ShapeCreationVisitor::visit(ChooseAtom& rAtom)
 
 void ShapeCreationVisitor::visit(LayoutNode& rAtom)
 {
+    // stop processing if it's not a child of previous LayoutNode
+
+    const DiagramData::PointsNameMap::const_iterator aDataNode = mrDgm.getData()->getPointsPresNameMap().find(rAtom.getName());
+    if (aDataNode == mrDgm.getData()->getPointsPresNameMap().end() || mnCurrIdx >= (sal_Int32)aDataNode->second.size())
+        return;
+
+    const dgm::Point* pNewNode = aDataNode->second.at(mnCurrIdx);
+    if (!mpCurrentNode || !pNewNode)
+        return;
+
+    bool bIsChild = false;
+    for (const auto & aConnection : mrDgm.getData()->getConnections())
+        if (aConnection.msSourceId == mpCurrentNode->msModelId && aConnection.msDestId == pNewNode->msModelId)
+            bIsChild = true;
+
+    if (!bIsChild)
+        return;
+
     ShapePtr pCurrParent(mpParentShape);
 
     if (rAtom.getExistingShape())
     {
         // reuse existing shape
-        if (rAtom.setupShape(rAtom.getExistingShape(), mrDgm, mnCurrIdx))
+        if (rAtom.setupShape(rAtom.getExistingShape(), mrDgm, pNewNode))
             rAtom.getNodeShapes().push_back(rAtom.getExistingShape());
     }
     else
@@ -115,7 +133,7 @@ void ShapeCreationVisitor::visit(LayoutNode& rAtom)
                     << (pShape->getCustomShapeProperties()
                         ->getShapePresetType()));
 
-            if (rAtom.setupShape(pShape, mrDgm, mnCurrIdx))
+            if (rAtom.setupShape(pShape, mrDgm, pNewNode))
             {
                 pCurrParent->addChild(pShape);
                 pCurrParent = pShape;
@@ -128,6 +146,9 @@ void ShapeCreationVisitor::visit(LayoutNode& rAtom)
         }
     }
 
+    const dgm::Point* pPreviousNode = mpCurrentNode;
+    mpCurrentNode = pNewNode;
+
     // set new parent for children
     ShapePtr pPreviousParent(mpParentShape);
     mpParentShape=pCurrParent;
@@ -137,6 +158,7 @@ void ShapeCreationVisitor::visit(LayoutNode& rAtom)
 
     // restore parent
     mpParentShape=pPreviousParent;
+    mpCurrentNode = pPreviousNode;
 
     // remove unneeded empty group shapes
     pCurrParent->getChildren().erase(
diff --git a/oox/source/drawingml/diagram/layoutatomvisitors.hxx b/oox/source/drawingml/diagram/layoutatomvisitors.hxx
index 86432a8a4376..28bdf7d855e6 100755
--- a/oox/source/drawingml/diagram/layoutatomvisitors.hxx
+++ b/oox/source/drawingml/diagram/layoutatomvisitors.hxx
@@ -33,6 +33,7 @@ class ShapeCreationVisitor : public LayoutAtomVisitor
     ShapePtr mpParentShape;
     const Diagram& mrDgm;
     sal_Int32 mnCurrIdx;
+    const dgm::Point* mpCurrentNode;
 
     void defaultVisit(LayoutAtom const & rAtom);
     virtual void visit(ConstraintAtom& rAtom) override;
@@ -48,7 +49,8 @@ public:
                          const Diagram& rDgm) :
         mpParentShape(rParentShape),
         mrDgm(rDgm),
-        mnCurrIdx(0)
+        mnCurrIdx(0),
+        mpCurrentNode(rDgm.getData()->getRootPoint())
     {}
 };
 
diff --git a/sd/qa/unit/data/pptx/smartart-children.pptx b/sd/qa/unit/data/pptx/smartart-children.pptx
new file mode 100755
index 000000000000..8eca2f21ec54
Binary files /dev/null and b/sd/qa/unit/data/pptx/smartart-children.pptx differ
diff --git a/sd/qa/unit/import-tests.cxx b/sd/qa/unit/import-tests.cxx
index 40804586e9db..726df149b40e 100644
--- a/sd/qa/unit/import-tests.cxx
+++ b/sd/qa/unit/import-tests.cxx
@@ -164,6 +164,7 @@ public:
     void testTdf108925();
     void testTdf109067();
     void testSmartArt1();
+    void testSmartArtChildren();
     void testTdf109223();
     void testActiveXCheckbox();
 
@@ -237,6 +238,7 @@ public:
     CPPUNIT_TEST(testTdf108925);
     CPPUNIT_TEST(testTdf109067);
     CPPUNIT_TEST(testSmartArt1);
+    CPPUNIT_TEST(testSmartArtChildren);
     CPPUNIT_TEST(testTdf109223);
     CPPUNIT_TEST(testActiveXCheckbox);
 
@@ -2259,6 +2261,43 @@ void SdImportTest::testSmartArt1()
     xDocShRef->DoClose();
 }
 
+void SdImportTest::testSmartArtChildren()
+{
+    sd::DrawDocShellRef xDocShRef = loadURL(m_directories.getURLFromSrc("sd/qa/unit/data/pptx/smartart-children.pptx"), PPTX);
+    uno::Reference<drawing::XShapes> xShapeGroup(getShapeFromPage(0, 0, xDocShRef), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xShapeGroup->getCount());
+
+    uno::Reference<drawing::XShapes> xShapeGroup0(xShapeGroup->getByIndex(0), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xShapeGroup0->getCount());
+    uno::Reference<text::XText> xTextA(xShapeGroup0->getByIndex(0), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(OUString("a"), xTextA->getString());
+
+    uno::Reference<drawing::XShapes> xChildren0(xShapeGroup0->getByIndex(1), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xChildren0->getCount());
+    uno::Reference<drawing::XShapes> xChildB(xChildren0->getByIndex(0), uno::UNO_QUERY_THROW);
+    uno::Reference<text::XText> xTextB(xChildB->getByIndex(0), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(OUString("b"), xTextB->getString());
+    uno::Reference<drawing::XShapes> xChildC(xChildren0->getByIndex(1), uno::UNO_QUERY_THROW);
+    uno::Reference<text::XText> xTextC(xChildC->getByIndex(0), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(OUString("c"), xTextC->getString());
+
+    uno::Reference<drawing::XShapes> xShapeGroup1(xShapeGroup->getByIndex(1), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xShapeGroup1->getCount());
+    uno::Reference<text::XText> xTextX(xShapeGroup1->getByIndex(0), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(OUString("x"), xTextX->getString());
+
+    uno::Reference<drawing::XShapes> xChildren1(xShapeGroup1->getByIndex(1), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xChildren1->getCount());
+    uno::Reference<drawing::XShapes> xChildY(xChildren1->getByIndex(0), uno::UNO_QUERY_THROW);
+    uno::Reference<text::XText> xTextY(xChildY->getByIndex(0), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(OUString("y"), xTextY->getString());
+    uno::Reference<drawing::XShapes> xChildZ(xChildren1->getByIndex(1), uno::UNO_QUERY_THROW);
+    uno::Reference<text::XText> xTextZ(xChildZ->getByIndex(0), uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_EQUAL(OUString("z"), xTextZ->getString());
+
+    xDocShRef->DoClose();
+}
+
 void SdImportTest::testTdf109223()
 {
     // In the test document flipV attribute is defined for a group shape


More information about the Libreoffice-commits mailing list