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>
