devtools: object inspector toolbar and object stack

This change adds a toolbar to the object inspector with buttons
for inspect (which just links to the same action added to the
context menu) and back. Back uses the newly added object stack
to return to the previously inspected object. Only the objects
which we used the "inspect" command in the object inspector tree
are added to the object stack.

Change-Id: Icb5b6e841200d6e0e41e260092a195fc84729d0f
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111532
Tested-by: Tomaž Vajngerl <quikee@gmail.com>
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
diff --git a/include/sfx2/devtools/DevelopmentToolDockingWindow.hxx b/include/sfx2/devtools/DevelopmentToolDockingWindow.hxx
index f363318..97b0c8e 100644
--- a/include/sfx2/devtools/DevelopmentToolDockingWindow.hxx
+++ b/include/sfx2/devtools/DevelopmentToolDockingWindow.hxx
@@ -36,6 +36,7 @@ private:
    std::unique_ptr<weld::TreeView> mpMethodsTreeView;
    std::unique_ptr<weld::TreeView> mpDocumentModelTreeView;
    std::unique_ptr<weld::ToggleButton> mpSelectionToggle;
    std::unique_ptr<weld::Toolbar> mpObjectInspectorToolbar;

    css::uno::Reference<css::uno::XInterface> mxRoot;
    css::uno::Reference<css::uno::XInterface> mxCurrentSelection;
diff --git a/include/sfx2/devtools/ObjectInspectorTreeHandler.hxx b/include/sfx2/devtools/ObjectInspectorTreeHandler.hxx
index 7008a39..c61da73 100644
--- a/include/sfx2/devtools/ObjectInspectorTreeHandler.hxx
+++ b/include/sfx2/devtools/ObjectInspectorTreeHandler.hxx
@@ -18,6 +18,9 @@
#include <com/sun/star/uno/XInterface.hpp>
#include <com/sun/star/uno/Reference.hxx>

#include <memory>
#include <deque>

class ObjectInspectorTreeHandler
{
private:
@@ -25,8 +28,10 @@ private:
    std::unique_ptr<weld::TreeView>& mpServicesTreeView;
    std::unique_ptr<weld::TreeView>& mpPropertiesTreeView;
    std::unique_ptr<weld::TreeView>& mpMethodsTreeView;

    std::unique_ptr<weld::Label>& mpClassNameLabel;
    std::unique_ptr<weld::Toolbar>& mpObjectInspectorToolbar;

    std::deque<css::uno::Any> maInspectionStack;

    static void clearObjectInspectorChildren(std::unique_ptr<weld::TreeView>& pTreeView,
                                             weld::TreeIter const& rParent);
@@ -39,18 +44,30 @@ private:
    void appendProperties(css::uno::Reference<css::uno::XInterface> const& xInterface);
    void appendMethods(css::uno::Reference<css::uno::XInterface> const& xInterface);

    void inspectObject(css::uno::Reference<css::uno::XInterface> const& xInterface);

    void clearStack();
    void addToStack(css::uno::Any const& rAny);
    css::uno::Any popFromStack();

    void updateBackButtonState();

public:
    ObjectInspectorTreeHandler(std::unique_ptr<weld::TreeView>& pInterfacesTreeView,
                               std::unique_ptr<weld::TreeView>& pServicesTreeView,
                               std::unique_ptr<weld::TreeView>& pPropertiesTreeView,
                               std::unique_ptr<weld::TreeView>& pMethodsTreeView,
                               std::unique_ptr<weld::Label>& pClassNameLabel);
                               std::unique_ptr<weld::Label>& pClassNameLabel,
                               std::unique_ptr<weld::Toolbar>& pObjectInspectorToolbar);

    DECL_LINK(ExpandingHandlerInterfaces, const weld::TreeIter&, bool);
    DECL_LINK(ExpandingHandlerServices, const weld::TreeIter&, bool);
    DECL_LINK(ExpandingHandlerProperties, const weld::TreeIter&, bool);
    DECL_LINK(ExpandingHandlerMethods, const weld::TreeIter&, bool);
    DECL_LINK(SelectionChanged, weld::TreeView&, void);

    DECL_LINK(PopupMenuHandler, const CommandEvent&, bool);
    DECL_LINK(ToolbarButtonClicked, const OString&, void);

    void introspect(css::uno::Reference<css::uno::XInterface> const& xInterface);

diff --git a/sfx2/source/devtools/DevelopmentToolDockingWindow.cxx b/sfx2/source/devtools/DevelopmentToolDockingWindow.cxx
index 6bd92f1..d7d0219 100644
--- a/sfx2/source/devtools/DevelopmentToolDockingWindow.cxx
+++ b/sfx2/source/devtools/DevelopmentToolDockingWindow.cxx
@@ -34,11 +34,12 @@ DevelopmentToolDockingWindow::DevelopmentToolDockingWindow(SfxBindings* pInputBi
    , mpMethodsTreeView(m_xBuilder->weld_tree_view("methods_treeview_id"))
    , mpDocumentModelTreeView(m_xBuilder->weld_tree_view("leftside_treeview_id"))
    , mpSelectionToggle(m_xBuilder->weld_toggle_button("selection_toggle"))
    , mpObjectInspectorToolbar(m_xBuilder->weld_toolbar("object_inspector_toolbar"))
    , maDocumentModelTreeHandler(
          mpDocumentModelTreeView,
          pInputBindings->GetDispatcher()->GetFrame()->GetObjectShell()->GetBaseModel())
    , maObjectInspectorTreeHandler(mpInterfacesTreeView, mpServicesTreeView, mpPropertiesTreeView,
                                   mpMethodsTreeView, mpClassNameLabel)
                                   mpMethodsTreeView, mpClassNameLabel, mpObjectInspectorToolbar)
{
    mpDocumentModelTreeView->connect_changed(
        LINK(this, DevelopmentToolDockingWindow, DocumentModelTreeViewSelectionHandler));
@@ -116,6 +117,7 @@ void DevelopmentToolDockingWindow::dispose()
    mpMethodsTreeView.reset();
    mpSelectionToggle.reset();
    mpDocumentModelTreeView.reset();
    mpObjectInspectorToolbar.reset();

    SfxDockingWindow::dispose();
}
diff --git a/sfx2/source/devtools/ObjectInspectorTreeHandler.cxx b/sfx2/source/devtools/ObjectInspectorTreeHandler.cxx
index 54cebc8..c100ed5f 100644
--- a/sfx2/source/devtools/ObjectInspectorTreeHandler.cxx
+++ b/sfx2/source/devtools/ObjectInspectorTreeHandler.cxx
@@ -593,6 +593,34 @@ ObjectInspectorNodeInterface* BasicValueNode::createNodeObjectForAny(OUString co
    return new BasicValueNode(rName, rAny, mxContext);
}

ObjectInspectorNodeInterface* getSelectedNode(weld::TreeView const& rTreeView)
{
    OUString sID = rTreeView.get_selected_id();
    if (sID.isEmpty())
        return nullptr;

    if (auto* pNode = reinterpret_cast<ObjectInspectorNodeInterface*>(sID.toInt64()))
        return pNode;

    return nullptr;
}

uno::Reference<uno::XInterface> getSelectedXInterface(weld::TreeView const& rTreeView)
{
    uno::Reference<uno::XInterface> xInterface;

    if (auto* pNode = getSelectedNode(rTreeView))
    {
        if (auto* pBasicValueNode = dynamic_cast<BasicValueNode*>(pNode))
        {
            uno::Any aAny = pBasicValueNode->getAny();
            xInterface.set(aAny, uno::UNO_QUERY);
        }
    }

    return xInterface;
}

} // end anonymous namespace

ObjectInspectorTreeHandler::ObjectInspectorTreeHandler(
@@ -600,12 +628,14 @@ ObjectInspectorTreeHandler::ObjectInspectorTreeHandler(
    std::unique_ptr<weld::TreeView>& pServicesTreeView,
    std::unique_ptr<weld::TreeView>& pPropertiesTreeView,
    std::unique_ptr<weld::TreeView>& pMethodsTreeView,
    std::unique_ptr<weld::Label>& pClassNameLabel)
    std::unique_ptr<weld::Label>& pClassNameLabel,
    std::unique_ptr<weld::Toolbar>& pObjectInspectorToolbar)
    : mpInterfacesTreeView(pInterfacesTreeView)
    , mpServicesTreeView(pServicesTreeView)
    , mpPropertiesTreeView(pPropertiesTreeView)
    , mpMethodsTreeView(pMethodsTreeView)
    , mpClassNameLabel(pClassNameLabel)
    , mpObjectInspectorToolbar(pObjectInspectorToolbar)
{
    mpInterfacesTreeView->connect_expanding(
        LINK(this, ObjectInspectorTreeHandler, ExpandingHandlerInterfaces));
@@ -618,6 +648,16 @@ ObjectInspectorTreeHandler::ObjectInspectorTreeHandler(

    mpPropertiesTreeView->connect_popup_menu(
        LINK(this, ObjectInspectorTreeHandler, PopupMenuHandler));

    mpInterfacesTreeView->connect_changed(LINK(this, ObjectInspectorTreeHandler, SelectionChanged));
    mpServicesTreeView->connect_changed(LINK(this, ObjectInspectorTreeHandler, SelectionChanged));
    mpPropertiesTreeView->connect_changed(LINK(this, ObjectInspectorTreeHandler, SelectionChanged));
    mpMethodsTreeView->connect_changed(LINK(this, ObjectInspectorTreeHandler, SelectionChanged));

    mpObjectInspectorToolbar->connect_clicked(
        LINK(this, ObjectInspectorTreeHandler, ToolbarButtonClicked));
    mpObjectInspectorToolbar->set_item_sensitive("inspect", false);
    mpObjectInspectorToolbar->set_item_sensitive("back", false);
}

void ObjectInspectorTreeHandler::handleExpanding(std::unique_ptr<weld::TreeView>& pTreeView,
@@ -659,43 +699,71 @@ IMPL_LINK(ObjectInspectorTreeHandler, ExpandingHandlerMethods, weld::TreeIter co
    return true;
}

IMPL_LINK(ObjectInspectorTreeHandler, SelectionChanged, weld::TreeView&, rTreeView, void)
{
    bool bHaveNodeWithObject = false;

    if (mpPropertiesTreeView.get() == &rTreeView)
    {
        auto* pNode = getSelectedNode(rTreeView);
        if (auto* pBasicValueNode = dynamic_cast<BasicValueNode*>(pNode))
        {
            uno::Any aAny = pBasicValueNode->getAny();
            uno::Reference<uno::XInterface> xInterface(aAny, uno::UNO_QUERY);
            bHaveNodeWithObject = xInterface.is();
        }
    }

    mpObjectInspectorToolbar->set_item_sensitive("inspect", bHaveNodeWithObject);
}

IMPL_LINK(ObjectInspectorTreeHandler, PopupMenuHandler, const CommandEvent&, rCommandEvent, bool)
{
    if (rCommandEvent.GetCommand() != CommandEventId::ContextMenu)
        return false;

    uno::Any aAny;
    OUString sID = mpPropertiesTreeView->get_selected_id();
    if (sID.isEmpty())
        return false;

    auto* pNode = reinterpret_cast<ObjectInspectorNodeInterface*>(sID.toInt64());
    if (pNode)
    auto xInterface = getSelectedXInterface(*mpPropertiesTreeView);
    if (xInterface.is())
    {
        auto* pBasicValueNode = dynamic_cast<BasicValueNode*>(pNode);
        if (pBasicValueNode)
        {
            aAny = pBasicValueNode->getAny();
            uno::Reference<uno::XInterface> xInterface(aAny, uno::UNO_QUERY);
            if (xInterface.is())
            {
                std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(
                    mpPropertiesTreeView.get(), "sfx/ui/devtoolsmenu.ui"));
                std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu("inspect_menu"));
        std::unique_ptr<weld::Builder> xBuilder(
            Application::CreateBuilder(mpPropertiesTreeView.get(), "sfx/ui/devtoolsmenu.ui"));
        std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu("inspect_menu"));

                OString sCommand(xMenu->popup_at_rect(
                    mpPropertiesTreeView.get(),
                    tools::Rectangle(rCommandEvent.GetMousePosPixel(), Size(1, 1))));
                if (sCommand == "inspect")
                {
                    introspect(xInterface);
                }
            }
        OString sCommand(
            xMenu->popup_at_rect(mpPropertiesTreeView.get(),
                                 tools::Rectangle(rCommandEvent.GetMousePosPixel(), Size(1, 1))));

        if (sCommand == "inspect")
        {
            addToStack(uno::Any(xInterface));
            inspectObject(xInterface);
        }
    }
    return true;
}

IMPL_LINK(ObjectInspectorTreeHandler, ToolbarButtonClicked, const OString&, rSelectionId, void)
{
    if (rSelectionId == "inspect")
    {
        auto xInterface = getSelectedXInterface(*mpPropertiesTreeView);
        if (xInterface.is())
        {
            addToStack(uno::Any(xInterface));
            inspectObject(xInterface);
        }
    }
    else if (rSelectionId == "back")
    {
        uno::Any aAny = popFromStack();
        if (aAny.hasValue())
        {
            uno::Reference<uno::XInterface> xInterface(aAny, uno::UNO_QUERY);
            inspectObject(xInterface);
        }
    }
}

void ObjectInspectorTreeHandler::clearObjectInspectorChildren(
    std::unique_ptr<weld::TreeView>& pTreeView, weld::TreeIter const& rParent)
{
@@ -784,7 +852,32 @@ void ObjectInspectorTreeHandler::appendMethods(uno::Reference<uno::XInterface> c
    }
}

void ObjectInspectorTreeHandler::introspect(uno::Reference<uno::XInterface> const& xInterface)
void ObjectInspectorTreeHandler::updateBackButtonState()
{
    mpObjectInspectorToolbar->set_item_sensitive("back", maInspectionStack.size() > 1);
}

void ObjectInspectorTreeHandler::clearStack()
{
    maInspectionStack.clear();
    updateBackButtonState();
}

void ObjectInspectorTreeHandler::addToStack(css::uno::Any const& rAny)
{
    maInspectionStack.push_back(rAny);
    updateBackButtonState();
}

css::uno::Any ObjectInspectorTreeHandler::popFromStack()
{
    maInspectionStack.pop_back();
    uno::Any aAny = maInspectionStack.back();
    updateBackButtonState();
    return aAny;
}

void ObjectInspectorTreeHandler::inspectObject(uno::Reference<uno::XInterface> const& xInterface)
{
    if (!xInterface.is())
        return;
@@ -820,6 +913,13 @@ void ObjectInspectorTreeHandler::introspect(uno::Reference<uno::XInterface> cons
    mpMethodsTreeView->thaw();
}

void ObjectInspectorTreeHandler::introspect(uno::Reference<uno::XInterface> const& xInterface)
{
    clearStack();
    addToStack(uno::Any(xInterface));
    inspectObject(xInterface);
}

void ObjectInspectorTreeHandler::dispose()
{
    clearAll(mpInterfacesTreeView);
diff --git a/sfx2/uiconfig/ui/developmenttool.ui b/sfx2/uiconfig/ui/developmenttool.ui
index 3eeb948..a234169 100644
--- a/sfx2/uiconfig/ui/developmenttool.ui
+++ b/sfx2/uiconfig/ui/developmenttool.ui
@@ -134,7 +134,7 @@
          </packing>
        </child>
        <child>
          <!-- n-columns=2 n-rows=2 -->
          <!-- n-columns=1 n-rows=2 -->
          <object class="GtkGrid">
            <property name="visible">True</property>
            <property name="can-focus">False</property>
@@ -145,38 +145,6 @@
            <property name="hexpand">True</property>
            <property name="vexpand">True</property>
            <child>
              <object class="GtkLabel" id="class_name_label">
                <property name="visible">True</property>
                <property name="can-focus">False</property>
                <property name="hexpand">False</property>
                <property name="vexpand">False</property>
                <property name="label" translatable="yes" context="developmenttool|classname">Class name:</property>
                <accessibility>
                  <relation type="label-for" target="class_name_value_id"/>
                </accessibility>
              </object>
              <packing>
                <property name="left-attach">0</property>
                <property name="top-attach">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkLabel" id="class_name_value_id">
                <property name="name">class_name_id</property>
                <property name="visible">True</property>
                <property name="can-focus">False</property>
                <property name="hexpand">True</property>
                <property name="selectable">True</property>
                <accessibility>
                  <relation type="labelled-by" target="class_name_label"/>
                </accessibility>
              </object>
              <packing>
                <property name="left-attach">1</property>
                <property name="top-attach">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkNotebook">
                <property name="visible">True</property>
                <property name="can-focus">True</property>
@@ -443,7 +411,94 @@
              <packing>
                <property name="left-attach">0</property>
                <property name="top-attach">1</property>
                <property name="width">2</property>
              </packing>
            </child>
            <child>
              <object class="GtkBox">
                <property name="visible">True</property>
                <property name="can-focus">False</property>
                <child>
                  <object class="GtkToolbar" id="object_inspector_toolbar">
                    <property name="visible">True</property>
                    <property name="can-focus">False</property>
                    <property name="toolbar-style">icons</property>
                    <child>
                      <object class="GtkToolButton" id="back">
                        <property name="visible">True</property>
                        <property name="can-focus">False</property>
                        <property name="tooltip-text" translatable="yes" context="developmenttool|back">Back</property>
                        <property name="label" translatable="yes" context="developmenttool|back">Back</property>
                        <property name="use-underline">True</property>
                        <property name="icon-name">cmd/lc_prevrecord.png</property>
                      </object>
                      <packing>
                        <property name="expand">False</property>
                        <property name="homogeneous">True</property>
                      </packing>
                    </child>
                    <child>
                      <object class="GtkToolButton" id="inspect">
                        <property name="visible">True</property>
                        <property name="can-focus">False</property>
                        <property name="tooltip-text" translatable="yes" context="developmenttool|inspect">Inspect</property>
                        <property name="label" translatable="yes" context="developmenttool|inspect">Inspect</property>
                        <property name="use-underline">True</property>
                        <property name="icon-name">cmd/lc_recsearch.png</property>
                      </object>
                      <packing>
                        <property name="expand">False</property>
                        <property name="homogeneous">True</property>
                      </packing>
                    </child>
                  </object>
                  <packing>
                    <property name="expand">False</property>
                    <property name="fill">True</property>
                    <property name="position">0</property>
                  </packing>
                </child>
                <child>
                  <object class="GtkLabel" id="class_name_label">
                    <property name="visible">True</property>
                    <property name="can-focus">False</property>
                    <property name="hexpand">False</property>
                    <property name="vexpand">False</property>
                    <property name="xpad">6</property>
                    <property name="ypad">6</property>
                    <property name="label" translatable="yes" context="developmenttool|classname">Class name:</property>
                    <accessibility>
                      <relation type="label-for" target="class_name_value_id"/>
                    </accessibility>
                  </object>
                  <packing>
                    <property name="expand">False</property>
                    <property name="fill">True</property>
                    <property name="position">1</property>
                  </packing>
                </child>
                <child>
                  <object class="GtkLabel" id="class_name_value_id">
                    <property name="name">class_name_id</property>
                    <property name="visible">True</property>
                    <property name="can-focus">False</property>
                    <property name="hexpand">True</property>
                    <property name="selectable">True</property>
                    <property name="xalign">0</property>
                    <property name="yalign">0.5</property>
                    <accessibility>
                      <relation type="labelled-by" target="class_name_label"/>
                    </accessibility>
                  </object>
                  <packing>
                    <property name="expand">False</property>
                    <property name="fill">True</property>
                    <property name="position">2</property>
                  </packing>
                </child>
              </object>
              <packing>
                <property name="left-attach">0</property>
                <property name="top-attach">0</property>
              </packing>
            </child>
          </object>