basctl/inc/strings.hrc                     |   49 +++
 basctl/source/basicide/idedataprovider.cxx |   75 ++++
 basctl/source/basicide/objectbrowser.cxx   |  441 ++++++++++++++++++++++++++++-
 basctl/source/inc/objectbrowser.hxx        |    3 
 4 files changed, 554 insertions(+), 14 deletions(-)

New commits:
commit 84401c3b181ba7cca4674aa29154443ec4e2aac0
Author:     Devansh Varshney <[email protected]>
AuthorDate: Thu Oct 9 01:34:31 2025 +0530
Commit:     Jonathan Clark <[email protected]>
CommitDate: Fri Oct 17 01:52:45 2025 +0200

    tdf#165785: basctl: Activate Details Pane and Contextual Status Bar
    
    This patch brings the Object Browser's information panels to life,
    activating the main details pane and the bottom status bar.
    
    When a user selects an item, the details pane now displays a rich,
    formatted view of its properties, such as its full signature or a summary
    of its contents. The status bar provides helpful context, like member
    counts, and displays the total time taken for the initial data load.
    
    (GSoC 2025 - Object Browser: Rich Information Display)
    
    Change-Id: Iadbc2f3dfeb8eba357558d8a98e7ef663a3e2683
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192079
    Reviewed-by: Jonathan Clark <[email protected]>
    Tested-by: Jenkins

diff --git a/basctl/inc/strings.hrc b/basctl/inc/strings.hrc
index 344cb8382630..f4ff665ac009 100644
--- a/basctl/inc/strings.hrc
+++ b/basctl/inc/strings.hrc
@@ -118,6 +118,55 @@
 #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")
+// For Details Pane Formatting and Symbol Types
+#define RID_STR_OB_AS                       NC_("RID_STR_OB_AS", " As ")
+#define RID_STR_OB_OPTIONAL                 NC_("RID_STR_OB_OPTIONAL", 
"Optional")
+#define RID_STR_OB_BYREF                    NC_("RID_STR_OB_BYREF", "ByRef")
+#define RID_STR_OB_OUT_PARAM                NC_("RID_STR_OB_OUT_PARAM", 
"[out]")
+#define RID_STR_OB_INOUT_PARAM              NC_("RID_STR_OB_INOUT_PARAM", 
"[inout]")
+#define RID_STR_OB_ACCESS                   NC_("RID_STR_OB_ACCESS", "Access: 
")
+#define RID_STR_OB_RETURNS                  NC_("RID_STR_OB_RETURNS", 
"Returns: ")
+#define RID_STR_OB_TYPE                     NC_("RID_STR_OB_TYPE", "Type: ")
+#define RID_STR_OB_PARAMETERS               NC_("RID_STR_OB_PARAMETERS", "
Parameters:
")
+#define RID_STR_OB_LOCATION_HEADER          NC_("RID_STR_OB_LOCATION_HEADER", "
Location
")
+#define RID_STR_OB_MEMBER_OF                NC_("RID_STR_OB_MEMBER_OF", 
"Member of: ")
+#define RID_STR_OB_LIBRARY                  NC_("RID_STR_OB_LIBRARY", 
"Library: ")
+#define RID_STR_OB_DOCUMENT                 NC_("RID_STR_OB_DOCUMENT", 
"Document: ")
+#define RID_STR_OB_LINE                     NC_("RID_STR_OB_LINE", "Line: ")
+#define RID_STR_OB_FULLNAME_HEADER          NC_("RID_STR_OB_FULLNAME_HEADER", "
Full Name
")
+#define RID_STR_OB_CONTENTS_HEADER          NC_("RID_STR_OB_CONTENTS_HEADER", 
"Contents
")
+#define RID_STR_OB_TOTAL_MEMBERS            NC_("RID_STR_OB_TOTAL_MEMBERS", 
"Total members: ")
+#define RID_STR_OB_CONTAINS_ITEMS           NC_("RID_STR_OB_CONTAINS_ITEMS", 
"Contains %1 items")
+// For Access Modifiers
+#define RID_STR_OB_ACCESS_PUBLIC            NC_("RID_STR_OB_ACCESS_PUBLIC", 
"Public")
+#define RID_STR_OB_ACCESS_PRIVATE           NC_("RID_STR_OB_ACCESS_PRIVATE", 
"Private")
+// For Status Bar
+#define RID_STR_OB_MEMBERS_COUNT            NC_("RID_STR_OB_MEMBERS_COUNT", 
"%1 members")
+#define RID_STR_OB_ITEMS_COUNT              NC_("RID_STR_OB_ITEMS_COUNT", "%1 
items")
+#define RID_STR_OB_READY                    NC_("RID_STR_OB_READY", "Ready")
+#define RID_STR_OB_READY_LOADED             NC_("RID_STR_OB_READY_LOADED", "✅ 
Ready (loaded in %1s)")
+// For Symbol Type Names
+#define RID_STR_OB_TYPE_UNO_APIS_ROOT       
NC_("RID_STR_OB_TYPE_UNO_APIS_ROOT", "UNO APIs Root")
+#define RID_STR_OB_TYPE_APP_LIBS            NC_("RID_STR_OB_TYPE_APP_LIBS", 
"Application Libraries")
+#define RID_STR_OB_TYPE_DOC_LIBS            NC_("RID_STR_OB_TYPE_DOC_LIBS", 
"Document Libraries")
+#define RID_STR_OB_TYPE_LIBRARY             NC_("RID_STR_OB_TYPE_LIBRARY", 
"Library")
+#define RID_STR_OB_TYPE_MODULE              NC_("RID_STR_OB_TYPE_MODULE", 
"Module")
+#define RID_STR_OB_TYPE_CLASS_MODULE        
NC_("RID_STR_OB_TYPE_CLASS_MODULE", "Class Module")
+#define RID_STR_OB_TYPE_NAMESPACE           NC_("RID_STR_OB_TYPE_NAMESPACE", 
"Namespace")
+#define RID_STR_OB_TYPE_INTERFACE           NC_("RID_STR_OB_TYPE_INTERFACE", 
"Interface")
+#define RID_STR_OB_TYPE_SERVICE             NC_("RID_STR_OB_TYPE_SERVICE", 
"Service")
+#define RID_STR_OB_TYPE_STRUCT              NC_("RID_STR_OB_TYPE_STRUCT", 
"Struct")
+#define RID_STR_OB_TYPE_ENUM                NC_("RID_STR_OB_TYPE_ENUM", 
"Enumeration")
+#define RID_STR_OB_TYPE_CONSTANTS           NC_("RID_STR_OB_TYPE_CONSTANTS", 
"Constants")
+#define RID_STR_OB_TYPE_EXCEPTION           NC_("RID_STR_OB_TYPE_EXCEPTION", 
"Exception")
+#define RID_STR_OB_TYPE_TYPEDEF             NC_("RID_STR_OB_TYPE_TYPEDEF", 
"Typedef")
+#define RID_STR_OB_TYPE_METHOD              NC_("RID_STR_OB_TYPE_METHOD", 
"Method")
+#define RID_STR_OB_TYPE_PROPERTY            NC_("RID_STR_OB_TYPE_PROPERTY", 
"Property")
+#define RID_STR_OB_TYPE_FIELD               NC_("RID_STR_OB_TYPE_FIELD", 
"Field")
+#define RID_STR_OB_TYPE_SUB                 NC_("RID_STR_OB_TYPE_SUB", 
"Subroutine")
+#define RID_STR_OB_TYPE_FUNCTION            NC_("RID_STR_OB_TYPE_FUNCTION", 
"Function")
+#define RID_STR_OB_TYPE_ENUM_MEMBER         NC_("RID_STR_OB_TYPE_ENUM_MEMBER", 
"Enum Member")
+#define RID_STR_OB_TYPE_ITEM                NC_("RID_STR_OB_TYPE_ITEM", "Item")
 #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 06f8444297c6..2c8e39fade5c 100644
--- a/basctl/source/basicide/idedataprovider.cxx
+++ b/basctl/source/basicide/idedataprovider.cxx
@@ -43,6 +43,53 @@ OUString helper_getTypeName(const Reference<XIdlClass>& 
xTypeClass)
     return u"<UnknownType>"_ustr;
 }
 
+OUString GetBasicTypeName(SbxDataType eType)
+{
+    switch (eType)
+    {
+        case SbxDataType::SbxSTRING:
+            return u"String"_ustr;
+        case SbxDataType::SbxINTEGER:
+            return u"Integer"_ustr;
+        case SbxDataType::SbxLONG:
+            return u"Long"_ustr;
+        case SbxDataType::SbxULONG:
+            return u"ULong"_ustr;
+        case SbxDataType::SbxSALINT64:
+            return u"Long64"_ustr;
+        case SbxDataType::SbxSALUINT64:
+            return u"ULong64"_ustr;
+        case SbxDataType::SbxDECIMAL:
+            return u"Decimal"_ustr;
+        case SbxDataType::SbxCHAR:
+            return u"Char"_ustr;
+        case SbxDataType::SbxBYTE:
+            return u"Byte"_ustr;
+        case SbxDataType::SbxSINGLE:
+            return u"Single"_ustr;
+        case SbxDataType::SbxDOUBLE:
+            return u"Double"_ustr;
+        case SbxDataType::SbxCURRENCY:
+            return u"Currency"_ustr;
+        case SbxDataType::SbxDATE:
+            return u"Date"_ustr;
+        case SbxDataType::SbxBOOL:
+            return u"Boolean"_ustr;
+        case SbxDataType::SbxOBJECT:
+            return u"Object"_ustr;
+        case SbxDataType::SbxERROR:
+            return u"Error"_ustr;
+        case SbxDataType::SbxEMPTY:
+            return u"Empty"_ustr;
+        case SbxDataType::SbxNULL:
+            return u"Null"_ustr;
+        case SbxDataType::SbxVOID:
+            return u"Void"_ustr;
+        default:
+            return u"Variant"_ustr;
+    }
+}
+
 void ImplGetMembersOfUnoType(SymbolInfoList& rMembers, const IdeSymbolInfo& 
rNode,
                              std::unordered_set<OUString>& rVisitedTypes, 
sal_uInt16 nDepth = 0)
 {
@@ -220,6 +267,34 @@ void ImplGetMembersOfBasicModule(SymbolInfoList& rMembers, 
const IdeSymbolInfo&
                     pMember->sParentName = rNode.sOriginLibrary + u"." + 
rNode.sName;
                     pMember->sQualifiedName = pMember->sParentName + u"." + 
pMethod->GetName();
                 }
+
+                SbxInfo* pInfo = pMethod->GetInfo();
+                if (pInfo)
+                {
+                    pMember->sReturnTypeName = 
GetBasicTypeName(pMethod->GetType());
+                    sal_uInt32 j = 1;
+                    const SbxParamInfo* pParamInfo = pInfo->GetParam(j);
+                    while (pParamInfo)
+                    {
+                        IdeParamInfo aParam;
+                        aParam.sName = pParamInfo->aName;
+                        aParam.sTypeName = GetBasicTypeName(pParamInfo->eType);
+                        aParam.bIsByVal = !(pParamInfo->nFlags & 
SbxFlagBits::Reference);
+                        aParam.bIsOptional
+                            = static_cast<bool>(pParamInfo->nFlags & 
SbxFlagBits::Optional);
+
+                        bool bIsByRef
+                            = static_cast<bool>(pParamInfo->nFlags & 
SbxFlagBits::Reference);
+                        aParam.bIsByVal = !bIsByRef;
+                        aParam.bIsIn = true;
+                        aParam.bIsOut = bIsByRef;
+
+                        pMember->aParameters.push_back(aParam);
+
+                        pParamInfo = pInfo->GetParam(++j);
+                    }
+                }
+
                 rMembers.push_back(pMember);
             }
         }
diff --git a/basctl/source/basicide/objectbrowser.cxx 
b/basctl/source/basicide/objectbrowser.cxx
index 11e475356274..d5cbfc7ea505 100644
--- a/basctl/source/basicide/objectbrowser.cxx
+++ b/basctl/source/basicide/objectbrowser.cxx
@@ -19,6 +19,9 @@
 
 #include <basctl/sbxitem.hxx>
 #include <comphelper/processfactory.hxx>
+#include <o3tl/string_view.hxx>
+#include <rtl/string.hxx>
+#include <rtl/ustrbuf.hxx>
 #include <sal/log.hxx>
 #include <sfx2/bindings.hxx>
 #include <sfx2/dispatch.hxx>
@@ -26,7 +29,6 @@
 #include <svl/itemset.hxx>
 #include <sfx2/sfxsids.hrc>
 #include <sfx2/viewfrm.hxx>
-#include <vcl/svapp.hxx>
 #include <vcl/taskpanelist.hxx>
 #include <vcl/weld.hxx>
 
@@ -158,6 +160,287 @@ OUString GetGroupNameForKind(IdeSymbolKind eKind)
     }
 }
 
+OUString GetSymbolTypeDescription(basctl::IdeSymbolKind eKind)
+{
+    using basctl::IdeSymbolKind;
+    switch (eKind)
+    {
+        case IdeSymbolKind::ROOT_UNO_APIS:
+            return IDEResId(RID_STR_OB_TYPE_UNO_APIS_ROOT);
+        case IdeSymbolKind::ROOT_APPLICATION_LIBS:
+            return IDEResId(RID_STR_OB_TYPE_APP_LIBS);
+        case IdeSymbolKind::ROOT_DOCUMENT_LIBS:
+            return IDEResId(RID_STR_OB_TYPE_DOC_LIBS);
+        case IdeSymbolKind::LIBRARY:
+            return IDEResId(RID_STR_OB_TYPE_LIBRARY);
+        case IdeSymbolKind::MODULE:
+            return IDEResId(RID_STR_OB_TYPE_MODULE);
+        case IdeSymbolKind::CLASS_MODULE:
+            return IDEResId(RID_STR_OB_TYPE_CLASS_MODULE);
+        case IdeSymbolKind::UNO_NAMESPACE:
+            return IDEResId(RID_STR_OB_TYPE_NAMESPACE);
+        case IdeSymbolKind::UNO_INTERFACE:
+            return IDEResId(RID_STR_OB_TYPE_INTERFACE);
+        case IdeSymbolKind::UNO_SERVICE:
+            return IDEResId(RID_STR_OB_TYPE_SERVICE);
+        case IdeSymbolKind::UNO_STRUCT:
+            return IDEResId(RID_STR_OB_TYPE_STRUCT);
+        case IdeSymbolKind::UNO_ENUM:
+            return IDEResId(RID_STR_OB_TYPE_ENUM);
+        case IdeSymbolKind::UNO_CONSTANTS:
+            return IDEResId(RID_STR_OB_TYPE_CONSTANTS);
+        case IdeSymbolKind::UNO_EXCEPTION:
+            return IDEResId(RID_STR_OB_TYPE_EXCEPTION);
+        case IdeSymbolKind::UNO_TYPEDEF:
+            return IDEResId(RID_STR_OB_TYPE_TYPEDEF);
+        case IdeSymbolKind::UNO_METHOD:
+            return IDEResId(RID_STR_OB_TYPE_METHOD);
+        case IdeSymbolKind::UNO_PROPERTY:
+            return IDEResId(RID_STR_OB_TYPE_PROPERTY);
+        case IdeSymbolKind::UNO_FIELD:
+            return IDEResId(RID_STR_OB_TYPE_FIELD);
+        case IdeSymbolKind::SUB:
+            return IDEResId(RID_STR_OB_TYPE_SUB);
+        case IdeSymbolKind::FUNCTION:
+            return IDEResId(RID_STR_OB_TYPE_FUNCTION);
+        case IdeSymbolKind::ENUM_MEMBER:
+            return IDEResId(RID_STR_OB_TYPE_ENUM_MEMBER);
+        default:
+            return IDEResId(RID_STR_OB_TYPE_ITEM);
+    }
+}
+
+void FormatMethodSignature(rtl::OUStringBuffer& rBuffer, const 
basctl::IdeSymbolInfo& rSymbol)
+{
+    rBuffer.append(rSymbol.sName);
+    rBuffer.append(u"(");
+    if (!rSymbol.aParameters.empty())
+    {
+        for (size_t i = 0; i < rSymbol.aParameters.size(); ++i)
+        {
+            const auto& param = rSymbol.aParameters[i];
+            if (param.bIsOptional)
+            {
+                rBuffer.append(IDEResId(RID_STR_OB_OPTIONAL) + u" ");
+            }
+            if (!param.bIsByVal)
+            {
+                rBuffer.append(IDEResId(RID_STR_OB_BYREF) + u" ");
+            }
+            if (param.bIsOut && !param.bIsIn)
+            {
+                rBuffer.append(IDEResId(RID_STR_OB_OUT_PARAM) + u" ");
+            }
+            else if (param.bIsOut && param.bIsIn)
+            {
+                rBuffer.append(IDEResId(RID_STR_OB_INOUT_PARAM) + u" ");
+            }
+
+            rBuffer.append(param.sName + IDEResId(RID_STR_OB_AS) + 
param.sTypeName);
+            if (i < rSymbol.aParameters.size() - 1)
+            {
+                rBuffer.append(u", ");
+            }
+        }
+    }
+    rBuffer.append(u")");
+    if (!rSymbol.sReturnTypeName.isEmpty() && rSymbol.sReturnTypeName != 
"Void")
+    {
+        rBuffer.append(IDEResId(RID_STR_OB_AS) + rSymbol.sReturnTypeName);
+    }
+}
+
+void FormatPropertySignature(rtl::OUStringBuffer& rBuffer, const 
basctl::IdeSymbolInfo& rSymbol)
+{
+    rBuffer.append(rSymbol.sName);
+    OUString sType = !rSymbol.sTypeName.isEmpty() ? rSymbol.sTypeName : 
rSymbol.sReturnTypeName;
+    if (!sType.isEmpty())
+    {
+        rBuffer.append(IDEResId(RID_STR_OB_AS) + sType);
+    }
+}
+
+OUString AccessModifierToString(IdeAccessModifier eAccess)
+{
+    switch (eAccess)
+    {
+        case IdeAccessModifier::PUBLIC:
+            return IDEResId(RID_STR_OB_ACCESS_PUBLIC);
+        case IdeAccessModifier::PRIVATE:
+            return IDEResId(RID_STR_OB_ACCESS_PRIVATE);
+        default:
+            return OUString();
+    }
+}
+
+OUString FormatSymbolSignature(const IdeSymbolInfo& rSymbol)
+{
+    rtl::OUStringBuffer sDescription;
+
+    sDescription.append(u"━━━ " + GetSymbolTypeDescription(rSymbol.eKind) + u" 
━━━

📌 ");
+
+    switch (rSymbol.eKind)
+    {
+        case IdeSymbolKind::UNO_METHOD:
+        case IdeSymbolKind::SUB:
+        case IdeSymbolKind::FUNCTION:
+            FormatMethodSignature(sDescription, rSymbol);
+            break;
+        case IdeSymbolKind::UNO_PROPERTY:
+        case IdeSymbolKind::UNO_FIELD:
+            FormatPropertySignature(sDescription, rSymbol);
+            break;
+        default:
+            sDescription.append(rSymbol.sName);
+            break;
+    }
+    sDescription.append(u"

");
+
+    if (rSymbol.eAccessModifier != IdeAccessModifier::NOT_APPLICABLE)
+    {
+        sDescription.append(u"🔒 " + IDEResId(RID_STR_OB_ACCESS)
+                            + AccessModifierToString(rSymbol.eAccessModifier) 
+ u"
");
+    }
+
+    if (!rSymbol.sReturnTypeName.isEmpty() && rSymbol.sReturnTypeName != 
"Void")
+    {
+        sDescription.append(u"↩️ " + IDEResId(RID_STR_OB_RETURNS) + 
rSymbol.sReturnTypeName
+                            + u"
");
+    }
+
+    if (!rSymbol.sTypeName.isEmpty() && rSymbol.sReturnTypeName.isEmpty())
+    {
+        sDescription.append(u"📦 " + IDEResId(RID_STR_OB_TYPE) + 
rSymbol.sTypeName + u"
");
+    }
+
+    if (!rSymbol.aParameters.empty())
+    {
+        sDescription.append(u"
📋 " + IDEResId(RID_STR_OB_PARAMETERS));
+        for (const auto& param : rSymbol.aParameters)
+        {
+            sDescription.append(u"  • " + param.sName + u" : " + 
param.sTypeName);
+            rtl::OUStringBuffer sModifiers;
+            if (param.bIsOptional)
+            {
+                sModifiers.append(IDEResId(RID_STR_OB_OPTIONAL));
+            }
+            if (param.bIsOut && !param.bIsIn)
+            {
+                OUString sOut = IDEResId(RID_STR_OB_OUT_PARAM);
+                sModifiers.append(sModifiers.getLength() ? (u", "_ustr + sOut) 
: sOut);
+            }
+            else if (param.bIsOut && param.bIsIn)
+            {
+                OUString sInOut = IDEResId(RID_STR_OB_INOUT_PARAM);
+                sModifiers.append(sModifiers.getLength() ? (u", "_ustr + 
sInOut) : sInOut);
+            }
+            if (!param.bIsByVal)
+            {
+                OUString sByRef = IDEResId(RID_STR_OB_BYREF);
+                sModifiers.append(sModifiers.getLength() ? (u", "_ustr + 
sByRef) : sByRef);
+            }
+            if (sModifiers.getLength() > 0)
+            {
+                sDescription.append(u" [" + sModifiers.makeStringAndClear() + 
u"]");
+            }
+            if (param.osDefaultValueExpression.has_value())
+            {
+                sDescription.append(u" = " + 
param.osDefaultValueExpression.value());
+            }
+            sDescription.append(u"
");
+        }
+    }
+
+    sDescription.append(
+        u"
━━━ " + OUString::Concat(o3tl::trim(IDEResId(RID_STR_OB_LOCATION_HEADER))) + u" 
━━━
");
+
+    if (!rSymbol.sParentName.isEmpty())
+    {
+        sDescription.append(u"📂 " + IDEResId(RID_STR_OB_MEMBER_OF) + 
rSymbol.sParentName + u"
");
+    }
+    if (!rSymbol.sOriginLibrary.isEmpty())
+    {
+        sDescription.append(u"📚 " + IDEResId(RID_STR_OB_LIBRARY) + 
rSymbol.sOriginLibrary + u"
");
+    }
+    if (!rSymbol.sOriginLocation.isEmpty())
+    {
+        sDescription.append(u"📄 " + IDEResId(RID_STR_OB_DOCUMENT) + 
rSymbol.sOriginLocation
+                            + u"
");
+    }
+    if (rSymbol.nSourceLine > 0)
+    {
+        sDescription.append(u"📍 " + IDEResId(RID_STR_OB_LINE)
+                            + OUString::number(rSymbol.nSourceLine) + u"
");
+    }
+    if (!rSymbol.sQualifiedName.isEmpty() && rSymbol.sQualifiedName != 
rSymbol.sName)
+    {
+        sDescription.append(u"
━━━ "
+                            + 
OUString::Concat(o3tl::trim(IDEResId(RID_STR_OB_FULLNAME_HEADER)))
+                            + u" ━━━
");
+        sDescription.append(rSymbol.sQualifiedName + u"
");
+    }
+
+    return sDescription.makeStringAndClear();
+}
+
+OUString FormatContainerSignature(const IdeSymbolInfo& rSymbol,
+                                  IdeDataProviderInterface* pDataProvider)
+{
+    if (!pDataProvider)
+    {
+        return OUString();
+    }
+
+    rtl::OUStringBuffer sInfo;
+    sInfo.append(u"━━━ " + GetSymbolTypeDescription(rSymbol.eKind) + u" ━━━

📌 " + rSymbol.sName
+                 + u"

");
+
+    if (ShouldShowMembers(rSymbol))
+    {
+        GroupedSymbolInfoList aMembers = pDataProvider->GetMembers(rSymbol);
+        size_t nTotalMembers = 0;
+        for (const auto& pair : aMembers)
+            nTotalMembers += pair.second.size();
+
+        sInfo.append(IDEResId(RID_STR_OB_CONTENTS_HEADER) + 
IDEResId(RID_STR_OB_TOTAL_MEMBERS)
+                     + OUString::number(static_cast<sal_Int64>(nTotalMembers)) 
+ u"

");
+
+        for (const auto& pair : aMembers)
+        {
+            sInfo.append(u"  • " + GetGroupNameForKind(pair.first) + u": "
+                         + 
OUString::number(static_cast<sal_Int64>(pair.second.size())) + u"
");
+        }
+    }
+    else if (IsExpandable(rSymbol))
+    {
+        auto aChildren = pDataProvider->GetChildNodes(rSymbol);
+        sal_Int64 nChildren = static_cast<sal_Int64>(aChildren.size());
+        sInfo.append(
+            IDEResId(RID_STR_OB_CONTENTS_HEADER)
+            + IDEResId(RID_STR_OB_CONTAINS_ITEMS).replaceFirst(u"%1", 
OUString::number(nChildren)));
+    }
+
+    if (!rSymbol.sQualifiedName.isEmpty() && rSymbol.sQualifiedName != 
rSymbol.sName)
+    {
+        sInfo.append(IDEResId(RID_STR_OB_FULLNAME_HEADER) + 
rSymbol.sQualifiedName + u"
");
+    }
+
+    if (!rSymbol.sOriginLibrary.isEmpty() || 
!rSymbol.sOriginLocation.isEmpty())
+    {
+        sInfo.append(IDEResId(RID_STR_OB_LOCATION_HEADER));
+        if (!rSymbol.sOriginLibrary.isEmpty())
+        {
+            sInfo.append(IDEResId(RID_STR_OB_LIBRARY) + rSymbol.sOriginLibrary 
+ u"
");
+        }
+        if (!rSymbol.sOriginLocation.isEmpty())
+        {
+            sInfo.append(IDEResId(RID_STR_OB_DOCUMENT) + 
rSymbol.sOriginLocation + u"
");
+        }
+    }
+
+    return sInfo.makeStringAndClear();
+}
+
 // 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,
@@ -383,6 +666,14 @@ void ObjectBrowser::Initialize()
             LINK(this, ObjectBrowser, OnRightTreeDoubleClick));
     }
 
+    if (m_xDetailPane)
+    {
+        vcl::Font aFont = m_xDetailPane->get_font();
+        aFont.SetFamilyName(u"Monospace"_ustr);
+        aFont.SetPitch(PITCH_FIXED);
+        m_xDetailPane->set_font(aFont);
+    }
+
     if (m_xBackButton)
         m_xBackButton->set_sensitive(false);
     if (m_xForwardButton)
@@ -486,12 +777,24 @@ void ObjectBrowser::Show(bool bVisible)
         if (!m_pDataProvider->IsInitialized())
         {
             ShowLoadingState();
+            IdeTimer aTotalInitTimer("ObjectBrowser::FullInitialization");
             weld::WaitObject aWait(GetFrameWeld());
             m_pDataProvider->Initialize();
-            m_bDataMayBeStale = true; // Data is fresh, but force a refresh
-        }
+            m_bDataMayBeStale = true;
+
+            RefreshUI();
+            m_bDataMayBeStale = false;
 
-        if (m_bDataMayBeStale)
+            if (!m_bFirstLoadComplete)
+            {
+                m_bFirstLoadComplete = true;
+                double fElapsedSeconds = aTotalInitTimer.getElapsedTimeMs() / 
1000.0;
+                OUString sStatus = IDEResId(RID_STR_OB_READY_LOADED)
+                                       .replaceFirst(u"%1", 
OUString::number(fElapsedSeconds));
+                m_xStatusLabel->set_label(sStatus);
+            }
+        }
+        else if (m_bDataMayBeStale)
         {
             RefreshUI();
             m_bDataMayBeStale = false;
@@ -527,10 +830,7 @@ void ObjectBrowser::RefreshUI(bool /*bForceKeepUno*/)
         m_xRightPaneHeaderLabel->set_label(IDEResId(RID_STR_OB_GROUP_MEMBERS));
     }
 
-    if (m_xDetailPane)
-    {
-        m_xDetailPane->set_text(u""_ustr);
-    }
+    UpdateDetailsPane(nullptr, false);
 
     // Get the filtered list of nodes based on the current scope
     SymbolInfoList aTopLevelNodes = m_pDataProvider->GetTopLevelNodes();
@@ -546,8 +846,8 @@ void ObjectBrowser::RefreshUI(bool /*bForceKeepUno*/)
     m_xLeftTreeView->thaw();
     m_xRightMembersView->thaw();
 
-    if (m_xStatusLabel)
-        m_xStatusLabel->set_label(u"Ready"_ustr);
+    if (!m_bFirstLoadComplete && m_xStatusLabel)
+        m_xStatusLabel->set_label(IDEResId(RID_STR_OB_READY));
 }
 
 void ObjectBrowser::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
@@ -651,6 +951,86 @@ void ObjectBrowser::ScheduleRefresh()
     }
 }
 
+void ObjectBrowser::UpdateDetailsPane(const IdeSymbolInfo* pSymbol, bool 
bIsContainer)
+{
+    if (!m_xDetailPane)
+    {
+        return;
+    }
+
+    if (pSymbol && pSymbol->eKind != IdeSymbolKind::PLACEHOLDER && 
pSymbol->bSelectable)
+    {
+        if (bIsContainer)
+        {
+            m_xDetailPane->set_text(FormatContainerSignature(*pSymbol, 
m_pDataProvider.get()));
+        }
+        else
+        {
+            m_xDetailPane->set_text(FormatSymbolSignature(*pSymbol));
+        }
+    }
+    else
+    {
+        m_xDetailPane->set_text(u""_ustr);
+    }
+}
+
+void ObjectBrowser::UpdateStatusBar(const IdeSymbolInfo* pLeftSymbol,
+                                    const IdeSymbolInfo* pRightSymbol)
+{
+    if (!m_xStatusLabel)
+    {
+        return;
+    }
+    OUString sStatusText;
+
+    if (pRightSymbol)
+    {
+        if (pRightSymbol->eKind == IdeSymbolKind::PLACEHOLDER)
+        {
+            sStatusText = pRightSymbol->sName;
+        }
+        else
+        {
+            sStatusText = GetSymbolTypeDescription(pRightSymbol->eKind);
+        }
+    }
+    else if (pLeftSymbol)
+    {
+        if (ShouldShowMembers(*pLeftSymbol))
+        {
+            GroupedSymbolInfoList aMembers = 
m_pDataProvider->GetMembers(*pLeftSymbol);
+            size_t nTotalMembers = 0;
+            for (const auto& pair : aMembers)
+                nTotalMembers += pair.second.size();
+            sStatusText
+                = IDEResId(RID_STR_OB_MEMBERS_COUNT)
+                      .replaceFirst(u"%1", 
OUString::number(static_cast<sal_Int64>(nTotalMembers)));
+        }
+        else if (IsExpandable(*pLeftSymbol))
+        {
+            auto aChildren = m_pDataProvider->GetChildNodes(*pLeftSymbol);
+            sStatusText
+                = IDEResId(RID_STR_OB_ITEMS_COUNT)
+                      .replaceFirst(u"%1",
+                                    
OUString::number(static_cast<sal_Int64>(aChildren.size())));
+        }
+        else
+        {
+            sStatusText = GetSymbolTypeDescription(pLeftSymbol->eKind);
+        }
+    }
+    else if (m_bFirstLoadComplete)
+    {
+        sStatusText = m_xStatusLabel->get_label();
+    }
+    else
+    {
+        sStatusText = IDEResId(RID_STR_OB_READY);
+    }
+    m_xStatusLabel->set_label(sStatusText);
+}
+
 void ObjectBrowser::NavigateToMacroSource(const IdeSymbolInfo& rSymbol)
 {
     SAL_INFO("basctl", "NavigateToMacroSource: Entry - Library='"
@@ -765,6 +1145,8 @@ IMPL_LINK(ObjectBrowser, OnLeftTreeSelect, 
weld::TreeView&, rTree, void)
     auto xSelectedIter = rTree.make_iterator();
     if (!rTree.get_selected(xSelectedIter.get()))
     {
+        UpdateStatusBar(nullptr, nullptr);
+        UpdateDetailsPane(nullptr, false);
         return;
     }
 
@@ -774,6 +1156,10 @@ IMPL_LINK(ObjectBrowser, OnLeftTreeSelect, 
weld::TreeView&, rTree, void)
         return;
     }
 
+    // A selection in the left pane is always a container
+    UpdateDetailsPane(pSymbol.get(), true);
+    UpdateStatusBar(pSymbol.get(), nullptr);
+
     ClearRightTreeView();
 
     if (ShouldShowMembers(*pSymbol))
@@ -789,17 +1175,34 @@ IMPL_LINK(ObjectBrowser, OnRightTreeSelect, 
weld::TreeView&, rTree, void)
         return;
     }
 
-    auto xSelectedIter = rTree.make_iterator();
-    if (!rTree.get_selected(xSelectedIter.get()))
+    auto xLeftIter = m_xLeftTreeView->make_iterator();
+    m_xLeftTreeView->get_selected(xLeftIter.get());
+    auto pLeftSymbol = GetSymbolForIter(*xLeftIter, *m_xLeftTreeView, 
m_aLeftTreeSymbolIndex);
+
+    auto xRightIter = rTree.make_iterator();
+    if (!rTree.get_selected(xRightIter.get()))
     {
+        // Revert to showing container info if right-pane selection is cleared
+        UpdateStatusBar(pLeftSymbol.get(), nullptr);
+        UpdateDetailsPane(pLeftSymbol.get(), true);
         return;
     }
 
-    auto pSymbol = GetSymbolForIter(*xSelectedIter, rTree, 
m_aRightTreeSymbolIndex);
-    if (!pSymbol || pSymbol->eKind == IdeSymbolKind::PLACEHOLDER)
+    auto pRightSymbol = GetSymbolForIter(*xRightIter, rTree, 
m_aRightTreeSymbolIndex);
+    if (!pRightSymbol)
     {
         return;
     }
+
+    // We create a temporary copy to correctly populate the parent name for 
display
+    IdeSymbolInfo tempSymbol = *pRightSymbol;
+    if (pLeftSymbol)
+    {
+        tempSymbol.sParentName = pLeftSymbol->sName;
+    }
+
+    UpdateDetailsPane(&tempSymbol, false);
+    UpdateStatusBar(pLeftSymbol.get(), pRightSymbol.get());
 }
 
 IMPL_LINK(ObjectBrowser, OnRightNodeExpand, const weld::TreeIter&, 
rParentIter, bool)
@@ -809,6 +1212,11 @@ IMPL_LINK(ObjectBrowser, OnRightNodeExpand, const 
weld::TreeIter&, rParentIter,
         return false;
     }
 
+    if (m_xRightMembersView->iter_has_child(rParentIter))
+    {
+        return true;
+    }
+
     auto pParentSymbol
         = GetSymbolForIter(rParentIter, *m_xRightMembersView, 
m_aRightTreeSymbolIndex);
     if (!pParentSymbol)
@@ -975,6 +1383,11 @@ IMPL_LINK(ObjectBrowser, OnNodeExpand, const 
weld::TreeIter&, rParentIter, bool)
         return false;
     }
 
+    if (m_xLeftTreeView->iter_has_child(rParentIter))
+    {
+        return true;
+    }
+
     const auto aAllChildren = m_pDataProvider->GetChildNodes(*pParentSymbol);
     if (aAllChildren.empty())
     {
diff --git a/basctl/source/inc/objectbrowser.hxx 
b/basctl/source/inc/objectbrowser.hxx
index c073f75be893..530c16f9c1e6 100644
--- a/basctl/source/inc/objectbrowser.hxx
+++ b/basctl/source/inc/objectbrowser.hxx
@@ -80,6 +80,8 @@ private:
     void ClearLeftTreeView();
     void ClearRightTreeView();
     void PopulateMembersPane(const IdeSymbolInfo& rSymbol);
+    void UpdateDetailsPane(const IdeSymbolInfo* pSymbol, bool bIsContainer);
+    void UpdateStatusBar(const IdeSymbolInfo* pLeftSymbol, const 
IdeSymbolInfo* pRightSymbol);
 
     // Core References
     Shell* m_pShell;
@@ -93,6 +95,7 @@ private:
     bool m_bDataMayBeStale = true;
     OUString m_sLastActiveDocumentIdentifier;
     bool m_bPerformingAction = false; // Flag for Right Pane Double Click
+    bool m_bFirstLoadComplete = false; // Tracks initial load message
 
     // Helper Method for Double-Click Actions
     void NavigateToMacroSource(const IdeSymbolInfo& rSymbol);

Reply via email to