The helloworld.C
example demonstrates several basic concepts used by LibCXX Widget Toolkit:
Running
helloworld.C
again should open its window in the same position with most
window managers. This uses LibCXX's
application identifier-based
configuration directory.
LibCXX Widget Toolkit automatically saves window positions in the
windows
file in the application's
configuration directory.
$ [command] --set-property x::w::preserve_screen_number=0 #include <x/w/screen_positions.H> x::w::preserve_screen_number(false);
On display servers with multiple screens the main windows get opened
on the same screen as before. This flag can be turned off by
calling x::w::preserve_screen_number
or setting
an application property of the same name.
helloworld.C
creates an
x::w::main_window
,
an object that represents the main application window.
x::w::main_window
is a smart pointer.
LibCXXW does not use C++ library's
std::shared_ptr
,
but LibCXX's own
x::ref
(non-nullptr
smart pointer),
x::const_ref
(a ref to a constant object),
x::ptr
(a nullable smart pointer),
and
x::const_ptr
(a constant ptr)
,
which work in similar ways (but have additional features).
This documentation uses the term “reference” to
refer to LibCXX's x::ref
family
of smart
pointers.
The application does not need to take an explicit step to destroy
the main window, or any other display object. When its
x::ref
goes out of scope and gets destroyed
the widget gets destroyed and removed.
x::w::main_window
is a
container,
a widget that contains other widgets.
All widgets are either individual elements, like buttons and input
fields, or they are containers with their own widgets; and
each container is, itself, a widget.
Naturally, containers hold references on the widgets in the container. Removing an widget from the container gets rid of the container's reference on the widget. Normally, no other references remain and all resources used by the widget get released.
The application creates all widgets. Each factory method that creates a widget returns a reference to the new widget. The application should generally avoid storing references to widgets in static or dynamic objects, the safest thing to do is to hold it in a local variable, in automatic scope, so that the reference gets destroyed at the end of the scope. This is because it is critical that no other references to a widget remain when it gets removed from its container; or when any of its parent containers get removed from their respective container. Otherwise the LibCXXW will not be able to properly clean up all resources, and handle its internal book-keeping.
Since x::w::main_window
is a top-level
application window, nothing else holds a reference to it.
helloworld.C
creates an x::w::main_window
in its
testlabel
() function. There are no other
references to it, and when it returns the last reference to the
main window goes out of scope, it gets destroyed and removed from
the display.
helloworld.C
's
create_label
() creates a
label
widget in the main window, but
testlabel
() does not store this reference
itself, leaving the only reference to the label in its
main window container. When testlabel
()
returns, the last reference to the main window goes away and
the underlying object gets destroyed. Since that object held
the only reference to the label widget object, the
label widget object gets destroyed as well.
In this manner, removing a container recursively removes and destroyes everything in the container, recursively. However if the application retained its own reference to the label object, this would prevent everything from getting destroyed properly, and all resources released.
Attaching a reference to a widget in some way (via a callback) is generally safe as long as the reference is to a widget that's not an immediate parent or child of the widget to which the reference gets attached to. If necessary, it is safe to store or capture weak pointer references and recover strong references when needed.
LibCXX Widget Toolkit starts an internal execution thread that manages the connection to the X server. Unlike other X toolkits, LibCXX Widget Toolkit does not force the application into an event-driven design pattern. LibCXX Widget Toolkit takes care of automatically starting the internal execution thread, and stopping it after the last top level window object gets destroyed.
In
helloworld.C
,
returning from testlabel
() destroys the
x::w::main_window
, which stops
the execution thread. However the execution thread does not get
stopped immediately, and it continues to perform some final
cleanup duties, while main
() wraps up its
own responsibilities.
helloworld.C
shows an example of waiting until the connection thread terminates
and the connection to the X server gets closed. This uses
various classes from the LibCXX parent library:
helloworld.C
uses connection_mcguffin
to retrieve
a mcguffin for the underlying
X protocol connection.
An x::destroy_callback::base::guard
object, from the base LibCXX library,
gets declared before the top level window. A such
the guard object gets destroyed last.
The guard object takes the mcguffin, and the guard object's
destructor waits until the mcguffin gets destroyed,
indicating that the X protocol connection object was destroyed
and the execution thread stopped.
In this manner, while destroying the guard object, when
returning from testlabel
(), the main
execution thread stops and waits for LibCXX Widget Toolkit's internal
execution thread to finish up, and disconnect from the
X server.
A callback is a lambda or a callable object that LibCXXW executes in response to an event of some kind.
helloworld.C
,
installs two callbacks:
the on_disconnect
() lambda gets invoked if
the connection to the X server was unexpectedly disconnected; and
x::w::main_window
's
on_delete
() lambda gets invoked when the
main window's “Close” button gets clicked.
on_delete
() callback's first parameter,
“ONLY IN_THREAD
”.
indicates it gets invoked from the connection-thread,
as explained in Chapter 46, Connection threads and callbacks.
Although on_disconnect
()'s callback also
gets invoked from the connection-thread its invocation indicates
that the connection-thread is no longer functional, so it does
not get the
“ONLY IN_THREAD
” parameter.
In all cases, callbacks, in general, should not engage in lengthy, time-consuming tasks. The internal connection thread does not process any additional X protocol messages until the callback returns. Callbacks should be limited to simple tasks, such as sending messages to the main application thread, to execute the actual task at hand.
To effectively do this in a thread safe manner, the recommended approach is:
Use LibCXX's x::ref
s and
x::obj
s to construct
reference-counted, thread-safe objects. See LibCXX's
documentation for more
information.
helloworld.C
,
and other example programs,
implements on_delete
()
using a simple
close_flag
object, containing a bool
flag,
a mutex, and a condition variable.
Capture the shared flag objects by value.
helloworld.C
's
on_delete
()
lambda callback captures the close_flag
by value.
Clicking the window's close button results in the lambda acquiring
the mutex, setting the flag, and signaling the condition
variable.
The main execution thread only needs to acquire
close_flag
's mutex, and wait on the
condition variable until the flag gets set.
The underlying LibCXX's smart pointers take care of destroying
the close_flag
by themselves.
As explained in the section called “Containers own references to the widgets in the container”, containers own references to all widgets in the container. It is possible to attach additional application data to any widget, including containers; and the section called “Attaching application data to widgets” explains that the attached application data cannot have references to any parent (of the widget the data is attached to). This results in a circular reference because the parent has its own direct or indirect reference to the widget in the container; and LibCXX Widget Toolkit uses reference-counted objects for all widgets, which get destroyed automatically, but only when the last reference to each object goes out of scope.
References captured by lambdas that are used as callbacks have more restrictions. Capturing either a parent or a child widget results in an internal circular reference. Capturing a reference to an widget not in the parent or the child widget hierarchy is fine.
There are several ways to correctly capture references to other widgets for use in callbacks. One way is to use weak captures, see the section called “Callbacks and captures” in Chapter 15, The grid layout manager for an example. Another design pattern is for callbacks to capture a message queue-like object, and use the message queue to send a message to the main execution thread:
main_window->on_delete([queue] (ONLY IN_THREAD, const x::w::busy &ignore) { queue->message([] (const x::w::main_window &main_window, const appdata_t &appdata) { appdata->close_flag=true; }) });
In this example the captured queue
is
a simple x::ref
to a thread-safe
queue-like object, perhaps a std::deque
of
std::function
s.
The messages take the form of type-erased
std::function
lambdas. The main execution
thread runs a loop that waits for and executes application
messages:
while (!appdata->close_flag) appdata->queue->next_message(main_window, appdata);
The thread-safe queue is a member of the
application data object, and contains no other references.
next_message
() waits for the next message
and invokes the function, passing to it the main window object,
and the application data object. The application data object can
simply be attached as the main window's
appdata
and the executed lambda could
fetch it, itself. This simply saves a step.
No rules are broken here. The callback lambda only captures a
reference to the message queue. It does not capture a reference to
anything else. When executed, the main window object and the
application data object get passed to it, as parameters.
The message lambdas always get invoked by the main execution thread,
so no locking is required for setting the
close_flag
which only the main execution thread
checks, as well. And even though the callback cannot capture
references to its parent or child widget, it has full access to
all widgets, from the passed-in main window widget;
or from any references in the application data object.
The only thing that the main execution thread needs to be careful with is to make sure that its own reference to the application data object goes out of scope before the main window widget goes out of scope and gets destroyed.
An example of this message-based design found is given in Chapter 12, Disabling input processing.
x::w::main_window
is an example of a
container.
A container is a widget which contains other widgets. Each container has a layout manager that controls the
position and the behavior of the elemnts in the container.
helloworld.C
creates a
x::w::main_window
that has a
label widget.
x::w::main_window
's
create
() static method returns a new
x::w::main_window
.
Other containers get created in various other ways.
Creator lambdas initialize the contents of a new widget,
including containers. For containers,
a lambda gets passed as a parameter to a function or a method
that creates a new container, like a
factory's
create_container
() or
create_focusable_container
().
A creator also gets passed to a
x::w::main_window
's
create
(). In general, a creator
lambda gets called just before the function or the method
returns the new container:
auto main_window=x::w::main_window ::create([] (const x::w::main_window &main_window) { // ... x::w::container c=factory->create_container( [] (const x::w::container &c) { // ...
The creator lambda gets the new widget as its parameter. This is the same object that's going to be returned from the function or a method that's creating the container. The creator lambda gets invoked just before that function or method returns.
The purpose of the creator lambda is to initialize the new container.
helloworld.C
initializes the main window, in the creator, by adding a new label
to it.
Observe that
helloworld.C
's
window gets automatically sized for its label.
The creator lambda is an optimization.
The size of a new container gets computed after the creator lambda
returns, but before the newly-created container itself gets returned
to the application.
helloworld.C
could do nothing in its creator lambda, and then add the label to
the
x::w::main_window
itself.
However, this will be less efficient. Initially the main window's
size gets calculated as empty, there's nothing in it. After the
label gets added the size of the main window must be recalculated
again.
Populating the container in its creator lambda avoids doing this extra recalculation, and is more efficient.
There are some isolated exceptions.
The peephole layout manager
does not use creator lambdas. Peepholes always contain a single
widget in the peephole (but the widget itself can
be a container with many other widgets).
The peepholed's creator gets specified separately, as a constructor
to the new layout manager specification parameter.
However, an empty creator lambda must still be provided, to
create_container
() or
create_focusable_container
(), which typically
does nothing; but it can be repurposed for some other use.
A container is a widget that contains other widgets.
The main application window is a container.
Each container has a “layout manager”. The layout
manager arranges the widgets in the container.
A container's get_layoutmanager
() method
returns a
x::w::layoutmanager
,
which is just a reference to a base layout manager class.
There are several layout managers that get derived from an
x::w::layoutmanager
's base class:
x::w::gridlayoutmanager
The default, generic layout manager that positions its widgets in a rectangular grid, or a table, with optional borders.
x::w::borderlayoutmanager
This layout manager draws a border around a single widget, the only widget in this layout manager's container.
This layout manager is optimized for this purpose. The grid layout manager is capable of drawing borders around the widgets in the rectangular grid too, and managing multiple widgets. The grid layout manager's complexity results in non-trivial overhead, and when all that's needed is a simple border around an widget, the border layout manager does the job faster and with less cost.
The border layout manager offers a value-added feature of adding title text to the border.
x::w::menubarlayoutmanager
Each widget in this container represents a title in the main application window's menu bar.
x::w::listlayoutmanager
A crafty impostor that is not a really true layout manager. It doesn't contain discrete widgets, but plain text strings. The list layout manager is available for selection lists, and it also gets called into duty for pop-up menus, and serves as a base class for combo-boxes.
The list layout manager is designed for handling relatively large numbers of list entries far more efficiently than if each one was an official label widget. The grid layout manager, for example, needs to figure out how to position each widget according to how many columns and rows it spans and figure out how to draw each cell's borders, if it has any. It's a lot of work. The list layout manager, on the other hand, has a single list of labels to draw vertically, a much simpler task.
x::w::tablelayoutmanager
A list with a header row. The table layout manager offers the optional ability to interactively adjust the relative widths of the columns in the table by moving the border between the columns.
x::w::standard_comboboxlayoutmanager
and x::w::editable_comboboxlayoutmanager
These layout managers leverage the list layout manager to quickly deal with the list of choices shown in the combo-box's pop-up, and also deal with the widget that represents the current selection in the combo-box.
x::w::itemlayoutmanager
Arranges its widgets horizontally and draws them as individual buttons. The item layout manager typically gets used together with an input field to implement a modern UI design pattern which provides for a free-form text entry field.
The typed-in text gets parsed into one or more discrete items, of some kind. A container with the item layout manager gets placed above or below the text input field. A widget, usually a simple label gets created and added to the item layout manager's container. The item layout manager draws its widgets with a border, like buttons.
In this manner, the text input field provides the means for entering a list of items of some kind, that appear above or below the text input field. The item layout manager places small “X” inside each item button, with the expectation that clicking on it removes the existing item from the list (the specific action that gets taken gets controlled by an installed callback).
x::w::pagelayoutmanager
Treats its widgets as virtual “pages”, showing one page at a time. “Opening” a different page widget replaces the current page's widget with the new widget.
All widgets in this container technically exist, all the time, but only one of the widgets is visible at any given time. Each one of page layout manager's widgets is typically a container of its own, with a collection of individual widgets. In this manner, each collection of widgets forms a virtual page, and the layout manager's methods make one of its pages visible.
x::w::booklayoutmanager
The book layout manager extends the page layout manager by supplying clickable buttons for each page, that get drawn as tabs in a horizontal strip above the paged container. Clicking on each button tab opens the corresponding page (in the book).
x::w::panelayoutmanager
All widgets in this container get arranged into a single row or column. Each widget is a resizable “pane” with draggable sliders between the panes dividing the row or column between the panes.
x::w::toolboxlayoutmanager
The toolbox layout manager arranges the widgets in a resizable matrix. This layout manager typically gets used to implement a separate dialog with “toolbox icons”.
The actual layout manager gets specified when creating the container,
and the return value of get_layoutmanager
()
gets converted accordingly.
helloworld.C
explicitly specifies the grid layout manager for its main window, so
its get_layoutmanager
() returns a
x::w::gridlayoutmanager
.
For convenience, methods named after each layout manager invoke
get_layoutmanager
() and convert the result
to the appropriate layout manager type.
gridlayout
() calls
get_layoutmanager
() and
returns a x::w::gridlayoutmanager
;
borderlayout
() calls
get_layoutmanager
() and
returns a x::w::borderlayoutmanager
;
and so on. An exception gets thrown if the container uses a different
layout manager.
The layout manager object returned from
get_layoutmanager
() holds several
internal locks.
Like all other LibCXX Widget Toolkit objects, this is a
reference-counted object.
The internal locks get released
when the application finishes using the layout manager, and the
last reference to the underlying object goes out of scope and
it gets destroyed.
For this reason, the application should not store, or stash away, the layout manager object, but use it only long enough to effect changes to the container, or examine its contents. As a rule of thumb, the layout manager should be a variable in automatic scope.
Layout managers' internal locks are likely to block the connection thread, until the lock gets released, and all changes made to the container, through the layout manager, take effect. Depending on the layout manager the internal locks may also block other execution threads from accessing the widgets in the container.
Retrieving the layout manager, and acquiring any required locks is not cheap, and the application should optimize its access to the layout manager. For the reasons explained here, the application should not store or stash away the layout manager, persistently; but it should not retrieve it over and over again, to make each individual change to the container. The optimal use pattern is to retrieve the layout manager and make use of it to effect all changes to the container, at once.
x::w::label l=f->create_label("Hello world");
An x::w::factory
creates new widgets. The factory comes from the layout manager,
and the process for obtaining a factory is specific to each layout
manager (except for the bare list layout manager, which is an impostor).
helloworld.C
calls its grid layout manager's
append_row
() method, which returns a
factory that places the new widget in a new row in the grid.
Some layout managers provide factories with additional functionality.
Grid layout manager's
methods return an
x::w::gridfactory
with additional methods that set each new widget's borders, padding
and alignment.
Factories are products of layout managers, and maintain an internal reference on their layout manager. Everything that's been said about layout managers apply to factories as well: the factory object should be used only long enough to manufacture new widgets; then they should go out of scope and get destroyed, releasing their internal reference to their layout manager, and releasing the indirect reference on their container, unblocking it.
Fatal errors in the LibCXX Widget Toolkit get reported by throwing an
x::exception
LibCXX object.
helloworld.C
shows the basic approach of catching the exception, and reporting it.