Basic concepts

The helloworld.C example demonstrates several basic concepts used by LibCXX Widget Toolkit:

Application identifiers and version

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.

Widgets are reference-counted objects with smart pointers

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.

Containers own references to the widgets in the container

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.

Proper reference-counting is critical

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.

Connection threads and callbacks

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:

  1. helloworld.C uses connection_mcguffin to retrieve a mcguffin for the underlying X protocol connection.

  2. 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.

Callbacks

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:

  1. Use LibCXX's x::refs and x::objs 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.

  2. 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.

Special rules for captured references in callbacks

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 &amp;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::functions. 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.

Containers

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

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.

Layout managers

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.

Note

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.

Factories

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.

Note

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.

Exceptions

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.