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
