basctl/source/basicide/idedataprovider.cxx   |    2 
 basctl/source/basicide/objectbrowser.cxx     |  174 ++++++++++++++++++++++++++-
 basctl/source/inc/objectbrowser.hxx          |    5 
 basctl/uiconfig/basicide/ui/objectbrowser.ui |   24 +++
 4 files changed, 198 insertions(+), 7 deletions(-)

New commits:
commit edb03719a7c220e25f47ee49a7a139a83b11e517
Author:     Devansh Varshney <[email protected]>
AuthorDate: Sun Oct 5 23:57:40 2025 +0530
Commit:     Jonathan Clark <[email protected]>
CommitDate: Thu Oct 16 23:09:08 2025 +0200

    tdf#165785: basctl: Activate Core UI Interactivity and Member View
    
    This patch brings the Object Browser's user interface to life. It
    connects the left navigation pane to the right members pane.
    
    When a user expands a node on the left, its children are now loaded on
    demand to keep the UI responsive. When a user selects an item, such as a
    module or an interface, its members are now fetched and displayed in the
    right-hand pane.
    
    (GSoC 2025 - Object Browser: Member View and UI Logic)
    
    Change-Id: I199ebcac20590d1a1ca83ef3ee7759ff34504252
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/191923
    Tested-by: Jenkins
    Reviewed-by: Jonathan Clark <[email protected]>

diff --git a/basctl/source/basicide/idedataprovider.cxx 
b/basctl/source/basicide/idedataprovider.cxx
index 14367b47ff94..4106b524f269 100644
--- a/basctl/source/basicide/idedataprovider.cxx
+++ b/basctl/source/basicide/idedataprovider.cxx
@@ -227,6 +227,7 @@ void ImplGetChildrenOfBasicLibrary(SymbolInfoList& 
rChildren, const IdeSymbolInf
                 pNode->sOriginLocation = aDoc.getDocument()->getURL();
             }
             pNode->sParentName = rParent.sName;
+            pNode->sIdentifier = rParent.sIdentifier + u":" + rLibName;
             rChildren.push_back(pNode);
         }
     }
@@ -245,6 +246,7 @@ void ImplGetChildrenOfBasicLibrary(SymbolInfoList& 
rChildren, const IdeSymbolInf
                     pNode->sOriginLocation = rParent.sOriginLocation;
                     pNode->sOriginLibrary = rParent.sName;
                     pNode->sParentName = rParent.sName;
+                    pNode->sIdentifier = rParent.sIdentifier + u":" + 
pNode->sName;
                     rChildren.push_back(pNode);
                 }
             }
diff --git a/basctl/source/basicide/objectbrowser.cxx 
b/basctl/source/basicide/objectbrowser.cxx
index 821662c50855..2dfc5ac0e253 100644
--- a/basctl/source/basicide/objectbrowser.cxx
+++ b/basctl/source/basicide/objectbrowser.cxx
@@ -92,6 +92,63 @@ bool IsExpandable(const IdeSymbolInfo& rSymbol)
     }
 }
 
+std::shared_ptr<const IdeSymbolInfo>
+GetSymbolForIter(const weld::TreeIter& rIter, weld::TreeView& rTree,
+                 const std::map<OUString, std::shared_ptr<IdeSymbolInfo>>& 
rIndex)
+{
+    const OUString sId = rTree.get_id(rIter);
+    if (sId.isEmpty())
+    {
+        return nullptr;
+    }
+
+    auto it = rIndex.find(sId);
+
+    return (it != rIndex.end()) ? it->second : nullptr;
+}
+
+bool ShouldShowMembers(const IdeSymbolInfo& rSymbol)
+{
+    switch (rSymbol.eKind)
+    {
+        case IdeSymbolKind::UNO_CONSTANTS:
+        case IdeSymbolKind::UNO_ENUM:
+        case IdeSymbolKind::UNO_EXCEPTION:
+        case IdeSymbolKind::UNO_INTERFACE:
+        case IdeSymbolKind::UNO_SERVICE:
+        case IdeSymbolKind::UNO_STRUCT:
+        case IdeSymbolKind::UDT:
+        case IdeSymbolKind::MODULE:
+        case IdeSymbolKind::CLASS_MODULE:
+            return true;
+        default:
+            return false;
+    }
+}
+OUString GetGroupNameForKind(IdeSymbolKind eKind)
+{
+    switch (eKind)
+    {
+        case IdeSymbolKind::UNO_PROPERTY:
+        case IdeSymbolKind::PROPERTY_GET:
+        case IdeSymbolKind::PROPERTY_LET:
+        case IdeSymbolKind::PROPERTY_SET:
+            return IDEResId(RID_STR_OB_GROUP_PROPERTIES);
+        case IdeSymbolKind::UNO_METHOD:
+            return IDEResId(RID_STR_OB_GROUP_METHODS);
+        case IdeSymbolKind::UNO_FIELD:
+            return IDEResId(RID_STR_OB_GROUP_FIELDS);
+        case IdeSymbolKind::ENUM_MEMBER:
+            return IDEResId(RID_STR_OB_GROUP_MEMBERS);
+        case IdeSymbolKind::SUB:
+            return IDEResId(RID_STR_OB_GROUP_PROCEDURES);
+        case IdeSymbolKind::FUNCTION:
+            return IDEResId(RID_STR_OB_GROUP_FUNCTIONS);
+        default:
+            return IDEResId(RID_STR_OB_GROUP_OTHER);
+    }
+}
+
 // Helper to add a symbol entry to a tree view and its corresponding data 
stores.
 void AddEntry(weld::TreeView& rTargetTree, 
std::vector<std::shared_ptr<IdeSymbolInfo>>& rStore,
               std::map<OUString, std::shared_ptr<IdeSymbolInfo>>& rIndex,
@@ -101,6 +158,12 @@ void AddEntry(weld::TreeView& rTargetTree, 
std::vector<std::shared_ptr<IdeSymbol
     if (!pSymbol)
         return;
 
+    if (pSymbol->sName.isEmpty())
+    {
+        SAL_WARN("basctl", "AddEntry - Symbol with empty name. ID: " << 
pSymbol->sIdentifier);
+        return;
+    }
+
     OUString sId = pSymbol->sIdentifier;
     if (pSymbol->eKind == IdeSymbolKind::PLACEHOLDER)
     {
@@ -243,6 +306,10 @@ void ObjectBrowser::dispose()
         m_xLeftTreeView->connect_selection_changed(Link<weld::TreeView&, 
void>());
         m_xLeftTreeView->connect_expanding(Link<const weld::TreeIter&, 
bool>());
     }
+    if (m_xRightMembersView)
+    {
+        m_xRightMembersView->connect_selection_changed(Link<weld::TreeView&, 
void>());
+    }
 
     m_pDocNotifier->dispose();
     m_pDocNotifier.reset();
@@ -340,6 +407,7 @@ void ObjectBrowser::RefreshUI(bool /*bForceKeepUno*/)
     }
 
     m_xLeftTreeView->thaw();
+    m_xRightMembersView->thaw();
 
     if (m_xStatusLabel)
         m_xStatusLabel->set_label(u"Ready"_ustr);
@@ -434,6 +502,53 @@ void ObjectBrowser::ScheduleRefresh()
         m_bDataMayBeStale = true;
 }
 
+void ObjectBrowser::PopulateMembersPane(const IdeSymbolInfo& rSymbol)
+{
+    if (!m_xRightMembersView)
+    {
+        return;
+    }
+
+    m_xRightMembersView->freeze();
+    ClearRightTreeView();
+
+    if (m_xRightPaneHeaderLabel)
+    {
+        m_xRightPaneHeaderLabel->set_label(u"Members of: "_ustr + 
rSymbol.sName);
+    }
+    GroupedSymbolInfoList aGroupedMembers = 
m_pDataProvider->GetMembers(rSymbol);
+    std::vector<std::unique_ptr<weld::TreeIter>> aGroupItersToExpand;
+
+    for (const auto& rPair : aGroupedMembers)
+    {
+        if (rPair.second.empty())
+        {
+            continue;
+        }
+        OUString sGroupName = GetGroupNameForKind(rPair.first);
+        auto pGroupNode
+            = std::make_shared<IdeSymbolInfo>(sGroupName, 
IdeSymbolKind::PLACEHOLDER, u"");
+        pGroupNode->bSelectable = false;
+        auto xGroupIter = m_xRightMembersView->make_iterator();
+        AddEntry(*m_xRightMembersView, m_aRightTreeSymbolStore, 
m_aRightTreeSymbolIndex, nullptr,
+                 pGroupNode, true, xGroupIter.get());
+
+        for (const auto& pMemberInfo : rPair.second)
+        {
+            AddEntry(*m_xRightMembersView, m_aRightTreeSymbolStore, 
m_aRightTreeSymbolIndex,
+                     xGroupIter.get(), pMemberInfo, false);
+        }
+        aGroupItersToExpand.push_back(std::move(xGroupIter));
+    }
+
+    m_xRightMembersView->thaw();
+
+    for (const auto& xGroupIter : aGroupItersToExpand)
+    {
+        m_xRightMembersView->expand_row(*xGroupIter);
+    }
+}
+
 // Document Event Handlers
 void ObjectBrowser::onDocumentCreated(const ScriptDocument&) { 
ScheduleRefresh(); }
 void ObjectBrowser::onDocumentOpened(const ScriptDocument&) { 
ScheduleRefresh(); }
@@ -445,11 +560,64 @@ void ObjectBrowser::onDocumentClosed(const 
ScriptDocument&) { ScheduleRefresh();
 void ObjectBrowser::onDocumentTitleChanged(const ScriptDocument&) { 
ScheduleRefresh(); }
 void ObjectBrowser::onDocumentModeChanged(const ScriptDocument&) { /* STUB */}
 
-IMPL_STATIC_LINK(ObjectBrowser, OnLeftTreeSelect, weld::TreeView&, /*rTree*/, 
void) { /* STUB */}
+IMPL_LINK(ObjectBrowser, OnLeftTreeSelect, weld::TreeView&, rTree, void)
+{
+    if (m_bDisposed)
+    {
+        return;
+    }
+
+    auto xSelectedIter = rTree.make_iterator();
+    if (!rTree.get_selected(xSelectedIter.get()))
+    {
+        return;
+    }
+
+    auto pSymbol = GetSymbolForIter(*xSelectedIter, rTree, 
m_aLeftTreeSymbolIndex);
+    if (!pSymbol)
+    {
+        return;
+    }
+
+    ClearRightTreeView();
+
+    if (ShouldShowMembers(*pSymbol))
+    {
+        PopulateMembersPane(*pSymbol);
+    }
+}
+
 IMPL_STATIC_LINK(ObjectBrowser, OnRightTreeSelect, weld::TreeView&, /*rTree*/, 
void) { /* STUB */}
-IMPL_STATIC_LINK(ObjectBrowser, OnNodeExpand, const weld::TreeIter&, 
/*rParentIter*/, bool)
+
+IMPL_LINK(ObjectBrowser, OnNodeExpand, const weld::TreeIter&, rParentIter, 
bool)
 {
-    return false;
+    if (m_bDisposed || !m_pDataProvider)
+    {
+        return false;
+    }
+
+    auto pParentSymbol = GetSymbolForIter(rParentIter, *m_xLeftTreeView, 
m_aLeftTreeSymbolIndex);
+    if (!pParentSymbol)
+    {
+        return false;
+    }
+
+    const auto aAllChildren = m_pDataProvider->GetChildNodes(*pParentSymbol);
+    if (aAllChildren.empty())
+    {
+        return true;
+    }
+
+    for (const auto& pChildInfo : aAllChildren)
+    {
+        if (pChildInfo)
+        {
+            AddEntry(*m_xLeftTreeView, m_aLeftTreeSymbolStore, 
m_aLeftTreeSymbolIndex, &rParentIter,
+                     pChildInfo, IsExpandable(*pChildInfo));
+        }
+    }
+
+    return true;
 }
 
 IMPL_LINK(ObjectBrowser, OnScopeChanged, weld::ComboBox&, rComboBox, void)
diff --git a/basctl/source/inc/objectbrowser.hxx 
b/basctl/source/inc/objectbrowser.hxx
index dd69e65b34cd..68d0b73c0bc0 100644
--- a/basctl/source/inc/objectbrowser.hxx
+++ b/basctl/source/inc/objectbrowser.hxx
@@ -79,6 +79,7 @@ private:
     void ShowLoadingState();
     void ClearLeftTreeView();
     void ClearRightTreeView();
+    void PopulateMembersPane(const IdeSymbolInfo& rSymbol);
 
     // Core References
     Shell* m_pShell;
@@ -117,9 +118,9 @@ private:
     std::unique_ptr<basctl::DocumentEventNotifier> m_pDocNotifier;
 
     // UI Event Handlers
-    DECL_STATIC_LINK(ObjectBrowser, OnLeftTreeSelect, weld::TreeView&, void);
+    DECL_LINK(OnLeftTreeSelect, weld::TreeView&, void);
     DECL_STATIC_LINK(ObjectBrowser, OnRightTreeSelect, weld::TreeView&, void);
-    DECL_STATIC_LINK(ObjectBrowser, OnNodeExpand, const weld::TreeIter&, bool);
+    DECL_LINK(OnNodeExpand, const weld::TreeIter&, bool);
     DECL_LINK(OnScopeChanged, weld::ComboBox&, void);
 };
 
diff --git a/basctl/uiconfig/basicide/ui/objectbrowser.ui 
b/basctl/uiconfig/basicide/ui/objectbrowser.ui
index 9ea9c21e5189..77300e280bba 100644
--- a/basctl/uiconfig/basicide/ui/objectbrowser.ui
+++ b/basctl/uiconfig/basicide/ui/objectbrowser.ui
@@ -4,7 +4,7 @@
   <requires lib="gtk+" version="3.20"/><!--actual version="3.24"-->
   <object class="GtkTreeStore" id="LeftTreeStore">
     <columns>
-      <column type="gchararray"/>
+      <column type="GdkPixbuf"/>
       <column type="gchararray"/>
       <column type="gchararray"/>
       <column type="gchararray"/>
@@ -13,7 +13,7 @@
   </object>
   <object class="GtkTreeStore" id="RightMembersStore">
     <columns>
-      <column type="gchararray"/>
+      <column type="GdkPixbuf"/>
       <column type="gchararray"/>
       <column type="gchararray"/>
       <column type="gchararray"/>
@@ -226,8 +226,18 @@
                         <child>
                           <object class="GtkTreeViewColumn" 
id="LeftTreeColumn">
                             <property name="title" translatable="yes" 
context="ObjectBrowserPanel">Objects</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkCellRendererPixbuf"/>
+                              <attributes>
+                                <attribute name="pixbuf">0</attribute>
+                              </attributes>
+                            </child>
                             <child>
                               <object class="GtkCellRendererText" 
id="LeftTreeCellRenderer"/>
+                              <attributes>
+                                <attribute name="text">1</attribute>
+                              </attributes>
                             </child>
                           </object>
                         </child>
@@ -294,8 +304,18 @@
                         <child>
                           <object class="GtkTreeViewColumn" 
id="RightTreeColumn">
                             <property name="title" translatable="yes" 
context="ObjectBrowserMembers">Members</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkCellRendererPixbuf"/>
+                              <attributes>
+                                <attribute name="pixbuf">0</attribute>
+                              </attributes>
+                            </child>
                             <child>
                               <object class="GtkCellRendererText" 
id="RightTreeCellRenderer"/>
+                              <attributes>
+                                <attribute name="text">1</attribute>
+                              </attributes>
                             </child>
                           </object>
                         </child>

Reply via email to