tdf#165785: basctl: Implement Right-Pane Interactivity
This patch brings the Object Browser's right-hand members pane to life. The right pane now supports on-demand expansion for nested UNO types. Double-clicking items now performs a context-aware action: opening the relevant Doxygen API page for UNO types or navigating to the source code for BASIC macros. (GSoC 2025 - Object Browser: Right-Pane UI Enhancement) Change-Id: Ibcc2a0479746f3724372c9bd89853626506d42f3 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192050 Tested-by: Jenkins Reviewed-by: Jonathan Clark <jonathan@libreoffice.org>
This commit is contained in:
committed by
Jonathan Clark
parent
34612217f8
commit
2b5803c3b4
@@ -103,6 +103,8 @@
|
||||
#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_DOCS_ERROR_TITLE NC_("RID_STR_OB_DOCS_ERROR_TITLE", "Documentation Error")
|
||||
#define RID_STR_OB_BROWSER_LAUNCH_FAILED NC_("RID_STR_OB_BROWSER_LAUNCH_FAILED", "Could not open the documentation in an external web 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")
|
||||
@@ -112,6 +114,7 @@
|
||||
#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_NO_DOCUMENTATION NC_("RID_STR_OB_NO_DOCUMENTATION", "No documentation link is available for the selected item.")
|
||||
#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")
|
||||
|
||||
@@ -93,6 +93,12 @@ void ImplGetMembersOfUnoType(SymbolInfoList& rMembers, const IdeSymbolInfo& rNod
|
||||
}
|
||||
auto pNode = std::make_shared<IdeSymbolInfo>(
|
||||
xMethod->getName(), IdeSymbolKind::UNO_METHOD, rNode.sIdentifier);
|
||||
|
||||
if (!rNode.sQualifiedName.isEmpty())
|
||||
{
|
||||
pNode->sQualifiedName = rNode.sQualifiedName + u"." + xMethod->getName();
|
||||
pNode->sParentName = rNode.sQualifiedName;
|
||||
}
|
||||
pNode->sReturnTypeName = helper_getTypeName(xMethod->getReturnType());
|
||||
for (const auto& rParam : xMethod->getParameterInfos())
|
||||
{
|
||||
@@ -129,6 +135,12 @@ void ImplGetMembersOfUnoType(SymbolInfoList& rMembers, const IdeSymbolInfo& rNod
|
||||
|
||||
auto pMember
|
||||
= std::make_shared<IdeSymbolInfo>(xField->getName(), eFieldKind, rNode.sIdentifier);
|
||||
|
||||
if (!rNode.sQualifiedName.isEmpty())
|
||||
{
|
||||
pMember->sQualifiedName = rNode.sQualifiedName + u"." + xField->getName();
|
||||
pMember->sParentName = rNode.sQualifiedName;
|
||||
}
|
||||
pMember->sTypeName = helper_getTypeName(xField->getType());
|
||||
|
||||
Reference<XIdlClass> xFieldType = xField->getType();
|
||||
@@ -198,6 +210,16 @@ void ImplGetMembersOfBasicModule(SymbolInfoList& rMembers, const IdeSymbolInfo&
|
||||
: IdeSymbolKind::FUNCTION;
|
||||
auto pMember
|
||||
= std::make_shared<IdeSymbolInfo>(pMethod->GetName(), eKind, rNode.sIdentifier);
|
||||
|
||||
pMember->sOriginLibrary = rNode.sOriginLibrary;
|
||||
pMember->sOriginModule = rNode.sName; // The parent node name is the module name
|
||||
pMember->sOriginLocation = rNode.sOriginLocation;
|
||||
|
||||
if (!rNode.sOriginLibrary.isEmpty() && !rNode.sName.isEmpty())
|
||||
{
|
||||
pMember->sParentName = rNode.sOriginLibrary + u"." + rNode.sName;
|
||||
pMember->sQualifiedName = pMember->sParentName + u"." + pMethod->GetName();
|
||||
}
|
||||
rMembers.push_back(pMember);
|
||||
}
|
||||
}
|
||||
@@ -367,6 +389,7 @@ void IdeDataProvider::Reset()
|
||||
SAL_INFO("basctl", "IdeDataProvider: Resetting state.");
|
||||
m_bInitialized = false;
|
||||
m_aAllTopLevelNodes.clear();
|
||||
m_aMembersCache.clear();
|
||||
|
||||
m_pUnoHierarchy = std::make_unique<UnoApiHierarchy>();
|
||||
}
|
||||
@@ -521,6 +544,12 @@ SymbolInfoList IdeDataProvider::GetTopLevelNodes()
|
||||
|
||||
GroupedSymbolInfoList IdeDataProvider::GetMembers(const IdeSymbolInfo& rNode)
|
||||
{
|
||||
auto it = m_aMembersCache.find(rNode.sIdentifier);
|
||||
if (it != m_aMembersCache.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
GroupedSymbolInfoList aGroupedMembers;
|
||||
SymbolInfoList aFlatMembers;
|
||||
|
||||
@@ -548,6 +577,9 @@ GroupedSymbolInfoList IdeDataProvider::GetMembers(const IdeSymbolInfo& rNode)
|
||||
{
|
||||
aGroupedMembers[pMemberInfo->eKind].push_back(pMemberInfo);
|
||||
}
|
||||
|
||||
m_aMembersCache[rNode.sIdentifier] = aGroupedMembers;
|
||||
|
||||
return aGroupedMembers;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,15 +17,24 @@
|
||||
#include <iderid.hxx>
|
||||
#include <strings.hrc>
|
||||
|
||||
#include <basctl/sbxitem.hxx>
|
||||
#include <comphelper/processfactory.hxx>
|
||||
#include <sal/log.hxx>
|
||||
#include <sfx2/bindings.hxx>
|
||||
#include <sfx2/dispatch.hxx>
|
||||
#include <sfx2/event.hxx>
|
||||
#include <svl/itemset.hxx>
|
||||
#include <sfx2/sfxsids.hrc>
|
||||
#include <sfx2/viewfrm.hxx>
|
||||
#include <vcl/svapp.hxx>
|
||||
#include <vcl/taskpanelist.hxx>
|
||||
#include <vcl/weld.hxx>
|
||||
|
||||
#include <com/sun/star/system/XSystemShellExecute.hpp>
|
||||
#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
|
||||
#include <com/sun/star/uno/XComponentContext.hpp>
|
||||
#include <com/sun/star/lang/XMultiComponentFactory.hpp>
|
||||
|
||||
namespace basctl
|
||||
{
|
||||
namespace
|
||||
@@ -193,6 +202,124 @@ void AddEntry(weld::TreeView& rTargetTree, std::vector<std::shared_ptr<IdeSymbol
|
||||
rTargetTree.set_image(*pRetIter, sIconName, -1);
|
||||
}
|
||||
|
||||
OUString BuildDoxygenUrl(const IdeSymbolInfo& rSymbol)
|
||||
{
|
||||
if (rSymbol.sQualifiedName.isEmpty())
|
||||
return OUString();
|
||||
|
||||
if (rSymbol.sQualifiedName.startsWith("ooo.vba."))
|
||||
{
|
||||
SAL_INFO("basctl", "BuildDoxygenUrl: Skipping VBA type '" << rSymbol.sQualifiedName << "'");
|
||||
return OUString();
|
||||
}
|
||||
// Doxygen mangles names by replacing '.' with "_1_1"
|
||||
OUString sMangledName;
|
||||
OUString sTypePrefix;
|
||||
OUString sAnchor;
|
||||
|
||||
switch (rSymbol.eKind)
|
||||
{
|
||||
// Case 1: Types that have their OWN dedicated .html page
|
||||
case IdeSymbolKind::UNO_STRUCT:
|
||||
sTypePrefix = u"struct"_ustr;
|
||||
sMangledName = rSymbol.sQualifiedName.replaceAll(".", "_1_1");
|
||||
break;
|
||||
case IdeSymbolKind::UNO_INTERFACE:
|
||||
sTypePrefix = u"interface"_ustr;
|
||||
sMangledName = rSymbol.sQualifiedName.replaceAll(".", "_1_1");
|
||||
break;
|
||||
case IdeSymbolKind::UNO_SERVICE:
|
||||
sTypePrefix = u"service"_ustr;
|
||||
sMangledName = rSymbol.sQualifiedName.replaceAll(".", "_1_1");
|
||||
break;
|
||||
case IdeSymbolKind::UNO_EXCEPTION:
|
||||
sTypePrefix = u"exception"_ustr;
|
||||
sMangledName = rSymbol.sQualifiedName.replaceAll(".", "_1_1");
|
||||
break;
|
||||
case IdeSymbolKind::UNO_NAMESPACE:
|
||||
sTypePrefix = u"namespace"_ustr;
|
||||
sMangledName = rSymbol.sQualifiedName.replaceAll(".", "_1_1");
|
||||
break;
|
||||
|
||||
// Case 2: Types that are documented as ANCHORS on their parent namespace page
|
||||
case IdeSymbolKind::UNO_ENUM:
|
||||
case IdeSymbolKind::UNO_CONSTANTS:
|
||||
case IdeSymbolKind::UNO_TYPEDEF:
|
||||
{
|
||||
if (rSymbol.sParentName.isEmpty())
|
||||
{
|
||||
return OUString();
|
||||
}
|
||||
sTypePrefix = u"namespace"_ustr;
|
||||
sMangledName = rSymbol.sParentName.replaceAll(".", "_1_1");
|
||||
sAnchor = u"#"_ustr + rSymbol.sName;
|
||||
break;
|
||||
}
|
||||
// This symbol kind does not have a Doxygen page
|
||||
default:
|
||||
return OUString();
|
||||
}
|
||||
|
||||
if (sMangledName.isEmpty() || sTypePrefix.isEmpty())
|
||||
{
|
||||
return OUString();
|
||||
}
|
||||
|
||||
return u"https://api.libreoffice.org/docs/idl/ref/"_ustr + sTypePrefix + sMangledName
|
||||
+ u".html"_ustr + sAnchor;
|
||||
}
|
||||
|
||||
void ShowDocsError(vcl::Window* pParent, const OUString& sPrimaryText,
|
||||
const OUString& sSecondaryText = OUString())
|
||||
{
|
||||
if (!pParent)
|
||||
return;
|
||||
|
||||
std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
|
||||
pParent->GetFrameWeld(), VclMessageType::Warning, VclButtonsType::Ok, sPrimaryText));
|
||||
xBox->set_title(IDEResId(RID_STR_OB_DOCS_ERROR_TITLE));
|
||||
|
||||
if (!sSecondaryText.isEmpty())
|
||||
{
|
||||
xBox->set_secondary_text(sSecondaryText);
|
||||
}
|
||||
xBox->run();
|
||||
}
|
||||
|
||||
void OpenDoxygenDocumentation(vcl::Window* pParent, const IdeSymbolInfo& rSymbol)
|
||||
{
|
||||
OUString sUrl = BuildDoxygenUrl(rSymbol);
|
||||
SAL_INFO("basctl", "OpenDoxygenDocumentation: Built URL='" << sUrl << "' for symbol '"
|
||||
<< rSymbol.sName << "'");
|
||||
if (sUrl.isEmpty())
|
||||
{
|
||||
ShowDocsError(pParent, IDEResId(RID_STR_OB_NO_DOCUMENTATION));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
css::uno::Reference<css::uno::XComponentContext> xContext
|
||||
= comphelper::getProcessComponentContext();
|
||||
css::uno::Reference<css::lang::XMultiComponentFactory> xServiceManager
|
||||
= xContext->getServiceManager();
|
||||
|
||||
css::uno::Reference<css::system::XSystemShellExecute> xSystemShell(
|
||||
xServiceManager->createInstanceWithContext(
|
||||
u"com.sun.star.system.SystemShellExecute"_ustr, xContext),
|
||||
css::uno::UNO_QUERY_THROW);
|
||||
|
||||
xSystemShell->execute(sUrl, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY);
|
||||
}
|
||||
catch (const css::uno::Exception& e)
|
||||
{
|
||||
SAL_WARN("basctl", "Failed to open Doxygen documentation: " << e.Message);
|
||||
|
||||
OUString sSecondaryMsg = u"URL: "_ustr + sUrl + u"\nError: "_ustr + e.Message;
|
||||
ShowDocsError(pParent, IDEResId(RID_STR_OB_BROWSER_LAUNCH_FAILED), sSecondaryMsg);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
ObjectBrowser::ObjectBrowser(Shell& rShell, vcl::Window* pParent)
|
||||
@@ -251,6 +378,9 @@ void ObjectBrowser::Initialize()
|
||||
{
|
||||
m_xRightMembersView->connect_selection_changed(
|
||||
LINK(this, ObjectBrowser, OnRightTreeSelect));
|
||||
m_xRightMembersView->connect_expanding(LINK(this, ObjectBrowser, OnRightNodeExpand));
|
||||
m_xRightMembersView->connect_row_activated(
|
||||
LINK(this, ObjectBrowser, OnRightTreeDoubleClick));
|
||||
}
|
||||
|
||||
if (m_xBackButton)
|
||||
@@ -372,6 +502,13 @@ void ObjectBrowser::Show(bool bVisible)
|
||||
void ObjectBrowser::RefreshUI(bool /*bForceKeepUno*/)
|
||||
{
|
||||
IdeTimer aTimer(u"ObjectBrowser::RefreshUI"_ustr);
|
||||
|
||||
if (m_bPerformingAction)
|
||||
{
|
||||
SAL_INFO("basctl", "ObjectBrowser::RefreshUI: Blocked because an action is in progress.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_pDataProvider || !m_pDataProvider->IsInitialized())
|
||||
{
|
||||
ShowLoadingState();
|
||||
@@ -432,9 +569,12 @@ void ObjectBrowser::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter out activations of the BASIC IDE
|
||||
if (pObjShell->GetFactory().GetDocumentServiceName().endsWith(u"BasicIDE"))
|
||||
OUString sServiceName = pObjShell->GetFactory().GetDocumentServiceName();
|
||||
|
||||
// Filter out BasicIDE and HTML/Web documents (Doxygen docs)
|
||||
if (sServiceName.endsWith(u"BasicIDE") || sServiceName.endsWith(u"WebDocument"))
|
||||
{
|
||||
SAL_INFO("basctl", "ObjectBrowser::Notify: Ignoring BasicIDE & WebDocument activation");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -496,10 +636,63 @@ void ObjectBrowser::ClearRightTreeView()
|
||||
|
||||
void ObjectBrowser::ScheduleRefresh()
|
||||
{
|
||||
if (m_bPerformingAction)
|
||||
{
|
||||
SAL_INFO("basctl",
|
||||
"ScheduleRefresh: Blocked refresh request because an action is in progress.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_bDataMayBeStale = true;
|
||||
|
||||
if (IsVisible())
|
||||
{
|
||||
RefreshUI();
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectBrowser::NavigateToMacroSource(const IdeSymbolInfo& rSymbol)
|
||||
{
|
||||
SAL_INFO("basctl", "NavigateToMacroSource: Entry - Library='"
|
||||
<< rSymbol.sOriginLibrary << "', Module='" << rSymbol.sOriginModule
|
||||
<< "', Method='" << rSymbol.sName << "'");
|
||||
|
||||
if (!m_pShell || rSymbol.sOriginLibrary.isEmpty() || rSymbol.sOriginModule.isEmpty())
|
||||
{
|
||||
SAL_WARN("basctl", "NavigateToMacroSource: Invalid parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
ScriptDocument aDoc
|
||||
= rSymbol.sOriginLocation.isEmpty()
|
||||
? ScriptDocument::getApplicationScriptDocument()
|
||||
: ScriptDocument::getDocumentWithURLOrCaption(rSymbol.sOriginLocation);
|
||||
|
||||
if (aDoc.isAlive())
|
||||
{
|
||||
SAL_INFO("basctl",
|
||||
"NavigateToMacroSource: Document is alive, dispatching SID_BASICIDE_SHOWSBX.");
|
||||
|
||||
// Pass the method name so it navigates to the exact line
|
||||
SbxItem aSbxItem(SID_BASICIDE_ARG_SBX, aDoc, rSymbol.sOriginLibrary, rSymbol.sOriginModule,
|
||||
rSymbol.sName, basctl::SBX_TYPE_METHOD);
|
||||
|
||||
SfxViewFrame& rViewFrame = m_pShell->GetViewFrame();
|
||||
if (SfxDispatcher* pDispatcher = rViewFrame.GetDispatcher())
|
||||
{
|
||||
std::ignore = pDispatcher->ExecuteList(SID_BASICIDE_SHOWSBX, SfxCallMode::SYNCHRON,
|
||||
{ &aSbxItem });
|
||||
SAL_INFO("basctl", "NavigateToMacroSource: Dispatched successfully.");
|
||||
}
|
||||
else
|
||||
{
|
||||
SAL_WARN("basctl", "NavigateToMacroSource: Could not get dispatcher.");
|
||||
}
|
||||
}
|
||||
else
|
||||
m_bDataMayBeStale = true;
|
||||
{
|
||||
SAL_WARN("basctl", "NavigateToMacroSource: ScriptDocument is not alive.");
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectBrowser::PopulateMembersPane(const IdeSymbolInfo& rSymbol)
|
||||
@@ -535,8 +728,10 @@ void ObjectBrowser::PopulateMembersPane(const IdeSymbolInfo& rSymbol)
|
||||
|
||||
for (const auto& pMemberInfo : rPair.second)
|
||||
{
|
||||
bool bHasNestedMembers = !pMemberInfo->mapMembers.empty();
|
||||
|
||||
AddEntry(*m_xRightMembersView, m_aRightTreeSymbolStore, m_aRightTreeSymbolIndex,
|
||||
xGroupIter.get(), pMemberInfo, false);
|
||||
xGroupIter.get(), pMemberInfo, bHasNestedMembers);
|
||||
}
|
||||
aGroupItersToExpand.push_back(std::move(xGroupIter));
|
||||
}
|
||||
@@ -587,7 +782,185 @@ IMPL_LINK(ObjectBrowser, OnLeftTreeSelect, weld::TreeView&, rTree, void)
|
||||
}
|
||||
}
|
||||
|
||||
IMPL_STATIC_LINK(ObjectBrowser, OnRightTreeSelect, weld::TreeView&, /*rTree*/, void) { /* STUB */}
|
||||
IMPL_LINK(ObjectBrowser, OnRightTreeSelect, weld::TreeView&, rTree, void)
|
||||
{
|
||||
if (m_bDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto xSelectedIter = rTree.make_iterator();
|
||||
if (!rTree.get_selected(xSelectedIter.get()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto pSymbol = GetSymbolForIter(*xSelectedIter, rTree, m_aRightTreeSymbolIndex);
|
||||
if (!pSymbol || pSymbol->eKind == IdeSymbolKind::PLACEHOLDER)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
IMPL_LINK(ObjectBrowser, OnRightNodeExpand, const weld::TreeIter&, rParentIter, bool)
|
||||
{
|
||||
if (m_bDisposed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pParentSymbol
|
||||
= GetSymbolForIter(rParentIter, *m_xRightMembersView, m_aRightTreeSymbolIndex);
|
||||
if (!pParentSymbol)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& pair : pParentSymbol->mapMembers)
|
||||
{
|
||||
const auto& pChildSymbol = pair.second;
|
||||
if (pChildSymbol)
|
||||
{
|
||||
// Check if the child is also expandable
|
||||
bool bChildHasChildren = !pChildSymbol->mapMembers.empty();
|
||||
AddEntry(*m_xRightMembersView, m_aRightTreeSymbolStore, m_aRightTreeSymbolIndex,
|
||||
&rParentIter, pChildSymbol, bChildHasChildren);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPL_LINK(ObjectBrowser, OnRightTreeDoubleClick, weld::TreeView&, rTree, bool)
|
||||
{
|
||||
SAL_INFO("basctl", "OnRightTreeDoubleClick: Handler entered.");
|
||||
|
||||
if (m_bDisposed)
|
||||
{
|
||||
SAL_WARN("basctl", "OnRightTreeDoubleClick: Browser is disposed, aborting.");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto xSelectedIter = rTree.make_iterator();
|
||||
if (!rTree.get_selected(xSelectedIter.get()))
|
||||
{
|
||||
SAL_INFO("basctl", "OnRightTreeDoubleClick: No item selected.");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pSymbol = GetSymbolForIter(*xSelectedIter, rTree, m_aRightTreeSymbolIndex);
|
||||
if (!pSymbol || pSymbol->eKind == IdeSymbolKind::PLACEHOLDER)
|
||||
{
|
||||
SAL_INFO("basctl",
|
||||
"OnRightTreeDoubleClick: Selected item is a placeholder or has no symbol.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bPerformingAction = true;
|
||||
SAL_INFO("basctl", "OnRightTreeDoubleClick: Processing symbol '"
|
||||
<< pSymbol->sName << "' of kind " << static_cast<int>(pSymbol->eKind));
|
||||
|
||||
// Symbol is a BASIC Macro
|
||||
if (pSymbol->eKind == IdeSymbolKind::SUB || pSymbol->eKind == IdeSymbolKind::FUNCTION)
|
||||
{
|
||||
SAL_INFO("basctl", "OnRightTreeDoubleClick: Action is NavigateToMacroSource.");
|
||||
NavigateToMacroSource(*pSymbol);
|
||||
m_bPerformingAction = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The symbol itself is documentable
|
||||
OUString sSymbolUrl = BuildDoxygenUrl(*pSymbol);
|
||||
if (!sSymbolUrl.isEmpty())
|
||||
{
|
||||
SAL_INFO("basctl",
|
||||
"OnRightTreeDoubleClick: Symbol is a documentable container. Opening its docs.");
|
||||
OpenDoxygenDocumentation(this, *pSymbol);
|
||||
m_bPerformingAction = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find documentable parent in RIGHT tree first
|
||||
auto xParentIter = rTree.make_iterator(xSelectedIter.get());
|
||||
while (rTree.iter_parent(*xParentIter))
|
||||
{
|
||||
auto pParentSymbol = GetSymbolForIter(*xParentIter, rTree, m_aRightTreeSymbolIndex);
|
||||
if (pParentSymbol && pParentSymbol->eKind != IdeSymbolKind::PLACEHOLDER)
|
||||
{
|
||||
// If this parent has a qualified name it's documentable
|
||||
if (!pParentSymbol->sQualifiedName.isEmpty())
|
||||
{
|
||||
SAL_INFO("basctl",
|
||||
"OnRightTreeDoubleClick: Action is OpenDoxygenDocumentation for parent '"
|
||||
<< pParentSymbol->sName << "' (from right pane).");
|
||||
OpenDoxygenDocumentation(this, *pParentSymbol);
|
||||
m_bPerformingAction = false;
|
||||
return true;
|
||||
}
|
||||
// Otherwise, keep walking up the tree
|
||||
}
|
||||
}
|
||||
|
||||
// Find documentable parent in LEFT tree
|
||||
auto xLeftTreeParentIter = m_xLeftTreeView->make_iterator();
|
||||
if (m_xLeftTreeView->get_selected(xLeftTreeParentIter.get()))
|
||||
{
|
||||
auto pParentSymbol
|
||||
= GetSymbolForIter(*xLeftTreeParentIter, *m_xLeftTreeView, m_aLeftTreeSymbolIndex);
|
||||
if (pParentSymbol)
|
||||
{
|
||||
// Try to build URL for parent
|
||||
OUString sParentUrl = BuildDoxygenUrl(*pParentSymbol);
|
||||
|
||||
if (sParentUrl.isEmpty())
|
||||
{
|
||||
auto xAncestorIter = m_xLeftTreeView->make_iterator(xLeftTreeParentIter.get());
|
||||
while (m_xLeftTreeView->iter_parent(*xAncestorIter))
|
||||
{
|
||||
auto pAncestorSymbol = GetSymbolForIter(*xAncestorIter, *m_xLeftTreeView,
|
||||
m_aLeftTreeSymbolIndex);
|
||||
if (pAncestorSymbol)
|
||||
{
|
||||
if (pAncestorSymbol->sQualifiedName.startsWith("ooo.vba"))
|
||||
{
|
||||
SAL_INFO("basctl", "OnRightTreeDoubleClick: Ancestor is VBA namespace, "
|
||||
"skipping documentation.");
|
||||
break;
|
||||
}
|
||||
OUString sAncestorUrl = BuildDoxygenUrl(*pAncestorSymbol);
|
||||
if (!sAncestorUrl.isEmpty())
|
||||
{
|
||||
SAL_INFO(
|
||||
"basctl",
|
||||
"OnRightTreeDoubleClick: Parent has no docs, opening ancestor '"
|
||||
<< pAncestorSymbol->sName << "'");
|
||||
OpenDoxygenDocumentation(this, *pAncestorSymbol);
|
||||
m_bPerformingAction = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
SAL_INFO("basctl", "OnRightTreeDoubleClick: No documentable ancestor found for '"
|
||||
<< pParentSymbol->sName << "'");
|
||||
}
|
||||
else
|
||||
{
|
||||
SAL_INFO("basctl",
|
||||
"OnRightTreeDoubleClick: Action is OpenDoxygenDocumentation for parent '"
|
||||
<< pParentSymbol->sName << "' (from left pane).");
|
||||
OpenDoxygenDocumentation(this, *pParentSymbol);
|
||||
m_bPerformingAction = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_bPerformingAction = false;
|
||||
SAL_WARN("basctl", "OnRightTreeDoubleClick: No action taken for symbol.");
|
||||
ShowDocsError(this, IDEResId(RID_STR_OB_NO_DOCUMENTATION));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
IMPL_LINK(ObjectBrowser, OnNodeExpand, const weld::TreeIter&, rParentIter, bool)
|
||||
{
|
||||
|
||||
@@ -77,6 +77,7 @@ private:
|
||||
// Core data
|
||||
std::unique_ptr<UnoApiHierarchy> m_pUnoHierarchy;
|
||||
SymbolInfoList m_aAllTopLevelNodes;
|
||||
std::map<OUString, GroupedSymbolInfoList> m_aMembersCache;
|
||||
|
||||
// State management
|
||||
bool m_bInitialized = false;
|
||||
|
||||
@@ -92,6 +92,10 @@ private:
|
||||
ObjectBrowserInitState m_eInitState = ObjectBrowserInitState::NotInitialized;
|
||||
bool m_bDataMayBeStale = true;
|
||||
OUString m_sLastActiveDocumentIdentifier;
|
||||
bool m_bPerformingAction = false; // Flag for Right Pane Double Click
|
||||
|
||||
// Helper Method for Double-Click Actions
|
||||
void NavigateToMacroSource(const IdeSymbolInfo& rSymbol);
|
||||
|
||||
// UI Widgets
|
||||
std::unique_ptr<weld::ComboBox> m_xScopeSelector;
|
||||
@@ -119,7 +123,9 @@ private:
|
||||
|
||||
// UI Event Handlers
|
||||
DECL_LINK(OnLeftTreeSelect, weld::TreeView&, void);
|
||||
DECL_STATIC_LINK(ObjectBrowser, OnRightTreeSelect, weld::TreeView&, void);
|
||||
DECL_LINK(OnRightTreeSelect, weld::TreeView&, void);
|
||||
DECL_LINK(OnRightNodeExpand, const weld::TreeIter&, bool);
|
||||
DECL_LINK(OnRightTreeDoubleClick, weld::TreeView&, bool);
|
||||
DECL_LINK(OnNodeExpand, const weld::TreeIter&, bool);
|
||||
DECL_LINK(OnScopeChanged, weld::ComboBox&, void);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user