VCL weld: create LibreOffice GUI from design files
LibreOffice uses VCL (Visual Class Library) as its internal widget toolkit to create the graphical user interface (GUI) of LibreOffice. Here I discuss how to use UI files designed with Glade interface designer to create LibreOffice user interfaces with a framework called weld, which is part of LibreOffice core source code.
Creating a Minimal VCL Weld Application
In my previous blog post, you can find out about the structure of a minimal VCL application. Please refer to the below blog post to see how a Window is created in VCL, and how it can be used as a test workbench called minvcl
. You can run it with ./bin/run minvcl
after you build LibreOffice.
Here I discuss how to go further, and create user interface with Glade interface designer, and do most of the things without writing code.
VCL Weld Mechanism
In order to simplify user interface creation in LibreOffice, experienced LibreOffice developer, Caolán, has introduced a mechanism to load UI files created with Glade interface designer, and use them as if they are UI files for each and every GUI framework that LibreOffice supports: from GTK itself to Qt, Windows, macOS and even so-called gen backend that only needs X library on Linux.
To illustrate how the VCL weld mechanism works, I have added a minimal example, minweld
, as a test workbench. The structure of the code is very similar to the previous example, minvcl
, but there are some changes in the code. In the new code, UI is created from the .ui
file that is designed visually with Glade interface designer. The .ui
file is an XML file which contains placement of widgets that should be displayed on the screen.
The complete code for minweld
is available in the LibreOffice core source code repository, which can also be viewed online:
Glade UI File
In minweld
, I have used an existing Glade UI file, tipofthedaydialog.ui
. This is the user interface for displaying a tip of the day in LibreOffice at startup. Heiko, the TDF design mentor, has discussed this dialog box in detail before:
But, you can assume that it is a simple .ui
file, that one can create with Glade. Here, we use it to create our own user interface in C++. You may use any other .ui
file that you have created with almost the same code.
This UI file is found in cui/uiconfig/ui/tipofthedaydialog.ui
, and minweld
loads it. This is how it looks when you open it in Glade interface designer:
Let’s look into the specifics of minweld.cxx
.
Header Includes
Headers are almost the same, but here we use vcl/weld.hxx
instead of vcl/wrkwin.hxx
. Therefore, you can see this line in the code:
#include <vcl/weld.hxx>
Then we have the C++ code for the application. The TipOfTheDayDialog
class is defined with:
class TipOfTheDayDialog : public weld::GenericDialogController { public: TipOfTheDayDialog(weld::Window* pParent = nullptr); DECL_LINK(OnNextClick, weld::Button&, void); private: std::unique_ptr<weld::Label> m_pTextLabel; std::unique_ptr<weld::Button> m_pNextButton; sal_Int32 m_nCounter = 0; }; ... }
As you can see, the TipOfTheDayDialog
inherits from weld::GenericDialogController
, and not Application
class as before. Also, TipOfTheDayDialog
constructor receives a parent of type weld::Window*
, which is nullptr
now. The reason is that there is no parent window in this example. Using weld::
prefix is also done for other types of widgets that we use in LibreOffice. for example, we use weld::Button
to denote a push button in LibreOffice, or in any application that is created with the vcl::weld
mechanism.
Class Constructor
This is the code for the TipOfTheDayDialog
constructor. Here, we initialize two member variables, m_pTextLabel
and m_pNextButton
which point to a label and a button, respectively. We will interact with these two in our code. There are string literals like lbText
and btnNext
, which are the IDs of those widgets in Glade. The IDs should be unique for linking to specific variables in the code.
TipOfTheDayDialog::TipOfTheDayDialog(weld::Window* pParent) : weld::GenericDialogController(pParent, u"cui/ui/tipofthedaydialog.ui"_ustr, u"TipOfTheDayDialog"_ustr) , m_pTextLabel(m_xBuilder->weld_label(u"lbText"_ustr)) , m_pNextButton(m_xBuilder->weld_button(u"btnNext"_ustr)) { m_pNextButton->connect_clicked(LINK(this, TipOfTheDayDialog, OnNextClick)); }
One last step is linking the events with functions in the code. You may do that with the LINK
macro. In the last line, connect_clicked
activates OnNextClick
from the class TipOfTheDayDialog
, whenever m_pNextButton
is clicked.
Event Handler
This is the implementation of the event handler. It should be started with IMPL_LINK macro, in the form of IMPL_LINK_NOARG(Class, Member, ArgType, RetType)
. The code is straightforward: It increases a counter which is initially zero, and displays it alongside a text:
IMPL_LINK_NOARG(TipOfTheDayDialog, OnNextClick, weld::Button&, void) { ++m_nCounter; m_pTextLabel->set_label(u"Here you will see tip of the day #"_ustr + OUString::number(m_nCounter) + "."); }
With a call to set_label
function, m_pTextLabel is updated every time that you click on “Next Tip” button.
Running the Example
You may run the example after you have built LibreOffice from sources. Then, you may simply invoke:
./bin/run minweld
The result is a little bit different from the tipoftheday dialog in LibreOffice, as it does not use a picture. But, it has a nice feature: if you click on “Next Tip”, it will show a text and a counter that goes up whenever you click on it again.
Final Notes
You may look into the original “tip of the day” dialog box in cui/source/dialogs/tipofthedaydlg.cxx
, which is more complex than the one that we created here, as it reads some data from the configuration and uses images. But, the idea is the same. Inherit a class from GenericDialogController
, define and link variables to the widgets with their IDs, add event handlers. Now, the application with VCL graphical user interface is ready to use!
This is somehow similar to the way one creates dialog boxes with Qt and other widget toolkits. On the other hand, the VCL weld mechanism is different in the way that it uses such a toolkit to create UI on the fly. Therefore, if you choose a desired VCL UI plugin, then it will use that specific library for creating user interface. For example, you can run minweld
example with Qt this way:
export SAL_USE_VCLPLUGIN=qt5 ./bin/run minweld
You may also run it with GTK3 UI, this way:
export SAL_USE_VCLPLUGIN=gtk3 export GTK_THEME=Adwaita:light # For light/dark theme ./bin/run minweld
I hope that this explanation was helpful for you to understand the basics of GUI design and implementation in LibreOffice. You can try doing small improvements in LibreOffice GUI by looking into the EasyHacks that with the tag “Design“:
TDF Wiki: EasyHacks categorized by “Design” as the required skill
We welcome your code submissions to improve LibreOffice. If you would like to start contributing to LibreOffice, please take a look at our video tutorial: