Containers

You compose an Inti::Gtk user interface by nesting widgets inside container widgets. Container widgets can go inside other container widgets. So, for example, you might have a Gtk::Window widget containing a Gtk::Button widget containing a Gtk::Label widget, as in the Hello, World examples. If you wanted an image instead of text inside your button, you might replace the Gtk::Label widget with a Gtk::Image widget.

There are two major kinds of container widgets in GTK+. Both are subclasses of the abstract Gtk::Container base class.

The first type of container widget always derives from Gtk::Bin; Gtk::Bin represents a container with a single child widget. These containers add some kind of functionality to the child. For example, Gtk::Button makes its child into a clickable button; Gtk::Frame draws a relieved frame around the child. Gtk::Window places a top-level window around the child widget.

The second type of container can have more than one child; its purpose is to manage layout. "Manage layout" means that these containers assign sizes and positions to the widgets they contain. For example, Gtk::VBox arranges its children in a vertical column, and Gtk::Table arranges children in a two-dimensional grid.

The rest of this section is about layout containers, and how to use them. In general, the goal of your layout is to create an attractive user interface that will still be attractive regardless of the user's native language, screen size, window size, fonts, and so forth. Your application should automatically adapt if these factors change.

Size Negotiation

To understand layout containers, you first have to understand how GTK+ widgets negotiate their size. It's quite simple, really. There are only two concepts: requisition and allocation. These correspond to the two phases of widget layout.

Size Requisition

A size requisition is a widget's desired width and height. This is represented by a Gtk::Requisition object, containing integer values for width and height.

Different widgets choose what size to request in different ways. Gtk::Label, for example, requests enough space to display all the text in the label. Most container widgets base their size request on their children's requisitions; for example, if you place several buttons in a box, the box will ask to be large enough to hold all the buttons.

The first phase of widget layout starts with a top-level widget (a Gtk::Window). Gtk::Window asks its child widget for a size request. That child might ask its own children, and so on recursively. When all child widgets have been queried, Gtk::Window will finally get a Gtk::Requisition back from its child. Depending on how it was configured, Gtk::Window may or may not be able to expand to accomodate the size request (if the window's size was hard-coded by the programmer, the window will have to ignore the child's size request rather than adjusting its size to match).

Size Allocation

Phase two of layout begins at this point, after the toplevel Gtk::Window has determined how much space its child would like to have. Gtk::Window now has to decide how much space is actually available and communicate that information to the child. This process is known as size allocation. Under normal circumstances, Gtk::Window will always give its child the amount of space the child requested.

A child's size allocation is represented by a Gtk::Allocation object. Gtk::Allocation is a subclass of Gdk::Rectangle, and includes not only a width and height, but also a position (i.e. X and Y coordinates). This is the key to the Inti::Gtk layout system. Containers with more than one child divide the size allocation they receive into sub-regions, and assign a sub-region to each of their children.

Widgets are required to honor the size allocation they receive; a size request is only a request, and widgets must be able to cope with any size.

Given the layout process, it's easy to see the overall role containers play. Their job is to assemble each child's requisition into a single requisition to be passed up the widget tree, and then to divide the allocation they receive between their children. Exactly how this happens tdepends on the particular container.

Gtk::Box

The Gtk::Box interface defines the most common, and simplest, layout container. It has two concrete subclasses: Gtk::VBox manages a column of child widgets, and Gtk::HBox manages a row of children. For HBox, all children are assigned the same height; the box distributes available width between them. VBox is identical, but distributes height instead of width. Either box can optionally leave gaps (spacing) between widgets.

A Box has two important attributes: homogeneity and spacing. A homogeneous box will assign the same amount of space to all children. Spacing is a gap to leave between each child. By default, boxes are not homogeneous and have 0 pixels of spacing. There's also a constructor that allows you to specify these attributes, and accessor functions to get and set them.

Children can be placed in the start or the end of the box. The start of a box is the left or top side; the end is the right or bottom side. To add children to the start of a box, you use the Box::pack_start() method. To add to the end of a box, you use Box::pack_end().

pack_start() and pack_end() accept three arguments which affect the layout of the child being added to the box. The expand parameter determines whether the child will get space beyond its size request, in the event that the box receives a size allocation larger than its size request. The fill parameter only matters if expand was true; it determines whether the child widget itself fills the extra space (if fill is false, the box will add padding around the child to fill the extra space). The final argument to pack_start() and pack_end() is a padding value; padding is extra space added to either side of a child widget.

Concretely, here's how you might create and fill a box container:
 Box * box = new VBox; // default to not homogeneous, 0 spacing
 
 box->pack_start (new Button ("Button 1"),
                  true,  // extra space can be added to this child
                  true,  // fill extra space by expanding the child widget
                  5);    // 5 pixels padding on either side of child

 box->pack_end   (new Button ("Button 2"),
                  false, // no extra space can be added to this child
                  false, // this argument is irrelevant since expand=false
                  0);    // 0 pixels of padding around this child
The above example would result in a vertical box; "Button 1" would be in the top of the box, and "Button 2" would be in the bottom. "Button 1" has 5 pixels of space above and below it. Resizing the window containing the box would increase the size of "Button 1", but "Button 2" would be unaffected.

Here's a different example, with a homogeneous box:
 Box * box = new VBox (true, 10); // homogeneous=true, spacing=10
 
 box->pack_start (new Button ("Button 1"),
                  false, // irrelevant for homogeneous boxes
                  true,  // child fills its allocated space
                  0);    // no padding

 box->pack_start (new Button ("Button 2"),
                  false, // irrelevant for homogeneous boxes
                  false, // child doesn't fill its space
                  0);    // no padding
In this example, the box would be divided in equal halves; "Button 1" would take up the entire first half, and "Button 2" would be centered in the second half. "Button 2" doesn't fill its half because the fill argument to pack_start() was false.

Box-packing patterns

The variety of ways to pack children in boxes can be confusing, but there are really only five basic patterns of box-packing.