basctl/inc/strings.hrc                     |   10 
 basctl/source/basicide/idedataprovider.cxx |  385 ++++++++++++++++++++++++++++-
 basctl/source/basicide/objectbrowser.cxx   |  317 +++++++++++++++++++----
 basctl/source/inc/idedataprovider.hxx      |   20 +
 basctl/source/inc/objectbrowser.hxx        |   37 +-
 5 files changed, 685 insertions(+), 84 deletions(-)

New commits:
commit dd1474c34f8177e1d5a4439a2e32edbf7194e273
Author:     Devansh Varshney <[email protected]>
AuthorDate: Sun Oct 5 20:39:25 2025 +0530
Commit:     Jonathan Clark <[email protected]>
CommitDate: Thu Oct 16 21:02:46 2025 +0200

    tdf#165785: basctl: Activate Object Browser Scope Selector and Dynamic 
Refresh
    
    This patch brings the Object Browser's scope mechanism to life. It
    connects the UI's "Scope" dropdown to the data provider, allowing the
    view to be filtered between "All Libraries" and "Current Document".
    
    The browser now also automatically updates its view to reflect the
    currently active document when in "Current Document" mode.
    
    (GSoC 2025 - Object Browser: Scope Activation and Dynamic UI)
    
    Change-Id: Icd3679330c9b05eaa0cb351d81fd4d2415f91d71
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/191886
    Tested-by: Jenkins
    Reviewed-by: Jonathan Clark <[email protected]>

diff --git a/basctl/inc/strings.hrc b/basctl/inc/strings.hrc
index 72abed45361d..166d53f239a2 100644
--- a/basctl/inc/strings.hrc
+++ b/basctl/inc/strings.hrc
@@ -103,8 +103,18 @@
 #define RID_STR_RUN                         NC_("RID_STR_RUN", "Run")
 #define RID_STR_RECORD                      NC_("RID_STR_RECORD", "~Save")
 #define RID_STR_OBJECT_BROWSER              NC_("RID_STR_OBJECT_BROWSER", 
"Object Browser")
+#define RID_STR_OB_GROUP_PROPERTIES         NC_("RID_STR_OB_GROUP_PROPERTIES", 
"Properties")
+#define RID_STR_OB_GROUP_METHODS            NC_("RID_STR_OB_GROUP_METHODS", 
"Methods")
+#define RID_STR_OB_GROUP_FIELDS             NC_("RID_STR_OB_GROUP_FIELDS", 
"Fields")
+#define RID_STR_OB_GROUP_MEMBERS            NC_("RID_STR_OB_GROUP_MEMBERS", 
"Members")
+#define RID_STR_OB_GROUP_PROCEDURES         NC_("RID_STR_OB_GROUP_PROCEDURES", 
"Procedures")
 #define RID_STR_OB_SCOPE_ALL                NC_("RID_STR_OB_SCOPE_ALL", "All 
Libraries")
 #define RID_STR_OB_SCOPE_CURRENT            NC_("RID_STR_OB_SCOPE_CURRENT", 
"Current Document")
+#define RID_STR_OB_GROUP_FUNCTIONS          NC_("RID_STR_OB_GROUP_FUNCTIONS", 
"Functions")
+#define RID_STR_OB_GROUP_OTHER              NC_("RID_STR_OB_GROUP_OTHER", 
"Other")
+#define RID_STR_OB_SEARCH_PLACEHOLDER       
NC_("RID_STR_OB_SEARCH_PLACEHOLDER", "Search objects and members...")
+#define RID_STR_OB_BACK_BUTTON              NC_("RID_STR_OB_BACK_BUTTON", 
"Back")
+#define RID_STR_OB_FORWARD_BUTTON           NC_("RID_STR_OB_FORWARD_BUTTON", 
"Forward")
 #define RID_BASICIDE_OBJCAT                 NC_("RID_BASICIDE_OBJCAT", "Object 
Catalog")
 // Property Browser Headline 
----------------------------------------------------------------
 #define RID_STR_BRWTITLE_PROPERTIES         NC_("RID_STR_BRWTITLE_PROPERTIES", 
"Properties: ")
diff --git a/basctl/source/basicide/idedataprovider.cxx 
b/basctl/source/basicide/idedataprovider.cxx
index 4ed1f5cfee3a..14367b47ff94 100644
--- a/basctl/source/basicide/idedataprovider.cxx
+++ b/basctl/source/basicide/idedataprovider.cxx
@@ -9,8 +9,10 @@
 
 #include <idedataprovider.hxx>
 #include "idetimer.hxx"
+#include <unordered_set>
 
 #include <basic/basmgr.hxx>
+#include <basic/sbmeth.hxx>
 #include <basctl/scriptdocument.hxx>
 #include <comphelper/processfactory.hxx>
 #include <sal/log.hxx>
@@ -30,9 +32,232 @@ using namespace css::uno;
 using namespace css::lang;
 using namespace css::reflection;
 
+namespace
+{
+OUString helper_getTypeName(const Reference<XIdlClass>& xTypeClass)
+{
+    if (xTypeClass.is())
+    {
+        return xTypeClass->getName();
+    }
+    return u"<UnknownType>"_ustr;
+}
+
+void ImplGetMembersOfUnoType(SymbolInfoList& rMembers, const IdeSymbolInfo& 
rNode,
+                             std::unordered_set<OUString>& rVisitedTypes, 
sal_uInt16 nDepth = 0)
+{
+    constexpr sal_uInt16 nMaxRecursionDepth(8);
+
+    if (nDepth > nMaxRecursionDepth || rNode.eKind == 
IdeSymbolKind::UNO_TYPEDEF
+        || rNode.eKind == IdeSymbolKind::UNO_TYPE)
+    {
+        return;
+    }
+
+    if (!rNode.sQualifiedName.isEmpty() && 
!rVisitedTypes.insert(rNode.sQualifiedName).second)
+    {
+        return;
+    }
+    try
+    {
+        Reference<XIdlReflection> xReflection
+            = 
css::reflection::theCoreReflection::get(comphelper::getProcessComponentContext());
+        if (!xReflection.is())
+        {
+            return;
+        }
+
+        Reference<XIdlClass> xTypeClass = 
xReflection->forName(rNode.sQualifiedName);
+        if (!xTypeClass.is())
+        {
+            return;
+        }
+
+        if (rNode.eKind == IdeSymbolKind::UNO_SERVICE)
+        {
+            for (const auto& xInterface : xTypeClass->getInterfaces())
+            {
+                auto pNode = std::make_shared<IdeSymbolInfo>(
+                    xInterface->getName(), IdeSymbolKind::UNO_INTERFACE, 
rNode.sIdentifier);
+                pNode->sQualifiedName = xInterface->getName();
+                rMembers.push_back(pNode);
+            }
+            return;
+        }
+
+        for (const auto& xMethod : xTypeClass->getMethods())
+        {
+            if (!xMethod.is())
+            {
+                continue;
+            }
+            auto pNode = std::make_shared<IdeSymbolInfo>(
+                xMethod->getName(), IdeSymbolKind::UNO_METHOD, 
rNode.sIdentifier);
+            pNode->sReturnTypeName = 
helper_getTypeName(xMethod->getReturnType());
+            for (const auto& rParam : xMethod->getParameterInfos())
+            {
+                IdeParamInfo aParam;
+                aParam.sName = rParam.aName;
+                aParam.sTypeName = helper_getTypeName(rParam.aType);
+                aParam.bIsIn = (rParam.aMode == css::reflection::ParamMode_IN
+                                || rParam.aMode == 
css::reflection::ParamMode_INOUT);
+                aParam.bIsOut = (rParam.aMode == css::reflection::ParamMode_OUT
+                                 || rParam.aMode == 
css::reflection::ParamMode_INOUT);
+                pNode->aParameters.push_back(std::move(aParam));
+            }
+
+            rMembers.push_back(pNode);
+        }
+
+        for (const auto& xField : xTypeClass->getFields())
+        {
+            if (!xField.is())
+            {
+                continue;
+            }
+            IdeSymbolKind eFieldKind = IdeSymbolKind::UNO_PROPERTY;
+            if (rNode.eKind == IdeSymbolKind::UNO_STRUCT
+                || rNode.eKind == IdeSymbolKind::UNO_EXCEPTION)
+            {
+                eFieldKind = IdeSymbolKind::UNO_FIELD;
+            }
+            else if (rNode.eKind == IdeSymbolKind::UNO_ENUM
+                     || rNode.eKind == IdeSymbolKind::UNO_CONSTANTS)
+            {
+                eFieldKind = IdeSymbolKind::ENUM_MEMBER;
+            }
+
+            auto pMember
+                = std::make_shared<IdeSymbolInfo>(xField->getName(), 
eFieldKind, rNode.sIdentifier);
+            pMember->sTypeName = helper_getTypeName(xField->getType());
+
+            Reference<XIdlClass> xFieldType = xField->getType();
+            if (xFieldType.is())
+            {
+                TypeClass eTypeClass = xFieldType->getTypeClass();
+                if (eTypeClass == TypeClass_STRUCT || eTypeClass == 
TypeClass_INTERFACE)
+                {
+                    IdeSymbolInfo tempFieldNodeAsType(
+                        xFieldType->getName(), 
UnoApiHierarchy::typeClassToSymbolKind(eTypeClass),
+                        rNode.sIdentifier);
+                    tempFieldNodeAsType.sQualifiedName = xFieldType->getName();
+
+                    // Recursively get the nested members
+                    SymbolInfoList aNestedMembers;
+                    ImplGetMembersOfUnoType(aNestedMembers, 
tempFieldNodeAsType, rVisitedTypes,
+                                            nDepth + 1);
+
+                    for (const auto& pNested : aNestedMembers)
+                    {
+                        pMember->mapMembers[pNested->sName] = pNested;
+                    }
+                }
+            }
+
+            rMembers.push_back(pMember);
+        }
+    }
+    catch (const Exception& e)
+    {
+        SAL_WARN("basctl", "Exception while getting members of UNO type: " << 
e.Message);
+    }
+}
+
+void ImplGetMembersOfBasicModule(SymbolInfoList& rMembers, const 
IdeSymbolInfo& rNode)
+{
+    ScriptDocument aDoc = rNode.sOriginLocation.isEmpty()
+                              ? ScriptDocument::getApplicationScriptDocument()
+                              : 
ScriptDocument::getDocumentWithURLOrCaption(rNode.sOriginLocation);
+    if (!aDoc.isAlive())
+    {
+        return;
+    }
+
+    BasicManager* pBasMgr = aDoc.getBasicManager();
+    StarBASIC* pLib = pBasMgr ? pBasMgr->GetLib(rNode.sOriginLibrary) : 
nullptr;
+    SbModule* pModule = pLib ? pLib->FindModule(rNode.sName) : nullptr;
+    if (pModule)
+    {
+        if (!pModule->IsCompiled())
+        {
+            pModule->Compile();
+        }
+        SbxArray* pMethods = pModule->GetMethods().get();
+        for (sal_uInt32 i = 0; i < pMethods->Count(); ++i)
+        {
+            SbxVariable* pVar = pMethods->Get(i);
+            if (auto* pMethod = dynamic_cast<SbMethod*>(pVar))
+            {
+                if (pMethod->IsHidden())
+                {
+                    continue;
+                }
+
+                IdeSymbolKind eKind = (pMethod->GetType() == 
SbxDataType::SbxVOID)
+                                          ? IdeSymbolKind::SUB
+                                          : IdeSymbolKind::FUNCTION;
+                auto pMember
+                    = std::make_shared<IdeSymbolInfo>(pMethod->GetName(), 
eKind, rNode.sIdentifier);
+                rMembers.push_back(pMember);
+            }
+        }
+    }
+}
+
+void ImplGetChildrenOfBasicLibrary(SymbolInfoList& rChildren, const 
IdeSymbolInfo& rParent)
+{
+    ScriptDocument aDoc
+        = rParent.sOriginLocation.isEmpty()
+              ? ScriptDocument::getApplicationScriptDocument()
+              : 
ScriptDocument::getDocumentWithURLOrCaption(rParent.sOriginLocation);
+    if (!aDoc.isAlive())
+    {
+        return;
+    }
+
+    if (rParent.eKind == IdeSymbolKind::ROOT_APPLICATION_LIBS
+        || rParent.eKind == IdeSymbolKind::ROOT_DOCUMENT_LIBS)
+    {
+        for (const auto& rLibName : aDoc.getLibraryNames())
+        {
+            auto pNode = std::make_shared<IdeSymbolInfo>(rLibName, 
IdeSymbolKind::LIBRARY,
+                                                         rParent.sIdentifier);
+            if (aDoc.isDocument())
+            {
+                pNode->sOriginLocation = aDoc.getDocument()->getURL();
+            }
+            pNode->sParentName = rParent.sName;
+            rChildren.push_back(pNode);
+        }
+    }
+    else if (rParent.eKind == IdeSymbolKind::LIBRARY)
+    {
+        BasicManager* pBasMgr = aDoc.getBasicManager();
+        if (aDoc.hasLibrary(E_SCRIPTS, rParent.sName))
+        {
+            StarBASIC* pLib = pBasMgr->GetLib(rParent.sName);
+            if (pLib)
+            {
+                for (const auto& pModule : pLib->GetModules())
+                {
+                    auto pNode = std::make_shared<IdeSymbolInfo>(
+                        pModule->GetName(), IdeSymbolKind::MODULE, 
rParent.sIdentifier);
+                    pNode->sOriginLocation = rParent.sOriginLocation;
+                    pNode->sOriginLibrary = rParent.sName;
+                    pNode->sParentName = rParent.sName;
+                    rChildren.push_back(pNode);
+                }
+            }
+        }
+    }
+}
+
+} // anonymous namespace
+
 IdeDataProvider::IdeDataProvider()
     : m_pUnoHierarchy(std::make_unique<UnoApiHierarchy>())
     , m_bInitialized(false)
+    , m_eCurrentScope(IdeBrowserScope::ALL_LIBRARIES)
 {
 }
 
@@ -123,12 +348,14 @@ void IdeDataProvider::Initialize()
     }
     SAL_INFO("basctl", "BASIC library preload finished.");
 
-    m_aTopLevelNodes.clear();
-    m_aTopLevelNodes.push_back(
+    m_aAllTopLevelNodes.clear();
+    m_aAllTopLevelNodes.push_back(
         std::make_shared<IdeSymbolInfo>(u"UNO APIs", 
IdeSymbolKind::ROOT_UNO_APIS, u"root"));
-    m_aTopLevelNodes.push_back(std::make_shared<IdeSymbolInfo>(
+    m_aAllTopLevelNodes.push_back(std::make_shared<IdeSymbolInfo>(
         u"Application Macros", IdeSymbolKind::ROOT_APPLICATION_LIBS, u"root"));
 
+    AddDocumentNodesWithModules();
+
     m_bInitialized = true;
     SAL_INFO("basctl", "Synchronous data provider initialization complete.");
 }
@@ -137,7 +364,7 @@ void IdeDataProvider::Reset()
 {
     SAL_INFO("basctl", "IdeDataProvider: Resetting state.");
     m_bInitialized = false;
-    m_aTopLevelNodes.clear();
+    m_aAllTopLevelNodes.clear();
 
     m_pUnoHierarchy = std::make_unique<UnoApiHierarchy>();
 }
@@ -190,6 +417,61 @@ void IdeDataProvider::performFullUnoScan()
                                                 << nProcessedCount << " 
types.");
 }
 
+void IdeDataProvider::AddDocumentNodesWithModules()
+{
+    for (const auto& rDoc : 
ScriptDocument::getAllScriptDocuments(ScriptDocument::DocumentsSorted))
+    {
+        if (rDoc.isAlive())
+        {
+            bool bHasModules = false;
+            BasicManager* pBasMgr = rDoc.getBasicManager();
+            if (pBasMgr)
+            {
+                sal_uInt16 nLibCount = pBasMgr->GetLibCount();
+                for (sal_uInt16 i = 0; i < nLibCount; ++i)
+                {
+                    StarBASIC* pLib = pBasMgr->GetLib(i);
+                    if (pLib && !pLib->GetModules().empty())
+                    {
+                        bHasModules = true;
+                        break;
+                    }
+                }
+            }
+
+            if (bHasModules)
+            {
+                auto pDocNode = std::make_shared<IdeSymbolInfo>(
+                    rDoc.getTitle(), IdeSymbolKind::ROOT_DOCUMENT_LIBS, 
u"root");
+                if (rDoc.isDocument() && rDoc.getDocument().is())
+                {
+                    pDocNode->sOriginLocation = rDoc.getDocument()->getURL();
+                }
+                m_aAllTopLevelNodes.push_back(pDocNode);
+            }
+        }
+    }
+}
+
+void IdeDataProvider::RefreshDocumentNodes()
+{
+    if (!m_bInitialized)
+    {
+        return;
+    }
+
+    // Remove old document nodes
+    m_aAllTopLevelNodes.erase(std::remove_if(m_aAllTopLevelNodes.begin(), 
m_aAllTopLevelNodes.end(),
+                                             [](const auto& pNode) {
+                                                 return pNode->eKind
+                                                        == 
IdeSymbolKind::ROOT_DOCUMENT_LIBS;
+                                             }),
+                              m_aAllTopLevelNodes.end());
+
+    // Re-add current document nodes
+    AddDocumentNodesWithModules();
+}
+
 SymbolInfoList IdeDataProvider::GetTopLevelNodes()
 {
     if (!m_bInitialized)
@@ -201,10 +483,71 @@ SymbolInfoList IdeDataProvider::GetTopLevelNodes()
         aNodes.push_back(pLoadingNode);
         return aNodes;
     }
-    return m_aTopLevelNodes;
+
+    SymbolInfoList aFilteredNodes;
+    for (const auto& pNode : m_aAllTopLevelNodes)
+    {
+        if (pNode->eKind == IdeSymbolKind::ROOT_UNO_APIS
+            || pNode->eKind == IdeSymbolKind::ROOT_APPLICATION_LIBS)
+        {
+            aFilteredNodes.push_back(pNode);
+        }
+        else if (pNode->eKind == IdeSymbolKind::ROOT_DOCUMENT_LIBS)
+        {
+            if (m_eCurrentScope == IdeBrowserScope::ALL_LIBRARIES)
+            {
+                aFilteredNodes.push_back(pNode);
+            }
+            else // CURRENT_DOCUMENT scope
+            {
+                // m_sCurrentDocumentURL now holds either the URL or the Title
+                bool bMatchesByURL = !m_sCurrentDocumentURL.isEmpty()
+                                     && (pNode->sOriginLocation == 
m_sCurrentDocumentURL);
+                bool bMatchesByTitle = !m_sCurrentDocumentURL.isEmpty()
+                                       && pNode->sOriginLocation.isEmpty()
+                                       && (pNode->sName == 
m_sCurrentDocumentURL);
+
+                if (bMatchesByURL || bMatchesByTitle)
+                {
+                    aFilteredNodes.push_back(pNode);
+                }
+            }
+        }
+    }
+    return aFilteredNodes;
 }
 
-GroupedSymbolInfoList IdeDataProvider::GetMembers(const IdeSymbolInfo& 
/*rNode*/) { return {}; }
+GroupedSymbolInfoList IdeDataProvider::GetMembers(const IdeSymbolInfo& rNode)
+{
+    GroupedSymbolInfoList aGroupedMembers;
+    SymbolInfoList aFlatMembers;
+
+    switch (rNode.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:
+        {
+            std::unordered_set<OUString> aVisitedTypes;
+            ImplGetMembersOfUnoType(aFlatMembers, rNode, aVisitedTypes);
+            break;
+        }
+        case IdeSymbolKind::MODULE:
+            ImplGetMembersOfBasicModule(aFlatMembers, rNode);
+            break;
+        default:
+            break;
+    }
+
+    for (const auto& pMemberInfo : aFlatMembers)
+    {
+        aGroupedMembers[pMemberInfo->eKind].push_back(pMemberInfo);
+    }
+    return aGroupedMembers;
+}
 
 SymbolInfoList IdeDataProvider::GetChildNodes(const IdeSymbolInfo& rParent)
 {
@@ -214,18 +557,36 @@ SymbolInfoList IdeDataProvider::GetChildNodes(const 
IdeSymbolInfo& rParent)
     }
 
     SymbolInfoList aChildren;
-    if (rParent.eKind == IdeSymbolKind::ROOT_UNO_APIS)
-    {
-        aChildren = m_pUnoHierarchy->m_hierarchyCache[OUString()];
-    }
-    else if (rParent.eKind == IdeSymbolKind::UNO_NAMESPACE)
+    switch (rParent.eKind)
     {
-        aChildren = m_pUnoHierarchy->m_hierarchyCache[rParent.sQualifiedName];
+        case IdeSymbolKind::ROOT_UNO_APIS:
+            aChildren = m_pUnoHierarchy->m_hierarchyCache[OUString()];
+            break;
+        case IdeSymbolKind::UNO_NAMESPACE:
+            aChildren = 
m_pUnoHierarchy->m_hierarchyCache[rParent.sQualifiedName];
+            break;
+        case IdeSymbolKind::ROOT_APPLICATION_LIBS:
+        case IdeSymbolKind::ROOT_DOCUMENT_LIBS:
+        case IdeSymbolKind::LIBRARY:
+            ImplGetChildrenOfBasicLibrary(aChildren, rParent);
+            break;
+        default:
+            break;
     }
 
     return aChildren;
 }
 
+void IdeDataProvider::SetScope(IdeBrowserScope eScope, const OUString& 
rCurrentDocumentURL)
+{
+    m_eCurrentScope = eScope;
+    m_sCurrentDocumentURL = rCurrentDocumentURL;
+    SAL_INFO("basctl", "SetScope: Scope changed to "
+                           << (eScope == IdeBrowserScope::ALL_LIBRARIES ? 
"ALL_LIBRARIES"
+                                                                        : 
"CURRENT_DOCUMENT")
+                           << " for doc URL: '" << rCurrentDocumentURL << "'");
+}
+
 void UnoApiHierarchy::addNode(std::u16string_view sQualifiedNameView, 
TypeClass eTypeClass)
 {
     const OUString sQualifiedName(sQualifiedNameView);
diff --git a/basctl/source/basicide/objectbrowser.cxx 
b/basctl/source/basicide/objectbrowser.cxx
index 5ccac1884b6f..821662c50855 100644
--- a/basctl/source/basicide/objectbrowser.cxx
+++ b/basctl/source/basicide/objectbrowser.cxx
@@ -12,18 +12,126 @@
 #include <objectbrowsersearch.hxx>
 #include <idedataprovider.hxx>
 #include "idetimer.hxx"
+
+#include <bitmaps.hlst>
 #include <iderid.hxx>
 #include <strings.hrc>
 
-#include <vcl/taskpanelist.hxx>
 #include <sal/log.hxx>
 #include <sfx2/bindings.hxx>
+#include <sfx2/event.hxx>
 #include <sfx2/sfxsids.hrc>
 #include <sfx2/viewfrm.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/taskpanelist.hxx>
 #include <vcl/weld.hxx>
 
 namespace basctl
 {
+namespace
+{
+// Helper to get an icon resource ID for a given symbol type.
+OUString GetIconForSymbol(IdeSymbolKind eKind)
+{
+    switch (eKind)
+    {
+        case IdeSymbolKind::ROOT_APPLICATION_LIBS:
+            return RID_BMP_INSTALLATION;
+        case IdeSymbolKind::ROOT_DOCUMENT_LIBS:
+            return RID_BMP_DOCUMENT;
+        case IdeSymbolKind::LIBRARY:
+            return RID_BMP_MODLIB;
+        case IdeSymbolKind::MODULE:
+            return RID_BMP_MODULE;
+        case IdeSymbolKind::FUNCTION:
+        case IdeSymbolKind::SUB:
+            return RID_BMP_MACRO;
+        case IdeSymbolKind::ROOT_UNO_APIS:
+            return u"cmd/sc_configuredialog.png"_ustr;
+        case IdeSymbolKind::CLASS_MODULE:
+            return u"cmd/sc_insertobject.png"_ustr;
+        case IdeSymbolKind::UNO_NAMESPACE:
+            return u"cmd/sc_navigator.png"_ustr;
+        case IdeSymbolKind::UNO_INTERFACE:
+            return u"cmd/sc_insertplugin.png"_ustr;
+        case IdeSymbolKind::UNO_SERVICE:
+            return u"cmd/sc_insertobjectstarmath.png"_ustr;
+        case IdeSymbolKind::UNO_STRUCT:
+            return u"cmd/sc_insertframe.png"_ustr;
+        case IdeSymbolKind::UNO_ENUM:
+            return u"cmd/sc_numberformatmenu.png"_ustr;
+        case IdeSymbolKind::UNO_PROPERTY:
+            return u"cmd/sc_controlproperties.png"_ustr;
+        case IdeSymbolKind::UNO_METHOD:
+            return u"cmd/sc_insertformula.png"_ustr;
+        case IdeSymbolKind::ENUM_MEMBER:
+            return u"cmd/sc_bullet.png"_ustr;
+        case IdeSymbolKind::PLACEHOLDER:
+            return u"cmd/sc_more.png"_ustr;
+        default:
+            return u"cmd/sc_insertobject.png"_ustr;
+    }
+}
+
+// Helper to determine if a symbol is expandable in the tree view.
+bool IsExpandable(const IdeSymbolInfo& rSymbol)
+{
+    switch (rSymbol.eKind)
+    {
+        case IdeSymbolKind::ROOT_UNO_APIS:
+        case IdeSymbolKind::ROOT_APPLICATION_LIBS:
+        case IdeSymbolKind::ROOT_DOCUMENT_LIBS:
+        case IdeSymbolKind::LIBRARY:
+        case IdeSymbolKind::MODULE:
+        case IdeSymbolKind::CLASS_MODULE:
+        case IdeSymbolKind::UNO_NAMESPACE:
+            return true;
+        default:
+            // This case is for future use when members are nested.
+            return !rSymbol.mapMembers.empty();
+    }
+}
+
+// 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,
+              const weld::TreeIter* pParent, const 
std::shared_ptr<IdeSymbolInfo>& pSymbol,
+              bool bChildrenOnDemand, weld::TreeIter* pRetIter = nullptr)
+{
+    if (!pSymbol)
+        return;
+
+    OUString sId = pSymbol->sIdentifier;
+    if (pSymbol->eKind == IdeSymbolKind::PLACEHOLDER)
+    {
+        sId = u"placeholder_for:"_ustr + (pParent ? 
rTargetTree.get_id(*pParent) : u"root"_ustr);
+        pSymbol->sIdentifier = sId;
+    }
+
+    if (sId.isEmpty())
+    {
+        SAL_WARN("basctl", "AddEntry - Symbol with empty ID. Name: " << 
pSymbol->sName);
+        return;
+    }
+
+    rStore.push_back(pSymbol);
+    rIndex[sId] = pSymbol;
+
+    std::unique_ptr<weld::TreeIter> xLocalIter;
+    if (!pRetIter)
+    {
+        xLocalIter = rTargetTree.make_iterator();
+        pRetIter = xLocalIter.get();
+    }
+
+    rTargetTree.insert(pParent, -1, &pSymbol->sName, &sId, nullptr, nullptr, 
bChildrenOnDemand,
+                       pRetIter);
+    OUString sIconName = GetIconForSymbol(pSymbol->eKind);
+    rTargetTree.set_image(*pRetIter, sIconName, -1);
+}
+
+} // anonymous namespace
+
 ObjectBrowser::ObjectBrowser(Shell& rShell, vcl::Window* pParent)
     : basctl::DockingWindow(pParent, 
u"modules/BasicIDE/ui/objectbrowser.ui"_ustr,
                             u"ObjectBrowser"_ustr)
@@ -31,7 +139,7 @@ ObjectBrowser::ObjectBrowser(Shell& rShell, vcl::Window* 
pParent)
     , m_pDataProvider(std::make_unique<IdeDataProvider>())
     , m_bDisposed(false)
     , m_eInitState(ObjectBrowserInitState::NotInitialized)
-    , m_bUIInitialized(false)
+    , m_bDataMayBeStale(true)
     , m_pDocNotifier(std::make_unique<DocumentEventNotifier>(*this))
 {
     SetText(IDEResId(RID_STR_OBJECT_BROWSER));
@@ -46,7 +154,6 @@ void ObjectBrowser::Initialize()
     if (m_eInitState != ObjectBrowserInitState::NotInitialized)
         return;
 
-    // Set state to initializing
     m_eInitState = ObjectBrowserInitState::Initializing;
 
     // Handles to all our widgets
@@ -66,8 +173,8 @@ void ObjectBrowser::Initialize()
 
     if (m_xScopeSelector)
     {
-        m_xScopeSelector->append(IDEResId(RID_STR_OB_SCOPE_ALL), 
u"ALL_LIBRARIES"_ustr);
-        m_xScopeSelector->append(IDEResId(RID_STR_OB_SCOPE_CURRENT), 
u"CURRENT_DOCUMENT"_ustr);
+        m_xScopeSelector->append(u"ALL_LIBRARIES"_ustr, 
IDEResId(RID_STR_OB_SCOPE_ALL));
+        m_xScopeSelector->append(u"CURRENT_DOCUMENT"_ustr, 
IDEResId(RID_STR_OB_SCOPE_CURRENT));
         m_xScopeSelector->set_active(0);
         m_xScopeSelector->connect_changed(LINK(this, ObjectBrowser, 
OnScopeChanged));
     }
@@ -93,7 +200,19 @@ void ObjectBrowser::Initialize()
         GetParent()->GetSystemWindow()->GetTaskPaneList()->AddWindow(this);
     }
 
-    m_bUIInitialized = true;
+    // Start listening for application-wide events like document activation
+    StartListening(*SfxGetpApp());
+    SfxViewFrame* pViewFrame = SfxViewFrame::Current();
+    if (pViewFrame)
+    {
+        if (SfxObjectShell* pObjShell = pViewFrame->GetObjectShell())
+        {
+            m_sLastActiveDocumentIdentifier = pObjShell->GetTitle();
+            SAL_INFO("basctl", "ObjectBrowser::Initialize: Active document -> 
'"
+                                   << m_sLastActiveDocumentIdentifier << "'");
+        }
+    }
+
     m_eInitState = ObjectBrowserInitState::Initialized;
 }
 
@@ -113,6 +232,9 @@ void ObjectBrowser::dispose()
             pTaskPaneList->RemoveWindow(this);
     }
 
+    // Stop listening to SFX events
+    EndListening(*SfxGetpApp());
+
     // Disconnect all signals
     if (m_xScopeSelector)
         m_xScopeSelector->connect_changed(Link<weld::ComboBox&, void>());
@@ -121,20 +243,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>());
-
-    if (m_pDocNotifier)
-    {
-        m_pDocNotifier->dispose();
-        m_pDocNotifier.reset();
-    }
-
-    if (m_pSearchHandler)
-    {
-        m_pSearchHandler.reset();
-    }
 
+    m_pDocNotifier->dispose();
+    m_pDocNotifier.reset();
+    m_pSearchHandler.reset();
     m_pDataProvider.reset();
 
     // Destroy widgets
@@ -163,55 +275,121 @@ void ObjectBrowser::Show(bool bVisible)
 {
     DockingWindow::Show(bVisible);
     if (!bVisible)
+    {
         return;
+    }
 
     if (m_eInitState == ObjectBrowserInitState::NotInitialized)
     {
         Initialize();
     }
 
-    if (m_pDataProvider && !m_pDataProvider->IsInitialized())
+    if (m_pDataProvider)
     {
-        ShowLoadingState();
+        if (!m_pDataProvider->IsInitialized())
         {
+            ShowLoadingState();
             weld::WaitObject aWait(GetFrameWeld());
             m_pDataProvider->Initialize();
+            m_bDataMayBeStale = true; // Data is fresh, but force a refresh
+        }
+
+        if (m_bDataMayBeStale)
+        {
+            RefreshUI();
+            m_bDataMayBeStale = false;
         }
-        RefreshUI();
     }
 }
 
 void ObjectBrowser::RefreshUI(bool /*bForceKeepUno*/)
 {
-    if (m_pDataProvider && m_pDataProvider->IsInitialized())
+    IdeTimer aTimer(u"ObjectBrowser::RefreshUI"_ustr);
+    if (!m_pDataProvider || !m_pDataProvider->IsInitialized())
     {
-        PopulateLeftTree();
-        if (m_xStatusLabel)
-            m_xStatusLabel->set_label(u"Ready"_ustr);
+        ShowLoadingState();
+        return;
     }
-    else
+
+    m_xLeftTreeView->freeze();
+    m_xRightMembersView->freeze();
+    ClearLeftTreeView();
+    ClearRightTreeView();
+
+    
static_cast<IdeDataProvider*>(m_pDataProvider.get())->RefreshDocumentNodes();
+
+    if (m_xRightPaneHeaderLabel)
     {
-        ShowLoadingState();
+        m_xRightPaneHeaderLabel->set_label(IDEResId(RID_STR_OB_GROUP_MEMBERS));
+    }
+
+    if (m_xDetailPane)
+    {
+        m_xDetailPane->set_text(u""_ustr);
+    }
+
+    // Get the filtered list of nodes based on the current scope
+    SymbolInfoList aTopLevelNodes = m_pDataProvider->GetTopLevelNodes();
+    for (const auto& pSymbol : aTopLevelNodes)
+    {
+        if (pSymbol)
+        {
+            AddEntry(*m_xLeftTreeView, m_aLeftTreeSymbolStore, 
m_aLeftTreeSymbolIndex, nullptr,
+                     pSymbol, IsExpandable(*pSymbol));
+        }
     }
+
+    m_xLeftTreeView->thaw();
+
+    if (m_xStatusLabel)
+        m_xStatusLabel->set_label(u"Ready"_ustr);
 }
 
-void ObjectBrowser::PopulateLeftTree()
+void ObjectBrowser::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
 {
-    if (!m_xLeftTreeView)
+    if (m_bDisposed)
+    {
         return;
+    }
 
-    m_xLeftTreeView->freeze();
-    ClearTreeView(*m_xLeftTreeView, m_aLeftTreeSymbolStore);
+    const SfxEventHint* pEventHint = dynamic_cast<const SfxEventHint*>(&rHint);
+    if (!pEventHint || pEventHint->GetEventId() != SfxEventHintId::ActivateDoc)
+    {
+        return;
+    }
 
-    SymbolInfoList aTopLevelNodes = m_pDataProvider->GetTopLevelNodes();
-    for (const auto& pNode : aTopLevelNodes)
+    rtl::Reference<SfxObjectShell> pObjShell = pEventHint->GetObjShell();
+    if (!pObjShell)
     {
-        m_aLeftTreeSymbolStore.push_back(pNode);
-        m_xLeftTreeView->insert(nullptr, -1, &pNode->sName, nullptr, nullptr, 
nullptr, true,
-                                nullptr);
+        return;
     }
 
-    m_xLeftTreeView->thaw();
+    // Filter out activations of the BASIC IDE
+    if (pObjShell->GetFactory().GetDocumentServiceName().endsWith(u"BasicIDE"))
+    {
+        return;
+    }
+
+    OUString sNewDocTitle = pObjShell->GetTitle();
+    if (sNewDocTitle != m_sLastActiveDocumentIdentifier)
+    {
+        m_sLastActiveDocumentIdentifier = sNewDocTitle;
+        SAL_INFO("basctl", "ObjectBrowser::Notify: Document activated -> '" << 
sNewDocTitle << "'");
+
+        // If the user is in "Current Document" mode, a focus change
+        // should trigger an immediate refresh to reflect the new context.
+        if (IsVisible() && m_xScopeSelector
+            && m_xScopeSelector->get_active_id() == "CURRENT_DOCUMENT")
+        {
+            SAL_INFO("basctl",
+                     "ObjectBrowser::Notify: In CURRENT_DOCUMENT scope, 
updating scope to '"
+                         << sNewDocTitle << "' and refreshing UI.");
+
+            // Update scope to newly activated document
+            m_pDataProvider->SetScope(IdeBrowserScope::CURRENT_DOCUMENT, 
sNewDocTitle);
+            ScheduleRefresh();
+        }
+    }
 }
 
 void ObjectBrowser::ShowLoadingState()
@@ -219,35 +397,52 @@ void ObjectBrowser::ShowLoadingState()
     if (m_xLeftTreeView)
     {
         m_xLeftTreeView->freeze();
-        ClearTreeView(*m_xLeftTreeView, m_aLeftTreeSymbolStore);
-
+        ClearLeftTreeView();
         auto pLoadingNode = 
std::make_shared<IdeSymbolInfo>(u"[Initializing...]",
                                                             
IdeSymbolKind::PLACEHOLDER, u"");
-
-        m_aLeftTreeSymbolStore.push_back(pLoadingNode);
-        m_xLeftTreeView->insert(nullptr, -1, &pLoadingNode->sName, nullptr, 
nullptr, nullptr, false,
-                                nullptr);
+        AddEntry(*m_xLeftTreeView, m_aLeftTreeSymbolStore, 
m_aLeftTreeSymbolIndex, nullptr,
+                 pLoadingNode, false);
         m_xLeftTreeView->thaw();
     }
     if (m_xStatusLabel)
+    {
         m_xStatusLabel->set_label(u"Initializing Object Browser..."_ustr);
+    }
+}
+
+void ObjectBrowser::ClearLeftTreeView()
+{
+    if (m_xLeftTreeView)
+        m_xLeftTreeView->clear();
+    m_aLeftTreeSymbolStore.clear();
+    m_aLeftTreeSymbolIndex.clear();
 }
 
-void ObjectBrowser::ClearTreeView(weld::TreeView& rTree,
-                                  
std::vector<std::shared_ptr<basctl::IdeSymbolInfo>>& rStore)
+void ObjectBrowser::ClearRightTreeView()
 {
-    rTree.clear();
-    rStore.clear();
+    if (m_xRightMembersView)
+        m_xRightMembersView->clear();
+    m_aRightTreeSymbolStore.clear();
+    m_aRightTreeSymbolIndex.clear();
+}
+
+void ObjectBrowser::ScheduleRefresh()
+{
+    if (IsVisible())
+        RefreshUI();
+    else
+        m_bDataMayBeStale = true;
 }
 
-void ObjectBrowser::onDocumentCreated(const ScriptDocument&) { /* STUB */}
-void ObjectBrowser::onDocumentOpened(const ScriptDocument&) { /* STUB */}
+// Document Event Handlers
+void ObjectBrowser::onDocumentCreated(const ScriptDocument&) { 
ScheduleRefresh(); }
+void ObjectBrowser::onDocumentOpened(const ScriptDocument&) { 
ScheduleRefresh(); }
 void ObjectBrowser::onDocumentSave(const ScriptDocument&) { /* STUB */}
-void ObjectBrowser::onDocumentSaveDone(const ScriptDocument&) { /* STUB */}
+void ObjectBrowser::onDocumentSaveDone(const ScriptDocument&) { 
ScheduleRefresh(); }
 void ObjectBrowser::onDocumentSaveAs(const ScriptDocument&) { /* STUB */}
-void ObjectBrowser::onDocumentSaveAsDone(const ScriptDocument&) { /* STUB */}
-void ObjectBrowser::onDocumentClosed(const ScriptDocument&) { /* STUB */}
-void ObjectBrowser::onDocumentTitleChanged(const ScriptDocument&) { /* STUB */}
+void ObjectBrowser::onDocumentSaveAsDone(const ScriptDocument&) { 
ScheduleRefresh(); }
+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 */}
@@ -256,7 +451,19 @@ IMPL_STATIC_LINK(ObjectBrowser, OnNodeExpand, const 
weld::TreeIter&, /*rParentIt
 {
     return false;
 }
-IMPL_STATIC_LINK(ObjectBrowser, OnScopeChanged, weld::ComboBox&, 
/*rComboBox*/, void) { /* STUB */}
+
+IMPL_LINK(ObjectBrowser, OnScopeChanged, weld::ComboBox&, rComboBox, void)
+{
+    if (m_bDisposed || !m_pDataProvider)
+        return;
+
+    OUString sSelectedId = rComboBox.get_active_id();
+    IdeBrowserScope eScope = (sSelectedId == "ALL_LIBRARIES") ? 
IdeBrowserScope::ALL_LIBRARIES
+                                                              : 
IdeBrowserScope::CURRENT_DOCUMENT;
+
+    m_pDataProvider->SetScope(eScope, m_sLastActiveDocumentIdentifier);
+    RefreshUI();
+}
 
 } // namespace basctl
 
diff --git a/basctl/source/inc/idedataprovider.hxx 
b/basctl/source/inc/idedataprovider.hxx
index 4d943b1f7556..bb2470bd4a84 100644
--- a/basctl/source/inc/idedataprovider.hxx
+++ b/basctl/source/inc/idedataprovider.hxx
@@ -11,12 +11,10 @@
 
 #include <basctl/idecodecompletiontypes.hxx>
 
+#include <map>
 #include <memory>
 #include <vector>
 
-#include <rtl/ustring.hxx>
-#include <tools/link.hxx>
-
 #include <com/sun/star/uno/TypeClass.hpp>
 
 namespace basctl
@@ -25,6 +23,12 @@ using IdeSymbolInfo = basctl::IdeSymbolInfo;
 using SymbolInfoList = std::vector<std::shared_ptr<IdeSymbolInfo>>;
 using GroupedSymbolInfoList = std::map<IdeSymbolKind, SymbolInfoList>;
 
+enum class IdeBrowserScope
+{
+    ALL_LIBRARIES,
+    CURRENT_DOCUMENT
+};
+
 // Data structure for the in-memory UNO type hierarchy
 struct UnoApiHierarchy
 {
@@ -46,6 +50,8 @@ public:
     virtual SymbolInfoList GetTopLevelNodes() = 0;
     virtual GroupedSymbolInfoList GetMembers(const IdeSymbolInfo& rNode) = 0;
     virtual SymbolInfoList GetChildNodes(const IdeSymbolInfo& rParent) = 0;
+    // Scope Management
+    virtual void SetScope(IdeBrowserScope eScope, const OUString& 
rCurrentDocumentURL) = 0;
 };
 
 class IdeDataProvider : public IdeDataProviderInterface
@@ -56,20 +62,26 @@ public:
     void Initialize() override;
     bool IsInitialized() const override { return m_bInitialized; }
     void Reset() override;
+    void AddDocumentNodesWithModules();
+    void RefreshDocumentNodes();
 
     SymbolInfoList GetTopLevelNodes() override;
     GroupedSymbolInfoList GetMembers(const IdeSymbolInfo& rNode) override;
     SymbolInfoList GetChildNodes(const IdeSymbolInfo& rParent) override;
+    void SetScope(IdeBrowserScope eScope, const OUString& rCurrentDocumentURL) 
override;
 
 private:
     void performFullUnoScan();
+    void addDocumentNodes();
 
     // Core data
     std::unique_ptr<UnoApiHierarchy> m_pUnoHierarchy;
-    SymbolInfoList m_aTopLevelNodes;
+    SymbolInfoList m_aAllTopLevelNodes;
 
     // State management
     bool m_bInitialized = false;
+    IdeBrowserScope m_eCurrentScope = IdeBrowserScope::ALL_LIBRARIES;
+    OUString m_sCurrentDocumentURL;
 };
 
 } // namespace basctl
diff --git a/basctl/source/inc/objectbrowser.hxx 
b/basctl/source/inc/objectbrowser.hxx
index 196d009bba22..dd69e65b34cd 100644
--- a/basctl/source/inc/objectbrowser.hxx
+++ b/basctl/source/inc/objectbrowser.hxx
@@ -14,12 +14,18 @@
 #include "idedataprovider.hxx"
 
 #include <basctl/idecodecompletiontypes.hxx>
+#include <sfx2/app.hxx>
+#include <sfx2/docfac.hxx>
+#include <sfx2/event.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/viewfrm.hxx>
+#include <vcl/weld.hxx>
 
+#include <map>
 #include <memory>
 #include <vector>
 
-#include <vcl/weld.hxx>
-
 namespace basctl
 {
 class Shell;
@@ -35,7 +41,9 @@ enum class ObjectBrowserInitState
     Disposed
 };
 
-class ObjectBrowser : public basctl::DockingWindow, public 
basctl::DocumentEventListener
+class ObjectBrowser : public basctl::DockingWindow,
+                      public basctl::DocumentEventListener,
+                      public SfxListener
 {
 public:
     ObjectBrowser(Shell& rShell, vcl::Window* pParent);
@@ -47,7 +55,10 @@ public:
     void Show(bool bVisible = true);
     void RefreshUI(bool bForceKeepUno = false);
 
-    // DocumentEventListener stubs
+    virtual void Notify(SfxBroadcaster& rBC, const SfxHint& rHint) override;
+
+    void ScheduleRefresh();
+
     void onDocumentCreated(const ScriptDocument& _rDocument) override;
     void onDocumentOpened(const ScriptDocument& _rDocument) override;
     void onDocumentSave(const ScriptDocument& _rDocument) override;
@@ -63,13 +74,11 @@ public:
     weld::Button* GetClearSearchButton() { return m_xClearSearchButton.get(); }
 
 private:
+    // Initialization & Core UI Management
     void Initialize();
-
-    // Core tree management methods
-    static void ClearTreeView(weld::TreeView& rTree,
-                              
std::vector<std::shared_ptr<basctl::IdeSymbolInfo>>& rStore);
-    void PopulateLeftTree();
     void ShowLoadingState();
+    void ClearLeftTreeView();
+    void ClearRightTreeView();
 
     // Core References
     Shell* m_pShell;
@@ -80,7 +89,8 @@ private:
     // State Management
     bool m_bDisposed = false;
     ObjectBrowserInitState m_eInitState = 
ObjectBrowserInitState::NotInitialized;
-    bool m_bUIInitialized = false;
+    bool m_bDataMayBeStale = true;
+    OUString m_sLastActiveDocumentIdentifier;
 
     // UI Widgets
     std::unique_ptr<weld::ComboBox> m_xScopeSelector;
@@ -94,10 +104,11 @@ private:
     std::unique_ptr<weld::Button> m_xForwardButton;
     std::unique_ptr<weld::Button> m_xClearSearchButton;
 
-    // Data Storage for TreeViews
+    // Data Storage & Indexes
     std::vector<std::shared_ptr<basctl::IdeSymbolInfo>> m_aLeftTreeSymbolStore;
     std::vector<std::shared_ptr<basctl::IdeSymbolInfo>> 
m_aRightTreeSymbolStore;
-    std::map<OUString, size_t> m_aNextChunk; // For lazy-loading nodes
+    std::map<OUString, std::shared_ptr<IdeSymbolInfo>> m_aLeftTreeSymbolIndex;
+    std::map<OUString, std::shared_ptr<IdeSymbolInfo>> m_aRightTreeSymbolIndex;
 
     // Search Handler
     std::unique_ptr<ObjectBrowserSearch> m_pSearchHandler;
@@ -109,7 +120,7 @@ private:
     DECL_STATIC_LINK(ObjectBrowser, OnLeftTreeSelect, weld::TreeView&, void);
     DECL_STATIC_LINK(ObjectBrowser, OnRightTreeSelect, weld::TreeView&, void);
     DECL_STATIC_LINK(ObjectBrowser, OnNodeExpand, const weld::TreeIter&, bool);
-    DECL_STATIC_LINK(ObjectBrowser, OnScopeChanged, weld::ComboBox&, void);
+    DECL_LINK(OnScopeChanged, weld::ComboBox&, void);
 };
 
 } // namespace basctl

Reply via email to