SmartArt Edit UI: remove node

Removes data node from data model including associated presentation nodes,
transition nodes and all connections between them (child-parent and
presentation-of).
It still doesn't update order of remaining connections after removal, so
empty entries can happen. Additional step is needed or using better data
structures.

Change-Id: I96e0752b6ec5a19ae8e972dbd421314e6c442b53
Reviewed-on: https://gerrit.libreoffice.org/79279
Tested-by: Jenkins
Reviewed-by: Grzegorz Araminowicz <grzegorz.araminowicz@collabora.com>
diff --git a/cui/source/dialogs/DiagramDialog.cxx b/cui/source/dialogs/DiagramDialog.cxx
index 4891ef9..f3a4a06 100644
--- a/cui/source/dialogs/DiagramDialog.cxx
+++ b/cui/source/dialogs/DiagramDialog.cxx
@@ -20,10 +20,12 @@ DiagramDialog::DiagramDialog(weld::Window* pWindow,
    , mpBtnOk(m_xBuilder->weld_button("btnOk"))
    , mpBtnCancel(m_xBuilder->weld_button("btnCancel"))
    , mpBtnAdd(m_xBuilder->weld_button("btnAdd"))
    , mpBtnRemove(m_xBuilder->weld_button("btnRemove"))
    , mpTreeDiagram(m_xBuilder->weld_tree_view("treeDiagram"))
    , mpTextAdd(m_xBuilder->weld_text_view("textAdd"))
{
    mpBtnAdd->connect_clicked(LINK(this, DiagramDialog, OnAddClick));
    mpBtnRemove->connect_clicked(LINK(this, DiagramDialog, OnRemoveClick));

    populateTree(nullptr, OUString());

@@ -40,15 +42,28 @@ IMPL_LINK_NOARG(DiagramDialog, OnAddClick, weld::Button&, void)
    OUString sText = mpTextAdd->get_text();
    if (!sText.isEmpty())
    {
        OUString sNodeId = mpDiagramData->addNode(sText);
        std::unique_ptr<weld::TreeIter> pEntry(mpTreeDiagram->make_iterator());
        mpTreeDiagram->insert(nullptr, -1, &sText, nullptr, nullptr, nullptr, nullptr, false,
        mpTreeDiagram->insert(nullptr, -1, &sText, &sNodeId, nullptr, nullptr, nullptr, false,
                              pEntry.get());
        mpTreeDiagram->select(*pEntry);
        mpDiagramData->addNode(sText);
        comphelper::dispatchCommand(".uno:RegenerateDiagram", {});
    }
}

IMPL_LINK_NOARG(DiagramDialog, OnRemoveClick, weld::Button&, void)
{
    std::unique_ptr<weld::TreeIter> pEntry(mpTreeDiagram->make_iterator());
    if (mpTreeDiagram->get_selected(pEntry.get()))
    {
        if (mpDiagramData->removeNode(mpTreeDiagram->get_id(*pEntry)))
        {
            mpTreeDiagram->remove(*pEntry);
            comphelper::dispatchCommand(".uno:RegenerateDiagram", {});
        }
    }
}

void DiagramDialog::populateTree(const weld::TreeIter* pParent, const OUString& rParentId)
{
    auto aItems = mpDiagramData->getChildren(rParentId);
diff --git a/cui/source/inc/DiagramDialog.hxx b/cui/source/inc/DiagramDialog.hxx
index 461ffee..e971440 100644
--- a/cui/source/inc/DiagramDialog.hxx
+++ b/cui/source/inc/DiagramDialog.hxx
@@ -27,10 +27,12 @@ private:
    std::unique_ptr<weld::Button> mpBtnOk;
    std::unique_ptr<weld::Button> mpBtnCancel;
    std::unique_ptr<weld::Button> mpBtnAdd;
    std::unique_ptr<weld::Button> mpBtnRemove;
    std::unique_ptr<weld::TreeView> mpTreeDiagram;
    std::unique_ptr<weld::TextView> mpTextAdd;

    DECL_LINK(OnAddClick, weld::Button&, void);
    DECL_LINK(OnRemoveClick, weld::Button&, void);

    void populateTree(const weld::TreeIter* pParent, const OUString& rParentId);
};
diff --git a/cui/uiconfig/ui/diagramdialog.ui b/cui/uiconfig/ui/diagramdialog.ui
index 98a6771..c05031f1 100644
--- a/cui/uiconfig/ui/diagramdialog.ui
+++ b/cui/uiconfig/ui/diagramdialog.ui
@@ -129,6 +129,20 @@
                <property name="position">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="btnRemove">
                <property name="label">gtk-remove</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_stock">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">2</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
diff --git a/include/svx/DiagramDataInterface.hxx b/include/svx/DiagramDataInterface.hxx
index aaa3a46..f64dc3a 100644
--- a/include/svx/DiagramDataInterface.hxx
+++ b/include/svx/DiagramDataInterface.hxx
@@ -38,8 +38,10 @@ public:
    virtual std::vector<std::pair<OUString, OUString>>
    getChildren(const OUString& rParentId) const = 0;

    // add new top-level node to data model
    virtual void addNode(const OUString& rText) = 0;
    // add new top-level node to data model, returns its id
    virtual OUString addNode(const OUString& rText) = 0;

    virtual bool removeNode(const OUString& rNodeId) = 0;

protected:
    ~DiagramDataInterface() throw() {}
diff --git a/oox/source/drawingml/diagram/datamodel.cxx b/oox/source/drawingml/diagram/datamodel.cxx
index 1f1794b..6b7d05e 100644
--- a/oox/source/drawingml/diagram/datamodel.cxx
+++ b/oox/source/drawingml/diagram/datamodel.cxx
@@ -29,6 +29,7 @@
#include <svx/DiagramDataInterface.hxx>
#include <comphelper/xmltools.hxx>

#include <unordered_set>
#include <iostream>
#include <fstream>

@@ -137,6 +138,12 @@ std::vector<std::pair<OUString, OUString>> DiagramData::getChildren(const OUStri
                    pChild->second->msModelId,
                    pChild->second->mpShape->getTextBody()->getParagraphs().front()->getRuns().front()->getText());
        }

    // HACK: empty items shouldn't appear there
    aChildren.erase(std::remove_if(aChildren.begin(), aChildren.end(),
                                   [](const std::pair<OUString, OUString>& aItem) { return aItem.first.isEmpty(); }),
                    aChildren.end());

    return aChildren;
}

@@ -154,7 +161,7 @@ void DiagramData::addConnection(sal_Int32 nType, const OUString& sSourceId, cons
    rCxn.mnSourceOrder = nMaxOrd + 1;
}

void DiagramData::addNode(const OUString& rText)
OUString DiagramData::addNode(const OUString& rText)
{
    const dgm::Point& rDataRoot = *getRootPoint();
    OUString sPresRoot;
@@ -163,11 +170,13 @@ void DiagramData::addNode(const OUString& rText)
            sPresRoot = aCxn.msDestId;

    if (sPresRoot.isEmpty())
        return;
        return OUString();

    OUString sNewNodeId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8);

    dgm::Point aDataPoint;
    aDataPoint.mnType = XML_node;
    aDataPoint.msModelId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8);
    aDataPoint.msModelId = sNewNodeId;
    aDataPoint.mpShape.reset(new Shape());
    aDataPoint.mpShape->setTextBody(std::make_shared<TextBody>());
    TextRunPtr pTextRun(new TextRun());
@@ -208,6 +217,59 @@ void DiagramData::addNode(const OUString& rText)
    maPoints.push_back(aPresPoint);

    build();
    return sNewNodeId;
}

bool DiagramData::removeNode(const OUString& rNodeId)
{
    // check if it doesn't have children
    for (const auto& aCxn : maConnections)
        if (aCxn.mnType == XML_parOf && aCxn.msSourceId == rNodeId)
        {
            SAL_WARN("oox.drawingml", "Node has children - can't be removed");
            return false;
        }

    dgm::Connection aParCxn;
    for (const auto& aCxn : maConnections)
        if (aCxn.mnType == XML_parOf && aCxn.msDestId == rNodeId)
            aParCxn = aCxn;

    std::unordered_set<OUString> aIdsToRemove;
    aIdsToRemove.insert(rNodeId);
    if (!aParCxn.msParTransId.isEmpty())
        aIdsToRemove.insert(aParCxn.msParTransId);
    if (!aParCxn.msSibTransId.isEmpty())
        aIdsToRemove.insert(aParCxn.msSibTransId);

    for (const dgm::Point& rPoint : maPoints)
        if (aIdsToRemove.count(rPoint.msPresentationAssociationId))
            aIdsToRemove.insert(rPoint.msModelId);

    // instert also transition nodes
    for (const auto& aCxn : maConnections)
        if (aIdsToRemove.count(aCxn.msSourceId) || aIdsToRemove.count(aCxn.msDestId))
            if (!aCxn.msPresId.isEmpty())
                aIdsToRemove.insert(aCxn.msPresId);

    // remove connections
    maConnections.erase(std::remove_if(maConnections.begin(), maConnections.end(),
                                       [aIdsToRemove](const dgm::Connection& rCxn) {
                                           return aIdsToRemove.count(rCxn.msSourceId) || aIdsToRemove.count(rCxn.msDestId);
                                       }),
                        maConnections.end());

    // remove data and presentation nodes
    maPoints.erase(std::remove_if(maPoints.begin(), maPoints.end(),
                                  [aIdsToRemove](const dgm::Point& rPoint) {
                                      return aIdsToRemove.count(rPoint.msModelId);
                                  }),
                   maPoints.end());

    // TODO: fix source/dest order

    build();
    return true;
}

#ifdef DEBUG_OOX_DIAGRAM
diff --git a/oox/source/drawingml/diagram/datamodel.hxx b/oox/source/drawingml/diagram/datamodel.hxx
index b4c7ce7..7f7f0f4 100644
--- a/oox/source/drawingml/diagram/datamodel.hxx
+++ b/oox/source/drawingml/diagram/datamodel.hxx
@@ -182,7 +182,8 @@ public:
    void dump() const;
    OUString getString() const override;
    std::vector<std::pair<OUString, OUString>> getChildren(const OUString& rParentId) const override;
    void addNode(const OUString& rText) override;
    OUString addNode(const OUString& rText) override;
    bool removeNode(const OUString& rNodeId) override;

private:
    void getChildrenString(OUStringBuffer& rBuf, const dgm::Point* pPoint, sal_Int32 nLevel) const;