GSoC 25: BASIC IDE - From PoCs to a Live UI Shell [WEEK 5]
Devansh Varshney
varshney.devansh614 at gmail.com
Fri Jun 27 18:07:20 UTC 2025
Hi everyone,
This past week has been a deep dive into the practical side of the SFX
framework, marking a significant pivot from data discovery to UI
implementation. The core directive from our mentor meetings was to build the
Object Browser shell first and populate it with live data, a decision driven
by the very promising performance results of our C++ PoCs.
Gerrit PoC Patch: https://gerrit.libreoffice.org/c/core/+/186475
<https://www.google.com/url?sa=E&q=https%3A%2F%2Fgerrit.libreoffice.org%2Fc%2Fcore%2F%2B%2F186475>
*The Strategic Pivot: Why Live Data First?*
Our C++ PoCs answered the critical question of whether live UNO API
introspection is fast enough for an interactive tool. The results were
conclusive: listing all services took only milliseconds, and introspecting
complex UNO types took mere microseconds.
This data gave the mentors confidence that a system built on runtime data
fetching is a viable starting point. They guided us to prioritize building
the live UI to get real-world performance metrics, deferring the creation
of a static offline cache to a later optimization stage if needed.
This data-driven decision process can be visualized as follows:
*<Initial Question: How to get UNO API data?>*
|
+---------------+------------------+
| |
v v+----------------+
+---------------------+| Static/Offline | |
Dynamic/Live || Approach | | Approach
|+----------------+ +---------------------+
| |
| unoidl-read PoC | C++ PoCs using
| | theCoreReflection
| |
v v* <Static Dump File>*
*<Fast Results: ~µs>*
|
v
+-------------------------+
| Mentor Feedback: |
| "This is fast enough. |
| Build the live UI." |
+-------------------------+
|
v
*---> Our Current Path: Live Data UI First <---*
*The Implementation Journey: A Chronicle of Learning*
Bringing a new UI component to life in LibreOffice was a fantastic lesson
in debugging the build system and understanding C++ best practices.
*Gerrit UI Patch:* https://gerrit.libreoffice.org/c/core/+/186822
*1. The .sdi Syntax Puzzle (Build System)*
*Problem:*
When I first tried to register the new command SID_BASICIDE_OBJECT_BROWSER,
my build failed with a cryptic svidl compiler error and a Not found
warning. This wasn't just a typo; it was a fundamental misunderstanding of
how LibreOffice's powerful SFX command framework is designed.
*Investigation & Learning: The Two-Tier Command Architecture*
To solve this, I looked into the history and structure of the SFX
(StarView Framework) dispatch system. By analyzing how existing commands
like SID_BASICIDE_WATCH were implemented (see Prof Lima’s patch
147305 <https://gerrit.libreoffice.org/c/core/+/147305>)
This system is inherited from *StarOffice* and was carried over into
*OpenOffice.org* and later *LibreOffice*.
https://wiki.openoffice.org/wiki/Framework/Article/Implementation_of_the_Dispatch_API_In_SFX2
It is also described in TDF's official documentation:
https://wiki.documentfoundation.org/Framework/Article/Implementation_of_the_Dispatch_API_In_SFX2
Even Andrew Pitonyak’s book points developers toward .sdi files when
working with commands:
http://www.pitonyak.org/OOME_3_0.pdf (Chapter 11, Page 256)
*Why This Design?*
The core challenge in StarOffice was supporting a large, modular,
scriptable office suite with many commands (.uno:Save, .uno:Copy, etc.).
Hardcoding every UI action would be unscalable and inflexible.
*Solution: Separate Global Identity from Local Handling*
-
*Global SID Registry* — defined in sfx2/sdi/sfx.sdi
-
Lists all command SIDs known system-wide
-
Makes them available for UI configuration (menus, toolbars)
-
Enables scripting/macro access via .uno: name
-
*Shell-Specific Mapping* — defined in basctl/sdi/baside.sdi
-
Binds that SID to methods like ExecuteGlobal() and GetState()
-
Enables context-sensitive behavior depending on the active component
-
Keeps each component’s logic self-contained
*Flowchart: From User Click to C++ Execution*
User Clicks Menu Item
┌─────────────────────────────────────┐
│ menubar.xml │ ← [UI XML / .ui file]
│ <menu:menuitem │
│ command=".uno:ObjectBrowser" /> │
└───────────────┬─────────────────────┘
│
▼
UI Command `.uno:ObjectBrowser`
┌────────────────────────────────────┐
│ GenericCommands.xcu │ ← [UI config]
│ Maps command → label, helptext │
│ and shows in customization UI │
└───────────────┬────────────────────┘
│
▼
SFX Dispatcher
┌───────────────────────────────────────────────────────┐
│ [Runtime Entry Point] │
│ Looks up ".uno:ObjectBrowser" in SID map │
│ │
│ Step 1: Check global registry │
│ → sfx2/sdi/sfx.sdi │
│ → Finds SID_BASICIDE_OBJECT_BROWSER │
│ + GroupId, MenuConfig, etc. │
│ │
│ Step 2: Determine active component shell │
│ → e.g. BasicIDEShell (basctl module) │
│ │
│ Step 3: Check shell mapping │
│ → basctl/sdi/baside.sdi │
│ → SID_BASICIDE_OBJECT_BROWSER → │
│ ExecMethod = ExecuteGlobal │
│ StateMethod = GetState │
│ │
│ Step 4: Call mapped C++ method │
│ → basctl/source/basicide/basides1.cxx │
│ → BasicIDEShell::ExecuteGlobal(...) │
└───────────────────────────────────────────────────────┘
*How It Works at Build Time *
LibreOffice uses a tool called svidl (Slot Interface Definition Language
compiler) to parse .sdi files. It generates .hdl headers containing
the command map, which is included in the build. This system allows
LibreOffice to keep UI and logic declarative, flexible, and modular.
You can explore the SFX2 build documentation here:
https://git.libreoffice.org/core/+/refs/heads/master/sfx2
*Correct Fix: Following the Two-Tier Pattern *
*Step 1: Global Registration* (in sfx2/sdi/sfx.sdi)
SfxVoidItem ObjectBrowser SID_BASICIDE_OBJECT_BROWSER
()
[
AutoUpdate = FALSE,
MenuConfig = TRUE,
ToolBoxConfig = TRUE,
AccelConfig = TRUE,
GroupId = SfxGroupId::Macro;
]
*Step 2: Local Shell Mapping* (in basctl/sdi/baside.sdi)
SID_BASICIDE_OBJECT_BROWSER
[
ExecMethod = ExecuteGlobal;
StateMethod = GetState;
]
* Runtime Flow Validation*User Clicks Menu Item
└─> menubar.xml calls .uno:ObjectBrowser
└─> SFX Dispatcher looks up SID
└─> Finds SID in sfx2/sdi/sfx.sdi
└─> Identifies shell: basctl_Shell
└─> basctl/sdi/baside.sdi mapping
└─> Calls ExecuteGlobal() in basides1.cxx
Takeaway
This investigation was a vital learning experience. The SDI/SFX system
is a powerful architectural relic from the StarOffice era that continues
to serve LibreOffice today. Understanding its separation of global vs.
local command handling is essential for anyone building or extending
LibreOffice UI functionality.
We now have a clean, working integration of the Object Browser as a first-
class .uno: command—correctly registered in both global and local layers.
References
-
DispatchCommands:
https://wiki.documentfoundation.org/Development/DispatchCommands
-
Dispatch API in SFX2 (TDF article):
https://wiki.documentfoundation.org/Framework/Article/Implementation_of_the_Dispatch_API_In_SFX2
-
OpenOffice.org discussion (2004):
https://www.openoffice.org/development/digest/2004_w08.html
-
Pitonyak, "OpenOffice.org Macros Explained":
http://www.pitonyak.org/OOME_3_0.pdf
-
SFX2 documentation:
https://git.libreoffice.org/core/+/refs/heads/master/sfx2/doc/sfx2doc.html
-
Gerrit patch showing Watch Window command structure (R. Lima):
https://gerrit.libreoffice.org/c/core/+/147305
*2. C++ Nuances & API Contracts (Compiler Errors)*
This is where the most valuable learning occurred. The loplugin CI checks
and C++'s strict typing were invaluable mentors.
*Problem:* const-Correctness in weld API.
The compiler rejected my OnNodeSelect handler when it was declared
with a const weld::TreeView&. *include/vcl/weld.hxx*
*Investigation:* I compared the weld API signatures for the two
signals we were using, connect_expanding and connect_selection_changed.
// Expects a Link that can handle a const iteratorvoid
connect_expanding(const Link<const TreeIter&, bool>& rLink);
// Expects a Link that can handle a NON-const TreeViewvoid
connect_selection_changed(const Link<TreeView&, void>& rLink);
*Learning:* The API's contract is the ultimate authority. The
connect_selection_changed method's signature requires a handler that
can work with a mutable TreeView&. Even if my code doesn't modify it,
I must conform to the API. This is a fundamental lesson in type safety
and API design.
*Problem:* Template Deduction Failure with std::make_shared.
The call std::make_shared<IdeSymbolInfo>(u"...") failed because the
template couldn't implicitly convert a string literal (const char16_t*)
to the constructor's const OUString& parameter.
*Learning:* Instead of forcing the conversion at the call site, the
more robust and idiomatic C++ solution is to make the class itself
smarter.
*Solution:* We enhanced the IdeSymbolInfo struct by adding a
"convenience constructor" that directly accepts the native literal type:
IdeSymbolInfo(const char16_t* pName, IdeSymbolKind eTheKind)
: sName(OUString(pName)), eKind(eTheKind) {}
This encapsulates the conversion, keeps the calling code clean, and
resolves the template ambiguity.
*Current Status: A Working but Empty UI Shell*
The result of this work is a visible, integrated Object Browser window.
However, it is currently empty, and the UI has some predictable glitches
due to the lack of data.
[Image of the Object Browser window in the IDE]
https://bug-attachments.documentfoundation.org/attachment.cgi?id=201520
The UI appears in the "View" menu and can be toggled.
The ObjectBrowser (View) successfully calls the IdeDataProvider (Model)
on initialization to fetch and display the top-level nodes.
The "vanishing arrow" happens because the OnNodeExpand handler is still
a
stub. This will be resolved when we populate it with child nodes.
The "two-click" focus issue is a minor polishing item we will address
as
we build out the interaction logic.
*Next Steps: Populating the UI*
With the shell complete, the immediate next task is to implement the data
connection, which will also resolve the current UI glitches.
*Implement Live Data Fetching:* The immediate next step is to replace
the
placeholder logic in IdeDataProvider.cxx. I will port the code from my
successful C++ PoCs to fetch live data from theCoreReflection.
*Enable Lazy Loading:* The OnNodeExpand handler will be implemented to
call the data provider and populate the child nodes on demand. This will
make the UI interactive and fix the "vanishing arrow" issue.
*Measure Performance:* Every live data call will be wrapped in our
IdeTimer utility to gather the real-world performance data the mentors
requested.
This phased approach ensures we have a solid foundation. I am now focused
on making the browser functional and data-rich.
Thank you for the guidance that helped me navigate these technical
challenges.
*Week 1 mail -*
https://lists.freedesktop.org/archives/libreoffice/2025-May/093264.html
*Week 2 and 3 mail -*
https://lists.freedesktop.org/archives/libreoffice/2025-June/093362.html
*Week 4 mail(Thread) -*
https://lists.freedesktop.org/archives/libreoffice/2025-June/093392.html
--
*Regards,*
*Devansh*
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.freedesktop.org/archives/libreoffice/attachments/20250627/8d7ef883/attachment-0001.htm>
More information about the LibreOffice
mailing list