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);
