Basic concepts

The helloworld.C example demonstrates several basic concepts used by LibCXX Windows Library:

Display elements 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 which works in a similar way (but has additional features). This documentation uses the term reference to refer to LibCXX's x::ref 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 display element gets destroyed and removed.

Containers own references to the elements in the container

x::w::main_window is a container, a display element that contains other display elements. They can be individual display elements like buttons and input fields, or they can be other containers with their own display elements. Each container is, itself, a display element.

Naturally, containers hold references on the elements in the container. Removing an element from the container gets rid of the container's reference on the element. Normally, no other references remain and all resources used by the display element are released.

The application creates all display elements, and it's possible for the application to retain its own reference to the display element. The application must make sure that it no longer holds its own reference on a display element when it gets removed from the container; or when any of its parent containers get removed from their respective container.

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 display element 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 display element object, the label display element 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.

Connection threads and callbacks

LibCXX Windows Library starts an internal execution thread that manages the connection to the X server. Unlike other X toolkits, LibCXX Windows Library does not force the application into an event-driven design pattern. LibCXX Windows Library 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 Windows Library'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 30, 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. Until the callback returns, the internal connection thread will be unable to process any additional X protocol messages. 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 elements in the container”, containers own references to all elements in the container. It is possible to attach additional application data to any display element, including containers; and the section called “Attaching application data to display elements” explains that the attached application data cannot have references to any parent (of the display element the data is attached to). This results in a circular reference because the parent has its own direct or indirect reference to the element in the container; and LibCXX Windows Library uses reference-counted objects for all display elements, 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 element results in an internal circular reference. Capturing a reference to an element not in the parent or the child element hierarchy is fine.

There are several ways to correctly capture references to other display elements for use in callbacks. One way is to use weak captures, see the section called “Callbacks and captures” in Chapter 12, 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::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 element, it has full access to all display elements, from the passed-in main window element; 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 display element goes out of scope and gets destroyed.

An example of this message-based design found is given in Chapter 9, Disabling input processing.

Containers

x::w::main_window is an example of a container. A container is a display element which contains other display elements, and a layout manager that controls them. helloworld.C creates a x::w::main_window that has a label element.

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 display element, 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 display element as its parameter. This is the same object that's going returned from the function or a method that's creating it. 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 the 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.

Layout managers

A container is a display element that contains other display elements. The main application window is a container. Each container has a layout manager. The layout manager arranges the display elements 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 display elements in a rectangular grid, or a table, with optional borders.

x::w::menubarlayoutmanager

Each display element 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 display elements, 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 the base class for combo-boxes.

The list layout manager is designed for handling relative large numbers of list entries far more efficiently than if each one was an official label display element. The grid layout manager, for example, needs to figure out how to position each display element 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 quite a bit 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::standard_comboboxlayoutmanager and x::w::editable_comboboxlayoutmanager

These layout manager 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 display element that represents the current selection in the combo-box.

x::w::pagelayoutmanager

Treats its display elements as virtual pages, and showing one page at a time. Opening a different page element replaces the current page's display element with the new element.

All elements in this container technically exist, all the time, but only one of the elements is visible at any given time. Each one of page layout manager's display elements is typically a container of its own, with a collection of individual display elements. In this manner, each collection of display elements 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 elements in this container get arranged into a single row or column. Each element is a resizable pane with draggable sliders between the panes dividing the row or column between the panes.

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.

Note

The layout manager object returned from get_layoutmanager() holds several internal locks. Like all other LibCXX Windows Library 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. The internal locks 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 thread from accessing the elements 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 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 display elements. 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 display element 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 element'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 display elements; 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 Windows Library get reported by throwing an x::exception LibCXX object. helloworld.C shows the basic approach of catching the exception, and reporting it.